54 lines
1.7 KiB
TypeScript
54 lines
1.7 KiB
TypeScript
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<string, AccessToken>();
|
|
|
|
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<string> {
|
|
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(' '));
|
|
}
|
|
}
|