package auth import ( "encoding/json" "errors" "log/slog" "net/http" ) type loginRequest struct { Email string `json:"email"` Password string `json:"password"` } type refreshRequest struct { RefreshToken string `json:"refresh_token"` } type APIResponse struct { Status string `json:"status"` Message string `json:"message"` Data any `json:"data,omitempty"` } type Handler struct { svc *Service } func NewHandler(svc *Service) *Handler { return &Handler{svc: svc} } // Login godoc // // @Summary Login user and issue token pair // @Description Verifikasi email dan password, lalu mengembalikan profil user, access_token Bearer 1 jam, dan refresh_token 7 hari. // @Tags auth // @Accept json // @Produce json // @Param body body loginRequest true "Login credentials" // @Success 200 {object} APIResponse{data=AuthResult} // @Failure 400 {object} APIResponse // @Failure 401 {object} APIResponse // @Failure 403 {object} APIResponse // @Failure 404 {object} APIResponse // @Failure 500 {object} APIResponse // @Router /api/login [post] func (h *Handler) Login(w http.ResponseWriter, r *http.Request) { var req loginRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeJSON(w, http.StatusBadRequest, APIResponse{Status: "error", Message: "invalid request body"}) return } if req.Email == "" || req.Password == "" { writeJSON(w, http.StatusBadRequest, APIResponse{Status: "error", Message: "email and password are required"}) return } result, err := h.svc.Login(r.Context(), req.Email, req.Password) if err != nil { h.writeAuthError(w, err) return } writeJSON(w, http.StatusOK, APIResponse{ Status: "success", Message: "login successful", Data: result, }) } // Refresh godoc // // @Summary Rotate refresh token and issue token pair // @Description Menggunakan refresh_token aktif untuk menerbitkan access_token baru dan refresh_token pengganti. Refresh token lama tidak dapat dipakai ulang. // @Tags auth // @Accept json // @Produce json // @Param body body refreshRequest true "Refresh token" // @Success 200 {object} APIResponse{data=AuthResult} // @Failure 400 {object} APIResponse // @Failure 401 {object} APIResponse // @Failure 403 {object} APIResponse // @Failure 500 {object} APIResponse // @Router /api/refresh [post] func (h *Handler) Refresh(w http.ResponseWriter, r *http.Request) { var req refreshRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeJSON(w, http.StatusBadRequest, APIResponse{Status: "error", Message: "invalid request body"}) return } if req.RefreshToken == "" { writeJSON(w, http.StatusBadRequest, APIResponse{Status: "error", Message: "refresh_token is required"}) return } result, err := h.svc.Refresh(r.Context(), req.RefreshToken) if err != nil { h.writeAuthError(w, err) return } writeJSON(w, http.StatusOK, APIResponse{Status: "success", Message: "token refreshed", Data: result}) } func (h *Handler) writeAuthError(w http.ResponseWriter, err error) { switch { case errors.Is(err, ErrUserNotFound): writeJSON(w, http.StatusNotFound, APIResponse{Status: "error", Message: "user not found"}) case errors.Is(err, ErrInactive): writeJSON(w, http.StatusForbidden, APIResponse{Status: "error", Message: "user account is inactive"}) case errors.Is(err, ErrWrongPassword): writeJSON(w, http.StatusUnauthorized, APIResponse{Status: "error", Message: "invalid credentials"}) case errors.Is(err, ErrInvalidRefreshToken): writeJSON(w, http.StatusUnauthorized, APIResponse{Status: "error", Message: "invalid refresh token"}) case errors.Is(err, ErrTokenServiceUnavailable): writeJSON(w, http.StatusInternalServerError, APIResponse{Status: "error", Message: "token service unavailable"}) default: slog.Error("auth request failed", "err", err) writeJSON(w, http.StatusInternalServerError, APIResponse{Status: "error", Message: "internal server error"}) } } func writeJSON(w http.ResponseWriter, status int, v any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) _ = json.NewEncoder(w).Encode(v) }