diff --git a/Cargo.lock b/Cargo.lock index 4222007d9..96508d285 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3141,6 +3141,7 @@ dependencies = [ "axum", "bytes", "camino", + "chrono", "clap", "console", "dialoguer", diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 55def8cea..49dc86737 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -18,6 +18,7 @@ anyhow.workspace = true axum.workspace = true bytes.workspace = true camino.workspace = true +chrono.workspace = true clap.workspace = true console = "0.15.11" dialoguer = { version = "0.11.0", default-features = false, features = [ diff --git a/crates/cli/src/commands/manage.rs b/crates/cli/src/commands/manage.rs index b52c56162..d2f13f0c5 100644 --- a/crates/cli/src/commands/manage.rs +++ b/crates/cli/src/commands/manage.rs @@ -7,6 +7,7 @@ use std::{collections::BTreeMap, process::ExitCode}; use anyhow::Context; +use chrono::Duration; use clap::{ArgAction, CommandFactory, Parser}; use console::{Alignment, Style, Term, pad_str, style}; use dialoguer::{Confirm, FuzzySelect, Input, Password, theme::ColorfulTheme}; @@ -28,7 +29,10 @@ use mas_storage::{ user::{BrowserSessionFilter, UserEmailRepository, UserPasswordRepository, UserRepository}, }; use mas_storage_pg::{DatabaseError, PgRepository}; -use rand::{RngCore, SeedableRng}; +use rand::{ + RngCore, SeedableRng, + distributions::{Alphanumeric, DistString as _}, +}; use sqlx::{Acquire, types::Uuid}; use tracing::{error, info, info_span, warn}; use zeroize::Zeroizing; @@ -95,6 +99,29 @@ enum Subcommand { admin: bool, }, + /// Create a new user registration token + IssueUserRegistrationToken { + /// Specific token string to use. If not provided, a random token will + /// be generated. + #[arg(long)] + token: Option, + + /// Maximum number of times this token can be used. + /// If not provided, the token can be used only once, unless the + /// `--unlimited` flag is set. + #[arg(long, group = "token-usage-limit")] + usage_limit: Option, + + /// Allow the token to be used an unlimited number of times. + #[arg(long, action = ArgAction::SetTrue, group = "token-usage-limit")] + unlimited: bool, + + /// Time in seconds after which the token expires. + /// If not provided, the token never expires. + #[arg(long)] + expires_in: Option, + }, + /// Trigger a provisioning job for all users ProvisionAllUsers, @@ -330,6 +357,46 @@ impl Options { Ok(ExitCode::SUCCESS) } + SC::IssueUserRegistrationToken { + token, + usage_limit, + unlimited, + expires_in, + } => { + let _span = info_span!("cli.manage.add_user_registration_token").entered(); + + let usage_limit = match (usage_limit, unlimited) { + (Some(usage_limit), false) => Some(usage_limit), + (None, false) => Some(1), + (None, true) => None, + (Some(_), true) => unreachable!(), // This should be handled by the clap group + }; + + let database_config = DatabaseConfig::extract_or_default(figment)?; + let mut conn = database_connection_from_config(&database_config).await?; + let txn = conn.begin().await?; + let mut repo = PgRepository::from_conn(txn); + + // Calculate expiration time if provided + let expires_at = + expires_in.map(|seconds| clock.now() + Duration::seconds(seconds.into())); + + // Generate a token if not provided + let token_str = token.unwrap_or_else(|| Alphanumeric.sample_string(&mut rng, 12)); + + // Create the token + let registration_token = repo + .user_registration_token() + .add(&mut rng, &clock, token_str, usage_limit, expires_at) + .await?; + + repo.into_inner().commit().await?; + + info!(%registration_token.id, "Created user registration token: {}", registration_token.token); + + Ok(ExitCode::SUCCESS) + } + SC::ProvisionAllUsers => { let _span = info_span!("cli.manage.provision_all_users").entered(); let database_config = DatabaseConfig::extract_or_default(figment)?; diff --git a/docs/reference/cli/manage.md b/docs/reference/cli/manage.md index 4b798a0d8..0f14f1773 100644 --- a/docs/reference/cli/manage.md +++ b/docs/reference/cli/manage.md @@ -46,6 +46,19 @@ Options: $ mas-cli manage issue-compatibility-token --device-id --yes-i-want-to-grant-synapse-admin-privileges ``` +## `manage issue-user-registration-token` + +Create a new user registration token. + +Options: +- `--token `: Specific token string to use. If not provided, a random token will be generated. +- `--usage-limit `: Limit the number of times the token can be used. If not provided, the token can be used an unlimited number of times. +- `--expires-in `: Time in seconds after which the token expires. If not provided, the token never expires. + +``` +$ mas-cli manage issue-user-registration-token --token --usage-limit --expires-in +``` + ## `manage provision-all-users` Trigger a provisioning job for all users. @@ -101,4 +114,4 @@ Options: ``` $ mas-cli manage register-user -``` \ No newline at end of file +```