package routes import ( "fmt" "strings" "dk/internal/csrf" "dk/internal/middleware" "dk/internal/models/users" "dk/internal/password" "dk/internal/router" "dk/internal/session" "dk/internal/template/components" "github.com/valyala/fasthttp" ) // RegisterAuthRoutes sets up authentication routes func RegisterAuthRoutes(r *router.Router) { guests := r.Group("") guests.Use(middleware.RequireGuest()) guests.Get("/login", showLogin) guests.Post("/login", processLogin) guests.Get("/register", showRegister) guests.Post("/register", processRegister) authed := r.Group("") authed.Use(middleware.RequireAuth()) authed.Post("/logout", processLogout) } // showLogin displays the login form func showLogin(ctx router.Ctx, _ []string) { sess := ctx.UserValue("session").(*session.Session) var errorHTML string var id string if flash, exists := sess.GetFlash("error"); exists { if msg, ok := flash.(string); ok { errorHTML = fmt.Sprintf(`
%s
`, msg) } } if formData, exists := sess.Get("form_data"); exists { if data, ok := formData.(map[string]string); ok { id = data["id"] } } sess.Delete("form_data") session.Store(sess) components.RenderPage(ctx, "Log In", "auth/login.html", map[string]any{ "error_message": errorHTML, "id": id, }) } // processLogin handles login form submission func processLogin(ctx router.Ctx, _ []string) { if !csrf.ValidateFormToken(ctx) { 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 == "" { setFlashAndFormData(ctx, "Email and password are required", map[string]string{"id": email}) ctx.Redirect("/login", fasthttp.StatusFound) return } user, err := authenticate(email, userPassword) if err != nil { setFlashAndFormData(ctx, "Invalid email or password", map[string]string{"id": email}) ctx.Redirect("/login", fasthttp.StatusFound) return } middleware.Login(ctx, user) // Set success message if sess := ctx.UserValue("session").(*session.Session); sess != nil { sess.SetFlash("success", fmt.Sprintf("Welcome back, %s!", user.Username)) session.Store(sess) } // Transfer CSRF token from cookie to session for authenticated user if cookieToken := csrf.GetTokenFromCookie(ctx); cookieToken != "" { if sess := ctx.UserValue("session").(*session.Session); sess != nil { csrf.StoreToken(sess, cookieToken) } } ctx.Redirect("/", fasthttp.StatusFound) } // showRegister displays the registration form func showRegister(ctx router.Ctx, _ []string) { sess := ctx.UserValue("session").(*session.Session) var errorHTML string var username, email string if flash, exists := sess.GetFlash("error"); exists { if msg, ok := flash.(string); ok { errorHTML = fmt.Sprintf(`
%s
`, msg) } } if formData, exists := sess.Get("form_data"); exists { if data, ok := formData.(map[string]string); ok { username = data["username"] email = data["email"] } } sess.Delete("form_data") session.Store(sess) components.RenderPage(ctx, "Register", "auth/register.html", map[string]any{ "error_message": errorHTML, "username": username, "email": email, }) } // processRegister handles registration form submission func processRegister(ctx router.Ctx, _ []string) { if !csrf.ValidateFormToken(ctx) { 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")) formData := map[string]string{ "username": username, "email": email, } if err := validateRegistration(username, email, userPassword, confirmPassword); err != nil { setFlashAndFormData(ctx, err.Error(), formData) ctx.Redirect("/register", fasthttp.StatusFound) return } if _, err := users.ByUsername(username); err == nil { setFlashAndFormData(ctx, "Username already exists", formData) ctx.Redirect("/register", fasthttp.StatusFound) return } if _, err := users.ByEmail(email); err == nil { setFlashAndFormData(ctx, "Email already registered", formData) 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 { setFlashAndFormData(ctx, "Failed to create account", formData) ctx.Redirect("/register", fasthttp.StatusFound) return } // Auto-login after registration middleware.Login(ctx, user) // Set success message if sess := ctx.UserValue("session").(*session.Session); sess != nil { sess.SetFlash("success", fmt.Sprintf("Greetings, %s!", user.Username)) session.Store(sess) } // Transfer CSRF token from cookie to session for authenticated user if cookieToken := csrf.GetTokenFromCookie(ctx); cookieToken != "" { if sess := ctx.UserValue("session").(*session.Session); sess != nil { csrf.StoreToken(sess, cookieToken) } } ctx.Redirect("/", fasthttp.StatusFound) } // processLogout handles logout func processLogout(ctx router.Ctx, params []string) { // Validate CSRF token if !csrf.ValidateFormToken(ctx) { ctx.SetStatusCode(fasthttp.StatusForbidden) ctx.WriteString("CSRF validation failed") return } middleware.Logout(ctx) 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 } func setFlashAndFormData(ctx router.Ctx, message string, formData map[string]string) { sess := ctx.UserValue("session").(*session.Session) sess.SetFlash("error", message) sess.Set("form_data", formData) session.Store(sess) } func authenticate(usernameOrEmail, plainPassword string) (*users.User, error) { var user *users.User var err error user, err = users.ByUsername(usernameOrEmail) if err != nil { user, err = users.ByEmail(usernameOrEmail) if err != nil { return nil, err } } isValid, err := password.Verify(plainPassword, user.Password) if err != nil { return nil, err } if !isValid { return nil, fmt.Errorf("invalid username/email or password") } return user, nil }