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": "^18.2.8",
"@types/react-dom": "^18.2.4", "@types/react-dom": "^18.2.4",
"date-and-time": "^3.0.1", "date-and-time": "^3.0.1",
"jotai": "^2.1.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-router-dom": "^6.11.2", "react-router-dom": "^6.11.2",
@ -11966,22 +11965,6 @@
"jiti": "bin/jiti.js" "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": { "node_modules/js-tokens": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "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", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz",
"integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==" "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": { "js-tokens": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "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": "^18.2.8",
"@types/react-dom": "^18.2.4", "@types/react-dom": "^18.2.4",
"date-and-time": "^3.0.1", "date-and-time": "^3.0.1",
"jotai": "^2.1.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-router-dom": "^6.11.2", "react-router-dom": "^6.11.2",

View File

@ -1,24 +1,37 @@
import React from "react";
import { Route, Routes } from "react-router-dom"; import { Route, Routes } from "react-router-dom";
import "./App.css"; import "./App.css";
import { AuthApi } from "./api/AuthApi"; import { AuthApi } from "./api/AuthApi";
import { NotFoundRoute } from "./routes/NotFound"; import { NotFoundRoute } from "./routes/NotFound";
import { BaseLoginPage } from "./widgets/BaseLoginpage"; import { ProfileRoute } from "./routes/ProfileRoute";
import { LoginRoute } from "./routes/auth/LoginRoute"; import { LoginRoute } from "./routes/auth/LoginRoute";
import { NewAccountRoute } from "./routes/auth/NewAccountRoute";
import { OIDCCbRoute } from "./routes/auth/OIDCCbRoute"; import { OIDCCbRoute } from "./routes/auth/OIDCCbRoute";
import { useAtom } from "jotai";
import { BaseAuthenticatedPage } from "./widgets/BaseAuthenticatedPage";
import { PasswordForgottenRoute } from "./routes/auth/PasswordForgottenRoute"; import { PasswordForgottenRoute } from "./routes/auth/PasswordForgottenRoute";
import { ResetPasswordRoute } from "./routes/auth/ResetPasswordRoute"; import { ResetPasswordRoute } from "./routes/auth/ResetPasswordRoute";
import { NewAccountRoute } from "./routes/auth/NewAccountRoute"; import { BaseAuthenticatedPage } from "./widgets/BaseAuthenticatedPage";
import { ProfileRoute } from "./routes/ProfileRoute"; import { BaseLoginPage } from "./widgets/BaseLoginpage";
interface AuthContext {
signedIn: boolean;
setSignedIn: (signedIn: boolean) => void;
}
const AuthContextK = React.createContext<AuthContext | null>(null);
/** /**
* Core app * Core app
*/ */
function App() { export function App(): React.ReactElement {
const [signedIn] = useAtom(AuthApi.authStatus); const [signedIn, setSignedIn] = React.useState(AuthApi.SignedIn);
const context: AuthContext = {
signedIn: signedIn,
setSignedIn: (s) => setSignedIn(s),
};
return ( return (
<AuthContextK.Provider value={context}>
<Routes> <Routes>
{signedIn ? ( {signedIn ? (
<Route path="*" element={<BaseAuthenticatedPage />}> <Route path="*" element={<BaseAuthenticatedPage />}>
@ -39,7 +52,10 @@ function App() {
</Route> </Route>
)} )}
</Routes> </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"; import { APIClient } from "./ApiClient";
export enum CreateAccountResult { export enum CreateAccountResult {
@ -30,8 +29,6 @@ export class AuthApi {
return localStorage.getItem(TokenStateKey) !== null; return localStorage.getItem(TokenStateKey) !== null;
} }
static authStatus = atom(this.SignedIn);
/** /**
* Get user auth token * Get user auth token
*/ */

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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