Finish migration to typescript
parent
e3257b81a5
commit
9e89a690fb
|
@ -59,6 +59,7 @@
|
|||
"@types/nodemailer": "^1.3.32",
|
||||
"@types/object-path": "^0.9.28",
|
||||
"@types/proxyquire": "^1.3.27",
|
||||
"@types/randomstring": "^1.1.5",
|
||||
"@types/request": "0.0.43",
|
||||
"@types/sinon": "^2.2.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 { LdapConfiguration } from "./Configuration";
|
||||
import { Ldapjs } from "../types/Dependencies";
|
||||
import { ILogger } from "./ILogger";
|
||||
import { ILogger } from "../types/ILogger";
|
||||
|
||||
interface SearchEntry {
|
||||
object: any;
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
|
||||
import express = require("express");
|
||||
|
||||
const routes = require("./routes");
|
||||
const identity_check = require("./identity_check");
|
||||
import routes = require("./routes");
|
||||
import IdentityValidator = require("./IdentityValidator");
|
||||
import UserDataStore from "./UserDataStore";
|
||||
import { ILogger } from "../types/ILogger";
|
||||
|
||||
export default class RestApi {
|
||||
static setup(app: express.Application): void {
|
||||
static setup(app: express.Application, userDataStore: UserDataStore, logger: ILogger): void {
|
||||
/**
|
||||
* @apiDefine UserSession
|
||||
* @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.
|
||||
* 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
|
||||
* 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
|
||||
|
@ -129,7 +130,7 @@ export default class RestApi {
|
|||
* @apiDescription Serves password reset form that allow the user to provide
|
||||
* 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"); });
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import TOTPGenerator from "./TOTPGenerator";
|
|||
import RestApi from "./RestApi";
|
||||
import { LdapClient } from "./LdapClient";
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import { IdentityValidator } from "./IdentityValidator";
|
||||
|
||||
import * as Express from "express";
|
||||
import * as BodyParser from "body-parser";
|
||||
|
@ -55,26 +56,28 @@ export default class Server {
|
|||
deps.winston.level = config.logs_level || "info";
|
||||
|
||||
const five_minutes = 5 * 60;
|
||||
const data_store = new UserDataStore(datastore_options, deps.nedb);
|
||||
const regulator = new AuthenticationRegulator(data_store, five_minutes);
|
||||
const userDataStore = new UserDataStore(datastore_options, deps.nedb);
|
||||
const regulator = new AuthenticationRegulator(userDataStore, five_minutes);
|
||||
const notifier = NotifierFactory.build(config.notifier, deps.nodemailer);
|
||||
const ldap = new LdapClient(config.ldap, deps.ldapjs, deps.winston);
|
||||
const accessController = new AccessController(config.access_control, deps.winston);
|
||||
const totpValidator = new TOTPValidator(deps.speakeasy);
|
||||
const totpGenerator = new TOTPGenerator(deps.speakeasy);
|
||||
const identityValidator = new IdentityValidator(userDataStore, deps.winston);
|
||||
|
||||
app.set("logger", deps.winston);
|
||||
app.set("ldap", ldap);
|
||||
app.set("totp validator", totpValidator);
|
||||
app.set("totp generator", totpGenerator);
|
||||
app.set("u2f", deps.u2f);
|
||||
app.set("user data store", data_store);
|
||||
app.set("user data store", userDataStore);
|
||||
app.set("notifier", notifier);
|
||||
app.set("authentication regulator", regulator);
|
||||
app.set("config", config);
|
||||
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) => {
|
||||
this.httpServer = app.listen(config.port, function (err: string) {
|
||||
|
|
|
@ -28,6 +28,17 @@ export interface Options {
|
|||
directory?: string;
|
||||
}
|
||||
|
||||
export interface IdentityValidationRequestContent {
|
||||
userid: string;
|
||||
data: string;
|
||||
}
|
||||
|
||||
export interface IdentityValidationRequestDocument {
|
||||
userid: string;
|
||||
token: string;
|
||||
content: IdentityValidationRequestContent;
|
||||
max_date: Date;
|
||||
}
|
||||
|
||||
// Source
|
||||
|
||||
|
@ -54,7 +65,7 @@ export default class UserDataStore {
|
|||
userid: userid,
|
||||
appid: appid,
|
||||
meta: meta
|
||||
};
|
||||
} as U2FMetaDocument;
|
||||
|
||||
const filter = {
|
||||
userid: userid,
|
||||
|
@ -110,7 +121,7 @@ export default class UserDataStore {
|
|||
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 = {
|
||||
token: token
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
import { ACLConfiguration } from "../Configuration";
|
||||
import PatternBuilder from "./PatternBuilder";
|
||||
import { ILogger } from "../ILogger";
|
||||
import { ILogger } from "../../types/ILogger";
|
||||
|
||||
export default class AccessController {
|
||||
private logger: ILogger;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
import { ILogger } from "../ILogger";
|
||||
import { ILogger } from "../../types/ILogger";
|
||||
import { ACLConfiguration, ACLGroupsRules, ACLUsersRules, ACLDefaultRules } from "../Configuration";
|
||||
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 second_factor = require("./routes/second_factor");
|
||||
import reset_password = require("./routes/reset_password");
|
||||
import SecondFactorRoutes = require("./routes/SecondFactorRoutes");
|
||||
import PasswordReset = require("./routes/PasswordReset");
|
||||
import AuthenticationValidator = require("./routes/AuthenticationValidator");
|
||||
import u2f_register_handler = require("./routes/u2f_register_handler");
|
||||
import totp_register = require("./routes/totp_register");
|
||||
import U2FRegistration = require("./routes/U2FRegistration");
|
||||
import TOTPRegistration = require("./routes/TOTPRegistration");
|
||||
import objectPath = require("object-path");
|
||||
|
||||
import express = require("express");
|
||||
|
@ -14,10 +14,10 @@ export = {
|
|||
logout: serveLogout,
|
||||
verify: AuthenticationValidator,
|
||||
first_factor: FirstFactor,
|
||||
second_factor: second_factor,
|
||||
reset_password: reset_password,
|
||||
u2f_register: u2f_register_handler,
|
||||
totp_register: totp_register,
|
||||
second_factor: SecondFactorRoutes,
|
||||
reset_password: PasswordReset,
|
||||
u2f_register: U2FRegistration,
|
||||
totp_register: TOTPRegistration,
|
||||
};
|
||||
|
||||
function serveLogin(req: express.Request, res: express.Response) {
|
||||
|
|
|
@ -2,8 +2,10 @@
|
|||
import objectPath = require("object-path");
|
||||
import express = require("express");
|
||||
|
||||
export = function denyNotLogged(callback: (req: express.Request, res: express.Response) => void) {
|
||||
return function (req: express.Request, res: express.Response) {
|
||||
type ExpressRequest = (req: express.Request, res: express.Response, next?: express.NextFunction) => void;
|
||||
|
||||
export = function(callback: ExpressRequest): ExpressRequest {
|
||||
return function (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
const auth_session = req.session.auth_session;
|
||||
const first_factor = objectPath.has(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();
|
||||
return;
|
||||
}
|
||||
|
||||
callback(req, res);
|
||||
callback(req, res, next);
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
consume_identity_check_token: sinon.SinonStub;
|
||||
get_totp_secret: sinon.SinonStub;
|
||||
set_totp_secret: sinon.SinonStub;
|
||||
}
|
||||
|
||||
export function UserDataStore(): UserDataStore {
|
||||
|
@ -15,6 +16,7 @@ export function UserDataStore(): UserDataStore {
|
|||
get_u2f_meta: sinon.stub(),
|
||||
issue_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;
|
||||
headers?: any;
|
||||
get?: any;
|
||||
query?: any;
|
||||
}
|
||||
|
||||
export interface ResponseMock {
|
||||
|
@ -16,7 +17,7 @@ export interface ResponseMock {
|
|||
sendFile: sinon.SinonStub;
|
||||
sendfile: sinon.SinonStub;
|
||||
status: sinon.SinonStub | sinon.SinonSpy;
|
||||
json: sinon.SinonStub;
|
||||
json: sinon.SinonStub | sinon.SinonSpy;
|
||||
links: sinon.SinonStub;
|
||||
jsonp: sinon.SinonStub;
|
||||
download: sinon.SinonStub;
|
||||
|
@ -32,7 +33,7 @@ export interface ResponseMock {
|
|||
cookie: sinon.SinonStub;
|
||||
location: sinon.SinonStub;
|
||||
redirect: sinon.SinonStub;
|
||||
render: sinon.SinonStub;
|
||||
render: sinon.SinonStub | sinon.SinonSpy;
|
||||
locals: sinon.SinonStub;
|
||||
charset: string;
|
||||
vary: sinon.SinonStub;
|
||||
|
|
|
@ -1,6 +1,22 @@
|
|||
|
||||
import sinon = require("sinon");
|
||||
|
||||
export = {
|
||||
export interface NodemailerMock {
|
||||
createTransport: sinon.SinonStub;
|
||||
}
|
||||
|
||||
export function NodemailerMock(): NodemailerMock {
|
||||
return {
|
||||
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 assert from "assert";
|
||||
|
||||
import nodemailerMock = require("../mocks/nodemailer");
|
||||
import NodemailerMock = require("../mocks/nodemailer");
|
||||
import GMailNotifier = require("../../../src/lib/notifiers/GMailNotifier");
|
||||
|
||||
|
||||
|
@ -10,6 +10,7 @@ describe("test gmail notifier", function () {
|
|||
const transporter = {
|
||||
sendMail: sinon.stub().yields()
|
||||
};
|
||||
const nodemailerMock = NodemailerMock.NodemailerMock();
|
||||
nodemailerMock.createTransport.returns(transporter);
|
||||
|
||||
const options = {
|
||||
|
|
|
@ -3,16 +3,15 @@ import * as sinon from "sinon";
|
|||
import * as BluebirdPromise from "bluebird";
|
||||
import * as assert from "assert";
|
||||
|
||||
import NodemailerMock = require("../mocks/nodemailer");
|
||||
|
||||
import { NotifierFactory } from "../../../src/lib/notifiers/NotifierFactory";
|
||||
import { GMailNotifier } from "../../../src/lib/notifiers/GMailNotifier";
|
||||
import { FileSystemNotifier } from "../../../src/lib/notifiers/FileSystemNotifier";
|
||||
|
||||
import nodemailerMock = require("../mocks/nodemailer");
|
||||
import NodemailerMock = require("../mocks/nodemailer");
|
||||
|
||||
|
||||
describe("test notifier factory", function() {
|
||||
let nodemailerMock: NodemailerMock.NodemailerMock;
|
||||
it("should build a Gmail Notifier", function() {
|
||||
const options = {
|
||||
gmail: {
|
||||
|
@ -20,6 +19,7 @@ describe("test notifier factory", function() {
|
|||
password: "password"
|
||||
}
|
||||
};
|
||||
nodemailerMock = NodemailerMock.NodemailerMock();
|
||||
nodemailerMock.createTransport.returns(sinon.spy());
|
||||
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 sinon = require("sinon");
|
||||
import winston = require("winston");
|
||||
|
@ -72,7 +72,7 @@ describe("test reset password", function () {
|
|||
function test_reset_password_check() {
|
||||
it("should fail when no userid is provided", function (done) {
|
||||
req.body.userid = undefined;
|
||||
reset_password.icheck_interface.pre_check_callback(req)
|
||||
PasswordReset.icheck_interface.preValidation(req as any)
|
||||
.catch(function (err: Error) {
|
||||
done();
|
||||
});
|
||||
|
@ -80,7 +80,7 @@ describe("test reset password", function () {
|
|||
|
||||
it("should fail if ldap fail", function (done) {
|
||||
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) {
|
||||
done();
|
||||
});
|
||||
|
@ -89,7 +89,7 @@ describe("test reset password", function () {
|
|||
it("should perform a search in ldap to find email address", function (done) {
|
||||
configuration.ldap.user_name_attribute = "uid";
|
||||
ldap_client.get_emails.returns(BluebirdPromise.resolve([]));
|
||||
reset_password.icheck_interface.pre_check_callback(req)
|
||||
PasswordReset.icheck_interface.preValidation(req as any)
|
||||
.then(function () {
|
||||
assert.equal("user", ldap_client.get_emails.getCall(0).args[0]);
|
||||
done();
|
||||
|
@ -98,7 +98,7 @@ describe("test reset password", function () {
|
|||
|
||||
it("should returns identity when ldap replies", function (done) {
|
||||
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 () {
|
||||
done();
|
||||
});
|
||||
|
@ -120,7 +120,7 @@ describe("test reset password", function () {
|
|||
assert.equal(req.session.auth_session, undefined);
|
||||
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) {
|
||||
|
@ -130,7 +130,7 @@ describe("test reset password", function () {
|
|||
assert.equal(res.status.getCall(0).args[0], 403);
|
||||
done();
|
||||
});
|
||||
reset_password.post(req, res);
|
||||
PasswordReset.post(req as any, res as any);
|
||||
});
|
||||
|
||||
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);
|
||||
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,
|
||||
"outDir": "dist",
|
||||
"baseUrl": ".",
|
||||
"allowJs": true,
|
||||
"paths": {
|
||||
"*": [
|
||||
"src/types/*",
|
||||
|
|
Loading…
Reference in New Issue