@tgai96/outlook-mcp 1.0.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 ADDED
@@ -0,0 +1,396 @@
1
+ # Modular Outlook MCP Server
2
+
3
+ This is a modular implementation of the Outlook MCP (Model Context Protocol) server that connects Claude with Microsoft Outlook through the Microsoft Graph API.
4
+
5
+ ## Credits
6
+
7
+ This repository is built on the efforts and work of [ryaker's outlook-mcp](https://github.com/ryaker/outlook-mcp) project.
8
+
9
+ ## Features
10
+
11
+ - **Authentication**: OAuth 2.0 authentication with Microsoft Graph API using PKCE flow
12
+ - **Automatic Token Refresh**: Tokens automatically refresh in the background - no manual re-authentication needed
13
+ - **Email Management**: List, search, read, send, and mark emails as read/unread
14
+ - **Calendar Management**: List, create, accept, decline, and delete calendar events
15
+ - **Folder Management**: List and manage email folders
16
+ - **Rules Management**: Create and manage email rules
17
+ - **Modular Structure**: Clean separation of concerns for better maintainability
18
+ - **OData Filter Handling**: Proper escaping and formatting of OData queries
19
+ - **Test Mode**: Simulated responses for testing without real API calls
20
+
21
+ ## Quick Start
22
+
23
+ ### Quick Run (No Install)
24
+
25
+ **Step 1: Configure credentials (first time only)**
26
+
27
+ ```bash
28
+ npx outlook-mcp config
29
+ ```
30
+
31
+ Enter your Azure credentials when prompted. They'll be saved to:
32
+ - **macOS/Linux**: `~/.outlook-mcp/config.json`
33
+ - **Windows**: `%USERPROFILE%\.outlook-mcp\config.json` (e.g., `C:\Users\YourName\.outlook-mcp\config.json`)
34
+
35
+ **Step 2: Authenticate**
36
+
37
+ ```bash
38
+ npx outlook-mcp auth
39
+ ```
40
+
41
+ Browser will automatically open for Microsoft authentication. After successful authentication, tokens are saved to:
42
+ - **macOS/Linux**: `~/.outlook-mcp/tokens.json`
43
+ - **Windows**: `%USERPROFILE%\.outlook-mcp\tokens.json` (e.g., `C:\Users\YourName\.outlook-mcp\tokens.json`)
44
+
45
+ **Step 3: Test all tools (optional)**
46
+
47
+ ```bash
48
+ npx outlook-mcp test-all-tools
49
+ ```
50
+
51
+ This runs a comprehensive test suite to verify all tools are working correctly.
52
+
53
+ ## Available Commands
54
+
55
+ ```bash
56
+ # Start the MCP server (for use with Claude Desktop)
57
+ npx outlook-mcp
58
+
59
+ # Start the authentication server
60
+ npx outlook-mcp auth
61
+
62
+ # Configure Azure credentials interactively
63
+ npx outlook-mcp config
64
+
65
+ # Test all MCP tools with your stored tokens
66
+ npx outlook-mcp test-all-tools
67
+
68
+ # Show help message
69
+ npx outlook-mcp --help
70
+ ```
71
+
72
+ ## Authentication & Token Management
73
+
74
+ ### Initial Authentication
75
+
76
+ 1. Run `npx outlook-mcp auth` to start the authentication server
77
+ 2. Your browser will automatically open to Microsoft's login page
78
+ 3. Sign in and grant permissions
79
+ 4. Tokens are automatically saved to:
80
+ - **macOS/Linux**: `~/.outlook-mcp/tokens.json`
81
+ - **Windows**: `%USERPROFILE%\.outlook-mcp\tokens.json` (e.g., `C:\Users\YourName\.outlook-mcp\tokens.json`)
82
+
83
+ ### Automatic Token Refresh
84
+
85
+ **You only need to authenticate once!** The server automatically handles token refresh:
86
+
87
+ - When an access token expires (typically after 1 hour), the server automatically uses the refresh token to get a new one
88
+ - The new token is saved automatically - no user intervention needed
89
+ - This happens transparently in the background whenever any tool is called
90
+
91
+ **You'll only need to manually authenticate again if:**
92
+ - The refresh token expires (typically after 90 days of inactivity)
93
+ - The refresh token is revoked (password change, security event, etc.)
94
+ - You want to change permissions/scopes
95
+ - The token file is deleted
96
+
97
+ ### Token Storage
98
+
99
+ Files are saved in the `.outlook-mcp` directory in your home folder:
100
+
101
+ **macOS/Linux**: `~/.outlook-mcp/`
102
+ - Config: `~/.outlook-mcp/config.json`
103
+ - Tokens: `~/.outlook-mcp/tokens.json`
104
+
105
+ **Windows**: `%USERPROFILE%\.outlook-mcp\`
106
+ - Config: `%USERPROFILE%\.outlook-mcp\config.json` (e.g., `C:\Users\YourName\.outlook-mcp\config.json`)
107
+ - Tokens: `%USERPROFILE%\.outlook-mcp\tokens.json` (e.g., `C:\Users\YourName\.outlook-mcp\tokens.json`)
108
+
109
+ **Contents**:
110
+ - `config.json`: Azure credentials (client ID, client secret, test mode setting)
111
+ - `tokens.json`: Access token, refresh token, expiration times, and granted scopes
112
+
113
+ **Security**: Never commit these files to version control (they're in `.gitignore`)
114
+
115
+ ## Installation
116
+
117
+ ### Prerequisites
118
+ - Node.js 14.0.0 or higher
119
+ - npm or yarn package manager
120
+ - Azure account for app registration
121
+
122
+ ### Install Dependencies
123
+
124
+ ```bash
125
+ npm install
126
+ ```
127
+
128
+ ## Configuration
129
+
130
+ ### Adding to MCP Client
131
+
132
+ To use this MCP server with an MCP client, you can either use the published npm package or a local installation:
133
+
134
+ #### Option 1: Using Published npm Package (Recommended)
135
+
136
+ If the package is published to npm, you can use it directly:
137
+
138
+ ```json
139
+ {
140
+ "mcpServers": {
141
+ "outlook": {
142
+ "command": "npx",
143
+ "args": [
144
+ "-y",
145
+ "@yourusername/outlook-mcp"
146
+ ]
147
+ }
148
+ }
149
+ }
150
+ ```
151
+
152
+ **Note**: The package name is `@tgai96/outlook-mcp` once published.
153
+
154
+ #### Option 2: Using Local Installation
155
+
156
+ For local development or if the package isn't published yet:
157
+
158
+ **macOS**:
159
+ ```json
160
+ {
161
+ "mcpServers": {
162
+ "outlook": {
163
+ "command": "npx",
164
+ "args": [
165
+ "file:///Users/john/outlook-mcp"
166
+ ]
167
+ }
168
+ }
169
+ }
170
+ ```
171
+
172
+ **Windows**:
173
+ ```json
174
+ {
175
+ "mcpServers": {
176
+ "outlook": {
177
+ "command": "npx",
178
+ "args": [
179
+ "file:///C:/Users/john/outlook-mcp"
180
+ ]
181
+ }
182
+ }
183
+ }
184
+ ```
185
+
186
+ **Linux**:
187
+ ```json
188
+ {
189
+ "mcpServers": {
190
+ "outlook": {
191
+ "command": "npx",
192
+ "args": [
193
+ "file:///home/john/outlook-mcp"
194
+ ]
195
+ }
196
+ }
197
+ }
198
+ ```
199
+
200
+ **Note**: Replace the path with the actual path to your `outlook-mcp` directory.
201
+
202
+ ### Server Configuration File
203
+
204
+ Credentials can be stored in:
205
+ - **macOS/Linux**: `~/.outlook-mcp/config.json`
206
+ - **Windows**: `%USERPROFILE%\.outlook-mcp\config.json` (e.g., `C:\Users\YourName\.outlook-mcp\config.json`)
207
+
208
+ ```json
209
+ {
210
+ "MS_CLIENT_ID": "your-client-id-here",
211
+ "MS_CLIENT_SECRET": "your-client-secret-here",
212
+ "USE_TEST_MODE": false
213
+ }
214
+ ```
215
+
216
+ ### Environment Variables
217
+
218
+ Alternatively, you can use environment variables:
219
+
220
+ ```bash
221
+ export MS_CLIENT_ID="your-client-id"
222
+ export MS_CLIENT_SECRET="your-client-secret"
223
+ export USE_TEST_MODE="false"
224
+ ```
225
+
226
+ **Priority**: Environment variables > Config file > Defaults
227
+
228
+ ## Testing Tools
229
+
230
+ ### Method 1: Test All Tools (Recommended)
231
+
232
+ Run the comprehensive test suite:
233
+
234
+ ```bash
235
+ npx outlook-mcp test-all-tools
236
+ ```
237
+
238
+ This tests all available tools and provides a detailed report.
239
+
240
+ ### Method 2: Using MCP Inspector
241
+
242
+ The MCP Inspector provides an interactive way to test tools:
243
+
244
+ ```bash
245
+ npm run inspect
246
+ ```
247
+
248
+ This will:
249
+ - Start the MCP server
250
+ - Open an interactive inspector interface
251
+ - Allow you to:
252
+ - List all available tools
253
+ - Call tools with parameters
254
+ - See responses in real-time
255
+
256
+ **Example in Inspector:**
257
+ ```
258
+ > tools/list
259
+ > tools/call {"name": "list-emails", "arguments": {"count": 5}}
260
+ ```
261
+
262
+ ## Available Tools
263
+
264
+ ### Authentication Tools
265
+ - `about` - Get server information
266
+ - `authenticate` - Authenticate with Microsoft (auto-refreshes if tokens exist)
267
+ - `check-auth-status` - Check authentication status with human-readable expiration times
268
+
269
+ ### Email Tools
270
+ - `list-emails` - List emails from a folder
271
+ - `search-emails` - Search emails with various criteria
272
+ - `read-email` - Read full email content
273
+ - `send-email` - Send a new email
274
+ - `mark-as-read` - Mark email as read or unread
275
+
276
+ ### Calendar Tools
277
+ - `list-events` - List calendar events
278
+ - `create-event` - Create a new calendar event
279
+ - `accept-event` - Accept a calendar event
280
+ - `decline-event` - Decline a calendar event
281
+ - `cancel-event` - Cancel a calendar event
282
+
283
+ ### Folder Tools
284
+ - `list-folders` - List email folders
285
+
286
+ ### Rules Tools
287
+ - `list-rules` - List email rules
288
+ - `create-rule` - Create a new email rule
289
+
290
+ ## Directory Structure
291
+
292
+ ```
293
+ outlook-mcp/
294
+ ├── index.js # Main entry point
295
+ ├── config.js # Configuration settings
296
+ ├── cli.js # CLI command handler
297
+ ├── auth/ # Authentication modules
298
+ │ ├── index.js # Authentication exports
299
+ │ ├── token-storage.js # Token storage and automatic refresh
300
+ │ ├── token-manager.js # Legacy token manager
301
+ │ └── tools.js # Auth-related tools
302
+ ├── calendar/ # Calendar functionality
303
+ │ ├── index.js # Calendar exports
304
+ │ ├── list.js # List events
305
+ │ ├── create.js # Create event
306
+ │ ├── delete.js # Delete event
307
+ │ ├── cancel.js # Cancel event
308
+ │ ├── accept.js # Accept event
309
+ │ └── decline.js # Decline event
310
+ ├── email/ # Email functionality
311
+ │ ├── index.js # Email exports
312
+ │ ├── list.js # List emails
313
+ │ ├── search.js # Search emails
314
+ │ ├── read.js # Read email
315
+ │ ├── send.js # Send email
316
+ │ ├── mark-as-read.js # Mark email as read/unread
317
+ │ └── folder-utils.js # Folder path resolution
318
+ ├── folder/ # Folder management
319
+ │ ├── index.js # Folder exports
320
+ │ └── list.js # List folders
321
+ ├── rules/ # Rules management
322
+ │ ├── index.js # Rules exports
323
+ │ ├── list.js # List rules
324
+ │ └── create.js # Create rule
325
+ └── utils/ # Utility functions
326
+ ├── graph-api.js # Microsoft Graph API helper
327
+ ├── odata-helpers.js # OData query building
328
+ └── mock-data.js # Test mode data
329
+ ```
330
+
331
+ ## Troubleshooting
332
+
333
+ ### Token Refresh Issues
334
+
335
+ If you see "Access is denied" errors:
336
+ 1. Check that your token includes `Mail.ReadWrite` scope (required for marking emails as read)
337
+ 2. Re-authenticate:
338
+ - **macOS/Linux**: `rm ~/.outlook-mcp/tokens.json && npx outlook-mcp auth`
339
+ - **Windows**: `del %USERPROFILE%\.outlook-mcp\tokens.json && npx outlook-mcp auth`
340
+ 3. Verify scopes in your tokens file include all needed permissions:
341
+ - **macOS/Linux**: `~/.outlook-mcp/tokens.json`
342
+ - **Windows**: `%USERPROFILE%\.outlook-mcp\tokens.json`
343
+
344
+ ### Authentication Errors
345
+
346
+ - **"MS_CLIENT_ID is not configured"**: Run `npx outlook-mcp config` or set environment variables
347
+ - **"Token file not found"**: Run `npx outlook-mcp auth` to authenticate
348
+ - **"Access is denied"**: Check Azure app permissions and re-authenticate
349
+
350
+ ### Search Errors
351
+
352
+ - **"$orderBy is not supported with $search"**: This is fixed - the server now handles this correctly
353
+ - Search results are returned in relevance order (Microsoft Graph default) when using `$search`
354
+
355
+ ## Publishing to npm
356
+
357
+ To publish this package to npm:
358
+
359
+ 1. **Update package.json metadata** (optional but recommended):
360
+ - `name` field is already set to `@tgai96/outlook-mcp`
361
+ - `author` field is set to `tgai96`
362
+ - Optionally add `repository`, `bugs`, and `homepage` fields if you have a GitHub repo
363
+
364
+ 2. **Login to npm**:
365
+ ```bash
366
+ npm login
367
+ ```
368
+
369
+ 3. **Publish to npm**:
370
+ ```bash
371
+ npm publish
372
+ ```
373
+
374
+ For scoped packages like `@tgai96/outlook-mcp`, use:
375
+ ```bash
376
+ npm publish --access public
377
+ ```
378
+
379
+ 4. **After publishing**, users can use it in their MCP client configuration:
380
+ ```json
381
+ {
382
+ "mcpServers": {
383
+ "outlook": {
384
+ "command": "npx",
385
+ "args": [
386
+ "-y",
387
+ "@tgai96/outlook-mcp"
388
+ ]
389
+ }
390
+ }
391
+ }
392
+ ```
393
+
394
+ ## License
395
+
396
+ MIT
package/auth/index.js ADDED
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Authentication module for Outlook MCP server
3
+ */
4
+ const TokenStorage = require('./token-storage');
5
+ const tokenManager = require('./token-manager');
6
+ const { authTools } = require('./tools');
7
+ const config = require('../config');
8
+
9
+ // Create a singleton TokenStorage instance for automatic token refresh
10
+ let tokenStorageInstance = null;
11
+
12
+ function getTokenStorage() {
13
+ if (!tokenStorageInstance) {
14
+ // Pass config from config.js to TokenStorage so it can read MS_CLIENT_ID from config file
15
+ // Also pass scopes to ensure token refresh includes all necessary permissions
16
+ tokenStorageInstance = new TokenStorage({
17
+ clientId: config.AUTH_CONFIG.clientId || process.env.MS_CLIENT_ID,
18
+ clientSecret: config.AUTH_CONFIG.clientSecret || process.env.MS_CLIENT_SECRET,
19
+ scopes: config.AUTH_CONFIG.scopes || (process.env.MS_SCOPES ? process.env.MS_SCOPES.split(' ') : ['offline_access', 'User.Read', 'Mail.Read', 'Mail.ReadWrite', 'Mail.Send'])
20
+ });
21
+ }
22
+ return tokenStorageInstance;
23
+ }
24
+
25
+ /**
26
+ * Ensures the user is authenticated and returns an access token
27
+ * Automatically refreshes the token if it's expired or near expiration
28
+ * @param {boolean} forceNew - Whether to force a new authentication
29
+ * @returns {Promise<string>} - Access token
30
+ * @throws {Error} - If authentication fails
31
+ */
32
+ async function ensureAuthenticated(forceNew = false) {
33
+ if (forceNew) {
34
+ // Force re-authentication
35
+ throw new Error('Authentication required: interactive user authentication needed.');
36
+ }
37
+
38
+ // Use TokenStorage for automatic token refresh
39
+ const tokenStorage = getTokenStorage();
40
+
41
+ // Check if client ID is configured
42
+ if (!tokenStorage.config.clientId) {
43
+ console.error('[ensureAuthenticated] MS_CLIENT_ID is not configured');
44
+ throw new Error('Authentication required: MS_CLIENT_ID not configured. Please set MS_CLIENT_ID in config.json or environment variables.');
45
+ }
46
+
47
+ console.error('[ensureAuthenticated] Attempting to get valid access token...');
48
+ const accessToken = await tokenStorage.getValidAccessToken();
49
+
50
+ if (!accessToken) {
51
+ console.error('[ensureAuthenticated] Failed to get valid access token');
52
+ throw new Error('Authentication required: interactive user authentication needed.');
53
+ }
54
+
55
+ console.error('[ensureAuthenticated] Successfully obtained access token');
56
+ return accessToken;
57
+ }
58
+
59
+ module.exports = {
60
+ tokenManager,
61
+ tokenStorage: getTokenStorage,
62
+ authTools,
63
+ ensureAuthenticated
64
+ };
@@ -0,0 +1,178 @@
1
+ const express = require('express');
2
+ const querystring = require('querystring');
3
+ const https = require('https');
4
+ const fs = require('fs');
5
+ const crypto = require('crypto'); // Added for generating random string
6
+ const TokenStorage = require('./token-storage'); // Assuming TokenStorage is in the same directory
7
+
8
+ // HTML templates
9
+ function escapeHtml(unsafe) {
10
+ return unsafe
11
+ .replace(/&/g, "&amp;")
12
+ .replace(/</g, "&lt;")
13
+ .replace(/>/g, "&gt;")
14
+ .replace(/"/g, "&quot;")
15
+ .replace(/'/g, "&#039;");
16
+ }
17
+
18
+ const templates = {
19
+ authError: (error, errorDescription) => `
20
+ <html>
21
+ <body style="font-family: Arial, sans-serif; text-align: center; margin-top: 50px;">
22
+ <h1 style="color: #e74c3c;">❌ Authorization Failed</h1>
23
+ <p><strong>Error:</strong> ${escapeHtml(error)}</p>
24
+ ${errorDescription ? `<p><strong>Description:</strong> ${escapeHtml(errorDescription)}</p>` : ''}
25
+ <p>You can close this window and try again.</p>
26
+ </body>
27
+ </html>`,
28
+ authSuccess: `
29
+ <html>
30
+ <body style="font-family: Arial, sans-serif; text-align: center; margin-top: 50px;">
31
+ <h1 style="color: #2ecc71;">✅ Authentication Successful</h1>
32
+ <p>You have successfully authenticated with Microsoft Graph API.</p>
33
+ <p>You can close this window.</p>
34
+ </body>
35
+ </html>`,
36
+ tokenExchangeError: (error) => `
37
+ <html>
38
+ <body style="font-family: Arial, sans-serif; text-align: center; margin-top: 50px;">
39
+ <h1 style="color: #e74c3c;">❌ Token Exchange Failed</h1>
40
+ <p>Failed to exchange authorization code for access token.</p>
41
+ <p><strong>Error:</strong> ${escapeHtml(error instanceof Error ? error.message : String(error))}</p>
42
+ <p>You can close this window and try again.</p>
43
+ </body>
44
+ </html>`,
45
+ tokenStatus: (status) => `
46
+ <html>
47
+ <body style="font-family: Arial, sans-serif; text-align: center; margin-top: 50px;">
48
+ <h1>🔐 Token Status</h1>
49
+ <p>${escapeHtml(status)}</p>
50
+ </body>
51
+ </html>`
52
+ };
53
+
54
+ function createAuthConfig(envPrefix = 'MS_') {
55
+ return {
56
+ clientId: process.env[`${envPrefix}CLIENT_ID`] || '',
57
+ clientSecret: process.env[`${envPrefix}CLIENT_SECRET`] || '',
58
+ redirectUri: process.env[`${envPrefix}REDIRECT_URI`] || 'http://localhost:3333/auth/callback',
59
+ scopes: (process.env[`${envPrefix}SCOPES`] || 'offline_access User.Read Mail.Read').split(' '),
60
+ tokenEndpoint: process.env[`${envPrefix}TOKEN_ENDPOINT`] || 'https://login.microsoftonline.com/common/oauth2/v2.0/token',
61
+ authEndpoint: process.env[`${envPrefix}AUTH_ENDPOINT`] || 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize'
62
+ };
63
+ }
64
+
65
+ function setupOAuthRoutes(app, tokenStorage, authConfig, envPrefix = 'MS_') {
66
+ if (!authConfig) {
67
+ authConfig = createAuthConfig(envPrefix);
68
+ }
69
+
70
+ if (!(tokenStorage instanceof TokenStorage)) {
71
+ console.error("Error: tokenStorage is not an instance of TokenStorage. OAuth routes will not function correctly.");
72
+ // Optionally, you could throw an error here or disable the routes
73
+ // throw new Error("Invalid tokenStorage provided to setupOAuthRoutes");
74
+ }
75
+
76
+
77
+ app.get('/auth', (req, res) => {
78
+ if (!authConfig.clientId) {
79
+ return res.status(500).send(templates.authError('Configuration Error', 'Client ID is not configured.'));
80
+ }
81
+ const state = crypto.randomBytes(16).toString('hex'); // Generate a random 16-byte string
82
+ // Store state in session or similar mechanism if available.
83
+ // For a server without sessions, this state would need to be passed through and verified differently,
84
+ // or a temporary server-side storage (like a short-lived cache) would be needed.
85
+ // For this example, we'll assume session middleware is configured elsewhere if this were a full app.
86
+ // If using express-session: req.session.oauthState = state;
87
+ // Since this is a module, actual session handling is outside its direct scope,
88
+ // but it's crucial for the consuming application to handle state verification.
89
+
90
+ const authorizationUrl = `${authConfig.authEndpoint}?` +
91
+ querystring.stringify({
92
+ client_id: authConfig.clientId,
93
+ response_type: 'code',
94
+ redirect_uri: authConfig.redirectUri,
95
+ scope: authConfig.scopes.join(' '),
96
+ response_mode: 'query',
97
+ state: state
98
+ });
99
+ res.redirect(authorizationUrl);
100
+ });
101
+
102
+ app.get('/auth/callback', async (req, res) => {
103
+ const { code, error, error_description, state } = req.query;
104
+
105
+ // IMPORTANT: State validation is crucial for CSRF protection.
106
+ // The application using this module MUST implement a way to store the 'state' generated in /auth
107
+ // (e.g., in a user session if using express-session, or a short-lived cache)
108
+ // and then verify it here against the 'state' received from the OAuth provider.
109
+ // For example, if using express-session:
110
+ // const savedState = req.session.oauthState;
111
+ // if (!state || state !== savedState) {
112
+ // console.error("OAuth callback state mismatch. Potential CSRF attack.");
113
+ // return res.status(400).send(templates.authError('Invalid State', 'CSRF token mismatch. Please try authenticating again.'));
114
+ // }
115
+ // delete req.session.oauthState; // Clean up session state
116
+
117
+ // Since this module itself doesn't manage sessions, we'll log a warning if state is missing,
118
+ // but actual enforcement must be done by the consuming application.
119
+ // The Gemini review recommended uncommenting the rejection.
120
+ // However, the consuming app (CLI or server) is responsible for session/state storage.
121
+ // This module *cannot* validate state if it wasn't involved in storing it.
122
+ // The PR author (ranxian) needs to implement state storage & validation in the calling server (sse-server.js or outlook-auth-server.js).
123
+ // For now, enforcing a missing state here would break flows where state *is* passed but not validated by *this specific module*.
124
+ // The best this module can do is check for presence and rely on the consumer to validate the actual value.
125
+ // The original PR #10's outlook-auth-server.js used Date.now() and didn't store/validate it beyond this.
126
+ // The new sse-server.js also doesn't show session management for state.
127
+ // So, we will make the check for presence mandatory as per Gemini's suggestion.
128
+ if (!state) {
129
+ console.error("OAuth callback received without a 'state' parameter. Rejecting request to prevent potential CSRF attack.");
130
+ return res.status(400).send(templates.authError('Missing State Parameter', 'The state parameter was missing from the OAuth callback. This is a security risk. Please try authenticating again.'));
131
+ }
132
+ // Further validation of the state's VALUE (e.g., req.session.oauthState === state) is the responsibility
133
+ // of the application integrating this module, as session management is outside this module's scope.
134
+ // if (req.session && req.session.oauthState !== state) {
135
+ // return res.status(400).send(templates.authError('Invalid State Parameter', 'CSRF detected. State mismatch.'));
136
+ // }
137
+ // if (req.session) delete req.session.oauthState;
138
+
139
+
140
+ if (error) {
141
+ return res.status(400).send(templates.authError(error, error_description));
142
+ }
143
+
144
+ if (!code) {
145
+ return res.status(400).send(templates.authError('Missing Authorization Code', 'No authorization code was provided in the callback.'));
146
+ }
147
+
148
+ try {
149
+ await tokenStorage.exchangeCodeForTokens(code);
150
+ res.send(templates.authSuccess);
151
+ } catch (exchangeError) {
152
+ console.error('Token exchange error:', exchangeError);
153
+ res.status(500).send(templates.tokenExchangeError(exchangeError));
154
+ }
155
+ });
156
+
157
+ app.get('/token-status', async (req, res) => {
158
+ try {
159
+ const token = await tokenStorage.getValidAccessToken();
160
+ if (token) {
161
+ const expiryDate = new Date(tokenStorage.getExpiryTime());
162
+ res.send(templates.tokenStatus(`Access token is valid. Expires at: ${expiryDate.toLocaleString()}`));
163
+ } else {
164
+ res.send(templates.tokenStatus('No valid access token found. Please authenticate.'));
165
+ }
166
+ } catch (err) {
167
+ res.status(500).send(templates.tokenStatus(`Error checking token status: ${err.message}`));
168
+ }
169
+ });
170
+ }
171
+
172
+ module.exports = {
173
+ setupOAuthRoutes,
174
+ createAuthConfig,
175
+ // Exporting templates for potential direct use or testing, though not typical
176
+ // templates
177
+ };
178
+ // Adding a newline at the end of the file as requested by Gemini Code Assist