UNPKG

@atproto/oauth-client

Version:

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

54 lines 2.75 kB
import { oauthProtectedResourceMetadataSchema, } from '@atproto/oauth-types'; import { FetchResponseError, bindFetch, cancelBody, } from '@atproto-labs/fetch'; import { CachedGetter, swallowStoreErrors, } from '@atproto-labs/simple-store'; import { contentMime } from './util.js'; /** * @see {@link https://www.rfc-editor.org/rfc/rfc9728.html} */ export class OAuthProtectedResourceMetadataResolver extends CachedGetter { constructor(cache, fetch = globalThis.fetch, config) { super(async (origin, options) => this.fetchMetadata(origin, options), swallowStoreErrors(cache, config?.onCacheError)); this.fetch = bindFetch(fetch); this.allowHttpResource = config?.allowHttpResource === true; } async get(resource, options) { const { protocol, origin } = new URL(resource); if (protocol !== 'https:' && protocol !== 'http:') { throw new TypeError(`Invalid protected resource metadata URL protocol: ${protocol}`); } if (protocol === 'http:' && !this.allowHttpResource) { throw new TypeError(`Unsecure resource metadata URL (${protocol}) only allowed in development and test environments`); } return super.get(origin, options); } async fetchMetadata(origin, options) { const url = new URL(`/.well-known/oauth-protected-resource`, origin); const request = new Request(url, { signal: options?.signal, headers: { accept: 'application/json' }, cache: options?.noCache ? 'no-cache' : undefined, redirect: 'manual', // response must be 200 OK }); const response = await this.fetch(request); if (response.status === 404) { await cancelBody(response, 'log'); return null; } // https://www.rfc-editor.org/rfc/rfc9728.html#section-3.2 if (response.status !== 200) { await cancelBody(response, 'log'); throw await FetchResponseError.from(response, `Unexpected status code ${response.status} for "${url}"`, undefined, { cause: request }); } if (contentMime(response.headers) !== 'application/json') { await cancelBody(response, 'log'); throw await FetchResponseError.from(response, `Unexpected content type for "${url}"`, undefined, { cause: request }); } const metadata = oauthProtectedResourceMetadataSchema.parse(await response.json()); // https://www.rfc-editor.org/rfc/rfc9728.html#section-3.3 if (metadata.resource !== origin) { throw new TypeError(`Invalid issuer ${metadata.resource}`); } return metadata; } } //# sourceMappingURL=oauth-protected-resource-metadata-resolver.js.map