Skip to content

Commit fa69cdc

Browse files
authored
Record auth related metrics (#4301)
2 parents a4ab2ef + f72ff85 commit fa69cdc

File tree

10 files changed

+256
-15
lines changed

10 files changed

+256
-15
lines changed

crates/handlers/src/compat/login.rs

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
// SPDX-License-Identifier: AGPL-3.0-only
55
// Please see LICENSE in the repository root for full details.
66

7-
use std::sync::Arc;
7+
use std::sync::{Arc, LazyLock};
88

99
use axum::{
1010
Json,
@@ -27,6 +27,7 @@ use mas_storage::{
2727
},
2828
user::{UserPasswordRepository, UserRepository},
2929
};
30+
use opentelemetry::{Key, KeyValue, metrics::Counter};
3031
use rand::{CryptoRng, RngCore};
3132
use serde::{Deserialize, Serialize};
3233
use serde_with::{DurationMilliSeconds, serde_as, skip_serializing_none};
@@ -35,10 +36,20 @@ use zeroize::Zeroizing;
3536

3637
use super::MatrixError;
3738
use crate::{
38-
BoundActivityTracker, Limiter, RequesterFingerprint, impl_from_error_for_route,
39+
BoundActivityTracker, Limiter, METER, RequesterFingerprint, impl_from_error_for_route,
3940
passwords::PasswordManager, rate_limit::PasswordCheckLimitedError,
4041
};
4142

43+
static LOGIN_COUNTER: LazyLock<Counter<u64>> = LazyLock::new(|| {
44+
METER
45+
.u64_counter("mas.compat.login_request")
46+
.with_description("How many compatibility login requests have happened")
47+
.with_unit("{request}")
48+
.build()
49+
});
50+
const TYPE: Key = Key::from_static_str("type");
51+
const RESULT: Key = Key::from_static_str("result");
52+
4253
#[derive(Debug, Serialize)]
4354
#[serde(tag = "type")]
4455
enum LoginType {
@@ -123,6 +134,16 @@ pub enum Credentials {
123134
Unsupported,
124135
}
125136

137+
impl Credentials {
138+
fn login_type(&self) -> &'static str {
139+
match self {
140+
Self::Password { .. } => "m.login.password",
141+
Self::Token { .. } => "m.login.token",
142+
Self::Unsupported => "unsupported",
143+
}
144+
}
145+
}
146+
126147
#[derive(Debug, Serialize, Deserialize)]
127148
#[serde(tag = "type")]
128149
pub enum Identifier {
@@ -192,6 +213,7 @@ impl_from_error_for_route!(mas_storage::RepositoryError);
192213
impl IntoResponse for RouteError {
193214
fn into_response(self) -> axum::response::Response {
194215
let event_id = sentry::capture_error(&self);
216+
LOGIN_COUNTER.add(1, &[KeyValue::new(RESULT, "error")]);
195217
let response = match self {
196218
Self::Internal(_) | Self::SessionNotFound | Self::ProvisionDeviceFailed(_) => {
197219
MatrixError {
@@ -278,6 +300,7 @@ pub(crate) async fn post(
278300
WithRejection(Json(input), _): WithRejection<Json<RequestBody>, RouteError>,
279301
) -> Result<impl IntoResponse, RouteError> {
280302
let user_agent = user_agent.map(|ua| UserAgent::parse(ua.as_str().to_owned()));
303+
let login_type = input.credentials.login_type();
281304
let (mut session, user) = match (password_manager.is_enabled(), input.credentials) {
282305
(
283306
true,
@@ -360,6 +383,14 @@ pub(crate) async fn post(
360383
.record_compat_session(&clock, &session)
361384
.await;
362385

386+
LOGIN_COUNTER.add(
387+
1,
388+
&[
389+
KeyValue::new(TYPE, login_type),
390+
KeyValue::new(RESULT, "success"),
391+
],
392+
);
393+
363394
Ok(Json(ResponseBody {
364395
access_token: access_token.token,
365396
device_id: session.device,

crates/handlers/src/compat/logout.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
// SPDX-License-Identifier: AGPL-3.0-only
55
// Please see LICENSE in the repository root for full details.
66

7+
use std::sync::LazyLock;
8+
79
use axum::{Json, response::IntoResponse};
810
use axum_extra::typed_header::TypedHeader;
911
use headers::{Authorization, authorization::Bearer};
@@ -15,10 +17,20 @@ use mas_storage::{
1517
compat::{CompatAccessTokenRepository, CompatSessionRepository},
1618
queue::{QueueJobRepositoryExt as _, SyncDevicesJob},
1719
};
20+
use opentelemetry::{Key, KeyValue, metrics::Counter};
1821
use thiserror::Error;
1922

2023
use super::MatrixError;
21-
use crate::{BoundActivityTracker, impl_from_error_for_route};
24+
use crate::{BoundActivityTracker, METER, impl_from_error_for_route};
25+
26+
static LOGOUT_COUNTER: LazyLock<Counter<u64>> = LazyLock::new(|| {
27+
METER
28+
.u64_counter("mas.compat.logout_request")
29+
.with_description("How many compatibility logout request have happened")
30+
.with_unit("{request}")
31+
.build()
32+
});
33+
const RESULT: Key = Key::from_static_str("result");
2234

2335
#[derive(Error, Debug)]
2436
pub enum RouteError {
@@ -40,6 +52,7 @@ impl_from_error_for_route!(mas_storage::RepositoryError);
4052
impl IntoResponse for RouteError {
4153
fn into_response(self) -> axum::response::Response {
4254
let event_id = sentry::capture_error(&self);
55+
LOGOUT_COUNTER.add(1, &[KeyValue::new(RESULT, "error")]);
4356
let response = match self {
4457
Self::Internal(_) => MatrixError {
4558
errcode: "M_UNKNOWN",
@@ -113,5 +126,7 @@ pub(crate) async fn post(
113126

114127
repo.save().await?;
115128

129+
LOGOUT_COUNTER.add(1, &[KeyValue::new(RESULT, "success")]);
130+
116131
Ok(Json(serde_json::json!({})))
117132
}

crates/handlers/src/oauth2/introspection.rs

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
// SPDX-License-Identifier: AGPL-3.0-only
55
// Please see LICENSE in the repository root for full details.
66

7+
use std::sync::LazyLock;
8+
79
use axum::{Json, extract::State, http::HeaderValue, response::IntoResponse};
810
use hyper::{HeaderMap, StatusCode};
911
use mas_axum_utils::{
@@ -24,9 +26,21 @@ use oauth2_types::{
2426
requests::{IntrospectionRequest, IntrospectionResponse},
2527
scope::ScopeToken,
2628
};
29+
use opentelemetry::{Key, KeyValue, metrics::Counter};
2730
use thiserror::Error;
2831

29-
use crate::{ActivityTracker, impl_from_error_for_route};
32+
use crate::{ActivityTracker, METER, impl_from_error_for_route};
33+
34+
static INTROSPECTION_COUNTER: LazyLock<Counter<u64>> = LazyLock::new(|| {
35+
METER
36+
.u64_counter("mas.oauth2.introspection_request")
37+
.with_description("Number of OAuth 2.0 introspection requests")
38+
.with_unit("{request}")
39+
.build()
40+
});
41+
42+
const KIND: Key = Key::from_static_str("kind");
43+
const ACTIVE: Key = Key::from_static_str("active");
3044

3145
#[derive(Debug, Error)]
3246
pub enum RouteError {
@@ -118,14 +132,20 @@ impl IntoResponse for RouteError {
118132
),
119133
)
120134
.into_response(),
135+
121136
Self::UnknownToken(_)
122137
| Self::UnexpectedTokenType
123138
| Self::InvalidToken(_)
124139
| Self::InvalidUser
125140
| Self::InvalidCompatSession
126141
| Self::InvalidOAuthSession
127142
| Self::InvalidTokenFormat(_)
128-
| Self::CantEncodeDeviceID(_) => Json(INACTIVE).into_response(),
143+
| Self::CantEncodeDeviceID(_) => {
144+
INTROSPECTION_COUNTER.add(1, &[KeyValue::new(ACTIVE.clone(), false)]);
145+
146+
Json(INACTIVE).into_response()
147+
}
148+
129149
Self::NotAllowed => (
130150
StatusCode::UNAUTHORIZED,
131151
Json(ClientError::from(ClientErrorCode::AccessDenied)),
@@ -275,6 +295,14 @@ pub(crate) async fn post(
275295
.record_oauth2_session(&clock, &session, ip)
276296
.await;
277297

298+
INTROSPECTION_COUNTER.add(
299+
1,
300+
&[
301+
KeyValue::new(KIND, "oauth2_access_token"),
302+
KeyValue::new(ACTIVE, true),
303+
],
304+
);
305+
278306
IntrospectionResponse {
279307
active: true,
280308
scope: Some(session.scope),
@@ -338,6 +366,14 @@ pub(crate) async fn post(
338366
.record_oauth2_session(&clock, &session, ip)
339367
.await;
340368

369+
INTROSPECTION_COUNTER.add(
370+
1,
371+
&[
372+
KeyValue::new(KIND, "oauth2_refresh_token"),
373+
KeyValue::new(ACTIVE, true),
374+
],
375+
);
376+
341377
IntrospectionResponse {
342378
active: true,
343379
scope: Some(session.scope),
@@ -412,6 +448,14 @@ pub(crate) async fn post(
412448
.record_compat_session(&clock, &session, ip)
413449
.await;
414450

451+
INTROSPECTION_COUNTER.add(
452+
1,
453+
&[
454+
KeyValue::new(KIND, "compat_access_token"),
455+
KeyValue::new(ACTIVE, true),
456+
],
457+
);
458+
415459
IntrospectionResponse {
416460
active: true,
417461
scope: Some(scope),
@@ -488,6 +532,14 @@ pub(crate) async fn post(
488532
.record_compat_session(&clock, &session, ip)
489533
.await;
490534

535+
INTROSPECTION_COUNTER.add(
536+
1,
537+
&[
538+
KeyValue::new(KIND, "compat_refresh_token"),
539+
KeyValue::new(ACTIVE, true),
540+
],
541+
);
542+
491543
IntrospectionResponse {
492544
active: true,
493545
scope: Some(scope),

crates/handlers/src/oauth2/registration.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
// SPDX-License-Identifier: AGPL-3.0-only
55
// Please see LICENSE in the repository root for full details.
66

7+
use std::sync::LazyLock;
8+
79
use axum::{Json, extract::State, response::IntoResponse};
810
use axum_extra::TypedHeader;
911
use hyper::StatusCode;
@@ -19,6 +21,7 @@ use oauth2_types::{
1921
VerifiedClientMetadata,
2022
},
2123
};
24+
use opentelemetry::{Key, KeyValue, metrics::Counter};
2225
use psl::Psl;
2326
use rand::distributions::{Alphanumeric, DistString};
2427
use serde::Serialize;
@@ -27,7 +30,16 @@ use thiserror::Error;
2730
use tracing::info;
2831
use url::Url;
2932

30-
use crate::{BoundActivityTracker, impl_from_error_for_route};
33+
use crate::{BoundActivityTracker, METER, impl_from_error_for_route};
34+
35+
static REGISTRATION_COUNTER: LazyLock<Counter<u64>> = LazyLock::new(|| {
36+
METER
37+
.u64_counter("mas.oauth2.registration_request")
38+
.with_description("Number of OAuth2 registration requests")
39+
.with_unit("{request}")
40+
.build()
41+
});
42+
const RESULT: Key = Key::from_static_str("result");
3143

3244
#[derive(Debug, Error)]
3345
pub(crate) enum RouteError {
@@ -56,6 +68,9 @@ impl_from_error_for_route!(serde_json::Error);
5668
impl IntoResponse for RouteError {
5769
fn into_response(self) -> axum::response::Response {
5870
let event_id = sentry::capture_error(&self);
71+
72+
REGISTRATION_COUNTER.add(1, &[KeyValue::new(RESULT, "denied")]);
73+
5974
let response = match self {
6075
Self::Internal(_) => (
6176
StatusCode::INTERNAL_SERVER_ERROR,
@@ -303,6 +318,7 @@ pub(crate) async fn post(
303318

304319
let client = if let Some(client) = existing_client {
305320
tracing::info!(%client.id, "Reusing existing client");
321+
REGISTRATION_COUNTER.add(1, &[KeyValue::new(RESULT, "reused")]);
306322
client
307323
} else {
308324
let client = repo
@@ -335,6 +351,7 @@ pub(crate) async fn post(
335351
)
336352
.await?;
337353
tracing::info!(%client.id, "Registered new client");
354+
REGISTRATION_COUNTER.add(1, &[KeyValue::new(RESULT, "created")]);
338355
client
339356
};
340357

crates/handlers/src/oauth2/token.rs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
// SPDX-License-Identifier: AGPL-3.0-only
55
// Please see LICENSE in the repository root for full details.
66

7-
use std::sync::Arc;
7+
use std::sync::{Arc, LazyLock};
88

99
use axum::{Json, extract::State, response::IntoResponse};
1010
use axum_extra::typed_header::TypedHeader;
@@ -40,12 +40,23 @@ use oauth2_types::{
4040
},
4141
scope,
4242
};
43+
use opentelemetry::{Key, KeyValue, metrics::Counter};
4344
use thiserror::Error;
4445
use tracing::{debug, info};
4546
use ulid::Ulid;
4647

4748
use super::{generate_id_token, generate_token_pair};
48-
use crate::{BoundActivityTracker, impl_from_error_for_route};
49+
use crate::{BoundActivityTracker, METER, impl_from_error_for_route};
50+
51+
static TOKEN_REQUEST_COUNTER: LazyLock<Counter<u64>> = LazyLock::new(|| {
52+
METER
53+
.u64_counter("mas.oauth2.token_request")
54+
.with_description("How many OAuth 2.0 token requests have gone through")
55+
.with_unit("{request}")
56+
.build()
57+
});
58+
const GRANT_TYPE: Key = Key::from_static_str("grant_type");
59+
const RESULT: Key = Key::from_static_str("successful");
4960

5061
#[derive(Debug, Error)]
5162
pub(crate) enum RouteError {
@@ -136,6 +147,8 @@ impl IntoResponse for RouteError {
136147
fn into_response(self) -> axum::response::Response {
137148
let event_id = sentry::capture_error(&self);
138149

150+
TOKEN_REQUEST_COUNTER.add(1, &[KeyValue::new(RESULT, "error")]);
151+
139152
let response = match self {
140153
Self::Internal(_)
141154
| Self::NoSuchBrowserSession
@@ -254,6 +267,8 @@ pub(crate) async fn post(
254267

255268
let form = client_authorization.form.ok_or(RouteError::BadRequest)?;
256269

270+
let grant_type = form.grant_type();
271+
257272
let (reply, repo) = match form {
258273
AccessTokenRequest::AuthorizationCode(grant) => {
259274
authorization_code_grant(
@@ -321,6 +336,14 @@ pub(crate) async fn post(
321336

322337
repo.save().await?;
323338

339+
TOKEN_REQUEST_COUNTER.add(
340+
1,
341+
&[
342+
KeyValue::new(GRANT_TYPE, grant_type),
343+
KeyValue::new(RESULT, "success"),
344+
],
345+
);
346+
324347
let mut headers = HeaderMap::new();
325348
headers.typed_insert(CacheControl::new().with_no_store());
326349
headers.typed_insert(Pragma::no_cache());

0 commit comments

Comments
 (0)