@makolabs/ripple 1.2.9 → 1.2.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/user-management/UserManagement.svelte +1 -0
- package/dist/user-management/UserModal.svelte +126 -1
- package/dist/user-management/adapters/UserManagement.remote.d.ts +10 -54
- package/dist/user-management/adapters/UserManagement.remote.js +145 -220
- package/dist/user-management/user-management.d.ts +14 -4
- package/package.json +3 -3
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
open = $bindable(),
|
|
9
9
|
user = $bindable(),
|
|
10
10
|
roles = [],
|
|
11
|
+
adapter,
|
|
11
12
|
onSave,
|
|
12
13
|
onClose,
|
|
13
14
|
class: className
|
|
@@ -20,6 +21,8 @@
|
|
|
20
21
|
let formErrors = $state<FormErrors>({});
|
|
21
22
|
let saving = $state(false);
|
|
22
23
|
let formElement = $state<HTMLFormElement | null>(null);
|
|
24
|
+
let showApiKey = $state(false);
|
|
25
|
+
let regeneratingApiKey = $state(false);
|
|
23
26
|
|
|
24
27
|
// Form data
|
|
25
28
|
let formData = $state<Partial<User>>({
|
|
@@ -165,10 +168,51 @@
|
|
|
165
168
|
}
|
|
166
169
|
}
|
|
167
170
|
|
|
171
|
+
async function handleRegenerateApiKey() {
|
|
172
|
+
if (!user?.id || !adapter?.generateApiKey || !formData.permissions) return;
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
regeneratingApiKey = true;
|
|
176
|
+
const result = await adapter.generateApiKey({
|
|
177
|
+
userId: user.id,
|
|
178
|
+
permissions: formData.permissions,
|
|
179
|
+
revokeOld: true
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// Update user's private_metadata with new API key
|
|
183
|
+
if (user && result.apiKey) {
|
|
184
|
+
user = {
|
|
185
|
+
...user,
|
|
186
|
+
private_metadata: {
|
|
187
|
+
...(user.private_metadata || {}),
|
|
188
|
+
mako_api_key: result.apiKey
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
} catch (error) {
|
|
193
|
+
console.error('Error regenerating API key:', error);
|
|
194
|
+
formErrors.apiKey = error instanceof Error ? error.message : 'Failed to regenerate API key';
|
|
195
|
+
} finally {
|
|
196
|
+
regeneratingApiKey = false;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
168
200
|
function getModalTitle() {
|
|
169
201
|
if (mode === 'create') return 'Create New User';
|
|
170
|
-
return `Edit ${getUserDisplayName(user)}`;
|
|
202
|
+
return `Edit ${getUserDisplayName(user ?? null)}`;
|
|
171
203
|
}
|
|
204
|
+
|
|
205
|
+
// Get API key from user's private_metadata
|
|
206
|
+
const apiKey = $derived(
|
|
207
|
+
user?.private_metadata &&
|
|
208
|
+
typeof user.private_metadata === 'object' &&
|
|
209
|
+
'mako_api_key' in user.private_metadata
|
|
210
|
+
? (user.private_metadata.mako_api_key as string) || ''
|
|
211
|
+
: ''
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
// Mask API key for display
|
|
215
|
+
const maskedApiKey = $derived(apiKey ? '•'.repeat(Math.min(apiKey.length, 40)) : '');
|
|
172
216
|
</script>
|
|
173
217
|
|
|
174
218
|
<Modal
|
|
@@ -244,6 +288,87 @@
|
|
|
244
288
|
<p class="mt-1 text-xs text-red-500">{formErrors.email}</p>
|
|
245
289
|
{/if}
|
|
246
290
|
</div>
|
|
291
|
+
|
|
292
|
+
<!-- Mako API Key (Edit mode only) -->
|
|
293
|
+
{#if mode === 'edit' && (apiKey || adapter?.generateApiKey)}
|
|
294
|
+
<div>
|
|
295
|
+
<label for="api-key" class="mb-1 block text-sm font-medium text-gray-700">
|
|
296
|
+
Mako API Key
|
|
297
|
+
</label>
|
|
298
|
+
<div class="flex gap-2">
|
|
299
|
+
<div class="relative flex-1">
|
|
300
|
+
<input
|
|
301
|
+
id="api-key"
|
|
302
|
+
type={showApiKey ? 'text' : 'password'}
|
|
303
|
+
value={showApiKey ? apiKey : maskedApiKey}
|
|
304
|
+
readonly
|
|
305
|
+
class="w-full rounded-lg border border-gray-300 bg-gray-50 px-3 py-2 pr-10 font-mono text-sm"
|
|
306
|
+
placeholder="No API key generated"
|
|
307
|
+
/>
|
|
308
|
+
<button
|
|
309
|
+
type="button"
|
|
310
|
+
onclick={() => (showApiKey = !showApiKey)}
|
|
311
|
+
class="absolute top-1/2 right-2 -translate-y-1/2 text-gray-500 hover:text-gray-700"
|
|
312
|
+
aria-label={showApiKey ? 'Hide API key' : 'Show API key'}
|
|
313
|
+
>
|
|
314
|
+
{#if showApiKey}
|
|
315
|
+
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
316
|
+
<path
|
|
317
|
+
stroke-linecap="round"
|
|
318
|
+
stroke-linejoin="round"
|
|
319
|
+
stroke-width="2"
|
|
320
|
+
d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.29 3.29m0 0A9.966 9.966 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"
|
|
321
|
+
></path>
|
|
322
|
+
</svg>
|
|
323
|
+
{:else}
|
|
324
|
+
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
325
|
+
<path
|
|
326
|
+
stroke-linecap="round"
|
|
327
|
+
stroke-linejoin="round"
|
|
328
|
+
stroke-width="2"
|
|
329
|
+
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
|
330
|
+
></path>
|
|
331
|
+
<path
|
|
332
|
+
stroke-linecap="round"
|
|
333
|
+
stroke-linejoin="round"
|
|
334
|
+
stroke-width="2"
|
|
335
|
+
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
|
|
336
|
+
></path>
|
|
337
|
+
</svg>
|
|
338
|
+
{/if}
|
|
339
|
+
</button>
|
|
340
|
+
</div>
|
|
341
|
+
{#if adapter?.generateApiKey}
|
|
342
|
+
<Button
|
|
343
|
+
type="button"
|
|
344
|
+
variant="outline"
|
|
345
|
+
onclick={handleRegenerateApiKey}
|
|
346
|
+
disabled={regeneratingApiKey ||
|
|
347
|
+
!formData.permissions ||
|
|
348
|
+
formData.permissions.length === 0}
|
|
349
|
+
isLoading={regeneratingApiKey}
|
|
350
|
+
>
|
|
351
|
+
<svg class="mr-2 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
352
|
+
<path
|
|
353
|
+
stroke-linecap="round"
|
|
354
|
+
stroke-linejoin="round"
|
|
355
|
+
stroke-width="2"
|
|
356
|
+
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
|
|
357
|
+
></path>
|
|
358
|
+
</svg>
|
|
359
|
+
Regenerate Key
|
|
360
|
+
</Button>
|
|
361
|
+
{/if}
|
|
362
|
+
</div>
|
|
363
|
+
{#if formErrors.apiKey}
|
|
364
|
+
<p class="mt-1 text-xs text-red-500">{formErrors.apiKey}</p>
|
|
365
|
+
{:else}
|
|
366
|
+
<p class="mt-1 text-xs text-gray-500">
|
|
367
|
+
API keys are system-managed and cannot be manually edited
|
|
368
|
+
</p>
|
|
369
|
+
{/if}
|
|
370
|
+
</div>
|
|
371
|
+
{/if}
|
|
247
372
|
</div>
|
|
248
373
|
|
|
249
374
|
<!-- Right Column: Permissions & Role -->
|
|
@@ -1,68 +1,24 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* User Management Remote Functions
|
|
3
|
-
*
|
|
4
|
-
* Complete implementation template for user management remote functions.
|
|
5
|
-
* This follows the same pattern as users.remote.ts and is designed to be
|
|
6
|
-
* used as-is in SvelteKit applications like sharkfin-frontend.
|
|
7
|
-
*
|
|
8
|
-
* Configuration:
|
|
9
|
-
* - Set environment variables: CLERK_SECRET_KEY, ADMIN_API_KEY, PRIVATE_BASE_AUTH_URL, ALLOWED_ORG_ID
|
|
10
|
-
* - Configure CLIENT_ID via ADMIN_CONFIG or env variable (CLIENT_ID)
|
|
11
|
-
* - Adjust permission prefix filter via PERMISSION_PREFIX env variable
|
|
12
|
-
*
|
|
13
|
-
* @see https://svelte.dev/docs/kit/remote-functions
|
|
14
|
-
*
|
|
15
|
-
* Usage in your app:
|
|
16
|
-
* ```svelte
|
|
17
|
-
* <script>
|
|
18
|
-
* import { UserManagement } from '@makolabs/ripple';
|
|
19
|
-
* import * as adapter from '../UserManagement.remote';
|
|
20
|
-
* </script>
|
|
21
|
-
*
|
|
22
|
-
* <UserManagement adapter={adapter} />
|
|
23
|
-
* ```
|
|
24
|
-
*/
|
|
25
1
|
import type { User } from '../user-management.js';
|
|
26
2
|
import type { GetUsersOptions, GetUsersResult } from './types.js';
|
|
27
|
-
/**
|
|
28
|
-
* Get users with pagination and sorting
|
|
29
|
-
*
|
|
30
|
-
* Transforms adapter options to Clerk API format
|
|
31
|
-
*/
|
|
32
3
|
export declare const getUsers: import("@sveltejs/kit").RemoteQueryFunction<GetUsersOptions, GetUsersResult>;
|
|
33
|
-
/**
|
|
34
|
-
* Create a new user
|
|
35
|
-
*
|
|
36
|
-
* Creates user in Clerk, optionally adds to organization, and creates admin key with permissions
|
|
37
|
-
*/
|
|
38
4
|
export declare const createUser: import("@sveltejs/kit").RemoteCommand<Partial<User>, Promise<User>>;
|
|
39
|
-
/**
|
|
40
|
-
* Update an existing user
|
|
41
|
-
*/
|
|
42
5
|
export declare const updateUser: import("@sveltejs/kit").RemoteCommand<{
|
|
43
6
|
userId: string;
|
|
44
7
|
userData: Partial<User>;
|
|
45
8
|
}, Promise<User>>;
|
|
46
|
-
/**
|
|
47
|
-
* Delete a single user
|
|
48
|
-
*/
|
|
49
9
|
export declare const deleteUser: import("@sveltejs/kit").RemoteCommand<string, Promise<void>>;
|
|
50
|
-
/**
|
|
51
|
-
* Delete multiple users
|
|
52
|
-
*/
|
|
53
10
|
export declare const deleteUsers: import("@sveltejs/kit").RemoteCommand<string[], Promise<void>>;
|
|
54
|
-
|
|
55
|
-
* Get permissions for a specific user
|
|
56
|
-
*
|
|
57
|
-
* Uses 'sub' (userId) parameter in admin API calls
|
|
58
|
-
*/
|
|
59
|
-
export declare const getUserPermissions: import("@sveltejs/kit").RemoteQueryFunction<string, string[]>;
|
|
60
|
-
/**
|
|
61
|
-
* Update permissions for a specific user
|
|
62
|
-
*
|
|
63
|
-
* Uses 'sub' (userId) parameter in admin API calls
|
|
64
|
-
*/
|
|
11
|
+
export declare const getUserPermissions: import("@sveltejs/kit").RemoteQueryFunction<string, Promise<string[]>>;
|
|
65
12
|
export declare const updateUserPermissions: import("@sveltejs/kit").RemoteCommand<{
|
|
66
13
|
userId: string;
|
|
67
14
|
permissions: string[];
|
|
68
15
|
}, Promise<void>>;
|
|
16
|
+
export declare const generateApiKey: import("@sveltejs/kit").RemoteCommand<{
|
|
17
|
+
userId: string;
|
|
18
|
+
permissions: string[];
|
|
19
|
+
revokeOld?: boolean;
|
|
20
|
+
}, Promise<{
|
|
21
|
+
success: boolean;
|
|
22
|
+
apiKey: string;
|
|
23
|
+
message: string;
|
|
24
|
+
}>>;
|
|
@@ -1,46 +1,18 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* User Management Remote Functions
|
|
3
|
-
*
|
|
4
|
-
* Complete implementation template for user management remote functions.
|
|
5
|
-
* This follows the same pattern as users.remote.ts and is designed to be
|
|
6
|
-
* used as-is in SvelteKit applications like sharkfin-frontend.
|
|
7
|
-
*
|
|
8
|
-
* Configuration:
|
|
9
|
-
* - Set environment variables: CLERK_SECRET_KEY, ADMIN_API_KEY, PRIVATE_BASE_AUTH_URL, ALLOWED_ORG_ID
|
|
10
|
-
* - Configure CLIENT_ID via ADMIN_CONFIG or env variable (CLIENT_ID)
|
|
11
|
-
* - Adjust permission prefix filter via PERMISSION_PREFIX env variable
|
|
12
|
-
*
|
|
13
|
-
* @see https://svelte.dev/docs/kit/remote-functions
|
|
14
|
-
*
|
|
15
|
-
* Usage in your app:
|
|
16
|
-
* ```svelte
|
|
17
|
-
* <script>
|
|
18
|
-
* import { UserManagement } from '@makolabs/ripple';
|
|
19
|
-
* import * as adapter from '../UserManagement.remote';
|
|
20
|
-
* </script>
|
|
21
|
-
*
|
|
22
|
-
* <UserManagement adapter={adapter} />
|
|
23
|
-
* ```
|
|
24
|
-
*/
|
|
25
1
|
import { query, command } from '$app/server';
|
|
26
2
|
import { env } from '$env/dynamic/private';
|
|
27
|
-
|
|
28
|
-
* Configuration
|
|
29
|
-
* Override these values or import from your config file
|
|
30
|
-
*/
|
|
31
|
-
// Option 1: Import from your config (recommended)
|
|
32
|
-
// import { ADMIN_CONFIG } from '../../constants/permissions';
|
|
33
|
-
// const CLIENT_ID = ADMIN_CONFIG.CLIENT_ID;
|
|
34
|
-
// Option 2: Use environment variable
|
|
35
|
-
const CLIENT_ID = env.CLIENT_ID || 'sharkfin'; // Default fallback
|
|
36
|
-
// Permission prefix filter - adjust based on your permission structure
|
|
37
|
-
// Example: 'sharkfin:' filters to only sharkfin permissions
|
|
3
|
+
const CLIENT_ID = env.CLIENT_ID || 'sharkfin';
|
|
38
4
|
const PERMISSION_PREFIX = env.PERMISSION_PREFIX || 'sharkfin:';
|
|
39
|
-
// Organization ID for adding users to organizations
|
|
40
5
|
const ORGANIZATION_ID = env.ALLOWED_ORG_ID;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
6
|
+
function handleClerkError(error, defaultMessage) {
|
|
7
|
+
if (error && typeof error === 'object' && 'status' in error && 'details' in error) {
|
|
8
|
+
const enrichedError = new Error('message' in error && typeof error.message === 'string' ? error.message : 'Unknown error');
|
|
9
|
+
enrichedError.status = error.status;
|
|
10
|
+
enrichedError.details = error.details;
|
|
11
|
+
enrichedError.clerkError = true;
|
|
12
|
+
throw enrichedError;
|
|
13
|
+
}
|
|
14
|
+
throw new Error(`${defaultMessage}: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
15
|
+
}
|
|
44
16
|
async function makeClerkRequest(endpoint, options = {}) {
|
|
45
17
|
const CLERK_SECRET_KEY = env.CLERK_SECRET_KEY;
|
|
46
18
|
if (!CLERK_SECRET_KEY) {
|
|
@@ -56,8 +28,7 @@ async function makeClerkRequest(endpoint, options = {}) {
|
|
|
56
28
|
});
|
|
57
29
|
if (!response.ok) {
|
|
58
30
|
const errorText = await response.text();
|
|
59
|
-
console.error(
|
|
60
|
-
// Try to parse error details
|
|
31
|
+
console.error(`[Clerk API] ${response.status} ${response.statusText} - ${errorText}`);
|
|
61
32
|
let errorDetails;
|
|
62
33
|
try {
|
|
63
34
|
errorDetails = JSON.parse(errorText);
|
|
@@ -65,7 +36,6 @@ async function makeClerkRequest(endpoint, options = {}) {
|
|
|
65
36
|
catch {
|
|
66
37
|
errorDetails = { message: errorText || `${response.status} ${response.statusText}` };
|
|
67
38
|
}
|
|
68
|
-
// Throw error with status and details for better frontend handling
|
|
69
39
|
const error = new Error(errorDetails.message || `Clerk API request failed: ${response.status} ${response.statusText}`);
|
|
70
40
|
error.status = response.status;
|
|
71
41
|
error.details = errorDetails;
|
|
@@ -73,9 +43,6 @@ async function makeClerkRequest(endpoint, options = {}) {
|
|
|
73
43
|
}
|
|
74
44
|
return response.json();
|
|
75
45
|
}
|
|
76
|
-
/**
|
|
77
|
-
* Helper: Make authenticated request to Admin API
|
|
78
|
-
*/
|
|
79
46
|
async function makeAdminRequest(endpoint, options = {}) {
|
|
80
47
|
const ADMIN_API_KEY = env.ADMIN_API_KEY;
|
|
81
48
|
const PRIVATE_BASE_AUTH_URL = env.PRIVATE_BASE_AUTH_URL;
|
|
@@ -98,60 +65,39 @@ async function makeAdminRequest(endpoint, options = {}) {
|
|
|
98
65
|
});
|
|
99
66
|
if (!response.ok) {
|
|
100
67
|
const errorText = await response.text();
|
|
101
|
-
console.error(
|
|
68
|
+
console.error(`[Admin API] ${response.status} ${response.statusText} - ${errorText}`);
|
|
102
69
|
throw new Error(`Admin API request failed: ${response.status} ${response.statusText} - ${errorText}`);
|
|
103
70
|
}
|
|
104
71
|
return response.json();
|
|
105
72
|
}
|
|
106
|
-
/**
|
|
107
|
-
* Helper: Create admin key with permissions for a user
|
|
108
|
-
*
|
|
109
|
-
* @param userId - The user ID (used as 'sub' in admin API)
|
|
110
|
-
* @param permissions - Array of permission strings
|
|
111
|
-
* @param clientId - Client ID (defaults to CLIENT_ID config)
|
|
112
|
-
*/
|
|
113
73
|
async function createUserPermissions(userId, permissions, clientId = CLIENT_ID) {
|
|
114
|
-
console.log(`🔑 [createUserPermissions] Creating admin key for user ${userId}`);
|
|
115
|
-
// Filter permissions by prefix if configured
|
|
116
74
|
const filteredPermissions = PERMISSION_PREFIX
|
|
117
75
|
? permissions.filter((scope) => scope.startsWith(PERMISSION_PREFIX))
|
|
118
76
|
: permissions;
|
|
119
77
|
if (filteredPermissions.length === 0) {
|
|
120
|
-
console.log(`⚠️ [createUserPermissions] No ${PERMISSION_PREFIX || ''} permissions provided, skipping`);
|
|
121
78
|
return null;
|
|
122
79
|
}
|
|
123
|
-
|
|
80
|
+
return await makeAdminRequest('/admin/keys', {
|
|
124
81
|
method: 'POST',
|
|
125
82
|
body: JSON.stringify({
|
|
126
83
|
client_id: clientId,
|
|
127
|
-
sub: userId,
|
|
84
|
+
sub: userId,
|
|
128
85
|
scopes: filteredPermissions
|
|
129
86
|
})
|
|
130
87
|
});
|
|
131
|
-
console.log(`✅ [createUserPermissions] Admin key created successfully for user ${userId}`);
|
|
132
|
-
return createData;
|
|
133
88
|
}
|
|
134
|
-
/**
|
|
135
|
-
* Get users with pagination and sorting
|
|
136
|
-
*
|
|
137
|
-
* Transforms adapter options to Clerk API format
|
|
138
|
-
*/
|
|
139
89
|
export const getUsers = query('unchecked', async (options) => {
|
|
140
|
-
console.log('🔍 [getUsers] Fetching users with options:', options);
|
|
141
90
|
try {
|
|
142
|
-
// Transform adapter options to Clerk API format
|
|
143
91
|
const limit = options.pageSize;
|
|
144
92
|
const offset = (options.page - 1) * options.pageSize;
|
|
145
|
-
// Build orderBy string
|
|
146
93
|
let orderBy = '';
|
|
147
94
|
if (options.sortBy) {
|
|
148
95
|
const prefix = options.sortOrder === 'desc' ? '-' : '';
|
|
149
96
|
orderBy = `${prefix}${options.sortBy}`;
|
|
150
97
|
}
|
|
151
98
|
else {
|
|
152
|
-
orderBy = '-created_at';
|
|
99
|
+
orderBy = '-created_at';
|
|
153
100
|
}
|
|
154
|
-
// Build Clerk API parameters
|
|
155
101
|
const params = new URLSearchParams({
|
|
156
102
|
limit: limit.toString(),
|
|
157
103
|
offset: offset.toString(),
|
|
@@ -159,91 +105,66 @@ export const getUsers = query('unchecked', async (options) => {
|
|
|
159
105
|
});
|
|
160
106
|
if (options.query)
|
|
161
107
|
params.append('query', options.query);
|
|
162
|
-
// Fetch users and count in parallel
|
|
163
108
|
const [usersData, countData] = await Promise.all([
|
|
164
109
|
makeClerkRequest(`/users?${params}`),
|
|
165
110
|
makeClerkRequest(`/users/count?${params}`)
|
|
166
111
|
]);
|
|
167
|
-
console.log(`✅ [getUsers] Fetched ${usersData.length} users, total: ${countData.total_count}`);
|
|
168
112
|
return {
|
|
169
113
|
users: usersData,
|
|
170
114
|
totalUsers: countData.total_count || 0
|
|
171
115
|
};
|
|
172
116
|
}
|
|
173
117
|
catch (error) {
|
|
174
|
-
console.error('
|
|
118
|
+
console.error('[getUsers] Error:', error);
|
|
175
119
|
throw new Error(`Failed to fetch users: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
176
120
|
}
|
|
177
121
|
});
|
|
178
|
-
/**
|
|
179
|
-
* Create a new user
|
|
180
|
-
*
|
|
181
|
-
* Creates user in Clerk, optionally adds to organization, and creates admin key with permissions
|
|
182
|
-
*/
|
|
183
122
|
export const createUser = command('unchecked', async (userData) => {
|
|
184
|
-
// Transform User to Clerk API format
|
|
185
123
|
const emailAddress = userData.email_addresses?.[0]?.email_address || '';
|
|
186
124
|
if (!emailAddress) {
|
|
187
125
|
throw new Error('Email address is required');
|
|
188
126
|
}
|
|
189
|
-
console.log('📝 [createUser] Creating new user:', emailAddress);
|
|
190
127
|
try {
|
|
191
|
-
// Extract permissions from userData
|
|
192
128
|
const { permissions, ...userDataOnly } = userData;
|
|
193
|
-
// Transform userData to match Clerk Backend API format (snake_case)
|
|
194
129
|
const clerkUserData = {
|
|
195
130
|
first_name: userDataOnly.first_name || '',
|
|
196
131
|
last_name: userDataOnly.last_name || '',
|
|
197
|
-
email_address: [emailAddress],
|
|
132
|
+
email_address: [emailAddress],
|
|
198
133
|
...(userDataOnly.username && { username: userDataOnly.username }),
|
|
199
134
|
...(userDataOnly.private_metadata && { private_metadata: userDataOnly.private_metadata })
|
|
200
135
|
};
|
|
201
|
-
// Step 1: Create user in Clerk
|
|
202
|
-
console.log('📝 [createUser] Creating user in Clerk...');
|
|
203
136
|
let result = await makeClerkRequest('/users', {
|
|
204
137
|
method: 'POST',
|
|
205
138
|
body: JSON.stringify(clerkUserData)
|
|
206
139
|
});
|
|
207
|
-
// Step 1.5: Add user to organization if configured
|
|
208
140
|
if (ORGANIZATION_ID) {
|
|
209
141
|
try {
|
|
210
|
-
console.log(`🏢 [createUser] Adding user ${result.id} to organization ${ORGANIZATION_ID}...`);
|
|
211
142
|
await makeClerkRequest(`/organizations/${ORGANIZATION_ID}/memberships`, {
|
|
212
143
|
method: 'POST',
|
|
213
144
|
body: JSON.stringify({
|
|
214
145
|
user_id: result.id,
|
|
215
|
-
role: 'org:member'
|
|
146
|
+
role: 'org:member'
|
|
216
147
|
})
|
|
217
148
|
});
|
|
218
|
-
console.log(`✅ [createUser] User added to organization successfully`);
|
|
219
149
|
}
|
|
220
150
|
catch (orgError) {
|
|
221
|
-
console.error(
|
|
222
|
-
// Clean up: delete the user since they can't be added to the org
|
|
151
|
+
console.error('[createUser] Failed to add user to organization:', orgError);
|
|
223
152
|
try {
|
|
224
153
|
await makeClerkRequest(`/users/${result.id}`, {
|
|
225
154
|
method: 'DELETE'
|
|
226
155
|
});
|
|
227
|
-
console.log(`🗑️ [createUser] User ${result.id} deleted due to org membership failure`);
|
|
228
156
|
}
|
|
229
157
|
catch (deleteError) {
|
|
230
|
-
console.error(
|
|
158
|
+
console.error('[createUser] Failed to delete user after org membership failure:', deleteError);
|
|
231
159
|
}
|
|
232
160
|
throw new Error(`Failed to add user to organization. User creation rolled back. Error: ${orgError instanceof Error ? orgError.message : String(orgError)}`);
|
|
233
161
|
}
|
|
234
162
|
}
|
|
235
|
-
else {
|
|
236
|
-
console.warn('⚠️ [createUser] ALLOWED_ORG_ID not configured, skipping organization membership');
|
|
237
|
-
}
|
|
238
|
-
// Step 2: Create admin key with permissions if permissions are provided
|
|
239
163
|
if (permissions && permissions.length > 0) {
|
|
240
164
|
try {
|
|
241
|
-
console.log(`🔑 [createUser] Creating permissions for new user: ${result.id}`);
|
|
242
165
|
const adminKeyResult = await createUserPermissions(result.id, permissions);
|
|
243
|
-
// Extract the API key from the admin service response
|
|
244
166
|
const apiKey = adminKeyResult?.data?.key;
|
|
245
167
|
if (adminKeyResult && apiKey) {
|
|
246
|
-
// Update user's private metadata with the API key
|
|
247
168
|
const updatedUser = await makeClerkRequest(`/users/${result.id}`, {
|
|
248
169
|
method: 'PATCH',
|
|
249
170
|
body: JSON.stringify({
|
|
@@ -253,60 +174,34 @@ export const createUser = command('unchecked', async (userData) => {
|
|
|
253
174
|
}
|
|
254
175
|
})
|
|
255
176
|
});
|
|
256
|
-
// Update the result with the updated user data
|
|
257
177
|
result = updatedUser;
|
|
258
|
-
console.log(`✅ [createUser] API key stored in user's private metadata`);
|
|
259
178
|
}
|
|
260
|
-
else {
|
|
261
|
-
console.warn(`⚠️ [createUser] No API key found in admin key result`);
|
|
262
|
-
}
|
|
263
|
-
// Add permission info to result
|
|
264
179
|
result.adminKey = adminKeyResult;
|
|
265
180
|
result.permissionsAssigned = permissions;
|
|
266
|
-
console.log(`✅ [createUser] User ${result.id} created with permissions successfully`);
|
|
267
181
|
}
|
|
268
182
|
catch (permissionError) {
|
|
269
|
-
console.error(
|
|
270
|
-
// Don't fail the entire operation, but warn
|
|
183
|
+
console.error('[createUser] Failed to assign permissions:', permissionError);
|
|
271
184
|
result.warning = 'User created but permissions assignment failed';
|
|
272
185
|
result.permissionError =
|
|
273
186
|
permissionError instanceof Error ? permissionError.message : String(permissionError);
|
|
274
187
|
}
|
|
275
188
|
}
|
|
276
|
-
else {
|
|
277
|
-
console.log(`⚠️ [createUser] User ${result.id} created without permissions`);
|
|
278
|
-
}
|
|
279
189
|
return result;
|
|
280
190
|
}
|
|
281
191
|
catch (error) {
|
|
282
|
-
console.error('
|
|
283
|
-
|
|
284
|
-
if (error && typeof error === 'object' && 'status' in error && 'details' in error) {
|
|
285
|
-
const enrichedError = new Error('message' in error && typeof error.message === 'string' ? error.message : 'Unknown error');
|
|
286
|
-
enrichedError.status = error.status;
|
|
287
|
-
enrichedError.details = error.details;
|
|
288
|
-
enrichedError.clerkError = true;
|
|
289
|
-
throw enrichedError;
|
|
290
|
-
}
|
|
291
|
-
throw new Error(`Failed to create user: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
192
|
+
console.error('[createUser] Error:', error);
|
|
193
|
+
handleClerkError(error, 'Failed to create user');
|
|
292
194
|
}
|
|
293
195
|
});
|
|
294
|
-
/**
|
|
295
|
-
* Update an existing user
|
|
296
|
-
*/
|
|
297
196
|
export const updateUser = command('unchecked', async (options) => {
|
|
298
197
|
const { userId, userData } = options;
|
|
299
|
-
console.log(`📝 [updateUser] Updating user ${userId}`);
|
|
300
198
|
try {
|
|
301
|
-
// Transform User to Clerk API format
|
|
302
|
-
// Only include fields that Clerk accepts
|
|
303
199
|
const updateData = {};
|
|
304
200
|
if (userData.first_name !== undefined)
|
|
305
201
|
updateData.first_name = userData.first_name;
|
|
306
202
|
if (userData.last_name !== undefined)
|
|
307
203
|
updateData.last_name = userData.last_name;
|
|
308
204
|
if (userData.username !== undefined && userData.username !== '') {
|
|
309
|
-
// Only include username if it's not empty (prevents "username already exists" error)
|
|
310
205
|
updateData.username = userData.username;
|
|
311
206
|
}
|
|
312
207
|
if (userData.private_metadata !== undefined) {
|
|
@@ -316,172 +211,202 @@ export const updateUser = command('unchecked', async (options) => {
|
|
|
316
211
|
method: 'PATCH',
|
|
317
212
|
body: JSON.stringify(updateData)
|
|
318
213
|
});
|
|
319
|
-
// If permissions changed, update them separately
|
|
320
214
|
if (userData.permissions !== undefined) {
|
|
321
215
|
try {
|
|
322
216
|
await updateUserPermissions({ userId, permissions: userData.permissions });
|
|
323
217
|
}
|
|
324
218
|
catch (permError) {
|
|
325
|
-
console.error(
|
|
326
|
-
// Don't fail the entire operation
|
|
219
|
+
console.error('[updateUser] Failed to update permissions:', permError);
|
|
327
220
|
}
|
|
328
221
|
}
|
|
329
|
-
console.log(`✅ [updateUser] User ${userId} updated successfully`);
|
|
330
222
|
return result;
|
|
331
223
|
}
|
|
332
224
|
catch (error) {
|
|
333
|
-
console.error('
|
|
334
|
-
|
|
335
|
-
if (error && typeof error === 'object' && 'status' in error && 'details' in error) {
|
|
336
|
-
const enrichedError = new Error('message' in error && typeof error.message === 'string' ? error.message : 'Unknown error');
|
|
337
|
-
enrichedError.status = error.status;
|
|
338
|
-
enrichedError.details = error.details;
|
|
339
|
-
enrichedError.clerkError = true;
|
|
340
|
-
throw enrichedError;
|
|
341
|
-
}
|
|
342
|
-
throw new Error(`Failed to update user: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
225
|
+
console.error('[updateUser] Error:', error);
|
|
226
|
+
handleClerkError(error, 'Failed to update user');
|
|
343
227
|
}
|
|
344
228
|
});
|
|
345
|
-
/**
|
|
346
|
-
* Delete a single user
|
|
347
|
-
*/
|
|
348
229
|
export const deleteUser = command('unchecked', async (userId) => {
|
|
349
|
-
console.log(`🗑️ [deleteUser] Deleting user ${userId}`);
|
|
350
230
|
try {
|
|
351
231
|
await makeClerkRequest(`/users/${userId}`, {
|
|
352
232
|
method: 'DELETE'
|
|
353
233
|
});
|
|
354
|
-
console.log(`✅ [deleteUser] User ${userId} deleted successfully`);
|
|
355
234
|
}
|
|
356
235
|
catch (error) {
|
|
357
|
-
console.error('
|
|
236
|
+
console.error('[deleteUser] Error:', error);
|
|
358
237
|
throw new Error(`Failed to delete user: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
359
238
|
}
|
|
360
239
|
});
|
|
361
|
-
/**
|
|
362
|
-
* Delete multiple users
|
|
363
|
-
*/
|
|
364
240
|
export const deleteUsers = command('unchecked', async (userIds) => {
|
|
365
|
-
console.log(`🗑️ [deleteUsers] Deleting ${userIds.length} users`);
|
|
366
241
|
try {
|
|
367
242
|
await Promise.all(userIds.map((userId) => makeClerkRequest(`/users/${userId}`, {
|
|
368
243
|
method: 'DELETE'
|
|
369
244
|
})));
|
|
370
|
-
console.log(`✅ [deleteUsers] ${userIds.length} users deleted successfully`);
|
|
371
245
|
}
|
|
372
246
|
catch (error) {
|
|
373
|
-
console.error('
|
|
247
|
+
console.error('[deleteUsers] Error:', error);
|
|
374
248
|
throw new Error(`Failed to delete users: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
375
249
|
}
|
|
376
250
|
});
|
|
377
|
-
|
|
378
|
-
* Get permissions for a specific user
|
|
379
|
-
*
|
|
380
|
-
* Uses 'sub' (userId) parameter in admin API calls
|
|
381
|
-
*/
|
|
382
|
-
export const getUserPermissions = query('unchecked', async (userId) => {
|
|
383
|
-
console.log(`🔍 [getUserPermissions] Fetching permissions for user: ${userId}`);
|
|
251
|
+
async function fetchUserPermissions(userId) {
|
|
384
252
|
try {
|
|
385
|
-
|
|
253
|
+
const userData = await makeAdminRequest(`/admin/keys?client_id=${CLIENT_ID}&sub=${userId}`);
|
|
254
|
+
if (userData?.data?.data && Array.isArray(userData.data.data)) {
|
|
255
|
+
userData.data.data = userData.data.data.filter((key) => key.status === 'active');
|
|
256
|
+
}
|
|
257
|
+
if (userData?.data?.data && Array.isArray(userData.data.data)) {
|
|
258
|
+
return userData.data.data.flatMap((key) => key.scopes || []);
|
|
259
|
+
}
|
|
260
|
+
else if (userData?.scopes) {
|
|
261
|
+
return Array.isArray(userData.scopes) ? userData.scopes : [userData.scopes];
|
|
262
|
+
}
|
|
263
|
+
return [];
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
386
266
|
try {
|
|
387
|
-
const
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
userData.data.data = userData.data.data.filter((key) => key.status === 'active');
|
|
392
|
-
console.log(`🔍 [getUserPermissions] Filtered to ${userData.data.data.length} active key(s)`);
|
|
393
|
-
}
|
|
394
|
-
// Extract scopes from the response
|
|
395
|
-
if (userData?.data?.data && Array.isArray(userData.data.data)) {
|
|
396
|
-
return userData.data.data.flatMap((key) => key.scopes || []);
|
|
397
|
-
}
|
|
398
|
-
else if (userData?.scopes) {
|
|
399
|
-
return Array.isArray(userData.scopes) ? userData.scopes : [userData.scopes];
|
|
267
|
+
const allKeysData = await makeAdminRequest('/admin/keys');
|
|
268
|
+
const userKey = allKeysData.data.data.find((key) => key.sub === userId && key.client_id === CLIENT_ID && key.status === 'active');
|
|
269
|
+
if (userKey) {
|
|
270
|
+
return Array.isArray(userKey.scopes) ? userKey.scopes : [userKey.scopes];
|
|
400
271
|
}
|
|
401
272
|
return [];
|
|
402
273
|
}
|
|
403
|
-
catch {
|
|
404
|
-
console.
|
|
405
|
-
|
|
406
|
-
try {
|
|
407
|
-
const allKeysData = await makeAdminRequest('/admin/keys');
|
|
408
|
-
console.log(`🔍 [getUserPermissions] Searching through ${allKeysData.data?.data?.length || 0} keys`);
|
|
409
|
-
// Find the ACTIVE key for this user (ignore revoked keys)
|
|
410
|
-
// Match by sub (userId) and client_id
|
|
411
|
-
const userKey = allKeysData.data.data.find((key) => key.sub === userId && key.client_id === CLIENT_ID && key.status === 'active');
|
|
412
|
-
if (userKey) {
|
|
413
|
-
console.log(`✅ [getUserPermissions] Found active user key by sub field`);
|
|
414
|
-
return Array.isArray(userKey.scopes) ? userKey.scopes : [userKey.scopes];
|
|
415
|
-
}
|
|
416
|
-
else {
|
|
417
|
-
console.log(`❌ [getUserPermissions] No user found, returning empty permissions`);
|
|
418
|
-
return [];
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
catch (searchError) {
|
|
422
|
-
console.error('❌ [getUserPermissions] Error searching for user by sub:', searchError);
|
|
423
|
-
throw new Error('Failed to fetch user permissions');
|
|
424
|
-
}
|
|
274
|
+
catch (searchError) {
|
|
275
|
+
console.error('[fetchUserPermissions] Error searching for user by sub:', searchError);
|
|
276
|
+
throw new Error('Failed to fetch user permissions');
|
|
425
277
|
}
|
|
426
278
|
}
|
|
279
|
+
}
|
|
280
|
+
export const getUserPermissions = query.batch('unchecked', async (userIds) => {
|
|
281
|
+
try {
|
|
282
|
+
const permissionPromises = userIds.map((userId) => fetchUserPermissions(userId));
|
|
283
|
+
const permissionsResults = await Promise.all(permissionPromises);
|
|
284
|
+
const lookup = new Map();
|
|
285
|
+
userIds.forEach((userId, index) => {
|
|
286
|
+
lookup.set(userId, permissionsResults[index]);
|
|
287
|
+
});
|
|
288
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
289
|
+
return (userId, _index) => {
|
|
290
|
+
const permissions = lookup.get(userId) || [];
|
|
291
|
+
return Promise.resolve(permissions);
|
|
292
|
+
};
|
|
293
|
+
}
|
|
427
294
|
catch (error) {
|
|
428
|
-
console.error('
|
|
429
|
-
|
|
295
|
+
console.error('[getUserPermissions] Batch error:', error);
|
|
296
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
297
|
+
return (_userId, _index) => {
|
|
298
|
+
throw new Error(`Failed to fetch user permissions: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
299
|
+
};
|
|
430
300
|
}
|
|
431
301
|
});
|
|
432
|
-
/**
|
|
433
|
-
* Update permissions for a specific user
|
|
434
|
-
*
|
|
435
|
-
* Uses 'sub' (userId) parameter in admin API calls
|
|
436
|
-
*/
|
|
437
302
|
export const updateUserPermissions = command('unchecked', async (options) => {
|
|
438
303
|
const { userId, permissions } = options;
|
|
439
|
-
console.log(`🔑 [updateUserPermissions] Updating permissions for user ${userId}`);
|
|
440
304
|
try {
|
|
441
|
-
let adminKeyId = userId;
|
|
442
|
-
// Try direct update first
|
|
305
|
+
let adminKeyId = userId;
|
|
443
306
|
try {
|
|
444
307
|
await makeAdminRequest(`/admin/keys/${adminKeyId}`, {
|
|
445
308
|
method: 'PUT',
|
|
446
309
|
body: JSON.stringify({ scopes: permissions })
|
|
447
310
|
});
|
|
448
|
-
console.log(`✅ [updateUserPermissions] Permissions updated successfully`);
|
|
449
311
|
return;
|
|
450
312
|
}
|
|
451
313
|
catch {
|
|
452
|
-
// If direct update fails, try to find the correct adminKeyId
|
|
453
|
-
console.log(`⚠️ [updateUserPermissions] Direct update failed, searching for key ID...`);
|
|
454
314
|
try {
|
|
455
315
|
const allKeysData = await makeAdminRequest('/admin/keys');
|
|
456
|
-
// Find the ACTIVE key for this user (ignore revoked keys)
|
|
457
|
-
// Match by sub (userId) and client_id
|
|
458
316
|
const userKey = allKeysData.data.data.find((key) => key.sub === userId && key.client_id === CLIENT_ID && key.status === 'active');
|
|
459
317
|
if (userKey) {
|
|
460
|
-
// Use the found key ID for update
|
|
461
318
|
adminKeyId = userKey.id;
|
|
462
319
|
await makeAdminRequest(`/admin/keys/${adminKeyId}`, {
|
|
463
320
|
method: 'PUT',
|
|
464
321
|
body: JSON.stringify({ scopes: permissions })
|
|
465
322
|
});
|
|
466
|
-
console.log(`✅ [updateUserPermissions] Permissions updated successfully (after search)`);
|
|
467
323
|
return;
|
|
468
324
|
}
|
|
469
325
|
else {
|
|
470
|
-
// User doesn't exist, create new admin key
|
|
471
|
-
console.log(`📝 [updateUserPermissions] Creating new admin key...`);
|
|
472
326
|
await createUserPermissions(userId, permissions);
|
|
473
|
-
console.log(`✅ [updateUserPermissions] New admin key created successfully`);
|
|
474
327
|
return;
|
|
475
328
|
}
|
|
476
329
|
}
|
|
477
330
|
catch (searchError) {
|
|
478
|
-
console.error('
|
|
331
|
+
console.error('[updateUserPermissions] Error during permission update:', searchError);
|
|
479
332
|
throw new Error('Failed to update permissions');
|
|
480
333
|
}
|
|
481
334
|
}
|
|
482
335
|
}
|
|
483
336
|
catch (error) {
|
|
484
|
-
console.error('
|
|
337
|
+
console.error('[updateUserPermissions] Error:', error);
|
|
485
338
|
throw new Error(`Failed to update user permissions: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
486
339
|
}
|
|
487
340
|
});
|
|
341
|
+
export const generateApiKey = command('unchecked', async (options) => {
|
|
342
|
+
try {
|
|
343
|
+
const filteredPermissions = PERMISSION_PREFIX
|
|
344
|
+
? options.permissions.filter((scope) => scope.startsWith(PERMISSION_PREFIX))
|
|
345
|
+
: options.permissions;
|
|
346
|
+
if (filteredPermissions.length === 0) {
|
|
347
|
+
throw new Error(`At least one ${PERMISSION_PREFIX || ''} permission is required to generate an API key`);
|
|
348
|
+
}
|
|
349
|
+
let oldKeyId = null;
|
|
350
|
+
if (options.revokeOld) {
|
|
351
|
+
try {
|
|
352
|
+
const allKeysData = await makeAdminRequest('/admin/keys');
|
|
353
|
+
if (allKeysData?.data?.data && Array.isArray(allKeysData.data.data)) {
|
|
354
|
+
const userKey = allKeysData.data.data.find((key) => key.sub === options.userId && key.client_id === CLIENT_ID && key.status === 'active');
|
|
355
|
+
if (userKey) {
|
|
356
|
+
oldKeyId = userKey.id;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
catch (e) {
|
|
361
|
+
console.warn('[generateApiKey] Could not fetch existing key for revocation:', e);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
const createData = await createUserPermissions(options.userId, filteredPermissions);
|
|
365
|
+
if (!createData) {
|
|
366
|
+
throw new Error('Failed to create admin key');
|
|
367
|
+
}
|
|
368
|
+
const newApiKey = createData?.data?.key;
|
|
369
|
+
if (!newApiKey) {
|
|
370
|
+
console.error('[generateApiKey] No API key in response:', createData);
|
|
371
|
+
throw new Error('Failed to generate API key - no key in response');
|
|
372
|
+
}
|
|
373
|
+
try {
|
|
374
|
+
const currentUser = await makeClerkRequest(`/users/${options.userId}`);
|
|
375
|
+
await makeClerkRequest(`/users/${options.userId}`, {
|
|
376
|
+
method: 'PATCH',
|
|
377
|
+
body: JSON.stringify({
|
|
378
|
+
private_metadata: {
|
|
379
|
+
...(currentUser.private_metadata || {}),
|
|
380
|
+
mako_api_key: newApiKey
|
|
381
|
+
}
|
|
382
|
+
})
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
catch (clerkError) {
|
|
386
|
+
console.error('[generateApiKey] Failed to update Clerk profile:', clerkError);
|
|
387
|
+
console.warn('[generateApiKey] Key generated but could not update Clerk profile');
|
|
388
|
+
}
|
|
389
|
+
if (oldKeyId) {
|
|
390
|
+
try {
|
|
391
|
+
await makeAdminRequest(`/admin/keys/${oldKeyId}`, {
|
|
392
|
+
method: 'DELETE'
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
catch (revokeError) {
|
|
396
|
+
console.error('[generateApiKey] Failed to revoke old key:', revokeError);
|
|
397
|
+
console.warn('[generateApiKey] New key generated but could not revoke old key');
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return {
|
|
401
|
+
success: true,
|
|
402
|
+
apiKey: newApiKey,
|
|
403
|
+
message: oldKeyId
|
|
404
|
+
? 'New API key generated and old key revoked successfully'
|
|
405
|
+
: 'API key generated successfully'
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
catch (error) {
|
|
409
|
+
console.error('[generateApiKey] Error:', error);
|
|
410
|
+
throw new Error(`Failed to generate API key: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
411
|
+
}
|
|
412
|
+
});
|
|
@@ -56,11 +56,12 @@ export interface UserTableProps {
|
|
|
56
56
|
class?: ClassValue;
|
|
57
57
|
}
|
|
58
58
|
export interface UserModalProps {
|
|
59
|
-
open
|
|
60
|
-
user
|
|
59
|
+
open?: boolean;
|
|
60
|
+
user?: User | null;
|
|
61
61
|
roles?: Role[];
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
adapter?: UserManagementAdapter;
|
|
63
|
+
onSave: (user: User, mode: 'create' | 'edit') => void | Promise<void>;
|
|
64
|
+
onClose?: () => void;
|
|
64
65
|
class?: ClassValue;
|
|
65
66
|
}
|
|
66
67
|
export interface UserViewModalProps {
|
|
@@ -95,6 +96,15 @@ export interface UserManagementAdapter {
|
|
|
95
96
|
userId: string;
|
|
96
97
|
permissions: string[];
|
|
97
98
|
}) => PromiseLike<void>;
|
|
99
|
+
generateApiKey?: (options: {
|
|
100
|
+
userId: string;
|
|
101
|
+
permissions: string[];
|
|
102
|
+
revokeOld?: boolean;
|
|
103
|
+
}) => PromiseLike<{
|
|
104
|
+
success: boolean;
|
|
105
|
+
apiKey: string;
|
|
106
|
+
message: string;
|
|
107
|
+
}>;
|
|
98
108
|
}
|
|
99
109
|
export interface UserManagementProps {
|
|
100
110
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@makolabs/ripple",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.11",
|
|
4
4
|
"description": "Simple Svelte 5 powered component library ✨",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"repository": {
|
|
@@ -59,7 +59,6 @@
|
|
|
59
59
|
"@storybook/addon-docs": "^9.0.18",
|
|
60
60
|
"@storybook/addon-svelte-csf": "^5.0.7",
|
|
61
61
|
"@storybook/sveltekit": "^9.0.18",
|
|
62
|
-
"@sveltejs/kit": "^2.16.0",
|
|
63
62
|
"@sveltejs/package": "^2.0.0",
|
|
64
63
|
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
|
65
64
|
"@tailwindcss/vite": "^4.0.14",
|
|
@@ -117,8 +116,9 @@
|
|
|
117
116
|
"dependencies": {
|
|
118
117
|
"@friendofsvelte/mermaid": "^0.0.4",
|
|
119
118
|
"@friendofsvelte/state": "^0.0.6-ts",
|
|
120
|
-
"@makolabs/ripple": "^1.2.
|
|
119
|
+
"@makolabs/ripple": "^1.2.9",
|
|
121
120
|
"@sveltejs/adapter-static": "^3.0.9",
|
|
121
|
+
"@sveltejs/kit": "^2.48.4",
|
|
122
122
|
"compromise": "^14.14.4",
|
|
123
123
|
"dayjs": "^1.11.13",
|
|
124
124
|
"echarts": "^5.6.0",
|