snow-flow 2.9.4 → 2.9.5
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.
|
@@ -15,6 +15,7 @@ const scope_manager_js_1 = require("../managers/scope-manager.js");
|
|
|
15
15
|
const global_scope_strategy_js_1 = require("../strategies/global-scope-strategy.js");
|
|
16
16
|
const artifact_tracker_js_1 = require("../utils/artifact-tracker.js");
|
|
17
17
|
const servicenow_id_generator_js_1 = require("../utils/servicenow-id-generator.js");
|
|
18
|
+
const deployment_auth_fix_js_1 = require("../utils/deployment-auth-fix.js");
|
|
18
19
|
const fs_1 = require("fs");
|
|
19
20
|
const path_1 = require("path");
|
|
20
21
|
class ServiceNowDeploymentMCP {
|
|
@@ -30,6 +31,7 @@ class ServiceNowDeploymentMCP {
|
|
|
30
31
|
this.client = new servicenow_client_js_1.ServiceNowClient();
|
|
31
32
|
this.oauth = new snow_oauth_js_1.ServiceNowOAuth();
|
|
32
33
|
this.logger = new logger_js_1.Logger('ServiceNowDeploymentMCP');
|
|
34
|
+
this.deploymentAuthManager = new deployment_auth_fix_js_1.DeploymentAuthManager();
|
|
33
35
|
// Initialize global scope management
|
|
34
36
|
this.scopeManager = new scope_manager_js_1.ScopeManager({
|
|
35
37
|
defaultScope: global_scope_strategy_js_1.ScopeType.GLOBAL,
|
|
@@ -472,6 +474,45 @@ class ServiceNowDeploymentMCP {
|
|
|
472
474
|
updateSetName: updateSetName
|
|
473
475
|
};
|
|
474
476
|
}
|
|
477
|
+
/**
|
|
478
|
+
* Create a record with automatic 403 error recovery
|
|
479
|
+
* Will attempt to refresh token and retry once if 403 error occurs
|
|
480
|
+
*/
|
|
481
|
+
async createRecordWithRetry(table, data) {
|
|
482
|
+
try {
|
|
483
|
+
// First attempt
|
|
484
|
+
return await this.client.createRecord(table, data);
|
|
485
|
+
}
|
|
486
|
+
catch (error) {
|
|
487
|
+
// Check if it's a 403 error
|
|
488
|
+
if (error.response?.status === 403 || error.message?.includes('403')) {
|
|
489
|
+
this.logger.warn('Got 403 error, attempting token refresh and retry...');
|
|
490
|
+
// Refresh token
|
|
491
|
+
const refreshResult = await this.deploymentAuthManager.forceTokenRefresh();
|
|
492
|
+
if (refreshResult.success && refreshResult.accessToken) {
|
|
493
|
+
// Client will use the new token automatically from unified auth store
|
|
494
|
+
// No need to call authenticate - the client reads from auth store
|
|
495
|
+
// Retry the operation
|
|
496
|
+
try {
|
|
497
|
+
this.logger.info('Retrying operation with refreshed token...');
|
|
498
|
+
return await this.client.createRecord(table, data);
|
|
499
|
+
}
|
|
500
|
+
catch (retryError) {
|
|
501
|
+
this.logger.error('Retry failed after token refresh:', retryError);
|
|
502
|
+
throw retryError;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
else {
|
|
506
|
+
this.logger.error('Failed to refresh token for retry');
|
|
507
|
+
throw error;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
else {
|
|
511
|
+
// Not a 403 error, just re-throw
|
|
512
|
+
throw error;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
475
516
|
/**
|
|
476
517
|
* Ensure artifact is tracked in current Update Set
|
|
477
518
|
*/
|
|
@@ -537,17 +578,26 @@ class ServiceNowDeploymentMCP {
|
|
|
537
578
|
}
|
|
538
579
|
async deployWidget(args) {
|
|
539
580
|
try {
|
|
540
|
-
//
|
|
541
|
-
const
|
|
542
|
-
if (!
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
581
|
+
// Enhanced authentication check with token refresh for deployment
|
|
582
|
+
const authResult = await this.deploymentAuthManager.ensureDeploymentAuth();
|
|
583
|
+
if (!authResult.isValid) {
|
|
584
|
+
this.logger.error('Deployment authentication failed:', authResult.error);
|
|
585
|
+
// If auth failed, try to refresh token
|
|
586
|
+
const refreshResult = await this.deploymentAuthManager.forceTokenRefresh();
|
|
587
|
+
if (!refreshResult.success) {
|
|
588
|
+
return {
|
|
589
|
+
content: [
|
|
590
|
+
{
|
|
591
|
+
type: 'text',
|
|
592
|
+
text: `❌ Deployment authentication failed.\n\nError: ${authResult.error || 'Unable to authenticate'}\n\nRecommendations:\n${(authResult.recommendations || ['Run: snow-flow auth login']).map(r => `• ${r}`).join('\n')}\n\nNote: Deployment requires valid OAuth tokens with write permissions.`,
|
|
593
|
+
},
|
|
594
|
+
],
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
// Warn if token may lack write permissions
|
|
599
|
+
if (!authResult.hasWriteScope) {
|
|
600
|
+
this.logger.warn('⚠️ Token may lack write permissions, deployment might fail with 403');
|
|
551
601
|
}
|
|
552
602
|
this.logger.info('Deploying widget to ServiceNow', { name: args.name });
|
|
553
603
|
// ENHANCED: Mandatory Update Set management with auto-activation
|
|
@@ -695,7 +745,7 @@ class ServiceNowDeploymentMCP {
|
|
|
695
745
|
// Strategy 2: Direct table record creation (fallback)
|
|
696
746
|
try {
|
|
697
747
|
this.logger.info('🔄 Attempting fallback: Direct table record creation');
|
|
698
|
-
result = await this.
|
|
748
|
+
result = await this.createRecordWithRetry('sp_widget', {
|
|
699
749
|
name: args.name,
|
|
700
750
|
id: args.name,
|
|
701
751
|
title: args.title,
|
|
@@ -1069,17 +1119,26 @@ Use \`snow_deployment_debug\` for more information about this session.`,
|
|
|
1069
1119
|
*/
|
|
1070
1120
|
async deployPortalPage(args) {
|
|
1071
1121
|
try {
|
|
1072
|
-
//
|
|
1073
|
-
const
|
|
1074
|
-
if (!
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1122
|
+
// Enhanced authentication check with token refresh for deployment
|
|
1123
|
+
const authResult = await this.deploymentAuthManager.ensureDeploymentAuth();
|
|
1124
|
+
if (!authResult.isValid) {
|
|
1125
|
+
this.logger.error('Deployment authentication failed:', authResult.error);
|
|
1126
|
+
// If auth failed, try to refresh token
|
|
1127
|
+
const refreshResult = await this.deploymentAuthManager.forceTokenRefresh();
|
|
1128
|
+
if (!refreshResult.success) {
|
|
1129
|
+
return {
|
|
1130
|
+
content: [
|
|
1131
|
+
{
|
|
1132
|
+
type: 'text',
|
|
1133
|
+
text: `❌ Deployment authentication failed.\n\nError: ${authResult.error || 'Unable to authenticate'}\n\nRecommendations:\n${(authResult.recommendations || ['Run: snow-flow auth login']).map(r => `• ${r}`).join('\n')}\n\nNote: Deployment requires valid OAuth tokens with write permissions.`,
|
|
1134
|
+
},
|
|
1135
|
+
],
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
// Warn if token may lack write permissions
|
|
1140
|
+
if (!authResult.hasWriteScope) {
|
|
1141
|
+
this.logger.warn('⚠️ Token may lack write permissions, deployment might fail with 403');
|
|
1083
1142
|
}
|
|
1084
1143
|
this.logger.info('Deploying portal page to ServiceNow', { name: args.page_id });
|
|
1085
1144
|
// Ensure Update Set is active
|
|
@@ -1124,7 +1183,7 @@ Use \`snow_deployment_debug\` for more information about this session.`,
|
|
|
1124
1183
|
let pageResult;
|
|
1125
1184
|
try {
|
|
1126
1185
|
// Create the page record
|
|
1127
|
-
pageResult = await this.
|
|
1186
|
+
pageResult = await this.createRecordWithRetry('sp_page', {
|
|
1128
1187
|
id: args.page_id,
|
|
1129
1188
|
title: args.title,
|
|
1130
1189
|
short_description: args.description || `Portal page created by Snow-Flow`,
|
|
@@ -1512,17 +1571,26 @@ ${args.widgets && args.widgets.length > 0 ? args.widgets.map((w, i) => `
|
|
|
1512
1571
|
}
|
|
1513
1572
|
async deployFlow(args) {
|
|
1514
1573
|
try {
|
|
1515
|
-
//
|
|
1516
|
-
const
|
|
1517
|
-
if (!
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1574
|
+
// Enhanced authentication check with token refresh for deployment
|
|
1575
|
+
const authResult = await this.deploymentAuthManager.ensureDeploymentAuth();
|
|
1576
|
+
if (!authResult.isValid) {
|
|
1577
|
+
this.logger.error('Deployment authentication failed:', authResult.error);
|
|
1578
|
+
// If auth failed, try to refresh token
|
|
1579
|
+
const refreshResult = await this.deploymentAuthManager.forceTokenRefresh();
|
|
1580
|
+
if (!refreshResult.success) {
|
|
1581
|
+
return {
|
|
1582
|
+
content: [
|
|
1583
|
+
{
|
|
1584
|
+
type: 'text',
|
|
1585
|
+
text: `❌ Deployment authentication failed.\n\nError: ${authResult.error || 'Unable to authenticate'}\n\nRecommendations:\n${(authResult.recommendations || ['Run: snow-flow auth login']).map(r => `• ${r}`).join('\n')}\n\nNote: Deployment requires valid OAuth tokens with write permissions.`,
|
|
1586
|
+
},
|
|
1587
|
+
],
|
|
1588
|
+
};
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
// Warn if token may lack write permissions
|
|
1592
|
+
if (!authResult.hasWriteScope) {
|
|
1593
|
+
this.logger.warn('⚠️ Token may lack write permissions, deployment might fail with 403');
|
|
1526
1594
|
}
|
|
1527
1595
|
// Ensure we have a flow definition
|
|
1528
1596
|
if (!args.flow_definition) {
|
|
@@ -1964,17 +2032,26 @@ ${isComposedFlow ? `
|
|
|
1964
2032
|
}
|
|
1965
2033
|
async deployApplication(args) {
|
|
1966
2034
|
try {
|
|
1967
|
-
//
|
|
1968
|
-
const
|
|
1969
|
-
if (!
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
2035
|
+
// Enhanced authentication check with token refresh for deployment
|
|
2036
|
+
const authResult = await this.deploymentAuthManager.ensureDeploymentAuth();
|
|
2037
|
+
if (!authResult.isValid) {
|
|
2038
|
+
this.logger.error('Deployment authentication failed:', authResult.error);
|
|
2039
|
+
// If auth failed, try to refresh token
|
|
2040
|
+
const refreshResult = await this.deploymentAuthManager.forceTokenRefresh();
|
|
2041
|
+
if (!refreshResult.success) {
|
|
2042
|
+
return {
|
|
2043
|
+
content: [
|
|
2044
|
+
{
|
|
2045
|
+
type: 'text',
|
|
2046
|
+
text: `❌ Deployment authentication failed.\n\nError: ${authResult.error || 'Unable to authenticate'}\n\nRecommendations:\n${(authResult.recommendations || ['Run: snow-flow auth login']).map(r => `• ${r}`).join('\n')}\n\nNote: Deployment requires valid OAuth tokens with write permissions.`,
|
|
2047
|
+
},
|
|
2048
|
+
],
|
|
2049
|
+
};
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
// Warn if token may lack write permissions
|
|
2053
|
+
if (!authResult.hasWriteScope) {
|
|
2054
|
+
this.logger.warn('⚠️ Token may lack write permissions, deployment might fail with 403');
|
|
1978
2055
|
}
|
|
1979
2056
|
this.logger.info('Deploying application with intelligent scope management', { name: args.name });
|
|
1980
2057
|
// Ensure Update Set is active
|
|
@@ -3480,13 +3557,13 @@ Run snow_deployment_debug for basic session info or check the logs for more deta
|
|
|
3480
3557
|
async previewWidget(args) {
|
|
3481
3558
|
try {
|
|
3482
3559
|
// Check authentication first
|
|
3483
|
-
const
|
|
3484
|
-
if (!
|
|
3560
|
+
const authResult = await this.deploymentAuthManager.ensureDeploymentAuth();
|
|
3561
|
+
if (!authResult.isValid) {
|
|
3485
3562
|
return {
|
|
3486
3563
|
content: [
|
|
3487
3564
|
{
|
|
3488
3565
|
type: 'text',
|
|
3489
|
-
text:
|
|
3566
|
+
text: `❌ Deployment authentication failed.\n\nError: ${authResult.error || 'Unable to authenticate'}\n\nRecommendations:\n${(authResult.recommendations || ['Run: snow-flow auth login']).map(r => `• ${r}`).join('\n')}\n\nNote: Deployment requires valid OAuth tokens with write permissions.`,
|
|
3490
3567
|
},
|
|
3491
3568
|
],
|
|
3492
3569
|
};
|
|
@@ -3653,13 +3730,13 @@ Use \`snow_widget_test\` to run automated tests with different scenarios.`,
|
|
|
3653
3730
|
async testWidget(args) {
|
|
3654
3731
|
try {
|
|
3655
3732
|
// Check authentication first
|
|
3656
|
-
const
|
|
3657
|
-
if (!
|
|
3733
|
+
const authResult = await this.deploymentAuthManager.ensureDeploymentAuth();
|
|
3734
|
+
if (!authResult.isValid) {
|
|
3658
3735
|
return {
|
|
3659
3736
|
content: [
|
|
3660
3737
|
{
|
|
3661
3738
|
type: 'text',
|
|
3662
|
-
text:
|
|
3739
|
+
text: `❌ Deployment authentication failed.\n\nError: ${authResult.error || 'Unable to authenticate'}\n\nRecommendations:\n${(authResult.recommendations || ['Run: snow-flow auth login']).map(r => `• ${r}`).join('\n')}\n\nNote: Deployment requires valid OAuth tokens with write permissions.`,
|
|
3663
3740
|
},
|
|
3664
3741
|
],
|
|
3665
3742
|
};
|
|
@@ -4689,21 +4766,21 @@ Use \`snow_preview_widget\` to see a detailed preview of the widget rendering.`,
|
|
|
4689
4766
|
};
|
|
4690
4767
|
case 'script':
|
|
4691
4768
|
case 'script_include':
|
|
4692
|
-
const scriptResult = await this.
|
|
4769
|
+
const scriptResult = await this.createRecordWithRetry('sys_script_include', config);
|
|
4693
4770
|
return {
|
|
4694
4771
|
success: scriptResult.success,
|
|
4695
4772
|
sys_id: scriptResult.data?.sys_id,
|
|
4696
4773
|
message: scriptResult.success ? 'Script deployed' : scriptResult.error
|
|
4697
4774
|
};
|
|
4698
4775
|
case 'business_rule':
|
|
4699
|
-
const ruleResult = await this.
|
|
4776
|
+
const ruleResult = await this.createRecordWithRetry('sys_script', config);
|
|
4700
4777
|
return {
|
|
4701
4778
|
success: ruleResult.success,
|
|
4702
4779
|
sys_id: ruleResult.data?.sys_id,
|
|
4703
4780
|
message: ruleResult.success ? 'Business rule deployed' : ruleResult.error
|
|
4704
4781
|
};
|
|
4705
4782
|
case 'table':
|
|
4706
|
-
const tableResult = await this.
|
|
4783
|
+
const tableResult = await this.createRecordWithRetry('sys_db_object', config);
|
|
4707
4784
|
return {
|
|
4708
4785
|
success: tableResult.success,
|
|
4709
4786
|
sys_id: tableResult.data?.sys_id,
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deployment Authentication Fix
|
|
3
|
+
* Ensures OAuth tokens are properly refreshed and validated before deployment operations
|
|
4
|
+
*/
|
|
5
|
+
export interface AuthValidationResult {
|
|
6
|
+
isValid: boolean;
|
|
7
|
+
hasWriteScope: boolean;
|
|
8
|
+
tokenAge?: number;
|
|
9
|
+
expiresIn?: number;
|
|
10
|
+
error?: string;
|
|
11
|
+
recommendations?: string[];
|
|
12
|
+
}
|
|
13
|
+
export declare class DeploymentAuthManager {
|
|
14
|
+
private oauth;
|
|
15
|
+
private lastTokenRefresh;
|
|
16
|
+
constructor();
|
|
17
|
+
/**
|
|
18
|
+
* Ensure we have valid tokens for deployment operations
|
|
19
|
+
* This is MORE strict than regular authentication
|
|
20
|
+
*/
|
|
21
|
+
ensureDeploymentAuth(): Promise<AuthValidationResult>;
|
|
22
|
+
/**
|
|
23
|
+
* Validate token by making a simple API call
|
|
24
|
+
*/
|
|
25
|
+
private validateTokenWithAPI;
|
|
26
|
+
/**
|
|
27
|
+
* Check if token has write permissions by attempting to read widget table
|
|
28
|
+
*/
|
|
29
|
+
private checkWritePermissions;
|
|
30
|
+
/**
|
|
31
|
+
* Force a fresh token for deployment operations
|
|
32
|
+
*/
|
|
33
|
+
forceTokenRefresh(): Promise<{
|
|
34
|
+
success: boolean;
|
|
35
|
+
accessToken?: string;
|
|
36
|
+
error?: string;
|
|
37
|
+
}>;
|
|
38
|
+
/**
|
|
39
|
+
* Get fresh access token for deployment
|
|
40
|
+
*/
|
|
41
|
+
getDeploymentToken(): Promise<string | null>;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=deployment-auth-fix.d.ts.map
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Deployment Authentication Fix
|
|
4
|
+
* Ensures OAuth tokens are properly refreshed and validated before deployment operations
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.DeploymentAuthManager = void 0;
|
|
8
|
+
const snow_oauth_js_1 = require("./snow-oauth.js");
|
|
9
|
+
const unified_auth_store_js_1 = require("./unified-auth-store.js");
|
|
10
|
+
const logger_js_1 = require("./logger.js");
|
|
11
|
+
const logger = new logger_js_1.Logger('DeploymentAuthFix');
|
|
12
|
+
class DeploymentAuthManager {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.lastTokenRefresh = 0;
|
|
15
|
+
this.oauth = new snow_oauth_js_1.ServiceNowOAuth();
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Ensure we have valid tokens for deployment operations
|
|
19
|
+
* This is MORE strict than regular authentication
|
|
20
|
+
*/
|
|
21
|
+
async ensureDeploymentAuth() {
|
|
22
|
+
try {
|
|
23
|
+
logger.info('🔐 Validating deployment authentication...');
|
|
24
|
+
// Step 1: Check if we have any auth at all
|
|
25
|
+
const isAuth = await this.oauth.isAuthenticated();
|
|
26
|
+
if (!isAuth) {
|
|
27
|
+
return {
|
|
28
|
+
isValid: false,
|
|
29
|
+
hasWriteScope: false,
|
|
30
|
+
error: 'Not authenticated',
|
|
31
|
+
recommendations: [
|
|
32
|
+
'Run: snow-flow auth login',
|
|
33
|
+
'Ensure OAuth app has write/admin scopes'
|
|
34
|
+
]
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
// Step 2: Get current tokens
|
|
38
|
+
let tokens = await this.oauth.loadTokens();
|
|
39
|
+
if (!tokens || !tokens.accessToken) {
|
|
40
|
+
// Try unified auth store as fallback
|
|
41
|
+
tokens = await unified_auth_store_js_1.unifiedAuthStore.getTokens();
|
|
42
|
+
if (!tokens || !tokens.accessToken) {
|
|
43
|
+
return {
|
|
44
|
+
isValid: false,
|
|
45
|
+
hasWriteScope: false,
|
|
46
|
+
error: 'No access token found',
|
|
47
|
+
recommendations: [
|
|
48
|
+
'Run: snow-flow auth login',
|
|
49
|
+
'Check .env configuration'
|
|
50
|
+
]
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Step 3: Check token age and expiry
|
|
55
|
+
const now = Date.now();
|
|
56
|
+
const tokenAge = tokens.issuedAt ? now - tokens.issuedAt : null;
|
|
57
|
+
const expiresIn = tokens.expiresAt ? tokens.expiresAt - now : null;
|
|
58
|
+
// If token expires in less than 5 minutes, refresh it
|
|
59
|
+
if (expiresIn && expiresIn < 300000) { // 5 minutes
|
|
60
|
+
logger.info('⚠️ Token expires soon, refreshing...');
|
|
61
|
+
try {
|
|
62
|
+
const refreshResult = await this.oauth.refreshAccessToken();
|
|
63
|
+
if (refreshResult.success && refreshResult.accessToken) {
|
|
64
|
+
logger.info('✅ Token refreshed successfully');
|
|
65
|
+
tokens = {
|
|
66
|
+
...tokens,
|
|
67
|
+
accessToken: refreshResult.accessToken,
|
|
68
|
+
expiresAt: refreshResult.expiresIn ? now + refreshResult.expiresIn * 1000 : undefined,
|
|
69
|
+
issuedAt: now
|
|
70
|
+
};
|
|
71
|
+
// Update unified auth store
|
|
72
|
+
await unified_auth_store_js_1.unifiedAuthStore.saveTokens(tokens);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
logger.warn('Failed to refresh token:', refreshResult.error);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
logger.error('Token refresh error:', error);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// Step 4: Validate token by making a test API call
|
|
83
|
+
try {
|
|
84
|
+
const testResponse = await this.validateTokenWithAPI(tokens.accessToken);
|
|
85
|
+
if (!testResponse.success) {
|
|
86
|
+
// Token is invalid, try to refresh
|
|
87
|
+
logger.warn('Token validation failed, attempting refresh...');
|
|
88
|
+
const refreshResult = await this.oauth.refreshAccessToken();
|
|
89
|
+
if (refreshResult.success && refreshResult.accessToken) {
|
|
90
|
+
tokens.accessToken = refreshResult.accessToken;
|
|
91
|
+
await unified_auth_store_js_1.unifiedAuthStore.saveTokens(tokens);
|
|
92
|
+
// Validate again
|
|
93
|
+
const retryResponse = await this.validateTokenWithAPI(tokens.accessToken);
|
|
94
|
+
if (!retryResponse.success) {
|
|
95
|
+
return {
|
|
96
|
+
isValid: false,
|
|
97
|
+
hasWriteScope: false,
|
|
98
|
+
error: 'Token validation failed after refresh',
|
|
99
|
+
recommendations: [
|
|
100
|
+
'Run: snow-flow auth login',
|
|
101
|
+
'Check ServiceNow OAuth app configuration',
|
|
102
|
+
'Verify API access is enabled for your user'
|
|
103
|
+
]
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
return {
|
|
109
|
+
isValid: false,
|
|
110
|
+
hasWriteScope: false,
|
|
111
|
+
error: 'Failed to refresh invalid token',
|
|
112
|
+
recommendations: [
|
|
113
|
+
'Run: snow-flow auth login',
|
|
114
|
+
'Your session may have expired'
|
|
115
|
+
]
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
logger.error('Token validation error:', error);
|
|
122
|
+
return {
|
|
123
|
+
isValid: false,
|
|
124
|
+
hasWriteScope: false,
|
|
125
|
+
error: `Token validation failed: ${error.message}`,
|
|
126
|
+
recommendations: [
|
|
127
|
+
'Check network connectivity',
|
|
128
|
+
'Verify ServiceNow instance is accessible',
|
|
129
|
+
'Run: snow-flow auth login'
|
|
130
|
+
]
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
// Step 5: Check for write permissions
|
|
134
|
+
const hasWriteScope = await this.checkWritePermissions(tokens.accessToken);
|
|
135
|
+
return {
|
|
136
|
+
isValid: true,
|
|
137
|
+
hasWriteScope,
|
|
138
|
+
tokenAge: tokenAge ? Math.round(tokenAge / 1000) : undefined,
|
|
139
|
+
expiresIn: expiresIn ? Math.round(expiresIn / 1000) : undefined,
|
|
140
|
+
recommendations: hasWriteScope ? [] : [
|
|
141
|
+
'OAuth token is valid but may lack write permissions',
|
|
142
|
+
'Check OAuth app scopes in ServiceNow',
|
|
143
|
+
'Ensure user has sp_admin or admin role'
|
|
144
|
+
]
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
logger.error('Deployment auth validation error:', error);
|
|
149
|
+
return {
|
|
150
|
+
isValid: false,
|
|
151
|
+
hasWriteScope: false,
|
|
152
|
+
error: error.message,
|
|
153
|
+
recommendations: [
|
|
154
|
+
'Unexpected error during authentication',
|
|
155
|
+
'Run: snow-flow auth login',
|
|
156
|
+
'Check error logs for details'
|
|
157
|
+
]
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Validate token by making a simple API call
|
|
163
|
+
*/
|
|
164
|
+
async validateTokenWithAPI(accessToken) {
|
|
165
|
+
try {
|
|
166
|
+
const axios = require('axios');
|
|
167
|
+
const credentials = await this.oauth.loadCredentials();
|
|
168
|
+
if (!credentials?.instance) {
|
|
169
|
+
return { success: false, error: 'No instance configured' };
|
|
170
|
+
}
|
|
171
|
+
const response = await axios.get(`https://${credentials.instance}/api/now/table/sys_user?sysparm_limit=1`, {
|
|
172
|
+
headers: {
|
|
173
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
174
|
+
'Accept': 'application/json'
|
|
175
|
+
},
|
|
176
|
+
timeout: 10000
|
|
177
|
+
});
|
|
178
|
+
return { success: response.status === 200 };
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
if (error.response?.status === 401) {
|
|
182
|
+
return { success: false, error: 'Token is invalid or expired' };
|
|
183
|
+
}
|
|
184
|
+
return { success: false, error: error.message };
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Check if token has write permissions by attempting to read widget table
|
|
189
|
+
*/
|
|
190
|
+
async checkWritePermissions(accessToken) {
|
|
191
|
+
try {
|
|
192
|
+
const axios = require('axios');
|
|
193
|
+
const credentials = await this.oauth.loadCredentials();
|
|
194
|
+
if (!credentials?.instance) {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
// Try to read from sp_widget table (requires portal access)
|
|
198
|
+
const response = await axios.get(`https://${credentials.instance}/api/now/table/sp_widget?sysparm_limit=1`, {
|
|
199
|
+
headers: {
|
|
200
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
201
|
+
'Accept': 'application/json'
|
|
202
|
+
},
|
|
203
|
+
timeout: 10000
|
|
204
|
+
});
|
|
205
|
+
// If we can read widgets, we likely have portal access
|
|
206
|
+
return response.status === 200;
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
// 403 means we don't have permission
|
|
210
|
+
if (error.response?.status === 403) {
|
|
211
|
+
logger.warn('No write permissions for Service Portal');
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
// Other errors we'll assume no permission
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Force a fresh token for deployment operations
|
|
220
|
+
*/
|
|
221
|
+
async forceTokenRefresh() {
|
|
222
|
+
try {
|
|
223
|
+
logger.info('🔄 Forcing token refresh for deployment...');
|
|
224
|
+
const refreshResult = await this.oauth.refreshAccessToken();
|
|
225
|
+
if (refreshResult.success && refreshResult.accessToken) {
|
|
226
|
+
// Store in unified auth store
|
|
227
|
+
const tokens = await this.oauth.loadTokens();
|
|
228
|
+
if (tokens) {
|
|
229
|
+
tokens.accessToken = refreshResult.accessToken;
|
|
230
|
+
tokens.expiresAt = refreshResult.expiresIn ? Date.now() + refreshResult.expiresIn * 1000 : undefined;
|
|
231
|
+
tokens.issuedAt = Date.now();
|
|
232
|
+
await unified_auth_store_js_1.unifiedAuthStore.saveTokens(tokens);
|
|
233
|
+
}
|
|
234
|
+
logger.info('✅ Token refreshed successfully');
|
|
235
|
+
return {
|
|
236
|
+
success: true,
|
|
237
|
+
accessToken: refreshResult.accessToken
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
success: false,
|
|
242
|
+
error: refreshResult.error || 'Failed to refresh token'
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
logger.error('Force refresh error:', error);
|
|
247
|
+
return {
|
|
248
|
+
success: false,
|
|
249
|
+
error: error.message
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Get fresh access token for deployment
|
|
255
|
+
*/
|
|
256
|
+
async getDeploymentToken() {
|
|
257
|
+
// First ensure we have valid auth
|
|
258
|
+
const authResult = await this.ensureDeploymentAuth();
|
|
259
|
+
if (!authResult.isValid) {
|
|
260
|
+
logger.error('Invalid authentication for deployment:', authResult.error);
|
|
261
|
+
throw new Error(authResult.error || 'Authentication failed');
|
|
262
|
+
}
|
|
263
|
+
if (!authResult.hasWriteScope) {
|
|
264
|
+
logger.warn('⚠️ Token may lack write permissions, deployment might fail');
|
|
265
|
+
}
|
|
266
|
+
// Get the token
|
|
267
|
+
const tokens = await this.oauth.loadTokens();
|
|
268
|
+
return tokens?.accessToken || null;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
exports.DeploymentAuthManager = DeploymentAuthManager;
|
|
272
|
+
//# sourceMappingURL=deployment-auth-fix.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "snow-flow",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.5",
|
|
4
4
|
"description": "Snow-Flow: ServiceNow Advanced Intelligence Platform - 100+ real MCP tools with AI-powered swarm orchestration and neural networks. Dynamic task categorization using AI. Machine learning for incident classification, change risk prediction, and anomaly detection. Zero Mock Data, 100% Real API Integration.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "commonjs",
|