├── .gitignore ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── controllers ├── AuthCtrl.js ├── LoginCtrl.js ├── NetworkCtrl.js └── SignupCtrl.js ├── middleware ├── IdentifyApp.js ├── IdentifyAppUser.js └── ReadHeaders.js ├── models ├── account.js ├── app.js ├── index.js ├── user.js ├── userEmail.js ├── userNetwork.js ├── userPassword.js ├── userResetCode.js └── userToken.js ├── package-lock.json ├── package.json ├── routes.js ├── scripts ├── create-account.js └── create-app.js ├── server.js └── services └── AuthService.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | # Elastic Beanstalk Files 3 | .elasticbeanstalk/* 4 | !.elasticbeanstalk/*.cfg.yml 5 | !.elasticbeanstalk/*.global.yml 6 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible Node.js debug attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "debug:local", 11 | "program": "${workspaceRoot}/server.js", 12 | "env": { 13 | "VA_ENV": "local" 14 | } 15 | }, 16 | { 17 | "type": "node", 18 | "request": "launch", 19 | "name": "debug:production", 20 | "program": "${workspaceRoot}/index.js" 21 | 22 | }, 23 | { 24 | "type": "node", 25 | "request": "attach", 26 | "name": "An den Port anfügen", 27 | "address": "localhost", 28 | "port": 5858 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vq-auth 2 | Token-based authentification microservice 3 | 4 | ## Configuration 5 | ### Database 6 | You need to specify a connection to a mySQL database in your env. variables: 7 | 8 | ``` 9 | export VA_ENV = 'production' || 'local' 10 | 11 | # database connection if VA_ENV is 'production' 12 | export VQ_VA_DB = mysql://:@:3306/ 13 | 14 | # database connection if VA_ENV is 'local' 15 | export VQ_VA_DB_TEST = mysql://:@:3306/ 16 | ``` 17 | 18 | Sequelize library will create the DB tables if they do not exist the first time you start the server. 19 | 20 | ## API 21 | ### POST /auth/token 22 | This API Endpoint is intended for validation of user's token. The token needs to be specified in 'x-viciauth-auth-token' header. 23 | 24 | * **Success Response:**
25 | **Code:** 200
26 | **Content:** `{ appId: 1, userId: 1, token: 'blablabla-some-token' }` 27 | 28 | ### POST /auth/password/request-reset 29 | Returns code with with one can restart user password. 30 | 31 | * **Data params:**
32 | 'x-viciauth-api-key' and 'x-viciauth-app-key' headers required. 33 | ``` 34 | { 35 | email: 'test@vq-labs.com', 36 | } 37 | ``` 38 | 39 | * **Success Response:**
40 | **Code:** 200
41 | **Content:** `{ appId: 1, userId: 2, code: 'asdiu123nunoaHUi2' }` 42 | 43 | * **Error Response:**
44 | **Code:** 400
45 | **Content:** `{ code: 'EMAIL_NOT_FOUND' }` 46 | 47 | ### POST /auth/local/login 48 | Allows authentification with password (local strategy). 49 | 50 | * **Data params:**
51 | 'x-viciauth-api-key' and 'x-viciauth-app-key' headers required. 52 | ``` 53 | { 54 | email: 'test@vq-labs.com', 55 | password: 'super-secret' 56 | } 57 | ``` 58 | 59 | * **Success Response:**
60 | **Code:** 200
61 | **Content:** `{ appId: 1, userId: 1, token: 'blablabla-some-token' }` 62 | 63 | * **Error Response:**
64 | **Code:** 400
65 | **Content:** `{ code: 'EMAIL_NOT_FOUND' }` 66 | 67 | ### POST /auth/local/signup 68 | 69 | * **Data params:**
70 | ``` 71 | { 72 | email: 'test@vq-labs.com', // required 73 | password: 'super-secret' // required 74 | } 75 | ``` 76 | * **Success Response:**
77 | **Code:** 200
78 | **Content:** `{ appId: 1, userId: 1, token: 'blablabla-some-token' }` 79 | 80 | * **Error Response:**
81 | * **Code:** 400
82 | **Content:** `{ code: 'INITIAL_PARAMS' }` 83 | 84 | OR 85 | 86 | * **Code:** 400
87 | **Content:** `{ code: 'EMAIL_EXISTS' }` 88 | 89 | 90 | ### POST /auth/networks/facebook 91 | 92 | ## What is used? 93 | ExpressJS, Sequelize 94 | 95 | ## Author 96 | adrianbarwicki[@]gmail.com
97 | VQ-Labs
98 | [https://vq-labs.com/](http://vq-labs.com/) -------------------------------------------------------------------------------- /controllers/AuthCtrl.js: -------------------------------------------------------------------------------- 1 | const async = require("async"); 2 | const AuthService = require("../services/AuthService.js"); 3 | const models = require("../models"); 4 | 5 | const checkToken = (appId, token, callback) => { 6 | if (!appId || !token) { 7 | return callback({ 8 | status: 400, 9 | code: "INITIAL_PARAMS" 10 | }); 11 | } 12 | 13 | async.waterfall([ 14 | callback => AuthService.checkToken(appId, token, (err, rUserToken) => { 15 | if (err) { 16 | return callback(err); 17 | } 18 | 19 | if (!rUserToken) { 20 | return callback({ 21 | status: 400, 22 | code: "WRONG_TOKEN" 23 | }); 24 | } 25 | 26 | if (rUserToken.deleted) { 27 | return callback({ 28 | status: 400, 29 | code: "INVALID_TOKEN" 30 | }); 31 | } 32 | 33 | return callback(null, rUserToken); 34 | }), 35 | ], (err, rUserToken) => callback(err, rUserToken)); 36 | }; 37 | 38 | const changePassword = (appId, userId, newPassword) => { 39 | models.userPassword.destroy({ 40 | where: [ 41 | { userId }, 42 | { appId } 43 | ] 44 | }).then(() => { 45 | AuthService.createNewPassword(appId, userId, newPassword, () => {}); 46 | }) 47 | }; 48 | 49 | 50 | 51 | module.exports = { 52 | changePassword, 53 | checkToken 54 | }; 55 | -------------------------------------------------------------------------------- /controllers/LoginCtrl.js: -------------------------------------------------------------------------------- 1 | var async = require("async"); 2 | var AuthService = require("../services/AuthService.js"); 3 | 4 | const loginWithPassword = (appId, email, password, callback) => { 5 | if (!appId) { 6 | return callback({ 7 | code: "UNIDENTIFIED_APP" 8 | }); 9 | } 10 | 11 | if (!appId || !email || !password) { 12 | return callback({ 13 | code: "INITIAL_PARAMS" 14 | }); 15 | } 16 | 17 | var User = {}, Token; 18 | 19 | if (password) { 20 | User.password = AuthService.generateHashSync(password); 21 | } 22 | 23 | async.waterfall([ 24 | callback => { 25 | AuthService.getUserIdFromEmail(appId, email, (err, rUser) => { 26 | if (err) { 27 | return callback(err); 28 | } 29 | 30 | if (!rUser) { 31 | return callback({ 32 | status:400, 33 | code:"EMAIL_NOT_FOUND" 34 | }); 35 | } 36 | 37 | User = rUser.dataValues; 38 | 39 | return callback(); 40 | }); 41 | }, 42 | callback => { 43 | AuthService 44 | .checkPassword(appId, User.userId, password, (err, checkResult) => { 45 | if (err) { 46 | return callback(err); 47 | } 48 | 49 | if (!checkResult) { 50 | return callback({ 51 | status: 400, 52 | code: "WRONG_PASSWORD" 53 | }); 54 | } 55 | 56 | return callback(); 57 | }); 58 | }, callback => { 59 | AuthService.createNewToken(appId, User.userId, (err, rToken) => { 60 | if (err) { 61 | return callback(err); 62 | } 63 | 64 | Token = rToken; 65 | 66 | return callback(); 67 | }); 68 | }, 69 | ], err => callback(err, Token)); 70 | } 71 | 72 | module.exports = { 73 | loginWithPassword 74 | }; 75 | 76 | -------------------------------------------------------------------------------- /controllers/NetworkCtrl.js: -------------------------------------------------------------------------------- 1 | var bcrypt = require('bcrypt-nodejs'); 2 | var async = require("async"); 3 | var AuthService = require("../services/AuthService.js"); 4 | 5 | module.exports = { 6 | connectToFacebook: connectToFacebook, 7 | }; 8 | 9 | function connectToFacebook(appId, token, refreshToken, profile, callback) { 10 | var User, Token, fbId; 11 | 12 | if (!appId) { 13 | return callback({ 14 | status: 400, 15 | code: "INITIAL_APP_ID" 16 | }); 17 | } 18 | if (!token) { 19 | return callback({ 20 | status: 400, 21 | code: "INITIAL_TOKEN" 22 | }); 23 | } 24 | if (!profile) { 25 | return callback({ 26 | status: 400, 27 | code: "INITIAL_PROFILE" 28 | }); 29 | } 30 | 31 | 32 | 33 | fbId = profile.id || profile.fbId; 34 | 35 | // id is obsolite, we should use fbId 36 | if (!fbId) { 37 | return callback({ 38 | status: 400, 39 | code: "INITIAL_PROFILE_ID" 40 | }); 41 | } 42 | 43 | 44 | async.waterfall([ 45 | function(callback) { 46 | AuthService.getUserIdFromNetwork('facebook', fbId, function(err, rUser) { 47 | if (err) { 48 | return callback(err); 49 | } 50 | User = rUser; 51 | return callback(); 52 | }); 53 | }, 54 | 55 | function(callback) { 56 | if (User) { 57 | AuthService.updateNetworkToken(User.user_id, 'facebook', fbId, token); 58 | return callback(); 59 | } 60 | 61 | var newUser; 62 | async.waterfall([ 63 | function(callback) { 64 | AuthService.createNewUser(appId, function(err, rNewUser) { 65 | if (err) { 66 | return callback(err); 67 | } 68 | User = rNewUser; 69 | return callback(); 70 | }); 71 | }, 72 | function(callback) { 73 | if (profile.emails && profile.emails.length) { 74 | 75 | async.eachSeries(profile.emails, function(Email, callback) { 76 | AuthService.createNewEmail(appId, User.userId, Email.value, function(err) { 77 | if (err) { 78 | return callback(err); 79 | } 80 | return callback(); 81 | }); 82 | }, function(err) { 83 | return callback(err); 84 | }); 85 | } else { 86 | return callback(); 87 | } 88 | }, 89 | function(callback) { 90 | AuthService.createNewNetwork(appId, User.userId, 'facebook',fbId, token, refreshToken, function(err) { 91 | if (err) { 92 | return callback(err); 93 | } 94 | return callback(); 95 | }); 96 | }, 97 | 98 | ], function(err) { 99 | callback(err); 100 | }); 101 | }, 102 | function(callback) { 103 | AuthService.createNewToken(appId, User.userId, function(err, rUser) { 104 | if (err) { 105 | return callback(err); 106 | } 107 | User = rUser; 108 | return callback(); 109 | }); 110 | }, 111 | function(callback) { 112 | if (profile.Props && profile.Props.length) { 113 | async.eachLimit(profile.Props, 4, function(Prop, callback) { 114 | AuthService.addUserProp(User.userId, Prop.key, Prop.value, function(err) { 115 | if (err) { 116 | return callback(err); 117 | } 118 | return callback(); 119 | }); 120 | }, function(err) { 121 | callback(err); 122 | }); 123 | } else { 124 | return callback(); 125 | } 126 | } 127 | ], function(err) { 128 | callback(err, User); 129 | }); 130 | } 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /controllers/SignupCtrl.js: -------------------------------------------------------------------------------- 1 | var async = require("async"); 2 | var AuthService = require("../services/AuthService.js"); 3 | 4 | const createLocalAccount = (appId, email, password, callback) => { 5 | if (!appId || !email || !password) { 6 | return callback({ 7 | status: 400, 8 | code: "INITIAL_PARAMS" 9 | }); 10 | } 11 | 12 | var newUser = {}; 13 | 14 | if (password) { 15 | newUser.password = AuthService.generateHashSync(password); 16 | } 17 | 18 | async.waterfall([ 19 | callback => AuthService.createNewUser(appId, (err, rNewUser) => { 20 | if (err) { 21 | return callback(err); 22 | } 23 | 24 | newUser = rNewUser; 25 | 26 | return callback(); 27 | }), 28 | callback => AuthService.createNewEmail(appId, newUser.id, email, callback), 29 | callback => AuthService.createNewPassword(appId, newUser.id, password, callback), 30 | callback => AuthService.createNewToken(appId, newUser.id, callback) 31 | ], (err, rUserToken) => { 32 | if (err) { 33 | console.error(err); 34 | } 35 | 36 | callback(err, rUserToken); 37 | }); 38 | }; 39 | 40 | module.exports = { 41 | createLocalAccount: createLocalAccount, 42 | }; -------------------------------------------------------------------------------- /middleware/IdentifyApp.js: -------------------------------------------------------------------------------- 1 | const models = require('../models'); 2 | 3 | /** 4 | Middleware for identifing app 5 | if the app is identified, the app property on request will be set with appName {string} and appId {number} 6 | */ 7 | const identifyApp = (req, res, next) => { 8 | if (!req.auth) { 9 | return next(); 10 | } 11 | 12 | var appKey = req.auth.appKey; 13 | var apiKey = req.auth.apiKey; 14 | 15 | var app = {}; 16 | 17 | models.app.findOne({ 18 | where: { 19 | $and: [ 20 | { appKey }, 21 | { apiKey } 22 | ] 23 | }}) 24 | .then(app => { 25 | if (app) { 26 | req.app = app.dataValues; 27 | } else { 28 | req.app = false; 29 | } 30 | 31 | return next(); 32 | }, err => { 33 | console.error(err); 34 | 35 | return res.status(502).send(err) 36 | }); 37 | }; 38 | 39 | module.exports = identifyApp; 40 | -------------------------------------------------------------------------------- /middleware/IdentifyAppUser.js: -------------------------------------------------------------------------------- 1 | const models = require('../models'); 2 | 3 | const identifyAppUser = (req, res, next) => { 4 | if (!req.app) { 5 | req.user = false; 6 | 7 | return next(); 8 | } 9 | 10 | var token = req.auth.token || req.body.token; 11 | var appId = req.auth.appId; 12 | 13 | models.userToken.findOne({ 14 | where: { 15 | $and: [ 16 | { appId }, 17 | { token } 18 | ] 19 | }}) 20 | .then(app => { 21 | if (app) { 22 | req.user = { 23 | id: app.dataValues.id 24 | }; 25 | } else { 26 | req.user = false; 27 | } 28 | 29 | return next(); 30 | }, err => { 31 | console.error(err); 32 | 33 | return res.status(502).send(err) 34 | }); 35 | }; 36 | 37 | module.exports = identifyAppUser; 38 | -------------------------------------------------------------------------------- /middleware/ReadHeaders.js: -------------------------------------------------------------------------------- 1 | /** 2 | Reads 'x-auth-viciauth-{token/app-key/api-key}' values from header of the request and assignes them to a property 'auth' of the request 3 | */ 4 | const readHeaders = (req, res, next) => { 5 | req.auth = {}; 6 | req.auth.token = req.headers['x-auth-viciauth-token']; 7 | req.auth.appKey = req.headers['x-auth-viciauth-app-key']; 8 | req.auth.apiKey = req.headers['x-auth-viciauth-api-key']; 9 | 10 | return next(); 11 | }; 12 | 13 | module.exports = readHeaders; 14 | -------------------------------------------------------------------------------- /models/account.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | const Model = sequelize.define("account", { 3 | }, { 4 | tableName: 'account', 5 | classMethods: { 6 | associate: models => { 7 | Model.hasMany(models.app); 8 | } 9 | } 10 | }); 11 | 12 | return Model; 13 | }; -------------------------------------------------------------------------------- /models/app.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | const Model = sequelize.define("app", { 3 | appName: { type: DataTypes.STRING }, 4 | appKey: { type: DataTypes.STRING }, 5 | apiKey: { type: DataTypes.STRING } 6 | }, { 7 | tableName: 'app', 8 | classMethods: { 9 | associate: models => { 10 | Model.belongsTo(models.account); 11 | Model.hasMany(models.user); 12 | Model.hasMany(models.userEmail); 13 | Model.hasMany(models.userToken); 14 | Model.hasMany(models.userNetwork); 15 | Model.hasMany(models.userPassword); 16 | } 17 | } 18 | }); 19 | 20 | return Model; 21 | }; -------------------------------------------------------------------------------- /models/index.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const path = require("path"); 3 | const Sequelize = require("sequelize"); 4 | 5 | const dbConn = process.env.VA_ENV === 'production' ? 6 | process.env.VQ_VA_DB : 7 | process.env.VQ_VA_DB_TEST || 'mysql://root:kurwa@localhost:3306/vq-auth'; 8 | 9 | const sequelize = new Sequelize(dbConn, { 10 | dialect: 'mysql', 11 | pool: { 12 | max: 50, 13 | min: 0, 14 | idle: 10000 15 | } 16 | }); 17 | 18 | const db = {}; 19 | 20 | fs.readdirSync(__dirname) 21 | .filter(file => file.indexOf(".") !== 0 && file !== "index.js") 22 | .forEach(file => { 23 | var model = sequelize.import(path.join(__dirname, file)); 24 | 25 | db[model.name] = model; 26 | }); 27 | 28 | Object.keys(db).forEach(modelName => { 29 | if ("associate" in db[modelName]) { 30 | db[modelName].associate(db); 31 | } 32 | }); 33 | 34 | db.seq = sequelize; 35 | db.Sequelize = Sequelize; 36 | 37 | module.exports = db; 38 | -------------------------------------------------------------------------------- /models/user.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | const Model = sequelize.define("user", { 3 | status: { 4 | type: DataTypes.INTEGER, 5 | defaultValue: 0 6 | } 7 | }, { 8 | tableName: 'user', 9 | classMethods: { 10 | associate: models => { 11 | Model.belongsTo(models.app); 12 | } 13 | } 14 | }); 15 | 16 | return Model; 17 | }; -------------------------------------------------------------------------------- /models/userEmail.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | const Model = sequelize.define("userEmail", { 3 | email: { type: DataTypes.STRING, required: true }, 4 | verified: { type: DataTypes.BOOLEAN, defaultValue: 0 } 5 | }, { 6 | tableName: 'userEmail', 7 | classMethods: { 8 | associate: models => { 9 | Model.belongsTo(models.user); 10 | Model.belongsTo(models.app); 11 | } 12 | } 13 | }); 14 | 15 | return Model; 16 | }; 17 | -------------------------------------------------------------------------------- /models/userNetwork.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | const Model = sequelize.define("userNetwork", { 3 | networkId: { type: DataTypes.STRING, required: true }, 4 | network: { type: DataTypes.STRING, required: true }, 5 | token: { type: DataTypes.STRING, required: true }, 6 | refreshToken: { type: DataTypes.STRING } 7 | }, { 8 | tableName: 'userNetwork', 9 | classMethods: { 10 | associate: models => { 11 | Model.belongsTo(models.user); 12 | Model.belongsTo(models.app); 13 | } 14 | } 15 | }); 16 | 17 | return Model; 18 | }; -------------------------------------------------------------------------------- /models/userPassword.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | const Model = sequelize.define("userPassword", { 3 | password: { type: DataTypes.STRING, required: true } 4 | }, { 5 | tableName: 'userPassword', 6 | classMethods: { 7 | associate: models => { 8 | Model.belongsTo(models.user); 9 | Model.belongsTo(models.app); 10 | } 11 | } 12 | }); 13 | 14 | return Model; 15 | }; 16 | -------------------------------------------------------------------------------- /models/userResetCode.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | const Model = sequelize.define("userResetCode", { 3 | code: { type: DataTypes.STRING, required: true } 4 | }, { 5 | tableName: 'userResetCode', 6 | classMethods: { 7 | associate: models => { 8 | Model.belongsTo(models.user); 9 | Model.belongsTo(models.app); 10 | } 11 | } 12 | }); 13 | 14 | return Model; 15 | }; 16 | -------------------------------------------------------------------------------- /models/userToken.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | const Model = sequelize.define("userToken", { 3 | token: { type: DataTypes.STRING }, 4 | deleted: { type: DataTypes.BOOLEAN, defaultValue: 0 } 5 | }, { 6 | tableName: 'userToken', 7 | classMethods: { 8 | associate: models => { 9 | Model.belongsTo(models.user); 10 | Model.belongsTo(models.app); 11 | } 12 | } 13 | }); 14 | 15 | return Model; 16 | }; -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vq-auth", 3 | "version": "0.0.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/geojson": { 8 | "version": "1.0.2", 9 | "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-1.0.2.tgz", 10 | "integrity": "sha1-sC0QqwKOKSisWSoFGqpJgaGUHQM=" 11 | }, 12 | "accepts": { 13 | "version": "1.3.3", 14 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz", 15 | "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo=", 16 | "requires": { 17 | "mime-types": "2.1.16", 18 | "negotiator": "0.6.1" 19 | } 20 | }, 21 | "array-flatten": { 22 | "version": "1.1.1", 23 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 24 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 25 | }, 26 | "async": { 27 | "version": "1.5.2", 28 | "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", 29 | "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" 30 | }, 31 | "bcrypt-nodejs": { 32 | "version": "0.0.3", 33 | "resolved": "https://registry.npmjs.org/bcrypt-nodejs/-/bcrypt-nodejs-0.0.3.tgz", 34 | "integrity": "sha1-xgkX8m3CNWYVZsaBBhwwPCsohCs=" 35 | }, 36 | "bignumber.js": { 37 | "version": "2.1.4", 38 | "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.1.4.tgz", 39 | "integrity": "sha1-KbO7aT27I46Ity6sL7iWUIiLLVk=" 40 | }, 41 | "bluebird": { 42 | "version": "3.5.0", 43 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.0.tgz", 44 | "integrity": "sha1-eRQg1/VR7qKJdFOop3ZT+WYG1nw=" 45 | }, 46 | "body-parser": { 47 | "version": "1.15.2", 48 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.15.2.tgz", 49 | "integrity": "sha1-11eM9PHRHV9uqATO813Hp/9trmc=", 50 | "requires": { 51 | "bytes": "2.4.0", 52 | "content-type": "1.0.2", 53 | "debug": "2.2.0", 54 | "depd": "1.1.1", 55 | "http-errors": "1.5.1", 56 | "iconv-lite": "0.4.13", 57 | "on-finished": "2.3.0", 58 | "qs": "6.2.0", 59 | "raw-body": "2.1.7", 60 | "type-is": "1.6.15" 61 | } 62 | }, 63 | "bytes": { 64 | "version": "2.4.0", 65 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", 66 | "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=" 67 | }, 68 | "content-disposition": { 69 | "version": "0.5.2", 70 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", 71 | "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" 72 | }, 73 | "content-type": { 74 | "version": "1.0.2", 75 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz", 76 | "integrity": "sha1-t9ETrueo3Se9IRM8TcJSnfFyHu0=" 77 | }, 78 | "cookie": { 79 | "version": "0.3.1", 80 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", 81 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" 82 | }, 83 | "cookie-signature": { 84 | "version": "1.0.6", 85 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 86 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 87 | }, 88 | "core-util-is": { 89 | "version": "1.0.2", 90 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 91 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 92 | }, 93 | "cors": { 94 | "version": "2.7.2", 95 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.7.2.tgz", 96 | "integrity": "sha1-IThd6//yTCI6EGBbgjEUUpmaHLs=", 97 | "requires": { 98 | "vary": "1.1.1" 99 | } 100 | }, 101 | "debug": { 102 | "version": "2.2.0", 103 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", 104 | "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", 105 | "requires": { 106 | "ms": "0.7.1" 107 | } 108 | }, 109 | "depd": { 110 | "version": "1.1.1", 111 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", 112 | "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" 113 | }, 114 | "destroy": { 115 | "version": "1.0.4", 116 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 117 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 118 | }, 119 | "dottie": { 120 | "version": "1.1.1", 121 | "resolved": "https://registry.npmjs.org/dottie/-/dottie-1.1.1.tgz", 122 | "integrity": "sha1-RcKj9IvWUo7u0memmoSOqspvqmo=" 123 | }, 124 | "ee-first": { 125 | "version": "1.1.1", 126 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 127 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 128 | }, 129 | "encodeurl": { 130 | "version": "1.0.1", 131 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", 132 | "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=" 133 | }, 134 | "escape-html": { 135 | "version": "1.0.3", 136 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 137 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 138 | }, 139 | "etag": { 140 | "version": "1.8.0", 141 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.0.tgz", 142 | "integrity": "sha1-b2Ma7zNtbEY2K1F2QETOIWvjwFE=" 143 | }, 144 | "express": { 145 | "version": "4.15.4", 146 | "resolved": "https://registry.npmjs.org/express/-/express-4.15.4.tgz", 147 | "integrity": "sha1-Ay4iU0ic+PzgJma+yj0R7XotrtE=", 148 | "requires": { 149 | "accepts": "1.3.3", 150 | "array-flatten": "1.1.1", 151 | "content-disposition": "0.5.2", 152 | "content-type": "1.0.2", 153 | "cookie": "0.3.1", 154 | "cookie-signature": "1.0.6", 155 | "debug": "2.6.8", 156 | "depd": "1.1.1", 157 | "encodeurl": "1.0.1", 158 | "escape-html": "1.0.3", 159 | "etag": "1.8.0", 160 | "finalhandler": "1.0.4", 161 | "fresh": "0.5.0", 162 | "merge-descriptors": "1.0.1", 163 | "methods": "1.1.2", 164 | "on-finished": "2.3.0", 165 | "parseurl": "1.3.1", 166 | "path-to-regexp": "0.1.7", 167 | "proxy-addr": "1.1.5", 168 | "qs": "6.5.0", 169 | "range-parser": "1.2.0", 170 | "send": "0.15.4", 171 | "serve-static": "1.12.4", 172 | "setprototypeof": "1.0.3", 173 | "statuses": "1.3.1", 174 | "type-is": "1.6.15", 175 | "utils-merge": "1.0.0", 176 | "vary": "1.1.1" 177 | }, 178 | "dependencies": { 179 | "debug": { 180 | "version": "2.6.8", 181 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", 182 | "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", 183 | "requires": { 184 | "ms": "2.0.0" 185 | } 186 | }, 187 | "ms": { 188 | "version": "2.0.0", 189 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 190 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 191 | }, 192 | "qs": { 193 | "version": "6.5.0", 194 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.0.tgz", 195 | "integrity": "sha512-fjVFjW9yhqMhVGwRExCXLhJKrLlkYSaxNWdyc9rmHlrVZbk35YHH312dFd7191uQeXkI3mKLZTIbSvIeFwFemg==" 196 | }, 197 | "setprototypeof": { 198 | "version": "1.0.3", 199 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", 200 | "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" 201 | } 202 | } 203 | }, 204 | "finalhandler": { 205 | "version": "1.0.4", 206 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.0.4.tgz", 207 | "integrity": "sha512-16l/r8RgzlXKmFOhZpHBztvye+lAhC5SU7hXavnerC9UfZqZxxXl3BzL8MhffPT3kF61lj9Oav2LKEzh0ei7tg==", 208 | "requires": { 209 | "debug": "2.6.8", 210 | "encodeurl": "1.0.1", 211 | "escape-html": "1.0.3", 212 | "on-finished": "2.3.0", 213 | "parseurl": "1.3.1", 214 | "statuses": "1.3.1", 215 | "unpipe": "1.0.0" 216 | }, 217 | "dependencies": { 218 | "debug": { 219 | "version": "2.6.8", 220 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", 221 | "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", 222 | "requires": { 223 | "ms": "2.0.0" 224 | } 225 | }, 226 | "ms": { 227 | "version": "2.0.0", 228 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 229 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 230 | } 231 | } 232 | }, 233 | "forwarded": { 234 | "version": "0.1.0", 235 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.0.tgz", 236 | "integrity": "sha1-Ge+YdMSuHCl7zweP3mOgm2aoQ2M=" 237 | }, 238 | "fresh": { 239 | "version": "0.5.0", 240 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.0.tgz", 241 | "integrity": "sha1-9HTKXmqSRtb9jglTz6m5yAWvp44=" 242 | }, 243 | "generic-pool": { 244 | "version": "2.4.2", 245 | "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.4.2.tgz", 246 | "integrity": "sha1-iGvFvwvrfblugby7oHiBjeWmJoM=" 247 | }, 248 | "http-errors": { 249 | "version": "1.5.1", 250 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.5.1.tgz", 251 | "integrity": "sha1-eIwNLB3iyBuebowBhDtrl+uSB1A=", 252 | "requires": { 253 | "inherits": "2.0.3", 254 | "setprototypeof": "1.0.2", 255 | "statuses": "1.3.1" 256 | } 257 | }, 258 | "iconv-lite": { 259 | "version": "0.4.13", 260 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", 261 | "integrity": "sha1-H4irpKsLFQjoMSrMOTRfNumS4vI=" 262 | }, 263 | "inflection": { 264 | "version": "1.12.0", 265 | "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", 266 | "integrity": "sha1-ogCTVlbW9fa8TcdQLhrstwMihBY=" 267 | }, 268 | "inherits": { 269 | "version": "2.0.3", 270 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 271 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 272 | }, 273 | "ipaddr.js": { 274 | "version": "1.4.0", 275 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.4.0.tgz", 276 | "integrity": "sha1-KWrKh4qCGBbluF0KKFqZvP9FgvA=" 277 | }, 278 | "isarray": { 279 | "version": "0.0.1", 280 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", 281 | "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" 282 | }, 283 | "lodash": { 284 | "version": "4.12.0", 285 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.12.0.tgz", 286 | "integrity": "sha1-K9bcRqBA9Z5obJcu0h2T3FkFMlg=" 287 | }, 288 | "media-typer": { 289 | "version": "0.3.0", 290 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 291 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 292 | }, 293 | "merge-descriptors": { 294 | "version": "1.0.1", 295 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 296 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 297 | }, 298 | "methods": { 299 | "version": "1.1.2", 300 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 301 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 302 | }, 303 | "mime": { 304 | "version": "1.3.4", 305 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz", 306 | "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=" 307 | }, 308 | "mime-db": { 309 | "version": "1.29.0", 310 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.29.0.tgz", 311 | "integrity": "sha1-SNJtI1WJZRcErFkWygYAGRQmaHg=" 312 | }, 313 | "mime-types": { 314 | "version": "2.1.16", 315 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.16.tgz", 316 | "integrity": "sha1-K4WKUuXs1RbbiXrCvodIeDBpjiM=", 317 | "requires": { 318 | "mime-db": "1.29.0" 319 | } 320 | }, 321 | "moment": { 322 | "version": "2.18.1", 323 | "resolved": "https://registry.npmjs.org/moment/-/moment-2.18.1.tgz", 324 | "integrity": "sha1-w2GT3Tzhwu7SrbfIAtu8d6gbHA8=" 325 | }, 326 | "moment-timezone": { 327 | "version": "0.5.13", 328 | "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.13.tgz", 329 | "integrity": "sha1-mc5cfYJyYusPH3AgRBd/YHRde5A=", 330 | "requires": { 331 | "moment": "2.18.1" 332 | } 333 | }, 334 | "ms": { 335 | "version": "0.7.1", 336 | "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", 337 | "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" 338 | }, 339 | "mysql": { 340 | "version": "2.10.2", 341 | "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.10.2.tgz", 342 | "integrity": "sha1-nuXkbwVrK6OnhAoQ6uNCb1k4QpI=", 343 | "requires": { 344 | "bignumber.js": "2.1.4", 345 | "readable-stream": "1.1.14" 346 | } 347 | }, 348 | "negotiator": { 349 | "version": "0.6.1", 350 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", 351 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" 352 | }, 353 | "on-finished": { 354 | "version": "2.3.0", 355 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 356 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 357 | "requires": { 358 | "ee-first": "1.1.1" 359 | } 360 | }, 361 | "parseurl": { 362 | "version": "1.3.1", 363 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz", 364 | "integrity": "sha1-yKuMkiO6NIiKpkopeyiFO+wY2lY=" 365 | }, 366 | "path-to-regexp": { 367 | "version": "0.1.7", 368 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 369 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 370 | }, 371 | "proxy-addr": { 372 | "version": "1.1.5", 373 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.5.tgz", 374 | "integrity": "sha1-ccDuOxAt4/IC87ZPYI0XP8uhqRg=", 375 | "requires": { 376 | "forwarded": "0.1.0", 377 | "ipaddr.js": "1.4.0" 378 | } 379 | }, 380 | "qs": { 381 | "version": "6.2.0", 382 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.2.0.tgz", 383 | "integrity": "sha1-O3hIwDwt7OaalSKw+ujEEm10Xzs=" 384 | }, 385 | "rand-token": { 386 | "version": "0.2.1", 387 | "resolved": "https://registry.npmjs.org/rand-token/-/rand-token-0.2.1.tgz", 388 | "integrity": "sha1-3GfIEjMGyRInstw/W+pz0wE3YiY=" 389 | }, 390 | "random-token": { 391 | "version": "0.0.8", 392 | "resolved": "https://registry.npmjs.org/random-token/-/random-token-0.0.8.tgz", 393 | "integrity": "sha1-HPhFrz+zHlf3yqS5oXNHjEZIO2E=" 394 | }, 395 | "range-parser": { 396 | "version": "1.2.0", 397 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", 398 | "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" 399 | }, 400 | "raw-body": { 401 | "version": "2.1.7", 402 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.7.tgz", 403 | "integrity": "sha1-rf6s4uT7MJgFgBTQjActzFl1h3Q=", 404 | "requires": { 405 | "bytes": "2.4.0", 406 | "iconv-lite": "0.4.13", 407 | "unpipe": "1.0.0" 408 | } 409 | }, 410 | "readable-stream": { 411 | "version": "1.1.14", 412 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", 413 | "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", 414 | "requires": { 415 | "core-util-is": "1.0.2", 416 | "inherits": "2.0.3", 417 | "isarray": "0.0.1", 418 | "string_decoder": "0.10.31" 419 | } 420 | }, 421 | "retry-as-promised": { 422 | "version": "2.3.0", 423 | "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-2.3.0.tgz", 424 | "integrity": "sha1-J79czZmZMrMWZWloJc82MMJ8Vi0=", 425 | "requires": { 426 | "bluebird": "3.5.0", 427 | "debug": "2.2.0" 428 | } 429 | }, 430 | "semver": { 431 | "version": "5.4.1", 432 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", 433 | "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==" 434 | }, 435 | "send": { 436 | "version": "0.15.4", 437 | "resolved": "https://registry.npmjs.org/send/-/send-0.15.4.tgz", 438 | "integrity": "sha1-mF+qPihLAnPHkzZKNcZze9k5Bbk=", 439 | "requires": { 440 | "debug": "2.6.8", 441 | "depd": "1.1.1", 442 | "destroy": "1.0.4", 443 | "encodeurl": "1.0.1", 444 | "escape-html": "1.0.3", 445 | "etag": "1.8.0", 446 | "fresh": "0.5.0", 447 | "http-errors": "1.6.2", 448 | "mime": "1.3.4", 449 | "ms": "2.0.0", 450 | "on-finished": "2.3.0", 451 | "range-parser": "1.2.0", 452 | "statuses": "1.3.1" 453 | }, 454 | "dependencies": { 455 | "debug": { 456 | "version": "2.6.8", 457 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", 458 | "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", 459 | "requires": { 460 | "ms": "2.0.0" 461 | } 462 | }, 463 | "http-errors": { 464 | "version": "1.6.2", 465 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", 466 | "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", 467 | "requires": { 468 | "depd": "1.1.1", 469 | "inherits": "2.0.3", 470 | "setprototypeof": "1.0.3", 471 | "statuses": "1.3.1" 472 | } 473 | }, 474 | "ms": { 475 | "version": "2.0.0", 476 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 477 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 478 | }, 479 | "setprototypeof": { 480 | "version": "1.0.3", 481 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", 482 | "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" 483 | } 484 | } 485 | }, 486 | "sequelize": { 487 | "version": "3.30.4", 488 | "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-3.30.4.tgz", 489 | "integrity": "sha1-vaLfHjGFSwmeQUmhEen8Clyh0aQ=", 490 | "requires": { 491 | "bluebird": "3.5.0", 492 | "depd": "1.1.1", 493 | "dottie": "1.1.1", 494 | "generic-pool": "2.4.2", 495 | "inflection": "1.12.0", 496 | "lodash": "4.12.0", 497 | "moment": "2.18.1", 498 | "moment-timezone": "0.5.13", 499 | "retry-as-promised": "2.3.0", 500 | "semver": "5.4.1", 501 | "shimmer": "1.1.0", 502 | "terraformer-wkt-parser": "1.1.2", 503 | "toposort-class": "1.0.1", 504 | "uuid": "3.1.0", 505 | "validator": "5.7.0", 506 | "wkx": "0.2.0" 507 | } 508 | }, 509 | "serve-static": { 510 | "version": "1.12.4", 511 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.12.4.tgz", 512 | "integrity": "sha1-m2qpjutyU8Tu3Ewfb9vKYJkBqWE=", 513 | "requires": { 514 | "encodeurl": "1.0.1", 515 | "escape-html": "1.0.3", 516 | "parseurl": "1.3.1", 517 | "send": "0.15.4" 518 | } 519 | }, 520 | "setprototypeof": { 521 | "version": "1.0.2", 522 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.2.tgz", 523 | "integrity": "sha1-gaVSFB7BBLiOic44MQOtXGZWTQg=" 524 | }, 525 | "shimmer": { 526 | "version": "1.1.0", 527 | "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.1.0.tgz", 528 | "integrity": "sha1-l9c3cTf/u6tCVSLkKf4KqJpIizU=" 529 | }, 530 | "statuses": { 531 | "version": "1.3.1", 532 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", 533 | "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" 534 | }, 535 | "string_decoder": { 536 | "version": "0.10.31", 537 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", 538 | "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" 539 | }, 540 | "terraformer": { 541 | "version": "1.0.8", 542 | "resolved": "https://registry.npmjs.org/terraformer/-/terraformer-1.0.8.tgz", 543 | "integrity": "sha1-UeCtiXRvzyFh3G9lqnDkI3fItZM=", 544 | "requires": { 545 | "@types/geojson": "1.0.2" 546 | } 547 | }, 548 | "terraformer-wkt-parser": { 549 | "version": "1.1.2", 550 | "resolved": "https://registry.npmjs.org/terraformer-wkt-parser/-/terraformer-wkt-parser-1.1.2.tgz", 551 | "integrity": "sha1-M2oMj8gglKWv+DKI9prt7NNpvww=", 552 | "requires": { 553 | "terraformer": "1.0.8" 554 | } 555 | }, 556 | "toposort-class": { 557 | "version": "1.0.1", 558 | "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", 559 | "integrity": "sha1-f/0feMi+KMO6Rc1OGj9e4ZO9mYg=" 560 | }, 561 | "type-is": { 562 | "version": "1.6.15", 563 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", 564 | "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=", 565 | "requires": { 566 | "media-typer": "0.3.0", 567 | "mime-types": "2.1.16" 568 | } 569 | }, 570 | "unpipe": { 571 | "version": "1.0.0", 572 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 573 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 574 | }, 575 | "utils-merge": { 576 | "version": "1.0.0", 577 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", 578 | "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=" 579 | }, 580 | "uuid": { 581 | "version": "3.1.0", 582 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", 583 | "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==" 584 | }, 585 | "validator": { 586 | "version": "5.7.0", 587 | "resolved": "https://registry.npmjs.org/validator/-/validator-5.7.0.tgz", 588 | "integrity": "sha1-eoelgUa2laxIYHEUHAxJ1n2gXlw=" 589 | }, 590 | "vary": { 591 | "version": "1.1.1", 592 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.1.tgz", 593 | "integrity": "sha1-Z1Neu2lMHVIldFeYRmUyP1h+jTc=" 594 | }, 595 | "wkx": { 596 | "version": "0.2.0", 597 | "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.2.0.tgz", 598 | "integrity": "sha1-dsJPFqzQzY+TzTSqMx4PeWElboQ=" 599 | } 600 | } 601 | } 602 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vq-auth", 3 | "version": "0.0.1", 4 | "description": "Token-based Authentification microservice", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "VQ LABS", 10 | "license": "MIT", 11 | "dependencies": { 12 | "async": "~1.5.2", 13 | "bcrypt-nodejs": "0.0.3", 14 | "body-parser": "~1.15.0", 15 | "cors": "~2.7.1", 16 | "express": "^4.15.2", 17 | "mysql": "~2.10.2", 18 | "rand-token": "~0.2.1", 19 | "random-token": "0.0.8", 20 | "sequelize": "^3.30.4" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /routes.js: -------------------------------------------------------------------------------- 1 | const async = require("async"); 2 | const AuthController = require("./controllers/AuthCtrl.js"); 3 | const NetworkController = require("./controllers/NetworkCtrl.js"); 4 | const SignupController = require("./controllers/SignupCtrl.js"); 5 | const LoginController = require("./controllers/LoginCtrl.js"); 6 | const AuthService = require("./services/AuthService.js"); 7 | const identifyApp = require("./middleware/IdentifyApp.js"); 8 | const identifyAppUser = require("./middleware/IdentifyAppUser.js"); 9 | const models = require("./models"); 10 | 11 | const randomToken = require('random-token'); 12 | 13 | const sendResponse = (res, err, data) => { 14 | if (err) { 15 | if (err.status) { 16 | return res.status(err.status).send(err); 17 | } 18 | 19 | if (err.code) { 20 | return res.status(400).send(err); 21 | } 22 | 23 | if (typeof err === 'string') { 24 | return res.status(400).send({ 25 | code: err 26 | }); 27 | } 28 | 29 | return res.status(500).send(err); 30 | } 31 | 32 | return res.status(200).send(data); 33 | }; 34 | 35 | module.exports = app => { 36 | app.post('/auth/token', (req, res, next) => { 37 | var appId = req.app ? req.app.id : false; 38 | var token = req.body.token; 39 | 40 | AuthController.checkToken(appId, token, (err, rUser) => { 41 | return sendResponse(res, err, rUser); 42 | }); 43 | }); 44 | 45 | /** 46 | * Marks the token as deleted, used for logout. 47 | * @param token: token that needs to be invalidated 48 | */ 49 | app.delete('/auth/token', (req, res, next) => { 50 | return models.update({ 51 | deleted: 1 52 | }, { 53 | where: [ 54 | { token: req.body.token }, 55 | { appId: req.auth.appId } 56 | ] 57 | }) 58 | .then(() => sendResponse(res), err => sendResponse(res, err)); 59 | }); 60 | 61 | app.post('/auth/password', identifyApp, 62 | (req, res, next) => { 63 | if (!req.app) { 64 | req.user = false; 65 | 66 | return next(); 67 | } 68 | 69 | const token = req.auth.token || req.body.token; 70 | const appId = req.app.id; 71 | 72 | models.userToken 73 | .findOne({ 74 | where: { 75 | $and: [ 76 | { appId }, 77 | { token } 78 | ] 79 | }}) 80 | .then(app => { 81 | if (app) { 82 | req.user = { 83 | id: app.dataValues.userId 84 | }; 85 | } else { 86 | req.user = false; 87 | } 88 | 89 | return next(); 90 | }, err => { 91 | console.error(err); 92 | 93 | return res.status(502).send(err) 94 | }); 95 | }, 96 | (req, res, next) => { 97 | var appId = req.app ? req.app.id : false; 98 | var userId = req.user.id; 99 | var currentPassword = req.body.currentPassword; 100 | var newPassword = req.body.newPassword; 101 | 102 | AuthService 103 | .checkPassword(appId, userId, currentPassword, (err, isCorrect) => { 104 | if (err) { 105 | return sendResponse(res, err); 106 | } 107 | 108 | if (isCorrect) { 109 | AuthController.changePassword(appId, userId, newPassword); 110 | 111 | return sendResponse(res); 112 | } 113 | 114 | return sendResponse(res, { 115 | status: 400, 116 | code: "WRONG_PASSWORD" 117 | }); 118 | }); 119 | }); 120 | 121 | app.post('/auth/password/request-reset', 122 | identifyApp, 123 | (req, res, next) => { 124 | var appId = req.app ? req.app.id : false; 125 | var email = req.body.email; 126 | var userEmail, userId; 127 | 128 | async.waterfall([ 129 | cb => models 130 | .userEmail 131 | .findOne({ 132 | where: { 133 | $and: [ 134 | { email }, 135 | { appId } 136 | ] 137 | } 138 | }) 139 | .then(rUserEmail => { 140 | if (!rUserEmail) { 141 | return setTimeout(() => { 142 | return cb('EMAIL_NOT_FOUND') 143 | }, 100); 144 | } 145 | 146 | userEmail = rUserEmail; 147 | userId = userEmail.userId; 148 | 149 | return cb(); 150 | }, cb), 151 | cb => models.userResetCode 152 | .create({ 153 | appId, 154 | userId: userEmail.userId, 155 | code: randomToken(64) 156 | }) 157 | .then(rCode => { 158 | cb(null, rCode); 159 | }, cb) 160 | ], (err, resetCode) => sendResponse(res, err, resetCode)); 161 | }); 162 | 163 | app.post('/auth/password/reset', 164 | identifyApp, 165 | (req, res, next) => { 166 | const appId = req.app ? req.app.id : false; 167 | const code = req.body.code; 168 | const newPassword = req.body.password; 169 | 170 | async.waterfall([ 171 | cb => models 172 | .userResetCode 173 | .findOne({ 174 | where: { 175 | $and: [ 176 | { code }, 177 | { appId } 178 | ] 179 | } 180 | }) 181 | .then(rUserResetCode => { 182 | if (!rUserResetCode) { 183 | return setTimeout(() => 184 | cb('WRONG_RESET_CODE'), 185 | 500 186 | ); 187 | } 188 | 189 | userId = rUserResetCode.userId; 190 | 191 | return cb(); 192 | }, cb), 193 | cb => { 194 | return models.userResetCode 195 | .destroy({ 196 | where: { 197 | $and: [ 198 | { appId }, 199 | { userId } 200 | ] 201 | } 202 | }) 203 | .then(_ => cb(), cb) 204 | }, 205 | cb => { 206 | return models.userResetCode 207 | .destroy({ 208 | where: { 209 | $and: [ 210 | { appId }, 211 | { userId } 212 | ] 213 | } 214 | }) 215 | .then(_ => cb(), cb) 216 | }, 217 | cb => AuthService 218 | .createNewPassword(appId, userId, newPassword, cb) 219 | ], err => sendResponse(res, err, { status: 200 })); 220 | }); 221 | 222 | app.post('/auth/networks/facebook', (req, res) => { 223 | var appId = req.app ? req.app.id : false; 224 | var token = req.body.token; 225 | var refreshToken = req.body.refreshToken; 226 | var Profile = req.body.Profile; 227 | 228 | NetworkController 229 | .connectToFacebook(appId, token, refreshToken, Profile, (err, rUser) => { 230 | return sendResponse(res, err, rUser); 231 | }); 232 | }); 233 | 234 | /** 235 | * Gets VQ user by 236 | * @query email 237 | * @query userId 238 | */ 239 | app.get('/auth/user', (req, res) => { 240 | var appId = req.app ? req.app.id : false; 241 | 242 | if (req.query.email) { 243 | return AuthService 244 | .getUserIdFromEmail(appId, req.query.email, (err, vqUser) => { 245 | return sendResponse(res, err, vqUser); 246 | }); 247 | } 248 | 249 | if (req.query.userId) { 250 | return AuthService 251 | .getEmailsFromUserId(appId, req.query.userId, (err, vqUser) => { 252 | return sendResponse(res, err, vqUser); 253 | }); 254 | } 255 | 256 | res.status(400).send('Provide email or userId'); 257 | }); 258 | 259 | app.post('/auth/local/signup', (req, res) => { 260 | const appId = req.app ? req.app.id : false; 261 | const email = req.body.email; 262 | const password = req.body.password; 263 | 264 | SignupController 265 | .createLocalAccount(appId, email, password, (err, rUser) => { 266 | return sendResponse(res,err,rUser); 267 | }); 268 | }); 269 | 270 | /** 271 | * 272 | */ 273 | app.post('/auth/local/login/token', (req, res, next) => { 274 | var appId = req.app ? req.app.id : false; 275 | var email = req.body.email; 276 | 277 | AuthService.getUserIdFromEmail(appId, email, (err, userEmail) => { 278 | if (err) { 279 | return sendResponse(res, err, rUser); 280 | } 281 | 282 | return AuthService.createNewToken(appId, userEmail.userId, (err, userToken) => { 283 | return sendResponse(res, err, userToken); 284 | }); 285 | }); 286 | }); 287 | 288 | app.post('/auth/local/login', (req, res) => { 289 | var appId = req.app ? req.app.id : false; 290 | var email = req.body.email; 291 | var password = req.body.password; 292 | 293 | LoginController 294 | .loginWithPassword(appId, email, password, (err, rUser) => { 295 | return sendResponse(res, err, rUser); 296 | }); 297 | }); 298 | 299 | app.post('/auth/pw-reset', (req, res, next) => { 300 | sendResponse(res, null, null); 301 | }); 302 | }; 303 | -------------------------------------------------------------------------------- /scripts/create-account.js: -------------------------------------------------------------------------------- 1 | const models = require('../models'); 2 | 3 | models.account.create({}, {}) 4 | .then(data => { 5 | console.log(data.dataValues); 6 | 7 | process.exit(); 8 | }); -------------------------------------------------------------------------------- /scripts/create-app.js: -------------------------------------------------------------------------------- 1 | const models = require('../models'); 2 | 3 | models.app.create({ 4 | apiKey: 'test', 5 | appKey: 'test', 6 | accountId: 1 7 | }, {}) 8 | .then(data => { 9 | console.log(data.dataValues); 10 | 11 | process.exit(); 12 | }); -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const cors = require("cors"); 2 | const async = require("async"); 3 | const express = require("express"); 4 | const models = require('./models'); 5 | 6 | 7 | /** 8 | * VQ_PORT - local 9 | * PORT - set by elastic beanstalk 10 | * 5000 - default test port 11 | */ 12 | const CONFIG = { 13 | PORT: process.env.VA_PORT || process.env.PORT || 5000, 14 | }; 15 | 16 | if (!process.env.VQ_VA_DB) { 17 | return console.error('VQ_VA_DB is initial'); 18 | } 19 | 20 | const app = express(); 21 | 22 | app.use(require("cors")()); 23 | app.use(require('body-parser')()); 24 | app.use(require("./middleware/ReadHeaders.js")); 25 | app.use(require("./middleware/IdentifyApp.js")); 26 | 27 | require("./routes")(app); 28 | 29 | models.seq.sync().then(() => { 30 | var server = app.listen(CONFIG.PORT, () => { 31 | var host = server.address().address; 32 | var port = server.address().port; 33 | 34 | console.log(`VQ-AUTH listening at http://${host}:${port}`); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /services/AuthService.js: -------------------------------------------------------------------------------- 1 | const bcrypt = require('bcrypt-nodejs'); 2 | const async = require("async"); 3 | const randtoken = require('rand-token'); 4 | const models = require("../models"); 5 | const logIndex = "[AuthService]"; 6 | 7 | const generateHashSync = password => bcrypt.hashSync(password, bcrypt.genSaltSync(8), null); 8 | 9 | const validPasswordSync = (password, encryptedPassword) => { 10 | if (!password || !encryptedPassword) { 11 | console.error(logIndex, "validPasswordSync", "initial arguments"); 12 | 13 | return false; 14 | } 15 | 16 | return bcrypt.compareSync(password, encryptedPassword); 17 | } 18 | 19 | const createNewUser = (appId, callback) => { 20 | return models.user 21 | .create({ appId: appId }) 22 | .then(instance => callback(null, instance), err => callback(err)); 23 | }; 24 | 25 | const createNewPassword = (appId, userId, password, cb) => { 26 | cb = cb || function() {}; 27 | 28 | if (!appId || !userId || !password) { 29 | return cb('INIT_PARAMS'); 30 | } 31 | 32 | models.userPassword 33 | .destroy({ 34 | where: { 35 | $and: [ 36 | { appId }, 37 | { userId } 38 | ] 39 | }, 40 | password: generateHashSync(password) 41 | }) 42 | .then(_ => models.userPassword.create({ 43 | appId: appId, 44 | userId: userId, 45 | password: generateHashSync(password) 46 | })) 47 | .then(_ => cb()) 48 | .catch(cb); 49 | }; 50 | 51 | const createNewEmail = (appId, userId, email, callback) => async.series([ 52 | callback => models.userEmail 53 | .findOne({ 54 | where: { 55 | $and: [ 56 | { appId }, 57 | { email } 58 | ] 59 | } 60 | }) 61 | .then(result => { 62 | if (result) { 63 | return callback({ 64 | code: "EMAIL_EXISTS" 65 | }); 66 | } 67 | 68 | return callback(); 69 | }), 70 | callback => { 71 | models.userEmail 72 | .create({ 73 | appId: appId, 74 | userId: userId, 75 | email: email 76 | }) 77 | .then(instance => callback(), err => callback(err)); 78 | } 79 | ], err => callback(err)); 80 | 81 | 82 | 83 | const getUserIdFromNetwork = (network, networkId, callback) => { 84 | var sql = "SELECT user.id AS userId FROM user AS user"; 85 | 86 | sql += " INNER JOIN userNetwork AS network"; 87 | sql += " ON network.userId = user.id" 88 | sql += ` WHERE network.networkId = ${networkId} AND network.network = '${network}'`; 89 | 90 | models.seq.query(sql) 91 | .then(result => { 92 | if (err) { 93 | return callback(err); 94 | } 95 | 96 | if (result.length) { 97 | return callback(null, result[0]) 98 | } 99 | 100 | return callback(null, false); 101 | }); 102 | }; 103 | 104 | const updateNetworkToken = (userId, network, networkId, token) => 105 | models.userToken 106 | .update({ 107 | token: token 108 | }, { 109 | where: { 110 | $and: [ 111 | { networkId }, 112 | { userId }, 113 | ] 114 | } 115 | }) 116 | .then(() => {}, err => console.error(err)); 117 | 118 | const createNewNetwork = (appId, userId, network, networkId, token, refreshToken, callback) => 119 | models.userNetwork 120 | .create({ 121 | appId: appId, 122 | userId: userId, 123 | network: network, 124 | networkId: networkId, 125 | token: token, 126 | refreshToken: refreshToken 127 | }) 128 | .then(instance => callback(), err => callback(err)); 129 | 130 | const createNewToken = (appId, userId, callback) => 131 | models.userToken 132 | .create({ 133 | token: randtoken.generate(250), 134 | userId: userId, 135 | appId: appId 136 | }) 137 | .then(instance => callback(null, instance), err => callback(err)); 138 | 139 | const checkToken = (appId, token, callback) => { 140 | models.userToken 141 | .findOne({ 142 | where: [ 143 | { token }, 144 | { appId } 145 | ] 146 | }) 147 | .then(instance => { 148 | var response = instance || false; 149 | 150 | return callback(null, response); 151 | }, err => callback(err)) 152 | }; 153 | 154 | const checkPassword = (appId, userId, password, callback) => { 155 | models.userPassword 156 | .findOne({ 157 | where: { 158 | $and: [ 159 | { userId }, 160 | { appId } 161 | ] 162 | } 163 | }) 164 | .then(instance => { 165 | var isCorrect = false; 166 | 167 | if (instance) { 168 | isCorrect = validPasswordSync(password, instance.password); 169 | } 170 | 171 | return callback(null, isCorrect); 172 | }, err => callback(err)) 173 | }; 174 | 175 | const getEmailsFromUserId = (appId, userId, callback) => { 176 | return models.userEmail 177 | .findAll({ 178 | where: { 179 | $and: [ 180 | { appId }, 181 | { userId } 182 | ] 183 | } 184 | }) 185 | .then(instances => 186 | callback(null, instances || false), 187 | err => callback(err) 188 | ) 189 | }; 190 | 191 | const getUserIdFromEmail = (appId, email, callback) => { 192 | return models.userEmail 193 | .findOne({ 194 | where: { 195 | $and: [ 196 | { appId }, 197 | { email } 198 | ] 199 | } 200 | }) 201 | .then( 202 | instance => callback(null, instance || false), 203 | err => callback(err) 204 | ) 205 | }; 206 | 207 | const addUserProp = (userId, propKey, propValue, callback) => { 208 | if (!userId || !propKey) { 209 | return callback({ 210 | status: 400, 211 | code: "INITIAL_PARAMS" 212 | }); 213 | } 214 | 215 | models.userProp 216 | .findOne({ 217 | where: { 218 | $and: [ 219 | { userId }, 220 | { propKey } 221 | ] 222 | } 223 | }) 224 | .then((err, result) => { 225 | if (err) { 226 | console.error(err); 227 | 228 | return callback(err); 229 | } 230 | 231 | var promise; 232 | 233 | if (result) { 234 | promise = models.userProp.update({ 235 | propValue 236 | }, { 237 | where: [ 238 | { propKey }, 239 | { userId } 240 | ] 241 | }); 242 | } else { 243 | promise = models.userProp.create({ 244 | propValue, 245 | propKey, 246 | userId 247 | }, { 248 | where: [ 249 | { propKey }, 250 | { userId } 251 | ] 252 | }); 253 | } 254 | 255 | promise 256 | .then(() => callback(), callback); 257 | }); 258 | }; 259 | 260 | module.exports = { 261 | createNewUser, 262 | createNewPassword, 263 | createNewEmail, 264 | createNewToken, 265 | createNewNetwork, 266 | addUserProp, 267 | checkPassword, 268 | checkToken, 269 | getUserIdFromEmail, 270 | getEmailsFromUserId, 271 | getUserIdFromNetwork, 272 | updateNetworkToken, 273 | generateHashSync, 274 | validPasswordSync 275 | }; --------------------------------------------------------------------------------