import { AuthenticationResponseJSON, RegistrationResponseJSON } from "@simplewebauthn/browser"; import { AbstractService, EndpointCreator, HttpMethod, Modules } from "../../../core"; import { getTokenHandler } from "../config"; import { AuthInterface } from "./auth.interface"; import { PasskeyInterface } from "./passkey.interface"; import { PasskeyAuthenticationOptionsInterface } from "./passkey-authentication-options.interface"; import { PasskeyRegistrationOptionsInterface } from "./passkey-registration-options"; import { TotpAuthenticatorInterface } from "./totp-authenticator.interface"; import { TotpSetupInterface } from "./totp-setup.interface"; import { TwoFactorChallengeInterface } from "./two-factor-challenge.interface"; import { TwoFactorStatusInterface } from "./two-factor-status.interface"; export class TwoFactorService extends AbstractService { // ============================================================ // Status // ============================================================ static async getStatus(): Promise { return this.callApi({ type: Modules.TwoFactorStatus, method: HttpMethod.GET, endpoint: new EndpointCreator({ endpoint: Modules.Auth, id: "two-factor", childEndpoint: "status", }).generate(), }); } static async enable(params: { id: string; preferredMethod: "totp" | "passkey" }): Promise { await this.callApi({ type: Modules.TwoFactorEnable, method: HttpMethod.POST, endpoint: new EndpointCreator({ endpoint: Modules.Auth, id: "two-factor", childEndpoint: "enable", }).generate(), input: params, }); } static async disable(params: { code: string }): Promise { await this.callApi({ type: Modules.Auth, method: HttpMethod.POST, endpoint: new EndpointCreator({ endpoint: Modules.Auth, id: "two-factor", childEndpoint: "disable", }).generate(), input: { code: params.code }, }); } // ============================================================ // TOTP // ============================================================ static async setupTotp(params: { id: string; name: string; accountName: string }): Promise { return this.callApi({ type: Modules.TotpSetup, method: HttpMethod.POST, endpoint: new EndpointCreator({ endpoint: Modules.Auth, id: "totp", childEndpoint: "setup", }).generate(), input: params, }); } static async verifyTotpSetup(params: { id: string; authenticatorId: string; code: string }): Promise { await this.callApi({ type: Modules.TotpVerify, method: HttpMethod.POST, endpoint: new EndpointCreator({ endpoint: Modules.Auth, id: "totp", childEndpoint: "verify-setup", }).generate(), input: params, }); } static async listTotpAuthenticators(): Promise { return this.callApi({ type: Modules.TotpAuthenticator, method: HttpMethod.GET, endpoint: new EndpointCreator({ endpoint: Modules.Auth, id: "totp", childEndpoint: "authenticators", }).generate(), }); } static async deleteTotpAuthenticator(params: { id: string }): Promise { await this.callApi({ type: Modules.Auth, method: HttpMethod.DELETE, endpoint: new EndpointCreator({ endpoint: Modules.Auth, id: "totp", childEndpoint: "authenticators", childId: params.id, }).generate(), }); } // ============================================================ // Passkeys // ============================================================ static async getPasskeyRegistrationOptions(params: { id: string; userName: string; userDisplayName?: string; }): Promise { return this.callApi({ type: Modules.PasskeyRegistrationOptions, method: HttpMethod.POST, endpoint: new EndpointCreator({ endpoint: Modules.Auth, id: "passkey", childEndpoint: "register", childId: "options", }).generate(), input: params, }); } static async verifyPasskeyRegistration(params: { id: string; pendingId: string; name: string; response: RegistrationResponseJSON; }): Promise { return this.callApi({ type: Modules.PasskeyRegistrationVerify, method: HttpMethod.POST, endpoint: new EndpointCreator({ endpoint: Modules.Auth, id: "passkey", childEndpoint: "register", childId: "verify", }).generate(), input: params, }); } static async listPasskeys(): Promise { return this.callApi({ type: Modules.Passkey, method: HttpMethod.GET, endpoint: new EndpointCreator({ endpoint: Modules.Auth, id: "passkeys", }).generate(), }); } static async deletePasskey(params: { id: string }): Promise { await this.callApi({ type: Modules.Auth, method: HttpMethod.DELETE, endpoint: new EndpointCreator({ endpoint: Modules.Auth, id: "passkeys", childEndpoint: params.id, }).generate(), }); } static async renamePasskey(params: { id: string; name: string }): Promise { return this.callApi({ type: Modules.PasskeyRename, method: HttpMethod.PATCH, endpoint: new EndpointCreator({ endpoint: Modules.Auth, id: "passkeys", childEndpoint: params.id, }).generate(), input: { id: params.id, name: params.name }, }); } // ============================================================ // Backup Codes // ============================================================ static async generateBackupCodes(): Promise { const response = await this.callApi<{ codes: string[] }>({ type: Modules.Auth, method: HttpMethod.POST, endpoint: new EndpointCreator({ endpoint: Modules.Auth, id: "backup-codes", childEndpoint: "generate", }).generate(), }); return response.codes; } static async getBackupCodesCount(): Promise { const response = await this.callApi<{ count: number }>({ type: Modules.Auth, method: HttpMethod.GET, endpoint: new EndpointCreator({ endpoint: Modules.Auth, id: "backup-codes", childEndpoint: "count", }).generate(), }); return response.count; } // ============================================================ // Login 2FA Verification // ============================================================ private static async handleSuccessfulAuth(auth: AuthInterface): Promise { const handler = getTokenHandler(); if (handler) { await handler.updateToken({ token: auth.token, refreshToken: auth.refreshToken, userId: auth.user.id, companyId: auth.user.company?.id, roles: auth.user.roles.map((role) => role.id), features: auth.user.company?.features?.map((feature) => feature.id) ?? [], modules: auth.user.modules.map((module) => ({ id: module.id, permissions: module.permissions, })), }); } return auth; } static async getChallenge(params: { id: string; pendingToken: string; method: "totp" | "passkey" | "backup"; }): Promise { return this.callApi({ type: Modules.TwoFactorChallenge, method: HttpMethod.POST, endpoint: new EndpointCreator({ endpoint: Modules.Auth, id: "two-factor", childEndpoint: "challenge", }).generate(), input: { id: params.id, method: params.method }, }); } static async verifyTotp(params: { id: string; pendingToken: string; code: string }): Promise { const auth = await this.callApi({ type: Modules.TotpVerifyLogin, // Request: { type: "totp-authenticators", attributes: { code } } responseType: Modules.Auth, // Response: Auth with user relationship method: HttpMethod.POST, endpoint: new EndpointCreator({ endpoint: Modules.Auth, id: "two-factor", childEndpoint: "verify", childId: "totp", }).generate(), input: { id: params.id, code: params.code }, token: params.pendingToken, }); return this.handleSuccessfulAuth(auth); } static async getPasskeyAuthOptions(params: { pendingToken: string }): Promise { return this.callApi({ type: Modules.PasskeyAuthenticationOptions, method: HttpMethod.POST, endpoint: new EndpointCreator({ endpoint: Modules.Auth, id: "two-factor", childEndpoint: "verify/passkey", childId: "options", }).generate(), token: params.pendingToken, }); } static async verifyPasskey(params: { id: string; pendingToken: string; pendingId: string; credential: AuthenticationResponseJSON; }): Promise { const auth = await this.callApi({ type: Modules.PasskeyVerifyLogin, // Request: passkey verification format responseType: Modules.Auth, // Response: Auth with user relationship method: HttpMethod.POST, endpoint: new EndpointCreator({ endpoint: Modules.Auth, id: "two-factor", childEndpoint: "verify", childId: "passkey", }).generate(), input: { id: params.id, pendingId: params.pendingId, response: params.credential }, token: params.pendingToken, }); return this.handleSuccessfulAuth(auth); } static async verifyBackupCode(params: { id: string; pendingToken: string; code: string }): Promise { const auth = await this.callApi({ type: Modules.BackupCodeVerify, // Request: backup code verification format responseType: Modules.Auth, // Response: Auth with user relationship method: HttpMethod.POST, endpoint: new EndpointCreator({ endpoint: Modules.Auth, id: "two-factor", childEndpoint: "verify", childId: "backup", }).generate(), input: { id: params.id, code: params.code }, token: params.pendingToken, }); return this.handleSuccessfulAuth(auth); } }