dollspace

Untitled

Nov 17th, 2025
49
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 5.44 KB | None | 0 0
  1. Step 1: Define Minimal Scope in Client Metadata
  2.  
  3. // Secure OAuth client metadata
  4. function buildClientMetadata() {
  5. const baseMetadata = {
  6. application_type: 'web',
  7. client_name: 'Bluesky Tool',
  8. dpop_bound_access_tokens: true,
  9. grant_types: ['authorization_code'],
  10. response_types: ['code'],
  11. scope: 'atproto', // <-- Notice: only 'atproto' is requested
  12. token_endpoint_auth_method: 'private_key_jwt',
  13. token_endpoint_auth_signing_alg: 'ES256'
  14. };
  15.  
  16. Step 2: Request Only the Base Scope During Login
  17.  
  18. export async function initiateBlueskyOAuth(req: Request, res: Response) {
  19. // ...
  20.  
  21. // ... (Production: Use PAR flow)
  22. const parPayload = new URLSearchParams({
  23. response_type: 'code',
  24. client_id: FINAL_CLIENT_METADATA_URL,
  25. redirect_uri: FINAL_REDIRECT_URI,
  26. scope: 'atproto', // <-- Explicitly 'atproto' for production
  27. state: state,
  28. code_challenge: codeChallenge,
  29. code_challenge_method: 'S256'
  30. });
  31.  
  32. // ...
  33.  
  34. // ... (Development: Use direct authorization flow)
  35. const authUrl = new URL(oauthMetadata.authorization_endpoint);
  36. authUrl.searchParams.set('response_type', 'code');
  37. authUrl.searchParams.set('client_id', FINAL_CLIENT_METADATA_URL);
  38. authUrl.searchParams.set('redirect_uri', FINAL_REDIRECT_URI);
  39. authUrl.searchParams.set('scope', 'atproto'); // <-- Explicitly 'atproto' for development
  40. authUrl.searchParams.set('state', state);
  41. authUrl.searchParams.set('code_challenge', codeChallenge);
  42. authUrl.searchParams.set('code_challenge_method', 'S256');
  43.  
  44. res.redirect(authUrl.toString());
  45. // ...
  46.  
  47. You've got it. Here is how the codebase implements that exact "authentication-only" flow, broken down into four steps with the specific TypeScript code.
  48.  
  49. How It's Done in Your Codebase:
  50. The process separates "who you are" (Bluesky login) from "what you're allowed to do" (the app's own user list).
  51.  
  52. Step 1: Define Minimal Scope in Client Metadata
  53. First, the application's metadata (what it tells Bluesky it's capable of) is configured to only include the base atproto scope. It does not include transition:generic or any other scopes that grant permissions.
  54.  
  55. File: SkySight-main/server/routes/oauth.ts
  56.  
  57. TypeScript
  58.  
  59. // --- SECURITY FIX ---
  60. // Secure OAuth client metadata - no hardcoded insecure defaults
  61. function buildClientMetadata() {
  62. const baseMetadata = {
  63. application_type: 'web',
  64. client_name: 'Bluesky OSINT Tool',
  65. dpop_bound_access_tokens: true,
  66. grant_types: ['authorization_code'],
  67. response_types: ['code'],
  68. scope: 'atproto', // <-- Notice: only 'atproto' is requested
  69. token_endpoint_auth_method: 'private_key_jwt',
  70. token_endpoint_auth_signing_alg: 'ES256'
  71. };
  72.  
  73. // ... rest of the function
  74. }
  75. Step 2: Request Only the Base Scope During Login
  76. When the user is redirected to log in, the code again explicitly requests only the atproto scope, ensuring it doesn't ask for more permissions at runtime.
  77.  
  78. File: SkySight-main/server/blueskyAuth.ts
  79.  
  80. TypeScript
  81.  
  82. export async function initiateBlueskyOAuth(req: Request, res: Response) {
  83. // ...
  84.  
  85. // ... (Production: Use PAR flow)
  86. const parPayload = new URLSearchParams({
  87. response_type: 'code',
  88. client_id: FINAL_CLIENT_METADATA_URL,
  89. redirect_uri: FINAL_REDIRECT_URI,
  90. scope: 'atproto', // <-- Explicitly 'atproto' for production
  91. state: state,
  92. code_challenge: codeChallenge,
  93. code_challenge_method: 'S256'
  94. });
  95.  
  96. // ...
  97.  
  98. // ... (Development: Use direct authorization flow)
  99. const authUrl = new URL(oauthMetadata.authorization_endpoint);
  100. authUrl.searchParams.set('response_type', 'code');
  101. authUrl.searchParams.set('client_id', FINAL_CLIENT_METADATA_URL);
  102. authUrl.searchParams.set('redirect_uri', FINAL_REDIRECT_URI);
  103. authUrl.searchParams.set('scope', 'atproto'); // <-- Explicitly 'atproto' for development
  104. authUrl.searchParams.set('state', state);
  105. authUrl.searchParams.set('code_challenge', codeChallenge);
  106. authUrl.searchParams.set('code_challenge_method', 'S256');
  107.  
  108. res.redirect(authUrl.toString());
  109. // ...
  110. }
  111.  
  112. Step 3: Use the Token Only to Get Profile/Identity
  113.  
  114. export async function handleBlueskyOAuthCallback(req: Request, res: Response) {
  115. // ... (token exchange logic) ...
  116.  
  117. // Get user profile information using access token
  118. let userProfile;
  119. try {
  120. const profileResponse = await fetch(`${pdsUrl}/xrpc/app.bsky.actor.getProfile?actor=${did}`, {
  121. headers: {
  122. 'Authorization': `Bearer ${tokens.access_token}`,
  123. },
  124. });
  125.  
  126. if (profileResponse.ok) {
  127. userProfile = await profileResponse.json();
  128. }
  129. // ... (fallback logic) ...
  130. } catch (profileError) {
  131. // ...
  132. return res.redirect('/login?error=profile_fetch_failed');
  133. }
  134.  
  135. // ... (next step) ...
  136. }
  137.  
  138. Step 4: Check Identity Against an Internal Authorization List
  139.  
  140. // ... (continuation of handleBlueskyOAuthCallback)
  141.  
  142. // Check if user is authorized
  143. const isAuthorized = await isAuthorizedDID(userProfile.did);
  144. if (!isAuthorized) {
  145. console.log(`Unauthorized access attempt from DID: ${userProfile.did}`);
  146. return res.redirect('/login?error=unauthorized');
  147. }
  148.  
  149. // Store user session (ONLY if they passed the isAuthorized check)
  150. session.user = {
  151. did: userProfile.did,
  152. handle: userProfile.handle,
  153. // ... (other user info)
  154. };
  155.  
  156. // Update last login time
  157. await storage.updateAuthorizedUserLogin(userProfile.did);
  158.  
  159. // ... (redirect to the app) ...
  160. }
Advertisement
Add Comment
Please, Sign In to add comment