UNPKG

10.5 kBJavaScriptView Raw
1"use strict";
2// Copyright 2013 Google LLC
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15Object.defineProperty(exports, "__esModule", { value: true });
16exports.JWT = void 0;
17const gtoken_1 = require("gtoken");
18const jwtaccess_1 = require("./jwtaccess");
19const oauth2client_1 = require("./oauth2client");
20const authclient_1 = require("./authclient");
21class JWT extends oauth2client_1.OAuth2Client {
22 constructor(optionsOrEmail, keyFile, key, scopes, subject, keyId) {
23 const opts = optionsOrEmail && typeof optionsOrEmail === 'object'
24 ? optionsOrEmail
25 : { email: optionsOrEmail, keyFile, key, keyId, scopes, subject };
26 super(opts);
27 this.email = opts.email;
28 this.keyFile = opts.keyFile;
29 this.key = opts.key;
30 this.keyId = opts.keyId;
31 this.scopes = opts.scopes;
32 this.subject = opts.subject;
33 this.additionalClaims = opts.additionalClaims;
34 // Start with an expired refresh token, which will automatically be
35 // refreshed before the first API call is made.
36 this.credentials = { refresh_token: 'jwt-placeholder', expiry_date: 1 };
37 }
38 /**
39 * Creates a copy of the credential with the specified scopes.
40 * @param scopes List of requested scopes or a single scope.
41 * @return The cloned instance.
42 */
43 createScoped(scopes) {
44 const jwt = new JWT(this);
45 jwt.scopes = scopes;
46 return jwt;
47 }
48 /**
49 * Obtains the metadata to be sent with the request.
50 *
51 * @param url the URI being authorized.
52 */
53 async getRequestMetadataAsync(url) {
54 url = this.defaultServicePath ? `https://${this.defaultServicePath}/` : url;
55 const useSelfSignedJWT = (!this.hasUserScopes() && url) ||
56 (this.useJWTAccessWithScope && this.hasAnyScopes()) ||
57 this.universeDomain !== authclient_1.DEFAULT_UNIVERSE;
58 if (this.subject && this.universeDomain !== authclient_1.DEFAULT_UNIVERSE) {
59 throw new RangeError(`Service Account user is configured for the credential. Domain-wide delegation is not supported in universes other than ${authclient_1.DEFAULT_UNIVERSE}`);
60 }
61 if (!this.apiKey && useSelfSignedJWT) {
62 if (this.additionalClaims &&
63 this.additionalClaims.target_audience) {
64 const { tokens } = await this.refreshToken();
65 return {
66 headers: this.addSharedMetadataHeaders({
67 Authorization: `Bearer ${tokens.id_token}`,
68 }),
69 };
70 }
71 else {
72 // no scopes have been set, but a uri has been provided. Use JWTAccess
73 // credentials.
74 if (!this.access) {
75 this.access = new jwtaccess_1.JWTAccess(this.email, this.key, this.keyId, this.eagerRefreshThresholdMillis);
76 }
77 let scopes;
78 if (this.hasUserScopes()) {
79 scopes = this.scopes;
80 }
81 else if (!url) {
82 scopes = this.defaultScopes;
83 }
84 const useScopes = this.useJWTAccessWithScope ||
85 this.universeDomain !== authclient_1.DEFAULT_UNIVERSE;
86 const headers = await this.access.getRequestHeaders(url !== null && url !== void 0 ? url : undefined, this.additionalClaims,
87 // Scopes take precedent over audience for signing,
88 // so we only provide them if `useJWTAccessWithScope` is on or
89 // if we are in a non-default universe
90 useScopes ? scopes : undefined);
91 return { headers: this.addSharedMetadataHeaders(headers) };
92 }
93 }
94 else if (this.hasAnyScopes() || this.apiKey) {
95 return super.getRequestMetadataAsync(url);
96 }
97 else {
98 // If no audience, apiKey, or scopes are provided, we should not attempt
99 // to populate any headers:
100 return { headers: {} };
101 }
102 }
103 /**
104 * Fetches an ID token.
105 * @param targetAudience the audience for the fetched ID token.
106 */
107 async fetchIdToken(targetAudience) {
108 // Create a new gToken for fetching an ID token
109 const gtoken = new gtoken_1.GoogleToken({
110 iss: this.email,
111 sub: this.subject,
112 scope: this.scopes || this.defaultScopes,
113 keyFile: this.keyFile,
114 key: this.key,
115 additionalClaims: { target_audience: targetAudience },
116 transporter: this.transporter,
117 });
118 await gtoken.getToken({
119 forceRefresh: true,
120 });
121 if (!gtoken.idToken) {
122 throw new Error('Unknown error: Failed to fetch ID token');
123 }
124 return gtoken.idToken;
125 }
126 /**
127 * Determine if there are currently scopes available.
128 */
129 hasUserScopes() {
130 if (!this.scopes) {
131 return false;
132 }
133 return this.scopes.length > 0;
134 }
135 /**
136 * Are there any default or user scopes defined.
137 */
138 hasAnyScopes() {
139 if (this.scopes && this.scopes.length > 0)
140 return true;
141 if (this.defaultScopes && this.defaultScopes.length > 0)
142 return true;
143 return false;
144 }
145 authorize(callback) {
146 if (callback) {
147 this.authorizeAsync().then(r => callback(null, r), callback);
148 }
149 else {
150 return this.authorizeAsync();
151 }
152 }
153 async authorizeAsync() {
154 const result = await this.refreshToken();
155 if (!result) {
156 throw new Error('No result returned');
157 }
158 this.credentials = result.tokens;
159 this.credentials.refresh_token = 'jwt-placeholder';
160 this.key = this.gtoken.key;
161 this.email = this.gtoken.iss;
162 return result.tokens;
163 }
164 /**
165 * Refreshes the access token.
166 * @param refreshToken ignored
167 * @private
168 */
169 async refreshTokenNoCache(
170 // eslint-disable-next-line @typescript-eslint/no-unused-vars
171 refreshToken) {
172 const gtoken = this.createGToken();
173 const token = await gtoken.getToken({
174 forceRefresh: this.isTokenExpiring(),
175 });
176 const tokens = {
177 access_token: token.access_token,
178 token_type: 'Bearer',
179 expiry_date: gtoken.expiresAt,
180 id_token: gtoken.idToken,
181 };
182 this.emit('tokens', tokens);
183 return { res: null, tokens };
184 }
185 /**
186 * Create a gToken if it doesn't already exist.
187 */
188 createGToken() {
189 if (!this.gtoken) {
190 this.gtoken = new gtoken_1.GoogleToken({
191 iss: this.email,
192 sub: this.subject,
193 scope: this.scopes || this.defaultScopes,
194 keyFile: this.keyFile,
195 key: this.key,
196 additionalClaims: this.additionalClaims,
197 transporter: this.transporter,
198 });
199 }
200 return this.gtoken;
201 }
202 /**
203 * Create a JWT credentials instance using the given input options.
204 * @param json The input object.
205 */
206 fromJSON(json) {
207 if (!json) {
208 throw new Error('Must pass in a JSON object containing the service account auth settings.');
209 }
210 if (!json.client_email) {
211 throw new Error('The incoming JSON object does not contain a client_email field');
212 }
213 if (!json.private_key) {
214 throw new Error('The incoming JSON object does not contain a private_key field');
215 }
216 // Extract the relevant information from the json key file.
217 this.email = json.client_email;
218 this.key = json.private_key;
219 this.keyId = json.private_key_id;
220 this.projectId = json.project_id;
221 this.quotaProjectId = json.quota_project_id;
222 this.universeDomain = json.universe_domain || this.universeDomain;
223 }
224 fromStream(inputStream, callback) {
225 if (callback) {
226 this.fromStreamAsync(inputStream).then(() => callback(), callback);
227 }
228 else {
229 return this.fromStreamAsync(inputStream);
230 }
231 }
232 fromStreamAsync(inputStream) {
233 return new Promise((resolve, reject) => {
234 if (!inputStream) {
235 throw new Error('Must pass in a stream containing the service account auth settings.');
236 }
237 let s = '';
238 inputStream
239 .setEncoding('utf8')
240 .on('error', reject)
241 .on('data', chunk => (s += chunk))
242 .on('end', () => {
243 try {
244 const data = JSON.parse(s);
245 this.fromJSON(data);
246 resolve();
247 }
248 catch (e) {
249 reject(e);
250 }
251 });
252 });
253 }
254 /**
255 * Creates a JWT credentials instance using an API Key for authentication.
256 * @param apiKey The API Key in string form.
257 */
258 fromAPIKey(apiKey) {
259 if (typeof apiKey !== 'string') {
260 throw new Error('Must provide an API Key string.');
261 }
262 this.apiKey = apiKey;
263 }
264 /**
265 * Using the key or keyFile on the JWT client, obtain an object that contains
266 * the key and the client email.
267 */
268 async getCredentials() {
269 if (this.key) {
270 return { private_key: this.key, client_email: this.email };
271 }
272 else if (this.keyFile) {
273 const gtoken = this.createGToken();
274 const creds = await gtoken.getCredentials(this.keyFile);
275 return { private_key: creds.privateKey, client_email: creds.clientEmail };
276 }
277 throw new Error('A key or a keyFile must be provided to getCredentials.');
278 }
279}
280exports.JWT = JWT;