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']
|
||||
},
|
||||
"test": {
|
||||
cmd: "npm",
|
||||
args: ['run', 'test']
|
||||
cmd: "./node_modules/.bin/mocha",
|
||||
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": {
|
||||
cmd: "docker",
|
||||
args: ['build', '-t', 'clems4ever/authelia', '.']
|
||||
},
|
||||
"docker-restart": {
|
||||
cmd: "docker-compose",
|
||||
args: ['-f', 'docker-compose.yml', '-f', 'docker-compose.dev.yml', 'restart', 'auth']
|
||||
cmd: "./scripts/dc-example.sh",
|
||||
args: ['up', '-d']
|
||||
},
|
||||
"minify": {
|
||||
cmd: "./node_modules/.bin/uglifyjs",
|
||||
|
@ -109,7 +113,7 @@ module.exports = function (grunt) {
|
|||
},
|
||||
client: {
|
||||
files: ['src/client/**/*.ts', 'test/client/**/*.ts'],
|
||||
tasks: ['build'],
|
||||
tasks: ['build-dev'],
|
||||
options: {
|
||||
interrupt: true,
|
||||
atBegin: true
|
||||
|
@ -117,9 +121,10 @@ module.exports = function (grunt) {
|
|||
},
|
||||
server: {
|
||||
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: {
|
||||
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
|
||||
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
|
||||
**[TOTP] - Time-Base One Time password -** or **[U2F] - Universal 2-Factor -**
|
||||
as 2nd factor.
|
||||
* Password reset with identity verification by sending links to user email
|
||||
address.
|
||||
* Access restriction after too many authentication attempts.
|
||||
* Session management using Redis key/value store.
|
||||
|
||||
## 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 auth.test.local
|
||||
|
||||
### Deployment
|
||||
### Run it!
|
||||
|
||||
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">
|
||||
|
||||
### 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
|
||||
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">
|
||||
|
||||
|
||||
### Second factor: TOTP (Time-Base One Time Password)
|
||||
In **Authelia**, you need to register a per user TOTP secret before
|
||||
### Second factor with TOTP
|
||||
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
|
||||
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
|
||||
|
@ -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">
|
||||
|
||||
### 2nd factor: U2F (Universal 2-Factor) with security keys
|
||||
**Authelia** also offers authentication using U2F devices like [Yubikey](Yubikey)
|
||||
### Second factor with U2F security keys
|
||||
**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
|
||||
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.
|
||||
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
|
||||
### Configuration
|
||||
### Authelia configuration
|
||||
The configuration of the server is defined in the file
|
||||
**configuration.template.yml**. All the details are documented there.
|
||||
You can specify another configuration file by giving it as first argument of
|
||||
|
|
|
@ -73,7 +73,9 @@ session:
|
|||
secret: unsecure_secret
|
||||
expiration: 3600000
|
||||
domain: test.local
|
||||
|
||||
redis:
|
||||
host: redis
|
||||
port: 6379
|
||||
|
||||
# The directory where the DB files will be saved
|
||||
store_directory: /var/lib/authelia/store
|
||||
|
|
|
@ -6,6 +6,7 @@ services:
|
|||
volumes:
|
||||
- ./config.template.yml:/etc/authelia/config.yml:ro
|
||||
- ./notifications:/var/lib/authelia/notifications
|
||||
depends_on:
|
||||
- redis
|
||||
networks:
|
||||
- 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"
|
||||
},
|
||||
"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",
|
||||
"serve": "node dist/server/index.js"
|
||||
},
|
||||
|
@ -27,6 +27,7 @@
|
|||
"@types/cors": "^2.8.1",
|
||||
"bluebird": "^3.4.7",
|
||||
"body-parser": "^1.15.2",
|
||||
"connect-redis": "^3.3.0",
|
||||
"dovehash": "0.0.5",
|
||||
"ejs": "^2.5.5",
|
||||
"express": "^4.14.0",
|
||||
|
@ -45,6 +46,7 @@
|
|||
"devDependencies": {
|
||||
"@types/bluebird": "^3.5.4",
|
||||
"@types/body-parser": "^1.16.3",
|
||||
"@types/connect-redis": "0.0.6",
|
||||
"@types/ejs": "^2.3.33",
|
||||
"@types/express": "^4.0.35",
|
||||
"@types/express-session": "0.0.32",
|
||||
|
@ -59,7 +61,7 @@
|
|||
"@types/proxyquire": "^1.3.27",
|
||||
"@types/query-string": "^4.3.1",
|
||||
"@types/randomstring": "^1.1.5",
|
||||
"@types/request": "0.0.45",
|
||||
"@types/request": "0.0.46",
|
||||
"@types/sinon": "^2.2.1",
|
||||
"@types/speakeasy": "^2.0.1",
|
||||
"@types/tmp": "0.0.33",
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
service_count=`docker ps -a | grep "Up " | wc -l`
|
||||
|
||||
if [ "${service_count}" -eq "3" ]
|
||||
if [ "${service_count}" -eq "4" ]
|
||||
then
|
||||
echo "Service are up and running."
|
||||
exit 0
|
||||
|
|
|
@ -2,4 +2,4 @@
|
|||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
docker ps -a
|
||||
|
||||
|
|
|
@ -3,31 +3,33 @@
|
|||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
||||
|
||||
import Server from "./lib/Server";
|
||||
import { GlobalDependencies } from "../types/Dependencies";
|
||||
const YAML = require("yamljs");
|
||||
|
||||
const config_path = process.argv[2];
|
||||
if (!config_path) {
|
||||
const configurationFilepath = process.argv[2];
|
||||
if (!configurationFilepath) {
|
||||
console.log("No config file has been provided.");
|
||||
console.log("Usage: authelia <config>");
|
||||
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"),
|
||||
nodemailer: require("nodemailer"),
|
||||
ldapjs: require("ldapjs"),
|
||||
session: require("express-session"),
|
||||
winston: require("winston"),
|
||||
speakeasy: require("speakeasy"),
|
||||
nedb: require("nedb")
|
||||
nedb: require("nedb"),
|
||||
ConnectRedis: require("connect-redis")
|
||||
};
|
||||
|
||||
const server = new Server();
|
||||
server.start(yaml_config, deps)
|
||||
server.start(yamlContent, deps)
|
||||
.then(() => {
|
||||
console.log("The server is started!");
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
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";
|
||||
|
||||
|
@ -32,6 +32,7 @@ function adaptFromUserConfiguration(userConfiguration: UserConfiguration): AppCo
|
|||
domain: ObjectPath.get<object, string>(userConfiguration, "session.domain"),
|
||||
secret: ObjectPath.get<object, string>(userConfiguration, "session.secret"),
|
||||
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),
|
||||
logs_level: get_optional<string>(userConfiguration, "logs_level", "info"),
|
||||
|
|
|
@ -5,12 +5,13 @@ import { GlobalDependencies } from "../../types/Dependencies";
|
|||
import { AuthenticationRegulator } from "./AuthenticationRegulator";
|
||||
import UserDataStore from "./UserDataStore";
|
||||
import ConfigurationAdapter from "./ConfigurationAdapter";
|
||||
import { TOTPValidator } from "./TOTPValidator";
|
||||
import { TOTPValidator } from "./TOTPValidator";
|
||||
import { TOTPGenerator } from "./TOTPGenerator";
|
||||
import RestApi from "./RestApi";
|
||||
import { LdapClient } from "./LdapClient";
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import ServerVariables = require("./ServerVariables");
|
||||
import SessionConfigurationBuilder from "./SessionConfigurationBuilder";
|
||||
|
||||
import * as Express from "express";
|
||||
import * as BodyParser from "body-parser";
|
||||
|
@ -20,40 +21,32 @@ import * as http from "http";
|
|||
export default class Server {
|
||||
private httpServer: http.Server;
|
||||
|
||||
start(yaml_configuration: UserConfiguration, deps: GlobalDependencies): BluebirdPromise<void> {
|
||||
const config = ConfigurationAdapter.adapt(yaml_configuration);
|
||||
start(yamlConfiguration: UserConfiguration, deps: GlobalDependencies): BluebirdPromise<void> {
|
||||
const config = ConfigurationAdapter.adapt(yamlConfiguration);
|
||||
|
||||
const view_directory = Path.resolve(__dirname, "../views");
|
||||
const public_html_directory = Path.resolve(__dirname, "../public_html");
|
||||
const viewsDirectory = Path.resolve(__dirname, "../views");
|
||||
const publicHtmlDirectory = Path.resolve(__dirname, "../public_html");
|
||||
|
||||
const expressSessionOptions = SessionConfigurationBuilder.build(config, deps);
|
||||
|
||||
const app = Express();
|
||||
app.use(Express.static(public_html_directory));
|
||||
app.use(Express.static(publicHtmlDirectory));
|
||||
app.use(BodyParser.urlencoded({ extended: false }));
|
||||
app.use(BodyParser.json());
|
||||
app.use(deps.session(expressSessionOptions));
|
||||
|
||||
app.set("trust proxy", 1); // trust first proxy
|
||||
|
||||
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("trust proxy", 1);
|
||||
app.set("views", viewsDirectory);
|
||||
app.set("view engine", "pug");
|
||||
|
||||
// by default the level of logs is info
|
||||
deps.winston.level = config.logs_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));
|
||||
|
||||
ServerVariables.fill(app, config, deps);
|
||||
|
||||
RestApi.setup(app);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
export interface SessionRedisOptions {
|
||||
host: string;
|
||||
port: number;
|
||||
}
|
||||
|
||||
interface SessionCookieConfiguration {
|
||||
secret: string;
|
||||
expiration?: number;
|
||||
domain?: string;
|
||||
redis?: SessionRedisOptions;
|
||||
}
|
||||
|
||||
export interface GmailNotifierConfiguration {
|
||||
|
|
|
@ -5,6 +5,7 @@ import session = require("express-session");
|
|||
import nedb = require("nedb");
|
||||
import ldapjs = require("ldapjs");
|
||||
import u2f = require("u2f");
|
||||
import RedisSession = require("connect-redis");
|
||||
|
||||
export type Nodemailer = typeof nodemailer;
|
||||
export type Speakeasy = typeof speakeasy;
|
||||
|
@ -13,12 +14,14 @@ export type Session = typeof session;
|
|||
export type Nedb = typeof nedb;
|
||||
export type Ldapjs = typeof ldapjs;
|
||||
export type U2f = typeof u2f;
|
||||
export type ConnectRedis = typeof RedisSession;
|
||||
|
||||
export interface GlobalDependencies {
|
||||
u2f: U2f;
|
||||
nodemailer: Nodemailer;
|
||||
ldapjs: Ldapjs;
|
||||
session: Session;
|
||||
ConnectRedis: ConnectRedis;
|
||||
winston: Winston;
|
||||
speakeasy: Speakeasy;
|
||||
nedb: Nedb;
|
||||
|
|
|
@ -2,4 +2,3 @@ FROM node:7-alpine
|
|||
|
||||
WORKDIR /usr/src
|
||||
|
||||
CMD ["./node_modules/.bin/mocha", "--compilers", "ts:ts-node/register", "--recursive", "test/integration"]
|
||||
|
|
|
@ -73,6 +73,9 @@ session:
|
|||
secret: unsecure_secret
|
||||
expiration: 3600000
|
||||
domain: test.local
|
||||
redis:
|
||||
host: redis
|
||||
port: 6379
|
||||
|
||||
|
||||
# The directory where the DB files will be saved
|
||||
|
|
|
@ -11,6 +11,7 @@ services:
|
|||
|
||||
int-test:
|
||||
build: ./test/integration
|
||||
command: ./node_modules/.bin/mocha --compilers ts:ts-node/register --recursive test/integration
|
||||
volumes:
|
||||
- ./:/usr/src
|
||||
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()
|
||||
};
|
||||
|
||||
const deps = {
|
||||
const deps: GlobalDependencies = {
|
||||
u2f: u2f,
|
||||
nedb: nedb,
|
||||
nodemailer: nodemailer,
|
||||
session: session,
|
||||
winston: winston,
|
||||
ldapjs: ldap,
|
||||
speakeasy: speakeasy
|
||||
} as GlobalDependencies;
|
||||
speakeasy: speakeasy,
|
||||
ConnectRedis: sinon.spy()
|
||||
};
|
||||
|
||||
const j1 = request.jar();
|
||||
const j2 = request.jar();
|
||||
|
|
|
@ -38,11 +38,12 @@ describe("test server configuration", function () {
|
|||
createClient: sinon.spy(function () {
|
||||
return {
|
||||
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 { LdapjsClientMock } from "./mocks/ldapjs";
|
||||
|
||||
import Server from "../../../src/server/lib/Server";
|
||||
import LdapClient = require("../../../src/server/lib/LdapClient");
|
||||
import { LdapjsClientMock } from "./../mocks/ldapjs";
|
||||
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import speakeasy = require("speakeasy");
|
||||
import request = require("request");
|
||||
import nedb = require("nedb");
|
||||
import { TOTPSecret } from "../../src/types/TOTPSecret";
|
||||
import U2FMock = require("./mocks/u2f");
|
||||
import Endpoints = require("../../src/server/endpoints");
|
||||
|
||||
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 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 BASE_URL = "http://localhost:" + PORT;
|
||||
const requests = require("./requests")(PORT);
|
||||
const requests = Requests(PORT);
|
||||
|
||||
describe("test the server", function () {
|
||||
let server: Server;
|
||||
let transporter: object;
|
||||
let transporter: any;
|
||||
let u2f: U2FMock.U2FMock;
|
||||
|
||||
beforeEach(function () {
|
||||
|
@ -54,8 +56,8 @@ describe("test the server", function () {
|
|||
|
||||
const ldapClient = LdapjsClientMock();
|
||||
const ldap = {
|
||||
Change: sinon.spy(),
|
||||
createClient: sinon.spy(function () {
|
||||
Change: Sinon.spy(),
|
||||
createClient: Sinon.spy(function () {
|
||||
return ldapClient;
|
||||
})
|
||||
};
|
||||
|
@ -63,11 +65,11 @@ describe("test the server", function () {
|
|||
u2f = U2FMock.U2FMock();
|
||||
|
||||
transporter = {
|
||||
sendMail: sinon.stub().yields()
|
||||
sendMail: Sinon.stub().yields()
|
||||
};
|
||||
|
||||
const nodemailer = {
|
||||
createTransport: sinon.spy(function () {
|
||||
createTransport: Sinon.spy(function () {
|
||||
return transporter;
|
||||
})
|
||||
};
|
||||
|
@ -79,7 +81,7 @@ describe("test the server", function () {
|
|||
};
|
||||
|
||||
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);
|
||||
})
|
||||
};
|
||||
|
@ -96,14 +98,15 @@ describe("test the server", function () {
|
|||
ldapClient.modify.yields();
|
||||
ldapClient.search.yields(undefined, search_res);
|
||||
|
||||
const deps = {
|
||||
const deps: GlobalDependencies = {
|
||||
u2f: u2f,
|
||||
nedb: nedb,
|
||||
nodemailer: nodemailer,
|
||||
ldapjs: ldap,
|
||||
session: session,
|
||||
winston: winston,
|
||||
speakeasy: speakeasy
|
||||
session: ExpressSession,
|
||||
winston: Winston,
|
||||
speakeasy: speakeasy,
|
||||
ConnectRedis: Sinon.spy()
|
||||
};
|
||||
|
||||
server = new Server();
|
||||
|
@ -114,114 +117,17 @@ describe("test the server", 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();
|
||||
});
|
||||
|
||||
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 () {
|
||||
test_authentication();
|
||||
test_reset_password();
|
||||
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() {
|
||||
it("should return status code 401 when user is not authenticated", function () {
|
||||
return requestp.getAsync({ url: BASE_URL + Endpoints.VERIFY_GET })
|
||||
.then(function (response: request.RequestResponse) {
|
||||
assert.equal(response.statusCode, 401);
|
||||
Assert.equal(response.statusCode, 401);
|
||||
return BluebirdPromise.resolve();
|
||||
});
|
||||
});
|
||||
|
@ -230,11 +136,11 @@ describe("test the server", function () {
|
|||
const j = requestp.jar();
|
||||
return requests.login(j)
|
||||
.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);
|
||||
})
|
||||
.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);
|
||||
})
|
||||
.then(function (base32_secret: string) {
|
||||
|
@ -245,11 +151,11 @@ describe("test the server", function () {
|
|||
return requests.totp(j, realToken);
|
||||
})
|
||||
.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);
|
||||
})
|
||||
.then(function (res: request.RequestResponse) {
|
||||
assert.equal(res.statusCode, 204, "verify failed");
|
||||
Assert.equal(res.statusCode, 204, "verify failed");
|
||||
return BluebirdPromise.resolve();
|
||||
})
|
||||
.catch(function (err: Error) { return BluebirdPromise.reject(err); });
|
||||
|
@ -259,11 +165,11 @@ describe("test the server", function () {
|
|||
const j = requestp.jar();
|
||||
return requests.login(j)
|
||||
.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);
|
||||
})
|
||||
.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);
|
||||
})
|
||||
.then(function (base32_secret: string) {
|
||||
|
@ -274,15 +180,15 @@ describe("test the server", function () {
|
|||
return requests.totp(j, realToken);
|
||||
})
|
||||
.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);
|
||||
})
|
||||
.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);
|
||||
})
|
||||
.then(function (res: request.RequestResponse) {
|
||||
assert.equal(res.statusCode, 204, "verify failed");
|
||||
Assert.equal(res.statusCode, 204, "verify failed");
|
||||
return BluebirdPromise.resolve();
|
||||
})
|
||||
.catch(function (err: Error) { return BluebirdPromise.reject(err); });
|
||||
|
@ -300,25 +206,25 @@ describe("test the server", function () {
|
|||
const j = requestp.jar();
|
||||
return requests.login(j)
|
||||
.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);
|
||||
})
|
||||
.then(function (res: request.RequestResponse) {
|
||||
// console.log(res);
|
||||
assert.equal(res.headers.location, Endpoints.SECOND_FACTOR_GET);
|
||||
assert.equal(res.statusCode, 302, "first factor failed");
|
||||
Assert.equal(res.headers.location, Endpoints.SECOND_FACTOR_GET);
|
||||
Assert.equal(res.statusCode, 302, "first factor failed");
|
||||
return requests.u2f_registration(j, transporter);
|
||||
})
|
||||
.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);
|
||||
})
|
||||
.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);
|
||||
})
|
||||
.then(function (res: request.RequestResponse) {
|
||||
assert.equal(res.statusCode, 204, "verify failed");
|
||||
Assert.equal(res.statusCode, 204, "verify failed");
|
||||
return BluebirdPromise.resolve();
|
||||
});
|
||||
});
|
||||
|
@ -329,16 +235,16 @@ describe("test the server", function () {
|
|||
const j = requestp.jar();
|
||||
return requests.login(j)
|
||||
.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);
|
||||
})
|
||||
.then(function (res: request.RequestResponse) {
|
||||
assert.equal(res.headers.location, Endpoints.SECOND_FACTOR_GET);
|
||||
assert.equal(res.statusCode, 302, "first factor failed");
|
||||
Assert.equal(res.headers.location, Endpoints.SECOND_FACTOR_GET);
|
||||
Assert.equal(res.statusCode, 302, "first factor failed");
|
||||
return requests.reset_password(j, transporter, "user", "new-password");
|
||||
})
|
||||
.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();
|
||||
});
|
||||
});
|
||||
|
@ -350,28 +256,28 @@ describe("test the server", function () {
|
|||
MockDate.set("1/2/2017 00:00:00");
|
||||
return requests.login(j)
|
||||
.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);
|
||||
})
|
||||
.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);
|
||||
})
|
||||
.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);
|
||||
})
|
||||
.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);
|
||||
})
|
||||
.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");
|
||||
return requests.failing_first_factor(j);
|
||||
})
|
||||
.then(function (res: request.RequestResponse) {
|
||||
assert.equal(res.statusCode, 401, "first factor failed");
|
||||
Assert.equal(res.statusCode, 401, "first factor failed");
|
||||
return BluebirdPromise.resolve();
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue