From f93cbe9fb9080fb52c7925aca82e57c401c8524d Mon Sep 17 00:00:00 2001 From: Joel Thoms Date: Mon, 12 Jun 2017 23:35:17 -0700 Subject: [PATCH 1/9] update tests --- events/user-registration.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/events/user-registration.json b/events/user-registration.json index 2ae5b47..0ac4a68 100644 --- a/events/user-registration.json +++ b/events/user-registration.json @@ -1,9 +1,9 @@ { "pathParameters": { - "realm": "demo:demo" + "realm": "mojo:default" }, "headers": { "Content-Type": "application/x-www-form-urlencoded" }, - "body": "client_id=default&username=test@test.com&password=password" + "body": "client_id=default&username=username&email=test@test.com&password=password" } \ No newline at end of file From e36b0015419e7e1d280f05a47542047685a02aed Mon Sep 17 00:00:00 2001 From: Joel Thoms Date: Mon, 12 Jun 2017 23:51:09 -0700 Subject: [PATCH 2/9] refactoring --- services/storage/dynamodb/index.js | 7 +------ services/storage/dynamodb/lib/helpers.js | 7 +++++++ 2 files changed, 8 insertions(+), 6 deletions(-) create mode 100644 services/storage/dynamodb/lib/helpers.js diff --git a/services/storage/dynamodb/index.js b/services/storage/dynamodb/index.js index 9328eda..a293e45 100644 --- a/services/storage/dynamodb/index.js +++ b/services/storage/dynamodb/index.js @@ -3,6 +3,7 @@ const config = require('config') const promisify = require('functional-helpers/promisify') const path = require('ramda/src/path') const createUser = require('./create-user') +const first = require('./lib/helpers').first const USERS = config.get('dynamodb.tables.users') const REALMS = config.get('dynamodb.tables.realms') @@ -23,12 +24,6 @@ const query = (table, condition, values, filter) => ExpressionAttributeValues: values, }) -/* istanbul ignore next */ -const first = func => function () { - return func.apply(this, arguments) - .then(path(['Items', 0])) -} - /* istanbul ignore next */ module.exports.getUser = (realm, userId) => first(query)(USERS, 'userId = :userId', { ':userId': `${realm}:${userId}` }) diff --git a/services/storage/dynamodb/lib/helpers.js b/services/storage/dynamodb/lib/helpers.js new file mode 100644 index 0000000..c16d760 --- /dev/null +++ b/services/storage/dynamodb/lib/helpers.js @@ -0,0 +1,7 @@ +const path = require('ramda/src/path') + +/* istanbul ignore next */ +module.exports.first = func => function () { + return func.apply(this, arguments) + .then(path(['Items', 0])) +} From fcbe084286825cb22392501805d9194eecdac98c Mon Sep 17 00:00:00 2001 From: Joel Thoms Date: Mon, 12 Jun 2017 23:55:27 -0700 Subject: [PATCH 3/9] refactoring --- services/storage/dynamodb/create-user.js | 19 ++++++++----------- services/storage/dynamodb/index.js | 2 +- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/services/storage/dynamodb/create-user.js b/services/storage/dynamodb/create-user.js index c52ac43..98d6bcd 100644 --- a/services/storage/dynamodb/create-user.js +++ b/services/storage/dynamodb/create-user.js @@ -2,7 +2,6 @@ const config = require('config') const promisify = require('functional-helpers/promisify') const Joi = require('joi') const joiValidate = promisify(Joi.validate) -const docClient = require('./doc-client') const schema = Joi.object().keys({ userId: Joi.string().regex(/^[^:]+:[^:]+:[^:]+$/).required(), @@ -13,9 +12,6 @@ const schema = Joi.object().keys({ roles: Joi.array().items(Joi.string().valid('', 'admin')), }) -const docClientPut = - promisify(docClient.put, docClient) - const toUserDoc = user => ({ TableName: config.get('dynamodb.tables.users'), Item: user, @@ -25,12 +21,13 @@ const toUserDoc = user => ({ }) const rejectMessage = err => - err.message === 'The conditional request failed' - ? 'User already exists' - : err + Promise.reject(err.message === 'The conditional request failed' ? 'User already exists' : err) /* istanbul ignore next */ -module.exports = (user) => - joiValidate(user, schema) - .then(user => docClientPut(toUserDoc(user)).then(() => user)) - .catch(err => Promise.reject(rejectMessage(err))) +module.exports = client => user => { + const put = promisify(client.put, client) + + return joiValidate(user, schema) + .then(user => put(toUserDoc(user)).then(() => user)) + .catch(rejectMessage) +} \ No newline at end of file diff --git a/services/storage/dynamodb/index.js b/services/storage/dynamodb/index.js index a293e45..5099bb4 100644 --- a/services/storage/dynamodb/index.js +++ b/services/storage/dynamodb/index.js @@ -29,7 +29,7 @@ module.exports.getUser = (realm, userId) => first(query)(USERS, 'userId = :userId', { ':userId': `${realm}:${userId}` }) /* istanbul ignore next */ -module.exports.createUser = createUser +module.exports.createUser = createUser(docClient) /* istanbul ignore next */ module.exports.getRealm = realmId => From 6108880401adc90eb0db52f1175d7fbeb0d896c3 Mon Sep 17 00:00:00 2001 From: Joel Thoms Date: Tue, 13 Jun 2017 00:01:57 -0700 Subject: [PATCH 4/9] refactoring --- services/storage/dynamodb/index.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/services/storage/dynamodb/index.js b/services/storage/dynamodb/index.js index 5099bb4..4dd6fdf 100644 --- a/services/storage/dynamodb/index.js +++ b/services/storage/dynamodb/index.js @@ -1,12 +1,11 @@ const AWS = require('aws-sdk') const config = require('config') const promisify = require('functional-helpers/promisify') -const path = require('ramda/src/path') const createUser = require('./create-user') +const getRealm = require('./get-realm') const first = require('./lib/helpers').first const USERS = config.get('dynamodb.tables.users') -const REALMS = config.get('dynamodb.tables.realms') const docClient = new AWS.DynamoDB.DocumentClient({ region: config.get('aws.region'), @@ -32,5 +31,4 @@ module.exports.getUser = (realm, userId) => module.exports.createUser = createUser(docClient) /* istanbul ignore next */ -module.exports.getRealm = realmId => - first(query)(REALMS, 'realmId = :realmId', { ':realmId': realmId }) +module.exports.getRealm = getRealm(docClient) From 0af00005bb20c2382027f01dca53b45c3101e95a Mon Sep 17 00:00:00 2001 From: Joel Thoms Date: Tue, 13 Jun 2017 00:10:38 -0700 Subject: [PATCH 5/9] refactoring --- services/storage/dynamodb/index.js | 30 ++++++------------------------ 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/services/storage/dynamodb/index.js b/services/storage/dynamodb/index.js index 4dd6fdf..72f69df 100644 --- a/services/storage/dynamodb/index.js +++ b/services/storage/dynamodb/index.js @@ -1,34 +1,16 @@ const AWS = require('aws-sdk') const config = require('config') -const promisify = require('functional-helpers/promisify') const createUser = require('./create-user') const getRealm = require('./get-realm') -const first = require('./lib/helpers').first - -const USERS = config.get('dynamodb.tables.users') +const getUser = require('./get-user') const docClient = new AWS.DynamoDB.DocumentClient({ region: config.get('aws.region'), apiVersion: config.get('aws.apiversion'), }) -const docClientQuery = promisify(docClient.query, docClient) - -/* istanbul ignore next */ -const query = (table, condition, values, filter) => - docClientQuery({ - TableName: table, - KeyConditionExpression: condition, - FilterExpression: filter, - ExpressionAttributeValues: values, - }) - -/* istanbul ignore next */ -module.exports.getUser = (realm, userId) => - first(query)(USERS, 'userId = :userId', { ':userId': `${realm}:${userId}` }) - -/* istanbul ignore next */ -module.exports.createUser = createUser(docClient) - -/* istanbul ignore next */ -module.exports.getRealm = getRealm(docClient) +module.exports = { + getUser: getUser(docClient), + createUser: createUser(docClient), + getRealm: getRealm(docClient), +} From 6ca6acfc157c84c55032bab022f32173d0e3a15f Mon Sep 17 00:00:00 2001 From: Joel Thoms Date: Tue, 13 Jun 2017 00:11:07 -0700 Subject: [PATCH 6/9] refactoring --- services/storage/dynamodb/get-realm.js | 18 ++++++++++++++++++ services/storage/dynamodb/get-user.js | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 services/storage/dynamodb/get-realm.js create mode 100644 services/storage/dynamodb/get-user.js diff --git a/services/storage/dynamodb/get-realm.js b/services/storage/dynamodb/get-realm.js new file mode 100644 index 0000000..d8bcfbe --- /dev/null +++ b/services/storage/dynamodb/get-realm.js @@ -0,0 +1,18 @@ +const config = require('config') +const promisify = require('functional-helpers/promisify') +const first = require('./lib/helpers').first + +const REALMS = config.get('dynamodb.tables.realms') + +/* istanbul ignore next */ +const query = (client, table, condition, values, filter) => + promisify(client.query, client)({ + TableName: table, + KeyConditionExpression: condition, + FilterExpression: filter, + ExpressionAttributeValues: values, + }) + +/* istanbul ignore next */ +module.exports = client => realmId => + first(query)(client, REALMS, 'realmId = :realmId', { ':realmId': realmId }) diff --git a/services/storage/dynamodb/get-user.js b/services/storage/dynamodb/get-user.js new file mode 100644 index 0000000..7367b4a --- /dev/null +++ b/services/storage/dynamodb/get-user.js @@ -0,0 +1,18 @@ +const config = require('config') +const promisify = require('functional-helpers/promisify') +const first = require('./lib/helpers').first + +const USERS = config.get('dynamodb.tables.users') + +/* istanbul ignore next */ +const query = (client, table, condition, values, filter) => + promisify(client.query, client)({ + TableName: table, + KeyConditionExpression: condition, + FilterExpression: filter, + ExpressionAttributeValues: values, + }) + +/* istanbul ignore next */ +module.exports = client => (realm, userId) => + first(query)(client, USERS, 'userId = :userId', { ':userId': `${realm}:${userId}` }) From 01c534681ab4c450b34188516d85110d7c61aa2a Mon Sep 17 00:00:00 2001 From: Joel Thoms Date: Tue, 13 Jun 2017 02:21:20 -0700 Subject: [PATCH 7/9] add create-realm action --- actions/create-realm/index.js | 32 +++++++++++++++++++++++ actions/create-realm/request.js | 17 ++++++++++++ config/default.json | 1 + events/create-realm.json | 9 +++++++ events/tokenPasswordEvent.json | 4 +-- handler.js | 7 +++++ lib/exceptionMapper.js | 14 ++++++---- package.json | 3 ++- serverless.yml | 7 +++++ services/storage/dynamodb/create-realm.js | 29 ++++++++++++++++++++ services/storage/dynamodb/index.js | 22 +++++++--------- 11 files changed, 124 insertions(+), 21 deletions(-) create mode 100644 actions/create-realm/index.js create mode 100644 actions/create-realm/request.js create mode 100644 events/create-realm.json create mode 100644 services/storage/dynamodb/create-realm.js diff --git a/actions/create-realm/index.js b/actions/create-realm/index.js new file mode 100644 index 0000000..41bec88 --- /dev/null +++ b/actions/create-realm/index.js @@ -0,0 +1,32 @@ +const config = require('config') +const promisify = require('functional-helpers/promisify') +const jwtVerify = promisify(require('jsonwebtoken').verify) +const validatedRequest = require('./request') +const getPublicKey = require('../../lib/getKeys').getPublicKey +const exceptionMapper = require('../../lib/exceptionMapper') + +const rejectIfRealmInvalid = jwt => + jwt.realm === config.get('realm') ? jwt : Promise.reject('Not Authorized') + +const rejectIfUserDoesntOwnRealm = realm => jwt => + realm.substr(0, jwt.sub.length + 1) === `${jwt.sub}:` ? jwt : Promise.reject('Not Authorized') + +const handleException = state => err => { + state.actions.log.error(err.stack || err) + + return Promise.reject(exceptionMapper(err)) +} + +module.exports = validatedRequest((request, actions) => { + const state = { props: request, actions } + + actions.log.debug('Starting create-realm') + + return Promise.resolve(state) + .then(state => getPublicKey(state.actions.readFile)) + .then(cert => jwtVerify(state.props.access_token, cert, { algorithms: ['RS256'] })) + .then(rejectIfRealmInvalid) + .then(rejectIfUserDoesntOwnRealm(state.props.realm)) + .then(jwt => state.actions.createRealm({ realmId: state.props.realm, owner: jwt.sub })) + .catch(handleException(state)) +}) diff --git a/actions/create-realm/request.js b/actions/create-realm/request.js new file mode 100644 index 0000000..5e95c12 --- /dev/null +++ b/actions/create-realm/request.js @@ -0,0 +1,17 @@ +const Joi = require('joi') +const querystring = require('querystring') +const { getValidatedRequest } = require('../../lib/joi-helpers') + +const options = { + stripUnknown: true +} + +const getRequest = event => + Object.assign({}, event.queryStringParameters, querystring.parse(event.body), event.pathParameters) + +const schema = Joi.object().keys({ + realm: Joi.string().min(4).max(30).required(), + access_token: Joi.string().required(), +}) + +module.exports = getValidatedRequest(getRequest, schema, options) diff --git a/config/default.json b/config/default.json index 2179f49..1339583 100644 --- a/config/default.json +++ b/config/default.json @@ -10,6 +10,7 @@ } }, "authorizationEndpoint": "https://yydpeix3rd.execute-api.us-west-2.amazonaws.com/dev/auth", + "realm": "mojo:default", "token": { "tokenExpiration": "5m", "refreshTokenExpiration": "30m" diff --git a/events/create-realm.json b/events/create-realm.json new file mode 100644 index 0000000..d06feb8 --- /dev/null +++ b/events/create-realm.json @@ -0,0 +1,9 @@ +{ + "pathParameters": { + "realm": "username:default" + }, + "headers": { + "Content-Type": "application/x-www-form-urlencoded" + }, + "body": "access_token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIzYmRhYjE3Yy04ZThhLTQ3NmItOTIzZS05N2E0NGY2MDhhZjQiLCJ0eXAiOiJCZWFyZXIiLCJyZWFsbSI6Im1vam86ZGVmYXVsdCIsInJvbGVzIjpbXSwiaWF0IjoxNDk3MzQzODc0LCJleHAiOjQ2NTMxMDM4NzQsImF1ZCI6ImRlZmF1bHQiLCJzdWIiOiJ1c2VybmFtZSJ9.XHgiltVcFFgFhqVGxBfwuk2FdFqk1QTm9mJa6IS81h5M5ycrkk3J9FXyc3ONiw80Qhx5lq3WDmNGremoynXboaPHWQFYKFZXBbaFhkK6G-uxMkQl3685ileHCpRSaMcw6X0yiFNWyO5fd-OsozGlVrnahyKenNCxVAJZae2QH0rYgKWlbLATv6kB08L0UZrnP2MSJ_KKZ6Iaz4hKiKlBezMlpz76gXovMNOEztd_WjsL8xz88LLBLwn3IqykW3guS8InOEtt8Ou5SFL37dnwDlP8Od-NoGiTiof6UzAugu1lxoyBbd0iAYjxvcao9sWIwClIbxFxsdHeV2hv7CJbDQ" +} \ No newline at end of file diff --git a/events/tokenPasswordEvent.json b/events/tokenPasswordEvent.json index bbd45d6..118896c 100644 --- a/events/tokenPasswordEvent.json +++ b/events/tokenPasswordEvent.json @@ -1,9 +1,9 @@ { "pathParameters": { - "realm": "demo:demo" + "realm": "mojo:default" }, "headers": { "Content-Type": "application/x-www-form-urlencoded" }, - "body": "grant_type=password&client_id=default&username=test@test.com&password=password" + "body": "grant_type=password&client_id=default&username=username&password=password" } \ No newline at end of file diff --git a/handler.js b/handler.js index a2288d1..9e5712d 100644 --- a/handler.js +++ b/handler.js @@ -8,14 +8,17 @@ const token = require('./actions/token') const authorize = require('./actions/authorize') const openidConfiguration = require('./actions/openid-configuration') const userRegistration = require('./actions/user-registration') +const createRealmAction = require('./actions/create-realm') const withJsonResponse = require('./lib/serviceHelpers').withJsonResponse const getUser = require('./services/storage').getUser const createUser = require('./services/storage').createUser const getRealm = require('./services/storage').getRealm +const createRealm = require('./services/storage').createRealm const logging = require('./services/logging') const actions = { getRealm, + createRealm, getUser, createUser, readFile: promisify(fs.readFile), @@ -41,3 +44,7 @@ module.exports.authorize = callbackify((request, context) => module.exports.userRegistration = callbackify((request, context) => withJsonResponse(userRegistration)(request, actions) ) + +module.exports.createRealm = callbackify((request, context) => + withJsonResponse(createRealmAction)(request, actions) +) diff --git a/lib/exceptionMapper.js b/lib/exceptionMapper.js index ad393d5..5892504 100644 --- a/lib/exceptionMapper.js +++ b/lib/exceptionMapper.js @@ -6,23 +6,27 @@ const UNKNOWN_ERROR = '[500] server_error' const map = [ { test: err => ['Not Found', 'Realm not found'].indexOf(err) > -1, - run: () => NOT_FOUND + run: () => NOT_FOUND, }, { test: err => ['User not found', 'Password does not match', 'jwt must be provided', 'jwt expired'].indexOf(err) > -1, - run: () => UNAUTHORIZED + run: () => UNAUTHORIZED, }, { test: err => ['jwt malformed'].indexOf(err) > -1, - run: () => BAD_REQUEST + run: () => BAD_REQUEST, }, { test: err => ['User already exists'].indexOf(err) > -1, - run: err => `[403] ${err}` + run: err => `[403] ${err}`, + }, + { + test: err => ['Realm already exists'].indexOf(err) > -1, + run: () => '[409] Realm already exists', }, { test: () => true, - run: () => UNKNOWN_ERROR + run: () => UNKNOWN_ERROR, }, ] diff --git a/package.json b/package.json index 142b5df..979535b 100644 --- a/package.json +++ b/package.json @@ -39,9 +39,10 @@ "invoke:authorize": "SLS_DEBUG=* serverless invoke local --function authorize --path events/authorizeEvent.json --stage dev", "invoke:openid-configuration": "SLS_DEBUG=* serverless invoke local --function openidConfiguration --path events/openid-configuration.json --stage dev", "invoke:user-registration": "SLS_DEBUG=* serverless invoke local --function userRegistration --path events/user-registration.json --stage dev", + "invoke:create-realm": "SLS_DEBUG=* serverless invoke local --function createRealm --path events/create-realm.json --stage dev", "secrets:generate": "node ./tools/generate-private-key.js", "static:up": "aws s3 sync static s3://social-core-dev-files/auth --acl public-read --delete" }, "author": "", "license": "MIT" -} +} \ No newline at end of file diff --git a/serverless.yml b/serverless.yml index 0f36b98..37745a0 100644 --- a/serverless.yml +++ b/serverless.yml @@ -63,6 +63,13 @@ functions: path: auth/{realm}/registration method: post cors: true + createRealm: + handler: handler.createRealm + events: + - http: + path: auth/{realm} + method: post + cors: true # you can add CloudFormation resource templates here #resources: diff --git a/services/storage/dynamodb/create-realm.js b/services/storage/dynamodb/create-realm.js new file mode 100644 index 0000000..349141c --- /dev/null +++ b/services/storage/dynamodb/create-realm.js @@ -0,0 +1,29 @@ +const config = require('config') +const promisify = require('functional-helpers/promisify') +const Joi = require('joi') +const joiValidate = promisify(Joi.validate) + +const schema = Joi.object().keys({ + realmId: Joi.string().regex(/^[^:]+:[^:]+$/).required(), + owner: Joi.string().required(), +}) + +const toUserDoc = model => ({ + TableName: config.get('dynamodb.tables.realms'), + Item: model, + Expected: { + realmId: { Exists: false } + }, +}) + +const rejectMessage = err => + Promise.reject(err.message === 'The conditional request failed' ? 'Realm already exists' : err) + +/* istanbul ignore next */ +module.exports = client => model => { + const put = promisify(client.put, client) + + return joiValidate(model, schema) + .then(model => put(toUserDoc(model)).then(() => model)) + .catch(rejectMessage) +} \ No newline at end of file diff --git a/services/storage/dynamodb/index.js b/services/storage/dynamodb/index.js index 72f69df..cba4095 100644 --- a/services/storage/dynamodb/index.js +++ b/services/storage/dynamodb/index.js @@ -1,16 +1,12 @@ -const AWS = require('aws-sdk') -const config = require('config') -const createUser = require('./create-user') -const getRealm = require('./get-realm') -const getUser = require('./get-user') - -const docClient = new AWS.DynamoDB.DocumentClient({ - region: config.get('aws.region'), - apiVersion: config.get('aws.apiversion'), -}) +const client = require('./doc-client') +const getUser = require('./get-user')(client) +const createUser = require('./create-user')(client) +const getRealm = require('./get-realm')(client) +const createRealm = require('./create-realm')(client) module.exports = { - getUser: getUser(docClient), - createUser: createUser(docClient), - getRealm: getRealm(docClient), + getUser, + createUser, + getRealm, + createRealm, } From 3ac3b6ba3cc1752678b3ab163454f194b2e8b2a1 Mon Sep 17 00:00:00 2001 From: Joel Thoms Date: Wed, 14 Jun 2017 22:53:50 -0700 Subject: [PATCH 8/9] added tests for create-realm/request.js --- .../create-realm/__tests__/request.tests.js | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 actions/create-realm/__tests__/request.tests.js diff --git a/actions/create-realm/__tests__/request.tests.js b/actions/create-realm/__tests__/request.tests.js new file mode 100644 index 0000000..e2a4919 --- /dev/null +++ b/actions/create-realm/__tests__/request.tests.js @@ -0,0 +1,58 @@ +const querystring = require('querystring') +const request = require('../request') + +const I = x => x + +describe('create-realm', () => { + test('no realm returns "realm" is required', () => { + expect.assertions(1) + const data = {} + + return request(I)(data) + .catch(error => expect(error).toBe('[400] "realm" is required')) + }) + + test('short realm returns "realm" length must be at least 4 characters long', () => { + expect.assertions(1) + const data = { + pathParameters: { realm: 'ab' }, + } + + return request(I)(data) + .catch(error => expect(error).toBe('[400] "realm" length must be at least 4 characters long')) + }) + + test('short realm returns "realm" length must be less than or equal to 30 characters long', () => { + expect.assertions(1) + const data = { + pathParameters: { realm: 'abcdefghijklmnopqrstuvwxyz0123456789' }, + } + + return request(I)(data) + .catch(error => expect(error).toBe('[400] "realm" length must be less than or equal to 30 characters long')) + }) + + test('no access_token returns "access_token" is required', () => { + expect.assertions(1) + const data = { + pathParameters: { realm: 'realm' }, + } + + return request(I)(data) + .catch(error => expect(error).toBe('[400] "access_token" is required')) + }) + + test('invalid access_token returns "access_token" is required', () => { + expect.assertions(1) + const data = { + pathParameters: { realm: 'realm' }, + body: querystring.encode({ + access_token: 'access_token', + }) + } + + return request(response => { + expect(response).toEqual({ realm: 'realm', access_token: 'access_token' }) + })(data) + }) +}) From 604fe871710f090bc461d853721051d1eec14855 Mon Sep 17 00:00:00 2001 From: Joel Thoms Date: Thu, 15 Jun 2017 02:21:38 -0700 Subject: [PATCH 9/9] add tests for create-realm --- actions/create-realm/__tests__/index.tests.js | 181 ++++++++++++++++++ actions/token/__tests__/index.test.js | 12 +- 2 files changed, 186 insertions(+), 7 deletions(-) create mode 100644 actions/create-realm/__tests__/index.tests.js diff --git a/actions/create-realm/__tests__/index.tests.js b/actions/create-realm/__tests__/index.tests.js new file mode 100644 index 0000000..9f1c103 --- /dev/null +++ b/actions/create-realm/__tests__/index.tests.js @@ -0,0 +1,181 @@ +const querystring = require('querystring') +const createRealm = require('..') +const R = require('ramda') + +const access_token = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIzYmRhYjE3Yy04ZThhLTQ3NmItOTIzZS05N2E0NGY2MDhhZjQiLCJ0eXAiOiJCZWFyZXIiLCJyZWFsbSI6Im1vam86ZGVmYXVsdCIsInJvbGVzIjpbXSwiaWF0IjoxNDk3MzQzODc0LCJleHAiOjQ2NTMxMDM4NzQsImF1ZCI6ImRlZmF1bHQiLCJzdWIiOiJ1c2VybmFtZSJ9.XHgiltVcFFgFhqVGxBfwuk2FdFqk1QTm9mJa6IS81h5M5ycrkk3J9FXyc3ONiw80Qhx5lq3WDmNGremoynXboaPHWQFYKFZXBbaFhkK6G-uxMkQl3685ileHCpRSaMcw6X0yiFNWyO5fd-OsozGlVrnahyKenNCxVAJZae2QH0rYgKWlbLATv6kB08L0UZrnP2MSJ_KKZ6Iaz4hKiKlBezMlpz76gXovMNOEztd_WjsL8xz88LLBLwn3IqykW3guS8InOEtt8Ou5SFL37dnwDlP8Od-NoGiTiof6UzAugu1lxoyBbd0iAYjxvcao9sWIwClIbxFxsdHeV2hv7CJbDQ' +const invalidRealmRefreshToken = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI3NzQwNmE4MC1mMGJjLTQ1ZGQtYmIwNy0wYjUyOTkxYzU5ZWUiLCJ0eXAiOiJSZWZyZXNoIiwicmVhbG0iOiJpbnZhbGlkIiwiaWF0IjoxNDk3NTE4MDU3LCJleHAiOjQ2NTMyNzgwNTcsImF1ZCI6ImNsaWVudF9pZCIsInN1YiI6Im1vam8ifQ.kvX_lueA5-sQEe3w6nUirhf70pNAo1MdJdTtDo5XQoJeQ0VMlqyG70TGv7j99UxW3Sn0AkRYvYoBIWwnYgzS7lT7HGdnDZW3QVy3fNCdJgGe8SlhKU1luJmheatPbSBN8k5FIsXJTZ4jS8iv9IcBJ2zl_Fv7YVdawGcrgmWjuKz70KS7wIcqT9kffIFppOjlqzutQ3r7NiTPyBfl2Qd35RdAgHSHUV3HQWNo6ntgPg0TLwQ7BC-iUxqecO_6IW8JWdV5-iyzkuRiiudyZ1FBorGOLyPebcQ1aLxlWdiAPCOQ70TgwRnqPNZmAu43EQrnBT6XoT3r-3QU3AAJXoI4kg' +const anotherUserRefreshToken = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI1MjA2ZDY2MS0wNWZiLTRmOWQtOWEzZC04ZjFhNWM3MTZiNTciLCJ0eXAiOiJSZWZyZXNoIiwicmVhbG0iOiJtb2pvOmRlZmF1bHQiLCJpYXQiOjE0OTc1MTY2MTIsImV4cCI6NDY1MzI3NjYxMiwiYXVkIjoiY2xpZW50X2lkIiwic3ViIjoidXNlcm5hbWUifQ.cMXo72f-3qoGQRaKt3Su4xqhBHQDHYZnWnVKzl-iBDaU8Hm9fBdihvlHroN-DB2SwVCSXhw2v2gq6DtXC4b7bmBG9VhiF3v7HtPfY0GJN6otCUVnQtV2quBS4-JxxxzeF6ie4hhKqnKAPwO7-qUdbaXc7codYYzYb-C5C3mSrjrTk-ipu_g32GTHjiXrdYCGZvH7v9S-dkcRyVM0CEChye4vCk_uffgIOb9xWgwwhqQkWuGmF89MF6EIdo8yihtTt3Xx_cW8-N4S5BBQDSXyxzfbYG1rkUSx-ADsdI5r86a7vqYkmaZldpmHaWqGeHTD5oV-aLocfSKA6bcSfV9v9g' +const refreshToken = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI4NWE4OGRmZC03ZjlmLTRkNDktOTcyNS0wMzFjNGI5YzZhNzMiLCJ0eXAiOiJSZWZyZXNoIiwicmVhbG0iOiJtb2pvOmRlZmF1bHQiLCJpYXQiOjE0OTc1MTcwNTksImV4cCI6NDY1MzI3NzA1OSwiYXVkIjoiY2xpZW50X2lkIiwic3ViIjoibW9qbyJ9.Sy82pm-IFqLTCuDqcx_zezNMGyyS9rZA8AmxH5K25YheovC7g_Yl8lQdh5-jk_zXiElYcMUnsijT4IVo7ENZ7LsWO0_VwhrThepFMl0BHyQAiHBEmR1HoKpGJfjpBrDL9D45a6bpw_t40aJCXbAiUkTD5f1hBPYio5Jw1eZPUvuo-UDhqvPSFz1dhgomV5cbXYXaipCK24oVKMS1Snf1gqSdhVqwN6usz9b6U1NYSEfIErdHhDkaQmIKFGItB4VoDQmVcG0OzI3DwwAvoQv9da3CQy2rq2HpM4mIHPaWWK1ihfBGQ7gOHso4fe35xMDzdov4cZ5U_wnCkNXYqEoVqQ' +//const foreverRefreshToken = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJkZDJiZjZkOC0xYmJmLTRmMWMtODkwOC01OGQ4YjBkZTk1MTIiLCJ0eXAiOiJSZWZyZXNoIiwicmVhbG0iOiJtb2pvOmRlZmF1bHQiLCJpYXQiOjE0OTc1MTQxNzIsImV4cCI6MTQ5NzUxNTk3MiwiYXVkIjoiY2xpZW50X2lkIiwic3ViIjoidXNlcm5hbWUifQ.KXTKsizvYxGaRdSTcuGEe-AWgdx7RMuxMzWOfdp9nAzJZe4ko-h8McvTUxaeApbWsZ8VixJCu3HlHMeMjZbWgzuALy7vbEW2jnDf0d274rIUJw2a_n_2LnIYYUmtH10F6IUcJrut9M-s6ST5q7zvmpW6HtMmOMdGJcmx2GSy-5L8-k9fVhCIFLUD3HKM70ilxLCJLvUloWbtkQE3a_ydfy4xvLucOhMS8qnhZ8BvzFtHcHwl-rdPkLZeW2SeHa3TyBhlrs12zki8BdczbgYbGJ2pUlVOFfY0g69sxrLXIny-bEv-cr3H8b0jExAwESEMWlGdsAAvdymLuLzAm7ZK2A' + +const privateKey = `-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEApU4JW+EgeFUZG2hI3n7C0x8/gSerp1Ga90JOTkeH9+KL+FU/wankZCBx +cHp/NgBlbuvdrdD8/Ym3CYwNiqC7CETztkXPRI4hWG2Z/eYZ4D9GKnxFviAAJ4TNr7esWjN7 +s12wnoD4KM5I9agKHobMGMPbiifOeYtgj2mVrkqlowlgw/WnpcPjXCnEXt6fns3LRPpsmruI +uCX3G4P9Sv+D3BK/RWSjXtfohmTLdo7mfg9fDJAv3I9N93kD1zZXanpQJE5UjmuUzpMtHW20 +2rxib5Y412Ds0qKC5AtgfM9BTUIaz9KVP2Y9YXgM6QdOL2zgNJHJUE7sUIw36fJ258FM2wID +AQABAoIBAGEfJV6HOdWZYgP5VH7tCTiTrnMKxM6sopjNq0ZQvrFEuKoyJCB72gV+DkhgoGcw ++meeSwN5u3qXNCR21enyH5FvOaWJBIsp2qui0Ywcam2Xn3kMxMk88fpGC7dG+guRHge3bzLh +YWxQLwuuLCvdVQLj6BQW6Tf+TMBV76yUjRbpT0cXzQM9HHSlRZwmGFQ137agAfEXnA2gBVCk +UkNDaS4DOeNEKgR5GstUCM3AKrv9HbYr3uUyUzRmpnISJQt6lU5/eoqa0+BDwNVCHTjEQjVq +VROwcr/uUtiCgMN8Up8mpBmdYP+QhBO/2GCtHzkqL3iawyx/Dlqg+fACxzwrUhkCgYEA/yR0 +UxxaS9wQsNHrtBwgtCs9pq2W5QwRaGqe4Zkvw+KCjAa069SUolAFHG8W2J4P0lFzzRnu+ETj +qjpHPggZQYfx+XVCluYP7bKN20yRPi0EhKscZjX7xStTyI4r7YKp737HuYRjHBsWtLYYGPRM +rS8OvK5manin9NDD+3LoFg8CgYEApdxHS3zn5SgFRoWQfGYembGGMtdYgNNupRC98UzIhSIH +qS3aIeX6VPnOo7HnM0xc6OdTeznrLJJmJ0ksI129r2rN6xxQ0dkeBGfKNK/cH+F/ZbGsOChB +er7srgnZhLbmXELTI9IIUyghUZmmdEbTC8iXG/tqUDHMBJiU7w3VSHUCgYEAzl+cP8WFPCsK +zRtfPdYqldEMExACJ861HfJwBSa1PgqvcbfTC5Zti0SSfcdVgW2IeqQruNCrPOHsDLsK+R/v +3dOqZA73B7ubUrbEi4fJS7N6Hh2R4RL1TSyYnnZxDbJM5k10G5j72bYHjbBkmXqxsruHfhLL +AIALyrg6bd8p3v0CgYBbkGP7lJUguRtQd2PwiR/TkWGYp7HATPkEP13c3JrGhKbeCuYlWKT+ +THp7fDc65qlUGoDHwo3GKXwjrA2l6JZTRQ8xAIzNjKM5o2LJ+1v2bbK7HX8J8Y9UiBp5ag6f +aal6vZl6aPUXk0vxlHWEM6VHGBHz7LQgWZ1b3DA8WNKqEQKBgQDPnXNz8O+SPPaGc92bRNQW +2CPB56qkJm/rPrYTO9N+DBKvALcuZHnujI5ph1wmiUdkSe/k99ihiCk/dncXa8MkBPbf6BmD +0EfrNr2oyMc3yMXz2CRzEh+zDZPCXwFH4C5oZ3vVwO4A8Sm3L9sibJw/FMqAvVWWGw+iRqRo +0IIkAA== +-----END RSA PRIVATE KEY-----` + +const publicKey = `-----BEGIN RSA PUBLIC KEY----- +MIIBCgKCAQEApU4JW+EgeFUZG2hI3n7C0x8/gSerp1Ga90JOTkeH9+KL+FU/wankZCBxcHp/ +NgBlbuvdrdD8/Ym3CYwNiqC7CETztkXPRI4hWG2Z/eYZ4D9GKnxFviAAJ4TNr7esWjN7s12w +noD4KM5I9agKHobMGMPbiifOeYtgj2mVrkqlowlgw/WnpcPjXCnEXt6fns3LRPpsmruIuCX3 +G4P9Sv+D3BK/RWSjXtfohmTLdo7mfg9fDJAv3I9N93kD1zZXanpQJE5UjmuUzpMtHW202rxi +b5Y412Ds0qKC5AtgfM9BTUIaz9KVP2Y9YXgM6QdOL2zgNJHJUE7sUIw36fJ258FM2wIDAQAB +-----END RSA PUBLIC KEY-----` + +const actions = { + readFile: () => Promise.resolve(publicKey), + createRealm: realm => Promise.resolve(realm), + // getRealm: realm => + // Promise.resolve(realm === 'demo' ? { realmId: 'demo', auth_uri: 'http://auth.uri/demo/login' } : null), + log: { + debug: () => null, + info: () => null, + error: () => null, + }, +} + +describe('create-realm', () => { + test('no realm fails', () => { + expect.assertions(1) + + const request = {} + + return createRealm(request, actions) + .catch(err => expect(err).toBe('[400] "realm" is required')) + }) + + test('no access_token fails', () => { + expect.assertions(1) + + const request = { + pathParameters: { realm: 'realm' }, + } + + return createRealm(request, actions) + .catch(err => expect(err).toBe('[400] "access_token" is required')) + }) + + test('invalid access_token returns jwt malformed', () => { + expect.assertions(2) + + const request = { + pathParameters: { realm: 'realm' }, + body: querystring.stringify({ + access_token: 'access_token', + }) + } + + const myActions = R.set(R.lensPath(['log', 'error']), err => { + expect(err).toMatch(/JsonWebTokenError: jwt malformed/) + }, actions) + + return createRealm(request, myActions) + .catch(err => expect(err).toBe('[500] server_error')) + }) + + test('non default realm returns Not Authorized', () => { + expect.assertions(2) + + const request = { + pathParameters: { realm: 'mojo:default' }, + body: querystring.stringify({ + access_token: invalidRealmRefreshToken, + }) + } + + const myActions = R.set(R.lensPath(['log', 'error']), err => { + expect(err).toBe('Not Authorized') + }, actions) + + return createRealm(request, myActions) + .catch(err => expect(err).toBe('[500] server_error')) + }) + + test('invalid realm returns Not Authorized', () => { + expect.assertions(2) + + const request = { + pathParameters: { realm: 'mojo:default' }, + body: querystring.stringify({ + access_token: anotherUserRefreshToken, + }) + } + + const myActions = R.set(R.lensPath(['log', 'error']), err => { + expect(err).toBe('Not Authorized') + }, actions) + + return createRealm(request, myActions) + .catch(err => expect(err).toBe('[500] server_error')) + }) + + test('user doesn\'t own realm returns Not Authorized', () => { + expect.assertions(2) + + const request = { + pathParameters: { realm: 'mojo:default' }, + body: querystring.stringify({ + access_token: invalidRealmRefreshToken, + }) + } + + const myActions = R.set(R.lensPath(['log', 'error']), err => { + expect(err).toBe('Not Authorized') + }, actions) + + return createRealm(request, myActions) + .catch(err => expect(err).toBe('[500] server_error')) + }) + + test('realm already exists return [409] Realm already exists', () => { + expect.assertions(1) + + const request = { + pathParameters: { realm: 'mojo:default' }, + body: querystring.stringify({ + access_token: refreshToken, + }) + } + + const myActions = Object.assign({}, actions, { + createRealm: realm => Promise.reject('Realm already exists') + }) + + return createRealm(request, myActions) + .catch(err => expect(err).toBe('[409] Realm already exists')) + }) + + test('user creates realm', () => { + expect.assertions(1) + + const request = { + pathParameters: { realm: 'mojo:default' }, + body: querystring.stringify({ + access_token: refreshToken, + }) + } + + return createRealm(request, actions) + .then(data => expect(data).toEqual({ owner: 'mojo', realmId: 'mojo:default' })) + }) +}) diff --git a/actions/token/__tests__/index.test.js b/actions/token/__tests__/index.test.js index 0785dec..4a24c7c 100644 --- a/actions/token/__tests__/index.test.js +++ b/actions/token/__tests__/index.test.js @@ -53,8 +53,8 @@ const log = { const readFile = file => file === config.get('certs.privateKey') ? Promise.resolve(privateKey) - : file === config.get('certs.publicKey') ? Promise.resolve(publicKey) - : Promise.reject('invalid file') + : file === config.get('certs.publicKey') ? Promise.resolve(publicKey) + : Promise.reject('invalid file') describe('actions.token', () => { test('with no grant_type fails', () => { @@ -179,7 +179,7 @@ describe('actions.token', () => { expect.assertions(3) const request = { - pathParameters: { realm: 'realm' }, + pathParameters: { realm: 'mojo:default' }, body: querystring.stringify({ grant_type: 'password', client_id: 'client_id', @@ -325,7 +325,7 @@ describe('actions.token', () => { expect.assertions(1) const request = { - pathParameters: { realm: 'realm' }, + pathParameters: { realm: 'mojo:default' }, body: querystring.stringify({ grant_type: 'refresh_token', client_id: 'client_id', @@ -337,9 +337,7 @@ describe('actions.token', () => { } return token(request, mocks) - .then(token => { - expect(token.access_token).toBeTruthy() - }) + .then(token => expect(token.access_token).toBeTruthy()) }) test('with redirect_uri redirects', () => {