From cedaf16acf75d10f6f126906b586c358f201121c Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Fri, 12 Apr 2024 15:27:03 +0100 Subject: [PATCH] feat: wrap API Token in a secrect::Secret and avoid logging Closes kemitix/git-next#30 --- Cargo.toml | 3 +++ src/server/actors/repo/branch.rs | 16 +++++++++++++--- src/server/config.rs | 22 ++++++++++++++-------- src/server/forge/forgejo/config.rs | 5 +++-- src/server/forge/forgejo/mod.rs | 11 +++++++---- 5 files changed, 40 insertions(+), 17 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 83cbed1..25aa3eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,9 @@ tempfile = "3.10" serde = { version = "1.0", features = ["derive"] } toml = "0.8" +# Secrets and Password +secrecy = "0.8" + # error handling terrors = "0.3" diff --git a/src/server/actors/repo/branch.rs b/src/server/actors/repo/branch.rs index dc77fab..484b871 100644 --- a/src/server/actors/repo/branch.rs +++ b/src/server/actors/repo/branch.rs @@ -3,6 +3,7 @@ use std::fmt::Display; use actix::prelude::*; use kxio::network; +use secrecy::ExposeSecret; use tracing::{error, info, warn}; use crate::server::{ @@ -179,7 +180,7 @@ pub async fn advance_main( }; } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct GitRef(pub String); impl From for GitRef { fn from(value: forge::Commit) -> Self { @@ -197,10 +198,16 @@ impl Display for GitRef { } } +#[derive(Debug)] pub enum ResetForce { None, Force(GitRef), } +impl Display for ResetForce { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} pub fn reset( branch: &BranchName, gitref: impl Into, @@ -209,16 +216,19 @@ pub fn reset( ) -> Result<(), std::io::Error> { let gitref: GitRef = gitref.into(); let user = &repo_details.forge.user; - let token = &repo_details.forge.token; + let hostname = &repo_details.forge.hostname; let path = &repo_details.repo; + let token = &repo_details.forge.token.expose_secret(); let origin = format!("https://{user}:{token}@{hostname}/{path}.git"); let force = match reset_force { ResetForce::None => "".to_string(), ResetForce::Force(old_ref) => format!("--force-with-lease={branch}:{old_ref}"), }; + // INFO: never log the command as it contains the API token let command = format!("/usr/bin/git push {origin} {gitref}:{branch} {force}"); - info!("Running command: {}", command); + drop(origin); + info!("Resetting {branch} to {gitref}"); match gix::command::prepare(command) .with_shell_allow_argument_splitting() .stdout(std::process::Stdio::null()) diff --git a/src/server/config.rs b/src/server/config.rs index a8c99c5..ea7ea44 100644 --- a/src/server/config.rs +++ b/src/server/config.rs @@ -3,6 +3,7 @@ use std::{ fmt::{Display, Formatter}, }; +use secrecy::ExposeSecret; use serde::Deserialize; use terrors::OneOf; @@ -94,7 +95,7 @@ impl Forge { } pub fn token(&self) -> ApiToken { - ApiToken(self.token.clone()) + ApiToken(self.token.clone().into()) } pub fn repos(&self) -> impl Iterator { @@ -157,14 +158,19 @@ impl Display for User { write!(f, "{}", self.0) } } -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct ApiToken(pub String); -impl Display for ApiToken { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) +#[derive(Clone, Debug)] +pub struct ApiToken(pub secrecy::Secret); +impl From for ApiToken { + fn from(value: String) -> Self { + Self(value.into()) } } -#[derive(Clone, Debug, PartialEq, Eq)] +impl ExposeSecret for ApiToken { + fn expose_secret(&self) -> &String { + self.0.expose_secret() + } +} +#[derive(Clone, Debug)] pub struct ForgeDetails { pub name: ForgeName, pub forge_type: ForgeType, @@ -206,7 +212,7 @@ impl Display for BranchName { write!(f, "{}", self.0) } } -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug)] pub struct RepoDetails { pub name: RepoName, pub repo: RepoPath, diff --git a/src/server/forge/forgejo/config.rs b/src/server/forge/forgejo/config.rs index 31fac64..779e57a 100644 --- a/src/server/forge/forgejo/config.rs +++ b/src/server/forge/forgejo/config.rs @@ -1,5 +1,6 @@ use base64::Engine; use kxio::network::{self, Network}; +use secrecy::ExposeSecret; use terrors::OneOf; use tracing::{error, info}; @@ -33,7 +34,7 @@ pub async fn load( let path = &details.repo; let filepath = ".git-next.toml"; let branch = &details.branch; - let token = &details.forge.token; + let token = details.forge.token.expose_secret(); let url = network::NetUrl::new(format!( "https://{hostname}/api/v1/repos/{path}/contents/{filepath}?ref={branch}&token={token}" )); @@ -127,7 +128,7 @@ pub async fn validate( ) -> Result> { let hostname = &details.forge.hostname; let path = &details.repo; - let token = &details.forge.token; + let token = details.forge.token.expose_secret(); let url = network::NetUrl::new(format!( "https://{hostname}/api/v1/repos/{path}/branches?token={token}" )); diff --git a/src/server/forge/forgejo/mod.rs b/src/server/forge/forgejo/mod.rs index 5347be5..b1cf897 100644 --- a/src/server/forge/forgejo/mod.rs +++ b/src/server/forge/forgejo/mod.rs @@ -1,4 +1,5 @@ use kxio::network; +use secrecy::ExposeSecret; use tracing::{error, info, warn}; use crate::server; @@ -49,17 +50,19 @@ async fn get_commit_history( ) -> Result, network::NetworkError> { let hostname = &repo_details.forge.hostname; let path = &repo_details.repo; - let token = &repo_details.forge.token; + let mut page = 1; let limit = match find_commits.is_empty() { true => 1, false => 50, }; + let options = "stat=false&verification=false&files=false"; let mut all_commits = Vec::new(); loop { + let token = repo_details.forge.token.expose_secret(); let url = network::NetUrl::new(format!( - "https://{hostname}/api/v1/repos/{path}/commits?sha={branch_name}&stat=false&verification=false&files=false&token={token}&page={page}&limit={limit}" - )); + "https://{hostname}/api/v1/repos/{path}/commits?sha={branch_name}&{options}&token={token}&page={page}&limit={limit}" + )); info!(%url, "Fetching commits"); let request = network::NetRequest::new( @@ -109,7 +112,7 @@ pub async fn get_commit_status( ) -> Status { let hostname = &repo_details.forge.hostname; let path = &repo_details.repo; - let token = &repo_details.forge.token; + let token = repo_details.forge.token.expose_secret(); let url = network::NetUrl::new(format!( "https://{hostname}/api/v1/repos/{path}/commits/{next}/status?token={token}" ));