alf: the atproto Latency Fabric alf.fly.dev/
7
fork

Configure Feed

Select the types of activity you want to include in your feed.

Consolidate OAuth scope to single definition in config

oauthScope is now computed once in getConfig() from allowedCollections.
oauth.ts and routes/oauth.ts both read config.oauthScope instead of
each building the scope string independently.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

+11 -2
+1
src/__tests__/database.test.ts
··· 27 27 encryptionKey: 'a'.repeat(64), 28 28 maxDraftsPerUser: null, 29 29 allowedCollections: '*', 30 + oauthScope: 'atproto repo:*?action=create blob:*/*', 30 31 ...overrides, 31 32 }); 32 33
+1
src/__tests__/oauth.test.ts
··· 37 37 encryptionKey: 'a'.repeat(64), 38 38 maxDraftsPerUser: null, 39 39 allowedCollections: '*', 40 + oauthScope: 'atproto repo:*?action=create blob:*/*', 40 41 }); 41 42 42 43 describe('createOAuthClient', () => {
+1
src/__tests__/routes/oauth.test.ts
··· 22 22 encryptionKey: 'a'.repeat(64), 23 23 maxDraftsPerUser: null, 24 24 allowedCollections: '*', 25 + oauthScope: 'atproto repo:*?action=create blob:*/*', 25 26 ...overrides, 26 27 }); 27 28
+1
src/__tests__/scheduler.test.ts
··· 48 48 encryptionKey: 'a'.repeat(64), 49 49 maxDraftsPerUser: null, 50 50 allowedCollections: '*', 51 + oauthScope: 'atproto repo:*?action=create blob:*/*', 51 52 }); 52 53 53 54 /** Flush all pending microtasks and I/O callbacks */
+1
src/__tests__/server.test.ts
··· 80 80 encryptionKey: 'a'.repeat(64), 81 81 maxDraftsPerUser: null, 82 82 allowedCollections: '*', 83 + oauthScope: 'atproto repo:*?action=create blob:*/*', 83 84 }; 84 85 85 86 const AUTH_HEADER = 'Bearer test-token';
+1
src/__tests__/storage.test.ts
··· 41 41 encryptionKey: 'a'.repeat(64), 42 42 maxDraftsPerUser: null, 43 43 allowedCollections: '*', 44 + oauthScope: 'atproto repo:*?action=create blob:*/*', 44 45 }); 45 46 46 47 describe('storage', () => {
+3
src/config.ts
··· 25 25 * Example: "app.bsky.feed.post" to restrict to Bluesky posts only. 26 26 */ 27 27 allowedCollections: string; 28 + /** OAuth scope string derived from allowedCollections. Single source of truth. */ 29 + oauthScope: string; 28 30 } 29 31 30 32 export const getConfig = (): ServiceConfig => { ··· 45 47 postPublishWebhookUrl: process.env.POST_PUBLISH_WEBHOOK_URL, 46 48 maxDraftsPerUser, 47 49 allowedCollections: process.env.ALLOWED_COLLECTIONS || '*', 50 + oauthScope: `atproto repo:${process.env.ALLOWED_COLLECTIONS || '*'}?action=create blob:*/*`, 48 51 }; 49 52 50 53 if (config.databaseType === 'postgres' && !config.databaseUrl) {
+1 -1
src/oauth.ts
··· 123 123 */ 124 124 export function createOAuthClient(config: ServiceConfig): NodeOAuthClient { 125 125 const isHttps = config.serviceUrl.startsWith('https://'); 126 - const scope = `atproto repo:${config.allowedCollections}?action=create blob:*/*`; 126 + const scope = config.oauthScope; 127 127 128 128 // RFC 8252: loopback client pattern for HTTP (dev), discoverable for HTTPS (prod) 129 129 let clientMetadata: OAuthClientOptions['clientMetadata'];
+1 -1
src/routes/oauth.ts
··· 20 20 * OAuth client metadata document (ATProto client registration) 21 21 * PDSes use this to discover client capabilities 22 22 */ 23 - const scope = `atproto repo:${config.allowedCollections}?action=create blob:*/*`; 23 + const scope = config.oauthScope; 24 24 25 25 router.get('/client-metadata.json', (_req, res) => { 26 26 res.json({