mirror of
https://github.com/revoltchat/revite.git
synced 2024-11-25 00:20:57 -05:00
feat: consistent authentication flow
fix: missing suspense on login feat: re-prompt MFA if fail on login
This commit is contained in:
parent
0261fec676
commit
1fcb3cedc1
5 changed files with 57 additions and 37 deletions
|
@ -41,10 +41,9 @@ export default function Context({ children }: { children: Children }) {
|
||||||
<UIProvider value={uiContext}>
|
<UIProvider value={uiContext}>
|
||||||
<Locale>
|
<Locale>
|
||||||
<Intermediate>
|
<Intermediate>
|
||||||
<Binder>
|
{children}
|
||||||
{children}
|
<SyncManager />
|
||||||
{<SyncManager />}
|
<Binder />
|
||||||
</Binder>
|
|
||||||
</Intermediate>
|
</Intermediate>
|
||||||
<ModalRenderer />
|
<ModalRenderer />
|
||||||
</Locale>
|
</Locale>
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { Redirect } from "react-router-dom";
|
import { Redirect } from "react-router-dom";
|
||||||
|
|
||||||
|
import { Preloader } from "@revoltchat/ui";
|
||||||
|
|
||||||
import { clientController } from "../../controllers/client/ClientController";
|
import { clientController } from "../../controllers/client/ClientController";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -10,6 +12,10 @@ interface Props {
|
||||||
children: Children;
|
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) => {
|
export const CheckAuth = observer((props: Props) => {
|
||||||
const loggedIn = clientController.isLoggedIn();
|
const loggedIn = clientController.isLoggedIn();
|
||||||
|
|
||||||
|
@ -22,5 +28,14 @@ export const CheckAuth = observer((props: Props) => {
|
||||||
return <Redirect to="/" />;
|
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}</>;
|
return <>{props.children}</>;
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { injectController } from "../../lib/window";
|
||||||
import { state } from "../../mobx/State";
|
import { state } from "../../mobx/State";
|
||||||
import Auth from "../../mobx/stores/Auth";
|
import Auth from "../../mobx/stores/Auth";
|
||||||
|
|
||||||
|
import { resetMemberSidebarFetched } from "../../components/navigation/right/MemberSidebar";
|
||||||
import { modalController } from "../modals/ModalController";
|
import { modalController } from "../modals/ModalController";
|
||||||
import Session from "./Session";
|
import Session from "./Session";
|
||||||
|
|
||||||
|
@ -205,29 +206,37 @@ class ClientController {
|
||||||
// Prompt for MFA verificaiton if necessary
|
// Prompt for MFA verificaiton if necessary
|
||||||
if (session.result === "MFA") {
|
if (session.result === "MFA") {
|
||||||
const { allowed_methods } = session;
|
const { allowed_methods } = session;
|
||||||
const mfa_response: API.MFAResponse | undefined = await new Promise(
|
while (session.result === "MFA") {
|
||||||
(callback) =>
|
const mfa_response: API.MFAResponse | undefined =
|
||||||
modalController.push({
|
await new Promise((callback) =>
|
||||||
type: "mfa_flow",
|
modalController.push({
|
||||||
state: "unknown",
|
type: "mfa_flow",
|
||||||
available_methods: allowed_methods,
|
state: "unknown",
|
||||||
callback,
|
available_methods: allowed_methods,
|
||||||
}),
|
callback,
|
||||||
);
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
if (typeof mfa_response === "undefined") {
|
if (typeof mfa_response === "undefined") {
|
||||||
throw "Cancelled";
|
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") {
|
if (session.result === "MFA") {
|
||||||
// unreachable code
|
throw "Cancelled";
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -247,12 +256,12 @@ class ClientController {
|
||||||
@action logout(user_id: string) {
|
@action logout(user_id: string) {
|
||||||
const session = this.sessions.get(user_id);
|
const session = this.sessions.get(user_id);
|
||||||
if (session) {
|
if (session) {
|
||||||
this.sessions.delete(user_id);
|
|
||||||
if (user_id === this.current) {
|
if (user_id === this.current) {
|
||||||
this.current = null;
|
this.current = null;
|
||||||
this.pickNextSession();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.sessions.delete(user_id);
|
||||||
|
this.pickNextSession();
|
||||||
session.destroy();
|
session.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -272,6 +281,10 @@ class ClientController {
|
||||||
*/
|
*/
|
||||||
@action switchAccount(user_id: string) {
|
@action switchAccount(user_id: string) {
|
||||||
this.current = user_id;
|
this.current = user_id;
|
||||||
|
|
||||||
|
// This will allow account switching to work more seamlessly,
|
||||||
|
// maybe it'll be properly / fully implemented at some point.
|
||||||
|
resetMemberSidebarFetched();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,26 +2,17 @@ import { observer } from "mobx-react-lite";
|
||||||
|
|
||||||
import { useEffect } from "preact/hooks";
|
import { useEffect } from "preact/hooks";
|
||||||
|
|
||||||
import { Preloader } from "@revoltchat/ui";
|
|
||||||
|
|
||||||
import { state } from "../../../mobx/State";
|
import { state } from "../../../mobx/State";
|
||||||
|
|
||||||
import { clientController } from "../ClientController";
|
import { clientController } from "../ClientController";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prevent render until the client is ready to display.
|
|
||||||
* Also binds listeners from state to the current client.
|
* Also binds listeners from state to the current client.
|
||||||
*/
|
*/
|
||||||
const Binder: React.FC = ({ children }) => {
|
const Binder: React.FC = () => {
|
||||||
const client = clientController.getReadyClient();
|
const client = clientController.getReadyClient();
|
||||||
useEffect(() => state.registerListeners(client!), [client]);
|
useEffect(() => state.registerListeners(client!), [client]);
|
||||||
|
return null;
|
||||||
// Block render if client is getting ready to work.
|
|
||||||
if (clientController.isLoggedIn() && !clientController.isReady()) {
|
|
||||||
return <Preloader type="spinner" />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <>{children}</>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default observer(Binder);
|
export default observer(Binder);
|
||||||
|
|
|
@ -49,7 +49,9 @@ export function App() {
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/login">
|
<Route path="/login">
|
||||||
<CheckAuth>
|
<CheckAuth>
|
||||||
<Login />
|
<LoadSuspense>
|
||||||
|
<Login />
|
||||||
|
</LoadSuspense>
|
||||||
</CheckAuth>
|
</CheckAuth>
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/">
|
<Route path="/">
|
||||||
|
|
Loading…
Reference in a new issue