From d55718f4deed0b434c0f6cb06b38a4225a91be63 Mon Sep 17 00:00:00 2001 From: Pierre Hubert Date: Fri, 9 Jun 2023 10:45:01 +0200 Subject: [PATCH] Can authenticate using OpenID --- geneit_app/src/App.tsx | 9 +- geneit_app/src/api/AuthApi.ts | 39 ++++++- geneit_app/src/api/ServerApi.ts | 2 +- geneit_app/src/routes/auth/LoginRoute.tsx | 116 +++++++++++++++++++++ geneit_app/src/routes/auth/OIDCCbRoute.tsx | 55 ++++++++++ geneit_backend/src/main.rs | 1 + 6 files changed, 219 insertions(+), 3 deletions(-) create mode 100644 geneit_app/src/routes/auth/LoginRoute.tsx create mode 100644 geneit_app/src/routes/auth/OIDCCbRoute.tsx diff --git a/geneit_app/src/App.tsx b/geneit_app/src/App.tsx index 062e5b2..2750f97 100644 --- a/geneit_app/src/App.tsx +++ b/geneit_app/src/App.tsx @@ -3,14 +3,21 @@ import "./App.css"; import { AuthApi } from "./api/AuthApi"; import { NotFoundRoute } from "./routes/NotFound"; import { BaseLoginPage } from "./widgets/BaseLoginpage"; +import { LoginRoute } from "./routes/auth/LoginRoute"; +import { OIDCCbRoute } from "./routes/auth/OIDCCbRoute"; +import { useAtom } from "jotai"; function App() { + const [signedIn] = useAtom(AuthApi.authStatus); + return ( - {AuthApi.SignedIn ? ( + {signedIn ? ( signed in

} /> ) : ( }> + } /> + } /> } /> )} diff --git a/geneit_app/src/api/AuthApi.ts b/geneit_app/src/api/AuthApi.ts index dfb47aa..1fb36af 100644 --- a/geneit_app/src/api/AuthApi.ts +++ b/geneit_app/src/api/AuthApi.ts @@ -1,8 +1,45 @@ +import { atom } from "jotai"; +import { APIClient } from "./ApiClient"; + +const TokenStateKey = "auth-token"; + export class AuthApi { /** * Check out whether user is signed in or not */ static get SignedIn(): boolean { - return false; + return sessionStorage.getItem(TokenStateKey) !== null; + } + + static authStatus = atom(this.SignedIn); + + /** + * Start OpenID login + * + * @param id The ID of the OIDC provider to use + */ + static async StartOpenIDLogin(id: string): Promise<{ url: string }> { + return ( + await APIClient.exec({ + uri: "/auth/start_openid_login", + method: "POST", + jsonData: { provider: id }, + }) + ).data; + } + + /** + * Finish OpenID login + */ + static async FinishOpenIDLogin(code: string, state: string): Promise { + const res: { user_id: number; token: string } = ( + await APIClient.exec({ + uri: "/auth/finish_openid_login", + method: "POST", + jsonData: { code: code, state: state }, + }) + ).data; + + sessionStorage.setItem(TokenStateKey, res.token); } } diff --git a/geneit_app/src/api/ServerApi.ts b/geneit_app/src/api/ServerApi.ts index b25fdc0..34f62f9 100644 --- a/geneit_app/src/api/ServerApi.ts +++ b/geneit_app/src/api/ServerApi.ts @@ -40,7 +40,7 @@ export class ServerApi { /** * Get cached configuration */ - static Config(): ServerConfig { + static get Config(): ServerConfig { if (config === null) throw new Error("Missing configuration!"); return config; } diff --git a/geneit_app/src/routes/auth/LoginRoute.tsx b/geneit_app/src/routes/auth/LoginRoute.tsx new file mode 100644 index 0000000..98ff777 --- /dev/null +++ b/geneit_app/src/routes/auth/LoginRoute.tsx @@ -0,0 +1,116 @@ +import { Alert, CircularProgress } from "@mui/material"; +import Box from "@mui/material/Box"; +import Button from "@mui/material/Button"; +import Grid from "@mui/material/Grid"; +import Link from "@mui/material/Link"; +import TextField from "@mui/material/TextField"; +import Typography from "@mui/material/Typography"; +import * as React from "react"; +import { AuthApi } from "../../api/AuthApi"; +import { ServerApi } from "../../api/ServerApi"; + +/** + * Login form + */ +export function LoginRoute(): React.ReactElement { + const [loading, setLoading] = React.useState(false); + const [error, setError] = React.useState(null); + + const handleSubmit = (event: React.FormEvent) => { + event.preventDefault(); + const data = new FormData(event.currentTarget); + console.log({ + email: data.get("email"), + password: data.get("password"), + }); + }; + + const authWithProvider = async (id: string) => { + try { + setLoading(true); + + const res = await AuthApi.StartOpenIDLogin(id); + window.location.href = res.url; + } catch (e) { + console.error(e); + setError("Echec de l'initialisation de l'authentification OpenID !"); + } + }; + + if (loading) + return ( + <> + + + ); + + return ( + <> + {error === null ? ( + <> + ) : ( + + {error} + + )} + + + Connexion + + + + + + + + + + Mot de passe oublié + + + + + Créer un nouveau compte + + + + +
+ {ServerApi.Config.oidc_providers.map((p) => ( + + ))} +
+
+ + ); +} diff --git a/geneit_app/src/routes/auth/OIDCCbRoute.tsx b/geneit_app/src/routes/auth/OIDCCbRoute.tsx new file mode 100644 index 0000000..f7c6a27 --- /dev/null +++ b/geneit_app/src/routes/auth/OIDCCbRoute.tsx @@ -0,0 +1,55 @@ +import { Button, CircularProgress } from "@mui/material"; +import { useEffect, useRef, useState } from "react"; +import { Link, useSearchParams } from "react-router-dom"; +import { AuthApi } from "../../api/AuthApi"; +import { useSetAtom } from "jotai"; + +/** + * OpenID login callback route + */ +export function OIDCCbRoute(): React.ReactElement { + const setAuth = useSetAtom(AuthApi.authStatus); + + const [error, setError] = useState(false); + + const [searchParams] = useSearchParams(); + const code = searchParams.get("code"); + const state = searchParams.get("state"); + + const count = useRef(""); + + useEffect(() => { + const load = async () => { + try { + if (count.current === code) { + return; + } + count.current = code!; + + await AuthApi.FinishOpenIDLogin(code!, state!); + setAuth(true); + } catch (e) { + console.error(e); + setError(true); + } + }; + + load(); + }, [code, state]); + + if (error) + return ( + <> +

Echec de la finalisation de l'authentification !

+ + + + + ); + + return ( + <> + + + ); +} diff --git a/geneit_backend/src/main.rs b/geneit_backend/src/main.rs index 1d58677..a1cf15a 100644 --- a/geneit_backend/src/main.rs +++ b/geneit_backend/src/main.rs @@ -18,6 +18,7 @@ async fn main() -> std::io::Result<()> { .allowed_origin(&AppConfig::get().website_origin) .allowed_methods(vec!["GET", "POST"]) .allowed_header("X-Auth-Token") + .allow_any_header() .supports_credentials() .max_age(3600), )