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:
sua yoo 2023-10-04 00:17:22 -07:00 committed by GitHub
parent 38efeccc25
commit f2261bcb34
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 57 additions and 15 deletions

View File

@ -13,7 +13,7 @@ import type { NotifyEvent, NavigateEvent } from "./utils/LiteElement";
import LiteElement, { html } from "./utils/LiteElement"; import LiteElement, { html } from "./utils/LiteElement";
import APIRouter from "./utils/APIRouter"; import APIRouter from "./utils/APIRouter";
import AuthService, { AuthState } from "./utils/AuthService"; 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 { ViewState } from "./utils/APIRouter";
import type { CurrentUser, UserOrg } from "./types/user"; import type { CurrentUser, UserOrg } from "./types/user";
import type { AuthStorageEventData } from "./utils/AuthService"; import type { AuthStorageEventData } from "./utils/AuthService";
@ -107,6 +107,7 @@ export class App extends LiteElement {
} }
super.connectedCallback(); super.connectedCallback();
window.addEventListener("need-login", this.onNeedLogin);
window.addEventListener("popstate", () => { window.addEventListener("popstate", () => {
this.syncViewState(); this.syncViewState();
}); });
@ -585,7 +586,8 @@ export class App extends LiteElement {
@logged-in=${this.onLoggedIn} @logged-in=${this.onLoggedIn}
.authState=${this.authService.authState} .authState=${this.authService.authState}
.viewState=${this.viewState} .viewState=${this.viewState}
redirectUrl=${this.viewState.params.redirectUrl} redirectUrl=${this.viewState.params.redirectUrl ||
this.viewState.data?.redirectUrl}
></btrix-log-in>`; ></btrix-log-in>`;
case "resetPassword": case "resetPassword":
@ -616,7 +618,6 @@ export class App extends LiteElement {
return html`<btrix-orgs return html`<btrix-orgs
class="w-full md:bg-neutral-50" class="w-full md:bg-neutral-50"
@navigate="${this.onNavigateTo}" @navigate="${this.onNavigateTo}"
@need-login="${this.onNeedLogin}"
.authState="${this.authService.authState}" .authState="${this.authService.authState}"
.userInfo="${this.userInfo}" .userInfo="${this.userInfo}"
></btrix-orgs>`; ></btrix-orgs>`;
@ -632,7 +633,6 @@ export class App extends LiteElement {
return html`<btrix-org return html`<btrix-org
class="w-full" class="w-full"
@navigate=${this.onNavigateTo} @navigate=${this.onNavigateTo}
@need-login=${this.onNeedLogin}
@update-user-info=${(e: CustomEvent) => { @update-user-info=${(e: CustomEvent) => {
e.stopPropagation(); e.stopPropagation();
this.updateUserInfo(); 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" class="w-full max-w-screen-lg mx-auto p-2 md:py-8 box-border"
@navigate="${this.onNavigateTo}" @navigate="${this.onNavigateTo}"
@logged-in=${this.onLoggedIn} @logged-in=${this.onLoggedIn}
@need-login="${this.onNeedLogin}"
.authState="${this.authService.authState}" .authState="${this.authService.authState}"
.userInfo="${this.userInfo}" .userInfo="${this.userInfo}"
></btrix-account-settings>`; ></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" class="w-full max-w-screen-lg mx-auto p-2 md:py-8 box-border"
@navigate="${this.onNavigateTo}" @navigate="${this.onNavigateTo}"
@logged-in=${this.onLoggedIn} @logged-in=${this.onLoggedIn}
@need-login="${this.onNeedLogin}"
.authState="${this.authService.authState}" .authState="${this.authService.authState}"
.userInfo="${this.userInfo}" .userInfo="${this.userInfo}"
></btrix-users-invite>`; ></btrix-users-invite>`;
@ -684,7 +682,6 @@ export class App extends LiteElement {
return html`<btrix-crawls return html`<btrix-crawls
class="w-full" class="w-full"
@navigate=${this.onNavigateTo} @navigate=${this.onNavigateTo}
@need-login=${this.onNeedLogin}
@notify=${this.onNotify} @notify=${this.onNotify}
.authState=${this.authService.authState} .authState=${this.authService.authState}
crawlId=${this.viewState.params.crawlId} crawlId=${this.viewState.params.crawlId}
@ -781,7 +778,7 @@ export class App extends LiteElement {
this.clearUser(); this.clearUser();
if (redirect) { if (redirect) {
this.navigate("/log-in"); this.navigate(ROUTES.login);
} }
} }
@ -805,10 +802,24 @@ export class App extends LiteElement {
this.updateUserInfo(); this.updateUserInfo();
} }
onNeedLogin() { onNeedLogin = (e: Event) => {
e.stopPropagation();
this.clearUser(); 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) { onNavigateTo(event: NavigateEvent) {
event.stopPropagation(); event.stopPropagation();

View File

@ -124,7 +124,7 @@ export class Org extends LiteElement {
} }
async willUpdate(changedProperties: Map<string, any>) { async willUpdate(changedProperties: Map<string, any>) {
if (changedProperties.has("orgId") && this.orgId) { if (changedProperties.has("orgId") && this.orgId && this.authState) {
try { try {
this.org = await this.getOrg(this.orgId); this.org = await this.getOrg(this.orgId);
this.checkStorageQuota(); this.checkStorageQuota();

View File

@ -1,3 +1,4 @@
import { ROUTES } from "../routes";
import { APIError } from "./api"; import { APIError } from "./api";
export type Auth = { export type Auth = {
@ -27,6 +28,14 @@ export interface LoggedInEvent<T = LoggedInEventDetail> extends CustomEvent {
readonly detail: T; readonly detail: T;
} }
export interface NeedLoginEvent extends CustomEvent {
readonly bubbles: boolean;
readonly composed: boolean;
readonly detail: {
redirectUrl?: string;
};
}
type AuthRequestEventData = { type AuthRequestEventData = {
name: "requesting_auth"; name: "requesting_auth";
}; };
@ -51,6 +60,7 @@ export default class AuthService {
static storageKey = "btrix.auth"; static storageKey = "btrix.auth";
static unsupportedAuthErrorCode = "UNSUPPORTED_AUTH_TYPE"; static unsupportedAuthErrorCode = "UNSUPPORTED_AUTH_TYPE";
static loggedInEvent = "logged-in"; static loggedInEvent = "logged-in";
static needLoginEvent = "need-login";
static broadcastChannel = new BroadcastChannel(AuthService.storageKey); static broadcastChannel = new BroadcastChannel(AuthService.storageKey);
static storage = { static storage = {
@ -85,6 +95,14 @@ export default class AuthService {
return new CustomEvent(AuthService.loggedInEvent, { detail }); 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({ static async login({
email, email,
password, password,
@ -307,6 +325,14 @@ export default class AuthService {
}, FRESHNESS_TIMER_INTERVAL); }, FRESHNESS_TIMER_INTERVAL);
} catch (e) { } catch (e) {
console.debug(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));
} }
} }
} }

View File

@ -3,6 +3,7 @@ import type { TemplateResult } from "lit";
import { msg } from "@lit/localize"; import { msg } from "@lit/localize";
import type { Auth } from "../utils/AuthService"; import type { Auth } from "../utils/AuthService";
import AuthService from "../utils/AuthService";
import { APIError } from "./api"; import { APIError } from "./api";
export interface NavigateEvent extends CustomEvent { export interface NavigateEvent extends CustomEvent {
@ -147,7 +148,7 @@ export default class LiteElement extends LitElement {
switch (resp.status) { switch (resp.status) {
case 401: { case 401: {
this.dispatchEvent(new CustomEvent("need-login")); this.dispatchEvent(AuthService.createNeedLoginEvent());
errorMessage = msg("Need login"); errorMessage = msg("Need login");
break; break;
} }

View File

@ -1,6 +1,6 @@
import LiteElement from "../utils/LiteElement"; import LiteElement from "../utils/LiteElement";
import type { AuthState } from "../utils/AuthService"; 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 * 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) { if (this.authState) {
super.update(changedProperties); super.update(changedProperties);
} else { } else {
this.dispatchEvent(new CustomEvent("need-login")); this.dispatchEvent(
AuthService.createNeedLoginEvent(
`${window.location.pathname}${window.location.search}${window.location.hash}`
)
);
} }
} }
}; };