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/server /usr/src/server
|
||||||
COPY dist/shared /usr/src/shared
|
COPY dist/shared /usr/src/shared
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 9091
|
||||||
|
|
||||||
VOLUME /etc/authelia
|
VOLUME /etc/authelia
|
||||||
VOLUME /var/lib/authelia
|
VOLUME /var/lib/authelia
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
|
import classnames from 'classnames';
|
||||||
|
|
||||||
import styles from '../../assets/scss/components/AlreadyAuthenticated/AlreadyAuthenticated.module.scss';
|
import styles from '../../assets/scss/components/AlreadyAuthenticated/AlreadyAuthenticated.module.scss';
|
||||||
import Button from "@material/react-button";
|
import Button from "@material/react-button";
|
||||||
|
@ -17,7 +18,7 @@ export type Props = OwnProps & DispatchProps;
|
||||||
class AlreadyAuthenticated extends Component<Props> {
|
class AlreadyAuthenticated extends Component<Props> {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={classnames(styles.container, 'already-authenticated-step')}>
|
||||||
<div className={styles.successContainer}>
|
<div className={styles.successContainer}>
|
||||||
<div className={styles.messageContainer}>
|
<div className={styles.messageContainer}>
|
||||||
<span className={styles.username}>{this.props.username}</span>
|
<span className={styles.username}>{this.props.username}</span>
|
||||||
|
|
|
@ -65,7 +65,7 @@ class FirstFactorForm extends Component<Props, State> {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className='first-factor-step'>
|
||||||
<Notification
|
<Notification
|
||||||
show={this.props.error != null}
|
show={this.props.error != null}
|
||||||
className={styles.notification}>
|
className={styles.notification}>
|
||||||
|
@ -103,8 +103,9 @@ class FirstFactorForm extends Component<Props, State> {
|
||||||
<div className={styles.buttons}>
|
<div className={styles.buttons}>
|
||||||
<Button
|
<Button
|
||||||
onClick={this.onLoginClicked}
|
onClick={this.onLoginClicked}
|
||||||
color="primary"
|
color='primary'
|
||||||
raised={true}
|
raised={true}
|
||||||
|
id='login-button'
|
||||||
disabled={this.props.formDisabled}>
|
disabled={this.props.formDisabled}>
|
||||||
Login
|
Login
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -11,7 +11,7 @@ interface Props {
|
||||||
class Notification extends Component<Props> {
|
class Notification extends Component<Props> {
|
||||||
render() {
|
render() {
|
||||||
return (this.props.show)
|
return (this.props.show)
|
||||||
? (<div className={classnames(styles.container, this.props.className)}>
|
? (<div className={classnames(styles.container, this.props.className, 'notification')}>
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</div>)
|
</div>)
|
||||||
: null;
|
: 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 TextField, { Input } from '@material/react-text-field';
|
||||||
import Button from '@material/react-button';
|
import Button from '@material/react-button';
|
||||||
|
@ -63,7 +64,7 @@ class SecondFactorView extends Component<Props, State> {
|
||||||
<CircleLoader status={u2fStatus}></CircleLoader>
|
<CircleLoader status={u2fStatus}></CircleLoader>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.registerDeviceContainer}>
|
<div className={styles.registerDeviceContainer}>
|
||||||
<a className={styles.registerDevice} href="#"
|
<a className={classnames(styles.registerDevice, 'register-u2f')} href="#"
|
||||||
onClick={this.props.onRegisterSecurityKeyClicked}>
|
onClick={this.props.onRegisterSecurityKeyClicked}>
|
||||||
Register device
|
Register device
|
||||||
</a>
|
</a>
|
||||||
|
@ -89,7 +90,7 @@ class SecondFactorView extends Component<Props, State> {
|
||||||
|
|
||||||
private renderTotp(n: number) {
|
private renderTotp(n: number) {
|
||||||
return (
|
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>
|
<div className={styles.methodName}>Option {n} - One-Time Password</div>
|
||||||
<Notification show={this.props.oneTimePasswordVerificationError !== null}>
|
<Notification show={this.props.oneTimePasswordVerificationError !== null}>
|
||||||
{this.props.oneTimePasswordVerificationError}
|
{this.props.oneTimePasswordVerificationError}
|
||||||
|
@ -99,14 +100,14 @@ class SecondFactorView extends Component<Props, State> {
|
||||||
label="One-Time Password"
|
label="One-Time Password"
|
||||||
outlined={true}>
|
outlined={true}>
|
||||||
<Input
|
<Input
|
||||||
name="totp-token"
|
name='totp-token'
|
||||||
id="totp-token"
|
id='totp-token'
|
||||||
onChange={this.onOneTimePasswordChanged as any}
|
onChange={this.onOneTimePasswordChanged as any}
|
||||||
onKeyPress={this.onTotpKeyPressed}
|
onKeyPress={this.onTotpKeyPressed}
|
||||||
value={this.state.oneTimePassword} />
|
value={this.state.oneTimePassword} />
|
||||||
</TextField>
|
</TextField>
|
||||||
<div className={styles.registerDeviceContainer}>
|
<div className={styles.registerDeviceContainer}>
|
||||||
<a className={styles.registerDevice} href="#"
|
<a className={classnames(styles.registerDevice, 'register-totp')} href="#"
|
||||||
onClick={this.props.onRegisterOneTimePasswordClicked}>
|
onClick={this.props.onRegisterOneTimePasswordClicked}>
|
||||||
Register device
|
Register device
|
||||||
</a>
|
</a>
|
||||||
|
@ -115,6 +116,7 @@ class SecondFactorView extends Component<Props, State> {
|
||||||
<Button
|
<Button
|
||||||
color="primary"
|
color="primary"
|
||||||
raised={true}
|
raised={true}
|
||||||
|
id='totp-button'
|
||||||
onClick={this.onOneTimePasswordValidationRequested}
|
onClick={this.onOneTimePasswordValidationRequested}
|
||||||
disabled={this.props.oneTimePasswordVerificationInProgress}>
|
disabled={this.props.oneTimePasswordVerificationInProgress}>
|
||||||
OK
|
OK
|
||||||
|
|
|
@ -51,9 +51,10 @@ class ForgotPasswordView extends Component<Props, State> {
|
||||||
<TextField
|
<TextField
|
||||||
className={styles.field}
|
className={styles.field}
|
||||||
outlined={true}
|
outlined={true}
|
||||||
id="username"
|
|
||||||
label="Username">
|
label="Username">
|
||||||
<Input
|
<Input
|
||||||
|
id="username"
|
||||||
|
name="username"
|
||||||
onChange={this.onUsernameChanged}
|
onChange={this.onUsernameChanged}
|
||||||
onKeyPress={this.onKeyPressed}
|
onKeyPress={this.onKeyPressed}
|
||||||
value={this.state.username}
|
value={this.state.username}
|
||||||
|
@ -64,6 +65,7 @@ class ForgotPasswordView extends Component<Props, State> {
|
||||||
<Button
|
<Button
|
||||||
onClick={this.onPasswordResetRequested}
|
onClick={this.onPasswordResetRequested}
|
||||||
color="primary"
|
color="primary"
|
||||||
|
id="next-button"
|
||||||
raised={true}
|
raised={true}
|
||||||
className={styles.buttonConfirm}
|
className={styles.buttonConfirm}
|
||||||
disabled={this.props.disabled}>
|
disabled={this.props.disabled}>
|
||||||
|
@ -75,6 +77,7 @@ class ForgotPasswordView extends Component<Props, State> {
|
||||||
onClick={this.props.onCancelClicked}
|
onClick={this.props.onCancelClicked}
|
||||||
color="primary"
|
color="primary"
|
||||||
raised={true}
|
raised={true}
|
||||||
|
id="cancel-button"
|
||||||
className={styles.buttonCancel}>
|
className={styles.buttonCancel}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
|
import classnames from 'classnames';
|
||||||
|
|
||||||
import Button from "@material/react-button";
|
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.
|
Register your device by scanning the barcode or adding the key.
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.secretContainer}>
|
<div className={styles.secretContainer}>
|
||||||
<div className={styles.qrcodeContainer}>
|
<div className={classnames(styles.qrcodeContainer, 'qrcode')}>
|
||||||
<QRCode value={secret.otpauth_url} size={180} level="Q"></QRCode>
|
<QRCode value={secret.otpauth_url} size={180} level="Q"></QRCode>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.base32Container}>{secret.base32_secret}</div>
|
<div className={classnames(styles.base32Container, 'base32-secret')}>{secret.base32_secret}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.loginButtonContainer}>
|
<div className={styles.loginButtonContainer}>
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -92,6 +92,7 @@ class ResetPasswordView extends Component<Props, State> {
|
||||||
<Input
|
<Input
|
||||||
type="password"
|
type="password"
|
||||||
key="password1"
|
key="password1"
|
||||||
|
name="password1"
|
||||||
value={this.state.password1}
|
value={this.state.password1}
|
||||||
onChange={this.onPassword1Changed}
|
onChange={this.onPassword1Changed}
|
||||||
disabled={this.props.disabled}/>
|
disabled={this.props.disabled}/>
|
||||||
|
@ -104,6 +105,7 @@ class ResetPasswordView extends Component<Props, State> {
|
||||||
<Input
|
<Input
|
||||||
type="password"
|
type="password"
|
||||||
key="password2"
|
key="password2"
|
||||||
|
name="password2"
|
||||||
value={this.state.password2}
|
value={this.state.password2}
|
||||||
onKeyPress={this.onKeyPressed}
|
onKeyPress={this.onKeyPressed}
|
||||||
onChange={this.onPassword2Changed}
|
onChange={this.onPassword2Changed}
|
||||||
|
@ -114,6 +116,7 @@ class ResetPasswordView extends Component<Props, State> {
|
||||||
<Button
|
<Button
|
||||||
onClick={this.onResetClicked}
|
onClick={this.onResetClicked}
|
||||||
color="primary"
|
color="primary"
|
||||||
|
id="reset-button"
|
||||||
raised={true}
|
raised={true}
|
||||||
disabled={this.props.disabled}
|
disabled={this.props.disabled}
|
||||||
className={classnames(styles.button, styles.buttonReset)}>
|
className={classnames(styles.button, styles.buttonReset)}>
|
||||||
|
@ -124,6 +127,7 @@ class ResetPasswordView extends Component<Props, State> {
|
||||||
<Button
|
<Button
|
||||||
onClick={this.props.onCancelClicked}
|
onClick={this.props.onCancelClicked}
|
||||||
color="primary"
|
color="primary"
|
||||||
|
id="cancel-button"
|
||||||
raised={true}
|
raised={true}
|
||||||
className={classnames(styles.button, styles.buttonCancel)}>
|
className={classnames(styles.button, styles.buttonCancel)}>
|
||||||
Cancel
|
Cancel
|
||||||
|
|
|
@ -2,9 +2,11 @@
|
||||||
# Authelia minimal configuration #
|
# Authelia minimal configuration #
|
||||||
###############################################################
|
###############################################################
|
||||||
|
|
||||||
|
port: 9091
|
||||||
|
|
||||||
authentication_backend:
|
authentication_backend:
|
||||||
file:
|
file:
|
||||||
path: /etc/authelia/users_database.yml
|
path: ./users_database.yml
|
||||||
|
|
||||||
session:
|
session:
|
||||||
secret: unsecure_session_secret
|
secret: unsecure_session_secret
|
||||||
|
@ -96,7 +98,7 @@ notifier:
|
||||||
username: test
|
username: test
|
||||||
password: password
|
password: password
|
||||||
secure: false
|
secure: false
|
||||||
host: 'smtp'
|
host: 127.0.0.1
|
||||||
port: 1025
|
port: 1025
|
||||||
sender: admin@example.com
|
sender: admin@example.com
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
###############################################################
|
###############################################################
|
||||||
|
|
||||||
# The port to listen on
|
# The port to listen on
|
||||||
port: 8080
|
port: 9091
|
||||||
|
|
||||||
# Log level
|
# Log level
|
||||||
#
|
#
|
||||||
|
@ -42,7 +42,7 @@ authentication_backend:
|
||||||
# production.
|
# production.
|
||||||
ldap:
|
ldap:
|
||||||
# The url of the ldap server
|
# The url of the ldap server
|
||||||
url: ldap://openldap
|
url: ldap://127.0.0.1
|
||||||
|
|
||||||
# The base dn for every entries
|
# The base dn for every entries
|
||||||
base_dn: dc=example,dc=com
|
base_dn: dc=example,dc=com
|
||||||
|
@ -198,7 +198,7 @@ session:
|
||||||
|
|
||||||
# The redis connection details
|
# The redis connection details
|
||||||
redis:
|
redis:
|
||||||
host: redis
|
host: 127.0.0.1
|
||||||
port: 6379
|
port: 6379
|
||||||
password: authelia
|
password: authelia
|
||||||
|
|
||||||
|
@ -229,7 +229,7 @@ storage:
|
||||||
|
|
||||||
# Settings to connect to mongo server
|
# Settings to connect to mongo server
|
||||||
mongo:
|
mongo:
|
||||||
url: mongodb://mongo
|
url: mongodb://127.0.0.1
|
||||||
database: authelia
|
database: authelia
|
||||||
auth:
|
auth:
|
||||||
username: authelia
|
username: authelia
|
||||||
|
@ -258,6 +258,6 @@ notifier:
|
||||||
username: test
|
username: test
|
||||||
password: password
|
password: password
|
||||||
secure: false
|
secure: false
|
||||||
host: 'smtp'
|
host: 127.0.0.1
|
||||||
port: 1025
|
port: 1025
|
||||||
sender: admin@example.com
|
sender: admin@example.com
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
version: '2'
|
version: '2'
|
||||||
services: {}
|
# services: {}
|
||||||
networks:
|
networks:
|
||||||
authelianet:
|
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;
|
server_name public.example.com;
|
||||||
|
|
||||||
resolver 127.0.0.11 ipv6=off;
|
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_endpoint http://nginx-backend;
|
||||||
set $upstream_headers http://httpbin:8000/headers;
|
set $upstream_headers http://httpbin:8000/headers;
|
||||||
|
|
||||||
|
@ -179,7 +179,7 @@ http {
|
||||||
server_name admin.example.com;
|
server_name admin.example.com;
|
||||||
|
|
||||||
resolver 127.0.0.11 ipv6=off;
|
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_endpoint http://nginx-backend;
|
||||||
|
|
||||||
ssl_certificate /etc/ssl/server.crt;
|
ssl_certificate /etc/ssl/server.crt;
|
||||||
|
@ -229,7 +229,7 @@ http {
|
||||||
server_name dev.example.com;
|
server_name dev.example.com;
|
||||||
|
|
||||||
resolver 127.0.0.11 ipv6=off;
|
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_endpoint http://nginx-backend;
|
||||||
|
|
||||||
ssl_certificate /etc/ssl/server.crt;
|
ssl_certificate /etc/ssl/server.crt;
|
||||||
|
@ -279,7 +279,7 @@ http {
|
||||||
server_name mx1.mail.example.com mx2.mail.example.com;
|
server_name mx1.mail.example.com mx2.mail.example.com;
|
||||||
|
|
||||||
resolver 127.0.0.11 ipv6=off;
|
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_endpoint http://nginx-backend;
|
||||||
|
|
||||||
ssl_certificate /etc/ssl/server.crt;
|
ssl_certificate /etc/ssl/server.crt;
|
||||||
|
@ -329,7 +329,7 @@ http {
|
||||||
server_name single_factor.example.com;
|
server_name single_factor.example.com;
|
||||||
|
|
||||||
resolver 127.0.0.11 ipv6=off;
|
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_endpoint http://nginx-backend;
|
||||||
set $upstream_headers http://httpbin:8000/headers;
|
set $upstream_headers http://httpbin:8000/headers;
|
||||||
|
|
||||||
|
@ -400,7 +400,7 @@ http {
|
||||||
server_name authelia.example.com;
|
server_name authelia.example.com;
|
||||||
|
|
||||||
resolver 127.0.0.11 ipv6=off;
|
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 /etc/ssl/server.crt;
|
||||||
ssl_certificate_key /etc/ssl/server.key;
|
ssl_certificate_key /etc/ssl/server.key;
|
||||||
|
|
|
@ -17,6 +17,14 @@ if (program.production) {
|
||||||
options['production'] = true;
|
options['production'] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
html = ejs.renderFile(__dirname + '/nginx.conf.ejs', options, (err, conf) => {
|
const templatePath = __dirname + '/nginx.conf.ejs';
|
||||||
fs.writeFileSync(__dirname + '/nginx.conf', conf);
|
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",
|
"grunt-run": "^0.8.0",
|
||||||
"istanbul": "^0.4.5",
|
"istanbul": "^0.4.5",
|
||||||
"jquery": "^3.2.1",
|
"jquery": "^3.2.1",
|
||||||
"mocha": "^5.0.5",
|
"mocha": "^5.2.0",
|
||||||
"mockdate": "^2.0.1",
|
"mockdate": "^2.0.1",
|
||||||
"nodemon": "^1.18.9",
|
"nodemon": "^1.18.9",
|
||||||
"nyc": "^13.1.0",
|
"nyc": "^13.1.0",
|
||||||
|
@ -107,7 +107,7 @@
|
||||||
"tmp": "0.0.33",
|
"tmp": "0.0.33",
|
||||||
"ts-node": "^6.0.1",
|
"ts-node": "^6.0.1",
|
||||||
"tslint": "^5.2.0",
|
"tslint": "^5.2.0",
|
||||||
"typescript": "^2.3.2",
|
"typescript": "^2.9.2",
|
||||||
"typescript-json-schema": "^0.23.0"
|
"typescript-json-schema": "^0.23.0"
|
||||||
},
|
},
|
||||||
"nyc": {
|
"nyc": {
|
||||||
|
|
|
@ -4,6 +4,7 @@ var program = require('commander');
|
||||||
|
|
||||||
program
|
program
|
||||||
.version('0.0.1')
|
.version('0.0.1')
|
||||||
|
|
||||||
.command('start', 'Start development environment.')
|
.command('start', 'Start development environment.')
|
||||||
.command('build', 'Build production version of Authelia from source.')
|
.command('build', 'Build production version of Authelia from source.')
|
||||||
.command('clean', 'Clean the production version of Authelia.')
|
.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('build-docker', 'Build Docker image containing production version of Authelia.')
|
||||||
.command('publish-docker', 'Publish Docker image containing production version of Authelia to Dockerhub.')
|
.command('publish-docker', 'Publish Docker image containing production version of Authelia to Dockerhub.')
|
||||||
|
.parse(process.argv);
|
||||||
program.parse(process.argv);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,39 @@
|
||||||
#!/bin/bash
|
#!/usr/bin/env node
|
||||||
|
|
||||||
# Render the production version of the nginx portal configuration
|
var program = require('commander');
|
||||||
./example/compose/nginx/portal/render.js --production
|
var execSync = require('child_process').execSync;
|
||||||
|
var spawn = require('child_process').spawn;
|
||||||
|
|
||||||
./scripts/utils/prepare-environment.sh
|
program
|
||||||
|
.option('-c, --config <config>', 'Configuration file to run Authelia with.')
|
||||||
|
.option('--no-watch', 'Disable hot reload.')
|
||||||
|
.parse(process.argv);
|
||||||
|
|
||||||
./node_modules/.bin/nodemon -e yml --exec 'node dist/server/src/index.js config.yml'
|
let config = 'config.yml'; // set default config file.
|
||||||
|
|
||||||
|
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
|
#!/bin/bash
|
||||||
|
|
||||||
|
config_path=$1
|
||||||
|
|
||||||
|
if [ "$config_path" == "" ];
|
||||||
|
then
|
||||||
|
echo "Please provide a configuration file."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
./example/compose/nginx/portal/render.js
|
./example/compose/nginx/portal/render.js
|
||||||
|
|
||||||
./scripts/utils/prepare-environment.sh
|
./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"
|
client_watcher="cd client && BROWSER=none npm run start > /tmp/authelia-client.log"
|
||||||
|
|
||||||
./node_modules/.bin/concurrently "$server_watcher" "$client_watcher"
|
./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
|
#!/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
|
#!/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
|
#!/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 up -d
|
||||||
|
|
||||||
./scripts/dc-dev.sh kill -s SIGHUP nginx-portal
|
./scripts/dc-dev.sh kill -s SIGHUP nginx-portal
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import WithDriver from "../helpers/with-driver";
|
import WithDriver from "../helpers/context/WithDriver";
|
||||||
import LoginAndRegisterTotp from "../helpers/login-and-register-totp";
|
import LoginAndRegisterTotp from "../helpers/LoginAndRegisterTotp";
|
||||||
import SeeNotification from "../helpers/see-notification";
|
import SeeNotification from "../helpers/SeeNotification";
|
||||||
import VisitPage from "../helpers/visit-page";
|
import VisitPage from "../helpers/VisitPage";
|
||||||
import FillLoginPageWithUserAndPasswordAndClick from '../helpers/fill-login-page-and-click';
|
import FillLoginPageWithUserAndPasswordAndClick from '../helpers/FillLoginPageAndClick';
|
||||||
import ValidateTotp from "../helpers/validate-totp";
|
import ValidateTotp from "../helpers/ValidateTotp";
|
||||||
import {CANNOT_REDIRECT_TO_EXTERNAL_DOMAIN} from '../../shared/UserMessages';
|
import {CANNOT_REDIRECT_TO_EXTERNAL_DOMAIN} from '../../shared/UserMessages';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import WithDriver from '../helpers/with-driver';
|
import WithDriver from '../helpers/context/WithDriver';
|
||||||
import fullLogin from '../helpers/full-login';
|
import fullLogin from '../helpers/FullLogin';
|
||||||
import loginAndRegisterTotp from '../helpers/login-and-register-totp';
|
import loginAndRegisterTotp from '../helpers/LoginAndRegisterTotp';
|
||||||
|
|
||||||
describe("Connection retry when mongo fails or restarts", function() {
|
describe("Connection retry when mongo fails or restarts", function() {
|
||||||
this.timeout(30000);
|
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 Bluebird = require("bluebird");
|
||||||
import LoginAndRegisterTotp from "../helpers/login-and-register-totp";
|
import LoginAndRegisterTotp from "../helpers/LoginAndRegisterTotp";
|
||||||
import VisitPage from "../helpers/visit-page";
|
import VisitPage from "../helpers/VisitPage";
|
||||||
import FillLoginPageWithUserAndPasswordAndClick from "../helpers/fill-login-page-and-click";
|
import FillLoginPageWithUserAndPasswordAndClick from "../helpers/FillLoginPageAndClick";
|
||||||
import WithDriver from "../helpers/with-driver";
|
import WithDriver from "../helpers/context/WithDriver";
|
||||||
import ValidateTotp from "../helpers/validate-totp";
|
import ValidateTotp from "../helpers/ValidateTotp";
|
||||||
import WaitRedirected from "../helpers/wait-redirected";
|
import WaitRedirected from "../helpers/WaitRedirected";
|
||||||
|
|
||||||
describe("Keep me logged in", function() {
|
describe("Keep me logged in", function() {
|
||||||
this.timeout(15000);
|
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