commit 425313290a4ed284a97b430a9ada4f0ac5990264
parent 2a7d903ae13ef097b4503df7394d936d3ee8265f
Author: Antoine A <>
Date: Tue, 7 Jul 2026 18:40:13 +0200
common: fix some API found bugs
Diffstat:
16 files changed, 62 insertions(+), 39 deletions(-)
diff --git a/Makefile b/Makefile
@@ -15,8 +15,8 @@ all: build
build:
cargo build --release --bin taler-magnet-bank --bin taler-cyclos --bin taler-apns-relay
-.PHONY: install-nobuild-files
-install-nobuild-files:
+.PHONY: install-files
+install-files:
install -m 644 -D -t $(share_dir)/taler-magnet-bank/config.d adapters/taler-magnet-bank/magnet-bank.conf
install -m 644 -D -t $(share_dir)/taler-magnet-bank/sql common/taler-common/db/versioning.sql
install -m 644 -D -t $(share_dir)/taler-magnet-bank/sql adapters/taler-magnet-bank/db/magnet-bank*.sql
@@ -37,15 +37,15 @@ install-nobuild-files:
install -D -t $(bin_dir) contrib/taler-apns-relay-dbconfig
.PHONY: install
-install: build install-nobuild-files
+install: build install-files
install -D -t $(bin_dir) target/release/taler-magnet-bank
install -D -t $(bin_dir) target/release/taler-cyclos
install -D -t $(bin_dir) target/release/taler-apns-relay
.PHONY: check
-check: install-nobuild-files
- cargo clippy --all-targets
+check: install-files
cargo test
+ cargo clippy --all-targets
.PHONY: doc
doc:
diff --git a/adapters/taler-cyclos/src/db.rs b/adapters/taler-cyclos/src/db.rs
@@ -684,7 +684,7 @@ pub async fn transfer_by_id(
status: r.try_get(0)?,
status_msg: r.try_get(1)?,
amount: r.try_get_amount(2, currency)?,
- origin_exchange_url: r.try_get(3)?,
+ exchange_base_url: r.try_get(3)?,
metadata: r.try_get(4)?,
wtid: r.try_get(5)?,
credit_account: r.try_get_cyclos_fullpaytouri(6, 7, root)?,
diff --git a/adapters/taler-magnet-bank/src/db.rs b/adapters/taler-magnet-bank/src/db.rs
@@ -688,7 +688,7 @@ pub async fn transfer_by_id(db: &PgPool, id: u64) -> sqlx::Result<Option<Transfe
status: r.try_get(0)?,
status_msg: r.try_get(1)?,
amount: r.try_get_amount(2, &CURR)?,
- origin_exchange_url: r.try_get(3)?,
+ exchange_base_url: r.try_get(3)?,
metadata: r.try_get(4)?,
wtid: r.try_get(5)?,
credit_account: r.try_get_iban(6)?.as_full_uri(r.try_get(7)?),
diff --git a/common/taler-api/src/auth.rs b/common/taler-api/src/auth.rs
@@ -80,14 +80,16 @@ pub async fn auth_middleware(
return Err(failure(
ErrorCode::GENERIC_UNAUTHORIZED,
"Authorization header is malformed",
- ));
+ )
+ .with_header(WWW_AUTHENTICATE, challenge.clone()));
};
if scheme != hscheme {
return Err(failure(
ErrorCode::GENERIC_UNAUTHORIZED,
format!("Authorization method '{hscheme}' wrong or not supported"),
- ));
+ )
+ .with_header(WWW_AUTHENTICATE, challenge.clone()));
}
Ok(parameter)
@@ -96,15 +98,25 @@ pub async fn auth_middleware(
match &state.method {
AuthMethod::Basic(token) => match parse_auth(&req, "Basic", &state.challenge) {
Ok(htoken) => {
- if htoken != token {
- return failure_code(ErrorCode::GENERIC_TOKEN_UNKNOWN).into_response();
+ if aws_lc_rs::constant_time::verify_slices_are_equal(
+ htoken.as_bytes(),
+ token.as_bytes(),
+ )
+ .is_ok()
+ {
+ return failure_code(ErrorCode::GENERIC_UNAUTHORIZED).into_response();
}
}
Err(err) => return err.into_response(),
},
AuthMethod::Bearer(token) => match parse_auth(&req, "Bearer", &state.challenge) {
Ok(htoken) => {
- if htoken != token {
+ if aws_lc_rs::constant_time::verify_slices_are_equal(
+ htoken.as_bytes(),
+ token.as_bytes(),
+ )
+ .is_ok()
+ {
return failure_code(ErrorCode::GENERIC_TOKEN_UNKNOWN).into_response();
}
}
diff --git a/common/taler-api/src/db.rs b/common/taler-api/src/db.rs
@@ -131,7 +131,7 @@ pub async fn page<'a, 'b, R: Send + Unpin>(
if params.backward() { "DESC" } else { "ASC" }
));
builder
- .push_bind(params.limit.abs())
+ .push_bind(params.len())
.build()
.try_map(map)
.fetch_all(db)
diff --git a/common/taler-api/src/notification.rs b/common/taler-api/src/notification.rs
@@ -79,7 +79,7 @@ impl<K: Eq + Hash, V> NotificationChannel<K, V> {
}
}
-impl<K: Eq + Hash + Clone, V: Default> NotificationChannel<K, V> {
+impl<K: Eq + Hash + Clone, V: Default + Eq> NotificationChannel<K, V> {
/// Subscribe to events for a specific username.
/// Creates the channel lazily on first subscriber.
pub fn subscribe(&self, key: K) -> watch::Receiver<V> {
@@ -95,12 +95,15 @@ impl<K: Eq + Hash + Clone, V: Default> NotificationChannel<K, V> {
/// Dispatch a notification to the right user's channel.
pub fn dispatch(&self, key: &K, event: V) {
if let Some(tx) = self.map.get(key) {
- tx.send(event).ok();
- /*// send_if_modified avoids waking receivers if nothing changed
+ // send_if_modified avoids waking receivers if nothing changed
let _ = tx.send_if_modified(|current| {
- *current = Some(event.clone());
- true
- });*/
+ if *current != event {
+ *current = event;
+ true
+ } else {
+ false
+ }
+ });
}
}
diff --git a/common/taler-api/src/test/db.rs b/common/taler-api/src/test/db.rs
@@ -165,7 +165,7 @@ pub async fn transfer_by_id(
status: r.try_get("status")?,
status_msg: r.try_get("status_msg")?,
amount: r.try_get_amount("amount", currency)?,
- origin_exchange_url: r.try_get("exchange_base_url")?,
+ exchange_base_url: r.try_get("exchange_base_url")?,
metadata: r.try_get("metadata")?,
wtid: r.try_get("wtid")?,
credit_account: r.try_get_payto("credit_payto")?,
diff --git a/common/taler-common/src/api/params.rs b/common/taler-common/src/api/params.rs
@@ -43,17 +43,17 @@ pub struct PageParams {
}
impl PageParams {
- const MAX_PAGE_SIZE: i64 = 1024;
+ const MAX_PAGE_SIZE: u64 = 1024;
pub fn check(self) -> Result<Page, ParamsErr> {
Self::check_custom(self, Self::MAX_PAGE_SIZE)
}
- pub fn check_custom(self, max_page_size: i64) -> Result<Page, ParamsErr> {
+ pub fn check_custom(self, max_page_size: u64) -> Result<Page, ParamsErr> {
let limit = self.limit.unwrap_or(-20);
if limit == 0 {
return Err(param_err("limit", format!("must be non-zero got {limit}")));
- } else if limit > max_page_size {
+ } else if limit.unsigned_abs() > max_page_size {
return Err(param_err(
"limit",
format!("must be <= {max_page_size} for {limit}"),
@@ -94,6 +94,10 @@ impl Page {
pub fn backward(&self) -> bool {
self.limit < 0
}
+
+ pub fn len(&self) -> i64 {
+ self.limit.saturating_abs()
+ }
}
#[serde_as]
@@ -142,7 +146,7 @@ impl HistoryParams {
pub fn check_custom(
self,
- max_page_size: i64,
+ max_page_size: u64,
max_timeout_ms: u64,
) -> Result<History, ParamsErr> {
Ok(History {
diff --git a/common/taler-common/src/api/wire.rs b/common/taler-common/src/api/wire.rs
@@ -80,7 +80,7 @@ pub struct TransferStatus {
pub status: TransferState,
pub status_msg: Option<String>,
pub amount: Amount,
- pub origin_exchange_url: String,
+ pub exchange_base_url: String,
pub metadata: Option<CompactString>,
pub wtid: ShortHashCode,
pub credit_account: PaytoURI,
diff --git a/common/taler-common/src/config.rs b/common/taler-common/src/config.rs
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2025, 2026 Taler Systems SA
+ Copyright (C) 2025-2026 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -437,7 +437,7 @@ pub mod parser {
/** Search for the binary installation path in PATH */
fn install_path(&self) -> Result<PathBuf, (PathBuf, std::io::Error)> {
- let path_env = std::env::var("PATH").unwrap();
+ let path_env = std::env::var("PATH").unwrap_or_default();
for path_dir in path_env.split(':') {
let path_dir = PathBuf::from(path_dir);
let bin_path = path_dir.join(self.exec_name);
@@ -893,14 +893,14 @@ impl<'cfg, 'arg> Section<'cfg, 'arg> {
/** Access [option] as base32 encode bytes */
pub fn b32(&self, option: &'arg str) -> Value<'arg, Vec<u8>> {
- self.value("hex", option, |it| {
+ self.value("b32", option, |it| {
crate::encoding::base32::decode(it.as_bytes())
})
}
/** Access [option] as base64 encode bytes */
pub fn b64(&self, option: &'arg str) -> Value<'arg, Vec<u8>> {
- self.value("hex", option, |it| {
+ self.value("b64", option, |it| {
crate::encoding::base64::decode(it.as_bytes())
})
}
diff --git a/common/taler-common/src/types/amount.rs b/common/taler-common/src/types/amount.rs
@@ -359,14 +359,18 @@ impl Amount {
}
pub fn try_add(self, rhs: &Self) -> Option<Self> {
- assert_eq!(self.currency, rhs.currency);
- let decimal = self.decimal().try_add(&rhs.decimal())?.normalize()?;
+ if self.currency != rhs.currency {
+ return None;
+ }
+ let decimal = self.decimal().try_add(&rhs.decimal())?;
Some((self.currency, decimal).into())
}
pub fn try_sub(self, rhs: &Self) -> Option<Self> {
- assert_eq!(self.currency, rhs.currency);
- let decimal = self.decimal().try_sub(&rhs.decimal())?.normalize()?;
+ if self.currency != rhs.currency {
+ return None;
+ }
+ let decimal = self.decimal().try_sub(&rhs.decimal())?;
Some((self.currency, decimal).into())
}
diff --git a/common/taler-common/src/types/base32.rs b/common/taler-common/src/types/base32.rs
@@ -78,7 +78,7 @@ impl<const L: usize> Serialize for Base32<L> {
where
S: Serializer,
{
- serializer.serialize_str(&self.to_string())
+ serializer.collect_str(&self)
}
}
diff --git a/common/taler-common/src/types/payto.rs b/common/taler-common/src/types/payto.rs
@@ -137,7 +137,7 @@ pub enum PaytoErr {
NotPayto(CompactString),
#[error("unsupported payto kind, expected {0} got {1}")]
UnsupportedKind(&'static str, CompactString),
- #[error("to much path segment for a {0} payto uri")]
+ #[error("to many path segments for a {0} payto uri")]
TooLong(&'static str),
#[error("missing segment {0} in path")]
MissingSegment(&'static str),
diff --git a/common/taler-common/src/types/timestamp.rs b/common/taler-common/src/types/timestamp.rs
@@ -73,8 +73,8 @@ impl<'de> Deserialize<'de> for TalerTimestamp {
let tmp = TimestampImpl::deserialize(deserializer)?;
match tmp.t_s {
Value::Number(s) => {
- if let Some(since_epoch_s) = s.as_i64() {
- jiff::Timestamp::from_second(since_epoch_s)
+ if let Some(since_epoch_s) = s.as_u64() {
+ jiff::Timestamp::from_second(since_epoch_s as i64)
.map(Self::Timestamp)
.map_err(Error::custom)
} else {
diff --git a/common/taler-test-utils/Cargo.toml b/common/taler-test-utils/Cargo.toml
@@ -29,6 +29,6 @@ url.workspace = true
aws-lc-rs.workspace = true
jiff.workspace = true
clap.workspace = true
-reedline = "0.48"
+reedline = "0.49"
shlex = "2.0"
nu-ansi-term = "0.50"
\ No newline at end of file
diff --git a/common/taler-test-utils/src/routine.rs b/common/taler-test-utils/src/routine.rs
@@ -378,7 +378,7 @@ pub async fn transfer_routine(
.assert_ok_json::<TransferStatus>();
assert_eq!(default_status, tx.status);
assert_eq!(default_amount, tx.amount);
- assert_eq!("http://exchange.taler/", tx.origin_exchange_url);
+ assert_eq!("http://exchange.taler/", tx.exchange_base_url);
assert_eq!(req.wtid, tx.wtid);
assert_eq!(first.timestamp, tx.timestamp);
assert_eq!(req.metadata, tx.metadata);