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:
damirka 2021-04-12 15:59:23 +03:00
parent c336f5a704
commit ab033ff857
4 changed files with 78 additions and 36 deletions

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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");

View File

@ -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(())
}