feat: Implement full auth pipeline — login, register, JWT, bootstrap
Backend auth flow is now functional: - services/auth.rs: Argon2id password hashing, JWT access/refresh tokens - services/encryption.rs: AES-256-GCM encrypt/decrypt, hex token generation - api/auth.rs: Login, register, refresh, logout, /me endpoints - middleware/auth.rs: JWT Bearer token extractor (FromRequestParts) - db/users.rs + licenses.rs: Full CRUD with runtime queries (no compile-time DB) - main.rs: Bootstrap admin user on first run via ADMIN_EMAIL/ADMIN_PASSWORD env vars - NATS connection now optional for dev (graceful fallback) - Added hex and http crates to Cargo.toml Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,12 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::extract::FromRequestParts;
|
||||
use http::request::Parts;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::services::auth as auth_service;
|
||||
use crate::AppState;
|
||||
|
||||
/// Extractor that validates the JWT from the Authorization header
|
||||
/// and provides the authenticated user's identity to handlers.
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -12,21 +17,39 @@ pub struct AuthUser {
|
||||
pub role: Option<String>,
|
||||
}
|
||||
|
||||
// TODO: Implement JWT validation logic
|
||||
// - Extract Bearer token from Authorization header
|
||||
// - Decode and verify JWT signature using the app's secret
|
||||
// - Check token expiration
|
||||
// - Build AuthUser from claims
|
||||
// - Return 401 Unauthorized on any failure
|
||||
|
||||
#[axum::async_trait]
|
||||
impl<S> FromRequestParts<S> for AuthUser
|
||||
where
|
||||
S: Send + Sync,
|
||||
{
|
||||
impl FromRequestParts<Arc<AppState>> for AuthUser {
|
||||
type Rejection = http::StatusCode;
|
||||
|
||||
async fn from_request_parts(_parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
|
||||
todo!()
|
||||
async fn from_request_parts(
|
||||
parts: &mut Parts,
|
||||
state: &Arc<AppState>,
|
||||
) -> Result<Self, Self::Rejection> {
|
||||
// Extract Bearer token from Authorization header
|
||||
let auth_header = parts
|
||||
.headers
|
||||
.get(http::header::AUTHORIZATION)
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.ok_or(http::StatusCode::UNAUTHORIZED)?;
|
||||
|
||||
let token = auth_header
|
||||
.strip_prefix("Bearer ")
|
||||
.ok_or(http::StatusCode::UNAUTHORIZED)?;
|
||||
|
||||
// Validate JWT
|
||||
let claims = auth_service::validate_token(&state.config, token)
|
||||
.map_err(|_| http::StatusCode::UNAUTHORIZED)?;
|
||||
|
||||
// Ensure it's an access token, not a refresh token
|
||||
if claims.token_type != "access" {
|
||||
return Err(http::StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
Ok(AuthUser {
|
||||
user_id: claims.sub,
|
||||
email: claims.email,
|
||||
license_id: claims.license_id,
|
||||
role: claims.role,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user