fix(android): the unlock code is asked for, and never undone in silence
Two defects, one feature made useless. The cache did seal under the code, but nothing on the home screen asked for it: opening without one failed, the failure was read as corruption, and the device offered to download a vault it held perfectly well. And the next refresh rewrote the cache in the old shape, so the code was undone seconds after being set, silently. The code now lives on the home screen, not among the server settings: it seals this device's cache, while the other three fields describe the shared remote. Always visible - hiding it when none is set would remove the only place to set one - the hint saying which case you are in. An absent code is no longer attempted, a wrong one is no longer corruption, and a sealed cache is never rewritten without its code. The two panels each got an arrow, and only one opens at a time. Measured end to end on the device: version byte 1, code typed, open, byte 4; cold start says "Unlock code required" and open without the code leaves the byte at 4; correct code opens the vault, and the refresh that follows leaves it at 4.
This commit is contained in:
@@ -797,6 +797,22 @@ begin
|
|||||||
// ETag, mot de passe et coffre chiffres ENSEMBLE : ils doivent rester
|
// ETag, mot de passe et coffre chiffres ENSEMBLE : ils doivent rester
|
||||||
// coherents. Un ETag qui survivrait a un coffre illisible ferait repondre
|
// coherents. Un ETag qui survivrait a un coffre illisible ferait repondre
|
||||||
// 304 au serveur alors que l'application n'a rien a afficher.
|
// 304 au serveur alors que l'application n'a rien a afficher.
|
||||||
|
// UN CACHE SCELLE SOUS UN CODE NE SE REECRIT JAMAIS SANS LUI. Sans cette
|
||||||
|
// regle, le premier rafraichissement venu le reecrivait dans l'ancienne
|
||||||
|
// forme - le code etait pose, puis defait en silence quelques secondes plus
|
||||||
|
// tard, sans que rien ne le dise. Le refuser fait perdre un instantane, ce
|
||||||
|
// que la prochaine ouverture rattrape ; l'accepter fait perdre la
|
||||||
|
// protection, ce que rien ne rattrape.
|
||||||
|
//
|
||||||
|
// Retirer le code demanderait donc un geste explicite, qui n'existe pas
|
||||||
|
// encore : aujourd'hui on le remplace en le retapant.
|
||||||
|
if (APin = '') and VaultNeedsPin then
|
||||||
|
begin
|
||||||
|
FLastStoreError := 'cache sealed under an unlock code; refusing to ' +
|
||||||
|
'rewrite it without one';
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
|
||||||
LWrap := TJSONObject.Create;
|
LWrap := TJSONObject.Create;
|
||||||
try
|
try
|
||||||
LWrap.AddPair('etag', AETag);
|
LWrap.AddPair('etag', AETag);
|
||||||
|
|||||||
+60
-19
@@ -187,6 +187,16 @@
|
|||||||
font: inherit; outline: none;
|
font: inherit; outline: none;
|
||||||
}
|
}
|
||||||
.field-in::placeholder { color: var(--muted); }
|
.field-in::placeholder { color: var(--muted); }
|
||||||
|
/* La fleche dit si le panneau est deplie, et de quel bouton il descend. */
|
||||||
|
.chev { display: inline-block; margin-left: 6px; font-size: 11px;
|
||||||
|
opacity: .7; transition: transform .18s ease; }
|
||||||
|
.btn.open .chev { transform: rotate(180deg); }
|
||||||
|
/* Un panneau est visiblement ATTACHE au bouton qui l'ouvre : sans ce trait
|
||||||
|
et ce retrait, les reglages et le journal se ressemblaient trop pour
|
||||||
|
qu'on sache lequel etait deplie. */
|
||||||
|
.panel { margin-top: 8px; padding-left: 10px;
|
||||||
|
border-left: 2px solid rgba(255,255,255,.18); }
|
||||||
|
|
||||||
.in-wrap { position: relative; }
|
.in-wrap { position: relative; }
|
||||||
/* Le champ laisse la place a la croix, sinon le texte passe dessous. */
|
/* Le champ laisse la place a la croix, sinon le texte passe dessous. */
|
||||||
.in-wrap .field-in { padding-right: 44px; }
|
.in-wrap .field-in { padding-right: 44px; }
|
||||||
@@ -221,8 +231,24 @@
|
|||||||
<button class="btn wide main" id="btnOpen">➜ Open vault</button>
|
<button class="btn wide main" id="btnOpen">➜ Open vault</button>
|
||||||
<button class="btn wide" id="btnSync">↻ Sync</button>
|
<button class="btn wide" id="btnSync">↻ Sync</button>
|
||||||
<div class="pair">
|
<div class="pair">
|
||||||
<button class="btn" id="btnCfg">⚙ Server settings</button>
|
<button class="btn" id="btnCfg" data-panel="cfg">⚙ Server
|
||||||
<button class="btn" id="btnLog">≡ Log</button>
|
settings<span class="chev">▾</span></button>
|
||||||
|
<button class="btn" id="btnLog" data-panel="log">≡
|
||||||
|
Log<span class="chev">▾</span></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- LE CODE EST ICI, PAS DANS LES REGLAGES DU SERVEUR. Il ne concerne que
|
||||||
|
cet appareil - il scelle le cache local - alors que les trois autres
|
||||||
|
champs decrivent le depot distant, partage entre les appareils. Range
|
||||||
|
parmi eux, rien n'indiquait qu'il fallait le retaper, et un coffre
|
||||||
|
scelle proposait de se retelecharger plutot que de se laisser ouvrir.
|
||||||
|
JAMAIS PRE-REMPLI : le retenir reviendrait a ne rien proteger. -->
|
||||||
|
<div id="pinbox">
|
||||||
|
<div class="in-wrap">
|
||||||
|
<input class="field-in" id="pin" placeholder="unlock code"
|
||||||
|
type="password" inputmode="numeric" autocomplete="off">
|
||||||
|
<div class="in-x" data-clear="pin">×</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- UNE CROIX PAR CHAMP. Vider par la touche retour demande une quarantaine
|
<!-- UNE CROIX PAR CHAMP. Vider par la touche retour demande une quarantaine
|
||||||
@@ -233,7 +259,7 @@
|
|||||||
L'indication d'aide sert de libelle, et le jeton comme le mot de passe
|
L'indication d'aide sert de libelle, et le jeton comme le mot de passe
|
||||||
sont masques - le placeholder reste donc le seul moyen de les
|
sont masques - le placeholder reste donc le seul moyen de les
|
||||||
distinguer quand ils sont vides. -->
|
distinguer quand ils sont vides. -->
|
||||||
<div id="cfg" hidden>
|
<div id="cfg" class="panel" hidden>
|
||||||
<div class="in-wrap">
|
<div class="in-wrap">
|
||||||
<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"
|
||||||
@@ -258,16 +284,8 @@
|
|||||||
type="password" autocomplete="off">
|
type="password" autocomplete="off">
|
||||||
<div class="in-x" data-clear="pwd">×</div>
|
<div class="in-x" data-clear="pwd">×</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- JAMAIS PRE-REMPLI, contrairement aux trois autres : ce code protege
|
|
||||||
le cache local, et le retenir reviendrait a ne rien proteger. Il se
|
|
||||||
retape a chaque lancement. -->
|
|
||||||
<div class="in-wrap">
|
|
||||||
<input class="field-in" id="pin" placeholder="unlock code (local cache)"
|
|
||||||
type="password" inputmode="numeric" autocomplete="off">
|
|
||||||
<div class="in-x" data-clear="pin">×</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<pre id="log" hidden></pre>
|
<pre id="log" class="panel" hidden></pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -375,8 +393,11 @@ document.getElementById('btnSync').addEventListener('click', function () {
|
|||||||
pushConfig(); // ce qui est tape mais pas encore quitte compte aussi
|
pushConfig(); // ce qui est tape mais pas encore quitte compte aussi
|
||||||
send('sync');
|
send('sync');
|
||||||
});
|
});
|
||||||
// Un seul ecouteur pour les trois croix : elles ne different que par leur cible.
|
// Un seul ecouteur pour les quatre croix : elles ne different que par leur
|
||||||
document.getElementById('cfg').addEventListener('click', function (ev) {
|
// cible. Pose sur l'ECRAN, pas sur le groupe des reglages - le code a son
|
||||||
|
// propre bloc depuis qu'il n'appartient plus au serveur, et sa croix serait
|
||||||
|
// restee sans effet.
|
||||||
|
document.getElementById('unlock').addEventListener('click', function (ev) {
|
||||||
var id = ev.target.getAttribute('data-clear');
|
var id = ev.target.getAttribute('data-clear');
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
var el = document.getElementById(id);
|
var el = document.getElementById(id);
|
||||||
@@ -387,13 +408,21 @@ document.getElementById('cfg').addEventListener('click', function (ev) {
|
|||||||
pushConfig();
|
pushConfig();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// UN SEUL PANNEAU DEPLIE A LA FOIS. Les deux s'ouvraient l'un sous l'autre,
|
||||||
|
// dans la meme colonne et avec le meme fond : le journal se lisait comme la
|
||||||
|
// suite des reglages.
|
||||||
|
function togglePanel(name) {
|
||||||
|
['cfg', 'log'].forEach(function (n) {
|
||||||
|
var open = (n === name) && document.getElementById(n).hidden;
|
||||||
|
document.getElementById(n).hidden = !open;
|
||||||
|
document.querySelector('[data-panel="' + n + '"]').classList.toggle('open', open);
|
||||||
|
});
|
||||||
|
}
|
||||||
document.getElementById('btnCfg').addEventListener('click', function () {
|
document.getElementById('btnCfg').addEventListener('click', function () {
|
||||||
var c = document.getElementById('cfg');
|
togglePanel('cfg');
|
||||||
c.hidden = !c.hidden;
|
|
||||||
});
|
});
|
||||||
document.getElementById('btnLog').addEventListener('click', function () {
|
document.getElementById('btnLog').addEventListener('click', function () {
|
||||||
var l = document.getElementById('log');
|
togglePanel('log');
|
||||||
l.hidden = !l.hidden;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function esc(s) {
|
function esc(s) {
|
||||||
@@ -682,8 +711,20 @@ var PM = {
|
|||||||
// elPin volontairement absent : rien ne le restaure, jamais.
|
// elPin volontairement absent : rien ne le restaure, jamais.
|
||||||
elUrl.value = url; elTok.value = tok; elPwd.value = pwd;
|
elUrl.value = url; elTok.value = tok; elPwd.value = pwd;
|
||||||
// Replier des champs vides revient a cacher la seule chose a faire.
|
// Replier des champs vides revient a cacher la seule chose a faire.
|
||||||
document.getElementById('cfg').hidden = !show;
|
if (show !== !document.getElementById('cfg').hidden) togglePanel('cfg');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// Le coffre local est scelle sous un code : il faut le retaper pour
|
||||||
|
/// l'ouvrir. Delphi le sait en lisant le premier octet du cache.
|
||||||
|
/// TOUJOURS VISIBLE, seule l'indication change. Le cacher quand aucun code
|
||||||
|
/// n'est pose supprimerait le seul endroit ou en poser un ; le montrer sans
|
||||||
|
/// rien dire laisserait croire qu'il en faut un pour ouvrir. C'est donc le
|
||||||
|
/// texte d'aide qui porte la difference.
|
||||||
|
setPinNeeded: function (on) {
|
||||||
|
document.getElementById('pin').placeholder =
|
||||||
|
on ? 'unlock code' : 'set an unlock code (optional)';
|
||||||
|
},
|
||||||
|
focusPin: function () { document.getElementById('pin').focus(); },
|
||||||
log: function (line) {
|
log: function (line) {
|
||||||
var l = document.getElementById('log');
|
var l = document.getElementById('log');
|
||||||
l.textContent += line + '\n';
|
l.textContent += line + '\n';
|
||||||
|
|||||||
+31
-2
@@ -334,10 +334,19 @@ begin
|
|||||||
// Le bouton d'ouverture n'a de sens que si un coffre local existe ET qu'il
|
// Le bouton d'ouverture n'a de sens que si un coffre local existe ET qu'il
|
||||||
// s'est deja laisse ouvrir ; sinon seul le telechargement peut en produire
|
// s'est deja laisse ouvrir ; sinon seul le telechargement peut en produire
|
||||||
// un utilisable.
|
// un utilisable.
|
||||||
|
// LE CODE SE DEMANDE ICI, SUR L'ECRAN D'ACCUEIL. Il etait range dans les
|
||||||
|
// reglages du serveur, ou rien n'indiquait qu'il fallait le retaper : le
|
||||||
|
// coffre scelle sous un code s'ouvrait donc sans code, echouait, et l'echec
|
||||||
|
// etait pris pour une corruption - l'appareil proposait de retelecharger un
|
||||||
|
// coffre qu'il avait parfaitement en main.
|
||||||
|
WebCall(Format('PM.setPinNeeded(%s)', [LowerCase(BoolToStr(VaultNeedsPin, True))]));
|
||||||
|
|
||||||
LCanOpen := HasVault and not FVaultUnreadable;
|
LCanOpen := HasVault and not FVaultUnreadable;
|
||||||
if LCanOpen then
|
if LCanOpen then
|
||||||
begin
|
begin
|
||||||
if VaultNeedsAuth then
|
if VaultNeedsPin then
|
||||||
|
Status('Vault on this device - unlock code required')
|
||||||
|
else if VaultNeedsAuth then
|
||||||
Status('Vault on this device - protected')
|
Status('Vault on this device - protected')
|
||||||
else
|
else
|
||||||
Status('Vault on this device');
|
Status('Vault on this device');
|
||||||
@@ -1025,8 +1034,18 @@ begin
|
|||||||
begin
|
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.
|
||||||
|
// Un code faux se retape ; le cache, lui, n'a rien perdu. Le marquer
|
||||||
|
// illisible retirerait le bouton d'ouverture pour le reste de la session.
|
||||||
|
if VaultNeedsPin then
|
||||||
|
begin
|
||||||
|
Log('Vault did not open under this unlock code');
|
||||||
|
ShowUnlock;
|
||||||
|
Status('Wrong unlock code');
|
||||||
|
WebCall('PM.focusPin()');
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
FVaultUnreadable := True;
|
FVaultUnreadable := True;
|
||||||
ShowUnlock;
|
ShowUnlock;
|
||||||
Log('Local vault unreadable: ' + LastStoreError);
|
Log('Local vault unreadable: ' + LastStoreError);
|
||||||
Status('Local vault unreadable - see log');
|
Status('Local vault unreadable - see log');
|
||||||
end;
|
end;
|
||||||
@@ -1040,6 +1059,16 @@ end;
|
|||||||
procedure TForm8.OpenVault(AAuto: Boolean);
|
procedure TForm8.OpenVault(AAuto: Boolean);
|
||||||
begin
|
begin
|
||||||
if FBusy then Exit;
|
if FBusy then Exit;
|
||||||
|
// NE PAS TENTER CE QUI NE PEUT QUE RATER. Un code absent n'est pas un coffre
|
||||||
|
// abime : l'essayer quand meme marquait le cache illisible et transformait le
|
||||||
|
// bouton en "Download vault", ce qui donnait a croire que le code avait
|
||||||
|
// detruit le cache alors qu'il le protegeait.
|
||||||
|
if VaultNeedsPin and (FPin = '') then
|
||||||
|
begin
|
||||||
|
Status('Unlock code required');
|
||||||
|
WebCall('PM.focusPin()');
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
// L'authentification affiche un dialogue systeme : elle doit rester sur le
|
// L'authentification affiche un dialogue systeme : elle doit rester sur le
|
||||||
// thread principal, et PRECEDER toute lecture du coffre.
|
// thread principal, et PRECEDER toute lecture du coffre.
|
||||||
if not VaultNeedsAuth then
|
if not VaultNeedsAuth then
|
||||||
|
|||||||
Reference in New Issue
Block a user