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:
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user