@smbcloud / cli / commits / e2a9e53

Forgot password

Seto Elkahfi committed Apr 10, 2023 at 17:32 UTC e2a9e5380cae7eb3f2fb7f04bf7b546e05d16b1e
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(&params)
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 +}