Can authenticate using OpenID

This commit is contained in:
Pierre HUBERT 2023-06-09 10:45:01 +02:00
parent 94a4ab4f91
commit d55718f4de
6 changed files with 219 additions and 3 deletions

View File

@ -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 (
<Routes>
{AuthApi.SignedIn ? (
{signedIn ? (
<Route path="*" element={<p>signed in</p>} />
) : (
<Route path="*" element={<BaseLoginPage />}>
<Route path="" element={<LoginRoute />} />
<Route path="oidc_cb" element={<OIDCCbRoute />} />
<Route path="*" element={<NotFoundRoute />} />
</Route>
)}

View File

@ -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<void> {
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);
}
}

View File

@ -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;
}

View File

@ -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<string | null>(null);
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
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 (
<>
<CircularProgress />
</>
);
return (
<>
{error === null ? (
<></>
) : (
<Alert style={{ width: "100%" }} severity="error">
{error}
</Alert>
)}
<Typography component="h2" variant="body1">
Connexion
</Typography>
<Box component="form" noValidate onSubmit={handleSubmit} sx={{ mt: 1 }}>
<TextField
margin="normal"
required
fullWidth
id="email"
label="Adresse mail"
name="email"
autoComplete="email"
autoFocus
/>
<TextField
margin="normal"
required
fullWidth
name="password"
label="Mot de passe"
type="password"
id="password"
autoComplete="current-password"
/>
<Button
type="submit"
fullWidth
variant="contained"
sx={{ mt: 3, mb: 2 }}
>
Connexion
</Button>
<Grid container>
<Grid item xs>
<Link href="#" variant="body2">
Mot de passe oublié
</Link>
</Grid>
<Grid item>
<Link href="#" variant="body2">
Créer un nouveau compte
</Link>
</Grid>
</Grid>
<div>
{ServerApi.Config.oidc_providers.map((p) => (
<Button
style={{ textAlign: "center", width: "100%", marginTop: "20px" }}
onClick={() => authWithProvider(p.id)}
>
Connection avec {p.name}
</Button>
))}
</div>
</Box>
</>
);
}

View File

@ -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 (
<>
<p>Echec de la finalisation de l'authentification !</p>
<Link to={"/"}>
<Button>Retour à l'accueil</Button>
</Link>
</>
);
return (
<>
<CircularProgress />
</>
);
}

View File

@ -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),
)