snow-flow 2.0.2 → 2.0.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 (173) hide show
  1. package/{.claude/mcp-config.json → .mcp.json} +13 -12
  2. package/.releaserc.json +47 -0
  3. package/README.md +6 -0
  4. package/package.json +1 -1
  5. package/.claude/claude-settings.json +0 -15
  6. package/.claude/commands/analysis/README.md +0 -9
  7. package/.claude/commands/automation/README.md +0 -9
  8. package/.claude/commands/github/README.md +0 -11
  9. package/.claude/commands/helpers/standard-checkpoint-hooks.sh +0 -179
  10. package/.claude/commands/hooks/README.md +0 -11
  11. package/.claude/commands/monitoring/README.md +0 -9
  12. package/.claude/commands/optimization/README.md +0 -9
  13. package/.claude/commands/training/README.md +0 -9
  14. package/.claude/commands/workflows/README.md +0 -9
  15. package/.claude/config.json +0 -36
  16. package/.claude/helpers/github-setup.sh +0 -28
  17. package/.claude/helpers/quick-start.sh +0 -19
  18. package/.claude/helpers/setup-mcp.sh +0 -18
  19. package/.claude/settings.json +0 -114
  20. package/.claude/settings.local.json +0 -33
  21. package/.claude-flow/metrics/agent-metrics.json +0 -1
  22. package/.claude-flow/metrics/performance.json +0 -9
  23. package/.claude-flow/metrics/system-metrics.json +0 -15986
  24. package/.claude-flow/metrics/task-metrics.json +0 -10
  25. package/.roo/README.md +0 -402
  26. package/.roo/mcp-list.txt +0 -257
  27. package/.roo/mcp.json +0 -32
  28. package/.roo/workflows/basic-tdd.json +0 -32
  29. package/.roomodes +0 -201
  30. package/CLAUDE.md +0 -393
  31. package/bin/snow-flow.js +0 -3
  32. package/dist/cli.js.bak +0 -2052
  33. package/dist/config/snow-flow-config.d.ts +0 -1492
  34. package/dist/config/snow-flow-config.js +0 -938
  35. package/scripts/create-env.js +0 -91
  36. package/scripts/postinstall.js +0 -30
  37. package/scripts/register-mcp-servers.js +0 -210
  38. package/scripts/reset-mcp-servers.js +0 -246
  39. package/scripts/setup-mcp.js +0 -106
  40. package/scripts/update-version.js +0 -31
  41. package/src/agents/README.md +0 -192
  42. package/src/agents/base-agent.ts +0 -251
  43. package/src/agents/coordinator.ts +0 -971
  44. package/src/agents/index.ts +0 -18
  45. package/src/agents/queen-403-handler.ts +0 -230
  46. package/src/agents/queen-agent.ts +0 -1399
  47. package/src/agents/script-writer-agent.ts +0 -769
  48. package/src/agents/security-agent.ts +0 -726
  49. package/src/agents/widget-creator-agent.ts +0 -568
  50. package/src/api/error-handling.ts +0 -1758
  51. package/src/api/natural-language-mapper.ts +0 -630
  52. package/src/api/performance-optimizer.ts +0 -891
  53. package/src/api/transaction-manager.ts +0 -754
  54. package/src/cli/deploy-artifact.ts +0 -304
  55. package/src/cli/snow-flow-cli-integration.ts +0 -434
  56. package/src/cli.ts +0 -4455
  57. package/src/cli.ts.bak +0 -5616
  58. package/src/compliance/advanced-compliance-system.ts +0 -1268
  59. package/src/compliance/index.ts +0 -19
  60. package/src/config/snow-flow-config.ts +0 -1019
  61. package/src/documentation/index.ts +0 -16
  62. package/src/documentation/self-documenting-system.ts +0 -1421
  63. package/src/dynamic-version.ts +0 -44
  64. package/src/healing/index.ts +0 -20
  65. package/src/healing/self-healing-system.ts +0 -1435
  66. package/src/health/README.md +0 -161
  67. package/src/health/system-health.ts +0 -995
  68. package/src/index.ts +0 -60
  69. package/src/intelligence/acl-analyzer.ts +0 -377
  70. package/src/intelligence/auto-resolution-engine.ts +0 -643
  71. package/src/intelligence/gap-analysis-engine.ts +0 -488
  72. package/src/intelligence/manual-instructions-generator.ts +0 -744
  73. package/src/intelligence/mcp-coverage-analyzer.ts +0 -708
  74. package/src/intelligence/multi-pass-requirements-analyzer.ts +0 -603
  75. package/src/intelligence/performance-recommendations-engine.ts +0 -1223
  76. package/src/intelligence/requirements-analyzer.ts +0 -1148
  77. package/src/intelligence/task-analyzer.ts +0 -628
  78. package/src/managers/scope-manager.ts +0 -555
  79. package/src/mcp/advanced/servicenow-advanced-features-mcp.ts +0 -16110
  80. package/src/mcp/base-mcp-server.ts +0 -660
  81. package/src/mcp/http-transport-wrapper.ts +0 -426
  82. package/src/mcp/service-discovery-client.ts +0 -312
  83. package/src/mcp/servicenow-automation-mcp-refactored.ts +0 -678
  84. package/src/mcp/servicenow-automation-mcp.ts +0 -838
  85. package/src/mcp/servicenow-deployment-mcp-refactored.ts +0 -1292
  86. package/src/mcp/servicenow-deployment-mcp.ts +0 -7244
  87. package/src/mcp/servicenow-graph-memory-mcp-refactored.ts +0 -721
  88. package/src/mcp/servicenow-graph-memory-mcp.ts +0 -832
  89. package/src/mcp/servicenow-integration-mcp-refactored.ts +0 -655
  90. package/src/mcp/servicenow-integration-mcp.ts +0 -819
  91. package/src/mcp/servicenow-intelligent-mcp-refactored.ts +0 -977
  92. package/src/mcp/servicenow-intelligent-mcp.ts +0 -4638
  93. package/src/mcp/servicenow-mcp-server.ts +0 -728
  94. package/src/mcp/servicenow-memory-mcp.ts +0 -474
  95. package/src/mcp/servicenow-operations-mcp-refactored.ts +0 -1995
  96. package/src/mcp/servicenow-operations-mcp.ts +0 -3384
  97. package/src/mcp/servicenow-platform-development-mcp-refactored.ts +0 -611
  98. package/src/mcp/servicenow-platform-development-mcp.ts +0 -970
  99. package/src/mcp/servicenow-progressive-indexer.ts +0 -370
  100. package/src/mcp/servicenow-reporting-analytics-mcp-refactored.ts +0 -695
  101. package/src/mcp/servicenow-reporting-analytics-mcp.ts +0 -1123
  102. package/src/mcp/servicenow-security-compliance-mcp-refactored.ts +0 -642
  103. package/src/mcp/servicenow-security-compliance-mcp.ts +0 -1157
  104. package/src/mcp/servicenow-update-set-mcp-refactored.ts +0 -751
  105. package/src/mcp/servicenow-update-set-mcp.ts +0 -811
  106. package/src/mcp/shared/agent-context-provider.ts +0 -365
  107. package/src/mcp/shared/base-mcp-server.ts +0 -2463
  108. package/src/mcp/shared/mcp-memory-manager.ts +0 -540
  109. package/src/mcp/shared/mcp-resource-manager.ts +0 -367
  110. package/src/mcp/snow-flow-mcp.ts +0 -911
  111. package/src/mcp/start-all-mcp-servers.ts +0 -102
  112. package/src/mcp/start-servicenow-mcp.ts +0 -67
  113. package/src/monitoring/enhanced-monitoring-system.ts +0 -1345
  114. package/src/monitoring/performance-tracker.ts +0 -777
  115. package/src/optimization/cost-optimization-engine.ts +0 -1125
  116. package/src/optimization/index.ts +0 -10
  117. package/src/queen/README.md +0 -403
  118. package/src/queen/agent-factory.ts +0 -574
  119. package/src/queen/index.ts +0 -94
  120. package/src/queen/mcp-execution-bridge.ts +0 -681
  121. package/src/queen/neural-learning.ts +0 -319
  122. package/src/queen/parallel-agent-engine.ts +0 -848
  123. package/src/queen/queen-memory-system.ts +0 -490
  124. package/src/queen/queen-memory.ts +0 -456
  125. package/src/queen/servicenow-queen.ts +0 -1029
  126. package/src/queen/types.ts +0 -87
  127. package/src/rollback/smart-rollback-system.ts +0 -866
  128. package/src/schemas/deployment.schema.json +0 -58
  129. package/src/schemas/flow.schema.json +0 -79
  130. package/src/schemas/widget.schema.json +0 -72
  131. package/src/snow-flow-system.ts +0 -541
  132. package/src/sparc/sparc-help.ts +0 -49
  133. package/src/sparc/team-sparc.ts +0 -88
  134. package/src/strategies/global-scope-strategy.ts +0 -736
  135. package/src/templates/base/application.template.json +0 -45
  136. package/src/templates/base/business_rule.template.json +0 -33
  137. package/src/templates/base/script_include.template.json +0 -18
  138. package/src/templates/base/table.template.json +0 -64
  139. package/src/templates/base/widget.template.json +0 -25
  140. package/src/templates/patterns/composite.incident-management.template.json +0 -140
  141. package/src/templates/patterns/widget.dashboard.template.json +0 -238
  142. package/src/templates/patterns/widget.datatable.template.json +0 -292
  143. package/src/testing/integration-test-suite.ts +0 -1276
  144. package/src/types/index.ts +0 -183
  145. package/src/types/neo4j-driver.d.ts +0 -23
  146. package/src/types/servicenow.types.ts +0 -307
  147. package/src/types/snow-flow.types.ts +0 -186
  148. package/src/types/todo.types.ts +0 -70
  149. package/src/utils/action-type-cache.ts +0 -286
  150. package/src/utils/agent-detector.ts +0 -573
  151. package/src/utils/artifact-tracker.ts +0 -379
  152. package/src/utils/dependency-detector.ts +0 -233
  153. package/src/utils/deployment-metadata-handler.ts +0 -518
  154. package/src/utils/error-recovery.ts +0 -680
  155. package/src/utils/logger.ts +0 -60
  156. package/src/utils/mcp-auth-middleware.ts +0 -262
  157. package/src/utils/mcp-config-manager.ts +0 -270
  158. package/src/utils/mcp-server-manager.ts +0 -446
  159. package/src/utils/mcp-tool-registry.ts +0 -298
  160. package/src/utils/migrate-snow-flow.ts +0 -98
  161. package/src/utils/oauth-html-templates.ts +0 -188
  162. package/src/utils/scope-utils.ts +0 -443
  163. package/src/utils/servicenow-client.ts +0 -3321
  164. package/src/utils/servicenow-id-generator.ts +0 -181
  165. package/src/utils/snow-memory-manager.ts +0 -605
  166. package/src/utils/snow-oauth.ts +0 -797
  167. package/src/utils/template-engine.ts +0 -463
  168. package/src/utils/theme-manager.ts +0 -295
  169. package/src/utils/unified-auth-store.ts +0 -222
  170. package/src/utils/update-set-importer.ts +0 -422
  171. package/src/utils/update-set-xml-packager.ts +0 -371
  172. package/src/utils/widget-template-generator.ts +0 -1744
  173. package/src/version.ts +0 -1950
@@ -1,797 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * ServiceNow OAuth Authentication Utility with Dynamic Port
4
- * Handles OAuth2 flow for ServiceNow integration
5
- */
6
-
7
- import { promises as fs } from 'fs';
8
- import { join } from 'path';
9
- import os from 'os';
10
- import { createServer } from 'http';
11
- import { URL } from 'url';
12
- import axios from 'axios';
13
- import https from 'https';
14
- import net from 'net';
15
- import crypto from 'crypto';
16
- import { snowFlowConfig } from '../config/snow-flow-config.js';
17
- import { unifiedAuthStore } from './unified-auth-store.js';
18
- import { OAuthTemplates } from './oauth-html-templates.js';
19
-
20
- export interface ServiceNowAuthResult {
21
- success: boolean;
22
- accessToken?: string;
23
- refreshToken?: string;
24
- expiresIn?: number;
25
- error?: string;
26
- }
27
-
28
- export interface ServiceNowCredentials {
29
- instance: string;
30
- clientId: string;
31
- clientSecret: string;
32
- accessToken?: string;
33
- refreshToken?: string;
34
- expiresAt?: string;
35
- }
36
-
37
- interface OAuthCredentials {
38
- instance: string;
39
- clientId: string;
40
- clientSecret: string;
41
- redirectUri: string;
42
- }
43
-
44
- export class ServiceNowOAuth {
45
- private credentials?: OAuthCredentials;
46
- private tokenPath: string;
47
- private stateParameter?: string;
48
- private codeVerifier?: string;
49
- private codeChallenge?: string;
50
-
51
- // 🔒 SEC-002 FIX: Add rate limiting to prevent authentication bypass attacks
52
- private lastTokenRequest: number = 0;
53
- private tokenRequestCount: number = 0;
54
- private readonly TOKEN_REQUEST_WINDOW_MS = 60000; // 1 minute window
55
- private readonly MAX_TOKEN_REQUESTS_PER_WINDOW = 10; // Max 10 token requests per minute
56
-
57
- constructor() {
58
- // Store tokens in user's home directory
59
- const configDir = process.env.SNOW_FLOW_HOME || join(os.homedir(), '.snow-flow');
60
- this.tokenPath = join(configDir, 'auth.json');
61
- }
62
-
63
- /**
64
- * 🔒 SEC-002 FIX: Check rate limiting for token requests to prevent brute force attacks
65
- */
66
- private checkTokenRequestRateLimit(): boolean {
67
- const now = Date.now();
68
-
69
- // Reset counter if window has passed
70
- if (now - this.lastTokenRequest > this.TOKEN_REQUEST_WINDOW_MS) {
71
- this.tokenRequestCount = 0;
72
- this.lastTokenRequest = now;
73
- }
74
-
75
- // Check if within rate limit
76
- if (this.tokenRequestCount >= this.MAX_TOKEN_REQUESTS_PER_WINDOW) {
77
- console.warn('🔒 Rate limit exceeded: Too many token requests. Please wait before retrying.');
78
- return false;
79
- }
80
-
81
- this.tokenRequestCount++;
82
- return true;
83
- }
84
-
85
- /**
86
- * Generate a random state parameter for CSRF protection
87
- */
88
- private generateState(): string {
89
- return Math.random().toString(36).substring(2, 15) +
90
- Math.random().toString(36).substring(2, 15);
91
- }
92
-
93
- /**
94
- * Generate PKCE code verifier and challenge
95
- */
96
- private generatePKCE() {
97
- // Generate code verifier (43-128 characters)
98
- this.codeVerifier = crypto.randomBytes(32).toString('base64url');
99
-
100
- // Generate code challenge (SHA256 hash of verifier)
101
- const hash = crypto.createHash('sha256');
102
- hash.update(this.codeVerifier);
103
- this.codeChallenge = hash.digest('base64url');
104
- }
105
-
106
- /**
107
- * Check if a specific port is available
108
- */
109
- private async checkPortAvailable(port: number): Promise<boolean> {
110
- return new Promise((resolve) => {
111
- const server = net.createServer();
112
-
113
- server.on('error', () => {
114
- resolve(false);
115
- });
116
-
117
- server.listen(port, () => {
118
- server.close(() => {
119
- resolve(true);
120
- });
121
- });
122
- });
123
- }
124
-
125
-
126
- /**
127
- * Initialize OAuth flow - opens browser and handles callback
128
- */
129
- /**
130
- * 🔧 CRIT-002 FIX: Normalize instance URL to prevent trailing slash 400 errors
131
- */
132
- private normalizeInstanceUrl(instance: string): string {
133
- // Remove any trailing slashes that cause 400 errors
134
- let normalized = instance.replace(/\/+$/, '');
135
-
136
- // Add https:// if missing
137
- if (!normalized.startsWith('http://') && !normalized.startsWith('https://')) {
138
- normalized = `https://${normalized}`;
139
- }
140
-
141
- // Ensure .service-now.com suffix if it's just the instance name
142
- if (!normalized.includes('.service-now.com') && !normalized.includes('localhost') && !normalized.includes('127.0.0.1')) {
143
- const instanceName = normalized.replace('https://', '').replace('http://', '');
144
- normalized = `https://${instanceName}.service-now.com`;
145
- }
146
-
147
- return normalized;
148
- }
149
-
150
- async authenticate(instance: string, clientId: string, clientSecret: string): Promise<ServiceNowAuthResult> {
151
- try {
152
- // 🔧 CRIT-002 FIX: Apply URL normalization
153
- const normalizedInstance = this.normalizeInstanceUrl(instance);
154
- // Validate client secret format
155
- const secretValidation = this.validateClientSecret(clientSecret);
156
- if (!secretValidation.valid) {
157
- console.error('❌ Invalid OAuth Client Secret:', secretValidation.reason);
158
- console.error('💡 To get a valid OAuth secret:');
159
- console.error(' 1. Log into ServiceNow as admin');
160
- console.error(' 2. Navigate to: System OAuth > Application Registry');
161
- console.error(' 3. Create a new OAuth application');
162
- console.error(' 4. Copy the generated Client Secret (long random string)');
163
- return {
164
- success: false,
165
- error: secretValidation.reason
166
- };
167
- }
168
-
169
- // Get OAuth redirect configuration from environment or use defaults
170
- const oauthConfig = snowFlowConfig.servicenow.oauth;
171
- const port = oauthConfig.redirectPort;
172
- const host = oauthConfig.redirectHost;
173
- const path = oauthConfig.redirectPath;
174
- const redirectUri = `http://${host}:${port}${path}`;
175
-
176
- // Check if port is available
177
- const isPortAvailable = await this.checkPortAvailable(port);
178
- if (!isPortAvailable) {
179
- console.error(`❌ Port ${port} is already in use!`);
180
- console.error(`💡 Please close any application using port ${port} and try again.`);
181
- return {
182
- success: false,
183
- error: `Port ${port} is already in use. Please free up the port and try again.`
184
- };
185
- }
186
-
187
- // Store credentials temporarily with normalized instance
188
- this.credentials = {
189
- instance: normalizedInstance.replace('https://', '').replace('http://', ''),
190
- clientId,
191
- clientSecret,
192
- redirectUri
193
- };
194
-
195
- console.log('🚀 Starting ServiceNow OAuth flow...');
196
- console.log(`📋 Instance: ${normalizedInstance}`);
197
- console.log(`🔐 Client ID: ${clientId}`);
198
- console.log(`🔗 Redirect URI: ${redirectUri}`);
199
-
200
- // Generate state parameter for CSRF protection
201
- this.stateParameter = this.generateState();
202
-
203
- // Generate PKCE parameters
204
- this.generatePKCE();
205
-
206
- // Generate authorization URL
207
- const authUrl = this.generateAuthUrl(this.credentials.instance, clientId, redirectUri);
208
-
209
- console.log('\n🌐 Authorization URL generated:');
210
- console.log(`${authUrl}\n`);
211
-
212
- // Start local server to handle callback
213
- const authResult = await this.startCallbackServer(redirectUri, port);
214
-
215
- if (authResult.success && authResult.accessToken) {
216
- // Save tokens with normalized instance
217
- await this.saveTokens({
218
- accessToken: authResult.accessToken,
219
- refreshToken: authResult.refreshToken || '',
220
- expiresIn: authResult.expiresIn || 3600,
221
- instance: this.credentials.instance,
222
- clientId,
223
- clientSecret
224
- });
225
-
226
- console.log('\n✅ Authentication successful!');
227
- console.log('🔐 Tokens saved securely');
228
- }
229
-
230
- return authResult;
231
- } catch (error) {
232
- const errorMessage = error instanceof Error ? error.message : String(error);
233
- console.error('❌ Authentication failed:', errorMessage);
234
- return {
235
- success: false,
236
- error: errorMessage
237
- };
238
- }
239
- }
240
-
241
- /**
242
- * Generate ServiceNow OAuth authorization URL
243
- */
244
- private generateAuthUrl(instance: string, clientId: string, redirectUri: string): string {
245
- const baseUrl = `https://${instance}/oauth_auth.do`;
246
- const params = new URLSearchParams({
247
- response_type: 'code',
248
- client_id: clientId,
249
- redirect_uri: redirectUri,
250
- scope: 'useraccount write admin',
251
- state: this.stateParameter || '',
252
- code_challenge: this.codeChallenge || '',
253
- code_challenge_method: 'S256'
254
- });
255
-
256
- return `${baseUrl}?${params.toString()}`;
257
- }
258
-
259
- /**
260
- * Start local HTTP server to handle OAuth callback
261
- */
262
- private async startCallbackServer(redirectUri: string, port: number): Promise<ServiceNowAuthResult> {
263
- return new Promise((resolve) => {
264
- const server = createServer(async (req, res) => {
265
- try {
266
- const url = new URL(req.url!, `http://${snowFlowConfig.servicenow.oauth.redirectHost}:${port}`);
267
-
268
- if (url.pathname === '/callback') {
269
- const code = url.searchParams.get('code');
270
- const error = url.searchParams.get('error');
271
- const state = url.searchParams.get('state');
272
-
273
- // Validate state parameter
274
- if (state !== this.stateParameter) {
275
- res.writeHead(400, { 'Content-Type': 'text/html' });
276
- res.end(OAuthTemplates.securityError);
277
-
278
- server.close();
279
- resolve({
280
- success: false,
281
- error: 'Invalid state parameter'
282
- });
283
- return;
284
- }
285
-
286
- if (error) {
287
- res.writeHead(400, { 'Content-Type': 'text/html' });
288
- res.end(OAuthTemplates.error(error));
289
-
290
- server.close();
291
- resolve({
292
- success: false,
293
- error: `OAuth error: ${error}`
294
- });
295
- return;
296
- }
297
-
298
- if (!code) {
299
- res.writeHead(400, { 'Content-Type': 'text/html' });
300
- res.end(OAuthTemplates.missingCode);
301
-
302
- server.close();
303
- resolve({
304
- success: false,
305
- error: 'No authorization code received'
306
- });
307
- return;
308
- }
309
-
310
- // Exchange code for tokens
311
- console.log('🔄 Exchanging authorization code for tokens...');
312
- const tokenResult = await this.exchangeCodeForTokens(code);
313
-
314
- if (tokenResult.success) {
315
- res.writeHead(200, { 'Content-Type': 'text/html' });
316
- res.end(OAuthTemplates.success);
317
-
318
- server.close();
319
- resolve(tokenResult);
320
- } else {
321
- res.writeHead(500, { 'Content-Type': 'text/html' });
322
- res.end(OAuthTemplates.tokenExchangeFailed(tokenResult.error || 'Unknown error'));
323
-
324
- server.close();
325
- resolve(tokenResult);
326
- }
327
- } else {
328
- res.writeHead(404, { 'Content-Type': 'text/plain' });
329
- res.end('Not Found');
330
- }
331
- } catch (error) {
332
- console.error('Callback server error:', error);
333
- res.writeHead(500, { 'Content-Type': 'text/plain' });
334
- res.end('Internal Server Error');
335
-
336
- server.close();
337
- resolve({
338
- success: false,
339
- error: error instanceof Error ? error.message : String(error)
340
- });
341
- }
342
- });
343
-
344
- server.listen(port, () => {
345
- console.log(`🌐 OAuth callback server started on http://${snowFlowConfig.servicenow.oauth.redirectHost}:${port}`);
346
- console.log('🚀 Please open the authorization URL in your browser...');
347
- console.log('⏳ Waiting for OAuth callback...');
348
-
349
- // Auto-open browser if possible
350
- try {
351
- const { spawn } = require('child_process');
352
- const authUrl = this.generateAuthUrl(
353
- this.credentials!.instance,
354
- this.credentials!.clientId,
355
- redirectUri
356
- );
357
-
358
- // Try to open browser on macOS
359
- if (process.platform === 'darwin') {
360
- spawn('open', [authUrl]);
361
- } else if (process.platform === 'win32') {
362
- spawn('cmd', ['/c', 'start', authUrl]);
363
- } else if (process.platform === 'linux') {
364
- spawn('xdg-open', [authUrl]);
365
- } else {
366
- console.log('Unknown OS:', process.platform);
367
- }
368
- } catch (err) {
369
- console.log('⚠️ Could not auto-open browser. Please open the URL manually.');
370
- }
371
- });
372
- });
373
- }
374
-
375
- /**
376
- * Exchange authorization code for tokens
377
- */
378
- private async exchangeCodeForTokens(code: string): Promise<ServiceNowAuthResult> {
379
- try {
380
- // 🔒 SEC-002 FIX: Apply rate limiting to prevent authentication bypass attacks
381
- if (!this.checkTokenRequestRateLimit()) {
382
- return {
383
- success: false,
384
- error: 'Rate limit exceeded. Too many token requests. Please wait 1 minute before retrying.'
385
- };
386
- }
387
-
388
- const tokenUrl = `https://${this.credentials!.instance}/oauth_token.do`;
389
-
390
- const response = await axios.post(tokenUrl, new URLSearchParams({
391
- grant_type: 'authorization_code',
392
- code,
393
- client_id: this.credentials!.clientId,
394
- client_secret: this.credentials!.clientSecret,
395
- redirect_uri: this.credentials!.redirectUri,
396
- code_verifier: this.codeVerifier || ''
397
- }), {
398
- headers: {
399
- 'Content-Type': 'application/x-www-form-urlencoded'
400
- },
401
- timeout: 15000, // 🔒 SEC-002 FIX: 15 second timeout to prevent hanging requests
402
- // Allow self-signed certificates for dev instances
403
- httpsAgent: new https.Agent({
404
- rejectUnauthorized: false
405
- })
406
- });
407
-
408
- const data = response.data;
409
-
410
- if (data.access_token) {
411
- return {
412
- success: true,
413
- accessToken: data.access_token,
414
- refreshToken: data.refresh_token,
415
- expiresIn: data.expires_in
416
- };
417
- } else {
418
- return {
419
- success: false,
420
- error: 'No access token received'
421
- };
422
- }
423
- } catch (error) {
424
- const errorMessage = error instanceof Error ? error.message : String(error);
425
- console.error('Token exchange error:', errorMessage);
426
- if (axios.isAxiosError(error) && error.response) {
427
- console.error('Response data:', error.response.data);
428
- }
429
- return {
430
- success: false,
431
- error: errorMessage
432
- };
433
- }
434
- }
435
-
436
- /**
437
- * Save tokens to file
438
- */
439
- private async saveTokens(tokenData: any): Promise<void> {
440
- try {
441
- const expiresAt = new Date();
442
- expiresAt.setSeconds(expiresAt.getSeconds() + tokenData.expiresIn);
443
-
444
- const authData = {
445
- ...tokenData,
446
- expiresAt: expiresAt.toISOString()
447
- };
448
-
449
- // Use unified auth store
450
- await unifiedAuthStore.saveTokens(authData);
451
-
452
- // Bridge to MCP servers immediately
453
- await unifiedAuthStore.bridgeToMCP();
454
- } catch (error) {
455
- console.error('Failed to save tokens:', error);
456
- throw error;
457
- }
458
- }
459
-
460
- /**
461
- * Load tokens from file
462
- */
463
- async loadTokens(): Promise<any> {
464
- try {
465
- return await unifiedAuthStore.getTokens();
466
- } catch (error) {
467
- return null;
468
- }
469
- }
470
-
471
- /**
472
- * Check if authenticated
473
- */
474
- async isAuthenticated(): Promise<boolean> {
475
- try {
476
- const tokens = await this.loadTokens();
477
- if (!tokens) return false;
478
-
479
- // Check if token is expired
480
- const expiresAt = new Date(tokens.expiresAt);
481
- const now = new Date();
482
-
483
- return now < expiresAt;
484
- } catch (error) {
485
- return false;
486
- }
487
- }
488
-
489
- /**
490
- * Get access token (refresh if needed)
491
- */
492
- async getAccessToken(): Promise<string | null> {
493
- try {
494
- const tokens = await this.loadTokens();
495
- if (!tokens) return null;
496
-
497
- // Check if token is expired
498
- const expiresAt = new Date(tokens.expiresAt);
499
- const now = new Date();
500
-
501
- if (now >= expiresAt && tokens.refreshToken) {
502
- // Token expired, try to refresh
503
- console.log('🔄 Token expired, refreshing...');
504
- const refreshResult = await this.refreshAccessToken(tokens);
505
-
506
- if (refreshResult.success && refreshResult.accessToken) {
507
- // Update saved tokens
508
- await this.saveTokens({
509
- ...tokens,
510
- accessToken: refreshResult.accessToken,
511
- expiresIn: refreshResult.expiresIn || 3600
512
- });
513
-
514
- return refreshResult.accessToken;
515
- } else {
516
- console.error('❌ Token refresh failed:', refreshResult.error);
517
- return null;
518
- }
519
- }
520
-
521
- return tokens.accessToken;
522
- } catch (error) {
523
- console.error('Failed to get access token:', error);
524
- return null;
525
- }
526
- }
527
-
528
- /**
529
- * Refresh access token
530
- */
531
- public async refreshAccessToken(tokens?: any): Promise<ServiceNowAuthResult> {
532
- try {
533
- // 🔒 SEC-002 FIX: Apply rate limiting to prevent authentication bypass attacks
534
- if (!this.checkTokenRequestRateLimit()) {
535
- return {
536
- success: false,
537
- error: 'Rate limit exceeded. Too many token requests. Please wait 1 minute before retrying.'
538
- };
539
- }
540
-
541
- // If no tokens provided, load from file
542
- if (!tokens) {
543
- tokens = await this.loadTokens();
544
- if (!tokens) {
545
- return {
546
- success: false,
547
- error: 'No tokens found to refresh'
548
- };
549
- }
550
- }
551
-
552
- const tokenUrl = `https://${tokens.instance}/oauth_token.do`;
553
-
554
- const response = await axios.post(tokenUrl, new URLSearchParams({
555
- grant_type: 'refresh_token',
556
- refresh_token: tokens.refreshToken,
557
- client_id: tokens.clientId,
558
- client_secret: tokens.clientSecret
559
- }), {
560
- headers: {
561
- 'Content-Type': 'application/x-www-form-urlencoded'
562
- },
563
- timeout: 15000, // 🔒 SEC-002 FIX: 15 second timeout to prevent hanging requests
564
- // Allow self-signed certificates for dev instances
565
- httpsAgent: new https.Agent({
566
- rejectUnauthorized: false
567
- })
568
- });
569
-
570
- const data = response.data;
571
-
572
- if (data.access_token) {
573
- // Update saved tokens
574
- await this.saveTokens({
575
- ...tokens,
576
- accessToken: data.access_token,
577
- expiresIn: data.expires_in || 3600
578
- });
579
-
580
- return {
581
- success: true,
582
- accessToken: data.access_token,
583
- expiresIn: data.expires_in
584
- };
585
- } else {
586
- return {
587
- success: false,
588
- error: 'No access token received'
589
- };
590
- }
591
- } catch (error) {
592
- const errorMessage = error instanceof Error ? error.message : String(error);
593
- return {
594
- success: false,
595
- error: errorMessage
596
- };
597
- }
598
- }
599
-
600
- /**
601
- * Logout - clear saved tokens
602
- */
603
- async logout(): Promise<void> {
604
- try {
605
- await fs.unlink(this.tokenPath);
606
- console.log('✅ Logged out successfully');
607
- } catch (error) {
608
- console.log('No active session to logout from');
609
- }
610
- }
611
-
612
- /**
613
- * Get stored OAuth tokens for use in other contexts (MCP servers)
614
- */
615
- async getStoredTokens(): Promise<any> {
616
- return await this.loadTokens();
617
- }
618
-
619
- /**
620
- * Load credentials (including tokens) with .env fallback
621
- */
622
- async loadCredentials(): Promise<ServiceNowCredentials | null> {
623
- try {
624
- // First, try to load saved OAuth tokens
625
- const tokens = await this.loadTokens();
626
-
627
- if (tokens && tokens.accessToken) {
628
- // Validate client secret when loading
629
- if (tokens.clientSecret) {
630
- const secretValidation = this.validateClientSecret(tokens.clientSecret);
631
- if (!secretValidation.valid) {
632
- console.warn('⚠️ OAuth Configuration Issue:', secretValidation.reason);
633
- console.warn('💡 Your stored client secret may be incorrect. Re-authenticate with: snow-flow auth login');
634
- }
635
- }
636
-
637
- // Check if token is expired
638
- const expiresAt = new Date(tokens.expiresAt);
639
- const now = new Date();
640
-
641
- if (now < expiresAt) {
642
- console.log('✅ Using saved OAuth tokens');
643
- return {
644
- instance: tokens.instance,
645
- clientId: tokens.clientId,
646
- clientSecret: tokens.clientSecret,
647
- accessToken: tokens.accessToken,
648
- refreshToken: tokens.refreshToken,
649
- expiresAt: tokens.expiresAt
650
- };
651
- } else {
652
- console.log('⏰ Saved OAuth token expired, will try refresh...');
653
- }
654
- }
655
-
656
- // 🔧 NEW: Fallback to .env file if no valid tokens
657
- console.log('🔍 No valid OAuth tokens found, checking .env file...');
658
-
659
- // Load environment variables with dotenv
660
- try {
661
- require('dotenv').config();
662
- } catch (err) {
663
- console.log('📝 dotenv not available, using process.env directly');
664
- }
665
-
666
- const envInstance = process.env.SNOW_INSTANCE;
667
- const envClientId = process.env.SNOW_CLIENT_ID;
668
- const envClientSecret = process.env.SNOW_CLIENT_SECRET;
669
-
670
- if (envInstance && envClientId && envClientSecret) {
671
- console.log('✅ Found ServiceNow credentials in .env file');
672
- console.log(` - Instance: ${envInstance}`);
673
- console.log(` - Client ID: ${envClientId}`);
674
- console.log(` - Client Secret: ✅ Present`);
675
-
676
- // Validate client secret
677
- const secretValidation = this.validateClientSecret(envClientSecret);
678
- if (!secretValidation.valid) {
679
- console.error('❌ Invalid OAuth Client Secret in .env file:', secretValidation.reason);
680
- console.error('💡 Please update SNOW_CLIENT_SECRET in .env with proper OAuth secret from ServiceNow');
681
- return null;
682
- }
683
-
684
- console.log('');
685
- console.log('🔐 OAuth Setup Required:');
686
- console.log(' Your .env has OAuth credentials but no active session.');
687
- console.log(' Run: snow-flow auth login');
688
- console.log(' This will authenticate and create persistent tokens.');
689
- console.log('');
690
-
691
- // Return credentials without access token - this will trigger auth flow
692
- return {
693
- instance: envInstance.replace(/\/$/, ''),
694
- clientId: envClientId,
695
- clientSecret: envClientSecret,
696
- // No accessToken - this signals that OAuth login is needed
697
- };
698
- }
699
-
700
- // 🔧 Check for old username/password setup in .env
701
- const envUsername = process.env.SNOW_USERNAME;
702
- const envPassword = process.env.SNOW_PASSWORD;
703
-
704
- if (envInstance && envUsername && envPassword) {
705
- console.warn('⚠️ Found username/password in .env - OAuth is recommended');
706
- console.warn('💡 For better security, set up OAuth credentials:');
707
- console.warn(' 1. In ServiceNow: System OAuth > Application Registry > New');
708
- console.warn(' 2. Update .env with SNOW_CLIENT_ID and SNOW_CLIENT_SECRET');
709
- console.warn(' 3. Run: snow-flow auth login');
710
-
711
- // Don't return username/password - force OAuth setup
712
- return null;
713
- }
714
-
715
- // No credentials found anywhere
716
- console.error('❌ No ServiceNow credentials found!');
717
- console.error('');
718
- console.error('🔧 Setup Instructions:');
719
- console.error(' 1. Create .env file with OAuth credentials:');
720
- console.error(' SNOW_INSTANCE=your-instance.service-now.com');
721
- console.error(' SNOW_CLIENT_ID=your_oauth_client_id');
722
- console.error(' SNOW_CLIENT_SECRET=your_oauth_client_secret');
723
- console.error(' 2. Run: snow-flow auth login');
724
- console.error('');
725
- console.error('💡 To get OAuth credentials:');
726
- console.error(' • ServiceNow: System OAuth > Application Registry > New OAuth Application');
727
- console.error(` • Redirect URI: http://${snowFlowConfig.servicenow.oauth.redirectHost}:${snowFlowConfig.servicenow.oauth.redirectPort}${snowFlowConfig.servicenow.oauth.redirectPath}`);
728
- console.error(' • Scopes: useraccount write admin');
729
- console.error('');
730
-
731
- return null;
732
-
733
- } catch (error) {
734
- console.error('❌ Error loading credentials:', error);
735
- return null;
736
- }
737
- }
738
-
739
- /**
740
- * Validate OAuth client secret format
741
- * OAuth secrets are typically long random strings (32+ chars) with mixed case and alphanumeric
742
- * Common passwords are shorter and may contain dictionary words
743
- */
744
- validateClientSecret(clientSecret: string): { valid: boolean; reason?: string } {
745
- // Check minimum length - OAuth secrets are typically 32+ characters
746
- if (clientSecret.length < 20) {
747
- return {
748
- valid: false,
749
- reason: 'OAuth Client Secret too short. Expected 32+ character random string from ServiceNow.'
750
- };
751
- }
752
-
753
- // Check for common password patterns
754
- const commonPasswordPatterns = [
755
- /^password/i,
756
- /^admin/i,
757
- /^test/i,
758
- /^demo/i,
759
- /^welcome/i,
760
- /123456/,
761
- /qwerty/i,
762
- /^[a-z]+\d{1,4}$/i, // Simple word + numbers like "Welkom123"
763
- /^[A-Z][a-z]+\d{1,4}$/ // Capitalized word + numbers
764
- ];
765
-
766
- for (const pattern of commonPasswordPatterns) {
767
- if (pattern.test(clientSecret)) {
768
- return {
769
- valid: false,
770
- reason: `OAuth Client Secret appears to be a password. ServiceNow OAuth secrets are long random strings (e.g., "a1b2c3d4e5f6..."). Check your Application Registry in ServiceNow.`
771
- };
772
- }
773
- }
774
-
775
- // Check for sufficient entropy (mix of upper, lower, numbers)
776
- const hasUpper = /[A-Z]/.test(clientSecret);
777
- const hasLower = /[a-z]/.test(clientSecret);
778
- const hasNumber = /[0-9]/.test(clientSecret);
779
- const charTypes = [hasUpper, hasLower, hasNumber].filter(Boolean).length;
780
-
781
- if (charTypes < 2) {
782
- return {
783
- valid: false,
784
- reason: 'OAuth Client Secret lacks complexity. ServiceNow generates secrets with mixed case and numbers.'
785
- };
786
- }
787
-
788
- return { valid: true };
789
- }
790
-
791
- /**
792
- * Get credentials (compatibility method for MCP servers)
793
- */
794
- async getCredentials(): Promise<ServiceNowCredentials | null> {
795
- return await this.loadCredentials();
796
- }
797
- }