Skip to content
Snippets Groups Projects
Commit ad11ac6c authored by James2Tulloch's avatar James2Tulloch
Browse files

Password hashing

parent 8480596d
Branches
No related tags found
No related merge requests found
...@@ -12,4 +12,6 @@ pyo3 = { version = "0.18", features = ["extension-module"] } ...@@ -12,4 +12,6 @@ pyo3 = { version = "0.18", features = ["extension-module"] }
postgres = "0.19" postgres = "0.19"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
jsonwebtoken = "8.2.0" jsonwebtoken = "8.2.0"
argon2 = "0.4"
rand = "0.8"
...@@ -67,6 +67,37 @@ fn verify_jwt(token: String, secret: String) -> PyResult<PyObject> { ...@@ -67,6 +67,37 @@ fn verify_jwt(token: String, secret: String) -> PyResult<PyObject> {
Ok(dict.to_object(py)) Ok(dict.to_object(py))
} }
// Pasword Hashing with Argon2
#[pyfunction]
fn hash_password(password: &str) -> PyResult<String> {
use argon2::{Argon2, PasswordHasher};
use argon2::password_hash::{SaltString, rand_core::OsRng};
// Generate a random salt using OsRng.
let salt = SaltString::generate(&mut OsRng);
// Create a default Argon2 instance.
let argon2 = Argon2::default();
// Hash the password using the salt.
let password_hash = argon2.hash_password(password.as_bytes(), &salt)
.map_err(|e| PyRuntimeError::new_err(e.to_string()))?
.to_string();
Ok(password_hash)
}
#[pyfunction]
fn verify_password(hash: &str, password: &str) -> PyResult<bool> {
use argon2::{Argon2, PasswordVerifier};
use argon2::password_hash::PasswordHash;
let argon2 = Argon2::default();
let parsed_hash = PasswordHash::new(hash)
.map_err(|e| PyRuntimeError::new_err(e.to_string()))?;
argon2.verify_password(password.as_bytes(), &parsed_hash)
.map_err(|e| PyRuntimeError::new_err(e.to_string()))
.map(|_| true)
}
/// A helper function to convert postgres::Error into a Python RuntimeError. /// A helper function to convert postgres::Error into a Python RuntimeError.
fn pg_err(e: postgres::Error) -> PyErr { fn pg_err(e: postgres::Error) -> PyErr {
PyRuntimeError::new_err(e.to_string()) PyRuntimeError::new_err(e.to_string())
...@@ -81,7 +112,8 @@ fn init_db(db_url: &str) -> PyResult<()> { ...@@ -81,7 +112,8 @@ fn init_db(db_url: &str) -> PyResult<()> {
CREATE TABLE IF NOT EXISTS users ( CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
name VARCHAR NOT NULL, name VARCHAR NOT NULL,
email VARCHAR NOT NULL email VARCHAR NOT NULL,
password_hash VARCHAR NOT NULL
); );
CREATE TABLE IF NOT EXISTS groups ( CREATE TABLE IF NOT EXISTS groups (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
...@@ -99,11 +131,12 @@ fn init_db(db_url: &str) -> PyResult<()> { ...@@ -99,11 +131,12 @@ fn init_db(db_url: &str) -> PyResult<()> {
/// Create a new user by inserting into the database. /// Create a new user by inserting into the database.
#[pyfunction] #[pyfunction]
fn create_user(db_url: &str, name: &str, email: &str) -> PyResult<()> { fn create_user(db_url: &str, name: &str, email: &str, password: &str) -> PyResult<()> {
let password_hash = hash_password(password)?;
let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?; let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?;
client.execute( client.execute(
"INSERT INTO users (name, email) VALUES ($1, $2)", "INSERT INTO users (name, email, password_hash) VALUES ($1, $2, $3)",
&[&name, &email] &[&name, &email, &password_hash]
).map_err(pg_err)?; ).map_err(pg_err)?;
Ok(()) Ok(())
} }
...@@ -173,6 +206,24 @@ fn delete_user(db_url: &str, user_id: i32) -> PyResult<bool> { ...@@ -173,6 +206,24 @@ fn delete_user(db_url: &str, user_id: i32) -> PyResult<bool> {
Ok(deleted > 0) Ok(deleted > 0)
} }
///Verify credentials
///Retrieve a stored password hash for the email and compare.
#[pyfunction]
fn verify_user(db_url: &str, email: &str, password: & str) -> PyResult<bool> {
let mut client = Client::connect(db_url, NoTls).map_err(pg_err)?;
let row_opt = client.query_opt(
"SELECT password_hash FROM users WHERE email = $1",
&[&email]
).map_err(pg_err)?;
if let Some(row) = row_opt {
let stored_hash: String = row.get(0);
verify_password(&stored_hash, password)
} else {
Ok(false)
}
}
/// Create a group /// Create a group
#[pyfunction] #[pyfunction]
fn create_group(db_url: &str, name: &str) -> PyResult<()> { fn create_group(db_url: &str, name: &str) -> PyResult<()> {
...@@ -270,6 +321,8 @@ fn rust_crud_api(_py: Python, m: &PyModule) -> PyResult<()> { ...@@ -270,6 +321,8 @@ fn rust_crud_api(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(get_group_members, m)?)?; m.add_function(wrap_pyfunction!(get_group_members, m)?)?;
m.add_function(wrap_pyfunction!(generate_jwt, m)?)?; m.add_function(wrap_pyfunction!(generate_jwt, m)?)?;
m.add_function(wrap_pyfunction!(verify_jwt, m)?)?; m.add_function(wrap_pyfunction!(verify_jwt, m)?)?;
m.add_function(wrap_pyfunction!(hash_password, m)?)?;
m.add_function(wrap_pyfunction!(verify_password, m)?)?;
Ok(()) Ok(())
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment