153 lines
5.3 KiB
JavaScript
153 lines
5.3 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.experimental_jwksCache = exports.jwksCache = void 0;
|
|
exports.createRemoteJWKSet = createRemoteJWKSet;
|
|
const fetch_jwks_js_1 = require("../runtime/fetch_jwks.js");
|
|
const errors_js_1 = require("../util/errors.js");
|
|
const local_js_1 = require("./local.js");
|
|
const is_object_js_1 = require("../lib/is_object.js");
|
|
function isCloudflareWorkers() {
|
|
return (typeof WebSocketPair !== 'undefined' ||
|
|
(typeof navigator !== 'undefined' && navigator.userAgent === 'Cloudflare-Workers') ||
|
|
(typeof EdgeRuntime !== 'undefined' && EdgeRuntime === 'vercel'));
|
|
}
|
|
let USER_AGENT;
|
|
if (typeof navigator === 'undefined' || !navigator.userAgent?.startsWith?.('Mozilla/5.0 ')) {
|
|
const NAME = 'jose';
|
|
const VERSION = 'v5.10.0';
|
|
USER_AGENT = `${NAME}/${VERSION}`;
|
|
}
|
|
exports.jwksCache = Symbol();
|
|
function isFreshJwksCache(input, cacheMaxAge) {
|
|
if (typeof input !== 'object' || input === null) {
|
|
return false;
|
|
}
|
|
if (!('uat' in input) || typeof input.uat !== 'number' || Date.now() - input.uat >= cacheMaxAge) {
|
|
return false;
|
|
}
|
|
if (!('jwks' in input) ||
|
|
!(0, is_object_js_1.default)(input.jwks) ||
|
|
!Array.isArray(input.jwks.keys) ||
|
|
!Array.prototype.every.call(input.jwks.keys, is_object_js_1.default)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
class RemoteJWKSet {
|
|
_url;
|
|
_timeoutDuration;
|
|
_cooldownDuration;
|
|
_cacheMaxAge;
|
|
_jwksTimestamp;
|
|
_pendingFetch;
|
|
_options;
|
|
_local;
|
|
_cache;
|
|
constructor(url, options) {
|
|
if (!(url instanceof URL)) {
|
|
throw new TypeError('url must be an instance of URL');
|
|
}
|
|
this._url = new URL(url.href);
|
|
this._options = { agent: options?.agent, headers: options?.headers };
|
|
this._timeoutDuration =
|
|
typeof options?.timeoutDuration === 'number' ? options?.timeoutDuration : 5000;
|
|
this._cooldownDuration =
|
|
typeof options?.cooldownDuration === 'number' ? options?.cooldownDuration : 30000;
|
|
this._cacheMaxAge = typeof options?.cacheMaxAge === 'number' ? options?.cacheMaxAge : 600000;
|
|
if (options?.[exports.jwksCache] !== undefined) {
|
|
this._cache = options?.[exports.jwksCache];
|
|
if (isFreshJwksCache(options?.[exports.jwksCache], this._cacheMaxAge)) {
|
|
this._jwksTimestamp = this._cache.uat;
|
|
this._local = (0, local_js_1.createLocalJWKSet)(this._cache.jwks);
|
|
}
|
|
}
|
|
}
|
|
coolingDown() {
|
|
return typeof this._jwksTimestamp === 'number'
|
|
? Date.now() < this._jwksTimestamp + this._cooldownDuration
|
|
: false;
|
|
}
|
|
fresh() {
|
|
return typeof this._jwksTimestamp === 'number'
|
|
? Date.now() < this._jwksTimestamp + this._cacheMaxAge
|
|
: false;
|
|
}
|
|
async getKey(protectedHeader, token) {
|
|
if (!this._local || !this.fresh()) {
|
|
await this.reload();
|
|
}
|
|
try {
|
|
return await this._local(protectedHeader, token);
|
|
}
|
|
catch (err) {
|
|
if (err instanceof errors_js_1.JWKSNoMatchingKey) {
|
|
if (this.coolingDown() === false) {
|
|
await this.reload();
|
|
return this._local(protectedHeader, token);
|
|
}
|
|
}
|
|
throw err;
|
|
}
|
|
}
|
|
async reload() {
|
|
if (this._pendingFetch && isCloudflareWorkers()) {
|
|
this._pendingFetch = undefined;
|
|
}
|
|
const headers = new Headers(this._options.headers);
|
|
if (USER_AGENT && !headers.has('User-Agent')) {
|
|
headers.set('User-Agent', USER_AGENT);
|
|
this._options.headers = Object.fromEntries(headers.entries());
|
|
}
|
|
this._pendingFetch ||= (0, fetch_jwks_js_1.default)(this._url, this._timeoutDuration, this._options)
|
|
.then((json) => {
|
|
this._local = (0, local_js_1.createLocalJWKSet)(json);
|
|
if (this._cache) {
|
|
this._cache.uat = Date.now();
|
|
this._cache.jwks = json;
|
|
}
|
|
this._jwksTimestamp = Date.now();
|
|
this._pendingFetch = undefined;
|
|
})
|
|
.catch((err) => {
|
|
this._pendingFetch = undefined;
|
|
throw err;
|
|
});
|
|
await this._pendingFetch;
|
|
}
|
|
}
|
|
function createRemoteJWKSet(url, options) {
|
|
const set = new RemoteJWKSet(url, options);
|
|
const remoteJWKSet = async (protectedHeader, token) => set.getKey(protectedHeader, token);
|
|
Object.defineProperties(remoteJWKSet, {
|
|
coolingDown: {
|
|
get: () => set.coolingDown(),
|
|
enumerable: true,
|
|
configurable: false,
|
|
},
|
|
fresh: {
|
|
get: () => set.fresh(),
|
|
enumerable: true,
|
|
configurable: false,
|
|
},
|
|
reload: {
|
|
value: () => set.reload(),
|
|
enumerable: true,
|
|
configurable: false,
|
|
writable: false,
|
|
},
|
|
reloading: {
|
|
get: () => !!set._pendingFetch,
|
|
enumerable: true,
|
|
configurable: false,
|
|
},
|
|
jwks: {
|
|
value: () => set._local?.jwks(),
|
|
enumerable: true,
|
|
configurable: false,
|
|
writable: false,
|
|
},
|
|
});
|
|
return remoteJWKSet;
|
|
}
|
|
exports.experimental_jwksCache = exports.jwksCache;
|