@lanonasis/oauth-client 1.2.6 → 1.2.7

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.
@@ -29,6 +29,42 @@ interface PKCEChallenge {
29
29
  codeVerifier: string;
30
30
  codeChallenge: string;
31
31
  }
32
+ type AuthTokenType = 'api_key' | 'jwt' | 'oauth' | 'cli';
33
+ interface AuthValidationResult {
34
+ valid: boolean;
35
+ type?: AuthTokenType;
36
+ userId?: string;
37
+ email?: string;
38
+ role?: string;
39
+ projectScope?: string;
40
+ permissions?: string[];
41
+ scope?: string | string[];
42
+ expiresAt?: string | null;
43
+ error?: string;
44
+ raw?: unknown;
45
+ }
46
+ interface AuthGatewayClientConfig {
47
+ authBaseUrl?: string;
48
+ clientId?: string;
49
+ projectScope?: string;
50
+ }
51
+ interface TokenExchangeOptions {
52
+ projectScope?: string;
53
+ platform?: string;
54
+ }
55
+ interface TokenExchangeResponse {
56
+ access_token: string;
57
+ refresh_token: string;
58
+ expires_in: number;
59
+ token_type?: string;
60
+ user?: {
61
+ id: string;
62
+ email?: string;
63
+ role?: string;
64
+ project_scope?: string;
65
+ };
66
+ [key: string]: unknown;
67
+ }
32
68
 
33
69
  declare abstract class BaseOAuthFlow {
34
70
  protected readonly clientId: string;
@@ -91,6 +127,22 @@ declare class TokenStorage implements TokenStorageAdapter {
91
127
  private getWebEncryptionKey;
92
128
  }
93
129
 
130
+ declare class AuthGatewayClient {
131
+ private authBaseUrl;
132
+ private projectScope;
133
+ private flow;
134
+ constructor(config?: AuthGatewayClientConfig);
135
+ exchangeSupabaseToken(supabaseToken: string, options?: TokenExchangeOptions): Promise<TokenExchangeResponse>;
136
+ refreshToken(refreshToken: string): Promise<TokenResponse>;
137
+ revokeToken(token: string, tokenType?: 'access_token' | 'refresh_token'): Promise<void>;
138
+ validateToken(token: string): Promise<AuthValidationResult>;
139
+ verifyApiKey(apiKey: string): Promise<AuthValidationResult>;
140
+ verifyToken(token: string): Promise<AuthValidationResult>;
141
+ introspectToken(token: string): Promise<AuthValidationResult>;
142
+ private normalizeTokenType;
143
+ private requestJson;
144
+ }
145
+
94
146
  /**
95
147
  * Browser-only token storage that avoids Node/Electron dependencies.
96
148
  * Tokens are encrypted with Web Crypto and stored in localStorage.
@@ -205,4 +257,4 @@ declare class ApiKeyStorageWeb {
205
257
  private base64Decode;
206
258
  }
207
259
 
208
- export { ApiKeyStorageWeb as A, BaseOAuthFlow as B, DesktopOAuthFlow as D, type GrantType as G, type OAuthConfig as O, type PKCEChallenge as P, type TokenStorageAdapter as T, TokenStorageWeb as a, type TokenResponse as b, type DeviceCodeResponse as c, type AuthError as d, TokenStorage as e, type ApiKeyData as f, ApiKeyStorage as g };
260
+ export { AuthGatewayClient as A, BaseOAuthFlow as B, DesktopOAuthFlow as D, type GrantType as G, type OAuthConfig as O, type PKCEChallenge as P, type TokenStorageAdapter as T, TokenStorageWeb as a, ApiKeyStorageWeb as b, type TokenResponse as c, type DeviceCodeResponse as d, type AuthError as e, type AuthTokenType as f, type AuthValidationResult as g, type AuthGatewayClientConfig as h, type TokenExchangeOptions as i, type TokenExchangeResponse as j, TokenStorage as k, type ApiKeyData as l, ApiKeyStorage as m };
@@ -29,6 +29,42 @@ interface PKCEChallenge {
29
29
  codeVerifier: string;
30
30
  codeChallenge: string;
31
31
  }
32
+ type AuthTokenType = 'api_key' | 'jwt' | 'oauth' | 'cli';
33
+ interface AuthValidationResult {
34
+ valid: boolean;
35
+ type?: AuthTokenType;
36
+ userId?: string;
37
+ email?: string;
38
+ role?: string;
39
+ projectScope?: string;
40
+ permissions?: string[];
41
+ scope?: string | string[];
42
+ expiresAt?: string | null;
43
+ error?: string;
44
+ raw?: unknown;
45
+ }
46
+ interface AuthGatewayClientConfig {
47
+ authBaseUrl?: string;
48
+ clientId?: string;
49
+ projectScope?: string;
50
+ }
51
+ interface TokenExchangeOptions {
52
+ projectScope?: string;
53
+ platform?: string;
54
+ }
55
+ interface TokenExchangeResponse {
56
+ access_token: string;
57
+ refresh_token: string;
58
+ expires_in: number;
59
+ token_type?: string;
60
+ user?: {
61
+ id: string;
62
+ email?: string;
63
+ role?: string;
64
+ project_scope?: string;
65
+ };
66
+ [key: string]: unknown;
67
+ }
32
68
 
33
69
  declare abstract class BaseOAuthFlow {
34
70
  protected readonly clientId: string;
@@ -91,6 +127,22 @@ declare class TokenStorage implements TokenStorageAdapter {
91
127
  private getWebEncryptionKey;
92
128
  }
93
129
 
130
+ declare class AuthGatewayClient {
131
+ private authBaseUrl;
132
+ private projectScope;
133
+ private flow;
134
+ constructor(config?: AuthGatewayClientConfig);
135
+ exchangeSupabaseToken(supabaseToken: string, options?: TokenExchangeOptions): Promise<TokenExchangeResponse>;
136
+ refreshToken(refreshToken: string): Promise<TokenResponse>;
137
+ revokeToken(token: string, tokenType?: 'access_token' | 'refresh_token'): Promise<void>;
138
+ validateToken(token: string): Promise<AuthValidationResult>;
139
+ verifyApiKey(apiKey: string): Promise<AuthValidationResult>;
140
+ verifyToken(token: string): Promise<AuthValidationResult>;
141
+ introspectToken(token: string): Promise<AuthValidationResult>;
142
+ private normalizeTokenType;
143
+ private requestJson;
144
+ }
145
+
94
146
  /**
95
147
  * Browser-only token storage that avoids Node/Electron dependencies.
96
148
  * Tokens are encrypted with Web Crypto and stored in localStorage.
@@ -205,4 +257,4 @@ declare class ApiKeyStorageWeb {
205
257
  private base64Decode;
206
258
  }
207
259
 
208
- export { ApiKeyStorageWeb as A, BaseOAuthFlow as B, DesktopOAuthFlow as D, type GrantType as G, type OAuthConfig as O, type PKCEChallenge as P, type TokenStorageAdapter as T, TokenStorageWeb as a, type TokenResponse as b, type DeviceCodeResponse as c, type AuthError as d, TokenStorage as e, type ApiKeyData as f, ApiKeyStorage as g };
260
+ export { AuthGatewayClient as A, BaseOAuthFlow as B, DesktopOAuthFlow as D, type GrantType as G, type OAuthConfig as O, type PKCEChallenge as P, type TokenStorageAdapter as T, TokenStorageWeb as a, ApiKeyStorageWeb as b, type TokenResponse as c, type DeviceCodeResponse as d, type AuthError as e, type AuthTokenType as f, type AuthValidationResult as g, type AuthGatewayClientConfig as h, type TokenExchangeOptions as i, type TokenExchangeResponse as j, TokenStorage as k, type ApiKeyData as l, ApiKeyStorage as m };
package/dist/browser.cjs CHANGED
@@ -31,6 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var browser_exports = {};
32
32
  __export(browser_exports, {
33
33
  ApiKeyStorageWeb: () => ApiKeyStorageWeb,
34
+ AuthGatewayClient: () => AuthGatewayClient,
34
35
  BaseOAuthFlow: () => BaseOAuthFlow,
35
36
  DesktopOAuthFlow: () => DesktopOAuthFlow,
36
37
  MCPClient: () => MCPClient,
@@ -662,6 +663,192 @@ var MCPClient = class {
662
663
  }
663
664
  };
664
665
 
666
+ // src/client/auth-gateway-client.ts
667
+ var import_cross_fetch4 = __toESM(require("cross-fetch"), 1);
668
+ var GatewayOAuthFlow = class extends BaseOAuthFlow {
669
+ async authenticate() {
670
+ throw new Error("Interactive authentication is not supported in AuthGatewayClient.");
671
+ }
672
+ };
673
+ var DEFAULT_AUTH_BASE_URL = "https://auth.lanonasis.com";
674
+ var DEFAULT_CLIENT_ID = "lanonasis-cli";
675
+ var DEFAULT_PROJECT_SCOPE = "lanonasis-maas";
676
+ var API_KEY_PREFIXES = ["lano_", "pk_", "sk_"];
677
+ var AuthGatewayClient = class {
678
+ constructor(config = {}) {
679
+ const baseUrl = config.authBaseUrl || DEFAULT_AUTH_BASE_URL;
680
+ this.authBaseUrl = baseUrl.replace(/\/+$/, "");
681
+ this.projectScope = config.projectScope || DEFAULT_PROJECT_SCOPE;
682
+ this.flow = new GatewayOAuthFlow({
683
+ clientId: config.clientId || DEFAULT_CLIENT_ID,
684
+ authBaseUrl: this.authBaseUrl
685
+ });
686
+ }
687
+ async exchangeSupabaseToken(supabaseToken, options = {}) {
688
+ const token = typeof supabaseToken === "string" ? supabaseToken.trim() : "";
689
+ if (!token) {
690
+ throw new Error("Supabase access token is required");
691
+ }
692
+ const projectScope = options.projectScope || this.projectScope;
693
+ const platform = options.platform || "web";
694
+ return this.requestJson("/v1/auth/token/exchange", {
695
+ method: "POST",
696
+ headers: {
697
+ Authorization: `Bearer ${token}`,
698
+ "Content-Type": "application/json",
699
+ "X-Project-Scope": projectScope
700
+ },
701
+ body: JSON.stringify({
702
+ project_scope: projectScope,
703
+ platform
704
+ })
705
+ });
706
+ }
707
+ async refreshToken(refreshToken) {
708
+ return this.flow.refreshToken(refreshToken);
709
+ }
710
+ async revokeToken(token, tokenType = "access_token") {
711
+ return this.flow.revokeToken(token, tokenType);
712
+ }
713
+ async validateToken(token) {
714
+ const trimmed = typeof token === "string" ? token.trim() : "";
715
+ if (!trimmed) {
716
+ return { valid: false, error: "Token is required" };
717
+ }
718
+ if (API_KEY_PREFIXES.some((prefix) => trimmed.startsWith(prefix))) {
719
+ return this.verifyApiKey(trimmed);
720
+ }
721
+ if (trimmed.startsWith("cli_") || trimmed.includes(".")) {
722
+ return this.verifyToken(trimmed);
723
+ }
724
+ return this.introspectToken(trimmed);
725
+ }
726
+ async verifyApiKey(apiKey) {
727
+ try {
728
+ const data = await this.requestJson("/v1/auth/verify-api-key", {
729
+ method: "POST",
730
+ headers: {
731
+ "Content-Type": "application/json",
732
+ "X-API-Key": apiKey
733
+ }
734
+ });
735
+ if (!data || data.valid === false) {
736
+ return {
737
+ valid: false,
738
+ type: "api_key",
739
+ error: data && (data.error || data.message) || "Invalid API key",
740
+ raw: data
741
+ };
742
+ }
743
+ return {
744
+ valid: true,
745
+ type: "api_key",
746
+ userId: data.userId,
747
+ projectScope: data.projectScope,
748
+ permissions: data.permissions || [],
749
+ raw: data
750
+ };
751
+ } catch (error) {
752
+ return {
753
+ valid: false,
754
+ type: "api_key",
755
+ error: error?.message || "API key validation failed",
756
+ raw: error?.data
757
+ };
758
+ }
759
+ }
760
+ async verifyToken(token) {
761
+ try {
762
+ const data = await this.requestJson("/v1/auth/verify-token", {
763
+ method: "POST",
764
+ headers: { "Content-Type": "application/json" },
765
+ body: JSON.stringify({ token })
766
+ });
767
+ if (!data || data.valid === false) {
768
+ return {
769
+ valid: false,
770
+ type: data?.type ? this.normalizeTokenType(data.type) : "jwt",
771
+ error: data?.error || "Invalid token",
772
+ raw: data
773
+ };
774
+ }
775
+ return {
776
+ valid: true,
777
+ type: data.type ? this.normalizeTokenType(data.type) : "jwt",
778
+ userId: data.user?.id,
779
+ email: data.user?.email,
780
+ role: data.user?.role,
781
+ expiresAt: data.expires_at || null,
782
+ raw: data
783
+ };
784
+ } catch (error) {
785
+ return {
786
+ valid: false,
787
+ type: "jwt",
788
+ error: error?.message || "Token verification failed",
789
+ raw: error?.data
790
+ };
791
+ }
792
+ }
793
+ async introspectToken(token) {
794
+ try {
795
+ const data = await this.requestJson("/oauth/introspect", {
796
+ method: "POST",
797
+ headers: { "Content-Type": "application/json" },
798
+ body: JSON.stringify({ token })
799
+ });
800
+ if (!data || data.active !== true) {
801
+ return {
802
+ valid: false,
803
+ type: "oauth",
804
+ error: "Token is inactive",
805
+ raw: data
806
+ };
807
+ }
808
+ return {
809
+ valid: true,
810
+ type: "oauth",
811
+ userId: data.user_id || data.sub,
812
+ scope: data.scope,
813
+ expiresAt: data.exp ? new Date(data.exp * 1e3).toISOString() : null,
814
+ raw: data
815
+ };
816
+ } catch (error) {
817
+ return {
818
+ valid: false,
819
+ type: "oauth",
820
+ error: error?.message || "Token introspection failed",
821
+ raw: error?.data
822
+ };
823
+ }
824
+ }
825
+ normalizeTokenType(type) {
826
+ if (type === "cli_token") return "cli";
827
+ if (type === "jwt") return "jwt";
828
+ return "jwt";
829
+ }
830
+ async requestJson(path, options) {
831
+ const response = await (0, import_cross_fetch4.default)(`${this.authBaseUrl}${path.startsWith("/") ? path : `/${path}`}`, options);
832
+ const text = await response.text();
833
+ let data = null;
834
+ if (text) {
835
+ try {
836
+ data = JSON.parse(text);
837
+ } catch (parseError) {
838
+ data = { raw: text };
839
+ }
840
+ }
841
+ if (!response.ok) {
842
+ const message = data?.message || data?.error || `Request failed (${response.status})`;
843
+ const error = new Error(message);
844
+ error.status = response.status;
845
+ error.data = data;
846
+ throw error;
847
+ }
848
+ return data;
849
+ }
850
+ };
851
+
665
852
  // src/storage/api-key-storage-web.ts
666
853
  var ApiKeyStorageWeb = class {
667
854
  constructor() {
@@ -1,5 +1,5 @@
1
- import { O as OAuthConfig, T as TokenStorageAdapter } from './api-key-storage-web-DannE11B.cjs';
2
- export { A as ApiKeyStorageWeb, d as AuthError, B as BaseOAuthFlow, D as DesktopOAuthFlow, c as DeviceCodeResponse, G as GrantType, P as PKCEChallenge, b as TokenResponse, a as TokenStorageWeb } from './api-key-storage-web-DannE11B.cjs';
1
+ import { O as OAuthConfig, T as TokenStorageAdapter } from './api-key-storage-web-J3W8nQi2.cjs';
2
+ export { b as ApiKeyStorageWeb, e as AuthError, A as AuthGatewayClient, h as AuthGatewayClientConfig, f as AuthTokenType, g as AuthValidationResult, B as BaseOAuthFlow, D as DesktopOAuthFlow, d as DeviceCodeResponse, G as GrantType, P as PKCEChallenge, i as TokenExchangeOptions, j as TokenExchangeResponse, c as TokenResponse, a as TokenStorageWeb } from './api-key-storage-web-J3W8nQi2.cjs';
3
3
 
4
4
  interface MCPClientConfig extends Partial<OAuthConfig> {
5
5
  mcpEndpoint?: string;
package/dist/browser.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { O as OAuthConfig, T as TokenStorageAdapter } from './api-key-storage-web-DannE11B.js';
2
- export { A as ApiKeyStorageWeb, d as AuthError, B as BaseOAuthFlow, D as DesktopOAuthFlow, c as DeviceCodeResponse, G as GrantType, P as PKCEChallenge, b as TokenResponse, a as TokenStorageWeb } from './api-key-storage-web-DannE11B.js';
1
+ import { O as OAuthConfig, T as TokenStorageAdapter } from './api-key-storage-web-J3W8nQi2.js';
2
+ export { b as ApiKeyStorageWeb, e as AuthError, A as AuthGatewayClient, h as AuthGatewayClientConfig, f as AuthTokenType, g as AuthValidationResult, B as BaseOAuthFlow, D as DesktopOAuthFlow, d as DeviceCodeResponse, G as GrantType, P as PKCEChallenge, i as TokenExchangeOptions, j as TokenExchangeResponse, c as TokenResponse, a as TokenStorageWeb } from './api-key-storage-web-J3W8nQi2.js';
3
3
 
4
4
  interface MCPClientConfig extends Partial<OAuthConfig> {
5
5
  mcpEndpoint?: string;
package/dist/browser.mjs CHANGED
@@ -629,6 +629,192 @@ var MCPClient = class {
629
629
  }
630
630
  };
631
631
 
632
+ // src/client/auth-gateway-client.ts
633
+ import fetch4 from "cross-fetch";
634
+ var GatewayOAuthFlow = class extends BaseOAuthFlow {
635
+ async authenticate() {
636
+ throw new Error("Interactive authentication is not supported in AuthGatewayClient.");
637
+ }
638
+ };
639
+ var DEFAULT_AUTH_BASE_URL = "https://auth.lanonasis.com";
640
+ var DEFAULT_CLIENT_ID = "lanonasis-cli";
641
+ var DEFAULT_PROJECT_SCOPE = "lanonasis-maas";
642
+ var API_KEY_PREFIXES = ["lano_", "pk_", "sk_"];
643
+ var AuthGatewayClient = class {
644
+ constructor(config = {}) {
645
+ const baseUrl = config.authBaseUrl || DEFAULT_AUTH_BASE_URL;
646
+ this.authBaseUrl = baseUrl.replace(/\/+$/, "");
647
+ this.projectScope = config.projectScope || DEFAULT_PROJECT_SCOPE;
648
+ this.flow = new GatewayOAuthFlow({
649
+ clientId: config.clientId || DEFAULT_CLIENT_ID,
650
+ authBaseUrl: this.authBaseUrl
651
+ });
652
+ }
653
+ async exchangeSupabaseToken(supabaseToken, options = {}) {
654
+ const token = typeof supabaseToken === "string" ? supabaseToken.trim() : "";
655
+ if (!token) {
656
+ throw new Error("Supabase access token is required");
657
+ }
658
+ const projectScope = options.projectScope || this.projectScope;
659
+ const platform = options.platform || "web";
660
+ return this.requestJson("/v1/auth/token/exchange", {
661
+ method: "POST",
662
+ headers: {
663
+ Authorization: `Bearer ${token}`,
664
+ "Content-Type": "application/json",
665
+ "X-Project-Scope": projectScope
666
+ },
667
+ body: JSON.stringify({
668
+ project_scope: projectScope,
669
+ platform
670
+ })
671
+ });
672
+ }
673
+ async refreshToken(refreshToken) {
674
+ return this.flow.refreshToken(refreshToken);
675
+ }
676
+ async revokeToken(token, tokenType = "access_token") {
677
+ return this.flow.revokeToken(token, tokenType);
678
+ }
679
+ async validateToken(token) {
680
+ const trimmed = typeof token === "string" ? token.trim() : "";
681
+ if (!trimmed) {
682
+ return { valid: false, error: "Token is required" };
683
+ }
684
+ if (API_KEY_PREFIXES.some((prefix) => trimmed.startsWith(prefix))) {
685
+ return this.verifyApiKey(trimmed);
686
+ }
687
+ if (trimmed.startsWith("cli_") || trimmed.includes(".")) {
688
+ return this.verifyToken(trimmed);
689
+ }
690
+ return this.introspectToken(trimmed);
691
+ }
692
+ async verifyApiKey(apiKey) {
693
+ try {
694
+ const data = await this.requestJson("/v1/auth/verify-api-key", {
695
+ method: "POST",
696
+ headers: {
697
+ "Content-Type": "application/json",
698
+ "X-API-Key": apiKey
699
+ }
700
+ });
701
+ if (!data || data.valid === false) {
702
+ return {
703
+ valid: false,
704
+ type: "api_key",
705
+ error: data && (data.error || data.message) || "Invalid API key",
706
+ raw: data
707
+ };
708
+ }
709
+ return {
710
+ valid: true,
711
+ type: "api_key",
712
+ userId: data.userId,
713
+ projectScope: data.projectScope,
714
+ permissions: data.permissions || [],
715
+ raw: data
716
+ };
717
+ } catch (error) {
718
+ return {
719
+ valid: false,
720
+ type: "api_key",
721
+ error: error?.message || "API key validation failed",
722
+ raw: error?.data
723
+ };
724
+ }
725
+ }
726
+ async verifyToken(token) {
727
+ try {
728
+ const data = await this.requestJson("/v1/auth/verify-token", {
729
+ method: "POST",
730
+ headers: { "Content-Type": "application/json" },
731
+ body: JSON.stringify({ token })
732
+ });
733
+ if (!data || data.valid === false) {
734
+ return {
735
+ valid: false,
736
+ type: data?.type ? this.normalizeTokenType(data.type) : "jwt",
737
+ error: data?.error || "Invalid token",
738
+ raw: data
739
+ };
740
+ }
741
+ return {
742
+ valid: true,
743
+ type: data.type ? this.normalizeTokenType(data.type) : "jwt",
744
+ userId: data.user?.id,
745
+ email: data.user?.email,
746
+ role: data.user?.role,
747
+ expiresAt: data.expires_at || null,
748
+ raw: data
749
+ };
750
+ } catch (error) {
751
+ return {
752
+ valid: false,
753
+ type: "jwt",
754
+ error: error?.message || "Token verification failed",
755
+ raw: error?.data
756
+ };
757
+ }
758
+ }
759
+ async introspectToken(token) {
760
+ try {
761
+ const data = await this.requestJson("/oauth/introspect", {
762
+ method: "POST",
763
+ headers: { "Content-Type": "application/json" },
764
+ body: JSON.stringify({ token })
765
+ });
766
+ if (!data || data.active !== true) {
767
+ return {
768
+ valid: false,
769
+ type: "oauth",
770
+ error: "Token is inactive",
771
+ raw: data
772
+ };
773
+ }
774
+ return {
775
+ valid: true,
776
+ type: "oauth",
777
+ userId: data.user_id || data.sub,
778
+ scope: data.scope,
779
+ expiresAt: data.exp ? new Date(data.exp * 1e3).toISOString() : null,
780
+ raw: data
781
+ };
782
+ } catch (error) {
783
+ return {
784
+ valid: false,
785
+ type: "oauth",
786
+ error: error?.message || "Token introspection failed",
787
+ raw: error?.data
788
+ };
789
+ }
790
+ }
791
+ normalizeTokenType(type) {
792
+ if (type === "cli_token") return "cli";
793
+ if (type === "jwt") return "jwt";
794
+ return "jwt";
795
+ }
796
+ async requestJson(path, options) {
797
+ const response = await fetch4(`${this.authBaseUrl}${path.startsWith("/") ? path : `/${path}`}`, options);
798
+ const text = await response.text();
799
+ let data = null;
800
+ if (text) {
801
+ try {
802
+ data = JSON.parse(text);
803
+ } catch (parseError) {
804
+ data = { raw: text };
805
+ }
806
+ }
807
+ if (!response.ok) {
808
+ const message = data?.message || data?.error || `Request failed (${response.status})`;
809
+ const error = new Error(message);
810
+ error.status = response.status;
811
+ error.data = data;
812
+ throw error;
813
+ }
814
+ return data;
815
+ }
816
+ };
817
+
632
818
  // src/storage/api-key-storage-web.ts
633
819
  var ApiKeyStorageWeb = class {
634
820
  constructor() {
@@ -760,6 +946,7 @@ var ApiKeyStorageWeb = class {
760
946
  };
761
947
  export {
762
948
  ApiKeyStorageWeb,
949
+ AuthGatewayClient,
763
950
  BaseOAuthFlow,
764
951
  DesktopOAuthFlow,
765
952
  MCPClient,
package/dist/index.cjs CHANGED
@@ -32,6 +32,7 @@ var index_exports = {};
32
32
  __export(index_exports, {
33
33
  ApiKeyStorage: () => ApiKeyStorage,
34
34
  ApiKeyStorageWeb: () => ApiKeyStorageWeb,
35
+ AuthGatewayClient: () => AuthGatewayClient,
35
36
  BaseOAuthFlow: () => BaseOAuthFlow,
36
37
  DesktopOAuthFlow: () => DesktopOAuthFlow,
37
38
  MCPClient: () => MCPClient,
@@ -1791,3 +1792,189 @@ var MCPClient = class {
1791
1792
  return this.request("memory/delete", { id });
1792
1793
  }
1793
1794
  };
1795
+
1796
+ // src/client/auth-gateway-client.ts
1797
+ var import_cross_fetch5 = __toESM(require("cross-fetch"), 1);
1798
+ var GatewayOAuthFlow = class extends BaseOAuthFlow {
1799
+ async authenticate() {
1800
+ throw new Error("Interactive authentication is not supported in AuthGatewayClient.");
1801
+ }
1802
+ };
1803
+ var DEFAULT_AUTH_BASE_URL = "https://auth.lanonasis.com";
1804
+ var DEFAULT_CLIENT_ID = "lanonasis-cli";
1805
+ var DEFAULT_PROJECT_SCOPE = "lanonasis-maas";
1806
+ var API_KEY_PREFIXES = ["lano_", "pk_", "sk_"];
1807
+ var AuthGatewayClient = class {
1808
+ constructor(config = {}) {
1809
+ const baseUrl = config.authBaseUrl || DEFAULT_AUTH_BASE_URL;
1810
+ this.authBaseUrl = baseUrl.replace(/\/+$/, "");
1811
+ this.projectScope = config.projectScope || DEFAULT_PROJECT_SCOPE;
1812
+ this.flow = new GatewayOAuthFlow({
1813
+ clientId: config.clientId || DEFAULT_CLIENT_ID,
1814
+ authBaseUrl: this.authBaseUrl
1815
+ });
1816
+ }
1817
+ async exchangeSupabaseToken(supabaseToken, options = {}) {
1818
+ const token = typeof supabaseToken === "string" ? supabaseToken.trim() : "";
1819
+ if (!token) {
1820
+ throw new Error("Supabase access token is required");
1821
+ }
1822
+ const projectScope = options.projectScope || this.projectScope;
1823
+ const platform = options.platform || "web";
1824
+ return this.requestJson("/v1/auth/token/exchange", {
1825
+ method: "POST",
1826
+ headers: {
1827
+ Authorization: `Bearer ${token}`,
1828
+ "Content-Type": "application/json",
1829
+ "X-Project-Scope": projectScope
1830
+ },
1831
+ body: JSON.stringify({
1832
+ project_scope: projectScope,
1833
+ platform
1834
+ })
1835
+ });
1836
+ }
1837
+ async refreshToken(refreshToken) {
1838
+ return this.flow.refreshToken(refreshToken);
1839
+ }
1840
+ async revokeToken(token, tokenType = "access_token") {
1841
+ return this.flow.revokeToken(token, tokenType);
1842
+ }
1843
+ async validateToken(token) {
1844
+ const trimmed = typeof token === "string" ? token.trim() : "";
1845
+ if (!trimmed) {
1846
+ return { valid: false, error: "Token is required" };
1847
+ }
1848
+ if (API_KEY_PREFIXES.some((prefix) => trimmed.startsWith(prefix))) {
1849
+ return this.verifyApiKey(trimmed);
1850
+ }
1851
+ if (trimmed.startsWith("cli_") || trimmed.includes(".")) {
1852
+ return this.verifyToken(trimmed);
1853
+ }
1854
+ return this.introspectToken(trimmed);
1855
+ }
1856
+ async verifyApiKey(apiKey) {
1857
+ try {
1858
+ const data = await this.requestJson("/v1/auth/verify-api-key", {
1859
+ method: "POST",
1860
+ headers: {
1861
+ "Content-Type": "application/json",
1862
+ "X-API-Key": apiKey
1863
+ }
1864
+ });
1865
+ if (!data || data.valid === false) {
1866
+ return {
1867
+ valid: false,
1868
+ type: "api_key",
1869
+ error: data && (data.error || data.message) || "Invalid API key",
1870
+ raw: data
1871
+ };
1872
+ }
1873
+ return {
1874
+ valid: true,
1875
+ type: "api_key",
1876
+ userId: data.userId,
1877
+ projectScope: data.projectScope,
1878
+ permissions: data.permissions || [],
1879
+ raw: data
1880
+ };
1881
+ } catch (error) {
1882
+ return {
1883
+ valid: false,
1884
+ type: "api_key",
1885
+ error: error?.message || "API key validation failed",
1886
+ raw: error?.data
1887
+ };
1888
+ }
1889
+ }
1890
+ async verifyToken(token) {
1891
+ try {
1892
+ const data = await this.requestJson("/v1/auth/verify-token", {
1893
+ method: "POST",
1894
+ headers: { "Content-Type": "application/json" },
1895
+ body: JSON.stringify({ token })
1896
+ });
1897
+ if (!data || data.valid === false) {
1898
+ return {
1899
+ valid: false,
1900
+ type: data?.type ? this.normalizeTokenType(data.type) : "jwt",
1901
+ error: data?.error || "Invalid token",
1902
+ raw: data
1903
+ };
1904
+ }
1905
+ return {
1906
+ valid: true,
1907
+ type: data.type ? this.normalizeTokenType(data.type) : "jwt",
1908
+ userId: data.user?.id,
1909
+ email: data.user?.email,
1910
+ role: data.user?.role,
1911
+ expiresAt: data.expires_at || null,
1912
+ raw: data
1913
+ };
1914
+ } catch (error) {
1915
+ return {
1916
+ valid: false,
1917
+ type: "jwt",
1918
+ error: error?.message || "Token verification failed",
1919
+ raw: error?.data
1920
+ };
1921
+ }
1922
+ }
1923
+ async introspectToken(token) {
1924
+ try {
1925
+ const data = await this.requestJson("/oauth/introspect", {
1926
+ method: "POST",
1927
+ headers: { "Content-Type": "application/json" },
1928
+ body: JSON.stringify({ token })
1929
+ });
1930
+ if (!data || data.active !== true) {
1931
+ return {
1932
+ valid: false,
1933
+ type: "oauth",
1934
+ error: "Token is inactive",
1935
+ raw: data
1936
+ };
1937
+ }
1938
+ return {
1939
+ valid: true,
1940
+ type: "oauth",
1941
+ userId: data.user_id || data.sub,
1942
+ scope: data.scope,
1943
+ expiresAt: data.exp ? new Date(data.exp * 1e3).toISOString() : null,
1944
+ raw: data
1945
+ };
1946
+ } catch (error) {
1947
+ return {
1948
+ valid: false,
1949
+ type: "oauth",
1950
+ error: error?.message || "Token introspection failed",
1951
+ raw: error?.data
1952
+ };
1953
+ }
1954
+ }
1955
+ normalizeTokenType(type) {
1956
+ if (type === "cli_token") return "cli";
1957
+ if (type === "jwt") return "jwt";
1958
+ return "jwt";
1959
+ }
1960
+ async requestJson(path, options) {
1961
+ const response = await (0, import_cross_fetch5.default)(`${this.authBaseUrl}${path.startsWith("/") ? path : `/${path}`}`, options);
1962
+ const text = await response.text();
1963
+ let data = null;
1964
+ if (text) {
1965
+ try {
1966
+ data = JSON.parse(text);
1967
+ } catch (parseError) {
1968
+ data = { raw: text };
1969
+ }
1970
+ }
1971
+ if (!response.ok) {
1972
+ const message = data?.message || data?.error || `Request failed (${response.status})`;
1973
+ const error = new Error(message);
1974
+ error.status = response.status;
1975
+ error.data = data;
1976
+ throw error;
1977
+ }
1978
+ return data;
1979
+ }
1980
+ };
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { B as BaseOAuthFlow, O as OAuthConfig, b as TokenResponse, T as TokenStorageAdapter } from './api-key-storage-web-DannE11B.cjs';
2
- export { f as ApiKeyData, g as ApiKeyStorage, A as ApiKeyStorageWeb, d as AuthError, D as DesktopOAuthFlow, c as DeviceCodeResponse, G as GrantType, P as PKCEChallenge, e as TokenStorage, a as TokenStorageWeb } from './api-key-storage-web-DannE11B.cjs';
1
+ import { B as BaseOAuthFlow, O as OAuthConfig, c as TokenResponse, T as TokenStorageAdapter } from './api-key-storage-web-J3W8nQi2.cjs';
2
+ export { l as ApiKeyData, m as ApiKeyStorage, b as ApiKeyStorageWeb, e as AuthError, A as AuthGatewayClient, h as AuthGatewayClientConfig, f as AuthTokenType, g as AuthValidationResult, D as DesktopOAuthFlow, d as DeviceCodeResponse, G as GrantType, P as PKCEChallenge, i as TokenExchangeOptions, j as TokenExchangeResponse, k as TokenStorage, a as TokenStorageWeb } from './api-key-storage-web-J3W8nQi2.cjs';
3
3
 
4
4
  declare class TerminalOAuthFlow extends BaseOAuthFlow {
5
5
  private pollInterval;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { B as BaseOAuthFlow, O as OAuthConfig, b as TokenResponse, T as TokenStorageAdapter } from './api-key-storage-web-DannE11B.js';
2
- export { f as ApiKeyData, g as ApiKeyStorage, A as ApiKeyStorageWeb, d as AuthError, D as DesktopOAuthFlow, c as DeviceCodeResponse, G as GrantType, P as PKCEChallenge, e as TokenStorage, a as TokenStorageWeb } from './api-key-storage-web-DannE11B.js';
1
+ import { B as BaseOAuthFlow, O as OAuthConfig, c as TokenResponse, T as TokenStorageAdapter } from './api-key-storage-web-J3W8nQi2.js';
2
+ export { l as ApiKeyData, m as ApiKeyStorage, b as ApiKeyStorageWeb, e as AuthError, A as AuthGatewayClient, h as AuthGatewayClientConfig, f as AuthTokenType, g as AuthValidationResult, D as DesktopOAuthFlow, d as DeviceCodeResponse, G as GrantType, P as PKCEChallenge, i as TokenExchangeOptions, j as TokenExchangeResponse, k as TokenStorage, a as TokenStorageWeb } from './api-key-storage-web-J3W8nQi2.js';
3
3
 
4
4
  declare class TerminalOAuthFlow extends BaseOAuthFlow {
5
5
  private pollInterval;
package/dist/index.mjs CHANGED
@@ -1755,9 +1755,196 @@ var MCPClient = class {
1755
1755
  return this.request("memory/delete", { id });
1756
1756
  }
1757
1757
  };
1758
+
1759
+ // src/client/auth-gateway-client.ts
1760
+ import fetch5 from "cross-fetch";
1761
+ var GatewayOAuthFlow = class extends BaseOAuthFlow {
1762
+ async authenticate() {
1763
+ throw new Error("Interactive authentication is not supported in AuthGatewayClient.");
1764
+ }
1765
+ };
1766
+ var DEFAULT_AUTH_BASE_URL = "https://auth.lanonasis.com";
1767
+ var DEFAULT_CLIENT_ID = "lanonasis-cli";
1768
+ var DEFAULT_PROJECT_SCOPE = "lanonasis-maas";
1769
+ var API_KEY_PREFIXES = ["lano_", "pk_", "sk_"];
1770
+ var AuthGatewayClient = class {
1771
+ constructor(config = {}) {
1772
+ const baseUrl = config.authBaseUrl || DEFAULT_AUTH_BASE_URL;
1773
+ this.authBaseUrl = baseUrl.replace(/\/+$/, "");
1774
+ this.projectScope = config.projectScope || DEFAULT_PROJECT_SCOPE;
1775
+ this.flow = new GatewayOAuthFlow({
1776
+ clientId: config.clientId || DEFAULT_CLIENT_ID,
1777
+ authBaseUrl: this.authBaseUrl
1778
+ });
1779
+ }
1780
+ async exchangeSupabaseToken(supabaseToken, options = {}) {
1781
+ const token = typeof supabaseToken === "string" ? supabaseToken.trim() : "";
1782
+ if (!token) {
1783
+ throw new Error("Supabase access token is required");
1784
+ }
1785
+ const projectScope = options.projectScope || this.projectScope;
1786
+ const platform = options.platform || "web";
1787
+ return this.requestJson("/v1/auth/token/exchange", {
1788
+ method: "POST",
1789
+ headers: {
1790
+ Authorization: `Bearer ${token}`,
1791
+ "Content-Type": "application/json",
1792
+ "X-Project-Scope": projectScope
1793
+ },
1794
+ body: JSON.stringify({
1795
+ project_scope: projectScope,
1796
+ platform
1797
+ })
1798
+ });
1799
+ }
1800
+ async refreshToken(refreshToken) {
1801
+ return this.flow.refreshToken(refreshToken);
1802
+ }
1803
+ async revokeToken(token, tokenType = "access_token") {
1804
+ return this.flow.revokeToken(token, tokenType);
1805
+ }
1806
+ async validateToken(token) {
1807
+ const trimmed = typeof token === "string" ? token.trim() : "";
1808
+ if (!trimmed) {
1809
+ return { valid: false, error: "Token is required" };
1810
+ }
1811
+ if (API_KEY_PREFIXES.some((prefix) => trimmed.startsWith(prefix))) {
1812
+ return this.verifyApiKey(trimmed);
1813
+ }
1814
+ if (trimmed.startsWith("cli_") || trimmed.includes(".")) {
1815
+ return this.verifyToken(trimmed);
1816
+ }
1817
+ return this.introspectToken(trimmed);
1818
+ }
1819
+ async verifyApiKey(apiKey) {
1820
+ try {
1821
+ const data = await this.requestJson("/v1/auth/verify-api-key", {
1822
+ method: "POST",
1823
+ headers: {
1824
+ "Content-Type": "application/json",
1825
+ "X-API-Key": apiKey
1826
+ }
1827
+ });
1828
+ if (!data || data.valid === false) {
1829
+ return {
1830
+ valid: false,
1831
+ type: "api_key",
1832
+ error: data && (data.error || data.message) || "Invalid API key",
1833
+ raw: data
1834
+ };
1835
+ }
1836
+ return {
1837
+ valid: true,
1838
+ type: "api_key",
1839
+ userId: data.userId,
1840
+ projectScope: data.projectScope,
1841
+ permissions: data.permissions || [],
1842
+ raw: data
1843
+ };
1844
+ } catch (error) {
1845
+ return {
1846
+ valid: false,
1847
+ type: "api_key",
1848
+ error: error?.message || "API key validation failed",
1849
+ raw: error?.data
1850
+ };
1851
+ }
1852
+ }
1853
+ async verifyToken(token) {
1854
+ try {
1855
+ const data = await this.requestJson("/v1/auth/verify-token", {
1856
+ method: "POST",
1857
+ headers: { "Content-Type": "application/json" },
1858
+ body: JSON.stringify({ token })
1859
+ });
1860
+ if (!data || data.valid === false) {
1861
+ return {
1862
+ valid: false,
1863
+ type: data?.type ? this.normalizeTokenType(data.type) : "jwt",
1864
+ error: data?.error || "Invalid token",
1865
+ raw: data
1866
+ };
1867
+ }
1868
+ return {
1869
+ valid: true,
1870
+ type: data.type ? this.normalizeTokenType(data.type) : "jwt",
1871
+ userId: data.user?.id,
1872
+ email: data.user?.email,
1873
+ role: data.user?.role,
1874
+ expiresAt: data.expires_at || null,
1875
+ raw: data
1876
+ };
1877
+ } catch (error) {
1878
+ return {
1879
+ valid: false,
1880
+ type: "jwt",
1881
+ error: error?.message || "Token verification failed",
1882
+ raw: error?.data
1883
+ };
1884
+ }
1885
+ }
1886
+ async introspectToken(token) {
1887
+ try {
1888
+ const data = await this.requestJson("/oauth/introspect", {
1889
+ method: "POST",
1890
+ headers: { "Content-Type": "application/json" },
1891
+ body: JSON.stringify({ token })
1892
+ });
1893
+ if (!data || data.active !== true) {
1894
+ return {
1895
+ valid: false,
1896
+ type: "oauth",
1897
+ error: "Token is inactive",
1898
+ raw: data
1899
+ };
1900
+ }
1901
+ return {
1902
+ valid: true,
1903
+ type: "oauth",
1904
+ userId: data.user_id || data.sub,
1905
+ scope: data.scope,
1906
+ expiresAt: data.exp ? new Date(data.exp * 1e3).toISOString() : null,
1907
+ raw: data
1908
+ };
1909
+ } catch (error) {
1910
+ return {
1911
+ valid: false,
1912
+ type: "oauth",
1913
+ error: error?.message || "Token introspection failed",
1914
+ raw: error?.data
1915
+ };
1916
+ }
1917
+ }
1918
+ normalizeTokenType(type) {
1919
+ if (type === "cli_token") return "cli";
1920
+ if (type === "jwt") return "jwt";
1921
+ return "jwt";
1922
+ }
1923
+ async requestJson(path, options) {
1924
+ const response = await fetch5(`${this.authBaseUrl}${path.startsWith("/") ? path : `/${path}`}`, options);
1925
+ const text = await response.text();
1926
+ let data = null;
1927
+ if (text) {
1928
+ try {
1929
+ data = JSON.parse(text);
1930
+ } catch (parseError) {
1931
+ data = { raw: text };
1932
+ }
1933
+ }
1934
+ if (!response.ok) {
1935
+ const message = data?.message || data?.error || `Request failed (${response.status})`;
1936
+ const error = new Error(message);
1937
+ error.status = response.status;
1938
+ error.data = data;
1939
+ throw error;
1940
+ }
1941
+ return data;
1942
+ }
1943
+ };
1758
1944
  export {
1759
1945
  ApiKeyStorage,
1760
1946
  ApiKeyStorageWeb,
1947
+ AuthGatewayClient,
1761
1948
  BaseOAuthFlow,
1762
1949
  DesktopOAuthFlow,
1763
1950
  MCPClient,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lanonasis/oauth-client",
3
- "version": "1.2.6",
3
+ "version": "1.2.7",
4
4
  "type": "module",
5
5
  "description": "OAuth and API Key authentication client for Lan Onasis MCP integration",
6
6
  "license": "MIT",
@@ -38,7 +38,9 @@
38
38
  "scripts": {
39
39
  "build": "tsup --config tsup.config.ts",
40
40
  "dev": "tsup --watch --config tsup.config.ts",
41
- "test": "bun test",
41
+ "test": "vitest run",
42
+ "test:watch": "vitest",
43
+ "test:coverage": "vitest run --coverage",
42
44
  "lint": "eslint src"
43
45
  },
44
46
  "keywords": [
@@ -52,6 +54,7 @@
52
54
  ],
53
55
  "dependencies": {
54
56
  "cross-fetch": "^4.0.0",
57
+ "eventsource": "^2.0.2",
55
58
  "keytar": "^7.9.0",
56
59
  "open": "^9.1.0"
57
60
  },
@@ -59,11 +62,13 @@
59
62
  "@eslint/js": "^9.17.0",
60
63
  "@types/eventsource": "^3.0.0",
61
64
  "@types/node": "^20.0.0",
65
+ "@vitest/coverage-v8": "^2.0.0",
62
66
  "eslint": "^9.17.0",
63
67
  "globals": "^16.4.0",
64
68
  "tsup": "^8.5.0",
65
69
  "typescript": "^5.3.3",
66
- "typescript-eslint": "^8.18.1"
70
+ "typescript-eslint": "^8.18.1",
71
+ "vitest": "^2.0.0"
67
72
  },
68
73
  "peerDependencies": {
69
74
  "@supabase/supabase-js": "^2.0.0"