import { AccessToken, ClientCredentials } from 'simple-oauth2'; import type { Config } from './config'; /** Scopes the prover itself needs: it creates participants and charges. */ export const FULL_SCOPES = [ 'checkout.solicitacoes-requisicao', 'checkout.participantes-requisicao', 'checkout.solicitacoes-info', 'checkout.participantes-info', ]; /** * Scope of the token that travels with the attested request. It can only * read charges, so its exposure to the Primus attestor (and to anyone who * sees the attested request) cannot cancel charges or rewrite a seller's * bank account. */ export const READ_SCOPES = ['checkout.solicitacoes-info']; /** Seconds before expiry at which a cached token is renewed. */ const RENEW_WINDOW = 60; /** * One cached client-credentials token per scope set. The token endpoint does * not require mTLS, so this runs outside the attestation. */ export class OAuth { private readonly client: ClientCredentials; private readonly tokens = new Map(); constructor(config: Config['bb']) { const url = new URL(config.oauthUrl); this.client = new ClientCredentials({ client: { id: config.clientId, secret: config.clientSecret }, auth: { tokenHost: url.origin, tokenPath: url.pathname }, }); } async bearer(scopes: string[]): Promise { const scope = scopes.join(' '); let token = this.tokens.get(scope); if (!token || token.expired(RENEW_WINDOW)) { token = await this.client.getToken({ scope }); this.tokens.set(scope, token); } return `Bearer ${token.token.access_token as string}`; } /** Drops a cached token so the next call mints a fresh one. */ forget(scopes: string[]): void { this.tokens.delete(scopes.join(' ')); } }