Add login route

Signed-off-by: trivernis <trivernis@protonmail.com>
pull/1/head
trivernis 4 years ago
parent 76921e8854
commit 5e77c77559
Signed by: Trivernis
GPG Key ID: DFFFCC2C7A02DB45

746
Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -14,7 +14,7 @@ dotenv = "0.15.0"
redis = "0.17.0"
serde = {version = "1.0.115", features = ["serde_derive"]}
rand = "0.7.3"
scrypt = "0.4.1"
bcrypt = "0.8.2"
zeroize = {version = "1.1.0", features = ["zeroize_derive"]}
byteorder = "1.3.4"
rmp-serde = "0.14.4"
@ -22,4 +22,7 @@ rmp = "0.8.9"
log = "0.4.11"
env_logger = "0.7.1"
colored = "2.0.0"
crossbeam-utils = "0.7.2"
crossbeam-utils = "0.7.2"
mime = "0.3.16"
serde_json = "1.0.57"
rouille = "3.0.0"

@ -3,12 +3,13 @@ use crate::utils::create_user_token;
use crate::utils::error::RedisConnection;
use byteorder::{BigEndian, ByteOrder};
use redis::{ErrorKind, RedisError, RedisResult};
use serde::Serialize;
use zeroize::Zeroize;
const REQUEST_TOKEN_EXPIRE_SECONDS: usize = 60 * 10;
const REFRESH_TOKEN_EXPIRE_SECONDS: usize = 60 * 60 * 24;
const REQUEST_TOKEN_EXPIRE_SECONDS: i32 = 60 * 10;
const REFRESH_TOKEN_EXPIRE_SECONDS: i32 = 60 * 60 * 24;
#[derive(Clone, Debug, Zeroize)]
#[derive(Clone, Debug, Zeroize, Serialize)]
#[zeroize(drop)]
pub struct SessionTokens {
pub request_token: [u8; 32],
@ -22,8 +23,8 @@ impl SessionTokens {
Self {
request_token: create_user_token(user_id),
refresh_token: create_user_token(user_id),
request_ttl: -1,
refresh_ttl: -1,
request_ttl: REQUEST_TOKEN_EXPIRE_SECONDS,
refresh_ttl: REFRESH_TOKEN_EXPIRE_SECONDS,
}
}
@ -31,8 +32,8 @@ impl SessionTokens {
Self {
request_token,
refresh_token,
request_ttl: -1,
refresh_ttl: -1,
request_ttl: REQUEST_TOKEN_EXPIRE_SECONDS,
refresh_ttl: REFRESH_TOKEN_EXPIRE_SECONDS,
}
}

@ -3,9 +3,9 @@ use crate::database::tokens::SessionTokens;
use crate::database::user_roles::UserRoles;
use crate::database::{DatabaseResult, RedisConnection, Table};
use crate::utils::error::DBError;
use crate::utils::{create_salt, get_user_id_from_token, TOKEN_LENGTH};
use crate::utils::{create_salt, get_user_id_from_token, hash_password, TOKEN_LENGTH};
use postgres::Client;
use scrypt::ScryptParams;
use std::sync::{Arc, Mutex};
use zeroize::{Zeroize, Zeroizing};
@ -65,14 +65,8 @@ impl Users {
return Err(DBError::RecordExists);
}
let salt = Zeroizing::new(create_salt());
let mut pw_hash = Zeroizing::new([0u8; 32]);
scrypt::scrypt(
password.as_bytes(),
&*salt,
&ScryptParams::recommended(),
&mut *pw_hash,
)
.map_err(|_| DBError::ScryptError)?;
let pw_hash =
hash_password(password.as_bytes(), &*salt).map_err(|e| DBError::GenericError(e))?;
password.zeroize();
let row = connection.query_one("
INSERT INTO users (name, email, password_hash, salt) VALUES ($1, $2, $3, $4) RETURNING *;
@ -81,17 +75,25 @@ impl Users {
Ok(UserRecord::from_ordered_row(&row))
}
pub fn create_token(&self, email: String, password: String) -> DatabaseResult<SessionTokens> {
pub fn create_get_tokens(
&self,
email: String,
password: String,
) -> DatabaseResult<SessionTokens> {
if self.validate_login(&email, password)? {
let mut connection = self.database_connection.lock().unwrap();
let row = connection.query_one("SELECT id FROM users WHERE email = $1", &[&email])?;
let id: i32 = row.get(0);
let mut redis_connection = self.redis_connection.lock().unwrap();
let tokens = SessionTokens::new(id);
tokens.store(&mut redis_connection)?;
if let Ok(tokens) = SessionTokens::retrieve(id, &mut redis_connection) {
Ok(tokens)
} else {
let tokens = SessionTokens::new(id);
tokens.store(&mut redis_connection)?;
Ok(tokens)
Ok(tokens)
}
} else {
Err(DBError::GenericError("Invalid password".to_string()))
}
@ -140,22 +142,20 @@ impl Users {
fn validate_login(&self, email: &String, password: String) -> DatabaseResult<bool> {
let password = Zeroizing::new(password);
let mut connection = self.database_connection.lock().unwrap();
let row = connection.query_one(
"SELECT password_hash, salt FROM users WHERE email = $1",
&[&email],
)?;
let row = connection
.query_opt(
"SELECT password_hash, salt FROM users WHERE email = $1",
&[&email],
)?
.ok_or(DBError::GenericError(format!(
"No user with the email '{}' found",
&email
)))?;
let original_pw_hash: Zeroizing<Vec<u8>> = Zeroizing::new(row.get(0));
let salt: Zeroizing<Vec<u8>> = Zeroizing::new(row.get(1));
let mut pw_hash = Zeroizing::new([0u8; 32]);
scrypt::scrypt(
password.as_bytes(),
&*salt,
&ScryptParams::recommended(),
&mut *pw_hash,
)
.map_err(|_| DBError::ScryptError)?;
let pw_hash =
hash_password(password.as_bytes(), &*salt).map_err(|e| DBError::GenericError(e))?;
Ok(*pw_hash == *original_pw_hash.as_slice())
Ok(pw_hash == *original_pw_hash.as_slice())
}
}

@ -1,3 +1,6 @@
#[macro_use]
extern crate rouille;
pub mod database;
pub mod server;
pub mod utils;

@ -2,6 +2,7 @@ use colored::Colorize;
use crossbeam_utils::sync::WaitGroup;
use env_logger::Env;
use flotte_user_management::database::Database;
use flotte_user_management::server::http_server::UserHttpServer;
use flotte_user_management::server::user_rpc::UserRpcServer;
use log::Level;
use std::thread;
@ -11,6 +12,7 @@ fn main() {
let database = Database::new().unwrap();
database.init().unwrap();
let rpc_server = UserRpcServer::new(&database);
let http_server = UserHttpServer::new(&database);
let wg = WaitGroup::new();
{
let wg = WaitGroup::clone(&wg);
@ -19,6 +21,14 @@ fn main() {
std::mem::drop(wg);
});
}
{
let wg = WaitGroup::clone(&wg);
let http_server = http_server;
thread::spawn(move || {
http_server.start();
std::mem::drop(wg);
});
}
wg.wait();
}

@ -0,0 +1,86 @@
use crate::database::Database;
use crate::server::messages::LoginMessage;
use crate::utils::error::DBError;
use rouille::{Request, Response, Server};
use serde::export::Formatter;
use std::error::Error;
use std::fmt::{self, Display};
use std::io::Read;
const LISTEN_ADDRESS: &str = "HTTP_SERVER_ADDRESS";
const DEFAULT_LISTEN_ADDRESS: &str = "127.0.0.1:8080";
pub struct UserHttpServer {
database: Database,
}
#[derive(Debug)]
pub struct HTTPError {
message: String,
code: usize,
}
impl Display for HTTPError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.message)
}
}
impl Error for HTTPError {}
impl From<DBError> for HTTPError {
fn from(other: DBError) -> Self {
Self {
message: other.to_string(),
code: 400,
}
}
}
impl HTTPError {
pub fn new(message: String, code: usize) -> Self {
Self { message, code }
}
}
type HTTPResult<T> = Result<T, HTTPError>;
impl UserHttpServer {
pub fn new(database: &Database) -> Self {
Self {
database: Database::clone(database),
}
}
pub fn start(&self) {
let listen_address =
dotenv::var(LISTEN_ADDRESS).unwrap_or(DEFAULT_LISTEN_ADDRESS.to_string());
let database = Database::clone(&self.database);
let server = Server::new(listen_address, move |request| {
router!(request,
(POST) (/login) => {
Self::login(&database, request).unwrap_or_else(|e|Response::text(e.to_string()))
},
_ => Response::empty_404()
)
})
.unwrap();
server.run()
}
fn login(database: &Database, request: &Request) -> HTTPResult<Response> {
if let Some(mut data) = request.data() {
let mut data_string = String::new();
data.read_to_string(&mut data_string)
.map_err(|_| HTTPError::new("Failed to read request data".to_string(), 500))?;
let login_request: LoginMessage = serde_json::from_str(data_string.as_str())
.map_err(|e| HTTPError::new(e.to_string(), 400))?;
let tokens = database
.users
.create_get_tokens(login_request.email, login_request.password)?;
Ok(Response::json(&tokens))
} else {
Err(HTTPError::new("Missing Request Data".to_string(), 400))
}
}
}

@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
use std::error::Error;
use std::fmt;
use std::fmt::Display;
use zeroize::Zeroize;
#[derive(Deserialize)]
pub struct TokenRequest {
@ -74,3 +75,9 @@ pub struct CreateRoleRequest {
pub struct CreatePermissionsRequest {
pub permissions: Vec<CreatePermissionsEntry>,
}
#[derive(Deserialize, Zeroize)]
pub struct LoginMessage {
pub email: String,
pub password: String,
}

@ -1,3 +1,4 @@
pub mod http_server;
pub mod messages;
pub mod rpc_methods;
pub mod user_rpc;

@ -8,7 +8,7 @@ pub enum DBError {
Redis(RedisError),
Postgres(PostgresError),
RecordExists,
ScryptError,
BCryptError,
DeserializeError(serde_postgres::DeError),
GenericError(String),
}
@ -29,7 +29,7 @@ impl DBError {
DBError::Postgres(p) => p.to_string(),
DBError::Redis(r) => r.to_string(),
DBError::DeserializeError(de) => de.to_string(),
DBError::ScryptError => "sCrypt Hash creation error".to_string(),
DBError::BCryptError => "BCrypt Hash creation error".to_string(),
}
}
}

@ -1,5 +1,7 @@
use bcrypt::DEFAULT_COST;
use byteorder::{BigEndian, ByteOrder};
use rand::Rng;
use std::panic;
pub mod error;
@ -26,3 +28,12 @@ pub fn create_user_token(user_id: i32) -> [u8; TOKEN_LENGTH] {
pub fn get_user_id_from_token(token: &[u8]) -> i32 {
BigEndian::read_i32(token)
}
pub fn hash_password(password: &[u8], salt: &[u8]) -> Result<[u8; 24], String> {
panic::catch_unwind(|| {
let mut pw_hash = [0u8; 24];
bcrypt::bcrypt(DEFAULT_COST, salt, password, &mut pw_hash);
Ok(pw_hash)
})
.map_err(|_| "Hashing failed".to_string())?
}

Loading…
Cancel
Save