Replace mocha integration tests by cucumber tests
parent
e45ac39c8f
commit
c12a085f8e
18
.travis.yml
18
.travis.yml
|
@ -1,23 +1,37 @@
|
|||
dist: trusty
|
||||
language: node_js
|
||||
sudo: required
|
||||
node_js:
|
||||
- node
|
||||
- "7"
|
||||
services:
|
||||
- docker
|
||||
- ntp
|
||||
addons:
|
||||
chrome: stable
|
||||
apt:
|
||||
sources:
|
||||
- google-chrome
|
||||
packages:
|
||||
- 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'
|
||||
|
|
|
@ -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=
|
||||
|
|
|
@ -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;
|
||||
|
|
11
package.json
11
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"
|
||||
},
|
||||
|
@ -48,6 +48,7 @@
|
|||
"@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",
|
||||
|
@ -64,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",
|
||||
|
@ -71,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",
|
||||
|
@ -82,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",
|
||||
|
@ -90,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": "^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",
|
||||
|
|
|
@ -9,5 +9,4 @@ docker-compose \
|
|||
-f example/mongo/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 $*
|
||||
-f example/ldap/docker-compose.yml $*
|
||||
|
|
|
@ -8,5 +8,4 @@ docker-compose \
|
|||
-f example/mongo/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 $*
|
||||
-f example/ldap/docker-compose.yml $*
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
#!/bin/bash
|
||||
|
||||
DC_SCRIPT=./scripts/example/dc-example.sh
|
||||
EXPECTED_SERVICES_COUNT=6
|
||||
EXPECTED_SERVICES_COUNT=5
|
||||
|
||||
start_services() {
|
||||
$DC_SCRIPT up -d mongo redis openldap authelia nginx nginx-tests
|
||||
$DC_SCRIPT up -d mongo redis openldap authelia nginx
|
||||
sleep 3
|
||||
}
|
||||
|
||||
|
@ -27,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
|
||||
|
||||
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..."
|
||||
start_services
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -80,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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
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";
|
||||
|
||||
|
@ -17,7 +17,7 @@ export class FileSystemNotifier implements INotifier {
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,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.");
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue