diff --git a/cerbos.yaml b/cerbos.yaml index 987413b..9df1d0f 100644 --- a/cerbos.yaml +++ b/cerbos.yaml @@ -8,7 +8,6 @@ server: grpcListenAddr: ":3593" storage: - driver: disk - disk: - directory: /data/policies - watchForChanges: true + driver: "sqlite3" + sqlite3: + dsn: "file:/tmp/cerbos.sqlite?mode=rwc&cache=shared&_fk=true" diff --git a/deno.json b/deno.json index dda9811..cbd7970 100644 --- a/deno.json +++ b/deno.json @@ -6,32 +6,18 @@ "apps/react", "modules/iam", "modules/workspace", + "platform/cerbos", "platform/config", "platform/database", "platform/logger", "platform/relay", + "platform/routes", "platform/server", "platform/socket", "platform/spec", "platform/storage", "platform/vault" ], - "imports": { - "@modules/iam/client.ts": "./modules/iam/client.ts", - "@modules/iam/server.ts": "./modules/iam/server.ts", - "@modules/iam/types.ts": "./modules/iam/types.ts", - "@modules/workspace/client.ts": "./modules/workspace/client.ts", - "@modules/workspace/server.ts": "./modules/workspace/server.ts", - "@platform/config/": "./platform/config/", - "@platform/database/": "./platform/database/", - "@platform/logger": "./platform/logger/mod.ts", - "@platform/relay": "./platform/relay/mod.ts", - "@platform/server/": "./platform/server/", - "@platform/socket/": "./platform/socket/", - "@platform/spec/": "./platform/spec/", - "@platform/storage": "./platform/storage/storage.ts", - "@platform/vault": "./platform/vault/vault.ts" - }, "tasks": { "start:api": { "command": "cd ./api && deno run start", diff --git a/deno.lock b/deno.lock index 4be9626..0f53e2b 100644 --- a/deno.lock +++ b/deno.lock @@ -4,7 +4,9 @@ "npm:@biomejs/biome@*": "2.2.4", "npm:@biomejs/biome@2.2.4": "2.2.4", "npm:@cerbos/core@0.24.1": "0.24.1", + "npm:@cerbos/core@0.25.1": "0.25.1_@bufbuild+protobuf@2.10.1", "npm:@cerbos/http@0.23.1": "0.23.1", + "npm:@cerbos/http@0.23.3": "0.23.3_@bufbuild+protobuf@2.10.1_@cerbos+api@0.2.0", "npm:@eslint/js@9.35.0": "9.35.0", "npm:@jsr/std__assert@1.0.14": "1.0.14", "npm:@jsr/std__dotenv@0.225.5": "0.225.5", @@ -14,14 +16,14 @@ "npm:@jsr/valkyr__event-store@2.0.1": "2.0.1", "npm:@jsr/valkyr__inverse@1.0.1": "1.0.1", "npm:@jsr/valkyr__json-rpc@1.1.0": "1.1.0", - "npm:@tailwindcss/vite@4.1.13": "4.1.13_vite@7.1.6__picomatch@4.0.3_@types+node@24.2.0", + "npm:@tailwindcss/vite@4.1.13": "4.1.13_vite@7.1.6__@types+node@24.2.0__picomatch@4.0.3_@types+node@24.2.0", "npm:@tanstack/react-query@5.89.0": "5.89.0_react@19.1.1", "npm:@tanstack/react-router-devtools@1.131.47": "1.131.47_@tanstack+react-router@1.131.47__react@19.1.1__react-dom@19.1.1___react@19.1.1_react@19.1.1_react-dom@19.1.1__react@19.1.1", "npm:@tanstack/react-router@1.131.47": "1.131.47_react@19.1.1_react-dom@19.1.1__react@19.1.1", "npm:@types/node@*": "24.2.0", "npm:@types/react-dom@19.1.9": "19.1.9_@types+react@19.1.13", "npm:@types/react@19.1.13": "19.1.13", - "npm:@vitejs/plugin-react@4.7.0": "4.7.0_vite@7.1.6__picomatch@4.0.3_@babel+core@7.28.4_@types+node@24.2.0", + "npm:@vitejs/plugin-react@4.7.0": "4.7.0_vite@7.1.6__@types+node@24.2.0__picomatch@4.0.3_@babel+core@7.28.4_@types+node@24.2.0", "npm:better-auth@1.3.16": "1.3.16_react@19.1.1_react-dom@19.1.1__react@19.1.1", "npm:cookie@1.0.2": "1.0.2", "npm:eslint-plugin-react-hooks@5.2.0": "5.2.0_eslint@9.35.0", @@ -38,7 +40,7 @@ "npm:tailwindcss@4.1.13": "4.1.13", "npm:typescript-eslint@8.44.0": "8.44.0_eslint@9.35.0_typescript@5.9.2_@typescript-eslint+parser@8.44.0__eslint@9.35.0__typescript@5.9.2", "npm:typescript@5.9.2": "5.9.2", - "npm:vite@7.1.6": "7.1.6_picomatch@4.0.3_@types+node@24.2.0", + "npm:vite@7.1.6": "7.1.6_@types+node@24.2.0_picomatch@4.0.3", "npm:zod@4.1.11": "4.1.11" }, "npm": { @@ -239,16 +241,42 @@ "os": ["win32"], "cpu": ["x64"] }, + "@bufbuild/protobuf@2.10.1": { + "integrity": "sha512-ckS3+vyJb5qGpEYv/s1OebUHDi/xSNtfgw1wqKZo7MR9F2z+qXr0q5XagafAG/9O0QPVIUfST0smluYSTpYFkg==" + }, + "@cerbos/api@0.2.0": { + "integrity": "sha512-p3kAfmgz0WwxXBJ8Dt1vugV/QjQoPtF5b1R+h16YnUPZ6O4YL8D9gjz5WQRg/0FllodyaEtJlrMPxGEvJetkIw==", + "dependencies": [ + "@bufbuild/protobuf" + ] + }, "@cerbos/core@0.24.1": { "integrity": "sha512-Gt9ETQR3WDVcPlxN+HiGUDtNgWFulwS5ZjBgzJFsdb7e2GCw0tOPE9Ex1qHNZvG/0JHpFWJWIiYaSKyXcp35YQ==", "dependencies": [ "uuid" ] }, + "@cerbos/core@0.25.1_@bufbuild+protobuf@2.10.1": { + "integrity": "sha512-aPi8IqqgGHq9xyoBk6dYAKQ1U1athW0YZfI+7lzxxwpLlNFdZ9EwJLhaRSUFgYpUS2TBWDtX094Yn1kgB1esCQ==", + "dependencies": [ + "@bufbuild/protobuf", + "@cerbos/api", + "uuid" + ] + }, "@cerbos/http@0.23.1": { "integrity": "sha512-XzWFS6L7M+oUnjGEFIoQygtlmZy3zOpUobN6spGp1MAaT6GQJMRFK8P8xhY2BQjTIhqYgnoiEFOAULTkbgNIjg==", "dependencies": [ - "@cerbos/core", + "@cerbos/core@0.24.1", + "qs" + ] + }, + "@cerbos/http@0.23.3_@bufbuild+protobuf@2.10.1_@cerbos+api@0.2.0": { + "integrity": "sha512-yf8s4v+T4sf/ZiLorHpXhdStOr+q5XEjF2m/yvpcR7E/7e5eGCr5yEov9NIgfRQg1HGW8h+B6CIFBl9amSsaGw==", + "dependencies": [ + "@bufbuild/protobuf", + "@cerbos/api", + "@cerbos/core@0.25.1_@bufbuild+protobuf@2.10.1", "qs" ] }, @@ -891,22 +919,13 @@ ], "scripts": true }, - "@tailwindcss/vite@4.1.13_vite@7.1.6__picomatch@4.0.3": { + "@tailwindcss/vite@4.1.13_vite@7.1.6__@types+node@24.2.0__picomatch@4.0.3_@types+node@24.2.0": { "integrity": "sha512-0PmqLQ010N58SbMTJ7BVJ4I2xopiQn/5i6nlb4JmxzQf8zcS5+m2Cv6tqh+sfDwtIdjoEnOvwsGQ1hkUi8QEHQ==", "dependencies": [ "@tailwindcss/node", "@tailwindcss/oxide", "tailwindcss", - "vite@7.1.6_picomatch@4.0.3" - ] - }, - "@tailwindcss/vite@4.1.13_vite@7.1.6__picomatch@4.0.3_@types+node@24.2.0": { - "integrity": "sha512-0PmqLQ010N58SbMTJ7BVJ4I2xopiQn/5i6nlb4JmxzQf8zcS5+m2Cv6tqh+sfDwtIdjoEnOvwsGQ1hkUi8QEHQ==", - "dependencies": [ - "@tailwindcss/node", - "@tailwindcss/oxide", - "tailwindcss", - "vite@7.1.6_picomatch@4.0.3_@types+node@24.2.0" + "vite" ] }, "@tanstack/history@1.131.2": { @@ -1140,7 +1159,7 @@ "eslint-visitor-keys@4.2.1" ] }, - "@vitejs/plugin-react@4.7.0_vite@7.1.6__picomatch@4.0.3_@babel+core@7.28.4": { + "@vitejs/plugin-react@4.7.0_vite@7.1.6__@types+node@24.2.0__picomatch@4.0.3_@babel+core@7.28.4_@types+node@24.2.0": { "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", "dependencies": [ "@babel/core", @@ -1149,19 +1168,7 @@ "@rolldown/pluginutils", "@types/babel__core", "react-refresh", - "vite@7.1.6_picomatch@4.0.3" - ] - }, - "@vitejs/plugin-react@4.7.0_vite@7.1.6__picomatch@4.0.3_@babel+core@7.28.4_@types+node@24.2.0": { - "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", - "dependencies": [ - "@babel/core", - "@babel/plugin-transform-react-jsx-self", - "@babel/plugin-transform-react-jsx-source", - "@rolldown/pluginutils", - "@types/babel__core", - "react-refresh", - "vite@7.1.6_picomatch@4.0.3_@types+node@24.2.0" + "vite" ] }, "acorn-jsx@5.3.2_acorn@8.15.0": { @@ -2259,22 +2266,7 @@ "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==", "bin": true }, - "vite@7.1.6_picomatch@4.0.3": { - "integrity": "sha512-SRYIB8t/isTwNn8vMB3MR6E+EQZM/WG1aKmmIUCfDXfVvKfc20ZpamngWHKzAmmu9ppsgxsg4b2I7c90JZudIQ==", - "dependencies": [ - "esbuild", - "fdir", - "picomatch@4.0.3", - "postcss", - "rollup", - "tinyglobby" - ], - "optionalDependencies": [ - "fsevents" - ], - "bin": true - }, - "vite@7.1.6_picomatch@4.0.3_@types+node@24.2.0": { + "vite@7.1.6_@types+node@24.2.0_picomatch@4.0.3": { "integrity": "sha512-SRYIB8t/isTwNn8vMB3MR6E+EQZM/WG1aKmmIUCfDXfVvKfc20ZpamngWHKzAmmu9ppsgxsg4b2I7c90JZudIQ==", "dependencies": [ "@types/node", @@ -2390,6 +2382,14 @@ ] } }, + "platform/cerbos": { + "packageJson": { + "dependencies": [ + "npm:@cerbos/core@0.25.1", + "npm:@cerbos/http@0.23.3" + ] + } + }, "platform/config": { "packageJson": { "dependencies": [ @@ -2423,6 +2423,13 @@ ] } }, + "platform/routes": { + "packageJson": { + "dependencies": [ + "npm:zod@4.1.11" + ] + } + }, "platform/server": { "packageJson": { "dependencies": [ diff --git a/docker-compose.yml b/docker-compose.yml index 7ddd5d0..fb5a455 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,3 +1,11 @@ +networks: + server: + name: server + +volumes: + mongo: + driver: local + services: # MongoDB @@ -5,37 +13,33 @@ services: # Used by event store and read store for managing and reading application data. mongo: - image: mongo:8 restart: unless-stopped + image: mongo:8 + container_name: boilerplate_mongo ports: - - "27017:27017" + - 6017:27017 environment: MONGO_INITDB_ROOT_USERNAME: root MONGO_INITDB_ROOT_PASSWORD: password volumes: - - ./.volumes/mongo/local:/data/db + - mongo:/data/db networks: - - localdev + - server # Cerbos # -------------------------------------------------------------------------------- # Policy engine for application access control. cerbos: - container_name: cerbos + restart: unless-stopped image: ghcr.io/cerbos/cerbos:latest + container_name: boilerplate_cerbos command: ["server", "--config=/config.yaml"] ports: - - "3592:3592" - - "3593:3593" - - "3594:3594" + - 6592:3592 + - 6593:3593 + - 6594:3594 volumes: - ./cerbos.yaml:/config.yaml - - ./modules/identity/cerbos/policies:/data/policies/identity - - ./modules/workspace/cerbos/policies:/data/policies/workspace networks: - - localdev - -networks: - localdev: - driver: bridge + - server diff --git a/modules/workspace/cerbos/policies/workspace.yaml b/modules/workspace/cerbos/policies/workspace.yaml deleted file mode 100644 index afcbc87..0000000 --- a/modules/workspace/cerbos/policies/workspace.yaml +++ /dev/null @@ -1,33 +0,0 @@ -# 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: - - - actions: ["create"] - effect: EFFECT_ALLOW - roles: ["super"] - - - actions: ["read"] - effect: EFFECT_ALLOW - roles: ["super", "admin", "user"] - condition: - match: - expr: R.attr.workspaceId in P.attr.workspaceIds - - - actions: ["update"] - effect: EFFECT_ALLOW - roles: ["super", "admin"] - condition: - match: - expr: R.attr.workspaceId in P.attr.workspaceIds - - - actions: ["delete"] - effect: EFFECT_ALLOW - roles: ["super"] - condition: - match: - expr: R.attr.workspaceId in P.attr.workspaceIds diff --git a/modules/workspace/cerbos/policies/workspace_user.yaml b/modules/workspace/cerbos/policies/workspace_user.yaml deleted file mode 100644 index 47e314e..0000000 --- a/modules/workspace/cerbos/policies/workspace_user.yaml +++ /dev/null @@ -1,54 +0,0 @@ -# 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_user - version: default - rules: - - # Admins can invite new members into their own workspace - - - actions: - - invite - effect: EFFECT_ALLOW - roles: - - admin - condition: - match: - expr: request.principal.workspaceIds.includes(request.resource.workspaceId) - - # Admins can remove members from their own workspace - - - actions: - - remove - effect: EFFECT_ALLOW - roles: - - admin - condition: - match: - expr: request.principal.workspaceIds.includes(request.resource.workspaceId) - - # Admins can update member roles in their own workspace - - - actions: - - update_role - effect: EFFECT_ALLOW - roles: - - admin - condition: - match: - expr: request.principal.workspaceIds.includes(request.resource.workspaceId) - - # Admins and users can list/read members of their own workspace - - - actions: - - list - - read - effect: EFFECT_ALLOW - roles: - - admin - - user - condition: - match: - expr: request.principal.workspaceIds.includes(request.resource.workspaceId) diff --git a/modules/workspace/cerbos/resources.ts b/modules/workspace/cerbos/resources.ts deleted file mode 100644 index bf7c7a3..0000000 --- a/modules/workspace/cerbos/resources.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* -export const resources = new ResourceRegistry([ - { - kind: "workspace", - actions: [], - attr: { - workspaceId: z.string(), - }, - }, - { - kind: "workspace_user", - actions: [], - attr: { - workspaceId: z.string(), - }, - }, -] as const); - -export type Resource = typeof resources.$resource; -*/ diff --git a/platform/cerbos/package.json b/platform/cerbos/package.json new file mode 100644 index 0000000..bb00cc1 --- /dev/null +++ b/platform/cerbos/package.json @@ -0,0 +1,10 @@ +{ + "name": "@platform/cerbos", + "version": "0.0.0", + "private": true, + "type": "module", + "dependencies": { + "@cerbos/core": "0.25.1", + "@cerbos/http": "0.23.3" + } +} diff --git a/platform/database/config.ts b/platform/database/config.ts index 8fbc6b2..6113d59 100644 --- a/platform/database/config.ts +++ b/platform/database/config.ts @@ -11,7 +11,7 @@ export const config = { port: getEnvironmentVariable({ key: "DB_MONGO_PORT", type: z.coerce.number(), - fallback: "27017", + fallback: "67017", }), user: getEnvironmentVariable({ key: "DB_MONGO_USER", diff --git a/platform/routes/package.json b/platform/routes/package.json new file mode 100644 index 0000000..6f95686 --- /dev/null +++ b/platform/routes/package.json @@ -0,0 +1,10 @@ +{ + "name": "@platform/routes", + "version": "0.0.0", + "private": true, + "type": "module", + "dependencies": { + "@platform/relay": "workspace:*", + "zod": "4.1.11" + } +} diff --git a/platform/routes/session/search.ts b/platform/routes/session/search.ts new file mode 100644 index 0000000..b5b4e3c --- /dev/null +++ b/platform/routes/session/search.ts @@ -0,0 +1,8 @@ +import { route } from "@platform/relay"; +import z from "zod"; + +export default route.post("/api/v1/sessions/search").query({ + offset: z.number().min(0).default(0), + limit: z.number().min(10).max(100).default(100), + asc: z.boolean().default(true), +}); diff --git a/policy.json b/policy.json new file mode 100644 index 0000000..0e8eb06 --- /dev/null +++ b/policy.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://api.cerbos.dev/v0.47.0/cerbos/policy/v1/Policy.schema.json", + "apiVersion": "api.cerbos.dev/v1", + "resourcePolicy": { + "resource": "workspace", + "version": "1", + "rules": [ + { + "actions": ["create"], + "effect": "EFFECT_ALLOW", + "roles": ["super"] + } + ] + } +} \ No newline at end of file