UNPKG

moleculer-iam

Version:

Centralized IAM module for moleculer. Including a certified OIDC provider and an Identity provider for user profile, credentials, and custom claims management. Custom claims could be defined/updated by declarative schema which contains claims validation a

154 lines 6.36 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.buildVerifyPhoneRoutes = void 0; const tslib_1 = require("tslib"); const _ = tslib_1.__importStar(require("lodash")); const moment_1 = tslib_1.__importDefault(require("moment")); const idp_1 = require("../../idp"); const error_1 = require("./error"); async function defaultSend({ logger, ...args }) { logger.warn("should implement op.app.verifyPhone.send option to send phone verification message", args); } function buildVerifyPhoneRoutes(builder, opts) { const options = _.defaultsDeep(opts.verifyPhone || {}, { timeoutSeconds: 180, send: defaultSend, }); builder.app.router // render .get("/verify_phone", ctx => { return ctx.op.render("verify_phone"); }) // validate phone number .post("/verify_phone/check_phone", async (ctx) => { // 'registered' means verifying already registered phone number const { registered = false, phone_number = "" } = ctx.request.body; const claims = { phone_number }; ctx.idp.validateEmailOrPhoneNumber(claims); // normalize phone number // assert user with the phone number const user = await ctx.idp.find({ claims: { phone_number: claims.phone_number || "" } }); if (registered && !user) { throw new idp_1.IAMErrors.IdentityNotExists(); } else if (!registered && user) { throw new idp_1.IAMErrors.IdentityAlreadyExists(); } // update session if (!ctx.op.sessionPublicState.verifyPhone || ctx.op.sessionPublicState.verifyPhone.phoneNumber !== claims.phone_number || ctx.op.sessionPublicState.verifyPhone.verified) { ctx.op.setSessionPublicState(prevState => ({ ...prevState, verifyPhone: { phoneNumber: claims.phone_number, registered, }, })); ctx.op.setSessionSecretState(prevState => ({ ...prevState, verifyPhone: undefined, })); } return ctx.op.end(); }) // send/resend verification code .post("/verify_phone/send", async (ctx) => { const { phone_number } = ctx.request.body; const claims = { phone_number }; await ctx.idp.validateEmailOrPhoneNumber(claims); // normalized phone number // check too much resend const publicState = ctx.op.sessionPublicState; if (publicState && publicState.verifyPhone) { const verifyState = publicState.verifyPhone; if (verifyState.phoneNumber === claims.phone_number && verifyState.expiresAt && moment_1.default().isBefore(verifyState.expiresAt)) { throw new error_1.ApplicationErrors.TooMuchVerificationCodeRequest(); } } // create and send code const expiresAt = moment_1.default().add(options.timeoutSeconds, "s").toISOString(); let secret = ""; for (let i = 0; i < 6; i++) secret += Math.floor((Math.random() * 10) % 10).toString(); // send sms via adapter props await options.send({ logger: builder.logger, language: ctx.locale.language, region: ctx.locale.region, phoneNumber: claims.phone_number, secret }); // store the secret ctx.op.setSessionSecretState(prev => ({ ...prev, verifyPhone: { secret, }, })); // store the state ctx.op.setSessionPublicState(prevState => ({ ...prevState, verifyPhone: { phoneNumber: claims.phone_number, expiresAt, }, // show secret on dev ...(builder.dev ? ({ dev: { ...prevState.dev, verifyPhoneSecret: secret, }, }) : {}) })); return ctx.op.end(); }) // verify .get("/verify_phone/verify", ctx => { if (!ctx.op.sessionPublicState.verifyPhone) { return ctx.op.redirect("/verify_phone"); } return ctx.op.render("verify_phone"); }) .post("/verify_phone/verify", async (ctx) => { const { phone_number, secret = "", callback = "" } = ctx.request.body; const claims = { phone_number }; await ctx.idp.validateEmailOrPhoneNumber(claims); // normalized phone number // check secret const publicState = ctx.op.sessionPublicState.verifyPhone || {}; const secretState = ctx.op.sessionSecretState.verifyPhone || {}; if (publicState.phoneNumber !== claims.phone_number || moment_1.default().isAfter(publicState.expiresAt) || secretState.secret !== secret) { throw new error_1.ApplicationErrors.InvalidVerificationCode(); } ctx.op.setSessionPublicState(prevState => ({ ...prevState, verifyPhone: { ...prevState.verifyPhone, verified: true, } })); // update verification state if registered phone number let identity; if (publicState.registered) { identity = await ctx.idp.findOrFail({ claims }); await identity.updateClaims({ phone_number_verified: true }); } // process callback switch (callback) { case "find_email": if (!identity) { identity = await ctx.idp.findOrFail({ claims }); } const identityClaims = await ctx.op.getPublicUserProps(identity); ctx.op.setSessionPublicState(prevState => ({ ...prevState, findEmail: { user: identityClaims, }, })); break; } return ctx.op.end(); }) // end .get("/verify_phone/end", ctx => { if (!ctx.op.sessionPublicState.verifyPhone || !ctx.op.sessionPublicState.verifyPhone.verified) { return ctx.op.redirect("/verify_phone"); } return ctx.op.render("verify_phone"); }); } exports.buildVerifyPhoneRoutes = buildVerifyPhoneRoutes; //# sourceMappingURL=verify_phone.js.map