mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-23 15:09:33 +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 { EV } from "../common";
|
||||||
import Settings from "./settings";
|
import Settings from "./settings";
|
||||||
import Migrations from "./migrations";
|
import Migrations from "./migrations";
|
||||||
|
import Outbox from "./outbox";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {EventSource}
|
* @type {EventSource}
|
||||||
@@ -59,6 +60,7 @@ class Database {
|
|||||||
this.backup = new Backup(this);
|
this.backup = new Backup(this);
|
||||||
this.settings = new Settings(this);
|
this.settings = new Settings(this);
|
||||||
this.migrations = new Migrations(this);
|
this.migrations = new Migrations(this);
|
||||||
|
this.outbox = new Outbox(this);
|
||||||
|
|
||||||
// collections
|
// collections
|
||||||
/** @type {Notes} */
|
/** @type {Notes} */
|
||||||
@@ -76,6 +78,7 @@ class Database {
|
|||||||
|
|
||||||
await this.settings.init();
|
await this.settings.init();
|
||||||
await this.user.sync();
|
await this.user.sync();
|
||||||
|
await this.outbox.init();
|
||||||
|
|
||||||
await this.migrations.init();
|
await this.migrations.init();
|
||||||
await this.migrations.migrate();
|
await this.migrations.migrate();
|
||||||
@@ -87,10 +90,10 @@ class Database {
|
|||||||
this.evtSource.close();
|
this.evtSource.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user) return;
|
if (!user || !user.accessToken) {
|
||||||
if (!user.accessToken) {
|
|
||||||
user = await this.user.get();
|
user = await this.user.get();
|
||||||
}
|
}
|
||||||
|
if (!user) return;
|
||||||
|
|
||||||
this.evtSource = new NNEventSource(`${Constants.HOST}/events`, {
|
this.evtSource = new NNEventSource(`${Constants.HOST}/events`, {
|
||||||
headers: { Authorization: `Bearer ${user.accessToken}` },
|
headers: { Authorization: `Bearer ${user.accessToken}` },
|
||||||
@@ -123,6 +126,10 @@ class Database {
|
|||||||
await this.user.logout();
|
await this.user.logout();
|
||||||
EV.publish("user:deleted");
|
EV.publish("user:deleted");
|
||||||
break;
|
break;
|
||||||
|
case "userPasswordChanged":
|
||||||
|
await this.user.logout();
|
||||||
|
EV.publish("user:passwordChanged");
|
||||||
|
break;
|
||||||
case "sync":
|
case "sync":
|
||||||
await this.syncer.eventMerge(data);
|
await this.syncer.eventMerge(data);
|
||||||
EV.publish("db:refresh");
|
EV.publish("db:refresh");
|
||||||
@@ -141,8 +148,8 @@ class Database {
|
|||||||
}, 15 * 1000);
|
}, 15 * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
sync(full = true) {
|
sync(full = true, force = false) {
|
||||||
return this.syncer.start(full);
|
return this.syncer.start(full, force);
|
||||||
}
|
}
|
||||||
|
|
||||||
host(host) {
|
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) {
|
constructor(db) {
|
||||||
this._db = 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._lastSyncedTimestamp = lastSyncedTimestamp;
|
||||||
this.key = await this._db.user.key();
|
this.key = await this._db.user.key();
|
||||||
|
this.force = force;
|
||||||
return {
|
return {
|
||||||
notes: await this._collect(this._db.notes.raw),
|
notes: await this._collect(this._db.notes.raw),
|
||||||
notebooks: await this._collect(this._db.notebooks.raw),
|
notebooks: await this._collect(this._db.notebooks.raw),
|
||||||
@@ -35,24 +50,15 @@ class Collector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_collect(array) {
|
_collect(array) {
|
||||||
|
if (this.force) {
|
||||||
|
return Promise.all(tfun.map(this._map)(array));
|
||||||
|
}
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
tfun
|
tfun
|
||||||
.filter(
|
.filter(
|
||||||
(item) => item.dateEdited > this._lastSyncedTimestamp || item.migrated
|
(item) => item.dateEdited > this._lastSyncedTimestamp || item.migrated
|
||||||
)
|
)
|
||||||
.map(async (i) => {
|
.map(this._map)(array)
|
||||||
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)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,13 +65,13 @@ export default class Sync {
|
|||||||
return { user, lastSynced, token };
|
return { user, lastSynced, token };
|
||||||
}
|
}
|
||||||
|
|
||||||
async start(full) {
|
async start(full, force) {
|
||||||
let { lastSynced, token } = await this._performChecks();
|
let { lastSynced, token } = await this._performChecks();
|
||||||
|
|
||||||
if (full) var serverResponse = await this._fetch(lastSynced, token);
|
if (full) var serverResponse = await this._fetch(lastSynced, token);
|
||||||
|
|
||||||
// we prepare local data before merging so we always have correct data
|
// 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) {
|
if (full) {
|
||||||
// merge the server response
|
// merge the server response
|
||||||
|
|||||||
@@ -28,8 +28,11 @@ export default class User {
|
|||||||
"GET"
|
"GET"
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.message.includes("not authorized")) await this.logout();
|
if (e.message.includes("not authorized")) {
|
||||||
else throw e;
|
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({
|
await this.set({
|
||||||
@@ -95,11 +98,11 @@ export default class User {
|
|||||||
EV.publish("user:tokenRefreshed", user);
|
EV.publish("user:tokenRefreshed", user);
|
||||||
}
|
}
|
||||||
|
|
||||||
async logout() {
|
async logout(reason) {
|
||||||
await this._context.clear();
|
await this._context.clear();
|
||||||
|
|
||||||
// propogate event
|
// propogate event
|
||||||
EV.publish("user:loggedOut");
|
EV.publish("user:loggedOut", reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
async signup(username, email, password) {
|
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) {
|
async _postLogin(password, remember, response) {
|
||||||
await this._context.deriveCryptoKey(`_uk_@${response.payload.username}`, {
|
await this._context.deriveCryptoKey(`_uk_@${response.payload.username}`, {
|
||||||
password,
|
password,
|
||||||
@@ -173,9 +198,8 @@ async function authRequest(endpoint, data, auth = false, method = "POST") {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
let json = await response.json();
|
|
||||||
let error =
|
let error =
|
||||||
json.error ||
|
(await response.text()) ||
|
||||||
`Request failed with status code: ${response.status} ${response.statusText}.`;
|
`Request failed with status code: ${response.status} ${response.statusText}.`;
|
||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user