nodram
Version:
A Really MEAN Web Framework
243 lines (188 loc) • 6.93 kB
text/typescript
import {Injectable, Injector, Inject, Provider} from '@angular/core';
import {Http, Headers, BaseRequestOptions, Request, RequestOptions, RequestOptionsArgs, RequestMethod, Response} from '@angular/http';
import {Observable} from 'rxjs/Observable';
import {Store} from "./Store";
// Avoid TS error "cannot find name escape"
declare const escape: any;
export interface IAuthConfig {
headerName?: string;
headerPrefix?: string;
tokenName?: string;
tokenGetter?: Function;
noJwtError?: boolean;
}
/**
* Sets up the authentication configuration.
*/
export class AuthConfig {
config: any;
headerName: string;
headerPrefix: string;
tokenName: string;
tokenGetter: Function;
noJwtError: boolean;
constructor(config?: any) {
this.config = config || {};
this.headerName = this.config.headerName || 'Authorization';
if(this.config.headerPrefix) {
this.headerPrefix = this.config.headerPrefix + ' ';
} else {
this.headerPrefix = 'Bearer ';
}
this.tokenName = this.config.tokenName || 'id_token';
this.noJwtError = this.config.noJwtError || false;
this.tokenGetter = this.config.tokenGetter || (() => Store.getFromStore(this.tokenName));
}
getConfig() {
return {
headerName: this.headerName,
headerPrefix: this.headerPrefix,
tokenName: this.tokenName,
tokenGetter: this.tokenGetter,
noJwtError: this.noJwtError
}
}
}
/**
* Function to provide AuthConfig
* @param configObject
* @returns Provider
*/
export function provideAuthConfig(configObject: IAuthConfig): Provider{
return {
provide : AuthConfig,
useFactory: ()=> new AuthConfig(configObject)
}
}
/**
* Allows for explicit authenticated HTTP requests.
*/
export class AuthHttp {
private _config: IAuthConfig;
public tokenStream: Observable<Object>;
constructor(options: AuthConfig, private http: Http) {
this._config = options.getConfig();
this.tokenStream = new Observable((obs: any) => {
obs.next(this._config.tokenGetter())
});
}
request(url: string | Request, options?: RequestOptionsArgs) : Observable<Response> {
let request:any;
if(!tokenNotExpired(null, this._config.tokenGetter())) {
if(!this._config.noJwtError) {
throw 'Invalid JWT';
} else {
request = this.http.request(url, options);
}
} else if(typeof url === 'string') {
let reqOpts = options || {};
if(!reqOpts.headers) {
reqOpts.headers = new Headers();
}
reqOpts.headers.set(this._config.headerName, this._config.headerPrefix + this._config.tokenGetter());
request = this.http.request(url, reqOpts);
} else {
let req:Request = <Request>url;
if(!req.headers) {
req.headers = new Headers();
}
req.headers.set(this._config.headerName, this._config.headerPrefix + this._config.tokenGetter());
request = this.http.request(req);
}
return request;
}
private requestHelper(requestArgs: RequestOptionsArgs, additionalOptions: RequestOptionsArgs) : Observable<Response> {
let options = new RequestOptions(requestArgs);
if(additionalOptions) {
options = options.merge(additionalOptions)
}
return this.request(new Request(options))
}
get(url: string, options?: RequestOptionsArgs) : Observable<Response> {
return this.requestHelper({ url: url, method: RequestMethod.Get }, options);
}
post(url: string, body: string, options?: RequestOptionsArgs) : Observable<Response> {
return this.requestHelper({ url: url, body: body, method: RequestMethod.Post }, options);
}
put(url: string, body: string, options ?: RequestOptionsArgs) : Observable<Response> {
return this.requestHelper({ url: url, body: body, method: RequestMethod.Put }, options);
}
delete(url: string, options ?: RequestOptionsArgs) : Observable<Response> {
return this.requestHelper({ url: url, method: RequestMethod.Delete }, options);
}
patch(url: string, body:string, options?: RequestOptionsArgs) : Observable<Response> {
return this.requestHelper({ url: url, body: body, method: RequestMethod.Patch }, options);
}
head(url: string, options?: RequestOptionsArgs) : Observable<Response> {
return this.requestHelper({ url: url, method: RequestMethod.Head }, options);
}
}
/**
* Helper class to decode and find JWT expiration.
*/
export class JwtHelper {
public urlBase64Decode(str:string) {
var output = str.replace(/-/g, '+').replace(/_/g, '/');
switch (output.length % 4) {
case 0: { break; }
case 2: { output += '=='; break; }
case 3: { output += '='; break; }
default: {
throw 'Illegal base64url string!';
}
}
return decodeURIComponent(escape(window.atob(output))); //polifyll https://github.com/davidchambers/Base64.js
}
public decodeToken(token:string) {
let parts = token.split('.');
if (parts.length !== 3) {
throw new Error('JWT must have 3 parts');
}
let decoded = this.urlBase64Decode(parts[1]);
if (!decoded) {
throw new Error('Cannot decode the token');
}
return JSON.parse(decoded);
}
public getTokenExpirationDate(token:string) {
var decoded: any;
decoded = this.decodeToken(token);
if(typeof decoded.exp === "undefined") {
return null;
}
var date = new Date(0); // The 0 here is the key, which sets the date to the epoch
date.setUTCSeconds(decoded.exp);
return date;
}
public isTokenExpired(token:string, offsetSeconds?:number) {
var date = this.getTokenExpirationDate(token);
offsetSeconds = offsetSeconds || 0;
if (date === null) {
return false;
}
// Token expired?
return !(date.valueOf() > (new Date().valueOf() + (offsetSeconds * 1000)));
}
}
/**
* Checks for presence of token and that token hasn't expired.
* For use with the @CanActivate router decorator and NgIf
*/
export function tokenNotExpired(tokenName?:string, jwt?:string) {
var authToken:string = tokenName || 'id_token';
var token:string;
if(jwt) {
token = jwt;
}
else {
token = Store.getFromStore(authToken);
}
var jwtHelper = new JwtHelper();
if(!token || jwtHelper.isTokenExpired(token, null)) {
return false;
}
else {
return true;
}
}