mirror of
https://github.com/ProvableHQ/leo.git
synced 2025-01-08 11:58:02 +03:00
updates login procedure
- locally stored token is now verified on leo login and removed if expired - we allow re-login when credentials or token are passed (did not before) - we now store username alongside jwt
This commit is contained in:
parent
c336f5a704
commit
ab033ff857
20
leo/api.rs
20
leo/api.rs
@ -20,7 +20,7 @@ use reqwest::{
|
||||
Method,
|
||||
StatusCode,
|
||||
};
|
||||
use serde::Serialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Trait describes API Routes and Request bodies, struct which implements
|
||||
/// Route MUST also support Serialize to be usable in Api::run_route(r: Route)
|
||||
@ -184,12 +184,18 @@ impl Route for Login {
|
||||
|
||||
/// Handler for 'my_profile' route. Meant to be used to get profile details but
|
||||
/// in current application is used to check if user is logged in. Any non-200 response
|
||||
/// is treated as Unauthorized
|
||||
/// is treated as Unauthorized.
|
||||
#[derive(Serialize)]
|
||||
pub struct Profile {}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ProfileResponse {
|
||||
username: String,
|
||||
}
|
||||
|
||||
impl Route for Profile {
|
||||
type Output = bool;
|
||||
// Some with Username is success, None is failure.
|
||||
type Output = Option<String>;
|
||||
|
||||
const AUTH: bool = true;
|
||||
const METHOD: Method = Method::GET;
|
||||
@ -197,6 +203,12 @@ impl Route for Profile {
|
||||
|
||||
fn process(&self, res: Response) -> Result<Self::Output> {
|
||||
// this may be extended for more precise error handling
|
||||
Ok(res.status() == 200)
|
||||
let status = res.status();
|
||||
if status == StatusCode::OK {
|
||||
let body: ProfileResponse = res.json()?;
|
||||
return Ok(Some(body.username));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
@ -59,34 +59,15 @@ impl Command for Login {
|
||||
}
|
||||
|
||||
fn apply(self, context: Context, _: Self::Input) -> Result<Self::Output> {
|
||||
// quick hack to check if user is already logged in. ;)
|
||||
if context.api.auth_token().is_some() {
|
||||
tracing::info!("You are already logged in");
|
||||
return Ok(context.api.auth_token().unwrap());
|
||||
};
|
||||
|
||||
let mut api = context.api;
|
||||
let mut api = context.clone().api;
|
||||
|
||||
// ...or trying to use arguments to either get token or user-pass
|
||||
let token = match (self.token, self.user, self.pass) {
|
||||
// Login using existing token, use get_profile route for that
|
||||
(Some(token), _, _) => {
|
||||
tracing::info!("Token passed, checking...");
|
||||
|
||||
api.set_auth_token(token.clone());
|
||||
|
||||
let is_ok = api.run_route(ProfileRoute {})?;
|
||||
if !is_ok {
|
||||
return Err(anyhow!("Supplied token is incorrect"));
|
||||
};
|
||||
|
||||
token
|
||||
}
|
||||
|
||||
// Login using username and password
|
||||
let (token, username) = match (self.token, self.user, self.pass) {
|
||||
// Login using username and password if they were passed. Even if token already
|
||||
// exists login procedure will be done first (we need that for expired credentials).
|
||||
(None, Some(email_username), Some(password)) => {
|
||||
let login = LoginRoute {
|
||||
email_username,
|
||||
email_username: email_username.clone(),
|
||||
password,
|
||||
};
|
||||
|
||||
@ -98,17 +79,48 @@ impl Command for Login {
|
||||
return Err(anyhow!("Unable to get token"));
|
||||
};
|
||||
|
||||
tok_opt.unwrap()
|
||||
(tok_opt.unwrap(), email_username)
|
||||
}
|
||||
|
||||
// Login with token, use get_profile route to verify that.
|
||||
(Some(token), _, _) => {
|
||||
tracing::info!("Token passed, checking...");
|
||||
|
||||
api.set_auth_token(token.clone());
|
||||
|
||||
match api.run_route(ProfileRoute {})? {
|
||||
Some(username) => (token, username),
|
||||
None => return Err(anyhow!("Supplied token is incorrect")),
|
||||
}
|
||||
}
|
||||
|
||||
// In case token or login/pass were not passed as arguments
|
||||
(_, _, _) => return Err(anyhow!("No credentials provided")),
|
||||
(_, _, _) => {
|
||||
// Check locally stored token if there is.
|
||||
let token = context.api.auth_token();
|
||||
|
||||
match token {
|
||||
Some(token) => {
|
||||
tracing::info!("Found locally stored credentials, verifying...");
|
||||
|
||||
if let Some(username) = api.run_route(ProfileRoute {})? {
|
||||
(token, username)
|
||||
} else {
|
||||
remove_token_and_username()?;
|
||||
return Err(anyhow!(
|
||||
"Stored credentials are incorrect or expired, please login again"
|
||||
));
|
||||
}
|
||||
}
|
||||
None => return Err(anyhow!("No credentials provided")),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// write token either after logging or if it was passed
|
||||
write_token(token.as_str())?;
|
||||
write_token_and_username(token.as_str(), username.as_str())?;
|
||||
|
||||
tracing::info!("Success! You are now logged in!");
|
||||
tracing::info!("Success! You are logged in!");
|
||||
|
||||
Ok(token)
|
||||
}
|
||||
|
@ -14,7 +14,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::{commands::Command, config::remove_token, context::Context};
|
||||
use crate::{commands::Command, config::remove_token_and_username, context::Context};
|
||||
|
||||
use anyhow::Result;
|
||||
use std::io::ErrorKind;
|
||||
@ -41,7 +41,7 @@ impl Command for Logout {
|
||||
fn apply(self, _context: Context, _: Self::Input) -> Result<Self::Output> {
|
||||
// the only error we're interested here is NotFound
|
||||
// however err in this case can also be of kind PermissionDenied or other
|
||||
if let Err(err) = remove_token() {
|
||||
if let Err(err) = remove_token_and_username() {
|
||||
match err.kind() {
|
||||
ErrorKind::NotFound => {
|
||||
tracing::info!("you are not logged in");
|
||||
|
@ -32,6 +32,7 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
pub const LEO_CREDENTIALS_FILE: &str = "credentials";
|
||||
pub const LEO_CONFIG_FILE: &str = "config.toml";
|
||||
pub const LEO_USERNAME_FILE: &str = "username";
|
||||
|
||||
lazy_static! {
|
||||
pub static ref LEO_CONFIG_DIRECTORY: PathBuf = {
|
||||
@ -44,6 +45,11 @@ lazy_static! {
|
||||
path.push(LEO_CREDENTIALS_FILE);
|
||||
path
|
||||
};
|
||||
pub static ref LEO_USERNAME_PATH: PathBuf = {
|
||||
let mut path = LEO_CONFIG_DIRECTORY.to_path_buf();
|
||||
path.push(LEO_USERNAME_FILE);
|
||||
path
|
||||
};
|
||||
pub static ref LEO_CONFIG_PATH: PathBuf = {
|
||||
let mut path = LEO_CONFIG_DIRECTORY.to_path_buf();
|
||||
path.push(LEO_CONFIG_FILE);
|
||||
@ -129,7 +135,7 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_token(token: &str) -> Result<(), io::Error> {
|
||||
pub fn write_token_and_username(token: &str, username: &str) -> Result<(), io::Error> {
|
||||
let config_dir = LEO_CONFIG_DIRECTORY.clone();
|
||||
|
||||
// Create Leo config directory if it not exists
|
||||
@ -139,6 +145,10 @@ pub fn write_token(token: &str) -> Result<(), io::Error> {
|
||||
|
||||
let mut credentials = File::create(&LEO_CREDENTIALS_PATH.to_path_buf())?;
|
||||
credentials.write_all(&token.as_bytes())?;
|
||||
|
||||
let mut username_file = File::create(&LEO_USERNAME_PATH.to_path_buf())?;
|
||||
username_file.write_all(&username.as_bytes())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -149,7 +159,15 @@ pub fn read_token() -> Result<String, io::Error> {
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
pub fn remove_token() -> Result<(), io::Error> {
|
||||
pub fn read_username() -> Result<String, io::Error> {
|
||||
let mut username = File::open(&LEO_USERNAME_PATH.to_path_buf())?;
|
||||
let mut buf = String::new();
|
||||
username.read_to_string(&mut buf)?;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
pub fn remove_token_and_username() -> Result<(), io::Error> {
|
||||
fs::remove_file(&LEO_CREDENTIALS_PATH.to_path_buf())?;
|
||||
fs::remove_file(&LEO_USERNAME_PATH.to_path_buf())?;
|
||||
Ok(())
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user