Move TOTP authenticator to typescript

pull/33/head
Clement Michaud 2017-05-21 12:14:59 +02:00
parent b54c181d27
commit c98c07832d
35 changed files with 354 additions and 319 deletions

View File

@ -47,7 +47,7 @@
}, },
"devDependencies": { "devDependencies": {
"@types/assert": "0.0.31", "@types/assert": "0.0.31",
"@types/bluebird": "^3.5.3", "@types/bluebird": "^3.5.4",
"@types/body-parser": "^1.16.3", "@types/body-parser": "^1.16.3",
"@types/ejs": "^2.3.33", "@types/ejs": "^2.3.33",
"@types/express": "^4.0.35", "@types/express": "^4.0.35",

View File

@ -1,5 +1,5 @@
import * as Promise from "bluebird"; import * as BluebirdPromise from "bluebird";
import exceptions = require("./Exceptions"); import exceptions = require("./Exceptions");
const REGULATION_TRACE_TYPE = "regulation"; const REGULATION_TRACE_TYPE = "regulation";
@ -19,16 +19,16 @@ export default class AuthenticationRegulator {
} }
// Mark authentication // Mark authentication
mark(userid: string, is_success: boolean): Promise<void> { mark(userid: string, is_success: boolean): BluebirdPromise<void> {
return this._user_data_store.save_authentication_trace(userid, REGULATION_TRACE_TYPE, is_success); return this._user_data_store.save_authentication_trace(userid, REGULATION_TRACE_TYPE, is_success);
} }
regulate(userid: string): Promise<void> { regulate(userid: string): BluebirdPromise<void> {
return this._user_data_store.get_last_authentication_traces(userid, REGULATION_TRACE_TYPE, false, 3) return this._user_data_store.get_last_authentication_traces(userid, REGULATION_TRACE_TYPE, false, 3)
.then((docs: Array<DatedDocument>) => { .then((docs: Array<DatedDocument>) => {
if (docs.length < MAX_AUTHENTICATION_COUNT_IN_TIME_RANGE) { if (docs.length < MAX_AUTHENTICATION_COUNT_IN_TIME_RANGE) {
// less than the max authorized number of authentication in time range, thus authorizing access // 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]; 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."); throw new exceptions.AuthenticationRegulationError("Max number of authentication. Please retry in few minutes.");
} }
return Promise.resolve(); return BluebirdPromise.resolve();
}); });
} }
} }

View File

@ -1,5 +1,4 @@
export class LdapSeachError extends Error { export class LdapSeachError extends Error {
constructor(message?: string) { constructor(message?: string) {
super(message); super(message);

View File

@ -115,7 +115,7 @@ export class LdapClient {
groups.push(docs[i].cn); groups.push(docs[i].cn);
} }
that.logger.debug("LDAP: got groups %s", groups); 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); that.logger.debug("LDAP: got emails %s", emails);
return Promise.resolve(emails); return BluebirdPromise.resolve(emails);
}); });
} }

View File

@ -9,6 +9,7 @@ import TOTPValidator from "./TOTPValidator";
import TOTPGenerator from "./TOTPGenerator"; import TOTPGenerator from "./TOTPGenerator";
import RestApi from "./RestApi"; import RestApi from "./RestApi";
import { LdapClient } from "./LdapClient"; import { LdapClient } from "./LdapClient";
import BluebirdPromise = require("bluebird");
import * as Express from "express"; import * as Express from "express";
import * as BodyParser from "body-parser"; import * as BodyParser from "body-parser";
@ -20,7 +21,7 @@ import AccessController from "./access_control/AccessController";
export default class Server { export default class Server {
private httpServer: http.Server; private httpServer: http.Server;
start(yaml_configuration: UserConfiguration, deps: GlobalDependencies): Promise<void> { start(yaml_configuration: UserConfiguration, deps: GlobalDependencies): BluebirdPromise<void> {
const config = ConfigurationAdapter.adapt(yaml_configuration); const config = ConfigurationAdapter.adapt(yaml_configuration);
const view_directory = Path.resolve(__dirname, "../views"); const view_directory = Path.resolve(__dirname, "../views");
@ -54,7 +55,7 @@ export default class Server {
deps.winston.level = config.logs_level || "info"; deps.winston.level = config.logs_level || "info";
const five_minutes = 5 * 60; 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 regulator = new AuthenticationRegulator(data_store, five_minutes);
const notifier = NotifierFactory.build(config.notifier, deps.nodemailer); const notifier = NotifierFactory.build(config.notifier, deps.nodemailer);
const ldap = new LdapClient(config.ldap, deps.ldapjs, deps.winston); const ldap = new LdapClient(config.ldap, deps.ldapjs, deps.winston);
@ -75,7 +76,7 @@ export default class Server {
RestApi.setup(app); RestApi.setup(app);
return new Promise<void>((resolve, reject) => { return new BluebirdPromise<void>((resolve, reject) => {
this.httpServer = app.listen(config.port, function (err: string) { this.httpServer = app.listen(config.port, function (err: string) {
console.log("Listening on %d...", config.port); console.log("Listening on %d...", config.port);
resolve(); resolve();

View File

@ -18,6 +18,6 @@ export default class TOTPValidator {
}); });
if (token == real_token) return BluebirdPromise.resolve(); if (token == real_token) return BluebirdPromise.resolve();
return BluebirdPromise.reject("Wrong challenge"); return BluebirdPromise.reject(new Error("Wrong challenge"));
} }
} }

View File

@ -1,8 +1,8 @@
import * as Promise from "bluebird"; import * as BluebirdPromise from "bluebird";
import * as path from "path"; import * as path from "path";
import Nedb = require("nedb");
import { NedbAsync } from "nedb"; import { NedbAsync } from "nedb";
import { TOTPSecret } from "../types/TOTPSecret"; import { TOTPSecret } from "../types/TOTPSecret";
import { Nedb } from "../types/Dependencies";
// Constants // Constants
@ -36,18 +36,20 @@ export default class UserDataStore {
private _identity_check_tokens_collection: NedbAsync; private _identity_check_tokens_collection: NedbAsync;
private _authentication_traces_collection: NedbAsync; private _authentication_traces_collection: NedbAsync;
private _totp_secret_collection: NedbAsync; private _totp_secret_collection: NedbAsync;
private nedb: Nedb;
constructor(options?: Options) { constructor(options: Options, nedb: Nedb) {
this._u2f_meta_collection = create_collection(U2F_META_COLLECTION_NAME, options); this.nedb = nedb;
this._u2f_meta_collection = this.create_collection(U2F_META_COLLECTION_NAME, options);
this._identity_check_tokens_collection = 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 = this._authentication_traces_collection =
create_collection(AUTHENTICATION_TRACES_COLLECTION_NAME, options); this.create_collection(AUTHENTICATION_TRACES_COLLECTION_NAME, options);
this._totp_secret_collection = 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<any> { set_u2f_meta(userid: string, appid: string, meta: Object): BluebirdPromise<any> {
const newDocument = { const newDocument = {
userid: userid, userid: userid,
appid: appid, appid: appid,
@ -62,7 +64,7 @@ export default class UserDataStore {
return this._u2f_meta_collection.updateAsync(filter, newDocument, { upsert: true }); return this._u2f_meta_collection.updateAsync(filter, newDocument, { upsert: true });
} }
get_u2f_meta(userid: string, appid: string): Promise<U2FMetaDocument> { get_u2f_meta(userid: string, appid: string): BluebirdPromise<U2FMetaDocument> {
const filter = { const filter = {
userid: userid, userid: userid,
appid: appid appid: appid
@ -81,7 +83,7 @@ export default class UserDataStore {
return this._authentication_traces_collection.insertAsync(newDocument); return this._authentication_traces_collection.insertAsync(newDocument);
} }
get_last_authentication_traces(userid: string, type: string, is_success: boolean, count: number): Promise<any> { get_last_authentication_traces(userid: string, type: string, is_success: boolean, count: number): BluebirdPromise<any> {
const q = { const q = {
userid: userid, userid: userid,
type: type, type: type,
@ -90,11 +92,11 @@ export default class UserDataStore {
const query = this._authentication_traces_collection.find(q) const query = this._authentication_traces_collection.find(q)
.sort({ date: -1 }).limit(count); .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(); return query_promisified();
} }
issue_identity_check_token(userid: string, token: string, data: string | object, max_age: number): Promise<any> { issue_identity_check_token(userid: string, token: string, data: string | object, max_age: number): BluebirdPromise<any> {
const newDocument = { const newDocument = {
userid: userid, userid: userid,
token: token, token: token,
@ -108,7 +110,7 @@ export default class UserDataStore {
return this._identity_check_tokens_collection.insertAsync(newDocument); return this._identity_check_tokens_collection.insertAsync(newDocument);
} }
consume_identity_check_token(token: string): Promise<any> { consume_identity_check_token(token: string): BluebirdPromise<any> {
const query = { const query = {
token: token token: token
}; };
@ -116,26 +118,26 @@ export default class UserDataStore {
return this._identity_check_tokens_collection.findOneAsync(query) return this._identity_check_tokens_collection.findOneAsync(query)
.then(function (doc) { .then(function (doc) {
if (!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 max_date = doc.max_date;
const current_date = new Date(); const current_date = new Date();
if (current_date > max_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) => { .then((content) => {
return Promise.join(this._identity_check_tokens_collection.removeAsync(query), return BluebirdPromise.join(this._identity_check_tokens_collection.removeAsync(query),
Promise.resolve(content)); BluebirdPromise.resolve(content));
}) })
.then((v) => { .then((v) => {
return Promise.resolve(v[1]); return BluebirdPromise.resolve(v[1]);
}); });
} }
set_totp_secret(userid: string, secret: TOTPSecret): Promise<any> { set_totp_secret(userid: string, secret: TOTPSecret): BluebirdPromise<any> {
const doc = { const doc = {
userid: userid, userid: userid,
secret: secret secret: secret
@ -147,15 +149,14 @@ export default class UserDataStore {
return this._totp_secret_collection.updateAsync(query, doc, { upsert: true }); return this._totp_secret_collection.updateAsync(query, doc, { upsert: true });
} }
get_totp_secret(userid: string): Promise<TOTPSecretDocument> { get_totp_secret(userid: string): BluebirdPromise<TOTPSecretDocument> {
const query = { const query = {
userid: userid userid: userid
}; };
return this._totp_secret_collection.findOneAsync(query); return this._totp_secret_collection.findOneAsync(query);
} }
}
function create_collection(name: string, options: any): NedbAsync { private create_collection(name: string, options: any): NedbAsync {
const datastore_options = { const datastore_options = {
inMemoryOnly: options.inMemoryOnly || false, inMemoryOnly: options.inMemoryOnly || false,
autoload: true, autoload: true,
@ -165,5 +166,6 @@ function create_collection(name: string, options: any): NedbAsync {
if (options.directory) if (options.directory)
datastore_options.filename = path.resolve(options.directory, name); datastore_options.filename = path.resolve(options.directory, name);
return Promise.promisifyAll(new Nedb(datastore_options)) as NedbAsync; return BluebirdPromise.promisifyAll(new this.nedb(datastore_options)) as NedbAsync;
}
} }

View File

@ -1,7 +1,7 @@
var objectPath = require('object-path'); var objectPath = require('object-path');
var randomstring = require('randomstring'); var randomstring = require('randomstring');
var Promise = require('bluebird'); var BluebirdPromise = require('bluebird');
var util = require('util'); var util = require('util');
var exceptions = require('./Exceptions'); var exceptions = require('./Exceptions');
var fs = require('fs'); 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); 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) return this._user_data_store.issue_identity_check_token(userid, token, content, five_minutes)
.then(function() { .then(function() {
return Promise.resolve(token); return BluebirdPromise.resolve(token);
}); });
} }

View File

@ -1,5 +1,5 @@
import * as Promise from "bluebird"; import * as BluebirdPromise from "bluebird";
import * as fs from "fs"; import * as fs from "fs";
import * as ejs from "ejs"; import * as ejs from "ejs";
import nodemailer = require("nodemailer"); import nodemailer = require("nodemailer");
@ -23,10 +23,10 @@ export class GMailNotifier extends INotifier {
pass: options.password pass: options.password
} }
}); });
this.transporter = Promise.promisifyAll(transporter); this.transporter = BluebirdPromise.promisifyAll(transporter);
} }
notify(identity: Identity, subject: string, link: string): Promise<void> { notify(identity: Identity, subject: string, link: string): BluebirdPromise<void> {
const d = { const d = {
url: link, url: link,
button_title: "Continue", button_title: "Continue",

View File

@ -1,7 +1,7 @@
import exceptions = require("../Exceptions"); import exceptions = require("../Exceptions");
import objectPath = require("object-path"); import objectPath = require("object-path");
import Promise = require("bluebird"); import BluebirdPromise = require("bluebird");
import express = require("express"); import express = require("express");
export = function (req: express.Request, res: express.Response) { export = function (req: express.Request, res: express.Response) {
@ -32,7 +32,7 @@ export = function(req: express.Request, res: express.Response) {
objectPath.set(req, "session.auth_session.first_factor", true); objectPath.set(req, "session.auth_session.first_factor", true);
logger.info("1st factor: LDAP binding successful"); logger.info("1st factor: LDAP binding successful");
logger.debug("1st factor: Retrieve email from LDAP"); logger.debug("1st factor: Retrieve email from LDAP");
return Promise.join(ldap.get_emails(username), ldap.get_groups(username)); return BluebirdPromise.join(ldap.get_emails(username), ldap.get_groups(username));
}) })
.then(function (data: string[2]) { .then(function (data: string[2]) {
const emails = data[0]; const emails = data[0];

View File

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

View File

@ -1,5 +1,5 @@
var Promise = require('bluebird'); var BluebirdPromise = require('bluebird');
var objectPath = require('object-path'); var objectPath = require('object-path');
var exceptions = require('../Exceptions'); var exceptions = require('../Exceptions');
var CHALLENGE = 'reset-password'; var CHALLENGE = 'reset-password';
@ -19,7 +19,7 @@ module.exports = {
function pre_check(req) { function pre_check(req) {
var userid = objectPath.get(req, 'body.userid'); var userid = objectPath.get(req, 'body.userid');
if(!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'); var ldap = req.app.get('ldap');
@ -30,7 +30,7 @@ function pre_check(req) {
var identity = {} var identity = {}
identity.email = emails[0]; identity.email = emails[0];
identity.userid = userid; identity.userid = userid;
return Promise.resolve(identity); return BluebirdPromise.resolve(identity);
}); });
} }

View File

@ -1,9 +1,10 @@
var denyNotLogged = require('./deny_not_logged'); var denyNotLogged = require('./deny_not_logged');
var u2f = require('./u2f'); var u2f = require('./u2f');
var TOTPAuthenticator = require("./TOTPAuthenticator");
module.exports = { module.exports = {
totp: denyNotLogged(require('./totp')), totp: denyNotLogged(TOTPAuthenticator),
u2f: { u2f: {
register_request: u2f.register_request, register_request: u2f.register_request,
register: u2f.register, register: u2f.register,

View File

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

View File

@ -1,5 +1,5 @@
var objectPath = require('object-path'); var objectPath = require('object-path');
var Promise = require('bluebird'); var BluebirdPromise = require('bluebird');
var CHALLENGE = 'totp-register'; var CHALLENGE = 'totp-register';
@ -18,20 +18,20 @@ module.exports = {
function pre_check(req) { function pre_check(req) {
var first_factor_passed = objectPath.get(req, 'session.auth_session.first_factor'); var first_factor_passed = objectPath.get(req, 'session.auth_session.first_factor');
if(!first_factor_passed) { 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 userid = objectPath.get(req, 'session.auth_session.userid');
var email = objectPath.get(req, 'session.auth_session.email'); var email = objectPath.get(req, 'session.auth_session.email');
if(!(userid && email)) { if(!(userid && email)) {
return Promise.reject('User ID or email is missing'); return BluebirdPromise.reject('User ID or email is missing');
} }
var identity = {}; var identity = {};
identity.email = email; identity.email = email;
identity.userid = userid; identity.userid = userid;
return Promise.resolve(identity); return BluebirdPromise.resolve(identity);
} }
// Generate a secret and send it to the user // Generate a secret and send it to the user

View File

@ -10,7 +10,7 @@ module.exports = {
var objectPath = require('object-path'); var objectPath = require('object-path');
var u2f_common = require('./u2f_common'); var u2f_common = require('./u2f_common');
var Promise = require('bluebird'); var BluebirdPromise = require('bluebird');
function register_request(req, res) { function register_request(req, res) {
var logger = req.app.get('logger'); var logger = req.app.get('logger');

View File

@ -1,6 +1,6 @@
var objectPath = require('object-path'); var objectPath = require('object-path');
var Promise = require('bluebird'); var BluebirdPromise = require('bluebird');
var CHALLENGE = 'u2f-register'; var CHALLENGE = 'u2f-register';
@ -19,19 +19,19 @@ module.exports = {
function pre_check(req) { function pre_check(req) {
var first_factor_passed = objectPath.get(req, 'session.auth_session.first_factor'); var first_factor_passed = objectPath.get(req, 'session.auth_session.first_factor');
if(!first_factor_passed) { 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 userid = objectPath.get(req, 'session.auth_session.userid');
var email = objectPath.get(req, 'session.auth_session.email'); var email = objectPath.get(req, 'session.auth_session.email');
if(!(userid && email)) { if(!(userid && email)) {
return Promise.reject('User ID or email is missing'); return BluebirdPromise.reject('User ID or email is missing');
} }
var identity = {}; var identity = {};
identity.email = email; identity.email = email;
identity.userid = userid; identity.userid = userid;
return Promise.resolve(identity); return BluebirdPromise.resolve(identity);
} }

View File

@ -2,31 +2,31 @@
module.exports = verify; module.exports = verify;
var objectPath = require('object-path'); var objectPath = require('object-path');
var Promise = require('bluebird'); var BluebirdPromise = require('bluebird');
function verify_filter(req, res) { function verify_filter(req, res) {
var logger = req.app.get('logger'); var logger = req.app.get('logger');
if(!objectPath.has(req, 'session.auth_session')) 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')) 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')) 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')) 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 host = objectPath.get(req, 'headers.host');
var domain = host.split(':')[0]; var domain = host.split(':')[0];
if(!req.session.auth_session.first_factor || if(!req.session.auth_session.first_factor ||
!req.session.auth_session.second_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) { function verify(req, res) {

View File

@ -1,4 +1,6 @@
import BluebirdPromise = require("bluebird");
declare module "authdog" { declare module "authdog" {
interface RegisterRequest { interface RegisterRequest {
challenge: string; challenge: string;
@ -60,8 +62,8 @@ declare module "authdog" {
counter: Uint32Array counter: Uint32Array
} }
export function startRegistration(appId: AppId, registeredKeys: RegisteredKeys, options?: Options): Promise<RegistrationRequest>; export function startRegistration(appId: AppId, registeredKeys: RegisteredKeys, options?: Options): BluebirdPromise<RegistrationRequest>;
export function finishRegistration(registrationRequest: RegistrationRequest, registrationResponse: RegistrationResponse): Promise<Registration>; export function finishRegistration(registrationRequest: RegistrationRequest, registrationResponse: RegistrationResponse): BluebirdPromise<Registration>;
export function startAuthentication(appId: AppId, registeredKeys: RegisteredKeys, options: Options): Promise<AuthenticationRequest>; export function startAuthentication(appId: AppId, registeredKeys: RegisteredKeys, options: Options): BluebirdPromise<AuthenticationRequest>;
export function finishAuthentication(challenge: string, deviceResponse: AuthenticationResponse, registeredKeys: RegisteredKeys): Promise<Authentication>; export function finishAuthentication(challenge: string, deviceResponse: AuthenticationResponse, registeredKeys: RegisteredKeys): BluebirdPromise<Authentication>;
} }

View File

@ -1,11 +1,11 @@
import ldapjs = require("ldapjs"); import ldapjs = require("ldapjs");
import * as Promise from "bluebird"; import * as BluebirdPromise from "bluebird";
import { EventEmitter } from "events"; import { EventEmitter } from "events";
declare module "ldapjs" { declare module "ldapjs" {
export interface ClientAsync { export interface ClientAsync {
bindAsync(username: string, password: string): Promise<void>; bindAsync(username: string, password: string): BluebirdPromise<void>;
searchAsync(base: string, query: ldapjs.SearchOptions): Promise<EventEmitter>; searchAsync(base: string, query: ldapjs.SearchOptions): BluebirdPromise<EventEmitter>;
modifyAsync(userdn: string, change: ldapjs.Change): Promise<void>; modifyAsync(userdn: string, change: ldapjs.Change): BluebirdPromise<void>;
} }
} }

View File

@ -1,12 +1,12 @@
import Nedb = require("nedb"); import Nedb = require("nedb");
import * as Promise from "bluebird"; import BluebirdPromise = require("bluebird");
declare module "nedb" { declare module "nedb" {
export class NedbAsync extends Nedb { export class NedbAsync extends Nedb {
constructor(pathOrOptions?: string | Nedb.DataStoreOptions); constructor(pathOrOptions?: string | Nedb.DataStoreOptions);
updateAsync(query: any, updateQuery: any, options?: Nedb.UpdateOptions): Promise<any>; updateAsync(query: any, updateQuery: any, options?: Nedb.UpdateOptions): BluebirdPromise<any>;
findOneAsync(query: any): Promise<any>; findOneAsync(query: any): BluebirdPromise<any>;
insertAsync<T>(newDoc: T): Promise<any>; insertAsync<T>(newDoc: T): BluebirdPromise<any>;
removeAsync(query: any): Promise<any>; removeAsync(query: any): BluebirdPromise<any>;
} }
} }

View File

@ -1,14 +1,14 @@
import * as Promise from "bluebird"; import * as BluebirdPromise from "bluebird";
import * as request from "request"; import * as request from "request";
declare module "request" { declare module "request" {
export interface RequestAsync extends RequestAPI<Request, CoreOptions, RequiredUriUrl> { export interface RequestAsync extends RequestAPI<Request, CoreOptions, RequiredUriUrl> {
getAsync(uri: string, options?: RequiredUriUrl): Promise<RequestResponse>; getAsync(uri: string, options?: RequiredUriUrl): BluebirdPromise<RequestResponse>;
getAsync(uri: string): Promise<RequestResponse>; getAsync(uri: string): BluebirdPromise<RequestResponse>;
getAsync(options: RequiredUriUrl & CoreOptions): Promise<RequestResponse>; getAsync(options: RequiredUriUrl & CoreOptions): BluebirdPromise<RequestResponse>;
postAsync(uri: string, options?: CoreOptions): Promise<RequestResponse>; postAsync(uri: string, options?: CoreOptions): BluebirdPromise<RequestResponse>;
postAsync(uri: string): Promise<RequestResponse>; postAsync(uri: string): BluebirdPromise<RequestResponse>;
postAsync(options: RequiredUriUrl & CoreOptions): Promise<RequestResponse>; postAsync(options: RequiredUriUrl & CoreOptions): BluebirdPromise<RequestResponse>;
} }
} }

View File

@ -3,13 +3,14 @@ import AuthenticationRegulator from "../../src/lib/AuthenticationRegulator";
import UserDataStore from "../../src/lib/UserDataStore"; import UserDataStore from "../../src/lib/UserDataStore";
import MockDate = require("mockdate"); import MockDate = require("mockdate");
import exceptions = require("../../src/lib/Exceptions"); import exceptions = require("../../src/lib/Exceptions");
import nedb = require("nedb");
describe("test authentication regulator", function() { describe("test authentication regulator", function() {
it("should mark 2 authentication and regulate (resolve)", function() { it("should mark 2 authentication and regulate (resolve)", function() {
const options = { const options = {
inMemoryOnly: true inMemoryOnly: true
}; };
const data_store = new UserDataStore(options); const data_store = new UserDataStore(options, nedb);
const regulator = new AuthenticationRegulator(data_store, 10); const regulator = new AuthenticationRegulator(data_store, 10);
const user = "user"; const user = "user";
@ -26,7 +27,7 @@ describe("test authentication regulator", function() {
const options = { const options = {
inMemoryOnly: true inMemoryOnly: true
}; };
const data_store = new UserDataStore(options); const data_store = new UserDataStore(options, nedb);
const regulator = new AuthenticationRegulator(data_store, 10); const regulator = new AuthenticationRegulator(data_store, 10);
const user = "user"; const user = "user";
@ -49,7 +50,7 @@ describe("test authentication regulator", function() {
const options = { const options = {
inMemoryOnly: true inMemoryOnly: true
}; };
const data_store = new UserDataStore(options); const data_store = new UserDataStore(options, nedb);
const regulator = new AuthenticationRegulator(data_store, 10); const regulator = new AuthenticationRegulator(data_store, 10);
const user = "user"; const user = "user";

View File

@ -18,12 +18,12 @@ describe("test TOTP validation", function() {
return totpValidator.validate(token, totp_secret); 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 totp_secret = "NBD2ZV64R9UV1O7K";
const token = "wrong token"; const token = "wrong token";
return totpValidator.validate(token, totp_secret) totpValidator.validate(token, totp_secret)
.catch(function() { .catch(function() {
return Promise.resolve(); done();
}); });
}); });
}); });

View File

@ -2,7 +2,7 @@
import UserDataStore from "../../src/lib/UserDataStore"; import UserDataStore from "../../src/lib/UserDataStore";
import { U2FMetaDocument, Options } from "../../src/lib/UserDataStore"; import { U2FMetaDocument, Options } from "../../src/lib/UserDataStore";
import DataStore = require("nedb"); import nedb = require("nedb");
import assert = require("assert"); import assert = require("assert");
import Promise = require("bluebird"); import Promise = require("bluebird");
import sinon = require("sinon"); import sinon = require("sinon");
@ -20,7 +20,7 @@ describe("test user data store", () => {
describe("test u2f meta", () => { describe("test u2f meta", () => {
it("should save a u2f meta", function () { it("should save a u2f meta", function () {
const data_store = new UserDataStore(options); const data_store = new UserDataStore(options, nedb);
const userid = "user"; const userid = "user";
const app_id = "https://localhost"; const app_id = "https://localhost";
@ -40,7 +40,7 @@ describe("test user data store", () => {
inMemoryOnly: true inMemoryOnly: true
}; };
const data_store = new UserDataStore(options); const data_store = new UserDataStore(options, nedb);
const userid = "user"; const userid = "user";
const app_id = "https://localhost"; const app_id = "https://localhost";
@ -60,7 +60,7 @@ describe("test user data store", () => {
inMemoryOnly: true inMemoryOnly: true
}; };
const data_store = new UserDataStore(options); const data_store = new UserDataStore(options, nedb);
const userid = "user"; const userid = "user";
const app_id = "https://localhost"; const app_id = "https://localhost";
@ -86,7 +86,7 @@ describe("test user data store", () => {
describe("test u2f registration token", () => { describe("test u2f registration token", () => {
it("should save u2f registration token", function () { it("should save u2f registration token", function () {
const data_store = new UserDataStore(options); const data_store = new UserDataStore(options, nedb);
const userid = "user"; const userid = "user";
const token = "token"; const token = "token";
@ -109,7 +109,7 @@ describe("test user data store", () => {
}); });
it("should save u2f registration token and consume it", function (done) { 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 userid = "user";
const token = "token"; const token = "token";
@ -128,7 +128,7 @@ describe("test user data store", () => {
}); });
it("should not be able to consume registration token twice", function (done) { 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 userid = "user";
const token = "token"; const token = "token";
@ -148,7 +148,7 @@ describe("test user data store", () => {
}); });
it("should fail when token does not exist", function () { 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"; const token = "token";
@ -162,7 +162,7 @@ describe("test user data store", () => {
}); });
it("should fail when token expired", function (done) { 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 userid = "user";
const token = "token"; 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) { 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 userid = "user";
const token = "token"; const token = "token";

View File

@ -1,8 +1,12 @@
import sinon = require("sinon"); import sinon = require("sinon");
export = function () { export interface AccessControllerMock {
isDomainAllowedForUser: sinon.SinonStub;
}
export function AccessControllerMock() {
return { return {
isDomainAllowedForUser: sinon.stub() isDomainAllowedForUser: sinon.stub()
}; };
}; }

View File

@ -1,9 +1,15 @@
import sinon = require("sinon"); import sinon = require("sinon");
export = function () {
export interface AuthenticationRegulatorMock {
mark: sinon.SinonStub;
regulate: sinon.SinonStub;
}
export function AuthenticationRegulatorMock() {
return { return {
mark: sinon.stub(), mark: sinon.stub(),
regulate: sinon.stub() regulate: sinon.stub()
}; };
}; }

View File

@ -0,0 +1,12 @@
import sinon = require("sinon");
export interface TOTPValidatorMock {
validate: sinon.SinonStub;
}
export function TOTPValidatorMock(): TOTPValidatorMock {
return {
validate: sinon.stub()
};
}

View File

@ -6,6 +6,7 @@ export interface UserDataStore {
get_u2f_meta: sinon.SinonStub; get_u2f_meta: sinon.SinonStub;
issue_identity_check_token: sinon.SinonStub; issue_identity_check_token: sinon.SinonStub;
consume_identity_check_token: sinon.SinonStub; consume_identity_check_token: sinon.SinonStub;
get_totp_secret: sinon.SinonStub;
} }
export function UserDataStore(): UserDataStore { export function UserDataStore(): UserDataStore {
@ -13,6 +14,7 @@ export function UserDataStore(): UserDataStore {
set_u2f_meta: sinon.stub(), set_u2f_meta: sinon.stub(),
get_u2f_meta: sinon.stub(), get_u2f_meta: sinon.stub(),
issue_identity_check_token: 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()
}; };
} }

View File

@ -12,14 +12,14 @@ import { LdapClientMock } from "../mocks/LdapClient";
import ExpressMock = require("../mocks/express"); import ExpressMock = require("../mocks/express");
describe("test the first factor validation route", function() { describe("test the first factor validation route", function() {
let req: any; let req: ExpressMock.RequestMock;
let res: any; let res: ExpressMock.ResponseMock;
let emails: string[]; let emails: string[];
let groups: string[]; let groups: string[];
let configuration; let configuration;
let ldapMock: LdapClientMock; let ldapMock: LdapClientMock;
let regulator: any; let regulator: AuthenticationRegulatorMock.AuthenticationRegulatorMock;
let accessController: any; let accessController: AccessControllerMock.AccessControllerMock;
beforeEach(function() { beforeEach(function() {
configuration = { configuration = {
@ -34,10 +34,10 @@ describe("test the first factor validation route", function() {
ldapMock = LdapClientMock(); ldapMock = LdapClientMock();
accessController = AccessControllerMock(); accessController = AccessControllerMock.AccessControllerMock();
accessController.isDomainAllowedForUser.returns(true); accessController.isDomainAllowedForUser.returns(true);
regulator = AuthenticationRegulatorMock(); regulator = AuthenticationRegulatorMock.AuthenticationRegulatorMock();
regulator.regulate.returns(BluebirdPromise.resolve()); regulator.regulate.returns(BluebirdPromise.resolve());
regulator.mark.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.bind.withArgs("username").returns(BluebirdPromise.resolve());
ldapMock.get_emails.returns(BluebirdPromise.resolve(emails)); 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) { it("should retrieve email from LDAP", function(done) {
res.send = sinon.spy(function() { done(); }); res.send = sinon.spy(function() { done(); });
ldapMock.bind.returns(BluebirdPromise.resolve()); ldapMock.bind.returns(BluebirdPromise.resolve());
ldapMock.get_emails = sinon.stub().withArgs("usernam").returns(BluebirdPromise.resolve([{mail: ["test@example.com"] }])); ldapMock.get_emails = sinon.stub().withArgs("username").returns(BluebirdPromise.resolve([{mail: ["test@example.com"] }]));
FirstFactor(req, res); FirstFactor(req as any, res as any);
}); });
it("should set email as session variables", function() { 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" ]; const emails = [ "test_ok@example.com" ];
ldapMock.bind.returns(BluebirdPromise.resolve()); ldapMock.bind.returns(BluebirdPromise.resolve());
ldapMock.get_emails.returns(BluebirdPromise.resolve(emails)); 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"); assert.equal(regulator.mark.getCall(0).args[0], "username");
done(); done();
}); });
ldapMock.bind.throws(new exceptions.LdapBindError("Bad credentials")); ldapMock.bind.returns(BluebirdPromise.reject(new exceptions.LdapBindError("Bad credentials")));
FirstFactor(req, res); FirstFactor(req as any, res as any);
}); });
it("should return status code 500 when LDAP search throws", function(done) { 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(); done();
}); });
ldapMock.bind.returns(BluebirdPromise.resolve()); ldapMock.bind.returns(BluebirdPromise.resolve());
ldapMock.get_emails.throws(new exceptions.LdapSeachError("error while retrieving emails")); ldapMock.get_emails.returns(BluebirdPromise.reject(new exceptions.LdapSeachError("error while retrieving emails")));
FirstFactor(req, res); FirstFactor(req as any, res as any);
}); });
it("should return status code 403 when regulator rejects authentication", function(done) { 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.bind.returns(BluebirdPromise.resolve());
ldapMock.get_emails.returns(BluebirdPromise.resolve()); ldapMock.get_emails.returns(BluebirdPromise.resolve());
FirstFactor(req, res); FirstFactor(req as any, res as any);
}); });
}); });

View File

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

View File

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

View File

@ -4,6 +4,7 @@ import * as Promise from "bluebird";
import * as sinon from "sinon"; import * as sinon from "sinon";
import * as MockDate from "mockdate"; import * as MockDate from "mockdate";
import UserDataStore from "../../../src/lib/UserDataStore"; import UserDataStore from "../../../src/lib/UserDataStore";
import nedb = require("nedb");
describe("test user data store", function() { describe("test user data store", function() {
describe("test authentication traces", test_authentication_traces); describe("test authentication traces", test_authentication_traces);
@ -15,7 +16,7 @@ function test_authentication_traces() {
inMemoryOnly: true inMemoryOnly: true
}; };
const data_store = new UserDataStore(options); const data_store = new UserDataStore(options, nedb);
const userid = "user"; const userid = "user";
const type = "1stfactor"; const type = "1stfactor";
const is_success = false; const is_success = false;
@ -34,7 +35,7 @@ function test_authentication_traces() {
inMemoryOnly: true inMemoryOnly: true
}; };
const data_store = new UserDataStore(options); const data_store = new UserDataStore(options, nedb);
const userid = "user"; const userid = "user";
const type = "1stfactor"; const type = "1stfactor";
const is_success = false; const is_success = false;

View File

@ -4,6 +4,7 @@ import * as Promise from "bluebird";
import * as sinon from "sinon"; import * as sinon from "sinon";
import * as MockDate from "mockdate"; import * as MockDate from "mockdate";
import UserDataStore from "../../../src/lib/UserDataStore"; import UserDataStore from "../../../src/lib/UserDataStore";
import nedb = require("nedb");
describe("test user data store", function() { describe("test user data store", function() {
describe("test totp secrets store", test_totp_secrets); describe("test totp secrets store", test_totp_secrets);
@ -15,7 +16,7 @@ function test_totp_secrets() {
inMemoryOnly: true inMemoryOnly: true
}; };
const data_store = new UserDataStore(options); const data_store = new UserDataStore(options, nedb);
const userid = "user"; const userid = "user";
const secret = { const secret = {
ascii: "abc", ascii: "abc",
@ -41,7 +42,7 @@ function test_totp_secrets() {
inMemoryOnly: true inMemoryOnly: true
}; };
const data_store = new UserDataStore(options); const data_store = new UserDataStore(options, nedb);
const userid = "user"; const userid = "user";
const secret1 = { const secret1 = {
ascii: "abc", ascii: "abc",

View File

@ -10,8 +10,8 @@
"allowJs": true, "allowJs": true,
"paths": { "paths": {
"*": [ "*": [
"node_modules/@types/*", "src/types/*",
"src/types/*" "node_modules/@types/*"
] ]
} }
}, },