@squiz/dxp-cli-next 5.11.0 → 5.12.0-develop.1
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/README.md +2 -2
- package/lib/__tests__/integration/main.spec.js +1 -1
- package/lib/auth/index.js +4 -2
- package/lib/auth/login/login.d.ts +22 -0
- package/lib/auth/{login.js → login/login.js} +128 -97
- package/lib/auth/login/login.spec.d.ts +1 -0
- package/lib/auth/login/login.spec.js +179 -0
- package/lib/auth/logout/logout.d.ts +3 -0
- package/lib/auth/logout/logout.js +115 -0
- package/lib/auth/logout/logout.spec.d.ts +1 -0
- package/lib/auth/logout/logout.spec.js +67 -0
- package/lib/cmp/dev-mode.js +1 -1
- package/lib/dxp.js +2 -2
- package/package.json +4 -2
- package/lib/auth/login.d.ts +0 -3
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ const helpers_1 = require("../helpers");
|
|
|
4
4
|
describe('dxp', () => {
|
|
5
5
|
it('should display the help contents', () => {
|
|
6
6
|
const { stdout } = (0, helpers_1.runCLI)(process.cwd(), ['--help']);
|
|
7
|
-
expect(stdout).toContain('Usage: dxp [options] [command]');
|
|
7
|
+
expect(stdout).toContain('Usage: dxp-next [options] [command]');
|
|
8
8
|
});
|
|
9
9
|
it('should show all available commands', () => {
|
|
10
10
|
var _a;
|
package/lib/auth/index.js
CHANGED
|
@@ -4,9 +4,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const commander_1 = require("commander");
|
|
7
|
-
const login_1 = __importDefault(require("./login"));
|
|
7
|
+
const login_1 = __importDefault(require("./login/login"));
|
|
8
|
+
const logout_1 = __importDefault(require("./logout/logout"));
|
|
8
9
|
const authCommand = new commander_1.Command('auth');
|
|
9
10
|
authCommand
|
|
10
11
|
.description('Authenticate into the DXP CLI')
|
|
11
|
-
.addCommand(login_1.default)
|
|
12
|
+
.addCommand((0, login_1.default)())
|
|
13
|
+
.addCommand((0, logout_1.default)());
|
|
12
14
|
exports.default = authCommand;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
interface LoginOptions {
|
|
3
|
+
dxpBaseUrl: string;
|
|
4
|
+
overrideSession: boolean | undefined;
|
|
5
|
+
region: string;
|
|
6
|
+
tenant: string | undefined;
|
|
7
|
+
username: string;
|
|
8
|
+
}
|
|
9
|
+
export declare type GetTenantsResponse = {
|
|
10
|
+
agencies: Array<any>;
|
|
11
|
+
organisations: Array<{
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
userRoles: Array<string>;
|
|
15
|
+
}>;
|
|
16
|
+
};
|
|
17
|
+
declare const createLoginCommand: () => Command;
|
|
18
|
+
/**
|
|
19
|
+
* Poll for the authorised DXP session.
|
|
20
|
+
*/
|
|
21
|
+
export declare function pollForDxpSession(options: LoginOptions, sessionId: string): Promise<unknown>;
|
|
22
|
+
export default createLoginCommand;
|
|
@@ -35,127 +35,158 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
35
35
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
36
36
|
};
|
|
37
37
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
+
exports.pollForDxpSession = void 0;
|
|
38
39
|
const cli_color_1 = __importDefault(require("cli-color"));
|
|
39
40
|
const commander_1 = require("commander");
|
|
40
|
-
const ApiService_1 = require("
|
|
41
|
-
const ApplicationStore_1 = require("
|
|
42
|
-
const
|
|
41
|
+
const ApiService_1 = require("../../ApiService");
|
|
42
|
+
const ApplicationStore_1 = require("../../ApplicationStore");
|
|
43
|
+
const opener_1 = __importDefault(require("opener"));
|
|
44
|
+
const ora_1 = __importDefault(require("ora"));
|
|
45
|
+
const crypto_1 = require("crypto");
|
|
43
46
|
const PRODUCTION_URL = 'https://dxp.squiz.cloud';
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const credentials = yield getLoginCredentials(options.username, options.password);
|
|
62
|
-
if (credentials == false) {
|
|
63
|
-
console.log(''); // needed to not screw up line endings in terminal
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
yield handleLoginRequest(credentials, options);
|
|
67
|
-
const availableTenants = yield handleGetTenantsRequest(options);
|
|
68
|
-
if (options.tenant == undefined) {
|
|
69
|
-
const selectedTenant = yield promptForTenant(availableTenants);
|
|
70
|
-
options.tenant = selectedTenant;
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
validateTenant(options.tenant, availableTenants);
|
|
74
|
-
}
|
|
75
|
-
return yield writeTenantToSessionConfig(options);
|
|
76
|
-
}
|
|
77
|
-
catch (error) {
|
|
78
|
-
if (!!process.env.DEBUG && error.stack) {
|
|
79
|
-
loginCommand.error(error.stack);
|
|
80
|
-
}
|
|
81
|
-
if (error.message) {
|
|
82
|
-
loginCommand.error(cli_color_1.default.red(error.message));
|
|
83
|
-
}
|
|
84
|
-
else {
|
|
85
|
-
loginCommand.error(cli_color_1.default.red('An unknown error occurred'));
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}));
|
|
89
|
-
function getLoginCredentials(optionalUsername, optionalPassword) {
|
|
90
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
91
|
-
const prompts = [];
|
|
92
|
-
let username = '';
|
|
93
|
-
let password = '';
|
|
94
|
-
if (!optionalUsername) {
|
|
95
|
-
prompts.push({
|
|
96
|
-
name: 'username',
|
|
97
|
-
description: 'Username: ',
|
|
98
|
-
required: true,
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
else {
|
|
102
|
-
username = optionalUsername;
|
|
103
|
-
}
|
|
104
|
-
if (!optionalPassword) {
|
|
105
|
-
prompts.push({
|
|
106
|
-
name: 'password',
|
|
107
|
-
description: 'Password: ',
|
|
108
|
-
hidden: true,
|
|
109
|
-
replace: '*',
|
|
110
|
-
required: true,
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
else {
|
|
114
|
-
password = optionalPassword;
|
|
115
|
-
}
|
|
116
|
-
let result;
|
|
47
|
+
const AUTH_TIMEOUT = 300000;
|
|
48
|
+
const createLoginCommand = () => {
|
|
49
|
+
const loginCommand = new commander_1.Command('login')
|
|
50
|
+
.name('login')
|
|
51
|
+
.description('Login to the DXP platform')
|
|
52
|
+
.addOption(new commander_1.Option('--dxp-base-url <baseURL>', 'dxp cloud base url e.g. "https://develop-apps-dxp-console.dev.dxp.squiz.cloud/"')
|
|
53
|
+
.env('DXP_BASE_URL')
|
|
54
|
+
.default(PRODUCTION_URL))
|
|
55
|
+
.addOption(new commander_1.Option('--region <region>').choices(['au']).default('au'))
|
|
56
|
+
.addOption(new commander_1.Option('--override-session', 'Override the existing authorized session if it exists'))
|
|
57
|
+
.addOption(new commander_1.Option('--tenant <tenantID>'))
|
|
58
|
+
.configureOutput({
|
|
59
|
+
outputError(str, write) {
|
|
60
|
+
write(cli_color_1.default.red(str));
|
|
61
|
+
},
|
|
62
|
+
})
|
|
63
|
+
.action((options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
117
64
|
try {
|
|
118
|
-
|
|
119
|
-
|
|
65
|
+
const spinner = (0, ora_1.default)().start();
|
|
66
|
+
yield handleLoginRequest(options);
|
|
67
|
+
const availableTenants = yield handleGetTenantsRequest(options);
|
|
68
|
+
spinner.stop();
|
|
69
|
+
if (options.tenant == undefined) {
|
|
70
|
+
const selectedTenant = yield promptForTenant(availableTenants);
|
|
71
|
+
options.tenant = selectedTenant;
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
validateTenant(options.tenant, availableTenants);
|
|
75
|
+
}
|
|
76
|
+
return yield writeTenantToSessionConfig(options);
|
|
120
77
|
}
|
|
121
|
-
catch (
|
|
122
|
-
if (
|
|
123
|
-
|
|
78
|
+
catch (error) {
|
|
79
|
+
if (!!process.env.DEBUG && error.stack) {
|
|
80
|
+
loginCommand.error(error.stack);
|
|
81
|
+
}
|
|
82
|
+
if (error.message) {
|
|
83
|
+
loginCommand.error(cli_color_1.default.red(error.message));
|
|
124
84
|
}
|
|
125
85
|
else {
|
|
126
|
-
|
|
86
|
+
loginCommand.error(cli_color_1.default.red('An unknown error occurred'));
|
|
127
87
|
}
|
|
128
88
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
89
|
+
}));
|
|
90
|
+
return loginCommand;
|
|
91
|
+
};
|
|
92
|
+
function handleLoginRequest(options) {
|
|
93
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
94
|
+
if (!options.overrideSession) {
|
|
95
|
+
// Check if currently stored session is still authorised.
|
|
96
|
+
const hasValidDxpSession = yield checkAuthorisation(options);
|
|
97
|
+
if (hasValidDxpSession) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const sessionId = yield openSSOLogin(options);
|
|
102
|
+
yield pollForDxpSession(options, sessionId);
|
|
134
103
|
});
|
|
135
104
|
}
|
|
136
|
-
function
|
|
105
|
+
function checkAuthorisation(options) {
|
|
137
106
|
return __awaiter(this, void 0, void 0, function* () {
|
|
138
107
|
const apiService = new ApiService_1.ApiService();
|
|
139
108
|
// Just need to make sure no extra slash at the end
|
|
140
109
|
const baseUrl = options.dxpBaseUrl.replace(/\/$/, '');
|
|
141
|
-
const
|
|
142
|
-
const
|
|
143
|
-
const
|
|
144
|
-
|
|
110
|
+
const { region } = options;
|
|
111
|
+
const healthCheckUrl = `${baseUrl}/__dxp/${region}/dxp/health`;
|
|
112
|
+
const response = yield apiService.client.get(healthCheckUrl);
|
|
113
|
+
return response.status === 200;
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Return the session ID token and open the DXP login page in the default browser.
|
|
118
|
+
*/
|
|
119
|
+
function openSSOLogin(options) {
|
|
120
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
121
|
+
// Just need to make sure no extra slash at the end
|
|
122
|
+
const baseUrl = options.dxpBaseUrl.replace(/\/$/, '');
|
|
123
|
+
// Generate a unique session to track the login request
|
|
124
|
+
const sessionId = `cli_${(0, crypto_1.randomUUID)()}`;
|
|
125
|
+
const tokenData = { sessionId };
|
|
126
|
+
const sessionToken = Buffer.from(JSON.stringify(tokenData)).toString('base64');
|
|
127
|
+
const loginUrl = `${baseUrl}/__sso/cli/login?code=${sessionToken}`;
|
|
128
|
+
(0, opener_1.default)(loginUrl);
|
|
129
|
+
return sessionToken;
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Make a request for the authorised DXP session.
|
|
134
|
+
*/
|
|
135
|
+
function retrieveDxpSession(options, sessionId) {
|
|
136
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
137
|
+
const apiService = new ApiService_1.ApiService();
|
|
138
|
+
// Just need to make sure no extra slash at the end
|
|
139
|
+
const baseUrl = options.dxpBaseUrl.replace(/\/$/, '');
|
|
140
|
+
const pollingUrl = `${baseUrl}/__sso/cli/session`;
|
|
141
|
+
const pollingResponse = yield apiService.client.get(pollingUrl, {
|
|
142
|
+
params: { code: sessionId },
|
|
145
143
|
});
|
|
146
|
-
switch (
|
|
147
|
-
case
|
|
144
|
+
switch (pollingResponse.status) {
|
|
145
|
+
case 200:
|
|
148
146
|
console.log('Login successful');
|
|
149
147
|
yield (0, ApplicationStore_1.saveApplicationFile)(ApplicationStore_1.STORE_FILES.sessionConfig, JSON.stringify({
|
|
150
148
|
baseUrl,
|
|
151
149
|
region: options.region,
|
|
152
150
|
}));
|
|
153
|
-
return;
|
|
151
|
+
return true;
|
|
152
|
+
case 404:
|
|
153
|
+
// Return false to allow polling to continue.
|
|
154
|
+
return false;
|
|
154
155
|
default:
|
|
155
|
-
throw new Error(`An error occurred during login ${
|
|
156
|
+
throw new Error(`An error occurred during login ${pollingResponse.data.message
|
|
157
|
+
? `"${pollingResponse.data.message}"`
|
|
158
|
+
: ''} (code: ${pollingResponse.status})`.trim());
|
|
156
159
|
}
|
|
157
160
|
});
|
|
158
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* Poll for the authorised DXP session.
|
|
164
|
+
*/
|
|
165
|
+
function pollForDxpSession(options, sessionId) {
|
|
166
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
167
|
+
return new Promise((resolve, reject) => {
|
|
168
|
+
const errHandler = (err) => {
|
|
169
|
+
reject(err);
|
|
170
|
+
};
|
|
171
|
+
const checkSessionTimeout = setTimeout(() => {
|
|
172
|
+
reject('Timed out waiting for authorization, please try again.');
|
|
173
|
+
}, AUTH_TIMEOUT);
|
|
174
|
+
const poll = () => __awaiter(this, void 0, void 0, function* () {
|
|
175
|
+
const isSessionCreated = yield retrieveDxpSession(options, sessionId);
|
|
176
|
+
if (!isSessionCreated) {
|
|
177
|
+
setTimeout(() => {
|
|
178
|
+
poll().catch(errHandler);
|
|
179
|
+
}, 2000);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
clearTimeout(checkSessionTimeout);
|
|
183
|
+
resolve(true);
|
|
184
|
+
});
|
|
185
|
+
poll().catch(errHandler);
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
exports.pollForDxpSession = pollForDxpSession;
|
|
159
190
|
function handleGetTenantsRequest(options) {
|
|
160
191
|
return __awaiter(this, void 0, void 0, function* () {
|
|
161
192
|
const apiService = new ApiService_1.ApiService();
|
|
@@ -234,4 +265,4 @@ function writeTenantToSessionConfig(options) {
|
|
|
234
265
|
}
|
|
235
266
|
});
|
|
236
267
|
}
|
|
237
|
-
exports.default =
|
|
268
|
+
exports.default = createLoginCommand;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
26
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
27
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
28
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
29
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
30
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
31
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
35
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
36
|
+
};
|
|
37
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
+
const nock_1 = __importDefault(require("nock"));
|
|
39
|
+
const ApplicationStore = __importStar(require("../../ApplicationStore"));
|
|
40
|
+
describe('login', () => {
|
|
41
|
+
jest.mock('crypto');
|
|
42
|
+
jest.mock('opener');
|
|
43
|
+
jest.mock('inquirer');
|
|
44
|
+
let mockSessionToken;
|
|
45
|
+
let mockTenants;
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
nock_1.default.cleanAll();
|
|
48
|
+
jest.clearAllMocks();
|
|
49
|
+
jest.useRealTimers();
|
|
50
|
+
const mockTokenData = { sessionId: 'cli_1234' };
|
|
51
|
+
mockSessionToken = Buffer.from(JSON.stringify(mockTokenData)).toString('base64');
|
|
52
|
+
mockTenants = {
|
|
53
|
+
agencies: [],
|
|
54
|
+
organisations: [{ id: 'org-1234', name: 'org-1234', userRoles: [] }],
|
|
55
|
+
};
|
|
56
|
+
require('opener').mockClear().mockImplementation();
|
|
57
|
+
require('inquirer').prompt = jest
|
|
58
|
+
.fn()
|
|
59
|
+
.mockImplementation(() => Promise.resolve({ selectedTenantId: 'tenant-1234' }));
|
|
60
|
+
require('crypto').randomUUID = jest.fn().mockReturnValue('1234');
|
|
61
|
+
jest.spyOn(ApplicationStore, 'saveApplicationFile').mockImplementation();
|
|
62
|
+
});
|
|
63
|
+
it('should successfully login', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
64
|
+
(0, nock_1.default)('http://localhost:9999')
|
|
65
|
+
.get('/__dxp/au/dxp/health')
|
|
66
|
+
.reply(401)
|
|
67
|
+
.post('/__sso/cli/login')
|
|
68
|
+
.reply(200)
|
|
69
|
+
.get(`/__sso/cli/session?code=${mockSessionToken}`)
|
|
70
|
+
.reply(200)
|
|
71
|
+
.get('/__dxp/au/dxp/access/tenants')
|
|
72
|
+
.reply(200, mockTenants);
|
|
73
|
+
const opener = require('opener');
|
|
74
|
+
const { default: createLoginCommand } = require('./login');
|
|
75
|
+
const program = createLoginCommand();
|
|
76
|
+
yield program.parseAsync([
|
|
77
|
+
'node',
|
|
78
|
+
'dxp-cli-next',
|
|
79
|
+
'auth',
|
|
80
|
+
'login',
|
|
81
|
+
'--dxp-base-url',
|
|
82
|
+
'http://localhost:9999',
|
|
83
|
+
]);
|
|
84
|
+
expect(opener).toHaveBeenCalledWith(expect.stringContaining('http://localhost:9999/__sso/cli/login?code='));
|
|
85
|
+
expect(ApplicationStore.saveApplicationFile).toHaveBeenCalledTimes(2);
|
|
86
|
+
}));
|
|
87
|
+
it('should successfully log in after polling for the DXP session', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
88
|
+
(0, nock_1.default)('http://localhost:9999')
|
|
89
|
+
.get('/__dxp/au/dxp/health')
|
|
90
|
+
.reply(401)
|
|
91
|
+
.post('/__sso/cli/login')
|
|
92
|
+
.reply(200)
|
|
93
|
+
.get(`/__sso/cli/session?code=${mockSessionToken}`)
|
|
94
|
+
.reply(404)
|
|
95
|
+
.get(`/__sso/cli/session?code=${mockSessionToken}`)
|
|
96
|
+
.reply(200)
|
|
97
|
+
.get('/__dxp/au/dxp/access/tenants')
|
|
98
|
+
.reply(200, mockTenants);
|
|
99
|
+
const opener = require('opener');
|
|
100
|
+
const { default: createLoginCommand } = require('./login');
|
|
101
|
+
const program = createLoginCommand();
|
|
102
|
+
yield program.parseAsync([
|
|
103
|
+
'node',
|
|
104
|
+
'dxp-cli-next',
|
|
105
|
+
'auth',
|
|
106
|
+
'login',
|
|
107
|
+
'--dxp-base-url',
|
|
108
|
+
'http://localhost:9999',
|
|
109
|
+
]);
|
|
110
|
+
expect(opener).toHaveBeenCalledWith(expect.stringContaining('http://localhost:9999/__sso/cli/login?code='));
|
|
111
|
+
expect(ApplicationStore.saveApplicationFile).toHaveBeenCalledTimes(2);
|
|
112
|
+
}));
|
|
113
|
+
it('should log into an existing authorised session', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
114
|
+
(0, nock_1.default)('http://localhost:9999')
|
|
115
|
+
.get('/__dxp/au/dxp/health')
|
|
116
|
+
.reply(200)
|
|
117
|
+
.get('/__dxp/au/dxp/access/tenants')
|
|
118
|
+
.reply(200, mockTenants);
|
|
119
|
+
const opener = require('opener');
|
|
120
|
+
const { default: createLoginCommand } = require('./login');
|
|
121
|
+
const program = createLoginCommand();
|
|
122
|
+
yield program.parseAsync([
|
|
123
|
+
'node',
|
|
124
|
+
'dxp-cli-next',
|
|
125
|
+
'auth',
|
|
126
|
+
'login',
|
|
127
|
+
'--dxp-base-url',
|
|
128
|
+
'http://localhost:9999',
|
|
129
|
+
]);
|
|
130
|
+
expect(opener).not.toHaveBeenCalled();
|
|
131
|
+
expect(ApplicationStore.saveApplicationFile).toHaveBeenCalledTimes(1);
|
|
132
|
+
}));
|
|
133
|
+
it('should forcefully start a new login session', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
134
|
+
(0, nock_1.default)('http://localhost:9999')
|
|
135
|
+
.get('/__dxp/au/dxp/health')
|
|
136
|
+
.reply(200)
|
|
137
|
+
.post('/__sso/cli/login')
|
|
138
|
+
.reply(200)
|
|
139
|
+
.get(`/__sso/cli/session?code=${mockSessionToken}`)
|
|
140
|
+
.reply(200)
|
|
141
|
+
.get('/__dxp/au/dxp/access/tenants')
|
|
142
|
+
.reply(200, mockTenants);
|
|
143
|
+
const opener = require('opener');
|
|
144
|
+
const { default: createLoginCommand } = require('./login');
|
|
145
|
+
const program = createLoginCommand();
|
|
146
|
+
yield program.parseAsync([
|
|
147
|
+
'node',
|
|
148
|
+
'dxp-cli-next',
|
|
149
|
+
'auth',
|
|
150
|
+
'login',
|
|
151
|
+
'--dxp-base-url',
|
|
152
|
+
'http://localhost:9999',
|
|
153
|
+
'--override-session',
|
|
154
|
+
]);
|
|
155
|
+
expect(opener).toHaveBeenCalledWith(expect.stringContaining('http://localhost:9999/__sso/cli/login?code='));
|
|
156
|
+
expect(ApplicationStore.saveApplicationFile).toHaveBeenCalledTimes(2);
|
|
157
|
+
}));
|
|
158
|
+
it('should throw an error if authorisation times out', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
159
|
+
jest.useFakeTimers();
|
|
160
|
+
(0, nock_1.default)('http://localhost:9999')
|
|
161
|
+
.get('/__dxp/au/dxp/health')
|
|
162
|
+
.reply(401)
|
|
163
|
+
.post('/__sso/cli/login')
|
|
164
|
+
.reply(200)
|
|
165
|
+
.get(`/__sso/cli/session?code=${mockSessionToken}`)
|
|
166
|
+
.reply(404);
|
|
167
|
+
const { pollForDxpSession } = require('./login');
|
|
168
|
+
pollForDxpSession({
|
|
169
|
+
dxpBaseUrl: 'http://localhost:9999',
|
|
170
|
+
overrideSession: undefined,
|
|
171
|
+
region: 'au',
|
|
172
|
+
tenant: 'tenant-1234',
|
|
173
|
+
username: 'mock-user',
|
|
174
|
+
}, 'mock').catch((err) => {
|
|
175
|
+
expect(err).toBe('Timed out waiting for authorization, please try again.');
|
|
176
|
+
});
|
|
177
|
+
jest.advanceTimersByTime(300000);
|
|
178
|
+
}));
|
|
179
|
+
});
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
26
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
27
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
28
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
29
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
30
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
31
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
35
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
36
|
+
};
|
|
37
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
+
const cli_color_1 = __importDefault(require("cli-color"));
|
|
39
|
+
const commander_1 = require("commander");
|
|
40
|
+
const ApplicationStore_1 = require("../../ApplicationStore");
|
|
41
|
+
const ora_1 = __importDefault(require("ora"));
|
|
42
|
+
const createLogoutCommand = () => {
|
|
43
|
+
const logoutCommand = new commander_1.Command('logout')
|
|
44
|
+
.name('logout')
|
|
45
|
+
.description('Log out of the current session')
|
|
46
|
+
.addOption(new commander_1.Option('-f, --force', 'Skip confirmation prompt'))
|
|
47
|
+
.action((options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
48
|
+
try {
|
|
49
|
+
yield handleLogoutRequest(options);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
if (!!process.env.DEBUG && error.stack) {
|
|
53
|
+
logoutCommand.error(error.stack);
|
|
54
|
+
}
|
|
55
|
+
if (error.message) {
|
|
56
|
+
logoutCommand.error(cli_color_1.default.red(error.message));
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
logoutCommand.error(cli_color_1.default.red('An unknown error occurred'));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}));
|
|
63
|
+
return logoutCommand;
|
|
64
|
+
};
|
|
65
|
+
function handleLogoutRequest(options) {
|
|
66
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
67
|
+
const continueLogout = yield promptForConfirmation(options);
|
|
68
|
+
if (!continueLogout) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const spinner = (0, ora_1.default)().start();
|
|
72
|
+
try {
|
|
73
|
+
const sessionCookie = yield (0, ApplicationStore_1.getApplicationFile)(ApplicationStore_1.STORE_FILES.sessionCookie);
|
|
74
|
+
if (!sessionCookie) {
|
|
75
|
+
spinner.stop();
|
|
76
|
+
console.log('You are not currently logged in');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
yield (0, ApplicationStore_1.deleteApplicationFile)(ApplicationStore_1.STORE_FILES.sessionCookie);
|
|
80
|
+
yield (0, ApplicationStore_1.deleteApplicationFile)(ApplicationStore_1.STORE_FILES.sessionConfig);
|
|
81
|
+
spinner.stop();
|
|
82
|
+
console.log('Logged out successfully');
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
spinner.stop();
|
|
86
|
+
throw new Error('An error occurred while logging out of session');
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Prompts the user for confirmation.
|
|
92
|
+
* Returns true if user has chosen to confirm.
|
|
93
|
+
*/
|
|
94
|
+
function promptForConfirmation(options) {
|
|
95
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
96
|
+
if (options.force) {
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
//Our tsconfig module is commonjs so we need to use require here and only version ^8.0.0 of inquirer as latest is default esm
|
|
100
|
+
const inquirer = yield Promise.resolve().then(() => __importStar(require('inquirer')));
|
|
101
|
+
const choice = yield inquirer.default.prompt([
|
|
102
|
+
{
|
|
103
|
+
type: 'list',
|
|
104
|
+
name: 'confirm',
|
|
105
|
+
message: 'Are you sure you want to log out?: ',
|
|
106
|
+
choices: [
|
|
107
|
+
{ name: 'No', value: false },
|
|
108
|
+
{ name: 'Yes', value: true },
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
]);
|
|
112
|
+
return choice.confirm;
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
exports.default = createLogoutCommand;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
26
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
27
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
28
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
29
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
30
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
31
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
35
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
36
|
+
};
|
|
37
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
+
const ApplicationStore = __importStar(require("../../ApplicationStore"));
|
|
39
|
+
const logout_1 = __importDefault(require("./logout"));
|
|
40
|
+
describe('logout', () => {
|
|
41
|
+
jest.spyOn(ApplicationStore, 'deleteApplicationFile').mockImplementation();
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
jest.clearAllMocks();
|
|
44
|
+
});
|
|
45
|
+
it('should log out of session without confirmation', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
46
|
+
jest
|
|
47
|
+
.spyOn(ApplicationStore, 'getApplicationFile')
|
|
48
|
+
.mockImplementationOnce(() => Promise.resolve('mock-session'));
|
|
49
|
+
const program = (0, logout_1.default)();
|
|
50
|
+
yield program.parseAsync(['node', 'dxp-cli', 'auth', 'logout', '--force']);
|
|
51
|
+
const opts = program.opts();
|
|
52
|
+
expect(opts.force).toEqual(true);
|
|
53
|
+
expect(ApplicationStore.getApplicationFile).toHaveBeenCalled();
|
|
54
|
+
expect(ApplicationStore.deleteApplicationFile).toHaveBeenCalled();
|
|
55
|
+
}));
|
|
56
|
+
it('should stop the program if there is no session to log out of', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
57
|
+
jest
|
|
58
|
+
.spyOn(ApplicationStore, 'getApplicationFile')
|
|
59
|
+
.mockImplementationOnce(() => Promise.resolve(undefined));
|
|
60
|
+
const program = (0, logout_1.default)();
|
|
61
|
+
yield program.parseAsync(['node', 'dxp-cli', 'auth', 'logout', '--force']);
|
|
62
|
+
const opts = program.opts();
|
|
63
|
+
expect(opts.force).toEqual(true);
|
|
64
|
+
expect(ApplicationStore.getApplicationFile).toHaveBeenCalled();
|
|
65
|
+
expect(ApplicationStore.deleteApplicationFile).not.toHaveBeenCalled();
|
|
66
|
+
}));
|
|
67
|
+
});
|
package/lib/cmp/dev-mode.js
CHANGED
|
@@ -20,7 +20,7 @@ const buildDevModeCommand = () => new commander_1.Command()
|
|
|
20
20
|
.addHelpText('after', `
|
|
21
21
|
|
|
22
22
|
The logs are currently \`bunyan\` formatted and should be piped into the bunyan cli:
|
|
23
|
-
$ dxp cmp dev <source> | npx bunyan
|
|
23
|
+
$ dxp-next cmp dev <source> | npx bunyan
|
|
24
24
|
`)
|
|
25
25
|
.argument('<source>', 'folder containing the template files in development')
|
|
26
26
|
.addOption(new commander_1.Option('-p, --port <number>', 'Define port the webserver runs on')
|
package/lib/dxp.js
CHANGED
|
@@ -19,9 +19,9 @@ const program = new commander_1.default.Command();
|
|
|
19
19
|
const packageJson = require('../package.json');
|
|
20
20
|
const version = packageJson.version;
|
|
21
21
|
program
|
|
22
|
-
.name('dxp')
|
|
22
|
+
.name('dxp-next')
|
|
23
23
|
.version(version)
|
|
24
|
-
.description('dxp commands')
|
|
24
|
+
.description('dxp-next commands')
|
|
25
25
|
.addCommand(auth_1.default)
|
|
26
26
|
.addCommand(cmp_1.default)
|
|
27
27
|
.addCommand(job_runner_1.default)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@squiz/dxp-cli-next",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.12.0-develop.1",
|
|
4
4
|
"repository": {
|
|
5
5
|
"url": "https://gitlab.squiz.net/dxp/dxp-cli-next"
|
|
6
6
|
},
|
|
@@ -39,14 +39,15 @@
|
|
|
39
39
|
"codecov"
|
|
40
40
|
],
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@squiz/component-cli-lib": "1.64.1",
|
|
43
42
|
"@apidevtools/swagger-parser": "10.1.0",
|
|
43
|
+
"@squiz/component-cli-lib": "1.64.1",
|
|
44
44
|
"axios": "1.1.3",
|
|
45
45
|
"cli-color": "2.0.3",
|
|
46
46
|
"commander": "9.4.0",
|
|
47
47
|
"dotenv": "^16.3.1",
|
|
48
48
|
"env-paths": "2.2.1",
|
|
49
49
|
"inquirer": "8.2.5",
|
|
50
|
+
"opener": "1.5.2",
|
|
50
51
|
"ora": "^5.4.1",
|
|
51
52
|
"prompt": "^1.3.0",
|
|
52
53
|
"tough-cookie": "4.1.2",
|
|
@@ -61,6 +62,7 @@
|
|
|
61
62
|
"@types/inquirer": "8.2.6",
|
|
62
63
|
"@types/jest": "28.1.6",
|
|
63
64
|
"@types/node": "17.0.45",
|
|
65
|
+
"@types/opener": "1.4.3",
|
|
64
66
|
"@types/prompt": "^1.1.4",
|
|
65
67
|
"@types/tough-cookie": "4.0.2",
|
|
66
68
|
"@types/update-notifier": "5.1.0",
|
package/lib/auth/login.d.ts
DELETED