Phase 2.3: JWT Authentication implementation

- Implemented JWT-based authentication system with access and refresh tokens
- Added password hashing service using PBKDF2
- Created authentication handlers: register, login, refresh, logout
- Added protected routes with JWT middleware
- Created user profile handlers
- Fixed all compilation errors
- Added integration tests for authentication endpoints
- Added reqwest dependency for testing
- Created test script and environment example documentation

All changes:
- backend/src/auth/: Complete auth module (JWT, password, claims)
- backend/src/handlers/: Auth, users, and health handlers
- backend/src/middleware/: JWT authentication middleware
- backend/src/config/: Added AppState with Clone derive
- backend/src/main.rs: Fixed imports and added auth routes
- backend/src/db/mod.rs: Changed error handling to anyhow::Result
- backend/Cargo.toml: Added reqwest for testing
- backend/tests/auth_tests.rs: Integration tests
- thoughts/: Documentation updates (STATUS.md, env.example, test_auth.sh)
This commit is contained in:
goose 2026-02-14 20:03:11 -03:00
parent 154c3d1152
commit 8b2c13501f
19 changed files with 935 additions and 98 deletions

152
backend/tests/auth_tests.rs Normal file
View file

@ -0,0 +1,152 @@
use reqwest::Client;
use serde_json::{json, Value};
const BASE_URL: &str = "http://127.0.0.1:8000";
#[tokio::test]
async fn test_health_check() {
let client = Client::new();
let response = client.get(&format!("{}/health", BASE_URL))
.send()
.await
.expect("Failed to send request");
assert_eq!(response.status(), 200);
}
#[tokio::test]
async fn test_ready_check() {
let client = Client::new();
let response = client.get(&format!("{}/ready", BASE_URL))
.send()
.await
.expect("Failed to send request");
assert_eq!(response.status(), 200);
}
#[tokio::test]
async fn test_register_user() {
let client = Client::new();
let email = format!("test_{}@example.com", uuid::Uuid::new_v4());
let payload = json!({
"email": email,
"password_hash": "hashed_password_placeholder",
"encrypted_recovery_phrase": "encrypted_phrase_placeholder",
"recovery_phrase_iv": "iv_placeholder",
"recovery_phrase_auth_tag": "auth_tag_placeholder"
});
let response = client.post(&format!("{}/api/auth/register", BASE_URL))
.json(&payload)
.send()
.await
.expect("Failed to send request");
assert_eq!(response.status(), 200);
let json: Value = response.json().await.expect("Failed to parse JSON");
assert_eq!(json["email"], email);
assert!(json["user_id"].is_string());
}
#[tokio::test]
async fn test_login() {
let client = Client::new();
let email = format!("test_{}@example.com", uuid::Uuid::new_v4());
// First register a user
let register_payload = json!({
"email": email,
"password_hash": "hashed_password_placeholder",
"encrypted_recovery_phrase": "encrypted_phrase_placeholder",
"recovery_phrase_iv": "iv_placeholder",
"recovery_phrase_auth_tag": "auth_tag_placeholder"
});
let _reg_response = client.post(&format!("{}/api/auth/register", BASE_URL))
.json(&register_payload)
.send()
.await
.expect("Failed to send request");
// Now login
let login_payload = json!({
"email": email,
"password_hash": "hashed_password_placeholder"
});
let response = client.post(&format!("{}/api/auth/login", BASE_URL))
.json(&login_payload)
.send()
.await
.expect("Failed to send request");
assert_eq!(response.status(), 200);
let json: Value = response.json().await.expect("Failed to parse JSON");
assert!(json["access_token"].is_string());
assert!(json["refresh_token"].is_string());
assert_eq!(json["email"], email);
}
#[tokio::test]
async fn test_get_profile_without_auth() {
let client = Client::new();
let response = client.get(&format!("{}/api/users/me", BASE_URL))
.send()
.await
.expect("Failed to send request");
// Should return 401 Unauthorized without auth token
assert_eq!(response.status(), 401);
}
#[tokio::test]
async fn test_get_profile_with_auth() {
let client = Client::new();
let email = format!("test_{}@example.com", uuid::Uuid::new_v4());
// Register and login
let register_payload = json!({
"email": email,
"password_hash": "hashed_password_placeholder",
"encrypted_recovery_phrase": "encrypted_phrase_placeholder",
"recovery_phrase_iv": "iv_placeholder",
"recovery_phrase_auth_tag": "auth_tag_placeholder"
});
client.post(&format!("{}/api/auth/register", BASE_URL))
.json(&register_payload)
.send()
.await
.expect("Failed to send request");
let login_payload = json!({
"email": email,
"password_hash": "hashed_password_placeholder"
});
let login_response = client.post(&format!("{}/api/auth/login", BASE_URL))
.json(&login_payload)
.send()
.await
.expect("Failed to send request");
let login_json: Value = login_response.json().await.expect("Failed to parse JSON");
let access_token = login_json["access_token"].as_str().expect("No access token");
// Get profile with auth token
let response = client.get(&format!("{}/api/users/me", BASE_URL))
.header("Authorization", format!("Bearer {}", access_token))
.send()
.await
.expect("Failed to send request");
assert_eq!(response.status(), 200);
let json: Value = response.json().await.expect("Failed to parse JSON");
assert_eq!(json["email"], email);
}