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 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();
|
||||||
|
@ -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();
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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}`
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user