639 lines
24 KiB
Go
639 lines
24 KiB
Go
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
|
|
}
|