From 920d03e1854a3a0eaeaf62c285133f3da6083fc7 Mon Sep 17 00:00:00 2001 From: syn Date: Thu, 6 Aug 2026 13:24:10 -0500 Subject: [PATCH 1/2] feat(admin): allow anaconda platform admins --- .../components/PlatformAdminsContent.tsx | 7 ++-- apps/web/src/lib/admin/platform-admin.test.ts | 17 ++++++-- apps/web/src/lib/admin/platform-admin.ts | 16 +++++--- .../src/routers/admin-platform-admins.test.ts | 40 ++++++++++++++++++- apps/web/src/routers/admin-router.ts | 18 ++++++--- 5 files changed, 77 insertions(+), 21 deletions(-) diff --git a/apps/web/src/app/admin/components/PlatformAdminsContent.tsx b/apps/web/src/app/admin/components/PlatformAdminsContent.tsx index fd3d502b21..af68859862 100644 --- a/apps/web/src/app/admin/components/PlatformAdminsContent.tsx +++ b/apps/web/src/app/admin/components/PlatformAdminsContent.tsx @@ -330,7 +330,7 @@ export function PlatformAdminsContent() { : rosterError || permissions.isError ? 'Could not determine your permissions. Reload to try again.' : canManageAdmins - ? 'Search registered kilocode.ai users who are not already admins. New admins receive no subordinate permissions.' + ? 'Search registered kilocode.ai or anaconda.com users who are not already admins. New admins receive no subordinate permissions.' : 'Superadmin access is required to grant platform admin access or manage permissions.'} @@ -350,7 +350,8 @@ export function PlatformAdminsContent() { id="platform-admin-candidate-search-hint" className="text-muted-foreground text-xs" > - Only registered kilocode.ai users who are not already admins can be granted access. + Only registered kilocode.ai or anaconda.com users who are not already admins can be + granted access.

@@ -366,7 +367,7 @@ export function PlatformAdminsContent() { ) : candidateRows.length === 0 && !isSearching ? (
- No eligible kilocode.ai users matched that search. + No eligible kilocode.ai or anaconda.com users matched that search.
) : (
diff --git a/apps/web/src/lib/admin/platform-admin.test.ts b/apps/web/src/lib/admin/platform-admin.test.ts index 0983d23f25..8f94a90752 100644 --- a/apps/web/src/lib/admin/platform-admin.test.ts +++ b/apps/web/src/lib/admin/platform-admin.test.ts @@ -1,9 +1,12 @@ import { isEligibleForPlatformAdmin, shouldAutoProvisionPlatformAdmin } from './platform-admin'; describe('isEligibleForPlatformAdmin', () => { - it('is eligible for an exact lowercase kilocode.ai email and hosted domain', () => { - expect(isEligibleForPlatformAdmin('person@kilocode.ai', 'kilocode.ai')).toBe(true); - }); + test.each(['kilocode.ai', 'anaconda.com'])( + 'is eligible for an exact lowercase %s email and hosted domain', + domain => { + expect(isEligibleForPlatformAdmin(`person@${domain}`, domain)).toBe(true); + } + ); it('is not eligible when the hosted domain is a personal/provider placeholder', () => { expect(isEligibleForPlatformAdmin('person@kilocode.ai', '@@personal@@')).toBe(false); @@ -13,10 +16,14 @@ describe('isEligibleForPlatformAdmin', () => { expect(isEligibleForPlatformAdmin('person@kilocode.ai', null)).toBe(false); }); - it('is not eligible when the hosted domain matches but the email is not a kilocode.ai email', () => { + it('is not eligible when the hosted domain matches but the email is not from an allowed domain', () => { expect(isEligibleForPlatformAdmin('person@example.com', 'kilocode.ai')).toBe(false); }); + it('is not eligible when the email and hosted domain belong to different allowed domains', () => { + expect(isEligibleForPlatformAdmin('person@anaconda.com', 'kilocode.ai')).toBe(false); + }); + it('is not eligible for uppercase email variants', () => { expect(isEligibleForPlatformAdmin('Person@Kilocode.ai', 'kilocode.ai')).toBe(false); }); @@ -27,10 +34,12 @@ describe('isEligibleForPlatformAdmin', () => { it('is not eligible for a subdomain lookalike', () => { expect(isEligibleForPlatformAdmin('person@sub.kilocode.ai', 'kilocode.ai')).toBe(false); + expect(isEligibleForPlatformAdmin('person@sub.anaconda.com', 'anaconda.com')).toBe(false); }); it('is not eligible for a registrable-parent-domain lookalike', () => { expect(isEligibleForPlatformAdmin('person@notkilocode.ai', 'kilocode.ai')).toBe(false); + expect(isEligibleForPlatformAdmin('person@notanaconda.com', 'anaconda.com')).toBe(false); }); it('is not eligible for a fake-login hosted domain even with a kilocode.ai email', () => { diff --git a/apps/web/src/lib/admin/platform-admin.ts b/apps/web/src/lib/admin/platform-admin.ts index 66d76a83bb..4ab710a3f7 100644 --- a/apps/web/src/lib/admin/platform-admin.ts +++ b/apps/web/src/lib/admin/platform-admin.ts @@ -2,12 +2,17 @@ import 'server-only'; import { hosted_domain_specials } from '@/lib/auth/constants'; +export const platformAdminDomains = [ + hosted_domain_specials.kilocode_admin, + 'anaconda.com', +] as const; + /** - * Exact eligibility rule for the Kilo production platform-admin domain. + * Exact eligibility rule for Kilo production platform-admin domains. * * This intentionally preserves current case-sensitive behavior: the hosted - * domain must equal `kilocode.ai` exactly and the email must end with - * `@kilocode.ai` exactly. It does not broaden matching to uppercase + * domain must equal an allowed corporate domain exactly and the email must + * end with that same domain exactly. It does not broaden matching to uppercase * variants, subdomains, or registrable parent domains. * * Used both to gate the production auto-provisioning rule (historically) @@ -17,9 +22,8 @@ import { hosted_domain_specials } from '@/lib/auth/constants'; * against freshly loaded rows. */ export function isEligibleForPlatformAdmin(email: string, hostedDomain: string | null): boolean { - return ( - hostedDomain === hosted_domain_specials.kilocode_admin && - email.endsWith('@' + hosted_domain_specials.kilocode_admin) + return platformAdminDomains.some( + domain => hostedDomain === domain && email.endsWith(`@${domain}`) ); } diff --git a/apps/web/src/routers/admin-platform-admins.test.ts b/apps/web/src/routers/admin-platform-admins.test.ts index 598348f819..bfeb08e05c 100644 --- a/apps/web/src/routers/admin-platform-admins.test.ts +++ b/apps/web/src/routers/admin-platform-admins.test.ts @@ -7,6 +7,7 @@ import { createCallerForUser } from '@/routers/test-utils'; import { hosted_domain_specials } from '@/lib/auth/constants'; const KILO_DOMAIN = hosted_domain_specials.kilocode_admin; +const ANACONDA_DOMAIN = 'anaconda.com'; async function insertQualifyingAdmin(overrides: Parameters[0] = {}) { return insertTestUser({ @@ -37,6 +38,17 @@ async function insertEligibleCandidate(overrides: Parameters[0] = {} +) { + return insertTestUser({ + google_user_email: `candidate-${crypto.randomUUID()}@anaconda.com`, + hosted_domain: ANACONDA_DOMAIN, + is_admin: false, + ...overrides, + }); +} + async function getUserAdminNotes(userId: string) { return db.query.user_admin_notes.findMany({ where: eq(user_admin_notes.kilo_user_id, userId), @@ -111,13 +123,16 @@ describe('admin.users.searchPlatformAdminCandidates', () => { ).rejects.toMatchObject({ code: 'FORBIDDEN' }); }); - test('returns only non-admin users satisfying both exact Kilo eligibility rules', async () => { + test('returns only non-admin users satisfying an exact allowed-domain email and hosted-domain pair', async () => { const searchToken = crypto.randomUUID(); const admin = await insertQualifyingAdmin(); const eligible = await insertEligibleCandidate({ google_user_email: `${searchToken}@kilocode.ai`, }); + const anacondaEligible = await insertAnacondaEligibleCandidate({ + google_user_email: `${searchToken}@anaconda.com`, + }); const alreadyAdmin = await insertTestUser({ google_user_email: `${searchToken}-already-admin@kilocode.ai`, hosted_domain: KILO_DOMAIN, @@ -138,6 +153,11 @@ describe('admin.users.searchPlatformAdminCandidates', () => { hosted_domain: KILO_DOMAIN, is_admin: false, }); + const mismatchedAllowedDomainUser = await insertTestUser({ + google_user_email: `${searchToken}-mismatch@anaconda.com`, + hosted_domain: KILO_DOMAIN, + is_admin: false, + }); const uppercaseEmailUser = await insertTestUser({ google_user_email: `${searchToken.toUpperCase()}@Kilocode.ai`, hosted_domain: KILO_DOMAIN, @@ -160,11 +180,13 @@ describe('admin.users.searchPlatformAdminCandidates', () => { }); const resultIds = results.map(user => user.id); - expect(resultIds).toEqual([eligible.id]); + expect(resultIds).toEqual(expect.arrayContaining([eligible.id, anacondaEligible.id])); + expect(resultIds).toHaveLength(2); expect(resultIds).not.toContain(alreadyAdmin.id); expect(resultIds).not.toContain(fakeLoginUser.id); expect(resultIds).not.toContain(wrongDomainUser.id); expect(resultIds).not.toContain(wrongEmailUser.id); + expect(resultIds).not.toContain(mismatchedAllowedDomainUser.id); expect(resultIds).not.toContain(uppercaseEmailUser.id); expect(resultIds).not.toContain(subdomainUser.id); expect(resultIds).not.toContain(lookalikeDomainUser.id); @@ -228,6 +250,20 @@ describe('admin.users.setPlatformAdminAccess — grant', () => { expect(notes[0]?.admin_kilo_user_id).toBe(admin.id); }); + test('a superadmin can grant an eligible anaconda.com target', async () => { + const admin = await insertQualifyingAdmin(); + const target = await insertAnacondaEligibleCandidate(); + const caller = await createCallerForUser(admin.id); + + const result = await caller.admin.users.setPlatformAdminAccess({ + userId: target.id, + isAdmin: true, + }); + + expect(result).toMatchObject({ changed: true, user: { id: target.id, is_admin: true } }); + expect((await getUser(target.id)).is_admin).toBe(true); + }); + test('rejects granting an ineligible target even if submitted directly', async () => { const admin = await insertQualifyingAdmin(); const ineligibleTarget = await insertTestUser({ diff --git a/apps/web/src/routers/admin-router.ts b/apps/web/src/routers/admin-router.ts index caf4ec3109..3affb3506e 100644 --- a/apps/web/src/routers/admin-router.ts +++ b/apps/web/src/routers/admin-router.ts @@ -7,8 +7,7 @@ import { } from '@/lib/trpc/init'; import { userCanViewSessions, userIsSuperadmin } from '@/lib/admin/admin-permissions'; import { userCanManageCredits } from '@/lib/admin/credit-management'; -import { isEligibleForPlatformAdmin } from '@/lib/admin/platform-admin'; -import { hosted_domain_specials } from '@/lib/auth/constants'; +import { isEligibleForPlatformAdmin, platformAdminDomains } from '@/lib/admin/platform-admin'; import { db, type DrizzleTransaction } from '@/lib/drizzle'; import { insertKiloClawSubscriptionChangeLog, type KiloClawSubscription } from '@kilocode/db'; import { @@ -1586,8 +1585,9 @@ export const adminRouter = createTRPCRouter({ }; }), - // Server-filtered so results only ever contain non-admin, exact-eligibility - // kilocode.ai users. Filtering here is a UX convenience, not a security + // Server-filtered so results only ever contain non-admin users matching an + // exact platform-admin email and hosted-domain pair. Filtering here is a UX + // convenience, not a security // boundary: setPlatformAdminAccess independently re-validates eligibility // against freshly locked rows before granting. searchPlatformAdminCandidates: superadminProcedure @@ -1601,10 +1601,16 @@ export const adminRouter = createTRPCRouter({ .where( and( eq(kilocode_users.is_admin, false), - eq(kilocode_users.hosted_domain, hosted_domain_specials.kilocode_admin), // Case-sensitive suffix match — `like` (not `ilike`) so search // eligibility cannot disagree with isEligibleForPlatformAdmin. - like(kilocode_users.google_user_email, `%@${hosted_domain_specials.kilocode_admin}`), + or( + ...platformAdminDomains.map(domain => + and( + eq(kilocode_users.hosted_domain, domain), + like(kilocode_users.google_user_email, `%@${domain}`) + ) + ) + ), or( ilike(kilocode_users.google_user_email, `%${escaped}%`), ilike(kilocode_users.google_user_name, `%${escaped}%`), From f8e32642c02c9d8ab241442650295cf01063df70 Mon Sep 17 00:00:00 2001 From: syn Date: Thu, 6 Aug 2026 15:25:12 -0500 Subject: [PATCH 2/2] chore(ci): retrigger pull request checks