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.
- package/{.claude/mcp-config.json → .mcp.json} +13 -12
- package/.releaserc.json +47 -0
- package/README.md +6 -0
- package/package.json +1 -1
- package/.claude/claude-settings.json +0 -15
- package/.claude/commands/analysis/README.md +0 -9
- package/.claude/commands/automation/README.md +0 -9
- package/.claude/commands/github/README.md +0 -11
- package/.claude/commands/helpers/standard-checkpoint-hooks.sh +0 -179
- package/.claude/commands/hooks/README.md +0 -11
- package/.claude/commands/monitoring/README.md +0 -9
- package/.claude/commands/optimization/README.md +0 -9
- package/.claude/commands/training/README.md +0 -9
- package/.claude/commands/workflows/README.md +0 -9
- package/.claude/config.json +0 -36
- package/.claude/helpers/github-setup.sh +0 -28
- package/.claude/helpers/quick-start.sh +0 -19
- package/.claude/helpers/setup-mcp.sh +0 -18
- package/.claude/settings.json +0 -114
- package/.claude/settings.local.json +0 -33
- package/.claude-flow/metrics/agent-metrics.json +0 -1
- package/.claude-flow/metrics/performance.json +0 -9
- package/.claude-flow/metrics/system-metrics.json +0 -15986
- package/.claude-flow/metrics/task-metrics.json +0 -10
- package/.roo/README.md +0 -402
- package/.roo/mcp-list.txt +0 -257
- package/.roo/mcp.json +0 -32
- package/.roo/workflows/basic-tdd.json +0 -32
- package/.roomodes +0 -201
- package/CLAUDE.md +0 -393
- package/bin/snow-flow.js +0 -3
- package/dist/cli.js.bak +0 -2052
- package/dist/config/snow-flow-config.d.ts +0 -1492
- package/dist/config/snow-flow-config.js +0 -938
- package/scripts/create-env.js +0 -91
- package/scripts/postinstall.js +0 -30
- package/scripts/register-mcp-servers.js +0 -210
- package/scripts/reset-mcp-servers.js +0 -246
- package/scripts/setup-mcp.js +0 -106
- package/scripts/update-version.js +0 -31
- package/src/agents/README.md +0 -192
- package/src/agents/base-agent.ts +0 -251
- package/src/agents/coordinator.ts +0 -971
- package/src/agents/index.ts +0 -18
- package/src/agents/queen-403-handler.ts +0 -230
- package/src/agents/queen-agent.ts +0 -1399
- package/src/agents/script-writer-agent.ts +0 -769
- package/src/agents/security-agent.ts +0 -726
- package/src/agents/widget-creator-agent.ts +0 -568
- package/src/api/error-handling.ts +0 -1758
- package/src/api/natural-language-mapper.ts +0 -630
- package/src/api/performance-optimizer.ts +0 -891
- package/src/api/transaction-manager.ts +0 -754
- package/src/cli/deploy-artifact.ts +0 -304
- package/src/cli/snow-flow-cli-integration.ts +0 -434
- package/src/cli.ts +0 -4455
- package/src/cli.ts.bak +0 -5616
- package/src/compliance/advanced-compliance-system.ts +0 -1268
- package/src/compliance/index.ts +0 -19
- package/src/config/snow-flow-config.ts +0 -1019
- package/src/documentation/index.ts +0 -16
- package/src/documentation/self-documenting-system.ts +0 -1421
- package/src/dynamic-version.ts +0 -44
- package/src/healing/index.ts +0 -20
- package/src/healing/self-healing-system.ts +0 -1435
- package/src/health/README.md +0 -161
- package/src/health/system-health.ts +0 -995
- package/src/index.ts +0 -60
- package/src/intelligence/acl-analyzer.ts +0 -377
- package/src/intelligence/auto-resolution-engine.ts +0 -643
- package/src/intelligence/gap-analysis-engine.ts +0 -488
- package/src/intelligence/manual-instructions-generator.ts +0 -744
- package/src/intelligence/mcp-coverage-analyzer.ts +0 -708
- package/src/intelligence/multi-pass-requirements-analyzer.ts +0 -603
- package/src/intelligence/performance-recommendations-engine.ts +0 -1223
- package/src/intelligence/requirements-analyzer.ts +0 -1148
- package/src/intelligence/task-analyzer.ts +0 -628
- package/src/managers/scope-manager.ts +0 -555
- package/src/mcp/advanced/servicenow-advanced-features-mcp.ts +0 -16110
- package/src/mcp/base-mcp-server.ts +0 -660
- package/src/mcp/http-transport-wrapper.ts +0 -426
- package/src/mcp/service-discovery-client.ts +0 -312
- package/src/mcp/servicenow-automation-mcp-refactored.ts +0 -678
- package/src/mcp/servicenow-automation-mcp.ts +0 -838
- package/src/mcp/servicenow-deployment-mcp-refactored.ts +0 -1292
- package/src/mcp/servicenow-deployment-mcp.ts +0 -7244
- package/src/mcp/servicenow-graph-memory-mcp-refactored.ts +0 -721
- package/src/mcp/servicenow-graph-memory-mcp.ts +0 -832
- package/src/mcp/servicenow-integration-mcp-refactored.ts +0 -655
- package/src/mcp/servicenow-integration-mcp.ts +0 -819
- package/src/mcp/servicenow-intelligent-mcp-refactored.ts +0 -977
- package/src/mcp/servicenow-intelligent-mcp.ts +0 -4638
- package/src/mcp/servicenow-mcp-server.ts +0 -728
- package/src/mcp/servicenow-memory-mcp.ts +0 -474
- package/src/mcp/servicenow-operations-mcp-refactored.ts +0 -1995
- package/src/mcp/servicenow-operations-mcp.ts +0 -3384
- package/src/mcp/servicenow-platform-development-mcp-refactored.ts +0 -611
- package/src/mcp/servicenow-platform-development-mcp.ts +0 -970
- package/src/mcp/servicenow-progressive-indexer.ts +0 -370
- package/src/mcp/servicenow-reporting-analytics-mcp-refactored.ts +0 -695
- package/src/mcp/servicenow-reporting-analytics-mcp.ts +0 -1123
- package/src/mcp/servicenow-security-compliance-mcp-refactored.ts +0 -642
- package/src/mcp/servicenow-security-compliance-mcp.ts +0 -1157
- package/src/mcp/servicenow-update-set-mcp-refactored.ts +0 -751
- package/src/mcp/servicenow-update-set-mcp.ts +0 -811
- package/src/mcp/shared/agent-context-provider.ts +0 -365
- package/src/mcp/shared/base-mcp-server.ts +0 -2463
- package/src/mcp/shared/mcp-memory-manager.ts +0 -540
- package/src/mcp/shared/mcp-resource-manager.ts +0 -367
- package/src/mcp/snow-flow-mcp.ts +0 -911
- package/src/mcp/start-all-mcp-servers.ts +0 -102
- package/src/mcp/start-servicenow-mcp.ts +0 -67
- package/src/monitoring/enhanced-monitoring-system.ts +0 -1345
- package/src/monitoring/performance-tracker.ts +0 -777
- package/src/optimization/cost-optimization-engine.ts +0 -1125
- package/src/optimization/index.ts +0 -10
- package/src/queen/README.md +0 -403
- package/src/queen/agent-factory.ts +0 -574
- package/src/queen/index.ts +0 -94
- package/src/queen/mcp-execution-bridge.ts +0 -681
- package/src/queen/neural-learning.ts +0 -319
- package/src/queen/parallel-agent-engine.ts +0 -848
- package/src/queen/queen-memory-system.ts +0 -490
- package/src/queen/queen-memory.ts +0 -456
- package/src/queen/servicenow-queen.ts +0 -1029
- package/src/queen/types.ts +0 -87
- package/src/rollback/smart-rollback-system.ts +0 -866
- package/src/schemas/deployment.schema.json +0 -58
- package/src/schemas/flow.schema.json +0 -79
- package/src/schemas/widget.schema.json +0 -72
- package/src/snow-flow-system.ts +0 -541
- package/src/sparc/sparc-help.ts +0 -49
- package/src/sparc/team-sparc.ts +0 -88
- package/src/strategies/global-scope-strategy.ts +0 -736
- package/src/templates/base/application.template.json +0 -45
- package/src/templates/base/business_rule.template.json +0 -33
- package/src/templates/base/script_include.template.json +0 -18
- package/src/templates/base/table.template.json +0 -64
- package/src/templates/base/widget.template.json +0 -25
- package/src/templates/patterns/composite.incident-management.template.json +0 -140
- package/src/templates/patterns/widget.dashboard.template.json +0 -238
- package/src/templates/patterns/widget.datatable.template.json +0 -292
- package/src/testing/integration-test-suite.ts +0 -1276
- package/src/types/index.ts +0 -183
- package/src/types/neo4j-driver.d.ts +0 -23
- package/src/types/servicenow.types.ts +0 -307
- package/src/types/snow-flow.types.ts +0 -186
- package/src/types/todo.types.ts +0 -70
- package/src/utils/action-type-cache.ts +0 -286
- package/src/utils/agent-detector.ts +0 -573
- package/src/utils/artifact-tracker.ts +0 -379
- package/src/utils/dependency-detector.ts +0 -233
- package/src/utils/deployment-metadata-handler.ts +0 -518
- package/src/utils/error-recovery.ts +0 -680
- package/src/utils/logger.ts +0 -60
- package/src/utils/mcp-auth-middleware.ts +0 -262
- package/src/utils/mcp-config-manager.ts +0 -270
- package/src/utils/mcp-server-manager.ts +0 -446
- package/src/utils/mcp-tool-registry.ts +0 -298
- package/src/utils/migrate-snow-flow.ts +0 -98
- package/src/utils/oauth-html-templates.ts +0 -188
- package/src/utils/scope-utils.ts +0 -443
- package/src/utils/servicenow-client.ts +0 -3321
- package/src/utils/servicenow-id-generator.ts +0 -181
- package/src/utils/snow-memory-manager.ts +0 -605
- package/src/utils/snow-oauth.ts +0 -797
- package/src/utils/template-engine.ts +0 -463
- package/src/utils/theme-manager.ts +0 -295
- package/src/utils/unified-auth-store.ts +0 -222
- package/src/utils/update-set-importer.ts +0 -422
- package/src/utils/update-set-xml-packager.ts +0 -371
- package/src/utils/widget-template-generator.ts +0 -1744
- package/src/version.ts +0 -1950
package/src/utils/snow-oauth.ts
DELETED
|
@@ -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
|
-
}
|