diff --git a/package.json b/package.json index f5dc2941..1d68985b 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ }, "devDependencies": { "@types/assert": "0.0.31", - "@types/bluebird": "^3.5.3", + "@types/bluebird": "^3.5.4", "@types/body-parser": "^1.16.3", "@types/ejs": "^2.3.33", "@types/express": "^4.0.35", diff --git a/src/lib/AuthenticationRegulator.ts b/src/lib/AuthenticationRegulator.ts index 89b62892..60fbbcfd 100644 --- a/src/lib/AuthenticationRegulator.ts +++ b/src/lib/AuthenticationRegulator.ts @@ -1,5 +1,5 @@ -import * as Promise from "bluebird"; +import * as BluebirdPromise from "bluebird"; import exceptions = require("./Exceptions"); const REGULATION_TRACE_TYPE = "regulation"; @@ -19,16 +19,16 @@ export default class AuthenticationRegulator { } // Mark authentication - mark(userid: string, is_success: boolean): Promise { + mark(userid: string, is_success: boolean): BluebirdPromise { return this._user_data_store.save_authentication_trace(userid, REGULATION_TRACE_TYPE, is_success); } - regulate(userid: string): Promise { + regulate(userid: string): BluebirdPromise { return this._user_data_store.get_last_authentication_traces(userid, REGULATION_TRACE_TYPE, false, 3) .then((docs: Array) => { if (docs.length < MAX_AUTHENTICATION_COUNT_IN_TIME_RANGE) { // less than the max authorized number of authentication in time range, thus authorizing access - return Promise.resolve(); + return BluebirdPromise.resolve(); } const oldest_doc = docs[MAX_AUTHENTICATION_COUNT_IN_TIME_RANGE - 1]; @@ -37,7 +37,7 @@ export default class AuthenticationRegulator { throw new exceptions.AuthenticationRegulationError("Max number of authentication. Please retry in few minutes."); } - return Promise.resolve(); + return BluebirdPromise.resolve(); }); } } diff --git a/src/lib/Exceptions.ts b/src/lib/Exceptions.ts index 0902e5dd..38d054c0 100644 --- a/src/lib/Exceptions.ts +++ b/src/lib/Exceptions.ts @@ -1,5 +1,4 @@ - export class LdapSeachError extends Error { constructor(message?: string) { super(message); diff --git a/src/lib/LdapClient.ts b/src/lib/LdapClient.ts index 27638430..879cf953 100644 --- a/src/lib/LdapClient.ts +++ b/src/lib/LdapClient.ts @@ -115,7 +115,7 @@ export class LdapClient { groups.push(docs[i].cn); } that.logger.debug("LDAP: got groups %s", groups); - return Promise.resolve(groups); + return BluebirdPromise.resolve(groups); }); } @@ -141,7 +141,7 @@ export class LdapClient { } } that.logger.debug("LDAP: got emails %s", emails); - return Promise.resolve(emails); + return BluebirdPromise.resolve(emails); }); } diff --git a/src/lib/Server.ts b/src/lib/Server.ts index 15c03edd..e61edf74 100644 --- a/src/lib/Server.ts +++ b/src/lib/Server.ts @@ -9,6 +9,7 @@ import TOTPValidator from "./TOTPValidator"; import TOTPGenerator from "./TOTPGenerator"; import RestApi from "./RestApi"; import { LdapClient } from "./LdapClient"; +import BluebirdPromise = require("bluebird"); import * as Express from "express"; import * as BodyParser from "body-parser"; @@ -20,7 +21,7 @@ import AccessController from "./access_control/AccessController"; export default class Server { private httpServer: http.Server; - start(yaml_configuration: UserConfiguration, deps: GlobalDependencies): Promise { + start(yaml_configuration: UserConfiguration, deps: GlobalDependencies): BluebirdPromise { const config = ConfigurationAdapter.adapt(yaml_configuration); const view_directory = Path.resolve(__dirname, "../views"); @@ -54,7 +55,7 @@ export default class Server { deps.winston.level = config.logs_level || "info"; const five_minutes = 5 * 60; - const data_store = new UserDataStore(datastore_options); + const data_store = new UserDataStore(datastore_options, deps.nedb); const regulator = new AuthenticationRegulator(data_store, five_minutes); const notifier = NotifierFactory.build(config.notifier, deps.nodemailer); const ldap = new LdapClient(config.ldap, deps.ldapjs, deps.winston); @@ -75,7 +76,7 @@ export default class Server { RestApi.setup(app); - return new Promise((resolve, reject) => { + return new BluebirdPromise((resolve, reject) => { this.httpServer = app.listen(config.port, function (err: string) { console.log("Listening on %d...", config.port); resolve(); diff --git a/src/lib/TOTPValidator.ts b/src/lib/TOTPValidator.ts index 2b009884..09d8e15c 100644 --- a/src/lib/TOTPValidator.ts +++ b/src/lib/TOTPValidator.ts @@ -18,6 +18,6 @@ export default class TOTPValidator { }); if (token == real_token) return BluebirdPromise.resolve(); - return BluebirdPromise.reject("Wrong challenge"); + return BluebirdPromise.reject(new Error("Wrong challenge")); } } \ No newline at end of file diff --git a/src/lib/UserDataStore.ts b/src/lib/UserDataStore.ts index e3cda5ad..468f7080 100644 --- a/src/lib/UserDataStore.ts +++ b/src/lib/UserDataStore.ts @@ -1,8 +1,8 @@ -import * as Promise from "bluebird"; +import * as BluebirdPromise from "bluebird"; import * as path from "path"; -import Nedb = require("nedb"); import { NedbAsync } from "nedb"; import { TOTPSecret } from "../types/TOTPSecret"; +import { Nedb } from "../types/Dependencies"; // Constants @@ -36,18 +36,20 @@ export default class UserDataStore { private _identity_check_tokens_collection: NedbAsync; private _authentication_traces_collection: NedbAsync; private _totp_secret_collection: NedbAsync; + private nedb: Nedb; - constructor(options?: Options) { - this._u2f_meta_collection = create_collection(U2F_META_COLLECTION_NAME, options); + constructor(options: Options, nedb: Nedb) { + this.nedb = nedb; + this._u2f_meta_collection = this.create_collection(U2F_META_COLLECTION_NAME, options); this._identity_check_tokens_collection = - create_collection(IDENTITY_CHECK_TOKENS_COLLECTION_NAME, options); + this.create_collection(IDENTITY_CHECK_TOKENS_COLLECTION_NAME, options); this._authentication_traces_collection = - create_collection(AUTHENTICATION_TRACES_COLLECTION_NAME, options); + this.create_collection(AUTHENTICATION_TRACES_COLLECTION_NAME, options); this._totp_secret_collection = - create_collection(TOTP_SECRETS_COLLECTION_NAME, options); + this.create_collection(TOTP_SECRETS_COLLECTION_NAME, options); } - set_u2f_meta(userid: string, appid: string, meta: Object): Promise { + set_u2f_meta(userid: string, appid: string, meta: Object): BluebirdPromise { const newDocument = { userid: userid, appid: appid, @@ -62,7 +64,7 @@ export default class UserDataStore { return this._u2f_meta_collection.updateAsync(filter, newDocument, { upsert: true }); } - get_u2f_meta(userid: string, appid: string): Promise { + get_u2f_meta(userid: string, appid: string): BluebirdPromise { const filter = { userid: userid, appid: appid @@ -81,7 +83,7 @@ export default class UserDataStore { return this._authentication_traces_collection.insertAsync(newDocument); } - get_last_authentication_traces(userid: string, type: string, is_success: boolean, count: number): Promise { + get_last_authentication_traces(userid: string, type: string, is_success: boolean, count: number): BluebirdPromise { const q = { userid: userid, type: type, @@ -90,11 +92,11 @@ export default class UserDataStore { const query = this._authentication_traces_collection.find(q) .sort({ date: -1 }).limit(count); - const query_promisified = Promise.promisify(query.exec, { context: query }); + const query_promisified = BluebirdPromise.promisify(query.exec, { context: query }); return query_promisified(); } - issue_identity_check_token(userid: string, token: string, data: string | object, max_age: number): Promise { + issue_identity_check_token(userid: string, token: string, data: string | object, max_age: number): BluebirdPromise { const newDocument = { userid: userid, token: token, @@ -108,7 +110,7 @@ export default class UserDataStore { return this._identity_check_tokens_collection.insertAsync(newDocument); } - consume_identity_check_token(token: string): Promise { + consume_identity_check_token(token: string): BluebirdPromise { const query = { token: token }; @@ -116,26 +118,26 @@ export default class UserDataStore { return this._identity_check_tokens_collection.findOneAsync(query) .then(function (doc) { if (!doc) { - return Promise.reject("Registration token does not exist"); + return BluebirdPromise.reject("Registration token does not exist"); } const max_date = doc.max_date; const current_date = new Date(); if (current_date > max_date) { - return Promise.reject("Registration token is not valid anymore"); + return BluebirdPromise.reject("Registration token is not valid anymore"); } - return Promise.resolve(doc.content); + return BluebirdPromise.resolve(doc.content); }) .then((content) => { - return Promise.join(this._identity_check_tokens_collection.removeAsync(query), - Promise.resolve(content)); + return BluebirdPromise.join(this._identity_check_tokens_collection.removeAsync(query), + BluebirdPromise.resolve(content)); }) .then((v) => { - return Promise.resolve(v[1]); + return BluebirdPromise.resolve(v[1]); }); } - set_totp_secret(userid: string, secret: TOTPSecret): Promise { + set_totp_secret(userid: string, secret: TOTPSecret): BluebirdPromise { const doc = { userid: userid, secret: secret @@ -147,23 +149,23 @@ export default class UserDataStore { return this._totp_secret_collection.updateAsync(query, doc, { upsert: true }); } - get_totp_secret(userid: string): Promise { + get_totp_secret(userid: string): BluebirdPromise { const query = { userid: userid }; return this._totp_secret_collection.findOneAsync(query); } -} - -function create_collection(name: string, options: any): NedbAsync { - const datastore_options = { - inMemoryOnly: options.inMemoryOnly || false, - autoload: true, - filename: "" - }; - - if (options.directory) - datastore_options.filename = path.resolve(options.directory, name); - - return Promise.promisifyAll(new Nedb(datastore_options)) as NedbAsync; + + private create_collection(name: string, options: any): NedbAsync { + const datastore_options = { + inMemoryOnly: options.inMemoryOnly || false, + autoload: true, + filename: "" + }; + + if (options.directory) + datastore_options.filename = path.resolve(options.directory, name); + + return BluebirdPromise.promisifyAll(new this.nedb(datastore_options)) as NedbAsync; + } } diff --git a/src/lib/identity_check.js b/src/lib/identity_check.js index 72fd603a..0a1f68fe 100644 --- a/src/lib/identity_check.js +++ b/src/lib/identity_check.js @@ -1,7 +1,7 @@ var objectPath = require('object-path'); var randomstring = require('randomstring'); -var Promise = require('bluebird'); +var BluebirdPromise = require('bluebird'); var util = require('util'); var exceptions = require('./Exceptions'); var fs = require('fs'); @@ -27,7 +27,7 @@ IdentityCheck.prototype.issue_token = function(userid, content, logger) { this._logger.debug('identity_check: issue identity token %s for 5 minutes', token); return this._user_data_store.issue_identity_check_token(userid, token, content, five_minutes) .then(function() { - return Promise.resolve(token); + return BluebirdPromise.resolve(token); }); } diff --git a/src/lib/notifiers/GMailNotifier.ts b/src/lib/notifiers/GMailNotifier.ts index 1cd11a3c..ee6136ad 100644 --- a/src/lib/notifiers/GMailNotifier.ts +++ b/src/lib/notifiers/GMailNotifier.ts @@ -1,5 +1,5 @@ -import * as Promise from "bluebird"; +import * as BluebirdPromise from "bluebird"; import * as fs from "fs"; import * as ejs from "ejs"; import nodemailer = require("nodemailer"); @@ -23,10 +23,10 @@ export class GMailNotifier extends INotifier { pass: options.password } }); - this.transporter = Promise.promisifyAll(transporter); + this.transporter = BluebirdPromise.promisifyAll(transporter); } - notify(identity: Identity, subject: string, link: string): Promise { + notify(identity: Identity, subject: string, link: string): BluebirdPromise { const d = { url: link, button_title: "Continue", diff --git a/src/lib/routes/FirstFactor.ts b/src/lib/routes/FirstFactor.ts index 9b24afe0..3a67f468 100644 --- a/src/lib/routes/FirstFactor.ts +++ b/src/lib/routes/FirstFactor.ts @@ -1,10 +1,10 @@ import exceptions = require("../Exceptions"); import objectPath = require("object-path"); -import Promise = require("bluebird"); +import BluebirdPromise = require("bluebird"); import express = require("express"); -export = function(req: express.Request, res: express.Response) { +export = function (req: express.Request, res: express.Response) { const username = req.body.username; const password = req.body.password; if (!username || !password) { @@ -24,53 +24,53 @@ export = function(req: express.Request, res: express.Response) { logger.debug("1st factor: username=%s", username); regulator.regulate(username) - .then(function() { - return ldap.bind(username, password); - }) - .then(function() { - objectPath.set(req, "session.auth_session.userid", username); - objectPath.set(req, "session.auth_session.first_factor", true); - logger.info("1st factor: LDAP binding successful"); - logger.debug("1st factor: Retrieve email from LDAP"); - return Promise.join(ldap.get_emails(username), ldap.get_groups(username)); - }) - .then(function(data: string[2]) { - const emails = data[0]; - const groups = data[1]; + .then(function () { + return ldap.bind(username, password); + }) + .then(function () { + objectPath.set(req, "session.auth_session.userid", username); + objectPath.set(req, "session.auth_session.first_factor", true); + logger.info("1st factor: LDAP binding successful"); + logger.debug("1st factor: Retrieve email from LDAP"); + return BluebirdPromise.join(ldap.get_emails(username), ldap.get_groups(username)); + }) + .then(function (data: string[2]) { + const emails = data[0]; + const groups = data[1]; - if (!emails && emails.length <= 0) throw new Error("No email found"); - logger.debug("1st factor: Retrieved email are %s", emails); - objectPath.set(req, "session.auth_session.email", emails[0]); + if (!emails && emails.length <= 0) throw new Error("No email found"); + logger.debug("1st factor: Retrieved email are %s", emails); + objectPath.set(req, "session.auth_session.email", emails[0]); - const isAllowed = accessController.isDomainAllowedForUser(username, groups); - if (!isAllowed) throw new Error("User not allowed to visit this domain"); + const isAllowed = accessController.isDomainAllowedForUser(username, groups); + if (!isAllowed) throw new Error("User not allowed to visit this domain"); - regulator.mark(username, true); - res.status(204); - res.send(); - }) - .catch(exceptions.LdapSeachError, function(err: Error) { - logger.error("1st factor: Unable to retrieve email from LDAP", err); - res.status(500); - res.send(); - }) - .catch(exceptions.LdapBindError, function(err: Error) { - logger.error("1st factor: LDAP binding failed"); - logger.debug("1st factor: LDAP binding failed due to ", err); - regulator.mark(username, false); - res.status(401); - res.send("Bad credentials"); - }) - .catch(exceptions.AuthenticationRegulationError, function(err: Error) { - logger.error("1st factor: the regulator rejected the authentication of user %s", username); - logger.debug("1st factor: authentication rejected due to %s", err); - res.status(403); - res.send("Access has been restricted for a few minutes..."); - }) - .catch(function(err: Error) { - console.log(err.stack); - logger.error("1st factor: Unhandled error %s", err); - res.status(500); - res.send("Internal error"); - }); + regulator.mark(username, true); + res.status(204); + res.send(); + }) + .catch(exceptions.LdapSeachError, function (err: Error) { + logger.error("1st factor: Unable to retrieve email from LDAP", err); + res.status(500); + res.send(); + }) + .catch(exceptions.LdapBindError, function (err: Error) { + logger.error("1st factor: LDAP binding failed"); + logger.debug("1st factor: LDAP binding failed due to ", err); + regulator.mark(username, false); + res.status(401); + res.send("Bad credentials"); + }) + .catch(exceptions.AuthenticationRegulationError, function (err: Error) { + logger.error("1st factor: the regulator rejected the authentication of user %s", username); + logger.debug("1st factor: authentication rejected due to %s", err); + res.status(403); + res.send("Access has been restricted for a few minutes..."); + }) + .catch(function (err: Error) { + console.log(err.stack); + logger.error("1st factor: Unhandled error %s", err); + res.status(500); + res.send("Internal error"); + }); }; diff --git a/src/lib/routes/TOTPAuthenticator.ts b/src/lib/routes/TOTPAuthenticator.ts new file mode 100644 index 00000000..7f63f2ff --- /dev/null +++ b/src/lib/routes/TOTPAuthenticator.ts @@ -0,0 +1,49 @@ + +import exceptions = require("../Exceptions"); +import objectPath = require("object-path"); +import express = require("express"); +import { TOTPSecretDocument } from "../UserDataStore"; +import BluebirdPromise = require("bluebird"); + +const UNAUTHORIZED_MESSAGE = "Unauthorized access"; + +export = function(req: express.Request, res: express.Response) { + const logger = req.app.get("logger"); + const userid = objectPath.get(req, "session.auth_session.userid"); + logger.info("POST 2ndfactor totp: Initiate TOTP validation for user %s", userid); + + if (!userid) { + logger.error("POST 2ndfactor totp: No user id in the session"); + res.status(403); + res.send(); + return; + } + + const token = req.body.token; + const totpValidator = req.app.get("totp validator"); + const userDataStore = req.app.get("user data store"); + + logger.debug("POST 2ndfactor totp: Fetching secret for user %s", userid); + userDataStore.get_totp_secret(userid) + .then(function (doc: TOTPSecretDocument) { + logger.debug("POST 2ndfactor totp: TOTP secret is %s", JSON.stringify(doc)); + return totpValidator.validate(token, doc.secret.base32); + }) + .then(function () { + logger.debug("POST 2ndfactor totp: TOTP validation succeeded"); + objectPath.set(req, "session.auth_session.second_factor", true); + res.status(204); + res.send(); + }) + .catch(exceptions.InvalidTOTPError, function (err: Error) { + logger.error("POST 2ndfactor totp: Invalid TOTP token %s", err.message); + res.status(401); + res.send("Invalid TOTP token"); + }) + .catch(function (err: Error) { + console.log(err.stack); + logger.error("POST 2ndfactor totp: Internal error %s", err.message); + res.status(500); + res.send("Internal error"); + }); +}; diff --git a/src/lib/routes/reset_password.js b/src/lib/routes/reset_password.js index d2271d57..ab39ff5a 100644 --- a/src/lib/routes/reset_password.js +++ b/src/lib/routes/reset_password.js @@ -1,5 +1,5 @@ -var Promise = require('bluebird'); +var BluebirdPromise = require('bluebird'); var objectPath = require('object-path'); var exceptions = require('../Exceptions'); var CHALLENGE = 'reset-password'; @@ -19,7 +19,7 @@ module.exports = { function pre_check(req) { var userid = objectPath.get(req, 'body.userid'); if(!userid) { - return Promise.reject(new exceptions.AccessDeniedError("No user id provided")); + return BluebirdPromise.reject(new exceptions.AccessDeniedError("No user id provided")); } var ldap = req.app.get('ldap'); @@ -30,7 +30,7 @@ function pre_check(req) { var identity = {} identity.email = emails[0]; identity.userid = userid; - return Promise.resolve(identity); + return BluebirdPromise.resolve(identity); }); } diff --git a/src/lib/routes/second_factor.js b/src/lib/routes/second_factor.js index f57149dd..413b4337 100644 --- a/src/lib/routes/second_factor.js +++ b/src/lib/routes/second_factor.js @@ -1,9 +1,10 @@ var denyNotLogged = require('./deny_not_logged'); -var u2f = require('./u2f'); +var u2f = require('./u2f'); +var TOTPAuthenticator = require("./TOTPAuthenticator"); module.exports = { - totp: denyNotLogged(require('./totp')), + totp: denyNotLogged(TOTPAuthenticator), u2f: { register_request: u2f.register_request, register: u2f.register, diff --git a/src/lib/routes/totp.js b/src/lib/routes/totp.js deleted file mode 100644 index dc5c5721..00000000 --- a/src/lib/routes/totp.js +++ /dev/null @@ -1,49 +0,0 @@ - -module.exports = totp_fn; - -var objectPath = require('object-path'); -var exceptions = require('../../../src/lib/Exceptions'); - -var UNAUTHORIZED_MESSAGE = 'Unauthorized access'; - -function totp_fn(req, res) { - var logger = req.app.get('logger'); - var userid = objectPath.get(req, 'session.auth_session.userid'); - logger.info('POST 2ndfactor totp: Initiate TOTP validation for user %s', userid); - - if(!userid) { - logger.error('POST 2ndfactor totp: No user id in the session'); - res.status(403); - res.send(); - return; - } - - var token = req.body.token; - var totpValidator = req.app.get('totp validator'); - var data_store = req.app.get('user data store'); - - logger.debug('POST 2ndfactor totp: Fetching secret for user %s', userid); - data_store.get_totp_secret(userid) - .then(function(doc) { - logger.debug('POST 2ndfactor totp: TOTP secret is %s', JSON.stringify(doc)); - return totpValidator.validate(token, doc.secret.base32); - }) - .then(function() { - logger.debug('POST 2ndfactor totp: TOTP validation succeeded'); - objectPath.set(req, 'session.auth_session.second_factor', true); - res.status(204); - res.send(); - }, function(err) { - throw new exceptions.InvalidTOTPError(); - }) - .catch(exceptions.InvalidTOTPError, function(err) { - logger.error('POST 2ndfactor totp: Invalid TOTP token %s', err); - res.status(401); - res.send('Invalid TOTP token'); - }) - .catch(function(err) { - logger.error('POST 2ndfactor totp: Internal error %s', err); - res.status(500); - res.send('Internal error'); - }); -} diff --git a/src/lib/routes/totp_register.js b/src/lib/routes/totp_register.js index 37c7e7ce..0f163f36 100644 --- a/src/lib/routes/totp_register.js +++ b/src/lib/routes/totp_register.js @@ -1,5 +1,5 @@ var objectPath = require('object-path'); -var Promise = require('bluebird'); +var BluebirdPromise = require('bluebird'); var CHALLENGE = 'totp-register'; @@ -18,20 +18,20 @@ module.exports = { function pre_check(req) { var first_factor_passed = objectPath.get(req, 'session.auth_session.first_factor'); if(!first_factor_passed) { - return Promise.reject('Authentication required before registering TOTP secret key'); + return BluebirdPromise.reject('Authentication required before registering TOTP secret key'); } var userid = objectPath.get(req, 'session.auth_session.userid'); var email = objectPath.get(req, 'session.auth_session.email'); if(!(userid && email)) { - return Promise.reject('User ID or email is missing'); + return BluebirdPromise.reject('User ID or email is missing'); } var identity = {}; identity.email = email; identity.userid = userid; - return Promise.resolve(identity); + return BluebirdPromise.resolve(identity); } // Generate a secret and send it to the user diff --git a/src/lib/routes/u2f_register.js b/src/lib/routes/u2f_register.js index 5161d965..220a1716 100644 --- a/src/lib/routes/u2f_register.js +++ b/src/lib/routes/u2f_register.js @@ -10,7 +10,7 @@ module.exports = { var objectPath = require('object-path'); var u2f_common = require('./u2f_common'); -var Promise = require('bluebird'); +var BluebirdPromise = require('bluebird'); function register_request(req, res) { var logger = req.app.get('logger'); diff --git a/src/lib/routes/u2f_register_handler.js b/src/lib/routes/u2f_register_handler.js index 2c2600a3..f321dc52 100644 --- a/src/lib/routes/u2f_register_handler.js +++ b/src/lib/routes/u2f_register_handler.js @@ -1,6 +1,6 @@ var objectPath = require('object-path'); -var Promise = require('bluebird'); +var BluebirdPromise = require('bluebird'); var CHALLENGE = 'u2f-register'; @@ -19,19 +19,19 @@ module.exports = { function pre_check(req) { var first_factor_passed = objectPath.get(req, 'session.auth_session.first_factor'); if(!first_factor_passed) { - return Promise.reject('Authentication required before issuing a u2f registration request'); + return BluebirdPromise.reject('Authentication required before issuing a u2f registration request'); } var userid = objectPath.get(req, 'session.auth_session.userid'); var email = objectPath.get(req, 'session.auth_session.email'); if(!(userid && email)) { - return Promise.reject('User ID or email is missing'); + return BluebirdPromise.reject('User ID or email is missing'); } var identity = {}; identity.email = email; identity.userid = userid; - return Promise.resolve(identity); + return BluebirdPromise.resolve(identity); } diff --git a/src/lib/routes/verify.js b/src/lib/routes/verify.js index 6ebbc852..1b40649b 100644 --- a/src/lib/routes/verify.js +++ b/src/lib/routes/verify.js @@ -2,31 +2,31 @@ module.exports = verify; var objectPath = require('object-path'); -var Promise = require('bluebird'); +var BluebirdPromise = require('bluebird'); function verify_filter(req, res) { var logger = req.app.get('logger'); if(!objectPath.has(req, 'session.auth_session')) - return Promise.reject('No auth_session variable'); + return BluebirdPromise.reject('No auth_session variable'); if(!objectPath.has(req, 'session.auth_session.first_factor')) - return Promise.reject('No first factor variable'); + return BluebirdPromise.reject('No first factor variable'); if(!objectPath.has(req, 'session.auth_session.second_factor')) - return Promise.reject('No second factor variable'); + return BluebirdPromise.reject('No second factor variable'); if(!objectPath.has(req, 'session.auth_session.userid')) - return Promise.reject('No userid variable'); + return BluebirdPromise.reject('No userid variable'); var host = objectPath.get(req, 'headers.host'); var domain = host.split(':')[0]; if(!req.session.auth_session.first_factor || !req.session.auth_session.second_factor) - return Promise.reject('First or second factor not validated'); + return BluebirdPromise.reject('First or second factor not validated'); - return Promise.resolve(); + return BluebirdPromise.resolve(); } function verify(req, res) { diff --git a/src/types/authdog.d.ts b/src/types/authdog.d.ts index 9cb1121f..4405f6f1 100644 --- a/src/types/authdog.d.ts +++ b/src/types/authdog.d.ts @@ -1,4 +1,6 @@ +import BluebirdPromise = require("bluebird"); + declare module "authdog" { interface RegisterRequest { challenge: string; @@ -60,8 +62,8 @@ declare module "authdog" { counter: Uint32Array } - export function startRegistration(appId: AppId, registeredKeys: RegisteredKeys, options?: Options): Promise; - export function finishRegistration(registrationRequest: RegistrationRequest, registrationResponse: RegistrationResponse): Promise; - export function startAuthentication(appId: AppId, registeredKeys: RegisteredKeys, options: Options): Promise; - export function finishAuthentication(challenge: string, deviceResponse: AuthenticationResponse, registeredKeys: RegisteredKeys): Promise; + export function startRegistration(appId: AppId, registeredKeys: RegisteredKeys, options?: Options): BluebirdPromise; + export function finishRegistration(registrationRequest: RegistrationRequest, registrationResponse: RegistrationResponse): BluebirdPromise; + export function startAuthentication(appId: AppId, registeredKeys: RegisteredKeys, options: Options): BluebirdPromise; + export function finishAuthentication(challenge: string, deviceResponse: AuthenticationResponse, registeredKeys: RegisteredKeys): BluebirdPromise; } \ No newline at end of file diff --git a/src/types/ldapjs-async.d.ts b/src/types/ldapjs-async.d.ts index c2f03775..e5fad359 100644 --- a/src/types/ldapjs-async.d.ts +++ b/src/types/ldapjs-async.d.ts @@ -1,11 +1,11 @@ import ldapjs = require("ldapjs"); -import * as Promise from "bluebird"; +import * as BluebirdPromise from "bluebird"; import { EventEmitter } from "events"; declare module "ldapjs" { export interface ClientAsync { - bindAsync(username: string, password: string): Promise; - searchAsync(base: string, query: ldapjs.SearchOptions): Promise; - modifyAsync(userdn: string, change: ldapjs.Change): Promise; + bindAsync(username: string, password: string): BluebirdPromise; + searchAsync(base: string, query: ldapjs.SearchOptions): BluebirdPromise; + modifyAsync(userdn: string, change: ldapjs.Change): BluebirdPromise; } } \ No newline at end of file diff --git a/src/types/nedb-async.d.ts b/src/types/nedb-async.d.ts index e5dc9926..1f4fe042 100644 --- a/src/types/nedb-async.d.ts +++ b/src/types/nedb-async.d.ts @@ -1,12 +1,12 @@ import Nedb = require("nedb"); -import * as Promise from "bluebird"; +import BluebirdPromise = require("bluebird"); declare module "nedb" { export class NedbAsync extends Nedb { constructor(pathOrOptions?: string | Nedb.DataStoreOptions); - updateAsync(query: any, updateQuery: any, options?: Nedb.UpdateOptions): Promise; - findOneAsync(query: any): Promise; - insertAsync(newDoc: T): Promise; - removeAsync(query: any): Promise; + updateAsync(query: any, updateQuery: any, options?: Nedb.UpdateOptions): BluebirdPromise; + findOneAsync(query: any): BluebirdPromise; + insertAsync(newDoc: T): BluebirdPromise; + removeAsync(query: any): BluebirdPromise; } } \ No newline at end of file diff --git a/src/types/request-async.d.ts b/src/types/request-async.d.ts index 38a36822..164d6919 100644 --- a/src/types/request-async.d.ts +++ b/src/types/request-async.d.ts @@ -1,14 +1,14 @@ -import * as Promise from "bluebird"; +import * as BluebirdPromise from "bluebird"; import * as request from "request"; declare module "request" { export interface RequestAsync extends RequestAPI { - getAsync(uri: string, options?: RequiredUriUrl): Promise; - getAsync(uri: string): Promise; - getAsync(options: RequiredUriUrl & CoreOptions): Promise; + getAsync(uri: string, options?: RequiredUriUrl): BluebirdPromise; + getAsync(uri: string): BluebirdPromise; + getAsync(options: RequiredUriUrl & CoreOptions): BluebirdPromise; - postAsync(uri: string, options?: CoreOptions): Promise; - postAsync(uri: string): Promise; - postAsync(options: RequiredUriUrl & CoreOptions): Promise; + postAsync(uri: string, options?: CoreOptions): BluebirdPromise; + postAsync(uri: string): BluebirdPromise; + postAsync(options: RequiredUriUrl & CoreOptions): BluebirdPromise; } } \ No newline at end of file diff --git a/test/unitary/AuthenticationRegulator.test.ts b/test/unitary/AuthenticationRegulator.test.ts index 88707aff..27053790 100644 --- a/test/unitary/AuthenticationRegulator.test.ts +++ b/test/unitary/AuthenticationRegulator.test.ts @@ -3,13 +3,14 @@ import AuthenticationRegulator from "../../src/lib/AuthenticationRegulator"; import UserDataStore from "../../src/lib/UserDataStore"; import MockDate = require("mockdate"); import exceptions = require("../../src/lib/Exceptions"); +import nedb = require("nedb"); describe("test authentication regulator", function() { it("should mark 2 authentication and regulate (resolve)", function() { const options = { inMemoryOnly: true }; - const data_store = new UserDataStore(options); + const data_store = new UserDataStore(options, nedb); const regulator = new AuthenticationRegulator(data_store, 10); const user = "user"; @@ -26,7 +27,7 @@ describe("test authentication regulator", function() { const options = { inMemoryOnly: true }; - const data_store = new UserDataStore(options); + const data_store = new UserDataStore(options, nedb); const regulator = new AuthenticationRegulator(data_store, 10); const user = "user"; @@ -49,7 +50,7 @@ describe("test authentication regulator", function() { const options = { inMemoryOnly: true }; - const data_store = new UserDataStore(options); + const data_store = new UserDataStore(options, nedb); const regulator = new AuthenticationRegulator(data_store, 10); const user = "user"; diff --git a/test/unitary/TOTPValidator.ts b/test/unitary/TOTPValidator.test.ts similarity index 82% rename from test/unitary/TOTPValidator.ts rename to test/unitary/TOTPValidator.test.ts index e2d06f67..84baa040 100644 --- a/test/unitary/TOTPValidator.ts +++ b/test/unitary/TOTPValidator.test.ts @@ -18,12 +18,12 @@ describe("test TOTP validation", function() { return totpValidator.validate(token, totp_secret); }); - it("should not validate a wrong TOTP token", function() { + it("should not validate a wrong TOTP token", function(done) { const totp_secret = "NBD2ZV64R9UV1O7K"; const token = "wrong token"; - return totpValidator.validate(token, totp_secret) + totpValidator.validate(token, totp_secret) .catch(function() { - return Promise.resolve(); + done(); }); }); }); diff --git a/test/unitary/UserDataStore.test.ts b/test/unitary/UserDataStore.test.ts index a631a946..a7ce7dd9 100644 --- a/test/unitary/UserDataStore.test.ts +++ b/test/unitary/UserDataStore.test.ts @@ -2,7 +2,7 @@ import UserDataStore from "../../src/lib/UserDataStore"; import { U2FMetaDocument, Options } from "../../src/lib/UserDataStore"; -import DataStore = require("nedb"); +import nedb = require("nedb"); import assert = require("assert"); import Promise = require("bluebird"); import sinon = require("sinon"); @@ -20,7 +20,7 @@ describe("test user data store", () => { describe("test u2f meta", () => { it("should save a u2f meta", function () { - const data_store = new UserDataStore(options); + const data_store = new UserDataStore(options, nedb); const userid = "user"; const app_id = "https://localhost"; @@ -40,7 +40,7 @@ describe("test user data store", () => { inMemoryOnly: true }; - const data_store = new UserDataStore(options); + const data_store = new UserDataStore(options, nedb); const userid = "user"; const app_id = "https://localhost"; @@ -60,7 +60,7 @@ describe("test user data store", () => { inMemoryOnly: true }; - const data_store = new UserDataStore(options); + const data_store = new UserDataStore(options, nedb); const userid = "user"; const app_id = "https://localhost"; @@ -86,7 +86,7 @@ describe("test user data store", () => { describe("test u2f registration token", () => { it("should save u2f registration token", function () { - const data_store = new UserDataStore(options); + const data_store = new UserDataStore(options, nedb); const userid = "user"; const token = "token"; @@ -109,7 +109,7 @@ describe("test user data store", () => { }); it("should save u2f registration token and consume it", function (done) { - const data_store = new UserDataStore(options); + const data_store = new UserDataStore(options, nedb); const userid = "user"; const token = "token"; @@ -128,7 +128,7 @@ describe("test user data store", () => { }); it("should not be able to consume registration token twice", function (done) { - const data_store = new UserDataStore(options); + const data_store = new UserDataStore(options, nedb); const userid = "user"; const token = "token"; @@ -148,7 +148,7 @@ describe("test user data store", () => { }); it("should fail when token does not exist", function () { - const data_store = new UserDataStore(options); + const data_store = new UserDataStore(options, nedb); const token = "token"; @@ -162,7 +162,7 @@ describe("test user data store", () => { }); it("should fail when token expired", function (done) { - const data_store = new UserDataStore(options); + const data_store = new UserDataStore(options, nedb); const userid = "user"; const token = "token"; @@ -181,7 +181,7 @@ describe("test user data store", () => { }); it("should save the userid and some data with the token", function (done) { - const data_store = new UserDataStore(options); + const data_store = new UserDataStore(options, nedb); const userid = "user"; const token = "token"; diff --git a/test/unitary/mocks/AccessController.ts b/test/unitary/mocks/AccessController.ts index a0c97853..ce46c0b8 100644 --- a/test/unitary/mocks/AccessController.ts +++ b/test/unitary/mocks/AccessController.ts @@ -1,8 +1,12 @@ import sinon = require("sinon"); -export = function () { +export interface AccessControllerMock { + isDomainAllowedForUser: sinon.SinonStub; +} + +export function AccessControllerMock() { return { isDomainAllowedForUser: sinon.stub() }; -}; +} diff --git a/test/unitary/mocks/AuthenticationRegulator.ts b/test/unitary/mocks/AuthenticationRegulator.ts index d4464c45..2d789d94 100644 --- a/test/unitary/mocks/AuthenticationRegulator.ts +++ b/test/unitary/mocks/AuthenticationRegulator.ts @@ -1,9 +1,15 @@ import sinon = require("sinon"); -export = function () { + +export interface AuthenticationRegulatorMock { + mark: sinon.SinonStub; + regulate: sinon.SinonStub; +} + +export function AuthenticationRegulatorMock() { return { mark: sinon.stub(), regulate: sinon.stub() }; -}; +} diff --git a/test/unitary/mocks/TOTPValidator.ts b/test/unitary/mocks/TOTPValidator.ts new file mode 100644 index 00000000..56434c79 --- /dev/null +++ b/test/unitary/mocks/TOTPValidator.ts @@ -0,0 +1,12 @@ + +import sinon = require("sinon"); + +export interface TOTPValidatorMock { + validate: sinon.SinonStub; +} + +export function TOTPValidatorMock(): TOTPValidatorMock { + return { + validate: sinon.stub() + }; +} diff --git a/test/unitary/mocks/UserDataStore.ts b/test/unitary/mocks/UserDataStore.ts index fc2774f1..a2221efa 100644 --- a/test/unitary/mocks/UserDataStore.ts +++ b/test/unitary/mocks/UserDataStore.ts @@ -6,6 +6,7 @@ export interface UserDataStore { get_u2f_meta: sinon.SinonStub; issue_identity_check_token: sinon.SinonStub; consume_identity_check_token: sinon.SinonStub; + get_totp_secret: sinon.SinonStub; } export function UserDataStore(): UserDataStore { @@ -13,6 +14,7 @@ export function UserDataStore(): UserDataStore { set_u2f_meta: sinon.stub(), get_u2f_meta: sinon.stub(), issue_identity_check_token: sinon.stub(), - consume_identity_check_token: sinon.stub() + consume_identity_check_token: sinon.stub(), + get_totp_secret: sinon.stub() }; } diff --git a/test/unitary/routes/FirstFactor.test.ts b/test/unitary/routes/FirstFactor.test.ts index 9d0b1af3..8c48a0eb 100644 --- a/test/unitary/routes/FirstFactor.test.ts +++ b/test/unitary/routes/FirstFactor.test.ts @@ -12,14 +12,14 @@ import { LdapClientMock } from "../mocks/LdapClient"; import ExpressMock = require("../mocks/express"); describe("test the first factor validation route", function() { - let req: any; - let res: any; + let req: ExpressMock.RequestMock; + let res: ExpressMock.ResponseMock; let emails: string[]; let groups: string[]; let configuration; let ldapMock: LdapClientMock; - let regulator: any; - let accessController: any; + let regulator: AuthenticationRegulatorMock.AuthenticationRegulatorMock; + let accessController: AccessControllerMock.AccessControllerMock; beforeEach(function() { configuration = { @@ -34,10 +34,10 @@ describe("test the first factor validation route", function() { ldapMock = LdapClientMock(); - accessController = AccessControllerMock(); + accessController = AccessControllerMock.AccessControllerMock(); accessController.isDomainAllowedForUser.returns(true); - regulator = AuthenticationRegulatorMock(); + regulator = AuthenticationRegulatorMock.AuthenticationRegulatorMock(); regulator.regulate.returns(BluebirdPromise.resolve()); regulator.mark.returns(BluebirdPromise.resolve()); @@ -75,15 +75,15 @@ describe("test the first factor validation route", function() { }); ldapMock.bind.withArgs("username").returns(BluebirdPromise.resolve()); ldapMock.get_emails.returns(BluebirdPromise.resolve(emails)); - FirstFactor(req, res); + FirstFactor(req as any, res as any); }); }); it("should retrieve email from LDAP", function(done) { res.send = sinon.spy(function() { done(); }); ldapMock.bind.returns(BluebirdPromise.resolve()); - ldapMock.get_emails = sinon.stub().withArgs("usernam").returns(BluebirdPromise.resolve([{mail: ["test@example.com"] }])); - FirstFactor(req, res); + ldapMock.get_emails = sinon.stub().withArgs("username").returns(BluebirdPromise.resolve([{mail: ["test@example.com"] }])); + FirstFactor(req as any, res as any); }); it("should set email as session variables", function() { @@ -95,7 +95,7 @@ describe("test the first factor validation route", function() { const emails = [ "test_ok@example.com" ]; ldapMock.bind.returns(BluebirdPromise.resolve()); ldapMock.get_emails.returns(BluebirdPromise.resolve(emails)); - FirstFactor(req, res); + FirstFactor(req as any, res as any); }); }); @@ -105,8 +105,8 @@ describe("test the first factor validation route", function() { assert.equal(regulator.mark.getCall(0).args[0], "username"); done(); }); - ldapMock.bind.throws(new exceptions.LdapBindError("Bad credentials")); - FirstFactor(req, res); + ldapMock.bind.returns(BluebirdPromise.reject(new exceptions.LdapBindError("Bad credentials"))); + FirstFactor(req as any, res as any); }); it("should return status code 500 when LDAP search throws", function(done) { @@ -115,8 +115,8 @@ describe("test the first factor validation route", function() { done(); }); ldapMock.bind.returns(BluebirdPromise.resolve()); - ldapMock.get_emails.throws(new exceptions.LdapSeachError("error while retrieving emails")); - FirstFactor(req, res); + ldapMock.get_emails.returns(BluebirdPromise.reject(new exceptions.LdapSeachError("error while retrieving emails"))); + FirstFactor(req as any, res as any); }); it("should return status code 403 when regulator rejects authentication", function(done) { @@ -129,7 +129,7 @@ describe("test the first factor validation route", function() { }); ldapMock.bind.returns(BluebirdPromise.resolve()); ldapMock.get_emails.returns(BluebirdPromise.resolve()); - FirstFactor(req, res); + FirstFactor(req as any, res as any); }); }); diff --git a/test/unitary/routes/TOTPAuthenticator.test.ts b/test/unitary/routes/TOTPAuthenticator.test.ts new file mode 100644 index 00000000..eab66d67 --- /dev/null +++ b/test/unitary/routes/TOTPAuthenticator.test.ts @@ -0,0 +1,90 @@ + +import BluebirdPromise = require("bluebird"); +import sinon = require("sinon"); +import assert = require("assert"); +import winston = require("winston"); + +import exceptions = require("../../../src/lib/Exceptions"); +import TOTPAuthenticator = require("../../../src/lib/routes/TOTPAuthenticator"); + +import ExpressMock = require("../mocks/express"); +import UserDataStoreMock = require("../mocks/UserDataStore"); +import TOTPValidatorMock = require("../mocks/TOTPValidator"); + +describe("test totp route", function() { + let req: ExpressMock.RequestMock; + let res: ExpressMock.ResponseMock; + let totpValidator: TOTPValidatorMock.TOTPValidatorMock; + let userDataStore: UserDataStoreMock.UserDataStore; + + beforeEach(function() { + const app_get = sinon.stub(); + req = { + app: { + get: app_get + }, + body: { + token: "abc" + }, + session: { + auth_session: { + userid: "user", + first_factor: false, + second_factor: false + } + } + }; + res = ExpressMock.ResponseMock(); + + const config = { totp_secret: "secret" }; + totpValidator = TOTPValidatorMock.TOTPValidatorMock(); + + userDataStore = UserDataStoreMock.UserDataStore(); + + const doc = { + userid: "user", + secret: { + base32: "ABCDEF" + } + }; + userDataStore.get_totp_secret.returns(BluebirdPromise.resolve(doc)); + + app_get.withArgs("logger").returns(winston); + app_get.withArgs("totp validator").returns(totpValidator); + app_get.withArgs("config").returns(config); + app_get.withArgs("user data store").returns(userDataStore); + }); + + + it("should send status code 204 when totp is valid", function(done) { + totpValidator.validate.returns(Promise.resolve("ok")); + res.send = sinon.spy(function() { + // Second factor passed + assert.equal(true, req.session.auth_session.second_factor); + assert.equal(204, res.status.getCall(0).args[0]); + done(); + }); + TOTPAuthenticator(req as any, res as any); + }); + + it("should send status code 401 when totp is not valid", function(done) { + totpValidator.validate.returns(Promise.reject(new exceptions.InvalidTOTPError("Bad TOTP token"))); + res.send = sinon.spy(function() { + assert.equal(false, req.session.auth_session.second_factor); + assert.equal(401, res.status.getCall(0).args[0]); + done(); + }); + TOTPAuthenticator(req as any, res as any); + }); + + it("should send status code 401 when session has not been initiated", function(done) { + totpValidator.validate.returns(Promise.resolve("abc")); + res.send = sinon.spy(function() { + assert.equal(403, res.status.getCall(0).args[0]); + done(); + }); + req.session = {}; + TOTPAuthenticator(req as any, res as any); + }); +}); + diff --git a/test/unitary/routes/test_totp.js b/test/unitary/routes/test_totp.js deleted file mode 100644 index 18e161df..00000000 --- a/test/unitary/routes/test_totp.js +++ /dev/null @@ -1,87 +0,0 @@ - -var totp = require('../../../src/lib/routes/totp'); -var Promise = require('bluebird'); -var sinon = require('sinon'); -var assert = require('assert'); -var winston = require('winston'); - -describe('test totp route', function() { - var req, res; - var totpValidator; - var user_data_store; - - beforeEach(function() { - var app_get = sinon.stub(); - req = { - app: { - get: app_get - }, - body: { - token: 'abc' - }, - session: { - auth_session: { - userid: 'user', - first_factor: false, - second_factor: false - } - } - }; - res = { - send: sinon.spy(), - status: sinon.spy() - }; - - var config = { totp_secret: 'secret' }; - totpValidator = { - validate: sinon.stub() - } - - user_data_store = {}; - user_data_store.get_totp_secret = sinon.stub(); - - var doc = {}; - doc.userid = 'user'; - doc.secret = {}; - doc.secret.base32 = 'ABCDEF'; - user_data_store.get_totp_secret.returns(Promise.resolve(doc)); - - app_get.withArgs('logger').returns(winston); - app_get.withArgs('totp validator').returns(totpValidator); - app_get.withArgs('config').returns(config); - app_get.withArgs('user data store').returns(user_data_store); - }); - - - it('should send status code 204 when totp is valid', function(done) { - totpValidator.validate.returns(Promise.resolve("ok")); - res.send = sinon.spy(function() { - // Second factor passed - assert.equal(true, req.session.auth_session.second_factor) - assert.equal(204, res.status.getCall(0).args[0]); - done(); - }); - totp(req, res); - }); - - it('should send status code 401 when totp is not valid', function(done) { - totpValidator.validate.returns(Promise.reject('bad_token')); - res.send = sinon.spy(function() { - assert.equal(false, req.session.auth_session.second_factor) - assert.equal(401, res.status.getCall(0).args[0]); - done(); - }); - totp(req, res); - }); - - it('should send status code 401 when session has not been initiated', function(done) { - totpValidator.validate.returns(Promise.resolve('abc')); - res.send = sinon.spy(function() { - assert.equal(403, res.status.getCall(0).args[0]); - done(); - }); - req.session = {}; - totp(req, res); - }); -}); - diff --git a/test/unitary/user_data_store/authentication_audit.test.ts b/test/unitary/user_data_store/authentication_audit.test.ts index 2dc5c930..8a8be4df 100644 --- a/test/unitary/user_data_store/authentication_audit.test.ts +++ b/test/unitary/user_data_store/authentication_audit.test.ts @@ -4,6 +4,7 @@ import * as Promise from "bluebird"; import * as sinon from "sinon"; import * as MockDate from "mockdate"; import UserDataStore from "../../../src/lib/UserDataStore"; +import nedb = require("nedb"); describe("test user data store", function() { describe("test authentication traces", test_authentication_traces); @@ -15,7 +16,7 @@ function test_authentication_traces() { inMemoryOnly: true }; - const data_store = new UserDataStore(options); + const data_store = new UserDataStore(options, nedb); const userid = "user"; const type = "1stfactor"; const is_success = false; @@ -34,7 +35,7 @@ function test_authentication_traces() { inMemoryOnly: true }; - const data_store = new UserDataStore(options); + const data_store = new UserDataStore(options, nedb); const userid = "user"; const type = "1stfactor"; const is_success = false; diff --git a/test/unitary/user_data_store/totp_secret.test.ts b/test/unitary/user_data_store/totp_secret.test.ts index cddaa227..bd5223ac 100644 --- a/test/unitary/user_data_store/totp_secret.test.ts +++ b/test/unitary/user_data_store/totp_secret.test.ts @@ -4,6 +4,7 @@ import * as Promise from "bluebird"; import * as sinon from "sinon"; import * as MockDate from "mockdate"; import UserDataStore from "../../../src/lib/UserDataStore"; +import nedb = require("nedb"); describe("test user data store", function() { describe("test totp secrets store", test_totp_secrets); @@ -15,7 +16,7 @@ function test_totp_secrets() { inMemoryOnly: true }; - const data_store = new UserDataStore(options); + const data_store = new UserDataStore(options, nedb); const userid = "user"; const secret = { ascii: "abc", @@ -41,7 +42,7 @@ function test_totp_secrets() { inMemoryOnly: true }; - const data_store = new UserDataStore(options); + const data_store = new UserDataStore(options, nedb); const userid = "user"; const secret1 = { ascii: "abc", diff --git a/tsconfig.json b/tsconfig.json index 4d4d2aa5..ebb0c747 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,8 +10,8 @@ "allowJs": true, "paths": { "*": [ - "node_modules/@types/*", - "src/types/*" + "src/types/*", + "node_modules/@types/*" ] } },