added account

This commit is contained in:
2025-10-25 15:55:25 +02:00
parent 72c50549d7
commit 4db5084bc6
15 changed files with 1214 additions and 150 deletions

300
handlers/auth.go Normal file
View File

@@ -0,0 +1,300 @@
package handlers
import (
"database/sql"
"html/template"
"mealprep/auth"
"mealprep/database"
"net/http"
"strings"
"time"
)
// LoginPageHandler shows the login page
func LoginPageHandler(w http.ResponseWriter, r *http.Request) {
tmpl := `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - Meal Prep Planner</title>
<link rel="stylesheet" href="/static/styles.css">
</head>
<body>
<div class="auth-container">
<div class="auth-card">
<h1>🍽️ Meal Prep Planner</h1>
<h2>Login</h2>
{{if .Error}}
<div class="error-message">{{.Error}}</div>
{{end}}
<form method="POST" action="/login" class="auth-form">
<div class="form-group">
<label>Email:</label>
<input type="email" name="email" required autofocus />
</div>
<div class="form-group">
<label>Password:</label>
<input type="password" name="password" required />
</div>
<button type="submit" class="btn-primary">Login</button>
</form>
<p class="auth-link">
Don't have an account? <a href="/register">Register here</a>
</p>
</div>
</div>
</body>
</html>
`
data := struct {
Error string
}{
Error: r.URL.Query().Get("error"),
}
t := template.Must(template.New("login").Parse(tmpl))
t.Execute(w, data)
}
// RegisterPageHandler shows the registration page
func RegisterPageHandler(w http.ResponseWriter, r *http.Request) {
tmpl := `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Register - Meal Prep Planner</title>
<link rel="stylesheet" href="/static/styles.css">
</head>
<body>
<div class="auth-container">
<div class="auth-card">
<h1>🍽️ Meal Prep Planner</h1>
<h2>Create Account</h2>
{{if .Error}}
<div class="error-message">{{.Error}}</div>
{{end}}
<form method="POST" action="/register" class="auth-form">
<div class="form-group">
<label>Email:</label>
<input type="email" name="email" required autofocus />
</div>
<div class="form-group">
<label>Password (min 8 characters):</label>
<input type="password" name="password" required minlength="8" />
</div>
<div class="form-group">
<label>Confirm Password:</label>
<input type="password" name="password_confirm" required minlength="8" />
</div>
<button type="submit" class="btn-primary">Create Account</button>
</form>
<p class="auth-link">
Already have an account? <a href="/login">Login here</a>
</p>
</div>
</div>
</body>
</html>
`
data := struct {
Error string
}{
Error: r.URL.Query().Get("error"),
}
t := template.Must(template.New("register").Parse(tmpl))
t.Execute(w, data)
}
// LoginHandler processes login requests
func LoginHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
if err := r.ParseForm(); err != nil {
http.Redirect(w, r, "/login?error=Invalid+request", http.StatusSeeOther)
return
}
email := strings.TrimSpace(strings.ToLower(r.FormValue("email")))
password := r.FormValue("password")
// Validate input
if email == "" || password == "" {
http.Redirect(w, r, "/login?error=Email+and+password+required", http.StatusSeeOther)
return
}
if !auth.ValidateEmail(email) {
http.Redirect(w, r, "/login?error=Invalid+email+format", http.StatusSeeOther)
return
}
// Get user from database
user, err := database.GetUserByEmail(email)
if err != nil {
if err == sql.ErrNoRows {
http.Redirect(w, r, "/login?error=Invalid+credentials", http.StatusSeeOther)
return
}
http.Redirect(w, r, "/login?error=Server+error", http.StatusSeeOther)
return
}
// Check password
if !auth.CheckPassword(password, user.PasswordHash) {
http.Redirect(w, r, "/login?error=Invalid+credentials", http.StatusSeeOther)
return
}
// Create session
token, err := auth.GenerateSessionToken()
if err != nil {
http.Redirect(w, r, "/login?error=Failed+to+create+session", http.StatusSeeOther)
return
}
expiresAt := auth.GetSessionExpiry()
if err := database.CreateSession(token, user.ID, expiresAt); err != nil {
http.Redirect(w, r, "/login?error=Failed+to+create+session", http.StatusSeeOther)
return
}
// Set secure cookie
http.SetCookie(w, &http.Cookie{
Name: "session_token",
Value: token,
Expires: expiresAt,
HttpOnly: true,
Secure: false, // Set to true in production with HTTPS
SameSite: http.SameSiteStrictMode,
Path: "/",
})
// Redirect to home
http.Redirect(w, r, "/", http.StatusSeeOther)
}
// RegisterHandler processes registration requests
func RegisterHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Redirect(w, r, "/register", http.StatusSeeOther)
return
}
if err := r.ParseForm(); err != nil {
http.Redirect(w, r, "/register?error=Invalid+request", http.StatusSeeOther)
return
}
email := strings.TrimSpace(strings.ToLower(r.FormValue("email")))
password := r.FormValue("password")
passwordConfirm := r.FormValue("password_confirm")
// Validate input
if email == "" || password == "" || passwordConfirm == "" {
http.Redirect(w, r, "/register?error=All+fields+required", http.StatusSeeOther)
return
}
if !auth.ValidateEmail(email) {
http.Redirect(w, r, "/register?error=Invalid+email+format", http.StatusSeeOther)
return
}
if password != passwordConfirm {
http.Redirect(w, r, "/register?error=Passwords+do+not+match", http.StatusSeeOther)
return
}
if len(password) < 8 {
http.Redirect(w, r, "/register?error=Password+must+be+at+least+8+characters", http.StatusSeeOther)
return
}
// Check if user exists
existingUser, _ := database.GetUserByEmail(email)
if existingUser != nil {
http.Redirect(w, r, "/register?error=Email+already+registered", http.StatusSeeOther)
return
}
// Hash password
passwordHash, err := auth.HashPassword(password)
if err != nil {
http.Redirect(w, r, "/register?error=Failed+to+create+account", http.StatusSeeOther)
return
}
// Create user
userID, err := database.CreateUser(email, passwordHash)
if err != nil {
http.Redirect(w, r, "/register?error=Failed+to+create+account", http.StatusSeeOther)
return
}
// Create session
token, err := auth.GenerateSessionToken()
if err != nil {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
expiresAt := auth.GetSessionExpiry()
if err := database.CreateSession(token, int(userID), expiresAt); err != nil {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
// Set secure cookie
http.SetCookie(w, &http.Cookie{
Name: "session_token",
Value: token,
Expires: expiresAt,
HttpOnly: true,
Secure: false, // Set to true in production with HTTPS
SameSite: http.SameSiteStrictMode,
Path: "/",
})
// Redirect to home
http.Redirect(w, r, "/", http.StatusSeeOther)
}
// LogoutHandler processes logout requests
func LogoutHandler(w http.ResponseWriter, r *http.Request) {
// Get session cookie
cookie, err := r.Cookie("session_token")
if err == nil {
// Delete session from database
database.DeleteSession(cookie.Value)
}
// Clear cookie
http.SetCookie(w, &http.Cookie{
Name: "session_token",
Value: "",
Expires: time.Unix(0, 0),
HttpOnly: true,
Secure: false,
SameSite: http.SameSiteStrictMode,
Path: "/",
MaxAge: -1,
})
http.Redirect(w, r, "/login", http.StatusSeeOther)
}