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(`
%s
`, 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(`
%s
`, 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 }