fix(android): locking the vault no longer discards the typed settings
Reported as "the token field saves nothing", and it was exact: LockVault called PushConfig, which pushes the STORED server settings back into the page. Every lock therefore overwrote a freshly typed token with the old one - including the three-minute auto-lock, so it happened with no gesture from the user at all, and the field looked like it forgot on its own. The user's own sequence isolated it: deleting one character gave a 401, which proves the typed value does reach the wire; then open, lock, sync gave a 200 again, the lock having restored the old token in between. PushConfig now runs at startup only. It was pointless in the other four places anyway: PM.wipe clears the list and the detail, never the configuration fields. Along the way, and part of the same confusion: the three settings fields had no labels, only placeholders that vanish once filled - and since the token was masked this morning it became indistinguishable from the sync password sitting right below it. They are labelled now, and the log reports both fingerprints so a value landing in the neighbouring field shows up immediately. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -186,6 +186,8 @@
|
|||||||
font: inherit; outline: none;
|
font: inherit; outline: none;
|
||||||
}
|
}
|
||||||
.field-in::placeholder { color: var(--muted); }
|
.field-in::placeholder { color: var(--muted); }
|
||||||
|
.in-label { color: var(--muted); font-size: 12px; margin: 10px 0 -2px 4px; }
|
||||||
|
.in-label span { opacity: .7; }
|
||||||
#log {
|
#log {
|
||||||
margin: 10px 0 0; padding: 10px 12px; max-height: 34vh; overflow: auto;
|
margin: 10px 0 0; padding: 10px 12px; max-height: 34vh; overflow: auto;
|
||||||
background: rgba(0,0,0,.20); border-radius: var(--radius-s);
|
background: rgba(0,0,0,.20); border-radius: var(--radius-s);
|
||||||
@@ -219,7 +221,13 @@
|
|||||||
depot, jamais dans le coffre. -->
|
depot, jamais dans le coffre. -->
|
||||||
<button class="btn wide" id="btnWriteTest" hidden>Write test (debug)</button>
|
<button class="btn wide" id="btnWriteTest" hidden>Write test (debug)</button>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- DES LIBELLES, PAS SEULEMENT DES INDICATIONS D'AIDE. Celles-ci
|
||||||
|
disparaissent des que le champ est rempli, et depuis que le jeton est
|
||||||
|
masque comme le mot de passe, deux champs en points noirs se
|
||||||
|
succedent sans rien qui les distingue - on colle alors dans le mauvais
|
||||||
|
sans que rien ne le signale. -->
|
||||||
<div id="cfg" hidden>
|
<div id="cfg" hidden>
|
||||||
|
<div class="in-label">Endpoint URL</div>
|
||||||
<input class="field-in" id="url" placeholder="endpoint URL"
|
<input class="field-in" id="url" placeholder="endpoint URL"
|
||||||
autocomplete="off" autocorrect="off" autocapitalize="none"
|
autocomplete="off" autocorrect="off" autocapitalize="none"
|
||||||
spellcheck="false" inputmode="url">
|
spellcheck="false" inputmode="url">
|
||||||
@@ -227,8 +235,10 @@
|
|||||||
clair alors qu'il ouvre le depot : le moins grave des trois secrets
|
clair alors qu'il ouvre le depot : le moins grave des trois secrets
|
||||||
de cet ecran, mais le seul qu'un regard par-dessus l'epaule
|
de cet ecran, mais le seul qu'un regard par-dessus l'epaule
|
||||||
attrapait. On le saisit une fois ; le relire n'a aucun usage. -->
|
attrapait. On le saisit une fois ; le relire n'a aucun usage. -->
|
||||||
|
<div class="in-label">Gitea token <span>· needs read AND write</span></div>
|
||||||
<input class="field-in" id="tok" placeholder="bearer token"
|
<input class="field-in" id="tok" placeholder="bearer token"
|
||||||
type="password" autocomplete="off">
|
type="password" autocomplete="off">
|
||||||
|
<div class="in-label">Sync password <span>· decrypts the vault</span></div>
|
||||||
<input class="field-in" id="pwd" placeholder="sync password"
|
<input class="field-in" id="pwd" placeholder="sync password"
|
||||||
type="password" autocomplete="off">
|
type="password" autocomplete="off">
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+32
-8
@@ -32,7 +32,7 @@ interface
|
|||||||
uses
|
uses
|
||||||
System.SysUtils, System.Types, System.UITypes, System.Classes,
|
System.SysUtils, System.Types, System.UITypes, System.Classes,
|
||||||
System.Diagnostics, System.Rtti, System.NetEncoding, System.DateUtils,
|
System.Diagnostics, System.Rtti, System.NetEncoding, System.DateUtils,
|
||||||
System.Generics.Collections, System.IOUtils,
|
System.Generics.Collections, System.IOUtils, System.Hash,
|
||||||
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Platform,
|
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Platform,
|
||||||
FMX.Controls.Presentation, FMX.StdCtrls, FMX.Memo, FMX.Memo.Types,
|
FMX.Controls.Presentation, FMX.StdCtrls, FMX.Memo, FMX.Memo.Types,
|
||||||
FMX.ScrollBox, FMX.Layouts, FMX.Edit, FMX.ListBox, FMX.Objects,
|
FMX.ScrollBox, FMX.Layouts, FMX.Edit, FMX.ListBox, FMX.Objects,
|
||||||
@@ -418,6 +418,14 @@ begin
|
|||||||
SetLength(FWebPending, 0);
|
SetLength(FWebPending, 0);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
/// Empreinte courte d'un secret, pour le journal. On veut savoir LEQUEL des
|
||||||
|
/// jetons est en place, jamais sa valeur : huit caracteres de son SHA-256
|
||||||
|
/// suffisent a les distinguer et n'en revelent rien.
|
||||||
|
function Fingerprint(const ASecret: string): string;
|
||||||
|
begin
|
||||||
|
if ASecret = '' then Exit('(vide)');
|
||||||
|
Result := Copy(THashSHA2.GetHashString(ASecret), 1, 8);
|
||||||
|
end;
|
||||||
/// Chiffre un petit conteneur, le relit, et le laisse sur le disque prive pour
|
/// Chiffre un petit conteneur, le relit, et le laisse sur le disque prive pour
|
||||||
/// qu'une autre implementation le verifie. Iterations volontairement basses :
|
/// qu'une autre implementation le verifie. Iterations volontairement basses :
|
||||||
/// ce qu'on eprouve ici est le CABLAGE - cle, IV, place du tag - pas le cout
|
/// ce qu'on eprouve ici est le CABLAGE - cle, IV, place du tag - pas le cout
|
||||||
@@ -496,8 +504,8 @@ begin
|
|||||||
TTimeZone.Local.ToUniversalTime(Now))]),
|
TTimeZone.Local.ToUniversalTime(Now))]),
|
||||||
FSyncPwd, 1000);
|
FSyncPwd, 1000);
|
||||||
Log('PUT ' + LUrl);
|
Log('PUT ' + LUrl);
|
||||||
Log(' token ' + IntToStr(Length(FToken)) + ' chars, body ' +
|
Log(Format(' token %s (%d chars), body %d chars',
|
||||||
IntToStr(Length(LBody)) + ' chars');
|
[Fingerprint(FToken), Length(FToken), Length(LBody)]));
|
||||||
// Sha vide : on cree le fichier, ou on echoue en 409 s'il existe deja -
|
// Sha vide : on cree le fichier, ou on echoue en 409 s'il existe deja -
|
||||||
// ce qui prouverait le controle de concurrence aussi surement.
|
// ce qui prouverait le controle de concurrence aussi surement.
|
||||||
LRes := LClient.Put(LBody, '', 'write path probe from phone');
|
LRes := LClient.Put(LBody, '', 'write path probe from phone');
|
||||||
@@ -528,6 +536,8 @@ begin
|
|||||||
FUrl := LUrl;
|
FUrl := LUrl;
|
||||||
if LToken <> '' then FToken := LToken;
|
if LToken <> '' then FToken := LToken;
|
||||||
end;
|
end;
|
||||||
|
Log(Format('config loaded: token %s (%d chars), url %s',
|
||||||
|
[Fingerprint(FToken), Length(FToken), FUrl]));
|
||||||
WebCall(Format('PM.setConfig(%s, %s, %s, %s)',
|
WebCall(Format('PM.setConfig(%s, %s, %s, %s)',
|
||||||
[JsArg(FUrl), JsArg(FToken), JsArg(FPwd),
|
[JsArg(FUrl), JsArg(FToken), JsArg(FPwd),
|
||||||
// Replier des champs vides revient a cacher la seule chose a faire.
|
// Replier des champs vides revient a cacher la seule chose a faire.
|
||||||
@@ -560,7 +570,16 @@ end;
|
|||||||
procedure TForm8.OnWebNavigate(ASender: TObject; const AUrl: string);
|
procedure TForm8.OnWebNavigate(ASender: TObject; const AUrl: string);
|
||||||
begin
|
begin
|
||||||
// Le moteur navigue aussi pour charger la page elle-meme.
|
// Le moteur navigue aussi pour charger la page elle-meme.
|
||||||
if AUrl.StartsWith('cmd://') then HandleWebCommand(AUrl.Substring(6));
|
// L'ACTION SEULE, jamais la requete : elle porte le jeton et le mot de passe.
|
||||||
|
// Sans cette trace on ne peut pas distinguer « la commande n'arrive pas » de
|
||||||
|
// « elle arrive et ne fait rien », et les deux se ressemblent de l'exterieur.
|
||||||
|
if AUrl.StartsWith('cmd://') then
|
||||||
|
begin
|
||||||
|
var LDbg := AUrl.Substring(6);
|
||||||
|
if LDbg.Contains('?') then LDbg := LDbg.Substring(0, LDbg.IndexOf('?')) + '?...';
|
||||||
|
Log('cmd ' + LDbg);
|
||||||
|
HandleWebCommand(AUrl.Substring(6));
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TForm8.HandleWebCommand(const AUrl: string);
|
procedure TForm8.HandleWebCommand(const AUrl: string);
|
||||||
@@ -613,6 +632,11 @@ begin
|
|||||||
FUrl := TNetEncoding.URL.Decode(Param('u')).Trim;
|
FUrl := TNetEncoding.URL.Decode(Param('u')).Trim;
|
||||||
FToken := TNetEncoding.URL.Decode(Param('t')).Trim;
|
FToken := TNetEncoding.URL.Decode(Param('t')).Trim;
|
||||||
FPwd := TNetEncoding.URL.Decode(Param('p'));
|
FPwd := TNetEncoding.URL.Decode(Param('p'));
|
||||||
|
// L'empreinte du mot de passe AUSSI : si une saisie destinee au jeton
|
||||||
|
// atterrit dans le champ voisin - les deux sont masques - c'est celle-ci
|
||||||
|
// qui bouge, et le jeton reste inchange sans que rien ne l'explique.
|
||||||
|
Log(Format('config from page: token %s, pwd %s',
|
||||||
|
[Fingerprint(FToken), Fingerprint(FPwd)]));
|
||||||
end
|
end
|
||||||
|
|
||||||
else if LAction = 'openvault' then
|
else if LAction = 'openvault' then
|
||||||
@@ -678,7 +702,10 @@ begin
|
|||||||
// Les lignes du detail portent des valeurs EN CLAIR (FRows) : les detruire
|
// Les lignes du detail portent des valeurs EN CLAIR (FRows) : les detruire
|
||||||
// fait partie du verrouillage, pas de l'affichage.
|
// fait partie du verrouillage, pas de l'affichage.
|
||||||
FCurrent := Default(TVaultEntry);
|
FCurrent := Default(TVaultEntry);
|
||||||
PushConfig;
|
// PAS DE PushConfig HORS DEMARRAGE. Redescendre les coordonnees du serveur
|
||||||
|
// ecrase ce que l'utilisateur vient de taper - l'auto-verrouillage le faisait
|
||||||
|
// donc sans le moindre geste de sa part, et le champ jeton semblait ne rien
|
||||||
|
// retenir. Verrouiller efface le clair ; la configuration n'a rien a y voir.
|
||||||
ShowUnlock;
|
ShowUnlock;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -775,7 +802,6 @@ begin
|
|||||||
// Un coffre local illisible se remplace, il ne se repare pas.
|
// Un coffre local illisible se remplace, il ne se repare pas.
|
||||||
Log('Local vault corrupt: ' + E.Message);
|
Log('Local vault corrupt: ' + E.Message);
|
||||||
ClearVault;
|
ClearVault;
|
||||||
PushConfig;
|
|
||||||
ShowUnlock;
|
ShowUnlock;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
@@ -785,7 +811,6 @@ begin
|
|||||||
// ShowUnlock AVANT le message : il repositionne le statut selon ce que
|
// ShowUnlock AVANT le message : il repositionne le statut selon ce que
|
||||||
// l'appareil detient, et ecraserait donc l'explication.
|
// l'appareil detient, et ecraserait donc l'explication.
|
||||||
FVaultUnreadable := True;
|
FVaultUnreadable := True;
|
||||||
PushConfig;
|
|
||||||
ShowUnlock;
|
ShowUnlock;
|
||||||
Log('Local vault unreadable: ' + LastStoreError);
|
Log('Local vault unreadable: ' + LastStoreError);
|
||||||
Status('Local vault unreadable - see log');
|
Status('Local vault unreadable - see log');
|
||||||
@@ -841,7 +866,6 @@ begin
|
|||||||
FVaultUnreadable := True;
|
FVaultUnreadable := True;
|
||||||
Log('No usable protection on this device - vault kept but unreadable');
|
Log('No usable protection on this device - vault kept but unreadable');
|
||||||
Log(' ' + LastBiometricError);
|
Log(' ' + LastBiometricError);
|
||||||
PushConfig;
|
|
||||||
ShowUnlock;
|
ShowUnlock;
|
||||||
Status('Screen lock missing - download again to replace the cache');
|
Status('Screen lock missing - download again to replace the cache');
|
||||||
end;
|
end;
|
||||||
|
|||||||
Reference in New Issue
Block a user