Skip to content

Commit a553826

Browse files
committed
Save server specific login tokens in server config
1 parent d1ed964 commit a553826

File tree

4 files changed

+106
-21
lines changed

4 files changed

+106
-21
lines changed

crates/cli/src/config.rs

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ pub struct ServerConfig {
2424
pub host: String,
2525
pub protocol: String,
2626
pub ecdsa_public_key: Option<String>,
27+
pub spacetimedb_token: Option<String>,
2728
}
2829

2930
impl ServerConfig {
@@ -40,6 +41,7 @@ impl ServerConfig {
4041
set_table_opt_value(edit, HOST_KEY, Some(&from.host));
4142
set_table_opt_value(edit, PROTOCOL_KEY, Some(&from.protocol));
4243
set_table_opt_value(edit, ECDSA_PUBLIC_KEY, from.ecdsa_public_key.as_deref());
44+
set_table_opt_value(edit, SPACETIMEDB_TOKEN_KEY, from.spacetimedb_token.as_deref());
4345
}
4446

4547
fn nick_or_host(&self) -> &str {
@@ -105,11 +107,13 @@ impl TryFrom<&toml_edit::Table> for ServerConfig {
105107
let host = read_str(table, HOST_KEY)?;
106108
let protocol = read_str(table, PROTOCOL_KEY)?;
107109
let ecdsa_public_key = read_opt_str(table, ECDSA_PUBLIC_KEY)?;
110+
let spacetimedb_token = read_opt_str(table, SPACETIMEDB_TOKEN_KEY)?;
108111
Ok(ServerConfig {
109112
nickname,
110113
host,
111114
protocol,
112115
ecdsa_public_key,
116+
spacetimedb_token,
113117
})
114118
}
115119
}
@@ -161,12 +165,14 @@ impl RawConfig {
161165
protocol: "http".to_string(),
162166
nickname: Some("local".to_string()),
163167
ecdsa_public_key: None,
168+
spacetimedb_token: None,
164169
};
165170
let maincloud = ServerConfig {
166171
host: "maincloud.spacetimedb.com".to_string(),
167172
protocol: "https".to_string(),
168173
nickname: Some("maincloud".to_string()),
169174
ecdsa_public_key: None,
175+
spacetimedb_token: None,
170176
};
171177
RawConfig {
172178
default_server: local.nickname.clone(),
@@ -245,6 +251,7 @@ impl RawConfig {
245251
host,
246252
protocol,
247253
ecdsa_public_key,
254+
spacetimedb_token: None
248255
});
249256
Ok(())
250257
}
@@ -444,13 +451,57 @@ Fetch the server's fingerprint with:
444451
self.web_session_token = Some(token);
445452
}
446453

454+
455+
/// Return the spacetimedb_token for the server named by `server`.
456+
///
457+
/// Returns an `Err` if there is no server configuration.
458+
/// Returns `None` if the server configuration exists, but does not have a token saved.
459+
pub fn server_spacetimedb_token(&self, server: &str) -> anyhow::Result<Option<&str>> {
460+
self.find_server(server)
461+
.map(|cfg| cfg.spacetimedb_token.as_deref())
462+
.with_context(|| format!("Cannot find spacetimedb_token for unknown server: {server}"))
463+
}
464+
465+
/// Return the spacetimedb_token for the default server.
466+
///
467+
/// Returns an `Err` if there is no default server configuration.
468+
/// Returns `None` if the server configuration exists, but does not have a token saved.
469+
pub fn default_server_spacetimedb_token(&self) -> anyhow::Result<Option<&str>> {
470+
if let Some(server) = &self.default_server {
471+
self.server_spacetimedb_token(server)
472+
} else {
473+
Err(anyhow::anyhow!(NO_DEFAULT_SERVER_ERROR_MESSAGE))
474+
}
475+
}
476+
447477
pub fn set_spacetimedb_token(&mut self, token: String) {
448478
self.spacetimedb_token = Some(token);
449479
}
450480

481+
/// Store the spacetimedb_token for the server named by `server`.
482+
///
483+
/// Returns an `Err` if no such server configuration exists.
484+
pub fn set_server_spacetimedb_token(&mut self, server: &str, spacetimedb_token: String) -> anyhow::Result<()> {
485+
let cfg = self.find_server_mut(server)?;
486+
cfg.spacetimedb_token = Some(spacetimedb_token);
487+
Ok(())
488+
}
489+
490+
/// Store the spacetimedb_token for the default server.
491+
///
492+
/// Returns an `Err` if there is no default server configuration.
493+
pub fn set_default_server_spacetimedb_token(&mut self, spacetimedb_token: String) -> anyhow::Result<()> {
494+
let cfg = self.default_server_mut()?;
495+
cfg.spacetimedb_token = Some(spacetimedb_token);
496+
Ok(())
497+
}
498+
451499
pub fn clear_login_tokens(&mut self) {
452500
self.web_session_token = None;
453501
self.spacetimedb_token = None;
502+
for cfg in &mut self.server_configs {
503+
cfg.spacetimedb_token = None;
504+
}
454505
}
455506
}
456507

@@ -808,6 +859,14 @@ Update the server's fingerprint with:
808859
self.home.set_spacetimedb_token(token);
809860
}
810861

862+
pub fn set_server_spacetimedb_token(&mut self, server: Option<&str>, token: String) -> anyhow::Result<()> {
863+
if let Some(server) = server {
864+
self.home.set_server_spacetimedb_token(server, token)
865+
} else {
866+
self.home.set_default_server_spacetimedb_token(token)
867+
}
868+
}
869+
811870
pub fn clear_login_tokens(&mut self) {
812871
self.home.clear_login_tokens();
813872
}
@@ -816,8 +875,19 @@ Update the server's fingerprint with:
816875
self.home.web_session_token.as_ref()
817876
}
818877

819-
pub fn spacetimedb_token(&self) -> Option<&String> {
820-
self.home.spacetimedb_token.as_ref()
878+
pub fn spacetimedb_token(&self, server: Option<&str>, allow_fallback: bool) -> Option<String> {
879+
let token = if let Some(server) = server {
880+
self.home.server_spacetimedb_token(server)
881+
} else {
882+
self.home.default_server_spacetimedb_token()
883+
};
884+
if let Ok(Some(token)) = token {
885+
Some(token.to_string())
886+
} else if allow_fallback {
887+
self.home.spacetimedb_token.clone()
888+
} else {
889+
None
890+
}
821891
}
822892
}
823893

@@ -1001,12 +1071,14 @@ default_server = "local"
10011071
host: "127.0.0.1:3000".to_string(),
10021072
protocol: "http".to_string(),
10031073
ecdsa_public_key: None,
1074+
spacetimedb_token: None,
10041075
},
10051076
ServerConfig {
10061077
nickname: Some("testnet".to_string()),
10071078
host: "testnet.spacetimedb.com".to_string(),
10081079
protocol: "https".to_string(),
10091080
ecdsa_public_key: None,
1081+
spacetimedb_token: None,
10101082
},
10111083
];
10121084
config.home.spacetimedb_token =

crates/cli/src/subcommands/login.rs

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::util::decode_identity;
1+
use crate::{common_args, util::decode_identity};
22
use crate::Config;
33
use clap::{Arg, ArgAction, ArgGroup, ArgMatches, Command};
44
use reqwest::Url;
@@ -42,6 +42,9 @@ fn get_subcommands() -> Vec<Command> {
4242
.action(ArgAction::SetTrue)
4343
.help("Also show the auth token"),
4444
)
45+
.arg(
46+
common_args::server().help("The server used to log in to a SpacetimeDB server directly, without going through a global auth server")
47+
)
4548
.about("Show the current login info")]
4649
}
4750

@@ -53,7 +56,7 @@ pub async fn exec(mut config: Config, args: &ArgMatches) -> Result<(), anyhow::E
5356
let spacetimedb_token: Option<&String> = args.get_one("spacetimedb-token");
5457
let host: &String = args.get_one("auth-host").unwrap();
5558
let host = Url::parse(host)?;
56-
let server_issued_login: Option<&String> = args.get_one("server");
59+
let server_issued_login: Option<&str> = args.get_one::<String>("server").map(|s| s.as_ref());
5760

5861
if let Some(token) = spacetimedb_token {
5962
config.set_spacetimedb_token(token.clone());
@@ -63,9 +66,9 @@ pub async fn exec(mut config: Config, args: &ArgMatches) -> Result<(), anyhow::E
6366

6467
if let Some(server) = server_issued_login {
6568
let host = Url::parse(&config.get_host_url(Some(server))?)?;
66-
spacetimedb_token_cached(&mut config, &host, true).await?;
69+
spacetimedb_token_cached(&mut config, &host, server_issued_login, true).await?;
6770
} else {
68-
spacetimedb_token_cached(&mut config, &host, false).await?;
71+
spacetimedb_token_cached(&mut config, &host, None, false).await?;
6972
}
7073

7174
Ok(())
@@ -80,16 +83,21 @@ async fn exec_subcommand(config: Config, cmd: &str, args: &ArgMatches) -> Result
8083

8184
async fn exec_show(config: Config, args: &ArgMatches) -> Result<(), anyhow::Error> {
8285
let include_token = args.get_flag("token");
86+
let server_issued_login: Option<&str> = args.get_one::<String>("server").map(|s| s.as_ref());
8387

84-
let token = if let Some(token) = config.spacetimedb_token() {
88+
let token = if let Some(token) = config.spacetimedb_token(server_issued_login, false) {
8589
token
8690
} else {
8791
println!("You are not logged in. Run `spacetime login` to log in.");
8892
return Ok(());
8993
};
9094

91-
let identity = decode_identity(token)?;
92-
println!("You are logged in as {}", identity);
95+
let identity = decode_identity(&token)?;
96+
if let Some(server) = server_issued_login {
97+
println!("You are logged in to server \"{}\" as {}", server, identity);
98+
} else {
99+
println!("You are logged in as {}", identity);
100+
}
93101

94102
if include_token {
95103
println!("Your auth token (don't share this!) is {}", token);
@@ -98,19 +106,20 @@ async fn exec_show(config: Config, args: &ArgMatches) -> Result<(), anyhow::Erro
98106
Ok(())
99107
}
100108

101-
async fn spacetimedb_token_cached(config: &mut Config, host: &Url, direct_login: bool) -> anyhow::Result<String> {
109+
async fn spacetimedb_token_cached(config: &mut Config, host: &Url, server: Option<&str>, direct_login: bool) -> anyhow::Result<String> {
102110
// Currently, this token does not expire. However, it will at some point in the future. When that happens,
103111
// this code will need to happen before any request to a spacetimedb server, rather than at the end of the login flow here.
104-
if let Some(token) = config.spacetimedb_token() {
112+
// If we are logging in to a specific server, we should not accept the root token as verification that we are logged in.
113+
if let Some(token) = config.spacetimedb_token(server, !direct_login) {
105114
println!("You are already logged in.");
106115
println!("If you want to log out, use spacetime logout.");
107116
Ok(token.clone())
108117
} else {
109-
spacetimedb_login_force(config, host, direct_login).await
118+
spacetimedb_login_force(config, host, server, direct_login).await
110119
}
111120
}
112121

113-
pub async fn spacetimedb_login_force(config: &mut Config, host: &Url, direct_login: bool) -> anyhow::Result<String> {
122+
pub async fn spacetimedb_login_force(config: &mut Config, host: &Url, server: Option<&str>, direct_login: bool) -> anyhow::Result<String> {
114123
let token = if direct_login {
115124
let token = spacetimedb_direct_login(host).await?;
116125
println!("We have logged in directly to your target server.");
@@ -120,7 +129,11 @@ pub async fn spacetimedb_login_force(config: &mut Config, host: &Url, direct_log
120129
let session_token = web_login_cached(config, host).await?;
121130
spacetimedb_login(host, &session_token).await?
122131
};
123-
config.set_spacetimedb_token(token.clone());
132+
if direct_login {
133+
config.set_server_spacetimedb_token(server, token.clone())?;
134+
} else {
135+
config.set_spacetimedb_token(token.clone());
136+
}
124137
config.save();
125138

126139
Ok(token)

crates/cli/src/subcommands/publish.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,8 @@ pub async fn exec(mut config: Config, args: &ArgMatches) -> Result<(), anyhow::E
160160
let res = builder.body(program_bytes).send().await?;
161161
if res.status() == StatusCode::UNAUTHORIZED && !anon_identity {
162162
// If we're not in the `anon_identity` case, then we have already forced the user to log in above (using `get_auth_header`), so this should be safe to unwrap.
163-
let token = config.spacetimedb_token().unwrap();
164-
let identity = decode_identity(token)?;
163+
let token = config.spacetimedb_token(server, true).unwrap();
164+
let identity = decode_identity(&token)?;
165165
let err = res.text().await?;
166166
return unauth_error_context(
167167
Err(anyhow::anyhow!(err)),
@@ -192,8 +192,8 @@ pub async fn exec(mut config: Config, args: &ArgMatches) -> Result<(), anyhow::E
192192
anyhow::bail!("You need to be logged in as the owner of {name} to publish to {name}",);
193193
}
194194
// If we're not in the `anon_identity` case, then we have already forced the user to log in above (using `get_auth_header`), so this should be safe to unwrap.
195-
let token = config.spacetimedb_token().unwrap();
196-
let identity = decode_identity(token)?;
195+
let token = config.spacetimedb_token(server, true).unwrap();
196+
let identity = decode_identity(&token)?;
197197
//TODO(jdetter): Have a nice name generator here, instead of using some abstract characters
198198
// we should perhaps generate fun names like 'green-fire-dragon' instead
199199
let suggested_tld: String = identity.chars().take(12).collect();

crates/cli/src/util.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ pub async fn get_login_token_or_log_in(
299299
target_server: Option<&str>,
300300
interactive: bool,
301301
) -> anyhow::Result<String> {
302-
if let Some(token) = config.spacetimedb_token() {
302+
if let Some(token) = config.spacetimedb_token(target_server, true) {
303303
return Ok(token.clone());
304304
}
305305

@@ -314,10 +314,10 @@ pub async fn get_login_token_or_log_in(
314314

315315
if full_login {
316316
let host = Url::parse(DEFAULT_AUTH_HOST)?;
317-
spacetimedb_login_force(config, &host, false).await
317+
spacetimedb_login_force(config, &host, None, false).await
318318
} else {
319319
let host = Url::parse(&config.get_host_url(target_server)?)?;
320-
spacetimedb_login_force(config, &host, true).await
320+
spacetimedb_login_force(config, &host, target_server, true).await
321321
}
322322
}
323323

0 commit comments

Comments
 (0)