@makolabs/ripple 1.7.1 → 1.7.3

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.
@@ -176,6 +176,10 @@ export async function getUserPermissions(userId) {
176
176
  export async function updateUserPermissions(options) {
177
177
  await delay();
178
178
  const { userId, permissions } = options;
179
+ // Validate permissions - require at least one permission scope
180
+ if (permissions.length === 0) {
181
+ throw new Error('At least one permission scope is required');
182
+ }
179
183
  const user = mockUsers.find((u) => u.id === userId);
180
184
  if (!user) {
181
185
  throw new Error(`User with ID ${userId} not found`);
@@ -188,6 +192,10 @@ export async function updateUserPermissions(options) {
188
192
  */
189
193
  export async function generateApiKey(options) {
190
194
  await delay();
195
+ // Validate permissions - require at least one permission scope
196
+ if (options.permissions.length === 0) {
197
+ throw new Error('At least one permission scope is required');
198
+ }
191
199
  const { userId } = options;
192
200
  const user = mockUsers.find((u) => u.id === userId);
193
201
  if (!user) {
@@ -145,7 +145,7 @@ async function verifyApiKeyToken(apiKey) {
145
145
  };
146
146
  }
147
147
  }
148
- async function createUserPermissions(userId, permissions, clientId = CLIENT_ID) {
148
+ async function createUserPermissions(email, permissions, clientId = CLIENT_ID) {
149
149
  if (permissions.length === 0) {
150
150
  return null;
151
151
  }
@@ -153,7 +153,7 @@ async function createUserPermissions(userId, permissions, clientId = CLIENT_ID)
153
153
  method: 'POST',
154
154
  body: JSON.stringify({
155
155
  client_id: clientId,
156
- sub: userId,
156
+ sub: email,
157
157
  scopes: permissions
158
158
  })
159
159
  });
@@ -242,7 +242,7 @@ export const createUser = command('unchecked', async (userData) => {
242
242
  }
243
243
  if (permissions && permissions.length > 0) {
244
244
  try {
245
- const adminKeyResult = await createUserPermissions(result.id, permissions);
245
+ const adminKeyResult = await createUserPermissions(emailAddress, permissions);
246
246
  const apiKey = adminKeyResult?.data?.key;
247
247
  if (adminKeyResult && apiKey) {
248
248
  const updatedUser = await makeClerkRequest(`/users/${result.id}`, {
@@ -333,9 +333,9 @@ export const deleteUsers = command('unchecked', async (userIds) => {
333
333
  throw new Error(`Failed to delete users: ${error instanceof Error ? error.message : 'Unknown error'}`);
334
334
  }
335
335
  });
336
- async function fetchUserPermissions(userId) {
336
+ async function fetchUserPermissions(email) {
337
337
  try {
338
- const userData = await makeAdminRequest(`/admin/keys?client_id=${CLIENT_ID}&sub=${userId}`);
338
+ const userData = await makeAdminRequest(`/admin/keys?client_id=${CLIENT_ID}&sub=${email}`);
339
339
  if (userData?.data?.data && Array.isArray(userData.data.data)) {
340
340
  userData.data.data = userData.data.data.filter((key) => key.status === 'active');
341
341
  }
@@ -355,7 +355,7 @@ async function fetchUserPermissions(userId) {
355
355
  console.error('[fetchUserPermissions] Error fetching user permissions:', error);
356
356
  try {
357
357
  const allKeysData = await makeAdminRequest('/admin/keys');
358
- const userKey = allKeysData.data.data.find((key) => key.sub === userId && key.client_id === CLIENT_ID && key.status === 'active');
358
+ const userKey = allKeysData.data.data.find((key) => key.sub === email && key.client_id === CLIENT_ID && key.status === 'active');
359
359
  if (userKey) {
360
360
  const permissions = Array.isArray(userKey.scopes) ? userKey.scopes : [userKey.scopes];
361
361
  return permissions;
@@ -370,7 +370,13 @@ async function fetchUserPermissions(userId) {
370
370
  }
371
371
  export const getUserPermissions = query('unchecked', async (userId) => {
372
372
  try {
373
- const permissions = await fetchUserPermissions(userId);
373
+ // Fetch user from Clerk to get email
374
+ const user = await makeClerkRequest(`/users/${userId}`);
375
+ const email = user.email_addresses?.[0]?.email_address;
376
+ if (!email) {
377
+ throw new Error('User has no email address');
378
+ }
379
+ const permissions = await fetchUserPermissions(email);
374
380
  // Ensure permissions array is serializable
375
381
  return JSON.parse(JSON.stringify(permissions));
376
382
  }
@@ -416,20 +422,29 @@ async function refreshTokenIfSelfUpdate(userId) {
416
422
  export const updateUserPermissions = command('unchecked', async (options) => {
417
423
  const { userId, permissions } = options;
418
424
  try {
425
+ // Validate permissions - require at least one permission scope
426
+ if (permissions.length === 0) {
427
+ throw new Error('At least one permission scope is required');
428
+ }
429
+ // Fetch user from Clerk to get email
430
+ const user = await makeClerkRequest(`/users/${userId}`);
431
+ const email = user.email_addresses?.[0]?.email_address;
432
+ if (!email) {
433
+ throw new Error('User has no email address');
434
+ }
419
435
  // Fetch user's active keys
420
- const allKeysData = await makeAdminRequest(`/admin/keys?client_id=${CLIENT_ID}&sub=${userId}`);
436
+ const allKeysData = await makeAdminRequest(`/admin/keys?client_id=${CLIENT_ID}&sub=${email}`);
421
437
  const userKeys = (allKeysData?.data?.data || []).filter((key) => key.status === 'active');
422
438
  if (userKeys.length === 0) {
423
439
  // No active key exists, create new one
424
- const newKeyResult = await createUserPermissions(userId, permissions);
440
+ const newKeyResult = await createUserPermissions(email, permissions);
425
441
  const newApiKey = newKeyResult?.data?.key;
426
442
  if (newApiKey) {
427
- const currentUser = await makeClerkRequest(`/users/${userId}`);
428
443
  await makeClerkRequest(`/users/${userId}`, {
429
444
  method: 'PATCH',
430
445
  body: JSON.stringify({
431
446
  private_metadata: {
432
- ...(currentUser.private_metadata || {}),
447
+ ...(user.private_metadata || {}),
433
448
  mako_api_key: newApiKey
434
449
  }
435
450
  })
@@ -455,7 +470,9 @@ export const updateUserPermissions = command('unchecked', async (options) => {
455
470
  console.log('[updateUserPermissions] Key verification:', verification);
456
471
  if (verification.valid) {
457
472
  // Check if the scopes match what we expect
458
- const scopesMatch = permissions.every((perm) => verification.scopes?.includes(perm));
473
+ // Note: permissions.length > 0 is guaranteed by validation above
474
+ const scopesMatch = permissions.length > 0 &&
475
+ permissions.every((perm) => verification.scopes?.includes(perm));
459
476
  if (!scopesMatch) {
460
477
  console.warn('[updateUserPermissions] Scopes mismatch. Expected:', permissions, 'Got:', verification.scopes);
461
478
  }
@@ -490,19 +507,24 @@ export const generateApiKey = command('unchecked', async (options) => {
490
507
  if (options.permissions.length === 0) {
491
508
  throw new Error('At least one permission scope is required');
492
509
  }
510
+ // Fetch user from Clerk to get email
511
+ const user = await makeClerkRequest(`/users/${options.userId}`);
512
+ const email = user.email_addresses?.[0]?.email_address;
513
+ if (!email) {
514
+ throw new Error('User has no email address');
515
+ }
493
516
  // Check if user has existing active key
494
- const allKeysData = await makeAdminRequest(`/admin/keys?client_id=${CLIENT_ID}&sub=${options.userId}`);
517
+ const allKeysData = await makeAdminRequest(`/admin/keys?client_id=${CLIENT_ID}&sub=${email}`);
495
518
  const userKeys = (allKeysData?.data?.data || []).filter((key) => key.status === 'active');
496
519
  let newApiKey;
497
520
  let wasRotated = false;
498
521
  let oldApiKey;
499
- let currentUser = null;
522
+ let currentUser = user;
500
523
  let verificationWarning;
501
524
  if (userKeys.length > 0 && options.revokeOld) {
502
525
  // Use rotate endpoint (per Mako Auth API spec)
503
526
  const keyId = userKeys[0].id;
504
527
  // Get the old API key from Clerk's private_metadata
505
- currentUser = await makeClerkRequest(`/users/${options.userId}`);
506
528
  oldApiKey = currentUser?.private_metadata?.mako_api_key;
507
529
  const rotateResult = await makeAdminRequest(`/admin/keys/${keyId}/rotate`, {
508
530
  method: 'POST',
@@ -553,7 +575,7 @@ export const generateApiKey = command('unchecked', async (options) => {
553
575
  }
554
576
  else {
555
577
  // Create new key if none exists or revokeOld is false
556
- const createData = await createUserPermissions(options.userId, options.permissions);
578
+ const createData = await createUserPermissions(email, options.permissions);
557
579
  if (!createData) {
558
580
  throw new Error('Failed to create admin key');
559
581
  }
package/dist/index.d.ts CHANGED
@@ -339,6 +339,10 @@ export interface NavItemProps {
339
339
  export interface SidebarProps {
340
340
  items?: NavigationItem[];
341
341
  logo: LogoType;
342
+ /** Optional footer snippet rendered at the bottom of the sidebar */
343
+ footer?: Snippet<[{
344
+ collapsed: boolean;
345
+ }]>;
342
346
  }
343
347
  export { tv, cn } from './helper/cls.js';
344
348
  export { isRouteActive } from './helper/nav.svelte.js';
@@ -5,7 +5,7 @@
5
5
  import { isRouteActive } from '../../helper/nav.svelte.js';
6
6
  import { resolve } from '$app/paths';
7
7
 
8
- let { items = [], logo }: SidebarProps = $props();
8
+ let { items = [], logo, footer }: SidebarProps = $props();
9
9
  let menubar: MenuBar = $state({
10
10
  collapsed: false
11
11
  });
@@ -178,6 +178,13 @@
178
178
  {/each}
179
179
  </nav>
180
180
  </div>
181
+
182
+ <!-- Footer slot for custom content -->
183
+ {#if footer}
184
+ <div class="shrink-0 border-t border-white/10 px-3 py-3">
185
+ {@render footer({ collapsed: menubar.collapsed })}
186
+ </div>
187
+ {/if}
181
188
  </div>
182
189
 
183
190
  {#snippet ToggleIcon(classes = 'size-6 shrink-0 text-default-200')}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@makolabs/ripple",
3
- "version": "1.7.1",
3
+ "version": "1.7.3",
4
4
  "description": "Simple Svelte 5 powered component library ✨",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "repository": {