Implement user login using bcrypt hashed passwords
This commit is contained in:
parent
f70e04c52d
commit
5f74e46607
|
@ -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"
|
||||
|
|
|
@ -49,3 +49,4 @@ jwt = "0.16.0"
|
|||
hmac = "0.12.1"
|
||||
sha2 = "0.10.6"
|
||||
rand = "0.8.5"
|
||||
bcrypt = "0.14.0"
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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)?)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
);
|
|
@ -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 {
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
pub struct User {
|
||||
username: String,
|
||||
email: String,
|
||||
}
|
||||
|
||||
impl User {
|
||||
pub fn new(username: String, email: String) -> Self {
|
||||
Self {
|
||||
username,
|
||||
email,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue