mirror of
https://github.com/element-hq/matrix-authentication-service.git
synced 2026-10-15 11:11:05 +00:00
Add username and email columns to registration tokens
A registration token can now optionally carry the username and/or the email address the account it is used for has to be registered with. This only adds the columns and plumbs them through the storage layer and the CLI; nothing enforces them yet.
This commit is contained in:
@@ -510,7 +510,15 @@ impl Options {
|
||||
// Create the token
|
||||
let registration_token = repo
|
||||
.user_registration_token()
|
||||
.add(&mut rng, &clock, token_str, usage_limit, expires_at)
|
||||
.add(
|
||||
&mut rng,
|
||||
&clock,
|
||||
token_str,
|
||||
usage_limit,
|
||||
expires_at,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
repo.into_inner().commit().await?;
|
||||
|
||||
@@ -230,6 +230,8 @@ pub struct UserRegistrationPassword {
|
||||
pub struct UserRegistrationToken {
|
||||
pub id: Ulid,
|
||||
pub token: String,
|
||||
pub username: Option<String>,
|
||||
pub email: Option<String>,
|
||||
pub usage_limit: Option<u32>,
|
||||
pub times_used: u32,
|
||||
pub created_at: DateTime<Utc>,
|
||||
|
||||
@@ -28,7 +28,7 @@ use crate::{
|
||||
#[aide(output_with = "Json<ErrorResponse>")]
|
||||
pub enum RouteError {
|
||||
#[error("A registration token with the same token already exists")]
|
||||
Conflict(mas_data_model::UserRegistrationToken),
|
||||
Conflict(Box<mas_data_model::UserRegistrationToken>),
|
||||
|
||||
#[error(transparent)]
|
||||
Internal(Box<dyn std::error::Error + Send + Sync + 'static>),
|
||||
@@ -61,6 +61,14 @@ pub struct Request {
|
||||
|
||||
/// When the token expires. If not provided, the token never expires.
|
||||
expires_at: Option<DateTime<Utc>>,
|
||||
|
||||
/// A username to impose on the registering user. If set, the user cannot
|
||||
/// choose their own username.
|
||||
username: Option<String>,
|
||||
|
||||
/// An email address to impose on the registering user. If set, the user
|
||||
/// cannot choose their own email.
|
||||
email: Option<String>,
|
||||
}
|
||||
|
||||
pub fn doc(operation: TransformOperation) -> TransformOperation {
|
||||
@@ -92,7 +100,7 @@ pub async fn handler(
|
||||
// See if we have an existing token with the same token
|
||||
let existing_token = repo.user_registration_token().find_by_token(&token).await?;
|
||||
if let Some(existing_token) = existing_token {
|
||||
return Err(RouteError::Conflict(existing_token));
|
||||
return Err(RouteError::Conflict(Box::new(existing_token)));
|
||||
}
|
||||
|
||||
let registration_token = repo
|
||||
@@ -103,6 +111,8 @@ pub async fn handler(
|
||||
token,
|
||||
params.usage_limit,
|
||||
params.expires_at,
|
||||
params.username,
|
||||
params.email,
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -104,6 +104,8 @@ mod tests {
|
||||
"test_token_123".to_owned(),
|
||||
Some(5),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -206,6 +206,8 @@ mod tests {
|
||||
"token_unused".to_owned(),
|
||||
Some(10),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -219,6 +221,8 @@ mod tests {
|
||||
"token_used".to_owned(),
|
||||
Some(10),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -236,6 +240,8 @@ mod tests {
|
||||
"token_revoked".to_owned(),
|
||||
Some(10),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -253,6 +259,8 @@ mod tests {
|
||||
"token_used_revoked".to_owned(),
|
||||
Some(10),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -275,6 +283,8 @@ mod tests {
|
||||
"token_expired".to_owned(),
|
||||
Some(5),
|
||||
Some(expires_at),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -125,6 +125,8 @@ mod tests {
|
||||
"test_token_456".to_owned(),
|
||||
Some(5),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -162,6 +164,8 @@ mod tests {
|
||||
"test_token_789".to_owned(),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -125,6 +125,8 @@ mod tests {
|
||||
"test_token_456".to_owned(),
|
||||
Some(5),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -191,6 +193,8 @@ mod tests {
|
||||
"test_token_789".to_owned(),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -166,6 +166,8 @@ mod tests {
|
||||
"test_update_expiry".to_owned(),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -271,6 +273,8 @@ mod tests {
|
||||
"test_update_limit".to_owned(),
|
||||
Some(5),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -375,6 +379,8 @@ mod tests {
|
||||
"test_update_multiple".to_owned(),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -441,6 +447,8 @@ mod tests {
|
||||
"test_update_none".to_owned(),
|
||||
Some(5),
|
||||
Some(state.clock.now() + Duration::days(30)),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
+19
-7
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT user_registration_token_id,\n token,\n usage_limit,\n times_used,\n created_at,\n last_used_at,\n expires_at,\n revoked_at\n FROM user_registration_tokens\n WHERE token = $1\n ",
|
||||
"query": "\n SELECT user_registration_token_id,\n token,\n username,\n email,\n usage_limit,\n times_used,\n created_at,\n last_used_at,\n expires_at,\n revoked_at\n FROM user_registration_tokens\n WHERE token = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -15,31 +15,41 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "username",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "email",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "usage_limit",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"ordinal": 5,
|
||||
"name": "times_used",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"ordinal": 6,
|
||||
"name": "created_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"ordinal": 7,
|
||||
"name": "last_used_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"ordinal": 8,
|
||||
"name": "expires_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"ordinal": 9,
|
||||
"name": "revoked_at",
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
@@ -53,6 +63,8 @@
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
@@ -60,5 +72,5 @@
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "fca331753aeccddbad96d06fc9d066dcefebe978a7af477bb6b55faa1d31e9b1"
|
||||
"hash": "1ba7c2d8929e3c7d9af7afe92015d3a07eb4609827d97b3477b020059c8a2d4f"
|
||||
}
|
||||
Generated
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE user_registration_tokens\n SET username = $2\n WHERE user_registration_token_id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "42ae3db131c3237ecbcc57451a09b72a72bbbda53d0f03e20c1471bc120c492b"
|
||||
}
|
||||
+19
-7
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT user_registration_token_id,\n token,\n usage_limit,\n times_used,\n created_at,\n last_used_at,\n expires_at,\n revoked_at\n FROM user_registration_tokens\n WHERE user_registration_token_id = $1\n ",
|
||||
"query": "\n SELECT user_registration_token_id,\n token,\n username,\n email,\n usage_limit,\n times_used,\n created_at,\n last_used_at,\n expires_at,\n revoked_at\n FROM user_registration_tokens\n WHERE user_registration_token_id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -15,31 +15,41 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "username",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "email",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "usage_limit",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"ordinal": 5,
|
||||
"name": "times_used",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"ordinal": 6,
|
||||
"name": "created_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"ordinal": 7,
|
||||
"name": "last_used_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"ordinal": 8,
|
||||
"name": "expires_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"ordinal": 9,
|
||||
"name": "revoked_at",
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
@@ -53,6 +63,8 @@
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
@@ -60,5 +72,5 @@
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "d0355d4e98bec6120f17d8cf81ac8c30ed19e9cebd0c8e7c7918b1c3ca0e3cba"
|
||||
"hash": "4cebc83f7699b61dd5704b82f25553eff02d3fb708071a60d2d5f55a35945218"
|
||||
}
|
||||
+5
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO user_registration_tokens\n (user_registration_token_id, token, usage_limit, created_at, expires_at)\n VALUES ($1, $2, $3, $4, $5)\n ",
|
||||
"query": "\n INSERT INTO user_registration_tokens\n (user_registration_token_id, token, usage_limit, created_at, expires_at, username, email)\n VALUES ($1, $2, $3, $4, $5, $6, $7)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -9,10 +9,12 @@
|
||||
"Text",
|
||||
"Int4",
|
||||
"Timestamptz",
|
||||
"Timestamptz"
|
||||
"Timestamptz",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "89edaec8661e435c3b71bb9b995cd711eb78a4d39608e897432d6124cd135938"
|
||||
"hash": "8e001a260d28c537f525e35f70e5cc4ea771124820e2f6f8cfc5b958ab21eecc"
|
||||
}
|
||||
Generated
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE user_registration_tokens\n SET email = $2\n WHERE user_registration_token_id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "e73b3604e554d17a8d2672c1f32b80274c6062889c2ba650049694bb4f7b2aa7"
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
-- Copyright 2026 Element Creations Ltd.
|
||||
--
|
||||
-- SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
-- Please see LICENSE files in the repository root for full details.
|
||||
|
||||
-- Add optional username and email columns to registration tokens,
|
||||
-- allowing admins to provision tokens that pre-fill these fields.
|
||||
ALTER TABLE "user_registration_tokens"
|
||||
ADD COLUMN "username" TEXT,
|
||||
ADD COLUMN "email" TEXT;
|
||||
@@ -210,6 +210,8 @@ pub enum UserRegistrationTokens {
|
||||
Table,
|
||||
UserRegistrationTokenId,
|
||||
Token,
|
||||
Username,
|
||||
Email,
|
||||
UsageLimit,
|
||||
TimesUsed,
|
||||
CreatedAt,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2026 Element Creations Ltd.
|
||||
// Copyright 2025, 2026 Element Creations Ltd.
|
||||
// Copyright 2025 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
@@ -47,6 +47,8 @@ impl<'c> PgUserRegistrationTokenRepository<'c> {
|
||||
struct UserRegistrationTokenLookup {
|
||||
user_registration_token_id: Uuid,
|
||||
token: String,
|
||||
username: Option<String>,
|
||||
email: Option<String>,
|
||||
usage_limit: Option<i32>,
|
||||
times_used: i32,
|
||||
created_at: DateTime<Utc>,
|
||||
@@ -211,6 +213,8 @@ impl TryFrom<UserRegistrationTokenLookup> for UserRegistrationToken {
|
||||
Ok(UserRegistrationToken {
|
||||
id,
|
||||
token: res.token,
|
||||
username: res.username,
|
||||
email: res.email,
|
||||
usage_limit,
|
||||
times_used,
|
||||
created_at: res.created_at,
|
||||
@@ -250,6 +254,17 @@ impl UserRegistrationTokenRepository for PgUserRegistrationTokenRepository<'_> {
|
||||
Expr::col((UserRegistrationTokens::Table, UserRegistrationTokens::Token)),
|
||||
UserRegistrationTokenLookupIden::Token,
|
||||
)
|
||||
.expr_as(
|
||||
Expr::col((
|
||||
UserRegistrationTokens::Table,
|
||||
UserRegistrationTokens::Username,
|
||||
)),
|
||||
UserRegistrationTokenLookupIden::Username,
|
||||
)
|
||||
.expr_as(
|
||||
Expr::col((UserRegistrationTokens::Table, UserRegistrationTokens::Email)),
|
||||
UserRegistrationTokenLookupIden::Email,
|
||||
)
|
||||
.expr_as(
|
||||
Expr::col((
|
||||
UserRegistrationTokens::Table,
|
||||
@@ -362,6 +377,8 @@ impl UserRegistrationTokenRepository for PgUserRegistrationTokenRepository<'_> {
|
||||
r#"
|
||||
SELECT user_registration_token_id,
|
||||
token,
|
||||
username,
|
||||
email,
|
||||
usage_limit,
|
||||
times_used,
|
||||
created_at,
|
||||
@@ -402,6 +419,8 @@ impl UserRegistrationTokenRepository for PgUserRegistrationTokenRepository<'_> {
|
||||
r#"
|
||||
SELECT user_registration_token_id,
|
||||
token,
|
||||
username,
|
||||
email,
|
||||
usage_limit,
|
||||
times_used,
|
||||
created_at,
|
||||
@@ -440,6 +459,8 @@ impl UserRegistrationTokenRepository for PgUserRegistrationTokenRepository<'_> {
|
||||
token: String,
|
||||
usage_limit: Option<u32>,
|
||||
expires_at: Option<DateTime<Utc>>,
|
||||
username: Option<String>,
|
||||
email: Option<String>,
|
||||
) -> Result<UserRegistrationToken, Self::Error> {
|
||||
let created_at = clock.now();
|
||||
let id = Ulid::from_datetime_with_rng(created_at, rng);
|
||||
@@ -452,14 +473,16 @@ impl UserRegistrationTokenRepository for PgUserRegistrationTokenRepository<'_> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO user_registration_tokens
|
||||
(user_registration_token_id, token, usage_limit, created_at, expires_at)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
(user_registration_token_id, token, usage_limit, created_at, expires_at, username, email)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
"#,
|
||||
Uuid::from(id),
|
||||
&token,
|
||||
usage_limit_i32,
|
||||
created_at,
|
||||
expires_at,
|
||||
username.as_deref(),
|
||||
email.as_deref(),
|
||||
)
|
||||
.traced()
|
||||
.execute(&mut *self.conn)
|
||||
@@ -468,6 +491,8 @@ impl UserRegistrationTokenRepository for PgUserRegistrationTokenRepository<'_> {
|
||||
Ok(UserRegistrationToken {
|
||||
id,
|
||||
token,
|
||||
username,
|
||||
email,
|
||||
usage_limit,
|
||||
times_used: 0,
|
||||
created_at,
|
||||
@@ -657,6 +682,74 @@ impl UserRegistrationTokenRepository for PgUserRegistrationTokenRepository<'_> {
|
||||
|
||||
Ok(token)
|
||||
}
|
||||
|
||||
#[tracing::instrument(
|
||||
name = "db.user_registration_token.set_username",
|
||||
skip_all,
|
||||
fields(
|
||||
db.query.text,
|
||||
user_registration_token.id = %token.id,
|
||||
),
|
||||
err,
|
||||
)]
|
||||
async fn set_username(
|
||||
&mut self,
|
||||
mut token: UserRegistrationToken,
|
||||
username: Option<String>,
|
||||
) -> Result<UserRegistrationToken, Self::Error> {
|
||||
let res = sqlx::query!(
|
||||
r#"
|
||||
UPDATE user_registration_tokens
|
||||
SET username = $2
|
||||
WHERE user_registration_token_id = $1
|
||||
"#,
|
||||
Uuid::from(token.id),
|
||||
username.as_deref(),
|
||||
)
|
||||
.traced()
|
||||
.execute(&mut *self.conn)
|
||||
.await?;
|
||||
|
||||
DatabaseError::ensure_affected_rows(&res, 1)?;
|
||||
|
||||
token.username = username;
|
||||
|
||||
Ok(token)
|
||||
}
|
||||
|
||||
#[tracing::instrument(
|
||||
name = "db.user_registration_token.set_email",
|
||||
skip_all,
|
||||
fields(
|
||||
db.query.text,
|
||||
user_registration_token.id = %token.id,
|
||||
),
|
||||
err,
|
||||
)]
|
||||
async fn set_email(
|
||||
&mut self,
|
||||
mut token: UserRegistrationToken,
|
||||
email: Option<String>,
|
||||
) -> Result<UserRegistrationToken, Self::Error> {
|
||||
let res = sqlx::query!(
|
||||
r#"
|
||||
UPDATE user_registration_tokens
|
||||
SET email = $2
|
||||
WHERE user_registration_token_id = $1
|
||||
"#,
|
||||
Uuid::from(token.id),
|
||||
email.as_deref(),
|
||||
)
|
||||
.traced()
|
||||
.execute(&mut *self.conn)
|
||||
.await?;
|
||||
|
||||
DatabaseError::ensure_affected_rows(&res, 1)?;
|
||||
|
||||
token.email = email;
|
||||
|
||||
Ok(token)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -680,7 +773,15 @@ mod tests {
|
||||
// Create a token
|
||||
let token = repo
|
||||
.user_registration_token()
|
||||
.add(&mut rng, &clock, "test_token".to_owned(), None, None)
|
||||
.add(
|
||||
&mut rng,
|
||||
&clock,
|
||||
"test_token".to_owned(),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -725,7 +826,15 @@ mod tests {
|
||||
// Create a token without expiry
|
||||
let token = repo
|
||||
.user_registration_token()
|
||||
.add(&mut rng, &clock, "test_token_expiry".to_owned(), None, None)
|
||||
.add(
|
||||
&mut rng,
|
||||
&clock,
|
||||
"test_token_expiry".to_owned(),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -764,7 +873,15 @@ mod tests {
|
||||
// Create a token without usage limit
|
||||
let token = repo
|
||||
.user_registration_token()
|
||||
.add(&mut rng, &clock, "test_token_limit".to_owned(), None, None)
|
||||
.add(
|
||||
&mut rng,
|
||||
&clock,
|
||||
"test_token_limit".to_owned(),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -813,14 +930,30 @@ mod tests {
|
||||
// 1. A regular token
|
||||
let _token1 = repo
|
||||
.user_registration_token()
|
||||
.add(&mut rng, &clock, "token1".to_owned(), None, None)
|
||||
.add(
|
||||
&mut rng,
|
||||
&clock,
|
||||
"token1".to_owned(),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// 2. A token that has been used
|
||||
let token2 = repo
|
||||
.user_registration_token()
|
||||
.add(&mut rng, &clock, "token2".to_owned(), None, None)
|
||||
.add(
|
||||
&mut rng,
|
||||
&clock,
|
||||
"token2".to_owned(),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let token2 = repo
|
||||
@@ -833,14 +966,30 @@ mod tests {
|
||||
let past_time = clock.now() - Duration::days(1);
|
||||
let token3 = repo
|
||||
.user_registration_token()
|
||||
.add(&mut rng, &clock, "token3".to_owned(), None, Some(past_time))
|
||||
.add(
|
||||
&mut rng,
|
||||
&clock,
|
||||
"token3".to_owned(),
|
||||
None,
|
||||
Some(past_time),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// 4. A token that is revoked
|
||||
let token4 = repo
|
||||
.user_registration_token()
|
||||
.add(&mut rng, &clock, "token4".to_owned(), None, None)
|
||||
.add(
|
||||
&mut rng,
|
||||
&clock,
|
||||
"token4".to_owned(),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let token4 = repo
|
||||
|
||||
@@ -150,10 +150,13 @@ pub trait UserRegistrationTokenRepository: Send + Sync {
|
||||
/// * `token`: The token string
|
||||
/// * `usage_limit`: Optional limit on how many times the token can be used
|
||||
/// * `expires_at`: Optional expiration time for the token
|
||||
/// * `username`: Optional username to impose on the registering user
|
||||
/// * `email`: Optional email to impose on the registering user
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
async fn add(
|
||||
&mut self,
|
||||
rng: &mut (dyn Rng + Send),
|
||||
@@ -161,6 +164,8 @@ pub trait UserRegistrationTokenRepository: Send + Sync {
|
||||
token: String,
|
||||
usage_limit: Option<u32>,
|
||||
expires_at: Option<DateTime<Utc>>,
|
||||
username: Option<String>,
|
||||
email: Option<String>,
|
||||
) -> Result<UserRegistrationToken, Self::Error>;
|
||||
|
||||
/// Increment the usage count of a [`UserRegistrationToken`]
|
||||
@@ -244,6 +249,38 @@ pub trait UserRegistrationTokenRepository: Send + Sync {
|
||||
usage_limit: Option<u32>,
|
||||
) -> Result<UserRegistrationToken, Self::Error>;
|
||||
|
||||
/// Set or clear the username imposed by this [`UserRegistrationToken`]
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `token`: The [`UserRegistrationToken`] to update
|
||||
/// * `username`: The new username, or `None` to clear it
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn set_username(
|
||||
&mut self,
|
||||
token: UserRegistrationToken,
|
||||
username: Option<String>,
|
||||
) -> Result<UserRegistrationToken, Self::Error>;
|
||||
|
||||
/// Set or clear the email imposed by this [`UserRegistrationToken`]
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `token`: The [`UserRegistrationToken`] to update
|
||||
/// * `email`: The new email, or `None` to clear it
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn set_email(
|
||||
&mut self,
|
||||
token: UserRegistrationToken,
|
||||
email: Option<String>,
|
||||
) -> Result<UserRegistrationToken, Self::Error>;
|
||||
|
||||
/// List [`UserRegistrationToken`]s based on the provided filter
|
||||
///
|
||||
/// Returns a list of matching [`UserRegistrationToken`]s
|
||||
@@ -286,6 +323,8 @@ repository_impl!(UserRegistrationTokenRepository:
|
||||
token: String,
|
||||
usage_limit: Option<u32>,
|
||||
expires_at: Option<DateTime<Utc>>,
|
||||
username: Option<String>,
|
||||
email: Option<String>,
|
||||
) -> Result<UserRegistrationToken, Self::Error>;
|
||||
async fn use_token(
|
||||
&mut self,
|
||||
@@ -311,6 +350,16 @@ repository_impl!(UserRegistrationTokenRepository:
|
||||
token: UserRegistrationToken,
|
||||
usage_limit: Option<u32>,
|
||||
) -> Result<UserRegistrationToken, Self::Error>;
|
||||
async fn set_username(
|
||||
&mut self,
|
||||
token: UserRegistrationToken,
|
||||
username: Option<String>,
|
||||
) -> Result<UserRegistrationToken, Self::Error>;
|
||||
async fn set_email(
|
||||
&mut self,
|
||||
token: UserRegistrationToken,
|
||||
email: Option<String>,
|
||||
) -> Result<UserRegistrationToken, Self::Error>;
|
||||
async fn list(
|
||||
&mut self,
|
||||
filter: UserRegistrationTokenFilter,
|
||||
|
||||
@@ -7389,6 +7389,20 @@
|
||||
"null"
|
||||
],
|
||||
"format": "date-time"
|
||||
},
|
||||
"username": {
|
||||
"description": "A username to impose on the registering user. If set, the user cannot\nchoose their own username.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"email": {
|
||||
"description": "An email address to impose on the registering user. If set, the user\ncannot choose their own email.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user