Commit b0d9ae6b authored by Lorex's avatar Lorex
Browse files

release: 完成 SSO 基本架構

parents 6b71aca1 767a9c9a
{
"apps": [{
"name": "exam-sso-api",
"script": "src/app.js",
"watch": true,
"env": {
"NODE_ENV": "development",
"PORT":"1337"
}
}]
}
\ No newline at end of file
module.exports = {
friendlyName: 'Auth',
description: 'Auth app.',
inputs: {
appId: {
type: 'string',
required: true
},
appSecret: {
type: 'string',
required: true
},
token: {
type: 'string',
required: true
}
},
exits: {
success: {
responseType: 'ok'
},
err: {
responseType: 'err'
}
},
fn: async function (inputs, exits) {
// 驗證 app 是否有效
const _getApp = await App.findOne({
and: [{
appId: inputs.appId
},
{
appSecret: inputs.appSecret
}
]});
if (!_getApp) {
return exits.err(901);
}
// app 有效,開始驗證 JWT
const jwt = await sails.helpers.verifyJwt(inputs.token);
if (jwt.hasOwnProperty('name') && jwt.name === 'JsonWebTokenError') {
return exits.err(902);
}
// All done.
return exits.success(jwt);
}
};
module.exports = {
friendlyName: 'Login',
description: 'Login user.',
inputs: {
email: {
type: 'string',
required: true,
isNotEmptyString: true
},
password: {
type: 'string',
required: true,
isNotEmptyString: true
}
},
exits: {
success: {
responseType: 'ok'
},
err: {
responseType: 'err'
}
},
fn: async function (inputs, exits) {
// 取得帳號並驗證帳號是否存在
const _findUser = await User.findOne({
email: inputs.email
}).decrypt();
if (!_findUser) {
return exits.err(100);
}
// 比對密碼
if(!(inputs.password === _findUser.password)){
return exits.err(101);
}
// 比對帳號是否啟用
if (_findUser.status !== 1) {
return exits.err(102);
}
// 驗證成功,簽發 JWT Token
let jwtToken = await sails.helpers.issueJwt(inputs.email);
// 回傳
return exits.success({
token: jwtToken
});
}
};
const jwt = require('jsonwebtoken');
const uuid = require('uuid/v4');
module.exports = {
friendlyName: 'Issue jwt',
description: '',
inputs: {
email: {
type: 'string',
required: true,
isNotEmptyString: true
}
},
exits: {
success: {
description: 'All done.',
},
},
fn: async function (inputs, exits) {
let token = await jwt.sign({
iat: Math.floor(Date.now()/1000)
}, sails.config.custom.jwtSecret, {
issuer: 'Exam API SSO System',
subject: inputs.email,
audience: 'Exam System',
expiresIn: '3h',
jwtid: uuid(),
});
return exits.success(token);
}
};
const jwt = require('jsonwebtoken');
module.exports = {
friendlyName: 'Verify jwt',
description: '',
inputs: {
token:{
type: 'string',
required: true
}
},
exits: {
success: {
description: 'All done.',
},
},
fn: async function (inputs, exits) {
try {
let decoded = await jwt.verify(inputs.token, sails.config.custom.jwtSecret);
return exits.success(decoded);
} catch (err) {
return exits.success(err);
}
}
};
/**
* App.js
*
* @description :: A model definition represents a database table/collection.
* @docs :: https://sailsjs.com/docs/concepts/models-and-orm/models
*/
const uuid = require('uuid/v4');
module.exports = {
attributes: {
name: {
type: 'string',
required: true,
isNotEmptyString: true
}, // App 名稱
info: {
type: 'string',
defaultsTo: ''
}, // App 介紹
appId: {
type: 'string',
defaultsTo: ''
}, // App ID
appSecret: {
type: 'string',
}, // App Secret
},
beforeCreate: (data, proceed) => {
data.appId = uuid().replace(/\-/g, '').substr(0, 8).toString();
data.appSecret = uuid().replace(/\-/g, '').toString();
return proceed();
}
};
/**
* User.js
*
* @description :: A model definition represents a database table/collection.
* @docs :: https://sailsjs.com/docs/concepts/models-and-orm/models
*/
const uuid = require('uuid/v4');
module.exports = {
attributes: {
email: {
type: 'string',
unique: true,
required: true,
isEmail: true
}, // mail(帳號)
// name: {
// type: 'string',
// required: true
// }, //姓名
password: {
type: 'string',
required: true,
encrypt: true
}, // 密碼,
// role: {
// type: 'number',
// defaultsTo: 999
// }, // 權限,0: 系統管理員、999:一般使用者
status: {
type: 'number',
defaultsTo: 1
}, // 狀態,0: 等待驗證、1: 啟用中、2: 停用中
// regToken: {
// type: 'string',
// defaultsTo: '',
// unique: true
// }, // 註冊驗證碼
},
beforeCreate: (data, proceed) => {
data.regToken = uuid().replace(/\-/g, '').substr(0, 16);
return proceed();
}
};
/**
* Module dependencies
*/
var util = require('util');
var _ = require('@sailshq/lodash');
/**
* 400 (Bad Request) Handler
*
* Usage:
* return res.badRequest();
* return res.badRequest(data);
*
* e.g.:
* ```
* return res.badRequest(
* 'Please choose a valid `password` (6-12 characters)',
* 'trial/signup'
* );
* ```
*/
module.exports = function badRequest(data) {
// Get access to `req` and `res`
var req = this.req;
var res = this.res;
// Get access to `sails`
var sails = req._sails;
// Set status code
res.status(400);
return res.json({success: false, msg: `請求錯誤:${data.details}`});
};
\ No newline at end of file
......@@ -15,9 +15,6 @@ module.exports.custom = {
* Any other custom config this Sails app should use during development. *
* *
***************************************************************************/
// mailgunDomain: 'transactional-mail.example.com',
// mailgunSecret: 'key-testkeyb183848139913858e8abd9a3',
// stripeSecret: 'sk_test_Zzd814nldl91104qor5911gjald',
// …
jwtSecret: 'ex@m-sso-api-jwt-s3cret'
};
module.exports.errcode = {
code: {
//- 100: 帳號相關
100: {
msg: '登入失敗:找不到使用者',
status: 400
},
101: {
msg: '登入失敗:密碼錯誤',
status: 400
},
102: {
msg: '登入失敗:帳號尚未啟用',
status: 400
},
//- 900: 操作相關
900: {
msg: '操作失敗:尚未登入',
status: 401
}
},
901: {
msg: '操作失敗:無效的 AppId 或 AppSecret',
status: 401
},
902: {
msg: '操作失敗:無效的 JWT Token',
status: 401
},
}
};
......@@ -9,6 +9,6 @@
*/
module.exports.routes = {
'POST /user/login': 'user/login',
'POST /app/auth': 'app/auth'
};
......@@ -356,6 +356,11 @@
"resolved": "https://registry.npmjs.org/bson/-/bson-1.0.9.tgz",
"integrity": "sha512-IQX9/h7WdMBIW/q/++tGd+emQr0XMdeZ6icnT/74Xk9fnabWn+gZgpE+9V+gujL3hhJOoNrnDVY7tWdzc7NUTg=="
},
"buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk="
},
"buffer-from": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
......@@ -723,6 +728,14 @@
"resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz",
"integrity": "sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw="
},
"ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
"requires": {
"safe-buffer": "^5.0.1"
}
},
"ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
......@@ -1389,6 +1402,54 @@
"graceful-fs": "^4.1.6"
}
},
"jsonwebtoken": {
"version": "8.5.1",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz",
"integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==",
"requires": {
"jws": "^3.2.2",
"lodash.includes": "^4.3.0",
"lodash.isboolean": "^3.0.3",
"lodash.isinteger": "^4.0.4",
"lodash.isnumber": "^3.0.3",
"lodash.isplainobject": "^4.0.6",
"lodash.isstring": "^4.0.1",
"lodash.once": "^4.0.0",
"ms": "^2.1.1",
"semver": "^5.6.0"
},
"dependencies": {
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"semver": {
"version": "5.7.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
"integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA=="
}
}
},
"jwa": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
"integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
"requires": {
"buffer-equal-constant-time": "1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"jws": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
"requires": {
"jwa": "^1.4.1",
"safe-buffer": "^5.0.1"
}
},
"klaw": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz",
......@@ -1428,11 +1489,46 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
},
"lodash.includes": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
"integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8="
},
"lodash.isboolean": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
"integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY="
},
"lodash.isinteger": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
"integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M="
},
"lodash.isnumber": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
"integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w="
},
"lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs="
},
"lodash.issafeinteger": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/lodash.issafeinteger/-/lodash.issafeinteger-4.0.4.tgz",
"integrity": "sha1-sXbVmQ7GSdBr7cvOLwKOeJBJT5A="
},
"lodash.isstring": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
"integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE="
},
"lodash.once": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
"integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w="
},
"lru-cache": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
......@@ -1741,6 +1837,11 @@
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
},
"node-uuid": {
"version": "1.4.8",
"resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz",
"integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc="
},
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
......
......@@ -6,6 +6,8 @@
"keywords": [],
"dependencies": {
"@sailshq/lodash": "^3.10.3",
"jsonwebtoken": "^8.5.1",
"node-uuid": "^1.4.8",
"sails": "^1.1.0",
"sails-hook-orm": "^2.1.1",
"sails-hook-sockets": "^1.5.5",
......@@ -15,12 +17,12 @@
"@sailshq/eslint": "^4.19.3"
},
"scripts": {
"start": "node src/app.js",
"start": "node app.js",
"test": "npm run lint && npm run custom-tests && echo 'Done.'",
"lint": "eslint . --max-warnings=0 --report-unused-disable-directives && echo '✔ Your .js files look good.'",
"custom-tests": "echo \"(No other custom tests yet.)\" && echo"
},
"main": "src/app.js",
"main": "app.js",
"repository": {
"type": "git",
"url": "http://gitlab.sita.tech/medic/exam-sso-api.git"
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment