209 lines
5.1 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
}
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) {
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")
return
}
if _, err := users.ByUsername(username); err == nil {
setFlashAndFormData(ctx, "Username already exists", formData)
ctx.Redirect("/register")
return
}
if _, err := users.ByEmail(email); err == nil {
setFlashAndFormData(ctx, "Email already registered", formData)
ctx.Redirect("/register")
return
}
user := users.New()
user.Username = username
user.Email = email
user.Password = password.HashPassword(userPassword)
user.ClassID = 1
user.Auth = 1
if err := user.Insert(); err != nil {
setFlashAndFormData(ctx, "Failed to create account", formData)
ctx.Redirect("/register")
return
}
// Auto-login after registration
ctx.Login(user.ID, user)
// Set success message
sess := ctx.GetCurrentSession()
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
user, err = users.ByUsername(usernameOrEmail)
if err != nil {
fmt.Println(err.Error())
user, err = users.ByEmail(usernameOrEmail)
if err != nil {
fmt.Println(err.Error())
return nil, err
}
}
isValid, err := password.VerifyPassword(plainPassword, user.Password)
if err != nil {
return nil, err
}
if !isValid {
return nil, fmt.Errorf("invalid username/email or password")
}
return user, nil
}