Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions crates/data-model/src/compat/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use serde::Serialize;
use ulid::Ulid;

use super::Device;
use crate::{InvalidTransitionError, UserAgent};
use crate::InvalidTransitionError;

#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
pub enum CompatSessionState {
Expand Down Expand Up @@ -76,7 +76,7 @@ pub struct CompatSession {
pub user_session_id: Option<Ulid>,
pub created_at: DateTime<Utc>,
pub is_synapse_admin: bool,
pub user_agent: Option<UserAgent>,
pub user_agent: Option<String>,
pub last_active_at: Option<DateTime<Utc>>,
pub last_active_ip: Option<IpAddr>,
}
Expand Down
4 changes: 2 additions & 2 deletions crates/data-model/src/oauth2/device_code_grant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use oauth2_types::scope::Scope;
use serde::Serialize;
use ulid::Ulid;

use crate::{BrowserSession, InvalidTransitionError, Session, UserAgent};
use crate::{BrowserSession, InvalidTransitionError, Session};

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case", tag = "state")]
Expand Down Expand Up @@ -192,7 +192,7 @@ pub struct DeviceCodeGrant {
pub ip_address: Option<IpAddr>,

/// The user agent used to request this device code grant.
pub user_agent: Option<UserAgent>,
pub user_agent: Option<String>,
}

impl std::ops::Deref for DeviceCodeGrant {
Expand Down
4 changes: 2 additions & 2 deletions crates/data-model/src/oauth2/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use oauth2_types::scope::Scope;
use serde::Serialize;
use ulid::Ulid;

use crate::{InvalidTransitionError, UserAgent};
use crate::InvalidTransitionError;

#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
pub enum SessionState {
Expand Down Expand Up @@ -80,7 +80,7 @@ pub struct Session {
pub user_session_id: Option<Ulid>,
pub client_id: Ulid,
pub scope: Scope,
pub user_agent: Option<UserAgent>,
pub user_agent: Option<String>,
pub last_active_at: Option<DateTime<Utc>>,
pub last_active_ip: Option<IpAddr>,
}
Expand Down
17 changes: 11 additions & 6 deletions crates/data-model/src/user_agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,18 @@
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.

use std::sync::LazyLock;

use serde::Serialize;
use woothee::{parser::Parser, woothee::VALUE_UNKNOWN};

static CUSTOM_USER_AGENT_REGEX: LazyLock<regex::Regex> = LazyLock::new(|| {
regex::Regex::new(r"^(?P<name>[^/]+)/(?P<version>[^ ]+) \((?P<segments>.+)\)$").unwrap()
});

static ELECTRON_USER_AGENT_REGEX: LazyLock<regex::Regex> =
LazyLock::new(|| regex::Regex::new(r"(?m)\w+/[\w.]+").unwrap());

#[derive(Debug, Serialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum DeviceType {
Expand Down Expand Up @@ -37,10 +46,7 @@ impl std::ops::Deref for UserAgent {

impl UserAgent {
fn parse_custom(user_agent: &str) -> Option<(&str, &str, &str, &str, Option<&str>)> {
let regex = regex::Regex::new(r"^(?P<name>[^/]+)/(?P<version>[^ ]+) \((?P<segments>.+)\)$")
.unwrap();

let captures = regex.captures(user_agent)?;
let captures = CUSTOM_USER_AGENT_REGEX.captures(user_agent)?;
let name = captures.name("name")?.as_str();
let version = captures.name("version")?.as_str();
let segments: Vec<&str> = captures
Expand Down Expand Up @@ -73,9 +79,8 @@ impl UserAgent {
}

fn parse_electron(user_agent: &str) -> Option<(&str, &str)> {
let regex = regex::Regex::new(r"(?m)\w+/[\w.]+").unwrap();
let omit_keys = ["Mozilla", "AppleWebKit", "Chrome", "Electron", "Safari"];
return regex
return ELECTRON_USER_AGENT_REGEX
.find_iter(user_agent)
.map(|caps| caps.as_str().split_once('/').unwrap())
.find(|pair| !omit_keys.contains(&pair.0));
Expand Down
12 changes: 5 additions & 7 deletions crates/data-model/src/users.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ use serde::Serialize;
use ulid::Ulid;
use url::Url;

use crate::UserAgent;

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct User {
pub id: Ulid,
Expand Down Expand Up @@ -81,7 +79,7 @@ pub enum AuthenticationMethod {
pub struct UserRecoverySession {
pub id: Ulid,
pub email: String,
pub user_agent: UserAgent,
pub user_agent: String,
pub ip_address: Option<IpAddr>,
pub locale: String,
pub created_at: DateTime<Utc>,
Expand Down Expand Up @@ -137,7 +135,7 @@ pub struct BrowserSession {
pub user: User,
pub created_at: DateTime<Utc>,
pub finished_at: Option<DateTime<Utc>>,
pub user_agent: Option<UserAgent>,
pub user_agent: Option<String>,
pub last_active_at: Option<DateTime<Utc>>,
pub last_active_ip: Option<IpAddr>,
}
Expand All @@ -159,9 +157,9 @@ impl BrowserSession {
user,
created_at: now,
finished_at: None,
user_agent: Some(UserAgent::parse(
user_agent: Some(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.0.0 Safari/537.36".to_owned()
)),
),
last_active_at: Some(now),
last_active_ip: None,
})
Expand Down Expand Up @@ -213,7 +211,7 @@ pub struct UserRegistration {
pub password: Option<UserRegistrationPassword>,
pub post_auth_action: Option<serde_json::Value>,
pub ip_address: Option<IpAddr>,
pub user_agent: Option<UserAgent>,
pub user_agent: Option<String>,
pub created_at: DateTime<Utc>,
pub completed_at: Option<DateTime<Utc>>,
}
6 changes: 3 additions & 3 deletions crates/handlers/src/admin/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ impl
user_session_id: session.user_session_id,
redirect_uri: sso_login.map(|sso| sso.redirect_uri),
created_at: session.created_at,
user_agent: session.user_agent.map(|ua| ua.raw),
user_agent: session.user_agent,
last_active_at: session.last_active_at,
last_active_ip: session.last_active_ip,
finished_at,
Expand Down Expand Up @@ -313,7 +313,7 @@ impl From<mas_data_model::Session> for OAuth2Session {
user_session_id: session.user_session_id,
client_id: session.client_id,
scope: session.scope.to_string(),
user_agent: session.user_agent.map(|ua| ua.raw),
user_agent: session.user_agent,
last_active_at: session.last_active_at,
last_active_ip: session.last_active_ip,
}
Expand Down Expand Up @@ -406,7 +406,7 @@ impl From<mas_data_model::BrowserSession> for UserSession {
created_at: value.created_at,
finished_at: value.finished_at,
user_id: value.user.id,
user_agent: value.user_agent.map(|ua| ua.raw),
user_agent: value.user_agent,
last_active_at: value.last_active_at,
last_active_ip: value.last_active_ip,
}
Expand Down
6 changes: 2 additions & 4 deletions crates/handlers/src/compat/login.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ use axum_extra::typed_header::TypedHeader;
use chrono::Duration;
use hyper::StatusCode;
use mas_axum_utils::record_error;
use mas_data_model::{
CompatSession, CompatSsoLoginState, Device, SiteConfig, TokenType, User, UserAgent,
};
use mas_data_model::{CompatSession, CompatSsoLoginState, Device, SiteConfig, TokenType, User};
use mas_matrix::HomeserverConnection;
use mas_storage::{
BoxClock, BoxRepository, BoxRng, Clock, RepositoryAccess,
Expand Down Expand Up @@ -276,7 +274,7 @@ pub(crate) async fn post(
user_agent: Option<TypedHeader<headers::UserAgent>>,
MatrixJsonBody(input): MatrixJsonBody<RequestBody>,
) -> Result<impl IntoResponse, RouteError> {
let user_agent = user_agent.map(|ua| UserAgent::parse(ua.as_str().to_owned()));
let user_agent = user_agent.map(|ua| ua.as_str().to_owned());
let login_type = input.credentials.login_type();
let (mut session, user) = match (password_manager.is_enabled(), input.credentials) {
(
Expand Down
6 changes: 5 additions & 1 deletion crates/handlers/src/graphql/model/browser_sessions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,11 @@ impl BrowserSession {

/// The user-agent with which the session was created.
pub async fn user_agent(&self) -> Option<UserAgent> {
self.0.user_agent.clone().map(UserAgent::from)
self.0
.user_agent
.clone()
.map(mas_data_model::UserAgent::parse)
.map(UserAgent::from)
}

/// The last IP address used by the session.
Expand Down
6 changes: 5 additions & 1 deletion crates/handlers/src/graphql/model/compat_sessions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,11 @@ impl CompatSession {

/// The user-agent with which the session was created.
pub async fn user_agent(&self) -> Option<UserAgent> {
self.session.user_agent.clone().map(UserAgent::from)
self.session
.user_agent
.clone()
.map(mas_data_model::UserAgent::parse)
.map(UserAgent::from)
}

/// The associated SSO login, if any.
Expand Down
6 changes: 5 additions & 1 deletion crates/handlers/src/graphql/model/oauth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,11 @@ impl OAuth2Session {

/// The user-agent with which the session was created.
pub async fn user_agent(&self) -> Option<UserAgent> {
self.0.user_agent.clone().map(UserAgent::from)
self.0
.user_agent
.clone()
.map(mas_data_model::UserAgent::parse)
.map(UserAgent::from)
}

/// The state of the session.
Expand Down
3 changes: 1 addition & 2 deletions crates/handlers/src/oauth2/device/authorize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ use mas_axum_utils::{
client_authorization::{ClientAuthorization, CredentialsVerificationError},
record_error,
};
use mas_data_model::UserAgent;
use mas_keystore::Encrypter;
use mas_router::UrlBuilder;
use mas_storage::{BoxClock, BoxRepository, BoxRng, oauth2::OAuth2DeviceCodeGrantParams};
Expand Down Expand Up @@ -137,7 +136,7 @@ pub(crate) async fn post(

let expires_in = Duration::microseconds(20 * 60 * 1000 * 1000);

let user_agent = user_agent.map(|ua| UserAgent::parse(ua.as_str().to_owned()));
let user_agent = user_agent.map(|ua| ua.as_str().to_owned());
let ip_address = activity_tracker.ip();

let device_code = Alphanumeric.sample_string(&mut rng, 32);
Expand Down
14 changes: 7 additions & 7 deletions crates/handlers/src/oauth2/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use mas_axum_utils::{
record_error,
};
use mas_data_model::{
AuthorizationGrantStage, Client, Device, DeviceCodeGrantState, SiteConfig, TokenType, UserAgent,
AuthorizationGrantStage, Client, Device, DeviceCodeGrantState, SiteConfig, TokenType,
};
use mas_keystore::{Encrypter, Keystore};
use mas_matrix::HomeserverConnection;
Expand Down Expand Up @@ -285,7 +285,7 @@ pub(crate) async fn post(
user_agent: Option<TypedHeader<headers::UserAgent>>,
client_authorization: ClientAuthorization<AccessTokenRequest>,
) -> Result<impl IntoResponse, RouteError> {
let user_agent = user_agent.map(|ua| UserAgent::parse(ua.as_str().to_owned()));
let user_agent = user_agent.map(|ua| ua.as_str().to_owned());
let client = client_authorization
.credentials
.fetch(&mut repo)
Expand Down Expand Up @@ -415,7 +415,7 @@ async fn authorization_code_grant(
site_config: &SiteConfig,
mut repo: BoxRepository,
homeserver: &Arc<dyn HomeserverConnection>,
user_agent: Option<UserAgent>,
user_agent: Option<String>,
) -> Result<(AccessTokenResponse, BoxRepository), RouteError> {
// Check that the client is allowed to use this grant type
if !client.grant_types.contains(&GrantType::AuthorizationCode) {
Expand Down Expand Up @@ -596,7 +596,7 @@ async fn refresh_token_grant(
client: &Client,
site_config: &SiteConfig,
mut repo: BoxRepository,
user_agent: Option<UserAgent>,
user_agent: Option<String>,
) -> Result<(AccessTokenResponse, BoxRepository), RouteError> {
// Check that the client is allowed to use this grant type
if !client.grant_types.contains(&GrantType::RefreshToken) {
Expand Down Expand Up @@ -749,7 +749,7 @@ async fn client_credentials_grant(
site_config: &SiteConfig,
mut repo: BoxRepository,
mut policy: Policy,
user_agent: Option<UserAgent>,
user_agent: Option<String>,
) -> Result<(AccessTokenResponse, BoxRepository), RouteError> {
// Check that the client is allowed to use this grant type
if !client.grant_types.contains(&GrantType::ClientCredentials) {
Expand All @@ -771,7 +771,7 @@ async fn client_credentials_grant(
grant_type: mas_policy::GrantType::ClientCredentials,
requester: mas_policy::Requester {
ip_address: activity_tracker.ip(),
user_agent: user_agent.clone().map(|ua| ua.raw),
user_agent: user_agent.clone(),
},
})
.await?;
Expand Down Expand Up @@ -828,7 +828,7 @@ async fn device_code_grant(
site_config: &SiteConfig,
mut repo: BoxRepository,
homeserver: &Arc<dyn HomeserverConnection>,
user_agent: Option<UserAgent>,
user_agent: Option<String>,
) -> Result<(AccessTokenResponse, BoxRepository), RouteError> {
// Check that the client is allowed to use this grant type
if !client.grant_types.contains(&GrantType::DeviceCode) {
Expand Down
9 changes: 4 additions & 5 deletions crates/handlers/src/upstream_oauth2/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ use mas_axum_utils::{
csrf::{CsrfExt, ProtectedForm},
record_error,
};
use mas_data_model::UserAgent;
use mas_jose::jwt::Jwt;
use mas_matrix::HomeserverConnection;
use mas_policy::Policy;
Expand Down Expand Up @@ -233,7 +232,7 @@ pub(crate) async fn get(
user_agent: Option<TypedHeader<headers::UserAgent>>,
Path(link_id): Path<Ulid>,
) -> Result<impl IntoResponse, RouteError> {
let user_agent = user_agent.map(|ua| UserAgent::parse(ua.as_str().to_owned()));
let user_agent = user_agent.map(|ua| ua.as_str().to_owned());
let sessions_cookie = UpstreamSessionsCookie::load(&cookie_jar);
let (session_id, post_auth_action) = sessions_cookie
.lookup_link(link_id)
Expand Down Expand Up @@ -502,7 +501,7 @@ pub(crate) async fn get(
email: None,
requester: mas_policy::Requester {
ip_address: activity_tracker.ip(),
user_agent: user_agent.clone().map(|ua| ua.raw),
user_agent: user_agent.clone(),
},
})
.await?;
Expand Down Expand Up @@ -568,7 +567,7 @@ pub(crate) async fn post(
Path(link_id): Path<Ulid>,
Form(form): Form<ProtectedForm<FormData>>,
) -> Result<Response, RouteError> {
let user_agent = user_agent.map(|ua| UserAgent::parse(ua.as_str().to_owned()));
let user_agent = user_agent.map(|ua| ua.as_str().to_owned());
let form = cookie_jar.verify_form(&clock, form)?;

let sessions_cookie = UpstreamSessionsCookie::load(&cookie_jar);
Expand Down Expand Up @@ -786,7 +785,7 @@ pub(crate) async fn post(
email: email.as_deref(),
requester: mas_policy::Requester {
ip_address: activity_tracker.ip(),
user_agent: user_agent.clone().map(|ua| ua.raw),
user_agent: user_agent.clone(),
},
})
.await?;
Expand Down
4 changes: 2 additions & 2 deletions crates/handlers/src/views/login.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use mas_axum_utils::{
cookies::CookieJar,
csrf::{CsrfExt, ProtectedForm},
};
use mas_data_model::{UserAgent, oauth2::LoginHint};
use mas_data_model::oauth2::LoginHint;
use mas_i18n::DataLocale;
use mas_matrix::HomeserverConnection;
use mas_router::{UpstreamOAuth2Authorize, UrlBuilder};
Expand Down Expand Up @@ -146,7 +146,7 @@ pub(crate) async fn post(
user_agent: Option<TypedHeader<headers::UserAgent>>,
Form(form): Form<ProtectedForm<LoginForm>>,
) -> Result<Response, FancyError> {
let user_agent = user_agent.map(|ua| UserAgent::parse(ua.as_str().to_owned()));
let user_agent = user_agent.map(|ua| ua.as_str().to_owned());
if !site_config.password_login_enabled {
// XXX: is it necessary to have better errors here?
return Ok(StatusCode::METHOD_NOT_ALLOWED.into_response());
Expand Down
4 changes: 2 additions & 2 deletions crates/handlers/src/views/recovery/start.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use mas_axum_utils::{
cookies::CookieJar,
csrf::{CsrfExt, ProtectedForm},
};
use mas_data_model::{SiteConfig, UserAgent};
use mas_data_model::SiteConfig;
use mas_router::UrlBuilder;
use mas_storage::{
BoxClock, BoxRepository, BoxRng,
Expand Down Expand Up @@ -102,7 +102,7 @@ pub(crate) async fn post(
return Ok((cookie_jar, url_builder.redirect(&mas_router::Index)).into_response());
}

let user_agent = UserAgent::parse(user_agent.as_str().to_owned());
let user_agent = user_agent.as_str().to_owned();
let ip_address = activity_tracker.ip();

let form = cookie_jar.verify_form(&clock, form)?;
Expand Down
Loading
Loading