7
7
use std:: { collections:: BTreeMap , process:: ExitCode } ;
8
8
9
9
use anyhow:: Context ;
10
+ use chrono:: Duration ;
10
11
use clap:: { ArgAction , CommandFactory , Parser } ;
11
12
use console:: { Alignment , Style , Term , pad_str, style} ;
12
13
use dialoguer:: { Confirm , FuzzySelect , Input , Password , theme:: ColorfulTheme } ;
@@ -28,7 +29,10 @@ use mas_storage::{
28
29
user:: { BrowserSessionFilter , UserEmailRepository , UserPasswordRepository , UserRepository } ,
29
30
} ;
30
31
use mas_storage_pg:: { DatabaseError , PgRepository } ;
31
- use rand:: { RngCore , SeedableRng } ;
32
+ use rand:: {
33
+ RngCore , SeedableRng ,
34
+ distributions:: { Alphanumeric , DistString as _} ,
35
+ } ;
32
36
use sqlx:: { Acquire , types:: Uuid } ;
33
37
use tracing:: { error, info, info_span, warn} ;
34
38
use zeroize:: Zeroizing ;
@@ -95,6 +99,29 @@ enum Subcommand {
95
99
admin : bool ,
96
100
} ,
97
101
102
+ /// Create a new user registration token
103
+ IssueUserRegistrationToken {
104
+ /// Specific token string to use. If not provided, a random token will
105
+ /// be generated.
106
+ #[ arg( long) ]
107
+ token : Option < String > ,
108
+
109
+ /// Maximum number of times this token can be used.
110
+ /// If not provided, the token can be used only once, unless the
111
+ /// `--unlimited` flag is set.
112
+ #[ arg( long, group = "token-usage-limit" ) ]
113
+ usage_limit : Option < u32 > ,
114
+
115
+ /// Allow the token to be used an unlimited number of times.
116
+ #[ arg( long, action = ArgAction :: SetTrue , group = "token-usage-limit" ) ]
117
+ unlimited : bool ,
118
+
119
+ /// Time in seconds after which the token expires.
120
+ /// If not provided, the token never expires.
121
+ #[ arg( long) ]
122
+ expires_in : Option < u32 > ,
123
+ } ,
124
+
98
125
/// Trigger a provisioning job for all users
99
126
ProvisionAllUsers ,
100
127
@@ -330,6 +357,46 @@ impl Options {
330
357
Ok ( ExitCode :: SUCCESS )
331
358
}
332
359
360
+ SC :: IssueUserRegistrationToken {
361
+ token,
362
+ usage_limit,
363
+ unlimited,
364
+ expires_in,
365
+ } => {
366
+ let _span = info_span ! ( "cli.manage.add_user_registration_token" ) . entered ( ) ;
367
+
368
+ let usage_limit = match ( usage_limit, unlimited) {
369
+ ( Some ( usage_limit) , false ) => Some ( usage_limit) ,
370
+ ( None , false ) => Some ( 1 ) ,
371
+ ( None , true ) => None ,
372
+ ( Some ( _) , true ) => unreachable ! ( ) , // This should be handled by the clap group
373
+ } ;
374
+
375
+ let database_config = DatabaseConfig :: extract_or_default ( figment) ?;
376
+ let mut conn = database_connection_from_config ( & database_config) . await ?;
377
+ let txn = conn. begin ( ) . await ?;
378
+ let mut repo = PgRepository :: from_conn ( txn) ;
379
+
380
+ // Calculate expiration time if provided
381
+ let expires_at =
382
+ expires_in. map ( |seconds| clock. now ( ) + Duration :: seconds ( seconds. into ( ) ) ) ;
383
+
384
+ // Generate a token if not provided
385
+ let token_str = token. unwrap_or_else ( || Alphanumeric . sample_string ( & mut rng, 12 ) ) ;
386
+
387
+ // Create the token
388
+ let registration_token = repo
389
+ . user_registration_token ( )
390
+ . add ( & mut rng, & clock, token_str, usage_limit, expires_at)
391
+ . await ?;
392
+
393
+ repo. into_inner ( ) . commit ( ) . await ?;
394
+
395
+ info ! ( %registration_token. id, "Created user registration token: {}" , registration_token. token) ;
396
+
397
+ Ok ( ExitCode :: SUCCESS )
398
+ }
399
+
333
400
SC :: ProvisionAllUsers => {
334
401
let _span = info_span ! ( "cli.manage.provision_all_users" ) . entered ( ) ;
335
402
let database_config = DatabaseConfig :: extract_or_default ( figment) ?;
0 commit comments