diff --git a/gateway/src/routes/oidc/authorize.ts b/gateway/src/routes/oidc/authorize.ts index 8f9a2e0..a85ddd2 100644 --- a/gateway/src/routes/oidc/authorize.ts +++ b/gateway/src/routes/oidc/authorize.ts @@ -440,7 +440,10 @@ export function createAuthorizeRouter( callbackParams.set('state', state); if (iss) callbackParams.set('iss', iss); - const callbackResult = await oauthService.handleCallback(callbackParams); + const callbackResult = await oauthService.handleCallback( + callbackParams, + `${oidcService.issuer}/oauth/callback` + ); if (!callbackResult) { return res.status(500).json({ error: 'server_error', diff --git a/gateway/src/routes/oidc/revoke.ts b/gateway/src/routes/oidc/revoke.ts index 40642fd..74fe6fe 100644 --- a/gateway/src/routes/oidc/revoke.ts +++ b/gateway/src/routes/oidc/revoke.ts @@ -72,8 +72,9 @@ export function createRevokeRouter(db: DatabaseService): Router { return; } - const expectedSecret = client.client_secret; - if (!expectedSecret || !crypto.timingSafeEqual(Buffer.from(clientSecret), Buffer.from(expectedSecret))) { + const expectedHash = client.client_secret; + const incomingHash = crypto.createHash('sha256').update(clientSecret).digest('hex'); + if (!expectedHash || !crypto.timingSafeEqual(Buffer.from(incomingHash), Buffer.from(expectedHash))) { res.status(401).json({ error: 'invalid_client', error_description: 'Invalid client credentials', diff --git a/gateway/src/routes/oidc/token.ts b/gateway/src/routes/oidc/token.ts index 1919009..959a5c8 100644 --- a/gateway/src/routes/oidc/token.ts +++ b/gateway/src/routes/oidc/token.ts @@ -183,9 +183,10 @@ async function handleAuthorizationCodeGrant( return; } - // Verify client secret (constant-time comparison) - const expectedSecret = client.client_secret; - if (!expectedSecret || !crypto.timingSafeEqual(Buffer.from(clientSecret), Buffer.from(expectedSecret))) { + // Verify client secret (hash incoming secret and constant-time compare against stored hash) + const expectedHash = client.client_secret; + const incomingHash = crypto.createHash('sha256').update(clientSecret).digest('hex'); + if (!expectedHash || !crypto.timingSafeEqual(Buffer.from(incomingHash), Buffer.from(expectedHash))) { res.status(401).json({ error: 'invalid_client', error_description: 'Invalid client credentials', @@ -346,8 +347,9 @@ async function handleRefreshTokenGrant( return; } - const expectedSecret = client.client_secret; - if (!expectedSecret || !crypto.timingSafeEqual(Buffer.from(clientSecret), Buffer.from(expectedSecret))) { + const expectedHash = client.client_secret; + const incomingHash = crypto.createHash('sha256').update(clientSecret).digest('hex'); + if (!expectedHash || !crypto.timingSafeEqual(Buffer.from(incomingHash), Buffer.from(expectedHash))) { res.status(401).json({ error: 'invalid_client', error_description: 'Invalid client credentials', diff --git a/gateway/src/routes/oidc/userinfo.ts b/gateway/src/routes/oidc/userinfo.ts index c3eea8e..07a1daf 100644 --- a/gateway/src/routes/oidc/userinfo.ts +++ b/gateway/src/routes/oidc/userinfo.ts @@ -43,29 +43,38 @@ export function createUserInfoRouter(db: DatabaseService, oidcService: OIDCServi // Get scopes from the token const scopes = parseScopes(claims.scope); + // Resolve the user's handle from mappings or AT Protocol API + let handle = ''; + + // Try to get handle from sessions or mappings first + const mapping = db.getUserMapping(claims.sub, claims.client_id); + if (mapping && mapping.handle) { + handle = mapping.handle; + } else { + // Resolve DID to handle via AT Protocol API + try { + const response = await fetch( + `https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile?actor=${encodeURIComponent(claims.sub)}` + ); + if (response.ok) { + const data = (await response.json()) as { handle: string }; + handle = data.handle; + } + } catch { + // Fall back to DID as handle + handle = claims.sub; + } + } + // Build user info based on scopes const userInfo = buildUserInfo( { did: claims.sub, - handle: '', // We need to look this up + handle, }, scopes ); - // Try to get additional user info from sessions or mappings - const mapping = db.getUserMapping(claims.sub, claims.client_id); - if (mapping && mapping.handle) { - const enhancedUserInfo = buildUserInfo( - { - did: claims.sub, - handle: mapping.handle, - }, - scopes - ); - res.json(enhancedUserInfo); - return; - } - // If we have email scope, try to get verified email if (scopes.includes('email')) { const emails = db.getUserEmails(claims.sub);