dollspace

Untitled

Nov 17th, 2025
81
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 2.98 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.  
  48. Step 3: Use the Token Only to Get Profile/Identity
  49.  
  50. export async function handleBlueskyOAuthCallback(req: Request, res: Response) {
  51. // ... (token exchange logic) ...
  52.  
  53. // Get user profile information using access token
  54. let userProfile;
  55. try {
  56. const profileResponse = await fetch(`${pdsUrl}/xrpc/app.bsky.actor.getProfile?actor=${did}`, {
  57. headers: {
  58. 'Authorization': `Bearer ${tokens.access_token}`,
  59. },
  60. });
  61.  
  62. if (profileResponse.ok) {
  63. userProfile = await profileResponse.json();
  64. }
  65. // ... (fallback logic) ...
  66. } catch (profileError) {
  67. // ...
  68. return res.redirect('/login?error=profile_fetch_failed');
  69. }
  70.  
  71. // ... (next step) ...
  72. }
  73.  
  74. Step 4: Check Identity Against an Internal Authorization List
  75.  
  76. // ... (continuation of handleBlueskyOAuthCallback)
  77.  
  78. // Check if user is authorized
  79. const isAuthorized = await isAuthorizedDID(userProfile.did);
  80. if (!isAuthorized) {
  81. console.log(`Unauthorized access attempt from DID: ${userProfile.did}`);
  82. return res.redirect('/login?error=unauthorized');
  83. }
  84.  
  85. // Store user session (ONLY if they passed the isAuthorized check)
  86. session.user = {
  87. did: userProfile.did,
  88. handle: userProfile.handle,
  89. // ... (other user info)
  90. };
  91.  
  92. // Update last login time
  93. await storage.updateAuthorizedUserLogin(userProfile.did);
  94.  
  95. // ... (redirect to the app) ...
  96. }
Advertisement
Add Comment
Please, Sign In to add comment