Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion lib/verifyReceipt.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ async function resolveSignerFromEns(signerEnsName, options = {}) {
const records = {};

let liveOk = true;
let resolutionError = false;
for (const key of requiredKeys) {
try {
const value = await resolver(signerEnsName, key);
Expand All @@ -90,6 +91,7 @@ async function resolveSignerFromEns(signerEnsName, options = {}) {
records[key] = value;
} catch {
liveOk = false;
resolutionError = true;
break;
}
}
Expand Down Expand Up @@ -119,7 +121,7 @@ async function resolveSignerFromEns(signerEnsName, options = {}) {
records: {},
ensResolved: false,
keySource: 'ens_txt',
errorCode: 'ens_key_unavailable',
errorCode: resolutionError ? 'key_resolution_failed' : 'ens_key_unavailable',
};
}

Expand Down
3 changes: 2 additions & 1 deletion tests/api-verify.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ test('POST /api/verify with canonical sample fixture => INVALID', async () => {
assert.equal(res.body.ok, false);
assert.equal(res.body.status, 'INVALID');
assert.equal(typeof res.body.public_key_source, 'string');
});
assert.equal(res.body.public_key_source, 'ens_txt');
});



Expand Down
51 changes: 51 additions & 0 deletions tests/verifyReceipt-runtime.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,57 @@ test('allows explicit local fallback for test/demo mode only when enabled', asyn
assert.equal(out.reason, 'Receipt is invalid, tampered, or does not match the signer key metadata.');
});


test('fails with key_resolution_failed when ENS resolver throws', async () => {
const { receipt } = await makeRuntimeReceipt();
const out = await verifyReceipt(receipt, {
ens: {
textResolver: async () => {
throw new Error('resolver offline');
},
allowLocalFallback: false,
},
});

assert.equal(out.status, 'INVALID');
assert.equal(out.reason, 'Receipt is invalid, tampered, or does not match the signer key metadata.');
assert.equal(out.debug.key_resolution_error, 'key_resolution_failed');
assert.equal(out.public_key_source, 'ens_txt');
});

test('allows env-flag local fallback only when COMMANDLAYER_ALLOW_LOCAL_KEY_FALLBACK=true', async () => {
const previous = process.env.COMMANDLAYER_ALLOW_LOCAL_KEY_FALLBACK;
const previousNodeEnv = process.env.NODE_ENV;

try {
process.env.NODE_ENV = 'production';
process.env.COMMANDLAYER_ALLOW_LOCAL_KEY_FALLBACK = 'false';

const { receipt } = await makeRuntimeReceipt();
const withoutFlag = await verifyReceipt(receipt, {
ens: { textResolver: async () => null },
});

assert.equal(withoutFlag.status, 'INVALID');
assert.equal(withoutFlag.reason, 'ens_key_unavailable');
assert.equal(withoutFlag.public_key_source, 'ens_txt');

process.env.COMMANDLAYER_ALLOW_LOCAL_KEY_FALLBACK = 'true';
const withFlag = await verifyReceipt(receipt, {
ens: { textResolver: async () => null },
});

assert.equal(withFlag.status, 'INVALID');
assert.equal(withFlag.public_key_source, 'local_test_fallback');
} finally {
if (previous === undefined) delete process.env.COMMANDLAYER_ALLOW_LOCAL_KEY_FALLBACK;
else process.env.COMMANDLAYER_ALLOW_LOCAL_KEY_FALLBACK = previous;

if (previousNodeEnv === undefined) delete process.env.NODE_ENV;
else process.env.NODE_ENV = previousNodeEnv;
}
});

test('tampered receipt invalidates', async () => {
const { receipt, rawPub } = await makeRuntimeReceipt();
receipt.output.ok = false;
Expand Down
Loading