From 76a592e3ab64bc060ad8aad40f8ec2c8f14785c9 Mon Sep 17 00:00:00 2001 From: winewill Date: Fri, 18 Sep 2026 11:57:56 +0800 Subject: [PATCH] refactor(desktop): configure test policy login origins --- apps/desktop/.env.macos.example | 9 ++--- apps/desktop/.env.windows.example | 9 ++--- apps/desktop/README.i18n.yaml | 4 +-- apps/desktop/README.md | 5 +-- apps/desktop/README.zh.md | 5 +-- .../scripts/desktop-policy-environment.d.mts | 1 + .../scripts/desktop-policy-environment.mjs | 8 +++++ .../scripts/test-windows-installer.mjs | 4 ++- apps/desktop/src/main.ts | 3 +- apps/desktop/src/mandatory-update-policy.ts | 9 +++++ apps/desktop/src/policy-test-auth.ts | 9 +++-- .../tests/desktop-package-environment.spec.ts | 3 +- .../tests/desktop-policy-environment.spec.ts | 35 +++++++++++++------ .../tests/installed-update-builder.spec.ts | 1 + .../installed-update-package-content.spec.ts | 2 +- .../desktop/tests/installer-packaging.spec.ts | 3 +- apps/desktop/tests/macos-signature.spec.ts | 3 ++ apps/desktop/tests/main-startup.spec.ts | 14 ++++---- .../tests/mandatory-update-policy.spec.ts | 11 +++++- apps/desktop/tests/policy-test-auth.spec.ts | 12 +++---- .../desktop/tests/windows-asar-unpack.spec.ts | 1 + .../tests/windows-update-publisher.spec.ts | 1 + 22 files changed, 104 insertions(+), 48 deletions(-) diff --git a/apps/desktop/.env.macos.example b/apps/desktop/.env.macos.example index 53c3945f60..d7da15ac63 100644 --- a/apps/desktop/.env.macos.example +++ b/apps/desktop/.env.macos.example @@ -5,11 +5,12 @@ DSH_DESKTOP_AUTO_UPDATE_ENV=test DOWNLOAD_TEST_ORIGIN= # The selected mandatory-update origin is required, including unsigned and preparation builds. -DSH_DESKTOP_MANDATORY_UPDATE_TEST_ORIGIN=https://harness-test.deepseek.com -DSH_DESKTOP_MANDATORY_UPDATE_PROD_ORIGIN=https://harness.deepseek.com -# Optional polling and fallback-page options; origin and authentication are selected automatically. +DSH_DESKTOP_MANDATORY_UPDATE_TEST_ORIGIN= +DSH_DESKTOP_MANDATORY_UPDATE_PROD_ORIGIN= +# Set approved login origins for test builds; omit allowedAuthOrigins for production. +# Optional polling and fallback-page options belong in the same JSON value. # The fallback-page allowlist defaults to the selected service origin. -# DSH_DESKTOP_MANDATORY_UPDATE_CONFIG='{"intervalMs":600000}' +DSH_DESKTOP_MANDATORY_UPDATE_CONFIG='{"allowedAuthOrigins":["https://login.example.com"]}' DSH_DESKTOP_MACOS_SIGNING_IDENTITY= DSH_DESKTOP_MACOS_TEAM_ID= diff --git a/apps/desktop/.env.windows.example b/apps/desktop/.env.windows.example index e162c805d0..a09951f402 100644 --- a/apps/desktop/.env.windows.example +++ b/apps/desktop/.env.windows.example @@ -5,11 +5,12 @@ DSH_DESKTOP_AUTO_UPDATE_ENV=test DOWNLOAD_TEST_ORIGIN= # The selected mandatory-update origin is required, including unsigned and preparation builds. -DSH_DESKTOP_MANDATORY_UPDATE_TEST_ORIGIN=https://harness-test.deepseek.com -DSH_DESKTOP_MANDATORY_UPDATE_PROD_ORIGIN=https://harness.deepseek.com -# Optional polling and fallback-page options; origin and authentication are selected automatically. +DSH_DESKTOP_MANDATORY_UPDATE_TEST_ORIGIN= +DSH_DESKTOP_MANDATORY_UPDATE_PROD_ORIGIN= +# Set approved login origins for test builds; omit allowedAuthOrigins for production. +# Optional polling and fallback-page options belong in the same JSON value. # The fallback-page allowlist defaults to the selected service origin. -# DSH_DESKTOP_MANDATORY_UPDATE_CONFIG='{"intervalMs":600000}' +DSH_DESKTOP_MANDATORY_UPDATE_CONFIG='{"allowedAuthOrigins":["https://login.example.com"]}' DSH_DESKTOP_WINDOWS_CER_FILE= DSH_DESKTOP_WINDOWS_SIGNTOOL= diff --git a/apps/desktop/README.i18n.yaml b/apps/desktop/README.i18n.yaml index 909bef683c..e72f9bca8d 100644 --- a/apps/desktop/README.i18n.yaml +++ b/apps/desktop/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write apps/desktop/README.md -README.md: c2a1f4df103d5fe74f4a25aa1bcfc1f8e38d3b1e -README.zh.md: 9dda03b4b40a7b46bf5e0f1404f77d73d397bfa4 +README.md: bfe8f68bfbe43de4545255e8005a9ee33ab5f4b7 +README.zh.md: 163a0f070ba9c396d256633fc18afc566ea7a997 diff --git a/apps/desktop/README.md b/apps/desktop/README.md index c2a1f4df10..bfe8f68bfb 100644 --- a/apps/desktop/README.md +++ b/apps/desktop/README.md @@ -275,15 +275,16 @@ Confirmed Host exit without successful task teardown displays localized recovery ### Mandatory update policy -The [mandatory client decision](../../.agents/notes/implemented/feature/2026-09-11-desktop-mandatory-update-client.md) owns policy polling and the blocking window. Packaging reads `.env.windows` or `.env.macos`: `DSH_DESKTOP_AUTO_UPDATE_ENV=test` (the default) selects `DSH_DESKTOP_MANDATORY_UPDATE_TEST_ORIGIN`; `production` selects `DSH_DESKTOP_MANDATORY_UPDATE_PROD_ORIGIN`. Templates use `https://harness-test.deepseek.com` and `https://harness.deepseek.com`, respectively. The selected origin is required before preparation or signing, including unsigned and preparation-only builds; the unselected origin is optional. These settings never fall back to the parent environment or the other deployment. Packaging embeds the selected policy with the application ID; packaged applications ignore runtime overrides. +The [mandatory client decision](../../.agents/notes/implemented/feature/2026-09-11-desktop-mandatory-update-client.md) owns policy polling and the blocking window. Packaging reads `.env.windows` or `.env.macos`: `DSH_DESKTOP_AUTO_UPDATE_ENV=test` (the default) selects `DSH_DESKTOP_MANDATORY_UPDATE_TEST_ORIGIN`; `production` selects `DSH_DESKTOP_MANDATORY_UPDATE_PROD_ORIGIN`. The templates leave both origins blank; fill in the selected deployment origin in the ignored local dotenv file. The selected origin is required before preparation or signing, including unsigned and preparation-only builds; the unselected origin is optional. These settings never fall back to the parent environment or the other deployment. Packaging embeds the selected policy with the application ID; packaged applications ignore runtime overrides. -Optional `DSH_DESKTOP_MANDATORY_UPDATE_CONFIG` JSON supplies polling and download-page options; packaging rejects `origin` and `authentication` inside it. The page allowlist defaults to the selected service origin; explicitly allow other approved download-page origins when needed. Test builds select `feishu-test`, and production selects `anonymous`. Policy requests reject redirects; only test authentication carries gateway cookies. Unpackaged development instead reads a complete policy JSON from this variable and requires `DSH_DESKTOP_APP_ID`; absent JSON disables development policy queries, and only anonymous development permits HTTP `127.0.0.1`. A user-initiated ordinary check triggers policy work concurrently but never waits for or reports a policy failure. Only a confirmed blocking decision cancels ordinary dialogs. Test authentication waits until the active ordinary dialog finishes, and cancellation or failure does not discard the updater result. +`DSH_DESKTOP_MANDATORY_UPDATE_CONFIG` JSON supplies test login origins and optional polling and download-page options; packaging rejects `origin` and `authentication` inside it. The page allowlist defaults to the selected service origin; explicitly allow other approved download-page origins when needed. Test builds select `feishu-test` and require `allowedAuthOrigins` in `DSH_DESKTOP_MANDATORY_UPDATE_CONFIG`; production selects `anonymous` and rejects that field. Each login origin must be an HTTPS origin without credentials, path, query, or fragment. The login window permits document navigation only to the selected policy origin and these configured origins. Policy requests reject redirects; only test authentication carries gateway cookies. Unpackaged development instead reads a complete policy JSON from this variable and requires `DSH_DESKTOP_APP_ID`; absent JSON disables development policy queries, and only anonymous development permits HTTP `127.0.0.1`. A user-initiated ordinary check triggers policy work concurrently but never waits for or reports a policy failure. Only a confirmed blocking decision cancels ordinary dialogs. Test authentication waits until the active ordinary dialog finishes, and cancellation or failure does not discard the updater result. | Resolved policy field | Meaning and default | |---|---| | `origin` | Required HTTPS API origin, without credentials, path, query, or fragment; requests use `/api/v0/check_client_update` | | `allowedPageOrigins` | Nonempty array of exact HTTPS origins; packaging defaults to the selected API origin; subdomains and alternate ports are not implied | | `authentication` | Packaging selects `feishu-test` for test and `anonymous` for production; unpackaged development defaults to `anonymous` | +| `allowedAuthOrigins` | Nonempty array of exact HTTPS login document origins for test authentication; forbidden for production | | `intervalMs` | Polling interval; default `600000` | | `timeoutMs` | Request deadline; default `15000` | | `maxBackoffMs` | Maximum failed-request interval including jitter; default `3600000`, at least `intervalMs` | diff --git a/apps/desktop/README.zh.md b/apps/desktop/README.zh.md index 9dda03b4b4..163a0f070b 100644 --- a/apps/desktop/README.zh.md +++ b/apps/desktop/README.zh.md @@ -276,15 +276,16 @@ macOS 打包在组装 App 时、代码签名前写入 `Contents/Resources/app-up ### 强制更新策略 -[强更客户端决策](../../.agents/notes/implemented/feature/2026-09-11-desktop-mandatory-update-client.zh.md)负责策略查询和阻塞窗口。打包读取 `.env.windows` 或 `.env.macos`:`DSH_DESKTOP_AUTO_UPDATE_ENV=test`(默认值)选择 `DSH_DESKTOP_MANDATORY_UPDATE_TEST_ORIGIN`;`production` 选择 `DSH_DESKTOP_MANDATORY_UPDATE_PROD_ORIGIN`。模板分别使用 `https://harness-test.deepseek.com` 和 `https://harness.deepseek.com`。在准备产物或签名前,所选源站必须配置,包括未签名和仅准备构建;未选环境的源站可不填。这些配置不会回退到父进程环境或另一部署环境。打包将选定策略与应用 ID 写入元数据;打包应用忽略运行时覆盖。 +[强更客户端决策](../../.agents/notes/implemented/feature/2026-09-11-desktop-mandatory-update-client.zh.md)负责策略查询和阻塞窗口。打包读取 `.env.windows` 或 `.env.macos`:`DSH_DESKTOP_AUTO_UPDATE_ENV=test`(默认值)选择 `DSH_DESKTOP_MANDATORY_UPDATE_TEST_ORIGIN`;`production` 选择 `DSH_DESKTOP_MANDATORY_UPDATE_PROD_ORIGIN`。模板将两个源站留空;在 Git 忽略的目标 dotenv 文件中填写所选部署的源站。在准备产物或签名前,所选源站必须配置,包括未签名和仅准备构建;未选环境的源站可不填。这些配置不会回退到父进程环境或另一部署环境。打包将选定策略与应用 ID 写入元数据;打包应用忽略运行时覆盖。 -可选的 `DSH_DESKTOP_MANDATORY_UPDATE_CONFIG` JSON 提供轮询和下载页面选项;打包拒绝其中的 `origin` 和 `authentication`。页面白名单默认只包含所选服务源站;需要其他已批准下载页面源站时应显式配置。测试包选择 `feishu-test`,正式包选择 `anonymous`。策略请求拒绝重定向;仅测试鉴权携带网关 Cookie。未打包开发模式则从此变量读取完整策略 JSON,并要求 `DSH_DESKTOP_APP_ID`;缺少 JSON 会禁用开发模式策略查询,仅匿名开发允许 HTTP `127.0.0.1`。用户发起常规检查时会并发触发策略检查,但不会等待或展示策略失败。只有已确认的强更决定可以关闭常规弹窗。测试环境鉴权会等待当前常规弹窗结束,取消或失败不会丢弃 updater 结果。 +`DSH_DESKTOP_MANDATORY_UPDATE_CONFIG` JSON 提供测试登录源站,以及可选的轮询和下载页面选项;打包拒绝其中的 `origin` 和 `authentication`。页面白名单默认只包含所选服务源站;需要其他已批准下载页面源站时应显式配置。测试包选择 `feishu-test`,且必须在 `DSH_DESKTOP_MANDATORY_UPDATE_CONFIG` 中配置 `allowedAuthOrigins`;正式包选择 `anonymous`,并拒绝该字段。每个登录源站必须是没有凭据、路径、查询或片段的 HTTPS origin。登录窗口仅允许文档导航到所选策略源站和这些已配置源站。策略请求拒绝重定向;仅测试鉴权携带网关 Cookie。未打包开发模式则从此变量读取完整策略 JSON,并要求 `DSH_DESKTOP_APP_ID`;缺少 JSON 会禁用开发模式策略查询,仅匿名开发允许 HTTP `127.0.0.1`。用户发起常规检查时会并发触发策略检查,但不会等待或展示策略失败。只有已确认的强更决定可以关闭常规弹窗。测试环境鉴权会等待当前常规弹窗结束,取消或失败不会丢弃 updater 结果。 | 解析后的策略字段 | 含义与默认值 | |---|---| | `origin` | 必填 HTTPS API 源站,不含凭据、路径、查询或片段;请求使用 `/api/v0/check_client_update` | | `allowedPageOrigins` | 非空的精确 HTTPS 源站数组;打包时默认只包含所选 API 源站;不隐含子域名或其他端口 | | `authentication` | 打包时测试环境选择 `feishu-test`,正式环境选择 `anonymous`;未打包开发模式默认为 `anonymous` | +| `allowedAuthOrigins` | 测试鉴权必填的非空 HTTPS 登录文档源站数组;正式环境禁止配置 | | `intervalMs` | 轮询间隔;默认 `600000` | | `timeoutMs` | 请求截止时间;默认 `15000` | | `maxBackoffMs` | 含抖动的失败请求最大间隔;默认 `3600000`,不小于 `intervalMs` | diff --git a/apps/desktop/scripts/desktop-policy-environment.d.mts b/apps/desktop/scripts/desktop-policy-environment.d.mts index a9cd2995fe..7b2230a277 100644 --- a/apps/desktop/scripts/desktop-policy-environment.d.mts +++ b/apps/desktop/scripts/desktop-policy-environment.d.mts @@ -2,6 +2,7 @@ export interface DesktopPolicyEnvironment { origin: string allowedPageOrigins: string[] + allowedAuthOrigins?: string[] authentication: 'anonymous' | 'feishu-test' [key: string]: unknown } diff --git a/apps/desktop/scripts/desktop-policy-environment.mjs b/apps/desktop/scripts/desktop-policy-environment.mjs index a582e37772..591147578b 100644 --- a/apps/desktop/scripts/desktop-policy-environment.mjs +++ b/apps/desktop/scripts/desktop-policy-environment.mjs @@ -30,7 +30,15 @@ export function resolveDesktopPolicyEnvironment(environment) { } const pages = settings.allowedPageOrigins ?? [selected] if (!Array.isArray(pages) || pages.length === 0) throw new Error('desktop package: allowedPageOrigins must be a nonempty array') + const authOrigins = settings.allowedAuthOrigins + if (deployment === 'test' && (!Array.isArray(authOrigins) || authOrigins.length === 0)) { + throw new Error('desktop package: test policy requires nonempty allowedAuthOrigins') + } + if (deployment === 'production' && authOrigins !== undefined) { + throw new Error('desktop package: production policy must not configure allowedAuthOrigins') + } return { ...settings, origin: selected, allowedPageOrigins: pages.map(value => origin(value, 'allowedPageOrigins')), + ...(deployment === 'test' ? { allowedAuthOrigins: authOrigins.map(value => origin(value, 'allowedAuthOrigins')) } : {}), authentication: deployment === 'test' ? 'feishu-test' : 'anonymous' } } diff --git a/apps/desktop/scripts/test-windows-installer.mjs b/apps/desktop/scripts/test-windows-installer.mjs index 95e4db8b70..a92df3844e 100644 --- a/apps/desktop/scripts/test-windows-installer.mjs +++ b/apps/desktop/scripts/test-windows-installer.mjs @@ -44,7 +44,9 @@ try { DSH_DESKTOP_TARGET_PLATFORM: 'win32', DSH_DESKTOP_TARGET_ARCH: 'x64', DSH_DESKTOP_UNSIGNED: '1', CSC_IDENTITY_AUTO_DISCOVERY: 'false', ELECTRON_BUILDER_7Z_FILTER: 'BCJ', DSH_DESKTOP_MANDATORY_UPDATE_TEST_ORIGIN: signingEnvironment.DSH_DESKTOP_MANDATORY_UPDATE_TEST_ORIGIN - ?? 'https://harness-test.deepseek.com', + ?? 'https://test.example.com', + DSH_DESKTOP_MANDATORY_UPDATE_CONFIG: signingEnvironment.DSH_DESKTOP_MANDATORY_UPDATE_CONFIG + ?? JSON.stringify({ allowedAuthOrigins: ['https://login.example.com'] }), ...signingRun ? { DSH_DESKTOP_PACKAGING_RUN_DIR: signingRun.directory } : {}, }) const { createElectronBuilderConfig } = await import('../electron-builder.config.mjs') diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index 5c21833b0e..8c21233fe0 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -728,7 +728,8 @@ async function main(): Promise { const policyConfig = resolveDesktopPolicyConfig(policyInput, !app.isPackaged) if (policyConfig !== undefined) { if (policyConfig.authentication === 'feishu-test') { - policyAuth = new DesktopPolicyTestAuth(policyConfig.origin, locale, () => mandatoryUI?.confirmationWindow ?? mainWindow, + policyAuth = new DesktopPolicyTestAuth(policyConfig.origin, policyConfig.allowedAuthOrigins, locale, + () => mandatoryUI?.confirmationWindow ?? mainWindow, (event) => { console.info(`desktop policy authentication: ${event}`); updateJournal?.action(`policy-login-${event}`) }) } const bundleId = app.isPackaged diff --git a/apps/desktop/src/mandatory-update-policy.ts b/apps/desktop/src/mandatory-update-policy.ts index 1ccc068969..584a52a966 100644 --- a/apps/desktop/src/mandatory-update-policy.ts +++ b/apps/desktop/src/mandatory-update-policy.ts @@ -16,6 +16,7 @@ export interface DesktopPolicyIdentity { export interface DesktopPolicyConfig { readonly origin: string readonly allowedPageOrigins: readonly string[] + readonly allowedAuthOrigins: readonly string[] readonly intervalMs: number readonly timeoutMs: number readonly maxBackoffMs: number @@ -74,12 +75,20 @@ export function resolveDesktopPolicyConfig(input: unknown, allowLoopback = false if (authentication !== 'anonymous' && authentication !== 'feishu-test') { throw new Error('desktop policy: authentication must be anonymous or feishu-test') } + const authOrigins = value.allowedAuthOrigins + if (authentication === 'feishu-test' && (!Array.isArray(authOrigins) || authOrigins.length === 0)) { + throw new Error('desktop policy: test authentication requires nonempty allowedAuthOrigins') + } + if (authentication === 'anonymous' && authOrigins !== undefined) { + throw new Error('desktop policy: anonymous policy must not configure allowedAuthOrigins') + } if (typeof jitter !== 'number' || !Number.isFinite(jitter) || jitter < 0 || jitter > 1 || maxBackoffMs < intervalMs) { throw new Error('desktop policy: jitter must be in [0, 1] and maxBackoffMs must cover intervalMs') } return { origin: origin(value.origin, authentication === 'anonymous' && allowLoopback), allowedPageOrigins: value.allowedPageOrigins.map(item => origin(item, false)), + allowedAuthOrigins: authentication === 'feishu-test' ? (authOrigins as unknown[]).map(item => origin(item, false)) : [], intervalMs, timeoutMs: duration('timeoutMs', 15_000), maxBackoffMs, jitter, authentication, } } diff --git a/apps/desktop/src/policy-test-auth.ts b/apps/desktop/src/policy-test-auth.ts index b7d5d65208..e7597a5e57 100644 --- a/apps/desktop/src/policy-test-auth.ts +++ b/apps/desktop/src/policy-test-auth.ts @@ -3,9 +3,6 @@ import { randomUUID } from 'node:crypto' import { BrowserWindow, session } from 'electron' import type { DesktopLocale } from './locale.ts' -const FEISHU_ORIGINS = ['https://open.feishu.cn', 'https://accounts.feishu.cn', - 'https://passport.feishu.cn', 'https://login.feishu.cn'] - /** Packaged placeholder document; it is the window's first document and needs no network. */ const LOGIN_LOADING_PAGE = 'renderer/policy-login-loading.html' @@ -28,11 +25,13 @@ export class DesktopPolicyTestAuth { /** * @param origin Validated HTTPS policy origin; login always starts at its root with fresh gateway state. + * @param allowedAuthOrigins Validated HTTPS origins for login document navigation. * @param locale Shell-owned login title. * @param parent Current application or mandatory-update window. * @param record Fixed, nonsecret login outcomes for diagnostic evidence. */ - constructor(private readonly origin: string, private readonly locale: DesktopLocale, + constructor(private readonly origin: string, private readonly allowedAuthOrigins: readonly string[], + private readonly locale: DesktopLocale, private readonly parent: () => BrowserWindow | undefined, private readonly record: (event: 'opened' | DesktopPolicyLoginResult) => void) { this.browserSession.setPermissionRequestHandler((_contents, _permission, callback) => { callback(false) }) @@ -144,7 +143,7 @@ export class DesktopPolicyTestAuth { let url: URL try { url = new URL(value) } catch { return false } return url.protocol === 'https:' && url.username === '' && url.password === '' - && (url.origin === this.origin || FEISHU_ORIGINS.includes(url.origin)) + && (url.origin === this.origin || this.allowedAuthOrigins.includes(url.origin)) } /** diff --git a/apps/desktop/tests/desktop-package-environment.spec.ts b/apps/desktop/tests/desktop-package-environment.spec.ts index 50d9726550..fb011401aa 100644 --- a/apps/desktop/tests/desktop-package-environment.spec.ts +++ b/apps/desktop/tests/desktop-package-environment.spec.ts @@ -6,7 +6,8 @@ import { loadDesktopPackageEnvironment, validateDesktopPackageEnvironment } from const WINDOWS = { platform: 'win32', arch: 'x64' } as const const MACOS = { platform: 'darwin', arch: 'arm64' } as const -const POLICY = { DSH_DESKTOP_MANDATORY_UPDATE_TEST_ORIGIN: 'https://policy.example.com' } +const POLICY = { DSH_DESKTOP_MANDATORY_UPDATE_TEST_ORIGIN: 'https://policy.example.com', + DSH_DESKTOP_MANDATORY_UPDATE_CONFIG: JSON.stringify({ allowedAuthOrigins: ['https://login.example.com'] }) } const RELEASE = { ...POLICY, DSH_DESKTOP_APP_ID: 'com.example.desktop', DOWNLOAD_TEST_ORIGIN: 'https://updates.example.com' } const MAC_IDENTITY = { DSH_DESKTOP_MACOS_SIGNING_IDENTITY: 'Example Company (TEAMID1234)', DSH_DESKTOP_MACOS_TEAM_ID: 'TEAMID1234' } diff --git a/apps/desktop/tests/desktop-policy-environment.spec.ts b/apps/desktop/tests/desktop-policy-environment.spec.ts index 8316b833fc..e4d893eca0 100644 --- a/apps/desktop/tests/desktop-policy-environment.spec.ts +++ b/apps/desktop/tests/desktop-policy-environment.spec.ts @@ -3,28 +3,32 @@ import { resolveDesktopPolicyEnvironment } from '../scripts/desktop-policy-envir import { validateDesktopPackageEnvironment } from '../scripts/desktop-package-environment.mjs' import { resolveDesktopPolicyConfig } from '../src/mandatory-update-policy.ts' -const origins = { DSH_DESKTOP_MANDATORY_UPDATE_TEST_ORIGIN: 'https://harness-test.deepseek.com', - DSH_DESKTOP_MANDATORY_UPDATE_PROD_ORIGIN: 'https://harness.deepseek.com' } +const origins = { DSH_DESKTOP_MANDATORY_UPDATE_TEST_ORIGIN: 'https://test.example.com', + DSH_DESKTOP_MANDATORY_UPDATE_PROD_ORIGIN: 'https://prod.example.com' } +const auth = { DSH_DESKTOP_MANDATORY_UPDATE_CONFIG: JSON.stringify({ allowedAuthOrigins: ['https://login.example.com'] }) } it.each(['test', 'production'] as const)('selects the %s policy and authentication together', (deployment) => { - const policy = resolveDesktopPolicyEnvironment({ ...origins, DSH_DESKTOP_AUTO_UPDATE_ENV: deployment }) + const policy = resolveDesktopPolicyEnvironment({ ...origins, ...(deployment === 'test' ? auth : {}), DSH_DESKTOP_AUTO_UPDATE_ENV: deployment }) const origin = deployment === 'test' ? origins.DSH_DESKTOP_MANDATORY_UPDATE_TEST_ORIGIN : origins.DSH_DESKTOP_MANDATORY_UPDATE_PROD_ORIGIN - expect(policy).toEqual({ origin, allowedPageOrigins: [origin], authentication: deployment === 'test' ? 'feishu-test' : 'anonymous' }) + expect(policy).toEqual({ origin, allowedPageOrigins: [origin], + ...(deployment === 'test' ? { allowedAuthOrigins: ['https://login.example.com'] } : {}), + authentication: deployment === 'test' ? 'feishu-test' : 'anonymous' }) expect(resolveDesktopPolicyConfig(policy)).toMatchObject(policy) }) it('requires only the selected origin, defaults to test, and accepts explicit page restrictions', () => { const policy = resolveDesktopPolicyEnvironment({ DSH_DESKTOP_MANDATORY_UPDATE_TEST_ORIGIN: origins.DSH_DESKTOP_MANDATORY_UPDATE_TEST_ORIGIN, - DSH_DESKTOP_MANDATORY_UPDATE_CONFIG: JSON.stringify({ allowedPageOrigins: ['https://download.deepseek.com'], intervalMs: 5000 }) }) - expect(policy).toMatchObject({ authentication: 'feishu-test', intervalMs: 5000, allowedPageOrigins: ['https://download.deepseek.com'] }) + DSH_DESKTOP_MANDATORY_UPDATE_CONFIG: JSON.stringify({ allowedAuthOrigins: ['https://login.example.com'], + allowedPageOrigins: ['https://download.example.com'], intervalMs: 5000 }) }) + expect(policy).toMatchObject({ authentication: 'feishu-test', intervalMs: 5000, allowedPageOrigins: ['https://download.example.com'] }) expect(() => resolveDesktopPolicyEnvironment({ ...origins, DSH_DESKTOP_AUTO_UPDATE_ENV: 'prod' })).toThrow('production') }) -it.each([undefined, '', 'http://harness-test.deepseek.com', 'https://user:secret@harness-test.deepseek.com', - 'https://harness-test.deepseek.com/api', 'https://harness-test.deepseek.com/?secret=value'])('rejects invalid selected origin %s', (origin) => { - expect(() => resolveDesktopPolicyEnvironment({ DSH_DESKTOP_MANDATORY_UPDATE_TEST_ORIGIN: origin })).toThrow('HTTPS origin') - expect(() => resolveDesktopPolicyEnvironment({ DSH_DESKTOP_MANDATORY_UPDATE_TEST_ORIGIN: origin })).not.toThrow('secret=value') +it.each([undefined, '', 'http://test.example.com', 'https://user:secret@test.example.com', + 'https://test.example.com/api', 'https://test.example.com/?secret=value'])('rejects invalid selected origin %s', (origin) => { + expect(() => resolveDesktopPolicyEnvironment({ ...auth, DSH_DESKTOP_MANDATORY_UPDATE_TEST_ORIGIN: origin })).toThrow('HTTPS origin') + expect(() => resolveDesktopPolicyEnvironment({ ...auth, DSH_DESKTOP_MANDATORY_UPDATE_TEST_ORIGIN: origin })).not.toThrow('secret=value') }) it.each(['{', 'null', '[]', '{"origin":"https://old.example.com"}', '{"authentication":"anonymous"}', @@ -32,6 +36,17 @@ it.each(['{', 'null', '[]', '{"origin":"https://old.example.com"}', '{"authentic expect(() => resolveDesktopPolicyEnvironment({ ...origins, DSH_DESKTOP_MANDATORY_UPDATE_CONFIG: options })).toThrow() }) +it.each([undefined, '[]', '["http://login.example.com"]', '["https://login.example.com/path"]', + '["https://user:secret@login.example.com"]'])('rejects missing or invalid test login origins %s', (value) => { + const settings = value === undefined ? {} : { allowedAuthOrigins: JSON.parse(value) as unknown } + expect(() => resolveDesktopPolicyEnvironment({ ...origins, DSH_DESKTOP_MANDATORY_UPDATE_CONFIG: JSON.stringify(settings) })).toThrow() +}) + +it('rejects login origins in production', () => { + expect(() => resolveDesktopPolicyEnvironment({ ...origins, ...auth, DSH_DESKTOP_AUTO_UPDATE_ENV: 'production' })) + .toThrow('must not configure allowedAuthOrigins') +}) + it.each([{ unsigned: true }, { prepareOnly: true }, {}])('fails before signing/preparation when policy is absent in %j', (options) => { for (const platform of ['win32', 'darwin'] as const) { expect(() => { validateDesktopPackageEnvironment({ DSH_DESKTOP_APP_ID: 'com.example.test' }, { platform, arch: 'x64' }, options) }) diff --git a/apps/desktop/tests/installed-update-builder.spec.ts b/apps/desktop/tests/installed-update-builder.spec.ts index 781a68687e..55ad22fd5d 100644 --- a/apps/desktop/tests/installed-update-builder.spec.ts +++ b/apps/desktop/tests/installed-update-builder.spec.ts @@ -20,6 +20,7 @@ vi.mock('../scripts/windows-sign.mjs', () => ({ const versions = ['0.1.6-nightly.20260914.1', '0.1.6-nightly.20260914.2'] as const const environment = { DSH_DESKTOP_AUTO_UPDATE_ENV: 'test', DOWNLOAD_TEST_ORIGIN: 'https://download-test.deepseek.com', DSH_DESKTOP_MANDATORY_UPDATE_TEST_ORIGIN: 'https://policy.example.com', + DSH_DESKTOP_MANDATORY_UPDATE_CONFIG: JSON.stringify({ allowedAuthOrigins: ['https://login.example.com'] }), DOWNLOAD_TEST_COS_BUCKET: 'bj-toc-download-test-1320056602' } const require = createRequire(import.meta.url) const { validateConfiguration } = require('app-builder-lib/out/util/config/config.js') as { diff --git a/apps/desktop/tests/installed-update-package-content.spec.ts b/apps/desktop/tests/installed-update-package-content.spec.ts index ddc53dbe50..8ecfeb9f39 100644 --- a/apps/desktop/tests/installed-update-package-content.spec.ts +++ b/apps/desktop/tests/installed-update-package-content.spec.ts @@ -61,7 +61,7 @@ async function fixture(body: (context: { await writeFile(join(source, 'package.json'), JSON.stringify({ name: `dsh-update-test-${run.id}`, version, dshDesktopAppId: run.appId, main: 'qualification-bootstrap.mjs', type: 'module', dshMandatoryUpdatePolicy: { origin: 'https://policy.example.com', allowedPageOrigins: ['https://policy.example.com'], - authentication: 'feishu-test' } })) + authentication: 'feishu-test', allowedAuthOrigins: ['https://login.example.com'] } })) for (const name of ['electron-updater', 'semver']) { await mkdir(join(source, 'node_modules', name), { recursive: true }) await cp(require.resolve(`${name}/package.json`), join(source, 'node_modules', name, 'package.json')) diff --git a/apps/desktop/tests/installer-packaging.spec.ts b/apps/desktop/tests/installer-packaging.spec.ts index 1eaa829407..1dcfcb24fd 100644 --- a/apps/desktop/tests/installer-packaging.spec.ts +++ b/apps/desktop/tests/installer-packaging.spec.ts @@ -16,7 +16,7 @@ describe('installer preparation preserves application dependencies', () => { const { createElectronBuilderConfig } = await import('../scripts/electron-builder-config.mjs') expect(() => createElectronBuilderConfig({ DSH_DESKTOP_APP_ID: 'com.example.installer', DSH_DESKTOP_AUTO_UPDATE_ENV: 'production', - DSH_DESKTOP_MANDATORY_UPDATE_TEST_ORIGIN: 'https://harness-test.deepseek.com', + DSH_DESKTOP_MANDATORY_UPDATE_TEST_ORIGIN: 'https://test.example.com', }, platform, 'x64')).toThrow('DSH_DESKTOP_MANDATORY_UPDATE_PROD_ORIGIN') }) it.each(['win32', 'darwin'] as const)('keeps electron-builder responsible for node_modules on %s', async (platform) => { @@ -24,6 +24,7 @@ describe('installer preparation preserves application dependencies', () => { const env = { DSH_DESKTOP_APP_ID: 'com.example.installer', DSH_DESKTOP_MANDATORY_UPDATE_TEST_ORIGIN: 'https://policy.example.com', + DSH_DESKTOP_MANDATORY_UPDATE_CONFIG: JSON.stringify({ allowedAuthOrigins: ['https://login.example.com'] }), DSH_DESKTOP_TARGET_PLATFORM: platform, DSH_DESKTOP_TARGET_ARCH: 'x64', DSH_DESKTOP_UNSIGNED: platform === 'win32' ? '1' : '0', diff --git a/apps/desktop/tests/macos-signature.spec.ts b/apps/desktop/tests/macos-signature.spec.ts index eaf5f03920..2bd63adbaa 100644 --- a/apps/desktop/tests/macos-signature.spec.ts +++ b/apps/desktop/tests/macos-signature.spec.ts @@ -14,6 +14,7 @@ import { const RELEASE_ENVIRONMENT = { DSH_DESKTOP_APP_ID: 'com.example.desktop', DSH_DESKTOP_MANDATORY_UPDATE_TEST_ORIGIN: 'https://policy.example.com', + DSH_DESKTOP_MANDATORY_UPDATE_CONFIG: JSON.stringify({ allowedAuthOrigins: ['https://login.example.com'] }), DSH_DESKTOP_TARGET_PLATFORM: 'darwin', DSH_DESKTOP_TARGET_ARCH: 'arm64', DSH_DESKTOP_MACOS_SIGNING_IDENTITY: 'Example Company (TEAMID1234)', @@ -97,6 +98,7 @@ describe('desktop macOS release signature', () => { expect(() => createElectronBuilderConfig({ DSH_DESKTOP_APP_ID: RELEASE_ENVIRONMENT.DSH_DESKTOP_APP_ID, DSH_DESKTOP_MANDATORY_UPDATE_TEST_ORIGIN: 'https://policy.example.com', + DSH_DESKTOP_MANDATORY_UPDATE_CONFIG: JSON.stringify({ allowedAuthOrigins: ['https://login.example.com'] }), DSH_DESKTOP_TARGET_PLATFORM: 'win32', }, 'win32')).toThrow(/DSH_DESKTOP_WINDOWS_CER_FILE/u) }) @@ -106,6 +108,7 @@ describe('desktop macOS release signature', () => { const config = createElectronBuilderConfig({ DSH_DESKTOP_APP_ID: RELEASE_ENVIRONMENT.DSH_DESKTOP_APP_ID, DSH_DESKTOP_MANDATORY_UPDATE_TEST_ORIGIN: 'https://policy.example.com', + DSH_DESKTOP_MANDATORY_UPDATE_CONFIG: JSON.stringify({ allowedAuthOrigins: ['https://login.example.com'] }), DSH_DESKTOP_TARGET_PLATFORM: 'win32', DSH_DESKTOP_UNSIGNED: '1', }, 'win32', 'x64') diff --git a/apps/desktop/tests/main-startup.spec.ts b/apps/desktop/tests/main-startup.spec.ts index 3df37cb4ae..81abfe8169 100644 --- a/apps/desktop/tests/main-startup.spec.ts +++ b/apps/desktop/tests/main-startup.spec.ts @@ -289,7 +289,7 @@ beforeEach(() => { vi.spyOn(console, 'info').mockImplementation(() => {}) vi.stubEnv('DSH_DESKTOP_PNPM_ENTRY', 'test-pnpm') vi.stubEnv('DSH_DESKTOP_DSH_DIR', 'test-runtime') - vi.stubGlobal('process', { ...process, platform: 'win32', resourcesPath: 'desktop-test-resources' }) + vi.stubGlobal('process', { ...process, platform: 'win32', arch: 'x64', resourcesPath: 'desktop-test-resources' }) vi.stubEnv('DSH_DESKTOP_HOST_INSPECT_PORT', undefined) vi.stubEnv('DSH_DESKTOP_MANDATORY_UPDATE_CONFIG', undefined) vi.stubEnv('DSH_DESKTOP_UPDATE_JOURNAL_DIR', undefined) @@ -340,7 +340,7 @@ describe('desktop main startup', () => { }) it('shows one explained startup login before Host readiness and joins concurrent checks without reopening it', async () => { - harness.embeddedPolicy = { origin: 'https://policy.example.com', authentication: 'feishu-test', + harness.embeddedPolicy = { origin: 'https://policy.example.com', authentication: 'feishu-test', allowedAuthOrigins: ['https://login.example.com'], allowedPageOrigins: ['https://downloads.example.com'], intervalMs: 1000, jitter: 0 } vi.stubGlobal('fetch', vi.fn().mockImplementation(async () => Response.json({ error: { code: 'UNAUTHENTICATED' } }, { status: 401 }))) @@ -383,7 +383,7 @@ describe('desktop main startup', () => { }) it.each(['returned', 'cancelled', 'failed'] as const)('requires explicit test login and handles %s without downloading', async (outcome) => { - harness.embeddedPolicy = { origin: 'https://policy.example.com', authentication: 'feishu-test', + harness.embeddedPolicy = { origin: 'https://policy.example.com', authentication: 'feishu-test', allowedAuthOrigins: ['https://login.example.com'], allowedPageOrigins: ['https://downloads.example.com'], intervalMs: 10_000, jitter: 0 } const request = vi.fn().mockImplementation(async () => Response.json({ error: { code: 'UNAUTHENTICATED' } }, { status: 401 })) vi.stubGlobal('fetch', request) @@ -410,7 +410,7 @@ describe('desktop main startup', () => { }) it('does not open Feishu when the user declines test login', async () => { - harness.embeddedPolicy = { origin: 'https://policy.example.com', authentication: 'feishu-test', + harness.embeddedPolicy = { origin: 'https://policy.example.com', authentication: 'feishu-test', allowedAuthOrigins: ['https://login.example.com'], allowedPageOrigins: ['https://downloads.example.com'] } vi.stubGlobal('fetch', vi.fn().mockImplementation(async () => Response.json({ error: { code: 'UNAUTHENTICATED' } }, { status: 401 }))) await readyForUpdate() @@ -420,7 +420,7 @@ describe('desktop main startup', () => { }) it('does not require gateway login to download an already available ordinary update', async () => { - harness.embeddedPolicy = { origin: 'https://policy.example.com', authentication: 'feishu-test', + harness.embeddedPolicy = { origin: 'https://policy.example.com', authentication: 'feishu-test', allowedAuthOrigins: ['https://login.example.com'], allowedPageOrigins: ['https://downloads.example.com'] } vi.stubGlobal('fetch', vi.fn().mockImplementation(async () => Response.json({ error: { code: 'UNAUTHENTICATED' } }, { status: 401 }))) @@ -433,7 +433,7 @@ describe('desktop main startup', () => { }) it('retains the same blocking window and running Host after expired test login is cancelled', async () => { - harness.embeddedPolicy = { origin: 'https://policy.example.com', authentication: 'feishu-test', + harness.embeddedPolicy = { origin: 'https://policy.example.com', authentication: 'feishu-test', allowedAuthOrigins: ['https://login.example.com'], allowedPageOrigins: ['https://downloads.example.com'], intervalMs: 1000, jitter: 0 } const request = vi.fn().mockImplementation(async () => Response.json({ error: { code: 'UNAUTHENTICATED' } }, { status: 401 })) @@ -932,7 +932,7 @@ describe('desktop main startup', () => { }) it('queues policy authentication until the ordinary result dialog closes', async () => { - harness.embeddedPolicy = { origin: 'https://policy.example.com', authentication: 'feishu-test', + harness.embeddedPolicy = { origin: 'https://policy.example.com', authentication: 'feishu-test', allowedAuthOrigins: ['https://login.example.com'], allowedPageOrigins: ['https://downloads.example.com'], intervalMs: 10_000, jitter: 0 } const request = vi.fn() .mockResolvedValueOnce(Response.json({ code: 0, data: { biz_code: 0, biz_data: null } })) diff --git a/apps/desktop/tests/mandatory-update-policy.spec.ts b/apps/desktop/tests/mandatory-update-policy.spec.ts index e43e09c814..530c54d295 100644 --- a/apps/desktop/tests/mandatory-update-policy.spec.ts +++ b/apps/desktop/tests/mandatory-update-policy.spec.ts @@ -13,7 +13,7 @@ const instances: DesktopMandatoryUpdatePolicy[] = [] function fixture(authentication: 'anonymous' | 'feishu-test' = 'anonymous') { const request = vi.fn().mockResolvedValue(Response.json(clear)) const publish = vi.fn<(state: DesktopPolicyState) => void>() - const policy = new DesktopMandatoryUpdatePolicy(resolveDesktopPolicyConfig({ ...deployment, authentication })!, + const policy = new DesktopMandatoryUpdatePolicy(resolveDesktopPolicyConfig({ ...deployment, authentication, ...(authentication === 'feishu-test' ? { allowedAuthOrigins: ['https://login.example.com'] } : {}) })!, identity, publish, request) instances.push(policy) return { policy, request, publish } @@ -162,6 +162,15 @@ describe('mandatory update policy', () => { }) describe('policy deployment and page validation', () => { + it('requires validated login origins only for test authentication', () => { + expect(() => resolveDesktopPolicyConfig({ ...deployment, authentication: 'feishu-test' })).toThrow('allowedAuthOrigins') + expect(() => resolveDesktopPolicyConfig({ ...deployment, allowedAuthOrigins: ['https://login.example.com'] })).toThrow('anonymous') + expect(() => resolveDesktopPolicyConfig({ ...deployment, authentication: 'feishu-test', + allowedAuthOrigins: ['https://login.example.com/path'] })).toThrow('HTTPS origin') + expect(resolveDesktopPolicyConfig({ ...deployment, authentication: 'feishu-test', + allowedAuthOrigins: ['https://login.example.com'] })?.allowedAuthOrigins).toEqual(['https://login.example.com']) + }) + it('requires explicit production origins and confines HTTP to an explicit local policy fixture', () => { expect(resolveDesktopPolicyConfig(undefined)).toBeUndefined() expect(() => resolveDesktopPolicyConfig({ ...deployment, origin: 'http://127.0.0.1:19001' })).toThrow('HTTPS') diff --git a/apps/desktop/tests/policy-test-auth.spec.ts b/apps/desktop/tests/policy-test-auth.spec.ts index 517eda15ca..d830dd1d22 100644 --- a/apps/desktop/tests/policy-test-auth.spec.ts +++ b/apps/desktop/tests/policy-test-auth.spec.ts @@ -35,7 +35,7 @@ beforeEach(() => { vi.clearAllMocks() browserSession = makeSession(); window = makeWindow() native.partition.mockReturnValue(browserSession); native.create.mockReturnValue(window) - auth = new DesktopPolicyTestAuth('https://policy.example.com', resolveDesktopLocale('zh'), () => undefined, record) + auth = new DesktopPolicyTestAuth('https://policy.example.com', ['https://login.example.com'], resolveDesktopLocale('zh'), () => undefined, record) }) afterEach(async () => { await auth.dispose() }) @@ -64,7 +64,7 @@ it('opens a sandboxed window on explicit action and coalesces logins without aut expect(window.loadURL).not.toHaveBeenCalled() await vi.waitFor(() => { expect(window.loadURL).toHaveBeenCalledWith('https://policy.example.com/') }) expect(window.loadFile).toHaveBeenCalledTimes(1) - window.webContents.emit('did-navigate', {}, 'https://accounts.feishu.cn/open-apis/authen/v1/index?state=secret') + window.webContents.emit('did-navigate', {}, 'https://login.example.com/open-apis/authen/v1/index?state=secret') expect(window.isDestroyed()).toBe(false) window.webContents.emit('did-navigate', {}, 'https://policy.example.com/') expect(await pending).toBe('returned') @@ -121,7 +121,7 @@ it('cancels a login closed while the placeholder is still loading, without start it.each(['will-navigate', 'will-redirect'])('refuses unapproved %s targets without logging OAuth data', async (eventName) => { const pending = auth.login() const event = { preventDefault: vi.fn() } - window.webContents.emit(eventName, event, 'https://accounts.feishu.cn.evil.example/?code=secret') + window.webContents.emit(eventName, event, 'https://login.example.com.evil.example/?code=secret') expect(event.preventDefault).toHaveBeenCalledOnce() expect(await pending).toBe('failed') expect(record.mock.calls).toEqual([['opened'], ['failed']]) @@ -144,8 +144,8 @@ it('rejects child-frame escapes, permissions, downloads, new windows and HTTP au expect(await pending).toBe('failed') }) -it.each(['https://open.feishu.cn/', 'https://accounts.feishu.cn/', 'https://passport.feishu.cn/', - 'https://login.feishu.cn/'])('allows reviewed login documents: %s', (url) => { +it.each(['https://login.example.com/', 'https://login.example.com/', 'https://login.example.com/', + 'https://login.example.com/'])('allows reviewed login documents: %s', (url) => { const callback = vi.fn() browserSession.webRequest.onBeforeRequest.mock.calls[0]![0]({ resourceType: 'mainFrame', url }, callback) expect(callback).toHaveBeenCalledWith({ cancel: false }) @@ -157,7 +157,7 @@ it('keeps manual login open until close, reports cancellation once, and permits expect(await pending).toBe('cancelled') window = makeWindow(); native.create.mockReturnValue(window) const retry = auth.login() - window.webContents.emit('did-fail-load', {}, -105, 'raw URL error', 'https://accounts.feishu.cn/?state=secret', true) + window.webContents.emit('did-fail-load', {}, -105, 'raw URL error', 'https://login.example.com/?state=secret', true) expect(await retry).toBe('failed') expect(record.mock.calls).toEqual([['opened'], ['cancelled'], ['opened'], ['failed']]) }) diff --git a/apps/desktop/tests/windows-asar-unpack.spec.ts b/apps/desktop/tests/windows-asar-unpack.spec.ts index fc45a38ef9..ddfaf15de2 100644 --- a/apps/desktop/tests/windows-asar-unpack.spec.ts +++ b/apps/desktop/tests/windows-asar-unpack.spec.ts @@ -166,6 +166,7 @@ it.each([true, false])('validates the real builder hook for unsigned=%s', async await writeFile(certificate, 'fixture public certificate') const config = createElectronBuilderConfig({ DSH_DESKTOP_APP_ID: 'com.example.unpack', DSH_DESKTOP_MANDATORY_UPDATE_TEST_ORIGIN: 'https://policy.example.com', + DSH_DESKTOP_MANDATORY_UPDATE_CONFIG: JSON.stringify({ allowedAuthOrigins: ['https://login.example.com'] }), DSH_DESKTOP_TARGET_PLATFORM: 'win32', DSH_DESKTOP_TARGET_ARCH: 'x64', DSH_DESKTOP_UNSIGNED: unsigned ? '1' : '0', DSH_DESKTOP_WINDOWS_CER_FILE: certificate, DOWNLOAD_TEST_ORIGIN: 'https://updates.example.com', }, 'win32', 'x64', input.source) diff --git a/apps/desktop/tests/windows-update-publisher.spec.ts b/apps/desktop/tests/windows-update-publisher.spec.ts index 8967f67b87..2c27e600a1 100644 --- a/apps/desktop/tests/windows-update-publisher.spec.ts +++ b/apps/desktop/tests/windows-update-publisher.spec.ts @@ -51,6 +51,7 @@ describe('Windows update publisher', () => { beforeAll(() => { vi.stubEnv('DSH_DESKTOP_APP_ID', 'com.example.publisher-test') vi.stubEnv('DSH_DESKTOP_MANDATORY_UPDATE_TEST_ORIGIN', 'https://policy.example.com') + vi.stubEnv('DSH_DESKTOP_MANDATORY_UPDATE_CONFIG', JSON.stringify({ allowedAuthOrigins: ['https://login.example.com'] })) vi.stubEnv('DSH_DESKTOP_TARGET_PLATFORM', 'win32') vi.stubEnv('DSH_DESKTOP_UNSIGNED', '1') })