16164ea14c
* feat(voucher): add create voucher and correct entry previews; update commit methods * feat: add support for pending operations in API key scopes and OAuth client management - Introduced new API key scopes for reading and approving pending operations. - Updated the scope groups to include pending operations. - Added new tools for listing and managing pending operations. - Implemented OAuth client registration and revocation endpoints. - Created a UI panel for managing OAuth clients, including registration and revocation. - Added tests for pending operations tools and OAuth allowlist functionality. - Implemented a database migration for OAuth client registrations with appropriate policies and constraints. * feat: Implement OAuth client registration rate limiting and enhance security measures - Added IP-based rate limiting to the OAuth client registration endpoint to prevent enumeration attacks. - Introduced a service-role client for allowlist lookups, ensuring trust boundaries are maintained. - Updated error responses to be uniform across different types of redirect URI validation failures. - Enhanced tests to reflect changes in OAuth scope handling, ensuring fallback to read-only scopes when no scopes are provided. - Improved handling of high-risk pending operations, requiring explicit confirmation for approvals. - Added audit logging for OAuth client revocations and pending operation approvals/rejections to maintain a security audit trail. - Refactored API key scope management to include default read-only scopes for OAuth-issued keys and added segregation-of-duties checks.
61 lines
2.1 KiB
TypeScript
61 lines
2.1 KiB
TypeScript
import type { SupabaseClient } from '@supabase/supabase-js'
|
|
import { createServiceClientNoCookies } from './api-keys'
|
|
|
|
/**
|
|
* Built-in redirect URI patterns. These bypass the DB lookup entirely so
|
|
* Claude's connector keeps working without seeded rows, and so local
|
|
* development never depends on having a registration.
|
|
*/
|
|
export const BUILT_IN_REDIRECT_PATTERNS: readonly RegExp[] = [
|
|
/^https:\/\/claude\.ai\/api\//,
|
|
/^https:\/\/claude\.com\/api\//,
|
|
/^http:\/\/localhost(:\d+)?(\/|$)/,
|
|
/^http:\/\/127\.0\.0\.1(:\d+)?(\/|$)/,
|
|
]
|
|
|
|
export function isBuiltInRedirectUri(uri: string): boolean {
|
|
return BUILT_IN_REDIRECT_PATTERNS.some((pattern) => pattern.test(uri))
|
|
}
|
|
|
|
/**
|
|
* Resolve whether a redirect URI is allowed. Built-in patterns short-circuit;
|
|
* otherwise we look for a non-revoked registration in oauth_client_registrations.
|
|
*
|
|
* The supabase client should be supplied explicitly by the caller so the
|
|
* trust boundary is visible at the callsite (SOC 2 CC6.1). When omitted, the
|
|
* function falls back to a service-role client — required for the /register
|
|
* endpoint which has no user session yet. The lookup is by exact URI; the
|
|
* unique partial index on the table ensures at most one active row.
|
|
*
|
|
* Fails closed on any error (client construction, DB query) — for an
|
|
* allowlist, "unknown → deny" is the safe default.
|
|
*/
|
|
export async function isAllowedRedirectUri(
|
|
uri: string,
|
|
supabase?: SupabaseClient
|
|
): Promise<boolean> {
|
|
if (typeof uri !== 'string' || uri.length === 0) return false
|
|
if (isBuiltInRedirectUri(uri)) return true
|
|
|
|
// Service-role client construction can throw when Supabase env vars are
|
|
// absent (unit tests, misconfigured deploys). Treat that as "not allowed"
|
|
// — failing closed is the safe default for an allowlist.
|
|
let client: SupabaseClient
|
|
try {
|
|
client = supabase ?? createServiceClientNoCookies()
|
|
} catch {
|
|
return false
|
|
}
|
|
|
|
const { data, error } = await client
|
|
.from('oauth_client_registrations')
|
|
.select('id')
|
|
.eq('redirect_uri', uri)
|
|
.is('revoked_at', null)
|
|
.limit(1)
|
|
.maybeSingle()
|
|
|
|
if (error) return false
|
|
return data !== null
|
|
}
|