redirect to login on 401, display error variants in 404 component

This commit is contained in:
Rigel Kent 2021-01-24 03:02:04 +01:00 committed by Chocobozzz
parent 6939cbac48
commit ab398a05e9
7 changed files with 55 additions and 22 deletions

View File

@ -50,7 +50,7 @@ export class AccountsComponent implements OnInit, OnDestroy {
switchMap(accountId => this.accountService.getAccount(accountId)), switchMap(accountId => this.accountService.getAccount(accountId)),
tap(account => this.onAccount(account)), tap(account => this.onAccount(account)),
switchMap(account => this.videoChannelService.listAccountVideoChannels(account)), switchMap(account => this.videoChannelService.listAccountVideoChannels(account)),
catchError(err => this.restExtractor.redirectTo404IfNotFound(err, [ catchError(err => this.restExtractor.redirectTo404IfNotFound(err, 'other', [
HttpStatusCode.BAD_REQUEST_400, HttpStatusCode.BAD_REQUEST_400,
HttpStatusCode.NOT_FOUND_404 HttpStatusCode.NOT_FOUND_404
])) ]))

View File

@ -1,23 +1,32 @@
<div class="root"> <div class="root">
<div *ngIf="status === 404" class="box"> <div *ngIf="status !== 403 && status !== 418" class="box">
<strong>{{ status }}.</strong> <strong>{{ status }}.</strong>
<span class="ml-1 text-muted" i18n>That's an error.</span> <span class="ml-1 text-muted" i18n>That's an error.</span>
<div class="text mt-4" i18n> <div class="text mt-4" i18n>
We couldn't find any ressource tied to the URL {{ pathname }} you were looking for. We couldn't find any {{ getRessourceName() }} tied to the URL {{ pathname }} you were looking for.
</div> </div>
<div class="text-muted mt-4"> <div class="text-muted mt-4">
<span i18n="Possible reasons preceding a list of reasons a `Not Found` error page may occur">Possible reasons:</span> <span i18n="Possible reasons preceding a list of reasons a `Not Found` error page may occur">Possible reasons:</span>
<ul> <ul>
<li i18n>The page may have been moved or deleted</li>
<li i18n>You may have used an outdated or broken link</li> <li i18n>You may have used an outdated or broken link</li>
<li i18n>The {{ getRessourceName() }} may have been moved or deleted</li>
<li i18n>You may have typed the address or URL incorrectly</li> <li i18n>You may have typed the address or URL incorrectly</li>
</ul> </ul>
</div> </div>
</div> </div>
<div *ngIf="status === 403" class="box">
<strong>{{ status }}.</strong>
<span class="ml-1 text-muted" i18n>You are not authorized here.</span>
<div class="text mt-4" i18n>
You might need to check your account is allowed by the {{ getRessourceName() }} or instance owner.
</div>
</div>
<div *ngIf="status === 418" class="box"> <div *ngIf="status === 418" class="box">
<strong>{{ status }}.</strong> <strong>{{ status }}.</strong>
<span class="ml-1 text-muted">I'm a teapot.</span> <span class="ml-1 text-muted">I'm a teapot.</span>

View File

@ -1,5 +1,6 @@
import { Component, OnInit } from '@angular/core' import { Component, OnInit } from '@angular/core'
import { Title } from '@angular/platform-browser' import { Title } from '@angular/platform-browser'
import { Router } from '@angular/router'
import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes'
@Component({ @Component({
@ -9,10 +10,16 @@ import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes'
}) })
export class PageNotFoundComponent implements OnInit { export class PageNotFoundComponent implements OnInit {
status = HttpStatusCode.NOT_FOUND_404 status = HttpStatusCode.NOT_FOUND_404
type: string
public constructor ( public constructor (
private titleService: Title private titleService: Title,
) {} private router: Router
) {
const state = this.router.getCurrentNavigation()?.extras.state
this.type = state?.type || this.type
this.status = state?.obj.status || this.status
}
ngOnInit () { ngOnInit () {
if (this.pathname.includes('teapot')) { if (this.pathname.includes('teapot')) {
@ -25,10 +32,21 @@ export class PageNotFoundComponent implements OnInit {
return window.location.pathname return window.location.pathname
} }
getRessourceName () {
switch (this.type) {
case 'video':
return $localize`video`
default:
return $localize`ressource`
}
}
getMascotName () { getMascotName () {
switch (this.status) { switch (this.status) {
case HttpStatusCode.I_AM_A_TEAPOT_418: case HttpStatusCode.I_AM_A_TEAPOT_418:
return 'happy' return 'happy'
case HttpStatusCode.FORBIDDEN_403:
return 'arguing'
case HttpStatusCode.NOT_FOUND_404: case HttpStatusCode.NOT_FOUND_404:
default: default:
return 'defeated' return 'defeated'

View File

@ -38,7 +38,7 @@ export class VideoChannelsComponent implements OnInit, OnDestroy {
map(params => params[ 'videoChannelName' ]), map(params => params[ 'videoChannelName' ]),
distinctUntilChanged(), distinctUntilChanged(),
switchMap(videoChannelName => this.videoChannelService.getVideoChannel(videoChannelName)), switchMap(videoChannelName => this.videoChannelService.getVideoChannel(videoChannelName)),
catchError(err => this.restExtractor.redirectTo404IfNotFound(err, [ catchError(err => this.restExtractor.redirectTo404IfNotFound(err, 'other', [
HttpStatusCode.BAD_REQUEST_400, HttpStatusCode.BAD_REQUEST_400,
HttpStatusCode.NOT_FOUND_404 HttpStatusCode.NOT_FOUND_404
])) ]))

View File

@ -404,7 +404,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
this.videoCaptionService.listCaptions(videoId) this.videoCaptionService.listCaptions(videoId)
]) ])
.pipe( .pipe(
// If 401, the video is private or blocked so redirect to 404 // If 400, 403 or 404, the video is private or blocked so redirect to 404
catchError(err => { catchError(err => {
if (err.body.errorCode === ServerErrorCode.DOES_NOT_RESPECT_FOLLOW_CONSTRAINTS && err.body.originUrl) { if (err.body.errorCode === ServerErrorCode.DOES_NOT_RESPECT_FOLLOW_CONSTRAINTS && err.body.originUrl) {
const search = window.location.search const search = window.location.search
@ -416,9 +416,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
$localize`Redirection` $localize`Redirection`
).then(res => { ).then(res => {
if (res === false) { if (res === false) {
return this.restExtractor.redirectTo404IfNotFound(err, [ return this.restExtractor.redirectTo404IfNotFound(err, 'video', [
HttpStatusCode.BAD_REQUEST_400, HttpStatusCode.BAD_REQUEST_400,
HttpStatusCode.UNAUTHORIZED_401,
HttpStatusCode.FORBIDDEN_403, HttpStatusCode.FORBIDDEN_403,
HttpStatusCode.NOT_FOUND_404 HttpStatusCode.NOT_FOUND_404
]) ])
@ -428,9 +427,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
}) })
} }
return this.restExtractor.redirectTo404IfNotFound(err, [ return this.restExtractor.redirectTo404IfNotFound(err, 'video', [
HttpStatusCode.BAD_REQUEST_400, HttpStatusCode.BAD_REQUEST_400,
HttpStatusCode.UNAUTHORIZED_401,
HttpStatusCode.FORBIDDEN_403, HttpStatusCode.FORBIDDEN_403,
HttpStatusCode.NOT_FOUND_404 HttpStatusCode.NOT_FOUND_404
]) ])
@ -464,10 +462,9 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
this.playlistService.getVideoPlaylist(playlistId) this.playlistService.getVideoPlaylist(playlistId)
.pipe( .pipe(
// If 401, the video is private or blocked so redirect to 404 // If 400 or 403, the video is private or blocked so redirect to 404
catchError(err => this.restExtractor.redirectTo404IfNotFound(err, [ catchError(err => this.restExtractor.redirectTo404IfNotFound(err, 'video', [
HttpStatusCode.BAD_REQUEST_400, HttpStatusCode.BAD_REQUEST_400,
HttpStatusCode.UNAUTHORIZED_401,
HttpStatusCode.FORBIDDEN_403, HttpStatusCode.FORBIDDEN_403,
HttpStatusCode.NOT_FOUND_404 HttpStatusCode.NOT_FOUND_404
])) ]))

View File

@ -93,10 +93,10 @@ export class RestExtractor {
return observableThrowError(errorObj) return observableThrowError(errorObj)
} }
redirectTo404IfNotFound (obj: { status: number }, status = [ HttpStatusCode.NOT_FOUND_404 ]) { redirectTo404IfNotFound (obj: { status: number }, type: 'video' | 'other', status = [ HttpStatusCode.NOT_FOUND_404 ]) {
if (obj && obj.status && status.indexOf(obj.status) !== -1) { if (obj && obj.status && status.indexOf(obj.status) !== -1) {
// Do not use redirectService to avoid circular dependencies // Do not use redirectService to avoid circular dependencies
this.router.navigate([ '/404' ], { skipLocationChange: true }) this.router.navigate([ '/404' ], { state: { type, obj }, skipLocationChange: true })
} }
return observableThrowError(obj) return observableThrowError(obj)

View File

@ -1,15 +1,17 @@
import { Observable, throwError as observableThrowError } from 'rxjs' import { Observable, of, throwError as observableThrowError } from 'rxjs'
import { catchError, switchMap } from 'rxjs/operators' import { catchError, switchMap } from 'rxjs/operators'
import { HTTP_INTERCEPTORS, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http' import { HTTP_INTERCEPTORS, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpErrorResponse } from '@angular/common/http'
import { Injectable, Injector } from '@angular/core' import { Injectable, Injector } from '@angular/core'
import { AuthService } from '@app/core/auth/auth.service' import { AuthService } from '@app/core/auth/auth.service'
import { Router } from '@angular/router'
import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes'
@Injectable() @Injectable()
export class AuthInterceptor implements HttpInterceptor { export class AuthInterceptor implements HttpInterceptor {
private authService: AuthService private authService: AuthService
// https://github.com/angular/angular/issues/18224#issuecomment-316957213 // https://github.com/angular/angular/issues/18224#issuecomment-316957213
constructor (private injector: Injector) {} constructor (private injector: Injector, private router: Router) {}
intercept (req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { intercept (req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (this.authService === undefined) { if (this.authService === undefined) {
@ -22,9 +24,11 @@ export class AuthInterceptor implements HttpInterceptor {
// Catch 401 errors (refresh token expired) // Catch 401 errors (refresh token expired)
return next.handle(authReq) return next.handle(authReq)
.pipe( .pipe(
catchError(err => { catchError((err: HttpErrorResponse) => {
if (err.status === 401 && err.error && err.error.code === 'invalid_token') { if (err.status === HttpStatusCode.UNAUTHORIZED_401 && err.error && err.error.code === 'invalid_token') {
return this.handleTokenExpired(req, next) return this.handleTokenExpired(req, next)
} else if (err.status === HttpStatusCode.UNAUTHORIZED_401) {
return this.handleNotAuthenticated(err)
} }
return observableThrowError(err) return observableThrowError(err)
@ -51,6 +55,11 @@ export class AuthInterceptor implements HttpInterceptor {
// Clone the request to add the new header // Clone the request to add the new header
return req.clone({ headers: req.headers.set('Authorization', authHeaderValue) }) return req.clone({ headers: req.headers.set('Authorization', authHeaderValue) })
} }
private handleNotAuthenticated (err: HttpErrorResponse, path = '/login'): Observable<any> {
this.router.navigateByUrl(path)
return of(err.message)
}
} }
export const AUTH_INTERCEPTOR_PROVIDER = { export const AUTH_INTERCEPTOR_PROVIDER = {