UNPKG

will-core

Version:
325 lines (324 loc) 15 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TwoFactorHandler = void 0; const uuid_1 = require("uuid"); const will_db_1 = require("will-db"); const will_sql_1 = require("will-sql"); const will_api_1 = require("will-api"); const will_util_1 = require("will-util"); const SchemeHandler_1 = require("./SchemeHandler"); const VerifyError_1 = require("../models/VerifyError"); const node_2fa_1 = require("node-2fa"); class TwoFactorHandler extends SchemeHandler_1.SchemeHandler { constructor() { super(...arguments); this.model = { name: "tuserfactor", alias: { privateAlias: this.section } }; this.handlers = [{ name: "get" }, { name: "generate" }, { name: "verify" }, { name: "confirm" }]; } async generate(context) { return this.callFunctional(context, { operate: "generate", raw: false }, this.doGenerate); } async verify(context) { return this.callFunctional(context, { operate: "verify", raw: false }, this.doVerify); } async confirm(context) { return this.callFunctional(context, { operate: "verify", raw: false }, this.doConfirm); } createFactorInfo(data) { let email = (data?.email && data.email.trim().length > 0) ? data?.email : ""; let factorissuer = (data?.issuer && data.issuer.trim().length > 0) ? data?.issuer : "Smart2FA"; let secret = (0, node_2fa_1.generateSecret)({ name: factorissuer, account: email }); this.factorInfo = { factorconfig: false, factorfound: false, factorverify: data?.factorverify || false, issuer: factorissuer, userid: (data?.userid && data.userid.trim().length > 0) ? data?.userid : "", factorid: (data?.factorid && data.factorid.trim().length > 0) ? data?.factorid : (0, uuid_1.v4)(), factorflag: (data?.factorflag && data.factorflag.trim().length > 0) ? data?.factorflag : "0", factorkey: secret.secret, factorurl: secret.uri, factorremark: secret.qr, email: email, }; return this.factorInfo; } async genterateFactor(info) { let token = (0, node_2fa_1.generateToken)(info.factorkey); if (token) { return { valid: true, token: token.token }; } return { valid: false, token: "" }; } async verifyFactor(info, factorcode) { let result = (0, node_2fa_1.verifyToken)(info.factorkey, factorcode); if (result) { return { verified: true, delta: result.delta }; } return { verified: false, delta: -1 }; } composeFactorUri(info) { return "otpauth://totp/" + encodeURIComponent(info.name + ":" + info.account) + "?secret=" + encodeURIComponent(info.secret) + "&issuer=" + encodeURIComponent(info.name); } async doGenerate(context, model) { let info = await this.doGet(context, model); if (info && info.factorfound) { return this.genterateFactor(info); } return this.recordNotFound(); } async doGenerating(context, db, findByUser = false) { await this.validateRequireFields(context, this.model, "generate"); let info = await this.getFactorInfo(db, context.params.factorid, findByUser, context); if (info && info.factorfound) { return this.genterateFactor(info); } return { valid: false, token: "" }; } async doVerify(context, model) { let vi = this.validateParameters(context.params, "factorcode"); if (!vi.valid) { return Promise.reject(new VerifyError_1.VerifyError("Parameter not found (" + vi.info + ")", will_api_1.HTTP.NOT_ACCEPTABLE, -16061)); } let info = await this.doGet(context, model); if (info && info.factorfound) { return this.verifyFactor(info, context.params.factorcode); } return this.recordNotFound(); } async doVerifying(context, db, findByUser = false) { await this.validateRequireFields(context, this.model, will_db_1.Operation.VERIFY); let info = await this.getFactorInfo(db, context.params.factorid, findByUser, context); if (info && info.factorfound) { return this.verifyFactor(info, context.params.factorcode); } return { verified: false, delta: -1 }; } async doGet(context, model) { await this.validateRequireFields(context, model, will_db_1.Operation.GET); let db = this.getPrivateConnector(model); try { let info = await this.getFactorInfo(db, context.params.factorid, false, context); return await this.createCipherData(context, will_db_1.Operation.GET, info); } catch (ex) { this.logger.error(this.constructor.name, ex); return Promise.reject(this.getDBError(ex)); } finally { if (db) db.close(); } } async doConfirm(context, model) { await this.validateRequireFields(context, model, will_db_1.Operation.UPDATE); let db = this.getPrivateConnector(model); try { let rs = await this.doConfirming(context, db); return await this.createCipherData(context, will_db_1.Operation.UPDATE, rs); } catch (ex) { this.logger.error(this.constructor.name, ex); return Promise.reject(this.getDBError(ex)); } finally { if (db) db.close(); } } async doConfirming(context, db) { let userToken = await this.getUserTokenInfo(context, true); await this.updateUserFactor(db, userToken?.useruuid, context.params.factorcode, context); return await this.confirmFactor(db, context.params.factorid, userToken?.userid, context); } validateRequireFields(context, model, action) { let vi = this.validateParameters(context.params, "factorid"); if (!vi.valid) { return Promise.reject(new VerifyError_1.VerifyError("Parameter not found (" + vi.info + ")", will_api_1.HTTP.NOT_ACCEPTABLE, -16061)); } return Promise.resolve(vi); } async doRetrieve(context, model) { await this.validateRequireFields(context, model, will_db_1.Operation.RETRIEVE); let db = this.getPrivateConnector(model); try { let rs = await this.doRetrieving(context, db); return await this.createCipherData(context, will_db_1.Operation.RETRIEVE, rs); } catch (ex) { this.logger.error(this.constructor.name, ex); return Promise.reject(this.getDBError(ex)); } finally { if (db) db.close(); } } async doRetrieving(context, db) { return await this.performRetrieving(db, context.params.factorid, false, context); } async performRetrieving(db, id, findByUser = true, context) { if (!id || id.trim().length == 0) return this.createRecordSet(); let knsql = new will_sql_1.KnSQL(); knsql.append("select * from tuserfactor "); if (findByUser) { knsql.append("where userid=?userid "); knsql.set("userid", id); } else { knsql.append("where factorid=?factorid "); knsql.set("factorid", id); } this.logger.debug(this.constructor.name + ".performRetrieving", knsql); let rs = await knsql.executeQuery(db, context); return this.createRecordSet(rs); } async getFactorInfo(db, id, findByUser = true, context) { let cfg = await this.getConfigure(db, context); let rs = await this.performRetrieving(db, id, findByUser, context); if (rs && rs.rows.length > 0) { let row = rs.rows[0]; let factorurl = this.composeFactorUri({ name: row.issuer, account: row.email, secret: row.factorkey }); return { factorfound: true, factorconfig: cfg?.factorconfig || false, factorverify: cfg?.factorverify || false, issuer: row.issuer || cfg?.issuer, userid: row.userid, factorid: row.factorid, factorkey: row.factorkey, factorflag: row.factorflag, email: row.email, factorurl: factorurl, factorremark: row.factorremark }; } return { ...cfg, userid: "", email: "", factorfound: false, factorid: "", factorkey: "", factorflag: "" }; } async getConfigure(db, context) { let result = { factorconfig: false, factorverify: false, issuer: "" }; let knsql = new will_sql_1.KnSQL(); knsql.append("select * from tconfig where category='2FA' "); let rs = await knsql.executeQuery(db, context); if (rs && rs.rows.length > 0) { for (let row of rs.rows) { if (will_util_1.Utilities.equalsIgnoreCase("FACTORVERIFY", row.colname)) { result.factorverify = will_util_1.Utilities.equalsIgnoreCase("true", row.colvalue); result.factorconfig = true; } else if (will_util_1.Utilities.equalsIgnoreCase("FACTORISSUER", row.colname)) { result.issuer = row.colvalue; result.factorconfig = true; } } } return result; } async doCreate(context, model) { let vi = this.validateParameters(context.params, "userid", "email"); if (!vi.valid) { return Promise.reject(new VerifyError_1.VerifyError("Parameter not found (" + vi.info + ")", will_api_1.HTTP.NOT_ACCEPTABLE, -16061)); } await this.doCreating(context, model); return await this.createCipherData(context, will_db_1.Operation.CREATE, this.factorInfo); } async doClear(context, model) { await this.validateRequireFields(context, model, will_db_1.Operation.CLEAR); let rs = await this.doClearing(context, model); return await this.createCipherData(context, will_db_1.Operation.CLEAR, rs); } async doInsert(context, model) { return this.doCreate(context, model); } async doUpdate(context, model) { return this.doConfirm(context, model); } async doRemove(context, model) { return this.doClear(context, model); } async performCreating(context, model, db) { let info = await this.performCreateFactor(context, db, context.params); return { rows: [info], columns: null }; } async performCreateFactor(context, db, data) { let userToken = await this.getUserTokenInfo(context, true); let info = this.createFactorInfo(data); await this.createFactor(db, info, userToken?.userid); return info; } async performClearing(context, model, db) { return this.deleteFactor(db, context.params.factorid); } async createFactor(db, info, edituser, context) { let now = will_util_1.Utilities.now(); let knsql = new will_sql_1.KnSQL(); knsql.append("insert into tuserfactor(factorid,userid,factorkey,email,issuer,factorflag,factorurl,"); knsql.append("createdate,createtime,createtranstime,editdate,edittime,edituser,factorremark) "); knsql.append("values(?factorid,?userid,?factorkey,?email,?issuer,?factorflag,?factorurl,"); knsql.append("?createdate,?createtime,?createtranstime,?editdate,?edittime,?edituser,?factorremark) "); knsql.set("factorid", info.factorid); knsql.set("userid", info.userid); knsql.set("factorkey", info.factorkey); knsql.set("email", info.email); knsql.set("issuer", info.issuer); knsql.set("factorflag", info.factorflag); knsql.set("factorurl", info.factorurl); knsql.set("createdate", now, "DATE"); knsql.set("createtime", now, "TIME"); knsql.set("createtranstime", now.getTime()); knsql.set("editdate", now, "DATE"); knsql.set("edittime", now, "TIME"); knsql.set("edituser", edituser); knsql.set("factorremark", info.factorremark); let rs = await knsql.executeUpdate(db, context); return this.createRecordSet(rs); } async deleteFactor(db, factorid, context) { await this.moveToHistory(db, factorid, context); return this.removeFactor(db, factorid, context); } async removeFactor(db, factorid, context) { let knsql = new will_sql_1.KnSQL(); knsql.append("delete from tuserfactor "); knsql.append("where factorid = ?factorid "); knsql.set("factorid", factorid); let rs = await knsql.executeUpdate(db, context); return this.createRecordSet(rs); } async moveToHistory(db, factorid, context) { let knsql = new will_sql_1.KnSQL(); knsql.append("insert into tuserfactorhistory "); knsql.append("select * from tuserfactor "); knsql.append("where factorid = ?factorid "); knsql.set("factorid", factorid); let rs = await knsql.executeUpdate(db, context); return this.createRecordSet(rs); } async confirmFactor(db, factorid, edituser, context) { let now = will_util_1.Utilities.now(); let knsql = new will_sql_1.KnSQL(); knsql.append("update tuserfactor set factorflag = '1' "); knsql.append(", confirmdate=?confirmdate, confirmtime=?confirmtime "); knsql.append(", confirmtranstime=?confirmtranstime "); knsql.append(", editdate=?editdate, edittime=?edittime, edituser=?edituser "); knsql.append("where factorid = ?factorid "); knsql.set("factorid", factorid); knsql.set("confirmdate", now, "DATE"); knsql.set("confirmtime", now, "TIME"); knsql.set("confirmtranstime", now.getTime()); knsql.set("editdate", now, "DATE"); knsql.set("edittime", now, "TIME"); knsql.set("edituser", edituser); let rs = await knsql.executeUpdate(db, context); return this.createRecordSet(rs); } async updateUserFactor(db, useruuid, factorcode, context) { if (!useruuid || useruuid.trim().length == 0) return this.createRecordSet(); let knsql = new will_sql_1.KnSQL(); knsql.append("update tusertoken set factorcode = ?factorcode "); knsql.append("where useruuid = ?useruuid "); knsql.set("factorcode", factorcode); knsql.set("useruuid", useruuid); let rs = await knsql.executeUpdate(db, context); return this.createRecordSet(rs); } } exports.TwoFactorHandler = TwoFactorHandler;