377 lines
11 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"
"dk/internal/users"
"github.com/valyala/fasthttp"
)
// RegisterAuthRoutes sets up authentication routes
func RegisterAuthRoutes(r *router.Router, authManager *auth.AuthManager, templateCache *template.Cache) {
// Guest routes (redirect to dashboard if already authenticated)
guestGroup := r.Group("")
guestGroup.Use(middleware.RequireGuest("/"))
guestGroup.Get("/login", showLogin(authManager, templateCache))
guestGroup.Post("/login", processLogin(authManager, templateCache))
guestGroup.Get("/register", showRegister(authManager, templateCache))
guestGroup.Post("/register", processRegister(authManager, templateCache))
// Authenticated routes
authGroup := r.Group("")
authGroup.Use(middleware.RequireAuth("/login"))
authGroup.Post("/logout", processLogout(authManager))
}
// showLogin displays the login form
func showLogin(authManager *auth.AuthManager, templateCache *template.Cache) router.Handler {
return func(ctx router.Ctx, params []string) {
layoutTmpl, err := templateCache.Load("layout.html")
if err != nil {
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
fmt.Fprintf(ctx, "Template error: %v", err)
return
}
loginTmpl, err := templateCache.Load("auth/login.html")
if err != nil {
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
fmt.Fprintf(ctx, "Template error: %v", err)
return
}
loginFormData := map[string]any{
"csrf_token": csrf.GetToken(ctx, authManager),
"csrf_field": csrf.HiddenField(ctx, authManager),
"error_message": "",
}
loginContent := loginTmpl.RenderNamed(loginFormData)
data := map[string]any{
"title": "Login - Dragon Knight",
"content": loginContent,
"topnav": "",
"leftside": "",
"rightside": "",
"totaltime": middleware.GetRequestTime(ctx),
"numqueries": "0",
"version": "1.0.0",
"build": "dev",
}
layoutTmpl.WriteTo(ctx, data)
}
}
// processLogin handles login form submission
func processLogin(authManager *auth.AuthManager, templateCache *template.Cache) router.Handler {
return func(ctx router.Ctx, params []string) {
// Validate CSRF token
if !csrf.ValidateFormToken(ctx, authManager) {
ctx.SetStatusCode(fasthttp.StatusForbidden)
ctx.WriteString("CSRF validation failed")
return
}
// Get form values
email := strings.TrimSpace(string(ctx.PostArgs().Peek("email")))
userPassword := string(ctx.PostArgs().Peek("password"))
// Validate input
if email == "" || userPassword == "" {
showLoginError(ctx, authManager, templateCache, "Email and password are required")
return
}
// Authenticate user
user, err := authManager.Authenticate(email, userPassword)
if err != nil {
showLoginError(ctx, authManager, templateCache, "Invalid email or password")
return
}
// Create session and login
middleware.Login(ctx, authManager, user)
// Redirect to dashboard
ctx.Redirect("/dashboard", fasthttp.StatusFound)
}
}
// showRegister displays the registration form
func showRegister(authManager *auth.AuthManager, templateCache *template.Cache) router.Handler {
return func(ctx router.Ctx, params []string) {
layoutTmpl, err := templateCache.Load("layout.html")
if err != nil {
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
fmt.Fprintf(ctx, "Template error: %v", err)
return
}
registerTmpl, err := templateCache.Load("auth/register.html")
if err != nil {
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
fmt.Fprintf(ctx, "Template error: %v", err)
return
}
registerFormData := map[string]any{
"csrf_token": csrf.GetToken(ctx, authManager),
"csrf_field": csrf.HiddenField(ctx, authManager),
"error_message": "",
"username": "",
"email": "",
}
registerContent := registerTmpl.RenderNamed(registerFormData)
data := map[string]any{
"title": "Register - Dragon Knight",
"content": registerContent,
"topnav": "",
"leftside": "",
"rightside": "",
"totaltime": middleware.GetRequestTime(ctx),
"numqueries": "0",
"version": "1.0.0",
"build": "dev",
}
layoutTmpl.WriteTo(ctx, data)
}
}
// processRegister handles registration form submission
func processRegister(authManager *auth.AuthManager, templateCache *template.Cache) router.Handler {
return func(ctx router.Ctx, params []string) {
// Validate CSRF token
if !csrf.ValidateFormToken(ctx, authManager) {
ctx.SetStatusCode(fasthttp.StatusForbidden)
ctx.WriteString("CSRF validation failed")
return
}
// Get form values
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"))
// Validate input
if err := validateRegistration(username, email, userPassword, confirmPassword); err != nil {
showRegisterError(ctx, authManager, templateCache, err.Error(), username, email)
return
}
// Check if username already exists
if _, err := users.GetByUsername(authManager.DB(), username); err == nil {
showRegisterError(ctx, authManager, templateCache, "Username already exists", username, email)
return
}
// Check if email already exists
if _, err := users.GetByEmail(authManager.DB(), email); err == nil {
showRegisterError(ctx, authManager, templateCache, "Email already registered", username, email)
return
}
// Hash password
hashedPassword, err := password.Hash(userPassword)
if err != nil {
showRegisterError(ctx, authManager, templateCache, "Failed to process password", username, email)
return
}
// Create user (this is a simplified approach - in a real app you'd use a proper user creation function)
user := &users.User{
Username: username,
Email: email,
Password: hashedPassword,
Verified: 1, // Auto-verify for now
Auth: 1, // Enabled
}
// Insert into database
if err := createUser(authManager, user); err != nil {
showRegisterError(ctx, authManager, templateCache, "Failed to create account", username, email)
return
}
// Auto-login after registration
authUser := &auth.User{
ID: user.ID,
Username: user.Username,
Email: user.Email,
}
middleware.Login(ctx, authManager, authUser)
ctx.Redirect("/", fasthttp.StatusFound)
}
}
// processLogout handles logout
func processLogout(authManager *auth.AuthManager) router.Handler {
return func(ctx router.Ctx, params []string) {
// Validate CSRF token
if !csrf.ValidateFormToken(ctx, authManager) {
ctx.SetStatusCode(fasthttp.StatusForbidden)
ctx.WriteString("CSRF validation failed")
return
}
middleware.Logout(ctx, authManager)
ctx.Redirect("/", fasthttp.StatusFound)
}
}
// Helper functions
func showLoginError(ctx router.Ctx, authManager *auth.AuthManager, templateCache *template.Cache, errorMsg string) {
layoutTmpl, err := templateCache.Load("layout.html")
if err != nil {
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
fmt.Fprintf(ctx, "Template error: %v", err)
return
}
loginTmpl, err := templateCache.Load("auth/login.html")
if err != nil {
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
fmt.Fprintf(ctx, "Template error: %v", err)
return
}
var errorHTML string
if errorMsg != "" {
errorHTML = fmt.Sprintf(`<div style="color: red; margin-bottom: 1rem;">%s</div>`, errorMsg)
}
loginFormData := map[string]any{
"csrf_token": csrf.GetToken(ctx, authManager),
"csrf_field": csrf.HiddenField(ctx, authManager),
"error_message": errorHTML,
}
loginContent := loginTmpl.RenderNamed(loginFormData)
data := map[string]any{
"title": "Login - Dragon Knight",
"content": loginContent,
"topnav": "",
"leftside": "",
"rightside": "",
"totaltime": middleware.GetRequestTime(ctx),
"numqueries": "0",
"version": "1.0.0",
"build": "dev",
}
ctx.SetStatusCode(fasthttp.StatusBadRequest)
layoutTmpl.WriteTo(ctx, data)
}
func showRegisterError(ctx router.Ctx, authManager *auth.AuthManager, templateCache *template.Cache, errorMsg, username, email string) {
layoutTmpl, err := templateCache.Load("layout.html")
if err != nil {
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
fmt.Fprintf(ctx, "Template error: %v", err)
return
}
registerTmpl, err := templateCache.Load("auth/register.html")
if err != nil {
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
fmt.Fprintf(ctx, "Template error: %v", err)
return
}
var errorHTML string
if errorMsg != "" {
errorHTML = fmt.Sprintf(`<div style="color: red; margin-bottom: 1rem;">%s</div>`, errorMsg)
}
registerFormData := map[string]any{
"csrf_token": csrf.GetToken(ctx, authManager),
"csrf_field": csrf.HiddenField(ctx, authManager),
"error_message": errorHTML,
"username": username,
"email": email,
}
registerContent := registerTmpl.RenderNamed(registerFormData)
data := map[string]any{
"title": "Register - Dragon Knight",
"content": registerContent,
"topnav": "",
"leftside": "",
"rightside": "",
"totaltime": middleware.GetRequestTime(ctx),
"numqueries": "0",
"version": "1.0.0",
"build": "dev",
}
ctx.SetStatusCode(fasthttp.StatusBadRequest)
layoutTmpl.WriteTo(ctx, data)
}
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
}
// createUser inserts a new user into the database
// This is a simplified version - in a real app you'd have a proper users.Create function
func createUser(authManager *auth.AuthManager, user *users.User) error {
db := authManager.DB()
query := `INSERT INTO users (username, password, email, verified, auth) VALUES (?, ?, ?, ?, ?)`
err := db.Exec(query, user.Username, user.Password, user.Email, user.Verified, user.Auth)
if err != nil {
return fmt.Errorf("failed to insert user: %w", err)
}
// Get the user ID (simplified - in real app you'd return it from insert)
createdUser, err := users.GetByUsername(db, user.Username)
if err != nil {
return fmt.Errorf("failed to get created user: %w", err)
}
user.ID = createdUser.ID
return nil
}