Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions supabase/functions/_backend/public/webhooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ async function assertWebhookOrgPolicy(
if (orgCheck.error === 'org_requires_expiring_key') {
throw quickError(401, 'org_requires_expiring_key', 'This organization requires API keys with an expiration date. Please use a different key or update this key with an expiration date.')
}
if (orgCheck.error === 'expiration_exceeds_max') {
throw quickError(401, 'expiration_exceeds_max', 'API key expiration exceeds this organization\'s maximum allowed validity window. Please use a different key or update this key with a shorter expiration date.')
}

throw simpleError('invalid_org_id', 'You can\'t access this organization', { org_id: orgId })
}
Expand Down
22 changes: 18 additions & 4 deletions supabase/functions/_backend/utils/supabase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,20 +306,26 @@ export function apikeyHasOrgRight(key: Database['public']['Tables']['apikeys']['
/**
* Check if API key has org access AND meets org's API key policy requirements
* Returns { valid: true } if all checks pass, or { valid: false, error: string } if not
*
* @param _supabase Deprecated compatibility parameter; policy lookups use
* supabaseAdmin(c) after local org-scope validation so RBAC denials do not hide
* the org policy row.
*/
export async function apikeyHasOrgRightWithPolicy(
c: Context,
key: Database['public']['Tables']['apikeys']['Row'],
orgId: string,
supabase: SupabaseClient<Database>,
_supabase: SupabaseClient<Database>,
): Promise<{ valid: boolean, error?: string }> {
// First check basic org access
if (!apikeyHasOrgRight(key, orgId)) {
return { valid: false, error: 'invalid_org_id' }
}

// Then check if org requires expiring keys
const policyCheck = await checkApikeyMeetsOrgPolicy(c, key, orgId, supabase)
// Then check if org requires expiring keys. The scope check above proves the
// key is org-scoped; use service role for the policy lookup so runtime
// permission denials for non-expiring keys do not hide the policy row.
const policyCheck = await checkApikeyMeetsOrgPolicy(c, key, orgId, supabaseAdmin(c))
if (!policyCheck.valid) {
return policyCheck
}
Expand Down Expand Up @@ -1688,7 +1694,7 @@ export async function checkApikeyMeetsOrgPolicy(
): Promise<{ valid: boolean, error?: string }> {
const { data: org, error } = await supabase
.from('orgs')
.select('require_apikey_expiration')
.select('require_apikey_expiration, max_apikey_expiration_days')
.eq('id', orgId)
.single()

Expand All @@ -1706,6 +1712,14 @@ export async function checkApikeyMeetsOrgPolicy(
return { valid: false, error: 'org_requires_expiring_key' }
}

if (org.max_apikey_expiration_days && key.expires_at) {
const maxDate = new Date()
maxDate.setDate(maxDate.getDate() + org.max_apikey_expiration_days)
if (new Date(key.expires_at) > maxDate) {
return { valid: false, error: 'expiration_exceeds_max' }
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

return { valid: true }
}

Expand Down
Loading
Loading