Private
Public Access
Both account-creation surfaces now require the password twice: - Web signup (invite-based self-service): SignupComplete gains a password_confirm field with a model_validator backstop server-side, plus a client-side match check in SignupPage.tsx for immediate feedback -- the client check is the primary UX, the server check is defense in depth so the guarantee doesn't rely on the client alone. - CLI (python -m app.cli create-user): password is now an optional positional argument. If omitted, prompts interactively via getpass (hidden input) twice, retrying on mismatch -- matching what "entered twice and verified" actually means for a human typing blind. Passing the password directly as before still works unchanged, for scripted/ automated provisioning. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
44 lines
1.2 KiB
Python
44 lines
1.2 KiB
Python
import uuid
|
|
from datetime import datetime
|
|
|
|
from pydantic import BaseModel, ConfigDict, EmailStr, Field, model_validator
|
|
|
|
from app.models import InviteStatus
|
|
|
|
|
|
class SiteInviteCreate(BaseModel):
|
|
email: EmailStr
|
|
|
|
|
|
class SiteInviteRead(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
id: uuid.UUID
|
|
email: str
|
|
invited_by: uuid.UUID
|
|
status: InviteStatus
|
|
expires_at: datetime
|
|
created_at: datetime
|
|
|
|
|
|
class SignupValidateRead(BaseModel):
|
|
email: str
|
|
|
|
|
|
class SignupComplete(BaseModel):
|
|
token: str
|
|
username: str = Field(min_length=3, max_length=50)
|
|
password: str = Field(min_length=8, max_length=200)
|
|
password_confirm: str
|
|
|
|
# Backend backstop -- the signup form does its own client-side match
|
|
# check for immediate feedback (see SignupPage.tsx), but account
|
|
# creation is irreversible enough (a typo'd password with no recovery
|
|
# path until forgot-password) that the guarantee shouldn't rely on the
|
|
# client alone.
|
|
@model_validator(mode="after")
|
|
def _passwords_match(self) -> "SignupComplete":
|
|
if self.password != self.password_confirm:
|
|
raise ValueError("Passwords don't match")
|
|
return self
|