@sasjs/cli 4.18.6 → 4.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/commands/add/spec/addCommand.spec.js +0 -21
- package/build/commands/auth/authCommand.d.ts +9 -0
- package/build/commands/auth/authCommand.js +181 -0
- package/build/commands/auth/login.d.ts +16 -0
- package/build/commands/auth/login.js +130 -0
- package/build/commands/auth/spec/authCommand.spec.d.ts +1 -0
- package/build/commands/auth/spec/authCommand.spec.js +207 -0
- package/build/commands/auth/spec/login.spec.d.ts +1 -0
- package/build/commands/auth/spec/login.spec.js +364 -0
- package/build/commands/deploy/internal/executeDeployScriptSasViya.js +2 -1
- package/build/commands/fs/internal/executeCode.js +2 -1
- package/build/commands/help/help.js +3 -2
- package/build/commands/run/run.js +2 -1
- package/build/types/command/commandAliases.js +0 -1
- package/build/types/command/commandBase.js +1 -0
- package/build/types/command/commandFactory.js +2 -0
- package/build/utils/auth.d.ts +35 -16
- package/build/utils/auth.js +97 -37
- package/build/utils/config.d.ts +13 -2
- package/build/utils/config.js +125 -55
- package/build/utils/spec/auth.spec.d.ts +1 -0
- package/build/utils/spec/auth.spec.js +255 -0
- package/build/utils/spec/config.spec.js +334 -32
- package/package.json +4 -3
package/build/utils/auth.js
CHANGED
|
@@ -35,13 +35,9 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
|
35
35
|
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
36
|
}
|
|
37
37
|
};
|
|
38
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
39
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
40
|
-
};
|
|
41
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
-
exports.getNewAccessToken = exports.
|
|
39
|
+
exports.getNewAccessToken = exports.fetchLoggedInUser = exports.getTokensWithPasswordGrant = exports.SAS_CLI_CLIENT_ID = exports.refreshTokens = exports.getAuthCode = exports.getAuthUrl = void 0;
|
|
43
40
|
var utils_1 = require("@sasjs/utils");
|
|
44
|
-
var jwt_decode_1 = __importDefault(require("jwt-decode"));
|
|
45
41
|
var node_1 = require("@sasjs/adapter/node");
|
|
46
42
|
var getAuthUrl = function (serverType, serverUrl, clientId) {
|
|
47
43
|
return serverType === utils_1.ServerType.Sasjs
|
|
@@ -69,38 +65,6 @@ function getAuthCode(authUrl) {
|
|
|
69
65
|
});
|
|
70
66
|
}
|
|
71
67
|
exports.getAuthCode = getAuthCode;
|
|
72
|
-
/**
|
|
73
|
-
* Checks if the Access Token is expired or is expiring in 1 hour. A default Access Token
|
|
74
|
-
* lasts 12 hours. If the Access Token expires, the Refresh Token is used to fetch a new
|
|
75
|
-
* Access Token. In the case that the Refresh Token is expired, 1 hour is enough to let
|
|
76
|
-
* most jobs finish.
|
|
77
|
-
* @param {string} token- token string that will be evaluated
|
|
78
|
-
*/
|
|
79
|
-
function isAccessTokenExpiring(token) {
|
|
80
|
-
if (!token) {
|
|
81
|
-
return true;
|
|
82
|
-
}
|
|
83
|
-
var payload = (0, jwt_decode_1.default)(token);
|
|
84
|
-
var timeToLive = payload.exp - new Date().valueOf() / 1000;
|
|
85
|
-
return timeToLive <= 60 * 60; // 1 hour
|
|
86
|
-
}
|
|
87
|
-
exports.isAccessTokenExpiring = isAccessTokenExpiring;
|
|
88
|
-
/**
|
|
89
|
-
* Checks if the Refresh Token is expired or expiring in 30 secs. A default Refresh Token
|
|
90
|
-
* lasts 30 days. Once the Refresh Token expires, the user must re-authenticate (provide
|
|
91
|
-
* credentials in a browser to obtain an authorisation code). 30 seconds is enough time
|
|
92
|
-
* to make a request for a final Access Token.
|
|
93
|
-
* @param {string} token- token string that will be evaluated
|
|
94
|
-
*/
|
|
95
|
-
function isRefreshTokenExpiring(token) {
|
|
96
|
-
if (!token) {
|
|
97
|
-
return true;
|
|
98
|
-
}
|
|
99
|
-
var payload = (0, jwt_decode_1.default)(token);
|
|
100
|
-
var timeToLive = payload.exp - new Date().valueOf() / 1000;
|
|
101
|
-
return timeToLive <= 30; // 30 seconds
|
|
102
|
-
}
|
|
103
|
-
exports.isRefreshTokenExpiring = isRefreshTokenExpiring;
|
|
104
68
|
/**
|
|
105
69
|
* Exchanges a refresh token for a new access/refresh token pair.
|
|
106
70
|
* SAS Viya's refresh tokens are single-use and rotate on every call: the
|
|
@@ -123,6 +87,102 @@ function refreshTokens(sasjsInstance, clientId, clientSecret, refreshToken) {
|
|
|
123
87
|
});
|
|
124
88
|
}
|
|
125
89
|
exports.refreshTokens = refreshTokens;
|
|
90
|
+
/**
|
|
91
|
+
* The pre-registered, secret-less OAuth client that ships with every SAS Viya
|
|
92
|
+
* deployment (used by the official SAS Viya CLI). Allows authenticating with a
|
|
93
|
+
* SAS username/password (OAuth2 resource owner password grant) when no
|
|
94
|
+
* administrator-registered client/secret is available.
|
|
95
|
+
*/
|
|
96
|
+
exports.SAS_CLI_CLIENT_ID = 'sas.cli';
|
|
97
|
+
/**
|
|
98
|
+
* Fetches an access/refresh token pair from SAS Viya using the resource owner
|
|
99
|
+
* password grant against the built-in `sas.cli` public client. Unlike the
|
|
100
|
+
* client/secret flows, no OAuth client registration is required - the
|
|
101
|
+
* credentials are the user's regular SAS logon credentials.
|
|
102
|
+
*
|
|
103
|
+
* Note: this requires the password grant to be enabled for `sas.cli` (the
|
|
104
|
+
* default) and a local/LDAP account - it cannot work on SSO/SAML/MFA-only
|
|
105
|
+
* estates.
|
|
106
|
+
* @param {Target} target - the SASVIYA target to authenticate against.
|
|
107
|
+
* @param {string} user - the SAS username.
|
|
108
|
+
* @param {string} pass - the SAS password.
|
|
109
|
+
* @returns the access and refresh token pair.
|
|
110
|
+
*/
|
|
111
|
+
function getTokensWithPasswordGrant(target, user, pass) {
|
|
112
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
113
|
+
var requestClient, basicAuth, data, authResponse;
|
|
114
|
+
return __generator(this, function (_a) {
|
|
115
|
+
switch (_a.label) {
|
|
116
|
+
case 0:
|
|
117
|
+
requestClient = new node_1.SasjsRequestClient(target.serverUrl, target.httpsAgentOptions);
|
|
118
|
+
basicAuth = Buffer.from("".concat(exports.SAS_CLI_CLIENT_ID, ":")).toString('base64');
|
|
119
|
+
data = new URLSearchParams({
|
|
120
|
+
grant_type: 'password',
|
|
121
|
+
username: user,
|
|
122
|
+
password: pass
|
|
123
|
+
});
|
|
124
|
+
return [4 /*yield*/, requestClient
|
|
125
|
+
.post('/SASLogon/oauth/token', data, undefined, 'application/x-www-form-urlencoded', {
|
|
126
|
+
Authorization: "Basic ".concat(basicAuth),
|
|
127
|
+
Accept: 'application/json'
|
|
128
|
+
})
|
|
129
|
+
.then(function (res) { return res.result; })
|
|
130
|
+
.catch(function (err) {
|
|
131
|
+
if (err instanceof node_1.CertificateError)
|
|
132
|
+
throw err;
|
|
133
|
+
throw new Error("Login failed for user '".concat(user, "' on ").concat(target.serverUrl, ".\n") +
|
|
134
|
+
"Please check your username and password and try again. If they are correct, " +
|
|
135
|
+
"the password grant may be disabled for the '".concat(exports.SAS_CLI_CLIENT_ID, "' client on this Viya deployment.\n") +
|
|
136
|
+
"".concat((err === null || err === void 0 ? void 0 : err.message) || err));
|
|
137
|
+
})];
|
|
138
|
+
case 1:
|
|
139
|
+
authResponse = _a.sent();
|
|
140
|
+
if (!(authResponse === null || authResponse === void 0 ? void 0 : authResponse.access_token)) {
|
|
141
|
+
throw new Error("Login failed: the token endpoint did not return an access token.");
|
|
142
|
+
}
|
|
143
|
+
return [2 /*return*/, {
|
|
144
|
+
access_token: authResponse.access_token,
|
|
145
|
+
// Some Viya deployments omit refresh_token for the password grant; default
|
|
146
|
+
// to '' so downstream code (saveTokens, config) can treat it as a plain
|
|
147
|
+
// string rather than handling undefined.
|
|
148
|
+
refresh_token: authResponse.refresh_token || ''
|
|
149
|
+
}];
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
exports.getTokensWithPasswordGrant = getTokensWithPasswordGrant;
|
|
155
|
+
/**
|
|
156
|
+
* Verifies an access token by fetching the identity it belongs to.
|
|
157
|
+
* @param {Target} target - the SASVIYA target the token was minted for.
|
|
158
|
+
* @param {string} accessToken - the access token to verify.
|
|
159
|
+
* @returns the id and display name of the authenticated user.
|
|
160
|
+
*/
|
|
161
|
+
function fetchLoggedInUser(target, accessToken) {
|
|
162
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
163
|
+
var requestClient, result;
|
|
164
|
+
return __generator(this, function (_a) {
|
|
165
|
+
switch (_a.label) {
|
|
166
|
+
case 0:
|
|
167
|
+
requestClient = new node_1.SasjsRequestClient(target.serverUrl, target.httpsAgentOptions);
|
|
168
|
+
return [4 /*yield*/, requestClient
|
|
169
|
+
.get('/identities/users/@currentUser', accessToken)
|
|
170
|
+
.catch(function (err) {
|
|
171
|
+
if (err instanceof node_1.CertificateError)
|
|
172
|
+
throw err;
|
|
173
|
+
throw new Error("Unable to verify the access token against ".concat(target.serverUrl, ": ").concat((err === null || err === void 0 ? void 0 : err.message) || err));
|
|
174
|
+
})];
|
|
175
|
+
case 1:
|
|
176
|
+
result = (_a.sent()).result;
|
|
177
|
+
if (!(result === null || result === void 0 ? void 0 : result.id)) {
|
|
178
|
+
throw new Error('Login succeeded but the identity endpoint returned no user id.');
|
|
179
|
+
}
|
|
180
|
+
return [2 /*return*/, { id: result.id, name: result === null || result === void 0 ? void 0 : result.name }];
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
exports.fetchLoggedInUser = fetchLoggedInUser;
|
|
126
186
|
function getNewAccessToken(sasjsInstance, clientId, clientSecret, target) {
|
|
127
187
|
return __awaiter(this, void 0, void 0, function () {
|
|
128
188
|
var authUrl, authCode, _a, access_token, refresh_token;
|
package/build/utils/config.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import SASjs from '@sasjs/adapter/node';
|
|
1
|
+
import SASjs, { OnTokensRefreshed } from '@sasjs/adapter/node';
|
|
2
2
|
import { Configuration, Target, AuthConfig, StreamConfig, AuthConfigSas9, SyncDirectoryMap } from '@sasjs/utils';
|
|
3
3
|
import { TargetScope } from '../types/targetScope';
|
|
4
4
|
/**
|
|
@@ -84,7 +84,17 @@ export declare function getAuthConfig(target: Target): Promise<AuthConfig>;
|
|
|
84
84
|
* so that a rotated (single-use) refresh token from SAS Viya is never reused -
|
|
85
85
|
* reusing it on a subsequent CLI invocation would be rejected by the server.
|
|
86
86
|
*/
|
|
87
|
-
export declare const saveTokens: (targetName: string,
|
|
87
|
+
export declare const saveTokens: (targetName: string, access_token: string, refresh_token: string, client?: string, secret?: string) => Promise<void>;
|
|
88
|
+
/**
|
|
89
|
+
* Returns an `onTokensRefreshed` handler for adapter methods that refresh
|
|
90
|
+
* tokens internally (e.g. `executeScript` on long-running jobs). The adapter
|
|
91
|
+
* receives the rotated pair; this handler persists it via {@link saveTokens}
|
|
92
|
+
* exactly as {@link getAuthConfig} does for its own refreshes, so the stored
|
|
93
|
+
* single-use refresh token never goes stale between CLI invocations.
|
|
94
|
+
* Any client/secret already configured for the target is passed through so
|
|
95
|
+
* {@link saveTokens} does not drop it from `.env.{target}`.
|
|
96
|
+
*/
|
|
97
|
+
export declare const persistTokensRefreshedByAdapter: (target: Target) => OnTokensRefreshed;
|
|
88
98
|
/**
|
|
89
99
|
* Gets the auth config for the specified SAS9 target.
|
|
90
100
|
* @param {Target} target - the target to get an access token for.
|
|
@@ -114,6 +124,7 @@ export declare function getAuthConfigSAS9(target: Target): AuthConfigSas9;
|
|
|
114
124
|
* should use `getAuthConfig` instead.
|
|
115
125
|
* @param {object} target - the target to get an access token for.
|
|
116
126
|
* @param {string} checkIfExpiring - flag that indicates whether to do an expiry check.
|
|
127
|
+
* @internal One-shot/test-only. User-facing commands must use {@link getAuthConfig}.
|
|
117
128
|
*/
|
|
118
129
|
export declare function getAccessToken(target: Target, checkIfExpiring?: boolean): Promise<string>;
|
|
119
130
|
/**
|
package/build/utils/config.js
CHANGED
|
@@ -75,10 +75,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
75
75
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
76
76
|
};
|
|
77
77
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
78
|
-
exports.getSASjsAndAuthConfig = exports.getSASjs = exports.getTestTearDown = exports.getTestSetUp = exports.overrideEnvVariables = exports.getAccessToken = exports.getAuthConfigSAS9 = exports.saveTokens = exports.getAuthConfig = exports.getProjectRoot = exports.sanitizeAppLoc = exports.getStreamConfig = exports.getMacroFolders = exports.getBinaryFolders = exports.getProgramFolders = exports.getFolders = exports.saveToLocalConfig = exports.saveLocalConfigFile = exports.getSyncDirectories = exports.getLocalOrGlobalConfig = exports.getLocalConfig = exports.removeFromLocalConfig = exports.removeFromGlobalConfig = exports.saveToGlobalConfig = exports.saveGlobalRcFile = exports.getLocalRcFile = exports.getGlobalRcFile = exports.findTargetInConfiguration = exports.getConfiguration = void 0;
|
|
78
|
+
exports.getSASjsAndAuthConfig = exports.getSASjs = exports.getTestTearDown = exports.getTestSetUp = exports.overrideEnvVariables = exports.getAccessToken = exports.getAuthConfigSAS9 = exports.persistTokensRefreshedByAdapter = exports.saveTokens = exports.getAuthConfig = exports.getProjectRoot = exports.sanitizeAppLoc = exports.getStreamConfig = exports.getMacroFolders = exports.getBinaryFolders = exports.getProgramFolders = exports.getFolders = exports.saveToLocalConfig = exports.saveLocalConfigFile = exports.getSyncDirectories = exports.getLocalOrGlobalConfig = exports.getLocalConfig = exports.removeFromLocalConfig = exports.removeFromGlobalConfig = exports.saveToGlobalConfig = exports.saveGlobalRcFile = exports.getLocalRcFile = exports.getGlobalRcFile = exports.findTargetInConfiguration = exports.getConfiguration = void 0;
|
|
79
79
|
var node_1 = __importDefault(require("@sasjs/adapter/node"));
|
|
80
80
|
var utils_1 = require("@sasjs/utils");
|
|
81
81
|
var auth_1 = require("./auth");
|
|
82
|
+
var auth_2 = require("@sasjs/utils/auth");
|
|
82
83
|
var path_1 = __importDefault(require("path"));
|
|
83
84
|
var dotenv_1 = __importDefault(require("dotenv"));
|
|
84
85
|
var targetScope_1 = require("../types/targetScope");
|
|
@@ -90,6 +91,19 @@ var ERROR_MESSAGE = function (targetName) {
|
|
|
90
91
|
NOT_FOUND_FALLBACK: "No target was found.\nPlease check the target name and try again, or use `sasjs add` to add a new target.\nMore info: https://cli.sasjs.io/faq/#what-is-the-difference-between-local-and-global-targets\n"
|
|
91
92
|
};
|
|
92
93
|
};
|
|
94
|
+
/**
|
|
95
|
+
* Sanitizes a credential value that may have come from an .env file or
|
|
96
|
+
* `process.env`. Node persists `process.env.X = undefined` as the literal
|
|
97
|
+
* string "undefined", and .env files can contain "null" as a placeholder.
|
|
98
|
+
* Returns undefined for those sentinel strings so they don't leak into
|
|
99
|
+
* authConfig; otherwise returns the original value (including the empty
|
|
100
|
+
* string, which is a valid client secret).
|
|
101
|
+
*/
|
|
102
|
+
var sanitizeEnvValue = function (value) {
|
|
103
|
+
return value && (value.trim() === 'null' || value.trim() === 'undefined')
|
|
104
|
+
? undefined
|
|
105
|
+
: value;
|
|
106
|
+
};
|
|
93
107
|
/**
|
|
94
108
|
* Returns an object that represents the SASjs CLI configuration in a given file.
|
|
95
109
|
* @param {string} pathToFile - the path to the file in question.
|
|
@@ -808,7 +822,7 @@ exports.getProjectRoot = getProjectRoot;
|
|
|
808
822
|
function getAuthConfig(target) {
|
|
809
823
|
var _a, _b, _c, _d;
|
|
810
824
|
return __awaiter(this, void 0, void 0, function () {
|
|
811
|
-
var access_token, refresh_token, client, secret, sasjs, tokens;
|
|
825
|
+
var access_token, refresh_token, client, passwordGrantHint, secret, sasjs, tokens, sasjs, tokens;
|
|
812
826
|
return __generator(this, function (_e) {
|
|
813
827
|
switch (_e.label) {
|
|
814
828
|
case 0:
|
|
@@ -827,56 +841,83 @@ function getAuthConfig(target) {
|
|
|
827
841
|
refresh_token = ((_b = target === null || target === void 0 ? void 0 : target.authConfig) === null || _b === void 0 ? void 0 : _b.refresh_token)
|
|
828
842
|
? target.authConfig.refresh_token
|
|
829
843
|
: process.env.REFRESH_TOKEN;
|
|
830
|
-
refresh_token =
|
|
831
|
-
refresh_token &&
|
|
832
|
-
(refresh_token.trim() === 'null' || refresh_token.trim() === 'undefined')
|
|
833
|
-
? undefined
|
|
834
|
-
: refresh_token;
|
|
844
|
+
refresh_token = sanitizeEnvValue(refresh_token);
|
|
835
845
|
client = ((_c = target === null || target === void 0 ? void 0 : target.authConfig) === null || _c === void 0 ? void 0 : _c.client)
|
|
836
846
|
? target.authConfig.client
|
|
837
847
|
: process.env.CLIENT;
|
|
838
|
-
client =
|
|
839
|
-
|
|
840
|
-
? undefined
|
|
841
|
-
: client;
|
|
842
|
-
if (!client) {
|
|
843
|
-
throw new Error("Client ID was not found.\n Please make sure that the 'client' property is set in your local .env file or in the correct target authConfig in your global ~".concat(path_1.default.sep, ".sasjsrc file."));
|
|
844
|
-
}
|
|
848
|
+
client = sanitizeEnvValue(client);
|
|
849
|
+
passwordGrantHint = "\nAlternatively, run 'sasjs auth login -t ".concat(target === null || target === void 0 ? void 0 : target.name, "' to authenticate with your SAS username and password (no client/secret required).");
|
|
845
850
|
secret = ((_d = target === null || target === void 0 ? void 0 : target.authConfig) === null || _d === void 0 ? void 0 : _d.secret)
|
|
846
851
|
? target.authConfig.secret
|
|
847
852
|
: process.env.SECRET;
|
|
848
|
-
secret =
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
853
|
+
secret = sanitizeEnvValue(secret);
|
|
854
|
+
// A fresh access token is sufficient on its own - return it before
|
|
855
|
+
// requiring client/secret. This enables token-based authentication for
|
|
856
|
+
// targets without a registered OAuth client (see `sasjs auth login`).
|
|
857
|
+
// Use the same 300 s margin as the refresh path below for consistency —
|
|
858
|
+
// see the comment at line 713 for why the default 3600 s is too large.
|
|
859
|
+
if (access_token && !(0, auth_2.isAccessTokenExpiring)(access_token, 300)) {
|
|
860
|
+
return [2 /*return*/, {
|
|
861
|
+
access_token: access_token,
|
|
862
|
+
refresh_token: refresh_token || '',
|
|
863
|
+
client: client,
|
|
864
|
+
secret: client ? secret || '' : undefined
|
|
865
|
+
}];
|
|
866
|
+
}
|
|
867
|
+
if (!(!client && target.serverType === utils_1.ServerType.SasViya)) return [3 /*break*/, 5];
|
|
868
|
+
// Some estates issue opaque (non-JWT) refresh tokens, which cannot be
|
|
869
|
+
// expiry-checked client-side - isRefreshTokenExpiring treats those as
|
|
870
|
+
// usable and lets the server reject them if they have actually expired.
|
|
871
|
+
if (!refresh_token || (0, auth_2.isRefreshTokenExpiring)(refresh_token)) {
|
|
872
|
+
throw new Error("The access token has expired and no refresh token is available.".concat(passwordGrantHint));
|
|
873
|
+
}
|
|
874
|
+
sasjs = getSASjs(target);
|
|
875
|
+
return [4 /*yield*/, (0, auth_1.refreshTokens)(sasjs, auth_1.SAS_CLI_CLIENT_ID, '', refresh_token)];
|
|
876
|
+
case 3:
|
|
877
|
+
tokens = _e.sent();
|
|
878
|
+
access_token = (tokens === null || tokens === void 0 ? void 0 : tokens.access_token) || access_token;
|
|
879
|
+
refresh_token = (tokens === null || tokens === void 0 ? void 0 : tokens.refresh_token) || refresh_token;
|
|
880
|
+
return [4 /*yield*/, (0, exports.saveTokens)(target.name, access_token, refresh_token || '')];
|
|
881
|
+
case 4:
|
|
882
|
+
_e.sent();
|
|
883
|
+
return [2 /*return*/, {
|
|
884
|
+
access_token: access_token,
|
|
885
|
+
refresh_token: refresh_token || '',
|
|
886
|
+
client: undefined,
|
|
887
|
+
secret: undefined
|
|
888
|
+
}];
|
|
889
|
+
case 5:
|
|
890
|
+
if (!client) {
|
|
891
|
+
throw new Error("Client ID was not found.\n Please make sure that the 'client' property is set in your local .env file or in the correct target authConfig in your global ~".concat(path_1.default.sep, ".sasjsrc file.").concat(passwordGrantHint));
|
|
892
|
+
}
|
|
852
893
|
if (!secret) {
|
|
853
894
|
if (target.serverType === utils_1.ServerType.Sasjs)
|
|
854
895
|
secret = '';
|
|
855
896
|
else
|
|
856
|
-
throw new Error("Client secret was not found.\n Please make sure that the 'secret' property is set in your local .env file or in the correct target authConfig in your global ~".concat(path_1.default.sep, ".sasjsrc file."));
|
|
897
|
+
throw new Error("Client secret was not found.\n Please make sure that the 'secret' property is set in your local .env file or in the correct target authConfig in your global ~".concat(path_1.default.sep, ".sasjsrc file.").concat(passwordGrantHint));
|
|
857
898
|
}
|
|
858
|
-
if (!(0,
|
|
899
|
+
if (!(0, auth_2.isAccessTokenExpiring)(access_token, 300)) return [3 /*break*/, 11];
|
|
859
900
|
sasjs = getSASjs(target);
|
|
860
901
|
tokens = void 0;
|
|
861
|
-
if (!(0,
|
|
902
|
+
if (!(0, auth_2.isRefreshTokenExpiring)(refresh_token)) return [3 /*break*/, 7];
|
|
862
903
|
return [4 /*yield*/, (0, auth_1.getNewAccessToken)(sasjs, client, secret, target)];
|
|
863
|
-
case
|
|
904
|
+
case 6:
|
|
864
905
|
tokens = _e.sent();
|
|
865
|
-
return [3 /*break*/,
|
|
866
|
-
case
|
|
867
|
-
case
|
|
906
|
+
return [3 /*break*/, 9];
|
|
907
|
+
case 7: return [4 /*yield*/, (0, auth_1.refreshTokens)(sasjs, client, secret, refresh_token || '')];
|
|
908
|
+
case 8:
|
|
868
909
|
tokens = _e.sent();
|
|
869
|
-
_e.label =
|
|
870
|
-
case
|
|
910
|
+
_e.label = 9;
|
|
911
|
+
case 9:
|
|
871
912
|
access_token = (tokens === null || tokens === void 0 ? void 0 : tokens.access_token) || access_token;
|
|
872
913
|
refresh_token = (tokens === null || tokens === void 0 ? void 0 : tokens.refresh_token) || refresh_token;
|
|
873
|
-
return [4 /*yield*/, (0, exports.saveTokens)(target.name,
|
|
874
|
-
case
|
|
914
|
+
return [4 /*yield*/, (0, exports.saveTokens)(target.name, access_token, refresh_token || '', client, secret)];
|
|
915
|
+
case 10:
|
|
875
916
|
_e.sent();
|
|
876
|
-
_e.label =
|
|
877
|
-
case
|
|
917
|
+
_e.label = 11;
|
|
918
|
+
case 11: return [2 /*return*/, {
|
|
878
919
|
access_token: access_token,
|
|
879
|
-
refresh_token: refresh_token,
|
|
920
|
+
refresh_token: refresh_token || '',
|
|
880
921
|
client: client,
|
|
881
922
|
secret: secret
|
|
882
923
|
}];
|
|
@@ -892,8 +933,8 @@ exports.getAuthConfig = getAuthConfig;
|
|
|
892
933
|
* so that a rotated (single-use) refresh token from SAS Viya is never reused -
|
|
893
934
|
* reusing it on a subsequent CLI invocation would be rejected by the server.
|
|
894
935
|
*/
|
|
895
|
-
var saveTokens = function (targetName,
|
|
896
|
-
var isLocalTarget, VERBOSE, envFileContent, envFilePath, globalConfig, target, targetJson;
|
|
936
|
+
var saveTokens = function (targetName, access_token, refresh_token, client, secret) { return __awaiter(void 0, void 0, void 0, function () {
|
|
937
|
+
var isLocalTarget, VERBOSE, clientSecretContent, envFileContent, envFilePath, globalConfig, target, targetJson;
|
|
897
938
|
var _a, _b, _c;
|
|
898
939
|
return __generator(this, function (_d) {
|
|
899
940
|
switch (_d.label) {
|
|
@@ -908,7 +949,10 @@ var saveTokens = function (targetName, client, secret, access_token, refresh_tok
|
|
|
908
949
|
isLocalTarget = _d.sent();
|
|
909
950
|
if (!isLocalTarget) return [3 /*break*/, 3];
|
|
910
951
|
VERBOSE = process.env.VERBOSE;
|
|
911
|
-
|
|
952
|
+
clientSecretContent = client
|
|
953
|
+
? "CLIENT=".concat(client, "\nSECRET=").concat(secret || '', "\n")
|
|
954
|
+
: '';
|
|
955
|
+
envFileContent = "".concat(clientSecretContent, "ACCESS_TOKEN=").concat(access_token, "\nREFRESH_TOKEN=").concat(refresh_token, "\n").concat(VERBOSE ? 'VERBOSE=' + VERBOSE + '\n' : '');
|
|
912
956
|
envFilePath = path_1.default.join(process.projectDir, ".env.".concat(targetName));
|
|
913
957
|
return [4 /*yield*/, (0, utils_1.createFile)(envFilePath, envFileContent)];
|
|
914
958
|
case 2:
|
|
@@ -922,8 +966,10 @@ var saveTokens = function (targetName, client, secret, access_token, refresh_tok
|
|
|
922
966
|
if (!target) {
|
|
923
967
|
throw new Error(ERROR_MESSAGE(targetName).NOT_FOUND_TARGET_NAME);
|
|
924
968
|
}
|
|
925
|
-
targetJson = target
|
|
926
|
-
targetJson.authConfig = {
|
|
969
|
+
targetJson = __assign({}, target);
|
|
970
|
+
targetJson.authConfig = __assign(__assign(__assign({}, (targetJson.authConfig || {})), (client
|
|
971
|
+
? { client: client, secret: secret || '' }
|
|
972
|
+
: { client: undefined, secret: undefined })), { access_token: access_token, refresh_token: refresh_token });
|
|
927
973
|
return [4 /*yield*/, saveToGlobalConfig(new utils_1.Target(targetJson), globalConfig.defaultTarget === targetName)];
|
|
928
974
|
case 5:
|
|
929
975
|
_d.sent();
|
|
@@ -934,6 +980,38 @@ var saveTokens = function (targetName, client, secret, access_token, refresh_tok
|
|
|
934
980
|
});
|
|
935
981
|
}); };
|
|
936
982
|
exports.saveTokens = saveTokens;
|
|
983
|
+
/**
|
|
984
|
+
* Returns an `onTokensRefreshed` handler for adapter methods that refresh
|
|
985
|
+
* tokens internally (e.g. `executeScript` on long-running jobs). The adapter
|
|
986
|
+
* receives the rotated pair; this handler persists it via {@link saveTokens}
|
|
987
|
+
* exactly as {@link getAuthConfig} does for its own refreshes, so the stored
|
|
988
|
+
* single-use refresh token never goes stale between CLI invocations.
|
|
989
|
+
* Any client/secret already configured for the target is passed through so
|
|
990
|
+
* {@link saveTokens} does not drop it from `.env.{target}`.
|
|
991
|
+
*/
|
|
992
|
+
var persistTokensRefreshedByAdapter = function (target) {
|
|
993
|
+
return function (_a) {
|
|
994
|
+
var access_token = _a.access_token, refresh_token = _a.refresh_token;
|
|
995
|
+
return __awaiter(void 0, void 0, void 0, function () {
|
|
996
|
+
var rawClient, rawSecret, client, secret;
|
|
997
|
+
var _b, _c;
|
|
998
|
+
return __generator(this, function (_d) {
|
|
999
|
+
switch (_d.label) {
|
|
1000
|
+
case 0:
|
|
1001
|
+
rawClient = ((_b = target.authConfig) === null || _b === void 0 ? void 0 : _b.client) || process.env.CLIENT;
|
|
1002
|
+
rawSecret = ((_c = target.authConfig) === null || _c === void 0 ? void 0 : _c.secret) || process.env.SECRET;
|
|
1003
|
+
client = sanitizeEnvValue(rawClient);
|
|
1004
|
+
secret = sanitizeEnvValue(rawSecret);
|
|
1005
|
+
return [4 /*yield*/, (0, exports.saveTokens)(target.name, access_token, refresh_token, client, secret)];
|
|
1006
|
+
case 1:
|
|
1007
|
+
_d.sent();
|
|
1008
|
+
return [2 /*return*/];
|
|
1009
|
+
}
|
|
1010
|
+
});
|
|
1011
|
+
});
|
|
1012
|
+
};
|
|
1013
|
+
};
|
|
1014
|
+
exports.persistTokensRefreshedByAdapter = persistTokensRefreshedByAdapter;
|
|
937
1015
|
/**
|
|
938
1016
|
* Gets the auth config for the specified SAS9 target.
|
|
939
1017
|
* @param {Target} target - the target to get an access token for.
|
|
@@ -982,11 +1060,12 @@ exports.getAuthConfigSAS9 = getAuthConfigSAS9;
|
|
|
982
1060
|
* should use `getAuthConfig` instead.
|
|
983
1061
|
* @param {object} target - the target to get an access token for.
|
|
984
1062
|
* @param {string} checkIfExpiring - flag that indicates whether to do an expiry check.
|
|
1063
|
+
* @internal One-shot/test-only. User-facing commands must use {@link getAuthConfig}.
|
|
985
1064
|
*/
|
|
986
1065
|
function getAccessToken(target, checkIfExpiring) {
|
|
987
1066
|
if (checkIfExpiring === void 0) { checkIfExpiring = true; }
|
|
988
1067
|
return __awaiter(this, void 0, void 0, function () {
|
|
989
|
-
var accessToken, sasjs, client, secret, refreshToken, tokens;
|
|
1068
|
+
var accessToken, sasjs, passwordGrantHint, client, secret, refreshToken, tokens;
|
|
990
1069
|
return __generator(this, function (_a) {
|
|
991
1070
|
switch (_a.label) {
|
|
992
1071
|
case 0:
|
|
@@ -1007,38 +1086,29 @@ function getAccessToken(target, checkIfExpiring) {
|
|
|
1007
1086
|
accessToken.trim() === 'undefined') {
|
|
1008
1087
|
throw new Error("A valid access token was not found.\nPlease provide an access token in the access_token property in your .env file or as part of the authInfo in your target configuration (sasjsconfig.json).");
|
|
1009
1088
|
}
|
|
1010
|
-
if (!(checkIfExpiring && (0,
|
|
1089
|
+
if (!(checkIfExpiring && (0, auth_2.isAccessTokenExpiring)(accessToken))) return [3 /*break*/, 7];
|
|
1011
1090
|
sasjs = getSASjs(target);
|
|
1091
|
+
passwordGrantHint = "\nAlternatively, run 'sasjs auth login -t ".concat(target === null || target === void 0 ? void 0 : target.name, "' to authenticate with your SAS username and password (no client/secret required).");
|
|
1012
1092
|
client = target.authConfig && target.authConfig.client
|
|
1013
1093
|
? target.authConfig.client
|
|
1014
1094
|
: process.env.CLIENT;
|
|
1015
|
-
client =
|
|
1016
|
-
client && (client.trim() === 'null' || client.trim() === 'undefined')
|
|
1017
|
-
? undefined
|
|
1018
|
-
: client;
|
|
1095
|
+
client = sanitizeEnvValue(client);
|
|
1019
1096
|
if (!client) {
|
|
1020
|
-
throw new Error("Client ID was not found.\n Please make sure that the 'client' property is set in your local .env file or in the correct target authConfig in your global ~".concat(path_1.default.sep, ".sasjsrc file."));
|
|
1097
|
+
throw new Error("Client ID was not found.\n Please make sure that the 'client' property is set in your local .env file or in the correct target authConfig in your global ~".concat(path_1.default.sep, ".sasjsrc file.").concat(passwordGrantHint));
|
|
1021
1098
|
}
|
|
1022
1099
|
secret = target.authConfig && target.authConfig.secret
|
|
1023
1100
|
? target.authConfig.secret
|
|
1024
1101
|
: process.env.SECRET;
|
|
1025
|
-
secret =
|
|
1026
|
-
secret && (secret.trim() === 'null' || secret.trim() === 'undefined')
|
|
1027
|
-
? undefined
|
|
1028
|
-
: secret;
|
|
1102
|
+
secret = sanitizeEnvValue(secret);
|
|
1029
1103
|
if (!secret) {
|
|
1030
|
-
throw new Error("Client secret was not found.\n Please make sure that the 'secret' property is set in your local .env file or in the correct target authConfig in your global ~".concat(path_1.default.sep, ".sasjsrc file."));
|
|
1104
|
+
throw new Error("Client secret was not found.\n Please make sure that the 'secret' property is set in your local .env file or in the correct target authConfig in your global ~".concat(path_1.default.sep, ".sasjsrc file.").concat(passwordGrantHint));
|
|
1031
1105
|
}
|
|
1032
1106
|
refreshToken = target.authConfig && target.authConfig.refresh_token
|
|
1033
1107
|
? target.authConfig.refresh_token
|
|
1034
1108
|
: process.env.REFRESH_TOKEN;
|
|
1035
|
-
refreshToken =
|
|
1036
|
-
refreshToken &&
|
|
1037
|
-
(refreshToken.trim() === 'null' || refreshToken.trim() === 'undefined')
|
|
1038
|
-
? undefined
|
|
1039
|
-
: refreshToken;
|
|
1109
|
+
refreshToken = sanitizeEnvValue(refreshToken);
|
|
1040
1110
|
tokens = void 0;
|
|
1041
|
-
if (!(0,
|
|
1111
|
+
if (!(0, auth_2.isRefreshTokenExpiring)(refreshToken)) return [3 /*break*/, 4];
|
|
1042
1112
|
return [4 /*yield*/, (0, auth_1.getNewAccessToken)(sasjs, client, secret, target)];
|
|
1043
1113
|
case 3:
|
|
1044
1114
|
tokens = _a.sent();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|