@fonderie/permissions 1.0.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/LICENSE +21 -0
- package/README.md +49 -0
- package/dist/config.cjs +19 -0
- package/dist/config.cjs.map +1 -0
- package/dist/config.d.cts +6 -0
- package/dist/config.d.ts +6 -0
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -0
- package/dist/index.cjs +253 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +45 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +220 -0
- package/dist/index.js.map +1 -0
- package/dist/middlewares/index.cjs +224 -0
- package/dist/middlewares/index.cjs.map +1 -0
- package/dist/middlewares/index.d.cts +11 -0
- package/dist/middlewares/index.d.ts +11 -0
- package/dist/middlewares/index.js +196 -0
- package/dist/middlewares/index.js.map +1 -0
- package/dist/migrations/index.js +7 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/migrations/sql/001_permissions.sql +17 -0
- package/dist/types.cjs +19 -0
- package/dist/types.cjs.map +1 -0
- package/dist/types.d.cts +26 -0
- package/dist/types.d.ts +26 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -0
- package/package.json +87 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Fonderie, Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# @fonderie/permissions
|
|
2
|
+
|
|
3
|
+
Role-based access control: define roles, assign CRUD permissions per
|
|
4
|
+
resource, and enforce them with middleware — the brick between "logged in"
|
|
5
|
+
and "allowed to".
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install @fonderie/permissions
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Use
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { FonderieApp, defineConfig } from '@fonderie/core';
|
|
17
|
+
import { PermissionsModule } from '@fonderie/permissions';
|
|
18
|
+
|
|
19
|
+
const app = await new FonderieApp(defineConfig({}))
|
|
20
|
+
.register(new PermissionsModule())
|
|
21
|
+
.boot();
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { requirePermission, requireRole, OPERATIONS } from '@fonderie/permissions';
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`PermissionsEngine` evaluates keys like `projects:update`; denials raise a
|
|
29
|
+
typed `PermissionDeniedError`.
|
|
30
|
+
|
|
31
|
+
## Why this exists
|
|
32
|
+
|
|
33
|
+
You've shipped this plumbing before — auth, teams, billing, messaging —
|
|
34
|
+
and the next project will ask for it again. Fonderie packages it once:
|
|
35
|
+
plain TypeScript modules for
|
|
36
|
+
[`@fonderie/core`](https://github.com/fonderie-js/sdk/tree/main/packages/core),
|
|
37
|
+
PostgreSQL-backed, self-hosted, MIT. No external control plane, no
|
|
38
|
+
per-seat anything. Register the modules you need; skip the ones you don't.
|
|
39
|
+
|
|
40
|
+
**This package owns** what the caller may do. Role and permission evaluation layered
|
|
41
|
+
on auth and workspaces, enforced at the route with one middleware.
|
|
42
|
+
|
|
43
|
+
Browse the whole set at
|
|
44
|
+
[fonderie-js/sdk](https://github.com/fonderie-js/sdk) · follow
|
|
45
|
+
[@fonderiejs](https://x.com/fonderiejs)
|
|
46
|
+
|
|
47
|
+
## License
|
|
48
|
+
|
|
49
|
+
MIT © Fonderie, Inc.
|
package/dist/config.cjs
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __copyProps = (to, from, except, desc) => {
|
|
7
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
8
|
+
for (let key of __getOwnPropNames(from))
|
|
9
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
10
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
11
|
+
}
|
|
12
|
+
return to;
|
|
13
|
+
};
|
|
14
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
15
|
+
|
|
16
|
+
// src/config.ts
|
|
17
|
+
var config_exports = {};
|
|
18
|
+
module.exports = __toCommonJS(config_exports);
|
|
19
|
+
//# sourceMappingURL=config.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config.ts"],"sourcesContent":["export interface IPermissionsConfig {\n\t// When true, a permission of action='*' or resource='*' matches anything\n\t// Default: true\n\twildcards?: boolean;\n\n\t// Super-admin role name — members with this role bypass all checks\n\t// Default: 'owner'\n\tsuperRole?: string;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
|
package/dist/config.d.ts
ADDED
package/dist/config.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
OPERATIONS: () => OPERATIONS,
|
|
24
|
+
PERMISSION_COLUMN: () => PERMISSION_COLUMN,
|
|
25
|
+
PermissionDeniedError: () => PermissionDeniedError,
|
|
26
|
+
PermissionsEngine: () => PermissionsEngine,
|
|
27
|
+
PermissionsModule: () => PermissionsModule,
|
|
28
|
+
requirePermission: () => requirePermission,
|
|
29
|
+
requireRole: () => requireRole
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(index_exports);
|
|
32
|
+
|
|
33
|
+
// src/services/membership.ts
|
|
34
|
+
async function getMembership(userId, workspaceId, store) {
|
|
35
|
+
const [row] = await store.query(
|
|
36
|
+
`SELECT ruw.user_id, ruw.workspace_id, ruw.role_id, r.name AS role_name
|
|
37
|
+
FROM fonderie_role_user_workspaces ruw
|
|
38
|
+
JOIN fonderie_roles r ON r.id = ruw.role_id
|
|
39
|
+
WHERE ruw.user_id = $1
|
|
40
|
+
AND ruw.workspace_id = $2
|
|
41
|
+
AND ruw.removed = false
|
|
42
|
+
AND ruw.suspended = false
|
|
43
|
+
LIMIT 1`,
|
|
44
|
+
[userId, workspaceId]
|
|
45
|
+
);
|
|
46
|
+
if (!row) return null;
|
|
47
|
+
return {
|
|
48
|
+
userId: row.user_id,
|
|
49
|
+
workspaceId: row.workspace_id,
|
|
50
|
+
roleId: row.role_id,
|
|
51
|
+
roleName: row.role_name
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
async function hasRole(userId, workspaceId, roleName, store) {
|
|
55
|
+
const [row] = await store.query(
|
|
56
|
+
`SELECT EXISTS (
|
|
57
|
+
SELECT 1
|
|
58
|
+
FROM fonderie_role_user_workspaces ruw
|
|
59
|
+
JOIN fonderie_roles r ON r.id = ruw.role_id
|
|
60
|
+
WHERE ruw.user_id = $1
|
|
61
|
+
AND ruw.workspace_id = $2
|
|
62
|
+
AND r.name = $3
|
|
63
|
+
AND ruw.removed = false
|
|
64
|
+
AND ruw.suspended = false
|
|
65
|
+
) AS exists`,
|
|
66
|
+
[userId, workspaceId, roleName]
|
|
67
|
+
);
|
|
68
|
+
return row?.exists ?? false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/constants.ts
|
|
72
|
+
var OPERATIONS = {
|
|
73
|
+
CREATE: "create",
|
|
74
|
+
READ: "read",
|
|
75
|
+
UPDATE: "update",
|
|
76
|
+
DELETE: "delete"
|
|
77
|
+
};
|
|
78
|
+
var PERMISSION_COLUMN = {
|
|
79
|
+
create: "can_create",
|
|
80
|
+
read: "can_read",
|
|
81
|
+
update: "can_update",
|
|
82
|
+
delete: "can_delete"
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// src/services/permissions.ts
|
|
86
|
+
async function checkPermission(userId, workspaceId, permissionKey, operation, store) {
|
|
87
|
+
const col = PERMISSION_COLUMN[operation];
|
|
88
|
+
const [row] = await store.query(
|
|
89
|
+
`SELECT BOOL_OR(rp.${col}) AS has_permission
|
|
90
|
+
FROM fonderie_role_user_workspaces ruw
|
|
91
|
+
JOIN fonderie_roles r ON ruw.role_id = r.id
|
|
92
|
+
JOIN fonderie_role_permissions rp ON r.id = rp.role_id
|
|
93
|
+
WHERE ruw.user_id = $1
|
|
94
|
+
AND ruw.workspace_id = $2
|
|
95
|
+
AND rp.workspace_id = $2
|
|
96
|
+
AND rp.permission_key = $3
|
|
97
|
+
AND ruw.removed = false
|
|
98
|
+
AND ruw.suspended = false
|
|
99
|
+
AND (r.workspace_id = $2 OR r.is_system = true)
|
|
100
|
+
GROUP BY ruw.user_id, ruw.workspace_id`,
|
|
101
|
+
[userId, workspaceId, permissionKey]
|
|
102
|
+
);
|
|
103
|
+
return row?.has_permission ?? false;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// src/engine.ts
|
|
107
|
+
var PermissionsEngine = class {
|
|
108
|
+
constructor(store, config = {}) {
|
|
109
|
+
this.store = store;
|
|
110
|
+
this.superRole = config.superRole ?? "owner";
|
|
111
|
+
}
|
|
112
|
+
store;
|
|
113
|
+
superRole;
|
|
114
|
+
async getMembership(userId, workspaceId) {
|
|
115
|
+
return getMembership(userId, workspaceId, this.store);
|
|
116
|
+
}
|
|
117
|
+
async can(userId, operation, permissionKey, workspaceId) {
|
|
118
|
+
const isMember = await getMembership(userId, workspaceId, this.store);
|
|
119
|
+
if (!isMember) return false;
|
|
120
|
+
if (await hasRole(userId, workspaceId, this.superRole, this.store)) return true;
|
|
121
|
+
return checkPermission(userId, workspaceId, permissionKey, operation, this.store);
|
|
122
|
+
}
|
|
123
|
+
async assert(userId, operation, permissionKey, workspaceId) {
|
|
124
|
+
const allowed = await this.can(userId, operation, permissionKey, workspaceId);
|
|
125
|
+
if (!allowed) {
|
|
126
|
+
throw new PermissionDeniedError(operation, permissionKey);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async canAll(userId, checks, workspaceId) {
|
|
130
|
+
const isMember = await getMembership(userId, workspaceId, this.store);
|
|
131
|
+
if (!isMember) return false;
|
|
132
|
+
if (await hasRole(userId, workspaceId, this.superRole, this.store)) return true;
|
|
133
|
+
const results = await Promise.all(
|
|
134
|
+
checks.map(
|
|
135
|
+
(c) => checkPermission(userId, workspaceId, c.permissionKey, c.operation, this.store)
|
|
136
|
+
)
|
|
137
|
+
);
|
|
138
|
+
return results.every(Boolean);
|
|
139
|
+
}
|
|
140
|
+
async canAny(userId, checks, workspaceId) {
|
|
141
|
+
const isMember = await getMembership(userId, workspaceId, this.store);
|
|
142
|
+
if (!isMember) return false;
|
|
143
|
+
if (await hasRole(userId, workspaceId, this.superRole, this.store)) return true;
|
|
144
|
+
const results = await Promise.all(
|
|
145
|
+
checks.map(
|
|
146
|
+
(c) => checkPermission(userId, workspaceId, c.permissionKey, c.operation, this.store)
|
|
147
|
+
)
|
|
148
|
+
);
|
|
149
|
+
return results.some(Boolean);
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
var PermissionDeniedError = class extends Error {
|
|
153
|
+
status = 403;
|
|
154
|
+
constructor(operation, permissionKey) {
|
|
155
|
+
super(`Permission denied: ${operation}:${permissionKey}`);
|
|
156
|
+
this.name = "PermissionDeniedError";
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// src/module.ts
|
|
161
|
+
var PERMISSIONS_ENGINE_KEY = "fonderie.permissions.engine";
|
|
162
|
+
var PermissionsModule = class {
|
|
163
|
+
engine;
|
|
164
|
+
name = "@fonderie/permissions";
|
|
165
|
+
deps = ["@fonderie/auth"];
|
|
166
|
+
constructor(store, config = {}) {
|
|
167
|
+
this.engine = new PermissionsEngine(store, config);
|
|
168
|
+
}
|
|
169
|
+
install(app) {
|
|
170
|
+
const engine = this.engine;
|
|
171
|
+
app.use(async (ctx, next) => {
|
|
172
|
+
ctx.meta[PERMISSIONS_ENGINE_KEY] = engine;
|
|
173
|
+
return next();
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// src/middlewares/require-role.ts
|
|
179
|
+
var import_core = require("@fonderie/core");
|
|
180
|
+
function makeHandler(roleName, store) {
|
|
181
|
+
const allowed = Array.isArray(roleName) ? roleName : [roleName];
|
|
182
|
+
return async (ctx, next) => {
|
|
183
|
+
if (!ctx.user) {
|
|
184
|
+
return (0, import_core.setApiResponse)(import_core.HTTP.UNAUTHORIZED, "UNAUTHORIZED", "Unauthorized");
|
|
185
|
+
}
|
|
186
|
+
const engine = ctx.meta[PERMISSIONS_ENGINE_KEY];
|
|
187
|
+
if (!(engine instanceof PermissionsEngine)) {
|
|
188
|
+
return (0, import_core.setApiResponse)(import_core.HTTP.SERVER_ERROR, "SERVER_ERROR", "Permissions module not installed");
|
|
189
|
+
}
|
|
190
|
+
const workspaceId = ctx.workspace?.id ?? ctx.meta["params"]?.["workspaceId"];
|
|
191
|
+
if (!workspaceId) {
|
|
192
|
+
return (0, import_core.setApiResponse)(import_core.HTTP.BAD_REQUEST, "WORKSPACE_REQUIRED", "Workspace context required");
|
|
193
|
+
}
|
|
194
|
+
const membership = await getMembership(ctx.user.id, workspaceId, store);
|
|
195
|
+
if (!membership || !allowed.includes(membership.roleName)) {
|
|
196
|
+
return (0, import_core.setApiResponse)(import_core.HTTP.FORBIDDEN, "FORBIDDEN", "Insufficient role");
|
|
197
|
+
}
|
|
198
|
+
return next();
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
function requireRole(roleName, store, ctx, next) {
|
|
202
|
+
const handler = makeHandler(roleName, store);
|
|
203
|
+
if (ctx !== void 0 && next !== void 0) return handler(ctx, next);
|
|
204
|
+
return handler;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// src/middlewares/require-permission.ts
|
|
208
|
+
var import_core2 = require("@fonderie/core");
|
|
209
|
+
function makeHandler2(operation, permissionKey) {
|
|
210
|
+
return async (ctx, next) => {
|
|
211
|
+
if (!ctx.user) {
|
|
212
|
+
return (0, import_core2.setApiResponse)(import_core2.HTTP.UNAUTHORIZED, "UNAUTHORIZED", "Unauthorized");
|
|
213
|
+
}
|
|
214
|
+
const engine = ctx.meta[PERMISSIONS_ENGINE_KEY];
|
|
215
|
+
if (!(engine instanceof PermissionsEngine)) {
|
|
216
|
+
return (0, import_core2.setApiResponse)(import_core2.HTTP.SERVER_ERROR, "SERVER_ERROR", "Permissions module not installed");
|
|
217
|
+
}
|
|
218
|
+
const workspaceId = resolveWorkspaceId(ctx);
|
|
219
|
+
if (!workspaceId) {
|
|
220
|
+
return (0, import_core2.setApiResponse)(import_core2.HTTP.BAD_REQUEST, "WORKSPACE_REQUIRED", "Workspace context required");
|
|
221
|
+
}
|
|
222
|
+
const allowed = await engine.can(ctx.user.id, operation, permissionKey, workspaceId);
|
|
223
|
+
if (!allowed) {
|
|
224
|
+
return (0, import_core2.setApiResponse)(
|
|
225
|
+
import_core2.HTTP.FORBIDDEN,
|
|
226
|
+
"FORBIDDEN",
|
|
227
|
+
`Permission denied: ${operation}:${permissionKey}`
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
return next();
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
function requirePermission(operation, permissionKey, ctx, next) {
|
|
234
|
+
const handler = makeHandler2(operation, permissionKey);
|
|
235
|
+
if (ctx !== void 0 && next !== void 0) return handler(ctx, next);
|
|
236
|
+
return handler;
|
|
237
|
+
}
|
|
238
|
+
function resolveWorkspaceId(ctx) {
|
|
239
|
+
if (ctx.workspace?.id) return ctx.workspace.id;
|
|
240
|
+
const params = ctx.meta["params"];
|
|
241
|
+
return params?.["workspaceId"] ?? null;
|
|
242
|
+
}
|
|
243
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
244
|
+
0 && (module.exports = {
|
|
245
|
+
OPERATIONS,
|
|
246
|
+
PERMISSION_COLUMN,
|
|
247
|
+
PermissionDeniedError,
|
|
248
|
+
PermissionsEngine,
|
|
249
|
+
PermissionsModule,
|
|
250
|
+
requirePermission,
|
|
251
|
+
requireRole
|
|
252
|
+
});
|
|
253
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/services/membership.ts","../src/constants.ts","../src/services/permissions.ts","../src/engine.ts","../src/module.ts","../src/middlewares/require-role.ts","../src/middlewares/require-permission.ts"],"sourcesContent":["export type {\n\tOperation,\n\tPermissionKey,\n\tIRole,\n\tIPermission,\n\tIMembership,\n\tIRoleWithPermissions,\n} from './types';\nexport { PermissionsModule } from './module';\nexport { PermissionsEngine, PermissionDeniedError } from './engine';\nexport type { IPermissionsConfig } from './config';\n\nexport { OPERATIONS, PERMISSION_COLUMN } from './constants';\nexport { requireRole } from './middlewares/require-role';\nexport { requirePermission } from './middlewares/require-permission';\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IMembership } from '../types';\n\nexport async function getMembership(\n\tuserId: string,\n\tworkspaceId: string,\n\tstore: IStoreAdapter,\n): Promise<IMembership | null> {\n\tconst [row] = await store.query<{\n\t\tuser_id: string;\n\t\tworkspace_id: string;\n\t\trole_id: string;\n\t\trole_name: string;\n\t}>(\n\t\t`SELECT ruw.user_id, ruw.workspace_id, ruw.role_id, r.name AS role_name\n\t\t FROM fonderie_role_user_workspaces ruw\n\t\t JOIN fonderie_roles r ON r.id = ruw.role_id\n\t\t WHERE ruw.user_id = $1\n\t\t AND ruw.workspace_id = $2\n\t\t AND ruw.removed = false\n\t\t AND ruw.suspended = false\n\t\t LIMIT 1`,\n\t\t[userId, workspaceId],\n\t);\n\n\tif (!row) return null;\n\n\treturn {\n\t\tuserId: row.user_id,\n\t\tworkspaceId: row.workspace_id,\n\t\troleId: row.role_id,\n\t\troleName: row.role_name,\n\t};\n}\n\nexport async function hasRole(\n\tuserId: string,\n\tworkspaceId: string,\n\troleName: string,\n\tstore: IStoreAdapter,\n): Promise<boolean> {\n\tconst [row] = await store.query<{ exists: boolean }>(\n\t\t`SELECT EXISTS (\n\t\t SELECT 1\n\t\t FROM fonderie_role_user_workspaces ruw\n\t\t JOIN fonderie_roles r ON r.id = ruw.role_id\n\t\t WHERE ruw.user_id = $1\n\t\t AND ruw.workspace_id = $2\n\t\t AND r.name = $3\n\t\t AND ruw.removed = false\n\t\t AND ruw.suspended = false\n\t\t ) AS exists`,\n\t\t[userId, workspaceId, roleName],\n\t);\n\n\treturn row?.exists ?? false;\n}\n","import type { Operation } from './types';\n\nexport const OPERATIONS = {\n\tCREATE: 'create',\n\tREAD: 'read',\n\tUPDATE: 'update',\n\tDELETE: 'delete',\n} as const satisfies Record<string, Operation>;\n\nexport const PERMISSION_COLUMN: Record<Operation, string> = {\n\tcreate: 'can_create',\n\tread: 'can_read',\n\tupdate: 'can_update',\n\tdelete: 'can_delete',\n};\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { Operation } from '../types';\nimport { PERMISSION_COLUMN } from '../constants';\n\nexport async function checkPermission(\n\tuserId: string,\n\tworkspaceId: string,\n\tpermissionKey: string,\n\toperation: Operation,\n\tstore: IStoreAdapter,\n): Promise<boolean> {\n\tconst col = PERMISSION_COLUMN[operation];\n\n\tconst [row] = await store.query<{ has_permission: boolean }>(\n\t\t`SELECT BOOL_OR(rp.${col}) AS has_permission\n\t\t FROM fonderie_role_user_workspaces ruw\n\t\t JOIN fonderie_roles r ON ruw.role_id = r.id\n\t\t JOIN fonderie_role_permissions rp ON r.id = rp.role_id\n\t\t WHERE ruw.user_id = $1\n\t\t AND ruw.workspace_id = $2\n\t\t AND rp.workspace_id = $2\n\t\t AND rp.permission_key = $3\n\t\t AND ruw.removed = false\n\t\t AND ruw.suspended = false\n\t\t AND (r.workspace_id = $2 OR r.is_system = true)\n\t\t GROUP BY ruw.user_id, ruw.workspace_id`,\n\t\t[userId, workspaceId, permissionKey],\n\t);\n\n\treturn row?.has_permission ?? false;\n}\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { Operation } from './types';\nimport type { IPermissionsConfig } from './config';\nimport { getMembership, hasRole } from './services/membership';\nimport { checkPermission } from './services/permissions';\n\nexport class PermissionsEngine {\n\tprivate superRole: string;\n\n\tconstructor(\n\t\tprivate store: IStoreAdapter,\n\t\tconfig: IPermissionsConfig = {},\n\t) {\n\t\tthis.superRole = config.superRole ?? 'owner';\n\t}\n\n\tasync getMembership(userId: string, workspaceId: string) {\n\t\treturn getMembership(userId, workspaceId, this.store);\n\t}\n\n\tasync can(\n\t\tuserId: string,\n\t\toperation: Operation,\n\t\tpermissionKey: string,\n\t\tworkspaceId: string,\n\t): Promise<boolean> {\n\t\tconst isMember = await getMembership(userId, workspaceId, this.store);\n\t\tif (!isMember) return false;\n\n\t\tif (await hasRole(userId, workspaceId, this.superRole, this.store)) return true;\n\n\t\treturn checkPermission(userId, workspaceId, permissionKey, operation, this.store);\n\t}\n\n\tasync assert(\n\t\tuserId: string,\n\t\toperation: Operation,\n\t\tpermissionKey: string,\n\t\tworkspaceId: string,\n\t): Promise<void> {\n\t\tconst allowed = await this.can(userId, operation, permissionKey, workspaceId);\n\t\tif (!allowed) {\n\t\t\tthrow new PermissionDeniedError(operation, permissionKey);\n\t\t}\n\t}\n\n\tasync canAll(\n\t\tuserId: string,\n\t\tchecks: Array<{ operation: Operation; permissionKey: string }>,\n\t\tworkspaceId: string,\n\t): Promise<boolean> {\n\t\tconst isMember = await getMembership(userId, workspaceId, this.store);\n\t\tif (!isMember) return false;\n\n\t\tif (await hasRole(userId, workspaceId, this.superRole, this.store)) return true;\n\n\t\tconst results = await Promise.all(\n\t\t\tchecks.map((c) =>\n\t\t\t\tcheckPermission(userId, workspaceId, c.permissionKey, c.operation, this.store),\n\t\t\t),\n\t\t);\n\t\treturn results.every(Boolean);\n\t}\n\n\tasync canAny(\n\t\tuserId: string,\n\t\tchecks: Array<{ operation: Operation; permissionKey: string }>,\n\t\tworkspaceId: string,\n\t): Promise<boolean> {\n\t\tconst isMember = await getMembership(userId, workspaceId, this.store);\n\t\tif (!isMember) return false;\n\n\t\tif (await hasRole(userId, workspaceId, this.superRole, this.store)) return true;\n\n\t\tconst results = await Promise.all(\n\t\t\tchecks.map((c) =>\n\t\t\t\tcheckPermission(userId, workspaceId, c.permissionKey, c.operation, this.store),\n\t\t\t),\n\t\t);\n\t\treturn results.some(Boolean);\n\t}\n}\n\nexport class PermissionDeniedError extends Error {\n\treadonly status = 403;\n\n\tconstructor(operation: string, permissionKey: string) {\n\t\tsuper(`Permission denied: ${operation}:${permissionKey}`);\n\t\tthis.name = 'PermissionDeniedError';\n\t}\n}\n","import type { IFonderieModule, IFonderieApp } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IPermissionsConfig } from './config';\nimport { PermissionsEngine } from './engine';\n\nexport const PERMISSIONS_ENGINE_KEY = 'fonderie.permissions.engine';\n\nexport class PermissionsModule implements IFonderieModule {\n\treadonly engine: PermissionsEngine;\n\treadonly name = '@fonderie/permissions';\n\treadonly deps = ['@fonderie/auth'];\n\n\tconstructor(store: IStoreAdapter, config: IPermissionsConfig = {}) {\n\t\tthis.engine = new PermissionsEngine(store, config);\n\t}\n\n\tinstall(app: IFonderieApp): void {\n\t\tconst engine = this.engine;\n\n\t\t// Inject engine into every request context\n\t\t// requirePermission() reads it from here — no manual passing needed\n\t\tapp.use(async (ctx, next) => {\n\t\t\tctx.meta[PERMISSIONS_ENGINE_KEY] = engine;\n\t\t\treturn next();\n\t\t});\n\t}\n}\n","import { setApiResponse, HTTP } from '@fonderie/core';\nimport type { Middleware } from '@fonderie/core';\nimport type { IFonderieContext } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport { PermissionsEngine } from '../engine';\nimport { PERMISSIONS_ENGINE_KEY } from '../module';\nimport { getMembership } from '../services/membership';\n\nfunction makeHandler(roleName: string | string[], store: IStoreAdapter): Middleware {\n\tconst allowed = Array.isArray(roleName) ? roleName : [roleName];\n\n\treturn async (ctx, next) => {\n\t\tif (!ctx.user) {\n\t\t\treturn setApiResponse(HTTP.UNAUTHORIZED, 'UNAUTHORIZED', 'Unauthorized');\n\t\t}\n\n\t\tconst engine = ctx.meta[PERMISSIONS_ENGINE_KEY];\n\t\tif (!(engine instanceof PermissionsEngine)) {\n\t\t\treturn setApiResponse(HTTP.SERVER_ERROR, 'SERVER_ERROR', 'Permissions module not installed');\n\t\t}\n\n\t\tconst workspaceId =\n\t\t\tctx.workspace?.id ??\n\t\t\t(ctx.meta['params'] as Record<string, string> | undefined)?.['workspaceId'];\n\n\t\tif (!workspaceId) {\n\t\t\treturn setApiResponse(HTTP.BAD_REQUEST, 'WORKSPACE_REQUIRED', 'Workspace context required');\n\t\t}\n\n\t\tconst membership = await getMembership(ctx.user.id, workspaceId, store);\n\t\tif (!membership || !allowed.includes(membership.roleName)) {\n\t\t\treturn setApiResponse(HTTP.FORBIDDEN, 'FORBIDDEN', 'Insufficient role');\n\t\t}\n\n\t\treturn next();\n\t};\n}\n\nexport function requireRole(roleName: string | string[], store: IStoreAdapter): Middleware;\nexport function requireRole(\n\troleName: string | string[],\n\tstore: IStoreAdapter,\n\tctx: IFonderieContext,\n\tnext: () => Promise<Response>,\n): Promise<Response>;\nexport function requireRole(\n\troleName: string | string[],\n\tstore: IStoreAdapter,\n\tctx?: IFonderieContext,\n\tnext?: () => Promise<Response>,\n): Middleware | Promise<Response> {\n\tconst handler = makeHandler(roleName, store);\n\tif (ctx !== undefined && next !== undefined) return handler(ctx, next);\n\treturn handler;\n}\n","import { setApiResponse, HTTP } from '@fonderie/core';\nimport type { Middleware } from '@fonderie/core';\nimport type { IFonderieContext } from '@fonderie/core';\n\nimport type { Operation } from '../types';\nimport { PermissionsEngine } from '../engine';\nimport { PERMISSIONS_ENGINE_KEY } from '../module';\n\nfunction makeHandler(operation: Operation, permissionKey: string): Middleware {\n\treturn async (ctx, next) => {\n\t\tif (!ctx.user) {\n\t\t\treturn setApiResponse(HTTP.UNAUTHORIZED, 'UNAUTHORIZED', 'Unauthorized');\n\t\t}\n\n\t\tconst engine = ctx.meta[PERMISSIONS_ENGINE_KEY];\n\t\tif (!(engine instanceof PermissionsEngine)) {\n\t\t\treturn setApiResponse(HTTP.SERVER_ERROR, 'SERVER_ERROR', 'Permissions module not installed');\n\t\t}\n\n\t\tconst workspaceId = resolveWorkspaceId(ctx);\n\t\tif (!workspaceId) {\n\t\t\treturn setApiResponse(HTTP.BAD_REQUEST, 'WORKSPACE_REQUIRED', 'Workspace context required');\n\t\t}\n\n\t\tconst allowed = await engine.can(ctx.user.id, operation, permissionKey, workspaceId);\n\t\tif (!allowed) {\n\t\t\treturn setApiResponse(\n\t\t\t\tHTTP.FORBIDDEN,\n\t\t\t\t'FORBIDDEN',\n\t\t\t\t`Permission denied: ${operation}:${permissionKey}`,\n\t\t\t);\n\t\t}\n\n\t\treturn next();\n\t};\n}\n\nexport function requirePermission(operation: Operation, permissionKey: string): Middleware;\nexport function requirePermission(\n\toperation: Operation,\n\tpermissionKey: string,\n\tctx: IFonderieContext,\n\tnext: () => Promise<Response>,\n): Promise<Response>;\nexport function requirePermission(\n\toperation: Operation,\n\tpermissionKey: string,\n\tctx?: IFonderieContext,\n\tnext?: () => Promise<Response>,\n): Middleware | Promise<Response> {\n\tconst handler = makeHandler(operation, permissionKey);\n\tif (ctx !== undefined && next !== undefined) return handler(ctx, next);\n\treturn handler;\n}\n\nfunction resolveWorkspaceId(ctx: {\n\tworkspace?: { id: string } | null;\n\tmeta: Record<string, unknown>;\n}): string | null {\n\tif (ctx.workspace?.id) return ctx.workspace.id;\n\n\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\treturn params?.['workspaceId'] ?? null;\n}\n"],"mappings":"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","names":["import_core","makeHandler"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { IMembership, Operation } from './types.cjs';
|
|
2
|
+
export { IPermission, IRole, IRoleWithPermissions, PermissionKey } from './types.cjs';
|
|
3
|
+
import { IFonderieModule, IFonderieApp } from '@fonderie/core';
|
|
4
|
+
import { IStoreAdapter } from '@fonderie/store';
|
|
5
|
+
import { IPermissionsConfig } from './config.cjs';
|
|
6
|
+
export { requirePermission, requireRole } from './middlewares/index.cjs';
|
|
7
|
+
|
|
8
|
+
declare class PermissionsEngine {
|
|
9
|
+
private store;
|
|
10
|
+
private superRole;
|
|
11
|
+
constructor(store: IStoreAdapter, config?: IPermissionsConfig);
|
|
12
|
+
getMembership(userId: string, workspaceId: string): Promise<IMembership | null>;
|
|
13
|
+
can(userId: string, operation: Operation, permissionKey: string, workspaceId: string): Promise<boolean>;
|
|
14
|
+
assert(userId: string, operation: Operation, permissionKey: string, workspaceId: string): Promise<void>;
|
|
15
|
+
canAll(userId: string, checks: Array<{
|
|
16
|
+
operation: Operation;
|
|
17
|
+
permissionKey: string;
|
|
18
|
+
}>, workspaceId: string): Promise<boolean>;
|
|
19
|
+
canAny(userId: string, checks: Array<{
|
|
20
|
+
operation: Operation;
|
|
21
|
+
permissionKey: string;
|
|
22
|
+
}>, workspaceId: string): Promise<boolean>;
|
|
23
|
+
}
|
|
24
|
+
declare class PermissionDeniedError extends Error {
|
|
25
|
+
readonly status = 403;
|
|
26
|
+
constructor(operation: string, permissionKey: string);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
declare class PermissionsModule implements IFonderieModule {
|
|
30
|
+
readonly engine: PermissionsEngine;
|
|
31
|
+
readonly name = "@fonderie/permissions";
|
|
32
|
+
readonly deps: string[];
|
|
33
|
+
constructor(store: IStoreAdapter, config?: IPermissionsConfig);
|
|
34
|
+
install(app: IFonderieApp): void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
declare const OPERATIONS: {
|
|
38
|
+
readonly CREATE: "create";
|
|
39
|
+
readonly READ: "read";
|
|
40
|
+
readonly UPDATE: "update";
|
|
41
|
+
readonly DELETE: "delete";
|
|
42
|
+
};
|
|
43
|
+
declare const PERMISSION_COLUMN: Record<Operation, string>;
|
|
44
|
+
|
|
45
|
+
export { IMembership, IPermissionsConfig, OPERATIONS, Operation, PERMISSION_COLUMN, PermissionDeniedError, PermissionsEngine, PermissionsModule };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { IMembership, Operation } from './types.js';
|
|
2
|
+
export { IPermission, IRole, IRoleWithPermissions, PermissionKey } from './types.js';
|
|
3
|
+
import { IFonderieModule, IFonderieApp } from '@fonderie/core';
|
|
4
|
+
import { IStoreAdapter } from '@fonderie/store';
|
|
5
|
+
import { IPermissionsConfig } from './config.js';
|
|
6
|
+
export { requirePermission, requireRole } from './middlewares/index.js';
|
|
7
|
+
|
|
8
|
+
declare class PermissionsEngine {
|
|
9
|
+
private store;
|
|
10
|
+
private superRole;
|
|
11
|
+
constructor(store: IStoreAdapter, config?: IPermissionsConfig);
|
|
12
|
+
getMembership(userId: string, workspaceId: string): Promise<IMembership | null>;
|
|
13
|
+
can(userId: string, operation: Operation, permissionKey: string, workspaceId: string): Promise<boolean>;
|
|
14
|
+
assert(userId: string, operation: Operation, permissionKey: string, workspaceId: string): Promise<void>;
|
|
15
|
+
canAll(userId: string, checks: Array<{
|
|
16
|
+
operation: Operation;
|
|
17
|
+
permissionKey: string;
|
|
18
|
+
}>, workspaceId: string): Promise<boolean>;
|
|
19
|
+
canAny(userId: string, checks: Array<{
|
|
20
|
+
operation: Operation;
|
|
21
|
+
permissionKey: string;
|
|
22
|
+
}>, workspaceId: string): Promise<boolean>;
|
|
23
|
+
}
|
|
24
|
+
declare class PermissionDeniedError extends Error {
|
|
25
|
+
readonly status = 403;
|
|
26
|
+
constructor(operation: string, permissionKey: string);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
declare class PermissionsModule implements IFonderieModule {
|
|
30
|
+
readonly engine: PermissionsEngine;
|
|
31
|
+
readonly name = "@fonderie/permissions";
|
|
32
|
+
readonly deps: string[];
|
|
33
|
+
constructor(store: IStoreAdapter, config?: IPermissionsConfig);
|
|
34
|
+
install(app: IFonderieApp): void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
declare const OPERATIONS: {
|
|
38
|
+
readonly CREATE: "create";
|
|
39
|
+
readonly READ: "read";
|
|
40
|
+
readonly UPDATE: "update";
|
|
41
|
+
readonly DELETE: "delete";
|
|
42
|
+
};
|
|
43
|
+
declare const PERMISSION_COLUMN: Record<Operation, string>;
|
|
44
|
+
|
|
45
|
+
export { IMembership, IPermissionsConfig, OPERATIONS, Operation, PERMISSION_COLUMN, PermissionDeniedError, PermissionsEngine, PermissionsModule };
|