add bootstrapping
This commit is contained in:
parent
8edc1dd15f
commit
b9c8ef81b3
8 changed files with 99 additions and 12 deletions
38
Cargo.lock
generated
38
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -16,3 +16,7 @@ serde_json = { version = "1", features = ["raw_value"] }
|
|||
figment = { version = "0.10", features = ["toml", "env"] }
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
anyhow = "1.0"
|
||||
argon2 = "0.5"
|
||||
|
||||
[profile.dev.package.sqlx-macros]
|
||||
opt-level = 3
|
|
@ -2,4 +2,3 @@ DROP TABLE pages;
|
|||
DROP TABLE sites;
|
||||
DROP TABLE audit;
|
||||
DROP TABLE users;
|
||||
DROP TABLE roles;
|
|
@ -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
25
src/auth.rs
Normal 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())
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
mod auth;
|
||||
mod content;
|
||||
mod error;
|
||||
mod roles;
|
||||
mod routes;
|
||||
mod state;
|
||||
|
||||
|
|
3
src/roles.rs
Normal file
3
src/roles.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
use uuid::{uuid, Uuid};
|
||||
|
||||
pub const ROLE_SUPERADMIN: Uuid = uuid!("8adbaff9-d3c7-44b9-ac67-93833a67380e");
|
|
@ -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})))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue