Merge pull request #316 from abhinavxd/fix/key-rotation

Don't fatal on encryption_key rotation
This commit is contained in:
Abhinav Raut
2026-05-15 10:44:23 +05:30
committed by GitHub
2 changed files with 39 additions and 50 deletions
+10 -7
View File
@@ -641,7 +641,7 @@ func (m *Manager) encryptInboxConfig(config json.RawMessage) (json.RawMessage, e
return encrypted, nil
}
// decryptInboxConfig decrypts sensitive fields in the inbox config JSON.
// Decrypt failures clear the field so the app stays usable across encryption_key rotation.
func (m *Manager) decryptInboxConfig(config json.RawMessage) (json.RawMessage, error) {
if len(config) == 0 {
return config, nil
@@ -652,14 +652,15 @@ func (m *Manager) decryptInboxConfig(config json.RawMessage) (json.RawMessage, e
return nil, fmt.Errorf("unmarshalling config: %w", err)
}
// Decrypt SMTP passwords
if smtpSlice, ok := cfg["smtp"].([]any); ok {
for i, smtpItem := range smtpSlice {
if smtpMap, ok := smtpItem.(map[string]any); ok {
if password, ok := smtpMap["password"].(string); ok && password != "" {
decrypted, err := crypto.Decrypt(password, m.encryptionKey)
if err != nil {
return nil, fmt.Errorf("decrypting SMTP password at index %d: %w", i, err)
m.lo.Error("error decrypting SMTP password, clearing field", "index", i, "error", err)
smtpMap["password"] = ""
continue
}
smtpMap["password"] = decrypted
}
@@ -667,14 +668,15 @@ func (m *Manager) decryptInboxConfig(config json.RawMessage) (json.RawMessage, e
}
}
// Decrypt IMAP passwords
if imapSlice, ok := cfg["imap"].([]any); ok {
for i, imapItem := range imapSlice {
if imapMap, ok := imapItem.(map[string]any); ok {
if password, ok := imapMap["password"].(string); ok && password != "" {
decrypted, err := crypto.Decrypt(password, m.encryptionKey)
if err != nil {
return nil, fmt.Errorf("decrypting IMAP password at index %d: %w", i, err)
m.lo.Error("error decrypting IMAP password, clearing field", "index", i, "error", err)
imapMap["password"] = ""
continue
}
imapMap["password"] = decrypted
}
@@ -682,14 +684,15 @@ func (m *Manager) decryptInboxConfig(config json.RawMessage) (json.RawMessage, e
}
}
// Decrypt OAuth fields if present
if oauthMap, ok := cfg["oauth"].(map[string]any); ok {
fields := []string{"client_secret", "access_token", "refresh_token"}
for _, fieldName := range fields {
if fieldValue, ok := oauthMap[fieldName].(string); ok && fieldValue != "" {
decrypted, err := crypto.Decrypt(fieldValue, m.encryptionKey)
if err != nil {
return nil, fmt.Errorf("decrypting OAuth %s: %w", fieldName, err)
m.lo.Error("error decrypting OAuth field, clearing field", "field", fieldName, "error", err)
oauthMap[fieldName] = ""
continue
}
oauthMap[fieldName] = decrypted
}
+29 -43
View File
@@ -77,12 +77,8 @@ func (o *Manager) Get(id int) (models.OIDC, error) {
return oidc, envelope.NewError(envelope.GeneralError, o.i18n.T("globals.messages.somethingWentWrong"), nil)
}
// Decrypt sensitive fields
if err := o.decryptOIDC(&oidc); err != nil {
return models.OIDC{}, envelope.NewError(envelope.GeneralError, o.i18n.T("globals.messages.somethingWentWrong"), nil)
}
o.decryptOIDC(&oidc)
// Set logo and redirect URL.
oidc.SetProviderLogo()
rootURL, err := o.setting.GetAppRootURL()
if err != nil {
@@ -106,10 +102,7 @@ func (o *Manager) GetAll() ([]models.OIDC, error) {
return nil, err
}
// Decrypt sensitive fields
if err := o.decryptOIDCSlice(oidc); err != nil {
return nil, envelope.NewError(envelope.GeneralError, o.i18n.T("globals.messages.somethingWentWrong"), nil)
}
o.decryptOIDCSlice(oidc)
// Set logo and redirect URL for each record
for i := range oidc {
@@ -133,10 +126,7 @@ func (o *Manager) Create(oidc models.OIDC) (models.OIDC, error) {
return models.OIDC{}, envelope.NewError(envelope.GeneralError, o.i18n.T("globals.messages.somethingWentWrong"), nil)
}
// Decrypt fields before returning (ignore errors as these are non-critical for creation response)
if err := o.decryptOIDC(&createdOIDC); err != nil {
o.lo.Error("error decrypting after creation", "error", err)
}
o.decryptOIDC(&createdOIDC)
return createdOIDC, nil
}
@@ -165,10 +155,7 @@ func (o *Manager) Update(id int, oidc models.OIDC) (models.OIDC, error) {
return models.OIDC{}, envelope.NewError(envelope.GeneralError, o.i18n.T("globals.messages.somethingWentWrong"), nil)
}
// Decrypt fields before returning (ignore errors as these are non-critical for update response)
if err := o.decryptOIDC(&updatedOIDC); err != nil {
o.lo.Error("error decrypting after update", "error", err)
}
o.decryptOIDC(&updatedOIDC)
return updatedOIDC, nil
}
@@ -200,32 +187,31 @@ func (o *Manager) encryptOIDC(clientID, clientSecret string) (encClientID, encCl
return encClientID, encClientSecret, nil
}
// decryptOIDC decrypts sensitive OIDC fields in-place.
// Returns an error if decryption of any field fails.
func (o *Manager) decryptOIDC(oidc *models.OIDC) error {
var err error
oidc.ClientID, err = crypto.Decrypt(oidc.ClientID, o.encryptionKey)
if err != nil {
o.lo.Error("error decrypting client_id", "error", err, "oidc_id", oidc.ID)
return err
}
oidc.ClientSecret, err = crypto.Decrypt(oidc.ClientSecret, o.encryptionKey)
if err != nil {
o.lo.Error("error decrypting client_secret", "error", err, "oidc_id", oidc.ID)
return err
}
return nil
}
// decryptOIDCSlice decrypts sensitive fields for all OIDC records in a slice.
// Returns an error if decryption of any record fails.
func (o *Manager) decryptOIDCSlice(oidcs []models.OIDC) error {
for i := range oidcs {
if err := o.decryptOIDC(&oidcs[i]); err != nil {
return err
// Decrypt failures clear the field so the app stays usable across encryption_key rotation.
func (o *Manager) decryptOIDC(oidc *models.OIDC) {
if oidc.ClientID != "" {
decrypted, err := crypto.Decrypt(oidc.ClientID, o.encryptionKey)
if err != nil {
o.lo.Error("error decrypting client_id, clearing field", "error", err, "oidc_id", oidc.ID)
oidc.ClientID = ""
} else {
oidc.ClientID = decrypted
}
}
if oidc.ClientSecret != "" {
decrypted, err := crypto.Decrypt(oidc.ClientSecret, o.encryptionKey)
if err != nil {
o.lo.Error("error decrypting client_secret, clearing field", "error", err, "oidc_id", oidc.ID)
oidc.ClientSecret = ""
} else {
oidc.ClientSecret = decrypted
}
}
return nil
}
func (o *Manager) decryptOIDCSlice(oidcs []models.OIDC) {
for i := range oidcs {
o.decryptOIDC(&oidcs[i])
}
}