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"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
|
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]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.3.2"
|
version = "1.3.2"
|
||||||
|
@ -220,6 +233,16 @@ dependencies = [
|
||||||
"generic-array",
|
"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]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.11.1"
|
version = "3.11.1"
|
||||||
|
@ -271,6 +294,16 @@ dependencies = [
|
||||||
"winapi",
|
"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]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.0.25"
|
version = "4.0.25"
|
||||||
|
@ -812,6 +845,15 @@ version = "0.1.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb"
|
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]]
|
[[package]]
|
||||||
name = "instant"
|
name = "instant"
|
||||||
version = "0.1.12"
|
version = "0.1.12"
|
||||||
|
@ -1034,6 +1076,7 @@ dependencies = [
|
||||||
"axum",
|
"axum",
|
||||||
"axum-auth",
|
"axum-auth",
|
||||||
"axum-macros",
|
"axum-macros",
|
||||||
|
"bcrypt",
|
||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
|
@ -2251,3 +2294,9 @@ name = "yansi"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
|
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"
|
hmac = "0.12.1"
|
||||||
sha2 = "0.10.6"
|
sha2 = "0.10.6"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
bcrypt = "0.14.0"
|
||||||
|
|
|
@ -13,6 +13,7 @@ use sha2::Sha256;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
|
||||||
use crate::{dto::scope::Scope, app_state::AppState};
|
use crate::{dto::scope::Scope, app_state::AppState};
|
||||||
|
use crate::database::Database;
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub struct TokenAuthRequest {
|
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");
|
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 now = SystemTime::now();
|
||||||
let token_str = create_jwt_token(account).unwrap();
|
let token_str = create_jwt_token(account).unwrap();
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ use tracing::debug;
|
||||||
|
|
||||||
use chrono::{DateTime, Utc, NaiveDateTime};
|
use chrono::{DateTime, Utc, NaiveDateTime};
|
||||||
|
|
||||||
use crate::dto::Tag;
|
use crate::dto::{Tag, user::User};
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait Database {
|
pub trait Database {
|
||||||
|
@ -45,6 +45,11 @@ pub trait Database {
|
||||||
/// List all repositories.
|
/// List all repositories.
|
||||||
/// If limit is not specified, a default limit of 1000 will be returned.
|
/// 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>>;
|
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]
|
#[async_trait]
|
||||||
|
@ -251,4 +256,26 @@ impl Database for Pool<Sqlite> {
|
||||||
|
|
||||||
Ok(repos)
|
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
|
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 (
|
CREATE TABLE IF NOT EXISTS image_tags (
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
repository TEXT NOT NULL,
|
repository TEXT NOT NULL,
|
||||||
|
@ -26,3 +21,10 @@ CREATE TABLE IF NOT EXISTS manifest_layers (
|
||||||
layer_digest TEXT NOT NULL,
|
layer_digest TEXT NOT NULL,
|
||||||
PRIMARY KEY (manifest, layer_digest)
|
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 manifest;
|
||||||
pub mod digest;
|
pub mod digest;
|
||||||
pub mod scope;
|
pub mod scope;
|
||||||
|
pub mod user;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Tag {
|
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::response::{Response, IntoResponse};
|
||||||
use axum::{Router, routing};
|
use axum::{Router, routing};
|
||||||
use axum::ServiceExt;
|
use axum::ServiceExt;
|
||||||
|
use bcrypt::Version;
|
||||||
use tower_layer::Layer;
|
use tower_layer::Layer;
|
||||||
|
|
||||||
use sqlx::sqlite::SqlitePoolOptions;
|
use sqlx::sqlite::SqlitePoolOptions;
|
||||||
|
|
Loading…
Reference in New Issue