// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0 // The tenant wall of the effect actions (lib/effects.ts). The kernel in an action resolves the // tenant scope and checks the permission here, inside a query, with the same tenancy hooks over // ctx.db that every other procedure uses. Both are internal: only this deployment's own actions // call them, and they derive the actor and the organization pin from the caller's verified // identity, never from their arguments. import { type Actor, type AnyScope, notFound, toProblem, unauthenticated } from "@conusai-kit/core" import { ConvexError, type Value, v } from "convex/values" import { internalQuery } from "./_generated/server" import { callContextOf, tenancyOver } from "./lib/runtime" const level = v.union(v.literal("org"), v.literal("workspace"), v.literal("project")) async function verifiedActor( auth: Parameters[0], ): Promise<{ actor: Actor; pinnedOrgId?: string }> { const { actor, pinnedOrgId } = await callContextOf(auth) if (!actor) throw unauthenticated() return { actor, ...(pinnedOrgId === undefined ? {} : { pinnedOrgId }) } } const problem = (error: unknown): never => { throw new ConvexError(toProblem(error) as unknown as Value) } /** The scope ids of a tenant-level call, or the kernel's problem (a stranger's notFound). */ export const resolve = internalQuery({ args: { level, input: v.any() }, handler: async (ctx, args) => { try { const { actor, pinnedOrgId } = await verifiedActor(ctx.auth) const scope = (await tenancyOver(ctx.db).resolveScope(args.level, args.input, actor, pinnedOrgId)) as AnyScope & { orgId: string workspaceId?: string projectId?: string } if (pinnedOrgId !== undefined && scope.orgId !== pinnedOrgId) throw notFound("no scope in this organization") return { orgId: scope.orgId, ...(scope.workspaceId === undefined ? {} : { workspaceId: scope.workspaceId }), ...(scope.projectId === undefined ? {} : { projectId: scope.projectId }), } } catch (error) { return problem(error) } }, }) /** Whether the verified actor holds `permission` in the scope, for this procedure. */ export const authorize = internalQuery({ args: { level, scope: v.object({ orgId: v.string(), workspaceId: v.optional(v.string()), projectId: v.optional(v.string()) }), permission: v.string(), procedure: v.object({ name: v.string(), kind: v.union(v.literal("read"), v.literal("write"), v.literal("effect")), }), }, handler: async (ctx, args) => { try { const { actor, pinnedOrgId } = await verifiedActor(ctx.auth) if (pinnedOrgId !== undefined && args.scope.orgId !== pinnedOrgId) return false const scope = { level: args.level, actor, ...args.scope } as AnyScope return await tenancyOver(ctx.db).authorize(scope, args.permission, args.procedure) } catch (error) { return problem(error) } }, })