Files
notesnook/packages/core/api/user-manager.js

308 lines
7.9 KiB
JavaScript
Raw Normal View History

2020-12-16 12:06:25 +05:00
import http from "../utils/http";
import constants from "../utils/constants";
import TokenManager from "./token-manager";
import {
EV,
EVENTS,
setUserPersonalizationBytes,
USER_PERSONALIZATION_HASH,
} from "../common";
2020-12-16 12:06:25 +05:00
const ENDPOINTS = {
signup: "/users",
token: "/connect/token",
user: "/users",
deleteUser: "/users/delete",
patchUser: "/account",
verifyUser: "/account/verify",
2020-12-16 12:06:25 +05:00
revoke: "/connect/revocation",
2020-12-22 09:36:35 +05:00
recoverAccount: "/account/recover",
2020-12-16 12:06:25 +05:00
};
class UserManager {
/**
*
* @param {import("../database/storage").default} storage
* @param {import("../api/index").default} db
2020-12-16 12:06:25 +05:00
*/
constructor(storage, db) {
this._storage = storage;
this._db = db;
this.tokenManager = new TokenManager(storage);
EV.subscribe(EVENTS.logoutUser, async (reason) => {
await this.logout(true, reason);
});
2020-12-16 12:06:25 +05:00
}
2020-12-22 16:13:01 +05:00
async init() {
const user = await this.getUser();
if (!user) return;
setUserPersonalizationBytes(user.salt);
2020-12-22 16:13:01 +05:00
}
2020-12-16 12:06:25 +05:00
async signup(email, password) {
const hashedPassword = await this._storage.hash(password, email);
2020-12-16 12:06:25 +05:00
await http.post(`${constants.API_HOST}${ENDPOINTS.signup}`, {
email,
2021-01-18 23:29:49 +05:00
password: hashedPassword,
client_id: "notesnook",
2020-12-16 12:06:25 +05:00
});
EV.publish(EVENTS.userSignedUp);
2021-05-25 16:19:58 +05:00
return await this.login(email, password, hashedPassword);
2021-01-18 23:29:49 +05:00
}
2021-05-25 16:19:58 +05:00
async login(email, password, hashedPassword) {
2021-04-09 12:55:32 +05:00
if (!hashedPassword) {
hashedPassword = await this._storage.hash(password, email);
2021-04-09 12:55:32 +05:00
}
2020-12-16 12:06:25 +05:00
await this.tokenManager.saveToken(
await http.post(`${constants.AUTH_HOST}${ENDPOINTS.token}`, {
username: email,
password: hashedPassword,
2020-12-16 12:06:25 +05:00
grant_type: "password",
2021-06-15 10:17:42 +05:00
scope: "notesnook.sync offline_access openid IdentityServerApi",
2020-12-16 12:06:25 +05:00
client_id: "notesnook",
})
);
2021-05-25 16:19:58 +05:00
const user = await this.fetchUser();
setUserPersonalizationBytes(user.salt);
await this._storage.deriveCryptoKey(`_uk_@${user.email}`, {
2020-12-16 12:06:25 +05:00
password,
salt: user.salt,
});
EV.publish(EVENTS.userLoggedIn, user);
2020-12-16 12:06:25 +05:00
}
async getSessions() {
const token = await this.tokenManager.getAccessToken();
if (!token) return;
await http.get(`${constants.AUTH_HOST}/account/sessions`, token);
}
async clearSessions(all = false) {
const token = await this.tokenManager.getToken();
if (!token) return;
const { access_token, refresh_token } = token;
await http.post(
`${constants.AUTH_HOST}/account/sessions/clear?all=${all}`,
{ refresh_token },
access_token
);
}
2020-12-16 12:06:25 +05:00
async logout(revoke = true, reason) {
2020-12-22 16:13:01 +05:00
try {
if (revoke) await this.tokenManager.revokeToken();
} catch (e) {
console.error(e);
} finally {
await this._storage.clear();
EV.publish(EVENTS.userLoggedOut, reason);
2021-07-03 23:07:54 +05:00
EV.publish(EVENTS.appRefreshRequested);
2020-12-22 16:13:01 +05:00
}
2020-12-16 12:06:25 +05:00
}
setUser(user) {
if (!user) return;
return this._storage.write("user", user);
2020-12-16 12:06:25 +05:00
}
getUser() {
return this._storage.read("user");
2020-12-16 12:06:25 +05:00
}
async updateUser(user) {
if (!user) return;
let token = await this.tokenManager.getAccessToken();
await http.patch.json(
`${constants.API_HOST}${ENDPOINTS.user}`,
user,
token
);
await this.setUser(user);
}
2020-12-16 12:06:25 +05:00
async deleteUser(password) {
let token = await this.tokenManager.getAccessToken();
if (!token) return;
const user = await this.getUser();
2020-12-16 12:06:25 +05:00
await http.post(
`${constants.API_HOST}${ENDPOINTS.deleteUser}`,
{ password: await this._storage.hash(password, user.email) },
2020-12-16 12:06:25 +05:00
token
);
await this.logout(false, "Account deleted.");
return true;
}
2021-05-25 16:19:58 +05:00
async fetchUser() {
2020-12-16 12:06:25 +05:00
try {
let token = await this.tokenManager.getAccessToken();
if (!token) return;
const user = await http.get(
`${constants.API_HOST}${ENDPOINTS.user}`,
token
);
if (user) {
2021-05-25 16:19:58 +05:00
await this.setUser(user);
EV.publish(EVENTS.userFetched, user);
2020-12-16 12:06:25 +05:00
return user;
} else {
return await this.getUser();
2020-12-16 12:06:25 +05:00
}
2021-01-22 22:08:56 +05:00
} catch (e) {
console.error(e);
return await this.getUser();
2020-12-16 12:06:25 +05:00
}
}
2020-12-22 13:13:18 +05:00
changePassword(oldPassword, newPassword) {
return this._updatePassword("change_password", {
old_password: oldPassword,
new_password: newPassword,
});
}
resetPassword(newPassword) {
return this._updatePassword("reset_password", {
new_password: newPassword,
});
}
2020-12-16 12:06:25 +05:00
async getEncryptionKey() {
const user = await this.getUser();
if (!user) return;
const key = await this._storage.getCryptoKey(`_uk_@${user.email}`);
2020-12-16 12:06:25 +05:00
return { key, salt: user.salt };
}
async getAttachmentsKey() {
try {
let user = await this.getUser();
if (!user) return;
if (!user.attachmentsKey) {
let token = await this.tokenManager.getAccessToken();
user = await http.get(`${constants.API_HOST}${ENDPOINTS.user}`, token);
}
const userEncryptionKey = await this.getEncryptionKey();
if (!userEncryptionKey) return;
if (!user.attachmentsKey) {
const key = await this._storage.generateRandomKey();
user.attachmentsKey = await this._storage.encrypt(
userEncryptionKey,
JSON.stringify(key)
);
await this.updateUser(user);
return key;
}
const plainData = await this._storage.decrypt(
userEncryptionKey,
user.attachmentsKey
);
return JSON.parse(plainData);
} catch (e) {
throw new Error(
`Could not get attachments encryption key. Please make sure you have Internet access. Error: ${e.message}`
);
}
}
2020-12-22 13:13:18 +05:00
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 verifyPassword(password) {
try {
const user = await this.getUser();
if (!user) return false;
const key = await this.getEncryptionKey();
const cipher = await this._storage.encrypt(key, "notesnook");
const plainText = await this._storage.decrypt({ password }, cipher);
return plainText === "notesnook";
} catch (e) {
return false;
}
}
2020-12-22 13:13:18 +05:00
async _updatePassword(type, data) {
const token = await this.tokenManager.getAccessToken();
const user = await this.getUser();
if (!token || !user) throw new Error("You are not logged in.");
const { email, salt } = user;
let { new_password, old_password } = data;
if (old_password && !(await this.verifyPassword(old_password)))
throw new Error("Incorrect old password.");
if (!new_password) throw new Error("New password is required.");
const attachmentsKey = await this.getAttachmentsKey();
data.encryptionKey = data.encryptionKey || (await this.getEncryptionKey());
await this._db.outbox.add(type, data, async () => {
await this._db.sync(true, true);
await this._storage.deriveCryptoKey(`_uk_@${email}`, {
password: new_password,
salt,
});
if (attachmentsKey) {
const userEncryptionKey = await this.getEncryptionKey();
if (!userEncryptionKey) return;
user.attachmentsKey = await this._storage.encrypt(
userEncryptionKey,
JSON.stringify(attachmentsKey)
);
await this.updateUser(user);
}
await this._db.sync(false, true);
if (old_password)
old_password = await this._storage.hash(old_password, email);
if (new_password)
new_password = await this._storage.hash(new_password, email);
await http.patch(
`${constants.AUTH_HOST}${ENDPOINTS.patchUser}`,
{
type,
old_password,
new_password,
},
token
);
});
2020-12-16 12:06:25 +05:00
return true;
}
}
export default UserManager;