fix(db): declare TOTP param DataType so .Clear works on first row

FireDAC raises EFDException -335 "data type unknown" when .Clear is
called on a TFDParam before any typed value has been assigned. Hit
in PM.Handler.Auth.HandleChangeMasterPassword when the first entry
in the migration loop had no TOTP secret — already fixed inline.

Same latent bug existed in every other handler that touches the
optional totp_secret / totp_iv columns:
 - HandleCreateEntry      (Entries.pas)
 - HandleUpdateEntry      (Entries.pas)
 - HandleBulkImport       (Entries.pas)

All three now declare:
  LQ.ParamByName('ts').DataType  := ftString;
  LQ.ParamByName('tiv').DataType := ftString;
right after setting SQL.Text, so the very first .Clear (when an
entry has no TOTP) doesn't fail with "data type unknown" on the
SQLite param binding path.

For HandleBulkImport the declaration is hoisted out of the per-entry
loop since the prepared statement is reused across iterations.
This commit is contained in:
2026-05-23 16:17:01 +01:00
parent d13e5bc89f
commit 664db65437
2 changed files with 24 additions and 4 deletions
+8 -4
View File
@@ -1,4 +1,4 @@
unit PM.Handler.Auth;
unit PM.Handler.Auth;
(*
/register POST body {username, masterPassword} -> {message,token,userId,salt,csrfToken}
@@ -21,7 +21,7 @@ implementation
uses
System.SysUtils, System.JSON, System.Classes,
FireDAC.Comp.Client,
IdCustomHTTPServer,
IdCustomHTTPServer,Data.DB,
PM.Router, PM.JSON, PM.Database, PM.Crypto,
PM.Session, PM.RateLimit, PM.Audit;
@@ -938,8 +938,12 @@ begin
LQ.ParamByName('iv').AsString := LIv;
// TOTP fields are optional per entry — clear when empty so
// existing-NULL rows don't get stomped with empty strings.
if LTotpSec = '' then LQ.ParamByName('ts').Clear
else LQ.ParamByName('ts').AsString := LTotpSec;
LQ.ParamByName('ts').DataType := ftString;
LQ.ParamByName('tiv').DataType := ftString;
if LTotpSec.IsEmpty then
LQ.ParamByName('ts').Clear
else
LQ.ParamByName('ts').AsString := LTotpSec;
if LTotpIv = '' then LQ.ParamByName('tiv').Clear
else LQ.ParamByName('tiv').AsString := LTotpIv;
LQ.ExecSQL;