fix(shared): legacy conv-key migration query used .eq(null) instead of .is(null)

PostgREST translates .eq('col', null) to `col = NULL` which is always false
in SQL. The migration silently returned zero rows -> setupNewUserIdentity
fired but re-wrapped nothing -> users could set a PIN but every send threw
'Awaiting key'. Switching to .is('col', null) emits `col IS NULL` and the
migration finally finds its work.

Also makes the migration trigger idempotent and re-fires it on:
  - every successful loadOrUnlockUserKey
  - AuthContext startup when the user-key is already cached
so users stuck on 0.18.0 auto-recover the moment they install 0.18.1.

PinInput: focused + active-slot now show a brand-coloured ring, glow, and
a blinking caret so users see where the next keystroke lands.
This commit is contained in:
byGalax
2026-05-16 00:15:15 +02:00
parent d9377fc52a
commit 6caa674c19
5 changed files with 54 additions and 13 deletions
+16 -1
View File
@@ -36,12 +36,24 @@ export async function setupNewUserIdentity(p: SetupParams): Promise<SetupResult>
recoverySalt: recoverySealed?.salt ?? null,
});
await devLocalSecretStore.setSecret(cacheKey(p.userId), kp.privateKey);
void runLegacyMigration(p.userId, kp.privateKey, kp.publicKey).catch((err) => {
void ensureLegacyMigrated(p.userId).catch((err) => {
console.warn('legacy conv-key migration failed', err);
});
return { publicKey: kp.publicKey, recoveryCode };
}
// Background, idempotent re-wrap of own legacy conv-key bundles for the new
// per-user identity. Safe to call repeatedly: the underlying RPC uses
// ON CONFLICT DO NOTHING. Triggered on every successful unlock so users who
// upgraded to 0.18.0 (where the .eq(null) bug made setup-time migration a
// no-op) auto-recover on the next launch.
export async function ensureLegacyMigrated(userId: string): Promise<void> {
const priv = await devLocalSecretStore.getSecret(cacheKey(userId));
if (!priv) return;
const pub = await derivePublicKey(priv);
await runLegacyMigration(userId, priv, pub);
}
export interface UnlockParams { userId: string; pin: string; isRecoveryCode?: boolean }
export type UnlockOutcome =
@@ -66,6 +78,9 @@ export async function loadOrUnlockUserKey(p: UnlockParams): Promise<UnlockOutcom
}
await recordPinAttempt(supabase, p.userId, true, p.isRecoveryCode === true).catch(() => {});
await devLocalSecretStore.setSecret(cacheKey(p.userId), priv);
void ensureLegacyMigrated(p.userId).catch((err) => {
console.warn('legacy conv-key migration failed', err);
});
return { kind: 'unlocked' };
}