@donkeylabs/cli 1.1.16 → 1.1.17
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/templates/sveltekit-app/src/lib/permissions.ts +203 -0
- package/templates/sveltekit-app/src/server/index.ts +45 -0
- package/templates/sveltekit-app/src/server/plugins/permissions/index.ts +1045 -0
- package/templates/sveltekit-app/src/server/plugins/permissions/migrations/001_create_tenants.ts +63 -0
- package/templates/sveltekit-app/src/server/plugins/permissions/migrations/002_create_roles.ts +90 -0
- package/templates/sveltekit-app/src/server/plugins/permissions/migrations/003_create_resource_grants.ts +50 -0
- package/templates/sveltekit-app/src/server/routes/permissions/index.ts +248 -0
- package/templates/sveltekit-app/src/server/routes/tenants/index.ts +339 -0
package/package.json
CHANGED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Permissions Helper for Svelte UI
|
|
3
|
+
*
|
|
4
|
+
* Provides reactive permission checking for UI locking.
|
|
5
|
+
*
|
|
6
|
+
* Usage in +page.server.ts:
|
|
7
|
+
* ```ts
|
|
8
|
+
* export const load = async ({ locals }) => {
|
|
9
|
+
* const api = createApi({ locals });
|
|
10
|
+
* const permissions = await api.permissions.context({ tenantId });
|
|
11
|
+
* return { permissions };
|
|
12
|
+
* };
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* Usage in +page.svelte:
|
|
16
|
+
* ```svelte
|
|
17
|
+
* <script>
|
|
18
|
+
* import { createPermissions } from "$lib/permissions";
|
|
19
|
+
* let { data } = $props();
|
|
20
|
+
* const can = createPermissions(data.permissions);
|
|
21
|
+
* </script>
|
|
22
|
+
*
|
|
23
|
+
* {#if can.has("documents.create")}
|
|
24
|
+
* <Button>Create Document</Button>
|
|
25
|
+
* {/if}
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { createApi } from "./api";
|
|
30
|
+
|
|
31
|
+
export interface PermissionContext {
|
|
32
|
+
tenantId: string;
|
|
33
|
+
roles: Array<{ id: string; name: string }>;
|
|
34
|
+
permissions: string[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface PermissionsHelper {
|
|
38
|
+
/** Check if user has a static permission */
|
|
39
|
+
has: (permission: string) => boolean;
|
|
40
|
+
|
|
41
|
+
/** Check if user has all of the specified permissions */
|
|
42
|
+
hasAll: (...permissions: string[]) => boolean;
|
|
43
|
+
|
|
44
|
+
/** Check if user has any of the specified permissions */
|
|
45
|
+
hasAny: (...permissions: string[]) => boolean;
|
|
46
|
+
|
|
47
|
+
/** Check if user has a specific role */
|
|
48
|
+
hasRole: (roleName: string) => boolean;
|
|
49
|
+
|
|
50
|
+
/** Get all permissions */
|
|
51
|
+
all: () => string[];
|
|
52
|
+
|
|
53
|
+
/** Get all roles */
|
|
54
|
+
roles: () => Array<{ id: string; name: string }>;
|
|
55
|
+
|
|
56
|
+
/** Get tenant ID */
|
|
57
|
+
tenantId: () => string;
|
|
58
|
+
|
|
59
|
+
/** Check resource access (async - makes API call) */
|
|
60
|
+
canAccess: (
|
|
61
|
+
resourceType: string,
|
|
62
|
+
resourceId: string,
|
|
63
|
+
action: "create" | "read" | "write" | "delete" | "admin",
|
|
64
|
+
ownerId?: string
|
|
65
|
+
) => Promise<boolean>;
|
|
66
|
+
|
|
67
|
+
/** Batch check resource access (async - makes single API call) */
|
|
68
|
+
canAccessMany: (
|
|
69
|
+
checks: Array<{
|
|
70
|
+
resourceType: string;
|
|
71
|
+
resourceId: string;
|
|
72
|
+
action: "create" | "read" | "write" | "delete" | "admin";
|
|
73
|
+
ownerId?: string;
|
|
74
|
+
}>
|
|
75
|
+
) => Promise<Record<string, boolean>>;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Create a permissions helper from context
|
|
80
|
+
*/
|
|
81
|
+
export function createPermissions(context: PermissionContext): PermissionsHelper {
|
|
82
|
+
const permissionSet = new Set(context.permissions);
|
|
83
|
+
const api = createApi();
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
has(permission: string): boolean {
|
|
87
|
+
// Exact match
|
|
88
|
+
if (permissionSet.has(permission)) return true;
|
|
89
|
+
|
|
90
|
+
// Wildcard match
|
|
91
|
+
if (permissionSet.has("*")) return true;
|
|
92
|
+
|
|
93
|
+
// Resource wildcard (e.g., "documents.*")
|
|
94
|
+
const [resource] = permission.split(".");
|
|
95
|
+
if (permissionSet.has(`${resource}.*`)) return true;
|
|
96
|
+
|
|
97
|
+
return false;
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
hasAll(...permissions: string[]): boolean {
|
|
101
|
+
return permissions.every((p) => this.has(p));
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
hasAny(...permissions: string[]): boolean {
|
|
105
|
+
return permissions.some((p) => this.has(p));
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
hasRole(roleName: string): boolean {
|
|
109
|
+
return context.roles.some(
|
|
110
|
+
(r) => r.name.toLowerCase() === roleName.toLowerCase()
|
|
111
|
+
);
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
all(): string[] {
|
|
115
|
+
return context.permissions;
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
roles(): Array<{ id: string; name: string }> {
|
|
119
|
+
return context.roles;
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
tenantId(): string {
|
|
123
|
+
return context.tenantId;
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
async canAccess(
|
|
127
|
+
resourceType: string,
|
|
128
|
+
resourceId: string,
|
|
129
|
+
action: "create" | "read" | "write" | "delete" | "admin",
|
|
130
|
+
ownerId?: string
|
|
131
|
+
): Promise<boolean> {
|
|
132
|
+
const result = await api.permissions.canAccess({
|
|
133
|
+
tenantId: context.tenantId,
|
|
134
|
+
checks: [{ resourceType, resourceId, action, ownerId }],
|
|
135
|
+
});
|
|
136
|
+
return result[`${resourceType}:${resourceId}:${action}`] ?? false;
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
async canAccessMany(
|
|
140
|
+
checks: Array<{
|
|
141
|
+
resourceType: string;
|
|
142
|
+
resourceId: string;
|
|
143
|
+
action: "create" | "read" | "write" | "delete" | "admin";
|
|
144
|
+
ownerId?: string;
|
|
145
|
+
}>
|
|
146
|
+
): Promise<Record<string, boolean>> {
|
|
147
|
+
return api.permissions.canAccess({
|
|
148
|
+
tenantId: context.tenantId,
|
|
149
|
+
checks,
|
|
150
|
+
});
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Svelte component helper - use in templates
|
|
157
|
+
*
|
|
158
|
+
* Usage:
|
|
159
|
+
* ```svelte
|
|
160
|
+
* <script>
|
|
161
|
+
* import { Can } from "$lib/permissions";
|
|
162
|
+
* let { data } = $props();
|
|
163
|
+
* </script>
|
|
164
|
+
*
|
|
165
|
+
* <Can permission="documents.create" context={data.permissions}>
|
|
166
|
+
* <Button>Create</Button>
|
|
167
|
+
* </Can>
|
|
168
|
+
* ```
|
|
169
|
+
*/
|
|
170
|
+
export function canCheck(
|
|
171
|
+
context: PermissionContext,
|
|
172
|
+
permission: string
|
|
173
|
+
): boolean {
|
|
174
|
+
const helper = createPermissions(context);
|
|
175
|
+
return helper.has(permission);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Type helper for defining permissions in your app
|
|
180
|
+
*
|
|
181
|
+
* Usage:
|
|
182
|
+
* ```ts
|
|
183
|
+
* const PERMISSIONS = definePermissions({
|
|
184
|
+
* documents: ["create", "read", "write", "delete", "admin"],
|
|
185
|
+
* users: ["invite", "remove", "manage"],
|
|
186
|
+
* billing: ["view", "manage"],
|
|
187
|
+
* } as const);
|
|
188
|
+
*
|
|
189
|
+
* // Type: "documents.create" | "documents.read" | ...
|
|
190
|
+
* type Permission = typeof PERMISSIONS[number];
|
|
191
|
+
* ```
|
|
192
|
+
*/
|
|
193
|
+
export function definePermissions<
|
|
194
|
+
T extends Record<string, readonly string[]>
|
|
195
|
+
>(permissions: T): Array<`${Extract<keyof T, string>}.${T[keyof T][number]}`> {
|
|
196
|
+
const result: string[] = [];
|
|
197
|
+
for (const [resource, actions] of Object.entries(permissions)) {
|
|
198
|
+
for (const action of actions) {
|
|
199
|
+
result.push(`${resource}.${action}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return result as any;
|
|
203
|
+
}
|
|
@@ -7,9 +7,12 @@ import { demoPlugin } from "./plugins/demo";
|
|
|
7
7
|
import { workflowDemoPlugin } from "./plugins/workflow-demo";
|
|
8
8
|
import { authPlugin } from "./plugins/auth";
|
|
9
9
|
import { emailPlugin } from "./plugins/email";
|
|
10
|
+
import { permissionsPlugin } from "./plugins/permissions";
|
|
10
11
|
import demoRoutes from "./routes/demo";
|
|
11
12
|
import { exampleRouter } from "./routes/example";
|
|
12
13
|
import { authRouter } from "./routes/auth";
|
|
14
|
+
import { permissionsRouter } from "./routes/permissions";
|
|
15
|
+
import { tenantsRouter } from "./routes/tenants";
|
|
13
16
|
|
|
14
17
|
// Simple in-memory database
|
|
15
18
|
const db = new Kysely<{}>({
|
|
@@ -67,11 +70,53 @@ server.registerPlugin(emailPlugin({
|
|
|
67
70
|
baseUrl: process.env.PUBLIC_BASE_URL || "http://localhost:5173",
|
|
68
71
|
}));
|
|
69
72
|
|
|
73
|
+
// =============================================================================
|
|
74
|
+
// PERMISSIONS PLUGIN - Multi-tenant RBAC
|
|
75
|
+
// =============================================================================
|
|
76
|
+
// Define your app's permissions here for type-safe checking.
|
|
77
|
+
// Client can use: api.permissions.check({ permissions: ["documents.create"] })
|
|
78
|
+
//
|
|
79
|
+
server.registerPlugin(permissionsPlugin({
|
|
80
|
+
permissions: {
|
|
81
|
+
// Documents
|
|
82
|
+
documents: ["create", "read", "write", "delete", "admin"],
|
|
83
|
+
// Members & Roles
|
|
84
|
+
members: ["invite", "remove", "list"],
|
|
85
|
+
roles: ["create", "assign", "manage"],
|
|
86
|
+
// Billing (example)
|
|
87
|
+
billing: ["view", "manage"],
|
|
88
|
+
} as const,
|
|
89
|
+
defaultRoles: [
|
|
90
|
+
{
|
|
91
|
+
name: "Admin",
|
|
92
|
+
permissions: ["*"], // Full access
|
|
93
|
+
isDefault: false,
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: "Member",
|
|
97
|
+
permissions: [
|
|
98
|
+
"documents.create",
|
|
99
|
+
"documents.read",
|
|
100
|
+
"documents.write",
|
|
101
|
+
"members.list",
|
|
102
|
+
],
|
|
103
|
+
isDefault: true, // Auto-assigned to new members
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: "Viewer",
|
|
107
|
+
permissions: ["documents.read", "members.list"],
|
|
108
|
+
isDefault: false,
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
}));
|
|
112
|
+
|
|
70
113
|
server.registerPlugin(demoPlugin);
|
|
71
114
|
server.registerPlugin(workflowDemoPlugin);
|
|
72
115
|
|
|
73
116
|
// Register routes
|
|
74
117
|
server.use(authRouter);
|
|
118
|
+
server.use(permissionsRouter);
|
|
119
|
+
server.use(tenantsRouter);
|
|
75
120
|
server.use(demoRoutes);
|
|
76
121
|
server.use(exampleRouter);
|
|
77
122
|
|