mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-23 06:59:31 +01:00
feat: impl account password changing
This commit is contained in:
@@ -15,6 +15,7 @@ import Constants from "../utils/constants";
|
||||
import { EV } from "../common";
|
||||
import Settings from "./settings";
|
||||
import Migrations from "./migrations";
|
||||
import Outbox from "./outbox";
|
||||
|
||||
/**
|
||||
* @type {EventSource}
|
||||
@@ -59,6 +60,7 @@ class Database {
|
||||
this.backup = new Backup(this);
|
||||
this.settings = new Settings(this);
|
||||
this.migrations = new Migrations(this);
|
||||
this.outbox = new Outbox(this);
|
||||
|
||||
// collections
|
||||
/** @type {Notes} */
|
||||
@@ -76,6 +78,7 @@ class Database {
|
||||
|
||||
await this.settings.init();
|
||||
await this.user.sync();
|
||||
await this.outbox.init();
|
||||
|
||||
await this.migrations.init();
|
||||
await this.migrations.migrate();
|
||||
@@ -87,10 +90,10 @@ class Database {
|
||||
this.evtSource.close();
|
||||
}
|
||||
|
||||
if (!user) return;
|
||||
if (!user.accessToken) {
|
||||
if (!user || !user.accessToken) {
|
||||
user = await this.user.get();
|
||||
}
|
||||
if (!user) return;
|
||||
|
||||
this.evtSource = new NNEventSource(`${Constants.HOST}/events`, {
|
||||
headers: { Authorization: `Bearer ${user.accessToken}` },
|
||||
@@ -123,6 +126,10 @@ class Database {
|
||||
await this.user.logout();
|
||||
EV.publish("user:deleted");
|
||||
break;
|
||||
case "userPasswordChanged":
|
||||
await this.user.logout();
|
||||
EV.publish("user:passwordChanged");
|
||||
break;
|
||||
case "sync":
|
||||
await this.syncer.eventMerge(data);
|
||||
EV.publish("db:refresh");
|
||||
@@ -141,8 +148,8 @@ class Database {
|
||||
}, 15 * 1000);
|
||||
}
|
||||
|
||||
sync(full = true) {
|
||||
return this.syncer.start(full);
|
||||
sync(full = true, force = false) {
|
||||
return this.syncer.start(full, force);
|
||||
}
|
||||
|
||||
host(host) {
|
||||
|
||||
43
packages/core/api/outbox.js
Normal file
43
packages/core/api/outbox.js
Normal file
@@ -0,0 +1,43 @@
|
||||
class Outbox {
|
||||
/**
|
||||
*
|
||||
* @param {import("./index").default} db
|
||||
*/
|
||||
constructor(db) {
|
||||
this._db = db;
|
||||
this.outbox = {};
|
||||
}
|
||||
|
||||
async init() {
|
||||
this.outbox = (await this._db.context.read("outbox")) || {};
|
||||
|
||||
for (var id in this.outbox) {
|
||||
const data = this.outbox[id];
|
||||
switch (id) {
|
||||
case "changePassword":
|
||||
const key = await this._db.user.key();
|
||||
const { username } = await this._db.user.get();
|
||||
await this._db.context.deriveCryptoKey(`_uk_@${username}`, {
|
||||
password: data.newPassword,
|
||||
salt: key.salt,
|
||||
});
|
||||
await this._db.sync(false, true);
|
||||
await this.delete(id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async add(id, data, action) {
|
||||
this.outbox[id] = data;
|
||||
await this._db.context.write("outbox", this.outbox);
|
||||
await action();
|
||||
await this.delete(id);
|
||||
}
|
||||
|
||||
delete(id) {
|
||||
delete this.outbox[id];
|
||||
return this._db.context.write("outbox", this.outbox);
|
||||
}
|
||||
}
|
||||
export default Outbox;
|
||||
@@ -12,11 +12,26 @@ class Collector {
|
||||
*/
|
||||
constructor(db) {
|
||||
this._db = db;
|
||||
|
||||
this._map = async (i) => {
|
||||
const item = { ...i };
|
||||
// in case of resolved delta
|
||||
delete item.resolved;
|
||||
// turn the migrated flag off so we don't keep syncing this item repeated
|
||||
delete item.migrated;
|
||||
|
||||
return {
|
||||
id: item.id,
|
||||
v: CURRENT_DATABASE_VERSION,
|
||||
...(await this._serialize(item)),
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
async collect(lastSyncedTimestamp) {
|
||||
async collect(lastSyncedTimestamp, force) {
|
||||
this._lastSyncedTimestamp = lastSyncedTimestamp;
|
||||
this.key = await this._db.user.key();
|
||||
this.force = force;
|
||||
return {
|
||||
notes: await this._collect(this._db.notes.raw),
|
||||
notebooks: await this._collect(this._db.notebooks.raw),
|
||||
@@ -35,24 +50,15 @@ class Collector {
|
||||
}
|
||||
|
||||
_collect(array) {
|
||||
if (this.force) {
|
||||
return Promise.all(tfun.map(this._map)(array));
|
||||
}
|
||||
return Promise.all(
|
||||
tfun
|
||||
.filter(
|
||||
(item) => item.dateEdited > this._lastSyncedTimestamp || item.migrated
|
||||
)
|
||||
.map(async (i) => {
|
||||
const item = { ...i };
|
||||
// in case of resolved delta
|
||||
delete item.resolved;
|
||||
// turn the migrated flag off so we don't keep syncing this item repeated
|
||||
delete item.migrated;
|
||||
|
||||
return {
|
||||
id: item.id,
|
||||
v: CURRENT_DATABASE_VERSION,
|
||||
...(await this._serialize(item)),
|
||||
};
|
||||
})(array)
|
||||
.map(this._map)(array)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,13 +65,13 @@ export default class Sync {
|
||||
return { user, lastSynced, token };
|
||||
}
|
||||
|
||||
async start(full) {
|
||||
async start(full, force) {
|
||||
let { lastSynced, token } = await this._performChecks();
|
||||
|
||||
if (full) var serverResponse = await this._fetch(lastSynced, token);
|
||||
|
||||
// we prepare local data before merging so we always have correct data
|
||||
const data = await this._collector.collect(lastSynced);
|
||||
const data = await this._collector.collect(lastSynced, force);
|
||||
|
||||
if (full) {
|
||||
// merge the server response
|
||||
|
||||
@@ -28,8 +28,11 @@ export default class User {
|
||||
"GET"
|
||||
);
|
||||
} catch (e) {
|
||||
if (e.message.includes("not authorized")) await this.logout();
|
||||
else throw e;
|
||||
if (e.message.includes("not authorized")) {
|
||||
return await this.logout(
|
||||
"You were logged out. Either your session expired or your account was deleted. Please try logging in again."
|
||||
);
|
||||
} else throw e;
|
||||
}
|
||||
|
||||
await this.set({
|
||||
@@ -95,11 +98,11 @@ export default class User {
|
||||
EV.publish("user:tokenRefreshed", user);
|
||||
}
|
||||
|
||||
async logout() {
|
||||
async logout(reason) {
|
||||
await this._context.clear();
|
||||
|
||||
// propogate event
|
||||
EV.publish("user:loggedOut");
|
||||
EV.publish("user:loggedOut", reason);
|
||||
}
|
||||
|
||||
async signup(username, email, password) {
|
||||
@@ -126,6 +129,28 @@ export default class User {
|
||||
}
|
||||
}
|
||||
|
||||
async changePassword(oldPassword, newPassword) {
|
||||
let response = await authRequest.call(
|
||||
this,
|
||||
"users/password",
|
||||
{ old_password: oldPassword, new_password: newPassword },
|
||||
true,
|
||||
"PATCH"
|
||||
);
|
||||
if (response.success) {
|
||||
await this._db.outbox.add("changePassword", { newPassword }, async () => {
|
||||
const key = await this.key();
|
||||
const { username } = await this.get();
|
||||
await this._context.deriveCryptoKey(`_uk_@${username}`, {
|
||||
password: newPassword,
|
||||
salt: key.salt,
|
||||
});
|
||||
await this._db.sync(false, true);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
async _postLogin(password, remember, response) {
|
||||
await this._context.deriveCryptoKey(`_uk_@${response.payload.username}`, {
|
||||
password,
|
||||
@@ -173,9 +198,8 @@ async function authRequest(endpoint, data, auth = false, method = "POST") {
|
||||
return result;
|
||||
}
|
||||
|
||||
let json = await response.json();
|
||||
let error =
|
||||
json.error ||
|
||||
(await response.text()) ||
|
||||
`Request failed with status code: ${response.status} ${response.statusText}.`;
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user