Fix frontend not redirecting on 401 (#1244)
- Ensures need-login event bubbles until handled - Redirects on 401 from /refresh endpoint - Go to previous URL upon login, rather than always to home page - Shows accurate login notification (rather than less precise "couldn't retrieve org" or similar message)
This commit is contained in:
		
							parent
							
								
									38efeccc25
								
							
						
					
					
						commit
						f2261bcb34
					
				| @ -13,7 +13,7 @@ import type { NotifyEvent, NavigateEvent } from "./utils/LiteElement"; | ||||
| import LiteElement, { html } from "./utils/LiteElement"; | ||||
| import APIRouter from "./utils/APIRouter"; | ||||
| import AuthService, { AuthState } from "./utils/AuthService"; | ||||
| import type { LoggedInEvent } from "./utils/AuthService"; | ||||
| import type { LoggedInEvent, NeedLoginEvent } from "./utils/AuthService"; | ||||
| import type { ViewState } from "./utils/APIRouter"; | ||||
| import type { CurrentUser, UserOrg } from "./types/user"; | ||||
| import type { AuthStorageEventData } from "./utils/AuthService"; | ||||
| @ -107,6 +107,7 @@ export class App extends LiteElement { | ||||
|     } | ||||
|     super.connectedCallback(); | ||||
| 
 | ||||
|     window.addEventListener("need-login", this.onNeedLogin); | ||||
|     window.addEventListener("popstate", () => { | ||||
|       this.syncViewState(); | ||||
|     }); | ||||
| @ -585,7 +586,8 @@ export class App extends LiteElement { | ||||
|           @logged-in=${this.onLoggedIn} | ||||
|           .authState=${this.authService.authState} | ||||
|           .viewState=${this.viewState} | ||||
|           redirectUrl=${this.viewState.params.redirectUrl} | ||||
|           redirectUrl=${this.viewState.params.redirectUrl || | ||||
|           this.viewState.data?.redirectUrl} | ||||
|         ></btrix-log-in>`; | ||||
| 
 | ||||
|       case "resetPassword": | ||||
| @ -616,7 +618,6 @@ export class App extends LiteElement { | ||||
|         return html`<btrix-orgs
 | ||||
|           class="w-full md:bg-neutral-50" | ||||
|           @navigate="${this.onNavigateTo}" | ||||
|           @need-login="${this.onNeedLogin}" | ||||
|           .authState="${this.authService.authState}" | ||||
|           .userInfo="${this.userInfo}" | ||||
|         ></btrix-orgs>`; | ||||
| @ -632,7 +633,6 @@ export class App extends LiteElement { | ||||
|         return html`<btrix-org
 | ||||
|           class="w-full" | ||||
|           @navigate=${this.onNavigateTo} | ||||
|           @need-login=${this.onNeedLogin} | ||||
|           @update-user-info=${(e: CustomEvent) => { | ||||
|             e.stopPropagation(); | ||||
|             this.updateUserInfo(); | ||||
| @ -653,7 +653,6 @@ export class App extends LiteElement { | ||||
|           class="w-full max-w-screen-lg mx-auto p-2 md:py-8 box-border" | ||||
|           @navigate="${this.onNavigateTo}" | ||||
|           @logged-in=${this.onLoggedIn} | ||||
|           @need-login="${this.onNeedLogin}" | ||||
|           .authState="${this.authService.authState}" | ||||
|           .userInfo="${this.userInfo}" | ||||
|         ></btrix-account-settings>`; | ||||
| @ -665,7 +664,6 @@ export class App extends LiteElement { | ||||
|               class="w-full max-w-screen-lg mx-auto p-2 md:py-8 box-border" | ||||
|               @navigate="${this.onNavigateTo}" | ||||
|               @logged-in=${this.onLoggedIn} | ||||
|               @need-login="${this.onNeedLogin}" | ||||
|               .authState="${this.authService.authState}" | ||||
|               .userInfo="${this.userInfo}" | ||||
|             ></btrix-users-invite>`; | ||||
| @ -684,7 +682,6 @@ export class App extends LiteElement { | ||||
|             return html`<btrix-crawls
 | ||||
|               class="w-full" | ||||
|               @navigate=${this.onNavigateTo} | ||||
|               @need-login=${this.onNeedLogin} | ||||
|               @notify=${this.onNotify} | ||||
|               .authState=${this.authService.authState} | ||||
|               crawlId=${this.viewState.params.crawlId} | ||||
| @ -781,7 +778,7 @@ export class App extends LiteElement { | ||||
|     this.clearUser(); | ||||
| 
 | ||||
|     if (redirect) { | ||||
|       this.navigate("/log-in"); | ||||
|       this.navigate(ROUTES.login); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| @ -805,10 +802,24 @@ export class App extends LiteElement { | ||||
|     this.updateUserInfo(); | ||||
|   } | ||||
| 
 | ||||
|   onNeedLogin() { | ||||
|   onNeedLogin = (e: Event) => { | ||||
|     e.stopPropagation(); | ||||
| 
 | ||||
|     this.clearUser(); | ||||
|     this.navigate(ROUTES.login); | ||||
|   } | ||||
|     const redirectUrl = (e as NeedLoginEvent).detail?.redirectUrl; | ||||
|     this.navigate(ROUTES.login, { | ||||
|       redirectUrl, | ||||
|     }); | ||||
|     this.onNotify( | ||||
|       new CustomEvent("notify", { | ||||
|         detail: { | ||||
|           message: msg("Please log in to continue."), | ||||
|           variant: "warning" as any, | ||||
|           icon: "exclamation-triangle", | ||||
|         }, | ||||
|       }) | ||||
|     ); | ||||
|   }; | ||||
| 
 | ||||
|   onNavigateTo(event: NavigateEvent) { | ||||
|     event.stopPropagation(); | ||||
|  | ||||
| @ -124,7 +124,7 @@ export class Org extends LiteElement { | ||||
|   } | ||||
| 
 | ||||
|   async willUpdate(changedProperties: Map<string, any>) { | ||||
|     if (changedProperties.has("orgId") && this.orgId) { | ||||
|     if (changedProperties.has("orgId") && this.orgId && this.authState) { | ||||
|       try { | ||||
|         this.org = await this.getOrg(this.orgId); | ||||
|         this.checkStorageQuota(); | ||||
|  | ||||
| @ -1,3 +1,4 @@ | ||||
| import { ROUTES } from "../routes"; | ||||
| import { APIError } from "./api"; | ||||
| 
 | ||||
| export type Auth = { | ||||
| @ -27,6 +28,14 @@ export interface LoggedInEvent<T = LoggedInEventDetail> extends CustomEvent { | ||||
|   readonly detail: T; | ||||
| } | ||||
| 
 | ||||
| export interface NeedLoginEvent extends CustomEvent { | ||||
|   readonly bubbles: boolean; | ||||
|   readonly composed: boolean; | ||||
|   readonly detail: { | ||||
|     redirectUrl?: string; | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| type AuthRequestEventData = { | ||||
|   name: "requesting_auth"; | ||||
| }; | ||||
| @ -51,6 +60,7 @@ export default class AuthService { | ||||
|   static storageKey = "btrix.auth"; | ||||
|   static unsupportedAuthErrorCode = "UNSUPPORTED_AUTH_TYPE"; | ||||
|   static loggedInEvent = "logged-in"; | ||||
|   static needLoginEvent = "need-login"; | ||||
| 
 | ||||
|   static broadcastChannel = new BroadcastChannel(AuthService.storageKey); | ||||
|   static storage = { | ||||
| @ -85,6 +95,14 @@ export default class AuthService { | ||||
|     return new CustomEvent(AuthService.loggedInEvent, { detail }); | ||||
|   }; | ||||
| 
 | ||||
|   static createNeedLoginEvent = (redirectUrl?: string): NeedLoginEvent => { | ||||
|     return new CustomEvent(AuthService.needLoginEvent, { | ||||
|       bubbles: true, | ||||
|       composed: true, | ||||
|       detail: { redirectUrl }, | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   static async login({ | ||||
|     email, | ||||
|     password, | ||||
| @ -307,6 +325,14 @@ export default class AuthService { | ||||
|         }, FRESHNESS_TIMER_INTERVAL); | ||||
|       } catch (e) { | ||||
|         console.debug(e); | ||||
| 
 | ||||
|         this.logout(); | ||||
|         const { pathname, search, hash } = window.location; | ||||
|         const redirectUrl = | ||||
|           pathname !== ROUTES.login && pathname !== ROUTES.home | ||||
|             ? `${pathname}${search}${hash}` | ||||
|             : ""; | ||||
|         window.dispatchEvent(AuthService.createNeedLoginEvent(redirectUrl)); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @ -3,6 +3,7 @@ import type { TemplateResult } from "lit"; | ||||
| import { msg } from "@lit/localize"; | ||||
| 
 | ||||
| import type { Auth } from "../utils/AuthService"; | ||||
| import AuthService from "../utils/AuthService"; | ||||
| import { APIError } from "./api"; | ||||
| 
 | ||||
| export interface NavigateEvent extends CustomEvent { | ||||
| @ -147,7 +148,7 @@ export default class LiteElement extends LitElement { | ||||
| 
 | ||||
|     switch (resp.status) { | ||||
|       case 401: { | ||||
|         this.dispatchEvent(new CustomEvent("need-login")); | ||||
|         this.dispatchEvent(AuthService.createNeedLoginEvent()); | ||||
|         errorMessage = msg("Need login"); | ||||
|         break; | ||||
|       } | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| import LiteElement from "../utils/LiteElement"; | ||||
| import type { AuthState } from "../utils/AuthService"; | ||||
| import type { CurrentUser } from "../types/user"; | ||||
| import AuthService from "../utils/AuthService"; | ||||
| 
 | ||||
| /** | ||||
|  * Block rendering and dispatch event if user is not logged in | ||||
| @ -27,7 +27,11 @@ export function needLogin<T extends { new (...args: any[]): LiteElement }>( | ||||
|       if (this.authState) { | ||||
|         super.update(changedProperties); | ||||
|       } else { | ||||
|         this.dispatchEvent(new CustomEvent("need-login")); | ||||
|         this.dispatchEvent( | ||||
|           AuthService.createNeedLoginEvent( | ||||
|             `${window.location.pathname}${window.location.search}${window.location.hash}` | ||||
|           ) | ||||
|         ); | ||||
|       } | ||||
|     } | ||||
|   }; | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user