Template
1
0

feat: add cerbos access control

This commit is contained in:
2025-09-19 03:28:00 +02:00
parent d322138502
commit 74a9426bcc
41 changed files with 999 additions and 821 deletions

View File

@@ -0,0 +1,19 @@
import { cerbos } from "./cerbos.ts";
import type { Principal } from "./principal.ts";
import { Resource } from "./resources.ts";
export function access(principal: Principal) {
return {
isAllowed(resource: Resource, action: string) {
return cerbos.isAllowed({ principal, resource, action });
},
checkResource(resource: Resource, actions: string[]) {
return cerbos.checkResource({ principal, resource, actions });
},
checkResources(resources: { resource: Resource; actions: string[] }[]) {
return cerbos.checkResources({ principal, resources });
},
};
}
export type Access = ReturnType<typeof access>;

View File

@@ -1,82 +1,21 @@
import { Auth, ResolvedSession } from "@valkyr/auth";
import z from "zod";
import { db } from "~stores/read-store/database.ts";
import { Auth } from "@valkyr/auth";
import { access } from "./access.ts";
import { config } from "./config.ts";
import { principal } from "./principal.ts";
import { resources } from "./resources.ts";
export const auth = new Auth(
{
settings: {
algorithm: "RS256",
privateKey: config.privateKey,
publicKey: config.publicKey,
issuer: "http://localhost",
audience: "http://localhost",
},
session: z.object({
accountId: z.string(),
}),
permissions: {} as const,
guards: [],
export const auth = new Auth({
principal,
resources,
access,
jwt: {
algorithm: "RS256",
privateKey: config.privateKey,
publicKey: config.publicKey,
issuer: "http://localhost",
audience: "http://localhost",
},
{
roles: {
async add(role) {
await db.collection("roles").insertOne(role);
},
});
async getById(id) {
const role = await db.collection("roles").findOne({ id });
if (role === null) {
return undefined;
}
return role;
},
async getBySession({ accountId }) {
const account = await db.collection("accounts").findOne({ id: accountId });
if (account === null) {
return [];
}
return db
.collection("roles")
.find({ id: { $in: account.roles } })
.toArray();
},
async setPermissions() {
throw new Error("MongoRolesProvider > .setPermissions is managed by Role aggregate projections");
},
async delete(id) {
await db.collection("roles").deleteOne({ id });
},
async assignAccount(roleId: string, accountId: string): Promise<void> {
await db.collection("accounts").updateOne(
{ id: accountId },
{
$push: {
roles: roleId,
},
},
);
},
async removeAccount(roleId: string, accountId: string): Promise<void> {
await db.collection("roles").updateOne(
{ id: accountId },
{
$pull: {
roles: roleId,
},
},
);
},
},
},
);
export type Session = ResolvedSession<typeof auth>;
export type Permissions = (typeof auth)["$permissions"];
export type Session = typeof auth.$session;

View File

@@ -0,0 +1,8 @@
import { HTTP } from "@cerbos/http";
export const cerbos = new HTTP("http://localhost:3592", {
adminCredentials: {
username: "cerbos",
password: "cerbosAdmin",
},
});

View File

@@ -0,0 +1,18 @@
import { RoleSchema } from "@spec/schemas/account/role.ts";
import { PrincipalProvider } from "@valkyr/auth";
import { db } from "~stores/read-store/database.ts";
export const principal = new PrincipalProvider(RoleSchema, {}, async function (id: string) {
const account = await db.collection("accounts").findOne({ id });
if (account === null) {
return undefined;
}
return {
id,
roles: account.roles,
attributes: {},
};
});
export type Principal = typeof principal.$principal;

View File

@@ -0,0 +1,10 @@
import { ResourceRegistry } from "@valkyr/auth";
export const resources = new ResourceRegistry([
{
kind: "account",
attributes: {},
},
] as const);
export type Resource = typeof resources.$resource;