mirror of
https://github.com/veops/oneterm.git
synced 2025-09-27 03:36:02 +08:00
195 lines
6.0 KiB
Go
195 lines
6.0 KiB
Go
package controller
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/samber/lo"
|
|
"github.com/spf13/cast"
|
|
|
|
"github.com/veops/oneterm/internal/acl"
|
|
"github.com/veops/oneterm/internal/model"
|
|
"github.com/veops/oneterm/internal/service"
|
|
"github.com/veops/oneterm/pkg/config"
|
|
myErrors "github.com/veops/oneterm/pkg/errors"
|
|
)
|
|
|
|
var (
|
|
accountService = service.NewAccountService()
|
|
|
|
accountPreHooks = []preHook[*model.Account]{
|
|
// Validate public key
|
|
func(ctx *gin.Context, data *model.Account) {
|
|
if err := accountService.ValidatePublicKey(data); err != nil {
|
|
ctx.AbortWithError(http.StatusBadRequest, &myErrors.ApiError{Code: myErrors.ErrWrongPvk, Data: nil})
|
|
return
|
|
}
|
|
},
|
|
// Encrypt sensitive data
|
|
func(ctx *gin.Context, data *model.Account) {
|
|
accountService.EncryptSensitiveData(data)
|
|
},
|
|
}
|
|
|
|
accountPostHooks = []postHook[*model.Account]{
|
|
// Attach asset count
|
|
func(ctx *gin.Context, data []*model.Account) {
|
|
if err := accountService.AttachAssetCount(ctx, data); err != nil {
|
|
return
|
|
}
|
|
},
|
|
}
|
|
|
|
accountDcs = []deleteCheck{
|
|
// Check dependencies
|
|
func(ctx *gin.Context, id int) {
|
|
assetName, err := accountService.CheckAssetDependencies(ctx, id)
|
|
if err == nil && assetName == "" {
|
|
return
|
|
}
|
|
code := lo.Ternary(err == nil, http.StatusBadRequest, http.StatusInternalServerError)
|
|
err = lo.Ternary[error](err == nil, &myErrors.ApiError{Code: myErrors.ErrHasDepency, Data: map[string]any{"name": assetName}}, err)
|
|
ctx.AbortWithError(code, err)
|
|
},
|
|
}
|
|
)
|
|
|
|
// CreateAccount godoc
|
|
//
|
|
// @Tags account
|
|
// @Param account body model.Account true "account"
|
|
// @Success 200 {object} HttpResponse
|
|
// @Router /account [post]
|
|
func (c *Controller) CreateAccount(ctx *gin.Context) {
|
|
doCreate(ctx, true, &model.Account{}, config.RESOURCE_ACCOUNT, accountPreHooks...)
|
|
}
|
|
|
|
// DeleteAccount godoc
|
|
//
|
|
// @Tags account
|
|
// @Param id path int true "account id"
|
|
// @Success 200 {object} HttpResponse
|
|
// @Router /account/:id [delete]
|
|
func (c *Controller) DeleteAccount(ctx *gin.Context) {
|
|
doDelete(ctx, true, &model.Account{}, config.RESOURCE_ACCOUNT, accountDcs...)
|
|
}
|
|
|
|
// UpdateAccount godoc
|
|
//
|
|
// @Tags account
|
|
// @Param id path int true "account id"
|
|
// @Param account body model.Account true "account"
|
|
// @Success 200 {object} HttpResponse
|
|
// @Router /account/:id [put]
|
|
func (c *Controller) UpdateAccount(ctx *gin.Context) {
|
|
doUpdate(ctx, true, &model.Account{}, config.RESOURCE_ACCOUNT, accountPreHooks...)
|
|
}
|
|
|
|
// GetAccounts godoc
|
|
//
|
|
// @Tags account
|
|
// @Param page_index query int true "page_index"
|
|
// @Param page_size query int true "page_size"
|
|
// @Param search query string false "name or account"
|
|
// @Param id query int false "account id"
|
|
// @Param ids query string false "account ids"
|
|
// @Param name query string false "account name"
|
|
// @Param info query bool false "is info mode"
|
|
// @Param type query int false "account type"
|
|
// @Success 200 {object} HttpResponse{data=ListData{list=[]model.Account}}
|
|
// @Router /account [get]
|
|
func (c *Controller) GetAccounts(ctx *gin.Context) {
|
|
info := cast.ToBool(ctx.Query("info"))
|
|
|
|
// Build query with integrated V2 authorization filter
|
|
db, err := accountService.BuildQueryWithAuthorization(ctx)
|
|
if err != nil {
|
|
ctx.AbortWithError(http.StatusInternalServerError, &myErrors.ApiError{Code: myErrors.ErrInternal, Data: map[string]any{"err": err}})
|
|
return
|
|
}
|
|
|
|
// Always exclude sensitive fields (password, pk, phrase) for security
|
|
// These fields require separate MFA-protected API calls to access
|
|
if info {
|
|
db = db.Select("id", "name", "account")
|
|
} else {
|
|
// Exclude sensitive fields but include other metadata
|
|
db = db.Select("id", "name", "account", "account_type", "resource_id",
|
|
"creator_id", "updater_id", "created_at", "updated_at", "deleted_at")
|
|
}
|
|
|
|
doGet(ctx, false, db, config.RESOURCE_ACCOUNT, accountPostHooks...)
|
|
}
|
|
|
|
// GetAccountCredentials godoc
|
|
//
|
|
// @Tags account
|
|
// @Summary Get account credentials with MFA verification
|
|
// @Param id path int true "Account ID"
|
|
// @Param X-MFA-Token header string true "MFA verification token"
|
|
// @Success 200 {object} HttpResponse{data=model.Account}
|
|
// @Router /account/{id}/credentials [post]
|
|
func (c *Controller) GetAccountCredentials(ctx *gin.Context) {
|
|
// Get account ID from path parameter
|
|
accountId := cast.ToInt(ctx.Param("id"))
|
|
if accountId == 0 {
|
|
ctx.AbortWithError(http.StatusBadRequest, &myErrors.ApiError{
|
|
Code: myErrors.ErrInvalidArgument,
|
|
Data: map[string]any{"err": "Invalid account ID"},
|
|
})
|
|
return
|
|
}
|
|
|
|
// Get MFA token from header
|
|
mfaToken := ctx.GetHeader("X-Mfa-Token")
|
|
if mfaToken == "" {
|
|
ctx.AbortWithError(http.StatusUnauthorized, errors.New("MFA token required in X-MFA-Token header"))
|
|
return
|
|
}
|
|
|
|
// Verify MFA token using ACL service
|
|
if !acl.VerifyMFAToken(mfaToken) {
|
|
ctx.AbortWithError(http.StatusUnauthorized, errors.New("MFA token verification failed"))
|
|
return
|
|
}
|
|
|
|
// Build query with authorization check
|
|
db, err := accountService.BuildQueryWithAuthorization(ctx)
|
|
if err != nil {
|
|
ctx.AbortWithError(http.StatusInternalServerError, &myErrors.ApiError{
|
|
Code: myErrors.ErrInternal,
|
|
Data: map[string]any{"err": err},
|
|
})
|
|
return
|
|
}
|
|
|
|
// Query for the specific account with all fields (including sensitive ones)
|
|
var account model.Account
|
|
if err := db.Where("id = ?", accountId).First(&account).Error; err != nil {
|
|
ctx.AbortWithError(http.StatusNotFound, &myErrors.ApiError{
|
|
Data: map[string]any{"err": "Account not found or access denied"},
|
|
})
|
|
return
|
|
}
|
|
|
|
// Decrypt sensitive data before returning
|
|
accountService.DecryptSensitiveData([]*model.Account{&account})
|
|
|
|
ctx.JSON(http.StatusOK, HttpResponse{
|
|
Data: account,
|
|
})
|
|
}
|
|
|
|
// GetAccountIdsByAuthorization gets account IDs by authorization
|
|
func GetAccountIdsByAuthorization(ctx *gin.Context) ([]int, error) {
|
|
assetIds, err := GetAssetIdsByAuthorization(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
_, _, authorizationIds := getIdsByAuthorizationIds(ctx)
|
|
|
|
return accountService.GetAccountIdsByAuthorization(ctx, assetIds, authorizationIds)
|
|
}
|