@smbcloud / cli / commits / bf6ec0d

refactor login method to be more modular

Seto committed Sep 26, 2025 at 12:43 UTC bf6ec0dfee99128153b5568ec0fd4c8f6f1213bb
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))