mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-28 16:00:18 +00:00
253 lines
13 KiB
Go
253 lines
13 KiB
Go
package v2
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"io"
|
|
"net/http"
|
|
|
|
"github.com/1Panel-dev/1Panel/core/app/api/v2/helper"
|
|
"github.com/1Panel-dev/1Panel/core/app/auth"
|
|
"github.com/1Panel-dev/1Panel/core/app/dto"
|
|
"github.com/1Panel-dev/1Panel/core/app/service"
|
|
"github.com/1Panel-dev/1Panel/core/buserr"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
func bindAPIKeyRequest(c *gin.Context, value interface{}) bool {
|
|
if _, err := auth.RequireAPIKeyManagement(c); err != nil {
|
|
helper.BadAuth(c, "ErrNotLogin", nil)
|
|
return false
|
|
}
|
|
decoder := json.NewDecoder(http.MaxBytesReader(c.Writer, c.Request.Body, 32<<10))
|
|
decoder.DisallowUnknownFields()
|
|
if err := decoder.Decode(value); err != nil {
|
|
helper.BadRequest(c, err)
|
|
return false
|
|
}
|
|
if err := decoder.Decode(new(interface{})); err != io.EOF {
|
|
helper.BadRequest(c, errors.New("unexpected JSON data"))
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func apiKeyError(c *gin.Context, err error) {
|
|
var business buserr.BusinessError
|
|
if errors.As(err, &business) {
|
|
helper.ErrorWithDetail(c, http.StatusBadRequest, business.Msg, business.Err)
|
|
return
|
|
}
|
|
helper.InternalServer(c, err)
|
|
}
|
|
|
|
// Swag's legacy || syntax combines schemes in one requirement (AND);
|
|
// separate @Security lines represent alternative authentication methods.
|
|
// @Tags API Key
|
|
// @Summary List the current account's API keys
|
|
// @Description Accepts a panel login session with CSRF protection, or a valid API Key signature. The authenticated account determines ownership; another account cannot be selected in the request.
|
|
// @Description Returns key metadata and masked hints, never plaintext secrets. The legacy key is included when present; deleted keys are excluded.
|
|
// @Description Authentication and business errors use HTTP 200 with envelope code 400, 401 or 500; check code rather than HTTP status alone.
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param request body dto.APIKeySearch true "Pagination and revoked-key filter"
|
|
// @Param 1Panel-Key-ID header string false "Optional ID of the authenticating independent key; omit for legacy keys"
|
|
// @Param 1Panel-Signature-Version header string false "Signature algorithm; omission accepts either supported algorithm" Enums(v1,md5,hmac-sha256)
|
|
// @Success 200 {object} dto.Response{data=dto.APIKeyPage} "code=200 on success; data is null on errors"
|
|
// @Failure 403 {object} dto.Response "Invalid or missing CSRF token for a session request"
|
|
// @Header 200 {string} Cache-Control "no-store"
|
|
// @Security ApiKeyAuth || Timestamp
|
|
// @Security PanelSession || CSRFToken
|
|
// @Router /core/auth/api/keys/search [post]
|
|
func (b *BaseApi) SearchAPIKeys(c *gin.Context) {
|
|
var req dto.APIKeySearch
|
|
if !bindAPIKeyRequest(c, &req) {
|
|
return
|
|
}
|
|
result, err := service.NewAPIKeyService().Search(c, req)
|
|
if err != nil {
|
|
apiKeyError(c, err)
|
|
return
|
|
}
|
|
c.Header("Cache-Control", "no-store")
|
|
helper.SuccessWithData(c, result)
|
|
}
|
|
|
|
// @Tags API Key
|
|
// @Summary Create an API key for the current account
|
|
// @Description Accepts a panel login session with CSRF protection, or a valid API Key signature. The new key belongs to the authenticated account.
|
|
// @Description requestID is optional. Omitted or empty values are generated by the server. For idempotent retries, supply a UUID and reuse it with the same payload.
|
|
// @Description A successful first request returns the plaintext secret. Repeating the same requestID and payload returns alreadyCreated=true and an empty apiKey; changing the payload for that requestID is rejected.
|
|
// @Description Omitted expiresAt defaults to 90 days; explicit null means no expiration. Omitted apiKeyValidityTime defaults to 120 minutes; zero disables timestamp validation.
|
|
// @Description API-created keys may have a longer lifetime or broader IP policy than the calling key. Revoking the calling key does not revoke keys it created.
|
|
// @Description Authentication and business errors use HTTP 200 with envelope code 400, 401 or 500; check code rather than HTTP status alone. Unknown fields and bodies larger than 32 KiB are rejected.
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param request body dto.APIKeyCreate true "Key policy and an optional UUID requestID for idempotency"
|
|
// @Param 1Panel-Key-ID header string false "Optional ID of the authenticating independent key; omit for legacy keys"
|
|
// @Param 1Panel-Signature-Version header string false "Signature algorithm; omission accepts either supported algorithm" Enums(v1,md5,hmac-sha256)
|
|
// @Success 200 {object} dto.Response{data=dto.APIKeyCreated} "code=200 on success; data is null on errors"
|
|
// @Failure 403 {object} dto.Response "Invalid or missing CSRF token for a session request"
|
|
// @Header 200 {string} Cache-Control "no-store"
|
|
// @Security ApiKeyAuth || Timestamp
|
|
// @Security PanelSession || CSRFToken
|
|
// @Router /core/auth/api/keys/create [post]
|
|
func (b *BaseApi) CreateAPIKey(c *gin.Context) {
|
|
req := dto.APIKeyCreate{APIKeyFields: dto.APIKeyFields{APIKeyValidityTime: 120}}
|
|
if !bindAPIKeyRequest(c, &req) {
|
|
return
|
|
}
|
|
result, err := service.NewAPIKeyService().Create(c, req)
|
|
if err != nil {
|
|
apiKeyError(c, err)
|
|
return
|
|
}
|
|
c.Header("Cache-Control", "no-store")
|
|
helper.SuccessWithData(c, result)
|
|
}
|
|
|
|
// @Tags API Key
|
|
// @Summary Update an API key's policy
|
|
// @Description Accepts a panel login session with CSRF protection, or a valid API Key signature. Only keys owned by the authenticated account can be changed.
|
|
// @Description Submit the current revision returned by search; stale revisions are rejected. This replaces editable policy fields without changing the secret. expiresAt must be supplied as an RFC 3339 timestamp or null.
|
|
// @Description Use id=legacy for the legacy key; its name is fixed and expiresAt must be null. Revoked or deleted independent keys cannot be updated.
|
|
// @Description Success increments the revision; search again before another mutation. Authentication and business errors use HTTP 200 with envelope code 400, 401 or 500.
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param request body dto.APIKeyUpdate true "Key ID, current revision and complete editable policy"
|
|
// @Param 1Panel-Key-ID header string false "Optional ID of the authenticating independent key; omit for legacy keys"
|
|
// @Param 1Panel-Signature-Version header string false "Signature algorithm; omission accepts either supported algorithm" Enums(v1,md5,hmac-sha256)
|
|
// @Success 200 {object} dto.Response{data=dto.APIKeyMutationResult} "code=200 on success; data is null on errors"
|
|
// @Failure 403 {object} dto.Response "Invalid or missing CSRF token for a session request"
|
|
// @Security ApiKeyAuth || Timestamp
|
|
// @Security PanelSession || CSRFToken
|
|
// @Router /core/auth/api/keys/update [post]
|
|
func (b *BaseApi) UpdateAPIKey(c *gin.Context) {
|
|
var req dto.APIKeyUpdate
|
|
if !bindAPIKeyRequest(c, &req) {
|
|
return
|
|
}
|
|
if err := service.NewAPIKeyService().Update(c, req); err != nil {
|
|
apiKeyError(c, err)
|
|
return
|
|
}
|
|
apiKeyMutationSuccess(c)
|
|
}
|
|
|
|
// @Tags API Key
|
|
// @Summary Enable or disable an API key
|
|
// @Description Accepts a panel login session with CSRF protection, or a valid API Key signature. Only keys owned by the authenticated account can be changed; id=legacy is supported.
|
|
// @Description Submit the current revision returned by search. Revoked or deleted keys cannot be enabled; expired keys must first receive a future expiration time. Disabling a key also closes its active terminal sessions.
|
|
// @Description Success increments the revision; search again before another mutation. Authentication and business errors use HTTP 200 with envelope code 400, 401 or 500.
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param request body dto.APIKeyStatus true "Key ID, current revision and Enable or Disable status"
|
|
// @Param 1Panel-Key-ID header string false "Optional ID of the authenticating independent key; omit for legacy keys"
|
|
// @Param 1Panel-Signature-Version header string false "Signature algorithm; omission accepts either supported algorithm" Enums(v1,md5,hmac-sha256)
|
|
// @Success 200 {object} dto.Response{data=dto.APIKeyMutationResult} "code=200 on success; data is null on errors"
|
|
// @Failure 403 {object} dto.Response "Invalid or missing CSRF token for a session request"
|
|
// @Security ApiKeyAuth || Timestamp
|
|
// @Security PanelSession || CSRFToken
|
|
// @Router /core/auth/api/keys/status [post]
|
|
func (b *BaseApi) SetAPIKeyStatus(c *gin.Context) {
|
|
var req dto.APIKeyStatus
|
|
if !bindAPIKeyRequest(c, &req) {
|
|
return
|
|
}
|
|
if err := service.NewAPIKeyService().Status(c, req); err != nil {
|
|
apiKeyError(c, err)
|
|
return
|
|
}
|
|
apiKeyMutationSuccess(c)
|
|
}
|
|
|
|
// @Tags API Key
|
|
// @Summary Revoke an API key
|
|
// @Description Accepts a panel login session with CSRF protection, or a valid API Key signature. Only keys owned by the authenticated account can be revoked.
|
|
// @Description Submit the current revision returned by search. Revocation destroys the independent key's stored secret and cannot be undone; its metadata remains listed. For id=legacy, the legacy secret is cleared and API access is disabled.
|
|
// @Description A key can revoke itself; subsequent requests signed with it fail. Keys previously created by the revoked key remain independent. Success increments the revision and closes associated terminal sessions.
|
|
// @Description Authentication and business errors use HTTP 200 with envelope code 400, 401 or 500.
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param request body dto.APIKeyMutation true "Key ID and current revision"
|
|
// @Param 1Panel-Key-ID header string false "Optional ID of the authenticating independent key; omit for legacy keys"
|
|
// @Param 1Panel-Signature-Version header string false "Signature algorithm; omission accepts either supported algorithm" Enums(v1,md5,hmac-sha256)
|
|
// @Success 200 {object} dto.Response{data=dto.APIKeyMutationResult} "code=200 on success; data is null on errors"
|
|
// @Failure 403 {object} dto.Response "Invalid or missing CSRF token for a session request"
|
|
// @Security ApiKeyAuth || Timestamp
|
|
// @Security PanelSession || CSRFToken
|
|
// @Router /core/auth/api/keys/revoke [post]
|
|
func (b *BaseApi) RevokeAPIKey(c *gin.Context) {
|
|
var req dto.APIKeyMutation
|
|
if !bindAPIKeyRequest(c, &req) {
|
|
return
|
|
}
|
|
if err := service.NewAPIKeyService().Revoke(c, req); err != nil {
|
|
apiKeyError(c, err)
|
|
return
|
|
}
|
|
apiKeyMutationSuccess(c)
|
|
}
|
|
|
|
func apiKeyMutationSuccess(c *gin.Context) {
|
|
helper.SuccessWithData(c, dto.APIKeyMutationResult{TerminalClosePending: c.GetBool("API_KEY_TERMINAL_CLOSE_PENDING")})
|
|
}
|
|
|
|
// @Tags API Key
|
|
// @Summary Delete an independent API key
|
|
// @Description Accepts a panel login session with CSRF protection, or a valid API Key signature. Only keys owned by the authenticated account can be deleted; id=legacy is not supported.
|
|
// @Description Submit the current revision returned by search. Deletion immediately revokes the key, destroys its stored secret and removes it from lists. Audit and idempotency records are retained. Already-revoked keys can be deleted; deletion cannot be undone.
|
|
// @Description A key can delete itself; subsequent requests signed with it fail. Authentication and business errors use HTTP 200 with envelope code 400, 401 or 500.
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param request body dto.APIKeyMutation true "Independent key ID and current revision"
|
|
// @Param 1Panel-Key-ID header string false "Optional ID of the authenticating independent key; omit for legacy keys"
|
|
// @Param 1Panel-Signature-Version header string false "Signature algorithm; omission accepts either supported algorithm" Enums(v1,md5,hmac-sha256)
|
|
// @Success 200 {object} dto.Response{data=dto.APIKeyMutationResult} "code=200 on success; data is null on errors"
|
|
// @Failure 403 {object} dto.Response "Invalid or missing CSRF token for a session request"
|
|
// @Security ApiKeyAuth || Timestamp
|
|
// @Security PanelSession || CSRFToken
|
|
// @Router /core/auth/api/keys/delete [post]
|
|
func (b *BaseApi) DeleteAPIKey(c *gin.Context) {
|
|
var req dto.APIKeyMutation
|
|
if !bindAPIKeyRequest(c, &req) {
|
|
return
|
|
}
|
|
if err := service.NewAPIKeyService().Delete(c, req); err != nil {
|
|
apiKeyError(c, err)
|
|
return
|
|
}
|
|
apiKeyMutationSuccess(c)
|
|
}
|
|
|
|
// @Tags API Key
|
|
// @Summary Reveal an independent API key's plaintext secret
|
|
// @Description Requires a panel login session and CSRF token. API Key authentication is not supported, including requests that also contain a valid session cookie; omit all 1Panel authentication headers.
|
|
// @Description Only the current account's independent keys can be revealed. Submit the current revision returned by search. Legacy, revoked and deleted keys are rejected; disabled or expired keys can still be revealed.
|
|
// @Description Secret disclosure is audited. Authentication and business errors use HTTP 200 with envelope code 400, 401 or 500. The response must not be cached or logged.
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param request body dto.APIKeyMutation true "Independent key ID and current revision"
|
|
// @Success 200 {object} dto.Response{data=dto.APIKeyRevealed} "code=200 on success; data is null on errors"
|
|
// @Failure 403 {object} dto.Response "Invalid or missing CSRF token"
|
|
// @Header 200 {string} Cache-Control "no-store"
|
|
// @Security PanelSession || CSRFToken
|
|
// @Router /core/auth/api/keys/reveal [post]
|
|
func (b *BaseApi) RevealAPIKey(c *gin.Context) {
|
|
c.Header("Cache-Control", "no-store")
|
|
if _, err := auth.RequireAPIKeySession(c); err != nil {
|
|
helper.BadAuth(c, "ErrNotLogin", nil)
|
|
return
|
|
}
|
|
var req dto.APIKeyMutation
|
|
if !bindAPIKeyRequest(c, &req) {
|
|
return
|
|
}
|
|
result, err := service.NewAPIKeyService().Reveal(c, req)
|
|
if err != nil {
|
|
apiKeyError(c, err)
|
|
return
|
|
}
|
|
helper.SuccessWithData(c, result)
|
|
}
|