-
-
Notifications
You must be signed in to change notification settings - Fork 67
feat: RBAC system v1 #1429
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat: RBAC system v1 #1429
Conversation
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. 📝 WalkthroughWalkthroughAdds a new RBAC system: DB schema, SQL RPCs, backend RBAC utilities/middleware, private REST endpoints for groups/roles/bindings, frontend permission service and async guards, UI components for access management, translations, seeds, and SQL tests. Hybrid legacy↔RBAC gating via org/global flags. Changes
Sequence Diagram(s)mermaid Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 12
Note
Due to the large number of review comments, Critical severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (22)
supabase/functions/_backend/public/channel/delete.ts (1)
50-55: Delete operation result is not checked—potential silent failure.The delete query result is not captured or validated. If the deletion fails due to RLS policies, foreign key constraints, or database errors, the function will still return a success response, misleading the caller.
🔧 Proposed fix to check delete result
- await supabaseApikey(c, apikey.key) + const { error: deleteError } = await supabaseApikey(c, apikey.key) .from('channels') .delete() .eq('app_id', body.app_id) .eq('name', body.channel) + if (deleteError) { + throw simpleError('cannot_delete_channel', 'Failed to delete channel', { supabaseError: deleteError }) + } return c.json(BRES)planetscale/replicate_to_planetscale.sh (1)
80-88: Stop logging DB credentials (password is printed directly, and also via echoed SQL).
Line 81 printsSOURCE_PASSWORD, and the subscription SQL echoed at Line 185 includes the password; this is a secrets leak risk.Proposed fix (mask/remove sensitive logging)
echo "SOURCE_USER: $SOURCE_USER" -echo "SOURCE_PASSWORD: $SOURCE_PASSWORD" +echo "SOURCE_PASSWORD: [redacted]" echo "HOST: $SOURCE_HOST" echo "PORT: $SOURCE_PORT" echo "DB: $SOURCE_DB" # exit() # Restore file DUMP_FILE='data_replicate.dump'echo "Subscription creation SQL:" -echo "$SQL_QUERY_SUB" +echo "[redacted: subscription SQL contains credentials]" psql-17 "$TARGET_DB_URL" -v ON_ERROR_STOP=1 <<SQL $SQL_QUERY_SUB SQLsupabase/functions/_backend/utils/pg.ts (1)
187-204: Don’t log rawdbUrl(it can contain DB credentials).This is a high-risk secret leak in logs; redact user/password at minimum.
Proposed fix (redact credentials)
export function getPgClient(c: Context, readOnly = false) { const dbUrl = getDatabaseURL(c, readOnly) const requestId = c.get('requestId') const appName = c.res.headers.get('X-Worker-Source') ?? 'unknown source' const dbName = c.res.headers.get('X-Database-Source') ?? 'unknown source' - cloudlog({ requestId, message: 'SUPABASE_DB_URL', dbUrl, dbName, appName }) + let safeDbUrl = dbUrl + try { + const u = new URL(dbUrl) + if (u.username) + u.username = 'REDACTED' + if (u.password) + u.password = 'REDACTED' + safeDbUrl = u.toString() + } + catch { + // ignore parse errors; keep minimal info + safeDbUrl = 'UNPARSEABLE_DB_URL' + } + cloudlog({ requestId, message: 'SUPABASE_DB_URL', dbUrl: safeDbUrl, dbName, appName }) const isPooler = dbName.startsWith('sb_pooler') const options = { connectionString: dbUrl,messages/en.json (1)
153-155: Fix duplicate translation keys in messages/en.json (JSON last-write-wins behavior).This file contains duplicate keys (
no-organization-selected,date,create) that will silently override earlier definitions and can break i18n tooling or export flows. Remove the duplicate entries from the later block:Minimal fix
- "no-organization-selected": "No organization selected", "assigned": "Assigned", "removed": "Removed", "event": "Event", "by": "By", - "date": "Date", "bundle-deployed": "Bundle Deployed",Also note:
createappears twice with different values ("create" vs "Create" at lines 498 and 1320), and there is a case-sensitive collision risk withaccessvsAccess(lines 1482 and 1516).supabase/functions/_backend/public/build/start.ts (1)
43-75: Handle hashed API keys:apikey.key!can be null → runtime crash.
checkPermissionexplicitly supports hashed keys by falling back toc.get('capgkey'), but this function still doesconst apikeyKey = apikey.key!and uses it for DB updates. With hashed keys, that can benulland will blow up / skip marking builds failed.Proposed diff
export async function startBuild( c: Context, jobId: string, appId: string, apikey: Database['public']['Tables']['apikeys']['Row'], ): Promise<Response> { let alreadyMarkedAsFailed = false - const apikeyKey = apikey.key! + // `apikey.key` can be null for hashed keys; use the raw key from request context. + const apikeyKey = apikey.key ?? c.get('capgkey') + if (!apikeyKey) { + cloudlogErr({ + requestId: c.get('requestId'), + message: 'Missing API key in context (cannot update build_requests)', + job_id: jobId, + app_id: appId, + user_id: apikey.user_id, + }) + throw simpleError('not_authorized', 'Missing API key') + } try { @@ if (!(await checkPermission(c, 'app.build_native', { appId }))) { const errorMsg = 'You do not have permission to start builds for this app' @@ await markBuildAsFailed(c, jobId, errorMsg, apikeyKey)supabase/functions/_backend/public/build/cancel.ts (1)
23-67: Handle hashed API keys when updatingbuild_requestsafter cancel.The RBAC check supports hashed keys, but the DB update path still uses
supabaseApikey(c, apikey.key)which may be null for hashed keys (same issue asstartBuild).Proposed diff
// Update build_requests status to cancelled // Use authenticated client for data queries - RLS will enforce access - const supabase = supabaseApikey(c, apikey.key) + const apikeyKey = apikey.key ?? c.get('capgkey') + if (!apikeyKey) { + cloudlogErr({ + requestId: c.get('requestId'), + message: 'Missing API key in context (cannot update build_requests)', + job_id: jobId, + app_id: appId, + user_id: apikey.user_id, + }) + throw simpleError('not_authorized', 'Missing API key') + } + const supabase = supabaseApikey(c, apikeyKey)supabase/functions/_backend/private/download_link.ts (1)
1-21: UsecreateHonoto initialize the Hono app, but the proposed fix is incomplete.The guideline requires using
createHonofromutils/hono.tsfor Hono initialization. However,createHono(functionName: string, version: string, sentryDsn?: string)requires at least two parameters (functionNameandversion). The proposed diff calls it with no arguments, which will fail at runtime.Additionally, the actual codebase pattern uses
honoFactory.createApp()(as seen in other backend files), which differs from this guideline requirement.Corrected approach needed
Option 1: Use
createHonowith proper parameters:-import { Hono } from 'hono/tiny' +import { createHono } from '../utils/hono.ts' import { middlewareAuth, parseBody, simpleError, useCors } from '../utils/hono.ts' -export const app = new Hono<MiddlewareKeyVariables>() +export const app = createHono('download_link', '1.0.0')Option 2: Align with actual backend pattern:
-import { Hono } from 'hono/tiny' +import { honoFactory } from '../utils/hono.ts' -import { middlewareAuth, parseBody, simpleError, useCors } from '../utils/hono.ts' +import { honoFactory, middlewareAuth, parseBody, simpleError, useCors } from '../utils/hono.ts' -export const app = new Hono<MiddlewareKeyVariables>() +export const app = honoFactory.createApp()Verify which approach aligns with the intended guideline requirement.
supabase/functions/_backend/private/delete_failed_version.ts (2)
20-25: Do not logapikey/capgkey(secret leakage)
These logs can leak credentials into log storage and third-party sinks.Proposed change
const apikey = c.get('apikey') const capgkey = c.get('capgkey') as string - cloudlog({ requestId: c.get('requestId'), message: 'apikey', apikey }) - cloudlog({ requestId: c.get('requestId'), message: 'capgkey', capgkey }) + cloudlog({ + requestId: c.get('requestId'), + message: 'auth context', + hasApikey: !!apikey, + capgkeyPrefix: capgkey ? capgkey.slice(0, 8) : null, + })
18-35: Move body validation before RBAC check (avoid “401” for malformed requests)
Right now a missingapp_id/namewon’t hit the 400s becausecheckPermissionexecutes first.Proposed change
app.delete('/', middlewareKey(['all', 'write', 'upload']), async (c) => { const body = await parseBody<DataUpload>(c) cloudlog({ requestId: c.get('requestId'), message: 'delete failed version body', body }) + + if (!body.app_id) { + return quickError(400, 'error_app_id_missing', 'Error app_id missing', { body }) + } + if (!body.name) { + return quickError(400, 'error_bundle_name_missing', 'Error bundle name missing', { body }) + } @@ - // Auth context is already set by middlewareKey - if (!(await checkPermission(c, 'bundle.delete', { appId: body.app_id }))) { + // Auth context is already set by middlewareKey + if (!(await checkPermission(c, 'bundle.delete', { appId: body.app_id }))) { return quickError(401, 'not_authorized', 'You can\'t access this app', { app_id: body.app_id }) } @@ - if (!body.app_id) { - return quickError(400, 'error_app_id_missing', 'Error bundle name missing', { body }) - } - if (!body.name) { - return quickError(400, 'error_bundle_name_missing', 'Error bundle name missing', { body }) - }Also applies to: 45-50
supabase/functions/_backend/private/upload_link.ts (1)
23-36: Do not logapikey/capgkey(secret leakage) + validatebody.app_idbefore RBAC checkProposed change
const apikey = c.get('apikey') as Database['public']['Tables']['apikeys']['Row'] const capgkey = c.get('capgkey') as string - cloudlog({ requestId: c.get('requestId'), message: 'apikey', apikey }) - cloudlog({ requestId: c.get('requestId'), message: 'capgkey', capgkey }) + cloudlog({ + requestId: c.get('requestId'), + message: 'auth context', + apikeyId: apikey?.id ?? null, + capgkeyPrefix: capgkey ? capgkey.slice(0, 8) : null, + }) + + if (!body.app_id) { + return quickError(400, 'error_app_id_missing', 'Error app_id missing', { body }) + } @@ // Auth context is already set by middlewareKey if (!(await checkPermission(c, 'app.upload_bundle', { appId: body.app_id }))) { throw simpleError('app_access_denied', 'You can\'t access this app', { app_id: body.app_id }) }src/components/tables/HistoryTable.vue (1)
416-472: Server-side permission enforcement for rollback is insufficientThe RLS UPDATE policy on the
channelstable uses genericcheck_min_rights('write')permission, while the frontend enforces a specificchannel.rollback_bundlepermission. A user with 'write' access but without thechannel.rollback_bundlepermission can bypass the UI check by calling Supabase directly:supabase.from('channels').update({version: item.version_id}).eq('id', channelId)The RLS policy will allow this since it only validates 'write' permission, not the specific rollback permission. Update the RLS policy to enforce the RBAC permission explicitly using
rbac_check_permission_direct('channel.rollback_bundle', ...)for version updates, or create a more restrictive trigger that blocks non-authorized version changes.src/pages/app/[package].channel.[channel].vue (1)
448-475: Fix local channel state update guard inonSelectAutoUpdate.The assignment currently won’t run if
disable_auto_updatewas falsy; it should update wheneverchannel.valueexists.Proposed fix
- if (channel.value?.disable_auto_update) - channel.value.disable_auto_update = value + if (channel.value) + channel.value.disable_auto_update = valuesupabase/functions/_backend/public/organization/delete.ts (2)
69-73: Usecapgkey(original key string) forsupabaseApikeyto support hashed keys safely.If
apikey.keyisnullfor hashed keys, this call can break auth (or behave unexpectedly). Other endpoints in this PR usec.get('capgkey')for this reason.Proposed fix
- const { error } = await supabaseApikey(c, apikey.key) + const { error } = await supabaseApikey(c, c.get('capgkey') as string) .from('orgs') .delete() .eq('id', orgId)
26-67: Re-checksupabaseAdminusage in a public endpoint (least privilege).This is user-triggered deletion code; using the admin client for storage deletion is high impact if anything upstream is bypassed. If this must remain, consider adding extra guardrails (defense-in-depth) and ensure logs don’t leak sensitive paths.
supabase/functions/_backend/public/organization/members/get.ts (2)
14-31: Remove PII logs (cloudlograw members data.
cloudlog(..., data)andcloudlog(..., data: parsed.data)will emit user emails (and possibly more) into logs.Proposed fix
- cloudlog({ requestId: c.get('requestId'), message: 'data', data, error }) + cloudlog({ requestId: c.get('requestId'), message: 'get_org_members result', hasData: !!data, hasError: !!error }) @@ - cloudlog({ requestId: c.get('requestId'), message: 'Members', data: parsed.data }) + cloudlog({ requestId: c.get('requestId'), message: 'Members fetched', count: parsed.data.length })Also applies to: 53-63
14-31: Update schema to handleimage_urlas optional.The RPC return for pending invitations sets
image_urlto an empty string, but for regular members it usesusers.image_urlwhich may be nullable.z.string()will fail validation if the value is null, causing a 400 error. Update toz.string().optional().Note:
supabaseApikey()already supports hashed keys via fallback toc.get('capgkey')whenapikey.keyis null, so no change needed there.supabase/functions/_backend/public/organization/members/post.ts (1)
22-34: Potential null safety issue withcapgkeytype assertion.The parameter is correctly renamed to
_apikeyto indicate it's unused. However, line 34 usesc.get('capgkey') as stringwhich could beundefinedbased on theMiddlewareKeyVariablesinterface wherecapgkey?: stringis optional.Consider adding a null check or using a fallback:
Suggested improvement
- const supabase = supabaseApikey(c, c.get('capgkey') as string) + const capgkey = c.get('capgkey') + if (!capgkey) { + throw simpleError('missing_api_key', 'API key is required') + } + const supabase = supabaseApikey(c, capgkey)supabase/functions/_backend/public/app/get.ts (2)
37-96: Blocker:simpleErroris used but not imported in this file.Line 58/78/93 call
simpleError(...), but the imports (Line 4) only includequickError. This should fail typecheck/build.Proposed fix
-import { quickError } from '../../utils/hono.ts' +import { quickError, simpleError } from '../../utils/hono.ts'
16-34: Hashed API keys likely break app fetch path.
checkPermissionexplicitly supports hashed keys by usingc.get('capgkey')whenapikey.keyis null, but Line 24 usessupabaseApikey(c, apikey.key). Ifapikey.keyis null, this will likely fail to query.Proposed fix
- const { data, error: dbError } = await supabaseApikey(c, apikey.key) + const apikeyString = apikey.key ?? c.get('capgkey') + if (!apikeyString) { + throw quickError(401, 'invalid_apikey', 'Missing API key') + } + + const { data, error: dbError } = await supabaseApikey(c, apikeyString) .from('apps') .select('*') .eq('app_id', appId) .single()supabase/functions/_backend/public/organization/members/delete.ts (2)
38-47: Don’t assumecapgkeyexists; derive apikey string safely.Line 39 (
c.get('capgkey') as string) can beundefinedat runtime; the assertion only silences types. Use the same apikey-string derivation pattern ascheckPermission(auth apikey key or capgkey) and fail fast if missing.Proposed fix
- const supabase = supabaseApikey(c, c.get('capgkey') as string) + const apikeyString = c.get('auth')?.apikey?.key ?? c.get('capgkey') + if (!apikeyString) { + throw simpleError('invalid_apikey', 'Missing API key') + } + const supabase = supabaseApikey(c, apikeyString)
27-36: AvoidsupabaseAdminin user-facing APIs (service key); prefer a constrained RPC.Per repo guidelines, using the admin SDK/service key in user-facing endpoints should be avoided. Here it’s used to look up a user by email (Line 28-32). Consider a SECURITY DEFINER SQL function like
get_user_id_by_email_in_org(org_id, email)(or similar) that:
- checks caller’s org permission / membership server-side
- returns only a UUID (or null)
Then call it via the authenticated client (supabaseApikey).src/pages/settings/organization/Members.vue (1)
299-416: RBAC invites will fail—RBAC role names are not valid in theuser_min_rightenum.When RBAC is enabled, the frontend passes
org_member,org_admin,org_billing_admin, ororg_super_admindirectly tosendInvitation(), which calls theinvite_user_to_orgRPC. However, that RPC expects theuser_min_rightenum type, which only contains legacy values:read,upload,write,admin,super_admin,invite_read,invite_upload,invite_write,invite_admin,invite_super_admin. RBAC role names are not part of this enum and will cause a type error at runtime.The
as anycast inhandlePermissionSelectionmasks the mismatch at compile time but does not resolve the underlying incompatibility.
🤖 Fix all issues with AI agents
In @RBAC_SYSTEM.md:
- Around line 1-35: CI flags French words in RBAC_SYSTEM.md ("ressources",
"Fonctions", "supporte") causing the pipeline to fail; fix by either translating
the document to English or updating the typo allowlist: add these exact French
tokens (and any other valid French terms in the doc) to the .typos.toml
allowlist/extend-words entry so the linter ignores them, ensuring you update the
repository's .typos.toml rather than changing the RBAC_SYSTEM.md content unless
you choose translation.
- Around line 801-915: The function rbac_check_permission_direct currently
accepts p_user_id and is executable by the authenticated role, allowing clients
to probe other users' permissions; fix by validating the caller: at the start of
rbac_check_permission_direct add a check that if p_user_id IS NOT NULL AND
p_user_id != auth.uid() then RETURN false; alternatively implement a public
wrapper without the p_user_id parameter that calls the internal
rbac_check_permission_direct (which you then restrict to service_role only) so
client-facing RPCs cannot supply arbitrary user IDs; update permission grants
accordingly.
In @src/components/dashboard/AppAccess.vue:
- Line 73: The file uses the Ref type (e.g., Ref<TableColumn[]>) for the columns
constant but did not import Ref from Vue; add an import for Ref alongside
existing Vue imports (so the Ref type is available) and ensure the columns
declaration remains: const columns: Ref<TableColumn[]> = ref<TableColumn[]>([
... ]). This fixes the missing type reference for Ref used with TableColumn and
the columns symbol.
In @src/pages/settings/organization/Members.vue:
- Around line 82-101: The function checkRbacEnabled is missing its closing
brace, causing subsequent top-level declarations like isInviteNewUserDialogOpen
to be nested inside the function; fix by inserting the missing closing "}"
immediately after the catch block (or before the line declaring const
isInviteNewUserDialogOpen = ref(false)) so checkRbacEnabled() properly ends and
isInviteNewUserDialogOpen remains a top-level reactive ref; verify no other
braces are unbalanced in the surrounding script.
In @src/stores/organization.ts:
- Around line 30-75: The file is missing three exports referenced by
auto-imports: implement and export LEGACY_TO_RBAC_ROLE_MAPPING (reverse of
RBAC_TO_LEGACY_ROLE_MAPPING, mapping each legacy OrganizationRole to the array
of RbacRoleName(s) that include it—ensure 'owner' remains org-scoped only), add
RBAC_ORG_ROLE_DISPLAY_NAMES as a Record<RbacRoleName, string> with
human-friendly labels for each RBAC role, and implement
getRbacRoleDisplayName(role: string): string | undefined to return
RBAC_ORG_ROLE_DISPLAY_NAMES[role]; ensure all three are exported and types match
the existing RbacRoleName and OrganizationRole usages and that names exactly
match LEGACY_TO_RBAC_ROLE_MAPPING, RBAC_ORG_ROLE_DISPLAY_NAMES, and
getRbacRoleDisplayName so auto-imports compile.
In @supabase/functions/_backend/utils/hono_middleware.ts:
- Around line 26-105: middlewareRbacContext has three issues: replace direct
c.req.json() with cloning (use c.req.raw.clone().json()) before parsing so
downstream handlers can still read the body; change the resolver handling from
resolved instanceof Promise ? await resolved : resolved to await
Promise.resolve(options.orgIdResolver(c)) (or await Promise.resolve(...)) to
safely handle thenables; and await the closeClient(...) calls after both DB
usages (the block resolving app org and the RBAC-enabled check) because
closeClient returns a Promise and must be awaited to avoid leaking connections
when using getPgClient/getDrizzleClient.
In @supabase/functions/_backend/utils/rbac.ts:
- Around line 35-81: The Permission union type and the
PERMISSION_TO_LEGACY_RIGHT mapping in rbac.ts are missing the
'app.update_user_roles' entry (the DB/migrations and helper
user_has_app_update_user_roles() expect it), causing a TypeScript error; add
'app.update_user_roles' to the exported Permission type and add an entry in
PERMISSION_TO_LEGACY_RIGHT mapping that maps 'app.update_user_roles' -> 'admin'
to match other app-level admin permissions like 'app.delete' and
'app.read_audit'.
In @supabase/seed.sql:
- Around line 544-547: Replace the stray semicolon that terminates the VALUES
list after the tuple starting with (13, NOW(), 'com.stats.app', '1.0.0', ...)
with a comma so the subsequent tuples (14, …), (15, …), (16, …) are part of the
same INSERT VALUES list, and ensure the final tuple (16, …) ends the statement
with a single semicolon.
- Around line 482-485: The VALUES block is malformed: a stray semicolon ends the
first tuple and the following tuples are left dangling. Replace the semicolon
after the first tuple with a comma (or prepend a single INSERT INTO ... VALUES
(...) before the first tuple if the INSERT is missing), ensure all tuples are
comma-separated, and terminate the final tuple with a single semicolon; in
short, make the rows with the 'user_min_right'::"public"."user_min_right" casts
(e.g., tuples containing UUIDs like 'f6a7b8c9-d0e1-4f2a-9b3c-4d5e6f7a8b92') part
of one well-formed INSERT INTO ... VALUES (...) , (...) , (...) ; statement.
🟠 Major comments (21)
tests/events.test.ts-3-3 (1)
3-3: UsegetEndpointUrl()instead ofBASE_URLin tests (per repo testing guidelines).
Line 3 importsBASE_URL, and the file builds URLs via${BASE_URL}/...; this can break Cloudflare Workers routing expectations. As per coding guidelines, prefergetEndpointUrl(path).Proposed fix (switch to getEndpointUrl)
-import { APP_NAME, BASE_URL, getSupabaseClient, headers, resetAndSeedAppData, resetAndSeedAppDataStats, resetAppData, resetAppDataStats, USER_EMAIL } from './test-utils.ts' +import { APP_NAME, getEndpointUrl, getSupabaseClient, headers, resetAndSeedAppData, resetAndSeedAppDataStats, resetAppData, resetAppDataStats, USER_EMAIL } from './test-utils.ts'- const response = await fetch(`${BASE_URL}/private/events`, { + const response = await fetch(getEndpointUrl('/private/events'), { method: 'POST', headers: { capgkey: headers.Authorization, },(Apply the same replacement for the other
${BASE_URL}/private/eventsoccurrences in this file.)messages/de.json-1454-1463 (1)
1454-1463: Add missing RBAC translation keys to de.json (and other locales).This hunk adds only the role labels at lines 1454–1463. The verification reveals de.json is missing 58 keys found in en.json, including critical RBAC strings:
app-access-control,assign-app-role,assign-role,error-assigning-role,error-removing-role,error-role-already-assigned,no-role-bindings,principal-type,role-assigned,role-assignments,role-assignments-description,role-removed,scope,scope-type, and others. If the UI references these keys, they will render as raw key names in German.This gap exists across most locales (es.json, hi.json, id.json, it.json, ja.json, ko.json, pl.json, pt-br.json, ru.json, tr.json, vi.json, zh-cn.json also missing 58 keys; fr.json missing 21).
supabase/functions/_backend/utils/postgres_schema.ts-164-169 (1)
164-169: Add explicit composite primary key togroup_memberstable in Drizzle schema.The
group_memberstable definition lacks aprimaryKey()declaration. The actual database migration definesPRIMARY KEY (group_id, user_id), but the Drizzle schema must explicitly declare this:Required fix
export const group_members = pgTable('group_members', { group_id: uuid('group_id').notNull(), user_id: uuid('user_id').notNull(), added_by: uuid('added_by'), added_at: timestamp('added_at').notNull().defaultNow(), }, (table) => ({ pk: primaryKey({ columns: [table.group_id, table.user_id] }), }))supabase/functions/_backend/private/roles.ts-1-13 (1)
1-13: UsecreateHonoinstead of instantiatingHonodirectly.All backend files must use
createHonofor Hono app initialization. This sets up essential middleware for Sentry error tracking, request logging, request ID generation (Cloudflare Ray ID, Supabase Execution ID, or UUID), and error handling. Direct instantiation withnew Hono()bypasses these.Replace
export const app = new Hono<MiddlewareKeyVariables>()with an appropriate call tocreateHono('roles', <version>)(ensurefunctionNameandversionare provided).RBAC_SYSTEM.md-665-707 (1)
665-707: Legacy mapping likely references non-existent enum values (invite_*).
rbac_permission_for_legacy()mapspublic.user_min_rightvalues likeinvite_super_admin,invite_admin,invite_write,invite_upload(Line 679-682, 687-690, 695-698). Based on learnings,user_min_rightisread, upload, write, admin, super_adminonly, so this section may be wrong/outdated and should be aligned to the actual enum values. Based on learnings, avoid introducing “owner” semantics into app-level checks as well.RBAC_SYSTEM.md-189-259 (1)
189-259: Doc correctness: conflicting schemas/types + scope examples don’t line up.
role_bindingsis defined once withscope_type,channel_id uuid REFERENCES channels(rbac_id), audit fields, etc. (Line 193-216), but later you show a simplifiedCREATE TABLE role_bindings (...)that conflicts (e.g.,channel_id bigint REFERENCES channels(id)and missing scope/audit columns) (Line 1239-1247). Pick one canonical definition (ideally the real migration schema) and delete/annotate the simplified one.- Bundle permissions are described as “scope: app” (Line 587-596) while later “Bundle permissions (scope: 'bundle')” (Line 1182-1186) — make this consistent with the
permissions.scope_typeCHECK (Line 158-161) and actual implementation.- Best-practices snippet uses
{ appId, bundleId }(Line 1906-1907) but the documentedPermissionScopeonly has orgId/appId/channelId (Line 970-974). Update the example or the scope type.Also applies to: 587-596, 1208-1247, 1901-1907
supabase/functions/_backend/private/devices.ts-1-7 (1)
1-7: UsecreateHonoper backend guidelines (instead ofnew Hono)
This repo’s backend guidelines requirecreateHonofromutils/hono.tsfor app initialization.Proposed change
import type { MiddlewareKeyVariables } from '../utils/hono.ts' import type { Order } from '../utils/types.ts' -import { Hono } from 'hono/tiny' -import { parseBody, simpleError, useCors } from '../utils/hono.ts' +import { createHono, parseBody, simpleError, useCors } from '../utils/hono.ts' import { middlewareV2 } from '../utils/hono_middleware.ts' import { cloudlog } from '../utils/logging.ts' import { checkPermission } from '../utils/rbac.ts' import { countDevices, readDevices } from '../utils/stats.ts' @@ -export const app = new Hono<MiddlewareKeyVariables>() +export const app = createHono<MiddlewareKeyVariables>()Also applies to: 25-25
src/components/tables/HistoryTable.vue-47-51 (1)
47-51: Likely incorrect scope: passchannelIdforchannel.rollback_bundle
Using only{ appId }for a channel-scoped permission risks over-authorizing if your RBAC differentiates per-channel access.Proposed change
const canRollbackBundle = computedAsync(async () => { if (!props.appId) return false - return await hasPermission('channel.rollback_bundle', { appId: props.appId }) + return await hasPermission('channel.rollback_bundle', { + appId: props.appId, + channelId: props.channelId, + }) }, false)supabase/functions/_backend/files/files.ts-380-381 (1)
380-381: Use'app.upload_bundle'instead of'app.read_bundles'for upload access.The
checkWriteAppAccessfunction guards upload endpoints (POST, PATCH at lines 461, 466) and is named for write access validation, but it checks the'app.read_bundles'permission (line 381). The RBAC system defines'app.upload_bundle'(upload level) as the correct permission for uploads. Other upload endpoints likeupload_link.tscorrectly use'app.upload_bundle'. Using the read permission allows users with only read access to perform uploads, bypassing intended access controls.Change line 381 from:
const hasPermission = await checkPermissionPg(c, 'app.read_bundles', { appId: app_id }, drizzleClient, userId, capgkey)to:
const hasPermission = await checkPermissionPg(c, 'app.upload_bundle', { appId: app_id }, drizzleClient, userId, capgkey)src/pages/app/[package].access.vue-21-33 (1)
21-33: Avoid stale “app found” state: clearappon refresh and handle Supabase errors explicitly.
app.value = dataApp || app.valuekeeps old data when the next app doesn’t exist (or query fails), so the UI can incorrectly renderAccessTablefor the previous app. Also.single()errors are ignored.Proposed fix
async function loadAppInfo() { try { - const { data: dataApp } = await supabase + const { data: dataApp, error } = await supabase .from('apps') .select() .eq('app_id', id.value) .single() - app.value = dataApp || app.value + if (error) { + console.error('Error loading app info:', error) + app.value = undefined + return + } + app.value = dataApp ?? undefined } catch (error) { console.error('Error loading app info:', error) + app.value = undefined } } async function refreshData() { isLoading.value = true try { + app.value = undefined await loadAppInfo() } catch (error) { console.error('Error refreshing data:', error) } finally { isLoading.value = false } }Also applies to: 35-46
supabase/tests/35_test_is_admin_rbac.sql-22-65 (1)
22-65: Make the test idempotent: protectrole_bindingsinsert against duplicates and assert prerequisites.If
public.role_bindingshas a uniqueness constraint for the binding, this can hard-fail the test run (not just anok(false)).Possible adjustment
-- 5) Grant platform_super_admin role to test_admin +SELECT ok( + EXISTS (SELECT 1 FROM public.roles WHERE name = 'platform_super_admin'), + 'platform_super_admin role exists' +); + INSERT INTO public.role_bindings ( @@ FROM public.roles r WHERE r.name = 'platform_super_admin'; +-- If a unique constraint exists for this binding, keep the test idempotent: +-- ON CONFLICT DO NOTHING;src/pages/app/[package].channel.[channel].vue-47-57 (1)
47-57: UsechannelIdinstead ofappIdfor channel permission checks—the backend auto-derivesappIdandorgId.Lines 50 and 56 should pass
{ channelId: id.value }not{ appId: packageId.value }. The permission service documentation explicitly shows channel-scoped permissions are checked viachannelId, and the backend automatically derives parent scopes.Affected locations
- Lines 47–57:
canUpdateChannelSettingsandcanPromoteBundlecomputed definitions- Lines 138–143, 263–269, 292–296: Functions using these computed values
supabase/tests/34_test_rbac_rls.sql-23-36 (1)
23-36: Replace tautological assertions for roles/permissions reads.Line 26 and Line 33 (
COUNT(*) >= 0) will always pass even if RLS blocks everything (returns 0). Prefer asserting existence of known rows (seeded), or create a row as admin and assert the regular user can see that exact row.Proposed fix (example)
-- 3) Regular user can read roles SELECT tests.authenticate_as('test_user'); SELECT ok( - (SELECT COUNT(*) FROM public.roles) >= 0, + EXISTS (SELECT 1 FROM public.roles), 'Regular user can read roles' ); -- 4) Regular user can read permissions SELECT tests.authenticate_as('test_user'); SELECT ok( - (SELECT COUNT(*) FROM public.permissions) >= 0, + EXISTS (SELECT 1 FROM public.permissions), 'Regular user can read permissions' );supabase/functions/_backend/private/role_bindings.ts-11-12 (1)
11-12: Middleware registration may not cover all routes.Same issue as in
groups.ts- middleware registered on'/'may not apply to sub-routes like/:org_idor/:binding_id.Proposed fix
-app.use('/', useCors) -app.use('/', middlewareAuth) +app.use('*', useCors) +app.use('*', middlewareAuth)supabase/functions/_backend/private/role_bindings.ts-99-101 (1)
99-101: Missing validation forapikeyprincipal type.The code accepts
'apikey'as a validprincipal_typebut there's no validation logic to verify the API key exists or belongs to the organization, unlike the validation for'user'(lines 150-165) and'group'(lines 168-183).Proposed fix
Add validation for apikey principal type:
// Si principal_type est apikey, vérifier qu'il appartient à l'org if (principal_type === 'apikey') { const [apikey] = await drizzle .select() .from(schema.apikeys) .where( and( eq(schema.apikeys.id, principal_id), eq(schema.apikeys.owner_org, org_id), ), ) .limit(1) if (!apikey) { return c.json({ error: 'API key not found in this org' }, 400) } }supabase/functions/_backend/private/role_bindings.ts-122-124 (1)
122-124: String comparison for permission levels is unreliable.Same issue as in
groups.ts- using< 'admin'for string comparison doesn't reliably determine privilege levels.Proposed fix
- if (!orgAccess.length || orgAccess[0].user_right < 'admin') { + const adminRoles = ['admin', 'super_admin'] + if (!orgAccess.length || !adminRoles.includes(orgAccess[0].user_right)) { return c.json({ error: 'Forbidden - Admin rights required' }, 403) }Consider creating a shared helper function for permission checks to ensure consistency across all endpoints.
supabase/functions/_backend/private/groups.ts-94-96 (1)
94-96: String comparison for permission levels may not work correctly.The comparison
orgAccess[0].user_right < 'admin'uses lexicographic string comparison. Ifuser_rightis an enum like['read', 'upload', 'write', 'admin', 'super_admin'], this comparison won't correctly determine privilege levels. Consider comparing against an explicit list or using a helper function.Proposed fix
- if (!orgAccess.length || orgAccess[0].user_right < 'admin') { + const adminRoles = ['admin', 'super_admin'] + if (!orgAccess.length || !adminRoles.includes(orgAccess[0].user_right)) { return c.json({ error: 'Forbidden - Admin rights required' }, 403) }src/services/permissions.ts-209-221 (1)
209-221:checkPermissionsBatchkeying bypermissionwill corrupt results across scopes.
Example: two checks for'app.read'on different apps will collide and one overwrites the other. Use a composite key (permission + scope) or return an array preserving order.One possible fix (stable composite key)
export async function checkPermissionsBatch( checks: Array<{ permission: Permission, scope: PermissionScope }>, ): Promise<Record<string, boolean>> { const results = await Promise.all( checks.map(async ({ permission, scope }) => ({ - key: permission, + key: `${permission}:${JSON.stringify(scope)}`, allowed: await hasPermission(permission, scope), })), ) return Object.fromEntries( results.map(({ key, allowed }) => [key, allowed]), ) }supabase/functions/_backend/utils/rbac.ts-170-207 (1)
170-207:isRbacEnabledForOrg()cache is incorrect across multiple orgIds in the same request.
It caches a single boolean inc.set('rbacEnabled', enabled)without tying it toorgId, so a second call with a different orgId may reuse the wrong value.One possible fix (cache orgId + value)
export async function isRbacEnabledForOrg( c: Context<MiddlewareKeyVariables>, orgId: string | null, ): Promise<boolean> { - const cached = c.get('rbacEnabled') - if (cached !== undefined) { - return cached - } + const cached = c.get('rbacEnabled') + const cachedOrgId = c.get('resolvedOrgId') + if (cached !== undefined && cachedOrgId === orgId) { + return cached + } if (!orgId) { return false } let pgClient try { pgClient = getPgClient(c) const drizzleClient = getDrizzleClient(pgClient) const result = await drizzleClient.execute( sql`SELECT public.rbac_is_enabled_for_org(${orgId}::uuid) as enabled`, ) const enabled = (result.rows[0] as any)?.enabled === true - c.set('rbacEnabled', enabled) + c.set('rbacEnabled', enabled) + c.set('resolvedOrgId', orgId) return enabled }src/pages/settings/organization/Members.vue-238-271 (1)
238-271: RBAC member deletion is broken:aid: 0mapping prevents all RBAC members from being deleted.RBAC members are mapped with
aid: 0(line 252), butdeleteMember()explicitly blocks deletion whenaid === 0with the "cannot-delete-owner" error message. This makes every RBAC member undeletable. Additionally,_deleteMember()attempts to delete fromorg_userstable bymember.aid, which would also fail for RBAC members sinceaidis not populated from the RBAC data model.src/auto-imports.d.ts-11-15 (1)
11-15: Remove non-existent exports from auto-imports declarations.The following exports are declared in auto-imports.d.ts but do not exist in
src/stores/organization.ts:
LEGACY_TO_RBAC_ROLE_MAPPING(lines 11, 353)RBAC_ORG_ROLE_DISPLAY_NAMES(lines 12, 353)getRbacRoleDisplayName(line 46)Only
RBAC_TO_LEGACY_ROLE_MAPPING,RBAC_ORG_ROLE_I18N_KEYS,RBAC_ROLE_HIERARCHY, andgetRbacRoleI18nKeyare actually exported from organization.ts. Remove the non-existent entries from auto-imports.d.ts or add them to organization.ts if they are intended.
🟡 Minor comments (15)
messages/es.json-1454-1463 (1)
1454-1463: Spanish consistency: consider matching existing “Súper administrador” capitalization/diacritics.
role-org-super-adminis “Super Administrador”; elsewhere the UI uses “Súper administrador”.Proposed tweak
- "role-org-super-admin": "Super Administrador", + "role-org-super-admin": "Súper administrador",messages/en.json-1482-1487 (1)
1482-1487: Remove the unused capitalized"Access"key at line 1516; use the existing lowercase"access"key instead.The lowercase
"access"key (line 1482) is actively used in the codebase—it's referenced insrc/constants/appTabs.tsas a tab label and translated viat(tab.label)insrc/components/Tabs.vue. The capitalized"Access"key at line 1516 has no references in the codebase and should be removed to avoid confusion and accidental misuse.messages/ko.json-1454-1463 (1)
1454-1463: Korean “role-app-reader” translation is likely misleading (“리더” reads as “leader”).Consider changing
"role-app-reader": "앱 리더"to something unambiguous like “앱 뷰어”, “읽기 전용”, or “읽기 권한”.
Also consider"zip-bundle": "지프 앱 번들"→ “ZIP 앱 번들” for consistency with “ZIP” as a term.messages/tr.json-1454-1463 (1)
1454-1463: Consider a more natural Turkish label for “role-app-reader”.“Uygulama Okuyucu” is understandable, but “Uygulama Görüntüleyici” (or similar) may read more naturally for “read-only/viewer”.
supabase/functions/_backend/private/devices.ts-29-35 (1)
29-35: Addbody.appIdvalidation before callingcheckPermissionMissing
appIdcurrently results in a 403 authorization error instead of a 400-style input validation error. Following the established pattern in the codebase (e.g.,stripe_checkout.ts), validate the required field before permission checks to provide clearer error semantics.Proposed change
app.post('/', middlewareV2(['read', 'write', 'all', 'upload']), async (c) => { const body = await parseBody<DataDevice>(c) cloudlog({ requestId: c.get('requestId'), message: 'post devices body', body }) + if (!body.appId) { + throw simpleError('missing_app_id', 'Missing appId', { body }) + } if (!(await checkPermission(c, 'app.read_devices', { appId: body.appId }))) { throw simpleError('app_access_denied', 'You can\'t access this app', { app_id: body.appId }) }supabase/functions/_backend/private/stripe_portal.ts-44-45 (1)
44-45: Typo in error code and message:not_authorizeshould benot_authorized.Same issue as in
stripe_checkout.ts- the error code and message are inconsistent with the rest of the file (lines 23, 31, 40 usenot_authorized/Not authorized).Proposed fix
if (!await checkPermission(c, 'org.update_billing', { orgId: body.orgId })) - throw simpleError('not_authorize', 'Not authorize') + throw simpleError('not_authorized', 'Not authorized')supabase/functions/_backend/private/stripe_checkout.ts-54-55 (1)
54-55: Typo in error code and message:not_authorizeshould benot_authorized.The error code and message are inconsistent with the rest of the file (lines 33, 41, 50 all use
not_authorized/Not authorized). This could cause issues for clients parsing error codes.Proposed fix
if (!await checkPermission(c, 'org.update_billing', { orgId: body.orgId })) - throw simpleError('not_authorize', 'Not authorize') + throw simpleError('not_authorized', 'Not authorized')src/pages/settings/organization/Plans.vue-176-177 (1)
176-177: Potential race condition between organization change and permission check.When
currentOrganizationchanges,canUpdateBillingwill re-compute asynchronously. At line 177,canUpdateBilling.valuemay still reflect the previous organization's permission result or the defaultfalsevalue, potentially triggering the "cannot-view-plans" dialog incorrectly.Consider awaiting the permission check or using
watchEffectwith the permission result as a dependency:Potential fix using explicit await
watch(currentOrganization, async (newOrg, prevOrg) => { - if (newOrg && !canUpdateBilling.value) { + if (newOrg) { + const hasBillingPermission = await hasPermission('org.update_billing', { orgId: newOrg.gid }) + if (!hasBillingPermission) { if (!initialLoad.value) { // ... rest of the logic } + } + }src/pages/app/[package].access.vue-1-17 (1)
1-17: UseuseRouter()instead of$routerin the template (per repo guidance).Proposed fix
<script setup lang="ts"> @@ -import { useRoute } from 'vue-router' +import { useRoute, useRouter } from 'vue-router' @@ const route = useRoute('/app/[package].access') +const router = useRouter() @@ </script> @@ - <button class="mt-4 text-white d-btn d-btn-primary" @click="$router.push(`/app`)"> + <button class="mt-4 text-white d-btn d-btn-primary" @click="router.push('/app')"> {{ t('back-to-apps') }} </button>Also applies to: 76-78
supabase/functions/_backend/public/app/post.ts-27-30 (1)
27-30: Remove the conditionalbody.owner_org &&from the permission check.The permission
'org.update_settings'is the correct choice—no dedicated'org.create_app'permission exists in the RBAC system. However, sinceowner_orgis a required field in theCreateAppinterface (line 12), the conditional check bypasses permission validation ifowner_orgis falsy, creating a security gap.Align with the pattern used in
organization/put.tsandorganization/delete.ts, which perform the permission check unconditionally:if (!(await checkPermission(c, 'org.update_settings', { orgId: body.owner_org }))) { throw quickError(403, 'cannot_access_organization', 'You can\'t access this organization', { org_id: body.owner_org }) }Alternatively, add explicit validation for
owner_orgbefore the permission check if optional handling is intentional.tests/rbac-permissions.test.ts-11-14 (1)
11-14: Drop unused constant or use it.
TEST_RBAC_ORG_ID(Line 13) isn’t referenced.supabase/functions/_backend/public/organization/members/delete.ts-10-13 (1)
10-13: Add UUID validation fororgId
orgIdshould be validated as a UUID on line 11, not just a string. For a permission-critical endpoint that manages organization membership, UUID validation is essential:const deleteBodySchema = z.object({ - orgId: z.string(), + orgId: z.string().uuid(), email: z.email(), })The
z.email()validation is correct and fully supported in zod/mini.supabase/functions/_backend/private/groups.ts-14-14 (1)
14-14: Fix typo flagged by pipeline.Proposed fix
-// GET /private/groups/:org_id - Liste des groupes d'un org +// GET /private/groups/:org_id - List groups for an orgsrc/components/tables/AccessTable.vue-162-162 (1)
162-162: Fix typo flagged by pipeline.The pipeline failure indicates a spelling error in the French comment.
Proposed fix
- // Mettre à jour le role_id existant + // Mettre à jour le role_id existentOr consider using English comments for consistency:
- // Mettre à jour le role_id existant + // Update the existing role_idmessages/fr.json-1454-1500 (1)
1454-1500: Translation map: consider removing the capitalized duplicate key and fix remaining English strings.
Having both"access"and"Access"is easy to misuse; also there are still values like"Open in new tab"/"Your API Key"in the FR file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
src/constants/organizationTabs.ts (1)
8-10: Duplicate icon import:IconShieldandIconSecurityreference the same icon.Both
IconSecurity(line 8) andIconShield(line 9) import from~icons/heroicons/shield-check. Either reuseIconSecurityfor therole-assignmentstab, or choose a different icon if visual distinction is intended.♻️ Proposed fix: Reuse existing IconSecurity
import IconSecurity from '~icons/heroicons/shield-check' -import IconShield from '~icons/heroicons/shield-check' import IconUserGroup from '~icons/heroicons/user-group'Then update line 20:
- { label: 'role-assignments', key: '/settings/organization/role-assignments', icon: IconShield }, + { label: 'role-assignments', key: '/settings/organization/role-assignments', icon: IconSecurity },
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/constants/organizationTabs.ts
🧰 Additional context used
📓 Path-based instructions (6)
src/**/*.{ts,tsx,vue,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use
~/alias for imports fromsrc/directory in frontend TypeScript and Vue components
Files:
src/constants/organizationTabs.ts
src/**/*.{vue,ts,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Frontend ESLint must pass before commit; run
bun lint:fixto auto-fix issues in frontend files
Files:
src/constants/organizationTabs.ts
**/*.{vue,ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Run
bun lintto lint Vue, TypeScript, and JavaScript files; usebun lint:fixto auto-fix issues
Files:
src/constants/organizationTabs.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use single quotes and no semicolons per @antfu/eslint-config rules
Files:
src/constants/organizationTabs.ts
src/**/*.{ts,tsx,js,jsx,vue}
📄 CodeRabbit inference engine (AGENTS.md)
Use path alias
~/to map tosrc/for imports in TypeScript and JavaScript files
Files:
src/constants/organizationTabs.ts
**/*.{ts,tsx,js,jsx,vue}
📄 CodeRabbit inference engine (AGENTS.md)
Run
bun lintor lint/format command before validating any backend or frontend task to ensure consistent formatting
Files:
src/constants/organizationTabs.ts
🧠 Learnings (2)
📓 Common learnings
Learnt from: riderx
Repo: Cap-go/capgo PR: 1297
File: src/components/dashboard/DeploymentBanner.vue:77-79
Timestamp: 2025-12-23T01:19:15.067Z
Learning: In the Cap-go codebase, the 'owner' role exists only at the organization level (used in src/stores/organization.ts), not as an app-level permission. App-level permissions use the user_min_right database enum which includes: read, upload, write, admin, super_admin (but NOT owner). When checking app permissions, never include 'owner' in the role checks.
📚 Learning: 2025-10-30T14:58:37.007Z
Learnt from: Dalanir
Repo: Cap-go/capgo PR: 1238
File: src/layouts/settings.vue:7-13
Timestamp: 2025-10-30T14:58:37.007Z
Learning: In the Cap-go/capgo repository, heroicons are imported using the pattern `~icons/heroicons/icon-name` (e.g., `~icons/heroicons/user`, `~icons/heroicons/bell`) without requiring `/outline/` or `/solid/` path segments. Solid variants use a `-solid` suffix in the icon name itself (e.g., `~icons/heroicons/arrow-path-solid`).
Applied to files:
src/constants/organizationTabs.ts
🔇 Additional comments (1)
src/constants/organizationTabs.ts (1)
19-20: New RBAC tabs structure looks correct.The
groupsandrole-assignmentstabs are appropriately placed and follow the existing tab definition pattern. The route paths are consistent with the organization settings structure.
75934f6 to
403d5ea
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 12
Note
Due to the large number of review comments, Critical severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (7)
supabase/seed.sql (1)
474-485: Fix brokenorg_usersseed INSERT (SQL syntax error).Line 482 ends the
VALUESlist with);, but Lines 483-485 continue with additional tuples—this will fail parsing.Proposed fix
INSERT INTO "public"."org_users" ("org_id", "user_id", "user_right", "app_id", "channel_id") VALUES @@ - ('c3d4e5f6-a7b8-4c9d-8e0f-1a2b3c4d5e6f', '8b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e', 'super_admin'::"public"."user_min_right", null, null), - ('f6a7b8c9-d0e1-4f2a-9b3c-4d5e6f7a8b92', 'e5f6a7b8-c9d0-4e1f-8a2b-3c4d5e6f7a81', 'super_admin'::"public"."user_min_right", null, null); - ('046a36ac-e03c-4590-9257-bd6c9dba9ee8', 'c591b04e-cf29-4945-b9a0-776d0672061a', 'admin'::"public"."user_min_right", null, null), - ('34a8c55d-2d0f-4652-a43f-684c7a9403ac', '6aa76066-55ef-4238-ade6-0b32334a4097', 'write'::"public"."user_min_right", null, null); - ('a7b8c9d0-e1f2-4a3b-9c4d-5e6f7a8b9ca4', 'f6a7b8c9-d0e1-4f2a-9b3c-4d5e6f708193', 'super_admin'::"public"."user_min_right", null, null); + ('c3d4e5f6-a7b8-4c9d-8e0f-1a2b3c4d5e6f', '8b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e', 'super_admin'::"public"."user_min_right", null, null), + ('f6a7b8c9-d0e1-4f2a-9b3c-4d5e6f7a8b92', 'e5f6a7b8-c9d0-4e1f-8a2b-3c4d5e6f7a81', 'super_admin'::"public"."user_min_right", null, null), + ('046a36ac-e03c-4590-9257-bd6c9dba9ee8', 'c591b04e-cf29-4945-b9a0-776d0672061a', 'admin'::"public"."user_min_right", null, null), + ('34a8c55d-2d0f-4652-a43f-684c7a9403ac', '6aa76066-55ef-4238-ade6-0b32334a4097', 'write'::"public"."user_min_right", null, null), + ('a7b8c9d0-e1f2-4a3b-9c4d-5e6f7a8b9ca4', 'f6a7b8c9-d0e1-4f2a-9b3c-4d5e6f708193', 'super_admin'::"public"."user_min_right", null, null);supabase/functions/_backend/utils/hono.ts (1)
111-136: JWT validation should return 401 Unauthorized, not 400 Bad Request.Line 125:
simpleError()always returns status 400, but authentication failures should return 401. Change toquickError(401, 'invalid_jwt', 'Invalid JWT').Note: The
jwtfield in the auth context stores the rawAuthorizationheader value (including the "Bearer " prefix), which is correct for the current usage insupabaseWithAuth()but should be documented to prevent downstream confusion if the auth object is used elsewhere.Proposed fix
if (authError || !authData.user) { cloudlog({ requestId: c.get('requestId'), message: 'Invalid JWT', error: authError }) - return simpleError('invalid_jwt', 'Invalid JWT') + throw quickError(401, 'invalid_jwt', 'Invalid JWT') }supabase/functions/_backend/public/bundle/set_channel.ts (1)
15-27: Bug: check channel-scoped permission withchannelId, not justappId.With
{ appId: body.app_id }(Line 25), a principal that only has a channel-level role binding can be denied incorrectly. Use{ channelId: body.channel_id }(and optionally includeappIdtoo).Proposed fix
- if (!(await checkPermission(c, 'channel.promote_bundle', { appId: body.app_id }))) { + if (!(await checkPermission(c, 'channel.promote_bundle', { channelId: body.channel_id }))) { throw simpleError('cannot_access_app', 'You can\'t access this app', { app_id: body.app_id }) }src/components/dashboard/AppSetting.vue (1)
104-147: Fix incorrect org role value at line 826.The code checks
org.role === 'org_super_admin', butorg.rolecontains legacy role values ('super_admin'or'owner'), not RBAC names. Change toorg.role === 'super_admin' || org.role === 'owner'to match the actual role values stored in the organization object.This also applies to lines 301-306, 430-435, 637-642, and 825-830 if they have the same issue.
supabase/functions/_backend/private/delete_failed_version.ts (1)
20-29: Do not logcapgkey(API key); remove or heavily redact.Line 24 logs the full
capgkey, which is a credential. This is a production security incident waiting to happen (logs are widely accessible and long-lived).Proposed diff
const apikey = c.get('apikey') const capgkey = c.get('capgkey') as string cloudlog({ requestId: c.get('requestId'), message: 'apikey', apikey }) - cloudlog({ requestId: c.get('requestId'), message: 'capgkey', capgkey }) + cloudlog({ + requestId: c.get('requestId'), + message: 'capgkey', + capgkey: capgkey ? `${capgkey.slice(0, 6)}...` : 'missing', + }) - const { data: userId, error: _errorUserId } = await supabaseApikey(c, capgkey) - .rpc('get_user_id', { apikey: capgkey, app_id: body.app_id }) - if (_errorUserId) { - return quickError(404, 'user_not_found', 'Error User not found', { _errorUserId }) - }supabase/functions/_backend/files/files.ts (1)
373-403: Changeapp.read_bundlestoapp.upload_bundleincheckWriteAppAccess.Line 381:
checkWriteAppAccessis a middleware protecting file upload/write operations (POST, PATCH routes), yet it checksapp.read_bundles(read permission). This is an authorization regression. The RBAC system clearly distinguishesapp.read_bundles(read) fromapp.upload_bundle(upload), and similar upload endpoints (e.g.,bundle/create.ts) correctly useapp.upload_bundle. File write operations should require upload permission, not read permission.src/layouts/settings.vue (1)
54-123: Billing tab can “stick” on native because removal only happens when!canReadBilling.Current logic only removes
/billingwhen!canReadBilling.value, not whenCapacitor.isNativePlatform()is true. If the tab ever gets added in a non-native context (or base tabs change), it won’t be forcibly removed on native.Proposed tweak
- if (!Capacitor.isNativePlatform() - && canReadBilling.value - && !organizationTabs.value.find(tab => tab.key === '/billing')) { + if (!Capacitor.isNativePlatform() + && canReadBilling.value + && !organizationTabs.value.find(tab => tab.key === '/billing')) { organizationTabs.value.push({ label: 'billing', icon: IconBilling, key: '/billing', onClick: () => openPortal(organizationStore.currentOrganization?.gid ?? '', t), }) } - else if (!canReadBilling.value) { + else if (Capacitor.isNativePlatform() || !canReadBilling.value) { organizationTabs.value = organizationTabs.value.filter(tab => tab.key !== '/billing') }
🤖 Fix all issues with AI agents
In @src/components/dashboard/AppAccess.vue:
- Line 3: The file imports computed, onMounted, ref, watch from 'vue' but uses
the Vue type Ref (e.g., Ref<TableColumn[]>) on line 73, causing a TS error; fix
it by adding Ref to the import list from 'vue' (include Ref alongside
computed/onMounted/ref/watch) so the type Ref<TableColumn[]> is recognized where
used in AppAccess.vue.
In @src/pages/settings/organization/Members.vue:
- Around line 82-101: The function checkRbacEnabled is missing its closing
brace, causing a syntax error; close the function body by adding a single '}'
right after the existing catch block (before the declaration of
isInviteNewUserDialogOpen) so the try/catch and the async function are properly
terminated and ensure indentation matches surrounding code.
In @src/stores/organization.ts:
- Around line 287-372: The comment block around invalidateRoleBindingsCache and
hasPermissionsInRbac contains French text and typos causing CI spellcheck
failures; replace or rewrite those block comments in English and fix
misspellings (e.g., "Invalide" → "Invalidate", "FONCTION" → "FUNCTION",
"EXEMPLE"/"AVANTAGES"/"autorisation" → "EXAMPLE"/"ADVANTAGES"/"authorization"),
keeping the same intent and references to RBAC migration and the TODO for
hasPermission; update the JSDoc above hasPermissionsInRbac and the surrounding
explanatory comments to clean, consistent English phrasing while preserving
mentions of RBAC_ROLE_HIERARCHY, role_bindings, role_permissions, and the
migration steps so spellcheck passes.
In @supabase/functions/_backend/private/groups.ts:
- Around line 94-96: The permission check using orgAccess[0].user_right <
'admin' is invalid because it relies on lexicographic string comparison; replace
it with an explicit privilege check (e.g., verify user_right is one of
['admin','super_admin'] or map enum values to numeric ranks and compare ranks)
inside the same conditional that returns the 403; update the check near
orgAccess[0].user_right to either test inclusion in the allowed set or call a
new utility (e.g., hasRequiredRight(user_right, 'admin')) that encapsulates the
enum-to-rank logic.
In @supabase/functions/_backend/private/invite_new_user_to_org.ts:
- Around line 51-56: validateInvite currently returns { status: 403 } on RBAC
denial which the invite_new_user_to_org handler ignores and later throws a 400;
change validateInvite to throw a quickError(403, ...) (or otherwise throw an
error) immediately when checkPermission fails so the route handler won’t
continue to the simpleError path. Specifically, inside validateInvite replace
the return { status: 403 } branch with throw quickError(403, 'not authorized
(insufficient permissions)') (or equivalent) and apply the same change for any
other permission-denial branches referenced by the invite_new_user_to_org flow
so callers stop execution and the correct 403 is returned.
In @supabase/functions/_backend/private/role_bindings.ts:
- Around line 251-253: The condition uses a lexicographic string comparison
(orgAccess[0].user_right < 'admin') which is incorrect; replace it with an
explicit role check (e.g., ensure orgAccess exists and orgAccess[0].user_right
=== 'admin' or use a role-to-rank lookup and compare numeric ranks) so the
handler correctly enforces admin-only access before returning c.json({ error:
'Forbidden - Admin rights required' }, 403).
- Around line 122-124: The current check uses lexicographic string comparison on
orgAccess[0].user_right which is invalid for role hierarchy; replace it with an
explicit hierarchy check (e.g., define an ordered roles array like
['read','upload','write','admin','super_admin'] and compare indices, or simply
check membership in allowedRoles = ['admin','super_admin']) and use that to
decide if the user is permitted before returning c.json({ error: 'Forbidden -
Admin rights required' }, 403). Ensure you reference orgAccess[0].user_right for
the lookup and keep the same response when unauthorized.
In @supabase/functions/_backend/public/build/start.ts:
- Around line 5-7: The code uses a non-null assertion apikey.key! before calling
checkPermission which will throw if the DB row has key=null; change the logic in
the start handler to defensively handle nullable keys: read apikey without using
!, prefer apikey.key ?? apikey.hashed_key (or pass the whole apikey object to
checkPermission if it understands hashed keys), and ensure any failure to
resolve a usable key triggers markBuildAsFailed and returns the controlled error
response rather than letting an exception bubble; update references to
apikey.key! to these safe checks and add a guarded error path that calls
markBuildAsFailed when no valid key is available.
🟠 Major comments (13)
supabase/functions/_backend/utils/postgres_schema.ts-164-169 (1)
164-169:group_membersneeds a PK/unique constraint + FK to prevent duplicates/orphans.Without a composite PK/unique index, the same
(group_id, user_id)can be inserted multiple times, which can break effective-permissions calculations.Possible patch
-import { bigint, boolean, integer, pgEnum, pgTable, serial, text, timestamp, uuid, varchar } from 'drizzle-orm/pg-core' +import { bigint, boolean, integer, pgEnum, pgTable, serial, text, timestamp, uuid, varchar, primaryKey } from 'drizzle-orm/pg-core' -export const group_members = pgTable('group_members', { - group_id: uuid('group_id').notNull(), +export const group_members = pgTable('group_members', { + group_id: uuid('group_id').notNull().references(() => groups.id, { onDelete: 'cascade' }), user_id: uuid('user_id').notNull(), added_by: uuid('added_by'), added_at: timestamp('added_at').notNull().defaultNow(), -}) +}, (t) => ({ + pk: primaryKey({ columns: [t.group_id, t.user_id] }), +}))supabase/functions/_backend/utils/postgres_schema.ts-154-162 (1)
154-162: Add FK + per-org uniqueness forgroups.
groups.org_idshould likely referenceorgs.id, andnameis typically unique within an org (otherwise UI/API gets ambiguous).Possible patch
-import { bigint, boolean, integer, pgEnum, pgTable, serial, text, timestamp, uuid, varchar } from 'drizzle-orm/pg-core' +import { bigint, boolean, integer, pgEnum, pgTable, serial, text, timestamp, uuid, varchar, uniqueIndex } from 'drizzle-orm/pg-core' -export const groups = pgTable('groups', { +export const groups = pgTable('groups', { id: uuid('id').primaryKey().defaultRandom(), - org_id: uuid('org_id').notNull(), + org_id: uuid('org_id').notNull().references(() => orgs.id, { onDelete: 'cascade' }), name: text('name').notNull(), description: text('description'), is_system: boolean('is_system').notNull().default(false), created_by: uuid('created_by'), created_at: timestamp('created_at').notNull().defaultNow(), -}) +}, (t) => ({ + orgNameUq: uniqueIndex('groups_org_id_name_uq').on(t.org_id, t.name), +}))supabase/functions/_backend/utils/postgres_schema.ts-171-186 (1)
171-186: Add missing foreign key constraints and indexes to match the database schema in role_bindings.The Drizzle schema for
role_bindings(lines 171–186) is missing critical constraints and indexes that are defined in the migration (20251222140030_rbac_system.sql). The types themselves are correct—channel_idisuuidand correctly referenceschannels(rbac_id)(alsouuid), andapp_idis intentionallyuuidto referenceapps.idrather thanapps.name. However:
- Missing foreign keys:
role_id,org_id,app_id,channel_id, andbundle_idall lack.references()constraints that exist in the database migration.- Missing CHECK constraint:
scope_typeshould validate against('platform', 'org', 'app', 'bundle', 'channel')as enforced in the migration.- Missing indexes: The migration defines five unique scope-based indexes and two composite indexes for efficient lookups and SSD enforcement.
Sync the Drizzle schema with the authoritative migration schema to ensure ORM queries reflect actual constraints:
Update to add FKs and basic index hint
export const role_bindings = pgTable('role_bindings', { id: uuid('id').primaryKey().defaultRandom(), principal_type: text('principal_type').notNull(), principal_id: uuid('principal_id').notNull(), - role_id: uuid('role_id').notNull(), + role_id: uuid('role_id').notNull().references(() => roles.id, { onDelete: 'cascade' }), scope_type: text('scope_type').notNull(), - org_id: uuid('org_id'), - app_id: uuid('app_id'), + org_id: uuid('org_id').references(() => orgs.id, { onDelete: 'cascade' }), + app_id: uuid('app_id').references(() => apps.id, { onDelete: 'cascade' }), bundle_id: bigint('bundle_id', { mode: 'number' }), - channel_id: uuid('channel_id'), + channel_id: uuid('channel_id').references(() => channels.rbac_id, { onDelete: 'cascade' }), granted_by: uuid('granted_by').notNull(), granted_at: timestamp('granted_at').notNull().defaultNow(), expires_at: timestamp('expires_at'), reason: text('reason'), is_direct: boolean('is_direct').notNull().default(true), })supabase/functions/_backend/private/stripe_portal.ts-5-5 (1)
5-5: Fix auth error code/message + consider checking permission before querying org.
- Line 45:
not_authorize/Not authorizelooks accidental and inconsistent with the rest of the file (and likely client expectations).- Line 44: You can run
checkPermissionbefore fetchingorg.customer_idto fail fast.Proposed fix
- if (!await checkPermission(c, 'org.update_billing', { orgId: body.orgId })) - throw simpleError('not_authorize', 'Not authorize') + if (!await checkPermission(c, 'org.update_billing', { orgId: body.orgId })) + throw simpleError('not_authorized', 'Not authorized')Also applies to: 9-12, 44-45
RBAC_SYSTEM.md-631-915 (1)
631-915: Security note: don’t expose SECURITY DEFINER functions that accept arbitraryp_user_id.The doc demonstrates calling
rbac_check_permission_direct(p_user_id => 'user-uuid'::uuid, ...)while the function isSECURITY DEFINER. If this RPC is granted toauthenticated, clients could probe permissions for other users unless the SQL enforcesp_user_id = auth.uid()(or you only expose a no-arg wrapper that usesauth.uid()). This mirrors prior learnings about not granting inner functions withuser_idparams.Also applies to: 797-945
tests/rbac-permissions.test.ts-26-177 (1)
26-177: Use dedicated TEST_RBAC_ORG_ID and a dedicated test user/app for RBAC tests instead of reusing ORG_ID.Tests that enable/disable RBAC mode modify shared global state (
rbac_settingsWHEREid = 1and per-orguse_new_rbacflags). Since tests run in parallel and share ORG_ID with other test files, these mutations can interfere with other tests' setup. TEST_RBAC_ORG_ID is already defined in seed but unused. Create dedicated RBAC-specific test fixtures for org, user, and app or reuse TEST_RBAC_ORG_ID; ensure all state mutations are properly reverted in afterEach hooks (currently line 156 updates only the org flag, not the global setting).src/stores/organization.ts-102-103 (1)
102-103: RBAC cache won’t be reactive withRef<Map>mutations; UI can get stuck showing “no access”.
_roleBindingsCacheis aref(new Map())(Line 102) and you mutate it via.set/.delete/.clear(Lines 278-279, 291-296, 508-509). Vue won’t track internalMapmutations through aRef, so anything relying on this for rendering can fail to update after role bindings load.One workable pattern (replace Map on writes)
- const _roleBindingsCache: Ref<Map<string, RoleBinding[]>> = ref(new Map()) // Cache des role_bindings par orgId + const _roleBindingsCache: Ref<Map<string, RoleBinding[]>> = ref(new Map()) // Cache des role_bindings par orgId + + const setRoleBindingsCache = (orgId: string, bindings: RoleBinding[]) => { + // Replace the Map instance to trigger reactivity + const next = new Map(_roleBindingsCache.value) + next.set(orgId, bindings) + _roleBindingsCache.value = next + } ... - _roleBindingsCache.value.set(orgId, userBindings) + setRoleBindingsCache(orgId, userBindings) ... - _roleBindingsCache.value.delete(orgId) + const next = new Map(_roleBindingsCache.value) + next.delete(orgId) + _roleBindingsCache.value = next ... - _roleBindingsCache.value.clear() + _roleBindingsCache.value = new Map() ... - _roleBindingsCache.value.set(org.gid, bindingsData) + setRoleBindingsCache(org.gid, bindingsData)Also applies to: 251-285, 290-297, 299-332, 496-515
supabase/functions/_backend/utils/rbac.ts-170-207 (1)
170-207:rbacEnabledcontext cache should be per-org (or include orgId).
isRbacEnabledForOrg()caches a single boolean inc.get('rbacEnabled')(Lines 175-178, 195-196). If any request ever checks multiple orgIds, the first result will be reused incorrectly.Safer cache shape
export async function isRbacEnabledForOrg( c: Context<MiddlewareKeyVariables>, orgId: string | null, ): Promise<boolean> { - // Check cache first - const cached = c.get('rbacEnabled') - if (cached !== undefined) { - return cached - } + const cachedOrgId = c.get('resolvedOrgId') + const cachedEnabled = c.get('rbacEnabled') + if (cachedOrgId === orgId && cachedEnabled !== undefined) + return cachedEnabled if (!orgId) { return false } ... - c.set('rbacEnabled', enabled) + c.set('resolvedOrgId', orgId) + c.set('rbacEnabled', enabled) return enabledsupabase/functions/_backend/utils/rbac.ts-239-312 (1)
239-312: Enforce “non-empty scope” and use read-only PG client for SELECT-only checks.
checkPermission()acceptsscope: PermissionScopebut doesn’t enforce the “at least one must be provided” contract (Lines 257-258). Also it usesgetPgClient(c)for a SELECT (Line 270) wheregetPgClient(c, true)seems more appropriate.Proposed diff
export async function checkPermission( c: Context<MiddlewareKeyVariables>, permission: Permission, scope: PermissionScope, ): Promise<boolean> { ... const { orgId = null, appId = null, channelId = null } = scope + if (orgId == null && appId == null && channelId == null) { + cloudlog({ + requestId: c.get('requestId'), + message: 'checkPermission: missing scope', + permission, + }) + return false + } ... - pgClient = getPgClient(c) + pgClient = getPgClient(c, true)supabase/functions/_backend/public/build/status.ts-5-7 (1)
5-7: Avoid assumingapikey.keyis present; hashed API keys likely break this path
checkPermissionexplicitly supports hashed keys viac.get('capgkey'), butsupabaseApikey(c, apikey.key)(Line 39) still assumes a non-null key. Ifapikey.keyis null for hashed keys, this can break build status reads (and subsequent DB updates) even though the RBAC check passes.Proposed diff
+import type { MiddlewareKeyVariables } from '../../utils/hono.ts' import type { Context } from 'hono' import type { Database } from '../../utils/supabase.types.ts' import { simpleError } from '../../utils/hono.ts' import { cloudlog, cloudlogErr } from '../../utils/logging.ts' import { recordBuildTime, supabaseApikey } from '../../utils/supabase.ts' import { checkPermission } from '../../utils/rbac.ts' import { getEnv } from '../../utils/utils.ts' export async function getBuildStatus( - c: Context, + c: Context<MiddlewareKeyVariables>, params: BuildStatusParams, apikey: Database['public']['Tables']['apikeys']['Row'], ): Promise<Response> { @@ - const supabase = supabaseApikey(c, apikey.key) + const apikeyString = apikey.key ?? c.get('capgkey') ?? null + if (!apikeyString) + throw simpleError('unauthorized', 'Missing API key') + const supabase = supabaseApikey(c, apikeyString)Also applies to: 33-40
supabase/tests/33_test_rbac_phase1.sql-1-7 (1)
1-7: MakeCREATE EXTENSIONidempotent to avoid flaky test runs.
CREATE EXTENSION "basejump-supabase_test_helpers";will error if the extension is already installed.Proposed fix
-CREATE EXTENSION "basejump-supabase_test_helpers"; +CREATE EXTENSION IF NOT EXISTS "basejump-supabase_test_helpers";supabase/functions/_backend/public/organization/delete.ts-1-24 (1)
1-24: Create a dedicatedorg.deletepermission instead of reusingorg.update_settingsfor deletion.The
org.update_settingspermission is semantically incorrect for organization deletion. It should only handle settings updates. Other resources follow the established pattern with dedicated delete permissions (app.delete,bundle.delete,channel.delete), but org deletion is missing this. Using an overly broad permission creates a privilege escalation risk: iforg.update_settingsis granted more widely in the future, deletion rights would inadvertently expand. Addorg.deleteto the permissions table and use it here instead.supabase/functions/_backend/private/groups.ts-9-9 (1)
9-9: UsecreateHonofrom utils/hono.ts per coding guidelines.Per the coding guidelines, Hono app initialization should use
createHonofromutils/hono.tsrather than instantiating directly.♻️ Use createHono helper
-import { Hono } from 'hono/tiny' -import { middlewareAuth, useCors } from '../utils/hono.ts' +import { createHono, middlewareAuth, useCors } from '../utils/hono.ts' -export const app = new Hono<MiddlewareKeyVariables>() +export const app = createHono()Based on learnings: "Use
createHonofromutils/hono.tsfor all Hono framework application initialization and routing"
🟡 Minor comments (9)
messages/zh-cn.json-1454-1463 (1)
1454-1463: Translations look OK; consider terminology consistency for role names.Keys/format look fine. Minor: double-check whether “计费经理 / 应用阅读者” matches your existing zh-CN terminology elsewhere (some products prefer “计费管理员 / 应用读者”).
messages/ja.json-1454-1463 (1)
1454-1463: Consider adjusting JP wording for “role-app-reader”.Line 1457 uses
アプリ読者, which can read a bit unnatural in UI. Consider閲覧者(common in admin UIs) orリーダーfor consistency with other locales.src/components/dashboard/DeploymentBanner.vue-28-34 (1)
28-34: Don’t requireuserRolefor an RBAC-based permission gate (and update the doc comment)
hasAdminPermissionnow hinges onhasPermission('channel.promote_bundle', ...), but it still returnsfalsewhenuserRole.valueis unset (Line 98-99). This can hide the banner for authorized users if role loading fails/returns null. Also the “admin/super_admin roles” comment is no longer accurate.Proposed diff
const hasAdminPermission = computedAsync(async () => { - if (!props.appId || !userRole.value) + if (!props.appId) return false return await hasPermission('channel.promote_bundle', { appId: props.appId }) }, false)Also applies to: 97-101
src/components/tables/ChannelTable.vue-54-65 (1)
54-65: Potential UX glitch: permission refs default tofalseand can block the auto “add channel” flowBecause
canUpdateChannelstarts asfalse, thecount === 0path that callsshowAddModal()can race and showno-permissionfor users who actually have permission (untilcomputedAsyncresolves). Consider deferring that auto-open until the permission check is finished, or separating “unknown yet” from “false”.Also applies to: 286-287, 302-306
messages/en.json-1504-1506 (1)
1504-1506: Confirm the confirmation text matches the actual scope being removed.
"remove-role-confirm": "Do you want to remove this user's access to the app?"reads app-specific, but it’s under generic role assignment management. If this confirm is also used for org/channel scope removals, the text will be misleading.supabase/functions/_backend/private/groups.ts-14-14 (1)
14-14: Fix French comments to English.The pipeline is failing due to French comments. Replace French text with English throughout the file.
🔧 Fix French comments
-// GET /private/groups/:org_id - Liste des groupes d'un org +// GET /private/groups/:org_id - List groups for an orgLine 42:
- // Récupérer les groupes + // Fetch groupsLine 63:
-// POST /private/groups/:org_id - Créer un groupe +// POST /private/groups/:org_id - Create a groupLine 98:
- // Créer le groupe + // Create the groupLine 118:
-// PUT /private/groups/:group_id - Modifier un groupe +// PUT /private/groups/:group_id - Update a groupAnd similar changes for all other French comments (lines 26, 82, 133, 148, 164, 182, 194, 209, 225, 238, 250, 261, 277, 297, 316, 327, 343, 359, 378, 391, 402, 418).
src/components/tables/AccessTable.vue-254-266 (1)
254-266: Comment typo flagged by pipeline: "fonction" should be "function".The pipeline flagged this French comment. Consider using English comments for consistency.
🔧 Suggested fix
-// Filtrer les éléments en fonction de la recherche +// Filter elements based on searchsupabase/functions/_backend/private/role_bindings.ts-75-78 (1)
75-78: Handle malformed JSON input gracefully.
c.req.json()throws if the request body is not valid JSON. Wrap it in a try-catch to return a 400 response for malformed input.Suggested fix
app.post('/', async (c: Context<MiddlewareKeyVariables>) => { const userId = c.get('auth')?.userId - const body = await c.req.json() + let body + try { + body = await c.req.json() + } + catch { + return c.json({ error: 'Invalid JSON body' }, 400) + }supabase/functions/_backend/public/organization/put.ts-27-30 (1)
27-30: Inconsistent error response for permission failure.Permission failures typically return 401 (Unauthorized) or 403 (Forbidden) status codes. This uses
simpleErrorwhich returns 400. Compare withapp/put.tswhich usesquickError(401, ...)for the same type of check.🐛 Suggested fix for consistency
// Auth context is already set by middlewareKey if (!(await checkPermission(c, 'org.update_settings', { orgId: body.orgId }))) { - throw simpleError('cannot_access_organization', 'You can\'t access this organization', { orgId: body.orgId }) + throw quickError(401, 'cannot_access_organization', 'You can\'t access this organization', { orgId: body.orgId }) }
🧹 Nitpick comments (24)
supabase/functions/_backend/public/device/get.ts (1)
45-55: RBAC migration looks correct.The permission check
'app.read_devices'with{ appId: body.app_id }scope is appropriate for this endpoint. The defensive ordering (validation before authorization) is good practice.As per coding guidelines, consider typing the context parameter more specifically for better type safety:
💡 Optional: Use typed Context
-export async function get(c: Context, body: GetDevice, apikey: Database['public']['Tables']['apikeys']['Row']): Promise<Response> { +export async function get(c: Context<MiddlewareKeyVariables>, body: GetDevice, apikey: Database['public']['Tables']['apikeys']['Row']): Promise<Response> {This would require importing
MiddlewareKeyVariablesfrom../../utils/hono.ts.supabase/functions/_backend/utils/postgres_schema.ts (1)
142-152: Consider constrainingrolesmore tightly (enum + uniqueness).
scope_type: text+name: textwith no uniqueness/indexing makes it easy to create ambiguous roles (same name in same scope). Also, ifdefaultRandom()is used, ensure the DB hasgen_random_uuid()available. As per learnings, keeping schema here is good—please ensure migrations match it.Proposed direction (sketch)
- Add
pgEnumforscope_type(and reuse it inrole_bindings.scope_type)- Add a unique constraint like
(scope_type, name)- Add an index on
priority_rankif it’s used for ordering- Verify
gen_random_uuid()support in your Supabase/Postgres setupsupabase/functions/_backend/private/download_link.ts (1)
7-7: Avoid double-auth and keep a single source of truth for user identity.Now that
middlewareAuthsetsc.get('auth'), this handler can drift ifsupabase.auth.getUser()ever disagrees (or if you later optimize middleware). Consider derivinguserIdfromc.get('auth')here (and only using Supabase client calls for data), so the RBAC decision + URL generation use the same identity.Also applies to: 38-40
supabase/functions/_backend/utils/hono.ts (1)
40-42: New RBAC context variables: ensure they’re set (or remove).
rbacEnabled/resolvedOrgIdare added toMiddlewareKeyVariables, but nothing here populates them—please ensure the intended middleware sets them, otherwise they’ll silently stayundefined.tests/rbac-permissions.test.ts (1)
179-215: Considerit.concurrent()for the pure read-only mapping loop.The permission→legacy mapping checks (Line 179+) are read-only and can safely be
it.concurrent()to align with the repo’s parallel testing guidance. Based on learnings.src/components/dashboard/AppSetting.vue (1)
42-53: Harden computed permission checks against thrown errors/timeouts.If
hasPermission(...)can throw (network/RPC),computedAsyncwill reject; consider a local try/catch and default tofalse.Proposed fix
const canUpdateSettings = computedAsync(async () => { if (!appRef.value) return false - return await hasPermission('app.update_settings', { appId: props.appId }) + try { + return await hasPermission('app.update_settings', { appId: props.appId }) + } + catch { + return false + } }, false) const canDeleteApp = computedAsync(async () => { if (!appRef.value) return false - return await hasPermission('app.delete', { appId: props.appId }) + try { + return await hasPermission('app.delete', { appId: props.appId }) + } + catch { + return false + } }, false)src/stores/organization.ts (2)
30-75: Tighten types for i18n helper + avoid stringly-typed map key access.
getRbacRoleI18nKey(role: string)(Line 73) defeats the point ofRbacRoleNameand can mask typos at callsites. Preferrole: RbacRoleName(orrole: stringbut with a type-guard).Proposed diff
-export function getRbacRoleI18nKey(role: string): string | undefined { +export function getRbacRoleI18nKey(role: RbacRoleName): string | undefined { return RBAC_ORG_ROLE_I18N_KEYS[role] }
299-405:hasPermissionsInRole()legacy path is brittle (no legacy hierarchy); consider “minimum right” semantics.Legacy fallback currently does strict equality (Line 404) against mapped legacy roles, so callers must remember to always include higher roles in
requiredRoles(e.g. pass bothorg_adminandorg_super_admin). That’s easy to get wrong and will silently deny legitimate access.If the legacy model is “minimum right”, consider a simple rank table (
read < upload < write < admin < super_admin, plusownerat org scope) and checkrank(currentRole) >= rank(requiredMinRole).supabase/functions/_backend/utils/rbac.ts (1)
339-366: Avoid N DB connections incheckPermissions/checkAnyPermission.Both helpers call
checkPermissionin a loop (Lines 344-347, 360-364), which opens/closes a PG client per permission. For endpoints checking multiple permissions, this is a big overhead. Prefer opening one client and reusing viacheckPermissionPg.supabase/functions/_backend/private/delete_failed_version.ts (1)
31-34: Permission check migration looks good; consider returning 403 (not 401) for “authenticated but forbidden”.The RBAC check itself (Line 32) is the right direction. For consistency, the failure path currently returns 401 (Line 33) which usually means “not authenticated”, while this is “forbidden”.
supabase/functions/_backend/public/build/logs.ts (1)
5-5: Align Hono context typing with RBAC helper expectations
checkPermissionis typed asContext<MiddlewareKeyVariables>(persupabase/functions/_backend/utils/rbac.ts), but this function acceptsContextwithout variables typing, which can drift from the repo convention and reduce type-safety aroundc.get('auth')/c.get('requestId'). As per coding guidelines, consider updating the signature (and imports) to useContext<MiddlewareKeyVariables>here.Proposed diff
-import type { Context } from 'hono' +import type { Context } from 'hono' +import type { MiddlewareKeyVariables } from '../../utils/hono.ts' export async function streamBuildLogs( - c: Context, + c: Context<MiddlewareKeyVariables>, jobId: string, appId: string, apikey: Database['public']['Tables']['apikeys']['Row'], ): Promise<Response> {Also applies to: 74-84
messages/en.json (1)
1478-1524: Avoid generic + mixed-case i18n keys (collision risk / hard-to-debug overrides).The newly added RBAC strings include very generic keys (
"access","assign","remove","user","group", etc.) and also both"access"and"Access"(Line 1482 vs Line 1516). Even if JSON parsing “works”, these keys are prone to accidental reuse across features and subtle key mismatches in code.Recommended: namespace these keys (e.g.,
rbac-access,rbac-assign,rbac-user, …) and pick one consistent casing.src/layouts/app.vue (1)
10-25: Avoid(… as any)?.use_new_rbac; prefer typed store + stable filter key.Two concerns:
(organizationStore.currentOrganization as any)?.use_new_rbaccan silently break if the field name/type changes.baseAppTabs.filter(t => t.label !== 'access')is brittle if labels change (or get localized); filtering on a stable route key is safer.supabase/functions/_backend/public/channel/get.ts (1)
15-55: Consider updating helper function context types for consistency.The internal helper functions
getAllandgetOnestill use the plainContexttype while the exportedgetfunction usesContext<MiddlewareKeyVariables>. While this works since these helpers are only called fromget, updating them toContext<MiddlewareKeyVariables>would improve type consistency and enable better IDE support for context variables.♻️ Optional consistency improvement
-async function getAll(c: Context, body: GetDevice, apikey: Database['public']['Tables']['apikeys']['Row']) { +async function getAll(c: Context<MiddlewareKeyVariables>, body: GetDevice, apikey: Database['public']['Tables']['apikeys']['Row']) {-async function getOne(c: Context, body: GetDevice, apikey: Database['public']['Tables']['apikeys']['Row']) { +async function getOne(c: Context<MiddlewareKeyVariables>, body: GetDevice, apikey: Database['public']['Tables']['apikeys']['Row']) {supabase/functions/_backend/public/channel/post.ts (1)
26-40: Optional: Update helper function context type.Similar to
get.ts, thefindVersionhelper uses plainContextwhile the exported function usesContext<MiddlewareKeyVariables>. Consider updating for consistency.supabase/functions/_backend/public/bundle/create.ts (1)
24-132: Large commented-out code block.This ~100 line block of commented code for URL verification could be removed or moved to a separate utility if the feature is not planned for near-term re-enablement. Keeping it adds maintenance burden.
src/components/dashboard/AppAccess.vue (2)
176-202: N+1 query pattern when enriching role bindings.Each binding triggers a separate query to fetch user email or group name. For organizations with many bindings, this could cause performance issues. Consider batching the user/group lookups.
♻️ Suggested optimization
// Batch fetch all user emails const userIds = appBindings .filter((b: RoleBinding) => b.principal_type === 'user') .map((b: RoleBinding) => b.principal_id) const { data: usersData } = await supabase .from('users') .select('id, email') .in('id', userIds) const userMap = new Map(usersData?.map(u => [u.id, u.email]) || []) // Then map bindings using the lookup map
361-385: Duplicate initialization logic between watch and onMounted.The same sequence of
fetchAppDetails,checkRbacEnabled, and conditional fetches appears in both the watcher andonMounted. Extract to a shared function for DRY.♻️ Suggested refactor
+async function initializeData() { + await fetchAppDetails() + await checkRbacEnabled() + if (useNewRbac.value) { + await Promise.all([ + fetchAppRoleBindings(), + fetchAvailableAppRoles(), + fetchAvailableMembers(), + fetchAvailableGroups(), + ]) + } +} -watch(() => props.appId, async () => { - await fetchAppDetails() - await checkRbacEnabled() - if (useNewRbac.value) { - await Promise.all([ - fetchAppRoleBindings(), - fetchAvailableAppRoles(), - fetchAvailableMembers(), - fetchAvailableGroups(), - ]) - } -}) +watch(() => props.appId, initializeData) -onMounted(async () => { - await fetchAppDetails() - await checkRbacEnabled() - if (useNewRbac.value) { - await Promise.all([ - fetchAppRoleBindings(), - fetchAvailableAppRoles(), - fetchAvailableMembers(), - fetchAvailableGroups(), - ]) - } -}) +onMounted(initializeData)supabase/functions/_backend/private/role_bindings.ts (2)
4-12: UsecreateHonofromutils/hono.tsfor app initialization.Per coding guidelines, all Hono application initialization should use
createHonofromutils/hono.tsinstead of directly instantiatingnew Hono(). This ensures consistent middleware setup across all backend endpoints.Suggested fix
-import { Hono } from 'hono/tiny' -import { middlewareAuth, useCors } from '../utils/hono.ts' +import { createHono, middlewareAuth, useCors } from '../utils/hono.ts' ... -export const app = new Hono<MiddlewareKeyVariables>() - -app.use('/', useCors) -app.use('/', middlewareAuth) +export const app = createHono() +app.use('*', middlewareAuth)Based on learnings,
createHonofromutils/hono.tsshould be used for all Hono framework application initialization and routing.
68-71: Use structured logging withcloudloginstead ofconsole.error.Per coding guidelines, backend logging should use
cloudlog({ requestId: c.get('requestId'), message: '...' })for structured logging.Suggested fix
+import { cloudlog } from '../utils/cloudflare.ts' ... catch (error) { - console.error('Error fetching role bindings:', error) + cloudlog({ requestId: c.get('requestId'), message: 'Error fetching role bindings', error }) return c.json({ error: 'Internal server error' }, 500) }src/services/permissions.ts (1)
169-222: Consider batch RPC optimization for future iterations.These helper functions execute N parallel RPC calls. For typical use cases with 2-5 permissions, this is acceptable. If permission checks grow, consider adding a batch RPC endpoint (e.g.,
rbac_check_permissions_batch) to reduce round trips.supabase/functions/_backend/utils/hono_middleware.ts (2)
65-93: Consider consolidating DB queries to reduce connection overhead.The middleware opens two separate database connections: one for resolving
app_id → orgIdand another for checkingrbac_is_enabled_for_org. These could be combined into a single connection/query whenorgIdis resolved fromapp_id.♻️ Suggested consolidation
if (appId) { let pgClient try { pgClient = getPgClient(c, true) const drizzleClient = getDrizzleClient(pgClient) - const appResult = await drizzleClient - .select({ ownerOrg: schema.apps.owner_org }) - .from(schema.apps) - .where(eq(schema.apps.app_id, appId)) - .limit(1) - if (appResult.length > 0 && appResult[0].ownerOrg) { - orgId = appResult[0].ownerOrg + // Resolve org and check RBAC in one query + const result = await drizzleClient.execute( + sql`SELECT a.owner_org, public.rbac_is_enabled_for_org(a.owner_org) as rbac_enabled + FROM public.apps a WHERE a.app_id = ${appId} LIMIT 1`, + ) + if (result.rows.length > 0) { + orgId = (result.rows[0] as any).owner_org + c.set('resolvedOrgId', orgId) + c.set('rbacEnabled', (result.rows[0] as any).rbac_enabled === true) + // Skip the second DB call below } } // ... rest of error handling } } - - // If we have an orgId, check if RBAC is enabled - if (orgId) { + // Only check RBAC separately if orgId was provided via resolver (not from app_id) + if (orgId && c.get('rbacEnabled') === undefined) {
424-432: Redundant null check onsubkey.The
if (subkey)check on line 424 is redundant since the function returns early on lines 416-418 ifsubkeyis null. The code works correctly, but the check adds unnecessary cognitive load.♻️ Simplify by removing redundant check
if (!subkey) { cloudlog({ requestId: c.get('requestId'), message: 'Invalid subkey', subkey_id }) return quickError(401, 'invalid_subkey', 'Invalid subkey') } if (subkey?.limited_to_apps && subkey?.limited_to_apps.length === 0 && subkey?.limited_to_orgs && subkey?.limited_to_orgs.length === 0) { cloudlog({ requestId: c.get('requestId'), message: 'Invalid subkey, no limited apps or orgs', subkey }) return quickError(401, 'invalid_subkey', 'Invalid subkey, no limited apps or orgs') } - if (subkey) { - c.set('subkey', subkey) - // Override auth context with subkey for RBAC - c.set('auth', { - userId: apikey!.user_id, - authType: 'apikey', - apikey: subkey, - } as AuthInfo) - } + c.set('subkey', subkey) + // Override auth context with subkey for RBAC + c.set('auth', { + userId: apikey!.user_id, + authType: 'apikey', + apikey: subkey, + } as AuthInfo) }src/constants/organizationTabs.ts (1)
8-10: Duplicate icon import.
IconSecurity(line 8) andIconShield(line 9) both import from~icons/heroicons/shield-check. Either reuseIconSecurityfor the role-assignments tab, or rename one import to clarify the distinction.♻️ Remove duplicate import
import IconInfo from '~icons/heroicons/information-circle' import IconSecurity from '~icons/heroicons/shield-check' -import IconShield from '~icons/heroicons/shield-check' import IconUserGroup from '~icons/heroicons/user-group' import IconUsers from '~icons/heroicons/users' export const organizationTabs: Tab[] = [ { label: 'general', key: '/settings/organization', icon: IconInfo }, { label: 'members', key: '/settings/organization/members', icon: IconUsers }, // Security tab is added dynamically in settings.vue for super_admins only { label: 'security', key: '/settings/organization/security', icon: IconSecurity }, { label: 'audit-logs', key: '/settings/organization/auditlogs', icon: IconAudit }, { label: 'groups', key: '/settings/organization/groups', icon: IconUserGroup }, - { label: 'role-assignments', key: '/settings/organization/role-assignments', icon: IconShield }, + { label: 'role-assignments', key: '/settings/organization/role-assignments', icon: IconSecurity },
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In `@RBAC_SYSTEM.md`:
- Around line 613-622: The doc is inconsistent about bundle scoping: the "Bundle
Permissions (scope: `app`)" header conflicts with other references to a real
bundle scope (e.g., role names like bundle_admin,
role_bindings.scope_type='bundle', p_bundle_id and bundle-only propagation).
Pick one model and make the text consistent: if bundles are a distinct scope,
change the header and note to "scope: bundle", update examples/notes to
reference role_bindings.scope_type='bundle' and p_bundle_id propagation
behavior; if bundles are actually app-scoped, remove bundle-scoped terminology
(bundle_admin role, role_bindings.scope_type='bundle', p_bundle_id) and explain
bundle permissions as app-scoped with corresponding role names and propagation
rules. Ensure the permissions table entries (e.g., bundle.read, bundle.update,
bundle.delete) and the explanatory note match the chosen scope model.
In `@supabase/functions/_backend/private/groups.ts`:
- Around line 391-399: Declare targetUserId before you assign to it to avoid
creating an implicit global or ReferenceError; e.g., add a variable declaration
like "let targetUserId: string | undefined" before the try/assignment and then
set "targetUserId = parsedBody.data?.user_id" as shown, keeping the subsequent
checks that call isUuid(targetUserId) and c.json intact so targetUserId is also
available in any surrounding catch/logging blocks.
- Around line 31-82: The comments in this file (for example the comment above
the GET handler at app.get('/:org_id') that reads "Récupérer les groupes" and
other French comments around handlers and blocks) must be converted to English
to satisfy the linter; update all French comment text to equivalent English
phrases (e.g., "List groups of an org" / "Retrieve groups") across this file
including the other comment locations noted (lines near 84, 118, 148, 166, 219,
237, 290, 308, 371, 401, 431, 484, 499) while leaving code and identifiers
(app.get, checkPermission, getPgClient, getDrizzleClient, cloudlogErr,
closeClient, etc.) unchanged. Ensure comments remain brief and descriptive and
run the linter to confirm the pipeline error is resolved.
In `@supabase/seed.sql`:
- Around line 546-549: The seed inserts in app_versions reference app_id
'com.test2.app' but there is no corresponding row in the apps INSERT, causing a
FK violation; update the apps INSERT block to include a new apps row for
'com.test2.app' (matching the org_id '34a8c55d-2d0f-4652-a43f-684c7a9403ac' and
appropriate defaults used by other app rows) so the app_versions entries that
call out 'com.test2.app' can reference an existing apps.id; ensure the new apps
entry uses the same column ordering and value types as the other app rows in
that INSERT.
♻️ Duplicate comments (3)
messages/vi.json (1)
1462-1473: Role labels are well-translated; previous feedback incorporated.The "role-app-reader" now correctly uses "Người xem ứng dụng" as suggested in a previous review. All role translations follow consistent patterns:
- App roles: "Quản trị viên/Nhà phát triển/Người tải lên/Người xem ứng dụng"
- Org roles: "Quản trị viên cấp cao/Quản trị viên/Quản lý thanh toán/Thành viên"
messages/hi.json (1)
1137-1137: Minor grammar refinement suggestion.The translation is functional, but there's a small grammatical polish opportunity: removing the extra "को" before "देखने" would read more naturally.
Optional tweak
- "plans-super-only": "केवल सुपर व्यवस्थापकों को ही योजनाओं और बिलिंग को देखने की अनुमति है", + "plans-super-only": "केवल सुपर व्यवस्थापकों को ही योजनाओं और बिलिंग देखने की अनुमति है",RBAC_SYSTEM.md (1)
154-163: Update documentation SQL for rbac_has_permission, rbac_check_permission_direct, and rbac_check_permission to match actual migration signatures.The documented function signatures in RBAC_SYSTEM.md (lines 750–765, 844–999) include a
p_bundle_idparameter that does not exist in the migration (supabase/migrations/20251222140030_rbac_system.sql). Additionally,rbac_check_permission_directparametersp_org_idandp_app_idare not optional in the migration, contradicting their documentedDEFAULT NULLclauses. The permissions tableCHECKconstraint also shows string literals in the doc ('platform', 'org', ...) rather than the function calls used in the migration (public.rbac_scope_platform(), etc.).
🧹 Nitpick comments (3)
supabase/seed.sql (1)
551-558: Consider addingapp_versions_metaentry forcom.test2.appversion 16.The
app_versions_metatable has entries for versions 3-7, 10, and 13, but the newly added version 16 (com.test2.appversion1.0.0) doesn't have a corresponding metadata entry. Whilebuiltinandunknownversions (14, 15) don't need metadata, the actual version (16) might benefit from having consistent test data.♻️ Optional: Add metadata for version 16
(10, NOW(), 'com.demoadmin.app', NOW(), 'admin123', 1500000), - (13, NOW(), 'com.stats.app', NOW(), 'stats123', 850000); + (13, NOW(), 'com.stats.app', NOW(), 'stats123', 850000), + (16, NOW(), 'com.test2.app', NOW(), 'test2123', 950000);supabase/functions/_backend/public/organization/delete.ts (1)
61-95: Add explicit return type and includeorg_idin success log for consistency.Two minor improvements:
- The function lacks an explicit return type annotation—adding
: Promise<boolean>improves readability and consistency withdeleteOrgImages.- The success log at line 93 omits
org_id, while all error logs include it. Adding it ensures consistent log structure for filtering/correlation.Suggested changes
-async function deleteOrgAppImages(storage: StorageBucket, orgId: string, folderName: string, requestId?: string) { +async function deleteOrgAppImages(storage: StorageBucket, orgId: string, folderName: string, requestId?: string): Promise<boolean> {- cloudlog({ requestId, message: 'deleted org app images', count: appFiles.length, folder: folderName }) + cloudlog({ requestId, message: 'deleted org app images', org_id: orgId, count: appFiles.length, folder: folderName })tests/rbac-permissions.test.ts (1)
118-131: Redundant flag resets inside transactional tests (and consider explicit NULL casts).
Because each test is wrapped in a transaction that’s rolled back, the extra “reset to legacy mode” updates are unnecessary noise. Also consider casting the last arg asNULL::textto avoid any future overload/ambiguity.Proposed simplification
describe('RBAC mode (use_new_rbac = true)', () => { beforeEach(async () => { // Enable RBAC globally for tests await query(` UPDATE public.rbac_settings SET use_new_rbac = true WHERE id = 1; `) }) - afterEach(async () => { - // Reset to legacy mode - await query(` - UPDATE public.rbac_settings SET use_new_rbac = false WHERE id = 1; - `) - }) - it('should check permissions via RBAC system when enabled', async () => { // ... }) })- NULL + NULL::textAlso applies to: 151-197, 236-273
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
RBAC_SYSTEM.mdmessages/de.jsonmessages/hi.jsonmessages/vi.jsonsupabase/functions/_backend/private/groups.tssupabase/functions/_backend/public/organization/delete.tssupabase/migrations/20251222140030_rbac_system.sqlsupabase/seed.sqltests/rbac-permissions.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- messages/de.json
🧰 Additional context used
📓 Path-based instructions (10)
supabase/functions/_backend/**/*.{ts,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
supabase/functions/_backend/**/*.{ts,js}: Backend code must be placed insupabase/functions/_backend/as shared code deployed to Cloudflare Workers (API/Plugin/Files workers), Supabase Edge Functions, and other platforms
UsecreateHonofromutils/hono.tsfor all Hono framework application initialization and routing
All database operations must usegetPgClient()orgetDrizzleClient()fromutils/pg.tsfor PostgreSQL access during active migration to Cloudflare D1
All Hono endpoint handlers must acceptContext<MiddlewareKeyVariables>and usec.get('requestId'),c.get('apikey'), andc.get('auth')for request context
Use structured logging withcloudlog({ requestId: c.get('requestId'), message: '...' })for all backend logging
UsemiddlewareAPISecretfor internal API endpoints andmiddlewareKeyfor external API keys; validate againstowner_orgin theapikeystable
Checkc.get('auth')?.authTypeto determine authentication type ('apikey' vs 'jwt') in backend endpoints
Use Drizzle ORM query patterns withschemafrompostgress_schema.tsfor all database operations; usealiasV2()for self-joins or multiple table references
Files:
supabase/functions/_backend/public/organization/delete.tssupabase/functions/_backend/private/groups.ts
supabase/functions/**/*.{ts,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Backend ESLint must pass before commit; run
bun lint:backendfor backend files
Files:
supabase/functions/_backend/public/organization/delete.tssupabase/functions/_backend/private/groups.ts
**/*.{vue,ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Run
bun lintto lint Vue, TypeScript, and JavaScript files; usebun lint:fixto auto-fix issues
Files:
supabase/functions/_backend/public/organization/delete.tssupabase/functions/_backend/private/groups.tstests/rbac-permissions.test.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use single quotes and no semicolons per
@antfu/eslint-configrules
Files:
supabase/functions/_backend/public/organization/delete.tssupabase/functions/_backend/private/groups.tstests/rbac-permissions.test.ts
supabase/functions/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
supabase/functions/**/*.ts: Never use the Supabase admin SDK with service key for user-facing APIs; always use client SDK with user authentication to enforce RLS policies. Admin SDK should only be used for internal operations (triggers, CRON jobs, etc.)
When admin access is unavoidable for a user-facing endpoint, sanitize all user inputs carefully to prevent PostgREST query injection
Files:
supabase/functions/_backend/public/organization/delete.tssupabase/functions/_backend/private/groups.ts
**/*.{ts,tsx,js,jsx,vue}
📄 CodeRabbit inference engine (AGENTS.md)
Run
bun lintor lint/format command before validating any backend or frontend task to ensure consistent formatting
Files:
supabase/functions/_backend/public/organization/delete.tssupabase/functions/_backend/private/groups.tstests/rbac-permissions.test.ts
supabase/functions/_backend/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
Place core backend logic in supabase/functions/_backend/ with plugins, private, public, triggers, and utils subdirectories
Files:
supabase/functions/_backend/public/organization/delete.tssupabase/functions/_backend/private/groups.ts
supabase/seed.sql
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Seed database with
supabase db resetto apply all migrations and test data fromsupabase/seed.sqlUpdate supabase/seed.sql for new or evolved tests; keep fixtures focused on current behavior while leaving committed migrations unchanged
Files:
supabase/seed.sql
tests/**/*.{ts,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Backend tests must use helpers from
tests/test-utils.tsincludinggetEndpointUrl(path)for correct worker routing andUSE_CLOUDFLARE_WORKERS=truefor CF Workers testing
Files:
tests/rbac-permissions.test.ts
tests/**/*.test.ts
📄 CodeRabbit inference engine (AGENTS.md)
tests/**/*.test.ts: ALL TEST FILES RUN IN PARALLEL - design tests accordingly with proper isolation and useit.concurrent()to maximize parallelism within the same file
Create dedicated seed data for tests that modify shared resources instead of reusing existing data; use unique naming conventions prefixed with test file name or feature being tested
Only reuse existing seed data if you only READ the data without modifying it, or create your OWN child resources under it without modifying the parent
Backend tests located in tests/ directory use Vitest test runner and require running Supabase instance
Files:
tests/rbac-permissions.test.ts
🧠 Learnings (29)
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Use `middlewareAPISecret` for internal API endpoints and `middlewareKey` for external API keys; validate against `owner_org` in the `apikeys` table
Applied to files:
supabase/functions/_backend/public/organization/delete.tssupabase/functions/_backend/private/groups.ts
📚 Learning: 2025-12-24T14:11:10.256Z
Learnt from: WcaleNieWolny
Repo: Cap-go/capgo PR: 1300
File: supabase/migrations/20251224103713_2fa_enforcement.sql:409-539
Timestamp: 2025-12-24T14:11:10.256Z
Learning: In supabase/migrations for get_orgs_v6 and get_orgs_v7: The inner functions with user_id parameter (get_orgs_v6(uuid) and get_orgs_v7(uuid)) should NOT be granted to anon/authenticated roles as this allows any user to query other users' organizations; only the no-argument wrapper functions should be public as they perform authentication checks.
Applied to files:
supabase/functions/_backend/public/organization/delete.tssupabase/functions/_backend/private/groups.tssupabase/seed.sqlRBAC_SYSTEM.md
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Use structured logging with `cloudlog({ requestId: c.get('requestId'), message: '...' })` for all backend logging
Applied to files:
supabase/functions/_backend/public/organization/delete.tssupabase/functions/_backend/private/groups.ts
📚 Learning: 2025-12-23T01:19:15.067Z
Learnt from: riderx
Repo: Cap-go/capgo PR: 1297
File: src/components/dashboard/DeploymentBanner.vue:77-79
Timestamp: 2025-12-23T01:19:15.067Z
Learning: In the Cap-go codebase, the 'owner' role exists only at the organization level (used in src/stores/organization.ts), not as an app-level permission. App-level permissions use the user_min_right database enum which includes: read, upload, write, admin, super_admin (but NOT owner). When checking app permissions, never include 'owner' in the role checks.
Applied to files:
supabase/functions/_backend/public/organization/delete.tssupabase/functions/_backend/private/groups.tsRBAC_SYSTEM.md
📚 Learning: 2026-01-10T04:55:25.264Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-10T04:55:25.264Z
Learning: Applies to supabase/migrations/*.sql : Use `get_identity_org_appid()` for RLS policies when app_id exists on the table instead of `get_identity()` directly
Applied to files:
supabase/functions/_backend/public/organization/delete.tssupabase/seed.sqlRBAC_SYSTEM.md
📚 Learning: 2025-12-25T11:22:19.594Z
Learnt from: WcaleNieWolny
Repo: Cap-go/capgo PR: 1300
File: supabase/migrations/20251224103713_2fa_enforcement.sql:85-96
Timestamp: 2025-12-25T11:22:19.594Z
Learning: In the 2FA enforcement implementation for supabase/migrations: When an org has enforcing_2fa=true, all users including super_admins must have 2FA enabled before accessing any org functions (including check_org_members_2fa_enabled); this is intentional behavior to ensure consistent security enforcement without exceptions for admins.
Applied to files:
supabase/functions/_backend/public/organization/delete.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : All Hono endpoint handlers must accept `Context<MiddlewareKeyVariables>` and use `c.get('requestId')`, `c.get('apikey')`, and `c.get('auth')` for request context
Applied to files:
supabase/functions/_backend/public/organization/delete.tssupabase/functions/_backend/private/groups.ts
📚 Learning: 2026-01-10T04:55:25.264Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-10T04:55:25.264Z
Learning: Applies to supabase/functions/_backend/**/*.ts : Place core backend logic in supabase/functions/_backend/ with plugins, private, public, triggers, and utils subdirectories
Applied to files:
supabase/functions/_backend/private/groups.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Use `createHono` from `utils/hono.ts` for all Hono framework application initialization and routing
Applied to files:
supabase/functions/_backend/private/groups.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Check `c.get('auth')?.authType` to determine authentication type ('apikey' vs 'jwt') in backend endpoints
Applied to files:
supabase/functions/_backend/private/groups.tsRBAC_SYSTEM.md
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Backend code must be placed in `supabase/functions/_backend/` as shared code deployed to Cloudflare Workers (API/Plugin/Files workers), Supabase Edge Functions, and other platforms
Applied to files:
supabase/functions/_backend/private/groups.ts
📚 Learning: 2026-01-10T04:55:25.264Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-10T04:55:25.264Z
Learning: Applies to supabase/functions/**/*.ts : Never use the Supabase admin SDK with service key for user-facing APIs; always use client SDK with user authentication to enforce RLS policies. Admin SDK should only be used for internal operations (triggers, CRON jobs, etc.)
Applied to files:
supabase/functions/_backend/private/groups.tsRBAC_SYSTEM.md
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : All database operations must use `getPgClient()` or `getDrizzleClient()` from `utils/pg.ts` for PostgreSQL access during active migration to Cloudflare D1
Applied to files:
supabase/functions/_backend/private/groups.tsRBAC_SYSTEM.mdtests/rbac-permissions.test.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to cloudflare_workers/api/index.ts : API Worker (port 8787) routes: `/bundle`, `/app`, `/device`, `/channel`, `/private/*`, `/triggers`
Applied to files:
supabase/functions/_backend/private/groups.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/utils/postgress_schema.ts : Schema definitions must be placed in `utils/postgress_schema.ts` using Drizzle ORM and never edited in committed migration files
Applied to files:
supabase/functions/_backend/private/groups.tsRBAC_SYSTEM.md
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Use Drizzle ORM query patterns with `schema` from `postgress_schema.ts` for all database operations; use `aliasV2()` for self-joins or multiple table references
Applied to files:
supabase/functions/_backend/private/groups.ts
📚 Learning: 2026-01-10T04:55:25.264Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-10T04:55:25.264Z
Learning: Applies to supabase/functions/**/*.ts : When admin access is unavoidable for a user-facing endpoint, sanitize all user inputs carefully to prevent PostgREST query injection
Applied to files:
supabase/functions/_backend/private/groups.tsRBAC_SYSTEM.md
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/**/*.{ts,js} : Backend ESLint must pass before commit; run `bun lint:backend` for backend files
Applied to files:
supabase/functions/_backend/private/groups.ts
📚 Learning: 2026-01-10T04:55:25.264Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-10T04:55:25.264Z
Learning: Applies to supabase/seed.sql : Update supabase/seed.sql for new or evolved tests; keep fixtures focused on current behavior while leaving committed migrations unchanged
Applied to files:
supabase/seed.sqlRBAC_SYSTEM.mdtests/rbac-permissions.test.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/seed.sql : Seed database with `supabase db reset` to apply all migrations and test data from `supabase/seed.sql`
Applied to files:
supabase/seed.sql
📚 Learning: 2026-01-10T04:55:25.264Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-10T04:55:25.264Z
Learning: Applies to supabase/migrations/*.sql : Never edit previously committed migrations; treat new seeding in migrations as part of the current feature
Applied to files:
supabase/seed.sqlRBAC_SYSTEM.md
📚 Learning: 2026-01-10T04:55:25.264Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-10T04:55:25.264Z
Learning: Applies to supabase/migrations/*.sql : Only use `get_identity_org_allowed()` as a last resort when the table genuinely has no app_id column and there is no way to join to get an app_id
Applied to files:
supabase/seed.sqlRBAC_SYSTEM.md
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Backend tests modify local database; always reset database with `supabase db reset` before running tests to ensure clean state
Applied to files:
supabase/seed.sqltests/rbac-permissions.test.ts
📚 Learning: 2026-01-10T04:55:25.264Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-10T04:55:25.264Z
Learning: Applies to supabase/migrations/*.sql : Use Supabase CLI for every migration and operational task; avoid manual changes through dashboard or direct SQL
Applied to files:
RBAC_SYSTEM.md
📚 Learning: 2026-01-10T04:55:25.264Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-10T04:55:25.264Z
Learning: Applies to tests/**/*.test.ts : Create dedicated seed data for tests that modify shared resources instead of reusing existing data; use unique naming conventions prefixed with test file name or feature being tested
Applied to files:
tests/rbac-permissions.test.ts
📚 Learning: 2026-01-10T04:55:25.264Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-10T04:55:25.264Z
Learning: Applies to tests/**/*.test.ts : ALL TEST FILES RUN IN PARALLEL - design tests accordingly with proper isolation and use `it.concurrent()` to maximize parallelism within the same file
Applied to files:
tests/rbac-permissions.test.ts
📚 Learning: 2026-01-10T04:55:25.264Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-10T04:55:25.264Z
Learning: Applies to tests/**/*.test.ts : Backend tests located in tests/ directory use Vitest test runner and require running Supabase instance
Applied to files:
tests/rbac-permissions.test.ts
📚 Learning: 2026-01-10T04:55:25.264Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-10T04:55:25.264Z
Learning: Applies to tests/**/*.test.ts : Only reuse existing seed data if you only READ the data without modifying it, or create your OWN child resources under it without modifying the parent
Applied to files:
tests/rbac-permissions.test.ts
📚 Learning: 2026-01-10T04:55:25.264Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-10T04:55:25.264Z
Learning: Applies to playwright/e2e/**/*.spec.ts : Use Playwright MCP suite to cover customer-facing flows; add scenarios under playwright/e2e and run with `bun run test:front` before shipping UI changes
Applied to files:
tests/rbac-permissions.test.ts
🧬 Code graph analysis (3)
supabase/functions/_backend/public/organization/delete.ts (3)
supabase/functions/_backend/utils/hono.ts (3)
MiddlewareKeyVariables(27-44)simpleError(268-270)quickError(245-258)supabase/functions/_backend/utils/logging.ts (1)
cloudlog(3-15)supabase/functions/_backend/utils/rbac.ts (1)
checkPermission(241-314)
supabase/functions/_backend/private/groups.ts (5)
supabase/functions/_backend/utils/hono.ts (1)
MiddlewareKeyVariables(27-44)supabase/functions/_backend/utils/version.ts (1)
version(1-1)supabase/functions/_backend/utils/rbac.ts (1)
checkPermission(241-314)supabase/functions/_backend/utils/pg.ts (3)
getPgClient(187-218)getDrizzleClient(220-222)closeClient(275-280)supabase/functions/_backend/utils/postgres_schema.ts (2)
groups(169-177)schema(210-225)
tests/rbac-permissions.test.ts (1)
tests/test-utils.ts (3)
POSTGRES_URL(7-7)ORG_ID(43-43)USER_ID(46-46)
🪛 GitHub Actions: Run tests
supabase/functions/_backend/public/organization/delete.ts
[error] 1-1: CI step failed with exit code 2
supabase/functions/_backend/private/groups.ts
[error] 31-31: groupes should be groups, grouped, or groupees
[error] 53-53: groupes should be groups, grouped, or groupees
[error] 84-84: groupe should be group or grouped
[error] 118-118: groupe should be group or grouped
[error] 148-148: groupe should be group or grouped
[error] 166-166: groupe should be group or grouped
[error] 219-219: groupe should be group or grouped
[error] 237-237: groupe should be group or grouped
[error] 290-290: groupe should be group or grouped
[error] 308-308: groupe should be group or grouped
[error] 371-371: groupe should be group or grouped
[error] 484-484: existant should be existent
[error] 1-1: CI step failed with exit code 2
supabase/seed.sql
[error] 1-1: CI step failed with exit code 2
messages/vi.json
[error] 1-1: CI step failed with exit code 2
messages/hi.json
[error] 1-1: CI step failed with exit code 2
RBAC_SYSTEM.md
[error] 1-1: CI step failed with exit code 2
tests/rbac-permissions.test.ts
[error] 1-1: CI step failed with exit code 2
🪛 GitHub Check: Run tests
supabase/functions/_backend/private/groups.ts
[warning] 308-308:
"groupe" should be "group" or "grouped".
[warning] 290-290:
"groupe" should be "group" or "grouped".
[warning] 237-237:
"groupe" should be "group" or "grouped".
[warning] 219-219:
"groupe" should be "group" or "grouped".
[warning] 166-166:
"groupe" should be "group" or "grouped".
[warning] 148-148:
"groupe" should be "group" or "grouped".
[warning] 118-118:
"groupe" should be "group" or "grouped".
[warning] 84-84:
"groupe" should be "group" or "grouped".
[warning] 53-53:
"groupes" should be "groups" or "grouped" or "groupees".
[warning] 31-31:
"groupes" should be "groups" or "grouped" or "groupees".
🪛 LanguageTool
RBAC_SYSTEM.md
[style] ~802-~802: This adverb was used twice in the sentence. Consider removing one of them or replacing them with a synonym.
Context: ...that bundle - Downward propagation only (no upward propagation) 7. *Return...
(ADVERB_REPETITION_PREMIUM)
🔇 Additional comments (21)
messages/vi.json (1)
1217-1224: RBAC UI strings look good; previous feedback addressed.The translations are consistent with Vietnamese UI conventions. The "rbac-system-enabled" value has been shortened as previously suggested, and the phrasing for migration-related strings ("các vai trò kế thừa vẫn hiển thị trong quá trình di chuyển/chuyển đổi") is clear.
supabase/functions/_backend/private/groups.ts (6)
1-30: LGTM — Imports and initialization follow backend conventions.The file correctly uses
createHonofromutils/hono.ts, imports DB utilities properly, and includes useful validation helpers (isUuid,parseJsonBody). Based on learnings, this follows the required patterns forsupabase/functions/_backend/**/*.ts.
84-146: LGTM — Create group endpoint properly validates and creates non-system groups.The endpoint correctly:
- Validates auth and permissions before parsing body
- Ensures
is_system: falsefor user-created groups- Uses proper DB client lifecycle
148-217: LGTM — Update endpoint correctly protects system groups and handles partial updates.Good pattern: fetches group first to resolve
org_idfor permission check, blocks system group modification, and handles partial updates by preserving existing values when fields are undefined.
256-270: LGTM — Atomic deletion with transaction properly cleans up role bindings.The transaction ensures both
role_bindingscleanup andgroupsdeletion happen atomically, preventing data inconsistency if either operation fails. This addresses the previous concern about non-atomic deletes.
461-526: LGTM — Member removal endpoint follows proper patterns.Clean implementation with proper auth, UUID validation, group lookup, and permission check before deletion.
324-333:schema.usersis properly exported in postgres_schema.ts.The users table is correctly exported as a named export at line 106. The query in groups.ts correctly uses this export in the innerJoin, and no changes are needed.
messages/hi.json (4)
1217-1224: LGTM! RBAC keys are well-translated and terminology is now consistent.The past review concern about "सुपर एडमिन" vs "सुपर व्यवस्थापक" inconsistency has been properly addressed — Line 1223 now correctly uses "सुपर व्यवस्थापक" to match the established terminology throughout the file.
1256-1256: LGTM!The grammar has been updated to include "के … ही" construction as previously suggested. The translation now reads naturally.
1462-1473: LGTM! Role-related keys are well-translated.The new RBAC role translations maintain consistent terminology:
- Uses "व्यवस्थापक" for admin roles throughout
- Clear Hindi equivalents for all app-level and org-level roles
- Confirmation prompt for role removal is appropriately phrased
1-3: JSON syntax is valid; pipeline failure is unrelated to this translation file.The
messages/hi.jsonfile is syntactically correct with proper structure. The inlang I18n validation rules configured inproject.inlang/settings.jsonare not executed in the CI pipeline (the linting step is disabled pending an npm issue fix), so the exit code 2 failure originates from another CI step such as typo checks, linting, or backend tests—not from this translation file.supabase/seed.sql (3)
46-53: LGTM! RBAC table initialization is correctly ordered.The truncation order respects foreign key constraints (role_bindings → group_members → groups), and the upsert pattern for
rbac_settingsensures deterministic RBAC state across test runs.
484-485: LGTM! New org membership entries for RBAC testing.These entries add cross-org membership scenarios (admin user with
adminrole in Demo org, test user withwriterole in Test2 org), which are useful for testing the new RBAC permission system.
1025-1042: Critical: Undeclaredv_orgvariable causes syntax error.In PL/pgSQL, "all variables used in a block must be declared in the declarations section of the block. (The only exceptions are that the loop variable of a FOR loop iterating over a range of integer values is automatically declared as an integer variable, and likewise the loop variable of a FOR loop iterating over a cursor's result is automatically declared as a record variable.)"
Line 1036 uses
v_orgas a record variable in a FOR loop over a query result, butv_orgis not declared in the DECLARE block (lines 1025-1026). This will fail with an error like "loop variable of loop over rows must be a record or row variable".This is the likely cause of the pipeline failure.
🐛 Proposed fix: Declare v_org as RECORD
DO $$ DECLARE v_migration_result jsonb; + v_org RECORD; BEGIN -- Execute seeding functions PERFORM public.reset_and_seed_data();⛔ Skipped due to learnings
Learnt from: WcaleNieWolny Repo: Cap-go/capgo PR: 1300 File: supabase/migrations/20251224103713_2fa_enforcement.sql:409-539 Timestamp: 2025-12-24T14:11:10.256Z Learning: In supabase/migrations for get_orgs_v6 and get_orgs_v7: The inner functions with user_id parameter (get_orgs_v6(uuid) and get_orgs_v7(uuid)) should NOT be granted to anon/authenticated roles as this allows any user to query other users' organizations; only the no-argument wrapper functions should be public as they perform authentication checks.Learnt from: CR Repo: Cap-go/capgo PR: 0 File: .github/copilot-instructions.md:0-0 Timestamp: 2025-12-23T02:53:12.055Z Learning: Applies to supabase/seed.sql : Seed database with `supabase db reset` to apply all migrations and test data from `supabase/seed.sql`Learnt from: CR Repo: Cap-go/capgo PR: 0 File: AGENTS.md:0-0 Timestamp: 2026-01-10T04:55:25.264Z Learning: Applies to supabase/seed.sql : Update supabase/seed.sql for new or evolved tests; keep fixtures focused on current behavior while leaving committed migrations unchangedsupabase/functions/_backend/public/organization/delete.ts (4)
1-14: LGTM on imports and type definitions.The imports correctly use
MiddlewareKeyVariablesfor typed context,checkPermissionfor RBAC authorization, andcloudlogfor structured logging, aligning with coding guidelines.
15-59: Well-implemented storage cleanup with proper error propagation.This implementation correctly addresses the past review concerns: storage API errors are now properly checked via
{ data, error }destructuring, and the function returns a boolean to propagate failure status to the caller. The per-entry error logging withrequestIdandorg_idprovides good observability.
97-125: LGTM on RBAC-integrated organization deletion.The function correctly:
- Uses
checkPermissionwith the dedicated'org.delete'permission (addressing the past review concern about using a semantic permission)- Throws a structured error when image cleanup fails (preventing orphaned images)
- Uses
supabaseApikeyfor the database deletion, respecting RLS policies per coding guidelines
97-97: No issues identified. The code is syntactically correct, follows established backend patterns used throughout the codebase, and properly implements the organization deletion logic with permission checks and image cleanup.tests/rbac-permissions.test.ts (2)
14-45: Per-test DB transactions + cleanup look solid (parallel-safe).
Using a dedicatedPoolClientwithBEGINinbeforeEachandROLLBACK+release()inafterEachis the right isolation strategy fortests/**/*.test.tsrunning in parallel.
11-13: This review comment is incorrect—TEST_APP_ID is already properly seeded.The app
com.demo.appis explicitly seeded insupabase/seed.sql(line 528) with the exactORG_IDandUSER_IDfromtest-utils.ts. The user also has the requiredsuper_adminrole binding in that organization (seed.sql line 477). All tests in this file are read-only SELECT queries against this seeded data, wrapped in transactions for proper isolation. No additional app creation or fixture setup is needed.Likely an incorrect or invalid review comment.
RBAC_SYSTEM.md (1)
825-1040: Clear client-safety guidance: wrapper RPC + GRANTs are well documented.
Calling outrbac_check_permission()as the client-safe wrapper (and keeping_directservice-role only) plus explicit REVOKE/GRANT examples is a strong guardrail against future regressions.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@AGENTS.md`:
- Line 196: Update the AGENTS.md line that references the Conventional Commits
URL by wrapping the bare URL in angle brackets; replace "Conventional Commits
v1.0.0 (https://www.conventionalcommits.org/en/v1.0.0/)" with text that keeps
the label "Conventional Commits v1.0.0" but surrounds the URL with angle
brackets so the markdown becomes "Conventional Commits v1.0.0
(<https://www.conventionalcommits.org/en/v1.0.0/>)" to satisfy markdownlint.
In `@src/components/tables/AccessTable.vue`:
- Around line 140-144: Move the capture of the selected role until after the
user has interacted with the dialog: don’t create roleSnapshot from
selectedRole.value before awaiting dialogStore.onDialogDismiss(); instead await
dialogStore.onDialogDismiss(), return undefined if dismissed, then read
selectedRole.value into roleSnapshot and return it (this ensures changeUserRole
uses the user-selected value).
♻️ Duplicate comments (2)
supabase/functions/_backend/private/groups.ts (1)
391-399: Critical: Missing variable declaration causes ReferenceError in strict mode.Line 391 assigns to
targetUserIdwithout a declaration keyword (const/let). This will throw aReferenceErrorin strict mode or create an implicit global otherwise. SincetargetUserIdis also referenced in the catch block (line 449) for logging, declare it withletbefore the try block.🐛 Proposed fix
app.post('/:group_id/members', async (c: Context<MiddlewareKeyVariables>) => { const groupId = c.req.param('group_id') const userId = c.get('auth')?.userId + let targetUserId: string | undefined if (!userId) { return c.json({ error: 'Unauthorized' }, 401)Then at line 391:
- targetUserId = parsedBody.data?.user_id + targetUserId = parsedBody.data?.user_id as string | undefinedsrc/components/tables/AccessTable.vue (1)
100-102: RPC response type cast still present.The
(data as any)cast on line 101 indicates the RPC return type isn't yet available in the generated types. After RBAC migrations are applied, runbun typesto regenerate TypeScript types and replace this cast with proper typing. Based on learnings, this step is required after schema changes.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
AGENTS.mdsrc/components/tables/AccessTable.vuesupabase/functions/_backend/private/groups.ts
🧰 Additional context used
📓 Path-based instructions (14)
supabase/functions/_backend/**/*.{ts,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
supabase/functions/_backend/**/*.{ts,js}: Backend code must be placed insupabase/functions/_backend/as shared code deployed to Cloudflare Workers (API/Plugin/Files workers), Supabase Edge Functions, and other platforms
UsecreateHonofromutils/hono.tsfor all Hono framework application initialization and routing
All database operations must usegetPgClient()orgetDrizzleClient()fromutils/pg.tsfor PostgreSQL access during active migration to Cloudflare D1
All Hono endpoint handlers must acceptContext<MiddlewareKeyVariables>and usec.get('requestId'),c.get('apikey'), andc.get('auth')for request context
Use structured logging withcloudlog({ requestId: c.get('requestId'), message: '...' })for all backend logging
UsemiddlewareAPISecretfor internal API endpoints andmiddlewareKeyfor external API keys; validate againstowner_orgin theapikeystable
Checkc.get('auth')?.authTypeto determine authentication type ('apikey' vs 'jwt') in backend endpoints
Use Drizzle ORM query patterns withschemafrompostgress_schema.tsfor all database operations; usealiasV2()for self-joins or multiple table references
Files:
supabase/functions/_backend/private/groups.ts
supabase/functions/**/*.{ts,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Backend ESLint must pass before commit; run
bun lint:backendfor backend files
Files:
supabase/functions/_backend/private/groups.ts
**/*.{vue,ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Run
bun lintto lint Vue, TypeScript, and JavaScript files; usebun lint:fixto auto-fix issues
Files:
supabase/functions/_backend/private/groups.tssrc/components/tables/AccessTable.vue
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use single quotes and no semicolons per
@antfu/eslint-configrules
Files:
supabase/functions/_backend/private/groups.ts
supabase/functions/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
supabase/functions/**/*.ts: Never use the Supabase admin SDK with service key for user-facing APIs; always use client SDK with user authentication to enforce RLS policies. Admin SDK should only be used for internal operations (triggers, CRON jobs, etc.)
When admin access is unavoidable for a user-facing endpoint, sanitize all user inputs carefully to prevent PostgREST query injection
Files:
supabase/functions/_backend/private/groups.ts
**/*.{ts,tsx,js,jsx,vue}
📄 CodeRabbit inference engine (AGENTS.md)
Run
bun lintor lint/format command before validating any backend or frontend task to ensure consistent formatting
Files:
supabase/functions/_backend/private/groups.tssrc/components/tables/AccessTable.vue
supabase/functions/_backend/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
Place core backend logic in supabase/functions/_backend/ with plugins, private, public, triggers, and utils subdirectories
Files:
supabase/functions/_backend/private/groups.ts
src/**/*.vue
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
src/**/*.vue: Use Vue 3<script setup>syntax exclusively for all Vue component scripts
Use Tailwind utility classes for layout and spacing in Vue components
Use DaisyUI components (d-btn,d-input,d-card) for interactive elements in Vue components
Use Konsta components ONLY for safe area helpers (top/bottom insets) in Vue components; avoid other uses
UseuseRoute()fromvue-routerto access route parameters anduseRouter()for programmatic navigation in Vue components
src/**/*.vue: Konsta components are reserved only for safe area helpers; avoid importing konsta anywhere else in the app
Use Pinia stores for state management in Vue components
Files:
src/components/tables/AccessTable.vue
src/**/*.{ts,tsx,vue,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use
~/alias for imports fromsrc/directory in frontend TypeScript and Vue components
Files:
src/components/tables/AccessTable.vue
src/**/*.{vue,ts,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Frontend ESLint must pass before commit; run
bun lint:fixto auto-fix issues in frontend files
Files:
src/components/tables/AccessTable.vue
**/*.vue
📄 CodeRabbit inference engine (AGENTS.md)
**/*.vue: Use Vue 3 with Composition API and<script setup>syntax for frontend components
Use TailwindCSS utility classes and DaisyUI (d- prefixed classes) for styling instead of bespoke CSS
Files:
src/components/tables/AccessTable.vue
src/**/*.{ts,tsx,js,jsx,vue}
📄 CodeRabbit inference engine (AGENTS.md)
Use path alias
~/to map tosrc/for imports in TypeScript and JavaScript files
Files:
src/components/tables/AccessTable.vue
src/**/*.{vue,css}
📄 CodeRabbit inference engine (AGENTS.md)
Mirror the palette from src/styles/style.css when introducing new UI; use deep slate bases with Extract azure highlight (
#119eff) and soft radii
Files:
src/components/tables/AccessTable.vue
src/components/**/*.vue
📄 CodeRabbit inference engine (AGENTS.md)
Place reusable Vue components in src/components/ directory
Files:
src/components/tables/AccessTable.vue
🧠 Learnings (26)
📚 Learning: 2026-01-09T18:23:34.200Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-09T18:23:34.200Z
Learning: Read AGENTS.md for all development guidelines, commands, architecture details, and coding conventions for this repository
Applied to files:
AGENTS.md
📚 Learning: 2026-01-10T04:55:25.264Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-10T04:55:25.264Z
Learning: ALL sections in a PR created by AI agents MUST be marked with '(AI generated)' - failure to mark is a violation of transparency requirements
Applied to files:
AGENTS.md
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/**/*.{ts,js} : Backend ESLint must pass before commit; run `bun lint:backend` for backend files
Applied to files:
AGENTS.mdsupabase/functions/_backend/private/groups.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/migrations/**/*.sql : Database migrations must be created with `supabase migration new <feature_slug>` and never modify previously committed migrations
Applied to files:
AGENTS.md
📚 Learning: 2026-01-10T04:55:25.264Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-10T04:55:25.264Z
Learning: Applies to supabase/migrations/*.sql : Use Supabase CLI for every migration and operational task; avoid manual changes through dashboard or direct SQL
Applied to files:
AGENTS.md
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to src/**/*.{vue,ts,js} : Frontend ESLint must pass before commit; run `bun lint:fix` to auto-fix issues in frontend files
Applied to files:
AGENTS.md
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Backend code must be placed in `supabase/functions/_backend/` as shared code deployed to Cloudflare Workers (API/Plugin/Files workers), Supabase Edge Functions, and other platforms
Applied to files:
AGENTS.mdsupabase/functions/_backend/private/groups.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : All database operations must use `getPgClient()` or `getDrizzleClient()` from `utils/pg.ts` for PostgreSQL access during active migration to Cloudflare D1
Applied to files:
AGENTS.mdsupabase/functions/_backend/private/groups.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Use `createHono` from `utils/hono.ts` for all Hono framework application initialization and routing
Applied to files:
supabase/functions/_backend/private/groups.ts
📚 Learning: 2026-01-10T04:55:25.264Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-10T04:55:25.264Z
Learning: Applies to supabase/functions/_backend/**/*.ts : Place core backend logic in supabase/functions/_backend/ with plugins, private, public, triggers, and utils subdirectories
Applied to files:
supabase/functions/_backend/private/groups.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Use `middlewareAPISecret` for internal API endpoints and `middlewareKey` for external API keys; validate against `owner_org` in the `apikeys` table
Applied to files:
supabase/functions/_backend/private/groups.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Check `c.get('auth')?.authType` to determine authentication type ('apikey' vs 'jwt') in backend endpoints
Applied to files:
supabase/functions/_backend/private/groups.tssrc/components/tables/AccessTable.vue
📚 Learning: 2026-01-10T04:55:25.264Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-10T04:55:25.264Z
Learning: Applies to supabase/functions/**/*.ts : Never use the Supabase admin SDK with service key for user-facing APIs; always use client SDK with user authentication to enforce RLS policies. Admin SDK should only be used for internal operations (triggers, CRON jobs, etc.)
Applied to files:
supabase/functions/_backend/private/groups.ts
📚 Learning: 2025-12-24T14:11:10.256Z
Learnt from: WcaleNieWolny
Repo: Cap-go/capgo PR: 1300
File: supabase/migrations/20251224103713_2fa_enforcement.sql:409-539
Timestamp: 2025-12-24T14:11:10.256Z
Learning: In supabase/migrations for get_orgs_v6 and get_orgs_v7: The inner functions with user_id parameter (get_orgs_v6(uuid) and get_orgs_v7(uuid)) should NOT be granted to anon/authenticated roles as this allows any user to query other users' organizations; only the no-argument wrapper functions should be public as they perform authentication checks.
Applied to files:
supabase/functions/_backend/private/groups.tssrc/components/tables/AccessTable.vue
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/utils/postgress_schema.ts : Schema definitions must be placed in `utils/postgress_schema.ts` using Drizzle ORM and never edited in committed migration files
Applied to files:
supabase/functions/_backend/private/groups.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Use Drizzle ORM query patterns with `schema` from `postgress_schema.ts` for all database operations; use `aliasV2()` for self-joins or multiple table references
Applied to files:
supabase/functions/_backend/private/groups.tssrc/components/tables/AccessTable.vue
📚 Learning: 2026-01-10T04:55:25.264Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-10T04:55:25.264Z
Learning: Applies to supabase/functions/**/*.ts : When admin access is unavoidable for a user-facing endpoint, sanitize all user inputs carefully to prevent PostgREST query injection
Applied to files:
supabase/functions/_backend/private/groups.tssrc/components/tables/AccessTable.vue
📚 Learning: 2026-01-10T04:55:25.264Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-10T04:55:25.264Z
Learning: Applies to supabase/seed.sql : Update supabase/seed.sql for new or evolved tests; keep fixtures focused on current behavior while leaving committed migrations unchanged
Applied to files:
supabase/functions/_backend/private/groups.ts
📚 Learning: 2025-12-23T01:19:15.067Z
Learnt from: riderx
Repo: Cap-go/capgo PR: 1297
File: src/components/dashboard/DeploymentBanner.vue:77-79
Timestamp: 2025-12-23T01:19:15.067Z
Learning: In the Cap-go codebase, the 'owner' role exists only at the organization level (used in src/stores/organization.ts), not as an app-level permission. App-level permissions use the user_min_right database enum which includes: read, upload, write, admin, super_admin (but NOT owner). When checking app permissions, never include 'owner' in the role checks.
Applied to files:
supabase/functions/_backend/private/groups.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : All Hono endpoint handlers must accept `Context<MiddlewareKeyVariables>` and use `c.get('requestId')`, `c.get('apikey')`, and `c.get('auth')` for request context
Applied to files:
supabase/functions/_backend/private/groups.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Use structured logging with `cloudlog({ requestId: c.get('requestId'), message: '...' })` for all backend logging
Applied to files:
supabase/functions/_backend/private/groups.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to cloudflare_workers/api/index.ts : API Worker (port 8787) routes: `/bundle`, `/app`, `/device`, `/channel`, `/private/*`, `/triggers`
Applied to files:
supabase/functions/_backend/private/groups.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Run `bun types` after schema changes to regenerate TypeScript types from Supabase
Applied to files:
src/components/tables/AccessTable.vue
📚 Learning: 2026-01-10T04:55:25.264Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-10T04:55:25.264Z
Learning: Applies to supabase/migrations/*.sql : Use `get_identity_org_appid()` for RLS policies when app_id exists on the table instead of `get_identity()` directly
Applied to files:
src/components/tables/AccessTable.vue
📚 Learning: 2025-05-29T18:21:17.703Z
Learnt from: WcaleNieWolny
Repo: Cap-go/capgo PR: 1107
File: src/components/dashboard/AppSetting.vue:85-182
Timestamp: 2025-05-29T18:21:17.703Z
Learning: In setUpdateChannelSync function in src/components/dashboard/AppSetting.vue, the database update code at the end is intentionally outside the conflict-resolution if block and should run for cases where sync is being toggled without conflicts or after conflicts are resolved. The return statements inside the dialog handling correctly prevent database updates when the user cancels.
Applied to files:
src/components/tables/AccessTable.vue
📚 Learning: 2025-12-23T01:19:04.593Z
Learnt from: riderx
Repo: Cap-go/capgo PR: 1297
File: src/components/dashboard/DeploymentBanner.vue:77-79
Timestamp: 2025-12-23T01:19:04.593Z
Learning: In the Cap-go codebase, ensure that app permission checks never include the role 'owner'. App-level permissions should be based on the user_min_right enum with values: read, upload, write, admin, super_admin (and NOT owner). This pattern applies across Vue components that perform permission checks; if you find a check referencing 'owner' for app-level access, replace it with the appropriate user_min_right value and keep organization-level owner handling in organization.ts.
Applied to files:
src/components/tables/AccessTable.vue
🪛 markdownlint-cli2 (0.18.1)
AGENTS.md
196-196: Bare URL used
(MD034, no-bare-urls)
🔇 Additional comments (16)
AGENTS.md (1)
190-197: Helpful editorial clarifications for development standards.These additions provide clear guidance on code comments and commit message conventions, improving consistency across the codebase. The requirement for English comments and adherence to Conventional Commits are both reasonable standards that will benefit maintainability and collaboration.
supabase/functions/_backend/private/groups.ts (7)
1-30: LGTM! Imports and initialization follow backend conventions.The file correctly uses
createHonofromutils/hono.ts, imports the proper database utilities (getPgClient,getDrizzleClient,closeClient), and includes structured logging withcloudlogErr. The UUID validation and JSON parsing helpers are well-implemented.
31-82: LGTM! Well-structured endpoint with proper auth, validation, and resource cleanup.The endpoint correctly validates auth and UUID format before permission checks, uses the proper database client lifecycle pattern with
finallyblock cleanup, and includes structured error logging withrequestId.
84-146: LGTM! Create group endpoint properly validates inputs and enforces admin permissions.JSON parsing occurs after authentication and authorization checks, following the recommended pattern from previous reviews.
148-217: LGTM! Update endpoint correctly protects system groups and validates ownership.The endpoint properly fetches the group first to obtain
org_idfor permission checking, and includes protection against modifying system groups.
256-270: LGTM! Atomic deletion with transaction ensures data consistency.The transactional handling of
role_bindingsandgroupsdeletion addresses the previous concern about non-atomic operations. This prevents orphaned role bindings if the group deletion fails.
290-351: LGTM! Members endpoint correctly joins user data and enforces read permissions.The inner join with
schema.usersproperly enriches the response with email addresses. Permission checking usesorg.read_memberswhich is appropriate for read-only access.
461-526: LGTM! Member removal endpoint properly validates both IDs and enforces admin permissions.Unlike the POST endpoint,
targetUserIdhere is correctly declared viac.req.param('user_id'). The endpoint follows the established pattern for auth, validation, and cleanup.src/components/tables/AccessTable.vue (8)
1-19: LGTM!Imports follow the
~/alias convention and use Vue 3 Composition API correctly. Props are properly typed.
20-61: LGTM!The
RoleBindinginterface is well-defined, andappRoleOptionsprovides a clean computed list of translated app-level roles.
63-83: LGTM!Properly handles the stale
app.valuecase by setting toundefinedon error or miss, and resetscanUpdateUserRolesappropriately.
147-197: LGTM!Permission backstop and role validation are now in place. The role lookup and update logic is sound, with proper error handling and user feedback.
199-240: LGTM!The dialog dismissal pattern correctly handles all cases including backdrop clicks and Escape key. The
lastButtonRolecheck ensures deletion only proceeds on explicit confirmation.
242-263: LGTM!The data loading flow correctly sequences
loadAppInfobeforefetchData, and the watcher withimmediate: trueensures data loads on mount.
265-339: LGTM!The filtering logic handles null values safely, and the dynamic columns correctly gate action buttons based on permission state.
342-383: LGTM!Template correctly uses Tailwind utilities and DaisyUI components per coding guidelines. The Teleport now uses
dialogOptions.idfor reliable modal detection.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
supabase/functions/_backend/triggers/on_user_delete.ts (2)
358-447: Avoid “all-or-nothing” cleanup: don’t abort on one lookup failure; don’t let one failing promise cancel the rest.
- The repeated
if (!X) return c.json(BRES)(Line 359-408) means a transient failure in any single lookup prevents subscription/Bento cleanup for sole-admin orgs.await Promise.all(promises)(Line 433) means one failingcancelSubscription/unsubscribeBento/storage deletion can abort all remaining cleanup work.Proposed resilient pattern
- const legacyOrgIds = await fetchLegacySuperAdminOrgIds(c, supabase, record.id) - if (!legacyOrgIds) { - return c.json(BRES) - } + const legacyOrgIds = (await fetchLegacySuperAdminOrgIds(c, supabase, record.id)) ?? [] @@ - const directRbacBindings = await fetchDirectRbacBindings(c, supabase, record.id) - if (!directRbacBindings) { - return c.json(BRES) - } + const directRbacBindings = (await fetchDirectRbacBindings(c, supabase, record.id)) ?? [] @@ - const groupIds = await fetchUserGroupIds(c, supabase, record.id) - if (!groupIds) { - return c.json(BRES) - } + const groupIds = (await fetchUserGroupIds(c, supabase, record.id)) ?? [] @@ - const groupRbacBindings = await fetchGroupRbacBindings(c, supabase, groupIds) - if (!groupRbacBindings) { - return c.json(BRES) - } + const groupRbacBindings = (await fetchGroupRbacBindings(c, supabase, groupIds)) ?? [] @@ - await Promise.all(promises) + const results = await Promise.allSettled(promises) + for (const r of results) { + if (r.status === 'rejected') { + logFailure(c, 'user deletion cleanup task failed', r.reason) + } + }
449-456: Don’t log full user records (PII risk).
cloudlog({ ..., message: 'record', record })(Line 451) will likely ship user PII (email, etc.) to logs. Prefer logging only stable identifiers (e.g.,user_id, maybe a redacted email domain if truly needed).Proposed change
- cloudlog({ requestId: c.get('requestId'), message: 'record', record }) + cloudlog({ requestId: c.get('requestId'), message: 'on_user_delete: received', user_id: record.id })supabase/functions/_backend/private/delete_failed_version.ts (1)
2-2: UsecreateHonoinstead ofnew Hono()per coding guidelines.Line 16 uses
new Hono<MiddlewareKeyVariables>()directly instead ofcreateHonofromutils/hono.ts. As per coding guidelines, all Hono framework application initialization should usecreateHonoto ensure consistent request ID handling, logging, and error formatting across the codebase.Proposed fix
import type { MiddlewareKeyVariables } from '../utils/hono.ts' -import { Hono } from 'hono/tiny' -import { parseBody, quickError, simpleError } from '../utils/hono.ts' +import { createHono, parseBody, quickError, simpleError } from '../utils/hono.ts' import { middlewareKey } from '../utils/hono_middleware.ts' import { cloudlog } from '../utils/logging.ts' import { logsnag } from '../utils/logsnag.ts' import { checkPermission } from '../utils/rbac.ts' import { s3 } from '../utils/s3.ts' import { supabaseApikey } from '../utils/supabase.ts' +import { version } from '../utils/version.ts' ... -export const app = new Hono<MiddlewareKeyVariables>() +export const app = createHono('', version)Also applies to: 16-16
🤖 Fix all issues with AI agents
In `@RBAC_SYSTEM.md`:
- Around line 193-198: There is a contradiction: one place states that org_admin
"cannot delete channels" while other places (the org_admin example listing "all
channel.* permissions" and the permission matrix granting channel.delete to
org_admin) allow channel deletion; decide which rule is correct and make all
three spots consistent — if org_admin should not delete channels, remove
channel.delete from the org_admin example list and from the permission matrix
row that grants channel.delete to org_admin and update any nearby explanatory
text; if org_admin should be allowed to delete channels, remove or reword the
"cannot delete channels" sentence to reflect that and ensure the example list
and permission matrix remain aligned.
In `@supabase/functions/_backend/public/statistics/index.ts`:
- Line 688: Replace the non-error logger call cloudlogErr(...) used to record
organization IDs with the informational structured logger cloudlog(...) so that
only true errors go to error logs; locate the call to cloudlogErr({ requestId:
c.get('requestId'), message: 'orgs', data: orgIds }) and swap it to cloudlog
while preserving the same structured payload (requestId, message, data) and
context so organization IDs are recorded as informational logs rather than
errors.
In `@supabase/functions/_backend/utils/hono_middleware.ts`:
- Around line 18-25: getAppIdFromRequest can throw synchronously because
Request.clone() throws if the body was consumed; replace the current .catch() on
the promise with a synchronous try/catch around the clone+json call inside
getAppIdFromRequest (i.e., wrap c.req.raw.clone().json() in try { const body =
await c.req.raw.clone().json(); } catch (err) { return null; } ), then use
body?.app_id if present; ensure the catch handles the synchronous TypeError
thrown by clone and any JSON parse errors so the function returns null on
failure.
♻️ Duplicate comments (2)
supabase/functions/_backend/private/role_bindings.ts (2)
122-140: Missing validation forapikeyprincipal type.The function validates
userandgroupprincipals but falls through to success forapikeywithout any validation. This could allow creating role bindings for non-existent or unauthorized API keys.Proposed fix to add apikey validation
if (principalType === 'group') { const [group] = await drizzle .select() .from(schema.groups) .where( and( eq(schema.groups.id, principalId), eq(schema.groups.org_id, orgId), ), ) .limit(1) if (!group) { return { ok: false, status: 400, error: 'Group not found in this org' } } } + if (principalType === 'apikey') { + const [apikey] = await drizzle + .select({ id: schema.apikeys.id }) + .from(schema.apikeys) + .where( + and( + eq(schema.apikeys.id, principalId), + eq(schema.apikeys.owner_org, orgId), + ), + ) + .limit(1) + + if (!apikey) { + return { ok: false, status: 400, error: 'API key not found in this org' } + } + } + return { ok: true, data: null } }
356-374: Addis_directcheck before allowing deletion.The endpoint allows deleting any role binding, but inherited bindings (where
is_directisfalse) come from group membership and typically shouldn't be deleted directly. Deleting an inherited binding could cause inconsistent state.Proposed fix to prevent deleting inherited bindings
const [binding] = await drizzle .select() .from(schema.role_bindings) .where(eq(schema.role_bindings.id, bindingId)) .limit(1) if (!binding) { return c.json({ error: 'Role binding not found' }, 404) } + if (!binding.is_direct) { + return c.json({ error: 'Cannot delete inherited role binding - remove user from group instead' }, 400) + } + if (!(await checkPermission(c, 'org.update_user_roles', { orgId: binding.org_id }))) { return c.json({ error: 'Forbidden - Admin rights required' }, 403) }
🧹 Nitpick comments (8)
supabase/functions/_backend/public/statistics/index.ts (1)
597-609: LGTM with a note on redundancy.The unified RBAC check for
org.readis correctly implemented. The additionallimited_to_orgsandlimited_to_appschecks at lines 601-609 provide a defensive layer. If the underlyingrbac_check_permission_directSQL function already handles these apikey restrictions, consider removing these redundant checks in a follow-up to simplify the code.supabase/functions/_backend/public/build/upload.ts (1)
14-18: Type annotation should useContext<MiddlewareKeyVariables>per coding guidelines.The function uses
c.get('requestId'),c.get('auth'), andc.get('capgkey')viacheckPermission, which require theMiddlewareKeyVariablestype. The bareContexttype doesn't guarantee these context values are available.As per coding guidelines, all Hono endpoint handlers must accept
Context<MiddlewareKeyVariables>.♻️ Suggested fix
+import type { MiddlewareKeyVariables } from '../../utils/hono.ts' export async function tusProxy( - c: Context, + c: Context<MiddlewareKeyVariables>, jobId: string, apikey: Database['public']['Tables']['apikeys']['Row'], ): Promise<Response> {supabase/functions/_backend/triggers/on_user_delete.ts (1)
4-12: UsecreateHonofor app initialization (guideline compliance).This file is under
supabase/functions/_backend/**but initializes Hono vianew Hono(...)(Line 11) instead of the requiredcreateHonofromutils/hono.ts.Proposed change
-import { Hono } from 'hono/tiny' +import { createHono } from '../utils/hono.ts' @@ -export const app = new Hono<MiddlewareKeyVariables>() +export const app = createHono()supabase/functions/_backend/utils/hono_middleware.ts (1)
280-283: Validatex-limited-key-idparsing (avoid silently treating invalid values as “no subkey”).
Number(headerValue)can produceNaN(Line 282), andif (subkey_id)will treat it as falsy, skipping subkey validation entirely. Consider either:
return Number.isFinite(n) ? n : null, or- returning a
quickError(400, ...)if the header is present but invalid.Also applies to: 406-441, 487-533
supabase/functions/_backend/utils/rbac.ts (1)
460-469: Consider making the fallback explicit or throwing for unknown prefixes.The
getScopeTypeFromPermissionfunction returns'org'as a default fallback for unknown permission prefixes. While this is safe, consider whether throwing an error would be more appropriate to catch typos or invalid permissions early.Optional: Make fallback behavior explicit
export function getScopeTypeFromPermission(permission: Permission): ScopeType { if (permission.startsWith('org.')) return 'org' if (permission.startsWith('app.') || permission.startsWith('bundle.')) return 'app' if (permission.startsWith('channel.')) return 'channel' if (permission.startsWith('platform.')) return 'platform' - return 'org' // Default fallback + // Since Permission is a union type, this should be unreachable + // If we get here, throw to catch unexpected permissions early + throw new Error(`Unknown permission scope for: ${permission}`) }supabase/functions/_backend/private/delete_failed_version.ts (1)
25-34: LGTM on RBAC integration, but_userIdappears unused now.The RBAC check using
checkPermission(c, 'bundle.delete', { appId: body.app_id })is correct. However,_userIdfetched via RPC on line 25-26 appears to be unused after the RBAC refactor. Consider whether this RPC call is still needed.Verify if get_user_id RPC call is still needed
#!/bin/bash # Check if _userId is used anywhere in the function after line 25 cat supabase/functions/_backend/private/delete_failed_version.ts | grep -n "_userId" | head -20supabase/functions/_backend/utils/org_email_notifications.ts (2)
105-163: Multi-source RBAC membership resolution with expiration filtering.The implementation correctly:
- Queries user-level role bindings with expiration checks
- Queries group-level role bindings and resolves group members
- Filters expired bindings using
new Date(expiresAt) <= now/> now- Accumulates unique user IDs in a Set to prevent duplicates across sources
However, consider consolidating the expiration filtering logic to reduce duplication:
♻️ Optional: Extract expiration check helper
function isBindingExpired(expiresAt: string | null | undefined): boolean { return !!expiresAt && new Date(expiresAt) <= new Date() }
117-124: Type assertions suggest missing type generation.Multiple
(binding as any)assertions are used to accessexpires_atandprincipal_idfields. This indicates the Supabase types may not be fully generated for the new RBAC tables.Consider regenerating Supabase types to include the RBAC schema (
role_bindings,group_members), which would remove the need for unsafe type assertions and improve type safety:# Typically run from project root bunx supabase gen types typescript --local > src/types/supabase.types.tsAlso applies to: 140-145
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (26)
AGENTS.mdRBAC_SYSTEM.mdsrc/components/tables/AccessTable.vuesupabase/functions/_backend/private/delete_failed_version.tssupabase/functions/_backend/private/download_link.tssupabase/functions/_backend/private/groups.tssupabase/functions/_backend/private/invite_new_user_to_org.tssupabase/functions/_backend/private/role_bindings.tssupabase/functions/_backend/private/upload_link.tssupabase/functions/_backend/public/app/delete.tssupabase/functions/_backend/public/build/cancel.tssupabase/functions/_backend/public/build/request.tssupabase/functions/_backend/public/build/start.tssupabase/functions/_backend/public/build/status.tssupabase/functions/_backend/public/build/upload.tssupabase/functions/_backend/public/organization/delete.tssupabase/functions/_backend/public/organization/get.tssupabase/functions/_backend/public/organization/members/get.tssupabase/functions/_backend/public/organization/put.tssupabase/functions/_backend/public/statistics/index.tssupabase/functions/_backend/triggers/on_user_delete.tssupabase/functions/_backend/utils/hono_middleware.tssupabase/functions/_backend/utils/org_email_notifications.tssupabase/functions/_backend/utils/pg.tssupabase/functions/_backend/utils/rbac.tssupabase/seed.sql
✅ Files skipped from review due to trivial changes (1)
- supabase/functions/_backend/utils/pg.ts
🚧 Files skipped from review as they are similar to previous changes (9)
- supabase/functions/_backend/private/groups.ts
- supabase/functions/_backend/public/app/delete.ts
- supabase/functions/_backend/public/build/status.ts
- supabase/functions/_backend/private/invite_new_user_to_org.ts
- src/components/tables/AccessTable.vue
- supabase/functions/_backend/public/build/cancel.ts
- supabase/functions/_backend/public/build/start.ts
- supabase/functions/_backend/private/download_link.ts
- supabase/functions/_backend/private/upload_link.ts
🧰 Additional context used
📓 Path-based instructions (8)
supabase/functions/_backend/**/*.{ts,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
supabase/functions/_backend/**/*.{ts,js}: Backend code must be placed insupabase/functions/_backend/as shared code deployed to Cloudflare Workers (API/Plugin/Files workers), Supabase Edge Functions, and other platforms
UsecreateHonofromutils/hono.tsfor all Hono framework application initialization and routing
All database operations must usegetPgClient()orgetDrizzleClient()fromutils/pg.tsfor PostgreSQL access during active migration to Cloudflare D1
All Hono endpoint handlers must acceptContext<MiddlewareKeyVariables>and usec.get('requestId'),c.get('apikey'), andc.get('auth')for request context
Use structured logging withcloudlog({ requestId: c.get('requestId'), message: '...' })for all backend logging
UsemiddlewareAPISecretfor internal API endpoints andmiddlewareKeyfor external API keys; validate againstowner_orgin theapikeystable
Checkc.get('auth')?.authTypeto determine authentication type ('apikey' vs 'jwt') in backend endpoints
Use Drizzle ORM query patterns withschemafrompostgress_schema.tsfor all database operations; usealiasV2()for self-joins or multiple table references
Files:
supabase/functions/_backend/public/build/request.tssupabase/functions/_backend/triggers/on_user_delete.tssupabase/functions/_backend/public/organization/delete.tssupabase/functions/_backend/public/statistics/index.tssupabase/functions/_backend/private/role_bindings.tssupabase/functions/_backend/utils/hono_middleware.tssupabase/functions/_backend/public/organization/put.tssupabase/functions/_backend/utils/rbac.tssupabase/functions/_backend/public/organization/members/get.tssupabase/functions/_backend/private/delete_failed_version.tssupabase/functions/_backend/public/organization/get.tssupabase/functions/_backend/public/build/upload.tssupabase/functions/_backend/utils/org_email_notifications.ts
supabase/functions/**/*.{ts,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Backend ESLint must pass before commit; run
bun lint:backendfor backend files
Files:
supabase/functions/_backend/public/build/request.tssupabase/functions/_backend/triggers/on_user_delete.tssupabase/functions/_backend/public/organization/delete.tssupabase/functions/_backend/public/statistics/index.tssupabase/functions/_backend/private/role_bindings.tssupabase/functions/_backend/utils/hono_middleware.tssupabase/functions/_backend/public/organization/put.tssupabase/functions/_backend/utils/rbac.tssupabase/functions/_backend/public/organization/members/get.tssupabase/functions/_backend/private/delete_failed_version.tssupabase/functions/_backend/public/organization/get.tssupabase/functions/_backend/public/build/upload.tssupabase/functions/_backend/utils/org_email_notifications.ts
**/*.{vue,ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Run
bun lintto lint Vue, TypeScript, and JavaScript files; usebun lint:fixto auto-fix issues
Files:
supabase/functions/_backend/public/build/request.tssupabase/functions/_backend/triggers/on_user_delete.tssupabase/functions/_backend/public/organization/delete.tssupabase/functions/_backend/public/statistics/index.tssupabase/functions/_backend/private/role_bindings.tssupabase/functions/_backend/utils/hono_middleware.tssupabase/functions/_backend/public/organization/put.tssupabase/functions/_backend/utils/rbac.tssupabase/functions/_backend/public/organization/members/get.tssupabase/functions/_backend/private/delete_failed_version.tssupabase/functions/_backend/public/organization/get.tssupabase/functions/_backend/public/build/upload.tssupabase/functions/_backend/utils/org_email_notifications.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use single quotes and no semicolons per
@antfu/eslint-configrules
Files:
supabase/functions/_backend/public/build/request.tssupabase/functions/_backend/triggers/on_user_delete.tssupabase/functions/_backend/public/organization/delete.tssupabase/functions/_backend/public/statistics/index.tssupabase/functions/_backend/private/role_bindings.tssupabase/functions/_backend/utils/hono_middleware.tssupabase/functions/_backend/public/organization/put.tssupabase/functions/_backend/utils/rbac.tssupabase/functions/_backend/public/organization/members/get.tssupabase/functions/_backend/private/delete_failed_version.tssupabase/functions/_backend/public/organization/get.tssupabase/functions/_backend/public/build/upload.tssupabase/functions/_backend/utils/org_email_notifications.ts
supabase/functions/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
supabase/functions/**/*.ts: Never use the Supabase admin SDK with service key for user-facing APIs; always use client SDK with user authentication to enforce RLS policies. Admin SDK should only be used for internal operations (triggers, CRON jobs, etc.)
When admin access is unavoidable for a user-facing endpoint, sanitize all user inputs carefully to prevent PostgREST query injection
Files:
supabase/functions/_backend/public/build/request.tssupabase/functions/_backend/triggers/on_user_delete.tssupabase/functions/_backend/public/organization/delete.tssupabase/functions/_backend/public/statistics/index.tssupabase/functions/_backend/private/role_bindings.tssupabase/functions/_backend/utils/hono_middleware.tssupabase/functions/_backend/public/organization/put.tssupabase/functions/_backend/utils/rbac.tssupabase/functions/_backend/public/organization/members/get.tssupabase/functions/_backend/private/delete_failed_version.tssupabase/functions/_backend/public/organization/get.tssupabase/functions/_backend/public/build/upload.tssupabase/functions/_backend/utils/org_email_notifications.ts
**/*.{ts,tsx,js,jsx,vue}
📄 CodeRabbit inference engine (AGENTS.md)
Run
bun lintor lint/format command before validating any backend or frontend task to ensure consistent formatting
Files:
supabase/functions/_backend/public/build/request.tssupabase/functions/_backend/triggers/on_user_delete.tssupabase/functions/_backend/public/organization/delete.tssupabase/functions/_backend/public/statistics/index.tssupabase/functions/_backend/private/role_bindings.tssupabase/functions/_backend/utils/hono_middleware.tssupabase/functions/_backend/public/organization/put.tssupabase/functions/_backend/utils/rbac.tssupabase/functions/_backend/public/organization/members/get.tssupabase/functions/_backend/private/delete_failed_version.tssupabase/functions/_backend/public/organization/get.tssupabase/functions/_backend/public/build/upload.tssupabase/functions/_backend/utils/org_email_notifications.ts
supabase/functions/_backend/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
Place core backend logic in supabase/functions/_backend/ with plugins, private, public, triggers, and utils subdirectories
Files:
supabase/functions/_backend/public/build/request.tssupabase/functions/_backend/triggers/on_user_delete.tssupabase/functions/_backend/public/organization/delete.tssupabase/functions/_backend/public/statistics/index.tssupabase/functions/_backend/private/role_bindings.tssupabase/functions/_backend/utils/hono_middleware.tssupabase/functions/_backend/public/organization/put.tssupabase/functions/_backend/utils/rbac.tssupabase/functions/_backend/public/organization/members/get.tssupabase/functions/_backend/private/delete_failed_version.tssupabase/functions/_backend/public/organization/get.tssupabase/functions/_backend/public/build/upload.tssupabase/functions/_backend/utils/org_email_notifications.ts
supabase/seed.sql
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Seed database with
supabase db resetto apply all migrations and test data fromsupabase/seed.sqlUpdate supabase/seed.sql for new or evolved tests; keep fixtures focused on current behavior while leaving committed migrations unchanged
Files:
supabase/seed.sql
🧠 Learnings (28)
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Check `c.get('auth')?.authType` to determine authentication type ('apikey' vs 'jwt') in backend endpoints
Applied to files:
supabase/functions/_backend/public/build/request.tssupabase/functions/_backend/triggers/on_user_delete.tssupabase/functions/_backend/public/organization/delete.tssupabase/functions/_backend/public/statistics/index.tssupabase/functions/_backend/private/role_bindings.tssupabase/functions/_backend/utils/hono_middleware.tssupabase/functions/_backend/public/organization/put.tssupabase/functions/_backend/utils/rbac.tssupabase/functions/_backend/public/organization/members/get.tssupabase/functions/_backend/private/delete_failed_version.tssupabase/functions/_backend/public/organization/get.tssupabase/functions/_backend/public/build/upload.tsRBAC_SYSTEM.md
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Use `middlewareAPISecret` for internal API endpoints and `middlewareKey` for external API keys; validate against `owner_org` in the `apikeys` table
Applied to files:
supabase/functions/_backend/public/build/request.tssupabase/functions/_backend/public/organization/delete.tssupabase/functions/_backend/public/statistics/index.tssupabase/functions/_backend/private/role_bindings.tssupabase/functions/_backend/utils/hono_middleware.tssupabase/functions/_backend/public/organization/put.tssupabase/functions/_backend/utils/rbac.tssupabase/functions/_backend/public/organization/members/get.tssupabase/functions/_backend/private/delete_failed_version.tssupabase/functions/_backend/public/organization/get.tssupabase/functions/_backend/public/build/upload.ts
📚 Learning: 2026-01-10T04:55:25.264Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-10T04:55:25.264Z
Learning: Applies to supabase/functions/**/*.ts : Never use the Supabase admin SDK with service key for user-facing APIs; always use client SDK with user authentication to enforce RLS policies. Admin SDK should only be used for internal operations (triggers, CRON jobs, etc.)
Applied to files:
supabase/functions/_backend/public/build/request.tssupabase/functions/_backend/triggers/on_user_delete.tssupabase/functions/_backend/public/statistics/index.tssupabase/functions/_backend/private/role_bindings.tssupabase/functions/_backend/utils/hono_middleware.tssupabase/functions/_backend/public/organization/put.tssupabase/functions/_backend/utils/rbac.tssupabase/functions/_backend/public/organization/members/get.tssupabase/functions/_backend/private/delete_failed_version.tssupabase/functions/_backend/public/organization/get.tssupabase/functions/_backend/public/build/upload.tsRBAC_SYSTEM.md
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : All Hono endpoint handlers must accept `Context<MiddlewareKeyVariables>` and use `c.get('requestId')`, `c.get('apikey')`, and `c.get('auth')` for request context
Applied to files:
supabase/functions/_backend/public/build/request.tssupabase/functions/_backend/public/organization/delete.tssupabase/functions/_backend/public/statistics/index.tssupabase/functions/_backend/private/role_bindings.tssupabase/functions/_backend/utils/hono_middleware.tssupabase/functions/_backend/public/organization/put.tssupabase/functions/_backend/utils/rbac.tssupabase/functions/_backend/public/organization/members/get.tssupabase/functions/_backend/private/delete_failed_version.tssupabase/functions/_backend/public/organization/get.tssupabase/functions/_backend/public/build/upload.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Use structured logging with `cloudlog({ requestId: c.get('requestId'), message: '...' })` for all backend logging
Applied to files:
supabase/functions/_backend/public/build/request.tssupabase/functions/_backend/public/organization/delete.tssupabase/functions/_backend/public/statistics/index.tssupabase/functions/_backend/private/role_bindings.tssupabase/functions/_backend/utils/hono_middleware.tssupabase/functions/_backend/utils/rbac.tssupabase/functions/_backend/public/build/upload.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Use `createHono` from `utils/hono.ts` for all Hono framework application initialization and routing
Applied to files:
supabase/functions/_backend/public/build/request.tssupabase/functions/_backend/public/statistics/index.tssupabase/functions/_backend/private/role_bindings.tssupabase/functions/_backend/utils/hono_middleware.tssupabase/functions/_backend/public/organization/put.tssupabase/functions/_backend/utils/rbac.tssupabase/functions/_backend/public/organization/members/get.tssupabase/functions/_backend/private/delete_failed_version.tssupabase/functions/_backend/public/organization/get.tssupabase/functions/_backend/public/build/upload.ts
📚 Learning: 2026-01-10T04:55:25.264Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-10T04:55:25.264Z
Learning: Applies to supabase/functions/**/*.ts : When admin access is unavoidable for a user-facing endpoint, sanitize all user inputs carefully to prevent PostgREST query injection
Applied to files:
supabase/functions/_backend/public/build/request.tssupabase/functions/_backend/private/role_bindings.tssupabase/functions/_backend/utils/hono_middleware.tssupabase/functions/_backend/public/build/upload.tsRBAC_SYSTEM.md
📚 Learning: 2026-01-10T04:55:25.264Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-10T04:55:25.264Z
Learning: Applies to supabase/functions/_backend/**/*.ts : Place core backend logic in supabase/functions/_backend/ with plugins, private, public, triggers, and utils subdirectories
Applied to files:
supabase/functions/_backend/triggers/on_user_delete.tssupabase/functions/_backend/public/statistics/index.tssupabase/functions/_backend/private/role_bindings.tssupabase/functions/_backend/utils/rbac.tssupabase/functions/_backend/public/build/upload.ts
📚 Learning: 2025-12-24T14:11:10.256Z
Learnt from: WcaleNieWolny
Repo: Cap-go/capgo PR: 1300
File: supabase/migrations/20251224103713_2fa_enforcement.sql:409-539
Timestamp: 2025-12-24T14:11:10.256Z
Learning: In supabase/migrations for get_orgs_v6 and get_orgs_v7: The inner functions with user_id parameter (get_orgs_v6(uuid) and get_orgs_v7(uuid)) should NOT be granted to anon/authenticated roles as this allows any user to query other users' organizations; only the no-argument wrapper functions should be public as they perform authentication checks.
Applied to files:
supabase/functions/_backend/public/organization/delete.tssupabase/functions/_backend/public/statistics/index.tssupabase/functions/_backend/private/role_bindings.tssupabase/functions/_backend/utils/hono_middleware.tssupabase/seed.sqlsupabase/functions/_backend/public/organization/put.tssupabase/functions/_backend/utils/rbac.tssupabase/functions/_backend/public/organization/members/get.tssupabase/functions/_backend/public/organization/get.tssupabase/functions/_backend/utils/org_email_notifications.tsRBAC_SYSTEM.md
📚 Learning: 2025-12-23T01:19:15.067Z
Learnt from: riderx
Repo: Cap-go/capgo PR: 1297
File: src/components/dashboard/DeploymentBanner.vue:77-79
Timestamp: 2025-12-23T01:19:15.067Z
Learning: In the Cap-go codebase, the 'owner' role exists only at the organization level (used in src/stores/organization.ts), not as an app-level permission. App-level permissions use the user_min_right database enum which includes: read, upload, write, admin, super_admin (but NOT owner). When checking app permissions, never include 'owner' in the role checks.
Applied to files:
supabase/functions/_backend/public/organization/delete.tssupabase/functions/_backend/public/statistics/index.tssupabase/functions/_backend/private/role_bindings.tssupabase/functions/_backend/utils/rbac.tsRBAC_SYSTEM.md
📚 Learning: 2026-01-10T04:55:25.264Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-10T04:55:25.264Z
Learning: Applies to supabase/migrations/*.sql : Use `get_identity_org_appid()` for RLS policies when app_id exists on the table instead of `get_identity()` directly
Applied to files:
supabase/functions/_backend/public/organization/delete.tssupabase/seed.sqlsupabase/functions/_backend/public/organization/put.tsRBAC_SYSTEM.md
📚 Learning: 2025-12-25T11:22:19.594Z
Learnt from: WcaleNieWolny
Repo: Cap-go/capgo PR: 1300
File: supabase/migrations/20251224103713_2fa_enforcement.sql:85-96
Timestamp: 2025-12-25T11:22:19.594Z
Learning: In the 2FA enforcement implementation for supabase/migrations: When an org has enforcing_2fa=true, all users including super_admins must have 2FA enabled before accessing any org functions (including check_org_members_2fa_enabled); this is intentional behavior to ensure consistent security enforcement without exceptions for admins.
Applied to files:
supabase/functions/_backend/public/organization/delete.tssupabase/seed.sqlsupabase/functions/_backend/public/organization/put.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Backend code must be placed in `supabase/functions/_backend/` as shared code deployed to Cloudflare Workers (API/Plugin/Files workers), Supabase Edge Functions, and other platforms
Applied to files:
supabase/functions/_backend/private/role_bindings.tssupabase/functions/_backend/utils/hono_middleware.tsAGENTS.mdsupabase/functions/_backend/utils/rbac.tssupabase/functions/_backend/public/build/upload.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/**/*.{ts,js} : Backend ESLint must pass before commit; run `bun lint:backend` for backend files
Applied to files:
supabase/functions/_backend/private/role_bindings.tsAGENTS.mdsupabase/functions/_backend/utils/rbac.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Use Drizzle ORM query patterns with `schema` from `postgress_schema.ts` for all database operations; use `aliasV2()` for self-joins or multiple table references
Applied to files:
supabase/functions/_backend/private/role_bindings.tssupabase/functions/_backend/utils/hono_middleware.tssupabase/functions/_backend/public/organization/put.tssupabase/functions/_backend/utils/rbac.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : All database operations must use `getPgClient()` or `getDrizzleClient()` from `utils/pg.ts` for PostgreSQL access during active migration to Cloudflare D1
Applied to files:
supabase/functions/_backend/private/role_bindings.tssupabase/functions/_backend/utils/hono_middleware.tssupabase/functions/_backend/public/organization/put.tsAGENTS.mdsupabase/functions/_backend/utils/rbac.tssupabase/functions/_backend/public/build/upload.tsRBAC_SYSTEM.md
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/utils/postgress_schema.ts : Schema definitions must be placed in `utils/postgress_schema.ts` using Drizzle ORM and never edited in committed migration files
Applied to files:
supabase/functions/_backend/private/role_bindings.tsRBAC_SYSTEM.md
📚 Learning: 2026-01-10T04:55:25.264Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-10T04:55:25.264Z
Learning: Applies to supabase/seed.sql : Update supabase/seed.sql for new or evolved tests; keep fixtures focused on current behavior while leaving committed migrations unchanged
Applied to files:
supabase/seed.sqlRBAC_SYSTEM.md
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/seed.sql : Seed database with `supabase db reset` to apply all migrations and test data from `supabase/seed.sql`
Applied to files:
supabase/seed.sql
📚 Learning: 2026-01-10T04:55:25.264Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-10T04:55:25.264Z
Learning: Applies to supabase/migrations/*.sql : Never edit previously committed migrations; treat new seeding in migrations as part of the current feature
Applied to files:
supabase/seed.sqlRBAC_SYSTEM.md
📚 Learning: 2026-01-10T04:55:25.264Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-10T04:55:25.264Z
Learning: Applies to supabase/migrations/*.sql : Use Supabase CLI for every migration and operational task; avoid manual changes through dashboard or direct SQL
Applied to files:
supabase/seed.sqlAGENTS.mdRBAC_SYSTEM.md
📚 Learning: 2026-01-10T04:55:25.264Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-10T04:55:25.264Z
Learning: Applies to supabase/migrations/*.sql : Only use `get_identity_org_allowed()` as a last resort when the table genuinely has no app_id column and there is no way to join to get an app_id
Applied to files:
supabase/seed.sqlsupabase/functions/_backend/public/organization/put.tsRBAC_SYSTEM.md
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Backend tests modify local database; always reset database with `supabase db reset` before running tests to ensure clean state
Applied to files:
supabase/seed.sql
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Test accounts available after `supabase db reset`: `testcapgo.app` / `testtest` (demo user) and `admincapgo.app` / `adminadmin` (admin user)
Applied to files:
supabase/seed.sql
📚 Learning: 2026-01-09T18:23:34.200Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-09T18:23:34.200Z
Learning: Read AGENTS.md for all development guidelines, commands, architecture details, and coding conventions for this repository
Applied to files:
AGENTS.md
📚 Learning: 2026-01-10T04:55:25.264Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-10T04:55:25.264Z
Learning: ALL sections in a PR created by AI agents MUST be marked with '(AI generated)' - failure to mark is a violation of transparency requirements
Applied to files:
AGENTS.md
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/migrations/**/*.sql : Database migrations must be created with `supabase migration new <feature_slug>` and never modify previously committed migrations
Applied to files:
AGENTS.md
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to src/**/*.{vue,ts,js} : Frontend ESLint must pass before commit; run `bun lint:fix` to auto-fix issues in frontend files
Applied to files:
AGENTS.md
🧬 Code graph analysis (11)
supabase/functions/_backend/public/build/request.ts (1)
supabase/functions/_backend/utils/rbac.ts (1)
checkPermission(240-313)
supabase/functions/_backend/triggers/on_user_delete.ts (3)
supabase/functions/_backend/utils/logging.ts (1)
cloudlog(3-15)supabase/functions/_backend/utils/postgres_schema.ts (1)
orgs(95-104)supabase/functions/_backend/utils/hono.ts (1)
BRES(156-156)
supabase/functions/_backend/public/organization/delete.ts (3)
supabase/functions/_backend/utils/hono.ts (1)
MiddlewareKeyVariables(27-44)supabase/functions/_backend/utils/logging.ts (1)
cloudlog(3-15)supabase/functions/_backend/utils/rbac.ts (1)
checkPermission(240-313)
supabase/functions/_backend/public/statistics/index.ts (3)
supabase/functions/_backend/utils/rbac.ts (1)
checkPermission(240-313)supabase/functions/_backend/utils/hono.ts (1)
quickError(245-258)supabase/functions/_backend/utils/logging.ts (1)
cloudlogErr(29-41)
supabase/functions/_backend/private/role_bindings.ts (7)
supabase/functions/_backend/private/groups.ts (1)
app(26-26)supabase/functions/_backend/utils/hono.ts (2)
createHono(158-217)MiddlewareKeyVariables(27-44)supabase/functions/_backend/utils/version.ts (1)
version(1-1)supabase/functions/_backend/utils/pg.ts (3)
getDrizzleClient(220-222)getPgClient(187-218)closeClient(275-280)supabase/functions/_backend/utils/postgres_schema.ts (1)
schema(210-225)supabase/functions/_backend/utils/rbac.ts (1)
checkPermission(240-313)supabase/functions/_backend/utils/logging.ts (2)
cloudlog(3-15)cloudlogErr(29-41)
supabase/functions/_backend/utils/hono_middleware.ts (3)
supabase/functions/_backend/utils/logging.ts (1)
cloudlog(3-15)supabase/functions/_backend/utils/hono.ts (1)
honoFactory(52-52)supabase/functions/_backend/utils/supabase.ts (2)
checkKey(1132-1166)checkKeyById(1172-1192)
supabase/functions/_backend/public/organization/put.ts (4)
supabase/functions/_backend/utils/hono.ts (2)
parseBody(272-283)MiddlewareKeyVariables(27-44)src/types/supabase.types.ts (1)
Database(9-3209)supabase/functions/_backend/utils/supabase.ts (2)
supabaseApikey(86-103)apikeyHasOrgRightWithPolicy(267-285)supabase/functions/_backend/utils/rbac.ts (1)
checkPermission(240-313)
supabase/functions/_backend/private/delete_failed_version.ts (3)
supabase/functions/_backend/utils/supabase.ts (1)
supabaseApikey(86-103)supabase/functions/_backend/utils/hono.ts (1)
quickError(245-258)supabase/functions/_backend/utils/rbac.ts (1)
checkPermission(240-313)
supabase/functions/_backend/public/organization/get.ts (5)
supabase/functions/_backend/utils/hono.ts (3)
parseBody(272-283)simpleError(268-270)MiddlewareKeyVariables(27-44)supabase/functions/_backend/utils/supabase.ts (1)
supabaseApikey(86-103)supabase/functions/_backend/utils/rbac.ts (1)
checkPermission(240-313)supabase/functions/_backend/utils/utils.ts (1)
fetchLimit(13-13)scripts/snippet/cloudflare-snippet-filter-appid.js (1)
body(65-65)
supabase/functions/_backend/public/build/upload.ts (1)
supabase/functions/_backend/utils/rbac.ts (1)
checkPermission(240-313)
supabase/functions/_backend/utils/org_email_notifications.ts (2)
supabase/functions/_backend/utils/logging.ts (1)
cloudlog(3-15)supabase/functions/_backend/utils/postgres_schema.ts (1)
users(106-119)
🪛 LanguageTool
RBAC_SYSTEM.md
[style] ~802-~802: This adverb was used twice in the sentence. Consider removing one of them or replacing them with a synonym.
Context: ...that bundle - Downward propagation only (no upward propagation) 7. *Return...
(ADVERB_REPETITION_PREMIUM)
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/types/supabase.types.ts (1)
3946-4010:ALLOWED_STATS_ACTIONSis missing three enum values, creating a data consistency issue.
The enum in src/types/supabase.types.ts includesbackend_refusal,disablePlatformElectron, andkeyMismatch, butALLOWED_STATS_ACTIONSin supabase/functions/_backend/plugins/stats_actions.ts excludes them. These values are actively used in supabase/functions/_backend/utils/update.ts viasendStatsAndDevice(), but external API requests will reject them due to the incomplete validation. UpdateALLOWED_STATS_ACTIONSto include all three missing values.
🤖 Fix all issues with AI agents
In `@supabase/functions/_backend/public/statistics/index.ts`:
- Around line 558-561: The quickError calls in the authorization checks (see the
checkPermission call and subsequent throw quickError usage) are currently
including c.get('auth') in the 401 payload; remove that sensitive object from
the error payloads (do not pass c.get('auth') into quickError) and instead
either omit the data field entirely or include only non-sensitive identifiers
(e.g., c.get('auth')?.userId or null). Update every occurrence of throw
quickError(...) that passes c.get('auth') (including the similar block around
checkPermission at the later lines) to use a safe/empty payload.
♻️ Duplicate comments (3)
RBAC_SYSTEM.md (1)
427-437: Clarify “except billing and deletions” fororg_admin(channels are deletable per tables).Right now “except billing and deletions” (Line 435) can be read as “cannot delete channels”, but
org_adminincludeschannel.deletein the permissions tables. Consider tightening wording to “except billing changes and app deletion” (or similar).src/types/supabase.types.ts (1)
2991-3057:get_orgs_v7.required_encryption_keytyped non-null—confirm SQL guarantees it.
orgs.required_encryption_keyisstring | null, soget_orgs_v7returningstringis only safe if the SQL uses something likeCOALESCE(...)or filters to non-null.#!/bin/bash set -euo pipefail # Confirm get_orgs_v7 selects/COALESCEs required_encryption_key and check its GRANTs rg -n --hidden -S '\bget_orgs_v7\b' supabase/migrations rg -n --hidden -S '\brequired_encryption_key\b' supabase/migrations rg -n --hidden -S '\bGRANT\b.*\bget_orgs_v7\b' supabase/migrationssupabase/functions/_backend/utils/supabase.types.ts (1)
2991-3057:get_orgs_v7.required_encryption_keynon-null typing: confirm SQL guarantees it.
Avoid backend code treating the encryption key as always present unless the query enforces that invariant.#!/bin/bash set -euo pipefail rg -n --hidden -S '\bget_orgs_v7\b|\brequired_encryption_key\b' supabase/migrations
🧹 Nitpick comments (2)
RBAC_SYSTEM.md (2)
223-246: Consider stronger integrity betweenorg_idand referencedapp_id/channel_id/bundle_id.
role_bindingsrequiresorg_idfor app/channel/bundle scopes, but nothing here prevents mismatches (e.g., anapp_idbelonging to a different org thanorg_id). If not already enforced elsewhere, consider a trigger (or validation in the write-path) to keep these consistent.Also applies to: 258-289
1461-1477: AsyncwatchEffectexample can race; consider showing an invalidation-safe pattern.As written, rapid scope changes can interleave async results and overwrite newer values. If you want the example to be robust, add
onCleanupcancellation or use awatchon scope keys with a request counter.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
RBAC_SYSTEM.mdsrc/types/supabase.types.tssupabase/functions/_backend/private/groups.tssupabase/functions/_backend/public/statistics/index.tssupabase/functions/_backend/utils/supabase.types.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- supabase/functions/_backend/private/groups.ts
🧰 Additional context used
📓 Path-based instructions (10)
supabase/functions/_backend/**/*.{ts,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
supabase/functions/_backend/**/*.{ts,js}: Backend code must be placed insupabase/functions/_backend/as shared code deployed to Cloudflare Workers (API/Plugin/Files workers), Supabase Edge Functions, and other platforms
UsecreateHonofromutils/hono.tsfor all Hono framework application initialization and routing
All database operations must usegetPgClient()orgetDrizzleClient()fromutils/pg.tsfor PostgreSQL access during active migration to Cloudflare D1
All Hono endpoint handlers must acceptContext<MiddlewareKeyVariables>and usec.get('requestId'),c.get('apikey'), andc.get('auth')for request context
Use structured logging withcloudlog({ requestId: c.get('requestId'), message: '...' })for all backend logging
UsemiddlewareAPISecretfor internal API endpoints andmiddlewareKeyfor external API keys; validate againstowner_orgin theapikeystable
Checkc.get('auth')?.authTypeto determine authentication type ('apikey' vs 'jwt') in backend endpoints
Use Drizzle ORM query patterns withschemafrompostgress_schema.tsfor all database operations; usealiasV2()for self-joins or multiple table references
Files:
supabase/functions/_backend/public/statistics/index.tssupabase/functions/_backend/utils/supabase.types.ts
supabase/functions/**/*.{ts,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Backend ESLint must pass before commit; run
bun lint:backendfor backend files
Files:
supabase/functions/_backend/public/statistics/index.tssupabase/functions/_backend/utils/supabase.types.ts
**/*.{vue,ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Run
bun lintto lint Vue, TypeScript, and JavaScript files; usebun lint:fixto auto-fix issues
Files:
supabase/functions/_backend/public/statistics/index.tssrc/types/supabase.types.tssupabase/functions/_backend/utils/supabase.types.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use single quotes and no semicolons per
@antfu/eslint-configrules
Files:
supabase/functions/_backend/public/statistics/index.tssrc/types/supabase.types.tssupabase/functions/_backend/utils/supabase.types.ts
supabase/functions/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
supabase/functions/**/*.ts: Never use the Supabase admin SDK with service key for user-facing APIs; always use client SDK with user authentication to enforce RLS policies. Admin SDK should only be used for internal operations (triggers, CRON jobs, etc.)
When admin access is unavoidable for a user-facing endpoint, sanitize all user inputs carefully to prevent PostgREST query injection
Files:
supabase/functions/_backend/public/statistics/index.tssupabase/functions/_backend/utils/supabase.types.ts
**/*.{ts,tsx,js,jsx,vue}
📄 CodeRabbit inference engine (AGENTS.md)
Run
bun lintor lint/format command before validating any backend or frontend task to ensure consistent formatting
Files:
supabase/functions/_backend/public/statistics/index.tssrc/types/supabase.types.tssupabase/functions/_backend/utils/supabase.types.ts
supabase/functions/_backend/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
Place core backend logic in supabase/functions/_backend/ with plugins, private, public, triggers, and utils subdirectories
Files:
supabase/functions/_backend/public/statistics/index.tssupabase/functions/_backend/utils/supabase.types.ts
src/**/*.{ts,tsx,vue,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use
~/alias for imports fromsrc/directory in frontend TypeScript and Vue components
Files:
src/types/supabase.types.ts
src/**/*.{vue,ts,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Frontend ESLint must pass before commit; run
bun lint:fixto auto-fix issues in frontend files
Files:
src/types/supabase.types.ts
src/**/*.{ts,tsx,js,jsx,vue}
📄 CodeRabbit inference engine (AGENTS.md)
Use path alias
~/to map tosrc/for imports in TypeScript and JavaScript files
Files:
src/types/supabase.types.ts
🧠 Learnings (21)
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Check `c.get('auth')?.authType` to determine authentication type ('apikey' vs 'jwt') in backend endpoints
Applied to files:
supabase/functions/_backend/public/statistics/index.tsRBAC_SYSTEM.mdsrc/types/supabase.types.tssupabase/functions/_backend/utils/supabase.types.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Use `middlewareAPISecret` for internal API endpoints and `middlewareKey` for external API keys; validate against `owner_org` in the `apikeys` table
Applied to files:
supabase/functions/_backend/public/statistics/index.tssrc/types/supabase.types.tssupabase/functions/_backend/utils/supabase.types.ts
📚 Learning: 2025-12-23T01:19:15.067Z
Learnt from: riderx
Repo: Cap-go/capgo PR: 1297
File: src/components/dashboard/DeploymentBanner.vue:77-79
Timestamp: 2025-12-23T01:19:15.067Z
Learning: In the Cap-go codebase, the 'owner' role exists only at the organization level (used in src/stores/organization.ts), not as an app-level permission. App-level permissions use the user_min_right database enum which includes: read, upload, write, admin, super_admin (but NOT owner). When checking app permissions, never include 'owner' in the role checks.
Applied to files:
supabase/functions/_backend/public/statistics/index.tsRBAC_SYSTEM.mdsrc/types/supabase.types.tssupabase/functions/_backend/utils/supabase.types.ts
📚 Learning: 2026-01-10T04:55:25.264Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-10T04:55:25.264Z
Learning: Applies to supabase/functions/_backend/**/*.ts : Place core backend logic in supabase/functions/_backend/ with plugins, private, public, triggers, and utils subdirectories
Applied to files:
supabase/functions/_backend/public/statistics/index.tssrc/types/supabase.types.tssupabase/functions/_backend/utils/supabase.types.ts
📚 Learning: 2025-12-24T14:11:10.256Z
Learnt from: WcaleNieWolny
Repo: Cap-go/capgo PR: 1300
File: supabase/migrations/20251224103713_2fa_enforcement.sql:409-539
Timestamp: 2025-12-24T14:11:10.256Z
Learning: In supabase/migrations for get_orgs_v6 and get_orgs_v7: The inner functions with user_id parameter (get_orgs_v6(uuid) and get_orgs_v7(uuid)) should NOT be granted to anon/authenticated roles as this allows any user to query other users' organizations; only the no-argument wrapper functions should be public as they perform authentication checks.
Applied to files:
supabase/functions/_backend/public/statistics/index.tsRBAC_SYSTEM.mdsrc/types/supabase.types.tssupabase/functions/_backend/utils/supabase.types.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Use `createHono` from `utils/hono.ts` for all Hono framework application initialization and routing
Applied to files:
supabase/functions/_backend/public/statistics/index.ts
📚 Learning: 2026-01-10T04:55:25.264Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-10T04:55:25.264Z
Learning: Applies to supabase/functions/**/*.ts : Never use the Supabase admin SDK with service key for user-facing APIs; always use client SDK with user authentication to enforce RLS policies. Admin SDK should only be used for internal operations (triggers, CRON jobs, etc.)
Applied to files:
supabase/functions/_backend/public/statistics/index.tsRBAC_SYSTEM.mdsupabase/functions/_backend/utils/supabase.types.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Use structured logging with `cloudlog({ requestId: c.get('requestId'), message: '...' })` for all backend logging
Applied to files:
supabase/functions/_backend/public/statistics/index.tsRBAC_SYSTEM.md
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : All Hono endpoint handlers must accept `Context<MiddlewareKeyVariables>` and use `c.get('requestId')`, `c.get('apikey')`, and `c.get('auth')` for request context
Applied to files:
supabase/functions/_backend/public/statistics/index.ts
📚 Learning: 2026-01-10T04:55:25.264Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-10T04:55:25.264Z
Learning: Applies to supabase/functions/**/*.ts : When admin access is unavoidable for a user-facing endpoint, sanitize all user inputs carefully to prevent PostgREST query injection
Applied to files:
RBAC_SYSTEM.md
📚 Learning: 2026-01-10T04:55:25.264Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-10T04:55:25.264Z
Learning: Applies to supabase/migrations/*.sql : Use `get_identity_org_appid()` for RLS policies when app_id exists on the table instead of `get_identity()` directly
Applied to files:
RBAC_SYSTEM.md
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : All database operations must use `getPgClient()` or `getDrizzleClient()` from `utils/pg.ts` for PostgreSQL access during active migration to Cloudflare D1
Applied to files:
RBAC_SYSTEM.mdsupabase/functions/_backend/utils/supabase.types.ts
📚 Learning: 2026-01-10T04:55:25.264Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-10T04:55:25.264Z
Learning: Applies to supabase/migrations/*.sql : Only use `get_identity_org_allowed()` as a last resort when the table genuinely has no app_id column and there is no way to join to get an app_id
Applied to files:
RBAC_SYSTEM.md
📚 Learning: 2026-01-10T04:55:25.264Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-10T04:55:25.264Z
Learning: Applies to supabase/migrations/*.sql : Use Supabase CLI for every migration and operational task; avoid manual changes through dashboard or direct SQL
Applied to files:
RBAC_SYSTEM.md
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/utils/postgress_schema.ts : Schema definitions must be placed in `utils/postgress_schema.ts` using Drizzle ORM and never edited in committed migration files
Applied to files:
RBAC_SYSTEM.mdsupabase/functions/_backend/utils/supabase.types.ts
📚 Learning: 2026-01-10T04:55:25.264Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-10T04:55:25.264Z
Learning: Applies to supabase/migrations/*.sql : Never edit previously committed migrations; treat new seeding in migrations as part of the current feature
Applied to files:
RBAC_SYSTEM.md
📚 Learning: 2026-01-10T04:55:25.264Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-10T04:55:25.264Z
Learning: Applies to supabase/seed.sql : Update supabase/seed.sql for new or evolved tests; keep fixtures focused on current behavior while leaving committed migrations unchanged
Applied to files:
RBAC_SYSTEM.md
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Use Drizzle ORM query patterns with `schema` from `postgress_schema.ts` for all database operations; use `aliasV2()` for self-joins or multiple table references
Applied to files:
RBAC_SYSTEM.mdsrc/types/supabase.types.tssupabase/functions/_backend/utils/supabase.types.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Backend code must be placed in `supabase/functions/_backend/` as shared code deployed to Cloudflare Workers (API/Plugin/Files workers), Supabase Edge Functions, and other platforms
Applied to files:
supabase/functions/_backend/utils/supabase.types.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Run `bun types` after schema changes to regenerate TypeScript types from Supabase
Applied to files:
supabase/functions/_backend/utils/supabase.types.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Use shared backend code from `supabase/functions/_backend/` across all deployment platforms; never create platform-specific implementations outside this directory
Applied to files:
supabase/functions/_backend/utils/supabase.types.ts
🧬 Code graph analysis (2)
supabase/functions/_backend/public/statistics/index.ts (1)
supabase/functions/_backend/utils/rbac.ts (1)
checkPermission(240-313)
supabase/functions/_backend/utils/supabase.types.ts (1)
src/types/supabase.types.ts (1)
Database(9-3807)
🪛 LanguageTool
RBAC_SYSTEM.md
[style] ~802-~802: This adverb was used twice in the sentence. Consider removing one of them or replacing them with a synonym.
Context: ...that bundle - Downward propagation only (no upward propagation) 7. *Return...
(ADVERB_REPETITION_PREMIUM)
🔇 Additional comments (20)
RBAC_SYSTEM.md (2)
154-170: Double-check whetherpublic.permissions.bundle_idis actually intended (and matches migrations).Having
bundle_idon the permission definition table is atypical (permissions are usually global; scoping happens via role_bindings + check inputs). If the real schema doesn’t include it (or it’s always NULL), consider removing it from the doc or adding a one-liner explaining why it exists.
825-842: Good: explicit “public wrapper / private inner function” + GRANT guidance.This is the right pattern to prevent user-id probing while keeping the client RPC ergonomic.
Also applies to: 971-999
supabase/functions/_backend/public/statistics/index.ts (3)
9-10: Nice simplification: funneling access checks throughcheckPermission.
586-606:org.readgate + apikeylimited_to_*validation looks consistent.
667-706: No security issues found—get_user_org_ids()correctly handles both JWT and apikey authentication.The function is implemented as a SECURITY DEFINER wrapper that explicitly checks for API keys first (validating against the
apikeystable and using the associateduser_id), then falls back topublic.get_identity()for JWT authentication. This dual-path design safely supports both authentication methods without relying solely onauth.uid().Additionally, the security pattern from prior learnings is correctly applied: the inner variants
get_orgs_v6(uuid)andget_orgs_v7(uuid)(which accept auser_idparameter) are revoked fromanonandauthenticatedroles and only granted topostgresandservice_role, while the no-argument wrapper functions are properly exposed to authenticated users.src/types/supabase.types.ts (12)
9-34: Good addition ofgraphql_publicschema typing; keep it in sync with generated constants.
No concerns with the structure (Tables/Views/Functions/Enums/CompositeTypes) and the emptyConstants.graphql_public.Enumsplaceholder.Also applies to: 3926-3930
1226-1299: Newgroups/group_memberstables look coherent (no surrogate id for membership is fine).
Relationships togroups.idandusers.idare consistent with the intended RBAC model.
1435-1515: No action needed—orgs.use_new_rbachas a database DEFAULT.The column is added in migration
20251222140030_rbac_system.sqlline 527 withDEFAULT false, so new org creation and updates do not require the field to be explicitly provided. The type definition is correct:Rowrequires it (always present with default),Insertmakes it optional (clients omit it, DB uses default). A per-org + global feature flag rollout strategy is already implemented viarbac_is_enabled_for_org().
1608-1628:rbac_settingstable is intentionally global (singleton)—TypeScript types are correct.
The migration confirms this via theCHECK (id = 1)constraint and explicit comment: "Singleton row to flip RBAC on globally without touching org records." The per-org footgun risk is mitigated by the OR logic (COALESCE(v_org_enabled, false) OR COALESCE(v_global_enabled, false)), which allows each org to independently enable RBAC regardless of the global flag.
2633-2652: RPC type signatures verified as correct. The TypeScript types forcount_non_compliant_bundlesanddelete_non_compliant_bundlesaccurately match their SQL function definitions. Args are properly mapped (uuid → string, text DEFAULT NULL → optional string), return types are correct (TABLE fields map to object properties, bigint → number), and the callable signature includes proper authorization checks in the SQL implementation.
621-687:channels.rbac_idis properly constrained—NOT NULL and UNIQUE constraints confirmed in schema.The database migration (20251222140030_rbac_system.sql) establishes
channels.rbac_idasNOT NULLwith aUNIQUEconstraint (channels_rbac_id_key) and defaultgen_random_uuid(). The foreign key inrole_bindings.channel_idcorrectly referenceschannels(rbac_id). The TypeScript types accurately reflect this:Rowrequiresrbac_id, whileInsertmarks it optional (since the database provides the default).
37-79: No action needed. The database schema forapikeys.rbac_idis properly configured with aDEFAULT gen_random_uuid()and aNOT NULLconstraint (added in migration20251222140030_rbac_system.sqlwith backfill for existing rows). The TypeScript typing correctly reflects this:Row.rbac_id: string(required, because DB enforces NOT NULL) andInsert.rbac_id?: string(optional, because DB provides DEFAULT). The typing is safe.Likely an incorrect or invalid review comment.
1516-1550: No changes needed—scope_typeis a text column with constraints, not a Postgres enum.The
permissions.scope_typecolumn is defined in the migration astext NOT NULL CHECK (scope_type IN (...)), not as a Postgres enum type. The currentstringtyping is correct and appropriate for this text column with a CHECK constraint.Likely an incorrect or invalid review comment.
2546-2575: No action needed —check_min_rights_legacyis not granted to any roles and is only called internally from wrapper functions, confirming proper security architecture.Likely an incorrect or invalid review comment.
1629-1715: FK design is intentional and properly constrained—no changes needed.The
role_bindings.channel_id→channels(rbac_idreference is a deliberate pattern:rbac_idis a stable, immutable UUID generated for RBAC scope binding, separate from the channel's business identifier. Therbac_idcolumn is properly constrained asUNIQUE NOT NULLwith the comment "Stable UUID to bind RBAC roles to channel scope." This is a sound architectural choice for polymorphic RBAC systems.
3134-3139:get_user_org_ids()is correctly implemented with proper authentication and scoping; no security issues found.The function is a no-arg, authenticated-only function that properly scopes results to the current user by checking API keys first, then falling back to
get_identity()(auth.uid()). It raises an exception if unauthenticated. The SECURITY DEFINER usage is intentional and correct—it's necessary to bypass RLS and directly query role_bindings and org_users tables while still filtering by the authenticated user's ID. The function accepts no parameters, preventing cross-user data leakage, and is granted only to the "authenticated" role, not "anon".Likely an incorrect or invalid review comment.
3286-3293: TypeScript types documentis_user_app_adminandis_user_org_adminas public RPC callables, but these functions lack explicit GRANTs to any role—creating a mismatch between the type definitions and actual runtime accessibility.Both
is_user_app_admin(p_user_id, p_app_id)andis_user_org_admin(p_user_id, p_org_id)are defined as SECURITY DEFINER helper functions at lines 2870 and 2892 in the migrations and are used internally within RLS policies (always called withauth.uid()). No explicit GRANT statements exist for these functions toauthenticatedoranonroles.The core concern is valid: these parameterized functions should never be directly callable by users to avoid privilege escalation. However, the solution is to either:
- Remove them from the TypeScript RPC type definitions if they are internal-only helpers, or
- Create zero-argument wrapper functions and GRANT those instead
Ensure the TypeScript types in
src/types/supabase.types.ts(lines 3286–3293) accurately reflect only functions that are explicitly granted for public use.supabase/functions/_backend/utils/supabase.types.ts (3)
9-34:graphql_publicschema typing addition is fine; keep generation source consistent with frontend types.
37-79: No action needed—database defaults and backfills are in place.Migration 20251222140030_rbac_system.sql properly defines
rbac_idwithDEFAULT gen_random_uuid(), backfills existing rows, and sets the columnNOT NULL. Similarly,use_new_rbaccolumns in bothorgsandrbac_settingstables haveNOT NULL DEFAULT false. The TypeScript types correctly reflect these constraints:rbac_idanduse_new_rbacare non-optional in Row (reading) but optional in Insert (writing), matching the database behavior.Likely an incorrect or invalid review comment.
2546-2575: No security issues found—RPC GRANTs are properly least-privilege.The functions mentioned are either not granted to public roles (check_min_rights_legacy, user_has_role_in_app, user_has_app_update_user_roles default to private) or have explicit REVOKE statements in place. Specifically:
get_orgs_v6(uuid)andget_orgs_v7(uuid): REVOKE ALL from anon/authenticated; GRANT EXECUTE only to postgres and service_rolecheck_min_rights_legacy,user_has_role_in_app,user_has_app_update_user_roles: No explicit GRANTs issued (default deny)rbac_check_permission: GRANTED to authenticated without caller-controlled user_id parameterrbac_check_permission_direct: GRANTED to service_role only
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
|



Summary (AI generated)
Motivation (AI generated)
The RBAC system enables granular access control to Capgo platform resources. This documentation is necessary to:
Business Impact (AI generated)
This documentation positively impacts Capgo's business by:
Test Plan (AI generated)
supabase/migrations/Screenshots
Checklist
bun run lint:backend && bun run lint.Summary by CodeRabbit
New Features
Permissions
Documentation
Localization
Backend
Tests
✏️ Tip: You can customize this high-level summary in your review settings.