Last active
February 14, 2021 03:33
-
-
Save trevorblades/aa0a52be887e1bb235a31cf2d3f0a996 to your computer and use it in GitHub Desktop.
Knoword auth flow
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}); | |
} | |
} | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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