gracefully handle invalid login credentials

This commit is contained in:
Ilion Beyst 2021-12-15 22:40:55 +01:00
parent 13cdbc7ff7
commit 6aa72b3c87
4 changed files with 79 additions and 28 deletions

View file

@ -58,24 +58,26 @@ pub fn create_user(credentials: &Credentials, conn: &PgConnection) -> QueryResul
}
pub fn authenticate_user(credentials: &Credentials, db_conn: &PgConnection) -> Option<User> {
let user = users::table
users::table
.filter(users::username.eq(&credentials.username))
.first::<User>(db_conn)
.unwrap();
.optional()
.unwrap()
.and_then(|user| {
let password_matches = argon2::verify_raw(
credentials.password.as_bytes(),
&user.password_salt,
&user.password_hash,
&argon2_config(),
)
.unwrap();
let password_matches = argon2::verify_raw(
credentials.password.as_bytes(),
&user.password_salt,
&user.password_hash,
&argon2_config(),
)
.unwrap();
if password_matches {
return Some(user);
} else {
return None;
}
if password_matches {
return Some(user);
} else {
return None;
}
})
}
#[test]

View file

@ -3,6 +3,6 @@ extern crate rocket;
extern crate mozaic4_backend;
#[launch]
fn launch() -> _ {
fn launch() -> rocket::Rocket<rocket::Build> {
mozaic4_backend::rocket()
}

View file

@ -7,7 +7,8 @@ use rocket::serde::json::Json;
use serde::{Deserialize, Serialize};
use rocket::http::Status;
use rocket::request::{self, FromRequest, Outcome, Request};
use rocket::request::{FromRequest, Outcome, Request};
use rocket::response::status;
#[derive(Debug)]
pub enum AuthTokenError {
@ -23,17 +24,25 @@ impl<'r> FromRequest<'r> for User {
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
let keys: Vec<_> = request.headers().get("Authorization").collect();
let token = match keys.len() {
let auth_header = match keys.len() {
0 => return Outcome::Failure((Status::BadRequest, AuthTokenError::Missing)),
1 => keys[0].to_string(),
1 => keys[0],
_ => return Outcome::Failure((Status::BadRequest, AuthTokenError::BadCount)),
};
let token = match auth_header.strip_prefix("Bearer ") {
Some(token) => token.to_string(),
None => return Outcome::Failure((Status::BadRequest, AuthTokenError::Invalid)),
};
let db = request.guard::<DbConn>().await.unwrap();
let (_session, user) = db
let res = db
.run(move |conn| sessions::find_user_by_session(&token, conn))
.await
.unwrap();
Outcome::Success(user)
.await;
match res {
Ok((_session, user)) => Outcome::Success(user),
Err(_) => Outcome::Failure((Status::Unauthorized, AuthTokenError::Invalid)),
}
}
}
@ -79,7 +88,10 @@ pub struct LoginParams {
}
#[post("/login", data = "<params>")]
pub async fn login(db_conn: DbConn, params: Json<LoginParams>) -> String {
pub async fn login(
db_conn: DbConn,
params: Json<LoginParams>,
) -> Result<String, status::Forbidden<&'static str>> {
db_conn
.run(move |conn| {
let credentials = Credentials {
@ -87,9 +99,15 @@ pub async fn login(db_conn: DbConn, params: Json<LoginParams>) -> String {
password: &params.password,
};
// TODO: handle failures
let user = users::authenticate_user(&credentials, conn).unwrap();
let session = sessions::create_session(&user, conn);
return session.token;
let authenticated = users::authenticate_user(&credentials, conn);
match authenticated {
None => Err(status::Forbidden(Some("invalid auth"))),
Some(user) => {
let session = sessions::create_session(&user, conn);
Ok(session.token)
}
}
})
.await
}

View file

@ -37,6 +37,22 @@ macro_rules! run_test {
}};
}
pub struct BearerAuth {
token: String,
}
impl BearerAuth {
pub fn new(token: String) -> Self {
Self { token }
}
}
impl<'a> Into<Header<'a>> for BearerAuth {
fn into(self) -> Header<'a> {
Header::new("Authorization", format!("Bearer {}", self.token))
}
}
#[test]
fn test_registration() {
run_test!(|client, _conn| {
@ -62,7 +78,7 @@ fn test_registration() {
let response = client
.get("/users/me")
.header(Header::new("Authorization", token))
.header(BearerAuth::new(token))
.dispatch()
.await;
@ -73,3 +89,18 @@ fn test_registration() {
assert_eq!(json["username"], "piepkonijn");
});
}
#[test]
fn test_reject_invalid_credentials() {
run_test!(|client, _conn| {
let response = client
.post("/login")
.header(ContentType::JSON)
.body(r#"{"username": "piepkonijn", "password": "letmeinplease"}"#)
.dispatch()
.await;
assert_eq!(response.status(), Status::Forbidden);
// assert_eq!(response.content_type(), Some(ContentType::JSON));
});
}