mirror of
https://github.com/Such-Software/smirk-backend-core.git
synced 2026-10-17 15:31:38 +02:00
Third wallet-handler slice. Authenticated, view-only proxies over the
GrinClient — the backend holds no Grin spend key; the wallet exports its
rewind_hash (a public-derived view credential) per scan, the backend forwards
it to grin-wallet's scan_rewind_hash and stores no secret. Spending happens in
the wallet; the backend only broadcasts the finalized tx.
Endpoints (under /api/v1/wallet/grin):
- POST scan {rewind_hash, start_height?} -> {outputs, total_balance,
last_pmmr_index} (incremental resume index)
- GET height -> {height}
- POST broadcast {tx} -> {ok} (node push_transaction)
- JWT-gated. rewind_hash is a view credential: GrinScanRequest omits Debug and
is skipped from every span (never logged), and is validated as 64 hex before
any grin call (applying the wallet-handler review's secret-DTO lesson).
- Feature-gated: grin disabled -> 400.
- Shared validate_hex64 in wallet/mod.rs (view_key + rewind_hash); 3 paths +
schemas + grin tag registered; spec regenerated, drift-clean.
4 L1 integration tests (auth gate on scan/height/broadcast; malformed
rewind_hash 400).
Also: dropped Debug from the xmr_wow view_key-bearing request DTOs
(ViewRequest/RegisterRequest) per the review — defense-in-depth against a
cleartext view_key dump.
65 lines
1.7 KiB
Rust
65 lines
1.7 KiB
Rust
//! L1 integration: Grin (view-only) wallet endpoints through the real router.
|
|
//!
|
|
//! Deterministic, no-network paths: the JWT gate and rewind_hash validation,
|
|
//! resolved before any grin-wallet call. The networked happy paths live in the
|
|
//! separate gitignored L3 harness.
|
|
|
|
mod common;
|
|
|
|
use axum::http::StatusCode;
|
|
use serde_json::json;
|
|
|
|
#[tokio::test]
|
|
async fn scan_requires_a_token() {
|
|
let app = require_app!();
|
|
let (status, _) = app
|
|
.request(
|
|
"POST",
|
|
"/api/v1/wallet/grin/scan",
|
|
None,
|
|
Some(json!({ "rewind_hash": "00" })),
|
|
)
|
|
.await;
|
|
assert_eq!(status, StatusCode::UNAUTHORIZED);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn scan_rejects_malformed_rewind_hash() {
|
|
let app = require_app!();
|
|
let (_uid, access, _r) = app.mint_session().await;
|
|
// Authed, but the rewind_hash is not 64 hex -> 400 before any grin call.
|
|
let (status, body) = app
|
|
.request(
|
|
"POST",
|
|
"/api/v1/wallet/grin/scan",
|
|
Some(&access),
|
|
Some(json!({ "rewind_hash": "tooshort" })),
|
|
)
|
|
.await;
|
|
assert_eq!(status, StatusCode::BAD_REQUEST);
|
|
assert_eq!(body["code"], "VALIDATION_ERROR");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn height_requires_a_token() {
|
|
let app = require_app!();
|
|
let (status, _) = app
|
|
.request("GET", "/api/v1/wallet/grin/height", None, None)
|
|
.await;
|
|
assert_eq!(status, StatusCode::UNAUTHORIZED);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn broadcast_requires_a_token() {
|
|
let app = require_app!();
|
|
let (status, _) = app
|
|
.request(
|
|
"POST",
|
|
"/api/v1/wallet/grin/broadcast",
|
|
None,
|
|
Some(json!({ "tx": {} })),
|
|
)
|
|
.await;
|
|
assert_eq!(status, StatusCode::UNAUTHORIZED);
|
|
}
|