249 lines
6.5 KiB
Go

package routes
import (
"fmt"
"strings"
"dk/internal/auth"
"dk/internal/csrf"
"dk/internal/models/users"
"dk/internal/password"
"dk/internal/router"
"dk/internal/session"
"dk/internal/template/components"
"github.com/valyala/fasthttp"
)
// RegisterAuthRoutes sets up authentication routes
func RegisterAuthRoutes(r *router.Router) {
guests := r.Group("")
guests.Use(auth.RequireGuest())
guests.Get("/login", showLogin)
guests.Post("/login", processLogin)
guests.Get("/register", showRegister)
guests.Post("/register", processRegister)
authed := r.Group("")
authed.Use(auth.RequireAuth())
authed.Post("/logout", processLogout)
}
// showLogin displays the login form
func showLogin(ctx router.Ctx, _ []string) {
sess := ctx.UserValue("session").(*session.Session)
var errorHTML string
var id string
if flash, exists := sess.GetFlash("error"); exists {
if msg, ok := flash.(string); ok {
errorHTML = fmt.Sprintf(`<div style="color: red; margin-bottom: 1rem;">%s</div>`, msg)
}
}
if formData, exists := sess.Get("form_data"); exists {
if data, ok := formData.(map[string]string); ok {
id = data["id"]
}
}
sess.Delete("form_data")
session.Store(sess)
components.RenderPage(ctx, "Log In", "auth/login.html", map[string]any{
"error_message": errorHTML,
"id": id,
})
}
// processLogin handles login form submission
func processLogin(ctx router.Ctx, _ []string) {
email := strings.TrimSpace(string(ctx.PostArgs().Peek("id")))
userPassword := string(ctx.PostArgs().Peek("password"))
if email == "" || userPassword == "" {
setFlashAndFormData(ctx, "Email and password are required", map[string]string{"id": email})
ctx.Redirect("/login", fasthttp.StatusFound)
return
}
user, err := authenticate(email, userPassword)
if err != nil {
setFlashAndFormData(ctx, "Invalid email or password", map[string]string{"id": email})
ctx.Redirect("/login", fasthttp.StatusFound)
return
}
auth.Login(ctx, user)
// Transfer CSRF token from cookie to session for authenticated user
if cookieToken := csrf.GetTokenFromCookie(ctx); cookieToken != "" {
if sess := ctx.UserValue("session").(*session.Session); sess != nil {
csrf.StoreToken(sess, cookieToken)
}
}
ctx.Redirect("/", fasthttp.StatusFound)
}
// showRegister displays the registration form
func showRegister(ctx router.Ctx, _ []string) {
sess := ctx.UserValue("session").(*session.Session)
var errorHTML string
var username, email string
if flash, exists := sess.GetFlash("error"); exists {
if msg, ok := flash.(string); ok {
errorHTML = fmt.Sprintf(`<div style="color: red; margin-bottom: 1rem;">%s</div>`, msg)
}
}
if formData, exists := sess.Get("form_data"); exists {
if data, ok := formData.(map[string]string); ok {
username = data["username"]
email = data["email"]
}
}
sess.Delete("form_data")
session.Store(sess)
components.RenderPage(ctx, "Register", "auth/register.html", map[string]any{
"error_message": errorHTML,
"username": username,
"email": email,
})
}
// processRegister handles registration form submission
func processRegister(ctx router.Ctx, _ []string) {
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"))
formData := map[string]string{
"username": username,
"email": email,
}
if err := validateRegistration(username, email, userPassword, confirmPassword); err != nil {
setFlashAndFormData(ctx, err.Error(), formData)
ctx.Redirect("/register", fasthttp.StatusFound)
return
}
if _, err := users.ByUsername(username); err == nil {
setFlashAndFormData(ctx, "Username already exists", formData)
ctx.Redirect("/register", fasthttp.StatusFound)
return
}
if _, err := users.ByEmail(email); err == nil {
setFlashAndFormData(ctx, "Email already registered", formData)
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 {
setFlashAndFormData(ctx, "Failed to create account", formData)
ctx.Redirect("/register", fasthttp.StatusFound)
return
}
// Store old session ID before creating new one
oldSess := ctx.UserValue("session").(*session.Session)
oldSessionID := oldSess.ID
// Auto-login after registration
auth.Login(ctx, user)
// Clean up old guest session
session.Delete(oldSessionID)
// Set success message
if sess := ctx.UserValue("session").(*session.Session); sess != nil {
sess.SetFlash("success", fmt.Sprintf("Greetings, %s!", user.Username))
session.Store(sess)
}
// Transfer CSRF token from cookie to session for authenticated user
if cookieToken := csrf.GetTokenFromCookie(ctx); cookieToken != "" {
if sess := ctx.UserValue("session").(*session.Session); sess != nil {
csrf.StoreToken(sess, cookieToken)
}
}
ctx.Redirect("/", fasthttp.StatusFound)
}
// processLogout handles logout
func processLogout(ctx router.Ctx, params []string) {
auth.Logout(ctx)
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
}
func setFlashAndFormData(ctx router.Ctx, message string, formData map[string]string) {
sess := ctx.UserValue("session").(*session.Session)
sess.SetFlash("error", message)
sess.Set("form_data", formData)
session.Store(sess)
}
func authenticate(usernameOrEmail, plainPassword string) (*users.User, error) {
var user *users.User
var err error
user, err = users.ByUsername(usernameOrEmail)
if err != nil {
user, err = users.ByEmail(usernameOrEmail)
if err != nil {
return nil, err
}
}
isValid, err := password.Verify(plainPassword, user.Password)
if err != nil {
return nil, err
}
if !isValid {
return nil, fmt.Errorf("invalid username/email or password")
}
return user, nil
}