336 lines
9.5 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/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("/login"))
authGroup.Post("/logout", processLogout())
}
// showLogin displays the login form
func showLogin() router.Handler {
return func(ctx router.Ctx, params []string) {
loginTmpl, err := template.Cache.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, auth.Manager),
"csrf_field": csrf.HiddenField(ctx, auth.Manager),
"error_message": "",
}
loginContent := loginTmpl.RenderNamed(loginFormData)
pageData := components.NewPageData("Login - Dragon Knight", loginContent)
if err := components.RenderPage(ctx, pageData, nil); err != nil {
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
fmt.Fprintf(ctx, "Template error: %v", err)
return
}
}
}
// processLogin handles login form submission
func processLogin() router.Handler {
return func(ctx router.Ctx, params []string) {
// Validate CSRF token
if !csrf.ValidateFormToken(ctx, auth.Manager) {
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, "Email and password are required")
return
}
// Authenticate user
user, err := auth.Manager.Authenticate(email, userPassword)
if err != nil {
showLoginError(ctx, "Invalid email or password")
return
}
// Create session and login
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)
}
}
// Redirect to dashboard
ctx.Redirect("/dashboard", fasthttp.StatusFound)
}
}
// showRegister displays the registration form
func showRegister() router.Handler {
return func(ctx router.Ctx, params []string) {
registerTmpl, err := template.Cache.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, auth.Manager),
"csrf_field": csrf.HiddenField(ctx, auth.Manager),
"error_message": "",
"username": "",
"email": "",
}
registerContent := registerTmpl.RenderNamed(registerFormData)
pageData := components.NewPageData("Register - Dragon Knight", registerContent)
if err := components.RenderPage(ctx, pageData, nil); err != nil {
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
fmt.Fprintf(ctx, "Template error: %v", err)
return
}
}
}
// processRegister handles registration form submission
func processRegister() router.Handler {
return func(ctx router.Ctx, params []string) {
// Validate CSRF token
if !csrf.ValidateFormToken(ctx, auth.Manager) {
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, err.Error(), username, email)
return
}
// Check if username already exists
if _, err := users.GetByUsername(auth.Manager.DB(), username); err == nil {
showRegisterError(ctx, "Username already exists", username, email)
return
}
// Check if email already exists
if _, err := users.GetByEmail(auth.Manager.DB(), email); err == nil {
showRegisterError(ctx, "Email already registered", username, email)
return
}
// Hash password
hashedPassword, err := password.Hash(userPassword)
if err != nil {
showRegisterError(ctx, "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(user); err != nil {
showRegisterError(ctx, "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, auth.Manager, authUser)
// 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() router.Handler {
return func(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 showLoginError(ctx router.Ctx, errorMsg string) {
loginTmpl, err := template.Cache.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, auth.Manager),
"csrf_field": csrf.HiddenField(ctx, auth.Manager),
"error_message": errorHTML,
}
loginContent := loginTmpl.RenderNamed(loginFormData)
ctx.SetStatusCode(fasthttp.StatusBadRequest)
pageData := components.NewPageData("Login - Dragon Knight", loginContent)
if err := components.RenderPage(ctx, pageData, nil); err != nil {
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
fmt.Fprintf(ctx, "Template error: %v", err)
return
}
}
func showRegisterError(ctx router.Ctx, errorMsg, username, email string) {
registerTmpl, err := template.Cache.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, auth.Manager),
"csrf_field": csrf.HiddenField(ctx, auth.Manager),
"error_message": errorHTML,
"username": username,
"email": email,
}
registerContent := registerTmpl.RenderNamed(registerFormData)
ctx.SetStatusCode(fasthttp.StatusBadRequest)
pageData := components.NewPageData("Register - Dragon Knight", registerContent)
if err := components.RenderPage(ctx, pageData, nil); err != nil {
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
fmt.Fprintf(ctx, "Template error: %v", err)
return
}
}
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(user *users.User) error {
db := auth.Manager.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
}