@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
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __assign = (this && this.__assign) || function () {
|
|
3
|
+
__assign = Object.assign || function(t) {
|
|
4
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
5
|
+
s = arguments[i];
|
|
6
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
7
|
+
t[p] = s[p];
|
|
8
|
+
}
|
|
9
|
+
return t;
|
|
10
|
+
};
|
|
11
|
+
return __assign.apply(this, arguments);
|
|
12
|
+
};
|
|
13
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
14
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
15
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
16
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
17
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
18
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
19
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
23
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
24
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
25
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
26
|
+
function step(op) {
|
|
27
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
28
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
29
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
30
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
31
|
+
switch (op[0]) {
|
|
32
|
+
case 0: case 1: t = op; break;
|
|
33
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
34
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
35
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
36
|
+
default:
|
|
37
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
38
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
39
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
40
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
41
|
+
if (t[2]) _.ops.pop();
|
|
42
|
+
_.trys.pop(); continue;
|
|
43
|
+
}
|
|
44
|
+
op = body.call(thisArg, _);
|
|
45
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
46
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
var __read = (this && this.__read) || function (o, n) {
|
|
50
|
+
var m = typeof Symbol === "function" && o[Symbol.iterator];
|
|
51
|
+
if (!m) return o;
|
|
52
|
+
var i = m.call(o), r, ar = [], e;
|
|
53
|
+
try {
|
|
54
|
+
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
|
|
55
|
+
}
|
|
56
|
+
catch (error) { e = { error: error }; }
|
|
57
|
+
finally {
|
|
58
|
+
try {
|
|
59
|
+
if (r && !r.done && (m = i["return"])) m.call(i);
|
|
60
|
+
}
|
|
61
|
+
finally { if (e) throw e.error; }
|
|
62
|
+
}
|
|
63
|
+
return ar;
|
|
64
|
+
};
|
|
65
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
66
|
+
var utils_1 = require("@sasjs/utils");
|
|
67
|
+
var node_1 = require("@sasjs/adapter/node");
|
|
68
|
+
var auth_1 = require("../auth");
|
|
69
|
+
// SasjsRequestClient is imported inside auth.ts from '@sasjs/adapter/node'.
|
|
70
|
+
// We mock the module so we can control the .get() and .post() responses.
|
|
71
|
+
jest.mock('@sasjs/adapter/node', function () {
|
|
72
|
+
var actual = jest.requireActual('@sasjs/adapter/node');
|
|
73
|
+
return __assign(__assign({}, actual), { SasjsRequestClient: jest.fn().mockImplementation(function () { return ({
|
|
74
|
+
get: jest.fn(),
|
|
75
|
+
post: jest.fn()
|
|
76
|
+
}); }) });
|
|
77
|
+
});
|
|
78
|
+
// Re-import after mock setup so the module under test picks up the mock.
|
|
79
|
+
// (Re-declared with the same name; TS allows this in spec files via the
|
|
80
|
+
// `esModuleInterop` + `allowJs` combination the CLI's tsconfig enables.)
|
|
81
|
+
var node_2 = require("@sasjs/adapter/node");
|
|
82
|
+
var target = new utils_1.Target({
|
|
83
|
+
name: 'test',
|
|
84
|
+
serverType: utils_1.ServerType.SasViya,
|
|
85
|
+
serverUrl: 'https://viya.example.com',
|
|
86
|
+
appLoc: '/Public/test',
|
|
87
|
+
contextName: 'test context'
|
|
88
|
+
});
|
|
89
|
+
describe('fetchLoggedInUser', function () {
|
|
90
|
+
afterEach(function () {
|
|
91
|
+
jest.clearAllMocks();
|
|
92
|
+
});
|
|
93
|
+
it('should return the user id and name when the identity endpoint responds', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
94
|
+
var mockGet, result;
|
|
95
|
+
return __generator(this, function (_a) {
|
|
96
|
+
switch (_a.label) {
|
|
97
|
+
case 0:
|
|
98
|
+
mockGet = jest.fn().mockResolvedValue({
|
|
99
|
+
result: { id: 'sastest', name: 'SAS Test User' }
|
|
100
|
+
});
|
|
101
|
+
node_2.SasjsRequestClient.mockImplementation(function () { return ({
|
|
102
|
+
get: mockGet
|
|
103
|
+
}); });
|
|
104
|
+
return [4 /*yield*/, (0, auth_1.fetchLoggedInUser)(target, 'valid-token')];
|
|
105
|
+
case 1:
|
|
106
|
+
result = _a.sent();
|
|
107
|
+
expect(result).toEqual({ id: 'sastest', name: 'SAS Test User' });
|
|
108
|
+
return [2 /*return*/];
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}); });
|
|
112
|
+
it('should throw a friendly error when the identity endpoint returns an error', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
113
|
+
var mockGet;
|
|
114
|
+
return __generator(this, function (_a) {
|
|
115
|
+
switch (_a.label) {
|
|
116
|
+
case 0:
|
|
117
|
+
mockGet = jest
|
|
118
|
+
.fn()
|
|
119
|
+
.mockRejectedValue(new Error('Request failed with status 401'));
|
|
120
|
+
node_2.SasjsRequestClient.mockImplementation(function () { return ({
|
|
121
|
+
get: mockGet
|
|
122
|
+
}); });
|
|
123
|
+
return [4 /*yield*/, expect((0, auth_1.fetchLoggedInUser)(target, 'bad-token')).rejects.toThrow("Unable to verify the access token against ".concat(target.serverUrl))];
|
|
124
|
+
case 1:
|
|
125
|
+
_a.sent();
|
|
126
|
+
return [2 /*return*/];
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}); });
|
|
130
|
+
it('should throw an error when the identity endpoint returns no user id', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
131
|
+
var mockGet;
|
|
132
|
+
return __generator(this, function (_a) {
|
|
133
|
+
switch (_a.label) {
|
|
134
|
+
case 0:
|
|
135
|
+
mockGet = jest.fn().mockResolvedValue({ result: {} });
|
|
136
|
+
node_2.SasjsRequestClient.mockImplementation(function () { return ({
|
|
137
|
+
get: mockGet
|
|
138
|
+
}); });
|
|
139
|
+
return [4 /*yield*/, expect((0, auth_1.fetchLoggedInUser)(target, 'token')).rejects.toThrow('Login succeeded but the identity endpoint returned no user id.')];
|
|
140
|
+
case 1:
|
|
141
|
+
_a.sent();
|
|
142
|
+
return [2 /*return*/];
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
}); });
|
|
146
|
+
});
|
|
147
|
+
describe('getTokensWithPasswordGrant', function () {
|
|
148
|
+
var user = 'myuser';
|
|
149
|
+
var pass = 'mypass';
|
|
150
|
+
afterEach(function () {
|
|
151
|
+
jest.clearAllMocks();
|
|
152
|
+
});
|
|
153
|
+
var mockPostSuccess = function (tokens) {
|
|
154
|
+
var mockPost = jest.fn().mockResolvedValue({ result: tokens });
|
|
155
|
+
node_2.SasjsRequestClient.mockImplementation(function () { return ({
|
|
156
|
+
post: mockPost
|
|
157
|
+
}); });
|
|
158
|
+
return mockPost;
|
|
159
|
+
};
|
|
160
|
+
var mockPostFailure = function (err) {
|
|
161
|
+
var mockPost = jest.fn().mockRejectedValue(err);
|
|
162
|
+
node_2.SasjsRequestClient.mockImplementation(function () { return ({
|
|
163
|
+
post: mockPost
|
|
164
|
+
}); });
|
|
165
|
+
return mockPost;
|
|
166
|
+
};
|
|
167
|
+
it('should send a password-grant request with the correct Basic auth header and grant_type', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
168
|
+
var mockPost, result, _a, endpoint, body, contentType, headers, expectedBasic;
|
|
169
|
+
return __generator(this, function (_b) {
|
|
170
|
+
switch (_b.label) {
|
|
171
|
+
case 0:
|
|
172
|
+
mockPost = mockPostSuccess({
|
|
173
|
+
access_token: 'access-123',
|
|
174
|
+
refresh_token: 'refresh-456'
|
|
175
|
+
});
|
|
176
|
+
return [4 /*yield*/, (0, auth_1.getTokensWithPasswordGrant)(target, user, pass)];
|
|
177
|
+
case 1:
|
|
178
|
+
result = _b.sent();
|
|
179
|
+
expect(result).toEqual({
|
|
180
|
+
access_token: 'access-123',
|
|
181
|
+
refresh_token: 'refresh-456'
|
|
182
|
+
});
|
|
183
|
+
// Verify the post() call: endpoint, body, and headers.
|
|
184
|
+
expect(mockPost).toHaveBeenCalledTimes(1);
|
|
185
|
+
_a = __read(mockPost.mock.calls[0], 5), endpoint = _a[0], body = _a[1], contentType = _a[3], headers = _a[4];
|
|
186
|
+
expect(endpoint).toBe('/SASLogon/oauth/token');
|
|
187
|
+
expect(contentType).toBe('application/x-www-form-urlencoded');
|
|
188
|
+
expectedBasic = Buffer.from("".concat(auth_1.SAS_CLI_CLIENT_ID, ":")).toString('base64');
|
|
189
|
+
expect(headers.Authorization).toBe("Basic ".concat(expectedBasic));
|
|
190
|
+
// Body must carry grant_type=password plus the user credentials.
|
|
191
|
+
expect(body).toBeInstanceOf(URLSearchParams);
|
|
192
|
+
expect(body.get('grant_type')).toBe('password');
|
|
193
|
+
expect(body.get('username')).toBe(user);
|
|
194
|
+
expect(body.get('password')).toBe(pass);
|
|
195
|
+
return [2 /*return*/];
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
}); });
|
|
199
|
+
it('should throw a friendly error when the token endpoint returns a non-200 response', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
200
|
+
return __generator(this, function (_a) {
|
|
201
|
+
switch (_a.label) {
|
|
202
|
+
case 0:
|
|
203
|
+
mockPostFailure(new Error('Request failed with status 401'));
|
|
204
|
+
return [4 /*yield*/, expect((0, auth_1.getTokensWithPasswordGrant)(target, user, pass)).rejects.toThrow("Login failed for user '".concat(user, "' on ").concat(target.serverUrl))];
|
|
205
|
+
case 1:
|
|
206
|
+
_a.sent();
|
|
207
|
+
return [2 /*return*/];
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
}); });
|
|
211
|
+
it('should pass through CertificateError without wrapping it', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
212
|
+
var certErr;
|
|
213
|
+
return __generator(this, function (_a) {
|
|
214
|
+
switch (_a.label) {
|
|
215
|
+
case 0:
|
|
216
|
+
certErr = new node_1.CertificateError('https://viya.example.com');
|
|
217
|
+
mockPostFailure(certErr);
|
|
218
|
+
return [4 /*yield*/, expect((0, auth_1.getTokensWithPasswordGrant)(target, user, pass)).rejects.toBe(certErr)];
|
|
219
|
+
case 1:
|
|
220
|
+
_a.sent();
|
|
221
|
+
return [2 /*return*/];
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
}); });
|
|
225
|
+
it('should throw when the token endpoint response has no access_token', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
226
|
+
return __generator(this, function (_a) {
|
|
227
|
+
switch (_a.label) {
|
|
228
|
+
case 0:
|
|
229
|
+
mockPostSuccess({ refresh_token: 'refresh-only' });
|
|
230
|
+
return [4 /*yield*/, expect((0, auth_1.getTokensWithPasswordGrant)(target, user, pass)).rejects.toThrow('Login failed: the token endpoint did not return an access token.')];
|
|
231
|
+
case 1:
|
|
232
|
+
_a.sent();
|
|
233
|
+
return [2 /*return*/];
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
}); });
|
|
237
|
+
it('should default refresh_token to empty string when the server omits it', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
238
|
+
var result;
|
|
239
|
+
return __generator(this, function (_a) {
|
|
240
|
+
switch (_a.label) {
|
|
241
|
+
case 0:
|
|
242
|
+
mockPostSuccess({ access_token: 'access-only' });
|
|
243
|
+
return [4 /*yield*/, (0, auth_1.getTokensWithPasswordGrant)(target, user, pass)];
|
|
244
|
+
case 1:
|
|
245
|
+
result = _a.sent();
|
|
246
|
+
expect(result.access_token).toBe('access-only');
|
|
247
|
+
// Some Viya deployments omit refresh_token for the password grant; we
|
|
248
|
+
// normalize to '' so downstream code (saveTokens, config) can treat it
|
|
249
|
+
// as a plain string.
|
|
250
|
+
expect(result.refresh_token).toBe('');
|
|
251
|
+
return [2 /*return*/];
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
}); });
|
|
255
|
+
});
|