From a00671115e154fed800759434983d697498e6c9d Mon Sep 17 00:00:00 2001 From: Nettika Date: Sun, 25 Jan 2026 16:02:13 -0800 Subject: [PATCH] Implement logging in --- TODO.md | 2 +- src/main.rs | 43 +++++++++++++++++++++++++++++++++++++++---- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/TODO.md b/TODO.md index b48eaac..41ebbeb 100644 --- a/TODO.md +++ b/TODO.md @@ -13,7 +13,7 @@ [x] Add .env file support using the dotenv crate. Load environment variables on server startup. [x] Create a `GET /login` endpoint that returns a simple HTML login form (username and password fields). [x] Add a rouille::session manager to set a session cookie. -[ ] Create a `POST /login` endpoint that validates credentials against USERNAME and PASSWORD environment variables. +[x] Create a `POST /login` endpoint that validates credentials against USERNAME and PASSWORD environment variables. [ ] Protect write endpoints (POST /projects, and any future write operations) with authentication. Redirect to /login if not authenticated. [ ] Add a login button to the front page (GET /projects) that links to /login. [ ] When logged in, show a "Create Project" button on the front page that links to /new-project. diff --git a/src/main.rs b/src/main.rs index 5002a5d..3b9c33d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,7 @@ use maud::{DOCTYPE, html}; -use rouille::{router, try_or_400, session}; +use rouille::{router, session, try_or_400}; +use std::collections::HashSet; +use std::sync::Mutex; mod db; mod project; @@ -13,10 +15,13 @@ fn main() { println!("Starting server on localhost:8080"); + // Track authenticated sessions + let authenticated_sessions: Mutex> = Mutex::new(HashSet::new()); + rouille::start_server("localhost:8080", move |request| { // Wrap all requests with session management // This sets a session cookie with a 1-hour timeout - session::session(request, "SESSION", 3600, |_session| { + session::session(request, "SESSION", 3600, |session| { router!(request, (GET) ["/"] => { rouille::Response::redirect_302("/projects") @@ -32,6 +37,7 @@ fn main() { (GET) ["/projects"] => display_projects(), (GET) ["/new-project"] => new_project_form(), (GET) ["/login"] => login_form(), + (POST) ["/login"] => handle_login(request, session, &authenticated_sessions), (POST) ["/projects"] => create_project(request), _ => rouille::Response::empty_404() ) @@ -47,10 +53,12 @@ fn display_projects() -> rouille::Response { .unwrap() .as_secs() as i64; - let active_projects: Vec<_> = projects.iter() + let active_projects: Vec<_> = projects + .iter() .filter(|p| !p.archived && (now - p.last_modified_time) <= STALE_AGE) .collect(); - let stale_projects: Vec<_> = projects.iter() + let stale_projects: Vec<_> = projects + .iter() .filter(|p| !p.archived && (now - p.last_modified_time) > STALE_AGE) .collect(); let archived_projects: Vec<_> = projects.iter().filter(|p| p.archived).collect(); @@ -167,3 +175,30 @@ fn create_project(request: &rouille::Request) -> rouille::Response { Err(_) => rouille::Response::text("Failed to create project").with_status_code(500), } } + +fn handle_login( + request: &rouille::Request, + session: &session::Session, + authenticated_sessions: &Mutex>, +) -> rouille::Response { + let input = try_or_400!(rouille::post_input!(request, { + username: String, + password: String, + })); + + // Get credentials from environment variables + let valid_username = std::env::var("USERNAME").unwrap_or_default(); + let valid_password = std::env::var("PASSWORD").unwrap_or_default(); + + // Validate credentials + if input.username == valid_username && input.password == valid_password { + // Add session ID to authenticated sessions + if let Ok(mut sessions) = authenticated_sessions.lock() { + sessions.insert(session.id().to_string()); + } + rouille::Response::redirect_302("/projects") + } else { + // Invalid credentials - redirect back to login + rouille::Response::redirect_302("/login") + } +}