feat(vault): add token introspection endpoint
Some checks failed
Build and Release / Trigger Server Rebuild (push) Has been skipped
Build and Release / Tests (push) Has been cancelled
Build and Release / Lint (push) Has been cancelled
Build and Release / Create Release (push) Has been skipped
Build and Release / Build Keygen Utility (arm64, linux) (push) Has been skipped
Build and Release / Build Keygen Utility (amd64, darwin) (push) Has been skipped
Build and Release / Build Keygen Utility (amd64, linux) (push) Has been skipped
Build and Release / Build Keygen Utility (amd64, windows) (push) Has been skipped
Build and Release / Build Keygen Utility (arm64, darwin) (push) Has been skipped
Some checks failed
Build and Release / Trigger Server Rebuild (push) Has been skipped
Build and Release / Tests (push) Has been cancelled
Build and Release / Lint (push) Has been cancelled
Build and Release / Create Release (push) Has been skipped
Build and Release / Build Keygen Utility (arm64, linux) (push) Has been skipped
Build and Release / Build Keygen Utility (amd64, darwin) (push) Has been skipped
Build and Release / Build Keygen Utility (amd64, linux) (push) Has been skipped
Build and Release / Build Keygen Utility (amd64, windows) (push) Has been skipped
Build and Release / Build Keygen Utility (arm64, darwin) (push) Has been skipped
Add GET /token/info API endpoint for vault token introspection. Allows clients to validate tokens and retrieve scope, permissions, and expiration info. Implement GetTokenInfo service method that validates tokens without checking specific permissions.
This commit is contained in:
@@ -99,6 +99,16 @@ type AuditEntryResponse struct {
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
}
|
||||
|
||||
// TokenInfoResponse represents token information for introspection
|
||||
type TokenInfoResponse struct {
|
||||
Scope string `json:"scope"`
|
||||
Description string `json:"description,omitempty"`
|
||||
ExpiresAt int64 `json:"expires_at,omitempty"`
|
||||
CanRead bool `json:"can_read"`
|
||||
CanWrite bool `json:"can_write"`
|
||||
IsAdmin bool `json:"is_admin"`
|
||||
}
|
||||
|
||||
// API Request types
|
||||
|
||||
// CreateSecretRequest is the request body for creating a secret
|
||||
@@ -182,6 +192,9 @@ func RegisterRepoAPIRoutes(r plugins.PluginRouter, lic *license.Manager) {
|
||||
r.Post("/tokens", apiCreateToken(lic))
|
||||
r.Delete("/tokens/{id}", apiRevokeToken(lic))
|
||||
|
||||
// Token introspection (uses Bearer auth)
|
||||
r.Get("/token/info", apiGetTokenInfo(lic))
|
||||
|
||||
// Key rotation (enterprise)
|
||||
r.Post("/rotate-key", apiRotateKey(lic))
|
||||
})
|
||||
@@ -884,6 +897,84 @@ func apiRevokeToken(lic *license.Manager) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func apiGetTokenInfo(lic *license.Manager) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if !requireWebLicense(lic, r) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx := getRepoContext(r)
|
||||
if ctx == nil || ctx.Repo.Repository == nil {
|
||||
http.Error(w, "Repository context not found", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Get token from Authorization header
|
||||
auth := r.Header.Get("Authorization")
|
||||
if auth == "" {
|
||||
ctx.JSON(http.StatusUnauthorized, map[string]any{
|
||||
"error": "unauthorized",
|
||||
"message": "Authorization header required",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Support "Bearer gvt_xxx" or just "gvt_xxx"
|
||||
rawToken := auth
|
||||
if strings.HasPrefix(auth, "Bearer ") {
|
||||
rawToken = strings.TrimPrefix(auth, "Bearer ")
|
||||
} else if strings.HasPrefix(auth, "token ") {
|
||||
rawToken = strings.TrimPrefix(auth, "token ")
|
||||
}
|
||||
|
||||
// Validate the token without checking permissions
|
||||
token, err := services.GetTokenInfo(ctx, rawToken)
|
||||
if err != nil {
|
||||
switch err {
|
||||
case services.ErrInvalidToken:
|
||||
ctx.JSON(http.StatusUnauthorized, map[string]any{
|
||||
"error": "invalid_token",
|
||||
"message": "Invalid vault token",
|
||||
})
|
||||
case services.ErrTokenExpired:
|
||||
ctx.JSON(http.StatusUnauthorized, map[string]any{
|
||||
"error": "token_expired",
|
||||
"message": "Vault token has expired",
|
||||
})
|
||||
case services.ErrTokenRevoked:
|
||||
ctx.JSON(http.StatusUnauthorized, map[string]any{
|
||||
"error": "token_revoked",
|
||||
"message": "Vault token has been revoked",
|
||||
})
|
||||
default:
|
||||
ctx.JSON(http.StatusInternalServerError, map[string]any{
|
||||
"error": "internal_error",
|
||||
"message": err.Error(),
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Verify token is for this repo
|
||||
if token.RepoID != ctx.Repo.Repository.ID {
|
||||
ctx.JSON(http.StatusForbidden, map[string]any{
|
||||
"error": "forbidden",
|
||||
"message": "Token not valid for this repository",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, TokenInfoResponse{
|
||||
Scope: string(token.Scope),
|
||||
Description: token.Description,
|
||||
ExpiresAt: int64(token.ExpiresUnix),
|
||||
CanRead: token.Scope.CanRead("*"),
|
||||
CanWrite: token.Scope.CanWrite("*"),
|
||||
IsAdmin: token.Scope.IsAdmin(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func apiRotateKey(lic *license.Manager) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if !requireWebLicense(lic, r) {
|
||||
|
||||
@@ -148,3 +148,33 @@ func ValidateToken(ctx context.Context, rawToken string, action string, secretNa
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// GetTokenInfo returns token information without checking permissions
|
||||
// This is used for token introspection by clients
|
||||
func GetTokenInfo(ctx context.Context, rawToken string) (*models.VaultToken, error) {
|
||||
// Hash the provided token
|
||||
hash := sha256.Sum256([]byte(rawToken))
|
||||
hashedToken := hex.EncodeToString(hash[:])
|
||||
|
||||
// Find the token
|
||||
token := &models.VaultToken{}
|
||||
has, err := db.GetEngine(ctx).Where("token_hash = ?", hashedToken).Get(token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !has {
|
||||
return nil, ErrInvalidToken
|
||||
}
|
||||
|
||||
// Check if revoked
|
||||
if token.IsRevoked() {
|
||||
return nil, ErrTokenRevoked
|
||||
}
|
||||
|
||||
// Check if expired
|
||||
if token.IsExpired() {
|
||||
return nil, ErrTokenExpired
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user