@lanonasis/oauth-client 1.2.7 → 2.0.0
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/README.md +19 -0
- package/dist/browser.cjs +1 -1
- package/dist/browser.mjs +1 -1
- package/dist/constants-BOZ6jJU4.d.cts +110 -0
- package/dist/constants-BOZ6jJU4.d.ts +110 -0
- package/dist/index.cjs +272 -63
- package/dist/index.d.cts +160 -1
- package/dist/index.d.ts +160 -1
- package/dist/index.mjs +272 -63
- package/dist/react/index.cjs +261 -0
- package/dist/react/index.d.cts +95 -0
- package/dist/react/index.d.ts +95 -0
- package/dist/react/index.mjs +238 -0
- package/dist/server/index.cjs +169 -0
- package/dist/server/index.d.cts +184 -0
- package/dist/server/index.d.ts +184 -0
- package/dist/server/index.mjs +146 -0
- package/package.json +19 -3
package/README.md
CHANGED
|
@@ -126,5 +126,24 @@ const hashed = await apiKeys.getApiKey(); // returns sha256 hex digest
|
|
|
126
126
|
- `README.md`
|
|
127
127
|
- `LICENSE`
|
|
128
128
|
|
|
129
|
+
## Auth-Gateway Compatibility
|
|
130
|
+
|
|
131
|
+
This SDK implements client-side equivalents of the auth-gateway's authentication methods:
|
|
132
|
+
|
|
133
|
+
| Auth-Gateway Endpoint | SDK Class | Use Case |
|
|
134
|
+
|-----------------------|-----------|----------|
|
|
135
|
+
| `POST /oauth/device` | `TerminalOAuthFlow` | CLI/terminal apps |
|
|
136
|
+
| `GET /oauth/authorize` | `DesktopOAuthFlow` | Desktop apps (Electron) |
|
|
137
|
+
| `POST /v1/auth/otp/*` | `MagicLinkFlow` | Mobile/passwordless |
|
|
138
|
+
| `X-API-Key` header | `APIKeyFlow` | Server-to-server |
|
|
139
|
+
| `MCPClient` | Combined | Auto-detects best method |
|
|
140
|
+
|
|
141
|
+
### Methods NOT Exposed (Security)
|
|
142
|
+
- Email/Password: Server-rendered form only
|
|
143
|
+
- Admin Bypass: Internal emergency access
|
|
144
|
+
- Supabase OAuth 2.1: Internal Supabase integration
|
|
145
|
+
|
|
146
|
+
See [auth-gateway/AUTHENTICATION-METHODS.md](../../apps/onasis-core/services/auth-gateway/AUTHENTICATION-METHODS.md) for complete server-side documentation.
|
|
147
|
+
|
|
129
148
|
## License
|
|
130
149
|
MIT © Lan Onasis
|
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
|
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React-specific types for SSO authentication
|
|
3
|
+
* @module @lanonasis/oauth-client/react
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* SSO User information from the lanonasis_user cookie
|
|
7
|
+
*/
|
|
8
|
+
interface SSOUser {
|
|
9
|
+
id: string;
|
|
10
|
+
email: string;
|
|
11
|
+
role: string;
|
|
12
|
+
name?: string;
|
|
13
|
+
avatar_url?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* SSO authentication state
|
|
17
|
+
*/
|
|
18
|
+
interface SSOState {
|
|
19
|
+
/** Whether the user is authenticated */
|
|
20
|
+
isAuthenticated: boolean;
|
|
21
|
+
/** Whether the auth state is being determined */
|
|
22
|
+
isLoading: boolean;
|
|
23
|
+
/** User information if authenticated */
|
|
24
|
+
user: SSOUser | null;
|
|
25
|
+
/** Error message if auth check failed */
|
|
26
|
+
error: string | null;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Configuration for the SSO hook
|
|
30
|
+
*/
|
|
31
|
+
interface SSOConfig {
|
|
32
|
+
/** Auth gateway URL (default: https://auth.lanonasis.com) */
|
|
33
|
+
authGatewayUrl?: string;
|
|
34
|
+
/** Cookie domain (default: .lanonasis.com) */
|
|
35
|
+
cookieDomain?: string;
|
|
36
|
+
/** Callback when auth state changes */
|
|
37
|
+
onAuthChange?: (state: SSOState) => void;
|
|
38
|
+
/** Polling interval in ms for cookie changes (default: 30000) */
|
|
39
|
+
pollInterval?: number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Configuration for Supabase-to-cookie sync
|
|
43
|
+
*/
|
|
44
|
+
interface SSOSyncConfig {
|
|
45
|
+
/** Auth gateway URL (default: https://auth.lanonasis.com) */
|
|
46
|
+
authGatewayUrl?: string;
|
|
47
|
+
/** Project scope for multi-tenant auth */
|
|
48
|
+
projectScope?: string;
|
|
49
|
+
/** Callback when sync completes */
|
|
50
|
+
onSyncComplete?: (success: boolean) => void;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Supabase session interface (minimal for what we need)
|
|
54
|
+
*/
|
|
55
|
+
interface SupabaseSession {
|
|
56
|
+
access_token: string;
|
|
57
|
+
refresh_token?: string;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Return type for useSSO hook
|
|
61
|
+
*/
|
|
62
|
+
interface UseSSOReturn extends SSOState {
|
|
63
|
+
/** Manually refresh auth state */
|
|
64
|
+
refresh: () => void;
|
|
65
|
+
/** Logout and redirect to auth gateway */
|
|
66
|
+
logout: (returnTo?: string) => void;
|
|
67
|
+
/** Get login URL with optional return URL */
|
|
68
|
+
getLoginUrl: (returnTo?: string) => string;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Return type for useSSOSync hook
|
|
72
|
+
*/
|
|
73
|
+
interface UseSSOSyncReturn {
|
|
74
|
+
/** Manually trigger sync */
|
|
75
|
+
sync: () => Promise<boolean>;
|
|
76
|
+
/** Sync with gateway directly */
|
|
77
|
+
syncWithGateway: (session: SupabaseSession) => Promise<boolean>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Cookie constants and utilities shared between browser and server
|
|
82
|
+
* @module @lanonasis/oauth-client/cookies
|
|
83
|
+
*/
|
|
84
|
+
/**
|
|
85
|
+
* Cookie names used by Lan Onasis auth system
|
|
86
|
+
*/
|
|
87
|
+
declare const COOKIE_NAMES: {
|
|
88
|
+
/** HttpOnly JWT session token */
|
|
89
|
+
readonly SESSION: "lanonasis_session";
|
|
90
|
+
/** Readable user metadata (JSON) */
|
|
91
|
+
readonly USER: "lanonasis_user";
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Default cookie domain for cross-subdomain SSO
|
|
95
|
+
*/
|
|
96
|
+
declare const DEFAULT_COOKIE_DOMAIN = ".lanonasis.com";
|
|
97
|
+
/**
|
|
98
|
+
* Default auth gateway URL
|
|
99
|
+
*/
|
|
100
|
+
declare const DEFAULT_AUTH_GATEWAY = "https://auth.lanonasis.com";
|
|
101
|
+
/**
|
|
102
|
+
* Default polling interval for cookie changes (30 seconds)
|
|
103
|
+
*/
|
|
104
|
+
declare const DEFAULT_POLL_INTERVAL = 30000;
|
|
105
|
+
/**
|
|
106
|
+
* Default project scope for multi-tenant auth
|
|
107
|
+
*/
|
|
108
|
+
declare const DEFAULT_PROJECT_SCOPE = "lanonasis-maas";
|
|
109
|
+
|
|
110
|
+
export { COOKIE_NAMES as C, DEFAULT_AUTH_GATEWAY as D, type SSOConfig as S, type UseSSOReturn as U, type SupabaseSession as a, type SSOSyncConfig as b, type UseSSOSyncReturn as c, type SSOUser as d, type SSOState as e, DEFAULT_COOKIE_DOMAIN as f, DEFAULT_POLL_INTERVAL as g, DEFAULT_PROJECT_SCOPE as h };
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React-specific types for SSO authentication
|
|
3
|
+
* @module @lanonasis/oauth-client/react
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* SSO User information from the lanonasis_user cookie
|
|
7
|
+
*/
|
|
8
|
+
interface SSOUser {
|
|
9
|
+
id: string;
|
|
10
|
+
email: string;
|
|
11
|
+
role: string;
|
|
12
|
+
name?: string;
|
|
13
|
+
avatar_url?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* SSO authentication state
|
|
17
|
+
*/
|
|
18
|
+
interface SSOState {
|
|
19
|
+
/** Whether the user is authenticated */
|
|
20
|
+
isAuthenticated: boolean;
|
|
21
|
+
/** Whether the auth state is being determined */
|
|
22
|
+
isLoading: boolean;
|
|
23
|
+
/** User information if authenticated */
|
|
24
|
+
user: SSOUser | null;
|
|
25
|
+
/** Error message if auth check failed */
|
|
26
|
+
error: string | null;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Configuration for the SSO hook
|
|
30
|
+
*/
|
|
31
|
+
interface SSOConfig {
|
|
32
|
+
/** Auth gateway URL (default: https://auth.lanonasis.com) */
|
|
33
|
+
authGatewayUrl?: string;
|
|
34
|
+
/** Cookie domain (default: .lanonasis.com) */
|
|
35
|
+
cookieDomain?: string;
|
|
36
|
+
/** Callback when auth state changes */
|
|
37
|
+
onAuthChange?: (state: SSOState) => void;
|
|
38
|
+
/** Polling interval in ms for cookie changes (default: 30000) */
|
|
39
|
+
pollInterval?: number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Configuration for Supabase-to-cookie sync
|
|
43
|
+
*/
|
|
44
|
+
interface SSOSyncConfig {
|
|
45
|
+
/** Auth gateway URL (default: https://auth.lanonasis.com) */
|
|
46
|
+
authGatewayUrl?: string;
|
|
47
|
+
/** Project scope for multi-tenant auth */
|
|
48
|
+
projectScope?: string;
|
|
49
|
+
/** Callback when sync completes */
|
|
50
|
+
onSyncComplete?: (success: boolean) => void;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Supabase session interface (minimal for what we need)
|
|
54
|
+
*/
|
|
55
|
+
interface SupabaseSession {
|
|
56
|
+
access_token: string;
|
|
57
|
+
refresh_token?: string;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Return type for useSSO hook
|
|
61
|
+
*/
|
|
62
|
+
interface UseSSOReturn extends SSOState {
|
|
63
|
+
/** Manually refresh auth state */
|
|
64
|
+
refresh: () => void;
|
|
65
|
+
/** Logout and redirect to auth gateway */
|
|
66
|
+
logout: (returnTo?: string) => void;
|
|
67
|
+
/** Get login URL with optional return URL */
|
|
68
|
+
getLoginUrl: (returnTo?: string) => string;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Return type for useSSOSync hook
|
|
72
|
+
*/
|
|
73
|
+
interface UseSSOSyncReturn {
|
|
74
|
+
/** Manually trigger sync */
|
|
75
|
+
sync: () => Promise<boolean>;
|
|
76
|
+
/** Sync with gateway directly */
|
|
77
|
+
syncWithGateway: (session: SupabaseSession) => Promise<boolean>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Cookie constants and utilities shared between browser and server
|
|
82
|
+
* @module @lanonasis/oauth-client/cookies
|
|
83
|
+
*/
|
|
84
|
+
/**
|
|
85
|
+
* Cookie names used by Lan Onasis auth system
|
|
86
|
+
*/
|
|
87
|
+
declare const COOKIE_NAMES: {
|
|
88
|
+
/** HttpOnly JWT session token */
|
|
89
|
+
readonly SESSION: "lanonasis_session";
|
|
90
|
+
/** Readable user metadata (JSON) */
|
|
91
|
+
readonly USER: "lanonasis_user";
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Default cookie domain for cross-subdomain SSO
|
|
95
|
+
*/
|
|
96
|
+
declare const DEFAULT_COOKIE_DOMAIN = ".lanonasis.com";
|
|
97
|
+
/**
|
|
98
|
+
* Default auth gateway URL
|
|
99
|
+
*/
|
|
100
|
+
declare const DEFAULT_AUTH_GATEWAY = "https://auth.lanonasis.com";
|
|
101
|
+
/**
|
|
102
|
+
* Default polling interval for cookie changes (30 seconds)
|
|
103
|
+
*/
|
|
104
|
+
declare const DEFAULT_POLL_INTERVAL = 30000;
|
|
105
|
+
/**
|
|
106
|
+
* Default project scope for multi-tenant auth
|
|
107
|
+
*/
|
|
108
|
+
declare const DEFAULT_PROJECT_SCOPE = "lanonasis-maas";
|
|
109
|
+
|
|
110
|
+
export { COOKIE_NAMES as C, DEFAULT_AUTH_GATEWAY as D, type SSOConfig as S, type UseSSOReturn as U, type SupabaseSession as a, type SSOSyncConfig as b, type UseSSOSyncReturn as c, type SSOUser as d, type SSOState as e, DEFAULT_COOKIE_DOMAIN as f, DEFAULT_POLL_INTERVAL as g, DEFAULT_PROJECT_SCOPE as h };
|
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,272 @@ 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
|
+
if (typeof email !== "string" || !email.trim()) {
|
|
402
|
+
throw new Error("Invalid email: a non-empty email address is required to request an OTP.");
|
|
403
|
+
}
|
|
404
|
+
const normalizedEmail = email.trim().toLowerCase();
|
|
405
|
+
const response = await (0, import_cross_fetch3.default)(`${this.authBaseUrl}/v1/auth/otp/send`, {
|
|
406
|
+
method: "POST",
|
|
407
|
+
headers: { "Content-Type": "application/json" },
|
|
408
|
+
body: JSON.stringify({
|
|
409
|
+
email: normalizedEmail,
|
|
410
|
+
type: "email",
|
|
411
|
+
// Explicitly request 6-digit code, not magic link
|
|
412
|
+
platform: this.platform,
|
|
413
|
+
project_scope: this.projectScope
|
|
414
|
+
})
|
|
415
|
+
});
|
|
416
|
+
const data = await response.json();
|
|
417
|
+
if (!response.ok) {
|
|
418
|
+
throw new Error(data.message || data.error || "Failed to send OTP");
|
|
419
|
+
}
|
|
420
|
+
return data;
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Verify the OTP code entered by the user and get tokens
|
|
424
|
+
*
|
|
425
|
+
* @param email - User's email address (must match the one used in requestOTP)
|
|
426
|
+
* @param code - 6-digit OTP code from email
|
|
427
|
+
* @returns Token response with access_token, refresh_token, etc.
|
|
428
|
+
*/
|
|
429
|
+
async verifyOTP(email, code) {
|
|
430
|
+
const response = await (0, import_cross_fetch3.default)(`${this.authBaseUrl}/v1/auth/otp/verify`, {
|
|
431
|
+
method: "POST",
|
|
432
|
+
headers: { "Content-Type": "application/json" },
|
|
433
|
+
body: JSON.stringify({
|
|
434
|
+
email: email.trim().toLowerCase(),
|
|
435
|
+
token: code.trim(),
|
|
436
|
+
type: "email",
|
|
437
|
+
platform: this.platform,
|
|
438
|
+
project_scope: this.projectScope
|
|
439
|
+
})
|
|
440
|
+
});
|
|
441
|
+
const data = await response.json();
|
|
442
|
+
if (!response.ok) {
|
|
443
|
+
throw new Error(data.message || data.error || "Invalid or expired OTP code");
|
|
444
|
+
}
|
|
445
|
+
return data;
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Resend OTP code (rate limited)
|
|
449
|
+
*
|
|
450
|
+
* @param email - User's email address
|
|
451
|
+
* @returns Response with success status
|
|
452
|
+
*/
|
|
453
|
+
async resendOTP(email) {
|
|
454
|
+
const response = await (0, import_cross_fetch3.default)(`${this.authBaseUrl}/v1/auth/otp/resend`, {
|
|
455
|
+
method: "POST",
|
|
456
|
+
headers: { "Content-Type": "application/json" },
|
|
457
|
+
body: JSON.stringify({
|
|
458
|
+
email: email.trim().toLowerCase(),
|
|
459
|
+
type: "email",
|
|
460
|
+
platform: this.platform
|
|
461
|
+
})
|
|
462
|
+
});
|
|
463
|
+
const data = await response.json();
|
|
464
|
+
if (!response.ok) {
|
|
465
|
+
if (response.status === 429) {
|
|
466
|
+
throw new Error("Rate limited. Please wait before requesting another code.");
|
|
467
|
+
}
|
|
468
|
+
throw new Error(data.message || data.error || "Failed to resend OTP");
|
|
469
|
+
}
|
|
470
|
+
return data;
|
|
471
|
+
}
|
|
472
|
+
// ============================================================================
|
|
473
|
+
// Magic Link Flow (for web, desktop - user clicks link in email)
|
|
474
|
+
// ============================================================================
|
|
475
|
+
/**
|
|
476
|
+
* Request a magic link to be sent via email
|
|
477
|
+
* User will click the link which redirects to your callback URL
|
|
478
|
+
*
|
|
479
|
+
* @param email - User's email address
|
|
480
|
+
* @param redirectUri - URL to redirect to after clicking the magic link
|
|
481
|
+
* @returns Response with success status
|
|
482
|
+
*/
|
|
483
|
+
async requestMagicLink(email, redirectUri) {
|
|
484
|
+
const response = await (0, import_cross_fetch3.default)(`${this.authBaseUrl}/v1/auth/otp/send`, {
|
|
485
|
+
method: "POST",
|
|
486
|
+
headers: { "Content-Type": "application/json" },
|
|
487
|
+
body: JSON.stringify({
|
|
488
|
+
email: email.trim().toLowerCase(),
|
|
489
|
+
type: "magiclink",
|
|
490
|
+
redirect_uri: redirectUri,
|
|
491
|
+
platform: this.platform,
|
|
492
|
+
project_scope: this.projectScope
|
|
493
|
+
})
|
|
494
|
+
});
|
|
495
|
+
const data = await response.json();
|
|
496
|
+
if (!response.ok) {
|
|
497
|
+
throw new Error(data.message || data.error || "Failed to send magic link");
|
|
498
|
+
}
|
|
499
|
+
return data;
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Alternative: Use the /v1/auth/magic-link endpoint (web-optimized)
|
|
503
|
+
* This endpoint provides better redirect handling for web apps
|
|
504
|
+
*
|
|
505
|
+
* @param email - User's email address
|
|
506
|
+
* @param redirectUri - URL to redirect to after authentication
|
|
507
|
+
* @param createUser - Whether to create a new user if email doesn't exist
|
|
508
|
+
*/
|
|
509
|
+
async requestMagicLinkWeb(email, redirectUri, createUser = true) {
|
|
510
|
+
const response = await (0, import_cross_fetch3.default)(`${this.authBaseUrl}/v1/auth/magic-link`, {
|
|
511
|
+
method: "POST",
|
|
512
|
+
headers: { "Content-Type": "application/json" },
|
|
513
|
+
body: JSON.stringify({
|
|
514
|
+
email: email.trim().toLowerCase(),
|
|
515
|
+
redirect_uri: redirectUri,
|
|
516
|
+
return_to: redirectUri,
|
|
517
|
+
// Alias for compatibility
|
|
518
|
+
project_scope: this.projectScope,
|
|
519
|
+
platform: "web",
|
|
520
|
+
create_user: createUser
|
|
521
|
+
})
|
|
522
|
+
});
|
|
523
|
+
const data = await response.json();
|
|
524
|
+
if (!response.ok) {
|
|
525
|
+
throw new Error(data.message || data.error || "Failed to send magic link");
|
|
526
|
+
}
|
|
527
|
+
return data;
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Exchange magic link token for auth-gateway tokens
|
|
531
|
+
* Called after user clicks the magic link and is redirected to your callback
|
|
532
|
+
*
|
|
533
|
+
* @param supabaseAccessToken - Access token from Supabase (from URL hash/query)
|
|
534
|
+
* @param state - State parameter from the callback URL
|
|
535
|
+
* @returns Token response with redirect URL
|
|
536
|
+
*/
|
|
537
|
+
async exchangeMagicLinkToken(supabaseAccessToken, state) {
|
|
538
|
+
const response = await (0, import_cross_fetch3.default)(`${this.authBaseUrl}/v1/auth/magic-link/exchange`, {
|
|
539
|
+
method: "POST",
|
|
540
|
+
headers: {
|
|
541
|
+
"Content-Type": "application/json",
|
|
542
|
+
"Authorization": `Bearer ${supabaseAccessToken}`
|
|
543
|
+
},
|
|
544
|
+
body: JSON.stringify({ state })
|
|
545
|
+
});
|
|
546
|
+
const data = await response.json();
|
|
547
|
+
if (!response.ok) {
|
|
548
|
+
throw new Error(data.message || data.error || "Magic link exchange failed");
|
|
549
|
+
}
|
|
550
|
+
return data;
|
|
551
|
+
}
|
|
552
|
+
// ============================================================================
|
|
553
|
+
// Utility Methods
|
|
554
|
+
// ============================================================================
|
|
555
|
+
/**
|
|
556
|
+
* Check if an email is valid format
|
|
557
|
+
*/
|
|
558
|
+
static isValidEmail(email) {
|
|
559
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
560
|
+
return emailRegex.test(email.trim());
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Check if an OTP code is valid format (6 digits).
|
|
564
|
+
*
|
|
565
|
+
* This method:
|
|
566
|
+
* - Trims leading and trailing whitespace from the input before validating.
|
|
567
|
+
* - Requires exactly 6 numeric digits (0–9); any non-numeric characters or
|
|
568
|
+
* incorrect length will cause it to return `false`.
|
|
569
|
+
*
|
|
570
|
+
* Note: This method expects a string value. Passing `null`, `undefined`, or
|
|
571
|
+
* other non-string values will result in a runtime error when `.trim()` is
|
|
572
|
+
* called, rather than returning `false`.
|
|
573
|
+
*
|
|
574
|
+
* @param code - The OTP code to validate.
|
|
575
|
+
* @returns `true` if the trimmed code consists of exactly 6 digits, otherwise `false`.
|
|
576
|
+
*/
|
|
577
|
+
static isValidOTPCode(code) {
|
|
578
|
+
return /^\d{6}$/.test(code.trim());
|
|
579
|
+
}
|
|
580
|
+
};
|
|
581
|
+
|
|
582
|
+
// src/flows/apikey-flow.ts
|
|
583
|
+
var import_cross_fetch4 = __toESM(require("cross-fetch"), 1);
|
|
584
|
+
var APIKeyFlow = class extends BaseOAuthFlow {
|
|
585
|
+
constructor(apiKey, authBaseUrl = "https://mcp.lanonasis.com") {
|
|
586
|
+
super({
|
|
587
|
+
clientId: "api-key-client",
|
|
588
|
+
authBaseUrl
|
|
589
|
+
});
|
|
590
|
+
this.apiKey = apiKey;
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* "Authenticate" by returning the API key as a virtual token
|
|
594
|
+
* The API key will be used directly in request headers
|
|
595
|
+
*/
|
|
596
|
+
async authenticate() {
|
|
597
|
+
if (!this.apiKey || !this.apiKey.startsWith("lano_") && !this.apiKey.startsWith("vx_")) {
|
|
598
|
+
throw new Error(
|
|
599
|
+
'Invalid API key format. Must start with "lano_" or "vx_". Please regenerate your API key from the dashboard.'
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
if (this.apiKey.startsWith("vx_")) {
|
|
603
|
+
console.warn(
|
|
604
|
+
'\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.'
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
return {
|
|
608
|
+
access_token: this.apiKey,
|
|
609
|
+
token_type: "api-key",
|
|
610
|
+
expires_in: 0,
|
|
611
|
+
// API keys don't expire
|
|
612
|
+
issued_at: Date.now()
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* API keys don't need refresh
|
|
617
|
+
*/
|
|
618
|
+
async refreshToken(refreshToken) {
|
|
619
|
+
throw new Error("API keys do not support token refresh");
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Optional: Validate API key by making a test request
|
|
623
|
+
*/
|
|
624
|
+
async validateAPIKey() {
|
|
625
|
+
try {
|
|
626
|
+
const response = await (0, import_cross_fetch4.default)(`${this.authBaseUrl}/api/v1/health`, {
|
|
627
|
+
headers: {
|
|
628
|
+
"x-api-key": this.apiKey
|
|
629
|
+
}
|
|
630
|
+
});
|
|
631
|
+
return response.ok;
|
|
632
|
+
} catch (error) {
|
|
633
|
+
console.error("API key validation failed:", error);
|
|
634
|
+
return false;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
};
|
|
638
|
+
|
|
371
639
|
// src/storage/token-storage.ts
|
|
372
640
|
var _fs = null;
|
|
373
641
|
var _path = null;
|
|
@@ -1405,66 +1673,7 @@ var ApiKeyStorageWeb = class {
|
|
|
1405
1673
|
};
|
|
1406
1674
|
|
|
1407
1675
|
// 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
|
|
1676
|
+
var import_cross_fetch5 = __toESM(require("cross-fetch"), 1);
|
|
1468
1677
|
var MCPClient = class {
|
|
1469
1678
|
constructor(config = {}) {
|
|
1470
1679
|
// ← NEW: Track auth mode
|
|
@@ -1693,7 +1902,7 @@ var MCPClient = class {
|
|
|
1693
1902
|
} else {
|
|
1694
1903
|
headers["Authorization"] = `Bearer ${this.accessToken}`;
|
|
1695
1904
|
}
|
|
1696
|
-
const response = await (0,
|
|
1905
|
+
const response = await (0, import_cross_fetch5.default)(`${this.config.mcpEndpoint}/api`, {
|
|
1697
1906
|
method: "POST",
|
|
1698
1907
|
headers,
|
|
1699
1908
|
body: JSON.stringify({
|
|
@@ -1794,7 +2003,7 @@ var MCPClient = class {
|
|
|
1794
2003
|
};
|
|
1795
2004
|
|
|
1796
2005
|
// src/client/auth-gateway-client.ts
|
|
1797
|
-
var
|
|
2006
|
+
var import_cross_fetch6 = __toESM(require("cross-fetch"), 1);
|
|
1798
2007
|
var GatewayOAuthFlow = class extends BaseOAuthFlow {
|
|
1799
2008
|
async authenticate() {
|
|
1800
2009
|
throw new Error("Interactive authentication is not supported in AuthGatewayClient.");
|
|
@@ -1958,7 +2167,7 @@ var AuthGatewayClient = class {
|
|
|
1958
2167
|
return "jwt";
|
|
1959
2168
|
}
|
|
1960
2169
|
async requestJson(path, options) {
|
|
1961
|
-
const response = await (0,
|
|
2170
|
+
const response = await (0, import_cross_fetch6.default)(`${this.authBaseUrl}${path.startsWith("/") ? path : `/${path}`}`, options);
|
|
1962
2171
|
const text = await response.text();
|
|
1963
2172
|
let data = null;
|
|
1964
2173
|
if (text) {
|