From 1e199cc075fec008e42889931914ab48815ad652 Mon Sep 17 00:00:00 2001 From: Jacob Mischka Date: Tue, 26 May 2026 18:36:27 -0500 Subject: [PATCH 1/2] Validate org slug on new organization creation --- src/server/trpc/organization.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/server/trpc/organization.ts b/src/server/trpc/organization.ts index ca96eb7..4d919f4 100644 --- a/src/server/trpc/organization.ts +++ b/src/server/trpc/organization.ts @@ -18,6 +18,7 @@ import { import { processInvitationGroupIds } from '../user' import { logger } from '~/server/utils/logger' import { SLACK_OAUTH_SCOPES } from '~/utils/isomorphicConsts' +import { isOrgSlugValid } from '~/utils/validate' export const organizationRouter = createRouter() .query('slug', { @@ -109,7 +110,11 @@ export const organizationRouter = createRouter() name: z.string(), }), async resolve({ ctx: { user }, input: { slug, name } }) { - if (!isSlugAvailable(slug)) { + if (!isOrgSlugValid(slug)) { + throw new TRPCError({ code: 'BAD_REQUEST' }) + } + + if (!(await isSlugAvailable(slug))) { throw new TRPCError({ code: 'BAD_REQUEST' }) } From 0085f34d3d98a2e62886e6ebe0da07f991bd46fb Mon Sep 17 00:00:00 2001 From: Jacob Mischka Date: Tue, 26 May 2026 18:55:28 -0500 Subject: [PATCH 2/2] Don't expose all permissions in error message, validate assigner perms Don't allow a user to assign permission that they themselves do not have, and don't expose the full list of permissions in the error response. Only allow assigning the subset of exposed roles and not the full list of permissions. --- src/server/trpc/organization.ts | 13 +++++++++++-- src/utils/permissions.ts | 8 ++++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/server/trpc/organization.ts b/src/server/trpc/organization.ts index 4d919f4..d7129b5 100644 --- a/src/server/trpc/organization.ts +++ b/src/server/trpc/organization.ts @@ -6,7 +6,7 @@ import { authenticatedMiddleware, organizationMiddleware, } from './util' -import { hasPermission } from '~/utils/permissions' +import { EXPOSED_ROLES, hasPermission } from '~/utils/permissions' import { inviteNewUser } from '~/emails' import { createOrganization, isSlugAvailable } from '../utils/organizations' import env from '~/env' @@ -419,7 +419,9 @@ export const organizationRouter = createRouter() .string() .email() .transform(email => email.toLowerCase()), - permissions: z.array(z.nativeEnum(UserAccessPermission)), + permissions: z.array( + z.enum(EXPOSED_ROLES, { invalid_type_error: 'Invalid role' }) + ), groupIds: z.array(z.string()), }), async resolve({ @@ -430,6 +432,13 @@ export const organizationRouter = createRouter() throw new TRPCError({ code: 'FORBIDDEN' }) } + // Only allow granting permissions that the current user has + for (const perm of permissions) { + if (!hasPermission(userOrganizationAccess, perm)) { + throw new TRPCError({ code: 'FORBIDDEN' }) + } + } + const existingUser = await prisma.user.findFirst({ where: { email }, }) diff --git a/src/utils/permissions.ts b/src/utils/permissions.ts index cb2d541..64947a7 100644 --- a/src/utils/permissions.ts +++ b/src/utils/permissions.ts @@ -12,6 +12,7 @@ */ import { UserOrganizationAccess, UserAccessPermission } from '@prisma/client' +import { z } from 'zod' type RolePermissionMap = { [key in UserAccessPermission]: UserAccessPermission[] | UserAccessPermission @@ -72,11 +73,14 @@ const ROLE_PERMISSIONS: RolePermissionMap = { /** * These permissions/roles are available in the user interface to assign to users. */ -export const EXPOSED_ROLES: UserAccessPermission[] = [ +export const EXPOSED_ROLES = [ 'ADMIN', 'DEVELOPER', 'ACTION_RUNNER', -] + 'READONLY_VIEWER', +] as const satisfies readonly UserAccessPermission[] + +export const exposedRole = z.enum(EXPOSED_ROLES) export const SDK_PERMISSIONS_MIN_VERSION = '0.34.0'