chore: import upstream snapshot with attribution
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

This commit is contained in:
wehub-resource-sync
2026-07-13 13:20:55 +08:00
commit d25d482dc2
13754 changed files with 4996608 additions and 0 deletions
+42
View File
@@ -0,0 +1,42 @@
{
"name": "@sim/platform-authz",
"version": "0.1.0",
"private": true,
"sideEffects": false,
"type": "module",
"license": "Apache-2.0",
"engines": {
"bun": ">=1.2.13",
"node": ">=20.0.0"
},
"exports": {
"./predicates": {
"types": "./src/predicates.ts",
"default": "./src/predicates.ts"
},
"./workspace": {
"types": "./src/workspace.ts",
"default": "./src/workspace.ts"
},
"./workflow": {
"types": "./src/workflow.ts",
"default": "./src/workflow.ts"
}
},
"scripts": {
"type-check": "tsc --noEmit",
"lint": "biome check --write --unsafe .",
"lint:check": "biome check .",
"format": "biome format --write .",
"format:check": "biome format ."
},
"dependencies": {
"@sim/db": "workspace:*",
"drizzle-orm": "^0.45.2"
},
"devDependencies": {
"@sim/tsconfig": "workspace:*",
"@types/node": "24.2.1",
"typescript": "^7.0.2"
}
}
+37
View File
@@ -0,0 +1,37 @@
import type { permissionTypeEnum } from '@sim/db/schema'
/** Workspace permission level: read < write < admin. */
export type PermissionType = (typeof permissionTypeEnum.enumValues)[number]
/** Total ordering of workspace permission levels: read < write < admin. */
export const PERMISSION_RANK = { read: 1, write: 2, admin: 3 } as const satisfies Record<
PermissionType,
number
>
/**
* Whether an effective permission satisfies a required level under the
* read < write < admin ordering. `null`/`undefined` (no access) never satisfies.
* Single source of truth for permission-level comparisons across the app and the
* realtime server — replaces the hand-written `=== 'admin' || === 'write'` ladders.
*/
export function permissionSatisfies(
have: PermissionType | null | undefined,
required: PermissionType
): boolean {
return have != null && PERMISSION_RANK[have] >= PERMISSION_RANK[required]
}
/** Organization membership roles (Better Auth) that confer admin authority. */
export const ORG_ADMIN_ROLES = ['owner', 'admin'] as const
/**
* Whether an organization membership role is owner/admin. Owner/admin org roles
* are derived workspace admins on the org's workspaces — single source of truth
* for the `role === 'owner' || role === 'admin'` predicate, shared by server
* resolvers and client UIs. Dependency-free (the only import is a type, which is
* erased) so client bundles can import it without pulling in the DB client.
*/
export function isOrgAdminRole(role: string | null | undefined): boolean {
return role === 'owner' || role === 'admin'
}
+302
View File
@@ -0,0 +1,302 @@
import { db, workflow, workflowFolder, workspace } from '@sim/db'
import { and, eq, isNull } from 'drizzle-orm'
import {
type PermissionType,
permissionSatisfies,
resolveEffectiveWorkspacePermission,
} from './workspace'
export type { PermissionType }
export type ActiveWorkflowRecord = typeof workflow.$inferSelect
export interface ActiveWorkflowContext {
workflow: ActiveWorkflowRecord
workspaceId: string
workspaceOrganizationId: string | null
}
export async function getActiveWorkflowContext(
workflowId: string
): Promise<ActiveWorkflowContext | null> {
const rows = await db
.select({
workflow,
workspaceId: workspace.id,
workspaceOrganizationId: workspace.organizationId,
})
.from(workflow)
.innerJoin(workspace, eq(workflow.workspaceId, workspace.id))
.where(
and(eq(workflow.id, workflowId), isNull(workflow.archivedAt), isNull(workspace.archivedAt))
)
.limit(1)
if (rows.length === 0) {
return null
}
return {
workflow: rows[0].workflow,
workspaceId: rows[0].workspaceId,
workspaceOrganizationId: rows[0].workspaceOrganizationId,
}
}
export async function getActiveWorkflowRecord(
workflowId: string
): Promise<ActiveWorkflowRecord | null> {
const context = await getActiveWorkflowContext(workflowId)
return context?.workflow ?? null
}
export async function assertActiveWorkflowContext(
workflowId: string
): Promise<ActiveWorkflowContext> {
const context = await getActiveWorkflowContext(workflowId)
if (!context) {
throw new Error(`Active workflow not found: ${workflowId}`)
}
return context
}
type WorkflowRecord = typeof workflow.$inferSelect
export class WorkflowLockedError extends Error {
readonly status = 423
constructor(message = 'Workflow is locked') {
super(message)
this.name = 'WorkflowLockedError'
}
}
export class FolderLockedError extends Error {
readonly status = 423
constructor(message = 'Folder is locked') {
super(message)
this.name = 'FolderLockedError'
}
}
export interface LockStatus {
locked: boolean
directLocked: boolean
inheritedLocked: boolean
lockedBy: 'workflow' | 'folder' | null
lockedFolderId: string | null
}
export async function getFolderLockStatus(folderId: string | null): Promise<LockStatus> {
if (!folderId) {
return {
locked: false,
directLocked: false,
inheritedLocked: false,
lockedBy: null,
lockedFolderId: null,
}
}
let currentFolderId: string | null = folderId
let isDirect = true
const visited = new Set<string>()
while (currentFolderId && !visited.has(currentFolderId)) {
visited.add(currentFolderId)
const [folder] = await db
.select({
id: workflowFolder.id,
parentId: workflowFolder.parentId,
locked: workflowFolder.locked,
})
.from(workflowFolder)
.where(and(eq(workflowFolder.id, currentFolderId), isNull(workflowFolder.archivedAt)))
.limit(1)
if (!folder) break
if (folder.locked) {
return {
locked: true,
directLocked: isDirect,
inheritedLocked: !isDirect,
lockedBy: 'folder',
lockedFolderId: folder.id,
}
}
currentFolderId = folder.parentId
isDirect = false
}
return {
locked: false,
directLocked: false,
inheritedLocked: false,
lockedBy: null,
lockedFolderId: null,
}
}
export async function getWorkflowLockStatus(workflowId: string): Promise<LockStatus> {
const [wf] = await db
.select({
locked: workflow.locked,
folderId: workflow.folderId,
})
.from(workflow)
.where(and(eq(workflow.id, workflowId), isNull(workflow.archivedAt)))
.limit(1)
if (!wf) {
return {
locked: false,
directLocked: false,
inheritedLocked: false,
lockedBy: null,
lockedFolderId: null,
}
}
if (wf.locked) {
return {
locked: true,
directLocked: true,
inheritedLocked: false,
lockedBy: 'workflow',
lockedFolderId: null,
}
}
return getFolderLockStatus(wf.folderId)
}
export async function assertWorkflowMutable(workflowId: string): Promise<void> {
const status = await getWorkflowLockStatus(workflowId)
if (status.locked) {
throw new WorkflowLockedError(
status.lockedBy === 'folder'
? 'Workflow is locked by its containing folder'
: 'Workflow is locked'
)
}
}
export async function assertFolderMutable(folderId: string | null): Promise<void> {
const status = await getFolderLockStatus(folderId)
if (status.locked) {
throw new FolderLockedError(
status.inheritedLocked ? 'Folder is locked by an ancestor folder' : 'Folder is locked'
)
}
}
export class FolderNotFoundError extends Error {
readonly status = 400
constructor(message = 'Target folder not found') {
super(message)
this.name = 'FolderNotFoundError'
}
}
/**
* Resolves whether a folder may be assigned to a workflow in the given workspace:
* it must exist, not be archived, and belong to that same workspace. A null/undefined
* folderId (the workspace root) is always allowed. Guards against cross-workspace
* folder references when a workflow's `folderId` is set from request input.
*/
export async function isFolderInWorkspace(
folderId: string | null | undefined,
workspaceId: string
): Promise<boolean> {
if (!folderId) return true
const [folder] = await db
.select({
workspaceId: workflowFolder.workspaceId,
archivedAt: workflowFolder.archivedAt,
})
.from(workflowFolder)
.where(eq(workflowFolder.id, folderId))
.limit(1)
return Boolean(folder && folder.workspaceId === workspaceId && !folder.archivedAt)
}
/**
* Throws {@link FolderNotFoundError} (HTTP 400) when `folderId` does not belong to
* `workspaceId` (or is archived/missing). No-op for a null/undefined folderId.
*/
export async function assertFolderInWorkspace(
folderId: string | null | undefined,
workspaceId: string
): Promise<void> {
if (!(await isFolderInWorkspace(folderId, workspaceId))) {
throw new FolderNotFoundError()
}
}
export interface WorkflowWorkspaceAuthorizationResult {
allowed: boolean
status: number
message?: string
workflow: WorkflowRecord | null
workspacePermission: PermissionType | null
}
export async function authorizeWorkflowByWorkspacePermission(params: {
workflowId: string
userId: string
action?: 'read' | 'write' | 'admin'
}): Promise<WorkflowWorkspaceAuthorizationResult> {
const { workflowId, userId, action = 'read' } = params
const activeContext = await getActiveWorkflowContext(workflowId)
if (!activeContext) {
return {
allowed: false,
status: 404,
message: 'Workflow not found',
workflow: null,
workspacePermission: null,
}
}
const wf = activeContext.workflow
if (!wf.workspaceId) {
return {
allowed: false,
status: 403,
message:
'This workflow is not attached to a workspace. Personal workflows are deprecated and cannot be accessed.',
workflow: wf,
workspacePermission: null,
}
}
const workspacePermission = await resolveEffectiveWorkspacePermission(
userId,
wf.workspaceId,
activeContext.workspaceOrganizationId
)
if (!permissionSatisfies(workspacePermission, action)) {
return {
allowed: false,
status: 403,
message: `Unauthorized: Access denied to ${action} this workflow`,
workflow: wf,
workspacePermission,
}
}
return {
allowed: true,
status: 200,
workflow: wf,
workspacePermission,
}
}
+55
View File
@@ -0,0 +1,55 @@
import { db } from '@sim/db'
import { member, permissions } from '@sim/db/schema'
import { and, eq } from 'drizzle-orm'
import { isOrgAdminRole, type PermissionType } from './predicates'
export * from './predicates'
/**
* Resolves the effective workspace permission under the governance inheritance
* model: the owners/admins of the organization that owns the workspace are
* derived workspace admins. Returns the higher of any explicit grant and the
* org-admin derivation.
*
* The workspace owner is intentionally NOT a special case: every owner already
* holds an explicit `admin` row in `permissions` (added at creation, verified
* across all production workspaces), so the lookup below already grants them
* admin. `workspace.ownerId` is a lifecycle anchor, not a permission input.
*
* Single source of truth for workspace-permission resolution, shared by the Next
* app (`getEffectiveWorkspacePermission`) and the realtime server (via the
* `/workflow` entry). Lives in a package because `apps/realtime` needs it and
* packages may not import app code.
*/
export async function resolveEffectiveWorkspacePermission(
userId: string,
workspaceId: string,
workspaceOrganizationId: string | null
): Promise<PermissionType | null> {
const [permissionRow] = await db
.select({ permissionType: permissions.permissionType })
.from(permissions)
.where(
and(
eq(permissions.userId, userId),
eq(permissions.entityType, 'workspace'),
eq(permissions.entityId, workspaceId)
)
)
.limit(1)
const explicit = (permissionRow?.permissionType as PermissionType | undefined) ?? null
if (workspaceOrganizationId && explicit !== 'admin') {
const [memberRow] = await db
.select({ role: member.role })
.from(member)
.where(and(eq(member.userId, userId), eq(member.organizationId, workspaceOrganizationId)))
.limit(1)
if (isOrgAdminRole(memberRow?.role)) {
return 'admin'
}
}
return explicit
}
+5
View File
@@ -0,0 +1,5 @@
{
"extends": "@sim/tsconfig/library.json",
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}