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
314 lines
10 KiB
TypeScript
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),
|
|
}),
|
|
},
|
|
})
|