···66 */
7788import { Database } from "bun:sqlite";
99-import * as path from "path";
99+import * as path from "node:path";
10101111const DATA_DIR = process.env.DATA_DIR || "./data";
1212const DB_PATH = path.join(DATA_DIR, "oauth.db");
13131414try {
1515- const db = new Database(DB_PATH);
1515+ const db = new Database(DB_PATH);
16161717- // Clean up OAuth states older than 1 hour
1818- const statesResult = db.run(
1919- `DELETE FROM oauth_states WHERE created_at < strftime('%s', 'now') - 3600`,
2020- );
1717+ // Clean up OAuth states older than 1 hour
1818+ const statesResult = db.run(
1919+ `DELETE FROM oauth_states WHERE created_at < strftime('%s', 'now') - 3600`,
2020+ );
21212222- // Clean up sessions older than 30 days (optional - sessions may still be valid)
2323- const sessionsResult = db.run(
2424- `DELETE FROM oauth_sessions WHERE updated_at < strftime('%s', 'now') - 2592000`,
2525- );
2222+ // Clean up sessions older than 30 days (optional - sessions may still be valid)
2323+ const sessionsResult = db.run(
2424+ `DELETE FROM oauth_sessions WHERE updated_at < strftime('%s', 'now') - 2592000`,
2525+ );
26262727- // Vacuum the database to reclaim space
2828- db.run("VACUUM");
2727+ // Vacuum the database to reclaim space
2828+ db.run("VACUUM");
29293030- const timestamp = new Date().toISOString();
3131- console.log(
3232- `[${timestamp}] Cleanup complete: removed old states and sessions, vacuumed database`,
3333- );
3030+ const timestamp = new Date().toISOString();
3131+ console.log(
3232+ `[${timestamp}] Cleanup complete: removed old states and sessions, vacuumed database`,
3333+ );
34343535- db.close();
3535+ db.close();
3636} catch (error) {
3737- console.error("Cleanup failed:", error);
3838- process.exit(1);
3737+ console.error("Cleanup failed:", error);
3838+ process.exit(1);
3939}
+2-2
packages/web/src/lib/logger.ts
···11-import * as fs from "fs";
22-import * as path from "path";
11+import * as fs from "node:fs";
22+import * as path from "node:path";
3344const DATA_DIR = process.env.DATA_DIR || "./data";
55const LOG_PATH = path.join(DATA_DIR, "app.log");
+96-96
packages/web/src/lib/oauth.ts
···11import { NodeOAuthClient } from "@atproto/oauth-client-node";
22import type {
33- NodeSavedSession,
44- NodeSavedState,
33+ NodeSavedSession,
44+ NodeSavedState,
55} from "@atproto/oauth-client-node";
66import { JoseKey } from "@atproto/jwk-jose";
77import { Agent } from "@atproto/api";
88import { Database } from "bun:sqlite";
99-import * as fs from "fs";
1010-import * as path from "path";
99+import * as fs from "node:fs";
1010+import * as path from "node:path";
11111212// Constants
1313const PUBLIC_URL = process.env.PUBLIC_URL || "http://localhost:8000";
···17171818// Ensure data directory exists
1919if (!fs.existsSync(DATA_DIR)) {
2020- fs.mkdirSync(DATA_DIR, { recursive: true });
2020+ fs.mkdirSync(DATA_DIR, { recursive: true });
2121}
22222323// Initialize SQLite database
···42424343// Clean up old states (older than 1 hour)
4444db.run(
4545- `DELETE FROM oauth_states WHERE created_at < strftime('%s', 'now') - 3600`,
4545+ `DELETE FROM oauth_states WHERE created_at < strftime('%s', 'now') - 3600`,
4646);
47474848// State store implementation
4949const stateStore = {
5050- async set(key: string, state: NodeSavedState): Promise<void> {
5151- const stateJson = JSON.stringify(state);
5252- db.run(
5353- `INSERT OR REPLACE INTO oauth_states (key, state, created_at) VALUES (?, ?, strftime('%s', 'now'))`,
5454- [key, stateJson],
5555- );
5656- },
5757- async get(key: string): Promise<NodeSavedState | undefined> {
5858- const row = db
5959- .query(`SELECT state FROM oauth_states WHERE key = ?`)
6060- .get(key) as { state: string } | null;
6161- if (!row) return undefined;
6262- return JSON.parse(row.state);
6363- },
6464- async del(key: string): Promise<void> {
6565- db.run(`DELETE FROM oauth_states WHERE key = ?`, [key]);
6666- },
5050+ async set(key: string, state: NodeSavedState): Promise<void> {
5151+ const stateJson = JSON.stringify(state);
5252+ db.run(
5353+ `INSERT OR REPLACE INTO oauth_states (key, state, created_at) VALUES (?, ?, strftime('%s', 'now'))`,
5454+ [key, stateJson],
5555+ );
5656+ },
5757+ async get(key: string): Promise<NodeSavedState | undefined> {
5858+ const row = db
5959+ .query(`SELECT state FROM oauth_states WHERE key = ?`)
6060+ .get(key) as { state: string } | null;
6161+ if (!row) return undefined;
6262+ return JSON.parse(row.state);
6363+ },
6464+ async del(key: string): Promise<void> {
6565+ db.run(`DELETE FROM oauth_states WHERE key = ?`, [key]);
6666+ },
6767};
68686969// Session store implementation
7070const sessionStore = {
7171- async set(did: string, session: NodeSavedSession): Promise<void> {
7272- const sessionJson = JSON.stringify(session);
7373- db.run(
7474- `INSERT OR REPLACE INTO oauth_sessions (did, session, updated_at) VALUES (?, ?, strftime('%s', 'now'))`,
7575- [did, sessionJson],
7676- );
7777- },
7878- async get(did: string): Promise<NodeSavedSession | undefined> {
7979- const row = db
8080- .query(`SELECT session FROM oauth_sessions WHERE did = ?`)
8181- .get(did) as { session: string } | null;
8282- if (!row) return undefined;
8383- return JSON.parse(row.session);
8484- },
8585- async del(did: string): Promise<void> {
8686- db.run(`DELETE FROM oauth_sessions WHERE did = ?`, [did]);
8787- },
7171+ async set(did: string, session: NodeSavedSession): Promise<void> {
7272+ const sessionJson = JSON.stringify(session);
7373+ db.run(
7474+ `INSERT OR REPLACE INTO oauth_sessions (did, session, updated_at) VALUES (?, ?, strftime('%s', 'now'))`,
7575+ [did, sessionJson],
7676+ );
7777+ },
7878+ async get(did: string): Promise<NodeSavedSession | undefined> {
7979+ const row = db
8080+ .query(`SELECT session FROM oauth_sessions WHERE did = ?`)
8181+ .get(did) as { session: string } | null;
8282+ if (!row) return undefined;
8383+ return JSON.parse(row.session);
8484+ },
8585+ async del(did: string): Promise<void> {
8686+ db.run(`DELETE FROM oauth_sessions WHERE did = ?`, [did]);
8787+ },
8888};
89899090// Generate or load private key for confidential client
9191async function getOrCreatePrivateKey(): Promise<JoseKey> {
9292- if (fs.existsSync(KEYS_PATH)) {
9393- const keyData = JSON.parse(fs.readFileSync(KEYS_PATH, "utf-8"));
9494- return JoseKey.fromJWK(keyData, keyData.kid);
9595- }
9292+ if (fs.existsSync(KEYS_PATH)) {
9393+ const keyData = JSON.parse(fs.readFileSync(KEYS_PATH, "utf-8"));
9494+ return JoseKey.fromJWK(keyData, keyData.kid);
9595+ }
96969797- // Generate a new ES256 key
9898- const key = await JoseKey.generate(["ES256"], crypto.randomUUID());
9999- const jwk = key.privateJwk;
9797+ // Generate a new ES256 key
9898+ const key = await JoseKey.generate(["ES256"], crypto.randomUUID());
9999+ const jwk = key.privateJwk;
100100101101- // Save to disk with restrictive permissions (owner read/write only)
102102- fs.writeFileSync(KEYS_PATH, JSON.stringify(jwk, null, 2), { mode: 0o600 });
101101+ // Save to disk with restrictive permissions (owner read/write only)
102102+ fs.writeFileSync(KEYS_PATH, JSON.stringify(jwk, null, 2), { mode: 0o600 });
103103104104- return key;
104104+ return key;
105105}
106106107107let oauthClientInstance: NodeOAuthClient | null = null;
108108let initPromise: Promise<NodeOAuthClient> | null = null;
109109110110async function initOAuthClient(): Promise<NodeOAuthClient> {
111111- if (oauthClientInstance) return oauthClientInstance;
112112- if (initPromise) return initPromise;
111111+ if (oauthClientInstance) return oauthClientInstance;
112112+ if (initPromise) return initPromise;
113113114114- initPromise = (async () => {
115115- const privateKey = await getOrCreatePrivateKey();
114114+ initPromise = (async () => {
115115+ const privateKey = await getOrCreatePrivateKey();
116116117117- oauthClientInstance = new NodeOAuthClient({
118118- clientMetadata: {
119119- client_id: `${PUBLIC_URL}/client-metadata.json`,
120120- client_name: "sitebase",
121121- client_uri: PUBLIC_URL,
122122- redirect_uris: [`${PUBLIC_URL}/auth/callback`],
123123- scope: "atproto transition:generic",
124124- grant_types: ["authorization_code", "refresh_token"],
125125- response_types: ["code"],
126126- application_type: "web",
127127- token_endpoint_auth_method: "private_key_jwt",
128128- token_endpoint_auth_signing_alg: "ES256",
129129- dpop_bound_access_tokens: true,
130130- jwks_uri: `${PUBLIC_URL}/jwks.json`,
131131- },
132132- keyset: [privateKey],
133133- stateStore,
134134- sessionStore,
135135- });
117117+ oauthClientInstance = new NodeOAuthClient({
118118+ clientMetadata: {
119119+ client_id: `${PUBLIC_URL}/client-metadata.json`,
120120+ client_name: "sitebase",
121121+ client_uri: PUBLIC_URL,
122122+ redirect_uris: [`${PUBLIC_URL}/auth/callback`],
123123+ scope: "atproto transition:generic",
124124+ grant_types: ["authorization_code", "refresh_token"],
125125+ response_types: ["code"],
126126+ application_type: "web",
127127+ token_endpoint_auth_method: "private_key_jwt",
128128+ token_endpoint_auth_signing_alg: "ES256",
129129+ dpop_bound_access_tokens: true,
130130+ jwks_uri: `${PUBLIC_URL}/jwks.json`,
131131+ },
132132+ keyset: [privateKey],
133133+ stateStore,
134134+ sessionStore,
135135+ });
136136137137- return oauthClientInstance;
138138- })();
137137+ return oauthClientInstance;
138138+ })();
139139140140- return initPromise;
140140+ return initPromise;
141141}
142142143143export async function getOAuthClient(): Promise<NodeOAuthClient> {
144144- return initOAuthClient();
144144+ return initOAuthClient();
145145}
146146147147export async function getClientMetadata() {
148148- const client = await getOAuthClient();
149149- return client.clientMetadata;
148148+ const client = await getOAuthClient();
149149+ return client.clientMetadata;
150150}
151151152152export async function getJwks() {
153153- const client = await getOAuthClient();
154154- return client.jwks;
153153+ const client = await getOAuthClient();
154154+ return client.jwks;
155155}
156156157157export async function getAgentForSession(
158158- did: string,
158158+ did: string,
159159): Promise<{ agent: Agent; did: string; handle: string }> {
160160- const client = await getOAuthClient();
161161- const oauthSession = await client.restore(did);
160160+ const client = await getOAuthClient();
161161+ const oauthSession = await client.restore(did);
162162163163- if (!oauthSession) {
164164- throw new Error("Session not found");
165165- }
163163+ if (!oauthSession) {
164164+ throw new Error("Session not found");
165165+ }
166166167167- const agent = new Agent(oauthSession);
167167+ const agent = new Agent(oauthSession);
168168169169- // Fetch profile to get handle
170170- const profile = await agent.getProfile({ actor: did });
169169+ // Fetch profile to get handle
170170+ const profile = await agent.getProfile({ actor: did });
171171172172- return {
173173- agent,
174174- did,
175175- handle: profile.data.handle,
176176- };
172172+ return {
173173+ agent,
174174+ did,
175175+ handle: profile.data.handle,
176176+ };
177177}
178178179179export async function deleteSession(did: string): Promise<void> {
180180- await sessionStore.del(did);
180180+ await sessionStore.del(did);
181181}