Skip to content

Commit 35d4622

Browse files
committed
refactor: relax www-authenticate auth-param presence requirement
1 parent dfe2321 commit 35d4622

File tree

2 files changed

+53
-33
lines changed

2 files changed

+53
-33
lines changed

src/index.ts

Lines changed: 44 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2651,13 +2651,13 @@ export interface WWWAuthenticateChallenge {
26512651
}
26522652

26532653
const tokenMatch = "[a-zA-Z0-9!#$%&\\'\\*\\+\\-\\.\\^_`\\|~]+"
2654-
const token68Match = '[a-zA-Z0-9\\-\\._\\~\\+\\/]+[=]{0,2}'
2655-
const quotedMatch = '"((?:[^"\\\\]|\\\\.)*)"'
2654+
const token68Match = '[a-zA-Z0-9\\-\\._\\~\\+\\/]+={0,2}'
2655+
const quotedMatch = '"((?:[^"\\\\]|\\\\[\\s\\S])*)"'
26562656

26572657
const quotedParamMatcher = '(' + tokenMatch + ')\\s*=\\s*' + quotedMatch
26582658
const paramMatcher = '(' + tokenMatch + ')\\s*=\\s*(' + tokenMatch + ')'
26592659

2660-
const schemeRE = new RegExp('^[,\\s]*(' + tokenMatch + ')\\s(.*)')
2660+
const schemeRE = new RegExp('^[,\\s]*(' + tokenMatch + ')')
26612661
const quotedParamRE = new RegExp('^[,\\s]*' + quotedParamMatcher + '[,\\s]*(.*)')
26622662
const unquotedParamRE = new RegExp('^[,\\s]*' + paramMatcher + '[,\\s]*(.*)')
26632663
const token68ParamRE = new RegExp('^(' + token68Match + ')(?:$|[,\\s])(.*)')
@@ -2680,45 +2680,60 @@ function parseWwwAuthenticateChallenges(
26802680
while (rest) {
26812681
let match: RegExpMatchArray | null = rest.match(schemeRE)
26822682
const scheme = match?.['1'].toLowerCase() as Lowercase<string>
2683-
rest = match?.['2']
26842683
if (!scheme) {
26852684
return undefined
26862685
}
26872686

2687+
// Calculate remainder after scheme
2688+
const afterScheme = rest.substring(match![0].length)
2689+
if (afterScheme && !afterScheme.match(/^[\s,]/)) {
2690+
// Invalid: scheme must be followed by space, comma, or end
2691+
return undefined
2692+
}
2693+
// Check if there's a space after scheme (indicating parameters may follow)
2694+
const spaceMatch = afterScheme.match(/^\s+(.*)$/)
2695+
const hasParameters = !!spaceMatch
2696+
rest = spaceMatch ? spaceMatch[1] : undefined
2697+
26882698
const parameters: WWWAuthenticateChallenge['parameters'] = {}
26892699
let token68: string | undefined
26902700

2691-
while (rest) {
2692-
let key: string
2693-
let value: string
2694-
if ((match = rest.match(quotedParamRE))) {
2695-
;[, key, value, rest] = match
2696-
if (value.includes('\\')) {
2697-
try {
2698-
value = JSON.parse(`"${value}"`)
2699-
} catch {}
2701+
if (hasParameters) {
2702+
while (rest) {
2703+
let key: string
2704+
let value: string
2705+
if ((match = rest.match(quotedParamRE))) {
2706+
;[, key, value, rest] = match
2707+
if (value.includes('\\')) {
2708+
try {
2709+
value = JSON.parse(`"${value}"`)
2710+
} catch {}
2711+
}
2712+
// @ts-expect-error
2713+
parameters[key.toLowerCase() as Lowercase<string>] = value
2714+
continue
27002715
}
2701-
// @ts-expect-error
2702-
parameters[key.toLowerCase() as Lowercase<string>] = value
2703-
continue
2704-
}
27052716

2706-
if ((match = rest.match(unquotedParamRE))) {
2707-
;[, key, value, rest] = match
2708-
// @ts-expect-error
2709-
parameters[key.toLowerCase() as Lowercase<string>] = value
2710-
continue
2711-
}
2717+
if ((match = rest.match(unquotedParamRE))) {
2718+
;[, key, value, rest] = match
2719+
// @ts-expect-error
2720+
parameters[key.toLowerCase() as Lowercase<string>] = value
2721+
continue
2722+
}
27122723

2713-
if ((match = rest.match(token68ParamRE))) {
2714-
if (Object.keys(parameters).length) {
2724+
if ((match = rest.match(token68ParamRE))) {
2725+
if (Object.keys(parameters).length) {
2726+
break
2727+
}
2728+
;[, token68, rest] = match
27152729
break
27162730
}
2717-
;[, token68, rest] = match
2718-
break
2719-
}
27202731

2721-
return undefined
2732+
return undefined
2733+
}
2734+
} else {
2735+
// No space after scheme - set rest to the comma-prefixed remainder for next iteration
2736+
rest = afterScheme || undefined
27222737
}
27232738

27242739
const challenge: WWWAuthenticateChallenge = { scheme, parameters }

test/www_authenticate.test.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,23 @@ const vectors = [
5555
{
5656
description: 'parameter missing',
5757
header: 'Scheme',
58+
expected: [{ scheme: 'scheme', parameters: {} }],
5859
},
5960
{
6061
description: 'two schemes, parameters missing',
6162
header: 'Scheme, DPoP',
63+
expected: [
64+
{ scheme: 'scheme', parameters: {} },
65+
{ scheme: 'dpop', parameters: {} },
66+
],
6267
},
6368
{
6469
description: 'two schemes, parameters missing in one',
6570
header: 'Scheme, DPoP algs="ES256"',
71+
expected: [
72+
{ scheme: 'scheme', parameters: {} },
73+
{ scheme: 'dpop', parameters: { algs: 'ES256' } },
74+
],
6675
},
6776
{
6877
description: 'using token format for a parameter including backslashes',
@@ -354,10 +363,6 @@ const vectors = [
354363
description: 'Empty scheme with valid parameters',
355364
header: ` error="invalid_token", error_description="no scheme"`,
356365
},
357-
{
358-
description: 'Garbage input',
359-
header: `%%%%%%%`,
360-
},
361366
{
362367
description: 'Valid scheme followed by unparseable junk',
363368
header: `Bearer error="ok", ???`,

0 commit comments

Comments
 (0)