an app to share curated trails
sidetrail.app
1"use server";
2
3import "server-only";
4import { redirect } from "next/navigation";
5import { getOAuthClient, getSession } from "./index";
6
7function isSameOrigin(url: string): boolean {
8 try {
9 // Use a dummy base - we only care that the URL doesn't escape to a different origin
10 const base = "https://self";
11 return new URL(url, base).origin === base;
12 } catch {
13 return false;
14 }
15}
16
17export async function login(formData: FormData) {
18 const rawHandle = formData.get("loginHint") as string;
19 let returnUrl = (formData.get("returnUrl") as string) || "/";
20 if (!isSameOrigin(returnUrl)) {
21 returnUrl = "/";
22 }
23
24 const handle = rawHandle?.trim().replace(/^@/, "");
25 if (!handle) {
26 redirect("/login?error=" + encodeURIComponent("Please enter your handle"));
27 }
28
29 let authorizationUrl: string;
30 try {
31 const client = await getOAuthClient();
32 authorizationUrl = (
33 await client.authorize(handle, {
34 scope: "atproto transition:generic",
35 state: JSON.stringify({ returnUrl }),
36 })
37 ).toString();
38 } catch (error) {
39 console.error("OAuth authorize error:", error);
40 const message = error instanceof Error ? error.message : "Unknown error";
41 redirect("/login?error=" + encodeURIComponent(`Login failed: ${message}`));
42 }
43 redirect(authorizationUrl);
44}
45
46export async function logout(returnUrl: string = "/") {
47 const session = await getSession();
48
49 if (session.did) {
50 try {
51 const client = await getOAuthClient();
52 const oauthSession = await client.restore(session.did);
53 if (oauthSession) {
54 await oauthSession.signOut();
55 }
56 } catch (err) {
57 console.error("Failed to sign out OAuth session:", err);
58 }
59 }
60
61 session.destroy();
62 redirect(returnUrl);
63}