feat: impl account password changing

This commit is contained in:
thecodrr
2020-12-11 20:19:28 +05:00
parent 34b6bb477c
commit a83b2355d2
5 changed files with 106 additions and 26 deletions

View File

@@ -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) {

View 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;

View File

@@ -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)
);
}
}

View File

@@ -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

View File

@@ -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);
}