From 4e610d87e35fd1b163f9489fa95dde4dadbc9cbd Mon Sep 17 00:00:00 2001 From: Jay Date: Wed, 15 Aug 2018 21:19:06 +0800 Subject: [PATCH] 1. add login api 2. add get channel list api --- bin/hashPass.js | 17 +++++ config/index.js | 1 + libs/route-utils/index.js | 89 ++++++++++++++++++++++++++- libs/route-utils/res-message/index.js | 7 +++ libs/tools/index.js | 64 +++++++++++++++++++ package.json | 1 + route/api/index.js | 26 +++++++- route/api/twitch/index.js | 41 ++++++++++++ 8 files changed, 242 insertions(+), 4 deletions(-) create mode 100644 bin/hashPass.js create mode 100644 libs/tools/index.js create mode 100644 route/api/twitch/index.js diff --git a/bin/hashPass.js b/bin/hashPass.js new file mode 100644 index 0000000..0d04fd6 --- /dev/null +++ b/bin/hashPass.js @@ -0,0 +1,17 @@ +require('module-alias/register') +const { + hashPassword +} = require('@libs/tools') + +let args = process.argv +let pass = args[2] || '' +if (typeof pass !== 'string' || pass.trim().length === 0) { + console.log('command: node hashPass ') + process.exit(1) +} + +hashPassword(pass).then(r => { + console.log(`Password: ${r}`) +}).catch(err => { + console.log(err) +}) diff --git a/config/index.js b/config/index.js index 3cbd297..ae69d30 100644 --- a/config/index.js +++ b/config/index.js @@ -2,6 +2,7 @@ module.exports = { port: process.env.NODE_PORT || 10230, url: process.env.HOST_URL || '', image_root: process.env.IMAGE_ROOT || '/image', + salt_round: process.env.SALT_ROUND || 10, line: { secret: process.env.LINE_SECRET || '', access: process.env.LINE_ACCESS || '' diff --git a/libs/route-utils/index.js b/libs/route-utils/index.js index 13dc203..ff5c26b 100644 --- a/libs/route-utils/index.js +++ b/libs/route-utils/index.js @@ -44,11 +44,96 @@ const chkObject = function (key = '', type = '', empty = false) { return true } -const resObject = (key = null, message = null, msgCode = null) => { +/** + * parse number + * @param {any} v input number + * @param {number} def default number + * @param {number} min min number + * @param {number} max max number + * @return {number} + */ +const toInt = (v, def = 0, min = null, max = null) => { + if (!isFinite(def)) def = 0 + if (typeof def === 'string') def = parseInt(def) + min = isFinite(min) ? (typeof min === 'string' ? parseInt(min) : min) : null + max = isFinite(max) ? (typeof max === 'string' ? parseInt(max) : max) : null + if (!isFinite(v)) return def + v = parseInt(v) + if (min !== null && v < min) v = min + if (max !== null && v > max) v = max + return v +} +/** + * response object + * @param {string} key response key + * @param {string|object} message response object or message + * @param {number} msgCode message code + */ +const resObject = (key = null, message = null, msgCode = null) => { + if (key === null || typeof key !== 'string' || key.trim().length === 0) key = 'InternalError' + key = key.trim() + let resObj = _.cloneDeep(resMessage[key] || {}) + + if (msgCode !== null && isFinite(msgCode)) { + resObj.obj.msgCode = toInt(msgCode) + } + + if (typeof message === 'string' && message.trim().length > 0) { + resObj.obj.message = message.trim() + } else if (typeof message === 'object') { + resObj.obj = message + } + return resObj +} + +/** + * API Error cls + */ +class APIError extends Error { + set apiMsg (txt = '') { + if (typeof txt !== 'string') txt = null + this._apiMsg = txt + } + get apiMsg () { + return this._apiMsg + } + + set msgCode (code = 0) { + this._code = toInt(code, 0) + } + get msgCode () { + return this._code + } + + set resKey (txt = '') { + if (typeof txt !== 'string' || txt.trim().length === 0) txt = 'InternalError' + this._resKey = txt + } + get resKey () { + return this._resKey + } +} + +const genError = (key = null, message = null, msgCode = null) => { + let cls = new APIError() + cls.apiMsg = message + cls.msgCode = msgCode + cls.resKey = key + return cls +} + +const checkSession = async (c, n) => { + if (!('session' in c) || !('user' in c.session) || !('loginType' in c.session)) throw genError('NotLogin') + + return n() } module.exports = { chkObject, - resObject + resObject, + toInt, + APIError, + genError, + checkSession } diff --git a/libs/route-utils/res-message/index.js b/libs/route-utils/res-message/index.js index 427a6f8..32a0004 100644 --- a/libs/route-utils/res-message/index.js +++ b/libs/route-utils/res-message/index.js @@ -22,6 +22,13 @@ module.exports = { message: 'login first' } }, + Forbidden: { + status: 403, + obj: { + status: 403, + message: 'Forbidden' + } + }, NotFound: { status: 404, obj: { diff --git a/libs/tools/index.js b/libs/tools/index.js new file mode 100644 index 0000000..06fe10d --- /dev/null +++ b/libs/tools/index.js @@ -0,0 +1,64 @@ +const config = require('@config/index') +const bcrypt = require('bcrypt') +const { + toInt +} = require('@libs/route-utils') + +/** + * hash password func + * @param {string} pass password string + * @return {string} + */ +const hashPassword = async (pass = '') => { + if (typeof pass !== 'string' || pass.trim().length === 0) return null + let saltRound = toInt(config.salt_round, 1, 1) + // gen salt + let salt = await new Promise((resolve, reject) => { + bcrypt.genSalt(saltRound, (err, saltStr) => { + if (err) { + reject(err) + return + } + resolve(saltStr) + }) + }) + + let passHash = new Promise((resolve, reject) => { + bcrypt.hash(pass, salt, (err, hash) => { + if (err) { + reject(err) + return + } + resolve(hash) + }) + }) + + return passHash +} + +/** + * compare password and hash + * @param {string} password password string + * @param {string} hash hash string + * @return {boolean} + */ +const comparePassword = async (password = '', hash = '') => { + if (typeof password !== 'string' || password.trim().length === 0) return null + if (typeof hash !== 'string' || hash.trim().length === 0) return null + let check = new Promise((resolve, reject) => { + bcrypt.compare(password, hash, (err, res) => { + if (err) { + reject(err) + return + } + resolve(res) + }) + }) + + return !!check +} + +module.exports = { + hashPassword, + comparePassword +} diff --git a/package.json b/package.json index 9e5dac5..c22a607 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "license": "ISC", "dependencies": { "axios": "^0.18.0", + "bcrypt": "^3.0.0", "cheerio": "^1.0.0-rc.2", "cron": "^1.3.0", "dotenv": "^6.0.0", diff --git a/route/api/index.js b/route/api/index.js index e730962..2f18773 100644 --- a/route/api/index.js +++ b/route/api/index.js @@ -1,10 +1,16 @@ const Router = require('koa-router') const koaBody = require('koa-body') const { - chkObject + chkObject, + resObject, + APIError, + genError } = require('@libs/route-utils') const DB = require('@libs/database') const r = new Router() +const { + comparePassword +} = require('@libs/tools') r.use(async (c, n) => { c.obj = {} @@ -14,12 +20,28 @@ r.use(async (c, n) => { await n() } catch (err) { console.log(err) + c.obj = resObject(err instanceof APIError ? err.resKey : 'InternalError', err.apiMsg || null, err.msgCode || null) } c.db.release() }) r.post('/login', koaBody(), async (c, n) => { - if (!c.chkBody('account', 'string') || !c.chkBody('password', 'string')) throw new Error('DataFormat') + if (!c.chkBody('account', 'string') || !c.chkBody('password', 'string')) throw genError('DataFormat') + + let text = `select * from "public"."account" where "account" = $1 limit 1` + let values = [c.request.body.account] + let userAcc = await c.db.query({text, values}) + if (userAcc.rowCount === 0) throw genError('NotFound', 'user not found') + if (!comparePassword(c.request.body.password, userAcc.rows[0].password)) throw genError('DataFormat', 'account or password error') + + let user = userAcc.rows[0] + delete user.password + c.session.user = user + c.session.loginType = 'system' + + c.obj = resObject('Success') }) +r.use('/twitch', require('./twitch').routes()) + module.exports = r diff --git a/route/api/twitch/index.js b/route/api/twitch/index.js new file mode 100644 index 0000000..54722fc --- /dev/null +++ b/route/api/twitch/index.js @@ -0,0 +1,41 @@ +const Router = require('koa-router') +const { + genError, + checkSession, + resObject +} = require('@libs/route-utils') +const r = new Router() + +const typeTwitch = async (c, n) => { + let text = `select * from "public"."twitch_channel" where "id" = $1` + let values = [c.session.user.id] + let result = await c.db.query({text, values}) + c.state.channelList = result.rows + return n() +} + +const typeSystem = async (c, n) => { + let text = `select * form "public"."twitch_channel"` + let values = [c.session.user.id] + let result = await c.db.query({text, values}) + c.state.channelList = result.rows + return n() +} + +r.get('/channels', checkSession, async (c, n) => { + if (c.session.loginType === 'twitch') { + return typeTwitch(c, n) + } else if (c.session.loginType === 'system') { + return typeSystem(c, n) + } + + throw genError('Forbidden') +}, async (c, n) => { + if (!('channelList' in c.state) || !Array.isArray(c.state.channelList)) throw genError('InternalError') + + c.obj = resObject('Success', { + list: c.state.channelList + }) +}) + +module.exports = r