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"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
|
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]]
|
[[package]]
|
||||||
name = "async-trait"
|
name = "async-trait"
|
||||||
version = "0.1.68"
|
version = "0.1.68"
|
||||||
|
@ -156,12 +167,27 @@ version = "0.21.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
|
checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64ct"
|
||||||
|
version = "1.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.3.2"
|
version = "1.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "blake2"
|
||||||
|
version = "0.10.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
|
||||||
|
dependencies = [
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block-buffer"
|
name = "block-buffer"
|
||||||
version = "0.10.4"
|
version = "0.10.4"
|
||||||
|
@ -666,6 +692,7 @@ name = "mabel"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"argon2",
|
||||||
"axum",
|
"axum",
|
||||||
"axum-macros",
|
"axum-macros",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
@ -822,6 +849,17 @@ dependencies = [
|
||||||
"windows-targets",
|
"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]]
|
[[package]]
|
||||||
name = "paste"
|
name = "paste"
|
||||||
version = "1.0.12"
|
version = "1.0.12"
|
||||||
|
|
|
@ -16,3 +16,7 @@ serde_json = { version = "1", features = ["raw_value"] }
|
||||||
figment = { version = "0.10", features = ["toml", "env"] }
|
figment = { version = "0.10", features = ["toml", "env"] }
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
anyhow = "1.0"
|
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 sites;
|
||||||
DROP TABLE audit;
|
DROP TABLE audit;
|
||||||
DROP TABLE users;
|
DROP TABLE users;
|
||||||
DROP TABLE roles;
|
|
|
@ -4,7 +4,7 @@ CREATE TABLE users (
|
||||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4 (),
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4 (),
|
||||||
name VARCHAR UNIQUE NOT NULL,
|
name VARCHAR UNIQUE NOT NULL,
|
||||||
email VARCHAR,
|
email VARCHAR,
|
||||||
password BYTEA,
|
password VARCHAR,
|
||||||
display_name VARCHAR,
|
display_name VARCHAR,
|
||||||
bio TEXT,
|
bio TEXT,
|
||||||
roles UUID[] NOT NULL,
|
roles UUID[] NOT NULL,
|
||||||
|
@ -13,11 +13,6 @@ CREATE TABLE users (
|
||||||
deleted_at TIMESTAMP
|
deleted_at TIMESTAMP
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE roles (
|
|
||||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4 (),
|
|
||||||
scopes VARCHAR[] NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE sites (
|
CREATE TABLE sites (
|
||||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4 (),
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4 (),
|
||||||
owner UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
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 content;
|
||||||
mod error;
|
mod error;
|
||||||
|
mod roles;
|
||||||
mod routes;
|
mod routes;
|
||||||
mod state;
|
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::extract::State;
|
||||||
use axum::http::StatusCode;
|
use axum::http::StatusCode;
|
||||||
|
use axum::Json;
|
||||||
|
use serde_json::{json, Value};
|
||||||
|
|
||||||
|
use crate::auth::{hash, random};
|
||||||
use crate::error::AppError;
|
use crate::error::AppError;
|
||||||
|
use crate::roles::ROLE_SUPERADMIN;
|
||||||
use crate::state::AppState;
|
use crate::state::AppState;
|
||||||
|
|
||||||
pub async fn bootstrap(State(state): State<Arc<AppState>>) -> Result<(), AppError> {
|
pub async fn bootstrap(State(state): State<Arc<AppState>>) -> Result<Json<Value>, AppError> {
|
||||||
// Only allow this request if the user db is completely empty!
|
// Only allow this request if the user table is completely empty!
|
||||||
let empty = sqlx::query!(
|
let empty = sqlx::query!(
|
||||||
"SELECT CASE WHEN EXISTS(SELECT 1 FROM users) THEN false ELSE true END AS empty;"
|
"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 => {
|
true => {
|
||||||
//todo add user
|
//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