377 lines
11 KiB
Go
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
|
|
}
|