You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-08-06 06:02:40 +03:00
Record the user agent and IP in the device code grant
This commit is contained in:
@@ -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;
|
||||
@@ -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 {
|
||||
|
@@ -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
|
||||
|
@@ -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},
|
||||
@@ -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 {
|
||||
@@ -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>,
|
||||
@@ -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();
|
||||
|
||||
@@ -137,6 +142,8 @@ pub(crate) async fn post(
|
||||
device_code,
|
||||
user_code,
|
||||
expires_in,
|
||||
user_agent,
|
||||
ip_address,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO \"oauth2_device_code_grant\" \n ( oauth2_device_code_grant_id\n , oauth2_client_id\n , scope\n , device_code\n , user_code\n , created_at\n , expires_at\n )\n VALUES\n ($1, $2, $3, $4, $5, $6, $7)\n ",
|
||||
"query": "\n INSERT INTO \"oauth2_device_code_grant\" \n ( oauth2_device_code_grant_id\n , oauth2_client_id\n , scope\n , device_code\n , user_code\n , created_at\n , expires_at\n , ip_address\n , user_agent\n )\n VALUES\n ($1, $2, $3, $4, $5, $6, $7, $8, $9)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -11,10 +11,12 @@
|
||||
"Text",
|
||||
"Text",
|
||||
"Timestamptz",
|
||||
"Timestamptz"
|
||||
"Timestamptz",
|
||||
"Inet",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "6a72c38cb718ac09b61e0fadd9703e4b7a984c46185cceea4eceff4655f4e81f"
|
||||
"hash": "9742df9a34fe64e294cae4fc4a18e261c03b2367adeaec8fd554ca6f52c2015e"
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT oauth2_device_code_grant_id\n , oauth2_client_id\n , scope\n , device_code\n , user_code\n , created_at\n , expires_at\n , fulfilled_at\n , rejected_at\n , exchanged_at\n , user_session_id\n , oauth2_session_id\n FROM \n oauth2_device_code_grant\n\n WHERE device_code = $1\n ",
|
||||
"query": "\n SELECT oauth2_device_code_grant_id\n , oauth2_client_id\n , scope\n , device_code\n , user_code\n , created_at\n , expires_at\n , fulfilled_at\n , rejected_at\n , exchanged_at\n , user_session_id\n , oauth2_session_id\n , ip_address as \"ip_address: IpAddr\"\n , user_agent\n FROM \n oauth2_device_code_grant\n\n WHERE device_code = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -62,6 +62,16 @@
|
||||
"ordinal": 11,
|
||||
"name": "oauth2_session_id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "ip_address: IpAddr",
|
||||
"type_info": "Inet"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"name": "user_agent",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -81,8 +91,10 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "be25896189a30862a0aa0b6d1d6ba44278b98d4b8d027036e8871853f5d175c0"
|
||||
"hash": "aabefb019c9195b0588882fef562472d6117ff68f8f37d02b7609c94aefdb5d6"
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT oauth2_device_code_grant_id\n , oauth2_client_id\n , scope\n , device_code\n , user_code\n , created_at\n , expires_at\n , fulfilled_at\n , rejected_at\n , exchanged_at\n , user_session_id\n , oauth2_session_id\n FROM \n oauth2_device_code_grant\n\n WHERE oauth2_device_code_grant_id = $1\n ",
|
||||
"query": "\n SELECT oauth2_device_code_grant_id\n , oauth2_client_id\n , scope\n , device_code\n , user_code\n , created_at\n , expires_at\n , fulfilled_at\n , rejected_at\n , exchanged_at\n , user_session_id\n , oauth2_session_id\n , ip_address as \"ip_address: IpAddr\"\n , user_agent\n FROM \n oauth2_device_code_grant\n\n WHERE oauth2_device_code_grant_id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -62,6 +62,16 @@
|
||||
"ordinal": 11,
|
||||
"name": "oauth2_session_id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "ip_address: IpAddr",
|
||||
"type_info": "Inet"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"name": "user_agent",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -81,8 +91,10 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "61dc64c1980b5d1d2e2b52c8c55c91e1953595e413bedcec27eafbf87e42f1cd"
|
||||
"hash": "f0977fee9b3919707e9aa20537d836d741acd5b2426218a2f9f9dab4fb8a2ad0"
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT oauth2_device_code_grant_id\n , oauth2_client_id\n , scope\n , device_code\n , user_code\n , created_at\n , expires_at\n , fulfilled_at\n , rejected_at\n , exchanged_at\n , user_session_id\n , oauth2_session_id\n FROM \n oauth2_device_code_grant\n\n WHERE user_code = $1\n ",
|
||||
"query": "\n SELECT oauth2_device_code_grant_id\n , oauth2_client_id\n , scope\n , device_code\n , user_code\n , created_at\n , expires_at\n , fulfilled_at\n , rejected_at\n , exchanged_at\n , user_session_id\n , oauth2_session_id\n , ip_address as \"ip_address: IpAddr\"\n , user_agent\n FROM \n oauth2_device_code_grant\n\n WHERE user_code = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -62,6 +62,16 @@
|
||||
"ordinal": 11,
|
||||
"name": "oauth2_session_id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "ip_address: IpAddr",
|
||||
"type_info": "Inet"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"name": "user_agent",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -81,8 +91,10 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "b83fd5c55a209151ce5053b56034c49b5972df523f21a17be76303bde4a88522"
|
||||
"hash": "fa5a57505066d03828015f79fdb1079eaea13cf7698073f2d5f74d4c50f7b094"
|
||||
}
|
@@ -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
|
||||
);
|
||||
|
@@ -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};
|
||||
@@ -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 {
|
||||
@@ -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);
|
||||
@@ -133,6 +139,8 @@ impl TryFrom<OAuth2DeviceGrantLookup> for DeviceCodeGrant {
|
||||
device_code,
|
||||
created_at,
|
||||
expires_at,
|
||||
ip_address,
|
||||
user_agent,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
@@ -187,6 +197,8 @@ impl<'c> OAuth2DeviceCodeGrantRepository for PgOAuth2DeviceCodeGrantRepository<'
|
||||
¶ms.user_code,
|
||||
created_at,
|
||||
expires_at,
|
||||
params.ip_address as Option<IpAddr>,
|
||||
params.user_agent.as_deref(),
|
||||
)
|
||||
.traced()
|
||||
.execute(&mut *self.conn)
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
@@ -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
|
||||
@@ -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
|
||||
|
@@ -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};
|
||||
@@ -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
|
||||
|
@@ -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};
|
||||
@@ -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,
|
||||
);
|
||||
@@ -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 }
|
||||
})
|
||||
|
@@ -33,13 +33,18 @@ limitations under the License.
|
||||
<h1 class="title">Allow access to your account?</h1>
|
||||
|
||||
<div class="consent-device-card">
|
||||
<div class="device">
|
||||
<div class="device" {%- if grant.user_agent %} title="{{ grant.user_agent }}"{% endif %}>
|
||||
{{ icon.web_browser() }}
|
||||
{# TODO: Infer from the user agent #}
|
||||
<div class="name">Device</div>
|
||||
</div>
|
||||
<div class="meta">
|
||||
{# TODO: add the IP address #}
|
||||
{% if grant.ip_address %}
|
||||
<div>
|
||||
<div class="key">IP address</div>
|
||||
<div class="value">{{ grant.ip_address }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div>
|
||||
<div class="key">Access requested</div>
|
||||
<div class="value">{{ _.relative_date(grant.created_at) | title }} {{ _.short_time(grant.created_at) }}</div>
|
||||
|
@@ -2,11 +2,11 @@
|
||||
"action": {
|
||||
"cancel": "Cancel",
|
||||
"@cancel": {
|
||||
"context": "pages/consent.html:72:11-29, pages/device_consent.html:76:13-31, pages/login.html:100:13-31, pages/policy_violation.html:52:13-31, pages/register.html:64:13-31"
|
||||
"context": "pages/consent.html:72:11-29, pages/device_consent.html:94:13-31, pages/login.html:100:13-31, pages/policy_violation.html:52:13-31, pages/register.html:64:13-31"
|
||||
},
|
||||
"continue": "Continue",
|
||||
"@continue": {
|
||||
"context": "pages/account/emails/add.html:45:26-46, pages/account/emails/verify.html:60:26-46, pages/consent.html:60:28-48, pages/device_consent.html:73:13-33, pages/device_link.html:50:26-46, pages/login.html:62:30-50, pages/reauth.html:40:28-48, pages/register.html:59:28-48, pages/sso.html:45:28-48"
|
||||
"context": "pages/account/emails/add.html:45:26-46, pages/account/emails/verify.html:60:26-46, pages/consent.html:60:28-48, pages/device_consent.html:91:13-33, pages/device_link.html:50:26-46, pages/login.html:62:30-50, pages/reauth.html:40:28-48, pages/register.html:59:28-48, pages/sso.html:45:28-48"
|
||||
},
|
||||
"create_account": "Create Account",
|
||||
"@create_account": {
|
||||
@@ -18,7 +18,7 @@
|
||||
},
|
||||
"sign_out": "Sign out",
|
||||
"@sign_out": {
|
||||
"context": "pages/consent.html:68:28-48, pages/device_consent.html:85:30-50, pages/index.html:36:28-48, pages/policy_violation.html:46:28-48, pages/sso.html:53:28-48, pages/upstream_oauth2/link_mismatch.html:32:24-44, pages/upstream_oauth2/suggest_link.html:40:26-46"
|
||||
"context": "pages/consent.html:68:28-48, pages/device_consent.html:103:30-50, pages/index.html:36:28-48, pages/policy_violation.html:46:28-48, pages/sso.html:53:28-48, pages/upstream_oauth2/link_mismatch.html:32:24-44, pages/upstream_oauth2/suggest_link.html:40:26-46"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
@@ -246,7 +246,7 @@
|
||||
},
|
||||
"not_you": "Not %(username)s?",
|
||||
"@not_you": {
|
||||
"context": "pages/consent.html:65:11-67, pages/device_consent.html:82:13-69, pages/sso.html:50:11-67",
|
||||
"context": "pages/consent.html:65:11-67, pages/device_consent.html:100:13-69, pages/sso.html:50:11-67",
|
||||
"description": "Suggestions for the user to log in as a different user"
|
||||
},
|
||||
"or_separator": "Or",
|
||||
|
Reference in New Issue
Block a user