from typing import List, Literal, Optional from pydantic import BaseModel, ConfigDict, Field, field_validator USERNAME_PATTERN = r"^[a-z_][a-z0-9_-]{0,31}$" GROUPNAME_PATTERN = r"^[a-z_][a-z0-9_-]{0,31}$" class UserCreateRequest(BaseModel): model_config = ConfigDict(extra="forbid") username: str = Field(pattern=USERNAME_PATTERN) password_hash: str = Field(min_length=10, max_length=512) primary_group: Optional[str] = Field(default=None, pattern=GROUPNAME_PATTERN) groups: List[str] = Field(default_factory=list) @field_validator("groups") @classmethod def validate_groups(cls, value: List[str]) -> List[str]: deduped = list(dict.fromkeys(value)) for group in deduped: if not __import__("re").match(GROUPNAME_PATTERN, group): raise ValueError(f"Invalid group name: {group}") return deduped class UserSummary(BaseModel): username: str uid: int gid: int home_dir: str shell: str class GroupCreateRequest(BaseModel): groupname: str = Field(pattern=GROUPNAME_PATTERN) class GroupSummary(BaseModel): groupname: str gid: int members: List[str] class UserGroupsUpdateRequest(BaseModel): groups: List[str] = Field(min_length=1) mode: Literal["append", "replace"] = "append" @field_validator("groups") @classmethod def validate_groups(cls, value: List[str]) -> List[str]: deduped = list(dict.fromkeys(value)) for group in deduped: if not __import__("re").match(GROUPNAME_PATTERN, group): raise ValueError(f"Invalid group name: {group}") return deduped class UserPasswordUpdateRequest(BaseModel): password_hash: str = Field(min_length=10, max_length=512) class ApiResponse(BaseModel): status: str = "ok" message: str