diff --git a/common/src/models/error.rs b/common/src/models/error.rs index 5b4e6fb31..7b20d5571 100644 --- a/common/src/models/error.rs +++ b/common/src/models/error.rs @@ -75,7 +75,18 @@ impl From for ApiError { ErrorKind::ProjectUnavailable => { (StatusCode::BAD_GATEWAY, "project returned invalid response") } - ErrorKind::InvalidProjectName => (StatusCode::BAD_REQUEST, "invalid project name"), + ErrorKind::InvalidProjectName => ( + StatusCode::BAD_REQUEST, + r#" + Invalid project name. Project name must: + 1. start and end with alphanumeric characters. + 2. only contain lowercase characters. + 3. only contain characters inside of the alphanumeric range, except for `-`. + 4. not be empty. + 5. be shorter than 63 characters. + 6. not contain profanity. + 7. not be a reserved word."#, + ), ErrorKind::InvalidOperation => ( StatusCode::BAD_REQUEST, "the requested operation is invalid", diff --git a/gateway/src/lib.rs b/gateway/src/lib.rs index a747bc673..3e66cea9c 100644 --- a/gateway/src/lib.rs +++ b/gateway/src/lib.rs @@ -127,6 +127,23 @@ impl ProjectName { pub fn as_str(&self) -> &str { self.0.as_str() } + + pub fn is_valid(&self) -> bool { + let name = self.0.clone(); + + fn is_valid_char(byte: u8) -> bool { + matches!(byte, b'a'..=b'z' | b'0'..=b'9' | b'-') + } + + // each label in a hostname can be between 1 and 63 chars + let is_invalid_length = name.len() > 63; + + !(name.bytes().any(|byte| !is_valid_char(byte)) + || name.ends_with('-') + || name.starts_with('-') + || name.is_empty() + || is_invalid_length) + } } impl<'de> Deserialize<'de> for ProjectName { diff --git a/gateway/src/service.rs b/gateway/src/service.rs index ed32cd884..b2ab9f6ed 100644 --- a/gateway/src/service.rs +++ b/gateway/src/service.rs @@ -434,10 +434,18 @@ impl GatewayService { Err(Error::from_kind(ErrorKind::ProjectAlreadyExists)) } } else { - // Otherwise attempt to create a new one. This will fail - // outright if the project already exists (this happens if - // it belongs to another account). - self.insert_project(project_name, account_name).await + // Check if project name is valid according to new rules if it + // doesn't exist. + // TODO: remove this check when we update the project name rules + // in shuttle-common + if project_name.is_valid() { + // Otherwise attempt to create a new one. This will fail + // outright if the project already exists (this happens if + // it belongs to another account). + self.insert_project(project_name, account_name).await + } else { + Err(Error::from_kind(ErrorKind::InvalidProjectName)) + } } }