Block WS access if Matrix account is not linked

This commit is contained in:
2025-11-21 09:12:13 +01:00
parent 6b70842b61
commit 24f06a78a9
7 changed files with 26 additions and 4 deletions

View File

@@ -6,7 +6,7 @@ use crate::extractors::matrix_client_extractor::MatrixClientExtractor;
use crate::matrix_connection::matrix_client::MatrixClient; use crate::matrix_connection::matrix_client::MatrixClient;
use crate::matrix_connection::matrix_manager::MatrixManagerMsg; use crate::matrix_connection::matrix_manager::MatrixManagerMsg;
use actix_web::dev::Payload; use actix_web::dev::Payload;
use actix_web::{FromRequest, HttpRequest, web}; use actix_web::{FromRequest, HttpRequest, HttpResponse, web};
use actix_ws::Message; use actix_ws::Message;
use futures_util::StreamExt; use futures_util::StreamExt;
use matrix_sdk::ruma::OwnedRoomId; use matrix_sdk::ruma::OwnedRoomId;
@@ -38,6 +38,11 @@ pub async fn ws(
// Forcefully ignore request payload by manually extracting authentication information // Forcefully ignore request payload by manually extracting authentication information
let client = MatrixClientExtractor::from_request(&req, &mut Payload::None).await?; let client = MatrixClientExtractor::from_request(&req, &mut Payload::None).await?;
// Check if Matrix link has been established first
if !client.client.is_client_connected() {
return Ok(HttpResponse::ExpectationFailed().json("Matrix link not established yet!"));
}
// Ensure sync thread is started // Ensure sync thread is started
ractor::cast!( ractor::cast!(
manager, manager,

View File

@@ -15,6 +15,7 @@ impl MatrixClientExtractor {
pub async fn to_extended_user_info(&self) -> anyhow::Result<ExtendedUserInfo> { pub async fn to_extended_user_info(&self) -> anyhow::Result<ExtendedUserInfo> {
Ok(ExtendedUserInfo { Ok(ExtendedUserInfo {
user: self.auth.user.clone(), user: self.auth.user.clone(),
matrix_account_connected: self.client.is_client_connected(),
matrix_user_id: self.client.user_id().map(|id| id.to_string()), matrix_user_id: self.client.user_id().map(|id| id.to_string()),
matrix_device_id: self.client.device_id().map(|id| id.to_string()), matrix_device_id: self.client.device_id().map(|id| id.to_string()),
matrix_recovery_state: self.client.recovery_state(), matrix_recovery_state: self.client.recovery_state(),

View File

@@ -281,6 +281,7 @@ impl APIToken {
pub struct ExtendedUserInfo { pub struct ExtendedUserInfo {
#[serde(flatten)] #[serde(flatten)]
pub user: User, pub user: User,
pub matrix_account_connected: bool,
pub matrix_user_id: Option<String>, pub matrix_user_id: Option<String>,
pub matrix_device_id: Option<String>, pub matrix_device_id: Option<String>,
pub matrix_recovery_state: EncryptionRecoveryState, pub matrix_recovery_state: EncryptionRecoveryState,

View File

@@ -6,6 +6,7 @@ export interface UserInfo {
time_update: number; time_update: number;
name: string; name: string;
email: string; email: string;
matrix_account_connected: boolean;
matrix_user_id?: string; matrix_user_id?: string;
matrix_device_id?: string; matrix_device_id?: string;
matrix_recovery_state?: "Enabled" | "Disabled" | "Unknown" | "Incomplete"; matrix_recovery_state?: "Enabled" | "Disabled" | "Unknown" | "Incomplete";

View File

@@ -6,7 +6,7 @@ import { NotLinkedAccountMessage } from "../widgets/NotLinkedAccountMessage";
export function HomeRoute(): React.ReactElement { export function HomeRoute(): React.ReactElement {
const user = useUserInfo(); const user = useUserInfo();
if (!user.info.matrix_user_id) return <NotLinkedAccountMessage />; if (!user.info.matrix_account_connected) return <NotLinkedAccountMessage />;
return ( return (
<p> <p>

View File

@@ -4,7 +4,9 @@ import "react-json-view-lite/dist/index.css";
import { WsApi, type WsMessage } from "../api/WsApi"; import { WsApi, type WsMessage } from "../api/WsApi";
import { useSnackbar } from "../hooks/contexts_provider/SnackbarProvider"; import { useSnackbar } from "../hooks/contexts_provider/SnackbarProvider";
import { time } from "../utils/DateUtils"; import { time } from "../utils/DateUtils";
import { useUserInfo } from "../widgets/dashboard/BaseAuthenticatedPage";
import { MatrixGWRouteContainer } from "../widgets/MatrixGWRouteContainer"; import { MatrixGWRouteContainer } from "../widgets/MatrixGWRouteContainer";
import { NotLinkedAccountMessage } from "../widgets/NotLinkedAccountMessage";
const State = { const State = {
Closed: "Closed", Closed: "Closed",
@@ -15,6 +17,9 @@ const State = {
type TimestampedMessages = WsMessage & { time: number }; type TimestampedMessages = WsMessage & { time: number };
export function WSDebugRoute(): React.ReactElement { export function WSDebugRoute(): React.ReactElement {
const user = useUserInfo();
if (!user.info.matrix_account_connected) return <NotLinkedAccountMessage />;
const snackbar = useSnackbar(); const snackbar = useSnackbar();
const [state, setState] = React.useState<string>(State.Closed); const [state, setState] = React.useState<string>(State.Closed);
@@ -28,7 +33,7 @@ export function WSDebugRoute(): React.ReactElement {
ws.onopen = () => setState(State.Connected); ws.onopen = () => setState(State.Connected);
ws.onerror = (e) => { ws.onerror = (e) => {
console.error(`WS Debug error! ${e}`); console.error(`WS Debug error!`, e);
snackbar(`WebSocket error! ${e}`); snackbar(`WebSocket error! ${e}`);
setState(State.Error); setState(State.Error);
}; };

View File

@@ -8,6 +8,7 @@ import { useTheme } from "@mui/material/styles";
import type {} from "@mui/material/themeCssVarsAugmentation"; import type {} from "@mui/material/themeCssVarsAugmentation";
import useMediaQuery from "@mui/material/useMediaQuery"; import useMediaQuery from "@mui/material/useMediaQuery";
import * as React from "react"; import * as React from "react";
import { useUserInfo } from "./BaseAuthenticatedPage";
import DashboardSidebarContext from "./DashboardSidebarContext"; import DashboardSidebarContext from "./DashboardSidebarContext";
import DashboardSidebarDividerItem from "./DashboardSidebarDividerItem"; import DashboardSidebarDividerItem from "./DashboardSidebarDividerItem";
import DashboardSidebarPageItem from "./DashboardSidebarPageItem"; import DashboardSidebarPageItem from "./DashboardSidebarPageItem";
@@ -31,6 +32,7 @@ export default function DashboardSidebar({
container, container,
}: DashboardSidebarProps) { }: DashboardSidebarProps) {
const theme = useTheme(); const theme = useTheme();
const user = useUserInfo();
const isOverSmViewport = useMediaQuery(theme.breakpoints.up("sm")); const isOverSmViewport = useMediaQuery(theme.breakpoints.up("sm"));
const isOverMdViewport = useMediaQuery(theme.breakpoints.up("md")); const isOverMdViewport = useMediaQuery(theme.breakpoints.up("md"));
@@ -99,6 +101,7 @@ export default function DashboardSidebar({
}} }}
> >
<DashboardSidebarPageItem <DashboardSidebarPageItem
disabled={!user.info.matrix_account_connected}
title="Messages" title="Messages"
icon={<Icon path={mdiForum} size={"1.5em"} />} icon={<Icon path={mdiForum} size={"1.5em"} />}
href="/" href="/"
@@ -115,6 +118,7 @@ export default function DashboardSidebar({
href="/tokens" href="/tokens"
/> />
<DashboardSidebarPageItem <DashboardSidebarPageItem
disabled={!user.info.matrix_account_connected}
title="WS Debug" title="WS Debug"
icon={<Icon path={mdiBug} size={"1.5em"} />} icon={<Icon path={mdiBug} size={"1.5em"} />}
href="/wsdebug" href="/wsdebug"
@@ -123,7 +127,12 @@ export default function DashboardSidebar({
</Box> </Box>
</React.Fragment> </React.Fragment>
), ),
[mini, hasDrawerTransitions, isFullyExpanded] [
mini,
hasDrawerTransitions,
isFullyExpanded,
user.info.matrix_account_connected,
]
); );
const getDrawerSharedSx = React.useCallback( const getDrawerSharedSx = React.useCallback(