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"] }
toml = "0.8"
# Secrets and Password
secrecy = "0.8"
# error handling
terrors = "0.3"

View file

@ -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<forge::Commit> 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<GitRef>,
@ -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())

View file

@ -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<Item = (RepoName, &Repo)> {
@ -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<String>);
impl From<String> for ApiToken {
fn from(value: String) -> Self {
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 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,

View file

@ -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<RepoConfig, OneOf<RepoConfigValidateErrors>> {
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}"
));

View file

@ -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<Vec<forge::Commit>, 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}"
));