Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
27 changes: 23 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 @@
/**
* 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

Check warning on line 310 in supabase/functions/_backend/utils/supabase.ts

View workflow job for this annotation

GitHub Actions / Run tests

Expected @param names to be "c, key, orgId, _supabase". Got "_supabase"
* 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 @@
): 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,19 @@
return { valid: false, error: 'org_requires_expiring_key' }
}

if (org.max_apikey_expiration_days && key.expires_at) {
const createdAt = key.created_at ? new Date(key.created_at) : null
const expiresAt = new Date(key.expires_at)
if (!createdAt || Number.isNaN(createdAt.getTime()) || Number.isNaN(expiresAt.getTime())) {
return { valid: false, error: 'expiration_exceeds_max' }
}
const maxDate = new Date(createdAt)
maxDate.setDate(maxDate.getDate() + org.max_apikey_expiration_days)
if (expiresAt > maxDate) {
return { valid: false, error: 'expiration_exceeds_max' }
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

return { valid: true }
}

Expand Down
Loading
Loading