Skip to content

Commit

Permalink
Using GraphQL Shield for permissions. Note that there is a bug in the…
Browse files Browse the repository at this point in the history
… dependency graphql-middleware that requires version 6.0.10 (maticzav/graphql-middleware#433)
  • Loading branch information
NoisyFlowers committed Sep 17, 2021
1 parent 54849a4 commit a644076
Show file tree
Hide file tree
Showing 4 changed files with 186 additions and 35 deletions.
138 changes: 103 additions & 35 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import jwt from 'jsonwebtoken';
import ejwt from 'express-jwt';
import unless from 'express-unless';

import { applyMiddleware } from "graphql-middleware";

import permissions from './permissions.js';

const getScope = (token) => {
console.log("getScope");
console.log(token);
Expand All @@ -23,21 +27,59 @@ const getScope = (token) => {
const app = express()

app.use(express.json());
app.post('/login',
(req, res) => {
if (!req.body.username || !req.body.password) {
res.status(400).send({
code: 400,
msg: "Please pass username and password",
});
} else {
const token = jwt.sign({
username: req.body.username
}, 'secret', { expiresIn: '1h' });
res.json({ token: token }); //TODO: error handling and reporting through API

const getUser = async (driver, token) => {
console.log("getUser");
console.log(driver);
console.log(token);
if (token === '') return {surname: 'dummy'};
const decodedToken = jwt.verify(token.split(' ')[1], "secret");
console.log(decodedToken);

const session = driver.session();
//The replace razzle-dazzle below is just to get a list of role strings rather than objects.
return session.run(`
MATCH
(person:Person {email : $email})-[:HAS_ROLE]->(role:Role)
RETURN
person{
.given,
.surname,
.email,
roles:collect(
replace(
replace(
apoc.convert.toString(role{.name}),
"{name=",
""
),
"}",
""
)
)
}
`, {
email: decodedToken.username//'douglasm@arizona.edu'
}
}
);
)
.then(result => {
let user;
result.records.forEach(record => {
console.log(record.get('person'));
user = record.get('person');
})
//console.log(user.get('given') + " " + user.get('surname'));
console.log(user);
return user;
})
.catch(error => {
console.log(error)
})
.then((user) => {
session.close();
return user;
})
}

const typeDefs = fs
.readFileSync(
Expand Down Expand Up @@ -66,7 +108,7 @@ const driver = neo4j.driver(
// generate CRUD GraphQL API using makeAugmentedSchema
const schema = makeAugmentedSchema({
typeDefs: typeDefs
});
});

console.log(schema);
console.log(schema._typeMap.Query);
Expand Down Expand Up @@ -101,6 +143,7 @@ const server = new ApolloServer({
});
*/

/*
const server = new ApolloServer({
context: {
driver,
Expand All @@ -110,31 +153,39 @@ const server = new ApolloServer({
introspection: true,
playground: true,
})
*/

const server = new ApolloServer({
context:
async ({ req }) => {
console.log("setting up context");
// Get the user token from the headers.
const token = req.headers.authorization || '';
console.log(token);

// Try to retrieve a user with the token
const user = await getUser(driver, token);

console.log("From context, user");
console.log(user);
// Add the user to the context
return {
user,
driver,
driverConfig: { database: process.env.NEO4J_DATABASE || 'neo4j' },
};
},
schema: applyMiddleware(schema, permissions),
introspection: true,
playground: true,
})

// Specify host, port and path for GraphQL endpoint
const port = process.env.GRAPHQL_SERVER_PORT || 4001
const pth = process.env.GRAPHQL_SERVER_PATH || '/graphql'
const host = process.env.GRAPHQL_SERVER_HOST || '0.0.0.0'

/*
app.post(pth, async (req, res, next) => {
console.log("here!");
if (req.body.query.match(/^mutation/)) {
console.log("mutation!");
const token = req.get("Authorization").replace(/^Bearer /, '');
console.log(token);
const payload = jwt.verify(token, 'secret');
console.log(payload.username);
//TODO: verify password
return next();
//return next(new ForbiddenError());
//return res.status(401).json({msg: "Not authorized"});
} else {
console.log("query");
return next();
}
});
*/
const checkUser = (req, res, next) =>{
console.log(req.token);
if (req.token.username === "douglas") {
Expand All @@ -152,7 +203,7 @@ app.use(
userProperty: 'token'
}).unless({
custom: req => {
if (req.body.query.match(/^mutation/)) {
if (req.body.query && req.body.query.match(/^mutation/)) {
console.log("mutation");
return false;
} else {
Expand All @@ -163,7 +214,7 @@ app.use(
}),
checkUser.unless({
custom: req => {
if (req.body.query.match(/^mutation/)) {
if (req.body.query && req.body.query.match(/^mutation/)) {
console.log("mutation");
return false;
} else {
Expand All @@ -173,13 +224,30 @@ app.use(
}
})
);
*/

/*
* Optionally, apply Express middleware for authentication, etc
* This also also allows us to specify a path for the GraphQL endpoint
*/
server.applyMiddleware({ app, pth })

app.post('/login',
(req, res) => {
if (!req.body.username || !req.body.password) {
res.status(400).send({
code: 400,
msg: "Please pass username and password",
});
} else {
const token = jwt.sign({
username: req.body.username
}, 'secret', { expiresIn: '1h' });
res.json({ token: token }); //TODO: error handling and reporting through API
}
}
);

app.listen({ host, port, path }, () => {
console.log(`GraphQL server ready at http://${host}:${port}${pth}`)
})
56 changes: 56 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
"apollo-server": "^2.25.2",
"apollo-server-express": "^2.25.2",
"express-jwt": "^6.1.0",
"graphql-middleware": "^6.0.10",
"graphql-shield": "^7.5.0",
"jsonwebtoken": "^8.5.1",
"neo4j-graphql-js": "^2.19.4"
},
Expand Down
25 changes: 25 additions & 0 deletions permissions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { shield, rule, and, or, not, allow, deny } from 'graphql-shield';

const isAuthenticated = rule({ cache: 'contextual' })(async (parent, args, ctx, info) => {
console.log("isAuthenticated");
console.log(ctx.user);
return ctx.user !== null
});

const isAdmin = rule({ cache: 'contextual' })(async (parent, args, ctx, info) => {
console.log("isAdmin");
console.log(ctx.user.roles.includes('admin'));
return ctx.user.roles.includes('admin');
});

const permissions = shield({
Query: {
"*": allow,
},
Mutation: {
"*": and(isAuthenticated, isAdmin)
},
})


export default permissions;

0 comments on commit a644076

Please sign in to comment.