Skip to content

Commit d020d1b

Browse files
committed
edgee: credentials: handle profiles
1 parent e284da3 commit d020d1b

File tree

5 files changed

+146
-40
lines changed

5 files changed

+146
-40
lines changed

crates/api-client/src/auth.rs

+55-13
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,29 @@ use anyhow::{Context, Result};
44
use serde::{Deserialize, Serialize};
55

66
use crate::{
7-
connect_builder::{IsUnset, SetApiToken, State},
7+
connect_builder::{IsUnset, SetApiToken, SetBaseurl, State},
88
ConnectBuilder,
99
};
1010

11-
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
11+
#[derive(Debug, Deserialize, Default, Serialize, Clone)]
12+
pub struct Config {
13+
#[serde(default)]
14+
api_token: Option<String>,
15+
#[serde(default)]
16+
url: Option<String>,
17+
18+
#[serde(flatten)]
19+
profiles: std::collections::HashMap<String, Credentials>,
20+
}
21+
22+
#[derive(Debug, Deserialize, Default, Serialize, Clone)]
1223
pub struct Credentials {
13-
pub api_token: Option<String>,
24+
pub api_token: String,
25+
#[serde(default)]
26+
pub url: Option<String>,
1427
}
1528

16-
impl Credentials {
29+
impl Config {
1730
pub fn path() -> Result<PathBuf> {
1831
let config_dir = dirs::config_dir()
1932
.ok_or_else(|| anyhow::anyhow!("Could not get user config directory"))?
@@ -66,23 +79,52 @@ impl Credentials {
6679
.context("Could not write credentials data")
6780
}
6881

69-
pub fn check_api_token(&self) -> Result<()> {
70-
let Some(_api_token) = self.api_token.as_deref() else {
71-
anyhow::bail!("Not logged in");
72-
};
82+
pub fn get(&self, profile: &Option<String>) -> Option<Credentials> {
83+
match profile {
84+
Some(profile) => self.profiles.get(profile).cloned(),
85+
None => match (self.api_token.clone(), self.url.clone()) {
86+
(Some(api_token), Some(url)) => Some(Credentials {
87+
api_token,
88+
url: Some(url),
89+
}),
90+
(Some(api_token), _) => Some(Credentials {
91+
api_token,
92+
url: Some("https://api.edgee.app".to_string()),
93+
}),
94+
_ => None,
95+
},
96+
}
97+
}
7398

74-
// TODO: Check API token is valid using the API
99+
pub fn set(&mut self, profile: Option<String>, creds: Credentials) {
100+
match profile {
101+
Some(profile) => {
102+
self.profiles.insert(profile, creds);
103+
}
104+
None => {
105+
self.api_token = Some(creds.api_token);
106+
self.url = creds.url;
107+
}
108+
}
109+
}
110+
}
75111

112+
impl Credentials {
113+
pub fn check_api_token(&self) -> Result<()> {
114+
// TODO: Check API token is valid using the API
76115
Ok(())
77116
}
78117
}
79118

80-
impl<'a, S: State> ConnectBuilder<'a, S> {
81-
pub fn credentials(self, creds: &Credentials) -> ConnectBuilder<'a, SetApiToken<S>>
119+
impl<S: State> ConnectBuilder<S> {
120+
pub fn credentials(self, creds: &Credentials) -> ConnectBuilder<SetApiToken<SetBaseurl<S>>>
82121
where
83122
S::ApiToken: IsUnset,
123+
S::Baseurl: IsUnset,
84124
{
85-
let api_token = creds.api_token.as_deref().unwrap();
86-
self.api_token(api_token)
125+
let api_token = creds.api_token.clone();
126+
let url = creds.url.clone();
127+
self.baseurl(url.unwrap_or("https://api.edgee.app".to_string()))
128+
.api_token(api_token)
87129
}
88130
}

crates/api-client/src/lib.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@ progenitor::generate_api! {
1818
finish_fn = connect,
1919
on(String, into),
2020
)]
21-
pub fn connect(#[builder(default = PROD_BASEURL)] baseurl: &str, api_token: String) -> Client {
21+
pub fn connect(#[builder(default = PROD_BASEURL)] baseurl: String, api_token: String) -> Client {
2222
use reqwest::header::{self, HeaderMap};
2323

2424
let mut default_headers = HeaderMap::new();
2525

2626
// if EDGEE_API_URL env var is set, redefine baseurl
27-
let baseurl = std::env::var("EDGEE_API_URL").unwrap_or(baseurl.to_string());
27+
let baseurl = baseurl.to_string();
2828

2929
// if EDGEE_API_TOKEN env var is set, redefine api_token
3030
let api_token = std::env::var("EDGEE_API_TOKEN").unwrap_or(api_token.to_string());

crates/cli/src/commands/auth/login.rs

+42-19
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,62 @@ use anyhow::Result;
22

33
use edgee_api_client::ResultExt;
44

5-
setup_command! {}
5+
setup_command! {
6+
#[arg(short, long, id = "PROFILE")]
7+
profile: Option<String>,
68

7-
pub async fn run(_opts: Options) -> Result<()> {
9+
#[arg(short, long, id = "URL")]
10+
url: Option<String>,
11+
}
12+
13+
pub async fn run(opts: Options) -> Result<()> {
814
use inquire::{Confirm, Password, PasswordDisplayMode};
915

10-
use edgee_api_client::auth::Credentials;
16+
use edgee_api_client::auth::{Config, Credentials};
17+
18+
let url = match opts.url {
19+
Some(url) => url,
20+
None => std::env::var("EDGEE_API_URL").unwrap_or("https://api.edgee.app".to_string()),
21+
};
1122

12-
let mut creds = Credentials::load()?;
23+
let mut config = Config::load()?;
24+
let creds = config.get(&opts.profile);
1325

1426
let confirm_overwrite =
1527
Confirm::new("An API token is already configured, do you want to overwrite it?")
1628
.with_default(false);
17-
if creds.api_token.is_some() && !confirm_overwrite.prompt()? {
29+
if creds.is_some() && !confirm_overwrite.prompt()? {
1830
return Ok(());
1931
}
2032

21-
let confirm_auto_open_browser = Confirm::new("Your default browser will be opening to Edgee's API token creation page. Do you want to continue?")
33+
let api_token = match std::env::var("EDGEE_API_TOKEN") {
34+
Ok(token) => token,
35+
Err(_) => {
36+
let confirm_auto_open_browser = Confirm::new("Your default browser will be opening to Edgee's API token creation page. Do you want to continue?")
2237
.with_default(true);
2338

24-
if confirm_auto_open_browser.prompt()? {
25-
open::that("https://www.edgee.cloud/~/me/settings/tokens")?;
26-
}
39+
if confirm_auto_open_browser.prompt()? {
40+
open::that("https://www.edgee.cloud/~/me/settings/tokens")?;
41+
}
2742

28-
let api_token = Password::new("Enter Edgee API token (press Ctrl+R to toggle input display):")
29-
.with_help_message("You can create one at https://www.edgee.cloud/~/me/settings/tokens")
30-
.with_display_mode(PasswordDisplayMode::Masked)
31-
.with_display_toggle_enabled()
32-
.without_confirmation()
33-
.with_validator(inquire::required!("API token cannot be empty"))
34-
.prompt()?;
35-
creds.api_token.replace(api_token);
43+
Password::new("Enter Edgee API token (press Ctrl+R to toggle input display):")
44+
.with_help_message(
45+
"You can create one at https://www.edgee.cloud/~/me/settings/tokens",
46+
)
47+
.with_display_mode(PasswordDisplayMode::Masked)
48+
.with_display_toggle_enabled()
49+
.without_confirmation()
50+
.with_validator(inquire::required!("API token cannot be empty"))
51+
.prompt()?
52+
}
53+
};
3654

37-
let client = edgee_api_client::new().credentials(&creds).connect();
55+
let creds = Credentials {
56+
api_token,
57+
url: Some(url),
58+
};
3859

60+
let client = edgee_api_client::new().credentials(&creds).connect();
3961
let user = client
4062
.get_me()
4163
.send()
@@ -44,5 +66,6 @@ pub async fn run(_opts: Options) -> Result<()> {
4466
.into_inner();
4567
println!("Logged as {} ({})", user.name, user.email);
4668

47-
creds.save()
69+
config.set(opts.profile, creds);
70+
config.save()
4871
}

crates/cli/src/commands/auth/whoami.rs

+28-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,29 @@
11
use edgee_api_client::ResultExt;
22

3-
setup_command! {}
3+
setup_command! {
4+
#[arg(short, long, id = "PROFILE")]
5+
profile: Option<String>,
6+
}
7+
8+
pub async fn run(opts: Options) -> anyhow::Result<()> {
9+
use edgee_api_client::auth::Config;
10+
11+
let config = Config::load()?;
412

5-
pub async fn run(_opts: Options) -> anyhow::Result<()> {
6-
use edgee_api_client::auth::Credentials;
13+
let creds = match config.get(&opts.profile) {
14+
Some(creds) => creds,
15+
None => {
16+
match opts.profile {
17+
None => {
18+
anyhow::bail!("No API token configured");
19+
}
20+
Some(profile) => {
21+
anyhow::bail!("No API token configured for profile '{}'", profile);
22+
}
23+
};
24+
}
25+
};
726

8-
let creds = Credentials::load()?;
927
creds.check_api_token()?;
1028

1129
let client = edgee_api_client::new().credentials(&creds).connect();
@@ -19,6 +37,12 @@ pub async fn run(_opts: Options) -> anyhow::Result<()> {
1937
println!(" ID: {}", user.id);
2038
println!(" Name: {}", user.name);
2139
println!(" Email: {}", user.email);
40+
if let Some(url) = &creds.url {
41+
println!(" Url: {}", url);
42+
}
43+
if let Some(profile) = opts.profile {
44+
println!(" Profile: {}", profile);
45+
}
2246

2347
Ok(())
2448
}

crates/cli/src/commands/components/push.rs

+19-2
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,33 @@ pub struct Options {
1212
///
1313
/// Defaults to the user "self" org
1414
pub organization: Option<String>,
15+
16+
#[arg(short, long, id = "PROFILE")]
17+
profile: Option<String>,
1518
}
1619

1720
pub async fn run(opts: Options) -> anyhow::Result<()> {
1821
use inquire::{Confirm, Editor, Select};
1922

20-
use edgee_api_client::{auth::Credentials, ErrorExt, ResultExt};
23+
use edgee_api_client::{auth::Config, ErrorExt, ResultExt};
2124

2225
use crate::components::manifest;
2326

24-
let creds = Credentials::load()?;
27+
let config = Config::load()?;
28+
29+
let creds = match config.get(&opts.profile) {
30+
Some(creds) => creds,
31+
None => {
32+
match opts.profile {
33+
None => {
34+
anyhow::bail!("No API token configured");
35+
}
36+
Some(profile) => {
37+
anyhow::bail!("No API token configured for profile '{}'", profile);
38+
}
39+
};
40+
}
41+
};
2542
creds.check_api_token()?;
2643

2744
let Some(manifest_path) = manifest::find_manifest_path() else {

0 commit comments

Comments
 (0)