250 lines
5.7 KiB
Python
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)
|