diff --git a/.env.template b/.env.template index 4cd3ed2..e964873 100644 --- a/.env.template +++ b/.env.template @@ -118,6 +118,14 @@ ## even if SIGNUPS_ALLOWED is set to false # SIGNUPS_DOMAINS_WHITELIST=example.com,example.net,example.org +## Controls which users can create new orgs. +## Blank or 'all' means all users can create orgs (this is the default): +# ORG_CREATION_USERS= +## 'none' means no users can create orgs: +# ORG_CREATION_USERS=none +## A comma-separated list means only those users can create orgs: +# ORG_CREATION_USERS=admin1@example.com,admin2@example.com + ## Token for the admin interface, preferably use a long random string ## One option is to use 'openssl rand -base64 48' ## If not set, the admin panel is disabled diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index b4ec4d8..322ed20 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -76,6 +76,10 @@ struct NewCollectionData { #[post("/organizations", data = "")] fn create_organization(headers: Headers, data: JsonUpcase, conn: DbConn) -> JsonResult { + if !CONFIG.is_org_creation_allowed(&headers.user.email) { + err!("User not allowed to create organizations") + } + let data: OrgData = data.into_inner().data; let org = Organization::new(data.Name, data.BillingEmail); diff --git a/src/config.rs b/src/config.rs index 1192f75..33b2fc3 100644 --- a/src/config.rs +++ b/src/config.rs @@ -115,6 +115,7 @@ macro_rules! make_config { config.domain_set = _domain_set; config.signups_domains_whitelist = config.signups_domains_whitelist.trim().to_lowercase(); + config.org_creation_users = config.org_creation_users.trim().to_lowercase(); config } @@ -276,6 +277,9 @@ make_config! { signups_verify_resend_limit: u32, true, def, 6; /// Email domain whitelist |> Allow signups only from this list of comma-separated domains, even when signups are otherwise disabled signups_domains_whitelist: String, true, def, "".to_string(); + /// Org creation users |> Allow org creation only by this list of comma-separated user emails. + /// Blank or 'all' means all users can create orgs; 'none' means no users can create orgs. + org_creation_users: String, true, def, "".to_string(); /// Allow invitations |> Controls whether users can be invited by organization admins, even when signups are otherwise disabled invitations_allowed: bool, true, def, true; /// Password iterations |> Number of server-side passwords hashing iterations. @@ -442,6 +446,13 @@ fn validate_config(cfg: &ConfigItems) -> Result<(), Error> { err!("`SIGNUPS_DOMAINS_WHITELIST` contains empty tokens"); } + let org_creation_users = cfg.org_creation_users.trim().to_lowercase(); + if !(org_creation_users.is_empty() || org_creation_users == "all" || org_creation_users == "none") { + if org_creation_users.split(',').any(|u| !u.contains('@')) { + err!("`ORG_CREATION_USERS` contains invalid email addresses"); + } + } + if let Some(ref token) = cfg.admin_token { if token.trim().is_empty() && !cfg.disable_admin_token { println!("[WARNING] `ADMIN_TOKEN` is enabled but has an empty value, so the admin page will be disabled."); @@ -592,6 +603,19 @@ impl Config { } } + /// Tests whether the specified user is allowed to create an organization. + pub fn is_org_creation_allowed(&self, email: &str) -> bool { + let users = self.org_creation_users(); + if users == "" || users == "all" { + true + } else if users == "none" { + false + } else { + let email = email.to_lowercase(); + users.split(',').any(|u| u.trim() == email) + } + } + pub fn delete_user_config(&self) -> Result<(), Error> { crate::util::delete_file(&CONFIG_FILE)?;