diff --git a/crates/data-model/src/compat/session.rs b/crates/data-model/src/compat/session.rs index fc660c3f0..91b48cea0 100644 --- a/crates/data-model/src/compat/session.rs +++ b/crates/data-model/src/compat/session.rs @@ -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 { @@ -76,7 +76,7 @@ pub struct CompatSession { pub user_session_id: Option, pub created_at: DateTime, pub is_synapse_admin: bool, - pub user_agent: Option, + pub user_agent: Option, pub last_active_at: Option>, pub last_active_ip: Option, } diff --git a/crates/data-model/src/oauth2/device_code_grant.rs b/crates/data-model/src/oauth2/device_code_grant.rs index bf230b850..794cc460b 100644 --- a/crates/data-model/src/oauth2/device_code_grant.rs +++ b/crates/data-model/src/oauth2/device_code_grant.rs @@ -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")] @@ -192,7 +192,7 @@ pub struct DeviceCodeGrant { pub ip_address: Option, /// The user agent used to request this device code grant. - pub user_agent: Option, + pub user_agent: Option, } impl std::ops::Deref for DeviceCodeGrant { diff --git a/crates/data-model/src/oauth2/session.rs b/crates/data-model/src/oauth2/session.rs index 675701619..3024aa082 100644 --- a/crates/data-model/src/oauth2/session.rs +++ b/crates/data-model/src/oauth2/session.rs @@ -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 { @@ -80,7 +80,7 @@ pub struct Session { pub user_session_id: Option, pub client_id: Ulid, pub scope: Scope, - pub user_agent: Option, + pub user_agent: Option, pub last_active_at: Option>, pub last_active_ip: Option, } diff --git a/crates/data-model/src/user_agent.rs b/crates/data-model/src/user_agent.rs index 2b02ebd48..2ac4b06bd 100644 --- a/crates/data-model/src/user_agent.rs +++ b/crates/data-model/src/user_agent.rs @@ -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 = LazyLock::new(|| { + regex::Regex::new(r"^(?P[^/]+)/(?P[^ ]+) \((?P.+)\)$").unwrap() +}); + +static ELECTRON_USER_AGENT_REGEX: LazyLock = + LazyLock::new(|| regex::Regex::new(r"(?m)\w+/[\w.]+").unwrap()); + #[derive(Debug, Serialize, Clone, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum DeviceType { @@ -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[^/]+)/(?P[^ ]+) \((?P.+)\)$") - .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 @@ -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)); diff --git a/crates/data-model/src/users.rs b/crates/data-model/src/users.rs index 41b6c4f70..7e40f4df2 100644 --- a/crates/data-model/src/users.rs +++ b/crates/data-model/src/users.rs @@ -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, @@ -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, pub locale: String, pub created_at: DateTime, @@ -137,7 +135,7 @@ pub struct BrowserSession { pub user: User, pub created_at: DateTime, pub finished_at: Option>, - pub user_agent: Option, + pub user_agent: Option, pub last_active_at: Option>, pub last_active_ip: Option, } @@ -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, }) @@ -213,7 +211,7 @@ pub struct UserRegistration { pub password: Option, pub post_auth_action: Option, pub ip_address: Option, - pub user_agent: Option, + pub user_agent: Option, pub created_at: DateTime, pub completed_at: Option>, } diff --git a/crates/handlers/src/admin/model.rs b/crates/handlers/src/admin/model.rs index c3e81c627..ab3a9d8a6 100644 --- a/crates/handlers/src/admin/model.rs +++ b/crates/handlers/src/admin/model.rs @@ -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, @@ -313,7 +313,7 @@ impl From 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, } @@ -406,7 +406,7 @@ impl From 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, } diff --git a/crates/handlers/src/compat/login.rs b/crates/handlers/src/compat/login.rs index d13b50d3c..78765b2cf 100644 --- a/crates/handlers/src/compat/login.rs +++ b/crates/handlers/src/compat/login.rs @@ -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, @@ -276,7 +274,7 @@ pub(crate) async fn post( user_agent: Option>, MatrixJsonBody(input): MatrixJsonBody, ) -> Result { - 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) { ( diff --git a/crates/handlers/src/graphql/model/browser_sessions.rs b/crates/handlers/src/graphql/model/browser_sessions.rs index 15046ebb5..5e15644e2 100644 --- a/crates/handlers/src/graphql/model/browser_sessions.rs +++ b/crates/handlers/src/graphql/model/browser_sessions.rs @@ -81,7 +81,11 @@ impl BrowserSession { /// The user-agent with which the session was created. pub async fn user_agent(&self) -> Option { - 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. diff --git a/crates/handlers/src/graphql/model/compat_sessions.rs b/crates/handlers/src/graphql/model/compat_sessions.rs index 1ac53a558..77ed7e6cc 100644 --- a/crates/handlers/src/graphql/model/compat_sessions.rs +++ b/crates/handlers/src/graphql/model/compat_sessions.rs @@ -98,7 +98,11 @@ impl CompatSession { /// The user-agent with which the session was created. pub async fn user_agent(&self) -> Option { - 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. diff --git a/crates/handlers/src/graphql/model/oauth.rs b/crates/handlers/src/graphql/model/oauth.rs index fec318eb8..9c8dc5f1a 100644 --- a/crates/handlers/src/graphql/model/oauth.rs +++ b/crates/handlers/src/graphql/model/oauth.rs @@ -61,7 +61,11 @@ impl OAuth2Session { /// The user-agent with which the session was created. pub async fn user_agent(&self) -> Option { - 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. diff --git a/crates/handlers/src/oauth2/device/authorize.rs b/crates/handlers/src/oauth2/device/authorize.rs index dcf0316d4..1feec8c3e 100644 --- a/crates/handlers/src/oauth2/device/authorize.rs +++ b/crates/handlers/src/oauth2/device/authorize.rs @@ -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}; @@ -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); diff --git a/crates/handlers/src/oauth2/token.rs b/crates/handlers/src/oauth2/token.rs index d66d7d2f4..a47207d9b 100644 --- a/crates/handlers/src/oauth2/token.rs +++ b/crates/handlers/src/oauth2/token.rs @@ -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; @@ -285,7 +285,7 @@ pub(crate) async fn post( user_agent: Option>, client_authorization: ClientAuthorization, ) -> Result { - 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) @@ -415,7 +415,7 @@ async fn authorization_code_grant( site_config: &SiteConfig, mut repo: BoxRepository, homeserver: &Arc, - user_agent: Option, + user_agent: Option, ) -> Result<(AccessTokenResponse, BoxRepository), RouteError> { // Check that the client is allowed to use this grant type if !client.grant_types.contains(&GrantType::AuthorizationCode) { @@ -596,7 +596,7 @@ async fn refresh_token_grant( client: &Client, site_config: &SiteConfig, mut repo: BoxRepository, - user_agent: Option, + user_agent: Option, ) -> Result<(AccessTokenResponse, BoxRepository), RouteError> { // Check that the client is allowed to use this grant type if !client.grant_types.contains(&GrantType::RefreshToken) { @@ -749,7 +749,7 @@ async fn client_credentials_grant( site_config: &SiteConfig, mut repo: BoxRepository, mut policy: Policy, - user_agent: Option, + user_agent: Option, ) -> Result<(AccessTokenResponse, BoxRepository), RouteError> { // Check that the client is allowed to use this grant type if !client.grant_types.contains(&GrantType::ClientCredentials) { @@ -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?; @@ -828,7 +828,7 @@ async fn device_code_grant( site_config: &SiteConfig, mut repo: BoxRepository, homeserver: &Arc, - user_agent: Option, + user_agent: Option, ) -> Result<(AccessTokenResponse, BoxRepository), RouteError> { // Check that the client is allowed to use this grant type if !client.grant_types.contains(&GrantType::DeviceCode) { diff --git a/crates/handlers/src/upstream_oauth2/link.rs b/crates/handlers/src/upstream_oauth2/link.rs index b80f3956a..730576b61 100644 --- a/crates/handlers/src/upstream_oauth2/link.rs +++ b/crates/handlers/src/upstream_oauth2/link.rs @@ -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; @@ -233,7 +232,7 @@ pub(crate) async fn get( user_agent: Option>, Path(link_id): Path, ) -> Result { - 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) @@ -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?; @@ -568,7 +567,7 @@ pub(crate) async fn post( Path(link_id): Path, Form(form): Form>, ) -> Result { - 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); @@ -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?; diff --git a/crates/handlers/src/views/login.rs b/crates/handlers/src/views/login.rs index d8798fcae..e22a077d0 100644 --- a/crates/handlers/src/views/login.rs +++ b/crates/handlers/src/views/login.rs @@ -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}; @@ -146,7 +146,7 @@ pub(crate) async fn post( user_agent: Option>, Form(form): Form>, ) -> Result { - 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()); diff --git a/crates/handlers/src/views/recovery/start.rs b/crates/handlers/src/views/recovery/start.rs index 728e71834..ad87bdd17 100644 --- a/crates/handlers/src/views/recovery/start.rs +++ b/crates/handlers/src/views/recovery/start.rs @@ -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, @@ -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)?; diff --git a/crates/handlers/src/views/register/password.rs b/crates/handlers/src/views/register/password.rs index 4aa0d76dd..92058970a 100644 --- a/crates/handlers/src/views/register/password.rs +++ b/crates/handlers/src/views/register/password.rs @@ -18,7 +18,7 @@ use mas_axum_utils::{ cookies::CookieJar, csrf::{CsrfExt, CsrfToken, ProtectedForm}, }; -use mas_data_model::{CaptchaConfig, UserAgent}; +use mas_data_model::CaptchaConfig; use mas_i18n::DataLocale; use mas_matrix::HomeserverConnection; use mas_policy::Policy; @@ -141,7 +141,7 @@ pub(crate) async fn post( cookie_jar: CookieJar, Form(form): Form>, ) -> Result { - 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(); if !site_config.password_registration_enabled { @@ -239,7 +239,7 @@ pub(crate) async fn post( email: Some(&form.email), requester: mas_policy::Requester { ip_address: activity_tracker.ip(), - user_agent: user_agent.clone().map(|ua| ua.raw), + user_agent: user_agent.clone(), }, }) .await?; diff --git a/crates/handlers/src/views/register/steps/finish.rs b/crates/handlers/src/views/register/steps/finish.rs index c0c0df404..afc05b65b 100644 --- a/crates/handlers/src/views/register/steps/finish.rs +++ b/crates/handlers/src/views/register/steps/finish.rs @@ -13,7 +13,6 @@ use axum::{ use axum_extra::TypedHeader; use chrono::Duration; use mas_axum_utils::{FancyError, SessionInfoExt as _, cookies::CookieJar}; -use mas_data_model::UserAgent; use mas_matrix::HomeserverConnection; use mas_router::{PostAuthAction, UrlBuilder}; use mas_storage::{ @@ -56,7 +55,7 @@ pub(crate) async fn get( cookie_jar: CookieJar, Path(id): Path, ) -> Result { - 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 registration = repo .user_registration() .lookup(id) diff --git a/crates/storage-pg/src/app_session.rs b/crates/storage-pg/src/app_session.rs index d54c604b3..2c747c8ca 100644 --- a/crates/storage-pg/src/app_session.rs +++ b/crates/storage-pg/src/app_session.rs @@ -7,9 +7,7 @@ //! A module containing PostgreSQL implementation of repositories for sessions use async_trait::async_trait; -use mas_data_model::{ - CompatSession, CompatSessionState, Device, Session, SessionState, User, UserAgent, -}; +use mas_data_model::{CompatSession, CompatSessionState, Device, Session, SessionState, User}; use mas_storage::{ Clock, Page, Pagination, app_session::{AppSession, AppSessionFilter, AppSessionRepository, AppSessionState}, @@ -106,7 +104,6 @@ impl TryFrom for AppSession { last_active_ip, } = value; - let user_agent = user_agent.map(UserAgent::parse); let user_session_id = user_session_id.map(Ulid::from); match ( diff --git a/crates/storage-pg/src/compat/mod.rs b/crates/storage-pg/src/compat/mod.rs index 1d0e40426..8ceb089b7 100644 --- a/crates/storage-pg/src/compat/mod.rs +++ b/crates/storage-pg/src/compat/mod.rs @@ -20,7 +20,7 @@ pub use self::{ #[cfg(test)] mod tests { use chrono::Duration; - use mas_data_model::{Device, UserAgent}; + use mas_data_model::Device; use mas_storage::{ Clock, Pagination, RepositoryAccess, clock::MockClock, @@ -125,7 +125,7 @@ mod tests { assert!(session_lookup.user_agent.is_none()); let session = repo .compat_session() - .record_user_agent(session_lookup, UserAgent::parse("Mozilla/5.0".to_owned())) + .record_user_agent(session_lookup, "Mozilla/5.0".to_owned()) .await .unwrap(); assert_eq!(session.user_agent.as_deref(), Some("Mozilla/5.0")); diff --git a/crates/storage-pg/src/compat/session.rs b/crates/storage-pg/src/compat/session.rs index 10c9fd9ad..c844be238 100644 --- a/crates/storage-pg/src/compat/session.rs +++ b/crates/storage-pg/src/compat/session.rs @@ -10,7 +10,7 @@ use async_trait::async_trait; use chrono::{DateTime, Utc}; use mas_data_model::{ BrowserSession, CompatSession, CompatSessionState, CompatSsoLogin, CompatSsoLoginState, Device, - User, UserAgent, + User, }; use mas_storage::{ Clock, Page, Pagination, @@ -77,7 +77,7 @@ impl From for CompatSession { human_name: value.human_name, created_at: value.created_at, is_synapse_admin: value.is_synapse_admin, - user_agent: value.user_agent.map(UserAgent::parse), + user_agent: value.user_agent, last_active_at: value.last_active_at, last_active_ip: value.last_active_ip, } @@ -126,7 +126,7 @@ impl TryFrom for (CompatSession, Option { async fn record_user_agent( &mut self, mut compat_session: CompatSession, - user_agent: UserAgent, + user_agent: String, ) -> Result { let res = sqlx::query!( r#" diff --git a/crates/storage-pg/src/oauth2/device_code_grant.rs b/crates/storage-pg/src/oauth2/device_code_grant.rs index 409ab3ff9..ebed4d859 100644 --- a/crates/storage-pg/src/oauth2/device_code_grant.rs +++ b/crates/storage-pg/src/oauth2/device_code_grant.rs @@ -8,7 +8,7 @@ use std::net::IpAddr; use async_trait::async_trait; use chrono::{DateTime, Utc}; -use mas_data_model::{BrowserSession, DeviceCodeGrant, DeviceCodeGrantState, Session, UserAgent}; +use mas_data_model::{BrowserSession, DeviceCodeGrant, DeviceCodeGrantState, Session}; use mas_storage::{ Clock, oauth2::{OAuth2DeviceCodeGrantParams, OAuth2DeviceCodeGrantRepository}, @@ -132,7 +132,7 @@ impl TryFrom for DeviceCodeGrant { created_at, expires_at, ip_address, - user_agent: user_agent.map(UserAgent::parse), + user_agent, }) } } diff --git a/crates/storage-pg/src/oauth2/mod.rs b/crates/storage-pg/src/oauth2/mod.rs index 5968e625d..d5e7f7694 100644 --- a/crates/storage-pg/src/oauth2/mod.rs +++ b/crates/storage-pg/src/oauth2/mod.rs @@ -24,7 +24,7 @@ pub use self::{ #[cfg(test)] mod tests { use chrono::Duration; - use mas_data_model::{AuthorizationCode, UserAgent}; + use mas_data_model::AuthorizationCode; use mas_storage::{ Clock, Pagination, clock::MockClock, @@ -351,7 +351,7 @@ mod tests { assert!(session.user_agent.is_none()); let session = repo .oauth2_session() - .record_user_agent(session, UserAgent::parse("Mozilla/5.0".to_owned())) + .record_user_agent(session, "Mozilla/5.0".to_owned()) .await .unwrap(); assert_eq!(session.user_agent.as_deref(), Some("Mozilla/5.0")); diff --git a/crates/storage-pg/src/oauth2/session.rs b/crates/storage-pg/src/oauth2/session.rs index 6b753e17d..b525e22a0 100644 --- a/crates/storage-pg/src/oauth2/session.rs +++ b/crates/storage-pg/src/oauth2/session.rs @@ -8,7 +8,7 @@ use std::net::IpAddr; use async_trait::async_trait; use chrono::{DateTime, Utc}; -use mas_data_model::{BrowserSession, Client, Session, SessionState, User, UserAgent}; +use mas_data_model::{BrowserSession, Client, Session, SessionState, User}; use mas_storage::{ Clock, Page, Pagination, oauth2::{OAuth2SessionFilter, OAuth2SessionRepository}, @@ -87,7 +87,7 @@ impl TryFrom for Session { user_id: value.user_id.map(Ulid::from), user_session_id: value.user_session_id.map(Ulid::from), scope, - user_agent: value.user_agent.map(UserAgent::parse), + user_agent: value.user_agent, last_active_at: value.last_active_at, last_active_ip: value.last_active_ip, }) @@ -490,14 +490,14 @@ impl OAuth2SessionRepository for PgOAuth2SessionRepository<'_> { %session.id, %session.scope, client.id = %session.client_id, - session.user_agent = %user_agent.raw, + session.user_agent = user_agent, ), err, )] async fn record_user_agent( &mut self, mut session: Session, - user_agent: UserAgent, + user_agent: String, ) -> Result { let res = sqlx::query!( r#" diff --git a/crates/storage-pg/src/user/recovery.rs b/crates/storage-pg/src/user/recovery.rs index d838b2531..bc108b52a 100644 --- a/crates/storage-pg/src/user/recovery.rs +++ b/crates/storage-pg/src/user/recovery.rs @@ -8,7 +8,7 @@ use std::net::IpAddr; use async_trait::async_trait; use chrono::{DateTime, Duration, Utc}; -use mas_data_model::{UserAgent, UserEmail, UserRecoverySession, UserRecoveryTicket}; +use mas_data_model::{UserEmail, UserRecoverySession, UserRecoveryTicket}; use mas_storage::{Clock, user::UserRecoveryRepository}; use rand::RngCore; use sqlx::PgConnection; @@ -45,7 +45,7 @@ impl From for UserRecoverySession { UserRecoverySession { id: row.user_recovery_session_id.into(), email: row.email, - user_agent: UserAgent::parse(row.user_agent), + user_agent: row.user_agent, ip_address: row.ip_address, locale: row.locale, created_at: row.created_at, @@ -127,7 +127,7 @@ impl UserRecoveryRepository for PgUserRecoveryRepository<'_> { db.query.text, user_recovery_session.id, user_recovery_session.email = email, - user_recovery_session.user_agent = &*user_agent, + user_recovery_session.user_agent = user_agent, user_recovery_session.ip_address = ip_address.map(|ip| ip.to_string()), ) )] @@ -136,7 +136,7 @@ impl UserRecoveryRepository for PgUserRecoveryRepository<'_> { rng: &mut (dyn RngCore + Send), clock: &dyn Clock, email: String, - user_agent: UserAgent, + user_agent: String, ip_address: Option, locale: String, ) -> Result { diff --git a/crates/storage-pg/src/user/registration.rs b/crates/storage-pg/src/user/registration.rs index 1aa2afe86..5d578ab79 100644 --- a/crates/storage-pg/src/user/registration.rs +++ b/crates/storage-pg/src/user/registration.rs @@ -7,9 +7,7 @@ use std::net::IpAddr; use async_trait::async_trait; use chrono::{DateTime, Utc}; -use mas_data_model::{ - UserAgent, UserEmailAuthentication, UserRegistration, UserRegistrationPassword, -}; +use mas_data_model::{UserEmailAuthentication, UserRegistration, UserRegistrationPassword}; use mas_storage::{Clock, user::UserRegistrationRepository}; use rand::RngCore; use sqlx::PgConnection; @@ -53,7 +51,6 @@ impl TryFrom for UserRegistration { fn try_from(value: UserRegistrationLookup) -> Result { let id = Ulid::from(value.user_registration_id); - let user_agent = value.user_agent.map(UserAgent::parse); let password = match (value.hashed_password, value.hashed_password_version) { (Some(hashed_password), Some(version)) => { @@ -91,7 +88,7 @@ impl TryFrom for UserRegistration { Ok(UserRegistration { id, ip_address: value.ip_address, - user_agent, + user_agent: value.user_agent, post_auth_action: value.post_auth_action, username: value.username, display_name: value.display_name, @@ -162,7 +159,7 @@ impl UserRegistrationRepository for PgUserRegistrationRepository<'_> { clock: &dyn Clock, username: String, ip_address: Option, - user_agent: Option, + user_agent: Option, post_auth_action: Option, ) -> Result { let created_at = clock.now(); @@ -394,7 +391,7 @@ impl UserRegistrationRepository for PgUserRegistrationRepository<'_> { mod tests { use std::net::{IpAddr, Ipv4Addr}; - use mas_data_model::{UserAgent, UserRegistrationPassword}; + use mas_data_model::UserRegistrationPassword; use mas_storage::{Clock, clock::MockClock}; use rand::SeedableRng; use rand_chacha::ChaChaRng; @@ -487,16 +484,13 @@ mod tests { &clock, "alice".to_owned(), Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))), - Some(UserAgent::parse("Mozilla/5.0".to_owned())), + Some("Mozilla/5.0".to_owned()), Some(serde_json::json!({"action": "continue_compat_sso_login", "id": "01FSHN9AG0MKGTBNZ16RDR3PVY"})), ) .await .unwrap(); - assert_eq!( - registration.user_agent, - Some(UserAgent::parse("Mozilla/5.0".to_owned())) - ); + assert_eq!(registration.user_agent, Some("Mozilla/5.0".to_owned())); assert_eq!( registration.ip_address, Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))) diff --git a/crates/storage-pg/src/user/session.rs b/crates/storage-pg/src/user/session.rs index ce027afc0..fa4c69f82 100644 --- a/crates/storage-pg/src/user/session.rs +++ b/crates/storage-pg/src/user/session.rs @@ -10,7 +10,7 @@ use async_trait::async_trait; use chrono::{DateTime, Utc}; use mas_data_model::{ Authentication, AuthenticationMethod, BrowserSession, Password, - UpstreamOAuthAuthorizationSession, User, UserAgent, + UpstreamOAuthAuthorizationSession, User, }; use mas_storage::{ Clock, Page, Pagination, @@ -83,7 +83,7 @@ impl TryFrom for BrowserSession { user, created_at: value.user_session_created_at, finished_at: value.user_session_finished_at, - user_agent: value.user_session_user_agent.map(UserAgent::parse), + user_agent: value.user_session_user_agent, last_active_at: value.user_session_last_active_at, last_active_ip: value.user_session_last_active_ip, }) @@ -208,7 +208,7 @@ impl BrowserSessionRepository for PgBrowserSessionRepository<'_> { rng: &mut (dyn RngCore + Send), clock: &dyn Clock, user: &User, - user_agent: Option, + user_agent: Option, ) -> Result { let created_at = clock.now(); let id = Ulid::from_datetime_with_source(created_at.into(), rng); diff --git a/crates/storage/src/compat/session.rs b/crates/storage/src/compat/session.rs index 81a417fa6..757f5269b 100644 --- a/crates/storage/src/compat/session.rs +++ b/crates/storage/src/compat/session.rs @@ -8,7 +8,7 @@ use std::net::IpAddr; use async_trait::async_trait; use chrono::{DateTime, Utc}; -use mas_data_model::{BrowserSession, CompatSession, CompatSsoLogin, Device, User, UserAgent}; +use mas_data_model::{BrowserSession, CompatSession, CompatSsoLogin, Device, User}; use rand_core::RngCore; use ulid::Ulid; @@ -322,7 +322,7 @@ pub trait CompatSessionRepository: Send + Sync { async fn record_user_agent( &mut self, compat_session: CompatSession, - user_agent: UserAgent, + user_agent: String, ) -> Result; } @@ -367,6 +367,6 @@ repository_impl!(CompatSessionRepository: async fn record_user_agent( &mut self, compat_session: CompatSession, - user_agent: UserAgent, + user_agent: String, ) -> Result; ); diff --git a/crates/storage/src/oauth2/device_code_grant.rs b/crates/storage/src/oauth2/device_code_grant.rs index 9a85a3a75..762e854cc 100644 --- a/crates/storage/src/oauth2/device_code_grant.rs +++ b/crates/storage/src/oauth2/device_code_grant.rs @@ -8,7 +8,7 @@ use std::net::IpAddr; use async_trait::async_trait; use chrono::Duration; -use mas_data_model::{BrowserSession, Client, DeviceCodeGrant, Session, UserAgent}; +use mas_data_model::{BrowserSession, Client, DeviceCodeGrant, Session}; use oauth2_types::scope::Scope; use rand_core::RngCore; use ulid::Ulid; @@ -36,7 +36,7 @@ pub struct OAuth2DeviceCodeGrantParams<'a> { pub ip_address: Option, /// The user agent from which the request was made - pub user_agent: Option, + pub user_agent: Option, } /// An [`OAuth2DeviceCodeGrantRepository`] helps interacting with diff --git a/crates/storage/src/oauth2/session.rs b/crates/storage/src/oauth2/session.rs index d53eaa85c..7dbba4a03 100644 --- a/crates/storage/src/oauth2/session.rs +++ b/crates/storage/src/oauth2/session.rs @@ -8,7 +8,7 @@ use std::net::IpAddr; use async_trait::async_trait; use chrono::{DateTime, Utc}; -use mas_data_model::{BrowserSession, Client, Device, Session, User, UserAgent}; +use mas_data_model::{BrowserSession, Client, Device, Session, User}; use oauth2_types::scope::Scope; use rand_core::RngCore; use ulid::Ulid; @@ -428,7 +428,7 @@ pub trait OAuth2SessionRepository: Send + Sync { async fn record_user_agent( &mut self, session: Session, - user_agent: UserAgent, + user_agent: String, ) -> Result; } @@ -487,6 +487,6 @@ repository_impl!(OAuth2SessionRepository: async fn record_user_agent( &mut self, session: Session, - user_agent: UserAgent, + user_agent: String, ) -> Result; ); diff --git a/crates/storage/src/user/recovery.rs b/crates/storage/src/user/recovery.rs index 05f2d9333..a5361e795 100644 --- a/crates/storage/src/user/recovery.rs +++ b/crates/storage/src/user/recovery.rs @@ -7,7 +7,7 @@ use std::net::IpAddr; use async_trait::async_trait; -use mas_data_model::{UserAgent, UserEmail, UserRecoverySession, UserRecoveryTicket}; +use mas_data_model::{UserEmail, UserRecoverySession, UserRecoveryTicket}; use rand_core::RngCore; use ulid::Ulid; @@ -59,7 +59,7 @@ pub trait UserRecoveryRepository: Send + Sync { rng: &mut (dyn RngCore + Send), clock: &dyn Clock, email: String, - user_agent: UserAgent, + user_agent: String, ip_address: Option, locale: String, ) -> Result; @@ -131,7 +131,7 @@ repository_impl!(UserRecoveryRepository: rng: &mut (dyn RngCore + Send), clock: &dyn Clock, email: String, - user_agent: UserAgent, + user_agent: String, ip_address: Option, locale: String, ) -> Result; diff --git a/crates/storage/src/user/registration.rs b/crates/storage/src/user/registration.rs index 8bc4ddcb0..3932db622 100644 --- a/crates/storage/src/user/registration.rs +++ b/crates/storage/src/user/registration.rs @@ -6,7 +6,7 @@ use std::net::IpAddr; use async_trait::async_trait; -use mas_data_model::{UserAgent, UserEmailAuthentication, UserRegistration}; +use mas_data_model::{UserEmailAuthentication, UserRegistration}; use rand_core::RngCore; use ulid::Ulid; use url::Url; @@ -56,7 +56,7 @@ pub trait UserRegistrationRepository: Send + Sync { clock: &dyn Clock, username: String, ip_address: Option, - user_agent: Option, + user_agent: Option, post_auth_action: Option, ) -> Result; @@ -166,7 +166,7 @@ repository_impl!(UserRegistrationRepository: clock: &dyn Clock, username: String, ip_address: Option, - user_agent: Option, + user_agent: Option, post_auth_action: Option, ) -> Result; async fn set_display_name( diff --git a/crates/storage/src/user/session.rs b/crates/storage/src/user/session.rs index 355507530..2421ff009 100644 --- a/crates/storage/src/user/session.rs +++ b/crates/storage/src/user/session.rs @@ -9,7 +9,7 @@ use std::net::IpAddr; use async_trait::async_trait; use chrono::{DateTime, Utc}; use mas_data_model::{ - Authentication, BrowserSession, Password, UpstreamOAuthAuthorizationSession, User, UserAgent, + Authentication, BrowserSession, Password, UpstreamOAuthAuthorizationSession, User, }; use rand_core::RngCore; use ulid::Ulid; @@ -151,7 +151,7 @@ pub trait BrowserSessionRepository: Send + Sync { rng: &mut (dyn RngCore + Send), clock: &dyn Clock, user: &User, - user_agent: Option, + user_agent: Option, ) -> Result; /// Finish a [`BrowserSession`] @@ -296,7 +296,7 @@ repository_impl!(BrowserSessionRepository: rng: &mut (dyn RngCore + Send), clock: &dyn Clock, user: &User, - user_agent: Option, + user_agent: Option, ) -> Result; async fn finish( &mut self, diff --git a/crates/templates/src/context.rs b/crates/templates/src/context.rs index b6661d540..e1ca20069 100644 --- a/crates/templates/src/context.rs +++ b/crates/templates/src/context.rs @@ -22,7 +22,7 @@ use mas_data_model::{ AuthorizationGrant, BrowserSession, Client, CompatSsoLogin, CompatSsoLoginState, DeviceCodeGrant, UpstreamOAuthLink, UpstreamOAuthProvider, UpstreamOAuthProviderClaimsImports, UpstreamOAuthProviderDiscoveryMode, UpstreamOAuthProviderPkceMode, - UpstreamOAuthProviderTokenAuthMethod, User, UserAgent, UserEmailAuthentication, + UpstreamOAuthProviderTokenAuthMethod, User, UserEmailAuthentication, UserEmailAuthenticationCode, UserRecoverySession, UserRegistration, }; use mas_i18n::DataLocale; @@ -808,7 +808,7 @@ impl TemplateContext for EmailRecoveryContext { let session = UserRecoverySession { id: Ulid::from_datetime_with_source(now.into(), rng), email: "hello@example.com".to_owned(), - user_agent: UserAgent::parse("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_4) AppleWebKit/536.30.1 (KHTML, like Gecko) Version/6.0.5 Safari/536.30.1".to_owned()), + user_agent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_4) AppleWebKit/536.30.1 (KHTML, like Gecko) Version/6.0.5 Safari/536.30.1".to_owned(), ip_address: Some(IpAddr::from([192_u8, 0, 2, 1])), locale: "en".to_owned(), created_at: now, @@ -1106,7 +1106,7 @@ impl TemplateContext for RecoveryProgressContext { let session = UserRecoverySession { id: Ulid::from_datetime_with_source(now.into(), rng), email: "name@mail.com".to_owned(), - user_agent: UserAgent::parse("Mozilla/5.0".to_owned()), + user_agent: "Mozilla/5.0".to_owned(), ip_address: None, locale: "en".to_owned(), created_at: now, @@ -1148,7 +1148,7 @@ impl TemplateContext for RecoveryExpiredContext { let session = UserRecoverySession { id: Ulid::from_datetime_with_source(now.into(), rng), email: "name@mail.com".to_owned(), - user_agent: UserAgent::parse("Mozilla/5.0".to_owned()), + user_agent: "Mozilla/5.0".to_owned(), ip_address: None, locale: "en".to_owned(), created_at: now, @@ -1529,7 +1529,7 @@ impl TemplateContext for DeviceConsentContext { created_at: now - Duration::try_minutes(5).unwrap(), expires_at: now + Duration::try_minutes(25).unwrap(), ip_address: Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))), - user_agent: Some(UserAgent::parse("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())), + 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()), }; Self { grant, client } })