From 0e94e6c2bbaf5d64c680976c57b2ccd5de951a79 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Tue, 3 Jun 2025 14:29:59 +0200 Subject: [PATCH] Add whether the registration is valid or not in the admin API --- crates/handlers/src/admin/model.rs | 10 ++++-- .../admin/v1/user_registration_tokens/add.rs | 19 +++++----- .../admin/v1/user_registration_tokens/get.rs | 11 +++--- .../admin/v1/user_registration_tokens/list.rs | 36 +++++++++++++++++-- .../v1/user_registration_tokens/revoke.rs | 2 +- docs/api/spec.json | 12 ++++++- 6 files changed, 71 insertions(+), 19 deletions(-) diff --git a/crates/handlers/src/admin/model.rs b/crates/handlers/src/admin/model.rs index 1dbf12b9b..03fd72ad0 100644 --- a/crates/handlers/src/admin/model.rs +++ b/crates/handlers/src/admin/model.rs @@ -612,6 +612,9 @@ pub struct UserRegistrationToken { /// The token string token: String, + /// Whether the token is valid + valid: bool, + /// Maximum number of times this token can be used usage_limit: Option, @@ -631,10 +634,11 @@ pub struct UserRegistrationToken { revoked_at: Option>, } -impl From for UserRegistrationToken { - fn from(token: mas_data_model::UserRegistrationToken) -> Self { +impl UserRegistrationToken { + pub fn new(token: mas_data_model::UserRegistrationToken, now: DateTime) -> Self { Self { id: token.id, + valid: token.is_valid(now), token: token.token, usage_limit: token.usage_limit, times_used: token.times_used, @@ -662,6 +666,7 @@ impl UserRegistrationToken { Self { id: Ulid::from_bytes([0x01; 16]), token: "abc123def456".to_owned(), + valid: true, usage_limit: Some(10), times_used: 5, created_at: DateTime::default(), @@ -672,6 +677,7 @@ impl UserRegistrationToken { Self { id: Ulid::from_bytes([0x02; 16]), token: "xyz789abc012".to_owned(), + valid: false, usage_limit: None, times_used: 0, created_at: DateTime::default(), diff --git a/crates/handlers/src/admin/v1/user_registration_tokens/add.rs b/crates/handlers/src/admin/v1/user_registration_tokens/add.rs index 50fcb110a..fdb958f58 100644 --- a/crates/handlers/src/admin/v1/user_registration_tokens/add.rs +++ b/crates/handlers/src/admin/v1/user_registration_tokens/add.rs @@ -9,7 +9,7 @@ use chrono::{DateTime, Utc}; use hyper::StatusCode; use mas_axum_utils::record_error; use mas_storage::BoxRng; -use rand::{Rng, distributions::Alphanumeric}; +use rand::distributions::{Alphanumeric, DistString}; use schemars::JsonSchema; use serde::Deserialize; @@ -79,13 +79,9 @@ pub async fn handler( Json(params): Json, ) -> Result<(StatusCode, Json>), RouteError> { // Generate a random token if none was provided - let token = params.token.unwrap_or_else(|| { - (&mut rng) - .sample_iter(&Alphanumeric) - .take(12) - .map(char::from) - .collect() - }); + let token = params + .token + .unwrap_or_else(|| Alphanumeric.sample_string(&mut rng, 12)); let registration_token = repo .user_registration_token() @@ -102,7 +98,10 @@ pub async fn handler( Ok(( StatusCode::CREATED, - Json(SingleResponse::new_canonical(registration_token.into())), + Json(SingleResponse::new_canonical(UserRegistrationToken::new( + registration_token, + clock.now(), + ))), )) } @@ -137,6 +136,7 @@ mod tests { "id": "01FSHN9AG0MZAA6S4AF7CTV32E", "attributes": { "token": "test_token_123", + "valid": true, "usage_limit": 5, "times_used": 0, "created_at": "2022-01-16T14:40:00Z", @@ -178,6 +178,7 @@ mod tests { "id": "01FSHN9AG0QMGC989M0XSFVF2X", "attributes": { "token": "42oTpLoieH5I", + "valid": true, "usage_limit": 1, "times_used": 0, "created_at": "2022-01-16T14:40:00Z", diff --git a/crates/handlers/src/admin/v1/user_registration_tokens/get.rs b/crates/handlers/src/admin/v1/user_registration_tokens/get.rs index a9714194d..833e3b17c 100644 --- a/crates/handlers/src/admin/v1/user_registration_tokens/get.rs +++ b/crates/handlers/src/admin/v1/user_registration_tokens/get.rs @@ -63,7 +63,9 @@ pub fn doc(operation: TransformOperation) -> TransformOperation { #[tracing::instrument(name = "handler.admin.v1.user_registration_tokens.get", skip_all)] pub async fn handler( - CallContext { mut repo, .. }: CallContext, + CallContext { + mut repo, clock, .. + }: CallContext, id: UlidPathParam, ) -> Result>, RouteError> { let token = repo @@ -73,7 +75,7 @@ pub async fn handler( .ok_or(RouteError::NotFound(*id))?; Ok(Json(SingleResponse::new_canonical( - UserRegistrationToken::from(token), + UserRegistrationToken::new(token, clock.now()), ))) } @@ -116,13 +118,14 @@ mod tests { response.assert_status(StatusCode::OK); let body: serde_json::Value = response.json(); - assert_json_snapshot!(body, @r###" + assert_json_snapshot!(body, @r#" { "data": { "type": "user-registration_token", "id": "01FSHN9AG0MZAA6S4AF7CTV32E", "attributes": { "token": "test_token_123", + "valid": true, "usage_limit": 5, "times_used": 0, "created_at": "2022-01-16T14:40:00Z", @@ -138,7 +141,7 @@ mod tests { "self": "/api/admin/v1/user-registration-tokens/01FSHN9AG0MZAA6S4AF7CTV32E" } } - "###); + "#); } #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")] diff --git a/crates/handlers/src/admin/v1/user_registration_tokens/list.rs b/crates/handlers/src/admin/v1/user_registration_tokens/list.rs index 1052dfea6..85a1a1945 100644 --- a/crates/handlers/src/admin/v1/user_registration_tokens/list.rs +++ b/crates/handlers/src/admin/v1/user_registration_tokens/list.rs @@ -135,7 +135,8 @@ pub async fn handler( params: FilterParams, ) -> Result>, RouteError> { let base = format!("{path}{params}", path = UserRegistrationToken::PATH); - let mut filter = UserRegistrationTokenFilter::new(clock.now()); + let now = clock.now(); + let mut filter = UserRegistrationTokenFilter::new(now); if let Some(used) = params.used { filter = filter.with_been_used(used); @@ -160,7 +161,7 @@ pub async fn handler( let count = repo.user_registration_token().count(filter).await?; Ok(Json(PaginatedResponse::new( - page.map(UserRegistrationToken::from), + page.map(|token| UserRegistrationToken::new(token, now)), pagination, count, &base, @@ -288,6 +289,7 @@ mod tests { "id": "01FSHN9AG064K8BYZXSY5G511Z", "attributes": { "token": "token_expired", + "valid": false, "usage_limit": 5, "times_used": 0, "created_at": "2022-01-16T14:40:00Z", @@ -304,6 +306,7 @@ mod tests { "id": "01FSHN9AG07HNEZXNQM2KNBNF6", "attributes": { "token": "token_used", + "valid": true, "usage_limit": 10, "times_used": 1, "created_at": "2022-01-16T14:40:00Z", @@ -320,6 +323,7 @@ mod tests { "id": "01FSHN9AG09AVTNSQFMSR34AJC", "attributes": { "token": "token_revoked", + "valid": false, "usage_limit": 10, "times_used": 0, "created_at": "2022-01-16T14:40:00Z", @@ -336,6 +340,7 @@ mod tests { "id": "01FSHN9AG0MZAA6S4AF7CTV32E", "attributes": { "token": "token_unused", + "valid": true, "usage_limit": 10, "times_used": 0, "created_at": "2022-01-16T14:40:00Z", @@ -352,6 +357,7 @@ mod tests { "id": "01FSHN9AG0S3ZJD8CXQ7F11KXN", "attributes": { "token": "token_used_revoked", + "valid": false, "usage_limit": 10, "times_used": 1, "created_at": "2022-01-16T14:40:00Z", @@ -399,6 +405,7 @@ mod tests { "id": "01FSHN9AG07HNEZXNQM2KNBNF6", "attributes": { "token": "token_used", + "valid": true, "usage_limit": 10, "times_used": 1, "created_at": "2022-01-16T14:40:00Z", @@ -415,6 +422,7 @@ mod tests { "id": "01FSHN9AG0S3ZJD8CXQ7F11KXN", "attributes": { "token": "token_used_revoked", + "valid": false, "usage_limit": 10, "times_used": 1, "created_at": "2022-01-16T14:40:00Z", @@ -454,6 +462,7 @@ mod tests { "id": "01FSHN9AG064K8BYZXSY5G511Z", "attributes": { "token": "token_expired", + "valid": false, "usage_limit": 5, "times_used": 0, "created_at": "2022-01-16T14:40:00Z", @@ -470,6 +479,7 @@ mod tests { "id": "01FSHN9AG09AVTNSQFMSR34AJC", "attributes": { "token": "token_revoked", + "valid": false, "usage_limit": 10, "times_used": 0, "created_at": "2022-01-16T14:40:00Z", @@ -486,6 +496,7 @@ mod tests { "id": "01FSHN9AG0MZAA6S4AF7CTV32E", "attributes": { "token": "token_unused", + "valid": true, "usage_limit": 10, "times_used": 0, "created_at": "2022-01-16T14:40:00Z", @@ -533,6 +544,7 @@ mod tests { "id": "01FSHN9AG09AVTNSQFMSR34AJC", "attributes": { "token": "token_revoked", + "valid": false, "usage_limit": 10, "times_used": 0, "created_at": "2022-01-16T14:40:00Z", @@ -549,6 +561,7 @@ mod tests { "id": "01FSHN9AG0S3ZJD8CXQ7F11KXN", "attributes": { "token": "token_used_revoked", + "valid": false, "usage_limit": 10, "times_used": 1, "created_at": "2022-01-16T14:40:00Z", @@ -588,6 +601,7 @@ mod tests { "id": "01FSHN9AG064K8BYZXSY5G511Z", "attributes": { "token": "token_expired", + "valid": false, "usage_limit": 5, "times_used": 0, "created_at": "2022-01-16T14:40:00Z", @@ -604,6 +618,7 @@ mod tests { "id": "01FSHN9AG07HNEZXNQM2KNBNF6", "attributes": { "token": "token_used", + "valid": true, "usage_limit": 10, "times_used": 1, "created_at": "2022-01-16T14:40:00Z", @@ -620,6 +635,7 @@ mod tests { "id": "01FSHN9AG0MZAA6S4AF7CTV32E", "attributes": { "token": "token_unused", + "valid": true, "usage_limit": 10, "times_used": 0, "created_at": "2022-01-16T14:40:00Z", @@ -667,6 +683,7 @@ mod tests { "id": "01FSHN9AG064K8BYZXSY5G511Z", "attributes": { "token": "token_expired", + "valid": false, "usage_limit": 5, "times_used": 0, "created_at": "2022-01-16T14:40:00Z", @@ -706,6 +723,7 @@ mod tests { "id": "01FSHN9AG07HNEZXNQM2KNBNF6", "attributes": { "token": "token_used", + "valid": true, "usage_limit": 10, "times_used": 1, "created_at": "2022-01-16T14:40:00Z", @@ -722,6 +740,7 @@ mod tests { "id": "01FSHN9AG09AVTNSQFMSR34AJC", "attributes": { "token": "token_revoked", + "valid": false, "usage_limit": 10, "times_used": 0, "created_at": "2022-01-16T14:40:00Z", @@ -738,6 +757,7 @@ mod tests { "id": "01FSHN9AG0MZAA6S4AF7CTV32E", "attributes": { "token": "token_unused", + "valid": true, "usage_limit": 10, "times_used": 0, "created_at": "2022-01-16T14:40:00Z", @@ -754,6 +774,7 @@ mod tests { "id": "01FSHN9AG0S3ZJD8CXQ7F11KXN", "attributes": { "token": "token_used_revoked", + "valid": false, "usage_limit": 10, "times_used": 1, "created_at": "2022-01-16T14:40:00Z", @@ -801,6 +822,7 @@ mod tests { "id": "01FSHN9AG07HNEZXNQM2KNBNF6", "attributes": { "token": "token_used", + "valid": true, "usage_limit": 10, "times_used": 1, "created_at": "2022-01-16T14:40:00Z", @@ -817,6 +839,7 @@ mod tests { "id": "01FSHN9AG0MZAA6S4AF7CTV32E", "attributes": { "token": "token_unused", + "valid": true, "usage_limit": 10, "times_used": 0, "created_at": "2022-01-16T14:40:00Z", @@ -856,6 +879,7 @@ mod tests { "id": "01FSHN9AG064K8BYZXSY5G511Z", "attributes": { "token": "token_expired", + "valid": false, "usage_limit": 5, "times_used": 0, "created_at": "2022-01-16T14:40:00Z", @@ -872,6 +896,7 @@ mod tests { "id": "01FSHN9AG09AVTNSQFMSR34AJC", "attributes": { "token": "token_revoked", + "valid": false, "usage_limit": 10, "times_used": 0, "created_at": "2022-01-16T14:40:00Z", @@ -888,6 +913,7 @@ mod tests { "id": "01FSHN9AG0S3ZJD8CXQ7F11KXN", "attributes": { "token": "token_used_revoked", + "valid": false, "usage_limit": 10, "times_used": 1, "created_at": "2022-01-16T14:40:00Z", @@ -937,6 +963,7 @@ mod tests { "id": "01FSHN9AG0S3ZJD8CXQ7F11KXN", "attributes": { "token": "token_used_revoked", + "valid": false, "usage_limit": 10, "times_used": 1, "created_at": "2022-01-16T14:40:00Z", @@ -984,6 +1011,7 @@ mod tests { "id": "01FSHN9AG064K8BYZXSY5G511Z", "attributes": { "token": "token_expired", + "valid": false, "usage_limit": 5, "times_used": 0, "created_at": "2022-01-16T14:40:00Z", @@ -1000,6 +1028,7 @@ mod tests { "id": "01FSHN9AG07HNEZXNQM2KNBNF6", "attributes": { "token": "token_used", + "valid": true, "usage_limit": 10, "times_used": 1, "created_at": "2022-01-16T14:40:00Z", @@ -1040,6 +1069,7 @@ mod tests { "id": "01FSHN9AG09AVTNSQFMSR34AJC", "attributes": { "token": "token_revoked", + "valid": false, "usage_limit": 10, "times_used": 0, "created_at": "2022-01-16T14:40:00Z", @@ -1056,6 +1086,7 @@ mod tests { "id": "01FSHN9AG0MZAA6S4AF7CTV32E", "attributes": { "token": "token_unused", + "valid": true, "usage_limit": 10, "times_used": 0, "created_at": "2022-01-16T14:40:00Z", @@ -1096,6 +1127,7 @@ mod tests { "id": "01FSHN9AG0S3ZJD8CXQ7F11KXN", "attributes": { "token": "token_used_revoked", + "valid": false, "usage_limit": 10, "times_used": 1, "created_at": "2022-01-16T14:40:00Z", diff --git a/crates/handlers/src/admin/v1/user_registration_tokens/revoke.rs b/crates/handlers/src/admin/v1/user_registration_tokens/revoke.rs index c06b445ff..e649cfef8 100644 --- a/crates/handlers/src/admin/v1/user_registration_tokens/revoke.rs +++ b/crates/handlers/src/admin/v1/user_registration_tokens/revoke.rs @@ -95,7 +95,7 @@ pub async fn handler( repo.save().await?; Ok(Json(SingleResponse::new( - UserRegistrationToken::from(token), + UserRegistrationToken::new(token, clock.now()), format!("/api/admin/v1/user-registration-tokens/{id}/revoke"), ))) } diff --git a/docs/api/spec.json b/docs/api/spec.json index 200e461fc..2fb0c3a85 100644 --- a/docs/api/spec.json +++ b/docs/api/spec.json @@ -2251,6 +2251,7 @@ "id": "01040G2081040G2081040G2081", "attributes": { "token": "abc123def456", + "valid": true, "usage_limit": 10, "times_used": 5, "created_at": "1970-01-01T00:00:00Z", @@ -2267,6 +2268,7 @@ "id": "02081040G2081040G2081040G2", "attributes": { "token": "xyz789abc012", + "valid": false, "usage_limit": null, "times_used": 0, "created_at": "1970-01-01T00:00:00Z", @@ -2321,6 +2323,7 @@ "id": "01040G2081040G2081040G2081", "attributes": { "token": "abc123def456", + "valid": true, "usage_limit": 10, "times_used": 5, "created_at": "1970-01-01T00:00:00Z", @@ -2375,6 +2378,7 @@ "id": "01040G2081040G2081040G2081", "attributes": { "token": "abc123def456", + "valid": true, "usage_limit": 10, "times_used": 5, "created_at": "1970-01-01T00:00:00Z", @@ -2447,6 +2451,7 @@ "id": "02081040G2081040G2081040G2", "attributes": { "token": "xyz789abc012", + "valid": false, "usage_limit": null, "times_used": 0, "created_at": "1970-01-01T00:00:00Z", @@ -4043,13 +4048,18 @@ "required": [ "created_at", "times_used", - "token" + "token", + "valid" ], "properties": { "token": { "description": "The token string", "type": "string" }, + "valid": { + "description": "Whether the token is valid", + "type": "boolean" + }, "usage_limit": { "description": "Maximum number of times this token can be used", "type": "integer",