package handler import ( "encoding/json" "errors" "io" "net/http" "time" "git.ilapage.cn/OPC/chorus/portal/service" "git.ilapage.cn/OPC/chorus/portal/web" "github.com/gin-gonic/gin" ) const apiKeyBodyLimit = 16 << 10 func (h *Handler) apiKeysPage(c *gin.Context) { state := currentSession(c) if state.UserID == 0 { c.Redirect(http.StatusSeeOther, "/login?return_to=%2Fapi-keys") return } user, err := h.service.User(c.Request.Context(), state.UserID) if err != nil { c.Status(http.StatusUnauthorized) return } c.Header("Cache-Control", "no-store") c.Header("Content-Type", "text/html; charset=utf-8") if err := h.renderer.Render(c.Writer, "api-keys", web.Page{Title: "API Key", DisplayName: user.DisplayName, CSRFToken: state.CSRFToken}); err != nil { c.Status(http.StatusInternalServerError) } } func (h *Handler) listAPIKeys(c *gin.Context) { keys, err := h.service.ListAPIKeys(c.Request.Context(), currentSession(c).UserID, time.Now().UTC()) if err != nil { h.apiKeyServiceError(c, err) return } noStore(c) c.JSON(http.StatusOK, gin.H{"items": keys}) } func (h *Handler) createAPIKey(c *gin.Context) { var input struct { Name string `json:"name"` ExpiresInDays *int `json:"expires_in_days"` } if !decodeAPIKeyJSON(c, &input) { return } created, err := h.service.CreateAPIKey(c.Request.Context(), currentSession(c).UserID, input.Name, input.ExpiresInDays, time.Now().UTC(), requestID(c)) if err != nil { h.apiKeyServiceError(c, err) return } noStore(c) c.JSON(http.StatusCreated, created) } func (h *Handler) renameAPIKey(c *gin.Context) { id, ok := uintParam(c, "id") if !ok { return } var input struct { Name string `json:"name"` } if !decodeAPIKeyJSON(c, &input) { return } key, err := h.service.RenameAPIKey(c.Request.Context(), currentSession(c).UserID, id, input.Name, time.Now().UTC(), requestID(c)) if err != nil { h.apiKeyServiceError(c, err) return } noStore(c) c.JSON(http.StatusOK, gin.H{"api_key": key}) } func (h *Handler) revokeAPIKey(c *gin.Context) { id, ok := uintParam(c, "id") if !ok { return } key, err := h.service.RevokeAPIKey(c.Request.Context(), currentSession(c).UserID, id, time.Now().UTC(), requestID(c)) if err != nil { h.apiKeyServiceError(c, err) return } noStore(c) c.JSON(http.StatusOK, gin.H{"api_key": key}) } func decodeAPIKeyJSON(c *gin.Context, target any) bool { c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, apiKeyBodyLimit) decoder := json.NewDecoder(c.Request.Body) decoder.DisallowUnknownFields() if err := decoder.Decode(target); err != nil || decoder.Decode(&struct{}{}) != io.EOF { writeError(c, http.StatusBadRequest, "invalid_request", "request body is invalid") return false } return true } func (h *Handler) apiKeyServiceError(c *gin.Context, err error) { switch { case errors.Is(err, service.ErrAccountDisabled): noStore(c) writeError(c, http.StatusForbidden, "account_disabled", "account is not allowed to manage API keys") case errors.Is(err, service.ErrInvalidAPIKeyName): writeError(c, http.StatusBadRequest, "invalid_api_key_name", "API key name is invalid") case errors.Is(err, service.ErrInvalidAPIKeyExpiry): writeError(c, http.StatusBadRequest, "invalid_api_key_expiry", "API key expiry is invalid") case errors.Is(err, service.ErrNotFound): writeError(c, http.StatusNotFound, "not_found", "resource was not found") default: writeError(c, http.StatusInternalServerError, "internal_error", "request could not be completed") } } func noStore(c *gin.Context) { c.Header("Cache-Control", "no-store") c.Header("Pragma", "no-cache") }