@lanonasis/oauth-client 1.2.1 → 1.2.3

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
@@ -1,13 +1,16 @@
1
1
  # @lanonasis/oauth-client
2
2
 
3
- Drop-in OAuth + MCP connectivity client for the Lanonasis ecosystem. Handles browser/desktop/terminal flows, token lifecycle, secure (hashed) API key storage, and MCP WebSocket/SSE connections so other projects can integrate without re-implementing auth.
3
+ Drop-in OAuth + API Key authentication client for the Lanonasis ecosystem. Supports dual authentication modes: OAuth2 PKCE flow for pre-registered clients and direct API key authentication for dashboard users. Handles browser/desktop/terminal flows, token lifecycle, secure storage, and MCP WebSocket/SSE connections.
4
4
 
5
5
  ## Features
6
+ - **Dual Authentication**: OAuth2 PKCE flow OR direct API key authentication
6
7
  - OAuth flows for terminal and desktop (Electron-friendly) environments
7
- - Token storage with secure backends (Keytar, encrypted files, Electron secure store, mobile secure storage, WebCrypto in browsers)
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); browser builds auto-use web storage
8
10
  - API key storage that normalizes to SHA-256 digests before persisting
9
11
  - MCP client that connects over WebSocket (`/ws`) or SSE (`/sse`) with auto-refreshing tokens
10
- - ESM + CJS bundles with TypeScript types
12
+ - Automatic auth mode detection based on configuration
13
+ - ESM + CJS bundles with a dedicated browser export to avoid Node-only deps
11
14
 
12
15
  ## Installation
13
16
  ```bash
@@ -17,6 +20,24 @@ bun add @lanonasis/oauth-client
17
20
  ```
18
21
 
19
22
  ## Quick Start
23
+
24
+ ### Option 1: API Key Authentication (Recommended for New Users)
25
+ ```ts
26
+ import { MCPClient } from '@lanonasis/oauth-client'; // or '@lanonasis/oauth-client/browser' in web-only bundles
27
+
28
+ // Simple API key mode - perfect for dashboard users
29
+ const client = new MCPClient({
30
+ apiKey: 'lano_abc123xyz', // Get from dashboard
31
+ mcpEndpoint: 'wss://mcp.lanonasis.com'
32
+ });
33
+
34
+ await client.connect(); // Automatically uses API key auth
35
+
36
+ // Make requests
37
+ const memories = await client.searchMemories('test query');
38
+ ```
39
+
40
+ ### Option 2: OAuth Authentication (For Pre-registered Clients)
20
41
  ```ts
21
42
  import { MCPClient } from '@lanonasis/oauth-client';
22
43
 
@@ -24,10 +45,10 @@ const client = new MCPClient({
24
45
  clientId: 'your_oauth_client_id',
25
46
  authBaseUrl: 'https://auth.lanonasis.com',
26
47
  mcpEndpoint: 'wss://mcp.lanonasis.com',
27
- scope: 'mcp:read mcp:write api_keys:manage'
48
+ scope: 'memories:read memories:write memories:delete profile' // default if omitted
28
49
  });
29
50
 
30
- await client.connect(); // handles auth, refresh, and MCP connect
51
+ await client.connect(); // Triggers OAuth flow, handles refresh
31
52
  ```
32
53
 
33
54
  ### Terminal OAuth flow
@@ -74,12 +95,26 @@ const hashed = await apiKeys.getApiKey(); // returns sha256 hex digest
74
95
  ```
75
96
 
76
97
  ## Configuration
98
+
99
+ ### API Key Mode
100
+ - `apiKey` (required): Your dashboard-generated API key (starts with `lano_`).
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.
103
+
104
+ ### OAuth Mode
77
105
  - `clientId` (required): OAuth client id issued by Lanonasis Auth.
78
106
  - `authBaseUrl` (optional): defaults to `https://auth.lanonasis.com`.
79
107
  - `mcpEndpoint` (optional): defaults to `wss://mcp.lanonasis.com` and can also be `https://...` for SSE.
80
- - `scope` (optional): defaults to `mcp:read mcp:write api_keys:manage`.
108
+ - `scope` (optional): defaults to `memories:read memories:write memories:delete profile`.
81
109
  - `autoRefresh` (MCPClient): refresh tokens 5 minutes before expiry (default `true`).
82
110
 
111
+ **Note**: Auth mode is automatically detected - if you provide `apiKey`, it uses API key authentication. If you provide `clientId`, it uses OAuth.
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
+
83
118
  ## Publishing (maintainers)
84
119
  1) Build artifacts: `npm install && npm run build`
85
120
  2) Verify contents: ensure `dist`, `README.md`, `LICENSE` are present.
@@ -0,0 +1,208 @@
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
+ /**
95
+ * Browser-only token storage that avoids Node/Electron dependencies.
96
+ * Tokens are encrypted with Web Crypto and stored in localStorage.
97
+ */
98
+ declare class TokenStorageWeb implements TokenStorageAdapter {
99
+ private readonly storageKey;
100
+ private readonly webEncryptionKeyStorage;
101
+ store(tokens: TokenResponse): Promise<void>;
102
+ retrieve(): Promise<TokenResponse | null>;
103
+ clear(): Promise<void>;
104
+ isTokenExpired(tokens: TokenResponse & {
105
+ issued_at?: number;
106
+ }): boolean;
107
+ private encrypt;
108
+ private decrypt;
109
+ private getWebEncryptionKey;
110
+ private base64Encode;
111
+ private base64Decode;
112
+ }
113
+
114
+ /**
115
+ * API Key Storage Service
116
+ * Secure multi-platform API key storage with encryption
117
+ * Supports Node.js, Electron, Web, and Mobile environments
118
+ */
119
+ interface ApiKeyData {
120
+ apiKey: string;
121
+ organizationId?: string;
122
+ userId?: string;
123
+ environment?: 'development' | 'staging' | 'production';
124
+ createdAt?: string;
125
+ expiresAt?: string;
126
+ metadata?: Record<string, unknown>;
127
+ }
128
+ declare class ApiKeyStorage {
129
+ private readonly storageKey;
130
+ private readonly legacyConfigKey;
131
+ private readonly webEncryptionKeyStorage;
132
+ private keytar;
133
+ private migrationCompleted;
134
+ constructor();
135
+ /**
136
+ * Initialize and migrate from legacy storage if needed
137
+ */
138
+ initialize(): Promise<void>;
139
+ /**
140
+ * Store API key securely
141
+ */
142
+ store(data: ApiKeyData): Promise<void>;
143
+ /**
144
+ * Retrieve API key from secure storage
145
+ */
146
+ retrieve(): Promise<ApiKeyData | null>;
147
+ /**
148
+ * Get just the API key string (convenience method)
149
+ */
150
+ getApiKey(): Promise<string | null>;
151
+ /**
152
+ * Check if API key exists
153
+ */
154
+ hasApiKey(): Promise<boolean>;
155
+ /**
156
+ * Clear API key from storage
157
+ */
158
+ clear(): Promise<void>;
159
+ /**
160
+ * Check if API key is expired
161
+ */
162
+ isExpired(data: ApiKeyData): boolean;
163
+ /**
164
+ * Update API key metadata without changing the key itself
165
+ */
166
+ updateMetadata(metadata: Record<string, unknown>): Promise<void>;
167
+ /**
168
+ * Migrate from legacy configuration storage
169
+ */
170
+ private migrateFromLegacyIfNeeded;
171
+ private storeToFile;
172
+ private retrieveFromFile;
173
+ private deleteFile;
174
+ private retrieveLegacyFromFile;
175
+ private deleteLegacyFile;
176
+ private getFileEncryptionKey;
177
+ private encrypt;
178
+ private decrypt;
179
+ private getWebEncryptionKey;
180
+ private isNode;
181
+ private isElectron;
182
+ private isMobile;
183
+ private base64Encode;
184
+ private base64Decode;
185
+ /**
186
+ * Normalize API keys to a SHA-256 hex digest.
187
+ * Accepts pre-hashed input and lowercases it to prevent double hashing.
188
+ */
189
+ private normalizeApiKey;
190
+ }
191
+
192
+ /**
193
+ * Browser-only API key storage using Web Crypto + localStorage.
194
+ */
195
+ declare class ApiKeyStorageWeb {
196
+ private readonly storageKey;
197
+ private readonly webEncryptionKeyStorage;
198
+ store(data: ApiKeyData): Promise<void>;
199
+ retrieve(): Promise<ApiKeyData | null>;
200
+ clear(): Promise<void>;
201
+ private encrypt;
202
+ private decrypt;
203
+ private getWebEncryptionKey;
204
+ private base64Encode;
205
+ private base64Decode;
206
+ }
207
+
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 };
@@ -0,0 +1,208 @@
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
+ /**
95
+ * Browser-only token storage that avoids Node/Electron dependencies.
96
+ * Tokens are encrypted with Web Crypto and stored in localStorage.
97
+ */
98
+ declare class TokenStorageWeb implements TokenStorageAdapter {
99
+ private readonly storageKey;
100
+ private readonly webEncryptionKeyStorage;
101
+ store(tokens: TokenResponse): Promise<void>;
102
+ retrieve(): Promise<TokenResponse | null>;
103
+ clear(): Promise<void>;
104
+ isTokenExpired(tokens: TokenResponse & {
105
+ issued_at?: number;
106
+ }): boolean;
107
+ private encrypt;
108
+ private decrypt;
109
+ private getWebEncryptionKey;
110
+ private base64Encode;
111
+ private base64Decode;
112
+ }
113
+
114
+ /**
115
+ * API Key Storage Service
116
+ * Secure multi-platform API key storage with encryption
117
+ * Supports Node.js, Electron, Web, and Mobile environments
118
+ */
119
+ interface ApiKeyData {
120
+ apiKey: string;
121
+ organizationId?: string;
122
+ userId?: string;
123
+ environment?: 'development' | 'staging' | 'production';
124
+ createdAt?: string;
125
+ expiresAt?: string;
126
+ metadata?: Record<string, unknown>;
127
+ }
128
+ declare class ApiKeyStorage {
129
+ private readonly storageKey;
130
+ private readonly legacyConfigKey;
131
+ private readonly webEncryptionKeyStorage;
132
+ private keytar;
133
+ private migrationCompleted;
134
+ constructor();
135
+ /**
136
+ * Initialize and migrate from legacy storage if needed
137
+ */
138
+ initialize(): Promise<void>;
139
+ /**
140
+ * Store API key securely
141
+ */
142
+ store(data: ApiKeyData): Promise<void>;
143
+ /**
144
+ * Retrieve API key from secure storage
145
+ */
146
+ retrieve(): Promise<ApiKeyData | null>;
147
+ /**
148
+ * Get just the API key string (convenience method)
149
+ */
150
+ getApiKey(): Promise<string | null>;
151
+ /**
152
+ * Check if API key exists
153
+ */
154
+ hasApiKey(): Promise<boolean>;
155
+ /**
156
+ * Clear API key from storage
157
+ */
158
+ clear(): Promise<void>;
159
+ /**
160
+ * Check if API key is expired
161
+ */
162
+ isExpired(data: ApiKeyData): boolean;
163
+ /**
164
+ * Update API key metadata without changing the key itself
165
+ */
166
+ updateMetadata(metadata: Record<string, unknown>): Promise<void>;
167
+ /**
168
+ * Migrate from legacy configuration storage
169
+ */
170
+ private migrateFromLegacyIfNeeded;
171
+ private storeToFile;
172
+ private retrieveFromFile;
173
+ private deleteFile;
174
+ private retrieveLegacyFromFile;
175
+ private deleteLegacyFile;
176
+ private getFileEncryptionKey;
177
+ private encrypt;
178
+ private decrypt;
179
+ private getWebEncryptionKey;
180
+ private isNode;
181
+ private isElectron;
182
+ private isMobile;
183
+ private base64Encode;
184
+ private base64Decode;
185
+ /**
186
+ * Normalize API keys to a SHA-256 hex digest.
187
+ * Accepts pre-hashed input and lowercases it to prevent double hashing.
188
+ */
189
+ private normalizeApiKey;
190
+ }
191
+
192
+ /**
193
+ * Browser-only API key storage using Web Crypto + localStorage.
194
+ */
195
+ declare class ApiKeyStorageWeb {
196
+ private readonly storageKey;
197
+ private readonly webEncryptionKeyStorage;
198
+ store(data: ApiKeyData): Promise<void>;
199
+ retrieve(): Promise<ApiKeyData | null>;
200
+ clear(): Promise<void>;
201
+ private encrypt;
202
+ private decrypt;
203
+ private getWebEncryptionKey;
204
+ private base64Encode;
205
+ private base64Decode;
206
+ }
207
+
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 };