mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-23 06:59:31 +01:00
feat: add mfa support (#23)
This commit is contained in:
@@ -24,6 +24,7 @@ import Attachments from "../collections/attachments";
|
|||||||
import Debug from "./debug";
|
import Debug from "./debug";
|
||||||
import { Mutex } from "async-mutex";
|
import { Mutex } from "async-mutex";
|
||||||
import NoteHistory from "../collections/note-history";
|
import NoteHistory from "../collections/note-history";
|
||||||
|
import MFAManager from "./mfa-manager";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {EventSource}
|
* @type {EventSource}
|
||||||
@@ -89,6 +90,7 @@ class Database {
|
|||||||
await this._validate();
|
await this._validate();
|
||||||
|
|
||||||
this.user = new UserManager(this.storage, this);
|
this.user = new UserManager(this.storage, this);
|
||||||
|
this.mfa = new MFAManager(this.storage, this);
|
||||||
this.syncer = new Sync(this);
|
this.syncer = new Sync(this);
|
||||||
this.vault = new Vault(this);
|
this.vault = new Vault(this);
|
||||||
this.conflicts = new Conflicts(this);
|
this.conflicts = new Conflicts(this);
|
||||||
|
|||||||
61
packages/core/api/mfa-manager.js
Normal file
61
packages/core/api/mfa-manager.js
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import http from "../utils/http";
|
||||||
|
import constants from "../utils/constants";
|
||||||
|
import TokenManager from "./token-manager";
|
||||||
|
|
||||||
|
const ENDPOINTS = {
|
||||||
|
setup: "/mfa/setup",
|
||||||
|
enable: "/mfa/enable",
|
||||||
|
disable: "/mfa/disable",
|
||||||
|
reset: "/mfa/reset",
|
||||||
|
recoveryCodes: "/mfa/recovery_codes",
|
||||||
|
};
|
||||||
|
|
||||||
|
class MFAManager {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {import("../database/storage").default} storage
|
||||||
|
* @param {import("../api/index").default} db
|
||||||
|
*/
|
||||||
|
constructor(storage, db) {
|
||||||
|
this._storage = storage;
|
||||||
|
this._db = db;
|
||||||
|
this.tokenManager = new TokenManager(storage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {"app" | "sms" | "email"} type
|
||||||
|
* @param {string} phoneNumber
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async setup(type, phoneNumber = undefined) {
|
||||||
|
const token = await this.tokenManager.getAccessToken();
|
||||||
|
if (!token) return;
|
||||||
|
return await http.post(
|
||||||
|
`${constants.AUTH_HOST}${ENDPOINTS.setup}`,
|
||||||
|
{
|
||||||
|
type,
|
||||||
|
phoneNumber,
|
||||||
|
},
|
||||||
|
token
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {"app" | "sms" | "email"} type
|
||||||
|
* @param {string} code
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async enable(type, code) {
|
||||||
|
const token = await this.tokenManager.getAccessToken();
|
||||||
|
if (!token) return;
|
||||||
|
return await http.post(
|
||||||
|
`${constants.AUTH_HOST}${ENDPOINTS.enable}`,
|
||||||
|
{ type, code },
|
||||||
|
token
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MFAManager;
|
||||||
@@ -61,7 +61,7 @@ class UserManager {
|
|||||||
return await this.login(email, password, hashedPassword);
|
return await this.login(email, password, hashedPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
async login(email, password, hashedPassword) {
|
async login(email, password, code, hashedPassword = undefined) {
|
||||||
if (!hashedPassword) {
|
if (!hashedPassword) {
|
||||||
hashedPassword = await this._storage.hash(password, email);
|
hashedPassword = await this._storage.hash(password, email);
|
||||||
}
|
}
|
||||||
@@ -70,6 +70,7 @@ class UserManager {
|
|||||||
await http.post(`${constants.AUTH_HOST}${ENDPOINTS.token}`, {
|
await http.post(`${constants.AUTH_HOST}${ENDPOINTS.token}`, {
|
||||||
username: email,
|
username: email,
|
||||||
password: hashedPassword,
|
password: hashedPassword,
|
||||||
|
code,
|
||||||
grant_type: "password",
|
grant_type: "password",
|
||||||
scope: "notesnook.sync offline_access openid IdentityServerApi",
|
scope: "notesnook.sync offline_access openid IdentityServerApi",
|
||||||
client_id: "notesnook",
|
client_id: "notesnook",
|
||||||
|
|||||||
@@ -38,9 +38,8 @@ function transformer(data, type) {
|
|||||||
if (type === "application/json") return JSON.stringify(data);
|
if (type === "application/json") return JSON.stringify(data);
|
||||||
else {
|
else {
|
||||||
return Object.entries(data)
|
return Object.entries(data)
|
||||||
.map(
|
.map(([key, value]) =>
|
||||||
([key, value]) =>
|
value ? `${encodeURIComponent(key)}=${encodeURIComponent(value)}` : ""
|
||||||
`${encodeURIComponent(key)}=${encodeURIComponent(value)}`
|
|
||||||
)
|
)
|
||||||
.join("&");
|
.join("&");
|
||||||
}
|
}
|
||||||
@@ -58,7 +57,7 @@ async function handleResponse(response) {
|
|||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
throw new Error(errorTransformer(json));
|
throw new RequestError(errorTransformer(json));
|
||||||
} else {
|
} else {
|
||||||
if (response.status === 429) throw new Error("You are being rate limited.");
|
if (response.status === 429) throw new Error("You are being rate limited.");
|
||||||
|
|
||||||
@@ -106,26 +105,41 @@ function getAuthorizationHeader(token) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function errorTransformer(errorJson) {
|
function errorTransformer(errorJson) {
|
||||||
|
let errorMessage = "Unknown error.";
|
||||||
|
let errorCode = "unknown";
|
||||||
|
|
||||||
if (!errorJson.error && !errorJson.errors && !errorJson.error_description)
|
if (!errorJson.error && !errorJson.errors && !errorJson.error_description)
|
||||||
return "Unknown error.";
|
return { description: errorMessage, code: errorCode, data: {} };
|
||||||
const { error, error_description, errors } = errorJson;
|
const { error, error_description, errors, data } = errorJson;
|
||||||
|
|
||||||
if (errors) {
|
if (errors) {
|
||||||
return errors.join("\n");
|
errorMessage = errors.join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (error) {
|
switch (error) {
|
||||||
case "invalid_grant": {
|
case "invalid_grant": {
|
||||||
switch (error_description) {
|
switch (error_description) {
|
||||||
case "invalid_username_or_password":
|
case "invalid_username_or_password":
|
||||||
return "Username or password incorrect.";
|
errorMessage = "Username or password incorrect.";
|
||||||
|
errorCode = error_description;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
return error_description || error;
|
errorMessage = error_description || error;
|
||||||
|
errorCode = error || "invalid_grant";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return error_description || error;
|
errorMessage = error_description || "An unknown error occured.";
|
||||||
|
errorCode = error;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
description: errorMessage,
|
||||||
|
code: errorCode,
|
||||||
|
data: data ? JSON.parse(data) : undefined,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -149,26 +163,10 @@ async function fetchWrapped(input, init) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// /**
|
class RequestError extends Error {
|
||||||
// *
|
constructor(error) {
|
||||||
// * @param {RequestInfo} resource
|
super(error.description);
|
||||||
// * @param {RequestInit} options
|
this.code = error.code;
|
||||||
// * @returns
|
this.data = error.data;
|
||||||
// */
|
}
|
||||||
// async function fetchWithTimeout(resource, options = {}) {
|
}
|
||||||
// try {
|
|
||||||
// const { timeout = 8000 } = options;
|
|
||||||
|
|
||||||
// const controller = new AbortController();
|
|
||||||
// const id = setTimeout(() => controller.abort(), timeout);
|
|
||||||
// const response = await fetch(resource, {
|
|
||||||
// ...options,
|
|
||||||
// signal: controller.signal,
|
|
||||||
// });
|
|
||||||
// clearTimeout(id);
|
|
||||||
// return response;
|
|
||||||
// } catch (e) {
|
|
||||||
// if (e.name === "AbortError") throw new Error("Request timed out.");
|
|
||||||
// throw e;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|||||||
Reference in New Issue
Block a user