New Http wrapper, updated components, minor API change

The new Http wrapper automatically handles updates to the JWT during
server communication and redirects to login on unauthenticated calls.
This commit is contained in:
Matthew Ross 2016-07-20 20:21:15 -04:00
parent c2938c9c38
commit 823daf740c
9 changed files with 135 additions and 79 deletions

View File

@ -74,7 +74,7 @@ class Auth extends BaseController {
return $response->withStatus(401); return $response->withStatus(401);
} }
$jwt = self::createJwt($payload->uid); $jwt = self::createJwt($payload->uid, (int) $payload->mul);
$user->active_token = $jwt; $user->active_token = $jwt;
$user->save(); $user->save();
@ -117,7 +117,7 @@ class Auth extends BaseController {
return $this->jsonResponse($response, 401); return $this->jsonResponse($response, 401);
} }
$jwt = self::createJwt($user->id); $jwt = self::createJwt($user->id, ($data->remember ? 200 : 1));
$user = new User($this->container, $user->id); $user = new User($this->container, $user->id);
$user->active_token = $jwt; $user->active_token = $jwt;
@ -199,10 +199,12 @@ class Auth extends BaseController {
return $payload; return $payload;
} }
private static function createJwt($userId) { private static function createJwt($userId, $mult = 1) {
// If 'remember me' feature is desired, set the multiplier higher
return JWT::encode(array( return JWT::encode(array(
'exp' => time() + (60 * 30), // 30 minutes 'exp' => time() + (60 * 30) * $mult, // 30 minutes * $mult
'uid' => $userId 'uid' => $userId,
'mul' => $mult
), Auth::getJwtKey()); ), Auth::getJwtKey());
} }

View File

@ -50,10 +50,7 @@ abstract class BaseController {
return 403; return 403;
} }
$token = new stdClass(); $this->apiJson->addData((string) $response->getBody());
$token->jwt = (string) $response->getBody();
$this->apiJson->addData($token);
return $status; return $status;
} }

View File

@ -15,8 +15,8 @@ import {
}) })
export class Login { export class Login {
version: string; version: string;
username: string; username: string = '';
password: string; password: string = '';
remember: boolean; remember: boolean;
isSubmitted: boolean = false; isSubmitted: boolean = false;
@ -26,6 +26,12 @@ export class Login {
} }
login(): void { login(): void {
if (this.username === '' || this.password === '') {
this.notes.add(new Notification('error',
'Username and password are required.'));
return;
}
this.isSubmitted = true; this.isSubmitted = true;
this.authService.login(this.username, this.password, this.remember) this.authService.login(this.username, this.password, this.remember)

View File

@ -5,7 +5,7 @@ import { enableProdMode } from '@angular/core';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { APP_ROUTER_PROVIDERS } from './app.routes'; import { APP_ROUTER_PROVIDERS } from './app.routes';
import { NotificationsService } from './shared/index'; import { NotificationsService, API_HTTP_PROVIDERS } from './shared/index';
import { Constants } from './shared/constants'; import { Constants } from './shared/constants';
// enableProdMode(); // enableProdMode();
@ -16,6 +16,7 @@ bootstrap(AppComponent,
disableDeprecatedForms(), disableDeprecatedForms(),
provideForms(), provideForms(),
APP_ROUTER_PROVIDERS, APP_ROUTER_PROVIDERS,
API_HTTP_PROVIDERS,
Constants, Constants,
NotificationsService NotificationsService
] ]

View File

@ -43,7 +43,6 @@ export class UserSettings {
.changePassword(this.changePassword.current, .changePassword(this.changePassword.current,
this.changePassword.newPass) this.changePassword.newPass)
.subscribe((response: ApiResponse) => { .subscribe((response: ApiResponse) => {
console.log(response);
response.alerts.forEach(msg => { response.alerts.forEach(msg => {
this.notes.add(new Notification(msg.type, msg.text)); this.notes.add(new Notification(msg.type, msg.text));

View File

@ -1,5 +1,6 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Http, Response, Headers } from '@angular/http'; import { Http, Response, Headers } from '@angular/http';
import { Router } from '@angular/router';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of'; import 'rxjs/add/observable/of';
@ -20,11 +21,10 @@ interface UpdateUser extends User {
@Injectable() @Injectable()
export class UserSettingsService { export class UserSettingsService {
activeUser: User = null; activeUser: User = null;
jwtKey: string;
constructor(auth: AuthService, constants: Constants, private http: Http) { constructor(auth: AuthService, constants: Constants,
private http: Http, private router: Router) {
this.activeUser = auth.activeUser; this.activeUser = auth.activeUser;
this.jwtKey = constants.TOKEN;
} }
changePassword(oldPass: string, newPass: string): Observable<ApiResponse> { changePassword(oldPass: string, newPass: string): Observable<ApiResponse> {
@ -33,30 +33,16 @@ export class UserSettingsService {
updateUser.old_password = oldPass; updateUser.old_password = oldPass;
let json = JSON.stringify(updateUser); let json = JSON.stringify(updateUser);
let headers = this.getHeaders();
return this.http.post('api/users/' + this.activeUser.id, json, return this.http.post('api/users/' + this.activeUser.id, json)
{ headers: headers })
.map(res => { .map(res => {
let response: ApiResponse = res.json(); let response: ApiResponse = res.json();
return response; return response;
}) })
.catch((res, caught) => { .catch((res, caught) => {
let response: ApiResponse = res.json(); let response: ApiResponse = res.json();
return Observable.of(response); return Observable.of(response);
}); });
} }
private getHeaders(): Headers {
let token = localStorage.getItem(this.jwtKey);
let headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('Authorization', token);
return headers;
}
} }

View File

@ -0,0 +1,102 @@
import { provide } from '@angular/core';
import {
Http,
Request,
RequestOptionsArgs,
Response,
XHRBackend,
RequestOptions,
ConnectionBackend,
Headers
} from '@angular/http';
import { Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/throw';
import { ApiResponse } from './index';
export const API_HTTP_PROVIDERS = [
provide(Http, {
useFactory: (xhrBackend: XHRBackend, requestOptions: RequestOptions,
router: Router) => new ApiHttp(xhrBackend, requestOptions, router),
deps: [XHRBackend, RequestOptions, Router]
})
];
export class ApiHttp extends Http {
private JWT_KEY = 'taskboard.jwt';
constructor(backend: ConnectionBackend,
defaultOptions: RequestOptions,
private router: Router) {
super(backend, defaultOptions);
}
request(url: string | Request,
options?: RequestOptionsArgs): Observable<Response> {
return this.intercept(super.request(url, options));
}
get(url: string, options?: RequestOptionsArgs): Observable<Response> {
return this.intercept(super.get(url, options));
}
post(url: string, body: string,
options?: RequestOptionsArgs): Observable<Response> {
return this.intercept(super.post(url, body,
this.getRequestOptionArgs(options)));
}
put(url: string, body: string,
options?: RequestOptionsArgs): Observable<Response> {
return this.intercept(super.put(url, body,
this.getRequestOptionArgs(options)));
}
delete(url: string, options?: RequestOptionsArgs): Observable<Response> {
return this.intercept(super.delete(url, options));
}
getRequestOptionArgs(options?: RequestOptionsArgs) : RequestOptionsArgs {
if (options == null) {
options = new RequestOptions();
}
if (options.headers == null) {
options.headers = new Headers();
}
options.headers.append('Content-Type', 'application/json');
let jwt = localStorage.getItem(this.JWT_KEY);
if (jwt) {
options.headers.append('Authorization', jwt);
}
return options;
}
intercept(observable: Observable<Response>): Observable<Response> {
return observable
.map((res: Response) => {
let response: ApiResponse = res.json();
console.log(response);
localStorage.setItem(this.JWT_KEY, response.data[0]);
return res;
})
.catch((err, source) => {
console.log(err);
if (err.status === 401 && err.url.indexOf('login') === -1) {
this.router.navigate(['']);
localStorage.removeItem(this.JWT_KEY);
}
return Observable.throw(err);
});
}
}

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Http, Response, Headers } from '@angular/http'; import { Http, Response } from '@angular/http';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
@ -12,92 +12,54 @@ import { Constants } from '../constants';
@Injectable() @Injectable()
export class AuthService { export class AuthService {
activeUser: User = null; activeUser: User = null;
jwtKey: string;
constructor(constants: Constants, private http: Http, constructor(constants: Constants, private http: Http,
private router: Router) { private router: Router) {
this.jwtKey = constants.TOKEN;
} }
authenticate(): Observable<boolean> { authenticate(): Observable<boolean> {
let headers = this.getHeaders(); return this.http.post('api/authenticate', null)
return this.http.post('api/authenticate', null, { headers: headers })
.map(res => { .map(res => {
this.checkStatus(res); let response: ApiResponse = res.json();
this.activeUser = response.data[1];
return this.activeUser !== null; return this.activeUser !== null;
}) })
.catch((res, caught) => { .catch((res, caught) => {
this.checkStatus(res);
return Observable.of(false); return Observable.of(false);
}); });
} }
login(username: string, password: string, login(username: string, password: string,
remember: boolean): Observable<ApiResponse> { remember: boolean): Observable<ApiResponse> {
// TODO Add remember flag to API
let json = JSON.stringify({ let json = JSON.stringify({
username: username, username: username,
password: password password: password,
remember: remember
}); });
return this.http.post('api/login', json) return this.http.post('api/login', json)
.map(res => { .map(res => {
let response: ApiResponse = res.json(); let response: ApiResponse = res.json();
this.checkStatus(res); this.activeUser = response.data[1];
return response; return response;
}) })
.catch((res, caught) => { .catch((res, caught) => {
let response: ApiResponse = res.json(); let response: ApiResponse = res.json();
this.checkStatus(res); this.activeUser = null;
return Observable.of(response); return Observable.of(response);
}); });
} }
logout(): Observable<ApiResponse> { logout(): Observable<ApiResponse> {
let headers = this.getHeaders(); return this.http.post('api/logout', null)
this.clearData();
return this.http.post('api/logout', null, { headers: headers })
.map(res => { .map(res => {
let response: ApiResponse = res.json(); let response: ApiResponse = res.json();
return response; return response;
}); });
} }
private clearData(): void {
this.activeUser = null;
localStorage.removeItem(this.jwtKey);
this.router.navigate(['']);
}
private checkStatus(response: Response): void {
if (response.status === 200) {
let apiResponse: ApiResponse = response.json();
this.activeUser = apiResponse.data[1];
localStorage.setItem(this.jwtKey, apiResponse.data[0])
}
if (response.status === 401) {
this.clearData();
}
}
private getHeaders(): Headers {
let token = localStorage.getItem(this.jwtKey);
let headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('Authorization', token);
return headers;
}
} }

View File

@ -3,4 +3,5 @@ export * from './auth/index';
export * from './models/index'; export * from './models/index';
export * from './notifications/index'; export * from './notifications/index';
export * from './constants'; export * from './constants';
export * from './auth-http.provider';