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,29 @@ 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 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+
98125 /// Trigger a provisioning job for all users
99126 ProvisionAllUsers ,
100127
@@ -330,6 +357,46 @@ impl Options {
330357 Ok ( ExitCode :: SUCCESS )
331358 }
332359
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+
333400 SC :: ProvisionAllUsers => {
334401 let _span = info_span ! ( "cli.manage.provision_all_users" ) . entered ( ) ;
335402 let database_config = DatabaseConfig :: extract_or_default ( figment) ?;
0 commit comments