feat(apk): generateur de mot de passe, erreurs du depot affichees dans les reglages

- de sur le champ mot de passe (nouvelle entree et modification), tirage
  crypto.getRandomValues avec rejet
- Download vault sans reglages ouvre les reglages
- 404/401 tranches (jeton refuse, utilisateur ou depot inexistant, fichier
  absent) et mauvais mot de passe de synchro : les reglages s'ouvrent avec
  le message en rouge

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
r-zakarya
2026-09-18 10:18:47 +01:00
parent 3eb26e23e5
commit ba8c4205f9
3 changed files with 132 additions and 7 deletions
+44 -2
View File
@@ -276,7 +276,7 @@
/* UN REFUS N'EST PAS UN ETAT. "Master password: 8 characters at least" se
lisait du meme gris que "Vault on this device" - on ne voyait pas que
l'application venait de refuser quelque chose. */
.status.err { color: #ff9d9d; }
.status.err, .note.err { color: #ff9d9d; }
.stack { display: flex; flex-direction: column; gap: 8px; }
/* L'espace de 8 px vit A L'INTERIEUR de chaque pile. Deux piles voisines se
touchaient donc, et "Create vault" collait a "Download vault" alors que
@@ -568,6 +568,7 @@
</div>
<div class="scroll">
<h2>Repository</h2>
<div class="note err" id="sRepoErr" hidden></div>
<div class="in-wrap">
<!-- ZONE DE TEXTE ET NON input : l'URL passe a la ligne et se lit en
entier. Un input d'une ligne ne defilait pas sous le doigt dans la
@@ -981,6 +982,9 @@ var ICONS = {
dots: '<circle cx="12" cy="5" r="1.4"/><circle cx="12" cy="12" r="1.4"/>'
+ '<circle cx="12" cy="19" r="1.4"/>',
check: '<path d="M4 12.5l5.5 5.5L20 7"/>',
dice: '<rect x="4" y="4" width="16" height="16" rx="3"/>'
+ '<circle cx="9" cy="9" r="1.2"/><circle cx="15" cy="15" r="1.2"/>'
+ '<circle cx="15" cy="9" r="1.2"/><circle cx="9" cy="15" r="1.2"/>',
close: '<path d="M6 6l12 12M18 6L6 18"/>',
// Un arc, pas un cercle : c'est l'ouverture qui rend la rotation visible.
loader: '<path d="M12 3a9 9 0 019 9"/>',
@@ -1051,6 +1055,15 @@ document.getElementById('fields').addEventListener('click', function (ev) {
// Meme raison que dans la liste : le dessin recoit l'appui, le bouton porte
// l'intention. Tout ce qui suit lit et ecrit sur le bouton.
var t = ev.target.closest('button') || ev.target;
var gen = t.getAttribute('data-gen');
if (gen) {
// Visible une fois tire : on garde ce qu'on voit, pas une suite de points.
var gi = document.getElementById(gen);
gi.value = genPassword(20);
gi.type = 'text';
send('touch');
return;
}
var k = t.getAttribute('data-show');
if (k !== null) {
var cell = document.querySelector('[data-v="' + k + '"]');
@@ -1207,6 +1220,7 @@ function openEditor(k) {
var line = row.parentNode;
line.classList.toggle('wrap', !!detailRows[k].m);
line.innerHTML = editorControl(k)
+ (editField === 'password' ? genButton('editval') : '')
// Show n'a de sens que sur un champ masque. Sur un site en clair il
// n'aurait rien a reveler - et l'inverse : il le CACHERAIT.
//
@@ -1355,6 +1369,25 @@ function newField(id, label, rowId) {
/// sanitisation de HTML et retire les retours a la ligne : on pouvait lire et
/// modifier du multiligne, mais pas en creer - il fallait creer la note puis
/// l'ouvrir pour la mettre en forme.
/// MOT DE PASSE ALEATOIRE, tire de crypto.getRandomValues. Rejet des octets
/// au-dela du dernier multiple de la taille du jeu : un simple modulo
/// favoriserait les premiers caracteres.
var GEN_CHARS = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789'
+ '!@#$%^&*-_=+?';
function genPassword(n) {
var out = '', buf = new Uint8Array(1),
lim = 256 - (256 % GEN_CHARS.length);
while (out.length < (n || 20)) {
crypto.getRandomValues(buf);
if (buf[0] < lim) out += GEN_CHARS.charAt(buf[0] % GEN_CHARS.length);
}
return out;
}
function genButton(target) {
return '<button class="btn ico-btn" data-gen="' + target + '">'
+ ico('dice', 'Generate') + '</button>';
}
function newBodyControl(kind) {
if (kind === 'note')
return '<textarea class="field-in" id="nPwd" rows="6" enterkeyhint="enter"'
@@ -1362,7 +1395,7 @@ function newBodyControl(kind) {
+ ';height:auto;padding:10px 14px;resize:vertical"></textarea>';
return '<input class="field-in" id="nPwd" type="text" autocomplete="off"'
+ ' autocorrect="off" autocapitalize="none" spellcheck="false"'
+ ' style="' + FIELD_STYLE + '">';
+ ' style="' + FIELD_STYLE + '">' + genButton('nPwd');
}
function openNewEntry() {
@@ -1633,6 +1666,7 @@ function openSettings() {
document.getElementById('sPwd').value = elPwd.value;
document.getElementById('sPinNew').value = '';
document.getElementById('secCode').hidden = !vaultOpen;
document.getElementById('sRepoErr').hidden = true;
// La section n'existe que coffre ouvert : le dire tant qu'elle manque, et se
// taire quand elle est juste en dessous.
document.getElementById('sUnlockHint').textContent =
@@ -1685,6 +1719,7 @@ document.getElementById('sSave').addEventListener('click', function () {
elTok.value = document.getElementById('sTok').value.trim();
elPwd.value = document.getElementById('sPwd').value;
pushConfig();
document.getElementById('sRepoErr').hidden = true;
toast('Saved');
});
@@ -1838,6 +1873,13 @@ var PM = {
document.body.classList.remove('on-list', 'on-detail', 'searching');
view = 'unlock';
},
/// Erreur de depot, affichee dans les reglages au-dessus des champs a corriger.
settingsError: function (msg) {
var e = document.getElementById('sRepoErr');
e.textContent = msg;
e.hidden = false;
},
setStatus: function (text, err) {
var s = document.getElementById('status');
s.textContent = text;