TorbatYar/backend/services/identity-access/app/schemas/auth.py
Mortezakoohjani 12c8615615 Ship enterprise Accounting FE/API with CRUD parity and production wiring.
Adds accounting-service PATCH/archive, fiscal helpers, COA templates and setup status, plus SuperApp Accounting UI (DS, scoreboard, masters, vouchers, ledger, ops modules) with session refresh and HTTPS public API URLs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-24 15:26:43 +03:30

173 lines
5.0 KiB
Python

from datetime import datetime
from typing import Literal
from pydantic import BaseModel, EmailStr, Field, field_validator, model_validator
from shared.phone import normalize_mobile, normalize_otp_digits
class AuthConfigResponse(BaseModel):
"""پیکربندی OIDC برای frontend — بدون hardcode در کلاینت."""
issuer: str
client_id: str
authorization_endpoint: str
token_endpoint: str
logout_endpoint: str
userinfo_endpoint: str
callback_url: str
class LoginUrlResponse(BaseModel):
"""آدرس ورود Keycloak به همراه state (PKCE سمت سرور)."""
authorization_url: str
state: str
class TokenExchangeRequest(BaseModel):
code: str = Field(..., min_length=1)
redirect_uri: str = Field(..., min_length=1)
state: str | None = Field(default=None)
code_verifier: str | None = Field(default=None, min_length=43, max_length=128)
class RefreshTokenRequest(BaseModel):
refresh_token: str = Field(..., min_length=1)
class TokenResponse(BaseModel):
access_token: str
refresh_token: str | None = None
expires_in: int
token_type: str = "Bearer"
class MeResponse(BaseModel):
user_id: str
keycloak_sub: str
email: str | None
username: str | None
display_name: str | None
mobile: str | None
mobile_verified: bool
requires_mobile_verification: bool
roles: list[str]
tenant_memberships: list[dict]
class ProfileUpdateRequest(BaseModel):
"""به‌روزرسانی پروفایل کاربر جاری (نام نمایشی و ایمیل)."""
display_name: str | None = Field(default=None, max_length=255)
email: EmailStr | None = None
class PasswordChangeRequest(BaseModel):
"""تغییر یا تنظیم رمز عبور کاربر جاری."""
current_password: str | None = Field(default=None, max_length=128)
new_password: str = Field(..., min_length=8, max_length=128)
class PasswordChangeResponse(BaseModel):
message: str
class RegisterRequest(BaseModel):
"""ثبت‌نام با نام کاربری، رمز عبور و شماره موبایل."""
email: EmailStr
username: str = Field(..., min_length=3, max_length=150)
password: str = Field(..., min_length=8)
display_name: str | None = None
mobile: str = Field(..., min_length=10, max_length=15)
@field_validator("mobile")
@classmethod
def validate_mobile(cls, value: str) -> str:
return normalize_mobile(value)
class MobileOtpRequest(BaseModel):
mobile: str = Field(..., min_length=10, max_length=15)
force_sms: bool = False
@field_validator("mobile")
@classmethod
def validate_mobile(cls, value: str) -> str:
return normalize_mobile(value)
class MobileOtpVerify(BaseModel):
mobile: str = Field(..., min_length=10, max_length=15)
code: str = Field(..., min_length=4, max_length=8)
@field_validator("mobile")
@classmethod
def validate_mobile(cls, value: str) -> str:
return normalize_mobile(value)
@field_validator("code")
@classmethod
def validate_code(cls, value: str) -> str:
normalized = normalize_otp_digits(value)
if not normalized:
raise ValueError("کد تأیید نامعتبر است")
return normalized
class MobileRegisterVerify(MobileOtpVerify):
"""تکمیل ورود/ثبت‌نام یکپارچه — فیلدهای پروفایل فقط برای ثبت‌نام جدید."""
display_name: str | None = None
email: EmailStr | None = None
username: str | None = Field(default=None, min_length=3, max_length=150)
password: str | None = Field(default=None, min_length=8)
create_handoff: bool = False
@model_validator(mode="after")
def validate_credentials_bundle(self) -> "MobileRegisterVerify":
fields = (self.email, self.username, self.password)
if any(fields) and not all(fields):
raise ValueError("ایمیل، نام کاربری و رمز عبور باید با هم ارسال شوند")
return self
class HandoffRedeemRequest(BaseModel):
handoff_code: str = Field(..., min_length=16)
class OtpRequestResponse(BaseModel):
message: str
expires_in: int
skipped: bool = False
class RegisterCompleteResponse(BaseModel):
message: str
mobile_verified: bool
user_id: str | None = None
class AuthSessionResponse(TokenResponse):
"""پاسخ ورود/ثبت‌نام موفق — شامل توکن SSO برای redirect مستقیم به داشبورد."""
message: str
intent: Literal["login", "register"]
user_id: str | None = None
mobile_verified: bool = True
redirect_url: str | None = None
class MobileAuthStartResponse(OtpRequestResponse):
"""شروع ورود/ثبت‌نام یکپارچه با موبایل — بک‌اند نوع عملیات را تشخیص می‌دهد."""
intent: Literal["login", "register"]
class VerifyMobileResponse(BaseModel):
message: str
mobile_verified: bool
mobile_verified_at: datetime | None = None