feat: stability for deployment and distribution #44
4 changed files with 1 additions and 162 deletions
|
|
@ -460,3 +460,4 @@ pub enum GatewayMsg {
|
|||
/// Periodic nonce garbage collection tick.
|
||||
NonceGcTick,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,145 +0,0 @@
|
|||
use std::collections::HashSet;
|
||||
use std::fmt;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::types::{NodeId, Signature};
|
||||
|
||||
// ─── ContentHash ────────────────────────────────────────────────────────────
|
||||
|
||||
/// A 32-byte blake3 digest used as content address for chunks and manifests.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct ContentHash(pub [u8; 32]);
|
||||
|
||||
impl fmt::Debug for ContentHash {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "ContentHash(")?;
|
||||
for b in &self.0[..4] {
|
||||
write!(f, "{:02x}", b)?;
|
||||
}
|
||||
write!(f, "\u{2026})")
|
||||
}
|
||||
}
|
||||
|
||||
// ─── DatastoreAction ────────────────────────────────────────────────────────
|
||||
|
||||
/// An action a client wants to perform on the datastore.
|
||||
///
|
||||
/// Carried inside a `SignedRequestPayload` for browser-relay auth (Auth Path 2).
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum DatastoreAction {
|
||||
Put {
|
||||
path: String,
|
||||
content_hash: ContentHash,
|
||||
size_bytes: u64,
|
||||
tags: std::collections::BTreeMap<String, String>,
|
||||
},
|
||||
Get {
|
||||
path: String,
|
||||
},
|
||||
Delete {
|
||||
path: String,
|
||||
},
|
||||
List {
|
||||
prefix: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
// ─── SignedRequestPayload ───────────────────────────────────────────────────
|
||||
|
||||
/// The signable payload of a client request.
|
||||
///
|
||||
/// Serialized canonically (serde_json) and signed by the client's ed25519 key.
|
||||
/// Includes timestamp and nonce for replay protection.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SignedRequestPayload {
|
||||
pub action: DatastoreAction,
|
||||
/// Unix timestamp in seconds.
|
||||
pub timestamp: u64,
|
||||
/// 16 random bytes — prevents replay within the timestamp window.
|
||||
pub nonce: [u8; 16],
|
||||
}
|
||||
|
||||
// ─── SignedRequest ──────────────────────────────────────────────────────────
|
||||
|
||||
/// A signed request envelope for browser-relay auth (Auth Path 2).
|
||||
///
|
||||
/// The relay forwards this opaquely — it cannot forge, modify, or replay it.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SignedRequest {
|
||||
pub payload: SignedRequestPayload,
|
||||
/// The client's ed25519 public key.
|
||||
pub public_key: NodeId,
|
||||
/// ed25519 signature over the canonical serialization of `payload`.
|
||||
pub signature: Signature,
|
||||
}
|
||||
|
||||
// ─── AccessControlList ──────────────────────────────────────────────────────
|
||||
|
||||
/// The datastore's access control list.
|
||||
///
|
||||
/// Persisted as `acl.json` alongside the datastore's `storage_path`.
|
||||
/// The owner always has implicit full access.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AccessControlList {
|
||||
/// The datastore owner's public key — always has full access.
|
||||
pub owner: NodeId,
|
||||
/// Explicitly authorized client keys.
|
||||
pub authorized_keys: HashSet<NodeId>,
|
||||
}
|
||||
|
||||
// ─── AuthzResult ────────────────────────────────────────────────────────────
|
||||
|
||||
/// The outcome of an authorization check.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum AuthzResult {
|
||||
Allowed,
|
||||
Denied(DeniedReason),
|
||||
}
|
||||
|
||||
/// Why a request was denied.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum DeniedReason {
|
||||
/// The key is not in the ACL.
|
||||
NotAuthorized,
|
||||
/// The ed25519 signature is invalid.
|
||||
InvalidSignature,
|
||||
/// The request timestamp is outside the ±300s window.
|
||||
RequestExpired,
|
||||
/// The nonce has already been seen within the time window.
|
||||
ReplayDetected,
|
||||
}
|
||||
|
||||
// ─── AuthzEngine ────────────────────────────────────────────────────────────
|
||||
|
||||
/// Authorization engine — checks requests against the ACL and replay state.
|
||||
///
|
||||
/// Sits at the edge of the actor system (Auth Gate) and decides whether
|
||||
/// to accept or reject external requests before they reach the actors.
|
||||
#[derive(Debug)]
|
||||
pub struct AuthzEngine {
|
||||
pub acl: AccessControlList,
|
||||
// Nonce tracking and other runtime state will be added during implementation.
|
||||
}
|
||||
|
||||
impl AuthzEngine {
|
||||
/// Check whether a `NodeId` is authorized (connection-level, Auth Path 1).
|
||||
pub fn check_node(&self, _node_id: &NodeId) -> AuthzResult {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// Verify and authorize a signed request (Auth Path 2).
|
||||
pub fn check_signed_request(&self, _request: &SignedRequest) -> AuthzResult {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// Grant access to a `NodeId`. Owner-only operation.
|
||||
pub fn grant(&mut self, _requester: &NodeId, _key: NodeId) -> Result<(), DeniedReason> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// Revoke access from a `NodeId`. Owner-only operation.
|
||||
pub fn revoke(&mut self, _requester: &NodeId, _key: NodeId) -> Result<(), DeniedReason> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
use ed25519_dalek::{Signer, Verifier};
|
||||
|
||||
use crate::auth::{SignedRequest, SignedRequestPayload};
|
||||
use crate::types::{DirectoryEntry, DirectoryEntryPayload, NodeId, Signature};
|
||||
|
||||
// ─── Keypair ────────────────────────────────────────────────────────────────
|
||||
|
|
@ -42,13 +41,6 @@ impl Keypair {
|
|||
Signature(sig.to_bytes())
|
||||
}
|
||||
|
||||
/// Sign a request payload, returning a complete `SignedRequest` envelope.
|
||||
///
|
||||
/// Used by clients for Auth Path 2 (browser relay).
|
||||
pub fn sign_request(&self, _payload: &SignedRequestPayload) -> SignedRequest {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// Sign a directory entry payload, returning a complete `DirectoryEntry`.
|
||||
pub fn sign_directory_entry(
|
||||
&self,
|
||||
|
|
@ -82,14 +74,6 @@ pub fn verify(node_id: &NodeId, msg: &[u8], sig: &Signature) -> bool {
|
|||
vk.verify(msg, &signature).is_ok()
|
||||
}
|
||||
|
||||
/// Verify a `SignedRequest`'s signature against its embedded `public_key`.
|
||||
///
|
||||
/// Checks only signature validity — does NOT check timestamp, nonce, or ACL.
|
||||
/// Use `AuthzEngine::check_signed_request` for full verification.
|
||||
pub fn verify_signed_request(_request: &SignedRequest) -> bool {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// Verify a `DirectoryEntry`'s signature against its embedded `node_id`.
|
||||
pub fn verify_directory_entry(entry: &DirectoryEntry) -> bool {
|
||||
let payload = entry.payload();
|
||||
|
|
|
|||
|
|
@ -14,4 +14,3 @@ pub mod snapshot;
|
|||
pub mod driver;
|
||||
#[cfg(feature = "iroh")]
|
||||
pub mod iroh_driver;
|
||||
pub mod auth;
|
||||
|
|
|
|||
Loading…
Reference in a new issue