(fix) user_id now retrieving from JWT not from URL

This commit is contained in:
Rorik Star Platinum 2025-11-08 20:18:59 +03:00
parent 80ed37647b
commit 4d0ad0226f
3 changed files with 86 additions and 78 deletions

View file

@ -1,10 +1,16 @@
// src/route.rs
// CORRECT VERSION - keeps your protected_routes pattern
use axum::{
middleware,
routing::{get, post},
Router,
};
use crate::{auth, state::AppState};
pub mod handlers;
use axum::{middleware, routing::{get, post}, Router};
use crate::{state::AppState, auth};
pub fn router(app_state: AppState) -> Router {
// Public routes (no auth required)
let public_routes = Router::new()
@ -12,31 +18,30 @@ pub fn router(app_state: AppState) -> Router {
.route("/api/auth/register", post(auth::handlers::register_handler))
.route("/api/auth/login", post(auth::handlers::login_handler));
// Protected routes (auth required)
// Protected routes (auth required) - user_id extracted from JWT in handlers
let protected_routes = Router::new()
.route("/api/auth/me", get(auth::handlers::me_handler))
.route("/api/consent/{bank}/{user_id}",
.route("/api/consent/{bank}", // ← Removed {user_id} - get from JWT!
post(handlers::create_consent_handler)
.get(handlers::get_consent_handler)
.delete(handlers::delete_consent_handler)
)
.route("/api/accounts/{bank}/{user_id}",
.route("/api/accounts/{bank}", // ← Removed {user_id} - get from JWT!
get(handlers::get_accounts_handler)
)
.route("/api/transactions/{bank}/{user_id}/{account_id}",
.route("/api/transactions/{bank}/{account_id}", // ← Removed {user_id}
get(handlers::get_transactions_handler)
)
.route("/api/transactions/{bank_user_id}",
get(handlers::get_all_transactions_handler) // ← Новый эндпоинт
.route("/api/transactions", // ← Get all transactions (user from JWT)
get(handlers::get_all_transactions_handler)
)
.route("/api/balances/{bank}/{account_id}",
get(handlers::get_balances_handler)
)
.layer(middleware::from_fn(auth::auth_middleware));
.layer(middleware::from_fn(auth::auth_middleware)); // ← Your existing middleware
// Merge both
public_routes
.merge(protected_routes)
.with_state(app_state)
}

View file

@ -15,7 +15,9 @@ use crate::{
db,
};
use crate::auth::jwt::Claims;
use tracing::info;
use axum::Extension;
// --- Health Check ---
@ -48,29 +50,33 @@ struct ConsentCreatedResponse {
pub async fn create_consent_handler(
State(state): State<AppState>,
Path((bank_code, user_id)): Path<(String, String)>,
Path(bank_code): Path<String>, // ← Only bank_code now
Extension(claims): Extension<Claims>, // ← Get user_id from JWT!
) -> impl IntoResponse {
let user_id = &claims.bank_user_id; // ← Extract from JWT
let bank = bank_code.parse::<Bank>()
.map_err(|_| (StatusCode::BAD_REQUEST, Json(json!({
"error": "Invalid bank code. Use: vbank, abank, or sbank"
}))))?;
// Rest stays the same - just use `user_id` variable
let client = state.banking_clients.get_client(bank);
let consent_response = client
.request_consent(&user_id)
.request_consent(user_id) // ← Use extracted user_id
.await
.map_err(map_banking_error)?;
let expires_at = chrono::DateTime::parse_from_rfc3339(&consent_response.created_at)
.ok()
.map(|dt| dt.with_timezone(&chrono::Utc))
.unwrap_or_else(|| chrono::Utc::now()) // fallback to now if parse fails
.unwrap_or_else(|| chrono::Utc::now())
+ chrono::Duration::days(365);
db::consents::store_consent(
&state.db_pool,
&user_id,
user_id, // ← Use extracted user_id
bank.code(),
&consent_response.consent_id,
expires_at,
@ -94,14 +100,17 @@ pub async fn create_consent_handler(
pub async fn get_consent_handler(
State(state): State<AppState>,
Path((bank_code, user_id)): Path<(String, String)>,
Path(bank_code): Path<String>, // ← Only bank_code now
Extension(claims): Extension<Claims>, // ← Get user_id from JWT!
) -> impl IntoResponse {
let user_id = &claims.bank_user_id; // ← Extract from JWT
let bank = bank_code.parse::<Bank>()
.map_err(|_| (StatusCode::BAD_REQUEST, Json(json!({
"error": "Invalid bank code"
}))))?;
db::consents::get_valid_consent(&state.db_pool, &user_id, bank.code())
db::consents::get_valid_consent(&state.db_pool, user_id, bank.code())
.await
.map_err(|e| (
StatusCode::INTERNAL_SERVER_ERROR,
@ -121,12 +130,15 @@ pub async fn get_consent_handler(
pub async fn get_accounts_handler(
State(state): State<AppState>,
Path((bank_code, user_id)): Path<(String, String)>,
Path(bank_code): Path<String>, // ← Only bank_code now
Extension(claims): Extension<Claims>, // ← Get user_id from JWT!
) -> Result<Json<serde_json::Value>, (StatusCode, Json<serde_json::Value>)> {
let user_id = &claims.bank_user_id; // ← Extract from JWT
let bank = bank_code.parse::<Bank>()
.map_err(|_| (StatusCode::BAD_REQUEST, Json(json!({ "error": "Invalid bank code" }))))?;
let consent_id = db::consents::get_valid_consent(&state.db_pool, &user_id, bank.code())
let consent_id = db::consents::get_valid_consent(&state.db_pool, user_id, bank.code())
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({ "error": e.to_string() }))))?
.ok_or_else(|| (
@ -136,16 +148,16 @@ pub async fn get_accounts_handler(
let client = state.banking_clients.get_client(bank);
let accounts_response = client.get_accounts(&user_id, &consent_id)
let accounts_response = client.get_accounts(user_id, &consent_id)
.await
.map_err(map_banking_error)?;
// ✨ NEW: Save accounts to database
// ✨ Save accounts to database
for account in &accounts_response.data.account {
let _ = db::accounts::store_account(
&state.db_pool,
&account.account_id,
&user_id,
user_id, // ← Use extracted user_id
bank.code(),
account.status.as_deref().unwrap_or("unknown"),
&account.currency,
@ -171,13 +183,16 @@ pub struct TransactionQuery {
pub async fn get_transactions_handler(
State(state): State<AppState>,
Path((bank_code, user_id, account_id)): Path<(String, String, String)>,
Path((bank_code, account_id)): Path<(String, String)>, // ← Changed: no user_id
Query(params): Query<TransactionQuery>,
Extension(claims): Extension<Claims>, // ← Get user_id from JWT!
) -> Result<Json<serde_json::Value>, (StatusCode, Json<serde_json::Value>)> {
let user_id = &claims.bank_user_id; // ← Extract from JWT
let bank = bank_code.parse::<Bank>()
.map_err(|_| (StatusCode::BAD_REQUEST, Json(json!({ "error": "Invalid bank code" }))))?;
let consent_id = db::consents::get_valid_consent(&state.db_pool, &user_id, bank.code())
let consent_id = db::consents::get_valid_consent(&state.db_pool, user_id, bank.code())
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({ "error": e.to_string() }))))?
.ok_or_else(|| (
@ -191,7 +206,7 @@ pub async fn get_transactions_handler(
.await
.map_err(map_banking_error)?;
// ✨ NEW: Save all transactions to cache
// ✨ Save all transactions to cache
for tx in &transactions_response.data.transaction {
let _ = db::transactions::store_transaction(
&state.db_pool,
@ -202,8 +217,8 @@ pub async fn get_transactions_handler(
&tx.amount.currency,
&tx.credit_debit_indicator,
&tx.status,
tx.booking_date_time, // Already DateTime<Utc>
tx.value_date_time, // Already Option<DateTime<Utc>>
tx.booking_date_time,
tx.value_date_time,
&tx.transaction_information,
tx.bank_transaction_code.as_ref().map(|b| b.code.as_str()),
).await;
@ -214,9 +229,10 @@ pub async fn get_transactions_handler(
pub async fn get_all_transactions_handler(
State(state): State<AppState>,
Path(bank_user_id): Path<String>,
Query(params): Query<TransactionQuery>, // ← Добавили query params
Query(params): Query<TransactionQuery>,
Extension(claims): Extension<Claims>, // ← Add this
) -> Result<Json<serde_json::Value>, (StatusCode, Json<serde_json::Value>)> {
let bank_user_id = &claims.bank_user_id;
let page = params.page.unwrap_or(1);
let limit = params.limit.unwrap_or(20); // Default: 20 на странице для ленты
@ -300,12 +316,15 @@ pub async fn get_all_transactions_handler(
pub async fn delete_consent_handler(
State(state): State<AppState>,
Path((bank_code, user_id)): Path<(String, String)>,
Path(bank_code): Path<String>, // ← Only bank_code now
Extension(claims): Extension<Claims>, // ← Get user_id from JWT!
) -> impl IntoResponse {
let user_id = &claims.bank_user_id; // ← Extract from JWT
let bank = bank_code.parse::<Bank>()
.map_err(|_| (StatusCode::BAD_REQUEST, Json(json!({ "error": "Invalid bank code" }))))?;
let consent_id = db::consents::get_valid_consent(&state.db_pool, &user_id, bank.code())
let consent_id = db::consents::get_valid_consent(&state.db_pool, user_id, bank.code())
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({ "error": e.to_string() }))))?
.ok_or_else(|| (StatusCode::NOT_FOUND, Json(json!({ "error": "Consent not found" }))))?;

View file

@ -1,6 +1,6 @@
#!/bin/bash
# Save as test_multiberry.sh
# Extended comprehensive test with DB verification
# Save as test_multiberry_secure.sh
# Tests secure API - user_id extracted from JWT token automatically
BASE_URL="http://localhost:3000/api"
@ -9,7 +9,7 @@ echo "1⃣ REGISTER USER"
echo "=========================================="
REGISTER=$(curl -s -X POST $BASE_URL/auth/register \
-H 'Content-Type: application/json' \
-d '{"bank_user_number": 1, "password": "testpass123"}')
-d '{"bank_user_number": 2, "password": "testpass123"}')
echo "$REGISTER" | jq .
TOKEN=$(echo "$REGISTER" | jq -r '.token')
@ -30,14 +30,14 @@ echo ""
echo "=========================================="
echo "3⃣ GET ME (verify auth middleware)"
echo "=========================================="
curl -s http://localhost:3000/api/auth/me \
curl -s $BASE_URL/auth/me \
-H "Authorization: Bearer $TOKEN" | jq .
echo ""
echo "=========================================="
echo "4⃣ REQUEST CONSENT from VBank"
echo "4⃣ REQUEST CONSENT from VBank (user from JWT)"
echo "=========================================="
CONSENT=$(curl -s -X POST $BASE_URL/consent/vbank/$BANK_USER_ID \
CONSENT=$(curl -s -X POST $BASE_URL/consent/vbank \
-H "Authorization: Bearer $TOKEN")
echo "$CONSENT" | jq .
@ -46,9 +46,9 @@ echo "✅ Consent ID: $CONSENT_ID"
echo ""
echo "=========================================="
echo "5⃣ GET ACCOUNTS (auto-saved to DB)"
echo "5⃣ GET ACCOUNTS (user from JWT, auto-saved to DB)"
echo "=========================================="
ACCOUNTS=$(curl -s $BASE_URL/accounts/vbank/$BANK_USER_ID \
ACCOUNTS=$(curl -s $BASE_URL/accounts/vbank \
-H "Authorization: Bearer $TOKEN")
echo "$ACCOUNTS" | jq .
@ -68,9 +68,9 @@ echo "✅ Current Balance: $BALANCE_AMOUNT RUB"
echo ""
echo "=========================================="
echo "7⃣ GET TRANSACTIONS (page 1, limit 6 - auto-saved)"
echo "7⃣ GET TRANSACTIONS (user from JWT, page 1, limit 6)"
echo "=========================================="
TRANS_PAGE1=$(curl -s "$BASE_URL/transactions/vbank/$BANK_USER_ID/$ACCOUNT_ID?page=1&limit=6" \
TRANS_PAGE1=$(curl -s "$BASE_URL/transactions/vbank/$ACCOUNT_ID?page=1&limit=6" \
-H "Authorization: Bearer $TOKEN")
echo "$TRANS_PAGE1" | jq .
@ -81,54 +81,38 @@ echo ""
echo "=========================================="
echo "8⃣ GET TRANSACTIONS (page 2, limit 6)"
echo "=========================================="
curl -s "$BASE_URL/transactions/vbank/$BANK_USER_ID/$ACCOUNT_ID?page=2&limit=6" \
curl -s "$BASE_URL/transactions/vbank/$ACCOUNT_ID?page=2&limit=6" \
-H "Authorization: Bearer $TOKEN" | jq .
echo ""
echo "=========================================="
echo "9DELETE CONSENT"
echo "9GET ALL TRANSACTIONS (unified view from all banks)"
echo "=========================================="
curl -s -X DELETE $BASE_URL/consent/vbank/$BANK_USER_ID \
curl -s "$BASE_URL/transactions?page=1&limit=10" \
-H "Authorization: Bearer $TOKEN" | jq .
echo ""
echo "=========================================="
echo "🔟 VERIFY DATABASE CACHE"
echo "🔟 DELETE CONSENT (user from JWT)"
echo "=========================================="
echo ""
echo "Run in another terminal:"
echo "just psql-exec"
echo ""
echo "Then execute these queries:"
echo ""
echo "-- Check users registered:"
echo "SELECT bank_user_id, created_at FROM users ORDER BY created_at DESC LIMIT 5;"
echo ""
echo "-- Check consents granted:"
echo "SELECT user_id, bank_code, consent_id, status, expires_at FROM user_consents;"
echo ""
echo "-- Check accounts cached:"
echo "SELECT account_id, user_id, bank_code, nickname, currency FROM accounts;"
echo ""
echo "-- Check balances cached:"
echo "SELECT account_id, balance_type, amount, currency, date_time FROM balances;"
echo ""
echo "-- Check transactions cached (show count by account):"
echo "SELECT account_id, COUNT(*) as tx_count, MIN(booking_date_time) as oldest, MAX(booking_date_time) as newest FROM transactions GROUP BY account_id;"
echo ""
echo "-- Show recent transactions:"
echo "SELECT transaction_id, account_id, amount, currency, credit_debit_indicator, transaction_information, booking_date_time FROM transactions ORDER BY booking_date_time DESC LIMIT 10;"
curl -s -X DELETE $BASE_URL/consent/vbank \
-H "Authorization: Bearer $TOKEN" | jq .
echo ""
echo "=========================================="
echo "✅ FULL TEST COMPLETE!"
echo "✅ FULL SECURE TEST COMPLETE!"
echo "=========================================="
echo ""
echo "Summary:"
echo "✅ Authentication (register/login/auth middleware)"
echo "✅ Consent Management (request consent)"
echo "✅ Account Aggregation (fetch & cache accounts)"
echo "✅ Balance Retrieval (fetch & cache balances)"
echo "✅ Transaction History (fetch & cache transactions)"
echo "✅ Data Persistence (all cached in PostgreSQL)"
echo "🔒 Security Benefits:"
echo " ✅ User cannot manipulate user_id in URL"
echo " ✅ All user identification comes from JWT"
echo " ✅ Frontend only needs to send token"
echo " ✅ Backend automatically knows WHO is making request"
echo ""
echo "📊 Data Aggregation:"
echo " ✅ Accounts cached from bank"
echo " ✅ Balances cached"
echo " ✅ Transactions cached"
echo " ✅ Multi-bank support ready"
echo ""