@xenon-device-management/xenon 1.29.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -2
- package/lib/package.json +1 -1
- package/lib/public/assets/{AnnotationOverlay-Ne9IA8t9.js → AnnotationOverlay-BmTPCAds.js} +1 -1
- package/lib/public/assets/{ApiKeyGate-CwXbDjtR.js → ApiKeyGate-B5eB7f8B.js} +1 -1
- package/lib/public/assets/{BugReportButton-D8694Ndn.js → BugReportButton-DKYsOdGV.js} +1 -1
- package/lib/public/assets/{DeviceMosaicView-vqKa_a7b.js → DeviceMosaicView-9DUoZnQi.js} +1 -1
- package/lib/public/assets/{EmptyState-DqbEejWx.js → EmptyState-lvyNiXev.js} +1 -1
- package/lib/public/assets/{FieldGroup-BNKuUsHt.js → FieldGroup-BduMPM9K.js} +1 -1
- package/lib/public/assets/{FilterMenu-CLqoSOsz.js → FilterMenu-BSqX6I94.js} +1 -1
- package/lib/public/assets/{Menu-CU57vgsd.js → Menu-Ba5cT7jZ.js} +1 -1
- package/lib/public/assets/{Modal-Dt2whV22.js → Modal-CceoMY7D.js} +1 -1
- package/lib/public/assets/{RecordingPage-BipS3IoG.js → RecordingPage-6T9dOidc.js} +1 -1
- package/lib/public/assets/{RecordingsPage-DORtXeH5.js → RecordingsPage-qj6FdZXR.js} +1 -1
- package/lib/public/assets/{SegmentedControl-IX6aSFFO.js → SegmentedControl-CSRuZ8TR.js} +1 -1
- package/lib/public/assets/{SettingCard-DDp2N5zA.js → SettingCard-Bp1kz4By.js} +1 -1
- package/lib/public/assets/{Table-CiaA4Dni.js → Table-LV7zFyZg.js} +1 -1
- package/lib/public/assets/{activity-DzhwJvJO.js → activity-D17TUloi.js} +1 -1
- package/lib/public/assets/{ai-settings-BllB4M52.js → ai-settings-qvbhY89P.js} +1 -1
- package/lib/public/assets/api-keys-BfpvdG7G.js +1 -0
- package/lib/public/assets/apps-DMhpZuKd.css +1 -0
- package/lib/public/assets/apps-Dbnzd0fW.js +26 -0
- package/lib/public/assets/{arrow-left-poHDsOnw.js → arrow-left-DD1VjtC7.js} +1 -1
- package/lib/public/assets/{arrow-right-Bt8ka8uH.js → arrow-right-D0pnM3ma.js} +1 -1
- package/lib/public/assets/{arrow-up-right-C7zjEfIX.js → arrow-up-right-BIC8JOV6.js} +1 -1
- package/lib/public/assets/{auth-shell-DqNP66nv.js → auth-shell-DWHPXSnI.js} +2 -2
- package/lib/public/assets/{builds-page-BmoSSMe1.js → builds-page-ClCNBUv_.js} +1 -1
- package/lib/public/assets/{button-DujkWRKD.js → button-xtAhYR-f.js} +1 -1
- package/lib/public/assets/{calendar-3xj_SfAt.js → calendar-CupwJ7Bm.js} +1 -1
- package/lib/public/assets/{check-DAtcemi7.js → check-CyuEoKkd.js} +1 -1
- package/lib/public/assets/{chevron-left-BODVavmZ.js → chevron-left-DgX-F9xK.js} +1 -1
- package/lib/public/assets/{chevron-right-BtzJa9CV.js → chevron-right-BKJgJnU9.js} +1 -1
- package/lib/public/assets/{circle-check-CNLy00dn.js → circle-check-CZdg8mMa.js} +1 -1
- package/lib/public/assets/{circle-x-DwlxOXJV.js → circle-x-CZH6t9z-.js} +1 -1
- package/lib/public/assets/{clock-CthJSgmT.js → clock-Cd-t7xpe.js} +1 -1
- package/lib/public/assets/{copy-o_vwnvDl.js → copy-C6zerX3I.js} +1 -1
- package/lib/public/assets/{device-explorer-BGwZQSit.js → device-explorer-B1pI6b_M.js} +2 -2
- package/lib/public/assets/device-explorer-DV8rWRxL.css +1 -0
- package/lib/public/assets/{download-CH7KYHQ6.js → download-x9WP7mcL.js} +1 -1
- package/lib/public/assets/{forgot-password-Fo0NoMuQ.js → forgot-password-B0TEH4N-.js} +1 -1
- package/lib/public/assets/{funnel-DLq7lVeF.js → funnel-Cb40MfFN.js} +1 -1
- package/lib/public/assets/{index-SVMaJ9rC.js → index-BR1wTIPC.js} +9 -9
- package/lib/public/assets/{index-tbOE1tW_.js → index-BvH_ekRy.js} +1 -1
- package/lib/public/assets/{index-Fa91L6sA.css → index-hymrhlEZ.css} +1 -1
- package/lib/public/assets/{input-Cn9aCi1V.js → input-DejcCiTI.js} +1 -1
- package/lib/public/assets/{keycodes-DVNcuy4F.js → keycodes-Mavx5iGg.js} +1 -1
- package/lib/public/assets/{layers-DFA4vIXT.js → layers-BKqYLiHE.js} +1 -1
- package/lib/public/assets/{lock-DbVuQMlE.js → lock-LFsFhc17.js} +1 -1
- package/lib/public/assets/{login-kvBEtLEI.js → login-x2pkzt-t.js} +1 -1
- package/lib/public/assets/{maintenance-settings-9MSQd7-h.js → maintenance-settings-CYafcoV-.js} +1 -1
- package/lib/public/assets/{monitor-C2wiQCzm.js → monitor-_7_uJ8d9.js} +1 -1
- package/lib/public/assets/{mouse-pointer-2-BxOt4PTR.js → mouse-pointer-2-BfLuURKN.js} +1 -1
- package/lib/public/assets/{network-VOsv-B1C.js → network-Z_zBhpsD.js} +1 -1
- package/lib/public/assets/{overview-BxRv07jX.js → overview-BY5uv08S.js} +1 -1
- package/lib/public/assets/{page-header-DRYmsZ55.js → page-header-CP2UkykG.js} +1 -1
- package/lib/public/assets/{plus-BY8JP9c5.js → plus-BTD0dnQ0.js} +1 -1
- package/lib/public/assets/{profile-page-C0IMKJRO.js → profile-page-BhJZuCS7.js} +1 -1
- package/lib/public/assets/{reset-password-2OxcQdve.js → reset-password-CMN0SO81.js} +1 -1
- package/lib/public/assets/{runbook-page-ChzDYLnk.js → runbook-page-CBpPX9_5.js} +1 -1
- package/lib/public/assets/{select-DkLtE2T3.js → select-Uc9E5pye.js} +1 -1
- package/lib/public/assets/{selector-detail-page-9XKCgLLk.js → selector-detail-page-BSyBnRuU.js} +1 -1
- package/lib/public/assets/{selector-health-CZb7lL82.js → selector-health-DzqxNkzm.js} +1 -1
- package/lib/public/assets/{selector-health-page-CgNkXERq.js → selector-health-page-CyPLjoD1.js} +1 -1
- package/lib/public/assets/{session-detail-page-V68omLzd.js → session-detail-page-BZQZN1P-.js} +1 -1
- package/lib/public/assets/{settings-BTO5_h8Z.js → settings-CkSWiZq8.js} +1 -1
- package/lib/public/assets/{status-pill-outline-DteJiQpj.js → status-pill-outline-BFYni2qo.js} +1 -1
- package/lib/public/assets/{tablet-BvW2cqBO.js → tablet-BSd-xSZK.js} +1 -1
- package/lib/public/assets/teams-PpoEQz31.js +1 -0
- package/lib/public/assets/{trash-2-Ck4Hj9AT.js → trash-2-BorJv5_u.js} +1 -1
- package/lib/public/assets/{upload-DOZwUk1H.js → upload-C3i7sAZl.js} +1 -1
- package/lib/public/assets/{use-password-reset-mode-BkBlX2qd.js → use-password-reset-mode-CeZQ3EsH.js} +1 -1
- package/lib/public/assets/{users-yoXht7X_.js → users-CEJ3fC78.js} +1 -1
- package/lib/public/assets/{users-CkggsUeX.js → users-Drd7ifJx.js} +1 -1
- package/lib/public/assets/{video-off-DZG2o_Gl.js → video-off-CNzn6bPE.js} +1 -1
- package/lib/public/assets/{webhook-settings-D31jQ3yC.js → webhook-settings-BFqX6QXV.js} +1 -1
- package/lib/public/assets/{zap-nAiJWOGv.js → zap-4yN8Jdfw.js} +1 -1
- package/lib/public/index.html +2 -2
- package/lib/schema.json +1 -1
- package/lib/src/XenonCapabilityManager.js +21 -50
- package/lib/src/app/index.js +14 -9
- package/lib/src/app/insertBeforeRoutes.js +57 -0
- package/lib/src/app/registerCommandAuth.js +67 -0
- package/lib/src/app/routers/apps.js +83 -5
- package/lib/src/app/routers/auth.js +1 -1
- package/lib/src/app/routers/bug-report.js +12 -0
- package/lib/src/app/routers/capabilities.js +4 -0
- package/lib/src/app/routers/control.js +6 -1
- package/lib/src/app/routers/dashboard.js +116 -66
- package/lib/src/app/routers/grid.js +38 -57
- package/lib/src/app/routers/recordings.js +3 -1
- package/lib/src/app/swagger-docs.js +7 -4
- package/lib/src/app/swagger.js +3 -0
- package/lib/src/dashboard/event-manager.js +18 -21
- package/lib/src/dashboard/services/app-service.js +24 -2
- package/lib/src/data-service/device-service.js +2 -1
- package/lib/src/data-service/queue-service.js +4 -3
- package/lib/src/device-utils.js +1 -1
- package/lib/src/generated/client/edge.js +5 -4
- package/lib/src/generated/client/index-browser.js +2 -1
- package/lib/src/generated/client/index.d.ts +413 -7
- package/lib/src/generated/client/index.js +5 -4
- package/lib/src/generated/client/package.json +1 -1
- package/lib/src/generated/client/schema.prisma +7 -0
- package/lib/src/generated/client/wasm.js +2 -1
- package/lib/src/interceptors/CommandInterceptor.js +5 -5
- package/lib/src/middleware/authMiddleware.js +44 -36
- package/lib/src/middleware/commandAuth.js +146 -0
- package/lib/src/middleware/commandCaller.js +142 -0
- package/lib/src/middleware/verifyCredential.js +124 -0
- package/lib/src/services/ServerManager.js +7 -0
- package/lib/src/services/SessionLifecycleService.js +143 -73
- package/lib/src/services/TeamService.js +6 -3
- package/lib/src/services/device-access/appVisibility.js +27 -0
- package/lib/src/services/device-access/queueVisibility.js +118 -0
- package/lib/src/services/device-access/sessionVisibility.js +65 -0
- package/lib/src/services/healing/HealingOrchestrator.js +11 -3
- package/lib/src/services/lease/LeaseService.js +13 -4
- package/lib/src/services/lease/buildCapabilityBag.js +4 -1
- package/lib/src/services/lease/leaseSessionCaps.js +13 -69
- package/lib/src/services/recording/annotation-render.js +33 -1
- package/lib/src/services/recording/recordingSummary.js +6 -5
- package/lib/src/services/recording/recordingTiming.js +19 -5
- package/lib/src/services/session/appCapability.js +50 -0
- package/lib/src/services/session/sessionCredentials.js +105 -0
- package/lib/src/services/session/xenonOptions.js +71 -0
- package/lib/src/services/sessionTokenGate.js +19 -9
- package/lib/src/services/token/AppDownloadTicketService.js +73 -0
- package/lib/src/services/token/StreamTicketService.js +7 -17
- package/lib/src/services/token/singleUseLedger.js +41 -0
- package/lib/src/sessions/XenonSession.js +2 -2
- package/lib/test/helpers/loopbackServer.js +48 -0
- package/lib/test/integration/sdk-leases-router.spec.js +1 -1
- package/lib/test/integration/team-visibility-apps.spec.js +181 -0
- package/lib/test/integration/team-visibility-recordings.spec.js +4 -0
- package/lib/test/integration/team-visibility-sessions.spec.js +274 -0
- package/lib/test/unit/AppDownloadTicketService.spec.js +101 -0
- package/lib/test/unit/CapabilitiesRouter.spec.js +10 -0
- package/lib/test/unit/SessionTokenGate.spec.js +7 -2
- package/lib/test/unit/TeamService.test.js +22 -0
- package/lib/test/unit/XenonCapabilityManager.spec.js +11 -1
- package/lib/test/unit/aggregate-hotspots-session-scope.spec.js +202 -0
- package/lib/test/unit/annotation-render.spec.js +90 -0
- package/lib/test/unit/apps-team-visibility.spec.js +305 -0
- package/lib/test/unit/authMiddleware.appTicket.spec.js +201 -0
- package/lib/test/unit/bug-report/route.spec.js +49 -2
- package/lib/test/unit/cliargs-admin-only.spec.js +182 -0
- package/lib/test/unit/command-auth-appium-server.spec.js +184 -0
- package/lib/test/unit/command-auth.spec.js +410 -0
- package/lib/test/unit/command-caller.spec.js +254 -0
- package/lib/test/unit/event-manager-regression-hook.spec.js +7 -1
- package/lib/test/unit/extract-access-key-token-pair.test.js +47 -4
- package/lib/test/unit/grid-admin-only.spec.js +55 -0
- package/lib/test/unit/healing-tier-gate.spec.js +24 -1
- package/lib/test/unit/insert-before-routes.spec.js +167 -0
- package/lib/test/unit/install-repository-app-team.spec.js +116 -0
- package/lib/test/unit/lease/LeaseService.spec.js +3 -1
- package/lib/test/unit/lease/allocate-lease-branch.spec.js +2 -2
- package/lib/test/unit/lease/buildCapabilityBag.spec.js +3 -2
- package/lib/test/unit/lease/lease-override.spec.js +1 -1
- package/lib/test/unit/lease/lease-session-ownership.spec.js +4 -4
- package/lib/test/unit/lease/lease-team-visibility.spec.js +1 -1
- package/lib/test/unit/lease/lease-token-log-filter.spec.js +1 -1
- package/lib/test/unit/lease/lease-token-oracle.spec.js +207 -0
- package/lib/test/unit/lease/session-lease-proof.spec.js +42 -26
- package/lib/test/unit/logcat-ws.spec.js +7 -4
- package/lib/test/unit/queue-requester.spec.js +211 -0
- package/lib/test/unit/queue-team-scope-routes.spec.js +209 -0
- package/lib/test/unit/queue-visibility.spec.js +179 -0
- package/lib/test/unit/recording-summary.spec.js +22 -0
- package/lib/test/unit/recording-timing.spec.js +27 -0
- package/lib/test/unit/recordings-library-routes.spec.js +25 -0
- package/lib/test/unit/session-app-resolution.spec.js +331 -0
- package/lib/test/unit/session-assets.spec.js +167 -0
- package/lib/test/unit/session-attribution-wiring.spec.js +134 -24
- package/lib/test/unit/session-credential-strip.spec.js +277 -0
- package/lib/test/unit/session-credentials.spec.js +149 -0
- package/lib/test/unit/session-identity.spec.js +2 -2
- package/lib/test/unit/session-visibility.spec.js +109 -0
- package/lib/test/unit/stored-session-capabilities.spec.js +54 -0
- package/lib/test/unit/verify-credential.spec.js +138 -0
- package/lib/test/unit/xenon-options.spec.js +95 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/prisma/migrations/20260928120000_app_team/migration.sql +26 -0
- package/prisma/schema.prisma +7 -0
- package/schema.json +1 -1
- package/lib/public/assets/api-keys-bjXg97iu.js +0 -1
- package/lib/public/assets/apps-CQEjt22I.js +0 -21
- package/lib/public/assets/apps-DJMD5JOy.css +0 -1
- package/lib/public/assets/device-explorer-DTH5oU54.css +0 -1
- package/lib/public/assets/teams-8Qo7HmUv.js +0 -1
|
@@ -49,7 +49,6 @@ exports.XENON_CAPABILITIES = void 0;
|
|
|
49
49
|
exports.androidCapabilities = androidCapabilities;
|
|
50
50
|
exports.iOSCapabilities = iOSCapabilities;
|
|
51
51
|
exports.extractTeamCap = extractTeamCap;
|
|
52
|
-
exports.extractAccessKeyCap = extractAccessKeyCap;
|
|
53
52
|
exports.extractAccessKeyTokenPair = extractAccessKeyTokenPair;
|
|
54
53
|
exports.extractSessionToken = extractSessionToken;
|
|
55
54
|
exports.getXenonCapabilities = getXenonCapabilities;
|
|
@@ -58,6 +57,7 @@ const lodash_1 = __importDefault(require("lodash"));
|
|
|
58
57
|
const typedi_1 = require("typedi");
|
|
59
58
|
const PortAllocator_1 = require("./services/PortAllocator");
|
|
60
59
|
const logger_1 = __importDefault(require("./logger"));
|
|
60
|
+
const xenonOptions_1 = require("./services/session/xenonOptions");
|
|
61
61
|
var XENON_CAPABILITIES;
|
|
62
62
|
(function (XENON_CAPABILITIES) {
|
|
63
63
|
XENON_CAPABILITIES["BUILD_NAME"] = "build";
|
|
@@ -68,7 +68,6 @@ var XENON_CAPABILITIES;
|
|
|
68
68
|
XENON_CAPABILITIES["LIVE_VIDEO"] = "live_video";
|
|
69
69
|
XENON_CAPABILITIES["SCREENSHOT_ON_FAILURE"] = "screenshot_on_failure";
|
|
70
70
|
XENON_CAPABILITIES["SCREENSHOT_ON_FAIL"] = "screenshotOnFailure";
|
|
71
|
-
XENON_CAPABILITIES["XENON_OPTIONS"] = "xenon:options";
|
|
72
71
|
XENON_CAPABILITIES["SAVE_DEVICE_LOGS"] = "saveDeviceLogs";
|
|
73
72
|
XENON_CAPABILITIES["SAVE_LOGS"] = "save_device_logs";
|
|
74
73
|
XENON_CAPABILITIES["SCREENSHOT_ON_EVERY_COMMAND"] = "screenshot_on_every_command";
|
|
@@ -221,58 +220,33 @@ function extractTeamCap(caps) {
|
|
|
221
220
|
}
|
|
222
221
|
return undefined;
|
|
223
222
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
const names = ['accessKey', 'access_key'];
|
|
229
|
-
for (const prefix of prefixes) {
|
|
230
|
-
for (const name of names) {
|
|
231
|
-
const v = merged[prefix ? `${prefix}${name}` : name];
|
|
232
|
-
if (typeof v === 'string' && v.length > 0)
|
|
233
|
-
return v;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
return undefined;
|
|
237
|
-
}
|
|
238
|
-
// Returns the (accessKey, token) pair from df:options.{accessKey,token} or
|
|
239
|
-
// equivalently df:options.{access_key,token}. Returns undefined if either
|
|
240
|
-
// piece is missing — callers fall back to extractAccessKeyCap (legacy).
|
|
223
|
+
// Returns the API-key pair from xe:options.{accessKey,token} (or the
|
|
224
|
+
// xenon:options alias; xe:options wins field by field). Returns undefined if
|
|
225
|
+
// either piece is missing. This is the only key-pair credential a session can
|
|
226
|
+
// present; there is no flat `xenon:accessKey`.
|
|
241
227
|
function extractAccessKeyTokenPair(caps) {
|
|
242
|
-
|
|
243
|
-
const
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
return undefined;
|
|
247
|
-
const accessKey = (_d = dfOptions.accessKey) !== null && _d !== void 0 ? _d : dfOptions.access_key;
|
|
248
|
-
const token = dfOptions.token;
|
|
249
|
-
if (typeof accessKey !== 'string' || typeof token !== 'string')
|
|
250
|
-
return undefined;
|
|
251
|
-
if (!accessKey || !token)
|
|
252
|
-
return undefined;
|
|
253
|
-
return { accessKey, token };
|
|
228
|
+
const options = (0, xenonOptions_1.xenonOptionsOf)(caps);
|
|
229
|
+
const accessKey = (0, xenonOptions_1.stringOption)(options, 'accessKey');
|
|
230
|
+
const token = (0, xenonOptions_1.stringOption)(options, 'token');
|
|
231
|
+
return accessKey && token ? { accessKey, token } : undefined;
|
|
254
232
|
}
|
|
255
|
-
// Returns the
|
|
256
|
-
// used by the opt-in session-token gate (spec
|
|
257
|
-
// null if absent — callers combine this with the
|
|
258
|
-
// pair to decide whether createSession is authorized.
|
|
233
|
+
// Returns the xe:options.sessionToken JWT (hub-minted, aud 'xenon-session'),
|
|
234
|
+
// or the xenon:options alias's, used by the opt-in session-token gate (spec
|
|
235
|
+
// §3 item 6, risk R9). Returns null if absent — callers combine this with the
|
|
236
|
+
// access-key/token pair to decide whether createSession is authorized.
|
|
259
237
|
function extractSessionToken(caps) {
|
|
260
238
|
var _a;
|
|
261
|
-
|
|
262
|
-
const xo = obj === null || obj === void 0 ? void 0 : obj['xenon:options'];
|
|
263
|
-
const t = xo && typeof xo === 'object' ? xo.sessionToken : undefined;
|
|
264
|
-
return typeof t === 'string' && t.length > 0 ? t : null;
|
|
265
|
-
};
|
|
266
|
-
const fm = Array.isArray(caps === null || caps === void 0 ? void 0 : caps.firstMatch) ? caps.firstMatch[0] : undefined;
|
|
267
|
-
return (_a = read(caps === null || caps === void 0 ? void 0 : caps.alwaysMatch)) !== null && _a !== void 0 ? _a : read(fm);
|
|
239
|
+
return (_a = (0, xenonOptions_1.stringOption)((0, xenonOptions_1.xenonOptionsOf)(caps), 'sessionToken')) !== null && _a !== void 0 ? _a : null;
|
|
268
240
|
}
|
|
269
241
|
function getXenonCapabilities(caps) {
|
|
270
242
|
var _a, _b;
|
|
271
243
|
const mergedCapabilites = Object.assign({}, (_b = (_a = caps.firstMatch) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : {}, caps.alwaysMatch);
|
|
244
|
+
const xenonOptions = (0, xenonOptions_1.xenonOptionsIn)(mergedCapabilites);
|
|
272
245
|
const getAnyCap = (snake, camel) => {
|
|
273
246
|
// Strict prefix resolution: xe:, appium:, no-prefix — snake_case + camelCase fallbacks.
|
|
274
|
-
// Also accept the namespaced `
|
|
275
|
-
// plugins document nested options and what most users
|
|
247
|
+
// Also accept the namespaced `xe:options.<name>` form (or its `xenon:options` alias),
|
|
248
|
+
// which is how other Appium plugins document nested options and what most users
|
|
249
|
+
// naturally try first.
|
|
276
250
|
const prefixes = ['xe:', 'appium:', ''];
|
|
277
251
|
const names = [snake, camel];
|
|
278
252
|
for (const prefix of prefixes) {
|
|
@@ -282,12 +256,9 @@ function getXenonCapabilities(caps) {
|
|
|
282
256
|
return mergedCapabilites[key];
|
|
283
257
|
}
|
|
284
258
|
}
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
if (xenonOptions[name] !== undefined)
|
|
289
|
-
return xenonOptions[name];
|
|
290
|
-
}
|
|
259
|
+
for (const name of names) {
|
|
260
|
+
if (xenonOptions[name] !== undefined)
|
|
261
|
+
return xenonOptions[name];
|
|
291
262
|
}
|
|
292
263
|
return undefined;
|
|
293
264
|
};
|
package/lib/src/app/index.js
CHANGED
|
@@ -55,8 +55,7 @@ const cors_1 = __importDefault(require("cors"));
|
|
|
55
55
|
const async_lock_1 = __importDefault(require("async-lock"));
|
|
56
56
|
const crypto_1 = __importDefault(require("crypto"));
|
|
57
57
|
const InternalHttpClient_1 = require("../InternalHttpClient");
|
|
58
|
-
const
|
|
59
|
-
const logger_1 = __importDefault(require("../logger"));
|
|
58
|
+
const logger_1 = __importStar(require("../logger"));
|
|
60
59
|
const sessionContext_1 = require("../logging/sessionContext");
|
|
61
60
|
const dashboard_1 = __importDefault(require("./routers/dashboard"));
|
|
62
61
|
const grid_1 = __importDefault(require("./routers/grid"));
|
|
@@ -67,7 +66,7 @@ const reservation_1 = __importDefault(require("./routers/reservation"));
|
|
|
67
66
|
const ports_1 = __importDefault(require("./routers/ports"));
|
|
68
67
|
const sdk_leases_1 = __importDefault(require("./routers/sdk-leases"));
|
|
69
68
|
const sdk_version_1 = __importDefault(require("./routers/sdk-version"));
|
|
70
|
-
const
|
|
69
|
+
const config_1 = __importDefault(require("./routers/config"));
|
|
71
70
|
const interceptor_1 = __importDefault(require("./routers/interceptor"));
|
|
72
71
|
const bug_report_1 = __importDefault(require("./routers/bug-report"));
|
|
73
72
|
const recordings_1 = __importDefault(require("./routers/recordings"));
|
|
@@ -81,6 +80,8 @@ const capabilities_1 = require("./routers/capabilities");
|
|
|
81
80
|
const projects_1 = require("./routers/projects");
|
|
82
81
|
const audit_1 = require("./routers/audit");
|
|
83
82
|
const authMiddleware_1 = require("../middleware/authMiddleware");
|
|
83
|
+
const roleGuard_1 = require("../middleware/roleGuard");
|
|
84
|
+
const scopeGuard_1 = require("../middleware/scopeGuard");
|
|
84
85
|
const rateLimitMiddleware_1 = require("../middleware/rateLimitMiddleware");
|
|
85
86
|
const csrfMiddleware_1 = require("../middleware/csrfMiddleware");
|
|
86
87
|
const swagger_1 = require("./swagger");
|
|
@@ -214,8 +215,10 @@ router.use((req, res, next) => {
|
|
|
214
215
|
});
|
|
215
216
|
staticFilesRouter.use(express_1.default.static(publicPath, { index: false }));
|
|
216
217
|
router.use('/api', apiRouter);
|
|
217
|
-
//
|
|
218
|
-
|
|
218
|
+
// Session files are served by GET /api/session/:sessionId/asset/:kind/:file,
|
|
219
|
+
// behind the login and the session's team check. They used to be an
|
|
220
|
+
// express.static mount here, outside the login, which also served every
|
|
221
|
+
// recording and interceptor capture stored under the same folder.
|
|
219
222
|
router.use(staticFilesRouter);
|
|
220
223
|
function createRouter(pluginArgs) {
|
|
221
224
|
// CSRF defense (runs before /health so even a hostile GET->POST confused-
|
|
@@ -253,9 +256,11 @@ function createRouter(pluginArgs) {
|
|
|
253
256
|
// MCP audit ingest: the 2b gateway ships batched tool-call audit JSONL here,
|
|
254
257
|
// authenticated as a service identity (roleGuard(MEMBER) + admin scope).
|
|
255
258
|
apiRouter.use('/audit', (0, audit_1.auditRouter)());
|
|
256
|
-
//
|
|
257
|
-
|
|
258
|
-
|
|
259
|
+
// The stored Appium arguments: a database URL with its password, AI and
|
|
260
|
+
// cloud provider keys, the hub URL. Admin-only, like /processes, and
|
|
261
|
+
// redacted the way the startup log redacts them.
|
|
262
|
+
apiRouter.get('/cliArgs', (0, roleGuard_1.roleGuard)('ADMIN'), (0, scopeGuard_1.scopeGuard)(['admin']), (_req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
263
|
+
res.json((0, logger_1.redactSecrets)(yield (0, pluginArgs_1.getCLIArgs)()));
|
|
259
264
|
}));
|
|
260
265
|
// Prometheus-style metrics — auth-gated to avoid operational recon.
|
|
261
266
|
apiRouter.get('/metrics', (_req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
@@ -269,7 +274,7 @@ function createRouter(pluginArgs) {
|
|
|
269
274
|
control_1.default.register(apiRouter);
|
|
270
275
|
apps_1.default.register(apiRouter);
|
|
271
276
|
webhook_1.default.register(apiRouter);
|
|
272
|
-
|
|
277
|
+
config_1.default.register(apiRouter, pluginArgs);
|
|
273
278
|
interceptor_1.default.register(apiRouter);
|
|
274
279
|
bug_report_1.default.register(apiRouter);
|
|
275
280
|
recordings_1.default.register(apiRouter);
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.routerStackOf = routerStackOf;
|
|
4
|
+
exports.insertBeforeRoutes = insertBeforeRoutes;
|
|
5
|
+
function isRouter(candidate) {
|
|
6
|
+
return !!candidate && Array.isArray(candidate.stack);
|
|
7
|
+
}
|
|
8
|
+
function findRouter(app) {
|
|
9
|
+
if (!app)
|
|
10
|
+
return undefined;
|
|
11
|
+
if (isRouter(app._router))
|
|
12
|
+
return { router: app._router, shape: 'express4' };
|
|
13
|
+
let router;
|
|
14
|
+
try {
|
|
15
|
+
// Express 4 defines `app.router` as a getter that throws a deprecation
|
|
16
|
+
// error. Express 5 defines it as the router itself.
|
|
17
|
+
router = app.router;
|
|
18
|
+
}
|
|
19
|
+
catch (_a) {
|
|
20
|
+
router = undefined;
|
|
21
|
+
}
|
|
22
|
+
if (isRouter(router))
|
|
23
|
+
return { router, shape: 'express5' };
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
/** The app's router stack, or undefined when neither Express shape has one. */
|
|
27
|
+
function routerStackOf(app) {
|
|
28
|
+
var _a;
|
|
29
|
+
return (_a = findRouter(app)) === null || _a === void 0 ? void 0 : _a.router.stack;
|
|
30
|
+
}
|
|
31
|
+
function insertBeforeRoutes(app, path, handler) {
|
|
32
|
+
const found = findRouter(app);
|
|
33
|
+
if (!found) {
|
|
34
|
+
return {
|
|
35
|
+
placed: false,
|
|
36
|
+
reason: 'no Express router found (neither app._router nor app.router has a stack)',
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const { stack } = found.router;
|
|
40
|
+
const lengthBefore = stack.length;
|
|
41
|
+
app.use(path, handler);
|
|
42
|
+
const added = stack.splice(lengthBefore);
|
|
43
|
+
if (added.length !== 1) {
|
|
44
|
+
// app.use() added its layer somewhere other than the stack we found, or
|
|
45
|
+
// added more than one. Put back whatever it added and refuse to guess.
|
|
46
|
+
stack.push(...added);
|
|
47
|
+
return {
|
|
48
|
+
placed: false,
|
|
49
|
+
shape: found.shape,
|
|
50
|
+
reason: `app.use() added ${added.length} layers to the router stack, expected 1`,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const firstRoute = stack.findIndex((layer) => !!layer.route);
|
|
54
|
+
const index = firstRoute === -1 ? stack.length : firstRoute;
|
|
55
|
+
stack.splice(index, 0, added[0]);
|
|
56
|
+
return { placed: true, shape: found.shape, index };
|
|
57
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.normalizeBasePath = normalizeBasePath;
|
|
7
|
+
exports.sessionCommandPath = sessionCommandPath;
|
|
8
|
+
exports.registerCommandAuth = registerCommandAuth;
|
|
9
|
+
const typedi_1 = require("typedi");
|
|
10
|
+
const logger_1 = __importDefault(require("../logger"));
|
|
11
|
+
const config_1 = require("../config");
|
|
12
|
+
const SessionOwnerResolver_1 = require("../services/device-access/SessionOwnerResolver");
|
|
13
|
+
const commandAuth_1 = require("../middleware/commandAuth");
|
|
14
|
+
const commandCaller_1 = require("../middleware/commandCaller");
|
|
15
|
+
const insertBeforeRoutes_1 = require("./insertBeforeRoutes");
|
|
16
|
+
/**
|
|
17
|
+
* Appium's base-path rule (base-driver `normalizeBasePath`): drop one trailing
|
|
18
|
+
* '/', and add a leading '/' unless the path is empty. `cliArgs.basePath`
|
|
19
|
+
* reaches a plugin as the operator wrote it, while Appium normalises its own
|
|
20
|
+
* copy before building routes, so the guard must normalise the same way or it
|
|
21
|
+
* would miss the routes it is meant to sit in front of.
|
|
22
|
+
*/
|
|
23
|
+
function normalizeBasePath(basePath) {
|
|
24
|
+
let normalized = typeof basePath === 'string' ? basePath : '';
|
|
25
|
+
normalized = normalized.replace(/\/$/, '');
|
|
26
|
+
if (normalized !== '' && !normalized.startsWith('/'))
|
|
27
|
+
normalized = `/${normalized}`;
|
|
28
|
+
return normalized;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Everything under `<basePath>/session/:sessionId`, every method. The create
|
|
32
|
+
* route (`POST <basePath>/session`) has no id segment and does not match.
|
|
33
|
+
*/
|
|
34
|
+
function sessionCommandPath(basePath) {
|
|
35
|
+
return `${normalizeBasePath(basePath)}/session/:sessionId`;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Install per-command auth (commandAuth.ts) ahead of Appium's routes. Called
|
|
39
|
+
* from ServerManager.registerRoutes, which Appium runs after adding them.
|
|
40
|
+
*
|
|
41
|
+
* The middleware is always installed and reads the setting per request, so a
|
|
42
|
+
* server with the setting off pays one env check per session command and does
|
|
43
|
+
* no lookups. If it cannot be placed ahead of the routes the error is logged,
|
|
44
|
+
* and with the setting on (and auth enabled) the server refuses to start:
|
|
45
|
+
* an operator who asked for the check must not get a server that silently
|
|
46
|
+
* serves session commands without it.
|
|
47
|
+
*/
|
|
48
|
+
function registerCommandAuth(app, cliArgs, overrides = {}) {
|
|
49
|
+
const deps = Object.assign({ enabled: () => (0, commandAuth_1.commandAuthEnabled)(), authDisabled: () => config_1.config.authDisabled === true, verifier: new commandCaller_1.CommandCallerVerifier(), ownerOf: (sessionId) => typedi_1.Container.get(SessionOwnerResolver_1.SessionOwnerResolver).ownerOf(sessionId), logger: logger_1.default.scope('CommandAuth') }, overrides);
|
|
50
|
+
const path = sessionCommandPath(cliArgs === null || cliArgs === void 0 ? void 0 : cliArgs.basePath);
|
|
51
|
+
const result = (0, insertBeforeRoutes_1.insertBeforeRoutes)(app, path, (0, commandAuth_1.createCommandAuthMiddleware)(deps));
|
|
52
|
+
const active = deps.enabled() && !deps.authDisabled();
|
|
53
|
+
if (!result.placed) {
|
|
54
|
+
deps.logger.error(`Per-command auth could not be placed ahead of Appium's routes for ${path}: ${result.reason}.`);
|
|
55
|
+
if (active) {
|
|
56
|
+
throw new Error('XENON_REQUIRE_COMMAND_AUTH is on, but the per-command check could not be placed ahead ' +
|
|
57
|
+
"of Appium's WebDriver routes, so it would never run. Refusing to start rather than " +
|
|
58
|
+
'serve session commands unchecked.');
|
|
59
|
+
}
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
deps.logger.info(active
|
|
63
|
+
? `Per-command auth is ON: every request under ${path} needs the session owner's ` +
|
|
64
|
+
'credentials or an override admin (XENON_REQUIRE_COMMAND_AUTH).'
|
|
65
|
+
: `Per-command auth is off for ${path} (XENON_REQUIRE_COMMAND_AUTH not set, or auth disabled).`);
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
@@ -20,15 +20,22 @@ const logger_1 = __importDefault(require("../../logger"));
|
|
|
20
20
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
21
21
|
const scopeGuard_1 = require("../../middleware/scopeGuard");
|
|
22
22
|
const roleGuard_1 = require("../../middleware/roleGuard");
|
|
23
|
+
const appVisibility_1 = require("../../services/device-access/appVisibility");
|
|
24
|
+
const prisma_1 = require("../../prisma");
|
|
23
25
|
const router = (0, express_1.Router)();
|
|
24
26
|
// MEMBER-tier baseline: device app visibility requires authenticated user
|
|
25
27
|
router.use((0, roleGuard_1.roleGuard)('MEMBER'));
|
|
26
28
|
// Uploading an APK/IPA or deleting one from the fleet requires devices scope.
|
|
27
29
|
// App listings stay readable to any authenticated key.
|
|
28
30
|
router.use((0, scopeGuard_1.mutationScopeGuard)(['devices']));
|
|
31
|
+
// Uploaded apps follow the device team rule on the app's team (canSeeApp):
|
|
32
|
+
// admins see every app, members shared apps and their teams'. An app the
|
|
33
|
+
// caller can't see answers every route exactly as an unknown id.
|
|
34
|
+
const UNKNOWN_APP = { error: 'App not found' };
|
|
29
35
|
router.get('/', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
36
|
+
var _a;
|
|
30
37
|
try {
|
|
31
|
-
const apps = yield app_service_1.APP_SERVICE.getApps();
|
|
38
|
+
const apps = yield app_service_1.APP_SERVICE.getApps((_a = req.auth) === null || _a === void 0 ? void 0 : _a.teamIds);
|
|
32
39
|
res.json(apps);
|
|
33
40
|
}
|
|
34
41
|
catch (err) {
|
|
@@ -57,15 +64,29 @@ router.get('/', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
57
64
|
* is not joined onto a path.
|
|
58
65
|
*/
|
|
59
66
|
exports.DOWNLOAD_OPTIONS = { dotfiles: 'allow' };
|
|
67
|
+
/**
|
|
68
|
+
* Whether this request may see the app. A download ticket (authMiddleware's
|
|
69
|
+
* app-ticket path) was team-checked when it was minted and is bound to one
|
|
70
|
+
* app, so it sees that app and nothing else; everyone else by the team rule.
|
|
71
|
+
*/
|
|
72
|
+
function canRequestSeeApp(req, app) {
|
|
73
|
+
var _a, _b;
|
|
74
|
+
if (!app)
|
|
75
|
+
return false;
|
|
76
|
+
if (((_a = req.auth) === null || _a === void 0 ? void 0 : _a.kind) === 'app-ticket')
|
|
77
|
+
return req.auth.appId === app.id;
|
|
78
|
+
return (0, appVisibility_1.canSeeApp)(app, (_b = req.auth) === null || _b === void 0 ? void 0 : _b.teamIds);
|
|
79
|
+
}
|
|
60
80
|
function downloadApp(req, res) {
|
|
61
81
|
return __awaiter(this, void 0, void 0, function* () {
|
|
62
82
|
try {
|
|
63
83
|
const app = yield app_service_1.APP_SERVICE.getAppById(req.params.id);
|
|
64
|
-
|
|
84
|
+
// The ticket path sends with DOWNLOAD_OPTIONS too: it is this same call.
|
|
85
|
+
if (app && canRequestSeeApp(req, app) && (yield fs_extra_1.default.exists(app.filepath))) {
|
|
65
86
|
res.download(app.filepath, app.filename, exports.DOWNLOAD_OPTIONS);
|
|
66
87
|
}
|
|
67
88
|
else {
|
|
68
|
-
res.status(404).json(
|
|
89
|
+
res.status(404).json(UNKNOWN_APP);
|
|
69
90
|
}
|
|
70
91
|
}
|
|
71
92
|
catch (err) {
|
|
@@ -75,7 +96,26 @@ function downloadApp(req, res) {
|
|
|
75
96
|
});
|
|
76
97
|
}
|
|
77
98
|
router.get('/:id/download', downloadApp);
|
|
99
|
+
/**
|
|
100
|
+
* The team an upload or a move names: a string id, or null for the shared
|
|
101
|
+
* pool. Undefined, null and '' all mean shared; anything else that is not a
|
|
102
|
+
* string is refused.
|
|
103
|
+
*/
|
|
104
|
+
function parseTeamId(value) {
|
|
105
|
+
if (value === undefined || value === null || value === '')
|
|
106
|
+
return { ok: true, teamId: null };
|
|
107
|
+
if (typeof value === 'string')
|
|
108
|
+
return { ok: true, teamId: value };
|
|
109
|
+
return { ok: false };
|
|
110
|
+
}
|
|
111
|
+
function teamExists(teamId) {
|
|
112
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
113
|
+
const team = yield prisma_1.prisma.team.findUnique({ where: { id: teamId }, select: { id: true } });
|
|
114
|
+
return !!team;
|
|
115
|
+
});
|
|
116
|
+
}
|
|
78
117
|
router.post('/upload', (0, roleGuard_1.roleGuard)('ADMIN'), (req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
118
|
+
var _a;
|
|
79
119
|
if (!req.files || Object.keys(req.files).length === 0) {
|
|
80
120
|
return res.status(400).json({ error: 'No files were uploaded.' });
|
|
81
121
|
}
|
|
@@ -83,9 +123,18 @@ router.post('/upload', (0, roleGuard_1.roleGuard)('ADMIN'), (req, res) => __awai
|
|
|
83
123
|
if (!appFile) {
|
|
84
124
|
return res.status(400).json({ error: 'Field "app" is required.' });
|
|
85
125
|
}
|
|
126
|
+
// Optional multipart field. Checked before anything is written, so a bad
|
|
127
|
+
// team stores nothing.
|
|
128
|
+
const team = parseTeamId((_a = req.body) === null || _a === void 0 ? void 0 : _a.teamId);
|
|
129
|
+
if (!team.ok)
|
|
130
|
+
return res.status(400).json({ error: 'teamId must be a string' });
|
|
86
131
|
try {
|
|
87
|
-
|
|
88
|
-
|
|
132
|
+
if (team.teamId && !(yield teamExists(team.teamId))) {
|
|
133
|
+
return res.status(400).json({ error: 'team not found' });
|
|
134
|
+
}
|
|
135
|
+
// An upload of bytes already stored returns that app, in its own team.
|
|
136
|
+
const app = yield app_service_1.APP_SERVICE.uploadApp(appFile, team.teamId);
|
|
137
|
+
logger_1.default.audit('APP_UPLOAD', req.ip, { appId: app.id, name: app.name, teamId: app.teamId });
|
|
89
138
|
res.json(app);
|
|
90
139
|
}
|
|
91
140
|
catch (err) {
|
|
@@ -93,8 +142,37 @@ router.post('/upload', (0, roleGuard_1.roleGuard)('ADMIN'), (req, res) => __awai
|
|
|
93
142
|
res.status(500).json({ error: err.message });
|
|
94
143
|
}
|
|
95
144
|
}));
|
|
145
|
+
/**
|
|
146
|
+
* Moves an app to a team, or back to the shared pool with `{ teamId: null }`.
|
|
147
|
+
* Shaped like PUT /grid/device/:udid/team: admin role and scope, 404 for an
|
|
148
|
+
* unknown team or app, `{ ok, updated }`.
|
|
149
|
+
*/
|
|
150
|
+
router.put('/:id/team', (0, roleGuard_1.roleGuard)('ADMIN'), (0, scopeGuard_1.scopeGuard)(['admin']), (req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
151
|
+
var _a;
|
|
152
|
+
const team = parseTeamId((_a = req.body) === null || _a === void 0 ? void 0 : _a.teamId);
|
|
153
|
+
if (!team.ok)
|
|
154
|
+
return res.status(400).json({ error: 'teamId must be a string or null' });
|
|
155
|
+
try {
|
|
156
|
+
if (team.teamId && !(yield teamExists(team.teamId))) {
|
|
157
|
+
return res.status(404).json({ error: 'team not found' });
|
|
158
|
+
}
|
|
159
|
+
const updated = yield app_service_1.APP_SERVICE.setAppTeam(req.params.id, team.teamId);
|
|
160
|
+
if (updated === 0)
|
|
161
|
+
return res.status(404).json(UNKNOWN_APP);
|
|
162
|
+
logger_1.default.audit('APP_TEAM', req.ip, { appId: req.params.id, teamId: team.teamId });
|
|
163
|
+
res.json({ ok: true, updated });
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
logger_1.default.error(`Failed to move app ${req.params.id}: ${err.message}`);
|
|
167
|
+
res.status(500).json({ error: err.message });
|
|
168
|
+
}
|
|
169
|
+
}));
|
|
96
170
|
router.delete('/:id', (0, roleGuard_1.roleGuard)('ADMIN'), (req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
97
171
|
try {
|
|
172
|
+
// Unknown and hidden alike: 404 with the unknown body, nothing deleted.
|
|
173
|
+
const app = yield app_service_1.APP_SERVICE.getAppById(req.params.id);
|
|
174
|
+
if (!canRequestSeeApp(req, app))
|
|
175
|
+
return res.status(404).json(UNKNOWN_APP);
|
|
98
176
|
yield app_service_1.APP_SERVICE.deleteApp(req.params.id);
|
|
99
177
|
logger_1.default.audit('APP_DELETE', req.ip, { appId: req.params.id });
|
|
100
178
|
res.sendStatus(204);
|
|
@@ -64,7 +64,7 @@ function issueToken(auth, body) {
|
|
|
64
64
|
roles: grant.roles,
|
|
65
65
|
}, { audience, ttlSeconds: expiresIn });
|
|
66
66
|
// Session-token capability (spec §3 item 6 / R9): a sibling credential the
|
|
67
|
-
// client injects as `
|
|
67
|
+
// client injects as `xe:options.sessionToken` so the Appium createSession
|
|
68
68
|
// interceptor can refuse tokenless direct-connect sessions when the gate is on.
|
|
69
69
|
const sessionToken = yield svc.sign({ sub: auth.userId, teamId: (_c = auth.teamId) !== null && _c !== void 0 ? _c : null }, { audience: 'xenon-session', ttlSeconds: expiresIn });
|
|
70
70
|
return { token, expiresIn, audience, scopes: grant.granular, sessionToken };
|
|
@@ -21,6 +21,8 @@ const roleGuard_1 = require("../../middleware/roleGuard");
|
|
|
21
21
|
const logger_1 = __importDefault(require("../../logger"));
|
|
22
22
|
const SocketServer_1 = require("../../services/SocketServer");
|
|
23
23
|
const SocketEvents_1 = require("../../enums/SocketEvents");
|
|
24
|
+
const prisma_1 = require("../../prisma");
|
|
25
|
+
const sessionVisibility_1 = require("../../services/device-access/sessionVisibility");
|
|
24
26
|
const router = (0, express_1.Router)();
|
|
25
27
|
router.use((0, roleGuard_1.roleGuard)('MEMBER'));
|
|
26
28
|
function parseMode(raw) {
|
|
@@ -54,6 +56,16 @@ router.post('/sessions/:sessionId/bug-report', (req, res) => __awaiter(void 0, v
|
|
|
54
56
|
}
|
|
55
57
|
windowSec = parsed;
|
|
56
58
|
}
|
|
59
|
+
// Another team's session gets the service's own unknown-session answer,
|
|
60
|
+
// and nothing is assembled for it.
|
|
61
|
+
const session = yield prisma_1.prisma.session.findUnique({
|
|
62
|
+
where: { id: sessionId },
|
|
63
|
+
select: { device_udid: true, user_id: true },
|
|
64
|
+
});
|
|
65
|
+
const auth = req.auth;
|
|
66
|
+
if (session && !(yield (0, sessionVisibility_1.canSeeSession)(session, auth))) {
|
|
67
|
+
return res.status(404).json({ error: `Session ${sessionId} not found` });
|
|
68
|
+
}
|
|
57
69
|
const svc = typedi_1.Container.get(BugReportService_1.BugReportService);
|
|
58
70
|
let bundle;
|
|
59
71
|
try {
|
|
@@ -4,6 +4,7 @@ exports.buildCapabilities = buildCapabilities;
|
|
|
4
4
|
exports.capabilitiesRouter = capabilitiesRouter;
|
|
5
5
|
const express_1 = require("express");
|
|
6
6
|
const sessionTokenGate_1 = require("../../services/sessionTokenGate");
|
|
7
|
+
const commandAuth_1 = require("../../middleware/commandAuth");
|
|
7
8
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
8
9
|
const pkg = require('../../../package.json');
|
|
9
10
|
function buildCapabilities() {
|
|
@@ -18,6 +19,9 @@ function buildCapabilities() {
|
|
|
18
19
|
projects: true, // Task 8
|
|
19
20
|
mcpScopedTokens: true, // Tasks 1-3: granular-claim minting available
|
|
20
21
|
sessionTokenGate: (0, sessionTokenGate_1.sessionTokenGateEnabled)(), // Task 4: live XENON_REQUIRE_SESSION_TOKEN value
|
|
22
|
+
// Live XENON_REQUIRE_COMMAND_AUTH value: when true, every WebDriver
|
|
23
|
+
// session command must carry the x-xenon-* pair or a Bearer token.
|
|
24
|
+
commandAuth: (0, commandAuth_1.commandAuthEnabled)(),
|
|
21
25
|
},
|
|
22
26
|
};
|
|
23
27
|
}
|
|
@@ -66,6 +66,7 @@ const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
|
66
66
|
const OmniVisionService_1 = require("../../services/omni-vision/OmniVisionService");
|
|
67
67
|
const InspectorService_1 = require("../../services/InspectorService");
|
|
68
68
|
const StreamTicketService_1 = require("../../services/token/StreamTicketService");
|
|
69
|
+
const appVisibility_1 = require("../../services/device-access/appVisibility");
|
|
69
70
|
const PluginContext_1 = require("../../PluginContext");
|
|
70
71
|
const streamType_1 = require("./streamType");
|
|
71
72
|
const iosStreamPort_1 = require("./iosStreamPort");
|
|
@@ -496,6 +497,7 @@ router.post('/:udid/install', (req, res) => __awaiter(void 0, void 0, void 0, fu
|
|
|
496
497
|
res.status(400).send('Manager not found or installApp not supported');
|
|
497
498
|
}));
|
|
498
499
|
router.post('/:udid/install-repository-app', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
500
|
+
var _a;
|
|
499
501
|
const { udid } = req.params;
|
|
500
502
|
const { appId } = req.body;
|
|
501
503
|
const device = yield getDeviceInfo(udid);
|
|
@@ -504,8 +506,11 @@ router.post('/:udid/install-repository-app', (req, res) => __awaiter(void 0, voi
|
|
|
504
506
|
try {
|
|
505
507
|
const { APP_SERVICE } = yield Promise.resolve().then(() => __importStar(require('../../dashboard/services/app-service')));
|
|
506
508
|
const app = yield APP_SERVICE.getAppById(appId);
|
|
507
|
-
|
|
509
|
+
// Installing hands over the binary as a download does: another team's
|
|
510
|
+
// app answers as an unknown one.
|
|
511
|
+
if (!(0, appVisibility_1.canSeeApp)(app, (_a = req.auth) === null || _a === void 0 ? void 0 : _a.teamIds)) {
|
|
508
512
|
return res.status(404).send('App not found in repository');
|
|
513
|
+
}
|
|
509
514
|
const manager = yield getDeviceManagerForPlatform(device.platform);
|
|
510
515
|
if (manager && manager.installApp) {
|
|
511
516
|
// If it's a local file, pass the path.
|