Skip to content

Commit 4830484

Browse files
committed
refactor: consolidate duplicated DTO types and token entities
1 parent 90e9982 commit 4830484

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+8412
-1622
lines changed

Cargo.lock

Lines changed: 248 additions & 108 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ aes-gcm = "0.10"
8080
argon2 = { version = "0.5", features = ["std"] }
8181
base64 = "0.22"
8282
chrono = { version = "0.4", features = ["serde"] }
83-
ed25519-dalek = { version = "2", features = ["rand_core", "zeroize"] }
83+
ed25519-dalek = { version = "2", features = ["rand_core", "zeroize", "pkcs8"] }
8484
hex = "0.4"
8585
jsonwebtoken = { version = "10", default-features = false, features = [
8686
"aws_lc_rs",
@@ -101,6 +101,9 @@ webauthn-rs = { version = "0.5", features = [
101101
# Snowflake ID generation
102102
idgenerator = "2"
103103

104+
# Testing
105+
proptest = "1"
106+
104107
# Email
105108
lettre = { version = "0.11", default-features = false, features = [
106109
"builder",
@@ -141,6 +144,9 @@ kube = { version = "3", features = ["client"] }
141144
# Caching
142145
moka = { version = "0.12", features = ["sync"] }
143146

147+
# NTP client
148+
rsntp = { version = "4", default-features = false, features = ["chrono", "async"] }
149+
144150
# TLS (for crypto provider initialization)
145151
rustls = { version = "0.23", default-features = false, features = [
146152
"aws_lc_rs",

MANIFEST.md

Lines changed: 209 additions & 192 deletions
Large diffs are not rendered by default.

crates/api/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ uuid = { workspace = true }
4343
[dev-dependencies]
4444
inferadb-control-test-fixtures = { path = "../test-fixtures" }
4545
inferadb-common-authn = ">=0.1.0-0"
46+
proptest = { workspace = true }
4647
rand_core = { version = "0.6", features = ["getrandom"] }
48+
sha2 = { workspace = true }
4749

4850
[lints]
4951
workspace = true

crates/api/src/handlers/auth.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ pub struct AppState {
3838
pub leader: Option<Arc<inferadb_control_core::LeaderElection<Backend>>>,
3939
pub email_service: Option<Arc<inferadb_control_core::EmailService>>,
4040
pub control_identity: Option<Arc<inferadb_control_types::ControlIdentity>>,
41+
#[builder(default)]
42+
pub rate_limits: crate::middleware::RateLimitConfig,
4143
}
4244

4345
impl AppState {
@@ -88,7 +90,10 @@ impl IntoResponse for ApiError {
8890
tracing::warn!(status = %status, error = %error_message, "Client error");
8991
}
9092

91-
(status, Json(ErrorResponse { error: error_message, details: None })).into_response()
93+
let error_code = self.0.error_code().to_string();
94+
95+
(status, Json(ErrorResponse { error: error_message, code: error_code, details: None }))
96+
.into_response()
9297
}
9398
}
9499

@@ -203,7 +208,7 @@ pub async fn register(
203208
if let Some(email_service) = &state.email_service {
204209
let email_addr = email.email.clone();
205210
let user_name = payload.name.clone();
206-
let token_str = verification_token.token.clone();
211+
let token_str = verification_token.secure_token.token.clone();
207212
let email_service = Arc::clone(email_service);
208213
let frontend_url = state.config.frontend.url.clone();
209214

@@ -784,7 +789,7 @@ mod tests {
784789
// Get the reset token from the repository
785790
let tokens = repos.user_password_reset_token.get_by_user(user_id).await.unwrap();
786791
assert_eq!(tokens.len(), 1);
787-
let reset_token = tokens[0].token.clone();
792+
let reset_token = tokens[0].secure_token.token.clone();
788793

789794
// Confirm password reset
790795
let confirm_request = axum::http::Request::builder()
@@ -926,7 +931,7 @@ mod tests {
926931

927932
// Get the reset token
928933
let tokens = repos.user_password_reset_token.get_by_user(user_id).await.unwrap();
929-
let reset_token = tokens[0].token.clone();
934+
let reset_token = tokens[0].secure_token.token.clone();
930935

931936
// Confirm password reset
932937
let confirm_request = axum::http::Request::builder()

crates/api/src/handlers/clients.rs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -339,14 +339,15 @@ pub async fn create_certificate(
339339
let now = Utc::now();
340340
let public_signing_key = PublicSigningKey {
341341
kid: cert.kid.clone(),
342-
public_key: public_key_base64.clone(),
343-
client_id,
344-
cert_id: cert.id,
342+
public_key: public_key_base64.clone().into(),
343+
client_id: client_id.into(),
344+
cert_id: cert.id.into(),
345345
created_at: now,
346346
valid_from: now,
347347
valid_until: None,
348348
active: true,
349349
revoked_at: None,
350+
revocation_reason: None,
350351
};
351352

352353
// org_id maps directly to namespace_id in Ledger
@@ -361,7 +362,7 @@ pub async fn create_certificate(
361362

362363
// Time the Ledger write operation for metrics
363364
let ledger_start = std::time::Instant::now();
364-
if let Err(e) = signing_key_store.create_key(namespace_id, &public_signing_key).await {
365+
if let Err(e) = signing_key_store.create_key(namespace_id.into(), &public_signing_key).await {
365366
tracing::error!(
366367
error = %e,
367368
kid = %cert.kid,
@@ -568,7 +569,7 @@ pub async fn revoke_certificate(
568569
// Time the Ledger revoke operation for metrics
569570
let ledger_start = std::time::Instant::now();
570571
if let Err(e) = signing_key_store
571-
.revoke_key(namespace_id, &cert.kid, Some("Certificate revoked by user"))
572+
.revoke_key(namespace_id.into(), &cert.kid, Some("Certificate revoked by user"))
572573
.await
573574
{
574575
tracing::error!(
@@ -758,14 +759,15 @@ pub async fn rotate_certificate(
758759
// Write public key to Ledger with valid_from in the future
759760
let public_signing_key = PublicSigningKey {
760761
kid: new_cert.kid.clone(),
761-
public_key: public_key_base64.clone(),
762-
client_id,
763-
cert_id: new_cert.id,
762+
public_key: public_key_base64.clone().into(),
763+
client_id: client_id.into(),
764+
cert_id: new_cert.id.into(),
764765
created_at: now,
765766
valid_from,
766767
valid_until: None,
767768
active: true,
768769
revoked_at: None,
770+
revocation_reason: None,
769771
};
770772

771773
// org_id maps directly to namespace_id in Ledger
@@ -781,7 +783,7 @@ pub async fn rotate_certificate(
781783

782784
// Time the Ledger write operation for metrics
783785
let ledger_start = std::time::Instant::now();
784-
if let Err(e) = signing_key_store.create_key(namespace_id, &public_signing_key).await {
786+
if let Err(e) = signing_key_store.create_key(namespace_id.into(), &public_signing_key).await {
785787
tracing::error!(
786788
error = %e,
787789
kid = %new_cert.kid,

crates/api/src/handlers/emails.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ pub async fn resend_verification(
227227
// Delete any existing tokens for this email
228228
let existing_tokens = repos.user_email_verification_token.get_by_email(email_id).await?;
229229
for token in existing_tokens {
230-
repos.user_email_verification_token.delete(token.id).await?;
230+
repos.user_email_verification_token.delete(token.secure_token.id).await?;
231231
}
232232

233233
// Generate new verification token

crates/api/src/handlers/teams.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ pub async fn create_team(
100100
repos.org_team.count_active_by_organization(org_ctx.organization_id).await?;
101101

102102
if current_count >= organization.tier.max_teams() {
103-
return Err(CoreError::validation(format!(
103+
return Err(CoreError::tier_limit(format!(
104104
"Team limit reached for tier {:?}. Maximum: {}",
105105
organization.tier,
106106
organization.tier.max_teams()

crates/api/src/handlers/tokens.rs

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -408,36 +408,26 @@ pub async fn client_assertion_authenticate(
408408
}
409409

410410
// Verify JWT signature using certificate public key
411-
use base64::{
412-
Engine,
413-
engine::general_purpose::{STANDARD as BASE64, URL_SAFE_NO_PAD},
414-
};
411+
use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
412+
use ed25519_dalek::pkcs8::spki::EncodePublicKey;
415413
use jsonwebtoken::{Algorithm, DecodingKey, Validation, decode};
416414

417415
let public_key_bytes = URL_SAFE_NO_PAD
418416
.decode(&certificate.public_key)
419417
.map_err(|e| CoreError::internal(format!("Failed to decode public key: {e}")))?;
420418

421-
if public_key_bytes.len() != 32 {
422-
return Err(CoreError::internal("Invalid public key length".to_string()).into());
423-
}
424-
425-
// Convert public key to PEM (same as in jwt.rs)
426-
let mut spki_der = vec![
427-
0x30, 0x2a, // SEQUENCE (42 bytes)
428-
0x30, 0x05, // SEQUENCE (algorithm)
429-
0x06, 0x03, 0x2b, 0x65, 0x70, // OID 1.3.101.112
430-
0x03, 0x21, 0x00, // BIT STRING (33 bytes, 0 unused bits)
431-
];
432-
spki_der.extend_from_slice(&public_key_bytes);
433-
434-
let public_key_pem = format!(
435-
"-----BEGIN PUBLIC KEY-----\n{}\n-----END PUBLIC KEY-----\n",
436-
BASE64.encode(&spki_der)
437-
);
438-
439-
let decoding_key = DecodingKey::from_ed_pem(public_key_pem.as_bytes())
440-
.map_err(|e| CoreError::internal(format!("Failed to create decoding key: {e}")))?;
419+
let public_key_array: [u8; 32] = public_key_bytes
420+
.as_slice()
421+
.try_into()
422+
.map_err(|_| CoreError::internal("Invalid public key length".to_string()))?;
423+
424+
// Encode public key as SPKI DER using the pkcs8/spki crate
425+
let verifying_key = ed25519_dalek::VerifyingKey::from_bytes(&public_key_array)
426+
.map_err(|e| CoreError::internal(format!("Invalid public key: {e}")))?;
427+
let spki_der = verifying_key
428+
.to_public_key_der()
429+
.map_err(|e| CoreError::internal(format!("Failed to encode SPKI DER: {e}")))?;
430+
let decoding_key = DecodingKey::from_ed_der(spki_der.as_ref());
441431

442432
// Set up validation - expect token endpoint as audience
443433
let mut validation = Validation::new(Algorithm::EdDSA);

crates/api/src/handlers/users.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ pub async fn delete_user(
140140
for email in &emails {
141141
let tokens = repos.user_email_verification_token.get_by_email(email.id).await?;
142142
for token in tokens {
143-
repos.user_email_verification_token.delete(token.id).await?;
143+
repos.user_email_verification_token.delete(token.secure_token.id).await?;
144144
}
145145
}
146146

@@ -152,7 +152,7 @@ pub async fn delete_user(
152152
// CASCADE DELETE: Delete all password reset tokens
153153
let reset_tokens = repos.user_password_reset_token.get_by_user(ctx.user_id).await?;
154154
for token in reset_tokens {
155-
repos.user_password_reset_token.delete(token.id).await?;
155+
repos.user_password_reset_token.delete(token.secure_token.id).await?;
156156
}
157157

158158
// Soft-delete the user

0 commit comments

Comments
 (0)