import { Component } from '@angular/core';
-import { HTTP_PROVIDERS } from '@angular/http';
import { ActivatedRoute, Router, ROUTER_DIRECTIVES } from '@angular/router';
import { FriendService } from './friends';
template: require('./app.component.html'),
styles: [ require('./app.component.scss') ],
directives: [ ROUTER_DIRECTIVES, SearchComponent ],
- providers: [ AuthService, FriendService, HTTP_PROVIDERS, VideoService, SearchService ]
+ providers: [ FriendService, VideoService, SearchService ]
})
export class AppComponent {
status => {
if (status === AuthStatus.LoggedIn) {
this.isLoggedIn = true;
+ console.log('Logged in.');
+ } else if (status === AuthStatus.LoggedOut) {
+ this.isLoggedIn = false;
+ console.log('Logged out.');
+ } else {
+ console.error('Unknown auth status: ' + status);
}
}
);
}
- // FIXME
logout() {
- // this._authService.logout();
+ this.authService.logout();
+ this.authService.setStatus(AuthStatus.LoggedOut);
}
makeFriends() {
import { Injectable } from '@angular/core';
-import { Http, Response } from '@angular/http';
+import { Response } from '@angular/http';
import { Observable } from 'rxjs/Observable';
-import { AuthService } from '../shared';
+import { AuthHttp, AuthService } from '../shared';
@Injectable()
export class FriendService {
private static BASE_FRIEND_URL: string = '/api/v1/pods/';
- constructor (private http: Http, private authService: AuthService) {}
+ constructor (private authHttp: AuthHttp, private authService: AuthService) {}
makeFriends() {
- const headers = this.authService.getRequestHeader();
- return this.http.get(FriendService.BASE_FRIEND_URL + 'makefriends', { headers })
+ return this.authHttp.get(FriendService.BASE_FRIEND_URL + 'makefriends')
.map(res => res.status)
.catch(this.handleError);
}
quitFriends() {
- const headers = this.authService.getRequestHeader();
- return this.http.get(FriendService.BASE_FRIEND_URL + 'quitfriends', { headers })
+ return this.authHttp.get(FriendService.BASE_FRIEND_URL + 'quitfriends')
.map(res => res.status)
.catch(this.handleError);
}
import { Component } from '@angular/core';
import { Router } from '@angular/router';
-import { AuthService, AuthStatus, User } from '../shared';
+import { AuthService } from '../shared';
@Component({
selector: 'my-login',
result => {
this.error = null;
- const user = new User(username, result);
- user.save();
-
- this.authService.setStatus(AuthStatus.LoggedIn);
-
this.router.navigate(['/videos/list']);
},
error => {
+ console.error(error);
+
if (error.error === 'invalid_grant') {
this.error = 'Credentials are invalid.';
} else {
--- /dev/null
+import { Injectable } from '@angular/core';
+import {
+ ConnectionBackend,
+ Headers,
+ Http,
+ Request,
+ RequestMethod,
+ RequestOptions,
+ RequestOptionsArgs,
+ Response
+} from '@angular/http';
+import { Observable } from 'rxjs/Observable';
+
+import { AuthService } from './auth.service';
+
+@Injectable()
+export class AuthHttp extends Http {
+ constructor(backend: ConnectionBackend, defaultOptions: RequestOptions, private authService: AuthService) {
+ super(backend, defaultOptions);
+ }
+
+ request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
+ if (!options) options = {};
+
+ options.headers = new Headers();
+ this.setAuthorizationHeader(options.headers);
+
+ return super.request(url, options)
+ .catch((err) => {
+ if (err.status === 401) {
+ return this.handleTokenExpired(err, url, options);
+ }
+
+ return Observable.throw(err);
+ });
+ }
+
+ delete(url: string, options?: RequestOptionsArgs): Observable<Response> {
+ if (!options) options = {};
+ options.method = RequestMethod.Delete;
+
+ return this.request(url, options);
+ }
+
+ get(url: string, options?: RequestOptionsArgs): Observable<Response> {
+ if (!options) options = {};
+ options.method = RequestMethod.Get;
+
+ return this.request(url, options);
+ }
+
+ post(url: string, options?: RequestOptionsArgs): Observable<Response> {
+ if (!options) options = {};
+ options.method = RequestMethod.Post;
+
+ return this.request(url, options);
+ }
+
+ put(url: string, options?: RequestOptionsArgs): Observable<Response> {
+ if (!options) options = {};
+ options.method = RequestMethod.Put;
+
+ return this.request(url, options);
+ }
+
+ private handleTokenExpired(err: Response, url: string | Request, options: RequestOptionsArgs) {
+ return this.authService.refreshAccessToken().flatMap(() => {
+ this.setAuthorizationHeader(options.headers);
+
+ return super.request(url, options);
+ });
+ }
+
+ private setAuthorizationHeader(headers: Headers) {
+ headers.set('Authorization', `${this.authService.getTokenType()} ${this.authService.getToken()}`);
+ }
+}
--- /dev/null
+export enum AuthStatus {
+ LoggedIn,
+ LoggedOut
+}
--- /dev/null
+import { Injectable } from '@angular/core';
+import { Headers, Http, RequestOptions, Response, URLSearchParams } from '@angular/http';
+import { Observable } from 'rxjs/Observable';
+import { Subject } from 'rxjs/Subject';
+
+import { AuthStatus } from './auth-status.model';
+import { User } from './user.model';
+
+@Injectable()
+export class AuthService {
+ private static BASE_CLIENT_URL = '/api/v1/users/client';
+ private static BASE_TOKEN_URL = '/api/v1/users/token';
+
+ loginChangedSource: Observable<AuthStatus>;
+
+ private clientId: string;
+ private clientSecret: string;
+ private loginChanged: Subject<AuthStatus>;
+ private user: User = null;
+
+ constructor(private http: Http) {
+ this.loginChanged = new Subject<AuthStatus>();
+ this.loginChangedSource = this.loginChanged.asObservable();
+
+ // Fetch the client_id/client_secret
+ // FIXME: save in local storage?
+ this.http.get(AuthService.BASE_CLIENT_URL)
+ .map(res => res.json())
+ .catch(this.handleError)
+ .subscribe(
+ result => {
+ this.clientId = result.client_id;
+ this.clientSecret = result.client_secret;
+ console.log('Client credentials loaded.');
+ },
+ error => {
+ alert(error);
+ }
+ );
+
+ // Return null if there is nothing to load
+ this.user = User.load();
+ }
+
+ getAuthRequestOptions(): RequestOptions {
+ return new RequestOptions({ headers: this.getRequestHeader() });
+ }
+
+ getRefreshToken() {
+ if (this.user === null) return null;
+
+ return this.user.getRefreshToken();
+ }
+
+ getRequestHeader() {
+ return new Headers({ 'Authorization': this.getRequestHeaderValue() });
+ }
+
+ getRequestHeaderValue() {
+ return `${this.getTokenType()} ${this.getToken()}`;
+ }
+
+ getToken() {
+ if (this.user === null) return null;
+
+ return this.user.getAccessToken();
+ }
+
+ getTokenType() {
+ if (this.user === null) return null;
+
+ return this.user.getTokenType();
+ }
+
+ getUser(): User {
+ return this.user;
+ }
+
+ isLoggedIn() {
+ if (this.getToken()) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ login(username: string, password: string) {
+ let body = new URLSearchParams();
+ body.set('client_id', this.clientId);
+ body.set('client_secret', this.clientSecret);
+ body.set('response_type', 'code');
+ body.set('grant_type', 'password');
+ body.set('scope', 'upload');
+ body.set('username', username);
+ body.set('password', password);
+
+ let headers = new Headers();
+ headers.append('Content-Type', 'application/x-www-form-urlencoded');
+
+ let options = {
+ headers: headers
+ };
+
+ return this.http.post(AuthService.BASE_TOKEN_URL, body.toString(), options)
+ .map(res => res.json())
+ .map(res => {
+ res.username = username;
+ return res;
+ })
+ .map(res => this.handleLogin(res))
+ .catch(this.handleError);
+ }
+
+ logout() {
+ // TODO: make an HTTP request to revoke the tokens
+ this.user = null;
+ User.flush();
+ }
+
+ refreshAccessToken() {
+ console.log('Refreshing token...');
+
+ const refreshToken = this.getRefreshToken();
+
+ let body = new URLSearchParams();
+ body.set('refresh_token', refreshToken);
+ body.set('client_id', this.clientId);
+ body.set('client_secret', this.clientSecret);
+ body.set('response_type', 'code');
+ body.set('grant_type', 'refresh_token');
+
+ let headers = new Headers();
+ headers.append('Content-Type', 'application/x-www-form-urlencoded');
+
+ let options = {
+ headers: headers
+ };
+
+ return this.http.post(AuthService.BASE_TOKEN_URL, body.toString(), options)
+ .map(res => res.json())
+ .map(res => this.handleRefreshToken(res))
+ .catch(this.handleError);
+ }
+
+ setStatus(status: AuthStatus) {
+ this.loginChanged.next(status);
+ }
+
+ private handleLogin (obj: any) {
+ const username = obj.username;
+ const hash_tokens = {
+ access_token: obj.access_token,
+ token_type: obj.token_type,
+ refresh_token: obj.refresh_token
+ };
+
+ this.user = new User(username, hash_tokens);
+ this.user.save();
+
+ this.setStatus(AuthStatus.LoggedIn);
+ }
+
+ private handleError (error: Response) {
+ console.error(error);
+ return Observable.throw(error.json() || { error: 'Server error' });
+ }
+
+ private handleRefreshToken (obj: any) {
+ this.user.refreshTokens(obj.access_token, obj.refresh_token);
+ this.user.save();
+ }
+}
--- /dev/null
+export * from './auth-http.service';
+export * from './auth-status.model';
+export * from './auth.service';
+export * from './user.model';
--- /dev/null
+export class User {
+ private static KEYS = {
+ USERNAME: 'username'
+ };
+
+ username: string;
+ tokens: Tokens;
+
+ static load() {
+ const usernameLocalStorage = localStorage.getItem(this.KEYS.USERNAME);
+ if (usernameLocalStorage) {
+ return new User(localStorage.getItem(this.KEYS.USERNAME), Tokens.load());
+ }
+
+ return null;
+ }
+
+ static flush() {
+ localStorage.removeItem(this.KEYS.USERNAME);
+ Tokens.flush();
+ }
+
+ constructor(username: string, hash_tokens: any) {
+ this.username = username;
+ this.tokens = new Tokens(hash_tokens);
+ }
+
+ getAccessToken() {
+ return this.tokens.access_token;
+ }
+
+ getRefreshToken() {
+ return this.tokens.refresh_token;
+ }
+
+ getTokenType() {
+ return this.tokens.token_type;
+ }
+
+ refreshTokens(access_token: string, refresh_token: string) {
+ this.tokens.access_token = access_token;
+ this.tokens.refresh_token = refresh_token;
+ }
+
+ save() {
+ localStorage.setItem('username', this.username);
+ this.tokens.save();
+ }
+}
+
+// Private class used only by User
+class Tokens {
+ private static KEYS = {
+ ACCESS_TOKEN: 'access_token',
+ REFRESH_TOKEN: 'refresh_token',
+ TOKEN_TYPE: 'token_type',
+ };
+
+ access_token: string;
+ refresh_token: string;
+ token_type: string;
+
+ static load() {
+ const accessTokenLocalStorage = localStorage.getItem(this.KEYS.ACCESS_TOKEN);
+ const refreshTokenLocalStorage = localStorage.getItem(this.KEYS.REFRESH_TOKEN);
+ const tokenTypeLocalStorage = localStorage.getItem(this.KEYS.TOKEN_TYPE);
+
+ if (accessTokenLocalStorage && refreshTokenLocalStorage && tokenTypeLocalStorage) {
+ return new Tokens({
+ access_token: accessTokenLocalStorage,
+ refresh_token: refreshTokenLocalStorage,
+ token_type: tokenTypeLocalStorage
+ });
+ }
+
+ return null;
+ }
+
+ static flush() {
+ localStorage.removeItem(this.KEYS.ACCESS_TOKEN);
+ localStorage.removeItem(this.KEYS.REFRESH_TOKEN);
+ localStorage.removeItem(this.KEYS.TOKEN_TYPE);
+ }
+
+ constructor(hash?: any) {
+ if (hash) {
+ this.access_token = hash.access_token;
+ this.refresh_token = hash.refresh_token;
+
+ if (hash.token_type === 'bearer') {
+ this.token_type = 'Bearer';
+ } else {
+ this.token_type = hash.token_type;
+ }
+ }
+ }
+
+ save() {
+ localStorage.setItem('access_token', this.access_token);
+ localStorage.setItem('refresh_token', this.refresh_token);
+ localStorage.setItem('token_type', this.token_type);
+ }
+}
+export * from './auth';
export * from './search';
-export * from './users'
+++ /dev/null
-export enum AuthStatus {
- LoggedIn,
- LoggedOut
-}
+++ /dev/null
-import { Injectable } from '@angular/core';
-import { Headers, Http, RequestOptions, Response, URLSearchParams } from '@angular/http';
-import { Observable } from 'rxjs/Observable';
-import { Subject } from 'rxjs/Subject';
-
-import { AuthStatus } from './auth-status.model';
-import { User } from './user.model';
-
-@Injectable()
-export class AuthService {
- private static BASE_CLIENT_URL = '/api/v1/users/client';
- private static BASE_LOGIN_URL = '/api/v1/users/token';
-
- loginChangedSource: Observable<AuthStatus>;
-
- private clientId: string;
- private clientSecret: string;
- private loginChanged: Subject<AuthStatus>;
-
- constructor(private http: Http) {
- this.loginChanged = new Subject<AuthStatus>();
- this.loginChangedSource = this.loginChanged.asObservable();
-
- // Fetch the client_id/client_secret
- // FIXME: save in local storage?
- this.http.get(AuthService.BASE_CLIENT_URL)
- .map(res => res.json())
- .catch(this.handleError)
- .subscribe(
- result => {
- this.clientId = result.client_id;
- this.clientSecret = result.client_secret;
- console.log('Client credentials loaded.');
- },
- error => {
- alert(error);
- }
- );
- }
-
- getAuthRequestOptions(): RequestOptions {
- return new RequestOptions({ headers: this.getRequestHeader() });
- }
-
- getRequestHeader() {
- return new Headers({ 'Authorization': this.getRequestHeaderValue() });
- }
-
- getRequestHeaderValue() {
- return `${this.getTokenType()} ${this.getToken()}`;
- }
-
- getToken() {
- return localStorage.getItem('access_token');
- }
-
- getTokenType() {
- return localStorage.getItem('token_type');
- }
-
- getUser(): User {
- if (this.isLoggedIn() === false) {
- return null;
- }
-
- const user = User.load();
-
- return user;
- }
-
- isLoggedIn() {
- if (this.getToken()) {
- return true;
- } else {
- return false;
- }
- }
-
- login(username: string, password: string) {
- let body = new URLSearchParams();
- body.set('client_id', this.clientId);
- body.set('client_secret', this.clientSecret);
- body.set('response_type', 'code');
- body.set('grant_type', 'password');
- body.set('scope', 'upload');
- body.set('username', username);
- body.set('password', password);
-
- let headers = new Headers();
- headers.append('Content-Type', 'application/x-www-form-urlencoded');
-
- let options = {
- headers: headers
- };
-
- return this.http.post(AuthService.BASE_LOGIN_URL, body.toString(), options)
- .map(res => res.json())
- .catch(this.handleError);
- }
-
- logout() {
- // TODO make HTTP request
- }
-
- setStatus(status: AuthStatus) {
- this.loginChanged.next(status);
- }
-
- private handleError (error: Response) {
- console.error(error);
- return Observable.throw(error.json() || { error: 'Server error' });
- }
-}
+++ /dev/null
-export * from './auth-status.model';
-export * from './auth.service';
-export * from './token.model';
-export * from './user.model';
+++ /dev/null
-export class Token {
- access_token: string;
- refresh_token: string;
- token_type: string;
-
- static load() {
- return new Token({
- access_token: localStorage.getItem('access_token'),
- refresh_token: localStorage.getItem('refresh_token'),
- token_type: localStorage.getItem('token_type')
- });
- }
-
- constructor(hash?: any) {
- if (hash) {
- this.access_token = hash.access_token;
- this.refresh_token = hash.refresh_token;
-
- if (hash.token_type === 'bearer') {
- this.token_type = 'Bearer';
- } else {
- this.token_type = hash.token_type;
- }
- }
- }
-
- save() {
- localStorage.setItem('access_token', this.access_token);
- localStorage.setItem('refresh_token', this.refresh_token);
- localStorage.setItem('token_type', this.token_type);
- }
-}
+++ /dev/null
-import { Token } from './token.model';
-
-export class User {
- username: string;
- token: Token;
-
- static load() {
- return new User(localStorage.getItem('username'), Token.load());
- }
-
- constructor(username: string, hash_token: any) {
- this.username = username;
- this.token = new Token(hash_token);
- }
-
- save() {
- localStorage.setItem('username', this.username);
- this.token.save();
- }
-}
import { Pagination } from './pagination.model';
import { Search } from '../../shared';
import { SortField } from './sort-field.type';
-import { AuthService } from '../../shared';
+import { AuthHttp, AuthService } from '../../shared';
import { Video } from './video.model';
@Injectable()
constructor(
private authService: AuthService,
+ private authHttp: AuthHttp,
private http: Http
) {}
}
removeVideo(id: string) {
- const options = this.authService.getAuthRequestOptions();
- return this.http.delete(VideoService.BASE_VIDEO_URL + id, options)
- .map(res => <number> res.status)
- .catch(this.handleError);
+ return this.authHttp.delete(VideoService.BASE_VIDEO_URL + id)
+ .map(res => <number> res.status)
+ .catch(this.handleError);
}
searchVideos(search: Search, pagination: Pagination, sort: SortField) {
};
item.onError = (response: string, status: number) => {
- this.error = (status === 400) ? response : 'Unknow error';
- console.error(this.error);
+ // We need to handle manually these cases beceause we use the FileUpload component
+ if (status === 400) {
+ this.error = response;
+ } else if (status === 401) {
+ this.error = 'Access token was expired, refreshing token...';
+ this.authService.refreshAccessToken().subscribe(
+ () => {
+ // Update the uploader request header
+ this.uploader.authToken = this.authService.getRequestHeaderValue();
+ this.error += ' access token refreshed. Please retry your request.';
+ }
+ );
+ } else {
+ this.error = 'Unknow error';
+ console.error(this.error);
+ }
};
-import { enableProdMode } from '@angular/core';
+import { enableProdMode, provide } from '@angular/core';
+import {
+ HTTP_PROVIDERS,
+ RequestOptions,
+ XHRBackend
+} from '@angular/http';
import { bootstrap } from '@angular/platform-browser-dynamic';
import { provideRouter } from '@angular/router';
import { AppComponent } from './app/app.component';
import { routes } from './app/app.routes';
+import { AuthHttp, AuthService } from './app/shared';
if (process.env.ENV === 'production') {
enableProdMode();
}
-bootstrap(AppComponent, [ provideRouter(routes) ]);
+bootstrap(AppComponent, [
+ HTTP_PROVIDERS,
+ provide(AuthHttp, {
+ useFactory: (backend: XHRBackend, defaultOptions: RequestOptions, authService: AuthService) => {
+ return new AuthHttp(backend, defaultOptions, authService);
+ },
+ deps: [ XHRBackend, RequestOptions, AuthService ]
+ }),
+ AuthService,
+ provideRouter(routes)
+]);
"src/app/login/index.ts",
"src/app/login/login.component.ts",
"src/app/login/login.routes.ts",
+ "src/app/shared/auth/auth-http.service.ts",
+ "src/app/shared/auth/auth-status.model.ts",
+ "src/app/shared/auth/auth.service.ts",
+ "src/app/shared/auth/index.ts",
+ "src/app/shared/auth/user.model.ts",
"src/app/shared/index.ts",
"src/app/shared/search/index.ts",
"src/app/shared/search/search-field.type.ts",
"src/app/shared/search/search.component.ts",
"src/app/shared/search/search.model.ts",
"src/app/shared/search/search.service.ts",
- "src/app/shared/users/auth-status.model.ts",
- "src/app/shared/users/auth.service.ts",
- "src/app/shared/users/index.ts",
- "src/app/shared/users/token.model.ts",
- "src/app/shared/users/user.model.ts",
"src/app/videos/index.ts",
"src/app/videos/shared/index.ts",
"src/app/videos/shared/loader/index.ts",