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

Viewing files at commit c6b86fb80ff9Commit detailsView latest

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