smtp: fix reading oauth2 settings from env

update access token and refresh token using the same mutex

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
Nicola Murino
2026-09-18 14:45:14 +02:00
parent 4731de9508
commit 2894f69027
3 changed files with 66 additions and 25 deletions
+5
View File
@@ -2267,6 +2267,11 @@ func setViperDefaults() {
viper.SetDefault("smtp.encryption", globalConf.SMTPConfig.Encryption)
viper.SetDefault("smtp.domain", globalConf.SMTPConfig.Domain)
viper.SetDefault("smtp.templates_path", globalConf.SMTPConfig.TemplatesPath)
viper.SetDefault("smtp.oauth2.provider", globalConf.SMTPConfig.OAuth2.Provider)
viper.SetDefault("smtp.oauth2.tenant", globalConf.SMTPConfig.OAuth2.Tenant)
viper.SetDefault("smtp.oauth2.client_id", globalConf.SMTPConfig.OAuth2.ClientID)
viper.SetDefault("smtp.oauth2.client_secret", globalConf.SMTPConfig.OAuth2.ClientSecret)
viper.SetDefault("smtp.oauth2.refresh_token", globalConf.SMTPConfig.OAuth2.RefreshToken)
}
func lookupBoolFromEnv(envName string) (bool, bool) {
+34
View File
@@ -424,6 +424,40 @@ func TestSMTPFromEnv(t *testing.T) {
assert.Equal(t, 587, smtpConfig.Port)
}
func TestSMTPOAuth2FromEnv(t *testing.T) {
reset()
os.Setenv("SFTPGO_SMTP__HOST", "smtp.example.com")
os.Setenv("SFTPGO_SMTP__USER", "sftpgo@example.com")
os.Setenv("SFTPGO_SMTP__AUTH_TYPE", "3")
os.Setenv("SFTPGO_SMTP__OAUTH2__PROVIDER", "1")
os.Setenv("SFTPGO_SMTP__OAUTH2__TENANT", "a-tenant-id")
os.Setenv("SFTPGO_SMTP__OAUTH2__CLIENT_ID", "client id")
os.Setenv("SFTPGO_SMTP__OAUTH2__CLIENT_SECRET", "client secret")
os.Setenv("SFTPGO_SMTP__OAUTH2__REFRESH_TOKEN", "refresh token")
t.Cleanup(func() {
os.Unsetenv("SFTPGO_SMTP__HOST")
os.Unsetenv("SFTPGO_SMTP__USER")
os.Unsetenv("SFTPGO_SMTP__AUTH_TYPE")
os.Unsetenv("SFTPGO_SMTP__OAUTH2__PROVIDER")
os.Unsetenv("SFTPGO_SMTP__OAUTH2__TENANT")
os.Unsetenv("SFTPGO_SMTP__OAUTH2__CLIENT_ID")
os.Unsetenv("SFTPGO_SMTP__OAUTH2__CLIENT_SECRET")
os.Unsetenv("SFTPGO_SMTP__OAUTH2__REFRESH_TOKEN")
})
// the environment is the only source, no config file is present
err := config.LoadConfig(t.TempDir(), "")
assert.NoError(t, err)
smtpConfig := config.GetSMTPConfig()
assert.Equal(t, 3, smtpConfig.AuthType)
assert.Equal(t, 1, smtpConfig.OAuth2.Provider)
assert.Equal(t, "a-tenant-id", smtpConfig.OAuth2.Tenant)
assert.Equal(t, "client id", smtpConfig.OAuth2.ClientID)
assert.Equal(t, "client secret", smtpConfig.OAuth2.ClientSecret)
assert.Equal(t, "refresh token", smtpConfig.OAuth2.RefreshToken)
}
func TestMFAFromEnv(t *testing.T) {
reset()
+27 -25
View File
@@ -92,17 +92,11 @@ func (c *OAuth2Config) isEqual(other *OAuth2Config) bool {
}
func (c *OAuth2Config) getAccessToken() (string, error) {
c.mu.RLock()
if c.accessToken.Expiry.After(time.Now().Add(30 * time.Second)) {
accessToken := c.accessToken.AccessToken
c.mu.RUnlock()
return accessToken, nil
token := c.getCachedToken()
if token.AccessToken != "" && token.Expiry.After(time.Now().Add(30*time.Second)) {
return token.AccessToken, nil
}
logger.Debug(logSender, "", "renew oauth2 token required, current token expires at %s", c.accessToken.Expiry)
token := new(oauth2.Token)
*token = *c.accessToken
c.mu.RUnlock()
logger.Debug(logSender, "", "renew oauth2 token required, current token expires at %s", token.Expiry)
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
@@ -112,25 +106,33 @@ func (c *OAuth2Config) getAccessToken() (string, error) {
logger.Error(logSender, "", "unable to get new token: %v", err)
return "", err
}
accessToken := newToken.AccessToken
refreshToken := newToken.RefreshToken
if refreshToken != "" && refreshToken != token.RefreshToken {
c.mu.Lock()
c.RefreshToken = refreshToken
c.accessToken = newToken
c.mu.Unlock()
updatedRefreshToken := ""
c.mu.Lock()
if newToken.RefreshToken != "" && newToken.RefreshToken != token.RefreshToken {
c.RefreshToken = newToken.RefreshToken
updatedRefreshToken = newToken.RefreshToken
}
c.accessToken = newToken
c.mu.Unlock()
if updatedRefreshToken != "" {
logger.Debug(logSender, "", "oauth2 refresh token changed")
go updateRefreshToken(refreshToken)
go updateRefreshToken(updatedRefreshToken)
}
if accessToken != token.AccessToken {
c.mu.Lock()
c.accessToken = newToken
c.mu.Unlock()
logger.Debug(logSender, "", "new oauth2 token saved, expires at %s", newToken.Expiry)
logger.Debug(logSender, "", "new oauth2 token saved, expires at %s", c.accessToken.Expiry)
}
return accessToken, nil
return newToken.AccessToken, nil
}
// getCachedToken returns a copy of the cached token.
func (c *OAuth2Config) getCachedToken() *oauth2.Token {
c.mu.RLock()
defer c.mu.RUnlock()
token := new(oauth2.Token)
*token = *c.accessToken
return token
}
func (c *OAuth2Config) initialize() {