Merge pull request #61 from clems4ever/resilient-db
Use a scalable and resilient database to improve Authelia QoSpull/63/head
commit
785182236c
24
.travis.yml
24
.travis.yml
|
@ -1,23 +1,37 @@
|
|||
dist: trusty
|
||||
language: node_js
|
||||
sudo: required
|
||||
node_js:
|
||||
- node
|
||||
- "7"
|
||||
services:
|
||||
- docker
|
||||
- ntp
|
||||
- docker
|
||||
- ntp
|
||||
addons:
|
||||
chrome: stable
|
||||
apt:
|
||||
sources:
|
||||
- google-chrome
|
||||
packages:
|
||||
- libgif-dev
|
||||
- libgif-dev
|
||||
- google-chrome-stable
|
||||
hosts:
|
||||
- auth.test.local
|
||||
- home.test.local
|
||||
- public.test.local
|
||||
- secret.test.local
|
||||
- secret1.test.local
|
||||
- secret2.test.local
|
||||
- mx1.mail.test.local
|
||||
- mx2.mail.test.local
|
||||
|
||||
before_install: npm install -g npm@'>=2.13.5'
|
||||
before_install:
|
||||
- npm install -g npm@'>=2.13.5'
|
||||
|
||||
before_script:
|
||||
- export DISPLAY=:99.0
|
||||
- sh -e /etc/init.d/xvfb start
|
||||
- sleep 3
|
||||
|
||||
script:
|
||||
- ./scripts/travis.sh
|
||||
|
||||
|
|
17
Gruntfile.js
17
Gruntfile.js
|
@ -12,17 +12,13 @@ module.exports = function (grunt) {
|
|||
cmd: "./node_modules/.bin/tslint",
|
||||
args: ['-c', 'tslint.json', '-p', 'tsconfig.json']
|
||||
},
|
||||
"test": {
|
||||
"unit-tests": {
|
||||
cmd: "./node_modules/.bin/mocha",
|
||||
args: ['--compilers', 'ts:ts-node/register', '--recursive', 'test/unit']
|
||||
},
|
||||
"test-int": {
|
||||
cmd: "./node_modules/.bin/mocha",
|
||||
args: ['--compilers', 'ts:ts-node/register', '--recursive', 'test/integration']
|
||||
},
|
||||
"test-system": {
|
||||
cmd: "./node_modules/.bin/mocha",
|
||||
args: ['--compilers', 'ts:ts-node/register', '--recursive', 'test/system']
|
||||
"integration-tests": {
|
||||
cmd: "./node_modules/.bin/cucumber-js",
|
||||
args: ["--compiler", "ts:ts-node/register", "./test/features"]
|
||||
},
|
||||
"docker-build": {
|
||||
cmd: "docker",
|
||||
|
@ -165,5 +161,8 @@ module.exports = function (grunt) {
|
|||
grunt.registerTask('docker-build', ['run:docker-build']);
|
||||
grunt.registerTask('docker-restart', ['run:docker-restart']);
|
||||
|
||||
grunt.registerTask('test', ['run:test']);
|
||||
grunt.registerTask('unit-tests', ['run:unit-tests']);
|
||||
grunt.registerTask('integration-tests', ['run:unit-tests']);
|
||||
|
||||
grunt.registerTask('test', ['unit-tests']);
|
||||
};
|
||||
|
|
|
@ -91,6 +91,7 @@ Make sure you don't have anything listening on port 8080.
|
|||
|
||||
Add the following lines to your **/etc/hosts** to alias multiple subdomains so that nginx can redirect request to the correct virtual host.
|
||||
|
||||
127.0.0.1 public.test.local
|
||||
127.0.0.1 secret.test.local
|
||||
127.0.0.1 secret1.test.local
|
||||
127.0.0.1 secret2.test.local
|
||||
|
|
|
@ -48,7 +48,7 @@ ldap:
|
|||
# beginning of the pattern.
|
||||
access_control:
|
||||
default:
|
||||
- home.test.local
|
||||
- public.test.local
|
||||
groups:
|
||||
admin:
|
||||
- '*.test.local'
|
||||
|
@ -77,9 +77,13 @@ session:
|
|||
host: redis
|
||||
port: 6379
|
||||
|
||||
# The directory where the DB files will be saved
|
||||
store_directory: /var/lib/authelia/store
|
||||
storage:
|
||||
# The directory where the DB files will be saved
|
||||
# local: /var/lib/authelia/store
|
||||
|
||||
# Settings to connect to mongo server
|
||||
mongo:
|
||||
url: mongodb://mongo/authelia
|
||||
|
||||
# Notifications are sent to users when they require a password reset, a u2f
|
||||
# registration or a TOTP registration.
|
||||
|
|
|
@ -45,3 +45,10 @@ mail: bob.dylan@example.com
|
|||
sn: Bob Dylan
|
||||
userpassword: {SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=
|
||||
|
||||
dn: cn=james,ou=users,dc=example,dc=com
|
||||
cn: james
|
||||
objectclass: inetOrgPerson
|
||||
objectclass: top
|
||||
mail: james.dean@example.com
|
||||
sn: James Dean
|
||||
userpassword: {SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
version: '2'
|
||||
services:
|
||||
mongo:
|
||||
image: mongo:3.4
|
||||
networks:
|
||||
- example-network
|
|
@ -9,6 +9,9 @@
|
|||
You need to log in to access the secret!<br/><br/>
|
||||
Try to access it via one of the following links.<br/>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://public.test.local:8080/secret.html">public.test.local</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://secret.test.local:8080/secret.html">secret.test.local</a>
|
||||
</li>
|
||||
|
@ -18,9 +21,6 @@
|
|||
<li>
|
||||
<a href="https://secret2.test.local:8080/secret.html">secret2.test.local</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://home.test.local:8080/secret.html">home.test.local</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://mx1.mail.test.local:8080/secret.html">mx1.mail.test.local</a>
|
||||
</li>
|
||||
|
@ -45,7 +45,7 @@
|
|||
<ul>
|
||||
<li><strong>Default policy</strong>
|
||||
<ul>
|
||||
<li>home.test.local</li>
|
||||
<li>public.test.local</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><strong>Groups policy</strong>
|
||||
|
|
|
@ -53,7 +53,8 @@ http {
|
|||
root /usr/share/nginx/html;
|
||||
|
||||
server_name secret1.test.local secret2.test.local secret.test.local
|
||||
home.test.local mx1.mail.test.local mx2.mail.test.local;
|
||||
home.test.local mx1.mail.test.local mx2.mail.test.local
|
||||
public.test.local;
|
||||
|
||||
ssl on;
|
||||
ssl_certificate /etc/ssl/server.crt;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
version: '2'
|
||||
services:
|
||||
redis:
|
||||
image: redis
|
||||
image: redis:4.0-alpine
|
||||
networks:
|
||||
- example-network
|
||||
|
|
20
package.json
20
package.json
|
@ -7,7 +7,7 @@
|
|||
"authelia": "dist/src/server/index.js"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "./node_modules/.bin/grunt test",
|
||||
"test": "./node_modules/.bin/grunt unit-tests",
|
||||
"cover": "NODE_ENV=test nyc npm t",
|
||||
"serve": "node dist/server/index.js"
|
||||
},
|
||||
|
@ -24,7 +24,6 @@
|
|||
"title": "Authelia API documentation"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/cors": "^2.8.1",
|
||||
"bluebird": "^3.4.7",
|
||||
"body-parser": "^1.15.2",
|
||||
"connect-redis": "^3.3.0",
|
||||
|
@ -33,6 +32,7 @@
|
|||
"express": "^4.14.0",
|
||||
"express-session": "^1.14.2",
|
||||
"ldapjs": "^1.0.1",
|
||||
"mongodb": "^2.2.30",
|
||||
"nedb": "^1.8.0",
|
||||
"nodemailer": "^4.0.1",
|
||||
"object-path": "^0.11.3",
|
||||
|
@ -47,6 +47,8 @@
|
|||
"@types/bluebird": "^3.5.4",
|
||||
"@types/body-parser": "^1.16.3",
|
||||
"@types/connect-redis": "0.0.6",
|
||||
"@types/cors": "^2.8.1",
|
||||
"@types/cucumber": "^2.0.1",
|
||||
"@types/ejs": "^2.3.33",
|
||||
"@types/express": "^4.0.35",
|
||||
"@types/express-session": "0.0.32",
|
||||
|
@ -55,6 +57,7 @@
|
|||
"@types/ldapjs": "^1.0.0",
|
||||
"@types/mocha": "^2.2.41",
|
||||
"@types/mockdate": "^2.0.0",
|
||||
"@types/mongodb": "^2.2.7",
|
||||
"@types/nedb": "^1.8.3",
|
||||
"@types/nodemailer": "^1.3.32",
|
||||
"@types/object-path": "^0.9.28",
|
||||
|
@ -62,6 +65,7 @@
|
|||
"@types/query-string": "^4.3.1",
|
||||
"@types/randomstring": "^1.1.5",
|
||||
"@types/request": "0.0.46",
|
||||
"@types/selenium-webdriver": "^3.0.4",
|
||||
"@types/sinon": "^2.2.1",
|
||||
"@types/speakeasy": "^2.0.1",
|
||||
"@types/tmp": "0.0.33",
|
||||
|
@ -69,6 +73,8 @@
|
|||
"@types/yamljs": "^0.2.30",
|
||||
"apidoc": "^0.17.6",
|
||||
"browserify": "^14.3.0",
|
||||
"chromedriver": "^2.31.0",
|
||||
"cucumber": "^2.3.1",
|
||||
"grunt": "^1.0.1",
|
||||
"grunt-browserify": "^5.0.0",
|
||||
"grunt-contrib-concat": "^1.0.1",
|
||||
|
@ -80,7 +86,7 @@
|
|||
"jquery": "^3.2.1",
|
||||
"js-logger": "^1.3.0",
|
||||
"jsdom": "^11.0.0",
|
||||
"mocha": "^3.2.0",
|
||||
"mocha": "^3.4.2",
|
||||
"mockdate": "^2.0.1",
|
||||
"notifyjs-browser": "^0.4.2",
|
||||
"nyc": "^10.3.2",
|
||||
|
@ -88,11 +94,12 @@
|
|||
"proxyquire": "^1.8.0",
|
||||
"query-string": "^4.3.4",
|
||||
"request": "^2.81.0",
|
||||
"selenium-webdriver": "^3.5.0",
|
||||
"should": "^11.1.1",
|
||||
"sinon": "^1.17.6",
|
||||
"sinon": "^2.3.8",
|
||||
"sinon-promise": "^0.1.3",
|
||||
"tmp": "0.0.31",
|
||||
"ts-node": "^3.0.4",
|
||||
"ts-node": "^3.3.0",
|
||||
"tslint": "^5.2.0",
|
||||
"typescript": "^2.3.2",
|
||||
"u2f-api": "0.0.9",
|
||||
|
@ -107,7 +114,8 @@
|
|||
"doc",
|
||||
"src/types",
|
||||
"dist",
|
||||
"test"
|
||||
"test",
|
||||
"src/**/*.d.ts"
|
||||
],
|
||||
"extension": [
|
||||
".ts"
|
||||
|
|
|
@ -2,4 +2,11 @@
|
|||
|
||||
set -e
|
||||
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.yml -f docker-compose.dev.yml -f example/redis/docker-compose.yml -f example/nginx/docker-compose.yml -f example/ldap/docker-compose.yml -f test/integration/docker-compose.yml $*
|
||||
docker-compose \
|
||||
-f docker-compose.base.yml \
|
||||
-f docker-compose.yml \
|
||||
-f docker-compose.dev.yml \
|
||||
-f example/mongo/docker-compose.yml \
|
||||
-f example/redis/docker-compose.yml \
|
||||
-f example/nginx/docker-compose.yml \
|
||||
-f example/ldap/docker-compose.yml $*
|
||||
|
|
|
@ -2,4 +2,10 @@
|
|||
|
||||
set -e
|
||||
|
||||
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 -f test/integration/docker-compose.yml $*
|
||||
docker-compose \
|
||||
-f docker-compose.base.yml \
|
||||
-f docker-compose.yml \
|
||||
-f example/mongo/docker-compose.yml \
|
||||
-f example/redis/docker-compose.yml \
|
||||
-f example/nginx/docker-compose.yml \
|
||||
-f example/ldap/docker-compose.yml $*
|
||||
|
|
|
@ -3,4 +3,4 @@
|
|||
DC_SCRIPT=./scripts/example/dc-example.sh
|
||||
|
||||
$DC_SCRIPT build
|
||||
$DC_SCRIPT up -d redis openldap authelia nginx
|
||||
$DC_SCRIPT up -d mongo redis openldap authelia nginx
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
#!/bin/bash
|
||||
|
||||
DC_SCRIPT=./scripts/example/dc-example.sh
|
||||
EXPECTED_SERVICES_COUNT=5
|
||||
|
||||
start_services() {
|
||||
$DC_SCRIPT up -d redis openldap authelia nginx nginx-tests
|
||||
$DC_SCRIPT up -d mongo redis openldap authelia nginx
|
||||
sleep 3
|
||||
}
|
||||
|
||||
|
@ -26,39 +27,12 @@ expect_services_count() {
|
|||
}
|
||||
|
||||
run_integration_tests() {
|
||||
echo "Prepare nginx-test configuration"
|
||||
cat example/nginx/nginx.conf | sed 's/listen 443 ssl/listen 8080 ssl/g' | dd of="test/integration/nginx.conf"
|
||||
|
||||
echo "Build services images..."
|
||||
$DC_SCRIPT build
|
||||
|
||||
echo "Start services..."
|
||||
start_services
|
||||
docker ps -a
|
||||
expect_services_count $EXPECTED_SERVICES_COUNT
|
||||
|
||||
echo "Display services logs..."
|
||||
$DC_SCRIPT logs redis
|
||||
$DC_SCRIPT logs openldap
|
||||
$DC_SCRIPT logs nginx
|
||||
$DC_SCRIPT logs nginx-tests
|
||||
$DC_SCRIPT logs authelia
|
||||
|
||||
echo "Check number of services"
|
||||
expect_services_count 5
|
||||
|
||||
echo "Run integration tests..."
|
||||
$DC_SCRIPT run --rm integration-tests
|
||||
|
||||
echo "Shutdown services..."
|
||||
shut_services
|
||||
}
|
||||
|
||||
run_system_tests() {
|
||||
echo "Start services..."
|
||||
start_services
|
||||
expect_services_count 5
|
||||
|
||||
./node_modules/.bin/mocha --compilers ts:ts-node/register --recursive test/system
|
||||
sleep 5
|
||||
./node_modules/.bin/grunt run:integration-tests
|
||||
shut_services
|
||||
}
|
||||
|
||||
|
@ -67,7 +41,7 @@ run_other_tests() {
|
|||
npm install --only=dev
|
||||
./node_modules/.bin/grunt build-dist
|
||||
./scripts/example/deploy-example.sh
|
||||
expect_services_count 4
|
||||
expect_services_count 5
|
||||
}
|
||||
|
||||
|
||||
|
@ -79,11 +53,8 @@ set -e
|
|||
echo "Make sure services are not already running"
|
||||
shut_services
|
||||
|
||||
# Prepare & run integration tests
|
||||
run_integration_tests
|
||||
|
||||
# Prepare & test example from end user perspective
|
||||
run_system_tests
|
||||
run_integration_tests
|
||||
|
||||
# Other tests like executing the deployment script
|
||||
run_other_tests
|
||||
|
|
|
@ -13,7 +13,7 @@ export function validate(username: string, password: string, $: JQueryStatic): B
|
|||
})
|
||||
.fail(function (xhr: JQueryXHR, textStatus: string) {
|
||||
if (xhr.status == 401)
|
||||
reject(new Error("Authetication failed. Please check your credentials"));
|
||||
reject(new Error("Authetication failed. Please check your credentials."));
|
||||
reject(new Error(textStatus));
|
||||
});
|
||||
});
|
||||
|
|
|
@ -30,7 +30,7 @@ export default function(window: Window, $: JQueryStatic) {
|
|||
|
||||
requestPasswordReset(username)
|
||||
.then(function () {
|
||||
$.notify("An email has been sent. Click on the link to change your password", "success");
|
||||
$.notify("An email has been sent. Click on the link to change your password.", "success");
|
||||
setTimeout(function () {
|
||||
window.location.replace(Endpoints.FIRST_FACTOR_GET);
|
||||
}, 1000);
|
||||
|
|
|
@ -1,43 +1,40 @@
|
|||
|
||||
import * as BluebirdPromise from "bluebird";
|
||||
import exceptions = require("./Exceptions");
|
||||
import { UserDataStore } from "./storage/UserDataStore";
|
||||
import { AuthenticationTraceDocument } from "./storage/AuthenticationTraceDocument";
|
||||
|
||||
const REGULATION_TRACE_TYPE = "regulation";
|
||||
const MAX_AUTHENTICATION_COUNT_IN_TIME_RANGE = 3;
|
||||
|
||||
interface DatedDocument {
|
||||
date: Date;
|
||||
}
|
||||
|
||||
export class AuthenticationRegulator {
|
||||
private _user_data_store: any;
|
||||
private _lock_time_in_seconds: number;
|
||||
private userDataStore: UserDataStore;
|
||||
private lockTimeInSeconds: number;
|
||||
|
||||
constructor(user_data_store: any, lock_time_in_seconds: number) {
|
||||
this._user_data_store = user_data_store;
|
||||
this._lock_time_in_seconds = lock_time_in_seconds;
|
||||
constructor(userDataStore: any, lockTimeInSeconds: number) {
|
||||
this.userDataStore = userDataStore;
|
||||
this.lockTimeInSeconds = lockTimeInSeconds;
|
||||
}
|
||||
|
||||
// Mark authentication
|
||||
mark(userid: string, is_success: boolean): BluebirdPromise<void> {
|
||||
return this._user_data_store.save_authentication_trace(userid, REGULATION_TRACE_TYPE, is_success);
|
||||
mark(userId: string, isAuthenticationSuccessful: boolean): BluebirdPromise<void> {
|
||||
return this.userDataStore.saveAuthenticationTrace(userId, isAuthenticationSuccessful);
|
||||
}
|
||||
|
||||
regulate(userid: string): BluebirdPromise<void> {
|
||||
return this._user_data_store.get_last_authentication_traces(userid, REGULATION_TRACE_TYPE, false, 3)
|
||||
.then((docs: Array<DatedDocument>) => {
|
||||
if (docs.length < MAX_AUTHENTICATION_COUNT_IN_TIME_RANGE) {
|
||||
// less than the max authorized number of authentication in time range, thus authorizing access
|
||||
regulate(userId: string): BluebirdPromise<void> {
|
||||
return this.userDataStore.retrieveLatestAuthenticationTraces(userId, false, 3)
|
||||
.then((docs: AuthenticationTraceDocument[]) => {
|
||||
if (docs.length < MAX_AUTHENTICATION_COUNT_IN_TIME_RANGE) {
|
||||
// less than the max authorized number of authentication in time range, thus authorizing access
|
||||
return BluebirdPromise.resolve();
|
||||
}
|
||||
|
||||
const oldestDocument = docs[MAX_AUTHENTICATION_COUNT_IN_TIME_RANGE - 1];
|
||||
const noLockMinDate = new Date(new Date().getTime() - this.lockTimeInSeconds * 1000);
|
||||
if (oldestDocument.date > noLockMinDate) {
|
||||
throw new exceptions.AuthenticationRegulationError("Max number of authentication. Please retry in few minutes.");
|
||||
}
|
||||
|
||||
return BluebirdPromise.resolve();
|
||||
}
|
||||
|
||||
const oldest_doc = docs[MAX_AUTHENTICATION_COUNT_IN_TIME_RANGE - 1];
|
||||
const no_lock_min_date = new Date(new Date().getTime() - this._lock_time_in_seconds * 1000);
|
||||
if (oldest_doc.date > no_lock_min_date) {
|
||||
throw new exceptions.AuthenticationRegulationError("Max number of authentication. Please retry in few minutes.");
|
||||
}
|
||||
|
||||
return BluebirdPromise.resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,9 +6,9 @@ import Exceptions = require("./Exceptions");
|
|||
import AuthenticationSession = require("./AuthenticationSession");
|
||||
|
||||
export function validate(req: express.Request): BluebirdPromise<void> {
|
||||
const authSession = AuthenticationSession.get(req);
|
||||
if (!authSession.userid || !authSession.first_factor)
|
||||
return BluebirdPromise.reject(new Exceptions.FirstFactorValidationError("First factor has not been validated yet."));
|
||||
const authSession = AuthenticationSession.get(req);
|
||||
if (!authSession.userid || !authSession.first_factor)
|
||||
return BluebirdPromise.reject(new Exceptions.FirstFactorValidationError("First factor has not been validated yet."));
|
||||
|
||||
return BluebirdPromise.resolve();
|
||||
return BluebirdPromise.resolve();
|
||||
}
|
|
@ -6,15 +6,15 @@ import util = require("util");
|
|||
import Exceptions = require("./Exceptions");
|
||||
import fs = require("fs");
|
||||
import ejs = require("ejs");
|
||||
import UserDataStore from "./UserDataStore";
|
||||
import { IUserDataStore } from "./storage/IUserDataStore";
|
||||
import { Winston } from "../../types/Dependencies";
|
||||
import express = require("express");
|
||||
import ErrorReplies = require("./ErrorReplies");
|
||||
import ServerVariables = require("./ServerVariables");
|
||||
import { ServerVariablesHandler } from "./ServerVariablesHandler";
|
||||
import AuthenticationSession = require("./AuthenticationSession");
|
||||
|
||||
import Identity = require("../../types/Identity");
|
||||
import { IdentityValidationRequestContent } from "./UserDataStore";
|
||||
import { IdentityValidationDocument } from "./storage/IdentityValidationDocument";
|
||||
|
||||
const filePath = __dirname + "/../resources/email-template.ejs";
|
||||
const email_template = fs.readFileSync(filePath, "utf8");
|
||||
|
@ -33,21 +33,21 @@ export interface IdentityValidable {
|
|||
mailSubject(): string;
|
||||
}
|
||||
|
||||
function issue_token(userid: string, content: Object, userDataStore: UserDataStore, logger: Winston): BluebirdPromise<string> {
|
||||
function createAndSaveToken(userid: string, challenge: string, userDataStore: IUserDataStore, logger: Winston): BluebirdPromise<string> {
|
||||
const five_minutes = 4 * 60 * 1000;
|
||||
const token = randomstring.generate({ length: 64 });
|
||||
const that = this;
|
||||
|
||||
logger.debug("identity_check: issue identity token %s for 5 minutes", token);
|
||||
return userDataStore.issue_identity_check_token(userid, token, content, five_minutes)
|
||||
return userDataStore.produceIdentityValidationToken(userid, token, challenge, five_minutes)
|
||||
.then(function () {
|
||||
return BluebirdPromise.resolve(token);
|
||||
});
|
||||
}
|
||||
|
||||
function consume_token(token: string, userDataStore: UserDataStore, logger: Winston): BluebirdPromise<IdentityValidationRequestContent> {
|
||||
function consumeToken(token: string, challenge: string, userDataStore: IUserDataStore, logger: Winston): BluebirdPromise<IdentityValidationDocument> {
|
||||
logger.debug("identity_check: consume token %s", token);
|
||||
return userDataStore.consume_identity_check_token(token);
|
||||
return userDataStore.consumeIdentityValidationToken(token, challenge);
|
||||
}
|
||||
|
||||
export function register(app: express.Application, pre_validation_endpoint: string, post_validation_endpoint: string, handler: IdentityValidable) {
|
||||
|
@ -63,8 +63,8 @@ function checkIdentityToken(req: express.Request, identityToken: string): Bluebi
|
|||
|
||||
export function get_finish_validation(handler: IdentityValidable): express.RequestHandler {
|
||||
return function (req: express.Request, res: express.Response): BluebirdPromise<void> {
|
||||
const logger = ServerVariables.getLogger(req.app);
|
||||
const userDataStore = ServerVariables.getUserDataStore(req.app);
|
||||
const logger = ServerVariablesHandler.getLogger(req.app);
|
||||
const userDataStore = ServerVariablesHandler.getUserDataStore(req.app);
|
||||
|
||||
const authSession = AuthenticationSession.get(req);
|
||||
const identityToken = objectPath.get<express.Request, string>(req, "query.identity_token");
|
||||
|
@ -75,12 +75,12 @@ export function get_finish_validation(handler: IdentityValidable): express.Reque
|
|||
return handler.postValidationInit(req);
|
||||
})
|
||||
.then(function () {
|
||||
return consume_token(identityToken, userDataStore, logger);
|
||||
return consumeToken(identityToken, handler.challenge(), userDataStore, logger);
|
||||
})
|
||||
.then(function (content: IdentityValidationRequestContent) {
|
||||
.then(function (doc: IdentityValidationDocument) {
|
||||
authSession.identity_check = {
|
||||
challenge: handler.challenge(),
|
||||
userid: content.userid
|
||||
userid: doc.userId
|
||||
};
|
||||
handler.postValidationResponse(req, res);
|
||||
return BluebirdPromise.resolve();
|
||||
|
@ -94,9 +94,9 @@ export function get_finish_validation(handler: IdentityValidable): express.Reque
|
|||
|
||||
export function get_start_validation(handler: IdentityValidable, postValidationEndpoint: string): express.RequestHandler {
|
||||
return function (req: express.Request, res: express.Response): BluebirdPromise<void> {
|
||||
const logger = ServerVariables.getLogger(req.app);
|
||||
const notifier = ServerVariables.getNotifier(req.app);
|
||||
const userDataStore = ServerVariables.getUserDataStore(req.app);
|
||||
const logger = ServerVariablesHandler.getLogger(req.app);
|
||||
const notifier = ServerVariablesHandler.getNotifier(req.app);
|
||||
const userDataStore = ServerVariablesHandler.getUserDataStore(req.app);
|
||||
let identity: Identity.Identity;
|
||||
logger.info("Identity Validation: Start identity validation");
|
||||
|
||||
|
@ -104,13 +104,13 @@ export function get_start_validation(handler: IdentityValidable, postValidationE
|
|||
.then(function (id: Identity.Identity) {
|
||||
logger.debug("Identity Validation: retrieved identity is %s", JSON.stringify(id));
|
||||
identity = id;
|
||||
const email_address = objectPath.get<Identity.Identity, string>(identity, "email");
|
||||
const userid = objectPath.get<Identity.Identity, string>(identity, "userid");
|
||||
const email = identity.email;
|
||||
const userid = identity.userid;
|
||||
|
||||
if (!(email_address && userid))
|
||||
if (!(email && userid))
|
||||
return BluebirdPromise.reject(new Exceptions.IdentityError("Missing user id or email address"));
|
||||
|
||||
return issue_token(userid, undefined, userDataStore, logger);
|
||||
return createAndSaveToken(userid, handler.challenge(), userDataStore, logger);
|
||||
})
|
||||
.then(function (token: string) {
|
||||
const host = req.get("Host");
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
import express = require("express");
|
||||
import UserDataStore from "./UserDataStore";
|
||||
import { UserDataStore } from "./storage/UserDataStore";
|
||||
import { Winston } from "../../types/Dependencies";
|
||||
|
||||
import FirstFactorGet = require("./routes/firstfactor/get");
|
||||
|
@ -34,7 +34,7 @@ import Error404Get = require("./routes/error/404/get");
|
|||
|
||||
import Endpoints = require("../endpoints");
|
||||
|
||||
export default class RestApi {
|
||||
export class RestApi {
|
||||
static setup(app: express.Application): void {
|
||||
app.get(Endpoints.FIRST_FACTOR_GET, FirstFactorGet.default);
|
||||
app.get(Endpoints.SECOND_FACTOR_GET, SecondFactorGet.default);
|
||||
|
|
|
@ -1,43 +1,54 @@
|
|||
import BluebirdPromise = require("bluebird");
|
||||
|
||||
import { AccessController } from "./access_control/AccessController";
|
||||
import { UserConfiguration } from "./../../types/Configuration";
|
||||
import { AppConfiguration, UserConfiguration } from "./configuration/Configuration";
|
||||
import { GlobalDependencies } from "../../types/Dependencies";
|
||||
import { AuthenticationRegulator } from "./AuthenticationRegulator";
|
||||
import UserDataStore from "./UserDataStore";
|
||||
import ConfigurationAdapter from "./ConfigurationAdapter";
|
||||
import { UserDataStore } from "./storage/UserDataStore";
|
||||
import { ConfigurationAdapter } from "./configuration/ConfigurationAdapter";
|
||||
import { TOTPValidator } from "./TOTPValidator";
|
||||
import { TOTPGenerator } from "./TOTPGenerator";
|
||||
import RestApi from "./RestApi";
|
||||
import { RestApi } from "./RestApi";
|
||||
import { Client } from "./ldap/Client";
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import ServerVariables = require("./ServerVariables");
|
||||
import SessionConfigurationBuilder from "./SessionConfigurationBuilder";
|
||||
import { ServerVariablesHandler } from "./ServerVariablesHandler";
|
||||
import { SessionConfigurationBuilder } from "./configuration/SessionConfigurationBuilder";
|
||||
|
||||
import * as Express from "express";
|
||||
import * as BodyParser from "body-parser";
|
||||
import * as Path from "path";
|
||||
import * as http from "http";
|
||||
|
||||
// Constants
|
||||
|
||||
const TRUST_PROXY = "trust proxy";
|
||||
const VIEWS = "views";
|
||||
const VIEW_ENGINE = "view engine";
|
||||
const PUG = "pug";
|
||||
|
||||
|
||||
export default class Server {
|
||||
private httpServer: http.Server;
|
||||
|
||||
start(yamlConfiguration: UserConfiguration, deps: GlobalDependencies): BluebirdPromise<void> {
|
||||
const config = ConfigurationAdapter.adapt(yamlConfiguration);
|
||||
|
||||
private setupExpressApplication(config: AppConfiguration, app: Express.Application, deps: GlobalDependencies): void {
|
||||
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(publicHtmlDirectory));
|
||||
app.use(BodyParser.urlencoded({ extended: false }));
|
||||
app.use(BodyParser.json());
|
||||
app.use(deps.session(expressSessionOptions));
|
||||
|
||||
app.set("trust proxy", 1);
|
||||
app.set("views", viewsDirectory);
|
||||
app.set("view engine", "pug");
|
||||
app.set(TRUST_PROXY, 1);
|
||||
app.set(VIEWS, viewsDirectory);
|
||||
app.set(VIEW_ENGINE, PUG);
|
||||
|
||||
RestApi.setup(app);
|
||||
}
|
||||
|
||||
private transformConfiguration(yamlConfiguration: UserConfiguration, deps: GlobalDependencies): AppConfiguration {
|
||||
const config = ConfigurationAdapter.adapt(yamlConfiguration);
|
||||
|
||||
// by default the level of logs is info
|
||||
deps.winston.level = config.logs_level;
|
||||
|
@ -45,18 +56,33 @@ export default class Server {
|
|||
|
||||
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));
|
||||
return config;
|
||||
}
|
||||
|
||||
ServerVariables.fill(app, config, deps);
|
||||
RestApi.setup(app);
|
||||
private setup(config: AppConfiguration, app: Express.Application, deps: GlobalDependencies): BluebirdPromise<void> {
|
||||
this.setupExpressApplication(config, app, deps);
|
||||
return ServerVariablesHandler.initialize(app, config, deps);
|
||||
}
|
||||
|
||||
private startServer(app: Express.Application, port: number) {
|
||||
return new BluebirdPromise<void>((resolve, reject) => {
|
||||
this.httpServer = app.listen(config.port, function (err: string) {
|
||||
console.log("Listening on %d...", config.port);
|
||||
this.httpServer = app.listen(port, function (err: string) {
|
||||
console.log("Listening on %d...", port);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
start(yamlConfiguration: UserConfiguration, deps: GlobalDependencies): BluebirdPromise<void> {
|
||||
const that = this;
|
||||
const app = Express();
|
||||
const config = this.transformConfiguration(yamlConfiguration, deps);
|
||||
return this.setup(config, app, deps)
|
||||
.then(function () {
|
||||
return that.startServer(app, config.port);
|
||||
});
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.httpServer.close();
|
||||
}
|
||||
|
|
|
@ -1,120 +0,0 @@
|
|||
|
||||
import winston = require("winston");
|
||||
import { Authenticator } from "./ldap/Authenticator";
|
||||
import { PasswordUpdater } from "./ldap/PasswordUpdater";
|
||||
import { EmailsRetriever } from "./ldap/EmailsRetriever";
|
||||
|
||||
import { TOTPValidator } from "./TOTPValidator";
|
||||
import { TOTPGenerator } from "./TOTPGenerator";
|
||||
import U2F = require("u2f");
|
||||
import UserDataStore from "./UserDataStore";
|
||||
import { INotifier } from "./notifiers/INotifier";
|
||||
import { AuthenticationRegulator } from "./AuthenticationRegulator";
|
||||
import Configuration = require("../../types/Configuration");
|
||||
import { AccessController } from "./access_control/AccessController";
|
||||
import { NotifierFactory } from "./notifiers/NotifierFactory";
|
||||
|
||||
import { GlobalDependencies } from "../../types/Dependencies";
|
||||
|
||||
import express = require("express");
|
||||
|
||||
export const VARIABLES_KEY = "authelia-variables";
|
||||
|
||||
export interface ServerVariables {
|
||||
logger: typeof winston;
|
||||
ldapAuthenticator: Authenticator;
|
||||
ldapPasswordUpdater: PasswordUpdater;
|
||||
ldapEmailsRetriever: EmailsRetriever;
|
||||
totpValidator: TOTPValidator;
|
||||
totpGenerator: TOTPGenerator;
|
||||
u2f: typeof U2F;
|
||||
userDataStore: UserDataStore;
|
||||
notifier: INotifier;
|
||||
regulator: AuthenticationRegulator;
|
||||
config: Configuration.AppConfiguration;
|
||||
accessController: AccessController;
|
||||
}
|
||||
|
||||
|
||||
export function fill(app: express.Application, config: Configuration.AppConfiguration, deps: GlobalDependencies) {
|
||||
const five_minutes = 5 * 60;
|
||||
const datastore_options = {
|
||||
directory: config.store_directory,
|
||||
inMemory: config.store_in_memory
|
||||
};
|
||||
|
||||
const userDataStore = new UserDataStore(datastore_options, deps.nedb);
|
||||
const regulator = new AuthenticationRegulator(userDataStore, five_minutes);
|
||||
const notifier = NotifierFactory.build(config.notifier, deps.nodemailer);
|
||||
const ldapAuthenticator = new Authenticator(config.ldap, deps.ldapjs, deps.winston);
|
||||
const ldapPasswordUpdater = new PasswordUpdater(config.ldap, deps.ldapjs, deps.dovehash, deps.winston);
|
||||
const ldapEmailsRetriever = new EmailsRetriever(config.ldap, deps.ldapjs, deps.winston);
|
||||
const accessController = new AccessController(config.access_control, deps.winston);
|
||||
const totpValidator = new TOTPValidator(deps.speakeasy);
|
||||
const totpGenerator = new TOTPGenerator(deps.speakeasy);
|
||||
|
||||
const variables: ServerVariables = {
|
||||
accessController: accessController,
|
||||
config: config,
|
||||
ldapAuthenticator: ldapAuthenticator,
|
||||
ldapPasswordUpdater: ldapPasswordUpdater,
|
||||
ldapEmailsRetriever: ldapEmailsRetriever,
|
||||
logger: deps.winston,
|
||||
notifier: notifier,
|
||||
regulator: regulator,
|
||||
totpGenerator: totpGenerator,
|
||||
totpValidator: totpValidator,
|
||||
u2f: deps.u2f,
|
||||
userDataStore: userDataStore
|
||||
};
|
||||
|
||||
app.set(VARIABLES_KEY, variables);
|
||||
}
|
||||
|
||||
export function getLogger(app: express.Application): typeof winston {
|
||||
return (app.get(VARIABLES_KEY) as ServerVariables).logger;
|
||||
}
|
||||
|
||||
export function getUserDataStore(app: express.Application): UserDataStore {
|
||||
return (app.get(VARIABLES_KEY) as ServerVariables).userDataStore;
|
||||
}
|
||||
|
||||
export function getNotifier(app: express.Application): INotifier {
|
||||
return (app.get(VARIABLES_KEY) as ServerVariables).notifier;
|
||||
}
|
||||
|
||||
export function getLdapAuthenticator(app: express.Application): Authenticator {
|
||||
return (app.get(VARIABLES_KEY) as ServerVariables).ldapAuthenticator;
|
||||
}
|
||||
|
||||
export function getLdapPasswordUpdater(app: express.Application): PasswordUpdater {
|
||||
return (app.get(VARIABLES_KEY) as ServerVariables).ldapPasswordUpdater;
|
||||
}
|
||||
|
||||
export function getLdapEmailsRetriever(app: express.Application): EmailsRetriever {
|
||||
return (app.get(VARIABLES_KEY) as ServerVariables).ldapEmailsRetriever;
|
||||
}
|
||||
|
||||
export function getConfiguration(app: express.Application): Configuration.AppConfiguration {
|
||||
return (app.get(VARIABLES_KEY) as ServerVariables).config;
|
||||
}
|
||||
|
||||
export function getAuthenticationRegulator(app: express.Application): AuthenticationRegulator {
|
||||
return (app.get(VARIABLES_KEY) as ServerVariables).regulator;
|
||||
}
|
||||
|
||||
export function getAccessController(app: express.Application): AccessController {
|
||||
return (app.get(VARIABLES_KEY) as ServerVariables).accessController;
|
||||
}
|
||||
|
||||
export function getTOTPGenerator(app: express.Application): TOTPGenerator {
|
||||
return (app.get(VARIABLES_KEY) as ServerVariables).totpGenerator;
|
||||
}
|
||||
|
||||
export function getTOTPValidator(app: express.Application): TOTPValidator {
|
||||
return (app.get(VARIABLES_KEY) as ServerVariables).totpValidator;
|
||||
}
|
||||
|
||||
export function getU2F(app: express.Application): typeof U2F {
|
||||
return (app.get(VARIABLES_KEY) as ServerVariables).u2f;
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
|
||||
import winston = require("winston");
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import { Authenticator } from "./ldap/Authenticator";
|
||||
import { PasswordUpdater } from "./ldap/PasswordUpdater";
|
||||
import { EmailsRetriever } from "./ldap/EmailsRetriever";
|
||||
|
||||
import { TOTPValidator } from "./TOTPValidator";
|
||||
import { TOTPGenerator } from "./TOTPGenerator";
|
||||
import U2F = require("u2f");
|
||||
import { IUserDataStore } from "./storage/IUserDataStore";
|
||||
import { UserDataStore } from "./storage/UserDataStore";
|
||||
import { INotifier } from "./notifiers/INotifier";
|
||||
import { AuthenticationRegulator } from "./AuthenticationRegulator";
|
||||
import Configuration = require("./configuration/Configuration");
|
||||
import { AccessController } from "./access_control/AccessController";
|
||||
import { NotifierFactory } from "./notifiers/NotifierFactory";
|
||||
import { CollectionFactoryFactory } from "./storage/CollectionFactoryFactory";
|
||||
import { ICollectionFactory } from "./storage/ICollectionFactory";
|
||||
import { MongoCollectionFactory } from "./storage/mongo/MongoCollectionFactory";
|
||||
import { MongoConnectorFactory } from "./connectors/mongo/MongoConnectorFactory";
|
||||
import { IMongoClient } from "./connectors/mongo/IMongoClient";
|
||||
|
||||
import { GlobalDependencies } from "../../types/Dependencies";
|
||||
|
||||
import express = require("express");
|
||||
|
||||
export const VARIABLES_KEY = "authelia-variables";
|
||||
|
||||
export interface ServerVariables {
|
||||
logger: typeof winston;
|
||||
ldapAuthenticator: Authenticator;
|
||||
ldapPasswordUpdater: PasswordUpdater;
|
||||
ldapEmailsRetriever: EmailsRetriever;
|
||||
totpValidator: TOTPValidator;
|
||||
totpGenerator: TOTPGenerator;
|
||||
u2f: typeof U2F;
|
||||
userDataStore: IUserDataStore;
|
||||
notifier: INotifier;
|
||||
regulator: AuthenticationRegulator;
|
||||
config: Configuration.AppConfiguration;
|
||||
accessController: AccessController;
|
||||
}
|
||||
|
||||
class UserDataStoreFactory {
|
||||
static create(config: Configuration.AppConfiguration): BluebirdPromise<UserDataStore> {
|
||||
if (config.storage.local) {
|
||||
const nedbOptions = {
|
||||
directory: config.storage.local.path,
|
||||
inMemory: config.storage.local.in_memory
|
||||
};
|
||||
const collectionFactory = CollectionFactoryFactory.createNedb(nedbOptions);
|
||||
return BluebirdPromise.resolve(new UserDataStore(collectionFactory));
|
||||
}
|
||||
else if (config.storage.mongo) {
|
||||
const mongoConnectorFactory = new MongoConnectorFactory();
|
||||
const mongoConnector = mongoConnectorFactory.create(config.storage.mongo.url);
|
||||
return mongoConnector.connect()
|
||||
.then(function (client: IMongoClient) {
|
||||
const collectionFactory = CollectionFactoryFactory.createMongo(client);
|
||||
return BluebirdPromise.resolve(new UserDataStore(collectionFactory));
|
||||
});
|
||||
}
|
||||
|
||||
return BluebirdPromise.reject(new Error("Storage backend incorrectly configured."));
|
||||
}
|
||||
}
|
||||
|
||||
export class ServerVariablesHandler {
|
||||
static initialize(app: express.Application, config: Configuration.AppConfiguration, deps: GlobalDependencies): BluebirdPromise<void> {
|
||||
const five_minutes = 5 * 60;
|
||||
|
||||
const notifier = NotifierFactory.build(config.notifier, deps.nodemailer);
|
||||
const ldapAuthenticator = new Authenticator(config.ldap, deps.ldapjs, deps.winston);
|
||||
const ldapPasswordUpdater = new PasswordUpdater(config.ldap, deps.ldapjs, deps.dovehash, deps.winston);
|
||||
const ldapEmailsRetriever = new EmailsRetriever(config.ldap, deps.ldapjs, deps.winston);
|
||||
const accessController = new AccessController(config.access_control, deps.winston);
|
||||
const totpValidator = new TOTPValidator(deps.speakeasy);
|
||||
const totpGenerator = new TOTPGenerator(deps.speakeasy);
|
||||
|
||||
return UserDataStoreFactory.create(config)
|
||||
.then(function (userDataStore: UserDataStore) {
|
||||
const regulator = new AuthenticationRegulator(userDataStore, five_minutes);
|
||||
|
||||
const variables: ServerVariables = {
|
||||
accessController: accessController,
|
||||
config: config,
|
||||
ldapAuthenticator: ldapAuthenticator,
|
||||
ldapPasswordUpdater: ldapPasswordUpdater,
|
||||
ldapEmailsRetriever: ldapEmailsRetriever,
|
||||
logger: deps.winston,
|
||||
notifier: notifier,
|
||||
regulator: regulator,
|
||||
totpGenerator: totpGenerator,
|
||||
totpValidator: totpValidator,
|
||||
u2f: deps.u2f,
|
||||
userDataStore: userDataStore
|
||||
};
|
||||
|
||||
app.set(VARIABLES_KEY, variables);
|
||||
});
|
||||
}
|
||||
|
||||
static getLogger(app: express.Application): typeof winston {
|
||||
return (app.get(VARIABLES_KEY) as ServerVariables).logger;
|
||||
}
|
||||
|
||||
static getUserDataStore(app: express.Application): IUserDataStore {
|
||||
return (app.get(VARIABLES_KEY) as ServerVariables).userDataStore;
|
||||
}
|
||||
|
||||
static getNotifier(app: express.Application): INotifier {
|
||||
return (app.get(VARIABLES_KEY) as ServerVariables).notifier;
|
||||
}
|
||||
|
||||
static getLdapAuthenticator(app: express.Application): Authenticator {
|
||||
return (app.get(VARIABLES_KEY) as ServerVariables).ldapAuthenticator;
|
||||
}
|
||||
|
||||
static getLdapPasswordUpdater(app: express.Application): PasswordUpdater {
|
||||
return (app.get(VARIABLES_KEY) as ServerVariables).ldapPasswordUpdater;
|
||||
}
|
||||
|
||||
static getLdapEmailsRetriever(app: express.Application): EmailsRetriever {
|
||||
return (app.get(VARIABLES_KEY) as ServerVariables).ldapEmailsRetriever;
|
||||
}
|
||||
|
||||
static getConfiguration(app: express.Application): Configuration.AppConfiguration {
|
||||
return (app.get(VARIABLES_KEY) as ServerVariables).config;
|
||||
}
|
||||
|
||||
static getAuthenticationRegulator(app: express.Application): AuthenticationRegulator {
|
||||
return (app.get(VARIABLES_KEY) as ServerVariables).regulator;
|
||||
}
|
||||
|
||||
static getAccessController(app: express.Application): AccessController {
|
||||
return (app.get(VARIABLES_KEY) as ServerVariables).accessController;
|
||||
}
|
||||
|
||||
static getTOTPGenerator(app: express.Application): TOTPGenerator {
|
||||
return (app.get(VARIABLES_KEY) as ServerVariables).totpGenerator;
|
||||
}
|
||||
|
||||
static getTOTPValidator(app: express.Application): TOTPValidator {
|
||||
return (app.get(VARIABLES_KEY) as ServerVariables).totpValidator;
|
||||
}
|
||||
|
||||
static getU2F(app: express.Application): typeof U2F {
|
||||
return (app.get(VARIABLES_KEY) as ServerVariables).u2f;
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -1,190 +0,0 @@
|
|||
import * as BluebirdPromise from "bluebird";
|
||||
import * as path from "path";
|
||||
import { NedbAsync } from "nedb";
|
||||
import { TOTPSecret } from "../../types/TOTPSecret";
|
||||
import { Nedb } from "../../types/Dependencies";
|
||||
import u2f = require("u2f");
|
||||
|
||||
// Constants
|
||||
|
||||
const U2F_META_COLLECTION_NAME = "u2f_meta";
|
||||
const IDENTITY_CHECK_TOKENS_COLLECTION_NAME = "identity_check_tokens";
|
||||
const AUTHENTICATION_TRACES_COLLECTION_NAME = "authentication_traces";
|
||||
const TOTP_SECRETS_COLLECTION_NAME = "totp_secrets";
|
||||
|
||||
|
||||
export interface TOTPSecretDocument {
|
||||
userid: string;
|
||||
secret: TOTPSecret;
|
||||
}
|
||||
|
||||
export interface U2FRegistrationDocument {
|
||||
keyHandle: string;
|
||||
publicKey: string;
|
||||
userId: string;
|
||||
appId: string;
|
||||
}
|
||||
|
||||
export interface Options {
|
||||
inMemoryOnly?: boolean;
|
||||
directory?: string;
|
||||
}
|
||||
|
||||
export interface IdentityValidationRequestContent {
|
||||
userid: string;
|
||||
data: string;
|
||||
}
|
||||
|
||||
export interface IdentityValidationRequestDocument {
|
||||
userid: string;
|
||||
token: string;
|
||||
content: IdentityValidationRequestContent;
|
||||
max_date: Date;
|
||||
}
|
||||
|
||||
interface U2FRegistrationFilter {
|
||||
userId: string;
|
||||
appId: string;
|
||||
}
|
||||
|
||||
// Source
|
||||
|
||||
export default class UserDataStore {
|
||||
private _u2f_meta_collection: NedbAsync;
|
||||
private _identity_check_tokens_collection: NedbAsync;
|
||||
private _authentication_traces_collection: NedbAsync;
|
||||
private _totp_secret_collection: NedbAsync;
|
||||
private nedb: Nedb;
|
||||
|
||||
constructor(options: Options, nedb: Nedb) {
|
||||
this.nedb = nedb;
|
||||
this._u2f_meta_collection = this.create_collection(U2F_META_COLLECTION_NAME, options);
|
||||
this._identity_check_tokens_collection =
|
||||
this.create_collection(IDENTITY_CHECK_TOKENS_COLLECTION_NAME, options);
|
||||
this._authentication_traces_collection =
|
||||
this.create_collection(AUTHENTICATION_TRACES_COLLECTION_NAME, options);
|
||||
this._totp_secret_collection =
|
||||
this.create_collection(TOTP_SECRETS_COLLECTION_NAME, options);
|
||||
}
|
||||
|
||||
set_u2f_meta(userId: string, appId: string, keyHandle: string, publicKey: string): BluebirdPromise<any> {
|
||||
const newDocument: U2FRegistrationDocument = {
|
||||
userId: userId,
|
||||
appId: appId,
|
||||
keyHandle: keyHandle,
|
||||
publicKey: publicKey
|
||||
};
|
||||
|
||||
const filter: U2FRegistrationFilter = {
|
||||
userId: userId,
|
||||
appId: appId
|
||||
};
|
||||
|
||||
return this._u2f_meta_collection.updateAsync(filter, newDocument, { upsert: true });
|
||||
}
|
||||
|
||||
get_u2f_meta(userId: string, appId: string): BluebirdPromise<U2FRegistrationDocument> {
|
||||
const filter: U2FRegistrationFilter = {
|
||||
userId: userId,
|
||||
appId: appId
|
||||
};
|
||||
return this._u2f_meta_collection.findOneAsync(filter);
|
||||
}
|
||||
|
||||
save_authentication_trace(userid: string, type: string, is_success: boolean) {
|
||||
const newDocument = {
|
||||
userid: userid,
|
||||
date: new Date(),
|
||||
is_success: is_success,
|
||||
type: type
|
||||
};
|
||||
|
||||
return this._authentication_traces_collection.insertAsync(newDocument);
|
||||
}
|
||||
|
||||
get_last_authentication_traces(userid: string, type: string, is_success: boolean, count: number): BluebirdPromise<any> {
|
||||
const q = {
|
||||
userid: userid,
|
||||
type: type,
|
||||
is_success: is_success
|
||||
};
|
||||
|
||||
const query = this._authentication_traces_collection.find(q)
|
||||
.sort({ date: -1 }).limit(count);
|
||||
const query_promisified = BluebirdPromise.promisify(query.exec, { context: query });
|
||||
return query_promisified();
|
||||
}
|
||||
|
||||
issue_identity_check_token(userid: string, token: string, data: string | object, max_age: number): BluebirdPromise<any> {
|
||||
const newDocument = {
|
||||
userid: userid,
|
||||
token: token,
|
||||
content: {
|
||||
userid: userid,
|
||||
data: data
|
||||
},
|
||||
max_date: new Date(new Date().getTime() + max_age)
|
||||
};
|
||||
|
||||
return this._identity_check_tokens_collection.insertAsync(newDocument);
|
||||
}
|
||||
|
||||
consume_identity_check_token(token: string): BluebirdPromise<IdentityValidationRequestContent> {
|
||||
const query = {
|
||||
token: token
|
||||
};
|
||||
|
||||
return this._identity_check_tokens_collection.findOneAsync(query)
|
||||
.then(function (doc) {
|
||||
if (!doc) {
|
||||
return BluebirdPromise.reject(new Error("Registration token does not exist"));
|
||||
}
|
||||
|
||||
const max_date = doc.max_date;
|
||||
const current_date = new Date();
|
||||
if (current_date > max_date)
|
||||
return BluebirdPromise.reject(new Error("Registration token is not valid anymore"));
|
||||
|
||||
return BluebirdPromise.resolve(doc.content);
|
||||
})
|
||||
.then((content) => {
|
||||
return BluebirdPromise.join(this._identity_check_tokens_collection.removeAsync(query),
|
||||
BluebirdPromise.resolve(content));
|
||||
})
|
||||
.then((v) => {
|
||||
return BluebirdPromise.resolve(v[1]);
|
||||
});
|
||||
}
|
||||
|
||||
set_totp_secret(userid: string, secret: TOTPSecret): BluebirdPromise<any> {
|
||||
const doc = {
|
||||
userid: userid,
|
||||
secret: secret
|
||||
};
|
||||
|
||||
const query = {
|
||||
userid: userid
|
||||
};
|
||||
return this._totp_secret_collection.updateAsync(query, doc, { upsert: true });
|
||||
}
|
||||
|
||||
get_totp_secret(userid: string): BluebirdPromise<TOTPSecretDocument> {
|
||||
const query = {
|
||||
userid: userid
|
||||
};
|
||||
return this._totp_secret_collection.findOneAsync(query);
|
||||
}
|
||||
|
||||
private create_collection(name: string, options: any): NedbAsync {
|
||||
const datastore_options = {
|
||||
inMemoryOnly: options.inMemoryOnly || false,
|
||||
autoload: true,
|
||||
filename: ""
|
||||
};
|
||||
|
||||
if (options.directory)
|
||||
datastore_options.filename = path.resolve(options.directory, name);
|
||||
|
||||
return BluebirdPromise.promisifyAll(new this.nedb(datastore_options)) as NedbAsync;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
import { ACLConfiguration } from "../../../types/Configuration";
|
||||
import { ACLConfiguration } from "../configuration/Configuration";
|
||||
import PatternBuilder from "./PatternBuilder";
|
||||
import { Winston } from "../../../types/Dependencies";
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
import { Winston } from "../../../types/Dependencies";
|
||||
import { ACLConfiguration, ACLGroupsRules, ACLUsersRules, ACLDefaultRules } from "../../../types/Configuration";
|
||||
import { ACLConfiguration, ACLGroupsRules, ACLUsersRules, ACLDefaultRules } from "../configuration/Configuration";
|
||||
import objectPath = require("object-path");
|
||||
|
||||
export default class AccessControlPatternBuilder {
|
||||
|
|
|
@ -50,12 +50,26 @@ export interface NotifierConfiguration {
|
|||
filesystem?: FileSystemNotifierConfiguration;
|
||||
}
|
||||
|
||||
export interface MongoStorageConfiguration {
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface LocalStorageConfiguration {
|
||||
path?: string;
|
||||
in_memory?: boolean;
|
||||
}
|
||||
|
||||
export interface StorageConfiguration {
|
||||
local?: LocalStorageConfiguration;
|
||||
mongo?: MongoStorageConfiguration;
|
||||
}
|
||||
|
||||
export interface UserConfiguration {
|
||||
port?: number;
|
||||
logs_level?: string;
|
||||
ldap: LdapConfiguration;
|
||||
session: SessionCookieConfiguration;
|
||||
store_directory?: string;
|
||||
storage: StorageConfiguration;
|
||||
notifier: NotifierConfiguration;
|
||||
access_control?: ACLConfiguration;
|
||||
}
|
||||
|
@ -65,8 +79,7 @@ export interface AppConfiguration {
|
|||
logs_level: string;
|
||||
ldap: LdapConfiguration;
|
||||
session: SessionCookieConfiguration;
|
||||
store_in_memory?: boolean;
|
||||
store_directory?: string;
|
||||
storage: StorageConfiguration;
|
||||
notifier: NotifierConfiguration;
|
||||
access_control?: ACLConfiguration;
|
||||
}
|
|
@ -1,6 +1,10 @@
|
|||
|
||||
import * as ObjectPath from "object-path";
|
||||
import { AppConfiguration, UserConfiguration, NotifierConfiguration, ACLConfiguration, LdapConfiguration, SessionRedisOptions } from "./../../types/Configuration";
|
||||
import {
|
||||
AppConfiguration, UserConfiguration, NotifierConfiguration,
|
||||
ACLConfiguration, LdapConfiguration, SessionRedisOptions,
|
||||
MongoStorageConfiguration, LocalStorageConfiguration
|
||||
} from "./Configuration";
|
||||
|
||||
const LDAP_URL_ENV_VARIABLE = "LDAP_URL";
|
||||
|
||||
|
@ -34,14 +38,17 @@ function adaptFromUserConfiguration(userConfiguration: UserConfiguration): AppCo
|
|||
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),
|
||||
storage: {
|
||||
local: get_optional<LocalStorageConfiguration>(userConfiguration, "storage.local", undefined),
|
||||
mongo: get_optional<MongoStorageConfiguration>(userConfiguration, "storage.mongo", undefined)
|
||||
},
|
||||
logs_level: get_optional<string>(userConfiguration, "logs_level", "info"),
|
||||
notifier: ObjectPath.get<object, NotifierConfiguration>(userConfiguration, "notifier"),
|
||||
access_control: ObjectPath.get<object, ACLConfiguration>(userConfiguration, "access_control")
|
||||
};
|
||||
}
|
||||
|
||||
export default class ConfigurationAdapter {
|
||||
export class ConfigurationAdapter {
|
||||
static adapt(userConfiguration: UserConfiguration): AppConfiguration {
|
||||
const appConfiguration = adaptFromUserConfiguration(userConfiguration);
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
|
||||
import ExpressSession = require("express-session");
|
||||
import { AppConfiguration } from "./Configuration";
|
||||
import { GlobalDependencies } from "../../../types/Dependencies";
|
||||
|
||||
export 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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import MongoDB = require("mongodb");
|
||||
|
||||
export interface IMongoClient {
|
||||
collection(name: string): MongoDB.Collection;
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import BluebirdPromise = require("bluebird");
|
||||
import { IMongoClient } from "./IMongoClient";
|
||||
|
||||
export interface IMongoConnector {
|
||||
connect(): BluebirdPromise<IMongoClient>;
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import { IMongoConnector } from "./IMongoConnector";
|
||||
|
||||
export interface IMongoConnectorFactory {
|
||||
create(url: string): IMongoConnector;
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
|
||||
import MongoDB = require("mongodb");
|
||||
import { IMongoClient } from "./IMongoClient";
|
||||
|
||||
export class MongoClient implements IMongoClient {
|
||||
private db: MongoDB.Db;
|
||||
|
||||
constructor(db: MongoDB.Db) {
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
collection(name: string): MongoDB.Collection {
|
||||
return this.db.collection(name);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
|
||||
import MongoDB = require("mongodb");
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import { IMongoClient } from "./IMongoClient";
|
||||
import { IMongoConnector } from "./IMongoConnector";
|
||||
import { MongoClient } from "./MongoClient";
|
||||
|
||||
export class MongoConnector implements IMongoConnector {
|
||||
private url: string;
|
||||
|
||||
constructor(url: string) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
connect(): BluebirdPromise<IMongoClient> {
|
||||
const connectAsync = BluebirdPromise.promisify(MongoDB.MongoClient.connect);
|
||||
return connectAsync(this.url)
|
||||
.then(function (db: MongoDB.Db) {
|
||||
return BluebirdPromise.resolve(new MongoClient(db));
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import { IMongoConnectorFactory } from "./IMongoConnectorFactory";
|
||||
import { IMongoConnector } from "./IMongoConnector";
|
||||
import { MongoConnector } from "./MongoConnector";
|
||||
import MongoDB = require("mongodb");
|
||||
|
||||
export class MongoConnectorFactory implements IMongoConnectorFactory {
|
||||
create(url: string): IMongoConnector {
|
||||
return new MongoConnector(url);
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ import ldapjs = require("ldapjs");
|
|||
import { Client, Attributes } from "./Client";
|
||||
import { buildUserDN } from "./common";
|
||||
|
||||
import { LdapConfiguration } from "./../../../types/Configuration";
|
||||
import { LdapConfiguration } from "../configuration/Configuration";
|
||||
import { Winston, Ldapjs, Dovehash } from "../../../types/Dependencies";
|
||||
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import ldapjs = require("ldapjs");
|
|||
import { buildUserDN } from "./common";
|
||||
|
||||
import { EventEmitter } from "events";
|
||||
import { LdapConfiguration } from "./../../../types/Configuration";
|
||||
import { LdapConfiguration } from "../configuration/Configuration";
|
||||
import { Winston, Ldapjs, Dovehash } from "../../../types/Dependencies";
|
||||
|
||||
interface SearchEntry {
|
||||
|
@ -41,10 +41,10 @@ export class Client {
|
|||
reconnect: true
|
||||
});
|
||||
|
||||
const clientLogger = (ldapClient as any).log;
|
||||
/*const clientLogger = (ldapClient as any).log;
|
||||
if (clientLogger) {
|
||||
clientLogger.level("trace");
|
||||
}
|
||||
}*/
|
||||
|
||||
this.client = BluebirdPromise.promisifyAll(ldapClient) as ldapjs.ClientAsync;
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import ldapjs = require("ldapjs");
|
|||
import { Client } from "./Client";
|
||||
import { buildUserDN } from "./common";
|
||||
|
||||
import { LdapConfiguration } from "./../../../types/Configuration";
|
||||
import { LdapConfiguration } from "../configuration/Configuration";
|
||||
import { Winston, Ldapjs, Dovehash } from "../../../types/Dependencies";
|
||||
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import ldapjs = require("ldapjs");
|
|||
import { Client } from "./Client";
|
||||
import { buildUserDN } from "./common";
|
||||
|
||||
import { LdapConfiguration } from "./../../../types/Configuration";
|
||||
import { LdapConfiguration } from "../configuration/Configuration";
|
||||
import { Winston, Ldapjs, Dovehash } from "../../../types/Dependencies";
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import util = require("util");
|
||||
|
||||
import { LdapConfiguration } from "./../../../types/Configuration";
|
||||
import { LdapConfiguration } from "../configuration/Configuration";
|
||||
|
||||
|
||||
export function buildUserDN(username: string, options: LdapConfiguration): string {
|
||||
|
|
|
@ -1,24 +1,23 @@
|
|||
|
||||
import * as BluebirdPromise from "bluebird";
|
||||
import * as util from "util";
|
||||
import * as fs from "fs";
|
||||
import * as Fs from "fs";
|
||||
import { INotifier } from "./INotifier";
|
||||
import { Identity } from "../../../types/Identity";
|
||||
|
||||
import { FileSystemNotifierConfiguration } from "../../../types/Configuration";
|
||||
import { FileSystemNotifierConfiguration } from "../configuration/Configuration";
|
||||
|
||||
export class FileSystemNotifier extends INotifier {
|
||||
export class FileSystemNotifier implements INotifier {
|
||||
private filename: string;
|
||||
|
||||
constructor(options: FileSystemNotifierConfiguration) {
|
||||
super();
|
||||
this.filename = options.filename;
|
||||
}
|
||||
|
||||
notify(identity: Identity, subject: string, link: string): BluebirdPromise<void> {
|
||||
const content = util.format("Date: %s\nUser: %s\nSubject: %s\nLink: %s", new Date().toString(), identity.userid,
|
||||
subject, link);
|
||||
const writeFilePromised = BluebirdPromise.promisify<void, string, string>(fs.writeFile);
|
||||
const writeFilePromised: any = BluebirdPromise.promisify(Fs.writeFile);
|
||||
return writeFilePromised(this.filename, content);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,16 +7,15 @@ import nodemailer = require("nodemailer");
|
|||
import { Nodemailer } from "../../../types/Dependencies";
|
||||
import { Identity } from "../../../types/Identity";
|
||||
import { INotifier } from "../notifiers/INotifier";
|
||||
import { GmailNotifierConfiguration } from "../../../types/Configuration";
|
||||
import { GmailNotifierConfiguration } from "../configuration/Configuration";
|
||||
import path = require("path");
|
||||
|
||||
const email_template = fs.readFileSync(path.join(__dirname, "../../resources/email-template.ejs"), "UTF-8");
|
||||
|
||||
export class GMailNotifier extends INotifier {
|
||||
export class GMailNotifier implements INotifier {
|
||||
private transporter: any;
|
||||
|
||||
constructor(options: GmailNotifierConfiguration, nodemailer: Nodemailer) {
|
||||
super();
|
||||
const transporter = nodemailer.createTransport({
|
||||
service: "gmail",
|
||||
auth: {
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
|
||||
import * as BluebirdPromise from "bluebird";
|
||||
import { Identity } from "../../../types/Identity";
|
||||
|
||||
export interface INotifier {
|
||||
notify(identity: Identity, subject: string, link: string): BluebirdPromise<void>;
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
|
||||
import * as BluebirdPromise from "bluebird";
|
||||
import { Identity } from "../../../types/Identity";
|
||||
|
||||
export abstract class INotifier {
|
||||
abstract notify(identity: Identity, subject: string, link: string): BluebirdPromise<void>;
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
import { NotifierConfiguration } from "../../../types/Configuration";
|
||||
import { NotifierConfiguration } from "../configuration/Configuration";
|
||||
import { Nodemailer } from "../../../types/Dependencies";
|
||||
import { INotifier } from "./INotifier";
|
||||
|
||||
|
|
|
@ -5,14 +5,14 @@ import FirstFactorValidator = require("../FirstFactorValidator");
|
|||
import Exceptions = require("../Exceptions");
|
||||
import ErrorReplies = require("../ErrorReplies");
|
||||
import objectPath = require("object-path");
|
||||
import ServerVariables = require("../ServerVariables");
|
||||
import { ServerVariablesHandler } from "../ServerVariablesHandler";
|
||||
import AuthenticationSession = require("../AuthenticationSession");
|
||||
|
||||
type Handler = (req: express.Request, res: express.Response) => BluebirdPromise<void>;
|
||||
|
||||
export default function (callback: Handler): Handler {
|
||||
return function (req: express.Request, res: express.Response): BluebirdPromise<void> {
|
||||
const logger = ServerVariables.getLogger(req.app);
|
||||
const logger = ServerVariablesHandler.getLogger(req.app);
|
||||
|
||||
const authSession = AuthenticationSession.get(req);
|
||||
logger.debug("AuthSession is %s", JSON.stringify(authSession));
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
|
||||
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import express = require("express");
|
||||
|
||||
export default function (req: express.Request, res: express.Response) {
|
||||
export default function (req: express.Request, res: express.Response): BluebirdPromise<void> {
|
||||
res.render("errors/401");
|
||||
return BluebirdPromise.resolve();
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
|
||||
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import express = require("express");
|
||||
|
||||
export default function (req: express.Request, res: express.Response) {
|
||||
export default function (req: express.Request, res: express.Response): BluebirdPromise<void> {
|
||||
res.render("errors/403");
|
||||
return BluebirdPromise.resolve();
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
|
||||
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import express = require("express");
|
||||
|
||||
export default function (req: express.Request, res: express.Response) {
|
||||
export default function (req: express.Request, res: express.Response): BluebirdPromise<void> {
|
||||
res.render("errors/404");
|
||||
return BluebirdPromise.resolve();
|
||||
}
|
|
@ -4,10 +4,10 @@ import objectPath = require("object-path");
|
|||
import winston = require("winston");
|
||||
import Endpoints = require("../../../endpoints");
|
||||
import AuthenticationValidator = require("../../AuthenticationValidator");
|
||||
import ServerVariables = require("../../ServerVariables");
|
||||
import { ServerVariablesHandler } from "../../ServerVariablesHandler";
|
||||
|
||||
export default function (req: express.Request, res: express.Response) {
|
||||
const logger = ServerVariables.getLogger(req.app);
|
||||
const logger = ServerVariablesHandler.getLogger(req.app);
|
||||
|
||||
logger.debug("First factor: headers are %s", JSON.stringify(req.headers));
|
||||
|
||||
|
|
|
@ -8,16 +8,16 @@ import { AuthenticationRegulator } from "../../AuthenticationRegulator";
|
|||
import { Client, Attributes } from "../../ldap/Client";
|
||||
import Endpoint = require("../../../endpoints");
|
||||
import ErrorReplies = require("../../ErrorReplies");
|
||||
import ServerVariables = require("../../ServerVariables");
|
||||
import { ServerVariablesHandler } from "../../ServerVariablesHandler";
|
||||
import AuthenticationSession = require("../../AuthenticationSession");
|
||||
|
||||
export default function (req: express.Request, res: express.Response): BluebirdPromise<void> {
|
||||
const username: string = req.body.username;
|
||||
const password: string = req.body.password;
|
||||
|
||||
const logger = ServerVariables.getLogger(req.app);
|
||||
const ldap = ServerVariables.getLdapAuthenticator(req.app);
|
||||
const config = ServerVariables.getConfiguration(req.app);
|
||||
const logger = ServerVariablesHandler.getLogger(req.app);
|
||||
const ldap = ServerVariablesHandler.getLdapAuthenticator(req.app);
|
||||
const config = ServerVariablesHandler.getConfiguration(req.app);
|
||||
|
||||
if (!username || !password) {
|
||||
const err = new Error("No username or password");
|
||||
|
@ -25,8 +25,8 @@ export default function (req: express.Request, res: express.Response): BluebirdP
|
|||
return BluebirdPromise.reject(err);
|
||||
}
|
||||
|
||||
const regulator = ServerVariables.getAuthenticationRegulator(req.app);
|
||||
const accessController = ServerVariables.getAccessController(req.app);
|
||||
const regulator = ServerVariablesHandler.getAuthenticationRegulator(req.app);
|
||||
const accessController = ServerVariablesHandler.getAccessController(req.app);
|
||||
const authSession = AuthenticationSession.get(req);
|
||||
|
||||
logger.info("1st factor: Starting authentication of user \"%s\"", username);
|
||||
|
|
|
@ -3,15 +3,15 @@ import express = require("express");
|
|||
import BluebirdPromise = require("bluebird");
|
||||
import objectPath = require("object-path");
|
||||
import exceptions = require("../../../Exceptions");
|
||||
import ServerVariables = require("../../../ServerVariables");
|
||||
import { ServerVariablesHandler } from "../../../ServerVariablesHandler";
|
||||
import AuthenticationSession = require("../../../AuthenticationSession");
|
||||
import ErrorReplies = require("../../../ErrorReplies");
|
||||
|
||||
import Constants = require("./../constants");
|
||||
|
||||
export default function (req: express.Request, res: express.Response): BluebirdPromise<void> {
|
||||
const logger = ServerVariables.getLogger(req.app);
|
||||
const ldapPasswordUpdater = ServerVariables.getLdapPasswordUpdater(req.app);
|
||||
const logger = ServerVariablesHandler.getLogger(req.app);
|
||||
const ldapPasswordUpdater = ServerVariablesHandler.getLdapPasswordUpdater(req.app);
|
||||
const authSession = AuthenticationSession.get(req);
|
||||
|
||||
const newPassword = objectPath.get<express.Request, string>(req, "body.password");
|
||||
|
|
|
@ -8,7 +8,7 @@ import { IdentityValidable } from "../../../IdentityCheckMiddleware";
|
|||
import { PRE_VALIDATION_TEMPLATE } from "../../../IdentityCheckPreValidationTemplate";
|
||||
import Constants = require("../constants");
|
||||
import { Winston } from "winston";
|
||||
import ServerVariables = require("../../../ServerVariables");
|
||||
import { ServerVariablesHandler } from "../../../ServerVariablesHandler";
|
||||
|
||||
export const TEMPLATE_NAME = "password-reset-form";
|
||||
|
||||
|
@ -18,14 +18,14 @@ export default class PasswordResetHandler implements IdentityValidable {
|
|||
}
|
||||
|
||||
preValidationInit(req: express.Request): BluebirdPromise<Identity> {
|
||||
const logger = ServerVariables.getLogger(req.app);
|
||||
const logger = ServerVariablesHandler.getLogger(req.app);
|
||||
const userid: string = objectPath.get<express.Request, string>(req, "query.userid");
|
||||
|
||||
logger.debug("Reset Password: user '%s' requested a password reset", userid);
|
||||
if (!userid)
|
||||
return BluebirdPromise.reject(new exceptions.AccessDeniedError("No user id provided"));
|
||||
|
||||
const emailsRetriever = ServerVariables.getLdapEmailsRetriever(req.app);
|
||||
const emailsRetriever = ServerVariablesHandler.getLdapEmailsRetriever(req.app);
|
||||
return emailsRetriever.retrieve(userid)
|
||||
.then(function (emails: string[]) {
|
||||
if (!emails && emails.length <= 0) throw new Error("No email found");
|
||||
|
|
|
@ -3,7 +3,7 @@ import express = require("express");
|
|||
import objectPath = require("object-path");
|
||||
import winston = require("winston");
|
||||
import Endpoints = require("../../../endpoints");
|
||||
import ServerVariables = require("../../ServerVariables");
|
||||
import { ServerVariablesHandler } from "../../ServerVariablesHandler";
|
||||
import AuthenticationSession = require("../../AuthenticationSession");
|
||||
|
||||
export default function (req: express.Request, res: express.Response) {
|
||||
|
|
|
@ -9,7 +9,7 @@ import { PRE_VALIDATION_TEMPLATE } from "../../../../IdentityCheckPreValidationT
|
|||
import Constants = require("../constants");
|
||||
import Endpoints = require("../../../../../endpoints");
|
||||
import ErrorReplies = require("../../../../ErrorReplies");
|
||||
import ServerVariables = require("../../../../ServerVariables");
|
||||
import { ServerVariablesHandler } from "../../../../ServerVariablesHandler";
|
||||
import AuthenticationSession = require("../../../../AuthenticationSession");
|
||||
|
||||
import FirstFactorValidator = require("../../../../FirstFactorValidator");
|
||||
|
@ -53,7 +53,7 @@ export default class RegistrationHandler implements IdentityValidable {
|
|||
}
|
||||
|
||||
postValidationResponse(req: express.Request, res: express.Response) {
|
||||
const logger = ServerVariables.getLogger(req.app);
|
||||
const logger = ServerVariablesHandler.getLogger(req.app);
|
||||
const authSession = AuthenticationSession.get(req);
|
||||
|
||||
const userid = authSession.identity_check.userid;
|
||||
|
@ -65,12 +65,12 @@ export default class RegistrationHandler implements IdentityValidable {
|
|||
return;
|
||||
}
|
||||
|
||||
const userDataStore = ServerVariables.getUserDataStore(req.app);
|
||||
const totpGenerator = ServerVariables.getTOTPGenerator(req.app);
|
||||
const userDataStore = ServerVariablesHandler.getUserDataStore(req.app);
|
||||
const totpGenerator = ServerVariablesHandler.getTOTPGenerator(req.app);
|
||||
const secret = totpGenerator.generate();
|
||||
|
||||
logger.debug("POST new-totp-secret: save the TOTP secret in DB");
|
||||
userDataStore.set_totp_secret(userid, secret)
|
||||
userDataStore.saveTOTPSecret(userid, secret)
|
||||
.then(function () {
|
||||
objectPath.set(req, "session", undefined);
|
||||
|
||||
|
|
|
@ -2,13 +2,13 @@
|
|||
import exceptions = require("../../../../Exceptions");
|
||||
import objectPath = require("object-path");
|
||||
import express = require("express");
|
||||
import { TOTPSecretDocument } from "../../../../UserDataStore";
|
||||
import { TOTPSecretDocument } from "../../../../storage/TOTPSecretDocument";
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import FirstFactorBlocker from "../../../FirstFactorBlocker";
|
||||
import Endpoints = require("../../../../../endpoints");
|
||||
import redirect from "../../redirect";
|
||||
import ErrorReplies = require("../../../../ErrorReplies");
|
||||
import ServerVariables = require("./../../../../ServerVariables");
|
||||
import { ServerVariablesHandler } from "./../../../../ServerVariablesHandler";
|
||||
import AuthenticationSession = require("../../../../AuthenticationSession");
|
||||
|
||||
const UNAUTHORIZED_MESSAGE = "Unauthorized access";
|
||||
|
@ -16,17 +16,17 @@ const UNAUTHORIZED_MESSAGE = "Unauthorized access";
|
|||
export default FirstFactorBlocker(handler);
|
||||
|
||||
export function handler(req: express.Request, res: express.Response): BluebirdPromise<void> {
|
||||
const logger = ServerVariables.getLogger(req.app);
|
||||
const logger = ServerVariablesHandler.getLogger(req.app);
|
||||
const authSession = AuthenticationSession.get(req);
|
||||
const userid = authSession.userid;
|
||||
logger.info("POST 2ndfactor totp: Initiate TOTP validation for user %s", userid);
|
||||
|
||||
const token = req.body.token;
|
||||
const totpValidator = ServerVariables.getTOTPValidator(req.app);
|
||||
const userDataStore = ServerVariables.getUserDataStore(req.app);
|
||||
const totpValidator = ServerVariablesHandler.getTOTPValidator(req.app);
|
||||
const userDataStore = ServerVariablesHandler.getUserDataStore(req.app);
|
||||
|
||||
logger.debug("POST 2ndfactor totp: Fetching secret for user %s", userid);
|
||||
return userDataStore.get_totp_secret(userid)
|
||||
return userDataStore.retrieveTOTPSecret(userid)
|
||||
.then(function (doc: TOTPSecretDocument) {
|
||||
logger.debug("POST 2ndfactor totp: TOTP secret is %s", JSON.stringify(doc));
|
||||
return totpValidator.validate(token, doc.secret.base32);
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
|
||||
import UserDataStore from "../../../../UserDataStore";
|
||||
import { UserDataStore } from "../../../../storage/UserDataStore";
|
||||
|
||||
import objectPath = require("object-path");
|
||||
import u2f_common = require("../U2FCommon");
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import express = require("express");
|
||||
import U2f = require("u2f");
|
||||
import { U2FRegistration } from "../../../../../../types/U2FRegistration";
|
||||
import FirstFactorBlocker from "../../../FirstFactorBlocker";
|
||||
import redirect from "../../redirect";
|
||||
import ErrorReplies = require("../../../../ErrorReplies");
|
||||
import ServerVariables = require("../../../../ServerVariables");
|
||||
import { ServerVariablesHandler } from "../../../../ServerVariablesHandler";
|
||||
import AuthenticationSession = require("../../../../AuthenticationSession");
|
||||
|
||||
|
||||
|
@ -34,11 +35,11 @@ function handler(req: express.Request, res: express.Response): BluebirdPromise<v
|
|||
}
|
||||
|
||||
|
||||
const userDataStore = ServerVariables.getUserDataStore(req.app);
|
||||
const u2f = ServerVariables.getU2F(req.app);
|
||||
const userDataStore = ServerVariablesHandler.getUserDataStore(req.app);
|
||||
const u2f = ServerVariablesHandler.getU2F(req.app);
|
||||
const userid = authSession.userid;
|
||||
const appid = u2f_common.extract_app_id(req);
|
||||
const logger = ServerVariables.getLogger(req.app);
|
||||
const logger = ServerVariablesHandler.getLogger(req.app);
|
||||
|
||||
const registrationResponse: U2f.RegistrationData = req.body;
|
||||
|
||||
|
@ -54,7 +55,11 @@ function handler(req: express.Request, res: express.Response): BluebirdPromise<v
|
|||
const registrationResult: U2f.RegistrationResult = u2fResult as U2f.RegistrationResult;
|
||||
logger.info("U2F register: Store regisutration and reply");
|
||||
logger.debug("U2F register: registration = %s", JSON.stringify(registrationResult));
|
||||
return userDataStore.set_u2f_meta(userid, appid, registrationResult.keyHandle, registrationResult.publicKey);
|
||||
const registration: U2FRegistration = {
|
||||
keyHandle: registrationResult.keyHandle,
|
||||
publicKey: registrationResult.publicKey
|
||||
};
|
||||
return userDataStore.saveU2FRegistration(userid, appid, registration);
|
||||
})
|
||||
.then(function () {
|
||||
authSession.identity_check = undefined;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
import UserDataStore from "../../../../UserDataStore";
|
||||
import { UserDataStore } from "../../../../storage/UserDataStore";
|
||||
|
||||
import objectPath = require("object-path");
|
||||
import u2f_common = require("../U2FCommon");
|
||||
|
@ -8,13 +8,13 @@ import express = require("express");
|
|||
import U2f = require("u2f");
|
||||
import FirstFactorBlocker from "../../../FirstFactorBlocker";
|
||||
import ErrorReplies = require("../../../../ErrorReplies");
|
||||
import ServerVariables = require("../../../../ServerVariables");
|
||||
import { ServerVariablesHandler } from "../../../../ServerVariablesHandler";
|
||||
import AuthenticationSession = require("../../../../AuthenticationSession");
|
||||
|
||||
export default FirstFactorBlocker(handler);
|
||||
|
||||
function handler(req: express.Request, res: express.Response): BluebirdPromise<void> {
|
||||
const logger = ServerVariables.getLogger(req.app);
|
||||
const logger = ServerVariablesHandler.getLogger(req.app);
|
||||
const authSession = AuthenticationSession.get(req);
|
||||
|
||||
if (!authSession.identity_check
|
||||
|
@ -24,7 +24,7 @@ function handler(req: express.Request, res: express.Response): BluebirdPromise<v
|
|||
return;
|
||||
}
|
||||
|
||||
const u2f = ServerVariables.getU2F(req.app);
|
||||
const u2f = ServerVariablesHandler.getU2F(req.app);
|
||||
const appid: string = u2f_common.extract_app_id(req);
|
||||
|
||||
logger.debug("U2F register_request: headers=%s", JSON.stringify(req.headers));
|
||||
|
|
|
@ -3,49 +3,49 @@ import objectPath = require("object-path");
|
|||
import u2f_common = require("../U2FCommon");
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import express = require("express");
|
||||
import UserDataStore, { U2FRegistrationDocument } from "../../../../UserDataStore";
|
||||
import { UserDataStore } from "../../../../storage/UserDataStore";
|
||||
import { U2FRegistrationDocument } from "../../../../storage/U2FRegistrationDocument";
|
||||
import { Winston } from "../../../../../../types/Dependencies";
|
||||
import U2f = require("u2f");
|
||||
import exceptions = require("../../../../Exceptions");
|
||||
import FirstFactorBlocker from "../../../FirstFactorBlocker";
|
||||
import redirect from "../../redirect";
|
||||
import ErrorReplies = require("../../../../ErrorReplies");
|
||||
import ServerVariables = require("../../../../ServerVariables");
|
||||
import { ServerVariablesHandler } from "../../../../ServerVariablesHandler";
|
||||
import AuthenticationSession = require("../../../../AuthenticationSession");
|
||||
|
||||
export default FirstFactorBlocker(handler);
|
||||
|
||||
|
||||
export function handler(req: express.Request, res: express.Response): BluebirdPromise<void> {
|
||||
const logger = ServerVariables.getLogger(req.app);
|
||||
const userDataStore = ServerVariables.getUserDataStore(req.app);
|
||||
const authSession = AuthenticationSession.get(req);
|
||||
const logger = ServerVariablesHandler.getLogger(req.app);
|
||||
const userDataStore = ServerVariablesHandler.getUserDataStore(req.app);
|
||||
const authSession = AuthenticationSession.get(req);
|
||||
|
||||
if (!authSession.sign_request) {
|
||||
const err = new Error("No sign request");
|
||||
ErrorReplies.replyWithError401(res, logger)(err);
|
||||
return BluebirdPromise.reject(err);
|
||||
}
|
||||
if (!authSession.sign_request) {
|
||||
const err = new Error("No sign request");
|
||||
ErrorReplies.replyWithError401(res, logger)(err);
|
||||
return BluebirdPromise.reject(err);
|
||||
}
|
||||
|
||||
const userid = authSession.userid;
|
||||
const appid = u2f_common.extract_app_id(req);
|
||||
return userDataStore.get_u2f_meta(userid, appid)
|
||||
.then(function (doc: U2FRegistrationDocument): BluebirdPromise<U2f.SignatureResult | U2f.Error> {
|
||||
const appid = u2f_common.extract_app_id(req);
|
||||
const u2f = ServerVariables.getU2F(req.app);
|
||||
const signRequest = authSession.sign_request;
|
||||
const signData: U2f.SignatureData = req.body;
|
||||
logger.info("U2F sign: Finish authentication");
|
||||
return BluebirdPromise.resolve(u2f.checkSignature(signRequest, signData, doc.publicKey));
|
||||
})
|
||||
.then(function (result: U2f.SignatureResult | U2f.Error): BluebirdPromise<void> {
|
||||
if (objectPath.has(result, "errorCode"))
|
||||
return BluebirdPromise.reject(new Error("Error while signing"));
|
||||
logger.info("U2F sign: Authentication successful");
|
||||
authSession.second_factor = true;
|
||||
redirect(req, res);
|
||||
return BluebirdPromise.resolve();
|
||||
})
|
||||
.catch(ErrorReplies.replyWithError500(res, logger));
|
||||
const userid = authSession.userid;
|
||||
const appid = u2f_common.extract_app_id(req);
|
||||
return userDataStore.retrieveU2FRegistration(userid, appid)
|
||||
.then(function (doc: U2FRegistrationDocument): BluebirdPromise<U2f.SignatureResult | U2f.Error> {
|
||||
const appId = u2f_common.extract_app_id(req);
|
||||
const u2f = ServerVariablesHandler.getU2F(req.app);
|
||||
const signRequest = authSession.sign_request;
|
||||
const signData: U2f.SignatureData = req.body;
|
||||
logger.info("U2F sign: Finish authentication");
|
||||
return BluebirdPromise.resolve(u2f.checkSignature(signRequest, signData, doc.registration.publicKey));
|
||||
})
|
||||
.then(function (result: U2f.SignatureResult | U2f.Error): BluebirdPromise<void> {
|
||||
if (objectPath.has(result, "errorCode"))
|
||||
return BluebirdPromise.reject(new Error("Error while signing"));
|
||||
logger.info("U2F sign: Authentication successful");
|
||||
authSession.second_factor = true;
|
||||
redirect(req, res);
|
||||
return BluebirdPromise.resolve();
|
||||
})
|
||||
.catch(ErrorReplies.replyWithError500(res, logger));
|
||||
}
|
||||
|
||||
|
|
|
@ -4,39 +4,39 @@ import U2f = require("u2f");
|
|||
import u2f_common = require("../../../secondfactor/u2f/U2FCommon");
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import express = require("express");
|
||||
import UserDataStore, { U2FRegistrationDocument } from "../../../../UserDataStore";
|
||||
import { UserDataStore } from "../../../../storage/UserDataStore";
|
||||
import { U2FRegistrationDocument } from "../../../../storage/U2FRegistrationDocument";
|
||||
import { Winston } from "../../../../../../types/Dependencies";
|
||||
import exceptions = require("../../../../Exceptions");
|
||||
import { SignMessage } from "./SignMessage";
|
||||
import FirstFactorBlocker from "../../../FirstFactorBlocker";
|
||||
import ErrorReplies = require("../../../../ErrorReplies");
|
||||
import ServerVariables = require("../../../../ServerVariables");
|
||||
import { ServerVariablesHandler } from "../../../../ServerVariablesHandler";
|
||||
import AuthenticationSession = require("../../../../AuthenticationSession");
|
||||
|
||||
export default FirstFactorBlocker(handler);
|
||||
|
||||
|
||||
export function handler(req: express.Request, res: express.Response): BluebirdPromise<void> {
|
||||
const logger = ServerVariables.getLogger(req.app);
|
||||
const userDataStore = ServerVariables.getUserDataStore(req.app);
|
||||
const logger = ServerVariablesHandler.getLogger(req.app);
|
||||
const userDataStore = ServerVariablesHandler.getUserDataStore(req.app);
|
||||
const authSession = AuthenticationSession.get(req);
|
||||
|
||||
const userid = authSession.userid;
|
||||
const appid = u2f_common.extract_app_id(req);
|
||||
return userDataStore.get_u2f_meta(userid, appid)
|
||||
const userId = authSession.userid;
|
||||
const appId = u2f_common.extract_app_id(req);
|
||||
return userDataStore.retrieveU2FRegistration(userId, appId)
|
||||
.then(function (doc: U2FRegistrationDocument): BluebirdPromise<SignMessage> {
|
||||
if (!doc)
|
||||
return BluebirdPromise.reject(new exceptions.AccessDeniedError("No U2F registration found"));
|
||||
|
||||
const u2f = ServerVariables.getU2F(req.app);
|
||||
const u2f = ServerVariablesHandler.getU2F(req.app);
|
||||
const appId: string = u2f_common.extract_app_id(req);
|
||||
logger.info("U2F sign_request: Start authentication to app %s", appId);
|
||||
logger.debug("U2F sign_request: appId=%s, keyHandle=%s", appId, JSON.stringify(doc.keyHandle));
|
||||
logger.debug("U2F sign_request: appId=%s, keyHandle=%s", appId, JSON.stringify(doc.registration.keyHandle));
|
||||
|
||||
const request = u2f.request(appId, doc.keyHandle);
|
||||
const request = u2f.request(appId, doc.registration.keyHandle);
|
||||
const authenticationMessage: SignMessage = {
|
||||
request: request,
|
||||
keyHandle: doc.keyHandle
|
||||
keyHandle: doc.registration.keyHandle
|
||||
};
|
||||
return BluebirdPromise.resolve(authenticationMessage);
|
||||
})
|
||||
|
|
|
@ -8,12 +8,12 @@ import exceptions = require("../../Exceptions");
|
|||
import winston = require("winston");
|
||||
import AuthenticationValidator = require("../../AuthenticationValidator");
|
||||
import ErrorReplies = require("../../ErrorReplies");
|
||||
import ServerVariables = require("../../ServerVariables");
|
||||
import { ServerVariablesHandler } from "../../ServerVariablesHandler";
|
||||
import AuthenticationSession = require("../../AuthenticationSession");
|
||||
|
||||
function verify_filter(req: express.Request, res: express.Response): BluebirdPromise<void> {
|
||||
const logger = ServerVariables.getLogger(req.app);
|
||||
const accessController = ServerVariables.getAccessController(req.app);
|
||||
const logger = ServerVariablesHandler.getLogger(req.app);
|
||||
const accessController = ServerVariablesHandler.getAccessController(req.app);
|
||||
const authSession = AuthenticationSession.get(req);
|
||||
|
||||
logger.debug("Verify: headers are %s", JSON.stringify(req.headers));
|
||||
|
@ -40,7 +40,7 @@ function verify_filter(req: express.Request, res: express.Response): BluebirdPro
|
|||
}
|
||||
|
||||
export default function (req: express.Request, res: express.Response) {
|
||||
const logger = ServerVariables.getLogger(req.app);
|
||||
const logger = ServerVariablesHandler.getLogger(req.app);
|
||||
verify_filter(req, res)
|
||||
.then(function () {
|
||||
res.status(204);
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
export interface AuthenticationTraceDocument {
|
||||
userId: string;
|
||||
date: Date;
|
||||
isAuthenticationSuccessful: boolean;
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import { ICollectionFactory } from "./ICollectionFactory";
|
||||
import { NedbCollectionFactory, NedbOptions } from "./nedb/NedbCollectionFactory";
|
||||
import { MongoCollectionFactory } from "./mongo/MongoCollectionFactory";
|
||||
import { IMongoClient } from "../connectors/mongo/IMongoClient";
|
||||
|
||||
|
||||
export class CollectionFactoryFactory {
|
||||
static createNedb(options: NedbOptions): ICollectionFactory {
|
||||
return new NedbCollectionFactory(options);
|
||||
}
|
||||
|
||||
static createMongo(client: IMongoClient): ICollectionFactory {
|
||||
return new MongoCollectionFactory(client);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
/* istanbul ignore next */
|
||||
import BluebirdPromise = require("bluebird");
|
||||
|
||||
/* istanbul ignore next */
|
||||
export interface ICollection {
|
||||
find(query: any, sortKeys: any, count: number): BluebirdPromise<any>;
|
||||
findOne(query: any): BluebirdPromise<any>;
|
||||
update(query: any, updateQuery: any, options?: any): BluebirdPromise<any>;
|
||||
remove(query: any): BluebirdPromise<any>;
|
||||
insert(document: any): BluebirdPromise<any>;
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
import { ICollection } from "./ICollection";
|
||||
|
||||
export interface ICollectionFactory {
|
||||
build(collectionName: string): ICollection;
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
import BluebirdPromise = require("bluebird");
|
||||
import { TOTPSecretDocument } from "./TOTPSecretDocument";
|
||||
import { U2FRegistrationDocument } from "./U2FRegistrationDocument";
|
||||
import { U2FRegistration } from "../../../types/U2FRegistration";
|
||||
import { TOTPSecret } from "../../../types/TOTPSecret";
|
||||
import { AuthenticationTraceDocument } from "./AuthenticationTraceDocument";
|
||||
import { IdentityValidationDocument } from "./IdentityValidationDocument";
|
||||
|
||||
export interface IUserDataStore {
|
||||
saveU2FRegistration(userId: string, appId: string, registration: U2FRegistration): BluebirdPromise<void>;
|
||||
retrieveU2FRegistration(userId: string, appId: string): BluebirdPromise<U2FRegistrationDocument>;
|
||||
|
||||
saveAuthenticationTrace(userId: string, isAuthenticationSuccessful: boolean): BluebirdPromise<void>;
|
||||
retrieveLatestAuthenticationTraces(userId: string, isAuthenticationSuccessful: boolean, count: number): BluebirdPromise<AuthenticationTraceDocument[]>;
|
||||
|
||||
produceIdentityValidationToken(userId: string, token: string, challenge: string, maxAge: number): BluebirdPromise<any>;
|
||||
consumeIdentityValidationToken(token: string, challenge: string): BluebirdPromise<IdentityValidationDocument>;
|
||||
|
||||
saveTOTPSecret(userId: string, secret: TOTPSecret): BluebirdPromise<void>;
|
||||
retrieveTOTPSecret(userId: string): BluebirdPromise<TOTPSecretDocument>;
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
|
||||
export interface IdentityValidationDocument {
|
||||
userId: string;
|
||||
token: string;
|
||||
challenge: string;
|
||||
maxDate: Date;
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import { TOTPSecret } from "../../../types/TOTPSecret";
|
||||
|
||||
export interface TOTPSecretDocument {
|
||||
userid: string;
|
||||
secret: TOTPSecret;
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
import { U2FRegistration } from "../../../types/U2FRegistration";
|
||||
|
||||
export interface U2FRegistrationDocument {
|
||||
userId: string;
|
||||
appId: string;
|
||||
registration: U2FRegistration;
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
import * as BluebirdPromise from "bluebird";
|
||||
import * as path from "path";
|
||||
import { IUserDataStore } from "./IUserDataStore";
|
||||
import { ICollection } from "./ICollection";
|
||||
import { ICollectionFactory } from "./ICollectionFactory";
|
||||
import { TOTPSecretDocument } from "./TOTPSecretDocument";
|
||||
import { U2FRegistrationDocument } from "./U2FRegistrationDocument";
|
||||
import { U2FRegistration } from "../../../types/U2FRegistration";
|
||||
import { TOTPSecret } from "../../../types/TOTPSecret";
|
||||
import { AuthenticationTraceDocument } from "./AuthenticationTraceDocument";
|
||||
import { IdentityValidationDocument } from "./IdentityValidationDocument";
|
||||
|
||||
// Constants
|
||||
|
||||
const IDENTITY_VALIDATION_TOKENS_COLLECTION_NAME = "identity_validation_tokens";
|
||||
const AUTHENTICATION_TRACES_COLLECTION_NAME = "authentication_traces";
|
||||
|
||||
const U2F_REGISTRATIONS_COLLECTION_NAME = "u2f_registrations";
|
||||
const TOTP_SECRETS_COLLECTION_NAME = "totp_secrets";
|
||||
|
||||
|
||||
export interface U2FRegistrationKey {
|
||||
userId: string;
|
||||
appId: string;
|
||||
}
|
||||
|
||||
// Source
|
||||
|
||||
export class UserDataStore implements IUserDataStore {
|
||||
private u2fSecretCollection: ICollection;
|
||||
private identityCheckTokensCollection: ICollection;
|
||||
private authenticationTracesCollection: ICollection;
|
||||
private totpSecretCollection: ICollection;
|
||||
|
||||
private collectionFactory: ICollectionFactory;
|
||||
|
||||
constructor(collectionFactory: ICollectionFactory) {
|
||||
this.collectionFactory = collectionFactory;
|
||||
|
||||
this.u2fSecretCollection = this.collectionFactory.build(U2F_REGISTRATIONS_COLLECTION_NAME);
|
||||
this.identityCheckTokensCollection = this.collectionFactory.build(IDENTITY_VALIDATION_TOKENS_COLLECTION_NAME);
|
||||
this.authenticationTracesCollection = this.collectionFactory.build(AUTHENTICATION_TRACES_COLLECTION_NAME);
|
||||
this.totpSecretCollection = this.collectionFactory.build(TOTP_SECRETS_COLLECTION_NAME);
|
||||
}
|
||||
|
||||
saveU2FRegistration(userId: string, appId: string, registration: U2FRegistration): BluebirdPromise<void> {
|
||||
const newDocument: U2FRegistrationDocument = {
|
||||
userId: userId,
|
||||
appId: appId,
|
||||
registration: registration
|
||||
};
|
||||
|
||||
const filter: U2FRegistrationKey = {
|
||||
userId: userId,
|
||||
appId: appId
|
||||
};
|
||||
|
||||
return this.u2fSecretCollection.update(filter, newDocument, { upsert: true });
|
||||
}
|
||||
|
||||
retrieveU2FRegistration(userId: string, appId: string): BluebirdPromise<U2FRegistrationDocument> {
|
||||
const filter: U2FRegistrationKey = {
|
||||
userId: userId,
|
||||
appId: appId
|
||||
};
|
||||
return this.u2fSecretCollection.findOne(filter);
|
||||
}
|
||||
|
||||
saveAuthenticationTrace(userId: string, isAuthenticationSuccessful: boolean): BluebirdPromise<void> {
|
||||
const newDocument: AuthenticationTraceDocument = {
|
||||
userId: userId,
|
||||
date: new Date(),
|
||||
isAuthenticationSuccessful: isAuthenticationSuccessful,
|
||||
};
|
||||
|
||||
return this.authenticationTracesCollection.insert(newDocument);
|
||||
}
|
||||
|
||||
retrieveLatestAuthenticationTraces(userId: string, isAuthenticationSuccessful: boolean, count: number): BluebirdPromise<AuthenticationTraceDocument[]> {
|
||||
const q = {
|
||||
userId: userId,
|
||||
isAuthenticationSuccessful: isAuthenticationSuccessful
|
||||
};
|
||||
|
||||
return this.authenticationTracesCollection.find(q, { date: -1 }, count);
|
||||
}
|
||||
|
||||
produceIdentityValidationToken(userId: string, token: string, challenge: string, maxAge: number): BluebirdPromise<any> {
|
||||
const newDocument: IdentityValidationDocument = {
|
||||
userId: userId,
|
||||
token: token,
|
||||
challenge: challenge,
|
||||
maxDate: new Date(new Date().getTime() + maxAge)
|
||||
};
|
||||
|
||||
return this.identityCheckTokensCollection.insert(newDocument);
|
||||
}
|
||||
|
||||
consumeIdentityValidationToken(token: string, challenge: string): BluebirdPromise<IdentityValidationDocument> {
|
||||
const that = this;
|
||||
const filter = {
|
||||
token: token,
|
||||
challenge: challenge
|
||||
};
|
||||
|
||||
let identityValidationDocument: IdentityValidationDocument;
|
||||
|
||||
return this.identityCheckTokensCollection.findOne(filter)
|
||||
.then(function (doc: IdentityValidationDocument) {
|
||||
if (!doc) {
|
||||
return BluebirdPromise.reject(new Error("Registration token does not exist"));
|
||||
}
|
||||
|
||||
identityValidationDocument = doc;
|
||||
const current_date = new Date();
|
||||
if (current_date > doc.maxDate)
|
||||
return BluebirdPromise.reject(new Error("Registration token is not valid anymore"));
|
||||
|
||||
return that.identityCheckTokensCollection.remove(filter);
|
||||
})
|
||||
.then(() => {
|
||||
return BluebirdPromise.resolve(identityValidationDocument);
|
||||
});
|
||||
}
|
||||
|
||||
saveTOTPSecret(userId: string, secret: TOTPSecret): BluebirdPromise<void> {
|
||||
const doc = {
|
||||
userId: userId,
|
||||
secret: secret
|
||||
};
|
||||
|
||||
const filter = {
|
||||
userId: userId
|
||||
};
|
||||
return this.totpSecretCollection.update(filter, doc, { upsert: true });
|
||||
}
|
||||
|
||||
retrieveTOTPSecret(userId: string): BluebirdPromise<TOTPSecretDocument> {
|
||||
const filter = {
|
||||
userId: userId
|
||||
};
|
||||
return this.totpSecretCollection.findOne(filter);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import { ICollection } from "../ICollection";
|
||||
import MongoDB = require("mongodb");
|
||||
|
||||
|
||||
export class MongoCollection implements ICollection {
|
||||
private collection: MongoDB.Collection;
|
||||
|
||||
constructor(collection: MongoDB.Collection) {
|
||||
this.collection = collection;
|
||||
}
|
||||
|
||||
find(query: any, sortKeys?: any, count?: number): BluebirdPromise<any> {
|
||||
const q = this.collection.find(query).sort(sortKeys).limit(count);
|
||||
const toArrayAsync = BluebirdPromise.promisify(q.toArray, { context: q });
|
||||
return toArrayAsync();
|
||||
}
|
||||
|
||||
findOne(query: any): BluebirdPromise<any> {
|
||||
const findOneAsync = BluebirdPromise.promisify<any, any>(this.collection.findOne, { context: this.collection });
|
||||
return findOneAsync(query);
|
||||
}
|
||||
|
||||
update(query: any, updateQuery: any, options?: any): BluebirdPromise<any> {
|
||||
const updateAsync = BluebirdPromise.promisify<any, any, any, any>(this.collection.update, { context: this.collection });
|
||||
return updateAsync(query, updateQuery, options);
|
||||
}
|
||||
|
||||
remove(query: any): BluebirdPromise<any> {
|
||||
const removeAsync = BluebirdPromise.promisify<any, any>(this.collection.remove, { context: this.collection });
|
||||
return removeAsync(query);
|
||||
}
|
||||
|
||||
insert(document: any): BluebirdPromise<any> {
|
||||
const insertAsync = BluebirdPromise.promisify<any, any>(this.collection.insert, { context: this.collection });
|
||||
return insertAsync(document);
|
||||
}
|
||||
|
||||
count(query: any): BluebirdPromise<number> {
|
||||
const countAsync = BluebirdPromise.promisify<any, any>(this.collection.count, { context: this.collection });
|
||||
return countAsync(query);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
import BluebirdPromise = require("bluebird");
|
||||
import { ICollection } from "../ICollection";
|
||||
import { ICollectionFactory } from "../ICollectionFactory";
|
||||
import { MongoCollection } from "./MongoCollection";
|
||||
import path = require("path");
|
||||
import MongoDB = require("mongodb");
|
||||
import { IMongoClient } from "../../connectors/mongo/IMongoClient";
|
||||
|
||||
export class MongoCollectionFactory implements ICollectionFactory {
|
||||
private mongoClient: IMongoClient;
|
||||
|
||||
constructor(mongoClient: IMongoClient) {
|
||||
this.mongoClient = mongoClient;
|
||||
}
|
||||
|
||||
build(collectionName: string): ICollection {
|
||||
return new MongoCollection(this.mongoClient.collection(collectionName));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
import BluebirdPromise = require("bluebird");
|
||||
import { ICollection } from "../ICollection";
|
||||
import Nedb = require("nedb");
|
||||
import { NedbAsync } from "nedb";
|
||||
|
||||
|
||||
export class NedbCollection implements ICollection {
|
||||
private collection: NedbAsync;
|
||||
|
||||
constructor(options: Nedb.DataStoreOptions) {
|
||||
this.collection = BluebirdPromise.promisifyAll(new Nedb(options)) as NedbAsync;
|
||||
}
|
||||
|
||||
find(query: any, sortKeys?: any, count?: number): BluebirdPromise<any> {
|
||||
const q = this.collection.find(query).sort(sortKeys).limit(count);
|
||||
return BluebirdPromise.promisify(q.exec, { context: q })();
|
||||
}
|
||||
|
||||
findOne(query: any): BluebirdPromise<any> {
|
||||
return this.collection.findOneAsync(query);
|
||||
}
|
||||
|
||||
update(query: any, updateQuery: any, options?: any): BluebirdPromise<any> {
|
||||
return this.collection.updateAsync(query, updateQuery, options);
|
||||
}
|
||||
|
||||
remove(query: any): BluebirdPromise<any> {
|
||||
return this.collection.removeAsync(query);
|
||||
}
|
||||
|
||||
insert(document: any): BluebirdPromise<any> {
|
||||
return this.collection.insertAsync(document);
|
||||
}
|
||||
|
||||
count(query: any): BluebirdPromise<number> {
|
||||
return this.collection.countAsync(query);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
import { ICollection } from "../ICollection";
|
||||
import { ICollectionFactory } from "../ICollectionFactory";
|
||||
import { NedbCollection } from "./NedbCollection";
|
||||
import path = require("path");
|
||||
import Nedb = require("nedb");
|
||||
|
||||
export interface NedbOptions {
|
||||
inMemoryOnly?: boolean;
|
||||
directory?: string;
|
||||
}
|
||||
|
||||
export class NedbCollectionFactory implements ICollectionFactory {
|
||||
private options: NedbOptions;
|
||||
|
||||
constructor(options: NedbOptions) {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
build(collectionName: string): ICollection {
|
||||
const datastoreOptions = {
|
||||
inMemoryOnly: this.options.inMemoryOnly || false,
|
||||
autoload: true,
|
||||
filename: (this.options.directory) ? path.resolve(this.options.directory, collectionName) : undefined
|
||||
};
|
||||
|
||||
return new NedbCollection(datastoreOptions);
|
||||
}
|
||||
}
|
|
@ -8,4 +8,4 @@ block form-header
|
|||
<img class="header-img" src="/img/warning.png" alt="">
|
||||
|
||||
block content
|
||||
<p>You are not authorized.</p>
|
||||
<p>You are either not authorized or not <a href="/">logged in</a>.</p>
|
||||
|
|
|
@ -8,4 +8,4 @@ block form-header
|
|||
<img class="header-img" src="/img/warning.png" alt="">
|
||||
|
||||
block content
|
||||
<p>You are not authorized.</p>
|
||||
<p>You are either not authorized or not <a href="/">logged in</a>.</p>
|
||||
|
|
|
@ -12,7 +12,7 @@ block content
|
|||
</div>
|
||||
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
|
||||
<!-- <label class="checkbox pull-left"><input type="checkbox" value="remember-me">Remember me</label> -->
|
||||
a(href=reset_password_request_endpoint, class="pull-right link") Forgot password?
|
||||
a(href=reset_password_request_endpoint, class="pull-right link forgot-password") Forgot password?
|
||||
<span class="clearfix"></span>
|
||||
</form>
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ html
|
|||
</div>
|
||||
</div>
|
||||
<div class="row poweredby-block">
|
||||
<div class="poweredby col-xs-10 col-xs-offset-1 col-sm-2 col-sm-offset-8 col-md-6 col-md-offset-4">Powered by <a class="authelia-brand" href="https://github.com/clems4ever/authelia">Authelia</a></div>
|
||||
<div class="poweredby col-xs-6 col-xs-offset-4 col-sm-6 col-sm-offset-4 col-md-6 col-md-offset-4">Powered by <a class="authelia-brand" href="https://github.com/clems4ever/authelia">Authelia</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -9,14 +9,14 @@ block content
|
|||
<div class="form-inputs">
|
||||
<input type="text" class="form-control" id="token" placeholder="Token" required autofocus>
|
||||
</div>
|
||||
<button class="btn btn-lg btn-primary btn-block" type="submit">TOTP</button>
|
||||
a(href=totp_identity_start_endpoint, class="pull-right link") Need to register?
|
||||
<button class="btn btn-lg btn-primary btn-block totp-button" type="submit">TOTP</button>
|
||||
a(href=totp_identity_start_endpoint, class="pull-right link register-totp") Need to register?
|
||||
<span class="clearfix"></span>
|
||||
</form>
|
||||
<hr>
|
||||
<form class="form-signin u2f">
|
||||
<button class="btn btn-lg btn-primary btn-block" type="submit">U2F</button>
|
||||
a(href=u2f_identity_start_endpoint, class="pull-right link") Need to register?
|
||||
<button class="btn btn-lg btn-primary btn-block u2f-button" type="submit">U2F</button>
|
||||
a(href=u2f_identity_start_endpoint, class="pull-right link register-u2f") Need to register?
|
||||
<span class="clearfix"></span>
|
||||
</form>
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
|
||||
export interface U2FRegistration {
|
||||
keyHandle: string;
|
||||
publicKey: string;
|
||||
}
|
|
@ -5,8 +5,9 @@ declare module "nedb" {
|
|||
export class NedbAsync extends Nedb {
|
||||
constructor(pathOrOptions?: string | Nedb.DataStoreOptions);
|
||||
updateAsync(query: any, updateQuery: any, options?: Nedb.UpdateOptions): BluebirdPromise<any>;
|
||||
findOneAsync(query: any): BluebirdPromise<any>;
|
||||
findOneAsync<T>(query: any): BluebirdPromise<T>;
|
||||
insertAsync<T>(newDoc: T): BluebirdPromise<any>;
|
||||
removeAsync(query: any): BluebirdPromise<any>;
|
||||
countAsync(query: any): BluebirdPromise<number>
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
Feature: User has access restricted access to domains
|
||||
|
||||
Scenario: User john has admin access
|
||||
When I register TOTP and login with user "john" and password "password"
|
||||
Then I have access to:
|
||||
| url |
|
||||
| https://public.test.local:8080/secret.html |
|
||||
| https://secret.test.local:8080/secret.html |
|
||||
| https://secret1.test.local:8080/secret.html |
|
||||
| https://secret2.test.local:8080/secret.html |
|
||||
| https://mx1.mail.test.local:8080/secret.html |
|
||||
| https://mx2.mail.test.local:8080/secret.html |
|
||||
|
||||
Scenario: User bob has restricted access
|
||||
When I register TOTP and login with user "bob" and password "password"
|
||||
Then I have access to:
|
||||
| url |
|
||||
| https://public.test.local:8080/secret.html |
|
||||
| https://secret.test.local:8080/secret.html |
|
||||
| https://secret2.test.local:8080/secret.html |
|
||||
| https://mx1.mail.test.local:8080/secret.html |
|
||||
| https://mx2.mail.test.local:8080/secret.html |
|
||||
And I have no access to:
|
||||
| url |
|
||||
| https://secret1.test.local:8080/secret.html |
|
||||
|
||||
Scenario: User harry has restricted access
|
||||
When I register TOTP and login with user "harry" and password "password"
|
||||
Then I have access to:
|
||||
| url |
|
||||
| https://public.test.local:8080/secret.html |
|
||||
| https://secret1.test.local:8080/secret.html |
|
||||
And I have no access to:
|
||||
| url |
|
||||
| https://secret.test.local:8080/secret.html |
|
||||
| https://secret2.test.local:8080/secret.html |
|
||||
| https://mx1.mail.test.local:8080/secret.html |
|
||||
| https://mx2.mail.test.local:8080/secret.html |
|
|
@ -0,0 +1,53 @@
|
|||
Feature: User validate first factor
|
||||
|
||||
Scenario: User succeeds first factor
|
||||
Given I visit "https://auth.test.local:8080/"
|
||||
When I set field "username" to "bob"
|
||||
And I set field "password" to "password"
|
||||
And I click on "Sign in"
|
||||
Then I'm redirected to "https://auth.test.local:8080/secondfactor"
|
||||
|
||||
Scenario: User fails first factor
|
||||
Given I visit "https://auth.test.local:8080/"
|
||||
When I set field "username" to "john"
|
||||
And I set field "password" to "bad-password"
|
||||
And I click on "Sign in"
|
||||
Then I get a notification with message "Error during authentication: Authetication failed. Please check your credentials."
|
||||
|
||||
Scenario: User succeeds TOTP second factor
|
||||
Given I visit "https://auth.test.local:8080/"
|
||||
And I login with user "john" and password "password"
|
||||
And I register a TOTP secret called "Sec0"
|
||||
When I visit "https://secret.test.local:8080/secret.html" and get redirected "https://auth.test.local:8080/"
|
||||
And I login with user "john" and password "password"
|
||||
And I use "Sec0" as TOTP token handle
|
||||
And I click on "TOTP"
|
||||
Then I'm redirected to "https://secret.test.local:8080/secret.html"
|
||||
|
||||
Scenario: User fails TOTP second factor
|
||||
When I visit "https://secret.test.local:8080/secret.html" and get redirected "https://auth.test.local:8080/"
|
||||
And I login with user "john" and password "password"
|
||||
And I use "BADTOKEN" as TOTP token
|
||||
And I click on "TOTP"
|
||||
Then I get a notification with message "Error while validating TOTP token. Cause: error"
|
||||
|
||||
Scenario: User logs out
|
||||
Given I visit "https://auth.test.local:8080/"
|
||||
And I login with user "john" and password "password"
|
||||
And I register a TOTP secret called "Sec0"
|
||||
And I visit "https://auth.test.local:8080/"
|
||||
And I login with user "john" and password "password"
|
||||
And I use "Sec0" as TOTP token handle
|
||||
When I visit "https://auth.test.local:8080/logout?redirect=https://www.google.fr"
|
||||
And I visit "https://secret.test.local:8080/secret.html"
|
||||
Then I'm redirected to "https://auth.test.local:8080/"
|
||||
|
||||
Scenario: Logout redirects user
|
||||
Given I visit "https://auth.test.local:8080/"
|
||||
And I login with user "john" and password "password"
|
||||
And I register a TOTP secret called "Sec0"
|
||||
And I visit "https://auth.test.local:8080/"
|
||||
And I login with user "john" and password "password"
|
||||
And I use "Sec0" as TOTP token handle
|
||||
When I visit "https://auth.test.local:8080/logout?redirect=https://www.google.fr"
|
||||
Then I'm redirected to "https://www.google.fr"
|
|
@ -0,0 +1,7 @@
|
|||
Feature: User is redirected to authelia when he is not authenticated
|
||||
|
||||
Scenario: User is redirected to authelia
|
||||
Given I'm on https://home.test.local:8080
|
||||
When I click on the link to secret.test.local
|
||||
Then I'm redirected to "https://auth.test.local:8080/"
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
Feature: User is able to reset his password
|
||||
|
||||
Scenario: User is redirected to password reset page
|
||||
Given I'm on https://auth.test.local:8080
|
||||
When I click on the link "Forgot password?"
|
||||
Then I'm redirected to "https://auth.test.local:8080/password-reset/request"
|
||||
|
||||
Scenario: User get an email with a link to reset password
|
||||
Given I'm on https://auth.test.local:8080/password-reset/request
|
||||
When I set field "username" to "james"
|
||||
And I click on "Reset Password"
|
||||
Then I get a notification with message "An email has been sent. Click on the link to change your password."
|
||||
|
||||
Scenario: User resets his password
|
||||
Given I'm on https://auth.test.local:8080/password-reset/request
|
||||
And I set field "username" to "james"
|
||||
And I click on "Reset Password"
|
||||
When I click on the link of the email
|
||||
And I set field "password1" to "newpassword"
|
||||
And I set field "password2" to "newpassword"
|
||||
And I click on "Reset Password"
|
||||
Then I'm redirected to "https://auth.test.local:8080/"
|
||||
|
||||
|
||||
Scenario: User does not confirm new password
|
||||
Given I'm on https://auth.test.local:8080/password-reset/request
|
||||
And I set field "username" to "james"
|
||||
And I click on "Reset Password"
|
||||
When I click on the link of the email
|
||||
And I set field "password1" to "newpassword"
|
||||
And I set field "password2" to "newpassword2"
|
||||
And I click on "Reset Password"
|
||||
Then I get a notification with message "The passwords are different"
|
|
@ -0,0 +1,13 @@
|
|||
Feature: Authelia keeps user sessions despite the application restart
|
||||
|
||||
Scenario: Session is still valid after Authelia restarts
|
||||
When I register TOTP and login with user "john" and password "password"
|
||||
And the application restarts
|
||||
Then I have access to:
|
||||
| url |
|
||||
| https://public.test.local:8080/secret.html |
|
||||
| https://secret.test.local:8080/secret.html |
|
||||
| https://secret1.test.local:8080/secret.html |
|
||||
| https://secret2.test.local:8080/secret.html |
|
||||
| https://mx1.mail.test.local:8080/secret.html |
|
||||
| https://mx2.mail.test.local:8080/secret.html |
|
|
@ -0,0 +1,17 @@
|
|||
Feature: Non authenticated users have no access to certain pages
|
||||
|
||||
Scenario Outline: User has no access to protected pages
|
||||
When I visit "<url>"
|
||||
Then I get an error <error code>
|
||||
|
||||
Examples:
|
||||
| url | error code |
|
||||
| https://auth.test.local:8080/secondfactor | 401 |
|
||||
| https://auth.test.local:8080/verify | 401 |
|
||||
| https://auth.test.local:8080/secondfactor/u2f/identity/start | 401 |
|
||||
| https://auth.test.local:8080/secondfactor/u2f/identity/finish | 403 |
|
||||
| https://auth.test.local:8080/secondfactor/totp/identity/start | 401 |
|
||||
| https://auth.test.local:8080/secondfactor/totp/identity/finish | 403 |
|
||||
| https://auth.test.local:8080/password-reset/identity/start | 403 |
|
||||
| https://auth.test.local:8080/password-reset/identity/finish | 403 |
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import Cucumber = require("cucumber");
|
||||
|
||||
Cucumber.defineSupportCode(function({After}) {
|
||||
After(function() {
|
||||
return this.driver.quit();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,92 @@
|
|||
import Cucumber = require("cucumber");
|
||||
import seleniumWebdriver = require("selenium-webdriver");
|
||||
import Assert = require("assert");
|
||||
import Fs = require("fs");
|
||||
import Speakeasy = require("speakeasy");
|
||||
import CustomWorld = require("../support/world");
|
||||
|
||||
Cucumber.defineSupportCode(function ({ Given, When, Then }) {
|
||||
When(/^I visit "(https:\/\/[a-z0-9:.\/=?-]+)"$/, function (link: string) {
|
||||
return this.visit(link);
|
||||
});
|
||||
|
||||
When("I set field {stringInDoubleQuotes} to {stringInDoubleQuotes}", function (fieldName: string, content: string) {
|
||||
return this.setFieldTo(fieldName, content);
|
||||
});
|
||||
|
||||
When("I click on {stringInDoubleQuotes}", function (text: string) {
|
||||
return this.clickOnButton(text);
|
||||
});
|
||||
|
||||
Given("I login with user {stringInDoubleQuotes} and password {stringInDoubleQuotes}", function (username: string, password: string) {
|
||||
return this.loginWithUserPassword(username, password);
|
||||
});
|
||||
|
||||
Given("I register a TOTP secret called {stringInDoubleQuotes}", function (handle: string) {
|
||||
return this.registerTotpSecret(handle);
|
||||
});
|
||||
|
||||
Given("I use {stringInDoubleQuotes} as TOTP token", function (token: string) {
|
||||
return this.useTotpToken(token);
|
||||
});
|
||||
|
||||
Given("I use {stringInDoubleQuotes} as TOTP token handle", function (handle) {
|
||||
return this.useTotpTokenHandle(handle);
|
||||
});
|
||||
|
||||
Then("I get a notification with message {stringInDoubleQuotes}", function (notificationMessage: string) {
|
||||
const that = this;
|
||||
that.driver.sleep(500);
|
||||
return this.driver
|
||||
.findElement(seleniumWebdriver.By.className("notifyjs-corner"))
|
||||
.findElement(seleniumWebdriver.By.tagName("span"))
|
||||
.findElement(seleniumWebdriver.By.xpath("//span[contains(.,'" + notificationMessage + "')]"));
|
||||
});
|
||||
|
||||
When("I visit {stringInDoubleQuotes} and get redirected {stringInDoubleQuotes}", function (url: string, redirectUrl: string) {
|
||||
const that = this;
|
||||
return this.driver.get(url)
|
||||
.then(function () {
|
||||
return that.driver.wait(seleniumWebdriver.until.urlIs(redirectUrl), 2000);
|
||||
});
|
||||
});
|
||||
|
||||
Given("I register TOTP and login with user {stringInDoubleQuotes} and password {stringInDoubleQuotes}", function (username: string, password: string) {
|
||||
return this.registerTotpAndSignin(username, password);
|
||||
});
|
||||
|
||||
function hasAccessToSecret(link: string, driver: any) {
|
||||
return driver.get(link)
|
||||
.then(function () {
|
||||
return driver.findElement(seleniumWebdriver.By.tagName("body")).getText()
|
||||
.then(function (body: string) {
|
||||
Assert(body.indexOf("This is a very important secret!") > -1);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function hasNoAccessToSecret(link: string, driver: any) {
|
||||
return driver.get(link)
|
||||
.then(function () {
|
||||
return driver.wait(seleniumWebdriver.until.urlIs("https://auth.test.local:8080/"));
|
||||
});
|
||||
}
|
||||
|
||||
Then("I have access to:", function (dataTable: Cucumber.TableDefinition) {
|
||||
const promises = [];
|
||||
for (let i = 0; i < dataTable.rows().length; i++) {
|
||||
const url = (dataTable.hashes() as any)[i].url;
|
||||
promises.push(hasAccessToSecret(url, this.driver));
|
||||
}
|
||||
return Promise.all(promises);
|
||||
});
|
||||
|
||||
Then("I have no access to:", function (dataTable: Cucumber.TableDefinition) {
|
||||
const promises = [];
|
||||
for (let i = 0; i < dataTable.rows().length; i++) {
|
||||
const url = (dataTable.hashes() as any)[i].url;
|
||||
promises.push(hasNoAccessToSecret(url, this.driver));
|
||||
}
|
||||
return Promise.all(promises);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,17 @@
|
|||
import Cucumber = require("cucumber");
|
||||
import seleniumWebdriver = require("selenium-webdriver");
|
||||
import Assert = require("assert");
|
||||
|
||||
Cucumber.defineSupportCode(function ({ Given, When, Then }) {
|
||||
Given("I'm on https://{string}", function (link: string) {
|
||||
return this.driver.get("https://" + link);
|
||||
});
|
||||
|
||||
When("I click on the link to {string}", function (link: string) {
|
||||
return this.driver.findElement(seleniumWebdriver.By.linkText(link)).click();
|
||||
});
|
||||
|
||||
Then("I'm redirected to {stringInDoubleQuotes}", function (link: string) {
|
||||
return this.driver.wait(seleniumWebdriver.until.urlContains(link), 5000);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,20 @@
|
|||
import Cucumber = require("cucumber");
|
||||
import seleniumWebdriver = require("selenium-webdriver");
|
||||
import Assert = require("assert");
|
||||
import Fs = require("fs");
|
||||
|
||||
Cucumber.defineSupportCode(function ({ Given, When, Then }) {
|
||||
When("I click on the link {stringInDoubleQuotes}", function (text: string) {
|
||||
return this.driver.findElement(seleniumWebdriver.By.linkText(text)).click();
|
||||
});
|
||||
|
||||
When("I click on the link of the email", function () {
|
||||
const notif = Fs.readFileSync("./notifications/notification.txt").toString();
|
||||
const regexp = new RegExp(/Link: (.+)/);
|
||||
const match = regexp.exec(notif);
|
||||
const link = match[1];
|
||||
const that = this;
|
||||
|
||||
return this.driver.get(link);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,12 @@
|
|||
import Cucumber = require("cucumber");
|
||||
import seleniumWebdriver = require("selenium-webdriver");
|
||||
import Assert = require("assert");
|
||||
import ChildProcess = require("child_process");
|
||||
import BluebirdPromise = require("bluebird");
|
||||
|
||||
Cucumber.defineSupportCode(function ({ Given, When, Then }) {
|
||||
When(/^the application restarts$/, {timeout: 15 * 1000}, function () {
|
||||
const exec = BluebirdPromise.promisify(ChildProcess.exec);
|
||||
return exec("./scripts/example/dc-example.sh restart authelia && sleep 2");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,11 @@
|
|||
import Cucumber = require("cucumber");
|
||||
import seleniumWebdriver = require("selenium-webdriver");
|
||||
import Assert = require("assert");
|
||||
|
||||
Cucumber.defineSupportCode(function ({ Given, When, Then }) {
|
||||
Then("I get an error {number}", function (code: number) {
|
||||
return this.driver
|
||||
.findElement(seleniumWebdriver.By.tagName("h1"))
|
||||
.findElement(seleniumWebdriver.By.xpath("//h1[contains(.,'Error " + code + "')]"));
|
||||
});
|
||||
});
|
|
@ -0,0 +1,110 @@
|
|||
require("chromedriver");
|
||||
import seleniumWebdriver = require("selenium-webdriver");
|
||||
import Cucumber = require("cucumber");
|
||||
import Fs = require("fs");
|
||||
import Speakeasy = require("speakeasy");
|
||||
|
||||
function CustomWorld() {
|
||||
const that = this;
|
||||
this.driver = new seleniumWebdriver.Builder()
|
||||
.forBrowser("chrome")
|
||||
.build();
|
||||
|
||||
this.totpSecrets = {};
|
||||
|
||||
this.visit = function (link: string) {
|
||||
return this.driver.get(link);
|
||||
};
|
||||
|
||||
this.setFieldTo = function (fieldName: string, content: string) {
|
||||
return this.driver.findElement(seleniumWebdriver.By.id(fieldName))
|
||||
.sendKeys(content);
|
||||
};
|
||||
|
||||
this.clickOnButton = function (buttonText: string) {
|
||||
return this.driver
|
||||
.findElement(seleniumWebdriver.By.tagName("button"))
|
||||
.findElement(seleniumWebdriver.By.xpath("//button[contains(.,'" + buttonText + "')]"))
|
||||
.click();
|
||||
};
|
||||
|
||||
this.loginWithUserPassword = function (username: string, password: string) {
|
||||
return this.driver
|
||||
.findElement(seleniumWebdriver.By.id("username"))
|
||||
.sendKeys(username)
|
||||
.then(function () {
|
||||
return that.driver.findElement(seleniumWebdriver.By.id("password"))
|
||||
.sendKeys(password);
|
||||
})
|
||||
.then(function () {
|
||||
return that.driver.findElement(seleniumWebdriver.By.tagName("button"))
|
||||
.click();
|
||||
})
|
||||
.then(function () {
|
||||
return that.driver.wait(seleniumWebdriver.until.elementLocated(seleniumWebdriver.By.className("register-totp")), 4000);
|
||||
});
|
||||
};
|
||||
|
||||
this.registerTotpSecret = function (totpSecretHandle: string) {
|
||||
return this.driver.findElement(seleniumWebdriver.By.className("register-totp")).click()
|
||||
.then(function () {
|
||||
const notif = Fs.readFileSync("./notifications/notification.txt").toString();
|
||||
const regexp = new RegExp(/Link: (.+)/);
|
||||
const match = regexp.exec(notif);
|
||||
const link = match[1];
|
||||
console.log("Link: " + link);
|
||||
return that.driver.get(link);
|
||||
})
|
||||
.then(function () {
|
||||
return that.driver.wait(seleniumWebdriver.until.elementLocated(seleniumWebdriver.By.id("secret")), 1000);
|
||||
})
|
||||
.then(function () {
|
||||
return that.driver.findElement(seleniumWebdriver.By.id("secret")).getText();
|
||||
})
|
||||
.then(function (secret: string) {
|
||||
that.totpSecrets[totpSecretHandle] = secret;
|
||||
});
|
||||
};
|
||||
|
||||
this.useTotpTokenHandle = function (totpSecretHandle: string) {
|
||||
const token = Speakeasy.totp({
|
||||
secret: this.totpSecrets[totpSecretHandle],
|
||||
encoding: "base32"
|
||||
});
|
||||
return this.useTotpToken(token);
|
||||
};
|
||||
|
||||
this.useTotpToken = function (totpSecret: string) {
|
||||
return this.driver.findElement(seleniumWebdriver.By.id("token"))
|
||||
.sendKeys(totpSecret);
|
||||
};
|
||||
|
||||
this.registerTotpAndSignin = function (username: string, password: string) {
|
||||
const totpHandle = "HANDLE";
|
||||
const authUrl = "https://auth.test.local:8080/";
|
||||
const that = this;
|
||||
return this.visit(authUrl)
|
||||
.then(function () {
|
||||
return that.loginWithUserPassword(username, password);
|
||||
})
|
||||
.then(function () {
|
||||
return that.registerTotpSecret(totpHandle);
|
||||
})
|
||||
.then(function () {
|
||||
return that.visit(authUrl);
|
||||
})
|
||||
.then(function () {
|
||||
return that.loginWithUserPassword(username, password);
|
||||
})
|
||||
.then(function () {
|
||||
return that.useTotpTokenHandle(totpHandle);
|
||||
})
|
||||
.then(function () {
|
||||
return that.clickOnButton("TOTP");
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
Cucumber.defineSupportCode(function ({ setWorldConstructor }) {
|
||||
setWorldConstructor(CustomWorld);
|
||||
});
|
|
@ -1,4 +0,0 @@
|
|||
FROM node:7-alpine
|
||||
|
||||
WORKDIR /usr/src
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
version: '2'
|
||||
services:
|
||||
integration-tests:
|
||||
build: ./test/integration
|
||||
command: ./node_modules/.bin/mocha --compilers ts:ts-node/register --recursive test/integration
|
||||
volumes:
|
||||
- ./:/usr/src
|
||||
networks:
|
||||
- example-network
|
||||
|
||||
nginx-tests:
|
||||
image: nginx:alpine
|
||||
volumes:
|
||||
- ./example/nginx/html:/usr/share/nginx/html
|
||||
- ./example/nginx/ssl:/etc/ssl
|
||||
- ./test/integration/nginx.conf:/etc/nginx/nginx.conf
|
||||
expose:
|
||||
- "8080"
|
||||
depends_on:
|
||||
- authelia
|
||||
networks:
|
||||
example-network:
|
||||
aliases:
|
||||
- home.test.local
|
||||
- secret.test.local
|
||||
- secret1.test.local
|
||||
- secret2.test.local
|
||||
- mx1.mail.test.local
|
||||
- mx2.mail.test.local
|
||||
- auth.test.local
|
|
@ -1,112 +0,0 @@
|
|||
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
||||
|
||||
import Request = require("request");
|
||||
import Assert = require("assert");
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import Util = require("util");
|
||||
import Redis = require("redis");
|
||||
import Endpoints = require("../../src/server/endpoints");
|
||||
|
||||
const RequestAsync = BluebirdPromise.promisifyAll(Request) as typeof Request;
|
||||
|
||||
const DOMAIN = "test.local";
|
||||
const PORT = 8080;
|
||||
|
||||
const HOME_URL = Util.format("https://%s.%s:%d", "home", DOMAIN, PORT);
|
||||
const SECRET_URL = Util.format("https://%s.%s:%d", "secret", DOMAIN, PORT);
|
||||
const SECRET1_URL = Util.format("https://%s.%s:%d", "secret1", DOMAIN, PORT);
|
||||
const SECRET2_URL = Util.format("https://%s.%s:%d", "secret2", DOMAIN, PORT);
|
||||
const MX1_URL = Util.format("https://%s.%s:%d", "mx1.mail", DOMAIN, PORT);
|
||||
const MX2_URL = Util.format("https://%s.%s:%d", "mx2.mail", DOMAIN, PORT);
|
||||
|
||||
const BASE_AUTH_URL = Util.format("https://%s.%s:%d", "auth", DOMAIN, PORT);
|
||||
const FIRST_FACTOR_URL = Util.format("%s/api/firstfactor", BASE_AUTH_URL);
|
||||
const LOGOUT_URL = Util.format("%s/logout", BASE_AUTH_URL);
|
||||
|
||||
|
||||
const redisOptions = {
|
||||
host: "redis",
|
||||
port: 6379
|
||||
};
|
||||
|
||||
|
||||
describe("integration tests", function () {
|
||||
let redisClient: Redis.RedisClient;
|
||||
|
||||
before(function () {
|
||||
redisClient = Redis.createClient(redisOptions);
|
||||
});
|
||||
|
||||
function str_contains(str: string, pattern: string) {
|
||||
return str.indexOf(pattern) != -1;
|
||||
}
|
||||
|
||||
function test_homepage_is_correct(body: string) {
|
||||
Assert(str_contains(body, BASE_AUTH_URL + Endpoints.LOGOUT_GET + "?redirect=" + HOME_URL + "/"));
|
||||
Assert(str_contains(body, HOME_URL + "/secret.html"));
|
||||
Assert(str_contains(body, SECRET_URL + "/secret.html"));
|
||||
Assert(str_contains(body, SECRET1_URL + "/secret.html"));
|
||||
Assert(str_contains(body, SECRET2_URL + "/secret.html"));
|
||||
Assert(str_contains(body, MX1_URL + "/secret.html"));
|
||||
Assert(str_contains(body, MX2_URL + "/secret.html"));
|
||||
Assert(str_contains(body, "Access the secret"));
|
||||
}
|
||||
|
||||
it("should access the home page", function () {
|
||||
return RequestAsync.getAsync(HOME_URL)
|
||||
.then(function (response: Request.RequestResponse) {
|
||||
Assert.equal(200, response.statusCode);
|
||||
test_homepage_is_correct(response.body);
|
||||
});
|
||||
});
|
||||
|
||||
it("should access the authentication page", function () {
|
||||
return RequestAsync.getAsync(BASE_AUTH_URL)
|
||||
.then(function (response: Request.RequestResponse) {
|
||||
Assert.equal(200, response.statusCode);
|
||||
Assert(response.body.indexOf("Sign in") > -1);
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail first factor when wrong credentials are provided", function () {
|
||||
return RequestAsync.postAsync(FIRST_FACTOR_URL, {
|
||||
json: true,
|
||||
body: {
|
||||
username: "john",
|
||||
password: "wrong password"
|
||||
}
|
||||
})
|
||||
.then(function (response: Request.RequestResponse) {
|
||||
Assert.equal(401, response.statusCode);
|
||||
});
|
||||
});
|
||||
|
||||
it("should redirect when correct credentials are provided during first factor", function () {
|
||||
return RequestAsync.postAsync(FIRST_FACTOR_URL, {
|
||||
json: true,
|
||||
body: {
|
||||
username: "john",
|
||||
password: "password"
|
||||
}
|
||||
})
|
||||
.then(function (response: Request.RequestResponse) {
|
||||
Assert.equal(302, response.statusCode);
|
||||
});
|
||||
});
|
||||
|
||||
it("should have registered four sessions in redis", function (done) {
|
||||
redisClient.dbsize(function (err: Error, count: number) {
|
||||
Assert.equal(3, count);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should redirect to home page when logout is called", function () {
|
||||
return RequestAsync.getAsync(Util.format("%s?redirect=%s", LOGOUT_URL, HOME_URL))
|
||||
.then(function (response: Request.RequestResponse) {
|
||||
Assert.equal(200, response.statusCode);
|
||||
Assert(response.body.indexOf("Access the secret") > -1);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,92 +0,0 @@
|
|||
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
||||
|
||||
import Request = require("request");
|
||||
import Assert = require("assert");
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import Util = require("util");
|
||||
import Endpoints = require("../../src/server/endpoints");
|
||||
|
||||
const RequestAsync = BluebirdPromise.promisifyAll(Request) as typeof Request;
|
||||
|
||||
const DOMAIN = "test.local";
|
||||
const PORT = 8080;
|
||||
|
||||
const HOME_URL = Util.format("https://%s.%s:%d", "home", DOMAIN, PORT);
|
||||
const SECRET_URL = Util.format("https://%s.%s:%d", "secret", DOMAIN, PORT);
|
||||
const SECRET1_URL = Util.format("https://%s.%s:%d", "secret1", DOMAIN, PORT);
|
||||
const SECRET2_URL = Util.format("https://%s.%s:%d", "secret2", DOMAIN, PORT);
|
||||
const MX1_URL = Util.format("https://%s.%s:%d", "mx1.mail", DOMAIN, PORT);
|
||||
const MX2_URL = Util.format("https://%s.%s:%d", "mx2.mail", DOMAIN, PORT);
|
||||
|
||||
const BASE_AUTH_URL = Util.format("https://%s.%s:%d", "auth", DOMAIN, PORT);
|
||||
const FIRST_FACTOR_URL = Util.format("%s/api/firstfactor", BASE_AUTH_URL);
|
||||
const LOGOUT_URL = Util.format("%s/logout", BASE_AUTH_URL);
|
||||
|
||||
|
||||
describe("test example environment", function () {
|
||||
function str_contains(str: string, pattern: string) {
|
||||
return str.indexOf(pattern) != -1;
|
||||
}
|
||||
|
||||
function test_homepage_is_correct(body: string) {
|
||||
Assert(str_contains(body, BASE_AUTH_URL + Endpoints.LOGOUT_GET + "?redirect=" + HOME_URL + "/"));
|
||||
Assert(str_contains(body, HOME_URL + "/secret.html"));
|
||||
Assert(str_contains(body, SECRET_URL + "/secret.html"));
|
||||
Assert(str_contains(body, SECRET1_URL + "/secret.html"));
|
||||
Assert(str_contains(body, SECRET2_URL + "/secret.html"));
|
||||
Assert(str_contains(body, MX1_URL + "/secret.html"));
|
||||
Assert(str_contains(body, MX2_URL + "/secret.html"));
|
||||
Assert(str_contains(body, "Access the secret"));
|
||||
}
|
||||
|
||||
it("should access the home page", function () {
|
||||
return RequestAsync.getAsync(HOME_URL)
|
||||
.then(function (response: Request.RequestResponse) {
|
||||
Assert.equal(200, response.statusCode);
|
||||
test_homepage_is_correct(response.body);
|
||||
});
|
||||
});
|
||||
|
||||
it("should access the authentication page", function () {
|
||||
return RequestAsync.getAsync(BASE_AUTH_URL)
|
||||
.then(function (response: Request.RequestResponse) {
|
||||
Assert.equal(200, response.statusCode);
|
||||
Assert(response.body.indexOf("Sign in") > -1);
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail first factor when wrong credentials are provided", function () {
|
||||
return RequestAsync.postAsync(FIRST_FACTOR_URL, {
|
||||
json: true,
|
||||
body: {
|
||||
username: "john",
|
||||
password: "wrong password"
|
||||
}
|
||||
})
|
||||
.then(function (response: Request.RequestResponse) {
|
||||
Assert.equal(401, response.statusCode);
|
||||
});
|
||||
});
|
||||
|
||||
it("should redirect when correct credentials are provided during first factor", function () {
|
||||
return RequestAsync.postAsync(FIRST_FACTOR_URL, {
|
||||
json: true,
|
||||
body: {
|
||||
username: "john",
|
||||
password: "password"
|
||||
}
|
||||
})
|
||||
.then(function (response: Request.RequestResponse) {
|
||||
Assert.equal(302, response.statusCode);
|
||||
});
|
||||
});
|
||||
|
||||
it("should redirect to home page when logout is called", function () {
|
||||
return RequestAsync.getAsync(Util.format("%s?redirect=%s", LOGOUT_URL, HOME_URL))
|
||||
.then(function (response: Request.RequestResponse) {
|
||||
Assert.equal(200, response.statusCode);
|
||||
Assert(response.body.indexOf("Access the secret") > -1);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -42,7 +42,7 @@ describe("test FirstFactorValidator", function () {
|
|||
});
|
||||
|
||||
it("should fail with error 401", () => {
|
||||
return should_fail_first_factor_validation(401, "Authetication failed. Please check your credentials");
|
||||
return should_fail_first_factor_validation(401, "Authetication failed. Please check your credentials.");
|
||||
});
|
||||
});
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue