167 lines
5.3 KiB
Rust
167 lines
5.3 KiB
Rust
use axum::{
|
|
Router,
|
|
extract::State,
|
|
routing::{delete, get, post, put},
|
|
};
|
|
use axum_extra::extract::Form;
|
|
use maud::{Markup, html};
|
|
use serde::Deserialize;
|
|
use serde_json::json;
|
|
use short_uuid::ShortUuid;
|
|
|
|
use super::Layout;
|
|
use crate::models::user::{AuthSession, Credentials};
|
|
use crate::{AppError, AppState};
|
|
|
|
pub fn router() -> Router<AppState> {
|
|
Router::new()
|
|
.route("/settings", get(self::get::settings))
|
|
.route("/settings/ics_path", post(self::post::ics_path))
|
|
.route("/settings/ics_path", delete(self::delete::ics_path))
|
|
.route("/password", put(self::put::password))
|
|
}
|
|
|
|
fn calendar_link(path: Option<String>) -> Markup {
|
|
if let Some(path) = path {
|
|
html! {
|
|
#cal-link x-data=(json!({ "path": path })) hx-target="this" hx-swap="outerHTML" {
|
|
a x-bind:href="window.location.origin + '/cal/' + path" {
|
|
span x-text="window.location.origin + '/cal/'" {}
|
|
span { (path) }
|
|
}
|
|
p {
|
|
"Warning: These actions unrecoverably change your calendar's URL."
|
|
}
|
|
button hx-post="/settings/ics_path" { "Regenerate path" }
|
|
button hx-delete="/settings/ics_path" { "Destroy calendar" }
|
|
}
|
|
}
|
|
} else {
|
|
html! {
|
|
#cal-link hx-target="this" hx-swap="outerHTML" {
|
|
div { "Birthdays calendar is disabled." }
|
|
button hx-post="/settings/ics_path" { "Enable calendar" }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
mod get {
|
|
use super::*;
|
|
|
|
pub async fn settings(
|
|
auth_session: AuthSession,
|
|
State(state): State<AppState>,
|
|
layout: Layout,
|
|
) -> Result<Markup, AppError> {
|
|
let pool = &state.db(&auth_session.user.unwrap()).pool;
|
|
let ics_path: (Option<String>,) = sqlx::query_as("select ics_path from settings")
|
|
.fetch_one(pool)
|
|
.await?;
|
|
let ics_path: Option<String> = ics_path.0;
|
|
|
|
Ok(layout.render(
|
|
Some(vec!["static/settings.css"]),
|
|
html! {
|
|
h2 { "Birthdays Calendar URL" }
|
|
(calendar_link(ics_path))
|
|
|
|
h2 { "Change Password" }
|
|
form x-data="{ old_p: '', new_p: '', confirm: '' }" hx-put="/password"
|
|
hx-on::after-request="if(event.detail.successful) { this.reset(); setTimeout(() => window.location.reload(), 5000); }"
|
|
hx-target="this" hx-target-error="this" hx-swap="beforeend" {
|
|
label for="old" { "Current password:" }
|
|
input id="old" name="current" x-model="old_p" type="password";
|
|
|
|
label for="new" { "New password:" }
|
|
input id="new" name="new_password" x-model="new_p" type="password";
|
|
|
|
label for="confirm" { "Confirm:" }
|
|
input id="confirm" x-model="confirm" type="password";
|
|
|
|
button type="submit" x-bind:disabled="!(new_p.length && new_p === confirm)" { "Submit" }
|
|
.error x-show="new_p.length && confirm.length && new_p !== confirm" {
|
|
"Passwords do not match"
|
|
}
|
|
}
|
|
},
|
|
))
|
|
}
|
|
}
|
|
|
|
mod post {
|
|
use super::*;
|
|
|
|
pub async fn ics_path(
|
|
auth_session: AuthSession,
|
|
State(state): State<AppState>,
|
|
) -> Result<Markup, AppError> {
|
|
let user = auth_session.user.unwrap();
|
|
let pool = &state.db(&user).pool;
|
|
|
|
let ics_path = format!("{}-{}.ics", &user.username, ShortUuid::generate());
|
|
|
|
sqlx::query!("update settings set ics_path=$1", ics_path)
|
|
.execute(pool)
|
|
.await?;
|
|
|
|
Ok(calendar_link(Some(ics_path)))
|
|
}
|
|
}
|
|
|
|
mod delete {
|
|
use super::*;
|
|
|
|
pub async fn ics_path(
|
|
auth_session: AuthSession,
|
|
State(state): State<AppState>,
|
|
) -> Result<Markup, AppError> {
|
|
let pool = &state.db(&auth_session.user.unwrap()).pool;
|
|
|
|
sqlx::query!("update settings set ics_path=null")
|
|
.execute(pool)
|
|
.await?;
|
|
|
|
Ok(calendar_link(None))
|
|
}
|
|
}
|
|
|
|
mod put {
|
|
use super::*;
|
|
|
|
#[derive(Deserialize)]
|
|
pub struct PassChange {
|
|
current: String,
|
|
new_password: String,
|
|
}
|
|
|
|
pub async fn password(
|
|
auth_session: AuthSession,
|
|
Form(payload): Form<PassChange>,
|
|
) -> Result<Markup, AppError> {
|
|
let username = auth_session.user.as_ref().unwrap().username.clone();
|
|
|
|
tracing::debug!("Resetting password for {}...", username);
|
|
|
|
let current_creds = Credentials {
|
|
username: username.clone(),
|
|
password: payload.current,
|
|
};
|
|
|
|
let new_creds = Credentials {
|
|
username: username,
|
|
password: payload.new_password,
|
|
};
|
|
|
|
match auth_session.authenticate(current_creds).await {
|
|
Err(_) => Ok(html! { .error { "Server error; could not verify authentication." } }),
|
|
Ok(None) => Ok(html! { .error { "Current password is incorrect." } }),
|
|
Ok(Some(_)) => {
|
|
auth_session.backend.set_password(new_creds).await?;
|
|
Ok(html! { .msg {
|
|
"Password changed successfully. Redirecting to login page after 5 seconds..."
|
|
} })
|
|
}
|
|
}
|
|
}
|
|
}
|