Files
notesnook/packages/core/api/user-manager.js
thecodrr dd2a065be7 fix: multiple settings objects created for 1 user
due to these multiple objects reset password wasn't working because
only the latest one would be encrypted with the new password
but all previous objects would require decryption
which resulted in a block while syncing
2021-02-26 17:33:46 +05:00

242 lines
6.2 KiB
JavaScript

import http from "../utils/http";
import constants from "../utils/constants";
import TokenManager from "./token-manager";
import { EV, EVENTS, NOTESNOOK_CORE_VERSION } from "../common";
const ENDPOINTS = {
signup: "/users",
token: "/connect/token",
user: "/users",
deleteUser: "/users/delete",
patchUser: "/account",
verifyUser: "/account/verify",
revoke: "/connect/revocation",
recoverAccount: "/account/recover",
migrateStatus: "/migrate/status",
hcliMigrate: "/migrate/hcli",
};
class UserManager {
/**
*
* @param {import("./index").default} db
*/
constructor(db) {
this._db = db;
this.tokenManager = new TokenManager(db);
}
async init() {
const user = await this.getUser();
if (!user) return;
if (!user.remember) await this.logout(true, "Session expired.");
}
async signup(email, password) {
const hashedPassword = await this._db.context.hash(password, email);
await http.post(`${constants.API_HOST}${ENDPOINTS.signup}`, {
email,
password: hashedPassword,
client_id: "notesnook",
});
EV.publish(EVENTS.userSignedUp);
return await this.login(email, password, true, hashedPassword);
}
async _getUserStatus(email) {
return await http.post(`${constants.AUTH_HOST}${ENDPOINTS.migrateStatus}`, {
email,
});
}
async _hcliMigrate(email, plaintextPassword, hashedPassword) {
return await http.post(`${constants.AUTH_HOST}${ENDPOINTS.hcliMigrate}`, {
username: email,
plaintext_password: plaintextPassword,
hashed_password: hashedPassword,
});
}
async _migrateUser(email, plaintextPassword) {
const status = await this._getUserStatus(email);
var hashedPassword = await this._db.context.hash(plaintextPassword, email);
if (!status.hcli) {
await this._hcliMigrate(email, plaintextPassword, hashedPassword);
}
return hashedPassword;
}
async login(email, password, remember, hashedPassword) {
if (!hashedPassword) {
hashedPassword = await this._migrateUser(email, password);
}
await this.tokenManager.saveToken(
await http.post(`${constants.AUTH_HOST}${ENDPOINTS.token}`, {
username: email,
password: hashedPassword,
grant_type: "password",
scope: "notesnook.sync offline_access openid IdentityServerApi",
client_id: "notesnook",
})
);
const user = await this.fetchUser(remember);
await this._db.context.deriveCryptoKey(`_uk_@${user.email}`, {
password,
salt: user.salt,
});
EV.publish(EVENTS.userLoggedIn, user);
}
async getSessions() {
const token = await this.tokenManager.getAccessToken();
if (!token) return;
await http.get(`${constants.AUTH_HOST}/account/sessions`, token);
}
async logout(revoke = true, reason) {
try {
if (revoke) await this.tokenManager.revokeToken();
} catch (e) {
console.error(e);
} finally {
await this._db.context.clear();
EV.publish(EVENTS.userLoggedOut, reason);
}
}
setUser(user) {
if (!user) return;
return this._db.context.write("user", user);
}
getUser() {
return this._db.context.read("user");
}
async deleteUser(password) {
let token = await this.tokenManager.getAccessToken();
if (!token) return;
const user = await this.getUser();
await http.post(
`${constants.API_HOST}${ENDPOINTS.deleteUser}`,
{ password: await this._db.context.hash(password, user.email) },
token
);
await this.logout(false, "Account deleted.");
return true;
}
async fetchUser(remember = false) {
try {
let token = await this.tokenManager.getAccessToken();
if (!token) return;
const user = await http.get(
`${constants.API_HOST}${ENDPOINTS.user}`,
token
);
if (user) {
const oldUser = await this.getUser();
if (!!oldUser && !oldUser.isEmailConfirmed && user.isEmailConfirmed) {
// generate new token
const token = await this.tokenManager.getToken(false);
await this.tokenManager._refreshToken(token);
}
await this.setUser({ ...user, remember });
EV.publish(EVENTS.userFetched, user);
return user;
} else {
return await this.getUser();
}
} catch (e) {
console.error(e);
return await this.getUser();
}
}
changePassword(oldPassword, newPassword) {
return this._updatePassword("change_password", {
old_password: oldPassword,
new_password: newPassword,
});
}
resetPassword(newPassword) {
return this._updatePassword("reset_password", {
new_password: newPassword,
});
}
async getEncryptionKey() {
const user = await this.getUser();
if (!user) return;
const key = await this._db.context.getCryptoKey(`_uk_@${user.email}`);
return { key, salt: user.salt };
}
async sendVerificationEmail() {
let token = await this.tokenManager.getAccessToken();
if (!token) return;
await http.post(
`${constants.AUTH_HOST}${ENDPOINTS.verifyUser}`,
null,
token
);
}
recoverAccount(email) {
return http.post(`${constants.AUTH_HOST}${ENDPOINTS.recoverAccount}`, {
email,
client_id: "notesnook",
});
}
async _updatePassword(type, data) {
let token = await this.tokenManager.getAccessToken();
if (!token) return;
// we hash the passwords beforehand
const { email, salt } = await this.getUser();
var hashedData = {};
if (data.old_password)
hashedData.old_password = await this._db.context.hash(
data.old_password,
email
);
if (data.new_password)
hashedData.new_password = await this._db.context.hash(
data.new_password,
email
);
await http.patch(
`${constants.AUTH_HOST}${ENDPOINTS.patchUser}`,
{
type,
...hashedData,
},
token
);
await this._db.outbox.add(
type,
{ newPassword: data.new_password },
async () => {
await this._db.sync(true);
await this._db.context.deriveCryptoKey(`_uk_@${email}`, {
password: data.new_password,
salt,
});
await this._db.sync(false, true);
}
);
return true;
}
}
export default UserManager;