@cfast/permissions 0.0.1 → 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/dist/{chunk-I35WLWUH.js → chunk-NCLN5YBQ.js} +4 -1
- package/dist/{client-CJBFS0IS.d.ts → client-C7xdwHTk.d.ts} +76 -19
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/index.d.ts +105 -13
- package/dist/index.js +26 -7
- package/llms.txt +233 -0
- package/package.json +10 -2
- package/dist/chunk-FVAAEIAE.js +0 -32
- package/dist/chunk-IPYPD2CZ.js +0 -53
- package/dist/chunk-PNHVTXCZ.js +0 -39
- package/dist/chunk-YYYHMPTS.js +0 -33
- package/dist/chunk-ZTQJZJFW.js +0 -38
- package/dist/client-8HHp1rPO.d.ts +0 -62
- package/dist/client-BBcryLDZ.d.ts +0 -61
- package/dist/client-BKD8jH5P.d.ts +0 -56
- package/dist/client-ChpyEV1r.d.ts +0 -62
- package/dist/client-CuB6igvw.d.ts +0 -195
- package/dist/client-DduKFZmk.d.ts +0 -59
- package/dist/client-FbortuK3.d.ts +0 -53
package/llms.txt
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# @cfast/permissions
|
|
2
|
+
|
|
3
|
+
> Isomorphic, Drizzle-native permission system. Define permissions once, enforce as SQL WHERE clauses.
|
|
4
|
+
|
|
5
|
+
## When to use
|
|
6
|
+
Use this package to define role-based permissions that compile down to Drizzle `where` clauses for Cloudflare D1. Permissions defined here are consumed by `@cfast/db` for server enforcement and `@cfast/actions` for client-side UI introspection.
|
|
7
|
+
|
|
8
|
+
## Key concepts
|
|
9
|
+
- **Permissions are WHERE clauses**: A read permission becomes a SQL filter. You don't write a check then repeat the logic in your query.
|
|
10
|
+
- **Two layers**: (1) Structural -- "does this role have any grant for update on posts?" (2) Row-level -- "does the grant's WHERE clause include this specific row?" Structural checks are cheap and work client-side. Row-level checks happen at execution time.
|
|
11
|
+
- **`"manage"` = all CRUD**: A shorthand for granting `read`, `create`, `update`, `delete`.
|
|
12
|
+
- **`"all"` = every table**: `grant("manage", "all")` grants full access to everything.
|
|
13
|
+
- **Role hierarchy**: Roles can inherit from parent roles. Multiple WHERE clauses on the same action+table are OR'd (more permissive grant wins).
|
|
14
|
+
|
|
15
|
+
## API Reference
|
|
16
|
+
|
|
17
|
+
### `definePermissions(config): Permissions`
|
|
18
|
+
```typescript
|
|
19
|
+
import { definePermissions, grant } from "@cfast/permissions";
|
|
20
|
+
|
|
21
|
+
const permissions = definePermissions({
|
|
22
|
+
roles: ["anonymous", "user", "admin"] as const,
|
|
23
|
+
grants: {
|
|
24
|
+
anonymous: [ grant("read", posts, { where: (post) => eq(post.published, true) }) ],
|
|
25
|
+
user: [ grant("read", posts), grant("create", posts) ],
|
|
26
|
+
admin: [ grant("manage", "all") ],
|
|
27
|
+
},
|
|
28
|
+
hierarchy: { // optional
|
|
29
|
+
user: ["anonymous"], // user inherits anonymous grants
|
|
30
|
+
admin: ["user"], // admin inherits user grants
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Curried form** for typed user in WHERE clauses:
|
|
36
|
+
```typescript
|
|
37
|
+
const permissions = definePermissions<MyUser>()({
|
|
38
|
+
roles: [...] as const,
|
|
39
|
+
grants: (grant) => ({
|
|
40
|
+
user: [ grant("update", posts, { where: (post, user) => eq(post.authorId, user.id) }) ],
|
|
41
|
+
// ...
|
|
42
|
+
}),
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Curried form with schema** to constrain string subjects to known table names at compile time:
|
|
47
|
+
```typescript
|
|
48
|
+
import * as schema from "./schema";
|
|
49
|
+
|
|
50
|
+
const permissions = definePermissions<MyUser, typeof schema>()({
|
|
51
|
+
roles: ["member", "admin"] as const,
|
|
52
|
+
grants: (grant) => ({
|
|
53
|
+
member: [
|
|
54
|
+
grant("read", "projects"), // ✓ string form, type-checked
|
|
55
|
+
grant("read", schema.projects), // ✓ object form still works
|
|
56
|
+
// grant("read", "unknownTable"), // ✗ TypeScript error
|
|
57
|
+
],
|
|
58
|
+
admin: [grant("manage", "all")],
|
|
59
|
+
}),
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Returns** `Permissions<TRoles>`:
|
|
64
|
+
```typescript
|
|
65
|
+
type Permissions<TRoles> = {
|
|
66
|
+
roles: TRoles;
|
|
67
|
+
grants: Record<TRoles[number], Grant[]>;
|
|
68
|
+
resolvedGrants: Record<TRoles[number], Grant[]>; // hierarchy-expanded
|
|
69
|
+
};
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### `grant(action, subject, options?): Grant`
|
|
73
|
+
```typescript
|
|
74
|
+
grant(action: PermissionAction, subject: DrizzleTable | string, options?: { where?: WhereClause }): Grant
|
|
75
|
+
```
|
|
76
|
+
- `PermissionAction`: `"read" | "create" | "update" | "delete" | "manage"`
|
|
77
|
+
- `WhereClause`: `(columns, user) => DrizzleSQL | undefined`
|
|
78
|
+
- `subject` may be a Drizzle table object, a string table name (e.g. `"projects"`), or `"all"`. String and object forms are interchangeable at runtime — both are normalized to the same key by `getTableName()`. To get compile-time validation that a string subject is a known table, use the `definePermissions<User, typeof schema>()` curried form.
|
|
79
|
+
|
|
80
|
+
### `checkPermissions(role, permissions, descriptors): PermissionCheckResult`
|
|
81
|
+
```typescript
|
|
82
|
+
import { checkPermissions } from "@cfast/permissions";
|
|
83
|
+
|
|
84
|
+
const result = checkPermissions("user", permissions, [
|
|
85
|
+
{ action: "update", table: posts }, // object form
|
|
86
|
+
{ action: "create", table: "comments" }, // string form also accepted
|
|
87
|
+
]);
|
|
88
|
+
result.permitted; // boolean
|
|
89
|
+
result.denied; // PermissionDescriptor[]
|
|
90
|
+
result.reasons; // string[]
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### `resolveGrants(permissions, userOrRoles): Grant[]`
|
|
94
|
+
Merges grants from multiple roles into a flat array. Deduplicates by action+subject and OR-merges WHERE clauses.
|
|
95
|
+
|
|
96
|
+
Two calling styles are supported (both equivalent):
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
// Preferred: pass the user directly — the function extracts user.roles
|
|
100
|
+
const grants = resolveGrants(permissions, user);
|
|
101
|
+
|
|
102
|
+
// Legacy: pass the roles array yourself (still supported)
|
|
103
|
+
const grants = resolveGrants(permissions, user.roles);
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
The user-object form accepts any value with a `roles: readonly string[]` field, so it works directly with the user shape from `@cfast/auth` without manual extraction.
|
|
107
|
+
|
|
108
|
+
### `ForbiddenError`
|
|
109
|
+
```typescript
|
|
110
|
+
import { ForbiddenError } from "@cfast/permissions";
|
|
111
|
+
|
|
112
|
+
class ForbiddenError extends Error {
|
|
113
|
+
readonly action: PermissionAction;
|
|
114
|
+
readonly table: DrizzleTable;
|
|
115
|
+
readonly role: string | undefined;
|
|
116
|
+
readonly descriptors: PermissionDescriptor[];
|
|
117
|
+
toJSON(): { name, message, action, table, role };
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### `can(grants, action, table): boolean`
|
|
122
|
+
```typescript
|
|
123
|
+
import { can } from "@cfast/permissions";
|
|
124
|
+
function can<TTables extends Record<string, DrizzleTable> = Record<string, DrizzleTable>>(
|
|
125
|
+
grants: Grant[],
|
|
126
|
+
action: PermissionAction,
|
|
127
|
+
table: DrizzleTable | string,
|
|
128
|
+
): boolean
|
|
129
|
+
```
|
|
130
|
+
Quick grant-level permission check that works directly with resolved grants. Unlike `checkPermissions` (which takes a role string + full permissions object), `can` works with the user's resolved grants -- the same grants available in loaders, actions, and client-side. Returns `true` if any grant permits the action on the table (including `manage`/`all` wildcards).
|
|
131
|
+
|
|
132
|
+
Accepts either a Drizzle table object or a string table name. To get compile-time validation that a string is an actual table, supply the schema map as a generic argument.
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
import * as schema from "../db/schema";
|
|
136
|
+
|
|
137
|
+
can(grants, "create", schema.posts) // ✓ object form
|
|
138
|
+
can(grants, "create", "posts") // ✓ string form (untyped)
|
|
139
|
+
can<typeof schema>(grants, "create", "posts") // ✓ string form, type-checked
|
|
140
|
+
// can<typeof schema>(grants, "create", "unknownTable") // ✗ TypeScript error
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### `CRUD_ACTIONS`
|
|
144
|
+
```typescript
|
|
145
|
+
const CRUD_ACTIONS: readonly CrudAction[] = ["read", "create", "update", "delete"];
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Client entrypoint
|
|
149
|
+
```typescript
|
|
150
|
+
import { ForbiddenError, can } from "@cfast/permissions/client";
|
|
151
|
+
import type { PermissionAction, CrudAction, PermissionDescriptor, PermissionCheckResult } from "@cfast/permissions/client";
|
|
152
|
+
```
|
|
153
|
+
Use this in client bundles to avoid importing server-only code. `can` is available from both the main and client entrypoints.
|
|
154
|
+
|
|
155
|
+
## Usage Examples
|
|
156
|
+
|
|
157
|
+
### Ownership-based permissions with hierarchy
|
|
158
|
+
```typescript
|
|
159
|
+
import { definePermissions, grant } from "@cfast/permissions";
|
|
160
|
+
import { eq } from "drizzle-orm";
|
|
161
|
+
import { posts, comments } from "./schema";
|
|
162
|
+
|
|
163
|
+
export const permissions = definePermissions({
|
|
164
|
+
roles: ["anonymous", "user", "editor", "admin"] as const,
|
|
165
|
+
hierarchy: {
|
|
166
|
+
user: ["anonymous"],
|
|
167
|
+
editor: ["user"],
|
|
168
|
+
admin: ["editor"],
|
|
169
|
+
},
|
|
170
|
+
grants: {
|
|
171
|
+
anonymous: [
|
|
172
|
+
grant("read", posts, { where: (p) => eq(p.published, true) }),
|
|
173
|
+
],
|
|
174
|
+
user: [
|
|
175
|
+
grant("create", posts),
|
|
176
|
+
grant("update", posts, { where: (p, u) => eq(p.authorId, u.id) }),
|
|
177
|
+
grant("delete", posts, { where: (p, u) => eq(p.authorId, u.id) }),
|
|
178
|
+
],
|
|
179
|
+
editor: [
|
|
180
|
+
grant("read", posts), // unrestricted overrides inherited "published only"
|
|
181
|
+
grant("update", posts),
|
|
182
|
+
grant("delete", posts),
|
|
183
|
+
],
|
|
184
|
+
admin: [
|
|
185
|
+
grant("manage", "all"),
|
|
186
|
+
],
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Quick permission check with `can()`
|
|
192
|
+
```typescript
|
|
193
|
+
import { can } from "@cfast/permissions";
|
|
194
|
+
|
|
195
|
+
// In a loader/action -- grants come from auth.requireUser()
|
|
196
|
+
const { grants } = await auth.requireUser(request);
|
|
197
|
+
if (can(grants, "update", posts)) {
|
|
198
|
+
// user has structural permission to update posts
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// In a React component -- grants come from loader data
|
|
202
|
+
function PostCard({ post, grants }) {
|
|
203
|
+
return (
|
|
204
|
+
<div>
|
|
205
|
+
<h2>{post.title}</h2>
|
|
206
|
+
{can(grants, "update", posts) && <Button>Edit</Button>}
|
|
207
|
+
{can(grants, "delete", posts) && <Button color="danger">Delete</Button>}
|
|
208
|
+
</div>
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Checking permissions manually
|
|
214
|
+
```typescript
|
|
215
|
+
const result = checkPermissions("user", permissions, [
|
|
216
|
+
{ action: "delete", table: posts },
|
|
217
|
+
]);
|
|
218
|
+
if (!result.permitted) {
|
|
219
|
+
console.log(result.reasons); // ["Role 'user' cannot delete on 'posts'"] -- if no grant exists
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Integration
|
|
224
|
+
- **`@cfast/db`**: Pass `permissions` to `createDb()`. Operations auto-enforce permissions.
|
|
225
|
+
- **`@cfast/actions`**: Actions extract `PermissionDescriptor[]` from operations for client-side `permitted` booleans.
|
|
226
|
+
- **`@cfast/admin`**: Admin CRUD goes through the same permission system.
|
|
227
|
+
|
|
228
|
+
## Common Mistakes
|
|
229
|
+
- **Forgetting `as const` on roles array** -- without it, TypeScript infers `string[]` and you lose type checking on grant keys.
|
|
230
|
+
- **Expecting WHERE on `"create"` grants** -- create is boolean (can/can't). There's no existing row to filter.
|
|
231
|
+
- **Circular hierarchy** -- detected at runtime and throws `Error`. E.g., A inherits B inherits A.
|
|
232
|
+
- **Using `checkPermissions` for row-level security** -- it only does structural checks (action + table). Row-level WHERE enforcement happens at execution time in `@cfast/db`.
|
|
233
|
+
- **Importing `definePermissions` in client bundles** -- use `@cfast/permissions/client` instead to keep the bundle small.
|
package/package.json
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cfast/permissions",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Isomorphic, composable permission system with Drizzle-native row-level access control",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"cfast",
|
|
7
|
+
"cloudflare-workers",
|
|
8
|
+
"permissions",
|
|
9
|
+
"rbac",
|
|
10
|
+
"access-control"
|
|
11
|
+
],
|
|
5
12
|
"license": "MIT",
|
|
6
13
|
"repository": {
|
|
7
14
|
"type": "git",
|
|
@@ -22,7 +29,8 @@
|
|
|
22
29
|
}
|
|
23
30
|
},
|
|
24
31
|
"files": [
|
|
25
|
-
"dist"
|
|
32
|
+
"dist",
|
|
33
|
+
"llms.txt"
|
|
26
34
|
],
|
|
27
35
|
"sideEffects": false,
|
|
28
36
|
"publishConfig": {
|
package/dist/chunk-FVAAEIAE.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
// src/errors.ts
|
|
2
|
-
function getTableName(table) {
|
|
3
|
-
return table._?.name ?? "unknown";
|
|
4
|
-
}
|
|
5
|
-
var ForbiddenError = class extends Error {
|
|
6
|
-
action;
|
|
7
|
-
table;
|
|
8
|
-
role;
|
|
9
|
-
descriptors;
|
|
10
|
-
constructor(options) {
|
|
11
|
-
const tableName = getTableName(options.table);
|
|
12
|
-
super(`Role '${options.role}' cannot ${options.action} on '${tableName}'`);
|
|
13
|
-
this.name = "ForbiddenError";
|
|
14
|
-
this.action = options.action;
|
|
15
|
-
this.table = options.table;
|
|
16
|
-
this.role = options.role;
|
|
17
|
-
this.descriptors = options.descriptors ?? [];
|
|
18
|
-
}
|
|
19
|
-
toJSON() {
|
|
20
|
-
return {
|
|
21
|
-
name: this.name,
|
|
22
|
-
message: this.message,
|
|
23
|
-
action: this.action,
|
|
24
|
-
table: getTableName(this.table),
|
|
25
|
-
role: this.role
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
export {
|
|
31
|
-
ForbiddenError
|
|
32
|
-
};
|
package/dist/chunk-IPYPD2CZ.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
// src/types.ts
|
|
2
|
-
var DRIZZLE_NAME_SYMBOL = /* @__PURE__ */ Symbol.for("drizzle:Name");
|
|
3
|
-
function getTableName(table) {
|
|
4
|
-
return table[DRIZZLE_NAME_SYMBOL] ?? "unknown";
|
|
5
|
-
}
|
|
6
|
-
var CRUD_ACTIONS = ["read", "create", "update", "delete"];
|
|
7
|
-
|
|
8
|
-
// src/errors.ts
|
|
9
|
-
var ForbiddenError = class extends Error {
|
|
10
|
-
/** The action that was denied (e.g., `"delete"`). */
|
|
11
|
-
action;
|
|
12
|
-
/** The Drizzle table the action targeted. */
|
|
13
|
-
table;
|
|
14
|
-
/** The role that lacked the permission, or `undefined` if not specified. */
|
|
15
|
-
role;
|
|
16
|
-
/** The full list of permission descriptors that were checked. */
|
|
17
|
-
descriptors;
|
|
18
|
-
/**
|
|
19
|
-
* Creates a new `ForbiddenError`.
|
|
20
|
-
*
|
|
21
|
-
* @param options - The action, table, and optional role/descriptors for the error.
|
|
22
|
-
*/
|
|
23
|
-
constructor(options) {
|
|
24
|
-
const tableName = getTableName(options.table);
|
|
25
|
-
const msg = options.role ? `Role '${options.role}' cannot ${options.action} on '${tableName}'` : `Cannot ${options.action} on '${tableName}'`;
|
|
26
|
-
super(msg);
|
|
27
|
-
this.name = "ForbiddenError";
|
|
28
|
-
this.action = options.action;
|
|
29
|
-
this.table = options.table;
|
|
30
|
-
this.role = options.role;
|
|
31
|
-
this.descriptors = options.descriptors ?? [];
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Serializes the error to a JSON-safe object for server-to-client transfer.
|
|
35
|
-
*
|
|
36
|
-
* @returns A plain object with `name`, `message`, `action`, `table`, and `role` fields.
|
|
37
|
-
*/
|
|
38
|
-
toJSON() {
|
|
39
|
-
return {
|
|
40
|
-
name: this.name,
|
|
41
|
-
message: this.message,
|
|
42
|
-
action: this.action,
|
|
43
|
-
table: getTableName(this.table),
|
|
44
|
-
role: this.role
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
export {
|
|
50
|
-
getTableName,
|
|
51
|
-
CRUD_ACTIONS,
|
|
52
|
-
ForbiddenError
|
|
53
|
-
};
|
package/dist/chunk-PNHVTXCZ.js
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
// src/types.ts
|
|
2
|
-
var DRIZZLE_NAME_SYMBOL = /* @__PURE__ */ Symbol.for("drizzle:Name");
|
|
3
|
-
function getTableName(table) {
|
|
4
|
-
return table[DRIZZLE_NAME_SYMBOL] ?? "unknown";
|
|
5
|
-
}
|
|
6
|
-
var CRUD_ACTIONS = ["read", "create", "update", "delete"];
|
|
7
|
-
|
|
8
|
-
// src/errors.ts
|
|
9
|
-
var ForbiddenError = class extends Error {
|
|
10
|
-
action;
|
|
11
|
-
table;
|
|
12
|
-
role;
|
|
13
|
-
descriptors;
|
|
14
|
-
constructor(options) {
|
|
15
|
-
const tableName = getTableName(options.table);
|
|
16
|
-
const msg = options.role ? `Role '${options.role}' cannot ${options.action} on '${tableName}'` : `Cannot ${options.action} on '${tableName}'`;
|
|
17
|
-
super(msg);
|
|
18
|
-
this.name = "ForbiddenError";
|
|
19
|
-
this.action = options.action;
|
|
20
|
-
this.table = options.table;
|
|
21
|
-
this.role = options.role;
|
|
22
|
-
this.descriptors = options.descriptors ?? [];
|
|
23
|
-
}
|
|
24
|
-
toJSON() {
|
|
25
|
-
return {
|
|
26
|
-
name: this.name,
|
|
27
|
-
message: this.message,
|
|
28
|
-
action: this.action,
|
|
29
|
-
table: getTableName(this.table),
|
|
30
|
-
role: this.role
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
export {
|
|
36
|
-
getTableName,
|
|
37
|
-
CRUD_ACTIONS,
|
|
38
|
-
ForbiddenError
|
|
39
|
-
};
|
package/dist/chunk-YYYHMPTS.js
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
// src/errors.ts
|
|
2
|
-
function getTableName(table) {
|
|
3
|
-
return table._?.name ?? "unknown";
|
|
4
|
-
}
|
|
5
|
-
var ForbiddenError = class extends Error {
|
|
6
|
-
action;
|
|
7
|
-
table;
|
|
8
|
-
role;
|
|
9
|
-
descriptors;
|
|
10
|
-
constructor(options) {
|
|
11
|
-
const tableName = getTableName(options.table);
|
|
12
|
-
const msg = options.role ? `Role '${options.role}' cannot ${options.action} on '${tableName}'` : `Cannot ${options.action} on '${tableName}'`;
|
|
13
|
-
super(msg);
|
|
14
|
-
this.name = "ForbiddenError";
|
|
15
|
-
this.action = options.action;
|
|
16
|
-
this.table = options.table;
|
|
17
|
-
this.role = options.role;
|
|
18
|
-
this.descriptors = options.descriptors ?? [];
|
|
19
|
-
}
|
|
20
|
-
toJSON() {
|
|
21
|
-
return {
|
|
22
|
-
name: this.name,
|
|
23
|
-
message: this.message,
|
|
24
|
-
action: this.action,
|
|
25
|
-
table: getTableName(this.table),
|
|
26
|
-
role: this.role
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
export {
|
|
32
|
-
ForbiddenError
|
|
33
|
-
};
|
package/dist/chunk-ZTQJZJFW.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
// src/types.ts
|
|
2
|
-
function getTableName(table) {
|
|
3
|
-
return table._?.name ?? "unknown";
|
|
4
|
-
}
|
|
5
|
-
var CRUD_ACTIONS = ["read", "create", "update", "delete"];
|
|
6
|
-
|
|
7
|
-
// src/errors.ts
|
|
8
|
-
var ForbiddenError = class extends Error {
|
|
9
|
-
action;
|
|
10
|
-
table;
|
|
11
|
-
role;
|
|
12
|
-
descriptors;
|
|
13
|
-
constructor(options) {
|
|
14
|
-
const tableName = getTableName(options.table);
|
|
15
|
-
const msg = options.role ? `Role '${options.role}' cannot ${options.action} on '${tableName}'` : `Cannot ${options.action} on '${tableName}'`;
|
|
16
|
-
super(msg);
|
|
17
|
-
this.name = "ForbiddenError";
|
|
18
|
-
this.action = options.action;
|
|
19
|
-
this.table = options.table;
|
|
20
|
-
this.role = options.role;
|
|
21
|
-
this.descriptors = options.descriptors ?? [];
|
|
22
|
-
}
|
|
23
|
-
toJSON() {
|
|
24
|
-
return {
|
|
25
|
-
name: this.name,
|
|
26
|
-
message: this.message,
|
|
27
|
-
action: this.action,
|
|
28
|
-
table: getTableName(this.table),
|
|
29
|
-
role: this.role
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
export {
|
|
35
|
-
getTableName,
|
|
36
|
-
CRUD_ACTIONS,
|
|
37
|
-
ForbiddenError
|
|
38
|
-
};
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
type DrizzleTable = {
|
|
2
|
-
_: {
|
|
3
|
-
name: string;
|
|
4
|
-
};
|
|
5
|
-
};
|
|
6
|
-
type DrizzleSQL = {
|
|
7
|
-
getSQL(): unknown;
|
|
8
|
-
};
|
|
9
|
-
type PermissionAction = "read" | "create" | "update" | "delete" | "manage";
|
|
10
|
-
type CrudAction = Exclude<PermissionAction, "manage">;
|
|
11
|
-
declare const CRUD_ACTIONS: readonly CrudAction[];
|
|
12
|
-
type WhereClause = (columns: Record<string, unknown>, user: any) => DrizzleSQL | undefined;
|
|
13
|
-
type Grant = {
|
|
14
|
-
action: PermissionAction;
|
|
15
|
-
subject: DrizzleTable | "all";
|
|
16
|
-
where?: WhereClause;
|
|
17
|
-
};
|
|
18
|
-
type GrantFn<TUser> = (action: PermissionAction, subject: DrizzleTable | "all", options?: {
|
|
19
|
-
where?: (columns: Record<string, unknown>, user: TUser) => DrizzleSQL | undefined;
|
|
20
|
-
}) => Grant;
|
|
21
|
-
type PermissionDescriptor = {
|
|
22
|
-
action: PermissionAction;
|
|
23
|
-
table: DrizzleTable;
|
|
24
|
-
};
|
|
25
|
-
type PermissionCheckResult = {
|
|
26
|
-
permitted: boolean;
|
|
27
|
-
denied: PermissionDescriptor[];
|
|
28
|
-
reasons: string[];
|
|
29
|
-
};
|
|
30
|
-
type PermissionsConfig<TRoles extends readonly string[], TUser = unknown> = {
|
|
31
|
-
roles: TRoles;
|
|
32
|
-
grants: Record<TRoles[number], Grant[]> | ((grant: GrantFn<TUser>) => Record<TRoles[number], Grant[]>);
|
|
33
|
-
hierarchy?: Partial<Record<TRoles[number], TRoles[number][]>>;
|
|
34
|
-
};
|
|
35
|
-
type Permissions<TRoles extends readonly string[] = readonly string[]> = {
|
|
36
|
-
roles: TRoles;
|
|
37
|
-
grants: Record<TRoles[number], Grant[]>;
|
|
38
|
-
resolvedGrants: Record<TRoles[number], Grant[]>;
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
type ForbiddenErrorOptions = {
|
|
42
|
-
action: PermissionAction;
|
|
43
|
-
table: DrizzleTable;
|
|
44
|
-
role?: string;
|
|
45
|
-
descriptors?: PermissionDescriptor[];
|
|
46
|
-
};
|
|
47
|
-
declare class ForbiddenError extends Error {
|
|
48
|
-
readonly action: PermissionAction;
|
|
49
|
-
readonly table: DrizzleTable;
|
|
50
|
-
readonly role: string | undefined;
|
|
51
|
-
readonly descriptors: PermissionDescriptor[];
|
|
52
|
-
constructor(options: ForbiddenErrorOptions);
|
|
53
|
-
toJSON(): {
|
|
54
|
-
name: string;
|
|
55
|
-
message: string;
|
|
56
|
-
action: PermissionAction;
|
|
57
|
-
table: string;
|
|
58
|
-
role: string | undefined;
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export { CRUD_ACTIONS as C, type DrizzleTable as D, ForbiddenError as F, type Grant as G, type PermissionsConfig as P, type WhereClause as W, type Permissions as a, type PermissionAction as b, type PermissionDescriptor as c, type PermissionCheckResult as d, type CrudAction as e, type GrantFn as f };
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { SQL } from 'drizzle-orm';
|
|
2
|
-
|
|
3
|
-
type DrizzleTable = {
|
|
4
|
-
_: {
|
|
5
|
-
name: string;
|
|
6
|
-
};
|
|
7
|
-
};
|
|
8
|
-
type PermissionAction = "read" | "create" | "update" | "delete" | "manage";
|
|
9
|
-
type CrudAction = Exclude<PermissionAction, "manage">;
|
|
10
|
-
declare const CRUD_ACTIONS: readonly CrudAction[];
|
|
11
|
-
type WhereClause = (columns: Record<string, unknown>, user: any) => SQL | undefined;
|
|
12
|
-
type Grant = {
|
|
13
|
-
action: PermissionAction;
|
|
14
|
-
subject: DrizzleTable | "all";
|
|
15
|
-
where?: WhereClause;
|
|
16
|
-
};
|
|
17
|
-
type GrantFn<TUser> = (action: PermissionAction, subject: DrizzleTable | "all", options?: {
|
|
18
|
-
where?: (columns: Record<string, unknown>, user: TUser) => SQL | undefined;
|
|
19
|
-
}) => Grant;
|
|
20
|
-
type PermissionDescriptor = {
|
|
21
|
-
action: PermissionAction;
|
|
22
|
-
table: DrizzleTable;
|
|
23
|
-
};
|
|
24
|
-
type PermissionCheckResult = {
|
|
25
|
-
permitted: boolean;
|
|
26
|
-
denied: PermissionDescriptor[];
|
|
27
|
-
reasons: string[];
|
|
28
|
-
};
|
|
29
|
-
type PermissionsConfig<TRoles extends readonly string[], TUser = unknown> = {
|
|
30
|
-
roles: TRoles;
|
|
31
|
-
grants: Record<TRoles[number], Grant[]> | ((grant: GrantFn<TUser>) => Record<TRoles[number], Grant[]>);
|
|
32
|
-
hierarchy?: Partial<Record<TRoles[number], TRoles[number][]>>;
|
|
33
|
-
};
|
|
34
|
-
type Permissions<TRoles extends readonly string[] = readonly string[]> = {
|
|
35
|
-
roles: TRoles;
|
|
36
|
-
grants: Record<TRoles[number], Grant[]>;
|
|
37
|
-
resolvedGrants: Record<TRoles[number], Grant[]>;
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
type ForbiddenErrorOptions = {
|
|
41
|
-
action: PermissionAction;
|
|
42
|
-
table: DrizzleTable;
|
|
43
|
-
role: string;
|
|
44
|
-
descriptors?: PermissionDescriptor[];
|
|
45
|
-
};
|
|
46
|
-
declare class ForbiddenError extends Error {
|
|
47
|
-
readonly action: PermissionAction;
|
|
48
|
-
readonly table: DrizzleTable;
|
|
49
|
-
readonly role: string;
|
|
50
|
-
readonly descriptors: PermissionDescriptor[];
|
|
51
|
-
constructor(options: ForbiddenErrorOptions);
|
|
52
|
-
toJSON(): {
|
|
53
|
-
name: string;
|
|
54
|
-
message: string;
|
|
55
|
-
action: PermissionAction;
|
|
56
|
-
table: string;
|
|
57
|
-
role: string;
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export { CRUD_ACTIONS as C, type DrizzleTable as D, ForbiddenError as F, type Grant as G, type PermissionsConfig as P, type WhereClause as W, type Permissions as a, type PermissionAction as b, type PermissionDescriptor as c, type PermissionCheckResult as d, type CrudAction as e, type GrantFn as f };
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { Table, SQL } from 'drizzle-orm';
|
|
2
|
-
|
|
3
|
-
type PermissionAction = "read" | "create" | "update" | "delete" | "manage";
|
|
4
|
-
type CrudAction = Exclude<PermissionAction, "manage">;
|
|
5
|
-
declare const CRUD_ACTIONS: readonly CrudAction[];
|
|
6
|
-
type WhereClause = (columns: Record<string, unknown>, user: any) => SQL | undefined;
|
|
7
|
-
type Grant = {
|
|
8
|
-
action: PermissionAction;
|
|
9
|
-
subject: Table | "all";
|
|
10
|
-
where?: WhereClause;
|
|
11
|
-
};
|
|
12
|
-
type GrantFn<TUser> = (action: PermissionAction, subject: Table | "all", options?: {
|
|
13
|
-
where?: (columns: Record<string, unknown>, user: TUser) => SQL | undefined;
|
|
14
|
-
}) => Grant;
|
|
15
|
-
type PermissionDescriptor = {
|
|
16
|
-
action: PermissionAction;
|
|
17
|
-
table: Table;
|
|
18
|
-
};
|
|
19
|
-
type PermissionCheckResult = {
|
|
20
|
-
permitted: boolean;
|
|
21
|
-
denied: PermissionDescriptor[];
|
|
22
|
-
reasons: string[];
|
|
23
|
-
};
|
|
24
|
-
type PermissionsConfig<TRoles extends readonly string[], TUser = unknown> = {
|
|
25
|
-
roles: TRoles;
|
|
26
|
-
grants: Record<TRoles[number], Grant[]> | ((grant: GrantFn<TUser>) => Record<TRoles[number], Grant[]>);
|
|
27
|
-
hierarchy?: Partial<Record<TRoles[number], TRoles[number][]>>;
|
|
28
|
-
};
|
|
29
|
-
type Permissions<TRoles extends readonly string[] = readonly string[]> = {
|
|
30
|
-
roles: TRoles;
|
|
31
|
-
grants: Record<TRoles[number], Grant[]>;
|
|
32
|
-
resolvedGrants: Record<TRoles[number], Grant[]>;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
type ForbiddenErrorOptions = {
|
|
36
|
-
action: PermissionAction;
|
|
37
|
-
table: Table;
|
|
38
|
-
role: string;
|
|
39
|
-
descriptors?: PermissionDescriptor[];
|
|
40
|
-
};
|
|
41
|
-
declare class ForbiddenError extends Error {
|
|
42
|
-
readonly action: PermissionAction;
|
|
43
|
-
readonly table: Table;
|
|
44
|
-
readonly role: string;
|
|
45
|
-
readonly descriptors: PermissionDescriptor[];
|
|
46
|
-
constructor(options: ForbiddenErrorOptions);
|
|
47
|
-
toJSON(): {
|
|
48
|
-
name: string;
|
|
49
|
-
message: string;
|
|
50
|
-
action: PermissionAction;
|
|
51
|
-
table: string;
|
|
52
|
-
role: string;
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export { CRUD_ACTIONS as C, ForbiddenError as F, type Grant as G, type PermissionsConfig as P, type WhereClause as W, type Permissions as a, type PermissionAction as b, type PermissionDescriptor as c, type PermissionCheckResult as d, type CrudAction as e, type GrantFn as f };
|