@alteran/astro 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +23 -0
  2. package/index.js +8 -0
  3. package/migrations/0009_oauth_session_state.sql +31 -0
  4. package/migrations/meta/0009_snapshot.json +749 -0
  5. package/migrations/meta/_journal.json +7 -0
  6. package/package.json +2 -1
  7. package/src/db/account.ts +134 -1
  8. package/src/db/schema.ts +31 -0
  9. package/src/handlers/root.ts +1 -1
  10. package/src/lib/appview/proxy.ts +11 -8
  11. package/src/lib/auth.ts +34 -3
  12. package/src/lib/jwt.ts +4 -0
  13. package/src/lib/oauth/as-keys.ts +29 -0
  14. package/src/lib/oauth/clients.ts +453 -24
  15. package/src/lib/oauth/consent.ts +180 -0
  16. package/src/lib/oauth/dpop.ts +39 -5
  17. package/src/lib/oauth/resource.ts +93 -21
  18. package/src/lib/oauth/store.ts +64 -7
  19. package/src/lib/refresh-session.ts +16 -0
  20. package/src/lib/session-tokens.ts +33 -5
  21. package/src/lib/token-cleanup.ts +4 -2
  22. package/src/lib/util.ts +0 -1
  23. package/src/pages/.well-known/oauth-authorization-server.ts +16 -3
  24. package/src/pages/.well-known/oauth-protected-resource.ts +8 -4
  25. package/src/pages/oauth/authorize.ts +31 -52
  26. package/src/pages/oauth/consent.ts +163 -66
  27. package/src/pages/oauth/jwks.ts +15 -0
  28. package/src/pages/oauth/par.ts +34 -56
  29. package/src/pages/oauth/revoke.ts +75 -0
  30. package/src/pages/oauth/token.ts +148 -89
  31. package/src/pages/xrpc/[...nsid].ts +7 -6
  32. package/src/pages/xrpc/app.bsky.actor.getPreferences.ts +3 -4
  33. package/src/pages/xrpc/app.bsky.actor.putPreferences.ts +3 -4
  34. package/src/pages/xrpc/app.bsky.unspecced.getAgeAssuranceState.ts +3 -4
  35. package/src/pages/xrpc/chat.bsky.convo.getLog.ts +3 -4
  36. package/src/pages/xrpc/chat.bsky.convo.listConvos.ts +3 -4
  37. package/src/pages/xrpc/com.atproto.identity.getRecommendedDidCredentials.ts +3 -4
  38. package/src/pages/xrpc/com.atproto.identity.requestPlcOperationSignature.ts +3 -4
  39. package/src/pages/xrpc/com.atproto.identity.signPlcOperation.ts +3 -4
  40. package/src/pages/xrpc/com.atproto.identity.submitPlcOperation.ts +3 -4
  41. package/src/pages/xrpc/com.atproto.repo.listMissingBlobs.ts +3 -4
  42. package/src/pages/xrpc/com.atproto.server.checkAccountStatus.ts +3 -4
  43. package/src/pages/xrpc/com.atproto.server.deleteSession.ts +28 -9
  44. package/src/pages/xrpc/com.atproto.server.getSession.ts +3 -4
  45. package/src/worker/runtime.ts +23 -1
  46. package/types/env.d.ts +1 -0
@@ -1,20 +1,39 @@
1
1
  import type { APIContext } from 'astro';
2
+ import { bearerToken } from '../../lib/util';
3
+ import { verifyRefreshToken } from '../../lib/session-tokens';
4
+ import { getRefreshToken, revokeOAuthSession, deleteRefreshToken } from '../../db/account';
2
5
 
3
6
  export const prerender = false;
4
7
 
5
8
  /**
6
9
  * com.atproto.server.deleteSession
7
- * Delete the current session (logout)
10
+ * Delete the current refresh credential. OAuth refresh rows also revoke the
11
+ * owning OAuth session, which invalidates the current DPoP-bound access token.
8
12
  */
9
- export async function POST({ locals }: APIContext) {
13
+ export async function POST({ locals, request }: APIContext) {
10
14
  const { env } = locals.runtime;
15
+ const token = bearerToken(request);
16
+ if (!token) {
17
+ return new Response(JSON.stringify({ error: 'AuthRequired', message: 'No authorization token provided' }), {
18
+ status: 401,
19
+ headers: { 'Content-Type': 'application/json' },
20
+ });
21
+ }
11
22
 
12
- // TODO: Implement proper session revocation
13
- // For single-user PDS, we just return success
14
- // In a full implementation, this would:
15
- // 1. Extract refresh token from Authorization header
16
- // 2. Add it to a blacklist/revocation list
17
- // 3. Invalidate associated access tokens
23
+ const verification = await verifyRefreshToken(env, token, { ignoreExpiration: true }).catch(() => null);
24
+ const jti = verification?.decoded?.jti;
25
+ if (!jti) {
26
+ return new Response(JSON.stringify({ error: 'InvalidToken', message: 'Invalid refresh token' }), {
27
+ status: 401,
28
+ headers: { 'Content-Type': 'application/json' },
29
+ });
30
+ }
31
+
32
+ const stored = await getRefreshToken(env, jti);
33
+ if (stored?.tokenKind === 'oauth' && stored.oauthSessionId) {
34
+ await revokeOAuthSession(env, stored.oauthSessionId);
35
+ }
36
+ await deleteRefreshToken(env, jti);
18
37
 
19
38
  return new Response(JSON.stringify({}), {
20
39
  status: 200,
@@ -22,4 +41,4 @@ export async function POST({ locals }: APIContext) {
22
41
  'Content-Type': 'application/json',
23
42
  },
24
43
  });
25
- }
44
+ }
@@ -1,5 +1,5 @@
1
1
  import type { APIContext } from 'astro';
2
- import { AuthTokenExpiredError, authenticateRequest, expiredToken, unauthorized } from '../../lib/auth';
2
+ import { authErrorResponse, authenticateRequest, unauthorized } from '../../lib/auth';
3
3
  import { getAccountByIdentifier } from '../../db/account';
4
4
 
5
5
  export const prerender = false;
@@ -16,9 +16,8 @@ export async function GET({ locals, request }: APIContext) {
16
16
  try {
17
17
  authContext = await authenticateRequest(request, env);
18
18
  } catch (error) {
19
- if (error instanceof AuthTokenExpiredError) {
20
- return expiredToken();
21
- }
19
+ const handled = await authErrorResponse(env, error);
20
+ if (handled) return handled;
22
21
  throw error;
23
22
  }
24
23
  if (!authContext) {
@@ -140,11 +140,33 @@ export function createPdsFetchHandler(options?: CreatePdsFetchHandlerOptions): P
140
140
  }
141
141
 
142
142
  const astroFetch = await getAstroFetch(options);
143
- const response = await astroFetch(request, resolvedEnv as any, ctx);
143
+ const response = await astroFetch(normalizeXrpcRequestForAstro(request), resolvedEnv as any, ctx);
144
144
  return response as unknown as WorkersResponse;
145
145
  };
146
146
  }
147
147
 
148
+ export function normalizeXrpcRequestForAstro(request: WorkersRequest): WorkersRequest {
149
+ const url = new URL(request.url);
150
+ if (!url.pathname.startsWith('/xrpc/')) {
151
+ return request;
152
+ }
153
+
154
+ // Astro's SSR origin-check middleware rejects unsafe requests when Origin is
155
+ // absent or cross-origin. XRPC is a bearer-token API, not cookie/form auth,
156
+ // and atproto clients legitimately send bodyless POSTs from native runtimes.
157
+ if (request.headers.get('origin') === url.origin) {
158
+ return request;
159
+ }
160
+
161
+ const headerRecord: Record<string, string> = {};
162
+ request.headers.forEach((value, key) => {
163
+ headerRecord[key] = value;
164
+ });
165
+ headerRecord.origin = url.origin;
166
+
167
+ return new Request(request as any, { headers: headerRecord }) as unknown as WorkersRequest;
168
+ }
169
+
148
170
  type AstroFetchHandler = (
149
171
  request: WorkersRequest,
150
172
  env: Env,
package/types/env.d.ts CHANGED
@@ -60,6 +60,7 @@ declare global {
60
60
  // Relay crawl configuration
61
61
  PDS_RELAY_HOSTS?: string; // CSV of relay hostnames (no scheme). Default: bsky.network
62
62
  PDS_RELAY_NOTIFY?: string; // 'false' to disable auto notify
63
+ PDS_OAUTH_CLIENT_HOSTS?: string; // CSV of trusted OAuth client metadata/JWKS hostnames
63
64
  }
64
65
 
65
66
  namespace App {