diff --git a/app/state/device-login.js b/app/state/device-login.js index e5afa567..a4705a1f 100644 --- a/app/state/device-login.js +++ b/app/state/device-login.js @@ -80,7 +80,11 @@ const body = await json(response); if (body?.success) { const data = body.data; - const token = typeof data === "string" ? data : data?.access_token ?? data?.token ?? data?.api_key ?? data?.apikey; + // The field is not documented: the usual names first, then any value shaped like a TorBox + // key (a UUID), so a renamed field still links the account. + const named = typeof data === "string" ? data : data?.access_token ?? data?.token ?? data?.api_key ?? data?.apikey; + const token = named ?? Object.values(data || {}).find((value) => typeof value === "string" && /^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$/i.test(value)); + if (!named) console.log(`[device-login] torbox: approved, fields ${Object.keys(data || {}).join(", ")}`); if (typeof token !== "string" || !token) throw new Error("TorBox approved the console but sent no key."); return { torbox: token.trim() }; } diff --git a/app/state/device-login.test.mjs b/app/state/device-login.test.mjs index b905fb99..8072e24c 100644 --- a/app/state/device-login.test.mjs +++ b/app/state/device-login.test.mjs @@ -61,6 +61,15 @@ test('TorBox is asked again until the player approves, and its token is the key' assert.equal(polls[0].body, JSON.stringify({device_code: TORBOX_START.data.device_code})); }); +test('a TorBox answer that names its key differently still links the account', async () => { + const {http} = service({ + 'https://api.torbox.app/v1/api/user/auth/device/start': [[200, TORBOX_START]], + 'https://api.torbox.app/v1/api/user/auth/device/token': [[200, {success: true, data: {expires_in: 0, key: '1b2c3d4e-5f60-4718-9a0b-1c2d3e4f5a6b'}}]], + }); + const login = createDeviceLogin({http, service: 'torbox', ...clock()}); + assert.deepEqual(await login.approved(await login.start()), {torbox: '1b2c3d4e-5f60-4718-9a0b-1c2d3e4f5a6b'}); +}); + test('a code that runs out is reported as expired, so the dialog asks for a new one', async () => { const {http} = service({ 'https://api.torbox.app/v1/api/user/auth/device/start': [[200, TORBOX_START]],