harryagasi
[KM-418]feat: align analysis flow contracts and scoped catalog rebuild
80fa986
Raw
History Blame Contribute Delete
4.14 kB
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)
}