WIP! A BB-style forum, on the ATmosphere! We're still working... we'll be back soon when we have something to show off!
node typescript hono htmx atproto
at atb-52-css-token-extraction 136 lines 4.1 kB view raw
1import { getCookie, deleteCookie } from "hono/cookie"; 2import type { Context, Next } from "hono"; 3import { Agent } from "@atproto/api"; 4import type { AppContext } from "../lib/app-context.js"; 5import { restoreOAuthSession } from "../lib/session.js"; 6import type { AuthenticatedUser, Variables } from "../types.js"; 7 8/** 9 * Helper to restore OAuth session from cookie and create an Agent. 10 * 11 * Delegates to the shared `restoreOAuthSession` for cookie lookup and 12 * OAuth session restoration, then enriches the result with an Agent, 13 * handle, and PDS URL to produce an AuthenticatedUser. 14 * 15 * Returns null if session doesn't exist or is expired (expected). 16 * Throws on unexpected errors (network failures, etc.) that should bubble up. 17 */ 18async function restoreSession(ctx: AppContext, cookieToken: string): Promise<AuthenticatedUser | null> { 19 const result = await restoreOAuthSession(ctx, cookieToken); 20 if (!result) { 21 return null; 22 } 23 24 const { oauthSession, cookieSession } = result; 25 26 // Create Agent from OAuth session 27 // The library's OAuthSession implements the fetch handler with DPoP 28 const agent = new Agent(oauthSession); 29 30 // Get handle from cookie session (fetched during login callback) 31 // Fall back to DID if handle wasn't stored 32 const handle = cookieSession.handle || oauthSession.did; 33 34 const user: AuthenticatedUser = { 35 did: oauthSession.did, 36 handle, 37 pdsUrl: oauthSession.serverMetadata.issuer, // PDS URL from server metadata 38 agent, 39 }; 40 41 return user; 42} 43 44/** 45 * Require authentication middleware. 46 * 47 * Validates session cookie and attaches authenticated user to context. 48 * Returns 401 if session is missing or invalid. 49 * 50 * Usage: 51 * app.post('/api/posts', requireAuth(ctx), async (c) => { 52 * const user = c.get('user'); // Guaranteed to exist 53 * const agent = user.agent; // Pre-configured Agent with DPoP 54 * }); 55 */ 56export function requireAuth(ctx: AppContext) { 57 return async (c: Context<{ Variables: Variables }>, next: Next) => { 58 const sessionToken = getCookie(c, "atbb_session"); 59 60 if (!sessionToken) { 61 return c.json({ error: "Authentication required" }, 401); 62 } 63 64 try { 65 const user = await restoreSession(ctx, sessionToken); 66 67 if (!user) { 68 return c.json({ error: "Invalid or expired session" }, 401); 69 } 70 71 // Attach user to context 72 c.set("user", user); 73 74 await next(); 75 } catch (error) { 76 ctx.logger.error("Authentication middleware error", { 77 path: c.req.path, 78 error: error instanceof Error ? error.message : String(error), 79 }); 80 81 return c.json( 82 { 83 error: "Authentication failed. Please try again.", 84 }, 85 500 86 ); 87 } 88 }; 89} 90 91/** 92 * Optional authentication middleware. 93 * 94 * Validates session if present, but doesn't return 401 if missing. 95 * Useful for endpoints that work for both authenticated and unauthenticated users. 96 * 97 * Usage: 98 * app.get('/api/posts/:id', optionalAuth(ctx), async (c) => { 99 * const user = c.get('user'); // May be undefined 100 * if (user) { 101 * // Show edit buttons, etc. 102 * } 103 * }); 104 */ 105export function optionalAuth(ctx: AppContext) { 106 return async (c: Context<{ Variables: Variables }>, next: Next) => { 107 const sessionToken = getCookie(c, "atbb_session"); 108 109 if (!sessionToken) { 110 await next(); 111 return; 112 } 113 114 try { 115 const user = await restoreSession(ctx, sessionToken); 116 117 if (user) { 118 c.set("user", user); 119 } else { 120 // Session is invalid/expired - clean up the cookie 121 deleteCookie(c, "atbb_session"); 122 } 123 } catch (error) { 124 // restoreSession now throws on unexpected errors only 125 // Log the unexpected error but don't fail the request 126 ctx.logger.warn("Unexpected error during optional auth", { 127 path: c.req.path, 128 error: error instanceof Error ? error.message : String(error), 129 }); 130 // Clean up potentially corrupted cookie 131 deleteCookie(c, "atbb_session"); 132 } 133 134 await next(); 135 }; 136}