Compare commits
3 Commits
37fad9ff55
...
1ba5372468
| Author | SHA1 | Date | |
|---|---|---|---|
| 1ba5372468 | |||
| 1438e2de0e | |||
| 1eaec9d319 |
1
matrixgw_backend/Cargo.lock
generated
1
matrixgw_backend/Cargo.lock
generated
@@ -3020,6 +3020,7 @@ dependencies = [
|
|||||||
"ractor",
|
"ractor",
|
||||||
"rand 0.9.2",
|
"rand 0.9.2",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"sha2",
|
"sha2",
|
||||||
"thiserror 2.0.17",
|
"thiserror 2.0.17",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
|||||||
@@ -31,3 +31,4 @@ mailchecker = "6.0.19"
|
|||||||
matrix-sdk = "0.14.0"
|
matrix-sdk = "0.14.0"
|
||||||
url = "2.5.7"
|
url = "2.5.7"
|
||||||
ractor = "0.15.9"
|
ractor = "0.15.9"
|
||||||
|
serde_json = "1.0.145"
|
||||||
@@ -220,6 +220,11 @@ impl AppConfig {
|
|||||||
pub fn user_matrix_passphrase_path(&self, mail: &UserEmail) -> PathBuf {
|
pub fn user_matrix_passphrase_path(&self, mail: &UserEmail) -> PathBuf {
|
||||||
self.user_directory(mail).join("matrix-db-passphrase")
|
self.user_directory(mail).join("matrix-db-passphrase")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get user Matrix session file path
|
||||||
|
pub fn user_matrix_session_file_path(&self, mail: &UserEmail) -> PathBuf {
|
||||||
|
self.user_directory(mail).join("matrix-session.json")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, serde::Serialize)]
|
#[derive(Debug, Clone, serde::Serialize)]
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
use crate::app_config::AppConfig;
|
use crate::app_config::AppConfig;
|
||||||
use crate::controllers::{HttpFailure, HttpResult};
|
use crate::controllers::{HttpFailure, HttpResult};
|
||||||
use crate::extractors::auth_extractor::{AuthExtractor, AuthenticatedMethod};
|
use crate::extractors::auth_extractor::{AuthExtractor, AuthenticatedMethod};
|
||||||
|
use crate::extractors::matrix_client_extractor::MatrixClientExtractor;
|
||||||
use crate::extractors::session_extractor::MatrixGWSession;
|
use crate::extractors::session_extractor::MatrixGWSession;
|
||||||
use crate::users::{ExtendedUserInfo, User, UserEmail};
|
use crate::users::{User, UserEmail};
|
||||||
use actix_remote_ip::RemoteIP;
|
use actix_remote_ip::RemoteIP;
|
||||||
use actix_web::{HttpResponse, web};
|
use actix_web::{HttpResponse, web};
|
||||||
use light_openid::primitives::OpenIDConfig;
|
use light_openid::primitives::OpenIDConfig;
|
||||||
@@ -107,8 +108,8 @@ pub async fn finish_oidc(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get current user information
|
/// Get current user information
|
||||||
pub async fn auth_info(auth: AuthExtractor) -> HttpResult {
|
pub async fn auth_info(client: MatrixClientExtractor) -> HttpResult {
|
||||||
Ok(HttpResponse::Ok().json(ExtendedUserInfo::from_user(auth.user).await?))
|
Ok(HttpResponse::Ok().json(client.to_extended_user_info().await?))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sign out user
|
/// Sign out user
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use crate::controllers::HttpResult;
|
use crate::controllers::HttpResult;
|
||||||
use crate::extractors::matrix_client_extractor::MatrixClientExtractor;
|
use crate::extractors::matrix_client_extractor::MatrixClientExtractor;
|
||||||
|
use crate::matrix_connection::matrix_client::FinishMatrixAuth;
|
||||||
use actix_web::HttpResponse;
|
use actix_web::HttpResponse;
|
||||||
|
|
||||||
#[derive(serde::Serialize)]
|
#[derive(serde::Serialize)]
|
||||||
@@ -12,3 +13,18 @@ pub async fn start_auth(client: MatrixClientExtractor) -> HttpResult {
|
|||||||
let url = client.client.initiate_login().await?.to_string();
|
let url = client.client.initiate_login().await?.to_string();
|
||||||
Ok(HttpResponse::Ok().json(StartAuthResponse { url }))
|
Ok(HttpResponse::Ok().json(StartAuthResponse { url }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Finish user authentication on Matrix server
|
||||||
|
pub async fn finish_auth(client: MatrixClientExtractor) -> HttpResult {
|
||||||
|
match client
|
||||||
|
.client
|
||||||
|
.finish_login(client.auth.decode_json_body::<FinishMatrixAuth>()?)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_) => Ok(HttpResponse::Accepted().finish()),
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Failed to finish Matrix authentication: {e}");
|
||||||
|
Err(e.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,7 +28,9 @@ impl ResponseError for HttpFailure {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn error_response(&self) -> HttpResponse {
|
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 bytes::Bytes;
|
||||||
use jwt_simple::common::VerificationOptions;
|
use jwt_simple::common::VerificationOptions;
|
||||||
use jwt_simple::prelude::{Duration, HS256Key, MACLike};
|
use jwt_simple::prelude::{Duration, HS256Key, MACLike};
|
||||||
|
use jwt_simple::reexports::serde_json;
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
@@ -32,6 +34,16 @@ pub struct AuthExtractor {
|
|||||||
pub payload: Option<Vec<u8>>,
|
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)]
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
pub struct MatrixJWTKID {
|
pub struct MatrixJWTKID {
|
||||||
pub user_email: UserEmail,
|
pub user_email: UserEmail,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use crate::extractors::auth_extractor::AuthExtractor;
|
use crate::extractors::auth_extractor::AuthExtractor;
|
||||||
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 crate::users::ExtendedUserInfo;
|
||||||
use actix_web::dev::Payload;
|
use actix_web::dev::Payload;
|
||||||
use actix_web::{FromRequest, HttpRequest, web};
|
use actix_web::{FromRequest, HttpRequest, web};
|
||||||
use ractor::ActorRef;
|
use ractor::ActorRef;
|
||||||
@@ -10,6 +11,16 @@ pub struct MatrixClientExtractor {
|
|||||||
pub client: MatrixClient,
|
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 {
|
impl FromRequest for MatrixClientExtractor {
|
||||||
type Error = actix_web::Error;
|
type Error = actix_web::Error;
|
||||||
type Future = futures_util::future::LocalBoxFuture<'static, Result<Self, Self::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()
|
let cors = Cors::default()
|
||||||
.allowed_origin(&AppConfig::get().website_origin)
|
.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)
|
.allowed_header(constants::API_AUTH_HEADER)
|
||||||
.allow_any_header()
|
.allow_any_header()
|
||||||
.supports_credentials()
|
.supports_credentials()
|
||||||
@@ -94,6 +94,10 @@ async fn main() -> std::io::Result<()> {
|
|||||||
"/api/matrix_link/start_auth",
|
"/api/matrix_link/start_auth",
|
||||||
web::post().to(matrix_link_controller::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)
|
.workers(4)
|
||||||
.bind(&AppConfig::get().listen_address)?
|
.bind(&AppConfig::get().listen_address)?
|
||||||
|
|||||||
@@ -1,15 +1,31 @@
|
|||||||
use crate::app_config::AppConfig;
|
use crate::app_config::AppConfig;
|
||||||
use crate::users::UserEmail;
|
use crate::users::UserEmail;
|
||||||
use crate::utils::rand_utils::rand_string;
|
use crate::utils::rand_utils::rand_string;
|
||||||
use matrix_sdk::authentication::oauth::OAuthError;
|
use anyhow::Context;
|
||||||
use matrix_sdk::authentication::oauth::error::OAuthDiscoveryError;
|
use matrix_sdk::authentication::oauth::error::OAuthDiscoveryError;
|
||||||
|
use matrix_sdk::authentication::oauth::{
|
||||||
|
ClientId, OAuthError, OAuthSession, UrlOrQuery, UserSession,
|
||||||
|
};
|
||||||
use matrix_sdk::ruma::serde::Raw;
|
use matrix_sdk::ruma::serde::Raw;
|
||||||
use matrix_sdk::{Client, ClientBuildError};
|
use matrix_sdk::{Client, ClientBuildError};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
/// The full session to persist.
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
struct StoredSession {
|
||||||
|
/// The OAuth 2.0 user session.
|
||||||
|
user_session: UserSession,
|
||||||
|
|
||||||
|
/// The OAuth 2.0 client ID.
|
||||||
|
client_id: ClientId,
|
||||||
|
}
|
||||||
|
|
||||||
/// Matrix Gateway session errors
|
/// Matrix Gateway session errors
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
enum MatrixClientError {
|
enum MatrixClientError {
|
||||||
|
#[error("Failed to destroy previous client data! {0}")]
|
||||||
|
DestroyPreviousData(Box<MatrixClientError>),
|
||||||
#[error("Failed to create Matrix database storage directory! {0}")]
|
#[error("Failed to create Matrix database storage directory! {0}")]
|
||||||
CreateMatrixDbDir(std::io::Error),
|
CreateMatrixDbDir(std::io::Error),
|
||||||
#[error("Failed to create database passphrase! {0}")]
|
#[error("Failed to create database passphrase! {0}")]
|
||||||
@@ -18,16 +34,34 @@ enum MatrixClientError {
|
|||||||
ReadDbPassphrase(std::io::Error),
|
ReadDbPassphrase(std::io::Error),
|
||||||
#[error("Failed to build Matrix client! {0}")]
|
#[error("Failed to build Matrix client! {0}")]
|
||||||
BuildMatrixClient(ClientBuildError),
|
BuildMatrixClient(ClientBuildError),
|
||||||
|
#[error("Failed to clear Matrix session file! {0}")]
|
||||||
|
ClearMatrixSessionFile(std::io::Error),
|
||||||
#[error("Failed to clear Matrix database storage directory! {0}")]
|
#[error("Failed to clear Matrix database storage directory! {0}")]
|
||||||
ClearMatrixDbDir(std::io::Error),
|
ClearMatrixDbDir(std::io::Error),
|
||||||
#[error("Failed to remove database passphrase! {0}")]
|
#[error("Failed to remove database passphrase! {0}")]
|
||||||
ClearDbPassphrase(std::io::Error),
|
ClearDbPassphrase(std::io::Error),
|
||||||
#[error("Failed to fetch server metadata! {0}")]
|
#[error("Failed to fetch server metadata! {0}")]
|
||||||
FetchServerMetadata(OAuthDiscoveryError),
|
FetchServerMetadata(OAuthDiscoveryError),
|
||||||
|
#[error("Failed to load stored session! {0}")]
|
||||||
|
LoadStoredSession(std::io::Error),
|
||||||
|
#[error("Failed to decode stored session! {0}")]
|
||||||
|
DecodeStoredSession(serde_json::Error),
|
||||||
|
#[error("Failed to restore stored session! {0}")]
|
||||||
|
RestoreSession(matrix_sdk::Error),
|
||||||
#[error("Failed to parse auth redirect URL! {0}")]
|
#[error("Failed to parse auth redirect URL! {0}")]
|
||||||
ParseAuthRedirectURL(url::ParseError),
|
ParseAuthRedirectURL(url::ParseError),
|
||||||
#[error("Failed to build auth request! {0}")]
|
#[error("Failed to build auth request! {0}")]
|
||||||
BuildAuthRequest(OAuthError),
|
BuildAuthRequest(OAuthError),
|
||||||
|
#[error("Failed to finalize authentication! {0}")]
|
||||||
|
FinishLogin(matrix_sdk::Error),
|
||||||
|
#[error("Failed to write session file! {0}")]
|
||||||
|
WriteSessionFile(std::io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
pub struct FinishMatrixAuth {
|
||||||
|
code: String,
|
||||||
|
state: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -39,6 +73,14 @@ pub struct MatrixClient {
|
|||||||
impl MatrixClient {
|
impl MatrixClient {
|
||||||
/// Start to build Matrix client to initiate user authentication
|
/// Start to build Matrix client to initiate user authentication
|
||||||
pub async fn build_client(email: &UserEmail) -> anyhow::Result<Self> {
|
pub async fn build_client(email: &UserEmail) -> anyhow::Result<Self> {
|
||||||
|
// Check if we are restoring a previous state
|
||||||
|
let session_file_path = AppConfig::get().user_matrix_session_file_path(email);
|
||||||
|
let is_restoring = session_file_path.is_file();
|
||||||
|
if !is_restoring {
|
||||||
|
Self::destroy_data(email).map_err(MatrixClientError::DestroyPreviousData)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine Matrix database path
|
||||||
let db_path = AppConfig::get().user_matrix_db_path(email);
|
let db_path = AppConfig::get().user_matrix_db_path(email);
|
||||||
std::fs::create_dir_all(&db_path).map_err(MatrixClientError::CreateMatrixDbDir)?;
|
std::fs::create_dir_all(&db_path).map_err(MatrixClientError::CreateMatrixDbDir)?;
|
||||||
|
|
||||||
@@ -61,37 +103,64 @@ impl MatrixClient {
|
|||||||
.map_err(MatrixClientError::BuildMatrixClient)?;
|
.map_err(MatrixClientError::BuildMatrixClient)?;
|
||||||
|
|
||||||
// Check metadata
|
// Check metadata
|
||||||
let server_metadata = client
|
let oauth = client.oauth();
|
||||||
.oauth()
|
let server_metadata = oauth
|
||||||
.server_metadata()
|
.server_metadata()
|
||||||
.await
|
.await
|
||||||
.map_err(MatrixClientError::FetchServerMetadata)?;
|
.map_err(MatrixClientError::FetchServerMetadata)?;
|
||||||
log::info!("OAuth2 server issuer: {:?}", server_metadata.issuer);
|
log::info!("OAuth2 server issuer: {:?}", server_metadata.issuer);
|
||||||
|
|
||||||
// TODO : restore client if client already existed
|
if is_restoring {
|
||||||
|
let session: StoredSession = serde_json::from_str(
|
||||||
|
std::fs::read_to_string(session_file_path)
|
||||||
|
.map_err(MatrixClientError::LoadStoredSession)?
|
||||||
|
.as_str(),
|
||||||
|
)
|
||||||
|
.map_err(MatrixClientError::DecodeStoredSession)?;
|
||||||
|
|
||||||
Ok(Self {
|
// Restore data
|
||||||
|
client
|
||||||
|
.restore_session(OAuthSession {
|
||||||
|
client_id: session.client_id,
|
||||||
|
user: session.user_session,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(MatrixClientError::RestoreSession)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let client = Self {
|
||||||
email: email.clone(),
|
email: email.clone(),
|
||||||
client,
|
client,
|
||||||
})
|
};
|
||||||
|
|
||||||
|
// Automatically save session when token gets refreshed
|
||||||
|
client.setup_background_session_save().await;
|
||||||
|
|
||||||
|
Ok(client)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Destroy this Matrix client instance
|
/// Destroy Matrix client related data
|
||||||
pub fn destroy(&self) -> anyhow::Result<()> {
|
fn destroy_data(email: &UserEmail) -> anyhow::Result<(), Box<MatrixClientError>> {
|
||||||
let db_path = AppConfig::get().user_matrix_db_path(&self.email);
|
let session_path = AppConfig::get().user_matrix_session_file_path(email);
|
||||||
if db_path.is_file() {
|
if session_path.is_file() {
|
||||||
|
std::fs::remove_file(&session_path)
|
||||||
|
.map_err(MatrixClientError::ClearMatrixSessionFile)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let db_path = AppConfig::get().user_matrix_db_path(email);
|
||||||
|
if db_path.is_dir() {
|
||||||
std::fs::remove_dir_all(&db_path).map_err(MatrixClientError::ClearMatrixDbDir)?;
|
std::fs::remove_dir_all(&db_path).map_err(MatrixClientError::ClearMatrixDbDir)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let passphrase_path = AppConfig::get().user_matrix_passphrase_path(&self.email);
|
let passphrase_path = AppConfig::get().user_matrix_passphrase_path(email);
|
||||||
if passphrase_path.is_file() {
|
if passphrase_path.is_file() {
|
||||||
std::fs::remove_file(passphrase_path).map_err(MatrixClientError::ClearDbPassphrase)?;
|
std::fs::remove_file(passphrase_path).map_err(MatrixClientError::ClearDbPassphrase)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
todo!()
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initiate oauth authentication
|
/// Initiate OAuth authentication
|
||||||
pub async fn initiate_login(&self) -> anyhow::Result<Url> {
|
pub async fn initiate_login(&self) -> anyhow::Result<Url> {
|
||||||
let oauth = self.client.oauth();
|
let oauth = self.client.oauth();
|
||||||
|
|
||||||
@@ -112,4 +181,77 @@ impl MatrixClient {
|
|||||||
|
|
||||||
Ok(auth.url)
|
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()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Persist session tokens
|
||||||
|
self.save_stored_session().await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Automatically persist session onto disk
|
||||||
|
pub async fn setup_background_session_save(&self) {
|
||||||
|
let this = self.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
while let Ok(update) = this.client.subscribe_to_session_changes().recv().await {
|
||||||
|
match update {
|
||||||
|
matrix_sdk::SessionChange::UnknownToken { soft_logout } => {
|
||||||
|
log::warn!("Received an unknown token error; soft logout? {soft_logout:?}");
|
||||||
|
}
|
||||||
|
matrix_sdk::SessionChange::TokensRefreshed => {
|
||||||
|
// The tokens have been refreshed, persist them to disk.
|
||||||
|
if let Err(err) = this.save_stored_session().await {
|
||||||
|
log::error!("Unable to store a session in the background: {err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the session stored on the filesystem.
|
||||||
|
async fn save_stored_session(&self) -> anyhow::Result<()> {
|
||||||
|
log::debug!("Save the stored session for {:?}...", self.email);
|
||||||
|
|
||||||
|
let user_session: UserSession = self
|
||||||
|
.client
|
||||||
|
.oauth()
|
||||||
|
.user_session()
|
||||||
|
.context("A logged in client must have a session")?;
|
||||||
|
|
||||||
|
let stored_session = StoredSession {
|
||||||
|
user_session,
|
||||||
|
client_id: self
|
||||||
|
.client
|
||||||
|
.oauth()
|
||||||
|
.client_id()
|
||||||
|
.context("Client ID should be set at this point!")?
|
||||||
|
.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let serialized_session = serde_json::to_string(&stored_session)?;
|
||||||
|
std::fs::write(
|
||||||
|
AppConfig::get().user_matrix_session_file_path(&self.email),
|
||||||
|
serialized_session,
|
||||||
|
)
|
||||||
|
.map_err(MatrixClientError::WriteSessionFile)?;
|
||||||
|
|
||||||
|
log::debug!("Updating the stored session: done!");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -172,13 +172,5 @@ pub struct ExtendedUserInfo {
|
|||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub user: User,
|
pub user: User,
|
||||||
pub matrix_user_id: Option<String>,
|
pub matrix_user_id: Option<String>,
|
||||||
}
|
pub matrix_device_id: Option<String>,
|
||||||
|
|
||||||
impl ExtendedUserInfo {
|
|
||||||
pub async fn from_user(user: User) -> anyhow::Result<Self> {
|
|
||||||
Ok(Self {
|
|
||||||
user,
|
|
||||||
matrix_user_id: None, // TODO
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { MatrixLinkRoute } from "./routes/MatrixLinkRoute";
|
|||||||
import { NotFoundRoute } from "./routes/NotFoundRoute";
|
import { NotFoundRoute } from "./routes/NotFoundRoute";
|
||||||
import { BaseLoginPage } from "./widgets/auth/BaseLoginPage";
|
import { BaseLoginPage } from "./widgets/auth/BaseLoginPage";
|
||||||
import BaseAuthenticatedPage from "./widgets/dashboard/BaseAuthenticatedPage";
|
import BaseAuthenticatedPage from "./widgets/dashboard/BaseAuthenticatedPage";
|
||||||
|
import { MatrixAuthCallback } from "./routes/MatrixAuthCallback";
|
||||||
|
|
||||||
interface AuthContext {
|
interface AuthContext {
|
||||||
signedIn: boolean;
|
signedIn: boolean;
|
||||||
@@ -39,6 +40,7 @@ export function App(): React.ReactElement {
|
|||||||
<Route path="*" element={<BaseAuthenticatedPage />}>
|
<Route path="*" element={<BaseAuthenticatedPage />}>
|
||||||
<Route path="" element={<HomeRoute />} />
|
<Route path="" element={<HomeRoute />} />
|
||||||
<Route path="matrix_link" element={<MatrixLinkRoute />} />
|
<Route path="matrix_link" element={<MatrixLinkRoute />} />
|
||||||
|
<Route path="matrix_auth_cb" element={<MatrixAuthCallback />} />
|
||||||
<Route path="*" element={<NotFoundRoute />} />
|
<Route path="*" element={<NotFoundRoute />} />
|
||||||
</Route>
|
</Route>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export interface UserInfo {
|
|||||||
name: string;
|
name: string;
|
||||||
email: string;
|
email: string;
|
||||||
matrix_user_id?: string;
|
matrix_user_id?: string;
|
||||||
|
matrix_device_id?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TokenStateKey = "auth-state";
|
const TokenStateKey = "auth-state";
|
||||||
|
|||||||
@@ -12,4 +12,15 @@ export class MatrixLinkApi {
|
|||||||
})
|
})
|
||||||
).data;
|
).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 },
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
81
matrixgw_frontend/src/routes/MatrixAuthCallback.tsx
Normal file
81
matrixgw_frontend/src/routes/MatrixAuthCallback.tsx
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
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!);
|
||||||
|
|
||||||
|
snackbar("Successfully linked to Matrix account!");
|
||||||
|
navigate("/matrix_link");
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
setError(String(e));
|
||||||
|
} finally {
|
||||||
|
info.reloadUserInfo();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
load();
|
||||||
|
}, [code, state]);
|
||||||
|
|
||||||
|
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>
|
<Typography variant="body1" gutterBottom>
|
||||||
<p>
|
<p>
|
||||||
MatrixGW is currently connected to your account with ID{" "}
|
MatrixGW is currently connected to your account with the following
|
||||||
<i>{user.info.matrix_user_id}</i>.
|
information:
|
||||||
</p>
|
</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>
|
<p>
|
||||||
If you encounter issues with your Matrix account you can try to
|
If you encounter issues with your Matrix account you can try to
|
||||||
disconnect and connect back again.
|
disconnect and connect back again.
|
||||||
|
|||||||
Reference in New Issue
Block a user