Skip to content

Commit 3be7579

Browse files
committed
Store username in credentials file
1 parent a629a6d commit 3be7579

File tree

3 files changed

+110
-23
lines changed

3 files changed

+110
-23
lines changed

compiler-cli/src/hex.rs

+27-7
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ pub(crate) fn authenticate() -> Result<()> {
9999
let previous = auth.read_stored_api_key()?;
100100

101101
if previous.is_some() {
102-
let question = "You already have a local Hex API token. Would you like to replace it
102+
let question = "You already have a local Hex API key. Would you like to replace it
103103
with a new one?";
104104
if !cli::confirm(question)? {
105105
return Ok(());
@@ -109,13 +109,33 @@ with a new one?";
109109
let new_key = auth.create_and_store_api_key()?;
110110

111111
if let Some(previous) = previous {
112+
if previous.username != new_key.username {
113+
if let Some(previous_username) = previous.username {
114+
println!(
115+
"
116+
Your previous Hex API key was created with username `{}` which is different from the username
117+
used to create the new Hex API key. You have to delete the key `{}` manually at https://hex.pm",
118+
previous_username, previous.name
119+
);
120+
return Ok(());
121+
}
122+
}
123+
112124
println!("Deleting previous key `{}` from Hex", previous.name);
113-
runtime.block_on(hex::remove_api_key(
114-
&previous.name,
115-
&config,
116-
&new_key.unencrypted,
117-
&http,
118-
))?;
125+
if runtime
126+
.block_on(hex::remove_api_key(
127+
&previous.name,
128+
&config,
129+
&new_key.unencrypted,
130+
&http,
131+
))
132+
.is_err()
133+
{
134+
println!(
135+
"There was an error deleting key `{}` from Hex. You have to delete the key manually at https://hex.pm",
136+
previous.name
137+
);
138+
};
119139
}
120140
Ok(())
121141
}

compiler-cli/src/hex/auth.rs

+68-16
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
use crate::{cli, fs::ConsoleWarningEmitter, http::HttpClient};
2+
use camino::Utf8Path;
23
use gleam_core::{
34
Error, Result, Warning, encryption, hex, paths::global_hexpm_credentials_path,
45
warning::WarningEmitter,
56
};
7+
use serde::{Deserialize, Serialize};
68
use std::{rc::Rc, time::SystemTime};
79

810
pub const USER_PROMPT: &str = "https://hex.pm username";
@@ -12,15 +14,17 @@ pub const LOCAL_PASS_PROMPT: &str = "Local password";
1214
pub const PASS_ENV_NAME: &str = "HEXPM_PASS";
1315
pub const API_ENV_NAME: &str = "HEXPM_API_KEY";
1416

15-
#[derive(Debug)]
17+
#[derive(Debug, Serialize, Deserialize)]
1618
pub struct EncryptedApiKey {
1719
pub name: String,
1820
pub encrypted: String,
21+
pub username: Option<String>,
1922
}
2023

2124
#[derive(Debug)]
2225
pub struct UnencryptedApiKey {
2326
pub unencrypted: String,
27+
pub username: Option<String>,
2428
}
2529

2630
pub struct HexAuthentication<'runtime> {
@@ -74,11 +78,18 @@ encrypt your Hex API key.
7478
detail: e.to_string(),
7579
})?;
7680

77-
crate::fs::write(&path, &format!("{name}\n{encrypted}"))?;
81+
let encrypted = EncryptedApiKey {
82+
name,
83+
encrypted,
84+
username: Some(username.clone()),
85+
};
86+
87+
encrypted.save(&path)?;
7888
println!("Encrypted Hex API key written to {path}");
7989

8090
Ok(UnencryptedApiKey {
8191
unencrypted: api_key,
92+
username: Some(username),
8293
})
8394
}
8495

@@ -117,7 +128,12 @@ encrypt your Hex API key.
117128
}
118129

119130
fn read_and_decrypt_stored_api_key(&mut self) -> Result<Option<UnencryptedApiKey>> {
120-
let Some(EncryptedApiKey { encrypted, .. }) = self.read_stored_api_key()? else {
131+
let Some(EncryptedApiKey {
132+
encrypted,
133+
username,
134+
..
135+
}) = self.read_stored_api_key()?
136+
else {
121137
return Ok(None);
122138
};
123139

@@ -127,26 +143,43 @@ encrypt your Hex API key.
127143
detail: e.to_string(),
128144
})?;
129145

130-
Ok(Some(UnencryptedApiKey { unencrypted }))
146+
Ok(Some(UnencryptedApiKey {
147+
unencrypted,
148+
username,
149+
}))
131150
}
132151

133152
pub fn read_stored_api_key(&self) -> Result<Option<EncryptedApiKey>> {
134153
let path = global_hexpm_credentials_path();
135154
if !path.exists() {
136155
return Ok(None);
137156
}
138-
let text = crate::fs::read(&path)?;
139-
let mut chunks = text.splitn(2, '\n');
140-
let Some(name) = chunks.next() else {
141-
return Ok(None);
142-
};
143-
let Some(encrypted) = chunks.next() else {
144-
return Ok(None);
145-
};
146-
Ok(Some(EncryptedApiKey {
147-
name: name.to_string(),
148-
encrypted: encrypted.to_string(),
149-
}))
157+
158+
let encrypted_key = EncryptedApiKey::load(&path);
159+
160+
if let Ok(encrypted_key) = encrypted_key {
161+
Ok(Some(encrypted_key))
162+
} else {
163+
// fallback from old format
164+
let text = crate::fs::read(&path)?;
165+
let mut chunks = text.splitn(2, '\n');
166+
let Some(name) = chunks.next() else {
167+
return Ok(None);
168+
};
169+
let Some(encrypted) = chunks.next() else {
170+
return Ok(None);
171+
};
172+
173+
let key = EncryptedApiKey {
174+
name: name.to_string(),
175+
encrypted: encrypted.to_string(),
176+
username: None,
177+
};
178+
179+
key.save(&path)?;
180+
181+
Ok(Some(key))
182+
}
150183
}
151184
}
152185

@@ -158,6 +191,25 @@ impl Drop for HexAuthentication<'_> {
158191
}
159192
}
160193

194+
impl EncryptedApiKey {
195+
pub fn save(&self, path: &Utf8Path) -> Result<()> {
196+
let text = toml::to_string(self).map_err(|_| Error::InvalidCredentialsFile {
197+
path: path.to_string(),
198+
})?;
199+
crate::fs::write(path, &text)?;
200+
Ok(())
201+
}
202+
203+
pub fn load(path: &Utf8Path) -> Result<Self> {
204+
let text = crate::fs::read(path)?;
205+
let key = toml::from_str(&text).map_err(|_| Error::InvalidCredentialsFile {
206+
path: path.to_string(),
207+
})?;
208+
209+
Ok(key)
210+
}
211+
}
212+
161213
fn ask_local_password(warnings: &mut Vec<Warning>) -> std::result::Result<String, Error> {
162214
std::env::var(PASS_ENV_NAME)
163215
.inspect(|_| {

compiler-core/src/error.rs

+15
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,9 @@ file_names.iter().map(|x| x.as_str()).join(", "))]
321321

322322
#[error("Failed to decrypt local Hex API key")]
323323
FailedToDecryptLocalHexApiKey { detail: String },
324+
325+
#[error("Invalid Credentials file")]
326+
InvalidCredentialsFile { path: String },
324327
}
325328

326329
/// This is to make clippy happy and not make the error variant too big by
@@ -1487,6 +1490,18 @@ The error from the encryption library was:
14871490
}]
14881491
}
14891492

1493+
Error::InvalidCredentialsFile {path}=> {
1494+
let text = wrap_format!("Your credentials file at {path} is in the wrong format. Try deleting the file and authenticate again.");
1495+
1496+
vec![Diagnostic {
1497+
title: "Invalid credentials file".into(),
1498+
text,
1499+
level: Level::Error,
1500+
location: None,
1501+
hint: None
1502+
}]
1503+
}
1504+
14901505
Error::NonUtf8Path { path } => {
14911506
let text = format!(
14921507
"Encountered a non UTF-8 path '{}', but only UTF-8 paths are supported.",

0 commit comments

Comments
 (0)