init push
This commit is contained in:
638
internal/config/loader.go
Normal file
638
internal/config/loader.go
Normal file
@@ -0,0 +1,638 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Duration unmarshals a Go duration string (e.g. "30s", "5m", "150ms") from JSON.
|
||||
// An empty string is treated as zero duration.
|
||||
type Duration struct{ time.Duration }
|
||||
|
||||
func (d *Duration) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(b, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
if s == "" {
|
||||
d.Duration = 0
|
||||
return nil
|
||||
}
|
||||
dur, err := time.ParseDuration(s)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid duration %q: %w", s, err)
|
||||
}
|
||||
d.Duration = dur
|
||||
return nil
|
||||
}
|
||||
|
||||
// AppConfig is the unified config loaded from a JSON file.
|
||||
type AppConfig struct {
|
||||
HTTPPort int
|
||||
MySQL MySQLConfig
|
||||
Logging LoggingConfig
|
||||
Audit AuditConfig
|
||||
Auth AuthConfig
|
||||
SES SESConfig
|
||||
Email EmailDeliveryConfig
|
||||
MicrosoftSSO MicrosoftSSOConfig
|
||||
HTTP HTTPConfig
|
||||
Resilience ResilienceConfig
|
||||
Queue QueueConfig
|
||||
FileQueue FileQueueConfig
|
||||
SQS SQSConfig
|
||||
FileSQS SQSConfig
|
||||
FileStorage FileStorageConfig
|
||||
FileUpload FileManagerUploadPolicyConfig
|
||||
SAML SAMLConfig
|
||||
WOPI WOPIConfig
|
||||
Gotenberg GotenbergConfig
|
||||
}
|
||||
|
||||
// LoadFromFile reads path, parses it as JSON, and returns a populated AppConfig.
|
||||
// The file is typically produced by scripts/inject-secrets.sh.
|
||||
func LoadFromFile(path string) (AppConfig, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return AppConfig{}, fmt.Errorf("config: read %q: %w", path, err)
|
||||
}
|
||||
var raw jsonAppConfig
|
||||
if err := json.Unmarshal(data, &raw); err != nil {
|
||||
return AppConfig{}, fmt.Errorf("config: parse %q: %w", path, err)
|
||||
}
|
||||
return raw.toAppConfig(), nil
|
||||
}
|
||||
|
||||
// ── JSON intermediate types ───────────────────────────────────────────────────
|
||||
|
||||
type jsonCircuitBreakerPolicy struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
MaxRequests uint32 `json:"maxRequests"`
|
||||
Interval Duration `json:"interval"`
|
||||
BucketPeriod Duration `json:"bucketPeriod"`
|
||||
Timeout Duration `json:"timeout"`
|
||||
MinRequests uint32 `json:"minRequests"`
|
||||
FailureRatio float64 `json:"failureRatio"`
|
||||
ConsecutiveFailures uint32 `json:"consecutiveFailures"`
|
||||
}
|
||||
|
||||
func (j jsonCircuitBreakerPolicy) toPolicy() CircuitBreakerPolicy {
|
||||
return CircuitBreakerPolicy{
|
||||
Enabled: j.Enabled,
|
||||
MaxRequests: j.MaxRequests,
|
||||
Interval: j.Interval.Duration,
|
||||
BucketPeriod: j.BucketPeriod.Duration,
|
||||
Timeout: j.Timeout.Duration,
|
||||
MinRequests: j.MinRequests,
|
||||
FailureRatio: j.FailureRatio,
|
||||
ConsecutiveFailures: j.ConsecutiveFailures,
|
||||
}
|
||||
}
|
||||
|
||||
type jsonWorker struct {
|
||||
Concurrency int `json:"concurrency"`
|
||||
MaxBatchSize int `json:"maxBatchSize"`
|
||||
ProcessTimeout Duration `json:"processTimeout"`
|
||||
ShutdownTimeout Duration `json:"shutdownTimeout"`
|
||||
BackoffMin Duration `json:"backoffMin"`
|
||||
BackoffMax Duration `json:"backoffMax"`
|
||||
PermanentFailureDelay Duration `json:"permanentFailureDelay"`
|
||||
MonitorAddr string `json:"monitorAddr"`
|
||||
HealthErrorThreshold int `json:"healthErrorThreshold"`
|
||||
DepthPollInterval Duration `json:"depthPollInterval"`
|
||||
}
|
||||
|
||||
type jsonOutbox struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
PollInterval Duration `json:"pollInterval"`
|
||||
BatchSize int `json:"batchSize"`
|
||||
DispatchTimeout Duration `json:"dispatchTimeout"`
|
||||
LockTTL Duration `json:"lockTTL"`
|
||||
MaxAttempts int `json:"maxAttempts"`
|
||||
}
|
||||
|
||||
type jsonSQS struct {
|
||||
Region string `json:"region"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
AllowInsecureEndpoint bool `json:"allowInsecureEndpoint"`
|
||||
QueueURL string `json:"queueUrl"`
|
||||
QueueType string `json:"queueType"`
|
||||
MessageGroupID string `json:"messageGroupId"`
|
||||
MaxMessages int `json:"maxMessages"`
|
||||
WaitTimeSeconds int32 `json:"waitTimeSeconds"`
|
||||
VisibilityTimeout Duration `json:"visibilityTimeout"`
|
||||
}
|
||||
|
||||
func (j jsonSQS) toSQSConfig(regionFallback, endpointFallback, accessKeyID, secretAccessKey string) SQSConfig {
|
||||
region := j.Region
|
||||
if region == "" {
|
||||
region = regionFallback
|
||||
}
|
||||
endpoint := j.Endpoint
|
||||
if endpoint == "" {
|
||||
endpoint = endpointFallback
|
||||
}
|
||||
return SQSConfig{
|
||||
Region: region,
|
||||
AccessKeyID: strings.TrimSpace(accessKeyID),
|
||||
SecretAccessKey: strings.TrimSpace(secretAccessKey),
|
||||
Endpoint: endpoint,
|
||||
QueueURL: j.QueueURL,
|
||||
QueueType: j.QueueType,
|
||||
MessageGroupID: j.MessageGroupID,
|
||||
MaxMessages: j.MaxMessages,
|
||||
WaitTimeSeconds: j.WaitTimeSeconds,
|
||||
VisibilityTimeout: j.VisibilityTimeout.Duration,
|
||||
AllowInsecureEndpoint: j.AllowInsecureEndpoint,
|
||||
}
|
||||
}
|
||||
|
||||
type jsonAppConfig struct {
|
||||
HTTP struct {
|
||||
Port int `json:"port"`
|
||||
BodyLimit int `json:"bodyLimit"`
|
||||
Timeouts struct {
|
||||
Read Duration `json:"read"`
|
||||
Write Duration `json:"write"`
|
||||
Idle Duration `json:"idle"`
|
||||
Request Duration `json:"request"`
|
||||
RequestOverrides string `json:"requestOverrides"`
|
||||
Shutdown Duration `json:"shutdown"`
|
||||
} `json:"timeouts"`
|
||||
CORS struct {
|
||||
AllowOrigins string `json:"allowOrigins"`
|
||||
AllowMethods string `json:"allowMethods"`
|
||||
AllowHeaders string `json:"allowHeaders"`
|
||||
ExposeHeaders string `json:"exposeHeaders"`
|
||||
AllowCredentials bool `json:"allowCredentials"`
|
||||
MaxAge int `json:"maxAge"`
|
||||
} `json:"cors"`
|
||||
RateLimit struct {
|
||||
Max int `json:"max"`
|
||||
Window Duration `json:"window"`
|
||||
Endpoints string `json:"endpoints"`
|
||||
} `json:"rateLimit"`
|
||||
Security struct {
|
||||
HSTSMaxAge int `json:"hstsMaxAge"`
|
||||
} `json:"security"`
|
||||
} `json:"http"`
|
||||
|
||||
Database struct {
|
||||
MySQL struct {
|
||||
DSN string `json:"dsn"`
|
||||
MaxOpenConns int `json:"maxOpenConns"`
|
||||
MaxIdleConns int `json:"maxIdleConns"`
|
||||
ConnMaxLifetime Duration `json:"connMaxLifetime"`
|
||||
ConnMaxIdleTime Duration `json:"connMaxIdleTime"`
|
||||
} `json:"mysql"`
|
||||
} `json:"database"`
|
||||
|
||||
Logging struct {
|
||||
Level string `json:"level"`
|
||||
Format string `json:"format"`
|
||||
} `json:"logging"`
|
||||
|
||||
Audit struct {
|
||||
QueueSize int `json:"queueSize"`
|
||||
Workers int `json:"workers"`
|
||||
} `json:"audit"`
|
||||
|
||||
Email struct {
|
||||
Provider string `json:"provider"`
|
||||
MockDir string `json:"mockDir"`
|
||||
} `json:"email"`
|
||||
|
||||
CircuitBreaker struct {
|
||||
Default jsonCircuitBreakerPolicy `json:"default"`
|
||||
MicrosoftSSO jsonCircuitBreakerPolicy `json:"microsoftSSO"`
|
||||
SES jsonCircuitBreakerPolicy `json:"ses"`
|
||||
SQS jsonCircuitBreakerPolicy `json:"sqs"`
|
||||
} `json:"circuitBreaker"`
|
||||
|
||||
Auth struct {
|
||||
PublicBaseURL string `json:"publicBaseURL"`
|
||||
PasswordPepper string `json:"passwordPepper"`
|
||||
IPEncryptionKey string `json:"ipEncryptionKey"`
|
||||
DefaultRole string `json:"defaultRole"`
|
||||
DisableRegister bool `json:"disableRegister"`
|
||||
Hash struct {
|
||||
Time uint32 `json:"time"`
|
||||
MemoryKB uint32 `json:"memoryKB"`
|
||||
Threads uint8 `json:"threads"`
|
||||
KeyLen uint32 `json:"keyLen"`
|
||||
} `json:"hash"`
|
||||
Email struct {
|
||||
VerifyTTL Duration `json:"verifyTTL"`
|
||||
VerifyURL string `json:"verifyURL"`
|
||||
InviteTTL Duration `json:"inviteTTL"`
|
||||
} `json:"email"`
|
||||
Password struct {
|
||||
SetURL string `json:"setURL"`
|
||||
ResetTTL Duration `json:"resetTTL"`
|
||||
ResetURL string `json:"resetURL"`
|
||||
ForgotEmailCooldown Duration `json:"forgotEmailCooldown"`
|
||||
ForgotMinDuration Duration `json:"forgotMinDuration"`
|
||||
ForgotIPRateMax int `json:"forgotIPRateMax"`
|
||||
ForgotIPRateWindow Duration `json:"forgotIPRateWindow"`
|
||||
LoginIPRateMax int `json:"loginIPRateMax"`
|
||||
LoginIPRateWindow Duration `json:"loginIPRateWindow"`
|
||||
} `json:"password"`
|
||||
SecurityPin struct {
|
||||
ResetTTL Duration `json:"resetTTL"`
|
||||
ResetURL string `json:"resetURL"`
|
||||
ForgotEmailCooldown Duration `json:"forgotEmailCooldown"`
|
||||
ForgotMinDuration Duration `json:"forgotMinDuration"`
|
||||
MaxAttempts int `json:"maxAttempts"`
|
||||
LockDuration Duration `json:"lockDuration"`
|
||||
ActionTokenTTL Duration `json:"actionTokenTTL"`
|
||||
} `json:"securityPin"`
|
||||
JWT struct {
|
||||
AccessSecret string `json:"accessSecret"`
|
||||
RefreshSecret string `json:"refreshSecret"`
|
||||
AccessTTL Duration `json:"accessTTL"`
|
||||
RefreshTTL Duration `json:"refreshTTL"`
|
||||
RefreshTotpTTL Duration `json:"refreshTotpTTL"`
|
||||
LocalRefreshTTL Duration `json:"localRefreshTTL"`
|
||||
SessionIdleTTL Duration `json:"sessionIdleTTL"`
|
||||
Cookie struct {
|
||||
Domain string `json:"domain"`
|
||||
Secure bool `json:"secure"`
|
||||
SameSite string `json:"sameSite"`
|
||||
AccessName string `json:"accessName"`
|
||||
RefreshName string `json:"refreshName"`
|
||||
} `json:"cookie"`
|
||||
} `json:"jwt"`
|
||||
TOTP struct {
|
||||
ChallengeTTL Duration `json:"challengeTTL"`
|
||||
} `json:"totp"`
|
||||
SSO struct {
|
||||
StateTTL Duration `json:"stateTTL"`
|
||||
SuccessRedirect string `json:"successRedirect"`
|
||||
} `json:"sso"`
|
||||
WebAuthn struct {
|
||||
RPID string `json:"rpId"`
|
||||
RPDisplayName string `json:"rpDisplayName"`
|
||||
RPOrigins string `json:"rpOrigins"`
|
||||
ChallengeTTL Duration `json:"challengeTTL"`
|
||||
} `json:"webauthn"`
|
||||
} `json:"auth"`
|
||||
|
||||
TOTP struct {
|
||||
Issuer string `json:"issuer"`
|
||||
SecretKey string `json:"secretKey"`
|
||||
} `json:"totp"`
|
||||
|
||||
MicrosoftSSO struct {
|
||||
TenantID string `json:"tenantId"`
|
||||
ClientID string `json:"clientId"`
|
||||
ClientSecret string `json:"clientSecret"`
|
||||
RedirectURL string `json:"redirectUrl"`
|
||||
Authority string `json:"authority"`
|
||||
Scopes string `json:"scopes"`
|
||||
} `json:"microsoftSSO"`
|
||||
SAML struct {
|
||||
CertFile string `json:"certFile"`
|
||||
KeyFile string `json:"keyFile"`
|
||||
MetadataFile string `json:"metadataFile"`
|
||||
RootURL string `json:"rootUrl"`
|
||||
FrontendURL string `json:"frontendUrl"`
|
||||
CookieDomain string `json:"cookieDomain"`
|
||||
CookieSecure bool `json:"cookieSecure"`
|
||||
SessionTTLHr int `json:"sessionTTLHour"`
|
||||
} `json:"saml"`
|
||||
|
||||
Queue struct {
|
||||
MessageSchemaVersion string `json:"messageSchemaVersion"`
|
||||
EnableLegacyPayloadFallback bool `json:"enableLegacyPayloadFallback"`
|
||||
IdempotencyKeyPrefix string `json:"idempotencyKeyPrefix"`
|
||||
IdempotencyTTL Duration `json:"idempotencyTTL"`
|
||||
ProcessingTTL Duration `json:"processingTTL"`
|
||||
Worker jsonWorker `json:"worker"`
|
||||
Outbox jsonOutbox `json:"outbox"`
|
||||
} `json:"queue"`
|
||||
|
||||
FileQueue struct {
|
||||
MessageSchemaVersion string `json:"messageSchemaVersion"`
|
||||
IdempotencyKeyPrefix string `json:"idempotencyKeyPrefix"`
|
||||
IdempotencyTTL Duration `json:"idempotencyTTL"`
|
||||
ProcessingTTL Duration `json:"processingTTL"`
|
||||
Worker jsonWorker `json:"worker"`
|
||||
Outbox jsonOutbox `json:"outbox"`
|
||||
} `json:"fileQueue"`
|
||||
|
||||
AWS struct {
|
||||
Region string `json:"region"`
|
||||
AccessKeyID string `json:"accessKeyId"`
|
||||
SecretAccessKey string `json:"secretAccessKey"`
|
||||
SQS jsonSQS `json:"sqs"`
|
||||
FileSQS jsonSQS `json:"fileSqs"`
|
||||
SES struct {
|
||||
Region string `json:"region"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
From string `json:"from"`
|
||||
ConfigurationSet string `json:"configurationSet"`
|
||||
SendTimeout Duration `json:"sendTimeout"`
|
||||
AllowInsecureEndpoint bool `json:"allowInsecureEndpoint"`
|
||||
} `json:"ses"`
|
||||
} `json:"aws"`
|
||||
|
||||
File struct {
|
||||
Provider string `json:"provider"`
|
||||
S3 struct {
|
||||
Bucket string `json:"bucket"`
|
||||
Region string `json:"region"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
ForcePathStyle bool `json:"forcePathStyle"`
|
||||
AllowInsecureEndpoint bool `json:"allowInsecureEndpoint"`
|
||||
AutoCreateBucket bool `json:"autoCreateBucket"`
|
||||
ObjectKeyPrefix string `json:"objectKeyPrefix"`
|
||||
PresignPutTTL Duration `json:"presignPutTTL"`
|
||||
PresignGetTTL Duration `json:"presignGetTTL"`
|
||||
} `json:"s3"`
|
||||
Upload struct {
|
||||
MaxSize int64 `json:"maxSize"`
|
||||
AllowedMimeTypes string `json:"allowedMimeTypes"`
|
||||
AllowedExtensions string `json:"allowedExtensions"`
|
||||
AllowEmptyFile bool `json:"allowEmptyFile"`
|
||||
MaxFilenameLength int `json:"maxFilenameLength"`
|
||||
} `json:"upload"`
|
||||
} `json:"file"`
|
||||
|
||||
WOPI struct {
|
||||
TokenSecret string `json:"tokenSecret"`
|
||||
ProofSecret string `json:"proofSecret"`
|
||||
DiscoveryURL string `json:"discoveryUrl"`
|
||||
PublicBaseURL string `json:"publicBaseUrl"`
|
||||
EditActionURL string `json:"editActionUrl"`
|
||||
TokenTTL Duration `json:"tokenTTL"`
|
||||
MaxBodyBytes int `json:"maxBodyBytes"`
|
||||
RequireProof bool `json:"requireProof"`
|
||||
} `json:"wopi"`
|
||||
|
||||
Gotenberg struct {
|
||||
URL string `json:"url"`
|
||||
} `json:"gotenberg"`
|
||||
}
|
||||
|
||||
func (r *jsonAppConfig) toAppConfig() AppConfig {
|
||||
// IPEncryptionKey falls back to PasswordPepper when not set (mirrors env loader behaviour)
|
||||
ipKey := r.Auth.IPEncryptionKey
|
||||
if ipKey == "" {
|
||||
ipKey = r.Auth.PasswordPepper
|
||||
}
|
||||
|
||||
// RefreshTotpTTL falls back to RefreshTTL when not set
|
||||
refreshTotpTTL := r.Auth.JWT.RefreshTotpTTL.Duration
|
||||
if refreshTotpTTL == 0 {
|
||||
refreshTotpTTL = r.Auth.JWT.RefreshTTL.Duration
|
||||
}
|
||||
|
||||
// SES region falls back to aws.region
|
||||
sesRegion := r.AWS.SES.Region
|
||||
if sesRegion == "" {
|
||||
sesRegion = r.AWS.Region
|
||||
}
|
||||
|
||||
return AppConfig{
|
||||
HTTPPort: r.HTTP.Port,
|
||||
|
||||
MySQL: MySQLConfig{
|
||||
DSN: r.Database.MySQL.DSN,
|
||||
MaxOpenConns: r.Database.MySQL.MaxOpenConns,
|
||||
MaxIdleConns: r.Database.MySQL.MaxIdleConns,
|
||||
ConnMaxLifetime: r.Database.MySQL.ConnMaxLifetime.Duration,
|
||||
ConnMaxIdleTime: r.Database.MySQL.ConnMaxIdleTime.Duration,
|
||||
},
|
||||
|
||||
Logging: LoggingConfig{
|
||||
Level: r.Logging.Level,
|
||||
Format: r.Logging.Format,
|
||||
},
|
||||
|
||||
Audit: AuditConfig{
|
||||
QueueSize: r.Audit.QueueSize,
|
||||
Workers: r.Audit.Workers,
|
||||
},
|
||||
|
||||
Email: EmailDeliveryConfig{
|
||||
Provider: strings.ToLower(strings.TrimSpace(r.Email.Provider)),
|
||||
MockDir: strings.TrimSpace(r.Email.MockDir),
|
||||
},
|
||||
|
||||
SES: SESConfig{
|
||||
Region: sesRegion,
|
||||
Endpoint: r.AWS.SES.Endpoint,
|
||||
From: r.AWS.SES.From,
|
||||
ConfigurationSetName: r.AWS.SES.ConfigurationSet,
|
||||
SendTimeout: r.AWS.SES.SendTimeout.Duration,
|
||||
AllowInsecureEndpoint: r.AWS.SES.AllowInsecureEndpoint,
|
||||
},
|
||||
|
||||
Auth: AuthConfig{
|
||||
PublicBaseURL: r.Auth.PublicBaseURL,
|
||||
PasswordPepper: r.Auth.PasswordPepper,
|
||||
IPEncryptionKey: ipKey,
|
||||
HashTime: r.Auth.Hash.Time,
|
||||
HashMemoryKB: r.Auth.Hash.MemoryKB,
|
||||
HashThreads: r.Auth.Hash.Threads,
|
||||
HashKeyLen: r.Auth.Hash.KeyLen,
|
||||
EmailVerifyTTL: r.Auth.Email.VerifyTTL.Duration,
|
||||
EmailVerifyURL: r.Auth.Email.VerifyURL,
|
||||
InviteTTL: r.Auth.Email.InviteTTL.Duration,
|
||||
SetPasswordURL: r.Auth.Password.SetURL,
|
||||
PasswordResetTTL: r.Auth.Password.ResetTTL.Duration,
|
||||
PasswordResetURL: r.Auth.Password.ResetURL,
|
||||
SecurityPINResetTTL: r.Auth.SecurityPin.ResetTTL.Duration,
|
||||
SecurityPINResetURL: r.Auth.SecurityPin.ResetURL,
|
||||
ForgotSecurityPINCooldown: r.Auth.SecurityPin.ForgotEmailCooldown.Duration,
|
||||
ForgotSecurityPINMinDuration: r.Auth.SecurityPin.ForgotMinDuration.Duration,
|
||||
SecurityPINMaxAttempts: r.Auth.SecurityPin.MaxAttempts,
|
||||
SecurityPINLockDuration: r.Auth.SecurityPin.LockDuration.Duration,
|
||||
SecurityPINActionTokenTTL: r.Auth.SecurityPin.ActionTokenTTL.Duration,
|
||||
ForgotPasswordEmailCooldown: r.Auth.Password.ForgotEmailCooldown.Duration,
|
||||
ForgotPasswordMinDuration: r.Auth.Password.ForgotMinDuration.Duration,
|
||||
ForgotPasswordIPRateMax: r.Auth.Password.ForgotIPRateMax,
|
||||
ForgotPasswordIPRateWindow: r.Auth.Password.ForgotIPRateWindow.Duration,
|
||||
LoginIPRateMax: r.Auth.Password.LoginIPRateMax,
|
||||
LoginIPRateWindow: r.Auth.Password.LoginIPRateWindow.Duration,
|
||||
TOTPChallengeTTL: r.Auth.TOTP.ChallengeTTL.Duration,
|
||||
SSOStateTTL: r.Auth.SSO.StateTTL.Duration,
|
||||
TOTPIssuer: r.TOTP.Issuer,
|
||||
TOTPSecretKeyB64: r.TOTP.SecretKey,
|
||||
JWTAccessSecret: r.Auth.JWT.AccessSecret,
|
||||
JWTRefreshSecret: r.Auth.JWT.RefreshSecret,
|
||||
JWTAccessTTL: r.Auth.JWT.AccessTTL.Duration,
|
||||
JWTRefreshTTL: r.Auth.JWT.RefreshTTL.Duration,
|
||||
JWTRefreshTOTPTTL: refreshTotpTTL,
|
||||
JWTLocalRefreshTTL: r.Auth.JWT.LocalRefreshTTL.Duration,
|
||||
SessionIdleTTL: r.Auth.JWT.SessionIdleTTL.Duration,
|
||||
JWTCookieDomain: r.Auth.JWT.Cookie.Domain,
|
||||
JWTCookieSecure: r.Auth.JWT.Cookie.Secure,
|
||||
JWTCookieSameSite: r.Auth.JWT.Cookie.SameSite,
|
||||
JWTAccessCookieName: r.Auth.JWT.Cookie.AccessName,
|
||||
JWTRefreshCookieName: r.Auth.JWT.Cookie.RefreshName,
|
||||
WebAuthnRPID: r.Auth.WebAuthn.RPID,
|
||||
WebAuthnRPDisplayName: r.Auth.WebAuthn.RPDisplayName,
|
||||
WebAuthnRPOrigins: splitCSV(r.Auth.WebAuthn.RPOrigins),
|
||||
WebAuthnChallengeTTL: r.Auth.WebAuthn.ChallengeTTL.Duration,
|
||||
SSOSuccessRedirectURL: r.Auth.SSO.SuccessRedirect,
|
||||
DefaultRoleName: r.Auth.DefaultRole,
|
||||
DisableRegister: r.Auth.DisableRegister,
|
||||
},
|
||||
|
||||
MicrosoftSSO: MicrosoftSSOConfig{
|
||||
TenantID: r.MicrosoftSSO.TenantID,
|
||||
ClientID: r.MicrosoftSSO.ClientID,
|
||||
ClientSecret: r.MicrosoftSSO.ClientSecret,
|
||||
RedirectURL: r.MicrosoftSSO.RedirectURL,
|
||||
Authority: r.MicrosoftSSO.Authority,
|
||||
Scopes: splitCSV(r.MicrosoftSSO.Scopes),
|
||||
},
|
||||
SAML: SAMLConfig{
|
||||
CertFile: strings.TrimSpace(r.SAML.CertFile),
|
||||
KeyFile: strings.TrimSpace(r.SAML.KeyFile),
|
||||
MetadataFile: strings.TrimSpace(r.SAML.MetadataFile),
|
||||
RootURL: strings.TrimSpace(r.SAML.RootURL),
|
||||
FrontendURL: strings.TrimSpace(r.SAML.FrontendURL),
|
||||
CookieDomain: strings.TrimSpace(r.SAML.CookieDomain),
|
||||
CookieSecure: r.SAML.CookieSecure,
|
||||
SessionTTLHr: r.SAML.SessionTTLHr,
|
||||
},
|
||||
|
||||
HTTP: HTTPConfig{
|
||||
BodyLimit: r.HTTP.BodyLimit,
|
||||
ReadTimeout: r.HTTP.Timeouts.Read.Duration,
|
||||
WriteTimeout: r.HTTP.Timeouts.Write.Duration,
|
||||
IdleTimeout: r.HTTP.Timeouts.Idle.Duration,
|
||||
RequestTimeout: r.HTTP.Timeouts.Request.Duration,
|
||||
RequestTimeoutByPath: parseHTTPTimeoutOverrides(r.HTTP.Timeouts.RequestOverrides),
|
||||
ShutdownTimeout: r.HTTP.Timeouts.Shutdown.Duration,
|
||||
CORSAllowOrigins: r.HTTP.CORS.AllowOrigins,
|
||||
CORSAllowMethods: r.HTTP.CORS.AllowMethods,
|
||||
CORSAllowHeaders: r.HTTP.CORS.AllowHeaders,
|
||||
CORSExposeHeaders: r.HTTP.CORS.ExposeHeaders,
|
||||
CORSAllowCredentials: r.HTTP.CORS.AllowCredentials,
|
||||
CORSMaxAge: r.HTTP.CORS.MaxAge,
|
||||
RateLimitMax: r.HTTP.RateLimit.Max,
|
||||
RateLimitWindow: r.HTTP.RateLimit.Window.Duration,
|
||||
RateLimitByEndpoint: parseHTTPEndpointRateLimitOverrides(r.HTTP.RateLimit.Endpoints),
|
||||
HSTSMaxAge: r.HTTP.Security.HSTSMaxAge,
|
||||
},
|
||||
|
||||
Resilience: ResilienceConfig{
|
||||
Default: r.CircuitBreaker.Default.toPolicy(),
|
||||
MicrosoftSSO: r.CircuitBreaker.MicrosoftSSO.toPolicy(),
|
||||
SES: r.CircuitBreaker.SES.toPolicy(),
|
||||
SQS: r.CircuitBreaker.SQS.toPolicy(),
|
||||
},
|
||||
|
||||
Queue: QueueConfig{
|
||||
MessageSchemaVersion: r.Queue.MessageSchemaVersion,
|
||||
EnableLegacyPayloadFallback: r.Queue.EnableLegacyPayloadFallback,
|
||||
IdempotencyKeyPrefix: r.Queue.IdempotencyKeyPrefix,
|
||||
IdempotencyTTL: r.Queue.IdempotencyTTL.Duration,
|
||||
ProcessingTTL: r.Queue.ProcessingTTL.Duration,
|
||||
WorkerConcurrency: r.Queue.Worker.Concurrency,
|
||||
WorkerMaxBatchSize: r.Queue.Worker.MaxBatchSize,
|
||||
WorkerProcessTimeout: r.Queue.Worker.ProcessTimeout.Duration,
|
||||
WorkerShutdownTimeout: r.Queue.Worker.ShutdownTimeout.Duration,
|
||||
WorkerBackoffMin: r.Queue.Worker.BackoffMin.Duration,
|
||||
WorkerBackoffMax: r.Queue.Worker.BackoffMax.Duration,
|
||||
WorkerPermanentFailureDelay: r.Queue.Worker.PermanentFailureDelay.Duration,
|
||||
WorkerMonitorAddr: r.Queue.Worker.MonitorAddr,
|
||||
WorkerHealthErrorThreshold: r.Queue.Worker.HealthErrorThreshold,
|
||||
WorkerDepthPollInterval: r.Queue.Worker.DepthPollInterval.Duration,
|
||||
OutboxEnabled: r.Queue.Outbox.Enabled,
|
||||
OutboxPollInterval: r.Queue.Outbox.PollInterval.Duration,
|
||||
OutboxBatchSize: r.Queue.Outbox.BatchSize,
|
||||
OutboxDispatchTimeout: r.Queue.Outbox.DispatchTimeout.Duration,
|
||||
OutboxLockTTL: r.Queue.Outbox.LockTTL.Duration,
|
||||
OutboxMaxAttempts: r.Queue.Outbox.MaxAttempts,
|
||||
},
|
||||
|
||||
FileQueue: FileQueueConfig{
|
||||
MessageSchemaVersion: r.FileQueue.MessageSchemaVersion,
|
||||
IdempotencyKeyPrefix: r.FileQueue.IdempotencyKeyPrefix,
|
||||
IdempotencyTTL: r.FileQueue.IdempotencyTTL.Duration,
|
||||
ProcessingTTL: r.FileQueue.ProcessingTTL.Duration,
|
||||
WorkerConcurrency: r.FileQueue.Worker.Concurrency,
|
||||
WorkerMaxBatchSize: r.FileQueue.Worker.MaxBatchSize,
|
||||
WorkerProcessTimeout: r.FileQueue.Worker.ProcessTimeout.Duration,
|
||||
WorkerShutdownTimeout: r.FileQueue.Worker.ShutdownTimeout.Duration,
|
||||
WorkerBackoffMin: r.FileQueue.Worker.BackoffMin.Duration,
|
||||
WorkerBackoffMax: r.FileQueue.Worker.BackoffMax.Duration,
|
||||
WorkerPermanentFailureDelay: r.FileQueue.Worker.PermanentFailureDelay.Duration,
|
||||
WorkerMonitorAddr: r.FileQueue.Worker.MonitorAddr,
|
||||
WorkerHealthErrorThreshold: r.FileQueue.Worker.HealthErrorThreshold,
|
||||
WorkerDepthPollInterval: r.FileQueue.Worker.DepthPollInterval.Duration,
|
||||
OutboxEnabled: r.FileQueue.Outbox.Enabled,
|
||||
OutboxPollInterval: r.FileQueue.Outbox.PollInterval.Duration,
|
||||
OutboxBatchSize: r.FileQueue.Outbox.BatchSize,
|
||||
OutboxDispatchTimeout: r.FileQueue.Outbox.DispatchTimeout.Duration,
|
||||
OutboxLockTTL: r.FileQueue.Outbox.LockTTL.Duration,
|
||||
OutboxMaxAttempts: r.FileQueue.Outbox.MaxAttempts,
|
||||
},
|
||||
|
||||
// SQS email queue: region falls back to aws.region
|
||||
SQS: r.AWS.SQS.toSQSConfig(r.AWS.Region, "", r.AWS.AccessKeyID, r.AWS.SecretAccessKey),
|
||||
|
||||
// FileSQS: region + endpoint fall back to aws.region / aws.sqs.endpoint
|
||||
FileSQS: r.AWS.FileSQS.toSQSConfig(r.AWS.Region, r.AWS.SQS.Endpoint, r.AWS.AccessKeyID, r.AWS.SecretAccessKey),
|
||||
|
||||
FileStorage: FileStorageConfig{
|
||||
Provider: strings.ToLower(strings.TrimSpace(r.File.Provider)),
|
||||
Bucket: strings.TrimSpace(r.File.S3.Bucket),
|
||||
Region: strings.TrimSpace(r.File.S3.Region),
|
||||
Endpoint: strings.TrimSpace(r.File.S3.Endpoint),
|
||||
AccessKeyID: strings.TrimSpace(r.AWS.AccessKeyID),
|
||||
SecretAccessKey: strings.TrimSpace(r.AWS.SecretAccessKey),
|
||||
ForcePathStyle: r.File.S3.ForcePathStyle,
|
||||
AllowInsecureEndpoint: r.File.S3.AllowInsecureEndpoint,
|
||||
AutoCreateBucket: r.File.S3.AutoCreateBucket,
|
||||
ObjectKeyPrefix: strings.TrimSpace(r.File.S3.ObjectKeyPrefix),
|
||||
PresignPutTTL: r.File.S3.PresignPutTTL.Duration,
|
||||
PresignGetTTL: r.File.S3.PresignGetTTL.Duration,
|
||||
},
|
||||
|
||||
FileUpload: FileManagerUploadPolicyConfig{
|
||||
MaxFileSizeBytes: r.File.Upload.MaxSize,
|
||||
AllowedMimeTypes: normalizeMimeTypes(splitCSV(r.File.Upload.AllowedMimeTypes)),
|
||||
AllowedExtensions: normalizeExtensions(splitCSV(r.File.Upload.AllowedExtensions)),
|
||||
AllowEmptyFile: r.File.Upload.AllowEmptyFile,
|
||||
MaxFilenameLength: r.File.Upload.MaxFilenameLength,
|
||||
},
|
||||
|
||||
WOPI: WOPIConfig{
|
||||
TokenSecret: strings.TrimSpace(r.WOPI.TokenSecret),
|
||||
ProofSecret: strings.TrimSpace(r.WOPI.ProofSecret),
|
||||
DiscoveryURL: strings.TrimSpace(r.WOPI.DiscoveryURL),
|
||||
PublicBaseURL: strings.TrimSpace(r.WOPI.PublicBaseURL),
|
||||
EditActionURL: strings.TrimSpace(r.WOPI.EditActionURL),
|
||||
TokenTTL: r.WOPI.TokenTTL.Duration,
|
||||
MaxBodyBytes: r.WOPI.MaxBodyBytes,
|
||||
RequireProof: r.WOPI.RequireProof,
|
||||
},
|
||||
|
||||
Gotenberg: GotenbergConfig{
|
||||
URL: strings.TrimSpace(r.Gotenberg.URL),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func splitCSV(s string) []string {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
parts := strings.Split(s, ",")
|
||||
out := make([]string, 0, len(parts))
|
||||
for _, p := range parts {
|
||||
if p = strings.TrimSpace(p); p != "" {
|
||||
out = append(out, p)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
Reference in New Issue
Block a user