refactor(db): drop cleartext title/tags column refs (columns removed)

User dropped the title + tags cleartext columns too — all four searchable
metadata columns are now gone. Removed their refs from GET emission and the
POST/PUT/bulk INSERT/UPDATE (columns + params + binds). Only *_enc columns
remain; the client reads everything via decryptEntryMeta.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
r-zakarya
2026-07-09 21:29:06 +01:00
parent bcdd2f44bc
commit 94ebc96d73
+14 -21
View File
@@ -118,9 +118,8 @@ begin
begin begin
LObj := TJSONObject.Create; LObj := TJSONObject.Create;
LObj.AddPair('id', TJSONNumber.Create(LQ.FieldByName('id').AsInteger)); LObj.AddPair('id', TJSONNumber.Create(LQ.FieldByName('id').AsInteger));
// site + username cleartext columns were dropped — client reads them // site/username/title/tags cleartext columns dropped — the client
// from *_enc only (decryptEntryMeta). title/tags cleartext still exist. // reads them from *_enc only (decryptEntryMeta).
LObj.AddPair('title', LQ.FieldByName('title').AsString);
if LQ.FieldByName('username_enc').IsNull then if LQ.FieldByName('username_enc').IsNull then
LObj.AddPair('username_enc', TJSONNull.Create) LObj.AddPair('username_enc', TJSONNull.Create)
else else
@@ -148,7 +147,6 @@ begin
LObj.AddPair('deleted_at', ISODateTimeField(LQ.FieldByName('deleted_at'))); LObj.AddPair('deleted_at', ISODateTimeField(LQ.FieldByName('deleted_at')));
LObj.AddPair('favorite', TJSONNumber.Create(LQ.FieldByName('favorite').AsInteger)); LObj.AddPair('favorite', TJSONNumber.Create(LQ.FieldByName('favorite').AsInteger));
LObj.AddPair('pinned', TJSONNumber.Create(LQ.FieldByName('pinned').AsInteger)); LObj.AddPair('pinned', TJSONNumber.Create(LQ.FieldByName('pinned').AsInteger));
LObj.AddPair('tags', LQ.FieldByName('tags').AsString);
// TOTP fields are NULL when the entry has no 2FA configured. We emit // TOTP fields are NULL when the entry has no 2FA configured. We emit
// JSON null instead of '' so the client can distinguish "no TOTP" from // JSON null instead of '' so the client can distinguish "no TOTP" from
// "TOTP configured with empty ciphertext" (which shouldn't happen). // "TOTP configured with empty ciphertext" (which shouldn't happen).
@@ -399,17 +397,16 @@ begin
LQ.Connection := DB.Connection; LQ.Connection := DB.Connection;
LQ.SQL.Text := LQ.SQL.Text :=
'INSERT INTO vault_entries ' + 'INSERT INTO vault_entries ' +
'(user_id, title, username_enc, username_iv, ' + '(user_id, username_enc, username_iv, ' +
' site_enc, site_iv, title_enc, title_iv, tags_enc, tags_iv, ' + ' site_enc, site_iv, title_enc, title_iv, tags_enc, tags_iv, ' +
' encrypted_password, iv, encryption_method, ' + ' encrypted_password, iv, encryption_method, ' +
' folder, tags, totp_secret, totp_iv, kind, custom_fields, custom_fields_iv,' + ' folder, totp_secret, totp_iv, kind, custom_fields, custom_fields_iv,' +
' icon_b64, template, uuid, created_at, updated_at, password_changed_at) ' + ' icon_b64, template, uuid, created_at, updated_at, password_changed_at) ' +
'VALUES (:uid, :tt, :uenc, :uiv, :senc, :siv, :tenc, :tiv2, :genc, :giv, ' + 'VALUES (:uid, :uenc, :uiv, :senc, :siv, :tenc, :tiv2, :genc, :giv, ' +
' :e, :i, ''client'', :f, :t, :ts, :tiv, :k, ' + ' :e, :i, ''client'', :f, :ts, :tiv, :k, ' +
' :cf, :cfiv, :ic, :tpl, :uuid, :c, :c2, :c)'; ' :cf, :cfiv, :ic, :tpl, :uuid, :c, :c2, :c)';
LQ.ParamByName('uid').AsInteger := LUserId; LQ.ParamByName('uid').AsInteger := LUserId;
LQ.ParamByName('tt').AsString := LTitle; // site/username/title/tags cleartext columns dropped — only *_enc written.
// site/username cleartext columns dropped — only *_enc are written.
BindNullable(LQ, 'uenc', LUserEnc); BindNullable(LQ, 'uenc', LUserEnc);
BindNullable(LQ, 'uiv', LUserIv); BindNullable(LQ, 'uiv', LUserIv);
BindNullable(LQ, 'senc', LSiteEnc); BindNullable(LQ, 'senc', LSiteEnc);
@@ -421,7 +418,6 @@ begin
LQ.ParamByName('e').AsString := LEnc; LQ.ParamByName('e').AsString := LEnc;
LQ.ParamByName('i').AsString := LIV; LQ.ParamByName('i').AsString := LIV;
LQ.ParamByName('f').AsString := LFolder; LQ.ParamByName('f').AsString := LFolder;
LQ.ParamByName('t').AsString := LTags;
// FireDAC needs an explicit DataType on params that are sometimes // FireDAC needs an explicit DataType on params that are sometimes
// assigned a string and sometimes Clear()ed to NULL — without a // assigned a string and sometimes Clear()ed to NULL — without a
// prior typed assignment, .Clear raises "data type unknown" on // prior typed assignment, .Clear raises "data type unknown" on
@@ -588,18 +584,18 @@ begin
if LHasTemplate then LTemplateSet := ', template=:tpl'; if LHasTemplate then LTemplateSet := ', template=:tpl';
LQ.SQL.Text := LQ.SQL.Text :=
'UPDATE vault_entries ' + 'UPDATE vault_entries ' +
'SET title=:tt, username_enc=:uenc, username_iv=:uiv, ' + 'SET username_enc=:uenc, username_iv=:uiv, ' +
' site_enc=:senc, site_iv=:siv, title_enc=:tenc, title_iv=:tiv2, ' + ' site_enc=:senc, site_iv=:siv, title_enc=:tenc, title_iv=:tiv2, ' +
' tags_enc=:genc, tags_iv=:giv, ' + ' tags_enc=:genc, tags_iv=:giv, ' +
' encrypted_password=:e, iv=:i, ' + ' encrypted_password=:e, iv=:i, ' +
' folder=:f, tags=:t, totp_secret=:ts, totp_iv=:tiv, kind=:k, ' + ' folder=:f, totp_secret=:ts, totp_iv=:tiv, kind=:k, ' +
' custom_fields=:cf, custom_fields_iv=:cfiv, ' + ' custom_fields=:cf, custom_fields_iv=:cfiv, ' +
' updated_at=:c, ' + ' updated_at=:c, ' +
' password_changed_at = CASE WHEN encrypted_password <> :e ' + ' password_changed_at = CASE WHEN encrypted_password <> :e ' +
' THEN :c ELSE password_changed_at END' + ' THEN :c ELSE password_changed_at END' +
LTemplateSet + ' ' + LTemplateSet + ' ' +
'WHERE id=:id AND user_id=:uid'; 'WHERE id=:id AND user_id=:uid';
LQ.ParamByName('tt').AsString := LTitle; // site/username/title/tags cleartext columns dropped — only *_enc written.
BindNullable(LQ, 'uenc', LUserEnc); BindNullable(LQ, 'uenc', LUserEnc);
BindNullable(LQ, 'uiv', LUserIv); BindNullable(LQ, 'uiv', LUserIv);
BindNullable(LQ, 'senc', LSiteEnc); BindNullable(LQ, 'senc', LSiteEnc);
@@ -611,7 +607,6 @@ begin
LQ.ParamByName('e').AsString := LEnc; LQ.ParamByName('e').AsString := LEnc;
LQ.ParamByName('i').AsString := LIV; LQ.ParamByName('i').AsString := LIV;
LQ.ParamByName('f').AsString := LFolder; LQ.ParamByName('f').AsString := LFolder;
LQ.ParamByName('t').AsString := LTags;
// Declare TOTP param types so .Clear works on first use (FireDAC // Declare TOTP param types so .Clear works on first use (FireDAC
// needs an inferred or explicit DataType before NULL binding). // needs an inferred or explicit DataType before NULL binding).
LQ.ParamByName('ts').DataType := ftMemo; LQ.ParamByName('ts').DataType := ftMemo;
@@ -1233,13 +1228,13 @@ begin
'WHERE user_id = :uid AND uuid = :uuid'; 'WHERE user_id = :uid AND uuid = :uuid';
LQ.SQL.Text := LQ.SQL.Text :=
'INSERT INTO vault_entries ' + 'INSERT INTO vault_entries ' +
'(user_id, title, username_enc, username_iv, ' + '(user_id, username_enc, username_iv, ' +
' site_enc, site_iv, title_enc, title_iv, tags_enc, tags_iv, ' + ' site_enc, site_iv, title_enc, title_iv, tags_enc, tags_iv, ' +
' encrypted_password, iv, encryption_method, ' + ' encrypted_password, iv, encryption_method, ' +
' folder, tags, totp_secret, totp_iv, kind, custom_fields, custom_fields_iv,' + ' folder, totp_secret, totp_iv, kind, custom_fields, custom_fields_iv,' +
' icon_b64, template, uuid, created_at, updated_at) ' + ' icon_b64, template, uuid, created_at, updated_at) ' +
'VALUES (:uid, :tt, :uenc, :uiv, :senc, :siv, :tenc, :tiv2, :genc, :giv, ' + 'VALUES (:uid, :uenc, :uiv, :senc, :siv, :tenc, :tiv2, :genc, :giv, ' +
' :e, :i, ''client'', :f, :t, :ts, :tiv, :k, ' + ' :e, :i, ''client'', :f, :ts, :tiv, :k, ' +
' :cf, :cfiv, :ic, :tpl, :uuid, :c, :c2)'; ' :cf, :cfiv, :ic, :tpl, :uuid, :c, :c2)';
// Declare optional param types ONCE — the prepared statement is // Declare optional param types ONCE — the prepared statement is
// reused across every imported entry, and FireDAC needs the // reused across every imported entry, and FireDAC needs the
@@ -1306,7 +1301,6 @@ begin
// row carries site_enc); the client validated before import. // row carries site_enc); the client validated before import.
LQ.ParamByName('uid').AsInteger := LUserId; LQ.ParamByName('uid').AsInteger := LUserId;
LQ.ParamByName('tt').AsString := LTitle;
BindNullable(LQ, 'uenc', LUserEnc); BindNullable(LQ, 'uenc', LUserEnc);
BindNullable(LQ, 'uiv', LUserIv); BindNullable(LQ, 'uiv', LUserIv);
BindNullable(LQ, 'senc', LSiteEnc); BindNullable(LQ, 'senc', LSiteEnc);
@@ -1318,7 +1312,6 @@ begin
LQ.ParamByName('e').AsString := LEnc; LQ.ParamByName('e').AsString := LEnc;
LQ.ParamByName('i').AsString := LIV; LQ.ParamByName('i').AsString := LIV;
LQ.ParamByName('f').AsString := LFolder; LQ.ParamByName('f').AsString := LFolder;
LQ.ParamByName('t').AsString := LTags;
if LTotpSec = '' then LQ.ParamByName('ts').Clear if LTotpSec = '' then LQ.ParamByName('ts').Clear
else LQ.ParamByName('ts').Value := LTotpSec; else LQ.ParamByName('ts').Value := LTotpSec;
if LTotpIv = '' then LQ.ParamByName('tiv').Clear if LTotpIv = '' then LQ.ParamByName('tiv').Clear