From 0e05461100b5d8d00f368eee05dc1700e0f6e102 Mon Sep 17 00:00:00 2001 From: Matthieu Sieben Date: Fri, 15 Dec 2023 22:22:37 +0100 Subject: [PATCH] wip --- .../oauth-server/src/client/client-store.ts | 25 +++++++++++++------ packages/oauth-server/src/client/client.ts | 18 ++++++++++--- packages/oauth-server/src/client/types.ts | 7 +++--- packages/oauth-server/src/util/did.ts | 4 --- 4 files changed, 36 insertions(+), 18 deletions(-) diff --git a/packages/oauth-server/src/client/client-store.ts b/packages/oauth-server/src/client/client-store.ts index 10bce833410..7f220f95241 100644 --- a/packages/oauth-server/src/client/client-store.ts +++ b/packages/oauth-server/src/client/client-store.ts @@ -18,6 +18,24 @@ export class ClientStore { constructor(fetch: Fetch = global.fetch) { this.fetchFunction = combine( + /** + * Disallow fetching from domains we know are not atproto client + * implementation. Note that other domains can be blocked by providing + * a custom fetch function combined with anohter forbiddenDomainNameRequestTransform. + */ + forbiddenDomainNameRequestTransform([ + 'example.com', + 'bsky.social', + 'bsky.network', + 'google.com', + 'googleusercontent.com', + 'facebook.com', + 'facebook.net', + 'instagram.com', + 'twitter.com', + 'x.com', + ]), + /** * Since we will be fetching from the network based on user provided * input, we need to make sure that the request is not vulnerable to SSRF @@ -25,13 +43,6 @@ export class ClientStore { */ ssrfSafeRequestTransform(), - /** - * Disallow fetching from domains we know are not atproto client - * implementation. Note that other domains can be blocked by providing - * a custom fetch function. - */ - forbiddenDomainNameRequestTransform(['bsky.social', 'bsky.network']), - // Wrap the fetch function to add some extra features fetch, diff --git a/packages/oauth-server/src/client/client.ts b/packages/oauth-server/src/client/client.ts index be072bf60a9..fc8c3ca9037 100644 --- a/packages/oauth-server/src/client/client.ts +++ b/packages/oauth-server/src/client/client.ts @@ -6,7 +6,6 @@ import { } from 'jose' import { InvalidClientError } from '../errors' -import { extractService } from '../util/did' import { didWebToUrl, fetchDidDocument } from '../util/did-web' import { Fetch } from '../util/fetch' import { JsonWebKeySet, fetchJsonWebKeySet } from '../util/jwk' @@ -14,6 +13,7 @@ import { isLoopbackHostname } from '../util/net' import { fetchClientMetadata } from './fetch-client-metadata' import { + CLIENT_ASSERTION_TYPE_JWT_BEARER, ClientCredentials, ClientId, ClientMetadata, @@ -98,6 +98,16 @@ export class Client { return this.metadata.token_endpoint_auth_method !== 'none' } + toJSON() { + return { + ...this.metadata, + client_id: this.id, + // When serializing, "burn" the jwks if they were fetched from jwks_uri + jwks_uri: undefined, + jwks: this.jwks, + } + } + async jwtVerify( token: string, options?: JWTVerifyOptions, @@ -121,8 +131,7 @@ export class Client { }, ): Promise { if ( - credentials.client_assertion_type === - 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer' + credentials.client_assertion_type === CLIENT_ASSERTION_TYPE_JWT_BEARER ) { const { payload } = await this.jwtVerify<{ jti: string }>( credentials.client_assertion, @@ -152,7 +161,8 @@ async function resolveClientMetadata(clientId: ClientId, fetch?: Fetch) { .then( // If service not found, allow fallback by returning undefined (didDocument) => - extractService(didDocument, 'OAuthClientMetadata')?.serviceEndpoint, + didDocument.service?.find((s) => s.type === 'OAuthClientMetadata') + ?.serviceEndpoint, ) .catch( // In case of 404, allow fallback by returning undefined diff --git a/packages/oauth-server/src/client/types.ts b/packages/oauth-server/src/client/types.ts index 8c6534c6cff..72006fe095f 100644 --- a/packages/oauth-server/src/client/types.ts +++ b/packages/oauth-server/src/client/types.ts @@ -7,11 +7,12 @@ export const clientIdSchema = didWebSchema export type ClientId = z.infer +export const CLIENT_ASSERTION_TYPE_JWT_BEARER = + 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer' + export const clientAssertionSchema = z.object({ client_id: clientIdSchema, - client_assertion_type: z.literal( - 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', - ), + client_assertion_type: z.literal(CLIENT_ASSERTION_TYPE_JWT_BEARER), /** * - "sub" the subject MUST be the "client_id" of the OAuth client * - "iat" is required and MUST be less than one minute diff --git a/packages/oauth-server/src/util/did.ts b/packages/oauth-server/src/util/did.ts index d7939bc632a..3da2ed7790d 100644 --- a/packages/oauth-server/src/util/did.ts +++ b/packages/oauth-server/src/util/did.ts @@ -70,7 +70,3 @@ export const didDocumentSchema = z.object({ }) export type DidDocument = z.infer - -export function extractService(didDocument: DidDocument, type: string) { - return didDocument.service?.find((s) => s.type === type) -}