import { APPVIEW_URL } from './appview' /** * Beta access control — allowlist via an AT Protocol list. * * Set BETA_LIST_URI to an AT URI (at://did/app.bsky.graph.list/rkey) to * restrict login to members of that list. Remove the env var to open the * app to everyone (production launch mode). * * The list is fetched from the public Bluesky API and cached in memory for * CACHE_TTL_MS. Members added to the list on Bluesky will gain access within * that window without any server restart or code change. */ declare global { // eslint-disable-next-line no-var var __betaListCache: { dids: Set; fetchedAt: number } | undefined } const CACHE_TTL_MS = 5 * 60 * 1000 // 5 minutes async function fetchAllowedDids(): Promise> { const listUri = process.env.BETA_LIST_URI if (!listUri) return new Set() const now = Date.now() if ( globalThis.__betaListCache && now - globalThis.__betaListCache.fetchedAt < CACHE_TTL_MS ) { return globalThis.__betaListCache.dids } const dids = new Set() let cursor: string | undefined try { do { const params = new URLSearchParams({ list: listUri, limit: '100' }) if (cursor) params.set('cursor', cursor) const res = await fetch( `${APPVIEW_URL}/xrpc/app.bsky.graph.getList?${params}` ) if (!res.ok) break const data = await res.json() for (const item of (data.items ?? []) as Array<{ subject?: { did?: string } }>) { if (item.subject?.did) dids.add(item.subject.did) } cursor = data.cursor } while (cursor) } catch (err) { console.error('[beta] failed to fetch allowlist:', err) // On fetch failure, fall back to stale cache rather than blocking everyone if (globalThis.__betaListCache) return globalThis.__betaListCache.dids } globalThis.__betaListCache = { dids, fetchedAt: now } return dids } /** Returns true if the user is allowed to access the app. */ export async function isAllowedUser(did: string): Promise { if (!process.env.BETA_LIST_URI) return true const dids = await fetchAllowedDids() return dids.has(did) } /** Returns true when beta restriction is active (BETA_LIST_URI is set). */ export function isBetaRestricted(): boolean { return !!process.env.BETA_LIST_URI }