Implement user login using bcrypt hashed passwords

This commit is contained in:
SeanOMik 2023-04-29 01:55:56 -04:00
parent f70e04c52d
commit 5f74e46607
Signed by: SeanOMik
GPG Key ID: 568F326C7EB33ACB
8 changed files with 113 additions and 7 deletions

49
Cargo.lock generated
View File

@ -196,6 +196,19 @@ version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
[[package]]
name = "bcrypt"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9df288bec72232f78c1ec5fe4e8f1d108aa0265476e93097593c803c8c02062a"
dependencies = [
"base64 0.21.0",
"blowfish",
"getrandom",
"subtle",
"zeroize",
]
[[package]]
name = "bitflags"
version = "1.3.2"
@ -220,6 +233,16 @@ dependencies = [
"generic-array",
]
[[package]]
name = "blowfish"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7"
dependencies = [
"byteorder",
"cipher",
]
[[package]]
name = "bumpalo"
version = "3.11.1"
@ -271,6 +294,16 @@ dependencies = [
"winapi",
]
[[package]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"inout",
]
[[package]]
name = "clap"
version = "4.0.25"
@ -812,6 +845,15 @@ version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb"
[[package]]
name = "inout"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
dependencies = [
"generic-array",
]
[[package]]
name = "instant"
version = "0.1.12"
@ -1034,6 +1076,7 @@ dependencies = [
"axum",
"axum-auth",
"axum-macros",
"bcrypt",
"bytes",
"chrono",
"clap",
@ -2251,3 +2294,9 @@ name = "yansi"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
[[package]]
name = "zeroize"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9"

View File

@ -49,3 +49,4 @@ jwt = "0.16.0"
hmac = "0.12.1"
sha2 = "0.10.6"
rand = "0.8.5"
bcrypt = "0.14.0"

View File

@ -13,6 +13,7 @@ use sha2::Sha256;
use rand::Rng;
use crate::{dto::scope::Scope, app_state::AppState};
use crate::database::Database;
#[derive(Deserialize, Debug)]
pub struct TokenAuthRequest {
@ -158,7 +159,18 @@ pub async fn auth_basic_get(basic_auth: Option<AuthBasic>, state: State<Arc<AppS
debug!("Constructed auth request");
if let Some(account) = auth.account {
if let (Some(account), Some(password)) = (auth.account, auth.password) {
// Ensure that the password is correct
let database = &state.database;
if !database.verify_user_login(account.clone(), password).await.unwrap() {
debug!("Authentication failed, incorrect password!");
return (
StatusCode::UNAUTHORIZED
).into_response();
}
drop(database);
debug!("User password is correct");
let now = SystemTime::now();
let token_str = create_jwt_token(account).unwrap();

View File

@ -4,7 +4,7 @@ use tracing::debug;
use chrono::{DateTime, Utc, NaiveDateTime};
use crate::dto::Tag;
use crate::dto::{Tag, user::User};
#[async_trait]
pub trait Database {
@ -45,6 +45,11 @@ pub trait Database {
/// List all repositories.
/// If limit is not specified, a default limit of 1000 will be returned.
async fn list_repositories(&self, limit: Option<u32>, last_repo: Option<String>) -> sqlx::Result<Vec<String>>;
/// User stuff
async fn create_user(&self, username: String, email: String, password_hash: String, password_salt: String) -> sqlx::Result<User>;
async fn verify_user_login(&self, username: String, password: String) -> anyhow::Result<bool>;
}
#[async_trait]
@ -251,4 +256,26 @@ impl Database for Pool<Sqlite> {
Ok(repos)
}
async fn create_user(&self, username: String, email: String, password_hash: String, password_salt: String) -> sqlx::Result<User> {
let username = username.to_lowercase();
let email = email.to_lowercase();
sqlx::query("INSERT INTO users (username, email, password_hash, password_salt) VALUES (?, ?, ?, ?)")
.bind(username.clone())
.bind(email.clone())
.bind(password_hash)
.bind(password_salt)
.execute(self).await?;
Ok(User::new(username, email))
}
async fn verify_user_login(&self, username: String, password: String) -> anyhow::Result<bool> {
let username = username.to_lowercase();
let row: (String, ) = sqlx::query_as("SELECT password_hash FROM users WHERE username = ?")
.bind(username)
.fetch_one(self).await?;
Ok(bcrypt::verify(password, &row.0)?)
}
}

View File

@ -8,11 +8,6 @@ CREATE TABLE IF NOT EXISTS image_manifests (
content TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS layer_blobs (
digest TEXT NOT NULL PRIMARY KEY,
blob BYTEA NOT NULL
);
CREATE TABLE IF NOT EXISTS image_tags (
name TEXT NOT NULL,
repository TEXT NOT NULL,
@ -26,3 +21,10 @@ CREATE TABLE IF NOT EXISTS manifest_layers (
layer_digest TEXT NOT NULL,
PRIMARY KEY (manifest, layer_digest)
);
CREATE TABLE IF NOT EXISTS users (
username TEXT NOT NULL UNIQUE PRIMARY KEY,
email TEXT NOT NULL,
password_hash TEXT NOT NULL,
password_salt TEXT NOT NULL
);

View File

@ -3,6 +3,7 @@ use chrono::{DateTime, Utc};
pub mod manifest;
pub mod digest;
pub mod scope;
pub mod user;
#[derive(Debug)]
pub struct Tag {

13
src/dto/user.rs Normal file
View File

@ -0,0 +1,13 @@
pub struct User {
username: String,
email: String,
}
impl User {
pub fn new(username: String, email: String) -> Self {
Self {
username,
email,
}
}
}

View File

@ -17,6 +17,7 @@ use axum::middleware::Next;
use axum::response::{Response, IntoResponse};
use axum::{Router, routing};
use axum::ServiceExt;
use bcrypt::Version;
use tower_layer::Layer;
use sqlx::sqlite::SqlitePoolOptions;