11import z from "zod"
2- import { createEndpoint , createEndpointConfig , HeadersBuilder , statusCode } from "@aura-stack/router"
2+ import { createEndpoint , createEndpointConfig , HeadersBuilder } from "@aura-stack/router"
33import { createCSRF } from "@/secure.js"
44import { cacheControl } from "@/headers.js"
5- import { AuraResponse } from "@/response.js"
65import { getUserInfo } from "@/actions/callback/userinfo.js"
7- import { AuthError , ERROR_RESPONSE , isAuthError } from "@/errors.js"
86import { equals , isValidRelativePath , sanitizeURL } from "@/utils.js"
97import { createAccessToken } from "@/actions/callback/access-token.js"
10- import { OAuthAuthorizationErrorResponse , OAuthAuthorizationResponse } from "@/schemas.js"
118import { createSessionCookie , getCookie , expiredCookieAttributes } from "@/cookie.js"
9+ import { OAuthAuthorizationErrorResponse , OAuthAuthorizationResponse } from "@/schemas.js"
10+ import { AuthSecurityError , OAuthProtocolError } from "@/errors.js"
1211import type { JWTPayload } from "@/jose.js"
13- import type { AccessTokenError , AuthorizationError , AuthRuntimeConfig } from "@/@types/index.js"
12+ import type { AuthRuntimeConfig } from "@/@types/index.js"
1413
1514const callbackConfig = ( oauth : AuthRuntimeConfig [ "oauth" ] ) => {
1615 return createEndpointConfig ( "/callback/:oauth" , {
@@ -25,7 +24,7 @@ const callbackConfig = (oauth: AuthRuntimeConfig["oauth"]) => {
2524 const response = OAuthAuthorizationErrorResponse . safeParse ( ctx . searchParams )
2625 if ( response . success ) {
2726 const { error, error_description } = response . data
28- throw new AuthError ( error , error_description ?? "OAuth Authorization Error" )
27+ throw new OAuthProtocolError ( error , error_description ?? "OAuth Authorization Error" )
2928 }
3029 return ctx
3130 } ,
@@ -44,58 +43,43 @@ export const callbackAction = (oauth: AuthRuntimeConfig["oauth"]) => {
4443 searchParams : { code, state } ,
4544 context : { oauth : providers , cookies, jose } ,
4645 } = ctx
47- try {
48- const oauthConfig = providers [ oauth ]
49- const cookieState = getCookie ( request , cookies . state . name )
50- const cookieRedirectTo = getCookie ( request , cookies . redirect_to . name )
51- const cookieRedirectURI = getCookie ( request , cookies . redirect_uri . name )
52- const codeVerifier = getCookie ( request , cookies . code_verifier . name )
53-
54- if ( ! equals ( cookieState , state ) ) {
55- throw new AuthError ( ERROR_RESPONSE . ACCESS_TOKEN . INVALID_REQUEST , "Mismatching state" )
56- }
57-
58- const accessToken = await createAccessToken ( oauthConfig , cookieRedirectURI , code , codeVerifier )
59- const sanitized = sanitizeURL ( cookieRedirectTo )
60- if ( ! isValidRelativePath ( sanitized ) ) {
61- throw new AuthError (
62- ERROR_RESPONSE . ACCESS_TOKEN . INVALID_REQUEST ,
63- "Invalid redirect path. Potential open redirect attack detected."
64- )
65- }
6646
67- const userInfo = await getUserInfo ( oauthConfig , accessToken . access_token )
47+ const oauthConfig = providers [ oauth ]
48+ const cookieState = getCookie ( request , cookies . state . name )
49+ const cookieRedirectTo = getCookie ( request , cookies . redirect_to . name )
50+ const cookieRedirectURI = getCookie ( request , cookies . redirect_uri . name )
51+ const codeVerifier = getCookie ( request , cookies . code_verifier . name )
6852
69- const sessionCookie = await createSessionCookie ( userInfo as JWTPayload , jose )
70-
71- const csrfToken = await createCSRF ( jose )
53+ if ( ! equals ( cookieState , state ) ) {
54+ throw new AuthSecurityError (
55+ "MISMATCHING_STATE" ,
56+ "The provided state passed in the OAuth response does not match the stored state."
57+ )
58+ }
7259
73- const headers = new HeadersBuilder ( cacheControl )
74- . setHeader ( "Location" , sanitized )
75- . setCookie ( cookies . sessionToken . name , sessionCookie , cookies . sessionToken . attributes )
76- . setCookie ( cookies . csrfToken . name , csrfToken , cookies . csrfToken . attributes )
77- . setCookie ( cookies . state . name , "" , expiredCookieAttributes )
78- . setCookie ( cookies . redirect_uri . name , "" , expiredCookieAttributes )
79- . setCookie ( cookies . redirect_to . name , "" , expiredCookieAttributes )
80- . setCookie ( cookies . code_verifier . name , "" , expiredCookieAttributes )
81- . toHeaders ( )
82- return Response . json ( { oauth } , { status : 302 , headers : headers } )
83- } catch ( error ) {
84- if ( isAuthError ( error ) ) {
85- const { type, message } = error
86- return AuraResponse . json < AuthorizationError > (
87- { error : type as AuthorizationError [ "error" ] , error_description : message } ,
88- { status : statusCode . BAD_REQUEST }
89- )
90- }
91- return AuraResponse . json < AccessTokenError > (
92- {
93- error : ERROR_RESPONSE . ACCESS_TOKEN . INVALID_CLIENT as AccessTokenError [ "error" ] ,
94- error_description : "An unexpected error occurred" ,
95- } ,
96- { status : statusCode . INTERNAL_SERVER_ERROR }
60+ const accessToken = await createAccessToken ( oauthConfig , cookieRedirectURI , code , codeVerifier )
61+ const sanitized = sanitizeURL ( cookieRedirectTo )
62+ if ( ! isValidRelativePath ( sanitized ) ) {
63+ throw new AuthSecurityError (
64+ "POTENTIAL_OPEN_REDIRECT_ATTACK_DETECTED" ,
65+ "Invalid redirect path. Potential open redirect attack detected."
9766 )
9867 }
68+
69+ const userInfo = await getUserInfo ( oauthConfig , accessToken . access_token )
70+ const sessionCookie = await createSessionCookie ( userInfo as JWTPayload , jose )
71+ const csrfToken = await createCSRF ( jose )
72+
73+ const headers = new HeadersBuilder ( cacheControl )
74+ . setHeader ( "Location" , sanitized )
75+ . setCookie ( cookies . sessionToken . name , sessionCookie , cookies . sessionToken . attributes )
76+ . setCookie ( cookies . csrfToken . name , csrfToken , cookies . csrfToken . attributes )
77+ . setCookie ( cookies . state . name , "" , expiredCookieAttributes )
78+ . setCookie ( cookies . redirect_uri . name , "" , expiredCookieAttributes )
79+ . setCookie ( cookies . redirect_to . name , "" , expiredCookieAttributes )
80+ . setCookie ( cookies . code_verifier . name , "" , expiredCookieAttributes )
81+ . toHeaders ( )
82+ return Response . json ( { oauth } , { status : 302 , headers : headers } )
9983 } ,
10084 callbackConfig ( oauth )
10185 )
0 commit comments