Repository navigation
fix(teams): keep the team identity across password changes and linked devices - #640
Merged
Merged
Conversation
The identity was derived from whatever key opened this device's vault, so linked and migrated accounts had a different one on every device that used the DEK. The stored x25519 key now wins once the roster confirms it, and a device that finds the right derived key writes it back.
Snapshot the session before the roster lookup and re-check it around the write, never write without a captured wrapped value, keep a stored key when the roster has none, log repair failures, and share the derive/wrap commands with account.ts.
A vault on a password-derived key became unopenable after a password change made on another device. With the identity stored in the wrapped secrets, the vault can follow the dek like every cloud-created account.
…nt switch Settling ran only from the team login setup, which non-Pro accounts and paused devices never reach. It now runs first in the login syncs and on a paused launch, and gives up when the session changed while it waited.
The device that linked an account kept its vault on the old password's key, so a change made on another device or on the portal left it on the recovery screen. The password this device stored before the sign-in still derives that key.
… unpublished - A stranded-device recovery that fails at any step (derive, read error, rekey) restores the session key it found and ends in VaultUnreadableError. - pushUnderNewVaultKey skips the upload while sync is paused; resuming pushes. - An unpublished roster resolves to HKDF(DEK), stored unless already held. - A stored private key the backend rejects is passed over, not fatal. - The identity store write skips the in-memory keys after an account switch during the keychain write. - base64ToByteArray moves to utils/base64 and replaces the inline copies; the identity import in account.ts goes through one helper.
A bound device kept the old password sealed after signing in with one changed on another device: rememberPassword leaves a sealed secret alone, so the next biometric unlock failed and the device-verified password change read a stale password. When the typed password does not unwrap this device's cached secrets, it is sealed anew once the vault is open.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes the password-change bugs left open after #368, ahead of #629.
Problem
The team X25519 identity was
derive_x25519_keypair(getVaultKey()), so it depended on which key a device used for its vault:That caused three bugs:
identityUnprovenfrom the first sign-in).Change
src/services/identity.ts,ensureIdentity().x25519_privatestored in the wrapped secrets once its public key matches the roster.wrapped_secrets_cas(VoltiusApp/serverfeat/wrapped-secrets-cas).getMyX25519Keypair) goes through it. The Switching accounts breaks the team vault key: wrapped_user_secrets is a global keychain slot, not per-account #228 publish guard is unchanged.settleVaultOnDek().linkToCloudnow creates the account like a cloud-created one: identity HKDF(DEK), vault on the DEK.changeMasterPasswordwraps the proven identity under the new key.login. When neither new key opens the vault, it tries the password this device stored before the sign-in. A sealed secret is read only then, with one biometric prompt. If that works, the vault moves to the DEK. Any failure ends in the usualVaultUnreadableError.rememberPasswordkept the old sealed password, so the next biometric unlock failed. Cancelling that prompt stores the new password plain, which is the existingvault_secret_setbehaviour shared withchangeMasterPassword.x25519_public_from_private.deriveX25519Keypair,wrapUserSecretsandbase64ToByteArraynow have one definition each.Verification
public_keyunchanged;secrets.encmoved to the DEK (the DEK opens it, the KEK no longer does); host password intact; team vault opens. A second device signed in afterwards also opens the team vault.public_keyis unchanged. The old linking device, upgraded and signed in with the new password, opened its vault with no recovery screen, kept its host password and opened the team vault. Its stale repair attempt was refused with 409.Known limits
/auth/meafter a 409;secrets_rekey, which reads the file outside its lock;key_mismatchwhen the roster fetch fails;Server: VoltiusApp/server#111 must be deployed first for repairs to happen. Without it the client works but stays on today's behaviour.