 131868bdca
			
		
	
	131868bdca
	
	
	
		
			
			Major security, observability, and configuration improvements:
## Security Hardening
- Implemented configurable CORS (no more wildcards)
- Added comprehensive auth middleware for admin endpoints
- Enhanced webhook HMAC validation
- Added input validation and rate limiting
- Security headers and CSP policies
## Configuration Management
- Made N8N webhook URL configurable (WHOOSH_N8N_BASE_URL)
- Replaced all hardcoded endpoints with environment variables
- Added feature flags for LLM vs heuristic composition
- Gitea fetch hardening with EAGER_FILTER and FULL_RESCAN options
## API Completeness
- Implemented GetCouncilComposition function
- Added GET /api/v1/councils/{id} endpoint
- Council artifacts API (POST/GET /api/v1/councils/{id}/artifacts)
- /admin/health/details endpoint with component status
- Database lookup for repository URLs (no hardcoded fallbacks)
## Observability & Performance
- Added OpenTelemetry distributed tracing with goal/pulse correlation
- Performance optimization database indexes
- Comprehensive health monitoring
- Enhanced logging and error handling
## Infrastructure
- Production-ready P2P discovery (replaces mock implementation)
- Removed unused Redis configuration
- Enhanced Docker Swarm integration
- Added migration files for performance indexes
## Code Quality
- Comprehensive input validation
- Graceful error handling and failsafe fallbacks
- Backwards compatibility maintained
- Following security best practices
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
		
	
		
			
				
	
	
		
			246 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			246 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2020-2023 The NATS Authors
 | |
| // Licensed under the Apache License, Version 2.0 (the "License");
 | |
| // you may not use this file except in compliance with the License.
 | |
| // You may obtain a copy of the License at
 | |
| //
 | |
| // http://www.apache.org/licenses/LICENSE-2.0
 | |
| //
 | |
| // Unless required by applicable law or agreed to in writing, software
 | |
| // distributed under the License is distributed on an "AS IS" BASIS,
 | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| // See the License for the specific language governing permissions and
 | |
| // limitations under the License.
 | |
| 
 | |
| package nats
 | |
| 
 | |
| import (
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	// API errors
 | |
| 
 | |
| 	// ErrJetStreamNotEnabled is an error returned when JetStream is not enabled for an account.
 | |
| 	//
 | |
| 	// Note: This error will not be returned in clustered mode, even if each
 | |
| 	// server in the cluster does not have JetStream enabled. In clustered mode,
 | |
| 	// requests will time out instead.
 | |
| 	ErrJetStreamNotEnabled JetStreamError = &jsError{apiErr: &APIError{ErrorCode: JSErrCodeJetStreamNotEnabled, Description: "jetstream not enabled", Code: 503}}
 | |
| 
 | |
| 	// ErrJetStreamNotEnabledForAccount is an error returned when JetStream is not enabled for an account.
 | |
| 	ErrJetStreamNotEnabledForAccount JetStreamError = &jsError{apiErr: &APIError{ErrorCode: JSErrCodeJetStreamNotEnabledForAccount, Description: "jetstream not enabled for account", Code: 503}}
 | |
| 
 | |
| 	// ErrStreamNotFound is an error returned when stream with given name does not exist.
 | |
| 	ErrStreamNotFound JetStreamError = &jsError{apiErr: &APIError{ErrorCode: JSErrCodeStreamNotFound, Description: "stream not found", Code: 404}}
 | |
| 
 | |
| 	// ErrStreamNameAlreadyInUse is returned when a stream with given name already exists and has a different configuration.
 | |
| 	ErrStreamNameAlreadyInUse JetStreamError = &jsError{apiErr: &APIError{ErrorCode: JSErrCodeStreamNameInUse, Description: "stream name already in use", Code: 400}}
 | |
| 
 | |
| 	// ErrStreamSubjectTransformNotSupported is returned when the connected nats-server version does not support setting
 | |
| 	// the stream subject transform. If this error is returned when executing AddStream(), the stream with invalid
 | |
| 	// configuration was already created in the server.
 | |
| 	ErrStreamSubjectTransformNotSupported JetStreamError = &jsError{message: "stream subject transformation not supported by nats-server"}
 | |
| 
 | |
| 	// ErrStreamSourceSubjectTransformNotSupported is returned when the connected nats-server version does not support setting
 | |
| 	// the stream source subject transform. If this error is returned when executing AddStream(), the stream with invalid
 | |
| 	// configuration was already created in the server.
 | |
| 	ErrStreamSourceSubjectTransformNotSupported JetStreamError = &jsError{message: "stream subject transformation not supported by nats-server"}
 | |
| 
 | |
| 	// ErrStreamSourceNotSupported is returned when the connected nats-server version does not support setting
 | |
| 	// the stream sources. If this error is returned when executing AddStream(), the stream with invalid
 | |
| 	// configuration was already created in the server.
 | |
| 	ErrStreamSourceNotSupported JetStreamError = &jsError{message: "stream sourcing is not supported by nats-server"}
 | |
| 
 | |
| 	// ErrStreamSourceMultipleSubjectTransformsNotSupported is returned when the connected nats-server version does not support setting
 | |
| 	// the stream sources. If this error is returned when executing AddStream(), the stream with invalid
 | |
| 	// configuration was already created in the server.
 | |
| 	ErrStreamSourceMultipleSubjectTransformsNotSupported JetStreamError = &jsError{message: "stream sourcing with multiple subject transforms not supported by nats-server"}
 | |
| 
 | |
| 	// ErrConsumerNotFound is an error returned when consumer with given name does not exist.
 | |
| 	ErrConsumerNotFound JetStreamError = &jsError{apiErr: &APIError{ErrorCode: JSErrCodeConsumerNotFound, Description: "consumer not found", Code: 404}}
 | |
| 
 | |
| 	// ErrMsgNotFound is returned when message with provided sequence number does npt exist.
 | |
| 	ErrMsgNotFound JetStreamError = &jsError{apiErr: &APIError{ErrorCode: JSErrCodeMessageNotFound, Description: "message not found", Code: 404}}
 | |
| 
 | |
| 	// ErrBadRequest is returned when invalid request is sent to JetStream API.
 | |
| 	ErrBadRequest JetStreamError = &jsError{apiErr: &APIError{ErrorCode: JSErrCodeBadRequest, Description: "bad request", Code: 400}}
 | |
| 
 | |
| 	// ErrDuplicateFilterSubjects is returned when both FilterSubject and FilterSubjects are specified when creating consumer.
 | |
| 	ErrDuplicateFilterSubjects JetStreamError = &jsError{apiErr: &APIError{ErrorCode: JSErrCodeDuplicateFilterSubjects, Description: "consumer cannot have both FilterSubject and FilterSubjects specified", Code: 500}}
 | |
| 
 | |
| 	// ErrDuplicateFilterSubjects is returned when filter subjects overlap when creating consumer.
 | |
| 	ErrOverlappingFilterSubjects JetStreamError = &jsError{apiErr: &APIError{ErrorCode: JSErrCodeOverlappingFilterSubjects, Description: "consumer subject filters cannot overlap", Code: 500}}
 | |
| 
 | |
| 	// ErrEmptyFilter is returned when a filter in FilterSubjects is empty.
 | |
| 	ErrEmptyFilter JetStreamError = &jsError{apiErr: &APIError{ErrorCode: JSErrCodeConsumerEmptyFilter, Description: "consumer filter in FilterSubjects cannot be empty", Code: 500}}
 | |
| 
 | |
| 	// Client errors
 | |
| 
 | |
| 	// ErrConsumerNameAlreadyInUse is an error returned when consumer with given name already exists.
 | |
| 	ErrConsumerNameAlreadyInUse JetStreamError = &jsError{message: "consumer name already in use"}
 | |
| 
 | |
| 	// ErrConsumerNotActive is an error returned when consumer is not active.
 | |
| 	ErrConsumerNotActive JetStreamError = &jsError{message: "consumer not active"}
 | |
| 
 | |
| 	// ErrInvalidJSAck is returned when JetStream ack from message publish is invalid.
 | |
| 	ErrInvalidJSAck JetStreamError = &jsError{message: "invalid jetstream publish response"}
 | |
| 
 | |
| 	// ErrStreamConfigRequired is returned when empty stream configuration is supplied to add/update stream.
 | |
| 	ErrStreamConfigRequired JetStreamError = &jsError{message: "stream configuration is required"}
 | |
| 
 | |
| 	// ErrStreamNameRequired is returned when the provided stream name is empty.
 | |
| 	ErrStreamNameRequired JetStreamError = &jsError{message: "stream name is required"}
 | |
| 
 | |
| 	// ErrConsumerNameRequired is returned when the provided consumer durable name is empty.
 | |
| 	ErrConsumerNameRequired JetStreamError = &jsError{message: "consumer name is required"}
 | |
| 
 | |
| 	// ErrConsumerMultipleFilterSubjectsNotSupported is returned when the connected nats-server version does not support setting
 | |
| 	// multiple filter subjects with filter_subjects field. If this error is returned when executing AddConsumer(), the consumer with invalid
 | |
| 	// configuration was already created in the server.
 | |
| 	ErrConsumerMultipleFilterSubjectsNotSupported JetStreamError = &jsError{message: "multiple consumer filter subjects not supported by nats-server"}
 | |
| 
 | |
| 	// ErrConsumerConfigRequired is returned when empty consumer consuguration is supplied to add/update consumer.
 | |
| 	ErrConsumerConfigRequired JetStreamError = &jsError{message: "consumer configuration is required"}
 | |
| 
 | |
| 	// ErrPullSubscribeToPushConsumer is returned when attempting to use PullSubscribe on push consumer.
 | |
| 	ErrPullSubscribeToPushConsumer JetStreamError = &jsError{message: "cannot pull subscribe to push based consumer"}
 | |
| 
 | |
| 	// ErrPullSubscribeRequired is returned when attempting to use subscribe methods not suitable for pull consumers for pull consumers.
 | |
| 	ErrPullSubscribeRequired JetStreamError = &jsError{message: "must use pull subscribe to bind to pull based consumer"}
 | |
| 
 | |
| 	// ErrMsgAlreadyAckd is returned when attempting to acknowledge message more than once.
 | |
| 	ErrMsgAlreadyAckd JetStreamError = &jsError{message: "message was already acknowledged"}
 | |
| 
 | |
| 	// ErrNoStreamResponse is returned when there is no response from stream (e.g. no responders error).
 | |
| 	ErrNoStreamResponse JetStreamError = &jsError{message: "no response from stream"}
 | |
| 
 | |
| 	// ErrNotJSMessage is returned when attempting to get metadata from non JetStream message .
 | |
| 	ErrNotJSMessage JetStreamError = &jsError{message: "not a jetstream message"}
 | |
| 
 | |
| 	// ErrInvalidStreamName is returned when the provided stream name is invalid (contains '.' or ' ').
 | |
| 	ErrInvalidStreamName JetStreamError = &jsError{message: "invalid stream name"}
 | |
| 
 | |
| 	// ErrInvalidConsumerName is returned when the provided consumer name is invalid (contains '.' or ' ').
 | |
| 	ErrInvalidConsumerName JetStreamError = &jsError{message: "invalid consumer name"}
 | |
| 
 | |
| 	// ErrInvalidFilterSubject is returned when the provided filter subject is invalid.
 | |
| 	ErrInvalidFilterSubject JetStreamError = &jsError{message: "invalid filter subject"}
 | |
| 
 | |
| 	// ErrNoMatchingStream is returned when stream lookup by subject is unsuccessful.
 | |
| 	ErrNoMatchingStream JetStreamError = &jsError{message: "no stream matches subject"}
 | |
| 
 | |
| 	// ErrSubjectMismatch is returned when the provided subject does not match consumer's filter subject.
 | |
| 	ErrSubjectMismatch JetStreamError = &jsError{message: "subject does not match consumer"}
 | |
| 
 | |
| 	// ErrContextAndTimeout is returned when attempting to use both context and timeout.
 | |
| 	ErrContextAndTimeout JetStreamError = &jsError{message: "context and timeout can not both be set"}
 | |
| 
 | |
| 	// ErrCantAckIfConsumerAckNone is returned when attempting to ack a message for consumer with AckNone policy set.
 | |
| 	ErrCantAckIfConsumerAckNone JetStreamError = &jsError{message: "cannot acknowledge a message for a consumer with AckNone policy"}
 | |
| 
 | |
| 	// ErrConsumerDeleted is returned when attempting to send pull request to a consumer which does not exist
 | |
| 	ErrConsumerDeleted JetStreamError = &jsError{message: "consumer deleted"}
 | |
| 
 | |
| 	// ErrConsumerLeadershipChanged is returned when pending requests are no longer valid after leadership has changed
 | |
| 	ErrConsumerLeadershipChanged JetStreamError = &jsError{message: "Leadership Changed"}
 | |
| 
 | |
| 	// ErrNoHeartbeat is returned when no heartbeat is received from server when sending requests with pull consumer.
 | |
| 	ErrNoHeartbeat JetStreamError = &jsError{message: "no heartbeat received"}
 | |
| 
 | |
| 	// ErrSubscriptionClosed is returned when attempting to send pull request to a closed subscription
 | |
| 	ErrSubscriptionClosed JetStreamError = &jsError{message: "subscription closed"}
 | |
| 
 | |
| 	// DEPRECATED: ErrInvalidDurableName is no longer returned and will be removed in future releases.
 | |
| 	// Use ErrInvalidConsumerName instead.
 | |
| 	ErrInvalidDurableName = errors.New("nats: invalid durable name")
 | |
| )
 | |
| 
 | |
| // Error code represents JetStream error codes returned by the API
 | |
| type ErrorCode uint16
 | |
| 
 | |
| const (
 | |
| 	JSErrCodeJetStreamNotEnabledForAccount ErrorCode = 10039
 | |
| 	JSErrCodeJetStreamNotEnabled           ErrorCode = 10076
 | |
| 	JSErrCodeInsufficientResourcesErr      ErrorCode = 10023
 | |
| 
 | |
| 	JSErrCodeStreamNotFound  ErrorCode = 10059
 | |
| 	JSErrCodeStreamNameInUse ErrorCode = 10058
 | |
| 
 | |
| 	JSErrCodeConsumerNotFound          ErrorCode = 10014
 | |
| 	JSErrCodeConsumerNameExists        ErrorCode = 10013
 | |
| 	JSErrCodeConsumerAlreadyExists     ErrorCode = 10105
 | |
| 	JSErrCodeDuplicateFilterSubjects   ErrorCode = 10136
 | |
| 	JSErrCodeOverlappingFilterSubjects ErrorCode = 10138
 | |
| 	JSErrCodeConsumerEmptyFilter       ErrorCode = 10139
 | |
| 
 | |
| 	JSErrCodeMessageNotFound ErrorCode = 10037
 | |
| 
 | |
| 	JSErrCodeBadRequest   ErrorCode = 10003
 | |
| 	JSStreamInvalidConfig ErrorCode = 10052
 | |
| 
 | |
| 	JSErrCodeStreamWrongLastSequence ErrorCode = 10071
 | |
| )
 | |
| 
 | |
| // APIError is included in all API responses if there was an error.
 | |
| type APIError struct {
 | |
| 	Code        int       `json:"code"`
 | |
| 	ErrorCode   ErrorCode `json:"err_code"`
 | |
| 	Description string    `json:"description,omitempty"`
 | |
| }
 | |
| 
 | |
| // Error prints the JetStream API error code and description
 | |
| func (e *APIError) Error() string {
 | |
| 	return fmt.Sprintf("nats: %s", e.Description)
 | |
| }
 | |
| 
 | |
| // APIError implements the JetStreamError interface.
 | |
| func (e *APIError) APIError() *APIError {
 | |
| 	return e
 | |
| }
 | |
| 
 | |
| // Is matches against an APIError.
 | |
| func (e *APIError) Is(err error) bool {
 | |
| 	if e == nil {
 | |
| 		return false
 | |
| 	}
 | |
| 	// Extract internal APIError to match against.
 | |
| 	var aerr *APIError
 | |
| 	ok := errors.As(err, &aerr)
 | |
| 	if !ok {
 | |
| 		return ok
 | |
| 	}
 | |
| 	return e.ErrorCode == aerr.ErrorCode
 | |
| }
 | |
| 
 | |
| // JetStreamError is an error result that happens when using JetStream.
 | |
| // In case of client-side error, `APIError()` returns nil
 | |
| type JetStreamError interface {
 | |
| 	APIError() *APIError
 | |
| 	error
 | |
| }
 | |
| 
 | |
| type jsError struct {
 | |
| 	apiErr  *APIError
 | |
| 	message string
 | |
| }
 | |
| 
 | |
| func (err *jsError) APIError() *APIError {
 | |
| 	return err.apiErr
 | |
| }
 | |
| 
 | |
| func (err *jsError) Error() string {
 | |
| 	if err.apiErr != nil && err.apiErr.Description != "" {
 | |
| 		return err.apiErr.Error()
 | |
| 	}
 | |
| 	return fmt.Sprintf("nats: %s", err.message)
 | |
| }
 | |
| 
 | |
| func (err *jsError) Unwrap() error {
 | |
| 	// Allow matching to embedded APIError in case there is one.
 | |
| 	if err.apiErr == nil {
 | |
| 		return nil
 | |
| 	}
 | |
| 	return err.apiErr
 | |
| }
 |