8 files changed
+183
-16
src/account/forgot/mod.rs
+102
-8
@@ -1,11 +1,14 @@
1
use anyhow::Result;
2
use console::style;
3
-use dialoguer::{theme::ColorfulTheme, Input};
3
+use dialoguer::{theme::ColorfulTheme, Input, Password};
4
use reqwest::{Client, StatusCode};
5
-use serde::Serialize;
5
+use serde::{Deserialize, Serialize};
6
use spinners::Spinner;
7
8
-use crate::{constants::BASE_URL, util::CommandResult};
8
+use crate::{
9
+ constants::BASE_URL,
10
+ util::{email_validation, CommandResult},
11
+};
12
13
#[derive(Debug, Serialize)]
14
struct Args {
@@ -21,11 +24,12 @@ pub async fn process_forgot() -> Result<CommandResult> {
24
println!("Provide your login credentials.");
25
let email = Input::<String>::with_theme(&ColorfulTheme::default())
26
.with_prompt("Email")
27
+ .validate_with(|email: &String| email_validation(email))
28
.interact()
29
.unwrap();
26
- let spinner = Spinner::new(
30
+ let mut spinner = Spinner::new(
31
spinners::Spinners::SimpleDotsScrolling,
28
- style("⏳ Logging in...").green().bold().to_string(),
32
+ style("Checking email...").green().bold().to_string(),
33
);
34
35
let params = Args {
@@ -38,16 +42,106 @@ pub async fn process_forgot() -> Result<CommandResult> {
42
.send()
43
.await?;
44
45
+ match response.status() {
46
+ StatusCode::OK => {
47
+ spinner.stop_and_persist(
48
+ "✅",
49
+ "Check your email and input your code here.".to_owned(),
50
+ );
51
+ input_code().await
52
+ }
53
+ _ => Ok(CommandResult {
54
+ spinner,
55
+ symbol: "😩".to_owned(),
56
+ msg: "Something wrong when trying to reset email.".to_owned(),
57
+ }),
58
+ }
59
+}
60
+
61
+async fn input_code() -> Result<CommandResult> {
62
+ let security_code = Input::<String>::with_theme(&ColorfulTheme::default())
63
+ .with_prompt("Code")
64
+ .interact()
65
+ .unwrap();
66
+
67
+ Spinner::new(
68
+ spinners::Spinners::SimpleDotsScrolling,
69
+ style("Checking your code...").green().bold().to_string(),
70
+ )
71
+ .stop_and_persist(
72
+ &"✅".to_owned(),
73
+ "Great. Now input your new password.".to_owned(),
74
+ );
75
+
76
+ let new_password = Password::with_theme(&ColorfulTheme::default())
77
+ .with_prompt("Password")
78
+ .validate_with(|input: &String| -> Result<(), &str> {
79
+ if input.len() >= 6 {
80
+ Ok(())
81
+ } else {
82
+ Err("Password must be at least 6 characters")
83
+ }
84
+ })
85
+ .with_confirmation("Confirm password", "Passwords do not match")
86
+ .interact()
87
+ .unwrap();
88
+ let password_confirmation = String::from(&new_password);
89
+
90
+ #[derive(Debug, Serialize)]
91
+ struct Param {
92
+ user: User,
93
+ }
94
+
95
+ #[derive(Debug, Serialize)]
96
+ struct User {
97
+ reset_password_token: String,
98
+ password: String,
99
+ password_confirmation: String,
100
+ }
101
+
102
+ let params = Param {
103
+ user: User {
104
+ reset_password_token: security_code,
105
+ password: new_password,
106
+ password_confirmation: password_confirmation,
107
+ },
108
+ };
109
+
110
+ let mut spinner = Spinner::new(
111
+ spinners::Spinners::SimpleDotsScrolling,
112
+ style("Updating your password...")
113
+ .green()
114
+ .bold()
115
+ .to_string(),
116
+ );
117
+
118
+ let response = Client::new()
119
+ .put([BASE_URL, "/v1/users/password"].join(""))
120
+ .json(¶ms)
121
+ .send()
122
+ .await?;
123
+
124
+ #[derive(Debug, Serialize, Deserialize)]
125
+ struct Response {
126
+ status: i32,
127
+ email: Option<String>,
128
+ }
129
+
130
match response.status() {
131
StatusCode::OK => Ok(CommandResult {
132
spinner,
133
symbol: "✅".to_owned(),
45
- msg: "Check your email and follow the instruction to reset your password.".to_owned(),
134
+ msg: "Your password has been updated. Login with your new password.".to_owned(),
135
+ }),
136
+ StatusCode::NOT_FOUND => Ok(CommandResult {
137
+ spinner,
138
+ symbol: "😩".to_owned(),
139
+ msg: "URL not found.".to_owned(),
140
}),
141
_ => Ok(CommandResult {
142
spinner,
49
- symbol: "✅".to_owned(),
50
- msg: "You are logged in!".to_owned(),
143
+ symbol: "😩".to_owned(),
144
+ msg: "Something wrong when trying to reset email.".to_owned(),
145
}),
146
}
147
}
src/auth_app/cli.rs
new
+30
@@ -0,0 +1,30 @@
1
+use clap::Subcommand;
2
+
3
+#[derive(Subcommand)]
4
+pub enum Commands {
5
+ #[clap(about = "Add new project.")]
6
+ New {},
7
+
8
+ #[clap(about = "List all your projects.")]
9
+ List {},
10
+
11
+ #[clap(about = "Show detail of a project.")]
12
+ Show {
13
+ /// Project Id
14
+ #[clap(short, long, required = true)]
15
+ id: String,
16
+ },
17
+
18
+ #[clap(about = "Delete a project.")]
19
+ Delete {
20
+ /// Project name
21
+ #[clap(short, long, required = true)]
22
+ id: String,
23
+ },
24
+
25
+ #[clap(about = "Use project for current CLI session.")]
26
+ Use {
27
+ #[clap(short, long, required = true)]
28
+ id: String,
29
+ },
30
+}
src/auth_app/mod.rs
new
+24
@@ -0,0 +1,24 @@
1
+use crate::{auth_app::cli::Commands, util::CommandResult};
2
+use anyhow::Result;
3
+
4
+pub(crate) mod cli;
5
+
6
+pub async fn process_auth_app(commands: Commands) -> Result<CommandResult> {
7
+ match commands {
8
+ Commands::New {} => {
9
+ todo!()
10
+ }
11
+ Commands::List {} => {
12
+ todo!()
13
+ }
14
+ Commands::Show { id } => {
15
+ todo!()
16
+ }
17
+ Commands::Delete { id } => {
18
+ todo!()
19
+ }
20
+ Commands::Use { id } => {
21
+ todo!()
22
+ }
23
+ }
24
+}
src/cli/mod.rs
+8
-6
@@ -1,6 +1,6 @@
1
use clap::{Parser, Subcommand};
2
3
-use crate::projects;
3
+use crate::{auth_app, projects};
4
5
#[derive(Parser)]
6
#[clap(author, version, about)]
@@ -17,14 +17,10 @@ pub struct Cli {
17
pub enum Commands {
18
#[clap(about = "Create an account. Use your email as your username.")]
19
Signup {},
20
-
21
- #[clap(
22
- about = "Login to your account. To create an account, use smb signup or visit https://smbpndk.com"
23
- )]
20
+ #[clap(about = "Login to your account. To create an account, use smb signup.")]
21
Login {},
22
#[clap(about = "Logout all session.")]
23
Logout {},
27
-
24
#[clap(about = "Forgot email? Use this command to reset your password.")]
25
Forgot {},
26
@@ -33,4 +29,10 @@ pub enum Commands {
29
#[clap(subcommand)]
30
command: projects::Commands,
31
},
32
+
33
+ #[clap(about = "Manage your AuthApp. Add, delete, edit. Need authentication.")]
34
+ AuthApp {
35
+ #[clap(subcommand)]
36
+ command: auth_app::cli::Commands,
37
+ },
38
}
src/constants.rs
+2
-2
@@ -1,10 +1,10 @@
1
use console::Emoji;
2
3
#[cfg(debug_assertions)]
4
-pub const BASE_URL: &str = "http://localhost:3000/";
4
+pub const BASE_URL: &str = "http://localhost:3000";
5
6
#[cfg(not(debug_assertions))]
7
-pub const BASE_URL: &str = "https://api.smbpndk.com/";
7
+pub const BASE_URL: &str = "https://api.smbpndk.com";
8
9
pub const OK_EMOJI: Emoji<'_, '_> = Emoji("✅ ", "");
10
pub const ERROR_EMOJI: Emoji<'_, '_> = Emoji("❌ ", "");
src/lib.rs
+1
@@ -1,4 +1,5 @@
1
pub mod account;
2
+pub mod auth_app;
3
pub mod cli;
4
pub mod constants;
5
pub mod projects;
src/main.rs
+2
@@ -10,6 +10,7 @@ use smbpndk_cli::{
10
login::{process_login, process_logout},
11
signup::process_signup,
12
},
13
+ auth_app::process_auth_app,
14
cli::{Cli, Commands},
15
projects::process_projects,
16
util::CommandResult,
@@ -91,5 +92,6 @@ async fn run() -> Result<CommandResult> {
92
Commands::Signup {} => process_signup().await,
93
Commands::Forgot {} => process_forgot().await,
94
Commands::Projects { command } => process_projects(command).await,
95
+ Commands::AuthApp { command } => process_auth_app(command).await,
96
}
97
}
src/util.rs
+14
@@ -1,3 +1,4 @@
1
+use regex::Regex;
2
use spinners::Spinner;
3
4
#[macro_export]
@@ -21,3 +22,16 @@ pub struct CommandResult {
22
pub symbol: String,
23
pub msg: String,
24
}
25
+
26
+pub fn email_validation(input: &String) -> Result<(), &'static str> {
27
+ let email_regex = Regex::new(
28
+ r"^([a-z0-9_+]([a-z0-9_+.]*[a-z0-9_+])?)@([a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,6})",
29
+ )
30
+ .unwrap();
31
+
32
+ if email_regex.is_match(input) {
33
+ Ok(())
34
+ } else {
35
+ Err("Username must be an email address")
36
+ }
37
+}