127 lines
3.6 KiB
Go
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")
|
|
}
|