Can start Matrix authentication from UI

This commit is contained in:
2025-11-05 18:27:41 +01:00
parent 3dab9f41d2
commit 37fad9ff55
12 changed files with 195 additions and 6 deletions

View File

@@ -2,7 +2,7 @@ use crate::app_config::AppConfig;
use crate::controllers::{HttpFailure, HttpResult};
use crate::extractors::auth_extractor::{AuthExtractor, AuthenticatedMethod};
use crate::extractors::session_extractor::MatrixGWSession;
use crate::users::{User, UserEmail};
use crate::users::{ExtendedUserInfo, User, UserEmail};
use actix_remote_ip::RemoteIP;
use actix_web::{HttpResponse, web};
use light_openid::primitives::OpenIDConfig;
@@ -108,7 +108,7 @@ pub async fn finish_oidc(
/// Get current user information
pub async fn auth_info(auth: AuthExtractor) -> HttpResult {
Ok(HttpResponse::Ok().json(auth.user))
Ok(HttpResponse::Ok().json(ExtendedUserInfo::from_user(auth.user).await?))
}
/// Sign out user

View File

@@ -166,3 +166,19 @@ impl APIToken {
(self.last_used + self.max_inactivity) < time_secs()
}
}
#[derive(serde::Serialize, Debug, Clone)]
pub struct ExtendedUserInfo {
#[serde(flatten)]
pub user: User,
pub matrix_user_id: Option<String>,
}
impl ExtendedUserInfo {
pub async fn from_user(user: User) -> anyhow::Result<Self> {
Ok(Self {
user,
matrix_user_id: None, // TODO
})
}
}

View File

@@ -10,6 +10,7 @@ import { ServerApi } from "./api/ServerApi";
import { LoginRoute } from "./routes/auth/LoginRoute";
import { OIDCCbRoute } from "./routes/auth/OIDCCbRoute";
import { HomeRoute } from "./routes/HomeRoute";
import { MatrixLinkRoute } from "./routes/MatrixLinkRoute";
import { NotFoundRoute } from "./routes/NotFoundRoute";
import { BaseLoginPage } from "./widgets/auth/BaseLoginPage";
import BaseAuthenticatedPage from "./widgets/dashboard/BaseAuthenticatedPage";
@@ -37,6 +38,7 @@ export function App(): React.ReactElement {
signedIn || ServerApi.Config.auth_disabled ? (
<Route path="*" element={<BaseAuthenticatedPage />}>
<Route path="" element={<HomeRoute />} />
<Route path="matrix_link" element={<MatrixLinkRoute />} />
<Route path="*" element={<NotFoundRoute />} />
</Route>
) : (

View File

@@ -6,6 +6,7 @@ export interface UserInfo {
time_update: number;
name: string;
email: string;
matrix_user_id?: string;
}
const TokenStateKey = "auth-state";

View File

@@ -0,0 +1,15 @@
import { APIClient } from "./ApiClient";
export class MatrixLinkApi {
/**
* Start Matrix Account login
*/
static async StartAuth(): Promise<{ url: string }> {
return (
await APIClient.exec({
uri: "/matrix_link/start_auth",
method: "POST",
})
).data;
}
}

View File

@@ -1,3 +1,10 @@
import { useUserInfo } from "../widgets/dashboard/BaseAuthenticatedPage";
import { NotLinkedAccountMessage } from "../widgets/NotLinkedAccountMessage";
export function HomeRoute(): React.ReactElement {
const user = useUserInfo();
if (!user.info.matrix_user_id) return <NotLinkedAccountMessage />;
return <p>Todo home route</p>;
}

View File

@@ -0,0 +1,98 @@
import LinkIcon from "@mui/icons-material/Link";
import LinkOffIcon from "@mui/icons-material/LinkOff";
import {
Button,
Card,
CardActions,
CardContent,
Typography,
} from "@mui/material";
import { MatrixLinkApi } from "../api/MatrixLinkApi";
import { useAlert } from "../hooks/contexts_provider/AlertDialogProvider";
import { useLoadingMessage } from "../hooks/contexts_provider/LoadingMessageProvider";
import { useUserInfo } from "../widgets/dashboard/BaseAuthenticatedPage";
import { MatrixGWRouteContainer } from "../widgets/MatrixGWRouteContainer";
export function MatrixLinkRoute(): React.ReactElement {
const user = useUserInfo();
return (
<MatrixGWRouteContainer label={"Matrix account link"}>
{user.info.matrix_user_id === null ? <ConnectCard /> : <ConnectedCard />}
</MatrixGWRouteContainer>
);
}
function ConnectCard(): React.ReactElement {
const alert = useAlert();
const loadingMessage = useLoadingMessage();
const startMatrixConnection = async () => {
try {
loadingMessage.show("Initiating Matrix link...");
const res = await MatrixLinkApi.StartAuth();
window.location.href = res.url;
} catch (e) {
console.error(`Failed to connect to Matrix account! ${e}`);
alert(`Failed to connect to Matrix account! ${e}`);
} finally {
loadingMessage.hide();
}
};
return (
<Card>
<CardContent>
<Typography variant="h5" component="div" gutterBottom>
<i>Disconnected from your Matrix account</i>
</Typography>
<Typography variant="body1" gutterBottom>
You need to connect MatrixGW to your Matrix account to let it access
your messages.
</Typography>
</CardContent>
<CardActions>
<Button
size="small"
variant="outlined"
startIcon={<LinkIcon />}
onClick={startMatrixConnection}
>
Connect now
</Button>
</CardActions>
</Card>
);
}
function ConnectedCard(): React.ReactElement {
const user = useUserInfo();
return (
<Card>
<CardContent>
<Typography variant="h5" component="div" gutterBottom>
<i>Connected to your Matrix account</i>
</Typography>
<Typography variant="body1" gutterBottom>
<p>
MatrixGW is currently connected to your account with ID{" "}
<i>{user.info.matrix_user_id}</i>.
</p>
<p>
If you encounter issues with your Matrix account you can try to
disconnect and connect back again.
</p>
</Typography>
</CardContent>
<CardActions>
<Button size="small" variant="outlined" startIcon={<LinkOffIcon />}>
Disconnect
</Button>
</CardActions>
</Card>
);
}

View File

@@ -0,0 +1,37 @@
import { Typography } from "@mui/material";
import React, { type PropsWithChildren } from "react";
export function MatrixGWRouteContainer(
p: {
label: string | React.ReactElement;
actions?: React.ReactElement;
} & PropsWithChildren
): React.ReactElement {
return (
<div
style={{
margin: "50px",
flexGrow: 1,
flexShrink: 0,
flexBasis: 0,
minWidth: 0,
display: "flex",
flexDirection: "column",
}}
>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: "20px",
}}
>
<Typography variant="h4">{p.label}</Typography>
{p.actions ?? <></>}
</div>
{p.children}
</div>
);
}

View File

@@ -0,0 +1,14 @@
export function NotLinkedAccountMessage(): React.ReactElement {
return (
<div
style={{
flex: 1,
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
Your Matrix account is not linked yet!
</div>
);
}

View File

@@ -124,7 +124,6 @@ export default function BaseAuthenticatedPage(): React.ReactElement {
flexDirection: "column",
flex: 1,
overflow: "auto",
padding: "50px",
}}
>
<Outlet />
@@ -137,6 +136,6 @@ export default function BaseAuthenticatedPage(): React.ReactElement {
);
}
export function userUserInfo(): UserInfoContext {
export function useUserInfo(): UserInfoContext {
return React.use(UserInfoContextK)!;
}

View File

@@ -14,7 +14,7 @@ import Tooltip from "@mui/material/Tooltip";
import Typography from "@mui/material/Typography";
import * as React from "react";
import { RouterLink } from "../RouterLink";
import { userUserInfo as useUserInfo } from "./BaseAuthenticatedPage";
import { useUserInfo } from "./BaseAuthenticatedPage";
import ThemeSwitcher from "./ThemeSwitcher";
const AppBar = styled(MuiAppBar)(({ theme }) => ({

View File

@@ -107,7 +107,7 @@ export default function DashboardSidebar({
<DashboardSidebarPageItem
title="Matrix link"
icon={<Icon path={mdiLinkLock} size={"1.5em"} />}
href="/matrixlink"
href="/matrix_link"
/>
<DashboardSidebarPageItem
title="API tokens"