├── .babelrc ├── .gitignore ├── .jshintrc ├── .npmignore ├── README.md ├── index.js ├── learningJs ├── arguments.js ├── error.js ├── exportObj │ ├── config.js │ ├── modifyObjAndPrint.js │ ├── obj.js │ ├── objThis.js │ ├── otherObj.js │ └── testConfig.js ├── object.js └── test_mailer.js ├── package.json ├── src ├── config.js ├── index.js ├── models │ └── User.js ├── router │ ├── email_verification.js │ ├── index.js │ ├── login.js │ ├── password_reset.js │ └── signup.js └── utils │ ├── createToken.js │ ├── crypts.js │ ├── sendMail.js │ ├── test │ └── crypts.js │ └── verifyToken.js ├── test ├── email_verification.js ├── index.js ├── login.js ├── needingToken.js ├── needingTokenAndEmailVerified.js ├── password_reset.js ├── signup.js └── testConfig.js ├── testServer.js └── todo.md /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015","stage-0"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | database 3 | lib 4 | *.log 5 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | /* 3 | * ENVIRONMENTS 4 | * ================= 5 | */ 6 | 7 | // Define globals exposed by modern browsers. 8 | "browser": true, 9 | 10 | // Define globals exposed by jQuery. 11 | "jquery": true, 12 | 13 | // Define globals exposed by Node.js. 14 | "node": true, 15 | 16 | // Allow ES6. 17 | "esnext": true, 18 | 19 | // Allow mocha 20 | "mocha": true, 21 | 22 | /* 23 | * ENFORCING OPTIONS 24 | * ================= 25 | */ 26 | 27 | // Force all variable names to use either camelCase style or UPPER_CASE 28 | // with underscores. 29 | "camelcase": true, 30 | 31 | // Prohibit use of == and != in favor of === and !==. 32 | "eqeqeq": true, 33 | 34 | // Enforce tab width of 2 spaces. 35 | "indent": 2, 36 | 37 | // Prohibit use of a variable before it is defined. 38 | "latedef": true, 39 | 40 | // Enforce line length to 80 characters 41 | "maxlen": 80, 42 | 43 | // Require capitalized names for constructor functions. 44 | "newcap": true, 45 | 46 | // Enforce use of single quotation marks for strings. 47 | "quotmark": "single", 48 | 49 | // Enforce placing 'use strict' at the top function scope 50 | "strict": false, 51 | 52 | // Prohibit use of explicitly undeclared variables. 53 | "undef": true, 54 | 55 | // Warn when variables are defined but never used. 56 | "unused": true, 57 | 58 | /* 59 | * RELAXING OPTIONS 60 | * ================= 61 | */ 62 | 63 | // Suppress warnings about == null comparisons. 64 | "eqnull": true 65 | } 66 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | database 2 | src 3 | learningJs 4 | test 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > As a rest API starter: see the [starter branch](https://github.com/timqian/auth-api/tree/jwtAuth-RESTful-server-starter-2.0) 2 | 3 | ## Purpose 4 | 5 | Reuse authentication part code of REST server, easily and flexibly. 6 | Thanks to `express.Router`. 7 | 8 | ## Features 9 | 10 | - [jwt](https://github.com/auth0/node-jsonwebtoken) to verify user; 11 | - [nodemailer](https://github.com/nodemailer/nodemailer) to send verification emails; 12 | - [mongoose](https://github.com/Automattic/mongoose) to drive mongodb (user model: https://github.com/timqian/auth-api/blob/master/src/models/User.js); 13 | - [axios](https://github.com/mzabriskie/axios) to test RESTful api (axios can be used both on browser and node, that means the test code can be reused in your web app); 14 | 15 | ## Sample usage: 16 | 17 | 1. Install `auth-api` and his peerDependencies: 18 | 19 | `npm install auth-api express body-parser mongoose --save` 20 | 21 | 2. Run the sample code below and boom~~ the auth server will be listening at `http://localhost:3000` 22 | 23 | ```javascript 24 | var authApi = require('auth-api'); 25 | var express = require('express'); 26 | var bodyParser = require('body-parser'); 27 | var mongoose = require('mongoose'); 28 | 29 | mongoose.connect('mongodb://localhost/database'); // connect to database 30 | 31 | var userConfig = { 32 | APP_NAME: 'STOCK APP', 33 | SECRET: 'ilovetim', // jwt secret 34 | CLIENT_TOKEN_EXPIRES_IN: 60 * 24 * 60 * 60, // client token expires time(60day) 35 | EMAIL_TOKEN_EXPIRES_IN: 24 * 60 * 60, // email token expires time(24h) 36 | 37 | EMAIL_SENDER: { // used to send mail by nodemailer 38 | service: 'Gmail', 39 | auth: { 40 | user: 'qianlijiang123@gmail.com', 41 | pass: '321qianqian', 42 | } 43 | }, 44 | 45 | USER_MESSAGE: { // message sent to client 46 | MAIL_SENT: 'mail sent', 47 | NAME_TAKEN: 'Name or email has been taken', 48 | USER_NOT_FOUND: 'User not found', 49 | WRONG_PASSWORD: 'wrong password', 50 | LOGIN_SUCCESS: 'Enjoy your token!', 51 | NEED_EMAIL_VERIFICATION: 'You need to verify your email first', 52 | }, 53 | 54 | API_URL: 'http://localhost:3000' // to be used in the mail 55 | }; 56 | 57 | authApi.init(userConfig); 58 | 59 | var app = express(); 60 | app.use(bodyParser.urlencoded({ extended: false })); 61 | app.use(bodyParser.json()); 62 | app.use('/', authApi.authRouter); 63 | 64 | // protecting api 65 | app.get('/needingToken', authApi.verifyToken, (req, res) => { 66 | 67 | // send back the jwt claim directly 68 | var claim = req.decoded; 69 | res.status(200).json(claim); 70 | }); 71 | 72 | app.get('/needingTokenAndEmailVerified', authApi.verifyToken, (req, res) => { 73 | if (req.decoded.verified) { 74 | res.status(200).json(req.decoded); 75 | } else { 76 | res.status(400).json({ 77 | success: false, 78 | message: 'Please verify your email before doing this!' 79 | }); 80 | } 81 | }); 82 | 83 | 84 | app.listen(3000); 85 | console.log('API magic happens at http://localhost:3000'); 86 | 87 | // handle unhandled promise rejection 88 | // https://nodejs.org/api/process.html#process_event_unhandledrejection 89 | process.on('unhandledRejection', function(reason, p) { 90 | console.log('Unhandled Rejection at: Promise ', p, ' reason: ', reason); 91 | // application specific logging, throwing an error, or other logic here 92 | }); 93 | ``` 94 | 95 | (es6 sample: https://github.com/timqian/auth-api/blob/master/testServer.js) 96 | 97 | ## What does the above code do for you 98 | 99 | 1. Generate the following auth api for you at `http://localhost:3000` 100 | 101 | 102 | |Method| url | data(if needed) | server action(if request is good) | 103 | | ---- |---------------------| ---------------------------------------------| -------------| 104 | | POST | /signup | {name: ..., email: ..., password: ...} |create a user in mongodb and send verification email | 105 | | POST | /login | {name/email: ..., password: ...} |check user and return jwt token| 106 | | POST | /password_reset | {email: ..., password(the new password): ...}| send verification link to email | 107 | | GET | /email_verification | | verify token and change password | 108 | 109 | (more details in the code) 110 | 111 | 112 | ## Module api 113 | 114 | - `authApi.init(config)`: configure the module 115 | - `authApi.authRouter`: an express router I wrote for you 116 | - `authApi.verifyToken`: an express middleware used to verify token sent by client 117 | 118 | ## TODOS 119 | 120 | - [ ] better http status code 121 | - [ ] better config params 122 | - [ ] docs 123 | - [ ] new feature 124 | 125 | ## license 126 | 127 | MIT 128 | 129 | 130 | 131 | [![paypal donate][paypal-image]][paypal-url] 132 | [paypal-image]: https://www.paypal.com/en_US/i/btn/btn_donate_SM.gif 133 | [paypal-url]: https://www.paypal.com/cgi-bin/webscr?cmd=_xclick&business=timqian92@qq.com¤cy_code=USD&amount=1&return=https://github.com/timqian&item_name=timqian&undefined_quantity=1&no_note=0 134 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('babel-polyfill'); 2 | module.exports = require('./lib').default; 3 | -------------------------------------------------------------------------------- /learningJs/arguments.js: -------------------------------------------------------------------------------- 1 | // arguments in function!! 2 | 3 | function getArgs() { 4 | console.log(arguments[0]); 5 | } 6 | 7 | getArgs(1, 2); 8 | getArgs({a:3}); 9 | -------------------------------------------------------------------------------- /learningJs/error.js: -------------------------------------------------------------------------------- 1 | // throw 'err' 会被 catch 到. 如果没有 catch, 程序报错后退出 2 | 3 | function throwErr() { 4 | throw 'err'; 5 | } 6 | try { 7 | throwErr(); 8 | } catch (e) { 9 | console.log('error catched:', e); 10 | } 11 | 12 | 13 | function throwErr2() { 14 | throw new Error('hi'); 15 | } 16 | try { 17 | throwErr2(); 18 | } catch (e) { 19 | console.log('new Error(e) catched', e); 20 | } 21 | 22 | // throwErr(); 23 | throwErr2(); 24 | console.log('continue'); 25 | -------------------------------------------------------------------------------- /learningJs/exportObj/config.js: -------------------------------------------------------------------------------- 1 | export default (function() { 2 | const config = { 3 | APP_NAME: 'TEST', 4 | SECRET: 'ilovetim', // jwt secret 5 | DATABASE: 'mongodb://localhost/database', // mongodb url 6 | BASEURL: 'http://localhost:3000', // api url 7 | EXPIRES_IN: 24 * 60 * 60, // token expires time(24 hours) 8 | 9 | EMAIL_SENDER: { // used to send mail by nodemailer 10 | service: 'Gmail', 11 | auth: { 12 | user: 'qianlijiang123@gmail.com', 13 | pass: '321qianqian', 14 | } 15 | }, 16 | 17 | USER_MESSAGE: { // message sent to client 18 | MAIL_SENT: 'mail sent', 19 | NAME_TAKEN: 'Name or email has been taken', 20 | USER_NOT_FOUND: 'User not found', 21 | WRONG_PASSWORD: 'wrong password', 22 | LOGIN_SUCCESS: 'Enjoy your token!', 23 | NEED_EMAIL_VERIFICATION: 'You need to verify your email first', 24 | }, 25 | 26 | EMAIL_RECEIVING_VERIFICATION: 'timqian92@qq.com', 27 | }; 28 | 29 | function init (userConfig) { 30 | Object.keys(userConfig).forEach((key) => { 31 | if (key) config[key] = this[key]; 32 | }); 33 | } 34 | 35 | return { 36 | config, 37 | init, 38 | }; 39 | })(); 40 | -------------------------------------------------------------------------------- /learningJs/exportObj/modifyObjAndPrint.js: -------------------------------------------------------------------------------- 1 | // 看看对另一个文件中 export 出来的 obj 操作会不会改变这个obj,答案是显而易见的:当然会! 2 | 3 | import {obj, a} from './obj'; 4 | 5 | obj.a = 38; 6 | 7 | 8 | console.log('next obj:', obj); 9 | console.log('next a:', a); 10 | 11 | // a = 38; 不能改变 a, a is readonly 12 | -------------------------------------------------------------------------------- /learningJs/exportObj/obj.js: -------------------------------------------------------------------------------- 1 | export let obj = { 2 | a: 1, 3 | b: 3 4 | }; 5 | 6 | console.log('obj:', obj); 7 | 8 | export let a = 3; 9 | 10 | console.log('a:', a); 11 | -------------------------------------------------------------------------------- /learningJs/exportObj/objThis.js: -------------------------------------------------------------------------------- 1 | const obj = { 2 | a: 3, 3 | fu: function() { 4 | console.log(this.a); 5 | } 6 | }; 7 | 8 | obj.fu(); 9 | -------------------------------------------------------------------------------- /learningJs/exportObj/otherObj.js: -------------------------------------------------------------------------------- 1 | import {obj} from './obj'; 2 | console.log('next obj:', obj); 3 | -------------------------------------------------------------------------------- /learningJs/exportObj/testConfig.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | console.log(config.config); 4 | -------------------------------------------------------------------------------- /learningJs/object.js: -------------------------------------------------------------------------------- 1 | // 使用 Object.keys() 2 | 3 | var obj = { 4 | a: 1, 5 | b: 2, 6 | }; 7 | 8 | console.log(Object.keys(obj)); 9 | console.log(Object.keys({})); 10 | 11 | Object.keys({}).forEach((item) => { 12 | console.log(item); 13 | }); 14 | -------------------------------------------------------------------------------- /learningJs/test_mailer.js: -------------------------------------------------------------------------------- 1 | // nodemailer 基本使用 2 | 3 | var nodemailer = require('nodemailer'); 4 | 5 | // create reusable transporter object using SMTP transport 6 | var transporter = nodemailer.createTransport({ 7 | service: 'Gmail', 8 | auth: { 9 | user: 'qianlijiang123@gmail.com', 10 | pass: '321qianqian' 11 | } 12 | }); 13 | 14 | // NB! No need to recreate the transporter object. You can use 15 | // the same transporter object for all e-mails 16 | 17 | // setup e-mail data with unicode symbols 18 | var mailOptions = { 19 | from: 'Fred Foo ✔ ', // sender address 20 | to: 'timqian92@qq.com', // list of receivers 21 | subject: 'Hello ✔', // Subject line 22 | text: 'Hello world ✔', // plaintext body 23 | html: 'Hello world ✔' // html body 24 | }; 25 | 26 | // send mail with defined transport object 27 | transporter.sendMail(mailOptions, function(error, info){ 28 | if(error){ 29 | return console.log(error); 30 | } 31 | console.log('Message sent: ' + info.response); 32 | 33 | }); 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "auth-api", 3 | "version": "2.0.0", 4 | "description": "generate auth api for you", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "nodemon --exec babel-node -- testServer.js", 8 | "test": "mocha --recursive --require babel-register --timeout 15000", 9 | "compile": "babel -d lib/ src/", 10 | "prepublish": "npm run compile" 11 | }, 12 | "keywords": [ 13 | "jwt", 14 | "express" 15 | ], 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/timqian/auth-api" 19 | }, 20 | "author": "timqian", 21 | "license": "MIT", 22 | "dependencies": { 23 | "babel-polyfill": "^6.3.14", 24 | "bcrypt": "^0.8.5", 25 | "es6-promisify": "^3.0.0", 26 | "jsonwebtoken": "^5.4.1", 27 | "nodemailer": "^1.10.0" 28 | }, 29 | "peerDependencies": { 30 | "express": "^4.0", 31 | "mongoose": "^4.0", 32 | "body-parser": "^1.0" 33 | }, 34 | "devDependencies": { 35 | "axios": "^0.7.0", 36 | "babel-cli": "^6.3.17", 37 | "babel-preset-es2015": "^6.3.13", 38 | "babel-preset-stage-0": "^6.3.13", 39 | "body-parser": "^1.14.1", 40 | "express": "^4.13.3", 41 | "method-override": "^2.3.5", 42 | "mocha": "^2.3.4", 43 | "mongoose": "^4.2.8", 44 | "morgan": "^1.6.1" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | // used to store config 2 | 3 | const config = { 4 | APP_NAME: 'TEST', 5 | SECRET: 'ilovetim', // jwt secret 6 | CLIENT_TOKEN_EXPIRES_IN: 24 * 60 * 60, // client token expires time TODO: test expiring 7 | EMAIL_TOKEN_EXPIRES_IN: 24 * 60 * 60, // email token expires time 8 | 9 | EMAIL_SENDER: { // used to send mail by nodemailer 10 | service: 'Gmail', 11 | auth: { 12 | user: 'qianlijiang123@gmail.com', 13 | pass: '321qianqian', 14 | } 15 | }, 16 | 17 | USER_MESSAGE: { // message sent to client 18 | MAIL_SENT: 'mail sent', 19 | NAME_TAKEN: 'Name or email has been taken', 20 | USER_NOT_FOUND: 'User not found', 21 | WRONG_PASSWORD: 'wrong password', 22 | LOGIN_SUCCESS: 'Enjoy your token!', 23 | NEED_EMAIL_VERIFICATION: 'You need to verify your email first', 24 | }, 25 | 26 | }; 27 | 28 | export default config; 29 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import router from './router'; 2 | import verifyToken from './utils/verifyToken'; 3 | import config from './config'; 4 | 5 | export default { 6 | init: (userConfig) => { 7 | Object.keys(userConfig).forEach((key) => { 8 | config[key] = userConfig[key]; 9 | }); 10 | // console.log(config); 11 | }, 12 | 13 | authRouter: router, 14 | 15 | verifyToken 16 | }; 17 | -------------------------------------------------------------------------------- /src/models/User.js: -------------------------------------------------------------------------------- 1 | // get an instance of mongoose and mongoose.Schema 2 | import mongoose from 'mongoose'; 3 | const Schema = mongoose.Schema; 4 | 5 | // set up a mongoose model and export 6 | const User = mongoose.model('User', new Schema({ 7 | email: String, 8 | name: { type: String, required: true, index: { unique: true } }, 9 | password: { type: String, required: true }, 10 | verified: Boolean, 11 | })); 12 | 13 | export default User; 14 | -------------------------------------------------------------------------------- /src/router/email_verification.js: -------------------------------------------------------------------------------- 1 | import User from '../models/User'; 2 | 3 | // set email verified 4 | export default async function(req, res) { 5 | const { name, hashedPassword } = req.decoded; 6 | 7 | const user = await User.findOne({ name }); 8 | if ( !hashedPassword ) { 9 | user.verified = true; 10 | await user.save(); 11 | 12 | res.status(200).json({ 13 | success: true, 14 | message: 'Your email account is now verified. Congratulations!' 15 | }); 16 | } else { 17 | user.verified = true; 18 | user.password = hashedPassword; 19 | await user.save(); 20 | 21 | res.status(200).json({ 22 | success: true, 23 | message: 'Your password is updated. Congratulations! ' 24 | }); 25 | 26 | } 27 | 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import signup from './signup'; 3 | import login from './login'; 4 | import email_verification from './email_verification'; 5 | import password_reset from './password_reset'; 6 | import verifyToken from '../utils/verifyToken'; 7 | 8 | const router = new Router(); 9 | 10 | router.post('/signup', signup); 11 | router.get('/email_verification', verifyToken, email_verification); 12 | router.post('/login', login); 13 | router.post('/password_reset', password_reset); 14 | 15 | export default router; 16 | -------------------------------------------------------------------------------- /src/router/login.js: -------------------------------------------------------------------------------- 1 | import User from '../models/User'; // get our mongoose model 2 | import createToken from '../utils/createToken'; 3 | import { checkPassword } from '../utils/crypts'; 4 | import config from '../config'; 5 | 6 | 7 | export default async function(req, res) { 8 | const { email, name, password } = req.body; 9 | const user = await User.findOne({ $or: [ { name }, { email } ] }); 10 | 11 | // console.log('login', config); 12 | if (!user) { 13 | res.status(400).json({ success: false, message: config.USER_MESSAGE.USER_NOT_FOUND }); 14 | } else if (user) { 15 | 16 | // check if password matches 17 | const result = await checkPassword(password, user.password); 18 | if ( !result ) { 19 | res.status(400).json({ success: false, message: config.USER_MESSAGE.WRONG_PASSWORD }); 20 | } else { 21 | // if user is found and password is right 22 | // create a token 23 | const payload = { name: user.name, verified: user.verified }; 24 | const token = createToken(payload, config.CLIENT_TOKEN_EXPIRES_IN); 25 | 26 | // return the information including token as JSON 27 | res.status(200).json({ 28 | success: true, 29 | message: `${config.USER_MESSAGE.LOGIN_SUCCESS} ${user.name}`, 30 | name: user.name, 31 | token: token 32 | }); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/router/password_reset.js: -------------------------------------------------------------------------------- 1 | import User from '../models/User'; // get our mongoose model 2 | import createToken from '../utils/createToken'; 3 | import { hashPassword } from '../utils/crypts'; 4 | import sendMail from '../utils/sendMail'; 5 | import config from '../config'; 6 | 7 | export default async function(req, res) { 8 | 9 | const { email, password } = req.body; 10 | const user = await User.findOne( { email } ); 11 | 12 | if (!user) { 13 | res.status(400).json({ success: false, message: config.USER_MESSAGE.USER_NOT_FOUND }); 14 | } else { 15 | const hashedPassword = await hashPassword(password); 16 | const token = createToken({ name:user.name, hashedPassword }, config.EMAIL_TOKEN_EXPIRES_IN); 17 | const verifyAddress = 18 | `${config.API_URL}/email_verification/?token=${token}`; 19 | const content = ` 20 | Click to change your password. 21 | `; 22 | const info = await sendMail(email, content).catch((err) => { 23 | res.status(500).json({ success: false, message: 'email sent error' }); 24 | console.log('Email not sent, err:' + err); 25 | }); 26 | 27 | res.status(200).json({ success: true, message: config.USER_MESSAGE.MAIL_SENT }); 28 | console.log('Email sent: ' + info.response); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/router/signup.js: -------------------------------------------------------------------------------- 1 | import createToken from '../utils/createToken'; 2 | import sendMail from '../utils/sendMail'; 3 | import User from '../models/User'; 4 | import { hashPassword } from '../utils/crypts'; 5 | import config from '../config'; 6 | 7 | 8 | export default async function(req, res) { 9 | const { email, name, password } = req.body; 10 | 11 | if ( !name || !password || !email ) { 12 | res.status(400).json({ success: false, message: 'info not enough', }); 13 | } 14 | 15 | // see if name or email is occupied 16 | const user = await User.findOne({ $or: [ { name }, { email } ] }); 17 | 18 | if (!user) { 19 | // send verification email 20 | const token = createToken({ name }); 21 | const verifyAddress = 22 | `${config.API_URL}/email_verification/?token=${token}`; 23 | const content = 24 | ` 25 | Click to verify your email address. 26 | `; 27 | 28 | const info = await sendMail(email, content).catch((err) => { 29 | res.status(500).json({ success: false, message: 'email sent error' }); 30 | console.log('Email not sent, err:' + err); 31 | }); 32 | 33 | const hashedPassword = await hashPassword(password); 34 | await new User({ email, name, password: hashedPassword, verified: false }).save(); 35 | 36 | console.log('____User saved'); 37 | 38 | res.status(200).json({ success: true, message: config.USER_MESSAGE.MAIL_SENT }); 39 | console.log('Email sent: ' + info.response); 40 | 41 | 42 | } else { 43 | console.log('____User not saved, name or email has been taken'); 44 | res.status(400).json({ success: false, message: config.USER_MESSAGE.NAME_TAKEN, }); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/utils/createToken.js: -------------------------------------------------------------------------------- 1 | import jwt from 'jsonwebtoken'; 2 | import config from '../config'; 3 | 4 | export default function(payload, expiresIn) { 5 | return jwt.sign(payload, config.SECRET, { expiresIn }); 6 | } 7 | -------------------------------------------------------------------------------- /src/utils/crypts.js: -------------------------------------------------------------------------------- 1 | import { genSalt, hash, compare } from 'bcrypt'; 2 | import promisify from 'es6-promisify'; 3 | 4 | const genSaltAsync = promisify(genSalt); 5 | const hashAsync = promisify(hash); 6 | const compareAsync = promisify(compare); 7 | 8 | export async function hashPassword(password) { 9 | const salt = await genSaltAsync(10); 10 | const hash = await hashAsync(password, salt); 11 | return hash; 12 | } 13 | 14 | export async function checkPassword(password, hash) { 15 | const res = await compareAsync(password, hash); 16 | return res; 17 | } 18 | -------------------------------------------------------------------------------- /src/utils/sendMail.js: -------------------------------------------------------------------------------- 1 | import { createTransport } from 'nodemailer'; 2 | import config from '../config'; 3 | // create reusable transporter object using SMTP transport 4 | 5 | /** 6 | * to: mail 7 | * verifyAddress: 8 | */ 9 | export default function sendMail(targetAddress, content) { 10 | // NB! No need to recreate the transporter object. You can use 11 | // the same transporter object for all e-mails 12 | const transporter = createTransport(config.EMAIL_SENDER, { 13 | // default values for sendMail method 14 | from: `${config.APP_NAME} <${config.EMAIL_SENDER.auth.user}>`, 15 | // headers: { 16 | // 'My-Awesome-Header': '123' 17 | // } 18 | }); 19 | 20 | // setup e-mail data with unicode symbols 21 | let mailOptions = { 22 | to: `${targetAddress}`, // list of receivers 23 | subject: 'Email verification', // Subject line 24 | html: `${content}`, 25 | }; 26 | 27 | // console.log('sendMail', mailOptions); 28 | // send mail with defined transport object 29 | return transporter.sendMail(mailOptions); 30 | } 31 | -------------------------------------------------------------------------------- /src/utils/test/crypts.js: -------------------------------------------------------------------------------- 1 | import { hashPassword, checkPassword} from '../crypts'; 2 | 3 | (async function() { 4 | const passHash = await hashPassword('123'); 5 | const res = await checkPassword('123', passHash); 6 | console.log(res); 7 | })(); 8 | -------------------------------------------------------------------------------- /src/utils/verifyToken.js: -------------------------------------------------------------------------------- 1 | /** 2 | * middleware used to verify token of client 3 | * NOTE: only with jsonParser, 4 | */ 5 | 6 | import jwt from 'jsonwebtoken'; 7 | import config from '../config'; 8 | 9 | export default function (req, res, next) { 10 | 11 | // check header or url parameters or post parameters for token 12 | const token = req.body.token || req.query.token || 13 | req.headers['x-access-token']; 14 | 15 | // decode token 16 | if (token) { 17 | 18 | // verifies secret and checks exp 19 | jwt.verify(token, config.SECRET, (err, decoded) => { 20 | if (err) { 21 | return res.json({ 22 | success: false, 23 | message: 'Failed to authenticate token.' 24 | }); 25 | } else { 26 | // if everything is good, save to request for use in other routes 27 | req.decoded = decoded; 28 | next(); 29 | } 30 | }); 31 | 32 | } else { 33 | 34 | // if there is no token 35 | // return an error 36 | return res.status(403).send({ 37 | success: false, 38 | message: 'No token provided.' 39 | }); 40 | 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/email_verification.js: -------------------------------------------------------------------------------- 1 | // go to your mail address and click the link. 2 | // Should return { 3 | // success: ture 4 | // } 5 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | require('babel-polyfill'); 2 | 3 | import config from './testConfig'; 4 | import axios from 'axios'; 5 | import login from './login'; 6 | import signup from './signup'; 7 | import passwordReset from './password_reset'; 8 | import needingToken from './needingToken'; 9 | import needingTokenAndEmailVerified from './needingTokenAndEmailVerified'; 10 | 11 | describe('Test starts', function () { 12 | 13 | // signup initial user 14 | before(async function() { 15 | 16 | // TODO: clean the database 17 | 18 | await axios.post(`${config.BASEURL}/signup`, { 19 | email: `${config.EMAIL_RECEIVING_VERIFICATION}`, 20 | name: `tim`, 21 | password: '123', 22 | }).then((res) => { 23 | console.log('before message:', res.data); 24 | }).catch((res) => { 25 | console.log('before message:', res.data); 26 | // throw res; 27 | }); 28 | }); 29 | 30 | login(); 31 | signup(); 32 | passwordReset(); 33 | needingToken(); 34 | needingTokenAndEmailVerified(); 35 | }); 36 | -------------------------------------------------------------------------------- /test/login.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import assert from 'assert'; 3 | import config from './testConfig'; 4 | 5 | export default function login() { 6 | describe('POST /login', function () { 7 | 8 | it('name login', function () { 9 | return axios.post(`${config.BASEURL}/login`, { 10 | name: 'tim', 11 | password: '123', 12 | }).then((res) => { 13 | assert.equal(res.status, 200); 14 | }); 15 | }); 16 | 17 | it('email login', function () { 18 | return axios.post(`${config.BASEURL}/login`, { 19 | email: `${config.EMAIL_RECEIVING_VERIFICATION}`, 20 | password: '123', 21 | }).then((res) => { 22 | assert.equal(res.status, 200); 23 | }); 24 | }); 25 | 26 | it('wrong user name', function () { 27 | return axios.post(`${config.BASEURL}/login`, { 28 | name: `tim${Date.now()}`, 29 | password: '123', 30 | }).then((res) => { 31 | throw res; 32 | }).catch((res) => { 33 | assert.equal(res.status, 400); 34 | }); 35 | }); 36 | 37 | it('wrong email', function () { 38 | return axios.post(`${config.BASEURL}/login`, { 39 | email: `tim${Date.now()}@qq.com`, 40 | password: '123', 41 | }).then((res) => { 42 | throw res; 43 | }).catch((res) => { 44 | assert.equal(res.status, 400); 45 | }); 46 | }); 47 | 48 | it('wrong password with name', function () { 49 | return axios.post(`${config.BASEURL}/login`, { 50 | name: `tim`, 51 | password: '1234', 52 | }).then((res) => { 53 | throw res; 54 | }).catch((res) => { 55 | assert.equal(res.status, 400); 56 | }); 57 | }); 58 | 59 | it('wrong password with email', function () { 60 | return axios.post(`${config.BASEURL}/login`, { 61 | email: `${config.EMAIL_RECEIVING_VERIFICATION}`, 62 | password: '1234', 63 | }).then((res) => { 64 | throw res; 65 | }).catch((res) => { 66 | assert.equal(res.status, 400); 67 | }); 68 | }); 69 | }); 70 | } 71 | -------------------------------------------------------------------------------- /test/needingToken.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import assert from 'assert'; 3 | import config from './testConfig'; 4 | 5 | export default function password_reset() { 6 | describe('GET /needingToken', function () { 7 | 8 | let token = ''; 9 | 10 | before(async function() { 11 | const loginRes = await axios.post(`${config.BASEURL}/login`, { 12 | name: 'tim', 13 | password: '123', 14 | }).catch((res) => { throw res.data;}); 15 | 16 | token = loginRes.data.token; 17 | }); 18 | 19 | it('should need token', function () { 20 | return axios.get(`${config.BASEURL}/needingToken`) 21 | .then((res) => { throw res; }) 22 | .catch((res) => { 23 | assert.equal(res.status, 403); 24 | }); 25 | }); 26 | 27 | it('should success', function () { 28 | return axios.get(`${config.BASEURL}/needingToken`, {params: {token}}) 29 | .then((res) => { 30 | assert.equal(res.status, 200); 31 | }); 32 | }); 33 | 34 | }); 35 | } 36 | -------------------------------------------------------------------------------- /test/needingTokenAndEmailVerified.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import assert from 'assert'; 3 | import config from './testConfig'; 4 | 5 | export default function password_reset() { 6 | 7 | let token = ''; 8 | 9 | describe('GET /needingTokenAndEmailVerified', function () { 10 | 11 | before(async function() { 12 | const loginRes = await axios.post(`${config.BASEURL}/login`, { 13 | name: 'tim', 14 | password: '123', 15 | }).catch((res) => { throw res.data; }); 16 | 17 | token = loginRes.data.token; 18 | }); 19 | 20 | it('should need token', function () { 21 | return axios.get(`${config.BASEURL}/needingTokenAndEmailVerified`) 22 | .then((res) => { 23 | throw res; 24 | }).catch((res) => { 25 | // console.log(res.data); 26 | assert.equal(res.data.success, false, 'should be false'); 27 | }); 28 | }); 29 | 30 | it('should need email verified', function () { 31 | return axios.get(`${config.BASEURL}/needingTokenAndEmailVerified`, {params: {token}}) 32 | .then((res) => { throw res; }) 33 | .catch((res) => { 34 | assert.equal(res.status, 400); 35 | }); 36 | }); 37 | 38 | // it('should success', function () { 39 | // return User.findOne({name: 'tim'}, (err, user) => { 40 | // user.verified = true; 41 | // user.save((err) => { 42 | // if (err) throw err; 43 | // 44 | // }) 45 | // }) 46 | // }); 47 | 48 | }); 49 | } 50 | -------------------------------------------------------------------------------- /test/password_reset.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import assert from 'assert'; 3 | import config from './testConfig'; 4 | 5 | export default function password_reset() { 6 | 7 | describe('POST /password_reset', function () { 8 | 9 | it('reset success', () => { 10 | return axios.post(`${config.BASEURL}/password_reset`, { 11 | email: `${config.EMAIL_RECEIVING_VERIFICATION}`, 12 | password: '123', 13 | }).then((res) => { 14 | assert.equal(res.status, 200); 15 | }); 16 | }); 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /test/signup.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import assert from 'assert'; 3 | import config from './testConfig'; 4 | 5 | 6 | export default function login() { 7 | 8 | describe('POST /signup', function () { 9 | 10 | // it('signup success', function () { 11 | // return axios.post(`${config.BASEURL}/signup`, { 12 | // email: `${Date.now()}@qq.com`, 13 | // name: `tim${Date.now()}`, 14 | // password: '123', 15 | // }).then((res) => { 16 | // assert.equal(res.status, 200); 17 | // }); 18 | // }); 19 | 20 | it('email taken', function () { 21 | return axios.post(`${config.BASEURL}/signup`, { 22 | email: `${config.EMAIL_RECEIVING_VERIFICATION}`, 23 | name: `timq`, 24 | password: '123', 25 | }).then((res) => { 26 | throw res; 27 | }).catch((res) => { 28 | assert.equal(res.status, 400); 29 | }); 30 | }); 31 | 32 | it('name taken', function () { 33 | return axios.post(`${config.BASEURL}/signup`, { 34 | email: 't92@qq.com', 35 | name: `tim`, 36 | password: '123', 37 | }).then((res) => { 38 | throw res; 39 | }).catch((res) => { 40 | assert.equal(res.status, 400); 41 | }); 42 | }); 43 | 44 | }); 45 | } 46 | -------------------------------------------------------------------------------- /test/testConfig.js: -------------------------------------------------------------------------------- 1 | // used to store config 2 | 3 | const testConfig = { 4 | BASEURL: 'http://localhost:3000', // api url 5 | EMAIL_RECEIVING_VERIFICATION: 'timqian92@qq.com', 6 | }; 7 | 8 | export default testConfig; 9 | -------------------------------------------------------------------------------- /testServer.js: -------------------------------------------------------------------------------- 1 | import authApi from './src/index'; 2 | import express from 'express'; 3 | import bodyParser from 'body-parser'; 4 | import methodOverride from 'method-override'; 5 | import morgan from 'morgan'; 6 | import mongoose from 'mongoose'; 7 | 8 | mongoose.connect('mongodb://localhost/database'); // connect to database 9 | 10 | const userConfig = { 11 | APP_NAME: 'STOCK APP', 12 | SECRET: 'ilovetim', // jwt secret 13 | CLIENT_TOKEN_EXPIRES_IN: 60 * 24 * 60 * 60, // client token expires time(60day) 14 | EMAIL_TOKEN_EXPIRES_IN: 24 * 60 * 60, // email token expires time(24h) 15 | 16 | EMAIL_SENDER: { // used to send mail by nodemailer 17 | service: 'Gmail', 18 | auth: { 19 | user: 'qianlijiang123@gmail.com', 20 | pass: '321qianqian', 21 | } 22 | }, 23 | 24 | USER_MESSAGE: { // message sent to client 25 | MAIL_SENT: 'mail sent', 26 | NAME_TAKEN: 'Name or email has been taken', 27 | USER_NOT_FOUND: 'User not found', 28 | WRONG_PASSWORD: 'wrong password', 29 | LOGIN_SUCCESS: 'Enjoy your token!', 30 | NEED_EMAIL_VERIFICATION: 'You need to verify your email first', 31 | }, 32 | 33 | API_URL: 'http://localhost:3000' 34 | }; 35 | 36 | authApi.init(userConfig); 37 | 38 | const app = express(); 39 | app.use(bodyParser.urlencoded({ extended: false })); 40 | app.use(bodyParser.json()); 41 | app.use(methodOverride()); 42 | app.use(morgan('dev')); 43 | app.use('/', authApi.authRouter); 44 | 45 | // protecting api 46 | app.get('/needingToken', authApi.verifyToken, (req, res) => { 47 | 48 | // send back the jwt claim directly 49 | const claim = req.decoded; 50 | res.status(200).json(claim); 51 | }); 52 | 53 | app.get('/needingTokenAndEmailVerified', authApi.verifyToken, (req, res) => { 54 | if (req.decoded.verified) { 55 | res.status(200).json(req.decoded); 56 | } else { 57 | res.status(400).json({ 58 | success: false, 59 | message: 'Please verify your email before doing this!' 60 | }); 61 | } 62 | }); 63 | 64 | 65 | app.listen(3000); 66 | console.log('API magic happens at http://localhost:3000'); 67 | 68 | // handle unhandled promise rejection 69 | // https://nodejs.org/api/process.html#process_event_unhandledrejection 70 | process.on('unhandledRejection', function(reason, p) { 71 | console.log('Unhandled Rejection at: Promise ', p, ' reason: ', reason); 72 | // application specific logging, throwing an error, or other logic here 73 | }); 74 | -------------------------------------------------------------------------------- /todo.md: -------------------------------------------------------------------------------- 1 | # 我想要的效果 2 | 3 | ## 使用时用户给的 config 4 | 5 | ```javascript 6 | const sampleConfig = { 7 | APP_NAME: 'STOCK APP', // app name 8 | JWT_SECRET: 'ilovetim', // jwt secret 9 | CLIENT_TOKEN_EXPIRES_IN: 24 * 60 * 60, // client token expires time 10 | EMAIL_TOKEN_EXPIRES_IN: 24 * 60 * 60, // email token expires time 11 | 12 | EMAIL_SENDER: { // used to send mail by nodemailer 13 | service: 'Gmail', 14 | auth: { 15 | user: 'qianlijiang123@gmail.com', 16 | pass: '321qianqian', 17 | } 18 | }, 19 | 20 | USER_MESSAGE: { // message sent to client 21 | MAIL_SENT: 'mail sent', 22 | NAME_TAKEN: 'Name or email has been taken', 23 | USER_NOT_FOUND: 'User not found', 24 | WRONG_PASSWORD: 'wrong password', 25 | LOGIN_SUCCESS: 'Enjoy your token!', 26 | NEED_EMAIL_VERIFICATION: 'You need to verify your email first', 27 | }, 28 | } 29 | ``` 30 | 31 | ## 关于测试代码 32 | 33 | 1. babel 编译到lib 34 | 2. 使用 lib 中的 index 开启一个服务器 35 | 3. 对该服务器进行测试(测试 有自己的 config) 36 | 37 | ## module 特点 38 | 39 | 1. easy to use 40 | 2. 方便增加功能(修改代码) 41 | --------------------------------------------------------------------------------