Can finalize Matrix authentication

This commit is contained in:
2025-11-05 19:32:11 +01:00
parent 37fad9ff55
commit 1eaec9d319
13 changed files with 180 additions and 18 deletions

View File

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

View File

@@ -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())
}

View File

@@ -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())
}
}

View File

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

View File

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

View File

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

View File

@@ -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(())
}
}

View File

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