@sasjs/cli 4.18.5 → 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.
@@ -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.refreshTokens = exports.isRefreshTokenExpiring = exports.isAccessTokenExpiring = exports.getAuthCode = exports.getAuthUrl = void 0;
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;
@@ -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, client: string, secret: string, access_token: string, refresh_token: string) => Promise<void>;
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
  /**
@@ -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
- client && (client.trim() === 'null' || client.trim() === 'undefined')
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
- secret && (secret.trim() === 'null' || secret.trim() === 'undefined')
850
- ? undefined
851
- : secret;
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, auth_1.isAccessTokenExpiring)(access_token)) return [3 /*break*/, 8];
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, auth_1.isRefreshTokenExpiring)(refresh_token)) return [3 /*break*/, 4];
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 3:
904
+ case 6:
864
905
  tokens = _e.sent();
865
- return [3 /*break*/, 6];
866
- case 4: return [4 /*yield*/, (0, auth_1.refreshTokens)(sasjs, client, secret, refresh_token)];
867
- case 5:
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 = 6;
870
- case 6:
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, client, secret, access_token, refresh_token || '')];
874
- case 7:
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 = 8;
877
- case 8: return [2 /*return*/, {
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, client, secret, access_token, refresh_token) { return __awaiter(void 0, void 0, void 0, function () {
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
- envFileContent = "CLIENT=".concat(client, "\nSECRET=").concat(secret, "\nACCESS_TOKEN=").concat(access_token, "\nREFRESH_TOKEN=").concat(refresh_token, "\n").concat(VERBOSE ? 'VERBOSE=' + VERBOSE + '\n' : '');
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.toJson();
926
- targetJson.authConfig = { client: client, secret: secret, access_token: access_token, refresh_token: refresh_token };
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, auth_1.isAccessTokenExpiring)(accessToken))) return [3 /*break*/, 7];
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, auth_1.isRefreshTokenExpiring)(refreshToken)) return [3 /*break*/, 4];
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 {};