Skip to content
This repository has been archived by the owner on Sep 10, 2024. It is now read-only.

Commit

Permalink
Record the user agent and IP in the device code grant
Browse files Browse the repository at this point in the history
  • Loading branch information
sandhose committed Feb 2, 2024
1 parent d39a1d2 commit 17e968f
Show file tree
Hide file tree
Showing 14 changed files with 129 additions and 20 deletions.
8 changes: 8 additions & 0 deletions crates/data-model/src/oauth2/device_code_grant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use std::net::IpAddr;

use chrono::{DateTime, Utc};
use oauth2_types::scope::Scope;
use serde::Serialize;
Expand Down Expand Up @@ -193,6 +195,12 @@ pub struct DeviceCodeGrant {

/// The time at which this device code grant will expire.
pub expires_at: DateTime<Utc>,

/// The IP address of the client which requested this device code grant.
pub ip_address: Option<IpAddr>,

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

impl std::ops::Deref for DeviceCodeGrant {
Expand Down
6 changes: 6 additions & 0 deletions crates/handlers/src/activity_tracker/bound.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ impl Bound {
Self { tracker, ip }
}

/// Get the IP address bound to this activity tracker.
#[must_use]
pub fn ip(&self) -> Option<IpAddr> {
self.ip
}

/// Record activity in an OAuth 2.0 session.
pub async fn record_oauth2_session(&self, clock: &dyn Clock, session: &Session) {
self.tracker
Expand Down
11 changes: 9 additions & 2 deletions crates/handlers/src/oauth2/device/authorize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

use axum::{extract::State, response::IntoResponse, Json, TypedHeader};
use chrono::Duration;
use headers::{CacheControl, Pragma};
use headers::{CacheControl, Pragma, UserAgent};
use hyper::StatusCode;
use mas_axum_utils::{
client_authorization::{ClientAuthorization, CredentialsVerificationError},
Expand All @@ -32,7 +32,7 @@ use oauth2_types::{
use rand::distributions::{Alphanumeric, DistString};
use thiserror::Error;

use crate::impl_from_error_for_route;
use crate::{impl_from_error_for_route, BoundActivityTracker};

#[derive(Debug, Error)]
pub(crate) enum RouteError {
Expand Down Expand Up @@ -84,6 +84,8 @@ pub(crate) async fn post(
mut rng: BoxRng,
clock: BoxClock,
mut repo: BoxRepository,
user_agent: Option<TypedHeader<UserAgent>>,
activity_tracker: BoundActivityTracker,
State(url_builder): State<UrlBuilder>,
State(http_client_factory): State<HttpClientFactory>,
State(encrypter): State<Encrypter>,
Expand Down Expand Up @@ -123,6 +125,9 @@ pub(crate) async fn post(

let expires_in = Duration::minutes(20);

let user_agent = user_agent.map(|ua| ua.0.to_string());
let ip_address = activity_tracker.ip();

let device_code = Alphanumeric.sample_string(&mut rng, 32);
let user_code = Alphanumeric.sample_string(&mut rng, 6).to_uppercase();

Expand All @@ -137,6 +142,8 @@ pub(crate) async fn post(
device_code,
user_code,
expires_in,
user_agent,
ip_address,
},
)
.await?;
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -72,5 +72,11 @@ CREATE TABLE "oauth2_device_code_grant" (
-- The browser session ID that the user used to authenticate
-- This means "fulfilled_at" or "rejected_at" has also been set
"user_session_id" UUID
REFERENCES "user_sessions" ("user_session_id")
REFERENCES "user_sessions" ("user_session_id"),

-- The IP address of the user when they authenticated
"ip_address" INET,

-- The user agent of the user when they authenticated
"user_agent" TEXT
);
22 changes: 21 additions & 1 deletion crates/storage-pg/src/oauth2/device_code_grant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use std::net::IpAddr;

use async_trait::async_trait;
use chrono::{DateTime, Utc};
use mas_data_model::{BrowserSession, DeviceCodeGrant, DeviceCodeGrantState, Session};
Expand Down Expand Up @@ -54,6 +56,8 @@ struct OAuth2DeviceGrantLookup {
exchanged_at: Option<DateTime<Utc>>,
user_session_id: Option<Uuid>,
oauth2_session_id: Option<Uuid>,
ip_address: Option<IpAddr>,
user_agent: Option<String>,
}

impl TryFrom<OAuth2DeviceGrantLookup> for DeviceCodeGrant {
Expand All @@ -73,6 +77,8 @@ impl TryFrom<OAuth2DeviceGrantLookup> for DeviceCodeGrant {
exchanged_at,
user_session_id,
oauth2_session_id,
ip_address,
user_agent,
}: OAuth2DeviceGrantLookup,
) -> Result<Self, Self::Error> {
let id = Ulid::from(oauth2_device_code_grant_id);
Expand Down Expand Up @@ -133,6 +139,8 @@ impl TryFrom<OAuth2DeviceGrantLookup> for DeviceCodeGrant {
device_code,
created_at,
expires_at,
ip_address,
user_agent,
})
}
}
Expand Down Expand Up @@ -176,9 +184,11 @@ impl<'c> OAuth2DeviceCodeGrantRepository for PgOAuth2DeviceCodeGrantRepository<'
, user_code
, created_at
, expires_at
, ip_address
, user_agent
)
VALUES
($1, $2, $3, $4, $5, $6, $7)
($1, $2, $3, $4, $5, $6, $7, $8, $9)
"#,
Uuid::from(id),
Uuid::from(client_id),
Expand All @@ -187,6 +197,8 @@ impl<'c> OAuth2DeviceCodeGrantRepository for PgOAuth2DeviceCodeGrantRepository<'
&params.user_code,
created_at,
expires_at,
params.ip_address as Option<IpAddr>,
params.user_agent.as_deref(),
)
.traced()
.execute(&mut *self.conn)
Expand All @@ -201,6 +213,8 @@ impl<'c> OAuth2DeviceCodeGrantRepository for PgOAuth2DeviceCodeGrantRepository<'
device_code: params.device_code,
created_at,
expires_at,
ip_address: params.ip_address,
user_agent: params.user_agent,
})
}

Expand Down Expand Up @@ -229,6 +243,8 @@ impl<'c> OAuth2DeviceCodeGrantRepository for PgOAuth2DeviceCodeGrantRepository<'
, exchanged_at
, user_session_id
, oauth2_session_id
, ip_address as "ip_address: IpAddr"
, user_agent
FROM
oauth2_device_code_grant
Expand Down Expand Up @@ -273,6 +289,8 @@ impl<'c> OAuth2DeviceCodeGrantRepository for PgOAuth2DeviceCodeGrantRepository<'
, exchanged_at
, user_session_id
, oauth2_session_id
, ip_address as "ip_address: IpAddr"
, user_agent
FROM
oauth2_device_code_grant
Expand Down Expand Up @@ -317,6 +335,8 @@ impl<'c> OAuth2DeviceCodeGrantRepository for PgOAuth2DeviceCodeGrantRepository<'
, exchanged_at
, user_session_id
, oauth2_session_id
, ip_address as "ip_address: IpAddr"
, user_agent
FROM
oauth2_device_code_grant
Expand Down
4 changes: 4 additions & 0 deletions crates/storage-pg/src/oauth2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,8 @@ mod tests {
device_code: device_code.to_owned(),
user_code: user_code.to_owned(),
expires_in: Duration::minutes(5),
ip_address: None,
user_agent: None,
},
)
.await
Expand Down Expand Up @@ -861,6 +863,8 @@ mod tests {
device_code: "second_devicecode".to_owned(),
user_code: "second_usercode".to_owned(),
expires_in: Duration::minutes(5),
ip_address: None,
user_agent: None,
},
)
.await
Expand Down
8 changes: 8 additions & 0 deletions crates/storage/src/oauth2/device_code_grant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use std::net::IpAddr;

use async_trait::async_trait;
use chrono::Duration;
use mas_data_model::{BrowserSession, Client, DeviceCodeGrant, Session};
Expand All @@ -37,6 +39,12 @@ pub struct OAuth2DeviceCodeGrantParams<'a> {

/// After how long the device code expires
pub expires_in: Duration,

/// IP address from which the request was made
pub ip_address: Option<IpAddr>,

/// The user agent from which the request was made
pub user_agent: Option<String>,
}

/// An [`OAuth2DeviceCodeGrantRepository`] helps interacting with
Expand Down
9 changes: 8 additions & 1 deletion crates/templates/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@
mod branding;

use std::fmt::Formatter;
use std::{
fmt::Formatter,
net::{IpAddr, Ipv4Addr},
};

use chrono::{DateTime, Duration, Utc};
use http::{Method, Uri, Version};
Expand Down Expand Up @@ -622,6 +625,8 @@ impl TemplateContext for PolicyViolationContext {
device_code: Alphanumeric.sample_string(rng, 32),
created_at: now - Duration::minutes(5),
expires_at: now + Duration::minutes(25),
ip_address: None,
user_agent: None,
},
client,
);
Expand Down Expand Up @@ -1152,6 +1157,8 @@ impl TemplateContext for DeviceConsentContext {
device_code: Alphanumeric.sample_string(rng, 32),
created_at: now - Duration::minutes(5),
expires_at: now + Duration::minutes(25),
ip_address: Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
user_agent: Some("Mozilla/5.0".to_owned()),
};
Self { grant, client }
})
Expand Down
Loading

0 comments on commit 17e968f

Please sign in to comment.