87 lines
2.3 KiB
Python

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)
default_environment_variables: str = Field(default="", max_length=20000)
@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 UserEnvironmentUpdateRequest(BaseModel):
content: str = Field(default="", max_length=20000)
class UserEnvironmentFailure(BaseModel):
username: str
code: str
message: str
class UserEnvironmentBatchResult(BaseModel):
status: str = "ok"
message: str
updated_users: List[str] = Field(default_factory=list)
failed_users: List[UserEnvironmentFailure] = Field(default_factory=list)
updated_count: int = 0
failed_count: int = 0
class ApiResponse(BaseModel):
status: str = "ok"
message: str