refactor login

refactor login so it's self contained in a single function
will be useful in adding other password based login method such as ldap
This commit is contained in:
Trinity Pointard 2019-07-07 16:24:24 +02:00
parent 54c6d21fc5
commit 7fd1fe6d52
5 changed files with 50 additions and 74 deletions

View File

@ -109,16 +109,8 @@ fn new<'a>(args: &ArgMatches<'a>, conn: &Connection) {
rpassword::read_password().expect("Couldn't read your password.") rpassword::read_password().expect("Couldn't read your password.")
}); });
NewUser::new_local( NewUser::new_local(conn, username, display_name, admin, &bio, email, &password)
conn, .expect("Couldn't save new user");
username,
display_name,
admin,
&bio,
email,
User::hash_pass(&password).expect("Couldn't hash password"),
)
.expect("Couldn't save new user");
} }
fn reset_password<'a>(args: &ArgMatches<'a>, conn: &Connection) { fn reset_password<'a>(args: &ArgMatches<'a>, conn: &Connection) {

View File

@ -333,17 +333,35 @@ impl User {
}) })
} }
pub fn hash_pass(pass: &str) -> Result<String> { fn hash_pass(pass: &str) -> Result<String> {
bcrypt::hash(pass, 10).map_err(Error::from) bcrypt::hash(pass, 10).map_err(Error::from)
} }
pub fn auth(&self, pass: &str) -> bool { fn auth(&self, pass: &str) -> bool {
self.hashed_password self.hashed_password
.clone() .clone()
.map(|hashed| bcrypt::verify(pass, hashed.as_ref()).unwrap_or(false)) .map(|hashed| bcrypt::verify(pass, hashed.as_ref()).unwrap_or(false))
.unwrap_or(false) .unwrap_or(false)
} }
pub fn connect(rocket: &PlumeRocket, name: &str, password: &str) -> Result<Self> {
let user = User::find_by_email(&*rocket.conn, &name)
.or_else(|_| User::find_by_fqn(&rocket, &name));
match user {
Ok(user) => {
if user.auth(password) {
Ok(user)
} else {
Err(Error::NotFound)
}
}
Err(_) => {
User::get(&rocket.conn, 1)?.auth(password);
Err(Error::NotFound)
}
}
}
pub fn reset_password(&self, conn: &Connection, pass: &str) -> Result<()> { pub fn reset_password(&self, conn: &Connection, pass: &str) -> Result<()> {
diesel::update(self) diesel::update(self)
.set(users::hashed_password.eq(User::hash_pass(pass)?)) .set(users::hashed_password.eq(User::hash_pass(pass)?))
@ -923,7 +941,7 @@ impl NewUser {
is_admin: bool, is_admin: bool,
summary: &str, summary: &str,
email: String, email: String,
password: String, password: &str,
) -> Result<User> { ) -> Result<User> {
let (pub_key, priv_key) = gen_keypair(); let (pub_key, priv_key) = gen_keypair();
User::insert( User::insert(
@ -935,7 +953,7 @@ impl NewUser {
summary: summary.to_owned(), summary: summary.to_owned(),
summary_html: SafeString::new(&utils::md_to_html(&summary, None, false, None).0), summary_html: SafeString::new(&utils::md_to_html(&summary, None, false, None).0),
email: Some(email), email: Some(email),
hashed_password: Some(password), hashed_password: Some(User::hash_pass(password)?),
instance_id: Instance::get_local()?.id, instance_id: Instance::get_local()?.id,
ap_url: String::new(), ap_url: String::new(),
public_key: String::from_utf8(pub_key).or(Err(Error::Signature))?, public_key: String::from_utf8(pub_key).or(Err(Error::Signature))?,
@ -964,7 +982,7 @@ pub(crate) mod tests {
true, true,
"Hello there, I'm the admin", "Hello there, I'm the admin",
"admin@example.com".to_owned(), "admin@example.com".to_owned(),
"invalid_admin_password".to_owned(), "invalid_admin_password",
) )
.unwrap(); .unwrap();
let user = NewUser::new_local( let user = NewUser::new_local(
@ -974,7 +992,7 @@ pub(crate) mod tests {
false, false,
"Hello there, I'm no one", "Hello there, I'm no one",
"user@example.com".to_owned(), "user@example.com".to_owned(),
"invalid_user_password".to_owned(), "invalid_user_password",
) )
.unwrap(); .unwrap();
let other = NewUser::new_local( let other = NewUser::new_local(
@ -984,7 +1002,7 @@ pub(crate) mod tests {
false, false,
"Hello there, I'm someone else", "Hello there, I'm someone else",
"other@example.com".to_owned(), "other@example.com".to_owned(),
"invalid_other_password".to_owned(), "invalid_other_password",
) )
.unwrap(); .unwrap();
vec![admin, user, other] vec![admin, user, other]
@ -1003,7 +1021,7 @@ pub(crate) mod tests {
false, false,
"Hello I'm a test", "Hello I'm a test",
"test@example.com".to_owned(), "test@example.com".to_owned(),
User::hash_pass("test_password").unwrap(), "test_password",
) )
.unwrap(); .unwrap();
@ -1109,7 +1127,7 @@ pub(crate) mod tests {
false, false,
"Hello I'm a test", "Hello I'm a test",
"test@example.com".to_owned(), "test@example.com".to_owned(),
User::hash_pass("test_password").unwrap(), "test_password",
) )
.unwrap(); .unwrap();

View File

@ -62,30 +62,20 @@ pub fn oauth(
let conn = &*rockets.conn; let conn = &*rockets.conn;
let app = App::find_by_client_id(conn, &query.client_id)?; let app = App::find_by_client_id(conn, &query.client_id)?;
if app.client_secret == query.client_secret { if app.client_secret == query.client_secret {
if let Ok(user) = User::find_by_fqn(&rockets, &query.username) { if let Ok(user) = User::connect(&rockets, &query.username, &query.password) {
if user.auth(&query.password) { let token = ApiToken::insert(
let token = ApiToken::insert( conn,
conn, NewApiToken {
NewApiToken { app_id: app.id,
app_id: app.id, user_id: user.id,
user_id: user.id, value: random_hex(),
value: random_hex(), scopes: query.scopes.clone(),
scopes: query.scopes.clone(), },
}, )?;
)?; Ok(Json(json!({
Ok(Json(json!({ "token": token.value
"token": token.value })))
})))
} else {
Ok(Json(json!({
"error": "Invalid credentials"
})))
}
} else { } else {
// Making fake password verification to avoid different
// response times that would make it possible to know
// if a username is registered or not.
User::get(conn, 1)?.auth(&query.password);
Ok(Json(json!({ Ok(Json(json!({
"error": "Invalid credentials" "error": "Invalid credentials"
}))) })))

View File

@ -33,53 +33,29 @@ pub fn new(m: Option<String>, rockets: PlumeRocket) -> Ructe {
)) ))
} }
#[derive(Default, FromForm, Validate)] #[derive(Default, FromForm)]
pub struct LoginForm { pub struct LoginForm {
#[validate(length(min = "1", message = "We need an email, or a username to identify you"))]
pub email_or_name: String, pub email_or_name: String,
#[validate(length(min = "1", message = "Your password can't be empty"))]
pub password: String, pub password: String,
} }
#[post("/login", data = "<form>")] #[post("/login", data = "<form>")]
pub fn create( pub fn create(
form: LenientForm<LoginForm>, form: LenientForm<LoginForm>,
mut cookies: Cookies,
rockets: PlumeRocket, rockets: PlumeRocket,
mut cookies: Cookies,
) -> RespondOrRedirect { ) -> RespondOrRedirect {
let conn = &*rockets.conn; let conn = &*rockets.conn;
let user = User::find_by_email(&*conn, &form.email_or_name)
.or_else(|_| User::find_by_fqn(&rockets, &form.email_or_name));
let mut errors = match form.validate() {
Ok(_) => ValidationErrors::new(),
Err(e) => e,
};
let user_id = if let Ok(user) = user { let user_id = if let Ok(user) = User::connect(&rockets, &form.email_or_name, &form.password) {
if !user.auth(&form.password) { user.id.to_string()
let mut err = ValidationError::new("invalid_login");
err.message = Some(Cow::from("Invalid username, or password"));
errors.add("email_or_name", err);
String::new()
} else {
user.id.to_string()
}
} else { } else {
// Fake password verification, only to avoid different login times let mut errors = ValidationErrors::new();
// that could be used to see if an email adress is registered or not
User::get(&*conn, 1)
.map(|u| u.auth(&form.password))
.expect("No user is registered");
let mut err = ValidationError::new("invalid_login"); let mut err = ValidationError::new("invalid_login");
err.message = Some(Cow::from("Invalid username, or password")); err.message = Some(Cow::from("Invalid username, or password"));
errors.add("email_or_name", err); errors.add("email_or_name", err);
String::new()
};
if !errors.is_empty() {
return render!(session::login(&rockets.to_context(), None, &*form, errors)).into(); return render!(session::login(&rockets.to_context(), None, &*form, errors)).into();
} };
cookies.add_private( cookies.add_private(
Cookie::build(AUTH_COOKIE, user_id) Cookie::build(AUTH_COOKIE, user_id)
@ -111,7 +87,7 @@ pub fn create(
&(conn, &rockets.intl.catalog, None, None), &(conn, &rockets.intl.catalog, None, None),
None, None,
&*form, &*form,
errors ValidationErrors::new()
)) ))
.into() .into()
} }

View File

@ -520,7 +520,7 @@ pub fn create(
false, false,
"", "",
form.email.to_string(), form.email.to_string(),
User::hash_pass(&form.password).map_err(to_validation)?, &form.password,
) )
.map_err(to_validation)?; .map_err(to_validation)?;
Ok(Flash::success( Ok(Flash::success(