atproto user agency toolkit for individuals and groups
at main 116 lines 2.5 kB view raw
1import type { Context, Next } from "hono"; 2import { verifyServiceJwt } from "../service-auth.js"; 3import { verifyAccessToken, TokenExpiredError } from "../session.js"; 4import type { Config } from "../config.js"; 5 6export interface AuthInfo { 7 did: string; 8 scope: string; 9} 10 11export type AuthVariables = { 12 auth: AuthInfo; 13}; 14 15export async function requireAuth( 16 c: Context<{ Bindings: Config; Variables: AuthVariables }>, 17 next: Next, 18): Promise<Response | void> { 19 const auth = c.req.header("Authorization"); 20 21 if (!auth) { 22 return c.json( 23 { 24 error: "AuthMissing", 25 message: "Authorization header required", 26 }, 27 401, 28 ); 29 } 30 31 // Handle Bearer tokens (session JWTs, static token, service JWTs) 32 if (!auth.startsWith("Bearer ")) { 33 return c.json( 34 { 35 error: "AuthMissing", 36 message: "Invalid authorization scheme", 37 }, 38 401, 39 ); 40 } 41 42 const token = auth.slice(7); 43 44 // Try static token first 45 if (token === c.env.AUTH_TOKEN) { 46 c.set("auth", { did: c.env.DID ?? "", scope: "com.atproto.access" }); 47 return next(); 48 } 49 50 // Legacy JWT auth requires PDS_HOSTNAME and JWT_SECRET 51 if (!c.env.PDS_HOSTNAME || !c.env.JWT_SECRET) { 52 return c.json({ error: "AuthenticationRequired", message: "Not authenticated" }, 401); 53 } 54 55 const serviceDid = `did:web:${c.env.PDS_HOSTNAME}`; 56 57 // Try session JWT verification (HS256, signed with JWT_SECRET) 58 try { 59 const payload = await verifyAccessToken( 60 token, 61 c.env.JWT_SECRET, 62 serviceDid, 63 ); 64 65 if (!payload.sub || payload.sub !== c.env.DID) { 66 return c.json( 67 { 68 error: "AuthenticationRequired", 69 message: "Invalid access token", 70 }, 71 401, 72 ); 73 } 74 75 c.set("auth", { did: payload.sub as string, scope: payload.scope as string }); 76 return next(); 77 } catch (err) { 78 if (err instanceof TokenExpiredError) { 79 return c.json( 80 { 81 error: "ExpiredToken", 82 message: err.message, 83 }, 84 400, 85 ); 86 } 87 // Session JWT verification failed, try service JWT 88 } 89 90 // Try service JWT verification (ES256K, signed with our signing key) 91 if (c.env.SIGNING_KEY && c.env.DID) { 92 try { 93 const payload = await verifyServiceJwt( 94 token, 95 c.env.SIGNING_KEY, 96 serviceDid, 97 c.env.DID, 98 ); 99 100 if (payload.iss === c.env.DID) { 101 c.set("auth", { did: payload.iss, scope: payload.lxm || "atproto" }); 102 return next(); 103 } 104 } catch { 105 // Service JWT verification also failed 106 } 107 } 108 109 return c.json( 110 { 111 error: "AuthenticationRequired", 112 message: "Invalid authentication token", 113 }, 114 401, 115 ); 116}