@xenon-device-management/xenon 1.7.2 → 1.7.3
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/lib/package.json +2 -1
- package/lib/src/app/index.js +6 -0
- package/lib/src/app/routers/auth.js +30 -0
- package/lib/src/app/routers/capabilities.js +25 -0
- package/lib/src/app/routers/control.js +14 -0
- package/lib/src/app/routers/projects.js +55 -0
- package/lib/src/generated/client/edge.js +22 -4
- package/lib/src/generated/client/index-browser.js +19 -1
- package/lib/src/generated/client/index.d.ts +2376 -158
- package/lib/src/generated/client/index.js +22 -4
- package/lib/src/generated/client/package.json +1 -1
- package/lib/src/generated/client/schema.prisma +21 -0
- package/lib/src/generated/client/wasm.js +19 -1
- package/lib/src/middleware/authMiddleware.js +64 -3
- package/lib/src/prisma.js +1 -1
- package/lib/src/services/EventLogService.js +81 -0
- package/lib/src/services/ServerManager.js +36 -0
- package/lib/src/services/SocketServer.js +2 -0
- package/lib/src/services/artifacts/ArtifactStore.js +60 -0
- package/lib/src/services/recording/RecordingOrchestrator.js +4 -5
- package/lib/src/services/token/JwtKeyService.js +137 -0
- package/lib/src/services/token/StreamTicketService.js +61 -0
- package/lib/test/unit/ArtifactStore.spec.js +59 -0
- package/lib/test/unit/CapabilitiesRouter.spec.js +18 -0
- package/lib/test/unit/EventLogService.spec.js +67 -0
- package/lib/test/unit/JwtKeyService.spec.js +86 -0
- package/lib/test/unit/ProjectsRouter.spec.js +45 -0
- package/lib/test/unit/StreamTicketService.spec.js +95 -0
- package/lib/test/unit/TokenEndpoint.spec.js +68 -0
- package/lib/test/unit/authMiddleware.bearer.spec.js +102 -0
- package/lib/test/unit/authMiddleware.streamTicket.spec.js +217 -0
- package/lib/test/unit/recording-instrumentation.spec.js +8 -0
- package/lib/test/unit/recording-orchestrator.spec.js +14 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -1
- package/prisma/migrations/20260717063103_event_log/migration.sql +15 -0
- package/prisma/migrations/20260717070124_project_entity/migration.sql +10 -0
- package/prisma/schema.prisma +21 -0
package/lib/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xenon-device-management/xenon",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.3",
|
|
4
4
|
"description": "Xenon - Intelligent Mobile Infrastructure. A self-healing device orchestration platform for Appium.",
|
|
5
5
|
"main": "./lib/src/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -92,6 +92,7 @@
|
|
|
92
92
|
"http-proxy-middleware": "^3.0.0-beta.1",
|
|
93
93
|
"https-proxy-agent": "7.0.2",
|
|
94
94
|
"ip": "^1.1.8",
|
|
95
|
+
"jose": "^5.10.0",
|
|
95
96
|
"jsonschema": "1.4.1",
|
|
96
97
|
"lodash": "^4.17.21",
|
|
97
98
|
"lokijs": "^1.5.12",
|
package/lib/src/app/index.js
CHANGED
|
@@ -77,6 +77,8 @@ const auth_1 = require("./routers/auth");
|
|
|
77
77
|
const processes_1 = require("./routers/processes");
|
|
78
78
|
const profile_1 = require("./routers/profile");
|
|
79
79
|
const users_1 = require("./routers/users");
|
|
80
|
+
const capabilities_1 = require("./routers/capabilities");
|
|
81
|
+
const projects_1 = require("./routers/projects");
|
|
80
82
|
const authMiddleware_1 = require("../middleware/authMiddleware");
|
|
81
83
|
const rateLimitMiddleware_1 = require("../middleware/rateLimitMiddleware");
|
|
82
84
|
const csrfMiddleware_1 = require("../middleware/csrfMiddleware");
|
|
@@ -243,6 +245,10 @@ function createRouter(pluginArgs) {
|
|
|
243
245
|
apiRouter.use('/teams', (0, teams_1.teamsRouter)());
|
|
244
246
|
// Admin: running process snapshot (ops debugging)
|
|
245
247
|
apiRouter.use('/processes', (0, processes_1.processesRouter)());
|
|
248
|
+
// Feature detection endpoint
|
|
249
|
+
apiRouter.use('/capabilities', (0, capabilities_1.capabilitiesRouter)());
|
|
250
|
+
// Project entity skeleton — container for later runs/flows/secrets (ARB guard #4)
|
|
251
|
+
apiRouter.use('/projects', (0, projects_1.projectsRouter)());
|
|
246
252
|
// Exposes plugin CLI args (may include host, hub URL, etc.) — auth-gated.
|
|
247
253
|
apiRouter.get('/cliArgs', (_req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
248
254
|
res.json(yield (0, pluginArgs_1.getCLIArgs)());
|
|
@@ -9,6 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.issueToken = issueToken;
|
|
12
13
|
exports.authPublicRouter = authPublicRouter;
|
|
13
14
|
exports.authAuthedRouter = authAuthedRouter;
|
|
14
15
|
exports.authRouter = authRouter;
|
|
@@ -23,8 +24,24 @@ const EmailService_1 = require("../../services/EmailService");
|
|
|
23
24
|
const loginRateLimiter_1 = require("../../middleware/loginRateLimiter");
|
|
24
25
|
const config_1 = require("../../config");
|
|
25
26
|
const prisma_1 = require("../../prisma");
|
|
27
|
+
const JwtKeyService_1 = require("../../services/token/JwtKeyService");
|
|
26
28
|
const SESSION_COOKIE = 'xenon_dashboard_session';
|
|
27
29
|
const isSecureFromReq = (req) => req.secure || req.headers['x-forwarded-proto'] === 'https';
|
|
30
|
+
const MCP_TTL_SEC = Number(process.env.XENON_MCP_TOKEN_TTL_SEC || 86400);
|
|
31
|
+
const REST_TTL_SEC = 3600;
|
|
32
|
+
const MINTABLE_AUDIENCES = ['xenon-rest', 'xenon-mcp'];
|
|
33
|
+
function issueToken(auth, body) {
|
|
34
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
35
|
+
var _a, _b;
|
|
36
|
+
const audience = (_a = body.audience) !== null && _a !== void 0 ? _a : 'xenon-rest';
|
|
37
|
+
if (!MINTABLE_AUDIENCES.includes(audience)) {
|
|
38
|
+
throw new Error(`unsupported audience: ${audience}`);
|
|
39
|
+
}
|
|
40
|
+
const expiresIn = audience === 'xenon-mcp' ? MCP_TTL_SEC : REST_TTL_SEC;
|
|
41
|
+
const token = yield typedi_1.Container.get(JwtKeyService_1.JwtKeyService).sign({ sub: auth.userId, role: auth.role, scopes: auth.scopes, teamId: (_b = auth.teamId) !== null && _b !== void 0 ? _b : null }, { audience, ttlSeconds: expiresIn });
|
|
42
|
+
return { token, expiresIn, audience };
|
|
43
|
+
});
|
|
44
|
+
}
|
|
28
45
|
// Public — anonymous-callable. Mounted before authMiddleware.
|
|
29
46
|
function authPublicRouter() {
|
|
30
47
|
var _a, _b;
|
|
@@ -108,6 +125,9 @@ function authPublicRouter() {
|
|
|
108
125
|
if (elapsed < 50)
|
|
109
126
|
yield new Promise((res2) => setTimeout(res2, 50 - elapsed));
|
|
110
127
|
}));
|
|
128
|
+
r.get('/jwks.json', (_req, res) => {
|
|
129
|
+
res.json(typedi_1.Container.get(JwtKeyService_1.JwtKeyService).jwks());
|
|
130
|
+
});
|
|
111
131
|
r.get('/reset-password/check/:token', (req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
112
132
|
const resetSvc = typedi_1.Container.get(PasswordResetService_1.PasswordResetService);
|
|
113
133
|
const row = yield resetSvc.verifyToken(req.params.token);
|
|
@@ -213,6 +233,16 @@ function authAuthedRouter() {
|
|
|
213
233
|
teams,
|
|
214
234
|
});
|
|
215
235
|
}));
|
|
236
|
+
r.post('/token', (req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
237
|
+
var _a;
|
|
238
|
+
try {
|
|
239
|
+
const out = yield issueToken(req.auth, (_a = req.body) !== null && _a !== void 0 ? _a : {});
|
|
240
|
+
res.json(out);
|
|
241
|
+
}
|
|
242
|
+
catch (e) {
|
|
243
|
+
res.status(400).json({ error: e.message });
|
|
244
|
+
}
|
|
245
|
+
}));
|
|
216
246
|
r.post('/dashboard-session', (req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
217
247
|
const { apiKey } = req.body;
|
|
218
248
|
if (!apiKey)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildCapabilities = buildCapabilities;
|
|
4
|
+
exports.capabilitiesRouter = capabilitiesRouter;
|
|
5
|
+
const express_1 = require("express");
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
7
|
+
const pkg = require('../../../package.json');
|
|
8
|
+
function buildCapabilities() {
|
|
9
|
+
return {
|
|
10
|
+
version: String(pkg.version),
|
|
11
|
+
features: {
|
|
12
|
+
bearerAuth: true,
|
|
13
|
+
tokenIssuance: true,
|
|
14
|
+
streamTickets: true,
|
|
15
|
+
leases: true, // pre-existing: /sdk/leases
|
|
16
|
+
eventLog: true, // Task 6
|
|
17
|
+
projects: true, // Task 8
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function capabilitiesRouter() {
|
|
22
|
+
const r = (0, express_1.Router)();
|
|
23
|
+
r.get('/', (_req, res) => res.json(buildCapabilities()));
|
|
24
|
+
return r;
|
|
25
|
+
}
|
|
@@ -60,6 +60,7 @@ const os_1 = __importDefault(require("os"));
|
|
|
60
60
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
61
61
|
const OmniVisionService_1 = require("../../services/omni-vision/OmniVisionService");
|
|
62
62
|
const InspectorService_1 = require("../../services/InspectorService");
|
|
63
|
+
const StreamTicketService_1 = require("../../services/token/StreamTicketService");
|
|
63
64
|
const scopeGuard_1 = require("../../middleware/scopeGuard");
|
|
64
65
|
const roleGuard_1 = require("../../middleware/roleGuard");
|
|
65
66
|
const manualLock_1 = require("../../services/recording/manualLock");
|
|
@@ -607,6 +608,19 @@ router.post('/:udid/stream/start', (req, res) => __awaiter(void 0, void 0, void
|
|
|
607
608
|
});
|
|
608
609
|
}
|
|
609
610
|
}));
|
|
611
|
+
/**
|
|
612
|
+
* Mint a single-use, udid-bound stream ticket for the webview <img> MJPEG
|
|
613
|
+
* path (spec §2.3/§7.1) — a raw <img src> can carry neither auth headers nor
|
|
614
|
+
* the SameSite=strict dashboard cookie, so it authenticates via ?ticket=.
|
|
615
|
+
*/
|
|
616
|
+
router.post('/:udid/stream/ticket', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
617
|
+
var _a, _b, _c;
|
|
618
|
+
const actorId = (_b = (_a = req.apiKey) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : (_c = req.auth) === null || _c === void 0 ? void 0 : _c.userId;
|
|
619
|
+
if (!actorId)
|
|
620
|
+
return res.status(401).json({ error: 'unauthenticated' });
|
|
621
|
+
const ticket = yield typedi_1.Container.get(StreamTicketService_1.StreamTicketService).mint(req.params.udid, actorId);
|
|
622
|
+
res.json({ ticket, expiresIn: 60 });
|
|
623
|
+
}));
|
|
610
624
|
/**
|
|
611
625
|
* Stop stream endpoint
|
|
612
626
|
*/
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.listProjects = listProjects;
|
|
13
|
+
exports.createProject = createProject;
|
|
14
|
+
exports.projectsRouter = projectsRouter;
|
|
15
|
+
const express_1 = require("express");
|
|
16
|
+
const prisma_1 = require("../../prisma");
|
|
17
|
+
const scopeGuard_1 = require("../../middleware/scopeGuard");
|
|
18
|
+
function listProjects(client, teamIds) {
|
|
19
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
20
|
+
const where = teamIds ? { where: { OR: [{ teamId: { in: teamIds } }, { teamId: null }] } } : {};
|
|
21
|
+
return client.project.findMany(where);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
function createProject(client, body) {
|
|
25
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
26
|
+
var _a;
|
|
27
|
+
if (!body.name || !body.name.trim())
|
|
28
|
+
throw new Error('name is required');
|
|
29
|
+
return client.project.create({
|
|
30
|
+
data: { name: body.name.trim(), teamId: ((_a = body.teamId) === null || _a === void 0 ? void 0 : _a.trim()) || null },
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function projectsRouter() {
|
|
35
|
+
const r = (0, express_1.Router)();
|
|
36
|
+
r.get('/', (req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
37
|
+
var _a;
|
|
38
|
+
try {
|
|
39
|
+
res.json(yield listProjects(prisma_1.prisma, (_a = req.auth) === null || _a === void 0 ? void 0 : _a.teamIds));
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
res.status(500).json({ error: e.message });
|
|
43
|
+
}
|
|
44
|
+
}));
|
|
45
|
+
r.post('/', (0, scopeGuard_1.scopeGuard)(['admin']), (req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
46
|
+
var _a;
|
|
47
|
+
try {
|
|
48
|
+
res.status(201).json(yield createProject(prisma_1.prisma, (_a = req.body) !== null && _a !== void 0 ? _a : {}));
|
|
49
|
+
}
|
|
50
|
+
catch (e) {
|
|
51
|
+
res.status(400).json({ error: e.message });
|
|
52
|
+
}
|
|
53
|
+
}));
|
|
54
|
+
return r;
|
|
55
|
+
}
|