@svadmin/cerbos 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +48 -4
- package/src/cerbos-access-control.ts +148 -0
- package/src/index.ts +8 -0
- package/README.md +0 -3
package/package.json
CHANGED
|
@@ -1,6 +1,50 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@svadmin/cerbos",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Cerbos Cloud-Native Policy PDP AccessControl adapter for svadmin",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"files": [
|
|
8
|
+
"src/**/*.ts",
|
|
9
|
+
"!src/**/*.test.*",
|
|
10
|
+
"!src/**/*.spec.*"
|
|
11
|
+
],
|
|
12
|
+
"main": "src/index.ts",
|
|
13
|
+
"types": "src/index.ts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./src/index.ts",
|
|
17
|
+
"default": "./src/index.ts"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"@svadmin/core": "^0.79.0"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"svadmin",
|
|
25
|
+
"cerbos",
|
|
26
|
+
"access-control",
|
|
27
|
+
"authorization",
|
|
28
|
+
"pdp",
|
|
29
|
+
"policies"
|
|
30
|
+
],
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"author": "zuohuadong",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/vibeunion/svadmin.git",
|
|
36
|
+
"directory": "packages/cerbos"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://github.com/vibeunion/svadmin#readme",
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/vibeunion/svadmin/issues"
|
|
41
|
+
},
|
|
42
|
+
"svadmin": {
|
|
43
|
+
"capabilities": [
|
|
44
|
+
"access-control"
|
|
45
|
+
],
|
|
46
|
+
"core": "^0.55.0",
|
|
47
|
+
"stability": "stable",
|
|
48
|
+
"migration": null
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import type { AccessControlProvider, CanParams, CanResult } from '@svadmin/core';
|
|
2
|
+
|
|
3
|
+
export interface CerbosPrincipal {
|
|
4
|
+
id: string;
|
|
5
|
+
roles: string[];
|
|
6
|
+
attr?: Record<string, unknown>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface CerbosResource {
|
|
10
|
+
kind: string;
|
|
11
|
+
id: string;
|
|
12
|
+
attr?: Record<string, unknown>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface CerbosCheckResponseLike {
|
|
16
|
+
isAllowed(action: string): boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface CerbosClientLike {
|
|
20
|
+
checkResource(params: {
|
|
21
|
+
principal: CerbosPrincipal;
|
|
22
|
+
resource: CerbosResource;
|
|
23
|
+
actions: string[];
|
|
24
|
+
}): Promise<CerbosCheckResponseLike>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface CerbosAccessControlOptions {
|
|
28
|
+
/** Initialized Cerbos Client instance */
|
|
29
|
+
client?: CerbosClientLike;
|
|
30
|
+
/** Cerbos PDP HTTP API base URL (e.g. 'http://localhost:3592') */
|
|
31
|
+
apiUrl?: string;
|
|
32
|
+
/** Optional bearer or access token for PDP communication */
|
|
33
|
+
token?: string;
|
|
34
|
+
/** Resolver returning current principal credentials and attributes */
|
|
35
|
+
principal: () => CerbosPrincipal | Promise<CerbosPrincipal>;
|
|
36
|
+
/** Optional custom fetch implementation */
|
|
37
|
+
fetch?: typeof fetch;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface CerbosHttpCheckResponse {
|
|
41
|
+
results?: Array<{
|
|
42
|
+
actions?: Record<string, 'EFFECT_ALLOW' | 'EFFECT_DENY'>;
|
|
43
|
+
}>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Creates a Cerbos PDP AccessControl adapter for svadmin.
|
|
48
|
+
* Evaluates fine-grained authorization against Cerbos policies.
|
|
49
|
+
*/
|
|
50
|
+
export function createCerbosAccessControl(
|
|
51
|
+
options: CerbosAccessControlOptions,
|
|
52
|
+
): AccessControlProvider {
|
|
53
|
+
const request = options.fetch ?? fetch;
|
|
54
|
+
|
|
55
|
+
async function checkViaHttp(
|
|
56
|
+
principal: CerbosPrincipal,
|
|
57
|
+
resource: CerbosResource,
|
|
58
|
+
action: string,
|
|
59
|
+
): Promise<boolean> {
|
|
60
|
+
if (!options.apiUrl) {
|
|
61
|
+
throw new Error('Cerbos adapter requires either options.client or options.apiUrl');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const endpoint = `${options.apiUrl.replace(/\/+$/, '')}/api/check/resources`;
|
|
65
|
+
const headers: Record<string, string> = {
|
|
66
|
+
'Content-Type': 'application/json',
|
|
67
|
+
Accept: 'application/json',
|
|
68
|
+
};
|
|
69
|
+
if (options.token) {
|
|
70
|
+
headers['Authorization'] = `Bearer ${options.token}`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const bodyPayload = {
|
|
74
|
+
principal: {
|
|
75
|
+
id: principal.id,
|
|
76
|
+
roles: principal.roles,
|
|
77
|
+
attr: principal.attr ?? {},
|
|
78
|
+
},
|
|
79
|
+
resources: [
|
|
80
|
+
{
|
|
81
|
+
resource: {
|
|
82
|
+
kind: resource.kind,
|
|
83
|
+
id: resource.id,
|
|
84
|
+
attr: resource.attr ?? {},
|
|
85
|
+
},
|
|
86
|
+
actions: [action],
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const res = await request(endpoint, {
|
|
92
|
+
method: 'POST',
|
|
93
|
+
headers,
|
|
94
|
+
body: JSON.stringify(bodyPayload),
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
if (!res.ok) {
|
|
98
|
+
throw new Error(`Cerbos PDP responded with status ${res.status}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const data = (await res.json()) as CerbosHttpCheckResponse;
|
|
102
|
+
const actionResult = data.results?.[0]?.actions?.[action];
|
|
103
|
+
return actionResult === 'EFFECT_ALLOW';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
async can(params: CanParams): Promise<CanResult> {
|
|
108
|
+
try {
|
|
109
|
+
const principal = await options.principal();
|
|
110
|
+
const resourceId = params.params?.id !== undefined ? String(params.params.id) : '*';
|
|
111
|
+
const resource: CerbosResource = {
|
|
112
|
+
kind: params.resource,
|
|
113
|
+
id: resourceId,
|
|
114
|
+
};
|
|
115
|
+
if (params.params && typeof params.params === 'object') {
|
|
116
|
+
resource.attr = params.params;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
let allowed = false;
|
|
120
|
+
|
|
121
|
+
if (options.client) {
|
|
122
|
+
const checkRes = await options.client.checkResource({
|
|
123
|
+
principal,
|
|
124
|
+
resource,
|
|
125
|
+
actions: [params.action],
|
|
126
|
+
});
|
|
127
|
+
allowed = checkRes.isAllowed(params.action);
|
|
128
|
+
} else {
|
|
129
|
+
allowed = await checkViaHttp(principal, resource, params.action);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (allowed) {
|
|
133
|
+
return { can: true };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
can: false,
|
|
138
|
+
reason: `Cerbos policy denied action "${params.action}" on resource "${params.resource}"`,
|
|
139
|
+
};
|
|
140
|
+
} catch {
|
|
141
|
+
return {
|
|
142
|
+
can: false,
|
|
143
|
+
reason: 'Cerbos authorization error',
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|
package/src/index.ts
ADDED
package/README.md
DELETED