Skip to content

Commit

Permalink
refactor: unify validating endpoints and checking their protocols
Browse files Browse the repository at this point in the history
  • Loading branch information
panva committed Oct 7, 2024
1 parent ef8fe9f commit e16254f
Show file tree
Hide file tree
Showing 9 changed files with 86 additions and 22 deletions.
74 changes: 62 additions & 12 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2136,29 +2136,59 @@ async function publicJwk(key: CryptoKey) {
return jwkCache.get(key) || getSetPublicJwkCache(key)
}

// @ts-ignore
const URLParse: (url: string | URL, base?: string | URL) => URL | null = URL.parse
? // @ts-ignore
(url, base) => URL.parse(url, base)
: (url, base) => {
try {
return new URL(url, base)
} catch {
return null
}
}

/**
* @ignore
*/
export function checkProtocol(url: URL, enforceHttps: boolean | undefined) {
if (enforceHttps && url.protocol !== 'https:') {
throw OPE('only requests to HTTPS are allowed', HTTP_REQUEST_FORBIDDEN, url)
}

if (url.protocol !== 'https:' && url.protocol !== 'http:') {
throw OPE('only HTTP and HTTPS requests are allowed', REQUEST_PROTOCOL_FORBIDDEN, url)
}
}

function validateEndpoint(
value: unknown,
endpoint: keyof AuthorizationServer,
useMtlsAlias: boolean | undefined,
enforceHttps: boolean | undefined,
) {
assertString(value, useMtlsAlias ? `"as.mtls_endpoint_aliases.${endpoint}"` : `"as.${endpoint}"`)

const url = new URL(value)

if (enforceHttps && url.protocol !== 'https:') {
throw OPE('only requests to HTTPS are allowed', HTTP_REQUEST_FORBIDDEN, url)
let url: URL | null
if (typeof value !== 'string' || !(url = URLParse(value))) {
throw OPE(
`authorization server metadata does not contain a valid ${useMtlsAlias ? `"as.mtls_endpoint_aliases.${endpoint}"` : `"as.${endpoint}"`}`,
value === undefined ? MISSING_SERVER_METADATA : INVALID_SERVER_METADATA,
)
}

checkProtocol(url, enforceHttps)

return url
}

function resolveEndpoint(
/**
* @ignore
*/
export function resolveEndpoint(
as: AuthorizationServer,
endpoint: keyof AuthorizationServer,
useMtlsAlias: boolean | undefined,
enforceHttps: boolean | undefined,
) {
): URL {
if (useMtlsAlias && as.mtls_endpoint_aliases && endpoint in as.mtls_endpoint_aliases) {
return validateEndpoint(
as.mtls_endpoint_aliases[endpoint],
Expand Down Expand Up @@ -2618,9 +2648,7 @@ export async function protectedResourceRequest(
throw CodedTypeError('"url" must be an instance of URL', ERR_INVALID_ARG_TYPE)
}

if (options?.[allowInsecureRequests] !== true && url.protocol !== 'https:') {
throw OPE('only requests to HTTPS are allowed', HTTP_REQUEST_FORBIDDEN, url)
}
checkProtocol(url, options?.[allowInsecureRequests] !== true)

headers = prepareHeaders(headers)

Expand Down Expand Up @@ -3504,7 +3532,10 @@ function validateAudience(expected: string, result: Awaited<ReturnType<typeof va
return result
}

function validateOptionalIssuer(as: AuthorizationServer, result: Awaited<ReturnType<typeof validateJwt>>) {
function validateOptionalIssuer(
as: AuthorizationServer,
result: Awaited<ReturnType<typeof validateJwt>>,
) {
if (result.claims.iss !== undefined) {
return validateIssuer(as, result)
}
Expand Down Expand Up @@ -3988,6 +4019,13 @@ export const RESPONSE_IS_NOT_CONFORM = 'OAUTH_RESPONSE_IS_NOT_CONFORM'
* @group Error Codes
*/
export const HTTP_REQUEST_FORBIDDEN = 'OAUTH_HTTP_REQUEST_FORBIDDEN'
/**
* Assigned as {@link OperationProcessingError.code} when a request is about to made to a non-HTTP(S)
* endpoint.
*
* @group Error Codes
*/
export const REQUEST_PROTOCOL_FORBIDDEN = 'OAUTH_REQUEST_PROTOCOL_FORBIDDEN'
/**
* Assigned as {@link OperationProcessingError.code} when a JWT NumericDate comparison with the
* current timestamp fails.
Expand Down Expand Up @@ -4020,6 +4058,18 @@ export const JSON_ATTRIBUTE_COMPARISON = 'OAUTH_JSON_ATTRIBUTE_COMPARISON_FAILED
* @group Error Codes
*/
export const KEY_SELECTION = 'OAUTH_KEY_SELECTION_FAILED'
/**
* Assigned as {@link OperationProcessingError.code} when the AS configuration is missing metadata.
*
* @group Error Codes
*/
export const MISSING_SERVER_METADATA = 'OAUTH_MISSING_SERVER_METADATA'
/**
* Assigned as {@link OperationProcessingError.code} when the AS configuration has invalid metadata.
*
* @group Error Codes
*/
export const INVALID_SERVER_METADATA = 'OAUTH_INVALID_SERVER_METADATA'

function checkJwtType(expected: string, result: Awaited<ReturnType<typeof validateJwt>>) {
if (typeof result.header.typ !== 'string' || normalizeTyp(result.header.typ) !== expected) {
Expand Down
2 changes: 1 addition & 1 deletion test/authorization_code.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ test('authorizationCodeGrantRequest()', async (t) => {
'verifier',
),
{
message: '"as.token_endpoint" must be a string',
message: 'authorization server metadata does not contain a valid "as.token_endpoint"',
},
)

Expand Down
15 changes: 14 additions & 1 deletion test/client_credentials.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,22 @@ const tClient: lib.Client = { ...client, client_secret: 'foo' }

test('clientCredentialsGrantRequest()', async (t) => {
await t.throwsAsync(lib.clientCredentialsGrantRequest(issuer, tClient, new URLSearchParams()), {
message: '"as.token_endpoint" must be a string',
message: 'authorization server metadata does not contain a valid "as.token_endpoint"',
code: 'OAUTH_MISSING_SERVER_METADATA',
})

await t.throwsAsync(
lib.clientCredentialsGrantRequest(
{ ...issuer, token_endpoint: '' },
tClient,
new URLSearchParams(),
),
{
message: 'authorization server metadata does not contain a valid "as.token_endpoint"',
code: 'OAUTH_INVALID_SERVER_METADATA',
},
)

const tIssuer: lib.AuthorizationServer = {
...issuer,
token_endpoint: endpoint('token-1'),
Expand Down
5 changes: 3 additions & 2 deletions test/device_flow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ const tClient: lib.Client = { ...client, token_endpoint_auth_method: 'none' }

test('deviceAuthorizationRequest()', async (t) => {
await t.throwsAsync(lib.deviceAuthorizationRequest(issuer, tClient, new URLSearchParams()), {
message: '"as.device_authorization_endpoint" must be a string',
message:
'authorization server metadata does not contain a valid "as.device_authorization_endpoint"',
})

const tIssuer: lib.AuthorizationServer = {
Expand Down Expand Up @@ -238,7 +239,7 @@ test('processDeviceAuthorizationResponse()', async (t) => {

test('deviceCodeGrantRequest()', async (t) => {
await t.throwsAsync(lib.deviceCodeGrantRequest(issuer, tClient, 'device_code'), {
message: '"as.token_endpoint" must be a string',
message: 'authorization server metadata does not contain a valid "as.token_endpoint"',
})

await t.throwsAsync(lib.deviceCodeGrantRequest(issuer, tClient, null as any), {
Expand Down
2 changes: 1 addition & 1 deletion test/introspection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const tClient: lib.Client = { ...client, client_secret: 'foo' }

test('introspectionRequest()', async (t) => {
await t.throwsAsync(lib.introspectionRequest(issuer, tClient, 'token'), {
message: '"as.introspection_endpoint" must be a string',
message: 'authorization server metadata does not contain a valid "as.introspection_endpoint"',
})

await t.throwsAsync(lib.introspectionRequest(issuer, tClient, null as any), {
Expand Down
3 changes: 2 additions & 1 deletion test/par.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ const tClient: lib.Client = { ...client, client_secret: 'foo' }

test('pushedAuthorizationRequest()', async (t) => {
await t.throwsAsync(lib.pushedAuthorizationRequest(issuer, tClient, new URLSearchParams()), {
message: '"as.pushed_authorization_request_endpoint" must be a string',
message:
'authorization server metadata does not contain a valid "as.pushed_authorization_request_endpoint"',
})

const tIssuer: lib.AuthorizationServer = {
Expand Down
2 changes: 1 addition & 1 deletion test/refresh_token.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const tClient: lib.Client = { ...client, client_secret: 'foo' }

test('refreshTokenGrantRequest()', async (t) => {
await t.throwsAsync(lib.refreshTokenGrantRequest(issuer, tClient, 'refresh_token'), {
message: '"as.token_endpoint" must be a string',
message: 'authorization server metadata does not contain a valid "as.token_endpoint"',
})

await t.throwsAsync(lib.refreshTokenGrantRequest(issuer, tClient, null as any), {
Expand Down
2 changes: 1 addition & 1 deletion test/revocation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const tClient: lib.Client = { ...client, client_secret: 'foo' }

test('revocationRequest()', async (t) => {
await t.throwsAsync(lib.revocationRequest(issuer, tClient, 'token'), {
message: '"as.revocation_endpoint" must be a string',
message: 'authorization server metadata does not contain a valid "as.revocation_endpoint"',
})

await t.throwsAsync(lib.revocationRequest(issuer, tClient, null as any), {
Expand Down
3 changes: 1 addition & 2 deletions test/userinfo.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,7 @@ test('userInfoRequest() w/ jwt signal', async (t) => {

test('userInfoRequest() requires userinfo_endpoint', async (t) => {
await t.throwsAsync(lib.userInfoRequest(issuer, client, 'token'), {
name: 'TypeError',
message: '"as.userinfo_endpoint" must be a string',
message: 'authorization server metadata does not contain a valid "as.userinfo_endpoint"',
})
})

Expand Down

0 comments on commit e16254f

Please sign in to comment.