feat: consistent authentication flow

fix: missing suspense on login
feat: re-prompt MFA if fail on login
This commit is contained in:
Paul Makles 2022-06-29 16:27:57 +01:00
parent 0261fec676
commit 1fcb3cedc1
5 changed files with 57 additions and 37 deletions

View file

@ -41,10 +41,9 @@ export default function Context({ children }: { children: Children }) {
<UIProvider value={uiContext}>
<Locale>
<Intermediate>
<Binder>
{children}
{<SyncManager />}
</Binder>
{children}
<SyncManager />
<Binder />
</Intermediate>
<ModalRenderer />
</Locale>

View file

@ -1,6 +1,8 @@
import { observer } from "mobx-react-lite";
import { Redirect } from "react-router-dom";
import { Preloader } from "@revoltchat/ui";
import { clientController } from "../../controllers/client/ClientController";
interface Props {
@ -10,6 +12,10 @@ interface Props {
children: Children;
}
/**
* Check that we are logged in or out and redirect accordingly.
* Also prevent render until the client is ready to display.
*/
export const CheckAuth = observer((props: Props) => {
const loggedIn = clientController.isLoggedIn();
@ -22,5 +28,14 @@ export const CheckAuth = observer((props: Props) => {
return <Redirect to="/" />;
}
// Block render if client is getting ready to work.
if (
props.auth &&
clientController.isLoggedIn() &&
!clientController.isReady()
) {
return <Preloader type="spinner" />;
}
return <>{props.children}</>;
});

View file

@ -7,6 +7,7 @@ import { injectController } from "../../lib/window";
import { state } from "../../mobx/State";
import Auth from "../../mobx/stores/Auth";
import { resetMemberSidebarFetched } from "../../components/navigation/right/MemberSidebar";
import { modalController } from "../modals/ModalController";
import Session from "./Session";
@ -205,29 +206,37 @@ class ClientController {
// Prompt for MFA verificaiton if necessary
if (session.result === "MFA") {
const { allowed_methods } = session;
const mfa_response: API.MFAResponse | undefined = await new Promise(
(callback) =>
modalController.push({
type: "mfa_flow",
state: "unknown",
available_methods: allowed_methods,
callback,
}),
);
while (session.result === "MFA") {
const mfa_response: API.MFAResponse | undefined =
await new Promise((callback) =>
modalController.push({
type: "mfa_flow",
state: "unknown",
available_methods: allowed_methods,
callback,
}),
);
if (typeof mfa_response === "undefined") {
throw "Cancelled";
if (typeof mfa_response === "undefined") {
break;
}
try {
session = await this.apiClient.api.post(
"/auth/session/login",
{
mfa_response,
mfa_ticket: session.ticket,
friendly_name,
},
);
} catch (err) {
console.error("Failed login:", err);
}
}
session = await this.apiClient.api.post("/auth/session/login", {
mfa_response,
mfa_ticket: session.ticket,
friendly_name,
});
if (session.result === "MFA") {
// unreachable code
return;
throw "Cancelled";
}
}
@ -247,12 +256,12 @@ class ClientController {
@action logout(user_id: string) {
const session = this.sessions.get(user_id);
if (session) {
this.sessions.delete(user_id);
if (user_id === this.current) {
this.current = null;
this.pickNextSession();
}
this.sessions.delete(user_id);
this.pickNextSession();
session.destroy();
}
}
@ -272,6 +281,10 @@ class ClientController {
*/
@action switchAccount(user_id: string) {
this.current = user_id;
// This will allow account switching to work more seamlessly,
// maybe it'll be properly / fully implemented at some point.
resetMemberSidebarFetched();
}
}

View file

@ -2,26 +2,17 @@ import { observer } from "mobx-react-lite";
import { useEffect } from "preact/hooks";
import { Preloader } from "@revoltchat/ui";
import { state } from "../../../mobx/State";
import { clientController } from "../ClientController";
/**
* Prevent render until the client is ready to display.
* Also binds listeners from state to the current client.
*/
const Binder: React.FC = ({ children }) => {
const Binder: React.FC = () => {
const client = clientController.getReadyClient();
useEffect(() => state.registerListeners(client!), [client]);
// Block render if client is getting ready to work.
if (clientController.isLoggedIn() && !clientController.isReady()) {
return <Preloader type="spinner" />;
}
return <>{children}</>;
return null;
};
export default observer(Binder);

View file

@ -49,7 +49,9 @@ export function App() {
</Route>
<Route path="/login">
<CheckAuth>
<Login />
<LoadSuspense>
<Login />
</LoadSuspense>
</CheckAuth>
</Route>
<Route path="/">