Skip to content

Commit ccb971d

Browse files
committed
Data model and repository for user registration tokens
1 parent 3207d23 commit ccb971d

19 files changed

+853
-19
lines changed

crates/data-model/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,6 @@ pub use self::{
5050
users::{
5151
Authentication, AuthenticationMethod, BrowserSession, Password, User, UserEmail,
5252
UserEmailAuthentication, UserEmailAuthenticationCode, UserRecoverySession,
53-
UserRecoveryTicket, UserRegistration, UserRegistrationPassword,
53+
UserRecoveryTicket, UserRegistration, UserRegistrationPassword, UserRegistrationToken,
5454
},
5555
};

crates/data-model/src/users.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,13 +201,60 @@ pub struct UserRegistrationPassword {
201201
pub version: u16,
202202
}
203203

204+
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
205+
pub struct UserRegistrationToken {
206+
pub id: Ulid,
207+
pub token: String,
208+
pub usage_limit: Option<u32>,
209+
pub times_used: u32,
210+
pub created_at: DateTime<Utc>,
211+
pub last_used_at: Option<DateTime<Utc>>,
212+
pub expires_at: Option<DateTime<Utc>>,
213+
pub revoked_at: Option<DateTime<Utc>>,
214+
}
215+
216+
impl UserRegistrationToken {
217+
/// Returns `true` if the token is still valid and can be used
218+
#[must_use]
219+
pub fn is_valid(&self, now: DateTime<Utc>) -> bool {
220+
// Check if revoked
221+
if self.revoked_at.is_some() {
222+
return false;
223+
}
224+
225+
// Check if expired
226+
if let Some(expires_at) = self.expires_at {
227+
if now >= expires_at {
228+
return false;
229+
}
230+
}
231+
232+
// Check if usage limit exceeded
233+
if let Some(usage_limit) = self.usage_limit {
234+
if self.times_used >= usage_limit {
235+
return false;
236+
}
237+
}
238+
239+
true
240+
}
241+
242+
/// Returns `true` if the token can still be used (not expired and under
243+
/// usage limit)
244+
#[must_use]
245+
pub fn can_be_used(&self, now: DateTime<Utc>) -> bool {
246+
self.is_valid(now)
247+
}
248+
}
249+
204250
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
205251
pub struct UserRegistration {
206252
pub id: Ulid,
207253
pub username: String,
208254
pub display_name: Option<String>,
209255
pub terms_url: Option<Url>,
210256
pub email_authentication_id: Option<Ulid>,
257+
pub user_registration_token_id: Option<Ulid>,
211258
pub password: Option<UserRegistrationPassword>,
212259
pub post_auth_action: Option<serde_json::Value>,
213260
pub ip_address: Option<IpAddr>,

crates/storage-pg/.sqlx/query-5133f9c5ba06201433be4ec784034d222975d084d0a9ebe7f1b6b865ab2e09ef.json

Lines changed: 23 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 11 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/storage-pg/.sqlx/query-860e01cd660b450439d63c5ee31ade59f478b0b096b4bc90c89fb9c26b467dd2.json

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/storage-pg/.sqlx/query-89edaec8661e435c3b71bb9b995cd711eb78a4d39608e897432d6124cd135938.json

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/storage-pg/.sqlx/query-b3568613352efae1125a88565d886157d96866f7ef9b09b03a45ba4322664bd0.json

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/storage-pg/.sqlx/query-d0355d4e98bec6120f17d8cf81ac8c30ed19e9cebd0c8e7c7918b1c3ca0e3cba.json

Lines changed: 64 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/storage-pg/.sqlx/query-fca331753aeccddbad96d06fc9d066dcefebe978a7af477bb6b55faa1d31e9b1.json

Lines changed: 64 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
-- Copyright 2025 New Vector Ltd.
2+
--
3+
-- SPDX-License-Identifier: AGPL-3.0-only
4+
-- Please see LICENSE in the repository root for full details.
5+
6+
-- Add a table for storing user registration tokens
7+
CREATE TABLE "user_registration_tokens" (
8+
"user_registration_token_id" UUID PRIMARY KEY,
9+
10+
-- The token string that users need to provide during registration
11+
"token" TEXT NOT NULL UNIQUE,
12+
13+
-- Optional limit on how many times this token can be used
14+
"usage_limit" INTEGER,
15+
16+
-- How many times this token has been used
17+
"times_used" INTEGER NOT NULL DEFAULT 0,
18+
19+
-- When the token was created
20+
"created_at" TIMESTAMP WITH TIME ZONE NOT NULL,
21+
22+
-- When the token was last used
23+
"last_used_at" TIMESTAMP WITH TIME ZONE,
24+
25+
-- Optional expiration time for the token
26+
"expires_at" TIMESTAMP WITH TIME ZONE,
27+
28+
-- When the token was revoked
29+
"revoked_at" TIMESTAMP WITH TIME ZONE
30+
);
31+
32+
-- Create a few indices on the table, as we use those for filtering
33+
-- They are safe to create non-concurrently, as the table is empty at this point
34+
CREATE INDEX "user_registration_tokens_usage_limit_idx"
35+
ON "user_registration_tokens" ("usage_limit");
36+
37+
CREATE INDEX "user_registration_tokens_times_used_idx"
38+
ON "user_registration_tokens" ("times_used");
39+
40+
CREATE INDEX "user_registration_tokens_created_at_idx"
41+
ON "user_registration_tokens" ("created_at");
42+
43+
CREATE INDEX "user_registration_tokens_last_used_at_idx"
44+
ON "user_registration_tokens" ("last_used_at");
45+
46+
CREATE INDEX "user_registration_tokens_expires_at_idx"
47+
ON "user_registration_tokens" ("expires_at");
48+
49+
CREATE INDEX "user_registration_tokens_revoked_at_idx"
50+
ON "user_registration_tokens" ("revoked_at");
51+
52+
-- Add foreign key reference to registration tokens in user registrations
53+
-- A second migration will add the index for this foreign key
54+
ALTER TABLE "user_registrations"
55+
ADD COLUMN "user_registration_token_id" UUID
56+
REFERENCES "user_registration_tokens" ("user_registration_token_id")
57+
ON DELETE SET NULL;

0 commit comments

Comments
 (0)