Compare commits
	
		
			No commits in common. "c1f76f4c8b0c11ab0fef2a553a0717f450ce9761" and "c65ae95b4365ce3eace88dffb679b261e8de7ff6" have entirely different histories.
		
	
	
		
			c1f76f4c8b
			...
			c65ae95b43
		
	
		
					 19 changed files with 235 additions and 2709 deletions
				
			
		
							
								
								
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -2,6 +2,7 @@
 | 
			
		|||
/target/
 | 
			
		||||
**/*.rs.bk
 | 
			
		||||
*.pdb
 | 
			
		||||
Cargo.lock
 | 
			
		||||
 | 
			
		||||
# Generated by Cargo
 | 
			
		||||
.cargo/
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										2026
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										2026
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										28
									
								
								flake.lock
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										28
									
								
								flake.lock
									
										
									
										generated
									
									
									
								
							| 
						 | 
				
			
			@ -1,20 +1,5 @@
 | 
			
		|||
{
 | 
			
		||||
  "nodes": {
 | 
			
		||||
    "crane": {
 | 
			
		||||
      "locked": {
 | 
			
		||||
        "lastModified": 1742394900,
 | 
			
		||||
        "narHash": "sha256-vVOAp9ahvnU+fQoKd4SEXB2JG2wbENkpqcwlkIXgUC0=",
 | 
			
		||||
        "owner": "ipetkov",
 | 
			
		||||
        "repo": "crane",
 | 
			
		||||
        "rev": "70947c1908108c0c551ddfd73d4f750ff2ea67cd",
 | 
			
		||||
        "type": "github"
 | 
			
		||||
      },
 | 
			
		||||
      "original": {
 | 
			
		||||
        "owner": "ipetkov",
 | 
			
		||||
        "repo": "crane",
 | 
			
		||||
        "type": "github"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "flake-utils": {
 | 
			
		||||
      "inputs": {
 | 
			
		||||
        "systems": "systems"
 | 
			
		||||
| 
						 | 
				
			
			@ -35,11 +20,11 @@
 | 
			
		|||
    },
 | 
			
		||||
    "nixpkgs": {
 | 
			
		||||
      "locked": {
 | 
			
		||||
        "lastModified": 1742751704,
 | 
			
		||||
        "narHash": "sha256-rBfc+H1dDBUQ2mgVITMGBPI1PGuCznf9rcWX/XIULyE=",
 | 
			
		||||
        "lastModified": 1742268799,
 | 
			
		||||
        "narHash": "sha256-IhnK4LhkBlf14/F8THvUy3xi/TxSQkp9hikfDZRD4Ic=",
 | 
			
		||||
        "owner": "NixOS",
 | 
			
		||||
        "repo": "nixpkgs",
 | 
			
		||||
        "rev": "f0946fa5f1fb876a9dc2e1850d9d3a4e3f914092",
 | 
			
		||||
        "rev": "da044451c6a70518db5b730fe277b70f494188f1",
 | 
			
		||||
        "type": "github"
 | 
			
		||||
      },
 | 
			
		||||
      "original": {
 | 
			
		||||
| 
						 | 
				
			
			@ -67,7 +52,6 @@
 | 
			
		|||
    },
 | 
			
		||||
    "root": {
 | 
			
		||||
      "inputs": {
 | 
			
		||||
        "crane": "crane",
 | 
			
		||||
        "flake-utils": "flake-utils",
 | 
			
		||||
        "nixpkgs": "nixpkgs",
 | 
			
		||||
        "rust-overlay": "rust-overlay"
 | 
			
		||||
| 
						 | 
				
			
			@ -78,11 +62,11 @@
 | 
			
		|||
        "nixpkgs": "nixpkgs_2"
 | 
			
		||||
      },
 | 
			
		||||
      "locked": {
 | 
			
		||||
        "lastModified": 1742783666,
 | 
			
		||||
        "narHash": "sha256-IwdSl51NL6V0f+mYXZR0UTKaGleOsk9zV3l6kt5SUWw=",
 | 
			
		||||
        "lastModified": 1742437918,
 | 
			
		||||
        "narHash": "sha256-Vflb6KJVDikFcM9E231mRN88uk4+jo7BWtaaQMifthI=",
 | 
			
		||||
        "owner": "oxalica",
 | 
			
		||||
        "repo": "rust-overlay",
 | 
			
		||||
        "rev": "60766d63c227d576510ecfb5edd3a687d56f6bc7",
 | 
			
		||||
        "rev": "f03085549609e49c7bcbbee86a1949057d087199",
 | 
			
		||||
        "type": "github"
 | 
			
		||||
      },
 | 
			
		||||
      "original": {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										130
									
								
								flake.nix
									
										
									
									
									
								
							
							
						
						
									
										130
									
								
								flake.nix
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -3,105 +3,41 @@
 | 
			
		|||
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
 | 
			
		||||
    flake-utils.url = "github:numtide/flake-utils";
 | 
			
		||||
    rust-overlay.url = "github:oxalica/rust-overlay";
 | 
			
		||||
    crane.url = "github:ipetkov/crane";
 | 
			
		||||
  };
 | 
			
		||||
  outputs =
 | 
			
		||||
    {
 | 
			
		||||
      self,
 | 
			
		||||
      nixpkgs,
 | 
			
		||||
      flake-utils,
 | 
			
		||||
      rust-overlay,
 | 
			
		||||
      crane,
 | 
			
		||||
    }:
 | 
			
		||||
    flake-utils.lib.eachDefaultSystem (
 | 
			
		||||
      system:
 | 
			
		||||
      let
 | 
			
		||||
        overlays = [
 | 
			
		||||
          rust-overlay.overlays.default
 | 
			
		||||
        ];
 | 
			
		||||
        pkgs = import nixpkgs {
 | 
			
		||||
          inherit system overlays;
 | 
			
		||||
          config = {
 | 
			
		||||
            allowUnfree = true;
 | 
			
		||||
          };
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        # Import rust setup
 | 
			
		||||
        rustSetup = import ./nix/rust-setup.nix { inherit pkgs crane; };
 | 
			
		||||
        inherit (rustSetup) rustVersion rustPlatform craneLib;
 | 
			
		||||
 | 
			
		||||
        src = craneLib.cleanCargoSource ./.;
 | 
			
		||||
 | 
			
		||||
        commonArgs = {
 | 
			
		||||
          inherit src;
 | 
			
		||||
 | 
			
		||||
          # Add any build inputs needed
 | 
			
		||||
          buildInputs = with pkgs; [
 | 
			
		||||
            openssl
 | 
			
		||||
            pkg-config
 | 
			
		||||
  outputs = { self, nixpkgs, flake-utils, rust-overlay }:
 | 
			
		||||
    flake-utils.lib.eachDefaultSystem
 | 
			
		||||
      (system:
 | 
			
		||||
        let
 | 
			
		||||
          overlays = [
 | 
			
		||||
            rust-overlay.overlays.default
 | 
			
		||||
          ];
 | 
			
		||||
 | 
			
		||||
          # Add any native build inputs
 | 
			
		||||
          nativeBuildInputs = with pkgs; [
 | 
			
		||||
            rustPlatform.bindgenHook
 | 
			
		||||
            pkg-config
 | 
			
		||||
          ];
 | 
			
		||||
 | 
			
		||||
          checkType = "debug";
 | 
			
		||||
          env = {
 | 
			
		||||
            OPENSSL_NO_VENDOR = "1";
 | 
			
		||||
            NIX_OUTPATH_USED_AS_RANDOM_SEED = "aaaaaaaaaa";
 | 
			
		||||
          pkgs = import nixpkgs {
 | 
			
		||||
            inherit system overlays;
 | 
			
		||||
            config = {
 | 
			
		||||
              allowUnfree = true;
 | 
			
		||||
            };
 | 
			
		||||
          };
 | 
			
		||||
        };
 | 
			
		||||
        in
 | 
			
		||||
        with pkgs;
 | 
			
		||||
        {
 | 
			
		||||
          devShells.default = mkShell {
 | 
			
		||||
            env = {
 | 
			
		||||
              OPENSSL_NO_VENDOR = "1";
 | 
			
		||||
              NIX_OUTPATH_USED_AS_RANDOM_SEED = "aaaaaaaaaa";
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
        cargoArtifacts = craneLib.buildDepsOnly commonArgs;
 | 
			
		||||
        individualCrateArgs = commonArgs // {
 | 
			
		||||
          inherit cargoArtifacts;
 | 
			
		||||
          inherit (craneLib.crateNameFromCargoToml { inherit src; }) version;
 | 
			
		||||
          # NB: we disable tests since we'll run them all via cargo-nextest
 | 
			
		||||
          doCheck = false;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        # Import vault-hier package
 | 
			
		||||
        vault-hier = import ./nix/packages/vault-hier.nix {
 | 
			
		||||
          inherit
 | 
			
		||||
            pkgs
 | 
			
		||||
            craneLib
 | 
			
		||||
            individualCrateArgs
 | 
			
		||||
            ;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        # Import devshell
 | 
			
		||||
        devshell_with_src = import ./nix/devshell.nix {
 | 
			
		||||
          inherit pkgs vault-hier rustVersion;
 | 
			
		||||
        };
 | 
			
		||||
      in
 | 
			
		||||
      {
 | 
			
		||||
        # Import checks
 | 
			
		||||
        checks = import ./nix/checks {
 | 
			
		||||
          inherit
 | 
			
		||||
            craneLib
 | 
			
		||||
            src
 | 
			
		||||
            commonArgs
 | 
			
		||||
            cargoArtifacts
 | 
			
		||||
            vault-hier
 | 
			
		||||
            pkgs
 | 
			
		||||
            ;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        # Add packages output
 | 
			
		||||
        packages = {
 | 
			
		||||
          inherit vault-hier;
 | 
			
		||||
          default = vault-hier;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        devShells = {
 | 
			
		||||
          inherit devshell_with_src;
 | 
			
		||||
          default = devshell_with_src;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        # Add formatter for `nix fmt`
 | 
			
		||||
        formatter = pkgs.nixfmt-rfc-style;
 | 
			
		||||
      }
 | 
			
		||||
    );
 | 
			
		||||
            packages = [
 | 
			
		||||
              pkg-config
 | 
			
		||||
              vault
 | 
			
		||||
              (rust-bin.stable.latest.default.override {
 | 
			
		||||
                extensions = [ "rust-src" ];
 | 
			
		||||
              })
 | 
			
		||||
              rustc
 | 
			
		||||
              cargo
 | 
			
		||||
              rustfmt
 | 
			
		||||
              clippy
 | 
			
		||||
            ];
 | 
			
		||||
          };
 | 
			
		||||
        }
 | 
			
		||||
      );
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,13 +0,0 @@
 | 
			
		|||
{
 | 
			
		||||
  craneLib,
 | 
			
		||||
  commonArgs,
 | 
			
		||||
  cargoArtifacts,
 | 
			
		||||
}:
 | 
			
		||||
 | 
			
		||||
craneLib.cargoClippy (
 | 
			
		||||
  commonArgs
 | 
			
		||||
  // {
 | 
			
		||||
    inherit cargoArtifacts;
 | 
			
		||||
    cargoClippyExtraArgs = "--all-targets -- --deny warnings";
 | 
			
		||||
  }
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -1,30 +0,0 @@
 | 
			
		|||
{
 | 
			
		||||
  craneLib,
 | 
			
		||||
  src,
 | 
			
		||||
  commonArgs,
 | 
			
		||||
  cargoArtifacts,
 | 
			
		||||
  vault-hier,
 | 
			
		||||
  pkgs,
 | 
			
		||||
}:
 | 
			
		||||
 | 
			
		||||
{
 | 
			
		||||
  inherit vault-hier;
 | 
			
		||||
 | 
			
		||||
  my-workspace-clippy = import ./clippy.nix {
 | 
			
		||||
    inherit craneLib commonArgs cargoArtifacts;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  my-workspace-doc = import ./doc.nix {
 | 
			
		||||
    inherit craneLib commonArgs cargoArtifacts;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  # Check formatting
 | 
			
		||||
  my-workspace-fmt = import ./fmt.nix {
 | 
			
		||||
    inherit craneLib src;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  my-workspace-toml-fmt = import ./toml-fmt.nix {
 | 
			
		||||
    inherit craneLib src;
 | 
			
		||||
    lib = pkgs.lib;
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,12 +0,0 @@
 | 
			
		|||
{
 | 
			
		||||
  craneLib,
 | 
			
		||||
  commonArgs,
 | 
			
		||||
  cargoArtifacts,
 | 
			
		||||
}:
 | 
			
		||||
 | 
			
		||||
craneLib.cargoDoc (
 | 
			
		||||
  commonArgs
 | 
			
		||||
  // {
 | 
			
		||||
    inherit cargoArtifacts;
 | 
			
		||||
  }
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -1,5 +0,0 @@
 | 
			
		|||
{ craneLib, src }:
 | 
			
		||||
 | 
			
		||||
craneLib.cargoFmt {
 | 
			
		||||
  inherit src;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,11 +0,0 @@
 | 
			
		|||
{
 | 
			
		||||
  craneLib,
 | 
			
		||||
  src,
 | 
			
		||||
  lib,
 | 
			
		||||
}:
 | 
			
		||||
 | 
			
		||||
craneLib.taploFmt {
 | 
			
		||||
  src = lib.sources.sourceFilesBySuffices src [ ".toml" ];
 | 
			
		||||
  # taplo arguments can be further customized below as needed
 | 
			
		||||
  # taploExtraArgs = "--config ./taplo.toml";
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,26 +0,0 @@
 | 
			
		|||
{
 | 
			
		||||
  pkgs,
 | 
			
		||||
  vault-hier,
 | 
			
		||||
  rustVersion,
 | 
			
		||||
}:
 | 
			
		||||
 | 
			
		||||
let
 | 
			
		||||
  toolchain_with_src = (
 | 
			
		||||
    rustVersion.override {
 | 
			
		||||
      extensions = [
 | 
			
		||||
        "rustfmt"
 | 
			
		||||
        "clippy"
 | 
			
		||||
        "rust-src"
 | 
			
		||||
      ];
 | 
			
		||||
    }
 | 
			
		||||
  );
 | 
			
		||||
in
 | 
			
		||||
pkgs.mkShell {
 | 
			
		||||
  packages = with pkgs; [
 | 
			
		||||
    vault-hier # Add the vault-hier package to the dev shell
 | 
			
		||||
    toolchain_with_src # Add the custom Rust toolchain with source code to the dev shell
 | 
			
		||||
  ];
 | 
			
		||||
  nativeBuildInputs = [
 | 
			
		||||
    vault-hier
 | 
			
		||||
  ];
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,12 +0,0 @@
 | 
			
		|||
{
 | 
			
		||||
  pkgs,
 | 
			
		||||
  craneLib,
 | 
			
		||||
  individualCrateArgs,
 | 
			
		||||
}:
 | 
			
		||||
craneLib.buildPackage (
 | 
			
		||||
  individualCrateArgs
 | 
			
		||||
  // {
 | 
			
		||||
    pname = "vault-hier";
 | 
			
		||||
    cargoExtraArgs = "-p vault-hier";
 | 
			
		||||
  }
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -1,13 +0,0 @@
 | 
			
		|||
{ pkgs, crane }:
 | 
			
		||||
 | 
			
		||||
let
 | 
			
		||||
  rustVersion = pkgs.rust-bin.fromRustupToolchainFile (../rust-toolchain.toml);
 | 
			
		||||
  rustPlatform = pkgs.makeRustPlatform {
 | 
			
		||||
    cargo = rustVersion;
 | 
			
		||||
    rustc = rustVersion;
 | 
			
		||||
  };
 | 
			
		||||
  craneLib = (crane.mkLib pkgs).overrideToolchain rustVersion;
 | 
			
		||||
in
 | 
			
		||||
{
 | 
			
		||||
  inherit rustVersion rustPlatform craneLib;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,3 +0,0 @@
 | 
			
		|||
[toolchain]
 | 
			
		||||
channel = "1.85.1"
 | 
			
		||||
components = ["rustfmt", "clippy", "rust-src"]
 | 
			
		||||
							
								
								
									
										72
									
								
								src/api.rs
									
										
									
									
									
								
							
							
						
						
									
										72
									
								
								src/api.rs
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -1,14 +1,15 @@
 | 
			
		|||
use anyhow::Result;
 | 
			
		||||
use axum::{
 | 
			
		||||
    Json, Router, Server,
 | 
			
		||||
    extract::{Multipart, Path, State},
 | 
			
		||||
    http::StatusCode,
 | 
			
		||||
    response::{IntoResponse, Response},
 | 
			
		||||
    routing::{get, post},
 | 
			
		||||
    Json, Router,
 | 
			
		||||
    Server,
 | 
			
		||||
};
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use std::sync::Arc;
 | 
			
		||||
use tracing::{debug, error, info, instrument};
 | 
			
		||||
use tracing::{info, error, debug, instrument};
 | 
			
		||||
 | 
			
		||||
use crate::document_service::{Document, DocumentService, SignatureVerification};
 | 
			
		||||
use crate::vault_setup::VaultClient;
 | 
			
		||||
| 
						 | 
				
			
			@ -67,7 +68,11 @@ where
 | 
			
		|||
 | 
			
		||||
// Start the API server
 | 
			
		||||
#[instrument(skip(vault_addr, root_token))]
 | 
			
		||||
pub async fn start_api(vault_addr: &str, root_token: &str, api_port: u16) -> Result<()> {
 | 
			
		||||
pub async fn start_api(
 | 
			
		||||
    vault_addr: &str,
 | 
			
		||||
    root_token: &str,
 | 
			
		||||
    api_port: u16,
 | 
			
		||||
) -> Result<()> {
 | 
			
		||||
    info!("Starting API server on port {}...", api_port);
 | 
			
		||||
 | 
			
		||||
    // Initialize Vault client
 | 
			
		||||
| 
						 | 
				
			
			@ -105,7 +110,9 @@ pub async fn start_api(vault_addr: &str, root_token: &str, api_port: u16) -> Res
 | 
			
		|||
 | 
			
		||||
    // Bind and serve
 | 
			
		||||
    info!("Serving API at {}", addr);
 | 
			
		||||
    Server::bind(&addr).serve(app.into_make_service()).await?;
 | 
			
		||||
    Server::bind(&addr)
 | 
			
		||||
        .serve(app.into_make_service())
 | 
			
		||||
        .await?;
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -125,8 +132,7 @@ async fn login(
 | 
			
		|||
) -> Result<Json<LoginResponse>, ApiError> {
 | 
			
		||||
    info!("Login attempt for user: {}", request.username);
 | 
			
		||||
 | 
			
		||||
    let token = state
 | 
			
		||||
        .vault_client
 | 
			
		||||
    let token = state.vault_client
 | 
			
		||||
        .login_user(&request.username, &request.password)
 | 
			
		||||
        .await?;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -154,10 +160,7 @@ async fn upload_document(
 | 
			
		|||
            debug!("Received document name: {}", document_name);
 | 
			
		||||
        } else if name == "file" {
 | 
			
		||||
            document_content = field.bytes().await?.to_vec();
 | 
			
		||||
            debug!(
 | 
			
		||||
                "Received document content: {} bytes",
 | 
			
		||||
                document_content.len()
 | 
			
		||||
            );
 | 
			
		||||
            debug!("Received document content: {} bytes", document_content.len());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -167,13 +170,14 @@ async fn upload_document(
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    // Upload document
 | 
			
		||||
    let document_id = state
 | 
			
		||||
        .document_service
 | 
			
		||||
    let document_id = state.document_service
 | 
			
		||||
        .upload_document(&document_name, &document_content)
 | 
			
		||||
        .await?;
 | 
			
		||||
 | 
			
		||||
    // Return document metadata
 | 
			
		||||
    let document = state.document_service.get_document(&document_id).await?;
 | 
			
		||||
    let document = state.document_service
 | 
			
		||||
        .get_document(&document_id)
 | 
			
		||||
        .await?;
 | 
			
		||||
 | 
			
		||||
    info!("Document uploaded successfully with ID: {}", document_id);
 | 
			
		||||
    Ok(Json(document))
 | 
			
		||||
| 
						 | 
				
			
			@ -187,13 +191,12 @@ async fn get_document(
 | 
			
		|||
) -> Result<Json<Document>, ApiError> {
 | 
			
		||||
    info!("Fetching document: {}", document_id);
 | 
			
		||||
 | 
			
		||||
    let document = state.document_service.get_document(&document_id).await?;
 | 
			
		||||
    let document = state.document_service
 | 
			
		||||
        .get_document(&document_id)
 | 
			
		||||
        .await?;
 | 
			
		||||
 | 
			
		||||
    debug!(
 | 
			
		||||
        "Retrieved document {} with {} signatures",
 | 
			
		||||
        document.id,
 | 
			
		||||
        document.signatures.len()
 | 
			
		||||
    );
 | 
			
		||||
    debug!("Retrieved document {} with {} signatures",
 | 
			
		||||
           document.id, document.signatures.len());
 | 
			
		||||
 | 
			
		||||
    Ok(Json(document))
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -205,22 +208,17 @@ async fn sign_document(
 | 
			
		|||
    Path(document_id): Path<String>,
 | 
			
		||||
    Json(request): Json<SignDocumentRequest>,
 | 
			
		||||
) -> Result<Json<Document>, ApiError> {
 | 
			
		||||
    info!(
 | 
			
		||||
        "Signing request for document {} by user {}",
 | 
			
		||||
        document_id, request.username
 | 
			
		||||
    );
 | 
			
		||||
    info!("Signing request for document {} by user {}", document_id, request.username);
 | 
			
		||||
 | 
			
		||||
    state
 | 
			
		||||
        .document_service
 | 
			
		||||
    state.document_service
 | 
			
		||||
        .sign_document(&document_id, &request.username, &request.token)
 | 
			
		||||
        .await?;
 | 
			
		||||
 | 
			
		||||
    let document = state.document_service.get_document(&document_id).await?;
 | 
			
		||||
    let document = state.document_service
 | 
			
		||||
        .get_document(&document_id)
 | 
			
		||||
        .await?;
 | 
			
		||||
 | 
			
		||||
    info!(
 | 
			
		||||
        "Document {} successfully signed by {}",
 | 
			
		||||
        document_id, request.username
 | 
			
		||||
    );
 | 
			
		||||
    info!("Document {} successfully signed by {}", document_id, request.username);
 | 
			
		||||
    Ok(Json(document))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -232,20 +230,12 @@ async fn verify_document(
 | 
			
		|||
) -> Result<Json<SignatureVerification>, ApiError> {
 | 
			
		||||
    info!("Verifying document signatures: {}", document_id);
 | 
			
		||||
 | 
			
		||||
    let verification = state
 | 
			
		||||
        .document_service
 | 
			
		||||
    let verification = state.document_service
 | 
			
		||||
        .verify_document_signatures(&document_id)
 | 
			
		||||
        .await?;
 | 
			
		||||
 | 
			
		||||
    info!(
 | 
			
		||||
        "Document {} verification result: {}",
 | 
			
		||||
        document_id,
 | 
			
		||||
        if verification.is_verified {
 | 
			
		||||
            "VERIFIED"
 | 
			
		||||
        } else {
 | 
			
		||||
            "PENDING"
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
    info!("Document {} verification result: {}",
 | 
			
		||||
           document_id, if verification.is_verified { "VERIFIED" } else { "PENDING" });
 | 
			
		||||
 | 
			
		||||
    Ok(Json(verification))
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,12 +1,12 @@
 | 
			
		|||
use anyhow::{Context, Result};
 | 
			
		||||
use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
 | 
			
		||||
use reqwest::StatusCode;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use serde_json::json;
 | 
			
		||||
use sha2::{Digest, Sha256};
 | 
			
		||||
use sha2::{Sha256, Digest};
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
use tracing::{debug, error, info, instrument};
 | 
			
		||||
use uuid::Uuid;
 | 
			
		||||
use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
 | 
			
		||||
use tracing::{info, error, debug, instrument};
 | 
			
		||||
 | 
			
		||||
use crate::vault_setup::{Department, User, VaultClient};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -88,10 +88,8 @@ impl DocumentService {
 | 
			
		|||
    async fn store_document_metadata(&self, document: &Document) -> Result<()> {
 | 
			
		||||
        debug!("Storing document metadata for {}", document.id);
 | 
			
		||||
 | 
			
		||||
        let url = format!(
 | 
			
		||||
            "{}/v1/documents/data/docs/{}",
 | 
			
		||||
            self.vault_client.addr, document.id
 | 
			
		||||
        );
 | 
			
		||||
        let url = format!("{}/v1/documents/data/docs/{}",
 | 
			
		||||
            self.vault_client.addr, document.id);
 | 
			
		||||
 | 
			
		||||
        let payload = json!({
 | 
			
		||||
            "data": {
 | 
			
		||||
| 
						 | 
				
			
			@ -106,9 +104,7 @@ impl DocumentService {
 | 
			
		|||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        let response = self
 | 
			
		||||
            .vault_client
 | 
			
		||||
            .client
 | 
			
		||||
        let response = self.vault_client.client
 | 
			
		||||
            .post(&url)
 | 
			
		||||
            .header("X-Vault-Token", &self.vault_client.token)
 | 
			
		||||
            .json(&payload)
 | 
			
		||||
| 
						 | 
				
			
			@ -122,15 +118,8 @@ impl DocumentService {
 | 
			
		|||
            }
 | 
			
		||||
            status => {
 | 
			
		||||
                let error_text = response.text().await?;
 | 
			
		||||
                error!(
 | 
			
		||||
                    "Failed to store document metadata: {} - {}",
 | 
			
		||||
                    status, error_text
 | 
			
		||||
                );
 | 
			
		||||
                Err(anyhow::anyhow!(
 | 
			
		||||
                    "Failed to store document metadata: {} - {}",
 | 
			
		||||
                    status,
 | 
			
		||||
                    error_text
 | 
			
		||||
                ))
 | 
			
		||||
                error!("Failed to store document metadata: {} - {}", status, error_text);
 | 
			
		||||
                Err(anyhow::anyhow!("Failed to store document metadata: {} - {}", status, error_text))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -140,14 +129,10 @@ impl DocumentService {
 | 
			
		|||
    pub async fn get_document(&self, document_id: &str) -> Result<Document> {
 | 
			
		||||
        debug!("Getting document metadata for {}", document_id);
 | 
			
		||||
 | 
			
		||||
        let url = format!(
 | 
			
		||||
            "{}/v1/documents/data/docs/{}",
 | 
			
		||||
            self.vault_client.addr, document_id
 | 
			
		||||
        );
 | 
			
		||||
        let url = format!("{}/v1/documents/data/docs/{}",
 | 
			
		||||
            self.vault_client.addr, document_id);
 | 
			
		||||
 | 
			
		||||
        let response = self
 | 
			
		||||
            .vault_client
 | 
			
		||||
            .client
 | 
			
		||||
        let response = self.vault_client.client
 | 
			
		||||
            .get(&url)
 | 
			
		||||
            .header("X-Vault-Token", &self.vault_client.token)
 | 
			
		||||
            .send()
 | 
			
		||||
| 
						 | 
				
			
			@ -184,49 +169,27 @@ impl DocumentService {
 | 
			
		|||
                }
 | 
			
		||||
 | 
			
		||||
                let document = Document {
 | 
			
		||||
                    id: json["data"]["data"]["id"]
 | 
			
		||||
                        .as_str()
 | 
			
		||||
                        .context("Missing id")?
 | 
			
		||||
                        .to_string(),
 | 
			
		||||
                    name: json["data"]["data"]["name"]
 | 
			
		||||
                        .as_str()
 | 
			
		||||
                        .context("Missing name")?
 | 
			
		||||
                        .to_string(),
 | 
			
		||||
                    hash: json["data"]["data"]["hash"]
 | 
			
		||||
                        .as_str()
 | 
			
		||||
                        .context("Missing hash")?
 | 
			
		||||
                        .to_string(),
 | 
			
		||||
                    id: json["data"]["data"]["id"].as_str().context("Missing id")?.to_string(),
 | 
			
		||||
                    name: json["data"]["data"]["name"].as_str().context("Missing name")?.to_string(),
 | 
			
		||||
                    hash: json["data"]["data"]["hash"].as_str().context("Missing hash")?.to_string(),
 | 
			
		||||
                    status,
 | 
			
		||||
                    signatures,
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                debug!(
 | 
			
		||||
                    "Retrieved document: {} with {} signatures",
 | 
			
		||||
                    document.id,
 | 
			
		||||
                    document.signatures.len()
 | 
			
		||||
                );
 | 
			
		||||
                debug!("Retrieved document: {} with {} signatures", document.id, document.signatures.len());
 | 
			
		||||
                Ok(document)
 | 
			
		||||
            }
 | 
			
		||||
            status => {
 | 
			
		||||
                let error_text = response.text().await?;
 | 
			
		||||
                error!("Failed to get document: {} - {}", status, error_text);
 | 
			
		||||
                Err(anyhow::anyhow!(
 | 
			
		||||
                    "Failed to get document: {} - {}",
 | 
			
		||||
                    status,
 | 
			
		||||
                    error_text
 | 
			
		||||
                ))
 | 
			
		||||
                Err(anyhow::anyhow!("Failed to get document: {} - {}", status, error_text))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Sign a document with the user's key
 | 
			
		||||
    #[instrument(skip(self, user_token), fields(document_id = %document_id, username = %username))]
 | 
			
		||||
    pub async fn sign_document(
 | 
			
		||||
        &self,
 | 
			
		||||
        document_id: &str,
 | 
			
		||||
        username: &str,
 | 
			
		||||
        user_token: &str,
 | 
			
		||||
    ) -> Result<()> {
 | 
			
		||||
    pub async fn sign_document(&self, document_id: &str, username: &str, user_token: &str) -> Result<()> {
 | 
			
		||||
        info!("Signing document {} by user {}", document_id, username);
 | 
			
		||||
 | 
			
		||||
        // Get document metadata
 | 
			
		||||
| 
						 | 
				
			
			@ -236,15 +199,14 @@ impl DocumentService {
 | 
			
		|||
        let user = self.vault_client.get_user_info(username).await?;
 | 
			
		||||
 | 
			
		||||
        // Sign the document hash with user's key
 | 
			
		||||
        let url = format!("{}/v1/transit/sign/{}", self.vault_client.addr, username);
 | 
			
		||||
        let url = format!("{}/v1/transit/sign/{}",
 | 
			
		||||
            self.vault_client.addr, username);
 | 
			
		||||
 | 
			
		||||
        let payload = json!({
 | 
			
		||||
            "input": BASE64.encode(document.hash.as_bytes()),
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        let response = self
 | 
			
		||||
            .vault_client
 | 
			
		||||
            .client
 | 
			
		||||
        let response = self.vault_client.client
 | 
			
		||||
            .post(&url)
 | 
			
		||||
            .header("X-Vault-Token", user_token)
 | 
			
		||||
            .json(&payload)
 | 
			
		||||
| 
						 | 
				
			
			@ -262,8 +224,7 @@ impl DocumentService {
 | 
			
		|||
                debug!("Generated signature for document {}", document_id);
 | 
			
		||||
 | 
			
		||||
                // Update document with signature
 | 
			
		||||
                self.add_signature(document_id, username, &signature)
 | 
			
		||||
                    .await?;
 | 
			
		||||
                self.add_signature(document_id, username, &signature).await?;
 | 
			
		||||
 | 
			
		||||
                // Update department signature record
 | 
			
		||||
                self.record_department_signature(document_id, &user).await?;
 | 
			
		||||
| 
						 | 
				
			
			@ -277,43 +238,26 @@ impl DocumentService {
 | 
			
		|||
            status => {
 | 
			
		||||
                let error_text = response.text().await?;
 | 
			
		||||
                error!("Failed to sign document: {} - {}", status, error_text);
 | 
			
		||||
                Err(anyhow::anyhow!(
 | 
			
		||||
                    "Failed to sign document: {} - {}",
 | 
			
		||||
                    status,
 | 
			
		||||
                    error_text
 | 
			
		||||
                ))
 | 
			
		||||
                Err(anyhow::anyhow!("Failed to sign document: {} - {}", status, error_text))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Add a signature to a document
 | 
			
		||||
    #[instrument(skip(self, signature), fields(document_id = %document_id, username = %username))]
 | 
			
		||||
    async fn add_signature(
 | 
			
		||||
        &self,
 | 
			
		||||
        document_id: &str,
 | 
			
		||||
        username: &str,
 | 
			
		||||
        signature: &str,
 | 
			
		||||
    ) -> Result<()> {
 | 
			
		||||
        debug!(
 | 
			
		||||
            "Adding signature from {} to document {}",
 | 
			
		||||
            username, document_id
 | 
			
		||||
        );
 | 
			
		||||
    async fn add_signature(&self, document_id: &str, username: &str, signature: &str) -> Result<()> {
 | 
			
		||||
        debug!("Adding signature from {} to document {}", username, document_id);
 | 
			
		||||
 | 
			
		||||
        // Get current document
 | 
			
		||||
        let mut document = self.get_document(document_id).await?;
 | 
			
		||||
 | 
			
		||||
        // Add signature
 | 
			
		||||
        document
 | 
			
		||||
            .signatures
 | 
			
		||||
            .insert(username.to_string(), signature.to_string());
 | 
			
		||||
        document.signatures.insert(username.to_string(), signature.to_string());
 | 
			
		||||
 | 
			
		||||
        // Store updated document
 | 
			
		||||
        self.store_document_metadata(&document).await?;
 | 
			
		||||
 | 
			
		||||
        debug!(
 | 
			
		||||
            "Added signature from {} to document {}",
 | 
			
		||||
            username, document_id
 | 
			
		||||
        );
 | 
			
		||||
        debug!("Added signature from {} to document {}", username, document_id);
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -325,20 +269,13 @@ impl DocumentService {
 | 
			
		|||
            Department::Finance => "finance",
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        debug!(
 | 
			
		||||
            "Recording {} department signature for document {}",
 | 
			
		||||
            dept_str, document_id
 | 
			
		||||
        );
 | 
			
		||||
        debug!("Recording {} department signature for document {}", dept_str, document_id);
 | 
			
		||||
 | 
			
		||||
        let url = format!(
 | 
			
		||||
            "{}/v1/documents/data/dept/{}/signatures/{}",
 | 
			
		||||
            self.vault_client.addr, dept_str, document_id
 | 
			
		||||
        );
 | 
			
		||||
        let url = format!("{}/v1/documents/data/dept/{}/signatures/{}",
 | 
			
		||||
            self.vault_client.addr, dept_str, document_id);
 | 
			
		||||
 | 
			
		||||
        // Check if department signatures already exist
 | 
			
		||||
        let response = self
 | 
			
		||||
            .vault_client
 | 
			
		||||
            .client
 | 
			
		||||
        let response = self.vault_client.client
 | 
			
		||||
            .get(&url)
 | 
			
		||||
            .header("X-Vault-Token", &self.vault_client.token)
 | 
			
		||||
            .send()
 | 
			
		||||
| 
						 | 
				
			
			@ -372,9 +309,7 @@ impl DocumentService {
 | 
			
		|||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        let response = self
 | 
			
		||||
            .vault_client
 | 
			
		||||
            .client
 | 
			
		||||
        let response = self.vault_client.client
 | 
			
		||||
            .post(&url)
 | 
			
		||||
            .header("X-Vault-Token", &self.vault_client.token)
 | 
			
		||||
            .json(&payload)
 | 
			
		||||
| 
						 | 
				
			
			@ -383,23 +318,13 @@ impl DocumentService {
 | 
			
		|||
 | 
			
		||||
        match response.status() {
 | 
			
		||||
            StatusCode::OK | StatusCode::NO_CONTENT => {
 | 
			
		||||
                info!(
 | 
			
		||||
                    "Recorded signature for {} in {} department",
 | 
			
		||||
                    user.username, dept_str
 | 
			
		||||
                );
 | 
			
		||||
                info!("Recorded signature for {} in {} department", user.username, dept_str);
 | 
			
		||||
                Ok(())
 | 
			
		||||
            }
 | 
			
		||||
            status => {
 | 
			
		||||
                let error_text = response.text().await?;
 | 
			
		||||
                error!(
 | 
			
		||||
                    "Failed to record department signature: {} - {}",
 | 
			
		||||
                    status, error_text
 | 
			
		||||
                );
 | 
			
		||||
                Err(anyhow::anyhow!(
 | 
			
		||||
                    "Failed to record department signature: {} - {}",
 | 
			
		||||
                    status,
 | 
			
		||||
                    error_text
 | 
			
		||||
                ))
 | 
			
		||||
                error!("Failed to record department signature: {} - {}", status, error_text);
 | 
			
		||||
                Err(anyhow::anyhow!("Failed to record department signature: {} - {}", status, error_text))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -439,24 +364,17 @@ impl DocumentService {
 | 
			
		|||
 | 
			
		||||
    // Verify document signatures
 | 
			
		||||
    #[instrument(skip(self))]
 | 
			
		||||
    pub async fn verify_document_signatures(
 | 
			
		||||
        &self,
 | 
			
		||||
        document_id: &str,
 | 
			
		||||
    ) -> Result<SignatureVerification> {
 | 
			
		||||
    pub async fn verify_document_signatures(&self, document_id: &str) -> Result<SignatureVerification> {
 | 
			
		||||
        info!("Verifying signatures for document {}", document_id);
 | 
			
		||||
 | 
			
		||||
        // Get document
 | 
			
		||||
        let document = self.get_document(document_id).await?;
 | 
			
		||||
 | 
			
		||||
        // Get signing requirements
 | 
			
		||||
        let url = format!(
 | 
			
		||||
            "{}/v1/documents/data/config/signing_requirements",
 | 
			
		||||
            self.vault_client.addr
 | 
			
		||||
        );
 | 
			
		||||
        let url = format!("{}/v1/documents/data/config/signing_requirements",
 | 
			
		||||
            self.vault_client.addr);
 | 
			
		||||
 | 
			
		||||
        let response = self
 | 
			
		||||
            .vault_client
 | 
			
		||||
            .client
 | 
			
		||||
        let response = self.vault_client.client
 | 
			
		||||
            .get(&url)
 | 
			
		||||
            .header("X-Vault-Token", &self.vault_client.token)
 | 
			
		||||
            .send()
 | 
			
		||||
| 
						 | 
				
			
			@ -466,15 +384,8 @@ impl DocumentService {
 | 
			
		|||
            StatusCode::OK => response.json::<serde_json::Value>().await?,
 | 
			
		||||
            status => {
 | 
			
		||||
                let error_text = response.text().await?;
 | 
			
		||||
                error!(
 | 
			
		||||
                    "Failed to get signing requirements: {} - {}",
 | 
			
		||||
                    status, error_text
 | 
			
		||||
                );
 | 
			
		||||
                return Err(anyhow::anyhow!(
 | 
			
		||||
                    "Failed to get signing requirements: {} - {}",
 | 
			
		||||
                    status,
 | 
			
		||||
                    error_text
 | 
			
		||||
                ));
 | 
			
		||||
                error!("Failed to get signing requirements: {} - {}", status, error_text);
 | 
			
		||||
                return Err(anyhow::anyhow!("Failed to get signing requirements: {} - {}", status, error_text));
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -502,15 +413,13 @@ impl DocumentService {
 | 
			
		|||
 | 
			
		||||
        // Get department signatures
 | 
			
		||||
        let legal_signatures = self.get_department_signatures(document_id, "legal").await?;
 | 
			
		||||
        let finance_signatures = self
 | 
			
		||||
            .get_department_signatures(document_id, "finance")
 | 
			
		||||
            .await?;
 | 
			
		||||
        let finance_signatures = self.get_department_signatures(document_id, "finance").await?;
 | 
			
		||||
 | 
			
		||||
        // Check if requirements are met
 | 
			
		||||
        let total_signatures = document.signatures.len();
 | 
			
		||||
        let is_verified = total_signatures >= required_signatures
 | 
			
		||||
            && legal_signatures.len() >= required_legal
 | 
			
		||||
            && finance_signatures.len() >= required_finance;
 | 
			
		||||
        let is_verified = total_signatures >= required_signatures &&
 | 
			
		||||
                          legal_signatures.len() >= required_legal &&
 | 
			
		||||
                          finance_signatures.len() >= required_finance;
 | 
			
		||||
 | 
			
		||||
        let verification = SignatureVerification {
 | 
			
		||||
            document_id: document_id.to_string(),
 | 
			
		||||
| 
						 | 
				
			
			@ -540,24 +449,13 @@ impl DocumentService {
 | 
			
		|||
 | 
			
		||||
    // Get department signatures for a document
 | 
			
		||||
    #[instrument(skip(self))]
 | 
			
		||||
    async fn get_department_signatures(
 | 
			
		||||
        &self,
 | 
			
		||||
        document_id: &str,
 | 
			
		||||
        department: &str,
 | 
			
		||||
    ) -> Result<Vec<String>> {
 | 
			
		||||
        debug!(
 | 
			
		||||
            "Getting {} department signatures for document {}",
 | 
			
		||||
            department, document_id
 | 
			
		||||
        );
 | 
			
		||||
    async fn get_department_signatures(&self, document_id: &str, department: &str) -> Result<Vec<String>> {
 | 
			
		||||
        debug!("Getting {} department signatures for document {}", department, document_id);
 | 
			
		||||
 | 
			
		||||
        let url = format!(
 | 
			
		||||
            "{}/v1/documents/data/dept/{}/signatures/{}",
 | 
			
		||||
            self.vault_client.addr, department, document_id
 | 
			
		||||
        );
 | 
			
		||||
        let url = format!("{}/v1/documents/data/dept/{}/signatures/{}",
 | 
			
		||||
            self.vault_client.addr, department, document_id);
 | 
			
		||||
 | 
			
		||||
        let response = self
 | 
			
		||||
            .vault_client
 | 
			
		||||
            .client
 | 
			
		||||
        let response = self.vault_client.client
 | 
			
		||||
            .get(&url)
 | 
			
		||||
            .header("X-Vault-Token", &self.vault_client.token)
 | 
			
		||||
            .send()
 | 
			
		||||
| 
						 | 
				
			
			@ -579,12 +477,8 @@ impl DocumentService {
 | 
			
		|||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        debug!(
 | 
			
		||||
            "Found {} signatures for {} department on document {}",
 | 
			
		||||
            signatures.len(),
 | 
			
		||||
            department,
 | 
			
		||||
            document_id
 | 
			
		||||
        );
 | 
			
		||||
        debug!("Found {} signatures for {} department on document {}",
 | 
			
		||||
               signatures.len(), department, document_id);
 | 
			
		||||
 | 
			
		||||
        Ok(signatures)
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										12
									
								
								src/lib.rs
									
										
									
									
									
								
							
							
						
						
									
										12
									
								
								src/lib.rs
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -1,11 +1,11 @@
 | 
			
		|||
// Modules that implement our hierarchical signing system
 | 
			
		||||
pub mod api;
 | 
			
		||||
pub mod document_service;
 | 
			
		||||
pub mod vault_init;
 | 
			
		||||
pub mod vault_setup;
 | 
			
		||||
pub mod vault_init;
 | 
			
		||||
pub mod document_service;
 | 
			
		||||
pub mod api;
 | 
			
		||||
 | 
			
		||||
// Re-export main components for easier access
 | 
			
		||||
pub use api::start_api;
 | 
			
		||||
pub use document_service::DocumentService;
 | 
			
		||||
pub use vault_init::initialize_vault;
 | 
			
		||||
pub use vault_setup::VaultClient;
 | 
			
		||||
pub use vault_init::initialize_vault;
 | 
			
		||||
pub use document_service::DocumentService;
 | 
			
		||||
pub use api::start_api;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										108
									
								
								src/main.rs
									
										
									
									
									
								
							
							
						
						
									
										108
									
								
								src/main.rs
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -1,18 +1,14 @@
 | 
			
		|||
use anyhow::Result;
 | 
			
		||||
use clap::{Parser, Subcommand};
 | 
			
		||||
use std::path::PathBuf;
 | 
			
		||||
use tracing::info;
 | 
			
		||||
use tracing_subscriber::{EnvFilter, fmt};
 | 
			
		||||
use tracing::{info};
 | 
			
		||||
use tracing_subscriber::{fmt, EnvFilter};
 | 
			
		||||
 | 
			
		||||
// Import our library
 | 
			
		||||
use vault_hier::{initialize_vault, start_api};
 | 
			
		||||
use vault_hier::{start_api, initialize_vault};
 | 
			
		||||
 | 
			
		||||
#[derive(Parser)]
 | 
			
		||||
#[command(
 | 
			
		||||
    author,
 | 
			
		||||
    version,
 | 
			
		||||
    about = "Hierarchical Document Signing with HashiCorp Vault"
 | 
			
		||||
)]
 | 
			
		||||
#[command(author, version, about = "Hierarchical Document Signing with HashiCorp Vault")]
 | 
			
		||||
struct Cli {
 | 
			
		||||
    #[command(subcommand)]
 | 
			
		||||
    command: Commands,
 | 
			
		||||
| 
						 | 
				
			
			@ -121,49 +117,27 @@ async fn main() -> Result<()> {
 | 
			
		|||
    let cli = Cli::parse();
 | 
			
		||||
 | 
			
		||||
    match cli.command {
 | 
			
		||||
        Commands::Server {
 | 
			
		||||
            vault_addr,
 | 
			
		||||
            api_port,
 | 
			
		||||
        } => {
 | 
			
		||||
        Commands::Server { vault_addr, api_port } => {
 | 
			
		||||
            run_server(&vault_addr, api_port).await?;
 | 
			
		||||
        }
 | 
			
		||||
        Commands::Login {
 | 
			
		||||
            username,
 | 
			
		||||
            password,
 | 
			
		||||
            api_url,
 | 
			
		||||
        } => {
 | 
			
		||||
        },
 | 
			
		||||
        Commands::Login { username, password, api_url } => {
 | 
			
		||||
            login(&username, &password, &api_url).await?;
 | 
			
		||||
        }
 | 
			
		||||
        Commands::Upload {
 | 
			
		||||
            name,
 | 
			
		||||
            file,
 | 
			
		||||
            api_url,
 | 
			
		||||
        } => {
 | 
			
		||||
        },
 | 
			
		||||
        Commands::Upload { name, file, api_url } => {
 | 
			
		||||
            upload_document(&name, file, &api_url).await?;
 | 
			
		||||
        }
 | 
			
		||||
        Commands::Sign {
 | 
			
		||||
            document_id,
 | 
			
		||||
            username,
 | 
			
		||||
            token,
 | 
			
		||||
            api_url,
 | 
			
		||||
        } => {
 | 
			
		||||
        },
 | 
			
		||||
        Commands::Sign { document_id, username, token, api_url } => {
 | 
			
		||||
            sign_document(&document_id, &username, &token, &api_url).await?;
 | 
			
		||||
        }
 | 
			
		||||
        Commands::Verify {
 | 
			
		||||
            document_id,
 | 
			
		||||
            api_url,
 | 
			
		||||
        } => {
 | 
			
		||||
        },
 | 
			
		||||
        Commands::Verify { document_id, api_url } => {
 | 
			
		||||
            verify_document(&document_id, &api_url).await?;
 | 
			
		||||
        }
 | 
			
		||||
        },
 | 
			
		||||
        Commands::List { api_url } => {
 | 
			
		||||
            list_documents(&api_url).await?;
 | 
			
		||||
        }
 | 
			
		||||
        Commands::Get {
 | 
			
		||||
            document_id,
 | 
			
		||||
            api_url,
 | 
			
		||||
        } => {
 | 
			
		||||
        },
 | 
			
		||||
        Commands::Get { document_id, api_url } => {
 | 
			
		||||
            get_document(&document_id, &api_url).await?;
 | 
			
		||||
        }
 | 
			
		||||
        },
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
| 
						 | 
				
			
			@ -191,7 +165,7 @@ async fn login(username: &str, password: &str, api_url: &str) -> Result<()> {
 | 
			
		|||
 | 
			
		||||
    let client = reqwest::Client::new();
 | 
			
		||||
    let response = client
 | 
			
		||||
        .post(format!("{}/api/login", api_url))
 | 
			
		||||
        .post(&format!("{}/api/login", api_url))
 | 
			
		||||
        .json(&serde_json::json!({
 | 
			
		||||
            "username": username,
 | 
			
		||||
            "password": password,
 | 
			
		||||
| 
						 | 
				
			
			@ -220,14 +194,12 @@ async fn upload_document(name: &str, file_path: PathBuf, api_url: &str) -> Resul
 | 
			
		|||
 | 
			
		||||
    let form = reqwest::multipart::Form::new()
 | 
			
		||||
        .text("name", name.to_string())
 | 
			
		||||
        .part(
 | 
			
		||||
            "file",
 | 
			
		||||
            reqwest::multipart::Part::bytes(file_content).file_name(file_name.to_string()),
 | 
			
		||||
        );
 | 
			
		||||
        .part("file", reqwest::multipart::Part::bytes(file_content)
 | 
			
		||||
            .file_name(file_name.to_string()));
 | 
			
		||||
 | 
			
		||||
    let client = reqwest::Client::new();
 | 
			
		||||
    let response = client
 | 
			
		||||
        .post(format!("{}/api/documents", api_url))
 | 
			
		||||
        .post(&format!("{}/api/documents", api_url))
 | 
			
		||||
        .multipart(form)
 | 
			
		||||
        .send()
 | 
			
		||||
        .await?;
 | 
			
		||||
| 
						 | 
				
			
			@ -244,17 +216,12 @@ async fn upload_document(name: &str, file_path: PathBuf, api_url: &str) -> Resul
 | 
			
		|||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn sign_document(
 | 
			
		||||
    document_id: &str,
 | 
			
		||||
    username: &str,
 | 
			
		||||
    token: &str,
 | 
			
		||||
    api_url: &str,
 | 
			
		||||
) -> Result<()> {
 | 
			
		||||
async fn sign_document(document_id: &str, username: &str, token: &str, api_url: &str) -> Result<()> {
 | 
			
		||||
    info!("Signing document: {}", document_id);
 | 
			
		||||
 | 
			
		||||
    let client = reqwest::Client::new();
 | 
			
		||||
    let response = client
 | 
			
		||||
        .post(format!("{}/api/documents/{}/sign", api_url, document_id))
 | 
			
		||||
        .post(&format!("{}/api/documents/{}/sign", api_url, document_id))
 | 
			
		||||
        .json(&serde_json::json!({
 | 
			
		||||
            "username": username,
 | 
			
		||||
            "token": token,
 | 
			
		||||
| 
						 | 
				
			
			@ -279,7 +246,7 @@ async fn verify_document(document_id: &str, api_url: &str) -> Result<()> {
 | 
			
		|||
 | 
			
		||||
    let client = reqwest::Client::new();
 | 
			
		||||
    let response = client
 | 
			
		||||
        .get(format!("{}/api/documents/{}/verify", api_url, document_id))
 | 
			
		||||
        .get(&format!("{}/api/documents/{}/verify", api_url, document_id))
 | 
			
		||||
        .send()
 | 
			
		||||
        .await?;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -288,21 +255,16 @@ async fn verify_document(document_id: &str, api_url: &str) -> Result<()> {
 | 
			
		|||
        println!("Verification result:");
 | 
			
		||||
        println!("  Valid: {}", data["valid"]);
 | 
			
		||||
        println!("  Total signatures: {}", data["signature_count"]);
 | 
			
		||||
        println!(
 | 
			
		||||
            "  Departments represented: {}",
 | 
			
		||||
            data["departments_represented"]
 | 
			
		||||
        );
 | 
			
		||||
        println!("  Departments represented: {}", data["departments_represented"]);
 | 
			
		||||
 | 
			
		||||
        if let Some(signatures) = data["signatures"].as_array() {
 | 
			
		||||
            println!("\nSignatures:");
 | 
			
		||||
            for (i, sig) in signatures.iter().enumerate() {
 | 
			
		||||
                println!(
 | 
			
		||||
                    "  {}. User: {}, Department: {}, Time: {}",
 | 
			
		||||
                    i + 1,
 | 
			
		||||
                println!("  {}. User: {}, Department: {}, Time: {}",
 | 
			
		||||
                    i+1,
 | 
			
		||||
                    sig["username"],
 | 
			
		||||
                    sig["department"],
 | 
			
		||||
                    sig["timestamp"]
 | 
			
		||||
                );
 | 
			
		||||
                    sig["timestamp"]);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
| 
						 | 
				
			
			@ -318,7 +280,7 @@ async fn list_documents(api_url: &str) -> Result<()> {
 | 
			
		|||
 | 
			
		||||
    let client = reqwest::Client::new();
 | 
			
		||||
    let response = client
 | 
			
		||||
        .get(format!("{}/api/documents", api_url))
 | 
			
		||||
        .get(&format!("{}/api/documents", api_url))
 | 
			
		||||
        .send()
 | 
			
		||||
        .await?;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -327,7 +289,7 @@ async fn list_documents(api_url: &str) -> Result<()> {
 | 
			
		|||
        println!("Documents:");
 | 
			
		||||
 | 
			
		||||
        for (i, doc) in data.iter().enumerate() {
 | 
			
		||||
            println!("  {}. ID: {}, Name: {}", i + 1, doc["id"], doc["name"]);
 | 
			
		||||
            println!("  {}. ID: {}, Name: {}", i+1, doc["id"], doc["name"]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if data.is_empty() {
 | 
			
		||||
| 
						 | 
				
			
			@ -346,7 +308,7 @@ async fn get_document(document_id: &str, api_url: &str) -> Result<()> {
 | 
			
		|||
 | 
			
		||||
    let client = reqwest::Client::new();
 | 
			
		||||
    let response = client
 | 
			
		||||
        .get(format!("{}/api/documents/{}", api_url, document_id))
 | 
			
		||||
        .get(&format!("{}/api/documents/{}", api_url, document_id))
 | 
			
		||||
        .send()
 | 
			
		||||
        .await?;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -361,13 +323,11 @@ async fn get_document(document_id: &str, api_url: &str) -> Result<()> {
 | 
			
		|||
        if let Some(signatures) = doc["signatures"].as_array() {
 | 
			
		||||
            println!("\nSignatures:");
 | 
			
		||||
            for (i, sig) in signatures.iter().enumerate() {
 | 
			
		||||
                println!(
 | 
			
		||||
                    "  {}. User: {}, Department: {}, Time: {}",
 | 
			
		||||
                    i + 1,
 | 
			
		||||
                println!("  {}. User: {}, Department: {}, Time: {}",
 | 
			
		||||
                    i+1,
 | 
			
		||||
                    sig["username"],
 | 
			
		||||
                    sig["department"],
 | 
			
		||||
                    sig["timestamp"]
 | 
			
		||||
                );
 | 
			
		||||
                    sig["timestamp"]);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if signatures.is_empty() {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,10 @@
 | 
			
		|||
use anyhow::{Context, Result};
 | 
			
		||||
use reqwest::Client;
 | 
			
		||||
use std::{env, fs};
 | 
			
		||||
use tracing::{debug, error, info, warn};
 | 
			
		||||
use std::{
 | 
			
		||||
    env,
 | 
			
		||||
    fs,
 | 
			
		||||
};
 | 
			
		||||
use tracing::{info, warn, error, debug};
 | 
			
		||||
 | 
			
		||||
use crate::vault_setup::VaultClient;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -13,17 +16,14 @@ pub async fn initialize_vault(vault_addr: &str) -> Result<String> {
 | 
			
		|||
    VaultClient::wait_for_vault(vault_addr).await?;
 | 
			
		||||
 | 
			
		||||
    // Display Vault health status
 | 
			
		||||
    let health_url = format!(
 | 
			
		||||
        "{}/v1/sys/health?standbyok=true&sealedok=true&uninitok=true",
 | 
			
		||||
        vault_addr
 | 
			
		||||
    );
 | 
			
		||||
    let health_url = format!("{}/v1/sys/health?standbyok=true&sealedok=true&uninitok=true", vault_addr);
 | 
			
		||||
    match client.get(&health_url).send().await {
 | 
			
		||||
        Ok(response) => {
 | 
			
		||||
            if response.status().is_success() {
 | 
			
		||||
                let status_text = response.text().await?;
 | 
			
		||||
                info!("Vault status: {}", status_text);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        },
 | 
			
		||||
        Err(e) => warn!("Error getting Vault status: {}", e),
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -47,7 +47,7 @@ pub async fn initialize_vault(vault_addr: &str) -> Result<String> {
 | 
			
		|||
                    Ok(key) => {
 | 
			
		||||
                        info!("Found unseal key {} from environment", i);
 | 
			
		||||
                        unseal_keys.push(key);
 | 
			
		||||
                    }
 | 
			
		||||
                    },
 | 
			
		||||
                    Err(_) => {
 | 
			
		||||
                        debug!("Unseal key {} not found in environment", i);
 | 
			
		||||
                    }
 | 
			
		||||
| 
						 | 
				
			
			@ -56,16 +56,11 @@ pub async fn initialize_vault(vault_addr: &str) -> Result<String> {
 | 
			
		|||
 | 
			
		||||
            // If we have unseal keys, try to unseal
 | 
			
		||||
            if !unseal_keys.is_empty() {
 | 
			
		||||
                info!(
 | 
			
		||||
                    "Found {} unseal keys. Attempting to unseal...",
 | 
			
		||||
                    unseal_keys.len()
 | 
			
		||||
                );
 | 
			
		||||
                info!("Found {} unseal keys. Attempting to unseal...", unseal_keys.len());
 | 
			
		||||
                VaultClient::unseal_vault(&client, vault_addr, &unseal_keys).await?;
 | 
			
		||||
            } else {
 | 
			
		||||
                warn!("No unseal keys found. Vault remains sealed.");
 | 
			
		||||
                info!(
 | 
			
		||||
                    "To unseal, set VAULT_UNSEAL_KEY_1, VAULT_UNSEAL_KEY_2, etc. environment variables."
 | 
			
		||||
                );
 | 
			
		||||
                info!("To unseal, set VAULT_UNSEAL_KEY_1, VAULT_UNSEAL_KEY_2, etc. environment variables.");
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            info!("Vault is already unsealed.");
 | 
			
		||||
| 
						 | 
				
			
			@ -76,7 +71,7 @@ pub async fn initialize_vault(vault_addr: &str) -> Result<String> {
 | 
			
		|||
            Ok(token) => {
 | 
			
		||||
                info!("Found root token from environment");
 | 
			
		||||
                root_token = token;
 | 
			
		||||
            }
 | 
			
		||||
            },
 | 
			
		||||
            Err(_) => {
 | 
			
		||||
                // Try to load from credentials file
 | 
			
		||||
                if let Ok(contents) = fs::read_to_string("vault-credentials.json") {
 | 
			
		||||
| 
						 | 
				
			
			@ -91,12 +86,8 @@ pub async fn initialize_vault(vault_addr: &str) -> Result<String> {
 | 
			
		|||
        }
 | 
			
		||||
 | 
			
		||||
        if root_token.is_empty() {
 | 
			
		||||
            error!(
 | 
			
		||||
                "Unable to find root token. Please set VAULT_TOKEN environment variable or provide vault-credentials.json file."
 | 
			
		||||
            );
 | 
			
		||||
            anyhow::bail!(
 | 
			
		||||
                "Unable to find root token. Please set VAULT_TOKEN environment variable or provide vault-credentials.json file."
 | 
			
		||||
            );
 | 
			
		||||
            error!("Unable to find root token. Please set VAULT_TOKEN environment variable or provide vault-credentials.json file.");
 | 
			
		||||
            anyhow::bail!("Unable to find root token. Please set VAULT_TOKEN environment variable or provide vault-credentials.json file.");
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        // Initialize Vault
 | 
			
		||||
| 
						 | 
				
			
			@ -121,17 +112,11 @@ pub async fn initialize_vault(vault_addr: &str) -> Result<String> {
 | 
			
		|||
        if let Ok(()) = std::fs::create_dir_all("/app/data") {
 | 
			
		||||
            let docker_json_path = "/app/data/vault-credentials.json";
 | 
			
		||||
            VaultClient::save_credentials(&init_response, docker_json_path)?;
 | 
			
		||||
            info!(
 | 
			
		||||
                "Backup JSON credentials saved to Docker volume at: {}",
 | 
			
		||||
                docker_json_path
 | 
			
		||||
            );
 | 
			
		||||
            info!("Backup JSON credentials saved to Docker volume at: {}", docker_json_path);
 | 
			
		||||
 | 
			
		||||
            let docker_text_path = "/app/data/vault-credentials.txt";
 | 
			
		||||
            VaultClient::save_credentials(&init_response, docker_text_path)?;
 | 
			
		||||
            info!(
 | 
			
		||||
                "Backup text credentials saved to Docker volume at: {}",
 | 
			
		||||
                docker_text_path
 | 
			
		||||
            );
 | 
			
		||||
            info!("Backup text credentials saved to Docker volume at: {}", docker_text_path);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        info!("=========================================");
 | 
			
		||||
| 
						 | 
				
			
			@ -145,10 +130,8 @@ pub async fn initialize_vault(vault_addr: &str) -> Result<String> {
 | 
			
		|||
        info!("=========================================");
 | 
			
		||||
 | 
			
		||||
        // Unseal Vault using the first three keys
 | 
			
		||||
        let unseal_keys = init_response
 | 
			
		||||
            .keys_base64
 | 
			
		||||
            .iter()
 | 
			
		||||
            .take(3) // We only need threshold number of keys (3)
 | 
			
		||||
        let unseal_keys = init_response.keys_base64.iter()
 | 
			
		||||
            .take(3)  // We only need threshold number of keys (3)
 | 
			
		||||
            .cloned()
 | 
			
		||||
            .collect::<Vec<String>>();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,9 +2,14 @@ use anyhow::{Context, Result};
 | 
			
		|||
use reqwest::{Client, StatusCode};
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use serde_json::json;
 | 
			
		||||
use std::{fs::File, io::Write, path::Path, time::Duration};
 | 
			
		||||
use std::{
 | 
			
		||||
    fs::File,
 | 
			
		||||
    io::Write,
 | 
			
		||||
    path::Path,
 | 
			
		||||
    time::Duration,
 | 
			
		||||
};
 | 
			
		||||
use tokio::time::sleep;
 | 
			
		||||
use tracing::{debug, error, info, instrument};
 | 
			
		||||
use tracing::{info, error, debug, instrument};
 | 
			
		||||
 | 
			
		||||
// Vault API response structures
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
| 
						 | 
				
			
			@ -72,17 +77,9 @@ impl VaultClient {
 | 
			
		|||
        let client = Client::new();
 | 
			
		||||
 | 
			
		||||
        for i in 1..=30 {
 | 
			
		||||
            let health_url = format!(
 | 
			
		||||
                "{}/v1/sys/health?standbyok=true&sealedok=true&uninitok=true",
 | 
			
		||||
                addr
 | 
			
		||||
            );
 | 
			
		||||
            let health_url = format!("{}/v1/sys/health?standbyok=true&sealedok=true&uninitok=true", addr);
 | 
			
		||||
 | 
			
		||||
            match client
 | 
			
		||||
                .get(&health_url)
 | 
			
		||||
                .timeout(Duration::from_secs(1))
 | 
			
		||||
                .send()
 | 
			
		||||
                .await
 | 
			
		||||
            {
 | 
			
		||||
            match client.get(&health_url).timeout(Duration::from_secs(1)).send().await {
 | 
			
		||||
                Ok(response) => {
 | 
			
		||||
                    let status = response.status().as_u16();
 | 
			
		||||
                    // Accept any of these status codes as "available"
 | 
			
		||||
| 
						 | 
				
			
			@ -92,7 +89,7 @@ impl VaultClient {
 | 
			
		|||
                    }
 | 
			
		||||
 | 
			
		||||
                    debug!("Vault returned unexpected status code: {}", status);
 | 
			
		||||
                }
 | 
			
		||||
                },
 | 
			
		||||
                Err(e) => {
 | 
			
		||||
                    debug!("Error connecting to Vault: {}", e);
 | 
			
		||||
                }
 | 
			
		||||
| 
						 | 
				
			
			@ -100,9 +97,7 @@ impl VaultClient {
 | 
			
		|||
 | 
			
		||||
            if i == 30 {
 | 
			
		||||
                error!("Timed out waiting for Vault to become available");
 | 
			
		||||
                return Err(anyhow::anyhow!(
 | 
			
		||||
                    "Timed out waiting for Vault to become available"
 | 
			
		||||
                ));
 | 
			
		||||
                return Err(anyhow::anyhow!("Timed out waiting for Vault to become available"));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            info!("Vault is unavailable - sleeping (attempt {}/30)", i);
 | 
			
		||||
| 
						 | 
				
			
			@ -117,7 +112,10 @@ impl VaultClient {
 | 
			
		|||
    pub async fn check_init_status(client: &Client, addr: &str) -> Result<bool> {
 | 
			
		||||
        info!("Checking if Vault is already initialized...");
 | 
			
		||||
 | 
			
		||||
        let response = client.get(format!("{}/v1/sys/init", addr)).send().await?;
 | 
			
		||||
        let response = client
 | 
			
		||||
            .get(format!("{}/v1/sys/init", addr))
 | 
			
		||||
            .send()
 | 
			
		||||
            .await?;
 | 
			
		||||
 | 
			
		||||
        if response.status().is_success() {
 | 
			
		||||
            let status = response.json::<serde_json::Value>().await?;
 | 
			
		||||
| 
						 | 
				
			
			@ -142,10 +140,8 @@ impl VaultClient {
 | 
			
		|||
 | 
			
		||||
        if response.status().is_success() {
 | 
			
		||||
            let status = response.json::<SealStatusResponse>().await?;
 | 
			
		||||
            info!(
 | 
			
		||||
                "Seal status: sealed={}, threshold={}, shares={}, progress={}",
 | 
			
		||||
                status.sealed, status.t, status.n, status.progress
 | 
			
		||||
            );
 | 
			
		||||
            info!("Seal status: sealed={}, threshold={}, shares={}, progress={}",
 | 
			
		||||
                status.sealed, status.t, status.n, status.progress);
 | 
			
		||||
            return Ok(status);
 | 
			
		||||
        } else {
 | 
			
		||||
            let error_text = response.text().await?;
 | 
			
		||||
| 
						 | 
				
			
			@ -228,7 +224,9 @@ impl VaultClient {
 | 
			
		|||
        for (i, key) in unseal_keys.iter().take(required_keys).enumerate() {
 | 
			
		||||
            info!("Applying unseal key {}/{}...", i + 1, required_keys);
 | 
			
		||||
 | 
			
		||||
            let unseal_req = UnsealRequest { key: key.clone() };
 | 
			
		||||
            let unseal_req = UnsealRequest {
 | 
			
		||||
                key: key.clone(),
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            let response = client
 | 
			
		||||
                .put(format!("{}/v1/sys/unseal", addr))
 | 
			
		||||
| 
						 | 
				
			
			@ -321,8 +319,7 @@ impl VaultClient {
 | 
			
		|||
            "type": engine_type,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        let response = self
 | 
			
		||||
            .client
 | 
			
		||||
        let response = self.client
 | 
			
		||||
            .post(&url)
 | 
			
		||||
            .header("X-Vault-Token", &self.token)
 | 
			
		||||
            .json(&payload)
 | 
			
		||||
| 
						 | 
				
			
			@ -342,23 +339,13 @@ impl VaultClient {
 | 
			
		|||
                    Ok(())
 | 
			
		||||
                } else {
 | 
			
		||||
                    error!("Failed to enable secrets engine: {}", error_text);
 | 
			
		||||
                    Err(anyhow::anyhow!(
 | 
			
		||||
                        "Failed to enable secrets engine: {}",
 | 
			
		||||
                        error_text
 | 
			
		||||
                    ))
 | 
			
		||||
                    Err(anyhow::anyhow!("Failed to enable secrets engine: {}", error_text))
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            status => {
 | 
			
		||||
                let error_text = response.text().await?;
 | 
			
		||||
                error!(
 | 
			
		||||
                    "Failed to enable secrets engine: {} - {}",
 | 
			
		||||
                    status, error_text
 | 
			
		||||
                );
 | 
			
		||||
                Err(anyhow::anyhow!(
 | 
			
		||||
                    "Failed to enable secrets engine: {} - {}",
 | 
			
		||||
                    status,
 | 
			
		||||
                    error_text
 | 
			
		||||
                ))
 | 
			
		||||
                error!("Failed to enable secrets engine: {} - {}", status, error_text);
 | 
			
		||||
                Err(anyhow::anyhow!("Failed to enable secrets engine: {} - {}", status, error_text))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -373,8 +360,7 @@ impl VaultClient {
 | 
			
		|||
            "type": method,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        let response = self
 | 
			
		||||
            .client
 | 
			
		||||
        let response = self.client
 | 
			
		||||
            .post(&url)
 | 
			
		||||
            .header("X-Vault-Token", &self.token)
 | 
			
		||||
            .json(&payload)
 | 
			
		||||
| 
						 | 
				
			
			@ -394,38 +380,25 @@ impl VaultClient {
 | 
			
		|||
                    Ok(())
 | 
			
		||||
                } else {
 | 
			
		||||
                    error!("Failed to enable auth method: {}", error_text);
 | 
			
		||||
                    Err(anyhow::anyhow!(
 | 
			
		||||
                        "Failed to enable auth method: {}",
 | 
			
		||||
                        error_text
 | 
			
		||||
                    ))
 | 
			
		||||
                    Err(anyhow::anyhow!("Failed to enable auth method: {}", error_text))
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            status => {
 | 
			
		||||
                let error_text = response.text().await?;
 | 
			
		||||
                error!("Failed to enable auth method: {} - {}", status, error_text);
 | 
			
		||||
                Err(anyhow::anyhow!(
 | 
			
		||||
                    "Failed to enable auth method: {} - {}",
 | 
			
		||||
                    status,
 | 
			
		||||
                    error_text
 | 
			
		||||
                ))
 | 
			
		||||
                Err(anyhow::anyhow!("Failed to enable auth method: {} - {}", status, error_text))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Create a new user in Vault and associate with department
 | 
			
		||||
    #[instrument(skip(self, password))]
 | 
			
		||||
    pub async fn create_user(
 | 
			
		||||
        &self,
 | 
			
		||||
        username: &str,
 | 
			
		||||
        password: &str,
 | 
			
		||||
        department: Department,
 | 
			
		||||
    ) -> Result<()> {
 | 
			
		||||
    pub async fn create_user(&self, username: &str, password: &str, department: Department) -> Result<()> {
 | 
			
		||||
        info!("Creating user {} in department {:?}", username, department);
 | 
			
		||||
 | 
			
		||||
        // Step 1: Create a policy for the user
 | 
			
		||||
        let policy_name = format!("{}-policy", username);
 | 
			
		||||
        self.create_signing_policy(&policy_name, department.clone())
 | 
			
		||||
            .await?;
 | 
			
		||||
        self.create_signing_policy(&policy_name, department.clone()).await?;
 | 
			
		||||
 | 
			
		||||
        // Step 2: Create the user with userpass auth
 | 
			
		||||
        let url = format!("{}/v1/auth/userpass/users/{}", self.addr, username);
 | 
			
		||||
| 
						 | 
				
			
			@ -434,8 +407,7 @@ impl VaultClient {
 | 
			
		|||
            "policies": [policy_name],
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        let response = self
 | 
			
		||||
            .client
 | 
			
		||||
        let response = self.client
 | 
			
		||||
            .post(&url)
 | 
			
		||||
            .header("X-Vault-Token", &self.token)
 | 
			
		||||
            .json(&payload)
 | 
			
		||||
| 
						 | 
				
			
			@ -457,11 +429,7 @@ impl VaultClient {
 | 
			
		|||
            status => {
 | 
			
		||||
                let error_text = response.text().await?;
 | 
			
		||||
                error!("Failed to create user: {} - {}", status, error_text);
 | 
			
		||||
                Err(anyhow::anyhow!(
 | 
			
		||||
                    "Failed to create user: {} - {}",
 | 
			
		||||
                    status,
 | 
			
		||||
                    error_text
 | 
			
		||||
                ))
 | 
			
		||||
                Err(anyhow::anyhow!("Failed to create user: {} - {}", status, error_text))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -478,8 +446,7 @@ impl VaultClient {
 | 
			
		|||
        let username = policy_name.trim_end_matches("-policy");
 | 
			
		||||
        
 | 
			
		||||
        // Policy content with specific paths for the department
 | 
			
		||||
        let policy = format!(
 | 
			
		||||
            r#"
 | 
			
		||||
        let policy = format!(r#"
 | 
			
		||||
            # Allow reading document metadata
 | 
			
		||||
            path "documents/data/docs/*" {{
 | 
			
		||||
                capabilities = ["read"]
 | 
			
		||||
| 
						 | 
				
			
			@ -499,17 +466,14 @@ impl VaultClient {
 | 
			
		|||
            path "documents/data/dept/{}/signatures/*" {{
 | 
			
		||||
                capabilities = ["create", "read", "update"]
 | 
			
		||||
            }}
 | 
			
		||||
        "#,
 | 
			
		||||
            username, dept_name
 | 
			
		||||
        );
 | 
			
		||||
        "#, username, dept_name);
 | 
			
		||||
 | 
			
		||||
        let url = format!("{}/v1/sys/policies/acl/{}", self.addr, policy_name);
 | 
			
		||||
        let payload = json!({
 | 
			
		||||
            "policy": policy,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        let response = self
 | 
			
		||||
            .client
 | 
			
		||||
        let response = self.client
 | 
			
		||||
            .put(&url)
 | 
			
		||||
            .header("X-Vault-Token", &self.token)
 | 
			
		||||
            .json(&payload)
 | 
			
		||||
| 
						 | 
				
			
			@ -524,11 +488,7 @@ impl VaultClient {
 | 
			
		|||
            status => {
 | 
			
		||||
                let error_text = response.text().await?;
 | 
			
		||||
                error!("Failed to create policy: {} - {}", status, error_text);
 | 
			
		||||
                Err(anyhow::anyhow!(
 | 
			
		||||
                    "Failed to create policy: {} - {}",
 | 
			
		||||
                    status,
 | 
			
		||||
                    error_text
 | 
			
		||||
                ))
 | 
			
		||||
                Err(anyhow::anyhow!("Failed to create policy: {} - {}", status, error_text))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -541,8 +501,7 @@ impl VaultClient {
 | 
			
		|||
            "type": "ed25519",
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        let response = self
 | 
			
		||||
            .client
 | 
			
		||||
        let response = self.client
 | 
			
		||||
            .post(&url)
 | 
			
		||||
            .header("X-Vault-Token", &self.token)
 | 
			
		||||
            .json(&payload)
 | 
			
		||||
| 
						 | 
				
			
			@ -557,11 +516,7 @@ impl VaultClient {
 | 
			
		|||
            status => {
 | 
			
		||||
                let error_text = response.text().await?;
 | 
			
		||||
                error!("Failed to create signing key: {} - {}", status, error_text);
 | 
			
		||||
                Err(anyhow::anyhow!(
 | 
			
		||||
                    "Failed to create signing key: {} - {}",
 | 
			
		||||
                    status,
 | 
			
		||||
                    error_text
 | 
			
		||||
                ))
 | 
			
		||||
                Err(anyhow::anyhow!("Failed to create signing key: {} - {}", status, error_text))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -582,8 +537,7 @@ impl VaultClient {
 | 
			
		|||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        let response = self
 | 
			
		||||
            .client
 | 
			
		||||
        let response = self.client
 | 
			
		||||
            .post(&url)
 | 
			
		||||
            .header("X-Vault-Token", &self.token)
 | 
			
		||||
            .json(&payload)
 | 
			
		||||
| 
						 | 
				
			
			@ -598,11 +552,7 @@ impl VaultClient {
 | 
			
		|||
            status => {
 | 
			
		||||
                let error_text = response.text().await?;
 | 
			
		||||
                error!("Failed to store user metadata: {} - {}", status, error_text);
 | 
			
		||||
                Err(anyhow::anyhow!(
 | 
			
		||||
                    "Failed to store user metadata: {} - {}",
 | 
			
		||||
                    status,
 | 
			
		||||
                    error_text
 | 
			
		||||
                ))
 | 
			
		||||
                Err(anyhow::anyhow!("Failed to store user metadata: {} - {}", status, error_text))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -617,8 +567,7 @@ impl VaultClient {
 | 
			
		|||
            let username = format!("legal{}", i);
 | 
			
		||||
            let password = format!("legal{}pass", i);
 | 
			
		||||
            debug!(username, "Creating Legal department user");
 | 
			
		||||
            self.create_user(&username, &password, Department::Legal)
 | 
			
		||||
                .await?;
 | 
			
		||||
            self.create_user(&username, &password, Department::Legal).await?;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Create 5 users in Finance department
 | 
			
		||||
| 
						 | 
				
			
			@ -626,8 +575,7 @@ impl VaultClient {
 | 
			
		|||
            let username = format!("finance{}", i);
 | 
			
		||||
            let password = format!("finance{}pass", i);
 | 
			
		||||
            debug!(username, "Creating Finance department user");
 | 
			
		||||
            self.create_user(&username, &password, Department::Finance)
 | 
			
		||||
                .await?;
 | 
			
		||||
            self.create_user(&username, &password, Department::Finance).await?;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Setup document signing requirements
 | 
			
		||||
| 
						 | 
				
			
			@ -642,10 +590,7 @@ impl VaultClient {
 | 
			
		|||
    async fn setup_signing_requirements(&self) -> Result<()> {
 | 
			
		||||
        info!("Setting up document signing requirements");
 | 
			
		||||
 | 
			
		||||
        let url = format!(
 | 
			
		||||
            "{}/v1/documents/data/config/signing_requirements",
 | 
			
		||||
            self.addr
 | 
			
		||||
        );
 | 
			
		||||
        let url = format!("{}/v1/documents/data/config/signing_requirements", self.addr);
 | 
			
		||||
        let payload = json!({
 | 
			
		||||
            "data": {
 | 
			
		||||
                "total_required": 3,
 | 
			
		||||
| 
						 | 
				
			
			@ -665,8 +610,7 @@ impl VaultClient {
 | 
			
		|||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        let response = self
 | 
			
		||||
            .client
 | 
			
		||||
        let response = self.client
 | 
			
		||||
            .post(&url)
 | 
			
		||||
            .header("X-Vault-Token", &self.token)
 | 
			
		||||
            .json(&payload)
 | 
			
		||||
| 
						 | 
				
			
			@ -680,15 +624,8 @@ impl VaultClient {
 | 
			
		|||
            }
 | 
			
		||||
            status => {
 | 
			
		||||
                let error_text = response.text().await?;
 | 
			
		||||
                error!(
 | 
			
		||||
                    "Failed to configure signing requirements: {} - {}",
 | 
			
		||||
                    status, error_text
 | 
			
		||||
                );
 | 
			
		||||
                Err(anyhow::anyhow!(
 | 
			
		||||
                    "Failed to configure signing requirements: {} - {}",
 | 
			
		||||
                    status,
 | 
			
		||||
                    error_text
 | 
			
		||||
                ))
 | 
			
		||||
                error!("Failed to configure signing requirements: {} - {}", status, error_text);
 | 
			
		||||
                Err(anyhow::anyhow!("Failed to configure signing requirements: {} - {}", status, error_text))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -703,7 +640,11 @@ impl VaultClient {
 | 
			
		|||
            "password": password,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        let response = self.client.post(&url).json(&payload).send().await?;
 | 
			
		||||
        let response = self.client
 | 
			
		||||
            .post(&url)
 | 
			
		||||
            .json(&payload)
 | 
			
		||||
            .send()
 | 
			
		||||
            .await?;
 | 
			
		||||
 | 
			
		||||
        match response.status() {
 | 
			
		||||
            StatusCode::OK => {
 | 
			
		||||
| 
						 | 
				
			
			@ -719,11 +660,7 @@ impl VaultClient {
 | 
			
		|||
            status => {
 | 
			
		||||
                let error_text = response.text().await?;
 | 
			
		||||
                error!("Failed to login: {} - {}", status, error_text);
 | 
			
		||||
                Err(anyhow::anyhow!(
 | 
			
		||||
                    "Failed to login: {} - {}",
 | 
			
		||||
                    status,
 | 
			
		||||
                    error_text
 | 
			
		||||
                ))
 | 
			
		||||
                Err(anyhow::anyhow!("Failed to login: {} - {}", status, error_text))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -735,8 +672,7 @@ impl VaultClient {
 | 
			
		|||
 | 
			
		||||
        let url = format!("{}/v1/documents/data/users/{}", self.addr, username);
 | 
			
		||||
 | 
			
		||||
        let response = self
 | 
			
		||||
            .client
 | 
			
		||||
        let response = self.client
 | 
			
		||||
            .get(&url)
 | 
			
		||||
            .header("X-Vault-Token", &self.token)
 | 
			
		||||
            .send()
 | 
			
		||||
| 
						 | 
				
			
			@ -758,10 +694,7 @@ impl VaultClient {
 | 
			
		|||
                    }
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                debug!(
 | 
			
		||||
                    "Retrieved user info for {} in {:?} department",
 | 
			
		||||
                    username, department
 | 
			
		||||
                );
 | 
			
		||||
                debug!("Retrieved user info for {} in {:?} department", username, department);
 | 
			
		||||
                Ok(User {
 | 
			
		||||
                    username: username.to_string(),
 | 
			
		||||
                    department,
 | 
			
		||||
| 
						 | 
				
			
			@ -770,11 +703,7 @@ impl VaultClient {
 | 
			
		|||
            status => {
 | 
			
		||||
                let error_text = response.text().await?;
 | 
			
		||||
                error!("Failed to get user info: {} - {}", status, error_text);
 | 
			
		||||
                Err(anyhow::anyhow!(
 | 
			
		||||
                    "Failed to get user info: {} - {}",
 | 
			
		||||
                    status,
 | 
			
		||||
                    error_text
 | 
			
		||||
                ))
 | 
			
		||||
                Err(anyhow::anyhow!("Failed to get user info: {} - {}", status, error_text))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue