{"record":{"id":"8d2dd984b0fbb565","repo":"koala73/worldmonitor","slug":"desktop-authentication-failed","errorCode":null,"errorMessage":"Desktop authentication failed","messagePattern":"Desktop authentication failed","errorType":"http","errorClass":"ApiError","httpStatus":403,"severity":"error","filePath":"server/worldmonitor/leads/v1/register-interest.ts","lineNumber":107,"sourceCode":"): Promise<string> {\n  return `sha256=${await hmacSha256Hex(secret, desktopAuthMessage(timestamp, req))}`;\n}\n\nasync function verifyDesktopAuth(request: Request, req: RegisterInterestRequest): Promise<void> {\n  const secret = process.env[DESKTOP_AUTH_SECRET_ENV];\n  const timestamp = request.headers.get(DESKTOP_AUTH_TIMESTAMP_HEADER);\n  const signature = request.headers.get(DESKTOP_AUTH_SIGNATURE_HEADER);\n\n  if (!secret) {\n    if (!timestamp && !signature && process.env[DESKTOP_AUTH_ALLOW_LEGACY_ENV] === 'true') {\n      console.warn(\n        `[register-interest] ${DESKTOP_AUTH_ALLOW_LEGACY_ENV}=true and ${DESKTOP_AUTH_SECRET_ENV} is unset; accepting unsigned legacy desktop bypass`,\n      );\n      return;\n    }\n\n    console.warn(`[register-interest] ${DESKTOP_AUTH_SECRET_ENV} not set; rejecting desktop bypass`);\n    throw new ApiError(403, 'Desktop authentication failed', '');\n  }\n\n  if (!timestamp || !signature) {\n    throw new ApiError(403, 'Desktop authentication failed', '');\n  }\n\n  const timestampMs = Number(timestamp);\n  if (!Number.isSafeInteger(timestampMs) || Math.abs(Date.now() - timestampMs) > DESKTOP_AUTH_WINDOW_MS) {\n    throw new ApiError(403, 'Desktop authentication failed', '');\n  }\n\n  const supplied = signature.trim();\n  if (!/^sha256=[a-f0-9]{64}$/.test(supplied)) {\n    throw new ApiError(403, 'Desktop authentication failed', '');\n  }\n\n  const expected = await createDesktopAuthSignature(secret, timestamp, req);\n  if (!timingSafeStringEqual(supplied, expected)) {","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/koala73/worldmonitor/blob/eeab0a219fce0f02a00603b532dbae9041b934ac/server/worldmonitor/leads/v1/register-interest.ts#L89-L125","documentation":"verifyDesktopAuth (register-interest.ts:107) throws 403 when the server has no WM_DESKTOP_SHARED_SECRET configured AND the legacy escape hatch does not apply (WM_DESKTOP_AUTH_ALLOW_LEGACY=true only accepts requests that carry no auth headers at all). This is the server refusing an unauthenticated Turnstile bypass from a desktop source — it is a deployment configuration problem, not a client signing bug.","triggerScenarios":"POSTing to register-interest with source=\"desktop-settings\" while the deployment env lacks WM_DESKTOP_SHARED_SECRET. The legacy bypass cannot save you if you also sent x-worldmonitor-desktop-* headers, because that path requires both headers to be absent.","commonSituations":"Promoting to a new Vercel/Railway environment without copying the secret; setting the secret on the API service but not on the Node sidecar; env name typo (WM_DESKTOP_SECRET instead of WM_DESKTOP_SHARED_SECRET); local dev against prod while the secret only exists in CI secrets.","solutions":["Set WM_DESKTOP_SHARED_SECRET in the serving environment to the same value the desktop app signs with, then redeploy","Verify with a health/env dump that the variable is actually visible to the process","For a legacy unsigned client only: set WM_DESKTOP_AUTH_ALLOW_LEGACY=true AND stop sending the auth headers","Do not rotate the secret on one side only — clients sign with the old value and fall into error 306 instead"],"exampleFix":"# before (server env missing)\nvercel env ls | grep WM_DESKTOP  # nothing\n\n# after\nvercel env add WM_DESKTOP_SHARED_SECRET production\nvercel deploy --prod","handlingStrategy":"validation","validationCode":"// Desktop client: refuse to send the desktop source unless we hold a secret to sign with\nif (source === 'desktop-settings' && !desktopSecret) {\n  source = 'website'; // fall back to the Turnstile path\n  deleteHeaders(DESKTOP_AUTH_TIMESTAMP_HEADER, DESKTOP_AUTH_SIGNATURE_HEADER);\n} else if (desktopSecret) {\n  const ts = Date.now().toString();\n  setHeader(DESKTOP_AUTH_TIMESTAMP_HEADER, ts);\n  setHeader(DESKTOP_AUTH_SIGNATURE_HEADER, await createDesktopAuthSignature(desktopSecret, ts, req));\n}\nawait post(req);","typeGuard":"function canSignDesktopRequests(secret: string | undefined): secret is string {\n  return typeof secret === 'string' && secret.length > 0;\n}","tryCatchPattern":"try { await register(req); }\ncatch (e) {\n  if (isApiError(e, 403, 'Desktop authentication failed') && !desktopSecret) {\n    reportOps('WM_DESKTOP_SHARED_SECRET missing on server — desktop signups are hard-down');\n  }\n  throw e;\n}","preventionTips":["Include WM_DESKTOP_SHARED_SECRET in every environment's checklist and in deploy smoke tests","Use the same secret store entry for server and desktop release pipelines so they cannot diverge","Treat a 100% 403 rate on desktop-source traffic as a config alarm, not user error"],"tags":["desktop-auth","hmac","missing-env-var","config"],"backgroundTag":"missing-env-var","analyzedSha":"eeab0a219fce0f02a00603b532dbae9041b934ac","analyzedAt":"2026-08-21T16:51:25.751Z","schemaVersion":2},"datasetVersion":"2026-08-23T16:17:53.355Z"}