77use std:: { collections:: BTreeMap , process:: ExitCode } ;
88
99use anyhow:: Context ;
10+ use chrono:: Duration ;
1011use clap:: { ArgAction , CommandFactory , Parser } ;
1112use console:: { Alignment , Style , Term , pad_str, style} ;
1213use dialoguer:: { Confirm , FuzzySelect , Input , Password , theme:: ColorfulTheme } ;
@@ -28,7 +29,10 @@ use mas_storage::{
2829 user:: { BrowserSessionFilter , UserEmailRepository , UserPasswordRepository , UserRepository } ,
2930} ;
3031use mas_storage_pg:: { DatabaseError , PgRepository } ;
31- use rand:: { RngCore , SeedableRng } ;
32+ use rand:: {
33+ RngCore , SeedableRng ,
34+ distributions:: { Alphanumeric , DistString as _} ,
35+ } ;
3236use sqlx:: { Acquire , types:: Uuid } ;
3337use tracing:: { error, info, info_span, warn} ;
3438use zeroize:: Zeroizing ;
@@ -95,6 +99,24 @@ enum Subcommand {
9599 admin : bool ,
96100 } ,
97101
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 an unlimited number of times.
111+ #[ arg( long) ]
112+ usage_limit : Option < u32 > ,
113+
114+ /// Time in seconds after which the token expires.
115+ /// If not provided, the token never expires.
116+ #[ arg( long) ]
117+ expires_in : Option < u32 > ,
118+ } ,
119+
98120 /// Trigger a provisioning job for all users
99121 ProvisionAllUsers ,
100122
@@ -330,6 +352,38 @@ impl Options {
330352 Ok ( ExitCode :: SUCCESS )
331353 }
332354
355+ SC :: IssueUserRegistrationToken {
356+ token,
357+ usage_limit,
358+ expires_in,
359+ } => {
360+ let _span = info_span ! ( "cli.manage.add_user_registration_token" ) . entered ( ) ;
361+
362+ let database_config = DatabaseConfig :: extract_or_default ( figment) ?;
363+ let mut conn = database_connection_from_config ( & database_config) . await ?;
364+ let txn = conn. begin ( ) . await ?;
365+ let mut repo = PgRepository :: from_conn ( txn) ;
366+
367+ // Calculate expiration time if provided
368+ let expires_at =
369+ expires_in. map ( |seconds| clock. now ( ) + Duration :: seconds ( seconds. into ( ) ) ) ;
370+
371+ // Generate a token if not provided
372+ let token_str = token. unwrap_or_else ( || Alphanumeric . sample_string ( & mut rng, 12 ) ) ;
373+
374+ // Create the token
375+ let registration_token = repo
376+ . user_registration_token ( )
377+ . add ( & mut rng, & clock, token_str, usage_limit, expires_at)
378+ . await ?;
379+
380+ repo. into_inner ( ) . commit ( ) . await ?;
381+
382+ info ! ( %registration_token. id, "Created user registration token: {}" , registration_token. token) ;
383+
384+ Ok ( ExitCode :: SUCCESS )
385+ }
386+
333387 SC :: ProvisionAllUsers => {
334388 let _span = info_span ! ( "cli.manage.provision_all_users" ) . entered ( ) ;
335389 let database_config = DatabaseConfig :: extract_or_default ( figment) ?;
0 commit comments