1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-07-31 09:24:31 +03:00

Store the browser user-agent when starting a browser session

This commit is contained in:
Quentin Gliech
2023-08-29 16:45:42 +02:00
parent 1849b86a7d
commit 5d3b8cd92f
16 changed files with 87 additions and 26 deletions

View File

@ -80,6 +80,7 @@ pub struct BrowserSession {
pub user: User,
pub created_at: DateTime<Utc>,
pub finished_at: Option<DateTime<Utc>>,
pub user_agent: Option<String>,
}
impl BrowserSession {
@ -99,6 +100,7 @@ impl BrowserSession {
user,
created_at: now,
finished_at: None,
user_agent: Some("Mozilla/5.0".to_owned()),
})
.collect()
}

View File

@ -84,7 +84,7 @@ async fn start_oauth_session(
let browser_session = repo
.browser_session()
.add(&mut rng, &state.clock, user)
.add(&mut rng, &state.clock, user, None)
.await
.unwrap();

View File

@ -421,7 +421,7 @@ mod tests {
let browser_session = repo
.browser_session()
.add(&mut state.rng(), &state.clock, &user)
.add(&mut state.rng(), &state.clock, &user, None)
.await
.unwrap();

View File

@ -284,7 +284,7 @@ mod tests {
let browser_session = repo
.browser_session()
.add(&mut state.rng(), &state.clock, &user)
.add(&mut state.rng(), &state.clock, &user, None)
.await
.unwrap();

View File

@ -464,7 +464,7 @@ mod tests {
let browser_session = repo
.browser_session()
.add(&mut state.rng(), &state.clock, &user)
.add(&mut state.rng(), &state.clock, &user, None)
.await
.unwrap();
@ -672,7 +672,7 @@ mod tests {
let browser_session = repo
.browser_session()
.add(&mut state.rng(), &state.clock, &user)
.add(&mut state.rng(), &state.clock, &user, None)
.await
.unwrap();

View File

@ -15,7 +15,7 @@
use axum::{
extract::{Path, State},
response::{Html, IntoResponse},
Form,
Form, TypedHeader,
};
use hyper::StatusCode;
use mas_axum_utils::{
@ -170,8 +170,10 @@ pub(crate) async fn get(
mut repo: BoxRepository,
State(templates): State<Templates>,
cookie_jar: CookieJar,
user_agent: Option<TypedHeader<headers::UserAgent>>,
Path(link_id): Path<Ulid>,
) -> Result<impl IntoResponse, RouteError> {
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)
@ -264,7 +266,10 @@ pub(crate) async fn get(
.filter(mas_data_model::User::is_valid)
.ok_or(RouteError::UserNotFound)?;
let session = repo.browser_session().add(&mut rng, &clock, &user).await?;
let session = repo
.browser_session()
.add(&mut rng, &clock, &user, user_agent)
.await?;
let upstream_session = repo
.upstream_oauth_session()
@ -352,9 +357,11 @@ pub(crate) async fn post(
clock: BoxClock,
mut repo: BoxRepository,
cookie_jar: CookieJar,
user_agent: Option<TypedHeader<headers::UserAgent>>,
Path(link_id): Path<Ulid>,
Form(form): Form<ProtectedForm<FormData>>,
) -> Result<impl IntoResponse, RouteError> {
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);
@ -503,7 +510,9 @@ pub(crate) async fn post(
.associate_to_user(&link, &user)
.await?;
repo.browser_session().add(&mut rng, &clock, &user).await?
repo.browser_session()
.add(&mut rng, &clock, &user, user_agent)
.await?
}
_ => return Err(RouteError::InvalidFormAction),

View File

@ -15,7 +15,9 @@
use axum::{
extract::{Form, Query, State},
response::{Html, IntoResponse, Response},
TypedHeader,
};
use headers::UserAgent;
use hyper::StatusCode;
use mas_axum_utils::{
cookies::CookieJar,
@ -109,8 +111,10 @@ pub(crate) async fn post(
mut repo: BoxRepository,
Query(query): Query<OptionalPostAuthAction>,
cookie_jar: CookieJar,
user_agent: Option<TypedHeader<UserAgent>>,
Form(form): Form<ProtectedForm<LoginForm>>,
) -> Result<Response, FancyError> {
let user_agent = user_agent.map(|ua| ua.as_str().to_owned());
if !password_manager.is_enabled() {
// XXX: is it necessary to have better errors here?
return Ok(StatusCode::METHOD_NOT_ALLOWED.into_response());
@ -158,6 +162,7 @@ pub(crate) async fn post(
&clock,
&form.username,
&form.password,
user_agent,
)
.await
{
@ -193,6 +198,7 @@ async fn login(
clock: &impl Clock,
username: &str,
password: &str,
user_agent: Option<String>,
) -> Result<BrowserSession, FormError> {
// XXX: we're loosing the error context here
// First, lookup the user
@ -245,7 +251,7 @@ async fn login(
// Start a new session
let user_session = repo
.browser_session()
.add(&mut rng, clock, &user)
.add(&mut rng, clock, &user, user_agent)
.await
.map_err(|_| FormError::Internal)?;

View File

@ -17,7 +17,9 @@ use std::{str::FromStr, sync::Arc};
use axum::{
extract::{Form, Query, State},
response::{Html, IntoResponse, Response},
TypedHeader,
};
use headers::UserAgent;
use hyper::StatusCode;
use lettre::Address;
use mas_axum_utils::{
@ -104,8 +106,10 @@ pub(crate) async fn post(
mut repo: BoxRepository,
Query(query): Query<OptionalPostAuthAction>,
cookie_jar: CookieJar,
user_agent: Option<TypedHeader<UserAgent>>,
Form(form): Form<ProtectedForm<RegisterForm>>,
) -> Result<Response, FancyError> {
let user_agent = user_agent.map(|ua| ua.as_str().to_owned());
if !password_manager.is_enabled() {
return Ok(StatusCode::METHOD_NOT_ALLOWED.into_response());
}
@ -206,7 +210,10 @@ pub(crate) async fn post(
let next = mas_router::AccountVerifyEmail::new(user_email.id).and_maybe(query.post_auth_action);
let session = repo.browser_session().add(&mut rng, &clock, &user).await?;
let session = repo
.browser_session()
.add(&mut rng, &clock, &user, user_agent)
.await?;
repo.browser_session()
.authenticate_with_password(&mut rng, &clock, &session, &user_password)

View File

@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT s.user_session_id\n , s.created_at AS \"user_session_created_at\"\n , s.finished_at AS \"user_session_finished_at\"\n , u.user_id\n , u.username AS \"user_username\"\n , u.primary_user_email_id AS \"user_primary_user_email_id\"\n , u.created_at AS \"user_created_at\"\n , u.locked_at AS \"user_locked_at\"\n FROM user_sessions s\n INNER JOIN users u\n USING (user_id)\n WHERE s.user_session_id = $1\n ",
"query": "\n SELECT s.user_session_id\n , s.created_at AS \"user_session_created_at\"\n , s.finished_at AS \"user_session_finished_at\"\n , s.user_agent AS \"user_session_user_agent\"\n , u.user_id\n , u.username AS \"user_username\"\n , u.primary_user_email_id AS \"user_primary_user_email_id\"\n , u.created_at AS \"user_created_at\"\n , u.locked_at AS \"user_locked_at\"\n FROM user_sessions s\n INNER JOIN users u\n USING (user_id)\n WHERE s.user_session_id = $1\n ",
"describe": {
"columns": [
{
@ -20,26 +20,31 @@
},
{
"ordinal": 3,
"name": "user_session_user_agent",
"type_info": "Text"
},
{
"ordinal": 4,
"name": "user_id",
"type_info": "Uuid"
},
{
"ordinal": 4,
"ordinal": 5,
"name": "user_username",
"type_info": "Text"
},
{
"ordinal": 5,
"ordinal": 6,
"name": "user_primary_user_email_id",
"type_info": "Uuid"
},
{
"ordinal": 6,
"ordinal": 7,
"name": "user_created_at",
"type_info": "Timestamptz"
},
{
"ordinal": 7,
"ordinal": 8,
"name": "user_locked_at",
"type_info": "Timestamptz"
}
@ -53,6 +58,7 @@
false,
false,
true,
true,
false,
false,
true,
@ -60,5 +66,5 @@
true
]
},
"hash": "73fe61f03a41778c6273b1c2dbdb13b91fbccfe5fbdbead8c4868d52a61a0f9d"
"hash": "b98052065469a71643bb332cbeb07e0e43c620ffa1a592eb45ab326e0064efa8"
}

View File

@ -1,16 +1,17 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO user_sessions (user_session_id, user_id, created_at)\n VALUES ($1, $2, $3)\n ",
"query": "\n INSERT INTO user_sessions (user_session_id, user_id, created_at, user_agent)\n VALUES ($1, $2, $3, $4)\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid",
"Uuid",
"Timestamptz"
"Timestamptz",
"Text"
]
},
"nullable": []
},
"hash": "c1d90a7f2287ec779c81a521fab19e5ede3fa95484033e0312c30d9b6ecc03f0"
"hash": "f41f76c94cd68fca2285b1cc60f426603c84df4ef1c6ce5dc441a63d2dc46f6e"
}

View File

@ -0,0 +1,16 @@
-- Copyright 2023 The Matrix.org Foundation C.I.C.
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
-- This adds a user_agent column to the user_sessions table
ALTER TABLE user_sessions ADD COLUMN user_agent TEXT;

View File

@ -18,9 +18,10 @@
pub enum UserSessions {
Table,
UserSessionId,
UserId,
CreatedAt,
FinishedAt,
UserId,
UserAgent,
}
#[derive(sea_query::Iden)]

View File

@ -177,7 +177,7 @@ mod tests {
.unwrap();
let user_session = repo
.browser_session()
.add(&mut rng, &clock, &user)
.add(&mut rng, &clock, &user, None)
.await
.unwrap();
@ -387,7 +387,7 @@ mod tests {
.unwrap();
let user1_session = repo
.browser_session()
.add(&mut rng, &clock, &user1)
.add(&mut rng, &clock, &user1, None)
.await
.unwrap();
@ -398,7 +398,7 @@ mod tests {
.unwrap();
let user2_session = repo
.browser_session()
.add(&mut rng, &clock, &user2)
.add(&mut rng, &clock, &user2, None)
.await
.unwrap();

View File

@ -53,6 +53,7 @@ struct SessionLookup {
user_session_id: Uuid,
user_session_created_at: DateTime<Utc>,
user_session_finished_at: Option<DateTime<Utc>>,
user_session_user_agent: Option<String>,
user_id: Uuid,
user_username: String,
user_primary_user_email_id: Option<Uuid>,
@ -79,6 +80,7 @@ impl TryFrom<SessionLookup> for BrowserSession {
user,
created_at: value.user_session_created_at,
finished_at: value.user_session_finished_at,
user_agent: value.user_session_user_agent,
})
}
}
@ -139,6 +141,7 @@ impl<'c> BrowserSessionRepository for PgBrowserSessionRepository<'c> {
SELECT s.user_session_id
, s.created_at AS "user_session_created_at"
, s.finished_at AS "user_session_finished_at"
, s.user_agent AS "user_session_user_agent"
, u.user_id
, u.username AS "user_username"
, u.primary_user_email_id AS "user_primary_user_email_id"
@ -175,6 +178,7 @@ impl<'c> BrowserSessionRepository for PgBrowserSessionRepository<'c> {
rng: &mut (dyn RngCore + Send),
clock: &dyn Clock,
user: &User,
user_agent: Option<String>,
) -> Result<BrowserSession, Self::Error> {
let created_at = clock.now();
let id = Ulid::from_datetime_with_source(created_at.into(), rng);
@ -182,12 +186,13 @@ impl<'c> BrowserSessionRepository for PgBrowserSessionRepository<'c> {
sqlx::query!(
r#"
INSERT INTO user_sessions (user_session_id, user_id, created_at)
VALUES ($1, $2, $3)
INSERT INTO user_sessions (user_session_id, user_id, created_at, user_agent)
VALUES ($1, $2, $3, $4)
"#,
Uuid::from(id),
Uuid::from(user.id),
created_at,
user_agent,
)
.traced()
.execute(&mut *self.conn)
@ -199,6 +204,7 @@ impl<'c> BrowserSessionRepository for PgBrowserSessionRepository<'c> {
user: user.clone(),
created_at,
finished_at: None,
user_agent,
};
Ok(session)
@ -265,6 +271,10 @@ impl<'c> BrowserSessionRepository for PgBrowserSessionRepository<'c> {
Expr::col((UserSessions::Table, UserSessions::FinishedAt)),
SessionLookupIden::UserSessionFinishedAt,
)
.expr_as(
Expr::col((UserSessions::Table, UserSessions::UserAgent)),
SessionLookupIden::UserSessionUserAgent,
)
.expr_as(
Expr::col((Users::Table, Users::UserId)),
SessionLookupIden::UserId,

View File

@ -433,7 +433,7 @@ async fn test_user_session(pool: PgPool) {
let session = repo
.browser_session()
.add(&mut rng, &clock, &user)
.add(&mut rng, &clock, &user, None)
.await
.unwrap();
assert_eq!(session.user.id, user.id);

View File

@ -114,6 +114,7 @@ pub trait BrowserSessionRepository: Send + Sync {
/// * `rng`: The random number generator to use
/// * `clock`: The clock used to generate timestamps
/// * `user`: The user to create the session for
/// * `user_agent`: If available, the user agent of the browser
///
/// # Errors
///
@ -123,6 +124,7 @@ pub trait BrowserSessionRepository: Send + Sync {
rng: &mut (dyn RngCore + Send),
clock: &dyn Clock,
user: &User,
user_agent: Option<String>,
) -> Result<BrowserSession, Self::Error>;
/// Finish a [`BrowserSession`]
@ -234,6 +236,7 @@ repository_impl!(BrowserSessionRepository:
rng: &mut (dyn RngCore + Send),
clock: &dyn Clock,
user: &User,
user_agent: Option<String>,
) -> Result<BrowserSession, Self::Error>;
async fn finish(
&mut self,