@makolabs/ripple 1.6.4 → 1.6.6
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.
|
@@ -21,3 +21,11 @@ export declare const generateApiKey: import("@sveltejs/kit").RemoteCommand<{
|
|
|
21
21
|
apiKey: string;
|
|
22
22
|
message: string;
|
|
23
23
|
}>>;
|
|
24
|
+
export declare const verifyToken: import("@sveltejs/kit").RemoteCommand<{
|
|
25
|
+
apiKey: string;
|
|
26
|
+
}, Promise<{
|
|
27
|
+
valid: boolean;
|
|
28
|
+
scopes?: string[];
|
|
29
|
+
error?: string;
|
|
30
|
+
token?: string;
|
|
31
|
+
}>>;
|
|
@@ -75,6 +75,62 @@ async function makeAdminRequest(endpoint, options = {}) {
|
|
|
75
75
|
// Ensure all data is serializable by converting to plain objects
|
|
76
76
|
return JSON.parse(JSON.stringify(data));
|
|
77
77
|
}
|
|
78
|
+
async function makeAuthRequest(endpoint, options = {}) {
|
|
79
|
+
const PRIVATE_BASE_AUTH_URL = env.PRIVATE_BASE_AUTH_URL;
|
|
80
|
+
if (!PRIVATE_BASE_AUTH_URL) {
|
|
81
|
+
throw new Error('PRIVATE_BASE_AUTH_URL environment variable is required');
|
|
82
|
+
}
|
|
83
|
+
const url = `${PRIVATE_BASE_AUTH_URL}${endpoint}`;
|
|
84
|
+
const response = await fetch(url, {
|
|
85
|
+
...options,
|
|
86
|
+
headers: {
|
|
87
|
+
'Content-Type': 'application/json',
|
|
88
|
+
...options.headers
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
const data = await response.json();
|
|
92
|
+
// Return both status and data for verification purposes
|
|
93
|
+
return {
|
|
94
|
+
ok: response.ok,
|
|
95
|
+
status: response.status,
|
|
96
|
+
data: JSON.parse(JSON.stringify(data))
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
async function verifyApiKeyToken(apiKey) {
|
|
100
|
+
try {
|
|
101
|
+
const result = await makeAuthRequest('/auth/issue', {
|
|
102
|
+
method: 'POST',
|
|
103
|
+
headers: {
|
|
104
|
+
'X-API-Key': apiKey
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
if (result.ok && result.data?.data?.access_token) {
|
|
108
|
+
// Parse the JWT to get scopes (or use verify endpoint)
|
|
109
|
+
const verifyResult = await makeAuthRequest('/auth/verify', {
|
|
110
|
+
method: 'POST',
|
|
111
|
+
headers: {
|
|
112
|
+
'Authorization': `Bearer ${result.data.data.access_token}`
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
if (verifyResult.ok && verifyResult.data?.data) {
|
|
116
|
+
return {
|
|
117
|
+
valid: true,
|
|
118
|
+
scopes: verifyResult.data.data.scopes || []
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
valid: false,
|
|
124
|
+
error: result.data?.error || `API key verification failed with status ${result.status}`
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
return {
|
|
129
|
+
valid: false,
|
|
130
|
+
error: error instanceof Error ? error.message : 'Unknown error during verification'
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
78
134
|
async function createUserPermissions(userId, permissions, clientId = CLIENT_ID) {
|
|
79
135
|
const filteredPermissions = PERMISSION_PREFIX
|
|
80
136
|
? permissions.filter((scope) => scope.startsWith(PERMISSION_PREFIX))
|
|
@@ -349,19 +405,69 @@ async function refreshTokenIfSelfUpdate(userId) {
|
|
|
349
405
|
export const updateUserPermissions = command('unchecked', async (options) => {
|
|
350
406
|
const { userId, permissions } = options;
|
|
351
407
|
try {
|
|
352
|
-
// Fetch
|
|
408
|
+
// Fetch user's active keys
|
|
353
409
|
const allKeysData = await makeAdminRequest(`/admin/keys?client_id=${CLIENT_ID}&sub=${userId}`);
|
|
354
|
-
// Find ALL active keys for this user (not just one)
|
|
355
410
|
const userKeys = (allKeysData?.data?.data || []).filter((key) => key.status === 'active');
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
if (userKeys.length
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
411
|
+
const filteredPermissions = PERMISSION_PREFIX
|
|
412
|
+
? permissions.filter((scope) => scope.startsWith(PERMISSION_PREFIX))
|
|
413
|
+
: permissions;
|
|
414
|
+
if (userKeys.length === 0) {
|
|
415
|
+
// No active key exists, create new one
|
|
416
|
+
const newKeyResult = await createUserPermissions(userId, permissions);
|
|
417
|
+
const newApiKey = newKeyResult?.data?.key;
|
|
418
|
+
if (newApiKey) {
|
|
419
|
+
const currentUser = await makeClerkRequest(`/users/${userId}`);
|
|
420
|
+
await makeClerkRequest(`/users/${userId}`, {
|
|
421
|
+
method: 'PATCH',
|
|
422
|
+
body: JSON.stringify({
|
|
423
|
+
private_metadata: {
|
|
424
|
+
...(currentUser.private_metadata || {}),
|
|
425
|
+
mako_api_key: newApiKey
|
|
426
|
+
}
|
|
427
|
+
})
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
// Use PUT to update existing key (per Mako Auth API spec)
|
|
433
|
+
const keyId = userKeys[0].id;
|
|
434
|
+
// Get the API key string before updating
|
|
435
|
+
const keyData = await makeAdminRequest(`/admin/keys/${keyId}`);
|
|
436
|
+
const apiKeyString = keyData?.data?.key;
|
|
437
|
+
await makeAdminRequest(`/admin/keys/${keyId}`, {
|
|
438
|
+
method: 'PUT',
|
|
439
|
+
body: JSON.stringify({
|
|
440
|
+
scopes: filteredPermissions
|
|
441
|
+
})
|
|
442
|
+
});
|
|
443
|
+
// Verify the token has updated scopes
|
|
444
|
+
if (apiKeyString) {
|
|
445
|
+
try {
|
|
446
|
+
const verification = await verifyApiKeyToken(apiKeyString);
|
|
447
|
+
if (verification.valid) {
|
|
448
|
+
console.log('[updateUserPermissions] Token verification successful. Scopes:', verification.scopes);
|
|
449
|
+
// Check if the scopes match what we expect
|
|
450
|
+
const scopesMatch = filteredPermissions.every(perm => verification.scopes?.includes(perm));
|
|
451
|
+
if (!scopesMatch) {
|
|
452
|
+
console.warn('[updateUserPermissions] Token scopes do not match expected permissions');
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
console.warn('[updateUserPermissions] Token verification failed:', verification.error);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
catch (verifyError) {
|
|
460
|
+
console.warn('[updateUserPermissions] Could not verify token:', verifyError);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
// Clean up any extra keys (there should only be one)
|
|
464
|
+
if (userKeys.length > 1) {
|
|
465
|
+
await Promise.all(userKeys.slice(1).map((key) => makeAdminRequest(`/admin/keys/${key.id}`, {
|
|
466
|
+
method: 'DELETE'
|
|
467
|
+
}).catch((err) => {
|
|
468
|
+
console.warn(`[updateUserPermissions] Failed to delete extra key ${key.id}:`, err);
|
|
469
|
+
})));
|
|
470
|
+
}
|
|
365
471
|
}
|
|
366
472
|
await refreshTokenIfSelfUpdate(userId);
|
|
367
473
|
}
|
|
@@ -379,30 +485,75 @@ export const generateApiKey = command('unchecked', async (options) => {
|
|
|
379
485
|
if (filteredPermissions.length === 0 && PERMISSION_PREFIX) {
|
|
380
486
|
filteredPermissions = [`${PERMISSION_PREFIX}readonly`];
|
|
381
487
|
}
|
|
382
|
-
|
|
383
|
-
|
|
488
|
+
// Check if user has existing active key
|
|
489
|
+
const allKeysData = await makeAdminRequest(`/admin/keys?client_id=${CLIENT_ID}&sub=${options.userId}`);
|
|
490
|
+
const userKeys = (allKeysData?.data?.data || []).filter((key) => key.status === 'active');
|
|
491
|
+
let newApiKey;
|
|
492
|
+
let wasRotated = false;
|
|
493
|
+
if (userKeys.length > 0 && options.revokeOld) {
|
|
494
|
+
// Use rotate endpoint (per Mako Auth API spec)
|
|
495
|
+
const keyId = userKeys[0].id;
|
|
496
|
+
// Get the old API key string before rotating
|
|
497
|
+
const oldKeyData = await makeAdminRequest(`/admin/keys/${keyId}`);
|
|
498
|
+
const oldApiKey = oldKeyData?.data?.key;
|
|
499
|
+
const rotateResult = await makeAdminRequest(`/admin/keys/${keyId}/rotate`, {
|
|
500
|
+
method: 'POST',
|
|
501
|
+
body: JSON.stringify({
|
|
502
|
+
scopes: filteredPermissions
|
|
503
|
+
})
|
|
504
|
+
});
|
|
505
|
+
// Rotate endpoint returns key in data.key field
|
|
506
|
+
newApiKey = rotateResult?.data?.key;
|
|
507
|
+
wasRotated = true;
|
|
508
|
+
if (!newApiKey) {
|
|
509
|
+
throw new Error('Failed to rotate API key - no key in response');
|
|
510
|
+
}
|
|
511
|
+
// Verify old key is revoked
|
|
512
|
+
if (oldApiKey) {
|
|
513
|
+
try {
|
|
514
|
+
const oldKeyVerification = await verifyApiKeyToken(oldApiKey);
|
|
515
|
+
if (oldKeyVerification.valid) {
|
|
516
|
+
console.warn('[generateApiKey] Old API key is still valid after rotation');
|
|
517
|
+
}
|
|
518
|
+
else {
|
|
519
|
+
console.log('[generateApiKey] Old API key successfully revoked');
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
catch (verifyError) {
|
|
523
|
+
console.warn('[generateApiKey] Could not verify old key revocation:', verifyError);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
// Verify new key works with correct scopes
|
|
384
527
|
try {
|
|
385
|
-
const
|
|
386
|
-
if (
|
|
387
|
-
|
|
388
|
-
if
|
|
389
|
-
|
|
528
|
+
const newKeyVerification = await verifyApiKeyToken(newApiKey);
|
|
529
|
+
if (newKeyVerification.valid) {
|
|
530
|
+
console.log('[generateApiKey] New API key verification successful. Scopes:', newKeyVerification.scopes);
|
|
531
|
+
// Check if the scopes match what we expect
|
|
532
|
+
const scopesMatch = filteredPermissions.every(perm => newKeyVerification.scopes?.includes(perm));
|
|
533
|
+
if (!scopesMatch) {
|
|
534
|
+
console.warn('[generateApiKey] New key scopes do not match expected permissions');
|
|
390
535
|
}
|
|
391
536
|
}
|
|
537
|
+
else {
|
|
538
|
+
console.warn('[generateApiKey] New API key verification failed:', newKeyVerification.error);
|
|
539
|
+
}
|
|
392
540
|
}
|
|
393
|
-
catch (
|
|
394
|
-
console.warn('[generateApiKey] Could not
|
|
541
|
+
catch (verifyError) {
|
|
542
|
+
console.warn('[generateApiKey] Could not verify new key:', verifyError);
|
|
395
543
|
}
|
|
396
544
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
545
|
+
else {
|
|
546
|
+
// Create new key if none exists or revokeOld is false
|
|
547
|
+
const createData = await createUserPermissions(options.userId, filteredPermissions);
|
|
548
|
+
if (!createData) {
|
|
549
|
+
throw new Error('Failed to create admin key');
|
|
550
|
+
}
|
|
551
|
+
newApiKey = createData?.data?.key;
|
|
552
|
+
if (!newApiKey) {
|
|
553
|
+
throw new Error('Failed to generate API key - no key in response');
|
|
554
|
+
}
|
|
405
555
|
}
|
|
556
|
+
// Update Clerk profile with new key
|
|
406
557
|
try {
|
|
407
558
|
const currentUser = await makeClerkRequest(`/users/${options.userId}`);
|
|
408
559
|
await makeClerkRequest(`/users/${options.userId}`, {
|
|
@@ -419,22 +570,11 @@ export const generateApiKey = command('unchecked', async (options) => {
|
|
|
419
570
|
console.error('[generateApiKey] Failed to update Clerk profile:', clerkError);
|
|
420
571
|
console.warn('[generateApiKey] Key generated but could not update Clerk profile');
|
|
421
572
|
}
|
|
422
|
-
if (oldKeyId) {
|
|
423
|
-
try {
|
|
424
|
-
await makeAdminRequest(`/admin/keys/${oldKeyId}`, {
|
|
425
|
-
method: 'DELETE'
|
|
426
|
-
});
|
|
427
|
-
}
|
|
428
|
-
catch (revokeError) {
|
|
429
|
-
console.error('[generateApiKey] Failed to revoke old key:', revokeError);
|
|
430
|
-
console.warn('[generateApiKey] New key generated but could not revoke old key');
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
573
|
const result = {
|
|
434
574
|
success: true,
|
|
435
575
|
apiKey: newApiKey,
|
|
436
|
-
message:
|
|
437
|
-
? '
|
|
576
|
+
message: wasRotated
|
|
577
|
+
? 'API key rotated successfully'
|
|
438
578
|
: 'API key generated successfully'
|
|
439
579
|
};
|
|
440
580
|
return JSON.parse(JSON.stringify(result));
|
|
@@ -444,3 +584,29 @@ export const generateApiKey = command('unchecked', async (options) => {
|
|
|
444
584
|
throw new Error(`Failed to generate API key: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
445
585
|
}
|
|
446
586
|
});
|
|
587
|
+
export const verifyToken = command('unchecked', async (options) => {
|
|
588
|
+
try {
|
|
589
|
+
const result = await verifyApiKeyToken(options.apiKey);
|
|
590
|
+
// Also return the issued token for debugging
|
|
591
|
+
if (result.valid) {
|
|
592
|
+
const tokenResult = await makeAuthRequest('/auth/issue', {
|
|
593
|
+
method: 'POST',
|
|
594
|
+
headers: {
|
|
595
|
+
'X-API-Key': options.apiKey
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
return {
|
|
599
|
+
...result,
|
|
600
|
+
token: tokenResult.data?.data?.access_token
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
return result;
|
|
604
|
+
}
|
|
605
|
+
catch (error) {
|
|
606
|
+
console.error('[verifyToken] Error:', error);
|
|
607
|
+
return {
|
|
608
|
+
valid: false,
|
|
609
|
+
error: error instanceof Error ? error.message : 'Unknown error during verification'
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
});
|
package/dist/index.d.ts
CHANGED
|
@@ -1031,6 +1031,14 @@ export interface UserManagementAdapter {
|
|
|
1031
1031
|
apiKey: string;
|
|
1032
1032
|
message: string;
|
|
1033
1033
|
}>;
|
|
1034
|
+
verifyToken?: (options: {
|
|
1035
|
+
apiKey: string;
|
|
1036
|
+
}) => PromiseLike<{
|
|
1037
|
+
valid: boolean;
|
|
1038
|
+
scopes?: string[];
|
|
1039
|
+
error?: string;
|
|
1040
|
+
token?: string;
|
|
1041
|
+
}>;
|
|
1034
1042
|
}
|
|
1035
1043
|
export interface UserManagementProps {
|
|
1036
1044
|
/**
|
|
@@ -30,6 +30,8 @@
|
|
|
30
30
|
let formElement = $state<HTMLFormElement | null>(null);
|
|
31
31
|
let showApiKey = $state(false);
|
|
32
32
|
let regeneratingApiKey = $state(false);
|
|
33
|
+
let verifyingToken = $state(false);
|
|
34
|
+
let tokenVerification = $state<{ valid?: boolean; scopes?: string[]; error?: string } | null>(null);
|
|
33
35
|
let initialRole = $state<string>('');
|
|
34
36
|
|
|
35
37
|
// Form data
|
|
@@ -131,6 +133,7 @@
|
|
|
131
133
|
open = false;
|
|
132
134
|
formErrors = {};
|
|
133
135
|
saving = false;
|
|
136
|
+
tokenVerification = null;
|
|
134
137
|
if (onClose) onClose();
|
|
135
138
|
}
|
|
136
139
|
|
|
@@ -184,6 +187,9 @@
|
|
|
184
187
|
role: roleValue,
|
|
185
188
|
permissions: role ? [...role.permissions] : []
|
|
186
189
|
};
|
|
190
|
+
|
|
191
|
+
// Clear token verification when permissions change
|
|
192
|
+
tokenVerification = null;
|
|
187
193
|
}
|
|
188
194
|
|
|
189
195
|
async function handleRegenerateApiKey() {
|
|
@@ -197,6 +203,7 @@
|
|
|
197
203
|
|
|
198
204
|
try {
|
|
199
205
|
regeneratingApiKey = true;
|
|
206
|
+
tokenVerification = null; // Clear previous verification
|
|
200
207
|
const result = await adapter.generateApiKey({
|
|
201
208
|
userId: user.id,
|
|
202
209
|
permissions: formData.permissions,
|
|
@@ -221,6 +228,27 @@
|
|
|
221
228
|
}
|
|
222
229
|
}
|
|
223
230
|
|
|
231
|
+
async function handleVerifyToken() {
|
|
232
|
+
if (!adapter?.verifyToken || !apiKey) return;
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
verifyingToken = true;
|
|
236
|
+
formErrors.apiKey = undefined;
|
|
237
|
+
const result = await adapter.verifyToken({ apiKey });
|
|
238
|
+
tokenVerification = result;
|
|
239
|
+
|
|
240
|
+
if (!result.valid) {
|
|
241
|
+
formErrors.apiKey = result.error || 'Token verification failed';
|
|
242
|
+
}
|
|
243
|
+
} catch (error) {
|
|
244
|
+
console.error('Error verifying token:', error);
|
|
245
|
+
formErrors.apiKey = error instanceof Error ? error.message : 'Failed to verify token';
|
|
246
|
+
tokenVerification = { valid: false, error: 'Verification failed' };
|
|
247
|
+
} finally {
|
|
248
|
+
verifyingToken = false;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
224
252
|
function getModalTitle() {
|
|
225
253
|
if (mode === 'create') return 'Create New User';
|
|
226
254
|
return `Edit ${getUserDisplayName(user ?? null)}`;
|
|
@@ -320,32 +348,58 @@
|
|
|
320
348
|
<label for="api-key" class="text-default-700 block text-sm font-medium">
|
|
321
349
|
Mako API Key
|
|
322
350
|
</label>
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
aria-label="Regenerate API key"
|
|
332
|
-
>
|
|
333
|
-
<svg
|
|
334
|
-
class="h-4 w-4 {regeneratingApiKey ? 'animate-spin' : ''}"
|
|
335
|
-
fill="none"
|
|
336
|
-
stroke="currentColor"
|
|
337
|
-
viewBox="0 0 24 24"
|
|
351
|
+
<div class="flex items-center gap-3">
|
|
352
|
+
{#if adapter?.verifyToken && apiKey}
|
|
353
|
+
<button
|
|
354
|
+
type="button"
|
|
355
|
+
onclick={handleVerifyToken}
|
|
356
|
+
disabled={verifyingToken}
|
|
357
|
+
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"
|
|
358
|
+
aria-label="Verify token"
|
|
338
359
|
>
|
|
339
|
-
<
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
stroke
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
360
|
+
<svg
|
|
361
|
+
class="h-4 w-4 {verifyingToken ? 'animate-spin' : ''}"
|
|
362
|
+
fill="none"
|
|
363
|
+
stroke="currentColor"
|
|
364
|
+
viewBox="0 0 24 24"
|
|
365
|
+
>
|
|
366
|
+
<path
|
|
367
|
+
stroke-linecap="round"
|
|
368
|
+
stroke-linejoin="round"
|
|
369
|
+
stroke-width="2"
|
|
370
|
+
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
371
|
+
></path>
|
|
372
|
+
</svg>
|
|
373
|
+
Verify
|
|
374
|
+
</button>
|
|
375
|
+
{/if}
|
|
376
|
+
{#if adapter?.generateApiKey}
|
|
377
|
+
<button
|
|
378
|
+
type="button"
|
|
379
|
+
onclick={handleRegenerateApiKey}
|
|
380
|
+
disabled={regeneratingApiKey ||
|
|
381
|
+
!formData.permissions ||
|
|
382
|
+
formData.permissions.length === 0}
|
|
383
|
+
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"
|
|
384
|
+
aria-label="Regenerate API key"
|
|
385
|
+
>
|
|
386
|
+
<svg
|
|
387
|
+
class="h-4 w-4 {regeneratingApiKey ? 'animate-spin' : ''}"
|
|
388
|
+
fill="none"
|
|
389
|
+
stroke="currentColor"
|
|
390
|
+
viewBox="0 0 24 24"
|
|
391
|
+
>
|
|
392
|
+
<path
|
|
393
|
+
stroke-linecap="round"
|
|
394
|
+
stroke-linejoin="round"
|
|
395
|
+
stroke-width="2"
|
|
396
|
+
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"
|
|
397
|
+
></path>
|
|
398
|
+
</svg>
|
|
399
|
+
Regenerate
|
|
400
|
+
</button>
|
|
401
|
+
{/if}
|
|
402
|
+
</div>
|
|
349
403
|
</div>
|
|
350
404
|
<div class="relative flex-1">
|
|
351
405
|
<input
|
|
@@ -391,6 +445,38 @@
|
|
|
391
445
|
</div>
|
|
392
446
|
{#if formErrors.apiKey}
|
|
393
447
|
<p class="text-danger-500 mt-1 text-xs">{formErrors.apiKey}</p>
|
|
448
|
+
{:else if tokenVerification}
|
|
449
|
+
{#if tokenVerification.valid}
|
|
450
|
+
<div class="bg-success-50 border-success-200 mt-2 rounded-lg border p-3">
|
|
451
|
+
<div class="flex items-start gap-2">
|
|
452
|
+
<svg class="text-success-600 mt-0.5 h-4 w-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
453
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
|
454
|
+
</svg>
|
|
455
|
+
<div class="min-w-0 flex-1">
|
|
456
|
+
<p class="text-success-800 text-xs font-medium">Token verified successfully</p>
|
|
457
|
+
{#if tokenVerification.scopes && tokenVerification.scopes.length > 0}
|
|
458
|
+
<p class="text-success-700 mt-1 text-xs">
|
|
459
|
+
Scopes: {tokenVerification.scopes.join(', ')}
|
|
460
|
+
</p>
|
|
461
|
+
{/if}
|
|
462
|
+
</div>
|
|
463
|
+
</div>
|
|
464
|
+
</div>
|
|
465
|
+
{:else}
|
|
466
|
+
<div class="bg-danger-50 border-danger-200 mt-2 rounded-lg border p-3">
|
|
467
|
+
<div class="flex items-start gap-2">
|
|
468
|
+
<svg class="text-danger-600 mt-0.5 h-4 w-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
469
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
|
470
|
+
</svg>
|
|
471
|
+
<div class="min-w-0 flex-1">
|
|
472
|
+
<p class="text-danger-800 text-xs font-medium">Token verification failed</p>
|
|
473
|
+
{#if tokenVerification.error}
|
|
474
|
+
<p class="text-danger-700 mt-1 text-xs">{tokenVerification.error}</p>
|
|
475
|
+
{/if}
|
|
476
|
+
</div>
|
|
477
|
+
</div>
|
|
478
|
+
</div>
|
|
479
|
+
{/if}
|
|
394
480
|
{:else}
|
|
395
481
|
<p class="text-default-500 mt-1 text-xs">
|
|
396
482
|
API keys are system-managed and cannot be manually edited
|