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.
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
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)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user