this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 171 lines 4.5 kB view raw
1import { Hono } from "hono"; 2import { getCookie, setCookie, deleteCookie } from "hono/cookie"; 3import { html } from "hono/html"; 4import { 5 getOAuthClient, 6 getClientMetadata, 7 getJwks, 8 deleteSession, 9} from "../lib/oauth"; 10import { layout } from "../views/layouts/main"; 11import { csrfField } from "../lib/csrf"; 12import type { AppVariables } from "../types"; 13 14export const authRoutes = new Hono<{ Variables: AppVariables }>(); 15 16// Client metadata endpoint (required for OAuth) 17authRoutes.get("/client-metadata.json", async (c) => { 18 try { 19 const metadata = await getClientMetadata(); 20 return c.json(metadata); 21 } catch (error) { 22 console.error("Error getting client metadata:", error); 23 return c.json({ error: "Failed to get client metadata" }, 500); 24 } 25}); 26 27// JWKS endpoint (required for confidential clients) 28authRoutes.get("/jwks.json", async (c) => { 29 try { 30 const jwks = await getJwks(); 31 return c.json(jwks); 32 } catch (error) { 33 console.error("Error getting JWKS:", error); 34 return c.json({ error: "Failed to get JWKS" }, 500); 35 } 36}); 37 38// Login page 39authRoutes.get("/login", async (c) => { 40 const error = c.req.query("error"); 41 const csrfToken = c.get("csrfToken") as string; 42 43 const content = html` 44 <div class="auth-form"> 45 <h1>Login with Bluesky</h1> 46 47 ${ 48 error 49 ? html` 50 <div class="error-message"> 51 ${ 52 error === "handle_required" 53 ? "Please enter your handle or DID." 54 : error === "authorization_failed" 55 ? "Authorization failed. Please try again." 56 : error === "callback_failed" 57 ? "Login failed. Please try again." 58 : "An error occurred. Please try again." 59 } 60 </div> 61 ` 62 : "" 63 } 64 65 <form action="/auth/login" method="POST"> 66 ${csrfField(csrfToken)} 67 <div class="form-group"> 68 <label for="handle">Handle or DID</label> 69 <input 70 type="text" 71 id="handle" 72 name="handle" 73 placeholder="yourname.bsky.social" 74 required 75 autocomplete="username" 76 autocapitalize="none" 77 /> 78 <small 79 >Enter your Bluesky handle (e.g., yourname.bsky.social) or 80 DID</small 81 > 82 </div> 83 <button type="submit" class="btn btn-primary">Login</button> 84 </form> 85 </div> 86 `; 87 88 return c.html(layout(content, { title: "Login - sitebase" })); 89}); 90 91// Handle login form submission 92authRoutes.post("/login", async (c) => { 93 const body = await c.req.parseBody(); 94 let handle = body.handle as string; 95 96 if (!handle) { 97 return c.redirect("/auth/login?error=handle_required"); 98 } 99 100 // Trim and normalize handle 101 handle = handle.trim().toLowerCase(); 102 103 // Remove @ prefix if present 104 if (handle.startsWith("@")) { 105 handle = handle.slice(1); 106 } 107 108 try { 109 const client = await getOAuthClient(); 110 const url = await client.authorize(handle, { 111 scope: "atproto transition:generic", 112 }); 113 114 return c.redirect(url.toString()); 115 } catch (error) { 116 console.error("Login error:", error); 117 return c.redirect("/auth/login?error=authorization_failed"); 118 } 119}); 120 121// OAuth callback 122authRoutes.get("/callback", async (c) => { 123 const url = new URL(c.req.url); 124 const params = url.searchParams; 125 126 // Check for error from authorization server 127 const error = params.get("error"); 128 if (error) { 129 console.error("OAuth error:", error, params.get("error_description")); 130 return c.redirect("/auth/login?error=callback_failed"); 131 } 132 133 try { 134 const client = await getOAuthClient(); 135 const { session } = await client.callback(params); 136 137 // Store the DID in a cookie for session management 138 // The actual OAuth session is stored in the database by the OAuth client 139 setCookie(c, "session", session.did, { 140 httpOnly: true, 141 secure: 142 process.env.NODE_ENV === "production" || 143 process.env.PUBLIC_URL?.startsWith("https"), 144 sameSite: "Lax", 145 maxAge: 60 * 60 * 24 * 7, // 7 days 146 path: "/", 147 }); 148 149 return c.redirect("/"); 150 } catch (error) { 151 console.error("Callback error:", error); 152 return c.redirect("/auth/login?error=callback_failed"); 153 } 154}); 155 156// Logout 157authRoutes.get("/logout", async (c) => { 158 const did = getCookie(c, "session"); 159 160 if (did) { 161 try { 162 // Delete the OAuth session from the database 163 await deleteSession(did); 164 } catch (error) { 165 console.error("Error deleting session:", error); 166 } 167 } 168 169 deleteCookie(c, "session", { path: "/" }); 170 return c.redirect("/"); 171});