X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=client%2Fsrc%2Fapp%2Fcore%2Fauth%2Fauth.service.ts;h=0aa276c693b4d866baecd6818f54741487d51991;hb=1e1265b36c09df1465aa2b4866815c957b6a532e;hp=1f0e322a96dff25641eeb21278506d5246dac6ac;hpb=693b1aba4675f7e3d850e0f6d07dbfc7bdff9b8c;p=github%2FChocobozzz%2FPeerTube.git diff --git a/client/src/app/core/auth/auth.service.ts b/client/src/app/core/auth/auth.service.ts index 1f0e322a9..0aa276c69 100644 --- a/client/src/app/core/auth/auth.service.ts +++ b/client/src/app/core/auth/auth.service.ts @@ -1,213 +1,270 @@ -import { Injectable } from '@angular/core'; -import { Headers, Http, Response, URLSearchParams } from '@angular/http'; -import { Router } from '@angular/router'; -import { Observable } from 'rxjs/Observable'; -import { Subject } from 'rxjs/Subject'; - +import { Injectable } from '@angular/core' +import { Router } from '@angular/router' +import { Observable } from 'rxjs/Observable' +import { Subject } from 'rxjs/Subject' +import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http' +import { ReplaySubject } from 'rxjs/ReplaySubject' +import 'rxjs/add/operator/do' +import 'rxjs/add/operator/map' +import 'rxjs/add/operator/mergeMap' +import 'rxjs/add/observable/throw' + +import { NotificationsService } from 'angular2-notifications' + +import { AuthStatus } from './auth-status.model' +import { AuthUser } from './auth-user.model' +import { + OAuthClientLocal, + UserRole, + UserRefreshToken, + VideoChannel, + User as UserServerModel +} from '../../../../../shared' // Do not use the barrel (dependency loop) -import { AuthStatus } from '../../shared/auth/auth-status.model'; -import { AuthUser } from '../../shared/auth/auth-user.model'; -import { RestExtractor } from '../../shared/rest'; +import { RestExtractor } from '../../shared/rest' +import { UserLogin } from '../../../../../shared/models/users/user-login.model' +import { UserConstructorHash } from '../../shared/users/user.model' + +interface UserLoginWithUsername extends UserLogin { + access_token: string + refresh_token: string + token_type: string + username: string +} + +interface UserLoginWithUserInformation extends UserLogin { + access_token: string + refresh_token: string + token_type: string + username: string + id: number + role: UserRole + displayNSFW: boolean + email: string + videoQuota: number + account: { + id: number + uuid: string + } + videoChannels: VideoChannel[] +} @Injectable() export class AuthService { - private static BASE_CLIENT_URL = '/api/v1/clients/local'; - private static BASE_TOKEN_URL = '/api/v1/users/token'; - private static BASE_USER_INFORMATIONS_URL = '/api/v1/users/me'; + private static BASE_CLIENT_URL = API_URL + '/api/v1/oauth-clients/local' + private static BASE_TOKEN_URL = API_URL + '/api/v1/users/token' + private static BASE_USER_INFORMATION_URL = API_URL + '/api/v1/users/me' - loginChangedSource: Observable; + loginChangedSource: Observable + userInformationLoaded = new ReplaySubject(1) - private clientId: string; - private clientSecret: string; - private loginChanged: Subject; - private user: AuthUser = null; + private clientId: string + private clientSecret: string + private loginChanged: Subject + private user: AuthUser = null - constructor( - private http: Http, + constructor ( + private http: HttpClient, + private notificationsService: NotificationsService, private restExtractor: RestExtractor, private router: Router ) { - this.loginChanged = new Subject(); - this.loginChangedSource = this.loginChanged.asObservable(); + this.loginChanged = new Subject() + this.loginChangedSource = this.loginChanged.asObservable() + // Return null if there is nothing to load + this.user = AuthUser.load() + } + + loadClientCredentials () { // Fetch the client_id/client_secret // FIXME: save in local storage? - this.http.get(AuthService.BASE_CLIENT_URL) - .map(this.restExtractor.extractDataGet) - .catch((res) => this.restExtractor.handleError(res)) - .subscribe( - result => { - this.clientId = result.client_id; - this.clientSecret = result.client_secret; - console.log('Client credentials loaded.'); - }, - error => { - alert( - `Cannot retrieve OAuth Client credentials: ${error.text}. \n` + - 'Ensure you have correctly configured PeerTube (config/ directory), in particular the "webserver" section.' - ); - } - ); - - // Return null if there is nothing to load - this.user = AuthUser.load(); + this.http.get(AuthService.BASE_CLIENT_URL) + .catch(res => this.restExtractor.handleError(res)) + .subscribe( + res => { + this.clientId = res.client_id + this.clientSecret = res.client_secret + console.log('Client credentials loaded.') + }, + + error => { + let errorMessage = `Cannot retrieve OAuth Client credentials: ${error.text}. \n` + errorMessage += 'Ensure you have correctly configured PeerTube (config/ directory), in particular the "webserver" section.' + + // We put a bigger timeout + // This is an important message + this.notificationsService.error('Error', errorMessage, { timeOut: 7000 }) + } + ) } - getRefreshToken() { - if (this.user === null) return null; + getRefreshToken () { + if (this.user === null) return null - return this.user.getRefreshToken(); + return this.user.getRefreshToken() } - getRequestHeaderValue() { - return `${this.getTokenType()} ${this.getAccessToken()}`; - } + getRequestHeaderValue () { + const accessToken = this.getAccessToken() - getAccessToken() { - if (this.user === null) return null; + if (accessToken === null) return null - return this.user.getAccessToken(); + return `${this.getTokenType()} ${accessToken}` } - getTokenType() { - if (this.user === null) return null; + getAccessToken () { + if (this.user === null) return null - return this.user.getTokenType(); + return this.user.getAccessToken() } - getUser(): AuthUser { - return this.user; - } + getTokenType () { + if (this.user === null) return null - isAdmin() { - if (this.user === null) return false; - - return this.user.isAdmin(); + return this.user.getTokenType() } - isLoggedIn() { - if (this.getAccessToken()) { - return true; - } else { - return false; - } + getUser () { + return this.user } - 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 - }; + isLoggedIn () { + return !!this.getAccessToken() + } - return this.http.post(AuthService.BASE_TOKEN_URL, body.toString(), options) - .map(this.restExtractor.extractDataGet) - .map(res => { - res.username = username; - return res; - }) - .flatMap(res => this.fetchUserInformations(res)) + login (username: string, password: string) { + // Form url encoded + const body = new HttpParams().set('client_id', this.clientId) + .set('client_secret', this.clientSecret) + .set('response_type', 'code') + .set('grant_type', 'password') + .set('scope', 'upload') + .set('username', username) + .set('password', password) + + const headers = new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded') + + return this.http.post(AuthService.BASE_TOKEN_URL, body, { headers }) + .map(res => Object.assign(res, { username })) + .flatMap(res => this.mergeUserInformation(res)) .map(res => this.handleLogin(res)) - .catch((res) => this.restExtractor.handleError(res)); + .catch(res => this.restExtractor.handleError(res)) } - logout() { + logout () { // TODO: make an HTTP request to revoke the tokens - this.user = null; + this.user = null - AuthUser.flush(); + AuthUser.flush() - this.setStatus(AuthStatus.LoggedOut); + this.setStatus(AuthStatus.LoggedOut) } - refreshAccessToken() { - console.log('Refreshing token...'); + refreshAccessToken () { + console.log('Refreshing token...') - const refreshToken = this.getRefreshToken(); + 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'); + // Form url encoded + const body = new HttpParams().set('refresh_token', refreshToken) + .set('client_id', this.clientId) + .set('client_secret', this.clientSecret) + .set('response_type', 'code') + .set('grant_type', 'refresh_token') - let headers = new Headers(); - headers.append('Content-Type', 'application/x-www-form-urlencoded'); + const headers = new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded') - let options = { - headers: headers - }; - - return this.http.post(AuthService.BASE_TOKEN_URL, body.toString(), options) - .map(this.restExtractor.extractDataGet) + return this.http.post(AuthService.BASE_TOKEN_URL, body, { headers }) .map(res => this.handleRefreshToken(res)) - .catch((res: Response) => { + .catch(res => { // The refresh token is invalid? - if (res.status === 400 && res.json() && res.json().error === 'invalid_grant') { - console.error('Cannot refresh token -> logout...'); - this.logout(); - this.router.navigate(['/login']); + if (res.status === 400 && res.error.error === 'invalid_grant') { + console.error('Cannot refresh token -> logout...') + this.logout() + this.router.navigate(['/login']) return Observable.throw({ - json: () => '', - text: () => 'You need to reconnect.' - }); + error: 'You need to reconnect.' + }) } - return this.restExtractor.handleError(res); - }); + return this.restExtractor.handleError(res) + }) } - private fetchUserInformations (obj: any) { - // Do not call authHttp here to avoid circular dependencies headaches + refreshUserInformation () { + const obj = { + access_token: this.user.getAccessToken(), + refresh_token: null, + token_type: this.user.getTokenType(), + username: this.user.username + } - const headers = new Headers(); - headers.set('Authorization', `Bearer ${obj.access_token}`); + this.mergeUserInformation(obj) + .do(() => this.userInformationLoaded.next(true)) + .subscribe( + res => { + this.user.displayNSFW = res.displayNSFW + this.user.role = res.role + this.user.videoChannels = res.videoChannels + this.user.account = res.account - return this.http.get(AuthService.BASE_USER_INFORMATIONS_URL, { headers }) - .map(res => res.json()) - .map(res => { - obj.id = res.id; - obj.role = res.role; - return obj; - } - ); + this.user.save() + } + ) } - private handleLogin (obj: any) { - const id = obj.id; - const username = obj.username; - const role = obj.role; - const hashTokens = { - access_token: obj.access_token, - token_type: obj.token_type, - refresh_token: obj.refresh_token - }; + private mergeUserInformation (obj: UserLoginWithUsername): Observable { + // User is not loaded yet, set manually auth header + const headers = new HttpHeaders().set('Authorization', `${obj.token_type} ${obj.access_token}`) - this.user = new AuthUser({ id, username, role }, hashTokens); - this.user.save(); + return this.http.get(AuthService.BASE_USER_INFORMATION_URL, { headers }) + .map(res => { + const newProperties = { + id: res.id, + role: res.role, + displayNSFW: res.displayNSFW, + email: res.email, + videoQuota: res.videoQuota, + account: res.account, + videoChannels: res.videoChannels + } - this.setStatus(AuthStatus.LoggedIn); + return Object.assign(obj, newProperties) + } + ) } - private handleRefreshToken (obj: any) { - this.user.refreshTokens(obj.access_token, obj.refresh_token); - this.user.save(); + private handleLogin (obj: UserLoginWithUserInformation) { + const hashUser: UserConstructorHash = { + id: obj.id, + username: obj.username, + role: obj.role, + email: obj.email, + displayNSFW: obj.displayNSFW, + videoQuota: obj.videoQuota, + videoChannels: obj.videoChannels, + account: obj.account + } + const hashTokens = { + accessToken: obj.access_token, + tokenType: obj.token_type, + refreshToken: obj.refresh_token + } + + this.user = new AuthUser(hashUser, hashTokens) + this.user.save() + + this.setStatus(AuthStatus.LoggedIn) } - private setStatus(status: AuthStatus) { - this.loginChanged.next(status); + private handleRefreshToken (obj: UserRefreshToken) { + this.user.refreshTokens(obj.access_token, obj.refresh_token) + this.user.save() } + private setStatus (status: AuthStatus) { + this.loginChanged.next(status) + } }