From d67f42abc5cb1a007a9a20440dcb0863fb65777f Mon Sep 17 00:00:00 2001 From: Pierre Hubert Date: Mon, 4 Sep 2023 15:12:00 +0200 Subject: [PATCH] Can sign out --- virtweb_backend/src/app_config.rs | 12 ++++ virtweb_backend/src/main.rs | 4 +- .../src/middlewares/auth_middleware.rs | 6 +- virtweb_frontend/src/api/ApiClient.ts | 1 + virtweb_frontend/src/api/AuthApi.ts | 16 +++++ .../providers/LoadingMessageProvider.tsx | 64 +++++++++++++++++++ virtweb_frontend/src/index.tsx | 9 ++- .../src/widgets/BaseAuthenticatedPage.tsx | 18 +++++- .../src/widgets/VirtWebAppBar.tsx | 61 ++++++++++++++++++ 9 files changed, 184 insertions(+), 7 deletions(-) create mode 100644 virtweb_frontend/src/hooks/providers/LoadingMessageProvider.tsx create mode 100644 virtweb_frontend/src/widgets/VirtWebAppBar.tsx diff --git a/virtweb_backend/src/app_config.rs b/virtweb_backend/src/app_config.rs index ea36e26..f10eb02 100644 --- a/virtweb_backend/src/app_config.rs +++ b/virtweb_backend/src/app_config.rs @@ -81,6 +81,18 @@ impl AppConfig { &ARGS } + /// Get auth cookie domain + pub fn cookie_domain(&self) -> Option { + let domain = self.website_origin.split_once("://")?.1; + Some( + domain + .split_once(':') + .map(|s| s.0) + .unwrap_or(domain) + .to_string(), + ) + } + /// Get app secret pub fn secret(&self) -> &str { let mut secret = self.secret.as_str(); diff --git a/virtweb_backend/src/main.rs b/virtweb_backend/src/main.rs index 93ccb08..c5baaad 100644 --- a/virtweb_backend/src/main.rs +++ b/virtweb_backend/src/main.rs @@ -34,6 +34,8 @@ async fn main() -> std::io::Result<()> { .cookie_name(SESSION_COOKIE_NAME.to_string()) .cookie_secure(AppConfig::get().cookie_secure) .cookie_same_site(SameSite::Strict) + .cookie_domain(AppConfig::get().cookie_domain()) + .cookie_http_only(true) .build(); let identity_middleware = IdentityMiddleware::builder() @@ -51,11 +53,11 @@ async fn main() -> std::io::Result<()> { .max_age(3600); App::new() - .wrap(cors) .wrap(Logger::default()) .wrap(AuthChecker) .wrap(identity_middleware) .wrap(session_mw) + .wrap(cors) .app_data(state_manager.clone()) .app_data(Data::new(RemoteIPConfig { proxy: AppConfig::get().proxy_ip.clone(), diff --git a/virtweb_backend/src/middlewares/auth_middleware.rs b/virtweb_backend/src/middlewares/auth_middleware.rs index 669abcf..0df2020 100644 --- a/virtweb_backend/src/middlewares/auth_middleware.rs +++ b/virtweb_backend/src/middlewares/auth_middleware.rs @@ -71,7 +71,7 @@ where "Failed to extract authentication information from request! {e}" ); return Ok(req - .into_response(HttpResponse::InternalServerError().finish()) + .into_response(HttpResponse::PreconditionFailed().finish()) .map_into_right_body()); } }; @@ -81,7 +81,9 @@ where "User attempted to access privileged route without authentication!" ); return Ok(req - .into_response(HttpResponse::Unauthorized().json("Please authenticate!")) + .into_response( + HttpResponse::PreconditionFailed().json("Please authenticate!"), + ) .map_into_right_body()); } } diff --git a/virtweb_frontend/src/api/ApiClient.ts b/virtweb_frontend/src/api/ApiClient.ts index 79cd77f..1b60200 100644 --- a/virtweb_frontend/src/api/ApiClient.ts +++ b/virtweb_frontend/src/api/ApiClient.ts @@ -57,6 +57,7 @@ export class APIClient { method: args.method, body: body, headers: headers, + credentials: "include", }); // Process response diff --git a/virtweb_frontend/src/api/AuthApi.ts b/virtweb_frontend/src/api/AuthApi.ts index d29b9fe..ceb9c92 100644 --- a/virtweb_frontend/src/api/AuthApi.ts +++ b/virtweb_frontend/src/api/AuthApi.ts @@ -1,5 +1,9 @@ import { APIClient } from "./ApiClient"; +export interface AuthInfo { + id: string; +} + const TokenStateKey = "auth-state"; export class AuthApi { @@ -71,6 +75,18 @@ export class AuthApi { this.SetAuthenticated(); } + /** + * Get auth information + */ + static async GetAuthInfo(): Promise { + return ( + await APIClient.exec({ + uri: "/auth/user", + method: "GET", + }) + ).data; + } + /** * Sign out */ diff --git a/virtweb_frontend/src/hooks/providers/LoadingMessageProvider.tsx b/virtweb_frontend/src/hooks/providers/LoadingMessageProvider.tsx new file mode 100644 index 0000000..6c0c826 --- /dev/null +++ b/virtweb_frontend/src/hooks/providers/LoadingMessageProvider.tsx @@ -0,0 +1,64 @@ +import { + CircularProgress, + Dialog, + DialogContent, + DialogContentText, +} from "@mui/material"; +import React, { PropsWithChildren } from "react"; + +type LoadingMessageContext = { + show: (message: string) => void; + hide: () => void; +}; + +const LoadingMessageContextK = + React.createContext(null); + +export function LoadingMessageProvider( + p: PropsWithChildren +): React.ReactElement { + const [open, setOpen] = React.useState(false); + + const [message, setMessage] = React.useState(""); + + const hook: LoadingMessageContext = { + show(message) { + setMessage(message); + setOpen(true); + }, + hide() { + setMessage(""); + setOpen(false); + }, + }; + + return ( + <> + + {p.children} + + + + + +
+ + + {message} +
+
+
+
+ + ); +} + +export function useLoadingMessage(): LoadingMessageContext { + return React.useContext(LoadingMessageContextK)!; +} diff --git a/virtweb_frontend/src/index.tsx b/virtweb_frontend/src/index.tsx index 4b68ee1..7493df2 100644 --- a/virtweb_frontend/src/index.tsx +++ b/virtweb_frontend/src/index.tsx @@ -10,6 +10,7 @@ import "./index.css"; import reportWebVitals from "./reportWebVitals"; import { LoadServerConfig } from "./widgets/LoadServerConfig"; import { ThemeProvider, createTheme } from "@mui/material"; +import { LoadingMessageProvider } from "./hooks/providers/LoadingMessageProvider"; const darkTheme = createTheme({ palette: { @@ -23,9 +24,11 @@ const root = ReactDOM.createRoot( root.render( - - - + + + + + ); diff --git a/virtweb_frontend/src/widgets/BaseAuthenticatedPage.tsx b/virtweb_frontend/src/widgets/BaseAuthenticatedPage.tsx index 37c40f8..bbc9f6d 100644 --- a/virtweb_frontend/src/widgets/BaseAuthenticatedPage.tsx +++ b/virtweb_frontend/src/widgets/BaseAuthenticatedPage.tsx @@ -1,3 +1,19 @@ +import { Box } from "@mui/material"; +import { VirtWebAppBar } from "./VirtWebAppBar"; + export function BaseAuthenticatedPage(): React.ReactElement { - return <>ready with login; + return ( + theme.palette.grey[900], + color: (theme) => theme.palette.grey[100], + }} + > + + + ); } diff --git a/virtweb_frontend/src/widgets/VirtWebAppBar.tsx b/virtweb_frontend/src/widgets/VirtWebAppBar.tsx new file mode 100644 index 0000000..180b7f5 --- /dev/null +++ b/virtweb_frontend/src/widgets/VirtWebAppBar.tsx @@ -0,0 +1,61 @@ +import { mdiLogout, mdiServer } from "@mdi/js"; +import Icon from "@mdi/react"; +import { AppBar, IconButton, Toolbar, Typography } from "@mui/material"; +import { RouterLink } from "./RouterLink"; +import { AsyncWidget } from "./AsyncWidget"; +import { AuthApi, AuthInfo } from "../api/AuthApi"; +import React from "react"; +import { useAuth } from "../App"; +import { useLoadingMessage } from "../hooks/providers/LoadingMessageProvider"; + +export function VirtWebAppBar(): React.ReactElement { + const loadingMessage = useLoadingMessage(); + + const auth = useAuth(); + + const [info, setInfo] = React.useState(null); + + const load = async () => { + setInfo(await AuthApi.GetAuthInfo()); + }; + + const signOut = async () => { + loadingMessage.show("Signing out..."); + try { + await AuthApi.SignOut(); + } catch (e) { + console.error(e); + } + + auth.setSignedIn(false); + loadingMessage.hide(); + }; + + return ( + ( + + + + + VirtWeb + +
+ + {info?.id} + + + + +
+
+ )} + /> + ); +}