Skip to content

Commit

Permalink
refactor: brand URLSearchParams instead of extending URLSearchParams
Browse files Browse the repository at this point in the history
  • Loading branch information
panva committed Apr 24, 2023
1 parent b1a914c commit 8e62c8a
Show file tree
Hide file tree
Showing 7 changed files with 44 additions and 30 deletions.
2 changes: 1 addition & 1 deletion docs/functions/authorizationCodeGrantRequest.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Performs an Authorization Code grant request at the
| :------ | :------ | :------ |
| `as` | [`AuthorizationServer`](../interfaces/AuthorizationServer.md) | Authorization Server Metadata. |
| `client` | [`Client`](../interfaces/Client.md) | Client Metadata. |
| `callbackParameters` | `CallbackParameters` | Parameters obtained from the callback to redirect_uri, this is returned from [validateAuthResponse](validateAuthResponse.md), or [validateJwtAuthResponse](validateJwtAuthResponse.md). |
| `callbackParameters` | [`URLSearchParams`]( https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams ) | Parameters obtained from the callback to redirect_uri, this is returned from [validateAuthResponse](validateAuthResponse.md), or [validateJwtAuthResponse](validateJwtAuthResponse.md). |
| `redirectUri` | `string` | `redirect_uri` value used in the authorization request. |
| `codeVerifier` | `string` | PKCE `code_verifier` to send to the token endpoint. |
| `options?` | [`TokenEndpointRequestOptions`](../interfaces/TokenEndpointRequestOptions.md) | - |
Expand Down
4 changes: 2 additions & 2 deletions docs/functions/validateAuthResponse.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[💗 Help the project](https://github.com/sponsors/panva)

**validateAuthResponse**(`as`, `client`, `parameters`, `expectedState?`): `CallbackParameters` \| [`OAuth2Error`](../interfaces/OAuth2Error.md)
**validateAuthResponse**(`as`, `client`, `parameters`, `expectedState?`): [`URLSearchParams`]( https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams ) \| [`OAuth2Error`](../interfaces/OAuth2Error.md)

Validates an OAuth 2.0 Authorization Response or Authorization Error Response message returned
from the authorization server's
Expand All @@ -25,6 +25,6 @@ from the authorization server's

#### Returns

`CallbackParameters` \| [`OAuth2Error`](../interfaces/OAuth2Error.md)
[`URLSearchParams`]( https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams ) \| [`OAuth2Error`](../interfaces/OAuth2Error.md)

Validated Authorization Response parameters or Authorization Error Response.
4 changes: 2 additions & 2 deletions docs/functions/validateJwtAuthResponse.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[💗 Help the project](https://github.com/sponsors/panva)

**validateJwtAuthResponse**(`as`, `client`, `parameters`, `expectedState?`, `options?`): [`Promise`]( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise )<`CallbackParameters` \| [`OAuth2Error`](../interfaces/OAuth2Error.md)\>
**validateJwtAuthResponse**(`as`, `client`, `parameters`, `expectedState?`, `options?`): [`Promise`]( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise )<[`URLSearchParams`]( https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams ) \| [`OAuth2Error`](../interfaces/OAuth2Error.md)\>

Same as [validateAuthResponse](validateAuthResponse.md) but for signed JARM responses.

Expand All @@ -20,6 +20,6 @@ Same as [validateAuthResponse](validateAuthResponse.md) but for signed JARM resp

#### Returns

[`Promise`]( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise )<`CallbackParameters` \| [`OAuth2Error`](../interfaces/OAuth2Error.md)\>
[`Promise`]( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise )<[`URLSearchParams`]( https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams ) \| [`OAuth2Error`](../interfaces/OAuth2Error.md)\>

Validated Authorization Response parameters or Authorization Error Response.
20 changes: 12 additions & 8 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2249,6 +2249,12 @@ function validateIssuer(expected: string, result: ParsedJWT) {
return result
}

const branded = new WeakSet<URLSearchParams>()
function brand(searchParams: URLSearchParams) {
branded.add(searchParams)
return searchParams
}

/**
* Performs an Authorization Code grant request at the
* {@link AuthorizationServer.token_endpoint `as.token_endpoint`}.
Expand All @@ -2268,17 +2274,17 @@ function validateIssuer(expected: string, result: ParsedJWT) {
export async function authorizationCodeGrantRequest(
as: AuthorizationServer,
client: Client,
callbackParameters: CallbackParameters,
callbackParameters: URLSearchParams,
redirectUri: string,
codeVerifier: string,
options?: TokenEndpointRequestOptions,
): Promise<Response> {
assertAs(as)
assertClient(client)

if (!(callbackParameters instanceof CallbackParameters)) {
if (!branded.has(callbackParameters)) {
throw new TypeError(
'"callbackParameters" must be an instance of CallbackParameters obtained from "validateAuthResponse()", or "validateJwtAuthResponse()',
'"callbackParameters" must be an instance of URLSearchParams obtained from "validateAuthResponse()", or "validateJwtAuthResponse()',
)
}

Expand Down Expand Up @@ -3097,7 +3103,7 @@ export async function validateJwtAuthResponse(
parameters: URLSearchParams | URL,
expectedState?: string | typeof expectNoState | typeof skipStateCheck,
options?: HttpRequestOptions,
): Promise<CallbackParameters | OAuth2Error> {
): Promise<URLSearchParams | OAuth2Error> {
assertAs(as)
assertClient(client)

Expand Down Expand Up @@ -3202,8 +3208,6 @@ export const skipStateCheck = Symbol()
*/
export const expectNoState = Symbol()

class CallbackParameters extends URLSearchParams {}

/**
* Validates an OAuth 2.0 Authorization Response or Authorization Error Response message returned
* from the authorization server's
Expand All @@ -3225,7 +3229,7 @@ export function validateAuthResponse(
client: Client,
parameters: URLSearchParams | URL,
expectedState?: string | typeof expectNoState | typeof skipStateCheck,
): CallbackParameters | OAuth2Error {
): URLSearchParams | OAuth2Error {
assertAs(as)
assertClient(client)

Expand Down Expand Up @@ -3290,7 +3294,7 @@ export function validateAuthResponse(
throw new UnsupportedOperationError('implicit and hybrid flows are not supported')
}

return new CallbackParameters(parameters)
return brand(new URLSearchParams(parameters))
}

type ReturnTypes =
Expand Down
38 changes: 24 additions & 14 deletions test/authorization_code.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import anyTest, { type TestFn } from 'ava'
import * as querystring from 'node:querystring'
import setup, {
ALGS,
client,
Expand All @@ -22,17 +23,27 @@ test.before(setupJwks)

const tClient: lib.Client = { ...client, client_secret: 'foo' }

const callbackParameters = lib.validateAuthResponse(
{ issuer: 'foo' },
{ client_id: 'foo' },
new URLSearchParams(),
lib.expectNoState,
)
if (lib.isOAuth2Error(callbackParameters)) throw new Error()

function cb(arg: any): Exclude<ReturnType<typeof lib.validateAuthResponse>, lib.OAuth2Error> {
// @ts-expect-error
return new callbackParameters.constructor(arg)
function cb(params: string) {
const callbackParameters = lib.validateAuthResponse(
{ issuer: 'foo' },
{ client_id: 'foo' },
new URLSearchParams(),
lib.expectNoState,
)
if (lib.isOAuth2Error(callbackParameters)) throw new Error()
for (const param of callbackParameters.keys()) {
callbackParameters.delete(param)
}
for (const [key, value] of Object.entries(querystring.parse(params))) {
if (Array.isArray(value)) {
for (const v of value) {
callbackParameters.append(key, v)
}
} else if (typeof value === 'string') {
callbackParameters.set(key, value)
}
}
return callbackParameters
}

test('authorizationCodeGrantRequest()', async (t) => {
Expand All @@ -53,7 +64,7 @@ test('authorizationCodeGrantRequest()', async (t) => {
lib.authorizationCodeGrantRequest(issuer, tClient, <any>null, 'redirect_uri', 'verifier'),
{
message:
'"callbackParameters" must be an instance of CallbackParameters obtained from "validateAuthResponse()", or "validateJwtAuthResponse()',
'"callbackParameters" must be an instance of URLSearchParams obtained from "validateAuthResponse()", or "validateJwtAuthResponse()',
},
)

Expand Down Expand Up @@ -137,8 +148,7 @@ test('authorizationCodeGrantRequest() w/ Extra Parameters', async (t) => {
path: '/token-2',
method: 'POST',
body(body) {
const params = new URLSearchParams(body)
return params.get('resource') === 'urn:example:resource'
return new URLSearchParams(body).get('resource') === 'urn:example:resource'
},
})
.reply(200, { access_token: 'token', token_type: 'Bearer' })
Expand Down
4 changes: 2 additions & 2 deletions test/jarm.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ test('validateJwtAuthResponse()', async (t) => {
t.fail()
throw new Error()
}
t.is(result.constructor.name, 'CallbackParameters')
t.true(result instanceof URLSearchParams)
t.deepEqual([...result.keys()], ['iss', 'code'])
})
})
Expand Down Expand Up @@ -85,7 +85,7 @@ test('validateJwtAuthResponse() as URL', async (t) => {
t.fail()
throw new Error()
}
t.is(result.constructor.name, 'CallbackParameters')
t.true(result instanceof URLSearchParams)
t.deepEqual([...result.keys()], ['iss', 'code'])
})
})
Expand Down
2 changes: 1 addition & 1 deletion typedoc.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"excludeProtected": true,
"gitRevision": "main",
"hideGenerator": true,
"intentionallyNotExported": ["ReturnTypes", "CallbackParameters", "JsonValue"],
"intentionallyNotExported": ["ReturnTypes", "JsonValue"],
"out": "docs",
"plugin": ["typedoc-plugin-markdown", "typedoc-plugin-mdn-links"],
"readme": "none",
Expand Down

0 comments on commit 8e62c8a

Please sign in to comment.