From 91ed03d5149729c5d272fcc923d2f0e132895011 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Wed, 29 Jul 2026 03:09:12 +0000 Subject: [PATCH] vault: pass full connect/token crypto through session/login Vaultwarden (2025.12.0/f21a3ada) returns both classic (Key/PrivateKey/Kdf*) and v2 crypto (UserDecryptionOptions.MasterPasswordUnlock, AccountKeys) synthesized from the classic stored fields. session/login now returns the whole native connect/token response minus the transport tokens (under `connectToken`), alongside the spec's protectedUserKey/privateKey/kdf aliases, so the SDK gets whatever unlock path it uses. Temp diagnostic logs the response field NAMES (values redacted) to empirically confirm on a real login. Co-Authored-By: Claude Opus 4.8 --- src/servers/api/vault/router.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/servers/api/vault/router.ts b/src/servers/api/vault/router.ts index 4fe9af78..b3833069 100644 --- a/src/servers/api/vault/router.ts +++ b/src/servers/api/vault/router.ts @@ -83,7 +83,19 @@ vaultRouter.post('/session/login', async (ctx) => { clientId, }); - // Ciphertext to us — passed straight to the client, which decrypts on-device with the master key. + // TEMP diagnostic — field NAMES only (values redacted) to empirically confirm the crypto shape. + const names = (o: unknown) => (o && typeof o === 'object' ? Object.keys(o as object) : typeof o); + const udo = (d as { UserDecryptionOptions?: { MasterPasswordUnlock?: unknown } }).UserDecryptionOptions; + console.log( + `[vault] connect/token fields: ${Object.keys(d).join(',')} | UserDecryptionOptions=${JSON.stringify(names(udo))} | MasterPasswordUnlock=${JSON.stringify(names(udo?.MasterPasswordUnlock))} | AccountKeys=${JSON.stringify(names((d as { AccountKeys?: unknown }).AccountKeys))}`, + ); + + // Everything except the transport tokens (which the platform holds) is crypto material the SDK unlocks + // on-device — ciphertext to us. Vaultwarden returns BOTH classic (Key/PrivateKey/Kdf*) and v2 + // (UserDecryptionOptions.MasterPasswordUnlock, AccountKeys); pass the whole native response through + // under `connectToken` so the SDK gets whatever it uses, alongside the spec's named aliases. + const connectToken: Record = { ...d }; + for (const k of ['access_token', 'refresh_token', 'token_type', 'expires_in']) delete connectToken[k]; return ctx.json({ ok: true, protectedUserKey: pick(d, 'Key') ?? null, @@ -94,6 +106,7 @@ vaultRouter.post('/session/login', async (ctx) => { kdfMemory: pick(d, 'KdfMemory') ?? null, kdfParallelism: pick(d, 'KdfParallelism') ?? null, }, + connectToken, }); });