Files
erp/crates/erp-auth/src/middleware/jwt_auth.rs
iven 9568dd7875 chore: apply cargo fmt across workspace and update docs
- Run cargo fmt on all Rust crates for consistent formatting
- Update CLAUDE.md with WASM plugin commands and dev.ps1 instructions
- Update wiki: add WASM plugin architecture, rewrite dev environment docs
- Minor frontend cleanup (unused imports)
2026-04-15 00:49:20 +08:00

65 lines
2.0 KiB
Rust

use axum::body::Body;
use axum::http::Request;
use axum::middleware::Next;
use axum::response::Response;
use erp_core::error::AppError;
use erp_core::types::TenantContext;
use crate::service::token_service::TokenService;
/// JWT authentication middleware function.
///
/// Extracts the `Bearer` token from the `Authorization` header, validates it
/// using `TokenService::decode_token`, and injects a `TenantContext` into the
/// request extensions so downstream handlers can access tenant/user identity.
///
/// The `jwt_secret` parameter is passed explicitly by the server crate at
/// middleware construction time, avoiding any circular dependency between
/// erp-auth and erp-server.
///
/// # Errors
///
/// Returns `AppError::Unauthorized` if:
/// - The `Authorization` header is missing
/// - The header value does not start with `"Bearer "`
/// - The token cannot be decoded or has expired
/// - The token type is not "access"
pub async fn jwt_auth_middleware_fn(
jwt_secret: String,
req: Request<Body>,
next: Next,
) -> Result<Response, AppError> {
let auth_header = req
.headers()
.get("Authorization")
.and_then(|v| v.to_str().ok())
.ok_or(AppError::Unauthorized)?;
let token = auth_header
.strip_prefix("Bearer ")
.ok_or(AppError::Unauthorized)?;
let claims =
TokenService::decode_token(token, &jwt_secret).map_err(|_| AppError::Unauthorized)?;
// Verify this is an access token, not a refresh token
if claims.token_type != "access" {
return Err(AppError::Unauthorized);
}
let ctx = TenantContext {
tenant_id: claims.tid,
user_id: claims.sub,
roles: claims.roles,
permissions: claims.permissions,
};
// Reconstruct the request with the TenantContext injected into extensions.
// We cannot borrow `req` mutably after reading headers, so we rebuild.
let (parts, body) = req.into_parts();
let mut req = Request::from_parts(parts, body);
req.extensions_mut().insert(ctx);
Ok(next.run(req).await)
}