252 lines
6.4 KiB
Go

package routes
import (
"fmt"
"slices"
"strings"
"dk/internal/components"
"dk/internal/models/classes"
"dk/internal/models/users"
sushi "git.sharkk.net/Sharkk/Sushi"
"git.sharkk.net/Sharkk/Sushi/auth"
"git.sharkk.net/Sharkk/Sushi/password"
)
// RegisterAuthRoutes sets up authentication routes
func RegisterAuthRoutes(app *sushi.App) {
// Public routes (no auth required)
app.Get("/login", showLogin)
app.Post("/login", processLogin)
app.Get("/register", showRegister)
app.Post("/register", processRegister)
// Protected routes
authed := app.Group("")
authed.Use(auth.RequireAuth("/login"))
authed.Post("/logout", processLogout)
}
// showLogin displays the login form
func showLogin(ctx sushi.Ctx) {
sess := ctx.GetCurrentSession()
var id string
if formData, exists := sess.Get("form_data"); exists {
if data, ok := formData.(map[string]string); ok {
id = data["id"]
}
}
sess.Delete("form_data")
components.RenderPage(ctx, "Log In", "auth/login.html", map[string]any{
"id": id,
})
}
// processLogin handles login form submission
func processLogin(ctx sushi.Ctx) {
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")
return
}
user, err := authenticate(email, userPassword)
if err != nil {
setFlashAndFormData(ctx, "Invalid email or password", map[string]string{"id": email})
ctx.Redirect("/login")
return
}
// Update last online time when logging in
user.UpdateLastOnline()
ctx.Login(user.ID, user)
// Set success message
sess := ctx.GetCurrentSession()
sess.SetFlash("success", fmt.Sprintf("Welcome back, %s!", user.Username))
ctx.Redirect("/")
}
// showRegister displays the registration form
func showRegister(ctx sushi.Ctx) {
sess := ctx.GetCurrentSession()
var username, email string
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")
allClasses, _ := classes.All()
slices.Reverse(allClasses)
components.RenderPage(ctx, "Register", "auth/register.html", map[string]any{
"username": username,
"email": email,
"error_message": sess.GetFlashMessage("error"),
"classes": allClasses,
})
}
// processRegister handles registration form submission
func processRegister(ctx sushi.Ctx) {
username := strings.TrimSpace(ctx.Form("username").String())
email := strings.TrimSpace(ctx.Form("email").String())
userPassword := ctx.Form("password").String()
confirmPassword := ctx.Form("confirm_password").String()
classID := ctx.Form("class").Int()
formData := map[string]string{
"username": username,
"email": email,
}
sess := ctx.GetCurrentSession()
if err := validateRegistration(username, email, userPassword, confirmPassword); err != nil {
fmt.Println("Error occured: " + err.Error())
setFlashAndFormData(ctx, err.Error(), formData)
ctx.Redirect("/register")
return
}
// Check if username already exists
if _, err := users.ByUsername(username); err != nil {
fmt.Println("Username already exists")
setFlashAndFormData(ctx, "Username already exists", formData)
ctx.Redirect("/register")
return
}
// Check if email already exists
if _, err := users.ByEmail(email); err != nil {
fmt.Println("Email already exists")
setFlashAndFormData(ctx, "Email already registered", formData)
ctx.Redirect("/register")
return
}
// Ensure class ID exists (should always, unless someone modified the form)
class, err := classes.Find(classID)
if err != nil {
fmt.Println("Invalid class ID")
setFlashAndFormData(ctx, "Invalid class selected", formData)
ctx.Redirect("/register")
return
}
// Create new user
user := users.New()
user.Username = username
user.Email = email
user.Password = password.HashPassword(userPassword)
user.Auth = 1
user.ClassID = classID
user.HP, user.MaxHP = class.BaseHP, class.BaseHP
user.MP, user.MaxMP = class.BaseMP, class.BaseMP
user.Strength = class.BaseSTR
user.Dexterity = class.BaseDEX
// Validate before inserting
if err := user.Validate(); err != nil {
setFlashAndFormData(ctx, fmt.Sprintf("Invalid user data: %s", err.Error()), formData)
ctx.Redirect("/register")
return
}
if err := user.Insert(); err != nil {
setFlashAndFormData(ctx, "Failed to create account", formData)
ctx.Redirect("/register")
return
}
// Grant level 1 spells for their class
if err := user.LearnNewSpells(); err != nil {
// Don't fail registration if spells fail, just log it
fmt.Printf("Failed to grant initial spells to user %d: %v\n", user.ID, err)
}
// Auto-login after registration
ctx.Login(user.ID, user)
// Set success message
sess.SetFlash("success", fmt.Sprintf("Greetings, %s!", user.Username))
ctx.Redirect("/")
}
// processLogout handles logout
func processLogout(ctx sushi.Ctx) {
ctx.Logout()
ctx.Redirect("/")
}
// 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 sushi.Ctx, message string, formData map[string]string) {
sess := ctx.GetCurrentSession()
sess.SetFlash("error", message)
sess.Set("form_data", formData)
}
func authenticate(usernameOrEmail, plainPassword string) (*users.User, error) {
var user *users.User
var err error
// Try username first
user, err = users.ByUsername(usernameOrEmail)
if err != nil {
// If username not found, try email
user, err = users.ByEmail(usernameOrEmail)
if err != nil {
return nil, fmt.Errorf("user not found")
}
}
isValid, err := password.VerifyPassword(plainPassword, user.Password)
if err != nil {
return nil, fmt.Errorf("password verification error: %w", err)
}
if !isValid {
return nil, fmt.Errorf("invalid password")
}
return user, nil
}