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