Add whether the registration is valid or not in the admin API

This commit is contained in:
Quentin Gliech
2025-06-03 14:29:59 +02:00
parent 3821c6550d
commit 0e94e6c2bb
6 changed files with 71 additions and 19 deletions

View File

@@ -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<u32>,
@@ -631,10 +634,11 @@ pub struct UserRegistrationToken {
revoked_at: Option<DateTime<Utc>>,
}
impl From<mas_data_model::UserRegistrationToken> for UserRegistrationToken {
fn from(token: mas_data_model::UserRegistrationToken) -> Self {
impl UserRegistrationToken {
pub fn new(token: mas_data_model::UserRegistrationToken, now: DateTime<Utc>) -> 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(),

View File

@@ -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<Request>,
) -> Result<(StatusCode, Json<SingleResponse<UserRegistrationToken>>), 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",

View File

@@ -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<Json<SingleResponse<UserRegistrationToken>>, 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")]

View File

@@ -135,7 +135,8 @@ pub async fn handler(
params: FilterParams,
) -> Result<Json<PaginatedResponse<UserRegistrationToken>>, 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",

View File

@@ -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"),
)))
}

View File

@@ -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",