Add complete NexaMFA push MFA system with: - FastAPI backend with PostgreSQL, Redis, OIDC provider, and Prometheus metrics - React TypeScript admin console - Android Kotlin/Jetpack Compose app with biometric authentication - Docker Compose deployment configuration - Gitea CI workflow for backend, frontend, and Android builds - Environment configuration template with security settings - Documentation for security model, deployment
107 lines
2.3 KiB
Python
107 lines
2.3 KiB
Python
from datetime import datetime
|
|
from typing import Any
|
|
from uuid import UUID
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
class UserOut(BaseModel):
|
|
id: UUID
|
|
username: str
|
|
display_name: str | None = None
|
|
email: str | None = None
|
|
created_at: datetime
|
|
|
|
model_config = {"from_attributes": True}
|
|
|
|
|
|
class DeviceOut(BaseModel):
|
|
id: UUID
|
|
user_id: UUID
|
|
name: str
|
|
platform: str
|
|
public_key_alg: str
|
|
is_revoked: bool
|
|
revoked_at: datetime | None = None
|
|
last_seen_at: datetime | None = None
|
|
created_at: datetime
|
|
|
|
model_config = {"from_attributes": True}
|
|
|
|
|
|
class EnrollmentStartRequest(BaseModel):
|
|
username: str = Field(min_length=1, max_length=255)
|
|
display_name: str | None = None
|
|
email: str | None = None
|
|
|
|
|
|
class EnrollmentStartResponse(BaseModel):
|
|
enrollment_id: UUID
|
|
qr_payload: dict[str, Any]
|
|
qr_png_base64: str
|
|
expires_at: datetime
|
|
|
|
|
|
class EnrollmentFinishRequest(BaseModel):
|
|
enrollment_token: str
|
|
device_name: str = Field(min_length=1, max_length=255)
|
|
public_key_pem: str
|
|
public_key_alg: str = "ES256"
|
|
fcm_token: str | None = None
|
|
app_version: str | None = None
|
|
attestation: dict[str, Any] | None = None
|
|
|
|
|
|
class EnrollmentFinishResponse(BaseModel):
|
|
device_id: UUID
|
|
user_id: UUID
|
|
|
|
|
|
class ChallengeCreateRequest(BaseModel):
|
|
username: str
|
|
relying_party: str
|
|
requester_ip: str
|
|
location: str | None = None
|
|
ttl_seconds: int | None = Field(default=None, ge=10, le=300)
|
|
oidc_state: str | None = None
|
|
|
|
|
|
class ChallengeOut(BaseModel):
|
|
id: UUID
|
|
user_id: UUID
|
|
device_id: UUID | None
|
|
status: str
|
|
relying_party: str
|
|
requester_ip: str
|
|
location: str | None
|
|
payload: dict[str, Any]
|
|
issued_at: datetime
|
|
expires_at: datetime
|
|
responded_at: datetime | None
|
|
|
|
model_config = {"from_attributes": True}
|
|
|
|
|
|
class ChallengeApprovalRequest(BaseModel):
|
|
device_id: UUID
|
|
signature: str
|
|
payload: dict[str, Any]
|
|
|
|
|
|
class DenyRequest(BaseModel):
|
|
device_id: UUID | None = None
|
|
reason: str | None = Field(default=None, max_length=255)
|
|
|
|
|
|
class AuditOut(BaseModel):
|
|
id: UUID
|
|
actor: str | None
|
|
action: str
|
|
target_type: str | None
|
|
target_id: str | None
|
|
ip_address: str | None
|
|
metadata_json: dict[str, Any] | None
|
|
created_at: datetime
|
|
|
|
model_config = {"from_attributes": True}
|