From 85616e3b6147584106dddbad09abee282062cf36 Mon Sep 17 00:00:00 2001 From: Andrew Judson Date: Tue, 2 Jul 2024 02:45:45 -0500 Subject: [PATCH] feat: add error for missing magic link (#67) --- docs/customization.md | 4 ++++ src/constants.ts | 1 + src/index.ts | 9 ++++++++- test/index.spec.ts | 35 +++++++++++++++++++++++++++++++++++ 4 files changed, 48 insertions(+), 1 deletion(-) diff --git a/docs/customization.md b/docs/customization.md index 75eef2c..2d6c064 100644 --- a/docs/customization.md +++ b/docs/customization.md @@ -111,6 +111,10 @@ export interface CustomErrorsOptions { * The expired TOTP error message. */ expiredTotp?: string + /** + * The missing session email error message. + */ + missingSessionEmail?: string } authenticator.use( diff --git a/src/constants.ts b/src/constants.ts index 0826fd6..88f45f8 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -16,6 +16,7 @@ export const ERRORS = { INVALID_EMAIL: 'Email is not valid.', INVALID_TOTP: 'Code is not valid.', EXPIRED_TOTP: 'Code has expired.', + MISSING_SESSION_EMAIL: 'Missing email to verify. Check that same browser used for verification.', // Miscellaneous errors. REQUIRED_ENV_SECRET: 'Missing required .env secret.', diff --git a/src/index.ts b/src/index.ts index fb4dd44..da9466c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -170,6 +170,11 @@ export interface CustomErrorsOptions { * The expired TOTP error message. */ expiredTotp?: string + + /** + * The missing session email error message. + */ + missingSessionEmail?: string } /** @@ -294,6 +299,7 @@ export class TOTPStrategy extends Strategy { invalidEmail: ERRORS.INVALID_EMAIL, invalidTotp: ERRORS.INVALID_TOTP, expiredTotp: ERRORS.EXPIRED_TOTP, + missingSessionEmail: ERRORS.MISSING_SESSION_EMAIL, } constructor( @@ -388,7 +394,8 @@ export class TOTPStrategy extends Strategy { const code = formDataCode ?? this._getMagicLinkCode(request) if (code) { - if (!sessionEmail || !sessionTotp) throw new Error(this.customErrors.expiredTotp) + if (!sessionEmail) throw new Error(this.customErrors.missingSessionEmail); + if (!sessionTotp) throw new Error(this.customErrors.expiredTotp) await this._validateTOTP({ code, sessionTotp, session, sessionStorage, options }) const user = await this.verify({ diff --git a/test/index.spec.ts b/test/index.spec.ts index 3834529..e59fa62 100644 --- a/test/index.spec.ts +++ b/test/index.spec.ts @@ -1051,10 +1051,45 @@ describe('[ TOTP ]', () => { }) }) + test('Should failure redirect on missing session email.', async () => { + let { session } = await setupGenerateSendTOTP() + session.unset(SESSION_KEYS.EMAIL) + const strategy = new TOTPStrategy(TOTP_STRATEGY_OPTIONS, verify) + const request = new Request('https://prodserver.com/magic-link?code=KJJERI', { + method: 'GET', + headers: { + cookie: await sessionStorage.commitSession(session), + }, + }) + await strategy + .authenticate(request, sessionStorage, { + ...AUTH_OPTIONS, + successRedirect: '/account', + failureRedirect: '/login', + }) + .catch(async (reason) => { + if (reason instanceof Response) { + expect(reason.status).toBe(302) + expect(reason.headers.get('location')).toBe(`/login`) + const session = await sessionStorage.getSession( + reason.headers.get('set-cookie') ?? '', + ) + expect(session.get(AUTH_OPTIONS.sessionErrorKey)).toEqual({ + message: ERRORS.MISSING_SESSION_EMAIL, + }) + } else throw reason + }) + }) + test('Should failure redirect on stale magic-link.', async () => { + let { session } = await setupGenerateSendTOTP() + session.unset(SESSION_KEYS.TOTP) const strategy = new TOTPStrategy(TOTP_STRATEGY_OPTIONS, verify) const request = new Request('https://prodserver.com/magic-link?code=KJJERI', { method: 'GET', + headers: { + cookie: await sessionStorage.commitSession(session), + }, }) await strategy .authenticate(request, sessionStorage, {