241 lines
6.4 KiB
Go

package routes
import (
"fmt"
"strings"
"dk/internal/components"
"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")
components.RenderPage(ctx, "Register", "auth/register.html", map[string]any{
"username": username,
"email": email,
"error_message": sess.GetFlashMessage("error"),
})
}
// processRegister handles registration form submission
func processRegister(ctx sushi.Ctx) {
fmt.Println("DEBUG: Starting registration process")
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()
fmt.Printf("DEBUG: Form data - username: '%s', email: '%s', password length: %d\n",
username, email, len(userPassword))
formData := map[string]string{
"username": username,
"email": email,
}
if err := validateRegistration(username, email, userPassword, confirmPassword); err != nil {
fmt.Printf("DEBUG: Validation failed: %v\n", err)
setFlashAndFormData(ctx, err.Error(), formData)
ctx.Redirect("/register")
return
}
fmt.Println("DEBUG: Validation passed")
// Check if username already exists
if _, err := users.ByUsername(username); err == nil {
fmt.Printf("DEBUG: Username '%s' already exists\n", username)
setFlashAndFormData(ctx, "Username already exists", formData)
ctx.Redirect("/register")
return
}
fmt.Printf("DEBUG: Username '%s' is available\n", username)
// Check if email already exists
if _, err := users.ByEmail(email); err == nil {
fmt.Printf("DEBUG: Email '%s' already exists\n", email)
setFlashAndFormData(ctx, "Email already registered", formData)
ctx.Redirect("/register")
return
}
fmt.Printf("DEBUG: Email '%s' is available\n", email)
// Create new user
user := users.New()
user.Username = username
user.Email = email
user.Password = password.HashPassword(userPassword)
user.ClassID = 1
user.Auth = 1
fmt.Printf("DEBUG: Created user struct with ID: %d\n", user.ID)
// Validate before inserting
if err := user.Validate(); err != nil {
fmt.Printf("DEBUG: User validation failed: %v\n", err)
setFlashAndFormData(ctx, fmt.Sprintf("Invalid user data: %s", err.Error()), formData)
ctx.Redirect("/register")
return
}
fmt.Println("DEBUG: User validation passed")
if err := user.Insert(); err != nil {
fmt.Printf("DEBUG: User insert failed: %v\n", err)
setFlashAndFormData(ctx, "Failed to create account", formData)
ctx.Redirect("/register")
return
}
fmt.Printf("DEBUG: User inserted successfully with ID: %d\n", user.ID)
// Auto-login after registration
ctx.Login(user.ID, user)
fmt.Printf("DEBUG: User logged in with ID: %d\n", user.ID)
// Set success message
sess := ctx.GetCurrentSession()
sess.SetFlash("success", fmt.Sprintf("Greetings, %s!", user.Username))
fmt.Println("DEBUG: Registration completed successfully")
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
}