Saving a calendar/contacts integration never blanks or drops its stored credentials

FixEmailService
Shipped
September 17, 2026 at 4:31 AM UTC
Author
Kamo
Commit
d35858f

A contact integration's credentialsJson was replaced by whatever a save sent. The org CalDAV/CardDAV tab sends all four server fields (empty — they never show what is stored) on every save of an Apple or custom server, so pressing Save to change a sync switch overwrote the stored address, username and password with empty strings. The Contacts page's source dialog sends only the typed fields, so changing a password dropped the address and username. IntegrationSettingsService now distinguishes the two kinds of credential write: - A settings save (org PUT, member POST/PUT) MERGES: a blank or null field is "not supplied" and keeps its stored value; all fields blank changes nothing; a typed field replaces its stored counterpart and every other stored field stays. A save that switches the provider starts from nothing, so an OAuth grant's tokens are never carried into an Apple ID's credentials or the reverse. A stored password is bound to the caldavUrl, carddavUrl and username it was saved with: changing any of them without typing the password is refused with a 400 rather than sending the old password to a different server or account. A credential that is not a JSON object is stored as sent, as before. - An OAuth consent REPLACES the grant whole (new saveOrgOAuthGrant for the org callback; saveMemberOAuth). A merge there would keep a refresh token the provider did not reissue, or another account's email. The controller answers IllegalArgumentException from these saves with 400 instead of 500. The token refreshers (here and in daemonservice) read and rewrite the whole decrypted map on the entity, and ProviderRegistry / GenericDavProvider only read it; none of them changes. There are no contact_integrations rows in production, so nothing was wiped yet. IntegrationSettingsServiceTest: blank fields keep the server, a typed field replaces only itself, a new address/account without the password is refused and changes nothing (the same address resent is not a change), a provider switch drops the other provider's keys, org and member OAuth grants replace. Mutation-checked: replacing instead of merging, removing the password binding, ignoring the provider switch, and merging either OAuth grant each fail a test. Full suite: 1763 tests, 0 failures.

All changes

Like what you see shipping?

All of it arrives in your workspace on its own. Start on the free plan and read this page again in a month.

Start Free ForeverView Pricing