feat: authsession service
This commit is contained in:
@@ -0,0 +1,610 @@
|
||||
// Package config loads the authsession process configuration from environment
|
||||
// variables.
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"galaxy/authsession/internal/api/internalhttp"
|
||||
"galaxy/authsession/internal/api/publichttp"
|
||||
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
const (
|
||||
shutdownTimeoutEnvVar = "AUTHSESSION_SHUTDOWN_TIMEOUT"
|
||||
logLevelEnvVar = "AUTHSESSION_LOG_LEVEL"
|
||||
|
||||
publicHTTPAddrEnvVar = "AUTHSESSION_PUBLIC_HTTP_ADDR"
|
||||
publicHTTPReadHeaderTimeoutEnvVar = "AUTHSESSION_PUBLIC_HTTP_READ_HEADER_TIMEOUT"
|
||||
publicHTTPReadTimeoutEnvVar = "AUTHSESSION_PUBLIC_HTTP_READ_TIMEOUT"
|
||||
publicHTTPIdleTimeoutEnvVar = "AUTHSESSION_PUBLIC_HTTP_IDLE_TIMEOUT"
|
||||
publicHTTPRequestTimeoutEnvVar = "AUTHSESSION_PUBLIC_HTTP_REQUEST_TIMEOUT"
|
||||
|
||||
internalHTTPAddrEnvVar = "AUTHSESSION_INTERNAL_HTTP_ADDR"
|
||||
internalHTTPReadHeaderTimeoutEnvVar = "AUTHSESSION_INTERNAL_HTTP_READ_HEADER_TIMEOUT"
|
||||
internalHTTPReadTimeoutEnvVar = "AUTHSESSION_INTERNAL_HTTP_READ_TIMEOUT"
|
||||
internalHTTPIdleTimeoutEnvVar = "AUTHSESSION_INTERNAL_HTTP_IDLE_TIMEOUT"
|
||||
internalHTTPRequestTimeoutEnvVar = "AUTHSESSION_INTERNAL_HTTP_REQUEST_TIMEOUT"
|
||||
|
||||
redisAddrEnvVar = "AUTHSESSION_REDIS_ADDR"
|
||||
redisUsernameEnvVar = "AUTHSESSION_REDIS_USERNAME"
|
||||
redisPasswordEnvVar = "AUTHSESSION_REDIS_PASSWORD"
|
||||
redisDBEnvVar = "AUTHSESSION_REDIS_DB"
|
||||
redisTLSEnabledEnvVar = "AUTHSESSION_REDIS_TLS_ENABLED"
|
||||
redisOperationTimeoutEnvVar = "AUTHSESSION_REDIS_OPERATION_TIMEOUT"
|
||||
|
||||
redisChallengeKeyPrefixEnvVar = "AUTHSESSION_REDIS_CHALLENGE_KEY_PREFIX"
|
||||
redisSessionKeyPrefixEnvVar = "AUTHSESSION_REDIS_SESSION_KEY_PREFIX"
|
||||
redisUserSessionsKeyPrefixEnvVar = "AUTHSESSION_REDIS_USER_SESSIONS_KEY_PREFIX"
|
||||
redisUserActiveSessionsKeyPrefixEnvVar = "AUTHSESSION_REDIS_USER_ACTIVE_SESSIONS_KEY_PREFIX"
|
||||
redisSessionLimitKeyEnvVar = "AUTHSESSION_REDIS_SESSION_LIMIT_KEY"
|
||||
redisGatewaySessionCacheKeyPrefixEnvVar = "AUTHSESSION_REDIS_GATEWAY_SESSION_CACHE_KEY_PREFIX"
|
||||
redisGatewaySessionEventsStreamEnvVar = "AUTHSESSION_REDIS_GATEWAY_SESSION_EVENTS_STREAM"
|
||||
redisGatewaySessionEventsStreamMaxLenEnvVar = "AUTHSESSION_REDIS_GATEWAY_SESSION_EVENTS_STREAM_MAX_LEN"
|
||||
redisSendEmailCodeThrottleKeyPrefixEnvVar = "AUTHSESSION_REDIS_SEND_EMAIL_CODE_THROTTLE_KEY_PREFIX"
|
||||
|
||||
userServiceModeEnvVar = "AUTHSESSION_USER_SERVICE_MODE"
|
||||
userServiceBaseURLEnvVar = "AUTHSESSION_USER_SERVICE_BASE_URL"
|
||||
userServiceRequestTimeoutEnvVar = "AUTHSESSION_USER_SERVICE_REQUEST_TIMEOUT"
|
||||
|
||||
mailServiceModeEnvVar = "AUTHSESSION_MAIL_SERVICE_MODE"
|
||||
mailServiceBaseURLEnvVar = "AUTHSESSION_MAIL_SERVICE_BASE_URL"
|
||||
mailServiceRequestTimeoutEnvVar = "AUTHSESSION_MAIL_SERVICE_REQUEST_TIMEOUT"
|
||||
|
||||
otelServiceNameEnvVar = "OTEL_SERVICE_NAME"
|
||||
otelTracesExporterEnvVar = "OTEL_TRACES_EXPORTER"
|
||||
otelMetricsExporterEnvVar = "OTEL_METRICS_EXPORTER"
|
||||
otelExporterOTLPProtocolEnvVar = "OTEL_EXPORTER_OTLP_PROTOCOL"
|
||||
otelExporterOTLPTracesProtocolEnvVar = "OTEL_EXPORTER_OTLP_TRACES_PROTOCOL"
|
||||
otelExporterOTLPMetricsProtocolEnvVar = "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL"
|
||||
otelStdoutTracesEnabledEnvVar = "AUTHSESSION_OTEL_STDOUT_TRACES_ENABLED"
|
||||
otelStdoutMetricsEnabledEnvVar = "AUTHSESSION_OTEL_STDOUT_METRICS_ENABLED"
|
||||
|
||||
defaultShutdownTimeout = 5 * time.Second
|
||||
defaultLogLevel = "info"
|
||||
defaultRedisDB = 0
|
||||
defaultRedisOperationTimeout = 250 * time.Millisecond
|
||||
defaultChallengeKeyPrefix = "authsession:challenge:"
|
||||
defaultSessionKeyPrefix = "authsession:session:"
|
||||
defaultUserSessionsKeyPrefix = "authsession:user-sessions:"
|
||||
defaultUserActiveSessionsKeyPrefix = "authsession:user-active-sessions:"
|
||||
defaultSessionLimitKey = "authsession:config:active-session-limit"
|
||||
defaultGatewaySessionCacheKeyPrefix = "gateway:session:"
|
||||
defaultGatewaySessionEventsStream = "gateway:session_events"
|
||||
defaultGatewaySessionEventsStreamMaxLen = 1024
|
||||
defaultSendEmailCodeThrottleKeyPrefix = "authsession:send-email-code-throttle:"
|
||||
defaultUserServiceMode = userServiceModeStub
|
||||
defaultUserServiceRequestTimeout = time.Second
|
||||
defaultMailServiceMode = mailServiceModeStub
|
||||
defaultMailServiceRequestTimeout = time.Second
|
||||
defaultOTelServiceName = "galaxy-authsession"
|
||||
otelExporterNone = "none"
|
||||
otelExporterOTLP = "otlp"
|
||||
otelProtocolHTTPProtobuf = "http/protobuf"
|
||||
otelProtocolGRPC = "grpc"
|
||||
userServiceModeStub = "stub"
|
||||
userServiceModeREST = "rest"
|
||||
mailServiceModeStub = "stub"
|
||||
mailServiceModeREST = "rest"
|
||||
)
|
||||
|
||||
// Config stores the full process-level authsession configuration.
|
||||
type Config struct {
|
||||
// ShutdownTimeout bounds graceful shutdown of every long-lived component.
|
||||
ShutdownTimeout time.Duration
|
||||
|
||||
// Logging configures the process-wide structured logger.
|
||||
Logging LoggingConfig
|
||||
|
||||
// PublicHTTP configures the public HTTP listener.
|
||||
PublicHTTP publichttp.Config
|
||||
|
||||
// InternalHTTP configures the trusted internal HTTP listener.
|
||||
InternalHTTP internalhttp.Config
|
||||
|
||||
// Redis configures the Redis-backed adapters.
|
||||
Redis RedisConfig
|
||||
|
||||
// UserService configures the selectable runtime user-directory adapter.
|
||||
UserService UserServiceConfig
|
||||
|
||||
// MailService configures the selectable runtime mail-delivery adapter.
|
||||
MailService MailServiceConfig
|
||||
|
||||
// Telemetry configures the process-wide OpenTelemetry runtime.
|
||||
Telemetry TelemetryConfig
|
||||
}
|
||||
|
||||
// LoggingConfig configures the process-wide structured logger.
|
||||
type LoggingConfig struct {
|
||||
// Level stores the zap-compatible log level string.
|
||||
Level string
|
||||
}
|
||||
|
||||
// RedisConfig configures the Redis-backed authsession adapters.
|
||||
type RedisConfig struct {
|
||||
// Addr is the shared Redis address used by the authsession adapters.
|
||||
Addr string
|
||||
|
||||
// Username is the optional Redis ACL username.
|
||||
Username string
|
||||
|
||||
// Password is the optional Redis ACL password.
|
||||
Password string
|
||||
|
||||
// DB is the Redis logical database index.
|
||||
DB int
|
||||
|
||||
// TLSEnabled configures whether Redis connections use TLS.
|
||||
TLSEnabled bool
|
||||
|
||||
// OperationTimeout bounds each adapter Redis round trip.
|
||||
OperationTimeout time.Duration
|
||||
|
||||
// ChallengeKeyPrefix namespaces the challenge source-of-truth records.
|
||||
ChallengeKeyPrefix string
|
||||
|
||||
// SessionKeyPrefix namespaces the primary session records.
|
||||
SessionKeyPrefix string
|
||||
|
||||
// UserSessionsKeyPrefix namespaces the all-session user index.
|
||||
UserSessionsKeyPrefix string
|
||||
|
||||
// UserActiveSessionsKeyPrefix namespaces the active-session user index.
|
||||
UserActiveSessionsKeyPrefix string
|
||||
|
||||
// SessionLimitKey stores the exact session-limit Redis key.
|
||||
SessionLimitKey string
|
||||
|
||||
// GatewaySessionCacheKeyPrefix namespaces the projected gateway session
|
||||
// cache keys.
|
||||
GatewaySessionCacheKeyPrefix string
|
||||
|
||||
// GatewaySessionEventsStream stores the projected gateway session-events
|
||||
// Redis Stream key.
|
||||
GatewaySessionEventsStream string
|
||||
|
||||
// GatewaySessionEventsStreamMaxLen bounds the projected gateway session
|
||||
// event stream with approximate trimming.
|
||||
GatewaySessionEventsStreamMaxLen int64
|
||||
|
||||
// SendEmailCodeThrottleKeyPrefix namespaces the resend-throttle TTL keys.
|
||||
SendEmailCodeThrottleKeyPrefix string
|
||||
}
|
||||
|
||||
// UserServiceConfig configures the runtime user-directory integration mode.
|
||||
type UserServiceConfig struct {
|
||||
// Mode selects the runtime adapter implementation. Supported values are
|
||||
// `stub` and `rest`.
|
||||
Mode string
|
||||
|
||||
// BaseURL is the absolute base URL of the REST-backed user-service when
|
||||
// Mode is `rest`.
|
||||
BaseURL string
|
||||
|
||||
// RequestTimeout bounds each outbound user-service request when Mode is
|
||||
// `rest`.
|
||||
RequestTimeout time.Duration
|
||||
}
|
||||
|
||||
// MailServiceConfig configures the runtime mail-delivery integration mode.
|
||||
type MailServiceConfig struct {
|
||||
// Mode selects the runtime adapter implementation. Supported values are
|
||||
// `stub` and `rest`.
|
||||
Mode string
|
||||
|
||||
// BaseURL is the absolute base URL of the REST-backed mail service when
|
||||
// Mode is `rest`.
|
||||
BaseURL string
|
||||
|
||||
// RequestTimeout bounds each outbound mail-service request when Mode is
|
||||
// `rest`.
|
||||
RequestTimeout time.Duration
|
||||
}
|
||||
|
||||
// TelemetryConfig configures the authsession OpenTelemetry runtime.
|
||||
type TelemetryConfig struct {
|
||||
// ServiceName overrides the default OpenTelemetry service name.
|
||||
ServiceName string
|
||||
|
||||
// TracesExporter selects the external traces exporter. Supported values are
|
||||
// `none` and `otlp`.
|
||||
TracesExporter string
|
||||
|
||||
// MetricsExporter selects the external metrics exporter. Supported values
|
||||
// are `none` and `otlp`.
|
||||
MetricsExporter string
|
||||
|
||||
// TracesProtocol selects the OTLP traces protocol when TracesExporter is
|
||||
// `otlp`.
|
||||
TracesProtocol string
|
||||
|
||||
// MetricsProtocol selects the OTLP metrics protocol when MetricsExporter is
|
||||
// `otlp`.
|
||||
MetricsProtocol string
|
||||
|
||||
// StdoutTracesEnabled enables the additional stdout trace exporter used for
|
||||
// local development and debugging.
|
||||
StdoutTracesEnabled bool
|
||||
|
||||
// StdoutMetricsEnabled enables the additional stdout metric exporter used
|
||||
// for local development and debugging.
|
||||
StdoutMetricsEnabled bool
|
||||
}
|
||||
|
||||
// DefaultConfig returns the default authsession process configuration with all
|
||||
// optional values filled.
|
||||
func DefaultConfig() Config {
|
||||
return Config{
|
||||
ShutdownTimeout: defaultShutdownTimeout,
|
||||
Logging: LoggingConfig{
|
||||
Level: defaultLogLevel,
|
||||
},
|
||||
PublicHTTP: publichttp.DefaultConfig(),
|
||||
InternalHTTP: internalhttp.DefaultConfig(),
|
||||
Redis: RedisConfig{
|
||||
DB: defaultRedisDB,
|
||||
OperationTimeout: defaultRedisOperationTimeout,
|
||||
ChallengeKeyPrefix: defaultChallengeKeyPrefix,
|
||||
SessionKeyPrefix: defaultSessionKeyPrefix,
|
||||
UserSessionsKeyPrefix: defaultUserSessionsKeyPrefix,
|
||||
UserActiveSessionsKeyPrefix: defaultUserActiveSessionsKeyPrefix,
|
||||
SessionLimitKey: defaultSessionLimitKey,
|
||||
GatewaySessionCacheKeyPrefix: defaultGatewaySessionCacheKeyPrefix,
|
||||
GatewaySessionEventsStream: defaultGatewaySessionEventsStream,
|
||||
GatewaySessionEventsStreamMaxLen: defaultGatewaySessionEventsStreamMaxLen,
|
||||
SendEmailCodeThrottleKeyPrefix: defaultSendEmailCodeThrottleKeyPrefix,
|
||||
},
|
||||
UserService: UserServiceConfig{
|
||||
Mode: defaultUserServiceMode,
|
||||
RequestTimeout: defaultUserServiceRequestTimeout,
|
||||
},
|
||||
MailService: MailServiceConfig{
|
||||
Mode: defaultMailServiceMode,
|
||||
RequestTimeout: defaultMailServiceRequestTimeout,
|
||||
},
|
||||
Telemetry: TelemetryConfig{
|
||||
ServiceName: defaultOTelServiceName,
|
||||
TracesExporter: otelExporterNone,
|
||||
MetricsExporter: otelExporterNone,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// LoadFromEnv loads the authsession process configuration from environment
|
||||
// variables, applying documented defaults where appropriate.
|
||||
func LoadFromEnv() (Config, error) {
|
||||
cfg := DefaultConfig()
|
||||
|
||||
var err error
|
||||
|
||||
cfg.ShutdownTimeout, err = loadDurationEnvWithDefault(shutdownTimeoutEnvVar, cfg.ShutdownTimeout)
|
||||
if err != nil {
|
||||
return Config{}, fmt.Errorf("load authsession config: %w", err)
|
||||
}
|
||||
|
||||
cfg.Logging.Level = loadStringEnvWithDefault(logLevelEnvVar, cfg.Logging.Level)
|
||||
if err := validateLogLevel(cfg.Logging.Level); err != nil {
|
||||
return Config{}, fmt.Errorf("load authsession config: %s: %w", logLevelEnvVar, err)
|
||||
}
|
||||
|
||||
cfg.PublicHTTP.Addr = loadStringEnvWithDefault(publicHTTPAddrEnvVar, cfg.PublicHTTP.Addr)
|
||||
cfg.PublicHTTP.ReadHeaderTimeout, err = loadDurationEnvWithDefault(publicHTTPReadHeaderTimeoutEnvVar, cfg.PublicHTTP.ReadHeaderTimeout)
|
||||
if err != nil {
|
||||
return Config{}, fmt.Errorf("load authsession config: %w", err)
|
||||
}
|
||||
cfg.PublicHTTP.ReadTimeout, err = loadDurationEnvWithDefault(publicHTTPReadTimeoutEnvVar, cfg.PublicHTTP.ReadTimeout)
|
||||
if err != nil {
|
||||
return Config{}, fmt.Errorf("load authsession config: %w", err)
|
||||
}
|
||||
cfg.PublicHTTP.IdleTimeout, err = loadDurationEnvWithDefault(publicHTTPIdleTimeoutEnvVar, cfg.PublicHTTP.IdleTimeout)
|
||||
if err != nil {
|
||||
return Config{}, fmt.Errorf("load authsession config: %w", err)
|
||||
}
|
||||
cfg.PublicHTTP.RequestTimeout, err = loadDurationEnvWithDefault(publicHTTPRequestTimeoutEnvVar, cfg.PublicHTTP.RequestTimeout)
|
||||
if err != nil {
|
||||
return Config{}, fmt.Errorf("load authsession config: %w", err)
|
||||
}
|
||||
|
||||
cfg.InternalHTTP.Addr = loadStringEnvWithDefault(internalHTTPAddrEnvVar, cfg.InternalHTTP.Addr)
|
||||
cfg.InternalHTTP.ReadHeaderTimeout, err = loadDurationEnvWithDefault(internalHTTPReadHeaderTimeoutEnvVar, cfg.InternalHTTP.ReadHeaderTimeout)
|
||||
if err != nil {
|
||||
return Config{}, fmt.Errorf("load authsession config: %w", err)
|
||||
}
|
||||
cfg.InternalHTTP.ReadTimeout, err = loadDurationEnvWithDefault(internalHTTPReadTimeoutEnvVar, cfg.InternalHTTP.ReadTimeout)
|
||||
if err != nil {
|
||||
return Config{}, fmt.Errorf("load authsession config: %w", err)
|
||||
}
|
||||
cfg.InternalHTTP.IdleTimeout, err = loadDurationEnvWithDefault(internalHTTPIdleTimeoutEnvVar, cfg.InternalHTTP.IdleTimeout)
|
||||
if err != nil {
|
||||
return Config{}, fmt.Errorf("load authsession config: %w", err)
|
||||
}
|
||||
cfg.InternalHTTP.RequestTimeout, err = loadDurationEnvWithDefault(internalHTTPRequestTimeoutEnvVar, cfg.InternalHTTP.RequestTimeout)
|
||||
if err != nil {
|
||||
return Config{}, fmt.Errorf("load authsession config: %w", err)
|
||||
}
|
||||
|
||||
cfg.Redis.Addr = loadStringEnvWithDefault(redisAddrEnvVar, cfg.Redis.Addr)
|
||||
cfg.Redis.Username = os.Getenv(redisUsernameEnvVar)
|
||||
cfg.Redis.Password = os.Getenv(redisPasswordEnvVar)
|
||||
cfg.Redis.DB, err = loadIntEnvWithDefault(redisDBEnvVar, cfg.Redis.DB)
|
||||
if err != nil {
|
||||
return Config{}, fmt.Errorf("load authsession config: %w", err)
|
||||
}
|
||||
cfg.Redis.TLSEnabled, err = loadBoolEnvWithDefault(redisTLSEnabledEnvVar, cfg.Redis.TLSEnabled)
|
||||
if err != nil {
|
||||
return Config{}, fmt.Errorf("load authsession config: %w", err)
|
||||
}
|
||||
cfg.Redis.OperationTimeout, err = loadDurationEnvWithDefault(redisOperationTimeoutEnvVar, cfg.Redis.OperationTimeout)
|
||||
if err != nil {
|
||||
return Config{}, fmt.Errorf("load authsession config: %w", err)
|
||||
}
|
||||
cfg.Redis.ChallengeKeyPrefix = loadStringEnvWithDefault(redisChallengeKeyPrefixEnvVar, cfg.Redis.ChallengeKeyPrefix)
|
||||
cfg.Redis.SessionKeyPrefix = loadStringEnvWithDefault(redisSessionKeyPrefixEnvVar, cfg.Redis.SessionKeyPrefix)
|
||||
cfg.Redis.UserSessionsKeyPrefix = loadStringEnvWithDefault(redisUserSessionsKeyPrefixEnvVar, cfg.Redis.UserSessionsKeyPrefix)
|
||||
cfg.Redis.UserActiveSessionsKeyPrefix = loadStringEnvWithDefault(redisUserActiveSessionsKeyPrefixEnvVar, cfg.Redis.UserActiveSessionsKeyPrefix)
|
||||
cfg.Redis.SessionLimitKey = loadStringEnvWithDefault(redisSessionLimitKeyEnvVar, cfg.Redis.SessionLimitKey)
|
||||
cfg.Redis.GatewaySessionCacheKeyPrefix = loadStringEnvWithDefault(redisGatewaySessionCacheKeyPrefixEnvVar, cfg.Redis.GatewaySessionCacheKeyPrefix)
|
||||
cfg.Redis.GatewaySessionEventsStream = loadStringEnvWithDefault(redisGatewaySessionEventsStreamEnvVar, cfg.Redis.GatewaySessionEventsStream)
|
||||
streamMaxLen, err := loadInt64EnvWithDefault(redisGatewaySessionEventsStreamMaxLenEnvVar, cfg.Redis.GatewaySessionEventsStreamMaxLen)
|
||||
if err != nil {
|
||||
return Config{}, fmt.Errorf("load authsession config: %w", err)
|
||||
}
|
||||
cfg.Redis.GatewaySessionEventsStreamMaxLen = streamMaxLen
|
||||
cfg.Redis.SendEmailCodeThrottleKeyPrefix = loadStringEnvWithDefault(redisSendEmailCodeThrottleKeyPrefixEnvVar, cfg.Redis.SendEmailCodeThrottleKeyPrefix)
|
||||
|
||||
cfg.UserService.Mode = strings.TrimSpace(loadStringEnvWithDefault(userServiceModeEnvVar, cfg.UserService.Mode))
|
||||
cfg.UserService.BaseURL = loadStringEnvWithDefault(userServiceBaseURLEnvVar, cfg.UserService.BaseURL)
|
||||
cfg.UserService.RequestTimeout, err = loadDurationEnvWithDefault(userServiceRequestTimeoutEnvVar, cfg.UserService.RequestTimeout)
|
||||
if err != nil {
|
||||
return Config{}, fmt.Errorf("load authsession config: %w", err)
|
||||
}
|
||||
|
||||
cfg.MailService.Mode = strings.TrimSpace(loadStringEnvWithDefault(mailServiceModeEnvVar, cfg.MailService.Mode))
|
||||
cfg.MailService.BaseURL = loadStringEnvWithDefault(mailServiceBaseURLEnvVar, cfg.MailService.BaseURL)
|
||||
cfg.MailService.RequestTimeout, err = loadDurationEnvWithDefault(mailServiceRequestTimeoutEnvVar, cfg.MailService.RequestTimeout)
|
||||
if err != nil {
|
||||
return Config{}, fmt.Errorf("load authsession config: %w", err)
|
||||
}
|
||||
|
||||
cfg.Telemetry.ServiceName = loadStringEnvWithDefault(otelServiceNameEnvVar, cfg.Telemetry.ServiceName)
|
||||
cfg.Telemetry.TracesExporter = normalizeExporterValue(loadStringEnvWithDefault(otelTracesExporterEnvVar, cfg.Telemetry.TracesExporter))
|
||||
cfg.Telemetry.MetricsExporter = normalizeExporterValue(loadStringEnvWithDefault(otelMetricsExporterEnvVar, cfg.Telemetry.MetricsExporter))
|
||||
cfg.Telemetry.TracesProtocol = loadOTLPProtocol(
|
||||
os.Getenv(otelExporterOTLPTracesProtocolEnvVar),
|
||||
os.Getenv(otelExporterOTLPProtocolEnvVar),
|
||||
cfg.Telemetry.TracesExporter,
|
||||
)
|
||||
cfg.Telemetry.MetricsProtocol = loadOTLPProtocol(
|
||||
os.Getenv(otelExporterOTLPMetricsProtocolEnvVar),
|
||||
os.Getenv(otelExporterOTLPProtocolEnvVar),
|
||||
cfg.Telemetry.MetricsExporter,
|
||||
)
|
||||
cfg.Telemetry.StdoutTracesEnabled, err = loadBoolEnvWithDefault(otelStdoutTracesEnabledEnvVar, cfg.Telemetry.StdoutTracesEnabled)
|
||||
if err != nil {
|
||||
return Config{}, fmt.Errorf("load authsession config: %w", err)
|
||||
}
|
||||
cfg.Telemetry.StdoutMetricsEnabled, err = loadBoolEnvWithDefault(otelStdoutMetricsEnabledEnvVar, cfg.Telemetry.StdoutMetricsEnabled)
|
||||
if err != nil {
|
||||
return Config{}, fmt.Errorf("load authsession config: %w", err)
|
||||
}
|
||||
|
||||
if err := cfg.Validate(); err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// Validate reports whether cfg contains a consistent authsession process
|
||||
// configuration.
|
||||
func (cfg Config) Validate() error {
|
||||
switch {
|
||||
case cfg.ShutdownTimeout <= 0:
|
||||
return fmt.Errorf("load authsession config: %s must be positive", shutdownTimeoutEnvVar)
|
||||
case strings.TrimSpace(cfg.Redis.Addr) == "":
|
||||
return fmt.Errorf("load authsession config: %s must not be empty", redisAddrEnvVar)
|
||||
case cfg.Redis.DB < 0:
|
||||
return fmt.Errorf("load authsession config: %s must not be negative", redisDBEnvVar)
|
||||
case cfg.Redis.OperationTimeout <= 0:
|
||||
return fmt.Errorf("load authsession config: %s must be positive", redisOperationTimeoutEnvVar)
|
||||
case strings.TrimSpace(cfg.Redis.ChallengeKeyPrefix) == "":
|
||||
return fmt.Errorf("load authsession config: %s must not be empty", redisChallengeKeyPrefixEnvVar)
|
||||
case strings.TrimSpace(cfg.Redis.SessionKeyPrefix) == "":
|
||||
return fmt.Errorf("load authsession config: %s must not be empty", redisSessionKeyPrefixEnvVar)
|
||||
case strings.TrimSpace(cfg.Redis.UserSessionsKeyPrefix) == "":
|
||||
return fmt.Errorf("load authsession config: %s must not be empty", redisUserSessionsKeyPrefixEnvVar)
|
||||
case strings.TrimSpace(cfg.Redis.UserActiveSessionsKeyPrefix) == "":
|
||||
return fmt.Errorf("load authsession config: %s must not be empty", redisUserActiveSessionsKeyPrefixEnvVar)
|
||||
case strings.TrimSpace(cfg.Redis.SessionLimitKey) == "":
|
||||
return fmt.Errorf("load authsession config: %s must not be empty", redisSessionLimitKeyEnvVar)
|
||||
case strings.TrimSpace(cfg.Redis.GatewaySessionCacheKeyPrefix) == "":
|
||||
return fmt.Errorf("load authsession config: %s must not be empty", redisGatewaySessionCacheKeyPrefixEnvVar)
|
||||
case strings.TrimSpace(cfg.Redis.GatewaySessionEventsStream) == "":
|
||||
return fmt.Errorf("load authsession config: %s must not be empty", redisGatewaySessionEventsStreamEnvVar)
|
||||
case cfg.Redis.GatewaySessionEventsStreamMaxLen <= 0:
|
||||
return fmt.Errorf("load authsession config: %s must be positive", redisGatewaySessionEventsStreamMaxLenEnvVar)
|
||||
case strings.TrimSpace(cfg.Redis.SendEmailCodeThrottleKeyPrefix) == "":
|
||||
return fmt.Errorf("load authsession config: %s must not be empty", redisSendEmailCodeThrottleKeyPrefixEnvVar)
|
||||
}
|
||||
|
||||
if err := cfg.PublicHTTP.Validate(); err != nil {
|
||||
return fmt.Errorf("load authsession config: public HTTP: %w", err)
|
||||
}
|
||||
if err := cfg.InternalHTTP.Validate(); err != nil {
|
||||
return fmt.Errorf("load authsession config: internal HTTP: %w", err)
|
||||
}
|
||||
if err := cfg.UserService.Validate(); err != nil {
|
||||
return fmt.Errorf("load authsession config: %w", err)
|
||||
}
|
||||
if err := cfg.MailService.Validate(); err != nil {
|
||||
return fmt.Errorf("load authsession config: %w", err)
|
||||
}
|
||||
if err := cfg.Telemetry.Validate(); err != nil {
|
||||
return fmt.Errorf("load authsession config: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate reports whether cfg contains a supported user-service runtime
|
||||
// configuration.
|
||||
func (cfg UserServiceConfig) Validate() error {
|
||||
switch cfg.Mode {
|
||||
case userServiceModeStub:
|
||||
return nil
|
||||
case userServiceModeREST:
|
||||
if strings.TrimSpace(cfg.BaseURL) == "" {
|
||||
return fmt.Errorf("%s must not be empty in rest mode", userServiceBaseURLEnvVar)
|
||||
}
|
||||
if cfg.RequestTimeout <= 0 {
|
||||
return fmt.Errorf("%s must be positive in rest mode", userServiceRequestTimeoutEnvVar)
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("%s %q is unsupported", userServiceModeEnvVar, cfg.Mode)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate reports whether cfg contains a supported mail-service runtime
|
||||
// configuration.
|
||||
func (cfg MailServiceConfig) Validate() error {
|
||||
switch cfg.Mode {
|
||||
case mailServiceModeStub:
|
||||
return nil
|
||||
case mailServiceModeREST:
|
||||
if strings.TrimSpace(cfg.BaseURL) == "" {
|
||||
return fmt.Errorf("%s must not be empty in rest mode", mailServiceBaseURLEnvVar)
|
||||
}
|
||||
if cfg.RequestTimeout <= 0 {
|
||||
return fmt.Errorf("%s must be positive in rest mode", mailServiceRequestTimeoutEnvVar)
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("%s %q is unsupported", mailServiceModeEnvVar, cfg.Mode)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate reports whether cfg contains a supported OpenTelemetry exporter
|
||||
// configuration.
|
||||
func (cfg TelemetryConfig) Validate() error {
|
||||
switch cfg.TracesExporter {
|
||||
case otelExporterNone, otelExporterOTLP:
|
||||
default:
|
||||
return fmt.Errorf("%s %q is unsupported", otelTracesExporterEnvVar, cfg.TracesExporter)
|
||||
}
|
||||
|
||||
switch cfg.MetricsExporter {
|
||||
case otelExporterNone, otelExporterOTLP:
|
||||
default:
|
||||
return fmt.Errorf("%s %q is unsupported", otelMetricsExporterEnvVar, cfg.MetricsExporter)
|
||||
}
|
||||
|
||||
if cfg.TracesProtocol != "" && cfg.TracesProtocol != otelProtocolHTTPProtobuf && cfg.TracesProtocol != otelProtocolGRPC {
|
||||
return fmt.Errorf("%s %q is unsupported", otelExporterOTLPTracesProtocolEnvVar, cfg.TracesProtocol)
|
||||
}
|
||||
if cfg.MetricsProtocol != "" && cfg.MetricsProtocol != otelProtocolHTTPProtobuf && cfg.MetricsProtocol != otelProtocolGRPC {
|
||||
return fmt.Errorf("%s %q is unsupported", otelExporterOTLPMetricsProtocolEnvVar, cfg.MetricsProtocol)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadStringEnvWithDefault(name string, value string) string {
|
||||
if raw, ok := os.LookupEnv(name); ok {
|
||||
return strings.TrimSpace(raw)
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
func loadDurationEnvWithDefault(name string, value time.Duration) (time.Duration, error) {
|
||||
raw, ok := os.LookupEnv(name)
|
||||
if !ok {
|
||||
return value, nil
|
||||
}
|
||||
|
||||
parsed, err := time.ParseDuration(strings.TrimSpace(raw))
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("%s: %w", name, err)
|
||||
}
|
||||
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
func loadIntEnvWithDefault(name string, value int) (int, error) {
|
||||
raw, ok := os.LookupEnv(name)
|
||||
if !ok {
|
||||
return value, nil
|
||||
}
|
||||
|
||||
parsed, err := strconv.Atoi(strings.TrimSpace(raw))
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("%s: %w", name, err)
|
||||
}
|
||||
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
func loadInt64EnvWithDefault(name string, value int64) (int64, error) {
|
||||
raw, ok := os.LookupEnv(name)
|
||||
if !ok {
|
||||
return value, nil
|
||||
}
|
||||
|
||||
parsed, err := strconv.ParseInt(strings.TrimSpace(raw), 10, 64)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("%s: %w", name, err)
|
||||
}
|
||||
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
func loadBoolEnvWithDefault(name string, value bool) (bool, error) {
|
||||
raw, ok := os.LookupEnv(name)
|
||||
if !ok {
|
||||
return value, nil
|
||||
}
|
||||
|
||||
parsed, err := strconv.ParseBool(strings.TrimSpace(raw))
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("%s: %w", name, err)
|
||||
}
|
||||
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
func validateLogLevel(value string) error {
|
||||
var level zapcore.Level
|
||||
if err := level.UnmarshalText([]byte(strings.TrimSpace(value))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizeExporterValue(value string) string {
|
||||
switch strings.TrimSpace(value) {
|
||||
case "", otelExporterNone:
|
||||
return otelExporterNone
|
||||
default:
|
||||
return strings.TrimSpace(value)
|
||||
}
|
||||
}
|
||||
|
||||
func loadOTLPProtocol(primary string, fallback string, exporter string) string {
|
||||
protocol := strings.TrimSpace(primary)
|
||||
if protocol == "" {
|
||||
protocol = strings.TrimSpace(fallback)
|
||||
}
|
||||
if protocol == "" && exporter == otelExporterOTLP {
|
||||
return otelProtocolHTTPProtobuf
|
||||
}
|
||||
|
||||
return protocol
|
||||
}
|
||||
Reference in New Issue
Block a user