Skip to content

Commit 5643380

Browse files
appleboyclaude
andauthored
refactor(audit): add NoopAuditService to eliminate nil checks (#137)
* refactor(audit): add NoopAuditService to eliminate nil checks - Add AuditLogger interface and AuditLogEntry type in core package - Add NoopAuditService with safe no-op methods for disabled audit - Remove ~37 nil checks across 12 service, handler, and middleware files - Remove enabled field and internal guards from AuditService - Simplify NewAuditService constructor to 2 params (drop enabled) - Bootstrap conditionally creates real vs noop audit service - Update all test files to use NewNoopAuditService instead of nil Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor(audit): apply NoopAuditService to new code and improve shutdown guard - Remove remaining auditService nil checks in admin user operations - Update DashboardService to accept core.AuditLogger interface - Skip audit shutdown job registration when audit logging is disabled - Document concurrency safety contract on AuditLogger interface * fix(audit): guard against nil AuditLogger in service constructors - Default auditService to NoopAuditService when nil in NewUserService, NewTokenService, NewClientService, and NewAuthorizationService - Return error from NewRateLimiter when AuditService is nil in config - Use safe type assertions for user_id/username in rate limit handler * refactor(audit): use singleton NoopAuditService and add missing nil guards - Return shared singleton from NewNoopAuditService to avoid repeated allocation of stateless struct across 120+ test call sites - Add nil guard to NewDeviceService and NewDashboardService to match the pattern applied to the other six service constructors * fix(audit): return meaningful pagination from NoopAuditService and clarify required field - Use CalculatePagination(0, params) in GetAuditLogs so callers receive correct CurrentPage/PageSize metadata even when auditing is disabled - Update noop test to assert against CalculatePagination result - Mark RateLimitConfig.AuditService comment as Required to prevent nil * fix(test): pass NoopAuditService in bootstrap rate limit test - TestSetupRateLimitingMemory passed nil audit service which now errors; use NewNoopAuditService() consistent with all other test call sites - Trim GetAuditLogs comment for consistency with other noop methods --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent afe42f0 commit 5643380

46 files changed

Lines changed: 990 additions & 818 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

internal/bootstrap/bootstrap.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ type Application struct {
3838
RateLimitRedisClient *redis.Client
3939

4040
// Services
41-
AuditService *services.AuditService
41+
AuditService core.AuditLogger
4242
services serviceSet
4343

4444
// HTTP
@@ -134,11 +134,14 @@ func (app *Application) initializeInfrastructure(ctx context.Context) error {
134134
// initializeBusinessLayer sets up services
135135
func (app *Application) initializeBusinessLayer() {
136136
// Audit service (required by other services)
137-
app.AuditService = services.NewAuditService(
138-
app.DB,
139-
app.Config.EnableAuditLogging,
140-
app.Config.AuditLogBufferSize,
141-
)
137+
if app.Config.EnableAuditLogging {
138+
app.AuditService = services.NewAuditService(
139+
app.DB,
140+
app.Config.AuditLogBufferSize,
141+
)
142+
} else {
143+
app.AuditService = services.NewNoopAuditService()
144+
}
142145

143146
// Initialize token provider (stored for JWKS handler)
144147
app.TokenProvider = initializeTokenProvider(app.Config)

internal/bootstrap/bootstrap_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
"github.com/gin-gonic/gin"
1010
"github.com/go-authgate/authgate/internal/config"
11+
"github.com/go-authgate/authgate/internal/services"
1112
"github.com/stretchr/testify/assert"
1213
"github.com/stretchr/testify/require"
1314
)
@@ -139,7 +140,7 @@ func TestSetupRateLimitingMemory(t *testing.T) {
139140
TokenRateLimit: 20,
140141
DeviceVerifyRateLimit: 10,
141142
}
142-
limiters := setupRateLimiting(cfg, nil, nil)
143+
limiters := setupRateLimiting(cfg, services.NewNoopAuditService(), nil)
143144
require.NotNil(t, limiters.login)
144145
require.NotNil(t, limiters.deviceCode)
145146
require.NotNil(t, limiters.token)

internal/bootstrap/handlers.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ type handlerSet struct {
3737
type handlerDeps struct {
3838
cfg *config.Config
3939
services serviceSet
40-
auditService *services.AuditService
40+
auditService core.AuditLogger
4141
oauthProviders map[string]*auth.OAuthProvider
4242
oauthClient *http.Client
4343
metrics core.Recorder

internal/bootstrap/ratelimit.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import (
55

66
"github.com/gin-gonic/gin"
77
"github.com/go-authgate/authgate/internal/config"
8+
"github.com/go-authgate/authgate/internal/core"
89
"github.com/go-authgate/authgate/internal/middleware"
9-
"github.com/go-authgate/authgate/internal/services"
1010
"github.com/redis/go-redis/v9"
1111
)
1212

@@ -24,7 +24,7 @@ type rateLimitMiddlewares struct {
2424
// Accepts an optional go-redis client
2525
func setupRateLimiting(
2626
cfg *config.Config,
27-
auditService *services.AuditService,
27+
auditService core.AuditLogger,
2828
redisClient *redis.Client,
2929
) rateLimitMiddlewares {
3030
// Return no-op middlewares when rate limiting is disabled
@@ -50,7 +50,7 @@ func setupRateLimiting(
5050
// Accepts an optional go-redis client
5151
func createRateLimiters(
5252
cfg *config.Config,
53-
auditService *services.AuditService,
53+
auditService core.AuditLogger,
5454
redisClient *redis.Client,
5555
) rateLimitMiddlewares {
5656
log.Printf("Rate limiting enabled (store: %s)", cfg.RateLimitStore)

internal/bootstrap/router.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import (
1515
"github.com/go-authgate/authgate/internal/handlers"
1616
"github.com/go-authgate/authgate/internal/metrics"
1717
"github.com/go-authgate/authgate/internal/middleware"
18-
"github.com/go-authgate/authgate/internal/services"
1918
"github.com/go-authgate/authgate/internal/store"
2019

2120
"github.com/gin-contrib/sessions"
@@ -33,7 +32,7 @@ func setupRouter(
3332
db *store.Store,
3433
h handlerSet,
3534
prometheusMetrics core.Recorder,
36-
auditService *services.AuditService,
35+
auditService core.AuditLogger,
3736
rateLimitRedisClient *redis.Client,
3837
templatesFS embed.FS,
3938
oauthProviders map[string]*auth.OAuthProvider,

internal/bootstrap/server.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"github.com/go-authgate/authgate/internal/core"
1111
"github.com/go-authgate/authgate/internal/metrics"
1212
"github.com/go-authgate/authgate/internal/models"
13-
"github.com/go-authgate/authgate/internal/services"
1413
"github.com/go-authgate/authgate/internal/store"
1514

1615
"github.com/appleboy/graceful"
@@ -98,9 +97,12 @@ func addRedisClientShutdownJob(m *graceful.Manager, redisClient *redis.Client, c
9897
// addAuditServiceShutdownJob adds audit service shutdown handler
9998
func addAuditServiceShutdownJob(
10099
m *graceful.Manager,
101-
auditService *services.AuditService,
100+
auditService core.AuditLogger,
102101
cfg *config.Config,
103102
) {
103+
if !cfg.EnableAuditLogging {
104+
return
105+
}
104106
m.AddShutdownJob(func() error {
105107
log.Println("Shutting down audit service...")
106108

@@ -120,7 +122,7 @@ func addAuditServiceShutdownJob(
120122
func addAuditLogCleanupJob(
121123
m *graceful.Manager,
122124
cfg *config.Config,
123-
auditService *services.AuditService,
125+
auditService core.AuditLogger,
124126
) {
125127
if !cfg.EnableAuditLogging || cfg.AuditLogRetention <= 0 {
126128
return

internal/bootstrap/services.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ type serviceSet struct {
2323
func initializeServices(
2424
cfg *config.Config,
2525
db *store.Store,
26-
auditService *services.AuditService,
26+
auditService core.AuditLogger,
2727
prometheusMetrics core.Recorder,
2828
userCache core.Cache[models.User],
2929
clientCountCache core.Cache[int64],

internal/core/audit.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package core
2+
3+
import (
4+
"context"
5+
"time"
6+
7+
"github.com/go-authgate/authgate/internal/models"
8+
"github.com/go-authgate/authgate/internal/store/types"
9+
)
10+
11+
// AuditLogEntry represents the data needed to create an audit log entry.
12+
type AuditLogEntry struct {
13+
EventType models.EventType
14+
Severity models.EventSeverity
15+
ActorUserID string
16+
ActorUsername string
17+
ActorIP string
18+
ResourceType models.ResourceType
19+
ResourceID string
20+
ResourceName string
21+
Action string
22+
Details models.AuditDetails
23+
Success bool
24+
ErrorMessage string
25+
UserAgent string
26+
RequestPath string
27+
RequestMethod string
28+
}
29+
30+
// AuditLogger defines the contract for audit logging operations.
31+
// Implementations must be safe for concurrent use by multiple goroutines.
32+
// Implementations include the real AuditService (buffered, database-backed)
33+
// and NoopAuditService (silent no-op for when auditing is disabled).
34+
type AuditLogger interface {
35+
Log(ctx context.Context, entry AuditLogEntry)
36+
LogSync(ctx context.Context, entry AuditLogEntry) error
37+
GetAuditLogs(
38+
params types.PaginationParams,
39+
filters types.AuditLogFilters,
40+
) ([]models.AuditLog, types.PaginationResult, error)
41+
CleanupOldLogs(retention time.Duration) (int64, error)
42+
GetAuditLogStats(startTime, endTime time.Time) (types.AuditLogStats, error)
43+
Shutdown(ctx context.Context) error
44+
}

internal/handlers/audit.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,20 @@ import (
88
"time"
99

1010
"github.com/gin-gonic/gin"
11+
"github.com/go-authgate/authgate/internal/core"
1112
"github.com/go-authgate/authgate/internal/middleware"
1213
"github.com/go-authgate/authgate/internal/models"
13-
"github.com/go-authgate/authgate/internal/services"
1414
"github.com/go-authgate/authgate/internal/store"
1515
"github.com/go-authgate/authgate/internal/templates"
1616
)
1717

1818
// AuditHandler handles audit log operations
1919
type AuditHandler struct {
20-
auditService *services.AuditService
20+
auditService core.AuditLogger
2121
}
2222

2323
// NewAuditHandler creates a new audit handler
24-
func NewAuditHandler(auditService *services.AuditService) *AuditHandler {
24+
func NewAuditHandler(auditService core.AuditLogger) *AuditHandler {
2525
return &AuditHandler{
2626
auditService: auditService,
2727
}
@@ -121,7 +121,7 @@ func (h *AuditHandler) ListAuditLogs(c *gin.Context) {
121121
// Log this action (viewing audit logs)
122122
if userID, exists := c.Get("user_id"); exists {
123123
if username, usernameExists := c.Get("username"); usernameExists {
124-
h.auditService.Log(c.Request.Context(), services.AuditLogEntry{
124+
h.auditService.Log(c.Request.Context(), core.AuditLogEntry{
125125
EventType: models.EventTypeAuditLogView,
126126
Severity: models.SeverityInfo,
127127
ActorUserID: userID.(string),
@@ -281,7 +281,7 @@ func (h *AuditHandler) ExportAuditLogs(c *gin.Context) {
281281
// Log this action
282282
if userID, exists := c.Get("user_id"); exists {
283283
if username, usernameExists := c.Get("username"); usernameExists {
284-
h.auditService.Log(c.Request.Context(), services.AuditLogEntry{
284+
h.auditService.Log(c.Request.Context(), core.AuditLogEntry{
285285
EventType: models.EventTypeAuditLogExported,
286286
Severity: models.SeverityInfo,
287287
ActorUserID: userID.(string),

internal/handlers/device_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ func setupDeviceTestEnv(t *testing.T) (*gin.Engine, *store.Store) {
3636
s, err := store.New(context.Background(), "sqlite", ":memory:", &config.Config{})
3737
require.NoError(t, err)
3838

39-
auditSvc := services.NewAuditService(s, false, 0)
39+
auditSvc := services.NewNoopAuditService()
4040
deviceSvc := services.NewDeviceService(s, cfg, auditSvc, metrics.NewNoopMetrics())
4141
userSvc := services.NewUserService(s, nil, nil, "local", false, auditSvc, nil, 0)
4242
authzSvc := services.NewAuthorizationService(s, cfg, auditSvc, nil)

0 commit comments

Comments
 (0)