Skip to content

Instantly share code, notes, and snippets.

@trevorblades
Last active February 14, 2021 03:33
Show Gist options
  • Save trevorblades/aa0a52be887e1bb235a31cf2d3f0a996 to your computer and use it in GitHub Desktop.
Save trevorblades/aa0a52be887e1bb235a31cf2d3f0a996 to your computer and use it in GitHub Desktop.
Knoword auth flow
import axios from 'axios';
import bcrypt from 'bcryptjs';
import {
AuthenticationError,
ForbiddenError,
UserInputError,
gql
} from 'apollo-server-express';
import {Op} from 'sequelize';
import {User} from '../db';
export const typeDefs = gql`
extend type Mutation {
logIn(email: String!, password: String!): String
googleAuth(accessToken: String!): String
facebookAuth(accessToken: String!): String
}
`;
async function findOrCreateUser({email, name, ...rest}) {
const [key] = Object.keys(rest);
let user = await User.findOne({
where: {
[Op.or]: {
email: {
[Op.iLike]: email
},
...rest
}
}
});
if (!user) {
user = await User.create({
name,
email,
...rest,
active: true
});
} else if (!user[key]) {
await user.update({
...rest,
active: true
});
}
return user.toJWT();
}
export const resolvers = {
Mutation: {
async logIn(parent, args) {
const user = await User.findOne({
where: {
email: {
[Op.iLike]: args.email
}
}
});
if (!user) {
return new AuthenticationError('User does not exist');
}
if (!user.password && (user.googleId || user.facebookId)) {
return new UserInputError(
'This account was created using social media login. Please use one of the buttons above and optionally set a password in your account settings after you log in.'
);
}
if (!bcrypt.compareSync(args.password, user.password)) {
return new AuthenticationError('Invalid email/password combination');
}
if (!user.active) {
return new ForbiddenError(
'This account is not activated. Check your email for an activation link, or send a new one. The email may end up in your spam folder.'
);
}
return user.toJWT();
},
async googleAuth(parent, args) {
const response = await axios.get(
'https://www.googleapis.com/oauth2/v3/userinfo',
{params: {access_token: args.accessToken}}
);
const {name, email, sub: googleId} = response.data;
return findOrCreateUser({name, email, googleId});
},
async facebookAuth(parent, args) {
const response = await axios.get('https://graph.facebook.com/me', {
params: {
fields: ['id', 'email', 'name'].join(','),
access_token: args.accessToken
}
});
const {id: facebookId, email, name} = response.data;
return findOrCreateUser({name, email, facebookId});
}
}
};
import merge from 'lodash/merge';
import {
typeDefs as Alternate,
resolvers as alternateResolvers
} from './alternate';
import {typeDefs as Answer, resolvers as answerResolvers} from './answer';
import {
typeDefs as Assignment,
resolvers as assignmentResolvers
} from './assignment';
import {typeDefs as Auth, resolvers as authResolvers} from './auth';
import {
typeDefs as Collection,
resolvers as collectionResolvers
} from './collection';
import {DIFFICULTY_ENUM, PART_OF_SPEECH_ENUM} from '../db';
import {
typeDefs as Definition,
resolvers as definitionResolvers
} from './definition';
import {typeDefs as Game, resolvers as gameResolvers} from './game';
import {GraphQLDate, GraphQLDateTime} from 'graphql-iso-date';
import {
typeDefs as Invitation,
resolvers as invitationResolvers
} from './invitation';
import {typeDefs as Pack, resolvers as packResolvers} from './pack';
import {typeDefs as Rank, resolvers as rankResolvers} from './rank';
import {typeDefs as Season, resolvers as seasonResolvers} from './season';
import {typeDefs as Stripe, resolvers as stripeResolvers} from './stripe';
import {typeDefs as User, resolvers as userResolvers} from './user';
import {typeDefs as Word, resolvers as wordResolvers} from './word';
import {gql, makeExecutableSchema} from 'apollo-server-express';
const typeDefs = gql`
scalar Date
scalar DateTime
type Query {
difficulties: [String!]!
partsOfSpeech: [String!]!
}
type Mutation {
_empty: String
}
`;
const resolvers = {
Date: GraphQLDate,
DateTime: GraphQLDateTime,
Query: {
difficulties: () => DIFFICULTY_ENUM.values,
partsOfSpeech: () => PART_OF_SPEECH_ENUM.values
}
};
const schema = makeExecutableSchema({
typeDefs: [
typeDefs,
Alternate,
Answer,
Assignment,
Auth,
Collection,
Definition,
Game,
Pack,
Rank,
Season,
Stripe,
User,
Word,
Invitation
],
resolvers: merge(
resolvers,
alternateResolvers,
answerResolvers,
assignmentResolvers,
authResolvers,
collectionResolvers,
definitionResolvers,
gameResolvers,
packResolvers,
rankResolvers,
seasonResolvers,
stripeResolvers,
userResolvers,
wordResolvers,
invitationResolvers
)
});
export default schema;
import Stripe from 'stripe';
import permissions from './permissions';
import schema from './schema';
import {ApolloServer} from 'apollo-server-express';
import {applyMiddleware} from 'graphql-middleware';
import {userFromToken} from './utils';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
const server = new ApolloServer({
introspection: true,
schema: applyMiddleware(schema, permissions),
subscriptions: {
keepAlive: 10000,
async onConnect({authToken}) {
if (authToken) {
const user = await userFromToken(authToken);
return {user};
}
}
},
async context({req, connection}) {
if (connection) {
return connection.context;
}
const {origin, authorization} = req.headers;
const context = {origin, stripe};
try {
const matches = authorization.match(/^bearer (\S+)$/i);
const user = await userFromToken(matches[1]);
if (!user || !user.active) {
throw new Error('Invalid token');
}
return {...context, user};
} catch (error) {
return context;
}
}
});
export default server;
import jwt from 'jsonwebtoken';
import {User} from './db';
export function userFromToken(token) {
const {sub} = jwt.verify(token, process.env.JWT_SECRET);
return User.findByPk(sub);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment