Add working password update
Signed-off-by: Marcel Müller <neikos@neikos.email>
This commit is contained in:
parent
a80ad57ca9
commit
7a5233e385
25 changed files with 221 additions and 60 deletions
66
Cargo.lock
generated
66
Cargo.lock
generated
|
|
@ -943,6 +943,12 @@ dependencies = [
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "http-range-header"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httparse"
|
name = "httparse"
|
||||||
version = "1.10.1"
|
version = "1.10.1"
|
||||||
|
|
@ -1338,6 +1344,16 @@ version = "0.3.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mime_guess"
|
||||||
|
version = "2.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
|
||||||
|
dependencies = [
|
||||||
|
"mime",
|
||||||
|
"unicase",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "1.1.1"
|
version = "1.1.1"
|
||||||
|
|
@ -1405,6 +1421,7 @@ dependencies = [
|
||||||
"thiserror 2.0.17",
|
"thiserror 2.0.17",
|
||||||
"time",
|
"time",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tower-http",
|
||||||
"tower-livereload",
|
"tower-livereload",
|
||||||
"tower-sessions",
|
"tower-sessions",
|
||||||
"tower-sessions-sqlx-store",
|
"tower-sessions-sqlx-store",
|
||||||
|
|
@ -1432,9 +1449,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "notify-debouncer-full"
|
name = "notify-debouncer-full"
|
||||||
version = "0.6.0"
|
version = "0.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "375bd3a138be7bfeff3480e4a623df4cbfb55b79df617c055cd810ba466fa078"
|
checksum = "c02b49179cfebc9932238d04d6079912d26de0379328872846118a0fa0dbb302"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"file-id",
|
"file-id",
|
||||||
"log",
|
"log",
|
||||||
|
|
@ -2559,6 +2576,19 @@ dependencies = [
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-util"
|
||||||
|
version = "0.7.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower"
|
name = "tower"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
|
|
@ -2591,6 +2621,32 @@ dependencies = [
|
||||||
"tower-service",
|
"tower-service",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower-http"
|
||||||
|
version = "0.6.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
"bytes",
|
||||||
|
"futures-core",
|
||||||
|
"futures-util",
|
||||||
|
"http",
|
||||||
|
"http-body",
|
||||||
|
"http-body-util",
|
||||||
|
"http-range-header",
|
||||||
|
"httpdate",
|
||||||
|
"mime",
|
||||||
|
"mime_guess",
|
||||||
|
"percent-encoding",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower-layer"
|
name = "tower-layer"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
|
|
@ -2756,6 +2812,12 @@ version = "0.1.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
|
checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicase"
|
||||||
|
version = "2.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-bidi"
|
name = "unicode-bidi"
|
||||||
version = "0.3.18"
|
version = "0.3.18"
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,9 @@
|
||||||
pkgs.cargo-nextest
|
pkgs.cargo-nextest
|
||||||
pkgs.sqlite
|
pkgs.sqlite
|
||||||
pkgs.sqlx-cli
|
pkgs.sqlx-cli
|
||||||
|
|
||||||
|
pkgs.nodejs_24
|
||||||
|
pkgs.tailwindcss_4
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
DATABASE_URL=sqlite://database.db
|
DATABASE_URL=sqlite:database.db
|
||||||
|
|
|
||||||
3
nixie-server/.gitignore
vendored
3
nixie-server/.gitignore
vendored
|
|
@ -1 +1,4 @@
|
||||||
database.db*
|
database.db*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
public/
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ readme.workspace = true
|
||||||
axum = { workspace = true, features = ["macros"] }
|
axum = { workspace = true, features = ["macros"] }
|
||||||
tokio = { workspace = true, features = ["full"] }
|
tokio = { workspace = true, features = ["full"] }
|
||||||
axum-login = { workspace = true }
|
axum-login = { workspace = true }
|
||||||
sqlx = { workspace = true, features = ["runtime-tokio", "sqlite"] }
|
sqlx = { workspace = true, features = ["runtime-tokio", "sqlite", "time"] }
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
tower-sessions = { workspace = true }
|
tower-sessions = { workspace = true }
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
|
|
@ -21,8 +21,9 @@ password-auth = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
tracing-subscriber = { workspace = true, features = ["env-filter"] }
|
tracing-subscriber = { workspace = true, features = ["env-filter"] }
|
||||||
tera = "1.20.1"
|
tera = "1.20.1"
|
||||||
notify-debouncer-full = "0.6.0"
|
notify-debouncer-full = "0.7.0"
|
||||||
tower-livereload = "0.10.2"
|
tower-livereload = "0.10.2"
|
||||||
time = "0.3.45"
|
time = "0.3.45"
|
||||||
rand = "0.9.2"
|
rand = "0.9.2"
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
|
tower-http = { version = "0.6.8", features = ["normalize-path", "fs"] }
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ CREATE TABLE IF NOT EXISTS api_keys
|
||||||
user_id INTEGER NOT NULL REFERENCES users(id),
|
user_id INTEGER NOT NULL REFERENCES users(id),
|
||||||
token TEXT NOT NULL,
|
token TEXT NOT NULL,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
expiration_date TEXT NOT NULL,
|
expiration_date DATE NOT NULL,
|
||||||
permissions TEXT NOT NULL,
|
permissions TEXT NOT NULL,
|
||||||
revoked BOOLEAN NOT NULL
|
revoked BOOLEAN NOT NULL
|
||||||
);
|
);
|
||||||
|
|
|
||||||
18
nixie-server/package-lock.json
generated
Normal file
18
nixie-server/package-lock.json
generated
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"name": "nixie-server",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"dependencies": {
|
||||||
|
"tailwindcss": "^4.1.18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tailwindcss": {
|
||||||
|
"version": "4.1.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
|
||||||
|
"integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==",
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
5
nixie-server/package.json
Normal file
5
nixie-server/package.json
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"tailwindcss": "^4.1.18"
|
||||||
|
}
|
||||||
|
}
|
||||||
0
nixie-server/public/.gitkeep
Normal file
0
nixie-server/public/.gitkeep
Normal file
|
|
@ -20,6 +20,8 @@ use tera::Tera;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tokio::task;
|
use tokio::task;
|
||||||
use tokio::task::AbortHandle;
|
use tokio::task::AbortHandle;
|
||||||
|
use tower_http::normalize_path::NormalizePathLayer;
|
||||||
|
use tower_http::services::ServeDir;
|
||||||
use tower_livereload::LiveReloadLayer;
|
use tower_livereload::LiveReloadLayer;
|
||||||
use tower_sessions::ExpiredDeletion;
|
use tower_sessions::ExpiredDeletion;
|
||||||
use tower_sessions::SessionManagerLayer;
|
use tower_sessions::SessionManagerLayer;
|
||||||
|
|
@ -30,16 +32,22 @@ use tracing_subscriber::EnvFilter;
|
||||||
pub mod settings;
|
pub mod settings;
|
||||||
pub mod users;
|
pub mod users;
|
||||||
|
|
||||||
pub type WebResult<T> = Result<T, AppError>;
|
pub(crate) type WebResult<T> = Result<T, AppError>;
|
||||||
pub type TemplatedHtml = Html<String>;
|
pub(crate) type TemplatedHtml = Html<String>;
|
||||||
|
|
||||||
#[derive(Debug, Error, Display)]
|
#[derive(Debug, Error, Display)]
|
||||||
pub enum AppError {
|
pub(crate) enum AppError {
|
||||||
/// An error occurred while templating
|
/// An error occurred while templating
|
||||||
Tera(#[from] tera::Error),
|
Tera(#[from] tera::Error),
|
||||||
|
|
||||||
/// An error occurred while interacting with the database
|
/// An error occurred while interacting with the database
|
||||||
Sqlx(#[from] sqlx::Error),
|
Sqlx(#[from] sqlx::Error),
|
||||||
|
|
||||||
|
/// An error occurred while interacting with the sessions
|
||||||
|
Session(#[from] tower_sessions::session::Error),
|
||||||
|
|
||||||
|
/// An error ocurred while interacting with user logins
|
||||||
|
Login(#[from] axum_login::Error<Backend>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoResponse for AppError {
|
impl IntoResponse for AppError {
|
||||||
|
|
@ -51,7 +59,7 @@ impl IntoResponse for AppError {
|
||||||
Html(
|
Html(
|
||||||
TERA.read()
|
TERA.read()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.render("internal_error.tera.html", &error_context)
|
.render("internal_error.html.tera", &error_context)
|
||||||
.unwrap_or_else(|_| "ERROR RENDERING ERROR! FATAL".to_string()),
|
.unwrap_or_else(|_| "ERROR RENDERING ERROR! FATAL".to_string()),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -67,7 +75,7 @@ async fn main() -> anyhow::Result<()> {
|
||||||
type AuthSession = axum_login::AuthSession<Backend>;
|
type AuthSession = axum_login::AuthSession<Backend>;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct Backend {
|
pub(crate) struct Backend {
|
||||||
db: SqlitePool,
|
db: SqlitePool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -125,7 +133,7 @@ pub struct AppState {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static TERA: LazyLock<std::sync::RwLock<Tera>> =
|
pub static TERA: LazyLock<std::sync::RwLock<Tera>> =
|
||||||
LazyLock::new(|| Tera::new("templates/**.tera.html").unwrap().into());
|
LazyLock::new(|| Tera::new("templates/**.html.tera").unwrap().into());
|
||||||
|
|
||||||
async fn run() -> anyhow::Result<()> {
|
async fn run() -> anyhow::Result<()> {
|
||||||
tracing_subscriber::fmt()
|
tracing_subscriber::fmt()
|
||||||
|
|
@ -156,8 +164,13 @@ async fn run() -> anyhow::Result<()> {
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.merge(users::routes())
|
.merge(users::routes())
|
||||||
.merge(settings::routes())
|
.merge(settings::routes())
|
||||||
|
.nest_service(
|
||||||
|
"/assets",
|
||||||
|
ServeDir::new("public").append_index_html_on_directories(false),
|
||||||
|
)
|
||||||
.route("/", get(show_index))
|
.route("/", get(show_index))
|
||||||
.layer(auth_layer)
|
.layer(auth_layer)
|
||||||
|
.layer(NormalizePathLayer::trim_trailing_slash())
|
||||||
.layer(livereload)
|
.layer(livereload)
|
||||||
.with_state(AppState { db });
|
.with_state(AppState { db });
|
||||||
|
|
||||||
|
|
@ -193,16 +206,18 @@ async fn run() -> anyhow::Result<()> {
|
||||||
|
|
||||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
||||||
axum::serve(listener, app.into_make_service())
|
axum::serve(listener, app.into_make_service())
|
||||||
.with_graceful_shutdown(shutdown_signal(deletion_task.abort_handle()))
|
.with_graceful_shutdown(async move {
|
||||||
.await?;
|
shutdown_signal(deletion_task.abort_handle()).await;
|
||||||
|
|
||||||
debouncer.stop();
|
debouncer.stop_nonblocking();
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn show_index(renderer: Renderer) -> WebResult<Html<String>> {
|
async fn show_index(renderer: Renderer) -> WebResult<Html<String>> {
|
||||||
renderer.render_template("index.tera.html", None)
|
renderer.render_template("index.html.tera", None)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn shutdown_signal(handle: AbortHandle) {
|
async fn shutdown_signal(handle: AbortHandle) {
|
||||||
|
|
@ -233,7 +248,7 @@ pub struct Renderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Renderer {
|
impl Renderer {
|
||||||
pub fn render_template(
|
pub(crate) fn render_template(
|
||||||
&self,
|
&self,
|
||||||
name: &str,
|
name: &str,
|
||||||
context: impl Into<Option<Context>>,
|
context: impl Into<Option<Context>>,
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ use axum::routing::get;
|
||||||
use axum::routing::post;
|
use axum::routing::post;
|
||||||
use axum_login::login_required;
|
use axum_login::login_required;
|
||||||
use password_auth::generate_hash;
|
use password_auth::generate_hash;
|
||||||
|
use password_auth::verify_password;
|
||||||
use rand::distr::Alphanumeric;
|
use rand::distr::Alphanumeric;
|
||||||
use rand::distr::SampleString;
|
use rand::distr::SampleString;
|
||||||
use rand::rng;
|
use rand::rng;
|
||||||
|
|
@ -15,7 +16,6 @@ use serde::Serialize;
|
||||||
use sqlx::prelude::FromRow;
|
use sqlx::prelude::FromRow;
|
||||||
use tera::Context;
|
use tera::Context;
|
||||||
use time::Date;
|
use time::Date;
|
||||||
use time::Duration;
|
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
use time::Time;
|
use time::Time;
|
||||||
|
|
||||||
|
|
@ -37,14 +37,56 @@ pub fn routes() -> Router<AppState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn show_settings(renderer: Renderer) -> WebResult<TemplatedHtml> {
|
async fn show_settings(renderer: Renderer) -> WebResult<TemplatedHtml> {
|
||||||
renderer.render_template("settings/index.tera.html", None)
|
renderer.render_template("settings/index.html.tera", None)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn show_change_password(renderer: Renderer) -> WebResult<TemplatedHtml> {
|
async fn show_change_password(renderer: Renderer) -> WebResult<TemplatedHtml> {
|
||||||
renderer.render_template("settings/change_password.tera.html", None)
|
renderer.render_template("settings/change_password.html.tera", None)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn do_change_password() {}
|
#[derive(Deserialize)]
|
||||||
|
struct ChangePasswordForm {
|
||||||
|
old_password: String,
|
||||||
|
password: String,
|
||||||
|
confirm_password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn do_change_password(
|
||||||
|
app_state: State<AppState>,
|
||||||
|
auth: AuthSession,
|
||||||
|
change_password: Form<ChangePasswordForm>,
|
||||||
|
) -> WebResult<impl IntoResponse> {
|
||||||
|
let old_password = change_password.old_password.clone();
|
||||||
|
let hash = auth.user.as_ref().unwrap().password().to_string();
|
||||||
|
|
||||||
|
let wrong_password =
|
||||||
|
tokio::task::spawn_blocking(move || verify_password(&old_password, &hash).is_err())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
if wrong_password {
|
||||||
|
panic!("WRONG PASSWORD?");
|
||||||
|
}
|
||||||
|
|
||||||
|
if change_password.password != change_password.confirm_password {
|
||||||
|
panic!("Passwords are not equal...");
|
||||||
|
}
|
||||||
|
|
||||||
|
let hashed_password =
|
||||||
|
tokio::task::spawn_blocking(move || generate_hash(&change_password.password))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
sqlx::query("UPDATE users SET password = ? WHERE id = ?")
|
||||||
|
.bind(&hashed_password)
|
||||||
|
.bind(auth.user.unwrap().id())
|
||||||
|
.execute(&app_state.db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
auth.session.delete().await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, FromRow, Serialize)]
|
#[derive(Debug, FromRow, Serialize)]
|
||||||
pub struct ApiKey {
|
pub struct ApiKey {
|
||||||
|
|
@ -52,7 +94,7 @@ pub struct ApiKey {
|
||||||
user_id: i64,
|
user_id: i64,
|
||||||
token: Vec<u8>,
|
token: Vec<u8>,
|
||||||
name: String,
|
name: String,
|
||||||
expiration_date: OffsetDateTime,
|
expiration_date: Date,
|
||||||
permissions: String,
|
permissions: String,
|
||||||
revoked: bool,
|
revoked: bool,
|
||||||
}
|
}
|
||||||
|
|
@ -87,7 +129,7 @@ async fn show_api_keys(
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
renderer.render_template("settings/api_keys.tera.html", context)
|
renderer.render_template("settings/api_keys.html.tera", context)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use axum::Form;
|
use axum::Form;
|
||||||
use axum::http::StatusCode;
|
|
||||||
use axum::response::Html;
|
use axum::response::Html;
|
||||||
use axum::response::IntoResponse;
|
use axum::response::IntoResponse;
|
||||||
use axum::response::Redirect;
|
use axum::response::Redirect;
|
||||||
|
use tera::Context;
|
||||||
|
|
||||||
use crate::AuthSession;
|
use crate::AuthSession;
|
||||||
use crate::Renderer;
|
use crate::Renderer;
|
||||||
|
|
@ -10,22 +10,33 @@ use crate::UserCredentials;
|
||||||
use crate::WebResult;
|
use crate::WebResult;
|
||||||
|
|
||||||
pub async fn show_login(renderer: Renderer) -> WebResult<Html<String>> {
|
pub async fn show_login(renderer: Renderer) -> WebResult<Html<String>> {
|
||||||
renderer.render_template("users/login.tera.html", None)
|
renderer.render_template("users/login.html.tera", None)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn do_login(
|
pub async fn do_login(
|
||||||
mut auth_session: AuthSession,
|
mut auth_session: AuthSession,
|
||||||
|
renderer: Renderer,
|
||||||
Form(creds): Form<UserCredentials>,
|
Form(creds): Form<UserCredentials>,
|
||||||
) -> impl IntoResponse {
|
) -> WebResult<impl IntoResponse> {
|
||||||
let user = match auth_session.authenticate(creds.clone()).await {
|
let user = match auth_session.authenticate(creds.clone()).await? {
|
||||||
Ok(Some(user)) => user,
|
Some(user) => user,
|
||||||
Ok(None) => return StatusCode::UNAUTHORIZED.into_response(),
|
None => {
|
||||||
Err(_) => return StatusCode::INTERNAL_SERVER_ERROR.into_response(),
|
let mut context = Context::new();
|
||||||
|
|
||||||
|
context.insert(
|
||||||
|
"form",
|
||||||
|
&serde_json::json! {{
|
||||||
|
"username": creds.username
|
||||||
|
}},
|
||||||
|
);
|
||||||
|
|
||||||
|
return Ok(renderer
|
||||||
|
.render_template("users/login.html.tera", context)
|
||||||
|
.into_response());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if auth_session.login(&user).await.is_err() {
|
auth_session.login(&user).await?;
|
||||||
return StatusCode::INTERNAL_SERVER_ERROR.into_response();
|
|
||||||
}
|
|
||||||
|
|
||||||
Redirect::to("/protected").into_response()
|
Ok(Redirect::to("/protected").into_response())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ use crate::UserCredentials;
|
||||||
use crate::WebResult;
|
use crate::WebResult;
|
||||||
|
|
||||||
pub async fn show_register(renderer: Renderer) -> WebResult<Html<String>> {
|
pub async fn show_register(renderer: Renderer) -> WebResult<Html<String>> {
|
||||||
renderer.render_template("users/register.tera.html", None)
|
renderer.render_template("users/register.html.tera", None)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn do_register(
|
pub async fn do_register(
|
||||||
|
|
|
||||||
1
nixie-server/templates/base.css
Normal file
1
nixie-server/templates/base.css
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
@import "tailwindcss";
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
<link rel="stylesheet" href="/assets/style.css" />
|
||||||
<script type="module" src="https://cdn.jsdelivr.net/gh/starfederation/datastar@1.0.0-RC.7/bundles/datastar.js"></script>
|
<script type="module" src="https://cdn.jsdelivr.net/gh/starfederation/datastar@1.0.0-RC.7/bundles/datastar.js"></script>
|
||||||
{% block head %}
|
{% block head %}
|
||||||
<title>{% block title %}{% endblock title%} - Nixie CI</title>
|
<title>{% block title %}{% endblock title%} - Nixie CI</title>
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="min-h-screen flex flex-col">
|
<body class="min-h-screen flex flex-col">
|
||||||
<nav class="bg-orange-300 px-2 py-2 inset-shadow-2xs border-b-2 border-orange-900">
|
<nav class="bg-orange-200 px-2 py-2 inset-shadow-2xs border-b-2 border-orange-900">
|
||||||
<div class="mx-auto container flex select-none space-x-4">
|
<div class="mx-auto container flex select-none space-x-4">
|
||||||
<a href="/" class="outline-2 outline-gray-800 bg-gray-700 rounded text-white p-2">
|
<a href="/" class="outline-2 outline-gray-800 bg-gray-700 rounded text-white p-2">
|
||||||
<span class="text-green-200">>_</span>
|
<span class="text-green-200">>_</span>
|
||||||
|
|
@ -27,13 +27,13 @@
|
||||||
<a href="/register" class="outline-2 hover:outline-emerald-900 outline-blue-700 p-2 select-none rounded-r bg-blue-600 text-white">Register</a>
|
<a href="/register" class="outline-2 hover:outline-emerald-900 outline-blue-700 p-2 select-none rounded-r bg-blue-600 text-white">Register</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="relative flex" data-on:click__outside="$openProfileDropdown = false">
|
<div class="relative flex" data-on:click__outside="$openProfileDropdown = false">
|
||||||
<a href="/users/{{ current_user.id }}" class="inline-block border-2 border-r-1 hover:border-emerald-900 border-emerald-700 p-2 select-none rounded-l bg-emerald-600 text-white">
|
<a href="/users/{{ current_user.id }}" class="inline-block border-2 border-r hover:border-emerald-900 border-emerald-700 p-2 select-none rounded-l bg-emerald-600 text-white">
|
||||||
{{ current_user.username }}
|
{{ current_user.username }}
|
||||||
</a>
|
</a>
|
||||||
<button data-on:click="$openProfileDropdown = !$openProfileDropdown"
|
<button data-on:click="$openProfileDropdown = !$openProfileDropdown"
|
||||||
class="border-2 border-l-1 hover:border-emerald-900 border-emerald-700 p-2 select-none rounded-r bg-emerald-600 text-white cursor-pointer">▼</button>
|
class="border-2 border-l hover:border-emerald-900 border-emerald-700 p-2 select-none rounded-r bg-emerald-600 text-white cursor-pointer">▼</button>
|
||||||
<div data-class:hidden="!$openProfileDropdown"
|
<div data-class:flex="$openProfileDropdown" data-class:hidden="!$openProfileDropdown"
|
||||||
class="hidden absolute top-[calc(100%+0.4rem)] right-0 bg-orange-300 p-2 rounded-b border border-orange-800 shadow-lg md:min-w-40 flex flex-col divide-y divide-orange-800">
|
class="hidden absolute top-[calc(100%+0.4rem)] right-0 bg-orange-300 p-2 rounded-b border border-orange-800 shadow-lg md:min-w-40 flex-col divide-y divide-orange-800">
|
||||||
<a class="block p-2 hover:bg-orange-800 hover:text-white" href="/users/{{ current_user.id }}">Profile</a>
|
<a class="block p-2 hover:bg-orange-800 hover:text-white" href="/users/{{ current_user.id }}">Profile</a>
|
||||||
<a class="block p-2 hover:bg-orange-800 hover:text-white" href="/logout">Logout</a>
|
<a class="block p-2 hover:bg-orange-800 hover:text-white" href="/logout">Logout</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends "base.tera.html" %}
|
{% extends "base.html.tera" %}
|
||||||
{% import "inputs.tera.html" as inputs %}
|
{% import "inputs.html.tera" as inputs %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
Home
|
Home
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends "base.tera.html" %}
|
{% extends "base.html.tera" %}
|
||||||
{% import "inputs.tera.html" as inputs %}
|
{% import "inputs.html.tera" as inputs %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
Home
|
Home
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "base.tera.html" %}
|
{% extends "base.html.tera" %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
Protected Page
|
Protected Page
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends "base.tera.html" %}
|
{% extends "base.html.tera" %}
|
||||||
{% import "inputs.tera.html" as inputs %}
|
{% import "inputs.html.tera" as inputs %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
API Keys - {{ current_user.username }}
|
API Keys - {{ current_user.username }}
|
||||||
|
|
@ -8,7 +8,7 @@ API Keys - {{ current_user.username }}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="flex flex-col md:flex-row grow">
|
<div class="flex flex-col md:flex-row grow">
|
||||||
<div class="basis-1/4 p-4 m-4 space-y-4">
|
<div class="basis-1/4 p-4 m-4 space-y-4">
|
||||||
{% include "settings/sidebar.tera.html" %}
|
{% include "settings/sidebar.html.tera" %}
|
||||||
</div>
|
</div>
|
||||||
<div class="basis-3/4 p-4">
|
<div class="basis-3/4 p-4">
|
||||||
<h1 class="font-bold text-3xl">API Keys</h1>
|
<h1 class="font-bold text-3xl">API Keys</h1>
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends "base.tera.html" %}
|
{% extends "base.html.tera" %}
|
||||||
{% import "inputs.tera.html" as inputs %}
|
{% import "inputs.html.tera" as inputs %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
Change Password - {{ current_user.username }}
|
Change Password - {{ current_user.username }}
|
||||||
|
|
@ -8,7 +8,7 @@ Change Password - {{ current_user.username }}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="flex flex-col md:flex-row grow">
|
<div class="flex flex-col md:flex-row grow">
|
||||||
<div class="basis-1/4 p-4 m-4 space-y-4">
|
<div class="basis-1/4 p-4 m-4 space-y-4">
|
||||||
{% include "settings/sidebar.tera.html" %}
|
{% include "settings/sidebar.html.tera" %}
|
||||||
</div>
|
</div>
|
||||||
<div class="basis-3/4 p-4">
|
<div class="basis-3/4 p-4">
|
||||||
<h1 class="font-bold text-3xl">Change Password</h1>
|
<h1 class="font-bold text-3xl">Change Password</h1>
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends "base.tera.html" %}
|
{% extends "base.html.tera" %}
|
||||||
{% import "inputs.tera.html" as inputs %}
|
{% import "inputs.html.tera" as inputs %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
Settings - {{ current_user.username }}
|
Settings - {{ current_user.username }}
|
||||||
|
|
@ -8,7 +8,7 @@ Settings - {{ current_user.username }}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="flex flex-col md:flex-row grow">
|
<div class="flex flex-col md:flex-row grow">
|
||||||
<div class="basis-1/4 p-4 m-4 space-y-4">
|
<div class="basis-1/4 p-4 m-4 space-y-4">
|
||||||
{% include "settings/sidebar.tera.html" %}
|
{% include "settings/sidebar.html.tera" %}
|
||||||
</div>
|
</div>
|
||||||
<div class="basis-3/4 p-4">
|
<div class="basis-3/4 p-4">
|
||||||
<p>THIS IS YOUR MAIN SETTINGS WOOHOOOO</p>
|
<p>THIS IS YOUR MAIN SETTINGS WOOHOOOO</p>
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends "base.tera.html" %}
|
{% extends "base.html.tera" %}
|
||||||
{% import "inputs.tera.html" as inputs %}
|
{% import "inputs.html.tera" as inputs %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
Login
|
Login
|
||||||
|
|
@ -10,7 +10,7 @@ Login
|
||||||
<div class="border-2 border-zinc-300 sm:rounded-2xl lg:rounded-4xl my-2 px-20 pb-14 pt-10 sm:shadow-lg space-y-4">
|
<div class="border-2 border-zinc-300 sm:rounded-2xl lg:rounded-4xl my-2 px-20 pb-14 pt-10 sm:shadow-lg space-y-4">
|
||||||
<h1 class="font-bold text-3xl">Login</h1>
|
<h1 class="font-bold text-3xl">Login</h1>
|
||||||
<form action="/login" method="POST" class="space-y-4">
|
<form action="/login" method="POST" class="space-y-4">
|
||||||
{{ inputs::text_input(label="Username", name="username", id="username") }}
|
{{ inputs::text_input(label="Username", name="username", id="username", value=form.username | default(value="")) }}
|
||||||
{{ inputs::text_input(label="Password", name="password", id="password", type="password") }}
|
{{ inputs::text_input(label="Password", name="password", id="password", type="password") }}
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<button type="submit" class="bg-blue-500 p-2 rounded-lg text-white">
|
<button type="submit" class="bg-blue-500 p-2 rounded-lg text-white">
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends "base.tera.html" %}
|
{% extends "base.html.tera" %}
|
||||||
{% import "inputs.tera.html" as inputs %}
|
{% import "inputs.html.tera" as inputs %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
Register
|
Register
|
||||||
Loading…
Add table
Add a link
Reference in a new issue