diff --git a/internal/inbox/inbox.go b/internal/inbox/inbox.go index f630e15b..2e901336 100644 --- a/internal/inbox/inbox.go +++ b/internal/inbox/inbox.go @@ -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 } diff --git a/internal/oidc/oidc.go b/internal/oidc/oidc.go index 5ce31588..49ff2e4a 100644 --- a/internal/oidc/oidc.go +++ b/internal/oidc/oidc.go @@ -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]) + } }