@efficy/tribecrm-mcp-server 0.4.1 → 0.4.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.
Files changed (44) hide show
  1. package/README.md +51 -3
  2. package/dist/auth/callback-server.d.ts +41 -0
  3. package/dist/auth/callback-server.d.ts.map +1 -0
  4. package/dist/auth/callback-server.js +247 -0
  5. package/dist/auth/callback-server.js.map +1 -0
  6. package/dist/auth/oauth-flow.d.ts +55 -0
  7. package/dist/auth/oauth-flow.d.ts.map +1 -0
  8. package/dist/auth/oauth-flow.js +106 -0
  9. package/dist/auth/oauth-flow.js.map +1 -0
  10. package/dist/auth/oauth-flow.test.d.ts +2 -0
  11. package/dist/auth/oauth-flow.test.d.ts.map +1 -0
  12. package/dist/auth/oauth-flow.test.js +160 -0
  13. package/dist/auth/oauth-flow.test.js.map +1 -0
  14. package/dist/auth/token-manager.d.ts +43 -0
  15. package/dist/auth/token-manager.d.ts.map +1 -0
  16. package/dist/auth/token-manager.js +105 -0
  17. package/dist/auth/token-manager.js.map +1 -0
  18. package/dist/auth/token-manager.test.d.ts +2 -0
  19. package/dist/auth/token-manager.test.d.ts.map +1 -0
  20. package/dist/auth/token-manager.test.js +190 -0
  21. package/dist/auth/token-manager.test.js.map +1 -0
  22. package/dist/auth/user-auth.d.ts +44 -0
  23. package/dist/auth/user-auth.d.ts.map +1 -0
  24. package/dist/auth/user-auth.js +173 -0
  25. package/dist/auth/user-auth.js.map +1 -0
  26. package/dist/client.d.ts +5 -0
  27. package/dist/client.d.ts.map +1 -1
  28. package/dist/client.js +47 -0
  29. package/dist/client.js.map +1 -1
  30. package/dist/client.test.js +31 -0
  31. package/dist/client.test.js.map +1 -1
  32. package/dist/index.js +18 -2
  33. package/dist/index.js.map +1 -1
  34. package/dist/index.test.js +34 -0
  35. package/dist/index.test.js.map +1 -1
  36. package/dist/scripts/authenticate.d.ts +12 -0
  37. package/dist/scripts/authenticate.d.ts.map +1 -0
  38. package/dist/scripts/authenticate.js +97 -0
  39. package/dist/scripts/authenticate.js.map +1 -0
  40. package/dist/types.d.ts +1 -0
  41. package/dist/types.d.ts.map +1 -1
  42. package/dist/types.test.js +19 -0
  43. package/dist/types.test.js.map +1 -1
  44. package/package.json +8 -7
package/README.md CHANGED
@@ -30,6 +30,44 @@ Model Context Protocol (MCP) server for TribeCRM API integration. This server en
30
30
 
31
31
  > ⚠️ **Breaking Change in v0.4.0**: The server now defaults to read-only mode for security. If you need write access, set `TRIBECRM_MODE=read-write` in your configuration.
32
32
 
33
+ ## 🔐 Authentication Methods
34
+
35
+ ### App Authentication (Default)
36
+ The server uses **OAuth2 Client Credentials flow** for service account authentication. This is the default and recommended method for AI assistants and automated systems.
37
+
38
+ **Use case**: AI assistants, automation, backend services
39
+
40
+ **Configuration**:
41
+ ```bash
42
+ TRIBECRM_AUTH=app # Default - can be omitted
43
+ ```
44
+
45
+ ### User Authentication ✨ NEW
46
+ **OAuth2 Authorization Code flow** for user-specific authentication. This allows the MCP server to act on behalf of a specific user rather than a service account.
47
+
48
+ **Use case**: Personal AI assistants, user-specific operations
49
+
50
+ **Setup**:
51
+ 1. Run the authentication helper:
52
+ ```bash
53
+ npx @efficy/tribecrm-mcp-server authenticate
54
+ # or if installed locally:
55
+ npm run authenticate
56
+ ```
57
+
58
+ 2. Your browser will open for TribeCRM login
59
+
60
+ 3. After successful authentication, tokens are saved locally to `~/.tribecrm/tokens.json`
61
+
62
+ 4. Configure your MCP client with `TRIBECRM_AUTH=user`
63
+
64
+ **Features**:
65
+ - ✅ Browser-based OAuth consent flow
66
+ - ✅ Automatic token refresh (tokens valid for 24 hours)
67
+ - ✅ Secure local token storage
68
+ - ✅ PKCE (Proof Key for Code Exchange) for additional security
69
+ - ✅ User-specific access rights respected
70
+
33
71
  ## 📋 Prerequisites
34
72
 
35
73
  - Node.js 18 or higher
@@ -73,14 +111,17 @@ Add to your Claude Desktop config file:
73
111
  "TRIBECRM_CLIENT_ID": "your_client_id",
74
112
  "TRIBECRM_CLIENT_SECRET": "your_client_secret",
75
113
  "TRIBECRM_ORGANIZATION_ID": "your_org_id",
76
- "TRIBECRM_MODE": "read-only"
114
+ "TRIBECRM_MODE": "read-only",
115
+ "TRIBECRM_AUTH": "app"
77
116
  }
78
117
  }
79
118
  }
80
119
  }
81
120
  ```
82
121
 
83
- **Note**: Set `TRIBECRM_MODE` to `read-write` if you need to create, update, or delete entities.
122
+ **Note**:
123
+ - Set `TRIBECRM_MODE` to `read-write` if you need to create, update, or delete entities.
124
+ - For user authentication, set `TRIBECRM_AUTH` to `user` and optionally add `TRIBECRM_REDIRECT_URI` (defaults to `http://localhost:3000/callback`).
84
125
 
85
126
  #### Using Local Installation
86
127
 
@@ -96,7 +137,8 @@ Add to your Claude Desktop config file:
96
137
  "TRIBECRM_CLIENT_ID": "your_client_id",
97
138
  "TRIBECRM_CLIENT_SECRET": "your_client_secret",
98
139
  "TRIBECRM_ORGANIZATION_ID": "your_org_id",
99
- "TRIBECRM_MODE": "read-only"
140
+ "TRIBECRM_MODE": "read-only",
141
+ "TRIBECRM_AUTH": "app"
100
142
  }
101
143
  }
102
144
  }
@@ -121,6 +163,12 @@ Add to your Claude Desktop config file:
121
163
  - `TRIBECRM_MODE` (optional): Server operation mode - `read-only` (default) or `read-write`
122
164
  - `read-only`: Only allows queries and data retrieval (get, query operations). Write tools are hidden.
123
165
  - `read-write`: Allows full access including create, update, and delete operations.
166
+ - `TRIBECRM_AUTH` (optional): Authentication method - `app` (default) or `user`
167
+ - `app`: OAuth2 Client Credentials flow for service account authentication
168
+ - `user`: OAuth2 Authorization Code flow for user-specific authentication
169
+ - `TRIBECRM_REDIRECT_URI` (optional): Redirect URI for user authentication (only needed when `TRIBECRM_AUTH=user`)
170
+ - Default: `http://localhost:3000/callback`
171
+ - Must match the redirect URI configured in your TribeCRM OAuth application
124
172
 
125
173
  ## 📚 Available Tools
126
174
 
@@ -0,0 +1,41 @@
1
+ export interface CallbackResult {
2
+ code: string;
3
+ state: string;
4
+ error?: string;
5
+ }
6
+ /**
7
+ * Temporary HTTP server to handle OAuth callback
8
+ */
9
+ export declare class CallbackServer {
10
+ private server;
11
+ private port;
12
+ constructor(port?: number);
13
+ /**
14
+ * Start the callback server and wait for OAuth redirect
15
+ * @param expectedState Expected state parameter for CSRF validation
16
+ * @param timeout Timeout in milliseconds (default: 5 minutes)
17
+ * @returns Promise that resolves with authorization code
18
+ */
19
+ waitForCallback(expectedState: string, timeout?: number): Promise<CallbackResult>;
20
+ /**
21
+ * Stop the callback server
22
+ */
23
+ stop(): void;
24
+ /**
25
+ * Get port number
26
+ */
27
+ getPort(): number;
28
+ /**
29
+ * Generate success HTML page
30
+ */
31
+ private getSuccessPage;
32
+ /**
33
+ * Generate error HTML page
34
+ */
35
+ private getErrorPage;
36
+ /**
37
+ * Escape HTML to prevent XSS
38
+ */
39
+ private escapeHtml;
40
+ }
41
+ //# sourceMappingURL=callback-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"callback-server.d.ts","sourceRoot":"","sources":["../../src/auth/callback-server.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,IAAI,CAAS;gBAET,IAAI,GAAE,MAAa;IAI/B;;;;;OAKG;IACG,eAAe,CACnB,aAAa,EAAE,MAAM,EACrB,OAAO,GAAE,MAAe,GACvB,OAAO,CAAC,cAAc,CAAC;IA4F1B;;OAEG;IACH,IAAI,IAAI,IAAI;IAQZ;;OAEG;IACH,OAAO,IAAI,MAAM;IAIjB;;OAEG;IACH,OAAO,CAAC,cAAc;IAwDtB;;OAEG;IACH,OAAO,CAAC,YAAY;IAgEpB;;OAEG;IACH,OAAO,CAAC,UAAU;CAQnB"}
@@ -0,0 +1,247 @@
1
+ import http from 'http';
2
+ import { URL } from 'url';
3
+ /**
4
+ * Temporary HTTP server to handle OAuth callback
5
+ */
6
+ export class CallbackServer {
7
+ server = null;
8
+ port;
9
+ constructor(port = 3000) {
10
+ this.port = port;
11
+ }
12
+ /**
13
+ * Start the callback server and wait for OAuth redirect
14
+ * @param expectedState Expected state parameter for CSRF validation
15
+ * @param timeout Timeout in milliseconds (default: 5 minutes)
16
+ * @returns Promise that resolves with authorization code
17
+ */
18
+ async waitForCallback(expectedState, timeout = 300000) {
19
+ return new Promise((resolve, reject) => {
20
+ const timeoutId = setTimeout(() => {
21
+ this.stop();
22
+ reject(new Error('OAuth callback timeout - user did not complete authentication'));
23
+ }, timeout);
24
+ this.server = http.createServer((req, res) => {
25
+ try {
26
+ const url = new URL(req.url || '', `http://localhost:${this.port}`);
27
+ // Only handle callback path
28
+ if (url.pathname !== '/callback') {
29
+ res.writeHead(404, { 'Content-Type': 'text/html' });
30
+ res.end('<html><body><h1>404 Not Found</h1></body></html>');
31
+ return;
32
+ }
33
+ const code = url.searchParams.get('code');
34
+ const state = url.searchParams.get('state');
35
+ const error = url.searchParams.get('error');
36
+ const errorDescription = url.searchParams.get('error_description');
37
+ // Handle OAuth errors
38
+ if (error) {
39
+ const errorMsg = errorDescription || error;
40
+ res.writeHead(400, { 'Content-Type': 'text/html' });
41
+ res.end(this.getErrorPage(errorMsg));
42
+ clearTimeout(timeoutId);
43
+ this.stop();
44
+ reject(new Error(`OAuth error: ${errorMsg}`));
45
+ return;
46
+ }
47
+ // Validate required parameters
48
+ if (!code || !state) {
49
+ res.writeHead(400, { 'Content-Type': 'text/html' });
50
+ res.end(this.getErrorPage('Missing code or state parameter'));
51
+ clearTimeout(timeoutId);
52
+ this.stop();
53
+ reject(new Error('Invalid callback: missing code or state'));
54
+ return;
55
+ }
56
+ // Validate state (CSRF protection)
57
+ if (state !== expectedState) {
58
+ res.writeHead(400, { 'Content-Type': 'text/html' });
59
+ res.end(this.getErrorPage('Invalid state parameter - possible CSRF attack'));
60
+ clearTimeout(timeoutId);
61
+ this.stop();
62
+ reject(new Error('State validation failed - possible CSRF attack'));
63
+ return;
64
+ }
65
+ // Success!
66
+ res.writeHead(200, { 'Content-Type': 'text/html' });
67
+ res.end(this.getSuccessPage());
68
+ clearTimeout(timeoutId);
69
+ this.stop();
70
+ resolve({ code, state });
71
+ }
72
+ catch (error) {
73
+ res.writeHead(500, { 'Content-Type': 'text/html' });
74
+ res.end(this.getErrorPage('Internal server error'));
75
+ clearTimeout(timeoutId);
76
+ this.stop();
77
+ reject(error);
78
+ }
79
+ });
80
+ this.server.listen(this.port, 'localhost', () => {
81
+ console.error(`Callback server listening on http://localhost:${this.port}/callback`);
82
+ });
83
+ this.server.on('error', (error) => {
84
+ clearTimeout(timeoutId);
85
+ this.stop();
86
+ if (error.code === 'EADDRINUSE') {
87
+ reject(new Error(`Port ${this.port} is already in use. Please close other applications using this port.`));
88
+ }
89
+ else {
90
+ reject(new Error(`Server error: ${error.message}`));
91
+ }
92
+ });
93
+ });
94
+ }
95
+ /**
96
+ * Stop the callback server
97
+ */
98
+ stop() {
99
+ if (this.server) {
100
+ this.server.close();
101
+ this.server = null;
102
+ console.error('Callback server stopped');
103
+ }
104
+ }
105
+ /**
106
+ * Get port number
107
+ */
108
+ getPort() {
109
+ return this.port;
110
+ }
111
+ /**
112
+ * Generate success HTML page
113
+ */
114
+ getSuccessPage() {
115
+ return `<!DOCTYPE html>
116
+ <html lang="en">
117
+ <head>
118
+ <meta charset="UTF-8">
119
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
120
+ <title>Authentication Successful</title>
121
+ <style>
122
+ body {
123
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
124
+ display: flex;
125
+ justify-content: center;
126
+ align-items: center;
127
+ min-height: 100vh;
128
+ margin: 0;
129
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
130
+ }
131
+ .container {
132
+ background: white;
133
+ padding: 3rem;
134
+ border-radius: 1rem;
135
+ box-shadow: 0 20px 60px rgba(0,0,0,0.3);
136
+ text-align: center;
137
+ max-width: 500px;
138
+ }
139
+ .icon {
140
+ font-size: 4rem;
141
+ margin-bottom: 1rem;
142
+ }
143
+ h1 {
144
+ color: #2d3748;
145
+ margin-bottom: 1rem;
146
+ }
147
+ p {
148
+ color: #4a5568;
149
+ line-height: 1.6;
150
+ }
151
+ .success {
152
+ color: #48bb78;
153
+ }
154
+ </style>
155
+ </head>
156
+ <body>
157
+ <div class="container">
158
+ <div class="icon success">✓</div>
159
+ <h1>Authentication Successful!</h1>
160
+ <p>You have successfully authenticated with TribeCRM.</p>
161
+ <p><strong>You can now close this window and return to your terminal.</strong></p>
162
+ <p style="font-size: 0.875rem; color: #718096; margin-top: 2rem;">
163
+ Your credentials have been securely stored locally.
164
+ </p>
165
+ </div>
166
+ </body>
167
+ </html>`;
168
+ }
169
+ /**
170
+ * Generate error HTML page
171
+ */
172
+ getErrorPage(errorMessage) {
173
+ return `<!DOCTYPE html>
174
+ <html lang="en">
175
+ <head>
176
+ <meta charset="UTF-8">
177
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
178
+ <title>Authentication Failed</title>
179
+ <style>
180
+ body {
181
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
182
+ display: flex;
183
+ justify-content: center;
184
+ align-items: center;
185
+ min-height: 100vh;
186
+ margin: 0;
187
+ background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
188
+ }
189
+ .container {
190
+ background: white;
191
+ padding: 3rem;
192
+ border-radius: 1rem;
193
+ box-shadow: 0 20px 60px rgba(0,0,0,0.3);
194
+ text-align: center;
195
+ max-width: 500px;
196
+ }
197
+ .icon {
198
+ font-size: 4rem;
199
+ margin-bottom: 1rem;
200
+ }
201
+ h1 {
202
+ color: #2d3748;
203
+ margin-bottom: 1rem;
204
+ }
205
+ p {
206
+ color: #4a5568;
207
+ line-height: 1.6;
208
+ }
209
+ .error {
210
+ color: #f56565;
211
+ }
212
+ .error-detail {
213
+ background: #fff5f5;
214
+ border: 1px solid #fc8181;
215
+ border-radius: 0.5rem;
216
+ padding: 1rem;
217
+ margin-top: 1rem;
218
+ font-family: monospace;
219
+ font-size: 0.875rem;
220
+ color: #c53030;
221
+ }
222
+ </style>
223
+ </head>
224
+ <body>
225
+ <div class="container">
226
+ <div class="icon error">✗</div>
227
+ <h1>Authentication Failed</h1>
228
+ <p>There was an error during authentication.</p>
229
+ <div class="error-detail">${this.escapeHtml(errorMessage)}</div>
230
+ <p style="margin-top: 2rem;">Please close this window and try again in your terminal.</p>
231
+ </div>
232
+ </body>
233
+ </html>`;
234
+ }
235
+ /**
236
+ * Escape HTML to prevent XSS
237
+ */
238
+ escapeHtml(unsafe) {
239
+ return unsafe
240
+ .replace(/&/g, '&amp;')
241
+ .replace(/</g, '&lt;')
242
+ .replace(/>/g, '&gt;')
243
+ .replace(/"/g, '&quot;')
244
+ .replace(/'/g, '&#039;');
245
+ }
246
+ }
247
+ //# sourceMappingURL=callback-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"callback-server.js","sourceRoot":"","sources":["../../src/auth/callback-server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ import { AuthToken } from '../types.js';
2
+ export interface OAuthConfig {
3
+ authUrl: string;
4
+ clientId: string;
5
+ clientSecret: string;
6
+ redirectUri: string;
7
+ organizationId?: string;
8
+ }
9
+ export interface PKCEPair {
10
+ verifier: string;
11
+ challenge: string;
12
+ }
13
+ /**
14
+ * OAuth2 Authorization Code Flow Helper
15
+ * Handles consent URL generation, PKCE, and token exchange
16
+ */
17
+ export declare class OAuthFlow {
18
+ private config;
19
+ constructor(config: OAuthConfig);
20
+ /**
21
+ * Generate PKCE code verifier and challenge
22
+ * @returns PKCEPair with verifier and challenge
23
+ */
24
+ generatePKCE(): PKCEPair;
25
+ /**
26
+ * Generate OAuth consent URL
27
+ * @param state Random state parameter for CSRF protection
28
+ * @param codeChallenge PKCE code challenge
29
+ * @returns Consent URL for user to visit
30
+ */
31
+ generateConsentUrl(state: string, codeChallenge: string): string;
32
+ /**
33
+ * Exchange authorization code for tokens
34
+ * @param code Authorization code from OAuth callback
35
+ * @param codeVerifier PKCE code verifier
36
+ * @returns Token response with access_token, refresh_token, etc.
37
+ */
38
+ exchangeCodeForTokens(code: string, codeVerifier: string): Promise<AuthToken & {
39
+ refresh_token?: string;
40
+ }>;
41
+ /**
42
+ * Refresh access token using refresh token
43
+ * @param refreshToken Refresh token from previous authentication
44
+ * @returns New token response
45
+ */
46
+ refreshAccessToken(refreshToken: string): Promise<AuthToken & {
47
+ refresh_token?: string;
48
+ }>;
49
+ /**
50
+ * Generate random state parameter for CSRF protection
51
+ * @returns Random state string
52
+ */
53
+ static generateState(): string;
54
+ }
55
+ //# sourceMappingURL=oauth-flow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-flow.d.ts","sourceRoot":"","sources":["../../src/auth/oauth-flow.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAc;gBAEhB,MAAM,EAAE,WAAW;IAI/B;;;OAGG;IACH,YAAY,IAAI,QAAQ;IAaxB;;;;;OAKG;IACH,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM;IAkBhE;;;;;OAKG;IACG,qBAAqB,CACzB,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,SAAS,GAAG;QAAE,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IA0BlD;;;;OAIG;IACG,kBAAkB,CACtB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,SAAS,GAAG;QAAE,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAwBlD;;;OAGG;IACH,MAAM,CAAC,aAAa,IAAI,MAAM;CAG/B"}
@@ -0,0 +1,106 @@
1
+ import crypto from 'crypto';
2
+ import axios from 'axios';
3
+ /**
4
+ * OAuth2 Authorization Code Flow Helper
5
+ * Handles consent URL generation, PKCE, and token exchange
6
+ */
7
+ export class OAuthFlow {
8
+ config;
9
+ constructor(config) {
10
+ this.config = config;
11
+ }
12
+ /**
13
+ * Generate PKCE code verifier and challenge
14
+ * @returns PKCEPair with verifier and challenge
15
+ */
16
+ generatePKCE() {
17
+ // Generate code verifier (43-128 characters, base64url)
18
+ const verifier = crypto.randomBytes(32).toString('base64url');
19
+ // Generate code challenge (SHA256 hash of verifier, base64url)
20
+ const challenge = crypto
21
+ .createHash('sha256')
22
+ .update(verifier)
23
+ .digest('base64url');
24
+ return { verifier, challenge };
25
+ }
26
+ /**
27
+ * Generate OAuth consent URL
28
+ * @param state Random state parameter for CSRF protection
29
+ * @param codeChallenge PKCE code challenge
30
+ * @returns Consent URL for user to visit
31
+ */
32
+ generateConsentUrl(state, codeChallenge) {
33
+ const params = new URLSearchParams({
34
+ client_id: this.config.clientId,
35
+ redirect_uri: this.config.redirectUri,
36
+ state: state,
37
+ response_type: 'code',
38
+ scope: 'read write offline',
39
+ code_challenge: codeChallenge,
40
+ code_challenge_method: 'S256',
41
+ });
42
+ if (this.config.organizationId) {
43
+ params.append('organization_id', this.config.organizationId);
44
+ }
45
+ return `${this.config.authUrl}/oauth2/auth?${params.toString()}`;
46
+ }
47
+ /**
48
+ * Exchange authorization code for tokens
49
+ * @param code Authorization code from OAuth callback
50
+ * @param codeVerifier PKCE code verifier
51
+ * @returns Token response with access_token, refresh_token, etc.
52
+ */
53
+ async exchangeCodeForTokens(code, codeVerifier) {
54
+ try {
55
+ const response = await axios.post(`${this.config.authUrl}/oauth2/token`, new URLSearchParams({
56
+ grant_type: 'authorization_code',
57
+ client_id: this.config.clientId,
58
+ client_secret: this.config.clientSecret,
59
+ redirect_uri: this.config.redirectUri,
60
+ code: code,
61
+ code_verifier: codeVerifier,
62
+ }), {
63
+ headers: {
64
+ 'Content-Type': 'application/x-www-form-urlencoded',
65
+ },
66
+ });
67
+ return response.data;
68
+ }
69
+ catch (error) {
70
+ const errorMsg = error.response?.data?.error_description || error.message;
71
+ throw new Error(`Token exchange failed: ${errorMsg}`);
72
+ }
73
+ }
74
+ /**
75
+ * Refresh access token using refresh token
76
+ * @param refreshToken Refresh token from previous authentication
77
+ * @returns New token response
78
+ */
79
+ async refreshAccessToken(refreshToken) {
80
+ try {
81
+ const response = await axios.post(`${this.config.authUrl}/oauth2/token`, new URLSearchParams({
82
+ grant_type: 'refresh_token',
83
+ client_id: this.config.clientId,
84
+ client_secret: this.config.clientSecret,
85
+ refresh_token: refreshToken,
86
+ }), {
87
+ headers: {
88
+ 'Content-Type': 'application/x-www-form-urlencoded',
89
+ },
90
+ });
91
+ return response.data;
92
+ }
93
+ catch (error) {
94
+ const errorMsg = error.response?.data?.error_description || error.message;
95
+ throw new Error(`Token refresh failed: ${errorMsg}`);
96
+ }
97
+ }
98
+ /**
99
+ * Generate random state parameter for CSRF protection
100
+ * @returns Random state string
101
+ */
102
+ static generateState() {
103
+ return crypto.randomBytes(16).toString('hex');
104
+ }
105
+ }
106
+ //# sourceMappingURL=oauth-flow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-flow.js","sourceRoot":"","sources":["../../src/auth/oauth-flow.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=oauth-flow.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-flow.test.d.ts","sourceRoot":"","sources":["../../src/auth/oauth-flow.test.ts"],"names":[],"mappings":""}