Spaces:
Running
Running
File size: 12,591 Bytes
4c9881b |
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 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 |
# ============================================================
# app/core/exceptions.py - ALL Auth Exceptions
# ============================================================
from fastapi import HTTPException, status
from typing import Optional, Any, Dict
class AuthException(HTTPException):
"""Base authentication exception"""
def __init__(
self,
status_code: int,
detail: str,
error_code: str,
message: str,
data: Optional[Dict[str, Any]] = None,
):
super().__init__(status_code=status_code, detail=detail)
self.error_code = error_code
self.message = message
self.data = data or {}
# ============================================================
# LOGIN ERRORS
# ============================================================
class InvalidCredentialsException(AuthException):
"""Invalid email/phone or password"""
def __init__(self):
super().__init__(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid credentials",
error_code="INVALID_CREDENTIALS",
message="The email/phone or password you entered is incorrect. Please try again.",
)
class AccountInactiveException(AuthException):
"""Account is inactive"""
def __init__(self):
super().__init__(
status_code=status.HTTP_403_FORBIDDEN,
detail="Account is inactive",
error_code="ACCOUNT_INACTIVE",
message="Your account has been deactivated. Please contact support for assistance.",
)
# ============================================================
# SIGNUP ERRORS
# ============================================================
class UserAlreadyExistsException(AuthException):
"""User already registered"""
def __init__(self, identifier: str):
super().__init__(
status_code=status.HTTP_409_CONFLICT,
detail="User already registered",
error_code="USER_ALREADY_EXISTS",
message="This email or phone number is already registered. Please log in instead.",
data={"identifier": identifier},
)
class SignupFailedException(AuthException):
"""Signup failed due to server error"""
def __init__(self, reason: str = "Unknown error"):
super().__init__(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Signup failed",
error_code="SIGNUP_FAILED",
message=f"Unable to create account. {reason} Please try again later.",
)
class InvalidEmailFormatException(AuthException):
"""Invalid email format"""
def __init__(self):
super().__init__(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Invalid email format",
error_code="INVALID_EMAIL_FORMAT",
message="Please enter a valid email address.",
)
class InvalidPhoneFormatException(AuthException):
"""Invalid phone format"""
def __init__(self):
super().__init__(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Invalid phone format",
error_code="INVALID_PHONE_FORMAT",
message="Please enter a valid phone number.",
)
class WeakPasswordException(AuthException):
"""Password doesn't meet requirements"""
def __init__(self, requirement: str):
super().__init__(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Password doesn't meet requirements",
error_code="WEAK_PASSWORD",
message=f"Password must {requirement}. Please choose a stronger password.",
)
class MissingEmailPhoneException(AuthException):
"""Neither email nor phone provided"""
def __init__(self):
super().__init__(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Email or phone required",
error_code="MISSING_EMAIL_PHONE",
message="Please provide either an email address or a phone number.",
)
# ============================================================
# OTP ERRORS
# ============================================================
class InvalidOtpException(AuthException):
"""Invalid OTP code"""
def __init__(self, attempts_left: Optional[int] = None):
msg = "The OTP code you entered is incorrect."
if attempts_left is not None:
msg += f" You have {attempts_left} attempt(s) left."
super().__init__(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Invalid OTP code",
error_code="INVALID_OTP",
message=msg,
data={"attempts_left": attempts_left} if attempts_left else {},
)
class OtpExpiredException(AuthException):
"""OTP has expired"""
def __init__(self):
super().__init__(
status_code=status.HTTP_400_BAD_REQUEST,
detail="OTP expired",
error_code="OTP_EXPIRED",
message="The OTP code has expired. Please request a new one.",
)
class OtpNotSentException(AuthException):
"""OTP was never sent"""
def __init__(self):
super().__init__(
status_code=status.HTTP_400_BAD_REQUEST,
detail="OTP not found",
error_code="OTP_NOT_FOUND",
message="No OTP was sent. Please request one first.",
)
class OtpSendFailedException(AuthException):
"""Failed to send OTP"""
def __init__(self, method: str = "email/SMS"):
super().__init__(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="OTP send failed",
error_code="OTP_SEND_FAILED",
message=f"Failed to send OTP via {method}. Please try again later.",
)
class OtpAlreadyValidException(AuthException):
"""OTP already validated/used"""
def __init__(self):
super().__init__(
status_code=status.HTTP_400_BAD_REQUEST,
detail="OTP already used",
error_code="OTP_ALREADY_USED",
message="This OTP has already been used. Please request a new one.",
)
class OtpTooManyAttemptsException(AuthException):
"""Too many failed OTP attempts"""
def __init__(self, retry_after_minutes: int = 15):
super().__init__(
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
detail="Too many OTP attempts",
error_code="OTP_TOO_MANY_ATTEMPTS",
message=f"Too many failed attempts. Please try again in {retry_after_minutes} minutes.",
data={"retry_after_minutes": retry_after_minutes},
)
# ============================================================
# PASSWORD RESET ERRORS
# ============================================================
class InvalidResetTokenException(AuthException):
"""Invalid reset token"""
def __init__(self):
super().__init__(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Invalid reset token",
error_code="INVALID_RESET_TOKEN",
message="The reset token is invalid or has expired. Please request a password reset again.",
)
class ResetTokenExpiredException(AuthException):
"""Reset token has expired"""
def __init__(self):
super().__init__(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Reset token expired",
error_code="RESET_TOKEN_EXPIRED",
message="The reset token has expired (valid for 10 minutes). Please request a new password reset.",
)
class TokenMismatchException(AuthException):
"""Token identifier doesn't match request identifier"""
def __init__(self):
super().__init__(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Token mismatch",
error_code="TOKEN_MISMATCH",
message="The reset token doesn't match. Please request a new password reset.",
)
class PasswordResetFailedException(AuthException):
"""Password reset failed"""
def __init__(self):
super().__init__(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Password reset failed",
error_code="PASSWORD_RESET_FAILED",
message="Unable to reset your password. Please try again later.",
)
# ============================================================
# USER ERRORS
# ============================================================
class UserNotFoundException(AuthException):
"""User not found"""
def __init__(self, identifier: str):
super().__init__(
status_code=status.HTTP_404_NOT_FOUND,
detail="User not found",
error_code="USER_NOT_FOUND",
message="No account found with this email or phone number. Please sign up first.",
data={"identifier": identifier},
)
class AccountNotVerifiedException(AuthException):
"""Account email/phone not verified"""
def __init__(self, verification_type: str = "email"):
super().__init__(
status_code=status.HTTP_403_FORBIDDEN,
detail="Account not verified",
error_code="ACCOUNT_NOT_VERIFIED",
message=f"Your {verification_type} has not been verified yet. Please verify to continue.",
data={"verification_type": verification_type},
)
class SamePasswordException(AuthException):
"""New password is same as old password"""
def __init__(self):
super().__init__(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Same password",
error_code="SAME_PASSWORD",
message="Your new password cannot be the same as your current password. Please choose a different one.",
)
# ============================================================
# DATABASE ERRORS
# ============================================================
class DatabaseException(AuthException):
"""Database operation failed"""
def __init__(self, operation: str = "operation"):
super().__init__(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Database error",
error_code="DATABASE_ERROR",
message=f"A database error occurred during {operation}. Please try again later.",
)
# ============================================================
# VALIDATION ERRORS
# ============================================================
class ValidationException(AuthException):
"""Validation error"""
def __init__(self, message: str, errors: Optional[list] = None):
super().__init__(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Validation error",
error_code="VALIDATION_ERROR",
message=message,
data={"errors": errors} if errors else {},
)
# ============================================================
# RATE LIMITING ERRORS
# ============================================================
class RateLimitException(AuthException):
"""Too many requests"""
def __init__(self, retry_after_seconds: int = 60):
super().__init__(
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
detail="Too many requests",
error_code="RATE_LIMIT_EXCEEDED",
message=f"You've made too many requests. Please try again in {retry_after_seconds} seconds.",
data={"retry_after_seconds": retry_after_seconds},
)
# ============================================================
# RESEND OTP ERRORS
# ============================================================
class OtpResendTooSoonException(AuthException):
"""OTP resend requested too soon"""
def __init__(self, wait_seconds: int = 60):
super().__init__(
status_code=status.HTTP_400_BAD_REQUEST,
detail="OTP resend too soon",
error_code="OTP_RESEND_TOO_SOON",
message=f"Please wait {wait_seconds} seconds before requesting a new OTP.",
data={"wait_seconds": wait_seconds},
)
class OtpStillValidException(AuthException):
"""OTP is still valid, don't resend yet"""
def __init__(self, expires_in_seconds: int):
super().__init__(
status_code=status.HTTP_400_BAD_REQUEST,
detail="OTP still valid",
error_code="OTP_STILL_VALID",
message=f"Your current OTP is still valid. Please use it first (expires in {expires_in_seconds} seconds).",
data={"expires_in_seconds": expires_in_seconds},
) |