From bcdd2f44bc6712dabfdfdf386e875cdf896982e7 Mon Sep 17 00:00:00 2001 From: r-zakarya <82443831+r-zakarya@users.noreply.github.com> Date: Thu, 9 Jul 2026 21:25:35 +0100 Subject: [PATCH] refactor(db): drop cleartext site/username column refs (columns removed) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User dropped the now-empty cleartext `site` + `username` columns after the §1.3 migration completed. Removed every reference so the code matches the schema: GET emission, POST/PUT/bulk INSERT/UPDATE (columns + params + binds). title/tags cleartext columns still exist and are untouched. decryptEntryMeta defaults e[f]='' for rows without *_enc (notes w/o site), since GET no longer returns the dropped columns. ponytail: contract phase of expand→migrate→contract; only safe because the migration is proven complete (0 cleartext, both accounts). Co-Authored-By: Claude Opus 4.8 --- .../Handlers/PM.Handler.Entries.pas | 28 ++++++------------- js/app.js | 2 ++ 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/delphi-backend/Handlers/PM.Handler.Entries.pas b/delphi-backend/Handlers/PM.Handler.Entries.pas index af612e9..f412029 100644 --- a/delphi-backend/Handlers/PM.Handler.Entries.pas +++ b/delphi-backend/Handlers/PM.Handler.Entries.pas @@ -118,14 +118,9 @@ begin begin LObj := TJSONObject.Create; LObj.AddPair('id', TJSONNumber.Create(LQ.FieldByName('id').AsInteger)); - LObj.AddPair('site', LQ.FieldByName('site').AsString); + // site + username cleartext columns were dropped — client reads them + // from *_enc only (decryptEntryMeta). title/tags cleartext still exist. LObj.AddPair('title', LQ.FieldByName('title').AsString); - // Cleartext username: '' for migrated rows (ciphertext lives in - // username_enc). Old rows still carry it until the client sweep. - LObj.AddPair('username', LQ.FieldByName('username').AsString); - // Encrypted username (AES-GCM under the vault key). NULL → JSON null - // so the client knows the row isn't migrated yet and falls back to - // the cleartext `username` above. if LQ.FieldByName('username_enc').IsNull then LObj.AddPair('username_enc', TJSONNull.Create) else @@ -404,20 +399,17 @@ begin LQ.Connection := DB.Connection; LQ.SQL.Text := 'INSERT INTO vault_entries ' + - '(user_id, site, title, username, username_enc, username_iv, ' + + '(user_id, title, username_enc, username_iv, ' + ' site_enc, site_iv, title_enc, title_iv, tags_enc, tags_iv, ' + ' encrypted_password, iv, encryption_method, ' + ' folder, tags, totp_secret, totp_iv, kind, custom_fields, custom_fields_iv,' + ' icon_b64, template, uuid, created_at, updated_at, password_changed_at) ' + - 'VALUES (:uid, :s, :tt, :u, :uenc, :uiv, :senc, :siv, :tenc, :tiv2, :genc, :giv, ' + + 'VALUES (:uid, :tt, :uenc, :uiv, :senc, :siv, :tenc, :tiv2, :genc, :giv, ' + ' :e, :i, ''client'', :f, :t, :ts, :tiv, :k, ' + ' :cf, :cfiv, :ic, :tpl, :uuid, :c, :c2, :c)'; LQ.ParamByName('uid').AsInteger := LUserId; - LQ.ParamByName('s').AsString := LSite; LQ.ParamByName('tt').AsString := LTitle; - LQ.ParamByName('u').AsString := LUser; - // Encrypted metadata: NULL when not supplied (pre-migration client or a - // row with no value) so GET emits JSON null and the client falls back. + // site/username cleartext columns dropped — only *_enc are written. BindNullable(LQ, 'uenc', LUserEnc); BindNullable(LQ, 'uiv', LUserIv); BindNullable(LQ, 'senc', LSiteEnc); @@ -596,7 +588,7 @@ begin if LHasTemplate then LTemplateSet := ', template=:tpl'; LQ.SQL.Text := 'UPDATE vault_entries ' + - 'SET site=:s, title=:tt, username=:u, username_enc=:uenc, username_iv=:uiv, ' + + 'SET title=:tt, username_enc=:uenc, username_iv=:uiv, ' + ' site_enc=:senc, site_iv=:siv, title_enc=:tenc, title_iv=:tiv2, ' + ' tags_enc=:genc, tags_iv=:giv, ' + ' encrypted_password=:e, iv=:i, ' + @@ -607,9 +599,7 @@ begin ' THEN :c ELSE password_changed_at END' + LTemplateSet + ' ' + 'WHERE id=:id AND user_id=:uid'; - LQ.ParamByName('s').AsString := LSite; LQ.ParamByName('tt').AsString := LTitle; - LQ.ParamByName('u').AsString := LUser; BindNullable(LQ, 'uenc', LUserEnc); BindNullable(LQ, 'uiv', LUserIv); BindNullable(LQ, 'senc', LSiteEnc); @@ -1243,12 +1233,12 @@ begin 'WHERE user_id = :uid AND uuid = :uuid'; LQ.SQL.Text := 'INSERT INTO vault_entries ' + - '(user_id, site, title, username, username_enc, username_iv, ' + + '(user_id, title, username_enc, username_iv, ' + ' site_enc, site_iv, title_enc, title_iv, tags_enc, tags_iv, ' + ' encrypted_password, iv, encryption_method, ' + ' folder, tags, totp_secret, totp_iv, kind, custom_fields, custom_fields_iv,' + ' icon_b64, template, uuid, created_at, updated_at) ' + - 'VALUES (:uid, :s, :tt, :u, :uenc, :uiv, :senc, :siv, :tenc, :tiv2, :genc, :giv, ' + + 'VALUES (:uid, :tt, :uenc, :uiv, :senc, :siv, :tenc, :tiv2, :genc, :giv, ' + ' :e, :i, ''client'', :f, :t, :ts, :tiv, :k, ' + ' :cf, :cfiv, :ic, :tpl, :uuid, :c, :c2)'; // Declare optional param types ONCE — the prepared statement is @@ -1316,9 +1306,7 @@ begin // row carries site_enc); the client validated before import. LQ.ParamByName('uid').AsInteger := LUserId; - LQ.ParamByName('s').AsString := LSite; LQ.ParamByName('tt').AsString := LTitle; - LQ.ParamByName('u').AsString := LUser; BindNullable(LQ, 'uenc', LUserEnc); BindNullable(LQ, 'uiv', LUserIv); BindNullable(LQ, 'senc', LSiteEnc); diff --git a/js/app.js b/js/app.js index bf23858..b2875be 100644 --- a/js/app.js +++ b/js/app.js @@ -1482,6 +1482,8 @@ async function decryptEntryMeta(list) { if (enc && iv) { const v = await decryptPwd(enc, iv); if (v !== '[ERROR]') e[f] = v; + } else if (e[f] == null) { + e[f] = ''; // site/username cleartext columns dropped → default } } }