@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.
- package/README.md +51 -3
- package/dist/auth/callback-server.d.ts +41 -0
- package/dist/auth/callback-server.d.ts.map +1 -0
- package/dist/auth/callback-server.js +247 -0
- package/dist/auth/callback-server.js.map +1 -0
- package/dist/auth/oauth-flow.d.ts +55 -0
- package/dist/auth/oauth-flow.d.ts.map +1 -0
- package/dist/auth/oauth-flow.js +106 -0
- package/dist/auth/oauth-flow.js.map +1 -0
- package/dist/auth/oauth-flow.test.d.ts +2 -0
- package/dist/auth/oauth-flow.test.d.ts.map +1 -0
- package/dist/auth/oauth-flow.test.js +160 -0
- package/dist/auth/oauth-flow.test.js.map +1 -0
- package/dist/auth/token-manager.d.ts +43 -0
- package/dist/auth/token-manager.d.ts.map +1 -0
- package/dist/auth/token-manager.js +105 -0
- package/dist/auth/token-manager.js.map +1 -0
- package/dist/auth/token-manager.test.d.ts +2 -0
- package/dist/auth/token-manager.test.d.ts.map +1 -0
- package/dist/auth/token-manager.test.js +190 -0
- package/dist/auth/token-manager.test.js.map +1 -0
- package/dist/auth/user-auth.d.ts +44 -0
- package/dist/auth/user-auth.d.ts.map +1 -0
- package/dist/auth/user-auth.js +173 -0
- package/dist/auth/user-auth.js.map +1 -0
- package/dist/client.d.ts +5 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +47 -0
- package/dist/client.js.map +1 -1
- package/dist/client.test.js +31 -0
- package/dist/client.test.js.map +1 -1
- package/dist/index.js +18 -2
- package/dist/index.js.map +1 -1
- package/dist/index.test.js +34 -0
- package/dist/index.test.js.map +1 -1
- package/dist/scripts/authenticate.d.ts +12 -0
- package/dist/scripts/authenticate.d.ts.map +1 -0
- package/dist/scripts/authenticate.js +97 -0
- package/dist/scripts/authenticate.js.map +1 -0
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.test.js +19 -0
- package/dist/types.test.js.map +1 -1
- 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**:
|
|
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, '&')
|
|
241
|
+
.replace(/</g, '<')
|
|
242
|
+
.replace(/>/g, '>')
|
|
243
|
+
.replace(/"/g, '"')
|
|
244
|
+
.replace(/'/g, ''');
|
|
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 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-flow.test.d.ts","sourceRoot":"","sources":["../../src/auth/oauth-flow.test.ts"],"names":[],"mappings":""}
|