diff --git a/waspc/data/Generator/templates/react-app/src/auth/pages/OAuthCallback.tsx b/waspc/data/Generator/templates/react-app/src/auth/pages/OAuthCallback.tsx index 25fbea3bed..5c697402f1 100644 --- a/waspc/data/Generator/templates/react-app/src/auth/pages/OAuthCallback.tsx +++ b/waspc/data/Generator/templates/react-app/src/auth/pages/OAuthCallback.tsx @@ -35,6 +35,15 @@ function useOAuthCallbackHandler() { async function handleCallback() { try { setIsLoading(true); + const query = new URLSearchParams(window.location.search); + + // If we got redirect with an error, display it to the user + // and don't continue with the login process. + if (query.get('error')) { + setError(query.get('error')); + return; + } + const code = window.location.hash.slice(1); const response = await exchangeOAuthCodeForToken({ code }); const sessionId = response.data.sessionId; diff --git a/waspc/data/Generator/templates/sdk/wasp/server/utils.ts b/waspc/data/Generator/templates/sdk/wasp/server/utils.ts index 35807dd6bb..a71e73fdc6 100644 --- a/waspc/data/Generator/templates/sdk/wasp/server/utils.ts +++ b/waspc/data/Generator/templates/sdk/wasp/server/utils.ts @@ -1,11 +1,6 @@ {{={= =}=}} -import crypto from 'crypto' import { Request, Response, NextFunction } from 'express' -import { readdir } from 'fs' -import { dirname } from 'path' -import { fileURLToPath } from 'url' - {=# isAuthEnabled =} import { type AuthUser } from 'wasp/auth' {=/ isAuthEnabled =} @@ -40,3 +35,10 @@ async (req: RequestWithExtraFields, res: Response, next: NextFunction) => { } export const sleep = (ms: number): Promise => new Promise((r) => setTimeout(r, ms)) + +export function redirect(res: Response, redirectUri: string) { + return res + .status(302) + .setHeader("Location", redirectUri) + .end(); +} diff --git a/waspc/data/Generator/templates/server/src/auth/providers/config/github.ts b/waspc/data/Generator/templates/server/src/auth/providers/config/github.ts index 872fb9ac1b..d1a73c825a 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/config/github.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/config/github.ts @@ -2,10 +2,9 @@ import { Router, Request as ExpressRequest } from "express"; import { GitHub, generateState } from "arctic"; -import { HttpError } from 'wasp/server'; -import { handleRejection } from "wasp/server/utils"; +import { handleRejection, redirect } from "wasp/server/utils"; import type { ProviderConfig } from "wasp/auth/providers/types"; -import { finishOAuthFlowAndGetRedirectUri } from "../oauth/user.js"; +import { finishOAuthFlowAndGetRedirectUri, handleOAuthErrorAndGetRedirectUri } from "../oauth/user.js"; import { getStateCookieName, getValueFromCookie, setValueInCookie } from "../oauth/cookies.js"; import { callbackPath, loginPath } from "../oauth/redirect.js"; import { ensureEnvVarsForProvider } from "../oauth/env.js"; @@ -51,9 +50,7 @@ const _waspConfig: ProviderConfig = { setValueInCookie(getStateCookieName(provider.id), state, res); const url = await github.createAuthorizationURL(state, config); - return res.status(302) - .setHeader("Location", url.toString()) - .end(); + return redirect(res, url.toString()); })); router.get(`/${callbackPath}`, handleRejection(async (req, res) => { @@ -69,16 +66,11 @@ const _waspConfig: ProviderConfig = { ); // Redirect to the client with the one time code - return res - .status(302) - .setHeader("Location", redirectUri) - .end(); - } catch (e) { - // TODO: handle different errors - console.error(e); - - // TODO: it makes sense to redirect to the client with the OAuth erorr! - throw new HttpError(500, "Something went wrong"); + return redirect(res, redirectUri); + } catch (e) { + const { redirectUri } = handleOAuthErrorAndGetRedirectUri(e); + // Redirect to the client with the error + return redirect(res, redirectUri); } })); diff --git a/waspc/data/Generator/templates/server/src/auth/providers/config/google.ts b/waspc/data/Generator/templates/server/src/auth/providers/config/google.ts index fa9d9b10e1..d96430ca3c 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/config/google.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/config/google.ts @@ -2,11 +2,10 @@ import { Router, Request as ExpressRequest } from "express"; import { Google, generateCodeVerifier, generateState } from "arctic"; -import { HttpError } from 'wasp/server'; -import { handleRejection } from "wasp/server/utils"; +import { handleRejection, redirect } from "wasp/server/utils"; import type { ProviderConfig } from "wasp/auth/providers/types"; import { callbackPath, loginPath, getRedirectUriForCallback } from "../oauth/redirect.js"; -import { finishOAuthFlowAndGetRedirectUri } from "../oauth/user.js"; +import { finishOAuthFlowAndGetRedirectUri, handleOAuthErrorAndGetRedirectUri } from "../oauth/user.js"; import { getCodeVerifierCookieName, getStateCookieName, getValueFromCookie, setValueInCookie } from "../oauth/cookies.js"; import { ensureEnvVarsForProvider } from "../oauth/env.js"; import { mergeDefaultAndUserConfig } from "../oauth/config.js"; @@ -59,9 +58,7 @@ const _waspConfig: ProviderConfig = { ); const url = await google.createAuthorizationURL(state, codeVerifier, config); - return res.status(302) - .setHeader("Location", url.toString()) - .end(); + return redirect(res, url.toString()); })); router.get(`/${callbackPath}`, handleRejection(async (req, res) => { @@ -76,16 +73,10 @@ const _waspConfig: ProviderConfig = { _waspUserSignupFields, ); - return res - .status(302) - .setHeader("Location", redirectUri) - .end(); + return redirect(res, redirectUri); } catch (e) { - // TODO: handle different errors - console.error(e); - - // TODO: it makes sense to redirect to the client with the OAuth erorr! - throw new HttpError(500, "Something went wrong"); + const { redirectUri } = handleOAuthErrorAndGetRedirectUri(e); + return redirect(res, redirectUri); } })); diff --git a/waspc/data/Generator/templates/server/src/auth/providers/oauth/redirect.ts b/waspc/data/Generator/templates/server/src/auth/providers/oauth/redirect.ts index 9d50568d9c..18487f595d 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/oauth/redirect.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/oauth/redirect.ts @@ -13,3 +13,7 @@ const clientOAuthCallbackPath = '{= clientOAuthCallbackPath =}' export function getRedirectUriForOneTimeCode(oneTimeCode: string) { return `${config.frontendUrl}${clientOAuthCallbackPath}#${oneTimeCode}`; } + +export function getRedirectUriForError(error: string) { + return `${config.frontendUrl}${clientOAuthCallbackPath}?error=${error}`; +} diff --git a/waspc/data/Generator/templates/server/src/auth/providers/oauth/user.ts b/waspc/data/Generator/templates/server/src/auth/providers/oauth/user.ts index 978727455d..89275f715e 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/oauth/user.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/oauth/user.ts @@ -9,9 +9,8 @@ import { } from 'wasp/auth/utils' import { type {= authEntityUpper =} } from 'wasp/entities' import { prisma } from 'wasp/server' -import { type ProviderConfig } from "wasp/auth/providers/types"; -import { type UserSignupFields } from 'wasp/auth/providers/types' -import { getRedirectUriForOneTimeCode } from './redirect' +import { type UserSignupFields, type ProviderConfig } from 'wasp/auth/providers/types' +import { getRedirectUriForOneTimeCode, getRedirectUriForError } from './redirect' import { tokenStore } from './oneTimeCode' export async function finishOAuthFlowAndGetRedirectUri( @@ -35,6 +34,13 @@ export async function finishOAuthFlowAndGetRedirectUri( }; } +export function handleOAuthErrorAndGetRedirectUri(error: unknown): { redirectUri: string } { + const errorMessage = (error as any).message ?? "An unknown error occurred while trying to log in with the OAuth provider."; + return { + redirectUri: getRedirectUriForError(errorMessage), + }; +} + // We need a user id to create the auth token, so we either find an existing user // or create a new one if none exists for this provider. async function getAuthIdFromProviderDetails(