mirror of
https://codeberg.org/valpackett/tiddlypwa.git
synced 2026-03-14 11:50:48 -07:00
Plugin: use a random per-wiki salt
Now that we have the bootstrapping process it's not that hard to have it. The only inconvenience is having to paste a value when initializing a new wiki to sync an old one in, which is advanced usage, not part of the normal easy sync-bootstrap workflow.
This commit is contained in:
parent
d681c26068
commit
263e5d82f6
3 changed files with 59 additions and 8 deletions
|
|
@ -42,6 +42,9 @@ The password (or technically, the encryption key derived from it) is currently
|
|||
''not remembered'' on this device. <$button message="tiddlypwa-remember">Remember it</$button>?
|
||||
</$reveal>
|
||||
|
||||
The salt value for this wiki is: <code>{{$:/status/TiddlyPWASalt}}</code>
|
||||
(if you were to manually initialize a new one to sync with this one, you would need to copy this value).
|
||||
|
||||
!! Synchronization
|
||||
|
||||
{{$:/plugins/valpackett/tiddlypwa/sync-status}}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ Formatted with `deno fmt`.
|
|||
|
||||
function b64dec(base64) {
|
||||
// welp touching binary strings here but seems to be a decent compact way
|
||||
return Uint8Array.from(atob(base64), (c) => c.charCodeAt(0)).buffer;
|
||||
return Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));
|
||||
}
|
||||
|
||||
function formatBytes(bytes) {
|
||||
|
|
@ -355,6 +355,7 @@ Formatted with `deno fmt`.
|
|||
}
|
||||
|
||||
initDb(db) {
|
||||
db.createObjectStore('metadata', { autoIncrement: true });
|
||||
db.createObjectStore('session', { autoIncrement: true });
|
||||
db.createObjectStore('syncservers', { autoIncrement: true });
|
||||
db.createObjectStore('tiddlers', { keyPath: 'thash' });
|
||||
|
|
@ -389,6 +390,12 @@ Formatted with `deno fmt`.
|
|||
this.db.transaction('tiddlers').objectStore('tiddlers').openCursor().onsuccess = (evt) =>
|
||||
resolve(!evt.target.result)
|
||||
);
|
||||
if (!this.salt) {
|
||||
const meta = await adb(this.db.transaction('metadata').objectStore('metadata').getAll());
|
||||
if (meta.length > 0) {
|
||||
this.salt = meta[meta.length - 1].salt;
|
||||
}
|
||||
}
|
||||
if (!this.key) {
|
||||
const ses = await adb(this.db.transaction('session').objectStore('session').getAll());
|
||||
if (ses.length > 0) {
|
||||
|
|
@ -457,13 +464,16 @@ Formatted with `deno fmt`.
|
|||
signal: giveUp.signal,
|
||||
cache: 'no-store',
|
||||
});
|
||||
const { state, endpoint } = await resp.json();
|
||||
if ((endpoint && typeof endpoint !== 'string') || (state && typeof state !== 'string')) {
|
||||
const { state, endpoint, salt } = await resp.json();
|
||||
if (
|
||||
(endpoint && typeof endpoint !== 'string') || (state && typeof state !== 'string') ||
|
||||
(salt && typeof salt !== 'string')
|
||||
) {
|
||||
alert('Something is weird with the server! Unexpected types in bootstrap.json');
|
||||
}
|
||||
bootstrapEndpoint = endpoint && { url: endpoint };
|
||||
clearTimeout(timeoutGiveUpBtn);
|
||||
let askToken = true;
|
||||
let askToken = true, askSalt = true;
|
||||
if (state === 'docs') {
|
||||
this.db.close();
|
||||
closeModal();
|
||||
|
|
@ -490,6 +500,8 @@ Formatted with `deno fmt`.
|
|||
body.innerHTML = '<p>Welcome back to your synchronized wiki!</p>';
|
||||
body.innerHTML +=
|
||||
`<p>Log in using your credentials below. You are using the sync server <code>${endpoint}</code>.</p>`;
|
||||
askSalt = false;
|
||||
this.salt = b64dec(salt);
|
||||
} else {
|
||||
body.innerHTML = '<p>We are not quite sure what happened on the sync server...</p>';
|
||||
body.innerHTML += `<p>Try to log in using your credentials below anyway?</p>`;
|
||||
|
|
@ -498,7 +510,7 @@ Formatted with `deno fmt`.
|
|||
if (!bootstrapEndpoint) {
|
||||
alert(`This sync server is misconfigured: no endpoint found while state is '${state}'.`);
|
||||
}
|
||||
const tokLbl = dm('label', { innerHTML: 'Sync token' });
|
||||
const tokLbl = dm('label', { text: 'Sync token' });
|
||||
tokLbl.appendChild(dm('input', {
|
||||
attributes: { type: 'password' },
|
||||
eventListeners: [{
|
||||
|
|
@ -508,6 +520,32 @@ Formatted with `deno fmt`.
|
|||
}));
|
||||
form.appendChild(tokLbl);
|
||||
}
|
||||
if (askSalt) {
|
||||
const saltDtl = dm('details', {
|
||||
innerHTML: `
|
||||
<summary>If you are going to sync a pre-existing wiki into this one, click here</summary>
|
||||
<p>In order for such a sync to succeed, the wiki needs to be initialized with the same "salt" as well as the same password.</p>
|
||||
<p>Copy the salt from the <strong>Settings</strong> → <strong>Storage and Sync</strong> page on the existing wiki, or from the sync admin interface.</p>
|
||||
`,
|
||||
});
|
||||
const saltLbl = dm('label', { text: 'Salt' });
|
||||
saltLbl.appendChild(dm('input', {
|
||||
attributes: { type: 'text' },
|
||||
eventListeners: [{
|
||||
name: 'change',
|
||||
handlerFunction: (e) => {
|
||||
try {
|
||||
this.salt = b64dec(e.target.value.trim());
|
||||
feedback.innerHTML = '';
|
||||
} catch (_e) {
|
||||
feedback.innerHTML = '<p class=tiddlypwa-form-error>Could not decode the salt</p>';
|
||||
}
|
||||
},
|
||||
}],
|
||||
}));
|
||||
saltDtl.appendChild(saltLbl);
|
||||
form.appendChild(saltDtl);
|
||||
}
|
||||
showForm();
|
||||
openModal();
|
||||
} catch (e) {
|
||||
|
|
@ -535,7 +573,8 @@ Formatted with `deno fmt`.
|
|||
resolve();
|
||||
};
|
||||
});
|
||||
const basebits = await argon.hash(utfenc.encode(passInput.value), utfenc.encode('tiddly.pwa.storage'));
|
||||
if (!this.salt) this.salt = crypto.getRandomValues(new Uint8Array(32));
|
||||
const basebits = await argon.hash(utfenc.encode(passInput.value), this.salt);
|
||||
const basekey = await crypto.subtle.importKey('raw', basebits, 'HKDF', false, ['deriveKey']);
|
||||
// fun: https://soatok.blog/2021/11/17/understanding-hkdf/ (but we don't have any randomness to shove into info)
|
||||
this.key = await crypto.subtle.deriveKey(
|
||||
|
|
@ -560,7 +599,9 @@ Formatted with `deno fmt`.
|
|||
}
|
||||
}
|
||||
argon.terminate();
|
||||
closeModal();
|
||||
if (freshDb) {
|
||||
this.db.transaction('metadata', 'readwrite').objectStore('metadata').put({ salt: this.salt });
|
||||
}
|
||||
if (bootstrapEndpoint) {
|
||||
const { url, token } = bootstrapEndpoint;
|
||||
await adb(
|
||||
|
|
@ -571,11 +612,16 @@ Formatted with `deno fmt`.
|
|||
}),
|
||||
);
|
||||
}
|
||||
closeModal();
|
||||
} else {
|
||||
await this.initialRead();
|
||||
}
|
||||
await this.reflectSyncServers();
|
||||
await this.reflectStorageInfo();
|
||||
this.wiki.addTiddler({
|
||||
title: '$:/status/TiddlyPWASalt',
|
||||
text: await b64enc(this.salt),
|
||||
});
|
||||
}
|
||||
|
||||
getStatus(cb) {
|
||||
|
|
@ -855,6 +901,7 @@ Formatted with `deno fmt`.
|
|||
token,
|
||||
browserToken: this.browserToken,
|
||||
authcode: await b64enc(await this.titlehash(token)),
|
||||
salt: await b64enc(this.salt),
|
||||
now: new Date(), // only for a desync check
|
||||
lastSync,
|
||||
clientChanges,
|
||||
|
|
|
|||
|
|
@ -7,5 +7,6 @@ Licensed under 0BSD, see license.tid.
|
|||
\*/
|
||||
|
||||
.tc-modal-wrapper { backdrop-filter: blur(3px); }
|
||||
.tiddlypwa-form input, .tiddlypwa-form button { display: block; width: 100%; margin: 0.5em 0; padding: 0.25em; }
|
||||
.tiddlypwa-form input, .tiddlypwa-form button, .tiddlypwa-form details { display: block; width: 100%; margin: 0.5em 0; }
|
||||
.tiddlypwa-form input, .tiddlypwa-form button { padding: 0.25em; border-radius: 3px; border: 1px solid #999; }
|
||||
.tiddlypwa-form-error { color: #ee1111; font-weight: bolder; }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue