Files
chorus/portal/handler/api_keys.go
T

127 lines
3.6 KiB
Go

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")
}