From 0d70749670cf641ae70031001a83042b40995f92 Mon Sep 17 00:00:00 2001 From: Kodemon Date: Mon, 22 Sep 2025 02:34:29 +0200 Subject: [PATCH] feat: add initial workspace resource --- deno.lock | 3 +- modules/identity/auth/principal.ts | 27 ++++++++++------ modules/identity/models/identity.ts | 1 + platform/cerbos/client.ts | 19 ++++++++--- platform/cerbos/package.json | 4 ++- platform/cerbos/policies/workspace.yaml | 42 +++++++++++++++++++++++++ platform/cerbos/resources.ts | 4 +++ 7 files changed, 83 insertions(+), 17 deletions(-) create mode 100644 platform/cerbos/policies/workspace.yaml diff --git a/deno.lock b/deno.lock index d4ec9ea..56fbc93 100644 --- a/deno.lock +++ b/deno.lock @@ -2239,7 +2239,8 @@ "packageJson": { "dependencies": [ "npm:@cerbos/http@0.23.1", - "npm:@jsr/valkyr__auth@2.1.4" + "npm:@jsr/valkyr__auth@2.1.4", + "npm:zod@4.1.11" ] } }, diff --git a/modules/identity/auth/principal.ts b/modules/identity/auth/principal.ts index 5f8d5ee..2cfba34 100644 --- a/modules/identity/auth/principal.ts +++ b/modules/identity/auth/principal.ts @@ -1,5 +1,6 @@ import { HttpAdapter, makeClient } from "@platform/relay"; import { PrincipalProvider } from "@valkyr/auth"; +import z from "zod"; import { config } from "../config.ts"; import resolve from "../routes/identities/resolve/spec.ts"; @@ -18,15 +19,21 @@ export const identity = makeClient( }, ); -export const principal = new PrincipalProvider(RoleSchema, {}, async function (id: string) { - const response = await identity.resolve({ params: { id } }); - if ("data" in response) { - return { - id, - roles: response.data.roles, - attributes: {}, - }; - } -}); +export const principal = new PrincipalProvider( + RoleSchema, + { + workspaceIds: z.array(z.string()).optional().default([]), + }, + async function (id: string) { + const response = await identity.resolve({ params: { id } }); + if ("data" in response) { + return { + id, + roles: response.data.roles, + attributes: this.attributes.parse(response.data.attributes), + }; + } + }, +); export type Principal = typeof principal.$principal; diff --git a/modules/identity/models/identity.ts b/modules/identity/models/identity.ts index 668f796..77d19f4 100644 --- a/modules/identity/models/identity.ts +++ b/modules/identity/models/identity.ts @@ -16,6 +16,7 @@ export const IdentitySchema = z.object({ }), strategies: z.array(StrategySchema).default([]), roles: z.array(RoleSchema).default([]), + attributes: z.record(z.string(), z.any()), }); /* diff --git a/platform/cerbos/client.ts b/platform/cerbos/client.ts index fa1f598..7248ad4 100644 --- a/platform/cerbos/client.ts +++ b/platform/cerbos/client.ts @@ -1,8 +1,17 @@ import { HTTP } from "@cerbos/http"; +import { getEnvironmentVariable } from "@platform/config/environment.ts"; +import z from "zod"; -export const cerbos = new HTTP("http://localhost:3592", { - adminCredentials: { - username: "cerbos", - password: "cerbosAdmin", +export const cerbos = new HTTP( + getEnvironmentVariable({ + key: "CERBOS_URL", + type: z.string(), + fallback: "http://localhost:3592", + }), + { + adminCredentials: { + username: "cerbos", + password: "cerbosAdmin", + }, }, -}); +); diff --git a/platform/cerbos/package.json b/platform/cerbos/package.json index e2ac91d..fc0d63d 100644 --- a/platform/cerbos/package.json +++ b/platform/cerbos/package.json @@ -5,6 +5,8 @@ "type": "module", "dependencies": { "@cerbos/http": "0.23.1", - "@valkyr/auth": "npm:@jsr/valkyr__auth@2.1.4" + "@platform/config": "workspace:*", + "@valkyr/auth": "npm:@jsr/valkyr__auth@2.1.4", + "zod": "4.1.11" } } \ No newline at end of file diff --git a/platform/cerbos/policies/workspace.yaml b/platform/cerbos/policies/workspace.yaml new file mode 100644 index 0000000..85f1c6e --- /dev/null +++ b/platform/cerbos/policies/workspace.yaml @@ -0,0 +1,42 @@ +# yaml-language-server: $schema=https://api.cerbos.dev/latest/cerbos/policy/v1/Policy.schema.json +# docs: https://docs.cerbos.dev/cerbos/latest/policies/resource_policies + +apiVersion: api.cerbos.dev/v1 +resourcePolicy: + resource: workspace + version: default + rules: + + ### Read + + - actions: + - read + effect: EFFECT_ALLOW + roles: + - admin + - user + condition: + match: + expr: request.principal.workspaceIds.includes(request.resource.id) + + ### Update + + - actions: + - update + effect: EFFECT_ALLOW + roles: + - admin + condition: + match: + expr: request.principal.workspaceIds.includes(request.resource.id) + + ### Delete + + - actions: + - delete + effect: EFFECT_ALLOW + roles: + - admin + condition: + match: + expr: request.principal.workspaceIds.includes(request.resource.id) diff --git a/platform/cerbos/resources.ts b/platform/cerbos/resources.ts index f2507e9..3c0644d 100644 --- a/platform/cerbos/resources.ts +++ b/platform/cerbos/resources.ts @@ -5,6 +5,10 @@ export const resources = new ResourceRegistry([ kind: "identity", attr: {}, }, + { + kind: "workspace", + attr: {}, + }, ] as const); export type Resource = typeof resources.$resource;