Finish migration to typescript
parent
e3257b81a5
commit
9e89a690fb
|
@ -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",
|
||||||
|
|
|
@ -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();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -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"); });
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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");
|
||||||
|
|
||||||
|
|
|
@ -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();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -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)
|
||||||
|
};
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
};
|
|
@ -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
|
||||||
|
};
|
|
@ -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(),
|
||||||
|
};
|
||||||
|
|
|
@ -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
|
||||||
|
};
|
|
@ -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;
|
|
@ -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();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
|
@ -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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
|
@ -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();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
};
|
|
@ -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');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
|
@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
|
@ -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()
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
import sinon = require("sinon");
|
||||||
|
|
||||||
|
export interface NotifierMock {
|
||||||
|
notify: sinon.SinonStub;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function NotifierMock(): NotifierMock {
|
||||||
|
return {
|
||||||
|
notify: sinon.stub()
|
||||||
|
};
|
||||||
|
}
|
|
@ -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()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
};
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -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()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -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,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
|
@ -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
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
|
@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
|
@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
|
@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
|
@ -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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
|
@ -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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -7,7 +7,6 @@
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"allowJs": true,
|
|
||||||
"paths": {
|
"paths": {
|
||||||
"*": [
|
"*": [
|
||||||
"src/types/*",
|
"src/types/*",
|
||||||
|
|
Loading…
Reference in New Issue