@makolabs/ripple 1.6.9 → 1.7.1

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.
@@ -2,7 +2,6 @@ import { query, command } from '$app/server';
2
2
  import { getRequestEvent } from '$app/server';
3
3
  import { env } from '$env/dynamic/private';
4
4
  const CLIENT_ID = env.CLIENT_ID || 'sharkfin';
5
- const PERMISSION_PREFIX = env.PERMISSION_PREFIX || 'sharkfin:';
6
5
  const ORGANIZATION_ID = env.ALLOWED_ORG_ID;
7
6
  function handleClerkError(error, defaultMessage) {
8
7
  if (error && typeof error === 'object' && 'status' in error && 'details' in error) {
@@ -147,10 +146,7 @@ async function verifyApiKeyToken(apiKey) {
147
146
  }
148
147
  }
149
148
  async function createUserPermissions(userId, permissions, clientId = CLIENT_ID) {
150
- const filteredPermissions = PERMISSION_PREFIX
151
- ? permissions.filter((scope) => scope.startsWith(PERMISSION_PREFIX))
152
- : permissions;
153
- if (filteredPermissions.length === 0) {
149
+ if (permissions.length === 0) {
154
150
  return null;
155
151
  }
156
152
  return await makeAdminRequest('/admin/keys', {
@@ -158,7 +154,7 @@ async function createUserPermissions(userId, permissions, clientId = CLIENT_ID)
158
154
  body: JSON.stringify({
159
155
  client_id: clientId,
160
156
  sub: userId,
161
- scopes: filteredPermissions
157
+ scopes: permissions
162
158
  })
163
159
  });
164
160
  }
@@ -423,9 +419,6 @@ export const updateUserPermissions = command('unchecked', async (options) => {
423
419
  // Fetch user's active keys
424
420
  const allKeysData = await makeAdminRequest(`/admin/keys?client_id=${CLIENT_ID}&sub=${userId}`);
425
421
  const userKeys = (allKeysData?.data?.data || []).filter((key) => key.status === 'active');
426
- const filteredPermissions = PERMISSION_PREFIX
427
- ? permissions.filter((scope) => scope.startsWith(PERMISSION_PREFIX))
428
- : permissions;
429
422
  if (userKeys.length === 0) {
430
423
  // No active key exists, create new one
431
424
  const newKeyResult = await createUserPermissions(userId, permissions);
@@ -452,7 +445,7 @@ export const updateUserPermissions = command('unchecked', async (options) => {
452
445
  await makeAdminRequest(`/admin/keys/${keyId}`, {
453
446
  method: 'PUT',
454
447
  body: JSON.stringify({
455
- scopes: filteredPermissions
448
+ scopes: permissions
456
449
  })
457
450
  });
458
451
  // Verify the token has updated scopes
@@ -462,9 +455,9 @@ export const updateUserPermissions = command('unchecked', async (options) => {
462
455
  console.log('[updateUserPermissions] Key verification:', verification);
463
456
  if (verification.valid) {
464
457
  // Check if the scopes match what we expect
465
- const scopesMatch = filteredPermissions.every((perm) => verification.scopes?.includes(perm));
458
+ const scopesMatch = permissions.every((perm) => verification.scopes?.includes(perm));
466
459
  if (!scopesMatch) {
467
- console.warn('[updateUserPermissions] Scopes mismatch. Expected:', filteredPermissions, 'Got:', verification.scopes);
460
+ console.warn('[updateUserPermissions] Scopes mismatch. Expected:', permissions, 'Got:', verification.scopes);
468
461
  }
469
462
  }
470
463
  else {
@@ -493,12 +486,9 @@ export const updateUserPermissions = command('unchecked', async (options) => {
493
486
  });
494
487
  export const generateApiKey = command('unchecked', async (options) => {
495
488
  try {
496
- let filteredPermissions = PERMISSION_PREFIX
497
- ? options.permissions.filter((scope) => scope.startsWith(PERMISSION_PREFIX))
498
- : options.permissions;
499
- // Default to readonly permission if none provided (first-time key generation)
500
- if (filteredPermissions.length === 0 && PERMISSION_PREFIX) {
501
- filteredPermissions = [`${PERMISSION_PREFIX}readonly`];
489
+ // No default permissions - require explicit permissions to be passed
490
+ if (options.permissions.length === 0) {
491
+ throw new Error('At least one permission scope is required');
502
492
  }
503
493
  // Check if user has existing active key
504
494
  const allKeysData = await makeAdminRequest(`/admin/keys?client_id=${CLIENT_ID}&sub=${options.userId}`);
@@ -507,6 +497,7 @@ export const generateApiKey = command('unchecked', async (options) => {
507
497
  let wasRotated = false;
508
498
  let oldApiKey;
509
499
  let currentUser = null;
500
+ let verificationWarning;
510
501
  if (userKeys.length > 0 && options.revokeOld) {
511
502
  // Use rotate endpoint (per Mako Auth API spec)
512
503
  const keyId = userKeys[0].id;
@@ -516,7 +507,7 @@ export const generateApiKey = command('unchecked', async (options) => {
516
507
  const rotateResult = await makeAdminRequest(`/admin/keys/${keyId}/rotate`, {
517
508
  method: 'POST',
518
509
  body: JSON.stringify({
519
- scopes: filteredPermissions
510
+ scopes: options.permissions
520
511
  })
521
512
  });
522
513
  // Rotate endpoint returns key in data.key field
@@ -544,22 +535,25 @@ export const generateApiKey = command('unchecked', async (options) => {
544
535
  console.log('[generateApiKey] New key verification:', newKeyVerification);
545
536
  if (newKeyVerification.valid) {
546
537
  // Check if the scopes match what we expect
547
- const scopesMatch = filteredPermissions.every((perm) => newKeyVerification.scopes?.includes(perm));
538
+ const scopesMatch = options.permissions.every((perm) => newKeyVerification.scopes?.includes(perm));
548
539
  if (!scopesMatch) {
549
- console.warn('[generateApiKey] Scopes mismatch. Expected:', filteredPermissions, 'Got:', newKeyVerification.scopes);
540
+ console.warn('[generateApiKey] Scopes mismatch. Expected:', options.permissions, 'Got:', newKeyVerification.scopes);
541
+ verificationWarning = `New API key scopes do not match expected permissions. Expected: ${options.permissions.join(', ')}, Got: ${newKeyVerification.scopes?.join(', ') || 'none'}`;
550
542
  }
551
543
  }
552
544
  else {
553
545
  console.warn('[generateApiKey] New key verification failed:', newKeyVerification.error);
546
+ verificationWarning = `New API key failed verification - ${newKeyVerification.error || 'Unknown error'}`;
554
547
  }
555
548
  }
556
549
  catch (verifyError) {
557
550
  console.warn('[generateApiKey] Could not verify new key:', verifyError);
551
+ verificationWarning = `Could not verify new API key - ${verifyError instanceof Error ? verifyError.message : 'Unknown error'}`;
558
552
  }
559
553
  }
560
554
  else {
561
555
  // Create new key if none exists or revokeOld is false
562
- const createData = await createUserPermissions(options.userId, filteredPermissions);
556
+ const createData = await createUserPermissions(options.userId, options.permissions);
563
557
  if (!createData) {
564
558
  throw new Error('Failed to create admin key');
565
559
  }
@@ -593,7 +587,8 @@ export const generateApiKey = command('unchecked', async (options) => {
593
587
  const result = {
594
588
  success: true,
595
589
  apiKey: newApiKey,
596
- message: wasRotated ? 'API key rotated successfully' : 'API key generated successfully'
590
+ message: wasRotated ? 'API key rotated successfully' : 'API key generated successfully',
591
+ verificationWarning
597
592
  };
598
593
  return JSON.parse(JSON.stringify(result));
599
594
  }
package/dist/index.d.ts CHANGED
@@ -1030,6 +1030,7 @@ export interface UserManagementAdapter {
1030
1030
  success: boolean;
1031
1031
  apiKey: string;
1032
1032
  message: string;
1033
+ verificationWarning?: string;
1033
1034
  }>;
1034
1035
  verifyToken?: (options: {
1035
1036
  apiKey: string;
@@ -4,9 +4,8 @@
4
4
  import UserTable from './UserTable.svelte';
5
5
  import UserModal from './UserModal.svelte';
6
6
  import UserViewModal from './UserViewModal.svelte';
7
- import type { User, UserManagementProps, Role, Permission, GetUsersResult } from '../index.js';
7
+ import type { User, UserManagementProps, Role, Permission } from '../index.js';
8
8
  import { SvelteSet } from 'svelte/reactivity';
9
- import type { RemoteQuery } from '@sveltejs/kit';
10
9
 
11
10
  let {
12
11
  adapter,
@@ -60,13 +59,7 @@
60
59
 
61
60
  // Refresh the query cache to get fresh data
62
61
  async function refreshUsersQuery() {
63
- const query = adapter.getUsers({
64
- page: currentPage,
65
- pageSize,
66
- sortBy: sortBy || undefined,
67
- sortOrder: sortOrder || 'desc'
68
- }) as RemoteQuery<GetUsersResult>;
69
- await query.refresh();
62
+ await loadUsers();
70
63
  }
71
64
 
72
65
  // Handlers
@@ -146,10 +139,8 @@
146
139
  } else {
147
140
  await adapter.updateUser({ userId: user.id, userData: user });
148
141
  }
149
- // Invalidate the query cache by calling refresh() before loading
142
+ // Refresh the users list
150
143
  await refreshUsersQuery();
151
- // Force refresh by reassigning state to trigger reactivity
152
- await loadUsers();
153
144
  } catch (error) {
154
145
  console.error('Error saving user:', error);
155
146
  // Reload on error to restore correct state
@@ -180,11 +171,8 @@
180
171
  // Perform the actual delete operation
181
172
  await adapter.deleteUser(userId);
182
173
 
183
- // Invalidate the query cache by calling refresh() before loading
184
- await refreshUsersQuery();
185
-
186
174
  // Refresh to ensure we have the latest data
187
- await loadUsers();
175
+ await refreshUsersQuery();
188
176
  } catch (error) {
189
177
  console.error('Error deleting user:', error);
190
178
  // Reload on error to restore correct state
@@ -225,11 +213,8 @@
225
213
  // Perform the actual delete operation
226
214
  await adapter.deleteUsers(userIds);
227
215
 
228
- // Invalidate the query cache by calling refresh() before loading
229
- await refreshUsersQuery();
230
-
231
216
  // Refresh to ensure we have the latest data
232
- await loadUsers();
217
+ await refreshUsersQuery();
233
218
  } catch (error) {
234
219
  console.error('Error deleting users:', error);
235
220
  // Reload on error to restore correct state
@@ -9,6 +9,7 @@
9
9
  type Role,
10
10
  getUserDisplayName
11
11
  } from '../index.js';
12
+ import { toast } from 'svelte-sonner';
12
13
 
13
14
  // Icons as simple SVGs
14
15
  let {
@@ -222,6 +223,14 @@
222
223
  }
223
224
  };
224
225
  }
226
+
227
+ // Show warning toast if new key verification failed
228
+ if (result.verificationWarning) {
229
+ toast.warning('API Key Verification Warning', {
230
+ description: result.verificationWarning,
231
+ duration: 8000 // 8 seconds for important warnings
232
+ });
233
+ }
225
234
  } catch (error) {
226
235
  console.error('Error regenerating API key:', error);
227
236
  formErrors.apiKey = error instanceof Error ? error.message : 'Failed to regenerate API key';
@@ -276,7 +285,7 @@
276
285
  contentclass="max-w-4xl"
277
286
  class={cn(className)}
278
287
  >
279
- <form bind:this={formElement} onsubmit={handleSubmit} class="flex gap-6">
288
+ <form bind:this={formElement} onsubmit={handleSubmit} class="flex gap-6" data-testid="user-form">
280
289
  <!-- Left Column: Profile Information -->
281
290
  <div class="min-w-0 flex-1 space-y-4">
282
291
  <div class="border-default-200 border-b pb-3">
@@ -296,6 +305,7 @@
296
305
  ? 'border-danger-300'
297
306
  : 'border-default-300'}"
298
307
  placeholder="First name"
308
+ data-testid="first-name-input"
299
309
  required
300
310
  />
301
311
  {#if formErrors.first_name}
@@ -316,6 +326,7 @@
316
326
  ? 'border-danger-300'
317
327
  : 'border-default-300'}"
318
328
  placeholder="Last name"
329
+ data-testid="last-name-input"
319
330
  required
320
331
  />
321
332
  {#if formErrors.last_name}
@@ -336,6 +347,7 @@
336
347
  ? 'border-danger-300'
337
348
  : 'border-default-300'}"
338
349
  placeholder="user@example.com"
350
+ data-testid="email-input"
339
351
  required
340
352
  />
341
353
  {#if formErrors.email}
@@ -358,6 +370,7 @@
358
370
  disabled={verifyingToken}
359
371
  class="disabled:text-default-400 inline-flex items-center gap-1 text-sm text-green-600 transition-colors hover:text-green-700 hover:underline disabled:cursor-not-allowed"
360
372
  aria-label="Verify token"
373
+ data-testid="verify-token-button"
361
374
  >
362
375
  <svg
363
376
  class="h-4 w-4 {verifyingToken ? 'animate-spin' : ''}"
@@ -384,6 +397,7 @@
384
397
  formData.permissions.length === 0}
385
398
  class="disabled:text-default-400 inline-flex items-center gap-1 text-sm text-blue-600 transition-colors hover:text-blue-700 hover:underline disabled:cursor-not-allowed"
386
399
  aria-label="Regenerate API key"
400
+ data-testid="regenerate-api-key-button"
387
401
  >
388
402
  <svg
389
403
  class="h-4 w-4 {regeneratingApiKey ? 'animate-spin' : ''}"
@@ -411,12 +425,14 @@
411
425
  readonly
412
426
  class="border-default-300 bg-default-50 w-full rounded-lg border px-3 py-2 pr-10 font-mono text-sm"
413
427
  placeholder="No API key generated"
428
+ data-testid="api-key-input"
414
429
  />
415
430
  <button
416
431
  type="button"
417
432
  onclick={() => (showApiKey = !showApiKey)}
418
433
  class="text-default-500 hover:text-default-700 absolute top-1/2 right-2 -translate-y-1/2"
419
434
  aria-label={showApiKey ? 'Hide API key' : 'Show API key'}
435
+ data-testid="toggle-api-key-visibility"
420
436
  >
421
437
  {#if showApiKey}
422
438
  <svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -533,6 +549,7 @@
533
549
  ? 'border-blue-500 bg-blue-50 opacity-75'
534
550
  : 'border-blue-500 bg-blue-50'
535
551
  : 'border-default-200 hover:border-default-300 bg-white'}"
552
+ data-testid="role-{role.value}"
536
553
  >
537
554
  <div class="flex items-center justify-between gap-2">
538
555
  <div class="min-w-0 flex-1">
@@ -587,7 +604,13 @@
587
604
  <div></div>
588
605
  {/if}
589
606
  <div class="flex gap-3">
590
- <Button variant="outline" onclick={handleClose} disabled={saving} type="button">
607
+ <Button
608
+ variant="outline"
609
+ onclick={handleClose}
610
+ disabled={saving}
611
+ type="button"
612
+ data-testid="cancel-button"
613
+ >
591
614
  Cancel
592
615
  </Button>
593
616
  <Button
@@ -596,6 +619,7 @@
596
619
  onclick={() => formElement?.requestSubmit()}
597
620
  disabled={saving}
598
621
  isLoading={saving}
622
+ data-testid="save-user-button"
599
623
  >
600
624
  {mode === 'create' ? 'Create User' : 'Save Changes'}
601
625
  </Button>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@makolabs/ripple",
3
- "version": "1.6.9",
3
+ "version": "1.7.1",
4
4
  "description": "Simple Svelte 5 powered component library ✨",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "repository": {
@@ -18,6 +18,10 @@
18
18
  "format": "prettier --write .",
19
19
  "lint": "prettier --check . && eslint .",
20
20
  "test:unit": "vitest",
21
+ "test:e2e": "playwright test",
22
+ "test:e2e:ui": "playwright test --ui",
23
+ "test:e2e:debug": "playwright test --debug",
24
+ "test:e2e:report": "playwright show-report",
21
25
  "test": "npm run test:unit -- --run",
22
26
  "pub:minor": "git add . && git commit -m \"chore: prepare for publish minor\" && npm version minor && git push --follow-tags && npm publish",
23
27
  "pub:patch": "git add . && git commit -m \"chore: prepare for publish patch\" && npm version patch && git push --follow-tags && npm publish",