This repository has been archived by the owner on Oct 21, 2020. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat(auth): add auth options 🎉 * feat(tools): add debug * fix(directives): allow graphql-tools to handle directive resolvers * fix(auth-errors): communicate verification errors to the client * feat(jwt-cert): add cert to process.env * chore(pem): remove references to .pem files * chore(dependencies): add new packages to yarn.lock * fix(jwt-cert): add jwt-cert to serverless.yml * chore(tools): add jwt-cert to depoly script * feat(tools): add initial directive tests scaffolding * feat: support authentication in tests * chore(tools): refactor integration tests * feat(tools): add directive tests * chore(tools): remove gulp in favour of cross-env * chore(deps): remove package-lock.json * remove dev-only code * chore(tools): fix for ci
- Loading branch information
1 parent
2652e5c
commit 2b9f1a0
Showing
24 changed files
with
458 additions
and
780 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import jwt from 'jsonwebtoken'; | ||
import debug from 'debug'; | ||
|
||
import { AuthorizationError } from '../graphql/errors'; | ||
|
||
const log = debug('fcc:auth'); | ||
const { JWT_CERT } = process.env; | ||
|
||
export function verifyWebToken(ctx) { | ||
log('Verifying token'); | ||
const token = ctx && ctx.headers && ctx.headers.authorization; | ||
if (!token) { | ||
throw new AuthorizationError({ | ||
message: 'You must supply a JSON Web Token for authorization!' | ||
}); | ||
} | ||
let decoded = null; | ||
let error = null; | ||
try { | ||
decoded = jwt.verify(token.replace('Bearer ', ''), JWT_CERT); | ||
} catch (err) { | ||
error = err; | ||
} finally { | ||
return { decoded, error, isAuth: !!decoded }; | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,9 @@ | ||
require('dotenv').config(); | ||
|
||
const { MONGODB_URL, GRAPHQL_ENDPOINT_URL } = process.env; | ||
const { MONGODB_URL, GRAPHQL_ENDPOINT_URL, JWT_CERT } = process.env; | ||
|
||
exports.getSecret = () => ({ | ||
MONGODB_URL, | ||
GRAPHQL_ENDPOINT_URL | ||
GRAPHQL_ENDPOINT_URL, | ||
JWT_CERT, | ||
MONGODB_URL | ||
}); |
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { createError } from 'apollo-errors'; | ||
|
||
export const AuthorizationError = createError('AuthorizationError', { | ||
message: 'You are not authorized.' | ||
}); |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
/* global expect beforeEach */ | ||
import sinon from 'sinon'; | ||
import sinonStubPromise from 'sinon-stub-promise'; | ||
import { createDirectives } from './directives'; | ||
|
||
sinonStubPromise(sinon); | ||
const testErrorMsg = 'Test error message'; | ||
let nextSpy = sinon.spy(); | ||
let nextPromiseStub = sinon.stub().returnsPromise(); | ||
let authTrueStub = sinon.stub().returns({ isAuth: true }); | ||
let authFalseStub = sinon | ||
.stub() | ||
.returns({ isAuth: false, error: { message: testErrorMsg } }); | ||
|
||
beforeEach(() => { | ||
authFalseStub.resetHistory(); | ||
authTrueStub.resetHistory(); | ||
nextSpy.resetHistory(); | ||
nextPromiseStub.resetHistory(); | ||
}); | ||
|
||
describe('isAuthenticatedOnField', () => { | ||
it('should return null if authenication fails', () => { | ||
const { isAuthenticatedOnField } = createDirectives(authFalseStub); | ||
const secretValue = 'secret squirrel'; | ||
nextPromiseStub.resolves(secretValue); | ||
const result = isAuthenticatedOnField(nextPromiseStub); | ||
|
||
expect(authFalseStub.calledOnce).toBe(true); | ||
expect(result.resolved).toBe(true); | ||
expect(result.resolveValue).toBe(null); | ||
}); | ||
|
||
it('should return the secretValue if authentication succeeds', () => { | ||
const { isAuthenticatedOnField } = createDirectives(authTrueStub); | ||
const secretValue = 'secret squirrel'; | ||
nextPromiseStub.resolves(secretValue); | ||
const result = isAuthenticatedOnField(nextPromiseStub); | ||
|
||
expect(authTrueStub.calledOnce).toBe(true); | ||
expect(result.resolved).toBe(true); | ||
expect(result.resolveValue).toBe(secretValue); | ||
}); | ||
}); | ||
|
||
describe('isAuthenticatedOnQuery', () => { | ||
it('should throw an error is auth fails', () => { | ||
const { isAuthenticatedOnQuery } = createDirectives(authFalseStub); | ||
|
||
expect(() => { | ||
isAuthenticatedOnQuery(nextSpy); | ||
}).toThrowError(testErrorMsg); | ||
expect(nextSpy.called).toBe(false); | ||
}); | ||
|
||
it('should call next if auth succeeds', () => { | ||
const { isAuthenticatedOnQuery } = createDirectives(authTrueStub); | ||
isAuthenticatedOnQuery(nextPromiseStub); | ||
|
||
expect(nextPromiseStub.calledOnce).toBe(true); | ||
}); | ||
}); |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { AuthorizationError } from '../errors'; | ||
import { verifyWebToken as _verifyWebToken } from '../../auth'; | ||
import { asyncErrorHandler } from '../../utils'; | ||
|
||
/* | ||
Interface: { | ||
Directive: ( | ||
next: Resolver <Promise> | ||
source: any <From Data Source>, Example <User Object> | ||
args: any, passed to directive | ||
ctx: Lambda context <Object> | ||
) => <Promise> | <Error> | ||
} | ||
*/ | ||
|
||
export const createDirectives = (verifyWebToken = _verifyWebToken) => ({ | ||
isAuthenticatedOnField: (next, source, args, ctx) => { | ||
const { isAuth } = verifyWebToken(ctx); | ||
return asyncErrorHandler(next().then(result => (isAuth ? result : null))); | ||
}, | ||
isAuthenticatedOnQuery: (next, source, args, ctx) => { | ||
const { isAuth, error } = verifyWebToken(ctx); | ||
if (isAuth) { | ||
return asyncErrorHandler(next()); | ||
} | ||
throw new AuthorizationError({ | ||
message: `You are not authorized, ${error.message}` | ||
}); | ||
} | ||
}); |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
import { mergeResolvers } from 'merge-graphql-schemas'; | ||
import { userResolvers } from './user'; | ||
|
||
export { createDirectives } from './directives'; | ||
export default mergeResolvers([userResolvers]); |
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,6 @@ type Query { | |
_id: ID | ||
name: String | ||
email: String | ||
): [User] | ||
): [User] @isAuthenticatedOnQuery | ||
} | ||
`; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
export default ` | ||
type User { | ||
_id: ID! | ||
_id: ID @isAuthenticatedOnField | ||
email: String | ||
name: String | ||
} | ||
|
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export default ` | ||
directive @isAuthenticatedOnField on FIELD | FIELD_DEFINITION | ||
directive @isAuthenticatedOnQuery on FIELD | FIELD_DEFINITION | ||
`; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
import { mergeTypes } from 'merge-graphql-schemas'; | ||
import User from './User'; | ||
import directives from './directives'; | ||
|
||
export default mergeTypes([User]); | ||
export default mergeTypes([User, directives]); |
This file was deleted.
Oops, something went wrong.
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
module.exports = { | ||
globalSetup: './test/utils/setup.js', | ||
globalTeardown: './test/utils/teardown.js', | ||
testEnvironment: './test/utils/mongo-environment.js' | ||
testEnvironment: './test/utils/test-environment.js' | ||
}; |
Oops, something went wrong.