mirror of
https://github.com/veops/oneterm.git
synced 2025-09-26 19:31:14 +08:00
feat(backend): implement MFA-protected account credentials endpoint and enhance security
This commit is contained in:
66
backend/internal/acl/mfa.go
Normal file
66
backend/internal/acl/mfa.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package acl
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
|
||||
"github.com/veops/oneterm/pkg/config"
|
||||
)
|
||||
|
||||
// MFAIntrospectRequest represents the request to MFA introspect endpoint
|
||||
type MFAIntrospectRequest struct {
|
||||
MfaToken string `json:"mfa_token"`
|
||||
}
|
||||
|
||||
// MFAIntrospectResponse represents the response from MFA introspect endpoint
|
||||
type MFAIntrospectResponse struct {
|
||||
Active bool `json:"active"`
|
||||
UID int64 `json:"uid"`
|
||||
Scope string `json:"scope"`
|
||||
Exp int64 `json:"exp"`
|
||||
}
|
||||
|
||||
// VerifyMFAToken verifies the MFA token by calling the introspect endpoint
|
||||
func VerifyMFAToken(mfaToken string) bool {
|
||||
// Build URL from config, replacing v1 with common-setting/v1
|
||||
baseURL := config.Cfg.Auth.Acl.Url
|
||||
url := strings.Replace(baseURL, "/v1", "/common-setting/v1", 1) + "/mfa/introspect"
|
||||
|
||||
// Prepare request data
|
||||
reqData := MFAIntrospectRequest{
|
||||
MfaToken: mfaToken,
|
||||
}
|
||||
|
||||
// Create resty client with timeout
|
||||
client := resty.New().SetTimeout(5 * time.Second)
|
||||
|
||||
var mfaResp MFAIntrospectResponse
|
||||
resp, err := client.R().
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetBody(reqData).
|
||||
SetResult(&mfaResp).
|
||||
Post(url)
|
||||
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if resp.StatusCode() != 200 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if token is active
|
||||
if !mfaResp.Active {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if token is not expired
|
||||
now := time.Now().Unix()
|
||||
if mfaResp.Exp > 0 && now > mfaResp.Exp {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -38,24 +39,6 @@ var (
|
||||
return
|
||||
}
|
||||
},
|
||||
// Decrypt sensitive data
|
||||
func(ctx *gin.Context, data []*model.Account) {
|
||||
accountService.DecryptSensitiveData(data)
|
||||
},
|
||||
// Filter sensitive fields for non-admin users
|
||||
func(ctx *gin.Context, data []*model.Account) {
|
||||
info := cast.ToBool(ctx.Query("info"))
|
||||
if !info {
|
||||
currentUser, _ := acl.GetSessionFromCtx(ctx)
|
||||
if !acl.IsAdmin(currentUser) {
|
||||
for _, account := range data {
|
||||
account.Password = ""
|
||||
account.Pk = ""
|
||||
account.Phrase = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
accountDcs = []deleteCheck{
|
||||
@@ -126,14 +109,78 @@ func (c *Controller) GetAccounts(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Apply info mode settings
|
||||
// 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)
|
||||
|
@@ -188,6 +188,50 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/account/{id}/credentials": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"account"
|
||||
],
|
||||
"summary": "Get account credentials with MFA verification",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Account ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "MFA verification token",
|
||||
"name": "X-MFA-Token",
|
||||
"in": "header",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/controller.HttpResponse"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/model.Account"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/asset": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -2340,12 +2384,6 @@ const docTemplate = `{
|
||||
"/proxy": {
|
||||
"get": {
|
||||
"description": "Handle web proxy requests for subdomain-based assets",
|
||||
"consumes": [
|
||||
"*/*"
|
||||
],
|
||||
"produces": [
|
||||
"*/*"
|
||||
],
|
||||
"tags": [
|
||||
"WebProxy"
|
||||
],
|
||||
@@ -2368,23 +2406,6 @@ const docTemplate = `{
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Proxied content"
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid subdomain format",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Session expired page"
|
||||
},
|
||||
"403": {
|
||||
"description": "Access denied",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4261,12 +4282,6 @@ const docTemplate = `{
|
||||
"/web_proxy/cleanup": {
|
||||
"post": {
|
||||
"description": "Clean up web session when browser tab is closed",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"WebProxy"
|
||||
],
|
||||
@@ -4301,12 +4316,6 @@ const docTemplate = `{
|
||||
"/web_proxy/close": {
|
||||
"post": {
|
||||
"description": "Close an active web session and clean up resources",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"WebProxy"
|
||||
],
|
||||
@@ -4334,20 +4343,6 @@ const docTemplate = `{
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid request",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Session not found",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4355,12 +4350,6 @@ const docTemplate = `{
|
||||
"/web_proxy/config/{asset_id}": {
|
||||
"get": {
|
||||
"description": "Get web asset configuration by asset ID",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"WebProxy"
|
||||
],
|
||||
@@ -4380,20 +4369,6 @@ const docTemplate = `{
|
||||
"schema": {
|
||||
"$ref": "#/definitions/model.WebConfig"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid asset ID",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Asset not found",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4401,12 +4376,6 @@ const docTemplate = `{
|
||||
"/web_proxy/external_redirect": {
|
||||
"get": {
|
||||
"description": "Show a page when an external redirect is blocked by the proxy",
|
||||
"consumes": [
|
||||
"text/html"
|
||||
],
|
||||
"produces": [
|
||||
"text/html"
|
||||
],
|
||||
"tags": [
|
||||
"WebProxy"
|
||||
],
|
||||
@@ -4430,12 +4399,6 @@ const docTemplate = `{
|
||||
"/web_proxy/heartbeat": {
|
||||
"post": {
|
||||
"description": "Update the last activity time for a web session (heartbeat)",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"WebProxy"
|
||||
],
|
||||
@@ -4463,20 +4426,6 @@ const docTemplate = `{
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid request",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Session not found",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4484,12 +4433,6 @@ const docTemplate = `{
|
||||
"/web_proxy/sessions/{asset_id}": {
|
||||
"get": {
|
||||
"description": "Get list of active web sessions for a specific asset",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"WebProxy"
|
||||
],
|
||||
@@ -4513,13 +4456,6 @@ const docTemplate = `{
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid asset ID",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4527,12 +4463,6 @@ const docTemplate = `{
|
||||
"/web_proxy/start": {
|
||||
"post": {
|
||||
"description": "Start a new web session for the specified asset",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"WebProxy"
|
||||
],
|
||||
@@ -4554,41 +4484,6 @@ const docTemplate = `{
|
||||
"schema": {
|
||||
"$ref": "#/definitions/web_proxy.StartWebSessionResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid request",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "No permission",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Asset not found",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"429": {
|
||||
"description": "Maximum concurrent connections exceeded",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -177,6 +177,50 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/account/{id}/credentials": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"account"
|
||||
],
|
||||
"summary": "Get account credentials with MFA verification",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Account ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "MFA verification token",
|
||||
"name": "X-MFA-Token",
|
||||
"in": "header",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/controller.HttpResponse"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/model.Account"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/asset": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -2329,12 +2373,6 @@
|
||||
"/proxy": {
|
||||
"get": {
|
||||
"description": "Handle web proxy requests for subdomain-based assets",
|
||||
"consumes": [
|
||||
"*/*"
|
||||
],
|
||||
"produces": [
|
||||
"*/*"
|
||||
],
|
||||
"tags": [
|
||||
"WebProxy"
|
||||
],
|
||||
@@ -2357,23 +2395,6 @@
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Proxied content"
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid subdomain format",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Session expired page"
|
||||
},
|
||||
"403": {
|
||||
"description": "Access denied",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4250,12 +4271,6 @@
|
||||
"/web_proxy/cleanup": {
|
||||
"post": {
|
||||
"description": "Clean up web session when browser tab is closed",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"WebProxy"
|
||||
],
|
||||
@@ -4290,12 +4305,6 @@
|
||||
"/web_proxy/close": {
|
||||
"post": {
|
||||
"description": "Close an active web session and clean up resources",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"WebProxy"
|
||||
],
|
||||
@@ -4323,20 +4332,6 @@
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid request",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Session not found",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4344,12 +4339,6 @@
|
||||
"/web_proxy/config/{asset_id}": {
|
||||
"get": {
|
||||
"description": "Get web asset configuration by asset ID",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"WebProxy"
|
||||
],
|
||||
@@ -4369,20 +4358,6 @@
|
||||
"schema": {
|
||||
"$ref": "#/definitions/model.WebConfig"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid asset ID",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Asset not found",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4390,12 +4365,6 @@
|
||||
"/web_proxy/external_redirect": {
|
||||
"get": {
|
||||
"description": "Show a page when an external redirect is blocked by the proxy",
|
||||
"consumes": [
|
||||
"text/html"
|
||||
],
|
||||
"produces": [
|
||||
"text/html"
|
||||
],
|
||||
"tags": [
|
||||
"WebProxy"
|
||||
],
|
||||
@@ -4419,12 +4388,6 @@
|
||||
"/web_proxy/heartbeat": {
|
||||
"post": {
|
||||
"description": "Update the last activity time for a web session (heartbeat)",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"WebProxy"
|
||||
],
|
||||
@@ -4452,20 +4415,6 @@
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid request",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Session not found",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4473,12 +4422,6 @@
|
||||
"/web_proxy/sessions/{asset_id}": {
|
||||
"get": {
|
||||
"description": "Get list of active web sessions for a specific asset",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"WebProxy"
|
||||
],
|
||||
@@ -4502,13 +4445,6 @@
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid asset ID",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4516,12 +4452,6 @@
|
||||
"/web_proxy/start": {
|
||||
"post": {
|
||||
"description": "Start a new web session for the specified asset",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"WebProxy"
|
||||
],
|
||||
@@ -4543,41 +4473,6 @@
|
||||
"schema": {
|
||||
"$ref": "#/definitions/web_proxy.StartWebSessionResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid request",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "No permission",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Asset not found",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"429": {
|
||||
"description": "Maximum concurrent connections exceeded",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1219,6 +1219,32 @@ paths:
|
||||
$ref: '#/definitions/controller.HttpResponse'
|
||||
tags:
|
||||
- account
|
||||
/account/{id}/credentials:
|
||||
post:
|
||||
parameters:
|
||||
- description: Account ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
- description: MFA verification token
|
||||
in: header
|
||||
name: X-MFA-Token
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/controller.HttpResponse'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/model.Account'
|
||||
type: object
|
||||
summary: Get account credentials with MFA verification
|
||||
tags:
|
||||
- account
|
||||
/asset:
|
||||
get:
|
||||
parameters:
|
||||
@@ -2532,8 +2558,6 @@ paths:
|
||||
- Preference
|
||||
/proxy:
|
||||
get:
|
||||
consumes:
|
||||
- '*/*'
|
||||
description: Handle web proxy requests for subdomain-based assets
|
||||
parameters:
|
||||
- description: Asset subdomain (asset-123.domain.com)
|
||||
@@ -2545,23 +2569,9 @@ paths:
|
||||
in: query
|
||||
name: session_id
|
||||
type: string
|
||||
produces:
|
||||
- '*/*'
|
||||
responses:
|
||||
"200":
|
||||
description: Proxied content
|
||||
"400":
|
||||
description: Invalid subdomain format
|
||||
schema:
|
||||
additionalProperties: true
|
||||
type: object
|
||||
"401":
|
||||
description: Session expired page
|
||||
"403":
|
||||
description: Access denied
|
||||
schema:
|
||||
additionalProperties: true
|
||||
type: object
|
||||
summary: Proxy web requests
|
||||
tags:
|
||||
- WebProxy
|
||||
@@ -3681,8 +3691,6 @@ paths:
|
||||
- time_template
|
||||
/web_proxy/cleanup:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Clean up web session when browser tab is closed
|
||||
parameters:
|
||||
- description: Cleanup request
|
||||
@@ -3693,8 +3701,6 @@ paths:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Session cleaned up
|
||||
@@ -3707,8 +3713,6 @@ paths:
|
||||
- WebProxy
|
||||
/web_proxy/close:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Close an active web session and clean up resources
|
||||
parameters:
|
||||
- description: Session close request
|
||||
@@ -3719,8 +3723,6 @@ paths:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Session closed successfully
|
||||
@@ -3728,23 +3730,11 @@ paths:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
"400":
|
||||
description: Invalid request
|
||||
schema:
|
||||
additionalProperties: true
|
||||
type: object
|
||||
"404":
|
||||
description: Session not found
|
||||
schema:
|
||||
additionalProperties: true
|
||||
type: object
|
||||
summary: Close web session
|
||||
tags:
|
||||
- WebProxy
|
||||
/web_proxy/config/{asset_id}:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Get web asset configuration by asset ID
|
||||
parameters:
|
||||
- description: Asset ID
|
||||
@@ -3752,30 +3742,16 @@ paths:
|
||||
name: asset_id
|
||||
required: true
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/model.WebConfig'
|
||||
"400":
|
||||
description: Invalid asset ID
|
||||
schema:
|
||||
additionalProperties: true
|
||||
type: object
|
||||
"404":
|
||||
description: Asset not found
|
||||
schema:
|
||||
additionalProperties: true
|
||||
type: object
|
||||
summary: Get web asset configuration
|
||||
tags:
|
||||
- WebProxy
|
||||
/web_proxy/external_redirect:
|
||||
get:
|
||||
consumes:
|
||||
- text/html
|
||||
description: Show a page when an external redirect is blocked by the proxy
|
||||
parameters:
|
||||
- description: Target URL that was blocked
|
||||
@@ -3783,8 +3759,6 @@ paths:
|
||||
name: url
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- text/html
|
||||
responses:
|
||||
"200":
|
||||
description: External redirect blocked page
|
||||
@@ -3793,8 +3767,6 @@ paths:
|
||||
- WebProxy
|
||||
/web_proxy/heartbeat:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Update the last activity time for a web session (heartbeat)
|
||||
parameters:
|
||||
- description: Heartbeat request
|
||||
@@ -3805,8 +3777,6 @@ paths:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Heartbeat updated
|
||||
@@ -3814,23 +3784,11 @@ paths:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
"400":
|
||||
description: Invalid request
|
||||
schema:
|
||||
additionalProperties: true
|
||||
type: object
|
||||
"404":
|
||||
description: Session not found
|
||||
schema:
|
||||
additionalProperties: true
|
||||
type: object
|
||||
summary: Update session heartbeat
|
||||
tags:
|
||||
- WebProxy
|
||||
/web_proxy/sessions/{asset_id}:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Get list of active web sessions for a specific asset
|
||||
parameters:
|
||||
- description: Asset ID
|
||||
@@ -3838,8 +3796,6 @@ paths:
|
||||
name: asset_id
|
||||
required: true
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: List of active sessions
|
||||
@@ -3848,18 +3804,11 @@ paths:
|
||||
additionalProperties: true
|
||||
type: object
|
||||
type: array
|
||||
"400":
|
||||
description: Invalid asset ID
|
||||
schema:
|
||||
additionalProperties: true
|
||||
type: object
|
||||
summary: Get active web sessions
|
||||
tags:
|
||||
- WebProxy
|
||||
/web_proxy/start:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Start a new web session for the specified asset
|
||||
parameters:
|
||||
- description: Start session request
|
||||
@@ -3868,38 +3817,11 @@ paths:
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/web_proxy.StartWebSessionRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/web_proxy.StartWebSessionResponse'
|
||||
"400":
|
||||
description: Invalid request
|
||||
schema:
|
||||
additionalProperties: true
|
||||
type: object
|
||||
"403":
|
||||
description: No permission
|
||||
schema:
|
||||
additionalProperties: true
|
||||
type: object
|
||||
"404":
|
||||
description: Asset not found
|
||||
schema:
|
||||
additionalProperties: true
|
||||
type: object
|
||||
"429":
|
||||
description: Maximum concurrent connections exceeded
|
||||
schema:
|
||||
additionalProperties: true
|
||||
type: object
|
||||
"500":
|
||||
description: Internal server error
|
||||
schema:
|
||||
additionalProperties: true
|
||||
type: object
|
||||
summary: Start web session
|
||||
tags:
|
||||
- WebProxy
|
||||
|
@@ -62,6 +62,7 @@ func SetupRouter(r *gin.Engine) {
|
||||
account.DELETE("/:id", c.DeleteAccount)
|
||||
account.PUT("/:id", c.UpdateAccount)
|
||||
account.GET("", c.GetAccounts)
|
||||
account.POST("/:id/credentials", c.GetAccountCredentials)
|
||||
}
|
||||
|
||||
asset := v1.Group("asset")
|
||||
|
@@ -9,21 +9,21 @@ import (
|
||||
type Account struct {
|
||||
Id int `json:"id" gorm:"column:id;primarykey;autoIncrement"`
|
||||
Name string `json:"name" gorm:"column:name;uniqueIndex:name_del;size:128"`
|
||||
AccountType int `json:"account_type" gorm:"column:account_type"`
|
||||
AccountType int `json:"account_type,omitempty" gorm:"column:account_type"`
|
||||
Account string `json:"account" gorm:"column:account"`
|
||||
Password string `json:"password" gorm:"column:password"`
|
||||
Pk string `json:"pk" gorm:"column:pk"`
|
||||
Phrase string `json:"phrase" gorm:"column:phrase"`
|
||||
Password string `json:"password,omitempty" gorm:"column:password"`
|
||||
Pk string `json:"pk,omitempty" gorm:"column:pk"`
|
||||
Phrase string `json:"phrase,omitempty" gorm:"column:phrase"`
|
||||
|
||||
Permissions []string `json:"permissions" gorm:"-"`
|
||||
ResourceId int `json:"resource_id" gorm:"column:resource_id"`
|
||||
CreatorId int `json:"creator_id" gorm:"column:creator_id"`
|
||||
UpdaterId int `json:"updater_id" gorm:"column:updater_id"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"column:created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"column:updated_at"`
|
||||
Permissions []string `json:"permissions,omitempty" gorm:"-"`
|
||||
ResourceId int `json:"resource_id,omitempty" gorm:"column:resource_id"`
|
||||
CreatorId int `json:"creator_id,omitempty" gorm:"column:creator_id"`
|
||||
UpdaterId int `json:"updater_id,omitempty" gorm:"column:updater_id"`
|
||||
CreatedAt time.Time `json:"created_at,omitempty" gorm:"column:created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at,omitempty" gorm:"column:updated_at"`
|
||||
DeletedAt soft_delete.DeletedAt `json:"-" gorm:"column:deleted_at;uniqueIndex:name_del"`
|
||||
|
||||
AssetCount int64 `json:"asset_count" gorm:"-"`
|
||||
AssetCount int64 `json:"asset_count,omitempty" gorm:"-"`
|
||||
}
|
||||
|
||||
func (m *Account) TableName() string {
|
||||
|
Reference in New Issue
Block a user