Finish migration to typescript

pull/33/head
Clement Michaud 2017-05-21 22:45:54 +02:00
parent e3257b81a5
commit 9e89a690fb
48 changed files with 1671 additions and 1499 deletions

View File

@ -59,6 +59,7 @@
"@types/nodemailer": "^1.3.32", "@types/nodemailer": "^1.3.32",
"@types/object-path": "^0.9.28", "@types/object-path": "^0.9.28",
"@types/proxyquire": "^1.3.27", "@types/proxyquire": "^1.3.27",
"@types/randomstring": "^1.1.5",
"@types/request": "0.0.43", "@types/request": "0.0.43",
"@types/sinon": "^2.2.1", "@types/sinon": "^2.2.1",
"@types/speakeasy": "^2.0.1", "@types/speakeasy": "^2.0.1",

View File

@ -0,0 +1,155 @@
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 UserDataStore from "./UserDataStore";
import { ILogger } from "../types/ILogger";
import express = require("express");
import Identity = require("../types/Identity");
import { IdentityValidationRequestContent } from "./UserDataStore";
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;
templateName(): string;
preValidation(req: express.Request): BluebirdPromise<Identity.Identity>;
mailSubject(): string;
}
export class IdentityValidator {
private userDataStore: UserDataStore;
private logger: ILogger;
constructor(userDataStore: UserDataStore, logger: ILogger) {
this.userDataStore = userDataStore;
this.logger = logger;
}
static setup(app: express.Application, endpoint: string, handler: IdentityValidable, userDataStore: UserDataStore, logger: ILogger) {
const identityValidator = new IdentityValidator(userDataStore, logger);
app.get(endpoint, identityValidator.identity_check_get(endpoint, handler));
app.post(endpoint, identityValidator.identity_check_post(endpoint, handler));
}
private issue_token(userid: string, content: Object): BluebirdPromise<string> {
const five_minutes = 4 * 60 * 1000;
const token = randomstring.generate({ length: 64 });
const that = this;
this.logger.debug("identity_check: issue identity token %s for 5 minutes", token);
return this.userDataStore.issue_identity_check_token(userid, token, content, five_minutes)
.then(function () {
return BluebirdPromise.resolve(token);
});
}
private consume_token(token: string): BluebirdPromise<IdentityValidationRequestContent> {
this.logger.debug("identity_check: consume token %s", token);
return this.userDataStore.consume_identity_check_token(token);
}
private identity_check_get(endpoint: string, handler: IdentityValidable): express.RequestHandler {
const that = this;
return function (req: express.Request, res: express.Response) {
const logger = req.app.get("logger");
const identity_token = objectPath.get<express.Request, string>(req, "query.identity_token");
logger.info("GET identity_check: identity token provided is %s", identity_token);
if (!identity_token) {
res.status(403);
res.send();
return;
}
that.consume_token(identity_token)
.then(function (content: IdentityValidationRequestContent) {
objectPath.set(req, "session.auth_session.identity_check", {});
req.session.auth_session.identity_check.challenge = handler.challenge();
req.session.auth_session.identity_check.userid = content.userid;
res.render(handler.templateName());
}, function (err: Error) {
logger.error("GET identity_check: Error while consuming token %s", err);
throw new exceptions.AccessDeniedError("Access denied");
})
.catch(exceptions.AccessDeniedError, function (err: Error) {
logger.error("GET identity_check: Access Denied %s", err);
res.status(403);
res.send();
})
.catch(function (err: Error) {
logger.error("GET identity_check: Internal error %s", err);
res.status(500);
res.send();
});
};
}
private identity_check_post(endpoint: string, handler: IdentityValidable): express.RequestHandler {
const that = this;
return function (req: express.Request, res: express.Response) {
const logger = req.app.get("logger");
const notifier = req.app.get("notifier");
let identity: Identity.Identity;
handler.preValidation(req)
.then(function (id: Identity.Identity) {
identity = id;
const email_address = objectPath.get<Identity.Identity, string>(identity, "email");
const userid = objectPath.get<Identity.Identity, string>(identity, "userid");
if (!(email_address && userid)) {
throw new exceptions.IdentityError("Missing user id or email address");
}
return that.issue_token(userid, undefined);
}, function (err: Error) {
throw new exceptions.AccessDeniedError(err.message);
})
.then(function (token: string) {
const redirect_url = objectPath.get<express.Request, string>(req, "body.redirect");
const original_url = util.format("https://%s%s", req.headers.host, req.headers["x-original-uri"]);
let link_url = util.format("%s?identity_token=%s", original_url, token);
if (redirect_url) {
link_url = util.format("%s&redirect=%s", link_url, redirect_url);
}
logger.info("POST identity_check: notify to %s", identity.userid);
return notifier.notify(identity, handler.mailSubject(), link_url);
})
.then(function () {
res.status(204);
res.send();
})
.catch(exceptions.IdentityError, function (err: Error) {
logger.error("POST identity_check: %s", err);
res.status(400);
res.send();
})
.catch(exceptions.AccessDeniedError, function (err: Error) {
logger.error("POST identity_check: %s", err);
res.status(403);
res.send();
})
.catch(function (err: Error) {
logger.error("POST identity_check: Error %s", err);
res.status(500);
res.send();
});
};
}
}

View File

@ -8,7 +8,7 @@ import ldapjs = require("ldapjs");
import { EventEmitter } from "events"; import { EventEmitter } from "events";
import { LdapConfiguration } from "./Configuration"; import { LdapConfiguration } from "./Configuration";
import { Ldapjs } from "../types/Dependencies"; import { Ldapjs } from "../types/Dependencies";
import { ILogger } from "./ILogger"; import { ILogger } from "../types/ILogger";
interface SearchEntry { interface SearchEntry {
object: any; object: any;

View File

@ -1,11 +1,12 @@
import express = require("express"); import express = require("express");
import routes = require("./routes");
const routes = require("./routes"); import IdentityValidator = require("./IdentityValidator");
const identity_check = require("./identity_check"); import UserDataStore from "./UserDataStore";
import { ILogger } from "../types/ILogger";
export default class RestApi { export default class RestApi {
static setup(app: express.Application): void { static setup(app: express.Application, userDataStore: UserDataStore, logger: ILogger): void {
/** /**
* @apiDefine UserSession * @apiDefine UserSession
* @apiHeader {String} Cookie Cookie containing "connect.sid", the user * @apiHeader {String} Cookie Cookie containing "connect.sid", the user
@ -86,7 +87,7 @@ export default class RestApi {
* @apiDescription Serves the TOTP registration page that displays the secret. * @apiDescription Serves the TOTP registration page that displays the secret.
* The secret is a QRCode and a base32 secret. * The secret is a QRCode and a base32 secret.
*/ */
identity_check(app, "/totp-register", routes.totp_register.icheck_interface); IdentityValidator.IdentityValidator.setup(app, "/totp-register", routes.totp_register.icheck_interface, userDataStore, logger);
/** /**
@ -108,7 +109,7 @@ export default class RestApi {
* @apiDescription Serves the U2F registration page that asks the user to * @apiDescription Serves the U2F registration page that asks the user to
* touch the token of the U2F device. * touch the token of the U2F device.
*/ */
identity_check(app, "/u2f-register", routes.u2f_register.icheck_interface); IdentityValidator.IdentityValidator.setup(app, "/u2f-register", routes.u2f_register.icheck_interface, userDataStore, logger);
/** /**
* @api {post} /reset-password Request for password reset * @api {post} /reset-password Request for password reset
@ -129,7 +130,7 @@ export default class RestApi {
* @apiDescription Serves password reset form that allow the user to provide * @apiDescription Serves password reset form that allow the user to provide
* the new password. * the new password.
*/ */
identity_check(app, "/reset-password", routes.reset_password.icheck_interface); IdentityValidator.IdentityValidator.setup(app, "/reset-password", routes.reset_password.icheck_interface, userDataStore, logger);
app.get("/reset-password-form", function (req, res) { res.render("reset-password-form"); }); app.get("/reset-password-form", function (req, res) { res.render("reset-password-form"); });

View File

@ -10,6 +10,7 @@ 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 BluebirdPromise = require("bluebird");
import { IdentityValidator } from "./IdentityValidator";
import * as Express from "express"; import * as Express from "express";
import * as BodyParser from "body-parser"; import * as BodyParser from "body-parser";
@ -55,26 +56,28 @@ 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, deps.nedb); const userDataStore = new UserDataStore(datastore_options, deps.nedb);
const regulator = new AuthenticationRegulator(data_store, five_minutes); const regulator = new AuthenticationRegulator(userDataStore, 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);
const accessController = new AccessController(config.access_control, deps.winston); const accessController = new AccessController(config.access_control, deps.winston);
const totpValidator = new TOTPValidator(deps.speakeasy); const totpValidator = new TOTPValidator(deps.speakeasy);
const totpGenerator = new TOTPGenerator(deps.speakeasy); const totpGenerator = new TOTPGenerator(deps.speakeasy);
const identityValidator = new IdentityValidator(userDataStore, deps.winston);
app.set("logger", deps.winston); app.set("logger", deps.winston);
app.set("ldap", ldap); app.set("ldap", ldap);
app.set("totp validator", totpValidator); app.set("totp validator", totpValidator);
app.set("totp generator", totpGenerator); app.set("totp generator", totpGenerator);
app.set("u2f", deps.u2f); app.set("u2f", deps.u2f);
app.set("user data store", data_store); app.set("user data store", userDataStore);
app.set("notifier", notifier); app.set("notifier", notifier);
app.set("authentication regulator", regulator); app.set("authentication regulator", regulator);
app.set("config", config); app.set("config", config);
app.set("access controller", accessController); app.set("access controller", accessController);
app.set("identity validator", identityValidator);
RestApi.setup(app); RestApi.setup(app, userDataStore, deps.winston);
return new BluebirdPromise<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) {

View File

@ -28,6 +28,17 @@ export interface Options {
directory?: string; directory?: string;
} }
export interface IdentityValidationRequestContent {
userid: string;
data: string;
}
export interface IdentityValidationRequestDocument {
userid: string;
token: string;
content: IdentityValidationRequestContent;
max_date: Date;
}
// Source // Source
@ -54,7 +65,7 @@ export default class UserDataStore {
userid: userid, userid: userid,
appid: appid, appid: appid,
meta: meta meta: meta
}; } as U2FMetaDocument;
const filter = { const filter = {
userid: userid, userid: userid,
@ -110,7 +121,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): BluebirdPromise<any> { consume_identity_check_token(token: string): BluebirdPromise<IdentityValidationRequestContent> {
const query = { const query = {
token: token token: token
}; };

View File

@ -1,7 +1,7 @@
import { ACLConfiguration } from "../Configuration"; import { ACLConfiguration } from "../Configuration";
import PatternBuilder from "./PatternBuilder"; import PatternBuilder from "./PatternBuilder";
import { ILogger } from "../ILogger"; import { ILogger } from "../../types/ILogger";
export default class AccessController { export default class AccessController {
private logger: ILogger; private logger: ILogger;

View File

@ -1,5 +1,5 @@
import { ILogger } from "../ILogger"; import { ILogger } from "../../types/ILogger";
import { ACLConfiguration, ACLGroupsRules, ACLUsersRules, ACLDefaultRules } from "../Configuration"; import { ACLConfiguration, ACLGroupsRules, ACLUsersRules, ACLDefaultRules } from "../Configuration";
import objectPath = require("object-path"); import objectPath = require("object-path");

View File

@ -1,144 +0,0 @@
var objectPath = require('object-path');
var randomstring = require('randomstring');
var BluebirdPromise = require('bluebird');
var util = require('util');
var exceptions = require('./Exceptions');
var fs = require('fs');
var ejs = require('ejs');
module.exports = identity_check;
var filePath = __dirname + '/../resources/email-template.ejs';
var email_template = fs.readFileSync(filePath, 'utf8');
// IdentityCheck class
function IdentityCheck(user_data_store, logger) {
this._user_data_store = user_data_store;
this._logger = logger;
}
IdentityCheck.prototype.issue_token = function(userid, content, logger) {
var five_minutes = 4 * 60 * 1000;
var token = randomstring.generate({ length: 64 });
var that = this;
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)
.then(function() {
return BluebirdPromise.resolve(token);
});
}
IdentityCheck.prototype.consume_token = function(token, logger) {
this._logger.debug('identity_check: consume token %s', token);
return this._user_data_store.consume_identity_check_token(token)
}
// The identity_check middleware that allows the user two perform a two step validation
// using the user email
function identity_check(app, endpoint, icheck_interface) {
app.get(endpoint, identity_check_get(endpoint, icheck_interface));
app.post(endpoint, identity_check_post(endpoint, icheck_interface));
}
function identity_check_get(endpoint, icheck_interface) {
return function(req, res) {
var logger = req.app.get('logger');
var identity_token = objectPath.get(req, 'query.identity_token');
logger.info('GET identity_check: identity token provided is %s', identity_token);
if(!identity_token) {
res.status(403);
res.send();
return;
}
var email_sender = req.app.get('email sender');
var user_data_store = req.app.get('user data store');
var identity_check = new IdentityCheck(user_data_store, logger);
identity_check.consume_token(identity_token, logger)
.then(function(content) {
objectPath.set(req, 'session.auth_session.identity_check', {});
req.session.auth_session.identity_check.challenge = icheck_interface.challenge;
req.session.auth_session.identity_check.userid = content.userid;
res.render(icheck_interface.render_template);
}, function(err) {
logger.error('GET identity_check: Error while consuming token %s', err);
throw new exceptions.AccessDeniedError('Access denied');
})
.catch(exceptions.AccessDeniedError, function(err) {
logger.error('GET identity_check: Access Denied %s', err);
res.status(403);
res.send();
  })
.catch(function(err) {
logger.error('GET identity_check: Internal error %s', err);
res.status(500);
res.send();
});
}
}
function identity_check_post(endpoint, icheck_interface) {
return function(req, res) {
var logger = req.app.get('logger');
var notifier = req.app.get('notifier');
var user_data_store = req.app.get('user data store');
var identity_check = new IdentityCheck(user_data_store, logger);
var identity;
icheck_interface.pre_check_callback(req)
.then(function(id) {
identity = id;
var email_address = objectPath.get(identity, 'email');
var userid = objectPath.get(identity, 'userid');
if(!(email_address && userid)) {
throw new exceptions.IdentityError('Missing user id or email address');
}
return identity_check.issue_token(userid, undefined, logger);
}, function(err) {
throw new exceptions.AccessDeniedError(err);
})
.then(function(token) {
var redirect_url = objectPath.get(req, 'body.redirect');
var original_url = util.format('https://%s%s', req.headers.host, req.headers['x-original-uri']);
var link_url = util.format('%s?identity_token=%s', original_url, token);
if(redirect_url) {
link_url = util.format('%s&redirect=%s', link_url, redirect_url);
}
logger.info('POST identity_check: notify to %s', identity.userid);
return notifier.notify(identity, icheck_interface.email_subject, link_url);
})
.then(function() {
res.status(204);
res.send();
})
.catch(exceptions.IdentityError, function(err) {
logger.error('POST identity_check: %s', err);
res.status(400);
res.send();
})
.catch(exceptions.AccessDeniedError, function(err) {
logger.error('POST identity_check: %s', err);
res.status(403);
res.send();
})
.catch(function(err) {
logger.error('POST identity_check: Error %s', err);
res.status(500);
res.send();
});
}
}

View File

@ -1,10 +1,10 @@
import FirstFactor = require("./routes/FirstFactor"); import FirstFactor = require("./routes/FirstFactor");
import second_factor = require("./routes/second_factor"); import SecondFactorRoutes = require("./routes/SecondFactorRoutes");
import reset_password = require("./routes/reset_password"); import PasswordReset = require("./routes/PasswordReset");
import AuthenticationValidator = require("./routes/AuthenticationValidator"); import AuthenticationValidator = require("./routes/AuthenticationValidator");
import u2f_register_handler = require("./routes/u2f_register_handler"); import U2FRegistration = require("./routes/U2FRegistration");
import totp_register = require("./routes/totp_register"); import TOTPRegistration = require("./routes/TOTPRegistration");
import objectPath = require("object-path"); import objectPath = require("object-path");
import express = require("express"); import express = require("express");
@ -14,10 +14,10 @@ export = {
logout: serveLogout, logout: serveLogout,
verify: AuthenticationValidator, verify: AuthenticationValidator,
first_factor: FirstFactor, first_factor: FirstFactor,
second_factor: second_factor, second_factor: SecondFactorRoutes,
reset_password: reset_password, reset_password: PasswordReset,
u2f_register: u2f_register_handler, u2f_register: U2FRegistration,
totp_register: totp_register, totp_register: TOTPRegistration,
}; };
function serveLogin(req: express.Request, res: express.Response) { function serveLogin(req: express.Request, res: express.Response) {

View File

@ -2,8 +2,10 @@
import objectPath = require("object-path"); import objectPath = require("object-path");
import express = require("express"); import express = require("express");
export = function denyNotLogged(callback: (req: express.Request, res: express.Response) => void) { type ExpressRequest = (req: express.Request, res: express.Response, next?: express.NextFunction) => void;
return function (req: express.Request, res: express.Response) {
export = function(callback: ExpressRequest): ExpressRequest {
return function (req: express.Request, res: express.Response, next: express.NextFunction) {
const auth_session = req.session.auth_session; const auth_session = req.session.auth_session;
const first_factor = objectPath.has(req, "session.auth_session.first_factor") const first_factor = objectPath.has(req, "session.auth_session.first_factor")
&& req.session.auth_session.first_factor; && req.session.auth_session.first_factor;
@ -12,7 +14,6 @@ export = function denyNotLogged(callback: (req: express.Request, res: express.Re
res.send(); res.send();
return; return;
} }
callback(req, res, next);
callback(req, res);
}; };
}; };

View File

@ -0,0 +1,81 @@
import BluebirdPromise = require("bluebird");
import objectPath = require("object-path");
import exceptions = require("../Exceptions");
import express = require("express");
import { Identity } from "../../types/Identity";
import { IdentityValidable } from "../IdentityValidator";
const CHALLENGE = "reset-password";
class PasswordResetHandler implements IdentityValidable {
challenge(): string {
return CHALLENGE;
}
templateName(): string {
return "reset-password";
}
preValidation(req: express.Request): BluebirdPromise<Identity> {
const userid = objectPath.get(req, "body.userid");
if (!userid) {
return BluebirdPromise.reject(new exceptions.AccessDeniedError("No user id provided"));
}
const ldap = req.app.get("ldap");
return ldap.get_emails(userid)
.then(function (emails: string[]) {
if (!emails && emails.length <= 0) throw new Error("No email found");
const identity = {
email: emails[0],
userid: userid
};
return BluebirdPromise.resolve(identity);
});
}
mailSubject(): string {
return "Reset your password";
}
}
function protect(fn: express.RequestHandler) {
return function (req: express.Request, res: express.Response) {
const challenge = objectPath.get(req, "session.auth_session.identity_check.challenge");
if (challenge != CHALLENGE) {
res.status(403);
res.send();
return;
}
fn(req, res, undefined);
};
}
function post(req: express.Request, res: express.Response) {
const logger = req.app.get("logger");
const ldap = req.app.get("ldap");
const new_password = objectPath.get(req, "body.password");
const userid = objectPath.get(req, "session.auth_session.identity_check.userid");
logger.info("POST reset-password: User %s wants to reset his/her password", userid);
ldap.update_password(userid, new_password)
.then(function () {
logger.info("POST reset-password: Password reset for user %s", userid);
objectPath.set(req, "session.auth_session", undefined);
res.status(204);
res.send();
})
.catch(function (err: Error) {
logger.error("POST reset-password: Error while resetting the password of user %s. %s", userid, err);
res.status(500);
res.send();
});
}
export = {
icheck_interface: new PasswordResetHandler(),
post: protect(post)
};

View File

@ -0,0 +1,28 @@
import DenyNotLogged = require("./DenyNotLogged");
import U2FRoutes = require("./U2FRoutes");
import TOTPAuthenticator = require("./TOTPAuthenticator");
import express = require("express");
interface SecondFactorRoutes {
totp: express.RequestHandler;
u2f: {
register_request: express.RequestHandler;
register: express.RequestHandler;
sign_request: express.RequestHandler;
sign: express.RequestHandler;
};
}
export = {
totp: DenyNotLogged(TOTPAuthenticator),
u2f: {
register_request: U2FRoutes.register_request,
register: U2FRoutes.register,
sign_request: DenyNotLogged(U2FRoutes.sign_request),
sign: DenyNotLogged(U2FRoutes.sign),
}
} as SecondFactorRoutes;

View File

@ -0,0 +1,86 @@
import objectPath = require("object-path");
import BluebirdPromise = require("bluebird");
import express = require("express");
import exceptions = require("../Exceptions");
import { Identity } from "../../types/Identity";
import { IdentityValidable } from "../IdentityValidator";
const CHALLENGE = "totp-register";
const TEMPLATE_NAME = "totp-register";
class TOTPRegistrationHandler implements IdentityValidable {
challenge(): string {
return CHALLENGE;
}
templateName(): string {
return TEMPLATE_NAME;
}
preValidation(req: express.Request): BluebirdPromise<Identity> {
const first_factor_passed = objectPath.get(req, "session.auth_session.first_factor");
if (!first_factor_passed) {
return BluebirdPromise.reject("Authentication required before registering TOTP secret key");
}
const userid = objectPath.get<express.Request, string>(req, "session.auth_session.userid");
const email = objectPath.get<express.Request, string>(req, "session.auth_session.email");
if (!(userid && email)) {
return BluebirdPromise.reject("User ID or email is missing");
}
const identity = {
email: email,
userid: userid
};
return BluebirdPromise.resolve(identity);
}
mailSubject(): string {
return "Register your TOTP secret key";
}
}
// Generate a secret and send it to the user
function post(req: express.Request, res: express.Response) {
const logger = req.app.get("logger");
const userid = objectPath.get(req, "session.auth_session.identity_check.userid");
const challenge = objectPath.get(req, "session.auth_session.identity_check.challenge");
if (challenge != CHALLENGE || !userid) {
res.status(403);
res.send();
return;
}
const user_data_store = req.app.get("user data store");
const totpGenerator = req.app.get("totp generator");
const secret = totpGenerator.generate();
logger.debug("POST new-totp-secret: save the TOTP secret in DB");
user_data_store.set_totp_secret(userid, secret)
.then(function () {
const doc = {
otpauth_url: secret.otpauth_url,
base32: secret.base32,
ascii: secret.ascii
};
objectPath.set(req, "session", undefined);
res.status(200);
res.json(doc);
})
.catch(function (err: Error) {
logger.error("POST new-totp-secret: Internal error %s", err);
res.status(500);
res.send();
});
}
export = {
icheck_interface: new TOTPRegistrationHandler(),
post: post,
};

View File

@ -0,0 +1,84 @@
import u2f_register_handler = require("./U2FRegistration");
import objectPath = require("object-path");
import u2f_common = require("./u2f_common");
import BluebirdPromise = require("bluebird");
import express = require("express");
import authdog = require("../../types/authdog");
import UserDataStore, { U2FMetaDocument } from "../UserDataStore";
function retrieve_u2f_meta(req: express.Request, userDataStore: UserDataStore) {
const userid = req.session.auth_session.userid;
const appid = u2f_common.extract_app_id(req);
return userDataStore.get_u2f_meta(userid, appid);
}
function sign_request(req: express.Request, res: express.Response) {
const logger = req.app.get("logger");
const userDataStore = req.app.get("user data store");
retrieve_u2f_meta(req, userDataStore)
.then(function (doc: U2FMetaDocument) {
if (!doc) {
u2f_common.reply_with_missing_registration(res);
return;
}
const u2f = req.app.get("u2f");
const meta = doc.meta;
const appid = u2f_common.extract_app_id(req);
logger.info("U2F sign_request: Start authentication to app %s", appid);
return u2f.startAuthentication(appid, [meta]);
})
.then(function (authRequest: authdog.AuthenticationRequest) {
logger.info("U2F sign_request: Store authentication request and reply");
req.session.auth_session.sign_request = authRequest;
res.status(200);
res.json(authRequest);
})
.catch(function (err: Error) {
logger.info("U2F sign_request: %s", err);
res.status(500);
res.send();
});
}
function sign(req: express.Request, res: express.Response) {
if (!objectPath.has(req, "session.auth_session.sign_request")) {
u2f_common.reply_with_unauthorized(res);
return;
}
const logger = req.app.get("logger");
const userDataStore = req.app.get("user data store");
retrieve_u2f_meta(req, userDataStore)
.then(function (doc: U2FMetaDocument) {
const appid = u2f_common.extract_app_id(req);
const u2f = req.app.get("u2f");
const authRequest = req.session.auth_session.sign_request;
const meta = doc.meta;
logger.info("U2F sign: Finish authentication");
return u2f.finishAuthentication(authRequest, req.body, [meta]);
})
.then(function (authenticationStatus: authdog.Authentication) {
logger.info("U2F sign: Authentication successful");
req.session.auth_session.second_factor = true;
res.status(204);
res.send();
})
.catch(function (err: Error) {
logger.error("U2F sign: %s", err);
res.status(500);
res.send();
});
}
export = {
sign_request: sign_request,
sign: sign
};

View File

@ -0,0 +1,51 @@
import objectPath = require("object-path");
import BluebirdPromise = require("bluebird");
import express = require("express");
import { IdentityValidable } from "../IdentityValidator";
import { Identity } from "../../types/Identity";
const CHALLENGE = "u2f-register";
const TEMPLATE_NAME = "u2f-register";
const MAIL_SUBJECT = "Register your U2F device";
class U2FRegistrationHandler implements IdentityValidable {
challenge(): string {
return CHALLENGE;
}
templateName(): string {
return TEMPLATE_NAME;
}
preValidation(req: express.Request): BluebirdPromise<Identity> {
const first_factor_passed = objectPath.get(req, "session.auth_session.first_factor");
if (!first_factor_passed) {
return BluebirdPromise.reject("Authentication required before issuing a u2f registration request");
}
const userid = objectPath.get<express.Request, string>(req, "session.auth_session.userid");
const email = objectPath.get<express.Request, string>(req, "session.auth_session.email");
if (!(userid && email)) {
return BluebirdPromise.reject("User ID or email is missing");
}
const identity = {
email: email,
userid: userid
};
return BluebirdPromise.resolve(identity);
}
mailSubject(): string {
return MAIL_SUBJECT;
}
}
export = {
icheck_interface: new U2FRegistrationHandler(),
};

View File

@ -0,0 +1,89 @@
import u2f_register_handler = require("./U2FRegistration");
import objectPath = require("object-path");
import u2f_common = require("./u2f_common");
import BluebirdPromise = require("bluebird");
import express = require("express");
import authdog = require("../../types/authdog");
function register_request(req: express.Request, res: express.Response) {
const logger = req.app.get("logger");
const challenge = objectPath.get(req, "session.auth_session.identity_check.challenge");
if (challenge != "u2f-register") {
res.status(403);
res.send();
return;
}
const u2f = req.app.get("u2f");
const appid = u2f_common.extract_app_id(req);
logger.debug("U2F register_request: headers=%s", JSON.stringify(req.headers));
logger.info("U2F register_request: Starting registration of app %s", appid);
u2f.startRegistration(appid, [])
.then(function (registrationRequest: authdog.AuthenticationRequest) {
logger.info("U2F register_request: Sending back registration request");
req.session.auth_session.register_request = registrationRequest;
res.status(200);
res.json(registrationRequest);
})
.catch(function (err: Error) {
logger.error("U2F register_request: %s", err);
res.status(500);
res.send("Unable to start registration request");
});
}
function register(req: express.Request, res: express.Response) {
const registrationRequest = objectPath.get(req, "session.auth_session.register_request");
const challenge = objectPath.get(req, "session.auth_session.identity_check.challenge");
if (!registrationRequest) {
res.status(403);
res.send();
return;
}
if (!(registrationRequest && challenge == "u2f-register")) {
res.status(403);
res.send();
return;
}
const user_data_storage = req.app.get("user data store");
const u2f = req.app.get("u2f");
const userid = req.session.auth_session.userid;
const appid = u2f_common.extract_app_id(req);
const logger = req.app.get("logger");
logger.info("U2F register: Finishing registration");
logger.debug("U2F register: register_request=%s", JSON.stringify(registrationRequest));
logger.debug("U2F register: body=%s", JSON.stringify(req.body));
u2f.finishRegistration(registrationRequest, req.body)
.then(function (registrationStatus: authdog.Registration) {
logger.info("U2F register: Store registration and reply");
const meta = {
keyHandle: registrationStatus.keyHandle,
publicKey: registrationStatus.publicKey,
certificate: registrationStatus.certificate
};
return user_data_storage.set_u2f_meta(userid, appid, meta);
})
.then(function () {
objectPath.set(req, "session.auth_session.identity_check", undefined);
res.status(204);
res.send();
})
.catch(function (err: Error) {
logger.error("U2F register: %s", err);
res.status(500);
res.send("Unable to register");
});
}
export = {
register_request: register_request,
register: register
};

View File

@ -0,0 +1,19 @@
import U2FRegistrationProcess = require("./U2FRegistrationProcess");
import U2FAuthenticationProcess = require("./U2FAuthenticationProcess");
import express = require("express");
interface U2FRoutes {
register_request: express.RequestHandler;
register: express.RequestHandler;
sign_request: express.RequestHandler;
sign: express.RequestHandler;
}
export = {
register_request: U2FRegistrationProcess.register_request,
register: U2FRegistrationProcess.register,
sign_request: U2FAuthenticationProcess.sign_request,
sign: U2FAuthenticationProcess.sign,
} as U2FRoutes;

View File

@ -1,70 +0,0 @@
var BluebirdPromise = require('bluebird');
var objectPath = require('object-path');
var exceptions = require('../Exceptions');
var CHALLENGE = 'reset-password';
var icheck_interface = {
challenge: CHALLENGE,
render_template: 'reset-password',
pre_check_callback: pre_check,
email_subject: 'Reset your password',
}
module.exports = {
icheck_interface: icheck_interface,
post: protect(post)
}
function pre_check(req) {
var userid = objectPath.get(req, 'body.userid');
if(!userid) {
return BluebirdPromise.reject(new exceptions.AccessDeniedError("No user id provided"));
}
var ldap = req.app.get('ldap');
return ldap.get_emails(userid)
.then(function(emails) {
if(!emails && emails.length <= 0) throw new Error('No email found');
var identity = {}
identity.email = emails[0];
identity.userid = userid;
return BluebirdPromise.resolve(identity);
});
}
function protect(fn) {
return function(req, res) {
var challenge = objectPath.get(req, 'session.auth_session.identity_check.challenge');
if(challenge != CHALLENGE) {
res.status(403);
res.send();
return;
}
fn(req, res);
  }
}
function post(req, res) {
var logger = req.app.get('logger');
var ldap = req.app.get('ldap');
var new_password = objectPath.get(req, 'body.password');
var userid = objectPath.get(req, 'session.auth_session.identity_check.userid');
logger.info('POST reset-password: User %s wants to reset his/her password', userid);
ldap.update_password(userid, new_password)
.then(function() {
logger.info('POST reset-password: Password reset for user %s', userid);
objectPath.set(req, 'session.auth_session', undefined);
res.status(204);
res.send();
})
.catch(function(err) {
logger.error('POST reset-password: Error while resetting the password of user %s. %s', userid, err);
res.status(500);
res.send();
});
}

View File

@ -1,18 +0,0 @@
var DenyNotLogged = require('./DenyNotLogged');
var u2f = require('./u2f');
var TOTPAuthenticator = require("./TOTPAuthenticator");
module.exports = {
totp: DenyNotLogged(TOTPAuthenticator),
u2f: {
register_request: u2f.register_request,
register: u2f.register,
register_handler_get: u2f.register_handler_get,
register_handler_post: u2f.register_handler_post,
sign_request: DenyNotLogged(u2f.sign_request),
sign: DenyNotLogged(u2f.sign),
}
}

View File

@ -1,72 +0,0 @@
var objectPath = require('object-path');
var BluebirdPromise = require('bluebird');
var CHALLENGE = 'totp-register';
var icheck_interface = {
challenge: CHALLENGE,
render_template: 'totp-register',
pre_check_callback: pre_check,
email_subject: 'Register your TOTP secret key',
}
module.exports = {
icheck_interface: icheck_interface,
post: post,
}
function pre_check(req) {
var first_factor_passed = objectPath.get(req, 'session.auth_session.first_factor');
if(!first_factor_passed) {
return BluebirdPromise.reject('Authentication required before registering TOTP secret key');
}
var userid = objectPath.get(req, 'session.auth_session.userid');
var email = objectPath.get(req, 'session.auth_session.email');
if(!(userid && email)) {
return BluebirdPromise.reject('User ID or email is missing');
}
var identity = {};
identity.email = email;
identity.userid = userid;
return BluebirdPromise.resolve(identity);
}
// Generate a secret and send it to the user
function post(req, res) {
var logger = req.app.get('logger');
var userid = objectPath.get(req, 'session.auth_session.identity_check.userid');
var challenge = objectPath.get(req, 'session.auth_session.identity_check.challenge');
if(challenge != CHALLENGE || !userid) {
res.status(403);
res.send();
return;
}
var user_data_store = req.app.get('user data store');
var totpGenerator = req.app.get('totp generator');
var secret = totpGenerator.generate();
logger.debug('POST new-totp-secret: save the TOTP secret in DB');
user_data_store.set_totp_secret(userid, secret)
.then(function() {
var doc = {};
doc.otpauth_url = secret.otpauth_url;
doc.base32 = secret.base32;
doc.ascii = secret.ascii;
objectPath.set(req, 'session', undefined);
res.status(200);
res.json(doc);
})
.catch(function(err) {
logger.error('POST new-totp-secret: Internal error %s', err);
res.status(500);
res.send();
});
}

View File

@ -1,85 +0,0 @@
var u2f_register = require('./u2f_register');
var u2f_common = require('./u2f_common');
var objectPath = require('object-path');
module.exports = {
register_request: u2f_register.register_request,
register: u2f_register.register,
register_handler_get: u2f_register.register_handler_get,
register_handler_post: u2f_register.register_handler_post,
sign_request: sign_request,
sign: sign,
}
function retrieve_u2f_meta(req, user_data_storage) {
var userid = req.session.auth_session.userid;
var appid = u2f_common.extract_app_id(req);
return user_data_storage.get_u2f_meta(userid, appid);
}
function sign_request(req, res) {
var logger = req.app.get('logger');
var user_data_storage = req.app.get('user data store');
retrieve_u2f_meta(req, user_data_storage)
.then(function(doc) {
if(!doc) {
u2f_common.reply_with_missing_registration(res);
return;
}
var u2f = req.app.get('u2f');
var meta = doc.meta;
var appid = u2f_common.extract_app_id(req);
logger.info('U2F sign_request: Start authentication to app %s', appid);
return u2f.startAuthentication(appid, [meta])
})
.then(function(authRequest) {
logger.info('U2F sign_request: Store authentication request and reply');
req.session.auth_session.sign_request = authRequest;
res.status(200);
res.json(authRequest);
})
.catch(function(err) {
logger.info('U2F sign_request: %s', err);
res.status(500);
res.send();
});
}
function sign(req, res) {
if(!objectPath.has(req, 'session.auth_session.sign_request')) {
u2f_common.reply_with_unauthorized(res);
return;
}
var logger = req.app.get('logger');
var user_data_storage = req.app.get('user data store');
retrieve_u2f_meta(req, user_data_storage)
.then(function(doc) {
var appid = u2f_common.extract_app_id(req);
var u2f = req.app.get('u2f');
var authRequest = req.session.auth_session.sign_request;
var meta = doc.meta;
logger.info('U2F sign: Finish authentication');
return u2f.finishAuthentication(authRequest, req.body, [meta])
})
.then(function(authenticationStatus) {
logger.info('U2F sign: Authentication successful');
req.session.auth_session.second_factor = true;
res.status(204);
res.send();
})
.catch(function(err) {
logger.error('U2F sign: %s', err);
res.status(500);
res.send();
});
}

View File

@ -1,38 +0,0 @@
module.exports = {
extract_app_id: extract_app_id,
extract_original_url: extract_original_url,
extract_referrer: extract_referrer,
reply_with_internal_error: reply_with_internal_error,
reply_with_missing_registration: reply_with_missing_registration,
reply_with_unauthorized: reply_with_unauthorized
}
var util = require('util');
function extract_app_id(req) {
return util.format('https://%s', req.headers.host);
}
function extract_original_url(req) {
return util.format('https://%s%s', req.headers.host, req.headers['x-original-uri']);
}
function extract_referrer(req) {
return req.headers.referrer;
}
function reply_with_internal_error(res, msg) {
res.status(500);
res.send(msg)
}
function reply_with_missing_registration(res) {
res.status(401);
res.send('Please register before authenticate');
}
function reply_with_unauthorized(res) {
res.status(401);
res.send();
}

View File

@ -0,0 +1,39 @@
import util = require("util");
import express = require("express");
function extract_app_id(req: express.Request) {
return util.format("https://%s", req.headers.host);
}
function extract_original_url(req: express.Request) {
return util.format("https://%s%s", req.headers.host, req.headers["x-original-uri"]);
}
function extract_referrer(req: express.Request) {
return req.headers.referrer;
}
function reply_with_internal_error(res: express.Response, msg: string) {
res.status(500);
res.send(msg);
}
function reply_with_missing_registration(res: express.Response) {
res.status(401);
res.send("Please register before authenticate");
}
function reply_with_unauthorized(res: express.Response) {
res.status(401);
res.send();
}
export = {
extract_app_id: extract_app_id,
extract_original_url: extract_original_url,
extract_referrer: extract_referrer,
reply_with_internal_error: reply_with_internal_error,
reply_with_missing_registration: reply_with_missing_registration,
reply_with_unauthorized: reply_with_unauthorized
};

View File

@ -1,91 +0,0 @@
var u2f_register_handler = require('./u2f_register_handler');
module.exports = {
register_request: register_request,
register: register,
register_handler_get: u2f_register_handler.get,
register_handler_post: u2f_register_handler.post
}
var objectPath = require('object-path');
var u2f_common = require('./u2f_common');
var BluebirdPromise = require('bluebird');
function register_request(req, res) {
var logger = req.app.get('logger');
var challenge = objectPath.get(req, 'session.auth_session.identity_check.challenge');
if(challenge != 'u2f-register') {
res.status(403);
res.send();
return;
}
var u2f = req.app.get('u2f');
var appid = u2f_common.extract_app_id(req);
logger.debug('U2F register_request: headers=%s', JSON.stringify(req.headers));
logger.info('U2F register_request: Starting registration of app %s', appid);
u2f.startRegistration(appid, [])
.then(function(registrationRequest) {
logger.info('U2F register_request: Sending back registration request');
req.session.auth_session.register_request = registrationRequest;
res.status(200);
res.json(registrationRequest);
})
.catch(function(err) {
logger.error('U2F register_request: %s', err);
res.status(500);
res.send('Unable to start registration request');
});
}
function register(req, res) {
var registrationRequest = objectPath.get(req, 'session.auth_session.register_request');
var challenge = objectPath.get(req, 'session.auth_session.identity_check.challenge');
if(!registrationRequest) {
res.status(403);
res.send();
return;
}
if(!(registrationRequest && challenge == 'u2f-register')) {
res.status(403);
res.send();
return;
}
var user_data_storage = req.app.get('user data store');
var u2f = req.app.get('u2f');
var userid = req.session.auth_session.userid;
var appid = u2f_common.extract_app_id(req);
var logger = req.app.get('logger');
logger.info('U2F register: Finishing registration');
logger.debug('U2F register: register_request=%s', JSON.stringify(registrationRequest));
logger.debug('U2F register: body=%s', JSON.stringify(req.body));
u2f.finishRegistration(registrationRequest, req.body)
.then(function(registrationStatus) {
logger.info('U2F register: Store registration and reply');
var meta = {
keyHandle: registrationStatus.keyHandle,
publicKey: registrationStatus.publicKey,
certificate: registrationStatus.certificate
}
return user_data_storage.set_u2f_meta(userid, appid, meta);
})
.then(function() {
objectPath.set(req, 'session.auth_session.identity_check', undefined);
res.status(204);
res.send();
})
.catch(function(err) {
logger.error('U2F register: %s', err);
res.status(500);
res.send('Unable to register');
});
}

View File

@ -1,37 +0,0 @@
var objectPath = require('object-path');
var BluebirdPromise = require('bluebird');
var CHALLENGE = 'u2f-register';
var icheck_interface = {
challenge: CHALLENGE,
render_template: 'u2f-register',
pre_check_callback: pre_check,
email_subject: 'Register your U2F device',
}
module.exports = {
icheck_interface: icheck_interface,
}
function pre_check(req) {
var first_factor_passed = objectPath.get(req, 'session.auth_session.first_factor');
if(!first_factor_passed) {
return BluebirdPromise.reject('Authentication required before issuing a u2f registration request');
}
var userid = objectPath.get(req, 'session.auth_session.userid');
var email = objectPath.get(req, 'session.auth_session.email');
if(!(userid && email)) {
return BluebirdPromise.reject('User ID or email is missing');
}
var identity = {};
identity.email = email;
identity.userid = userid;
return BluebirdPromise.resolve(identity);
}

View File

@ -0,0 +1,213 @@
import sinon = require("sinon");
import IdentityValidator = require("../../src/lib/IdentityValidator");
import exceptions = require("../../src/lib/Exceptions");
import assert = require("assert");
import winston = require("winston");
import Promise = require("bluebird");
import express = require("express");
import BluebirdPromise = require("bluebird");
import ExpressMock = require("./mocks/express");
import UserDataStoreMock = require("./mocks/UserDataStore");
import NotifierMock = require("./mocks/Notifier");
import IdentityValidatorMock = require("./mocks/IdentityValidator");
describe("test identity check process", function() {
let req: ExpressMock.RequestMock;
let res: ExpressMock.ResponseMock;
let userDataStore: UserDataStoreMock.UserDataStore;
let notifier: NotifierMock.NotifierMock;
let app: express.Application;
let app_get: sinon.SinonStub;
let app_post: sinon.SinonStub;
let identityValidable: IdentityValidatorMock.IdentityValidableMock;
beforeEach(function() {
req = ExpressMock.RequestMock();
res = ExpressMock.ResponseMock();
userDataStore = UserDataStoreMock.UserDataStore();
userDataStore.issue_identity_check_token = sinon.stub();
userDataStore.issue_identity_check_token.returns(Promise.resolve());
userDataStore.consume_identity_check_token = sinon.stub();
userDataStore.consume_identity_check_token.returns(Promise.resolve({ userid: "user" }));
notifier = NotifierMock.NotifierMock();
notifier.notify = sinon.stub().returns(Promise.resolve());
req.headers = {};
req.session = {};
req.session.auth_session = {};
req.query = {};
req.app = {};
req.app.get = sinon.stub();
req.app.get.withArgs("logger").returns(winston);
req.app.get.withArgs("user data store").returns(userDataStore);
req.app.get.withArgs("notifier").returns(notifier);
app = express();
app_get = sinon.stub(app, "get");
app_post = sinon.stub(app, "post");
identityValidable = IdentityValidatorMock.IdentityValidableMock();
});
afterEach(function() {
app_get.restore();
app_post.restore();
});
it("should register a POST and GET endpoint", function() {
const endpoint = "/test";
const icheck_interface = {};
IdentityValidator.IdentityValidator.setup(app, endpoint, identityValidable, userDataStore as any, winston);
assert(app_get.calledOnce);
assert(app_get.calledWith(endpoint));
assert(app_post.calledOnce);
assert(app_post.calledWith(endpoint));
});
describe("test POST", test_post_handler);
describe("test GET", test_get_handler);
function test_post_handler() {
it("should send 403 if pre check rejects", function(done) {
const endpoint = "/protected";
identityValidable.preValidation.returns(Promise.reject("No access"));
IdentityValidator.IdentityValidator.setup(app, endpoint, identityValidable, userDataStore as any, winston);
res.send = sinon.spy(function() {
assert.equal(res.status.getCall(0).args[0], 403);
done();
});
const handler = app_post.getCall(0).args[1];
handler(req, res);
});
it("should send 400 if email is missing in provided identity", function(done) {
const endpoint = "/protected";
const identity = { userid: "abc" };
identityValidable.preValidation.returns(Promise.resolve(identity));
IdentityValidator.IdentityValidator.setup(app, endpoint, identityValidable, userDataStore as any, winston);
res.send = sinon.spy(function() {
assert.equal(res.status.getCall(0).args[0], 400);
done();
});
const handler = app_post.getCall(0).args[1];
handler(req, res);
});
it("should send 400 if userid is missing in provided identity", function(done) {
const endpoint = "/protected";
const identity = { email: "abc@example.com" };
identityValidable.preValidation.returns(Promise.resolve(identity));
IdentityValidator.IdentityValidator.setup(app, endpoint, identityValidable, userDataStore as any, winston);
res.send = sinon.spy(function() {
assert.equal(res.status.getCall(0).args[0], 400);
done();
});
const handler = app_post.getCall(0).args[1];
handler(req, res);
});
it("should issue a token, send an email and return 204", function(done) {
const endpoint = "/protected";
const identity = { userid: "user", email: "abc@example.com" };
req.headers.host = "localhost";
req.headers["x-original-uri"] = "/auth/test";
identityValidable.preValidation.returns(Promise.resolve(identity));
IdentityValidator.IdentityValidator.setup(app, endpoint, identityValidable, userDataStore as any, winston);
res.send = sinon.spy(function() {
assert.equal(res.status.getCall(0).args[0], 204);
assert(notifier.notify.calledOnce);
assert(userDataStore.issue_identity_check_token.calledOnce);
assert.equal(userDataStore.issue_identity_check_token.getCall(0).args[0], "user");
assert.equal(userDataStore.issue_identity_check_token.getCall(0).args[3], 240000);
done();
});
const handler = app_post.getCall(0).args[1];
handler(req, res);
});
}
function test_get_handler() {
it("should send 403 if no identity_token is provided", function(done) {
const endpoint = "/protected";
IdentityValidator.IdentityValidator.setup(app, endpoint, identityValidable, userDataStore as any, winston);
res.send = sinon.spy(function() {
assert.equal(res.status.getCall(0).args[0], 403);
done();
});
const handler = app_get.getCall(0).args[1];
handler(req, res);
});
it("should render template if identity_token is provided and still valid", function(done) {
req.query.identity_token = "token";
const endpoint = "/protected";
identityValidable.templateName.returns("template");
IdentityValidator.IdentityValidator.setup(app, endpoint, identityValidable, userDataStore as any, winston);
res.render = sinon.spy(function(template: string) {
assert.equal(template, "template");
done();
});
const handler = app_get.getCall(0).args[1];
handler(req, res);
});
it("should return 403 if identity_token is provided but invalid", function(done) {
req.query.identity_token = "token";
const endpoint = "/protected";
identityValidable.templateName.returns("template");
userDataStore.consume_identity_check_token
.returns(Promise.reject("Invalid token"));
IdentityValidator.IdentityValidator.setup(app, endpoint, identityValidable, userDataStore as any, winston);
res.send = sinon.spy(function(template: string) {
assert.equal(res.status.getCall(0).args[0], 403);
done();
});
const handler = app_get.getCall(0).args[1];
handler(req, res);
});
it("should set the identity_check session object even if session does not exist yet", function(done) {
req.query.identity_token = "token";
const endpoint = "/protected";
req.session = {};
identityValidable.templateName.returns("template");
IdentityValidator.IdentityValidator.setup(app, endpoint, identityValidable, userDataStore as any, winston);
res.render = sinon.spy(function(template: string) {
assert.equal(req.session.auth_session.identity_check.userid, "user");
assert.equal(template, "template");
done();
});
const handler = app_get.getCall(0).args[1];
handler(req, res);
});
}
});

View File

@ -0,0 +1,35 @@
import sinon = require("sinon");
import { IdentityValidable } from "../../../src/lib/IdentityValidator";
import express = require("express");
import BluebirdPromise = require("bluebird");
import { Identity } from "../../../src/types/Identity";
export interface IdentityValidableMock {
challenge: sinon.SinonStub;
templateName: sinon.SinonStub;
preValidation: sinon.SinonStub;
mailSubject: sinon.SinonStub;
}
export function IdentityValidableMock() {
return {
challenge: sinon.stub(),
templateName: sinon.stub(),
preValidation: sinon.stub(),
mailSubject: sinon.stub()
};
}
export interface IdentityValidatorMock {
consume_token: sinon.SinonStub;
issue_token: sinon.SinonStub;
}
export function IdentityValidatorMock() {
return {
consume_token: sinon.stub(),
issue_token: sinon.stub()
};
}

View File

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

View File

@ -7,6 +7,7 @@ export interface UserDataStore {
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; get_totp_secret: sinon.SinonStub;
set_totp_secret: sinon.SinonStub;
} }
export function UserDataStore(): UserDataStore { export function UserDataStore(): UserDataStore {
@ -15,6 +16,7 @@ export function UserDataStore(): UserDataStore {
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() get_totp_secret: sinon.stub(),
set_totp_secret: sinon.stub()
}; };
} }

View File

@ -0,0 +1,19 @@
import sinon = require("sinon");
import authdog = require("authdog");
export interface AuthdogMock {
startRegistration: sinon.SinonStub;
finishRegistration: sinon.SinonStub;
startAuthentication: sinon.SinonStub;
finishAuthentication: sinon.SinonStub;
}
export function AuthdogMock(): AuthdogMock {
return {
startRegistration: sinon.stub(),
finishAuthentication: sinon.stub(),
startAuthentication: sinon.stub(),
finishRegistration: sinon.stub()
};
}

View File

@ -8,6 +8,7 @@ export interface RequestMock {
session?: any; session?: any;
headers?: any; headers?: any;
get?: any; get?: any;
query?: any;
} }
export interface ResponseMock { export interface ResponseMock {
@ -16,7 +17,7 @@ export interface ResponseMock {
sendFile: sinon.SinonStub; sendFile: sinon.SinonStub;
sendfile: sinon.SinonStub; sendfile: sinon.SinonStub;
status: sinon.SinonStub | sinon.SinonSpy; status: sinon.SinonStub | sinon.SinonSpy;
json: sinon.SinonStub; json: sinon.SinonStub | sinon.SinonSpy;
links: sinon.SinonStub; links: sinon.SinonStub;
jsonp: sinon.SinonStub; jsonp: sinon.SinonStub;
download: sinon.SinonStub; download: sinon.SinonStub;
@ -32,7 +33,7 @@ export interface ResponseMock {
cookie: sinon.SinonStub; cookie: sinon.SinonStub;
location: sinon.SinonStub; location: sinon.SinonStub;
redirect: sinon.SinonStub; redirect: sinon.SinonStub;
render: sinon.SinonStub; render: sinon.SinonStub | sinon.SinonSpy;
locals: sinon.SinonStub; locals: sinon.SinonStub;
charset: string; charset: string;
vary: sinon.SinonStub; vary: sinon.SinonStub;

View File

@ -1,6 +1,22 @@
import sinon = require("sinon"); import sinon = require("sinon");
export = { export interface NodemailerMock {
createTransport: sinon.SinonStub;
}
export function NodemailerMock(): NodemailerMock {
return {
createTransport: sinon.stub() createTransport: sinon.stub()
}; };
}
export interface NodemailerTransporterMock {
sendMail: sinon.SinonStub;
}
export function NodemailerTransporterMock() {
return {
sendMail: sinon.stub()
};
}

View File

@ -1,7 +1,7 @@
import * as sinon from "sinon"; import * as sinon from "sinon";
import * as assert from "assert"; import * as assert from "assert";
import nodemailerMock = require("../mocks/nodemailer"); import NodemailerMock = require("../mocks/nodemailer");
import GMailNotifier = require("../../../src/lib/notifiers/GMailNotifier"); import GMailNotifier = require("../../../src/lib/notifiers/GMailNotifier");
@ -10,6 +10,7 @@ describe("test gmail notifier", function () {
const transporter = { const transporter = {
sendMail: sinon.stub().yields() sendMail: sinon.stub().yields()
}; };
const nodemailerMock = NodemailerMock.NodemailerMock();
nodemailerMock.createTransport.returns(transporter); nodemailerMock.createTransport.returns(transporter);
const options = { const options = {

View File

@ -3,16 +3,15 @@ import * as sinon from "sinon";
import * as BluebirdPromise from "bluebird"; import * as BluebirdPromise from "bluebird";
import * as assert from "assert"; import * as assert from "assert";
import NodemailerMock = require("../mocks/nodemailer");
import { NotifierFactory } from "../../../src/lib/notifiers/NotifierFactory"; import { NotifierFactory } from "../../../src/lib/notifiers/NotifierFactory";
import { GMailNotifier } from "../../../src/lib/notifiers/GMailNotifier"; import { GMailNotifier } from "../../../src/lib/notifiers/GMailNotifier";
import { FileSystemNotifier } from "../../../src/lib/notifiers/FileSystemNotifier"; import { FileSystemNotifier } from "../../../src/lib/notifiers/FileSystemNotifier";
import nodemailerMock = require("../mocks/nodemailer"); import NodemailerMock = require("../mocks/nodemailer");
describe("test notifier factory", function() { describe("test notifier factory", function() {
let nodemailerMock: NodemailerMock.NodemailerMock;
it("should build a Gmail Notifier", function() { it("should build a Gmail Notifier", function() {
const options = { const options = {
gmail: { gmail: {
@ -20,6 +19,7 @@ describe("test notifier factory", function() {
password: "password" password: "password"
} }
}; };
nodemailerMock = NodemailerMock.NodemailerMock();
nodemailerMock.createTransport.returns(sinon.spy()); nodemailerMock.createTransport.returns(sinon.spy());
assert(NotifierFactory.build(options, nodemailerMock) instanceof GMailNotifier); assert(NotifierFactory.build(options, nodemailerMock) instanceof GMailNotifier);
}); });

View File

@ -1,174 +0,0 @@
var Promise = require('bluebird');
var request = Promise.promisifyAll(require('request'));
var assert = require('assert');
module.exports = function(port) {
var PORT = port;
var BASE_URL = 'http://localhost:' + PORT;
function execute_reset_password(jar, transporter, user, new_password) {
return request.postAsync({
url: BASE_URL + '/reset-password',
jar: jar,
form: { userid: user }
})
.then(function(res) {
assert.equal(res.statusCode, 204);
var html_content = transporter.sendMail.getCall(0).args[0].html;
var regexp = /identity_token=([a-zA-Z0-9]+)/;
var token = regexp.exec(html_content)[1];
// console.log(html_content, token);
return request.getAsync({
url: BASE_URL + '/reset-password?identity_token=' + token,
jar: jar
})
})
.then(function(res) {
assert.equal(res.statusCode, 200);
return request.postAsync({
url: BASE_URL + '/new-password',
jar: jar,
form: {
password: new_password
}
});
});
}
function execute_register_totp(jar, transporter) {
return request.postAsync({
url: BASE_URL + '/totp-register',
jar: jar
})
.then(function(res) {
assert.equal(res.statusCode, 204);
var html_content = transporter.sendMail.getCall(0).args[0].html;
var regexp = /identity_token=([a-zA-Z0-9]+)/;
var token = regexp.exec(html_content)[1];
// console.log(html_content, token);
return request.getAsync({
url: BASE_URL + '/totp-register?identity_token=' + token,
jar: jar
})
})
.then(function(res) {
assert.equal(res.statusCode, 200);
return request.postAsync({
url : BASE_URL + '/new-totp-secret',
jar: jar,
})
})
.then(function(res) {
console.log(res.statusCode);
console.log(res.body);
assert.equal(res.statusCode, 200);
return Promise.resolve(res.body);
});
}
function execute_totp(jar, token) {
return request.postAsync({
url: BASE_URL + '/2ndfactor/totp',
jar: jar,
form: {
token: token
}
});
}
function execute_u2f_authentication(jar) {
return request.getAsync({
url: BASE_URL + '/2ndfactor/u2f/sign_request',
jar: jar
})
.then(function(res) {
assert.equal(res.statusCode, 200);
return request.postAsync({
url: BASE_URL + '/2ndfactor/u2f/sign',
jar: jar,
form: {
}
});
});
}
function execute_verification(jar) {
return request.getAsync({ url: BASE_URL + '/verify', jar: jar })
}
function execute_login(jar) {
return request.getAsync({ url: BASE_URL + '/login', jar: jar })
}
function execute_u2f_registration(jar, transporter) {
return request.postAsync({
url: BASE_URL + '/u2f-register',
jar: jar
})
.then(function(res) {
assert.equal(res.statusCode, 204);
var html_content = transporter.sendMail.getCall(0).args[0].html;
var regexp = /identity_token=([a-zA-Z0-9]+)/;
var token = regexp.exec(html_content)[1];
// console.log(html_content, token);
return request.getAsync({
url: BASE_URL + '/u2f-register?identity_token=' + token,
jar: jar
})
})
.then(function(res) {
assert.equal(res.statusCode, 200);
return request.getAsync({
url: BASE_URL + '/2ndfactor/u2f/register_request',
jar: jar,
});
})
.then(function(res) {
assert.equal(res.statusCode, 200);
return request.postAsync({
url: BASE_URL + '/2ndfactor/u2f/register',
jar: jar,
form: {
s: 'test'
}
});
});
}
function execute_first_factor(jar) {
return request.postAsync({
url: BASE_URL + '/1stfactor',
jar: jar,
form: {
username: 'test_ok',
password: 'password'
}
});
}
function execute_failing_first_factor(jar) {
return request.postAsync({
url: BASE_URL + '/1stfactor',
jar: jar,
form: {
username: 'test_nok',
password: 'password'
}
});
}
return {
login: execute_login,
verify: execute_verification,
reset_password: execute_reset_password,
u2f_authentication: execute_u2f_authentication,
u2f_registration: execute_u2f_registration,
first_factor: execute_first_factor,
failing_first_factor: execute_failing_first_factor,
totp: execute_totp,
register_totp: execute_register_totp,
}
}

View File

@ -0,0 +1,179 @@
import BluebirdPromise = require("bluebird");
import request = require("request");
import assert = require("assert");
import express = require("express");
import nodemailer = require("nodemailer");
import NodemailerMock = require("./mocks/nodemailer");
const requestAsync = BluebirdPromise.promisifyAll(request) as request.RequestAsync;
export = function (port: number) {
const PORT = port;
const BASE_URL = "http://localhost:" + PORT;
function execute_reset_password(jar: request.CookieJar, transporter: NodemailerMock.NodemailerTransporterMock, user: string, new_password: string) {
return requestAsync.postAsync({
url: BASE_URL + "/reset-password",
jar: jar,
form: { userid: user }
})
.then(function (res: request.RequestResponse) {
assert.equal(res.statusCode, 204);
const html_content = transporter.sendMail.getCall(0).args[0].html;
const regexp = /identity_token=([a-zA-Z0-9]+)/;
const token = regexp.exec(html_content)[1];
// console.log(html_content, token);
return requestAsync.getAsync({
url: BASE_URL + "/reset-password?identity_token=" + token,
jar: jar
});
})
.then(function (res: request.RequestResponse) {
assert.equal(res.statusCode, 200);
return requestAsync.postAsync({
url: BASE_URL + "/new-password",
jar: jar,
form: {
password: new_password
}
});
});
}
function execute_register_totp(jar: request.CookieJar, transporter: NodemailerMock.NodemailerTransporterMock) {
return requestAsync.postAsync({
url: BASE_URL + "/totp-register",
jar: jar
})
.then(function (res: request.RequestResponse) {
assert.equal(res.statusCode, 204);
const html_content = transporter.sendMail.getCall(0).args[0].html;
const regexp = /identity_token=([a-zA-Z0-9]+)/;
const token = regexp.exec(html_content)[1];
// console.log(html_content, token);
return requestAsync.getAsync({
url: BASE_URL + "/totp-register?identity_token=" + token,
jar: jar
});
})
.then(function (res: request.RequestResponse) {
assert.equal(res.statusCode, 200);
return requestAsync.postAsync({
url: BASE_URL + "/new-totp-secret",
jar: jar,
});
})
.then(function (res: request.RequestResponse) {
console.log(res.statusCode);
console.log(res.body);
assert.equal(res.statusCode, 200);
return Promise.resolve(res.body);
});
}
function execute_totp(jar: request.CookieJar, token: string) {
return requestAsync.postAsync({
url: BASE_URL + "/2ndfactor/totp",
jar: jar,
form: {
token: token
}
});
}
function execute_u2f_authentication(jar: request.CookieJar) {
return requestAsync.getAsync({
url: BASE_URL + "/2ndfactor/u2f/sign_request",
jar: jar
})
.then(function (res: request.RequestResponse) {
assert.equal(res.statusCode, 200);
return requestAsync.postAsync({
url: BASE_URL + "/2ndfactor/u2f/sign",
jar: jar,
form: {
}
});
});
}
function execute_verification(jar: request.CookieJar) {
return requestAsync.getAsync({ url: BASE_URL + "/verify", jar: jar });
}
function execute_login(jar: request.CookieJar) {
return requestAsync.getAsync({ url: BASE_URL + "/login", jar: jar });
}
function execute_u2f_registration(jar: request.CookieJar, transporter: NodemailerMock.NodemailerTransporterMock) {
return requestAsync.postAsync({
url: BASE_URL + "/u2f-register",
jar: jar
})
.then(function (res: request.RequestResponse) {
assert.equal(res.statusCode, 204);
const html_content = transporter.sendMail.getCall(0).args[0].html;
const regexp = /identity_token=([a-zA-Z0-9]+)/;
const token = regexp.exec(html_content)[1];
// console.log(html_content, token);
return requestAsync.getAsync({
url: BASE_URL + "/u2f-register?identity_token=" + token,
jar: jar
});
})
.then(function (res: request.RequestResponse) {
assert.equal(res.statusCode, 200);
return requestAsync.getAsync({
url: BASE_URL + "/2ndfactor/u2f/register_request",
jar: jar,
});
})
.then(function (res: request.RequestResponse) {
assert.equal(res.statusCode, 200);
return requestAsync.postAsync({
url: BASE_URL + "/2ndfactor/u2f/register",
jar: jar,
form: {
s: "test"
}
});
});
}
function execute_first_factor(jar: request.CookieJar) {
return requestAsync.postAsync({
url: BASE_URL + "/1stfactor",
jar: jar,
form: {
username: "test_ok",
password: "password"
}
});
}
function execute_failing_first_factor(jar: request.CookieJar) {
return requestAsync.postAsync({
url: BASE_URL + "/1stfactor",
jar: jar,
form: {
username: "test_nok",
password: "password"
}
});
}
return {
login: execute_login,
verify: execute_verification,
reset_password: execute_reset_password,
u2f_authentication: execute_u2f_authentication,
u2f_registration: execute_u2f_registration,
first_factor: execute_first_factor,
failing_first_factor: execute_failing_first_factor,
totp: execute_totp,
register_totp: execute_register_totp,
};
};

View File

@ -1,24 +0,0 @@
module.exports = create_res_mock;
var sinon = require('sinon');
var sinonPromise = require('sinon-promise');
sinonPromise(sinon);
function create_res_mock() {
var status_mock = sinon.mock();
var send_mock = sinon.mock();
var set_mock = sinon.mock();
var cookie_mock = sinon.mock();
var render_mock = sinon.mock();
var redirect_mock = sinon.mock();
return {
status: status_mock,
send: send_mock,
set: set_mock,
cookie: cookie_mock,
render: render_mock,
redirect: redirect_mock
};
}

View File

@ -1,5 +1,5 @@
import reset_password = require("../../../src/lib/routes/reset_password"); import PasswordReset = require("../../../src/lib/routes/PasswordReset");
import LdapClient = require("../../../src/lib/LdapClient"); import LdapClient = require("../../../src/lib/LdapClient");
import sinon = require("sinon"); import sinon = require("sinon");
import winston = require("winston"); import winston = require("winston");
@ -72,7 +72,7 @@ describe("test reset password", function () {
function test_reset_password_check() { function test_reset_password_check() {
it("should fail when no userid is provided", function (done) { it("should fail when no userid is provided", function (done) {
req.body.userid = undefined; req.body.userid = undefined;
reset_password.icheck_interface.pre_check_callback(req) PasswordReset.icheck_interface.preValidation(req as any)
.catch(function (err: Error) { .catch(function (err: Error) {
done(); done();
}); });
@ -80,7 +80,7 @@ describe("test reset password", function () {
it("should fail if ldap fail", function (done) { it("should fail if ldap fail", function (done) {
ldap_client.get_emails.returns(BluebirdPromise.reject("Internal error")); ldap_client.get_emails.returns(BluebirdPromise.reject("Internal error"));
reset_password.icheck_interface.pre_check_callback(req) PasswordReset.icheck_interface.preValidation(req as any)
.catch(function (err: Error) { .catch(function (err: Error) {
done(); done();
}); });
@ -89,7 +89,7 @@ describe("test reset password", function () {
it("should perform a search in ldap to find email address", function (done) { it("should perform a search in ldap to find email address", function (done) {
configuration.ldap.user_name_attribute = "uid"; configuration.ldap.user_name_attribute = "uid";
ldap_client.get_emails.returns(BluebirdPromise.resolve([])); ldap_client.get_emails.returns(BluebirdPromise.resolve([]));
reset_password.icheck_interface.pre_check_callback(req) PasswordReset.icheck_interface.preValidation(req as any)
.then(function () { .then(function () {
assert.equal("user", ldap_client.get_emails.getCall(0).args[0]); assert.equal("user", ldap_client.get_emails.getCall(0).args[0]);
done(); done();
@ -98,7 +98,7 @@ describe("test reset password", function () {
it("should returns identity when ldap replies", function (done) { it("should returns identity when ldap replies", function (done) {
ldap_client.get_emails.returns(BluebirdPromise.resolve(["test@example.com"])); ldap_client.get_emails.returns(BluebirdPromise.resolve(["test@example.com"]));
reset_password.icheck_interface.pre_check_callback(req) PasswordReset.icheck_interface.preValidation(req as any)
.then(function () { .then(function () {
done(); done();
}); });
@ -120,7 +120,7 @@ describe("test reset password", function () {
assert.equal(req.session.auth_session, undefined); assert.equal(req.session.auth_session, undefined);
done(); done();
}); });
reset_password.post(req, res); PasswordReset.post(req as any, res as any);
}); });
it("should fail if identity_challenge does not exist", function (done) { it("should fail if identity_challenge does not exist", function (done) {
@ -130,7 +130,7 @@ describe("test reset password", function () {
assert.equal(res.status.getCall(0).args[0], 403); assert.equal(res.status.getCall(0).args[0], 403);
done(); done();
}); });
reset_password.post(req, res); PasswordReset.post(req as any, res as any);
}); });
it("should fail when ldap fails", function (done) { it("should fail when ldap fails", function (done) {
@ -145,7 +145,7 @@ describe("test reset password", function () {
assert.equal(res.status.getCall(0).args[0], 500); assert.equal(res.status.getCall(0).args[0], 500);
done(); done();
}); });
reset_password.post(req, res); PasswordReset.post(req as any, res as any);
}); });
} }
}); });

View File

@ -0,0 +1,137 @@
import sinon = require("sinon");
import winston = require("winston");
import TOTPRegistration = require("../../../src/lib/routes/TOTPRegistration");
import assert = require("assert");
import BluebirdPromise = require("bluebird");
import ExpressMock = require("../mocks/express");
import UserDataStoreMock = require("../mocks/UserDataStore");
describe("test totp register", function () {
let req: ExpressMock.RequestMock;
let res: ExpressMock.ResponseMock;
let userDataStore: UserDataStoreMock.UserDataStore;
beforeEach(function () {
req = ExpressMock.RequestMock();
req.app.get = sinon.stub();
req.app.get.withArgs("logger").returns(winston);
req.session = {};
req.session.auth_session = {};
req.session.auth_session.userid = "user";
req.session.auth_session.email = "user@example.com";
req.session.auth_session.first_factor = true;
req.session.auth_session.second_factor = false;
req.headers = {};
req.headers.host = "localhost";
const options = {
inMemoryOnly: true
};
userDataStore = UserDataStoreMock.UserDataStore();
userDataStore.set_u2f_meta = sinon.stub().returns(Promise.resolve({}));
userDataStore.get_u2f_meta = sinon.stub().returns(Promise.resolve({}));
userDataStore.issue_identity_check_token = sinon.stub().returns(Promise.resolve({}));
userDataStore.consume_identity_check_token = sinon.stub().returns(Promise.resolve({}));
userDataStore.set_totp_secret = sinon.stub().returns(Promise.resolve({}));
req.app.get.withArgs("user data store").returns(userDataStore);
res = ExpressMock.ResponseMock();
});
describe("test totp registration check", test_registration_check);
describe("test totp post secret", test_post_secret);
function test_registration_check() {
it("should fail if first_factor has not been passed", function (done) {
req.session.auth_session.first_factor = false;
TOTPRegistration.icheck_interface.preValidation(req as any)
.catch(function (err) {
done();
});
});
it("should fail if userid is missing", function (done) {
req.session.auth_session.first_factor = false;
req.session.auth_session.userid = undefined;
TOTPRegistration.icheck_interface.preValidation(req as any)
.catch(function (err) {
done();
});
});
it("should fail if email is missing", function (done) {
req.session.auth_session.first_factor = false;
req.session.auth_session.email = undefined;
TOTPRegistration.icheck_interface.preValidation(req as any)
.catch(function (err) {
done();
});
});
it("should succeed if first factor passed, userid and email are provided", function (done) {
TOTPRegistration.icheck_interface.preValidation(req as any)
.then(function (err) {
done();
});
});
}
function test_post_secret() {
it("should send the secret in json format", function (done) {
req.app.get.withArgs("totp generator").returns({
generate: sinon.stub().returns({ otpauth_url: "abc" })
});
req.session.auth_session.identity_check = {};
req.session.auth_session.identity_check.userid = "user";
req.session.auth_session.identity_check.challenge = "totp-register";
res.json = sinon.spy(function () {
done();
});
TOTPRegistration.post(req as any, res as any);
});
it("should clear the session for reauthentication", function (done) {
req.app.get.withArgs("totp generator").returns({
generate: sinon.stub().returns({ otpauth_url: "abc" })
});
req.session.auth_session.identity_check = {};
req.session.auth_session.identity_check.userid = "user";
req.session.auth_session.identity_check.challenge = "totp-register";
res.json = sinon.spy(function () {
assert.equal(req.session, undefined);
done();
});
TOTPRegistration.post(req as any, res as any);
});
it("should return 403 if the identity check challenge is not set", function (done) {
req.session.auth_session.identity_check = {};
req.session.auth_session.identity_check.challenge = undefined;
res.send = sinon.spy(function () {
assert.equal(res.status.getCall(0).args[0], 403);
done();
});
TOTPRegistration.post(req as any, res as any);
});
it("should return 500 if db throws", function (done) {
req.app.get.withArgs("totp generator").returns({
generate: sinon.stub().returns({ otpauth_url: "abc" })
});
req.session.auth_session.identity_check = {};
req.session.auth_session.identity_check.userid = "user";
req.session.auth_session.identity_check.challenge = "totp-register";
userDataStore.set_totp_secret.returns(BluebirdPromise.reject("internal error"));
res.send = sinon.spy(function () {
assert.equal(res.status.getCall(0).args[0], 500);
done();
});
TOTPRegistration.post(req as any, res as any);
});
}
});

View File

@ -0,0 +1,83 @@
import sinon = require("sinon");
import winston = require("winston");
import u2f_register = require("../../../src/lib/routes/U2FRegistration");
import assert = require("assert");
import ExpressMock = require("../mocks/express");
import UserDataStoreMock = require("../mocks/UserDataStore");
describe("test register handler", function() {
let req: ExpressMock.RequestMock;
let res: ExpressMock.ResponseMock;
let user_data_store: UserDataStoreMock.UserDataStore;
beforeEach(function() {
req = ExpressMock.RequestMock;
req.app = {};
req.app.get = sinon.stub();
req.app.get.withArgs("logger").returns(winston);
req.session = {};
req.session.auth_session = {};
req.session.auth_session.userid = "user";
req.session.auth_session.email = "user@example.com";
req.session.auth_session.first_factor = true;
req.session.auth_session.second_factor = false;
req.headers = {};
req.headers.host = "localhost";
const options = {
inMemoryOnly: true
};
user_data_store = UserDataStoreMock.UserDataStore();
user_data_store.set_u2f_meta = sinon.stub().returns(Promise.resolve({}));
user_data_store.get_u2f_meta = sinon.stub().returns(Promise.resolve({}));
user_data_store.issue_identity_check_token = sinon.stub().returns(Promise.resolve({}));
user_data_store.consume_identity_check_token = sinon.stub().returns(Promise.resolve({}));
req.app.get.withArgs("user data store").returns(user_data_store);
res = ExpressMock.ResponseMock();
res.send = sinon.spy();
res.json = sinon.spy();
res.status = sinon.spy();
});
describe("test u2f registration check", test_registration_check);
function test_registration_check() {
it("should fail if first_factor has not been passed", function(done) {
req.session.auth_session.first_factor = false;
u2f_register.icheck_interface.preValidation(req as any)
.catch(function(err: Error) {
done();
});
});
it("should fail if userid is missing", function(done) {
req.session.auth_session.first_factor = false;
req.session.auth_session.userid = undefined;
u2f_register.icheck_interface.preValidation(req as any)
.catch(function(err: Error) {
done();
});
});
it("should fail if email is missing", function(done) {
req.session.auth_session.first_factor = false;
req.session.auth_session.email = undefined;
u2f_register.icheck_interface.preValidation(req as any)
.catch(function(err) {
done();
});
});
it("should succeed if first factor passed, userid and email are provided", function(done) {
u2f_register.icheck_interface.preValidation(req as any)
.then(function(err) {
done();
});
});
}
});

View File

@ -0,0 +1,278 @@
import sinon = require("sinon");
import Promise = require("bluebird");
import assert = require("assert");
import u2f = require("../../../src/lib/routes/U2FRoutes");
import winston = require("winston");
import ExpressMock = require("../mocks/express");
import UserDataStoreMock = require("../mocks/UserDataStore");
import AuthdogMock = require("../mocks/authdog");
describe("test u2f routes", function () {
let req: ExpressMock.RequestMock;
let res: ExpressMock.ResponseMock;
let user_data_store: UserDataStoreMock.UserDataStore;
beforeEach(function () {
req = ExpressMock.RequestMock();
req.app = {};
req.app.get = sinon.stub();
req.app.get.withArgs("logger").returns(winston);
req.session = {};
req.session.auth_session = {};
req.session.auth_session.userid = "user";
req.session.auth_session.first_factor = true;
req.session.auth_session.second_factor = false;
req.session.auth_session.identity_check = {};
req.session.auth_session.identity_check.challenge = "u2f-register";
req.session.auth_session.register_request = {};
req.headers = {};
req.headers.host = "localhost";
const options = {
inMemoryOnly: true
};
user_data_store = UserDataStoreMock.UserDataStore();
user_data_store.set_u2f_meta = sinon.stub().returns(Promise.resolve({}));
user_data_store.get_u2f_meta = sinon.stub().returns(Promise.resolve({}));
req.app.get.withArgs("user data store").returns(user_data_store);
res = ExpressMock.ResponseMock();
res.send = sinon.spy();
res.json = sinon.spy();
res.status = sinon.spy();
});
describe("test registration request", test_registration_request);
describe("test registration", test_registration);
describe("test signing request", test_signing_request);
describe("test signing", test_signing);
function test_registration_request() {
it("should send back the registration request and save it in the session", function (done) {
const expectedRequest = {
test: "abc"
};
res.json = sinon.spy(function (data: any) {
assert.equal(200, res.status.getCall(0).args[0]);
assert.deepEqual(expectedRequest, data);
done();
});
const user_key_container = {};
const u2f_mock = AuthdogMock.AuthdogMock();
u2f_mock.startRegistration.returns(Promise.resolve(expectedRequest));
req.app.get.withArgs("u2f").returns(u2f_mock);
u2f.register_request(req as any, res as any, undefined);
});
it("should return internal error on registration request", function (done) {
res.send = sinon.spy(function (data: any) {
assert.equal(500, res.status.getCall(0).args[0]);
done();
});
const user_key_container = {};
const u2f_mock = AuthdogMock.AuthdogMock();
u2f_mock.startRegistration.returns(Promise.reject("Internal error"));
req.app.get.withArgs("u2f").returns(u2f_mock);
u2f.register_request(req as any, res as any, undefined);
});
it("should return forbidden if identity has not been verified", function (done) {
res.send = sinon.spy(function (data: any) {
assert.equal(403, res.status.getCall(0).args[0]);
done();
});
req.session.auth_session.identity_check = undefined;
u2f.register_request(req as any, res as any, undefined);
});
}
function test_registration() {
it("should save u2f meta and return status code 200", function (done) {
const expectedStatus = {
keyHandle: "keyHandle",
publicKey: "pbk",
certificate: "cert"
};
res.send = sinon.spy(function (data: any) {
assert.equal("user", user_data_store.set_u2f_meta.getCall(0).args[0]);
assert.equal(req.session.auth_session.identity_check, undefined);
done();
});
const u2f_mock = AuthdogMock.AuthdogMock();
u2f_mock.finishRegistration.returns(Promise.resolve(expectedStatus));
req.session.auth_session.register_request = {};
req.app.get.withArgs("u2f").returns(u2f_mock);
u2f.register(req as any, res as any, undefined);
});
it("should return unauthorized on finishRegistration error", function (done) {
res.send = sinon.spy(function (data: any) {
assert.equal(500, res.status.getCall(0).args[0]);
done();
});
const user_key_container = {};
const u2f_mock = AuthdogMock.AuthdogMock();
u2f_mock.finishRegistration.returns(Promise.reject("Internal error"));
req.session.auth_session.register_request = "abc";
req.app.get.withArgs("u2f").returns(u2f_mock);
u2f.register(req as any, res as any, undefined);
});
it("should return 403 when register_request is not provided", function (done) {
res.send = sinon.spy(function (data: any) {
assert.equal(403, res.status.getCall(0).args[0]);
done();
});
const user_key_container = {};
const u2f_mock = AuthdogMock.AuthdogMock();
u2f_mock.finishRegistration.returns(Promise.resolve());
req.session.auth_session.register_request = undefined;
req.app.get.withArgs("u2f").returns(u2f_mock);
u2f.register(req as any, res as any, undefined);
});
it("should return forbidden error when no auth request has been initiated", function (done) {
res.send = sinon.spy(function (data: any) {
assert.equal(403, res.status.getCall(0).args[0]);
done();
});
const user_key_container = {};
const u2f_mock = AuthdogMock.AuthdogMock();
u2f_mock.finishRegistration.returns(Promise.resolve());
req.session.auth_session.register_request = undefined;
req.app.get.withArgs("u2f").returns(u2f_mock);
u2f.register(req as any, res as any, undefined);
});
it("should return forbidden error when identity has not been verified", function (done) {
res.send = sinon.spy(function (data: any) {
assert.equal(403, res.status.getCall(0).args[0]);
done();
});
req.session.auth_session.identity_check = undefined;
u2f.register(req as any, res as any, undefined);
});
}
function test_signing_request() {
it("should send back the sign request and save it in the session", function (done) {
const expectedRequest = {
test: "abc"
};
res.json = sinon.spy(function (data: any) {
assert.deepEqual(expectedRequest, req.session.auth_session.sign_request);
assert.equal(200, res.status.getCall(0).args[0]);
assert.deepEqual(expectedRequest, data);
done();
});
const user_key_container = {
user: {}
};
const u2f_mock = AuthdogMock.AuthdogMock();
u2f_mock.startAuthentication.returns(Promise.resolve(expectedRequest));
req.app.get.withArgs("u2f").returns(u2f_mock);
u2f.sign_request(req as any, res as any, undefined);
});
it("should return unauthorized error on registration request error", function (done) {
res.send = sinon.spy(function (data: any) {
assert.equal(500, res.status.getCall(0).args[0]);
done();
});
const user_key_container = {
user: {}
};
const u2f_mock = AuthdogMock.AuthdogMock();
u2f_mock.startAuthentication.returns(Promise.reject("Internal error"));
req.app.get.withArgs("u2f").returns(u2f_mock);
u2f.sign_request(req as any, res as any, undefined);
});
it("should send unauthorized error when no registration exists", function (done) {
const expectedRequest = {
test: "abc"
};
res.send = sinon.spy(function (data: any) {
assert.equal(401, res.status.getCall(0).args[0]);
done();
});
const user_key_container = {}; // no entry means no registration
const u2f_mock = AuthdogMock.AuthdogMock();
u2f_mock.startAuthentication.returns(Promise.resolve(expectedRequest));
user_data_store.get_u2f_meta = sinon.stub().returns(Promise.resolve());
req.app.get = sinon.stub();
req.app.get.withArgs("logger").returns(winston);
req.app.get.withArgs("user data store").returns(user_data_store);
req.app.get.withArgs("u2f").returns(u2f_mock);
u2f.sign_request(req as any, res as any, undefined);
});
}
function test_signing() {
it("should return status code 204", function (done) {
const user_key_container = {
user: {}
};
const expectedStatus = {
keyHandle: "keyHandle",
publicKey: "pbk",
certificate: "cert"
};
res.send = sinon.spy(function (data: any) {
assert(204, res.status.getCall(0).args[0]);
assert(req.session.auth_session.second_factor);
done();
});
const u2f_mock = AuthdogMock.AuthdogMock();
u2f_mock.finishAuthentication.returns(Promise.resolve(expectedStatus));
req.session.auth_session.sign_request = {};
req.app.get.withArgs("u2f").returns(u2f_mock);
u2f.sign(req as any, res as any, undefined);
});
it("should return unauthorized error on registration request internal error", function (done) {
res.send = sinon.spy(function (data: any) {
assert.equal(500, res.status.getCall(0).args[0]);
done();
});
const user_key_container = {
user: {}
};
const u2f_mock = AuthdogMock.AuthdogMock();
u2f_mock.finishAuthentication.returns(Promise.reject("Internal error"));
req.session.auth_session.sign_request = {};
req.app.get.withArgs("u2f").returns(u2f_mock);
u2f.sign(req as any, res as any, undefined);
});
it("should return unauthorized error when no sign request has been initiated", function (done) {
res.send = sinon.spy(function (data: any) {
assert.equal(401, res.status.getCall(0).args[0]);
done();
});
const user_key_container = {};
const u2f_mock = AuthdogMock.AuthdogMock();
u2f_mock.finishAuthentication.returns(Promise.resolve());
req.app.get.withArgs("u2f").returns(u2f_mock);
u2f.sign(req as any, res as any, undefined);
});
}
});

View File

@ -1,136 +0,0 @@
var sinon = require('sinon');
var winston = require('winston');
var totp_register = require('../../../src/lib/routes/totp_register');
var assert = require('assert');
var Promise = require('bluebird');
describe('test totp register', function() {
var req, res;
var user_data_store;
beforeEach(function() {
req = {}
req.app = {};
req.app.get = sinon.stub();
req.app.get.withArgs('logger').returns(winston);
req.session = {};
req.session.auth_session = {};
req.session.auth_session.userid = 'user';
req.session.auth_session.email = 'user@example.com';
req.session.auth_session.first_factor = true;
req.session.auth_session.second_factor = false;
req.headers = {};
req.headers.host = 'localhost';
var options = {};
options.inMemoryOnly = true;
user_data_store = {};
user_data_store.set_u2f_meta = sinon.stub().returns(Promise.resolve({}));
user_data_store.get_u2f_meta = sinon.stub().returns(Promise.resolve({}));
user_data_store.issue_identity_check_token = sinon.stub().returns(Promise.resolve({}));
user_data_store.consume_identity_check_token = sinon.stub().returns(Promise.resolve({}));
user_data_store.set_totp_secret = sinon.stub().returns(Promise.resolve({}));
req.app.get.withArgs('user data store').returns(user_data_store);
res = {};
res.send = sinon.spy();
res.json = sinon.spy();
res.status = sinon.spy();
});
describe('test totp registration check', test_registration_check);
describe('test totp post secret', test_post_secret);
function test_registration_check() {
it('should fail if first_factor has not been passed', function(done) {
req.session.auth_session.first_factor = false;
totp_register.icheck_interface.pre_check_callback(req)
.catch(function(err) {
done();
});
});
it('should fail if userid is missing', function(done) {
req.session.auth_session.first_factor = false;
req.session.auth_session.userid = undefined;
totp_register.icheck_interface.pre_check_callback(req)
.catch(function(err) {
done();
});
});
it('should fail if email is missing', function(done) {
req.session.auth_session.first_factor = false;
req.session.auth_session.email = undefined;
totp_register.icheck_interface.pre_check_callback(req)
.catch(function(err) {
done();
});
});
it('should succeed if first factor passed, userid and email are provided', function(done) {
totp_register.icheck_interface.pre_check_callback(req)
.then(function(err) {
done();
});
});
}
function test_post_secret() {
it('should send the secret in json format', function(done) {
req.app.get.withArgs('totp generator').returns({
generate: sinon.stub().returns({ otpauth_url: "abc"})
});
req.session.auth_session.identity_check = {};
req.session.auth_session.identity_check.userid = 'user';
req.session.auth_session.identity_check.challenge = 'totp-register';
res.json = sinon.spy(function() {
done();
});
totp_register.post(req, res);
});
it('should clear the session for reauthentication', function(done) {
req.app.get.withArgs('totp generator').returns({
generate: sinon.stub().returns({ otpauth_url: "abc"})
});
req.session.auth_session.identity_check = {};
req.session.auth_session.identity_check.userid = 'user';
req.session.auth_session.identity_check.challenge = 'totp-register';
res.json = sinon.spy(function() {
assert.equal(req.session, undefined);
done();
});
totp_register.post(req, res);
});
it('should return 403 if the identity check challenge is not set', function(done) {
req.session.auth_session.identity_check = {};
req.session.auth_session.identity_check.challenge = undefined;
res.send = sinon.spy(function() {
assert.equal(res.status.getCall(0).args[0], 403);
done();
});
totp_register.post(req, res);
});
it('should return 500 if db throws', function(done) {
req.app.get.withArgs('totp generator').returns({
generate: sinon.stub().returns({ otpauth_url: "abc" })
});
req.session.auth_session.identity_check = {};
req.session.auth_session.identity_check.userid = 'user';
req.session.auth_session.identity_check.challenge = 'totp-register';
user_data_store.set_totp_secret.returns(new Promise.reject('internal error'));
res.send = sinon.spy(function() {
assert.equal(res.status.getCall(0).args[0], 500);
done();
});
totp_register.post(req, res);
});
  }
});

View File

@ -1,280 +0,0 @@
var sinon = require('sinon');
var Promise = require('bluebird');
var assert = require('assert');
var u2f = require('../../../src/lib/routes/u2f');
var winston = require('winston');
describe('test u2f routes', function() {
var req, res;
var user_data_store;
beforeEach(function() {
req = {}
req.app = {};
req.app.get = sinon.stub();
req.app.get.withArgs('logger').returns(winston);
req.session = {};
req.session.auth_session = {};
req.session.auth_session.userid = 'user';
req.session.auth_session.first_factor = true;
req.session.auth_session.second_factor = false;
req.session.auth_session.identity_check = {};
req.session.auth_session.identity_check.challenge = 'u2f-register';
req.session.auth_session.register_request = {};
req.headers = {};
req.headers.host = 'localhost';
var options = {};
options.inMemoryOnly = true;
user_data_store = {};
user_data_store.set_u2f_meta = sinon.stub().returns(Promise.resolve({}));
user_data_store.get_u2f_meta = sinon.stub().returns(Promise.resolve({}));
req.app.get.withArgs('user data store').returns(user_data_store);
res = {};
res.send = sinon.spy();
res.json = sinon.spy();
res.status = sinon.spy();
})
describe('test registration request', test_registration_request);
describe('test registration', test_registration);
describe('test signing request', test_signing_request);
describe('test signing', test_signing);
function test_registration_request() {
it('should send back the registration request and save it in the session', function(done) {
var expectedRequest = {
test: 'abc'
};
res.json = sinon.spy(function(data) {
assert.equal(200, res.status.getCall(0).args[0]);
assert.deepEqual(expectedRequest, data);
done();
});
var user_key_container = {};
var u2f_mock = {};
u2f_mock.startRegistration = sinon.stub();
u2f_mock.startRegistration.returns(Promise.resolve(expectedRequest));
req.app.get.withArgs('u2f').returns(u2f_mock);
u2f.register_request(req, res);
});
it('should return internal error on registration request', function(done) {
res.send = sinon.spy(function(data) {
assert.equal(500, res.status.getCall(0).args[0]);
done();
});
var user_key_container = {};
var u2f_mock = {};
u2f_mock.startRegistration = sinon.stub();
u2f_mock.startRegistration.returns(Promise.reject('Internal error'));
req.app.get.withArgs('u2f').returns(u2f_mock);
u2f.register_request(req, res);
});
it('should return forbidden if identity has not been verified', function(done) {
res.send = sinon.spy(function(data) {
assert.equal(403, res.status.getCall(0).args[0]);
done();
});
req.session.auth_session.identity_check = undefined;
u2f.register_request(req, res);
});
}
function test_registration() {
it('should save u2f meta and return status code 200', function(done) {
var expectedStatus = {
keyHandle: 'keyHandle',
publicKey: 'pbk',
certificate: 'cert'
};
res.send = sinon.spy(function(data) {
assert.equal('user', user_data_store.set_u2f_meta.getCall(0).args[0])
assert.equal(req.session.auth_session.identity_check, undefined);
done();
});
var u2f_mock = {};
u2f_mock.finishRegistration = sinon.stub();
u2f_mock.finishRegistration.returns(Promise.resolve(expectedStatus));
req.session.auth_session.register_request = {};
req.app.get.withArgs('u2f').returns(u2f_mock);
u2f.register(req, res);
});
it('should return unauthorized on finishRegistration error', function(done) {
res.send = sinon.spy(function(data) {
assert.equal(500, res.status.getCall(0).args[0]);
done();
});
var user_key_container = {};
var u2f_mock = {};
u2f_mock.finishRegistration = sinon.stub();
u2f_mock.finishRegistration.returns(Promise.reject('Internal error'));
req.session.auth_session.register_request = 'abc';
req.app.get.withArgs('u2f').returns(u2f_mock);
u2f.register(req, res);
});
it('should return 403 when register_request is not provided', function(done) {
res.send = sinon.spy(function(data) {
assert.equal(403, res.status.getCall(0).args[0]);
done();
});
var user_key_container = {};
var u2f_mock = {};
u2f_mock.finishRegistration = sinon.stub();
u2f_mock.finishRegistration.returns(Promise.resolve());
req.session.auth_session.register_request = undefined;
req.app.get.withArgs('u2f').returns(u2f_mock);
u2f.register(req, res);
});
it('should return forbidden error when no auth request has been initiated', function(done) {
res.send = sinon.spy(function(data) {
assert.equal(403, res.status.getCall(0).args[0]);
done();
});
var user_key_container = {};
var u2f_mock = {};
u2f_mock.finishRegistration = sinon.stub();
u2f_mock.finishRegistration.returns(Promise.resolve());
req.session.auth_session.register_request = undefined;
req.app.get.withArgs('u2f').returns(u2f_mock);
u2f.register(req, res);
});
it('should return forbidden error when identity has not been verified', function(done) {
res.send = sinon.spy(function(data) {
assert.equal(403, res.status.getCall(0).args[0]);
done();
});
req.session.auth_session.identity_check = undefined;
u2f.register(req, res);
});
}
function test_signing_request() {
it('should send back the sign request and save it in the session', function(done) {
var expectedRequest = {
test: 'abc'
};
res.json = sinon.spy(function(data) {
assert.deepEqual(expectedRequest, req.session.auth_session.sign_request);
assert.equal(200, res.status.getCall(0).args[0]);
assert.deepEqual(expectedRequest, data);
done();
});
var user_key_container = {};
user_key_container['user'] = {}; // simulate a registration
var u2f_mock = {};
u2f_mock.startAuthentication = sinon.stub();
u2f_mock.startAuthentication.returns(Promise.resolve(expectedRequest));
req.app.get.withArgs('u2f').returns(u2f_mock);
u2f.sign_request(req, res);
});
it('should return unauthorized error on registration request error', function(done) {
res.send = sinon.spy(function(data) {
assert.equal(500, res.status.getCall(0).args[0]);
done();
});
var user_key_container = {};
user_key_container['user'] = {}; // simulate a registration
var u2f_mock = {};
u2f_mock.startAuthentication = sinon.stub();
u2f_mock.startAuthentication.returns(Promise.reject('Internal error'));
req.app.get.withArgs('u2f').returns(u2f_mock);
u2f.sign_request(req, res);
});
it('should send unauthorized error when no registration exists', function(done) {
var expectedRequest = {
test: 'abc'
};
res.send = sinon.spy(function(data) {
assert.equal(401, res.status.getCall(0).args[0]);
done();
});
var user_key_container = {}; // no entry means no registration
var u2f_mock = {};
u2f_mock.startAuthentication = sinon.stub();
u2f_mock.startAuthentication.returns(Promise.resolve(expectedRequest));
user_data_store.get_u2f_meta = sinon.stub().returns(Promise.resolve());
req.app.get = sinon.stub();
req.app.get.withArgs('logger').returns(winston);
req.app.get.withArgs('user data store').returns(user_data_store);
req.app.get.withArgs('u2f').returns(u2f_mock);
u2f.sign_request(req, res);
});
}
function test_signing() {
it('should return status code 204', function(done) {
var user_key_container = {};
user_key_container['user'] = {};
var expectedStatus = {
keyHandle: 'keyHandle',
publicKey: 'pbk',
certificate: 'cert'
};
res.send = sinon.spy(function(data) {
assert(204, res.status.getCall(0).args[0]);
assert(req.session.auth_session.second_factor);
done();
});
var u2f_mock = {};
u2f_mock.finishAuthentication = sinon.stub();
u2f_mock.finishAuthentication.returns(Promise.resolve(expectedStatus));
req.session.auth_session.sign_request = {};
req.app.get.withArgs('u2f').returns(u2f_mock);
u2f.sign(req, res);
});
it('should return unauthorized error on registration request internal error', function(done) {
res.send = sinon.spy(function(data) {
assert.equal(500, res.status.getCall(0).args[0]);
done();
});
var user_key_container = {};
user_key_container['user'] = {};
var u2f_mock = {};
u2f_mock.finishAuthentication = sinon.stub();
u2f_mock.finishAuthentication.returns(Promise.reject('Internal error'));
req.session.auth_session.sign_request = {};
req.app.get.withArgs('u2f').returns(u2f_mock);
u2f.sign(req, res);
});
it('should return unauthorized error when no sign request has been initiated', function(done) {
res.send = sinon.spy(function(data) {
assert.equal(401, res.status.getCall(0).args[0]);
done();
});
var user_key_container = {};
var u2f_mock = {};
u2f_mock.finishAuthentication = sinon.stub();
u2f_mock.finishAuthentication.returns(Promise.resolve());
req.app.get.withArgs('u2f').returns(u2f_mock);
u2f.sign(req, res);
});
}
});

View File

@ -1,78 +0,0 @@
var sinon = require('sinon');
var winston = require('winston');
var u2f_register = require('../../../src/lib/routes/u2f_register_handler');
var assert = require('assert');
describe('test register handler', function() {
var req, res;
var user_data_store;
beforeEach(function() {
req = {}
req.app = {};
req.app.get = sinon.stub();
req.app.get.withArgs('logger').returns(winston);
req.session = {};
req.session.auth_session = {};
req.session.auth_session.userid = 'user';
req.session.auth_session.email = 'user@example.com';
req.session.auth_session.first_factor = true;
req.session.auth_session.second_factor = false;
req.headers = {};
req.headers.host = 'localhost';
var options = {};
options.inMemoryOnly = true;
user_data_store = {};
user_data_store.set_u2f_meta = sinon.stub().returns(Promise.resolve({}));
user_data_store.get_u2f_meta = sinon.stub().returns(Promise.resolve({}));
user_data_store.issue_identity_check_token = sinon.stub().returns(Promise.resolve({}));
user_data_store.consume_identity_check_token = sinon.stub().returns(Promise.resolve({}));
req.app.get.withArgs('user data store').returns(user_data_store);
res = {};
res.send = sinon.spy();
res.json = sinon.spy();
res.status = sinon.spy();
});
describe('test u2f registration check', test_registration_check);
function test_registration_check() {
it('should fail if first_factor has not been passed', function(done) {
req.session.auth_session.first_factor = false;
u2f_register.icheck_interface.pre_check_callback(req)
.catch(function(err) {
done();
});
});
it('should fail if userid is missing', function(done) {
req.session.auth_session.first_factor = false;
req.session.auth_session.userid = undefined;
u2f_register.icheck_interface.pre_check_callback(req)
.catch(function(err) {
done();
});
});
it('should fail if email is missing', function(done) {
req.session.auth_session.first_factor = false;
req.session.auth_session.email = undefined;
u2f_register.icheck_interface.pre_check_callback(req)
.catch(function(err) {
done();
});
});
it('should succeed if first factor passed, userid and email are provided', function(done) {
u2f_register.icheck_interface.pre_check_callback(req)
.then(function(err) {
done();
});
});
}
});

View File

@ -1,205 +0,0 @@
var sinon = require('sinon');
var identity_check = require('../../src/lib/identity_check');
var exceptions = require('../../src/lib/Exceptions');
var assert = require('assert');
var winston = require('winston');
var Promise = require('bluebird');
describe('test identity check process', function() {
var req, res, app, icheck_interface;
var user_data_store;
var notifier;
beforeEach(function() {
req = {};
res = {};
app = {};
icheck_interface = {};
icheck_interface.pre_check_callback = sinon.stub();
user_data_store = {};
user_data_store.issue_identity_check_token = sinon.stub();
user_data_store.issue_identity_check_token.returns(Promise.resolve());
user_data_store.consume_identity_check_token = sinon.stub();
user_data_store.consume_identity_check_token.returns(Promise.resolve({ userid: 'user' }));
notifier = {};
notifier.notify = sinon.stub().returns(Promise.resolve());
req.headers = {};
req.session = {};
req.session.auth_session = {};
req.query = {};
req.app = {};
req.app.get = sinon.stub();
req.app.get.withArgs('logger').returns(winston);
req.app.get.withArgs('user data store').returns(user_data_store);
req.app.get.withArgs('notifier').returns(notifier);
res.status = sinon.spy();
res.send = sinon.spy();
res.redirect = sinon.spy();
res.render = sinon.spy();
app.get = sinon.spy();
app.post = sinon.spy();
});
it('should register a POST and GET endpoint', function() {
var app = {};
app.get = sinon.spy();
app.post = sinon.spy();
var endpoint = '/test';
var icheck_interface = {};
identity_check(app, endpoint, icheck_interface);
assert(app.get.calledOnce);
assert(app.get.calledWith(endpoint));
assert(app.post.calledOnce);
assert(app.post.calledWith(endpoint));
});
describe('test POST', test_post_handler);
describe('test GET', test_get_handler);
function test_post_handler() {
it('should send 403 if pre check rejects', function(done) {
var endpoint = '/protected';
icheck_interface.pre_check_callback.returns(Promise.reject('No access'));
identity_check(app, endpoint, icheck_interface);
res.send = sinon.spy(function() {
assert.equal(res.status.getCall(0).args[0], 403);
done();
});
var handler = app.post.getCall(0).args[1];
handler(req, res);
});
it('should send 400 if email is missing in provided identity', function(done) {
var endpoint = '/protected';
var identity = { userid: 'abc' };
icheck_interface.pre_check_callback.returns(Promise.resolve(identity));
identity_check(app, endpoint, icheck_interface);
res.send = sinon.spy(function() {
assert.equal(res.status.getCall(0).args[0], 400);
done();
});
var handler = app.post.getCall(0).args[1];
handler(req, res);
});
it('should send 400 if userid is missing in provided identity', function(done) {
var endpoint = '/protected';
var identity = { email: 'abc@example.com' };
icheck_interface.pre_check_callback.returns(Promise.resolve(identity));
identity_check(app, endpoint, icheck_interface);
res.send = sinon.spy(function() {
assert.equal(res.status.getCall(0).args[0], 400);
done();
});
var handler = app.post.getCall(0).args[1];
handler(req, res);
});
it('should issue a token, send an email and return 204', function(done) {
var endpoint = '/protected';
var identity = { userid: 'user', email: 'abc@example.com' };
req.headers.host = 'localhost';
req.headers['x-original-uri'] = '/auth/test';
icheck_interface.pre_check_callback.returns(Promise.resolve(identity));
identity_check(app, endpoint, icheck_interface);
res.send = sinon.spy(function() {
assert.equal(res.status.getCall(0).args[0], 204);
assert(notifier.notify.calledOnce);
assert(user_data_store.issue_identity_check_token.calledOnce);
assert.equal(user_data_store.issue_identity_check_token.getCall(0).args[0], 'user');
assert.equal(user_data_store.issue_identity_check_token.getCall(0).args[3], 240000);
done();
});
var handler = app.post.getCall(0).args[1];
handler(req, res);
});
}
function test_get_handler() {
it('should send 403 if no identity_token is provided', function(done) {
var endpoint = '/protected';
identity_check(app, endpoint, icheck_interface);
res.send = sinon.spy(function() {
assert.equal(res.status.getCall(0).args[0], 403);
done();
});
var handler = app.get.getCall(0).args[1];
handler(req, res);
});
it('should render template if identity_token is provided and still valid', function(done) {
req.query.identity_token = 'token';
var endpoint = '/protected';
icheck_interface.render_template = 'template';
identity_check(app, endpoint, icheck_interface);
res.render = sinon.spy(function(template) {
assert.equal(template, 'template');
done();
});
var handler = app.get.getCall(0).args[1];
handler(req, res);
});
it('should return 403 if identity_token is provided but invalid', function(done) {
req.query.identity_token = 'token';
var endpoint = '/protected';
icheck_interface.render_template = 'template';
user_data_store.consume_identity_check_token
.returns(Promise.reject('Invalid token'));
identity_check(app, endpoint, icheck_interface);
res.send = sinon.spy(function(template) {
assert.equal(res.status.getCall(0).args[0], 403);
done();
});
var handler = app.get.getCall(0).args[1];
handler(req, res);
});
it('should set the identity_check session object even if session does not exist yet', function(done) {
req.query.identity_token = 'token';
var endpoint = '/protected';
req.session = {};
icheck_interface.render_template = 'template';
identity_check(app, endpoint, icheck_interface);
res.render = sinon.spy(function(template) {
assert.equal(req.session.auth_session.identity_check.userid, 'user');
assert.equal(template, 'template');
done();
});
var handler = app.get.getCall(0).args[1];
handler(req, res);
});
}
});

View File

@ -7,7 +7,6 @@
"sourceMap": true, "sourceMap": true,
"outDir": "dist", "outDir": "dist",
"baseUrl": ".", "baseUrl": ".",
"allowJs": true,
"paths": { "paths": {
"*": [ "*": [
"src/types/*", "src/types/*",