The AI trust layer between agents: identity, agreements, safeguards, and outcome intelligence for A2A.

Viewing files at commit 61a3d5a92277Commit detailsView latest

api/oauth-callback.ts 71 lines · 2.8 KB · typescript
1
import { auth0Config, verifyAuth0Token } from '../apps/api/src/auth0.js';
2
import { oauthStore, type OAuthStore } from '../apps/api/src/oauth-store.js';
3
import { guardRequest } from '../apps/api/src/request-security.js';
4
import { assertProductionConfiguration } from '../apps/api/src/production-config.js';
5
6
assertProductionConfiguration('auth');
7
8
export async function callback(request: Request, store: OAuthStore): Promise<Response> {
9
  let input: { code?: unknown; state?: unknown };
10
  try {
11
    input = (await request.json()) as { code?: unknown; state?: unknown };
12
  } catch {
13
    return Response.json({ error: 'invalid_request' }, { status: 400 });
14
  }
15
  if (
16
    typeof input.code !== 'string' ||
17
    typeof input.state !== 'string' ||
18
    !input.state.startsWith('oc_tx_')
19
  )
20
    return Response.json({ error: 'invalid_request' }, { status: 400 });
21
22
  const transaction = await store.takeTransaction(input.state);
23
  if (!transaction) return Response.json({ error: 'invalid_transaction' }, { status: 400 });
24
  const config = auth0Config();
25
  const redirectUri = `${config.publicOrigin}/sso-callback`;
26
  const tokenResponse = await fetch(`${config.issuer}oauth/token`, {
27
    method: 'POST',
28
    headers: { 'content-type': 'application/x-www-form-urlencoded' },
29
    body: new URLSearchParams({
30
      grant_type: 'authorization_code',
31
      client_id: config.dashboardClientId,
32
      code: input.code,
33
      code_verifier: transaction.auth0Verifier,
34
      redirect_uri: redirectUri,
35
    }),
36
  });
37
  if (!tokenResponse.ok)
38
    return Response.json({ error: 'identity_exchange_failed' }, { status: 401 });
39
  const tokens = (await tokenResponse.json()) as { access_token?: unknown };
40
  if (typeof tokens.access_token !== 'string')
41
    return Response.json({ error: 'invalid_identity_response' }, { status: 401 });
42
  const authentication = await verifyAuth0Token(tokens.access_token, { dashboard: true });
43
  const code = await store.createAuthorizationCode({
44
    clientId: transaction.clientId,
45
    operatorId: authentication.payload.sub!,
46
    redirectUri: transaction.redirectUri,
47
    scope: transaction.scope,
48
    codeChallenge: transaction.codeChallenge,
49
  });
50
  const downstream = new URL(transaction.redirectUri);
51
  downstream.searchParams.set('code', code);
52
  if (transaction.downstreamState)
53
    downstream.searchParams.set('state', transaction.downstreamState);
54
  return Response.json(
55
    { redirectTo: downstream.href },
56
    { headers: { 'cache-control': 'no-store' } },
57
  );
58
}
59
60
export async function POST(request: Request): Promise<Response> {
61
  const rejected = await guardRequest(request, 'oauth-callback', {
62
    limit: 30,
63
    maximumBytes: 16_384,
64
  });
65
  if (rejected) return rejected;
66
  try {
67
    return await callback(request, oauthStore());
68
  } catch {
69
    return Response.json({ error: 'callback_failed' }, { status: 500 });
70
  }
71
}