feat: add mfa support (#23)

This commit is contained in:
Abdullah Atta
2022-03-11 22:49:24 +05:00
committed by GitHub
parent 9fe100f09f
commit c350da9fda
4 changed files with 96 additions and 34 deletions

View File

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

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

View File

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

View File

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