235 lines
6.4 KiB
Go

package routes
import (
"fmt"
"strings"
"dk/internal/auth"
"dk/internal/csrf"
"dk/internal/middleware"
"dk/internal/password"
"dk/internal/router"
"dk/internal/template/components"
"dk/internal/users"
"github.com/valyala/fasthttp"
)
// RegisterAuthRoutes sets up authentication routes
func RegisterAuthRoutes(r *router.Router) {
// Guest routes
guestGroup := r.Group("")
guestGroup.Use(middleware.RequireGuest())
guestGroup.Get("/login", showLogin)
guestGroup.Post("/login", processLogin)
guestGroup.Get("/register", showRegister)
guestGroup.Post("/register", processRegister)
// Authenticated routes
authGroup := r.Group("")
authGroup.Use(middleware.RequireAuth())
authGroup.Post("/logout", processLogout)
}
// showLogin displays the login form
func showLogin(ctx router.Ctx, _ []string) {
// Get flash message if any
var errorHTML string
if flash := auth.GetFlashMessage(ctx); flash != nil {
errorHTML = fmt.Sprintf(`<div style="color: red; margin-bottom: 1rem;">%s</div>`, flash.Message)
}
// Get form data if any (for preserving email/username on error)
formData := auth.GetFormData(ctx)
id := ""
if formData != nil {
id = formData["id"]
}
components.RenderPageTemplate(ctx, "Log In", "auth/login.html", map[string]any{
"csrf_token": csrf.GetToken(ctx, auth.Manager),
"csrf_field": csrf.HiddenField(ctx, auth.Manager),
"error_message": errorHTML,
"id": id,
})
}
// processLogin handles login form submission
func processLogin(ctx router.Ctx, _ []string) {
if !csrf.ValidateFormToken(ctx, auth.Manager) {
ctx.SetStatusCode(fasthttp.StatusForbidden)
ctx.WriteString("CSRF validation failed")
return
}
email := strings.TrimSpace(string(ctx.PostArgs().Peek("id")))
userPassword := string(ctx.PostArgs().Peek("password"))
if email == "" || userPassword == "" {
auth.SetFlashMessage(ctx, "error", "Email and password are required")
auth.SetFormData(ctx, map[string]string{"id": email})
ctx.Redirect("/login", fasthttp.StatusFound)
return
}
user, err := auth.Manager.Authenticate(email, userPassword)
if err != nil {
auth.SetFlashMessage(ctx, "error", "Invalid email or password")
auth.SetFormData(ctx, map[string]string{"id": email})
ctx.Redirect("/login", fasthttp.StatusFound)
return
}
middleware.Login(ctx, auth.Manager, user)
// Transfer CSRF token from cookie to session for authenticated user
if cookieToken := csrf.GetTokenFromCookie(ctx); cookieToken != "" {
if session := csrf.GetCurrentSession(ctx); session != nil {
csrf.StoreToken(session, cookieToken)
}
}
ctx.Redirect("/", fasthttp.StatusFound)
}
// showRegister displays the registration form
func showRegister(ctx router.Ctx, _ []string) {
// Get flash message if any
var errorHTML string
if flash := auth.GetFlashMessage(ctx); flash != nil {
errorHTML = fmt.Sprintf(`<div style="color: red; margin-bottom: 1rem;">%s</div>`, flash.Message)
}
// Get form data if any (for preserving values on error)
formData := auth.GetFormData(ctx)
username := ""
email := ""
if formData != nil {
username = formData["username"]
email = formData["email"]
}
components.RenderPageTemplate(ctx, "Register", "auth/register.html", map[string]any{
"csrf_token": csrf.GetToken(ctx, auth.Manager),
"csrf_field": csrf.HiddenField(ctx, auth.Manager),
"error_message": errorHTML,
"username": username,
"email": email,
})
}
// processRegister handles registration form submission
func processRegister(ctx router.Ctx, _ []string) {
if !csrf.ValidateFormToken(ctx, auth.Manager) {
ctx.SetStatusCode(fasthttp.StatusForbidden)
ctx.WriteString("CSRF validation failed")
return
}
username := strings.TrimSpace(string(ctx.PostArgs().Peek("username")))
email := strings.TrimSpace(string(ctx.PostArgs().Peek("email")))
userPassword := string(ctx.PostArgs().Peek("password"))
confirmPassword := string(ctx.PostArgs().Peek("confirm_password"))
if err := validateRegistration(username, email, userPassword, confirmPassword); err != nil {
auth.SetFlashMessage(ctx, "error", err.Error())
auth.SetFormData(ctx, map[string]string{
"username": username,
"email": email,
})
ctx.Redirect("/register", fasthttp.StatusFound)
return
}
if _, err := users.GetByUsername(username); err == nil {
auth.SetFlashMessage(ctx, "error", "Username already exists")
auth.SetFormData(ctx, map[string]string{
"username": username,
"email": email,
})
ctx.Redirect("/register", fasthttp.StatusFound)
return
}
if _, err := users.GetByEmail(email); err == nil {
auth.SetFlashMessage(ctx, "error", "Email already registered")
auth.SetFormData(ctx, map[string]string{
"username": username,
"email": email,
})
ctx.Redirect("/register", fasthttp.StatusFound)
return
}
user := users.New()
user.Username = username
user.Email = email
user.Password = password.Hash(userPassword)
user.ClassID = 1
user.Auth = 1
if err := user.Insert(); err != nil {
auth.SetFlashMessage(ctx, "error", "Failed to create account")
auth.SetFormData(ctx, map[string]string{
"username": username,
"email": email,
})
ctx.Redirect("/register", fasthttp.StatusFound)
return
}
// Auto-login after registration
middleware.Login(ctx, auth.Manager, user)
// Transfer CSRF token from cookie to session for authenticated user
if cookieToken := csrf.GetTokenFromCookie(ctx); cookieToken != "" {
if session := csrf.GetCurrentSession(ctx); session != nil {
csrf.StoreToken(session, cookieToken)
}
}
ctx.Redirect("/", fasthttp.StatusFound)
}
// processLogout handles logout
func processLogout(ctx router.Ctx, params []string) {
// Validate CSRF token
if !csrf.ValidateFormToken(ctx, auth.Manager) {
ctx.SetStatusCode(fasthttp.StatusForbidden)
ctx.WriteString("CSRF validation failed")
return
}
middleware.Logout(ctx, auth.Manager)
ctx.Redirect("/", fasthttp.StatusFound)
}
// Helper functions
func validateRegistration(username, email, password, confirmPassword string) error {
if username == "" {
return fmt.Errorf("username is required")
}
if len(username) < 3 {
return fmt.Errorf("username must be at least 3 characters")
}
if email == "" {
return fmt.Errorf("email is required")
}
if !strings.Contains(email, "@") {
return fmt.Errorf("invalid email address")
}
if password == "" {
return fmt.Errorf("password is required")
}
if len(password) < 6 {
return fmt.Errorf("password must be at least 6 characters")
}
if password != confirmPassword {
return fmt.Errorf("passwords do not match")
}
return nil
}