feat: identity cerbos implementation
This commit is contained in:
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@@ -3,6 +3,7 @@
|
|||||||
"deno.enable": true,
|
"deno.enable": true,
|
||||||
"deno.lint": false,
|
"deno.lint": false,
|
||||||
"editor.defaultFormatter": "biomejs.biome",
|
"editor.defaultFormatter": "biomejs.biome",
|
||||||
|
"editor.formatOnSave": true,
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.organizeImports.biome": "explicit",
|
"source.organizeImports.biome": "explicit",
|
||||||
"source.fixAll.biome": "explicit"
|
"source.fixAll.biome": "explicit"
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import "@modules/identity/server.ts";
|
import { identity } from "@modules/identity/client.ts";
|
||||||
|
import { getPrincipalSession } from "@modules/identity/server.ts";
|
||||||
import { getAccessControlMethods, identity } from "@modules/identity/server.ts";
|
|
||||||
import { context, UnauthorizedError } from "@platform/relay";
|
import { context, UnauthorizedError } from "@platform/relay";
|
||||||
import { storage } from "@platform/storage";
|
import { storage } from "@platform/storage";
|
||||||
|
|
||||||
@@ -92,7 +91,7 @@ async function resolvePrincipalSession(request: Request) {
|
|||||||
// Fetch session from identity module and tag it as a resolution
|
// Fetch session from identity module and tag it as a resolution
|
||||||
// call so it can break out of a resolution loop.
|
// call so it can break out of a resolution loop.
|
||||||
|
|
||||||
const session = await identity.resolve({
|
const session = await getPrincipalSession({
|
||||||
headers: new Headers({
|
headers: new Headers({
|
||||||
cookie,
|
cookie,
|
||||||
[IDENTITY_RESOLVE_HEADER]: "true",
|
[IDENTITY_RESOLVE_HEADER]: "true",
|
||||||
@@ -102,13 +101,13 @@ async function resolvePrincipalSession(request: Request) {
|
|||||||
// ### Populate Context
|
// ### Populate Context
|
||||||
// On successfull resolution we build the request identity context.
|
// On successfull resolution we build the request identity context.
|
||||||
|
|
||||||
if ("data" in session) {
|
if (session !== undefined) {
|
||||||
const context = storage.getStore();
|
const context = storage.getStore();
|
||||||
if (context === undefined) {
|
if (context === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
context.session = session.data.session;
|
context.session = session.session;
|
||||||
context.principal = session.data.principal;
|
context.principal = session.principal;
|
||||||
context.access = getAccessControlMethods(session.data.principal);
|
context.access = identity.access;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
"apps/react",
|
"apps/react",
|
||||||
"modules/identity",
|
"modules/identity",
|
||||||
"modules/workspace",
|
"modules/workspace",
|
||||||
"platform/cerbos",
|
|
||||||
"platform/config",
|
"platform/config",
|
||||||
"platform/database",
|
"platform/database",
|
||||||
"platform/logger",
|
"platform/logger",
|
||||||
@@ -22,7 +21,6 @@
|
|||||||
"@modules/identity/server.ts": "./modules/identity/server.ts",
|
"@modules/identity/server.ts": "./modules/identity/server.ts",
|
||||||
"@modules/workspace/client.ts": "./modules/workspace/client.ts",
|
"@modules/workspace/client.ts": "./modules/workspace/client.ts",
|
||||||
"@modules/workspace/server.ts": "./modules/workspace/server.ts",
|
"@modules/workspace/server.ts": "./modules/workspace/server.ts",
|
||||||
"@platform/cerbos": "./platform/cerbos/mod.ts",
|
|
||||||
"@platform/config/": "./platform/config/",
|
"@platform/config/": "./platform/config/",
|
||||||
"@platform/database/": "./platform/database/",
|
"@platform/database/": "./platform/database/",
|
||||||
"@platform/logger": "./platform/logger/mod.ts",
|
"@platform/logger": "./platform/logger/mod.ts",
|
||||||
|
|||||||
11
deno.lock
generated
11
deno.lock
generated
@@ -3,6 +3,7 @@
|
|||||||
"specifiers": {
|
"specifiers": {
|
||||||
"npm:@biomejs/biome@*": "2.2.4",
|
"npm:@biomejs/biome@*": "2.2.4",
|
||||||
"npm:@biomejs/biome@2.2.4": "2.2.4",
|
"npm:@biomejs/biome@2.2.4": "2.2.4",
|
||||||
|
"npm:@cerbos/core@0.24.1": "0.24.1",
|
||||||
"npm:@cerbos/http@0.23.1": "0.23.1",
|
"npm:@cerbos/http@0.23.1": "0.23.1",
|
||||||
"npm:@eslint/js@9.35.0": "9.35.0",
|
"npm:@eslint/js@9.35.0": "9.35.0",
|
||||||
"npm:@jsr/std__assert@1.0.14": "1.0.14",
|
"npm:@jsr/std__assert@1.0.14": "1.0.14",
|
||||||
@@ -2372,6 +2373,8 @@
|
|||||||
"modules/identity": {
|
"modules/identity": {
|
||||||
"packageJson": {
|
"packageJson": {
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
|
"npm:@cerbos/core@0.24.1",
|
||||||
|
"npm:@cerbos/http@0.23.1",
|
||||||
"npm:better-auth@1.3.16",
|
"npm:better-auth@1.3.16",
|
||||||
"npm:cookie@1.0.2",
|
"npm:cookie@1.0.2",
|
||||||
"npm:zod@4.1.11"
|
"npm:zod@4.1.11"
|
||||||
@@ -2387,14 +2390,6 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"platform/cerbos": {
|
|
||||||
"packageJson": {
|
|
||||||
"dependencies": [
|
|
||||||
"npm:@cerbos/http@0.23.1",
|
|
||||||
"npm:zod@4.1.11"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"platform/config": {
|
"platform/config": {
|
||||||
"packageJson": {
|
"packageJson": {
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
|
|||||||
@@ -1,17 +1,34 @@
|
|||||||
|
import { CheckResourcesResponse } from "@cerbos/core";
|
||||||
import { HttpAdapter, makeClient } from "@platform/relay";
|
import { HttpAdapter, makeClient } from "@platform/relay";
|
||||||
|
|
||||||
import { config } from "./config.ts";
|
import { config } from "./config.ts";
|
||||||
|
import checkResource from "./routes/access/check-resource/spec.ts";
|
||||||
|
import checkResources from "./routes/access/check-resources/spec.ts";
|
||||||
|
import isAllowed from "./routes/access/is-allowed/spec.ts";
|
||||||
import getById from "./routes/identities/get/spec.ts";
|
import getById from "./routes/identities/get/spec.ts";
|
||||||
import loginByPassword from "./routes/login/code/spec.ts";
|
import loginByPassword from "./routes/login/code/spec.ts";
|
||||||
import loginByEmail from "./routes/login/email/spec.ts";
|
import loginByEmail from "./routes/login/email/spec.ts";
|
||||||
import loginByCode from "./routes/login/password/spec.ts";
|
import loginByCode from "./routes/login/password/spec.ts";
|
||||||
import me from "./routes/me/spec.ts";
|
import me from "./routes/me/spec.ts";
|
||||||
|
|
||||||
|
const adapter = new HttpAdapter({
|
||||||
|
url: config.url,
|
||||||
|
});
|
||||||
|
|
||||||
|
const access = makeClient(
|
||||||
|
{
|
||||||
|
adapter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
isAllowed,
|
||||||
|
checkResource,
|
||||||
|
checkResources,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export const identity = makeClient(
|
export const identity = makeClient(
|
||||||
{
|
{
|
||||||
adapter: new HttpAdapter({
|
adapter,
|
||||||
url: config.url,
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
@@ -43,5 +60,104 @@ export const identity = makeClient(
|
|||||||
*/
|
*/
|
||||||
code: loginByCode,
|
code: loginByCode,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
access: {
|
||||||
|
/**
|
||||||
|
* Check if a principal is allowed to perform an action on a resource.
|
||||||
|
*
|
||||||
|
* @param resource - Resource which we are validating.
|
||||||
|
* @param action - Action which we are validating.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* await access.isAllowed(
|
||||||
|
* {
|
||||||
|
* kind: "document",
|
||||||
|
* id: "1",
|
||||||
|
* attr: { owner: "user@example.com" },
|
||||||
|
* },
|
||||||
|
* "view"
|
||||||
|
* ); // => true
|
||||||
|
*/
|
||||||
|
isAllowed: async (resource: Resource, action: string) => {
|
||||||
|
const response = await access.isAllowed({ body: { resource, action } });
|
||||||
|
if ("error" in response) {
|
||||||
|
throw response.error;
|
||||||
|
}
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check a principal's permissions on a resource.
|
||||||
|
*
|
||||||
|
* @param resource - Resource which we are validating.
|
||||||
|
* @param actions - Actions which we are validating.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* const decision = await access.checkResource(
|
||||||
|
* {
|
||||||
|
* kind: "document",
|
||||||
|
* id: "1",
|
||||||
|
* attr: { owner: "user@example.com" },
|
||||||
|
* },
|
||||||
|
* ["view", "edit"],
|
||||||
|
* );
|
||||||
|
*
|
||||||
|
* decision.isAllowed("view"); // => true
|
||||||
|
*/
|
||||||
|
checkResource: async (resource: Resource, actions: string[]) => {
|
||||||
|
const response = await access.checkResource({ body: { resource, actions } });
|
||||||
|
if ("error" in response) {
|
||||||
|
throw response.error;
|
||||||
|
}
|
||||||
|
return new CheckResourcesResponse(response.data);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check a principal's permissions on a set of resources.
|
||||||
|
*
|
||||||
|
* @param resources - Resources which we are validating.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* const decision = await access.checkResources([
|
||||||
|
* {
|
||||||
|
* resource: {
|
||||||
|
* kind: "document",
|
||||||
|
* id: "1",
|
||||||
|
* attr: { owner: "user@example.com" },
|
||||||
|
* },
|
||||||
|
* actions: ["view", "edit"],
|
||||||
|
* },
|
||||||
|
* {
|
||||||
|
* resource: {
|
||||||
|
* kind: "image",
|
||||||
|
* id: "1",
|
||||||
|
* attr: { owner: "user@example.com" },
|
||||||
|
* },
|
||||||
|
* actions: ["delete"],
|
||||||
|
* },
|
||||||
|
* ]);
|
||||||
|
*
|
||||||
|
* decision.isAllowed({
|
||||||
|
* resource: { kind: "document", id: "1" },
|
||||||
|
* action: "view",
|
||||||
|
* }); // => true
|
||||||
|
*/
|
||||||
|
checkResources: async (resources: { resource: Resource; actions: string[] }[]) => {
|
||||||
|
const response = await access.checkResources({ body: resources });
|
||||||
|
if ("error" in response) {
|
||||||
|
throw response.error;
|
||||||
|
}
|
||||||
|
return new CheckResourcesResponse(response.data);
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
type Resource = {
|
||||||
|
kind: string;
|
||||||
|
id: string;
|
||||||
|
attr: Record<string, any>;
|
||||||
|
};
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
"./server.ts": "./server.ts"
|
"./server.ts": "./server.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@platform/cerbos": "workspace:*",
|
"@cerbos/core": "0.24.1",
|
||||||
|
"@cerbos/http": "0.23.1",
|
||||||
"@platform/config": "workspace:*",
|
"@platform/config": "workspace:*",
|
||||||
"@platform/logger": "workspace:*",
|
"@platform/logger": "workspace:*",
|
||||||
"@platform/relay": "workspace:*",
|
"@platform/relay": "workspace:*",
|
||||||
|
|||||||
6
modules/identity/routes/access/check-resource/handle.ts
Normal file
6
modules/identity/routes/access/check-resource/handle.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { cerbos } from "../../../cerbos/client.ts";
|
||||||
|
import route from "./spec.ts";
|
||||||
|
|
||||||
|
export default route.access("session").handle(async ({ body: { resource, actions } }, { principal }) => {
|
||||||
|
return cerbos.checkResource({ principal, resource, actions });
|
||||||
|
});
|
||||||
16
modules/identity/routes/access/check-resource/spec.ts
Normal file
16
modules/identity/routes/access/check-resource/spec.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { route } from "@platform/relay";
|
||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
export default route
|
||||||
|
.post("/api/v1/identity/access/check-resource")
|
||||||
|
.body(
|
||||||
|
z.strictObject({
|
||||||
|
resource: z.strictObject({
|
||||||
|
kind: z.string(),
|
||||||
|
id: z.string(),
|
||||||
|
attr: z.record(z.string(), z.any()),
|
||||||
|
}),
|
||||||
|
actions: z.array(z.string()),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.response(z.any());
|
||||||
6
modules/identity/routes/access/check-resources/handle.ts
Normal file
6
modules/identity/routes/access/check-resources/handle.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { cerbos } from "../../../cerbos/client.ts";
|
||||||
|
import route from "./spec.ts";
|
||||||
|
|
||||||
|
export default route.access("session").handle(async ({ body: resources }, { principal }) => {
|
||||||
|
return cerbos.checkResources({ principal, resources });
|
||||||
|
});
|
||||||
18
modules/identity/routes/access/check-resources/spec.ts
Normal file
18
modules/identity/routes/access/check-resources/spec.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { route } from "@platform/relay";
|
||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
export default route
|
||||||
|
.post("/api/v1/identity/access/check-resources")
|
||||||
|
.body(
|
||||||
|
z.array(
|
||||||
|
z.strictObject({
|
||||||
|
resource: z.strictObject({
|
||||||
|
kind: z.string(),
|
||||||
|
id: z.string(),
|
||||||
|
attr: z.record(z.string(), z.any()),
|
||||||
|
}),
|
||||||
|
actions: z.array(z.string()),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.response(z.any());
|
||||||
6
modules/identity/routes/access/is-allowed/handle.ts
Normal file
6
modules/identity/routes/access/is-allowed/handle.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { cerbos } from "../../../cerbos/client.ts";
|
||||||
|
import route from "./spec.ts";
|
||||||
|
|
||||||
|
export default route.access("session").handle(async ({ body: { resource, action } }, { principal }) => {
|
||||||
|
return cerbos.isAllowed({ principal, resource, action });
|
||||||
|
});
|
||||||
16
modules/identity/routes/access/is-allowed/spec.ts
Normal file
16
modules/identity/routes/access/is-allowed/spec.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { route } from "@platform/relay";
|
||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
export default route
|
||||||
|
.post("/api/v1/identity/access/is-allowed")
|
||||||
|
.body(
|
||||||
|
z.strictObject({
|
||||||
|
resource: z.strictObject({
|
||||||
|
kind: z.string(),
|
||||||
|
id: z.string(),
|
||||||
|
attr: z.record(z.string(), z.any()),
|
||||||
|
}),
|
||||||
|
action: z.string(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.response(z.boolean());
|
||||||
@@ -1,17 +1,16 @@
|
|||||||
|
import { ForbiddenError, NotFoundError } from "@platform/relay";
|
||||||
|
|
||||||
|
import { getPrincipalById } from "../../../services/database.ts";
|
||||||
import route from "./spec.ts";
|
import route from "./spec.ts";
|
||||||
|
|
||||||
export default route.access("session").handle(async () => {
|
export default route.access("session").handle(async ({ params: { id } }, { access }) => {
|
||||||
// const user = await getUserById(id);
|
const principal = await getPrincipalById(id);
|
||||||
// if (user === undefined) {
|
if (principal === undefined) {
|
||||||
// return new NotFoundError("Identity does not exist, or has been removed.");
|
return new NotFoundError("Identity does not exist, or has been removed.");
|
||||||
// }
|
}
|
||||||
// const decision = await access.isAllowed({ kind: "identity", id: user.id, attr: {} }, "read");
|
const decision = await access.isAllowed({ kind: "identity", id, attr: {} }, "read");
|
||||||
// if (decision === false) {
|
if (decision === false) {
|
||||||
// return new ForbiddenError("You do not have permission to view this identity.");
|
return new ForbiddenError("You do not have permission to view this identity.");
|
||||||
// }
|
}
|
||||||
// return {
|
return principal;
|
||||||
// id: user.id,
|
|
||||||
// roles: await getPrincipalRoles(id),
|
|
||||||
// attr: await getPrincipalAttributes(id),
|
|
||||||
// };
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import resolve from "./routes/session/resolve/spec.ts";
|
|||||||
|--------------------------------------------------------------------------------
|
|--------------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const identity = makeClient(
|
const identity = makeClient(
|
||||||
{
|
{
|
||||||
adapter: new HttpAdapter({
|
adapter: new HttpAdapter({
|
||||||
url: config.url,
|
url: config.url,
|
||||||
@@ -22,13 +22,19 @@ export const identity = makeClient(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export async function getPrincipalSession(payload: { headers: Headers }) {
|
||||||
|
const response = await identity.resolve(payload);
|
||||||
|
if ("data" in response) {
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------------
|
|--------------------------------------------------------------------------------
|
||||||
| Server Exports
|
| Server Exports
|
||||||
|--------------------------------------------------------------------------------
|
|--------------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export * from "./services/access.ts";
|
|
||||||
export * from "./services/session.ts";
|
export * from "./services/session.ts";
|
||||||
export * from "./types.ts";
|
export * from "./types.ts";
|
||||||
|
|
||||||
@@ -49,5 +55,8 @@ export default {
|
|||||||
(await import("./routes/me/handle.ts")).default,
|
(await import("./routes/me/handle.ts")).default,
|
||||||
(await import("./routes/roles/handle.ts")).default,
|
(await import("./routes/roles/handle.ts")).default,
|
||||||
(await import("./routes/session/resolve/handle.ts")).default,
|
(await import("./routes/session/resolve/handle.ts")).default,
|
||||||
|
(await import("./routes/access/is-allowed/handle.ts")).default,
|
||||||
|
(await import("./routes/access/check-resource/handle.ts")).default,
|
||||||
|
(await import("./routes/access/check-resources/handle.ts")).default,
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,88 +0,0 @@
|
|||||||
import { cerbos } from "@platform/cerbos";
|
|
||||||
|
|
||||||
import type { Principal } from "../models/principal.ts";
|
|
||||||
|
|
||||||
export function getAccessControlMethods(principal: Principal) {
|
|
||||||
return {
|
|
||||||
/**
|
|
||||||
* Check if a principal is allowed to perform an action on a resource.
|
|
||||||
*
|
|
||||||
* @param resource - Resource which we are validating.
|
|
||||||
* @param action - Action which we are validating.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
*
|
|
||||||
* await access.isAllowed(
|
|
||||||
* {
|
|
||||||
* kind: "document",
|
|
||||||
* id: "1",
|
|
||||||
* attr: { owner: "user@example.com" },
|
|
||||||
* },
|
|
||||||
* "view"
|
|
||||||
* ); // => true
|
|
||||||
*/
|
|
||||||
isAllowed(resource: any, action: string) {
|
|
||||||
return cerbos.isAllowed({ principal, resource, action });
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check a principal's permissions on a resource.
|
|
||||||
*
|
|
||||||
* @param resource - Resource which we are validating.
|
|
||||||
* @param actions - Actions which we are validating.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
*
|
|
||||||
* const decision = await access.checkResource(
|
|
||||||
* {
|
|
||||||
* kind: "document",
|
|
||||||
* id: "1",
|
|
||||||
* attr: { owner: "user@example.com" },
|
|
||||||
* },
|
|
||||||
* ["view", "edit"],
|
|
||||||
* );
|
|
||||||
*
|
|
||||||
* decision.isAllowed("view"); // => true
|
|
||||||
*/
|
|
||||||
checkResource(resource: any, actions: string[]) {
|
|
||||||
return cerbos.checkResource({ principal, resource, actions });
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check a principal's permissions on a set of resources.
|
|
||||||
*
|
|
||||||
* @param resources - Resources which we are validating.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
*
|
|
||||||
* const decision = await access.checkResources([
|
|
||||||
* {
|
|
||||||
* resource: {
|
|
||||||
* kind: "document",
|
|
||||||
* id: "1",
|
|
||||||
* attr: { owner: "user@example.com" },
|
|
||||||
* },
|
|
||||||
* actions: ["view", "edit"],
|
|
||||||
* },
|
|
||||||
* {
|
|
||||||
* resource: {
|
|
||||||
* kind: "image",
|
|
||||||
* id: "1",
|
|
||||||
* attr: { owner: "user@example.com" },
|
|
||||||
* },
|
|
||||||
* actions: ["delete"],
|
|
||||||
* },
|
|
||||||
* ]);
|
|
||||||
*
|
|
||||||
* decision.isAllowed({
|
|
||||||
* resource: { kind: "document", id: "1" },
|
|
||||||
* action: "view",
|
|
||||||
* }); // => true
|
|
||||||
*/
|
|
||||||
checkResources(resources: { resource: any; actions: string[] }[]) {
|
|
||||||
return cerbos.checkResources({ principal, resources });
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AccessControlMethods = ReturnType<typeof getAccessControlMethods>;
|
|
||||||
@@ -3,8 +3,8 @@ import "@platform/storage";
|
|||||||
|
|
||||||
import type { Session } from "better-auth";
|
import type { Session } from "better-auth";
|
||||||
|
|
||||||
import type { AccessControlMethods } from "./access.ts";
|
import type { identity } from "./client.ts";
|
||||||
import type { Principal } from "./principal.ts";
|
import type { Principal } from "./models/principal.ts";
|
||||||
|
|
||||||
declare module "@platform/storage" {
|
declare module "@platform/storage" {
|
||||||
interface StorageContext {
|
interface StorageContext {
|
||||||
@@ -21,7 +21,7 @@ declare module "@platform/storage" {
|
|||||||
/**
|
/**
|
||||||
* TODO ...
|
* TODO ...
|
||||||
*/
|
*/
|
||||||
access?: AccessControlMethods;
|
access?: typeof identity.access;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,6 +45,6 @@ declare module "@platform/relay" {
|
|||||||
/**
|
/**
|
||||||
* TODO ...
|
* TODO ...
|
||||||
*/
|
*/
|
||||||
access: AccessControlMethods;
|
access: typeof identity.access;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
export * from "./cerbos.ts";
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@platform/auth",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"private": true,
|
|
||||||
"type": "module",
|
|
||||||
"main": "./mod.ts",
|
|
||||||
"exports": {
|
|
||||||
".": "./mod.ts"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@cerbos/http": "0.23.1",
|
|
||||||
"@platform/logger": "workspace:*",
|
|
||||||
"zod": "4.1.11"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
import type { ZodObject, ZodType } from "zod";
|
import type { ZodObject, ZodType } from "zod";
|
||||||
|
|
||||||
import type { RelayAdapter, RelayInput, RelayResponse } from "./adapter.ts";
|
import type { RelayAdapter, RelayInput, RelayResponse } from "./adapter.ts";
|
||||||
import { Route, type Routes } from "./route.ts";
|
import { Route, type RouteFn, type Routes } from "./route.ts";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory method for generating a new relay client instance.
|
* Factory method for generating a new relay client instance.
|
||||||
@@ -20,6 +20,8 @@ export function makeClient<TRoutes extends Routes>(config: Config, routes: TRout
|
|||||||
const route = routes[key];
|
const route = routes[key];
|
||||||
if (route instanceof Route) {
|
if (route instanceof Route) {
|
||||||
client[key] = getRouteFn(route, config);
|
client[key] = getRouteFn(route, config);
|
||||||
|
} else if (typeof route === "function") {
|
||||||
|
client[key] = route;
|
||||||
} else {
|
} else {
|
||||||
client[key] = getNestedRoute(config, route);
|
client[key] = getNestedRoute(config, route);
|
||||||
}
|
}
|
||||||
@@ -39,6 +41,8 @@ function getNestedRoute<TRoutes extends Routes>(config: Config, routes: TRoutes)
|
|||||||
const route = routes[key];
|
const route = routes[key];
|
||||||
if (route instanceof Route) {
|
if (route instanceof Route) {
|
||||||
nested[key] = getRouteFn(route, config);
|
nested[key] = getRouteFn(route, config);
|
||||||
|
} else if (typeof route === "function") {
|
||||||
|
nested[key] = route;
|
||||||
} else {
|
} else {
|
||||||
nested[key] = getNestedRoute(config, route);
|
nested[key] = getNestedRoute(config, route);
|
||||||
}
|
}
|
||||||
@@ -148,22 +152,26 @@ type RelayRequest = {
|
|||||||
|
|
||||||
type RelayRoutes<TRoutes extends Routes> = {
|
type RelayRoutes<TRoutes extends Routes> = {
|
||||||
[TKey in keyof TRoutes]: TRoutes[TKey] extends Route
|
[TKey in keyof TRoutes]: TRoutes[TKey] extends Route
|
||||||
? HasPayload<TRoutes[TKey]> extends true
|
? ClientRoute<TRoutes[TKey]>
|
||||||
? (
|
: TRoutes[TKey] extends RouteFn
|
||||||
payload: Prettify<
|
? TRoutes[TKey]
|
||||||
(TRoutes[TKey]["state"]["params"] extends ZodObject ? { params: TRoutes[TKey]["$params"] } : {}) &
|
: TRoutes[TKey] extends Routes
|
||||||
(TRoutes[TKey]["state"]["query"] extends ZodObject ? { query: TRoutes[TKey]["$query"] } : {}) &
|
? RelayRoutes<TRoutes[TKey]>
|
||||||
(TRoutes[TKey]["state"]["body"] extends ZodType ? { body: TRoutes[TKey]["$body"] } : {}) & {
|
: never;
|
||||||
headers?: HeadersInit;
|
|
||||||
}
|
|
||||||
>,
|
|
||||||
) => RouteResponse<TRoutes[TKey]>
|
|
||||||
: (payload?: { headers: HeadersInit }) => RouteResponse<TRoutes[TKey]>
|
|
||||||
: TRoutes[TKey] extends Routes
|
|
||||||
? RelayRoutes<TRoutes[TKey]>
|
|
||||||
: never;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ClientRoute<TRoute extends Route> = HasPayload<TRoute> extends true
|
||||||
|
? (
|
||||||
|
payload: Prettify<
|
||||||
|
(TRoute["state"]["params"] extends ZodObject ? { params: TRoute["$params"] } : {}) &
|
||||||
|
(TRoute["state"]["query"] extends ZodObject ? { query: TRoute["$query"] } : {}) &
|
||||||
|
(TRoute["state"]["body"] extends ZodType ? { body: TRoute["$body"] } : {}) & {
|
||||||
|
headers?: HeadersInit;
|
||||||
|
}
|
||||||
|
>,
|
||||||
|
) => RouteResponse<TRoute>
|
||||||
|
: (payload?: { headers: HeadersInit }) => RouteResponse<TRoute>;
|
||||||
|
|
||||||
type HasPayload<TRoute extends Route> = TRoute["state"]["params"] extends ZodObject
|
type HasPayload<TRoute extends Route> = TRoute["state"]["params"] extends ZodObject
|
||||||
? true
|
? true
|
||||||
: TRoute["state"]["query"] extends ZodObject
|
: TRoute["state"]["query"] extends ZodObject
|
||||||
|
|||||||
@@ -443,9 +443,11 @@ export const route: {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export type Routes = {
|
export type Routes = {
|
||||||
[key: string]: Routes | Route;
|
[key: string]: Routes | Route | RouteFn;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type RouteFn = (...args: any[]) => any;
|
||||||
|
|
||||||
type RouteState = {
|
type RouteState = {
|
||||||
method: RouteMethod;
|
method: RouteMethod;
|
||||||
path: string;
|
path: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user