Spaces:
Sleeping
Sleeping
fix: add missing Response param to all rate-limited auth endpoints, completing slowapi compatibility across OTP and auth flows
Browse files- docs/hflogs/runtimeerror.txt +36 -22
- src/app/api/v1/auth.py +6 -3
- tests/integration/reset-password-otp.js +13 -3
docs/hflogs/runtimeerror.txt
CHANGED
|
@@ -1,29 +1,43 @@
|
|
| 1 |
-
===== Application Startup at 2025-11-16 12:
|
| 2 |
|
| 3 |
-
INFO: Started server process [
|
| 4 |
INFO: Waiting for application startup.
|
| 5 |
-
INFO: 2025-11-16T12:
|
| 6 |
-
INFO: 2025-11-16T12:
|
| 7 |
-
INFO: 2025-11-16T12:
|
| 8 |
-
INFO: 2025-11-16T12:
|
| 9 |
-
INFO: 2025-11-16T12:
|
| 10 |
-
INFO: 2025-11-16T12:
|
| 11 |
-
INFO: 2025-11-16T12:
|
| 12 |
-
INFO: 2025-11-16T12:
|
| 13 |
-
INFO: 2025-11-16T12:
|
| 14 |
-
INFO: 2025-11-16T12:
|
| 15 |
-
INFO: 2025-11-16T12:
|
| 16 |
-
INFO: 2025-11-16T12:
|
| 17 |
-
INFO: 2025-11-16T12:
|
| 18 |
-
INFO: 2025-11-16T12:
|
| 19 |
-
INFO: 2025-11-16T12:
|
| 20 |
-
INFO: 2025-11-16T12:
|
| 21 |
INFO: Application startup complete.
|
| 22 |
INFO: Uvicorn running on http://0.0.0.0:7860 (Press CTRL+C to quit)
|
| 23 |
-
INFO: 10.16.
|
| 24 |
-
INFO:
|
| 25 |
-
INFO: 2025-11-16T12:
|
| 26 |
-
INFO:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
ERROR: Exception in ASGI application
|
| 28 |
Traceback (most recent call last):
|
| 29 |
File "/usr/local/lib/python3.11/site-packages/uvicorn/protocols/http/httptools_impl.py", line 426, in run_asgi
|
|
|
|
| 1 |
+
===== Application Startup at 2025-11-16 12:22:34 =====
|
| 2 |
|
| 3 |
+
INFO: Started server process [7]
|
| 4 |
INFO: Waiting for application startup.
|
| 5 |
+
INFO: 2025-11-16T12:22:44 - app.main: ============================================================
|
| 6 |
+
INFO: 2025-11-16T12:22:44 - app.main: π SwiftOps API v1.0.0 | PRODUCTION
|
| 7 |
+
INFO: 2025-11-16T12:22:44 - app.main: ============================================================
|
| 8 |
+
INFO: 2025-11-16T12:22:44 - app.main: π¦ Database:
|
| 9 |
+
INFO: 2025-11-16T12:22:47 - app.main: β Connected | 42 tables | 10 users
|
| 10 |
+
INFO: 2025-11-16T12:22:47 - app.main: πΎ Cache & Sessions:
|
| 11 |
+
INFO: 2025-11-16T12:22:48 - app.services.otp_service: β
OTP Service initialized with Redis storage
|
| 12 |
+
INFO: 2025-11-16T12:22:49 - app.main: β Redis: Connected
|
| 13 |
+
INFO: 2025-11-16T12:22:49 - app.main: π External Services:
|
| 14 |
+
INFO: 2025-11-16T12:22:50 - app.main: β Cloudinary: Connected
|
| 15 |
+
INFO: 2025-11-16T12:22:50 - app.main: β Resend: Configured
|
| 16 |
+
INFO: 2025-11-16T12:22:50 - app.main: β WASender: Connected
|
| 17 |
+
INFO: 2025-11-16T12:22:50 - app.main: β Supabase: Connected | 6 buckets
|
| 18 |
+
INFO: 2025-11-16T12:22:50 - app.main: ============================================================
|
| 19 |
+
INFO: 2025-11-16T12:22:50 - app.main: β
Startup complete | Ready to serve requests
|
| 20 |
+
INFO: 2025-11-16T12:22:50 - app.main: ============================================================
|
| 21 |
INFO: Application startup complete.
|
| 22 |
INFO: Uvicorn running on http://0.0.0.0:7860 (Press CTRL+C to quit)
|
| 23 |
+
INFO: 10.16.24.211:6043 - "GET /?logs=container HTTP/1.1" 200 OK
|
| 24 |
+
INFO: 10.16.39.23:26777 - "GET /?logs=container HTTP/1.1" 200 OK
|
| 25 |
+
INFO: 2025-11-16T12:23:02 - app.services.otp_service: OTP sent via whatsapp for password_reset (storage: redis)
|
| 26 |
+
INFO: 2025-11-16T12:23:03 - app.services.audit_service: Audit log created: create on otp by system
|
| 27 |
+
INFO: 10.16.6.135:36185 - "POST /api/v1/otp/send-public HTTP/1.1" 200 OK
|
| 28 |
+
INFO: 2025-11-16T12:23:22 - app.services.otp_service: OTP verified successfully for password_reset (storage: redis)
|
| 29 |
+
INFO: 2025-11-16T12:23:24 - app.services.audit_service: Audit log created: update on otp by system
|
| 30 |
+
INFO: 10.16.39.23:57790 - "POST /api/v1/otp/verify-public HTTP/1.1" 200 OK
|
| 31 |
+
INFO: 2025-11-16T12:25:53 - app.services.otp_service: OTP sent via whatsapp for password_reset (storage: redis)
|
| 32 |
+
INFO: 2025-11-16T12:25:54 - app.services.audit_service: Audit log created: create on otp by system
|
| 33 |
+
INFO: 10.16.39.23:17864 - "POST /api/v1/otp/send-public HTTP/1.1" 200 OK
|
| 34 |
+
INFO: 2025-11-16T12:26:08 - app.services.otp_service: OTP verified successfully for password_reset (storage: redis)
|
| 35 |
+
INFO: 2025-11-16T12:26:09 - app.services.audit_service: Audit log created: update on otp by system
|
| 36 |
+
INFO: 10.16.25.32:65235 - "POST /api/v1/otp/verify-public HTTP/1.1" 200 OK
|
| 37 |
+
INFO: 2025-11-16T12:26:11 - app.services.notification_service: Email sent to lesley@example.com: Reset Your SwiftOps Password
|
| 38 |
+
INFO: 2025-11-16T12:26:11 - app.services.password_reset_service: Password reset requested for: lesley@example.com
|
| 39 |
+
INFO: 2025-11-16T12:26:12 - app.services.audit_service: Audit log created: create on auth by system
|
| 40 |
+
INFO: 10.16.39.23:11335 - "POST /api/v1/auth/forgot-password HTTP/1.1" 500 Internal Server Error
|
| 41 |
ERROR: Exception in ASGI application
|
| 42 |
Traceback (most recent call last):
|
| 43 |
File "/usr/local/lib/python3.11/site-packages/uvicorn/protocols/http/httptools_impl.py", line 426, in run_asgi
|
src/app/api/v1/auth.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
"""
|
| 2 |
Authentication Endpoints - Supabase Auth Integration
|
| 3 |
"""
|
| 4 |
-
from fastapi import APIRouter, Depends, HTTPException, status, Request
|
| 5 |
from sqlalchemy.orm import Session
|
| 6 |
from app.api.deps import get_db, get_current_active_user
|
| 7 |
from app.core.rate_limit import limiter
|
|
@@ -22,7 +22,7 @@ router = APIRouter(prefix="/auth", tags=["Authentication"])
|
|
| 22 |
|
| 23 |
@router.post("/register", response_model=TokenResponse, status_code=status.HTTP_201_CREATED)
|
| 24 |
@limiter.limit("5/hour") # 5 registrations per hour per IP
|
| 25 |
-
async def register(request: Request, user_data: UserCreate, db: Session = Depends(get_db)):
|
| 26 |
"""
|
| 27 |
Register a new user account via Supabase Auth
|
| 28 |
|
|
@@ -120,7 +120,7 @@ async def register(request: Request, user_data: UserCreate, db: Session = Depend
|
|
| 120 |
|
| 121 |
@router.post("/login", response_model=TokenResponse)
|
| 122 |
@limiter.limit("10/minute") # 10 login attempts per minute per IP
|
| 123 |
-
async def login(request: Request, credentials: LoginRequest, db: Session = Depends(get_db)):
|
| 124 |
"""
|
| 125 |
Login with email and password via Supabase Auth
|
| 126 |
|
|
@@ -296,6 +296,7 @@ async def update_profile(
|
|
| 296 |
@limiter.limit("5/hour") # 5 password changes per hour per user
|
| 297 |
async def change_password(
|
| 298 |
request: Request,
|
|
|
|
| 299 |
password_data: PasswordChange,
|
| 300 |
current_user: User = Depends(get_current_active_user),
|
| 301 |
db: Session = Depends(get_db)
|
|
@@ -361,6 +362,7 @@ async def change_password(
|
|
| 361 |
@limiter.limit("3/hour") # 3 password reset requests per hour per IP
|
| 362 |
async def forgot_password(
|
| 363 |
request: Request,
|
|
|
|
| 364 |
request_data: ForgotPasswordRequest,
|
| 365 |
db: Session = Depends(get_db)
|
| 366 |
):
|
|
@@ -396,6 +398,7 @@ async def forgot_password(
|
|
| 396 |
@limiter.limit("5/hour") # 5 password reset attempts per hour per IP
|
| 397 |
async def reset_password(
|
| 398 |
request: Request,
|
|
|
|
| 399 |
reset_data: ResetPasswordRequest,
|
| 400 |
db: Session = Depends(get_db)
|
| 401 |
):
|
|
|
|
| 1 |
"""
|
| 2 |
Authentication Endpoints - Supabase Auth Integration
|
| 3 |
"""
|
| 4 |
+
from fastapi import APIRouter, Depends, HTTPException, status, Request, Response
|
| 5 |
from sqlalchemy.orm import Session
|
| 6 |
from app.api.deps import get_db, get_current_active_user
|
| 7 |
from app.core.rate_limit import limiter
|
|
|
|
| 22 |
|
| 23 |
@router.post("/register", response_model=TokenResponse, status_code=status.HTTP_201_CREATED)
|
| 24 |
@limiter.limit("5/hour") # 5 registrations per hour per IP
|
| 25 |
+
async def register(request: Request, response: Response, user_data: UserCreate, db: Session = Depends(get_db)):
|
| 26 |
"""
|
| 27 |
Register a new user account via Supabase Auth
|
| 28 |
|
|
|
|
| 120 |
|
| 121 |
@router.post("/login", response_model=TokenResponse)
|
| 122 |
@limiter.limit("10/minute") # 10 login attempts per minute per IP
|
| 123 |
+
async def login(request: Request, response: Response, credentials: LoginRequest, db: Session = Depends(get_db)):
|
| 124 |
"""
|
| 125 |
Login with email and password via Supabase Auth
|
| 126 |
|
|
|
|
| 296 |
@limiter.limit("5/hour") # 5 password changes per hour per user
|
| 297 |
async def change_password(
|
| 298 |
request: Request,
|
| 299 |
+
response: Response,
|
| 300 |
password_data: PasswordChange,
|
| 301 |
current_user: User = Depends(get_current_active_user),
|
| 302 |
db: Session = Depends(get_db)
|
|
|
|
| 362 |
@limiter.limit("3/hour") # 3 password reset requests per hour per IP
|
| 363 |
async def forgot_password(
|
| 364 |
request: Request,
|
| 365 |
+
response: Response,
|
| 366 |
request_data: ForgotPasswordRequest,
|
| 367 |
db: Session = Depends(get_db)
|
| 368 |
):
|
|
|
|
| 398 |
@limiter.limit("5/hour") # 5 password reset attempts per hour per IP
|
| 399 |
async def reset_password(
|
| 400 |
request: Request,
|
| 401 |
+
response: Response,
|
| 402 |
reset_data: ResetPasswordRequest,
|
| 403 |
db: Session = Depends(get_db)
|
| 404 |
):
|
tests/integration/reset-password-otp.js
CHANGED
|
@@ -143,7 +143,13 @@ async function sendOTP() {
|
|
| 143 |
|
| 144 |
console.log(`${colors.green}β OTP sent successfully!${colors.reset}`);
|
| 145 |
console.log(`${colors.dim}Message: ${response.message}${colors.reset}`);
|
| 146 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 147 |
|
| 148 |
return true;
|
| 149 |
} catch (error) {
|
|
@@ -165,12 +171,16 @@ async function verifyOTP(code) {
|
|
| 165 |
purpose: 'password_reset'
|
| 166 |
});
|
| 167 |
|
| 168 |
-
if (response.
|
| 169 |
console.log(`${colors.green}β OTP verified successfully!${colors.reset}\n`);
|
| 170 |
return true;
|
| 171 |
} else {
|
| 172 |
console.log(`${colors.red}β Invalid OTP${colors.reset}`);
|
| 173 |
-
console.log(`${colors.dim}Message: ${response.message}${colors.reset}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 174 |
return false;
|
| 175 |
}
|
| 176 |
} catch (error) {
|
|
|
|
| 143 |
|
| 144 |
console.log(`${colors.green}β OTP sent successfully!${colors.reset}`);
|
| 145 |
console.log(`${colors.dim}Message: ${response.message}${colors.reset}`);
|
| 146 |
+
if (response.expires_in_minutes) {
|
| 147 |
+
console.log(`${colors.dim}Expires in: ${response.expires_in_minutes} minutes${colors.reset}`);
|
| 148 |
+
}
|
| 149 |
+
if (response.masked_contact) {
|
| 150 |
+
console.log(`${colors.dim}Sent to: ${response.masked_contact}${colors.reset}`);
|
| 151 |
+
}
|
| 152 |
+
console.log();
|
| 153 |
|
| 154 |
return true;
|
| 155 |
} catch (error) {
|
|
|
|
| 171 |
purpose: 'password_reset'
|
| 172 |
});
|
| 173 |
|
| 174 |
+
if (response.verified) {
|
| 175 |
console.log(`${colors.green}β OTP verified successfully!${colors.reset}\n`);
|
| 176 |
return true;
|
| 177 |
} else {
|
| 178 |
console.log(`${colors.red}β Invalid OTP${colors.reset}`);
|
| 179 |
+
console.log(`${colors.dim}Message: ${response.message}${colors.reset}`);
|
| 180 |
+
if (response.attempts_remaining !== undefined) {
|
| 181 |
+
console.log(`${colors.dim}Attempts remaining: ${response.attempts_remaining}${colors.reset}`);
|
| 182 |
+
}
|
| 183 |
+
console.log();
|
| 184 |
return false;
|
| 185 |
}
|
| 186 |
} catch (error) {
|