feat(core): support one time password (#9798)

This commit is contained in:
forehalo
2025-01-22 07:33:09 +00:00
parent bf797c7a0c
commit 5828eb53b6
16 changed files with 362 additions and 131 deletions

View File

@@ -14,7 +14,9 @@ import {
import type { Request, Response } from 'express';
import {
Cache,
Config,
CryptoHelper,
EarlyAccessRequired,
EmailTokenNotFound,
InternalServerError,
@@ -49,6 +51,8 @@ interface MagicLinkCredential {
token: string;
}
const OTP_CACHE_KEY = (otp: string) => `magic-link-otp:${otp}`;
@Throttle('strict')
@Controller('/api/auth')
export class AuthController {
@@ -57,7 +61,9 @@ export class AuthController {
private readonly auth: AuthService,
private readonly models: Models,
private readonly config: Config,
private readonly runtime: Runtime
private readonly runtime: Runtime,
private readonly cache: Cache,
private readonly crypto: CryptoHelper
) {
if (config.node.dev) {
// set DNS servers in dev mode
@@ -190,13 +196,20 @@ export class AuthController {
}
}
const ttlInSec = 30 * 60;
const token = await this.models.verificationToken.create(
TokenType.SignIn,
email
email,
ttlInSec
);
const otp = this.crypto.otp();
// TODO(@forehalo): this is a temporary solution, we should not rely on cache to store the otp
const cacheKey = OTP_CACHE_KEY(otp);
await this.cache.set(cacheKey, token, { ttl: ttlInSec * 1000 });
const magicLink = this.url.link(callbackUrl, {
token,
token: otp,
email,
...(redirectUrl
? {
@@ -205,7 +218,12 @@ export class AuthController {
: {}),
});
const result = await this.auth.sendSignInEmail(email, magicLink, !user);
const result = await this.auth.sendSignInEmail(
email,
magicLink,
otp,
!user
);
if (result.rejected.length) {
throw new InternalServerError('Failed to send sign-in email.');
@@ -247,9 +265,16 @@ export class AuthController {
validators.assertValidEmail(email);
const cacheKey = OTP_CACHE_KEY(token);
const cachedToken = await this.cache.get<string>(cacheKey);
if (!cachedToken) {
throw new InvalidEmailToken();
}
const tokenRecord = await this.models.verificationToken.verify(
TokenType.SignIn,
token,
cachedToken,
{
credential: email,
}

View File

@@ -322,9 +322,14 @@ export class AuthService implements OnApplicationBootstrap {
});
}
async sendSignInEmail(email: string, link: string, signUp: boolean) {
async sendSignInEmail(
email: string,
link: string,
otp: string,
signUp: boolean
) {
return signUp
? await this.mailer.sendSignUpMail(email, { url: link })
: await this.mailer.sendSignInMail(email, { url: link });
? await this.mailer.sendSignUpMail(email, { url: link, otp })
: await this.mailer.sendSignInMail(email, { url: link, otp });
}
}