aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/config.rs1
-rw-r--r--src/errors.rs6
-rw-r--r--src/handlers/auth.rs79
-rw-r--r--src/handlers/mod.rs16
-rw-r--r--src/main.rs3
-rw-r--r--src/models.rs12
6 files changed, 116 insertions, 1 deletions
diff --git a/src/config.rs b/src/config.rs
index 9bacc6a..4ceba75 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -14,6 +14,7 @@ pub struct Config {
#[derive(Deserialize)]
pub struct ServerConfig {
pub bind_addr: String,
+ pub admin_email: String
}
#[derive(Deserialize, Clone)]
diff --git a/src/errors.rs b/src/errors.rs
index 19e0a8a..23ad8fa 100644
--- a/src/errors.rs
+++ b/src/errors.rs
@@ -14,7 +14,11 @@ pub enum ServiceError {
#[error("Not Found")]
NotFound,
#[error("Axum: {0}")]
- Axum(#[from] axum::Error)
+ Axum(#[from] axum::Error),
+ #[error("SQL: {0}")]
+ Sql(#[from] sqlx::Error),
+ #[error("Bcrypt: {0}")]
+ Bcrypt(#[from] bcrypt::BcryptError),
}
pub type StringResult<T = String> = Result<T, ServiceError>;
diff --git a/src/handlers/auth.rs b/src/handlers/auth.rs
new file mode 100644
index 0000000..c4672aa
--- /dev/null
+++ b/src/handlers/auth.rs
@@ -0,0 +1,79 @@
+use std::sync::Arc;
+
+use axum::{response::{IntoResponse, Html, Redirect}, Form, Extension};
+use axum_extra::extract::{PrivateCookieJar, cookie::Cookie};
+use serde::Deserialize;
+use sqlx::{query, query_as};
+use tracing::debug;
+use crate::{errors::ServiceError, State, models::DbUser};
+use chrono::prelude::*;
+
+#[derive(Deserialize, Debug)]
+pub struct RegisterForm {
+ pub email: String,
+ pub prefname: String,
+ pub password: String,
+ #[serde(rename="password-confirm")]
+ pub password_confirm: String
+}
+
+#[derive(Deserialize)]
+pub struct LoginForm {
+ pub email: String,
+ pub password: String,
+}
+
+pub async fn login() -> impl IntoResponse {
+ let mut buf = Vec::new();
+ crate::templates::login_html(&mut buf).unwrap();
+
+ Html(buf)
+}
+
+pub async fn login_post(Form(login): Form<LoginForm>, state: Extension<Arc<State>>, jar: PrivateCookieJar) -> Result<(PrivateCookieJar, Redirect), ServiceError> {
+ let mut conn = state.conn.acquire().await?;
+
+ let user: DbUser = query_as("SELECT * FROM users WHERE email=$1").bind(login.email)
+ .fetch_one(&mut conn)
+ .await?;
+
+ if bcrypt::verify(login.password, &user.pw_hash)? {
+ debug!("Logged in ID {} (email {})", user.id, user.email);
+ query("UPDATE users SET last_login=$1 WHERE id=$2").bind(Utc::now()).bind(user.id)
+ .execute(&mut conn)
+ .await?;
+
+ let updated_jar = jar.add(Cookie::new("user-id", user.id.clone()));
+ } else {
+
+ }
+ Ok((updated_jar, Redirect::to("/")))
+}
+
+pub async fn register() -> impl IntoResponse {
+ let mut buf = Vec::new();
+ crate::templates::register_html(&mut buf).unwrap();
+
+ Html(buf)
+}
+
+pub async fn register_post(Form(reg): Form<RegisterForm>, state: Extension<Arc<State>>) -> impl IntoResponse {
+ if reg.password_confirm == reg.password {
+ let hash = match bcrypt::hash(reg.password, bcrypt::DEFAULT_COST) {
+ Ok(h) => h,
+ Err(e) => {
+ return Err(ServiceError::NotAuthorized);
+ }
+ };
+
+ let mut conn = state.conn.acquire().await?;
+ let ulid = ulid::Ulid::new();
+
+ query("INSERT INTO users (id, email, pref_name, pw_hash, last_login) VALUES ($1, $2, $3, $4, $5)").bind(ulid.to_string()).bind(reg.email.clone()).bind(reg.prefname).bind(hash).bind(Utc::now())
+ .execute(&mut conn)
+ .await?;
+
+ }
+
+ Ok(Redirect::to("/dash/auth/login"))
+}
diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs
new file mode 100644
index 0000000..4076e68
--- /dev/null
+++ b/src/handlers/mod.rs
@@ -0,0 +1,16 @@
+use axum::{Router, routing::get};
+
+mod auth;
+
+pub async fn gen_routers() -> Router {
+
+ Router::new()
+ .nest("/auth", auth_routes().await)
+}
+
+async fn auth_routes() -> Router {
+
+ Router::new()
+ .route("/login", get(auth::login).post(auth::login_post))
+ .route("/register", get(auth::register).post(auth::register_post))
+}
diff --git a/src/main.rs b/src/main.rs
index c4a298f..ed943c7 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,5 +1,7 @@
mod config;
mod errors;
+mod handlers;
+mod models;
use std::{net::SocketAddr, str::FromStr, sync::Arc, time::Duration};
@@ -50,6 +52,7 @@ async fn main() {
let app = Router::new()
.route("/health", get(health_check))
.route("/", get(index))
+ .nest("/dash", handlers::gen_routers().await)
.route("/static/:name", get(statics))
.layer(
ServiceBuilder::new()
diff --git a/src/models.rs b/src/models.rs
new file mode 100644
index 0000000..b367f4f
--- /dev/null
+++ b/src/models.rs
@@ -0,0 +1,12 @@
+use sqlx::FromRow;
+
+
+
+#[derive(Debug, FromRow)]
+pub struct DbUser {
+ pub id: String,
+ pub email: String,
+ pub pref_name: String,
+ pub pw_hash: String,
+ pub last_login: chrono::NaiveDateTime
+}