forked from
rocksky.app/rocksky
A decentralized music tracking and discovery platform built on AT Protocol 馃幍
1import { JoseKey } from "@atproto/jwk-jose";
2import type { RuntimeLock } from "@atproto/oauth-client-node";
3import Redis from "ioredis";
4import Redlock from "redlock";
5import type { Database } from "../db";
6import { env } from "../lib/env";
7import { SessionStore, StateStore } from "./storage";
8import { CustomOAuthClient } from "./oauth-client";
9
10export const SCOPES = [
11 "atproto",
12 "repo:app.rocksky.album",
13 "repo:app.rocksky.artist",
14 "repo:app.rocksky.graph.follow",
15 "repo:app.rocksky.like",
16 "repo:app.rocksky.playlist",
17 "repo:app.rocksky.scrobble",
18 "repo:app.rocksky.shout",
19 "repo:app.rocksky.song",
20 "repo:app.rocksky.feed.generator",
21 "repo:fm.teal.alpha.feed.play",
22 "repo:fm.teal.alpha.actor.status",
23];
24
25export const createClient = async (db: Database) => {
26 const publicUrl = env.PUBLIC_URL;
27 const url = publicUrl.includes("localhost")
28 ? `http://127.0.0.1:${env.PORT}`
29 : publicUrl;
30 const enc = encodeURIComponent;
31
32 const redis = new Redis(env.REDIS_URL);
33 const redlock = new Redlock([redis]);
34
35 const requestLock: RuntimeLock = async (key, fn) => {
36 const lock = await redlock.acquire([key], 45e3); // 45 seconds
37 try {
38 return await fn();
39 } finally {
40 await lock.release();
41 }
42 };
43
44 return new CustomOAuthClient({
45 clientMetadata: {
46 client_name: "Rocksky",
47 client_id: !publicUrl.includes("localhost")
48 ? `${url}/oauth-client-metadata.json`
49 : `http://localhost?redirect_uri=${enc(
50 `${url}/oauth/callback`,
51 )}&scope=${enc(SCOPES.join(" "))}`,
52 client_uri: url,
53 redirect_uris: [`${url}/oauth/callback`],
54 scope: SCOPES.join(" "),
55 grant_types: ["authorization_code", "refresh_token"],
56 response_types: ["code"],
57 application_type: "web",
58 token_endpoint_auth_method: url.startsWith("https")
59 ? "private_key_jwt"
60 : "none",
61 token_endpoint_auth_signing_alg: url.startsWith("https")
62 ? "ES256"
63 : undefined,
64 dpop_bound_access_tokens: true,
65 jwks_uri: url.startsWith("https") ? `${url}/jwks.json` : undefined,
66 },
67 keyset: url.startsWith("https")
68 ? await Promise.all([
69 JoseKey.fromImportable(env.PRIVATE_KEY_1),
70 JoseKey.fromImportable(env.PRIVATE_KEY_2),
71 JoseKey.fromImportable(env.PRIVATE_KEY_3),
72 ])
73 : undefined,
74 stateStore: new StateStore(db),
75 sessionStore: new SessionStore(db),
76 requestLock,
77 });
78};