@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(
|
|
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:
|
|
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(
|
|
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(
|
|
336
|
+
async function fetchUserPermissions(email) {
|
|
337
337
|
try {
|
|
338
|
-
const userData = await makeAdminRequest(`/admin/keys?client_id=${CLIENT_ID}&sub=${
|
|
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 ===
|
|
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
|
-
|
|
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=${
|
|
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(
|
|
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
|
-
...(
|
|
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
|
-
|
|
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=${
|
|
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 =
|
|
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(
|
|
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')}
|