1 |
|
2 | "use strict";
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 | Object.defineProperty(exports, "__esModule", { value: true });
|
19 | exports.createSessionCookieVerifier = exports.createIdTokenVerifier = exports.FirebaseTokenVerifier = exports.SESSION_COOKIE_INFO = exports.ID_TOKEN_INFO = void 0;
|
20 | var error_1 = require("../utils/error");
|
21 | var util = require("../utils/index");
|
22 | var validator = require("../utils/validator");
|
23 | var jwt_1 = require("../utils/jwt");
|
24 |
|
25 | var FIREBASE_AUDIENCE = 'https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit';
|
26 |
|
27 |
|
28 | var CLIENT_CERT_URL = 'https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com';
|
29 |
|
30 | var SESSION_COOKIE_CERT_URL = 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/publicKeys';
|
31 | var EMULATOR_VERIFIER = new jwt_1.EmulatorSignatureVerifier();
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 | exports.ID_TOKEN_INFO = {
|
38 | url: 'https://firebase.google.com/docs/auth/admin/verify-id-tokens',
|
39 | verifyApiName: 'verifyIdToken()',
|
40 | jwtName: 'Firebase ID token',
|
41 | shortName: 'ID token',
|
42 | expiredErrorCode: error_1.AuthClientErrorCode.ID_TOKEN_EXPIRED,
|
43 | };
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 | exports.SESSION_COOKIE_INFO = {
|
50 | url: 'https://firebase.google.com/docs/auth/admin/manage-cookies',
|
51 | verifyApiName: 'verifySessionCookie()',
|
52 | jwtName: 'Firebase session cookie',
|
53 | shortName: 'session cookie',
|
54 | expiredErrorCode: error_1.AuthClientErrorCode.SESSION_COOKIE_EXPIRED,
|
55 | };
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 | var FirebaseTokenVerifier = (function () {
|
62 | function FirebaseTokenVerifier(clientCertUrl, issuer, tokenInfo, app) {
|
63 | this.issuer = issuer;
|
64 | this.tokenInfo = tokenInfo;
|
65 | this.app = app;
|
66 | if (!validator.isURL(clientCertUrl)) {
|
67 | throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, 'The provided public client certificate URL is an invalid URL.');
|
68 | }
|
69 | else if (!validator.isURL(issuer)) {
|
70 | throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, 'The provided JWT issuer is an invalid URL.');
|
71 | }
|
72 | else if (!validator.isNonNullObject(tokenInfo)) {
|
73 | throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, 'The provided JWT information is not an object or null.');
|
74 | }
|
75 | else if (!validator.isURL(tokenInfo.url)) {
|
76 | throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, 'The provided JWT verification documentation URL is invalid.');
|
77 | }
|
78 | else if (!validator.isNonEmptyString(tokenInfo.verifyApiName)) {
|
79 | throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, 'The JWT verify API name must be a non-empty string.');
|
80 | }
|
81 | else if (!validator.isNonEmptyString(tokenInfo.jwtName)) {
|
82 | throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, 'The JWT public full name must be a non-empty string.');
|
83 | }
|
84 | else if (!validator.isNonEmptyString(tokenInfo.shortName)) {
|
85 | throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, 'The JWT public short name must be a non-empty string.');
|
86 | }
|
87 | else if (!validator.isNonNullObject(tokenInfo.expiredErrorCode) || !('code' in tokenInfo.expiredErrorCode)) {
|
88 | throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, 'The JWT expiration error code must be a non-null ErrorInfo object.');
|
89 | }
|
90 | this.shortNameArticle = tokenInfo.shortName.charAt(0).match(/[aeiou]/i) ? 'an' : 'a';
|
91 | this.signatureVerifier =
|
92 | jwt_1.PublicKeySignatureVerifier.withCertificateUrl(clientCertUrl, app.options.httpAgent);
|
93 |
|
94 | }
|
95 | |
96 |
|
97 |
|
98 |
|
99 |
|
100 |
|
101 |
|
102 | FirebaseTokenVerifier.prototype.verifyJWT = function (jwtToken, isEmulator) {
|
103 | var _this = this;
|
104 | if (isEmulator === void 0) { isEmulator = false; }
|
105 | if (!validator.isString(jwtToken)) {
|
106 | throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, "First argument to " + this.tokenInfo.verifyApiName + " must be a " + this.tokenInfo.jwtName + " string.");
|
107 | }
|
108 | return this.ensureProjectId()
|
109 | .then(function (projectId) {
|
110 | return _this.decodeAndVerify(jwtToken, projectId, isEmulator);
|
111 | })
|
112 | .then(function (decoded) {
|
113 | var decodedIdToken = decoded.payload;
|
114 | decodedIdToken.uid = decodedIdToken.sub;
|
115 | return decodedIdToken;
|
116 | });
|
117 | };
|
118 | FirebaseTokenVerifier.prototype.ensureProjectId = function () {
|
119 | var _this = this;
|
120 | return util.findProjectId(this.app)
|
121 | .then(function (projectId) {
|
122 | if (!validator.isNonEmptyString(projectId)) {
|
123 | throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_CREDENTIAL, 'Must initialize app with a cert credential or set your Firebase project ID as the ' +
|
124 | ("GOOGLE_CLOUD_PROJECT environment variable to call " + _this.tokenInfo.verifyApiName + "."));
|
125 | }
|
126 | return Promise.resolve(projectId);
|
127 | });
|
128 | };
|
129 | FirebaseTokenVerifier.prototype.decodeAndVerify = function (token, projectId, isEmulator) {
|
130 | var _this = this;
|
131 | return this.safeDecode(token)
|
132 | .then(function (decodedToken) {
|
133 | _this.verifyContent(decodedToken, projectId, isEmulator);
|
134 | return _this.verifySignature(token, isEmulator)
|
135 | .then(function () { return decodedToken; });
|
136 | });
|
137 | };
|
138 | FirebaseTokenVerifier.prototype.safeDecode = function (jwtToken) {
|
139 | var _this = this;
|
140 | return jwt_1.decodeJwt(jwtToken)
|
141 | .catch(function (err) {
|
142 | if (err.code == jwt_1.JwtErrorCode.INVALID_ARGUMENT) {
|
143 | var verifyJwtTokenDocsMessage = " See " + _this.tokenInfo.url + " " +
|
144 | ("for details on how to retrieve " + _this.shortNameArticle + " " + _this.tokenInfo.shortName + ".");
|
145 | var errorMessage = "Decoding " + _this.tokenInfo.jwtName + " failed. Make sure you passed " +
|
146 | ("the entire string JWT which represents " + _this.shortNameArticle + " ") +
|
147 | (_this.tokenInfo.shortName + ".") + verifyJwtTokenDocsMessage;
|
148 | throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, errorMessage);
|
149 | }
|
150 | throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INTERNAL_ERROR, err.message);
|
151 | });
|
152 | };
|
153 | |
154 |
|
155 |
|
156 |
|
157 |
|
158 |
|
159 |
|
160 | FirebaseTokenVerifier.prototype.verifyContent = function (fullDecodedToken, projectId, isEmulator) {
|
161 | var header = fullDecodedToken && fullDecodedToken.header;
|
162 | var payload = fullDecodedToken && fullDecodedToken.payload;
|
163 | var projectIdMatchMessage = " Make sure the " + this.tokenInfo.shortName + " comes from the same " +
|
164 | 'Firebase project as the service account used to authenticate this SDK.';
|
165 | var verifyJwtTokenDocsMessage = " See " + this.tokenInfo.url + " " +
|
166 | ("for details on how to retrieve " + this.shortNameArticle + " " + this.tokenInfo.shortName + ".");
|
167 | var errorMessage;
|
168 | if (!isEmulator && typeof header.kid === 'undefined') {
|
169 | var isCustomToken = (payload.aud === FIREBASE_AUDIENCE);
|
170 | var isLegacyCustomToken = (header.alg === 'HS256' && payload.v === 0 && 'd' in payload && 'uid' in payload.d);
|
171 | if (isCustomToken) {
|
172 | errorMessage = this.tokenInfo.verifyApiName + " expects " + this.shortNameArticle + " " +
|
173 | (this.tokenInfo.shortName + ", but was given a custom token.");
|
174 | }
|
175 | else if (isLegacyCustomToken) {
|
176 | errorMessage = this.tokenInfo.verifyApiName + " expects " + this.shortNameArticle + " " +
|
177 | (this.tokenInfo.shortName + ", but was given a legacy custom token.");
|
178 | }
|
179 | else {
|
180 | errorMessage = 'Firebase ID token has no "kid" claim.';
|
181 | }
|
182 | errorMessage += verifyJwtTokenDocsMessage;
|
183 | }
|
184 | else if (!isEmulator && header.alg !== jwt_1.ALGORITHM_RS256) {
|
185 | errorMessage = this.tokenInfo.jwtName + " has incorrect algorithm. Expected \"" + jwt_1.ALGORITHM_RS256 + '" but got ' +
|
186 | '"' + header.alg + '".' + verifyJwtTokenDocsMessage;
|
187 | }
|
188 | else if (payload.aud !== projectId) {
|
189 | errorMessage = this.tokenInfo.jwtName + " has incorrect \"aud\" (audience) claim. Expected \"" +
|
190 | projectId + '" but got "' + payload.aud + '".' + projectIdMatchMessage +
|
191 | verifyJwtTokenDocsMessage;
|
192 | }
|
193 | else if (payload.iss !== this.issuer + projectId) {
|
194 | errorMessage = this.tokenInfo.jwtName + " has incorrect \"iss\" (issuer) claim. Expected " +
|
195 | ("\"" + this.issuer) + projectId + '" but got "' +
|
196 | payload.iss + '".' + projectIdMatchMessage + verifyJwtTokenDocsMessage;
|
197 | }
|
198 | else if (typeof payload.sub !== 'string') {
|
199 | errorMessage = this.tokenInfo.jwtName + " has no \"sub\" (subject) claim." + verifyJwtTokenDocsMessage;
|
200 | }
|
201 | else if (payload.sub === '') {
|
202 | errorMessage = this.tokenInfo.jwtName + " has an empty string \"sub\" (subject) claim." + verifyJwtTokenDocsMessage;
|
203 | }
|
204 | else if (payload.sub.length > 128) {
|
205 | errorMessage = this.tokenInfo.jwtName + " has \"sub\" (subject) claim longer than 128 characters." +
|
206 | verifyJwtTokenDocsMessage;
|
207 | }
|
208 | if (errorMessage) {
|
209 | throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, errorMessage);
|
210 | }
|
211 | };
|
212 | FirebaseTokenVerifier.prototype.verifySignature = function (jwtToken, isEmulator) {
|
213 | var _this = this;
|
214 | var verifier = isEmulator ? EMULATOR_VERIFIER : this.signatureVerifier;
|
215 | return verifier.verify(jwtToken)
|
216 | .catch(function (error) {
|
217 | throw _this.mapJwtErrorToAuthError(error);
|
218 | });
|
219 | };
|
220 | |
221 |
|
222 |
|
223 |
|
224 |
|
225 |
|
226 | FirebaseTokenVerifier.prototype.mapJwtErrorToAuthError = function (error) {
|
227 | var verifyJwtTokenDocsMessage = " See " + this.tokenInfo.url + " " +
|
228 | ("for details on how to retrieve " + this.shortNameArticle + " " + this.tokenInfo.shortName + ".");
|
229 | if (error.code === jwt_1.JwtErrorCode.TOKEN_EXPIRED) {
|
230 | var errorMessage = this.tokenInfo.jwtName + " has expired. Get a fresh " + this.tokenInfo.shortName +
|
231 | (" from your client app and try again (auth/" + this.tokenInfo.expiredErrorCode.code + ").") +
|
232 | verifyJwtTokenDocsMessage;
|
233 | return new error_1.FirebaseAuthError(this.tokenInfo.expiredErrorCode, errorMessage);
|
234 | }
|
235 | else if (error.code === jwt_1.JwtErrorCode.INVALID_SIGNATURE) {
|
236 | var errorMessage = this.tokenInfo.jwtName + " has invalid signature." + verifyJwtTokenDocsMessage;
|
237 | return new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, errorMessage);
|
238 | }
|
239 | else if (error.code === jwt_1.JwtErrorCode.NO_MATCHING_KID) {
|
240 | var errorMessage = this.tokenInfo.jwtName + " has \"kid\" claim which does not " +
|
241 | ("correspond to a known public key. Most likely the " + this.tokenInfo.shortName + " ") +
|
242 | 'is expired, so get a fresh token from your client app and try again.';
|
243 | return new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, errorMessage);
|
244 | }
|
245 | return new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, error.message);
|
246 | };
|
247 | return FirebaseTokenVerifier;
|
248 | }());
|
249 | exports.FirebaseTokenVerifier = FirebaseTokenVerifier;
|
250 |
|
251 |
|
252 |
|
253 |
|
254 |
|
255 |
|
256 |
|
257 | function createIdTokenVerifier(app) {
|
258 | return new FirebaseTokenVerifier(CLIENT_CERT_URL, 'https://securetoken.google.com/', exports.ID_TOKEN_INFO, app);
|
259 | }
|
260 | exports.createIdTokenVerifier = createIdTokenVerifier;
|
261 |
|
262 |
|
263 |
|
264 |
|
265 |
|
266 |
|
267 |
|
268 | function createSessionCookieVerifier(app) {
|
269 | return new FirebaseTokenVerifier(SESSION_COOKIE_CERT_URL, 'https://session.firebase.google.com/', exports.SESSION_COOKIE_INFO, app);
|
270 | }
|
271 | exports.createSessionCookieVerifier = createSessionCookieVerifier;
|