feat: wrap API Token in a secrect::Secret and avoid logging
Closes kemitix/git-next#30
This commit is contained in:
parent
e8d174ee84
commit
cedaf16acf
5 changed files with 40 additions and 17 deletions
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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}"
|
||||||
));
|
));
|
||||||
|
|
|
@ -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,16 +50,18 @@ 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");
|
||||||
|
@ -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}"
|
||||||
));
|
));
|
||||||
|
|
Loading…
Reference in a new issue