Skip to content

Commit ee1d18d

Browse files
committed
chore: minor clean-up
1 parent 02d8610 commit ee1d18d

File tree

1 file changed

+64
-41
lines changed

1 file changed

+64
-41
lines changed

src/index.ts

Lines changed: 64 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ export interface TOTPGenerationOptions {
5252
* The secret used to generate the TOTP.
5353
* It should be Base32 encoded (Feel free to use: https://npm.im/thirty-two).
5454
*
55-
* @default random Base32 secret.
55+
* Defaults to a random Base32 secret.
56+
* @default random
5657
*/
5758
secret?: string
5859

@@ -119,15 +120,15 @@ export interface SendTOTPOptions {
119120

120121
/**
121122
* The sender email method.
122-
* @param options The send TOTP options.
123+
* @param options The SendTOTPOptions options.
123124
*/
124125
export interface SendTOTP {
125126
(options: SendTOTPOptions): Promise<void>
126127
}
127128

128129
/**
129130
* The validate email method.
130-
* This can be useful to ensure it's not a disposable email address.
131+
* Useful to ensure it's not a disposable email address.
131132
*
132133
* @param email The email address to validate.
133134
*/
@@ -247,7 +248,7 @@ export interface TOTPStrategyOptions {
247248

248249
/**
249250
* The verify method callback.
250-
* Returns the user for the email to be stored in the session.
251+
* Returns the email user to be stored in the session.
251252
*/
252253
export interface TOTPVerifyParams {
253254
/**
@@ -266,6 +267,21 @@ export interface TOTPVerifyParams {
266267
request: Request
267268
}
268269

270+
/**
271+
* The magic link parameters.
272+
*/
273+
interface MagicLinkParams {
274+
/**
275+
* The TOTP code.
276+
*/
277+
code: string
278+
279+
/**
280+
* The TOTP expiry date.
281+
*/
282+
expires: number
283+
}
284+
269285
/**
270286
* A store class that manages TOTP-related state in a cookie.
271287
* Handles email, TOTP session data, and error messages.
@@ -367,8 +383,9 @@ class TOTPStore {
367383

368384
/**
369385
* Commits the current store state to a cookie string.
370-
* @param maxAge - Optional maximum age of the cookie in seconds
371-
* @returns A string representation of the cookie with all current values
386+
*
387+
* @param maxAge - Optional maximum age of the cookie in seconds.
388+
* @returns A string representation of the cookie with its current values.
372389
*/
373390
commit(maxAge?: number): string {
374391
const params = new URLSearchParams()
@@ -400,13 +417,8 @@ class TOTPStore {
400417
}
401418

402419
/**
403-
* Interface for encrypted magic link parameters
420+
* The TOTP Strategy.
404421
*/
405-
interface MagicLinkParams {
406-
code: string
407-
expires: number
408-
}
409-
410422
export class TOTPStrategy<User> extends Strategy<User, TOTPVerifyParams> {
411423
public name = STRATEGY_NAME
412424

@@ -424,8 +436,8 @@ export class TOTPStrategy<User> extends Strategy<User, TOTPVerifyParams> {
424436
private _successRedirect: string
425437
private _failureRedirect: string
426438
private readonly _totpGenerationDefaults = {
427-
algorithm: 'SHA-256', // More secure than SHA1.
428-
charSet: 'abcdefghijklmnpqrstuvwxyzABCDEFGHIJKLMNPQRSTUVWXYZ123456789', // No O or 0
439+
algorithm: 'SHA-256',
440+
charSet: 'abcdefghijklmnpqrstuvwxyzABCDEFGHIJKLMNPQRSTUVWXYZ123456789', // Does not include O or 0.
429441
digits: 6,
430442
period: 60,
431443
maxAttempts: 3,
@@ -434,8 +446,8 @@ export class TOTPStrategy<User> extends Strategy<User, TOTPVerifyParams> {
434446
requiredEmail: ERRORS.REQUIRED_EMAIL,
435447
invalidEmail: ERRORS.INVALID_EMAIL,
436448
invalidTotp: ERRORS.INVALID_TOTP,
437-
rateLimitExceeded: ERRORS.RATE_LIMIT_EXCEEDED,
438449
expiredTotp: ERRORS.EXPIRED_TOTP,
450+
rateLimitExceeded: ERRORS.RATE_LIMIT_EXCEEDED,
439451
missingSessionEmail: ERRORS.MISSING_SESSION_EMAIL,
440452
missingSessionTotp: ERRORS.MISSING_SESSION_TOTP,
441453
}
@@ -465,38 +477,38 @@ export class TOTPStrategy<User> extends Strategy<User, TOTPVerifyParams> {
465477
}
466478
}
467479

468-
// Getter for emailSentRedirect
480+
/** Gets the email sent redirect URL. */
469481
get emailSentRedirect(): string {
470482
return this._emailSentRedirect
471483
}
472484

473-
// Setter for emailSentRedirect
485+
/** Sets the email sent redirect URL. */
474486
set emailSentRedirect(url: string) {
475487
if (!url) {
476488
throw new Error(ERRORS.REQUIRED_EMAIL_SENT_REDIRECT_URL)
477489
}
478490
this._emailSentRedirect = url
479491
}
480492

481-
// Getter for successRedirect
493+
/** Gets the success redirect URL. */
482494
get successRedirect(): string {
483495
return this._successRedirect
484496
}
485497

486-
// Setter for successRedirect
498+
/** Sets the success redirect URL. */
487499
set successRedirect(url: string) {
488500
if (!url) {
489501
throw new Error(ERRORS.REQUIRED_SUCCESS_REDIRECT_URL)
490502
}
491503
this._successRedirect = url
492504
}
493505

494-
// Getter for failureRedirect
506+
/** Gets the failure redirect URL. */
495507
get failureRedirect(): string {
496508
return this._failureRedirect
497509
}
498510

499-
// Setter for failureRedirect
511+
/** Sets the failure redirect URL. */
500512
set failureRedirect(url: string) {
501513
if (!url) {
502514
throw new Error(ERRORS.REQUIRED_FAILURE_REDIRECT_URL)
@@ -506,19 +518,16 @@ export class TOTPStrategy<User> extends Strategy<User, TOTPVerifyParams> {
506518

507519
/**
508520
* Authenticates a user using TOTP.
509-
*
510521
* If the user is already authenticated, simply returns the user.
511522
*
512523
* | Method | Email | Code | Sess. Email | Sess. TOTP | Action/Logic |
513524
* |--------|-------|------|-------------|------------|------------------------------------------|
514-
* | POST | ✓ | - | - | - | Generate/send TOTP using form email. |
515-
* | POST | ✗ | ✗ | ✓ | - | Generate/send TOTP using session email. |
525+
* | POST | ✓ | - | - | - | Generate/Send TOTP using form email. |
526+
* | POST | ✗ | ✗ | ✓ | - | Generate/Send TOTP using session email. |
516527
* | POST | ✗ | ✓ | ✓ | ✓ | Validate form TOTP code. |
517-
* | GET | - | - | ✓ | ✓ | Validate magic link TOTP. |
528+
* | GET | - | - | ✓ | ✓ | Validate magic-link TOTP. |
518529
*
519530
* @param {Request} request - The request object.
520-
* @param {SessionStorage} sessionStorage - The session storage instance.
521-
* @param {AuthenticateOptions} options - The authentication options. successRedirect is required.
522531
* @returns {Promise<User>} The authenticated user.
523532
*/
524533
async authenticate(request: Request): Promise<User> {
@@ -535,10 +544,16 @@ export class TOTPStrategy<User> extends Strategy<User, TOTPVerifyParams> {
535544
const formDataCode = coerceToOptionalNonEmptyString(formData.get(this.codeFieldKey))
536545
const sessionEmail = coerceToOptionalString(store.getEmail())
537546
const sessionTotp = coerceToOptionalTotpSessionData(store.getTOTP())
538-
const email =
539-
request.method === 'POST'
540-
? formDataEmail ?? (!formDataCode ? sessionEmail : null)
541-
: null
547+
548+
let email = null
549+
550+
if (request.method === 'POST') {
551+
if (formDataEmail) {
552+
email = formDataEmail
553+
} else if (sessionEmail && !formDataCode) {
554+
email = sessionEmail
555+
}
556+
}
542557

543558
try {
544559
if (email) {
@@ -554,24 +569,25 @@ export class TOTPStrategy<User> extends Strategy<User, TOTPVerifyParams> {
554569
request,
555570
})
556571

572+
// Set the TOTP data in the store.
557573
const totpData: TOTPCookieData = { jwe, attempts: 0 }
558574
store.setEmail(email)
559575
store.setTOTP(totpData)
560576
store.setError(undefined)
561577

578+
// Redirect to the email sent URL.
562579
throw redirect(this._emailSentRedirect, {
563580
headers: {
564581
'Set-Cookie': store.commit(this.maxAge),
565582
},
566583
})
567584
}
568585

569-
// Get the TOTP code and expiry, either from form data or magic link.
586+
// Try to get the TOTP code either from the form data or the magic link.
570587
const { code: linkCode, expires: linkExpires } = await this._getMagicLinkCode(
571588
request,
572589
sessionTotp,
573590
)
574-
575591
const code = formDataCode ?? linkCode
576592

577593
if (code) {
@@ -586,14 +602,18 @@ export class TOTPStrategy<User> extends Strategy<User, TOTPVerifyParams> {
586602
store.setTOTP(undefined)
587603
store.setError(undefined)
588604

605+
// Call the verify method, allowing developers to handle the user.
589606
await this.verify({ email: sessionEmail, formData, request })
590607

608+
// Redirect to the success URL.
591609
throw redirect(this._successRedirect, {
592610
headers: {
593611
'Set-Cookie': store.commit(this.maxAge),
594612
},
595613
})
596614
}
615+
616+
// If no email was provided, throw an error.
597617
throw new Error(this.customErrors.requiredEmail)
598618
} catch (err: unknown) {
599619
if (err instanceof Response) {
@@ -654,14 +674,15 @@ export class TOTPStrategy<User> extends Strategy<User, TOTPVerifyParams> {
654674
urlExpires?: number
655675
}) {
656676
try {
657-
// Check if the TOTP is expired from the URL
677+
// Check if the TOTP is expired from the URL.
658678
if (urlExpires) {
659679
const dateNow = Date.now()
660680
if (dateNow > urlExpires) {
661681
throw new Error(this.customErrors.expiredTotp)
662682
}
663683
}
664684

685+
// Decrypt the TOTP data from the Cookie.
665686
// https://github.com/panva/jose/blob/main/docs/jwe/compact/decrypt/functions/compactDecrypt.md
666687
const { plaintext } = await jose.compactDecrypt(
667688
sessionTotp.jwe,
@@ -673,6 +694,7 @@ export class TOTPStrategy<User> extends Strategy<User, TOTPVerifyParams> {
673694
// Check if the TOTP is expired from the Cookie.
674695
const dateNow = Date.now()
675696
const isExpired = dateNow - totpData.createdAt > this.totpGeneration.period * 1000
697+
676698
if (isExpired) {
677699
throw new Error(this.customErrors.expiredTotp)
678700
}
@@ -683,6 +705,7 @@ export class TOTPStrategy<User> extends Strategy<User, TOTPVerifyParams> {
683705
secret: totpData.secret,
684706
otp: code,
685707
})
708+
686709
if (!isValid) {
687710
throw new Error(this.customErrors.invalidTotp)
688711
}
@@ -737,9 +760,9 @@ export class TOTPStrategy<User> extends Strategy<User, TOTPVerifyParams> {
737760
}
738761

739762
/**
740-
* Encrypts magic link parameters
741-
* @param params - The parameters to encrypt
742-
* @returns The encrypted JWE token
763+
* Encrypts magic link parameters.
764+
* @param params - The parameters to encrypt.
765+
* @returns The encrypted JWE token.
743766
*/
744767
private async _encryptUrlParams(params: MagicLinkParams): Promise<string> {
745768
const payload = new TextEncoder().encode(JSON.stringify(params))
@@ -749,9 +772,9 @@ export class TOTPStrategy<User> extends Strategy<User, TOTPVerifyParams> {
749772
}
750773

751774
/**
752-
* Decrypts and validates magic link parameters
753-
* @param encrypted - The encrypted JWE token
754-
* @returns The decrypted and validated parameters
775+
* Decrypts and validates magic link parameters.
776+
* @param encrypted - The encrypted JWE token.
777+
* @returns The decrypted and validated parameters.
755778
*/
756779
private async _decryptUrlParams(
757780
encrypted: string,
@@ -762,7 +785,7 @@ export class TOTPStrategy<User> extends Strategy<User, TOTPVerifyParams> {
762785
const params = JSON.parse(new TextDecoder().decode(plaintext))
763786

764787
if (!params?.code || !params?.expires || typeof params.expires !== 'number') {
765-
throw new Error('Invalid magic link format')
788+
throw new Error('Invalid magic-link format.')
766789
}
767790

768791
return params

0 commit comments

Comments
 (0)