diff --git a/client/src/api/mod.rs b/client/src/api/mod.rs index 399627b..a369a73 100644 --- a/client/src/api/mod.rs +++ b/client/src/api/mod.rs @@ -62,7 +62,7 @@ pub struct StructuredApiError { impl ApiClient { pub fn from_server_config(config: ServerConfig) -> Result { - let client = build_http_client(config.token.as_deref()); + let client = build_http_client(config.token()?.as_deref()); Ok(Self { endpoint: Url::parse(&config.endpoint)?, diff --git a/client/src/command/login.rs b/client/src/command/login.rs index 4cb9281..9abcea7 100644 --- a/client/src/command/login.rs +++ b/client/src/command/login.rs @@ -3,7 +3,7 @@ use clap::Parser; use crate::cache::ServerName; use crate::cli::Opts; -use crate::config::{Config, ServerConfig}; +use crate::config::{Config, ServerConfig, ServerTokenConfig}; /// Log into an Attic server. #[derive(Debug, Parser)] @@ -32,8 +32,10 @@ pub async fn run(opts: Opts) -> Result<()> { server.endpoint = sub.endpoint.to_owned(); - if sub.token.is_some() { - server.token = sub.token.to_owned(); + if let Some(token) = &sub.token { + server.token = Some(ServerTokenConfig::Raw { + token: token.clone(), + }); } } else { eprintln!("✍️ Configuring server \"{}\"", sub.name.as_str()); @@ -42,7 +44,10 @@ pub async fn run(opts: Opts) -> Result<()> { sub.name.to_owned(), ServerConfig { endpoint: sub.endpoint.to_owned(), - token: sub.token.to_owned(), + token: sub + .token + .to_owned() + .map(|token| ServerTokenConfig::Raw { token }), }, ); } diff --git a/client/src/command/use.rs b/client/src/command/use.rs index 1548072..37d8cd6 100644 --- a/client/src/command/use.rs +++ b/client/src/command/use.rs @@ -49,7 +49,7 @@ pub async fn run(opts: Opts) -> Result<()> { nix_config.add_trusted_public_key(&public_key); // Modify netrc - if let Some(token) = &server.token { + if let Some(token) = server.token()? { eprintln!("+ Access Token"); let mut nix_netrc = NixNetrc::load().await?; diff --git a/client/src/config.rs b/client/src/config.rs index 254c155..a208da9 100644 --- a/client/src/config.rs +++ b/client/src/config.rs @@ -5,13 +5,13 @@ //! experience (e.g., `attic login`). use std::collections::HashMap; -use std::fs::{self, OpenOptions, Permissions}; +use std::fs::{self, read_to_string, OpenOptions, Permissions}; use std::io::Write; use std::ops::{Deref, DerefMut}; use std::os::unix::fs::{OpenOptionsExt, PermissionsExt}; use std::path::PathBuf; -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, Context, Result}; use serde::{Deserialize, Serialize}; use xdg::BaseDirectories; @@ -52,7 +52,38 @@ pub struct ConfigData { #[derive(Debug, Clone, Deserialize, Serialize)] pub struct ServerConfig { pub endpoint: String, - pub token: Option, + #[serde(flatten)] + pub token: Option, +} + +impl ServerConfig { + pub fn token(&self) -> Result> { + self.token.as_ref().map(|token| token.get()).transpose() + } +} + +/// Configured server token +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(untagged)] +pub enum ServerTokenConfig { + Raw { + token: String, + }, + File { + #[serde(rename = "token-file")] + token_file: String, + }, +} + +impl ServerTokenConfig { + /// Get the token either directly from the config or through the token file + pub fn get(&self) -> Result { + match self { + ServerTokenConfig::Raw { token } => Ok(token.clone()), + ServerTokenConfig::File { token_file } => Ok(read_to_string(token_file) + .with_context(|| format!("Failed to read token from {token_file}"))?), + } + } } /// Wrapper that automatically saves the config once dropped.