feat: wrap API Token in a secrect::Secret and avoid logging

Closes kemitix/git-next#30
This commit is contained in:
Paul Campbell 2024-04-12 15:27:03 +01:00
parent e8d174ee84
commit cedaf16acf
5 changed files with 40 additions and 17 deletions

View file

@ -30,6 +30,9 @@ tempfile = "3.10"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
toml = "0.8" toml = "0.8"
# Secrets and Password
secrecy = "0.8"
# error handling # error handling
terrors = "0.3" terrors = "0.3"

View file

@ -3,6 +3,7 @@ use std::fmt::Display;
use actix::prelude::*; use actix::prelude::*;
use kxio::network; use kxio::network;
use secrecy::ExposeSecret;
use tracing::{error, info, warn}; use tracing::{error, info, warn};
use crate::server::{ use crate::server::{
@ -179,7 +180,7 @@ pub async fn advance_main(
}; };
} }
#[derive(Clone)] #[derive(Clone, Debug)]
pub struct GitRef(pub String); pub struct GitRef(pub String);
impl From<forge::Commit> for GitRef { impl From<forge::Commit> for GitRef {
fn from(value: forge::Commit) -> Self { fn from(value: forge::Commit) -> Self {
@ -197,10 +198,16 @@ impl Display for GitRef {
} }
} }
#[derive(Debug)]
pub enum ResetForce { pub enum ResetForce {
None, None,
Force(GitRef), Force(GitRef),
} }
impl Display for ResetForce {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
pub fn reset( pub fn reset(
branch: &BranchName, branch: &BranchName,
gitref: impl Into<GitRef>, gitref: impl Into<GitRef>,
@ -209,16 +216,19 @@ pub fn reset(
) -> Result<(), std::io::Error> { ) -> Result<(), std::io::Error> {
let gitref: GitRef = gitref.into(); let gitref: GitRef = gitref.into();
let user = &repo_details.forge.user; let user = &repo_details.forge.user;
let token = &repo_details.forge.token;
let hostname = &repo_details.forge.hostname; let hostname = &repo_details.forge.hostname;
let path = &repo_details.repo; let path = &repo_details.repo;
let token = &repo_details.forge.token.expose_secret();
let origin = format!("https://{user}:{token}@{hostname}/{path}.git"); let origin = format!("https://{user}:{token}@{hostname}/{path}.git");
let force = match reset_force { let force = match reset_force {
ResetForce::None => "".to_string(), ResetForce::None => "".to_string(),
ResetForce::Force(old_ref) => format!("--force-with-lease={branch}:{old_ref}"), 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}"); 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) match gix::command::prepare(command)
.with_shell_allow_argument_splitting() .with_shell_allow_argument_splitting()
.stdout(std::process::Stdio::null()) .stdout(std::process::Stdio::null())

View file

@ -3,6 +3,7 @@ use std::{
fmt::{Display, Formatter}, fmt::{Display, Formatter},
}; };
use secrecy::ExposeSecret;
use serde::Deserialize; use serde::Deserialize;
use terrors::OneOf; use terrors::OneOf;
@ -94,7 +95,7 @@ impl Forge {
} }
pub fn token(&self) -> ApiToken { pub fn token(&self) -> ApiToken {
ApiToken(self.token.clone()) ApiToken(self.token.clone().into())
} }
pub fn repos(&self) -> impl Iterator<Item = (RepoName, &Repo)> { pub fn repos(&self) -> impl Iterator<Item = (RepoName, &Repo)> {
@ -157,14 +158,19 @@ impl Display for User {
write!(f, "{}", self.0) write!(f, "{}", self.0)
} }
} }
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug)]
pub struct ApiToken(pub String); pub struct ApiToken(pub secrecy::Secret<String>);
impl Display for ApiToken { impl From<String> for ApiToken {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn from(value: String) -> Self {
write!(f, "{}", self.0) Self(value.into())
} }
} }
#[derive(Clone, Debug, PartialEq, Eq)] impl ExposeSecret<String> for ApiToken {
fn expose_secret(&self) -> &String {
self.0.expose_secret()
}
}
#[derive(Clone, Debug)]
pub struct ForgeDetails { pub struct ForgeDetails {
pub name: ForgeName, pub name: ForgeName,
pub forge_type: ForgeType, pub forge_type: ForgeType,
@ -206,7 +212,7 @@ impl Display for BranchName {
write!(f, "{}", self.0) write!(f, "{}", self.0)
} }
} }
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug)]
pub struct RepoDetails { pub struct RepoDetails {
pub name: RepoName, pub name: RepoName,
pub repo: RepoPath, pub repo: RepoPath,

View file

@ -1,5 +1,6 @@
use base64::Engine; use base64::Engine;
use kxio::network::{self, Network}; use kxio::network::{self, Network};
use secrecy::ExposeSecret;
use terrors::OneOf; use terrors::OneOf;
use tracing::{error, info}; use tracing::{error, info};
@ -33,7 +34,7 @@ pub async fn load(
let path = &details.repo; let path = &details.repo;
let filepath = ".git-next.toml"; let filepath = ".git-next.toml";
let branch = &details.branch; let branch = &details.branch;
let token = &details.forge.token; let token = details.forge.token.expose_secret();
let url = network::NetUrl::new(format!( let url = network::NetUrl::new(format!(
"https://{hostname}/api/v1/repos/{path}/contents/{filepath}?ref={branch}&token={token}" "https://{hostname}/api/v1/repos/{path}/contents/{filepath}?ref={branch}&token={token}"
)); ));
@ -127,7 +128,7 @@ pub async fn validate(
) -> Result<RepoConfig, OneOf<RepoConfigValidateErrors>> { ) -> Result<RepoConfig, OneOf<RepoConfigValidateErrors>> {
let hostname = &details.forge.hostname; let hostname = &details.forge.hostname;
let path = &details.repo; let path = &details.repo;
let token = &details.forge.token; let token = details.forge.token.expose_secret();
let url = network::NetUrl::new(format!( let url = network::NetUrl::new(format!(
"https://{hostname}/api/v1/repos/{path}/branches?token={token}" "https://{hostname}/api/v1/repos/{path}/branches?token={token}"
)); ));

View file

@ -1,4 +1,5 @@
use kxio::network; use kxio::network;
use secrecy::ExposeSecret;
use tracing::{error, info, warn}; use tracing::{error, info, warn};
use crate::server; use crate::server;
@ -49,17 +50,19 @@ async fn get_commit_history(
) -> Result<Vec<forge::Commit>, network::NetworkError> { ) -> Result<Vec<forge::Commit>, network::NetworkError> {
let hostname = &repo_details.forge.hostname; let hostname = &repo_details.forge.hostname;
let path = &repo_details.repo; let path = &repo_details.repo;
let token = &repo_details.forge.token;
let mut page = 1; let mut page = 1;
let limit = match find_commits.is_empty() { let limit = match find_commits.is_empty() {
true => 1, true => 1,
false => 50, false => 50,
}; };
let options = "stat=false&verification=false&files=false";
let mut all_commits = Vec::new(); let mut all_commits = Vec::new();
loop { loop {
let token = repo_details.forge.token.expose_secret();
let url = network::NetUrl::new(format!( 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"); info!(%url, "Fetching commits");
let request = network::NetRequest::new( let request = network::NetRequest::new(
@ -109,7 +112,7 @@ pub async fn get_commit_status(
) -> Status { ) -> Status {
let hostname = &repo_details.forge.hostname; let hostname = &repo_details.forge.hostname;
let path = &repo_details.repo; 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!( let url = network::NetUrl::new(format!(
"https://{hostname}/api/v1/repos/{path}/commits/{next}/status?token={token}" "https://{hostname}/api/v1/repos/{path}/commits/{next}/status?token={token}"
)); ));