├── .gitignore ├── awsm ├── authenticate │ ├── event.json │ ├── handler.js │ ├── index.js │ └── awsm.json ├── create │ ├── event.json │ ├── handler.js │ ├── index.js │ └── awsm.json └── list │ ├── event.json │ ├── handler.js │ ├── index.js │ └── awsm.json ├── index.js ├── lib ├── list.js ├── verify.js ├── authenticate.js └── create.js ├── package.json ├── awsm.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /awsm/authenticate/event.json: -------------------------------------------------------------------------------- 1 | { "email": "jacob@jaws.com", "password": "password" } -------------------------------------------------------------------------------- /awsm/create/event.json: -------------------------------------------------------------------------------- 1 | { "email": "jaws@jaws.com", "password": "password" } -------------------------------------------------------------------------------- /awsm/list/event.json: -------------------------------------------------------------------------------- 1 | {"jwt": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1aWQiOiJ1XzM5YWU3YzMwLTY0YjYtMTFlNS1hNmU5LWI5YzJlNWFhZjgwZSIsImlhdCI6MTQ0MzMyNTg0OSwiZXhwIjoxNDQzMzI2NDQ5fQ.cObf-xffrujyWzs2S5HnbHjM2l_ycYixnjKueXInDyI"} -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | exports.listUsers = require('./lib/list.js'); 2 | exports.verify = require('./lib/verify.js'); 3 | exports.createUser = require('./lib/create.js'); 4 | exports.authenticate = require('./lib/authenticate.js'); 5 | -------------------------------------------------------------------------------- /awsm/create/handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('jaws-core-js/env'); 4 | 5 | var action = require('./index.js'); 6 | 7 | module.exports.handler = function(event, context) { 8 | action.run(event, context, function(error, result) { 9 | return context.done(error, result); 10 | }); 11 | }; 12 | -------------------------------------------------------------------------------- /awsm/list/handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('jaws-core-js/env'); 4 | 5 | var action = require('./index.js'); 6 | 7 | module.exports.handler = function(event, context) { 8 | action.run(event, context, function(error, result) { 9 | return context.done(error, result); 10 | }); 11 | }; 12 | -------------------------------------------------------------------------------- /awsm/authenticate/handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('jaws-core-js/env'); 4 | 5 | var action = require('./index.js'); 6 | 7 | module.exports.handler = function(event, context) { 8 | action.run(event, context, function(error, result) { 9 | return context.done(error, result); 10 | }); 11 | }; 12 | -------------------------------------------------------------------------------- /awsm/create/index.js: -------------------------------------------------------------------------------- 1 | var action = require('awsm-users').createUser; 2 | 3 | module.exports.run = function(event, context, cb) { 4 | console.log(action); 5 | return action(event) 6 | .then(function(result) { 7 | cb(null, result); 8 | }) 9 | .error(function(error) { 10 | cb(error, null); 11 | }); 12 | }; 13 | -------------------------------------------------------------------------------- /lib/list.js: -------------------------------------------------------------------------------- 1 | var Promise = require("bluebird"), 2 | AWS = require('aws-sdk'), 3 | debug = require('debug')('awsm-users'); 4 | 5 | var dynamodb = new AWS.DynamoDB(); 6 | Promise.promisifyAll(Object.getPrototypeOf(dynamodb)); 7 | 8 | module.exports = function() { 9 | return dynamodb.scanAsync({ TableName: process.env.USERS_TABLE }); 10 | } 11 | -------------------------------------------------------------------------------- /awsm/authenticate/index.js: -------------------------------------------------------------------------------- 1 | var action = require('awsm-users').authenticate; 2 | 3 | module.exports.run = function(event, context, cb) { 4 | return action(event) 5 | .then(function(result) { 6 | cb(null, { status: 201, jwt: result }); 7 | }) 8 | .error(function(error) { 9 | cb(null, { status: 400, message: 'Authentication Failed' }); 10 | }); 11 | }; 12 | -------------------------------------------------------------------------------- /awsm/list/index.js: -------------------------------------------------------------------------------- 1 | var verify = require('awsm-users').verify; 2 | var listUsers = require('awsm-users').listUsers; 3 | 4 | module.exports.run = function(event, context, cb) { 5 | return verify(event.Authorization) 6 | .then(listUsers) 7 | .then(function(result) { 8 | cb(null, result); 9 | }) 10 | .error(function(error) { 11 | cb(error, null); 12 | }); 13 | }; 14 | -------------------------------------------------------------------------------- /lib/verify.js: -------------------------------------------------------------------------------- 1 | var Promise = require('bluebird'), 2 | jwt = require('jsonwebtoken'); 3 | 4 | module.exports = function(token) { 5 | return new Promise(function(resolve, reject) { 6 | jwt.verify(token, process.env.JWT_SECRET,function(err, data) { 7 | if (err) { 8 | reject(err); 9 | } else { 10 | resolve(token); 11 | } 12 | }) 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "awsm-users", 3 | "version": "0.0.3", 4 | "description": "AWSM module for user authentication", 5 | "author": "dekz", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "git://github.com/dekz/awsm-users" 10 | }, 11 | "keywords": ["awsm"], 12 | "devDependencies": {}, 13 | "dependencies": { 14 | "bcryptjs": "^2.3.0", 15 | "bluebird": "^2.10.0", 16 | "debug": "^2.2.0", 17 | "dotenv": "^1.2.0", 18 | "jsonwebtoken": "^5.0.5", 19 | "moment": "^2.10.6", 20 | "node-uuid": "^1.4.3" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /awsm/create/awsm.json: -------------------------------------------------------------------------------- 1 | { 2 | "lambda": { 3 | "envVars": ["USERS_TABLE"], 4 | "deploy": true, 5 | "package": { 6 | "optimize": { 7 | "builder": "browserify", 8 | "minify": true, 9 | "ignore": [], 10 | "exclude": [ 11 | "aws-sdk" 12 | ], 13 | "includePaths": [ ] 14 | }, 15 | "excludePatterns": [] 16 | }, 17 | "cloudFormation": { 18 | "Description": "", 19 | "Handler": "aws_modules/awsm-users/create/handler.handler", 20 | "MemorySize": 1024, 21 | "Runtime": "nodejs", 22 | "Timeout": 6 23 | } 24 | }, 25 | "apiGateway": { 26 | "deploy": false, 27 | "cloudFormation": { 28 | "Type": "AWS", 29 | "Path": "users/create", 30 | "Method": "POST", 31 | "AuthorizationType": "none", 32 | "ApiKeyRequired": false, 33 | "RequestTemplates": {}, 34 | "RequestParameters": {}, 35 | "Responses": { 36 | "400": { 37 | "statusCode": "400" 38 | }, 39 | "default": { 40 | "statusCode": "200", 41 | "responseParameters": {}, 42 | "responseTemplates": { 43 | "application/json": "" 44 | } 45 | } 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /awsm/authenticate/awsm.json: -------------------------------------------------------------------------------- 1 | { 2 | "lambda": { 3 | "envVars": ["JWT_SECRET", "JWT_ISSUER", "USERS_TABLE"], 4 | "deploy": false, 5 | "package": { 6 | "optimize": { 7 | "builder": "browserify", 8 | "minify": true, 9 | "ignore": [], 10 | "exclude": [ 11 | "aws-sdk" 12 | ], 13 | "includePaths": [ ] 14 | }, 15 | "excludePatterns": [] 16 | }, 17 | "cloudFormation": { 18 | "Description": "", 19 | "Handler": "aws_modules/awsm-users/authenticate/handler.handler", 20 | "MemorySize": 1024, 21 | "Runtime": "nodejs", 22 | "Timeout": 6 23 | } 24 | }, 25 | "apiGateway": { 26 | "deploy": false, 27 | "cloudFormation": { 28 | "Type": "AWS", 29 | "Path": "users/authenticate", 30 | "Method": "POST", 31 | "AuthorizationType": "none", 32 | "ApiKeyRequired": false, 33 | "RequestTemplates": {}, 34 | "RequestParameters": {}, 35 | "Responses": { 36 | "400": { 37 | "statusCode": "400" 38 | }, 39 | "default": { 40 | "statusCode": "200", 41 | "responseParameters": {}, 42 | "responseTemplates": { 43 | "application/json": "" 44 | } 45 | } 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /awsm/list/awsm.json: -------------------------------------------------------------------------------- 1 | { 2 | "lambda": { 3 | "envVars": ["USERS_TABLE"], 4 | "deploy": false, 5 | "package": { 6 | "optimize": { 7 | "builder": "browserify", 8 | "minify": true, 9 | "ignore": [], 10 | "exclude": [ 11 | "aws-sdk" 12 | ], 13 | "includePaths": [ ] 14 | }, 15 | "excludePatterns": [] 16 | }, 17 | "cloudFormation": { 18 | "Description": "", 19 | "Handler": "aws_modules/awsm-users/list/handler.handler", 20 | "MemorySize": 1024, 21 | "Runtime": "nodejs", 22 | "Timeout": 6 23 | } 24 | }, 25 | "apiGateway": { 26 | "deploy": false, 27 | "cloudFormation": { 28 | "Type": "AWS", 29 | "Path": "users/list", 30 | "Method": "GET", 31 | "AuthorizationType": "none", 32 | "ApiKeyRequired": false, 33 | "RequestTemplates": { 34 | "application/json": "{\"Authorization\":\"$input.params('Authorization')\"}" 35 | }, 36 | "RequestParameters": {}, 37 | "Responses": { 38 | "400": { 39 | "statusCode": "400" 40 | }, 41 | "default": { 42 | "statusCode": "200", 43 | "responseParameters": {}, 44 | "responseTemplates": { 45 | "application/json": "" 46 | } 47 | } 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /awsm.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "awsm-users", 3 | "version": "0.0.1", 4 | "location": "https://github.com/dekz/awsm-users", 5 | "author": "dekz ", 6 | "description": "Authentication Lifecycle for JAWS", 7 | "resources": { 8 | "cloudFormation": { 9 | "ApiGatewayIamPolicyDocumentStatements": [], 10 | "LambdaIamPolicyDocumentStatements": [ 11 | { 12 | "Effect": "Allow", 13 | "Action": [ "*" ], 14 | "Resource": { 15 | "Fn::Join": [ 16 | ":", 17 | [ "arn:aws:dynamodb", { "Ref": "AWS::Region" }, "*", "table/jaws-users" ] 18 | ] 19 | } 20 | } 21 | ], 22 | "Resources": { 23 | "DynamoDB": { 24 | "Type" : "AWS::DynamoDB::Table", 25 | "Properties": { 26 | "AttributeDefinitions": [ 27 | { 28 | "AttributeName": "email", 29 | "AttributeType": "S" 30 | } 31 | ], 32 | "KeySchema" : [ 33 | { 34 | "AttributeName": "email", 35 | "KeyType": "HASH" 36 | } 37 | ], 38 | "ProvisionedThroughput" : { 39 | "ReadCapacityUnits": { "Ref" : "aaDefaultDynamoRWThroughput" }, 40 | "WriteCapacityUnits": { "Ref" : "aaDefaultDynamoRWThroughput" } 41 | }, 42 | "TableName": "jaws-users" 43 | } 44 | } 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/authenticate.js: -------------------------------------------------------------------------------- 1 | var Promise = require('bluebird'), 2 | AWS = require('aws-sdk'), 3 | bcryptjs = require('bcryptjs'), 4 | jwt = require('jsonwebtoken'), 5 | debug = require('debug')('awsm-users'); 6 | 7 | var dynamodb = new AWS.DynamoDB(); 8 | Promise.promisifyAll(Object.getPrototypeOf(dynamodb)); 9 | 10 | var getUser = function(data) { 11 | return new Promise(function(resolve, reject) { 12 | dynamodb.getItemAsync ({ 13 | TableName: process.env.USERS_TABLE, 14 | Key: { 15 | email: { S: data.email } 16 | } 17 | }).then(function(result) { 18 | if (result && 'Item' in result) { 19 | var user = { 20 | id: result.Item.id.S, 21 | email: result.Item.email.S, 22 | password: result.Item.password.S 23 | } 24 | resolve([data, user]); 25 | } else { reject(data) } 26 | }); 27 | }); 28 | } 29 | 30 | var authenticate = function(data, user) { 31 | var user = data[1], 32 | data = data[0]; 33 | return new Promise(function(resolve, reject) { 34 | if (bcryptjs.compareSync(data.password, user.password)) { 35 | resolve(user); 36 | } else { 37 | debug('failed auth compare'); 38 | reject(data); 39 | } 40 | }); 41 | } 42 | 43 | var createToken = function(user) { 44 | return new Promise(function(resolve, reject) { 45 | var token = jwt.sign({ 46 | uid: user.id, 47 | }, process.env.JWT_SECRET, { 48 | issuer: process.env.JWT_ISSUER, 49 | expiresInMinutes: 10 50 | }); 51 | 52 | resolve(token); 53 | }); 54 | } 55 | 56 | module.exports = function(event) { 57 | return getUser(event) 58 | .then(authenticate) 59 | .then(createToken); 60 | }; 61 | -------------------------------------------------------------------------------- /lib/create.js: -------------------------------------------------------------------------------- 1 | var Promise = require('bluebird'), 2 | AWS = require('aws-sdk'), 3 | bcryptjs = require('bcryptjs'), 4 | moment = require('moment'), 5 | uuid = require('node-uuid'), 6 | debug = require('debug')('awsm-users'); 7 | 8 | var dynamodb = new AWS.DynamoDB(); 9 | Promise.promisifyAll(Object.getPrototypeOf(dynamodb)); 10 | 11 | var validateInput = function(data) { 12 | return new Promise(function(resolve, reject) { 13 | resolve(data); 14 | }); 15 | } 16 | 17 | var secureUser = function(user) { 18 | return new Promise(function(resolve, reject) { 19 | user.salt = bcryptjs.genSaltSync(10); 20 | user.password = bcryptjs.hashSync(user.password, user.salt); 21 | resolve(user); 22 | }); 23 | } 24 | 25 | var createUser = function(data) { 26 | return new Promise(function(resolve, reject) { 27 | var user = { 28 | id: 'u_' + uuid.v1(), 29 | email: data.email, 30 | password: data.password, 31 | created: moment().unix().toString(), 32 | updated: moment().unix().toString(), 33 | } 34 | resolve(user); 35 | }); 36 | } 37 | 38 | var storeUser = function(user) { 39 | debug('Saving ' + JSON.stringify(user)); 40 | return dynamodb.putItemAsync({ 41 | TableName: process.env.USERS_TABLE, 42 | Item: { 43 | id: { S: user.id }, 44 | email: { S: user.email }, 45 | password: { S: user.password }, 46 | salt: { S: user.salt }, 47 | created: { S: user.created }, 48 | updated: { S: user.updated }, 49 | }, 50 | ConditionExpression: 'attribute_not_exists (id)' 51 | }); 52 | } 53 | 54 | module.exports = function(event) { 55 | return validateInput(event) 56 | .then(createUser) 57 | .then(secureUser) 58 | .then(storeUser); 59 | }; 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Awsm Users 2 | AWSM Users is an example [awsm](https://github.com/awsm-org/awsm) module around the lifecycle of authentication. 3 | 4 | ## Requirements 5 | Jaws 1.3+ 6 | 7 | ### What does this project demonstatrate? 8 | * Custom API endpoint request templates. 9 | * Additional Cloudformation Resources (DynamoDB) and IAM roles 10 | * Shared library code in `lib` 11 | * [JWT](http://jwt.io/) 12 | * Other HTTP Api endpoint methods 13 | 14 | ## Usage 15 | 16 | In your JAWS project root directory, run: 17 | ``` 18 | npm install --save awsm-users 19 | npm install 20 | jaws postinstall awsm-users npm 21 | jaws deploy resources 22 | jaws dash 23 | 24 | # Create a User with a POST to /users/create 25 | curl -X POST -H "Content-Type: application/json" -H "Cache-Control: no-cache" -d '{ 26 | "email": "jacob@jaws.com", 27 | "password": "password" 28 | }' '/users/create' 29 | # Authenticate a User with a POST to /users/authenticate. This returns a JWT token 30 | curl -X POST -H "Content-Type: application/json" -H "Cache-Control: no-cache" -d '{ 31 | "email": "jacob@jaws.com", 32 | "password": "password" 33 | }' '/users/authenticate' 34 | # Hit the List endpoint which is behind an authentication wall 35 | curl -H "Authorization: " "/users/list" 36 | ``` 37 | 38 | This will install the awsm modules into your project and save the resource creations into your cloudformation. 39 | 40 | 41 | 42 | ## Environment Variables 43 | You will need to populate the environment variables provided by the `jaws env list` command, here is an example: 44 | ``` 45 | ~/t/j/myproject ❯❯❯ jaws env list dev us-east-1 46 | JAWS: Getting ENV file from S3 bucket: jaws-project in us-east-1 47 | JAWS: ENV vars for stage dev: 48 | JAWS: ------------------------------ 49 | JAWS: us-east-1 50 | JAWS: ------------------------------ 51 | JAWS_STAGE=dev 52 | JAWS_DATA_MODEL_STAGE=dev 53 | USERS_TABLE=jaws-users 54 | JWT_SECRET=abcd 55 | JWT_ISSUER=jacob 56 | 57 | 58 | JAWS: awsm.json:lambda.envVars and regions where they are used (red means NOT defined in region): 59 | JAWS: ------------------------------ 60 | JAWS: USERS_TABLE 61 | JAWS: ------------------------------ 62 | JAWS: aws mods using: users/create/awsm.json,users/authenticate/awsm.json,users/list/awsm.json 63 | JAWS: regions: us-east-1 64 | 65 | JAWS: ------------------------------ 66 | JAWS: JWT_SECRET 67 | JAWS: ------------------------------ 68 | JAWS: aws mods using: users/authenticate/awsm.json 69 | JAWS: regions: us-east-1 70 | 71 | JAWS: ------------------------------ 72 | JAWS: JWT_ISSUER 73 | JAWS: ------------------------------ 74 | JAWS: aws mods using: users/authenticate/awsm.json 75 | JAWS: regions: us-east-1 76 | ``` 77 | 78 | USERS_TABLE must be set to: `jaws-users` (for now) 79 | 80 | ## Can I use this project in Production 81 | No 82 | 83 | ## TODO 84 | * [x] Create Users 85 | * [x] List Users 86 | * [x] Authenticate Users 87 | * [ ] Delete Users 88 | 89 | ## Putting a lambda behind an authentication wall 90 | 91 | Your API endpoint must pull out the Autheorization parameter and pass that through. Here is an example of a Request Template which pulls out the Auth token and sets it onto the `event`. See [list awsm.json](https://github.com/dekz/awsm-users/blob/master/awsm/list/awsm.json#L33) 92 | ```json 93 | "RequestTemplates": { 94 | "application/json": "{\"Authorization\":\"$input.params('Authorization')\"}" 95 | } 96 | ``` 97 | 98 | Verify before doing any work in your Lambda, See [list lambda](https://github.com/dekz/awsm-users/blob/master/awsm/list/index.js#L5) as an example. 99 | ```javascript 100 | module.exports.run = function(event, context, cb) { 101 | return verify(event.Authorization) 102 | .then(action) 103 | .then(function(result) { 104 | cb(null, result); 105 | }) 106 | .error(function(error) { 107 | debug('List Users Failed: %s', JSON.stringify(error)); 108 | cb(error, null); 109 | }); 110 | }; 111 | 112 | ``` 113 | --------------------------------------------------------------------------------