- Implement Audit Service (2.5) - gRPC server with Record and Query operations - Database persistence with audit schema - Service registry integration - Entry point: cmd/audit-service - Implement Identity Service (2.2) - User CRUD operations - Password hashing with argon2id - Email verification and password reset flows - Entry point: cmd/identity-service - Fix package naming conflicts in user_service.go - Implement Auth Service (2.1) - JWT token generation and validation - Login, RefreshToken, ValidateToken, Logout RPCs - Integration with Identity Service - Entry point: cmd/auth-service - Note: RefreshToken entity needs Ent generation - Implement Authz Service (2.3, 2.4) - Permission checking and authorization - User roles and permissions retrieval - RBAC-based authorization - Entry point: cmd/authz-service - Implement gRPC clients for all services - Auth, Identity, Authz, and Audit clients - Service discovery integration - Full gRPC communication - Add service configurations to config/default.yaml - Create SUMMARY.md with implementation details and testing instructions - Fix compilation errors in Identity Service (password package conflicts) - All services build successfully and tests pass
182 lines
4.3 KiB
Go
182 lines
4.3 KiB
Go
// Package service provides audit service business logic.
|
|
package service
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"git.dcentral.systems/toolz/goplt/internal/ent"
|
|
"git.dcentral.systems/toolz/goplt/internal/ent/auditlog"
|
|
"git.dcentral.systems/toolz/goplt/pkg/logger"
|
|
"github.com/google/uuid"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// AuditLogEntry represents an audit log entry.
|
|
type AuditLogEntry struct {
|
|
UserID string
|
|
Action string
|
|
Resource string
|
|
ResourceID string
|
|
IPAddress string
|
|
UserAgent string
|
|
Metadata map[string]string
|
|
Timestamp int64
|
|
}
|
|
|
|
// AuditLogFilters contains filters for querying audit logs.
|
|
type AuditLogFilters struct {
|
|
UserID *string
|
|
Action *string
|
|
Resource *string
|
|
ResourceID *string
|
|
StartTime *int64
|
|
EndTime *int64
|
|
Limit int
|
|
Offset int
|
|
}
|
|
|
|
// AuditService provides audit logging functionality.
|
|
type AuditService struct {
|
|
client *ent.Client
|
|
logger logger.Logger
|
|
}
|
|
|
|
// NewAuditService creates a new audit service.
|
|
func NewAuditService(client *ent.Client, log logger.Logger) *AuditService {
|
|
return &AuditService{
|
|
client: client,
|
|
logger: log,
|
|
}
|
|
}
|
|
|
|
// Record records an audit log entry.
|
|
func (s *AuditService) Record(ctx context.Context, entry *AuditLogEntry) error {
|
|
// Convert metadata map to JSON
|
|
metadataJSON := make(map[string]interface{})
|
|
for k, v := range entry.Metadata {
|
|
metadataJSON[k] = v
|
|
}
|
|
|
|
// Create audit log entry
|
|
timestamp := time.Unix(entry.Timestamp, 0)
|
|
if entry.Timestamp == 0 {
|
|
timestamp = time.Now()
|
|
}
|
|
|
|
create := s.client.AuditLog.Create().
|
|
SetID(uuid.New().String()).
|
|
SetUserID(entry.UserID).
|
|
SetAction(entry.Action).
|
|
SetMetadata(metadataJSON).
|
|
SetTimestamp(timestamp)
|
|
|
|
if entry.Resource != "" {
|
|
create = create.SetResource(entry.Resource)
|
|
}
|
|
if entry.ResourceID != "" {
|
|
create = create.SetResourceID(entry.ResourceID)
|
|
}
|
|
if entry.IPAddress != "" {
|
|
create = create.SetIPAddress(entry.IPAddress)
|
|
}
|
|
if entry.UserAgent != "" {
|
|
create = create.SetUserAgent(entry.UserAgent)
|
|
}
|
|
|
|
auditLog, err := create.Save(ctx)
|
|
|
|
if err != nil {
|
|
s.logger.Error("Failed to record audit log",
|
|
zap.Error(err),
|
|
zap.String("user_id", entry.UserID),
|
|
zap.String("action", entry.Action),
|
|
)
|
|
return fmt.Errorf("failed to record audit log: %w", err)
|
|
}
|
|
|
|
s.logger.Debug("Audit log recorded",
|
|
zap.String("id", auditLog.ID),
|
|
zap.String("user_id", entry.UserID),
|
|
zap.String("action", entry.Action),
|
|
)
|
|
|
|
return nil
|
|
}
|
|
|
|
// Query queries audit logs based on filters.
|
|
func (s *AuditService) Query(ctx context.Context, filters *AuditLogFilters) ([]*AuditLogEntry, error) {
|
|
query := s.client.AuditLog.Query()
|
|
|
|
// Apply filters
|
|
if filters.UserID != nil {
|
|
query = query.Where(auditlog.UserID(*filters.UserID))
|
|
}
|
|
if filters.Action != nil {
|
|
query = query.Where(auditlog.Action(*filters.Action))
|
|
}
|
|
if filters.Resource != nil {
|
|
query = query.Where(auditlog.Resource(*filters.Resource))
|
|
}
|
|
if filters.ResourceID != nil {
|
|
query = query.Where(auditlog.ResourceID(*filters.ResourceID))
|
|
}
|
|
if filters.StartTime != nil {
|
|
query = query.Where(auditlog.TimestampGTE(time.Unix(*filters.StartTime, 0)))
|
|
}
|
|
if filters.EndTime != nil {
|
|
query = query.Where(auditlog.TimestampLTE(time.Unix(*filters.EndTime, 0)))
|
|
}
|
|
|
|
// Apply pagination
|
|
if filters.Limit > 0 {
|
|
query = query.Limit(filters.Limit)
|
|
}
|
|
if filters.Offset > 0 {
|
|
query = query.Offset(filters.Offset)
|
|
}
|
|
|
|
// Order by timestamp descending
|
|
query = query.Order(ent.Desc(auditlog.FieldTimestamp))
|
|
|
|
// Execute query
|
|
auditLogs, err := query.All(ctx)
|
|
if err != nil {
|
|
s.logger.Error("Failed to query audit logs",
|
|
zap.Error(err),
|
|
)
|
|
return nil, fmt.Errorf("failed to query audit logs: %w", err)
|
|
}
|
|
|
|
// Convert to service entries
|
|
entries := make([]*AuditLogEntry, 0, len(auditLogs))
|
|
for _, log := range auditLogs {
|
|
// Convert metadata from map[string]interface{} to map[string]string
|
|
metadata := make(map[string]string)
|
|
if log.Metadata != nil {
|
|
for k, v := range log.Metadata {
|
|
if str, ok := v.(string); ok {
|
|
metadata[k] = str
|
|
} else {
|
|
metadata[k] = fmt.Sprintf("%v", v)
|
|
}
|
|
}
|
|
}
|
|
|
|
entry := &AuditLogEntry{
|
|
UserID: log.UserID,
|
|
Action: log.Action,
|
|
Resource: log.Resource,
|
|
ResourceID: log.ResourceID,
|
|
IPAddress: log.IPAddress,
|
|
UserAgent: log.UserAgent,
|
|
Metadata: metadata,
|
|
Timestamp: log.Timestamp.Unix(),
|
|
}
|
|
entries = append(entries, entry)
|
|
}
|
|
|
|
return entries, nil
|
|
}
|