You've already forked authentication-service
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:
@ -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()
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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),
|
||||
|
@ -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)?;
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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"
|
||||
}
|
@ -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"
|
||||
}
|
@ -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;
|
@ -18,9 +18,10 @@
|
||||
pub enum UserSessions {
|
||||
Table,
|
||||
UserSessionId,
|
||||
UserId,
|
||||
CreatedAt,
|
||||
FinishedAt,
|
||||
UserId,
|
||||
UserAgent,
|
||||
}
|
||||
|
||||
#[derive(sea_query::Iden)]
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
Reference in New Issue
Block a user