refactor(db): drop cleartext site/username column refs (columns removed)

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 <noreply@anthropic.com>
This commit is contained in:
r-zakarya
2026-07-09 21:25:35 +01:00
parent 6379772305
commit bcdd2f44bc
2 changed files with 10 additions and 20 deletions
+8 -20
View File
@@ -118,14 +118,9 @@ 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));
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); 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 if LQ.FieldByName('username_enc').IsNull then
LObj.AddPair('username_enc', TJSONNull.Create) LObj.AddPair('username_enc', TJSONNull.Create)
else else
@@ -404,20 +399,17 @@ 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, 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, ' + ' 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, tags, 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, :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, ' + ' :e, :i, ''client'', :f, :t, :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('s').AsString := LSite;
LQ.ParamByName('tt').AsString := LTitle; LQ.ParamByName('tt').AsString := LTitle;
LQ.ParamByName('u').AsString := LUser; // site/username cleartext columns dropped — only *_enc are written.
// 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.
BindNullable(LQ, 'uenc', LUserEnc); BindNullable(LQ, 'uenc', LUserEnc);
BindNullable(LQ, 'uiv', LUserIv); BindNullable(LQ, 'uiv', LUserIv);
BindNullable(LQ, 'senc', LSiteEnc); BindNullable(LQ, 'senc', LSiteEnc);
@@ -596,7 +588,7 @@ 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 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, ' + ' 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, ' +
@@ -607,9 +599,7 @@ begin
' 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('s').AsString := LSite;
LQ.ParamByName('tt').AsString := LTitle; LQ.ParamByName('tt').AsString := LTitle;
LQ.ParamByName('u').AsString := LUser;
BindNullable(LQ, 'uenc', LUserEnc); BindNullable(LQ, 'uenc', LUserEnc);
BindNullable(LQ, 'uiv', LUserIv); BindNullable(LQ, 'uiv', LUserIv);
BindNullable(LQ, 'senc', LSiteEnc); BindNullable(LQ, 'senc', LSiteEnc);
@@ -1243,12 +1233,12 @@ 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, 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, ' + ' 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, tags, 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, :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, ' + ' :e, :i, ''client'', :f, :t, :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
@@ -1316,9 +1306,7 @@ 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('s').AsString := LSite;
LQ.ParamByName('tt').AsString := LTitle; LQ.ParamByName('tt').AsString := LTitle;
LQ.ParamByName('u').AsString := LUser;
BindNullable(LQ, 'uenc', LUserEnc); BindNullable(LQ, 'uenc', LUserEnc);
BindNullable(LQ, 'uiv', LUserIv); BindNullable(LQ, 'uiv', LUserIv);
BindNullable(LQ, 'senc', LSiteEnc); BindNullable(LQ, 'senc', LSiteEnc);
+2
View File
@@ -1482,6 +1482,8 @@ async function decryptEntryMeta(list) {
if (enc && iv) { if (enc && iv) {
const v = await decryptPwd(enc, iv); const v = await decryptPwd(enc, iv);
if (v !== '[ERROR]') e[f] = v; if (v !== '[ERROR]') e[f] = v;
} else if (e[f] == null) {
e[f] = ''; // site/username cleartext columns dropped → default
} }
} }
} }