File

src/auth/jwtaccess.ts

Index

Properties
Methods

Constructor

constructor(email?: string | null, key?: string | null, keyId?: string | null, eagerRefreshThresholdMillis?: number)

JWTAccess service account credentials.

Create a new access token by using the credential to create a new JWT token that's recognized as the access token.

Parameters :
Name Type Optional Description
email string | null Yes

the service account email address.

key string | null Yes

the private key that will be used to sign the token.

keyId string | null Yes

the ID of the private key used to sign the token.

eagerRefreshThresholdMillis number Yes

Properties

eagerRefreshThresholdMillis
Type : number
Optional email
Type : string | null
Optional key
Type : string | null
Optional keyId
Type : string | null
Optional projectId
Type : string

Methods

fromJSON
fromJSON(json: JWTInput)

Create a JWTAccess credentials instance using the given input options.

Parameters :
Name Type Optional Description
json JWTInput No

The input object.

Returns : void
fromStream
fromStream(inputStream: stream.Readable)

Create a JWTAccess credentials instance using the given input stream.

Parameters :
Name Type Optional Description
inputStream stream.Readable No

The input stream.

Returns : Promise<void>
fromStream
fromStream(inputStream: stream.Readable, callback: (err: Error) => void)
Parameters :
Name Type Optional
inputStream stream.Readable No
callback function No
Returns : void
fromStream
fromStream(inputStream: stream.Readable, callback?: (err?: Error) => void)
Parameters :
Name Type Optional
inputStream stream.Readable No
callback function Yes
Returns : void | Promise
getRequestHeaders
getRequestHeaders(url: string, additionalClaims?: Claims)

Get a non-expired access token, after refreshing if necessary.

Parameters :
Name Type Optional Description
url string No

The URI being authorized.

additionalClaims Claims Yes

An object with a set of additional claims to include in the payload.

Returns : Headers

An object that includes the authorization header.

import * as jws from 'jws';
import * as LRU from 'lru-cache';
import * as stream from 'stream';

import {JWTInput} from './credentials';
import {Headers} from './oauth2client';

const DEFAULT_HEADER: jws.Header = {
  alg: 'RS256',
  typ: 'JWT',
};

export interface Claims {
  [index: string]: string;
}

export class JWTAccess {
  email?: string | null;
  key?: string | null;
  keyId?: string | null;
  projectId?: string;
  eagerRefreshThresholdMillis: number;

  private cache = new LRU<string, {expiration: number; headers: Headers}>({
    max: 500,
    maxAge: 60 * 60 * 1000,
  });

  /**
   * JWTAccess service account credentials.
   *
   * Create a new access token by using the credential to create a new JWT token
   * that's recognized as the access token.
   *
   * @param email the service account email address.
   * @param key the private key that will be used to sign the token.
   * @param keyId the ID of the private key used to sign the token.
   */
  constructor(
    email?: string | null,
    key?: string | null,
    keyId?: string | null,
    eagerRefreshThresholdMillis?: number
  ) {
    this.email = email;
    this.key = key;
    this.keyId = keyId;
    this.eagerRefreshThresholdMillis =
      eagerRefreshThresholdMillis ?? 5 * 60 * 1000;
  }

  /**
   * Get a non-expired access token, after refreshing if necessary.
   *
   * @param url The URI being authorized.
   * @param additionalClaims An object with a set of additional claims to
   * include in the payload.
   * @returns An object that includes the authorization header.
   */
  getRequestHeaders(url: string, additionalClaims?: Claims): Headers {
    // Return cached authorization headers, unless we are within
    // eagerRefreshThresholdMillis ms of them expiring:
    const cachedToken = this.cache.get(url);
    const now = Date.now();
    if (
      cachedToken &&
      cachedToken.expiration - now > this.eagerRefreshThresholdMillis
    ) {
      return cachedToken.headers;
    }
    const iat = Math.floor(Date.now() / 1000);
    const exp = JWTAccess.getExpirationTime(iat);

    // The payload used for signed JWT headers has:
    // iss == sub == <client email>
    // aud == <the authorization uri>
    const defaultClaims = {
      iss: this.email,
      sub: this.email,
      aud: url,
      exp,
      iat,
    };

    // if additionalClaims are provided, ensure they do not collide with
    // other required claims.
    if (additionalClaims) {
      for (const claim in defaultClaims) {
        if (additionalClaims[claim]) {
          throw new Error(
            `The '${claim}' property is not allowed when passing additionalClaims. This claim is included in the JWT by default.`
          );
        }
      }
    }

    const header = this.keyId
      ? {...DEFAULT_HEADER, kid: this.keyId}
      : DEFAULT_HEADER;
    const payload = Object.assign(defaultClaims, additionalClaims);

    // Sign the jwt and add it to the cache
    const signedJWT = jws.sign({header, payload, secret: this.key});
    const headers = {Authorization: `Bearer ${signedJWT}`};
    this.cache.set(url, {
      expiration: exp * 1000,
      headers,
    });
    return headers;
  }

  /**
   * Returns an expiration time for the JWT token.
   *
   * @param iat The issued at time for the JWT.
   * @returns An expiration time for the JWT.
   */
  private static getExpirationTime(iat: number): number {
    const exp = iat + 3600; // 3600 seconds = 1 hour
    return exp;
  }

  /**
   * Create a JWTAccess credentials instance using the given input options.
   * @param json The input object.
   */
  fromJSON(json: JWTInput): void {
    if (!json) {
      throw new Error(
        'Must pass in a JSON object containing the service account auth settings.'
      );
    }
    if (!json.client_email) {
      throw new Error(
        'The incoming JSON object does not contain a client_email field'
      );
    }
    if (!json.private_key) {
      throw new Error(
        'The incoming JSON object does not contain a private_key field'
      );
    }
    // Extract the relevant information from the json key file.
    this.email = json.client_email;
    this.key = json.private_key;
    this.keyId = json.private_key_id;
    this.projectId = json.project_id;
  }

  /**
   * Create a JWTAccess credentials instance using the given input stream.
   * @param inputStream The input stream.
   * @param callback Optional callback.
   */
  fromStream(inputStream: stream.Readable): Promise<void>;
  fromStream(
    inputStream: stream.Readable,
    callback: (err?: Error) => void
  ): void;
  fromStream(
    inputStream: stream.Readable,
    callback?: (err?: Error) => void
  ): void | Promise<void> {
    if (callback) {
      this.fromStreamAsync(inputStream).then(() => callback(), callback);
    } else {
      return this.fromStreamAsync(inputStream);
    }
  }

  private fromStreamAsync(inputStream: stream.Readable): Promise<void> {
    return new Promise((resolve, reject) => {
      if (!inputStream) {
        reject(
          new Error(
            'Must pass in a stream containing the service account auth settings.'
          )
        );
      }
      let s = '';
      inputStream
        .setEncoding('utf8')
        .on('data', chunk => (s += chunk))
        .on('error', reject)
        .on('end', () => {
          try {
            const data = JSON.parse(s);
            this.fromJSON(data);
            resolve();
          } catch (err) {
            reject(err);
          }
        });
    });
  }
}

result-matching ""

    No results matching ""