Add API token auth and issues doc

This commit is contained in:
2025-10-25 22:19:45 -07:00
parent 8cf369cc38
commit a3365c303a
18 changed files with 5572 additions and 22 deletions
+63 -6
View File
@@ -15,12 +15,14 @@ import (
grpcapi "github.com/igodwin/notifier/api/grpc"
pb "github.com/igodwin/notifier/api/grpc/pb"
"github.com/igodwin/notifier/api/rest"
"github.com/igodwin/notifier/internal/auth"
"github.com/igodwin/notifier/internal/config"
"github.com/igodwin/notifier/internal/domain"
"github.com/igodwin/notifier/internal/logging"
"github.com/igodwin/notifier/internal/notifier"
"github.com/igodwin/notifier/internal/queue"
"github.com/igodwin/notifier/internal/service"
"github.com/gorilla/mux"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
)
@@ -105,6 +107,18 @@ func main() {
}
logger.Infof("Started %d worker(s)", cfg.Queue.WorkerCount)
// Initialize authentication if enabled
var authStore *auth.APIKeyStore
var authz *auth.NotifierAuthz
if cfg.Auth.Enabled {
authStore = auth.NewAPIKeyStore()
authz = auth.NewNotifierAuthz()
logger.Info("API authentication enabled")
// Register authorization rules for notifiers
registerAuthorizationRules(cfg, authz, logger)
}
// Wait group for both servers
var wg sync.WaitGroup
@@ -112,14 +126,14 @@ func main() {
var grpcServer *grpc.Server
if cfg.Server.Mode == "both" || cfg.Server.Mode == "grpc" {
wg.Add(1)
grpcServer = startGRPCServer(ctx, &wg, cfg, svc, logger)
grpcServer = startGRPCServer(ctx, &wg, cfg, svc, logger, authStore)
}
// Start REST server if enabled
var restServer *http.Server
if cfg.Server.Mode == "both" || cfg.Server.Mode == "rest" {
wg.Add(1)
restServer = startRESTServer(ctx, &wg, cfg, svc, logger)
restServer = startRESTServer(ctx, &wg, cfg, svc, logger, authStore)
}
// Wait for interrupt signal
@@ -217,7 +231,7 @@ func registerNotifiers(cfg *config.Config, factory *notifier.Factory, logger *lo
}
}
func startGRPCServer(ctx context.Context, wg *sync.WaitGroup, cfg *config.Config, svc domain.NotificationService, logger *logging.Logger) *grpc.Server {
func startGRPCServer(ctx context.Context, wg *sync.WaitGroup, cfg *config.Config, svc domain.NotificationService, logger *logging.Logger, authStore *auth.APIKeyStore) *grpc.Server {
addr := fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.GRPCPort)
lis, err := net.Listen("tcp", addr)
@@ -225,7 +239,19 @@ func startGRPCServer(ctx context.Context, wg *sync.WaitGroup, cfg *config.Config
logger.Fatalf("Failed to listen on %s: %v", addr, err)
}
grpcServer := grpc.NewServer()
// Create gRPC server options
var serverOpts []grpc.ServerOption
// Add authentication interceptors if enabled
if authStore != nil {
authMiddleware := auth.NewGRPCAuthMiddleware(authStore, logger)
serverOpts = append(serverOpts,
grpc.UnaryInterceptor(authMiddleware.UnaryInterceptor()),
grpc.StreamInterceptor(authMiddleware.StreamInterceptor()),
)
}
grpcServer := grpc.NewServer(serverOpts...)
// Create and register gRPC handler
grpcHandler := grpcapi.NewNotifierHandler(svc, logger)
@@ -247,8 +273,13 @@ func startGRPCServer(ctx context.Context, wg *sync.WaitGroup, cfg *config.Config
return grpcServer
}
func startRESTServer(ctx context.Context, wg *sync.WaitGroup, cfg *config.Config, svc domain.NotificationService, logger *logging.Logger) *http.Server {
router := rest.NewRouter(svc, logger)
func startRESTServer(ctx context.Context, wg *sync.WaitGroup, cfg *config.Config, svc domain.NotificationService, logger *logging.Logger, authStore *auth.APIKeyStore) *http.Server {
var router *mux.Router
if authStore != nil {
router = rest.NewRouterWithAuth(svc, logger, authStore)
} else {
router = rest.NewRouter(svc, logger)
}
addr := fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.RESTPort)
server := &http.Server{
@@ -270,6 +301,32 @@ func startRESTServer(ctx context.Context, wg *sync.WaitGroup, cfg *config.Config
return server
}
func registerAuthorizationRules(cfg *config.Config, authz *auth.NotifierAuthz, logger *logging.Logger) {
// Register SMTP authorization rules
for accountName, smtpConfig := range cfg.Notifiers.SMTP {
if len(smtpConfig.AllowedRoles) > 0 {
authz.RegisterRule(domain.TypeEmail, accountName, smtpConfig.AllowedRoles)
logger.Infof("Registered auth rule for SMTP account '%s' - allowed roles: %v", accountName, smtpConfig.AllowedRoles)
}
}
// Register Slack authorization rules
for accountName, slackConfig := range cfg.Notifiers.Slack {
if len(slackConfig.AllowedRoles) > 0 {
authz.RegisterRule(domain.TypeSlack, accountName, slackConfig.AllowedRoles)
logger.Infof("Registered auth rule for Slack account '%s' - allowed roles: %v", accountName, slackConfig.AllowedRoles)
}
}
// Register Ntfy authorization rules
for accountName, ntfyConfig := range cfg.Notifiers.Ntfy {
if len(ntfyConfig.AllowedRoles) > 0 {
authz.RegisterRule(domain.TypeNtfy, accountName, ntfyConfig.AllowedRoles)
logger.Infof("Registered auth rule for Ntfy account '%s' - allowed roles: %v", accountName, ntfyConfig.AllowedRoles)
}
}
}
func getDefaultConfig() *config.Config {
return &config.Config{
Server: config.ServerConfig{