feat(backend): Implement enhanced profile management

Phase 2.4 - Enhanced Profile Management

Features implemented:
- Get user profile endpoint
- Update user profile endpoint
- Delete user account endpoint with password confirmation
- Input validation on all profile fields
- Security: Password required for account deletion
- Security: All tokens revoked on deletion

New API endpoints:
- GET /api/users/me (protected)
- PUT /api/users/me (protected)
- DELETE /api/users/me (protected)

Security features:
- JWT token required for all operations
- Password confirmation required for deletion
- All tokens revoked on account deletion
- User data removed from database
- Input validation on all fields

Files modified:
- backend/src/handlers/users.rs
- backend/src/main.rs

Testing:
- backend/test-profile-management.sh
- backend/PROFILE-MANAGEMENT-IMPLEMENTED.md
This commit is contained in:
goose 2026-02-15 19:33:43 -03:00
parent b0729f846f
commit c69d3be302
4 changed files with 445 additions and 33 deletions

View file

@ -6,7 +6,7 @@ mod handlers;
mod middleware;
use axum::{
routing::{get, post},
routing::{get, post, put, delete},
Router,
middleware as axum_middleware,
};
@ -19,11 +19,9 @@ use config::Config;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// DEBUG: Print to stderr so we can see it in logs
eprintln!("NORMOGEN BACKEND STARTING...");
eprintln!("Loading environment variables...");
// Try to load .env, but don't fail if it doesn't exist
match dotenv::dotenv() {
Ok(path) => eprintln!("Loaded .env from: {:?}", path),
Err(e) => eprintln!("No .env file found (this is OK in Docker): {}", e),
@ -76,7 +74,6 @@ async fn main() -> anyhow::Result<()> {
eprintln!("Building router...");
// Create separate routers for public and protected routes
let public_routes = Router::new()
.route("/health", get(handlers::health_check))
.route("/ready", get(handlers::ready_check))
@ -84,7 +81,6 @@ async fn main() -> anyhow::Result<()> {
.route("/api/auth/login", post(handlers::login))
.route("/api/auth/refresh", post(handlers::refresh_token))
.route("/api/auth/logout", post(handlers::logout))
// Password recovery (public - user doesn't have access yet)
.route("/api/auth/recovery/verify", post(handlers::verify_recovery))
.route("/api/auth/recovery/reset-password", post(handlers::reset_password))
.layer(
@ -94,9 +90,9 @@ async fn main() -> anyhow::Result<()> {
);
let protected_routes = Router::new()
// Profile management
.route("/api/users/me", get(handlers::get_profile))
// Password recovery (protected - user must be logged in)
.route("/api/users/me", put(handlers::update_profile))
.route("/api/users/me", delete(handlers::delete_account))
.route("/api/auth/recovery/setup", post(handlers::setup_recovery))
.layer(
ServiceBuilder::new()
@ -108,7 +104,6 @@ async fn main() -> anyhow::Result<()> {
crate::middleware::auth::jwt_auth_middleware
));
// Merge public and protected routes
let app = public_routes.merge(protected_routes).with_state(app_state);
eprintln!("Binding to {}:{}...", config.server.host, config.server.port);