mirror of
https://github.com/0rangebananaspy/authelia.git
synced 2024-09-14 22:47:21 +07:00
Replace mocha integration tests by cucumber tests
This commit is contained in:
parent
e45ac39c8f
commit
c12a085f8e
24
.travis.yml
24
.travis.yml
|
@ -1,23 +1,37 @@
|
||||||
|
dist: trusty
|
||||||
language: node_js
|
language: node_js
|
||||||
|
sudo: required
|
||||||
node_js:
|
node_js:
|
||||||
- node
|
- "7"
|
||||||
services:
|
services:
|
||||||
- docker
|
- docker
|
||||||
- ntp
|
- ntp
|
||||||
addons:
|
addons:
|
||||||
|
chrome: stable
|
||||||
apt:
|
apt:
|
||||||
|
sources:
|
||||||
|
- google-chrome
|
||||||
packages:
|
packages:
|
||||||
- libgif-dev
|
- libgif-dev
|
||||||
|
- google-chrome-stable
|
||||||
hosts:
|
hosts:
|
||||||
- auth.test.local
|
- auth.test.local
|
||||||
- home.test.local
|
- home.test.local
|
||||||
|
- public.test.local
|
||||||
- secret.test.local
|
- secret.test.local
|
||||||
- secret1.test.local
|
- secret1.test.local
|
||||||
- secret2.test.local
|
- secret2.test.local
|
||||||
- mx1.mail.test.local
|
- mx1.mail.test.local
|
||||||
- mx2.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:
|
script:
|
||||||
- ./scripts/travis.sh
|
- ./scripts/travis.sh
|
||||||
|
|
||||||
|
|
17
Gruntfile.js
17
Gruntfile.js
|
@ -12,17 +12,13 @@ module.exports = function (grunt) {
|
||||||
cmd: "./node_modules/.bin/tslint",
|
cmd: "./node_modules/.bin/tslint",
|
||||||
args: ['-c', 'tslint.json', '-p', 'tsconfig.json']
|
args: ['-c', 'tslint.json', '-p', 'tsconfig.json']
|
||||||
},
|
},
|
||||||
"test": {
|
"unit-tests": {
|
||||||
cmd: "./node_modules/.bin/mocha",
|
cmd: "./node_modules/.bin/mocha",
|
||||||
args: ['--compilers', 'ts:ts-node/register', '--recursive', 'test/unit']
|
args: ['--compilers', 'ts:ts-node/register', '--recursive', 'test/unit']
|
||||||
},
|
},
|
||||||
"test-int": {
|
"integration-tests": {
|
||||||
cmd: "./node_modules/.bin/mocha",
|
cmd: "./node_modules/.bin/cucumber-js",
|
||||||
args: ['--compilers', 'ts:ts-node/register', '--recursive', 'test/integration']
|
args: ["--compiler", "ts:ts-node/register", "./test/features"]
|
||||||
},
|
|
||||||
"test-system": {
|
|
||||||
cmd: "./node_modules/.bin/mocha",
|
|
||||||
args: ['--compilers', 'ts:ts-node/register', '--recursive', 'test/system']
|
|
||||||
},
|
},
|
||||||
"docker-build": {
|
"docker-build": {
|
||||||
cmd: "docker",
|
cmd: "docker",
|
||||||
|
@ -165,5 +161,8 @@ module.exports = function (grunt) {
|
||||||
grunt.registerTask('docker-build', ['run:docker-build']);
|
grunt.registerTask('docker-build', ['run:docker-build']);
|
||||||
grunt.registerTask('docker-restart', ['run:docker-restart']);
|
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.
|
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 secret.test.local
|
||||||
127.0.0.1 secret1.test.local
|
127.0.0.1 secret1.test.local
|
||||||
127.0.0.1 secret2.test.local
|
127.0.0.1 secret2.test.local
|
||||||
|
|
|
@ -48,7 +48,7 @@ ldap:
|
||||||
# beginning of the pattern.
|
# beginning of the pattern.
|
||||||
access_control:
|
access_control:
|
||||||
default:
|
default:
|
||||||
- home.test.local
|
- public.test.local
|
||||||
groups:
|
groups:
|
||||||
admin:
|
admin:
|
||||||
- '*.test.local'
|
- '*.test.local'
|
||||||
|
|
|
@ -45,3 +45,10 @@ mail: bob.dylan@example.com
|
||||||
sn: Bob Dylan
|
sn: Bob Dylan
|
||||||
userpassword: {SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=
|
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=
|
||||||
|
|
|
@ -9,6 +9,9 @@
|
||||||
You need to log in to access the secret!<br/><br/>
|
You need to log in to access the secret!<br/><br/>
|
||||||
Try to access it via one of the following links.<br/>
|
Try to access it via one of the following links.<br/>
|
||||||
<ul>
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="https://public.test.local:8080/secret.html">public.test.local</a>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="https://secret.test.local:8080/secret.html">secret.test.local</a>
|
<a href="https://secret.test.local:8080/secret.html">secret.test.local</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -18,9 +21,6 @@
|
||||||
<li>
|
<li>
|
||||||
<a href="https://secret2.test.local:8080/secret.html">secret2.test.local</a>
|
<a href="https://secret2.test.local:8080/secret.html">secret2.test.local</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
|
||||||
<a href="https://home.test.local:8080/secret.html">home.test.local</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
<li>
|
||||||
<a href="https://mx1.mail.test.local:8080/secret.html">mx1.mail.test.local</a>
|
<a href="https://mx1.mail.test.local:8080/secret.html">mx1.mail.test.local</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -45,7 +45,7 @@
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>Default policy</strong>
|
<li><strong>Default policy</strong>
|
||||||
<ul>
|
<ul>
|
||||||
<li>home.test.local</li>
|
<li>public.test.local</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li><strong>Groups policy</strong>
|
<li><strong>Groups policy</strong>
|
||||||
|
|
|
@ -53,7 +53,8 @@ http {
|
||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
|
|
||||||
server_name secret1.test.local secret2.test.local secret.test.local
|
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 on;
|
||||||
ssl_certificate /etc/ssl/server.crt;
|
ssl_certificate /etc/ssl/server.crt;
|
||||||
|
|
11
package.json
11
package.json
|
@ -7,7 +7,7 @@
|
||||||
"authelia": "dist/src/server/index.js"
|
"authelia": "dist/src/server/index.js"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "./node_modules/.bin/grunt test",
|
"test": "./node_modules/.bin/grunt unit-tests",
|
||||||
"cover": "NODE_ENV=test nyc npm t",
|
"cover": "NODE_ENV=test nyc npm t",
|
||||||
"serve": "node dist/server/index.js"
|
"serve": "node dist/server/index.js"
|
||||||
},
|
},
|
||||||
|
@ -48,6 +48,7 @@
|
||||||
"@types/body-parser": "^1.16.3",
|
"@types/body-parser": "^1.16.3",
|
||||||
"@types/connect-redis": "0.0.6",
|
"@types/connect-redis": "0.0.6",
|
||||||
"@types/cors": "^2.8.1",
|
"@types/cors": "^2.8.1",
|
||||||
|
"@types/cucumber": "^2.0.1",
|
||||||
"@types/ejs": "^2.3.33",
|
"@types/ejs": "^2.3.33",
|
||||||
"@types/express": "^4.0.35",
|
"@types/express": "^4.0.35",
|
||||||
"@types/express-session": "0.0.32",
|
"@types/express-session": "0.0.32",
|
||||||
|
@ -64,6 +65,7 @@
|
||||||
"@types/query-string": "^4.3.1",
|
"@types/query-string": "^4.3.1",
|
||||||
"@types/randomstring": "^1.1.5",
|
"@types/randomstring": "^1.1.5",
|
||||||
"@types/request": "0.0.46",
|
"@types/request": "0.0.46",
|
||||||
|
"@types/selenium-webdriver": "^3.0.4",
|
||||||
"@types/sinon": "^2.2.1",
|
"@types/sinon": "^2.2.1",
|
||||||
"@types/speakeasy": "^2.0.1",
|
"@types/speakeasy": "^2.0.1",
|
||||||
"@types/tmp": "0.0.33",
|
"@types/tmp": "0.0.33",
|
||||||
|
@ -71,6 +73,8 @@
|
||||||
"@types/yamljs": "^0.2.30",
|
"@types/yamljs": "^0.2.30",
|
||||||
"apidoc": "^0.17.6",
|
"apidoc": "^0.17.6",
|
||||||
"browserify": "^14.3.0",
|
"browserify": "^14.3.0",
|
||||||
|
"chromedriver": "^2.31.0",
|
||||||
|
"cucumber": "^2.3.1",
|
||||||
"grunt": "^1.0.1",
|
"grunt": "^1.0.1",
|
||||||
"grunt-browserify": "^5.0.0",
|
"grunt-browserify": "^5.0.0",
|
||||||
"grunt-contrib-concat": "^1.0.1",
|
"grunt-contrib-concat": "^1.0.1",
|
||||||
|
@ -82,7 +86,7 @@
|
||||||
"jquery": "^3.2.1",
|
"jquery": "^3.2.1",
|
||||||
"js-logger": "^1.3.0",
|
"js-logger": "^1.3.0",
|
||||||
"jsdom": "^11.0.0",
|
"jsdom": "^11.0.0",
|
||||||
"mocha": "^3.2.0",
|
"mocha": "^3.4.2",
|
||||||
"mockdate": "^2.0.1",
|
"mockdate": "^2.0.1",
|
||||||
"notifyjs-browser": "^0.4.2",
|
"notifyjs-browser": "^0.4.2",
|
||||||
"nyc": "^10.3.2",
|
"nyc": "^10.3.2",
|
||||||
|
@ -90,11 +94,12 @@
|
||||||
"proxyquire": "^1.8.0",
|
"proxyquire": "^1.8.0",
|
||||||
"query-string": "^4.3.4",
|
"query-string": "^4.3.4",
|
||||||
"request": "^2.81.0",
|
"request": "^2.81.0",
|
||||||
|
"selenium-webdriver": "^3.5.0",
|
||||||
"should": "^11.1.1",
|
"should": "^11.1.1",
|
||||||
"sinon": "^2.3.8",
|
"sinon": "^2.3.8",
|
||||||
"sinon-promise": "^0.1.3",
|
"sinon-promise": "^0.1.3",
|
||||||
"tmp": "0.0.31",
|
"tmp": "0.0.31",
|
||||||
"ts-node": "^3.0.4",
|
"ts-node": "^3.3.0",
|
||||||
"tslint": "^5.2.0",
|
"tslint": "^5.2.0",
|
||||||
"typescript": "^2.3.2",
|
"typescript": "^2.3.2",
|
||||||
"u2f-api": "0.0.9",
|
"u2f-api": "0.0.9",
|
||||||
|
|
|
@ -3,11 +3,10 @@
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
docker-compose \
|
docker-compose \
|
||||||
-f docker-compose.base.yml \
|
-f docker-compose.base.yml \
|
||||||
-f docker-compose.yml \
|
-f docker-compose.yml \
|
||||||
-f docker-compose.dev.yml \
|
-f docker-compose.dev.yml \
|
||||||
-f example/mongo/docker-compose.yml \
|
-f example/mongo/docker-compose.yml \
|
||||||
-f example/redis/docker-compose.yml \
|
-f example/redis/docker-compose.yml \
|
||||||
-f example/nginx/docker-compose.yml \
|
-f example/nginx/docker-compose.yml \
|
||||||
-f example/ldap/docker-compose.yml \
|
-f example/ldap/docker-compose.yml $*
|
||||||
-f test/integration/docker-compose.yml $*
|
|
||||||
|
|
|
@ -8,5 +8,4 @@ docker-compose \
|
||||||
-f example/mongo/docker-compose.yml \
|
-f example/mongo/docker-compose.yml \
|
||||||
-f example/redis/docker-compose.yml \
|
-f example/redis/docker-compose.yml \
|
||||||
-f example/nginx/docker-compose.yml \
|
-f example/nginx/docker-compose.yml \
|
||||||
-f example/ldap/docker-compose.yml \
|
-f example/ldap/docker-compose.yml $*
|
||||||
-f test/integration/docker-compose.yml $*
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
DC_SCRIPT=./scripts/example/dc-example.sh
|
DC_SCRIPT=./scripts/example/dc-example.sh
|
||||||
EXPECTED_SERVICES_COUNT=6
|
EXPECTED_SERVICES_COUNT=5
|
||||||
|
|
||||||
start_services() {
|
start_services() {
|
||||||
$DC_SCRIPT up -d mongo redis openldap authelia nginx nginx-tests
|
$DC_SCRIPT up -d mongo redis openldap authelia nginx
|
||||||
sleep 3
|
sleep 3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,39 +27,12 @@ expect_services_count() {
|
||||||
}
|
}
|
||||||
|
|
||||||
run_integration_tests() {
|
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
|
|
||||||
|
|
||||||
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 $EXPECTED_SERVICES_COUNT
|
|
||||||
|
|
||||||
echo "Run integration tests..."
|
|
||||||
$DC_SCRIPT run --rm integration-tests
|
|
||||||
|
|
||||||
echo "Shutdown services..."
|
|
||||||
shut_services
|
|
||||||
}
|
|
||||||
|
|
||||||
run_system_tests() {
|
|
||||||
echo "Start services..."
|
echo "Start services..."
|
||||||
start_services
|
start_services
|
||||||
expect_services_count $EXPECTED_SERVICES_COUNT
|
expect_services_count $EXPECTED_SERVICES_COUNT
|
||||||
|
|
||||||
./node_modules/.bin/mocha --compilers ts:ts-node/register --recursive test/system
|
sleep 5
|
||||||
|
./node_modules/.bin/grunt run:integration-tests
|
||||||
shut_services
|
shut_services
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,11 +53,8 @@ set -e
|
||||||
echo "Make sure services are not already running"
|
echo "Make sure services are not already running"
|
||||||
shut_services
|
shut_services
|
||||||
|
|
||||||
# Prepare & run integration tests
|
|
||||||
run_integration_tests
|
|
||||||
|
|
||||||
# Prepare & test example from end user perspective
|
# Prepare & test example from end user perspective
|
||||||
run_system_tests
|
run_integration_tests
|
||||||
|
|
||||||
# Other tests like executing the deployment script
|
# Other tests like executing the deployment script
|
||||||
run_other_tests
|
run_other_tests
|
||||||
|
|
|
@ -13,7 +13,7 @@ export function validate(username: string, password: string, $: JQueryStatic): B
|
||||||
})
|
})
|
||||||
.fail(function (xhr: JQueryXHR, textStatus: string) {
|
.fail(function (xhr: JQueryXHR, textStatus: string) {
|
||||||
if (xhr.status == 401)
|
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));
|
reject(new Error(textStatus));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -30,7 +30,7 @@ export default function(window: Window, $: JQueryStatic) {
|
||||||
|
|
||||||
requestPasswordReset(username)
|
requestPasswordReset(username)
|
||||||
.then(function () {
|
.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 () {
|
setTimeout(function () {
|
||||||
window.location.replace(Endpoints.FIRST_FACTOR_GET);
|
window.location.replace(Endpoints.FIRST_FACTOR_GET);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import * as BluebirdPromise from "bluebird";
|
import * as BluebirdPromise from "bluebird";
|
||||||
import exceptions = require("./Exceptions");
|
import exceptions = require("./Exceptions");
|
||||||
import { UserDataStore } from "./storage/UserDataStore";
|
import { UserDataStore } from "./storage/UserDataStore";
|
||||||
import {AuthenticationTraceDocument} from "./storage/AuthenticationTraceDocument";
|
import { AuthenticationTraceDocument } from "./storage/AuthenticationTraceDocument";
|
||||||
|
|
||||||
const MAX_AUTHENTICATION_COUNT_IN_TIME_RANGE = 3;
|
const MAX_AUTHENTICATION_COUNT_IN_TIME_RANGE = 3;
|
||||||
|
|
||||||
|
@ -22,19 +22,19 @@ export class AuthenticationRegulator {
|
||||||
|
|
||||||
regulate(userId: string): BluebirdPromise<void> {
|
regulate(userId: string): BluebirdPromise<void> {
|
||||||
return this.userDataStore.retrieveLatestAuthenticationTraces(userId, false, 3)
|
return this.userDataStore.retrieveLatestAuthenticationTraces(userId, false, 3)
|
||||||
.then((docs: AuthenticationTraceDocument[]) => {
|
.then((docs: AuthenticationTraceDocument[]) => {
|
||||||
if (docs.length < MAX_AUTHENTICATION_COUNT_IN_TIME_RANGE) {
|
if (docs.length < MAX_AUTHENTICATION_COUNT_IN_TIME_RANGE) {
|
||||||
// less than the max authorized number of authentication in time range, thus authorizing access
|
// 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();
|
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();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,10 +41,10 @@ export class Client {
|
||||||
reconnect: true
|
reconnect: true
|
||||||
});
|
});
|
||||||
|
|
||||||
const clientLogger = (ldapClient as any).log;
|
/*const clientLogger = (ldapClient as any).log;
|
||||||
if (clientLogger) {
|
if (clientLogger) {
|
||||||
clientLogger.level("trace");
|
clientLogger.level("trace");
|
||||||
}
|
}*/
|
||||||
|
|
||||||
this.client = BluebirdPromise.promisifyAll(ldapClient) as ldapjs.ClientAsync;
|
this.client = BluebirdPromise.promisifyAll(ldapClient) as ldapjs.ClientAsync;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
import * as BluebirdPromise from "bluebird";
|
import * as BluebirdPromise from "bluebird";
|
||||||
import * as util from "util";
|
import * as util from "util";
|
||||||
import * as fs from "fs";
|
import * as Fs from "fs";
|
||||||
import { INotifier } from "./INotifier";
|
import { INotifier } from "./INotifier";
|
||||||
import { Identity } from "../../../types/Identity";
|
import { Identity } from "../../../types/Identity";
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ export class FileSystemNotifier implements INotifier {
|
||||||
notify(identity: Identity, subject: string, link: string): BluebirdPromise<void> {
|
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,
|
const content = util.format("Date: %s\nUser: %s\nSubject: %s\nLink: %s", new Date().toString(), identity.userid,
|
||||||
subject, link);
|
subject, link);
|
||||||
const writeFilePromised = BluebirdPromise.promisify<void, string, string>(fs.writeFile);
|
const writeFilePromised: any = BluebirdPromise.promisify(Fs.writeFile);
|
||||||
return writeFilePromised(this.filename, content);
|
return writeFilePromised(this.filename, content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,4 +8,4 @@ block form-header
|
||||||
<img class="header-img" src="/img/warning.png" alt="">
|
<img class="header-img" src="/img/warning.png" alt="">
|
||||||
|
|
||||||
block content
|
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="">
|
<img class="header-img" src="/img/warning.png" alt="">
|
||||||
|
|
||||||
block content
|
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>
|
</div>
|
||||||
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
|
<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> -->
|
<!-- <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>
|
<span class="clearfix"></span>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ html
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row poweredby-block">
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -9,14 +9,14 @@ block content
|
||||||
<div class="form-inputs">
|
<div class="form-inputs">
|
||||||
<input type="text" class="form-control" id="token" placeholder="Token" required autofocus>
|
<input type="text" class="form-control" id="token" placeholder="Token" required autofocus>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-lg btn-primary btn-block" type="submit">TOTP</button>
|
<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") Need to register?
|
a(href=totp_identity_start_endpoint, class="pull-right link register-totp") Need to register?
|
||||||
<span class="clearfix"></span>
|
<span class="clearfix"></span>
|
||||||
</form>
|
</form>
|
||||||
<hr>
|
<hr>
|
||||||
<form class="form-signin u2f">
|
<form class="form-signin u2f">
|
||||||
<button class="btn btn-lg btn-primary btn-block" type="submit">U2F</button>
|
<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") Need to register?
|
a(href=u2f_identity_start_endpoint, class="pull-right link register-u2f") Need to register?
|
||||||
<span class="clearfix"></span>
|
<span class="clearfix"></span>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
38
test/features/access-control.feature
Normal file
38
test/features/access-control.feature
Normal file
|
@ -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 |
|
53
test/features/authentication.feature
Normal file
53
test/features/authentication.feature
Normal file
|
@ -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"
|
7
test/features/redirection.feature
Normal file
7
test/features/redirection.feature
Normal file
|
@ -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/"
|
||||||
|
|
33
test/features/reset-password.feature
Normal file
33
test/features/reset-password.feature
Normal file
|
@ -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"
|
13
test/features/resilience.feature
Normal file
13
test/features/resilience.feature
Normal file
|
@ -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 |
|
17
test/features/restrictions.feature
Normal file
17
test/features/restrictions.feature
Normal file
|
@ -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 |
|
||||||
|
|
7
test/features/step_definitions/after.ts
Normal file
7
test/features/step_definitions/after.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import Cucumber = require("cucumber");
|
||||||
|
|
||||||
|
Cucumber.defineSupportCode(function({After}) {
|
||||||
|
After(function() {
|
||||||
|
return this.driver.quit();
|
||||||
|
});
|
||||||
|
});
|
92
test/features/step_definitions/authentication.ts
Normal file
92
test/features/step_definitions/authentication.ts
Normal file
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
17
test/features/step_definitions/redirection.ts
Normal file
17
test/features/step_definitions/redirection.ts
Normal file
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
20
test/features/step_definitions/reset-password.ts
Normal file
20
test/features/step_definitions/reset-password.ts
Normal file
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
12
test/features/step_definitions/resilience.ts
Normal file
12
test/features/step_definitions/resilience.ts
Normal file
|
@ -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");
|
||||||
|
});
|
||||||
|
});
|
11
test/features/step_definitions/restrictions.ts
Normal file
11
test/features/step_definitions/restrictions.ts
Normal file
|
@ -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 + "')]"));
|
||||||
|
});
|
||||||
|
});
|
110
test/features/support/world.ts
Normal file
110
test/features/support/world.ts
Normal file
|
@ -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", () => {
|
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.");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
Loading…
Reference in New Issue
Block a user