Create a context to store authentication

This commit is contained in:
Pierre HUBERT 2023-06-15 08:45:21 +02:00
parent 10e5c124fd
commit 1934354665
10 changed files with 71 additions and 92 deletions

View File

@ -23,7 +23,6 @@
"@types/react": "^18.2.8",
"@types/react-dom": "^18.2.4",
"date-and-time": "^3.0.1",
"jotai": "^2.1.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.11.2",
@ -11966,22 +11965,6 @@
"jiti": "bin/jiti.js"
}
},
"node_modules/jotai": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/jotai/-/jotai-2.1.1.tgz",
"integrity": "sha512-LaaiuSaq+6XkwkrCtCkczyFVZOXe0dfjAFN4DVMsSZSRv/A/4xuLHnlpHMEDqvngjWYBotTIrnQ7OogMkUE6wA==",
"engines": {
"node": ">=12.20.0"
},
"peerDependencies": {
"react": ">=17.0.0"
},
"peerDependenciesMeta": {
"react": {
"optional": true
}
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@ -26267,12 +26250,6 @@
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz",
"integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg=="
},
"jotai": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/jotai/-/jotai-2.1.1.tgz",
"integrity": "sha512-LaaiuSaq+6XkwkrCtCkczyFVZOXe0dfjAFN4DVMsSZSRv/A/4xuLHnlpHMEDqvngjWYBotTIrnQ7OogMkUE6wA==",
"requires": {}
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",

View File

@ -18,7 +18,6 @@
"@types/react": "^18.2.8",
"@types/react-dom": "^18.2.4",
"date-and-time": "^3.0.1",
"jotai": "^2.1.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.11.2",

View File

@ -1,45 +1,61 @@
import React from "react";
import { Route, Routes } from "react-router-dom";
import "./App.css";
import { AuthApi } from "./api/AuthApi";
import { NotFoundRoute } from "./routes/NotFound";
import { BaseLoginPage } from "./widgets/BaseLoginpage";
import { ProfileRoute } from "./routes/ProfileRoute";
import { LoginRoute } from "./routes/auth/LoginRoute";
import { NewAccountRoute } from "./routes/auth/NewAccountRoute";
import { OIDCCbRoute } from "./routes/auth/OIDCCbRoute";
import { useAtom } from "jotai";
import { BaseAuthenticatedPage } from "./widgets/BaseAuthenticatedPage";
import { PasswordForgottenRoute } from "./routes/auth/PasswordForgottenRoute";
import { ResetPasswordRoute } from "./routes/auth/ResetPasswordRoute";
import { NewAccountRoute } from "./routes/auth/NewAccountRoute";
import { ProfileRoute } from "./routes/ProfileRoute";
import { BaseAuthenticatedPage } from "./widgets/BaseAuthenticatedPage";
import { BaseLoginPage } from "./widgets/BaseLoginpage";
interface AuthContext {
signedIn: boolean;
setSignedIn: (signedIn: boolean) => void;
}
const AuthContextK = React.createContext<AuthContext | null>(null);
/**
* Core app
*/
function App() {
const [signedIn] = useAtom(AuthApi.authStatus);
export function App(): React.ReactElement {
const [signedIn, setSignedIn] = React.useState(AuthApi.SignedIn);
const context: AuthContext = {
signedIn: signedIn,
setSignedIn: (s) => setSignedIn(s),
};
return (
<Routes>
{signedIn ? (
<Route path="*" element={<BaseAuthenticatedPage />}>
<Route path="profile" element={<ProfileRoute />} />
<Route path="*" element={<NotFoundRoute />} />
</Route>
) : (
<Route path="*" element={<BaseLoginPage />}>
<Route path="" element={<LoginRoute />} />
<Route path="oidc_cb" element={<OIDCCbRoute />} />
<Route path="new-account" element={<NewAccountRoute />} />
<Route
path="password_forgotten"
element={<PasswordForgottenRoute />}
/>
<Route path="reset_password" element={<ResetPasswordRoute />} />
<Route path="*" element={<NotFoundRoute />} />
</Route>
)}
</Routes>
<AuthContextK.Provider value={context}>
<Routes>
{signedIn ? (
<Route path="*" element={<BaseAuthenticatedPage />}>
<Route path="profile" element={<ProfileRoute />} />
<Route path="*" element={<NotFoundRoute />} />
</Route>
) : (
<Route path="*" element={<BaseLoginPage />}>
<Route path="" element={<LoginRoute />} />
<Route path="oidc_cb" element={<OIDCCbRoute />} />
<Route path="new-account" element={<NewAccountRoute />} />
<Route
path="password_forgotten"
element={<PasswordForgottenRoute />}
/>
<Route path="reset_password" element={<ResetPasswordRoute />} />
<Route path="*" element={<NotFoundRoute />} />
</Route>
)}
</Routes>
</AuthContextK.Provider>
);
}
export default App;
export function useAuth(): AuthContext {
return React.useContext(AuthContextK)!;
}

View File

@ -1,4 +1,3 @@
import { atom } from "jotai";
import { APIClient } from "./ApiClient";
export enum CreateAccountResult {
@ -30,8 +29,6 @@ export class AuthApi {
return localStorage.getItem(TokenStateKey) !== null;
}
static authStatus = atom(this.SignedIn);
/**
* Get user auth token
*/

View File

@ -1,7 +1,7 @@
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import { App } from "./App";
import reportWebVitals from "./reportWebVitals";
import { ServerApi } from "./api/ServerApi";

View File

@ -1,3 +1,4 @@
import { Visibility, VisibilityOff } from "@mui/icons-material";
import {
Alert,
CircularProgress,
@ -14,11 +15,10 @@ import Grid from "@mui/material/Grid";
import TextField from "@mui/material/TextField";
import Typography from "@mui/material/Typography";
import * as React from "react";
import { Link, useNavigate } from "react-router-dom";
import { useAuth } from "../../App";
import { AuthApi, PasswordLoginResult } from "../../api/AuthApi";
import { ServerApi } from "../../api/ServerApi";
import { Link, useNavigate } from "react-router-dom";
import { VisibilityOff, Visibility } from "@mui/icons-material";
import { useSetAtom } from "jotai";
/**
* Login form
@ -27,7 +27,7 @@ export function LoginRoute(): React.ReactElement {
const [loading, setLoading] = React.useState(false);
const [error, setError] = React.useState<string | null>(null);
const setAuth = useSetAtom(AuthApi.authStatus);
const auth = useAuth();
const navigate = useNavigate();
const [mail, setMail] = React.useState("");
@ -64,7 +64,7 @@ export function LoginRoute(): React.ReactElement {
case PasswordLoginResult.Success:
navigate("/");
setAuth(true);
auth.setSignedIn(true);
break;
case PasswordLoginResult.Error:

View File

@ -1,15 +1,15 @@
import { CircularProgress } from "@mui/material";
import { useSetAtom } from "jotai";
import { useEffect, useRef, useState } from "react";
import { useNavigate, useSearchParams } from "react-router-dom";
import { AuthApi } from "../../api/AuthApi";
import { AuthSingleMessage } from "../../widgets/AuthSingleMessage";
import { useAuth } from "../../App";
/**
* OpenID login callback route
*/
export function OIDCCbRoute(): React.ReactElement {
const setAuth = useSetAtom(AuthApi.authStatus);
const auth = useAuth();
const navigate = useNavigate();
const [error, setError] = useState(false);
@ -30,7 +30,7 @@ export function OIDCCbRoute(): React.ReactElement {
await AuthApi.FinishOpenIDLogin(code!, state!);
navigate("/");
setAuth(true);
auth.setSignedIn(true);
} catch (e) {
console.error(e);
setError(true);

View File

@ -6,18 +6,13 @@ import {
DialogContentText,
DialogTitle,
} from "@mui/material";
import React, {
PropsWithChildren,
createContext,
useContext,
useRef,
} from "react";
import React, { PropsWithChildren } from "react";
interface AlertHook {
interface AlertContext {
alert: (message: string, title?: string) => Promise<void>;
}
const AlertContext = createContext<AlertHook | null>(null);
const AlertContextK = React.createContext<AlertContext | null>(null);
export function AlertDialogProvider(p: PropsWithChildren): React.ReactElement {
const [open, setOpen] = React.useState(false);
@ -34,7 +29,7 @@ export function AlertDialogProvider(p: PropsWithChildren): React.ReactElement {
cb.current = null;
};
const hook: AlertHook = {
const hook: AlertContext = {
alert: (message, title) => {
setTitle(title);
setMessage(message);
@ -48,7 +43,7 @@ export function AlertDialogProvider(p: PropsWithChildren): React.ReactElement {
return (
<>
<AlertContext.Provider value={hook}>{p.children}</AlertContext.Provider>
<AlertContextK.Provider value={hook}>{p.children}</AlertContextK.Provider>
<Dialog
open={open}
@ -72,6 +67,6 @@ export function AlertDialogProvider(p: PropsWithChildren): React.ReactElement {
);
}
export function useAlert(): AlertHook {
return useContext(AlertContext)!;
export function useAlert(): AlertContext {
return React.useContext(AlertContextK)!;
}

View File

@ -7,9 +7,9 @@ import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
import Toolbar from "@mui/material/Toolbar";
import Typography from "@mui/material/Typography";
import { useSetAtom } from "jotai";
import * as React from "react";
import { Link, Outlet, useNavigate } from "react-router-dom";
import { useAuth } from "../App";
import { AuthApi } from "../api/AuthApi";
import { User, UserApi } from "../api/UserApi";
import { AsyncWidget } from "./AsyncWidget";
@ -18,7 +18,7 @@ import { RouterLink } from "./RouterLink";
export function BaseAuthenticatedPage(): React.ReactElement {
const [user, setUser] = React.useState<null | User>(null);
const setSignedIn = useSetAtom(AuthApi.authStatus);
const auth = useAuth();
const navigate = useNavigate();
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
@ -39,7 +39,7 @@ export function BaseAuthenticatedPage(): React.ReactElement {
handleCloseMenu();
AuthApi.SignOut();
navigate("/");
setSignedIn(false);
auth.setSignedIn(false);
};
return (

View File

@ -6,14 +6,9 @@ import {
DialogContentText,
DialogTitle,
} from "@mui/material";
import React, {
PropsWithChildren,
createContext,
useContext,
useRef,
} from "react";
import React, { PropsWithChildren } from "react";
interface ConfirmHook {
interface ConfirmContext {
confirm: (
message: string,
title?: string,
@ -21,7 +16,7 @@ interface ConfirmHook {
) => Promise<boolean>;
}
const ConfirmContext = createContext<ConfirmHook | null>(null);
const ConfirmContextK = React.createContext<ConfirmContext | null>(null);
export function ConfirmDialogProvider(
p: PropsWithChildren
@ -43,7 +38,7 @@ export function ConfirmDialogProvider(
cb.current = null;
};
const hook: ConfirmHook = {
const hook: ConfirmContext = {
confirm: (message, title, confirmButton) => {
setTitle(title);
setMessage(message);
@ -58,9 +53,9 @@ export function ConfirmDialogProvider(
return (
<>
<ConfirmContext.Provider value={hook}>
<ConfirmContextK.Provider value={hook}>
{p.children}
</ConfirmContext.Provider>
</ConfirmContextK.Provider>
<Dialog
open={open}
@ -87,6 +82,6 @@ export function ConfirmDialogProvider(
);
}
export function useConfirm(): ConfirmHook {
return useContext(ConfirmContext)!;
export function useConfirm(): ConfirmContext {
return React.useContext(ConfirmContextK)!;
}