import objectPath = require("object-path"); import randomstring = require("randomstring"); import BluebirdPromise = require("bluebird"); import util = require("util"); import Exceptions = require("./Exceptions"); import fs = require("fs"); import ejs = require("ejs"); import { IUserDataStore } from "./storage/IUserDataStore"; import { Winston } from "../../types/Dependencies"; import express = require("express"); import ErrorReplies = require("./ErrorReplies"); import AuthenticationSessionHandler = require("./AuthenticationSession"); import { AuthenticationSession } from "../../types/AuthenticationSession"; import { ServerVariables } from "./ServerVariables"; import Identity = require("../../types/Identity"); import { IdentityValidationDocument } from "./storage/IdentityValidationDocument"; const filePath = __dirname + "/../resources/email-template.ejs"; const email_template = fs.readFileSync(filePath, "utf8"); // IdentityValidator allows user to go through a identity validation process in two steps: // - Request an operation to be performed (password reset, registration). // - Confirm operation with email. export interface IdentityValidable { challenge(): string; preValidationInit(req: express.Request): BluebirdPromise; postValidationInit(req: express.Request): BluebirdPromise; // Serves a page after identity check request preValidationResponse(req: express.Request, res: express.Response): void; // Serves the page if identity validated postValidationResponse(req: express.Request, res: express.Response): void; mailSubject(): string; } function createAndSaveToken(userid: string, challenge: string, userDataStore: IUserDataStore) : BluebirdPromise { const five_minutes = 4 * 60 * 1000; const token = randomstring.generate({ length: 64 }); const that = this; return userDataStore.produceIdentityValidationToken(userid, token, challenge, five_minutes) .then(function () { return BluebirdPromise.resolve(token); }); } function consumeToken(token: string, challenge: string, userDataStore: IUserDataStore) : BluebirdPromise { return userDataStore.consumeIdentityValidationToken(token, challenge); } export function register(app: express.Application, pre_validation_endpoint: string, post_validation_endpoint: string, handler: IdentityValidable, vars: ServerVariables) { app.get(pre_validation_endpoint, get_start_validation(handler, post_validation_endpoint, vars)); app.get(post_validation_endpoint, get_finish_validation(handler, vars)); } function checkIdentityToken(req: express.Request, identityToken: string): BluebirdPromise { if (!identityToken) return BluebirdPromise.reject(new Exceptions.AccessDeniedError("No identity token provided")); return BluebirdPromise.resolve(); } export function get_finish_validation(handler: IdentityValidable, vars: ServerVariables) : express.RequestHandler { return function (req: express.Request, res: express.Response): BluebirdPromise { let authSession: AuthenticationSession; const identityToken = objectPath.get(req, "query.identity_token"); vars.logger.debug(req, "Identity token provided is %s", identityToken); return checkIdentityToken(req, identityToken) .then(function () { return handler.postValidationInit(req); }) .then(function () { return AuthenticationSessionHandler.get(req, vars.logger); }) .then(function (_authSession) { authSession = _authSession; }) .then(function () { return consumeToken(identityToken, handler.challenge(), vars.userDataStore); }) .then(function (doc: IdentityValidationDocument) { authSession.identity_check = { challenge: handler.challenge(), userid: doc.userId }; handler.postValidationResponse(req, res); return BluebirdPromise.resolve(); }) .catch(ErrorReplies.replyWithError401(req, res, vars.logger)); }; } export function get_start_validation(handler: IdentityValidable, postValidationEndpoint: string, vars: ServerVariables) : express.RequestHandler { return function (req: express.Request, res: express.Response): BluebirdPromise { let identity: Identity.Identity; return handler.preValidationInit(req) .then(function (id: Identity.Identity) { identity = id; const email = identity.email; const userid = identity.userid; vars.logger.info(req, "Start identity validation of user \"%s\"", userid); if (!(email && userid)) return BluebirdPromise.reject(new Exceptions.IdentityError( "Missing user id or email address")); return createAndSaveToken(userid, handler.challenge(), vars.userDataStore); }) .then(function (token: string) { const host = req.get("Host"); const link_url = util.format("https://%s%s?identity_token=%s", host, postValidationEndpoint, token); vars.logger.info(req, "Notification sent to user \"%s\"", identity.userid); return vars.notifier.notify(identity.email, handler.mailSubject(), link_url); }) .then(function () { handler.preValidationResponse(req, res); return BluebirdPromise.resolve(); }) .catch(ErrorReplies.replyWithError401(req, res, vars.logger)); }; }