@efficy/tribecrm-mcp-server 0.4.2 → 0.5.0
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 +86 -9
- 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/callback-server.test.d.ts +2 -0
- package/dist/auth/callback-server.test.d.ts.map +1 -0
- package/dist/auth/callback-server.test.js +233 -0
- package/dist/auth/callback-server.test.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 +120 -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 +154 -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 +48 -0
- package/dist/auth/user-auth.d.ts.map +1 -0
- package/dist/auth/user-auth.js +186 -0
- package/dist/auth/user-auth.js.map +1 -0
- package/dist/auth/user-auth.test.d.ts +2 -0
- package/dist/auth/user-auth.test.d.ts.map +1 -0
- package/dist/auth/user-auth.test.js +366 -0
- package/dist/auth/user-auth.test.js.map +1 -0
- package/dist/client.d.ts +5 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +42 -4
- package/dist/client.js.map +1 -1
- package/dist/client.test.js +2 -3
- package/dist/client.test.js.map +1 -1
- package/dist/index.js +109 -6
- package/dist/index.js.map +1 -1
- package/dist/index.test.js +5 -3
- 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 +95 -0
- package/dist/scripts/authenticate.js.map +1 -0
- package/dist/types.d.ts +0 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.test.js +0 -11
- package/dist/types.test.js.map +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -42,12 +42,45 @@ The server uses **OAuth2 Client Credentials flow** for service account authentic
|
|
|
42
42
|
TRIBECRM_AUTH=app # Default - can be omitted
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
### User Authentication
|
|
46
|
-
**OAuth2 Authorization Code flow** for user-specific authentication
|
|
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
47
|
|
|
48
48
|
**Use case**: Personal AI assistants, user-specific operations
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
**✨ Recommended: Authenticate directly from Claude**
|
|
51
|
+
|
|
52
|
+
The easiest way to authenticate is directly from your conversation with Claude:
|
|
53
|
+
|
|
54
|
+
1. Configure your MCP client with `TRIBECRM_AUTH=user` (see Configuration section)
|
|
55
|
+
2. Restart Claude Desktop
|
|
56
|
+
3. Ask Claude: **"Can you authenticate with TribeCRM?"**
|
|
57
|
+
4. Claude will use the `tribecrm_authenticate` tool to generate an authentication link
|
|
58
|
+
5. Click the link, log in to TribeCRM, and accept the consent
|
|
59
|
+
6. Tokens are saved automatically - no restart needed!
|
|
60
|
+
|
|
61
|
+
**Alternative: CLI Authentication**
|
|
62
|
+
|
|
63
|
+
For testing, debugging, or pre-authentication, you can use the CLI command:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npx @efficy/tribecrm-mcp-server authenticate
|
|
67
|
+
# or if installed locally:
|
|
68
|
+
npm run authenticate
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Your browser will open for TribeCRM login, and tokens will be saved to `~/.tribecrm/tokens.json`
|
|
72
|
+
|
|
73
|
+
**Features**:
|
|
74
|
+
- ✨ **No terminal needed** - authenticate directly from Claude
|
|
75
|
+
- 🔄 Automatic token refresh (tokens valid for 24 hours)
|
|
76
|
+
- 💾 Secure local token storage (`~/.tribecrm/tokens.json`)
|
|
77
|
+
- ⚡ No restart required after authentication
|
|
78
|
+
- ✅ Standard OAuth2 Authorization Code flow
|
|
79
|
+
- 🔐 User-specific access rights respected
|
|
80
|
+
|
|
81
|
+
**Requirements**:
|
|
82
|
+
- Both `TRIBECRM_CLIENT_ID` and `TRIBECRM_CLIENT_SECRET` are required for the authentication flow
|
|
83
|
+
- Redirect URI must be pre-registered in your OAuth client configuration (default: `http://localhost:3001/callback`)
|
|
51
84
|
|
|
52
85
|
## 📋 Prerequisites
|
|
53
86
|
|
|
@@ -91,7 +124,6 @@ Add to your Claude Desktop config file:
|
|
|
91
124
|
"TRIBECRM_AUTH_URL": "https://auth.tribecrm.nl",
|
|
92
125
|
"TRIBECRM_CLIENT_ID": "your_client_id",
|
|
93
126
|
"TRIBECRM_CLIENT_SECRET": "your_client_secret",
|
|
94
|
-
"TRIBECRM_ORGANIZATION_ID": "your_org_id",
|
|
95
127
|
"TRIBECRM_MODE": "read-only",
|
|
96
128
|
"TRIBECRM_AUTH": "app"
|
|
97
129
|
}
|
|
@@ -100,7 +132,9 @@ Add to your Claude Desktop config file:
|
|
|
100
132
|
}
|
|
101
133
|
```
|
|
102
134
|
|
|
103
|
-
**Note**:
|
|
135
|
+
**Note**:
|
|
136
|
+
- Set `TRIBECRM_MODE` to `read-write` if you need to create, update, or delete entities.
|
|
137
|
+
- For user authentication, set `TRIBECRM_AUTH` to `user` and optionally add `TRIBECRM_REDIRECT_URI` (defaults to `http://localhost:3000/callback`).
|
|
104
138
|
|
|
105
139
|
#### Using Local Installation
|
|
106
140
|
|
|
@@ -115,7 +149,6 @@ Add to your Claude Desktop config file:
|
|
|
115
149
|
"TRIBECRM_AUTH_URL": "https://auth.tribecrm.nl",
|
|
116
150
|
"TRIBECRM_CLIENT_ID": "your_client_id",
|
|
117
151
|
"TRIBECRM_CLIENT_SECRET": "your_client_secret",
|
|
118
|
-
"TRIBECRM_ORGANIZATION_ID": "your_org_id",
|
|
119
152
|
"TRIBECRM_MODE": "read-only",
|
|
120
153
|
"TRIBECRM_AUTH": "app"
|
|
121
154
|
}
|
|
@@ -137,17 +170,61 @@ Add to your Claude Desktop config file:
|
|
|
137
170
|
- `TRIBECRM_API_URL` (required): Your TribeCRM API URL (e.g., https://api.tribecrm.nl or https://api-staging.tribecrm.nl)
|
|
138
171
|
- `TRIBECRM_AUTH_URL` (required): Your TribeCRM OAuth2 authentication URL (e.g., https://auth.tribecrm.nl or https://auth-staging.tribecrm.nl)
|
|
139
172
|
- `TRIBECRM_CLIENT_ID` (required): OAuth2 Client ID
|
|
140
|
-
- `TRIBECRM_CLIENT_SECRET` (required): OAuth2 Client Secret
|
|
141
|
-
- `
|
|
173
|
+
- `TRIBECRM_CLIENT_SECRET` (required for both auth methods): OAuth2 Client Secret
|
|
174
|
+
- Required when using `TRIBECRM_AUTH=app` (default) - for Client Credentials flow
|
|
175
|
+
- Required when using `TRIBECRM_AUTH=user` - for Authorization Code token exchange
|
|
142
176
|
- `TRIBECRM_MODE` (optional): Server operation mode - `read-only` (default) or `read-write`
|
|
143
177
|
- `read-only`: Only allows queries and data retrieval (get, query operations). Write tools are hidden.
|
|
144
178
|
- `read-write`: Allows full access including create, update, and delete operations.
|
|
145
179
|
- `TRIBECRM_AUTH` (optional): Authentication method - `app` (default) or `user`
|
|
146
180
|
- `app`: OAuth2 Client Credentials flow for service account authentication
|
|
147
|
-
- `user`: OAuth2 Authorization Code flow
|
|
181
|
+
- `user`: OAuth2 Authorization Code flow for user-specific authentication
|
|
182
|
+
- `TRIBECRM_REDIRECT_URI` (optional): Redirect URI for user authentication (only needed when `TRIBECRM_AUTH=user`)
|
|
183
|
+
- Default: `http://localhost:3001/callback`
|
|
184
|
+
- Must match the redirect URI configured in your TribeCRM OAuth application
|
|
148
185
|
|
|
149
186
|
## 📚 Available Tools
|
|
150
187
|
|
|
188
|
+
### Authentication Operations
|
|
189
|
+
|
|
190
|
+
#### `tribecrm_authenticate`
|
|
191
|
+
Start user authentication flow (only available when `TRIBECRM_AUTH=user`)
|
|
192
|
+
|
|
193
|
+
**Use case**: When user authentication is required but no valid tokens are found, this tool generates an authentication URL and starts a background callback server to handle the OAuth flow.
|
|
194
|
+
|
|
195
|
+
**Parameters:** None
|
|
196
|
+
|
|
197
|
+
**Returns**: Authentication URL and step-by-step instructions
|
|
198
|
+
|
|
199
|
+
**Features**:
|
|
200
|
+
- ✨ Authenticate directly from Claude without using the terminal
|
|
201
|
+
- 🔄 Background callback server automatically handles OAuth redirects
|
|
202
|
+
- 💾 Tokens saved automatically to `~/.tribecrm/tokens.json`
|
|
203
|
+
- ⚡ No restart required - tokens work immediately after authentication
|
|
204
|
+
|
|
205
|
+
**Example workflow**:
|
|
206
|
+
1. User asks: "Can you authenticate with TribeCRM?"
|
|
207
|
+
2. Claude calls `tribecrm_authenticate` tool
|
|
208
|
+
3. User clicks the provided authentication link
|
|
209
|
+
4. User logs in to TribeCRM and accepts consent
|
|
210
|
+
5. Tokens are saved automatically
|
|
211
|
+
6. User can immediately use TribeCRM tools
|
|
212
|
+
|
|
213
|
+
**Error messages**: If authentication is needed, other TribeCRM tools will display a helpful error message suggesting to use this tool or run `npm run authenticate` manually.
|
|
214
|
+
|
|
215
|
+
#### `tribecrm_get_current_employee`
|
|
216
|
+
Get information about the currently authenticated employee
|
|
217
|
+
|
|
218
|
+
**Parameters:**
|
|
219
|
+
- `expand` (string, optional): OData $expand parameter (e.g., "Person")
|
|
220
|
+
|
|
221
|
+
**Example:**
|
|
222
|
+
```json
|
|
223
|
+
{
|
|
224
|
+
"expand": "Person"
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
151
228
|
### Entity Operations
|
|
152
229
|
|
|
153
230
|
#### `tribecrm_get_entity`
|
|
@@ -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 @@
|
|
|
1
|
+
{"version":3,"file":"callback-server.test.d.ts","sourceRoot":"","sources":["../../src/auth/callback-server.test.ts"],"names":[],"mappings":""}
|