Webhooks for the AT Protocol airglow.run
atproto atprotocol automation webhook
12
fork

Configure Feed

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

feat: improve auth and add signout

Hugo f8390e68 c3fc9eda

+56 -2
+5
app/components/Layout/Header/index.tsx
··· 21 21 Dashboard 22 22 </a> 23 23 <span class={s.userInfo}>@{user.handle}</span> 24 + <form method="post" action="/auth/signout"> 25 + <button type="submit" class={s.signOutButton}> 26 + Sign out 27 + </button> 28 + </form> 24 29 </> 25 30 )} 26 31 {actions}
+13
app/components/Layout/Header/styles.css.ts
··· 52 52 fontSize: fontSize.sm, 53 53 color: vars.color.textSecondary, 54 54 }); 55 + 56 + export const signOutButton = style({ 57 + background: "none", 58 + border: "none", 59 + cursor: "pointer", 60 + color: vars.color.textSecondary, 61 + fontSize: fontSize.sm, 62 + fontWeight: fontWeight.medium, 63 + padding: 0, 64 + ":hover": { 65 + color: vars.color.text, 66 + }, 67 + });
+2 -2
app/routes/auth/callback.tsx
··· 1 1 import { createRoute } from "honox/factory"; 2 2 import { setSignedCookie } from "hono/cookie"; 3 - import { getOAuthClient } from "@/auth/client.js"; 3 + import { getOAuthClient, resolveDidToHandle } from "@/auth/client.js"; 4 4 import { COOKIE_NAME } from "@/auth/middleware.js"; 5 5 import { config } from "@/config.js"; 6 6 import { db } from "@/db/index.js"; ··· 52 52 const { session, state } = await client.callback(params); 53 53 54 54 const did = session.did; 55 - const handle = state || did; 55 + const handle = await resolveDidToHandle(state || did); 56 56 57 57 // Upsert user 58 58 await db.insert(users).values({ did, handle, createdAt: new Date() }).onConflictDoUpdate({
+36
lib/auth/client.ts
··· 59 59 } 60 60 61 61 /** 62 + * Resolve a DID to a handle. In local dev, queries the local PDS. 63 + * In production, queries the PLC directory. 64 + */ 65 + export async function resolveDidToHandle(did: string): Promise<string> { 66 + if (!did.startsWith("did:")) return did; 67 + 68 + if (pdsUrl) { 69 + try { 70 + const res = await fetch( 71 + `${pdsUrl}/xrpc/com.atproto.repo.describeRepo?repo=${encodeURIComponent(did)}`, 72 + ); 73 + if (res.ok) { 74 + const data = (await res.json()) as { handle: string }; 75 + if (data.handle) return data.handle; 76 + } 77 + } catch { 78 + // fall through 79 + } 80 + } 81 + 82 + // Production: resolve via PLC directory 83 + try { 84 + const res = await fetch(`https://plc.directory/${did}`); 85 + if (res.ok) { 86 + const data = (await res.json()) as { alsoKnownAs?: string[] }; 87 + const atUri = data.alsoKnownAs?.find((u) => u.startsWith("at://")); 88 + if (atUri) return atUri.replace("at://", ""); 89 + } 90 + } catch { 91 + // fall through 92 + } 93 + 94 + return did; 95 + } 96 + 97 + /** 62 98 * Rewrite a URL targeting the PDS public hostname to the local PDS. 63 99 * The local PDS registers DIDs with its public hostname (e.g. "pds.dev") 64 100 * which isn't reachable — this maps those URLs to the actual PDS_URL.