package auth import ( "RenewCMS/api/pkg/apperr" validator "RenewCMS/api/pkg/validator" "RenewCMS/internal/domain/user" "RenewCMS/internal/infrastructure/useCases" "encoding/json" "errors" "net/http" "time" "github.com/go-chi/jwtauth/v5" ) type LoginHandler struct { Token *jwtauth.JWTAuth CreateUseCase *useCases.CreateUserUseCase AuthenticateUseCase *useCases.AuthenticateUserUseCase IsProduction bool } type RegisterCredentials struct { Username string `json:"username" validate:"required,min=3,max=20"` Password string `json:"password" validate:"required,min=8"` Email string `json:"email" validate:"required,email"` } type LoginCredentials struct { Username string `json:"username" validate:"required,min=3,max=20"` Password string `json:"password" validate:"required,min=8"` } func NewLoginHandler(token *jwtauth.JWTAuth, create *useCases.CreateUserUseCase, authenticate *useCases.AuthenticateUserUseCase, isProduction bool) *LoginHandler { return &LoginHandler{ Token: token, CreateUseCase: create, AuthenticateUseCase: authenticate, IsProduction: isProduction, } } func removeJwtCookie(w http.ResponseWriter, isProduction bool) { http.SetCookie(w, &http.Cookie{ Name: "jwt", Value: "", Expires: time.Now(), MaxAge: -1, Secure: isProduction, HttpOnly: true, Path: "/", }) } func setJwtCookie(w http.ResponseWriter, token *jwtauth.JWTAuth, isProduction bool, u user.User) error { _, tokenString, err := token.Encode(map[string]any{"user_id": u.ID, "token_version": u.TokenVersion}) if err != nil { return err } http.SetCookie(w, &http.Cookie{ Name: "jwt", Value: tokenString, Expires: time.Now().Add(24 * time.Hour), Secure: isProduction, HttpOnly: true, Path: "/", }) return nil } func (h *LoginHandler) SetJwtCookie(w *http.ResponseWriter, u user.User) error { return setJwtCookie(*w, h.Token, h.IsProduction, u) } func (h *LoginHandler) Login(w http.ResponseWriter, r *http.Request) { var credentials LoginCredentials err := json.NewDecoder(r.Body).Decode(&credentials) if err != nil { http.Error(w, apperr.BodyErrorMessage, http.StatusBadRequest) return } err = validator.Validate.Struct(credentials) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } dbUser, err := h.AuthenticateUseCase.Authenticate(r.Context(), credentials.Username, credentials.Password) if err != nil { http.Error(w, apperr.LogsErrorMessage, http.StatusForbidden) return } _ = h.SetJwtCookie(&w, dbUser) message, _ := json.Marshal(map[string]any{"message": "User logged in! HTTPonly jwt cookie created"}) _, _ = w.Write(message) } func (h *LoginHandler) Register(w http.ResponseWriter, r *http.Request) { var credentials RegisterCredentials err := json.NewDecoder(r.Body).Decode(&credentials) if err != nil { http.Error(w, apperr.BodyErrorMessage, http.StatusBadRequest) return } err = validator.Validate.Struct(credentials) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } createdUser, err := h.CreateUseCase.CreateUser(useCases.CreateUserCommand{ Username: credentials.Username, Password: credentials.Password, Email: credentials.Email, }) if err != nil { if errors.Is(err, user.ErrRegistrationClosed) { http.Error(w, apperr.RegistrationClosedErrorMessage, http.StatusForbidden) return } http.Error(w, apperr.RegistrationFailedErrorMessage, http.StatusInternalServerError) return } _ = h.SetJwtCookie(&w, createdUser) message, _ := json.Marshal(map[string]any{"message": "User registered! HTTPonly jwt cookie created"}) _, _ = w.Write(message) } func (h *LoginHandler) RegistrationStatus(w http.ResponseWriter, _ *http.Request) { body, _ := json.Marshal(map[string]bool{"open": h.CreateUseCase.IsRegistrationOpen()}) w.Header().Set("Cache-Control", "no-store") _, _ = w.Write(body) } func (h *LoginHandler) Logout(w http.ResponseWriter, r *http.Request) { h.AuthenticateUseCase.Logout(r.Context()) removeJwtCookie(w, h.IsProduction) message, _ := json.Marshal(map[string]any{"message": "User logged out! HTTPonly jwt cookie deleted"}) _, _ = w.Write(message) }