7 files changed
+151
-101
crates/cli/src/account/lib.rs
+18
-14
@@ -224,25 +224,29 @@ pub async fn save_token(env: Environment, response: &Response) -> Result<()> {
224
match headers.get("Authorization") {
225
Some(token) => {
226
debug!("{}", token.to_str()?);
227
- match home::home_dir() {
228
- Some(path) => {
229
- debug!("{}", path.to_str().unwrap());
230
- create_dir_all(path.join(env.smb_dir()))?;
231
- let mut file = OpenOptions::new()
232
- .create(true)
233
- .truncate(true)
234
- .write(true)
235
- .open([path.to_str().unwrap(), "/", &env.smb_dir(), "/token"].join(""))?;
236
- file.write_all(token.to_str()?.as_bytes())?;
237
- Ok(())
238
- }
239
- None => Err(anyhow!("Failed to get home directory.")),
240
- }
227
+ store_token(env, token.to_str()?.to_string()).await
228
}
229
None => Err(anyhow!("Failed to get token. Probably a backend issue.")),
230
}
231
}
232
233
+pub async fn store_token(env: Environment, token: String) -> Result<()> {
234
+ match home::home_dir() {
235
+ Some(path) => {
236
+ debug!("{}", path.to_str().unwrap());
237
+ create_dir_all(path.join(env.smb_dir()))?;
238
+ let mut file = OpenOptions::new()
239
+ .create(true)
240
+ .truncate(true)
241
+ .write(true)
242
+ .open([path.to_str().unwrap(), "/", &env.smb_dir(), "/token"].join(""))?;
243
+ file.write_all(token.as_bytes())?;
244
+ Ok(())
245
+ }
246
+ None => Err(anyhow!("Failed to get home directory.")),
247
+ }
248
+}
249
+
250
pub fn is_logged_in(env: Environment) -> bool {
251
// Check if token file exists
252
smb_token_file_path(env).is_some()
crates/cli/src/account/login/process.rs
+62
-39
@@ -1,7 +1,7 @@
1
use {
2
crate::{
3
account::{
4
- lib::{authorize_github, save_token},
4
+ lib::{authorize_github, store_token},
5
signup::{do_signup, signup_with_email, SignupMethod},
6
},
7
cli::CommandResult,
@@ -14,12 +14,16 @@ use {
14
log::debug,
15
reqwest::{Client, StatusCode},
16
smbcloud_model::{
17
- account::{ErrorCode, GithubInfo, SmbAuthorization, User},
17
+ account::{
18
+ ErrorCode as AccountErrorCode, ErrorCode::EmailNotFound, ErrorCode::EmailUnverified,
19
+ ErrorCode::GithubNotLinked, ErrorCode::PasswordNotSet, GithubInfo, SmbAuthorization,
20
+ User,
21
+ },
22
forgot::{Param, UserUpdatePassword},
19
- login::{LoginArgs, LoginParams, UserParam},
23
+ login::{AccountStatus, LoginArgs, LoginParams, UserParam},
24
signup::{GithubEmail, Provider, SignupGithubParams, SignupUserGithub},
25
},
22
- smbcloud_network::environment::Environment,
26
+ smbcloud_network::{environment::Environment, network::request_login},
27
smbcloud_networking::{
28
constants::{
29
PATH_LINK_GITHUB_ACCOUNT, PATH_RESEND_CONFIRMATION, PATH_RESET_PASSWORD_INSTRUCTIONS,
@@ -91,16 +95,14 @@ async fn process_authorization(env: Environment, auth: SmbAuthorization) -> Resu
95
if let Some(error_code) = auth.error_code {
96
debug!("{}", error_code);
97
match error_code {
94
- ErrorCode::EmailNotFound => {
95
- return create_new_account(env, auth.user_email, auth.user_info).await
96
- }
97
- ErrorCode::EmailUnverified => return send_email_verification(env, auth.user).await,
98
- ErrorCode::PasswordNotSet => {
98
+ EmailNotFound => return create_new_account(env, auth.user_email, auth.user_info).await,
99
+ EmailUnverified => return send_email_verification(env, auth.user).await,
100
+ PasswordNotSet => {
101
// Only for email and password login
102
let error = anyhow!("Password not set.");
103
return Err(error);
104
}
103
- ErrorCode::GithubNotLinked => return connect_github_account(env, auth).await,
105
+ GithubNotLinked => return connect_github_account(env, auth).await,
106
}
107
}
108
@@ -299,7 +301,7 @@ async fn login_with_email(env: Environment) -> Result<CommandResult> {
301
match check_email(env, &username).await {
302
Ok(auth) => {
303
// Only continue with password input if email is found and confirmed.
302
- if let Some(_) = auth.error_code {
304
+ if auth.error_code.is_some() {
305
// Check if email is in the database, unconfirmed. Only presents password input if email is found and confirmed.
306
let spinner = Spinner::new(
307
spinners::Spinners::SimpleDotsScrolling,
@@ -339,43 +341,28 @@ async fn do_process_login(env: Environment, args: LoginArgs) -> Result<CommandRe
341
},
342
};
343
342
- let response = match Client::new()
344
+ let builder = Client::new()
345
.post(build_smb_login_url(env))
344
- .json(&login_params)
345
- .send()
346
- .await
347
- {
346
+ .json(&login_params);
347
+
348
+ let account_status = match request_login(builder).await {
349
Ok(response) => response,
350
Err(_) => return Err(anyhow!(fail_message("Check your internet connection."))),
351
};
352
352
- match response.status() {
353
- StatusCode::OK => {
354
- // Login successful
355
- save_token(env, &response).await?;
353
+ match account_status {
354
+ AccountStatus::Ready { access_token } => {
355
+ store_token(env, access_token).await?;
356
Ok(CommandResult {
357
spinner,
358
symbol: succeed_symbol(),
359
msg: succeed_message("You are logged in!"),
360
})
361
}
362
- StatusCode::NOT_FOUND => {
363
- // Account not found and we show signup option
364
- Ok(CommandResult {
365
- spinner,
366
- symbol: fail_symbol(),
367
- msg: fail_message("Account not found. Please signup!"),
368
- })
369
- }
370
- StatusCode::UNPROCESSABLE_ENTITY => {
371
- // Account found but email not verified / password not set
372
- let result: SmbAuthorization = response.json().await?;
373
- // println!("Result: {:#?}", &result);
374
- after_checking_email_step(&env, spinner, result, None).await
362
+ AccountStatus::NotFound => Err(anyhow!(fail_message("Check your internet connection."))),
363
+ AccountStatus::Incomplete { status } => {
364
+ action_on_account_status(&env, spinner, status, None, None).await
365
}
376
- _ => Err(anyhow!(fail_message(
377
- "Login failed. Check your username and password."
378
- ))),
366
}
367
}
368
@@ -389,7 +376,7 @@ async fn after_checking_email_step(
376
Some(error_code) => {
377
debug!("{}", error_code);
378
match error_code {
392
- ErrorCode::EmailNotFound => {
379
+ EmailNotFound => {
380
spinner.stop_and_persist(
381
&succeed_symbol(),
382
succeed_message(
@@ -398,14 +385,14 @@ async fn after_checking_email_step(
385
);
386
signup_with_email(env.to_owned(), username).await
387
}
401
- ErrorCode::EmailUnverified => {
388
+ EmailUnverified => {
389
spinner.stop_and_persist(
390
&succeed_symbol(),
391
succeed_message("Email not verified. Please verify your email."),
392
);
393
send_email_verification(*env, result.user).await
394
}
408
- ErrorCode::PasswordNotSet => {
395
+ PasswordNotSet => {
396
spinner.stop_and_persist(
397
&succeed_symbol(),
398
succeed_message("Password not set. Please reset your password."),
@@ -422,6 +409,42 @@ async fn after_checking_email_step(
409
}
410
}
411
412
+async fn action_on_account_status(
413
+ env: &Environment,
414
+ mut spinner: Spinner,
415
+ error_code: AccountErrorCode,
416
+ username: Option<String>,
417
+ user: Option<User>,
418
+) -> Result<CommandResult> {
419
+ match error_code {
420
+ EmailNotFound => {
421
+ spinner.stop_and_persist(
422
+ &succeed_symbol(),
423
+ succeed_message("Account not found. Please continue with setting up your account."),
424
+ );
425
+ signup_with_email(env.to_owned(), username).await
426
+ }
427
+ EmailUnverified => {
428
+ spinner.stop_and_persist(
429
+ &succeed_symbol(),
430
+ succeed_message("Email not verified. Please verify your email."),
431
+ );
432
+ send_email_verification(*env, user).await
433
+ }
434
+ PasswordNotSet => {
435
+ spinner.stop_and_persist(
436
+ &succeed_symbol(),
437
+ succeed_message("Password not set. Please reset your password."),
438
+ );
439
+ send_reset_password(*env, user).await
440
+ }
441
+ _ => {
442
+ spinner.stop_and_persist(&fail_symbol(), fail_message("An error occurred."));
443
+ Err(anyhow!("Idk what happened."))
444
+ }
445
+ }
446
+}
447
+
448
async fn send_reset_password(env: Environment, user: Option<User>) -> Result<CommandResult> {
449
// Return early if user is null
450
if let Some(user) = user {
crates/smbcloud-model/src/account.rs
+2
-1
@@ -32,7 +32,8 @@ pub struct Data {
32
created_at: String,
33
}
34
35
-// This is smb authorization model.
35
+/// This is smbCloud authorization model
36
+/// that is currently tightly coupled with GitHub OAuth model.
37
#[derive(Debug, Serialize, Deserialize)]
38
pub struct SmbAuthorization {
39
pub message: String,
crates/smbcloud-model/src/login.rs
+12
-2
@@ -1,5 +1,7 @@
1
-use crate::account::{Data, Status};
2
-use serde::{Deserialize, Serialize};
1
+use {
2
+ crate::account::{Data, ErrorCode, Status},
3
+ serde::{Deserialize, Serialize},
4
+};
5
6
#[derive(Debug, Serialize)]
7
pub struct LoginArgs {
@@ -24,6 +26,14 @@ pub struct LoginResult {
26
pub data: Data,
27
}
28
29
+/// Login endpoint result.
30
+#[derive(Debug, Serialize, Deserialize)]
31
+pub enum AccountStatus {
32
+ NotFound,
33
+ Ready { access_token: String },
34
+ Incomplete { status: ErrorCode },
35
+}
36
+
37
#[cfg(test)]
38
mod tests {
39
use super::*;
crates/smbcloud-model/src/runner.rs
+3
-12
@@ -46,16 +46,7 @@ impl Runner {
46
if repo_path.join("package.json").exists()
47
&& (next_config_exists(repo_path) || astro_config_exists(repo_path))
48
{
49
- if next_config_exists(repo_path) {
50
- return Ok(Runner::NodeJs);
51
- } else if astro_config_exists(repo_path) {
52
- return Ok(Runner::NodeJs);
53
- } else {
54
- return Err(ErrorResponse::Error {
55
- error_code: UnsupportedRunner,
56
- message: UnsupportedRunner.message(None).to_string(),
57
- });
58
- };
49
+ return Ok(Runner::NodeJs);
50
}
51
52
if repo_path.join("Gemfile").exists() {
@@ -64,10 +55,10 @@ impl Runner {
55
if repo_path.join("Package.swift").exists() {
56
return Ok(Runner::Swift);
57
}
67
- return Err(ErrorResponse::Error {
58
+ Err(ErrorResponse::Error {
59
error_code: UnsupportedRunner,
60
message: UnsupportedRunner.message(None).to_string(),
70
- });
61
+ })
62
}
63
64
pub fn git_host(&self) -> String {
crates/smbcloud-network/src/network.rs
+49
-31
@@ -1,8 +1,12 @@
1
use {
2
log::{debug, error},
3
- reqwest::{RequestBuilder, Response},
3
+ reqwest::{RequestBuilder, Response, StatusCode},
4
serde::de::DeserializeOwned,
5
- smbcloud_model::error_codes::{ErrorCode, ErrorResponse},
5
+ smbcloud_model::{
6
+ account::SmbAuthorization,
7
+ error_codes::{ErrorCode, ErrorResponse},
8
+ login::AccountStatus,
9
+ },
10
};
11
12
//use std::time::Duration;
@@ -81,7 +85,7 @@ pub async fn parse_error_response<T: DeserializeOwned>(
85
Err(e)
86
}
87
84
-pub async fn request_login(builder: RequestBuilder) -> Result<String, ErrorResponse> {
88
+pub async fn request_login(builder: RequestBuilder) -> Result<AccountStatus, ErrorResponse> {
89
let response = builder.send().await;
90
let response = match response {
91
Ok(response) => response,
@@ -93,37 +97,51 @@ pub async fn request_login(builder: RequestBuilder) -> Result<String, ErrorRespo
97
});
98
}
99
};
96
- let response = match response.status() {
97
- reqwest::StatusCode::OK | reqwest::StatusCode::CREATED => response,
98
- status => {
99
- error!("Failed to get response: {:?}", status);
100
- return Err(ErrorResponse::Error {
101
- error_code: ErrorCode::NetworkError,
102
- message: ErrorCode::NetworkError.message(None).to_string(),
103
- });
100
+ match (response.status(), response.headers().get("Authorization")) {
101
+ (StatusCode::OK, Some(token)) => {
102
+ // Login successful, let's get the access token for real.
103
+ let access_token = match token.to_str() {
104
+ Ok(token) => token.to_string(),
105
+ Err(_) => {
106
+ return Err(ErrorResponse::Error {
107
+ error_code: ErrorCode::NetworkError,
108
+ message: ErrorCode::NetworkError.message(None).to_string(),
109
+ });
110
+ }
111
+ };
112
+ Ok(AccountStatus::Ready { access_token })
113
}
105
- };
106
-
107
- match response.headers().get("Authorization") {
108
- Some(token) => {
109
- debug!("{:?}", token.to_str());
110
- if let Ok(token_str) = token.to_str() {
111
- return Ok(token_str.to_string());
112
- } else {
113
- error!("Failed to convert token to string");
114
- return Err(ErrorResponse::Error {
115
- error_code: ErrorCode::NetworkError,
116
- message: ErrorCode::NetworkError.message(None).to_string(),
117
- });
118
- }
114
+ (StatusCode::NOT_FOUND, _) => {
115
+ // Account not found
116
+ Ok(AccountStatus::NotFound)
117
}
120
- None => {
121
- error!("Failed to get token. Probably a backend issue.");
122
- Err(ErrorResponse::Error {
123
- error_code: ErrorCode::NetworkError,
124
- message: ErrorCode::NetworkError.message(None).to_string(),
125
- })
118
+ (StatusCode::UNPROCESSABLE_ENTITY, _) => {
119
+ // Account found but email not verified / password not set.
120
+ let result: SmbAuthorization = match response.json().await {
121
+ Ok(res) => res,
122
+ Err(_) => {
123
+ return Err(ErrorResponse::Error {
124
+ error_code: ErrorCode::NetworkError,
125
+ message: ErrorCode::NetworkError.message(None).to_string(),
126
+ });
127
+ }
128
+ };
129
+ // println!("Result: {:#?}", &result);
130
+ let error_code = match result.error_code {
131
+ Some(code) => code,
132
+ None => {
133
+ return Err(ErrorResponse::Error {
134
+ error_code: ErrorCode::NetworkError,
135
+ message: ErrorCode::NetworkError.message(None).to_string(),
136
+ });
137
+ }
138
+ };
139
+ Ok(AccountStatus::Incomplete { status: error_code })
140
}
141
+ _ => Err(ErrorResponse::Error {
142
+ error_code: ErrorCode::NetworkError,
143
+ message: ErrorCode::NetworkError.message(None).to_string(),
144
+ }),
145
}
146
}
147
crates/smbcloud-networking-account/src/login.rs
+5
-2
@@ -1,6 +1,9 @@
1
use {
2
reqwest::Client,
3
- smbcloud_model::{error_codes::ErrorResponse, login::LoginArgs},
3
+ smbcloud_model::{
4
+ error_codes::ErrorResponse,
5
+ login::{AccountStatus, LoginArgs},
6
+ },
7
smbcloud_network::{environment::Environment, network::request_login},
8
smbcloud_networking::{
9
constants::{PATH_USERS_SIGN_IN, SMB_USER_AGENT},
@@ -12,7 +15,7 @@ pub async fn login(
15
env: Environment,
16
username: String,
17
password: String,
15
-) -> Result<String, ErrorResponse> {
18
+) -> Result<AccountStatus, ErrorResponse> {
19
let login_params = LoginArgs { username, password };
20
let builder = Client::new()
21
.post(build_smb_login_url(env))