@donkeylabs/cli 2.0.15 → 2.0.16
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 +1 -1
- package/src/commands/config.ts +610 -0
- package/src/commands/deploy-enhanced.ts +354 -0
- package/src/commands/deploy.ts +204 -0
- package/src/commands/init-enhanced.ts +1994 -0
- package/src/deployment/manager.ts +356 -0
- package/src/index.ts +47 -19
- package/templates/starter/.env.example +0 -44
- package/templates/starter/.gitignore.template +0 -4
- package/templates/starter/donkeylabs.config.ts +0 -6
- package/templates/starter/package.json +0 -21
- package/templates/starter/src/index.ts +0 -54
- package/templates/starter/src/plugins/stats/index.ts +0 -105
- package/templates/starter/src/routes/health/handlers/ping.ts +0 -22
- package/templates/starter/src/routes/health/index.ts +0 -19
- package/templates/starter/tsconfig.json +0 -27
- package/templates/sveltekit-app/.env.example +0 -59
- package/templates/sveltekit-app/README.md +0 -103
- package/templates/sveltekit-app/bun.lock +0 -683
- package/templates/sveltekit-app/donkeylabs.config.ts +0 -12
- package/templates/sveltekit-app/package.json +0 -38
- package/templates/sveltekit-app/src/app.css +0 -40
- package/templates/sveltekit-app/src/app.html +0 -12
- package/templates/sveltekit-app/src/hooks.server.ts +0 -4
- package/templates/sveltekit-app/src/lib/components/ui/badge/badge.svelte +0 -30
- package/templates/sveltekit-app/src/lib/components/ui/badge/index.ts +0 -3
- package/templates/sveltekit-app/src/lib/components/ui/button/button.svelte +0 -48
- package/templates/sveltekit-app/src/lib/components/ui/button/index.ts +0 -9
- package/templates/sveltekit-app/src/lib/components/ui/card/card-content.svelte +0 -18
- package/templates/sveltekit-app/src/lib/components/ui/card/card-description.svelte +0 -18
- package/templates/sveltekit-app/src/lib/components/ui/card/card-footer.svelte +0 -18
- package/templates/sveltekit-app/src/lib/components/ui/card/card-header.svelte +0 -18
- package/templates/sveltekit-app/src/lib/components/ui/card/card-title.svelte +0 -18
- package/templates/sveltekit-app/src/lib/components/ui/card/card.svelte +0 -21
- package/templates/sveltekit-app/src/lib/components/ui/card/index.ts +0 -21
- package/templates/sveltekit-app/src/lib/components/ui/index.ts +0 -4
- package/templates/sveltekit-app/src/lib/components/ui/input/index.ts +0 -2
- package/templates/sveltekit-app/src/lib/components/ui/input/input.svelte +0 -20
- package/templates/sveltekit-app/src/lib/permissions.ts +0 -213
- package/templates/sveltekit-app/src/lib/utils/index.ts +0 -6
- package/templates/sveltekit-app/src/routes/+layout.svelte +0 -8
- package/templates/sveltekit-app/src/routes/+page.server.ts +0 -25
- package/templates/sveltekit-app/src/routes/+page.svelte +0 -680
- package/templates/sveltekit-app/src/routes/workflows/+page.server.ts +0 -23
- package/templates/sveltekit-app/src/routes/workflows/+page.svelte +0 -522
- package/templates/sveltekit-app/src/server/events.ts +0 -28
- package/templates/sveltekit-app/src/server/index.ts +0 -124
- package/templates/sveltekit-app/src/server/plugins/auth/auth.test.ts +0 -377
- package/templates/sveltekit-app/src/server/plugins/auth/index.ts +0 -815
- package/templates/sveltekit-app/src/server/plugins/auth/migrations/001_create_users.ts +0 -25
- package/templates/sveltekit-app/src/server/plugins/auth/migrations/002_create_sessions.ts +0 -32
- package/templates/sveltekit-app/src/server/plugins/auth/migrations/003_create_refresh_tokens.ts +0 -33
- package/templates/sveltekit-app/src/server/plugins/auth/migrations/004_create_passkeys.ts +0 -60
- package/templates/sveltekit-app/src/server/plugins/auth/schema.ts +0 -65
- package/templates/sveltekit-app/src/server/plugins/demo/index.ts +0 -262
- package/templates/sveltekit-app/src/server/plugins/email/email.test.ts +0 -369
- package/templates/sveltekit-app/src/server/plugins/email/index.ts +0 -411
- package/templates/sveltekit-app/src/server/plugins/email/migrations/001_create_email_tokens.ts +0 -33
- package/templates/sveltekit-app/src/server/plugins/email/schema.ts +0 -24
- package/templates/sveltekit-app/src/server/plugins/permissions/index.ts +0 -1048
- package/templates/sveltekit-app/src/server/plugins/permissions/migrations/001_create_tenants.ts +0 -63
- package/templates/sveltekit-app/src/server/plugins/permissions/migrations/002_create_roles.ts +0 -90
- package/templates/sveltekit-app/src/server/plugins/permissions/migrations/003_create_resource_grants.ts +0 -50
- package/templates/sveltekit-app/src/server/plugins/permissions/permissions.test.ts +0 -566
- package/templates/sveltekit-app/src/server/plugins/permissions/schema.ts +0 -67
- package/templates/sveltekit-app/src/server/plugins/workflow-demo/index.ts +0 -198
- package/templates/sveltekit-app/src/server/routes/auth/auth.schemas.ts +0 -66
- package/templates/sveltekit-app/src/server/routes/auth/handlers/login.handler.ts +0 -18
- package/templates/sveltekit-app/src/server/routes/auth/handlers/logout.handler.ts +0 -16
- package/templates/sveltekit-app/src/server/routes/auth/handlers/me.handler.ts +0 -20
- package/templates/sveltekit-app/src/server/routes/auth/handlers/refresh.handler.ts +0 -17
- package/templates/sveltekit-app/src/server/routes/auth/handlers/register.handler.ts +0 -19
- package/templates/sveltekit-app/src/server/routes/auth/handlers/update-profile.handler.ts +0 -21
- package/templates/sveltekit-app/src/server/routes/auth/index.ts +0 -73
- package/templates/sveltekit-app/src/server/routes/demo.ts +0 -464
- package/templates/sveltekit-app/src/server/routes/example/example.schemas.ts +0 -22
- package/templates/sveltekit-app/src/server/routes/example/handlers/greet.handler.ts +0 -21
- package/templates/sveltekit-app/src/server/routes/example/index.ts +0 -28
- package/templates/sveltekit-app/src/server/routes/permissions/index.ts +0 -248
- package/templates/sveltekit-app/src/server/routes/tenants/index.ts +0 -339
- package/templates/sveltekit-app/static/robots.txt +0 -3
- package/templates/sveltekit-app/svelte.config.ts +0 -17
- package/templates/sveltekit-app/tsconfig.json +0 -20
- package/templates/sveltekit-app/vite.config.ts +0 -12
|
@@ -1,566 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Permissions Plugin Tests
|
|
3
|
-
*
|
|
4
|
-
* Tests for multi-tenant RBAC:
|
|
5
|
-
* - Tenant management
|
|
6
|
-
* - Role management
|
|
7
|
-
* - Permission checking
|
|
8
|
-
* - Resource grants
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { describe, it, expect, beforeEach, afterEach } from "bun:test";
|
|
12
|
-
import { createTestHarness } from "@donkeylabs/server";
|
|
13
|
-
import { permissionsPlugin } from "./index";
|
|
14
|
-
import type { Kysely } from "kysely";
|
|
15
|
-
|
|
16
|
-
// Test permission config
|
|
17
|
-
const testPermissions = {
|
|
18
|
-
documents: ["create", "read", "write", "delete", "admin"],
|
|
19
|
-
members: ["invite", "remove", "list"],
|
|
20
|
-
roles: ["create", "assign", "manage"],
|
|
21
|
-
} as const;
|
|
22
|
-
|
|
23
|
-
// Helper to create test harness with permissions plugin
|
|
24
|
-
async function createPermissionsTestHarness() {
|
|
25
|
-
const harness = await createTestHarness(permissionsPlugin({
|
|
26
|
-
permissions: testPermissions,
|
|
27
|
-
defaultRoles: [
|
|
28
|
-
{ name: "Admin", permissions: ["*"], isDefault: false },
|
|
29
|
-
{ name: "Member", permissions: ["documents.read", "documents.write", "members.list"], isDefault: true },
|
|
30
|
-
{ name: "Viewer", permissions: ["documents.read", "members.list"], isDefault: false },
|
|
31
|
-
],
|
|
32
|
-
}));
|
|
33
|
-
return { ...harness, permissions: harness.manager.getServices().permissions };
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Helper to create a test user in the database
|
|
37
|
-
async function createTestUser(db: Kysely<any>, email: string): Promise<string> {
|
|
38
|
-
const id = crypto.randomUUID();
|
|
39
|
-
await db
|
|
40
|
-
.insertInto("users")
|
|
41
|
-
.values({
|
|
42
|
-
id,
|
|
43
|
-
email,
|
|
44
|
-
password_hash: "test",
|
|
45
|
-
name: "Test User",
|
|
46
|
-
created_at: new Date().toISOString(),
|
|
47
|
-
updated_at: new Date().toISOString(),
|
|
48
|
-
})
|
|
49
|
-
.onConflict((oc: any) => oc.doNothing())
|
|
50
|
-
.execute()
|
|
51
|
-
.catch(() => {
|
|
52
|
-
// Table might not exist if auth plugin not registered
|
|
53
|
-
});
|
|
54
|
-
return id;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// ==========================================
|
|
58
|
-
// Tenant Management Tests
|
|
59
|
-
// ==========================================
|
|
60
|
-
describe("Permissions Plugin - Tenants", () => {
|
|
61
|
-
let harness: Awaited<ReturnType<typeof createPermissionsTestHarness>>;
|
|
62
|
-
|
|
63
|
-
beforeEach(async () => {
|
|
64
|
-
harness = await createPermissionsTestHarness();
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
afterEach(async () => {
|
|
68
|
-
await harness.db.destroy();
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it("should create a tenant", async () => {
|
|
72
|
-
const userId = crypto.randomUUID();
|
|
73
|
-
const tenant = await harness.permissions.createTenant({
|
|
74
|
-
name: "Test Org",
|
|
75
|
-
slug: "test-org",
|
|
76
|
-
ownerId: userId,
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
expect(tenant).toBeDefined();
|
|
80
|
-
expect(tenant.name).toBe("Test Org");
|
|
81
|
-
expect(tenant.slug).toBe("test-org");
|
|
82
|
-
expect(tenant.id).toBeDefined();
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it("should get tenant by ID", async () => {
|
|
86
|
-
const userId = crypto.randomUUID();
|
|
87
|
-
const created = await harness.permissions.createTenant({
|
|
88
|
-
name: "Test Org",
|
|
89
|
-
slug: "test-org",
|
|
90
|
-
ownerId: userId,
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
const tenant = await harness.permissions.getTenant(created.id);
|
|
94
|
-
expect(tenant).toBeDefined();
|
|
95
|
-
expect(tenant?.name).toBe("Test Org");
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it("should get tenant by slug", async () => {
|
|
99
|
-
const userId = crypto.randomUUID();
|
|
100
|
-
await harness.permissions.createTenant({
|
|
101
|
-
name: "Test Org",
|
|
102
|
-
slug: "test-org",
|
|
103
|
-
ownerId: userId,
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
const tenant = await harness.permissions.getTenantBySlug("test-org");
|
|
107
|
-
expect(tenant).toBeDefined();
|
|
108
|
-
expect(tenant?.slug).toBe("test-org");
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it("should return null for non-existent tenant", async () => {
|
|
112
|
-
const tenant = await harness.permissions.getTenant("non-existent");
|
|
113
|
-
expect(tenant).toBeNull();
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it("should add owner as admin member on creation", async () => {
|
|
117
|
-
const userId = crypto.randomUUID();
|
|
118
|
-
const tenant = await harness.permissions.createTenant({
|
|
119
|
-
name: "Test Org",
|
|
120
|
-
slug: "test-org",
|
|
121
|
-
ownerId: userId,
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
const isMember = await harness.permissions.isTenantMember(userId, tenant.id);
|
|
125
|
-
expect(isMember).toBe(true);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it("should get user tenants", async () => {
|
|
129
|
-
const userId = crypto.randomUUID();
|
|
130
|
-
await harness.permissions.createTenant({
|
|
131
|
-
name: "Org 1",
|
|
132
|
-
slug: "org-1",
|
|
133
|
-
ownerId: userId,
|
|
134
|
-
});
|
|
135
|
-
await harness.permissions.createTenant({
|
|
136
|
-
name: "Org 2",
|
|
137
|
-
slug: "org-2",
|
|
138
|
-
ownerId: userId,
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
const tenants = await harness.permissions.getUserTenants(userId);
|
|
142
|
-
expect(tenants.length).toBe(2);
|
|
143
|
-
});
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
// ==========================================
|
|
147
|
-
// Membership Tests
|
|
148
|
-
// ==========================================
|
|
149
|
-
describe("Permissions Plugin - Membership", () => {
|
|
150
|
-
let harness: Awaited<ReturnType<typeof createPermissionsTestHarness>>;
|
|
151
|
-
let tenantId: string;
|
|
152
|
-
let ownerId: string;
|
|
153
|
-
|
|
154
|
-
beforeEach(async () => {
|
|
155
|
-
harness = await createPermissionsTestHarness();
|
|
156
|
-
ownerId = crypto.randomUUID();
|
|
157
|
-
const tenant = await harness.permissions.createTenant({
|
|
158
|
-
name: "Test Org",
|
|
159
|
-
slug: "test-org",
|
|
160
|
-
ownerId,
|
|
161
|
-
});
|
|
162
|
-
tenantId = tenant.id;
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
afterEach(async () => {
|
|
166
|
-
await harness.db.destroy();
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
it("should add member to tenant", async () => {
|
|
170
|
-
const newUserId = crypto.randomUUID();
|
|
171
|
-
await harness.permissions.addTenantMember(tenantId, newUserId, ownerId);
|
|
172
|
-
|
|
173
|
-
const isMember = await harness.permissions.isTenantMember(newUserId, tenantId);
|
|
174
|
-
expect(isMember).toBe(true);
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
it("should remove member from tenant", async () => {
|
|
178
|
-
const newUserId = crypto.randomUUID();
|
|
179
|
-
await harness.permissions.addTenantMember(tenantId, newUserId, ownerId);
|
|
180
|
-
await harness.permissions.removeTenantMember(tenantId, newUserId);
|
|
181
|
-
|
|
182
|
-
const isMember = await harness.permissions.isTenantMember(newUserId, tenantId);
|
|
183
|
-
expect(isMember).toBe(false);
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
it("should not report non-member as member", async () => {
|
|
187
|
-
const randomUserId = crypto.randomUUID();
|
|
188
|
-
const isMember = await harness.permissions.isTenantMember(randomUserId, tenantId);
|
|
189
|
-
expect(isMember).toBe(false);
|
|
190
|
-
});
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
// ==========================================
|
|
194
|
-
// Role Tests
|
|
195
|
-
// ==========================================
|
|
196
|
-
describe("Permissions Plugin - Roles", () => {
|
|
197
|
-
let harness: Awaited<ReturnType<typeof createPermissionsTestHarness>>;
|
|
198
|
-
let tenantId: string;
|
|
199
|
-
let ownerId: string;
|
|
200
|
-
|
|
201
|
-
beforeEach(async () => {
|
|
202
|
-
harness = await createPermissionsTestHarness();
|
|
203
|
-
ownerId = crypto.randomUUID();
|
|
204
|
-
const tenant = await harness.permissions.createTenant({
|
|
205
|
-
name: "Test Org",
|
|
206
|
-
slug: "test-org",
|
|
207
|
-
ownerId,
|
|
208
|
-
});
|
|
209
|
-
tenantId = tenant.id;
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
afterEach(async () => {
|
|
213
|
-
await harness.db.destroy();
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
it("should create a role", async () => {
|
|
217
|
-
const role = await harness.permissions.createRole({
|
|
218
|
-
tenantId,
|
|
219
|
-
name: "Editor",
|
|
220
|
-
permissions: ["documents.read", "documents.write"],
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
expect(role).toBeDefined();
|
|
224
|
-
expect(role.name).toBe("Editor");
|
|
225
|
-
expect(role.permissions).toContain("documents.read");
|
|
226
|
-
expect(role.permissions).toContain("documents.write");
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
it("should get role by ID", async () => {
|
|
230
|
-
const created = await harness.permissions.createRole({
|
|
231
|
-
tenantId,
|
|
232
|
-
name: "Editor",
|
|
233
|
-
permissions: ["documents.read"],
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
const role = await harness.permissions.getRole(created.id);
|
|
237
|
-
expect(role).toBeDefined();
|
|
238
|
-
expect(role?.name).toBe("Editor");
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
it("should get tenant roles", async () => {
|
|
242
|
-
await harness.permissions.createRole({
|
|
243
|
-
tenantId,
|
|
244
|
-
name: "Editor",
|
|
245
|
-
permissions: ["documents.read"],
|
|
246
|
-
});
|
|
247
|
-
await harness.permissions.createRole({
|
|
248
|
-
tenantId,
|
|
249
|
-
name: "Reviewer",
|
|
250
|
-
permissions: ["documents.read"],
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
const roles = await harness.permissions.getTenantRoles(tenantId);
|
|
254
|
-
// Should include default Admin role + custom roles
|
|
255
|
-
expect(roles.length).toBeGreaterThanOrEqual(2);
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
it("should assign role to user", async () => {
|
|
259
|
-
const userId = crypto.randomUUID();
|
|
260
|
-
await harness.permissions.addTenantMember(tenantId, userId, ownerId);
|
|
261
|
-
|
|
262
|
-
const role = await harness.permissions.createRole({
|
|
263
|
-
tenantId,
|
|
264
|
-
name: "Editor",
|
|
265
|
-
permissions: ["documents.write"],
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
await harness.permissions.assignRole(userId, role.id, tenantId, ownerId);
|
|
269
|
-
|
|
270
|
-
const userRoles = await harness.permissions.getUserRoles(userId, tenantId);
|
|
271
|
-
expect(userRoles.some((r: { name: string }) => r.name === "Editor")).toBe(true);
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
it("should revoke role from user", async () => {
|
|
275
|
-
const userId = crypto.randomUUID();
|
|
276
|
-
await harness.permissions.addTenantMember(tenantId, userId, ownerId);
|
|
277
|
-
|
|
278
|
-
const role = await harness.permissions.createRole({
|
|
279
|
-
tenantId,
|
|
280
|
-
name: "Editor",
|
|
281
|
-
permissions: ["documents.write"],
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
await harness.permissions.assignRole(userId, role.id, tenantId, ownerId);
|
|
285
|
-
await harness.permissions.revokeRole(userId, role.id, tenantId);
|
|
286
|
-
|
|
287
|
-
const userRoles = await harness.permissions.getUserRoles(userId, tenantId);
|
|
288
|
-
expect(userRoles.some((r: { name: string }) => r.name === "Editor")).toBe(false);
|
|
289
|
-
});
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
// ==========================================
|
|
293
|
-
// Permission Checking Tests
|
|
294
|
-
// ==========================================
|
|
295
|
-
describe("Permissions Plugin - Permission Checks", () => {
|
|
296
|
-
let harness: Awaited<ReturnType<typeof createPermissionsTestHarness>>;
|
|
297
|
-
let tenantId: string;
|
|
298
|
-
let ownerId: string;
|
|
299
|
-
|
|
300
|
-
beforeEach(async () => {
|
|
301
|
-
harness = await createPermissionsTestHarness();
|
|
302
|
-
ownerId = crypto.randomUUID();
|
|
303
|
-
const tenant = await harness.permissions.createTenant({
|
|
304
|
-
name: "Test Org",
|
|
305
|
-
slug: "test-org",
|
|
306
|
-
ownerId,
|
|
307
|
-
});
|
|
308
|
-
tenantId = tenant.id;
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
afterEach(async () => {
|
|
312
|
-
await harness.db.destroy();
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
it("should check permission from role", async () => {
|
|
316
|
-
const userId = crypto.randomUUID();
|
|
317
|
-
await harness.permissions.addTenantMember(tenantId, userId, ownerId);
|
|
318
|
-
|
|
319
|
-
const role = await harness.permissions.createRole({
|
|
320
|
-
tenantId,
|
|
321
|
-
name: "Editor",
|
|
322
|
-
permissions: ["documents.read", "documents.write"],
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
await harness.permissions.assignRole(userId, role.id, tenantId, ownerId);
|
|
326
|
-
|
|
327
|
-
const canRead = await harness.permissions.hasPermission(userId, tenantId, "documents.read");
|
|
328
|
-
const canWrite = await harness.permissions.hasPermission(userId, tenantId, "documents.write");
|
|
329
|
-
const canDelete = await harness.permissions.hasPermission(userId, tenantId, "documents.delete");
|
|
330
|
-
|
|
331
|
-
expect(canRead).toBe(true);
|
|
332
|
-
expect(canWrite).toBe(true);
|
|
333
|
-
expect(canDelete).toBe(false);
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
it("should handle wildcard permissions", async () => {
|
|
337
|
-
// Owner should have admin role with wildcard
|
|
338
|
-
const hasAll = await harness.permissions.hasPermission(ownerId, tenantId, "documents.delete");
|
|
339
|
-
expect(hasAll).toBe(true);
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
it("should get user permissions", async () => {
|
|
343
|
-
const userId = crypto.randomUUID();
|
|
344
|
-
await harness.permissions.addTenantMember(tenantId, userId, ownerId);
|
|
345
|
-
|
|
346
|
-
const role = await harness.permissions.createRole({
|
|
347
|
-
tenantId,
|
|
348
|
-
name: "Editor",
|
|
349
|
-
permissions: ["documents.read", "documents.write"],
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
await harness.permissions.assignRole(userId, role.id, tenantId, ownerId);
|
|
353
|
-
|
|
354
|
-
const permissions = await harness.permissions.getUserPermissions(userId, tenantId);
|
|
355
|
-
expect(permissions).toContain("documents.read");
|
|
356
|
-
expect(permissions).toContain("documents.write");
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
it("should reject permission for non-member", async () => {
|
|
360
|
-
const randomUserId = crypto.randomUUID();
|
|
361
|
-
const hasPermission = await harness.permissions.hasPermission(
|
|
362
|
-
randomUserId,
|
|
363
|
-
tenantId,
|
|
364
|
-
"documents.read"
|
|
365
|
-
);
|
|
366
|
-
expect(hasPermission).toBe(false);
|
|
367
|
-
});
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
// ==========================================
|
|
371
|
-
// Resource Grant Tests
|
|
372
|
-
// ==========================================
|
|
373
|
-
describe("Permissions Plugin - Resource Grants", () => {
|
|
374
|
-
let harness: Awaited<ReturnType<typeof createPermissionsTestHarness>>;
|
|
375
|
-
let tenantId: string;
|
|
376
|
-
let ownerId: string;
|
|
377
|
-
|
|
378
|
-
beforeEach(async () => {
|
|
379
|
-
harness = await createPermissionsTestHarness();
|
|
380
|
-
ownerId = crypto.randomUUID();
|
|
381
|
-
const tenant = await harness.permissions.createTenant({
|
|
382
|
-
name: "Test Org",
|
|
383
|
-
slug: "test-org",
|
|
384
|
-
ownerId,
|
|
385
|
-
});
|
|
386
|
-
tenantId = tenant.id;
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
afterEach(async () => {
|
|
390
|
-
await harness.db.destroy();
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
it("should grant access to specific resource", async () => {
|
|
394
|
-
const userId = crypto.randomUUID();
|
|
395
|
-
await harness.permissions.addTenantMember(tenantId, userId, ownerId);
|
|
396
|
-
|
|
397
|
-
await harness.permissions.grantAccess({
|
|
398
|
-
granteeId: userId,
|
|
399
|
-
granteeType: "user",
|
|
400
|
-
tenantId,
|
|
401
|
-
resourceType: "document",
|
|
402
|
-
resourceId: "doc-123",
|
|
403
|
-
permissions: ["read"],
|
|
404
|
-
grantedBy: ownerId,
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
const canAccess = await harness.permissions.canAccess(
|
|
408
|
-
userId,
|
|
409
|
-
tenantId,
|
|
410
|
-
"document",
|
|
411
|
-
"doc-123",
|
|
412
|
-
"read"
|
|
413
|
-
);
|
|
414
|
-
|
|
415
|
-
expect(canAccess).toBe(true);
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
it("should reject access without grant", async () => {
|
|
419
|
-
const userId = crypto.randomUUID();
|
|
420
|
-
await harness.permissions.addTenantMember(tenantId, userId, ownerId);
|
|
421
|
-
|
|
422
|
-
const canAccess = await harness.permissions.canAccess(
|
|
423
|
-
userId,
|
|
424
|
-
tenantId,
|
|
425
|
-
"document",
|
|
426
|
-
"doc-123",
|
|
427
|
-
"read"
|
|
428
|
-
);
|
|
429
|
-
|
|
430
|
-
expect(canAccess).toBe(false);
|
|
431
|
-
});
|
|
432
|
-
|
|
433
|
-
it("should allow owner access", async () => {
|
|
434
|
-
const userId = crypto.randomUUID();
|
|
435
|
-
await harness.permissions.addTenantMember(tenantId, userId, ownerId);
|
|
436
|
-
|
|
437
|
-
// Check access with owner - should be allowed
|
|
438
|
-
const canAccess = await harness.permissions.canAccess(
|
|
439
|
-
userId,
|
|
440
|
-
tenantId,
|
|
441
|
-
"document",
|
|
442
|
-
"doc-123",
|
|
443
|
-
"read",
|
|
444
|
-
userId // ownerId matches userId
|
|
445
|
-
);
|
|
446
|
-
|
|
447
|
-
expect(canAccess).toBe(true);
|
|
448
|
-
});
|
|
449
|
-
|
|
450
|
-
it("should revoke access", async () => {
|
|
451
|
-
const userId = crypto.randomUUID();
|
|
452
|
-
await harness.permissions.addTenantMember(tenantId, userId, ownerId);
|
|
453
|
-
|
|
454
|
-
await harness.permissions.grantAccess({
|
|
455
|
-
granteeId: userId,
|
|
456
|
-
granteeType: "user",
|
|
457
|
-
tenantId,
|
|
458
|
-
resourceType: "document",
|
|
459
|
-
resourceId: "doc-123",
|
|
460
|
-
permissions: ["read"],
|
|
461
|
-
grantedBy: ownerId,
|
|
462
|
-
});
|
|
463
|
-
|
|
464
|
-
await harness.permissions.revokeAccess({
|
|
465
|
-
granteeId: userId,
|
|
466
|
-
granteeType: "user",
|
|
467
|
-
tenantId,
|
|
468
|
-
resourceType: "document",
|
|
469
|
-
resourceId: "doc-123",
|
|
470
|
-
});
|
|
471
|
-
|
|
472
|
-
const canAccess = await harness.permissions.canAccess(
|
|
473
|
-
userId,
|
|
474
|
-
tenantId,
|
|
475
|
-
"document",
|
|
476
|
-
"doc-123",
|
|
477
|
-
"read"
|
|
478
|
-
);
|
|
479
|
-
|
|
480
|
-
expect(canAccess).toBe(false);
|
|
481
|
-
});
|
|
482
|
-
|
|
483
|
-
it("should get resource grants", async () => {
|
|
484
|
-
const userId = crypto.randomUUID();
|
|
485
|
-
await harness.permissions.addTenantMember(tenantId, userId, ownerId);
|
|
486
|
-
|
|
487
|
-
await harness.permissions.grantAccess({
|
|
488
|
-
granteeId: userId,
|
|
489
|
-
granteeType: "user",
|
|
490
|
-
tenantId,
|
|
491
|
-
resourceType: "document",
|
|
492
|
-
resourceId: "doc-123",
|
|
493
|
-
permissions: ["read", "write"],
|
|
494
|
-
grantedBy: ownerId,
|
|
495
|
-
});
|
|
496
|
-
|
|
497
|
-
const grants = await harness.permissions.getResourceGrants(
|
|
498
|
-
tenantId,
|
|
499
|
-
"document",
|
|
500
|
-
"doc-123"
|
|
501
|
-
);
|
|
502
|
-
|
|
503
|
-
// One grant record with multiple permissions
|
|
504
|
-
expect(grants.length).toBe(1);
|
|
505
|
-
expect(grants[0].permissions).toContain("read");
|
|
506
|
-
expect(grants[0].permissions).toContain("write");
|
|
507
|
-
});
|
|
508
|
-
});
|
|
509
|
-
|
|
510
|
-
// ==========================================
|
|
511
|
-
// Context Building Tests
|
|
512
|
-
// ==========================================
|
|
513
|
-
describe("Permissions Plugin - Context Building", () => {
|
|
514
|
-
let harness: Awaited<ReturnType<typeof createPermissionsTestHarness>>;
|
|
515
|
-
let tenantId: string;
|
|
516
|
-
let ownerId: string;
|
|
517
|
-
|
|
518
|
-
beforeEach(async () => {
|
|
519
|
-
harness = await createPermissionsTestHarness();
|
|
520
|
-
ownerId = crypto.randomUUID();
|
|
521
|
-
const tenant = await harness.permissions.createTenant({
|
|
522
|
-
name: "Test Org",
|
|
523
|
-
slug: "test-org",
|
|
524
|
-
ownerId,
|
|
525
|
-
});
|
|
526
|
-
tenantId = tenant.id;
|
|
527
|
-
});
|
|
528
|
-
|
|
529
|
-
afterEach(async () => {
|
|
530
|
-
await harness.db.destroy();
|
|
531
|
-
});
|
|
532
|
-
|
|
533
|
-
it("should build permission context", async () => {
|
|
534
|
-
const context = await harness.permissions.buildContext(ownerId, tenantId);
|
|
535
|
-
|
|
536
|
-
expect(context).toBeDefined();
|
|
537
|
-
expect(context.userId).toBe(ownerId);
|
|
538
|
-
expect(context.tenantId).toBe(tenantId);
|
|
539
|
-
expect(context.roles.length).toBeGreaterThan(0);
|
|
540
|
-
expect(context.permissions.size).toBeGreaterThan(0);
|
|
541
|
-
});
|
|
542
|
-
|
|
543
|
-
it("should get client context (serializable)", async () => {
|
|
544
|
-
const context = await harness.permissions.getClientContext(ownerId, tenantId);
|
|
545
|
-
|
|
546
|
-
expect(context).toBeDefined();
|
|
547
|
-
expect(context.tenantId).toBe(tenantId);
|
|
548
|
-
expect(Array.isArray(context.roles)).toBe(true);
|
|
549
|
-
expect(Array.isArray(context.permissions)).toBe(true);
|
|
550
|
-
});
|
|
551
|
-
});
|
|
552
|
-
|
|
553
|
-
// ==========================================
|
|
554
|
-
// Configuration Tests
|
|
555
|
-
// ==========================================
|
|
556
|
-
describe("Permissions Plugin - Configuration", () => {
|
|
557
|
-
it("should expose permission definitions", async () => {
|
|
558
|
-
const harness = await createPermissionsTestHarness();
|
|
559
|
-
const definitions = harness.permissions.getPermissionDefinitions();
|
|
560
|
-
|
|
561
|
-
expect(definitions).toBeDefined();
|
|
562
|
-
expect(definitions.documents).toBeDefined();
|
|
563
|
-
expect(definitions.members).toBeDefined();
|
|
564
|
-
await harness.db.destroy();
|
|
565
|
-
});
|
|
566
|
-
});
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This file was generated by kysely-codegen.
|
|
3
|
-
* Please do not edit it manually.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { ColumnType } from "kysely";
|
|
7
|
-
|
|
8
|
-
export type Generated<T> = T extends ColumnType<infer S, infer I, infer U>
|
|
9
|
-
? ColumnType<S, I | undefined, U>
|
|
10
|
-
: ColumnType<T, T | undefined, T>;
|
|
11
|
-
|
|
12
|
-
export interface ResourceGrants {
|
|
13
|
-
created_at: Generated<string>;
|
|
14
|
-
granted_by: string | null;
|
|
15
|
-
grantee_id: string;
|
|
16
|
-
grantee_type: string;
|
|
17
|
-
id: string | null;
|
|
18
|
-
permissions: Generated<string>;
|
|
19
|
-
resource_id: string;
|
|
20
|
-
resource_type: string;
|
|
21
|
-
tenant_id: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface Roles {
|
|
25
|
-
created_at: Generated<string>;
|
|
26
|
-
description: string | null;
|
|
27
|
-
id: string | null;
|
|
28
|
-
inherits_from: string | null;
|
|
29
|
-
is_default: Generated<number>;
|
|
30
|
-
name: string;
|
|
31
|
-
permissions: Generated<string>;
|
|
32
|
-
tenant_id: string | null;
|
|
33
|
-
updated_at: string;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface TenantMembers {
|
|
37
|
-
created_at: Generated<string>;
|
|
38
|
-
id: string | null;
|
|
39
|
-
tenant_id: string;
|
|
40
|
-
user_id: string;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export interface Tenants {
|
|
44
|
-
created_at: Generated<string>;
|
|
45
|
-
id: string | null;
|
|
46
|
-
name: string;
|
|
47
|
-
settings: string | null;
|
|
48
|
-
slug: string;
|
|
49
|
-
updated_at: string;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export interface UserRoles {
|
|
53
|
-
assigned_by: string | null;
|
|
54
|
-
created_at: Generated<string>;
|
|
55
|
-
id: string | null;
|
|
56
|
-
role_id: string;
|
|
57
|
-
tenant_id: string;
|
|
58
|
-
user_id: string;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export interface DB {
|
|
62
|
-
resource_grants: ResourceGrants;
|
|
63
|
-
roles: Roles;
|
|
64
|
-
tenant_members: TenantMembers;
|
|
65
|
-
tenants: Tenants;
|
|
66
|
-
user_roles: UserRoles;
|
|
67
|
-
}
|