mirror of
https://github.com/element-hq/matrix-authentication-service.git
synced 2026-09-26 20:20:06 +00:00
Record auth related metrics (#4301)
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// Please see LICENSE in the repository root for full details.
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, LazyLock};
|
||||
|
||||
use axum::{
|
||||
Json,
|
||||
@@ -27,6 +27,7 @@ use mas_storage::{
|
||||
},
|
||||
user::{UserPasswordRepository, UserRepository},
|
||||
};
|
||||
use opentelemetry::{Key, KeyValue, metrics::Counter};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::{DurationMilliSeconds, serde_as, skip_serializing_none};
|
||||
@@ -35,10 +36,20 @@ use zeroize::Zeroizing;
|
||||
|
||||
use super::MatrixError;
|
||||
use crate::{
|
||||
BoundActivityTracker, Limiter, RequesterFingerprint, impl_from_error_for_route,
|
||||
BoundActivityTracker, Limiter, METER, RequesterFingerprint, impl_from_error_for_route,
|
||||
passwords::PasswordManager, rate_limit::PasswordCheckLimitedError,
|
||||
};
|
||||
|
||||
static LOGIN_COUNTER: LazyLock<Counter<u64>> = LazyLock::new(|| {
|
||||
METER
|
||||
.u64_counter("mas.compat.login_request")
|
||||
.with_description("How many compatibility login requests have happened")
|
||||
.with_unit("{request}")
|
||||
.build()
|
||||
});
|
||||
const TYPE: Key = Key::from_static_str("type");
|
||||
const RESULT: Key = Key::from_static_str("result");
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(tag = "type")]
|
||||
enum LoginType {
|
||||
@@ -123,6 +134,16 @@ pub enum Credentials {
|
||||
Unsupported,
|
||||
}
|
||||
|
||||
impl Credentials {
|
||||
fn login_type(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Password { .. } => "m.login.password",
|
||||
Self::Token { .. } => "m.login.token",
|
||||
Self::Unsupported => "unsupported",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum Identifier {
|
||||
@@ -192,6 +213,7 @@ impl_from_error_for_route!(mas_storage::RepositoryError);
|
||||
impl IntoResponse for RouteError {
|
||||
fn into_response(self) -> axum::response::Response {
|
||||
let event_id = sentry::capture_error(&self);
|
||||
LOGIN_COUNTER.add(1, &[KeyValue::new(RESULT, "error")]);
|
||||
let response = match self {
|
||||
Self::Internal(_) | Self::SessionNotFound | Self::ProvisionDeviceFailed(_) => {
|
||||
MatrixError {
|
||||
@@ -278,6 +300,7 @@ pub(crate) async fn post(
|
||||
WithRejection(Json(input), _): WithRejection<Json<RequestBody>, RouteError>,
|
||||
) -> Result<impl IntoResponse, RouteError> {
|
||||
let user_agent = user_agent.map(|ua| UserAgent::parse(ua.as_str().to_owned()));
|
||||
let login_type = input.credentials.login_type();
|
||||
let (mut session, user) = match (password_manager.is_enabled(), input.credentials) {
|
||||
(
|
||||
true,
|
||||
@@ -360,6 +383,14 @@ pub(crate) async fn post(
|
||||
.record_compat_session(&clock, &session)
|
||||
.await;
|
||||
|
||||
LOGIN_COUNTER.add(
|
||||
1,
|
||||
&[
|
||||
KeyValue::new(TYPE, login_type),
|
||||
KeyValue::new(RESULT, "success"),
|
||||
],
|
||||
);
|
||||
|
||||
Ok(Json(ResponseBody {
|
||||
access_token: access_token.token,
|
||||
device_id: session.device,
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// Please see LICENSE in the repository root for full details.
|
||||
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use axum::{Json, response::IntoResponse};
|
||||
use axum_extra::typed_header::TypedHeader;
|
||||
use headers::{Authorization, authorization::Bearer};
|
||||
@@ -15,10 +17,20 @@ use mas_storage::{
|
||||
compat::{CompatAccessTokenRepository, CompatSessionRepository},
|
||||
queue::{QueueJobRepositoryExt as _, SyncDevicesJob},
|
||||
};
|
||||
use opentelemetry::{Key, KeyValue, metrics::Counter};
|
||||
use thiserror::Error;
|
||||
|
||||
use super::MatrixError;
|
||||
use crate::{BoundActivityTracker, impl_from_error_for_route};
|
||||
use crate::{BoundActivityTracker, METER, impl_from_error_for_route};
|
||||
|
||||
static LOGOUT_COUNTER: LazyLock<Counter<u64>> = LazyLock::new(|| {
|
||||
METER
|
||||
.u64_counter("mas.compat.logout_request")
|
||||
.with_description("How many compatibility logout request have happened")
|
||||
.with_unit("{request}")
|
||||
.build()
|
||||
});
|
||||
const RESULT: Key = Key::from_static_str("result");
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum RouteError {
|
||||
@@ -40,6 +52,7 @@ impl_from_error_for_route!(mas_storage::RepositoryError);
|
||||
impl IntoResponse for RouteError {
|
||||
fn into_response(self) -> axum::response::Response {
|
||||
let event_id = sentry::capture_error(&self);
|
||||
LOGOUT_COUNTER.add(1, &[KeyValue::new(RESULT, "error")]);
|
||||
let response = match self {
|
||||
Self::Internal(_) => MatrixError {
|
||||
errcode: "M_UNKNOWN",
|
||||
@@ -113,5 +126,7 @@ pub(crate) async fn post(
|
||||
|
||||
repo.save().await?;
|
||||
|
||||
LOGOUT_COUNTER.add(1, &[KeyValue::new(RESULT, "success")]);
|
||||
|
||||
Ok(Json(serde_json::json!({})))
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// Please see LICENSE in the repository root for full details.
|
||||
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use axum::{Json, extract::State, http::HeaderValue, response::IntoResponse};
|
||||
use hyper::{HeaderMap, StatusCode};
|
||||
use mas_axum_utils::{
|
||||
@@ -24,9 +26,21 @@ use oauth2_types::{
|
||||
requests::{IntrospectionRequest, IntrospectionResponse},
|
||||
scope::ScopeToken,
|
||||
};
|
||||
use opentelemetry::{Key, KeyValue, metrics::Counter};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{ActivityTracker, impl_from_error_for_route};
|
||||
use crate::{ActivityTracker, METER, impl_from_error_for_route};
|
||||
|
||||
static INTROSPECTION_COUNTER: LazyLock<Counter<u64>> = LazyLock::new(|| {
|
||||
METER
|
||||
.u64_counter("mas.oauth2.introspection_request")
|
||||
.with_description("Number of OAuth 2.0 introspection requests")
|
||||
.with_unit("{request}")
|
||||
.build()
|
||||
});
|
||||
|
||||
const KIND: Key = Key::from_static_str("kind");
|
||||
const ACTIVE: Key = Key::from_static_str("active");
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum RouteError {
|
||||
@@ -118,6 +132,7 @@ impl IntoResponse for RouteError {
|
||||
),
|
||||
)
|
||||
.into_response(),
|
||||
|
||||
Self::UnknownToken(_)
|
||||
| Self::UnexpectedTokenType
|
||||
| Self::InvalidToken(_)
|
||||
@@ -125,7 +140,12 @@ impl IntoResponse for RouteError {
|
||||
| Self::InvalidCompatSession
|
||||
| Self::InvalidOAuthSession
|
||||
| Self::InvalidTokenFormat(_)
|
||||
| Self::CantEncodeDeviceID(_) => Json(INACTIVE).into_response(),
|
||||
| Self::CantEncodeDeviceID(_) => {
|
||||
INTROSPECTION_COUNTER.add(1, &[KeyValue::new(ACTIVE.clone(), false)]);
|
||||
|
||||
Json(INACTIVE).into_response()
|
||||
}
|
||||
|
||||
Self::NotAllowed => (
|
||||
StatusCode::UNAUTHORIZED,
|
||||
Json(ClientError::from(ClientErrorCode::AccessDenied)),
|
||||
@@ -275,6 +295,14 @@ pub(crate) async fn post(
|
||||
.record_oauth2_session(&clock, &session, ip)
|
||||
.await;
|
||||
|
||||
INTROSPECTION_COUNTER.add(
|
||||
1,
|
||||
&[
|
||||
KeyValue::new(KIND, "oauth2_access_token"),
|
||||
KeyValue::new(ACTIVE, true),
|
||||
],
|
||||
);
|
||||
|
||||
IntrospectionResponse {
|
||||
active: true,
|
||||
scope: Some(session.scope),
|
||||
@@ -338,6 +366,14 @@ pub(crate) async fn post(
|
||||
.record_oauth2_session(&clock, &session, ip)
|
||||
.await;
|
||||
|
||||
INTROSPECTION_COUNTER.add(
|
||||
1,
|
||||
&[
|
||||
KeyValue::new(KIND, "oauth2_refresh_token"),
|
||||
KeyValue::new(ACTIVE, true),
|
||||
],
|
||||
);
|
||||
|
||||
IntrospectionResponse {
|
||||
active: true,
|
||||
scope: Some(session.scope),
|
||||
@@ -412,6 +448,14 @@ pub(crate) async fn post(
|
||||
.record_compat_session(&clock, &session, ip)
|
||||
.await;
|
||||
|
||||
INTROSPECTION_COUNTER.add(
|
||||
1,
|
||||
&[
|
||||
KeyValue::new(KIND, "compat_access_token"),
|
||||
KeyValue::new(ACTIVE, true),
|
||||
],
|
||||
);
|
||||
|
||||
IntrospectionResponse {
|
||||
active: true,
|
||||
scope: Some(scope),
|
||||
@@ -488,6 +532,14 @@ pub(crate) async fn post(
|
||||
.record_compat_session(&clock, &session, ip)
|
||||
.await;
|
||||
|
||||
INTROSPECTION_COUNTER.add(
|
||||
1,
|
||||
&[
|
||||
KeyValue::new(KIND, "compat_refresh_token"),
|
||||
KeyValue::new(ACTIVE, true),
|
||||
],
|
||||
);
|
||||
|
||||
IntrospectionResponse {
|
||||
active: true,
|
||||
scope: Some(scope),
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// Please see LICENSE in the repository root for full details.
|
||||
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use axum::{Json, extract::State, response::IntoResponse};
|
||||
use axum_extra::TypedHeader;
|
||||
use hyper::StatusCode;
|
||||
@@ -19,6 +21,7 @@ use oauth2_types::{
|
||||
VerifiedClientMetadata,
|
||||
},
|
||||
};
|
||||
use opentelemetry::{Key, KeyValue, metrics::Counter};
|
||||
use psl::Psl;
|
||||
use rand::distributions::{Alphanumeric, DistString};
|
||||
use serde::Serialize;
|
||||
@@ -27,7 +30,16 @@ use thiserror::Error;
|
||||
use tracing::info;
|
||||
use url::Url;
|
||||
|
||||
use crate::{BoundActivityTracker, impl_from_error_for_route};
|
||||
use crate::{BoundActivityTracker, METER, impl_from_error_for_route};
|
||||
|
||||
static REGISTRATION_COUNTER: LazyLock<Counter<u64>> = LazyLock::new(|| {
|
||||
METER
|
||||
.u64_counter("mas.oauth2.registration_request")
|
||||
.with_description("Number of OAuth2 registration requests")
|
||||
.with_unit("{request}")
|
||||
.build()
|
||||
});
|
||||
const RESULT: Key = Key::from_static_str("result");
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub(crate) enum RouteError {
|
||||
@@ -56,6 +68,9 @@ impl_from_error_for_route!(serde_json::Error);
|
||||
impl IntoResponse for RouteError {
|
||||
fn into_response(self) -> axum::response::Response {
|
||||
let event_id = sentry::capture_error(&self);
|
||||
|
||||
REGISTRATION_COUNTER.add(1, &[KeyValue::new(RESULT, "denied")]);
|
||||
|
||||
let response = match self {
|
||||
Self::Internal(_) => (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
@@ -303,6 +318,7 @@ pub(crate) async fn post(
|
||||
|
||||
let client = if let Some(client) = existing_client {
|
||||
tracing::info!(%client.id, "Reusing existing client");
|
||||
REGISTRATION_COUNTER.add(1, &[KeyValue::new(RESULT, "reused")]);
|
||||
client
|
||||
} else {
|
||||
let client = repo
|
||||
@@ -335,6 +351,7 @@ pub(crate) async fn post(
|
||||
)
|
||||
.await?;
|
||||
tracing::info!(%client.id, "Registered new client");
|
||||
REGISTRATION_COUNTER.add(1, &[KeyValue::new(RESULT, "created")]);
|
||||
client
|
||||
};
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// Please see LICENSE in the repository root for full details.
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, LazyLock};
|
||||
|
||||
use axum::{Json, extract::State, response::IntoResponse};
|
||||
use axum_extra::typed_header::TypedHeader;
|
||||
@@ -40,12 +40,23 @@ use oauth2_types::{
|
||||
},
|
||||
scope,
|
||||
};
|
||||
use opentelemetry::{Key, KeyValue, metrics::Counter};
|
||||
use thiserror::Error;
|
||||
use tracing::{debug, info};
|
||||
use ulid::Ulid;
|
||||
|
||||
use super::{generate_id_token, generate_token_pair};
|
||||
use crate::{BoundActivityTracker, impl_from_error_for_route};
|
||||
use crate::{BoundActivityTracker, METER, impl_from_error_for_route};
|
||||
|
||||
static TOKEN_REQUEST_COUNTER: LazyLock<Counter<u64>> = LazyLock::new(|| {
|
||||
METER
|
||||
.u64_counter("mas.oauth2.token_request")
|
||||
.with_description("How many OAuth 2.0 token requests have gone through")
|
||||
.with_unit("{request}")
|
||||
.build()
|
||||
});
|
||||
const GRANT_TYPE: Key = Key::from_static_str("grant_type");
|
||||
const RESULT: Key = Key::from_static_str("successful");
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub(crate) enum RouteError {
|
||||
@@ -136,6 +147,8 @@ impl IntoResponse for RouteError {
|
||||
fn into_response(self) -> axum::response::Response {
|
||||
let event_id = sentry::capture_error(&self);
|
||||
|
||||
TOKEN_REQUEST_COUNTER.add(1, &[KeyValue::new(RESULT, "error")]);
|
||||
|
||||
let response = match self {
|
||||
Self::Internal(_)
|
||||
| Self::NoSuchBrowserSession
|
||||
@@ -254,6 +267,8 @@ pub(crate) async fn post(
|
||||
|
||||
let form = client_authorization.form.ok_or(RouteError::BadRequest)?;
|
||||
|
||||
let grant_type = form.grant_type();
|
||||
|
||||
let (reply, repo) = match form {
|
||||
AccessTokenRequest::AuthorizationCode(grant) => {
|
||||
authorization_code_grant(
|
||||
@@ -321,6 +336,14 @@ pub(crate) async fn post(
|
||||
|
||||
repo.save().await?;
|
||||
|
||||
TOKEN_REQUEST_COUNTER.add(
|
||||
1,
|
||||
&[
|
||||
KeyValue::new(GRANT_TYPE, grant_type),
|
||||
KeyValue::new(RESULT, "success"),
|
||||
],
|
||||
);
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.typed_insert(CacheControl::new().with_no_store());
|
||||
headers.typed_insert(Pragma::no_cache());
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// Please see LICENSE in the repository root for full details.
|
||||
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use axum::{
|
||||
Form,
|
||||
extract::{Path, State},
|
||||
@@ -26,6 +28,7 @@ use mas_storage::{
|
||||
};
|
||||
use mas_templates::{FormPostContext, Templates};
|
||||
use oauth2_types::{errors::ClientErrorCode, requests::AccessTokenRequest};
|
||||
use opentelemetry::{Key, KeyValue, metrics::Counter};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use thiserror::Error;
|
||||
@@ -37,7 +40,18 @@ use super::{
|
||||
client_credentials_for_provider,
|
||||
template::{AttributeMappingContext, environment},
|
||||
};
|
||||
use crate::{PreferredLanguage, impl_from_error_for_route, upstream_oauth2::cache::MetadataCache};
|
||||
use crate::{
|
||||
METER, PreferredLanguage, impl_from_error_for_route, upstream_oauth2::cache::MetadataCache,
|
||||
};
|
||||
|
||||
static CALLBACK_COUNTER: LazyLock<Counter<u64>> = LazyLock::new(|| {
|
||||
METER
|
||||
.u64_counter("mas.upstream_oauth2.callback")
|
||||
.with_description("Number of requests to the upstream OAuth2 callback endpoint")
|
||||
.build()
|
||||
});
|
||||
const PROVIDER: Key = Key::from_static_str("provider");
|
||||
const RESULT: Key = Key::from_static_str("result");
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Params {
|
||||
@@ -216,6 +230,14 @@ pub(crate) async fn handler(
|
||||
}
|
||||
|
||||
if let Some(error) = params.error {
|
||||
CALLBACK_COUNTER.add(
|
||||
1,
|
||||
&[
|
||||
KeyValue::new(PROVIDER, provider_id.to_string()),
|
||||
KeyValue::new(RESULT, "error"),
|
||||
],
|
||||
);
|
||||
|
||||
return Err(RouteError::ClientError {
|
||||
error,
|
||||
error_description: params.error_description.clone(),
|
||||
@@ -256,6 +278,14 @@ pub(crate) async fn handler(
|
||||
return Err(RouteError::MissingCode);
|
||||
};
|
||||
|
||||
CALLBACK_COUNTER.add(
|
||||
1,
|
||||
&[
|
||||
KeyValue::new(PROVIDER, provider_id.to_string()),
|
||||
KeyValue::new(RESULT, "success"),
|
||||
],
|
||||
);
|
||||
|
||||
let mut lazy_metadata = LazyProviderInfos::new(&metadata_cache, &provider, &client);
|
||||
|
||||
// Figure out the client credentials
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// Please see LICENSE in the repository root for full details.
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, LazyLock};
|
||||
|
||||
use axum::{
|
||||
Form,
|
||||
@@ -35,6 +35,7 @@ use mas_templates::{
|
||||
ToFormState, UpstreamExistingLinkContext, UpstreamRegister, UpstreamSuggestLink,
|
||||
};
|
||||
use minijinja::Environment;
|
||||
use opentelemetry::{Key, KeyValue, metrics::Counter};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
use tracing::warn;
|
||||
@@ -45,10 +46,26 @@ use super::{
|
||||
template::{AttributeMappingContext, environment},
|
||||
};
|
||||
use crate::{
|
||||
BoundActivityTracker, PreferredLanguage, SiteConfig, impl_from_error_for_route,
|
||||
BoundActivityTracker, METER, PreferredLanguage, SiteConfig, impl_from_error_for_route,
|
||||
views::shared::OptionalPostAuthAction,
|
||||
};
|
||||
|
||||
static LOGIN_COUNTER: LazyLock<Counter<u64>> = LazyLock::new(|| {
|
||||
METER
|
||||
.u64_counter("mas.upstream_oauth2.login")
|
||||
.with_description("Successful upstream OAuth 2.0 login to existing accounts")
|
||||
.with_unit("{login}")
|
||||
.build()
|
||||
});
|
||||
static REGISTRATION_COUNTER: LazyLock<Counter<u64>> = LazyLock::new(|| {
|
||||
METER
|
||||
.u64_counter("mas.upstream_oauth2.registration")
|
||||
.with_description("Successful upstream OAuth 2.0 registration")
|
||||
.with_unit("{registration}")
|
||||
.build()
|
||||
});
|
||||
const PROVIDER: Key = Key::from_static_str("provider");
|
||||
|
||||
const DEFAULT_LOCALPART_TEMPLATE: &str = "{{ user.preferred_username }}";
|
||||
const DEFAULT_DISPLAYNAME_TEMPLATE: &str = "{{ user.name }}";
|
||||
const DEFAULT_EMAIL_TEMPLATE: &str = "{{ user.email }}";
|
||||
@@ -340,6 +357,14 @@ pub(crate) async fn get(
|
||||
|
||||
repo.save().await?;
|
||||
|
||||
LOGIN_COUNTER.add(
|
||||
1,
|
||||
&[KeyValue::new(
|
||||
PROVIDER,
|
||||
upstream_session.provider_id.to_string(),
|
||||
)],
|
||||
);
|
||||
|
||||
post_auth_action.go_next(&url_builder).into_response()
|
||||
}
|
||||
|
||||
@@ -805,6 +830,8 @@ pub(crate) async fn post(
|
||||
.into_response());
|
||||
}
|
||||
|
||||
REGISTRATION_COUNTER.add(1, &[KeyValue::new(PROVIDER, provider.id.to_string())]);
|
||||
|
||||
// Now we can create the user
|
||||
let user = repo.user().add(&mut rng, &clock, username).await?;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// Please see LICENSE in the repository root for full details.
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, LazyLock};
|
||||
|
||||
use axum::{
|
||||
extract::{Form, Query, State},
|
||||
@@ -30,17 +30,27 @@ use mas_templates::{
|
||||
AccountInactiveContext, FieldError, FormError, FormState, LoginContext, LoginFormField,
|
||||
PostAuthContext, PostAuthContextInner, TemplateContext, Templates, ToFormState,
|
||||
};
|
||||
use opentelemetry::{Key, KeyValue, metrics::Counter};
|
||||
use rand::Rng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
use super::shared::OptionalPostAuthAction;
|
||||
use crate::{
|
||||
BoundActivityTracker, Limiter, PreferredLanguage, RequesterFingerprint, SiteConfig,
|
||||
BoundActivityTracker, Limiter, METER, PreferredLanguage, RequesterFingerprint, SiteConfig,
|
||||
passwords::PasswordManager,
|
||||
session::{SessionOrFallback, load_session_or_fallback},
|
||||
};
|
||||
|
||||
static PASSWORD_LOGIN_COUNTER: LazyLock<Counter<u64>> = LazyLock::new(|| {
|
||||
METER
|
||||
.u64_counter("mas.user.password_login_attempt")
|
||||
.with_description("Number of password login attempts")
|
||||
.with_unit("{attempt}")
|
||||
.build()
|
||||
});
|
||||
const RESULT: Key = Key::from_static_str("result");
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub(crate) struct LoginForm {
|
||||
username: String,
|
||||
@@ -156,6 +166,7 @@ pub(crate) async fn post(
|
||||
}
|
||||
|
||||
if !form_state.is_valid() {
|
||||
PASSWORD_LOGIN_COUNTER.add(1, &[KeyValue::new(RESULT, "error")]);
|
||||
return render(
|
||||
locale,
|
||||
cookie_jar,
|
||||
@@ -178,6 +189,7 @@ pub(crate) async fn post(
|
||||
// First, lookup the user
|
||||
let Some(user) = repo.user().find_by_username(username).await? else {
|
||||
let form_state = form_state.with_error_on_form(FormError::InvalidCredentials);
|
||||
PASSWORD_LOGIN_COUNTER.add(1, &[KeyValue::new(RESULT, "error")]);
|
||||
return render(
|
||||
locale,
|
||||
cookie_jar,
|
||||
@@ -196,6 +208,7 @@ pub(crate) async fn post(
|
||||
if let Err(e) = limiter.check_password(requester, &user) {
|
||||
tracing::warn!(error = &e as &dyn std::error::Error);
|
||||
let form_state = form_state.with_error_on_form(FormError::RateLimitExceeded);
|
||||
PASSWORD_LOGIN_COUNTER.add(1, &[KeyValue::new(RESULT, "error")]);
|
||||
return render(
|
||||
locale,
|
||||
cookie_jar,
|
||||
@@ -215,6 +228,7 @@ pub(crate) async fn post(
|
||||
// There is no password for this user, but we don't want to disclose that. Show
|
||||
// a generic 'invalid credentials' error instead
|
||||
let form_state = form_state.with_error_on_form(FormError::InvalidCredentials);
|
||||
PASSWORD_LOGIN_COUNTER.add(1, &[KeyValue::new(RESULT, "error")]);
|
||||
return render(
|
||||
locale,
|
||||
cookie_jar,
|
||||
@@ -257,6 +271,7 @@ pub(crate) async fn post(
|
||||
Ok(None) => user_password,
|
||||
Err(_) => {
|
||||
let form_state = form_state.with_error_on_form(FormError::InvalidCredentials);
|
||||
PASSWORD_LOGIN_COUNTER.add(1, &[KeyValue::new(RESULT, "error")]);
|
||||
return render(
|
||||
locale,
|
||||
cookie_jar,
|
||||
@@ -275,6 +290,7 @@ pub(crate) async fn post(
|
||||
// Now that we have checked the user password, we now want to show an error if
|
||||
// the user is locked or deactivated
|
||||
if user.deactivated_at.is_some() {
|
||||
PASSWORD_LOGIN_COUNTER.add(1, &[KeyValue::new(RESULT, "error")]);
|
||||
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng);
|
||||
let ctx = AccountInactiveContext::new(user)
|
||||
.with_csrf(csrf_token.form_value())
|
||||
@@ -284,6 +300,7 @@ pub(crate) async fn post(
|
||||
}
|
||||
|
||||
if user.locked_at.is_some() {
|
||||
PASSWORD_LOGIN_COUNTER.add(1, &[KeyValue::new(RESULT, "error")]);
|
||||
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng);
|
||||
let ctx = AccountInactiveContext::new(user)
|
||||
.with_csrf(csrf_token.form_value())
|
||||
@@ -309,6 +326,8 @@ pub(crate) async fn post(
|
||||
|
||||
repo.save().await?;
|
||||
|
||||
PASSWORD_LOGIN_COUNTER.add(1, &[KeyValue::new(RESULT, "success")]);
|
||||
|
||||
activity_tracker
|
||||
.record_browser_session(&clock, &user_session)
|
||||
.await;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// Please see LICENSE in the repository root for full details.
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, LazyLock};
|
||||
|
||||
use anyhow::Context as _;
|
||||
use axum::{
|
||||
@@ -22,10 +22,21 @@ use mas_storage::{
|
||||
user::UserEmailFilter,
|
||||
};
|
||||
use mas_templates::{RegisterStepsEmailInUseContext, TemplateContext as _, Templates};
|
||||
use opentelemetry::metrics::Counter;
|
||||
use ulid::Ulid;
|
||||
|
||||
use super::super::cookie::UserRegistrationSessions;
|
||||
use crate::{BoundActivityTracker, PreferredLanguage, views::shared::OptionalPostAuthAction};
|
||||
use crate::{
|
||||
BoundActivityTracker, METER, PreferredLanguage, views::shared::OptionalPostAuthAction,
|
||||
};
|
||||
|
||||
static PASSWORD_REGISTER_COUNTER: LazyLock<Counter<u64>> = LazyLock::new(|| {
|
||||
METER
|
||||
.u64_counter("mas.user.password_registration")
|
||||
.with_description("Number of password registrations")
|
||||
.with_unit("{registration}")
|
||||
.build()
|
||||
});
|
||||
|
||||
#[tracing::instrument(
|
||||
name = "handlers.views.register.steps.finish.get",
|
||||
@@ -203,6 +214,8 @@ pub(crate) async fn get(
|
||||
repo.browser_session()
|
||||
.authenticate_with_password(&mut rng, &clock, &user_session, &user_password)
|
||||
.await?;
|
||||
|
||||
PASSWORD_REGISTER_COUNTER.add(1, &[]);
|
||||
}
|
||||
|
||||
if let Some(terms_url) = registration.terms_url {
|
||||
|
||||
@@ -638,6 +638,20 @@ pub enum AccessTokenRequest {
|
||||
Unsupported,
|
||||
}
|
||||
|
||||
impl AccessTokenRequest {
|
||||
/// Returns the string representation of the grant type of the request.
|
||||
#[must_use]
|
||||
pub fn grant_type(&self) -> &'static str {
|
||||
match self {
|
||||
Self::AuthorizationCode(_) => "authorization_code",
|
||||
Self::RefreshToken(_) => "refresh_token",
|
||||
Self::ClientCredentials(_) => "client_credentials",
|
||||
Self::DeviceCode(_) => "urn:ietf:params:oauth:grant-type:device_code",
|
||||
Self::Unsupported => "unsupported",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A successful response from the [Token Endpoint].
|
||||
///
|
||||
/// [Token Endpoint]: https://www.rfc-editor.org/rfc/rfc6749#section-3.2
|
||||
|
||||
Reference in New Issue
Block a user