UNPKG

@delewis13/appauth

Version:

A general purpose OAuth client. Vendored awaiting PR merge

121 lines (112 loc) 3.78 kB
/* * Copyright 2017 Google Inc. * * 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 {Crypto, DefaultCrypto} from './crypto_utils'; import {log} from './logger'; import {StringMap} from './types'; /** * Represents an AuthorizationRequest as JSON. */ export interface AuthorizationRequestJson { response_type: string; client_id: string; redirect_uri: string; scope: string; state?: string; extras?: StringMap; internal?: StringMap; } /** * Generates a cryptographically random new state. Useful for CSRF protection. */ const SIZE = 10; // 10 bytes const newState = function(crypto: Crypto): string { return crypto.generateRandom(SIZE); }; /** * Represents the AuthorizationRequest. * For more information look at * https://tools.ietf.org/html/rfc6749#section-4.1.1 */ export class AuthorizationRequest { static RESPONSE_TYPE_TOKEN = 'token'; static RESPONSE_TYPE_CODE = 'code'; // NOTE: // Both redirect_uri and state are actually optional. // However AppAuth is more opionionated, and requires you to use both. clientId: string; redirectUri: string; scope: string; responseType: string; state: string; extras?: StringMap; internal?: StringMap; /** * Constructs a new AuthorizationRequest. * Use a `undefined` value for the `state` parameter, to generate a random * state for CSRF protection. */ constructor( request: AuthorizationRequestJson, private crypto: Crypto = new DefaultCrypto(), private usePkce: boolean = true) { this.clientId = request.client_id; this.redirectUri = request.redirect_uri; this.scope = request.scope; this.responseType = request.response_type || AuthorizationRequest.RESPONSE_TYPE_CODE; this.state = request.state || newState(crypto); this.extras = request.extras; // read internal properties if available this.internal = request.internal; } setupCodeVerifier(): Promise<void> { if (!this.usePkce) { return Promise.resolve(); } else { const codeVerifier = this.crypto.generateRandom(128); const challenge: Promise<string|undefined> = this.crypto.deriveChallenge(codeVerifier).catch(error => { log('Unable to generate PKCE challenge. Not using PKCE', error); return undefined; }); return challenge.then(result => { if (result) { // keep track of the code used. this.internal = this.internal || {}; this.internal['code_verifier'] = codeVerifier; this.extras = this.extras || {}; this.extras['code_challenge'] = result; // We always use S256. Plain is not good enough. this.extras['code_challenge_method'] = 'S256'; } }); } } /** * Serializes the AuthorizationRequest to a JavaScript Object. */ toJson(): Promise<AuthorizationRequestJson> { // Always make sure that the code verifier is setup when toJson() is called. return this.setupCodeVerifier().then(() => { return { response_type: this.responseType, client_id: this.clientId, redirect_uri: this.redirectUri, scope: this.scope, state: this.state, extras: this.extras, internal: this.internal }; }); } }