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(`
%s
`, 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.RenderPageTemplate(ctx, "Log In", "auth/login.html", map[string]any{ "csrf_token": csrf.GetToken(ctx, auth.Manager), "csrf_field": csrf.HiddenField(ctx, auth.Manager), "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(`
%s
`, 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.RenderPageTemplate(ctx, "Register", "auth/register.html", map[string]any{ "csrf_token": csrf.GetToken(ctx, auth.Manager), "csrf_field": csrf.HiddenField(ctx, auth.Manager), "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.GetByUsername(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.GetByEmail(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 }