A CLI for publishing standard.site documents to ATProto
at main 124 lines 3.0 kB view raw
1import * as fs from "node:fs/promises"; 2import * as os from "node:os"; 3import * as path from "node:path"; 4import type { 5 NodeSavedSession, 6 NodeSavedSessionStore, 7 NodeSavedState, 8 NodeSavedStateStore, 9} from "@atproto/oauth-client-node"; 10 11const CONFIG_DIR = path.join(os.homedir(), ".config", "sequoia"); 12const OAUTH_FILE = path.join(CONFIG_DIR, "oauth.json"); 13 14interface OAuthStore { 15 states: Record<string, NodeSavedState>; 16 sessions: Record<string, NodeSavedSession>; 17} 18 19async function fileExists(filePath: string): Promise<boolean> { 20 try { 21 await fs.access(filePath); 22 return true; 23 } catch { 24 return false; 25 } 26} 27 28async function loadOAuthStore(): Promise<OAuthStore> { 29 if (!(await fileExists(OAUTH_FILE))) { 30 return { states: {}, sessions: {} }; 31 } 32 33 try { 34 const content = await fs.readFile(OAUTH_FILE, "utf-8"); 35 return JSON.parse(content) as OAuthStore; 36 } catch { 37 return { states: {}, sessions: {} }; 38 } 39} 40 41async function saveOAuthStore(store: OAuthStore): Promise<void> { 42 await fs.mkdir(CONFIG_DIR, { recursive: true }); 43 await fs.writeFile(OAUTH_FILE, JSON.stringify(store, null, 2)); 44 await fs.chmod(OAUTH_FILE, 0o600); 45} 46 47/** 48 * State store for PKCE flow (temporary, used during auth) 49 */ 50export const stateStore: NodeSavedStateStore = { 51 async set(key: string, state: NodeSavedState): Promise<void> { 52 const store = await loadOAuthStore(); 53 store.states[key] = state; 54 await saveOAuthStore(store); 55 }, 56 57 async get(key: string): Promise<NodeSavedState | undefined> { 58 const store = await loadOAuthStore(); 59 return store.states[key]; 60 }, 61 62 async del(key: string): Promise<void> { 63 const store = await loadOAuthStore(); 64 delete store.states[key]; 65 await saveOAuthStore(store); 66 }, 67}; 68 69/** 70 * Session store for OAuth tokens (persistent) 71 */ 72export const sessionStore: NodeSavedSessionStore = { 73 async set(sub: string, session: NodeSavedSession): Promise<void> { 74 const store = await loadOAuthStore(); 75 store.sessions[sub] = session; 76 await saveOAuthStore(store); 77 }, 78 79 async get(sub: string): Promise<NodeSavedSession | undefined> { 80 const store = await loadOAuthStore(); 81 return store.sessions[sub]; 82 }, 83 84 async del(sub: string): Promise<void> { 85 const store = await loadOAuthStore(); 86 delete store.sessions[sub]; 87 await saveOAuthStore(store); 88 }, 89}; 90 91/** 92 * List all stored OAuth session DIDs 93 */ 94export async function listOAuthSessions(): Promise<string[]> { 95 const store = await loadOAuthStore(); 96 return Object.keys(store.sessions); 97} 98 99/** 100 * Get an OAuth session by DID 101 */ 102export async function getOAuthSession( 103 did: string, 104): Promise<NodeSavedSession | undefined> { 105 const store = await loadOAuthStore(); 106 return store.sessions[did]; 107} 108 109/** 110 * Delete an OAuth session by DID 111 */ 112export async function deleteOAuthSession(did: string): Promise<boolean> { 113 const store = await loadOAuthStore(); 114 if (!store.sessions[did]) { 115 return false; 116 } 117 delete store.sessions[did]; 118 await saveOAuthStore(store); 119 return true; 120} 121 122export function getOAuthStorePath(): string { 123 return OAUTH_FILE; 124}