add bootstrapping

This commit is contained in:
Hamcha 2023-06-29 14:59:39 +02:00
parent 8edc1dd15f
commit b9c8ef81b3
Signed by: hamcha
GPG Key ID: 1669C533B8CF6D89
8 changed files with 99 additions and 12 deletions

38
Cargo.lock generated
View File

@ -51,6 +51,17 @@ version = "1.0.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
[[package]]
name = "argon2"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95c2fcf79ad1932ac6269a738109997a83c227c09b75842ae564dc8ede6a861c"
dependencies = [
"base64ct",
"blake2",
"password-hash",
]
[[package]]
name = "async-trait"
version = "0.1.68"
@ -156,12 +167,27 @@ version = "0.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
[[package]]
name = "base64ct"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "blake2"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
dependencies = [
"digest",
]
[[package]]
name = "block-buffer"
version = "0.10.4"
@ -666,6 +692,7 @@ name = "mabel"
version = "0.1.0"
dependencies = [
"anyhow",
"argon2",
"axum",
"axum-macros",
"chrono",
@ -822,6 +849,17 @@ dependencies = [
"windows-targets",
]
[[package]]
name = "password-hash"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
dependencies = [
"base64ct",
"rand_core",
"subtle",
]
[[package]]
name = "paste"
version = "1.0.12"

View File

@ -15,4 +15,8 @@ serde = { version = "1" }
serde_json = { version = "1", features = ["raw_value"] }
figment = { version = "0.10", features = ["toml", "env"] }
chrono = { version = "0.4", features = ["serde"] }
anyhow = "1.0"
anyhow = "1.0"
argon2 = "0.5"
[profile.dev.package.sqlx-macros]
opt-level = 3

View File

@ -1,5 +1,4 @@
DROP TABLE pages;
DROP TABLE sites;
DROP TABLE audit;
DROP TABLE users;
DROP TABLE roles;
DROP TABLE users;

View File

@ -4,7 +4,7 @@ CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4 (),
name VARCHAR UNIQUE NOT NULL,
email VARCHAR,
password BYTEA,
password VARCHAR,
display_name VARCHAR,
bio TEXT,
roles UUID[] NOT NULL,
@ -13,11 +13,6 @@ CREATE TABLE users (
deleted_at TIMESTAMP
);
CREATE TABLE roles (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4 (),
scopes VARCHAR[] NOT NULL
);
CREATE TABLE sites (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4 (),
owner UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,

25
src/auth.rs Normal file
View File

@ -0,0 +1,25 @@
use anyhow::{anyhow, Result};
use argon2::{
password_hash::{rand_core::OsRng, SaltString},
Argon2, PasswordHash, PasswordHasher, PasswordVerifier,
};
pub fn random() -> String {
SaltString::generate(&mut OsRng).to_string()
}
pub fn hash(plaintext: &String) -> Result<String> {
let salt = SaltString::generate(&mut OsRng);
let hashed = Argon2::default()
.hash_password(plaintext.as_bytes(), &salt)
.map_err(|err| anyhow!(err))?
.to_string();
Ok(hashed)
}
pub fn verify(plaintext: &String, hash: &String) -> Result<bool> {
let parsed_hash = PasswordHash::new(&hash).map_err(|err| anyhow!(err))?;
Ok(Argon2::default()
.verify_password(plaintext.as_bytes(), &parsed_hash)
.is_ok())
}

View File

@ -1,5 +1,7 @@
mod auth;
mod content;
mod error;
mod roles;
mod routes;
mod state;

3
src/roles.rs Normal file
View File

@ -0,0 +1,3 @@
use uuid::{uuid, Uuid};
pub const ROLE_SUPERADMIN: Uuid = uuid!("8adbaff9-d3c7-44b9-ac67-93833a67380e");

View File

@ -2,12 +2,16 @@ use std::sync::Arc;
use axum::extract::State;
use axum::http::StatusCode;
use axum::Json;
use serde_json::{json, Value};
use crate::auth::{hash, random};
use crate::error::AppError;
use crate::roles::ROLE_SUPERADMIN;
use crate::state::AppState;
pub async fn bootstrap(State(state): State<Arc<AppState>>) -> Result<(), AppError> {
// Only allow this request if the user db is completely empty!
pub async fn bootstrap(State(state): State<Arc<AppState>>) -> Result<Json<Value>, AppError> {
// Only allow this request if the user table is completely empty!
let empty = sqlx::query!(
"SELECT CASE WHEN EXISTS(SELECT 1 FROM users) THEN false ELSE true END AS empty;"
)
@ -23,7 +27,24 @@ pub async fn bootstrap(State(state): State<Arc<AppState>>) -> Result<(), AppErro
}),
true => {
//todo add user
Ok(())
let username = "admin";
let password = random();
sqlx::query!(
r#"
INSERT INTO users ( name, display_name, password, roles )
VALUES ( $1, $2, $3, $4 )
RETURNING id
"#,
username,
"Administrator",
hash(&password)?,
&[ROLE_SUPERADMIN],
)
.fetch_one(&state.database)
.await?;
Ok(Json(json!({"username": username, "password": password})))
}
}
}