X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=client%2Fsrc%2Fapp%2Fcore%2Fauth%2Fauth.service.ts;h=4de28e51e9d95ee53c969b882e53065a43e617ca;hb=3545e72c686ff1725bbdfd8d16d693e2f4aa75a3;hp=de8c509d1d6a3d7345792fafe50aa7c35ac06f96;hpb=610d0be13b3d01f653ef269271dd667a57c85ef2;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 de8c509d1..4de28e51e 100644 --- a/client/src/app/core/auth/auth.service.ts +++ b/client/src/app/core/auth/auth.service.ts @@ -1,20 +1,16 @@ +import { Hotkey, HotkeysService } from 'angular2-hotkeys' import { Observable, ReplaySubject, Subject, throwError as observableThrowError } from 'rxjs' import { catchError, map, mergeMap, share, tap } from 'rxjs/operators' -import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http' +import { HttpClient, HttpErrorResponse, HttpHeaders, HttpParams } from '@angular/common/http' import { Injectable } from '@angular/core' import { Router } from '@angular/router' import { Notifier } from '@app/core/notification/notifier.service' -import { OAuthClientLocal, MyUser as UserServerModel, UserRefreshToken } from '../../../../../shared' -import { User } from '../../../../../shared/models/users' -import { UserLogin } from '../../../../../shared/models/users/user-login.model' +import { logger, OAuthUserTokens, objectToUrlEncoded, peertubeLocalStorage } from '@root-helpers/index' +import { HttpStatusCode, MyUser as UserServerModel, OAuthClientLocal, User, UserLogin, UserRefreshToken } from '@shared/models' import { environment } from '../../../environments/environment' -import { RestExtractor } from '../../shared/rest/rest-extractor.service' +import { RestExtractor } from '../rest/rest-extractor.service' import { AuthStatus } from './auth-status.model' import { AuthUser } from './auth-user.model' -import { objectToUrlEncoded } from '@app/shared/misc/utils' -import { peertubeLocalStorage } from '@app/shared/misc/peertube-web-storage' -import { I18n } from '@ngx-translate/i18n-polyfill' -import { Hotkey, HotkeysService } from 'angular2-hotkeys' interface UserLoginWithUsername extends UserLogin { access_token: string @@ -38,6 +34,7 @@ export class AuthService { loginChangedSource: Observable userInformationLoaded = new ReplaySubject(1) + tokensRefreshed = new ReplaySubject(1) hotkeys: Hotkey[] private clientId: string = peertubeLocalStorage.getItem(AuthService.LOCAL_STORAGE_OAUTH_CLIENT_KEYS.CLIENT_ID) @@ -51,65 +48,63 @@ export class AuthService { private notifier: Notifier, private hotkeysService: HotkeysService, private restExtractor: RestExtractor, - private router: Router, - private i18n: I18n + private router: Router ) { this.loginChanged = new Subject() this.loginChangedSource = this.loginChanged.asObservable() - // Return null if there is nothing to load - this.user = AuthUser.load() - // Set HotKeys this.hotkeys = [ new Hotkey('m s', (event: KeyboardEvent): boolean => { this.router.navigate([ '/videos/subscriptions' ]) return false - }, undefined, this.i18n('Go to my subscriptions')), + }, undefined, $localize`Go to my subscriptions`), new Hotkey('m v', (event: KeyboardEvent): boolean => { - this.router.navigate([ '/my-account/videos' ]) + this.router.navigate([ '/my-library/videos' ]) return false - }, undefined, this.i18n('Go to my videos')), + }, undefined, $localize`Go to my videos`), new Hotkey('m i', (event: KeyboardEvent): boolean => { - this.router.navigate([ '/my-account/video-imports' ]) + this.router.navigate([ '/my-library/video-imports' ]) return false - }, undefined, this.i18n('Go to my imports')), + }, undefined, $localize`Go to my imports`), new Hotkey('m c', (event: KeyboardEvent): boolean => { - this.router.navigate([ '/my-account/video-channels' ]) + this.router.navigate([ '/my-library/video-channels' ]) return false - }, undefined, this.i18n('Go to my channels')) + }, undefined, $localize`Go to my channels`) ] } + buildAuthUser (userInfo: Partial, tokens: OAuthUserTokens) { + this.user = new AuthUser(userInfo, tokens) + } + loadClientCredentials () { // Fetch the client_id/client_secret this.http.get(AuthService.BASE_CLIENT_URL) .pipe(catchError(res => this.restExtractor.handleError(res))) - .subscribe( - res => { + .subscribe({ + next: res => { this.clientId = res.client_id this.clientSecret = res.client_secret peertubeLocalStorage.setItem(AuthService.LOCAL_STORAGE_OAUTH_CLIENT_KEYS.CLIENT_ID, this.clientId) peertubeLocalStorage.setItem(AuthService.LOCAL_STORAGE_OAUTH_CLIENT_KEYS.CLIENT_SECRET, this.clientSecret) - console.log('Client credentials loaded.') + logger.info('Client credentials loaded.') }, - error => { - let errorMessage = error.message + error: err => { + let errorMessage = err.message - if (error.status === 403) { - errorMessage = this.i18n('Cannot retrieve OAuth Client credentials: {{errorText}}.\n', { errorText: error.text }) - errorMessage += this.i18n( - 'Ensure you have correctly configured PeerTube (config/ directory), in particular the "webserver" section.' - ) + if (err.status === HttpStatusCode.FORBIDDEN_403) { + errorMessage = $localize`Cannot retrieve OAuth Client credentials: ${err.message}. +Ensure you have correctly configured PeerTube (config/ directory), in particular the "webserver" section.` } // We put a bigger timeout: this is an important message - this.notifier.error(errorMessage, this.i18n('Error'), 7000) + this.notifier.error(errorMessage, $localize`Error`, 7000) } - ) + }) } getRefreshToken () { @@ -146,7 +141,14 @@ export class AuthService { return !!this.getAccessToken() } - login (username: string, password: string, token?: string) { + login (options: { + username: string + password: string + otpToken?: string + token?: string + }) { + const { username, password, token, otpToken } = options + // Form url encoded const body = { client_id: this.clientId, @@ -160,7 +162,9 @@ export class AuthService { if (token) Object.assign(body, { externalAuthToken: token }) - const headers = new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded') + let headers = new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded') + if (otpToken) headers = headers.set('x-peertube-otp', otpToken) + return this.http.post(AuthService.BASE_TOKEN_URL, objectToUrlEncoded(body), { headers }) .pipe( map(res => Object.assign(res, { username })), @@ -174,17 +178,19 @@ export class AuthService { const authHeaderValue = this.getRequestHeaderValue() const headers = new HttpHeaders().set('Authorization', authHeaderValue) - this.http.post(AuthService.BASE_REVOKE_TOKEN_URL, {}, { headers }) - .subscribe( - () => { /* nothing to do */ }, + this.http.post<{ redirectUrl?: string }>(AuthService.BASE_REVOKE_TOKEN_URL, {}, { headers }) + .subscribe({ + next: res => { + if (res.redirectUrl) { + window.location.href = res.redirectUrl + } + }, - err => console.error(err) - ) + error: err => logger.error(err) + }) this.user = null - AuthUser.flush() - this.setStatus(AuthStatus.LoggedOut) this.hotkeysService.remove(this.hotkeys) @@ -193,7 +199,7 @@ export class AuthService { refreshAccessToken () { if (this.refreshingTokenObservable) return this.refreshingTokenObservable - console.log('Refreshing token...') + logger.info('Refreshing token...') const refreshToken = this.getRefreshToken() @@ -209,18 +215,20 @@ export class AuthService { this.refreshingTokenObservable = this.http.post(AuthService.BASE_TOKEN_URL, body, { headers }) .pipe( map(res => this.handleRefreshToken(res)), - tap(() => this.refreshingTokenObservable = null), + tap(() => { + this.refreshingTokenObservable = null + }), catchError(err => { this.refreshingTokenObservable = null - console.error(err) - console.log('Cannot refresh token -> logout...') + logger.error(err) + logger.info('Cannot refresh token -> logout...') this.logout() this.router.navigate([ '/login' ]) - return observableThrowError({ - error: this.i18n('You need to reconnect.') - }) + return observableThrowError(() => ({ + error: $localize`You need to reconnect.` + })) }), share() ) @@ -237,14 +245,21 @@ export class AuthService { } this.mergeUserInformation(obj) - .subscribe( - res => { + .subscribe({ + next: res => { this.user.patch(res) - this.user.save() this.userInformationLoaded.next(true) } - ) + }) + } + + isOTPMissingError (err: HttpErrorResponse) { + if (err.status !== HttpStatusCode.UNAUTHORIZED_401) return false + + if (err.headers.get('x-peertube-otp') !== 'required; app') return false + + return true } private mergeUserInformation (obj: UserLoginWithUsername): Observable { @@ -263,7 +278,6 @@ export class AuthService { } this.user = new AuthUser(obj, hashTokens) - this.user.save() this.setStatus(AuthStatus.LoggedIn) this.userInformationLoaded.next(true) @@ -273,7 +287,7 @@ export class AuthService { private handleRefreshToken (obj: UserRefreshToken) { this.user.refreshTokens(obj.access_token, obj.refresh_token) - this.user.save() + this.tokensRefreshed.next() } private setStatus (status: AuthStatus) {