an app to share curated trails
sidetrail.app
1import { NextRequest, NextResponse } from "next/server";
2import { getOAuthClient, getSession } from "@/auth";
3
4function getBaseUrl(request: NextRequest): string {
5 const host =
6 request.headers.get("x-forwarded-host") ||
7 request.headers.get("host") ||
8 new URL(request.url).host;
9 const proto = request.headers.get("x-forwarded-proto") || "https";
10
11 return `${proto}://${host}`;
12}
13
14function isSameOrigin(url: string, baseUrl: string): boolean {
15 try {
16 return new URL(url, baseUrl).origin === new URL(baseUrl).origin;
17 } catch {
18 return false;
19 }
20}
21
22async function handleCallback(request: NextRequest) {
23 const baseUrl = getBaseUrl(request);
24 const params = new URLSearchParams(request.nextUrl.search);
25
26 // Check for OAuth error
27 const error = params.get("error");
28 if (error) {
29 const description = params.get("error_description") || error;
30 return NextResponse.redirect(
31 new URL(`/login?error=${encodeURIComponent(description)}`, baseUrl),
32 );
33 }
34
35 try {
36 const client = await getOAuthClient();
37
38 // Complete the OAuth flow
39 const { session: oauthSession, state } = await client.callback(params);
40
41 // Parse returnUrl from state
42 let returnUrl = "/";
43 if (state) {
44 try {
45 const parsed = JSON.parse(state);
46 if (
47 parsed.returnUrl &&
48 typeof parsed.returnUrl === "string" &&
49 isSameOrigin(parsed.returnUrl, baseUrl)
50 ) {
51 returnUrl = parsed.returnUrl;
52 }
53 } catch {
54 // Ignore parse errors
55 }
56 }
57
58 // Store the DID in our session cookie
59 const session = await getSession();
60
61 // If already signed in with different account, sign out old one
62 if (session.did && session.did !== oauthSession.did) {
63 try {
64 const oldSession = await client.restore(session.did);
65 if (oldSession) await oldSession.signOut();
66 } catch {
67 // Ignore errors signing out old session
68 }
69 }
70
71 session.did = oauthSession.did;
72 await session.save();
73
74 // Redirect to returnUrl: We have ensured the return URL is relative above:
75 const redirectUrl = new URL(returnUrl, baseUrl);
76 return NextResponse.redirect(redirectUrl);
77 } catch (err) {
78 console.error("OAuth callback error:", err);
79 const message = err instanceof Error ? err.message : "Authentication failed";
80 return NextResponse.redirect(new URL(`/login?error=${encodeURIComponent(message)}`, baseUrl));
81 }
82}
83
84export async function GET(request: NextRequest) {
85 return handleCallback(request);
86}