diff --git a/android-client/PMA.Store.pas b/android-client/PMA.Store.pas index b67b716..4233aab 100644 --- a/android-client/PMA.Store.pas +++ b/android-client/PMA.Store.pas @@ -797,6 +797,22 @@ begin // ETag, mot de passe et coffre chiffres ENSEMBLE : ils doivent rester // coherents. Un ETag qui survivrait a un coffre illisible ferait repondre // 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; try LWrap.AddPair('etag', AETag); diff --git a/android-client/web/app.html b/android-client/web/app.html index 1dfdc2d..bf0af06 100644 --- a/android-client/web/app.html +++ b/android-client/web/app.html @@ -187,6 +187,16 @@ font: inherit; outline: none; } .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; } /* Le champ laisse la place a la croix, sinon le texte passe dessous. */ .in-wrap .field-in { padding-right: 44px; } @@ -221,8 +231,24 @@
- - + + +
+ + + +
+
+ +
×
- @@ -375,8 +393,11 @@ document.getElementById('btnSync').addEventListener('click', function () { pushConfig(); // ce qui est tape mais pas encore quitte compte aussi send('sync'); }); -// Un seul ecouteur pour les trois croix : elles ne different que par leur cible. -document.getElementById('cfg').addEventListener('click', function (ev) { +// Un seul ecouteur pour les quatre croix : elles ne different que par leur +// 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'); if (!id) return; var el = document.getElementById(id); @@ -387,13 +408,21 @@ document.getElementById('cfg').addEventListener('click', function (ev) { 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 () { - var c = document.getElementById('cfg'); - c.hidden = !c.hidden; + togglePanel('cfg'); }); document.getElementById('btnLog').addEventListener('click', function () { - var l = document.getElementById('log'); - l.hidden = !l.hidden; + togglePanel('log'); }); function esc(s) { @@ -682,8 +711,20 @@ var PM = { // elPin volontairement absent : rien ne le restaure, jamais. elUrl.value = url; elTok.value = tok; elPwd.value = pwd; // 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) { var l = document.getElementById('log'); l.textContent += line + '\n'; diff --git a/mainApk.pas b/mainApk.pas index 37259be..8a2c569 100644 --- a/mainApk.pas +++ b/mainApk.pas @@ -334,10 +334,19 @@ begin // 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 // 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; if LCanOpen then 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') else Status('Vault on this device'); @@ -1025,8 +1034,18 @@ begin begin // ShowUnlock AVANT le message : il repositionne le statut selon ce que // 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; - ShowUnlock; + ShowUnlock; Log('Local vault unreadable: ' + LastStoreError); Status('Local vault unreadable - see log'); end; @@ -1040,6 +1059,16 @@ end; procedure TForm8.OpenVault(AAuto: Boolean); begin 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 // thread principal, et PRECEDER toute lecture du coffre. if not VaultNeedsAuth then