Files
accounted/lib/auth/__tests__/oauth-allowlist.test.ts
T
Mattsson 16164ea14c Fix/mcp fixes and bugs (#518)
* 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.
2026-05-18 19:02:42 +02:00

63 lines
2.2 KiB
TypeScript

import { describe, it, expect, vi } from 'vitest'
import { isBuiltInRedirectUri, isAllowedRedirectUri } from '../oauth-allowlist'
import type { SupabaseClient } from '@supabase/supabase-js'
describe('isBuiltInRedirectUri', () => {
it.each([
['https://claude.ai/api/oauth/callback', true],
['https://claude.com/api/oauth/callback', true],
['http://localhost:3000/cb', true],
['http://localhost/cb', true],
['http://127.0.0.1:8080/cb', true],
['https://evil.com/cb', false],
['https://example.com/api/foo', false],
['ftp://localhost/cb', false],
['', false],
])('classifies %s as %s', (uri, expected) => {
expect(isBuiltInRedirectUri(uri)).toBe(expected)
})
})
function makeFakeSupabase(rows: Array<{ id: string }>): SupabaseClient {
// Chainable thenable that resolves to { data, error } when awaited via
// .maybeSingle(). Matches the shape isAllowedRedirectUri actually invokes.
const chain = {
from() { return chain },
select() { return chain },
eq() { return chain },
is() { return chain },
limit() { return chain },
async maybeSingle() {
return { data: rows[0] ?? null, error: null }
},
}
return chain as unknown as SupabaseClient
}
describe('isAllowedRedirectUri', () => {
it('short-circuits to true for built-in patterns without touching the DB', async () => {
const sb = {
from: vi.fn(() => {
throw new Error('should not be called')
}),
} as unknown as SupabaseClient
expect(await isAllowedRedirectUri('https://claude.ai/api/cb', sb)).toBe(true)
expect(await isAllowedRedirectUri('http://localhost:3000/cb', sb)).toBe(true)
})
it('returns true when the DB has a registration for the URI', async () => {
const sb = makeFakeSupabase([{ id: 'reg-1' }])
expect(await isAllowedRedirectUri('https://myapp.example.com/cb', sb)).toBe(true)
})
it('returns false when no registration exists', async () => {
const sb = makeFakeSupabase([])
expect(await isAllowedRedirectUri('https://evil.com/cb', sb)).toBe(false)
})
it('returns false for empty / non-string inputs', async () => {
expect(await isAllowedRedirectUri('')).toBe(false)
expect(await isAllowedRedirectUri(undefined as unknown as string)).toBe(false)
})
})