forked from attevon-llc/OpenTranscribe
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathuser.py
More file actions
187 lines (131 loc) · 5.41 KB
/
Copy pathuser.py
File metadata and controls
187 lines (131 loc) · 5.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
from datetime import datetime
from typing import Optional
from pydantic import BaseModel
from pydantic import EmailStr
from pydantic import Field
from pydantic import field_validator
from pydantic import model_validator
from app.schemas.base import UUIDBaseSchema
class UserBase(BaseModel):
email: EmailStr
full_name: Optional[str] = None
class UserCreate(UserBase):
"""Schema for creating a new user with password policy validation.
Password validation is performed against the configured password policy
when PASSWORD_POLICY_ENABLED is true. The policy enforces:
- Minimum length (default: 12 characters)
- Character complexity (uppercase, lowercase, digits, special chars)
- No user information in password (email username, name parts)
"""
password: str = Field(..., min_length=8) # Base minimum for backward compatibility
role: Optional[str] = "user"
is_active: Optional[bool] = True
is_superuser: Optional[bool] = False
@model_validator(mode="after")
def validate_password_policy(self) -> "UserCreate":
"""Validate password against the configured password policy.
This validator runs after all field validators, so we have access
to both email and full_name for comprehensive validation.
Raises:
ValueError: If password doesn't meet policy requirements
"""
from app.auth.password_policy import validate_password
result = validate_password(
password=self.password,
email=self.email,
full_name=self.full_name,
)
if not result.is_valid:
# Combine all errors into a single message
error_msg = "; ".join(result.errors)
raise ValueError(f"Password does not meet policy requirements: {error_msg}")
return self
class UserUpdate(BaseModel):
email: Optional[EmailStr] = None
full_name: Optional[str] = None
password: Optional[str] = None
current_password: Optional[str] = None # For password change verification
is_active: Optional[bool] = None
is_superuser: Optional[bool] = None
role: Optional[str] = None
class UserInDB(UserBase, UUIDBaseSchema):
"""User schema with UUID as public identifier"""
role: str
created_at: datetime
updated_at: datetime
is_active: bool
is_superuser: bool
auth_type: str # "local", "ldap", "keycloak", "pki"
ldap_uid: Optional[str] = None
keycloak_id: Optional[str] = None
pki_subject_dn: Optional[str] = None
# FedRAMP compliance fields
password_changed_at: Optional[datetime] = None
must_change_password: bool = False
last_login_at: Optional[datetime] = None
account_expires_at: Optional[datetime] = None
class User(UserInDB):
pass
class Token(BaseModel):
access_token: str
token_type: str
refresh_token: Optional[str] = None
expires_in: Optional[int] = None # Access token expiration in seconds
class TokenRefreshRequest(BaseModel):
"""Request body for token refresh endpoint."""
refresh_token: str
class TokenPayload(BaseModel):
sub: Optional[str] = None
exp: Optional[int] = None
jti: Optional[str] = None
role: Optional[str] = None
type: Optional[str] = None # 'access' or 'refresh'
# ===== MFA Schemas (FedRAMP IA-2) =====
class MFASetupResponse(BaseModel):
"""Response from MFA setup initiation."""
secret: str # Base32-encoded TOTP secret (for manual entry)
provisioning_uri: str # otpauth:// URI for QR code
qr_code_base64: str # Base64-encoded PNG QR code image
class MFAVerifySetupRequest(BaseModel):
"""Request to verify MFA setup with initial TOTP code."""
code: str = Field(..., min_length=6, max_length=6, description="6-digit TOTP code")
@field_validator("code")
@classmethod
def validate_code_format(cls, v: str) -> str:
"""Ensure code contains only digits."""
if not v.isdigit():
raise ValueError("Code must contain only digits")
return v
class MFAVerifySetupResponse(BaseModel):
"""Response from successful MFA setup verification."""
success: bool
backup_codes: list[str] # One-time use backup codes (shown only once)
message: str
class MFAVerifyRequest(BaseModel):
"""Request to verify MFA code during login."""
mfa_token: str # Short-lived token from initial login
code: str = Field(
..., min_length=6, max_length=8, description="6-digit TOTP code or 8-char backup code"
)
class MFAVerifyResponse(BaseModel):
"""Response from successful MFA verification during login."""
access_token: str
token_type: str = "bearer"
refresh_token: Optional[str] = None
expires_in: Optional[int] = None
class MFADisableRequest(BaseModel):
"""Request to disable MFA for current user."""
code: str = Field(
..., min_length=6, max_length=8, description="6-digit TOTP code or 8-char backup code"
)
class MFAStatusResponse(BaseModel):
"""Response indicating MFA status for current user."""
mfa_enabled: bool
mfa_configured: bool # True if user has started MFA setup
mfa_required: bool # True if system requires MFA
can_setup_mfa: bool # True if user can set up MFA (not PKI/Keycloak)
class MFALoginResponse(BaseModel):
"""Response when MFA is required during login."""
mfa_required: bool = True
mfa_token: str # Short-lived token for MFA verification step
message: str = "MFA verification required"