
- Phone number authentication with OTP verification - Email/password authentication with secure bcrypt hashing - Third-party OAuth login support for Google and Apple - JWT token-based authentication system - Rate limiting for OTP requests (5/minute) - SQLite database with SQLAlchemy ORM - Comprehensive user model with multiple auth providers - Alembic database migrations setup - API documentation with Swagger/OpenAPI - Health check and system endpoints - Environment configuration with security best practices - Code quality with Ruff linting and formatting Features: - POST /auth/request-otp - Request OTP for phone authentication - POST /auth/verify-otp - Verify OTP and get access token - POST /auth/signup-email - Email signup with password - POST /auth/login-email - Email login authentication - POST /auth/login-google - Google OAuth integration - POST /auth/login-apple - Apple OAuth integration - GET /user/me - Get current authenticated user info - GET / - API information and documentation links - GET /health - Application health check
82 lines
2.0 KiB
Python
82 lines
2.0 KiB
Python
import random
|
|
import string
|
|
from typing import Optional
|
|
import redis
|
|
import json
|
|
from app.core.config import settings
|
|
|
|
|
|
# Mock Redis client for development
|
|
class MockRedisClient:
|
|
def __init__(self):
|
|
self.data = {}
|
|
|
|
def setex(self, key: str, time: int, value: str):
|
|
self.data[key] = value
|
|
return True
|
|
|
|
def get(self, key: str) -> Optional[str]:
|
|
return self.data.get(key)
|
|
|
|
def delete(self, key: str):
|
|
if key in self.data:
|
|
del self.data[key]
|
|
return True
|
|
|
|
|
|
try:
|
|
redis_client = redis.from_url(settings.REDIS_URL)
|
|
redis_client.ping()
|
|
except Exception:
|
|
redis_client = MockRedisClient()
|
|
|
|
|
|
def generate_otp() -> str:
|
|
return "".join(random.choices(string.digits, k=6))
|
|
|
|
|
|
def store_otp(phone_number: str, otp: str) -> bool:
|
|
try:
|
|
otp_data = {"otp": otp, "attempts": 0}
|
|
redis_client.setex(
|
|
f"otp:{phone_number}",
|
|
settings.OTP_EXPIRE_MINUTES * 60,
|
|
json.dumps(otp_data),
|
|
)
|
|
return True
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
def verify_otp(phone_number: str, otp: str) -> bool:
|
|
try:
|
|
stored_data = redis_client.get(f"otp:{phone_number}")
|
|
if not stored_data:
|
|
return False
|
|
|
|
otp_data = json.loads(stored_data)
|
|
|
|
if otp_data["attempts"] >= settings.OTP_MAX_ATTEMPTS:
|
|
redis_client.delete(f"otp:{phone_number}")
|
|
return False
|
|
|
|
if otp_data["otp"] == otp:
|
|
redis_client.delete(f"otp:{phone_number}")
|
|
return True
|
|
else:
|
|
otp_data["attempts"] += 1
|
|
redis_client.setex(
|
|
f"otp:{phone_number}",
|
|
settings.OTP_EXPIRE_MINUTES * 60,
|
|
json.dumps(otp_data),
|
|
)
|
|
return False
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
def send_otp(phone_number: str, otp: str) -> bool:
|
|
# Mock SMS sending for development
|
|
print(f"Sending OTP {otp} to {phone_number}")
|
|
return True
|