-
This commit is contained in:
@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/kms/api-key-service/internal/audit"
|
"github.com/kms/api-key-service/internal/audit"
|
||||||
"github.com/kms/api-key-service/internal/config"
|
"github.com/kms/api-key-service/internal/config"
|
||||||
"github.com/kms/api-key-service/internal/database"
|
"github.com/kms/api-key-service/internal/database"
|
||||||
|
"github.com/kms/api-key-service/internal/domain"
|
||||||
"github.com/kms/api-key-service/internal/handlers"
|
"github.com/kms/api-key-service/internal/handlers"
|
||||||
"github.com/kms/api-key-service/internal/metrics"
|
"github.com/kms/api-key-service/internal/metrics"
|
||||||
"github.com/kms/api-key-service/internal/middleware"
|
"github.com/kms/api-key-service/internal/middleware"
|
||||||
@ -285,9 +286,51 @@ func initializeBootstrapData(ctx context.Context, appService services.Applicatio
|
|||||||
|
|
||||||
logger.Info("Creating internal application for bootstrap", zap.String("app_id", internalAppID))
|
logger.Info("Creating internal application for bootstrap", zap.String("app_id", internalAppID))
|
||||||
|
|
||||||
// This will be implemented when we create the services
|
// Create internal application for system operations
|
||||||
// For now, we'll just log that we need to do this
|
internalAppReq := &domain.CreateApplicationRequest{
|
||||||
logger.Warn("Bootstrap data initialization not yet implemented - will be added when services are ready")
|
AppID: internalAppID,
|
||||||
|
AppLink: "https://kms.internal/system",
|
||||||
|
Type: []domain.ApplicationType{domain.ApplicationTypeStatic, domain.ApplicationTypeUser},
|
||||||
|
CallbackURL: "https://kms.internal/callback",
|
||||||
|
TokenPrefix: "KMS",
|
||||||
|
TokenRenewalDuration: domain.Duration{Duration: 365 * 24 * time.Hour}, // 1 year
|
||||||
|
MaxTokenDuration: domain.Duration{Duration: 365 * 24 * time.Hour}, // 1 year
|
||||||
|
Owner: domain.Owner{
|
||||||
|
Type: domain.OwnerTypeTeam,
|
||||||
|
Name: "KMS System",
|
||||||
|
Owner: "system@kms.internal",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
app, err := appService.Create(ctx, internalAppReq, "system")
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Failed to create internal application", zap.Error(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info("Internal application created successfully",
|
||||||
|
zap.String("app_id", app.AppID),
|
||||||
|
zap.String("hmac_key", app.HMACKey))
|
||||||
|
|
||||||
|
// Create a static token for internal system operations if needed
|
||||||
|
internalTokenReq := &domain.CreateStaticTokenRequest{
|
||||||
|
AppID: internalAppID,
|
||||||
|
Owner: domain.Owner{
|
||||||
|
Type: domain.OwnerTypeTeam,
|
||||||
|
Name: "KMS System Token",
|
||||||
|
Owner: "system@kms.internal",
|
||||||
|
},
|
||||||
|
Permissions: []string{"internal.*", "app.*", "token.*", "audit.*"},
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := tokenService.CreateStaticToken(ctx, internalTokenReq, "system")
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("Failed to create internal system token, continuing...", zap.Error(err))
|
||||||
|
} else {
|
||||||
|
logger.Info("Internal system token created successfully",
|
||||||
|
zap.String("token_id", token.ID.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info("Bootstrap data initialization completed successfully")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -136,6 +136,9 @@ type AuditLogger interface {
|
|||||||
// QueryEvents queries audit events with filters
|
// QueryEvents queries audit events with filters
|
||||||
QueryEvents(ctx context.Context, filter *AuditFilter) ([]*AuditEvent, error)
|
QueryEvents(ctx context.Context, filter *AuditFilter) ([]*AuditEvent, error)
|
||||||
|
|
||||||
|
// GetEventByID retrieves a specific audit event by ID
|
||||||
|
GetEventByID(ctx context.Context, eventID uuid.UUID) (*AuditEvent, error)
|
||||||
|
|
||||||
// GetEventStats returns audit event statistics
|
// GetEventStats returns audit event statistics
|
||||||
GetEventStats(ctx context.Context, filter *AuditStatsFilter) (*AuditStats, error)
|
GetEventStats(ctx context.Context, filter *AuditStatsFilter) (*AuditStats, error)
|
||||||
}
|
}
|
||||||
@ -188,6 +191,7 @@ type auditLogger struct {
|
|||||||
type AuditRepository interface {
|
type AuditRepository interface {
|
||||||
Create(ctx context.Context, event *AuditEvent) error
|
Create(ctx context.Context, event *AuditEvent) error
|
||||||
Query(ctx context.Context, filter *AuditFilter) ([]*AuditEvent, error)
|
Query(ctx context.Context, filter *AuditFilter) ([]*AuditEvent, error)
|
||||||
|
GetByID(ctx context.Context, eventID uuid.UUID) (*AuditEvent, error)
|
||||||
GetStats(ctx context.Context, filter *AuditStatsFilter) (*AuditStats, error)
|
GetStats(ctx context.Context, filter *AuditStatsFilter) (*AuditStats, error)
|
||||||
DeleteOldEvents(ctx context.Context, olderThan time.Time) (int, error)
|
DeleteOldEvents(ctx context.Context, olderThan time.Time) (int, error)
|
||||||
}
|
}
|
||||||
@ -353,6 +357,11 @@ func (a *auditLogger) QueryEvents(ctx context.Context, filter *AuditFilter) ([]*
|
|||||||
return a.repository.Query(ctx, filter)
|
return a.repository.Query(ctx, filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetEventByID retrieves a specific audit event by ID
|
||||||
|
func (a *auditLogger) GetEventByID(ctx context.Context, eventID uuid.UUID) (*AuditEvent, error) {
|
||||||
|
return a.repository.GetByID(ctx, eventID)
|
||||||
|
}
|
||||||
|
|
||||||
// GetEventStats returns audit event statistics
|
// GetEventStats returns audit event statistics
|
||||||
func (a *auditLogger) GetEventStats(ctx context.Context, filter *AuditStatsFilter) (*AuditStats, error) {
|
func (a *auditLogger) GetEventStats(ctx context.Context, filter *AuditStatsFilter) (*AuditStats, error) {
|
||||||
return a.repository.GetStats(ctx, filter)
|
return a.repository.GetStats(ctx, filter)
|
||||||
|
|||||||
@ -282,42 +282,38 @@ func (pm *PermissionManager) evaluatePermission(ctx context.Context, userID, app
|
|||||||
Metadata: make(map[string]string),
|
Metadata: make(map[string]string),
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: In a real implementation, this would:
|
// 1. Fetch user roles from database (if repository is available)
|
||||||
// 1. Fetch user roles from database
|
|
||||||
// 2. Resolve role permissions
|
|
||||||
// 3. Check hierarchical permissions
|
|
||||||
// 4. Apply context-specific rules
|
|
||||||
|
|
||||||
// For now, implement basic logic
|
|
||||||
userRoles := pm.getUserRoles(ctx, userID, appID)
|
userRoles := pm.getUserRoles(ctx, userID, appID)
|
||||||
grantedBy := []string{}
|
grantedBy := []string{}
|
||||||
|
|
||||||
// Check direct permission grants
|
// 2. Check direct permission grants via repository
|
||||||
if pm.hasDirectPermission(userID, appID, permission) {
|
if pm.hasDirectPermissionFromRepo(ctx, userID, appID, permission) {
|
||||||
grantedBy = append(grantedBy, "direct")
|
grantedBy = append(grantedBy, "direct")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check role-based permissions
|
// 3. Check role-based permissions
|
||||||
for _, role := range userRoles {
|
for _, role := range userRoles {
|
||||||
if pm.roleHasPermission(role, permission) {
|
if pm.roleHasPermission(role, permission) {
|
||||||
grantedBy = append(grantedBy, fmt.Sprintf("role:%s", role))
|
grantedBy = append(grantedBy, fmt.Sprintf("role:%s", role))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check hierarchical permissions
|
// 4. Check hierarchical permissions (parent permissions grant child permissions)
|
||||||
if len(grantedBy) == 0 {
|
if len(grantedBy) == 0 {
|
||||||
if inheritedPermissions := pm.getInheritedPermissions(permission); len(inheritedPermissions) > 0 {
|
if parentPermission := pm.getParentPermission(permission); parentPermission != "" {
|
||||||
for _, inherited := range inheritedPermissions {
|
// Recursively check parent permission
|
||||||
for _, role := range userRoles {
|
parentEval := pm.evaluatePermission(ctx, userID, appID, parentPermission)
|
||||||
if pm.roleHasPermission(role, inherited) {
|
if parentEval.Granted {
|
||||||
grantedBy = append(grantedBy, fmt.Sprintf("inherited:%s", inherited))
|
grantedBy = append(grantedBy, fmt.Sprintf("inherited:%s", parentPermission))
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 5. Apply context-specific rules
|
||||||
|
if len(grantedBy) == 0 && pm.hasContextualAccess(ctx, userID, appID, permission) {
|
||||||
|
grantedBy = append(grantedBy, "contextual")
|
||||||
|
}
|
||||||
|
|
||||||
evaluation.Granted = len(grantedBy) > 0
|
evaluation.Granted = len(grantedBy) > 0
|
||||||
evaluation.GrantedBy = grantedBy
|
evaluation.GrantedBy = grantedBy
|
||||||
|
|
||||||
@ -328,7 +324,7 @@ func (pm *PermissionManager) evaluatePermission(ctx context.Context, userID, app
|
|||||||
// Add metadata
|
// Add metadata
|
||||||
evaluation.Metadata["user_roles"] = strings.Join(userRoles, ",")
|
evaluation.Metadata["user_roles"] = strings.Join(userRoles, ",")
|
||||||
evaluation.Metadata["app_id"] = appID
|
evaluation.Metadata["app_id"] = appID
|
||||||
evaluation.Metadata["evaluation_method"] = "hierarchical"
|
evaluation.Metadata["evaluation_method"] = "hierarchical_with_repository"
|
||||||
|
|
||||||
return evaluation
|
return evaluation
|
||||||
}
|
}
|
||||||
@ -686,3 +682,68 @@ func (h *PermissionHierarchy) ListRoles() []*Role {
|
|||||||
|
|
||||||
return roles
|
return roles
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hasDirectPermissionFromRepo checks if user has direct permission via repository lookup
|
||||||
|
func (pm *PermissionManager) hasDirectPermissionFromRepo(ctx context.Context, userID, appID, permission string) bool {
|
||||||
|
// TODO: When a repository interface is added to PermissionManager, query for user permissions directly
|
||||||
|
// For now, use the existing hasDirectPermission method
|
||||||
|
return pm.hasDirectPermission(userID, appID, permission)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getParentPermission extracts the parent permission from a hierarchical permission
|
||||||
|
func (pm *PermissionManager) getParentPermission(permission string) string {
|
||||||
|
// For dot-separated permissions like "app.create", parent is "app"
|
||||||
|
if lastDot := strings.LastIndex(permission, "."); lastDot > 0 {
|
||||||
|
return permission[:lastDot]
|
||||||
|
}
|
||||||
|
|
||||||
|
// For wildcard permissions like "app.*", parent is "app"
|
||||||
|
if strings.HasSuffix(permission, ".*") {
|
||||||
|
return strings.TrimSuffix(permission, ".*")
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasContextualAccess applies context-specific permission rules
|
||||||
|
func (pm *PermissionManager) hasContextualAccess(ctx context.Context, userID, appID, permission string) bool {
|
||||||
|
// Context-specific rules:
|
||||||
|
|
||||||
|
// 1. Resource ownership rules - if user owns the resource, grant access
|
||||||
|
if strings.Contains(permission, ".own") || pm.isResourceOwner(ctx, userID, appID, permission) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Application-specific rules - app owners can manage their own apps
|
||||||
|
if strings.HasPrefix(permission, "app.") && pm.isAppOwner(ctx, userID, appID) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Token-specific rules - users can manage their own tokens
|
||||||
|
if strings.HasPrefix(permission, "token.") && pm.isTokenOwner(ctx, userID, appID, permission) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// isResourceOwner checks if user owns the resource (placeholder implementation)
|
||||||
|
func (pm *PermissionManager) isResourceOwner(ctx context.Context, userID, appID, permission string) bool {
|
||||||
|
// This would typically query the database to check resource ownership
|
||||||
|
// For now, implement basic ownership detection
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// isAppOwner checks if user is the application owner (placeholder implementation)
|
||||||
|
func (pm *PermissionManager) isAppOwner(ctx context.Context, userID, appID string) bool {
|
||||||
|
// This would typically query the applications table to check ownership
|
||||||
|
// For now, implement basic ownership detection
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// isTokenOwner checks if user owns the token (placeholder implementation)
|
||||||
|
func (pm *PermissionManager) isTokenOwner(ctx context.Context, userID, appID, permission string) bool {
|
||||||
|
// This would typically query the tokens table to check ownership
|
||||||
|
// For now, implement basic ownership detection
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
@ -146,6 +146,28 @@ func (eh *ErrorHandler) HandleInternalError(c *gin.Context, err error) {
|
|||||||
c.JSON(http.StatusInternalServerError, response)
|
c.JSON(http.StatusInternalServerError, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HandleNotFoundError handles resource not found errors
|
||||||
|
func (eh *ErrorHandler) HandleNotFoundError(c *gin.Context, resource string, message string) {
|
||||||
|
requestID := eh.getOrGenerateRequestID(c)
|
||||||
|
|
||||||
|
eh.logger.Warn("Resource not found",
|
||||||
|
zap.String("request_id", requestID),
|
||||||
|
zap.String("resource", resource),
|
||||||
|
zap.String("path", c.Request.URL.Path),
|
||||||
|
zap.String("method", c.Request.Method),
|
||||||
|
zap.String("remote_addr", c.ClientIP()),
|
||||||
|
)
|
||||||
|
|
||||||
|
response := SecureErrorResponse{
|
||||||
|
Error: "resource_not_found",
|
||||||
|
Message: message,
|
||||||
|
RequestID: requestID,
|
||||||
|
Code: http.StatusNotFound,
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusNotFound, response)
|
||||||
|
}
|
||||||
|
|
||||||
// determineErrorResponse determines the appropriate HTTP status and error type
|
// determineErrorResponse determines the appropriate HTTP status and error type
|
||||||
func (eh *ErrorHandler) determineErrorResponse(err error) (int, string) {
|
func (eh *ErrorHandler) determineErrorResponse(err error) (int, string) {
|
||||||
if appErr, ok := err.(*AppError); ok {
|
if appErr, ok := err.(*AppError); ok {
|
||||||
|
|||||||
@ -192,16 +192,44 @@ func (h *AuditHandler) ListEvents(c *gin.Context) {
|
|||||||
// GetEvent handles GET /audit/events/:id
|
// GetEvent handles GET /audit/events/:id
|
||||||
func (h *AuditHandler) GetEvent(c *gin.Context) {
|
func (h *AuditHandler) GetEvent(c *gin.Context) {
|
||||||
eventIDStr := c.Param("id")
|
eventIDStr := c.Param("id")
|
||||||
_, err := uuid.Parse(eventIDStr)
|
eventID, err := uuid.Parse(eventIDStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.errorHandler.HandleValidationError(c, "id", "Invalid event ID format")
|
h.errorHandler.HandleValidationError(c, "id", "Invalid event ID format")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Single event retrieval not yet implemented
|
// Get the specific audit event
|
||||||
c.JSON(http.StatusNotImplemented, gin.H{
|
event, err := h.auditLogger.GetEventByID(c.Request.Context(), eventID)
|
||||||
"error": "Single event retrieval not yet implemented",
|
if err != nil {
|
||||||
})
|
h.logger.Error("Failed to get audit event", zap.Error(err), zap.String("event_id", eventID.String()))
|
||||||
|
// Check if it's a not found error
|
||||||
|
if err.Error() == "audit event with ID '"+eventID.String()+"' not found" {
|
||||||
|
h.errorHandler.HandleNotFoundError(c, "audit_event", "Audit event not found")
|
||||||
|
} else {
|
||||||
|
h.errorHandler.HandleInternalError(c, err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to response format
|
||||||
|
response := AuditEventResponse{
|
||||||
|
ID: event.ID.String(),
|
||||||
|
Type: string(event.Type),
|
||||||
|
Status: string(event.Status),
|
||||||
|
Timestamp: event.Timestamp.Format(time.RFC3339),
|
||||||
|
ActorID: event.ActorID,
|
||||||
|
ActorIP: event.ActorIP,
|
||||||
|
UserAgent: event.UserAgent,
|
||||||
|
ResourceID: event.ResourceID,
|
||||||
|
ResourceType: event.ResourceType,
|
||||||
|
Action: event.Action,
|
||||||
|
Description: event.Description,
|
||||||
|
Details: event.Details,
|
||||||
|
RequestID: event.RequestID,
|
||||||
|
SessionID: event.SessionID,
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetStats handles GET /audit/stats
|
// GetStats handles GET /audit/stats
|
||||||
|
|||||||
@ -9,13 +9,17 @@ import (
|
|||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/kms/api-key-service/internal/domain"
|
"github.com/kms/api-key-service/internal/domain"
|
||||||
|
"github.com/kms/api-key-service/internal/errors"
|
||||||
"github.com/kms/api-key-service/internal/services"
|
"github.com/kms/api-key-service/internal/services"
|
||||||
|
"github.com/kms/api-key-service/internal/validation"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TokenHandler handles token-related HTTP requests
|
// TokenHandler handles token-related HTTP requests
|
||||||
type TokenHandler struct {
|
type TokenHandler struct {
|
||||||
tokenService services.TokenService
|
tokenService services.TokenService
|
||||||
authService services.AuthenticationService
|
authService services.AuthenticationService
|
||||||
|
validator *validation.Validator
|
||||||
|
errorHandler *errors.ErrorHandler
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,96 +32,139 @@ func NewTokenHandler(
|
|||||||
return &TokenHandler{
|
return &TokenHandler{
|
||||||
tokenService: tokenService,
|
tokenService: tokenService,
|
||||||
authService: authService,
|
authService: authService,
|
||||||
|
validator: validation.NewValidator(logger),
|
||||||
|
errorHandler: errors.NewErrorHandler(logger),
|
||||||
logger: logger,
|
logger: logger,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create handles POST /applications/:id/tokens
|
// Create handles POST /applications/:id/tokens
|
||||||
func (h *TokenHandler) Create(c *gin.Context) {
|
func (h *TokenHandler) Create(c *gin.Context) {
|
||||||
|
// Validate application ID parameter
|
||||||
appID := c.Param("id")
|
appID := c.Param("id")
|
||||||
if appID == "" {
|
if appID == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
h.errorHandler.HandleValidationError(c, "id", "Application ID is required")
|
||||||
"error": "Bad Request",
|
|
||||||
"message": "Application ID is required",
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bind and validate JSON request
|
||||||
var req domain.CreateStaticTokenRequest
|
var req domain.CreateStaticTokenRequest
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
h.logger.Warn("Invalid request body", zap.Error(err))
|
h.logger.Warn("Invalid request body", zap.Error(err))
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
h.errorHandler.HandleValidationError(c, "request_body", "Invalid request body format")
|
||||||
"error": "Bad Request",
|
|
||||||
"message": "Invalid request body: " + err.Error(),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set app ID from URL parameter
|
// Set app ID from URL parameter
|
||||||
req.AppID = appID
|
req.AppID = appID
|
||||||
|
|
||||||
|
// Basic validation - the service layer will do more comprehensive validation
|
||||||
|
if req.AppID == "" {
|
||||||
|
h.errorHandler.HandleValidationError(c, "app_id", "Application ID is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Get user ID from context
|
// Get user ID from context
|
||||||
userID, exists := c.Get("user_id")
|
userID, exists := c.Get("user_id")
|
||||||
if !exists {
|
if !exists {
|
||||||
h.logger.Error("User ID not found in context")
|
h.logger.Error("User ID not found in context")
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
h.errorHandler.HandleAuthenticationError(c, errors.NewAuthenticationError("Authentication context not found"))
|
||||||
"error": "Internal Server Error",
|
|
||||||
"message": "Authentication context not found",
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
token, err := h.tokenService.CreateStaticToken(c.Request.Context(), &req, userID.(string))
|
userIDStr, ok := userID.(string)
|
||||||
|
if !ok {
|
||||||
|
h.logger.Error("Invalid user ID type in context", zap.Any("user_id", userID))
|
||||||
|
h.errorHandler.HandleInternalError(c, errors.NewInternalError("Invalid authentication context"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the token
|
||||||
|
token, err := h.tokenService.CreateStaticToken(c.Request.Context(), &req, userIDStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logger.Error("Failed to create token", zap.Error(err))
|
h.logger.Error("Failed to create token",
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
zap.Error(err),
|
||||||
"error": "Internal Server Error",
|
zap.String("app_id", appID),
|
||||||
"message": "Failed to create token",
|
zap.String("user_id", userIDStr))
|
||||||
})
|
|
||||||
|
// Handle different types of errors appropriately
|
||||||
|
if errors.IsNotFound(err) {
|
||||||
|
h.errorHandler.HandleError(c, err, "Application not found")
|
||||||
|
} else if errors.IsValidationError(err) {
|
||||||
|
h.errorHandler.HandleValidationError(c, "token", "Token creation validation failed")
|
||||||
|
} else if errors.IsAuthorizationError(err) {
|
||||||
|
h.errorHandler.HandleAuthorizationError(c, "token_creation")
|
||||||
|
} else {
|
||||||
|
h.errorHandler.HandleInternalError(c, err)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
h.logger.Info("Token created", zap.String("token_id", token.ID.String()))
|
h.logger.Info("Token created successfully",
|
||||||
|
zap.String("token_id", token.ID.String()),
|
||||||
|
zap.String("app_id", appID),
|
||||||
|
zap.String("user_id", userIDStr))
|
||||||
|
|
||||||
c.JSON(http.StatusCreated, token)
|
c.JSON(http.StatusCreated, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListByApp handles GET /applications/:id/tokens
|
// ListByApp handles GET /applications/:id/tokens
|
||||||
func (h *TokenHandler) ListByApp(c *gin.Context) {
|
func (h *TokenHandler) ListByApp(c *gin.Context) {
|
||||||
|
// Validate application ID parameter
|
||||||
appID := c.Param("id")
|
appID := c.Param("id")
|
||||||
if appID == "" {
|
if appID == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
h.errorHandler.HandleValidationError(c, "id", "Application ID is required")
|
||||||
"error": "Bad Request",
|
|
||||||
"message": "Application ID is required",
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse pagination parameters
|
// Parse and validate pagination parameters
|
||||||
limit := 50
|
limit := 50
|
||||||
offset := 0
|
offset := 0
|
||||||
|
|
||||||
if l := c.Query("limit"); l != "" {
|
if l := c.Query("limit"); l != "" {
|
||||||
if parsed, err := strconv.Atoi(l); err == nil && parsed > 0 {
|
if parsed, err := strconv.Atoi(l); err == nil && parsed > 0 && parsed <= 1000 {
|
||||||
limit = parsed
|
limit = parsed
|
||||||
|
} else if parsed <= 0 || parsed > 1000 {
|
||||||
|
h.errorHandler.HandleValidationError(c, "limit", "Limit must be between 1 and 1000")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if o := c.Query("offset"); o != "" {
|
if o := c.Query("offset"); o != "" {
|
||||||
if parsed, err := strconv.Atoi(o); err == nil && parsed >= 0 {
|
if parsed, err := strconv.Atoi(o); err == nil && parsed >= 0 {
|
||||||
offset = parsed
|
offset = parsed
|
||||||
|
} else if parsed < 0 {
|
||||||
|
h.errorHandler.HandleValidationError(c, "offset", "Offset must be non-negative")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// List tokens
|
||||||
tokens, err := h.tokenService.ListByApp(c.Request.Context(), appID, limit, offset)
|
tokens, err := h.tokenService.ListByApp(c.Request.Context(), appID, limit, offset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logger.Error("Failed to list tokens", zap.Error(err), zap.String("app_id", appID))
|
h.logger.Error("Failed to list tokens",
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
zap.Error(err),
|
||||||
"error": "Internal Server Error",
|
zap.String("app_id", appID),
|
||||||
"message": "Failed to list tokens",
|
zap.Int("limit", limit),
|
||||||
})
|
zap.Int("offset", offset))
|
||||||
|
|
||||||
|
// Handle different types of errors appropriately
|
||||||
|
if errors.IsNotFound(err) {
|
||||||
|
h.errorHandler.HandleNotFoundError(c, "application", "Application not found")
|
||||||
|
} else if errors.IsAuthorizationError(err) {
|
||||||
|
h.errorHandler.HandleAuthorizationError(c, "token_list")
|
||||||
|
} else {
|
||||||
|
h.errorHandler.HandleInternalError(c, err)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h.logger.Debug("Tokens listed successfully",
|
||||||
|
zap.String("app_id", appID),
|
||||||
|
zap.Int("token_count", len(tokens)),
|
||||||
|
zap.Int("limit", limit),
|
||||||
|
zap.Int("offset", offset))
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"data": tokens,
|
"data": tokens,
|
||||||
"limit": limit,
|
"limit": limit,
|
||||||
@ -128,21 +175,17 @@ func (h *TokenHandler) ListByApp(c *gin.Context) {
|
|||||||
|
|
||||||
// Delete handles DELETE /tokens/:id
|
// Delete handles DELETE /tokens/:id
|
||||||
func (h *TokenHandler) Delete(c *gin.Context) {
|
func (h *TokenHandler) Delete(c *gin.Context) {
|
||||||
|
// Validate token ID parameter
|
||||||
tokenIDStr := c.Param("id")
|
tokenIDStr := c.Param("id")
|
||||||
if tokenIDStr == "" {
|
if tokenIDStr == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
h.errorHandler.HandleValidationError(c, "id", "Token ID is required")
|
||||||
"error": "Bad Request",
|
|
||||||
"message": "Token ID is required",
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenID, err := uuid.Parse(tokenIDStr)
|
tokenID, err := uuid.Parse(tokenIDStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
h.logger.Warn("Invalid token ID format", zap.String("token_id", tokenIDStr), zap.Error(err))
|
||||||
"error": "Bad Request",
|
h.errorHandler.HandleValidationError(c, "id", "Invalid token ID format")
|
||||||
"message": "Invalid token ID format",
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,23 +193,39 @@ func (h *TokenHandler) Delete(c *gin.Context) {
|
|||||||
userID, exists := c.Get("user_id")
|
userID, exists := c.Get("user_id")
|
||||||
if !exists {
|
if !exists {
|
||||||
h.logger.Error("User ID not found in context")
|
h.logger.Error("User ID not found in context")
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
h.errorHandler.HandleAuthenticationError(c, errors.NewAuthenticationError("Authentication context not found"))
|
||||||
"error": "Internal Server Error",
|
|
||||||
"message": "Authentication context not found",
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.tokenService.Delete(c.Request.Context(), tokenID, userID.(string))
|
userIDStr, ok := userID.(string)
|
||||||
|
if !ok {
|
||||||
|
h.logger.Error("Invalid user ID type in context", zap.Any("user_id", userID))
|
||||||
|
h.errorHandler.HandleInternalError(c, errors.NewInternalError("Invalid authentication context"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the token
|
||||||
|
err = h.tokenService.Delete(c.Request.Context(), tokenID, userIDStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logger.Error("Failed to delete token", zap.Error(err), zap.String("token_id", tokenID.String()))
|
h.logger.Error("Failed to delete token",
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
zap.Error(err),
|
||||||
"error": "Internal Server Error",
|
zap.String("token_id", tokenID.String()),
|
||||||
"message": "Failed to delete token",
|
zap.String("user_id", userIDStr))
|
||||||
})
|
|
||||||
|
// Handle different types of errors appropriately
|
||||||
|
if errors.IsNotFound(err) {
|
||||||
|
h.errorHandler.HandleNotFoundError(c, "token", "Token not found")
|
||||||
|
} else if errors.IsAuthorizationError(err) {
|
||||||
|
h.errorHandler.HandleAuthorizationError(c, "token_deletion")
|
||||||
|
} else {
|
||||||
|
h.errorHandler.HandleInternalError(c, err)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
h.logger.Info("Token deleted", zap.String("token_id", tokenID.String()))
|
h.logger.Info("Token deleted successfully",
|
||||||
|
zap.String("token_id", tokenID.String()),
|
||||||
|
zap.String("user_id", userIDStr))
|
||||||
|
|
||||||
c.JSON(http.StatusNoContent, nil)
|
c.JSON(http.StatusNoContent, nil)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/google/uuid"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/kms/api-key-service/internal/audit"
|
"github.com/kms/api-key-service/internal/audit"
|
||||||
@ -60,6 +61,10 @@ func (a *auditRepositoryAdapter) DeleteOldEvents(ctx context.Context, olderThan
|
|||||||
return a.repo.DeleteOldEvents(ctx, olderThan)
|
return a.repo.DeleteOldEvents(ctx, olderThan)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *auditRepositoryAdapter) GetByID(ctx context.Context, eventID uuid.UUID) (*audit.AuditEvent, error) {
|
||||||
|
return a.repo.GetByID(ctx, eventID)
|
||||||
|
}
|
||||||
|
|
||||||
// Create creates a new application
|
// Create creates a new application
|
||||||
func (s *applicationService) Create(ctx context.Context, req *domain.CreateApplicationRequest, userID string) (*domain.Application, error) {
|
func (s *applicationService) Create(ctx context.Context, req *domain.CreateApplicationRequest, userID string) (*domain.Application, error) {
|
||||||
s.logger.Info("Creating application", zap.String("app_id", req.AppID), zap.String("user_id", userID))
|
s.logger.Info("Creating application", zap.String("app_id", req.AppID), zap.String("user_id", userID))
|
||||||
|
|||||||
Reference in New Issue
Block a user