Skip to content

Commit 422ea5c

Browse files
committed
fix: reject invalid nonce format instead of silently regenerating
Old cached URLs with standard base64 nonces (containing = padding) are now rejected with a clear error message instead of silently regenerating. This maintains the integrity of signed form data. Users with old cached authentication URLs will see: "Invalid nonce format. Please generate a new authentication URL."
1 parent d825303 commit 422ea5c

File tree

3 files changed

+25
-19
lines changed

3 files changed

+25
-19
lines changed

lib/routes-ui.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4433,10 +4433,13 @@ ${Buffer.from(data.content, 'base64url').toString('base64')}
44334433
// throws if invalid or unknown app ID
44344434
const oAuth2Client = await oauth2Apps.getClient(oauth2App.id);
44354435

4436-
// Validate nonce format (base64url, 21-22 chars) or generate new one
4437-
// This handles old cached URLs that may have standard base64 nonces
4438-
const isValidNonce = data.n && /^[A-Za-z0-9_-]{21,22}$/.test(data.n);
4439-
const nonce = isValidNonce ? data.n : crypto.randomBytes(NONCE_BYTES).toString('base64url');
4436+
const nonce = data.n || crypto.randomBytes(NONCE_BYTES).toString('base64url');
4437+
4438+
// Validate nonce format (base64url, 21-22 chars)
4439+
if (!/^[A-Za-z0-9_-]{21,22}$/.test(nonce)) {
4440+
let error = Boom.boomify(new Error('Invalid nonce format. Please generate a new authentication URL.'), { statusCode: 400 });
4441+
throw error;
4442+
}
44404443

44414444
// store account data with atomic SET + EX
44424445
await redis.set(`${REDIS_PREFIX}account:add:${nonce}`, JSON.stringify(accountData), 'EX', Math.floor(MAX_FORM_TTL / 1000));

lib/ui-routes/account-routes.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -518,10 +518,13 @@ function init(args) {
518518
// throws if invalid or unknown app ID
519519
const oAuth2Client = await oauth2Apps.getClient(oauth2App.id);
520520

521-
// Validate nonce format (base64url, 21-22 chars) or generate new one
522-
// This handles old cached URLs that may have standard base64 nonces
523-
const isValidNonce = data.n && /^[A-Za-z0-9_-]{21,22}$/.test(data.n);
524-
const nonce = isValidNonce ? data.n : crypto.randomBytes(NONCE_BYTES).toString('base64url');
521+
const nonce = data.n || crypto.randomBytes(NONCE_BYTES).toString('base64url');
522+
523+
// Validate nonce format (base64url, 21-22 chars)
524+
if (!/^[A-Za-z0-9_-]{21,22}$/.test(nonce)) {
525+
let error = Boom.boomify(new Error('Invalid nonce format. Please generate a new authentication URL.'), { statusCode: 400 });
526+
throw error;
527+
}
525528

526529
// store account data with atomic SET + EX
527530
await redis.set(`${REDIS_PREFIX}account:add:${nonce}`, JSON.stringify(accountData), 'EX', Math.floor(MAX_FORM_TTL / 1000));

test/oauth-nonce-test.js

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -97,26 +97,26 @@ test('OAuth nonce encoding tests', async t => {
9797
assert.strictEqual(incorrectUsages.length, 0, `Found NONCE_BYTES with incorrect encoding:\n${incorrectUsages.join('\n')}`);
9898
});
9999

100-
await t.test('nonce validation pattern used where data.n is consumed', async () => {
101-
// Verify that files using data.n validate the format before using it
102-
// This prevents old cached URLs with base64 nonces from causing errors
103-
const filesToCheck = [
104-
{ file: 'lib/routes-ui.js', pattern: /isValidNonce.*data\.n.*test\(data\.n\)/ },
105-
{ file: 'lib/ui-routes/account-routes.js', pattern: /isValidNonce.*data\.n.*test\(data\.n\)/ }
106-
];
100+
await t.test('nonce validation with error for invalid format', async () => {
101+
// Verify that files using data.n validate the format and throw error if invalid
102+
// This rejects old cached URLs with standard base64 nonces
103+
const filesToCheck = ['lib/routes-ui.js', 'lib/ui-routes/account-routes.js'];
104+
105+
// Pattern: validates nonce and throws Boom error for invalid format
106+
const validationPattern = /if.*!.*test\(nonce\).*\{[\s\S]*?Boom\.boomify.*Invalid nonce format/;
107107

108108
const missingValidation = [];
109109

110-
for (const { file, pattern } of filesToCheck) {
110+
for (const file of filesToCheck) {
111111
const filePath = path.join(__dirname, '..', file);
112112
if (fs.existsSync(filePath)) {
113113
const content = fs.readFileSync(filePath, 'utf8');
114-
if (!pattern.test(content)) {
115-
missingValidation.push(`${file}: missing nonce validation pattern`);
114+
if (!validationPattern.test(content)) {
115+
missingValidation.push(`${file}: missing nonce validation with error`);
116116
}
117117
}
118118
}
119119

120-
assert.strictEqual(missingValidation.length, 0, `Files using data.n must validate nonce format:\n${missingValidation.join('\n')}`);
120+
assert.strictEqual(missingValidation.length, 0, `Files using data.n must validate and reject invalid nonces:\n${missingValidation.join('\n')}`);
121121
});
122122
});

0 commit comments

Comments
 (0)