@sasjs/cli 4.18.6 → 4.20.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.
@@ -1,4 +1,15 @@
1
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
+ };
2
13
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
14
  if (k2 === undefined) k2 = k;
4
15
  var desc = Object.getOwnPropertyDescriptor(m, k);
@@ -64,12 +75,27 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
64
75
  Object.defineProperty(exports, "__esModule", { value: true });
65
76
  var config_1 = require("../config");
66
77
  var authUtils = __importStar(require("../auth"));
78
+ var sasjsAuthUtils = __importStar(require("@sasjs/utils/auth"));
67
79
  var fileUtils = __importStar(require("@sasjs/utils/file"));
68
80
  var dotenv_1 = __importDefault(require("dotenv"));
69
81
  var path_1 = __importDefault(require("path"));
70
82
  var utils_1 = require("@sasjs/utils");
83
+ var file_1 = require("@sasjs/utils/file");
71
84
  var test_1 = require("../test");
72
85
  var setConstants_1 = require("../setConstants");
86
+ // The compiled @sasjs/utils/auth module exposes getters that jest.spyOn
87
+ // cannot redefine - mock the module with configurable jest.fn()s that
88
+ // default to the real implementations.
89
+ jest.mock('@sasjs/utils/auth', function () {
90
+ var actual = jest.requireActual('@sasjs/utils/auth');
91
+ return __assign(__assign({}, actual), { isAccessTokenExpiring: jest.fn(), isRefreshTokenExpiring: jest.fn() });
92
+ });
93
+ var actualAuthUtils = jest.requireActual('@sasjs/utils/auth');
94
+ beforeEach(function () {
95
+ ;
96
+ sasjsAuthUtils.isAccessTokenExpiring.mockImplementation(actualAuthUtils.isAccessTokenExpiring);
97
+ sasjsAuthUtils.isRefreshTokenExpiring.mockImplementation(actualAuthUtils.isRefreshTokenExpiring);
98
+ });
73
99
  describe('getAccessToken', function () {
74
100
  beforeEach(function () { return __awaiter(void 0, void 0, void 0, function () {
75
101
  return __generator(this, function (_a) {
@@ -189,12 +215,9 @@ describe('getAccessToken', function () {
189
215
  return __generator(this, function (_a) {
190
216
  switch (_a.label) {
191
217
  case 0:
192
- jest
193
- .spyOn(authUtils, 'isAccessTokenExpiring')
194
- .mockImplementation(function () { return true; });
195
- jest
196
- .spyOn(authUtils, 'isRefreshTokenExpiring')
197
- .mockImplementation(function () { return false; });
218
+ ;
219
+ sasjsAuthUtils.isAccessTokenExpiring.mockImplementation(function () { return true; });
220
+ sasjsAuthUtils.isRefreshTokenExpiring.mockImplementation(function () { return false; });
198
221
  jest.spyOn(authUtils, 'getNewAccessToken');
199
222
  jest.spyOn(authUtils, 'refreshTokens').mockImplementation(function () {
200
223
  return Promise.resolve({
@@ -212,8 +235,8 @@ describe('getAccessToken', function () {
212
235
  return [4 /*yield*/, (0, config_1.getAccessToken)(target, true)];
213
236
  case 1:
214
237
  token = _a.sent();
215
- expect(authUtils.isAccessTokenExpiring).toHaveBeenCalledTimes(1);
216
- expect(authUtils.isRefreshTokenExpiring).toHaveBeenCalledTimes(1);
238
+ expect(sasjsAuthUtils.isAccessTokenExpiring).toHaveBeenCalledTimes(1);
239
+ expect(sasjsAuthUtils.isRefreshTokenExpiring).toHaveBeenCalledTimes(1);
217
240
  expect(authUtils.refreshTokens).toHaveBeenCalledTimes(1);
218
241
  expect(authUtils.getNewAccessToken).not.toHaveBeenCalled();
219
242
  expect(token).toEqual('N3WT0K3N');
@@ -226,12 +249,9 @@ describe('getAccessToken', function () {
226
249
  return __generator(this, function (_a) {
227
250
  switch (_a.label) {
228
251
  case 0:
229
- jest
230
- .spyOn(authUtils, 'isAccessTokenExpiring')
231
- .mockImplementation(function () { return true; });
232
- jest
233
- .spyOn(authUtils, 'isRefreshTokenExpiring')
234
- .mockImplementation(function () { return true; });
252
+ ;
253
+ sasjsAuthUtils.isAccessTokenExpiring.mockImplementation(function () { return true; });
254
+ sasjsAuthUtils.isRefreshTokenExpiring.mockImplementation(function () { return true; });
235
255
  jest.spyOn(authUtils, 'refreshTokens');
236
256
  jest.spyOn(authUtils, 'getNewAccessToken').mockImplementation(function () {
237
257
  return Promise.resolve({
@@ -250,8 +270,8 @@ describe('getAccessToken', function () {
250
270
  return [4 /*yield*/, (0, config_1.getAccessToken)(target, true)];
251
271
  case 1:
252
272
  token = _a.sent();
253
- expect(authUtils.isAccessTokenExpiring).toHaveBeenCalledTimes(1);
254
- expect(authUtils.isRefreshTokenExpiring).toHaveBeenCalledTimes(1);
273
+ expect(sasjsAuthUtils.isAccessTokenExpiring).toHaveBeenCalledTimes(1);
274
+ expect(sasjsAuthUtils.isRefreshTokenExpiring).toHaveBeenCalledTimes(1);
255
275
  expect(authUtils.refreshTokens).not.toHaveBeenCalled();
256
276
  expect(authUtils.getNewAccessToken).toHaveBeenCalledTimes(1);
257
277
  expect(token).toEqual('N3WT0K3N');
@@ -264,12 +284,9 @@ describe('getAccessToken', function () {
264
284
  return __generator(this, function (_a) {
265
285
  switch (_a.label) {
266
286
  case 0:
267
- jest
268
- .spyOn(authUtils, 'isAccessTokenExpiring')
269
- .mockImplementation(function () { return true; });
270
- jest
271
- .spyOn(authUtils, 'isRefreshTokenExpiring')
272
- .mockImplementation(function () { return true; });
287
+ ;
288
+ sasjsAuthUtils.isAccessTokenExpiring.mockImplementation(function () { return true; });
289
+ sasjsAuthUtils.isRefreshTokenExpiring.mockImplementation(function () { return true; });
273
290
  jest.spyOn(authUtils, 'refreshTokens');
274
291
  jest.spyOn(authUtils, 'getNewAccessToken').mockImplementation(function () {
275
292
  return Promise.resolve({
@@ -287,8 +304,8 @@ describe('getAccessToken', function () {
287
304
  return [4 /*yield*/, (0, config_1.getAccessToken)(target, true)];
288
305
  case 1:
289
306
  token = _a.sent();
290
- expect(authUtils.isAccessTokenExpiring).toHaveBeenCalledTimes(1);
291
- expect(authUtils.isRefreshTokenExpiring).toHaveBeenCalledTimes(1);
307
+ expect(sasjsAuthUtils.isAccessTokenExpiring).toHaveBeenCalledTimes(1);
308
+ expect(sasjsAuthUtils.isRefreshTokenExpiring).toHaveBeenCalledTimes(1);
292
309
  expect(authUtils.refreshTokens).not.toHaveBeenCalled();
293
310
  expect(authUtils.getNewAccessToken).toHaveBeenCalledTimes(1);
294
311
  expect(token).toEqual('N3WT0K3N');
@@ -301,17 +318,17 @@ describe('getAccessToken', function () {
301
318
  return __generator(this, function (_a) {
302
319
  switch (_a.label) {
303
320
  case 0:
304
- jest
305
- .spyOn(authUtils, 'isAccessTokenExpiring')
306
- .mockImplementation(function () { return true; });
321
+ ;
322
+ sasjsAuthUtils.isAccessTokenExpiring.mockImplementation(function () { return true; });
307
323
  target = {
324
+ name: 'viya',
308
325
  authConfig: {
309
326
  access_token: 'T0K3N',
310
327
  secret: '53CR3T'
311
328
  }
312
329
  };
313
330
  process.env.CLIENT = undefined;
314
- return [4 /*yield*/, expect((0, config_1.getAccessToken)(target)).rejects.toThrow()];
331
+ return [4 /*yield*/, expect((0, config_1.getAccessToken)(target)).rejects.toThrow(/sasjs auth login -t viya/)];
315
332
  case 1:
316
333
  _a.sent();
317
334
  return [2 /*return*/];
@@ -323,17 +340,98 @@ describe('getAccessToken', function () {
323
340
  return __generator(this, function (_a) {
324
341
  switch (_a.label) {
325
342
  case 0:
326
- jest
327
- .spyOn(authUtils, 'isAccessTokenExpiring')
328
- .mockImplementation(function () { return true; });
343
+ ;
344
+ sasjsAuthUtils.isAccessTokenExpiring.mockImplementation(function () { return true; });
329
345
  target = {
346
+ name: 'viya',
330
347
  authConfig: {
331
348
  access_token: 'T0K3N',
332
349
  client: 'CL13NT'
333
350
  }
334
351
  };
335
352
  process.env.SECRET = undefined;
336
- return [4 /*yield*/, expect((0, config_1.getAccessToken)(target)).rejects.toThrow()];
353
+ return [4 /*yield*/, expect((0, config_1.getAccessToken)(target)).rejects.toThrow(/sasjs auth login -t viya/)];
354
+ case 1:
355
+ _a.sent();
356
+ return [2 /*return*/];
357
+ }
358
+ });
359
+ }); });
360
+ });
361
+ describe('getAuthConfig', function () {
362
+ beforeEach(function () { return __awaiter(void 0, void 0, void 0, function () {
363
+ return __generator(this, function (_a) {
364
+ process.projectDir = process.cwd();
365
+ process.env.ACCESS_TOKEN = undefined;
366
+ process.env.CLIENT = undefined;
367
+ process.env.SECRET = undefined;
368
+ process.env.REFRESH_TOKEN = undefined;
369
+ return [2 /*return*/];
370
+ });
371
+ }); });
372
+ afterEach(function () {
373
+ jest.resetAllMocks();
374
+ });
375
+ it('should return a fresh access token even when client and secret are not configured', function () { return __awaiter(void 0, void 0, void 0, function () {
376
+ var target, authConfig;
377
+ return __generator(this, function (_a) {
378
+ switch (_a.label) {
379
+ case 0:
380
+ ;
381
+ sasjsAuthUtils.isAccessTokenExpiring.mockImplementation(function () { return false; });
382
+ target = {
383
+ name: 'viya',
384
+ authConfig: {
385
+ access_token: 'T0K3N'
386
+ }
387
+ };
388
+ return [4 /*yield*/, (0, config_1.getAuthConfig)(target)];
389
+ case 1:
390
+ authConfig = _a.sent();
391
+ expect(authConfig.access_token).toEqual('T0K3N');
392
+ return [2 /*return*/];
393
+ }
394
+ });
395
+ }); });
396
+ it('should return the configured secret on the fresh-token early return when client/secret are set', function () { return __awaiter(void 0, void 0, void 0, function () {
397
+ var target, authConfig;
398
+ return __generator(this, function (_a) {
399
+ switch (_a.label) {
400
+ case 0:
401
+ ;
402
+ sasjsAuthUtils.isAccessTokenExpiring.mockImplementation(function () { return false; });
403
+ target = {
404
+ name: 'viya',
405
+ authConfig: {
406
+ access_token: 'T0K3N',
407
+ client: 'CL13NT',
408
+ secret: '53CR3T'
409
+ }
410
+ };
411
+ return [4 /*yield*/, (0, config_1.getAuthConfig)(target)];
412
+ case 1:
413
+ authConfig = _a.sent();
414
+ expect(authConfig.access_token).toEqual('T0K3N');
415
+ expect(authConfig.client).toEqual('CL13NT');
416
+ expect(authConfig.secret).toEqual('53CR3T');
417
+ return [2 /*return*/];
418
+ }
419
+ });
420
+ }); });
421
+ it('should throw an error mentioning sasjs auth login when the token is expiring and no client is available', function () { return __awaiter(void 0, void 0, void 0, function () {
422
+ var target;
423
+ return __generator(this, function (_a) {
424
+ switch (_a.label) {
425
+ case 0:
426
+ ;
427
+ sasjsAuthUtils.isAccessTokenExpiring.mockImplementation(function () { return true; });
428
+ target = {
429
+ name: 'viya',
430
+ authConfig: {
431
+ access_token: 'T0K3N'
432
+ }
433
+ };
434
+ return [4 /*yield*/, expect((0, config_1.getAuthConfig)(target)).rejects.toThrow(/sasjs auth login/)];
337
435
  case 1:
338
436
  _a.sent();
339
437
  return [2 /*return*/];
@@ -341,6 +439,210 @@ describe('getAccessToken', function () {
341
439
  });
342
440
  }); });
343
441
  });
442
+ describe('getAuthConfig - opaque refresh tokens', function () {
443
+ var opaqueRefreshToken = '1f8da55057bd4f50a6577f0bc2b38b1a-r';
444
+ var projectDir;
445
+ var setupLocalProjectWithTarget = function (target) { return __awaiter(void 0, void 0, void 0, function () {
446
+ return __generator(this, function (_a) {
447
+ switch (_a.label) {
448
+ case 0:
449
+ projectDir = path_1.default.join(process.env.TEMP || process.env.TMP || '/tmp', "sasjs-cli-test-".concat((0, utils_1.generateTimestamp)()));
450
+ process.projectDir = projectDir;
451
+ return [4 /*yield*/, (0, setConstants_1.setConstants)()];
452
+ case 1:
453
+ _a.sent();
454
+ return [4 /*yield*/, (0, utils_1.createFile)(path_1.default.join(projectDir, 'sasjs', 'sasjsconfig.json'), JSON.stringify({ targets: [target.toJson()] }))];
455
+ case 2:
456
+ _a.sent();
457
+ return [2 /*return*/];
458
+ }
459
+ });
460
+ }); };
461
+ beforeEach(function () {
462
+ process.env.ACCESS_TOKEN = undefined;
463
+ process.env.CLIENT = undefined;
464
+ process.env.SECRET = undefined;
465
+ process.env.REFRESH_TOKEN = undefined;
466
+ });
467
+ afterEach(function () { return __awaiter(void 0, void 0, void 0, function () {
468
+ return __generator(this, function (_a) {
469
+ switch (_a.label) {
470
+ case 0:
471
+ jest.resetAllMocks();
472
+ return [4 /*yield*/, (0, utils_1.deleteFile)(projectDir).catch(function () { })];
473
+ case 1:
474
+ _a.sent();
475
+ return [2 /*return*/];
476
+ }
477
+ });
478
+ }); });
479
+ it('should refresh without crashing and persist the rotated pair in the client/secret branch', function () { return __awaiter(void 0, void 0, void 0, function () {
480
+ var target, authConfig, envContent;
481
+ return __generator(this, function (_a) {
482
+ switch (_a.label) {
483
+ case 0:
484
+ // The real isRefreshTokenExpiring must be used - it should treat the
485
+ // opaque token as usable rather than throwing InvalidTokenError.
486
+ ;
487
+ sasjsAuthUtils.isAccessTokenExpiring.mockImplementation(function () { return true; });
488
+ jest.spyOn(authUtils, 'refreshTokens').mockImplementation(function () {
489
+ return Promise.resolve({
490
+ access_token: 'N3WT0K3N',
491
+ refresh_token: 'N3WR3FR35H'
492
+ });
493
+ });
494
+ target = new utils_1.Target({
495
+ name: 'opaq',
496
+ serverType: utils_1.ServerType.SasViya,
497
+ serverUrl: 'https://example.com',
498
+ appLoc: '/Public/app',
499
+ contextName: 'test context',
500
+ authConfig: {
501
+ access_token: 'T0K3N',
502
+ refresh_token: opaqueRefreshToken,
503
+ client: 'CL13NT',
504
+ secret: '53CR3T'
505
+ }
506
+ });
507
+ return [4 /*yield*/, setupLocalProjectWithTarget(target)];
508
+ case 1:
509
+ _a.sent();
510
+ return [4 /*yield*/, (0, config_1.getAuthConfig)(target)];
511
+ case 2:
512
+ authConfig = _a.sent();
513
+ expect(authUtils.refreshTokens).toHaveBeenCalledTimes(1);
514
+ expect(authConfig.access_token).toEqual('N3WT0K3N');
515
+ return [4 /*yield*/, (0, file_1.readFile)(path_1.default.join(projectDir, '.env.opaq'))];
516
+ case 3:
517
+ envContent = _a.sent();
518
+ expect(envContent).toContain('ACCESS_TOKEN=N3WT0K3N');
519
+ expect(envContent).toContain('REFRESH_TOKEN=N3WR3FR35H');
520
+ expect(envContent).toContain('CLIENT=CL13NT');
521
+ return [2 /*return*/];
522
+ }
523
+ });
524
+ }); });
525
+ it('should refresh via the sas.cli public client and persist the rotated pair when no client is configured', function () { return __awaiter(void 0, void 0, void 0, function () {
526
+ var refreshTokensSpy, target, authConfig, envContent;
527
+ return __generator(this, function (_a) {
528
+ switch (_a.label) {
529
+ case 0:
530
+ ;
531
+ sasjsAuthUtils.isAccessTokenExpiring.mockImplementation(function () { return true; });
532
+ refreshTokensSpy = jest
533
+ .spyOn(authUtils, 'refreshTokens')
534
+ .mockImplementation(function () {
535
+ return Promise.resolve({
536
+ access_token: 'N3WT0K3N',
537
+ refresh_token: 'N3WR3FR35H'
538
+ });
539
+ });
540
+ target = new utils_1.Target({
541
+ name: 'opaq',
542
+ serverType: utils_1.ServerType.SasViya,
543
+ serverUrl: 'https://example.com',
544
+ appLoc: '/Public/app',
545
+ contextName: 'test context',
546
+ authConfig: {
547
+ access_token: 'T0K3N',
548
+ refresh_token: opaqueRefreshToken
549
+ }
550
+ });
551
+ return [4 /*yield*/, setupLocalProjectWithTarget(target)];
552
+ case 1:
553
+ _a.sent();
554
+ return [4 /*yield*/, (0, config_1.getAuthConfig)(target)];
555
+ case 2:
556
+ authConfig = _a.sent();
557
+ expect(refreshTokensSpy).toHaveBeenCalledWith(expect.anything(), 'sas.cli', '', opaqueRefreshToken);
558
+ expect(authConfig.access_token).toEqual('N3WT0K3N');
559
+ expect(authConfig.client).toBeUndefined();
560
+ expect(authConfig.secret).toBeUndefined();
561
+ return [4 /*yield*/, (0, file_1.readFile)(path_1.default.join(projectDir, '.env.opaq'))];
562
+ case 3:
563
+ envContent = _a.sent();
564
+ expect(envContent).toContain('ACCESS_TOKEN=N3WT0K3N');
565
+ expect(envContent).toContain('REFRESH_TOKEN=N3WR3FR35H');
566
+ expect(envContent).not.toContain('CLIENT=');
567
+ return [2 /*return*/];
568
+ }
569
+ });
570
+ }); });
571
+ it('should persist tokens refreshed internally by the adapter without dropping client/secret', function () { return __awaiter(void 0, void 0, void 0, function () {
572
+ var target, envContent;
573
+ return __generator(this, function (_a) {
574
+ switch (_a.label) {
575
+ case 0:
576
+ target = new utils_1.Target({
577
+ name: 'opaq',
578
+ serverType: utils_1.ServerType.SasViya,
579
+ serverUrl: 'https://example.com',
580
+ appLoc: '/Public/app',
581
+ contextName: 'test context',
582
+ authConfig: {
583
+ access_token: 'T0K3N',
584
+ refresh_token: opaqueRefreshToken,
585
+ client: 'CL13NT',
586
+ secret: '53CR3T'
587
+ }
588
+ });
589
+ return [4 /*yield*/, setupLocalProjectWithTarget(target)];
590
+ case 1:
591
+ _a.sent();
592
+ return [4 /*yield*/, (0, config_1.persistTokensRefreshedByAdapter)(target)({
593
+ access_token: '4D4PT3R',
594
+ refresh_token: '4D4PT3RR3FR35H'
595
+ })];
596
+ case 2:
597
+ _a.sent();
598
+ return [4 /*yield*/, (0, file_1.readFile)(path_1.default.join(projectDir, '.env.opaq'))];
599
+ case 3:
600
+ envContent = _a.sent();
601
+ expect(envContent).toContain('ACCESS_TOKEN=4D4PT3R');
602
+ expect(envContent).toContain('REFRESH_TOKEN=4D4PT3RR3FR35H');
603
+ expect(envContent).toContain('CLIENT=CL13NT');
604
+ expect(envContent).toContain('SECRET=53CR3T');
605
+ return [2 /*return*/];
606
+ }
607
+ });
608
+ }); });
609
+ it('should persist tokens refreshed by the adapter without adding CLIENT=/SECRET= lines for a password-grant-only target', function () { return __awaiter(void 0, void 0, void 0, function () {
610
+ var target, envContent;
611
+ return __generator(this, function (_a) {
612
+ switch (_a.label) {
613
+ case 0:
614
+ target = new utils_1.Target({
615
+ name: 'opaq',
616
+ serverType: utils_1.ServerType.SasViya,
617
+ serverUrl: 'https://example.com',
618
+ appLoc: '/Public/app',
619
+ contextName: 'test context',
620
+ authConfig: {
621
+ access_token: 'T0K3N',
622
+ refresh_token: opaqueRefreshToken
623
+ }
624
+ });
625
+ return [4 /*yield*/, setupLocalProjectWithTarget(target)];
626
+ case 1:
627
+ _a.sent();
628
+ return [4 /*yield*/, (0, config_1.persistTokensRefreshedByAdapter)(target)({
629
+ access_token: '4D4PT3R',
630
+ refresh_token: '4D4PT3RR3FR35H'
631
+ })];
632
+ case 2:
633
+ _a.sent();
634
+ return [4 /*yield*/, (0, file_1.readFile)(path_1.default.join(projectDir, '.env.opaq'))];
635
+ case 3:
636
+ envContent = _a.sent();
637
+ expect(envContent).toContain('ACCESS_TOKEN=4D4PT3R');
638
+ expect(envContent).toContain('REFRESH_TOKEN=4D4PT3RR3FR35H');
639
+ expect(envContent).not.toContain('CLIENT=');
640
+ expect(envContent).not.toContain('SECRET=');
641
+ return [2 /*return*/];
642
+ }
643
+ });
644
+ }); });
645
+ });
344
646
  describe('sanitizeAppLoc', function () {
345
647
  var notValidAppLoc = '///Public/app///';
346
648
  var validAppLoc = '/Public/app';
package/package.json CHANGED
@@ -69,10 +69,10 @@
69
69
  "registry": "https://registry.npmjs.org"
70
70
  },
71
71
  "dependencies": {
72
- "@sasjs/adapter": "4.17.2",
72
+ "@sasjs/adapter": "^4.19.0",
73
73
  "@sasjs/core": "4.68.1",
74
74
  "@sasjs/lint": "2.4.3",
75
- "@sasjs/utils": "3.5.9",
75
+ "@sasjs/utils": "^3.6.0",
76
76
  "adm-zip": "0.6.0",
77
77
  "chalk": "4.1.2",
78
78
  "dotenv": "16.0.3",
@@ -85,6 +85,7 @@
85
85
  "node-graphviz": "0.1.1",
86
86
  "node-powershell": "5.0.1",
87
87
  "ora": "5.4.1",
88
+ "prompts": "2.4.1",
88
89
  "shelljs": "0.10.0",
89
90
  "ssl-root-cas": "1.3.1",
90
91
  "xml": "1.0.1",
@@ -123,5 +124,5 @@
123
124
  "ws": "8.21.0",
124
125
  "nanoid": "3.3.18"
125
126
  },
126
- "version": "4.18.6"
127
+ "version": "4.20.0"
127
128
  }