From ab398a05e9ffaacb8fc713bb2ba9717ac463b34c Mon Sep 17 00:00:00 2001
From: Rigel Kent <sendmemail@rigelk.eu>
Date: Sun, 24 Jan 2021 03:02:04 +0100
Subject: [PATCH] redirect to login on 401, display error variants in 404
 component

---
 .../src/app/+accounts/accounts.component.ts   |  2 +-
 .../page-not-found.component.html             | 15 ++++++++++---
 .../page-not-found.component.ts               | 22 +++++++++++++++++--
 .../video-channels.component.ts               |  2 +-
 .../+video-watch/video-watch.component.ts     | 13 +++++------
 .../app/core/rest/rest-extractor.service.ts   |  4 ++--
 .../auth/auth-interceptor.service.ts          | 19 +++++++++++-----
 7 files changed, 55 insertions(+), 22 deletions(-)

diff --git a/client/src/app/+accounts/accounts.component.ts b/client/src/app/+accounts/accounts.component.ts
index 1458ea59c..e6a5a5d5e 100644
--- a/client/src/app/+accounts/accounts.component.ts
+++ b/client/src/app/+accounts/accounts.component.ts
@@ -50,7 +50,7 @@ export class AccountsComponent implements OnInit, OnDestroy {
                           switchMap(accountId => this.accountService.getAccount(accountId)),
                           tap(account => this.onAccount(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.NOT_FOUND_404
                           ]))
diff --git a/client/src/app/+page-not-found/page-not-found.component.html b/client/src/app/+page-not-found/page-not-found.component.html
index aa57b07e8..efd3cc9f9 100644
--- a/client/src/app/+page-not-found/page-not-found.component.html
+++ b/client/src/app/+page-not-found/page-not-found.component.html
@@ -1,23 +1,32 @@
 <div class="root">
-  <div *ngIf="status === 404" class="box">
+  <div *ngIf="status !== 403 && status !== 418" class="box">
     <strong>{{ status }}.</strong>
     <span class="ml-1 text-muted" i18n>That's an error.</span>
 
     <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 class="text-muted mt-4">
       <span i18n="Possible reasons preceding a list of reasons a `Not Found` error page may occur">Possible reasons:</span>
 
       <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>The {{ getRessourceName() }} may have been moved or deleted</li>
         <li i18n>You may have typed the address or URL incorrectly</li>
       </ul>
     </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">
     <strong>{{ status }}.</strong>
     <span class="ml-1 text-muted">I'm a teapot.</span>
diff --git a/client/src/app/+page-not-found/page-not-found.component.ts b/client/src/app/+page-not-found/page-not-found.component.ts
index 81830d415..9302201ea 100644
--- a/client/src/app/+page-not-found/page-not-found.component.ts
+++ b/client/src/app/+page-not-found/page-not-found.component.ts
@@ -1,5 +1,6 @@
 import { Component, OnInit } from '@angular/core'
 import { Title } from '@angular/platform-browser'
+import { Router } from '@angular/router'
 import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes'
 
 @Component({
@@ -9,10 +10,16 @@ import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes'
 })
 export class PageNotFoundComponent implements OnInit {
   status = HttpStatusCode.NOT_FOUND_404
+  type: string
 
   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 () {
     if (this.pathname.includes('teapot')) {
@@ -25,10 +32,21 @@ export class PageNotFoundComponent implements OnInit {
     return window.location.pathname
   }
 
+  getRessourceName () {
+    switch (this.type) {
+      case 'video':
+        return $localize`video`
+      default:
+        return $localize`ressource`
+    }
+  }
+
   getMascotName () {
     switch (this.status) {
       case HttpStatusCode.I_AM_A_TEAPOT_418:
         return 'happy'
+      case HttpStatusCode.FORBIDDEN_403:
+        return 'arguing'
       case HttpStatusCode.NOT_FOUND_404:
       default:
         return 'defeated'
diff --git a/client/src/app/+video-channels/video-channels.component.ts b/client/src/app/+video-channels/video-channels.component.ts
index d2fd265c4..bb601e227 100644
--- a/client/src/app/+video-channels/video-channels.component.ts
+++ b/client/src/app/+video-channels/video-channels.component.ts
@@ -38,7 +38,7 @@ export class VideoChannelsComponent implements OnInit, OnDestroy {
                           map(params => params[ 'videoChannelName' ]),
                           distinctUntilChanged(),
                           switchMap(videoChannelName => this.videoChannelService.getVideoChannel(videoChannelName)),
-                          catchError(err => this.restExtractor.redirectTo404IfNotFound(err, [
+                          catchError(err => this.restExtractor.redirectTo404IfNotFound(err, 'other', [
                             HttpStatusCode.BAD_REQUEST_400,
                             HttpStatusCode.NOT_FOUND_404
                           ]))
diff --git a/client/src/app/+videos/+video-watch/video-watch.component.ts b/client/src/app/+videos/+video-watch/video-watch.component.ts
index c757a5e93..b698d554f 100644
--- a/client/src/app/+videos/+video-watch/video-watch.component.ts
+++ b/client/src/app/+videos/+video-watch/video-watch.component.ts
@@ -404,7 +404,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
       this.videoCaptionService.listCaptions(videoId)
     ])
       .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 => {
           if (err.body.errorCode === ServerErrorCode.DOES_NOT_RESPECT_FOLLOW_CONSTRAINTS && err.body.originUrl) {
             const search = window.location.search
@@ -416,9 +416,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
               $localize`Redirection`
             ).then(res => {
               if (res === false) {
-                return this.restExtractor.redirectTo404IfNotFound(err, [
+                return this.restExtractor.redirectTo404IfNotFound(err, 'video', [
                   HttpStatusCode.BAD_REQUEST_400,
-                  HttpStatusCode.UNAUTHORIZED_401,
                   HttpStatusCode.FORBIDDEN_403,
                   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.UNAUTHORIZED_401,
             HttpStatusCode.FORBIDDEN_403,
             HttpStatusCode.NOT_FOUND_404
           ])
@@ -464,10 +462,9 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
 
     this.playlistService.getVideoPlaylist(playlistId)
       .pipe(
-        // If 401, the video is private or blocked so redirect to 404
-        catchError(err => this.restExtractor.redirectTo404IfNotFound(err, [
+        // If 400 or 403, the video is private or blocked so redirect to 404
+        catchError(err => this.restExtractor.redirectTo404IfNotFound(err, 'video', [
           HttpStatusCode.BAD_REQUEST_400,
-          HttpStatusCode.UNAUTHORIZED_401,
           HttpStatusCode.FORBIDDEN_403,
           HttpStatusCode.NOT_FOUND_404
         ]))
diff --git a/client/src/app/core/rest/rest-extractor.service.ts b/client/src/app/core/rest/rest-extractor.service.ts
index 84d9ed074..b8a95cca6 100644
--- a/client/src/app/core/rest/rest-extractor.service.ts
+++ b/client/src/app/core/rest/rest-extractor.service.ts
@@ -93,10 +93,10 @@ export class RestExtractor {
     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) {
       // 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)
diff --git a/client/src/app/shared/shared-main/auth/auth-interceptor.service.ts b/client/src/app/shared/shared-main/auth/auth-interceptor.service.ts
index 68a4acdb5..3ddaffbdf 100644
--- a/client/src/app/shared/shared-main/auth/auth-interceptor.service.ts
+++ b/client/src/app/shared/shared-main/auth/auth-interceptor.service.ts
@@ -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 { 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 { AuthService } from '@app/core/auth/auth.service'
+import { Router } from '@angular/router'
+import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes'
 
 @Injectable()
 export class AuthInterceptor implements HttpInterceptor {
   private authService: AuthService
 
   // 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>> {
     if (this.authService === undefined) {
@@ -22,9 +24,11 @@ export class AuthInterceptor implements HttpInterceptor {
     // Catch 401 errors (refresh token expired)
     return next.handle(authReq)
                .pipe(
-                 catchError(err => {
-                   if (err.status === 401 && err.error && err.error.code === 'invalid_token') {
+                 catchError((err: HttpErrorResponse) => {
+                   if (err.status === HttpStatusCode.UNAUTHORIZED_401 && err.error && err.error.code === 'invalid_token') {
                      return this.handleTokenExpired(req, next)
+                   } else if (err.status === HttpStatusCode.UNAUTHORIZED_401) {
+                     return this.handleNotAuthenticated(err)
                    }
 
                    return observableThrowError(err)
@@ -51,6 +55,11 @@ export class AuthInterceptor implements HttpInterceptor {
     // Clone the request to add the new header
     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 = {