WIP: Migrate to matrix-rust-sdk #111
@@ -1,8 +1,9 @@
|
||||
use crate::app_config::AppConfig;
|
||||
use crate::controllers::{HttpFailure, HttpResult};
|
||||
use crate::extractors::auth_extractor::{AuthExtractor, AuthenticatedMethod};
|
||||
use crate::extractors::matrix_client_extractor::MatrixClientExtractor;
|
||||
use crate::extractors::session_extractor::MatrixGWSession;
|
||||
use crate::users::{ExtendedUserInfo, User, UserEmail};
|
||||
use crate::users::{User, UserEmail};
|
||||
use actix_remote_ip::RemoteIP;
|
||||
use actix_web::{HttpResponse, web};
|
||||
use light_openid::primitives::OpenIDConfig;
|
||||
@@ -107,8 +108,8 @@ pub async fn finish_oidc(
|
||||
}
|
||||
|
||||
/// Get current user information
|
||||
pub async fn auth_info(auth: AuthExtractor) -> HttpResult {
|
||||
Ok(HttpResponse::Ok().json(ExtendedUserInfo::from_user(auth.user).await?))
|
||||
pub async fn auth_info(client: MatrixClientExtractor) -> HttpResult {
|
||||
Ok(HttpResponse::Ok().json(client.to_extended_user_info().await?))
|
||||
}
|
||||
|
||||
/// Sign out user
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use crate::controllers::HttpResult;
|
||||
use crate::extractors::matrix_client_extractor::MatrixClientExtractor;
|
||||
use crate::matrix_connection::matrix_client::FinishMatrixAuth;
|
||||
use actix_web::HttpResponse;
|
||||
use anyhow::Context;
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
struct StartAuthResponse {
|
||||
@@ -12,3 +14,13 @@ pub async fn start_auth(client: MatrixClientExtractor) -> HttpResult {
|
||||
let url = client.client.initiate_login().await?.to_string();
|
||||
Ok(HttpResponse::Ok().json(StartAuthResponse { url }))
|
||||
}
|
||||
|
||||
/// Finish user authentication on Matrix server
|
||||
pub async fn finish_auth(client: MatrixClientExtractor) -> HttpResult {
|
||||
client
|
||||
.client
|
||||
.finish_login(client.auth.decode_json_body::<FinishMatrixAuth>()?)
|
||||
.await
|
||||
.context("Failed to finalize Matrix authentication!")?;
|
||||
Ok(HttpResponse::Accepted().finish())
|
||||
}
|
||||
|
||||
@@ -28,7 +28,9 @@ impl ResponseError for HttpFailure {
|
||||
}
|
||||
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
HttpResponse::build(self.status_code()).body(self.to_string())
|
||||
HttpResponse::build(self.status_code())
|
||||
.content_type("text/plain")
|
||||
.body(self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ use anyhow::Context;
|
||||
use bytes::Bytes;
|
||||
use jwt_simple::common::VerificationOptions;
|
||||
use jwt_simple::prelude::{Duration, HS256Key, MACLike};
|
||||
use jwt_simple::reexports::serde_json;
|
||||
use serde::de::DeserializeOwned;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::fmt::Display;
|
||||
use std::net::IpAddr;
|
||||
@@ -32,6 +34,16 @@ pub struct AuthExtractor {
|
||||
pub payload: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl AuthExtractor {
|
||||
pub fn decode_json_body<E: DeserializeOwned + Send>(&self) -> anyhow::Result<E> {
|
||||
let payload = self
|
||||
.payload
|
||||
.as_ref()
|
||||
.context("Failed to decode request as json: missing payload!")?;
|
||||
serde_json::from_slice(payload).context("Failed to decode request json!")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct MatrixJWTKID {
|
||||
pub user_email: UserEmail,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::extractors::auth_extractor::AuthExtractor;
|
||||
use crate::matrix_connection::matrix_client::MatrixClient;
|
||||
use crate::matrix_connection::matrix_manager::MatrixManagerMsg;
|
||||
use crate::users::ExtendedUserInfo;
|
||||
use actix_web::dev::Payload;
|
||||
use actix_web::{FromRequest, HttpRequest, web};
|
||||
use ractor::ActorRef;
|
||||
@@ -10,6 +11,16 @@ pub struct MatrixClientExtractor {
|
||||
pub client: MatrixClient,
|
||||
}
|
||||
|
||||
impl MatrixClientExtractor {
|
||||
pub async fn to_extended_user_info(&self) -> anyhow::Result<ExtendedUserInfo> {
|
||||
Ok(ExtendedUserInfo {
|
||||
user: self.auth.user.clone(),
|
||||
matrix_user_id: self.client.client.user_id().map(|id| id.to_string()),
|
||||
matrix_device_id: self.client.client.device_id().map(|id| id.to_string()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FromRequest for MatrixClientExtractor {
|
||||
type Error = actix_web::Error;
|
||||
type Future = futures_util::future::LocalBoxFuture<'static, Result<Self, Self::Error>>;
|
||||
|
||||
@@ -55,7 +55,7 @@ async fn main() -> std::io::Result<()> {
|
||||
|
||||
let cors = Cors::default()
|
||||
.allowed_origin(&AppConfig::get().website_origin)
|
||||
.allowed_methods(vec!["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"])
|
||||
.allowed_methods(["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"])
|
||||
.allowed_header(constants::API_AUTH_HEADER)
|
||||
.allow_any_header()
|
||||
.supports_credentials()
|
||||
@@ -94,6 +94,10 @@ async fn main() -> std::io::Result<()> {
|
||||
"/api/matrix_link/start_auth",
|
||||
web::post().to(matrix_link_controller::start_auth),
|
||||
)
|
||||
.route(
|
||||
"/api/matrix_link/finish_auth",
|
||||
web::post().to(matrix_link_controller::finish_auth),
|
||||
)
|
||||
})
|
||||
.workers(4)
|
||||
.bind(&AppConfig::get().listen_address)?
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::app_config::AppConfig;
|
||||
use crate::users::UserEmail;
|
||||
use crate::utils::rand_utils::rand_string;
|
||||
use matrix_sdk::authentication::oauth::OAuthError;
|
||||
use matrix_sdk::authentication::oauth::error::OAuthDiscoveryError;
|
||||
use matrix_sdk::authentication::oauth::{OAuthError, UrlOrQuery};
|
||||
use matrix_sdk::ruma::serde::Raw;
|
||||
use matrix_sdk::{Client, ClientBuildError};
|
||||
use url::Url;
|
||||
@@ -28,6 +28,14 @@ enum MatrixClientError {
|
||||
ParseAuthRedirectURL(url::ParseError),
|
||||
#[error("Failed to build auth request! {0}")]
|
||||
BuildAuthRequest(OAuthError),
|
||||
#[error("Failed to finalize authentication! {0}")]
|
||||
FinishLogin(matrix_sdk::Error),
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
pub struct FinishMatrixAuth {
|
||||
code: String,
|
||||
state: String,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -91,7 +99,7 @@ impl MatrixClient {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// Initiate oauth authentication
|
||||
/// Initiate OAuth authentication
|
||||
pub async fn initiate_login(&self) -> anyhow::Result<Url> {
|
||||
let oauth = self.client.oauth();
|
||||
|
||||
@@ -112,4 +120,23 @@ impl MatrixClient {
|
||||
|
||||
Ok(auth.url)
|
||||
}
|
||||
|
||||
/// Finish OAuth authentication
|
||||
pub async fn finish_login(&self, info: FinishMatrixAuth) -> anyhow::Result<()> {
|
||||
let oauth = self.client.oauth();
|
||||
oauth
|
||||
.finish_login(UrlOrQuery::Query(format!(
|
||||
"state={}&code={}",
|
||||
info.state, info.code
|
||||
)))
|
||||
.await
|
||||
.map_err(MatrixClientError::FinishLogin)?;
|
||||
|
||||
log::info!(
|
||||
"User successfully authenticated as {}!",
|
||||
self.client.user_id().unwrap()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,13 +172,5 @@ 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
|
||||
})
|
||||
}
|
||||
pub matrix_device_id: Option<String>,
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import { MatrixLinkRoute } from "./routes/MatrixLinkRoute";
|
||||
import { NotFoundRoute } from "./routes/NotFoundRoute";
|
||||
import { BaseLoginPage } from "./widgets/auth/BaseLoginPage";
|
||||
import BaseAuthenticatedPage from "./widgets/dashboard/BaseAuthenticatedPage";
|
||||
import { MatrixAuthCallback } from "./routes/MatrixAuthCallback";
|
||||
|
||||
interface AuthContext {
|
||||
signedIn: boolean;
|
||||
@@ -39,6 +40,7 @@ export function App(): React.ReactElement {
|
||||
<Route path="*" element={<BaseAuthenticatedPage />}>
|
||||
<Route path="" element={<HomeRoute />} />
|
||||
<Route path="matrix_link" element={<MatrixLinkRoute />} />
|
||||
<Route path="matrix_auth_cb" element={<MatrixAuthCallback />} />
|
||||
<Route path="*" element={<NotFoundRoute />} />
|
||||
</Route>
|
||||
) : (
|
||||
|
||||
@@ -7,6 +7,7 @@ export interface UserInfo {
|
||||
name: string;
|
||||
email: string;
|
||||
matrix_user_id?: string;
|
||||
matrix_device_id?: string;
|
||||
}
|
||||
|
||||
const TokenStateKey = "auth-state";
|
||||
|
||||
@@ -12,4 +12,15 @@ export class MatrixLinkApi {
|
||||
})
|
||||
).data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish Matrix Account login
|
||||
*/
|
||||
static async FinishAuth(code: string, state: string): Promise<void> {
|
||||
await APIClient.exec({
|
||||
uri: "/matrix_link/finish_auth",
|
||||
method: "POST",
|
||||
jsonData: { code, state },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
79
matrixgw_frontend/src/routes/MatrixAuthCallback.tsx
Normal file
79
matrixgw_frontend/src/routes/MatrixAuthCallback.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import { Alert, Box, Button, CircularProgress } from "@mui/material";
|
||||
import React from "react";
|
||||
import { useNavigate, useSearchParams } from "react-router";
|
||||
import { MatrixLinkApi } from "../api/MatrixLinkApi";
|
||||
import { useUserInfo } from "../widgets/dashboard/BaseAuthenticatedPage";
|
||||
import { RouterLink } from "../widgets/RouterLink";
|
||||
import { useSnackbar } from "../hooks/contexts_provider/SnackbarProvider";
|
||||
|
||||
export function MatrixAuthCallback(): React.ReactElement {
|
||||
const navigate = useNavigate();
|
||||
const snackbar = useSnackbar();
|
||||
|
||||
const info = useUserInfo();
|
||||
|
||||
const [error, setError] = React.useState<null | string>(null);
|
||||
|
||||
const [searchParams] = useSearchParams();
|
||||
const code = searchParams.get("code");
|
||||
const state = searchParams.get("state");
|
||||
|
||||
const count = React.useRef("");
|
||||
|
||||
React.useEffect(() => {
|
||||
const load = async () => {
|
||||
try {
|
||||
if (count.current === code) {
|
||||
return;
|
||||
}
|
||||
count.current = code!;
|
||||
|
||||
await MatrixLinkApi.FinishAuth(code!, state!);
|
||||
info.reloadUserInfo();
|
||||
snackbar("Successfully linked to Matrix account!");
|
||||
navigate("/matrix_link");
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
setError(String(e));
|
||||
}
|
||||
};
|
||||
|
||||
load();
|
||||
});
|
||||
|
||||
if (error)
|
||||
return (
|
||||
<Box
|
||||
component="div"
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: "100%",
|
||||
flex: "1",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<Alert
|
||||
variant="outlined"
|
||||
severity="error"
|
||||
style={{ margin: "0px 15px 15px 15px" }}
|
||||
>
|
||||
Failed to finalize Matrix authentication!
|
||||
<br />
|
||||
<br />
|
||||
{error}
|
||||
</Alert>
|
||||
|
||||
<Button>
|
||||
<RouterLink to="/matrix_link">Go back</RouterLink>
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
|
||||
return (
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<CircularProgress />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -79,9 +79,17 @@ function ConnectedCard(): React.ReactElement {
|
||||
|
||||
<Typography variant="body1" gutterBottom>
|
||||
<p>
|
||||
MatrixGW is currently connected to your account with ID{" "}
|
||||
<i>{user.info.matrix_user_id}</i>.
|
||||
MatrixGW is currently connected to your account with the following
|
||||
information:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
User id: <i>{user.info.matrix_user_id}</i>
|
||||
</li>
|
||||
<li>
|
||||
Device id: <i>{user.info.matrix_device_id}</i>
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
If you encounter issues with your Matrix account you can try to
|
||||
disconnect and connect back again.
|
||||
|
||||
Reference in New Issue
Block a user