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.

179 lines 9.48 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createIdTokenVerifier = exports.FirebaseTokenVerifier = void 0; const firebase_1 = require("./firebase"); const signature_verifier_1 = require("./signature-verifier"); const validator_1 = require("./validator"); const error_1 = require("./error"); const error_2 = require("./jwt/error"); const EMULATOR_VERIFIER = new signature_verifier_1.EmulatorSignatureVerifier(); class FirebaseTokenVerifier { constructor(clientCertUrl, issuer, tokenInfo, projectId) { this.issuer = issuer; this.tokenInfo = tokenInfo; this.projectId = projectId; if (!(0, validator_1.isURL)(clientCertUrl)) { throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, "The provided public client certificate URL is an invalid URL."); } else if (!(0, validator_1.isURL)(issuer)) { throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, "The provided JWT issuer is an invalid URL."); } else if (!(0, validator_1.isNonNullObject)(tokenInfo)) { throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, "The provided JWT information is not an object or null."); } else if (!(0, validator_1.isURL)(tokenInfo.url)) { throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, "The provided JWT verification documentation URL is invalid."); } else if (!(0, validator_1.isNonEmptyString)(tokenInfo.verifyApiName)) { throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, "The JWT verify API name must be a non-empty string."); } else if (!(0, validator_1.isNonEmptyString)(tokenInfo.jwtName)) { throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, "The JWT public full name must be a non-empty string."); } else if (!(0, validator_1.isNonEmptyString)(tokenInfo.shortName)) { throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, "The JWT public short name must be a non-empty string."); } else if (!(0, validator_1.isNonNullObject)(tokenInfo.expiredErrorCode) || !("code" in tokenInfo.expiredErrorCode)) { throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, "The JWT expiration error code must be a non-null ErrorInfo object."); } this.shortNameArticle = tokenInfo.shortName.charAt(0).match(/[aeiou]/i) ? "an" : "a"; this.signatureVerifier = signature_verifier_1.PublicKeySignatureVerifier.withCertificateUrl(clientCertUrl); } async verifyJWT(jwtToken, isEmulator = false) { if (!(0, validator_1.isString)(jwtToken)) { throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, `First argument to ${this.tokenInfo.verifyApiName} must be a ${this.tokenInfo.jwtName} string.`); } const decoded = await this.decodeAndVerify(jwtToken, this.projectId, isEmulator); const decodedIdToken = decoded.payload; decodedIdToken.uid = decodedIdToken.sub; return decodedIdToken; } decodeAndVerify(token, projectId, isEmulator, audience) { return this.safeDecode(token).then((decodedToken) => { this.verifyContent(decodedToken, projectId, isEmulator, audience); return this.verifySignature(token, isEmulator).then(() => decodedToken); }); } safeDecode(jwtToken) { return (0, signature_verifier_1.decodeJwt)(jwtToken).catch((err) => { if (err.code == error_2.JwtErrorCode.INVALID_ARGUMENT) { const verifyJwtTokenDocsMessage = ` See ${this.tokenInfo.url} ` + `for details on how to retrieve ${this.shortNameArticle} ${this.tokenInfo.shortName}.`; const errorMessage = `Decoding ${this.tokenInfo.jwtName} failed. Make sure you passed ` + `the entire string JWT which represents ${this.shortNameArticle} ` + `${this.tokenInfo.shortName}.` + verifyJwtTokenDocsMessage; throw error_1.FirebaseAuthError.toAuthErrorWithStack(error_1.AuthClientErrorCode.INVALID_ARGUMENT, errorMessage, err); } throw error_1.FirebaseAuthError.toAuthErrorWithStack(error_1.AuthClientErrorCode.INTERNAL_ERROR, err.message, err); }); } verifyContent(fullDecodedToken, projectId, isEmulator, audience) { const header = fullDecodedToken && fullDecodedToken.header; const payload = fullDecodedToken && fullDecodedToken.payload; const projectIdMatchMessage = ` Make sure the ${this.tokenInfo.shortName} comes from the same ` + "Firebase project as the service account used to authenticate this SDK."; const verifyJwtTokenDocsMessage = ` See ${this.tokenInfo.url} ` + `for details on how to retrieve ${this.shortNameArticle} ${this.tokenInfo.shortName}.`; let errorMessage; if (!isEmulator && typeof header.kid === "undefined") { const isCustomToken = payload.aud === firebase_1.FIREBASE_AUDIENCE; const isLegacyCustomToken = header.alg === "HS256" && payload.v === 0 && "d" in payload && "uid" in payload.d; if (isCustomToken) { errorMessage = `${this.tokenInfo.verifyApiName} expects ${this.shortNameArticle} ` + `${this.tokenInfo.shortName}, but was given a custom token.`; } else if (isLegacyCustomToken) { errorMessage = `${this.tokenInfo.verifyApiName} expects ${this.shortNameArticle} ` + `${this.tokenInfo.shortName}, but was given a legacy custom token.`; } else { errorMessage = `${this.tokenInfo.jwtName} has no "kid" claim.`; } errorMessage += verifyJwtTokenDocsMessage; } else if (!isEmulator && header.alg !== signature_verifier_1.ALGORITHM_RS256) { errorMessage = `${this.tokenInfo.jwtName} has incorrect algorithm. Expected "` + signature_verifier_1.ALGORITHM_RS256 + '" but got ' + '"' + header.alg + '".' + verifyJwtTokenDocsMessage; } else if (typeof audience !== "undefined" && !payload.aud.includes(audience)) { errorMessage = `${this.tokenInfo.jwtName} has incorrect "aud" (audience) claim. Expected "` + audience + '" but got "' + payload.aud + '".' + verifyJwtTokenDocsMessage; } else if (typeof audience === "undefined" && payload.aud !== projectId) { errorMessage = `${this.tokenInfo.jwtName} has incorrect "aud" (audience) claim. Expected "` + projectId + '" but got "' + payload.aud + '".' + projectIdMatchMessage + verifyJwtTokenDocsMessage; } else if (payload.iss !== this.issuer + projectId) { errorMessage = `${this.tokenInfo.jwtName} has incorrect "iss" (issuer) claim. Expected ` + `"${this.issuer}` + projectId + '" but got "' + payload.iss + '".' + projectIdMatchMessage + verifyJwtTokenDocsMessage; } else if (typeof payload.sub !== "string") { errorMessage = `${this.tokenInfo.jwtName} has no "sub" (subject) claim.` + verifyJwtTokenDocsMessage; } else if (payload.sub === "") { errorMessage = `${this.tokenInfo.jwtName} has an empty string "sub" (subject) claim.` + verifyJwtTokenDocsMessage; } else if (payload.sub.length > 128) { errorMessage = `${this.tokenInfo.jwtName} has "sub" (subject) claim longer than 128 characters.` + verifyJwtTokenDocsMessage; } if (errorMessage) { throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, errorMessage); } } verifySignature(jwtToken, isEmulator) { const verifier = isEmulator ? EMULATOR_VERIFIER : this.signatureVerifier; return verifier.verify(jwtToken).catch((error) => { throw this.mapJwtErrorToAuthError(error); }); } mapJwtErrorToAuthError(error) { return error_1.FirebaseAuthError.fromJwtError(error, this.tokenInfo, this.shortNameArticle); } } exports.FirebaseTokenVerifier = FirebaseTokenVerifier; function createIdTokenVerifier(projectId) { return new FirebaseTokenVerifier(firebase_1.CLIENT_CERT_URL, "https://securetoken.google.com/", firebase_1.ID_TOKEN_INFO, projectId); } exports.createIdTokenVerifier = createIdTokenVerifier; //# sourceMappingURL=token-verifier.js.map