--- /dev/null
+"use strict";
+/**
+ * Copyright 2019 Google LLC. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
+ return new (P || (P = Promise))(function (resolve, reject) {
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+ function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
+ });
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+const querystring = require("querystring");
+const stream = require("stream");
+const crypto_1 = require("../crypto/crypto");
+const isbrowser_1 = require("../isbrowser");
+const messages = require("../messages");
+const authclient_1 = require("./authclient");
+const loginticket_1 = require("./loginticket");
+var CodeChallengeMethod;
+(function (CodeChallengeMethod) {
+ CodeChallengeMethod["Plain"] = "plain";
+ CodeChallengeMethod["S256"] = "S256";
+})(CodeChallengeMethod = exports.CodeChallengeMethod || (exports.CodeChallengeMethod = {}));
+var CertificateFormat;
+(function (CertificateFormat) {
+ CertificateFormat["PEM"] = "PEM";
+ CertificateFormat["JWK"] = "JWK";
+})(CertificateFormat = exports.CertificateFormat || (exports.CertificateFormat = {}));
+class OAuth2Client extends authclient_1.AuthClient {
+ constructor(optionsOrClientId, clientSecret, redirectUri) {
+ super();
+ this.certificateCache = {};
+ this.certificateExpiry = null;
+ this.certificateCacheFormat = CertificateFormat.PEM;
+ this.refreshTokenPromises = new Map();
+ const opts = (optionsOrClientId && typeof optionsOrClientId === 'object') ?
+ optionsOrClientId :
+ { clientId: optionsOrClientId, clientSecret, redirectUri };
+ this._clientId = opts.clientId;
+ this._clientSecret = opts.clientSecret;
+ this.redirectUri = opts.redirectUri;
+ this.eagerRefreshThresholdMillis =
+ opts.eagerRefreshThresholdMillis || 5 * 60 * 1000;
+ }
+ /**
+ * Generates URL for consent page landing.
+ * @param opts Options.
+ * @return URL to consent page.
+ */
+ generateAuthUrl(opts = {}) {
+ if (opts.code_challenge_method && !opts.code_challenge) {
+ throw new Error('If a code_challenge_method is provided, code_challenge must be included.');
+ }
+ opts.response_type = opts.response_type || 'code';
+ opts.client_id = opts.client_id || this._clientId;
+ opts.redirect_uri = opts.redirect_uri || this.redirectUri;
+ // Allow scopes to be passed either as array or a string
+ if (opts.scope instanceof Array) {
+ opts.scope = opts.scope.join(' ');
+ }
+ const rootUrl = OAuth2Client.GOOGLE_OAUTH2_AUTH_BASE_URL_;
+ return rootUrl + '?' + querystring.stringify(opts);
+ }
+ generateCodeVerifier() {
+ // To make the code compatible with browser SubtleCrypto we need to make
+ // this method async.
+ throw new Error('generateCodeVerifier is removed, please use generateCodeVerifierAsync instead.');
+ }
+ /**
+ * Convenience method to automatically generate a code_verifier, and it's
+ * resulting SHA256. If used, this must be paired with a S256
+ * code_challenge_method.
+ */
+ generateCodeVerifierAsync() {
+ return __awaiter(this, void 0, void 0, function* () {
+ // base64 encoding uses 6 bits per character, and we want to generate128
+ // characters. 6*128/8 = 96.
+ const crypto = crypto_1.createCrypto();
+ const randomString = crypto.randomBytesBase64(96);
+ // The valid characters in the code_verifier are [A-Z]/[a-z]/[0-9]/
+ // "-"/"."/"_"/"~". Base64 encoded strings are pretty close, so we're just
+ // swapping out a few chars.
+ const codeVerifier = randomString.replace(/\+/g, '~').replace(/=/g, '_').replace(/\//g, '-');
+ // Generate the base64 encoded SHA256
+ const unencodedCodeChallenge = yield crypto.sha256DigestBase64(codeVerifier);
+ // We need to use base64UrlEncoding instead of standard base64
+ const codeChallenge = unencodedCodeChallenge.split('=')[0]
+ .replace(/\+/g, '-')
+ .replace(/\//g, '_');
+ return { codeVerifier, codeChallenge };
+ });
+ }
+ getToken(codeOrOptions, callback) {
+ const options = (typeof codeOrOptions === 'string') ?
+ { code: codeOrOptions } :
+ codeOrOptions;
+ if (callback) {
+ this.getTokenAsync(options).then(r => callback(null, r.tokens, r.res), e => callback(e, null, e.response));
+ }
+ else {
+ return this.getTokenAsync(options);
+ }
+ }
+ getTokenAsync(options) {
+ return __awaiter(this, void 0, void 0, function* () {
+ const url = OAuth2Client.GOOGLE_OAUTH2_TOKEN_URL_;
+ const values = {
+ code: options.code,
+ client_id: options.client_id || this._clientId,
+ client_secret: this._clientSecret,
+ redirect_uri: options.redirect_uri || this.redirectUri,
+ grant_type: 'authorization_code',
+ code_verifier: options.codeVerifier
+ };
+ const res = yield this.transporter.request({
+ method: 'POST',
+ url,
+ data: querystring.stringify(values),
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
+ });
+ const tokens = res.data;
+ if (res.data && res.data.expires_in) {
+ tokens.expiry_date =
+ ((new Date()).getTime() + (res.data.expires_in * 1000));
+ delete tokens.expires_in;
+ }
+ this.emit('tokens', tokens);
+ return { tokens, res };
+ });
+ }
+ /**
+ * Refreshes the access token.
+ * @param refresh_token Existing refresh token.
+ * @private
+ */
+ refreshToken(refreshToken) {
+ return __awaiter(this, void 0, void 0, function* () {
+ if (!refreshToken) {
+ return this.refreshTokenNoCache(refreshToken);
+ }
+ // If a request to refresh using the same token has started,
+ // return the same promise.
+ if (this.refreshTokenPromises.has(refreshToken)) {
+ return this.refreshTokenPromises.get(refreshToken);
+ }
+ const p = this.refreshTokenNoCache(refreshToken)
+ .then(r => {
+ this.refreshTokenPromises.delete(refreshToken);
+ return r;
+ }, e => {
+ this.refreshTokenPromises.delete(refreshToken);
+ throw e;
+ });
+ this.refreshTokenPromises.set(refreshToken, p);
+ return p;
+ });
+ }
+ refreshTokenNoCache(refreshToken) {
+ return __awaiter(this, void 0, void 0, function* () {
+ const url = OAuth2Client.GOOGLE_OAUTH2_TOKEN_URL_;
+ const data = {
+ refresh_token: refreshToken,
+ client_id: this._clientId,
+ client_secret: this._clientSecret,
+ grant_type: 'refresh_token'
+ };
+ // request for new token
+ const res = yield this.transporter.request({
+ method: 'POST',
+ url,
+ data: querystring.stringify(data),
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
+ });
+ const tokens = res.data;
+ // TODO: de-duplicate this code from a few spots
+ if (res.data && res.data.expires_in) {
+ tokens.expiry_date =
+ ((new Date()).getTime() + (res.data.expires_in * 1000));
+ delete tokens.expires_in;
+ }
+ this.emit('tokens', tokens);
+ return { tokens, res };
+ });
+ }
+ refreshAccessToken(callback) {
+ messages.warn(messages.REFRESH_ACCESS_TOKEN_DEPRECATED);
+ if (callback) {
+ this.refreshAccessTokenAsync().then(r => callback(null, r.credentials, r.res), callback);
+ }
+ else {
+ return this.refreshAccessTokenAsync();
+ }
+ }
+ refreshAccessTokenAsync() {
+ return __awaiter(this, void 0, void 0, function* () {
+ if (!this.credentials.refresh_token) {
+ throw new Error('No refresh token is set.');
+ }
+ const r = yield this.refreshToken(this.credentials.refresh_token);
+ const tokens = r.tokens;
+ tokens.refresh_token = this.credentials.refresh_token;
+ this.credentials = tokens;
+ return { credentials: this.credentials, res: r.res };
+ });
+ }
+ getAccessToken(callback) {
+ if (callback) {
+ this.getAccessTokenAsync().then(r => callback(null, r.token, r.res), callback);
+ }
+ else {
+ return this.getAccessTokenAsync();
+ }
+ }
+ getAccessTokenAsync() {
+ return __awaiter(this, void 0, void 0, function* () {
+ const shouldRefresh = !this.credentials.access_token || this.isTokenExpiring();
+ if (shouldRefresh) {
+ if (!this.credentials.refresh_token) {
+ throw new Error('No refresh token is set.');
+ }
+ const r = yield this.refreshAccessTokenAsync();
+ if (!r.credentials || (r.credentials && !r.credentials.access_token)) {
+ throw new Error('Could not refresh access token.');
+ }
+ return { token: r.credentials.access_token, res: r.res };
+ }
+ else {
+ return { token: this.credentials.access_token };
+ }
+ });
+ }
+ getRequestMetadata(url, callback) {
+ messages.warn(messages.OAUTH_GET_REQUEST_METADATA_DEPRECATED);
+ if (callback) {
+ this.getRequestMetadataAsync(url).then(r => callback(null, r.headers, r.res), callback);
+ }
+ else {
+ return this.getRequestMetadataAsync();
+ }
+ }
+ /**
+ * The main authentication interface. It takes an optional url which when
+ * present is the endpoint being accessed, and returns a Promise which
+ * resolves with authorization header fields.
+ *
+ * In OAuth2Client, the result has the form:
+ * { Authorization: 'Bearer <access_token_value>' }
+ * @param url The optional url being authorized
+ */
+ getRequestHeaders(url) {
+ return __awaiter(this, void 0, void 0, function* () {
+ const res = yield this.getRequestMetadataAsync(url);
+ return res.headers;
+ });
+ }
+ getRequestMetadataAsync(url) {
+ return __awaiter(this, void 0, void 0, function* () {
+ const thisCreds = this.credentials;
+ if (!thisCreds.access_token && !thisCreds.refresh_token && !this.apiKey) {
+ throw new Error('No access, refresh token or API key is set.');
+ }
+ if (thisCreds.access_token && !this.isTokenExpiring()) {
+ thisCreds.token_type = thisCreds.token_type || 'Bearer';
+ const headers = {
+ Authorization: thisCreds.token_type + ' ' + thisCreds.access_token
+ };
+ return { headers };
+ }
+ if (this.apiKey) {
+ return { headers: {} };
+ }
+ let r = null;
+ let tokens = null;
+ try {
+ r = yield this.refreshToken(thisCreds.refresh_token);
+ tokens = r.tokens;
+ }
+ catch (err) {
+ const e = err;
+ if (e.response &&
+ (e.response.status === 403 || e.response.status === 404)) {
+ e.message = 'Could not refresh access token.';
+ }
+ throw e;
+ }
+ const credentials = this.credentials;
+ credentials.token_type = credentials.token_type || 'Bearer';
+ tokens.refresh_token = credentials.refresh_token;
+ this.credentials = tokens;
+ const headers = {
+ Authorization: credentials.token_type + ' ' + tokens.access_token
+ };
+ return { headers, res: r.res };
+ });
+ }
+ /**
+ * Generates an URL to revoke the given token.
+ * @param token The existing token to be revoked.
+ */
+ static getRevokeTokenUrl(token) {
+ const parameters = querystring.stringify({ token });
+ return `${OAuth2Client.GOOGLE_OAUTH2_REVOKE_URL_}?${parameters}`;
+ }
+ revokeToken(token, callback) {
+ const opts = {
+ url: OAuth2Client.getRevokeTokenUrl(token),
+ method: 'POST'
+ };
+ if (callback) {
+ this.transporter.request(opts).then(r => callback(null, r), callback);
+ }
+ else {
+ return this.transporter.request(opts);
+ }
+ }
+ revokeCredentials(callback) {
+ if (callback) {
+ this.revokeCredentialsAsync().then(res => callback(null, res), callback);
+ }
+ else {
+ return this.revokeCredentialsAsync();
+ }
+ }
+ revokeCredentialsAsync() {
+ return __awaiter(this, void 0, void 0, function* () {
+ const token = this.credentials.access_token;
+ this.credentials = {};
+ if (token) {
+ return this.revokeToken(token);
+ }
+ else {
+ throw new Error('No access token to revoke.');
+ }
+ });
+ }
+ request(opts, callback) {
+ if (callback) {
+ this.requestAsync(opts).then(r => callback(null, r), e => {
+ return callback(e, e.response);
+ });
+ }
+ else {
+ return this.requestAsync(opts);
+ }
+ }
+ requestAsync(opts, retry = false) {
+ return __awaiter(this, void 0, void 0, function* () {
+ let r2;
+ try {
+ const r = yield this.getRequestMetadataAsync(opts.url);
+ if (r.headers && r.headers.Authorization) {
+ opts.headers = opts.headers || {};
+ opts.headers.Authorization = r.headers.Authorization;
+ }
+ if (this.apiKey) {
+ opts.params = Object.assign(opts.params || {}, { key: this.apiKey });
+ }
+ r2 = yield this.transporter.request(opts);
+ }
+ catch (e) {
+ const res = e.response;
+ if (res) {
+ const statusCode = res.status;
+ // Retry the request for metadata if the following criteria are true:
+ // - We haven't already retried. It only makes sense to retry once.
+ // - The response was a 401 or a 403
+ // - The request didn't send a readableStream
+ // - An access_token and refresh_token were available, but no
+ // expiry_date was availabe. This can happen when developers stash
+ // the access_token and refresh_token for later use, but the
+ // access_token fails on the first try because it's expired.
+ const mayRequireRefresh = this.credentials &&
+ this.credentials.access_token && this.credentials.refresh_token &&
+ !this.credentials.expiry_date;
+ const isReadableStream = res.config.data instanceof stream.Readable;
+ const isAuthErr = statusCode === 401 || statusCode === 403;
+ if (!retry && isAuthErr && !isReadableStream && mayRequireRefresh) {
+ yield this.refreshAccessTokenAsync();
+ return this.requestAsync(opts, true);
+ }
+ }
+ throw e;
+ }
+ return r2;
+ });
+ }
+ verifyIdToken(options, callback) {
+ // This function used to accept two arguments instead of an options object.
+ // Check the types to help users upgrade with less pain.
+ // This check can be removed after a 2.0 release.
+ if (callback && typeof callback !== 'function') {
+ throw new Error('This method accepts an options object as the first parameter, which includes the idToken, audience, and maxExpiry.');
+ }
+ if (callback) {
+ this.verifyIdTokenAsync(options).then(r => callback(null, r), callback);
+ }
+ else {
+ return this.verifyIdTokenAsync(options);
+ }
+ }
+ verifyIdTokenAsync(options) {
+ return __awaiter(this, void 0, void 0, function* () {
+ if (!options.idToken) {
+ throw new Error('The verifyIdToken method requires an ID Token');
+ }
+ const response = yield this.getFederatedSignonCertsAsync();
+ const login = yield this.verifySignedJwtWithCertsAsync(options.idToken, response.certs, options.audience, OAuth2Client.ISSUERS_, options.maxExpiry);
+ return login;
+ });
+ }
+ /**
+ * Obtains information about the provisioned access token. Especially useful
+ * if you want to check the scopes that were provisioned to a given token.
+ *
+ * @param accessToken Required. The Access Token for which you want to get
+ * user info.
+ */
+ getTokenInfo(accessToken) {
+ return __awaiter(this, void 0, void 0, function* () {
+ const { data } = yield this.transporter.request({
+ method: 'GET',
+ url: OAuth2Client.GOOGLE_TOKEN_INFO_URL,
+ params: { access_token: accessToken }
+ });
+ const info = Object.assign({
+ expiry_date: ((new Date()).getTime() + (data.expires_in * 1000)),
+ scopes: data.scope.split(' ')
+ }, data);
+ delete info.expires_in;
+ delete info.scope;
+ return info;
+ });
+ }
+ getFederatedSignonCerts(callback) {
+ if (callback) {
+ this.getFederatedSignonCertsAsync().then(r => callback(null, r.certs, r.res), callback);
+ }
+ else {
+ return this.getFederatedSignonCertsAsync();
+ }
+ }
+ getFederatedSignonCertsAsync() {
+ return __awaiter(this, void 0, void 0, function* () {
+ const nowTime = (new Date()).getTime();
+ const format = isbrowser_1.isBrowser() ? CertificateFormat.JWK : CertificateFormat.PEM;
+ if (this.certificateExpiry &&
+ (nowTime < this.certificateExpiry.getTime()) &&
+ this.certificateCacheFormat === format) {
+ return { certs: this.certificateCache, format };
+ }
+ let res;
+ let url;
+ switch (format) {
+ case CertificateFormat.PEM:
+ url = OAuth2Client.GOOGLE_OAUTH2_FEDERATED_SIGNON_PEM_CERTS_URL_;
+ break;
+ case CertificateFormat.JWK:
+ url = OAuth2Client.GOOGLE_OAUTH2_FEDERATED_SIGNON_JWK_CERTS_URL_;
+ break;
+ default:
+ throw new Error(`Unsupported certificate format ${format}`);
+ }
+ try {
+ res = yield this.transporter.request({ url });
+ }
+ catch (e) {
+ throw new Error('Failed to retrieve verification certificates: ' + e);
+ }
+ const cacheControl = res ? res.headers['cache-control'] : undefined;
+ let cacheAge = -1;
+ if (cacheControl) {
+ const pattern = new RegExp('max-age=([0-9]*)');
+ const regexResult = pattern.exec(cacheControl);
+ if (regexResult && regexResult.length === 2) {
+ // Cache results with max-age (in seconds)
+ cacheAge = Number(regexResult[1]) * 1000; // milliseconds
+ }
+ }
+ let certificates = {};
+ switch (format) {
+ case CertificateFormat.PEM:
+ certificates = res.data;
+ break;
+ case CertificateFormat.JWK:
+ for (const key of res.data.keys) {
+ certificates[key.kid] = key;
+ }
+ break;
+ default:
+ throw new Error(`Unsupported certificate format ${format}`);
+ }
+ const now = new Date();
+ this.certificateExpiry =
+ cacheAge === -1 ? null : new Date(now.getTime() + cacheAge);
+ this.certificateCache = certificates;
+ this.certificateCacheFormat = format;
+ return { certs: certificates, format, res };
+ });
+ }
+ verifySignedJwtWithCerts() {
+ // To make the code compatible with browser SubtleCrypto we need to make
+ // this method async.
+ throw new Error('verifySignedJwtWithCerts is removed, please use verifySignedJwtWithCertsAsync instead.');
+ }
+ /**
+ * Verify the id token is signed with the correct certificate
+ * and is from the correct audience.
+ * @param jwt The jwt to verify (The ID Token in this case).
+ * @param certs The array of certs to test the jwt against.
+ * @param requiredAudience The audience to test the jwt against.
+ * @param issuers The allowed issuers of the jwt (Optional).
+ * @param maxExpiry The max expiry the certificate can be (Optional).
+ * @return Returns a promise resolving to LoginTicket on verification.
+ */
+ verifySignedJwtWithCertsAsync(jwt, certs, requiredAudience, issuers, maxExpiry) {
+ return __awaiter(this, void 0, void 0, function* () {
+ const crypto = crypto_1.createCrypto();
+ if (!maxExpiry) {
+ maxExpiry = OAuth2Client.MAX_TOKEN_LIFETIME_SECS_;
+ }
+ const segments = jwt.split('.');
+ if (segments.length !== 3) {
+ throw new Error('Wrong number of segments in token: ' + jwt);
+ }
+ const signed = segments[0] + '.' + segments[1];
+ const signature = segments[2];
+ let envelope;
+ let payload;
+ try {
+ envelope = JSON.parse(crypto.decodeBase64StringUtf8(segments[0]));
+ }
+ catch (err) {
+ throw new Error('Can\'t parse token envelope: ' + segments[0]);
+ }
+ if (!envelope) {
+ throw new Error('Can\'t parse token envelope: ' + segments[0]);
+ }
+ try {
+ payload = JSON.parse(crypto.decodeBase64StringUtf8(segments[1]));
+ }
+ catch (err) {
+ throw new Error('Can\'t parse token payload: ' + segments[0]);
+ }
+ if (!payload) {
+ throw new Error('Can\'t parse token payload: ' + segments[1]);
+ }
+ if (!certs.hasOwnProperty(envelope.kid)) {
+ // If this is not present, then there's no reason to attempt verification
+ throw new Error('No pem found for envelope: ' + JSON.stringify(envelope));
+ }
+ const cert = certs[envelope.kid];
+ const verified = yield crypto.verify(cert, signed, signature);
+ if (!verified) {
+ throw new Error('Invalid token signature: ' + jwt);
+ }
+ if (!payload.iat) {
+ throw new Error('No issue time in token: ' + JSON.stringify(payload));
+ }
+ if (!payload.exp) {
+ throw new Error('No expiration time in token: ' + JSON.stringify(payload));
+ }
+ const iat = Number(payload.iat);
+ if (isNaN(iat))
+ throw new Error('iat field using invalid format');
+ const exp = Number(payload.exp);
+ if (isNaN(exp))
+ throw new Error('exp field using invalid format');
+ const now = new Date().getTime() / 1000;
+ if (exp >= now + maxExpiry) {
+ throw new Error('Expiration time too far in future: ' + JSON.stringify(payload));
+ }
+ const earliest = iat - OAuth2Client.CLOCK_SKEW_SECS_;
+ const latest = exp + OAuth2Client.CLOCK_SKEW_SECS_;
+ if (now < earliest) {
+ throw new Error('Token used too early, ' + now + ' < ' + earliest + ': ' +
+ JSON.stringify(payload));
+ }
+ if (now > latest) {
+ throw new Error('Token used too late, ' + now + ' > ' + latest + ': ' +
+ JSON.stringify(payload));
+ }
+ if (issuers && issuers.indexOf(payload.iss) < 0) {
+ throw new Error('Invalid issuer, expected one of [' + issuers + '], but got ' +
+ payload.iss);
+ }
+ // Check the audience matches if we have one
+ if (typeof requiredAudience !== 'undefined' && requiredAudience !== null) {
+ const aud = payload.aud;
+ let audVerified = false;
+ // If the requiredAudience is an array, check if it contains token
+ // audience
+ if (requiredAudience.constructor === Array) {
+ audVerified = (requiredAudience.indexOf(aud) > -1);
+ }
+ else {
+ audVerified = (aud === requiredAudience);
+ }
+ if (!audVerified) {
+ throw new Error('Wrong recipient, payload audience != requiredAudience');
+ }
+ }
+ return new loginticket_1.LoginTicket(envelope, payload);
+ });
+ }
+ /**
+ * Returns true if a token is expired or will expire within
+ * eagerRefreshThresholdMillismilliseconds.
+ * If there is no expiry time, assumes the token is not expired or expiring.
+ */
+ isTokenExpiring() {
+ const expiryDate = this.credentials.expiry_date;
+ return expiryDate ? expiryDate <=
+ ((new Date()).getTime() + this.eagerRefreshThresholdMillis) :
+ false;
+ }
+}
+OAuth2Client.GOOGLE_TOKEN_INFO_URL = 'https://oauth2.googleapis.com/tokeninfo';
+/**
+ * The base URL for auth endpoints.
+ */
+OAuth2Client.GOOGLE_OAUTH2_AUTH_BASE_URL_ = 'https://accounts.google.com/o/oauth2/v2/auth';
+/**
+ * The base endpoint for token retrieval.
+ */
+OAuth2Client.GOOGLE_OAUTH2_TOKEN_URL_ = 'https://oauth2.googleapis.com/token';
+/**
+ * The base endpoint to revoke tokens.
+ */
+OAuth2Client.GOOGLE_OAUTH2_REVOKE_URL_ = 'https://oauth2.googleapis.com/revoke';
+/**
+ * Google Sign on certificates in PEM format.
+ */
+OAuth2Client.GOOGLE_OAUTH2_FEDERATED_SIGNON_PEM_CERTS_URL_ = 'https://www.googleapis.com/oauth2/v1/certs';
+/**
+ * Google Sign on certificates in JWK format.
+ */
+OAuth2Client.GOOGLE_OAUTH2_FEDERATED_SIGNON_JWK_CERTS_URL_ = 'https://www.googleapis.com/oauth2/v3/certs';
+/**
+ * Clock skew - five minutes in seconds
+ */
+OAuth2Client.CLOCK_SKEW_SECS_ = 300;
+/**
+ * Max Token Lifetime is one day in seconds
+ */
+OAuth2Client.MAX_TOKEN_LIFETIME_SECS_ = 86400;
+/**
+ * The allowed oauth token issuers.
+ */
+OAuth2Client.ISSUERS_ = ['accounts.google.com', 'https://accounts.google.com'];
+exports.OAuth2Client = OAuth2Client;
+//# sourceMappingURL=oauth2client.js.map
\ No newline at end of file