diff --git a/apps/backend/src/app/api/v1/auth/oauth/callback/[provider_id]/route.tsx b/apps/backend/src/app/api/v1/auth/oauth/callback/[provider_id]/route.tsx index 5ccf06d83..49a0a4f85 100644 --- a/apps/backend/src/app/api/v1/auth/oauth/callback/[provider_id]/route.tsx +++ b/apps/backend/src/app/api/v1/auth/oauth/callback/[provider_id]/route.tsx @@ -20,7 +20,11 @@ const redirectOrThrowError = (error: KnownError, project: ProjectsCrud["Admin"][ throw error; } - redirect(`${errorRedirectUrl}?errorCode=${error.errorCode}&message=${error.message}&details=${error.details}`); + const url = new URL(errorRedirectUrl); + url.searchParams.set("errorCode", error.errorCode); + url.searchParams.set("message", error.message); + url.searchParams.set("details", error.details ? JSON.stringify(error.details) : JSON.stringify({})); + redirect(url.toString()); }; const handler = createSmartRouteHandler({ @@ -92,14 +96,24 @@ const handler = createSmartRouteHandler({ } const providerObj = await getProvider(provider); - const { userInfo, tokenSet } = await providerObj.getCallback({ - codeVerifier: innerCodeVerifier, - state: innerState, - callbackParams: { - ...query, - ...body, - }, - }); + let callbackResult: Awaited>; + try { + callbackResult = await providerObj.getCallback({ + codeVerifier: innerCodeVerifier, + state: innerState, + callbackParams: { + ...query, + ...body, + }, + }); + } catch (error) { + if (error instanceof KnownErrors['OAuthProviderAccessDenied']) { + redirectOrThrowError(error, project, errorRedirectUrl); + } + throw error; + } + + const { userInfo, tokenSet } = callbackResult; if (type === "link") { if (!projectUserId) { diff --git a/apps/backend/src/oauth/providers/base.tsx b/apps/backend/src/oauth/providers/base.tsx index 4fa4ea330..432519149 100644 --- a/apps/backend/src/oauth/providers/base.tsx +++ b/apps/backend/src/oauth/providers/base.tsx @@ -154,6 +154,9 @@ export abstract class OAuthBaseProvider { captureError("inner-oauth-callback", error); throw new KnownErrors.InvalidAuthorizationCode(); } + if (error?.error === 'access_denied') { + throw new KnownErrors.OAuthProviderAccessDenied(); + } throw new StackAssertionError(`Inner OAuth callback failed due to error: ${error}`, undefined, { cause: error }); } diff --git a/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/callback.test.ts b/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/callback.test.ts index 5e4c6e172..40d2c74be 100644 --- a/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/callback.test.ts +++ b/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/callback.test.ts @@ -128,7 +128,7 @@ it("should redirect to error callback url when inner callback has invalid author NiceResponse { "status": 307, "headers": Headers { - "location": "http://stack-test.localhost/some-callback-url/callback-error?errorCode=INVALID_AUTHORIZATION_CODE&message=The%20given%20authorization%20code%20is%20invalid.&details=undefined", + "location": "http://stack-test.localhost/some-callback-url/callback-error?errorCode=INVALID_AUTHORIZATION_CODE&message=The+given+authorization+code+is+invalid.&details=%7B%7D", "set-cookie": ' at path '/'>,