177 lines
4.9 KiB
Go
177 lines
4.9 KiB
Go
package apikey
|
|
|
|
import (
|
|
"RenewCMS/api/pkg/apperr"
|
|
"RenewCMS/api/pkg/claims"
|
|
validator "RenewCMS/api/pkg/validator"
|
|
domain "RenewCMS/internal/domain/apikey"
|
|
"RenewCMS/internal/domain/user"
|
|
"RenewCMS/internal/infrastructure/useCases"
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
)
|
|
|
|
type Handler struct {
|
|
CreateUseCase *useCases.CreateAPIKeyUseCase
|
|
ListUseCase *useCases.ListAPIKeysUseCase
|
|
RevokeUseCase *useCases.RevokeAPIKeyUseCase
|
|
}
|
|
|
|
type CreateAPIKeyRequest struct {
|
|
Name string `json:"name" validate:"required,max=60"`
|
|
ExpiresAt *time.Time `json:"expires_at"`
|
|
}
|
|
|
|
type createdKeyResponse struct {
|
|
ID uint32 `json:"id"`
|
|
Name string `json:"name"`
|
|
Prefix string `json:"prefix"`
|
|
Key string `json:"key"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
ExpiresAt *time.Time `json:"expires_at"`
|
|
}
|
|
|
|
type keyListItem struct {
|
|
ID uint32 `json:"id"`
|
|
UserID uint32 `json:"user_id"`
|
|
Name string `json:"name"`
|
|
Prefix string `json:"prefix"`
|
|
Status domain.Status `json:"status"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
LastUsedAt *time.Time `json:"last_used_at"`
|
|
ExpiresAt *time.Time `json:"expires_at"`
|
|
RevokedAt *time.Time `json:"revoked_at"`
|
|
CreatedByName *string `json:"created_by_name,omitempty"`
|
|
}
|
|
|
|
func NewHandler(create *useCases.CreateAPIKeyUseCase, list *useCases.ListAPIKeysUseCase, revoke *useCases.RevokeAPIKeyUseCase) *Handler {
|
|
return &Handler{CreateUseCase: create, ListUseCase: list, RevokeUseCase: revoke}
|
|
}
|
|
|
|
func (h *Handler) Create(w http.ResponseWriter, r *http.Request) {
|
|
var request CreateAPIKeyRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
|
http.Error(w, apperr.BodyErrorMessage, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if err := validator.Validate.Struct(request); err != nil {
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
userId, ok := claims.UserId(r)
|
|
if !ok {
|
|
http.Error(w, apperr.UnauthorizedErrorMessage, http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
created, err := h.CreateUseCase.CreateAPIKey(r.Context(), useCases.CreateAPIKeyCommand{
|
|
UserID: userId,
|
|
Name: request.Name,
|
|
ExpiresAt: request.ExpiresAt,
|
|
})
|
|
if err != nil {
|
|
if errors.Is(err, domain.ErrExpiryNotInFuture) {
|
|
http.Error(w, apperr.APIKeyExpiryErrorMessage, http.StatusBadRequest)
|
|
return
|
|
}
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
body, err := json.Marshal(createdKeyResponse{
|
|
ID: created.Key.ID,
|
|
Name: created.Key.Name,
|
|
Prefix: created.Key.Prefix,
|
|
Key: created.RawKey,
|
|
CreatedAt: created.Key.CreatedAt,
|
|
ExpiresAt: created.Key.ExpiresAt,
|
|
})
|
|
if err != nil {
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
w.Header().Set("Cache-Control", "no-store")
|
|
w.WriteHeader(http.StatusCreated)
|
|
_, _ = w.Write(body)
|
|
}
|
|
|
|
func (h *Handler) List(w http.ResponseWriter, r *http.Request) {
|
|
userId, ok := claims.UserId(r)
|
|
if !ok {
|
|
http.Error(w, apperr.UnauthorizedErrorMessage, http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
entries, err := h.ListUseCase.ListAPIKeys(userId)
|
|
if err != nil {
|
|
if errors.Is(err, user.ErrUserNotFound) {
|
|
http.Error(w, apperr.UnauthorizedErrorMessage, http.StatusUnauthorized)
|
|
return
|
|
}
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
items := make([]keyListItem, 0, len(entries))
|
|
for _, entry := range entries {
|
|
key := entry.Key
|
|
item := keyListItem{
|
|
ID: key.ID,
|
|
UserID: key.UserID,
|
|
Name: key.Name,
|
|
Prefix: key.Prefix,
|
|
Status: entry.Status,
|
|
CreatedAt: key.CreatedAt,
|
|
LastUsedAt: key.LastUsedAt,
|
|
ExpiresAt: key.ExpiresAt,
|
|
RevokedAt: key.RevokedAt,
|
|
}
|
|
if entry.ListsCreator {
|
|
item.CreatedByName = &entry.CreatorName
|
|
}
|
|
items = append(items, item)
|
|
}
|
|
|
|
body, err := json.Marshal(items)
|
|
if err != nil {
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
w.Header().Set("Cache-Control", "no-store")
|
|
_, _ = w.Write(body)
|
|
}
|
|
|
|
func (h *Handler) Revoke(w http.ResponseWriter, r *http.Request) {
|
|
id, err := strconv.ParseUint(chi.URLParam(r, "id"), 10, 32)
|
|
if err != nil {
|
|
http.Error(w, apperr.IdUint32ErrorMessage, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
userId, ok := claims.UserId(r)
|
|
if !ok {
|
|
http.Error(w, apperr.UnauthorizedErrorMessage, http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
if err := h.RevokeUseCase.RevokeAPIKey(r.Context(), userId, uint32(id)); err != nil {
|
|
switch {
|
|
case errors.Is(err, domain.ErrAPIKeyNotFound):
|
|
http.Error(w, apperr.APIKeyNotFoundErrorMessage, http.StatusNotFound)
|
|
case errors.Is(err, user.ErrUserNotFound):
|
|
http.Error(w, apperr.UnauthorizedErrorMessage, http.StatusUnauthorized)
|
|
default:
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
}
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|