A minimal starter for ATProto logins in Astro
at main 2.3 kB view raw
1import type { AstroCookies } from 'astro' 2import type { 3 NodeSavedSession, 4 NodeSavedSessionStore, 5 NodeSavedState, 6 NodeSavedStateStore, 7} from '@atproto/oauth-client-node' 8 9// Cookie-based storage for OAuth state and sessions 10// All data is serialized into cookies for stateless operation 11 12export class CookieStateStore implements NodeSavedStateStore { 13 constructor(private cookies: AstroCookies) {} 14 15 async get(key: string): Promise<NodeSavedState | undefined> { 16 const cookieName = `oauth_state_${key}` 17 const cookie = this.cookies.get(cookieName) 18 if (!cookie?.value) return undefined 19 20 try { 21 const decoded = atob(cookie.value) 22 return JSON.parse(decoded) as NodeSavedState 23 } catch (err) { 24 console.warn('Failed to decode OAuth state:', err) 25 return undefined 26 } 27 } 28 29 async set(key: string, val: NodeSavedState) { 30 const cookieName = `oauth_state_${key}` 31 const encoded = btoa(JSON.stringify(val)) 32 33 this.cookies.set(cookieName, encoded, { 34 httpOnly: true, 35 secure: false, 36 sameSite: 'lax', 37 path: '/', 38 maxAge: 60 * 10, // 10 minutes (OAuth flow timeout) 39 }) 40 } 41 42 async del(key: string) { 43 const cookieName = `oauth_state_${key}` 44 this.cookies.delete(cookieName, { path: '/' }) 45 } 46} 47 48export class CookieSessionStore implements NodeSavedSessionStore { 49 constructor(private cookies: AstroCookies) {} 50 51 async get(key: string): Promise<NodeSavedSession | undefined> { 52 const cookieName = `oauth_session_${key}` 53 const cookie = this.cookies.get(cookieName) 54 if (!cookie?.value) return undefined 55 56 try { 57 const decoded = atob(cookie.value) 58 return JSON.parse(decoded) as NodeSavedSession 59 } catch (err) { 60 console.warn('Failed to decode OAuth session:', err) 61 return undefined 62 } 63 } 64 65 async set(key: string, val: NodeSavedSession) { 66 const cookieName = `oauth_session_${key}` 67 const encoded = btoa(JSON.stringify(val)) 68 69 this.cookies.set(cookieName, encoded, { 70 httpOnly: true, 71 secure: false, 72 sameSite: 'lax', 73 path: '/', 74 maxAge: 60 * 60 * 24 * 30, // 30 days 75 }) 76 } 77 78 async del(key: string) { 79 const cookieName = `oauth_session_${key}` 80 this.cookies.delete(cookieName, { path: '/' }) 81 } 82}