Skip to content

Conversation

@jokabuyasina
Copy link

@jokabuyasina jokabuyasina commented Jan 7, 2026

Description

This PR adds the complete frontend UI for SAML SSO authentication, enabling organization administrators to configure SSO providers and users to authenticate via SSO.

What's Included

New Pages

  • SSO Configuration Wizard (src/pages/settings/organization/sso.vue)

    • Multi-step wizard for SSO setup
    • Display Capgo Entity ID and ACS URL for IdP configuration
    • Input and validate IdP metadata URL
    • Configure domain-based auto-join rules
    • Test SSO connection before saving
  • SSO Login Page (src/pages/sso-login.vue)

    • Dedicated SSO login flow
    • Email domain detection
    • Automatic redirect to configured SSO provider
    • Fallback to standard login for non-SSO domains

New Composables

  • SSO Detection (src/composables/useSSODetection.ts)
    • Email domain → SSO provider lookup
    • SSO initiation helpers
    • Shared state management across SSO flows

Modified Components

  • Login Page (src/pages/login.vue)

    • Added SSO redirect detection (from_sso=true param)
    • SSO loading spinner during authentication
    • "Sign in with SSO" link for quick access
  • Organization Settings (src/constants/organizationTabs.ts)

    • Added SSO tab with shield icon

Internationalization

  • 65 new i18n messages (messages/en.json)
    • All SSO wizard UI text
    • Login flow messages
    • Error messages and validation text

User Flows

For Organization Admins

  1. Navigate to Settings → Organization → SSO
  2. Copy Capgo metadata (Entity ID, ACS URL)
  3. Configure IdP in their SSO provider
  4. Paste IdP metadata URL into Capgo
  5. (Optional) Configure auto-join domains
  6. Test connection and save

For End Users

  1. Click "Sign in with SSO" on login page, or navigate to /sso-login
  2. Enter email address
  3. System detects SSO configuration for domain
  4. Redirects to IdP for authentication
  5. Returns to Capgo with active session

Summary by CodeRabbit

  • New Features

    • Full SSO/SAML support: configure providers, domain mappings, test flows, and auto-enroll users; SSO login flow and admin SSO settings UI.
    • Local development: mock SSO callback for testing.
    • Electron added as a supported platform.
  • Bug Fixes / Security

    • Safer sensitive-data redaction in logs.
    • Improved login flow handling for SSO redirects.
  • Localization

    • Added/new translation and typo fix for channel-related strings.

✏️ Tip: You can customize this high-level summary in your review settings.

Consolidates 12 incremental SSO migrations (20251224022658 through 20260106000000) into a single comprehensive migration.

Schema includes:
- Tables: org_saml_connections, saml_domain_mappings, sso_audit_logs
- Functions: check_org_sso_configured, lookup_sso_provider_*, auto_join_*
- Triggers: auto_join_sso_user_trigger, check_sso_domain_on_signup_trigger
- RLS policies for all tables
- Indexes for performance
- Single SSO per org constraint (UNIQUE org_id, entity_id)
- auto_join_enabled flag for controlling enrollment

This is PR #1 of the SSO feature split (schema foundation only).
No backend endpoints, no frontend, no tests included yet.

Related: feature/sso-saml-authentication
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 7, 2026

Warning

Rate limit exceeded

@jokabuyasina has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 10 minutes and 49 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between fc92708 and 74e848b.

📒 Files selected for processing (3)
  • supabase/functions/_backend/plugins/channel_self.ts
  • supabase/functions/_backend/public/build/start.ts
  • supabase/functions/_backend/triggers/logsnag_insights.ts
📝 Walkthrough

Walkthrough

Adds end-to-end SSO/SAML support: frontend SSO pages and composable, backend endpoints and Edge function for mock SSO, DB migration with SSO tables/functions/triggers/policies, updates to backend utilities and Supabase types, plus related seed, gitignore, and i18n changes.

Changes

Cohort / File(s) Summary
Frontend SSO Pages
src/pages/sso-login.vue, src/pages/settings/organization/sso.vue
New SSO login page and an admin SAML configuration wizard for IdP metadata, domain mappings, testing, enabling/disabling, and deletion.
Frontend SSO Logic
src/composables/useSSODetection.ts, src/pages/login.vue, src/constants/organizationTabs.ts
New composable for SSO detection/initiation; login page detects SSO redirects and conditionally shows signing-in UI; added SSO tab to organization tabs.
Backend DB Migration & Schema
supabase/migrations/20260107210800_sso_saml_complete.sql, supabase/functions/_backend/utils/postgres_schema.ts
Large migration adding org_saml_connections, saml_domain_mappings, sso_audit_logs, many helper functions, triggers for auto-enroll/validation, RLS policies, grants, and indexes; corresponding pgTable exports.
Edge Function (mock)
supabase/functions/mock-sso-callback/index.ts
New mock SSO callback for local development with email and RelayState validation and safe redirect handling.
Backend Utilities & Middleware
supabase/functions/_backend/utils/hono_middleware.ts, supabase/functions/_backend/utils/discord.ts, supabase/functions/_backend/utils/stripe.ts
API key lookup replaced with DB function + redaction of keys in logs; safer regex escaping for Discord sanitization; Supabase dashboard link generation (local vs remote) and attaching link to Stripe customer metadata.
Backend Functions & Private APIs
supabase/functions/_backend/private/create_device.ts, supabase/functions/_backend/private/invite_new_user_to_org.ts, supabase/functions/_backend/public/build/start.ts
create_device now uses authenticated client and requires org_id with ownership checks; invite flow enforces org admin rights and hardened CAPTCHA handling; small apikey nullability adjustment in build start.
Types & Generated Supabase Types
src/types/supabase.types.ts, supabase/functions/_backend/utils/supabase.types.ts
Rework of Database typings to add graphql_public namespace, add electron platform to enums and tables, remove plan_enterprise fields, and propagate enum/value updates across types.
Seeds, Misc, and CI
supabase/seed.sql, .gitignore, .opencode/worktree-session-state.json, messages/en.json
Seed tweaks to test hashed-only API key branch; added .opencode/ to .gitignore; removed worktree session file; minor i18n additions/fix.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Frontend as Frontend (sso-login.vue / useSSODetection)
    participant Functions as Supabase Functions (/functions/v1/sso_check)
    participant SSO as Identity Provider

    User->>Frontend: Enter email & continue
    Frontend->>Frontend: validate email & filter public domains
    Frontend->>Functions: POST /functions/v1/sso_check (domain)
    Functions-->>Frontend: provider_id, entity_id (or none)
    alt SSO configured
        Frontend->>Frontend: store provider/entity, compute redirect
        Frontend->>SSO: initiate SSO (redirect with RelayState/email)
        SSO-->>User: SSO auth flow and callback redirect
        User->>Frontend: arrives via callback (session established)
    else Not configured
        Frontend-->>User: show standard login or error
    end
Loading
sequenceDiagram
    participant Admin
    participant SettingsUI as Settings UI (sso.vue)
    participant Backend as API/Functions
    participant DB as Database
    participant IdP as Identity Provider

    Admin->>SettingsUI: Open SSO settings
    SettingsUI->>Backend: GET org SSO config
    Backend->>DB: query org_saml_connections & mappings
    DB-->>Backend: return config
    Backend-->>SettingsUI: present config
    Admin->>SettingsUI: Submit IdP metadata & domains
    SettingsUI->>Backend: POST /private/sso_configure
    Backend->>IdP: fetch/validate metadata (if URL)
    IdP-->>Backend: metadata
    Backend->>DB: insert/update org_saml_connections and saml_domain_mappings
    Backend-->>SettingsUI: saved confirmation
    Admin->>SettingsUI: Click test
    SettingsUI->>Backend: POST /private/sso_test
    Backend->>DB: validate config & return checks/results
    Backend-->>SettingsUI: display test results
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~70 minutes

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • riderx

Poem

🐰
I hopped through domains both wide and small,
Built a bridge so users need not stall,
IdP metadata, domains neatly spun,
One login to rule them — hop, it's done! ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.82% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Feature/sso 03 frontend' uses a vague naming convention with 'sso 03' that doesn't clearly convey the main change to someone scanning PR history. Use a more descriptive title like 'Add frontend UI and composables for SAML SSO authentication' to better convey the scope and nature of the changes.
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The PR description includes comprehensive summary, detailed sections for new pages/composables/modifications, internationalization changes, and documented user flows for both admins and end users, covering the template requirements well.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 @messages/en.json:
- Line 1200: Update the localization entry for the key
"remove-bundle-from-this-channel" to correct the typo by changing "budnle" to
"bundle" in the messages/en.json translations so the displayed text reads
"Remove bundle from this channel".
- Line 439: The translation key "choose-which-channel-to-link-this-bundle-to"
contains typos in its value; update the string value to correct "witch" ->
"which" and "channek" -> "channel" so it reads "Choose which channel to link
this bundle to" while keeping the same JSON key unchanged.

In @src/pages/sso-login.vue:
- Line 11: The TypeScript error comes from passing a string literal to useRoute;
remove the hardcoded '/sso-login' argument and call useRoute() with no
parameters, or ensure the route name is present in your typed router definitions
by rebuilding/generating types (check RouteNamedMap in src/typed-router.d.ts and
confirm the auto-generated file-based route includes the sso-login entry);
either update the route types so useRoute('sso-login') is valid or switch to
useRoute() without arguments to fix the type error.
🧹 Nitpick comments (5)
src/composables/useSSODetection.ts (2)

31-33: Consider defensive handling for edge cases.

While the @ check on line 40 prevents calling extractDomain with invalid emails, the function itself is fragile. If called independently, email.split('@')[1] could return undefined.

♻️ Strengthen the function defensively
 function extractDomain(email: string): string {
-  return email.split('@')[1]?.toLowerCase() || ''
+  const parts = email.split('@')
+  return parts.length === 2 ? parts[1].toLowerCase() : ''
 }

49-50: Consider externalizing the public domain list.

Hardcoding the public email providers makes maintenance harder. Consider moving this to a configuration file or environment variable, especially as this list may need updates (e.g., adding protonmail.com, aol.com, etc.).

src/pages/sso-login.vue (1)

27-27: Remove console.log statements before production.

Multiple console.log statements are present throughout the SSO flow (lines 27, 32, 34, 37, 48, 50, 53). While helpful for debugging, these should be removed or converted to proper logging before merging to production.

🧹 Remove debug logs
-  console.log('🔵 SSO Login - Starting flow for:', form.email)
   isLoading.value = true

   try {
     // Check if SSO is available for this domain
-    console.log('🔵 SSO Login - Checking SSO availability...')
     const hasSSO = await checkSSO(form.email)
-    console.log('🔵 SSO Login - SSO available:', hasSSO)

     if (!hasSSO) {
-      console.error('❌ SSO Login - SSO not configured for this email domain')
       toast.error(t('sso-not-configured', 'SSO is not configured for this email domain. Please contact your administrator.'))
       isLoading.value = false
       return
     }
     
     // ... rest of function ...
-    console.log('🔵 SSO Login - Initiating SSO with redirectTo:', redirectTo)
     await initiateSSO(redirectTo, form.email)
-    console.log('🔵 SSO Login - initiateSSO completed (should have redirected)')
   }
   catch (error: any) {
-    console.error('❌ SSO login error:', error)
     toast.error(t('sso-login-failed', 'Failed to initiate SSO login'))
     isLoading.value = false
   }

Also applies to: 32-32, 34-34, 37-37, 48-48, 50-50, 53-53

src/pages/settings/organization/sso.vue (2)

343-349: Domain validation could be more robust.

The domain validation (lines 343-349) performs basic checks:

  • Strips leading @ symbols
  • Checks for . presence
  • Checks minimum length of 3 characters

Consider adding:

  • TLD validation (e.g., ensure at least 2 characters after last dot)
  • Regex pattern for valid domain characters
  • Check for invalid patterns like .. or leading/trailing dots
🔒 Strengthen domain validation
-  // Basic validation
-  if (!domain.includes('.') || domain.length < 3) {
+  // Validate domain format
+  const domainRegex = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*\.[a-z]{2,}$/i
+  if (!domainRegex.test(domain)) {
     toast.error(t('invalid-domain-format', 'Invalid domain format. Please enter a valid domain like "company.com"'))
     return
   }

1-50: Consider splitting this large component.

At 1321 lines, this component is quite large and handles multiple concerns:

  • Configuration state management
  • API interactions for CRUD operations
  • Multi-step wizard UI
  • Toggle state management
  • Test results display

While the current implementation works, consider extracting:

  • API calls into a composable (e.g., useSSOConfig)
  • Wizard steps into separate components
  • Test results into a dedicated component

This would improve maintainability and testability. However, given this is initial SSO feature development and the "Chill" review setting, this can be deferred to future refactoring.

Also applies to: 788-1321

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 70bc57d and 5d90f96.

📒 Files selected for processing (8)
  • messages/en.json
  • src/composables/useSSODetection.ts
  • src/constants/organizationTabs.ts
  • src/pages/login.vue
  • src/pages/settings/organization/sso.vue
  • src/pages/sso-login.vue
  • src/types/supabase.types.ts
  • supabase/functions/_backend/utils/supabase.types.ts
🧰 Additional context used
📓 Path-based instructions (10)
src/**/*.vue

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.vue: Use Vue 3 with Composition API and <script setup> syntax for frontend components
Style components using TailwindCSS with DaisyUI components

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
Use useRoute() from vue-router to access route parameters and useRouter() for programmatic navigation in Vue components

Use DaisyUI (d- prefixed classes) for buttons, inputs, and other interactive primitives to keep behavior and spacing consistent

Files:

  • src/pages/settings/organization/sso.vue
  • src/pages/sso-login.vue
  • src/pages/login.vue
**/*.{ts,tsx,js,jsx,vue}

📄 CodeRabbit inference engine (CLAUDE.md)

Use single quotes and no semicolons per @antfu/eslint-config

Files:

  • src/pages/settings/organization/sso.vue
  • src/constants/organizationTabs.ts
  • src/composables/useSSODetection.ts
  • src/pages/sso-login.vue
  • src/pages/login.vue
src/pages/**/*.vue

📄 CodeRabbit inference engine (CLAUDE.md)

Use file-based routing with unplugin-vue-router for frontend pages

Frontend file-based routing uses src/pages/ directory structure; routes auto-generate with unplugin-vue-router and types are available in src/typed-router.d.ts

Files:

  • src/pages/settings/organization/sso.vue
  • src/pages/sso-login.vue
  • src/pages/login.vue
{capacitor.config.{ts,js},src/**/*.{ts,tsx,vue}}

📄 CodeRabbit inference engine (CLAUDE.md)

Mobile apps should use Capacitor with app ID ee.forgr.capacitor_go for native mobile functionality

Files:

  • src/pages/settings/organization/sso.vue
  • src/constants/organizationTabs.ts
  • src/composables/useSSODetection.ts
  • src/pages/sso-login.vue
  • src/pages/login.vue
src/**/*.{ts,tsx,vue,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use ~/ alias for imports from src/ directory in frontend TypeScript and Vue components

Files:

  • src/pages/settings/organization/sso.vue
  • src/constants/organizationTabs.ts
  • src/composables/useSSODetection.ts
  • src/pages/sso-login.vue
  • src/pages/login.vue
src/**/*.{vue,ts,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Frontend ESLint must pass before commit; run bun lint:fix to auto-fix issues in frontend files

Files:

  • src/pages/settings/organization/sso.vue
  • src/constants/organizationTabs.ts
  • src/composables/useSSODetection.ts
  • src/pages/sso-login.vue
  • src/pages/login.vue
src/**/*.{vue,css,scss}

📄 CodeRabbit inference engine (AGENTS.md)

The web client is built with Vue.js and Tailwind CSS; lean on utility classes and composition-friendly patterns rather than bespoke CSS

Files:

  • src/pages/settings/organization/sso.vue
  • src/pages/sso-login.vue
  • src/pages/login.vue
src/**/*.{vue,ts,tsx,js}

📄 CodeRabbit inference engine (AGENTS.md)

Konsta components are reserved for the safe area helpers; avoid importing konsta anywhere else in the app

Files:

  • src/pages/settings/organization/sso.vue
  • src/constants/organizationTabs.ts
  • src/composables/useSSODetection.ts
  • src/pages/sso-login.vue
  • src/pages/login.vue
src/**/*.{css,scss,vue}

📄 CodeRabbit inference engine (AGENTS.md)

Mirror the Capgo design palette from src/styles/style.css (e.g., --color-primary-500: #515271, --color-azure-500: #119eff) when introducing new UI, using deep slate bases with the Extract azure highlight and soft radii

Files:

  • src/pages/settings/organization/sso.vue
  • src/pages/sso-login.vue
  • src/pages/login.vue
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use TypeScript strict mode with path aliases mapping ~/ to src/

Files:

  • src/constants/organizationTabs.ts
  • src/composables/useSSODetection.ts
🧠 Learnings (5)
📚 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/pages/settings/organization/sso.vue
  • src/pages/sso-login.vue
  • src/pages/login.vue
📚 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
📚 Learning: 2025-12-27T03:51:23.575Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-27T03:51:23.575Z
Learning: Applies to src/**/*.{vue,css,scss} : The web client is built with Vue.js and Tailwind CSS; lean on utility classes and composition-friendly patterns rather than bespoke CSS

Applied to files:

  • src/pages/sso-login.vue
📚 Learning: 2025-12-05T17:34:25.556Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-05T17:34:25.556Z
Learning: Applies to src/pages/**/*.vue : Use file-based routing with unplugin-vue-router for frontend pages

Applied to files:

  • src/pages/sso-login.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 src/pages/**/*.vue : Frontend file-based routing uses `src/pages/` directory structure; routes auto-generate with `unplugin-vue-router` and types are available in `src/typed-router.d.ts`

Applied to files:

  • src/pages/sso-login.vue
🧬 Code graph analysis (1)
src/composables/useSSODetection.ts (1)
src/services/supabase.ts (1)
  • useSupabase (58-71)
🪛 GitHub Actions: Run tests
src/pages/sso-login.vue

[error] 11-11: TS2345: Argument of type '"/sso-login"' is not assignable to parameter of type 'keyof RouteNamedMap | undefined'.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (java-kotlin)
🔇 Additional comments (7)
src/constants/organizationTabs.ts (1)

9-14: LGTM!

The SSO tab addition follows the correct heroicon import pattern and is appropriately positioned in the navigation hierarchy. The route key aligns with the new SSO configuration page.

src/pages/login.vue (1)

33-34: LGTM!

The SSO redirect handling is well-structured:

  • State management with isFromSSO correctly tracks SSO flow
  • Loading UI provides clear feedback during authentication
  • Error recovery resets state appropriately
  • The SSO login link integrates cleanly into the existing UI

Also applies to: 269-274, 283-283, 345-357, 360-360, 408-417

src/pages/sso-login.vue (1)

21-61: SSO flow logic looks solid.

The continueWithSSO function properly:

  • Validates email input before proceeding
  • Checks SSO availability for the domain
  • Handles the redirect with query parameter preservation
  • Provides clear error feedback via toasts
  • Manages loading state appropriately
src/pages/settings/organization/sso.vue (2)

149-238: Comprehensive SSO configuration implementation.

The SSO wizard implementation is thorough and well-structured:

  • ✓ Proper permission checks before allowing configuration
  • ✓ Multi-step wizard with clear progress indicators
  • ✓ Backend error handling with user-friendly messages
  • ✓ SSRF protection mentioned for metadata URLs
  • ✓ Domain uniqueness validation
  • ✓ Separate toggles for SSO and Auto-Join with dependency logic
  • ✓ Test functionality before enabling
  • ✓ Danger zone for deletion with clear warnings

The implementation follows security best practices and provides excellent UX for organization admins.

Also applies to: 243-334, 339-467, 483-611, 616-706, 711-766


126-128: Include 'owner' role in SSO configuration permission check.

The super_admin check is incomplete. The organization store's deleteOrganization method (line 351 in organization.ts) checks both super_admin and owner roles for sensitive operations. The SSO permission check should follow the same pattern by including ['super_admin', 'owner'] to ensure organization owners can also configure SSO, consistent with other privileged organization settings.

⛔ Skipped due to 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.
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: 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.
src/composables/useSSODetection.ts (1)

59-60: The URL manipulation is defensively designed and reliable.

The .replace('/rest/v1', '') pattern is safe: if the path component exists, it gets stripped; if absent, the replace is a no-op. The fallback || '' handles the empty case. This is necessary because Supabase REST API URLs include /rest/v1, but Edge Functions endpoints are at /functions/v1, not /rest/v1/functions/v1. However, note the inconsistency: other files (sso.vue, ChangePassword.vue) construct function endpoints without stripping, suggesting VITE_SUPABASE_URL may already exclude /rest/v1 in some environments. Consider adding a clarifying comment about this defensive pattern or standardizing URL handling across the codebase.

messages/en.json (1)

22-1495: LGTM: Comprehensive SSO and webhook localization added.

The localization keys for SSO configuration (wizard steps, IdP metadata, auto-join, testing), webhooks (CRUD operations, deliveries, signatures), audit logs, credits pricing, and password policies are well-organized and cover all necessary user-facing scenarios. The key naming follows consistent conventions and the messages are clear.

Comment on lines +101 to +146
async function initiateSSO(redirectTo?: string, email?: string): Promise<void> {
if (!ssoProviderId.value)
return

try {
// Check if Supabase URL is local (meaning truly local testing)
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL || ''
const isLocalSupabase = supabaseUrl.includes('localhost') || supabaseUrl.includes('127.0.0.1')

if (isLocalSupabase && email) {
// Use mock SSO endpoint ONLY when Supabase is local
const relayState = redirectTo || '/dashboard'
const mockSSOUrl = `${supabaseUrl}/functions/v1/mock-sso-callback?email=${encodeURIComponent(email)}&RelayState=${encodeURIComponent(relayState)}`

window.location.href = mockSSOUrl
return
}

// Production/Development/Preprod: Use real Supabase SAML SSO
const options: any = {
provider: 'saml',
options: {
providerId: ssoProviderId.value,
},
}

// Add redirect URL if provided
if (redirectTo) {
options.options.redirectTo = `${window.location.origin}${redirectTo}`
}

const { data, error } = await supabase.auth.signInWithSSO(options)

if (error)
throw error

// Redirect to SSO provider (Okta)
if (data?.url) {
window.location.href = data.url
}
}
catch (error) {
console.error('Exception initiating SSO:', error)
throw error
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for mock-sso-callback endpoint implementation
echo "=== Searching for mock-sso-callback endpoint implementation ==="
rg -n 'mock-sso-callback' -C 10

echo ""
echo "=== Searching for mock SSO related functions ==="
rg -n 'mock.*sso|sso.*mock' -i --type ts --type js -C 3

Repository: Cap-go/capgo

Length of output: 3255


🏁 Script executed:

#!/bin/bash
# Search for Supabase functions directory
echo "=== Looking for Supabase functions directory ==="
find . -type d -name 'functions' 2>/dev/null | head -20

echo ""
echo "=== Listing files in supabase/functions if exists ==="
if [ -d "supabase/functions" ]; then
  find supabase/functions -type f -name "*.ts" -o -name "*.js" | head -30
fi

echo ""
echo "=== Searching for files containing 'sso' or 'callback' in functions ==="
find . -path "*/functions/*" -type f \( -name "*.ts" -o -name "*.js" \) 2>/dev/null | xargs grep -l 'sso\|callback' -i 2>/dev/null

Repository: Cap-go/capgo

Length of output: 2054


🏁 Script executed:

#!/bin/bash
# List all top-level directories in supabase/functions
echo "=== Supabase functions structure ==="
ls -la supabase/functions/ | grep -E '^d'

echo ""
echo "=== All files directly in supabase/functions (excluding _backend) ==="
find supabase/functions -maxdepth 1 -type f

echo ""
echo "=== Looking for auth or sso related directories/files ==="
find supabase/functions -type d -name "*auth*" -o -type d -name "*sso*" 2>/dev/null

echo ""
echo "=== Search for all files with 'auth' in supabase/functions ==="
find supabase/functions -type f \( -name "*.ts" -o -name "*.js" \) | xargs grep -l 'auth\|sso' -i 2>/dev/null | head -20

echo ""
echo "=== Check for mock-related functions ==="
find supabase/functions -type f \( -name "*.ts" -o -name "*.js" \) | xargs grep -l 'mock' 2>/dev/null

Repository: Cap-go/capgo

Length of output: 2669


🏁 Script executed:

#!/bin/bash
# Search entire repo for mock-sso
echo "=== Searching entire repo for 'mock-sso' ==="
rg -i 'mock-sso' -C 2

echo ""
echo "=== Searching for test files related to SSO ==="
find . -type f \( -name "*.test.ts" -o -name "*.spec.ts" -o -name "*.test.js" -o -name "*.spec.js" \) | xargs grep -l 'sso' -i 2>/dev/null

echo ""
echo "=== Check if useSSODetection.ts has tests ==="
find . -type f \( -name "*useSSODetection*" \)

echo ""
echo "=== Search for Vitest or Jest mocks ==="
rg -i 'vi.mock|jest.mock' -C 3 | head -50

Repository: Cap-go/capgo

Length of output: 929


🏁 Script executed:

#!/bin/bash
# Look for README or documentation about SSO/local development
echo "=== Searching for SSO or local dev documentation ==="
find . -type f \( -name "README*" -o -name "*.md" \) -exec grep -l 'mock.*sso\|sso.*mock\|local.*dev' -i {} \; 2>/dev/null | head -10

echo ""
echo "=== Check supabase.json or local dev config ==="
find . -type f -name "supabase.json" -o -name ".env*" | head -10

echo ""
echo "=== Search for inline comments about mock-sso endpoint ==="
rg -i 'mock.*sso|sso.*mock' -B 5 -A 5

echo ""
echo "=== Check if there are pending/TODO comments about this ==="
rg -i 'TODO|FIXME|XXX' src/composables/useSSODetection.ts

Repository: Cap-go/capgo

Length of output: 2614


🏁 Script executed:

#!/bin/bash
# Check README and CLAUDE.md for SSO information
echo "=== Checking README.md for SSO/mock mentions ==="
grep -i 'sso\|mock.*endpoint' README.md

echo ""
echo "=== Checking CLAUDE.md ==="
grep -i 'sso\|mock' CLAUDE.md

echo ""
echo "=== Full context from useSSODetection file ==="
wc -l src/composables/useSSODetection.ts
head -20 src/composables/useSSODetection.ts

Repository: Cap-go/capgo

Length of output: 676


🏁 Script executed:

#!/bin/bash
# Read the complete useSSODetection.ts file
cat -n src/composables/useSSODetection.ts

Repository: Cap-go/capgo

Length of output: 5710


Implement the missing mock-sso-callback endpoint for local development SSO testing.

The code at lines 110-117 references a Supabase Edge Function endpoint (/functions/v1/mock-sso-callback) that does not exist in the repository. This will cause the local development SSO authentication flow to fail. The endpoint needs to be created in supabase/functions/ and should:

  1. Accept email and RelayState query parameters
  2. Validate and sanitize the email parameter to prevent injection attacks
  3. Return a valid authentication response that integrates with Supabase Auth

jokabuyasina and others added 27 commits January 7, 2026 22:42
…undefined

fix(migrations): remove redundant revoke on get_customer_counts for service_role
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
* fix: make is_allowed_capgkey support hashed API keys

Update is_allowed_capgkey and get_user_id functions to support both plain-text and hashed API keys using find_apikey_by_value(). Add expiration checks to prevent expired keys from passing validation. Add comprehensive tests for hashed key validation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Haiku 4.5 <[email protected]>

* refactor: use find_apikey_by_value RPC in checkKey

Refactor checkKey function to use the find_apikey_by_value SQL function instead of duplicating the hashing logic in JavaScript. This ensures consistent key lookup behavior between SQL functions and TypeScript code.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Haiku 4.5 <[email protected]>

* refactor: remove isSafeAlphanumeric check from checkKey

Remove the isSafeAlphanumeric validation as it's no longer needed for security. The RPC call to find_apikey_by_value uses parameterized queries, which prevents SQL injection regardless of input characters.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Haiku 4.5 <[email protected]>

* refactor: remove isSafeAlphanumeric function

Remove the isSafeAlphanumeric validation function as it's no longer needed. Both Supabase RPC calls and Drizzle ORM use parameterized queries which prevent SQL injection regardless of input characters.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Haiku 4.5 <[email protected]>

* refactor: use find_apikey_by_value in checkKeyPg

Refactor checkKeyPg to use the find_apikey_by_value SQL function instead of manually hashing and querying. This ensures consistent key lookup behavior between all code paths.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Haiku 4.5 <[email protected]>

* perf: optimize find_apikey_by_value to use single query

Replace sequential two-query approach with a single query using OR.
This reduces database round-trips and allows PostgreSQL to potentially
use index union optimization.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Haiku 4.5 <[email protected]>

* refactor: merge find_apikey_by_value optimization into main migration

Consolidate the find_apikey_by_value query optimization (single query
with OR instead of two sequential queries) into the original migration
file for cleaner PR history.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>

* fix: add index signature to FindApikeyByValueResult type

Drizzle's execute method requires the generic type to satisfy
Record<string, unknown>, so added intersection with index signature.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>

---------

Co-authored-by: Claude Haiku 4.5 <[email protected]>
- Introduced new keys for allowing and helping with bundle previews in Japanese, Korean, Polish, Brazilian Portuguese, Russian, Turkish, Vietnamese, and Simplified Chinese.
- Added error messages for changing preview settings across all supported languages.
- Included success messages for changing preview settings in all relevant languages.
- Updated device names and other related strings to enhance user experience in the respective languages.
…oints

- Updated password compliance validation to use authenticated Supabase client.
- Refactored app deletion logic to utilize API key-based access for data queries.
- Modified build request handling to enforce RLS with API key authentication.
- Enhanced organization member management to ensure proper access control.
- Streamlined webhook operations to leverage API key authentication for data access.
- Improved statistics retrieval to support authenticated access based on user roles.
- Adjusted tests to ensure proper error handling and access control scenarios.
Add comprehensive test coverage for CLI operations using hashed API keys.
Tests verify that all core SDK operations (upload, list, channel management)
work correctly with hashed API keys stored as SHA-256 hashes in the database.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Haiku 4.5 <[email protected]>
* security: remove passwords from all logs

Ensure passwords are never logged to Cloudflare, Supabase, or Discord by:
- Removing password field from cloudlog calls in accept_invitation and validate_password_compliance
- Sanitizing Discord alerts to completely remove password field and partially redact other sensitive fields

🤖 Generated with Claude Code

Co-Authored-By: Claude Haiku 4.5 <[email protected]>

* fix: move password redaction after validation to handle null body

Address PR feedback - if a client sends JSON null, destructuring before
validation throws TypeError (500) instead of returning 400. Move cloudlog
calls after safeParse validation to ensure body is valid before destructuring.

🤖 Generated with Claude Code

Co-Authored-By: Claude Haiku 4.5 <[email protected]>

* chore: remove deno.lock from commit

🤖 Generated with Claude Code

Co-Authored-By: Claude Haiku 4.5 <[email protected]>

---------

Co-authored-by: Claude Haiku 4.5 <[email protected]>
- Add Webhook type alias to webhooks store and Vue components
- Fix apikey.key null check in build/start.ts
The generateEncryptionKeysSDK() function was modifying the project's
capacitor.config.ts without cleanup. The SDK asynchronously writes the
public key to the config during key generation, which was leaving the
test environment dirty. Now we back up and restore the config in a
finally block to ensure clean state regardless of test outcome.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Haiku 4.5 <[email protected]>
This adds a dynamic link to the Supabase dashboard for each Stripe customer, allowing easy navigation from Stripe to the customer's org record in Supabase. The link is parameterized with the SUPABASE_URL environment variable and supports both production and local Supabase instances.

🤖 Generated with Claude Code

Co-authored-by: Claude Haiku 4.5 <[email protected]>
- Refactored webhook GET, POST, and PUT functions to utilize the capgkey from the context instead of the API key for authenticated client access.
- Added new RLS policies to support anon role for webhooks and webhook deliveries, allowing API key-based authentication.
- Updated seed data to include dedicated users and API keys for testing, ensuring isolation between tests.
- Enhanced tests for CLI hashed API keys and RLS to prevent interference with other tests, using dedicated test data.
riderx and others added 8 commits January 7, 2026 23:45
Add SSO configuration wizard, login detection, and UI:
- SSO settings page with multi-step wizard
- SSO login page with email detection
- Login page SSO redirect handling and link
- SSO tab in organization settings
- 65 i18n messages for SSO UI

This is PR #3 of SSO split (frontend only).
@jokabuyasina jokabuyasina force-pushed the feature/sso-03-frontend branch from 5d90f96 to bc3bd8d Compare January 7, 2026 21:46
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
supabase/functions/_backend/private/download_link.ts (2)

40-55: Potential null reference on owner_org access.

On line 47, bundle?.owner_org.created_by will throw a TypeError if bundle exists but owner_org is null (e.g., if the foreign key relationship fails). The null check on line 53 comes too late.

🐛 Suggested fix
   const { data: bundle, error: getBundleError } = await supabase
     .from('app_versions')
     .select('*, owner_org ( created_by )')
     .eq('app_id', body.app_id)
     .eq('id', body.id)
     .single()

-  const ownerOrg = bundle?.owner_org.created_by
+  const ownerOrg = bundle?.owner_org?.created_by

   if (getBundleError) {
     return simpleError('cannot_get_bundle', 'Cannot get bundle', { getBundleError })
   }

57-68: Fix manifest query to use correct column reference.

The manifest query on lines 61-62 uses .eq('app_id', body.app_id) and .eq('id', body.id), but the manifest table does not have these columns. According to the schema, the manifest table has app_version_id as the foreign key to app_versions. Replace the query with .eq('app_version_id', body.id).

Suggested change
  if (body.isManifest) {
    const { data: manifest, error: getManifestError } = await supabase
      .from('manifest')
      .select('*')
-     .eq('app_id', body.app_id)
-     .eq('id', body.id)
+     .eq('app_version_id', body.id)

    if (getManifestError) {
      return simpleError('cannot_get_manifest', 'Cannot get manifest', { getManifestError })
    }
supabase/functions/_backend/public/organization/members/delete.ts (1)

24-29: Inconsistent with the new AGENTS.md guidance you're adding in this PR.

This endpoint uses the admin SDK for user lookup but an authenticated client for the delete operation. The AGENTS.md guidance (added in this same PR) states: "Never use the Supabase admin SDK (with service key) for user-facing APIs."

Since this is a user-facing organization management endpoint, both operations should use the authenticated client. The proper approach is to ensure the RLS policy on the users table allows organization admins to query users within their organization scope, then use supabaseApikey for both the lookup and delete operations.

♻️ Suggested approach
- // Use admin client to lookup user by email - RLS on users table prevents cross-user lookups
- const { data: userData, error: userError } = await supabaseAdmin(c)
+ // Use authenticated client for lookup - RLS should allow org admins to query users in their org
+ const supabase = supabaseApikey(c, c.get('capgkey') as string)
+ const { data: userData, error: userError } = await supabase
    .from('users')
    .select('id')
    .eq('email', body.email)
    .single()

  if (userError || !userData) {
    throw quickError(404, 'user_not_found', 'User not found', { error: userError })
  }

- // Use authenticated client for the delete operation - RLS will enforce org access
- const supabase = supabaseApikey(c, c.get('capgkey') as string)
+ // Continue using authenticated client for the delete operation
  cloudlog({ requestId: c.get('requestId'), message: 'userData.id', data: userData.id })
  cloudlog({ requestId: c.get('requestId'), message: 'body.orgId', data: body.orgId })
  const { error } = await supabase

Then verify that the RLS policy on users table permits this query for organization admins.

Based on learnings and coding guidelines about using authenticated clients for user-facing endpoints.

supabase/functions/_backend/public/webhooks/post.ts (1)

46-56: Add webhooks table definition to Drizzle ORM schema to restore type safety

The (supabase as any) cast is necessary because the webhooks table definition is missing from postgres_schema.ts. Per the project guidelines, all schema definitions must be placed in utils/postgres_schema.ts using Drizzle ORM. Add the webhooks table definition there (matching the structure in the migration: id, org_id, name, url, secret, enabled, events, created_at, updated_at, created_by), then run bun types to regenerate TypeScript types and remove the type assertion.

🤖 Fix all issues with AI agents
In @.opencode/worktree-session-state.json:
- Around line 1-28: Remove the committed local session file
.opencode/worktree-session-state.json from the repository and add the .opencode/
directory to .gitignore so session state is no longer tracked; ensure you delete
the file from the repo index/history (stop tracking it) and commit the change,
and if a .gitignore does not exist at the repo root create one containing
".opencode/" to prevent future commits of files under that directory.

In @package.json:
- Line 4: Revert the manual change to the "version" field in package.json (reset
"version" back to its previous value) and remove any other manual version bumps
in the PR so that release automation/CI can handle version updates during tagged
releases; ensure no code or scripts rely on a hardcoded version string in this
branch and leave version management to the release pipeline.

In @src/pages/sso-login.vue:
- Line 11: The call useRoute('/sso-login') is causing a TypeScript type error;
change it to call useRoute() with no arguments (e.g., replace
useRoute('/sso-login') with useRoute()) and keep using the returned route object
(const route = useRoute()) so the TypeScript types align with the
composition-router API.

In @supabase/functions/_backend/private/invite_new_user_to_org.ts:
- Around line 51-54: The captcha result from verifyCaptchaToken is being
ignored; update the invite_new_user_to_org flow to handle its return value (or
change verifyCaptchaToken to throw) so failed verification stops execution:
capture the result of verifyCaptchaToken(c, body.captcha_token), check for the
returned simpleError/failure case and immediately return that error response (or
rethrow) before creating the Supabase client (supabaseClient) and proceeding;
ensure the handler uses the same simpleError shape that verifyCaptchaToken
returns so RLS/client creation is only reached on successful captcha.
- Around line 68-76: After confirming the org exists, call hasOrgRight(c,
body.org_id, authData.user.id, 'admin') to ensure the requester has admin
permission before proceeding; if that call returns false, return an appropriate
"forbidden" response (e.g., status 403 and message indicating insufficient
permissions) and do not create/update the invitation record; place this check
immediately after the org existence check and before any logic that upserts the
invitation.

In @supabase/functions/_backend/public/build/start.ts:
- Line 49: The non-null assertion on apikey.key (assigned to apikeyKey) can
throw for hashed API keys; change the assignment to pass apikey.key (allowing
null) rather than apikey.key! and update any downstream usages and function
signatures (e.g., the start handler and any calls into supabaseApikey) to accept
string | null so supabaseApikey can perform the fallback (effectiveApikey =
apikey ?? c.get('capgkey')) as intended; remove the forced non-null assertions
and ensure callers propagate nullable types to supabaseApikey.

In @supabase/functions/_backend/public/webhooks/deliveries.ts:
- Around line 40-41: The code forcibly casts c.get('capgkey') to string when
calling supabaseApikey, which can mask undefined and cause runtime issues;
change the call to pass the raw value (e.g., c.get('capgkey')) or explicitly
pass null instead of using "as string" so supabaseApikey can handle
undefined/null itself. Update the invocation of supabaseApikey to remove the
type assertion and allow its internal fallback (reference supabaseApikey and
c.get('capgkey')).

In @supabase/functions/_backend/triggers/logsnag_insights.ts:
- Line 477: Remove the nonexistent plan_enterprise field from the insert/upsert
payload to global_stats: delete the "plan_enterprise: plans.Enterprise || 0"
entry (or replace it with the correct fields plan_solo, plan_maker, plan_team,
plan_enterprise_monthly, plan_enterprise_yearly as intended) so the upsert
against the global_stats table no longer writes a column that isn’t in the
schema; update the payload in the same function where plan_enterprise is set.

In @supabase/functions/_backend/utils/stripe.ts:
- Around line 435-441: The metadata update call after customer creation
(buildSupabaseDashboardLink, metadata,
getStripe(c).customers.update(customer.id, { metadata })) lacks error handling;
wrap that await in a try/catch, log the failure with context (include
customer.id, metadata and the caught error) using the existing logger (or
console.error if no logger available) and rethrow or return an explicit failure
so the error is not silently swallowed and callers know the metadata update
failed.
- Around line 25-40: The hardcoded Supabase table ID 445780 in
buildSupabaseDashboardLink will break across projects; change the URL to
reference the table by stable name or make the table identifier configurable
(env var) instead of embedding 445780. Update buildSupabaseDashboardLink to
construct the Studio/dashboard URL using the orgs table name (e.g., "orgs"
view/path) or read a SUPABASE_ORGS_TABLE_ID environment variable (using getEnv)
and fall back to the table-name-based URL for portability, ensuring both the
local (isLocalSupabase) and production branches use the new approach.

In @tests/cli-hashed-apikey.test.ts:
- Around line 25-35: The CLI-hashed tests currently exercise only the plain-key
lookup because createHashedKeySDK() uses the CLI plain key mapped to
CLI_HASHED_USER_ID, so find_apikey_by_value() matches on WHERE key = apikey and
never tests the key_hash branch; fix by either updating supabase/seed.sql to add
a hashed-only API key row for CLI_HASHED_USER_ID (key = NULL and key_hash =
encode(digest('a7b8c9d0-e1f2-4a3b-8c4d-5e6f7a8b9c03','sha256'),'hex')) or change
the test wiring/constants to point createHashedKeySDK() at the existing user
that has a hashed-only key (user id 6aa76066-55ef-4238-ade6-0b32334a4097) so the
tests exercise the key_hash lookup in find_apikey_by_value().
🧹 Nitpick comments (29)
supabase/migrations/20260107000000_add_anon_role_to_webhooks_rls.sql (1)

124-136: Missing WITH CHECK clause on UPDATE policy.

The webhooks UPDATE policy (lines 49-70) has both USING and WITH CHECK clauses, but this one only has USING. While PostgreSQL defaults to using the USING expression when WITH CHECK is omitted, adding an explicit WITH CHECK would:

  1. Maintain consistency with the webhooks table pattern
  2. Make the write constraint explicit
♻️ Suggested fix for consistency
 CREATE POLICY "Allow admin to update webhook_deliveries"
 ON public.webhook_deliveries
 FOR UPDATE
 TO authenticated, anon
 USING (
     public.check_min_rights(
         'admin'::public.user_min_right,
         (SELECT public.get_identity('{read,upload,write,all}'::public.key_mode[])),
         org_id,
         null::CHARACTER VARYING,
         null::BIGINT
     )
-)
+)
+WITH CHECK (
+    public.check_min_rights(
+        'admin'::public.user_min_right,
+        (SELECT public.get_identity('{read,upload,write,all}'::public.key_mode[])),
+        org_id,
+        null::CHARACTER VARYING,
+        null::BIGINT
+    )
+);
supabase/functions/_backend/private/credits.ts (1)

352-354: Move session ID validation earlier in the handler.

This validation protects against injection in the sourceMatchFilters query, but it runs after the Stripe API call at line 312. For consistency and fail-fast behavior, validate the format immediately after parsing the request body (around line 307).

♻️ Suggested change

Move the validation to the beginning of the handler:

 app.post('/complete-top-up', middlewareAuth, async (c) => {
   const body = await parseBody<CompleteTopUpRequest>(c)
   if (!body.orgId || !body.sessionId)
     throw simpleError('missing_parameters', 'orgId and sessionId are required')

+  // Validate sessionId format to prevent injection (Stripe session IDs: cs_test_* or cs_live_*)
+  if (!/^cs_(?:test|live)_[a-zA-Z0-9]+$/.test(body.sessionId))
+    throw simpleError('invalid_session_id', 'Invalid session ID format')
+
   const { customerId, token } = await resolveOrgStripeContext(c, body.orgId)

And remove lines 352-354 from their current location.

supabase/functions/_backend/private/set_org_email.ts (1)

32-66: Consider migrating to getPgClient() or getDrizzleClient() pattern.

Per coding guidelines, database operations should use getPgClient() or getDrizzleClient() from utils/pg.ts during the active migration to Cloudflare D1. While Supabase client usage may still be acceptable during transition, consider whether this endpoint should be migrated to the new pattern for consistency.

Based on coding guidelines for supabase/functions/_backend/**/*.{ts,js}.

supabase/functions/_backend/private/invite_new_user_to_org.ts (5)

47-48: Inconsistent error response structure.

This error return uses a plain object { message, status } while other errors use simpleError(). This inconsistency may cause issues in the caller at lines 103-104, which checks !res.inviteCreatorUser rather than checking for error properties.

♻️ Suggested fix for consistency
-  if (!authorization)
-    return { message: 'not authorized', status: 401 }
+  if (!authorization)
+    return simpleError('not_authorized', 'Not authorized', {}, 'Missing authorization header')

Note: This will require updating the caller logic at lines 103-108 to handle simpleError return type consistently.


113-114: Non-null assertion on authorization may mask logic errors.

The non-null assertion res.authorization! assumes authorization is always present when res.inviteCreatorUser exists, but this coupling is implicit and fragile. Consider restructuring the return type or adding an explicit check.

♻️ Safer alternative
-  const supabase = supabaseClient(c, res.authorization!)
+  if (!res.authorization) {
+    return quickError(401, 'not_authorized', 'Missing authorization')
+  }
+  const supabase = supabaseClient(c, res.authorization)

64-66: HTTP 500 is inappropriate for "User already exists" error.

A 500 status code indicates a server error, but a user already existing is a client-side validation issue. Consider using 409 (Conflict) or 400 (Bad Request).

♻️ Suggested fix
   if (existingUser || !userError) {
-    return { message: 'Failed to invite user', error: 'User already exists', status: 500 }
+    return { message: 'Failed to invite user', error: 'User already exists', status: 409 }
   }

98-111: Error handling doesn't propagate HTTP status from validateInvite.

When validateInvite returns an error with a specific status (e.g., 401, 409, 500), the caller always returns a generic error via simpleError or quickError with hardcoded status codes, ignoring the actual status from the validation result.

♻️ Suggested improvement

Consider checking res.status and using it in the error response, or restructuring to throw typed errors that carry status codes.


177-206: verifyCaptchaToken returns a value but is typed as returning Promise<void>.

The function returns simpleError(...) on failure but the implicit return type suggests it returns nothing. This contributes to the issue where the caller doesn't handle the return value. Consider either:

  1. Throwing an error on failure, or
  2. Explicitly typing the return and documenting that callers must check it.
♻️ Option 1: Throw on failure (cleaner pattern)
 async function verifyCaptchaToken(c: Context, token: string) {
   const captchaSecret = getEnv(c, 'CAPTCHA_SECRET_KEY')
   if (!captchaSecret) {
-    return simpleError('captcha_secret_key_not_set', 'CAPTCHA_SECRET_KEY not set')
+    throw new Error('CAPTCHA_SECRET_KEY not set')
   }

   // ... fetch logic ...

   if (!captchaResultData.success) {
-    return simpleError('invalid_captcha', 'Invalid captcha result')
+    throw new Error('Invalid captcha result')
   }
   cloudlog({ requestId: c.get('requestId'), context: 'captcha_result', captchaResultData })
   if (captchaResultData.data.success !== true) {
-    return simpleError('invalid_captcha', 'Invalid captcha result')
+    throw new Error('Invalid captcha')
   }
 }

Then wrap the call in try-catch in validateInvite.

supabase/functions/_backend/utils/stripe.ts (1)

16-23: Prefer URL constructor over string splitting.

The current string-splitting approach is fragile and may fail with unexpected URL formats. Using the built-in URL constructor provides more robust parsing.

♻️ Proposed refactor
 function getSupabaseProjectId(c: Context): string | null {
   const supabaseUrl = getEnv(c, 'SUPABASE_URL')
   if (!supabaseUrl)
     return null
-  return supabaseUrl.split('//')[1]?.split('.')[0]?.split(':')[0] || null
+  try {
+    const url = new URL(supabaseUrl)
+    // Extract subdomain from hostname (e.g., "xvwzpoazmxkqosrdewyv" from "xvwzpoazmxkqosrdewyv.supabase.co")
+    return url.hostname.split('.')[0] || null
+  }
+  catch {
+    return null
+  }
 }
src/services/posthog.ts (1)

1-1: Consider adding typed global declarations instead of @ts-nocheck.

While @ts-nocheck works for the dynamic PostHog loader pattern, it disables all TypeScript checking in this file. Consider adding a minimal type declaration for the global posthog object to retain type safety for the exported helper functions.

💡 Suggested type declaration
// Instead of @ts-nocheck, add at the top of the file:
declare global {
  interface Window {
    posthog: {
      init: (token: string, config: object) => void
      capture: (event: string) => void
      identify: (id: string, properties: object) => void
      setPersonProperties: (properties: object) => void
      reset: () => void
      _i: any[]
      __SV?: number
    }
  }
}
declare const posthog: Window['posthog']
supabase/functions/_backend/public/webhooks/deliveries.ts (1)

165-165: Double type cast for JSON payload is acceptable but could be improved.

The as any as WebhookPayload double cast is necessary because the database returns JSON as an unknown type. Consider validating the payload structure if payload integrity is critical.

tests/private-error-cases.test.ts (1)

411-451: invite_new_user_to_org tests accurately cover captcha-first failure path

These two tests clearly document and enforce the behavior that CAPTCHA validation runs before any org lookup, both for existing (testOrgId) and nonexistent (randomUUID()) orgs, and they assert the new captcha_secret_key_not_set error. If the test environment ever needs a global CAPTCHA_SECRET_KEY for other suites, consider explicitly overriding/unsetting it in this describe block to keep these expectations stable, but that’s optional.

supabase/functions/_backend/files/preview.ts (1)

101-112: JWT/RLS-based preview flow is solid; consider hardening Authorization parsing

The updated handler correctly:

  • Accepts a token via Authorization header, token query param, or cookie and persists it in an httpOnly, SameSite: 'Strict' cookie for subsequent asset requests.
  • Uses supabaseClient(c, \Bearer ${token}`)so allapps, app_versions, and manifest` reads go through RLS tied to that JWT.
  • Enforces allow_preview, rejects encrypted bundles, and requires manifest_count > 0, which aligns with the new preview-* error keys.

One small robustness improvement you could make (non-blocking) is to parse the Authorization header case‑insensitively and trim it, so unusual casing or extra whitespace from upstream proxies doesn’t break token extraction.

Optional header parsing tweak
-  const authorization = c.req.header('authorization')
-  const tokenFromQuery = c.req.query('token')
-  const tokenFromCookie = getCookie(c, TOKEN_COOKIE_NAME)
-  const token = authorization?.split('Bearer ')[1] || tokenFromQuery || tokenFromCookie
+  const authHeader = c.req.header('authorization') || ''
+  const bearerMatch = authHeader.match(/^Bearer\s+(.+)$/i)
+  const tokenFromHeader = bearerMatch?.[1]
+  const tokenFromQuery = c.req.query('token')
+  const tokenFromCookie = getCookie(c, TOKEN_COOKIE_NAME)
+  const token = tokenFromHeader || tokenFromQuery || tokenFromCookie

Also applies to: 114-120, 128-138, 150-185

supabase/functions/_backend/public/app/delete.ts (1)

5-6: Good split between apikey and admin clients; consider tightening error handling and org_users filter

The refactor improves security by:

  • Checking hasAppRightApikey up front, and
  • Using supabaseApikey for user-facing tables (app_versions_meta, channel_devices, channels, devices, etc.) while reserving supabaseAdmin for internal stats tables and the final app_versions/apps deletes.

A couple of things are worth revisiting:

  1. Silent failures from intermediate deletes

    All the deletes inside Promise.all([...]) ignore their { error } results. If RLS or constraints prevent some rows from being deleted (especially on the supabase client), you could end up with partial cleanup without any signal. Even a lightweight pattern like logging non‑null error for each operation would make debugging much easier.

  2. org_users filtered by app_id

    The org_users deletion uses .eq('app_id', appId). In most schemas org_users is keyed by org_id/user_id and doesn’t carry an app_id column; if that’s true here, this delete is effectively a no‑op. If you genuinely need to clean up per‑app rows in org_users, double‑check the schema and adjust the filter (or table) accordingly; otherwise you might want to drop this delete entirely.

These aren’t blockers for the current change, but tightening them up would make deleteApp more robust and predictable.

Also applies to: 15-17, 19-24, 32-55, 57-145, 147-160

messages/zh-cn.json (1)

180-181: Preview-related zh-CN strings look consistent and safe

The new preview, device, and fullscreen translations are coherent, match the expected English semantics, and introduce no interpolation or JSON issues. Optional: consider keeping brand names like “Pixel” untranslated if you want stricter consistency with the device label in other locales, but current wording is acceptable.

Also applies to: 316-316, 371-371, 678-681, 738-738, 766-766, 1131-1139, 1199-1199

supabase/functions/_backend/public/statistics/index.ts (2)

79-118: Minor cleanup opportunity in getNormalStats aggregation

Functionally the aggregation logic looks unchanged and compatible with the new client type, but there’s a small readability/perf nit:

  • You sort arrItem twice: once into sortedArrItem (which you log) and then immediately call arrItem?.sort(...).forEach(...) again. Since Array.prototype.sort is in-place, a single sort is enough; you can reuse sortedArrItem in the forEach, or skip the first assignment entirely.

This doesn’t break behaviour, but simplifying it will make the code clearer and slightly cheaper to run.

Also applies to: 128-163


330-388: Guard getLatestVersion against empty input for future callers

getLatestVersion assumes versions is non-empty and seeds reduce with versions[0]. If versionNames ever happens to be empty (e.g. if daily_version returns no rows for a given range) and shouldGetLatestVersion is set to true in future, this will throw at runtime.

Right now /app/:app_id/bundle_usage always passes shouldGetLatestVersion = false, so the path is unreachable, but it’s cheap to make this safe for future use:

Suggested defensive guard
-function getLatestVersion(versions: VersionName[]) {
-  return versions.reduce((latest, current) =>
-    new Date(current.created_at ?? '') > new Date(latest.created_at ?? '') ? current : latest, versions[0])
-}
+function getLatestVersion(versions: VersionName[]) {
+  if (versions.length === 0)
+    return undefined
+
+  return versions.reduce((latest, current) =>
+    new Date(current.created_at ?? '') > new Date(latest.created_at ?? '') ? current : latest, versions[0])
+}
+
+function getLatestVersionPercentage(datasets: any[], latestVersion?: { name?: string }) {
+  if (!latestVersion?.name)
+    return 0
+  const latestVersionDataset = datasets.find(dataset => dataset.label === latestVersion.name)
+  return latestVersionDataset ? latestVersionDataset.data[latestVersionDataset.data.length - 1] : 0
+}

Then adapt the call site to handle undefined safely.

Also applies to: 529-539

src/pages/app/[package].channel.[channel].devices.vue (1)

127-135: Consider validating owner_org before sending to backend.

The empty string fallback (channel.value?.owner_org ?? '') could mask bugs where owner_org is unexpectedly missing. Consider adding explicit validation:

if (!channel.value?.owner_org) {
  toast.error(t('cannot-create-empty-device'))
  return
}

const { error: addDeviceError } = await supabase.functions.invoke('private/create_device', {
  body: {
    device_id: deviceId,
    app_id: route.params.package as string,
    org_id: channel.value.owner_org,
    platform,
    version_name: channel.value?.version.name ?? 'unknown',
  },
})

This provides clearer feedback to users and prevents sending invalid requests to the backend.

To confirm the backend properly handles this case, verify that the create_device function validates org_id and returns a meaningful error for empty or invalid values.

supabase/functions/_backend/public/webhooks/post.ts (1)

45-45: Use apikey.key for consistency with other endpoints

Line 45 passes c.get('capgkey') as string to supabaseApikey, but other endpoints in this PR use apikey.key (e.g., build/request.ts line 91, organization/members/get.ts line 43). Since supabaseApikey already falls back to c.get('capgkey') when the key parameter is null/undefined, prefer the consistent pattern.

♻️ Suggested fix
-  const supabase = supabaseApikey(c, c.get('capgkey') as string)
+  const supabase = supabaseApikey(c, apikey.key)

Based on learnings and as per coding guidelines.

supabase/functions/_backend/public/webhooks/get.ts (1)

36-37: Minor inconsistency in API key parameter passing.

This file uses c.get('capgkey') as string while cancel.ts in this PR uses apikey.key. Both work because supabaseApikey falls back to c.get('capgkey') when the first parameter is null/undefined, but using a consistent pattern across all webhook endpoints would improve maintainability.

The current approach is functionally correct since supabaseApikey handles the fallback internally.

supabase/functions/_backend/utils/hono_middleware.ts (1)

63-84: Consider pushing mode and expiration filtering to the SQL function for consistency.

The checkKeyByIdPg function (lines 117-127) filters by mode and expiration in SQL using notExpiredCondition, but checkKeyPg performs these checks in JavaScript after fetching the key. This creates an inconsistency:

  1. Clock skew risk: The JS expiration check (new Date(apiKey.expires_at) < new Date()) may differ from the DB's now() if clocks are not synchronized.
  2. Unnecessary data transfer: Keys with wrong modes or expired keys are still fetched from the DB.

If the find_apikey_by_value SQL function cannot be modified to accept mode/expiration filters, this approach is acceptable as a fallback, but documenting the rationale would help future maintainers.

supabase/functions/_backend/public/webhooks/test.ts (1)

78-81: Consider error handling for the delivery update.

The attempt_count update doesn't check for errors. While this is a non-critical update, silent failures could make debugging difficult.

♻️ Suggested improvement
-  await (supabase as any)
+  const { error: updateError } = await (supabase as any)
     .from('webhook_deliveries')
     .update({ attempt_count: 1 })
     .eq('id', delivery.id)
+
+  if (updateError) {
+    console.warn('Failed to update attempt_count:', updateError)
+  }
SSO_PR_SPLIT_PLAN.md (1)

22-25: Add language specifiers to fenced code blocks for better rendering.

Per markdownlint, fenced code blocks should have language specifiers. This applies to lines 22, 88, 164, 236, and 285.

♻️ Example fix
-```
+```text
 supabase/migrations/20260107_sso_saml_complete.sql
</details>

</blockquote></details>
<details>
<summary>src/composables/useSSODetection.ts (3)</summary><blockquote>

`49-53`: **Consider externalizing or extending the public domain list.**

The hardcoded list of public email domains is limited. Common domains like `protonmail.com`, `aol.com`, `live.com`, `mail.com` are not included. Consider:
1. Extracting to a constants file for easier maintenance
2. Adding more common public email providers

---

`58-66`: **URL construction could be fragile.**

The pattern `replace('/rest/v1', '')` assumes the Supabase URL always contains `/rest/v1`. This may not hold for all configurations. Consider using `URL` API for safer manipulation.


<details>
<summary>♻️ Safer URL construction</summary>

```diff
-      const apiUrl = import.meta.env.VITE_SUPABASE_URL?.replace('/rest/v1', '') || ''
+      // Extract base URL from Supabase URL (remove /rest/v1 if present)
+      let apiUrl = import.meta.env.VITE_SUPABASE_URL || ''
+      try {
+        const url = new URL(apiUrl)
+        url.pathname = ''
+        apiUrl = url.origin
+      }
+      catch {
+        apiUrl = apiUrl.replace('/rest/v1', '')
+      }

120-125: Avoid any type for SSO options.

Using any loses type safety. The Supabase SDK provides types for SSO options.

♻️ Type-safe options
-      const options: any = {
-        provider: 'saml',
-        options: {
-          providerId: ssoProviderId.value,
-        },
-      }
+      const options = {
+        provider: 'saml' as const,
+        options: {
+          providerId: ssoProviderId.value,
+          redirectTo: redirectTo ? `${window.location.origin}${redirectTo}` : undefined,
+        },
+      }

Then remove the separate redirectTo assignment block.

src/pages/sso-login.vue (1)

52-56: Consider more specific error handling.

The catch block logs the error and shows a generic message. Since initiateSSO can throw specific errors, consider providing more helpful user feedback based on the error type.

supabase/functions/_backend/utils/webhook.ts (1)

70-89: Return type inference is acceptable but consider explicit typing for API stability.

The removal of explicit return types relies on Supabase's type inference. While this works, explicit return types on exported functions provide better API documentation and catch breaking changes earlier.

♻️ Optional: Add explicit return types for documentation
 export async function findWebhooksForEvent(
   c: Context,
   orgId: string,
   tableName: string,
-) {
+): Promise<Database['public']['Tables']['webhooks']['Row'][]> {
src/stores/webhooks.ts (1)

406-419: Local state update in retryDelivery may cause stale data.

When retrying a delivery, the code optimistically updates local state (lines 408-412) but the actual retry happens asynchronously via an edge function. If the retry fails or the status changes differently, the local state won't reflect reality until the next fetch.

Consider either:

  1. Re-fetching deliveries after a successful retry request
  2. Adding a comment documenting this is an optimistic update

Comment on lines 1 to 28
{
"sessions": {
"ses_46bc1d6ffffeljgiVpepptQJpo": {
"sessionId": "ses_46bc1d6ffffeljgiVpepptQJpo",
"createdAt": 1767718660391
},
"ses_46ba7fa47ffeBntHbdn9H2Q1zZ": {
"sessionId": "ses_46ba7fa47ffeBntHbdn9H2Q1zZ",
"createdAt": 1767720355306
},
"ses_46ba7dd2cffeJ97QlIv6YZPNU5": {
"sessionId": "ses_46ba7dd2cffeJ97QlIv6YZPNU5",
"createdAt": 1767720362750
},
"ses_46ba7d141ffeHeloQHLposBOMp": {
"sessionId": "ses_46ba7d141ffeHeloQHLposBOMp",
"createdAt": 1767720365780
},
"ses_46ba317c2ffezf9crrwEIUl8QN": {
"sessionId": "ses_46ba317c2ffezf9crrwEIUl8QN",
"createdAt": 1767720675420
},
"ses_46ba2f84effe4l1eoBWyYUSSox": {
"sessionId": "ses_46ba2f84effe4l1eoBWyYUSSox",
"createdAt": 1767720683481
}
}
} No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Remove this file and add .opencode/ to .gitignore.

This file contains local development tool session state that should not be committed to version control. It will cause merge conflicts, pollute the repository history, and potentially expose sensitive session identifiers.

🔧 Recommended fix
  1. Remove this file from the commit:
git rm .opencode/worktree-session-state.json
  1. Add the .opencode directory to .gitignore to prevent future commits:
+# OpenCode IDE
+.opencode/
  1. If .gitignore doesn't exist at the repository root, create it with the above content.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In @.opencode/worktree-session-state.json around lines 1 - 28, Remove the
committed local session file .opencode/worktree-session-state.json from the
repository and add the .opencode/ directory to .gitignore so session state is no
longer tracked; ensure you delete the file from the repo index/history (stop
tracking it) and commit the change, and if a .gitignore does not exist at the
repo root create one containing ".opencode/" to prevent future commits of files
under that directory.

package.json Outdated
"name": "capgo-app",
"type": "module",
"version": "12.89.4",
"version": "12.89.14",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Version bump should be handled by release automation.

The version field was incremented from 12.89.4 to 12.89.14 while the PR is still open. Based on learnings, version updates should be left to CI/CD during tagged releases.

🤖 Prompt for AI Agents
In @package.json at line 4, Revert the manual change to the "version" field in
package.json (reset "version" back to its previous value) and remove any other
manual version bumps in the PR so that release automation/CI can handle version
updates during tagged releases; ensure no code or scripts rely on a hardcoded
version string in this branch and leave version management to the release
pipeline.

Comment on lines 61 to 68
const { error: appError } = await supabase.from('apps')
.select('owner_org')
.eq('app_id', safeBody.app_id)
.single()

if (appError) {
return quickError(404, 'app_not_found', 'App not found', { app_id: safeBody.app_id })
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

App existence check could be more precise.

Two concerns with this check:

  1. Error handling: Any database error (not just "not found") returns a 404, which could mask other issues.
  2. Missing validation: The check doesn't verify that the app belongs to the org_id provided in the request. A user could potentially create a device for an app they have access to but associate it with a different org_id.
🐛 Suggested improvement
-  const { error: appError } = await supabase.from('apps')
+  const { data: app, error: appError } = await supabase.from('apps')
     .select('owner_org')
     .eq('app_id', safeBody.app_id)
     .single()

-  if (appError) {
+  if (appError || !app) {
     return quickError(404, 'app_not_found', 'App not found', { app_id: safeBody.app_id })
   }
+
+  if (app.owner_org !== safeBody.org_id) {
+    return quickError(403, 'org_mismatch', 'App does not belong to the specified organization', { app_id: safeBody.app_id, org_id: safeBody.org_id })
+  }

Comment on lines 435 to 441
// Add supabase dashboard link with the real customer ID after creation
const supabaseLink = buildSupabaseDashboardLink(c, customer.id)
if (supabaseLink) {
metadata.supabase = supabaseLink
await getStripe(c).customers.update(customer.id, { metadata })
}
return customer
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add error handling for metadata update after customer creation.

The Supabase dashboard link update occurs after customer creation without error handling. If this update fails, the error is swallowed silently, and the returned customer object won't reflect the updated metadata.

🛡️ Suggested fix with error handling
  const customer = await getStripe(c).customers.create({
    email,
    name,
    metadata,
  })
  // Add supabase dashboard link with the real customer ID after creation
  const supabaseLink = buildSupabaseDashboardLink(c, customer.id)
  if (supabaseLink) {
    metadata.supabase = supabaseLink
-   await getStripe(c).customers.update(customer.id, { metadata })
+   try {
+     const updatedCustomer = await getStripe(c).customers.update(customer.id, { metadata })
+     return updatedCustomer
+   }
+   catch (error) {
+     cloudlogErr({ requestId: c.get('requestId'), message: 'Failed to update customer with Supabase link', customerId: customer.id, error })
+     // Return original customer if metadata update fails
+   }
  }
  return customer
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Add supabase dashboard link with the real customer ID after creation
const supabaseLink = buildSupabaseDashboardLink(c, customer.id)
if (supabaseLink) {
metadata.supabase = supabaseLink
await getStripe(c).customers.update(customer.id, { metadata })
}
return customer
// Add supabase dashboard link with the real customer ID after creation
const supabaseLink = buildSupabaseDashboardLink(c, customer.id)
if (supabaseLink) {
metadata.supabase = supabaseLink
try {
const updatedCustomer = await getStripe(c).customers.update(customer.id, { metadata })
return updatedCustomer
}
catch (error) {
cloudlogErr({ requestId: c.get('requestId'), message: 'Failed to update customer with Supabase link', customerId: customer.id, error })
// Return original customer if metadata update fails
}
}
return customer
🤖 Prompt for AI Agents
In @supabase/functions/_backend/utils/stripe.ts around lines 435 - 441, The
metadata update call after customer creation (buildSupabaseDashboardLink,
metadata, getStripe(c).customers.update(customer.id, { metadata })) lacks error
handling; wrap that await in a try/catch, log the failure with context (include
customer.id, metadata and the caught error) using the existing logger (or
console.error if no logger available) and rethrow or return an explicit failure
so the error is not silently swallowed and callers know the metadata update
failed.

- Fix 'witch channek' -> 'which channel' in choose-which-channel-to-link-this-bundle-to
- Fix 'budnle' -> 'bundle' in remove-bundle-from-this-channel
- Remove hardcoded '/sso-login' argument from useRoute() call
- Remove .opencode/ session state from tracking and add to .gitignore
- Create mock-sso-callback endpoint for local SSO testing with email validation
- Remove debug console.log statements from sso-login.vue
- Fix NULL comparisons in SQL migration (use NULLIF pattern instead of direct comparisons)
- Remove type assertion on capgkey in webhooks/deliveries.ts
- Remove nonexistent plan_enterprise field from logsnag_insights.ts
- Replace hardcoded Supabase table ID 445780 with table name in stripe.ts
- Add error handling for Stripe customer metadata updates
- Improve app ownership validation in create_device.ts
- Fix regex pattern in discord.ts to handle JSON-escaped quotes
- Update seed.sql to use hashed-only API key for CLI tests
- Add redactKey() helper function to show only first 8 characters
- Redact capgkeyString in 'Capgkey provided' and 'Invalid apikey' logs
- Redact JWT in 'JWT provided' and 'Setting apikey' logs
- Log only subkey.id instead of full subkey object to avoid exposing key/key_hash
- Prevents CodeQL high severity vulnerability: Clear-text logging of sensitive information

Fixes clear-text logging of sensitive data returned by access to apikey/apiKey
- Restore Database type export from commit b719d45
- Copy types to both src/types/ and supabase/functions/_backend/utils/
- Fixes TypeScript error in hono_middleware.ts
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
supabase/functions/_backend/private/validate_password_compliance.ts (1)

70-79: Critical: Admin client bypasses RLS on public endpoint.

This endpoint has no authentication middleware, making it publicly accessible. Using the admin client to query org password policies bypasses RLS, allowing anyone to retrieve any organization's password policy configuration by providing an arbitrary org_id.

Based on learnings, the admin SDK should only be used for internal operations, not user-facing APIs. While password policies may not be highly sensitive, this violates the principle of least privilege and could expose organizational metadata.

Recommendations:

  1. Add authentication middleware to this endpoint, OR
  2. If the endpoint must be public (e.g., for pre-auth policy display), ensure RLS policies on the orgs table restrict password_policy_config visibility appropriately, OR
  3. Create a dedicated public RPC function with controlled access to policy data
🤖 Fix all issues with AI agents
In @messages/en.json:
- Line 715: The messages/en.json is missing seven i18n keys used by
sso-login.vue; add the keys "sso-login-title", "sso-login-subtitle",
"work-email", "sso-not-configured", "sso-login-failed", "sso-info", and
"back-to-login" to messages/en.json (and mirror them into other locale files)
with the appropriate English strings used in the SSO login UI so the component's
t() calls resolve to localized text rather than falling back to hardcoded
strings.

In @src/pages/sso-login.vue:
- Around line 145-147: The template uses double quotes in the i18n call
t("support"); update the template expression to use single quotes (t('support'))
to conform to the project's linting/quote style—locate the button template
containing @click="openSupport" and replace t("support") with t('support') only.
- Around line 21-50: In continueWithSSO, prevent open redirects by constraining
route.query.to to a safe internal path (must be a string that startsWith('/')
and not include protocol/host); fall back to '/dashboard' otherwise, and use
that sanitized value when calling initiateSSO. Also ensure isLoading.value is
always cleared after initiateSSO returns or fails — wrap the async work in a
try/finally (or set isLoading.value = false immediately after await initiateSSO
if it returns) so the UI never remains stuck loading; reference continueWithSSO,
route.query.to, initiateSSO, and isLoading.value when making these changes.

In @supabase/functions/_backend/utils/discord.ts:
- Around line 21-47: The regex construction in sanitizeSensitiveFromString uses
raw field names from REMOVED_FIELDS and PARTIALLY_REDACTED_FIELDS and must
escape regex metacharacters to avoid ReDoS; add a small helper like escapeRegex
that replaces characters like . * + ? ^ $ { } ( ) | [ ] \ / with escaped
versions, then use escapeRegex(field) when building the two RegExp instances
(the jsonRegexWithComma and jsonRegex) so patterns are safe even if field names
change or become configurable; keep the existing logic and calls to
partialRedact and the replacement flows unchanged.

In @supabase/functions/mock-sso-callback/index.ts:
- Around line 4-13: The mock SSO callback endpoint in
supabase/functions/mock-sso-callback/index.ts must be hard-blocked outside
local/dev; add an environment guard (e.g., check NODE_ENV !==
'development'/'local' or a dedicated flag like MOCK_SSO_ENABLED) at the start of
the exported handler for this function and return an early 403/404 response with
a brief log when the guard fails. Reuse the same environment threading approach
your backend uses (middleware or env helper) to read the env flag, and ensure
the guard runs before any email validation or response generation so the
simulated SSO cannot be invoked in production.
- Around line 21-47: The current handler in app.get('/', async (c: Context) => {
... }) builds redirectUrl from c.req.url which causes the endpoint to redirect
back to itself (infinite loop) and allows an open-redirect via RelayState; fix
by validating and normalizing relayState before use (ensure it is either a
relative path starting with '/' and not containing '//' or a full URL only if it
matches a strict allowlist) and then construct redirectUrl to point to the
validated relayState (not c.req.url) with success and email query params; update
the redirectUrl construction (the symbol to change is redirectUrl) to use
encodeURIComponent for params and return c.redirect to that safe, normalized
path to eliminate the loop and close the open-redirect risk.
- Around line 15-17: Import the version value and pass it plus the Sentry DSN as
the second and third arguments to createHono (replace createHono(functionName,
import.meta.url) with createHono(functionName, version,
Deno.env.get('SENTRY_DSN_SUPABASE'))), and update the route handler signature to
use the generic Context<MiddlewareKeyVariables> (change the app.get('/', async
(c: Context) => ...) to app.get('/', async (c: Context<MiddlewareKeyVariables>)
=> ...)) so middleware-provided keys like requestId and auth are correctly
typed.

In @supabase/seed.sql:
- Around line 487-492: The INSERT row for the CLI hashed key tuple starting with
(110, NOW(), 'e5f6a7b8-...') is missing the key_hash column in its column list,
causing value misalignment; update the INSERT to include key_hash (and set
key=NULL) so the encode(extensions.digest(...), 'hex') expression maps to
key_hash and the subsequent 'all' and NOW() map to mode and updated_at
respectively, matching the pattern used for other hashed-key rows.
🧹 Nitpick comments (3)
supabase/functions/_backend/utils/discord.ts (1)

109-110: Remove type assertion for better type safety.

The as any cast bypasses TypeScript's type checking. The c.req.raw.headers should be a standard Headers object from the Web API, which has an .entries() method.

♻️ Proposed fix: Use proper Headers typing
-  const rawHeaders = Object.fromEntries((c.req.raw.headers as any).entries())
+  const rawHeaders = Object.fromEntries(c.req.raw.headers.entries())
   const headers = sanitizeSensitiveHeaders(rawHeaders)

If TypeScript still complains, the Headers interface should already include entries(). If not, consider using:

-  const rawHeaders = Object.fromEntries((c.req.raw.headers as any).entries())
+  const rawHeaders = Object.fromEntries(Array.from(c.req.raw.headers.entries()))
   const headers = sanitizeSensitiveHeaders(rawHeaders)
supabase/functions/_backend/utils/hono_middleware.ts (1)

74-95: Consider moving expiration check to the SQL function for consistency and efficiency.

The expiration check at lines 92-95 is performed in JavaScript, while checkKeyByIdPg performs the same check in SQL using notExpiredCondition (line 135). This inconsistency means:

  1. Less efficient: The database returns expired keys that are immediately rejected
  2. Inconsistent pattern: Similar operations use different approaches
  3. Extra network overhead: Transferring expired keys from DB to application

Consider updating the find_apikey_by_value SQL function to include the expiration condition directly:

-- In the SQL function
WHERE (expires_at IS NULL OR expires_at > now())

This would align with checkKeyByIdPg and eliminate the need for the JavaScript check at lines 92-95.

Based on coding guidelines requiring consistent database operation patterns with Drizzle ORM.

src/pages/sso-login.vue (1)

73-148: UI primitives: consider aligning buttons with DaisyUI (d- components/classes)

The primary CTA button is custom Tailwind; if the rest of the app standardizes on DaisyUI for interactive elements, consider swapping to keep spacing/hover/focus consistent (per coding guidelines).

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bc3bd8d and fee3872.

📒 Files selected for processing (12)
  • .gitignore
  • messages/en.json
  • src/pages/sso-login.vue
  • supabase/functions/_backend/private/create_device.ts
  • supabase/functions/_backend/private/validate_password_compliance.ts
  • supabase/functions/_backend/public/webhooks/deliveries.ts
  • supabase/functions/_backend/utils/discord.ts
  • supabase/functions/_backend/utils/hono_middleware.ts
  • supabase/functions/_backend/utils/stripe.ts
  • supabase/functions/mock-sso-callback/index.ts
  • supabase/migrations/20260107210800_sso_saml_complete.sql
  • supabase/seed.sql
✅ Files skipped from review due to trivial changes (1)
  • .gitignore
🚧 Files skipped from review as they are similar to previous changes (3)
  • supabase/functions/_backend/public/webhooks/deliveries.ts
  • supabase/functions/_backend/utils/stripe.ts
  • supabase/migrations/20260107210800_sso_saml_complete.sql
🧰 Additional context used
📓 Path-based instructions (16)
**/*.{ts,tsx,js,jsx,vue}

📄 CodeRabbit inference engine (CLAUDE.md)

Use single quotes and no semicolons per @antfu/eslint-config

Files:

  • supabase/functions/_backend/utils/hono_middleware.ts
  • supabase/functions/mock-sso-callback/index.ts
  • supabase/functions/_backend/private/create_device.ts
  • supabase/functions/_backend/utils/discord.ts
  • src/pages/sso-login.vue
  • supabase/functions/_backend/private/validate_password_compliance.ts
supabase/functions/_backend/**

📄 CodeRabbit inference engine (CLAUDE.md)

Backend logic should be organized in supabase/functions/_backend/ with subdirectories for plugins, private endpoints, public endpoints, triggers, and utilities

Files:

  • supabase/functions/_backend/utils/hono_middleware.ts
  • supabase/functions/_backend/private/create_device.ts
  • supabase/functions/_backend/utils/discord.ts
  • supabase/functions/_backend/private/validate_password_compliance.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use TypeScript strict mode with path aliases mapping ~/ to src/

Files:

  • supabase/functions/_backend/utils/hono_middleware.ts
  • supabase/functions/mock-sso-callback/index.ts
  • supabase/functions/_backend/private/create_device.ts
  • supabase/functions/_backend/utils/discord.ts
  • supabase/functions/_backend/private/validate_password_compliance.ts
supabase/functions/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Supabase Edge Functions use Deno runtime

Files:

  • supabase/functions/_backend/utils/hono_middleware.ts
  • supabase/functions/mock-sso-callback/index.ts
  • supabase/functions/_backend/private/create_device.ts
  • supabase/functions/_backend/utils/discord.ts
  • supabase/functions/_backend/private/validate_password_compliance.ts
supabase/functions/_backend/**/*.{ts,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

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
Use createHono from utils/hono.ts for all Hono framework application initialization and routing
All database operations must use getPgClient() or getDrizzleClient() from utils/pg.ts for PostgreSQL access during active migration to Cloudflare D1
All Hono endpoint handlers must accept Context<MiddlewareKeyVariables> and use c.get('requestId'), c.get('apikey'), and c.get('auth') for request context
Use structured logging with cloudlog({ requestId: c.get('requestId'), message: '...' }) for all backend logging
Use middlewareAPISecret for internal API endpoints and middlewareKey for external API keys; validate against owner_org in the apikeys table
Check c.get('auth')?.authType to determine authentication type ('apikey' vs 'jwt') in backend endpoints
Use Drizzle ORM query patterns with schema from postgress_schema.ts for all database operations; use aliasV2() for self-joins or multiple table references

Files:

  • supabase/functions/_backend/utils/hono_middleware.ts
  • supabase/functions/_backend/private/create_device.ts
  • supabase/functions/_backend/utils/discord.ts
  • supabase/functions/_backend/private/validate_password_compliance.ts
supabase/functions/**/*.{ts,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Backend ESLint must pass before commit; run bun lint:backend for backend files

Files:

  • supabase/functions/_backend/utils/hono_middleware.ts
  • supabase/functions/mock-sso-callback/index.ts
  • supabase/functions/_backend/private/create_device.ts
  • supabase/functions/_backend/utils/discord.ts
  • supabase/functions/_backend/private/validate_password_compliance.ts
src/**/*.vue

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.vue: Use Vue 3 with Composition API and <script setup> syntax for frontend components
Style components using TailwindCSS with DaisyUI components

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
Use useRoute() from vue-router to access route parameters and useRouter() for programmatic navigation in Vue components

Files:

  • src/pages/sso-login.vue
src/pages/**/*.vue

📄 CodeRabbit inference engine (CLAUDE.md)

Use file-based routing with unplugin-vue-router for frontend pages

Frontend file-based routing uses src/pages/ directory structure; routes auto-generate with unplugin-vue-router and types are available in src/typed-router.d.ts

Files:

  • src/pages/sso-login.vue
{capacitor.config.{ts,js},src/**/*.{ts,tsx,vue}}

📄 CodeRabbit inference engine (CLAUDE.md)

Mobile apps should use Capacitor with app ID ee.forgr.capacitor_go for native mobile functionality

Files:

  • src/pages/sso-login.vue
src/**/*.{ts,tsx,vue,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use ~/ alias for imports from src/ directory in frontend TypeScript and Vue components

Files:

  • src/pages/sso-login.vue
src/**/*.{vue,ts,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Frontend ESLint must pass before commit; run bun lint:fix to auto-fix issues in frontend files

Files:

  • src/pages/sso-login.vue
**/{src,app}/{components,views,pages}/**/*.{vue,css,scss}

📄 CodeRabbit inference engine (AGENTS.md)

The web client is built with Vue.js and Tailwind CSS; lean on utility classes and composition-friendly patterns rather than bespoke CSS.

Files:

  • src/pages/sso-login.vue
**/{src,app}/{components,views,pages}/**/*.vue

📄 CodeRabbit inference engine (AGENTS.md)

Use DaisyUI (d- prefixed classes) for buttons, inputs, and other interactive primitives to keep behavior and spacing consistent.

Files:

  • src/pages/sso-login.vue
**/{src,app}/**/*.{vue,ts,tsx,js}

📄 CodeRabbit inference engine (AGENTS.md)

Konsta components are reserved for the safe area helpers. Avoid importing konsta anywhere else in the app.

Files:

  • src/pages/sso-login.vue
**/{src,app}/**/*.{css,scss,vue}

📄 CodeRabbit inference engine (AGENTS.md)

Capgo's design uses deep slate bases with the 'Extract' azure highlight (--color-azure-500: #119eff) and soft radii; mirror the palette from src/styles/style.css (e.g., --color-primary-500: #515271) when introducing new UI.

Files:

  • src/pages/sso-login.vue
supabase/seed.sql

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Seed database with supabase db reset to apply all migrations and test data from supabase/seed.sql

Updating supabase/seed.sql to back new or evolved tests is expected; keep fixtures focused on current behavior while leaving committed migrations unchanged.

Files:

  • supabase/seed.sql
🧠 Learnings (20)
📚 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/utils/hono_middleware.ts
  • supabase/functions/_backend/private/create_device.ts
  • supabase/functions/_backend/utils/discord.ts
  • 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: 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/utils/hono_middleware.ts
  • supabase/functions/mock-sso-callback/index.ts
  • supabase/functions/_backend/private/create_device.ts
  • supabase/functions/_backend/private/validate_password_compliance.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/utils/hono_middleware.ts
  • supabase/functions/mock-sso-callback/index.ts
  • supabase/functions/_backend/private/create_device.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/utils/hono_middleware.ts
  • supabase/functions/mock-sso-callback/index.ts
  • supabase/functions/_backend/private/create_device.ts
  • supabase/functions/_backend/private/validate_password_compliance.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/utils/hono_middleware.ts
  • supabase/functions/_backend/utils/discord.ts
📚 Learning: 2026-01-08T00:40:00.524Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-08T00:40:00.524Z
Learning: Applies to **/{api,server,src/server,backend}/**/*.{ts,tsx,js} : Never use the Supabase admin SDK (with service key) for user-facing APIs. Always use the client SDK with user authentication so RLS policies are enforced. The admin SDK should only be used for internal operations (triggers, CRON jobs, etc.), and when unavoidable, sanitize all user inputs carefully to prevent PostgREST query injection.

Applied to files:

  • supabase/functions/_backend/utils/hono_middleware.ts
  • supabase/functions/_backend/private/create_device.ts
  • supabase/functions/_backend/private/validate_password_compliance.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/utils/hono_middleware.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/utils/hono_middleware.ts
  • supabase/functions/_backend/private/validate_password_compliance.ts
📚 Learning: 2025-12-05T17:34:25.556Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-05T17:34:25.556Z
Learning: Applies to supabase/functions/**/*.ts : Supabase Edge Functions use Deno runtime

Applied to files:

  • supabase/functions/_backend/utils/hono_middleware.ts
  • supabase/functions/mock-sso-callback/index.ts
  • supabase/functions/_backend/private/validate_password_compliance.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/create_device.ts
  • supabase/seed.sql
📚 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/create_device.ts
📚 Learning: 2026-01-08T00:40:00.524Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-08T00:40:00.524Z
Learning: Applies to **/{src,app}/{components,views,pages}/**/*.{vue,css,scss} : The web client is built with Vue.js and Tailwind CSS; lean on utility classes and composition-friendly patterns rather than bespoke CSS.

Applied to files:

  • src/pages/sso-login.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 src/**/*.vue : Use `useRoute()` from `vue-router` to access route parameters and `useRouter()` for programmatic navigation in Vue components

Applied to files:

  • src/pages/sso-login.vue
📚 Learning: 2025-12-05T17:34:25.556Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-05T17:34:25.556Z
Learning: Applies to src/pages/**/*.vue : Use file-based routing with unplugin-vue-router for frontend pages

Applied to files:

  • src/pages/sso-login.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 src/pages/**/*.vue : Frontend file-based routing uses `src/pages/` directory structure; routes auto-generate with `unplugin-vue-router` and types are available in `src/typed-router.d.ts`

Applied to files:

  • src/pages/sso-login.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/pages/sso-login.vue
📚 Learning: 2026-01-08T00:40:00.524Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-08T00:40:00.524Z
Learning: Applies to supabase/seed.sql : Updating `supabase/seed.sql` to back new or evolved tests is expected; keep fixtures focused on current behavior while leaving committed migrations unchanged.

Applied to files:

  • supabase/seed.sql
📚 Learning: 2026-01-08T00:40:00.524Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-08T00:40:00.524Z
Learning: Applies to supabase/migrations/**/*.sql : A migration that introduces a new table may include seed inserts for that table, but treat that seeding as part of the current feature and do not modify previously committed migrations.

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: 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: 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
🧬 Code graph analysis (4)
supabase/functions/mock-sso-callback/index.ts (1)
supabase/functions/_backend/utils/hono.ts (1)
  • createHono (137-196)
supabase/functions/_backend/private/create_device.ts (2)
supabase/functions/_backend/utils/supabase.ts (1)
  • supabaseWithAuth (50-61)
supabase/functions/_backend/utils/hono.ts (1)
  • quickError (224-237)
supabase/functions/_backend/utils/discord.ts (1)
scripts/snippet/cloudflare-snippet-filter-appid.js (1)
  • body (65-65)
supabase/functions/_backend/private/validate_password_compliance.ts (3)
supabase/functions/_backend/utils/logging.ts (1)
  • cloudlog (3-15)
supabase/functions/_backend/utils/hono.ts (1)
  • quickError (224-237)
supabase/functions/_backend/utils/supabase.ts (1)
  • supabaseClient (38-48)
🪛 ast-grep (0.40.4)
supabase/functions/_backend/utils/discord.ts

[warning] 28-28: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp("${field}"\\s*:\\s*"(?:[^"\\\\]|\\\\.)*"\\s*,?\\s*, 'gi')
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)


[warning] 39-39: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(("${field}"\\s*:\\s*)"((?:[^"\\\\]|\\\\.)*)", 'gi')
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

🔇 Additional comments (25)
supabase/functions/_backend/utils/discord.ts (6)

8-11: LGTM! Well-defined redaction rules.

The separation between completely removed fields (password) and partially redacted fields (tokens, secrets, etc.) is appropriate and follows security best practices.


13-19: LGTM! Safe partial redaction logic.

The function correctly handles short values by fully redacting them, and provides useful partial visibility for longer values while maintaining security.


49-66: LGTM! Proper header sanitization logic.

The function correctly handles case-insensitive header matching and applies appropriate redaction rules. The use of continue for removed fields ensures they're completely excluded from the sanitized output.


115-115: LGTM! Effective defense-in-depth sanitization.

The body sanitization prevents sensitive data from being exposed in Discord error alerts. The comment clearly indicates this is a defensive measure.


137-137: LGTM! Sanitized body correctly used in Discord embed.

The request body field now uses the sanitized version, preventing exposure of passwords, tokens, and API keys in error alerts.


142-142: LGTM! Headers properly sanitized before logging.

The headers are now sanitized before being included in the Discord embed, ensuring sensitive authentication tokens and credentials are redacted.

supabase/seed.sql (5)

2-2: LGTM: Test user additions follow existing patterns.

The new test users (stats, rls, cli_hashed) are properly structured with consistent UUIDs and follow the established seeding pattern for isolated test scenarios.

Also applies to: 54-56


233-235: LGTM: Stripe info entries are consistent.

The new subscription records for the test organizations follow the same structure and settings as existing test data.


243-245: LGTM: Organization and user relationships are correctly established.

The new test organizations and their associated users maintain proper referential integrity across auth.users, public.users, public.orgs, and public.org_users tables.

Also applies to: 254-256, 467-469


494-505: LGTM: Hashed and expired API key test fixtures are well-structured.

The separate INSERT statements for hashed and expired API keys correctly include the necessary columns (key_hash, expires_at) and provide comprehensive test coverage for different authentication scenarios.


510-511: LGTM: Apps and sequence setting are correct.

The new test apps for statistics and RLS testing are properly linked to their respective organizations. The sequence is correctly set to 111 to account for the highest used ID (110), with the intentional gap from 15-100 providing logical separation between standard test keys and specialized test fixtures (hashed, expired).

Also applies to: 559-559

supabase/functions/_backend/private/create_device.ts (4)

40-41: Excellent migration to authenticated client.

The switch from admin client to supabaseWithAuth correctly enforces RLS policies and follows security best practices for user-facing APIs.


45-51: RPC authorization check correctly updated.

The check_min_rights call now includes org_id, maintaining proper authorization while using the authenticated client for RLS enforcement.


61-72: Strong security improvement with ownership validation.

The explicit app existence check and org ownership validation provide defense-in-depth against cross-organization access attempts. The authenticated client ensures RLS policies are also enforced.


12-12: Breaking change appears intentional and properly coordinated with callers.

Adding org_id as a required field in the request body is a breaking change, but evidence from tests and the frontend caller (src/pages/app/[package].channel.[channel].devices.vue at lines 128–132) shows that both have been updated to pass org_id. This suggests the breaking change is intentional and coordinated across the codebase.

supabase/functions/_backend/private/validate_password_compliance.ts (5)

66-67: Excellent security practice for password logging.

Stripping the password from logged request data prevents credential exposure in logs. This is an important security improvement.


95-104: Admin Auth API usage in public endpoint requires scrutiny.

While the Auth Admin API's signInWithPassword may be necessary for credential verification in this flow, using admin authentication on a public endpoint is concerning. The added session check on line 101 is a good improvement.

Context from the larger security issue:
This endpoint lacks authentication middleware, allowing unrestricted public access. If authentication middleware were added, the standard auth flow could be used instead of the admin client.

Recommendation: Consider whether this endpoint truly needs to be public or if it should require pre-authentication.


108-109: Correct pattern for post-authentication operations.

After credential verification, the code correctly switches to an authenticated client using the session token. This ensures RLS policies are enforced for all subsequent database operations.


112-121: Security-critical membership verification implemented correctly.

The org membership check uses the authenticated client (RLS enforced) and properly validates that the user belongs to the organization before proceeding with password validation.


140-141: Remaining operations correctly use authenticated client.

The policy hash RPC call and compliance record upsert both use the authenticated client, ensuring RLS policies are enforced throughout the remaining operations.

Also applies to: 149-149

supabase/functions/_backend/utils/hono_middleware.ts (3)

11-20: LGTM! Good security practice for sensitive data in logs.

The redaction helper properly masks keys while preserving enough information (first 8 characters) for debugging correlation. This aligns with the PR's security objectives to prevent clear-text exposure of API keys and JWTs in logs.


167-292: Excellent security improvement with comprehensive redaction.

All sensitive values (API keys at lines 167, 170, 232, 292 and JWTs at lines 210, 232) are now properly redacted using the redactKey helper. Non-sensitive identifiers (user IDs, subkey IDs) are correctly left unredacted for debugging purposes.

The consistent application of redaction across all log statements effectively prevents clear-text exposure of credentials.


48-61: Fix type accuracy for FindApikeyByValueResult to match the actual apikeys table schema.

The type has several inaccuracies:

  • created_at and updated_at should not be nullable (SQL columns are NOT NULL with defaults)
  • limited_to_orgs should be uuid[] not string[] (stores UUID arrays, not string arrays)
  • limited_to_apps should not be nullable (SQL column has DEFAULT '{}'::character varying[])

Update the type definition to reflect the actual schema returned by the SQL function:

type FindApikeyByValueResult = {
  id: number
  created_at: string
  user_id: string
  key: string | null
  key_hash: string | null
  mode: Database['public']['Enums']['key_mode']
  updated_at: string
  name: string
  limited_to_orgs: string[]
  limited_to_apps: string[]
  expires_at: string | null
} & Record<string, unknown>
⛔ Skipped due to learnings
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
messages/en.json (1)

22-27: SSO/2FA strings: consistent tone/capitalization is good

The new SSO and 2FA-related messages read clear and user-facing (e.g., ACS URL help text).

Also applies to: 53-55

src/pages/sso-login.vue (1)

141-148: No action needed. <LangSelector /> is globally auto-registered via unplugin-vue-components, which generates and maintains src/components.d.ts. The component is correctly available without explicit imports in the file.

Likely an incorrect or invalid review comment.

messages/en.json Outdated
"discover-your-bundle": "See your bundle appear in the list",
"discover-your-dashbo": "Discover your dashboard !",
"documentation": "documentation",
"domain-sso-help": "Add email domains that should be allowed to authenticate via SSO. Users with these email domains will be able to sign in using your identity provider",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, check if the suggested keys exist in messages/en.json
echo "=== Checking for specified keys in messages/en.json ==="
rg -n "\"(sso-login-title|sso-login-subtitle|sso-info|work-email|back-to-login|sso-not-configured|sso-login-failed)\"\\s*:" messages/en.json -S

echo ""
echo "=== Checking for any sso-related keys in messages/en.json ==="
rg -n "\"sso-[^\"]+\"\\s*:" messages/en.json | head -20

Repository: Cap-go/capgo

Length of output: 718


🏁 Script executed:

#!/bin/bash
# Find and examine the sso-login.vue file
echo "=== Searching for sso-login.vue ==="
find . -name "sso-login.vue" -type f

echo ""
echo "=== Checking for key references in sso-login.vue ==="
# Once we find it, let's see what it contains
find . -name "sso-login.vue" -type f -exec cat {} \;

Repository: Cap-go/capgo

Length of output: 6765


🏁 Script executed:

#!/bin/bash
# Check all keys referenced in sso-login.vue
echo "=== Checking for all keys used in sso-login.vue ==="
rg -n "\"(sso-login-title|sso-login-subtitle|invalid-email|sso-not-configured|sso-login-failed|work-email|email|sso-info|back-to-login|continue|support)\"\\s*:" messages/en.json

echo ""
echo "=== Total count of SSO-related keys ==="
rg "\"sso-" messages/en.json | wc -l

Repository: Cap-go/capgo

Length of output: 286


🏁 Script executed:

#!/bin/bash
# Let's also check the lines around 715, 726-729, 1309-1336, 1355-1357 mentioned in the review
echo "=== Content around line 715 ==="
sed -n '710,720p' messages/en.json

echo ""
echo "=== Content around lines 726-729 ==="
sed -n '724,731p' messages/en.json

echo ""
echo "=== Content around lines 1309-1336 ==="
sed -n '1309,1336p' messages/en.json

echo ""
echo "=== Content around lines 1355-1357 ==="
sed -n '1355,1357p' messages/en.json

Repository: Cap-go/capgo

Length of output: 2941


Add missing i18n keys for SSO login page to messages/en.json

The sso-login.vue component references 7 keys that do not exist in messages/en.json:

  • sso-login-title
  • sso-login-subtitle
  • work-email
  • sso-not-configured
  • sso-login-failed
  • sso-info
  • back-to-login

These keys must be added to messages/en.json (and any other required locales) for proper localization. Without them, the component will fall back to the hardcoded text in the t() function calls.

🤖 Prompt for AI Agents
In @messages/en.json at line 715, The messages/en.json is missing seven i18n
keys used by sso-login.vue; add the keys "sso-login-title",
"sso-login-subtitle", "work-email", "sso-not-configured", "sso-login-failed",
"sso-info", and "back-to-login" to messages/en.json (and mirror them into other
locale files) with the appropriate English strings used in the SSO login UI so
the component's t() calls resolve to localized text rather than falling back to
hardcoded strings.

- Export org_saml_connections table schema
- Export saml_domain_mappings table schema
- Export sso_audit_logs table schema
- Fixes TypeScript errors in sso_management.ts and sso_test.ts
Resolved 25 merge conflicts:
- 15 translation files: accepted upstream i18n updates from main
- package.json: accepted main version (managed by CI)
- seed.sql: preserved hashed-only API key test data
- preview.ts: accepted main implementation (backgroundTask, better caching)
- create_device.ts: kept ownership validation logic
- request.ts, deliveries.ts: merged admin client imports
- discord.ts: kept enhanced regex for escaped quotes security fix
- hono_middleware.ts: preserved redactKey() security function
- stripe.ts: kept table name approach + error handling
- version.ts: accepted main version number

Backend linting passes cleanly. All SSO security fixes preserved.
Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 (2)
src/types/supabase.types.ts (2)

1-34: Consider consolidating duplicate type files.

This file is identical to supabase/functions/_backend/utils/supabase.types.ts. Maintaining two copies of ~3400 lines increases the risk of drift and maintenance burden.

Consider either:

  1. Using a symlink or build step to generate one from the other
  2. Publishing types to a shared location imported by both frontend and backend
  3. At minimum, documenting which file is the source of truth

This is a recommended refactor for long-term maintainability but not blocking for this PR.


3193-3195: Same stale type reference as backend file.

DatabaseWithoutInternals references "__InternalSupabase" which no longer exists in the Database type. See comment on the corresponding backend file.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fee3872 and 0356d97.

📒 Files selected for processing (3)
  • src/types/supabase.types.ts
  • supabase/functions/_backend/utils/postgres_schema.ts
  • supabase/functions/_backend/utils/supabase.types.ts
🧰 Additional context used
📓 Path-based instructions (10)
**/*.{ts,tsx,js,jsx,vue}

📄 CodeRabbit inference engine (CLAUDE.md)

Use single quotes and no semicolons per @antfu/eslint-config

Files:

  • supabase/functions/_backend/utils/postgres_schema.ts
  • src/types/supabase.types.ts
  • supabase/functions/_backend/utils/supabase.types.ts
supabase/functions/_backend/**

📄 CodeRabbit inference engine (CLAUDE.md)

Backend logic should be organized in supabase/functions/_backend/ with subdirectories for plugins, private endpoints, public endpoints, triggers, and utilities

Files:

  • supabase/functions/_backend/utils/postgres_schema.ts
  • supabase/functions/_backend/utils/supabase.types.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use TypeScript strict mode with path aliases mapping ~/ to src/

Files:

  • supabase/functions/_backend/utils/postgres_schema.ts
  • src/types/supabase.types.ts
  • supabase/functions/_backend/utils/supabase.types.ts
supabase/functions/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Supabase Edge Functions use Deno runtime

Files:

  • supabase/functions/_backend/utils/postgres_schema.ts
  • supabase/functions/_backend/utils/supabase.types.ts
supabase/functions/_backend/**/*.{ts,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

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
Use createHono from utils/hono.ts for all Hono framework application initialization and routing
All database operations must use getPgClient() or getDrizzleClient() from utils/pg.ts for PostgreSQL access during active migration to Cloudflare D1
All Hono endpoint handlers must accept Context<MiddlewareKeyVariables> and use c.get('requestId'), c.get('apikey'), and c.get('auth') for request context
Use structured logging with cloudlog({ requestId: c.get('requestId'), message: '...' }) for all backend logging
Use middlewareAPISecret for internal API endpoints and middlewareKey for external API keys; validate against owner_org in the apikeys table
Check c.get('auth')?.authType to determine authentication type ('apikey' vs 'jwt') in backend endpoints
Use Drizzle ORM query patterns with schema from postgress_schema.ts for all database operations; use aliasV2() for self-joins or multiple table references

Files:

  • supabase/functions/_backend/utils/postgres_schema.ts
  • supabase/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:backend for backend files

Files:

  • supabase/functions/_backend/utils/postgres_schema.ts
  • supabase/functions/_backend/utils/supabase.types.ts
{capacitor.config.{ts,js},src/**/*.{ts,tsx,vue}}

📄 CodeRabbit inference engine (CLAUDE.md)

Mobile apps should use Capacitor with app ID ee.forgr.capacitor_go for native mobile functionality

Files:

  • src/types/supabase.types.ts
src/**/*.{ts,tsx,vue,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use ~/ alias for imports from src/ 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:fix to auto-fix issues in frontend files

Files:

  • src/types/supabase.types.ts
**/{src,app}/**/*.{vue,ts,tsx,js}

📄 CodeRabbit inference engine (AGENTS.md)

Konsta components are reserved for the safe area helpers. Avoid importing konsta anywhere else in the app.

Files:

  • src/types/supabase.types.ts
🧠 Learnings (11)
📚 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/utils/postgres_schema.ts
  • src/types/supabase.types.ts
  • 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: 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/utils/postgres_schema.ts
  • src/types/supabase.types.ts
  • 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: 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/utils/postgres_schema.ts
  • src/types/supabase.types.ts
  • supabase/functions/_backend/utils/supabase.types.ts
📚 Learning: 2026-01-08T00:40:00.524Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-08T00:40:00.524Z
Learning: Applies to supabase/migrations/**/*.sql : A migration that introduces a new table may include seed inserts for that table, but treat that seeding as part of the current feature and do not modify previously committed migrations.

Applied to files:

  • supabase/functions/_backend/utils/postgres_schema.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/utils/postgres_schema.ts
📚 Learning: 2026-01-08T00:40:00.524Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-08T00:40:00.524Z
Learning: Applies to supabase/seed.sql : Updating `supabase/seed.sql` to back new or evolved tests is expected; keep fixtures focused on current behavior while leaving committed migrations unchanged.

Applied to files:

  • supabase/functions/_backend/utils/postgres_schema.ts
  • src/types/supabase.types.ts
📚 Learning: 2026-01-08T00:40:00.524Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-08T00:40:00.524Z
Learning: Applies to **/{tests,__tests__,test,spec}/**/*.{ts,tsx,js} : Always cover database changes with Postgres-level tests and complement them with end-to-end tests for affected user flows.

Applied to files:

  • supabase/functions/_backend/utils/postgres_schema.ts
  • src/types/supabase.types.ts
  • supabase/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/utils/postgres_schema.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/utils/supabase.types.ts
📚 Learning: 2025-12-05T17:34:25.556Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-05T17:34:25.556Z
Learning: Applies to supabase/functions/**/*.ts : Supabase Edge Functions use Deno runtime

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
🧬 Code graph analysis (2)
src/types/supabase.types.ts (1)
supabase/functions/_backend/utils/supabase.types.ts (1)
  • Json (1-7)
supabase/functions/_backend/utils/supabase.types.ts (1)
src/types/supabase.types.ts (1)
  • Json (1-7)
🔇 Additional comments (8)
supabase/functions/_backend/utils/postgres_schema.ts (3)

141-159: LGTM - SSO connection table schema is well-defined.

The org_saml_connections table schema properly captures the SAML SSO configuration with appropriate field types. The sso_provider_id uniqueness constraint ensures one-to-one mapping with Supabase's internal SSO provider.

One observation: the org_id and created_by fields don't have .references() constraints defined here, unlike other tables in this file (e.g., app_versions references apps.name). This may be intentional if the foreign keys are handled in the SQL migration, but verify consistency with the migration file.


161-171: LGTM - Domain mappings table follows expected structure.

The saml_domain_mappings table correctly captures domain-to-SSO-connection mappings with priority ordering support and verification workflow fields.


173-190: LGTM - SSO audit logs table captures comprehensive event data.

The sso_audit_logs table includes all essential fields for security auditing: user context, event details, network metadata (IP, user agent, country), and SAML-specific session tracking. The metadata field as text (presumably for JSON) provides flexibility for additional event-specific data.

supabase/functions/_backend/utils/supabase.types.ts (5)

10-34: LGTM - GraphQL public namespace follows Supabase conventions.

The graphql_public namespace with the graphql function signature is the standard Supabase structure for GraphQL API support. The empty Tables/Views/Enums/CompositeTypes with [_ in never]: never pattern correctly indicates no custom entities in this namespace.


631-673: LGTM - Electron platform support added to channels.

The electron: boolean field is correctly added to the channels table Row/Insert/Update types, consistent with the existing ios and android platform fields.


3072-3136: LGTM - Platform and stats enums extended for Electron support.

The platform_os enum now includes "electron", and stats_action includes the corresponding "disablePlatformElectron" action, maintaining consistency with the existing iOS and Android patterns ("disablePlatformIos", "disablePlatformAndroid").


3310-3420: LGTM - Constants object correctly synchronized with type definitions.

The Constants object includes the new graphql_public namespace and all electron-related enum values, maintaining consistency with the type definitions above.


3193-3195: and

Security fixes:
- Add environment guard to mock-sso-callback (block in production)
- Fix open redirect vulnerability in mock-sso-callback RelayState handling
- Add regex metacharacter escaping in discord.ts to prevent ReDoS
- Prevent open redirect in sso-login.vue continueWithSSO function
- Add captcha validation and admin permission check in invite_new_user_to_org.ts
- Remove non-null assertion on apikey.key in build/start.ts

Code quality improvements:
- Fix SQL column alignment in seed.sql CLI hashed key INSERT
- Add missing SSO i18n keys to messages/en.json
- Fix typos: 'budnle' -> 'bundle', remove duplicate key with typos
- Fix quote style in sso-login.vue (double -> single quotes)
- Ensure isLoading is always cleared with try/finally pattern
- Remove .opencode/worktree-session-state.json from tracking
- Update mock-sso-callback to use proper Context type and imports

All changes validated with linting and type checking.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (5)
supabase/functions/_backend/private/invite_new_user_to_org.ts (1)

113-119: Error status codes are not propagated to the response.

The validateInvite function returns differentiated status codes (401, 403, 500), but the caller ignores them and always returns a generic simpleError. This means a 403 "Insufficient permissions" error becomes a generic 500 "Failed to invite user" to the client.

Consider propagating the actual status:

♻️ Suggested improvement
   const res = await validateInvite(c, rawBody)
   if (!res.inviteCreatorUser) {
-    return simpleError('failed_to_invite_user', 'Failed to invite user', { }, res.error ?? 'Failed to invite user')
+    return quickError(res.status ?? 500, res.message ?? 'failed_to_invite_user', res.error ?? 'Failed to invite user')
   }
supabase/functions/mock-sso-callback/index.ts (3)

30-33: Consider simplifying the environment check logic.

The double fallback pattern for environment variables (getEnv(c, 'NODE_ENV') || Deno.env.get('NODE_ENV')) is redundant. Consider using just one method consistently, or document why both are needed.

♻️ Simplified environment check
-  const nodeEnv = getEnv(c, 'NODE_ENV') || Deno.env.get('NODE_ENV')
-  const mockSSOEnabled = getEnv(c, 'MOCK_SSO_ENABLED') || Deno.env.get('MOCK_SSO_ENABLED')
+  const nodeEnv = getEnv(c, 'NODE_ENV')
+  const mockSSOEnabled = getEnv(c, 'MOCK_SSO_ENABLED')

56-59: Injection protection could be more comprehensive.

The email injection check only validates against three characters (<, >, ;). While this is a mock endpoint for development, consider adding validation for other potentially dangerous characters like newlines (\n, \r) or null bytes (\0) that could be used in header injection attacks.

🔒 Enhanced injection protection
-    // Prevent email injection attacks
-    if (sanitizedEmail.includes('<') || sanitizedEmail.includes('>') || sanitizedEmail.includes(';')) {
+    // Prevent email injection attacks (newlines, null bytes, angle brackets)
+    if (sanitizedEmail.includes('<') || sanitizedEmail.includes('>') || sanitizedEmail.includes(';') 
+        || sanitizedEmail.includes('\n') || sanitizedEmail.includes('\r') || sanitizedEmail.includes('\0')) {
       return c.json({ error: 'Invalid characters in email' }, 400)
     }

62-85: Add structured logging for successful SSO operations.

The endpoint only logs blocked requests (line 34-38) but not successful mock SSO callbacks. Consider adding a success log before the redirect to aid in debugging and monitoring.

Based on learnings, all backend operations should use structured logging with cloudlog({ requestId: c.get('requestId'), ... }).

📝 Add success logging
     // Construct safe redirect URL pointing to the validated RelayState
     const redirectUrl = `${safeRelayState}${safeRelayState.includes('?') ? '&' : '?'}success=true&email=${encodeURIComponent(sanitizedEmail)}`
 
+    cloudlog({
+      requestId: c.get('requestId'),
+      message: 'Mock SSO callback successful',
+      email: sanitizedEmail,
+      relayState: safeRelayState,
+    })
+
     return c.redirect(redirectUrl, 302)
src/pages/sso-login.vue (1)

21-25: Redundant email validation.

The manual email check at lines 22-25 is redundant since the FormKit input already has validation="required:trim|email" (line 92). The FormKit validation will prevent submission of invalid emails, making this additional check unnecessary.

♻️ Simplify by removing redundant validation
 async function continueWithSSO(form: { email: string }) {
-  if (!form.email || !form.email.includes('@')) {
-    toast.error(t('invalid-email', 'Please enter a valid email address'))
-    return
-  }
-
   isLoading.value = true
 
   try {

FormKit's built-in email validation is sufficient.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0356d97 and fc92708.

📒 Files selected for processing (8)
  • .opencode/worktree-session-state.json
  • messages/en.json
  • src/pages/sso-login.vue
  • supabase/functions/_backend/private/invite_new_user_to_org.ts
  • supabase/functions/_backend/public/build/start.ts
  • supabase/functions/_backend/utils/discord.ts
  • supabase/functions/mock-sso-callback/index.ts
  • supabase/seed.sql
💤 Files with no reviewable changes (1)
  • .opencode/worktree-session-state.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • supabase/seed.sql
🧰 Additional context used
📓 Path-based instructions (15)
**/*.{ts,tsx,js,jsx,vue}

📄 CodeRabbit inference engine (CLAUDE.md)

Use single quotes and no semicolons per @antfu/eslint-config

Files:

  • supabase/functions/_backend/public/build/start.ts
  • supabase/functions/mock-sso-callback/index.ts
  • src/pages/sso-login.vue
  • supabase/functions/_backend/private/invite_new_user_to_org.ts
  • supabase/functions/_backend/utils/discord.ts
supabase/functions/_backend/**

📄 CodeRabbit inference engine (CLAUDE.md)

Backend logic should be organized in supabase/functions/_backend/ with subdirectories for plugins, private endpoints, public endpoints, triggers, and utilities

Files:

  • supabase/functions/_backend/public/build/start.ts
  • supabase/functions/_backend/private/invite_new_user_to_org.ts
  • supabase/functions/_backend/utils/discord.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use TypeScript strict mode with path aliases mapping ~/ to src/

Files:

  • supabase/functions/_backend/public/build/start.ts
  • supabase/functions/mock-sso-callback/index.ts
  • supabase/functions/_backend/private/invite_new_user_to_org.ts
  • supabase/functions/_backend/utils/discord.ts
supabase/functions/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Supabase Edge Functions use Deno runtime

Files:

  • supabase/functions/_backend/public/build/start.ts
  • supabase/functions/mock-sso-callback/index.ts
  • supabase/functions/_backend/private/invite_new_user_to_org.ts
  • supabase/functions/_backend/utils/discord.ts
supabase/functions/_backend/**/*.{ts,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

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
Use createHono from utils/hono.ts for all Hono framework application initialization and routing
All database operations must use getPgClient() or getDrizzleClient() from utils/pg.ts for PostgreSQL access during active migration to Cloudflare D1
All Hono endpoint handlers must accept Context<MiddlewareKeyVariables> and use c.get('requestId'), c.get('apikey'), and c.get('auth') for request context
Use structured logging with cloudlog({ requestId: c.get('requestId'), message: '...' }) for all backend logging
Use middlewareAPISecret for internal API endpoints and middlewareKey for external API keys; validate against owner_org in the apikeys table
Check c.get('auth')?.authType to determine authentication type ('apikey' vs 'jwt') in backend endpoints
Use Drizzle ORM query patterns with schema from postgress_schema.ts for all database operations; use aliasV2() for self-joins or multiple table references

Files:

  • supabase/functions/_backend/public/build/start.ts
  • supabase/functions/_backend/private/invite_new_user_to_org.ts
  • supabase/functions/_backend/utils/discord.ts
supabase/functions/**/*.{ts,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Backend ESLint must pass before commit; run bun lint:backend for backend files

Files:

  • supabase/functions/_backend/public/build/start.ts
  • supabase/functions/mock-sso-callback/index.ts
  • supabase/functions/_backend/private/invite_new_user_to_org.ts
  • supabase/functions/_backend/utils/discord.ts
src/**/*.vue

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.vue: Use Vue 3 with Composition API and <script setup> syntax for frontend components
Style components using TailwindCSS with DaisyUI components

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
Use useRoute() from vue-router to access route parameters and useRouter() for programmatic navigation in Vue components

Files:

  • src/pages/sso-login.vue
src/pages/**/*.vue

📄 CodeRabbit inference engine (CLAUDE.md)

Use file-based routing with unplugin-vue-router for frontend pages

Frontend file-based routing uses src/pages/ directory structure; routes auto-generate with unplugin-vue-router and types are available in src/typed-router.d.ts

Files:

  • src/pages/sso-login.vue
{capacitor.config.{ts,js},src/**/*.{ts,tsx,vue}}

📄 CodeRabbit inference engine (CLAUDE.md)

Mobile apps should use Capacitor with app ID ee.forgr.capacitor_go for native mobile functionality

Files:

  • src/pages/sso-login.vue
src/**/*.{ts,tsx,vue,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use ~/ alias for imports from src/ directory in frontend TypeScript and Vue components

Files:

  • src/pages/sso-login.vue
src/**/*.{vue,ts,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Frontend ESLint must pass before commit; run bun lint:fix to auto-fix issues in frontend files

Files:

  • src/pages/sso-login.vue
**/{src,app}/{components,views,pages}/**/*.{vue,css,scss}

📄 CodeRabbit inference engine (AGENTS.md)

The web client is built with Vue.js and Tailwind CSS; lean on utility classes and composition-friendly patterns rather than bespoke CSS.

Files:

  • src/pages/sso-login.vue
**/{src,app}/{components,views,pages}/**/*.vue

📄 CodeRabbit inference engine (AGENTS.md)

Use DaisyUI (d- prefixed classes) for buttons, inputs, and other interactive primitives to keep behavior and spacing consistent.

Files:

  • src/pages/sso-login.vue
**/{src,app}/**/*.{vue,ts,tsx,js}

📄 CodeRabbit inference engine (AGENTS.md)

Konsta components are reserved for the safe area helpers. Avoid importing konsta anywhere else in the app.

Files:

  • src/pages/sso-login.vue
**/{src,app}/**/*.{css,scss,vue}

📄 CodeRabbit inference engine (AGENTS.md)

Capgo's design uses deep slate bases with the 'Extract' azure highlight (--color-azure-500: #119eff) and soft radii; mirror the palette from src/styles/style.css (e.g., --color-primary-500: #515271) when introducing new UI.

Files:

  • src/pages/sso-login.vue
🧠 Learnings (18)
📚 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/start.ts
  • supabase/functions/mock-sso-callback/index.ts
  • supabase/functions/_backend/private/invite_new_user_to_org.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/build/start.ts
  • supabase/functions/mock-sso-callback/index.ts
  • supabase/functions/_backend/private/invite_new_user_to_org.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/public/build/start.ts
  • supabase/functions/mock-sso-callback/index.ts
📚 Learning: 2026-01-08T00:40:00.524Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-08T00:40:00.524Z
Learning: Applies to **/{api,server,src/server,backend}/**/*.{ts,tsx,js} : Never use the Supabase admin SDK (with service key) for user-facing APIs. Always use the client SDK with user authentication so RLS policies are enforced. The admin SDK should only be used for internal operations (triggers, CRON jobs, etc.), and when unavoidable, sanitize all user inputs carefully to prevent PostgREST query injection.

Applied to files:

  • supabase/functions/_backend/public/build/start.ts
  • supabase/functions/mock-sso-callback/index.ts
  • supabase/functions/_backend/private/invite_new_user_to_org.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/start.ts
  • supabase/functions/mock-sso-callback/index.ts
  • supabase/functions/_backend/private/invite_new_user_to_org.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/public/build/start.ts
📚 Learning: 2025-12-05T17:34:25.556Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-05T17:34:25.556Z
Learning: Applies to supabase/functions/**/*.ts : Supabase Edge Functions use Deno runtime

Applied to files:

  • supabase/functions/_backend/public/build/start.ts
  • supabase/functions/mock-sso-callback/index.ts
  • supabase/functions/_backend/private/invite_new_user_to_org.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/build/start.ts
  • supabase/functions/mock-sso-callback/index.ts
  • supabase/functions/_backend/private/invite_new_user_to_org.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/public/build/start.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/mock-sso-callback/index.ts
📚 Learning: 2026-01-08T00:40:00.524Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-08T00:40:00.524Z
Learning: Applies to **/{src,app}/{components,views,pages}/**/*.{vue,css,scss} : The web client is built with Vue.js and Tailwind CSS; lean on utility classes and composition-friendly patterns rather than bespoke CSS.

Applied to files:

  • src/pages/sso-login.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 src/**/*.vue : Use `useRoute()` from `vue-router` to access route parameters and `useRouter()` for programmatic navigation in Vue components

Applied to files:

  • src/pages/sso-login.vue
📚 Learning: 2025-12-05T17:34:25.556Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-05T17:34:25.556Z
Learning: Applies to src/pages/**/*.vue : Use file-based routing with unplugin-vue-router for frontend pages

Applied to files:

  • src/pages/sso-login.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 src/pages/**/*.vue : Frontend file-based routing uses `src/pages/` directory structure; routes auto-generate with `unplugin-vue-router` and types are available in `src/typed-router.d.ts`

Applied to files:

  • src/pages/sso-login.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/pages/sso-login.vue
📚 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/invite_new_user_to_org.ts
📚 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/private/invite_new_user_to_org.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/invite_new_user_to_org.ts
🧬 Code graph analysis (1)
supabase/functions/_backend/private/invite_new_user_to_org.ts (1)
supabase/functions/_backend/utils/supabase.ts (1)
  • hasOrgRight (287-304)
🪛 ast-grep (0.40.4)
supabase/functions/_backend/utils/discord.ts

[warning] 34-34: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp("${escapedField}"\\s*:\\s*"(?:[^"\\\\]|\\\\.)*"\\s*,?\\s*, 'gi')
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)


[warning] 46-46: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(("${escapedField}"\\s*:\\s*)"((?:[^"\\\\]|\\\\.)*)", 'gi')
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

🪛 GitHub Actions: Run tests
supabase/functions/_backend/public/build/start.ts

[error] 70-70: TS2345: Argument of type 'string | null' is not assignable to parameter of type 'string'.


[error] 95-95: TS2345: Argument of type 'string | null' is not assignable to parameter of type 'string'.


[error] 138-138: TS2345: Argument of type 'string | null' is not assignable to parameter of type 'string'.

🔇 Additional comments (10)
supabase/functions/_backend/private/invite_new_user_to_org.ts (2)

51-55: LGTM!

Good hardening of the CAPTCHA flow. The early return on error ensures failed CAPTCHA verification is no longer silently ignored.


85-94: Good addition of admin permission check.

The explicit admin permission verification before allowing invitations is a solid security improvement. Using hasOrgRight with the authenticated user's ID ensures only authorized admins can invite new users.

Minor: Consider returning status 401 instead of 500 on line 87 since "Failed to get user" from an authenticated context is more of an authentication issue than a server error.

supabase/functions/_backend/utils/discord.ts (2)

13-16: LGTM! Well-implemented defense-in-depth measure.

The escapeRegex function correctly escapes all regex metacharacters, preventing any potential regex injection. This is a solid security practice even though the current field names come from hardcoded constants.


32-47: Static analysis false positive—patterns are safe.

The ast-grep warnings about ReDoS on lines 35 and 47 are false positives:

  1. Field names are hardcoded constants (lines 9, 11), not user input—no injection risk exists.
  2. The pattern (?:[^"\\\\]|\\\\.)* is safe: it alternates between a character class and a fixed-length sequence with no nested quantifiers or ambiguous backtracking. This has linear time complexity.

The updated regex correctly handles escaped quotes within JSON values (e.g., "field":"val\"ue"), and the capture group logic on line 48 properly applies partial redaction.

messages/en.json (2)

445-445: LGTM: New translation key added.

The new translation key for channel-to-bundle linking is properly formatted and follows the existing naming convention.


1096-1096: LGTM: Typo corrected.

Good catch fixing the typo "budnle" → "bundle".

supabase/functions/mock-sso-callback/index.ts (1)

1-20: LGTM: Proper Hono setup.

The function correctly uses createHono from utils/hono.ts and imports the necessary utilities. Setup follows backend coding guidelines.

Based on learnings, this aligns with the pattern for Supabase Edge Functions using Hono framework.

src/pages/sso-login.vue (3)

1-20: LGTM: Proper Vue 3 Composition API setup.

The component correctly uses <script setup>, imports composables, and follows Vue 3 patterns. The use of useRoute() and useRouter() aligns with frontend coding guidelines.


38-46: LGTM: Secure redirect validation.

The redirect path validation properly prevents open redirect vulnerabilities by:

  • Only allowing relative paths starting with /
  • Blocking protocol/host sequences (//)
  • Defaulting to /dashboard for invalid inputs

This is a good security practice.


63-163: LGTM: Well-structured Vue template.

The template follows frontend guidelines:

  • Uses Tailwind utility classes appropriately
  • Implements proper loading states with visual feedback (spinner)
  • Includes helpful informational content for users
  • Has proper accessibility with semantic HTML
  • Includes data-test attributes for testing
  • Uses FormKit for form handling with proper validation

The component provides a good user experience with clear messaging and feedback.

- Add electron property to channel type in channel_self.ts
- Add null checks for apikeyKey before calling markBuildAsFailed
- Remove nonexistent plan_enterprise field from global_stats payload

All backend TypeScript errors resolved.
Instead of conditionally checking for null throughout the function,
add early validation that throws a clear error if the API key is null.
This prevents hashed-only keys from starting builds and ensures all
downstream code can safely assume apikeyKey is non-null.

- Add validation check after apikeyKey assignment
- Remove conditional null checks from markBuildAsFailed calls
- Provide clear error message for unsupported hashed keys
@sonarqubecloud
Copy link

sonarqubecloud bot commented Jan 9, 2026

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants