Merge pull request #47 from clems4ever/redis-sessions
Use Redis as a store of user sessions for resiliencepull/50/head
commit
1e1653d81f
17
Gruntfile.js
17
Gruntfile.js
|
@ -13,16 +13,20 @@ module.exports = function (grunt) {
|
||||||
args: ['-c', 'tslint.json', '-p', 'tsconfig.json']
|
args: ['-c', 'tslint.json', '-p', 'tsconfig.json']
|
||||||
},
|
},
|
||||||
"test": {
|
"test": {
|
||||||
cmd: "npm",
|
cmd: "./node_modules/.bin/mocha",
|
||||||
args: ['run', 'test']
|
args: ['--compilers', 'ts:ts-node/register', '--recursive', 'test/client', 'test/server']
|
||||||
|
},
|
||||||
|
"test-int": {
|
||||||
|
cmd: "./node_modules/.bin/mocha",
|
||||||
|
args: ['--compilers', 'ts:ts-node/register', '--recursive', 'test/integration']
|
||||||
},
|
},
|
||||||
"docker-build": {
|
"docker-build": {
|
||||||
cmd: "docker",
|
cmd: "docker",
|
||||||
args: ['build', '-t', 'clems4ever/authelia', '.']
|
args: ['build', '-t', 'clems4ever/authelia', '.']
|
||||||
},
|
},
|
||||||
"docker-restart": {
|
"docker-restart": {
|
||||||
cmd: "docker-compose",
|
cmd: "./scripts/dc-example.sh",
|
||||||
args: ['-f', 'docker-compose.yml', '-f', 'docker-compose.dev.yml', 'restart', 'auth']
|
args: ['up', '-d']
|
||||||
},
|
},
|
||||||
"minify": {
|
"minify": {
|
||||||
cmd: "./node_modules/.bin/uglifyjs",
|
cmd: "./node_modules/.bin/uglifyjs",
|
||||||
|
@ -109,7 +113,7 @@ module.exports = function (grunt) {
|
||||||
},
|
},
|
||||||
client: {
|
client: {
|
||||||
files: ['src/client/**/*.ts', 'test/client/**/*.ts'],
|
files: ['src/client/**/*.ts', 'test/client/**/*.ts'],
|
||||||
tasks: ['build'],
|
tasks: ['build-dev'],
|
||||||
options: {
|
options: {
|
||||||
interrupt: true,
|
interrupt: true,
|
||||||
atBegin: true
|
atBegin: true
|
||||||
|
@ -117,9 +121,10 @@ module.exports = function (grunt) {
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
files: ['src/server/**/*.ts', 'test/server/**/*.ts'],
|
files: ['src/server/**/*.ts', 'test/server/**/*.ts'],
|
||||||
tasks: ['build', 'run:docker-restart', 'run:make-dev-views' ],
|
tasks: ['build-dev', 'run:docker-restart', 'run:make-dev-views' ],
|
||||||
options: {
|
options: {
|
||||||
interrupt: true,
|
interrupt: true,
|
||||||
|
atBegin: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
45
README.md
45
README.md
|
@ -7,13 +7,37 @@
|
||||||
nginx. It has been made to work with nginx [auth_request] module and is currently
|
nginx. It has been made to work with nginx [auth_request] module and is currently
|
||||||
used in production to secure internal services in a small docker swarm cluster.
|
used in production to secure internal services in a small docker swarm cluster.
|
||||||
|
|
||||||
## Features
|
# Table of Contents
|
||||||
|
1. [Features summary](#features-summary)
|
||||||
|
2. [Deployment](#deployment)
|
||||||
|
1. [With NPM](#with-npm)
|
||||||
|
2. [With Docker](#with-docker)
|
||||||
|
3. [Getting started](#getting-started)
|
||||||
|
1. [Pre-requisites](#pre-requisites)
|
||||||
|
2. [Run it!](#run-it)
|
||||||
|
4. [Features in details](#features-in-details)
|
||||||
|
1. [First factor with LDAP and ACL](#first-factor-with-ldap-and-acl)
|
||||||
|
2. [Second factor with TOTP](#second-factor-with-totp)
|
||||||
|
3. [Second factor with U2F security keys](#second-factor-with-u2f-security-keys)
|
||||||
|
4. [Password reset](#password-reset)
|
||||||
|
5. [Access control](#access-control)
|
||||||
|
6. [Session management with Redis](#session-management-with-redis)
|
||||||
|
4. [Documentation](#documentation)
|
||||||
|
1. [Authelia configuration](#authelia-configuration)
|
||||||
|
1. [API documentation](#api-documentation)
|
||||||
|
5. [Contributing to Authelia](#contributing-to-authelia)
|
||||||
|
6. [License](#license)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Features summary
|
||||||
* Two-factor authentication using either
|
* Two-factor authentication using either
|
||||||
**[TOTP] - Time-Base One Time password -** or **[U2F] - Universal 2-Factor -**
|
**[TOTP] - Time-Base One Time password -** or **[U2F] - Universal 2-Factor -**
|
||||||
as 2nd factor.
|
as 2nd factor.
|
||||||
* Password reset with identity verification by sending links to user email
|
* Password reset with identity verification by sending links to user email
|
||||||
address.
|
address.
|
||||||
* Access restriction after too many authentication attempts.
|
* Access restriction after too many authentication attempts.
|
||||||
|
* Session management using Redis key/value store.
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
|
@ -73,7 +97,7 @@ Add the following lines to your **/etc/hosts** to alias multiple subdomains so t
|
||||||
127.0.0.1 mx2.mail.test.local
|
127.0.0.1 mx2.mail.test.local
|
||||||
127.0.0.1 auth.test.local
|
127.0.0.1 auth.test.local
|
||||||
|
|
||||||
### Deployment
|
### Run it!
|
||||||
|
|
||||||
Deploy **Authelia** example with the following command:
|
Deploy **Authelia** example with the following command:
|
||||||
|
|
||||||
|
@ -93,7 +117,9 @@ Below is what the login page looks like:
|
||||||
|
|
||||||
<img src="https://raw.githubusercontent.com/clems4ever/authelia/master/images/first_factor.png" width="400">
|
<img src="https://raw.githubusercontent.com/clems4ever/authelia/master/images/first_factor.png" width="400">
|
||||||
|
|
||||||
### First factor: LDAP and ACL
|
## Features in details
|
||||||
|
|
||||||
|
### First factor with LDAP and ACL
|
||||||
An LDAP server has been deployed for you with the following credentials and
|
An LDAP server has been deployed for you with the following credentials and
|
||||||
access control list:
|
access control list:
|
||||||
|
|
||||||
|
@ -117,8 +143,8 @@ your credentials are wrong.
|
||||||
<img src="https://raw.githubusercontent.com/clems4ever/authelia/master/images/second_factor.png" width="400">
|
<img src="https://raw.githubusercontent.com/clems4ever/authelia/master/images/second_factor.png" width="400">
|
||||||
|
|
||||||
|
|
||||||
### Second factor: TOTP (Time-Base One Time Password)
|
### Second factor with TOTP
|
||||||
In **Authelia**, you need to register a per user TOTP secret before
|
In **Authelia**, you need to register a per user TOTP (Time-Based One Time Password) secret before
|
||||||
authenticating. To do that, you need to click on the register button. It will
|
authenticating. To do that, you need to click on the register button. It will
|
||||||
send a link to the user email address. Since this is an example, no email will
|
send a link to the user email address. Since this is an example, no email will
|
||||||
be sent, the link is rather delivered in the file
|
be sent, the link is rather delivered in the file
|
||||||
|
@ -129,8 +155,8 @@ to store them and get the generated tokens with the app.
|
||||||
|
|
||||||
<img src="https://raw.githubusercontent.com/clems4ever/authelia/master/images/totp.png" width="400">
|
<img src="https://raw.githubusercontent.com/clems4ever/authelia/master/images/totp.png" width="400">
|
||||||
|
|
||||||
### 2nd factor: U2F (Universal 2-Factor) with security keys
|
### Second factor with U2F security keys
|
||||||
**Authelia** also offers authentication using U2F devices like [Yubikey](Yubikey)
|
**Authelia** also offers authentication using U2F (Universal 2-Factor) devices like [Yubikey](Yubikey)
|
||||||
USB security keys. U2F is one of the most secure authentication protocol and is
|
USB security keys. U2F is one of the most secure authentication protocol and is
|
||||||
already available for Google, Facebook, Github accounts and more.
|
already available for Google, Facebook, Github accounts and more.
|
||||||
|
|
||||||
|
@ -160,8 +186,11 @@ the user access to some subdomains. Those rules are defined in the
|
||||||
configuration file and can be set either for everyone, per-user or per-group policies.
|
configuration file and can be set either for everyone, per-user or per-group policies.
|
||||||
Check out the *config.template.yml* to see how they are defined.
|
Check out the *config.template.yml* to see how they are defined.
|
||||||
|
|
||||||
|
### Session management with Redis
|
||||||
|
When your users authenticate against Authelia, sessions are stored in a Redis key/value store. You can specify your own Redis instance in the [configuration file](#authelia-configuration).
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
### Configuration
|
### Authelia configuration
|
||||||
The configuration of the server is defined in the file
|
The configuration of the server is defined in the file
|
||||||
**configuration.template.yml**. All the details are documented there.
|
**configuration.template.yml**. All the details are documented there.
|
||||||
You can specify another configuration file by giving it as first argument of
|
You can specify another configuration file by giving it as first argument of
|
||||||
|
|
|
@ -73,7 +73,9 @@ session:
|
||||||
secret: unsecure_secret
|
secret: unsecure_secret
|
||||||
expiration: 3600000
|
expiration: 3600000
|
||||||
domain: test.local
|
domain: test.local
|
||||||
|
redis:
|
||||||
|
host: redis
|
||||||
|
port: 6379
|
||||||
|
|
||||||
# The directory where the DB files will be saved
|
# The directory where the DB files will be saved
|
||||||
store_directory: /var/lib/authelia/store
|
store_directory: /var/lib/authelia/store
|
||||||
|
|
|
@ -6,6 +6,7 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
- ./config.template.yml:/etc/authelia/config.yml:ro
|
- ./config.template.yml:/etc/authelia/config.yml:ro
|
||||||
- ./notifications:/var/lib/authelia/notifications
|
- ./notifications:/var/lib/authelia/notifications
|
||||||
|
depends_on:
|
||||||
|
- redis
|
||||||
networks:
|
networks:
|
||||||
- example-network
|
- example-network
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
version: '2'
|
||||||
|
services:
|
||||||
|
redis:
|
||||||
|
image: redis
|
||||||
|
networks:
|
||||||
|
- example-network
|
|
@ -7,7 +7,7 @@
|
||||||
"authelia": "dist/src/server/index.js"
|
"authelia": "dist/src/server/index.js"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "./node_modules/.bin/mocha --compilers ts:ts-node/register --recursive test/client test/server",
|
"test": "./node_modules/.bin/grunt test",
|
||||||
"cover": "NODE_ENV=test nyc npm t",
|
"cover": "NODE_ENV=test nyc npm t",
|
||||||
"serve": "node dist/server/index.js"
|
"serve": "node dist/server/index.js"
|
||||||
},
|
},
|
||||||
|
@ -27,6 +27,7 @@
|
||||||
"@types/cors": "^2.8.1",
|
"@types/cors": "^2.8.1",
|
||||||
"bluebird": "^3.4.7",
|
"bluebird": "^3.4.7",
|
||||||
"body-parser": "^1.15.2",
|
"body-parser": "^1.15.2",
|
||||||
|
"connect-redis": "^3.3.0",
|
||||||
"dovehash": "0.0.5",
|
"dovehash": "0.0.5",
|
||||||
"ejs": "^2.5.5",
|
"ejs": "^2.5.5",
|
||||||
"express": "^4.14.0",
|
"express": "^4.14.0",
|
||||||
|
@ -45,6 +46,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bluebird": "^3.5.4",
|
"@types/bluebird": "^3.5.4",
|
||||||
"@types/body-parser": "^1.16.3",
|
"@types/body-parser": "^1.16.3",
|
||||||
|
"@types/connect-redis": "0.0.6",
|
||||||
"@types/ejs": "^2.3.33",
|
"@types/ejs": "^2.3.33",
|
||||||
"@types/express": "^4.0.35",
|
"@types/express": "^4.0.35",
|
||||||
"@types/express-session": "0.0.32",
|
"@types/express-session": "0.0.32",
|
||||||
|
@ -59,7 +61,7 @@
|
||||||
"@types/proxyquire": "^1.3.27",
|
"@types/proxyquire": "^1.3.27",
|
||||||
"@types/query-string": "^4.3.1",
|
"@types/query-string": "^4.3.1",
|
||||||
"@types/randomstring": "^1.1.5",
|
"@types/randomstring": "^1.1.5",
|
||||||
"@types/request": "0.0.45",
|
"@types/request": "0.0.46",
|
||||||
"@types/sinon": "^2.2.1",
|
"@types/sinon": "^2.2.1",
|
||||||
"@types/speakeasy": "^2.0.1",
|
"@types/speakeasy": "^2.0.1",
|
||||||
"@types/tmp": "0.0.33",
|
"@types/tmp": "0.0.33",
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
service_count=`docker ps -a | grep "Up " | wc -l`
|
service_count=`docker ps -a | grep "Up " | wc -l`
|
||||||
|
|
||||||
if [ "${service_count}" -eq "3" ]
|
if [ "${service_count}" -eq "4" ]
|
||||||
then
|
then
|
||||||
echo "Service are up and running."
|
echo "Service are up and running."
|
||||||
exit 0
|
exit 0
|
||||||
|
|
|
@ -2,4 +2,4 @@
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
docker-compose -f docker-compose.base.yml -f docker-compose.yml -f example/nginx/docker-compose.yml -f example/ldap/docker-compose.yml $*
|
docker-compose -f docker-compose.base.yml -f docker-compose.yml -f example/redis/docker-compose.yml -f example/nginx/docker-compose.yml -f example/ldap/docker-compose.yml $*
|
||||||
|
|
|
@ -2,4 +2,4 @@
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
docker-compose -f docker-compose.base.yml -f example/ldap/docker-compose.yml -f test/integration/docker-compose.yml $*
|
docker-compose -f docker-compose.base.yml -f example/redis/docker-compose.yml -f example/ldap/docker-compose.yml -f test/integration/docker-compose.yml $*
|
||||||
|
|
|
@ -6,7 +6,9 @@ echo "Build services images..."
|
||||||
./scripts/dc-test.sh build
|
./scripts/dc-test.sh build
|
||||||
|
|
||||||
echo "Start services..."
|
echo "Start services..."
|
||||||
./scripts/dc-test.sh up -d authelia nginx openldap
|
./scripts/dc-test.sh up -d redis openldap
|
||||||
|
sleep 2
|
||||||
|
./scripts/dc-test.sh up -d authelia nginx
|
||||||
sleep 3
|
sleep 3
|
||||||
docker ps -a
|
docker ps -a
|
||||||
|
|
||||||
|
|
|
@ -3,31 +3,33 @@
|
||||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
||||||
|
|
||||||
import Server from "./lib/Server";
|
import Server from "./lib/Server";
|
||||||
|
import { GlobalDependencies } from "../types/Dependencies";
|
||||||
const YAML = require("yamljs");
|
const YAML = require("yamljs");
|
||||||
|
|
||||||
const config_path = process.argv[2];
|
const configurationFilepath = process.argv[2];
|
||||||
if (!config_path) {
|
if (!configurationFilepath) {
|
||||||
console.log("No config file has been provided.");
|
console.log("No config file has been provided.");
|
||||||
console.log("Usage: authelia <config>");
|
console.log("Usage: authelia <config>");
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Parse configuration file: %s", config_path);
|
console.log("Parse configuration file: %s", configurationFilepath);
|
||||||
|
|
||||||
const yaml_config = YAML.load(config_path);
|
const yamlContent = YAML.load(configurationFilepath);
|
||||||
|
|
||||||
const deps = {
|
const deps: GlobalDependencies = {
|
||||||
u2f: require("u2f"),
|
u2f: require("u2f"),
|
||||||
nodemailer: require("nodemailer"),
|
nodemailer: require("nodemailer"),
|
||||||
ldapjs: require("ldapjs"),
|
ldapjs: require("ldapjs"),
|
||||||
session: require("express-session"),
|
session: require("express-session"),
|
||||||
winston: require("winston"),
|
winston: require("winston"),
|
||||||
speakeasy: require("speakeasy"),
|
speakeasy: require("speakeasy"),
|
||||||
nedb: require("nedb")
|
nedb: require("nedb"),
|
||||||
|
ConnectRedis: require("connect-redis")
|
||||||
};
|
};
|
||||||
|
|
||||||
const server = new Server();
|
const server = new Server();
|
||||||
server.start(yaml_config, deps)
|
server.start(yamlContent, deps)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log("The server is started!");
|
console.log("The server is started!");
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
|
||||||
import * as ObjectPath from "object-path";
|
import * as ObjectPath from "object-path";
|
||||||
import { AppConfiguration, UserConfiguration, NotifierConfiguration, ACLConfiguration, LdapConfiguration } from "./../../types/Configuration";
|
import { AppConfiguration, UserConfiguration, NotifierConfiguration, ACLConfiguration, LdapConfiguration, SessionRedisOptions } from "./../../types/Configuration";
|
||||||
|
|
||||||
const LDAP_URL_ENV_VARIABLE = "LDAP_URL";
|
const LDAP_URL_ENV_VARIABLE = "LDAP_URL";
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@ function adaptFromUserConfiguration(userConfiguration: UserConfiguration): AppCo
|
||||||
domain: ObjectPath.get<object, string>(userConfiguration, "session.domain"),
|
domain: ObjectPath.get<object, string>(userConfiguration, "session.domain"),
|
||||||
secret: ObjectPath.get<object, string>(userConfiguration, "session.secret"),
|
secret: ObjectPath.get<object, string>(userConfiguration, "session.secret"),
|
||||||
expiration: get_optional<number>(userConfiguration, "session.expiration", 3600000), // in ms
|
expiration: get_optional<number>(userConfiguration, "session.expiration", 3600000), // in ms
|
||||||
|
redis: ObjectPath.get<object, SessionRedisOptions>(userConfiguration, "session.redis")
|
||||||
},
|
},
|
||||||
store_directory: get_optional<string>(userConfiguration, "store_directory", undefined),
|
store_directory: get_optional<string>(userConfiguration, "store_directory", undefined),
|
||||||
logs_level: get_optional<string>(userConfiguration, "logs_level", "info"),
|
logs_level: get_optional<string>(userConfiguration, "logs_level", "info"),
|
||||||
|
|
|
@ -5,12 +5,13 @@ import { GlobalDependencies } from "../../types/Dependencies";
|
||||||
import { AuthenticationRegulator } from "./AuthenticationRegulator";
|
import { AuthenticationRegulator } from "./AuthenticationRegulator";
|
||||||
import UserDataStore from "./UserDataStore";
|
import UserDataStore from "./UserDataStore";
|
||||||
import ConfigurationAdapter from "./ConfigurationAdapter";
|
import ConfigurationAdapter from "./ConfigurationAdapter";
|
||||||
import { TOTPValidator } from "./TOTPValidator";
|
import { TOTPValidator } from "./TOTPValidator";
|
||||||
import { TOTPGenerator } from "./TOTPGenerator";
|
import { TOTPGenerator } from "./TOTPGenerator";
|
||||||
import RestApi from "./RestApi";
|
import RestApi from "./RestApi";
|
||||||
import { LdapClient } from "./LdapClient";
|
import { LdapClient } from "./LdapClient";
|
||||||
import BluebirdPromise = require("bluebird");
|
import BluebirdPromise = require("bluebird");
|
||||||
import ServerVariables = require("./ServerVariables");
|
import ServerVariables = require("./ServerVariables");
|
||||||
|
import SessionConfigurationBuilder from "./SessionConfigurationBuilder";
|
||||||
|
|
||||||
import * as Express from "express";
|
import * as Express from "express";
|
||||||
import * as BodyParser from "body-parser";
|
import * as BodyParser from "body-parser";
|
||||||
|
@ -20,40 +21,32 @@ import * as http from "http";
|
||||||
export default class Server {
|
export default class Server {
|
||||||
private httpServer: http.Server;
|
private httpServer: http.Server;
|
||||||
|
|
||||||
start(yaml_configuration: UserConfiguration, deps: GlobalDependencies): BluebirdPromise<void> {
|
start(yamlConfiguration: UserConfiguration, deps: GlobalDependencies): BluebirdPromise<void> {
|
||||||
const config = ConfigurationAdapter.adapt(yaml_configuration);
|
const config = ConfigurationAdapter.adapt(yamlConfiguration);
|
||||||
|
|
||||||
const view_directory = Path.resolve(__dirname, "../views");
|
const viewsDirectory = Path.resolve(__dirname, "../views");
|
||||||
const public_html_directory = Path.resolve(__dirname, "../public_html");
|
const publicHtmlDirectory = Path.resolve(__dirname, "../public_html");
|
||||||
|
|
||||||
|
const expressSessionOptions = SessionConfigurationBuilder.build(config, deps);
|
||||||
|
|
||||||
const app = Express();
|
const app = Express();
|
||||||
app.use(Express.static(public_html_directory));
|
app.use(Express.static(publicHtmlDirectory));
|
||||||
app.use(BodyParser.urlencoded({ extended: false }));
|
app.use(BodyParser.urlencoded({ extended: false }));
|
||||||
app.use(BodyParser.json());
|
app.use(BodyParser.json());
|
||||||
|
app.use(deps.session(expressSessionOptions));
|
||||||
|
|
||||||
app.set("trust proxy", 1); // trust first proxy
|
app.set("trust proxy", 1);
|
||||||
|
app.set("views", viewsDirectory);
|
||||||
app.use(deps.session({
|
|
||||||
secret: config.session.secret,
|
|
||||||
resave: false,
|
|
||||||
saveUninitialized: true,
|
|
||||||
cookie: {
|
|
||||||
secure: false,
|
|
||||||
maxAge: config.session.expiration,
|
|
||||||
domain: config.session.domain
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
app.set("views", view_directory);
|
|
||||||
app.set("view engine", "pug");
|
app.set("view engine", "pug");
|
||||||
|
|
||||||
// by default the level of logs is info
|
// by default the level of logs is info
|
||||||
deps.winston.level = config.logs_level;
|
deps.winston.level = config.logs_level;
|
||||||
console.log("Log level = ", deps.winston.level);
|
console.log("Log level = ", deps.winston.level);
|
||||||
|
|
||||||
|
deps.winston.debug("Content of YAML configuration file is %s", JSON.stringify(yamlConfiguration, undefined, 2));
|
||||||
deps.winston.debug("Authelia configuration is %s", JSON.stringify(config, undefined, 2));
|
deps.winston.debug("Authelia configuration is %s", JSON.stringify(config, undefined, 2));
|
||||||
|
|
||||||
ServerVariables.fill(app, config, deps);
|
ServerVariables.fill(app, config, deps);
|
||||||
|
|
||||||
RestApi.setup(app);
|
RestApi.setup(app);
|
||||||
|
|
||||||
return new BluebirdPromise<void>((resolve, reject) => {
|
return new BluebirdPromise<void>((resolve, reject) => {
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
|
||||||
|
import ExpressSession = require("express-session");
|
||||||
|
import { AppConfiguration } from "../../types/Configuration";
|
||||||
|
import { GlobalDependencies } from "../../types/Dependencies";
|
||||||
|
|
||||||
|
export default class SessionConfigurationBuilder {
|
||||||
|
|
||||||
|
static build(configuration: AppConfiguration, deps: GlobalDependencies): ExpressSession.SessionOptions {
|
||||||
|
const sessionOptions: ExpressSession.SessionOptions = {
|
||||||
|
secret: configuration.session.secret,
|
||||||
|
resave: false,
|
||||||
|
saveUninitialized: true,
|
||||||
|
cookie: {
|
||||||
|
secure: false,
|
||||||
|
maxAge: configuration.session.expiration,
|
||||||
|
domain: configuration.session.domain
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (configuration.session.redis) {
|
||||||
|
let redisOptions;
|
||||||
|
if (configuration.session.redis.host
|
||||||
|
&& configuration.session.redis.port) {
|
||||||
|
redisOptions = {
|
||||||
|
host: configuration.session.redis.host,
|
||||||
|
port: configuration.session.redis.port
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (redisOptions) {
|
||||||
|
const RedisStore = deps.ConnectRedis(deps.session);
|
||||||
|
sessionOptions.store = new RedisStore(redisOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sessionOptions;
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,10 +24,16 @@ export interface ACLConfiguration {
|
||||||
users: ACLUsersRules;
|
users: ACLUsersRules;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SessionRedisOptions {
|
||||||
|
host: string;
|
||||||
|
port: number;
|
||||||
|
}
|
||||||
|
|
||||||
interface SessionCookieConfiguration {
|
interface SessionCookieConfiguration {
|
||||||
secret: string;
|
secret: string;
|
||||||
expiration?: number;
|
expiration?: number;
|
||||||
domain?: string;
|
domain?: string;
|
||||||
|
redis?: SessionRedisOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GmailNotifierConfiguration {
|
export interface GmailNotifierConfiguration {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import session = require("express-session");
|
||||||
import nedb = require("nedb");
|
import nedb = require("nedb");
|
||||||
import ldapjs = require("ldapjs");
|
import ldapjs = require("ldapjs");
|
||||||
import u2f = require("u2f");
|
import u2f = require("u2f");
|
||||||
|
import RedisSession = require("connect-redis");
|
||||||
|
|
||||||
export type Nodemailer = typeof nodemailer;
|
export type Nodemailer = typeof nodemailer;
|
||||||
export type Speakeasy = typeof speakeasy;
|
export type Speakeasy = typeof speakeasy;
|
||||||
|
@ -13,12 +14,14 @@ export type Session = typeof session;
|
||||||
export type Nedb = typeof nedb;
|
export type Nedb = typeof nedb;
|
||||||
export type Ldapjs = typeof ldapjs;
|
export type Ldapjs = typeof ldapjs;
|
||||||
export type U2f = typeof u2f;
|
export type U2f = typeof u2f;
|
||||||
|
export type ConnectRedis = typeof RedisSession;
|
||||||
|
|
||||||
export interface GlobalDependencies {
|
export interface GlobalDependencies {
|
||||||
u2f: U2f;
|
u2f: U2f;
|
||||||
nodemailer: Nodemailer;
|
nodemailer: Nodemailer;
|
||||||
ldapjs: Ldapjs;
|
ldapjs: Ldapjs;
|
||||||
session: Session;
|
session: Session;
|
||||||
|
ConnectRedis: ConnectRedis;
|
||||||
winston: Winston;
|
winston: Winston;
|
||||||
speakeasy: Speakeasy;
|
speakeasy: Speakeasy;
|
||||||
nedb: Nedb;
|
nedb: Nedb;
|
||||||
|
|
|
@ -2,4 +2,3 @@ FROM node:7-alpine
|
||||||
|
|
||||||
WORKDIR /usr/src
|
WORKDIR /usr/src
|
||||||
|
|
||||||
CMD ["./node_modules/.bin/mocha", "--compilers", "ts:ts-node/register", "--recursive", "test/integration"]
|
|
||||||
|
|
|
@ -73,6 +73,9 @@ session:
|
||||||
secret: unsecure_secret
|
secret: unsecure_secret
|
||||||
expiration: 3600000
|
expiration: 3600000
|
||||||
domain: test.local
|
domain: test.local
|
||||||
|
redis:
|
||||||
|
host: redis
|
||||||
|
port: 6379
|
||||||
|
|
||||||
|
|
||||||
# The directory where the DB files will be saved
|
# The directory where the DB files will be saved
|
||||||
|
|
|
@ -11,6 +11,7 @@ services:
|
||||||
|
|
||||||
int-test:
|
int-test:
|
||||||
build: ./test/integration
|
build: ./test/integration
|
||||||
|
command: ./node_modules/.bin/mocha --compilers ts:ts-node/register --recursive test/integration
|
||||||
volumes:
|
volumes:
|
||||||
- ./:/usr/src
|
- ./:/usr/src
|
||||||
networks:
|
networks:
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
|
||||||
|
import Redis = require("redis");
|
||||||
|
import Assert = require("assert");
|
||||||
|
|
||||||
|
const redisOptions = {
|
||||||
|
host: "redis",
|
||||||
|
port: 6379
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("test redis is correctly used", function () {
|
||||||
|
let redisClient: Redis.RedisClient;
|
||||||
|
|
||||||
|
before(function () {
|
||||||
|
redisClient = Redis.createClient(redisOptions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should have registered at least one session", function (done) {
|
||||||
|
redisClient.dbsize(function (err: Error, count: number) {
|
||||||
|
Assert.equal(1, count);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -100,15 +100,16 @@ describe("test data persistence", function () {
|
||||||
sendMail: sinon.stub().yields()
|
sendMail: sinon.stub().yields()
|
||||||
};
|
};
|
||||||
|
|
||||||
const deps = {
|
const deps: GlobalDependencies = {
|
||||||
u2f: u2f,
|
u2f: u2f,
|
||||||
nedb: nedb,
|
nedb: nedb,
|
||||||
nodemailer: nodemailer,
|
nodemailer: nodemailer,
|
||||||
session: session,
|
session: session,
|
||||||
winston: winston,
|
winston: winston,
|
||||||
ldapjs: ldap,
|
ldapjs: ldap,
|
||||||
speakeasy: speakeasy
|
speakeasy: speakeasy,
|
||||||
} as GlobalDependencies;
|
ConnectRedis: sinon.spy()
|
||||||
|
};
|
||||||
|
|
||||||
const j1 = request.jar();
|
const j1 = request.jar();
|
||||||
const j2 = request.jar();
|
const j2 = request.jar();
|
||||||
|
|
|
@ -38,11 +38,12 @@ describe("test server configuration", function () {
|
||||||
createClient: sinon.spy(function () {
|
createClient: sinon.spy(function () {
|
||||||
return {
|
return {
|
||||||
on: sinon.spy(),
|
on: sinon.spy(),
|
||||||
bind: sinon.spy()
|
bind: sinon.spy(),
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
session: sessionMock as any
|
session: sessionMock as any,
|
||||||
|
ConnectRedis: sinon.spy()
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,131 @@
|
||||||
|
import SessionConfigurationBuilder from "../../src/server/lib/SessionConfigurationBuilder";
|
||||||
|
import { AppConfiguration } from "../../src/types/Configuration";
|
||||||
|
import { GlobalDependencies } from "../../src/types/Dependencies";
|
||||||
|
import ExpressSession = require("express-session");
|
||||||
|
import ConnectRedis = require("connect-redis");
|
||||||
|
import sinon = require("sinon");
|
||||||
|
import Assert = require("assert");
|
||||||
|
|
||||||
|
describe("test session configuration builder", function () {
|
||||||
|
it("should return session options without redis options", function () {
|
||||||
|
const configuration: AppConfiguration = {
|
||||||
|
access_control: {
|
||||||
|
default: [],
|
||||||
|
users: {},
|
||||||
|
groups: {}
|
||||||
|
},
|
||||||
|
ldap: {
|
||||||
|
url: "ldap://ldap",
|
||||||
|
base_dn: "dc=example,dc=com",
|
||||||
|
user: "user",
|
||||||
|
password: "password"
|
||||||
|
},
|
||||||
|
logs_level: "debug",
|
||||||
|
notifier: {
|
||||||
|
filesystem: {
|
||||||
|
filename: "/test"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
port: 8080,
|
||||||
|
session: {
|
||||||
|
domain: "example.com",
|
||||||
|
expiration: 3600,
|
||||||
|
secret: "secret"
|
||||||
|
},
|
||||||
|
store_in_memory: true
|
||||||
|
};
|
||||||
|
|
||||||
|
const deps: GlobalDependencies = {
|
||||||
|
ConnectRedis: sinon.spy() as any,
|
||||||
|
ldapjs: sinon.spy() as any,
|
||||||
|
nedb: sinon.spy() as any,
|
||||||
|
nodemailer: sinon.spy() as any,
|
||||||
|
session: sinon.spy() as any,
|
||||||
|
speakeasy: sinon.spy() as any,
|
||||||
|
u2f: sinon.spy() as any,
|
||||||
|
winston: sinon.spy() as any
|
||||||
|
};
|
||||||
|
|
||||||
|
const options = SessionConfigurationBuilder.build(configuration, deps);
|
||||||
|
|
||||||
|
const expectedOptions = {
|
||||||
|
secret: "secret",
|
||||||
|
resave: false,
|
||||||
|
saveUninitialized: true,
|
||||||
|
cookie: {
|
||||||
|
secure: false,
|
||||||
|
maxAge: 3600,
|
||||||
|
domain: "example.com"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Assert.deepEqual(expectedOptions, options);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return session options with redis options", function () {
|
||||||
|
const configuration: AppConfiguration = {
|
||||||
|
access_control: {
|
||||||
|
default: [],
|
||||||
|
users: {},
|
||||||
|
groups: {}
|
||||||
|
},
|
||||||
|
ldap: {
|
||||||
|
url: "ldap://ldap",
|
||||||
|
base_dn: "dc=example,dc=com",
|
||||||
|
user: "user",
|
||||||
|
password: "password"
|
||||||
|
},
|
||||||
|
logs_level: "debug",
|
||||||
|
notifier: {
|
||||||
|
filesystem: {
|
||||||
|
filename: "/test"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
port: 8080,
|
||||||
|
session: {
|
||||||
|
domain: "example.com",
|
||||||
|
expiration: 3600,
|
||||||
|
secret: "secret",
|
||||||
|
redis: {
|
||||||
|
host: "redis.example.com",
|
||||||
|
port: 6379
|
||||||
|
}
|
||||||
|
},
|
||||||
|
store_in_memory: true
|
||||||
|
};
|
||||||
|
|
||||||
|
const RedisStoreMock = sinon.spy();
|
||||||
|
|
||||||
|
const deps: GlobalDependencies = {
|
||||||
|
ConnectRedis: sinon.stub().returns(RedisStoreMock) as any,
|
||||||
|
ldapjs: sinon.spy() as any,
|
||||||
|
nedb: sinon.spy() as any,
|
||||||
|
nodemailer: sinon.spy() as any,
|
||||||
|
session: sinon.spy() as any,
|
||||||
|
speakeasy: sinon.spy() as any,
|
||||||
|
u2f: sinon.spy() as any,
|
||||||
|
winston: sinon.spy() as any
|
||||||
|
};
|
||||||
|
|
||||||
|
const options = SessionConfigurationBuilder.build(configuration, deps);
|
||||||
|
|
||||||
|
const expectedOptions: ExpressSession.SessionOptions = {
|
||||||
|
secret: "secret",
|
||||||
|
resave: false,
|
||||||
|
saveUninitialized: true,
|
||||||
|
cookie: {
|
||||||
|
secure: false,
|
||||||
|
maxAge: 3600,
|
||||||
|
domain: "example.com"
|
||||||
|
},
|
||||||
|
store: sinon.match.object as any
|
||||||
|
};
|
||||||
|
|
||||||
|
Assert((deps.ConnectRedis as sinon.SinonStub).calledWith(deps.session));
|
||||||
|
Assert.equal(options.secret, expectedOptions.secret);
|
||||||
|
Assert.equal(options.resave, expectedOptions.resave);
|
||||||
|
Assert.equal(options.saveUninitialized, expectedOptions.saveUninitialized);
|
||||||
|
Assert.deepEqual(options.cookie, expectedOptions.cookie);
|
||||||
|
Assert(options.store != undefined);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,177 @@
|
||||||
|
|
||||||
|
import Server from "../../../src/server/lib/Server";
|
||||||
|
import LdapClient = require("../../../src/server/lib/LdapClient");
|
||||||
|
|
||||||
|
import BluebirdPromise = require("bluebird");
|
||||||
|
import speakeasy = require("speakeasy");
|
||||||
|
import request = require("request");
|
||||||
|
import nedb = require("nedb");
|
||||||
|
import { GlobalDependencies } from "../../../src/types/Dependencies";
|
||||||
|
import { TOTPSecret } from "../../../src/types/TOTPSecret";
|
||||||
|
import U2FMock = require("./../mocks/u2f");
|
||||||
|
import Endpoints = require("../../../src/server/endpoints");
|
||||||
|
import Requests = require("../requests");
|
||||||
|
import Assert = require("assert");
|
||||||
|
import Sinon = require("sinon");
|
||||||
|
import Winston = require("winston");
|
||||||
|
import MockDate = require("mockdate");
|
||||||
|
import ExpressSession = require("express-session");
|
||||||
|
import ldapjs = require("ldapjs");
|
||||||
|
|
||||||
|
const requestp = BluebirdPromise.promisifyAll(request) as typeof request;
|
||||||
|
|
||||||
|
const PORT = 8090;
|
||||||
|
const BASE_URL = "http://localhost:" + PORT;
|
||||||
|
const requests = Requests(PORT);
|
||||||
|
|
||||||
|
describe("Private pages of the server must not be accessible without session", function () {
|
||||||
|
let server: Server;
|
||||||
|
let transporter: any;
|
||||||
|
let u2f: U2FMock.U2FMock;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
const config = {
|
||||||
|
port: PORT,
|
||||||
|
ldap: {
|
||||||
|
url: "ldap://127.0.0.1:389",
|
||||||
|
base_dn: "ou=users,dc=example,dc=com",
|
||||||
|
user_name_attribute: "cn",
|
||||||
|
user: "cn=admin,dc=example,dc=com",
|
||||||
|
password: "password",
|
||||||
|
},
|
||||||
|
session: {
|
||||||
|
secret: "session_secret",
|
||||||
|
expiration: 50000,
|
||||||
|
},
|
||||||
|
store_in_memory: true,
|
||||||
|
notifier: {
|
||||||
|
gmail: {
|
||||||
|
username: "user@example.com",
|
||||||
|
password: "password"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ldap_client = {
|
||||||
|
bind: Sinon.stub(),
|
||||||
|
search: Sinon.stub(),
|
||||||
|
modify: Sinon.stub(),
|
||||||
|
on: Sinon.spy()
|
||||||
|
};
|
||||||
|
const ldap = {
|
||||||
|
Change: Sinon.spy(),
|
||||||
|
createClient: Sinon.spy(function () {
|
||||||
|
return ldap_client;
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
u2f = U2FMock.U2FMock();
|
||||||
|
|
||||||
|
transporter = {
|
||||||
|
sendMail: Sinon.stub().yields()
|
||||||
|
};
|
||||||
|
|
||||||
|
const nodemailer = {
|
||||||
|
createTransport: Sinon.spy(function () {
|
||||||
|
return transporter;
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
const ldap_document = {
|
||||||
|
object: {
|
||||||
|
mail: "test_ok@example.com",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const search_res = {
|
||||||
|
on: Sinon.spy(function (event: string, fn: (s: any) => void) {
|
||||||
|
if (event != "error") fn(ldap_document);
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
ldap_client.bind.withArgs("cn=test_ok,ou=users,dc=example,dc=com",
|
||||||
|
"password").yields(undefined);
|
||||||
|
ldap_client.bind.withArgs("cn=admin,dc=example,dc=com",
|
||||||
|
"password").yields(undefined);
|
||||||
|
|
||||||
|
ldap_client.bind.withArgs("cn=test_nok,ou=users,dc=example,dc=com",
|
||||||
|
"password").yields("error");
|
||||||
|
|
||||||
|
ldap_client.modify.yields(undefined);
|
||||||
|
ldap_client.search.yields(undefined, search_res);
|
||||||
|
|
||||||
|
const deps: GlobalDependencies = {
|
||||||
|
u2f: u2f,
|
||||||
|
nedb: nedb,
|
||||||
|
nodemailer: nodemailer,
|
||||||
|
ldapjs: ldap,
|
||||||
|
session: ExpressSession,
|
||||||
|
winston: Winston,
|
||||||
|
speakeasy: speakeasy,
|
||||||
|
ConnectRedis: Sinon.spy()
|
||||||
|
};
|
||||||
|
|
||||||
|
server = new Server();
|
||||||
|
return server.start(config, deps);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
server.stop();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Second factor endpoints must be protected if first factor is not validated", function () {
|
||||||
|
function should_post_and_reply_with(url: string, status_code: number): BluebirdPromise<void> {
|
||||||
|
return requestp.postAsync(url).then(function (response: request.RequestResponse) {
|
||||||
|
Assert.equal(response.statusCode, status_code);
|
||||||
|
return BluebirdPromise.resolve();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function should_get_and_reply_with(url: string, status_code: number): BluebirdPromise<void> {
|
||||||
|
return requestp.getAsync(url).then(function (response: request.RequestResponse) {
|
||||||
|
Assert.equal(response.statusCode, status_code);
|
||||||
|
return BluebirdPromise.resolve();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function should_post_and_reply_with_401(url: string): BluebirdPromise<void> {
|
||||||
|
return should_post_and_reply_with(url, 401);
|
||||||
|
}
|
||||||
|
function should_get_and_reply_with_401(url: string): BluebirdPromise<void> {
|
||||||
|
return should_get_and_reply_with(url, 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
it("should block " + Endpoints.SECOND_FACTOR_GET, function () {
|
||||||
|
return should_get_and_reply_with_401(BASE_URL + Endpoints.SECOND_FACTOR_GET);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should block " + Endpoints.SECOND_FACTOR_U2F_IDENTITY_START_GET, function () {
|
||||||
|
return should_get_and_reply_with_401(BASE_URL + Endpoints.SECOND_FACTOR_U2F_IDENTITY_START_GET);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should block " + Endpoints.SECOND_FACTOR_U2F_IDENTITY_FINISH_GET, function () {
|
||||||
|
return should_get_and_reply_with_401(BASE_URL + Endpoints.SECOND_FACTOR_U2F_IDENTITY_FINISH_GET + "?identity_token=dummy");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should block " + Endpoints.SECOND_FACTOR_U2F_REGISTER_REQUEST_GET, function () {
|
||||||
|
return should_get_and_reply_with_401(BASE_URL + Endpoints.SECOND_FACTOR_U2F_REGISTER_REQUEST_GET);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should block " + Endpoints.SECOND_FACTOR_U2F_REGISTER_POST, function () {
|
||||||
|
return should_post_and_reply_with_401(BASE_URL + Endpoints.SECOND_FACTOR_U2F_REGISTER_POST);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should block " + Endpoints.SECOND_FACTOR_U2F_SIGN_REQUEST_GET, function () {
|
||||||
|
return should_get_and_reply_with_401(BASE_URL + Endpoints.SECOND_FACTOR_U2F_SIGN_REQUEST_GET);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should block " + Endpoints.SECOND_FACTOR_U2F_SIGN_POST, function () {
|
||||||
|
return should_post_and_reply_with_401(BASE_URL + Endpoints.SECOND_FACTOR_U2F_SIGN_POST);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should block " + Endpoints.SECOND_FACTOR_TOTP_POST, function () {
|
||||||
|
return should_post_and_reply_with_401(BASE_URL + Endpoints.SECOND_FACTOR_TOTP_POST);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -0,0 +1,165 @@
|
||||||
|
|
||||||
|
import Server from "../../../src/server/lib/Server";
|
||||||
|
import LdapClient = require("../../../src/server/lib/LdapClient");
|
||||||
|
|
||||||
|
import BluebirdPromise = require("bluebird");
|
||||||
|
import speakeasy = require("speakeasy");
|
||||||
|
import Request = require("request");
|
||||||
|
import nedb = require("nedb");
|
||||||
|
import { GlobalDependencies } from "../../../src/types/Dependencies";
|
||||||
|
import { TOTPSecret } from "../../../src/types/TOTPSecret";
|
||||||
|
import U2FMock = require("./../mocks/u2f");
|
||||||
|
import Endpoints = require("../../../src/server/endpoints");
|
||||||
|
import Requests = require("../requests");
|
||||||
|
import Assert = require("assert");
|
||||||
|
import Sinon = require("sinon");
|
||||||
|
import Winston = require("winston");
|
||||||
|
import MockDate = require("mockdate");
|
||||||
|
import ExpressSession = require("express-session");
|
||||||
|
import ldapjs = require("ldapjs");
|
||||||
|
|
||||||
|
const requestp = BluebirdPromise.promisifyAll(Request) as typeof Request;
|
||||||
|
|
||||||
|
const PORT = 8090;
|
||||||
|
const BASE_URL = "http://localhost:" + PORT;
|
||||||
|
const requests = Requests(PORT);
|
||||||
|
|
||||||
|
describe("Public pages of the server must be accessible without session", function () {
|
||||||
|
let server: Server;
|
||||||
|
let transporter: object;
|
||||||
|
let u2f: U2FMock.U2FMock;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
const config = {
|
||||||
|
port: PORT,
|
||||||
|
ldap: {
|
||||||
|
url: "ldap://127.0.0.1:389",
|
||||||
|
base_dn: "ou=users,dc=example,dc=com",
|
||||||
|
user_name_attribute: "cn",
|
||||||
|
user: "cn=admin,dc=example,dc=com",
|
||||||
|
password: "password",
|
||||||
|
},
|
||||||
|
session: {
|
||||||
|
secret: "session_secret",
|
||||||
|
expiration: 50000,
|
||||||
|
},
|
||||||
|
store_in_memory: true,
|
||||||
|
notifier: {
|
||||||
|
gmail: {
|
||||||
|
username: "user@example.com",
|
||||||
|
password: "password"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ldap_client = {
|
||||||
|
bind: Sinon.stub(),
|
||||||
|
search: Sinon.stub(),
|
||||||
|
modify: Sinon.stub(),
|
||||||
|
on: Sinon.spy()
|
||||||
|
};
|
||||||
|
const ldap = {
|
||||||
|
Change: Sinon.spy(),
|
||||||
|
createClient: Sinon.spy(function () {
|
||||||
|
return ldap_client;
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
u2f = U2FMock.U2FMock();
|
||||||
|
|
||||||
|
transporter = {
|
||||||
|
sendMail: Sinon.stub().yields()
|
||||||
|
};
|
||||||
|
|
||||||
|
const nodemailer = {
|
||||||
|
createTransport: Sinon.spy(function () {
|
||||||
|
return transporter;
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
const ldap_document = {
|
||||||
|
object: {
|
||||||
|
mail: "test_ok@example.com",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const search_res = {
|
||||||
|
on: Sinon.spy(function (event: string, fn: (s: any) => void) {
|
||||||
|
if (event != "error") fn(ldap_document);
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
ldap_client.bind.withArgs("cn=test_ok,ou=users,dc=example,dc=com",
|
||||||
|
"password").yields(undefined);
|
||||||
|
ldap_client.bind.withArgs("cn=admin,dc=example,dc=com",
|
||||||
|
"password").yields(undefined);
|
||||||
|
|
||||||
|
ldap_client.bind.withArgs("cn=test_nok,ou=users,dc=example,dc=com",
|
||||||
|
"password").yields("error");
|
||||||
|
|
||||||
|
ldap_client.modify.yields(undefined);
|
||||||
|
ldap_client.search.yields(undefined, search_res);
|
||||||
|
|
||||||
|
const deps: GlobalDependencies = {
|
||||||
|
u2f: u2f,
|
||||||
|
nedb: nedb,
|
||||||
|
nodemailer: nodemailer,
|
||||||
|
ldapjs: ldap,
|
||||||
|
session: ExpressSession,
|
||||||
|
winston: Winston,
|
||||||
|
speakeasy: speakeasy,
|
||||||
|
ConnectRedis: Sinon.spy()
|
||||||
|
};
|
||||||
|
|
||||||
|
server = new Server();
|
||||||
|
return server.start(config, deps);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
server.stop();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("test GET " + Endpoints.FIRST_FACTOR_GET, function () {
|
||||||
|
test_login();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("test GET " + Endpoints.LOGOUT_GET, function () {
|
||||||
|
test_logout();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("test GET" + Endpoints.RESET_PASSWORD_REQUEST_GET, function () {
|
||||||
|
test_reset_password_form();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function test_reset_password_form() {
|
||||||
|
it("should serve the reset password form page", function (done) {
|
||||||
|
requestp.getAsync(BASE_URL + Endpoints.RESET_PASSWORD_REQUEST_GET)
|
||||||
|
.then(function (response: Request.RequestResponse) {
|
||||||
|
Assert.equal(response.statusCode, 200);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_login() {
|
||||||
|
it("should serve the login page", function (done) {
|
||||||
|
requestp.getAsync(BASE_URL + Endpoints.FIRST_FACTOR_GET)
|
||||||
|
.then(function (response: Request.RequestResponse) {
|
||||||
|
Assert.equal(response.statusCode, 200);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_logout() {
|
||||||
|
it("should logout and redirect to /", function (done) {
|
||||||
|
requestp.getAsync(BASE_URL + Endpoints.LOGOUT_GET)
|
||||||
|
.then(function (response: any) {
|
||||||
|
Assert.equal(response.req.path, "/");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
|
@ -1,32 +1,34 @@
|
||||||
|
|
||||||
import Server from "../../src/server/lib/Server";
|
|
||||||
import LdapClient = require("../../src/server/lib/LdapClient");
|
import Server from "../../../src/server/lib/Server";
|
||||||
import { LdapjsClientMock } from "./mocks/ldapjs";
|
import LdapClient = require("../../../src/server/lib/LdapClient");
|
||||||
|
import { LdapjsClientMock } from "./../mocks/ldapjs";
|
||||||
|
|
||||||
import BluebirdPromise = require("bluebird");
|
import BluebirdPromise = require("bluebird");
|
||||||
import speakeasy = require("speakeasy");
|
import speakeasy = require("speakeasy");
|
||||||
import request = require("request");
|
import request = require("request");
|
||||||
import nedb = require("nedb");
|
import nedb = require("nedb");
|
||||||
import { TOTPSecret } from "../../src/types/TOTPSecret";
|
import { GlobalDependencies } from "../../../src/types/Dependencies";
|
||||||
import U2FMock = require("./mocks/u2f");
|
import { TOTPSecret } from "../../../src/types/TOTPSecret";
|
||||||
import Endpoints = require("../../src/server/endpoints");
|
import U2FMock = require("./../mocks/u2f");
|
||||||
|
import Endpoints = require("../../../src/server/endpoints");
|
||||||
|
import Requests = require("../requests");
|
||||||
|
import Assert = require("assert");
|
||||||
|
import Sinon = require("sinon");
|
||||||
|
import Winston = require("winston");
|
||||||
|
import MockDate = require("mockdate");
|
||||||
|
import ExpressSession = require("express-session");
|
||||||
|
import ldapjs = require("ldapjs");
|
||||||
|
|
||||||
const requestp = BluebirdPromise.promisifyAll(request) as typeof request;
|
const requestp = BluebirdPromise.promisifyAll(request) as typeof request;
|
||||||
const assert = require("assert");
|
|
||||||
const sinon = require("sinon");
|
|
||||||
const MockDate = require("mockdate");
|
|
||||||
const session = require("express-session");
|
|
||||||
const winston = require("winston");
|
|
||||||
const ldapjs = require("ldapjs");
|
|
||||||
|
|
||||||
const PORT = 8090;
|
const PORT = 8090;
|
||||||
const BASE_URL = "http://localhost:" + PORT;
|
const BASE_URL = "http://localhost:" + PORT;
|
||||||
const requests = require("./requests")(PORT);
|
const requests = Requests(PORT);
|
||||||
|
|
||||||
describe("test the server", function () {
|
describe("test the server", function () {
|
||||||
let server: Server;
|
let server: Server;
|
||||||
let transporter: object;
|
let transporter: any;
|
||||||
let u2f: U2FMock.U2FMock;
|
let u2f: U2FMock.U2FMock;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
|
@ -54,8 +56,8 @@ describe("test the server", function () {
|
||||||
|
|
||||||
const ldapClient = LdapjsClientMock();
|
const ldapClient = LdapjsClientMock();
|
||||||
const ldap = {
|
const ldap = {
|
||||||
Change: sinon.spy(),
|
Change: Sinon.spy(),
|
||||||
createClient: sinon.spy(function () {
|
createClient: Sinon.spy(function () {
|
||||||
return ldapClient;
|
return ldapClient;
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
@ -63,11 +65,11 @@ describe("test the server", function () {
|
||||||
u2f = U2FMock.U2FMock();
|
u2f = U2FMock.U2FMock();
|
||||||
|
|
||||||
transporter = {
|
transporter = {
|
||||||
sendMail: sinon.stub().yields()
|
sendMail: Sinon.stub().yields()
|
||||||
};
|
};
|
||||||
|
|
||||||
const nodemailer = {
|
const nodemailer = {
|
||||||
createTransport: sinon.spy(function () {
|
createTransport: Sinon.spy(function () {
|
||||||
return transporter;
|
return transporter;
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
@ -79,7 +81,7 @@ describe("test the server", function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
const search_res = {
|
const search_res = {
|
||||||
on: sinon.spy(function (event: string, fn: (s: any) => void) {
|
on: Sinon.spy(function (event: string, fn: (s: any) => void) {
|
||||||
if (event != "error") fn(ldapDocument);
|
if (event != "error") fn(ldapDocument);
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
@ -96,14 +98,15 @@ describe("test the server", function () {
|
||||||
ldapClient.modify.yields();
|
ldapClient.modify.yields();
|
||||||
ldapClient.search.yields(undefined, search_res);
|
ldapClient.search.yields(undefined, search_res);
|
||||||
|
|
||||||
const deps = {
|
const deps: GlobalDependencies = {
|
||||||
u2f: u2f,
|
u2f: u2f,
|
||||||
nedb: nedb,
|
nedb: nedb,
|
||||||
nodemailer: nodemailer,
|
nodemailer: nodemailer,
|
||||||
ldapjs: ldap,
|
ldapjs: ldap,
|
||||||
session: session,
|
session: ExpressSession,
|
||||||
winston: winston,
|
winston: Winston,
|
||||||
speakeasy: speakeasy
|
speakeasy: speakeasy,
|
||||||
|
ConnectRedis: Sinon.spy()
|
||||||
};
|
};
|
||||||
|
|
||||||
server = new Server();
|
server = new Server();
|
||||||
|
@ -114,114 +117,17 @@ describe("test the server", function () {
|
||||||
server.stop();
|
server.stop();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("test GET " + Endpoints.FIRST_FACTOR_GET, function () {
|
|
||||||
test_login();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("test GET " + Endpoints.LOGOUT_GET, function () {
|
|
||||||
test_logout();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("test GET" + Endpoints.RESET_PASSWORD_REQUEST_GET, function () {
|
|
||||||
test_reset_password_form();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Second factor endpoints must be protected if first factor is not validated", function () {
|
|
||||||
function should_post_and_reply_with(url: string, status_code: number): BluebirdPromise<void> {
|
|
||||||
return requestp.postAsync(url).then(function (response: request.RequestResponse) {
|
|
||||||
assert.equal(response.statusCode, status_code);
|
|
||||||
return BluebirdPromise.resolve();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function should_get_and_reply_with(url: string, status_code: number): BluebirdPromise<void> {
|
|
||||||
return requestp.getAsync(url).then(function (response: request.RequestResponse) {
|
|
||||||
assert.equal(response.statusCode, status_code);
|
|
||||||
return BluebirdPromise.resolve();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function should_post_and_reply_with_401(url: string): BluebirdPromise<void> {
|
|
||||||
return should_post_and_reply_with(url, 401);
|
|
||||||
}
|
|
||||||
function should_get_and_reply_with_401(url: string): BluebirdPromise<void> {
|
|
||||||
return should_get_and_reply_with(url, 401);
|
|
||||||
}
|
|
||||||
|
|
||||||
it("should block " + Endpoints.SECOND_FACTOR_GET, function () {
|
|
||||||
return should_get_and_reply_with_401(BASE_URL + Endpoints.SECOND_FACTOR_GET);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should block " + Endpoints.SECOND_FACTOR_U2F_IDENTITY_START_GET, function () {
|
|
||||||
return should_get_and_reply_with_401(BASE_URL + Endpoints.SECOND_FACTOR_U2F_IDENTITY_START_GET);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should block " + Endpoints.SECOND_FACTOR_U2F_IDENTITY_FINISH_GET, function () {
|
|
||||||
return should_get_and_reply_with_401(BASE_URL + Endpoints.SECOND_FACTOR_U2F_IDENTITY_FINISH_GET + "?identity_token=dummy");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should block " + Endpoints.SECOND_FACTOR_U2F_REGISTER_REQUEST_GET, function () {
|
|
||||||
return should_get_and_reply_with_401(BASE_URL + Endpoints.SECOND_FACTOR_U2F_REGISTER_REQUEST_GET);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should block " + Endpoints.SECOND_FACTOR_U2F_REGISTER_POST, function () {
|
|
||||||
return should_post_and_reply_with_401(BASE_URL + Endpoints.SECOND_FACTOR_U2F_REGISTER_POST);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should block " + Endpoints.SECOND_FACTOR_U2F_SIGN_REQUEST_GET, function () {
|
|
||||||
return should_get_and_reply_with_401(BASE_URL + Endpoints.SECOND_FACTOR_U2F_SIGN_REQUEST_GET);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should block " + Endpoints.SECOND_FACTOR_U2F_SIGN_POST, function () {
|
|
||||||
return should_post_and_reply_with_401(BASE_URL + Endpoints.SECOND_FACTOR_U2F_SIGN_POST);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should block " + Endpoints.SECOND_FACTOR_TOTP_POST, function () {
|
|
||||||
return should_post_and_reply_with_401(BASE_URL + Endpoints.SECOND_FACTOR_TOTP_POST);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("test authentication and verification", function () {
|
describe("test authentication and verification", function () {
|
||||||
test_authentication();
|
test_authentication();
|
||||||
test_reset_password();
|
test_reset_password();
|
||||||
test_regulation();
|
test_regulation();
|
||||||
});
|
});
|
||||||
|
|
||||||
function test_reset_password_form() {
|
|
||||||
it("should serve the reset password form page", function (done) {
|
|
||||||
requestp.getAsync(BASE_URL + Endpoints.RESET_PASSWORD_REQUEST_GET)
|
|
||||||
.then(function (response: request.RequestResponse) {
|
|
||||||
assert.equal(response.statusCode, 200);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function test_login() {
|
|
||||||
it("should serve the login page", function (done) {
|
|
||||||
requestp.getAsync(BASE_URL + Endpoints.FIRST_FACTOR_GET)
|
|
||||||
.then(function (response: request.RequestResponse) {
|
|
||||||
assert.equal(response.statusCode, 200);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function test_logout() {
|
|
||||||
it("should logout and redirect to /", function (done) {
|
|
||||||
requestp.getAsync(BASE_URL + Endpoints.LOGOUT_GET)
|
|
||||||
.then(function (response: any) {
|
|
||||||
assert.equal(response.req.path, "/");
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function test_authentication() {
|
function test_authentication() {
|
||||||
it("should return status code 401 when user is not authenticated", function () {
|
it("should return status code 401 when user is not authenticated", function () {
|
||||||
return requestp.getAsync({ url: BASE_URL + Endpoints.VERIFY_GET })
|
return requestp.getAsync({ url: BASE_URL + Endpoints.VERIFY_GET })
|
||||||
.then(function (response: request.RequestResponse) {
|
.then(function (response: request.RequestResponse) {
|
||||||
assert.equal(response.statusCode, 401);
|
Assert.equal(response.statusCode, 401);
|
||||||
return BluebirdPromise.resolve();
|
return BluebirdPromise.resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -230,11 +136,11 @@ describe("test the server", function () {
|
||||||
const j = requestp.jar();
|
const j = requestp.jar();
|
||||||
return requests.login(j)
|
return requests.login(j)
|
||||||
.then(function (res: request.RequestResponse) {
|
.then(function (res: request.RequestResponse) {
|
||||||
assert.equal(res.statusCode, 200, "get login page failed");
|
Assert.equal(res.statusCode, 200, "get login page failed");
|
||||||
return requests.first_factor(j);
|
return requests.first_factor(j);
|
||||||
})
|
})
|
||||||
.then(function (res: request.RequestResponse) {
|
.then(function (res: request.RequestResponse) {
|
||||||
assert.equal(res.statusCode, 302, "first factor failed");
|
Assert.equal(res.statusCode, 302, "first factor failed");
|
||||||
return requests.register_totp(j, transporter);
|
return requests.register_totp(j, transporter);
|
||||||
})
|
})
|
||||||
.then(function (base32_secret: string) {
|
.then(function (base32_secret: string) {
|
||||||
|
@ -245,11 +151,11 @@ describe("test the server", function () {
|
||||||
return requests.totp(j, realToken);
|
return requests.totp(j, realToken);
|
||||||
})
|
})
|
||||||
.then(function (res: request.RequestResponse) {
|
.then(function (res: request.RequestResponse) {
|
||||||
assert.equal(res.statusCode, 200, "second factor failed");
|
Assert.equal(res.statusCode, 200, "second factor failed");
|
||||||
return requests.verify(j);
|
return requests.verify(j);
|
||||||
})
|
})
|
||||||
.then(function (res: request.RequestResponse) {
|
.then(function (res: request.RequestResponse) {
|
||||||
assert.equal(res.statusCode, 204, "verify failed");
|
Assert.equal(res.statusCode, 204, "verify failed");
|
||||||
return BluebirdPromise.resolve();
|
return BluebirdPromise.resolve();
|
||||||
})
|
})
|
||||||
.catch(function (err: Error) { return BluebirdPromise.reject(err); });
|
.catch(function (err: Error) { return BluebirdPromise.reject(err); });
|
||||||
|
@ -259,11 +165,11 @@ describe("test the server", function () {
|
||||||
const j = requestp.jar();
|
const j = requestp.jar();
|
||||||
return requests.login(j)
|
return requests.login(j)
|
||||||
.then(function (res: request.RequestResponse) {
|
.then(function (res: request.RequestResponse) {
|
||||||
assert.equal(res.statusCode, 200, "get login page failed");
|
Assert.equal(res.statusCode, 200, "get login page failed");
|
||||||
return requests.first_factor(j);
|
return requests.first_factor(j);
|
||||||
})
|
})
|
||||||
.then(function (res: request.RequestResponse) {
|
.then(function (res: request.RequestResponse) {
|
||||||
assert.equal(res.statusCode, 302, "first factor failed");
|
Assert.equal(res.statusCode, 302, "first factor failed");
|
||||||
return requests.register_totp(j, transporter);
|
return requests.register_totp(j, transporter);
|
||||||
})
|
})
|
||||||
.then(function (base32_secret: string) {
|
.then(function (base32_secret: string) {
|
||||||
|
@ -274,15 +180,15 @@ describe("test the server", function () {
|
||||||
return requests.totp(j, realToken);
|
return requests.totp(j, realToken);
|
||||||
})
|
})
|
||||||
.then(function (res: request.RequestResponse) {
|
.then(function (res: request.RequestResponse) {
|
||||||
assert.equal(res.statusCode, 200, "second factor failed");
|
Assert.equal(res.statusCode, 200, "second factor failed");
|
||||||
return requests.login(j);
|
return requests.login(j);
|
||||||
})
|
})
|
||||||
.then(function (res: request.RequestResponse) {
|
.then(function (res: request.RequestResponse) {
|
||||||
assert.equal(res.statusCode, 200, "login page loading failed");
|
Assert.equal(res.statusCode, 200, "login page loading failed");
|
||||||
return requests.verify(j);
|
return requests.verify(j);
|
||||||
})
|
})
|
||||||
.then(function (res: request.RequestResponse) {
|
.then(function (res: request.RequestResponse) {
|
||||||
assert.equal(res.statusCode, 204, "verify failed");
|
Assert.equal(res.statusCode, 204, "verify failed");
|
||||||
return BluebirdPromise.resolve();
|
return BluebirdPromise.resolve();
|
||||||
})
|
})
|
||||||
.catch(function (err: Error) { return BluebirdPromise.reject(err); });
|
.catch(function (err: Error) { return BluebirdPromise.reject(err); });
|
||||||
|
@ -300,25 +206,25 @@ describe("test the server", function () {
|
||||||
const j = requestp.jar();
|
const j = requestp.jar();
|
||||||
return requests.login(j)
|
return requests.login(j)
|
||||||
.then(function (res: request.RequestResponse) {
|
.then(function (res: request.RequestResponse) {
|
||||||
assert.equal(res.statusCode, 200, "get login page failed");
|
Assert.equal(res.statusCode, 200, "get login page failed");
|
||||||
return requests.first_factor(j);
|
return requests.first_factor(j);
|
||||||
})
|
})
|
||||||
.then(function (res: request.RequestResponse) {
|
.then(function (res: request.RequestResponse) {
|
||||||
// console.log(res);
|
// console.log(res);
|
||||||
assert.equal(res.headers.location, Endpoints.SECOND_FACTOR_GET);
|
Assert.equal(res.headers.location, Endpoints.SECOND_FACTOR_GET);
|
||||||
assert.equal(res.statusCode, 302, "first factor failed");
|
Assert.equal(res.statusCode, 302, "first factor failed");
|
||||||
return requests.u2f_registration(j, transporter);
|
return requests.u2f_registration(j, transporter);
|
||||||
})
|
})
|
||||||
.then(function (res: request.RequestResponse) {
|
.then(function (res: request.RequestResponse) {
|
||||||
assert.equal(res.statusCode, 200, "second factor, finish register failed");
|
Assert.equal(res.statusCode, 200, "second factor, finish register failed");
|
||||||
return requests.u2f_authentication(j);
|
return requests.u2f_authentication(j);
|
||||||
})
|
})
|
||||||
.then(function (res: request.RequestResponse) {
|
.then(function (res: request.RequestResponse) {
|
||||||
assert.equal(res.statusCode, 200, "second factor, finish sign failed");
|
Assert.equal(res.statusCode, 200, "second factor, finish sign failed");
|
||||||
return requests.verify(j);
|
return requests.verify(j);
|
||||||
})
|
})
|
||||||
.then(function (res: request.RequestResponse) {
|
.then(function (res: request.RequestResponse) {
|
||||||
assert.equal(res.statusCode, 204, "verify failed");
|
Assert.equal(res.statusCode, 204, "verify failed");
|
||||||
return BluebirdPromise.resolve();
|
return BluebirdPromise.resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -329,16 +235,16 @@ describe("test the server", function () {
|
||||||
const j = requestp.jar();
|
const j = requestp.jar();
|
||||||
return requests.login(j)
|
return requests.login(j)
|
||||||
.then(function (res: request.RequestResponse) {
|
.then(function (res: request.RequestResponse) {
|
||||||
assert.equal(res.statusCode, 200, "get login page failed");
|
Assert.equal(res.statusCode, 200, "get login page failed");
|
||||||
return requests.first_factor(j);
|
return requests.first_factor(j);
|
||||||
})
|
})
|
||||||
.then(function (res: request.RequestResponse) {
|
.then(function (res: request.RequestResponse) {
|
||||||
assert.equal(res.headers.location, Endpoints.SECOND_FACTOR_GET);
|
Assert.equal(res.headers.location, Endpoints.SECOND_FACTOR_GET);
|
||||||
assert.equal(res.statusCode, 302, "first factor failed");
|
Assert.equal(res.statusCode, 302, "first factor failed");
|
||||||
return requests.reset_password(j, transporter, "user", "new-password");
|
return requests.reset_password(j, transporter, "user", "new-password");
|
||||||
})
|
})
|
||||||
.then(function (res: request.RequestResponse) {
|
.then(function (res: request.RequestResponse) {
|
||||||
assert.equal(res.statusCode, 204, "second factor, finish register failed");
|
Assert.equal(res.statusCode, 204, "second factor, finish register failed");
|
||||||
return BluebirdPromise.resolve();
|
return BluebirdPromise.resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -350,28 +256,28 @@ describe("test the server", function () {
|
||||||
MockDate.set("1/2/2017 00:00:00");
|
MockDate.set("1/2/2017 00:00:00");
|
||||||
return requests.login(j)
|
return requests.login(j)
|
||||||
.then(function (res: request.RequestResponse) {
|
.then(function (res: request.RequestResponse) {
|
||||||
assert.equal(res.statusCode, 200, "get login page failed");
|
Assert.equal(res.statusCode, 200, "get login page failed");
|
||||||
return requests.failing_first_factor(j);
|
return requests.failing_first_factor(j);
|
||||||
})
|
})
|
||||||
.then(function (res: request.RequestResponse) {
|
.then(function (res: request.RequestResponse) {
|
||||||
assert.equal(res.statusCode, 401, "first factor failed");
|
Assert.equal(res.statusCode, 401, "first factor failed");
|
||||||
return requests.failing_first_factor(j);
|
return requests.failing_first_factor(j);
|
||||||
})
|
})
|
||||||
.then(function (res: request.RequestResponse) {
|
.then(function (res: request.RequestResponse) {
|
||||||
assert.equal(res.statusCode, 401, "first factor failed");
|
Assert.equal(res.statusCode, 401, "first factor failed");
|
||||||
return requests.failing_first_factor(j);
|
return requests.failing_first_factor(j);
|
||||||
})
|
})
|
||||||
.then(function (res: request.RequestResponse) {
|
.then(function (res: request.RequestResponse) {
|
||||||
assert.equal(res.statusCode, 401, "first factor failed");
|
Assert.equal(res.statusCode, 401, "first factor failed");
|
||||||
return requests.failing_first_factor(j);
|
return requests.failing_first_factor(j);
|
||||||
})
|
})
|
||||||
.then(function (res: request.RequestResponse) {
|
.then(function (res: request.RequestResponse) {
|
||||||
assert.equal(res.statusCode, 403, "first factor failed");
|
Assert.equal(res.statusCode, 403, "first factor failed");
|
||||||
MockDate.set("1/2/2017 00:30:00");
|
MockDate.set("1/2/2017 00:30:00");
|
||||||
return requests.failing_first_factor(j);
|
return requests.failing_first_factor(j);
|
||||||
})
|
})
|
||||||
.then(function (res: request.RequestResponse) {
|
.then(function (res: request.RequestResponse) {
|
||||||
assert.equal(res.statusCode, 401, "first factor failed");
|
Assert.equal(res.statusCode, 401, "first factor failed");
|
||||||
return BluebirdPromise.resolve();
|
return BluebirdPromise.resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
Loading…
Reference in New Issue