@lego-box/shell 1.0.5 → 1.0.7
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/.krasrc +13 -0
- package/dist/emulator/lego-box-shell-1.0.7.tgz +0 -0
- package/package.json +6 -3
- package/postcss.config.js +6 -0
- package/src/auth/auth-store.ts +33 -0
- package/src/auth/auth.ts +176 -0
- package/src/components/ProtectedPage.tsx +48 -0
- package/src/config/env.node.ts +38 -0
- package/src/config/env.ts +105 -0
- package/src/context/AbilityContext.tsx +213 -0
- package/src/context/PiralInstanceContext.tsx +17 -0
- package/src/hooks/index.ts +11 -0
- package/src/hooks/useAuditLogs.ts +190 -0
- package/src/hooks/useDebounce.ts +34 -0
- package/src/hooks/usePermissionGuard.tsx +39 -0
- package/src/hooks/usePermissions.ts +190 -0
- package/src/hooks/useRoles.ts +233 -0
- package/src/hooks/useTickets.ts +214 -0
- package/src/hooks/useUserLogins.ts +39 -0
- package/src/hooks/useUsers.ts +252 -0
- package/src/index.html +16 -0
- package/src/index.tsx +296 -0
- package/src/layout.tsx +246 -0
- package/src/migrations/config.ts +62 -0
- package/src/migrations/dev-migrations.ts +75 -0
- package/src/migrations/index.ts +13 -0
- package/src/migrations/run-migrations.ts +187 -0
- package/src/migrations/runner.ts +925 -0
- package/src/migrations/types.ts +207 -0
- package/src/migrations/utils.ts +264 -0
- package/src/pages/AuditLogsPage.tsx +378 -0
- package/src/pages/ContactSupportPage.tsx +610 -0
- package/src/pages/LandingPage.tsx +221 -0
- package/src/pages/LoginPage.tsx +217 -0
- package/src/pages/MigrationsPage.tsx +1364 -0
- package/src/pages/ProfilePage.tsx +335 -0
- package/src/pages/SettingsPage.tsx +101 -0
- package/src/pages/SystemHealthCheckPage.tsx +144 -0
- package/src/pages/UserManagementPage.tsx +1010 -0
- package/src/piral/api.ts +39 -0
- package/src/piral/auth-casl.ts +56 -0
- package/src/piral/menu.ts +102 -0
- package/src/piral/piral.json +4 -0
- package/src/services/telemetry.ts +84 -0
- package/src/styles/globals.css +1351 -0
- package/src/utils/auditLogger.ts +68 -0
- package/tailwind.config.js +86 -0
- package/webpack.config.js +89 -0
- package/dist/emulator/lego-box-shell-1.0.5.tgz +0 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migration System Types and Interfaces
|
|
3
|
+
*
|
|
4
|
+
* This file defines the core types for the RBAC migration system.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Represents a single database migration
|
|
9
|
+
*/
|
|
10
|
+
export interface Migration {
|
|
11
|
+
id: string;
|
|
12
|
+
filename: string;
|
|
13
|
+
timestamp: number;
|
|
14
|
+
name: string;
|
|
15
|
+
status: 'pending' | 'applied' | 'failed' | 'rolled_back';
|
|
16
|
+
appliedAt: string | null;
|
|
17
|
+
errorMessage?: string;
|
|
18
|
+
checksum: string;
|
|
19
|
+
batch: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Migration history entry for audit trail
|
|
24
|
+
*/
|
|
25
|
+
export interface MigrationHistory {
|
|
26
|
+
id: string;
|
|
27
|
+
migrationId: string;
|
|
28
|
+
action: 'apply' | 'rollback' | 'fail';
|
|
29
|
+
executedAt: string;
|
|
30
|
+
executedBy: string;
|
|
31
|
+
errorMessage?: string;
|
|
32
|
+
duration: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Field type for PocketBase collection schema
|
|
37
|
+
*/
|
|
38
|
+
export type FieldType =
|
|
39
|
+
| 'text'
|
|
40
|
+
| 'number'
|
|
41
|
+
| 'bool'
|
|
42
|
+
| 'email'
|
|
43
|
+
| 'url'
|
|
44
|
+
| 'date'
|
|
45
|
+
| 'select'
|
|
46
|
+
| 'json'
|
|
47
|
+
| 'file'
|
|
48
|
+
| 'relation';
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Field schema definition
|
|
52
|
+
*/
|
|
53
|
+
export interface FieldSchema {
|
|
54
|
+
name: string;
|
|
55
|
+
type: FieldType;
|
|
56
|
+
required?: boolean;
|
|
57
|
+
unique?: boolean;
|
|
58
|
+
options?: {
|
|
59
|
+
min?: number;
|
|
60
|
+
max?: number;
|
|
61
|
+
pattern?: string;
|
|
62
|
+
values?: string[];
|
|
63
|
+
maxSelect?: number;
|
|
64
|
+
collectionId?: string;
|
|
65
|
+
cascadeDelete?: boolean;
|
|
66
|
+
[key: string]: any;
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Collection schema definition
|
|
72
|
+
*/
|
|
73
|
+
export interface CollectionSchema {
|
|
74
|
+
name: string;
|
|
75
|
+
type?: 'base' | 'auth' | 'view';
|
|
76
|
+
fields: FieldSchema[];
|
|
77
|
+
indexes?: string[];
|
|
78
|
+
listRule?: string | null;
|
|
79
|
+
viewRule?: string | null;
|
|
80
|
+
createRule?: string | null;
|
|
81
|
+
updateRule?: string | null;
|
|
82
|
+
deleteRule?: string | null;
|
|
83
|
+
options?: {
|
|
84
|
+
allowEmailAuth?: boolean;
|
|
85
|
+
allowOAuth2Auth?: boolean;
|
|
86
|
+
allowUsernameAuth?: boolean;
|
|
87
|
+
exceptEmailDomains?: string[] | null;
|
|
88
|
+
onlyEmailDomains?: string[] | null;
|
|
89
|
+
minPasswordLength?: number;
|
|
90
|
+
requireEmail?: boolean;
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Permission action types
|
|
96
|
+
*/
|
|
97
|
+
export type PermissionAction = 'create' | 'read' | 'update' | 'delete';
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Permission definition
|
|
101
|
+
*/
|
|
102
|
+
export interface Permission {
|
|
103
|
+
id?: string;
|
|
104
|
+
name: string;
|
|
105
|
+
action: PermissionAction;
|
|
106
|
+
collection: string;
|
|
107
|
+
description?: string;
|
|
108
|
+
resourceType: 'collection' | 'field' | 'custom';
|
|
109
|
+
resourceName?: string;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Role definition
|
|
114
|
+
*/
|
|
115
|
+
export interface Role {
|
|
116
|
+
id?: string;
|
|
117
|
+
name: string;
|
|
118
|
+
description?: string;
|
|
119
|
+
permissions: string[];
|
|
120
|
+
organization: string;
|
|
121
|
+
isDefault?: boolean;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Organization definition
|
|
126
|
+
*/
|
|
127
|
+
export interface Organization {
|
|
128
|
+
id?: string;
|
|
129
|
+
name: string;
|
|
130
|
+
slug: string;
|
|
131
|
+
description?: string;
|
|
132
|
+
settings?: Record<string, any>;
|
|
133
|
+
isActive?: boolean;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Extended User with RBAC fields
|
|
138
|
+
*/
|
|
139
|
+
export interface RBACUser {
|
|
140
|
+
id?: string;
|
|
141
|
+
email: string;
|
|
142
|
+
name: string;
|
|
143
|
+
avatar?: string;
|
|
144
|
+
organization: string;
|
|
145
|
+
role: string;
|
|
146
|
+
is_superuser?: boolean;
|
|
147
|
+
isActive?: boolean;
|
|
148
|
+
permissions?: string[];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Migration context passed to each migration
|
|
153
|
+
*/
|
|
154
|
+
export interface MigrationContext {
|
|
155
|
+
pb: any; // PocketBase instance
|
|
156
|
+
log: (message: string, type?: 'info' | 'success' | 'error' | 'warn') => void;
|
|
157
|
+
createCollection: (schema: CollectionSchema) => Promise<void>;
|
|
158
|
+
updateCollection: (name: string, schema: Partial<CollectionSchema>) => Promise<void>;
|
|
159
|
+
deleteCollection: (name: string) => Promise<void>;
|
|
160
|
+
collectionExists: (name: string) => Promise<boolean>;
|
|
161
|
+
insert: (collection: string, data: any) => Promise<any>;
|
|
162
|
+
update: (collection: string, id: string, data: any) => Promise<any>;
|
|
163
|
+
delete: (collection: string, id: string) => Promise<void>;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Migration function signature
|
|
168
|
+
*/
|
|
169
|
+
export type MigrationFunction = (context: MigrationContext) => Promise<void>;
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Migration module interface
|
|
173
|
+
*/
|
|
174
|
+
export interface MigrationModule {
|
|
175
|
+
up: MigrationFunction;
|
|
176
|
+
down?: MigrationFunction;
|
|
177
|
+
id: string;
|
|
178
|
+
name: string;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Options for migration runner
|
|
183
|
+
*/
|
|
184
|
+
export interface MigrationRunnerOptions {
|
|
185
|
+
pocketbaseUrl: string;
|
|
186
|
+
adminEmail?: string;
|
|
187
|
+
adminPassword?: string;
|
|
188
|
+
migrationsPath: string;
|
|
189
|
+
autoMigrate?: boolean;
|
|
190
|
+
onProgress?: (message: string, type?: 'info' | 'success' | 'error' | 'warn') => void;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* API Rule generator function type
|
|
195
|
+
*/
|
|
196
|
+
export type APIRuleGenerator = (collectionName: string, action: PermissionAction) => string;
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Migration log entry for console logs
|
|
200
|
+
*/
|
|
201
|
+
export interface MigrationLog {
|
|
202
|
+
id?: string;
|
|
203
|
+
message: string;
|
|
204
|
+
type: 'command' | 'info' | 'success' | 'error';
|
|
205
|
+
timestamp: string;
|
|
206
|
+
sessionId?: string;
|
|
207
|
+
}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RBAC Utilities
|
|
3
|
+
*
|
|
4
|
+
* Helper functions for managing permissions, roles, and API rules dynamically.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PermissionAction, Permission, Role, Organization } from './types';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Generate a permission name from action and collection
|
|
11
|
+
*/
|
|
12
|
+
export function generatePermissionName(action: PermissionAction, collection: string): string {
|
|
13
|
+
return `${action}:${collection}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Parse a permission name into action and collection
|
|
18
|
+
*/
|
|
19
|
+
export function parsePermissionName(name: string): { action: PermissionAction; collection: string } | null {
|
|
20
|
+
const parts = name.split(':');
|
|
21
|
+
if (parts.length !== 2) return null;
|
|
22
|
+
|
|
23
|
+
const action = parts[0] as PermissionAction;
|
|
24
|
+
if (!['create', 'read', 'update', 'delete'].includes(action)) return null;
|
|
25
|
+
|
|
26
|
+
return { action, collection: parts[1] };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Generate all CRUD permissions for a collection
|
|
31
|
+
*/
|
|
32
|
+
export function generateCollectionPermissions(collection: string): string[] {
|
|
33
|
+
const actions: PermissionAction[] = ['create', 'read', 'update', 'delete'];
|
|
34
|
+
return actions.map(action => generatePermissionName(action, collection));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Generate API rule for a collection and action
|
|
39
|
+
*/
|
|
40
|
+
export function generateAPIRule(collection: string, action: PermissionAction): string {
|
|
41
|
+
const actionMap: Record<PermissionAction, string> = {
|
|
42
|
+
create: 'create',
|
|
43
|
+
read: 'read',
|
|
44
|
+
update: 'update',
|
|
45
|
+
delete: 'delete',
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Superusers can do everything
|
|
49
|
+
// Users with the specific permission in their role can access if in same organization
|
|
50
|
+
return `(@request.auth.is_superuser = true) || (@request.auth.role.permissions ?~ "${collection}:${actionMap[action]}" && @request.auth.organization = organization)`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Generate all API rules for a collection
|
|
55
|
+
*/
|
|
56
|
+
export function generateAllAPIRules(collection: string): {
|
|
57
|
+
listRule: string;
|
|
58
|
+
viewRule: string;
|
|
59
|
+
createRule: string;
|
|
60
|
+
updateRule: string;
|
|
61
|
+
deleteRule: string;
|
|
62
|
+
} {
|
|
63
|
+
return {
|
|
64
|
+
listRule: generateAPIRule(collection, 'read'),
|
|
65
|
+
viewRule: generateAPIRule(collection, 'read'),
|
|
66
|
+
createRule: generateAPIRule(collection, 'create'),
|
|
67
|
+
updateRule: generateAPIRule(collection, 'update'),
|
|
68
|
+
deleteRule: generateAPIRule(collection, 'delete'),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Check if user has a specific permission
|
|
74
|
+
*/
|
|
75
|
+
export function hasPermission(
|
|
76
|
+
userPermissions: string[],
|
|
77
|
+
rolePermissions: string[],
|
|
78
|
+
requiredPermission: string
|
|
79
|
+
): boolean {
|
|
80
|
+
// Direct user permissions take precedence
|
|
81
|
+
if (userPermissions.includes(requiredPermission)) return true;
|
|
82
|
+
|
|
83
|
+
// Check role permissions
|
|
84
|
+
if (rolePermissions.includes(requiredPermission)) return true;
|
|
85
|
+
|
|
86
|
+
// Check wildcard permissions (e.g., "read:*" grants read access to all)
|
|
87
|
+
const [action] = requiredPermission.split(':');
|
|
88
|
+
if (userPermissions.includes(`${action}:*`)) return true;
|
|
89
|
+
if (rolePermissions.includes(`${action}:*`)) return true;
|
|
90
|
+
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Check if user has permission for a specific action on a collection
|
|
96
|
+
*/
|
|
97
|
+
export function hasCollectionPermission(
|
|
98
|
+
userPermissions: string[],
|
|
99
|
+
rolePermissions: string[],
|
|
100
|
+
collection: string,
|
|
101
|
+
action: PermissionAction
|
|
102
|
+
): boolean {
|
|
103
|
+
const permissionName = generatePermissionName(action, collection);
|
|
104
|
+
return hasPermission(userPermissions, rolePermissions, permissionName);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Get all permissions for a user combining role and direct permissions
|
|
109
|
+
*/
|
|
110
|
+
export function getAllPermissions(
|
|
111
|
+
userPermissions: string[] = [],
|
|
112
|
+
rolePermissions: string[] = []
|
|
113
|
+
): string[] {
|
|
114
|
+
return Array.from(new Set([...rolePermissions, ...userPermissions]));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Role permission builder
|
|
119
|
+
* Fluent API for building role permissions
|
|
120
|
+
*/
|
|
121
|
+
export class RolePermissionBuilder {
|
|
122
|
+
private permissions: string[] = [];
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Grant permission for an action on a collection
|
|
126
|
+
*/
|
|
127
|
+
can(action: PermissionAction, collection: string): this {
|
|
128
|
+
this.permissions.push(generatePermissionName(action, collection));
|
|
129
|
+
return this;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Grant all CRUD permissions for a collection
|
|
134
|
+
*/
|
|
135
|
+
canManage(collection: string): this {
|
|
136
|
+
this.permissions.push(...generateCollectionPermissions(collection));
|
|
137
|
+
return this;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Grant read permission for a collection
|
|
142
|
+
*/
|
|
143
|
+
canRead(collection: string): this {
|
|
144
|
+
this.permissions.push(generatePermissionName('read', collection));
|
|
145
|
+
return this;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Grant create permission for a collection
|
|
150
|
+
*/
|
|
151
|
+
canCreate(collection: string): this {
|
|
152
|
+
this.permissions.push(generatePermissionName('create', collection));
|
|
153
|
+
return this;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Grant update permission for a collection
|
|
158
|
+
*/
|
|
159
|
+
canUpdate(collection: string): this {
|
|
160
|
+
this.permissions.push(generatePermissionName('update', collection));
|
|
161
|
+
return this;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Grant delete permission for a collection
|
|
166
|
+
*/
|
|
167
|
+
canDelete(collection: string): this {
|
|
168
|
+
this.permissions.push(generatePermissionName('delete', collection));
|
|
169
|
+
return this;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Build and return the permissions array
|
|
174
|
+
*/
|
|
175
|
+
build(): string[] {
|
|
176
|
+
return Array.from(new Set(this.permissions));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Create a new permission builder
|
|
182
|
+
*/
|
|
183
|
+
export function definePermissions(): RolePermissionBuilder {
|
|
184
|
+
return new RolePermissionBuilder();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Default role definitions
|
|
189
|
+
*/
|
|
190
|
+
export const DEFAULT_ROLES = {
|
|
191
|
+
superuser: (orgId: string) => ({
|
|
192
|
+
name: 'Superuser',
|
|
193
|
+
description: 'Full system access with all permissions',
|
|
194
|
+
organization: orgId,
|
|
195
|
+
isDefault: false,
|
|
196
|
+
}),
|
|
197
|
+
|
|
198
|
+
admin: (orgId: string) => ({
|
|
199
|
+
name: 'Admin',
|
|
200
|
+
description: 'Organization administrator',
|
|
201
|
+
organization: orgId,
|
|
202
|
+
isDefault: false,
|
|
203
|
+
}),
|
|
204
|
+
|
|
205
|
+
user: (orgId: string) => ({
|
|
206
|
+
name: 'User',
|
|
207
|
+
description: 'Standard user',
|
|
208
|
+
organization: orgId,
|
|
209
|
+
isDefault: true,
|
|
210
|
+
}),
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Default permissions for standard user role
|
|
215
|
+
*/
|
|
216
|
+
export function getDefaultUserPermissions(): string[] {
|
|
217
|
+
return [
|
|
218
|
+
'read:rbac_users',
|
|
219
|
+
'update:rbac_users',
|
|
220
|
+
'read:organizations',
|
|
221
|
+
'read:roles',
|
|
222
|
+
'read:permissions',
|
|
223
|
+
];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Validate permission name format
|
|
228
|
+
*/
|
|
229
|
+
export function isValidPermissionName(name: string): boolean {
|
|
230
|
+
return parsePermissionName(name) !== null;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Extract collection names from permissions
|
|
235
|
+
*/
|
|
236
|
+
export function extractCollections(permissions: string[]): string[] {
|
|
237
|
+
const collections = new Set<string>();
|
|
238
|
+
for (const perm of permissions) {
|
|
239
|
+
const parsed = parsePermissionName(perm);
|
|
240
|
+
if (parsed) {
|
|
241
|
+
collections.add(parsed.collection);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return Array.from(collections);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Group permissions by collection
|
|
249
|
+
*/
|
|
250
|
+
export function groupPermissionsByCollection(permissions: string[]): Record<string, PermissionAction[]> {
|
|
251
|
+
const grouped: Record<string, PermissionAction[]> = {};
|
|
252
|
+
|
|
253
|
+
for (const perm of permissions) {
|
|
254
|
+
const parsed = parsePermissionName(perm);
|
|
255
|
+
if (parsed) {
|
|
256
|
+
if (!grouped[parsed.collection]) {
|
|
257
|
+
grouped[parsed.collection] = [];
|
|
258
|
+
}
|
|
259
|
+
grouped[parsed.collection].push(parsed.action);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return grouped;
|
|
264
|
+
}
|