import camelize from "camelize-ts"; import { defaultBaseUrl, defaultRealm } from "./constants.js"; import { fetchWithError } from "./fetchWithError.js"; import { stringifyQueryParams } from "./stringifyQueryParams.js"; // See: https://developer.mozilla.org/en-US/docs/Glossary/Base64 const bytesToBase64 = (bytes) => btoa(Array.from(bytes, (byte) => String.fromCodePoint(byte)).join("")); const toBase64 = (input) => bytesToBase64(new TextEncoder().encode(input)); // See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent#encoding_for_rfc3986 const encodeRFC3986URIComponent = (input) => encodeURIComponent(input).replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`); // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent // Specifically, the section on encoding `application/x-www-form-urlencoded`. const encodeFormURIComponent = (data) => encodeRFC3986URIComponent(data).replaceAll("%20", "+"); export const getToken = async (settings) => { // Construct URL const baseUrl = settings.baseUrl || defaultBaseUrl; const realmName = settings.realmName || defaultRealm; const url = `${baseUrl}/realms/${realmName}/protocol/openid-connect/token`; // Prepare credentials for openid-connect token request // ref: http://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint const credentials = settings.credentials || {}; const payload = stringifyQueryParams({ username: credentials.username, password: credentials.password, grant_type: credentials.grantType, client_id: credentials.clientId, totp: credentials.totp, ...(credentials.offlineToken ? { scope: "offline_access" } : {}), ...(credentials.scopes ? { scope: credentials.scopes.join(" ") } : {}), ...(credentials.refreshToken ? { refresh_token: credentials.refreshToken, client_secret: credentials.clientSecret, } : {}), }); const options = settings.requestOptions ?? {}; const headers = new Headers(options.headers); if (credentials.clientSecret) { // See: https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1 const username = encodeFormURIComponent(credentials.clientId); const password = encodeFormURIComponent(credentials.clientSecret); // See: https://datatracker.ietf.org/doc/html/rfc2617#section-2 headers.set("authorization", `Basic ${toBase64(`${username}:${password}`)}`); } headers.set("content-type", "application/x-www-form-urlencoded"); const response = await fetchWithError(url, { ...options, method: "POST", headers, body: payload, }); const data = (await response.json()); return camelize(data); };