Skip to content

Commit

Permalink
🌳 Support reading token from environment variable (#82)
Browse files Browse the repository at this point in the history
* 🌳 Read token from TOGGL_API_TOKEN environment variable

  - Environment store overrides keyring store, write and delete operations
are disabled for the keyring store.

  - This should allow linux users to authenticate and persist their credentials
in their shell configuration, until we figure out the keyring crate's persistence issue.

* 🧢 Make clippy happy, drop `return` at the end of blocks

Closes #81
  • Loading branch information
shantanuraj authored Dec 10, 2024
1 parent 7e6c9bd commit a0ad0e4
Show file tree
Hide file tree
Showing 7 changed files with 50 additions and 16 deletions.
10 changes: 5 additions & 5 deletions src/commands/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ impl AuthenticationCommand {
pub async fn execute<W: Write>(
mut writer: W,
api_client: impl ApiClient,
credentials_storage: impl CredentialsStorage,
credentials_storage: Box<dyn CredentialsStorage>,
) -> ResultWithDefaultError<()> {
let user = api_client.get_user().await?;
credentials_storage.persist(user.api_token)?;
Expand Down Expand Up @@ -90,7 +90,7 @@ mod tests {
// Arrange
let mut output = Vec::new();
let api_client = create_working_api_client();
let credentials_storage = create_working_credentials_storage();
let credentials_storage = Box::new(create_working_credentials_storage());

// Act
let result =
Expand All @@ -105,7 +105,7 @@ mod tests {
// Arrange
let mut output = Vec::new();
let api_client = create_working_api_client();
let credentials_storage = create_working_credentials_storage();
let credentials_storage = Box::new(create_working_credentials_storage());

// Act
let _ = AuthenticationCommand::execute(&mut output, api_client, credentials_storage).await;
Expand All @@ -127,7 +127,7 @@ mod tests {
// Arrange
let mut output = Vec::new();
let api_client = create_failing_api_client();
let credentials_storage = create_working_credentials_storage();
let credentials_storage = Box::new(create_working_credentials_storage());

// Act
let result =
Expand All @@ -142,7 +142,7 @@ mod tests {
// Arrange
let mut output = Vec::new();
let api_client = create_working_api_client();
let credentials_storage = create_failing_credentials_storage();
let credentials_storage = Box::new(create_failing_credentials_storage());

// Act
let result =
Expand Down
2 changes: 1 addition & 1 deletion src/commands/cont.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,5 +75,5 @@ fn get_first_stopped_time_entry(
None => 0,
Some(_) => 1,
};
return time_entries.get(continue_entry_index).cloned();
time_entries.get(continue_entry_index).cloned()
}
2 changes: 1 addition & 1 deletion src/config/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -523,7 +523,7 @@ impl TrackConfig {
}
pub fn get_active_config(&self) -> ResultWithDefaultError<&BranchConfig> {
let current_dir = std::env::current_dir().expect("Failed to get current directory");
return Ok(self.get_branch_config_for_dir(&current_dir));
Ok(self.get_branch_config_for_dir(&current_dir))
}
pub fn get_default_entry(&self, entities: Entities) -> ResultWithDefaultError<TimeEntry> {
let config = self.get_active_config()?;
Expand Down
4 changes: 3 additions & 1 deletion src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ pub const CREDENTIALS_WRITE_ERROR: &str =
pub const CREDENTIALS_DELETE_ERROR: &str =
"An unknown error occurred while deleting your credentials.";
pub const CREDENTIALS_EMPTY_ERROR: &str =
"Please set your API token first by calling toggl auth <API_TOKEN>.";
"Please set your API token first by calling toggl auth <API_TOKEN>. Or export it as TOGGL_API_TOKEN.";
pub const CREDENTIALS_FIND_TOKEN_MESSAGE: &str = "You can find your API token at";
pub const CREDENTIALS_FIND_TOKEN_LINK: &str = "https://track.toggl.com/profile";
pub const CREDENTIALS_OVERRIDE_ERROR: &str =
"You have set TOGGL_API_TOKEN in your environment. Unset it to update or delete your saved credentials.";

pub const FZF_NOT_INSTALLED_ERROR: &str = "fzf could not be found. Is it installed?";
pub const OPERATION_CANCELLED: &str = "Operation cancelled";
Expand Down
34 changes: 34 additions & 0 deletions src/credentials.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,37 @@ impl CredentialsStorage for KeyringStorage {
}
}
}

pub struct EnvironmentStorage {
token: String,
}

impl EnvironmentStorage {
pub fn new(token: String) -> EnvironmentStorage {
Self { token }
}
}

impl CredentialsStorage for EnvironmentStorage {
fn read(&self) -> ResultWithDefaultError<Credentials> {
Ok(Credentials {
api_token: self.token.clone(),
})
}
fn persist(&self, _api_token: String) -> ResultWithDefaultError<()> {
Err(Box::new(StorageError::EnvironmentOverride))
}
fn clear(&self) -> ResultWithDefaultError<()> {
Err(Box::new(StorageError::EnvironmentOverride))
}
}

pub fn get_storage() -> Box<dyn CredentialsStorage> {
if let Ok(api_token) = std::env::var("TOGGL_API_TOKEN") {
return Box::new(EnvironmentStorage::new(api_token));
}

let keyring = Entry::new("togglcli", "default")
.unwrap_or_else(|err| panic!("Couldn't create credentials_storage: {err}"));
Box::new(KeyringStorage::new(keyring))
}
4 changes: 4 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ pub enum StorageError {
Write,
Delete,
Unknown,
EnvironmentOverride,
}

impl Display for StorageError {
Expand Down Expand Up @@ -62,6 +63,9 @@ impl Display for StorageError {
constants::ISSUE_LINK.blue().bold().underline()
)
}
StorageError::EnvironmentOverride => {
format!("{}", constants::CREDENTIALS_OVERRIDE_ERROR.red())
}
};

writeln!(f, "{}", summary)
Expand Down
10 changes: 2 additions & 8 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ use commands::list::ListCommand;
use commands::running::RunningTimeEntryCommand;
use commands::start::StartCommand;
use commands::stop::{StopCommand, StopCommandOrigin};
use credentials::{Credentials, CredentialsStorage, KeyringStorage};
use keyring::Entry;
use credentials::get_storage;
use credentials::Credentials;
use models::ResultWithDefaultError;
use std::io;
use structopt::StructOpt;
Expand Down Expand Up @@ -141,9 +141,3 @@ fn get_api_client(proxy: Option<String>) -> ResultWithDefaultError<impl ApiClien
Err(err) => Err(err),
}
}

fn get_storage() -> impl CredentialsStorage {
let keyring = Entry::new("togglcli", "default")
.unwrap_or_else(|err| panic!("Couldn't create credentials_storage: {err}"));
KeyringStorage::new(keyring)
}

0 comments on commit a0ad0e4

Please sign in to comment.