authelia/server/src/lib/IdentityCheckMiddleware.ts
Clement Michaud 1ab09b71d4 Specify the sender email in Gmail and Smtp notifier configuration
Sender email address can now be specified in configuration and applies to
GMail notifier and SMTP notifier.
2017-10-10 00:07:12 +02:00

136 lines
5.9 KiB
TypeScript

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 { ServerVariablesHandler } from "./ServerVariablesHandler";
import AuthenticationSession = require("./AuthenticationSession");
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<Identity.Identity>;
postValidationInit(req: express.Request): BluebirdPromise<void>;
preValidationResponse(req: express.Request, res: express.Response): void; // Serves a page after identity check request
postValidationResponse(req: express.Request, res: express.Response): void; // Serves the page if identity validated
mailSubject(): string;
}
function createAndSaveToken(userid: string, challenge: string, userDataStore: IUserDataStore)
: BluebirdPromise<string> {
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<IdentityValidationDocument> {
return userDataStore.consumeIdentityValidationToken(token, challenge);
}
export function register(app: express.Application, pre_validation_endpoint: string, post_validation_endpoint: string, handler: IdentityValidable) {
app.get(pre_validation_endpoint, get_start_validation(handler, post_validation_endpoint));
app.get(post_validation_endpoint, get_finish_validation(handler));
}
function checkIdentityToken(req: express.Request, identityToken: string): BluebirdPromise<void> {
if (!identityToken)
return BluebirdPromise.reject(new Exceptions.AccessDeniedError("No identity token provided"));
return BluebirdPromise.resolve();
}
export function get_finish_validation(handler: IdentityValidable): express.RequestHandler {
return function (req: express.Request, res: express.Response): BluebirdPromise<void> {
const logger = ServerVariablesHandler.getLogger(req.app);
const userDataStore = ServerVariablesHandler.getUserDataStore(req.app);
let authSession: AuthenticationSession.AuthenticationSession;
const identityToken = objectPath.get<express.Request, string>(req, "query.identity_token");
logger.debug(req, "Identity token provided is %s", identityToken);
return checkIdentityToken(req, identityToken)
.then(function () {
return handler.postValidationInit(req);
})
.then(function () {
return AuthenticationSession.get(req);
})
.then(function (_authSession: AuthenticationSession.AuthenticationSession) {
authSession = _authSession;
})
.then(function () {
return consumeToken(identityToken, handler.challenge(), userDataStore);
})
.then(function (doc: IdentityValidationDocument) {
authSession.identity_check = {
challenge: handler.challenge(),
userid: doc.userId
};
handler.postValidationResponse(req, res);
return BluebirdPromise.resolve();
})
.catch(Exceptions.FirstFactorValidationError, ErrorReplies.replyWithError401(req, res, logger))
.catch(Exceptions.AccessDeniedError, ErrorReplies.replyWithError403(req, res, logger))
.catch(ErrorReplies.replyWithError500(req, res, logger));
};
}
export function get_start_validation(handler: IdentityValidable, postValidationEndpoint: string): express.RequestHandler {
return function (req: express.Request, res: express.Response): BluebirdPromise<void> {
const logger = ServerVariablesHandler.getLogger(req.app);
const notifier = ServerVariablesHandler.getNotifier(req.app);
const userDataStore = ServerVariablesHandler.getUserDataStore(req.app);
let identity: Identity.Identity;
return handler.preValidationInit(req)
.then(function (id: Identity.Identity) {
identity = id;
const email = identity.email;
const userid = identity.userid;
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(), userDataStore);
})
.then(function (token: string) {
const host = req.get("Host");
const link_url = util.format("https://%s%s?identity_token=%s", host, postValidationEndpoint, token);
logger.info(req, "Notification sent to user \"%s\"", identity.userid);
return notifier.notify(identity.email, handler.mailSubject(), link_url);
})
.then(function () {
handler.preValidationResponse(req, res);
return BluebirdPromise.resolve();
})
.catch(Exceptions.FirstFactorValidationError, ErrorReplies.replyWithError401(req, res, logger))
.catch(Exceptions.IdentityError, ErrorReplies.replyWithError400(req, res, logger))
.catch(Exceptions.AccessDeniedError, ErrorReplies.replyWithError403(req, res, logger))
.catch(ErrorReplies.replyWithError500(req, res, logger));
};
}