Files
simstudioai--sim/apps/sim/lib/api/contracts/permission-groups.ts
T
wehub-resource-sync d25d482dc2
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:20:55 +08:00

314 lines
10 KiB
TypeScript

import { z } from 'zod'
import { organizationIdSchema } from '@/lib/api/contracts/primitives'
import { shareAuthTypeSchema } from '@/lib/api/contracts/public-shares'
import { defineRouteContract } from '@/lib/api/contracts/types'
import { permissionGroupConfigSchema } from '@/lib/permission-groups/types'
export const permissionGroupFullConfigSchema = z.object({
allowedIntegrations: z.array(z.string()).nullable(),
allowedModelProviders: z.array(z.string()).nullable(),
deniedModels: z.array(z.string()).default([]),
deniedTools: z.array(z.string()).default([]),
hideTraceSpans: z.boolean(),
hideKnowledgeBaseTab: z.boolean(),
hideTablesTab: z.boolean(),
hideCopilot: z.boolean(),
hideIntegrationsTab: z.boolean(),
hideSecretsTab: z.boolean(),
hideApiKeysTab: z.boolean(),
hideInboxTab: z.boolean(),
hideFilesTab: z.boolean(),
disableMcpTools: z.boolean(),
disableCustomTools: z.boolean(),
disableSkills: z.boolean(),
disableInvitations: z.boolean(),
disablePublicApi: z.boolean(),
disablePublicFileSharing: z.boolean(),
allowedFileShareAuthTypes: z.array(shareAuthTypeSchema).nullable(),
hideDeployApi: z.boolean(),
hideDeployMcp: z.boolean(),
hideDeployChatbot: z.boolean(),
hideDeployTemplate: z.boolean(),
})
export const addPermissionGroupMemberBodySchema = z.object({
userId: z.string().min(1),
})
/** Route params for organization-scoped permission-group collection routes (`id` = organizationId). */
export const permissionGroupParamsSchema = z.object({
id: organizationIdSchema,
})
/** Route params for a single permission group (`id` = organizationId, `groupId` = permission group id). */
export const permissionGroupDetailParamsSchema = z.object({
id: organizationIdSchema,
groupId: z.string().min(1),
})
/** A workspace a permission group targets (id + display name). */
export const permissionGroupWorkspaceRefSchema = z.object({
id: z.string(),
name: z.string(),
})
export type PermissionGroupWorkspaceRef = z.output<typeof permissionGroupWorkspaceRefSchema>
export const permissionGroupSchema = z.object({
id: z.string(),
name: z.string(),
description: z.string().nullable(),
config: permissionGroupFullConfigSchema,
createdBy: z.string(),
createdAt: z.string(),
updatedAt: z.string(),
creatorName: z.string().nullable(),
creatorEmail: z.string().nullable(),
memberCount: z.number(),
isDefault: z.boolean(),
/**
* Workspaces this group targets. Empty for the default group (which governs
* every workspace) and for a non-default group scoped to nothing.
*/
workspaces: z.array(permissionGroupWorkspaceRefSchema),
})
export type PermissionGroup = z.output<typeof permissionGroupSchema>
export const permissionGroupWriteSchema = z.object({
id: z.string(),
organizationId: z.string(),
name: z.string(),
description: z.string().nullable(),
config: permissionGroupFullConfigSchema,
createdBy: z.string(),
createdAt: z.string(),
updatedAt: z.string(),
isDefault: z.boolean(),
/** Ids of targeted workspaces (empty for the default group). */
workspaceIds: z.array(z.string()),
})
export type PermissionGroupWrite = z.output<typeof permissionGroupWriteSchema>
export const permissionGroupMemberSchema = z.object({
id: z.string(),
userId: z.string(),
assignedAt: z.string(),
userName: z.string().nullable(),
userEmail: z.string().nullable(),
userImage: z.string().nullable(),
})
export type PermissionGroupMember = z.output<typeof permissionGroupMemberSchema>
export const userPermissionConfigQuerySchema = z.object({
workspaceId: z.string().min(1),
})
export const userPermissionConfigSchema = z.object({
permissionGroupId: z.string().nullable(),
groupName: z.string().nullable(),
config: permissionGroupFullConfigSchema.nullable(),
entitled: z.boolean(),
/** The workspace's owning organization id (null when the workspace has no org). */
organizationId: z.string().nullable(),
/** Whether the caller is an owner/admin of the workspace's owning organization. */
isOrgAdmin: z.boolean(),
})
export type UserPermissionConfig = z.output<typeof userPermissionConfigSchema>
/** Upper bound on how many workspaces a single group can explicitly target. */
export const MAX_PERMISSION_GROUP_WORKSPACES = 500
const workspaceIdsSchema = z.array(z.string().min(1)).max(MAX_PERMISSION_GROUP_WORKSPACES)
/**
* The one cross-field scope rule shared by create and update: the organization
* default group governs every workspace, so it cannot also name specific
* workspaces (they would be silently dropped server-side). "Org-wide" is
* definitionally `isDefault` — there is no separate flag — so a default group
* with no `workspaceIds` is already the all-workspaces case and needs no
* assertion here.
*
* Everything else is left to the routes: a non-default group targets the
* workspaces in `workspaceIds` (empty is allowed on update — the group then
* governs nothing, since the resolver inner-joins the workspace link table), and
* the create route requires at least one workspace up front.
*/
function refineWorkspaceScope(
body: { workspaceIds?: string[]; isDefault?: boolean },
ctx: z.RefinementCtx
) {
if (body.isDefault === true && body.workspaceIds && body.workspaceIds.length > 0) {
ctx.addIssue({
code: 'custom',
path: ['workspaceIds'],
message: 'The default group governs all workspaces and cannot target specific workspaces',
})
}
}
export const createPermissionGroupBodySchema = z
.object({
name: z.string().trim().min(1).max(100),
description: z.string().max(500).optional(),
config: permissionGroupConfigSchema.optional(),
isDefault: z.boolean().optional(),
workspaceIds: workspaceIdsSchema.optional(),
})
.superRefine(refineWorkspaceScope)
export const updatePermissionGroupBodySchema = z
.object({
name: z.string().trim().min(1).max(100).optional(),
description: z.string().max(500).nullable().optional(),
config: permissionGroupConfigSchema.optional(),
isDefault: z.boolean().optional(),
workspaceIds: workspaceIdsSchema.optional(),
})
.superRefine(refineWorkspaceScope)
export const removePermissionGroupMemberQuerySchema = z.object({
memberId: z.string().min(1),
})
export const bulkAddPermissionGroupMembersBodySchema = z.object({
userIds: z.array(z.string()).optional(),
addAllOrganizationMembers: z.boolean().optional(),
})
const successResponseSchema = z.object({
success: z.literal(true),
})
export const listPermissionGroupsContract = defineRouteContract({
method: 'GET',
path: '/api/organizations/[id]/permission-groups',
params: permissionGroupParamsSchema,
response: {
mode: 'json',
schema: z.object({
permissionGroups: z.array(permissionGroupSchema).optional(),
}),
},
})
export const createPermissionGroupContract = defineRouteContract({
method: 'POST',
path: '/api/organizations/[id]/permission-groups',
params: permissionGroupParamsSchema,
body: createPermissionGroupBodySchema,
response: {
mode: 'json',
schema: z.object({
permissionGroup: permissionGroupWriteSchema,
}),
},
})
export const getUserPermissionConfigContract = defineRouteContract({
method: 'GET',
path: '/api/permission-groups/user',
query: userPermissionConfigQuerySchema,
response: {
mode: 'json',
schema: userPermissionConfigSchema,
},
})
export const updatePermissionGroupContract = defineRouteContract({
method: 'PUT',
path: '/api/organizations/[id]/permission-groups/[groupId]',
params: permissionGroupDetailParamsSchema,
body: updatePermissionGroupBodySchema,
response: {
mode: 'json',
schema: z.object({
permissionGroup: permissionGroupWriteSchema,
}),
},
})
export const deletePermissionGroupContract = defineRouteContract({
method: 'DELETE',
path: '/api/organizations/[id]/permission-groups/[groupId]',
params: permissionGroupDetailParamsSchema,
response: {
mode: 'json',
schema: successResponseSchema,
},
})
export const listPermissionGroupMembersContract = defineRouteContract({
method: 'GET',
path: '/api/organizations/[id]/permission-groups/[groupId]/members',
params: permissionGroupDetailParamsSchema,
response: {
mode: 'json',
schema: z.object({
members: z.array(permissionGroupMemberSchema).optional(),
}),
},
})
export const removePermissionGroupMemberContract = defineRouteContract({
method: 'DELETE',
path: '/api/organizations/[id]/permission-groups/[groupId]/members',
params: permissionGroupDetailParamsSchema,
query: removePermissionGroupMemberQuerySchema,
response: {
mode: 'json',
schema: successResponseSchema,
},
})
export const addPermissionGroupMemberContract = defineRouteContract({
method: 'POST',
path: '/api/organizations/[id]/permission-groups/[groupId]/members',
params: permissionGroupDetailParamsSchema,
body: addPermissionGroupMemberBodySchema,
response: {
mode: 'json',
schema: z.object({
member: z.object({
id: z.string(),
permissionGroupId: z.string(),
organizationId: z.string(),
userId: z.string(),
assignedBy: z.string(),
assignedAt: z.string(),
}),
}),
},
})
export const bulkAddPermissionGroupMembersContract = defineRouteContract({
method: 'POST',
path: '/api/organizations/[id]/permission-groups/[groupId]/members/bulk',
params: permissionGroupDetailParamsSchema,
body: bulkAddPermissionGroupMembersBodySchema,
response: {
mode: 'json',
schema: z.object({
added: z.number(),
// Users not added because they were already in this group. A conflicting
// selection fails the whole request (409) rather than being skipped, so
// the add is all-or-nothing for conflicts.
skipped: z.number(),
}),
},
})
/**
* List the workspaces belonging to an organization, used to populate the
* workspace multi-select when scoping a permission group to specific workspaces.
*/
export const listOrganizationWorkspacesContract = defineRouteContract({
method: 'GET',
path: '/api/organizations/[id]/workspaces',
params: permissionGroupParamsSchema,
response: {
mode: 'json',
schema: z.object({
workspaces: z.array(permissionGroupWorkspaceRefSchema),
}),
},
})