@delewis13/appauth
Version:
A general purpose OAuth client. Vendored awaiting PR merge
148 lines (137 loc) • 6.4 kB
text/typescript
/*
* 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 {AuthorizationRequest} from './authorization_request';
import {AuthorizationRequestHandler, AuthorizationRequestResponse} from './authorization_request_handler';
import {AuthorizationError, AuthorizationResponse} from './authorization_response'
import {AuthorizationServiceConfiguration} from './authorization_service_configuration';
import {Crypto, DefaultCrypto} from './crypto_utils';
import {log} from './logger';
import {BasicQueryStringUtils} from './query_string_utils';
import {LocalStorageBackend, StorageBackend} from './storage';
import {LocationLike} from './types';
/** key for authorization request. */
const authorizationRequestKey =
(handle: string) => {
return `${handle}_appauth_authorization_request`;
}
/** key for authorization service configuration */
const authorizationServiceConfigurationKey =
(handle: string) => {
return `${handle}_appauth_authorization_service_configuration`;
}
/** key in local storage which represents the current authorization request. */
const AUTHORIZATION_REQUEST_HANDLE_KEY = 'appauth_current_authorization_request';
/**
* Represents an AuthorizationRequestHandler which uses a standard
* redirect based code flow.
*/
export class RedirectRequestHandler extends AuthorizationRequestHandler {
constructor(
// use the provided storage backend
// or initialize local storage with the default storage backend which
// uses window.localStorage
public storageBackend: StorageBackend = new LocalStorageBackend(),
utils = new BasicQueryStringUtils(),
public locationLike: LocationLike = window.location,
crypto: Crypto = new DefaultCrypto()) {
super(utils, crypto);
}
performAuthorizationRequest(
configuration: AuthorizationServiceConfiguration,
request: AuthorizationRequest) {
const handle = this.crypto.generateRandom(10);
// before you make request, persist all request related data in local storage.
const persisted = Promise.all([
this.storageBackend.setItem(AUTHORIZATION_REQUEST_HANDLE_KEY, handle),
// Calling toJson() adds in the code & challenge when possible
request.toJson().then(
result =>
this.storageBackend.setItem(authorizationRequestKey(handle), JSON.stringify(result))),
this.storageBackend.setItem(
authorizationServiceConfigurationKey(handle), JSON.stringify(configuration.toJson())),
]);
persisted.then(() => {
// make the redirect request
let url = this.buildRequestUrl(configuration, request);
log('Making a request to ', request, url);
this.locationLike.assign(url);
});
}
/**
* Attempts to introspect the contents of storage backend and completes the
* request.
*/
protected completeAuthorizationRequest(): Promise<AuthorizationRequestResponse|null> {
// TODO(rahulrav@): handle authorization errors.
return this.storageBackend.getItem(AUTHORIZATION_REQUEST_HANDLE_KEY).then(handle => {
if (handle) {
// we have a pending request.
// fetch authorization request, and check state
return this.storageBackend
.getItem(authorizationRequestKey(handle))
// requires a corresponding instance of result
// TODO(rahulrav@): check for inconsitent state here
.then(result => JSON.parse(result!))
.then(json => new AuthorizationRequest(json))
.then(request => {
// check redirect_uri and state
let currentUri = `${this.locationLike.origin}${this.locationLike.pathname}`;
let queryParams = this.utils.parse(this.locationLike, true /* use hash */);
let state: string|undefined = queryParams['state'];
let code: string|undefined = queryParams['code'];
let error: string|undefined = queryParams['error'];
log('Potential authorization request ', currentUri, queryParams, state, code, error);
let shouldNotify = state === request.state;
let authorizationResponse: AuthorizationResponse|null = null;
let authorizationError: AuthorizationError|null = null;
if (shouldNotify) {
if (error) {
// get additional optional info.
let errorUri = queryParams['error_uri'];
let errorDescription = queryParams['error_description'];
authorizationError = new AuthorizationError({
error: error,
error_description: errorDescription,
error_uri: errorUri,
state: state
});
} else {
authorizationResponse = new AuthorizationResponse({code: code, state: state});
}
// cleanup state
return Promise
.all([
this.storageBackend.removeItem(AUTHORIZATION_REQUEST_HANDLE_KEY),
this.storageBackend.removeItem(authorizationRequestKey(handle)),
this.storageBackend.removeItem(authorizationServiceConfigurationKey(handle))
])
.then(() => {
log('Delivering authorization response');
return {
request: request,
response: authorizationResponse,
error: authorizationError
} as AuthorizationRequestResponse;
});
} else {
log('Mismatched request (state and request_uri) dont match.');
return Promise.resolve(null);
}
});
} else {
return null;
}
});
}
}