From 31b1c0609734309cc65bd9b225ade55407452062 Mon Sep 17 00:00:00 2001 From: Ardis Lu Date: Sat, 23 May 2026 23:16:34 -0700 Subject: [PATCH] Use custom `Envpass-Challenge` header Separating `challenge` from `prf` allows more flexibility to change the format that `prf` is bridged (e.g., to `application/octet-stream`) and it is more consistent with how similar material is usually passed in other APIs. --- src/getPrf.html | 7 +++++-- src/getPrf.js | 8 ++++---- test/getPrf.test.js | 17 ++++++++--------- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/getPrf.html b/src/getPrf.html index 4c84f46..1965a30 100644 --- a/src/getPrf.html +++ b/src/getPrf.html @@ -94,8 +94,11 @@ const prf = new Uint8Array(prfBuffer).toHex(); const response = await fetch(`http://localhost:${location.port}`, { // Intentionally hardcoding localhost, do not broadcast anywhere else method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ challenge, prf }) + headers: { + 'Content-Type': 'application/json', + 'Envpass-Challenge': challenge + }, + body: JSON.stringify({ prf }) }); if (response.status === 200) { p.textContent = 'Successfully sent passkey. You may close this window now.'; diff --git a/src/getPrf.js b/src/getPrf.js index 8dc3c6f..be19216 100644 --- a/src/getPrf.js +++ b/src/getPrf.js @@ -125,14 +125,14 @@ export async function getPrf(options = {}) { response.writeHead(400).end(); return; } + if (request.headers['envpass-challenge'] !== challenge) { + response.writeHead(401).end(); + return; + } let body = ''; request.on('data', chunk => body += chunk); request.on('end', () => { const payload = JSON.parse(body); - if (payload.challenge !== challenge) { - response.writeHead(401).end(); - return; - } const prf = parsePrf(payload.prf); if (prf === null) { response.writeHead(400).end(); diff --git a/test/getPrf.test.js b/test/getPrf.test.js index 0828d57..a582431 100644 --- a/test/getPrf.test.js +++ b/test/getPrf.test.js @@ -22,7 +22,6 @@ function filterNullValues(obj) { * @typedef {Object} MakePostRequestOptions * @property {Record} [headers] Headers to set on the request. Set a property to * `null` to *delete* that header. - * @property {string|null} [challenge] `challenge` string to pass on the request body. * @property {string|null} [prf] `prf` string to pass on the request body. */ @@ -35,7 +34,6 @@ function makePostRequest(url, options = {}) { const { origin, searchParams } = new URL(url); const { headers = {}, - challenge = searchParams.get('challenge'), prf = 'deadbeef000000000000000000000000000000000000000000000000cafebabe' } = options; const mergedHeaders = { @@ -44,10 +42,11 @@ function makePostRequest(url, options = {}) { 'Sec-Fetch-Site': 'same-origin', 'Sec-Fetch-Mode': 'cors', 'Sec-Fetch-Dest': 'empty', + 'Envpass-Challenge': searchParams.get('challenge'), ...headers } const filteredHeaders = filterNullValues(mergedHeaders); - const body = filterNullValues({ challenge, prf }); + const body = filterNullValues({ prf }); return new Request(origin, { method: 'POST', headers: filteredHeaders, @@ -88,7 +87,6 @@ suite('getPrf.js (server)', () => { // See https://github.com/nodejs/undici/issues/1305 const { url } = await setupServer(t); const { origin, searchParams } = new URL(url); - const challenge = searchParams.get('challenge'); const prf = 'deadbeef000000000000000000000000000000000000000000000000cafebabe'; const options = { method: 'POST', @@ -96,9 +94,10 @@ suite('getPrf.js (server)', () => { 'Content-Type': 'application/json', 'Origin': origin, 'Sec-Fetch-Site': 'same-origin', - 'Sec-Fetch-Dest': 'empty' + 'Sec-Fetch-Dest': 'empty', + 'Envpass-Challenge': searchParams.get('challenge') }), - body: JSON.stringify({ challenge, prf }) + body: JSON.stringify({ prf }) } deepStrictEqual((await request(origin, options)).statusCode, 403); // Tests the unset case options.headers['Sec-Fetch-Mode'] = 'navigate'; @@ -126,10 +125,10 @@ suite('getPrf.js (server)', () => { deepStrictEqual((await fetch(makePostRequest(url, { headers: { 'Content-Type': null } }))).status, 400); deepStrictEqual((await fetch(makePostRequest(url, { headers: { 'Content-Type': 'INVALID' } }))).status, 400); }); - test('validates challenge on POST', async (t) => { + test('validates Envpass-Challenge header on POST', async (t) => { const { url } = await setupServer(t); - deepStrictEqual((await fetch(makePostRequest(url, { challenge: null }))).status, 401); - deepStrictEqual((await fetch(makePostRequest(url, { challenge: 'INVALID' }))).status, 401); + deepStrictEqual((await fetch(makePostRequest(url, { headers: { 'Envpass-Challenge': null } }))).status, 401); + deepStrictEqual((await fetch(makePostRequest(url, { headers: { 'Envpass-Challenge': 'INVALID' } }))).status, 401); }); test('validates prf on POST', async (t) => { const { url } = await setupServer(t); -- 2.47.3