import * as BluebirdPromise from "bluebird"; import * as path from "path"; import { NedbAsync } from "nedb"; import { TOTPSecret } from "../../types/TOTPSecret"; import { Nedb } from "../../types/Dependencies"; import u2f = require("u2f"); // Constants const U2F_META_COLLECTION_NAME = "u2f_meta"; const IDENTITY_CHECK_TOKENS_COLLECTION_NAME = "identity_check_tokens"; const AUTHENTICATION_TRACES_COLLECTION_NAME = "authentication_traces"; const TOTP_SECRETS_COLLECTION_NAME = "totp_secrets"; export interface TOTPSecretDocument { userid: string; secret: TOTPSecret; } export interface U2FRegistrationDocument { keyHandle: string; publicKey: string; userId: string; appId: string; } export interface Options { inMemoryOnly?: boolean; directory?: string; } export interface IdentityValidationRequestContent { userid: string; data: string; } export interface IdentityValidationRequestDocument { userid: string; token: string; content: IdentityValidationRequestContent; max_date: Date; } interface U2FRegistrationFilter { userId: string; appId: string; } // Source export default class UserDataStore { private _u2f_meta_collection: NedbAsync; private _identity_check_tokens_collection: NedbAsync; private _authentication_traces_collection: NedbAsync; private _totp_secret_collection: NedbAsync; private nedb: Nedb; 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 = this.create_collection(IDENTITY_CHECK_TOKENS_COLLECTION_NAME, options); this._authentication_traces_collection = this.create_collection(AUTHENTICATION_TRACES_COLLECTION_NAME, options); this._totp_secret_collection = this.create_collection(TOTP_SECRETS_COLLECTION_NAME, options); } set_u2f_meta(userId: string, appId: string, keyHandle: string, publicKey: string): BluebirdPromise<any> { const newDocument: U2FRegistrationDocument = { userId: userId, appId: appId, keyHandle: keyHandle, publicKey: publicKey }; const filter: U2FRegistrationFilter = { userId: userId, appId: appId }; return this._u2f_meta_collection.updateAsync(filter, newDocument, { upsert: true }); } get_u2f_meta(userId: string, appId: string): BluebirdPromise<U2FRegistrationDocument> { const filter: U2FRegistrationFilter = { userId: userId, appId: appId }; return this._u2f_meta_collection.findOneAsync(filter); } save_authentication_trace(userid: string, type: string, is_success: boolean) { const newDocument = { userid: userid, date: new Date(), is_success: is_success, type: type }; return this._authentication_traces_collection.insertAsync(newDocument); } get_last_authentication_traces(userid: string, type: string, is_success: boolean, count: number): BluebirdPromise<any> { const q = { userid: userid, type: type, is_success: is_success }; const query = this._authentication_traces_collection.find(q) .sort({ date: -1 }).limit(count); 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): BluebirdPromise<any> { const newDocument = { userid: userid, token: token, content: { userid: userid, data: data }, max_date: new Date(new Date().getTime() + max_age) }; return this._identity_check_tokens_collection.insertAsync(newDocument); } consume_identity_check_token(token: string): BluebirdPromise<IdentityValidationRequestContent> { const query = { token: token }; return this._identity_check_tokens_collection.findOneAsync(query) .then(function (doc) { if (!doc) { return BluebirdPromise.reject(new Error("Registration token does not exist")); } const max_date = doc.max_date; const current_date = new Date(); if (current_date > max_date) return BluebirdPromise.reject(new Error("Registration token is not valid anymore")); return BluebirdPromise.resolve(doc.content); }) .then((content) => { return BluebirdPromise.join(this._identity_check_tokens_collection.removeAsync(query), BluebirdPromise.resolve(content)); }) .then((v) => { return BluebirdPromise.resolve(v[1]); }); } set_totp_secret(userid: string, secret: TOTPSecret): BluebirdPromise<any> { const doc = { userid: userid, secret: secret }; const query = { userid: userid }; return this._totp_secret_collection.updateAsync(query, doc, { upsert: true }); } get_totp_secret(userid: string): BluebirdPromise<TOTPSecretDocument> { const query = { userid: userid }; return this._totp_secret_collection.findOneAsync(query); } 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; } }