Files
movim-rs/frontend/widgets/Account/account.js
T
2026-10-24 17:46:20 +07:00

181 lines
7.4 KiB
JavaScript

var Account = {
resetPassword: function () {
var form = document.querySelector('form[name=password]');
form.reset();
document.querySelector('#password_save').className = 'button color flat';
},
resolveSessionsStates: function () {
var store = new ChatOmemoStorage();
store.getSessionsIds(store.jid).map(id => {
store.getSessionState(store.jid + '.' + id).then(state => {
if (state) {
let checkbox = document.querySelector('input[name=accountsessionstate_' + id + ']');
if (checkbox) {
checkbox.checked = true;
}
}
})
});
},
toggleFingerprintState: function (checkbox) {
var store = new ChatOmemoStorage();
store.setSessionState(checkbox.dataset.identifier, checkbox.checked);
},
deleteBundle: async function (bundleid) {
ChatOmemo.resolveContactFingerprints(USER_JID).then(remoteKeys => {
remoteKeys = remoteKeys.filter(remoteKey =>
remoteKey.bundleid == bundleid
);
Account_ajaxDeleteBundle(remoteKeys[0])
});
},
deleteBundleConfirm: function (bundleid) {
var store = new ChatOmemoStorage();
var address = new libsignal.SignalProtocolAddress(USER_JID, bundleid);
store.removeSession(address);
Account_ajaxDeleteBundleConfirm(bundleid, store.getOwnSessionsIds());
},
/**
* OMEMO key backup of this device: identity, prekeys and sessions,
* always encrypted (PBKDF2 + AES-256-GCM), even with an empty password.
* Everything happens in the browser, the server never sees the keys.
*/
backupFormat: 'movim-omemo-backup',
backupIterations: 600000,
backupCipherKey: async function (password, salt, iterations) {
const base = await crypto.subtle.importKey('raw', new TextEncoder().encode(password), 'PBKDF2', false, ['deriveKey']);
return crypto.subtle.deriveKey(
{ name: 'PBKDF2', salt: salt, iterations: iterations, hash: 'SHA-256' },
base, { name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt']
);
},
backupToBase64: function (buffer) {
let bytes = new Uint8Array(buffer);
let binary = '';
for (let i = 0; i < bytes.length; i += 0x8000) {
binary += String.fromCharCode.apply(null, bytes.subarray(i, i + 0x8000));
}
return btoa(binary);
},
backupError: function (text) {
document.querySelector('#omemo_backup_error').textContent = text;
},
omemoBackup: async function () {
let form = document.querySelector('form[name=omemo_backup]');
let password = form.password.value;
if (password != form.password_confirmation.value) return Account.backupError(form.dataset.notsame);
let store = new ChatOmemoStorage();
let prefix = store.storageVersion + '.' + store.jid + '.';
let keys = {};
Object.keys(localStorage).filter(k => k.startsWith(prefix)).forEach(k => keys[k] = localStorage.getItem(k));
let deviceId = await store.getLocalRegistrationId();
let payload = JSON.stringify({
jid: store.jid,
deviceId: deviceId,
keys: keys
});
const salt = crypto.getRandomValues(new Uint8Array(16));
const iv = crypto.getRandomValues(new Uint8Array(12));
const key = await Account.backupCipherKey(password, salt, Account.backupIterations);
const cipher = await crypto.subtle.encrypt({ name: 'AES-GCM', iv: iv }, key, new TextEncoder().encode(payload));
let backup = JSON.stringify({
format: Account.backupFormat,
version: 1,
jid: store.jid,
deviceId: deviceId,
created: new Date().toISOString(),
kdf: { name: 'PBKDF2-SHA256', iterations: Account.backupIterations, salt: Account.backupToBase64(salt) },
iv: Account.backupToBase64(iv),
data: Account.backupToBase64(cipher)
});
let link = document.createElement('a');
link.href = URL.createObjectURL(new Blob([backup], { type: 'application/json' }));
link.download = 'movim-omemo-' + store.jid + '-' + deviceId + '-' + new Date().toISOString().slice(0, 10) + '.json';
link.click();
URL.revokeObjectURL(link.href);
Dialog_ajaxClear();
},
omemoRestore: async function () {
let form = document.querySelector('form[name=omemo_restore]');
let file = form.file.files[0];
if (!file) return Account.backupError(form.dataset.nofile);
let backup, payload;
try {
backup = JSON.parse(await file.text());
if (backup.format != Account.backupFormat || backup.version != 1) throw new Error();
} catch (e) {
return Account.backupError(form.dataset.invalid);
}
if (backup.jid != USER_JID) return Account.backupError(form.dataset.otheraccount + ' ' + backup.jid);
try {
const key = await Account.backupCipherKey(form.password.value,
MovimUtils.base64ToArrayBuffer(backup.kdf.salt), backup.kdf.iterations);
const plain = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv: MovimUtils.base64ToArrayBuffer(backup.iv) },
key, MovimUtils.base64ToArrayBuffer(backup.data));
payload = JSON.parse(new TextDecoder().decode(plain));
} catch (e) {
return Account.backupError(form.dataset.wrongpassword);
}
let store = new ChatOmemoStorage();
let prefix = store.storageVersion + '.' + store.jid + '.';
if (Object.keys(payload.keys).some(k => !k.startsWith(prefix))) {
return Account.backupError(form.dataset.invalid);
}
// Replace the keys of this device with the backup ones
let replaced = await store.getLocalRegistrationId();
Object.keys(localStorage).filter(k => k.startsWith(prefix)).forEach(k => localStorage.removeItem(k));
Object.entries(payload.keys).forEach(([k, v]) => localStorage.setItem(k, v));
localStorage.setObject(OMEMO_RESTORED_KEY, { replaced: replaced != payload.deviceId ? replaced ?? null : null });
// The page reload publishes the restored key (ChatOmemo.ownDevicesReceived)
location.reload();
},
refreshFingerprints: async function () {
ChatOmemo.resolveContactFingerprints(USER_JID).then(remoteKeys => {
let omemoStorage = new ChatOmemoStorage;
omemoStorage.getLocalRegistrationId().then(localBundleId => {
omemoStorage.getIdentityKeyPair().then(identityKey => {
// Remove it if its already there
remoteKeys = remoteKeys.filter(remoteKey =>
remoteKey.bundleid != localBundleId.toString()
);
remoteKeys.unshift({
jid: USER_JID,
self: true,
bundleid: localBundleId.toString(),
fingerprint: MovimUtils.arrayBufferToBase64(identityKey.pubKey)
});
Account_ajaxHttpGetFingerprints(remoteKeys);
});
});
});
}
}
MovimWebsocket.attach(function () {
if (OMEMO_ENABLED) Account.refreshFingerprints();
Account_ajaxHttpGetPresences();
Account_ajaxGetGateways();
});