// Package validate provides shared input-validation helpers used by the REST, // gRPC, and UI handlers. package validate import ( "fmt" "regexp" ) // usernameRE is the allowed character set for usernames: alphanumeric plus a // small set of punctuation that is safe in all contexts (URLs, HTML, logs). // Length is enforced separately so the error message can be more precise. // // Security (F-12): rejecting control characters, null bytes, newlines, and // unusual Unicode prevents log injection, stored-XSS via username display, // and rendering anomalies in the admin UI. var usernameRE = regexp.MustCompile(`^[a-zA-Z0-9._@-]+$`) // MinUsernameLen and MaxUsernameLen are the inclusive bounds on username length. const ( MinUsernameLen = 1 MaxUsernameLen = 255 ) // Username returns nil if the username is valid, or a descriptive error if not. // Valid usernames are 1–255 characters long and contain only alphanumeric // characters and the symbols . _ @ - func Username(username string) error { l := len(username) if l < MinUsernameLen || l > MaxUsernameLen { return fmt.Errorf("username must be between %d and %d characters", MinUsernameLen, MaxUsernameLen) } if !usernameRE.MatchString(username) { return fmt.Errorf("username may only contain letters, digits, and the characters . _ @ -") } return nil } // MinPasswordLen is the minimum acceptable plaintext password length. // // Security (F-13): NIST SP 800-63B recommends a minimum of 8 characters; // we use 12 to provide additional margin against offline brute-force attacks // even though Argon2id is expensive. The check is performed at the handler // level (before hashing) so Argon2id is never invoked with a trivially weak // password. const MinPasswordLen = 12 // Password returns nil if the plaintext password meets the minimum length // requirement, or a descriptive error if not. func Password(password string) error { if len(password) < MinPasswordLen { return fmt.Errorf("password must be at least %d characters", MinPasswordLen) } return nil }