firebase-auth-cloudflare-workers
Version:
Zero-dependencies firebase auth library for Cloudflare Workers.
118 lines (117 loc) • 3.99 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.HTTPFetcher = exports.parseMaxAge = exports.UrlKeyFetcher = exports.isX509Certificates = exports.isJWKMetadata = void 0;
const validator_1 = require("./validator");
const x509_1 = require("./x509");
const isJWKMetadata = (value) => {
if (!(0, validator_1.isNonNullObject)(value) || !value.keys) {
return false;
}
const keys = value.keys;
if (!Array.isArray(keys)) {
return false;
}
const filtered = keys.filter((key) => (0, validator_1.isObject)(key) && !!key.kid && typeof key.kid === 'string');
return keys.length === filtered.length;
};
exports.isJWKMetadata = isJWKMetadata;
const isX509Certificates = (value) => {
if (!(0, validator_1.isNonNullObject)(value)) {
return false;
}
const values = Object.values(value);
if (values.length === 0) {
return false;
}
for (const v of values) {
if (typeof v !== 'string' || v === '') {
return false;
}
}
return true;
};
exports.isX509Certificates = isX509Certificates;
/**
* Class to fetch public keys from a client certificates URL.
*/
class UrlKeyFetcher {
fetcher;
keyStorer;
constructor(fetcher, keyStorer) {
this.fetcher = fetcher;
this.keyStorer = keyStorer;
}
/**
* Fetches the public keys for the Google certs.
*
* @returns A promise fulfilled with public keys for the Google certs.
*/
async fetchPublicKeys() {
const publicKeys = await this.keyStorer.get();
if (publicKeys === null || typeof publicKeys !== 'object') {
return await this.refresh();
}
return publicKeys;
}
async refresh() {
const resp = await this.fetcher.fetch();
if (!resp.ok) {
const errorMessage = 'Error fetching public keys for Google certs: ';
const text = await resp.text();
throw new Error(errorMessage + text);
}
const json = await resp.json();
const publicKeys = await this.retrievePublicKeys(json);
const cacheControlHeader = resp.headers.get('cache-control');
// store the public keys cache in the KV store.
const maxAge = (0, exports.parseMaxAge)(cacheControlHeader);
if (!isNaN(maxAge) && maxAge > 0) {
await this.keyStorer.put(JSON.stringify(publicKeys), maxAge);
}
return publicKeys;
}
async retrievePublicKeys(json) {
if ((0, exports.isX509Certificates)(json)) {
const jwks = [];
for (const [kid, x509] of Object.entries(json)) {
jwks.push(await (0, x509_1.jwkFromX509)(kid, x509));
}
return jwks;
}
if (!(0, exports.isJWKMetadata)(json)) {
throw new Error(`The public keys are not an object or null: "${json}`);
}
return json.keys;
}
}
exports.UrlKeyFetcher = UrlKeyFetcher;
// parseMaxAge parses Cache-Control header and returns max-age value as number.
// returns NaN when Cache-Control header is none or max-age is not found, the value is invalid.
const parseMaxAge = (cacheControlHeader) => {
if (cacheControlHeader === null) {
return NaN;
}
const parts = cacheControlHeader.split(',');
for (const part of parts) {
const subParts = part.trim().split('=');
if (subParts[0] !== 'max-age') {
continue;
}
return Number(subParts[1]); // maxAge is a seconds value.
}
return NaN;
};
exports.parseMaxAge = parseMaxAge;
class HTTPFetcher {
clientCertUrl;
constructor(clientCertUrl) {
this.clientCertUrl = clientCertUrl;
if (!(0, validator_1.isURL)(clientCertUrl)) {
throw new Error('The provided public client certificate URL is not a valid URL.');
}
}
fetch() {
return fetch(this.clientCertUrl);
}
}
exports.HTTPFetcher = HTTPFetcher;