// Copyright 2023 wanderer // SPDX-License-Identifier: AGPL-3.0-only package settings import ( "os" "time" "git.dotya.ml/mirre-mt/pcmt/config" "git.dotya.ml/mirre-mt/pcmt/slogging" "golang.org/x/exp/slog" ) type Settings struct { host string port int appPath string httpDomain string httpSecure bool httpGzipEnabled bool httpGzipLevel int httpRateLimitEnabled bool httpRateLimit int isLive bool isDevel bool initCreateAdmin bool initAdminPassword string loggerJSON bool sessionCookieName string sessionCookieAuthSecret string sessionCookieEncrSecret string sessionAuthIsHex bool sessionEncrIsHex bool sessionMaxAge int assetsPath string templatesPath string version string dbConnstring string dbType string dbIsSetUp bool RegistrationAllowed bool hibpAPIKey string dehashedAPIKey string } const ( appName = "pcmt" defaultPort = 3000 defaultSessionMaxAge = 86400 // seconds. defaultHTTPDomain = "localhost" defaultServerWriteTimeout = 30 * time.Second defaultServerReadHeaderTimeout = 30 * time.Second defaultLoggerSkipAssets = true ) var log slogging.Slogger // cleantgt is a list of ENV vars pertaining to pcmt. var cleantgt = []string{ "PCMT_LIVE", "PCMT_DEVEL", "PCMT_CONNSTRING", "PCMT_DBTYPE", "PCMT_SESSION_AUTH_SECRET", "PCMT_SESSION_ENCR_SECRET", "PCMT_INIT_ADMIN_PASSWORD", "PCMT_HIBP_API_KEY", "PCMT_DEHASHED_API_KEY", } // New returns a new instance of the settings struct. func New() *Settings { return &Settings{} } // DefaultServerWriteTimeout returns the server default write timeout. func (s *Settings) DefaultServerWriteTimeout() time.Duration { return defaultServerWriteTimeout } // DefaultServerReadHeaderTimeout returns the server default read header timeout. func (s *Settings) DefaultServerReadHeaderTimeout() time.Duration { return defaultServerReadHeaderTimeout } // DefaultLoggerSkipAssets returns whether the logger skips reporting asset visits. func (s *Settings) DefaultLoggerSkipAssets() bool { return defaultLoggerSkipAssets } // Consolidate reconciles whatever values are set in config and via flags and // sets it to one place that should be regarded as a single source of truth - // the settings struct. Order of preference for values is (from higher to // lower) as follows: flag -> Env var -> configuration file. func (s *Settings) Consolidate(conf *config.Config, host *string, port *int, devel *bool, version string) { log = *slogging.Logger() // have a local copy. log.Logger = log.With( slog.Group("pcmt extra", slog.String("module", "app/settings")), ) log.Debug("starting to consolidate settings") log.Debug("parsing config values") if p := conf.Port; p > 0 && p < 65536 { s.SetPort(conf.Port) } else { log.Warnf("port '%d', outside of bounds, setting a default of %d/tcp", p, defaultPort) s.SetPort(defaultPort) } s.SetHost(conf.Host) s.SetAppPath(conf.AppPath) s.SetIsLive(conf.LiveMode) s.SetIsDevel(conf.DevelMode) s.SetLoggerIsJSON(conf.Logger.JSON) s.SetSessionCookieName(conf.Session.CookieName) s.SetSessionCookieAuthSecret(conf.Session.CookieAuthSecret) s.SetSessionCookieEncrSecret(conf.Session.CookieEncrSecret) authHex, encrHex := conf.SessionSecretsAreHex() if authHex { s.sessionAuthIsHex = true } if encrHex { s.sessionEncrIsHex = true } if conf.HTTP.Gzip > 0 { s.SetHTTPGzipEnabled(true) s.SetHTTPGzipLevel(conf.HTTP.Gzip) } if conf.HTTP.RateLimit > 0 { s.SetHTTPRateLimitEnabled(true) s.SetHTTPRateLimit(conf.HTTP.RateLimit) } if conf.Init.CreateAdmin { s.SetInitCreateAdmin(true) s.SetInitAdminPassword(conf.Init.AdminPassword) } s.SetHTTPDomain(conf.HTTP.Domain) s.SetHTTPSecure(conf.HTTP.Secure) log.Debug("checking flag overrides") overrideMsg := "overriding '%s' based on a flag: %+v" if isFlagPassed("host") { if h := *host; h != "unset" && h != conf.Host { log.Debugf(overrideMsg, "host", h) s.SetHost(h) } } if isFlagPassed("port") { if p := *port; p > 0 && p < 65536 { if p != conf.Port { log.Debugf(overrideMsg, "port", p) s.SetPort(p) } } else { log.Warnf("flag-supplied port '%d' outside of bounds, ignoring", p) } } if isFlagPassed("devel") { if d := *devel; d != conf.DevelMode { log.Debugf(overrideMsg, "develMode", d) s.SetIsDevel(d) } } if conf.Registration.Allowed { s.RegistrationAllowed = true } s.setAPIKeys() s.SetVersion(version) } // Host returns the host. func (s *Settings) Host() string { return s.host } // Port returns the port. func (s *Settings) Port() int { return s.port } // AppName returns the appName. func (s *Settings) AppName() string { return appName } // AppPath returns the appPath. func (s *Settings) AppPath() string { return s.appPath } // IsLive returns the value of isLive of the receiver. func (s *Settings) IsLive() bool { return s.isLive } // IsDevel returns the value of isDevel of the receiver. func (s *Settings) IsDevel() bool { return s.isDevel } // InitCreateAdmin returns the value of initCreateAdmin of the receiver. func (s *Settings) InitCreateAdmin() bool { return s.initCreateAdmin } // InitAdminPassword returns the value of initAdminPassword of the receiver. func (s *Settings) InitAdminPassword() string { return s.initAdminPassword } // LoggerIsJSON returns whether the logger should use the JSON handler. func (s *Settings) LoggerIsJSON() bool { return s.loggerJSON } // SessionCookieName returns the name of the session cookie. func (s *Settings) SessionCookieName() string { return s.sessionCookieName } // SessionCookieAuthSecret returns the session cookie authentication secret. func (s *Settings) SessionCookieAuthSecret() string { return s.sessionCookieAuthSecret } // SessionCookieEncrSecret returns the session cookie encryption secret. func (s *Settings) SessionCookieEncrSecret() string { return s.sessionCookieEncrSecret } // SessionAuthIsHex returns whether the session cookie authentication secret is hex. func (s *Settings) SessionAuthIsHex() bool { return s.sessionAuthIsHex } // SessionEncrIsHex returns whether the session cookie encryption secret is hex. func (s *Settings) SessionEncrIsHex() bool { return s.sessionEncrIsHex } // SessionMaxAge returns the session cookie MaxAge value. func (s *Settings) SessionMaxAge() int { return s.sessionMaxAge } // HTTPDomain returns the httpDomain. func (s *Settings) HTTPDomain() string { return s.httpDomain } // HTTPSecure returns the httpSecure. func (s *Settings) HTTPSecure() bool { return s.httpSecure } // HTTPGzipEnabled returns the httpGzipEnabled variable. func (s *Settings) HTTPGzipEnabled() bool { return s.httpGzipEnabled } // HTTPGzipLevel returns the httpGzipLevel. func (s *Settings) HTTPGzipLevel() int { return s.httpGzipLevel } // HTTPRateLimitEnabled returns the httpRateLimitEnabled variable. func (s *Settings) HTTPRateLimitEnabled() bool { return s.httpRateLimitEnabled } // HTTPRateLimit returns the httpRateLimit. func (s *Settings) HTTPRateLimit() int { return s.httpRateLimit } // AssetsPath returns the assetsPath. func (s *Settings) AssetsPath() string { return s.assetsPath } // TemplatesPath returns the templatesPath. func (s *Settings) TemplatesPath() string { return s.templatesPath } // Version returns the version. func (s *Settings) Version() string { return s.version } // DbIsSetUp returns the dbIsSetUp. func (s *Settings) DbIsSetUp() bool { return s.dbIsSetUp } // APIKeyHIBP returns the hibpAPIKey. func (s *Settings) APIKeyHIBP() string { return s.hibpAPIKey } // APIKeyDehashed returns the dehashedAPIKey. func (s *Settings) APIKeyDehashed() string { return s.dehashedAPIKey } // DbConnstring returns the dbConnString. func (s *Settings) DbConnstring() string { return s.dbConnstring } // DbType returns the dbType. func (s *Settings) DbType() string { return s.dbType } // SetHost sets the host. func (s *Settings) SetHost(host string) { s.host = host } // SetPort sets the port. func (s *Settings) SetPort(port int) { s.port = port } // SetAppPath sets the appPath. func (s *Settings) SetAppPath(appPath string) { s.appPath = appPath } // SetIsLive sets the value of isLive of the receiver. func (s *Settings) SetIsLive(live bool) { s.isLive = live } // SetIsDevel sets the value of isDevel of the receiver. func (s *Settings) SetIsDevel(devel bool) { s.isDevel = devel } // SetInitCreateAdmin sets the value of initCreateAdmin of the receiver. func (s *Settings) SetInitCreateAdmin(create bool) { s.initCreateAdmin = create } // SetInitAdminPassword sets the value of initAdminPassword of the receiver. func (s *Settings) SetInitAdminPassword(password string) { s.initAdminPassword = password } // SetLoggerIsJSON sets the setting value of loggerIsJSON. func (s *Settings) SetLoggerIsJSON(isJSON bool) { s.loggerJSON = isJSON } // SetSessionCookieName sets session cookie name. func (s *Settings) SetSessionCookieName(sessionCookieName string) { s.sessionCookieName = sessionCookieName } // SetSessionCookieAuthSecret sets the session cookie authentication secret. func (s *Settings) SetSessionCookieAuthSecret(sessionCookieAuthSecret string) { s.sessionCookieAuthSecret = sessionCookieAuthSecret } // SetSessionCookieEncrSecret sets the session cookie encryption secret. func (s *Settings) SetSessionCookieEncrSecret(sessionCookieEncrSecret string) { s.sessionCookieEncrSecret = sessionCookieEncrSecret } // SetSessionMaxAge sets sessionMaxAge. func (s *Settings) SetSessionMaxAge(sessionMaxAge int) { if sessionMaxAge < 1 { s.sessionMaxAge = defaultSessionMaxAge } else { s.sessionMaxAge = sessionMaxAge } } // SetHTTPDomain sets the httpDomain. func (s *Settings) SetHTTPDomain(domain string) { switch domain { case "": s.httpDomain = defaultHTTPDomain default: s.httpDomain = domain } } // SetHTTPSecure sets the httpSecure. func (s *Settings) SetHTTPSecure(secure bool) { s.httpSecure = secure } // SetHTTPGzipEnabled sets the httpGzipEnabled variable. func (s *Settings) SetHTTPGzipEnabled(enabled bool) { s.httpGzipEnabled = enabled } // SetHTTPGzipLevel sets the httpGzipLevel. func (s *Settings) SetHTTPGzipLevel(level int) { s.httpGzipLevel = level } // SetHTTPRateLimitEnabled sets the httpRateLimitEnabled variable. func (s *Settings) SetHTTPRateLimitEnabled(enabled bool) { s.httpRateLimitEnabled = enabled } // SetHTTPRateLimit sets the httpRateLimit. func (s *Settings) SetHTTPRateLimit(rateLimit int) { s.httpRateLimit = rateLimit } // SetAssetsPath sets the assetsPath. func (s *Settings) SetAssetsPath(assetsPath string) { s.assetsPath = assetsPath } // SetTemplatesPath sets the templatesPath. func (s *Settings) SetTemplatesPath(templatesPath string) { s.templatesPath = templatesPath } // SetVersion sets the version. func (s *Settings) SetVersion(version string) { s.version = version } // SetDbConnstring sets the dbConnString. func (s *Settings) SetDbConnstring(connstring string) { s.dbConnstring = connstring } // SetDbType sets the dbType. func (s *Settings) SetDbType(dbType string) { s.dbType = dbType } // SetDbIsSetUp sets the dbIsSetUp. func (s *Settings) SetDbIsSetUp(is bool) { s.dbIsSetUp = is } // SetAPIKeyHIBP sets the hibpAPIKey. func (s *Settings) SetAPIKeyHIBP(k string) { s.hibpAPIKey = k } // SetAPIKeyDehashed sets the dehashedAPIKey. func (s *Settings) SetAPIKeyDehashed(k string) { s.dehashedAPIKey = k } // EraseENVs attempts to clear environment vars pertaining to pcmt. func (s *Settings) EraseENVs() error { for _, v := range cleantgt { err := os.Unsetenv(v) if err != nil { return err } } return nil }