Skip to content

Commit 294cad6

Browse files
authored
Merge pull request #60 from NillionNetwork/fix/api-break
fix: allow legacy mode to be used
2 parents 0ed8536 + 039dc80 commit 294cad6

File tree

7 files changed

+79
-22
lines changed

7 files changed

+79
-22
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"
2121
rust_decimal = { version = "1.39", features = ["serde-float", "serde-arbitrary-precision"] }
2222
serde = { version = "1.0", features = ["derive"] }
2323
serde_json = "1.0"
24-
serde_with = "3.14"
24+
serde_with = { version = "3.14", features = ["hex"] }
2525
strum = { version = "0.27", features = ["derive"] }
2626
sqlx = { version = "0.8", features = ["postgres", "runtime-tokio", "chrono"] }
2727
thiserror = "2"

src/config.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
use anyhow::Context;
2-
use nillion_nucs::{DidMethod, NucSigner, Signer};
32
use rust_decimal::Decimal;
43
use serde::Deserialize;
54
use serde_with::serde_as;
@@ -61,7 +60,7 @@ pub enum PrivateKeyConfig {
6160

6261
impl PrivateKeyConfig {
6362
/// Load a signer using this configuration.
64-
pub fn load_signer(&self) -> anyhow::Result<Box<dyn NucSigner>> {
63+
pub fn load_private_key(&self) -> anyhow::Result<[u8; 32]> {
6564
let bytes: [u8; 32] = match self {
6665
PrivateKeyConfig::Hex(hex_bytes) => hex_bytes
6766
.to_vec()
@@ -74,7 +73,7 @@ impl PrivateKeyConfig {
7473
})?
7574
}
7675
};
77-
Ok(Signer::from_private_key(&bytes, DidMethod::Key))
76+
Ok(bytes)
7877
}
7978
}
8079

src/routes/nucs/create.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use axum::{
1010
};
1111
use chrono::{DateTime, Utc};
1212
use metrics::counter;
13+
use nillion_nucs::NucSigner;
1314
use nillion_nucs::token::Command;
1415
use nillion_nucs::{builder::DelegationBuilder, did::Did};
1516
use serde::{Deserialize, Serialize};
@@ -117,19 +118,19 @@ pub(crate) async fn handler(
117118
OptionalIdentityNuc(opt_auth): OptionalIdentityNuc,
118119
Json(request): Json<serde_json::Value>,
119120
) -> Result<Json<CreateNucResponse>, HandlerError> {
120-
let (requestor_did, blind_module) = if let Some(auth) = opt_auth {
121-
handle_modern_auth(auth, request).await?
121+
let ((requestor_did, blind_module), signer) = if let Some(auth) = opt_auth {
122+
(handle_modern_auth(auth, request).await?, &state.parameters.signer)
122123
} else {
123-
handle_legacy_auth(&state, request).await?
124+
(handle_legacy_auth(&state, request).await?, &state.parameters.legacy_signer)
124125
};
125-
126-
handle_nuc_creation(state, requestor_did, blind_module).await
126+
handle_nuc_creation(&state, requestor_did, blind_module, signer.as_ref()).await
127127
}
128128

129129
async fn handle_nuc_creation(
130-
state: SharedState,
130+
state: &SharedState,
131131
requestor_did: Did,
132132
blind_module: BlindModule,
133+
signer: &dyn NucSigner,
133134
) -> Result<Json<CreateNucResponse>, HandlerError> {
134135
let expires_at = match state.databases.subscriptions.find_subscription_end(&requestor_did, &blind_module).await {
135136
Ok(Some(timestamp)) if timestamp > state.services.time.current_time() => timestamp,
@@ -147,13 +148,12 @@ async fn handle_nuc_creation(
147148
};
148149

149150
info!("Minting token for {requestor_did}, expires at '{expires_at}'");
150-
let signer = &state.parameters.signer;
151151
let token = DelegationBuilder::new()
152152
.command(["nil", segment])
153153
.subject(requestor_did)
154154
.audience(requestor_did)
155155
.expires_at(expires_at)
156-
.sign_and_serialize(signer.as_ref())
156+
.sign_and_serialize(signer)
157157
.await
158158
.map_err(|e| {
159159
error!("Failed to sign token: {e}");

src/routes/subscriptions/status.rs

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,24 @@ use axum::{
99
use chrono::{DateTime, Utc};
1010
use nillion_nucs::did::Did;
1111
use serde::{Deserialize, Serialize};
12+
use serde_with::hex::Hex;
13+
use serde_with::serde_as;
1214
use strum::EnumDiscriminants;
1315
use tracing::error;
1416
use utoipa::{IntoParams, ToSchema};
1517

1618
/// A request to get a subscription's status.
19+
#[serde_as]
1720
#[derive(Deserialize, IntoParams)]
1821
pub(crate) struct SubscriptionStatusArgs {
1922
/// The Did to check the subscription for.
2023
#[param(value_type = String, example = "did:key:zQ3sh...")]
21-
did: Did,
24+
did: Option<Did>,
25+
26+
/// The Did to check the subscription for.
27+
#[param(value_type = String)]
28+
#[serde_as(as = "Option<Hex>")]
29+
public_key: Option<[u8; 33]>,
2230

2331
/// The blind module to check the subscription for.
2432
#[param(value_type = String, example = crate::docs::blind_module)]
@@ -65,13 +73,17 @@ pub(crate) async fn handler(
6573
state: SharedState,
6674
request: Query<SubscriptionStatusArgs>,
6775
) -> Result<Json<SubscriptionStatusResponse>, HandlerError> {
76+
let did = match (request.did, request.public_key) {
77+
(Some(did), _) => did,
78+
#[allow(deprecated)]
79+
(_, Some(public_key)) => Did::nil(public_key),
80+
_ => return Err(HandlerError::MissingIdentity),
81+
};
6882
let expires_at =
69-
state.databases.subscriptions.find_subscription_end(&request.did, &request.blind_module).await.map_err(
70-
|e| {
71-
error!("Subscription lookup failed: {e}");
72-
HandlerError::Internal
73-
},
74-
)?;
83+
state.databases.subscriptions.find_subscription_end(&did, &request.blind_module).await.map_err(|e| {
84+
error!("Subscription lookup failed: {e}");
85+
HandlerError::Internal
86+
})?;
7587
let details = expires_at.map(|expires_at| Subscription {
7688
expires_at,
7789
renewable_at: expires_at - state.parameters.subscription_renewal_threshold,
@@ -83,13 +95,15 @@ pub(crate) async fn handler(
8395
#[derive(Debug, EnumDiscriminants)]
8496
pub(crate) enum HandlerError {
8597
Internal,
98+
MissingIdentity,
8699
}
87100

88101
impl IntoResponse for HandlerError {
89102
fn into_response(self) -> Response {
90103
let discriminant = HandlerErrorDiscriminants::from(&self);
91104
let (code, message) = match self {
92105
Self::Internal => (StatusCode::INTERNAL_SERVER_ERROR, "internal error"),
106+
Self::MissingIdentity => (StatusCode::BAD_REQUEST, "need either `did` or `public_key`"),
93107
};
94108
let response = RequestHandlerError::new(message, format!("{discriminant:?}"));
95109
(code, Json(response)).into_response()
@@ -145,7 +159,38 @@ mod tests {
145159
let renewal_threshold = Duration::from_secs(30);
146160
handler.builder.subscription_renewal_threshold = renewal_threshold;
147161

148-
let request = SubscriptionStatusArgs { did: subscriber_did, blind_module };
162+
let request = SubscriptionStatusArgs { did: Some(subscriber_did), public_key: None, blind_module };
163+
let response = handler.invoke(request).await.expect("handler failed");
164+
assert!(response.subscribed);
165+
assert_eq!(
166+
response.details,
167+
Some(Subscription { expires_at: timestamp, renewable_at: timestamp - renewal_threshold })
168+
);
169+
}
170+
171+
#[tokio::test]
172+
async fn valid_request_public_key() {
173+
let mut handler = Handler::default();
174+
let key = SecretKey::random(&mut rand::thread_rng());
175+
let public_key_bytes: [u8; 33] = key.public_key().to_sec1_bytes().as_ref().try_into().unwrap();
176+
#[allow(deprecated)]
177+
let subscriber_did = Did::nil(public_key_bytes);
178+
let now = Utc::now();
179+
let timestamp = now + Duration::from_secs(120);
180+
let blind_module = BlindModule::NilDb;
181+
handler.builder.time_service.expect_current_time().returning(move || now);
182+
183+
handler
184+
.builder
185+
.subscriptions_db
186+
.expect_find_subscription_end()
187+
.with(eq(subscriber_did), eq(blind_module))
188+
.return_once(move |_, _| Ok(Some(timestamp)));
189+
190+
let renewal_threshold = Duration::from_secs(30);
191+
handler.builder.subscription_renewal_threshold = renewal_threshold;
192+
193+
let request = SubscriptionStatusArgs { did: None, public_key: Some(public_key_bytes), blind_module };
149194
let response = handler.invoke(request).await.expect("handler failed");
150195
assert!(response.subscribed);
151196
assert_eq!(
@@ -172,7 +217,7 @@ mod tests {
172217
.with(eq(subscriber_did), eq(blind_module))
173218
.return_once(move |_, _| Ok(Some(timestamp)));
174219

175-
let request = SubscriptionStatusArgs { did: subscriber_did, blind_module };
220+
let request = SubscriptionStatusArgs { did: Some(subscriber_did), public_key: None, blind_module };
176221
let response = handler.invoke(request).await.expect("handler failed");
177222
assert!(!response.subscribed);
178223
response.details.expect("subscription should still be returned");

src/run.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use axum_prometheus::{EndpointLabel, PrometheusMetricLayerBuilder, metrics_expor
1414
use chrono::Utc;
1515
use nilauth_client::nilchain_client::tx::DefaultPaymentTransactionRetriever;
1616
use nillion_nucs::did::Did;
17+
use nillion_nucs::{DidMethod, Signer};
1718
use std::net::SocketAddr;
1819
use std::sync::Arc;
1920
use tokio::signal;
@@ -27,7 +28,10 @@ use tracing::info;
2728
/// and starts the main application and metrics servers. It also handles
2829
/// graceful shutdown on receiving a termination signal.
2930
pub async fn run(config: Config) -> anyhow::Result<()> {
30-
let signer = config.private_key.load_signer()?;
31+
let private_key = config.private_key.load_private_key()?;
32+
let signer = Signer::from_private_key(&private_key, DidMethod::Key);
33+
#[allow(deprecated)]
34+
let legacy_signer = Signer::from_private_key(&private_key, DidMethod::Nil);
3135
let did = *signer.did();
3236
let public_key = match did {
3337
Did::Key { public_key } => public_key,
@@ -50,6 +54,7 @@ pub async fn run(config: Config) -> anyhow::Result<()> {
5054
let state = AppState {
5155
parameters: Parameters {
5256
signer,
57+
legacy_signer,
5358
did,
5459
public_key,
5560
started_at: Utc::now(),

src/state.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ pub struct Parameters {
5050
/// The server's secret signer.
5151
pub signer: Box<dyn NucSigner>,
5252

53+
/// The server's legacy secret signer.
54+
pub legacy_signer: Box<dyn NucSigner>,
55+
5356
/// The Did of the server's signer.
5457
pub did: Did,
5558

src/tests.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ mock! {
2323

2424
pub(crate) struct AppStateBuilder {
2525
pub(crate) signer: Box<dyn NucSigner>,
26+
pub(crate) legacy_signer: Box<dyn NucSigner>,
2627
pub(crate) tx_retriever: MockPaymentRetriever,
2728
pub(crate) time_service: MockTimeService,
2829
pub(crate) subscription_costs_service: MockSubscriptionCostService,
@@ -35,6 +36,8 @@ impl Default for AppStateBuilder {
3536
fn default() -> Self {
3637
Self {
3738
signer: Signer::generate(DidMethod::Key),
39+
#[allow(deprecated)]
40+
legacy_signer: Signer::generate(DidMethod::Nil),
3841
tx_retriever: Default::default(),
3942
time_service: Default::default(),
4043
subscription_costs_service: Default::default(),
@@ -49,6 +52,7 @@ impl AppStateBuilder {
4952
pub(crate) fn build(self) -> Arc<AppState> {
5053
let Self {
5154
signer,
55+
legacy_signer,
5256
tx_retriever,
5357
time_service,
5458
subscription_costs_service,
@@ -66,6 +70,7 @@ impl AppStateBuilder {
6670
Arc::new(AppState {
6771
parameters: Parameters {
6872
signer,
73+
legacy_signer,
6974
did,
7075
public_key,
7176
started_at: Utc::now(),

0 commit comments

Comments
 (0)