From 667d40b5a1b2fdab4db576b86f2872d6c434782a Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 18 Oct 2025 09:22:35 +0800 Subject: [PATCH] Add ACL support to OAuth guard and error handling - Integrated ACL (Access Control List) functionality into the OAuth guard, enabling permission checks and rate limiting based on ACL configurations. - Updated error responses for token validation and refresh token handling to use standardized error types. - Enhanced error handling for ACL-related issues, providing detailed HTTP responses based on specific ACL error types. - Improved overall security and user experience by ensuring proper authorization checks are enforced during API requests. --- openapi/oauth/ERRORS.md | 263 ++++++++++++++++++++++++++++++++ openapi/oauth/acl/acl.go | 27 ++++ openapi/oauth/acl/enforce.go | 8 + openapi/oauth/acl/errors.go | 126 +++++++++++++++ openapi/oauth/acl/interfaces.go | 12 ++ openapi/oauth/acl/types.go | 16 ++ openapi/oauth/guard.go | 98 +++++++++++- openapi/oauth/types/errors.go | 34 ++++- openapi/openapi.go | 7 + 9 files changed, 586 insertions(+), 5 deletions(-) create mode 100644 openapi/oauth/ERRORS.md create mode 100644 openapi/oauth/acl/acl.go create mode 100644 openapi/oauth/acl/enforce.go create mode 100644 openapi/oauth/acl/errors.go create mode 100644 openapi/oauth/acl/interfaces.go create mode 100644 openapi/oauth/acl/types.go diff --git a/openapi/oauth/ERRORS.md b/openapi/oauth/ERRORS.md new file mode 100644 index 00000000..0ef11354 --- /dev/null +++ b/openapi/oauth/ERRORS.md @@ -0,0 +1,263 @@ +# OAuth Error Handling Documentation + +This document describes the error handling specifications and ACL error definitions for OAuth services. + +## Overview + +OAuth services use a standardized error response format. All errors follow the `ErrorResponse` structure: + +```go +type ErrorResponse struct { + Code string `json:"error"` + ErrorDescription string `json:"error_description,omitempty"` + ErrorURI string `json:"error_uri,omitempty"` + State string `json:"state,omitempty"` +} +``` + +## Configuration Errors + +These errors are used during OAuth service initialization and configuration: + +| Error Code | Variable Name | Description | +| ---------------------------- | ----------------------------- | -------------------------------------------------------------------- | +| `invalid_configuration` | `ErrInvalidConfiguration` | Invalid OAuth service configuration | +| `store_missing` | `ErrStoreMissing` | Store is required for OAuth service | +| `issuer_url_missing` | `ErrIssuerURLMissing` | Issuer URL is missing | +| `certificate_missing` | `ErrCertificateMissing` | JWT signing certificate and key paths must both be provided or empty | +| `invalid_token_lifetime` | `ErrInvalidTokenLifetime` | Token lifetime must be greater than 0 | +| `pkce_configuration_invalid` | `ErrPKCEConfigurationInvalid` | PKCE configuration is invalid | + +## Authentication & Authorization Errors + +### Token Related Errors + +| Error Code | Variable Name | HTTP Status | Description | +| ----------------------- | ------------------------ | ----------- | -------------------------------------------------- | +| `token_missing` | `ErrTokenMissing` | 401 | No access token provided in the request | +| `invalid_token` | `ErrInvalidToken` | 401 | The access token is invalid, expired or malformed | +| `token_expired` | `ErrTokenExpired` | 401 | The access token has expired | +| `unauthorized` | `ErrUnauthorized` | 401 | Authentication is required to access this resource | +| `refresh_token_missing` | `ErrRefreshTokenMissing` | 401 | No refresh token provided in the request | +| `invalid_refresh_token` | `ErrInvalidRefreshToken` | 401 | The refresh token is invalid or expired | + +### Permission Related Errors + +| Error Code | Variable Name | HTTP Status | Description | +| -------------------- | ---------------------- | ----------- | -------------------------------------------------- | +| `forbidden` | `ErrForbidden` | 403 | You do not have permission to access this resource | +| `access_denied` | `ErrAccessDenied` | 403 | Access to this resource has been denied | +| `insufficient_scope` | `ErrInsufficientScope` | 403 | The access token does not have the required scope | + +### ACL Errors + +| Error Code | Variable Name | HTTP Status | Description | +| -------------------- | --------------------- | ----------- | ---------------------------------------- | +| `acl_check_failed` | `ErrACLCheckFailed` | 500 | ACL verification failed | +| `acl_internal_error` | `ErrACLInternalError` | 500 | Internal error occurred during ACL check | + +### Rate Limiting Errors + +| Error Code | Variable Name | HTTP Status | Description | +| --------------------- | ---------------------- | ----------- | ----------------------------------------- | +| `rate_limit_exceeded` | `ErrRateLimitExceeded` | 429 | Too many requests. Please try again later | +| `too_many_requests` | `ErrTooManyRequests` | 429 | Request rate limit exceeded | + +### Resource Errors + +| Error Code | Variable Name | HTTP Status | Description | +| -------------------- | --------------------- | ----------- | ------------------------------------------------ | +| `resource_not_found` | `ErrResourceNotFound` | 404 | The requested resource was not found | +| `method_not_allowed` | `ErrMethodNotAllowed` | 405 | The HTTP method is not allowed for this resource | + +### Server Errors + +| Error Code | Variable Name | HTTP Status | Description | +| ----------------------- | ------------------------ | ----------- | -------------------------------------- | +| `internal_server_error` | `ErrInternalServerError` | 500 | An internal server error occurred | +| `service_unavailable` | `ErrServiceUnavailable` | 503 | The service is temporarily unavailable | + +## ACL Error Types + +ACL implementations can return the following error types, which the Guard middleware automatically converts to appropriate HTTP responses: + +### acl.Error Structure + +```go +type Error struct { + Type ErrorType // Error type + Message string // Error message + Details map[string]interface{} // Additional error details + RetryAfter int // Retry wait time (seconds) +} +``` + +### ACL Error Type Definitions + +| Error Type | HTTP Status | Description | Retryable | +| ---------------------- | ----------- | ------------------------------------------------- | --------- | +| `permission_denied` | 403 | User does not have required permissions | No | +| `rate_limit_exceeded` | 429 | Request rate limit exceeded | Yes | +| `insufficient_scope` | 403 | Token scope is insufficient | No | +| `resource_not_allowed` | 403 | Access to the resource is not allowed | No | +| `method_not_allowed` | 405 | The HTTP method is not allowed | No | +| `ip_blocked` | 403 | IP address is blocked | No | +| `geo_restricted` | 403 | Access is restricted based on geographic location | No | +| `time_restricted` | 403 | Access is restricted based on time | Yes | +| `quota_exceeded` | 429 | Usage quota has been exceeded | Yes | +| `invalid_request` | 400 | Request is invalid | No | +| `internal_error` | 500 | Internal error occurred during ACL check | Yes | + +### ACL Error Creation Functions + +```go +// Basic error creation +acl.NewError(errorType, message) + +// Permission denied +acl.NewPermissionDeniedError("User does not have admin role") + +// Rate limit error (with retry time) +acl.NewRateLimitError("Too many requests", 60) // Retry after 60 seconds + +// Insufficient scope +acl.NewInsufficientScopeError("Missing required scope", []string{"read", "write"}) + +// Resource not allowed +acl.NewResourceNotAllowedError("/admin/users") + +// Method not allowed +acl.NewMethodNotAllowedError("DELETE", []string{"GET", "POST"}) + +// IP blocked +acl.NewIPBlockedError("192.168.1.1") + +// Quota exceeded +acl.NewQuotaExceededError("API quota exceeded", "api_calls", 1000, 1050) + +// Internal error +acl.NewInternalError("Failed to load ACL rules") +``` + +## Usage Examples + +### Using in Guard Middleware + +The Guard middleware automatically handles all error types: + +```go +func (s *Service) Guard(c *gin.Context) { + // Token validation + token := s.getAccessToken(c) + if token == "" { + c.JSON(http.StatusUnauthorized, types.ErrTokenMissing) + c.Abort() + return + } + + // ACL check + ok, err := acl.Global.Enforce(c) + if err != nil { + s.handleACLError(c, err) // Automatically handles different types of ACL errors + return + } +} +``` + +### Implementing ACL Enforce Method + +```go +func (a *MyACL) Enforce(c *gin.Context) (bool, error) { + // Check rate limit + if rateLimitExceeded { + return false, acl.NewRateLimitError("Too many requests", 60) + } + + // Check permissions + if !hasPermission { + return false, acl.NewPermissionDeniedError("User does not have required permission") + } + + // Check IP + if ipBlocked { + return false, acl.NewIPBlockedError(clientIP) + } + + // Check quota + if quotaExceeded { + return false, acl.NewQuotaExceededError("API quota exceeded", "api_calls", limit, current) + } + + return true, nil +} +``` + +### Error Response Examples + +#### Standard Error Response + +```json +{ + "error": "token_missing", + "error_description": "No access token provided in the request" +} +``` + +#### Rate Limit Error Response (with Retry-After header) + +``` +HTTP/1.1 429 Too Many Requests +Retry-After: 60 + +{ + "error": "rate_limit_exceeded", + "error_description": "Too many requests. Please try again later" +} +``` + +#### Forbidden Response + +```json +{ + "error": "forbidden", + "error_description": "You do not have permission to access this resource" +} +``` + +## Error Handling Best Practices + +1. **Use Predefined Error Constants**: Always use `types.Err*` constants instead of manually creating error responses +2. **Provide Detailed Error Information**: For ACL errors, use the Details field to provide additional context +3. **Set Appropriate HTTP Status Codes**: The Guard middleware handles this automatically, but ensure correct status codes elsewhere +4. **Set Retry-After for Retryable Errors**: For rate limiting and quota errors, provide retry time +5. **Log Error Details**: Log detailed error information for debugging before returning errors +6. **Avoid Leaking Sensitive Information**: Error messages should be user-friendly but not expose internal system details + +## Standard HTTP Status Code Mapping + +- **200 OK**: Request successful +- **400 Bad Request**: Invalid request parameters +- **401 Unauthorized**: Not authenticated or authentication failed +- **403 Forbidden**: Authenticated but not authorized to access +- **404 Not Found**: Resource does not exist +- **405 Method Not Allowed**: HTTP method not allowed +- **429 Too Many Requests**: Rate limit or quota exceeded +- **500 Internal Server Error**: Internal server error +- **503 Service Unavailable**: Service temporarily unavailable + +## Testing Error Handling + +It is recommended to test the following scenarios: + +1. Missing Token +2. Invalid Token +3. Expired Token +4. Insufficient Permissions +5. Rate Limit Exceeded +6. Quota Exceeded +7. IP Blocked +8. Resource Not Found +9. Method Not Allowed +10. ACL Internal Error + +Each scenario should return the appropriate HTTP status code and error response. diff --git a/openapi/oauth/acl/acl.go b/openapi/oauth/acl/acl.go new file mode 100644 index 00000000..3ccaf3df --- /dev/null +++ b/openapi/oauth/acl/acl.go @@ -0,0 +1,27 @@ +package acl + +// Global is the global ACL enforcer +var Global Enforcer = nil + +// New creates a new ACL enforcer +func New(config *Config) Enforcer { + + if config == nil { + config = &DefaultConfig + } + + return &ACL{ + Config: config, + } +} + +// Load loads the ACL enforcer +func Load(config *Config) (Enforcer, error) { + Global = New(config) + return Global, nil +} + +// Enabled returns true if the ACL is enabled, otherwise false +func (acl *ACL) Enabled() bool { + return acl.Config.Enabled +} diff --git a/openapi/oauth/acl/enforce.go b/openapi/oauth/acl/enforce.go new file mode 100644 index 00000000..e0959176 --- /dev/null +++ b/openapi/oauth/acl/enforce.go @@ -0,0 +1,8 @@ +package acl + +import "github.com/gin-gonic/gin" + +// Enforce checks if the user has access to the resource +func (acl *ACL) Enforce(c *gin.Context) (bool, error) { + return true, nil +} diff --git a/openapi/oauth/acl/errors.go b/openapi/oauth/acl/errors.go new file mode 100644 index 00000000..b703075c --- /dev/null +++ b/openapi/oauth/acl/errors.go @@ -0,0 +1,126 @@ +package acl + +import "fmt" + +// ErrorType represents different types of ACL errors +type ErrorType string + +const ( + // ErrorTypePermissionDenied indicates the user does not have required permissions + ErrorTypePermissionDenied ErrorType = "permission_denied" + + // ErrorTypeRateLimitExceeded indicates the request rate limit has been exceeded + ErrorTypeRateLimitExceeded ErrorType = "rate_limit_exceeded" + + // ErrorTypeInsufficientScope indicates the token scope is insufficient + ErrorTypeInsufficientScope ErrorType = "insufficient_scope" + + // ErrorTypeResourceNotAllowed indicates access to the resource is not allowed + ErrorTypeResourceNotAllowed ErrorType = "resource_not_allowed" + + // ErrorTypeMethodNotAllowed indicates the HTTP method is not allowed + ErrorTypeMethodNotAllowed ErrorType = "method_not_allowed" + + // ErrorTypeIPBlocked indicates the request IP is blocked + ErrorTypeIPBlocked ErrorType = "ip_blocked" + + // ErrorTypeGeoRestricted indicates access is restricted based on geographic location + ErrorTypeGeoRestricted ErrorType = "geo_restricted" + + // ErrorTypeTimeRestricted indicates access is restricted based on time + ErrorTypeTimeRestricted ErrorType = "time_restricted" + + // ErrorTypeQuotaExceeded indicates the usage quota has been exceeded + ErrorTypeQuotaExceeded ErrorType = "quota_exceeded" + + // ErrorTypeInvalidRequest indicates the request is invalid + ErrorTypeInvalidRequest ErrorType = "invalid_request" + + // ErrorTypeInternal indicates an internal error occurred during ACL check + ErrorTypeInternal ErrorType = "internal_error" +) + +// Error represents an ACL-related error with additional context +type Error struct { + Type ErrorType + Message string + Details map[string]interface{} + RetryAfter int // seconds to wait before retrying (for rate limit errors) +} + +// Error implements the error interface +func (e *Error) Error() string { + if e.Message != "" { + return fmt.Sprintf("ACL error [%s]: %s", e.Type, e.Message) + } + return fmt.Sprintf("ACL error: %s", e.Type) +} + +// IsRetryable returns true if the error is retryable (e.g., rate limit) +func (e *Error) IsRetryable() bool { + return e.Type == ErrorTypeRateLimitExceeded || e.Type == ErrorTypeQuotaExceeded +} + +// NewError creates a new ACL error +func NewError(errorType ErrorType, message string) *Error { + return &Error{ + Type: errorType, + Message: message, + Details: make(map[string]interface{}), + } +} + +// NewPermissionDeniedError creates a permission denied error +func NewPermissionDeniedError(message string) *Error { + return NewError(ErrorTypePermissionDenied, message) +} + +// NewRateLimitError creates a rate limit error with retry-after information +func NewRateLimitError(message string, retryAfter int) *Error { + err := NewError(ErrorTypeRateLimitExceeded, message) + err.RetryAfter = retryAfter + return err +} + +// NewInsufficientScopeError creates an insufficient scope error +func NewInsufficientScopeError(message string, requiredScopes []string) *Error { + err := NewError(ErrorTypeInsufficientScope, message) + err.Details["required_scopes"] = requiredScopes + return err +} + +// NewResourceNotAllowedError creates a resource not allowed error +func NewResourceNotAllowedError(resource string) *Error { + err := NewError(ErrorTypeResourceNotAllowed, "Access to this resource is not allowed") + err.Details["resource"] = resource + return err +} + +// NewMethodNotAllowedError creates a method not allowed error +func NewMethodNotAllowedError(method string, allowedMethods []string) *Error { + err := NewError(ErrorTypeMethodNotAllowed, fmt.Sprintf("HTTP method '%s' is not allowed", method)) + err.Details["method"] = method + err.Details["allowed_methods"] = allowedMethods + return err +} + +// NewIPBlockedError creates an IP blocked error +func NewIPBlockedError(ip string) *Error { + err := NewError(ErrorTypeIPBlocked, "Access from this IP address is blocked") + err.Details["ip"] = ip + return err +} + +// NewQuotaExceededError creates a quota exceeded error +func NewQuotaExceededError(message string, quotaType string, limit int64, current int64) *Error { + err := NewError(ErrorTypeQuotaExceeded, message) + err.Details["quota_type"] = quotaType + err.Details["limit"] = limit + err.Details["current"] = current + return err +} + +// NewInternalError creates an internal error +func NewInternalError(message string) *Error { + return NewError(ErrorTypeInternal, message) +} diff --git a/openapi/oauth/acl/interfaces.go b/openapi/oauth/acl/interfaces.go new file mode 100644 index 00000000..ee43ede3 --- /dev/null +++ b/openapi/oauth/acl/interfaces.go @@ -0,0 +1,12 @@ +package acl + +import "github.com/gin-gonic/gin" + +// Enforcer interface is used to enforce access control rules +type Enforcer interface { + // Enforce checks if a user has access to a resource + Enforce(c *gin.Context) (bool, error) + + // Enabled returns true if the ACL is enabled, otherwise false + Enabled() bool +} diff --git a/openapi/oauth/acl/types.go b/openapi/oauth/acl/types.go new file mode 100644 index 00000000..c4cab3ea --- /dev/null +++ b/openapi/oauth/acl/types.go @@ -0,0 +1,16 @@ +package acl + +// DefaultConfig is the default configuration for the ACL +var DefaultConfig = Config{ + Enabled: false, +} + +// Config is the configuration for the ACL +type Config struct { + Enabled bool `json:"enabled"` +} + +// ACL is the ACL checker +type ACL struct { + Config *Config +} diff --git a/openapi/oauth/guard.go b/openapi/oauth/guard.go index f4b36e06..e473b462 100644 --- a/openapi/oauth/guard.go +++ b/openapi/oauth/guard.go @@ -1,11 +1,13 @@ package oauth import ( + "fmt" "net/http" "strings" "time" "github.com/gin-gonic/gin" + "github.com/yaoapp/yao/openapi/oauth/acl" "github.com/yaoapp/yao/openapi/oauth/types" ) @@ -16,7 +18,7 @@ func (s *Service) Guard(c *gin.Context) { // Validate the token if token == "" { - c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) + c.JSON(http.StatusUnauthorized, types.ErrTokenMissing) c.Abort() return } @@ -24,7 +26,7 @@ func (s *Service) Guard(c *gin.Context) { // Validate the token claims, err := s.VerifyToken(token) if err != nil { - c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"}) + c.JSON(http.StatusUnauthorized, types.ErrInvalidToken) c.Abort() return } @@ -36,6 +38,25 @@ func (s *Service) Guard(c *gin.Context) { // Set Authorized Info s.setAuthorizedInfo(c, claims) + + // Check if ACL is enabled + if acl.Global == nil || !acl.Global.Enabled() { + return + } + + // Check permissions and enforce rate limits when ACL is configured + ok, err := acl.Global.Enforce(c) + if err != nil { + s.handleACLError(c, err) + return + } + + // If permissions are not granted, return forbidden + if !ok { + c.JSON(http.StatusForbidden, types.ErrForbidden) + c.Abort() + return + } } // GetAuthorizedInfo Get Authorized Info from context @@ -114,7 +135,7 @@ func (s *Service) setAuthorizedInfo(c *gin.Context, claims *types.TokenClaims) { func (s *Service) tryAutoRefreshToken(c *gin.Context, _ *types.TokenClaims) { refreshToken := s.getRefreshToken(c) if refreshToken == "" { - c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) + c.JSON(http.StatusUnauthorized, types.ErrRefreshTokenMissing) c.Abort() return } @@ -122,7 +143,7 @@ func (s *Service) tryAutoRefreshToken(c *gin.Context, _ *types.TokenClaims) { // Verify the refresh token _, err := s.VerifyToken(refreshToken) if err != nil { - c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"}) + c.JSON(http.StatusUnauthorized, types.ErrInvalidRefreshToken) c.Abort() return } @@ -195,3 +216,72 @@ func (s *Service) getSessionID(c *gin.Context) string { return "" } + +// handleACLError handles ACL errors and returns appropriate HTTP responses +func (s *Service) handleACLError(c *gin.Context, err error) { + // Check if it's an ACL error with detailed information + if aclErr, ok := err.(*acl.Error); ok { + var statusCode int + var errResponse *types.ErrorResponse + + switch aclErr.Type { + case acl.ErrorTypeRateLimitExceeded: + statusCode = http.StatusTooManyRequests + errResponse = types.ErrRateLimitExceeded + // Set Retry-After header if available + if aclErr.RetryAfter > 0 { + c.Header("Retry-After", fmt.Sprintf("%d", aclErr.RetryAfter)) + } + + case acl.ErrorTypeQuotaExceeded: + statusCode = http.StatusTooManyRequests + errResponse = &types.ErrorResponse{ + Code: "quota_exceeded", + ErrorDescription: aclErr.Message, + } + + case acl.ErrorTypeInsufficientScope: + statusCode = http.StatusForbidden + errResponse = types.ErrInsufficientScope + + case acl.ErrorTypePermissionDenied: + statusCode = http.StatusForbidden + errResponse = types.ErrForbidden + + case acl.ErrorTypeResourceNotAllowed: + statusCode = http.StatusForbidden + errResponse = types.ErrAccessDenied + + case acl.ErrorTypeMethodNotAllowed: + statusCode = http.StatusMethodNotAllowed + errResponse = types.ErrMethodNotAllowed + + case acl.ErrorTypeIPBlocked, acl.ErrorTypeGeoRestricted, acl.ErrorTypeTimeRestricted: + statusCode = http.StatusForbidden + errResponse = types.ErrAccessDenied + + case acl.ErrorTypeInvalidRequest: + statusCode = http.StatusBadRequest + errResponse = &types.ErrorResponse{ + Code: "invalid_request", + ErrorDescription: aclErr.Message, + } + + case acl.ErrorTypeInternal: + statusCode = http.StatusInternalServerError + errResponse = types.ErrACLInternalError + + default: + statusCode = http.StatusInternalServerError + errResponse = types.ErrACLInternalError + } + + c.JSON(statusCode, errResponse) + c.Abort() + return + } + + // If it's not an ACL error, treat it as an internal error + c.JSON(http.StatusInternalServerError, types.ErrACLInternalError) + c.Abort() +} diff --git a/openapi/oauth/types/errors.go b/openapi/oauth/types/errors.go index e4bee7ed..8c2140a0 100644 --- a/openapi/oauth/types/errors.go +++ b/openapi/oauth/types/errors.go @@ -1,6 +1,6 @@ package types -// Error definitions +// Configuration Error definitions var ( ErrInvalidConfiguration = &ErrorResponse{Code: "invalid_configuration", ErrorDescription: "Invalid OAuth service configuration"} ErrStoreMissing = &ErrorResponse{Code: "store_missing", ErrorDescription: "Store is required for OAuth service"} @@ -9,3 +9,35 @@ var ( ErrInvalidTokenLifetime = &ErrorResponse{Code: "invalid_token_lifetime", ErrorDescription: "Token lifetime must be greater than 0"} ErrPKCEConfigurationInvalid = &ErrorResponse{Code: "pkce_configuration_invalid", ErrorDescription: "PKCE configuration is invalid"} ) + +// Authentication & Authorization Error definitions +var ( + // Token related errors + ErrUnauthorized = &ErrorResponse{Code: "unauthorized", ErrorDescription: "Authentication is required to access this resource"} + ErrInvalidToken = &ErrorResponse{Code: "invalid_token", ErrorDescription: "The access token provided is invalid, expired or malformed"} + ErrTokenExpired = &ErrorResponse{Code: "token_expired", ErrorDescription: "The access token has expired"} + ErrTokenMissing = &ErrorResponse{Code: "token_missing", ErrorDescription: "No access token provided in the request"} + ErrInvalidRefreshToken = &ErrorResponse{Code: "invalid_refresh_token", ErrorDescription: "The refresh token provided is invalid or expired"} + ErrRefreshTokenMissing = &ErrorResponse{Code: "refresh_token_missing", ErrorDescription: "No refresh token provided in the request"} + + // Permission related errors + ErrForbidden = &ErrorResponse{Code: "forbidden", ErrorDescription: "You do not have permission to access this resource"} + ErrInsufficientScope = &ErrorResponse{Code: "insufficient_scope", ErrorDescription: "The access token does not have the required scope"} + ErrAccessDenied = &ErrorResponse{Code: "access_denied", ErrorDescription: "Access to this resource has been denied"} + + // ACL related errors + ErrACLCheckFailed = &ErrorResponse{Code: "acl_check_failed", ErrorDescription: "ACL verification failed"} + ErrACLInternalError = &ErrorResponse{Code: "acl_internal_error", ErrorDescription: "Internal error occurred during ACL verification"} + + // Rate limiting errors + ErrRateLimitExceeded = &ErrorResponse{Code: "rate_limit_exceeded", ErrorDescription: "Too many requests. Please try again later"} + ErrTooManyRequests = &ErrorResponse{Code: "too_many_requests", ErrorDescription: "Request rate limit exceeded"} + + // Resource related errors + ErrResourceNotFound = &ErrorResponse{Code: "resource_not_found", ErrorDescription: "The requested resource was not found"} + ErrMethodNotAllowed = &ErrorResponse{Code: "method_not_allowed", ErrorDescription: "The HTTP method is not allowed for this resource"} + + // Server errors + ErrInternalServerError = &ErrorResponse{Code: "internal_server_error", ErrorDescription: "An internal server error occurred"} + ErrServiceUnavailable = &ErrorResponse{Code: "service_unavailable", ErrorDescription: "The service is temporarily unavailable"} +) diff --git a/openapi/openapi.go b/openapi/openapi.go index 82c273f3..a33066eb 100644 --- a/openapi/openapi.go +++ b/openapi/openapi.go @@ -15,6 +15,7 @@ import ( "github.com/yaoapp/yao/openapi/kb" "github.com/yaoapp/yao/openapi/messenger" "github.com/yaoapp/yao/openapi/oauth" + "github.com/yaoapp/yao/openapi/oauth/acl" "github.com/yaoapp/yao/openapi/oauth/types" "github.com/yaoapp/yao/openapi/team" "github.com/yaoapp/yao/openapi/user" @@ -63,6 +64,12 @@ func Load(appConfig config.Config) (*OpenAPI, error) { return nil, err } + // Load the ACL enforcer + _, err = acl.Load(&acl.Config{Enabled: true}) + if err != nil { + return nil, err + } + // Create the OpenAPI server Server = &OpenAPI{Config: &config, OAuth: oauthService} return Server, nil