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 main 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}