Commit cb257484 authored by ml's avatar ml

资产管理后台(未完成)

parent 517e8b33
// 配置文件暂时和 js 的导出方式保持一致,涉及到线上环境变量的替换等
module.exports = {
BASE_URL: "/backend/v1/",
BASE_URL: "/backend/v1/",//Madex 管理后台
BASE_ABK_URL: "/abk/v2/",//资产管理后台
OSS_CONFIG: {
region: "oss-cn-hongkong",
accessKeyId: "LTAIKTww9T29bp9t",
......
// 配置文件暂时和 js 的导出方式保持一致,涉及到线上环境变量的替换等
module.exports = {
BASE_URL: "/backend/v1/"
BASE_URL: "/backend/v1/",//Madex 管理后台
BASE_ABK_URL: "/abk/v2/",//资产管理后台
OSS_CONFIG: {
region: "oss-cn-hongkong",
accessKeyId: "LTAIKTww9T29bp9t",
accessKeySecret: "6cbKA6dlkg2BIHovQV217izCrwJg8B",
endPoint: "https://oss-cn-hongkong.aliyuncs.com",
avatar_bucket: "biiigle-oss-bucket-oss",
linkcoin_host: "https://res.linkcoin.pro",
kyc_bucket: "bibox-oss",
bibox_host: "https://res.bibox.com"
}
}
\ No newline at end of file
......@@ -4,6 +4,7 @@ let logger = require("@madex/ex-js-public").logger;
import configSetting from "./config"
import { apiRouterV1 } from "./src/functional/router/v1/index"
import { apiRouterV2 } from "./src/functional/router/v2/index"
/* ======================================================================
* Config
......@@ -23,6 +24,7 @@ require("@madex/ex-js-common").apiExceedAlertUtils.check(app, { projectNO: 20, e
require("./src/setting/access-limit").filter(app);
require("./src/setting/login-filter").filter(app);
require("./src/setting/abk-login-filter").filter(app);
if (configSetting.node_env == "development") {
......@@ -36,7 +38,8 @@ else {
}
app.use(apiRouterV1);
app.use(configSetting.BASE_URL,apiRouterV1);
app.use(configSetting.BASE_ABK_URL,apiRouterV2);
......
export const AbkUserInfoConst = {
USER_STATUS: {
UN_ACTIVE: 0,//待激活
USE: 1,//在用
STOP: 2,//停用
DEL: 3,//删除
},
USER_TYPE: {
NORMAL: 0,//普通
BOSS: 1,//老板
},
}
......@@ -82,4 +82,5 @@ export const ErrorCode = {
PAIR_IS_NOT_ACTIVE:'30078',//交易对已是未激活状态
PAIR_IS_HIDE:'30079',//交易对已是隐藏状态
PAIR_IS_NOT_HIDE:'30080',//交易对已是未隐藏状态
PWD_FORMAT_ERR:'30081',//密码格式错误
}
......@@ -10,6 +10,8 @@
export const RedisVal = {
loginErrTimesKey: (userId: any) => `${userId}${RedisValInner.LOGIN_ERROR_TIMES_SUFFIX}`,
sessionListKey: (userId: any) => `${userId}${RedisValInner.SESSION_ID_LIST_SUFFIX}`,
abkLoginErrTimesKey: (userId: any) => `abk.${userId}${RedisValInner.LOGIN_ERROR_TIMES_SUFFIX}`,
abkSessionListKey: (userId: any) => `abk.${userId}${RedisValInner.SESSION_ID_LIST_SUFFIX}`,
}
/**
......
import * as abkUserService from "../../service/abkservice/abkUserInfo.service";
import { AbkUserInfoVO, AbkUserInfoPageVO } from "../../service/abkservice/abkUserInfo.service";
let { logger, Res3Utils, optionalUtils: Optional, apiAssertUtils: ApiAssert } = require('@madex/ex-js-public');
import { ErrorCode } from "../../../../constant/errorCode";
import { getCurrentUser, getCurrentUserId, } from "../../../utils/aclUserUtils";
let isIp = require('is-ip');
/**
* 获取当前登陆的用户信息
* @param req
* @param infoVO
*/
export const getInfo = async (req: any, aclUserInfoVO: AclUserInfoVO) => {
let func_name = "userOptCtrl.getInfo";
let cmd = req.path;
try {
let currentUserId = await getCurrentUserId(req.cookies.session_id);
let res = await userOptService.getInfo(currentUserId, req.cookies.session_id);
return Res3Utils.result(res);
}
catch (e) {
logger.error(`${func_name} error:${e}`);
return Res3Utils.getErrorResult(e);
}
};
/**
* 获取用户信息根据uid
* @param req
* @param infoVO
*/
export const getInfoByUserId = async (req: any, aclUserInfoVO: AclUserInfoVO) => {
let func_name = "userOptCtrl.getInfoByUserId";
let cmd = req.path;
try {
if (!aclUserInfoVO.user_id) {
throw ErrorCode.PARAM_MISS
}
let res = await userOptService.getInfoByUserId(aclUserInfoVO.user_id);
return Res3Utils.result(res);
}
catch (e) {
logger.error(`${func_name} error:${e}`);
return Res3Utils.getErrorResult(e);
}
};
/**
* 获取用户信息详情(这里主要包含密码、google等敏感信息)根据uid
* @param req
* @param infoVO
*/
export const getInfoDetailByUserId = async (req: any, aclUserInfoVO: AclUserInfoVO) => {
let func_name = "userOptCtrl.getInfoDetailByUserId";
let cmd = req.path;
try {
if (!aclUserInfoVO.user_id) {
throw ErrorCode.PARAM_MISS
}
let res = await userOptService.getInfoDetailByUserId(aclUserInfoVO.user_id);
return Res3Utils.result(res);
}
catch (e) {
logger.error(`${func_name} error:${e}`);
return Res3Utils.getErrorResult(e);
}
};
/**
* 登陆
* @param req
* @param infoVO
*/
export const login = async (req: any, abkUserInfoVO: AbkUserInfoVO, res: any) => {
let func_name = "userOptCtrl.login";
let cmd = req.path;
let result: any;
try {
let ip = isIp(req.ip) ? req.ip : '*.*.*.*';
ApiAssert.isNotEmpty(ErrorCode.PARAM_MISS, abkUserInfoVO.account);
ApiAssert.isNotEmpty(ErrorCode.PARAM_MISS, abkUserInfoVO.pwd);
ApiAssert.isNotEmpty(ErrorCode.PARAM_MISS, abkUserInfoVO.totp_code);
ApiAssert.isFalse(ErrorCode.ACCOUNT_OR_PWD_ERR, abkUserInfoVO.pwd.length < 8);
ApiAssert.isFalse(ErrorCode.ACCOUNT_OR_PWD_ERR, abkUserInfoVO.pwd.length > 12);
checkPwd(abkUserInfoVO.pwd);
result = await abkUserService.login(abkUserInfoVO.account, abkUserInfoVO.pwd, abkUserInfoVO.totp_code, ip || "");
}
catch (e) {
logger.error(`${func_name} error:${e}`);
return Res3Utils.getErrorResult(e);
}
if (result.result && result.sessionId) {
if (req.headers.origin) {
let iSecure = req.headers.origin.startsWith("https");
let options = {
expires: new Date(new Date().getTime() + 1000 * 60 * 60 * 8), // 15 days
httpOnly: true,
secure: iSecure
};
res.cookie('session_id', result.sessionId, options);
delete result.sessionId;
}
}
return Res3Utils.result(result);
};
/**
* 登出
* @param req
* @param aclUserInfoVO
* @param res
*/
export const logout = async (req: any, aclUserInfoVO: AclUserInfoVO) => {
let func_name = "userOptCtrl.logout";
let cmd = req.path;
try {
if (req.cookies.session_id) {
let currentUser = await getCurrentUser(req.cookies.session_id);
if (currentUser) {
await userOptService.deleteAllSessionByUserId(currentUser.userId);
}
}
return Res3Utils.result('ok');
}
catch (e) {
logger.error(`${func_name} error:${e}`);
return Res3Utils.getErrorResult(e);
}
};
/**
* 登录-二次验证
* @param req
* @param aclUserInfoVO
* @param res
*/
export const loginConfirm = async (req: any, aclUserInfoVO: AclUserInfoVO) => {
let func_name = "userOptCtrl.loginConfirm";
let cmd = req.path;
try {
ApiAssert.notNull(ErrorCode.PARAM_MISS, aclUserInfoVO.totpCode);
let currentUserId = await getCurrentUserId(req.cookies.session_id);
let res = await userOptService.loginConfirm(req.cookies.session_id, currentUserId, aclUserInfoVO.totpCode);
return Res3Utils.result(res);
}
catch (e) {
logger.error(`${func_name} error:${e}`);
return Res3Utils.getErrorResult(e);
}
};
/**
* 绑定谷歌-生成新的密钥
* @param req
* @param aclUserInfoVO
* @param res
*/
export const bindTotpAsk = async (req: any, aclUserInfoVO: AclUserInfoVO) => {
let func_name = "userOptCtrl.bindTotpAsk";
let cmd = req.path;
try {
let currentUserId = await getCurrentUserId(req.cookies.session_id);
let res = await userOptService.bindTotpAsk(req.cookies.session_id, currentUserId);
return Res3Utils.result(res);
}
catch (e) {
logger.error(`${func_name} error:${e}`);
return Res3Utils.getErrorResult(e);
}
};
/**
* 绑定谷歌-验证新密钥
* @param req
* @param aclUserInfoVO
* @param res
*/
export const bindTotpConfirm = async (req: any, aclUserInfoVO: AclUserInfoVO) => {
let func_name = "userOptCtrl.bindTotpConfirm";
let cmd = req.path;
try {
let currentUserId = await getCurrentUserId(req.cookies.session_id);
ApiAssert.notNull(ErrorCode.PARAM_MISS, aclUserInfoVO.totpCode);
let res = await userOptService.bindTotpConfirm(req.cookies.session_id, currentUserId, aclUserInfoVO.totpCode);
return Res3Utils.result(res);
}
catch (e) {
logger.error(`${func_name} error:${e}`);
return Res3Utils.getErrorResult(e);
}
};
function checkPwd(pwd: string) {
let reg = /^(?=.[0-9])(?=.[A-Z])(?=.[a-z])(?=.[!@#%^&*?]).{8,12}$/;
if (!reg.test(pwd)) {
throw ErrorCode.PWD_FORMAT_ERR;
}
}
import { madAdminOrmDB, abkUserInfo, aclUserInfo } from "@madex/ex-ts-dao";
import { getOneAclUserByAccount, getOneAclUserByUid } from "../../../../utils/aclUserUtils";
import { ErrorCode } from "../../../../constant/errorCode";
import { AbkUserInfoConst } from "../../../../constant/abkUserConstant";
import { CryptUtils } from "../../../../utils/crypt-utils";
import { RedisVal } from "../../../../constant/redis-val";
import * as aclRoleAuthService from "../aclRoleAuth.service";
import Config from "../../../../../config";
import { addOptLog } from "../userOptLog.service";
import * as aclUserService from "../aclUser.service";
let { logger, apiAssertUtils: ApiAssert, BigNumberUtils } = require('@madex/ex-js-public');
let { authCommon: AuthCommon, redisUtilsCommon: RedisClient, } = require('@madex/ex-js-common');
let _ = require('lodash');
export interface AbkUserInfoVO {
user_id?: number;
account?: string | any;
remark?: string | any;//备注
pwd?: string | any;
pwd_salt?: string;
user_status?: number;
user_type?: number;
totp_encrypt?: string;
allow_ips?: string;
createdAt?: Date | any;
updatedAt?: Date | any;
totp_code?: any
}
export interface AbkUserInfoPageVO extends AbkUserInfoVO {
page?: number,
size?: number
}
export async function login(account: any, pwd: any, totp_code: any, ip: string) {
let userInfo = await getOneAbkUserByAccount(account);
ApiAssert.isNotEmpty(ErrorCode.ACCOUNT_OR_PWD_ERR, userInfo);
ApiAssert.isFalse(ErrorCode.ACCOUNT_STOP, userInfo.user_status === AbkUserInfoConst.USER_STATUS.STOP);
ApiAssert.isFalse(ErrorCode.USER_IS_DEL, userInfo.user_status === AbkUserInfoConst.USER_STATUS.DEL);
await _checkPwd(userInfo, pwd);
let sessionId = CryptUtils.sessionId(userInfo.user_id);
RedisClient.rpush(RedisVal.sessionListKey(userInfo.user_id), sessionId);
let cookies = {
account: userInfo.account,
userId: userInfo.user_id,
//是否需要进行二次验证。0:不需要或者已通过验证;1:需要;
needConfirm: 0,
is_abk_user: 1,
allow_ips: userInfo.allow_ips,//ip白名单
totp_encrypt: ''
};
let hasTotp = 0
//如果绑定了谷歌则必须校验, 增加属性 needConfirm = 1
let totp_encrypt = userInfo.totp_encrypt;
if (totp_encrypt) {
cookies.needConfirm = 1;
cookies.totp_encrypt = totp_encrypt;
hasTotp = 1;
}
await RedisClient.writeSync(sessionId, cookies, Config.LOGIN_EXPIRED);
await _unlockPwd(userInfo.user_id);
return {
result: "success",
sessionId: sessionId,
//是否已绑定谷歌。0:未绑定;1:已绑定
hasTotp: hasTotp,
};
}
/**
* 根据account 查询 资产管理后台用户
* @param account
*/
export async function getOneAbkUserByAccount(account: string) {
let dbOne = await abkUserInfo.prototype.findOne({
where: {
account
},
raw: true
});
if (!dbOne) {
throw ErrorCode.USER_NOT_EXIST;
}
return dbOne;
}
/**
* 根据user_id 查询 资产管理后台用户
* @param account
*/
export async function getOneAbkUserByUserId(user_id: number) {
let dbOne = await abkUserInfo.prototype.findOne({
where: {
user_id
},
raw: true
});
if (!dbOne) {
throw ErrorCode.USER_NOT_EXIST;
}
return dbOne;
}
/**
* 校验密码
* @param userInfo
* @param pwd
*/
async function _checkPwd(userInfo: any, pwd: any) {
let pwdSuc = userInfo.pwd == pwd;
if (!pwdSuc) await _pwdError(userInfo.user_id);
}
async function _pwdError(userId: any) {
let key = RedisVal.abkLoginErrTimesKey(userId)
let errorCount = await RedisClient.getMemSync(key);
errorCount = BigNumberUtils.add(errorCount, 1);
await RedisClient.writeSync(key, errorCount);
if (errorCount && Number(errorCount) >= Config.LOGIN_ERROR_LIMIT) {
await updateAbkUserStatus(userId, AbkUserInfoConst.USER_STATUS.STOP);
throw ErrorCode.ACCOUNT_LOCK;
}
throw ErrorCode.ACCOUNT_OR_PWD_ERR;
}
async function _unlockPwd(userId: any) {
let key = RedisVal.abkLoginErrTimesKey(userId);
await RedisClient.delSync(key);
}
/**
* 更新用户状态
* @param user_id
* @param user_status
*/
export async function updateAbkUserStatus(user_id: number, user_status: number) {
try {
await abkUserInfo.prototype.update({
user_status: Number(user_status)
}, {
where: {
user_id: Number(user_id),
}
});
}
catch (e) {
logger.error('abkUserService.updateAbkUserStatus.error:' + e)
throw e;
}
}
export async function loginConfirm(sessionId: any, userId: any, totpCode: any) {
//判断是否在登录锁定中
let cookies = await RedisClient.getSync(sessionId)
ApiAssert.isTrue(ErrorCode.PARAM_MISS, Number(cookies.needConfirm) === 1);
let totp_encrypt = cookies.totp_encrypt;
//谷歌密钥验证
ApiAssert.isTrue(ErrorCode.UNBOUND_TOTP, totp_encrypt !== '');
await AuthCommon.totpCheckSync(totpCode, totp_encrypt)
//判断是否已经使用过
let latestVerifiedKey = "bastard.totp.used.user." + userId
let latestUsed = RedisClient.getSync(latestVerifiedKey)
ApiAssert.isFalse(ErrorCode.TOTP_CODE_USED, totpCode === latestUsed)
await RedisClient.writeSync(latestVerifiedKey, totpCode, 60 * 60)
//解除缓存中的锁定标识
cookies.needConfirm = 0
await RedisClient.writeSync(sessionId, cookies, Config.LOGIN_EXPIRED)
return "success";
}
export const checkAbkTotp = async function (user_id: number, totp_code: string) {
//获取谷歌密钥并验证
let dbUserInfo = await getOneAbkUserByUserId(user_id);
ApiAssert.isTrue(ErrorCode.UNBOUND_TOTP, dbUserInfo && dbUserInfo.totp_encrypt !== '');
await AuthCommon.totpCheckSync(totp_code, dbUserInfo.totp_encrypt)
//判断是否已经使用过
let latestVerifiedKey = "abk.totp.used.user." + user_id
let latestUsed = RedisClient.getSync(latestVerifiedKey)
ApiAssert.isFalse(ErrorCode.TOTP_CODE_USED, totp_code === latestUsed)
await RedisClient.writeSync(latestVerifiedKey, totp_code, 60 * 60)
}
......@@ -120,7 +120,7 @@ export async function login(account: any, pwd: any, ip: string) {
await _checkPwd(userInfo, pwd);
let sessionId = CryptUtils.sessionId(userInfo.b_user_id);
let sessionId = CryptUtils.sessionId(userInfo.user_id);
RedisClient.rpush(RedisVal.sessionListKey(userInfo.user_id), sessionId);
let { roleSet, authSet } = await aclRoleAuthService.getUserAcl(userInfo.user_id);
......
/**
* Madex 管理后台路由
*/
import Express from "express"
const {
......
/**
* 资产管理后台路由
*/
import Express from "express"
const {
Res3Utils,
logger,
} = require("@madex/ex-js-public");
const router = Express.Router();
import * as abkUserCtrl from "../../mvc/control/abkctrl/abkUserInfo.control";
import * as userAuthConfigCtrl from "../../mvc/control/userAuthConfig.control";
import Config from "../../../../config";
const postFunc = {
'abkUser/login': abkUserCtrl.login,
'abkUser/logout': abkUserCtrl.logout,
/*
'user/login/confirm': userOptCtrl.loginConfirm,
'user/bind/totp/ask': userOptCtrl.bindTotpAsk,
'user/bind/totp/confirm': userOptCtrl.bindTotpConfirm,
'user/auth/reset/totp': userAuthConfigCtrl.resetTotp,*/
};
router.post('/*', (req, res, next) => {
//去除前缀
let path = req.originalUrl.replace(Config.BASE_ABK_URL, "");
if (!path) {
return Res3Utils.responseError('4003', res);
}
if (!postFunc[path]) {
return Res3Utils.responseError('4003', res);
}
doJob(req, res, next, req.body, postFunc[path]);
});
const doJob = (req, res, next, param, func) => {
let _func_name_ = 'router.doJob';
func(req, param, res).then(data => {
res.json(data);
}).catch(err => { // 可以抛出错误码
logger.error(_func_name_, err);
return Res3Utils.responseError(err, res);
});
};
export const apiRouterV2 = router;
/**
* 资产管理后台登录过滤
*/
import Config from "../../config";
const {
Res3Utils,
logger: Logger,
} = require("@madex/ex-js-public");
import * as ReqUtils from "../utils/req-utils"
const CLASS_NAME = "login-filter";
const ExcludeApi = {
"user/login": 1,
"user/logout": 1,
"user/login/confirm": 1,
"mUser/fee/vip/level/list": 1,
"spotPair/getAllSubmitSuccess": 1,
"coinType/getAllSubmitSuccess": 1,
'acl/role/getAll': 1,
'position/allList': 1,
'department/allList': 1,
'operate/other/business/area/list': 1,
};
let filter = function (app: any) {
app.use(function (req, res, next) {
let path = req.originalUrl;
try {
// Madex 管理后台的接口 跳过
if (ExcludeApi[path] || path.startsWith(Config.BASE_URL)) {
next();
}
else {
let cookies = req.cookies
ReqUtils
.checkAbkCookie(cookies,req)
.then(() => next())
.catch(e => res.json(Res3Utils.getErrorResult(e)))
}
}
catch (e) {
Logger.error(`${CLASS_NAME} abk-login-filter error!! - ${e}`);
return res.json(Res3Utils.getErrorResult(e));
}
});
};
module.exports = {
filter: filter,
};
......@@ -7,7 +7,7 @@ const {
logger: Logger,
} = require("@madex/ex-js-public");
//madex 管理后台
let cmdWhiteList = {
//技术部-其他管理-国际化管理
'i18n/info/list': 1,
......@@ -233,13 +233,17 @@ let cmdWhiteList = {
'tech/other/reward/time/period/get': 1,
};
//资产管理后台
let abkCmdWhiteList = {
};
let filter = function (app: any) {
app.use(function (req, res, next) {
let path = ReqUtils.parsePath(req.originalUrl);
try {
//如果是非登录接口,可以直接跳过
if (!cmdWhiteList[path]) {
if (!cmdWhiteList[path] && !abkCmdWhiteList[path]) {
throw '3000'
}
next();
......
/**
* 登录过滤
* Madex 管理后台登录过滤
*/
import Config from "../../config";
const {
Res3Utils,
logger: Logger,
......@@ -32,9 +32,8 @@ let filter = function (app: any) {
app.use(function (req, res, next) {
let path = ReqUtils.parsePath(req.originalUrl);
try {
//如果是非登录接口,可以直接跳过
if (ExcludeApi[path] || path.indexOf("bastard") > 0) {
// Madex 资产管理后台的接口 跳过
if (ExcludeApi[path] || path.startsWith(Config.BASE_ABK_URL)) {
next();
}
else {
......
......@@ -63,6 +63,28 @@ export const checkCookie = async (cookies: any, isAdminExclude: any, path: any,
}
export const checkAbkCookie = async (cookies: any, req: any) => {
ApiAssert.isNotEmpty(ErrorCode.NOT_LOGIN, cookies);
ApiAssert.isNotEmpty(ErrorCode.NOT_LOGIN, cookies.session_id);
let sessionId = cookies.session_id;
let cookieData = await RedisUtils.getSync(sessionId);
ApiAssert.isNotEmpty(ErrorCode.NOT_LOGIN, cookieData);
ApiAssert.isNotEmpty(ErrorCode.NO_PERMISSION, cookieData.is_abk_user);
ApiAssert.isTrue(ErrorCode.NEED_INPUT_GOOGLE_CODE, Number(cookieData.needConfirm) === 0)
if (cookieData.allow_ips) {
let ips = cookieData.allow_ips.split(",");
if (!ips.includes("*.*.*.*") && !ips.includes("0.0.0.0") && !ips.includes(req.ip)) {
throw ErrorCode.IP_ADDR_LIMIT
}
}
return cookieData;
}
async function aclCheck(cookieData: any, path: any, req: any) {
path = toHump(path); //先转驼峰
......
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