diff --git a/job/README.md b/job/README.md new file mode 100644 index 00000000..8a9ed691 --- /dev/null +++ b/job/README.md @@ -0,0 +1 @@ +# Job diff --git a/job/job.go b/job/job.go new file mode 100644 index 00000000..8d7765aa --- /dev/null +++ b/job/job.go @@ -0,0 +1 @@ +package job diff --git a/job/types/interfaces.go b/job/types/interfaces.go new file mode 100644 index 00000000..4216a060 --- /dev/null +++ b/job/types/interfaces.go @@ -0,0 +1,12 @@ +package types + +import "context" + +// Job interface +type Job interface { + Run(ctx context.Context) error + AddTask(ctx context.Context, task Task) error +} + +// Task interface +type Task func(ctx context.Context, job Job) error diff --git a/job/types/types.go b/job/types/types.go new file mode 100644 index 00000000..ab1254f4 --- /dev/null +++ b/job/types/types.go @@ -0,0 +1 @@ +package types diff --git a/openapi/oauth/interfaces.go b/openapi/oauth/interfaces.go new file mode 100644 index 00000000..9a00f9cf --- /dev/null +++ b/openapi/oauth/interfaces.go @@ -0,0 +1,148 @@ +package oauth + +import ( + "context" +) + +// OAuth interface defines the complete OAuth 2.1 and MCP authorization server functionality +type OAuth interface { + // AuthorizationServer returns the authorization server endpoint URL + // This endpoint is used to initiate the authorization flow + AuthorizationServer(ctx context.Context) string + + // ProtectedResource returns the protected resource endpoint URL + // This endpoint is used to access protected resources using access tokens + ProtectedResource(ctx context.Context) string + + // Authorize processes an authorization request and returns an authorization code + // The authorization code can be exchanged for an access token + Authorize(ctx context.Context, request *AuthorizationRequest) (*AuthorizationResponse, error) + + // Token exchanges an authorization code for an access token + // This is the core token endpoint functionality + Token(ctx context.Context, grantType string, code string, clientID string, codeVerifier string) (*Token, error) + + // Revoke revokes an access token or refresh token + // Once revoked, the token cannot be used for accessing protected resources + Revoke(ctx context.Context, token string, tokenTypeHint string) error + + // Introspect returns information about an access token + // This endpoint allows resource servers to validate tokens + Introspect(ctx context.Context, token string) (*TokenIntrospectionResponse, error) + + // Register registers a new OAuth client with the authorization server + // This is used for static client registration + Register(ctx context.Context, clientInfo *ClientInfo) (*ClientInfo, error) + + // JWKS returns the JSON Web Key Set for token verification + // This endpoint provides public keys for validating JWT tokens + JWKS(ctx context.Context) (*JWKSResponse, error) + + // Endpoints returns a map of all available OAuth endpoints + // This provides endpoint discovery for clients + Endpoints(ctx context.Context) (map[string]string, error) + + // RefreshToken exchanges a refresh token for a new access token + // This allows clients to obtain fresh access tokens without user interaction + RefreshToken(ctx context.Context, refreshToken string, scope string) (*RefreshTokenResponse, error) + + // DeviceAuthorization initiates the device authorization flow + // This is used for devices with limited input capabilities + DeviceAuthorization(ctx context.Context, clientID string, scope string) (*DeviceAuthorizationResponse, error) + + // UserInfo returns user information for a given access token + // This endpoint provides user profile information in the format defined by the UserProvider + UserInfo(ctx context.Context, accessToken string) (interface{}, error) + + // GenerateCodeChallenge generates a code challenge from a code verifier + // This is used for PKCE (Proof Key for Code Exchange) flow + GenerateCodeChallenge(ctx context.Context, codeVerifier string, method string) (string, error) + + // ValidateCodeChallenge validates a code verifier against a code challenge + // This verifies the PKCE code challenge during token exchange + ValidateCodeChallenge(ctx context.Context, codeVerifier string, codeChallenge string, method string) error + + // PushAuthorizationRequest processes a pushed authorization request + // This implements RFC 9126 for enhanced security + PushAuthorizationRequest(ctx context.Context, request *PushedAuthorizationRequest) (*PushedAuthorizationResponse, error) + + // TokenExchange exchanges one token for another token + // This implements RFC 8693 for token exchange scenarios + TokenExchange(ctx context.Context, subjectToken string, subjectTokenType string, audience string, scope string) (*TokenExchangeResponse, error) + + // UpdateClient updates an existing OAuth client configuration + // This allows modification of client metadata + UpdateClient(ctx context.Context, clientID string, clientInfo *ClientInfo) (*ClientInfo, error) + + // DeleteClient removes an OAuth client from the authorization server + // This permanently deletes the client and invalidates all associated tokens + DeleteClient(ctx context.Context, clientID string) error + + // ValidateScope validates requested scopes against available scopes + // This ensures clients only request permitted scopes + ValidateScope(ctx context.Context, requestedScopes []string, clientID string) (*ValidationResult, error) + + // GetServerMetadata returns OAuth 2.0 Authorization Server Metadata + // This implements RFC 8414 for server discovery + GetServerMetadata(ctx context.Context) (*AuthorizationServerMetadata, error) + + // MCP Requirements + + // ValidateResourceParameter validates an OAuth 2.0 resource parameter + // This ensures the resource parameter is valid and properly formatted + ValidateResourceParameter(ctx context.Context, resource string) (*ValidationResult, error) + + // GetCanonicalResourceURI returns the canonical form of a resource URI + // This normalizes resource URIs for consistent processing + GetCanonicalResourceURI(ctx context.Context, serverURI string) (string, error) + + // GetProtectedResourceMetadata returns OAuth 2.0 Protected Resource Metadata + // This implements RFC 9728 for MCP server discovery + GetProtectedResourceMetadata(ctx context.Context) (*ProtectedResourceMetadata, error) + + // HandleWWWAuthenticate processes WWW-Authenticate challenges + // This handles authentication challenges from protected resources + HandleWWWAuthenticate(ctx context.Context, challenge string) (*WWWAuthenticateChallenge, error) + + // DynamicClientRegistration handles dynamic client registration + // This implements RFC 7591 for automatic client registration + DynamicClientRegistration(ctx context.Context, request *DynamicClientRegistrationRequest) (*DynamicClientRegistrationResponse, error) + + // ValidateStateParameter validates OAuth state parameters + // This prevents CSRF attacks by verifying state parameters + ValidateStateParameter(ctx context.Context, state string, clientID string) (*ValidationResult, error) + + // GenerateStateParameter generates a secure state parameter + // This creates cryptographically secure state values for CSRF protection + GenerateStateParameter(ctx context.Context, clientID string) (*StateParameter, error) + + // ValidateTokenAudience validates token audience claims + // This ensures tokens are only used with their intended audiences + ValidateTokenAudience(ctx context.Context, token string, expectedAudience string) (*ValidationResult, error) + + // MCP Security Requirements + + // ValidateRedirectURI validates redirect URIs against registered URIs + // This prevents open redirect attacks by enforcing exact URI matching + ValidateRedirectURI(ctx context.Context, redirectURI string, registeredURIs []string) (*ValidationResult, error) + + // RotateRefreshToken rotates a refresh token and invalidates the old one + // This implements refresh token rotation for enhanced security + RotateRefreshToken(ctx context.Context, oldToken string) (*RefreshTokenResponse, error) + + // ValidateTokenBinding validates token binding information + // This ensures tokens are bound to the correct client or device + ValidateTokenBinding(ctx context.Context, token string, binding *TokenBinding) (*ValidationResult, error) +} + +// UserProvider interface for user information retrieval +type UserProvider interface { + // GetUserByAccessToken retrieves user information using an access token + GetUserByAccessToken(ctx context.Context, accessToken string) (interface{}, error) + + // GetUserBySubject retrieves user information using a subject identifier + GetUserBySubject(ctx context.Context, subject string) (interface{}, error) + + // ValidateUserScope validates if a user has access to requested scopes + ValidateUserScope(ctx context.Context, userID string, scopes []string) (bool, error) +} diff --git a/openapi/oauth/oauth.go b/openapi/oauth/oauth.go index c90af7b3..750c370b 100644 --- a/openapi/oauth/oauth.go +++ b/openapi/oauth/oauth.go @@ -1 +1,234 @@ package oauth + +import ( + "context" + "time" + + "github.com/yaoapp/gou/store" +) + +// Service OAuth service +type Service struct { + config *Config + store store.Store + userProvider UserProvider +} + +// Config OAuth service configuration +type Config struct { + // Core storage interface + Store store.Store `json:"-"` + + // User provider interface + UserProvider UserProvider `json:"-"` + + // Certificate and key management + Signing SigningConfig `json:"signing"` + + // Token management settings + Token TokenConfig `json:"token"` + + // Security configuration + Security SecurityConfig `json:"security"` + + // Default client settings + Client ClientConfig `json:"client"` + + // Feature flags + Features FeatureFlags `json:"features"` + + // OAuth server metadata + IssuerURL string `json:"issuer_url"` // JWT token issuer URL +} + +// FeatureFlags represents feature toggle configuration +type FeatureFlags struct { + // OAuth 2.1 features + OAuth21Enabled bool `json:"oauth21_enabled"` + PKCEEnforced bool `json:"pkce_enforced"` + RefreshTokenRotationEnabled bool `json:"refresh_token_rotation_enabled"` + + // Advanced features + DeviceFlowEnabled bool `json:"device_flow_enabled"` + TokenExchangeEnabled bool `json:"token_exchange_enabled"` + PushedAuthorizationEnabled bool `json:"pushed_authorization_enabled"` + DynamicClientRegistrationEnabled bool `json:"dynamic_client_registration_enabled"` + + // MCP features + MCPComplianceEnabled bool `json:"mcp_compliance_enabled"` + ResourceParameterEnabled bool `json:"resource_parameter_enabled"` + + // Security features + TokenBindingEnabled bool `json:"token_binding_enabled"` + MTLSEnabled bool `json:"mtls_enabled"` + DPoPEnabled bool `json:"dpop_enabled"` + + // Experimental features + JWTIntrospectionEnabled bool `json:"jwt_introspection_enabled"` + TokenRevocationEnabled bool `json:"token_revocation_enabled"` + UserInfoJWTEnabled bool `json:"userinfo_jwt_enabled"` +} + +// NewService creates a new OAuth service with the given configuration +func NewService(config *Config) (*Service, error) { + if config == nil { + return nil, ErrInvalidConfiguration + } + + // Set default values if not provided + if err := setConfigDefaults(config); err != nil { + return nil, err + } + + // Validate configuration + if err := validateConfig(config); err != nil { + return nil, err + } + + // Use UserProvider from config, or create a default one if not provided + userProvider := config.UserProvider + if userProvider == nil { + userProvider = NewDefaultUserProvider(nil, nil, nil) + } + + service := &Service{ + config: config, + store: config.Store, + userProvider: userProvider, + } + + return service, nil +} + +// GetConfig returns the service configuration +func (s *Service) GetConfig() *Config { + return s.config +} + +// GetUserProvider returns the user provider for the service +func (s *Service) GetUserProvider() UserProvider { + return s.userProvider +} + +// setConfigDefaults sets default values for configuration +func setConfigDefaults(config *Config) error { + // Certificate defaults + if config.Signing.SigningAlgorithm == "" { + config.Signing.SigningAlgorithm = "RS256" + } + + // Token defaults + if config.Token.AccessTokenLifetime == 0 { + config.Token.AccessTokenLifetime = time.Hour + } + if config.Token.RefreshTokenLifetime == 0 { + config.Token.RefreshTokenLifetime = 24 * time.Hour + } + if config.Token.AuthorizationCodeLifetime == 0 { + config.Token.AuthorizationCodeLifetime = 10 * time.Minute + } + if config.Token.DeviceCodeLifetime == 0 { + config.Token.DeviceCodeLifetime = 15 * time.Minute + } + if config.Token.AccessTokenFormat == "" { + config.Token.AccessTokenFormat = "jwt" + } + if config.Token.RefreshTokenFormat == "" { + config.Token.RefreshTokenFormat = "opaque" + } + + // Security defaults + if len(config.Security.PKCECodeChallengeMethod) == 0 { + config.Security.PKCECodeChallengeMethod = []string{"S256"} + } + if config.Security.PKCECodeVerifierLength == 0 { + config.Security.PKCECodeVerifierLength = 128 + } + if config.Security.StateParameterLifetime == 0 { + config.Security.StateParameterLifetime = 10 * time.Minute + } + if config.Security.StateParameterLength == 0 { + config.Security.StateParameterLength = 32 + } + + // Client defaults + if config.Client.DefaultClientType == "" { + config.Client.DefaultClientType = "confidential" + } + if config.Client.DefaultTokenEndpointAuthMethod == "" { + config.Client.DefaultTokenEndpointAuthMethod = "client_secret_basic" + } + if len(config.Client.DefaultGrantTypes) == 0 { + config.Client.DefaultGrantTypes = []string{"authorization_code", "refresh_token"} + } + if len(config.Client.DefaultResponseTypes) == 0 { + config.Client.DefaultResponseTypes = []string{"code"} + } + if config.Client.ClientIDLength == 0 { + config.Client.ClientIDLength = 32 + } + if config.Client.ClientSecretLength == 0 { + config.Client.ClientSecretLength = 64 + } + + // Feature flags defaults - enable OAuth 2.1 features by default + config.Features.OAuth21Enabled = true + config.Features.PKCEEnforced = true + config.Features.RefreshTokenRotationEnabled = true + + return nil +} + +// validateConfig validates the configuration +func validateConfig(config *Config) error { + if config.Store == nil { + return ErrStoreMissing + } + + // Validate issuer URL + if config.IssuerURL == "" { + return ErrIssuerURLMissing + } + + // Validate certificate configuration + if config.Signing.SigningCertPath == "" || config.Signing.SigningKeyPath == "" { + return ErrCertificateMissing + } + + // Validate token configuration + if config.Token.AccessTokenLifetime <= 0 { + return ErrInvalidTokenLifetime + } + + // Validate security configuration + if config.Security.PKCERequired && len(config.Security.PKCECodeChallengeMethod) == 0 { + return ErrPKCEConfigurationInvalid + } + + return nil +} + +// 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"} + ErrIssuerURLMissing = &ErrorResponse{Code: "issuer_url_missing", ErrorDescription: "Issuer URL is required for OAuth service"} + ErrCertificateMissing = &ErrorResponse{Code: "certificate_missing", ErrorDescription: "JWT signing certificate and key are required"} + 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"} +) + +// AuthorizationServer returns the authorization server endpoint URL +func (s *Service) AuthorizationServer(ctx context.Context) string { + return s.config.IssuerURL +} + +// ProtectedResource returns the protected resource endpoint URL +func (s *Service) ProtectedResource(ctx context.Context) string { + return s.config.IssuerURL +} + +// UserInfo returns user information for a given access token +func (s *Service) UserInfo(ctx context.Context, accessToken string) (interface{}, error) { + return s.userProvider.GetUserByAccessToken(ctx, accessToken) +} diff --git a/openapi/oauth/types.go b/openapi/oauth/types.go new file mode 100644 index 00000000..822c3e9f --- /dev/null +++ b/openapi/oauth/types.go @@ -0,0 +1,555 @@ +package oauth + +import ( + "time" +) + +// ErrorResponse represents an OAuth 2.1 error response +type ErrorResponse struct { + Code string `json:"error"` + ErrorDescription string `json:"error_description,omitempty"` + ErrorURI string `json:"error_uri,omitempty"` + State string `json:"state,omitempty"` +} + +// Error implements the error interface +func (e *ErrorResponse) Error() string { + if e.ErrorDescription != "" { + return e.Code + ": " + e.ErrorDescription + } + return e.Code +} + +// OAuth 2.1 Grant Types +const ( + GrantTypeAuthorizationCode = "authorization_code" + GrantTypeClientCredentials = "client_credentials" + GrantTypeRefreshToken = "refresh_token" + GrantTypeDeviceCode = "urn:ietf:params:oauth:grant-type:device_code" + GrantTypeTokenExchange = "urn:ietf:params:oauth:grant-type:token-exchange" +) + +// OAuth 2.1 Response Types +const ( + ResponseTypeCode = "code" +) + +// OAuth 2.1 Token Types +const ( + TokenTypeBearer = "Bearer" + TokenTypeMAC = "MAC" + TokenTypeDPoP = "DPoP" +) + +// OAuth 2.1 Client Types +const ( + ClientTypeConfidential = "confidential" + ClientTypePublic = "public" + ClientTypeCredentialed = "credentialed" +) + +// PKCE Code Challenge Methods +const ( + CodeChallengeMethodS256 = "S256" + CodeChallengeMethodPlain = "plain" +) + +// OAuth 2.1 Error Codes +const ( + ErrorInvalidRequest = "invalid_request" + ErrorInvalidClient = "invalid_client" + ErrorInvalidGrant = "invalid_grant" + ErrorUnauthorizedClient = "unauthorized_client" + ErrorUnsupportedGrantType = "unsupported_grant_type" + ErrorInvalidScope = "invalid_scope" + ErrorAccessDenied = "access_denied" + ErrorUnsupportedResponseType = "unsupported_response_type" + ErrorServerError = "server_error" + ErrorTemporarilyUnavailable = "temporarily_unavailable" + ErrorInvalidToken = "invalid_token" + ErrorInsufficientScope = "insufficient_scope" + ErrorExpiredToken = "expired_token" + ErrorAuthorizationPending = "authorization_pending" + ErrorSlowDown = "slow_down" +) + +// Token Binding Types +const ( + TokenBindingTypeDPoP = "dpop" + TokenBindingTypeMTLS = "mtls" + TokenBindingTypeCertificate = "certificate" +) + +// Application Types +const ( + ApplicationTypeWeb = "web" + ApplicationTypeNative = "native" +) + +// Token Endpoint Authentication Methods +const ( + TokenEndpointAuthNone = "none" + TokenEndpointAuthPost = "client_secret_post" + TokenEndpointAuthBasic = "client_secret_basic" + TokenEndpointAuthJWT = "client_secret_jwt" + TokenEndpointAuthPrivateKeyJWT = "private_key_jwt" + TokenEndpointAuthTLSClientAuth = "tls_client_auth" + TokenEndpointAuthSelfSignedTLS = "self_signed_tls_client_auth" +) + +// Response Modes +const ( + ResponseModeQuery = "query" + ResponseModeFragment = "fragment" + ResponseModeFormPost = "form_post" +) + +// Standard OAuth Scopes +const ( + ScopeOpenID = "openid" + ScopeProfile = "profile" + ScopeEmail = "email" + ScopeAddress = "address" + ScopePhone = "phone" + ScopeOffline = "offline_access" +) + +// MCP Specific Constants +const ( + MCPResourceParameter = "resource" + MCPBearerTokenHeader = "Authorization" + MCPBearerTokenPrefix = "Bearer " +) + +// WWW-Authenticate Schemes +const ( + WWWAuthenticateSchemeBearer = "Bearer" + WWWAuthenticateSchemeBasic = "Basic" + WWWAuthenticateSchemeDPoP = "DPoP" +) + +// Token represents an OAuth 2.1 access token +type Token struct { + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + ExpiresIn int `json:"expires_in"` + RefreshToken string `json:"refresh_token,omitempty"` + Scope string `json:"scope,omitempty"` + IssuedAt time.Time `json:"issued_at"` + ExpiresAt time.Time `json:"expires_at"` + Audience []string `json:"audience,omitempty"` + Subject string `json:"subject,omitempty"` + Issuer string `json:"issuer,omitempty"` + ClientID string `json:"client_id,omitempty"` +} + +// RefreshTokenResponse represents the response from refresh token endpoint +type RefreshTokenResponse struct { + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + ExpiresIn int `json:"expires_in"` + RefreshToken string `json:"refresh_token,omitempty"` + Scope string `json:"scope,omitempty"` +} + +// DeviceAuthorizationResponse represents device authorization response +type DeviceAuthorizationResponse struct { + DeviceCode string `json:"device_code"` + UserCode string `json:"user_code"` + VerificationURI string `json:"verification_uri"` + VerificationURIComplete string `json:"verification_uri_complete,omitempty"` + ExpiresIn int `json:"expires_in"` + Interval int `json:"interval,omitempty"` +} + +// UserInfo represents user information from userinfo endpoint +type UserInfo struct { + Subject string `json:"sub"` + Name string `json:"name,omitempty"` + GivenName string `json:"given_name,omitempty"` + FamilyName string `json:"family_name,omitempty"` + MiddleName string `json:"middle_name,omitempty"` + Nickname string `json:"nickname,omitempty"` + PreferredUsername string `json:"preferred_username,omitempty"` + Profile string `json:"profile,omitempty"` + Picture string `json:"picture,omitempty"` + Website string `json:"website,omitempty"` + Email string `json:"email,omitempty"` + EmailVerified bool `json:"email_verified,omitempty"` + Gender string `json:"gender,omitempty"` + Birthdate string `json:"birthdate,omitempty"` + Zoneinfo string `json:"zoneinfo,omitempty"` + Locale string `json:"locale,omitempty"` + PhoneNumber string `json:"phone_number,omitempty"` + PhoneVerified bool `json:"phone_number_verified,omitempty"` + Address *UserAddress `json:"address,omitempty"` + UpdatedAt int64 `json:"updated_at,omitempty"` + CustomClaims map[string]interface{} `json:"-"` +} + +// UserAddress represents user address information +type UserAddress struct { + Formatted string `json:"formatted,omitempty"` + StreetAddress string `json:"street_address,omitempty"` + Locality string `json:"locality,omitempty"` + Region string `json:"region,omitempty"` + PostalCode string `json:"postal_code,omitempty"` + Country string `json:"country,omitempty"` +} + +// ClientInfo represents OAuth client information +type ClientInfo struct { + ClientID string `json:"client_id"` + ClientSecret string `json:"client_secret,omitempty"` + ClientName string `json:"client_name,omitempty"` + ClientType string `json:"client_type"` // "confidential", "public", "credentialed" + RedirectURIs []string `json:"redirect_uris"` + ResponseTypes []string `json:"response_types,omitempty"` + GrantTypes []string `json:"grant_types,omitempty"` + ApplicationType string `json:"application_type,omitempty"` + Contacts []string `json:"contacts,omitempty"` + ClientURI string `json:"client_uri,omitempty"` + LogoURI string `json:"logo_uri,omitempty"` + Scope string `json:"scope,omitempty"` + TosURI string `json:"tos_uri,omitempty"` + PolicyURI string `json:"policy_uri,omitempty"` + JwksURI string `json:"jwks_uri,omitempty"` + JwksValue string `json:"jwks,omitempty"` + TokenEndpointAuthMethod string `json:"token_endpoint_auth_method,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty"` + UpdatedAt time.Time `json:"updated_at,omitempty"` +} + +// AuthorizationServerMetadata represents OAuth 2.0 Authorization Server Metadata (RFC 8414) +type AuthorizationServerMetadata struct { + Issuer string `json:"issuer"` + AuthorizationEndpoint string `json:"authorization_endpoint"` + TokenEndpoint string `json:"token_endpoint"` + JwksURI string `json:"jwks_uri,omitempty"` + RegistrationEndpoint string `json:"registration_endpoint,omitempty"` + ScopesSupported []string `json:"scopes_supported,omitempty"` + ResponseTypesSupported []string `json:"response_types_supported"` + ResponseModesSupported []string `json:"response_modes_supported,omitempty"` + GrantTypesSupported []string `json:"grant_types_supported,omitempty"` + TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported,omitempty"` + TokenEndpointAuthSigningAlgValuesSupported []string `json:"token_endpoint_auth_signing_alg_values_supported,omitempty"` + ServiceDocumentation string `json:"service_documentation,omitempty"` + UILocalesSupported []string `json:"ui_locales_supported,omitempty"` + OpPolicyURI string `json:"op_policy_uri,omitempty"` + OpTosURI string `json:"op_tos_uri,omitempty"` + RevocationEndpoint string `json:"revocation_endpoint,omitempty"` + RevocationEndpointAuthMethodsSupported []string `json:"revocation_endpoint_auth_methods_supported,omitempty"` + IntrospectionEndpoint string `json:"introspection_endpoint,omitempty"` + IntrospectionEndpointAuthMethodsSupported []string `json:"introspection_endpoint_auth_methods_supported,omitempty"` + CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported,omitempty"` + DeviceAuthorizationEndpoint string `json:"device_authorization_endpoint,omitempty"` + UserinfoEndpoint string `json:"userinfo_endpoint,omitempty"` + PushedAuthorizationRequestEndpoint string `json:"pushed_authorization_request_endpoint,omitempty"` + RequirePushedAuthorizationRequests bool `json:"require_pushed_authorization_requests,omitempty"` + DPoPSigningAlgValuesSupported []string `json:"dpop_signing_alg_values_supported,omitempty"` +} + +// ProtectedResourceMetadata represents OAuth 2.0 Protected Resource Metadata (RFC 9728) +type ProtectedResourceMetadata struct { + Resource string `json:"resource"` + AuthorizationServers []string `json:"authorization_servers"` + JwksURI string `json:"jwks_uri,omitempty"` + BearerMethodsSupported []string `json:"bearer_methods_supported,omitempty"` + ResourceDocumentation string `json:"resource_documentation,omitempty"` +} + +// TokenIntrospectionResponse represents token introspection response +type TokenIntrospectionResponse struct { + Active bool `json:"active"` + Scope string `json:"scope,omitempty"` + ClientID string `json:"client_id,omitempty"` + Username string `json:"username,omitempty"` + TokenType string `json:"token_type,omitempty"` + ExpiresAt int64 `json:"exp,omitempty"` + IssuedAt int64 `json:"iat,omitempty"` + NotBefore int64 `json:"nbf,omitempty"` + Subject string `json:"sub,omitempty"` + Audience []string `json:"aud,omitempty"` + Issuer string `json:"iss,omitempty"` + JwtID string `json:"jti,omitempty"` +} + +// PushedAuthorizationRequest represents PAR request +type PushedAuthorizationRequest struct { + ClientID string `json:"client_id"` + ResponseType string `json:"response_type"` + RedirectURI string `json:"redirect_uri"` + Scope string `json:"scope,omitempty"` + State string `json:"state,omitempty"` + CodeChallenge string `json:"code_challenge,omitempty"` + CodeChallengeMethod string `json:"code_challenge_method,omitempty"` + Resource string `json:"resource,omitempty"` + RequestURI string `json:"request_uri,omitempty"` + Request string `json:"request,omitempty"` +} + +// PushedAuthorizationResponse represents PAR response +type PushedAuthorizationResponse struct { + RequestURI string `json:"request_uri"` + ExpiresIn int `json:"expires_in"` +} + +// TokenExchangeResponse represents token exchange response +type TokenExchangeResponse struct { + AccessToken string `json:"access_token"` + IssuedTokenType string `json:"issued_token_type"` + TokenType string `json:"token_type"` + ExpiresIn int `json:"expires_in,omitempty"` + Scope string `json:"scope,omitempty"` + RefreshToken string `json:"refresh_token,omitempty"` +} + +// DynamicClientRegistrationRequest represents dynamic client registration request +type DynamicClientRegistrationRequest struct { + RedirectURIs []string `json:"redirect_uris"` + ResponseTypes []string `json:"response_types,omitempty"` + GrantTypes []string `json:"grant_types,omitempty"` + ApplicationType string `json:"application_type,omitempty"` + Contacts []string `json:"contacts,omitempty"` + ClientName string `json:"client_name,omitempty"` + LogoURI string `json:"logo_uri,omitempty"` + ClientURI string `json:"client_uri,omitempty"` + PolicyURI string `json:"policy_uri,omitempty"` + TosURI string `json:"tos_uri,omitempty"` + JwksURI string `json:"jwks_uri,omitempty"` + Jwks string `json:"jwks,omitempty"` + Scope string `json:"scope,omitempty"` + TokenEndpointAuthMethod string `json:"token_endpoint_auth_method,omitempty"` + TokenEndpointAuthSigningAlg string `json:"token_endpoint_auth_signing_alg,omitempty"` + DefaultMaxAge int `json:"default_max_age,omitempty"` + RequireAuthTime bool `json:"require_auth_time,omitempty"` + DefaultACRValues []string `json:"default_acr_values,omitempty"` + InitiateLoginURI string `json:"initiate_login_uri,omitempty"` + RequestURIs []string `json:"request_uris,omitempty"` + SoftwareID string `json:"software_id,omitempty"` + SoftwareVersion string `json:"software_version,omitempty"` + SoftwareStatement string `json:"software_statement,omitempty"` +} + +// DynamicClientRegistrationResponse represents dynamic client registration response +type DynamicClientRegistrationResponse struct { + ClientID string `json:"client_id"` + ClientSecret string `json:"client_secret,omitempty"` + ClientSecretExpiresAt int64 `json:"client_secret_expires_at,omitempty"` + RegistrationAccessToken string `json:"registration_access_token,omitempty"` + RegistrationClientURI string `json:"registration_client_uri,omitempty"` + ClientIDIssuedAt int64 `json:"client_id_issued_at,omitempty"` + *DynamicClientRegistrationRequest +} + +// WWWAuthenticateChallenge represents WWW-Authenticate challenge +type WWWAuthenticateChallenge struct { + Scheme string `json:"scheme"` + Realm string `json:"realm,omitempty"` + Scope string `json:"scope,omitempty"` + Error string `json:"error,omitempty"` + ErrorDesc string `json:"error_description,omitempty"` + ErrorURI string `json:"error_uri,omitempty"` + Resource string `json:"resource,omitempty"` + Parameters map[string]string `json:"parameters,omitempty"` +} + +// StateParameter represents OAuth state parameter +type StateParameter struct { + Value string `json:"value"` + ExpiresAt time.Time `json:"expires_at"` + ClientID string `json:"client_id"` + Nonce string `json:"nonce,omitempty"` +} + +// TokenBinding represents token binding information +type TokenBinding struct { + TokenID string `json:"token_id"` + ClientID string `json:"client_id"` + BindingType string `json:"binding_type"` // "dpop", "mtls", "certificate" + BindingValue string `json:"binding_value"` + BindingData map[string]interface{} `json:"binding_data,omitempty"` + CreatedAt time.Time `json:"created_at"` + ExpiresAt time.Time `json:"expires_at"` +} + +// ResourceParameter represents OAuth 2.0 resource parameter +type ResourceParameter struct { + Resource string `json:"resource"` + Canonical string `json:"canonical"` + Audiences []string `json:"audiences,omitempty"` + Scopes []string `json:"scopes,omitempty"` + ValidatedAt time.Time `json:"validated_at"` +} + +// ValidationResult represents validation result +type ValidationResult struct { + Valid bool `json:"valid"` + Errors []string `json:"errors,omitempty"` + Details map[string]string `json:"details,omitempty"` +} + +// AuthorizationRequest represents authorization request +type AuthorizationRequest struct { + ClientID string `json:"client_id"` + ResponseType string `json:"response_type"` + RedirectURI string `json:"redirect_uri"` + Scope string `json:"scope,omitempty"` + State string `json:"state,omitempty"` + CodeChallenge string `json:"code_challenge,omitempty"` + CodeChallengeMethod string `json:"code_challenge_method,omitempty"` + Resource string `json:"resource,omitempty"` + Nonce string `json:"nonce,omitempty"` +} + +// AuthorizationResponse represents authorization response +type AuthorizationResponse struct { + Code string `json:"code,omitempty"` + State string `json:"state,omitempty"` + Error string `json:"error,omitempty"` + ErrorDescription string `json:"error_description,omitempty"` +} + +// JWKSResponse represents JWKS response +type JWKSResponse struct { + Keys []JWK `json:"keys"` +} + +// JWK represents JSON Web Key +type JWK struct { + Kty string `json:"kty"` + Use string `json:"use,omitempty"` + KeyOps []string `json:"key_ops,omitempty"` + Alg string `json:"alg,omitempty"` + Kid string `json:"kid,omitempty"` + X5U string `json:"x5u,omitempty"` + X5C []string `json:"x5c,omitempty"` + X5T string `json:"x5t,omitempty"` + X5TS256 string `json:"x5t#S256,omitempty"` + // RSA + N string `json:"n,omitempty"` + E string `json:"e,omitempty"` + D string `json:"d,omitempty"` + P string `json:"p,omitempty"` + Q string `json:"q,omitempty"` + DP string `json:"dp,omitempty"` + DQ string `json:"dq,omitempty"` + QI string `json:"qi,omitempty"` + // EC + Crv string `json:"crv,omitempty"` + X string `json:"x,omitempty"` + Y string `json:"y,omitempty"` + // Symmetric + K string `json:"k,omitempty"` +} + +// OAuth Service Configuration Types + +// SigningConfig represents signing configuration for OAuth service +type SigningConfig struct { + // Token signing certificate and key (for JWT tokens) + SigningCertPath string `json:"signing_cert_path"` + SigningKeyPath string `json:"signing_key_path"` + SigningKeyPassword string `json:"signing_key_password,omitempty"` + SigningAlgorithm string `json:"signing_algorithm"` // RS256, RS384, RS512, ES256, ES384, ES512 + + // Token verification certificates (for token validation) + VerificationCerts []string `json:"verification_certs,omitempty"` + + // mTLS client certificate validation + MTLSClientCACertPath string `json:"mtls_client_ca_cert_path,omitempty"` + MTLSEnabled bool `json:"mtls_enabled"` + + // Certificate rotation settings + CertRotationEnabled bool `json:"cert_rotation_enabled"` + CertRotationInterval time.Duration `json:"cert_rotation_interval"` +} + +// TokenConfig represents token-related configuration +type TokenConfig struct { + // Access token settings + AccessTokenLifetime time.Duration `json:"access_token_lifetime"` // 1h + AccessTokenFormat string `json:"access_token_format"` // jwt, opaque + AccessTokenSigningAlg string `json:"access_token_signing_alg"` // RS256 + + // Refresh token settings + RefreshTokenLifetime time.Duration `json:"refresh_token_lifetime"` // 24h + RefreshTokenRotation bool `json:"refresh_token_rotation"` // true for OAuth 2.1 + RefreshTokenFormat string `json:"refresh_token_format"` // opaque, jwt + + // Authorization code settings + AuthorizationCodeLifetime time.Duration `json:"authorization_code_lifetime"` // 10m + AuthorizationCodeLength int `json:"authorization_code_length"` // 32 + + // Device code settings + DeviceCodeLifetime time.Duration `json:"device_code_lifetime"` // 15m + DeviceCodeLength int `json:"device_code_length"` // 8 + UserCodeLength int `json:"user_code_length"` // 8 + DeviceCodeInterval time.Duration `json:"device_code_interval"` // 5s + + // Token binding settings + TokenBindingEnabled bool `json:"token_binding_enabled"` + SupportedBindingTypes []string `json:"supported_binding_types"` // dpop, mtls + + // Token audience settings + DefaultAudience []string `json:"default_audience"` + AudienceValidationMode string `json:"audience_validation_mode"` // strict, relaxed +} + +// SecurityConfig represents security-related configuration +type SecurityConfig struct { + // PKCE settings (mandatory for OAuth 2.1) + PKCERequired bool `json:"pkce_required"` // true for OAuth 2.1 + PKCECodeChallengeMethod []string `json:"pkce_code_challenge_method"` // S256 + PKCECodeVerifierLength int `json:"pkce_code_verifier_length"` // 128 + + // State parameter settings + StateParameterRequired bool `json:"state_parameter_required"` + StateParameterLifetime time.Duration `json:"state_parameter_lifetime"` // 10m + StateParameterLength int `json:"state_parameter_length"` // 32 + + // Rate limiting + RateLimitEnabled bool `json:"rate_limit_enabled"` + RateLimitRequests int `json:"rate_limit_requests"` // requests per window + RateLimitWindow time.Duration `json:"rate_limit_window"` // 1m + RateLimitByClientID bool `json:"rate_limit_by_client_id"` + + // Brute force protection + BruteForceProtectionEnabled bool `json:"brute_force_protection_enabled"` + MaxFailedAttempts int `json:"max_failed_attempts"` // 5 + LockoutDuration time.Duration `json:"lockout_duration"` // 15m + + // Encryption settings + EncryptionKey string `json:"encryption_key"` // for encrypting sensitive data + EncryptionAlgorithm string `json:"encryption_algorithm"` // AES-256-GCM + + // Additional security features + IPWhitelist []string `json:"ip_whitelist,omitempty"` + IPBlacklist []string `json:"ip_blacklist,omitempty"` + RequireHTTPS bool `json:"require_https"` + DisableUnsecureEndpoints bool `json:"disable_unsecure_endpoints"` +} + +// ClientConfig represents default client configuration +type ClientConfig struct { + // Default client settings + DefaultClientType string `json:"default_client_type"` // confidential, public + DefaultTokenEndpointAuthMethod string `json:"default_token_endpoint_auth_method"` // client_secret_basic, client_secret_post, private_key_jwt + DefaultGrantTypes []string `json:"default_grant_types"` // authorization_code, refresh_token + DefaultResponseTypes []string `json:"default_response_types"` // code + DefaultScopes []string `json:"default_scopes"` // openid, profile, email + + // Client validation settings + ClientIDLength int `json:"client_id_length"` // 32 + ClientSecretLength int `json:"client_secret_length"` // 64 + ClientSecretLifetime time.Duration `json:"client_secret_lifetime"` // 0 (never expires) + + // Dynamic client registration + DynamicRegistrationEnabled bool `json:"dynamic_registration_enabled"` + AllowedRedirectURISchemes []string `json:"allowed_redirect_uri_schemes"` // https, http (for dev) + AllowedRedirectURIHosts []string `json:"allowed_redirect_uri_hosts"` // localhost (for dev) + + // Client certificate settings + ClientCertificateRequired bool `json:"client_certificate_required"` + ClientCertificateValidation string `json:"client_certificate_validation"` // none, optional, required +} diff --git a/openapi/oauth/user.go b/openapi/oauth/user.go new file mode 100644 index 00000000..cd0d3917 --- /dev/null +++ b/openapi/oauth/user.go @@ -0,0 +1,50 @@ +package oauth + +import ( + "context" +) + +// DefaultUserProvider provides a default implementation of UserProvider +type DefaultUserProvider struct { + getUserByAccessTokenFunc func(ctx context.Context, accessToken string) (interface{}, error) + getUserBySubjectFunc func(ctx context.Context, subject string) (interface{}, error) + validateUserScopeFunc func(ctx context.Context, userID string, scopes []string) (bool, error) +} + +// NewDefaultUserProvider creates a new DefaultUserProvider with the given functions +func NewDefaultUserProvider( + getUserByAccessTokenFunc func(ctx context.Context, accessToken string) (interface{}, error), + getUserBySubjectFunc func(ctx context.Context, subject string) (interface{}, error), + validateUserScopeFunc func(ctx context.Context, userID string, scopes []string) (bool, error), +) *DefaultUserProvider { + return &DefaultUserProvider{ + getUserByAccessTokenFunc: getUserByAccessTokenFunc, + getUserBySubjectFunc: getUserBySubjectFunc, + validateUserScopeFunc: validateUserScopeFunc, + } +} + +// GetUserByAccessToken retrieves user information using an access token +func (p *DefaultUserProvider) GetUserByAccessToken(ctx context.Context, accessToken string) (interface{}, error) { + if p.getUserByAccessTokenFunc == nil { + return nil, &ErrorResponse{Code: "not_implemented", ErrorDescription: "GetUserByAccessToken is not implemented"} + } + return p.getUserByAccessTokenFunc(ctx, accessToken) +} + +// GetUserBySubject retrieves user information using a subject identifier +func (p *DefaultUserProvider) GetUserBySubject(ctx context.Context, subject string) (interface{}, error) { + if p.getUserBySubjectFunc == nil { + return nil, &ErrorResponse{Code: "not_implemented", ErrorDescription: "GetUserBySubject is not implemented"} + } + return p.getUserBySubjectFunc(ctx, subject) +} + +// ValidateUserScope validates if a user has access to requested scopes +func (p *DefaultUserProvider) ValidateUserScope(ctx context.Context, userID string, scopes []string) (bool, error) { + if p.validateUserScopeFunc == nil { + // Default implementation: allow all scopes + return true, nil + } + return p.validateUserScopeFunc(ctx, userID, scopes) +}