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
156 lines • 6.31 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildVerifyEmailRoutes = 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.verifyEmail.send option to send email verification mail", args);
}
function buildVerifyEmailRoutes(builder, opts) {
const options = _.defaultsDeep(opts.verifyEmail || {}, {
timeoutSeconds: 180,
send: defaultSend,
});
builder.app.router
// render
.get("/verify_email", ctx => {
return ctx.op.render("verify_email");
})
// validate email
.post("/verify_email/check_email", async (ctx) => {
// 'registered' means verifying already registered email
const { registered = false, email = "" } = ctx.request.body;
const claims = { email };
ctx.idp.validateEmailOrPhoneNumber(claims); // normalize email
// assert user with the email
const user = await ctx.idp.find({ claims: { email: claims.email || "" } });
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.verifyEmail
|| ctx.op.sessionPublicState.verifyEmail.email !== claims.email
|| ctx.op.sessionPublicState.verifyEmail.verified) {
ctx.op.setSessionPublicState(prevState => ({
...prevState,
verifyEmail: {
email: claims.email,
registered,
},
}));
ctx.op.setSessionSecretState(prevState => ({
...prevState,
verifyEmail: undefined,
}));
}
return ctx.op.end();
})
// send/resend verification code
.post("/verify_email/send", async (ctx) => {
const { email } = ctx.request.body;
const claims = { email };
await ctx.idp.validateEmailOrPhoneNumber(claims); // normalized email
// check too much resend
const publicState = ctx.op.sessionPublicState;
if (publicState && publicState.verifyEmail) {
const verifyState = publicState.verifyEmail;
if (verifyState.email === claims.email && 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 email via adapter props
await options.send({ logger: builder.logger, language: ctx.locale.language, region: ctx.locale.region, email: claims.email, secret });
// store the secret
ctx.op.setSessionSecretState(prev => ({
...prev,
verifyEmail: {
secret,
},
}));
// store the state
ctx.op.setSessionPublicState(prevState => ({
...prevState,
verifyEmail: {
email: claims.email,
expiresAt,
},
// show secret on dev
...(builder.dev ? ({
dev: {
...prevState.dev,
verifyEmailSecret: secret,
},
}) : {})
}));
return ctx.op.end();
})
// verify
.get("/verify_email/verify", ctx => {
if (!ctx.op.sessionPublicState.verifyEmail) {
return ctx.op.redirect("/verify_email");
}
return ctx.op.render("verify_email");
})
.post("/verify_email/verify", async (ctx) => {
const { email, secret = "", callback = "" } = ctx.request.body;
const claims = { email };
await ctx.idp.validateEmailOrPhoneNumber(claims); // normalized email
// check secret
const publicState = ctx.op.sessionPublicState.verifyEmail || {};
const secretState = ctx.op.sessionSecretState.verifyEmail || {};
if (publicState.email !== claims.email || moment_1.default().isAfter(publicState.expiresAt) || secretState.secret !== secret) {
throw new error_1.ApplicationErrors.InvalidVerificationCode();
}
ctx.op.setSessionPublicState(prevState => ({
...prevState,
verifyEmail: {
...prevState.verifyEmail,
verified: true,
}
}));
// update verification state if registered email
let identity;
if (publicState.registered) {
identity = await ctx.idp.findOrFail({ claims });
await identity.updateClaims({ email_verified: true });
}
// process callback
switch (callback) {
case "reset_password":
if (!identity) {
identity = await ctx.idp.findOrFail({ claims });
}
const expiresAt = moment_1.default().add(options.timeoutSeconds, "s").toISOString();
const identityClaims = await ctx.op.getPublicUserProps(identity);
ctx.op.setSessionPublicState(prevState => ({
...prevState,
resetPassword: {
user: identityClaims,
expiresAt,
},
}));
break;
}
return ctx.op.end();
})
// end
.get("/verify_email/end", ctx => {
if (!ctx.op.sessionPublicState.verifyEmail || !ctx.op.sessionPublicState.verifyEmail.verified) {
return ctx.op.redirect("/verify_email");
}
return ctx.op.render("verify_email");
});
}
exports.buildVerifyEmailRoutes = buildVerifyEmailRoutes;
//# sourceMappingURL=verify_email.js.map