carta-controller 5.0.3 → 6.0.0-beta.1.0.2
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/.github/workflows/build.yml +43 -0
- package/COPYING.md +636 -0
- package/README.md +4 -7
- package/biome.jsonc +37 -0
- package/dist/auth/external.js +10 -4
- package/dist/auth/external.js.map +1 -1
- package/dist/auth/google.js +18 -11
- package/dist/auth/google.js.map +1 -1
- package/dist/auth/index.js +12 -12
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/ldap.js +6 -3
- package/dist/auth/ldap.js.map +1 -1
- package/dist/auth/local.js +30 -14
- package/dist/auth/local.js.map +1 -1
- package/dist/auth/oidc.js +95 -91
- package/dist/auth/oidc.js.map +1 -1
- package/dist/auth/oidcRefreshManager.js +21 -24
- package/dist/auth/oidcRefreshManager.js.map +1 -1
- package/dist/auth/pam.js +8 -5
- package/dist/auth/pam.js.map +1 -1
- package/dist/config.js +17 -16
- package/dist/controllerTests.js +10 -10
- package/dist/database.js +64 -27
- package/dist/index.js +24 -23
- package/dist/serverHandlers.js +70 -33
- package/dist/util.js +14 -5
- package/package.json +13 -10
- package/public/dashboard.js +47 -48
- package/public/templated.css +155 -143
- package/test/auth.external.test.ts +19 -18
- package/.prettierrc.json +0 -18
package/dist/serverHandlers.js
CHANGED
|
@@ -46,19 +46,19 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
46
46
|
};
|
|
47
47
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
48
|
exports.serverRouter = exports.createScriptingProxyHandler = exports.createUpgradeHandler = void 0;
|
|
49
|
+
const node_child_process_1 = require("node:child_process");
|
|
50
|
+
const fs = __importStar(require("node:fs"));
|
|
51
|
+
const querystring = __importStar(require("node:querystring"));
|
|
52
|
+
const url = __importStar(require("node:url"));
|
|
53
|
+
const io_1 = __importDefault(require("@pm2/io"));
|
|
49
54
|
const express_1 = __importDefault(require("express"));
|
|
50
|
-
const
|
|
51
|
-
const fs = __importStar(require("fs"));
|
|
55
|
+
const mnemonist_1 = require("mnemonist");
|
|
52
56
|
const moment_1 = __importDefault(require("moment"));
|
|
53
|
-
const querystring = __importStar(require("querystring"));
|
|
54
|
-
const uuid_1 = require("uuid");
|
|
55
|
-
const io_1 = __importDefault(require("@pm2/io"));
|
|
56
57
|
const tcpPortUsed = __importStar(require("tcp-port-used"));
|
|
57
|
-
const
|
|
58
|
-
const mnemonist_1 = require("mnemonist");
|
|
59
|
-
const util_1 = require("./util");
|
|
58
|
+
const uuid_1 = require("uuid");
|
|
60
59
|
const auth_1 = require("./auth");
|
|
61
60
|
const config_1 = require("./config");
|
|
61
|
+
const util_1 = require("./util");
|
|
62
62
|
const processMap = new Map();
|
|
63
63
|
const logMap = new Map();
|
|
64
64
|
const LOG_LIMIT = 1000;
|
|
@@ -101,7 +101,7 @@ function deleteProcess(username) {
|
|
|
101
101
|
function nextAvailablePort() {
|
|
102
102
|
return __awaiter(this, void 0, void 0, function* () {
|
|
103
103
|
// Get a map of all the ports in the range currently in use
|
|
104
|
-
|
|
104
|
+
const existingPorts = new Map();
|
|
105
105
|
processMap.forEach(value => {
|
|
106
106
|
existingPorts.set(value.port, true);
|
|
107
107
|
});
|
|
@@ -176,7 +176,7 @@ function handleStartServer(req, res, next) {
|
|
|
176
176
|
const existingProcess = processMap.get(username);
|
|
177
177
|
if (existingProcess) {
|
|
178
178
|
// Kill the process via the kill script
|
|
179
|
-
(0,
|
|
179
|
+
(0, node_child_process_1.spawnSync)("sudo", ["-u", `${username}`, config_1.ServerConfig.killCommand, `${existingProcess.process.pid}`]);
|
|
180
180
|
// Delay to allow the parent process to exit
|
|
181
181
|
yield (0, util_1.delay)(10);
|
|
182
182
|
deleteProcess(username);
|
|
@@ -185,7 +185,10 @@ function handleStartServer(req, res, next) {
|
|
|
185
185
|
catch (e) {
|
|
186
186
|
util_1.logger.debug(e);
|
|
187
187
|
util_1.logger.error(`Error killing existing process belonging to user ${username}`);
|
|
188
|
-
return next({
|
|
188
|
+
return next({
|
|
189
|
+
statusCode: 400,
|
|
190
|
+
message: "Problem killing existing process"
|
|
191
|
+
});
|
|
189
192
|
}
|
|
190
193
|
}
|
|
191
194
|
else {
|
|
@@ -210,7 +213,10 @@ function startServer(username) {
|
|
|
210
213
|
try {
|
|
211
214
|
const port = yield nextAvailablePort();
|
|
212
215
|
if (port < 0) {
|
|
213
|
-
throw {
|
|
216
|
+
throw {
|
|
217
|
+
statusCode: 500,
|
|
218
|
+
message: "No available ports for the backend process"
|
|
219
|
+
};
|
|
214
220
|
}
|
|
215
221
|
let args = [];
|
|
216
222
|
if (config_1.ServerConfig.preserveEnv) {
|
|
@@ -238,9 +244,14 @@ function startServer(username) {
|
|
|
238
244
|
// Finally, add the positional argument for the base folder
|
|
239
245
|
args.push(config_1.ServerConfig.baseFolderTemplate.replace("{username}", username));
|
|
240
246
|
const headerToken = (0, uuid_1.v4)();
|
|
241
|
-
const child = (0,
|
|
242
|
-
|
|
243
|
-
|
|
247
|
+
const child = (0, node_child_process_1.spawn)("sudo", args, {
|
|
248
|
+
env: { CARTA_AUTH_TOKEN: headerToken }
|
|
249
|
+
});
|
|
250
|
+
if ((child === null || child === void 0 ? void 0 : child.pid) == null) {
|
|
251
|
+
throw {
|
|
252
|
+
statusCode: 500,
|
|
253
|
+
message: `Problem starting process for user ${username}`
|
|
254
|
+
};
|
|
244
255
|
}
|
|
245
256
|
setPendingProcess(username, port, headerToken, child);
|
|
246
257
|
let logLocation;
|
|
@@ -248,16 +259,16 @@ function startServer(username) {
|
|
|
248
259
|
logLocation = config_1.ServerConfig.backendLogFileTemplate.replace("{username}", username).replace("{pid}", child.pid.toString()).replace("{datetime}", (0, moment_1.default)().format("YYYYMMDD.h_mm_ss"));
|
|
249
260
|
try {
|
|
250
261
|
logStream = fs.createWriteStream(logLocation, { flags: "a" });
|
|
251
|
-
if (logStream ==
|
|
262
|
+
if (logStream == null) {
|
|
252
263
|
throw new Error("Unable to open stream");
|
|
253
264
|
}
|
|
254
265
|
child.stdout.pipe(logStream);
|
|
255
266
|
child.stderr.pipe(logStream);
|
|
256
|
-
child.stdout.on("data",
|
|
267
|
+
child.stdout.on("data", data => {
|
|
257
268
|
const line = data.toString();
|
|
258
269
|
appendLog(username, line);
|
|
259
270
|
});
|
|
260
|
-
child.stderr.on("data",
|
|
271
|
+
child.stderr.on("data", data => {
|
|
261
272
|
const line = data.toString();
|
|
262
273
|
appendLog(username, line);
|
|
263
274
|
});
|
|
@@ -269,12 +280,12 @@ function startServer(username) {
|
|
|
269
280
|
}
|
|
270
281
|
else {
|
|
271
282
|
logLocation = "stdout";
|
|
272
|
-
child.stdout.on("data",
|
|
283
|
+
child.stdout.on("data", data => {
|
|
273
284
|
const line = data.toString();
|
|
274
285
|
appendLog(username, line);
|
|
275
286
|
util_1.logger.info(line);
|
|
276
287
|
});
|
|
277
|
-
child.stderr.on("data",
|
|
288
|
+
child.stderr.on("data", data => {
|
|
278
289
|
const line = data.toString();
|
|
279
290
|
appendLog(username, line);
|
|
280
291
|
util_1.logger.error(line);
|
|
@@ -288,7 +299,10 @@ function startServer(username) {
|
|
|
288
299
|
// Check for early exit of backend process
|
|
289
300
|
yield (0, util_1.delay)(config_1.ServerConfig.startDelay);
|
|
290
301
|
if (child.exitCode || child.signalCode) {
|
|
291
|
-
throw {
|
|
302
|
+
throw {
|
|
303
|
+
statusCode: 500,
|
|
304
|
+
message: `Problem starting process for user ${username}`
|
|
305
|
+
};
|
|
292
306
|
}
|
|
293
307
|
else {
|
|
294
308
|
util_1.logger.info(`Started process with PID ${child.pid} for user ${username} on port ${port}. Outputting to ${logLocation}`);
|
|
@@ -304,7 +318,10 @@ function startServer(username) {
|
|
|
304
318
|
throw e;
|
|
305
319
|
}
|
|
306
320
|
else {
|
|
307
|
-
throw {
|
|
321
|
+
throw {
|
|
322
|
+
statusCode: 500,
|
|
323
|
+
message: `Problem starting process for user ${username}`
|
|
324
|
+
};
|
|
308
325
|
}
|
|
309
326
|
}
|
|
310
327
|
});
|
|
@@ -320,7 +337,7 @@ function handleStopServer(req, res, next) {
|
|
|
320
337
|
if (existingProcess) {
|
|
321
338
|
existingProcess.process.removeAllListeners();
|
|
322
339
|
// Kill the process via the kill script
|
|
323
|
-
(0,
|
|
340
|
+
(0, node_child_process_1.spawnSync)("sudo", ["-u", `${req.username}`, config_1.ServerConfig.killCommand, `${existingProcess.process.pid}`]);
|
|
324
341
|
// Delay to allow the parent process to exit
|
|
325
342
|
yield (0, util_1.delay)(10);
|
|
326
343
|
util_1.logger.info(`Process with PID ${existingProcess.process.pid} for user ${req.username} exited via stop request`);
|
|
@@ -328,13 +345,19 @@ function handleStopServer(req, res, next) {
|
|
|
328
345
|
res.json({ success: true });
|
|
329
346
|
}
|
|
330
347
|
else {
|
|
331
|
-
return next({
|
|
348
|
+
return next({
|
|
349
|
+
statusCode: 400,
|
|
350
|
+
message: `No existing process belonging to user ${req.username}`
|
|
351
|
+
});
|
|
332
352
|
}
|
|
333
353
|
}
|
|
334
354
|
catch (e) {
|
|
335
355
|
util_1.logger.debug(e);
|
|
336
356
|
util_1.logger.error(`Error killing existing process belonging to user ${req.username}`);
|
|
337
|
-
return next({
|
|
357
|
+
return next({
|
|
358
|
+
statusCode: 500,
|
|
359
|
+
message: "Problem killing existing process"
|
|
360
|
+
});
|
|
338
361
|
}
|
|
339
362
|
});
|
|
340
363
|
}
|
|
@@ -344,12 +367,12 @@ const createUpgradeHandler = (server) => (req, socket, head) => __awaiter(void 0
|
|
|
344
367
|
if (!(req === null || req === void 0 ? void 0 : req.url)) {
|
|
345
368
|
return socket.end();
|
|
346
369
|
}
|
|
347
|
-
|
|
370
|
+
const parsedUrl = url.parse(req.url);
|
|
348
371
|
if (!(parsedUrl === null || parsedUrl === void 0 ? void 0 : parsedUrl.query)) {
|
|
349
372
|
util_1.logger.warning(`Incoming Websocket upgrade request could not be parsed: ${req.url}`);
|
|
350
373
|
return socket.end();
|
|
351
374
|
}
|
|
352
|
-
|
|
375
|
+
const queryParameters = querystring.parse(parsedUrl.query);
|
|
353
376
|
const tokenString = queryParameters === null || queryParameters === void 0 ? void 0 : queryParameters.token;
|
|
354
377
|
if (!tokenString || Array.isArray(tokenString)) {
|
|
355
378
|
util_1.logger.warning(`Incoming Websocket upgrade request is missing an authentication token`);
|
|
@@ -362,7 +385,7 @@ const createUpgradeHandler = (server) => (req, socket, head) => __awaiter(void 0
|
|
|
362
385
|
}
|
|
363
386
|
const remoteAddress = ((_a = req.headers) === null || _a === void 0 ? void 0 : _a["x-forwarded-for"]) || ((_b = req.connection) === null || _b === void 0 ? void 0 : _b.remoteAddress);
|
|
364
387
|
util_1.logger.info(`WS upgrade request from ${remoteAddress} for authenticated user ${token.username}`);
|
|
365
|
-
const username = (0, auth_1.getUser)(token.username, token.iss);
|
|
388
|
+
const username = (0, auth_1.getUser)(token.username, `${token.iss}`);
|
|
366
389
|
if (!username) {
|
|
367
390
|
util_1.logger.error(`Could not find username ${token.username} in the user map`);
|
|
368
391
|
return socket.end();
|
|
@@ -382,7 +405,9 @@ const createUpgradeHandler = (server) => (req, socket, head) => __awaiter(void 0
|
|
|
382
405
|
util_1.logger.info(`Redirecting to backend process for ${username} (port ${existingProcess.port})`);
|
|
383
406
|
req.headers["carta-auth-token"] = existingProcess.headerToken;
|
|
384
407
|
req.url = "/";
|
|
385
|
-
return server.ws(req, socket, head, {
|
|
408
|
+
return server.ws(req, socket, head, {
|
|
409
|
+
target: { host: "localhost", port: existingProcess.port }
|
|
410
|
+
});
|
|
386
411
|
}
|
|
387
412
|
else {
|
|
388
413
|
util_1.logger.error(`Backend process could not be started`);
|
|
@@ -403,10 +428,14 @@ const createScriptingProxyHandler = (server) => (req, res, next) => __awaiter(vo
|
|
|
403
428
|
return next({ statusCode: 401, message: "Not authorized" });
|
|
404
429
|
}
|
|
405
430
|
if (!req.scripting) {
|
|
406
|
-
return next({
|
|
431
|
+
return next({
|
|
432
|
+
statusCode: 403,
|
|
433
|
+
message: "API token supplied does not permit scripting"
|
|
434
|
+
});
|
|
407
435
|
}
|
|
408
436
|
try {
|
|
409
437
|
const remoteAddress = ((_a = req.headers) === null || _a === void 0 ? void 0 : _a["x-forwarded-for"]) || ((_b = req.connection) === null || _b === void 0 ? void 0 : _b.remoteAddress);
|
|
438
|
+
util_1.logger.info(`Scripting proxy request from ${remoteAddress} for authenticated user ${username}`);
|
|
410
439
|
let existingProcess = processMap.get(username);
|
|
411
440
|
if (!(existingProcess === null || existingProcess === void 0 ? void 0 : existingProcess.process) || existingProcess.process.signalCode) {
|
|
412
441
|
// Attempt to start new process
|
|
@@ -420,16 +449,24 @@ const createScriptingProxyHandler = (server) => (req, res, next) => __awaiter(vo
|
|
|
420
449
|
yield (0, util_1.delay)(config_1.ServerConfig.startDelay);
|
|
421
450
|
}
|
|
422
451
|
req.headers["carta-auth-token"] = existingProcess.headerToken;
|
|
423
|
-
return server.web(req, res, {
|
|
452
|
+
return server.web(req, res, {
|
|
453
|
+
target: { host: "localhost", port: existingProcess.port }
|
|
454
|
+
});
|
|
424
455
|
}
|
|
425
456
|
else {
|
|
426
|
-
return next({
|
|
457
|
+
return next({
|
|
458
|
+
statusCode: 500,
|
|
459
|
+
message: `Backend process could not be started for ${username}`
|
|
460
|
+
});
|
|
427
461
|
}
|
|
428
462
|
}
|
|
429
463
|
catch (err) {
|
|
430
464
|
util_1.logger.error(`Error proxying scripting request for ${req.username}`);
|
|
431
465
|
util_1.logger.debug(err);
|
|
432
|
-
return next({
|
|
466
|
+
return next({
|
|
467
|
+
statusCode: 500,
|
|
468
|
+
message: `Error proxying scripting request for ${req.username}`
|
|
469
|
+
});
|
|
433
470
|
}
|
|
434
471
|
});
|
|
435
472
|
exports.createScriptingProxyHandler = createScriptingProxyHandler;
|
package/dist/util.js
CHANGED
|
@@ -16,11 +16,12 @@ exports.logger = void 0;
|
|
|
16
16
|
exports.delay = delay;
|
|
17
17
|
exports.noCache = noCache;
|
|
18
18
|
exports.getUserId = getUserId;
|
|
19
|
-
|
|
19
|
+
exports.generateUrlSafeString = generateUrlSafeString;
|
|
20
|
+
const node_child_process_1 = require("node:child_process");
|
|
20
21
|
const winston_1 = __importDefault(require("winston"));
|
|
21
22
|
exports.logger = winston_1.default.createLogger({
|
|
22
23
|
// Detailed setup is completed in config.ts
|
|
23
|
-
levels: winston_1.default.config.syslog.levels
|
|
24
|
+
levels: winston_1.default.config.syslog.levels
|
|
24
25
|
});
|
|
25
26
|
// Delay for the specified number of milliseconds
|
|
26
27
|
function delay(delay) {
|
|
@@ -30,7 +31,7 @@ function delay(delay) {
|
|
|
30
31
|
});
|
|
31
32
|
});
|
|
32
33
|
}
|
|
33
|
-
function noCache(
|
|
34
|
+
function noCache(_req, res, next) {
|
|
34
35
|
res.header("Cache-Control", "private, no-cache, no-store, must-revalidate");
|
|
35
36
|
res.header("Expires", "-1");
|
|
36
37
|
res.header("Pragma", "no-cache");
|
|
@@ -40,13 +41,21 @@ function getUserId(username) {
|
|
|
40
41
|
if (!username) {
|
|
41
42
|
throw new Error("Missing argument for username");
|
|
42
43
|
}
|
|
43
|
-
const result = (0,
|
|
44
|
+
const result = (0, node_child_process_1.spawnSync)("id", ["-u", username]);
|
|
44
45
|
if (!result.status && (result === null || result === void 0 ? void 0 : result.stdout)) {
|
|
45
46
|
const uid = Number.parseInt(result.stdout.toString());
|
|
46
|
-
if (isFinite(uid)) {
|
|
47
|
+
if (Number.isFinite(uid)) {
|
|
47
48
|
return uid;
|
|
48
49
|
}
|
|
49
50
|
}
|
|
50
51
|
throw new Error(`Can't find uid for username ${username}`);
|
|
51
52
|
}
|
|
53
|
+
function generateUrlSafeString(length) {
|
|
54
|
+
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~";
|
|
55
|
+
let sessionId = "";
|
|
56
|
+
for (let i = 0; i < length; i++) {
|
|
57
|
+
sessionId += charset[Math.floor(Math.random() * charset.length)];
|
|
58
|
+
}
|
|
59
|
+
return sessionId;
|
|
60
|
+
}
|
|
52
61
|
//# sourceMappingURL=util.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "carta-controller",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.0-beta.1.0.2",
|
|
4
4
|
"description": "NodeJS-based controller for CARTA",
|
|
5
5
|
"repository": "https://github.com/CARTAvis/carta-controller",
|
|
6
6
|
"homepage": "https://www.cartavis.org",
|
|
@@ -12,20 +12,24 @@
|
|
|
12
12
|
"scripts": {
|
|
13
13
|
"start": "npx ts-node src/index.ts",
|
|
14
14
|
"test": "npx ts-node src/index.ts --test",
|
|
15
|
-
"reformat": "npx
|
|
16
|
-
"checkformat": "npx
|
|
15
|
+
"reformat": "npx @biomejs/biome format --write",
|
|
16
|
+
"checkformat": "npx @biomejs/biome format",
|
|
17
|
+
"lint": "npx @biomejs/biome lint",
|
|
18
|
+
"fixlint": "npx @biomejs/biome lint --write",
|
|
19
|
+
"checkformat-and-lint": "npx @biomejs/biome check",
|
|
20
|
+
"reformat-and-fixlint": "npx @biomejs/biome check --write",
|
|
17
21
|
"prepare": "npx tsc",
|
|
18
22
|
"unit-test": "vitest run"
|
|
19
23
|
},
|
|
20
24
|
"author": "",
|
|
21
|
-
"license": "
|
|
25
|
+
"license": "GPLV3",
|
|
22
26
|
"dependencies": {
|
|
23
27
|
"@pm2/io": "^6.1.0",
|
|
24
28
|
"ajv": "^8.17.1",
|
|
25
29
|
"ajv-formats": "^3.0.1",
|
|
26
30
|
"axios": "^1.8.2",
|
|
27
31
|
"body-parser": "^1.19.0",
|
|
28
|
-
"carta-frontend": "^
|
|
32
|
+
"carta-frontend": "^6.0.0-beta.1.0.2",
|
|
29
33
|
"compression": "^1.8.0",
|
|
30
34
|
"cookie-parser": "^1.4.7",
|
|
31
35
|
"cors": "^2.8.5",
|
|
@@ -45,22 +49,23 @@
|
|
|
45
49
|
"moment-timezone": "^0.6.0",
|
|
46
50
|
"mongodb": "^5.8.0",
|
|
47
51
|
"ms": "^2.1.3",
|
|
48
|
-
"node-linux-pam": "
|
|
52
|
+
"node-linux-pam": "github:cartavis/node-linux-pam#v0.2.2",
|
|
49
53
|
"pug": "^3.0.0",
|
|
50
54
|
"read": "^2.0.0",
|
|
51
55
|
"tcp-port-used": "^1.0.2",
|
|
52
56
|
"uuid": "^9.0.0",
|
|
53
|
-
"websocket": "^1.0.
|
|
57
|
+
"websocket": "^1.0.35",
|
|
54
58
|
"winston": "^3.17.0",
|
|
55
59
|
"yargs": "^17.0.1"
|
|
56
60
|
},
|
|
57
61
|
"devDependencies": {
|
|
62
|
+
"@biomejs/biome": "2.1.4",
|
|
58
63
|
"@types/compression": "^1.7.0",
|
|
59
64
|
"@types/cookie-parser": "^1.4.8",
|
|
60
65
|
"@types/cors": "^2.8.10",
|
|
61
66
|
"@types/express": "^4.17.11",
|
|
62
67
|
"@types/http-proxy": "^1.17.16",
|
|
63
|
-
"@types/jsonwebtoken": "
|
|
68
|
+
"@types/jsonwebtoken": "9.0.6",
|
|
64
69
|
"@types/lodash": "^4.17.15",
|
|
65
70
|
"@types/ms": "^0.7.31",
|
|
66
71
|
"@types/node": "^22.13.1",
|
|
@@ -68,8 +73,6 @@
|
|
|
68
73
|
"@types/uuid": "^9.0.0",
|
|
69
74
|
"@types/websocket": "^1.0.2",
|
|
70
75
|
"@types/yargs": "^17.0.22",
|
|
71
|
-
"patch-package": "^7.0.0",
|
|
72
|
-
"prettier": "2.8.4",
|
|
73
76
|
"ts-node": "^10.9.1",
|
|
74
77
|
"typescript": "^5.5.4",
|
|
75
78
|
"vitest": "^3.1.4"
|
package/public/dashboard.js
CHANGED
|
@@ -12,7 +12,6 @@ const isPopup = urlParams.get("popup");
|
|
|
12
12
|
|
|
13
13
|
let serverCheckHandle;
|
|
14
14
|
|
|
15
|
-
let authenticationType = "";
|
|
16
15
|
let authenticatedUser = "";
|
|
17
16
|
let token = "";
|
|
18
17
|
let tokenLifetime = -1;
|
|
@@ -22,8 +21,7 @@ let notyf;
|
|
|
22
21
|
|
|
23
22
|
let apiBase;
|
|
24
23
|
getApiBase = async () => {
|
|
25
|
-
if (apiBase)
|
|
26
|
-
return apiBase;
|
|
24
|
+
if (apiBase) return apiBase;
|
|
27
25
|
else {
|
|
28
26
|
try {
|
|
29
27
|
const configData = await fetch(`${strippedPath}config`);
|
|
@@ -35,7 +33,7 @@ getApiBase = async () => {
|
|
|
35
33
|
return "/api"; // use default
|
|
36
34
|
}
|
|
37
35
|
}
|
|
38
|
-
}
|
|
36
|
+
};
|
|
39
37
|
|
|
40
38
|
apiCall = async (callName, jsonBody, method, authRequired) => {
|
|
41
39
|
const options = {
|
|
@@ -43,13 +41,13 @@ apiCall = async (callName, jsonBody, method, authRequired) => {
|
|
|
43
41
|
};
|
|
44
42
|
if (method !== "get" && jsonBody) {
|
|
45
43
|
options.body = JSON.stringify(jsonBody);
|
|
46
|
-
options.headers = {"Content-Type": "application/json"}
|
|
44
|
+
options.headers = {"Content-Type": "application/json"};
|
|
47
45
|
} else {
|
|
48
46
|
options.headers = {};
|
|
49
47
|
}
|
|
50
48
|
|
|
51
49
|
if (token) {
|
|
52
|
-
options.headers
|
|
50
|
+
options.headers.Authorization = `Bearer ${token}`;
|
|
53
51
|
}
|
|
54
52
|
|
|
55
53
|
const currentTime = Date.now() / 1000;
|
|
@@ -62,12 +60,12 @@ apiCall = async (callName, jsonBody, method, authRequired) => {
|
|
|
62
60
|
}
|
|
63
61
|
}
|
|
64
62
|
return fetch(`${await getApiBase()}/${callName}`, options);
|
|
65
|
-
}
|
|
63
|
+
};
|
|
66
64
|
|
|
67
65
|
function setToken(tokenString, expiresIn) {
|
|
68
66
|
token = tokenString;
|
|
69
67
|
tokenLifetime = expiresIn;
|
|
70
|
-
if (isFinite(tokenLifetime) && tokenLifetime > 0) {
|
|
68
|
+
if (Number.isFinite(tokenLifetime) && tokenLifetime > 0) {
|
|
71
69
|
console.log(`Token updated and valid for ${tokenLifetime.toFixed()} seconds`);
|
|
72
70
|
const currentTimeSeconds = Date.now() / 1000;
|
|
73
71
|
tokenExpiryTime = currentTimeSeconds + tokenLifetime;
|
|
@@ -98,7 +96,7 @@ showMessage = (message, error, elementId) => {
|
|
|
98
96
|
statusElement.className = "success-message";
|
|
99
97
|
}
|
|
100
98
|
statusElement.innerHTML = message;
|
|
101
|
-
}
|
|
99
|
+
};
|
|
102
100
|
|
|
103
101
|
setButtonDisabled = (elementId, disabled) => {
|
|
104
102
|
const button = document.getElementById(elementId);
|
|
@@ -107,10 +105,10 @@ setButtonDisabled = (elementId, disabled) => {
|
|
|
107
105
|
if (disabled) {
|
|
108
106
|
button.classList.add("button-disabled");
|
|
109
107
|
} else {
|
|
110
|
-
button.classList.remove("button-disabled")
|
|
108
|
+
button.classList.remove("button-disabled");
|
|
111
109
|
}
|
|
112
110
|
}
|
|
113
|
-
}
|
|
111
|
+
};
|
|
114
112
|
|
|
115
113
|
updateServerStatus = async () => {
|
|
116
114
|
let hasServer = false;
|
|
@@ -130,15 +128,15 @@ updateServerStatus = async () => {
|
|
|
130
128
|
}
|
|
131
129
|
updateRedirectURL(hasServer);
|
|
132
130
|
serverRunning = hasServer;
|
|
133
|
-
}
|
|
131
|
+
};
|
|
134
132
|
|
|
135
|
-
updateRedirectURL =
|
|
133
|
+
updateRedirectURL = hasServer => {
|
|
136
134
|
if (hasServer) {
|
|
137
135
|
showMessage("CARTA server running", false, "carta-status");
|
|
138
136
|
} else {
|
|
139
137
|
showMessage(`Logged in as ${authenticatedUser}`, false, "carta-status");
|
|
140
138
|
}
|
|
141
|
-
}
|
|
139
|
+
};
|
|
142
140
|
|
|
143
141
|
handleLogin = async () => {
|
|
144
142
|
setButtonDisabled("login", true);
|
|
@@ -156,20 +154,19 @@ handleLogin = async () => {
|
|
|
156
154
|
} else {
|
|
157
155
|
onLoginFailed(res.status);
|
|
158
156
|
}
|
|
159
|
-
} catch (
|
|
157
|
+
} catch (_e) {
|
|
160
158
|
onLoginFailed(500);
|
|
161
159
|
}
|
|
162
160
|
setButtonDisabled("login", false);
|
|
163
161
|
};
|
|
164
162
|
|
|
165
|
-
onLoginFailed =
|
|
163
|
+
onLoginFailed = status => {
|
|
166
164
|
clearToken();
|
|
167
165
|
notyf.error(status === 403 ? "Invalid username/password combination" : "Could not authenticate correctly");
|
|
168
|
-
}
|
|
166
|
+
};
|
|
169
167
|
|
|
170
168
|
onLoginSucceeded = async (username, type) => {
|
|
171
169
|
authenticatedUser = username;
|
|
172
|
-
authenticationType = type;
|
|
173
170
|
localStorage.setItem("authenticationType", type);
|
|
174
171
|
notyf.success(`Logged in as ${authenticatedUser}`);
|
|
175
172
|
if (autoRedirect) {
|
|
@@ -181,7 +178,7 @@ onLoginSucceeded = async (username, type) => {
|
|
|
181
178
|
serverCheckHandle = setInterval(updateServerStatus, 5000);
|
|
182
179
|
await updateServerStatus();
|
|
183
180
|
}
|
|
184
|
-
}
|
|
181
|
+
};
|
|
185
182
|
|
|
186
183
|
handleServerStop = async () => {
|
|
187
184
|
try {
|
|
@@ -202,7 +199,7 @@ handleServerStop = async () => {
|
|
|
202
199
|
notyf.error("Failed to stop CARTA server");
|
|
203
200
|
console.log(e);
|
|
204
201
|
}
|
|
205
|
-
}
|
|
202
|
+
};
|
|
206
203
|
|
|
207
204
|
handleLogout = async () => {
|
|
208
205
|
localStorage.removeItem("authenticationType");
|
|
@@ -210,11 +207,11 @@ handleLogout = async () => {
|
|
|
210
207
|
await handleServerStop();
|
|
211
208
|
}
|
|
212
209
|
window.open(`${await getApiBase()}/auth/logout`, "_self");
|
|
213
|
-
}
|
|
210
|
+
};
|
|
214
211
|
|
|
215
212
|
handleOpenCarta = () => {
|
|
216
213
|
window.open(redirectUrl, "_self");
|
|
217
|
-
}
|
|
214
|
+
};
|
|
218
215
|
|
|
219
216
|
handleLog = async () => {
|
|
220
217
|
// Disable log buttons for 5 seconds
|
|
@@ -230,7 +227,7 @@ handleLog = async () => {
|
|
|
230
227
|
const res = await apiCall("server/log", undefined, "get", true);
|
|
231
228
|
const body = await res.json();
|
|
232
229
|
if (body.success && body.log) {
|
|
233
|
-
document.getElementById("log-modal").style.display = "block"
|
|
230
|
+
document.getElementById("log-modal").style.display = "block";
|
|
234
231
|
document.getElementById("main-div").classList.add("blurred");
|
|
235
232
|
const outputElement = document.getElementById("log-output");
|
|
236
233
|
if (outputElement) {
|
|
@@ -244,25 +241,25 @@ handleLog = async () => {
|
|
|
244
241
|
} catch (e) {
|
|
245
242
|
console.log(e);
|
|
246
243
|
}
|
|
247
|
-
}
|
|
244
|
+
};
|
|
248
245
|
|
|
249
246
|
handleHideLog = () => {
|
|
250
|
-
document.getElementById("log-modal").style.display = "none"
|
|
247
|
+
document.getElementById("log-modal").style.display = "none";
|
|
251
248
|
document.getElementById("main-div").classList.remove("blurred");
|
|
252
|
-
}
|
|
249
|
+
};
|
|
253
250
|
|
|
254
251
|
handleLocalLogout = async () => {
|
|
255
252
|
await apiCall("auth/logout", undefined, "post", false);
|
|
256
|
-
}
|
|
253
|
+
};
|
|
257
254
|
|
|
258
|
-
handleKeyup =
|
|
255
|
+
handleKeyup = e => {
|
|
259
256
|
if (e.keyCode === 13) {
|
|
260
257
|
const loginButton = document.getElementById("login");
|
|
261
258
|
if (loginButton && !loginButton.disabled) {
|
|
262
259
|
handleLogin();
|
|
263
260
|
}
|
|
264
261
|
}
|
|
265
|
-
}
|
|
262
|
+
};
|
|
266
263
|
|
|
267
264
|
refreshLocalToken = async () => {
|
|
268
265
|
try {
|
|
@@ -277,46 +274,47 @@ refreshLocalToken = async () => {
|
|
|
277
274
|
notyf.error("Error refreshing authentication");
|
|
278
275
|
console.log(err);
|
|
279
276
|
}
|
|
280
|
-
}
|
|
277
|
+
};
|
|
281
278
|
|
|
282
|
-
showCartaForm =
|
|
279
|
+
showCartaForm = show => {
|
|
283
280
|
const cartaForm = document.getElementsByClassName("carta-form")[0];
|
|
284
281
|
if (show) {
|
|
285
282
|
cartaForm.style.display = "block";
|
|
286
283
|
} else {
|
|
287
284
|
cartaForm.style.display = "none";
|
|
288
|
-
|
|
289
285
|
}
|
|
290
|
-
}
|
|
286
|
+
};
|
|
291
287
|
|
|
292
|
-
showLoginForm =
|
|
288
|
+
showLoginForm = show => {
|
|
293
289
|
const loginForm = document.getElementsByClassName("login-form")[0];
|
|
294
290
|
if (show) {
|
|
295
291
|
loginForm.style.display = "block";
|
|
296
292
|
} else {
|
|
297
293
|
loginForm.style.display = "none";
|
|
298
|
-
|
|
299
294
|
}
|
|
300
|
-
}
|
|
295
|
+
};
|
|
301
296
|
|
|
302
297
|
window.onload = async () => {
|
|
303
298
|
notyf = new Notyf({
|
|
304
299
|
ripple: true,
|
|
305
300
|
position: {x: "center"},
|
|
306
|
-
types: [
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
301
|
+
types: [
|
|
302
|
+
{
|
|
303
|
+
type: "warning",
|
|
304
|
+
background: "orange"
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
type: "info",
|
|
308
|
+
background: "#4c84af"
|
|
309
|
+
}
|
|
310
|
+
]
|
|
313
311
|
});
|
|
314
312
|
|
|
315
313
|
// Check for completed login
|
|
316
314
|
const usp = new URLSearchParams(window.location.search);
|
|
317
315
|
if (usp.has("oidcuser")) {
|
|
318
316
|
await refreshLocalToken();
|
|
319
|
-
onLoginSucceeded(usp.get("oidcuser"), "oidc")
|
|
317
|
+
onLoginSucceeded(usp.get("oidcuser"), "oidc");
|
|
320
318
|
} else if (usp.has("googleuser")) {
|
|
321
319
|
await refreshLocalToken();
|
|
322
320
|
if (localStorage.getItem("redirectParams")) {
|
|
@@ -324,7 +322,7 @@ window.onload = async () => {
|
|
|
324
322
|
localStorage.removeItem("redirectParams");
|
|
325
323
|
autoRedirect = true;
|
|
326
324
|
}
|
|
327
|
-
onLoginSucceeded(usp.get("googleuser"), "google")
|
|
325
|
+
onLoginSucceeded(usp.get("googleuser"), "google");
|
|
328
326
|
} else if (usp.has("err")) {
|
|
329
327
|
console.log(usp.get("err"));
|
|
330
328
|
notyf.open({type: "error", message: usp.get("err")});
|
|
@@ -375,7 +373,9 @@ window.onload = async () => {
|
|
|
375
373
|
|
|
376
374
|
const oidcLoginButton = document.getElementById("oidcLogin");
|
|
377
375
|
if (oidcLoginButton) {
|
|
378
|
-
oidcLoginButton.onclick = async () => {
|
|
376
|
+
oidcLoginButton.onclick = async () => {
|
|
377
|
+
window.location.href = `${await getApiBase()}/auth/login${window.location.search}`;
|
|
378
|
+
};
|
|
379
379
|
}
|
|
380
380
|
|
|
381
381
|
document.getElementById("stop").onclick = handleServerStop;
|
|
@@ -384,5 +384,4 @@ window.onload = async () => {
|
|
|
384
384
|
document.getElementById("refresh-logs").onclick = handleLog;
|
|
385
385
|
document.getElementById("hide-logs").onclick = handleHideLog;
|
|
386
386
|
document.getElementById("logout").onclick = handleLogout;
|
|
387
|
-
|
|
388
|
-
}
|
|
387
|
+
};
|