A discord bot for teal.fm
discord tealfm music

more auth work, some validation stuff

besaid.zone 703bc015 09091851

verified
Changed files
+60 -26
apps
+44 -16
apps/web/index.ts
··· 1 + import { isValidHandle } from "@atproto/syntax"; 1 2 import { serve, type HttpBindings } from "@hono/node-server"; 3 + import { COOKIE_SECRET } from "@tealfmbot/common/constants.js"; 2 4 import { logger } from "@tealfmbot/common/logger.js"; 3 5 import { Hono } from "hono"; 6 + import { getSignedCookie } from "hono/cookie"; 7 + import { validator } from "hono/validator"; 4 8 import pinoHttpLogger from "pino-http"; 5 9 6 10 import { client } from "./client"; 7 11 import { createSession, MAX_AGE } from "./utils"; 8 - import { getSignedCookie } from "hono/cookie"; 9 - import { COOKIE_SECRET } from "@tealfmbot/common/constants.js"; 10 12 11 13 type Variables = { 12 14 logger: typeof logger; ··· 27 29 }); 28 30 29 31 app.get("/", (c) => { 30 - c.var.logger.info("test log"); 31 32 return c.text("yo!!"); 32 33 }); 33 34 35 + app.get("/health", (c) => { 36 + return c.json({ message: "OK" }, 200); 37 + }); 38 + 34 39 app.get("/oauth-client-metadata.json", (c) => { 35 40 c.header("Cache-Control", `max-age=${MAX_AGE}, public`); 36 41 return c.json(client.clientMetadata); ··· 42 47 }); 43 48 44 49 app.get("/oauth/callback", async (c) => { 45 - c.header("Cache-Control", "no-store") 46 - const params = new URLSearchParams(c.req.url.split("?")[1]) 50 + c.header("Cache-Control", "no-store"); 51 + const params = new URLSearchParams(c.req.url.split("?")[1]); 47 52 48 53 try { 49 - const session = await getSignedCookie(c, COOKIE_SECRET, "__teal_fm_bot_session") 54 + const session = await getSignedCookie(c, COOKIE_SECRET, "__teal_fm_bot_session"); 50 55 if (session) { 51 56 try { 52 - const oauthSession = await client.restore(session) 53 - if (oauthSession) oauthSession.signOut() 54 - } catch { 55 - logger.warn("oauth restore failed") 57 + const oauthSession = await client.restore(session); 58 + if (oauthSession) oauthSession.signOut(); 59 + } catch (error) { 60 + logger.warn({ error }, "oauth restore failed"); 56 61 } 57 62 } 58 - const oauth = await client.callback(params) 63 + const oauth = await client.callback(params); 59 64 await createSession(oauth.session.did); 60 - 61 - } catch { 62 - logger.error("oauth callback failed") 65 + } catch (error) { 66 + logger.error({ error }, "oauth callback failed"); 63 67 } 64 68 65 - return c.redirect("/") 66 - }) 69 + return c.redirect("/"); 70 + }); 71 + 72 + app.post( 73 + "/login", 74 + validator("form", (value, c) => { 75 + const identifier = value["identifier"]; 76 + if (!identifier || typeof identifier !== "string" || !isValidHandle(identifier)) { 77 + return c.json({ message: "Invalid handle, did or PDS URL" }, 400); 78 + } 79 + 80 + return { 81 + identifier, 82 + }; 83 + }), 84 + (c) => { 85 + c.header("Cache-Control", "no-store"); 86 + const { identifier } = c.req.valid("form"); 87 + try { 88 + return c.text(identifier); 89 + } catch (error) { 90 + logger.error({ error }, "oauth authorize failed"); 91 + return c.json({ message: "oauth authorize failed" }, 500); 92 + } 93 + }, 94 + ); 67 95 68 96 const server = serve({ 69 97 fetch: app.fetch,
+1
apps/web/package.json
··· 11 11 "dependencies": { 12 12 "@atproto/api": "^0.18.8", 13 13 "@atproto/oauth-client-node": "^0.3.13", 14 + "@atproto/syntax": "^0.4.2", 14 15 "@hono/node-server": "^1.19.7", 15 16 "@tealfmbot/common": "workspace:*", 16 17 "@tealfmbot/database": "workspace:*",
+12 -10
apps/web/utils.ts
··· 1 - import { COOKIE_SECRET } from "@tealfmbot/common/constants.ts"; 2 1 import type { Context } from "hono"; 3 - import { deleteCookie, generateSignedCookie, getSignedCookie } from "hono/cookie"; 4 - import { client } from "./client"; 2 + 5 3 import { Agent } from "@atproto/api"; 4 + import { COOKIE_SECRET } from "@tealfmbot/common/constants.ts"; 6 5 import { logger } from "@tealfmbot/common/logger.js"; 6 + import { deleteCookie, generateSignedCookie, getSignedCookie } from "hono/cookie"; 7 + 8 + import { client } from "./client"; 7 9 8 10 export const MAX_AGE = process.env.NODE_ENV === "production" ? 60 : 0; 9 11 ··· 19 21 } 20 22 21 23 export async function getSessionAgent(c: Context) { 22 - c.header("Vary", "Cookie") 23 - const session = await getSignedCookie(c, COOKIE_SECRET, "__teal_fm_bot_session") 24 + c.header("Vary", "Cookie"); 25 + const session = await getSignedCookie(c, COOKIE_SECRET, "__teal_fm_bot_session"); 24 26 if (!session) return null; 25 - c.header("Cache-Control", `max-age=${MAX_AGE}, private`) 27 + c.header("Cache-Control", `max-age=${MAX_AGE}, private`); 26 28 27 29 try { 28 - const oauthSession = await client.restore(session) 29 - return oauthSession ? new Agent(oauthSession) : null 30 + const oauthSession = await client.restore(session); 31 + return oauthSession ? new Agent(oauthSession) : null; 30 32 } catch { 31 - logger.warn("oauth restore failed") 32 - deleteCookie(c, "__teal_fm_bot_session") 33 + logger.warn("oauth restore failed"); 34 + deleteCookie(c, "__teal_fm_bot_session"); 33 35 return null; 34 36 } 35 37 }
+3
pnpm-lock.yaml
··· 73 73 '@atproto/oauth-client-node': 74 74 specifier: ^0.3.13 75 75 version: 0.3.13 76 + '@atproto/syntax': 77 + specifier: ^0.4.2 78 + version: 0.4.2 76 79 '@hono/node-server': 77 80 specifier: ^1.19.7 78 81 version: 1.19.7(hono@4.11.3)