@lanonasis/oauth-client 1.2.7 → 1.2.8
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/browser.cjs +1 -1
- package/dist/browser.mjs +1 -1
- package/dist/index.cjs +256 -63
- package/dist/index.d.cts +148 -1
- package/dist/index.d.ts +148 -1
- package/dist/index.mjs +256 -63
- package/package.json +1 -1
package/dist/browser.cjs
CHANGED
|
@@ -433,7 +433,7 @@ var APIKeyFlow = class extends BaseOAuthFlow {
|
|
|
433
433
|
*/
|
|
434
434
|
async validateAPIKey() {
|
|
435
435
|
try {
|
|
436
|
-
const response = await (0, import_cross_fetch2.default)(`${this.
|
|
436
|
+
const response = await (0, import_cross_fetch2.default)(`${this.authBaseUrl}/api/v1/health`, {
|
|
437
437
|
headers: {
|
|
438
438
|
"x-api-key": this.apiKey
|
|
439
439
|
}
|
package/dist/browser.mjs
CHANGED
|
@@ -399,7 +399,7 @@ var APIKeyFlow = class extends BaseOAuthFlow {
|
|
|
399
399
|
*/
|
|
400
400
|
async validateAPIKey() {
|
|
401
401
|
try {
|
|
402
|
-
const response = await fetch2(`${this.
|
|
402
|
+
const response = await fetch2(`${this.authBaseUrl}/api/v1/health`, {
|
|
403
403
|
headers: {
|
|
404
404
|
"x-api-key": this.apiKey
|
|
405
405
|
}
|
package/dist/index.cjs
CHANGED
|
@@ -30,12 +30,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
APIKeyFlow: () => APIKeyFlow,
|
|
33
34
|
ApiKeyStorage: () => ApiKeyStorage,
|
|
34
35
|
ApiKeyStorageWeb: () => ApiKeyStorageWeb,
|
|
35
36
|
AuthGatewayClient: () => AuthGatewayClient,
|
|
36
37
|
BaseOAuthFlow: () => BaseOAuthFlow,
|
|
37
38
|
DesktopOAuthFlow: () => DesktopOAuthFlow,
|
|
38
39
|
MCPClient: () => MCPClient,
|
|
40
|
+
MagicLinkFlow: () => MagicLinkFlow,
|
|
39
41
|
TerminalOAuthFlow: () => TerminalOAuthFlow,
|
|
40
42
|
TokenStorage: () => TokenStorage,
|
|
41
43
|
TokenStorageWeb: () => TokenStorageWeb
|
|
@@ -368,6 +370,256 @@ var DesktopOAuthFlow = class extends BaseOAuthFlow {
|
|
|
368
370
|
}
|
|
369
371
|
};
|
|
370
372
|
|
|
373
|
+
// src/flows/magic-link-flow.ts
|
|
374
|
+
var import_cross_fetch3 = __toESM(require("cross-fetch"), 1);
|
|
375
|
+
var MagicLinkFlow = class extends BaseOAuthFlow {
|
|
376
|
+
constructor(config) {
|
|
377
|
+
super(config);
|
|
378
|
+
this.projectScope = config.projectScope || "lanonasis-maas";
|
|
379
|
+
this.platform = config.platform || "cli";
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Main authenticate method - uses OTP code flow by default
|
|
383
|
+
* For interactive CLI usage, prefer using requestOTP() and verifyOTP() separately
|
|
384
|
+
*/
|
|
385
|
+
async authenticate() {
|
|
386
|
+
throw new Error(
|
|
387
|
+
"MagicLinkFlow requires two-step authentication. Use requestOTP() + verifyOTP() for CLI, or requestMagicLink() for web."
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
// ============================================================================
|
|
391
|
+
// OTP Code Flow (for CLI, mobile - user enters code manually)
|
|
392
|
+
// ============================================================================
|
|
393
|
+
/**
|
|
394
|
+
* Request a 6-digit OTP code to be sent via email
|
|
395
|
+
* User will enter this code manually in the CLI
|
|
396
|
+
*
|
|
397
|
+
* @param email - User's email address
|
|
398
|
+
* @returns Response with success status and expiration time
|
|
399
|
+
*/
|
|
400
|
+
async requestOTP(email) {
|
|
401
|
+
const response = await (0, import_cross_fetch3.default)(`${this.authBaseUrl}/v1/auth/otp/send`, {
|
|
402
|
+
method: "POST",
|
|
403
|
+
headers: { "Content-Type": "application/json" },
|
|
404
|
+
body: JSON.stringify({
|
|
405
|
+
email: email.trim().toLowerCase(),
|
|
406
|
+
type: "email",
|
|
407
|
+
// Explicitly request 6-digit code, not magic link
|
|
408
|
+
platform: this.platform,
|
|
409
|
+
project_scope: this.projectScope
|
|
410
|
+
})
|
|
411
|
+
});
|
|
412
|
+
const data = await response.json();
|
|
413
|
+
if (!response.ok) {
|
|
414
|
+
throw new Error(data.message || data.error || "Failed to send OTP");
|
|
415
|
+
}
|
|
416
|
+
return data;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Verify the OTP code entered by the user and get tokens
|
|
420
|
+
*
|
|
421
|
+
* @param email - User's email address (must match the one used in requestOTP)
|
|
422
|
+
* @param code - 6-digit OTP code from email
|
|
423
|
+
* @returns Token response with access_token, refresh_token, etc.
|
|
424
|
+
*/
|
|
425
|
+
async verifyOTP(email, code) {
|
|
426
|
+
const response = await (0, import_cross_fetch3.default)(`${this.authBaseUrl}/v1/auth/otp/verify`, {
|
|
427
|
+
method: "POST",
|
|
428
|
+
headers: { "Content-Type": "application/json" },
|
|
429
|
+
body: JSON.stringify({
|
|
430
|
+
email: email.trim().toLowerCase(),
|
|
431
|
+
token: code.trim(),
|
|
432
|
+
type: "email",
|
|
433
|
+
platform: this.platform,
|
|
434
|
+
project_scope: this.projectScope
|
|
435
|
+
})
|
|
436
|
+
});
|
|
437
|
+
const data = await response.json();
|
|
438
|
+
if (!response.ok) {
|
|
439
|
+
throw new Error(data.message || data.error || "Invalid or expired OTP code");
|
|
440
|
+
}
|
|
441
|
+
return data;
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Resend OTP code (rate limited)
|
|
445
|
+
*
|
|
446
|
+
* @param email - User's email address
|
|
447
|
+
* @returns Response with success status
|
|
448
|
+
*/
|
|
449
|
+
async resendOTP(email) {
|
|
450
|
+
const response = await (0, import_cross_fetch3.default)(`${this.authBaseUrl}/v1/auth/otp/resend`, {
|
|
451
|
+
method: "POST",
|
|
452
|
+
headers: { "Content-Type": "application/json" },
|
|
453
|
+
body: JSON.stringify({
|
|
454
|
+
email: email.trim().toLowerCase(),
|
|
455
|
+
type: "email",
|
|
456
|
+
platform: this.platform
|
|
457
|
+
})
|
|
458
|
+
});
|
|
459
|
+
const data = await response.json();
|
|
460
|
+
if (!response.ok) {
|
|
461
|
+
if (response.status === 429) {
|
|
462
|
+
throw new Error("Rate limited. Please wait before requesting another code.");
|
|
463
|
+
}
|
|
464
|
+
throw new Error(data.message || data.error || "Failed to resend OTP");
|
|
465
|
+
}
|
|
466
|
+
return data;
|
|
467
|
+
}
|
|
468
|
+
// ============================================================================
|
|
469
|
+
// Magic Link Flow (for web, desktop - user clicks link in email)
|
|
470
|
+
// ============================================================================
|
|
471
|
+
/**
|
|
472
|
+
* Request a magic link to be sent via email
|
|
473
|
+
* User will click the link which redirects to your callback URL
|
|
474
|
+
*
|
|
475
|
+
* @param email - User's email address
|
|
476
|
+
* @param redirectUri - URL to redirect to after clicking the magic link
|
|
477
|
+
* @returns Response with success status
|
|
478
|
+
*/
|
|
479
|
+
async requestMagicLink(email, redirectUri) {
|
|
480
|
+
const response = await (0, import_cross_fetch3.default)(`${this.authBaseUrl}/v1/auth/otp/send`, {
|
|
481
|
+
method: "POST",
|
|
482
|
+
headers: { "Content-Type": "application/json" },
|
|
483
|
+
body: JSON.stringify({
|
|
484
|
+
email: email.trim().toLowerCase(),
|
|
485
|
+
type: "magiclink",
|
|
486
|
+
redirect_uri: redirectUri,
|
|
487
|
+
platform: this.platform,
|
|
488
|
+
project_scope: this.projectScope
|
|
489
|
+
})
|
|
490
|
+
});
|
|
491
|
+
const data = await response.json();
|
|
492
|
+
if (!response.ok) {
|
|
493
|
+
throw new Error(data.message || data.error || "Failed to send magic link");
|
|
494
|
+
}
|
|
495
|
+
return data;
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Alternative: Use the /v1/auth/magic-link endpoint (web-optimized)
|
|
499
|
+
* This endpoint provides better redirect handling for web apps
|
|
500
|
+
*
|
|
501
|
+
* @param email - User's email address
|
|
502
|
+
* @param redirectUri - URL to redirect to after authentication
|
|
503
|
+
* @param createUser - Whether to create a new user if email doesn't exist
|
|
504
|
+
*/
|
|
505
|
+
async requestMagicLinkWeb(email, redirectUri, createUser = true) {
|
|
506
|
+
const response = await (0, import_cross_fetch3.default)(`${this.authBaseUrl}/v1/auth/magic-link`, {
|
|
507
|
+
method: "POST",
|
|
508
|
+
headers: { "Content-Type": "application/json" },
|
|
509
|
+
body: JSON.stringify({
|
|
510
|
+
email: email.trim().toLowerCase(),
|
|
511
|
+
redirect_uri: redirectUri,
|
|
512
|
+
return_to: redirectUri,
|
|
513
|
+
// Alias for compatibility
|
|
514
|
+
project_scope: this.projectScope,
|
|
515
|
+
platform: "web",
|
|
516
|
+
create_user: createUser
|
|
517
|
+
})
|
|
518
|
+
});
|
|
519
|
+
const data = await response.json();
|
|
520
|
+
if (!response.ok) {
|
|
521
|
+
throw new Error(data.message || data.error || "Failed to send magic link");
|
|
522
|
+
}
|
|
523
|
+
return data;
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Exchange magic link token for auth-gateway tokens
|
|
527
|
+
* Called after user clicks the magic link and is redirected to your callback
|
|
528
|
+
*
|
|
529
|
+
* @param supabaseAccessToken - Access token from Supabase (from URL hash/query)
|
|
530
|
+
* @param state - State parameter from the callback URL
|
|
531
|
+
* @returns Token response with redirect URL
|
|
532
|
+
*/
|
|
533
|
+
async exchangeMagicLinkToken(supabaseAccessToken, state) {
|
|
534
|
+
const response = await (0, import_cross_fetch3.default)(`${this.authBaseUrl}/v1/auth/magic-link/exchange`, {
|
|
535
|
+
method: "POST",
|
|
536
|
+
headers: {
|
|
537
|
+
"Content-Type": "application/json",
|
|
538
|
+
"Authorization": `Bearer ${supabaseAccessToken}`
|
|
539
|
+
},
|
|
540
|
+
body: JSON.stringify({ state })
|
|
541
|
+
});
|
|
542
|
+
const data = await response.json();
|
|
543
|
+
if (!response.ok) {
|
|
544
|
+
throw new Error(data.message || data.error || "Magic link exchange failed");
|
|
545
|
+
}
|
|
546
|
+
return data;
|
|
547
|
+
}
|
|
548
|
+
// ============================================================================
|
|
549
|
+
// Utility Methods
|
|
550
|
+
// ============================================================================
|
|
551
|
+
/**
|
|
552
|
+
* Check if an email is valid format
|
|
553
|
+
*/
|
|
554
|
+
static isValidEmail(email) {
|
|
555
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
556
|
+
return emailRegex.test(email.trim());
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Check if an OTP code is valid format (6 digits)
|
|
560
|
+
*/
|
|
561
|
+
static isValidOTPCode(code) {
|
|
562
|
+
return /^\d{6}$/.test(code.trim());
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
// src/flows/apikey-flow.ts
|
|
567
|
+
var import_cross_fetch4 = __toESM(require("cross-fetch"), 1);
|
|
568
|
+
var APIKeyFlow = class extends BaseOAuthFlow {
|
|
569
|
+
constructor(apiKey, authBaseUrl = "https://mcp.lanonasis.com") {
|
|
570
|
+
super({
|
|
571
|
+
clientId: "api-key-client",
|
|
572
|
+
authBaseUrl
|
|
573
|
+
});
|
|
574
|
+
this.apiKey = apiKey;
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* "Authenticate" by returning the API key as a virtual token
|
|
578
|
+
* The API key will be used directly in request headers
|
|
579
|
+
*/
|
|
580
|
+
async authenticate() {
|
|
581
|
+
if (!this.apiKey || !this.apiKey.startsWith("lano_") && !this.apiKey.startsWith("vx_")) {
|
|
582
|
+
throw new Error(
|
|
583
|
+
'Invalid API key format. Must start with "lano_" or "vx_". Please regenerate your API key from the dashboard.'
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
if (this.apiKey.startsWith("vx_")) {
|
|
587
|
+
console.warn(
|
|
588
|
+
'\u26A0\uFE0F DEPRECATION WARNING: API keys with "vx_" prefix are deprecated and will stop working soon. Please regenerate your API key from the dashboard to get a "lano_" prefixed key. Support for "vx_" keys will be removed in a future version.'
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
return {
|
|
592
|
+
access_token: this.apiKey,
|
|
593
|
+
token_type: "api-key",
|
|
594
|
+
expires_in: 0,
|
|
595
|
+
// API keys don't expire
|
|
596
|
+
issued_at: Date.now()
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* API keys don't need refresh
|
|
601
|
+
*/
|
|
602
|
+
async refreshToken(refreshToken) {
|
|
603
|
+
throw new Error("API keys do not support token refresh");
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Optional: Validate API key by making a test request
|
|
607
|
+
*/
|
|
608
|
+
async validateAPIKey() {
|
|
609
|
+
try {
|
|
610
|
+
const response = await (0, import_cross_fetch4.default)(`${this.authBaseUrl}/api/v1/health`, {
|
|
611
|
+
headers: {
|
|
612
|
+
"x-api-key": this.apiKey
|
|
613
|
+
}
|
|
614
|
+
});
|
|
615
|
+
return response.ok;
|
|
616
|
+
} catch (error) {
|
|
617
|
+
console.error("API key validation failed:", error);
|
|
618
|
+
return false;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
};
|
|
622
|
+
|
|
371
623
|
// src/storage/token-storage.ts
|
|
372
624
|
var _fs = null;
|
|
373
625
|
var _path = null;
|
|
@@ -1405,66 +1657,7 @@ var ApiKeyStorageWeb = class {
|
|
|
1405
1657
|
};
|
|
1406
1658
|
|
|
1407
1659
|
// src/client/mcp-client.ts
|
|
1408
|
-
var
|
|
1409
|
-
|
|
1410
|
-
// src/flows/apikey-flow.ts
|
|
1411
|
-
var import_cross_fetch3 = __toESM(require("cross-fetch"), 1);
|
|
1412
|
-
var APIKeyFlow = class extends BaseOAuthFlow {
|
|
1413
|
-
constructor(apiKey, authBaseUrl = "https://mcp.lanonasis.com") {
|
|
1414
|
-
super({
|
|
1415
|
-
clientId: "api-key-client",
|
|
1416
|
-
authBaseUrl
|
|
1417
|
-
});
|
|
1418
|
-
this.apiKey = apiKey;
|
|
1419
|
-
}
|
|
1420
|
-
/**
|
|
1421
|
-
* "Authenticate" by returning the API key as a virtual token
|
|
1422
|
-
* The API key will be used directly in request headers
|
|
1423
|
-
*/
|
|
1424
|
-
async authenticate() {
|
|
1425
|
-
if (!this.apiKey || !this.apiKey.startsWith("lano_") && !this.apiKey.startsWith("vx_")) {
|
|
1426
|
-
throw new Error(
|
|
1427
|
-
'Invalid API key format. Must start with "lano_" or "vx_". Please regenerate your API key from the dashboard.'
|
|
1428
|
-
);
|
|
1429
|
-
}
|
|
1430
|
-
if (this.apiKey.startsWith("vx_")) {
|
|
1431
|
-
console.warn(
|
|
1432
|
-
'\u26A0\uFE0F DEPRECATION WARNING: API keys with "vx_" prefix are deprecated and will stop working soon. Please regenerate your API key from the dashboard to get a "lano_" prefixed key. Support for "vx_" keys will be removed in a future version.'
|
|
1433
|
-
);
|
|
1434
|
-
}
|
|
1435
|
-
return {
|
|
1436
|
-
access_token: this.apiKey,
|
|
1437
|
-
token_type: "api-key",
|
|
1438
|
-
expires_in: 0,
|
|
1439
|
-
// API keys don't expire
|
|
1440
|
-
issued_at: Date.now()
|
|
1441
|
-
};
|
|
1442
|
-
}
|
|
1443
|
-
/**
|
|
1444
|
-
* API keys don't need refresh
|
|
1445
|
-
*/
|
|
1446
|
-
async refreshToken(refreshToken) {
|
|
1447
|
-
throw new Error("API keys do not support token refresh");
|
|
1448
|
-
}
|
|
1449
|
-
/**
|
|
1450
|
-
* Optional: Validate API key by making a test request
|
|
1451
|
-
*/
|
|
1452
|
-
async validateAPIKey() {
|
|
1453
|
-
try {
|
|
1454
|
-
const response = await (0, import_cross_fetch3.default)(`${this.config.authBaseUrl}/api/v1/health`, {
|
|
1455
|
-
headers: {
|
|
1456
|
-
"x-api-key": this.apiKey
|
|
1457
|
-
}
|
|
1458
|
-
});
|
|
1459
|
-
return response.ok;
|
|
1460
|
-
} catch (error) {
|
|
1461
|
-
console.error("API key validation failed:", error);
|
|
1462
|
-
return false;
|
|
1463
|
-
}
|
|
1464
|
-
}
|
|
1465
|
-
};
|
|
1466
|
-
|
|
1467
|
-
// src/client/mcp-client.ts
|
|
1660
|
+
var import_cross_fetch5 = __toESM(require("cross-fetch"), 1);
|
|
1468
1661
|
var MCPClient = class {
|
|
1469
1662
|
constructor(config = {}) {
|
|
1470
1663
|
// ← NEW: Track auth mode
|
|
@@ -1693,7 +1886,7 @@ var MCPClient = class {
|
|
|
1693
1886
|
} else {
|
|
1694
1887
|
headers["Authorization"] = `Bearer ${this.accessToken}`;
|
|
1695
1888
|
}
|
|
1696
|
-
const response = await (0,
|
|
1889
|
+
const response = await (0, import_cross_fetch5.default)(`${this.config.mcpEndpoint}/api`, {
|
|
1697
1890
|
method: "POST",
|
|
1698
1891
|
headers,
|
|
1699
1892
|
body: JSON.stringify({
|
|
@@ -1794,7 +1987,7 @@ var MCPClient = class {
|
|
|
1794
1987
|
};
|
|
1795
1988
|
|
|
1796
1989
|
// src/client/auth-gateway-client.ts
|
|
1797
|
-
var
|
|
1990
|
+
var import_cross_fetch6 = __toESM(require("cross-fetch"), 1);
|
|
1798
1991
|
var GatewayOAuthFlow = class extends BaseOAuthFlow {
|
|
1799
1992
|
async authenticate() {
|
|
1800
1993
|
throw new Error("Interactive authentication is not supported in AuthGatewayClient.");
|
|
@@ -1958,7 +2151,7 @@ var AuthGatewayClient = class {
|
|
|
1958
2151
|
return "jwt";
|
|
1959
2152
|
}
|
|
1960
2153
|
async requestJson(path, options) {
|
|
1961
|
-
const response = await (0,
|
|
2154
|
+
const response = await (0, import_cross_fetch6.default)(`${this.authBaseUrl}${path.startsWith("/") ? path : `/${path}`}`, options);
|
|
1962
2155
|
const text = await response.text();
|
|
1963
2156
|
let data = null;
|
|
1964
2157
|
if (text) {
|
package/dist/index.d.cts
CHANGED
|
@@ -13,6 +13,153 @@ declare class TerminalOAuthFlow extends BaseOAuthFlow {
|
|
|
13
13
|
private checkDeviceCode;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Magic Link / OTP Flow
|
|
18
|
+
*
|
|
19
|
+
* Passwordless authentication supporting two modes:
|
|
20
|
+
* 1. OTP Code (type: 'email') - User receives 6-digit code to enter manually (CLI, mobile)
|
|
21
|
+
* 2. Magic Link (type: 'magiclink') - User clicks link in email (web, desktop)
|
|
22
|
+
*
|
|
23
|
+
* Usage:
|
|
24
|
+
* ```typescript
|
|
25
|
+
* // OTP Code Flow (CLI)
|
|
26
|
+
* const flow = new MagicLinkFlow({ clientId: 'my-app' });
|
|
27
|
+
* await flow.requestOTP('user@example.com');
|
|
28
|
+
* const tokens = await flow.verifyOTP('user@example.com', '123456');
|
|
29
|
+
*
|
|
30
|
+
* // Magic Link Flow (Web)
|
|
31
|
+
* const flow = new MagicLinkFlow({ clientId: 'my-app' });
|
|
32
|
+
* await flow.requestMagicLink('user@example.com', 'https://myapp.com/auth/callback');
|
|
33
|
+
* // User clicks link in email, then:
|
|
34
|
+
* const tokens = await flow.exchangeMagicLinkToken(supabaseAccessToken, state);
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
type OTPType = 'email' | 'magiclink';
|
|
39
|
+
type Platform = 'cli' | 'web' | 'mcp' | 'api';
|
|
40
|
+
interface MagicLinkConfig extends OAuthConfig {
|
|
41
|
+
projectScope?: string;
|
|
42
|
+
platform?: Platform;
|
|
43
|
+
}
|
|
44
|
+
interface OTPSendResponse {
|
|
45
|
+
success: boolean;
|
|
46
|
+
message: string;
|
|
47
|
+
type: OTPType;
|
|
48
|
+
expires_in: number;
|
|
49
|
+
}
|
|
50
|
+
interface OTPVerifyResponse extends TokenResponse {
|
|
51
|
+
auth_method: 'otp' | 'magic_link';
|
|
52
|
+
user?: {
|
|
53
|
+
id: string;
|
|
54
|
+
email: string;
|
|
55
|
+
role: string;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
interface MagicLinkExchangeResponse extends TokenResponse {
|
|
59
|
+
redirect_to?: string;
|
|
60
|
+
user?: {
|
|
61
|
+
id: string;
|
|
62
|
+
email?: string;
|
|
63
|
+
role?: string;
|
|
64
|
+
project_scope?: string;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
declare class MagicLinkFlow extends BaseOAuthFlow {
|
|
68
|
+
private readonly projectScope;
|
|
69
|
+
private readonly platform;
|
|
70
|
+
constructor(config: MagicLinkConfig);
|
|
71
|
+
/**
|
|
72
|
+
* Main authenticate method - uses OTP code flow by default
|
|
73
|
+
* For interactive CLI usage, prefer using requestOTP() and verifyOTP() separately
|
|
74
|
+
*/
|
|
75
|
+
authenticate(): Promise<TokenResponse>;
|
|
76
|
+
/**
|
|
77
|
+
* Request a 6-digit OTP code to be sent via email
|
|
78
|
+
* User will enter this code manually in the CLI
|
|
79
|
+
*
|
|
80
|
+
* @param email - User's email address
|
|
81
|
+
* @returns Response with success status and expiration time
|
|
82
|
+
*/
|
|
83
|
+
requestOTP(email: string): Promise<OTPSendResponse>;
|
|
84
|
+
/**
|
|
85
|
+
* Verify the OTP code entered by the user and get tokens
|
|
86
|
+
*
|
|
87
|
+
* @param email - User's email address (must match the one used in requestOTP)
|
|
88
|
+
* @param code - 6-digit OTP code from email
|
|
89
|
+
* @returns Token response with access_token, refresh_token, etc.
|
|
90
|
+
*/
|
|
91
|
+
verifyOTP(email: string, code: string): Promise<OTPVerifyResponse>;
|
|
92
|
+
/**
|
|
93
|
+
* Resend OTP code (rate limited)
|
|
94
|
+
*
|
|
95
|
+
* @param email - User's email address
|
|
96
|
+
* @returns Response with success status
|
|
97
|
+
*/
|
|
98
|
+
resendOTP(email: string): Promise<OTPSendResponse>;
|
|
99
|
+
/**
|
|
100
|
+
* Request a magic link to be sent via email
|
|
101
|
+
* User will click the link which redirects to your callback URL
|
|
102
|
+
*
|
|
103
|
+
* @param email - User's email address
|
|
104
|
+
* @param redirectUri - URL to redirect to after clicking the magic link
|
|
105
|
+
* @returns Response with success status
|
|
106
|
+
*/
|
|
107
|
+
requestMagicLink(email: string, redirectUri: string): Promise<OTPSendResponse>;
|
|
108
|
+
/**
|
|
109
|
+
* Alternative: Use the /v1/auth/magic-link endpoint (web-optimized)
|
|
110
|
+
* This endpoint provides better redirect handling for web apps
|
|
111
|
+
*
|
|
112
|
+
* @param email - User's email address
|
|
113
|
+
* @param redirectUri - URL to redirect to after authentication
|
|
114
|
+
* @param createUser - Whether to create a new user if email doesn't exist
|
|
115
|
+
*/
|
|
116
|
+
requestMagicLinkWeb(email: string, redirectUri: string, createUser?: boolean): Promise<{
|
|
117
|
+
success: boolean;
|
|
118
|
+
message: string;
|
|
119
|
+
}>;
|
|
120
|
+
/**
|
|
121
|
+
* Exchange magic link token for auth-gateway tokens
|
|
122
|
+
* Called after user clicks the magic link and is redirected to your callback
|
|
123
|
+
*
|
|
124
|
+
* @param supabaseAccessToken - Access token from Supabase (from URL hash/query)
|
|
125
|
+
* @param state - State parameter from the callback URL
|
|
126
|
+
* @returns Token response with redirect URL
|
|
127
|
+
*/
|
|
128
|
+
exchangeMagicLinkToken(supabaseAccessToken: string, state: string): Promise<MagicLinkExchangeResponse>;
|
|
129
|
+
/**
|
|
130
|
+
* Check if an email is valid format
|
|
131
|
+
*/
|
|
132
|
+
static isValidEmail(email: string): boolean;
|
|
133
|
+
/**
|
|
134
|
+
* Check if an OTP code is valid format (6 digits)
|
|
135
|
+
*/
|
|
136
|
+
static isValidOTPCode(code: string): boolean;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* API Key Authentication Flow
|
|
141
|
+
*
|
|
142
|
+
* This flow uses a direct API key for authentication instead of OAuth.
|
|
143
|
+
* The API key is sent via x-api-key header to the backend.
|
|
144
|
+
*/
|
|
145
|
+
declare class APIKeyFlow extends BaseOAuthFlow {
|
|
146
|
+
private apiKey;
|
|
147
|
+
constructor(apiKey: string, authBaseUrl?: string);
|
|
148
|
+
/**
|
|
149
|
+
* "Authenticate" by returning the API key as a virtual token
|
|
150
|
+
* The API key will be used directly in request headers
|
|
151
|
+
*/
|
|
152
|
+
authenticate(): Promise<TokenResponse>;
|
|
153
|
+
/**
|
|
154
|
+
* API keys don't need refresh
|
|
155
|
+
*/
|
|
156
|
+
refreshToken(refreshToken: string): Promise<TokenResponse>;
|
|
157
|
+
/**
|
|
158
|
+
* Optional: Validate API key by making a test request
|
|
159
|
+
*/
|
|
160
|
+
validateAPIKey(): Promise<boolean>;
|
|
161
|
+
}
|
|
162
|
+
|
|
16
163
|
interface MCPClientConfig extends Partial<OAuthConfig> {
|
|
17
164
|
mcpEndpoint?: string;
|
|
18
165
|
autoRefresh?: boolean;
|
|
@@ -51,4 +198,4 @@ declare class MCPClient {
|
|
|
51
198
|
deleteMemory(id: string): Promise<void>;
|
|
52
199
|
}
|
|
53
200
|
|
|
54
|
-
export { BaseOAuthFlow, MCPClient, type MCPClientConfig, OAuthConfig, TerminalOAuthFlow, TokenResponse, TokenStorageAdapter };
|
|
201
|
+
export { APIKeyFlow, BaseOAuthFlow, MCPClient, type MCPClientConfig, type MagicLinkConfig, type MagicLinkExchangeResponse, MagicLinkFlow, OAuthConfig, type OTPSendResponse, type OTPType, type OTPVerifyResponse, type Platform, TerminalOAuthFlow, TokenResponse, TokenStorageAdapter };
|
package/dist/index.d.ts
CHANGED
|
@@ -13,6 +13,153 @@ declare class TerminalOAuthFlow extends BaseOAuthFlow {
|
|
|
13
13
|
private checkDeviceCode;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Magic Link / OTP Flow
|
|
18
|
+
*
|
|
19
|
+
* Passwordless authentication supporting two modes:
|
|
20
|
+
* 1. OTP Code (type: 'email') - User receives 6-digit code to enter manually (CLI, mobile)
|
|
21
|
+
* 2. Magic Link (type: 'magiclink') - User clicks link in email (web, desktop)
|
|
22
|
+
*
|
|
23
|
+
* Usage:
|
|
24
|
+
* ```typescript
|
|
25
|
+
* // OTP Code Flow (CLI)
|
|
26
|
+
* const flow = new MagicLinkFlow({ clientId: 'my-app' });
|
|
27
|
+
* await flow.requestOTP('user@example.com');
|
|
28
|
+
* const tokens = await flow.verifyOTP('user@example.com', '123456');
|
|
29
|
+
*
|
|
30
|
+
* // Magic Link Flow (Web)
|
|
31
|
+
* const flow = new MagicLinkFlow({ clientId: 'my-app' });
|
|
32
|
+
* await flow.requestMagicLink('user@example.com', 'https://myapp.com/auth/callback');
|
|
33
|
+
* // User clicks link in email, then:
|
|
34
|
+
* const tokens = await flow.exchangeMagicLinkToken(supabaseAccessToken, state);
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
type OTPType = 'email' | 'magiclink';
|
|
39
|
+
type Platform = 'cli' | 'web' | 'mcp' | 'api';
|
|
40
|
+
interface MagicLinkConfig extends OAuthConfig {
|
|
41
|
+
projectScope?: string;
|
|
42
|
+
platform?: Platform;
|
|
43
|
+
}
|
|
44
|
+
interface OTPSendResponse {
|
|
45
|
+
success: boolean;
|
|
46
|
+
message: string;
|
|
47
|
+
type: OTPType;
|
|
48
|
+
expires_in: number;
|
|
49
|
+
}
|
|
50
|
+
interface OTPVerifyResponse extends TokenResponse {
|
|
51
|
+
auth_method: 'otp' | 'magic_link';
|
|
52
|
+
user?: {
|
|
53
|
+
id: string;
|
|
54
|
+
email: string;
|
|
55
|
+
role: string;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
interface MagicLinkExchangeResponse extends TokenResponse {
|
|
59
|
+
redirect_to?: string;
|
|
60
|
+
user?: {
|
|
61
|
+
id: string;
|
|
62
|
+
email?: string;
|
|
63
|
+
role?: string;
|
|
64
|
+
project_scope?: string;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
declare class MagicLinkFlow extends BaseOAuthFlow {
|
|
68
|
+
private readonly projectScope;
|
|
69
|
+
private readonly platform;
|
|
70
|
+
constructor(config: MagicLinkConfig);
|
|
71
|
+
/**
|
|
72
|
+
* Main authenticate method - uses OTP code flow by default
|
|
73
|
+
* For interactive CLI usage, prefer using requestOTP() and verifyOTP() separately
|
|
74
|
+
*/
|
|
75
|
+
authenticate(): Promise<TokenResponse>;
|
|
76
|
+
/**
|
|
77
|
+
* Request a 6-digit OTP code to be sent via email
|
|
78
|
+
* User will enter this code manually in the CLI
|
|
79
|
+
*
|
|
80
|
+
* @param email - User's email address
|
|
81
|
+
* @returns Response with success status and expiration time
|
|
82
|
+
*/
|
|
83
|
+
requestOTP(email: string): Promise<OTPSendResponse>;
|
|
84
|
+
/**
|
|
85
|
+
* Verify the OTP code entered by the user and get tokens
|
|
86
|
+
*
|
|
87
|
+
* @param email - User's email address (must match the one used in requestOTP)
|
|
88
|
+
* @param code - 6-digit OTP code from email
|
|
89
|
+
* @returns Token response with access_token, refresh_token, etc.
|
|
90
|
+
*/
|
|
91
|
+
verifyOTP(email: string, code: string): Promise<OTPVerifyResponse>;
|
|
92
|
+
/**
|
|
93
|
+
* Resend OTP code (rate limited)
|
|
94
|
+
*
|
|
95
|
+
* @param email - User's email address
|
|
96
|
+
* @returns Response with success status
|
|
97
|
+
*/
|
|
98
|
+
resendOTP(email: string): Promise<OTPSendResponse>;
|
|
99
|
+
/**
|
|
100
|
+
* Request a magic link to be sent via email
|
|
101
|
+
* User will click the link which redirects to your callback URL
|
|
102
|
+
*
|
|
103
|
+
* @param email - User's email address
|
|
104
|
+
* @param redirectUri - URL to redirect to after clicking the magic link
|
|
105
|
+
* @returns Response with success status
|
|
106
|
+
*/
|
|
107
|
+
requestMagicLink(email: string, redirectUri: string): Promise<OTPSendResponse>;
|
|
108
|
+
/**
|
|
109
|
+
* Alternative: Use the /v1/auth/magic-link endpoint (web-optimized)
|
|
110
|
+
* This endpoint provides better redirect handling for web apps
|
|
111
|
+
*
|
|
112
|
+
* @param email - User's email address
|
|
113
|
+
* @param redirectUri - URL to redirect to after authentication
|
|
114
|
+
* @param createUser - Whether to create a new user if email doesn't exist
|
|
115
|
+
*/
|
|
116
|
+
requestMagicLinkWeb(email: string, redirectUri: string, createUser?: boolean): Promise<{
|
|
117
|
+
success: boolean;
|
|
118
|
+
message: string;
|
|
119
|
+
}>;
|
|
120
|
+
/**
|
|
121
|
+
* Exchange magic link token for auth-gateway tokens
|
|
122
|
+
* Called after user clicks the magic link and is redirected to your callback
|
|
123
|
+
*
|
|
124
|
+
* @param supabaseAccessToken - Access token from Supabase (from URL hash/query)
|
|
125
|
+
* @param state - State parameter from the callback URL
|
|
126
|
+
* @returns Token response with redirect URL
|
|
127
|
+
*/
|
|
128
|
+
exchangeMagicLinkToken(supabaseAccessToken: string, state: string): Promise<MagicLinkExchangeResponse>;
|
|
129
|
+
/**
|
|
130
|
+
* Check if an email is valid format
|
|
131
|
+
*/
|
|
132
|
+
static isValidEmail(email: string): boolean;
|
|
133
|
+
/**
|
|
134
|
+
* Check if an OTP code is valid format (6 digits)
|
|
135
|
+
*/
|
|
136
|
+
static isValidOTPCode(code: string): boolean;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* API Key Authentication Flow
|
|
141
|
+
*
|
|
142
|
+
* This flow uses a direct API key for authentication instead of OAuth.
|
|
143
|
+
* The API key is sent via x-api-key header to the backend.
|
|
144
|
+
*/
|
|
145
|
+
declare class APIKeyFlow extends BaseOAuthFlow {
|
|
146
|
+
private apiKey;
|
|
147
|
+
constructor(apiKey: string, authBaseUrl?: string);
|
|
148
|
+
/**
|
|
149
|
+
* "Authenticate" by returning the API key as a virtual token
|
|
150
|
+
* The API key will be used directly in request headers
|
|
151
|
+
*/
|
|
152
|
+
authenticate(): Promise<TokenResponse>;
|
|
153
|
+
/**
|
|
154
|
+
* API keys don't need refresh
|
|
155
|
+
*/
|
|
156
|
+
refreshToken(refreshToken: string): Promise<TokenResponse>;
|
|
157
|
+
/**
|
|
158
|
+
* Optional: Validate API key by making a test request
|
|
159
|
+
*/
|
|
160
|
+
validateAPIKey(): Promise<boolean>;
|
|
161
|
+
}
|
|
162
|
+
|
|
16
163
|
interface MCPClientConfig extends Partial<OAuthConfig> {
|
|
17
164
|
mcpEndpoint?: string;
|
|
18
165
|
autoRefresh?: boolean;
|
|
@@ -51,4 +198,4 @@ declare class MCPClient {
|
|
|
51
198
|
deleteMemory(id: string): Promise<void>;
|
|
52
199
|
}
|
|
53
200
|
|
|
54
|
-
export { BaseOAuthFlow, MCPClient, type MCPClientConfig, OAuthConfig, TerminalOAuthFlow, TokenResponse, TokenStorageAdapter };
|
|
201
|
+
export { APIKeyFlow, BaseOAuthFlow, MCPClient, type MCPClientConfig, type MagicLinkConfig, type MagicLinkExchangeResponse, MagicLinkFlow, OAuthConfig, type OTPSendResponse, type OTPType, type OTPVerifyResponse, type Platform, TerminalOAuthFlow, TokenResponse, TokenStorageAdapter };
|
package/dist/index.mjs
CHANGED
|
@@ -331,6 +331,256 @@ var DesktopOAuthFlow = class extends BaseOAuthFlow {
|
|
|
331
331
|
}
|
|
332
332
|
};
|
|
333
333
|
|
|
334
|
+
// src/flows/magic-link-flow.ts
|
|
335
|
+
import fetch3 from "cross-fetch";
|
|
336
|
+
var MagicLinkFlow = class extends BaseOAuthFlow {
|
|
337
|
+
constructor(config) {
|
|
338
|
+
super(config);
|
|
339
|
+
this.projectScope = config.projectScope || "lanonasis-maas";
|
|
340
|
+
this.platform = config.platform || "cli";
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Main authenticate method - uses OTP code flow by default
|
|
344
|
+
* For interactive CLI usage, prefer using requestOTP() and verifyOTP() separately
|
|
345
|
+
*/
|
|
346
|
+
async authenticate() {
|
|
347
|
+
throw new Error(
|
|
348
|
+
"MagicLinkFlow requires two-step authentication. Use requestOTP() + verifyOTP() for CLI, or requestMagicLink() for web."
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
// ============================================================================
|
|
352
|
+
// OTP Code Flow (for CLI, mobile - user enters code manually)
|
|
353
|
+
// ============================================================================
|
|
354
|
+
/**
|
|
355
|
+
* Request a 6-digit OTP code to be sent via email
|
|
356
|
+
* User will enter this code manually in the CLI
|
|
357
|
+
*
|
|
358
|
+
* @param email - User's email address
|
|
359
|
+
* @returns Response with success status and expiration time
|
|
360
|
+
*/
|
|
361
|
+
async requestOTP(email) {
|
|
362
|
+
const response = await fetch3(`${this.authBaseUrl}/v1/auth/otp/send`, {
|
|
363
|
+
method: "POST",
|
|
364
|
+
headers: { "Content-Type": "application/json" },
|
|
365
|
+
body: JSON.stringify({
|
|
366
|
+
email: email.trim().toLowerCase(),
|
|
367
|
+
type: "email",
|
|
368
|
+
// Explicitly request 6-digit code, not magic link
|
|
369
|
+
platform: this.platform,
|
|
370
|
+
project_scope: this.projectScope
|
|
371
|
+
})
|
|
372
|
+
});
|
|
373
|
+
const data = await response.json();
|
|
374
|
+
if (!response.ok) {
|
|
375
|
+
throw new Error(data.message || data.error || "Failed to send OTP");
|
|
376
|
+
}
|
|
377
|
+
return data;
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Verify the OTP code entered by the user and get tokens
|
|
381
|
+
*
|
|
382
|
+
* @param email - User's email address (must match the one used in requestOTP)
|
|
383
|
+
* @param code - 6-digit OTP code from email
|
|
384
|
+
* @returns Token response with access_token, refresh_token, etc.
|
|
385
|
+
*/
|
|
386
|
+
async verifyOTP(email, code) {
|
|
387
|
+
const response = await fetch3(`${this.authBaseUrl}/v1/auth/otp/verify`, {
|
|
388
|
+
method: "POST",
|
|
389
|
+
headers: { "Content-Type": "application/json" },
|
|
390
|
+
body: JSON.stringify({
|
|
391
|
+
email: email.trim().toLowerCase(),
|
|
392
|
+
token: code.trim(),
|
|
393
|
+
type: "email",
|
|
394
|
+
platform: this.platform,
|
|
395
|
+
project_scope: this.projectScope
|
|
396
|
+
})
|
|
397
|
+
});
|
|
398
|
+
const data = await response.json();
|
|
399
|
+
if (!response.ok) {
|
|
400
|
+
throw new Error(data.message || data.error || "Invalid or expired OTP code");
|
|
401
|
+
}
|
|
402
|
+
return data;
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Resend OTP code (rate limited)
|
|
406
|
+
*
|
|
407
|
+
* @param email - User's email address
|
|
408
|
+
* @returns Response with success status
|
|
409
|
+
*/
|
|
410
|
+
async resendOTP(email) {
|
|
411
|
+
const response = await fetch3(`${this.authBaseUrl}/v1/auth/otp/resend`, {
|
|
412
|
+
method: "POST",
|
|
413
|
+
headers: { "Content-Type": "application/json" },
|
|
414
|
+
body: JSON.stringify({
|
|
415
|
+
email: email.trim().toLowerCase(),
|
|
416
|
+
type: "email",
|
|
417
|
+
platform: this.platform
|
|
418
|
+
})
|
|
419
|
+
});
|
|
420
|
+
const data = await response.json();
|
|
421
|
+
if (!response.ok) {
|
|
422
|
+
if (response.status === 429) {
|
|
423
|
+
throw new Error("Rate limited. Please wait before requesting another code.");
|
|
424
|
+
}
|
|
425
|
+
throw new Error(data.message || data.error || "Failed to resend OTP");
|
|
426
|
+
}
|
|
427
|
+
return data;
|
|
428
|
+
}
|
|
429
|
+
// ============================================================================
|
|
430
|
+
// Magic Link Flow (for web, desktop - user clicks link in email)
|
|
431
|
+
// ============================================================================
|
|
432
|
+
/**
|
|
433
|
+
* Request a magic link to be sent via email
|
|
434
|
+
* User will click the link which redirects to your callback URL
|
|
435
|
+
*
|
|
436
|
+
* @param email - User's email address
|
|
437
|
+
* @param redirectUri - URL to redirect to after clicking the magic link
|
|
438
|
+
* @returns Response with success status
|
|
439
|
+
*/
|
|
440
|
+
async requestMagicLink(email, redirectUri) {
|
|
441
|
+
const response = await fetch3(`${this.authBaseUrl}/v1/auth/otp/send`, {
|
|
442
|
+
method: "POST",
|
|
443
|
+
headers: { "Content-Type": "application/json" },
|
|
444
|
+
body: JSON.stringify({
|
|
445
|
+
email: email.trim().toLowerCase(),
|
|
446
|
+
type: "magiclink",
|
|
447
|
+
redirect_uri: redirectUri,
|
|
448
|
+
platform: this.platform,
|
|
449
|
+
project_scope: this.projectScope
|
|
450
|
+
})
|
|
451
|
+
});
|
|
452
|
+
const data = await response.json();
|
|
453
|
+
if (!response.ok) {
|
|
454
|
+
throw new Error(data.message || data.error || "Failed to send magic link");
|
|
455
|
+
}
|
|
456
|
+
return data;
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Alternative: Use the /v1/auth/magic-link endpoint (web-optimized)
|
|
460
|
+
* This endpoint provides better redirect handling for web apps
|
|
461
|
+
*
|
|
462
|
+
* @param email - User's email address
|
|
463
|
+
* @param redirectUri - URL to redirect to after authentication
|
|
464
|
+
* @param createUser - Whether to create a new user if email doesn't exist
|
|
465
|
+
*/
|
|
466
|
+
async requestMagicLinkWeb(email, redirectUri, createUser = true) {
|
|
467
|
+
const response = await fetch3(`${this.authBaseUrl}/v1/auth/magic-link`, {
|
|
468
|
+
method: "POST",
|
|
469
|
+
headers: { "Content-Type": "application/json" },
|
|
470
|
+
body: JSON.stringify({
|
|
471
|
+
email: email.trim().toLowerCase(),
|
|
472
|
+
redirect_uri: redirectUri,
|
|
473
|
+
return_to: redirectUri,
|
|
474
|
+
// Alias for compatibility
|
|
475
|
+
project_scope: this.projectScope,
|
|
476
|
+
platform: "web",
|
|
477
|
+
create_user: createUser
|
|
478
|
+
})
|
|
479
|
+
});
|
|
480
|
+
const data = await response.json();
|
|
481
|
+
if (!response.ok) {
|
|
482
|
+
throw new Error(data.message || data.error || "Failed to send magic link");
|
|
483
|
+
}
|
|
484
|
+
return data;
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Exchange magic link token for auth-gateway tokens
|
|
488
|
+
* Called after user clicks the magic link and is redirected to your callback
|
|
489
|
+
*
|
|
490
|
+
* @param supabaseAccessToken - Access token from Supabase (from URL hash/query)
|
|
491
|
+
* @param state - State parameter from the callback URL
|
|
492
|
+
* @returns Token response with redirect URL
|
|
493
|
+
*/
|
|
494
|
+
async exchangeMagicLinkToken(supabaseAccessToken, state) {
|
|
495
|
+
const response = await fetch3(`${this.authBaseUrl}/v1/auth/magic-link/exchange`, {
|
|
496
|
+
method: "POST",
|
|
497
|
+
headers: {
|
|
498
|
+
"Content-Type": "application/json",
|
|
499
|
+
"Authorization": `Bearer ${supabaseAccessToken}`
|
|
500
|
+
},
|
|
501
|
+
body: JSON.stringify({ state })
|
|
502
|
+
});
|
|
503
|
+
const data = await response.json();
|
|
504
|
+
if (!response.ok) {
|
|
505
|
+
throw new Error(data.message || data.error || "Magic link exchange failed");
|
|
506
|
+
}
|
|
507
|
+
return data;
|
|
508
|
+
}
|
|
509
|
+
// ============================================================================
|
|
510
|
+
// Utility Methods
|
|
511
|
+
// ============================================================================
|
|
512
|
+
/**
|
|
513
|
+
* Check if an email is valid format
|
|
514
|
+
*/
|
|
515
|
+
static isValidEmail(email) {
|
|
516
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
517
|
+
return emailRegex.test(email.trim());
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Check if an OTP code is valid format (6 digits)
|
|
521
|
+
*/
|
|
522
|
+
static isValidOTPCode(code) {
|
|
523
|
+
return /^\d{6}$/.test(code.trim());
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
// src/flows/apikey-flow.ts
|
|
528
|
+
import fetch4 from "cross-fetch";
|
|
529
|
+
var APIKeyFlow = class extends BaseOAuthFlow {
|
|
530
|
+
constructor(apiKey, authBaseUrl = "https://mcp.lanonasis.com") {
|
|
531
|
+
super({
|
|
532
|
+
clientId: "api-key-client",
|
|
533
|
+
authBaseUrl
|
|
534
|
+
});
|
|
535
|
+
this.apiKey = apiKey;
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* "Authenticate" by returning the API key as a virtual token
|
|
539
|
+
* The API key will be used directly in request headers
|
|
540
|
+
*/
|
|
541
|
+
async authenticate() {
|
|
542
|
+
if (!this.apiKey || !this.apiKey.startsWith("lano_") && !this.apiKey.startsWith("vx_")) {
|
|
543
|
+
throw new Error(
|
|
544
|
+
'Invalid API key format. Must start with "lano_" or "vx_". Please regenerate your API key from the dashboard.'
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
if (this.apiKey.startsWith("vx_")) {
|
|
548
|
+
console.warn(
|
|
549
|
+
'\u26A0\uFE0F DEPRECATION WARNING: API keys with "vx_" prefix are deprecated and will stop working soon. Please regenerate your API key from the dashboard to get a "lano_" prefixed key. Support for "vx_" keys will be removed in a future version.'
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
return {
|
|
553
|
+
access_token: this.apiKey,
|
|
554
|
+
token_type: "api-key",
|
|
555
|
+
expires_in: 0,
|
|
556
|
+
// API keys don't expire
|
|
557
|
+
issued_at: Date.now()
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* API keys don't need refresh
|
|
562
|
+
*/
|
|
563
|
+
async refreshToken(refreshToken) {
|
|
564
|
+
throw new Error("API keys do not support token refresh");
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Optional: Validate API key by making a test request
|
|
568
|
+
*/
|
|
569
|
+
async validateAPIKey() {
|
|
570
|
+
try {
|
|
571
|
+
const response = await fetch4(`${this.authBaseUrl}/api/v1/health`, {
|
|
572
|
+
headers: {
|
|
573
|
+
"x-api-key": this.apiKey
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
return response.ok;
|
|
577
|
+
} catch (error) {
|
|
578
|
+
console.error("API key validation failed:", error);
|
|
579
|
+
return false;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
};
|
|
583
|
+
|
|
334
584
|
// src/storage/token-storage.ts
|
|
335
585
|
var _fs = null;
|
|
336
586
|
var _path = null;
|
|
@@ -1368,66 +1618,7 @@ var ApiKeyStorageWeb = class {
|
|
|
1368
1618
|
};
|
|
1369
1619
|
|
|
1370
1620
|
// src/client/mcp-client.ts
|
|
1371
|
-
import
|
|
1372
|
-
|
|
1373
|
-
// src/flows/apikey-flow.ts
|
|
1374
|
-
import fetch3 from "cross-fetch";
|
|
1375
|
-
var APIKeyFlow = class extends BaseOAuthFlow {
|
|
1376
|
-
constructor(apiKey, authBaseUrl = "https://mcp.lanonasis.com") {
|
|
1377
|
-
super({
|
|
1378
|
-
clientId: "api-key-client",
|
|
1379
|
-
authBaseUrl
|
|
1380
|
-
});
|
|
1381
|
-
this.apiKey = apiKey;
|
|
1382
|
-
}
|
|
1383
|
-
/**
|
|
1384
|
-
* "Authenticate" by returning the API key as a virtual token
|
|
1385
|
-
* The API key will be used directly in request headers
|
|
1386
|
-
*/
|
|
1387
|
-
async authenticate() {
|
|
1388
|
-
if (!this.apiKey || !this.apiKey.startsWith("lano_") && !this.apiKey.startsWith("vx_")) {
|
|
1389
|
-
throw new Error(
|
|
1390
|
-
'Invalid API key format. Must start with "lano_" or "vx_". Please regenerate your API key from the dashboard.'
|
|
1391
|
-
);
|
|
1392
|
-
}
|
|
1393
|
-
if (this.apiKey.startsWith("vx_")) {
|
|
1394
|
-
console.warn(
|
|
1395
|
-
'\u26A0\uFE0F DEPRECATION WARNING: API keys with "vx_" prefix are deprecated and will stop working soon. Please regenerate your API key from the dashboard to get a "lano_" prefixed key. Support for "vx_" keys will be removed in a future version.'
|
|
1396
|
-
);
|
|
1397
|
-
}
|
|
1398
|
-
return {
|
|
1399
|
-
access_token: this.apiKey,
|
|
1400
|
-
token_type: "api-key",
|
|
1401
|
-
expires_in: 0,
|
|
1402
|
-
// API keys don't expire
|
|
1403
|
-
issued_at: Date.now()
|
|
1404
|
-
};
|
|
1405
|
-
}
|
|
1406
|
-
/**
|
|
1407
|
-
* API keys don't need refresh
|
|
1408
|
-
*/
|
|
1409
|
-
async refreshToken(refreshToken) {
|
|
1410
|
-
throw new Error("API keys do not support token refresh");
|
|
1411
|
-
}
|
|
1412
|
-
/**
|
|
1413
|
-
* Optional: Validate API key by making a test request
|
|
1414
|
-
*/
|
|
1415
|
-
async validateAPIKey() {
|
|
1416
|
-
try {
|
|
1417
|
-
const response = await fetch3(`${this.config.authBaseUrl}/api/v1/health`, {
|
|
1418
|
-
headers: {
|
|
1419
|
-
"x-api-key": this.apiKey
|
|
1420
|
-
}
|
|
1421
|
-
});
|
|
1422
|
-
return response.ok;
|
|
1423
|
-
} catch (error) {
|
|
1424
|
-
console.error("API key validation failed:", error);
|
|
1425
|
-
return false;
|
|
1426
|
-
}
|
|
1427
|
-
}
|
|
1428
|
-
};
|
|
1429
|
-
|
|
1430
|
-
// src/client/mcp-client.ts
|
|
1621
|
+
import fetch5 from "cross-fetch";
|
|
1431
1622
|
var MCPClient = class {
|
|
1432
1623
|
constructor(config = {}) {
|
|
1433
1624
|
// ← NEW: Track auth mode
|
|
@@ -1656,7 +1847,7 @@ var MCPClient = class {
|
|
|
1656
1847
|
} else {
|
|
1657
1848
|
headers["Authorization"] = `Bearer ${this.accessToken}`;
|
|
1658
1849
|
}
|
|
1659
|
-
const response = await
|
|
1850
|
+
const response = await fetch5(`${this.config.mcpEndpoint}/api`, {
|
|
1660
1851
|
method: "POST",
|
|
1661
1852
|
headers,
|
|
1662
1853
|
body: JSON.stringify({
|
|
@@ -1757,7 +1948,7 @@ var MCPClient = class {
|
|
|
1757
1948
|
};
|
|
1758
1949
|
|
|
1759
1950
|
// src/client/auth-gateway-client.ts
|
|
1760
|
-
import
|
|
1951
|
+
import fetch6 from "cross-fetch";
|
|
1761
1952
|
var GatewayOAuthFlow = class extends BaseOAuthFlow {
|
|
1762
1953
|
async authenticate() {
|
|
1763
1954
|
throw new Error("Interactive authentication is not supported in AuthGatewayClient.");
|
|
@@ -1921,7 +2112,7 @@ var AuthGatewayClient = class {
|
|
|
1921
2112
|
return "jwt";
|
|
1922
2113
|
}
|
|
1923
2114
|
async requestJson(path, options) {
|
|
1924
|
-
const response = await
|
|
2115
|
+
const response = await fetch6(`${this.authBaseUrl}${path.startsWith("/") ? path : `/${path}`}`, options);
|
|
1925
2116
|
const text = await response.text();
|
|
1926
2117
|
let data = null;
|
|
1927
2118
|
if (text) {
|
|
@@ -1942,12 +2133,14 @@ var AuthGatewayClient = class {
|
|
|
1942
2133
|
}
|
|
1943
2134
|
};
|
|
1944
2135
|
export {
|
|
2136
|
+
APIKeyFlow,
|
|
1945
2137
|
ApiKeyStorage,
|
|
1946
2138
|
ApiKeyStorageWeb,
|
|
1947
2139
|
AuthGatewayClient,
|
|
1948
2140
|
BaseOAuthFlow,
|
|
1949
2141
|
DesktopOAuthFlow,
|
|
1950
2142
|
MCPClient,
|
|
2143
|
+
MagicLinkFlow,
|
|
1951
2144
|
TerminalOAuthFlow,
|
|
1952
2145
|
TokenStorage,
|
|
1953
2146
|
TokenStorageWeb
|