Skip to content

Commit 7e4965b

Browse files
Merge pull request #74 from datum-cloud/fix/handle-missing-oidc-auth-request
fix: handle missing OIDC auth request gracefully
2 parents a67b4fc + 1c33724 commit 7e4965b

2 files changed

Lines changed: 98 additions & 16 deletions

File tree

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Alert, AlertType } from "@/components/alert";
2+
3+
export default async function Page(props: {
4+
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
5+
}) {
6+
const searchParams = await props.searchParams;
7+
const title = searchParams.title ?? "Something went wrong";
8+
const message =
9+
searchParams.error ?? "An unexpected error occurred. Please try again.";
10+
11+
return (
12+
<>
13+
<h1 className="text-2xl font-semibold">{title}</h1>
14+
<div className="pt-6">
15+
<Alert type={AlertType.ALERT}>{message}</Alert>
16+
</div>
17+
</>
18+
);
19+
}

apps/login/src/app/login/route.ts

Lines changed: 79 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,44 @@ import { DEFAULT_CSP } from "../../../constants/csp";
3232
// NOTE: Route segment configs (dynamic, revalidate, fetchCache) removed for Next.js 15.6+ compatibility
3333
// With dynamicIO enabled, route handlers are dynamic by default
3434

35+
const gotoError = ({
36+
request,
37+
title,
38+
error,
39+
}: {
40+
request: NextRequest;
41+
title: string;
42+
error: string;
43+
}): NextResponse<unknown> => {
44+
const errorUrl = constructUrl(request, "/error");
45+
errorUrl.searchParams.set("title", title);
46+
errorUrl.searchParams.set("error", error);
47+
return NextResponse.redirect(errorUrl);
48+
};
49+
50+
const getAuthRequestErrorMessage = (error: unknown): string => {
51+
const code =
52+
error != null &&
53+
typeof error === "object" &&
54+
"code" in error &&
55+
typeof (error as { code: unknown }).code === "number"
56+
? (error as { code: number }).code
57+
: undefined;
58+
59+
switch (code) {
60+
case 5: // NOT_FOUND
61+
return "Your login session has expired. Please return to the application and try signing in again.";
62+
case 7: // PERMISSION_DENIED
63+
return "You don't have permission to access this login request. Please contact your administrator.";
64+
case 14: // UNAVAILABLE
65+
return "The authentication service is temporarily unavailable. Please try again in a few moments.";
66+
case 4: // DEADLINE_EXCEEDED
67+
return "The authentication service took too long to respond. Please try again.";
68+
default:
69+
return "Your login request could not be found or has expired. Please return to the application and try signing in again.";
70+
}
71+
};
72+
3573
const gotoAccounts = ({
3674
request,
3775
requestId,
@@ -134,10 +172,29 @@ export async function GET(request: NextRequest) {
134172

135173
// continue with OIDC
136174
if (requestId && requestId.startsWith("oidc_")) {
137-
const { authRequest } = await getAuthRequest({
138-
serviceUrl,
139-
authRequestId: requestId.replace("oidc_", ""),
140-
});
175+
let authRequest;
176+
try {
177+
({ authRequest } = await getAuthRequest({
178+
serviceUrl,
179+
authRequestId: requestId.replace("oidc_", ""),
180+
}));
181+
} catch (error) {
182+
console.error("Failed to get auth request:", error);
183+
return gotoError({
184+
request,
185+
title: "Login request expired",
186+
error: getAuthRequestErrorMessage(error),
187+
});
188+
}
189+
190+
if (!authRequest) {
191+
return gotoError({
192+
request,
193+
title: "Login request not found",
194+
error:
195+
"Your login request could not be found. Please return to the application and try signing in again.",
196+
});
197+
}
141198

142199
let organization = "";
143200
let suffix = "";
@@ -232,10 +289,12 @@ export async function GET(request: NextRequest) {
232289
});
233290

234291
if (!url) {
235-
return NextResponse.json(
236-
{ error: "Could not start IDP flow" },
237-
{ status: 500 },
238-
);
292+
return gotoError({
293+
request,
294+
title: "Sign-in unavailable",
295+
error:
296+
"We couldn't connect to your identity provider. Please try again or contact your administrator.",
297+
});
239298
}
240299

241300
if (url.startsWith("/")) {
@@ -471,10 +530,12 @@ export async function GET(request: NextRequest) {
471530
});
472531

473532
if (!samlRequest) {
474-
return NextResponse.json(
475-
{ error: "No samlRequest found" },
476-
{ status: 400 },
477-
);
533+
return gotoError({
534+
request,
535+
title: "Login request not found",
536+
error:
537+
"Your SAML login request could not be found or has expired. Please return to the application and try signing in again.",
538+
});
478539
}
479540

480541
let selectedSession = await findValidSession({
@@ -558,9 +619,11 @@ export async function GET(request: NextRequest) {
558619
}
559620
// Device Authorization does not need to start here as it is handled on the /device endpoint
560621
else {
561-
return NextResponse.json(
562-
{ error: "No authRequest nor samlRequest provided" },
563-
{ status: 500 },
564-
);
622+
return gotoError({
623+
request,
624+
title: "No login request",
625+
error:
626+
"No authentication request was provided. Please start the sign-in process from your application.",
627+
});
565628
}
566629
}

0 commit comments

Comments
 (0)