231 lines
6.2 KiB
Go
231 lines
6.2 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.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) {
|
|
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.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) {
|
|
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.ByUsername(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.ByEmail(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
|
|
}
|