authui-container/server/api/token-manager.ts (55 lines of code) (raw):

/* * Copyright 2020 Google Inc. 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. */ import {HttpServerRequestHandler} from '../../server/utils/http-server-request-handler'; /** Interface defining a Google OAuth access token. */ export interface GoogleOAuthAccessToken { access_token: string; expires_in: number; } /** Interface defining an OAuth access token manager used to retrieve tokens. */ export interface AccessTokenManager { getAccessToken(): Promise<string>; } /** Interface defining a credential object used to retrieve access tokens. */ export interface Credential { getAccessToken(): Promise<GoogleOAuthAccessToken>; } /** Metadata server access token endpoint. */ const METADATA_SERVER_ACCESS_TOKEN_URL = 'http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token'; /** The default OAuth scope to include in the access token. */ const DEFAULT_OAUTH_SCOPE = 'https://www.googleapis.com/auth/cloud-platform'; /** Time offset in milliseconds for forcing a refresh before a token expires. */ export const OFFSET = 30000; /** Network request timeout duration. */ const TIMEOUT_DURATION = 10000; /** Default error message to show when access token fails to be obtained. */ const DEFAULT_ERROR_MESSAGE = 'Unable to retrieve an OAuth access tokens.'; /** Utility used to manage OAuth access tokens generated via the metadata server. */ export class TokenManager implements Credential { private readonly metadataServerTokenRetriever: HttpServerRequestHandler; private expirationTime: number; private accessToken: string | null; /** * Instantiates an instance of a token manager used to retrieve OAuth access * tokens retrieved from the metadata server. * @param scopes The OAuth scopes to set on the generated access tokens. */ constructor(scopes: string[] = [DEFAULT_OAUTH_SCOPE]) { this.metadataServerTokenRetriever = new HttpServerRequestHandler({ method: 'GET', url: `${METADATA_SERVER_ACCESS_TOKEN_URL}?scopes=${scopes.join(',')}`, headers: { 'Metadata-Flavor': 'Google', }, timeout: TIMEOUT_DURATION, }); } /** * @return A promise that resolves with a Google OAuth access token. * A cached token is returned if it is not yet expired. */ getAccessToken(forceRefresh: boolean = false): Promise<GoogleOAuthAccessToken> { const currentTime = new Date().getTime(); if (!forceRefresh && (this.accessToken && currentTime + OFFSET <= this.expirationTime)) { return Promise.resolve({ access_token: this.accessToken, expires_in: (this.expirationTime - currentTime) / 1000, }); } return this.metadataServerTokenRetriever.send(null, DEFAULT_ERROR_MESSAGE) .then((httpResponse) => { if (httpResponse.statusCode === 200 && httpResponse.body) { const tokenResponse: GoogleOAuthAccessToken = typeof httpResponse.body === 'object' ? httpResponse.body : JSON.parse(httpResponse.body); this.accessToken = tokenResponse.access_token; this.expirationTime = currentTime + (tokenResponse.expires_in * 1000); return tokenResponse; } else { throw new Error(DEFAULT_ERROR_MESSAGE); } }); } /** Reset cached access tokens. */ reset() { this.accessToken = null; } }