refactor: restructure in entirety
This commit is contained in:
176
internal/handler/auth.go
Normal file
176
internal/handler/auth.go
Normal file
@@ -0,0 +1,176 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"billit/internal/logic"
|
||||
"billit/internal/view"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
// AuthHandlers holds auth service reference
|
||||
type AuthHandlers struct {
|
||||
auth *logic.AuthService
|
||||
}
|
||||
|
||||
// NewAuthHandlers creates handlers with auth service
|
||||
func NewAuthHandlers(authService *logic.AuthService) *AuthHandlers {
|
||||
return &AuthHandlers{auth: authService}
|
||||
}
|
||||
|
||||
// createAuthCookie creates an HTTP-only secure cookie for the token
|
||||
func createAuthCookie(token string) *http.Cookie {
|
||||
domain := os.Getenv("COOKIE_DOMAIN")
|
||||
secure := os.Getenv("COOKIE_SECURE") == "true"
|
||||
|
||||
return &http.Cookie{
|
||||
Name: "auth_token",
|
||||
Value: token,
|
||||
Path: "/",
|
||||
Domain: domain,
|
||||
MaxAge: int(24 * time.Hour.Seconds()), // Match token duration
|
||||
HttpOnly: true, // Prevents JavaScript access
|
||||
Secure: secure, // Only send over HTTPS in production
|
||||
SameSite: http.SameSiteStrictMode,
|
||||
}
|
||||
}
|
||||
|
||||
// clearAuthCookie returns a cookie that clears the auth token
|
||||
func clearAuthCookie() *http.Cookie {
|
||||
domain := os.Getenv("COOKIE_DOMAIN")
|
||||
secure := os.Getenv("COOKIE_SECURE") == "true"
|
||||
|
||||
return &http.Cookie{
|
||||
Name: "auth_token",
|
||||
Value: "",
|
||||
Path: "/",
|
||||
Domain: domain,
|
||||
MaxAge: -1,
|
||||
HttpOnly: true,
|
||||
Secure: secure,
|
||||
SameSite: http.SameSiteStrictMode,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// LoginPageHandler renders the login page (home page)
|
||||
func (h *AuthHandlers) LoginPageHandler(c echo.Context) error {
|
||||
// Check if already logged in
|
||||
cookie, err := c.Cookie("auth_token")
|
||||
if err == nil && cookie.Value != "" {
|
||||
_, err := h.auth.ValidateToken(cookie.Value)
|
||||
if err == nil {
|
||||
// Already logged in, redirect to home
|
||||
return c.Redirect(http.StatusFound, "/home")
|
||||
}
|
||||
}
|
||||
// Capture redirect URL from query param
|
||||
redirectURL := c.QueryParam("redirect")
|
||||
return view.Render(c, view.LoginPage("", "", redirectURL))
|
||||
}
|
||||
|
||||
// LoginHandler handles login form submission
|
||||
func (h *AuthHandlers) LoginHandler(c echo.Context) error {
|
||||
email := strings.TrimSpace(c.FormValue("email"))
|
||||
password := c.FormValue("password")
|
||||
redirectURL := c.FormValue("redirect")
|
||||
|
||||
if email == "" || password == "" {
|
||||
return view.Render(c, view.LoginPage("Email and password are required", email, redirectURL))
|
||||
}
|
||||
|
||||
token, err := h.auth.Login(email, password)
|
||||
if err != nil {
|
||||
return view.Render(c, view.LoginPage("Invalid email or password", email, redirectURL))
|
||||
}
|
||||
|
||||
// Set HTTP-only cookie
|
||||
cookie := createAuthCookie(token)
|
||||
c.SetCookie(cookie)
|
||||
|
||||
// Redirect to original URL or home page
|
||||
if redirectURL != "" && strings.HasPrefix(redirectURL, "/") {
|
||||
return c.Redirect(http.StatusFound, redirectURL)
|
||||
}
|
||||
return c.Redirect(http.StatusFound, "/home")
|
||||
}
|
||||
|
||||
// RegisterPageHandler renders the registration page
|
||||
func (h *AuthHandlers) RegisterPageHandler(c echo.Context) error {
|
||||
return view.Render(c, view.RegisterPage("", ""))
|
||||
}
|
||||
|
||||
// RegisterHandler handles registration form submission
|
||||
func (h *AuthHandlers) RegisterHandler(c echo.Context) error {
|
||||
email := strings.TrimSpace(c.FormValue("email"))
|
||||
password := c.FormValue("password")
|
||||
confirmPassword := c.FormValue("confirm_password")
|
||||
|
||||
if email == "" || password == "" {
|
||||
return view.Render(c, view.RegisterPage("Email and password are required", email))
|
||||
}
|
||||
|
||||
if password != confirmPassword {
|
||||
return view.Render(c, view.RegisterPage("Passwords do not match", email))
|
||||
}
|
||||
|
||||
if len(password) < 8 {
|
||||
return view.Render(c, view.RegisterPage("Password must be at least 8 characters", email))
|
||||
}
|
||||
|
||||
_, err := h.auth.Register(email, password)
|
||||
if err != nil {
|
||||
if err == logic.ErrUserExists {
|
||||
return view.Render(c, view.RegisterPage("An account with this email already exists", email))
|
||||
}
|
||||
return view.Render(c, view.RegisterPage(err.Error(), email))
|
||||
}
|
||||
|
||||
// Auto-login after registration
|
||||
token, err := h.auth.Login(email, password)
|
||||
if err != nil {
|
||||
return c.Redirect(http.StatusFound, "/")
|
||||
}
|
||||
|
||||
cookie := createAuthCookie(token)
|
||||
c.SetCookie(cookie)
|
||||
|
||||
return c.Redirect(http.StatusFound, "/home")
|
||||
}
|
||||
|
||||
// LogoutHandler clears the auth cookie and redirects to login
|
||||
func (h *AuthHandlers) LogoutHandler(c echo.Context) error {
|
||||
cookie := clearAuthCookie()
|
||||
c.SetCookie(cookie)
|
||||
return c.Redirect(http.StatusFound, "/")
|
||||
}
|
||||
|
||||
// AuthMiddleware protects routes that require authentication
|
||||
func (h *AuthHandlers) AuthMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
cookie, err := c.Cookie("auth_token")
|
||||
if err != nil || cookie.Value == "" {
|
||||
// No cookie - redirect to login with original URL for post-login redirect
|
||||
redirectPath := url.QueryEscape(c.Request().URL.RequestURI())
|
||||
return c.Redirect(http.StatusFound, "/?redirect="+redirectPath)
|
||||
}
|
||||
|
||||
claims, err := h.auth.ValidateToken(cookie.Value)
|
||||
if err != nil {
|
||||
// Invalid/expired token - show session expired dialog
|
||||
c.SetCookie(clearAuthCookie())
|
||||
redirectPath := url.QueryEscape(c.Request().URL.RequestURI())
|
||||
return view.Render(c, view.SessionExpiredPage(redirectPath))
|
||||
}
|
||||
|
||||
// Store user info in context
|
||||
c.Set("user_id", claims.UserID)
|
||||
c.Set("user_email", claims.Email)
|
||||
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user