A discord bot for teal.fm
discord
tealfm
music
1import {
2 Keyset,
3 JoseKey,
4 atprotoLoopbackClientMetadata,
5 NodeOAuthClient,
6 type OAuthClientMetadataInput,
7} from "@atproto/oauth-client-node";
8import { env } from "@tealfmbot/common/constants";
9import { db } from "@tealfmbot/database/db";
10import assert from "node:assert";
11
12import { SessionStore, StateStore } from "./storage.js";
13
14const loadJwk = async () => {
15 const raw = env.PRIVATE_KEYS;
16 if (!raw) return undefined;
17 const json = JSON.parse(raw);
18 if (!json) return undefined;
19 const keys = await Promise.all(
20 json.map((jwk: string | Record<string, unknown>) => JoseKey.fromJWK(jwk)),
21 );
22 return new Keyset(keys);
23};
24
25const keyset = env.PUBLIC_URL && env.PRIVATE_KEYS ? await loadJwk() : undefined;
26
27assert(!env.PUBLIC_URL || keyset?.size, "PRIVATE_KEYS environment variable must be set");
28
29const pk = keyset?.findPrivateKey({ usage: "sign" });
30
31const metadata: OAuthClientMetadataInput = env.PUBLIC_URL
32 ? {
33 client_name: "Disco Stu - Teal.fm Discord Bot",
34 client_id: `${env.PUBLIC_URL}/oauth-client-metadata.json`,
35 jwks_uri: `${env.PUBLIC_URL}/.well-known/jwks.json`,
36 redirect_uris: [`${env.PUBLIC_URL}/oauth/callback`],
37 scope: "atproto",
38 grant_types: ["authorization_code", "refresh_token"],
39 response_types: ["code"],
40 application_type: "web",
41 token_endpoint_auth_method: pk ? "private_key_jwt" : "none",
42 token_endpoint_auth_signing_alg: pk ? pk.alg : undefined,
43 dpop_bound_access_tokens: true,
44 }
45 : atprotoLoopbackClientMetadata(
46 `http://localhost?${new URLSearchParams([
47 ["redirect_uri", `http://127.0.0.1:${env.WEB_SERVICE_PORT}/oauth/callback`],
48 ["scope", "atproto"],
49 ])}`,
50 );
51
52export const client = new NodeOAuthClient({
53 ...(typeof keyset !== "undefined" ? { keyset } : undefined),
54 clientMetadata: metadata,
55 stateStore: new StateStore(db),
56 sessionStore: new SessionStore(db),
57});