UNPKG

@atproto/oauth-client

Version:

OAuth client for ATPROTO PDS. This package serves as common base for environment-specific implementations (NodeJS, Browser, React-Native).

88 lines (76 loc) 2.72 kB
import { Keyset } from '@atproto/jwk' import { OAuthClientMetadataInput, assertOAuthDiscoverableClientId, assertOAuthLoopbackClientId, } from '@atproto/oauth-types' import { ClientMetadata, clientMetadataSchema } from './types.js' const TOKEN_ENDPOINT_AUTH_METHOD = `token_endpoint_auth_method` const TOKEN_ENDPOINT_AUTH_SIGNING_ALG = `token_endpoint_auth_signing_alg` export function validateClientMetadata( input: OAuthClientMetadataInput, keyset?: Keyset, ): ClientMetadata { if (input.jwks) { if (!keyset) { throw new TypeError(`Keyset must not be provided when jwks is provided`) } for (const key of input.jwks.keys) { if (!key.kid) { throw new TypeError(`Key must have a "kid" property`) } else if (!keyset.has(key.kid)) { throw new TypeError(`Key with kid "${key.kid}" not found in keyset`) } } } // Allow to pass a keyset and omit the jwks/jwks_uri properties if (!input.jwks && !input.jwks_uri && keyset?.size) { input = { ...input, jwks: keyset.toJSON() } } const metadata = clientMetadataSchema.parse(input) // Validate client ID if (metadata.client_id.startsWith('http:')) { assertOAuthLoopbackClientId(metadata.client_id) } else { assertOAuthDiscoverableClientId(metadata.client_id) } const scopes = metadata.scope?.split(' ') if (!scopes?.includes('atproto')) { throw new TypeError(`Client metadata must include the "atproto" scope`) } if (!metadata.response_types.includes('code')) { throw new TypeError(`"response_types" must include "code"`) } if (!metadata.grant_types.includes('authorization_code')) { throw new TypeError(`"grant_types" must include "authorization_code"`) } const method = metadata[TOKEN_ENDPOINT_AUTH_METHOD] switch (method) { case undefined: throw new TypeError(`${TOKEN_ENDPOINT_AUTH_METHOD} must be provided`) case 'none': if (metadata[TOKEN_ENDPOINT_AUTH_SIGNING_ALG]) { throw new TypeError( `${TOKEN_ENDPOINT_AUTH_SIGNING_ALG} must not be provided when ${TOKEN_ENDPOINT_AUTH_METHOD} is "${method}"`, ) } break case 'private_key_jwt': if (!keyset?.size) { throw new TypeError( `A non-empty keyset must be provided when ${TOKEN_ENDPOINT_AUTH_METHOD} is "${method}"`, ) } if (!metadata[TOKEN_ENDPOINT_AUTH_SIGNING_ALG]) { throw new TypeError( `${TOKEN_ENDPOINT_AUTH_SIGNING_ALG} must be provided when ${TOKEN_ENDPOINT_AUTH_METHOD} is "${method}"`, ) } break default: throw new TypeError( `Invalid "token_endpoint_auth_method" value: ${method}`, ) } return metadata }