feat: implement long-term user login with refresh tokens
All checks were successful
Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 3m23s

- Introduced a dual-token system for user authentication: a short-lived access token and a long-lived rotating refresh token.
- Created a new RefreshToken model to manage refresh tokens securely.
- Updated auth_api.py to handle login, refresh, and logout processes with the new token system.
- Enhanced security measures including token rotation and theft detection.
- Updated frontend to handle token refresh on 401 errors and adjusted SSE authentication.
- Removed CORS middleware as it's unnecessary behind the nginx proxy.
- Added tests to ensure functionality and security of the new token system.
This commit is contained in:
2026-03-01 19:27:25 -05:00
parent d7316bb00a
commit ebaef16daf
32 changed files with 713 additions and 201 deletions

View File

@@ -7,6 +7,7 @@ from db.db import users_db
from tinydb import Query
import jwt
from werkzeug.security import generate_password_hash
from tests.conftest import TEST_SECRET_KEY, TEST_REFRESH_TOKEN_EXPIRY_DAYS
# Test user credentials
TEST_EMAIL = "usertest@example.com"
@@ -50,9 +51,10 @@ def login_and_get_token(client, email, password):
"""Login and extract JWT token from response."""
resp = client.post('/auth/login', json={"email": email, "password": password})
assert resp.status_code == 200
# Extract token from Set-Cookie header
set_cookie = resp.headers.get("Set-Cookie")
assert set_cookie and "token=" in set_cookie
# Verify auth cookies are set
cookies = resp.headers.getlist('Set-Cookie')
cookie_str = ' '.join(cookies)
assert 'access_token=' in cookie_str
# Flask test client automatically handles cookies
return resp
@@ -63,7 +65,8 @@ def client():
app.register_blueprint(user_api)
app.register_blueprint(auth_api, url_prefix='/auth')
app.config['TESTING'] = True
app.config['SECRET_KEY'] = 'supersecretkey'
app.config['SECRET_KEY'] = TEST_SECRET_KEY
app.config['REFRESH_TOKEN_EXPIRY_DAYS'] = TEST_REFRESH_TOKEN_EXPIRY_DAYS
app.config['FRONTEND_URL'] = 'http://localhost:5173' # Needed for email_sender
with app.test_client() as client:
add_test_users()
@@ -200,7 +203,7 @@ def test_mark_for_deletion_clears_tokens(authenticated_client):
def test_mark_for_deletion_with_invalid_jwt(client):
"""Test marking for deletion with invalid JWT token."""
# Set invalid cookie manually
client.set_cookie('token', 'invalid.jwt.token')
client.set_cookie('access_token', 'invalid.jwt.token')
response = client.post('/user/mark-for-deletion', json={})
assert response.status_code == 401