InfraNotes Module · v0
Welcome
Select a document from the sidebar to read it.
NATS Authentication Integration for InfraNotes Main Application
Deprecated – historical NATS design
InfraNotes has fully migrated away from NATS and now uses Kafka at the platform level, with HTTP APIs secured via shared JWT tokens between InfraNotes and this module. This document is kept only as a historical reference for the previous NATS-based design.
This document provides the historical implementation details for adding NATS support to the InfraNotes main application to enable authentication integration with the InfraNotes-Module.
Implementation Steps
1. Add Dependencies
Add the NATS client library to the InfraNotes main application:
# Run in the InfraNotes directory
go get github.com/nats-io/nats.go
2. Create NATS Authentication Service
Create a new file at internal/services/nats_auth_service.go:
package services
import (
"encoding/json"
"fmt"
"log"
"strconv"
"time"
"github.com/StackCatalyst/Infra_Notes/internal/models"
"github.com/nats-io/nats.go"
)
// TokenValidationRequest represents a request to validate a token
type TokenValidationRequest struct {
Token string `json:"token"`
RequestID string `json:"request_id"`
}
// TokenValidationResponse represents the response to a token validation
type TokenValidationResponse struct {
Valid bool `json:"valid"`
UserID string `json:"user_id"`
UserRole string `json:"user_role"`
Permissions []string `json:"permissions"`
SessionID string `json:"session_id"`
RequestID string `json:"request_id"`
Error string `json:"error,omitempty"`
}
// TokenRevokedEvent represents a token revocation event
type TokenRevokedEvent struct {
Token string `json:"token"`
UserID string `json:"user_id"`
RevokedAt time.Time `json:"revoked_at"`
Reason string `json:"reason,omitempty"`
SessionID string `json:"session_id,omitempty"`
RevokedByApp string `json:"revoked_by_app"`
}
// NatsAuthService handles authentication via NATS
type NatsAuthService struct {
nc *nats.Conn
authService *AuthService
userRepo models.UserRepository
connected bool
}
// NewNatsAuthService creates a new NATS authentication service
func NewNatsAuthService(
natsURL string,
authService *AuthService,
userRepo models.UserRepository,
) (*NatsAuthService, error) {
// Connect to NATS
nc, err := nats.Connect(natsURL,
nats.Name("infranotes-main-auth"),
nats.ReconnectWait(5*time.Second),
nats.MaxReconnects(10))
if err != nil {
return nil, fmt.Errorf("failed to connect to NATS: %w", err)
}
service := &NatsAuthService{
nc: nc,
authService: authService,
userRepo: userRepo,
connected: true,
}
// Initialize subscribers
if err := service.initSubscribers(); err != nil {
nc.Close()
return nil, err
}
return service, nil
}
// initSubscribers sets up the NATS subscribers for auth requests
func (s *NatsAuthService) initSubscribers() error {
// Subscribe to token validation requests
_, err := s.nc.Subscribe("auth.validate.token", s.handleTokenValidation)
if err != nil {
return fmt.Errorf("failed to subscribe to token validation: %w", err)
}
// Subscribe to user permission validation requests
_, err = s.nc.QueueSubscribe("auth.validate.user.*", "infranotes-auth", s.handleUserValidation)
if err != nil {
return fmt.Errorf("failed to subscribe to user validation: %w", err)
}
log.Println("NATS Auth Service: Subscribed to authentication topics")
return nil
}
// handleTokenValidation processes token validation requests
func (s *NatsAuthService) handleTokenValidation(msg *nats.Msg) {
var req TokenValidationRequest
if err := json.Unmarshal(msg.Data, &req); err != nil {
s.sendErrorResponse(msg, "Invalid request format", req.RequestID)
return
}
// Validate the token
userID, role, sessionID, err := s.authService.VerifyToken(req.Token)
// Prepare the response
resp := TokenValidationResponse{
Valid: err == nil,
RequestID: req.RequestID,
}
if err == nil {
// Token is valid, include user info
resp.UserID = fmt.Sprintf("%d", userID)
resp.UserRole = role
resp.SessionID = sessionID
// Get permissions if needed
if user, err := s.userRepo.GetByID(userID); err == nil {
permissions, _ := s.getPermissionsForUser(user)
resp.Permissions = permissions
}
} else {
// Include error message
resp.Error = err.Error()
}
// Send the response
respData, _ := json.Marshal(resp)
msg.Respond(respData)
}
// handleUserValidation processes user permission validation requests
func (s *NatsAuthService) handleUserValidation(msg *nats.Msg) {
// TODO: Implement user permission validation logic
// Extract user ID from the subject
// Verify if the user has permission for the requested resource/action
}
// getPermissionsForUser returns the permissions for a user
func (s *NatsAuthService) getPermissionsForUser(user *models.User) ([]string, error) {
// TODO: Get actual permissions based on user role and other attributes
// This is a placeholder implementation
permissions := []string{}
// Add basic permissions based on role
if user.IsAdmin() {
permissions = append(permissions,
"read:analytics",
"write:analytics",
"read:documents",
"write:documents",
"admin:system")
} else {
permissions = append(permissions,
"read:analytics",
"read:documents")
// Add write permissions for standard users
permissions = append(permissions,
"write:documents")
}
return permissions, nil
}
// PublishLogout publishes a logout event
func (s *NatsAuthService) PublishLogout(userID int64, sessionID, reason string) error {
event := TokenRevokedEvent{
UserID: strconv.FormatInt(userID, 10),
SessionID: sessionID,
RevokedAt: time.Now().UTC(),
Reason: reason,
RevokedByApp: "infranotes-main",
}
data, err := json.Marshal(event)
if err != nil {
return err
}
return s.nc.Publish("auth.event.logout", data)
}
// PublishTokenRevoked publishes a token revocation event
func (s *NatsAuthService) PublishTokenRevoked(token string, userID int64, sessionID, reason string) error {
event := TokenRevokedEvent{
Token: token,
UserID: strconv.FormatInt(userID, 10),
SessionID: sessionID,
RevokedAt: time.Now().UTC(),
Reason: reason,
RevokedByApp: "infranotes-main",
}
data, err := json.Marshal(event)
if err != nil {
return err
}
return s.nc.Publish("auth.event.token.revoked", data)
}
// sendErrorResponse sends an error response to a NATS request
func (s *NatsAuthService) sendErrorResponse(msg *nats.Msg, errMsg string, requestID string) {
resp := TokenValidationResponse{
Valid: false,
Error: errMsg,
RequestID: requestID,
}
respData, _ := json.Marshal(resp)
msg.Respond(respData)
}
// Close closes the NATS connection
func (s *NatsAuthService) Close() {
if s.nc != nil {
s.nc.Close()
}
}
3. Update Auth Service to Notify on Logout/Token Invalidation
Modify internal/services/auth_service.go to publish events when tokens are invalidated:
// Logout logs out a user and invalidates their session
func (s *AuthService) Logout(userID int64, sessionID string) error {
// Existing logout logic...
// If NATS service is available, publish a logout event
if natsAuthService != nil {
natsAuthService.PublishLogout(userID, sessionID, "user logout")
}
return nil
}
// InvalidateToken adds a token to the blacklist
func (s *AuthService) InvalidateToken(token string, userID int64, sessionID string, reason string) error {
// Existing token invalidation logic...
// If NATS service is available, publish a token revocation event
if natsAuthService != nil {
natsAuthService.PublishTokenRevoked(token, userID, sessionID, reason)
}
return nil
}
4. Update main.go to Initialize NATS Service
Add the NATS service initialization to the main application:
// Initialize NATS auth service if configured
natsURL := config.GetString("nats.url")
if natsURL != "" {
natsAuthService, err := services.NewNatsAuthService(
natsURL,
authService,
userRepo,
)
if err != nil {
log.Printf("Warning: Failed to initialize NATS authentication service: %v", err)
// Continue without NATS - will use local auth only
} else {
defer natsAuthService.Close()
log.Printf("NATS authentication service initialized at %s", natsURL)
}
}
5. Update Docker Compose
Update the Docker Compose configuration to include NATS:
services:
# ... existing services ...
nats:
image: nats:2.10-alpine
container_name: infranotes-nats
restart: unless-stopped
ports:
- "4222:4222" # Client connections
- "8222:8222" # HTTP monitoring
volumes:
- nats_data:/nats/data
command: >
--jetstream
--http_port=8222
--store_dir=/nats/data
healthcheck:
test: ["CMD", "nats-server", "check", "localhost:8222"]
interval: 5s
timeout: 5s
retries: 5
api:
# ... existing api service configuration ...
environment:
# ... existing environment variables ...
NATS_URL: nats://nats:4222
depends_on:
# ... existing dependencies ...
nats:
condition: service_healthy
volumes:
# ... existing volumes ...
nats_data:
6. Update Configuration
Add NATS configuration to configs/config.go:
// Define default configuration
var defaultConfig = map[string]interface{}{
// ... existing defaults ...
"nats": map[string]interface{}{
"url": "nats://localhost:4222",
"clientName": "infranotes-main",
"reconnectWait": 5, // seconds
"maxReconnects": 10,
},
}
Testing the Integration
1. Unit Tests
Create unit tests for the NATS authentication service:
package services_test
import (
"testing"
"time"
"github.com/StackCatalyst/Infra_Notes/internal/services"
"github.com/nats-io/nats-server/v2/test"
)
func TestNatsAuthService(t *testing.T) {
// Start a test NATS server
opts := test.DefaultTestOptions
opts.Port = 8368
server := test.RunServer(&opts)
defer server.Shutdown()
// TODO: Implement tests for token validation and event publishing
}
2. Integration Testing
Test the full integration between InfraNotes and InfraNotes-Module using NATS:
- Start both applications with NATS enabled
- Generate a valid auth token in InfraNotes
- Use the token to access InfraNotes-Module
- Verify that the token is properly validated
- Invalidate the token and verify that it's no longer accepted
Security Considerations
- For production deployments, always enable TLS for NATS connections
- Use NATS authentication (username/password or certificates)
- Implement proper error handling and timeouts for NATS operations
- Monitor NATS connections and authentication requests
- Implement rate limiting for authentication requests
Performance Considerations
- Consider caching validation results to reduce NATS traffic
- Implement circuit breakers to handle NATS connection failures gracefully
- Use connection pooling if necessary
- Monitor NATS message latency and throughput