mirror of
https://github.com/0rangebananaspy/authelia.git
synced 2024-09-14 22:47:21 +07:00
Fix e2e test with minimal configuration.
This commit is contained in:
parent
561578dffc
commit
c5eb86e0fd
|
@ -12,7 +12,7 @@ RUN apk --update add --no-cache --virtual \
|
|||
COPY dist/server /usr/src/server
|
||||
COPY dist/shared /usr/src/shared
|
||||
|
||||
EXPOSE 8080
|
||||
EXPOSE 9091
|
||||
|
||||
VOLUME /etc/authelia
|
||||
VOLUME /var/lib/authelia
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React, { Component } from "react";
|
||||
import classnames from 'classnames';
|
||||
|
||||
import styles from '../../assets/scss/components/AlreadyAuthenticated/AlreadyAuthenticated.module.scss';
|
||||
import Button from "@material/react-button";
|
||||
|
@ -17,7 +18,7 @@ export type Props = OwnProps & DispatchProps;
|
|||
class AlreadyAuthenticated extends Component<Props> {
|
||||
render() {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={classnames(styles.container, 'already-authenticated-step')}>
|
||||
<div className={styles.successContainer}>
|
||||
<div className={styles.messageContainer}>
|
||||
<span className={styles.username}>{this.props.username}</span>
|
||||
|
|
|
@ -65,7 +65,7 @@ class FirstFactorForm extends Component<Props, State> {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<div className='first-factor-step'>
|
||||
<Notification
|
||||
show={this.props.error != null}
|
||||
className={styles.notification}>
|
||||
|
@ -103,8 +103,9 @@ class FirstFactorForm extends Component<Props, State> {
|
|||
<div className={styles.buttons}>
|
||||
<Button
|
||||
onClick={this.onLoginClicked}
|
||||
color="primary"
|
||||
color='primary'
|
||||
raised={true}
|
||||
id='login-button'
|
||||
disabled={this.props.formDisabled}>
|
||||
Login
|
||||
</Button>
|
||||
|
|
|
@ -11,7 +11,7 @@ interface Props {
|
|||
class Notification extends Component<Props> {
|
||||
render() {
|
||||
return (this.props.show)
|
||||
? (<div className={classnames(styles.container, this.props.className)}>
|
||||
? (<div className={classnames(styles.container, this.props.className, 'notification')}>
|
||||
{this.props.children}
|
||||
</div>)
|
||||
: null;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React, { Component, KeyboardEvent, ChangeEvent, FormEvent } from 'react';
|
||||
import React, { Component, KeyboardEvent, FormEvent } from 'react';
|
||||
import classnames from 'classnames';
|
||||
|
||||
import TextField, { Input } from '@material/react-text-field';
|
||||
import Button from '@material/react-button';
|
||||
|
@ -63,7 +64,7 @@ class SecondFactorView extends Component<Props, State> {
|
|||
<CircleLoader status={u2fStatus}></CircleLoader>
|
||||
</div>
|
||||
<div className={styles.registerDeviceContainer}>
|
||||
<a className={styles.registerDevice} href="#"
|
||||
<a className={classnames(styles.registerDevice, 'register-u2f')} href="#"
|
||||
onClick={this.props.onRegisterSecurityKeyClicked}>
|
||||
Register device
|
||||
</a>
|
||||
|
@ -89,7 +90,7 @@ class SecondFactorView extends Component<Props, State> {
|
|||
|
||||
private renderTotp(n: number) {
|
||||
return (
|
||||
<div className={styles.methodTotp} key='totp-method'>
|
||||
<div className={classnames(styles.methodTotp, 'second-factor-step')} key='totp-method'>
|
||||
<div className={styles.methodName}>Option {n} - One-Time Password</div>
|
||||
<Notification show={this.props.oneTimePasswordVerificationError !== null}>
|
||||
{this.props.oneTimePasswordVerificationError}
|
||||
|
@ -99,14 +100,14 @@ class SecondFactorView extends Component<Props, State> {
|
|||
label="One-Time Password"
|
||||
outlined={true}>
|
||||
<Input
|
||||
name="totp-token"
|
||||
id="totp-token"
|
||||
name='totp-token'
|
||||
id='totp-token'
|
||||
onChange={this.onOneTimePasswordChanged as any}
|
||||
onKeyPress={this.onTotpKeyPressed}
|
||||
value={this.state.oneTimePassword} />
|
||||
</TextField>
|
||||
<div className={styles.registerDeviceContainer}>
|
||||
<a className={styles.registerDevice} href="#"
|
||||
<a className={classnames(styles.registerDevice, 'register-totp')} href="#"
|
||||
onClick={this.props.onRegisterOneTimePasswordClicked}>
|
||||
Register device
|
||||
</a>
|
||||
|
@ -115,6 +116,7 @@ class SecondFactorView extends Component<Props, State> {
|
|||
<Button
|
||||
color="primary"
|
||||
raised={true}
|
||||
id='totp-button'
|
||||
onClick={this.onOneTimePasswordValidationRequested}
|
||||
disabled={this.props.oneTimePasswordVerificationInProgress}>
|
||||
OK
|
||||
|
|
|
@ -51,9 +51,10 @@ class ForgotPasswordView extends Component<Props, State> {
|
|||
<TextField
|
||||
className={styles.field}
|
||||
outlined={true}
|
||||
id="username"
|
||||
label="Username">
|
||||
<Input
|
||||
<Input
|
||||
id="username"
|
||||
name="username"
|
||||
onChange={this.onUsernameChanged}
|
||||
onKeyPress={this.onKeyPressed}
|
||||
value={this.state.username}
|
||||
|
@ -64,6 +65,7 @@ class ForgotPasswordView extends Component<Props, State> {
|
|||
<Button
|
||||
onClick={this.onPasswordResetRequested}
|
||||
color="primary"
|
||||
id="next-button"
|
||||
raised={true}
|
||||
className={styles.buttonConfirm}
|
||||
disabled={this.props.disabled}>
|
||||
|
@ -75,6 +77,7 @@ class ForgotPasswordView extends Component<Props, State> {
|
|||
onClick={this.props.onCancelClicked}
|
||||
color="primary"
|
||||
raised={true}
|
||||
id="cancel-button"
|
||||
className={styles.buttonCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React, { Component } from "react";
|
||||
import classnames from 'classnames';
|
||||
|
||||
import Button from "@material/react-button";
|
||||
|
||||
|
@ -54,10 +55,10 @@ class OneTimePasswordRegistrationView extends Component<Props> {
|
|||
Register your device by scanning the barcode or adding the key.
|
||||
</div>
|
||||
<div className={styles.secretContainer}>
|
||||
<div className={styles.qrcodeContainer}>
|
||||
<div className={classnames(styles.qrcodeContainer, 'qrcode')}>
|
||||
<QRCode value={secret.otpauth_url} size={180} level="Q"></QRCode>
|
||||
</div>
|
||||
<div className={styles.base32Container}>{secret.base32_secret}</div>
|
||||
<div className={classnames(styles.base32Container, 'base32-secret')}>{secret.base32_secret}</div>
|
||||
</div>
|
||||
<div className={styles.loginButtonContainer}>
|
||||
<Button
|
||||
|
|
|
@ -92,6 +92,7 @@ class ResetPasswordView extends Component<Props, State> {
|
|||
<Input
|
||||
type="password"
|
||||
key="password1"
|
||||
name="password1"
|
||||
value={this.state.password1}
|
||||
onChange={this.onPassword1Changed}
|
||||
disabled={this.props.disabled}/>
|
||||
|
@ -104,6 +105,7 @@ class ResetPasswordView extends Component<Props, State> {
|
|||
<Input
|
||||
type="password"
|
||||
key="password2"
|
||||
name="password2"
|
||||
value={this.state.password2}
|
||||
onKeyPress={this.onKeyPressed}
|
||||
onChange={this.onPassword2Changed}
|
||||
|
@ -114,6 +116,7 @@ class ResetPasswordView extends Component<Props, State> {
|
|||
<Button
|
||||
onClick={this.onResetClicked}
|
||||
color="primary"
|
||||
id="reset-button"
|
||||
raised={true}
|
||||
disabled={this.props.disabled}
|
||||
className={classnames(styles.button, styles.buttonReset)}>
|
||||
|
@ -124,6 +127,7 @@ class ResetPasswordView extends Component<Props, State> {
|
|||
<Button
|
||||
onClick={this.props.onCancelClicked}
|
||||
color="primary"
|
||||
id="cancel-button"
|
||||
raised={true}
|
||||
className={classnames(styles.button, styles.buttonCancel)}>
|
||||
Cancel
|
||||
|
|
|
@ -2,9 +2,11 @@
|
|||
# Authelia minimal configuration #
|
||||
###############################################################
|
||||
|
||||
port: 9091
|
||||
|
||||
authentication_backend:
|
||||
file:
|
||||
path: /etc/authelia/users_database.yml
|
||||
path: ./users_database.yml
|
||||
|
||||
session:
|
||||
secret: unsecure_session_secret
|
||||
|
@ -96,7 +98,7 @@ notifier:
|
|||
username: test
|
||||
password: password
|
||||
secure: false
|
||||
host: 'smtp'
|
||||
host: 127.0.0.1
|
||||
port: 1025
|
||||
sender: admin@example.com
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
###############################################################
|
||||
|
||||
# The port to listen on
|
||||
port: 8080
|
||||
port: 9091
|
||||
|
||||
# Log level
|
||||
#
|
||||
|
@ -42,7 +42,7 @@ authentication_backend:
|
|||
# production.
|
||||
ldap:
|
||||
# The url of the ldap server
|
||||
url: ldap://openldap
|
||||
url: ldap://127.0.0.1
|
||||
|
||||
# The base dn for every entries
|
||||
base_dn: dc=example,dc=com
|
||||
|
@ -198,7 +198,7 @@ session:
|
|||
|
||||
# The redis connection details
|
||||
redis:
|
||||
host: redis
|
||||
host: 127.0.0.1
|
||||
port: 6379
|
||||
password: authelia
|
||||
|
||||
|
@ -229,7 +229,7 @@ storage:
|
|||
|
||||
# Settings to connect to mongo server
|
||||
mongo:
|
||||
url: mongodb://mongo
|
||||
url: mongodb://127.0.0.1
|
||||
database: authelia
|
||||
auth:
|
||||
username: authelia
|
||||
|
@ -258,6 +258,6 @@ notifier:
|
|||
username: test
|
||||
password: password
|
||||
secure: false
|
||||
host: 'smtp'
|
||||
host: 127.0.0.1
|
||||
port: 1025
|
||||
sender: admin@example.com
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
version: '2'
|
||||
services: {}
|
||||
# services: {}
|
||||
networks:
|
||||
authelianet:
|
||||
external: true
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 192.168.240.0/24
|
||||
gateway: 192.168.240.1
|
||||
|
|
|
@ -111,7 +111,7 @@ http {
|
|||
server_name public.example.com;
|
||||
|
||||
resolver 127.0.0.11 ipv6=off;
|
||||
set $upstream_verify http://authelia:8080/api/verify;
|
||||
set $upstream_verify http://192.168.240.1:9091/api/verify;
|
||||
set $upstream_endpoint http://nginx-backend;
|
||||
set $upstream_headers http://httpbin:8000/headers;
|
||||
|
||||
|
@ -179,7 +179,7 @@ http {
|
|||
server_name admin.example.com;
|
||||
|
||||
resolver 127.0.0.11 ipv6=off;
|
||||
set $upstream_verify http://authelia:8080/api/verify;
|
||||
set $upstream_verify http://192.168.240.1:9091/api/verify;
|
||||
set $upstream_endpoint http://nginx-backend;
|
||||
|
||||
ssl_certificate /etc/ssl/server.crt;
|
||||
|
@ -229,7 +229,7 @@ http {
|
|||
server_name dev.example.com;
|
||||
|
||||
resolver 127.0.0.11 ipv6=off;
|
||||
set $upstream_verify http://authelia:8080/api/verify;
|
||||
set $upstream_verify http://192.168.240.1:9091/api/verify;
|
||||
set $upstream_endpoint http://nginx-backend;
|
||||
|
||||
ssl_certificate /etc/ssl/server.crt;
|
||||
|
@ -279,7 +279,7 @@ http {
|
|||
server_name mx1.mail.example.com mx2.mail.example.com;
|
||||
|
||||
resolver 127.0.0.11 ipv6=off;
|
||||
set $upstream_verify http://authelia:8080/api/verify;
|
||||
set $upstream_verify http://192.168.240.1:9091/api/verify;
|
||||
set $upstream_endpoint http://nginx-backend;
|
||||
|
||||
ssl_certificate /etc/ssl/server.crt;
|
||||
|
@ -329,7 +329,7 @@ http {
|
|||
server_name single_factor.example.com;
|
||||
|
||||
resolver 127.0.0.11 ipv6=off;
|
||||
set $upstream_verify http://authelia:8080/api/verify;
|
||||
set $upstream_verify http://192.168.240.1:9091/api/verify;
|
||||
set $upstream_endpoint http://nginx-backend;
|
||||
set $upstream_headers http://httpbin:8000/headers;
|
||||
|
||||
|
@ -400,7 +400,7 @@ http {
|
|||
server_name authelia.example.com;
|
||||
|
||||
resolver 127.0.0.11 ipv6=off;
|
||||
set $upstream_endpoint http://authelia:8080;
|
||||
set $upstream_endpoint http://192.168.240.1:9091;
|
||||
|
||||
ssl_certificate /etc/ssl/server.crt;
|
||||
ssl_certificate_key /etc/ssl/server.key;
|
||||
|
|
|
@ -17,6 +17,14 @@ if (program.production) {
|
|||
options['production'] = true;
|
||||
}
|
||||
|
||||
html = ejs.renderFile(__dirname + '/nginx.conf.ejs', options, (err, conf) => {
|
||||
fs.writeFileSync(__dirname + '/nginx.conf', conf);
|
||||
const templatePath = __dirname + '/nginx.conf.ejs';
|
||||
const outputPath = __dirname + '/nginx.conf';
|
||||
|
||||
html = ejs.renderFile(templatePath, options, (err, conf) => {
|
||||
try {
|
||||
var fd = fs.openSync(outputPath, 'w');
|
||||
fs.writeFileSync(fd, conf);
|
||||
} catch (e) {
|
||||
fs.writeFileSync(outputPath, conf);
|
||||
}
|
||||
});
|
2898
package-lock.json
generated
2898
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -93,7 +93,7 @@
|
|||
"grunt-run": "^0.8.0",
|
||||
"istanbul": "^0.4.5",
|
||||
"jquery": "^3.2.1",
|
||||
"mocha": "^5.0.5",
|
||||
"mocha": "^5.2.0",
|
||||
"mockdate": "^2.0.1",
|
||||
"nodemon": "^1.18.9",
|
||||
"nyc": "^13.1.0",
|
||||
|
@ -107,7 +107,7 @@
|
|||
"tmp": "0.0.33",
|
||||
"ts-node": "^6.0.1",
|
||||
"tslint": "^5.2.0",
|
||||
"typescript": "^2.3.2",
|
||||
"typescript": "^2.9.2",
|
||||
"typescript-json-schema": "^0.23.0"
|
||||
},
|
||||
"nyc": {
|
||||
|
|
|
@ -4,6 +4,7 @@ var program = require('commander');
|
|||
|
||||
program
|
||||
.version('0.0.1')
|
||||
|
||||
.command('start', 'Start development environment.')
|
||||
.command('build', 'Build production version of Authelia from source.')
|
||||
.command('clean', 'Clean the production version of Authelia.')
|
||||
|
@ -12,7 +13,6 @@ program
|
|||
|
||||
.command('build-docker', 'Build Docker image containing production version of Authelia.')
|
||||
.command('publish-docker', 'Publish Docker image containing production version of Authelia to Dockerhub.')
|
||||
|
||||
program.parse(process.argv);
|
||||
.parse(process.argv);
|
||||
|
||||
|
||||
|
|
|
@ -1,8 +1,39 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env node
|
||||
|
||||
# Render the production version of the nginx portal configuration
|
||||
./example/compose/nginx/portal/render.js --production
|
||||
var program = require('commander');
|
||||
var execSync = require('child_process').execSync;
|
||||
var spawn = require('child_process').spawn;
|
||||
|
||||
program
|
||||
.option('-c, --config <config>', 'Configuration file to run Authelia with.')
|
||||
.option('--no-watch', 'Disable hot reload.')
|
||||
.parse(process.argv);
|
||||
|
||||
./scripts/utils/prepare-environment.sh
|
||||
let config = 'config.yml'; // set default config file.
|
||||
|
||||
./node_modules/.bin/nodemon -e yml --exec 'node dist/server/src/index.js config.yml'
|
||||
if (program.config) {
|
||||
config = program.config;
|
||||
}
|
||||
|
||||
// Render the production version of the nginx portal configuration
|
||||
execSync('./example/compose/nginx/portal/render.js --production');
|
||||
|
||||
// Prepare the environment
|
||||
execSync('./scripts/utils/prepare-environment.sh');
|
||||
|
||||
var server;
|
||||
if (program.watch) {
|
||||
server = spawn('./node_modules/.bin/nodemon',
|
||||
['-e', 'yml', '--ignore', './users_database*.yml', '--exec', `node dist/server/src/index.js ${config}`]);
|
||||
}
|
||||
else {
|
||||
server = spawn('/usr/bin/env', ['node', 'dist/server/src/index.js', config]);
|
||||
}
|
||||
|
||||
server.stdout.on('data', (data) => {
|
||||
process.stdout.write(`${data}`);
|
||||
});
|
||||
|
||||
server.stderr.on('data', (data) => {
|
||||
process.stderr.write(`${data}`);
|
||||
});
|
||||
|
|
|
@ -1,10 +1,20 @@
|
|||
#!/bin/bash
|
||||
|
||||
config_path=$1
|
||||
|
||||
if [ "$config_path" == "" ];
|
||||
then
|
||||
echo "Please provide a configuration file."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
./example/compose/nginx/portal/render.js
|
||||
|
||||
./scripts/utils/prepare-environment.sh
|
||||
|
||||
server_watcher="./node_modules/.bin/nodemon -e yml,js,ts,json --exec ./scripts/run-dev-server.sh 2>&1 /tmp/authelia-server.log"
|
||||
./node_modules/.bin/typescript-json-schema -o server/src/lib/configuration/Configuration.schema.json --strictNullChecks --required server/tsconfig.json Configuration
|
||||
|
||||
server_watcher="./node_modules/.bin/nodemon -e yml,js,ts,json --exec ./scripts/run-dev-server.sh $config_path 2>&1 /tmp/authelia-server.log"
|
||||
client_watcher="cd client && BROWSER=none npm run start > /tmp/authelia-client.log"
|
||||
|
||||
./node_modules/.bin/concurrently "$server_watcher" "$client_watcher"
|
||||
|
|
|
@ -1,3 +1,25 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env node
|
||||
|
||||
TS_NODE_PROJECT=server/tsconfig.json ./node_modules/.bin/mocha --colors --require ts-node/register server/src/**/*.spec.ts
|
||||
var program = require('commander');
|
||||
var spawn = require('child_process').spawn;
|
||||
|
||||
program
|
||||
.option('--with-server', 'Spawn Authelia before running the tests.')
|
||||
.parse(process.argv);
|
||||
|
||||
mocha = spawn('./node_modules/.bin/mocha', ['--exit', '--colors', '--require', 'ts-node/register', ...program.args], {
|
||||
env: {
|
||||
...process.env,
|
||||
TS_NODE_PROJECT: 'test/tsconfig.json',
|
||||
WITH_SERVER: (program.withServer) ? 'y' : 'n',
|
||||
}
|
||||
});
|
||||
|
||||
mocha.stdout.on('data', (data) => {
|
||||
process.stdout.write(`${data}`);
|
||||
});
|
||||
|
||||
mocha.stderr.on('data', (data) => {
|
||||
process.stderr.write(`${data}`);
|
||||
});
|
||||
// TS_NODE_PROJECT=server/tsconfig.json ./node_modules/.bin/mocha --colors --require ts-node/register server/src/**/*.spec.ts
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
#!/bin/bash
|
||||
|
||||
./node_modules/.bin/mocha --colors --require ts-node/register $*
|
||||
WITH_SERVER=n TS_NODE_PROJECT=test/tsconfig.json ./node_modules/.bin/mocha --exit --colors --require ts-node/register $*
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
./node_modules/.bin/ts-node -P ./server/tsconfig.json ./server/src/index.ts ./config.yml
|
||||
./node_modules/.bin/ts-node -P ./server/tsconfig.json ./server/src/index.ts $*
|
||||
|
|
|
@ -1,14 +1,4 @@
|
|||
#!/bin/bash
|
||||
|
||||
bridge_exists=`docker network ls | grep " authelianet " | wc -l`
|
||||
|
||||
if [ "$bridge_exists" != "1" ];
|
||||
then
|
||||
docker network create -d bridge --subnet 192.168.240.0/24 --gateway 192.168.240.1 authelianet
|
||||
else
|
||||
echo "Bridge authelianet already exist."
|
||||
fi
|
||||
|
||||
./scripts/dc-dev.sh up -d
|
||||
|
||||
./scripts/dc-dev.sh kill -s SIGHUP nginx-portal
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import WithDriver from "../helpers/with-driver";
|
||||
import LoginAndRegisterTotp from "../helpers/login-and-register-totp";
|
||||
import SeeNotification from "../helpers/see-notification";
|
||||
import VisitPage from "../helpers/visit-page";
|
||||
import FillLoginPageWithUserAndPasswordAndClick from '../helpers/fill-login-page-and-click';
|
||||
import ValidateTotp from "../helpers/validate-totp";
|
||||
import WithDriver from "../helpers/context/WithDriver";
|
||||
import LoginAndRegisterTotp from "../helpers/LoginAndRegisterTotp";
|
||||
import SeeNotification from "../helpers/SeeNotification";
|
||||
import VisitPage from "../helpers/VisitPage";
|
||||
import FillLoginPageWithUserAndPasswordAndClick from '../helpers/FillLoginPageAndClick';
|
||||
import ValidateTotp from "../helpers/ValidateTotp";
|
||||
import {CANNOT_REDIRECT_TO_EXTERNAL_DOMAIN} from '../../shared/UserMessages';
|
||||
|
||||
/*
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import WithDriver from '../helpers/with-driver';
|
||||
import fullLogin from '../helpers/full-login';
|
||||
import loginAndRegisterTotp from '../helpers/login-and-register-totp';
|
||||
import WithDriver from '../helpers/context/WithDriver';
|
||||
import fullLogin from '../helpers/FullLogin';
|
||||
import loginAndRegisterTotp from '../helpers/LoginAndRegisterTotp';
|
||||
|
||||
describe("Connection retry when mongo fails or restarts", function() {
|
||||
this.timeout(30000);
|
||||
|
|
14
test/helpers/AccessSecret.ts
Normal file
14
test/helpers/AccessSecret.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import SeleniumWebdriver from "selenium-webdriver";
|
||||
|
||||
export default async function(driver: any) {
|
||||
const content = await driver.wait(
|
||||
SeleniumWebdriver.until.elementLocated(
|
||||
SeleniumWebdriver.By.tagName('body')), 5000).getText();
|
||||
|
||||
if (content.indexOf('This is a very important secret') > - 1) {
|
||||
return;
|
||||
}
|
||||
else {
|
||||
throw new Error('Secret page is not accessible.');
|
||||
}
|
||||
}
|
8
test/helpers/ClickOn.ts
Normal file
8
test/helpers/ClickOn.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import SeleniumWebdriver, { WebDriver, Locator } from "selenium-webdriver";
|
||||
|
||||
export default async function(driver: WebDriver, locator: Locator) {
|
||||
const el = await driver.wait(
|
||||
SeleniumWebdriver.until.elementLocated(locator), 5000);
|
||||
|
||||
await el.click();
|
||||
};
|
8
test/helpers/ClickOnLink.ts
Normal file
8
test/helpers/ClickOnLink.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import SeleniumWebdriver, { WebDriver } from "selenium-webdriver";
|
||||
|
||||
export default async function(driver: WebDriver, linkText: string) {
|
||||
const element = await driver.wait(
|
||||
SeleniumWebdriver.until.elementLocated(
|
||||
SeleniumWebdriver.By.linkText(linkText)), 5000)
|
||||
await element.click();
|
||||
};
|
9
test/helpers/FillField.ts
Normal file
9
test/helpers/FillField.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import SeleniumWebdriver, { WebDriver } from "selenium-webdriver";
|
||||
|
||||
export default async function(driver: WebDriver, fieldName: string, text: string) {
|
||||
const element = await driver.wait(
|
||||
SeleniumWebdriver.until.elementLocated(
|
||||
SeleniumWebdriver.By.name(fieldName)), 5000)
|
||||
|
||||
await element.sendKeys(text);
|
||||
};
|
17
test/helpers/FillLoginPageAndClick.ts
Normal file
17
test/helpers/FillLoginPageAndClick.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import SeleniumWebdriver, { WebDriver } from "selenium-webdriver";
|
||||
|
||||
export default async function(
|
||||
driver: WebDriver,
|
||||
username: string,
|
||||
password: string,
|
||||
keepMeLoggedIn: boolean = false) {
|
||||
|
||||
await driver.wait(SeleniumWebdriver.until.elementLocated(SeleniumWebdriver.By.id("username")), 5000)
|
||||
await driver.findElement(SeleniumWebdriver.By.id("username")).sendKeys(username);
|
||||
await driver.findElement(SeleniumWebdriver.By.id("password")).sendKeys(password);
|
||||
if (keepMeLoggedIn) {
|
||||
await driver.findElement(SeleniumWebdriver.By.id("keep_me_logged_in")).click();
|
||||
return;
|
||||
}
|
||||
await driver.findElement(SeleniumWebdriver.By.tagName("button")).click();
|
||||
};
|
13
test/helpers/FullLogin.ts
Normal file
13
test/helpers/FullLogin.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import VisitPage from "./VisitPage";
|
||||
import FillLoginPageWithUserAndPasswordAndClick from "./FillLoginPageAndClick";
|
||||
import ValidateTotp from "./ValidateTotp";
|
||||
import WaitRedirected from "./WaitRedirected";
|
||||
import { WebDriver } from "selenium-webdriver";
|
||||
|
||||
// Validate the two factors!
|
||||
export default async function(driver: WebDriver, url: string, user: string, secret: string) {
|
||||
await VisitPage(driver, `https://login.example.com:8080/?rd=${url}`);
|
||||
await FillLoginPageWithUserAndPasswordAndClick(driver, user, 'password');
|
||||
await ValidateTotp(driver, secret);
|
||||
await WaitRedirected(driver, "https://admin.example.com:8080/secret.html");
|
||||
}
|
32
test/helpers/GetIdentityLink.ts
Normal file
32
test/helpers/GetIdentityLink.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import Bluebird = require("bluebird");
|
||||
import Fs = require("fs");
|
||||
import Request = require("request-promise");
|
||||
|
||||
export async function GetLinkFromFile() {
|
||||
const data = await Bluebird.promisify(Fs.readFile)("/tmp/authelia/notification.txt")
|
||||
const regexp = new RegExp(/Link: (.+)/);
|
||||
const match = regexp.exec(data.toLocaleString());
|
||||
if (match == null) {
|
||||
throw new Error('No match');
|
||||
}
|
||||
return match[1];
|
||||
};
|
||||
|
||||
export async function GetLinkFromEmail() {
|
||||
const data = await Request({
|
||||
method: "GET",
|
||||
uri: "http://localhost:8085/messages",
|
||||
json: true
|
||||
});
|
||||
const messageId = data[data.length - 1].id;
|
||||
const data2 = await Request({
|
||||
method: "GET",
|
||||
uri: `http://localhost:8085/messages/${messageId}.html`
|
||||
});
|
||||
const regexp = new RegExp(/<a href="(.+)" class="button">Continue<\/a>/);
|
||||
const match = regexp.exec(data2);
|
||||
if (match == null) {
|
||||
throw new Error('No match');
|
||||
}
|
||||
return match[1];
|
||||
};
|
5
test/helpers/IsSecondFactorStage.ts
Normal file
5
test/helpers/IsSecondFactorStage.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import SeleniumWebDriver, { WebDriver } from "selenium-webdriver";
|
||||
|
||||
export default async function(driver: WebDriver) {
|
||||
await driver.wait(SeleniumWebDriver.until.elementLocated(SeleniumWebDriver.By.className('second-factor-step')));
|
||||
}
|
10
test/helpers/LoginAndRegisterTotp.ts
Normal file
10
test/helpers/LoginAndRegisterTotp.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import RegisterTotp from './RegisterTotp';
|
||||
import LoginAs from './LoginAs';
|
||||
import { WebDriver } from 'selenium-webdriver';
|
||||
import IsSecondFactorStage from './IsSecondFactorStage';
|
||||
|
||||
export default async function(driver: WebDriver, user: string, email?: boolean) {
|
||||
await LoginAs(driver, user);
|
||||
await IsSecondFactorStage(driver);
|
||||
return await RegisterTotp(driver, email);
|
||||
}
|
8
test/helpers/LoginAs.ts
Normal file
8
test/helpers/LoginAs.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import VisitPage from "./VisitPage";
|
||||
import FillLoginPageAndClick from './FillLoginPageAndClick';
|
||||
import { WebDriver } from "selenium-webdriver";
|
||||
|
||||
export default async function(driver: WebDriver, user: string) {
|
||||
await VisitPage(driver, "https://login.example.com:8080/");
|
||||
await FillLoginPageAndClick(driver, user, "password");
|
||||
}
|
13
test/helpers/RegisterTotp.ts
Normal file
13
test/helpers/RegisterTotp.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import SeleniumWebdriver = require("selenium-webdriver");
|
||||
import {GetLinkFromFile, GetLinkFromEmail} from './GetIdentityLink';
|
||||
|
||||
export default async function(driver: SeleniumWebdriver.WebDriver, email?: boolean){
|
||||
await driver.wait(SeleniumWebdriver.until.elementLocated(SeleniumWebdriver.By.className("register-totp")), 5000)
|
||||
await driver.findElement(SeleniumWebdriver.By.className("register-totp")).click();
|
||||
await driver.sleep(500);
|
||||
|
||||
const link = (email) ? await GetLinkFromEmail() : await GetLinkFromFile();
|
||||
await driver.get(link);
|
||||
await driver.wait(SeleniumWebdriver.until.elementLocated(SeleniumWebdriver.By.className("base32-secret")), 5000);
|
||||
return await driver.findElement(SeleniumWebdriver.By.className("base32-secret")).getText();
|
||||
};
|
9
test/helpers/SeeNotification.ts
Normal file
9
test/helpers/SeeNotification.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import SeleniumWebdriver, { ThenableWebDriver, WebDriver } from "selenium-webdriver";
|
||||
import Assert = require("assert");
|
||||
|
||||
export default async function(driver: WebDriver, type: string, message: string) {
|
||||
await driver.wait(SeleniumWebdriver.until.elementLocated(SeleniumWebdriver.By.className("notification")), 5000)
|
||||
const notificationEl = driver.findElement(SeleniumWebdriver.By.className("notification"));
|
||||
const txt = await notificationEl.getText();
|
||||
Assert.equal(message, txt);
|
||||
}
|
16
test/helpers/ValidateTotp.ts
Normal file
16
test/helpers/ValidateTotp.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import Speakeasy from "speakeasy";
|
||||
import SeleniumWebdriver, { WebDriver } from 'selenium-webdriver';
|
||||
|
||||
export default async function(driver: WebDriver, secret: string) {
|
||||
const token = Speakeasy.totp({
|
||||
secret: secret,
|
||||
encoding: "base32"
|
||||
});
|
||||
|
||||
await driver.wait(SeleniumWebdriver.until.elementLocated(
|
||||
SeleniumWebdriver.By.id("totp-token")), 5000)
|
||||
await driver.findElement(SeleniumWebdriver.By.id("totp-token")).sendKeys(token);
|
||||
|
||||
const el = await driver.wait(SeleniumWebdriver.until.elementLocated(SeleniumWebdriver.By.id('totp-button')));
|
||||
el.click();
|
||||
}
|
6
test/helpers/VisitPage.ts
Normal file
6
test/helpers/VisitPage.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
import SeleniumWebdriver, { WebDriver } from "selenium-webdriver";
|
||||
|
||||
export default async function(driver: WebDriver, url: string, timeout: number = 5000) {
|
||||
await driver.get(url)
|
||||
await driver.wait(SeleniumWebdriver.until.urlIs(url), timeout);
|
||||
}
|
5
test/helpers/WaitRedirected.ts
Normal file
5
test/helpers/WaitRedirected.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import SeleniumWebdriver, { WebDriver } from "selenium-webdriver";
|
||||
|
||||
export default async function(driver: WebDriver, url: string, timeout: number = 5000) {
|
||||
await driver.wait(SeleniumWebdriver.until.urlIs(url), timeout);
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
import SeleniumWebdriver = require("selenium-webdriver");
|
||||
import Bluebird = require("bluebird");
|
||||
|
||||
export default function(driver: any) {
|
||||
return driver.findElement(
|
||||
SeleniumWebdriver.By.tagName('h1')).getText()
|
||||
.then(function(content: string) {
|
||||
return (content.indexOf('Secret') > -1)
|
||||
? Bluebird.resolve()
|
||||
: Bluebird.reject(new Error("Secret is not accessible."));
|
||||
})
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
import SeleniumWebdriver = require("selenium-webdriver");
|
||||
|
||||
export default function(driver: any, buttonText: string) {
|
||||
return driver.wait(
|
||||
SeleniumWebdriver.until.elementLocated(
|
||||
SeleniumWebdriver.By.tagName("button")), 5000)
|
||||
.then(function () {
|
||||
return driver
|
||||
.findElement(SeleniumWebdriver.By.tagName("button"))
|
||||
.findElement(SeleniumWebdriver.By.xpath("//button[contains(.,'" + buttonText + "')]"))
|
||||
.click();
|
||||
});
|
||||
};
|
|
@ -1,10 +0,0 @@
|
|||
import SeleniumWebdriver = require("selenium-webdriver");
|
||||
|
||||
export default function(driver: any, linkText: string) {
|
||||
return driver.wait(
|
||||
SeleniumWebdriver.until.elementLocated(
|
||||
SeleniumWebdriver.By.linkText(linkText)), 5000)
|
||||
.then(function (el) {
|
||||
return el.click();
|
||||
});
|
||||
};
|
34
test/helpers/context/AutheliaSuite.ts
Normal file
34
test/helpers/context/AutheliaSuite.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
import WithAutheliaRunning from "./WithAutheliaRunning";
|
||||
import WithDriver from "./WithDriver";
|
||||
|
||||
let running = false;
|
||||
|
||||
interface AutheliaSuiteType {
|
||||
(description: string, cb: (this: Mocha.ISuiteCallbackContext) => void): Mocha.ISuite;
|
||||
only: (description: string, cb: (this: Mocha.ISuiteCallbackContext) => void) => Mocha.ISuite;
|
||||
}
|
||||
|
||||
function AutheliaSuiteBase(description: string,
|
||||
context: (description: string, ctx: (this: Mocha.ISuiteCallbackContext) => void) => Mocha.ISuite,
|
||||
cb: (this: Mocha.ISuiteCallbackContext) => void) {
|
||||
if (!running && process.env['WITH_SERVER'] == 'y') {
|
||||
WithAutheliaRunning();
|
||||
running = true;
|
||||
}
|
||||
|
||||
return context('Suite: ' + description, function(this: Mocha.ISuiteCallbackContext) {
|
||||
WithDriver.call(this);
|
||||
cb.call(this);
|
||||
});
|
||||
}
|
||||
|
||||
const AutheliaSuite = <AutheliaSuiteType>function(description: string, cb: (this: Mocha.ISuiteCallbackContext) => void) {
|
||||
return AutheliaSuiteBase(description, describe, cb);
|
||||
}
|
||||
|
||||
|
||||
AutheliaSuite.only = function(description: string, cb: (this: Mocha.ISuiteCallbackContext) => void) {
|
||||
return AutheliaSuiteBase(description, describe.only, cb);
|
||||
}
|
||||
|
||||
export default AutheliaSuite as AutheliaSuiteType;
|
23
test/helpers/context/WithAutheliaRunning.ts
Normal file
23
test/helpers/context/WithAutheliaRunning.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
|
||||
import ChildProcess from 'child_process';
|
||||
|
||||
export default function WithAutheliaRunning(waitTimeout: number = 3000) {
|
||||
before(function() {
|
||||
this.timeout(5000);
|
||||
const authelia = ChildProcess.spawn(
|
||||
'./scripts/authelia-scripts',
|
||||
['serve', '--no-watch', '--config', 'config.minimal.yml'],
|
||||
{detached: true});
|
||||
this.authelia = authelia;
|
||||
|
||||
const waitPromise = new Promise((resolve, reject) => setTimeout(() => resolve(), waitTimeout));
|
||||
return waitPromise;
|
||||
});
|
||||
|
||||
after(function() {
|
||||
this.timeout(1000);
|
||||
|
||||
// Kill the group of processes.
|
||||
process.kill(-this.authelia.pid);
|
||||
});
|
||||
}
|
20
test/helpers/context/WithDriver.ts
Normal file
20
test/helpers/context/WithDriver.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
require("chromedriver");
|
||||
import chrome from 'selenium-webdriver/chrome';
|
||||
import SeleniumWebdriver from "selenium-webdriver";
|
||||
|
||||
export default function() {
|
||||
const options = new chrome.Options().addArguments('headless');
|
||||
|
||||
beforeEach(function() {
|
||||
const driver = new SeleniumWebdriver.Builder()
|
||||
.forBrowser("chrome")
|
||||
.setChromeOptions(
|
||||
new chrome.Options().headless())
|
||||
.build();
|
||||
this.driver = driver;
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
this.driver.quit();
|
||||
});
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
import SeleniumWebdriver = require("selenium-webdriver");
|
||||
|
||||
export default function(driver: any, fieldName: string, text: string) {
|
||||
return driver.wait(
|
||||
SeleniumWebdriver.until.elementLocated(
|
||||
SeleniumWebdriver.By.name(fieldName)), 5000)
|
||||
.then(function (el) {
|
||||
return el.sendKeys(text);
|
||||
});
|
||||
};
|
|
@ -1,29 +0,0 @@
|
|||
import Bluebird = require("bluebird");
|
||||
import SeleniumWebdriver = require("selenium-webdriver");
|
||||
|
||||
export default function(
|
||||
driver: any,
|
||||
username: string,
|
||||
password: string,
|
||||
keepMeLoggedIn: boolean = false) {
|
||||
return driver.wait(SeleniumWebdriver.until.elementLocated(SeleniumWebdriver.By.id("username")), 5000)
|
||||
.then(() => {
|
||||
return driver.findElement(SeleniumWebdriver.By.id("username"))
|
||||
.sendKeys(username);
|
||||
})
|
||||
.then(() => {
|
||||
return driver.findElement(SeleniumWebdriver.By.id("password"))
|
||||
.sendKeys(password);
|
||||
})
|
||||
.then(() => {
|
||||
if (keepMeLoggedIn) {
|
||||
return driver.findElement(SeleniumWebdriver.By.id("keep_me_logged_in"))
|
||||
.click();
|
||||
}
|
||||
return Bluebird.resolve();
|
||||
})
|
||||
.then(() => {
|
||||
return driver.findElement(SeleniumWebdriver.By.tagName("button"))
|
||||
.click();
|
||||
});
|
||||
};
|
|
@ -1,12 +0,0 @@
|
|||
import VisitPage from "./visit-page";
|
||||
import FillLoginPageWithUserAndPasswordAndClick from "./fill-login-page-and-click";
|
||||
import ValidateTotp from "./validate-totp";
|
||||
import WaitRedirected from "./wait-redirected";
|
||||
|
||||
// Validate the two factors!
|
||||
export default function(driver: any, url: string, user: string, secret: string) {
|
||||
return VisitPage(driver, `https://login.example.com:8080/?rd=${url}`)
|
||||
.then(() => FillLoginPageWithUserAndPasswordAndClick(driver, user, 'password'))
|
||||
.then(() => ValidateTotp(driver, secret))
|
||||
.then(() => WaitRedirected(driver, "https://admin.example.com:8080/secret.html"));
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
import Bluebird = require("bluebird");
|
||||
import Fs = require("fs");
|
||||
import Request = require("request-promise");
|
||||
|
||||
export function GetLinkFromFile(): Bluebird<string> {
|
||||
return Bluebird.promisify(Fs.readFile)("/tmp/authelia/notification.txt")
|
||||
.then(function (data: any) {
|
||||
const regexp = new RegExp(/Link: (.+)/);
|
||||
const match = regexp.exec(data);
|
||||
const link = match[1];
|
||||
return Bluebird.resolve(link);
|
||||
});
|
||||
};
|
||||
|
||||
export function GetLinkFromEmail(): Bluebird<string> {
|
||||
return Request({
|
||||
method: "GET",
|
||||
uri: "http://localhost:8085/messages",
|
||||
json: true
|
||||
})
|
||||
.then(function (data: any) {
|
||||
const messageId = data[data.length - 1].id;
|
||||
return Request({
|
||||
method: "GET",
|
||||
uri: `http://localhost:8085/messages/${messageId}.html`
|
||||
});
|
||||
})
|
||||
.then(function (data: any) {
|
||||
const regexp = new RegExp(/<a href="(.+)" class="button">Continue<\/a>/);
|
||||
const match = regexp.exec(data);
|
||||
const link = match[1];
|
||||
return Bluebird.resolve(link);
|
||||
});
|
||||
};
|
|
@ -1,10 +0,0 @@
|
|||
import RegisterTotp from './register-totp';
|
||||
import WaitRedirected from './wait-redirected';
|
||||
import LoginAs from './login-as';
|
||||
import Bluebird = require("bluebird");
|
||||
|
||||
export default function(driver: any, user: string, email?: boolean): Bluebird<string> {
|
||||
return LoginAs(driver, user)
|
||||
.then(() => WaitRedirected(driver, "https://login.example.com:8080/secondfactor"))
|
||||
.then(() => RegisterTotp(driver, email));
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
import VisitPage from "./visit-page";
|
||||
import FillLoginPageAndClick from './fill-login-page-and-click';
|
||||
|
||||
export default function(driver: any, user: string) {
|
||||
return VisitPage(driver, "https://login.example.com:8080/")
|
||||
.then(() => FillLoginPageAndClick(driver, user, "password"));
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
import Bluebird = require("bluebird");
|
||||
import SeleniumWebdriver = require("selenium-webdriver");
|
||||
import {GetLinkFromFile, GetLinkFromEmail} from '../helpers/get-identity-link';
|
||||
|
||||
export default function(driver: any, email?: boolean): Bluebird<string> {
|
||||
return driver.wait(SeleniumWebdriver.until.elementLocated(SeleniumWebdriver.By.className("register-totp")), 5000)
|
||||
.then(function () {
|
||||
return driver.findElement(SeleniumWebdriver.By.className("register-totp")).click();
|
||||
})
|
||||
.then(function () {
|
||||
if(email) return GetLinkFromEmail();
|
||||
else return GetLinkFromFile();
|
||||
})
|
||||
.then(function (link: string) {
|
||||
return driver.get(link);
|
||||
})
|
||||
.then(function () {
|
||||
return driver.wait(SeleniumWebdriver.until.elementLocated(SeleniumWebdriver.By.id("secret")), 5000);
|
||||
})
|
||||
.then(function () {
|
||||
return driver.findElement(SeleniumWebdriver.By.id("secret")).getText();
|
||||
});
|
||||
};
|
|
@ -1,18 +0,0 @@
|
|||
import SeleniumWebdriver = require("selenium-webdriver");
|
||||
import Assert = require("assert");
|
||||
|
||||
export default function(driver: any, type: string, message: string) {
|
||||
const notificationEl = driver.findElement(SeleniumWebdriver.By.className("notification"));
|
||||
return driver.wait(SeleniumWebdriver.until.elementIsVisible(notificationEl), 5000)
|
||||
.then(function () {
|
||||
return notificationEl.getText();
|
||||
})
|
||||
.then(function (txt: string) {
|
||||
Assert.equal(message, txt);
|
||||
return notificationEl.getAttribute("class");
|
||||
})
|
||||
.then(function (classes: string) {
|
||||
Assert(classes.indexOf(type) > -1, "Class '" + type + "' not found in notification element.");
|
||||
return driver.sleep(500);
|
||||
});
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
import Speakeasy = require("speakeasy");
|
||||
import SeleniumWebdriver = require("selenium-webdriver");
|
||||
import ClickOnButton from "./click-on-button";
|
||||
|
||||
export default function(driver: any, secret: string) {
|
||||
const token = Speakeasy.totp({
|
||||
secret: secret,
|
||||
encoding: "base32"
|
||||
});
|
||||
return driver.wait(
|
||||
SeleniumWebdriver.until.elementLocated(
|
||||
SeleniumWebdriver.By.id("token")), 5000)
|
||||
.then(function () {
|
||||
return driver.findElement(SeleniumWebdriver.By.id("token"))
|
||||
.sendKeys(token);
|
||||
})
|
||||
.then(function () {
|
||||
return ClickOnButton(driver, "Sign in");
|
||||
});
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
import SeleniumWebdriver = require("selenium-webdriver");
|
||||
|
||||
export default function(driver: any, url: string, timeout: number = 5000) {
|
||||
return driver.get(url)
|
||||
.then(function () {
|
||||
return driver.wait(SeleniumWebdriver.until.urlIs(url), timeout);
|
||||
});
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
import SeleniumWebdriver = require("selenium-webdriver");
|
||||
|
||||
export default function(driver: any, url: string, timeout: number = 5000) {
|
||||
return driver.wait(SeleniumWebdriver.until.urlIs(url), timeout);
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
import SeleniumWebdriver = require("selenium-webdriver");
|
||||
|
||||
export default function() {
|
||||
before(function() {
|
||||
this.driver = new SeleniumWebdriver.Builder()
|
||||
.forBrowser("chrome")
|
||||
.build();
|
||||
})
|
||||
|
||||
after(function() {
|
||||
this.driver.quit();
|
||||
});
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
import Bluebird = require("bluebird");
|
||||
import LoginAndRegisterTotp from "../helpers/login-and-register-totp";
|
||||
import VisitPage from "../helpers/visit-page";
|
||||
import FillLoginPageWithUserAndPasswordAndClick from "../helpers/fill-login-page-and-click";
|
||||
import WithDriver from "../helpers/with-driver";
|
||||
import ValidateTotp from "../helpers/validate-totp";
|
||||
import WaitRedirected from "../helpers/wait-redirected";
|
||||
import LoginAndRegisterTotp from "../helpers/LoginAndRegisterTotp";
|
||||
import VisitPage from "../helpers/VisitPage";
|
||||
import FillLoginPageWithUserAndPasswordAndClick from "../helpers/FillLoginPageAndClick";
|
||||
import WithDriver from "../helpers/context/WithDriver";
|
||||
import ValidateTotp from "../helpers/ValidateTotp";
|
||||
import WaitRedirected from "../helpers/WaitRedirected";
|
||||
|
||||
describe("Keep me logged in", function() {
|
||||
this.timeout(15000);
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
require("chromedriver");
|
||||
import ChildProcess = require('child_process');
|
||||
import Bluebird = require("bluebird");
|
||||
|
||||
const execAsync = Bluebird.promisify(ChildProcess.exec);
|
||||
|
||||
before(function() {
|
||||
this.timeout(1000);
|
||||
return execAsync("cp users_database.yml users_database.test.yml");
|
||||
});
|
||||
|
||||
after(function() {
|
||||
this.timeout(1000);
|
||||
return execAsync("rm users_database.test.yml");
|
||||
});
|
|
@ -1,30 +0,0 @@
|
|||
import WithDriver from '../helpers/with-driver';
|
||||
import FillLoginPageWithUserAndPasswordAndClick from '../helpers/fill-login-page-and-click';
|
||||
import VisitPage from '../helpers/visit-page';
|
||||
import SeeNotification from '../helpers/see-notification';
|
||||
import {AUTHENTICATION_FAILED} from '../../shared/UserMessages';
|
||||
|
||||
/**
|
||||
* When user provides bad password,
|
||||
* Then he gets a notification message.
|
||||
*/
|
||||
describe("Provide bad password", function() {
|
||||
WithDriver();
|
||||
|
||||
describe('failed login as john', function() {
|
||||
before(function() {
|
||||
this.timeout(10000);
|
||||
|
||||
const driver = this.driver;
|
||||
return VisitPage(driver, "https://login.example.com:8080/")
|
||||
.then(function() {
|
||||
return FillLoginPageWithUserAndPasswordAndClick(driver, 'john', 'bad_password');
|
||||
});
|
||||
});
|
||||
|
||||
it('should get a notification message', function() {
|
||||
this.timeout(10000);
|
||||
return SeeNotification(this.driver, "error", AUTHENTICATION_FAILED);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,39 +0,0 @@
|
|||
require("chromedriver");
|
||||
import WithDriver from '../helpers/with-driver';
|
||||
import FillLoginPageWithUserAndPasswordAndClick from '../helpers/fill-login-page-and-click';
|
||||
import VisitPage from '../helpers/visit-page';
|
||||
import ValidateTotp from '../helpers/validate-totp';
|
||||
import LoginAndRegisterTotp from '../helpers/login-and-register-totp';
|
||||
import seeNotification from "../helpers/see-notification";
|
||||
import {AUTHENTICATION_TOTP_FAILED} from '../../shared/UserMessages';
|
||||
|
||||
/**
|
||||
* Given john has registered a TOTP secret,
|
||||
* When he fails the TOTP challenge,
|
||||
* Then he gets a notification message.
|
||||
*/
|
||||
describe('Fail TOTP challenge', function() {
|
||||
this.timeout(10000);
|
||||
WithDriver();
|
||||
|
||||
describe('successfully login as john', function() {
|
||||
before(function() {
|
||||
return LoginAndRegisterTotp(this.driver, "john", true);
|
||||
});
|
||||
|
||||
describe('fail second factor', function() {
|
||||
before(function() {
|
||||
const BAD_TOKEN = "125478";
|
||||
const driver = this.driver;
|
||||
|
||||
return VisitPage(driver, "https://login.example.com:8080/?rd=https://admin.example.com:8080/secret.html")
|
||||
.then(() => FillLoginPageWithUserAndPasswordAndClick(driver, 'john', 'password'))
|
||||
.then(() => ValidateTotp(driver, BAD_TOKEN));
|
||||
});
|
||||
|
||||
it("get a notification message", function() {
|
||||
return seeNotification(this.driver, "error", AUTHENTICATION_TOTP_FAILED);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
23
test/minimal-config/index.ts
Normal file
23
test/minimal-config/index.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import ChildProcess from 'child_process';
|
||||
import Bluebird from "bluebird";
|
||||
|
||||
import AutheliaSuite from "../helpers/context/AutheliaSuite";
|
||||
import BadPassword from "./scenarii/BadPassword";
|
||||
import RegisterTotp from './scenarii/RegisterTotp';
|
||||
import ResetPassword from './scenarii/ResetPassword';
|
||||
import TOTPValidation from './scenarii/TOTPValidation';
|
||||
|
||||
const execAsync = Bluebird.promisify(ChildProcess.exec);
|
||||
|
||||
AutheliaSuite('Minimal configuration', function() {
|
||||
this.timeout(10000);
|
||||
beforeEach(function() {
|
||||
return execAsync("cp users_database.example.yml users_database.yml");
|
||||
});
|
||||
|
||||
describe('Bad password', BadPassword);
|
||||
describe('Reset password', ResetPassword);
|
||||
|
||||
describe('TOTP Registration', RegisterTotp);
|
||||
describe('TOTP Validation', TOTPValidation);
|
||||
});
|
|
@ -1,32 +0,0 @@
|
|||
import SeleniumWebdriver = require("selenium-webdriver");
|
||||
import WithDriver from '../helpers/with-driver';
|
||||
import LoginAndRegisterTotp from '../helpers/login-and-register-totp';
|
||||
|
||||
/**
|
||||
* Given the user logs in as john,
|
||||
* When he register a TOTP token,
|
||||
* Then he reach a page containing the secret as string an qrcode
|
||||
*/
|
||||
describe('Registering TOTP', function() {
|
||||
this.timeout(10000);
|
||||
WithDriver();
|
||||
|
||||
describe('successfully login as john', function() {
|
||||
before('register successfully', function() {
|
||||
this.timeout(10000);
|
||||
return LoginAndRegisterTotp(this.driver, "john", true);
|
||||
})
|
||||
|
||||
it("should see generated qrcode", function() {
|
||||
this.driver.findElement(
|
||||
SeleniumWebdriver.By.id("qrcode"),
|
||||
5000);
|
||||
});
|
||||
|
||||
it("should see generated secret", function() {
|
||||
this.driver.findElement(
|
||||
SeleniumWebdriver.By.id("secret"),
|
||||
5000);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,42 +0,0 @@
|
|||
require("chromedriver");
|
||||
import Bluebird = require("bluebird");
|
||||
import ChildProcess = require("child_process");
|
||||
|
||||
import WithDriver from '../helpers/with-driver';
|
||||
import VisitPage from '../helpers/visit-page';
|
||||
import ClickOnLink from '../helpers/click-on-link';
|
||||
import ClickOnButton from '../helpers/click-on-button';
|
||||
import WaitRedirect from '../helpers/wait-redirected';
|
||||
import FillField from "../helpers/fill-field";
|
||||
import {GetLinkFromEmail} from "../helpers/get-identity-link";
|
||||
import FillLoginPageAndClick from "../helpers/fill-login-page-and-click";
|
||||
|
||||
const execAsync = Bluebird.promisify(ChildProcess.exec);
|
||||
|
||||
describe('Reset password', function() {
|
||||
this.timeout(10000);
|
||||
WithDriver();
|
||||
|
||||
after(() => {
|
||||
return execAsync("cp users_database.yml users_database.test.yml");
|
||||
})
|
||||
|
||||
describe('click on reset password', function() {
|
||||
it("should reset password for john", function() {
|
||||
return VisitPage(this.driver, "https://login.example.com:8080/")
|
||||
.then(() => ClickOnLink(this.driver, "Forgot password\?"))
|
||||
.then(() => WaitRedirect(this.driver, "https://login.example.com:8080/password-reset/request"))
|
||||
.then(() => FillField(this.driver, "username", "john"))
|
||||
.then(() => ClickOnButton(this.driver, "Reset Password"))
|
||||
.then(() => this.driver.sleep(1000)) // Simulate the time to read it from mailbox.
|
||||
.then(() => GetLinkFromEmail())
|
||||
.then((link) => VisitPage(this.driver, link))
|
||||
.then(() => FillField(this.driver, "password1", "newpass"))
|
||||
.then(() => FillField(this.driver, "password2", "newpass"))
|
||||
.then(() => ClickOnButton(this.driver, "Reset Password"))
|
||||
.then(() => WaitRedirect(this.driver, "https://login.example.com:8080/"))
|
||||
.then(() => FillLoginPageAndClick(this.driver, "john", "newpass"))
|
||||
.then(() => WaitRedirect(this.driver, "https://login.example.com:8080/secondfactor"))
|
||||
});
|
||||
});
|
||||
});
|
23
test/minimal-config/scenarii/BadPassword.ts
Normal file
23
test/minimal-config/scenarii/BadPassword.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import FillLoginPageWithUserAndPasswordAndClick from '../../helpers/FillLoginPageAndClick';
|
||||
import VisitPage from '../../helpers/VisitPage';
|
||||
import SeeNotification from '../../helpers/SeeNotification';
|
||||
import {AUTHENTICATION_FAILED} from '../../../shared/UserMessages';
|
||||
|
||||
export default function() {
|
||||
/**
|
||||
* When user provides bad password,
|
||||
* Then he gets a notification message.
|
||||
*/
|
||||
describe('failed login as john in first factor', function() {
|
||||
beforeEach(async function() {
|
||||
this.timeout(10000);
|
||||
await VisitPage(this.driver, "https://login.example.com:8080/")
|
||||
await FillLoginPageWithUserAndPasswordAndClick(this.driver, 'john', 'bad_password');
|
||||
});
|
||||
|
||||
it('should get a notification message', async function () {
|
||||
this.timeout(10000);
|
||||
await SeeNotification(this.driver, "error", AUTHENTICATION_FAILED);
|
||||
});
|
||||
});
|
||||
}
|
30
test/minimal-config/scenarii/RegisterTotp.ts
Normal file
30
test/minimal-config/scenarii/RegisterTotp.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import SeleniumWebdriver from "selenium-webdriver";
|
||||
import LoginAndRegisterTotp from '../../helpers/LoginAndRegisterTotp';
|
||||
|
||||
/**
|
||||
* Given the user logs in as john,
|
||||
* When he register a TOTP token,
|
||||
* Then he reach a page containing the secret as string an qrcode
|
||||
*/
|
||||
export default function() {
|
||||
describe('successfully login as john', function() {
|
||||
beforeEach('register successfully', async function() {
|
||||
this.timeout(10000);
|
||||
await LoginAndRegisterTotp(this.driver, "john", true);
|
||||
})
|
||||
|
||||
it("should see generated qrcode", async function() {
|
||||
await this.driver.wait(
|
||||
SeleniumWebdriver.until.elementLocated(
|
||||
SeleniumWebdriver.By.className("qrcode")),
|
||||
5000);
|
||||
});
|
||||
|
||||
it("should see generated secret", async function() {
|
||||
await this.driver.wait(
|
||||
SeleniumWebdriver.until.elementLocated(
|
||||
SeleniumWebdriver.By.className("base32-secret")),
|
||||
5000);
|
||||
});
|
||||
});
|
||||
};
|
29
test/minimal-config/scenarii/ResetPassword.ts
Normal file
29
test/minimal-config/scenarii/ResetPassword.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
import VisitPage from '../../helpers/VisitPage';
|
||||
import ClickOnLink from '../../helpers/ClickOnLink';
|
||||
import ClickOn from '../../helpers/ClickOn';
|
||||
import WaitRedirected from '../../helpers/WaitRedirected';
|
||||
import FillField from "../../helpers/FillField";
|
||||
import {GetLinkFromEmail} from "../../helpers/GetIdentityLink";
|
||||
import FillLoginPageAndClick from "../../helpers/FillLoginPageAndClick";
|
||||
import SeleniumWebDriver from 'selenium-webdriver';
|
||||
import IsSecondFactorStage from "../../helpers/IsSecondFactorStage";
|
||||
|
||||
export default function() {
|
||||
it("should reset password for john", async function() {
|
||||
await VisitPage(this.driver, "https://login.example.com:8080/");
|
||||
await ClickOnLink(this.driver, "Forgot password\?");
|
||||
await WaitRedirected(this.driver, "https://login.example.com:8080/forgot-password");
|
||||
await FillField(this.driver, "username", "john");
|
||||
await ClickOn(this.driver, SeleniumWebDriver.By.id('next-button'));
|
||||
|
||||
await this.driver.sleep(500); // Simulate the time it takes to receive the e-mail.
|
||||
const link = await GetLinkFromEmail();
|
||||
await VisitPage(this.driver, link);
|
||||
await FillField(this.driver, "password1", "newpass");
|
||||
await FillField(this.driver, "password2", "newpass");
|
||||
await ClickOn(this.driver, SeleniumWebDriver.By.id('reset-button'));
|
||||
await WaitRedirected(this.driver, "https://login.example.com:8080/");
|
||||
await FillLoginPageAndClick(this.driver, "john", "newpass");
|
||||
await IsSecondFactorStage(this.driver);
|
||||
});
|
||||
}
|
51
test/minimal-config/scenarii/TOTPValidation.ts
Normal file
51
test/minimal-config/scenarii/TOTPValidation.ts
Normal file
|
@ -0,0 +1,51 @@
|
|||
import FillLoginPageWithUserAndPasswordAndClick from '../../helpers/FillLoginPageAndClick';
|
||||
import WaitRedirected from '../../helpers/WaitRedirected';
|
||||
import VisitPage from '../../helpers/VisitPage';
|
||||
import ValidateTotp from '../../helpers/ValidateTotp';
|
||||
import AccessSecret from "../../helpers/AccessSecret";
|
||||
import LoginAndRegisterTotp from '../../helpers/LoginAndRegisterTotp';
|
||||
import SeeNotification from '../../helpers/SeeNotification';
|
||||
import { AUTHENTICATION_TOTP_FAILED } from '../../../shared/UserMessages';
|
||||
|
||||
export default function() {
|
||||
/**
|
||||
* Given john has registered a TOTP secret,
|
||||
* When he validates the TOTP second factor,
|
||||
* Then he has access to secret page.
|
||||
*/
|
||||
describe('Successfully pass second factor with TOTP', function() {
|
||||
beforeEach(async function() {
|
||||
const secret = await LoginAndRegisterTotp(this.driver, "john", true);
|
||||
if (!secret) throw new Error('No secret!');
|
||||
|
||||
await VisitPage(this.driver, "https://login.example.com:8080/?rd=https://admin.example.com:8080/secret.html");
|
||||
await FillLoginPageWithUserAndPasswordAndClick(this.driver, 'john', 'password');
|
||||
await ValidateTotp(this.driver, secret);
|
||||
await WaitRedirected(this.driver, "https://admin.example.com:8080/secret.html");
|
||||
});
|
||||
|
||||
it("should access the secret", async function() {
|
||||
await AccessSecret(this.driver);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Given john has registered a TOTP secret,
|
||||
* When he fails the TOTP challenge,
|
||||
* Then he gets a notification message.
|
||||
*/
|
||||
describe('Fail validation of second factor with TOTP', function() {
|
||||
beforeEach(async function() {
|
||||
await LoginAndRegisterTotp(this.driver, "john", true);
|
||||
const BAD_TOKEN = "125478";
|
||||
|
||||
await VisitPage(this.driver, "https://login.example.com:8080/?rd=https://admin.example.com:8080/secret.html");
|
||||
await FillLoginPageWithUserAndPasswordAndClick(this.driver, 'john', 'password');
|
||||
await ValidateTotp(this.driver, BAD_TOKEN);
|
||||
});
|
||||
|
||||
it("get a notification message", async function() {
|
||||
await SeeNotification(this.driver, "error", AUTHENTICATION_TOTP_FAILED);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
require("chromedriver");
|
||||
import Bluebird = require("bluebird");
|
||||
import WithDriver from '../helpers/with-driver';
|
||||
import FillLoginPageWithUserAndPasswordAndClick from '../helpers/fill-login-page-and-click';
|
||||
import WaitRedirected from '../helpers/wait-redirected';
|
||||
import VisitPage from '../helpers/visit-page';
|
||||
import ValidateTotp from '../helpers/validate-totp';
|
||||
import AccessSecret from "../helpers/access-secret";
|
||||
import LoginAndRegisterTotp from '../helpers/login-and-register-totp';
|
||||
|
||||
/**
|
||||
* Given john has registered a TOTP secret,
|
||||
* When he validates the TOTP second factor,
|
||||
* Then he has access to secret page.
|
||||
*/
|
||||
describe('Validate TOTP factor', function() {
|
||||
this.timeout(10000);
|
||||
WithDriver();
|
||||
|
||||
describe('successfully login as john', function() {
|
||||
before(function() {
|
||||
const that = this;
|
||||
return LoginAndRegisterTotp(this.driver, "john", true)
|
||||
.then(function(secret: string) {
|
||||
that.secret = secret;
|
||||
})
|
||||
});
|
||||
|
||||
describe('validate second factor', function() {
|
||||
before(function() {
|
||||
const secret = this.secret;
|
||||
if(!secret) return Bluebird.reject(new Error("No secret!"));
|
||||
const driver = this.driver;
|
||||
|
||||
return VisitPage(driver, "https://login.example.com:8080/?rd=https://admin.example.com:8080/secret.html")
|
||||
.then(() => FillLoginPageWithUserAndPasswordAndClick(driver, 'john', 'password'))
|
||||
.then(() => ValidateTotp(driver, secret))
|
||||
.then(() => WaitRedirected(driver, "https://admin.example.com:8080/secret.html"));
|
||||
});
|
||||
|
||||
it("should access the secret", function() {
|
||||
return AccessSecret(this.driver);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
19
test/tsconfig.json
Normal file
19
test/tsconfig.json
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"allowJs": true,
|
||||
"skipLibCheck": false,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"lib": [
|
||||
"esnext"
|
||||
]
|
||||
}
|
||||
}
|
9
test/types/mocha.d.ts
vendored
Normal file
9
test/types/mocha.d.ts
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { WebDriver } from "selenium-webdriver";
|
||||
|
||||
/*
|
||||
declare module 'mocha' {
|
||||
interface ISuiteCallbackContext {
|
||||
driver1: WebDriver;
|
||||
}
|
||||
}
|
||||
*/
|
29
users_database.example.yml
Normal file
29
users_database.example.yml
Normal file
|
@ -0,0 +1,29 @@
|
|||
###############################################################
|
||||
# Users Database #
|
||||
###############################################################
|
||||
|
||||
# This file can be used if you do not have an LDAP set up.
|
||||
|
||||
# List of users
|
||||
users:
|
||||
john:
|
||||
password: "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
|
||||
email: john.doe@authelia.com
|
||||
groups:
|
||||
- admins
|
||||
- dev
|
||||
|
||||
harry:
|
||||
password: "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
|
||||
emails: harry.potter@authelia.com
|
||||
groups: []
|
||||
|
||||
bob:
|
||||
password: "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
|
||||
email: bob.dylan@authelia.com
|
||||
groups:
|
||||
- dev
|
||||
|
||||
james:
|
||||
password: "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
|
||||
email: james.dean@authelia.com
|
Loading…
Reference in New Issue
Block a user