import * as Promise from "bluebird"; import * as path from "path"; import Nedb = require("nedb"); import { NedbAsync } from "nedb"; import { TOTPSecret } from "./TOTPSecret"; // 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 U2FMetaDocument { meta: object; userid: string; appid: string; } export interface Options { inMemoryOnly?: boolean; directory?: 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; constructor(options?: Options) { this._u2f_meta_collection = create_collection(U2F_META_COLLECTION_NAME, options); this._identity_check_tokens_collection = create_collection(IDENTITY_CHECK_TOKENS_COLLECTION_NAME, options); this._authentication_traces_collection = create_collection(AUTHENTICATION_TRACES_COLLECTION_NAME, options); this._totp_secret_collection = create_collection(TOTP_SECRETS_COLLECTION_NAME, options); } set_u2f_meta(userid: string, appid: string, meta: Object): Promise<any> { const newDocument = { userid: userid, appid: appid, meta: meta }; const filter = { userid: userid, appid: appid }; return this._u2f_meta_collection.updateAsync(filter, newDocument, { upsert: true }); } get_u2f_meta(userid: string, appid: string): Promise<U2FMetaDocument> { const filter = { 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): Promise<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 = Promise.promisify(query.exec, { context: query }); return query_promisified(); } issue_identity_check_token(userid: string, token: string, data: string | object, max_age: number): Promise<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): Promise<any> { const query = { token: token }; return this._identity_check_tokens_collection.findOneAsync(query) .then(function (doc) { if (!doc) { return Promise.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 Promise.resolve(doc.content); }) .then((content) => { return Promise.join(this._identity_check_tokens_collection.removeAsync(query), Promise.resolve(content)); }) .then((v) => { return Promise.resolve(v[1]); }); } set_totp_secret(userid: string, secret: TOTPSecret): Promise<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): Promise<TOTPSecretDocument> { 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; }