DealDocumentScreening/src/contract_check/api/schemas/auth.py
febux 2fe54ffd86 Phase 2 - Raw SQL migration, pytest-cov coverage gate, CI branch
triggers + coverage,  Dead tooling & config drift
2026-08-24 00:45:39 +03:00

250 lines
5.7 KiB
Python

"""Authentication request/response schemas."""
from __future__ import annotations
import datetime as dt
import uuid
from pydantic import BaseModel, EmailStr, Field
class TelegramProfile(BaseModel):
username: str | None = None
first_name: str | None = None
last_name: str | None = None
language_code: str | None = None
class TelegramBotAuthRequest(BaseModel):
telegram_id: int = Field(..., gt=0, description="Verified Telegram user id from aiogram")
profile: TelegramProfile | None = None
class TelegramWebAuthRequest(BaseModel):
id: int = Field(..., gt=0)
first_name: str | None = None
last_name: str | None = None
username: str | None = None
photo_url: str | None = None
auth_date: int
hash: str
class TelegramMiniAppAuthRequest(BaseModel):
init_data: str = Field(..., description="Raw initData query string from Telegram.WebApp")
class AuthResponse(BaseModel):
access_token: str
token_type: str = "bearer"
expires_in: int
user_id: str
telegram_id: int
class TokenIntrospectResponse(BaseModel):
sub: str
telegram_id: int
type: str
exp: int
class WebUserPublic(BaseModel):
"""User profile subset safe to return to the webUI."""
id: str
email: str | None = None
name: str | None = None
telegram_id: int | None = None
credits_left: int
is_active: bool
created_at: dt.datetime
class MeResponse(TokenIntrospectResponse):
"""Extends the introspection shape with web-user fields (additive)."""
email: str | None = None
name: str | None = None
credits_left: int = 0
is_active: bool = True
created_at: dt.datetime | None = None
class RegisterRequest(BaseModel):
email: EmailStr
name: str = Field(..., min_length=1, max_length=128)
password: str = Field(..., min_length=8, max_length=128)
class LoginRequest(BaseModel):
email: EmailStr
password: str = Field(..., min_length=1, max_length=128)
class TokenPairResponse(BaseModel):
"""JWT pair returned by register/login."""
access_token: str
refresh_token: str
token_type: str = "bearer"
expires_in: int # access TTL in seconds
user: WebUserPublic
class LogoutRequest(BaseModel):
refresh_token: str = Field(..., min_length=1)
class ForgotPasswordRequest(BaseModel):
email: EmailStr
class ResetPasswordRequest(BaseModel):
token: str = Field(..., min_length=1, max_length=256)
password: str = Field(..., min_length=8, max_length=128)
class OkResponse(BaseModel):
"""Generic `{ok: true, ...}` payload for state-mutating auth endpoints."""
ok: bool = True
detail: str | None = None
class PasskeyRpInfo(BaseModel):
id: str
name: str
origin: str
class PasskeyUserInfo(BaseModel):
id: str
name: str
displayName: str
class PubKeyCredParam(BaseModel):
alg: int
type: str
class CredDescriptor(BaseModel):
id: str
type: str
class AuthenticatorSelectionInfo(BaseModel):
residentKey: str
userVerification: str
class PasskeyRegistrationOptions(BaseModel):
"""PublicKeyCredentialCreationOptions — matches the frontend zod schema."""
challenge: str
rp: PasskeyRpInfo
user: PasskeyUserInfo
pubKeyCredParams: list[PubKeyCredParam]
timeout: int
attestation: str
excludeCredentials: list[CredDescriptor]
authenticatorSelection: AuthenticatorSelectionInfo
class PasskeyAuthenticationOptions(BaseModel):
"""PublicKeyCredentialRequestOptions — matches the frontend zod schema."""
challenge: str
timeout: int
rpId: str
allowCredentials: list[CredDescriptor]
userVerification: str
class PasskeyCredentialPublic(BaseModel):
"""Stored credential — matches passkeyCredentialSchema on the frontend."""
id: str
credentialID: str
credentialPublicKey: str
counter: int
userId: str
deviceName: str
createdAt: dt.datetime
class PasskeyRegistrationFinishResponse(BaseModel):
verified: bool
credentialId: str
class SingleTokenAuthResponse(BaseModel):
"""Shared by passkey-auth finish and magic-link verify (frontend schemas)."""
access_token: str
token_type: str = "bearer"
expires_in: int
user_id: uuid.UUID
email: str
class MagicLinkResponse(BaseModel):
"""Matches magicLinkResponseSchema on the frontend."""
success: bool = True
message: str
expires_in: int # link TTL in seconds
class PasskeyRegisterStartRequest(BaseModel):
device_name: str | None = Field(default=None, max_length=128)
class PasskeyRegistrationCredentialData(BaseModel):
"""`navigator.credentials.create()` result, base64url-encoded."""
clientDataJSON: str
attestationObject: str
transports: list[str] | None = None
class PasskeyRegistrationCredentialPayload(BaseModel):
id: str
rawId: str
type: str
response: PasskeyRegistrationCredentialData
class PasskeyRegisterFinishRequest(BaseModel):
credential: PasskeyRegistrationCredentialPayload
device_name: str | None = Field(default=None, max_length=128)
class PasskeyAuthenticationCredentialData(BaseModel):
"""`navigator.credentials.get()` result, base64url-encoded."""
clientDataJSON: str
authenticatorData: str
signature: str
userHandle: str | None = None
class PasskeyAuthenticationCredentialPayload(BaseModel):
id: str
rawId: str
type: str
response: PasskeyAuthenticationCredentialData
class PasskeyAuthenticateFinishRequest(BaseModel):
challenge: str = Field(..., min_length=16, max_length=512)
credential: PasskeyAuthenticationCredentialPayload
class MagicLinkRequest(BaseModel):
email: EmailStr
class MagicLinkVerifyRequest(BaseModel):
token: str = Field(..., min_length=1, max_length=256)