UNPKG

@pedwise/next-firebase-auth-edge

Version:

Next.js 13 Firebase Authentication for Edge and server runtimes. Dedicated for Next 13 server components. Compatible with Next.js middleware.

171 lines 6.89 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.decodeJwt = exports.verifyJwtSignature = exports.EmulatorSignatureVerifier = exports.PublicKeySignatureVerifier = exports.NextCachedUrlKeyFetcher = exports.ALGORITHM_RS256 = void 0; const validator_1 = require("./validator"); const error_1 = require("./jwt/error"); const jwt_1 = require("./jwt"); const verify_1 = require("./jwt/verify"); const response_cache_1 = require("./response-cache"); exports.ALGORITHM_RS256 = "RS256"; const NO_MATCHING_KID_ERROR_MESSAGE = "no-matching-kid-error"; const NO_KID_IN_HEADER_ERROR_MESSAGE = "no-kid-in-header-error"; function getExpiresAt(res) { if (!res.headers.has("cache-control")) { return 0; } const cacheControlHeader = res.headers.get("cache-control"); const parts = cacheControlHeader.split(","); const maxAge = parts.reduce((acc, part) => { const subParts = part.trim().split("="); if (subParts[0] === "max-age") { return +subParts[1]; } return acc; }, 0); return Date.now() + maxAge * 1000; } class NextCachedUrlKeyFetcher { constructor(clientCertUrl) { this.clientCertUrl = clientCertUrl; this.cache = (0, response_cache_1.getResponseCache)(); if (!(0, validator_1.isURL)(clientCertUrl)) { throw new Error("The provided public client certificate URL is not a valid URL."); } } async fetchPublicKeysResponse(url) { const res = await fetch(url); if (!res.ok) { let errorMessage = "Error fetching public keys for Google certs: "; const data = await res.json(); if (data.error) { errorMessage += `${data.error}`; if (data.error_description) { errorMessage += " (" + data.error_description + ")"; } } else { errorMessage += `${await res.text()}`; } throw new Error(errorMessage); } const clone = res.clone(); const data = await res.json(); if (data.error) { throw new error_1.JwtError(error_1.JwtErrorCode.KEY_FETCH_ERROR, data.error); } const publicKeys = data; const body = JSON.stringify({ publicKeys, publicKeysExpireAt: getExpiresAt(res), }); return new Response(body, clone); } async fetchAndCachePublicKeys(url) { const res = await this.fetchPublicKeysResponse(url); await this.cache.put(url, res.clone()); return (await res.json()).publicKeys; } async fetchPublicKeys() { const url = new URL(this.clientCertUrl); const cachedResponse = await this.cache.get(url); if (!cachedResponse) { return this.fetchAndCachePublicKeys(url); } const { publicKeys, publicKeysExpireAt } = await cachedResponse.json(); if (publicKeysExpireAt <= Date.now()) { return this.fetchAndCachePublicKeys(url); } return publicKeys; } } exports.NextCachedUrlKeyFetcher = NextCachedUrlKeyFetcher; class PublicKeySignatureVerifier { constructor(keyFetcher) { this.keyFetcher = keyFetcher; if (!(0, validator_1.isNonNullObject)(keyFetcher)) { throw new Error("The provided key fetcher is not an object or null."); } } static withCertificateUrl(clientCertUrl) { return new PublicKeySignatureVerifier(new NextCachedUrlKeyFetcher(clientCertUrl)); } async verify(token) { if (!(0, validator_1.isString)(token)) { return Promise.reject(new error_1.JwtError(error_1.JwtErrorCode.INVALID_ARGUMENT, "The provided token must be a string.")); } const decoded = (0, jwt_1.decode)(token, { complete: true }); const publicKey = await getKey(this.keyFetcher, decoded.header); return verifyJwtSignature(token, publicKey).catch((error) => { if (error.code === error_1.JwtErrorCode.NO_KID_IN_HEADER) { return this.verifyWithoutKid(token); } throw error; }); } verifyWithoutKid(token) { return this.keyFetcher .fetchPublicKeys() .then((publicKeys) => this.verifyWithAllKeys(token, publicKeys)); } verifyWithAllKeys(token, keys) { const promises = []; Object.values(keys).forEach((key) => { const result = verifyJwtSignature(token, key) .then(() => true) .catch((error) => { if (error.code === error_1.JwtErrorCode.TOKEN_EXPIRED) { throw error; } return false; }); promises.push(result); }); return Promise.all(promises).then((result) => { if (result.every((r) => r === false)) { throw new error_1.JwtError(error_1.JwtErrorCode.INVALID_SIGNATURE, "Invalid token signature."); } }); } } exports.PublicKeySignatureVerifier = PublicKeySignatureVerifier; class EmulatorSignatureVerifier { verify(token) { // Signature checks skipped for emulator; no need to fetch public keys. return verifyJwtSignature(token, ""); } } exports.EmulatorSignatureVerifier = EmulatorSignatureVerifier; async function getKey(fetcher, header) { if (!header.kid) { throw new Error(NO_KID_IN_HEADER_ERROR_MESSAGE); } const kid = header.kid || ""; const publicKeys = await fetcher.fetchPublicKeys(); if (!Object.prototype.hasOwnProperty.call(publicKeys, kid)) { throw new Error(NO_MATCHING_KID_ERROR_MESSAGE); } return publicKeys[kid]; } async function verifyJwtSignature(token, secretOrPublicKey) { if (!token) { throw new error_1.JwtError(error_1.JwtErrorCode.INVALID_ARGUMENT, "The provided token must be a string."); } await (0, verify_1.verify)(token, secretOrPublicKey); } exports.verifyJwtSignature = verifyJwtSignature; function decodeJwt(jwtToken) { if (!(0, validator_1.isString)(jwtToken)) { return Promise.reject(new error_1.JwtError(error_1.JwtErrorCode.INVALID_ARGUMENT, "The provided token must be a string.")); } const fullDecodedToken = (0, jwt_1.decode)(jwtToken, { complete: true, }); if (!fullDecodedToken) { return Promise.reject(new error_1.JwtError(error_1.JwtErrorCode.INVALID_ARGUMENT, "Decoding token failed.")); } const header = fullDecodedToken === null || fullDecodedToken === void 0 ? void 0 : fullDecodedToken.header; const payload = fullDecodedToken === null || fullDecodedToken === void 0 ? void 0 : fullDecodedToken.payload; return Promise.resolve({ header, payload }); } exports.decodeJwt = decodeJwt; //# sourceMappingURL=signature-verifier.js.map