Skip to content

Commit

Permalink
11-Authorization with GraphQL and Apollo Server
Browse files Browse the repository at this point in the history
  • Loading branch information
rwieruch committed Oct 25, 2018
1 parent 831ab56 commit 4f6e8e6
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 17 deletions.
14 changes: 14 additions & 0 deletions server/package-lock.json

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

1 change: 1 addition & 0 deletions server/package.json
Expand Up @@ -24,6 +24,7 @@
"dotenv": "^6.1.0",
"express": "^4.16.4",
"graphql": "^14.0.2",
"graphql-resolvers": "^0.2.2",
"jsonwebtoken": "^8.3.0",
"pg": "^7.5.0",
"sequelize": "^4.41.0",
Expand Down
35 changes: 29 additions & 6 deletions server/src/index.js
@@ -1,8 +1,12 @@
import 'dotenv/config';
import cors from 'cors';
import uuidv4 from 'uuid/v4';
import jwt from 'jsonwebtoken';
import express from 'express';
import { ApolloServer } from 'apollo-server-express';
import {
ApolloServer,
AuthenticationError,
} from 'apollo-server-express';

import schema from './schema';
import resolvers from './resolvers';
Expand All @@ -12,6 +16,20 @@ const app = express();

app.use(cors());

const getMe = async req => {
const token = req.headers['x-token'];

if (token) {
try {
return await jwt.verify(token, process.env.SECRET);
} catch (e) {
throw new AuthenticationError(
'Your session expired. Sign in again.',
);
}
}
};

const server = new ApolloServer({
typeDefs: schema,
resolvers,
Expand All @@ -27,11 +45,15 @@ const server = new ApolloServer({
message,
};
},
context: async () => ({
models,
me: await models.User.findByLogin('rwieruch'),
secret: process.env.SECRET,
}),
context: async ({ req }) => {
const me = await getMe(req);

return {
models,
me,
secret: process.env.SECRET,
};
},
});

server.applyMiddleware({ app, path: '/graphql' });
Expand All @@ -54,6 +76,7 @@ const createUsersWithMessages = async () => {
username: 'rwieruch',
email: 'hello@robin.com',
password: 'rwieruch',
role: 'ADMIN',
messages: [
{
text: 'Published the Road to learn React',
Expand Down
3 changes: 3 additions & 0 deletions server/src/models/user.js
Expand Up @@ -27,6 +27,9 @@ const user = (sequelize, DataTypes) => {
len: [7, 42],
},
},
role: {
type: DataTypes.STRING,
},
});

User.associate = models => {
Expand Down
27 changes: 27 additions & 0 deletions server/src/resolvers/authorization.js
@@ -0,0 +1,27 @@
import { ForbiddenError } from 'apollo-server';
import { combineResolvers, skip } from 'graphql-resolvers';

export const isAuthenticated = (parent, args, { me }) =>
me ? skip : new ForbiddenError('Not authenticated as user.');

export const isAdmin = combineResolvers(
isAuthenticated,
(parent, args, { me: { role } }) =>
role === 'ADMIN'
? skip
: new ForbiddenError('Not authorized as admin.'),
);

export const isMessageOwner = async (
parent,
{ id },
{ models, me },
) => {
const message = await models.Message.findById(id, { raw: true });

if (message.userId !== me.id) {
throw new ForbiddenError('Not authenticated as owner.');
}

return skip;
};
29 changes: 20 additions & 9 deletions server/src/resolvers/message.js
@@ -1,3 +1,7 @@
import { combineResolvers } from 'graphql-resolvers';

import { isAuthenticated, isMessageOwner } from './authorization';

export default {
Query: {
messages: async (parent, args, { models }) => {
Expand All @@ -9,16 +13,23 @@ export default {
},

Mutation: {
createMessage: async (parent, { text }, { me, models }) => {
return await models.Message.create({
text,
userId: me.id,
});
},
createMessage: combineResolvers(
isAuthenticated,
async (parent, { text }, { models, me }) => {
return await models.Message.create({
text,
userId: me.id,
});
},
),

deleteMessage: async (parent, { id }, { models }) => {
return await models.Message.destroy({ where: { id } });
},
deleteMessage: combineResolvers(
isAuthenticated,
isMessageOwner,
async (parent, { id }, { models }) => {
return await models.Message.destroy({ where: { id } });
},
),
},

Message: {
Expand Down
16 changes: 14 additions & 2 deletions server/src/resolvers/user.js
@@ -1,9 +1,12 @@
import jwt from 'jsonwebtoken';
import { combineResolvers } from 'graphql-resolvers';
import { AuthenticationError, UserInputError } from 'apollo-server';

import { isAdmin } from './authorization';

const createToken = async (user, secret, expiresIn) => {
const { id, email, username } = user;
return await jwt.sign({ id, email, username }, secret, {
const { id, email, username, role } = user;
return await jwt.sign({ id, email, username, role }, secret, {
expiresIn,
});
};
Expand Down Expand Up @@ -61,6 +64,15 @@ export default {

return { token: createToken(user, secret, '30m') };
},

deleteUser: combineResolvers(
isAdmin,
async (parent, { id }, { models }) => {
return await models.User.destroy({
where: { id },
});
},
),
},

User: {
Expand Down
2 changes: 2 additions & 0 deletions server/src/schema/user.js
Expand Up @@ -15,6 +15,7 @@ export default gql`
): Token!
signIn(login: String!, password: String!): Token!
deleteUser(id: ID!): Boolean!
}
type Token {
Expand All @@ -25,6 +26,7 @@ export default gql`
id: ID!
username: String!
email: String!
role: String
messages: [Message!]
}
`;

0 comments on commit 4f6e8e6

Please sign in to comment.