105 lines
3 KiB
Rust
105 lines
3 KiB
Rust
mod users;
|
|
|
|
use axum::extract::Extension;
|
|
use axum::response::Html;
|
|
use axum::response::IntoResponse;
|
|
use axum::response::Redirect;
|
|
use axum::response::Response;
|
|
use axum::{
|
|
routing::{get, post},
|
|
Router,
|
|
};
|
|
use axum_sessions::extractors::ReadableSession;
|
|
use axum_sessions::{async_session::MemoryStore, SessionLayer};
|
|
use sqlx::postgres::PgPoolOptions;
|
|
use std::sync::Arc;
|
|
use tera::Tera;
|
|
use thiserror::Error as ThisError;
|
|
use tower::ServiceBuilder;
|
|
|
|
#[derive(ThisError, Debug)]
|
|
pub enum Error {
|
|
Database(#[from] sqlx::Error),
|
|
Tera(#[from] tera::Error),
|
|
Tokio(#[from] tokio::task::JoinError),
|
|
Pbkdf2(pbkdf2::password_hash::Error),
|
|
Session(#[from] axum_sessions::async_session::serde_json::Error),
|
|
}
|
|
|
|
impl std::fmt::Display for Error {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "{:#?}", self)
|
|
}
|
|
}
|
|
|
|
impl IntoResponse for Error {
|
|
fn into_response(self) -> Response {
|
|
self.to_string().into_response()
|
|
}
|
|
}
|
|
|
|
// We need to implement this manually because pbkdf2's Error type does not meet the bounds required
|
|
// for `thiserror`
|
|
impl From<pbkdf2::password_hash::Error> for Error {
|
|
fn from(e: pbkdf2::password_hash::Error) -> Self {
|
|
Self::Pbkdf2(e)
|
|
}
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<(), Error> {
|
|
let pool = Arc::new(
|
|
PgPoolOptions::new()
|
|
.connect("postgresql://tmtd@192.168.1.133/tmtd")
|
|
.await?,
|
|
);
|
|
|
|
sqlx::query("create table if not exists users(id serial primary key, username text not null unique, password_hash text not null)").execute(&*pool).await?;
|
|
|
|
let mut tera = Arc::new(Tera::new("templates/**/*.html")?);
|
|
|
|
{
|
|
Arc::get_mut(&mut tera).unwrap().full_reload()?;
|
|
}
|
|
|
|
let store = MemoryStore::new();
|
|
// TODO: make this configurable
|
|
let secret = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
|
|
let session_layer = SessionLayer::new(store, secret).with_cookie_name("2m2d_session");
|
|
|
|
let app = Router::new()
|
|
.route("/", get(homepage))
|
|
.route("/register", post(users::create_user))
|
|
.route("/register", get(users::register_form))
|
|
.route("/login", get(users::login_form))
|
|
.route("/login", post(users::login_backend))
|
|
.layer(
|
|
ServiceBuilder::new()
|
|
.layer(Extension(pool))
|
|
.layer(Extension(tera))
|
|
.layer(session_layer),
|
|
);
|
|
|
|
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
|
.serve(app.into_make_service())
|
|
.await
|
|
.unwrap();
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn homepage(
|
|
session: ReadableSession,
|
|
Extension(tera): Extension<Arc<Tera>>,
|
|
) -> Result<Html<String>, Response> {
|
|
if let Some(_username) = session.get::<String>("logged_in_as") {
|
|
let rendered = tera
|
|
.render("home.html", &tera::Context::default())
|
|
.map_err(|e| Error::from(e).into_response())?;
|
|
|
|
return Ok(Html(rendered));
|
|
}
|
|
|
|
Err(Redirect::to("/login").into_response())
|
|
}
|