@lanonasis/oauth-client 1.2.0 → 1.2.2

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 CHANGED
@@ -6,11 +6,11 @@ Drop-in OAuth + API Key authentication client for the Lanonasis ecosystem. Suppo
6
6
  - **Dual Authentication**: OAuth2 PKCE flow OR direct API key authentication
7
7
  - OAuth flows for terminal and desktop (Electron-friendly) environments
8
8
  - API key authentication for new users with dashboard-generated keys
9
- - Token storage with secure backends (Keytar, encrypted files, Electron secure store, mobile secure storage, WebCrypto in browsers)
9
+ - Token storage with secure backends (Keytar, encrypted files, Electron secure store, mobile secure storage, WebCrypto in browsers); browser builds auto-use web storage
10
10
  - API key storage that normalizes to SHA-256 digests before persisting
11
11
  - MCP client that connects over WebSocket (`/ws`) or SSE (`/sse`) with auto-refreshing tokens
12
12
  - Automatic auth mode detection based on configuration
13
- - ESM + CJS bundles with TypeScript types
13
+ - ESM + CJS bundles with a dedicated browser export to avoid Node-only deps
14
14
 
15
15
  ## Installation
16
16
  ```bash
@@ -23,7 +23,7 @@ bun add @lanonasis/oauth-client
23
23
 
24
24
  ### Option 1: API Key Authentication (Recommended for New Users)
25
25
  ```ts
26
- import { MCPClient } from '@lanonasis/oauth-client';
26
+ import { MCPClient } from '@lanonasis/oauth-client'; // or '@lanonasis/oauth-client/browser' in web-only bundles
27
27
 
28
28
  // Simple API key mode - perfect for dashboard users
29
29
  const client = new MCPClient({
@@ -45,7 +45,7 @@ const client = new MCPClient({
45
45
  clientId: 'your_oauth_client_id',
46
46
  authBaseUrl: 'https://auth.lanonasis.com',
47
47
  mcpEndpoint: 'wss://mcp.lanonasis.com',
48
- scope: 'mcp:read mcp:write api_keys:manage'
48
+ scope: 'memories:read memories:write memories:delete profile' // default if omitted
49
49
  });
50
50
 
51
51
  await client.connect(); // Triggers OAuth flow, handles refresh
@@ -99,16 +99,22 @@ const hashed = await apiKeys.getApiKey(); // returns sha256 hex digest
99
99
  ### API Key Mode
100
100
  - `apiKey` (required): Your dashboard-generated API key (starts with `lano_`).
101
101
  - `mcpEndpoint` (optional): defaults to `wss://mcp.lanonasis.com` and can also be `https://...` for SSE.
102
+ - `tokenStorage` (optional): provide a custom storage adapter; defaults to secure Node storage in Node/Electron and WebCrypto+localStorage in browsers.
102
103
 
103
104
  ### OAuth Mode
104
105
  - `clientId` (required): OAuth client id issued by Lanonasis Auth.
105
106
  - `authBaseUrl` (optional): defaults to `https://auth.lanonasis.com`.
106
107
  - `mcpEndpoint` (optional): defaults to `wss://mcp.lanonasis.com` and can also be `https://...` for SSE.
107
- - `scope` (optional): defaults to `mcp:read mcp:write api_keys:manage`.
108
+ - `scope` (optional): defaults to `memories:read memories:write memories:delete profile`.
108
109
  - `autoRefresh` (MCPClient): refresh tokens 5 minutes before expiry (default `true`).
109
110
 
110
111
  **Note**: Auth mode is automatically detected - if you provide `apiKey`, it uses API key authentication. If you provide `clientId`, it uses OAuth.
111
112
 
113
+ ## Browser builds
114
+ - The package ships a browser entry at `@lanonasis/oauth-client/browser`; modern bundlers will also pick it via the `browser` export.
115
+ - Browser bundles avoid Node-only deps (`keytar`, `open`, `fs`, `os`, etc.) and use Web Crypto + `localStorage` for token/API key persistence.
116
+ - The terminal/device code flow remains Node-only; in browser previews use API key or desktop flow.
117
+
112
118
  ## Publishing (maintainers)
113
119
  1) Build artifacts: `npm install && npm run build`
114
120
  2) Verify contents: ensure `dist`, `README.md`, `LICENSE` are present.
@@ -0,0 +1,246 @@
1
+ interface TokenResponse {
2
+ access_token: string;
3
+ refresh_token?: string;
4
+ expires_in: number;
5
+ token_type: string;
6
+ scope?: string;
7
+ issued_at?: number;
8
+ }
9
+ interface DeviceCodeResponse {
10
+ device_code: string;
11
+ user_code: string;
12
+ verification_uri: string;
13
+ verification_uri_complete?: string;
14
+ expires_in: number;
15
+ interval: number;
16
+ }
17
+ interface OAuthConfig {
18
+ clientId: string;
19
+ authBaseUrl?: string;
20
+ redirectUri?: string;
21
+ scope?: string;
22
+ }
23
+ interface AuthError {
24
+ error: string;
25
+ error_description?: string;
26
+ }
27
+ type GrantType = 'authorization_code' | 'urn:ietf:params:oauth:grant-type:device_code' | 'refresh_token';
28
+ interface PKCEChallenge {
29
+ codeVerifier: string;
30
+ codeChallenge: string;
31
+ }
32
+
33
+ declare abstract class BaseOAuthFlow {
34
+ protected readonly clientId: string;
35
+ protected readonly authBaseUrl: string;
36
+ protected readonly scope: string;
37
+ constructor(config: OAuthConfig);
38
+ abstract authenticate(): Promise<TokenResponse>;
39
+ protected makeTokenRequest(body: Record<string, string>): Promise<TokenResponse>;
40
+ protected generateState(): string;
41
+ protected base64URLEncode(buffer: ArrayBuffer | Uint8Array): string;
42
+ refreshToken(refreshToken: string): Promise<TokenResponse>;
43
+ revokeToken(token: string, tokenType?: 'access_token' | 'refresh_token'): Promise<void>;
44
+ }
45
+
46
+ declare class DesktopOAuthFlow extends BaseOAuthFlow {
47
+ private readonly redirectUri;
48
+ private authWindow;
49
+ constructor(config: OAuthConfig);
50
+ authenticate(): Promise<TokenResponse>;
51
+ private generatePKCEChallenge;
52
+ private generateCodeVerifier;
53
+ private generateCodeChallenge;
54
+ private buildAuthorizationUrl;
55
+ private openAuthWindow;
56
+ private openBrowserWindow;
57
+ private openElectronWindow;
58
+ private exchangeCodeForToken;
59
+ }
60
+
61
+ interface TokenStorageAdapter {
62
+ store(tokens: TokenResponse): Promise<void>;
63
+ retrieve(): Promise<TokenResponse | null>;
64
+ clear(): Promise<void>;
65
+ isTokenExpired(tokens: TokenResponse & {
66
+ issued_at?: number;
67
+ }): boolean;
68
+ }
69
+ declare class TokenStorage implements TokenStorageAdapter {
70
+ private readonly storageKey;
71
+ private readonly webEncryptionKeyStorage;
72
+ private keytar;
73
+ constructor();
74
+ store(tokens: TokenResponse): Promise<void>;
75
+ retrieve(): Promise<TokenResponse | null>;
76
+ clear(): Promise<void>;
77
+ isTokenExpired(tokens: TokenResponse & {
78
+ issued_at?: number;
79
+ }): boolean;
80
+ private storeToFile;
81
+ private retrieveFromFile;
82
+ private deleteFile;
83
+ private getFileEncryptionKey;
84
+ private encrypt;
85
+ private decrypt;
86
+ private isNode;
87
+ private isElectron;
88
+ private isMobile;
89
+ private base64Encode;
90
+ private base64Decode;
91
+ private getWebEncryptionKey;
92
+ }
93
+
94
+ interface MCPClientConfig extends Partial<OAuthConfig> {
95
+ mcpEndpoint?: string;
96
+ autoRefresh?: boolean;
97
+ apiKey?: string;
98
+ tokenStorage?: TokenStorageAdapter;
99
+ }
100
+ declare class MCPClient {
101
+ private tokenStorage;
102
+ private authFlow;
103
+ private config;
104
+ private authMode;
105
+ private ws;
106
+ private eventSource;
107
+ private accessToken;
108
+ private refreshTimer;
109
+ constructor(config?: MCPClientConfig);
110
+ connect(): Promise<void>;
111
+ private authenticate;
112
+ private ensureAccessToken;
113
+ private scheduleTokenRefresh;
114
+ private establishConnection;
115
+ private connectWebSocket;
116
+ private connectSSE;
117
+ private handleMessage;
118
+ private reconnect;
119
+ request<T = unknown>(method: string, params?: unknown): Promise<T>;
120
+ disconnect(): void;
121
+ logout(): Promise<void>;
122
+ private isTerminal;
123
+ private isElectron;
124
+ private generateId;
125
+ createMemory<T = unknown>(title: string, content: string, options?: unknown): Promise<T>;
126
+ searchMemories<T = unknown>(query: string, options?: unknown): Promise<T[]>;
127
+ getMemory<T = unknown>(id: string): Promise<T>;
128
+ updateMemory<T = unknown>(id: string, updates: Partial<T>): Promise<T>;
129
+ deleteMemory(id: string): Promise<void>;
130
+ }
131
+
132
+ /**
133
+ * Browser-only token storage that avoids Node/Electron dependencies.
134
+ * Tokens are encrypted with Web Crypto and stored in localStorage.
135
+ */
136
+ declare class TokenStorageWeb implements TokenStorageAdapter {
137
+ private readonly storageKey;
138
+ private readonly webEncryptionKeyStorage;
139
+ store(tokens: TokenResponse): Promise<void>;
140
+ retrieve(): Promise<TokenResponse | null>;
141
+ clear(): Promise<void>;
142
+ isTokenExpired(tokens: TokenResponse & {
143
+ issued_at?: number;
144
+ }): boolean;
145
+ private encrypt;
146
+ private decrypt;
147
+ private getWebEncryptionKey;
148
+ private base64Encode;
149
+ private base64Decode;
150
+ }
151
+
152
+ /**
153
+ * API Key Storage Service
154
+ * Secure multi-platform API key storage with encryption
155
+ * Supports Node.js, Electron, Web, and Mobile environments
156
+ */
157
+ interface ApiKeyData {
158
+ apiKey: string;
159
+ organizationId?: string;
160
+ userId?: string;
161
+ environment?: 'development' | 'staging' | 'production';
162
+ createdAt?: string;
163
+ expiresAt?: string;
164
+ metadata?: Record<string, unknown>;
165
+ }
166
+ declare class ApiKeyStorage {
167
+ private readonly storageKey;
168
+ private readonly legacyConfigKey;
169
+ private readonly webEncryptionKeyStorage;
170
+ private keytar;
171
+ private migrationCompleted;
172
+ constructor();
173
+ /**
174
+ * Initialize and migrate from legacy storage if needed
175
+ */
176
+ initialize(): Promise<void>;
177
+ /**
178
+ * Store API key securely
179
+ */
180
+ store(data: ApiKeyData): Promise<void>;
181
+ /**
182
+ * Retrieve API key from secure storage
183
+ */
184
+ retrieve(): Promise<ApiKeyData | null>;
185
+ /**
186
+ * Get just the API key string (convenience method)
187
+ */
188
+ getApiKey(): Promise<string | null>;
189
+ /**
190
+ * Check if API key exists
191
+ */
192
+ hasApiKey(): Promise<boolean>;
193
+ /**
194
+ * Clear API key from storage
195
+ */
196
+ clear(): Promise<void>;
197
+ /**
198
+ * Check if API key is expired
199
+ */
200
+ isExpired(data: ApiKeyData): boolean;
201
+ /**
202
+ * Update API key metadata without changing the key itself
203
+ */
204
+ updateMetadata(metadata: Record<string, unknown>): Promise<void>;
205
+ /**
206
+ * Migrate from legacy configuration storage
207
+ */
208
+ private migrateFromLegacyIfNeeded;
209
+ private storeToFile;
210
+ private retrieveFromFile;
211
+ private deleteFile;
212
+ private retrieveLegacyFromFile;
213
+ private deleteLegacyFile;
214
+ private getFileEncryptionKey;
215
+ private encrypt;
216
+ private decrypt;
217
+ private getWebEncryptionKey;
218
+ private isNode;
219
+ private isElectron;
220
+ private isMobile;
221
+ private base64Encode;
222
+ private base64Decode;
223
+ /**
224
+ * Normalize API keys to a SHA-256 hex digest.
225
+ * Accepts pre-hashed input and lowercases it to prevent double hashing.
226
+ */
227
+ private normalizeApiKey;
228
+ }
229
+
230
+ /**
231
+ * Browser-only API key storage using Web Crypto + localStorage.
232
+ */
233
+ declare class ApiKeyStorageWeb {
234
+ private readonly storageKey;
235
+ private readonly webEncryptionKeyStorage;
236
+ store(data: ApiKeyData): Promise<void>;
237
+ retrieve(): Promise<ApiKeyData | null>;
238
+ clear(): Promise<void>;
239
+ private encrypt;
240
+ private decrypt;
241
+ private getWebEncryptionKey;
242
+ private base64Encode;
243
+ private base64Decode;
244
+ }
245
+
246
+ export { type ApiKeyData as A, BaseOAuthFlow as B, DesktopOAuthFlow as D, type GrantType as G, type MCPClientConfig as M, type OAuthConfig as O, type PKCEChallenge as P, type TokenResponse as T, type TokenStorageAdapter as a, TokenStorage as b, ApiKeyStorage as c, TokenStorageWeb as d, ApiKeyStorageWeb as e, MCPClient as f, type DeviceCodeResponse as g, type AuthError as h };
@@ -0,0 +1,246 @@
1
+ interface TokenResponse {
2
+ access_token: string;
3
+ refresh_token?: string;
4
+ expires_in: number;
5
+ token_type: string;
6
+ scope?: string;
7
+ issued_at?: number;
8
+ }
9
+ interface DeviceCodeResponse {
10
+ device_code: string;
11
+ user_code: string;
12
+ verification_uri: string;
13
+ verification_uri_complete?: string;
14
+ expires_in: number;
15
+ interval: number;
16
+ }
17
+ interface OAuthConfig {
18
+ clientId: string;
19
+ authBaseUrl?: string;
20
+ redirectUri?: string;
21
+ scope?: string;
22
+ }
23
+ interface AuthError {
24
+ error: string;
25
+ error_description?: string;
26
+ }
27
+ type GrantType = 'authorization_code' | 'urn:ietf:params:oauth:grant-type:device_code' | 'refresh_token';
28
+ interface PKCEChallenge {
29
+ codeVerifier: string;
30
+ codeChallenge: string;
31
+ }
32
+
33
+ declare abstract class BaseOAuthFlow {
34
+ protected readonly clientId: string;
35
+ protected readonly authBaseUrl: string;
36
+ protected readonly scope: string;
37
+ constructor(config: OAuthConfig);
38
+ abstract authenticate(): Promise<TokenResponse>;
39
+ protected makeTokenRequest(body: Record<string, string>): Promise<TokenResponse>;
40
+ protected generateState(): string;
41
+ protected base64URLEncode(buffer: ArrayBuffer | Uint8Array): string;
42
+ refreshToken(refreshToken: string): Promise<TokenResponse>;
43
+ revokeToken(token: string, tokenType?: 'access_token' | 'refresh_token'): Promise<void>;
44
+ }
45
+
46
+ declare class DesktopOAuthFlow extends BaseOAuthFlow {
47
+ private readonly redirectUri;
48
+ private authWindow;
49
+ constructor(config: OAuthConfig);
50
+ authenticate(): Promise<TokenResponse>;
51
+ private generatePKCEChallenge;
52
+ private generateCodeVerifier;
53
+ private generateCodeChallenge;
54
+ private buildAuthorizationUrl;
55
+ private openAuthWindow;
56
+ private openBrowserWindow;
57
+ private openElectronWindow;
58
+ private exchangeCodeForToken;
59
+ }
60
+
61
+ interface TokenStorageAdapter {
62
+ store(tokens: TokenResponse): Promise<void>;
63
+ retrieve(): Promise<TokenResponse | null>;
64
+ clear(): Promise<void>;
65
+ isTokenExpired(tokens: TokenResponse & {
66
+ issued_at?: number;
67
+ }): boolean;
68
+ }
69
+ declare class TokenStorage implements TokenStorageAdapter {
70
+ private readonly storageKey;
71
+ private readonly webEncryptionKeyStorage;
72
+ private keytar;
73
+ constructor();
74
+ store(tokens: TokenResponse): Promise<void>;
75
+ retrieve(): Promise<TokenResponse | null>;
76
+ clear(): Promise<void>;
77
+ isTokenExpired(tokens: TokenResponse & {
78
+ issued_at?: number;
79
+ }): boolean;
80
+ private storeToFile;
81
+ private retrieveFromFile;
82
+ private deleteFile;
83
+ private getFileEncryptionKey;
84
+ private encrypt;
85
+ private decrypt;
86
+ private isNode;
87
+ private isElectron;
88
+ private isMobile;
89
+ private base64Encode;
90
+ private base64Decode;
91
+ private getWebEncryptionKey;
92
+ }
93
+
94
+ interface MCPClientConfig extends Partial<OAuthConfig> {
95
+ mcpEndpoint?: string;
96
+ autoRefresh?: boolean;
97
+ apiKey?: string;
98
+ tokenStorage?: TokenStorageAdapter;
99
+ }
100
+ declare class MCPClient {
101
+ private tokenStorage;
102
+ private authFlow;
103
+ private config;
104
+ private authMode;
105
+ private ws;
106
+ private eventSource;
107
+ private accessToken;
108
+ private refreshTimer;
109
+ constructor(config?: MCPClientConfig);
110
+ connect(): Promise<void>;
111
+ private authenticate;
112
+ private ensureAccessToken;
113
+ private scheduleTokenRefresh;
114
+ private establishConnection;
115
+ private connectWebSocket;
116
+ private connectSSE;
117
+ private handleMessage;
118
+ private reconnect;
119
+ request<T = unknown>(method: string, params?: unknown): Promise<T>;
120
+ disconnect(): void;
121
+ logout(): Promise<void>;
122
+ private isTerminal;
123
+ private isElectron;
124
+ private generateId;
125
+ createMemory<T = unknown>(title: string, content: string, options?: unknown): Promise<T>;
126
+ searchMemories<T = unknown>(query: string, options?: unknown): Promise<T[]>;
127
+ getMemory<T = unknown>(id: string): Promise<T>;
128
+ updateMemory<T = unknown>(id: string, updates: Partial<T>): Promise<T>;
129
+ deleteMemory(id: string): Promise<void>;
130
+ }
131
+
132
+ /**
133
+ * Browser-only token storage that avoids Node/Electron dependencies.
134
+ * Tokens are encrypted with Web Crypto and stored in localStorage.
135
+ */
136
+ declare class TokenStorageWeb implements TokenStorageAdapter {
137
+ private readonly storageKey;
138
+ private readonly webEncryptionKeyStorage;
139
+ store(tokens: TokenResponse): Promise<void>;
140
+ retrieve(): Promise<TokenResponse | null>;
141
+ clear(): Promise<void>;
142
+ isTokenExpired(tokens: TokenResponse & {
143
+ issued_at?: number;
144
+ }): boolean;
145
+ private encrypt;
146
+ private decrypt;
147
+ private getWebEncryptionKey;
148
+ private base64Encode;
149
+ private base64Decode;
150
+ }
151
+
152
+ /**
153
+ * API Key Storage Service
154
+ * Secure multi-platform API key storage with encryption
155
+ * Supports Node.js, Electron, Web, and Mobile environments
156
+ */
157
+ interface ApiKeyData {
158
+ apiKey: string;
159
+ organizationId?: string;
160
+ userId?: string;
161
+ environment?: 'development' | 'staging' | 'production';
162
+ createdAt?: string;
163
+ expiresAt?: string;
164
+ metadata?: Record<string, unknown>;
165
+ }
166
+ declare class ApiKeyStorage {
167
+ private readonly storageKey;
168
+ private readonly legacyConfigKey;
169
+ private readonly webEncryptionKeyStorage;
170
+ private keytar;
171
+ private migrationCompleted;
172
+ constructor();
173
+ /**
174
+ * Initialize and migrate from legacy storage if needed
175
+ */
176
+ initialize(): Promise<void>;
177
+ /**
178
+ * Store API key securely
179
+ */
180
+ store(data: ApiKeyData): Promise<void>;
181
+ /**
182
+ * Retrieve API key from secure storage
183
+ */
184
+ retrieve(): Promise<ApiKeyData | null>;
185
+ /**
186
+ * Get just the API key string (convenience method)
187
+ */
188
+ getApiKey(): Promise<string | null>;
189
+ /**
190
+ * Check if API key exists
191
+ */
192
+ hasApiKey(): Promise<boolean>;
193
+ /**
194
+ * Clear API key from storage
195
+ */
196
+ clear(): Promise<void>;
197
+ /**
198
+ * Check if API key is expired
199
+ */
200
+ isExpired(data: ApiKeyData): boolean;
201
+ /**
202
+ * Update API key metadata without changing the key itself
203
+ */
204
+ updateMetadata(metadata: Record<string, unknown>): Promise<void>;
205
+ /**
206
+ * Migrate from legacy configuration storage
207
+ */
208
+ private migrateFromLegacyIfNeeded;
209
+ private storeToFile;
210
+ private retrieveFromFile;
211
+ private deleteFile;
212
+ private retrieveLegacyFromFile;
213
+ private deleteLegacyFile;
214
+ private getFileEncryptionKey;
215
+ private encrypt;
216
+ private decrypt;
217
+ private getWebEncryptionKey;
218
+ private isNode;
219
+ private isElectron;
220
+ private isMobile;
221
+ private base64Encode;
222
+ private base64Decode;
223
+ /**
224
+ * Normalize API keys to a SHA-256 hex digest.
225
+ * Accepts pre-hashed input and lowercases it to prevent double hashing.
226
+ */
227
+ private normalizeApiKey;
228
+ }
229
+
230
+ /**
231
+ * Browser-only API key storage using Web Crypto + localStorage.
232
+ */
233
+ declare class ApiKeyStorageWeb {
234
+ private readonly storageKey;
235
+ private readonly webEncryptionKeyStorage;
236
+ store(data: ApiKeyData): Promise<void>;
237
+ retrieve(): Promise<ApiKeyData | null>;
238
+ clear(): Promise<void>;
239
+ private encrypt;
240
+ private decrypt;
241
+ private getWebEncryptionKey;
242
+ private base64Encode;
243
+ private base64Decode;
244
+ }
245
+
246
+ export { type ApiKeyData as A, BaseOAuthFlow as B, DesktopOAuthFlow as D, type GrantType as G, type MCPClientConfig as M, type OAuthConfig as O, type PKCEChallenge as P, type TokenResponse as T, type TokenStorageAdapter as a, TokenStorage as b, ApiKeyStorage as c, TokenStorageWeb as d, ApiKeyStorageWeb as e, MCPClient as f, type DeviceCodeResponse as g, type AuthError as h };