A CLI for publishing standard.site documents to ATProto
Something went wrong. Try again.
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152import { Hono } from "hono";import { createOAuthClient, OAUTH_SCOPE } from "../lib/oauth-client";import { getSessionDid, setSessionCookie, clearSessionCookie, getReturnToCookie, clearReturnToCookie,} from "../lib/session";
interface Env { SEQUOIA_SESSIONS: KVNamespace; CLIENT_URL: string;}
const auth = new Hono<{ Bindings: Env }>();
// OAuth client metadata endpointauth.get("/client-metadata.json", (c) => { const clientId = `${c.env.CLIENT_URL}/oauth/client-metadata.json`; const redirectUri = `${c.env.CLIENT_URL}/oauth/callback`;
return c.json({ client_id: clientId, client_name: "Sequoia", client_uri: c.env.CLIENT_URL, redirect_uris: [redirectUri], grant_types: ["authorization_code", "refresh_token"], response_types: ["code"], scope: OAUTH_SCOPE, token_endpoint_auth_method: "none", application_type: "web", dpop_bound_access_tokens: true, });});
// Start OAuth login flowauth.get("/login", async (c) => { try { const handle = c.req.query("handle"); if (!handle) { return c.redirect(`${c.env.CLIENT_URL}/?error=missing_handle`); }
const client = createOAuthClient(c.env.SEQUOIA_SESSIONS, c.env.CLIENT_URL); const authUrl = await client.authorize(handle, { scope: OAUTH_SCOPE, });
return c.redirect(authUrl.toString()); } catch (error) { console.error("Login error:", error); return c.redirect(`${c.env.CLIENT_URL}/?error=login_failed`); }});
// OAuth callback handlerauth.get("/callback", async (c) => { try { const params = new URLSearchParams(c.req.url.split("?")[1] || "");
if (params.get("error")) { const error = params.get("error"); console.error("OAuth error:", error, params.get("error_description")); return c.redirect( `${c.env.CLIENT_URL}/?error=${encodeURIComponent(error!)}`, ); }
const client = createOAuthClient(c.env.SEQUOIA_SESSIONS, c.env.CLIENT_URL); const { session } = await client.callback(params);
// Resolve handle from DID let handle: string | undefined; try { const identity = await client.identityResolver.resolve(session.did); handle = identity.handle; } catch { // Handle resolution is best-effort }
// Store handle in KV alongside the session for quick lookup if (handle) { await c.env.SEQUOIA_SESSIONS.put(`oauth_handle:${session.did}`, handle, { expirationTtl: 60 * 60 * 24 * 14, }); }
setSessionCookie(c, session.did, c.env.CLIENT_URL);
// If a subscribe flow set a return URL before initiating OAuth, honor it const returnTo = getReturnToCookie(c); clearReturnToCookie(c, c.env.CLIENT_URL);
return c.redirect(returnTo ?? `${c.env.CLIENT_URL}/`); } catch (error) { console.error("Callback error:", error); return c.redirect(`${c.env.CLIENT_URL}/?error=callback_failed`); }});
// Logout endpointauth.post("/logout", async (c) => { const did = getSessionDid(c);
if (did) { try { const client = createOAuthClient( c.env.SEQUOIA_SESSIONS, c.env.CLIENT_URL, ); await client.revoke(did); } catch (error) { console.error("Revoke error:", error); } await c.env.SEQUOIA_SESSIONS.delete(`oauth_handle:${did}`); }
clearSessionCookie(c, c.env.CLIENT_URL); return c.json({ success: true });});
// Check auth statusauth.get("/status", async (c) => { const did = getSessionDid(c);
if (!did) { return c.json({ authenticated: false }); }
try { const client = createOAuthClient(c.env.SEQUOIA_SESSIONS, c.env.CLIENT_URL); const session = await client.restore(did);
const handle = await c.env.SEQUOIA_SESSIONS.get( `oauth_handle:${session.did}`, );
return c.json({ authenticated: true, did: session.did, handle: handle || undefined, }); } catch (error) { console.error("Session restore failed:", error); clearSessionCookie(c, c.env.CLIENT_URL); return c.json({ authenticated: false }); }});
export default auth;