@@ -46,6 +46,25 @@ const OAUTH_STATE_COOKIE = 'oauth_state';
4646const DEFAULT_OAUTH_STATE_MAX_AGE = 10 * 60 * 1000 ; // 10 minutes
4747const DEFAULT_ERROR_REDIRECT_PATH = '/auth/error' ;
4848
49+ /**
50+ * Parse PostgreSQL interval string to milliseconds.
51+ * Supports: '10 minutes', '1 hour', '30 seconds', etc.
52+ */
53+ function parseIntervalToMs ( interval : string | null | undefined ) : number {
54+ if ( ! interval ) return DEFAULT_OAUTH_STATE_MAX_AGE ;
55+ const match = interval . match ( / ^ ( \d + ) \s * ( s e c o n d | m i n u t e | h o u r | d a y ) s ? $ / i) ;
56+ if ( ! match ) return DEFAULT_OAUTH_STATE_MAX_AGE ;
57+ const value = parseInt ( match [ 1 ] , 10 ) ;
58+ const unit = match [ 2 ] . toLowerCase ( ) ;
59+ const multipliers : Record < string , number > = {
60+ second : 1000 ,
61+ minute : 60 * 1000 ,
62+ hour : 60 * 60 * 1000 ,
63+ day : 24 * 60 * 60 * 1000 ,
64+ } ;
65+ return value * ( multipliers [ unit ] || 60 * 1000 ) ;
66+ }
67+
4968// =============================================================================
5069// Signed State Utilities
5170// =============================================================================
@@ -376,28 +395,32 @@ export function createOAuthRoutes(_opts: ConstructiveOptions): Router {
376395 }
377396
378397 const providerConfig = await getIdentityProvider ( ctx , modules , provider ) ;
398+ const { authSettings } = modules ;
399+ const errorRedirectPath =
400+ authSettings ?. oauthErrorRedirectPath || DEFAULT_ERROR_REDIRECT_PATH ;
401+
379402 if ( ! providerConfig ) {
380403 log . warn ( `[oauth] Provider ${ provider } not found or not configured` ) ;
381404 return redirectToError (
382405 res ,
383406 baseUrl ,
384- DEFAULT_ERROR_REDIRECT_PATH ,
407+ errorRedirectPath ,
385408 'PROVIDER_NOT_CONFIGURED' ,
386409 provider ,
387410 ) ;
388411 }
389412
390- const stateMaxAge = DEFAULT_OAUTH_STATE_MAX_AGE ;
413+ const stateMaxAge = parseIntervalToMs ( authSettings ?. oauthStateMaxAge ) ;
391414 const state = createSignedState (
392415 { redirect_uri : redirectUri , provider } ,
393416 stateMaxAge ,
394417 ) ;
395418
396419 res . cookie ( OAUTH_STATE_COOKIE , state , {
397- httpOnly : true ,
398- secure : isProduction ,
420+ httpOnly : authSettings ?. cookieHttponly ?? true ,
421+ secure : authSettings ?. cookieSecure ?? isProduction ,
399422 maxAge : stateMaxAge ,
400- sameSite : 'lax' ,
423+ sameSite : ( authSettings ?. cookieSamesite as 'lax' | 'strict' | 'none' ) ?? 'lax' ,
401424 } ) ;
402425
403426 const client = createOAuthClientForProvider ( providerConfig , baseUrl ) ;
@@ -485,8 +508,9 @@ export function createOAuthRoutes(_opts: ConstructiveOptions): Router {
485508 ) ;
486509 }
487510
511+ let modules : OAuthModules | null = null ;
488512 try {
489- const modules = await resolveOAuthModules ( ctx ) ;
513+ modules = await resolveOAuthModules ( ctx ) ;
490514 if ( ! modules ) {
491515 log . error (
492516 `[oauth] Required modules not provisioned for ${ provider } ` ,
@@ -500,6 +524,12 @@ export function createOAuthRoutes(_opts: ConstructiveOptions): Router {
500524 ) ;
501525 }
502526
527+ const { authSettings } = modules ;
528+ const errorRedirectPath =
529+ authSettings ?. oauthErrorRedirectPath || DEFAULT_ERROR_REDIRECT_PATH ;
530+ const requireVerifiedEmail =
531+ authSettings ?. oauthRequireVerifiedEmail ?? true ;
532+
503533 const providerConfig = await getIdentityProvider (
504534 ctx ,
505535 modules ,
@@ -510,7 +540,7 @@ export function createOAuthRoutes(_opts: ConstructiveOptions): Router {
510540 return redirectToError (
511541 res ,
512542 baseUrl ,
513- DEFAULT_ERROR_REDIRECT_PATH ,
543+ errorRedirectPath ,
514544 'PROVIDER_NOT_CONFIGURED' ,
515545 provider ,
516546 ) ;
@@ -591,7 +621,7 @@ export function createOAuthRoutes(_opts: ConstructiveOptions): Router {
591621 `[oauth] Account not found for ${ profile . email } , attempting signup` ,
592622 ) ;
593623
594- if ( ! emailVerified ) {
624+ if ( requireVerifiedEmail && ! emailVerified ) {
595625 log . warn (
596626 `[oauth] Rejecting unverified email for signup: ${ profile . email } ` ,
597627 ) ;
@@ -623,7 +653,7 @@ export function createOAuthRoutes(_opts: ConstructiveOptions): Router {
623653 return redirectToError (
624654 res ,
625655 baseUrl ,
626- DEFAULT_ERROR_REDIRECT_PATH ,
656+ errorRedirectPath ,
627657 'EMAIL_NOT_VERIFIED' ,
628658 provider ,
629659 ) ;
@@ -677,13 +707,10 @@ export function createOAuthRoutes(_opts: ConstructiveOptions): Router {
677707 }
678708 } catch ( error : any ) {
679709 log . error ( `[oauth] Callback failed for ${ provider } :` , error ) ;
680- redirectToError (
681- res ,
682- baseUrl ,
683- DEFAULT_ERROR_REDIRECT_PATH ,
684- 'CALLBACK_FAILED' ,
685- provider ,
686- ) ;
710+ const fallbackPath =
711+ modules ?. authSettings ?. oauthErrorRedirectPath ||
712+ DEFAULT_ERROR_REDIRECT_PATH ;
713+ redirectToError ( res , baseUrl , fallbackPath , 'CALLBACK_FAILED' , provider ) ;
687714 }
688715 } ,
689716 ) ;
0 commit comments