package handler import ( "encoding/json" "errors" "net/http" "strings" "time" "unicode/utf8" "5803025048/W04/internal/auth" "5803025048/W04/internal/middleware" "5803025048/W04/internal/models" "5803025048/W04/internal/store" "github.com/google/uuid" ) // bcrypt menolak password lebih dari 72 byte. const maxPasswordBytes = 72 type AuthHandler struct { userStore UserStore sessionStore SessionStore } func NewAuthHandler(userStore UserStore, sessionStore SessionStore) *AuthHandler { return &AuthHandler{ userStore: userStore, sessionStore: sessionStore, } } type authRequest struct { Username string `json:"username"` Password string `json:"password"` } func (h *AuthHandler) Register(w http.ResponseWriter, r *http.Request) { var req authRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "Bad Request") return } req.Username = strings.TrimSpace(req.Username) n := utf8.RuneCountInString(req.Username) if n < 3 || n > 32 || req.Password == "" || len(req.Password) > maxPasswordBytes { writeError(w, http.StatusBadRequest, "Invalid username or password") return } hash, err := auth.HashPassword(req.Password) if err != nil { writeError(w, http.StatusInternalServerError, "Internal Server Error") return } user := &models.User{ ID: uuid.New().String(), Username: req.Username, PasswordHash: hash, } if err := h.userStore.Create(r.Context(), user); err != nil { if errors.Is(err, store.ErrUsernameTaken) { writeError(w, http.StatusConflict, "Username already exists") return } writeError(w, http.StatusInternalServerError, "Internal Server Error") return } writeJSON(w, http.StatusCreated, user) } func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) { var req authRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "Bad Request") return } user, err := h.userStore.GetByUsername(r.Context(), req.Username) if err != nil { if errors.Is(err, store.ErrNotFound) { writeError(w, http.StatusUnauthorized, "Invalid credentials") return } writeError(w, http.StatusInternalServerError, "Internal Server Error") return } if !auth.CheckPassword(req.Password, user.PasswordHash) { writeError(w, http.StatusUnauthorized, "Invalid credentials") return } token, err := auth.GenerateToken() if err != nil { writeError(w, http.StatusInternalServerError, "Internal Server Error") return } session := &models.Session{ Token: token, UserID: user.ID, CreatedAt: time.Now(), } if err := h.sessionStore.Create(r.Context(), session); err != nil { writeError(w, http.StatusInternalServerError, "Internal Server Error") return } writeJSON(w, http.StatusOK, map[string]string{"session_token": session.Token}) } func (h *AuthHandler) Logout(w http.ResponseWriter, r *http.Request) { token, ok := middleware.BearerToken(r) if !ok { writeError(w, http.StatusUnauthorized, "Unauthorized") return } if _, err := h.sessionStore.Get(r.Context(), token); err != nil { if errors.Is(err, store.ErrNotFound) { writeError(w, http.StatusUnauthorized, "Unauthorized") return } writeError(w, http.StatusInternalServerError, "Internal Server Error") return } if err := h.sessionStore.Delete(r.Context(), token); err != nil { writeError(w, http.StatusInternalServerError, "Internal Server Error") return } w.WriteHeader(http.StatusNoContent) }