commit
d0954a202a
|
@ -0,0 +1,19 @@
|
||||||
|
Release Notes - Version 3.7.1
|
||||||
|
-------------------------------
|
||||||
|
Configuration change:
|
||||||
|
* storage.mongo now contains two keys: `url` and `database`
|
||||||
|
|
||||||
|
Release Notes - Version 3.7.0
|
||||||
|
-------------------------------
|
||||||
|
Features:
|
||||||
|
* Support basic authorization for single factor endpoints
|
||||||
|
* Add issuer and label in TOTP otp url
|
||||||
|
* Improve UI of second factor page
|
||||||
|
* Use SHA512 password encryption algorithm of LDAP
|
||||||
|
* Improve security of Authelia website
|
||||||
|
* Support for default redirection url
|
||||||
|
* Support for session inactivity timeout
|
||||||
|
|
||||||
|
Bugs:
|
||||||
|
* Fix U2F factor not working in Firefox
|
||||||
|
|
|
@ -180,8 +180,8 @@ module.exports = function (grunt) {
|
||||||
grunt.registerTask('compile-server', ['run:lint-server', 'run:compile-server'])
|
grunt.registerTask('compile-server', ['run:lint-server', 'run:compile-server'])
|
||||||
grunt.registerTask('compile-client', ['run:lint-client', 'run:compile-client'])
|
grunt.registerTask('compile-client', ['run:lint-client', 'run:compile-client'])
|
||||||
|
|
||||||
grunt.registerTask('test-server', ['env:env-test-server-unit', 'run:test-server-unit'])
|
grunt.registerTask('test-server', ['run:test-server-unit'])
|
||||||
grunt.registerTask('test-client', ['env:env-test-client-unit', 'run:test-client-unit'])
|
grunt.registerTask('test-client', ['run:test-client-unit'])
|
||||||
grunt.registerTask('test-unit', ['test-server', 'test-client']);
|
grunt.registerTask('test-unit', ['test-server', 'test-client']);
|
||||||
grunt.registerTask('test-int', ['run:test-int']);
|
grunt.registerTask('test-int', ['run:test-int']);
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,8 @@ export default function ($: JQueryStatic, url: string, data: Object, fn: any,
|
||||||
.done(function (data: any) {
|
.done(function (data: any) {
|
||||||
resolve(data);
|
resolve(data);
|
||||||
})
|
})
|
||||||
.fail(function (err: Error) {
|
.fail(function (xhr: JQueryXHR, textStatus: string) {
|
||||||
reject(err);
|
reject(textStatus);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
|
@ -13,8 +13,8 @@ export default function (window: Window, $: JQueryStatic,
|
||||||
const notifier = new Notifier(".notification", $);
|
const notifier = new Notifier(".notification", $);
|
||||||
|
|
||||||
function onFormSubmitted() {
|
function onFormSubmitted() {
|
||||||
const username: string = $(UISelectors.USERNAME_FIELD_ID).val();
|
const username: string = $(UISelectors.USERNAME_FIELD_ID).val() as string;
|
||||||
const password: string = $(UISelectors.PASSWORD_FIELD_ID).val();
|
const password: string = $(UISelectors.PASSWORD_FIELD_ID).val() as string;
|
||||||
$(UISelectors.PASSWORD_FIELD_ID).val("");
|
$(UISelectors.PASSWORD_FIELD_ID).val("");
|
||||||
|
|
||||||
const redirectUrl = QueryParametersRetriever.get(Constants.REDIRECT_QUERY_PARAM);
|
const redirectUrl = QueryParametersRetriever.get(Constants.REDIRECT_QUERY_PARAM);
|
||||||
|
|
|
@ -28,8 +28,8 @@ export default function (window: Window, $: JQueryStatic) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function onFormSubmitted() {
|
function onFormSubmitted() {
|
||||||
const password1 = $("#password1").val();
|
const password1 = $("#password1").val() as string;
|
||||||
const password2 = $("#password2").val();
|
const password2 = $("#password2").val() as string;
|
||||||
|
|
||||||
if (!password1 || !password2) {
|
if (!password1 || !password2) {
|
||||||
notifier.warning(UserMessages.MISSING_PASSWORD);
|
notifier.warning(UserMessages.MISSING_PASSWORD);
|
||||||
|
|
|
@ -29,7 +29,7 @@ export default function (window: Window, $: JQueryStatic) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function onFormSubmitted() {
|
function onFormSubmitted() {
|
||||||
const username = $("#username").val();
|
const username = $("#username").val() as string;
|
||||||
|
|
||||||
if (!username) {
|
if (!username) {
|
||||||
notifier.warning(UserMessages.MISSING_USERNAME);
|
notifier.warning(UserMessages.MISSING_USERNAME);
|
||||||
|
|
|
@ -40,7 +40,7 @@ export default function (window: Window, $: JQueryStatic, u2fApi: U2fApi.U2fApi)
|
||||||
}
|
}
|
||||||
|
|
||||||
function onTOTPFormSubmitted(): boolean {
|
function onTOTPFormSubmitted(): boolean {
|
||||||
const token = $(ClientConstants.TOTP_TOKEN_SELECTOR).val();
|
const token = $(ClientConstants.TOTP_TOKEN_SELECTOR).val() as string;
|
||||||
TOTPValidator.validate(token, $)
|
TOTPValidator.validate(token, $)
|
||||||
.then(onSecondFactorTotpSuccess)
|
.then(onSecondFactorTotpSuccess)
|
||||||
.catch(onSecondFactorTotpFailure);
|
.catch(onSecondFactorTotpFailure);
|
||||||
|
|
|
@ -209,7 +209,8 @@ storage:
|
||||||
|
|
||||||
# Settings to connect to mongo server
|
# Settings to connect to mongo server
|
||||||
mongo:
|
mongo:
|
||||||
url: mongodb://mongo/authelia
|
url: mongodb://mongo
|
||||||
|
database: authelia
|
||||||
|
|
||||||
# Configuration of the notification system.
|
# Configuration of the notification system.
|
||||||
#
|
#
|
||||||
|
|
|
@ -3,3 +3,7 @@ services:
|
||||||
authelia:
|
authelia:
|
||||||
volumes:
|
volumes:
|
||||||
- ./config.test.yml:/etc/authelia/config.yml:ro
|
- ./config.test.yml:/etc/authelia/config.yml:ro
|
||||||
|
- ./dist/server:/usr/src/server
|
||||||
|
- ./dist/shared:/usr/src/shared
|
||||||
|
networks:
|
||||||
|
- example-network
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
66
package.json
66
package.json
|
@ -6,7 +6,7 @@
|
||||||
"authelia": "./dist/server/src/index.js"
|
"authelia": "./dist/server/src/index.js"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "./node_modules/.bin/grunt unit-tests",
|
"test": "./node_modules/.bin/grunt test-unit",
|
||||||
"cover": "NODE_ENV=test nyc npm t",
|
"cover": "NODE_ENV=test nyc npm t",
|
||||||
"serve": "node dist/server/index.js"
|
"serve": "node dist/server/index.js"
|
||||||
},
|
},
|
||||||
|
@ -23,7 +23,7 @@
|
||||||
"title": "Authelia API documentation"
|
"title": "Authelia API documentation"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ajv": "^5.2.3",
|
"ajv": "^6.3.0",
|
||||||
"bluebird": "^3.5.0",
|
"bluebird": "^3.5.0",
|
||||||
"body-parser": "^1.15.2",
|
"body-parser": "^1.15.2",
|
||||||
"connect-redis": "^3.3.0",
|
"connect-redis": "^3.3.0",
|
||||||
|
@ -32,10 +32,12 @@
|
||||||
"express": "^4.14.0",
|
"express": "^4.14.0",
|
||||||
"express-request-id": "^1.4.0",
|
"express-request-id": "^1.4.0",
|
||||||
"express-session": "^1.14.2",
|
"express-session": "^1.14.2",
|
||||||
"ldapjs": "^1.0.1",
|
"ldapjs": "^1.0.2",
|
||||||
"mongodb": "^2.2.30",
|
"mongodb": "^3.0.5",
|
||||||
"nedb": "^1.8.0",
|
"nedb": "^1.8.0",
|
||||||
"nodemailer": "^4.0.1",
|
"nodemailer": "^4.0.1",
|
||||||
|
"nodemailer-direct-transport": "^3.3.2",
|
||||||
|
"nodemailer-smtp-transport": "^2.7.4",
|
||||||
"object-path": "^0.11.3",
|
"object-path": "^0.11.3",
|
||||||
"pug": "^2.0.0-rc.2",
|
"pug": "^2.0.0-rc.2",
|
||||||
"randomstring": "^1.1.5",
|
"randomstring": "^1.1.5",
|
||||||
|
@ -43,43 +45,45 @@
|
||||||
"speakeasy": "^2.0.0",
|
"speakeasy": "^2.0.0",
|
||||||
"u2f": "^0.1.2",
|
"u2f": "^0.1.2",
|
||||||
"winston": "^2.3.1",
|
"winston": "^2.3.1",
|
||||||
"yamljs": "^0.2.8"
|
"yamljs": "^0.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bluebird": "^3.5.4",
|
"@types/bluebird": "^3.5.4",
|
||||||
"@types/body-parser": "^1.16.3",
|
"@types/body-parser": "^1.16.3",
|
||||||
"@types/bootstrap": "^3.3.36",
|
"@types/bootstrap": "^4.0.1",
|
||||||
"@types/connect-redis": "0.0.6",
|
"@types/connect-redis": "0.0.7",
|
||||||
"@types/cors": "^2.8.1",
|
"@types/cors": "^2.8.1",
|
||||||
"@types/cucumber": "^2.0.1",
|
"@types/cucumber": "^4.0.1",
|
||||||
"@types/ejs": "^2.3.33",
|
"@types/ejs": "^2.3.33",
|
||||||
"@types/express": "^4.0.35",
|
"@types/express": "^4.0.35",
|
||||||
"@types/express-session": "0.0.32",
|
"@types/express-session": "1.15.8",
|
||||||
"@types/jquery": "^2.0.45",
|
"@types/jquery": "^3.3.1",
|
||||||
"@types/jsdom": "^2.0.30",
|
"@types/jsdom": "^11.0.4",
|
||||||
"@types/ldapjs": "^1.0.0",
|
"@types/ldapjs": "^1.0.2",
|
||||||
"@types/mocha": "^2.2.41",
|
"@types/mocha": "^5.0.0",
|
||||||
"@types/mockdate": "^2.0.0",
|
"@types/mockdate": "^2.0.0",
|
||||||
"@types/mongodb": "^2.2.7",
|
"@types/mongodb": "^3.0.9",
|
||||||
"@types/nedb": "^1.8.3",
|
"@types/nedb": "^1.8.3",
|
||||||
"@types/nodemailer": "^3.1.3",
|
"@types/nodemailer": "^4.6.0",
|
||||||
|
"@types/nodemailer-direct-transport": "^1.0.31",
|
||||||
|
"@types/nodemailer-smtp-transport": "^2.7.4",
|
||||||
"@types/object-path": "^0.9.28",
|
"@types/object-path": "^0.9.28",
|
||||||
"@types/proxyquire": "^1.3.27",
|
"@types/proxyquire": "^1.3.27",
|
||||||
"@types/query-string": "^4.3.1",
|
"@types/query-string": "^5.1.0",
|
||||||
"@types/randomstring": "^1.1.5",
|
"@types/randomstring": "^1.1.5",
|
||||||
"@types/redis": "^2.6.0",
|
"@types/redis": "^2.6.0",
|
||||||
"@types/request": "^2.0.5",
|
"@types/request": "^2.0.5",
|
||||||
"@types/request-promise": "^4.1.38",
|
"@types/request-promise": "^4.1.38",
|
||||||
"@types/selenium-webdriver": "^3.0.4",
|
"@types/selenium-webdriver": "^3.0.4",
|
||||||
"@types/sinon": "^2.3.7",
|
"@types/sinon": "^4.3.0",
|
||||||
"@types/tmp": "0.0.33",
|
"@types/tmp": "0.0.33",
|
||||||
"@types/winston": "^2.3.2",
|
"@types/winston": "^2.3.2",
|
||||||
"@types/yamljs": "^0.2.30",
|
"@types/yamljs": "^0.2.30",
|
||||||
"apidoc": "^0.17.6",
|
"apidoc": "^0.17.6",
|
||||||
"bootstrap": "^3.3.7",
|
"bootstrap": "^4.0.0",
|
||||||
"browserify": "^14.3.0",
|
"browserify": "^16.1.1",
|
||||||
"chromedriver": "^2.31.0",
|
"chromedriver": "^2.37.0",
|
||||||
"cucumber": "^2.3.1",
|
"cucumber": "^4.0.0",
|
||||||
"grunt": "^1.0.1",
|
"grunt": "^1.0.1",
|
||||||
"grunt-browserify": "^5.0.0",
|
"grunt-browserify": "^5.0.0",
|
||||||
"grunt-contrib-concat": "^1.0.1",
|
"grunt-contrib-concat": "^1.0.1",
|
||||||
|
@ -87,28 +91,28 @@
|
||||||
"grunt-contrib-cssmin": "^2.2.0",
|
"grunt-contrib-cssmin": "^2.2.0",
|
||||||
"grunt-contrib-watch": "^1.0.0",
|
"grunt-contrib-watch": "^1.0.0",
|
||||||
"grunt-env": "^0.4.4",
|
"grunt-env": "^0.4.4",
|
||||||
"grunt-run": "^0.6.0",
|
"grunt-run": "^0.8.0",
|
||||||
"istanbul": "^0.4.5",
|
"istanbul": "^0.4.5",
|
||||||
"jquery": "^3.2.1",
|
"jquery": "^3.2.1",
|
||||||
"js-logger": "^1.3.0",
|
"js-logger": "^1.3.0",
|
||||||
"jsdom": "^11.0.0",
|
"jsdom": "^11.0.0",
|
||||||
"mocha": "^3.4.2",
|
"mocha": "^5.0.5",
|
||||||
"mockdate": "^2.0.1",
|
"mockdate": "^2.0.1",
|
||||||
"nyc": "^10.3.2",
|
"nyc": "^11.6.0",
|
||||||
"power-assert": "^1.4.4",
|
"power-assert": "^1.4.4",
|
||||||
"proxyquire": "^1.8.0",
|
"proxyquire": "^2.0.1",
|
||||||
"query-string": "^4.3.4",
|
"query-string": "^6.0.0",
|
||||||
"readable-stream": "^2.3.3",
|
"readable-stream": "^2.3.3",
|
||||||
"request": "^2.83.0",
|
"request": "^2.83.0",
|
||||||
"request-promise": "^4.2.2",
|
"request-promise": "^4.2.2",
|
||||||
"selenium-webdriver": "^3.5.0",
|
"selenium-webdriver": "^4.0.0-alpha.1",
|
||||||
"should": "^11.1.1",
|
"should": "^13.2.1",
|
||||||
"sinon": "^4.0.2",
|
"sinon": "^4.0.2",
|
||||||
"tmp": "0.0.31",
|
"tmp": "0.0.33",
|
||||||
"ts-node": "^3.3.0",
|
"ts-node": "^5.0.1",
|
||||||
"tslint": "^5.2.0",
|
"tslint": "^5.2.0",
|
||||||
"typescript": "^2.3.2",
|
"typescript": "^2.3.2",
|
||||||
"typescript-json-schema": "^0.17.0",
|
"typescript-json-schema": "^0.21.0",
|
||||||
"uglify-es": "^3.0.15"
|
"uglify-es": "^3.0.15"
|
||||||
},
|
},
|
||||||
"nyc": {
|
"nyc": {
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
./node_modules/.bin/cucumber-js --colors --compiler ts:ts-node/register $*
|
REQ=`for f in test/features/step_definitions/*.ts; do echo "--require $f"; done;`
|
||||||
|
|
||||||
|
./node_modules/.bin/cucumber-js --format-options '{"colorsEnabled": true}' --require-module ts-node/register --require test/features/support/world.ts $REQ $*
|
||||||
|
|
|
@ -20,7 +20,8 @@ const deps: GlobalDependencies = {
|
||||||
winston: require("winston"),
|
winston: require("winston"),
|
||||||
speakeasy: require("speakeasy"),
|
speakeasy: require("speakeasy"),
|
||||||
nedb: require("nedb"),
|
nedb: require("nedb"),
|
||||||
ConnectRedis: require("connect-redis")
|
ConnectRedis: require("connect-redis"),
|
||||||
|
Redis: require("redis")
|
||||||
};
|
};
|
||||||
|
|
||||||
const server = new Server(deps);
|
const server = new Server(deps);
|
||||||
|
|
|
@ -52,7 +52,7 @@ class UserDataStoreFactory {
|
||||||
else if (config.storage.mongo) {
|
else if (config.storage.mongo) {
|
||||||
const mongoConnectorFactory = new MongoConnectorFactory();
|
const mongoConnectorFactory = new MongoConnectorFactory();
|
||||||
const mongoConnector = mongoConnectorFactory.create(config.storage.mongo.url);
|
const mongoConnector = mongoConnectorFactory.create(config.storage.mongo.url);
|
||||||
return mongoConnector.connect()
|
return mongoConnector.connect(config.storage.mongo.database)
|
||||||
.then(function (client: IMongoClient) {
|
.then(function (client: IMongoClient) {
|
||||||
const collectionFactory = CollectionFactoryFactory.createMongo(client);
|
const collectionFactory = CollectionFactoryFactory.createMongo(client);
|
||||||
return BluebirdPromise.resolve(new UserDataStore(collectionFactory));
|
return BluebirdPromise.resolve(new UserDataStore(collectionFactory));
|
||||||
|
|
|
@ -95,6 +95,7 @@ export interface NotifierConfiguration {
|
||||||
|
|
||||||
export interface MongoStorageConfiguration {
|
export interface MongoStorageConfiguration {
|
||||||
url: string;
|
url: string;
|
||||||
|
database: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LocalStorageConfiguration {
|
export interface LocalStorageConfiguration {
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
import ExpressSession = require("express-session");
|
import ExpressSession = require("express-session");
|
||||||
import { AppConfiguration } from "./Configuration";
|
import { AppConfiguration } from "./Configuration";
|
||||||
import { GlobalDependencies } from "../../../types/Dependencies";
|
import { GlobalDependencies } from "../../../types/Dependencies";
|
||||||
import Redis = require("redis");
|
|
||||||
|
|
||||||
export class SessionConfigurationBuilder {
|
export class SessionConfigurationBuilder {
|
||||||
|
|
||||||
|
@ -23,7 +22,7 @@ export class SessionConfigurationBuilder {
|
||||||
let redisOptions;
|
let redisOptions;
|
||||||
if (configuration.session.redis.host
|
if (configuration.session.redis.host
|
||||||
&& configuration.session.redis.port) {
|
&& configuration.session.redis.port) {
|
||||||
const client = Redis.createClient({
|
const client = deps.Redis.createClient({
|
||||||
host: configuration.session.redis.host,
|
host: configuration.session.redis.host,
|
||||||
port: configuration.session.redis.port
|
port: configuration.session.redis.port
|
||||||
});
|
});
|
||||||
|
|
|
@ -13,7 +13,7 @@ function validateSchema(configuration: UserConfiguration): string[] {
|
||||||
allErrors: true,
|
allErrors: true,
|
||||||
missingRefs: "fail"
|
missingRefs: "fail"
|
||||||
});
|
});
|
||||||
ajv.addMetaSchema(require("ajv/lib/refs/json-schema-draft-04.json"));
|
ajv.addMetaSchema(require("ajv/lib/refs/json-schema-draft-06.json"));
|
||||||
const valid = ajv.validate(schema, configuration);
|
const valid = ajv.validate(schema, configuration);
|
||||||
if (!valid)
|
if (!valid)
|
||||||
return ajv.errors.map(
|
return ajv.errors.map(
|
||||||
|
@ -21,14 +21,15 @@ function validateSchema(configuration: UserConfiguration): string[] {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function diff(a: string[], b: string[]) {
|
||||||
|
return a.filter(function(i) {return b.indexOf(i) < 0; });
|
||||||
|
}
|
||||||
|
|
||||||
function validateUnknownKeys(path: string, obj: any, knownKeys: string[]) {
|
function validateUnknownKeys(path: string, obj: any, knownKeys: string[]) {
|
||||||
const keysSet = new Set(Object.keys(obj));
|
const keysSet = Object.keys(obj);
|
||||||
const knownKeysSet = new Set(knownKeys);
|
|
||||||
|
|
||||||
const unknownKeysSet = new Set(
|
const unknownKeysSet = diff(keysSet, knownKeys);
|
||||||
[...keysSet].filter(x => !knownKeysSet.has(x)));
|
if (unknownKeysSet.length > 0) {
|
||||||
|
|
||||||
if (unknownKeysSet.size > 0) {
|
|
||||||
const unknownKeys = Array.from(unknownKeysSet);
|
const unknownKeys = Array.from(unknownKeysSet);
|
||||||
return unknownKeys.map((k: string) => { return Util.format("data.%s has unknown key '%s'", path, k); });
|
return unknownKeys.map((k: string) => { return Util.format("data.%s has unknown key '%s'", path, k); });
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,5 +2,6 @@ import BluebirdPromise = require("bluebird");
|
||||||
import { IMongoClient } from "./IMongoClient";
|
import { IMongoClient } from "./IMongoClient";
|
||||||
|
|
||||||
export interface IMongoConnector {
|
export interface IMongoConnector {
|
||||||
connect(): BluebirdPromise<IMongoClient>;
|
connect(databaseName: string): BluebirdPromise<IMongoClient>;
|
||||||
|
close(): BluebirdPromise<void>;
|
||||||
}
|
}
|
|
@ -7,16 +7,25 @@ import { MongoClient } from "./MongoClient";
|
||||||
|
|
||||||
export class MongoConnector implements IMongoConnector {
|
export class MongoConnector implements IMongoConnector {
|
||||||
private url: string;
|
private url: string;
|
||||||
|
private client: MongoDB.MongoClient;
|
||||||
|
|
||||||
constructor(url: string) {
|
constructor(url: string) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
connect(): BluebirdPromise<IMongoClient> {
|
connect(databaseName: string): BluebirdPromise<IMongoClient> {
|
||||||
|
const that = this;
|
||||||
const connectAsync = BluebirdPromise.promisify(MongoDB.MongoClient.connect);
|
const connectAsync = BluebirdPromise.promisify(MongoDB.MongoClient.connect);
|
||||||
return connectAsync(this.url)
|
return connectAsync(this.url)
|
||||||
.then(function (db: MongoDB.Db) {
|
.then(function (client: MongoDB.MongoClient) {
|
||||||
|
that.client = client;
|
||||||
|
const db = client.db(databaseName);
|
||||||
return BluebirdPromise.resolve(new MongoClient(db));
|
return BluebirdPromise.resolve(new MongoClient(db));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
close(): BluebirdPromise<void> {
|
||||||
|
this.client.close();
|
||||||
|
return BluebirdPromise.resolve();
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -75,6 +75,7 @@ describe("test server configuration", function () {
|
||||||
.then(function () {
|
.then(function () {
|
||||||
Assert(sessionMock.calledOnce);
|
Assert(sessionMock.calledOnce);
|
||||||
Assert.equal(sessionMock.getCall(0).args[0].cookie.domain, "example.com");
|
Assert.equal(sessionMock.getCall(0).args[0].cookie.domain, "example.com");
|
||||||
|
server.stop();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -141,6 +141,7 @@ describe("test session configuration builder", function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
const RedisStoreMock = Sinon.spy();
|
const RedisStoreMock = Sinon.spy();
|
||||||
|
const redisClient = Sinon.mock().returns({ on: Sinon.spy() });
|
||||||
|
|
||||||
const deps: GlobalDependencies = {
|
const deps: GlobalDependencies = {
|
||||||
ConnectRedis: Sinon.stub().returns(RedisStoreMock) as any,
|
ConnectRedis: Sinon.stub().returns(RedisStoreMock) as any,
|
||||||
|
@ -149,7 +150,10 @@ describe("test session configuration builder", function () {
|
||||||
session: Sinon.spy() as any,
|
session: Sinon.spy() as any,
|
||||||
speakeasy: Sinon.spy() as any,
|
speakeasy: Sinon.spy() as any,
|
||||||
u2f: Sinon.spy() as any,
|
u2f: Sinon.spy() as any,
|
||||||
winston: Sinon.spy() as any
|
winston: Sinon.spy() as any,
|
||||||
|
Redis: {
|
||||||
|
createClient: Sinon.mock().returns(redisClient)
|
||||||
|
} as any
|
||||||
};
|
};
|
||||||
|
|
||||||
const options = SessionConfigurationBuilder.build(configuration, deps);
|
const options = SessionConfigurationBuilder.build(configuration, deps);
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { MongoConnector } from "../../../src/lib/connectors/mongo/MongoConnector
|
||||||
|
|
||||||
describe("MongoConnector", function () {
|
describe("MongoConnector", function () {
|
||||||
let mongoClientConnectStub: Sinon.SinonStub;
|
let mongoClientConnectStub: Sinon.SinonStub;
|
||||||
|
|
||||||
describe("create", function () {
|
describe("create", function () {
|
||||||
before(function () {
|
before(function () {
|
||||||
mongoClientConnectStub = Sinon.stub(MongoDB.MongoClient, "connect");
|
mongoClientConnectStub = Sinon.stub(MongoDB.MongoClient, "connect");
|
||||||
|
@ -17,11 +18,12 @@ describe("MongoConnector", function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should create a connector", function () {
|
it("should create a connector", function () {
|
||||||
mongoClientConnectStub.yields(undefined);
|
const client = { db: Sinon.mock() };
|
||||||
|
mongoClientConnectStub.yields(undefined, client);
|
||||||
|
|
||||||
const url = "mongodb://test.url";
|
const url = "mongodb://test.url";
|
||||||
const connector = new MongoConnector(url);
|
const connector = new MongoConnector(url);
|
||||||
return connector.connect()
|
return connector.connect("database")
|
||||||
.then(function (client: IMongoClient) {
|
.then(function (client: IMongoClient) {
|
||||||
Assert(client);
|
Assert(client);
|
||||||
Assert(mongoClientConnectStub.calledWith(url));
|
Assert(mongoClientConnectStub.calledWith(url));
|
||||||
|
@ -33,7 +35,7 @@ describe("MongoConnector", function () {
|
||||||
|
|
||||||
const url = "mongodb://test.url";
|
const url = "mongodb://test.url";
|
||||||
const connector = new MongoConnector(url);
|
const connector = new MongoConnector(url);
|
||||||
return connector.connect()
|
return connector.connect("database")
|
||||||
.then(function () { return BluebirdPromise.reject(new Error("It should not be here")); })
|
.then(function () { return BluebirdPromise.reject(new Error("It should not be here")); })
|
||||||
.error(function (client: IMongoClient) {
|
.error(function (client: IMongoClient) {
|
||||||
Assert(client);
|
Assert(client);
|
||||||
|
|
|
@ -6,6 +6,7 @@ import nedb = require("nedb");
|
||||||
import ldapjs = require("ldapjs");
|
import ldapjs = require("ldapjs");
|
||||||
import u2f = require("u2f");
|
import u2f = require("u2f");
|
||||||
import RedisSession = require("connect-redis");
|
import RedisSession = require("connect-redis");
|
||||||
|
import Redis = require("redis");
|
||||||
|
|
||||||
export type Speakeasy = typeof speakeasy;
|
export type Speakeasy = typeof speakeasy;
|
||||||
export type Winston = typeof winston;
|
export type Winston = typeof winston;
|
||||||
|
@ -14,11 +15,13 @@ export type Nedb = typeof nedb;
|
||||||
export type Ldapjs = typeof ldapjs;
|
export type Ldapjs = typeof ldapjs;
|
||||||
export type U2f = typeof u2f;
|
export type U2f = typeof u2f;
|
||||||
export type ConnectRedis = typeof RedisSession;
|
export type ConnectRedis = typeof RedisSession;
|
||||||
|
export type Redis = typeof Redis;
|
||||||
|
|
||||||
export interface GlobalDependencies {
|
export interface GlobalDependencies {
|
||||||
u2f: U2f;
|
u2f: U2f;
|
||||||
ldapjs: Ldapjs;
|
ldapjs: Ldapjs;
|
||||||
session: Session;
|
session: Session;
|
||||||
|
Redis: Redis;
|
||||||
ConnectRedis: ConnectRedis;
|
ConnectRedis: ConnectRedis;
|
||||||
winston: Winston;
|
winston: Winston;
|
||||||
speakeasy: Speakeasy;
|
speakeasy: Speakeasy;
|
||||||
|
|
|
@ -7,20 +7,16 @@ Feature: User has access restricted access to domains
|
||||||
And I use "REGISTERED" as TOTP token handle
|
And I use "REGISTERED" as TOTP token handle
|
||||||
And I click on "Sign in"
|
And I click on "Sign in"
|
||||||
And I'm redirected to "https://home.example.com:8080/"
|
And I'm redirected to "https://home.example.com:8080/"
|
||||||
Then I have access to:
|
Then I have access to "https://public.example.com:8080/secret.html"
|
||||||
| url |
|
And I have access to "https://dev.example.com:8080/groups/admin/secret.html"
|
||||||
| https://public.example.com:8080/secret.html |
|
And I have access to "https://dev.example.com:8080/groups/dev/secret.html"
|
||||||
| https://dev.example.com:8080/groups/admin/secret.html |
|
And I have access to "https://dev.example.com:8080/users/john/secret.html"
|
||||||
| https://dev.example.com:8080/groups/dev/secret.html |
|
And I have access to "https://dev.example.com:8080/users/harry/secret.html"
|
||||||
| https://dev.example.com:8080/users/john/secret.html |
|
And I have access to "https://dev.example.com:8080/users/bob/secret.html"
|
||||||
| https://dev.example.com:8080/users/harry/secret.html |
|
And I have access to "https://admin.example.com:8080/secret.html"
|
||||||
| https://dev.example.com:8080/users/bob/secret.html |
|
And I have access to "https://mx1.mail.example.com:8080/secret.html"
|
||||||
| https://admin.example.com:8080/secret.html |
|
And I have access to "https://single_factor.example.com:8080/secret.html"
|
||||||
| https://mx1.mail.example.com:8080/secret.html |
|
And I have no access to "https://mx2.mail.example.com:8080/secret.html"
|
||||||
| https://single_factor.example.com:8080/secret.html |
|
|
||||||
And I have no access to:
|
|
||||||
| url |
|
|
||||||
| https://mx2.mail.example.com:8080/secret.html |
|
|
||||||
|
|
||||||
@need-registered-user-bob
|
@need-registered-user-bob
|
||||||
Scenario: User bob has restricted access
|
Scenario: User bob has restricted access
|
||||||
|
@ -29,20 +25,16 @@ Feature: User has access restricted access to domains
|
||||||
And I use "REGISTERED" as TOTP token handle
|
And I use "REGISTERED" as TOTP token handle
|
||||||
And I click on "Sign in"
|
And I click on "Sign in"
|
||||||
And I'm redirected to "https://home.example.com:8080/"
|
And I'm redirected to "https://home.example.com:8080/"
|
||||||
Then I have access to:
|
Then I have access to "https://public.example.com:8080/secret.html"
|
||||||
| url |
|
And I have no access to "https://dev.example.com:8080/groups/admin/secret.html"
|
||||||
| https://public.example.com:8080/secret.html |
|
And I have access to "https://dev.example.com:8080/groups/dev/secret.html"
|
||||||
| https://dev.example.com:8080/groups/dev/secret.html |
|
And I have no access to "https://dev.example.com:8080/users/john/secret.html"
|
||||||
| https://dev.example.com:8080/users/bob/secret.html |
|
And I have no access to "https://dev.example.com:8080/users/harry/secret.html"
|
||||||
| https://mx1.mail.example.com:8080/secret.html |
|
And I have access to "https://dev.example.com:8080/users/bob/secret.html"
|
||||||
| https://mx2.mail.example.com:8080/secret.html |
|
And I have no access to "https://admin.example.com:8080/secret.html"
|
||||||
And I have no access to:
|
And I have access to "https://mx1.mail.example.com:8080/secret.html"
|
||||||
| url |
|
And I have no access to "https://single_factor.example.com:8080/secret.html"
|
||||||
| https://dev.example.com:8080/groups/admin/secret.html |
|
And I have access to "https://mx2.mail.example.com:8080/secret.html"
|
||||||
| https://admin.example.com:8080/secret.html |
|
|
||||||
| https://dev.example.com:8080/users/john/secret.html |
|
|
||||||
| https://dev.example.com:8080/users/harry/secret.html |
|
|
||||||
| https://single_factor.example.com:8080/secret.html |
|
|
||||||
|
|
||||||
@need-registered-user-harry
|
@need-registered-user-harry
|
||||||
Scenario: User harry has restricted access
|
Scenario: User harry has restricted access
|
||||||
|
@ -51,17 +43,13 @@ Feature: User has access restricted access to domains
|
||||||
And I use "REGISTERED" as TOTP token handle
|
And I use "REGISTERED" as TOTP token handle
|
||||||
And I click on "Sign in"
|
And I click on "Sign in"
|
||||||
And I'm redirected to "https://home.example.com:8080/"
|
And I'm redirected to "https://home.example.com:8080/"
|
||||||
Then I have access to:
|
Then I have access to "https://public.example.com:8080/secret.html"
|
||||||
| url |
|
And I have no access to "https://dev.example.com:8080/groups/admin/secret.html"
|
||||||
| https://public.example.com:8080/secret.html |
|
And I have no access to "https://dev.example.com:8080/groups/dev/secret.html"
|
||||||
| https://dev.example.com:8080/users/harry/secret.html |
|
And I have no access to "https://dev.example.com:8080/users/john/secret.html"
|
||||||
And I have no access to:
|
And I have access to "https://dev.example.com:8080/users/harry/secret.html"
|
||||||
| url |
|
And I have no access to "https://dev.example.com:8080/users/bob/secret.html"
|
||||||
| https://dev.example.com:8080/groups/dev/secret.html |
|
And I have no access to "https://admin.example.com:8080/secret.html"
|
||||||
| https://dev.example.com:8080/users/bob/secret.html |
|
And I have no access to "https://mx1.mail.example.com:8080/secret.html"
|
||||||
| https://dev.example.com:8080/groups/admin/secret.html |
|
And I have no access to "https://single_factor.example.com:8080/secret.html"
|
||||||
| https://admin.example.com:8080/secret.html |
|
And I have no access to "https://mx2.mail.example.com:8080/secret.html"
|
||||||
| https://dev.example.com:8080/users/john/secret.html |
|
|
||||||
| https://mx1.mail.example.com:8080/secret.html |
|
|
||||||
| https://mx2.mail.example.com:8080/secret.html |
|
|
||||||
| https://single_factor.example.com:8080/secret.html |
|
|
||||||
|
|
|
@ -1,24 +1,24 @@
|
||||||
Feature: User is able to reset his password
|
Feature: User is able to reset his password
|
||||||
|
|
||||||
Scenario: User is redirected to password reset page
|
Scenario: User is redirected to password reset page
|
||||||
Given I'm on https://login.example.com:8080
|
Given I'm on "https://login.example.com:8080"
|
||||||
When I click on the link "Forgot password?"
|
When I click on the link "Forgot password?"
|
||||||
Then I'm redirected to "https://login.example.com:8080/password-reset/request"
|
Then I'm redirected to "https://login.example.com:8080/password-reset/request"
|
||||||
|
|
||||||
Scenario: User get an email with a link to reset password
|
Scenario: User get an email with a link to reset password
|
||||||
Given I'm on https://login.example.com:8080/password-reset/request
|
Given I'm on "https://login.example.com:8080/password-reset/request"
|
||||||
When I set field "username" to "james"
|
When I set field "username" to "james"
|
||||||
And I click on "Reset Password"
|
And I click on "Reset Password"
|
||||||
Then I get a notification of type "success" with message "An email has been sent to you. Follow the link to change your password."
|
Then I get a notification of type "success" with message "An email has been sent to you. Follow the link to change your password."
|
||||||
|
|
||||||
Scenario: Request password for unexisting user should behave like existing user
|
Scenario: Request password for unexisting user should behave like existing user
|
||||||
Given I'm on https://login.example.com:8080/password-reset/request
|
Given I'm on "https://login.example.com:8080/password-reset/request"
|
||||||
When I set field "username" to "fake_user"
|
When I set field "username" to "fake_user"
|
||||||
And I click on "Reset Password"
|
And I click on "Reset Password"
|
||||||
Then I get a notification of type "success" with message "An email has been sent to you. Follow the link to change your password."
|
Then I get a notification of type "success" with message "An email has been sent to you. Follow the link to change your password."
|
||||||
|
|
||||||
Scenario: User resets his password
|
Scenario: User resets his password
|
||||||
Given I'm on https://login.example.com:8080/password-reset/request
|
Given I'm on "https://login.example.com:8080/password-reset/request"
|
||||||
And I set field "username" to "james"
|
And I set field "username" to "james"
|
||||||
And I click on "Reset Password"
|
And I click on "Reset Password"
|
||||||
When I click on the link of the email
|
When I click on the link of the email
|
||||||
|
@ -29,7 +29,7 @@ Feature: User is able to reset his password
|
||||||
|
|
||||||
|
|
||||||
Scenario: User does not confirm new password
|
Scenario: User does not confirm new password
|
||||||
Given I'm on https://login.example.com:8080/password-reset/request
|
Given I'm on "https://login.example.com:8080/password-reset/request"
|
||||||
And I set field "username" to "james"
|
And I set field "username" to "james"
|
||||||
And I click on "Reset Password"
|
And I click on "Reset Password"
|
||||||
When I click on the link of the email
|
When I click on the link of the email
|
||||||
|
|
|
@ -3,9 +3,7 @@ Feature: Authelia keeps user sessions despite the application restart
|
||||||
@need-authenticated-user-john
|
@need-authenticated-user-john
|
||||||
Scenario: Session is still valid after Authelia restarts
|
Scenario: Session is still valid after Authelia restarts
|
||||||
When the application restarts
|
When the application restarts
|
||||||
Then I have access to:
|
Then I have access to "https://admin.example.com:8080/secret.html"
|
||||||
| url |
|
|
||||||
| https://admin.example.com:8080/secret.html |
|
|
||||||
|
|
||||||
@need-registered-user-john
|
@need-registered-user-john
|
||||||
Scenario: Secrets are stored even when Authelia restarts
|
Scenario: Secrets are stored even when Authelia restarts
|
||||||
|
|
|
@ -3,18 +3,14 @@ Feature: Session is closed after a certain amount of time
|
||||||
|
|
||||||
@need-authenticated-user-john
|
@need-authenticated-user-john
|
||||||
Scenario: An authenticated user is disconnected after a certain inactivity period
|
Scenario: An authenticated user is disconnected after a certain inactivity period
|
||||||
Given I have access to:
|
Given I have access to "https://public.example.com:8080/secret.html"
|
||||||
| url |
|
|
||||||
| https://public.example.com:8080/secret.html |
|
|
||||||
When I sleep for 6 seconds
|
When I sleep for 6 seconds
|
||||||
And I visit "https://public.example.com:8080/secret.html"
|
And I visit "https://public.example.com:8080/secret.html"
|
||||||
Then I'm redirected to "https://login.example.com:8080/?redirect=https%3A%2F%2Fpublic.example.com%3A8080%2Fsecret.html"
|
Then I'm redirected to "https://login.example.com:8080/?redirect=https%3A%2F%2Fpublic.example.com%3A8080%2Fsecret.html"
|
||||||
|
|
||||||
@need-authenticated-user-john
|
@need-authenticated-user-john
|
||||||
Scenario: An authenticated user is disconnected after session expiration period
|
Scenario: An authenticated user is disconnected after session expiration period
|
||||||
Given I have access to:
|
Given I have access to "https://public.example.com:8080/secret.html"
|
||||||
| url |
|
|
||||||
| https://public.example.com:8080/secret.html |
|
|
||||||
When I sleep for 4 seconds
|
When I sleep for 4 seconds
|
||||||
And I visit "https://public.example.com:8080/secret.html"
|
And I visit "https://public.example.com:8080/secret.html"
|
||||||
And I sleep for 4 seconds
|
And I sleep for 4 seconds
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
import {Then} from "cucumber";
|
||||||
|
|
||||||
|
Then("I have access to {string}", function(url: string) {
|
||||||
|
const that = this;
|
||||||
|
return this.driver.get(url)
|
||||||
|
.then(function () {
|
||||||
|
return that.waitUntilUrlContains(url);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Then("I have no access to {string}", function(url: string) {
|
||||||
|
const that = this;
|
||||||
|
return this.driver.get(url)
|
||||||
|
.then(function () {
|
||||||
|
return that.getErrorPage(403);
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,51 +1,45 @@
|
||||||
import Cucumber = require("cucumber");
|
import {Before, When, Then} from "cucumber";
|
||||||
import seleniumWebdriver = require("selenium-webdriver");
|
import seleniumWebdriver = require("selenium-webdriver");
|
||||||
import Assert = require("assert");
|
import Assert = require("assert");
|
||||||
import Request = require("request-promise");
|
import Request = require("request-promise");
|
||||||
import Bluebird = require("bluebird");
|
import Bluebird = require("bluebird");
|
||||||
|
|
||||||
Cucumber.defineSupportCode(function ({ Given, When, Then, Before, After }) {
|
When("I query {string}", function (url: string) {
|
||||||
Before(function () {
|
const that = this;
|
||||||
this.jar = Request.jar();
|
return Request(url, { followRedirect: false })
|
||||||
})
|
.then(function(response) {
|
||||||
|
console.log(response);
|
||||||
|
that.response = response;
|
||||||
|
})
|
||||||
|
.catch(function(err: Error) {
|
||||||
|
that.error = err;
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
When("I query {stringInDoubleQuotes}", function (url: string) {
|
Then("I get error code 401", function() {
|
||||||
const that = this;
|
const that = this;
|
||||||
return Request(url, { followRedirect: false })
|
return new Bluebird(function(resolve, reject) {
|
||||||
.then(function(response) {
|
if(that.error && that.error.statusCode == 401) {
|
||||||
console.log(response);
|
resolve();
|
||||||
that.response = response;
|
}
|
||||||
})
|
else {
|
||||||
.catch(function(err: Error) {
|
if(that.response)
|
||||||
that.error = err;
|
reject(new Error("No error thrown"));
|
||||||
})
|
else if(that.error.statusCode != 401)
|
||||||
|
reject(new Error("Error code != 401"));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
Then("I get error code 401", function() {
|
Then("I get redirected to {string}", function(url: string) {
|
||||||
const that = this;
|
const that = this;
|
||||||
return new Bluebird(function(resolve, reject) {
|
return new Bluebird(function(resolve, reject) {
|
||||||
if(that.error && that.error.statusCode == 401) {
|
if(that.error && that.error.statusCode == 302
|
||||||
resolve();
|
&& that.error.message.indexOf(url) > -1) {
|
||||||
}
|
resolve();
|
||||||
else {
|
}
|
||||||
if(that.response)
|
else {
|
||||||
reject(new Error("No error thrown"));
|
reject(new Error("Not redirected"));
|
||||||
else if(that.error.statusCode != 401)
|
}
|
||||||
reject(new Error("Error code != 401"));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Then("I get redirected to {stringInDoubleQuotes}", function(url: string) {
|
|
||||||
const that = this;
|
|
||||||
return new Bluebird(function(resolve, reject) {
|
|
||||||
if(that.error && that.error.statusCode == 302
|
|
||||||
&& that.error.message.indexOf(url) > -1) {
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
reject(new Error("Not redirected"));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
});
|
});
|
|
@ -1,4 +1,4 @@
|
||||||
import Cucumber = require("cucumber");
|
import {Given, When, Then, TableDefinition} from "cucumber";
|
||||||
import seleniumWebdriver = require("selenium-webdriver");
|
import seleniumWebdriver = require("selenium-webdriver");
|
||||||
import Assert = require("assert");
|
import Assert = require("assert");
|
||||||
import Fs = require("fs");
|
import Fs = require("fs");
|
||||||
|
@ -7,127 +7,93 @@ import CustomWorld = require("../support/world");
|
||||||
import BluebirdPromise = require("bluebird");
|
import BluebirdPromise = require("bluebird");
|
||||||
import Request = require("request-promise");
|
import Request = require("request-promise");
|
||||||
|
|
||||||
Cucumber.defineSupportCode(function ({ Given, When, Then }) {
|
When(/^I visit "(https:\/\/[a-zA-Z0-9:%&._\/=?-]+)"$/, function (link: string) {
|
||||||
When(/^I visit "(https:\/\/[a-zA-Z0-9:%&._\/=?-]+)"$/, function (link: string) {
|
return this.visit(link);
|
||||||
return this.visit(link);
|
});
|
||||||
});
|
|
||||||
|
|
||||||
When("I wait for notification to disappear", function () {
|
When("I wait for notification to disappear", function () {
|
||||||
const that = this;
|
const that = this;
|
||||||
const notificationEl = this.driver.findElement(seleniumWebdriver.By.className("notification"));
|
const notificationEl = this.driver.findElement(seleniumWebdriver.By.className("notification"));
|
||||||
return this.driver.wait(seleniumWebdriver.until.elementIsVisible(notificationEl), 15000)
|
return this.driver.wait(seleniumWebdriver.until.elementIsVisible(notificationEl), 15000)
|
||||||
.then(function () {
|
.then(function () {
|
||||||
return that.driver.wait(seleniumWebdriver.until.elementIsNotVisible(notificationEl), 15000);
|
return that.driver.wait(seleniumWebdriver.until.elementIsNotVisible(notificationEl), 15000);
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
When("I set field {stringInDoubleQuotes} to {stringInDoubleQuotes}", function (fieldName: string, content: string) {
|
|
||||||
return this.setFieldTo(fieldName, content);
|
|
||||||
});
|
|
||||||
|
|
||||||
When("I clear field {stringInDoubleQuotes}", function (fieldName: string) {
|
|
||||||
return this.clearField(fieldName);
|
|
||||||
});
|
|
||||||
|
|
||||||
When("I click on {stringInDoubleQuotes}", function (text: string) {
|
|
||||||
return this.clickOnButton(text);
|
|
||||||
});
|
|
||||||
|
|
||||||
Given("I login with user {stringInDoubleQuotes} and password {stringInDoubleQuotes}",
|
|
||||||
function (username: string, password: string) {
|
|
||||||
return this.loginWithUserPassword(username, password);
|
|
||||||
});
|
|
||||||
|
|
||||||
Given("I login with user {stringInDoubleQuotes} and password {stringInDoubleQuotes} \
|
|
||||||
and I use TOTP token handle {stringInDoubleQuotes}",
|
|
||||||
function (username: string, password: string, totpTokenHandle: string) {
|
|
||||||
const that = this;
|
|
||||||
return this.loginWithUserPassword(username, password)
|
|
||||||
.then(function () {
|
|
||||||
return that.useTotpTokenHandle(totpTokenHandle);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
Given("I register a TOTP secret called {stringInDoubleQuotes}", function (handle: string) {
|
|
||||||
return this.registerTotpSecret(handle);
|
|
||||||
});
|
|
||||||
|
|
||||||
Given("I use {stringInDoubleQuotes} as TOTP token", function (token: string) {
|
|
||||||
return this.useTotpToken(token);
|
|
||||||
});
|
|
||||||
|
|
||||||
Given("I use {stringInDoubleQuotes} as TOTP token handle", function (handle) {
|
|
||||||
return this.useTotpTokenHandle(handle);
|
|
||||||
});
|
|
||||||
|
|
||||||
When("I visit {stringInDoubleQuotes} and get redirected {stringInDoubleQuotes}",
|
|
||||||
function (url: string, redirectUrl: string) {
|
|
||||||
const that = this;
|
|
||||||
return this.driver.get(url)
|
|
||||||
.then(function () {
|
|
||||||
return that.driver.wait(seleniumWebdriver.until.urlIs(redirectUrl), 2000);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
Given("I register TOTP and login with user {stringInDoubleQuotes} and password {stringInDoubleQuotes}",
|
|
||||||
function (username: string, password: string) {
|
|
||||||
return this.registerTotpAndSignin(username, password);
|
|
||||||
});
|
|
||||||
|
|
||||||
function hasAccessToSecret(link: string, that: any) {
|
|
||||||
return that.driver.get(link)
|
|
||||||
.then(function () {
|
|
||||||
return that.waitUntilUrlContains(link);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasNoAccessToSecret(link: string, that: any) {
|
|
||||||
return that.driver.get(link)
|
|
||||||
.then(function () {
|
|
||||||
return that.getErrorPage(403);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Then("I have access to:", function (dataTable: Cucumber.TableDefinition) {
|
|
||||||
const promises: any = [];
|
|
||||||
for (let i = 0; i < dataTable.rows().length; i++) {
|
|
||||||
const url = (dataTable.hashes() as any)[i].url;
|
|
||||||
promises.push(hasAccessToSecret(url, this));
|
|
||||||
}
|
|
||||||
return BluebirdPromise.all(promises);
|
|
||||||
});
|
|
||||||
|
|
||||||
Then("I have no access to:", function (dataTable: Cucumber.TableDefinition) {
|
|
||||||
const promises = [];
|
|
||||||
for (let i = 0; i < dataTable.rows().length; i++) {
|
|
||||||
const url = (dataTable.hashes() as any)[i].url;
|
|
||||||
promises.push(hasNoAccessToSecret(url, this));
|
|
||||||
}
|
|
||||||
return BluebirdPromise.all(promises);
|
|
||||||
});
|
|
||||||
|
|
||||||
function endpointReplyWith(context: any, link: string, method: string,
|
|
||||||
returnCode: number) {
|
|
||||||
return Request(link, {
|
|
||||||
method: method
|
|
||||||
})
|
})
|
||||||
.then(function (response: string) {
|
})
|
||||||
Assert(response.indexOf("Error " + returnCode) >= 0);
|
|
||||||
return BluebirdPromise.resolve();
|
|
||||||
}, function (response: any) {
|
|
||||||
Assert.equal(response.statusCode, returnCode);
|
|
||||||
return BluebirdPromise.resolve();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Then("the following endpoints reply with:", function (dataTable: Cucumber.TableDefinition) {
|
When("I set field {string} to {string}", function (fieldName: string, content: string) {
|
||||||
const promises = [];
|
return this.setFieldTo(fieldName, content);
|
||||||
for (let i = 0; i < dataTable.rows().length; i++) {
|
});
|
||||||
const url: string = (dataTable.hashes() as any)[i].url;
|
|
||||||
const method: string = (dataTable.hashes() as any)[i].method;
|
When("I clear field {string}", function (fieldName: string) {
|
||||||
const code: number = (dataTable.hashes() as any)[i].code;
|
return this.clearField(fieldName);
|
||||||
promises.push(endpointReplyWith(this, url, method, code));
|
});
|
||||||
}
|
|
||||||
return BluebirdPromise.all(promises);
|
When("I click on {string}", function (text: string) {
|
||||||
|
return this.clickOnButton(text);
|
||||||
|
});
|
||||||
|
|
||||||
|
Given("I login with user {string} and password {string}",
|
||||||
|
function (username: string, password: string) {
|
||||||
|
return this.loginWithUserPassword(username, password);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Given("I login with user {string} and password {string} \
|
||||||
|
and I use TOTP token handle {string}",
|
||||||
|
function (username: string, password: string, totpTokenHandle: string) {
|
||||||
|
const that = this;
|
||||||
|
return this.loginWithUserPassword(username, password)
|
||||||
|
.then(function () {
|
||||||
|
return that.useTotpTokenHandle(totpTokenHandle);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Given("I register a TOTP secret called {string}", function (handle: string) {
|
||||||
|
return this.registerTotpSecret(handle);
|
||||||
|
});
|
||||||
|
|
||||||
|
Given("I use {string} as TOTP token", function (token: string) {
|
||||||
|
return this.useTotpToken(token);
|
||||||
|
});
|
||||||
|
|
||||||
|
Given("I use {string} as TOTP token handle", function (handle) {
|
||||||
|
return this.useTotpTokenHandle(handle);
|
||||||
|
});
|
||||||
|
|
||||||
|
When("I visit {string} and get redirected {string}",
|
||||||
|
function (url: string, redirectUrl: string) {
|
||||||
|
const that = this;
|
||||||
|
return this.driver.get(url)
|
||||||
|
.then(function () {
|
||||||
|
return that.driver.wait(seleniumWebdriver.until.urlIs(redirectUrl), 2000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Given("I register TOTP and login with user {string} and password {string}",
|
||||||
|
function (username: string, password: string) {
|
||||||
|
return this.registerTotpAndSignin(username, password);
|
||||||
|
});
|
||||||
|
|
||||||
|
function endpointReplyWith(context: any, link: string, method: string,
|
||||||
|
returnCode: number) {
|
||||||
|
return Request(link, {
|
||||||
|
method: method
|
||||||
|
})
|
||||||
|
.then(function (response: string) {
|
||||||
|
Assert(response.indexOf("Error " + returnCode) >= 0);
|
||||||
|
return BluebirdPromise.resolve();
|
||||||
|
}, function (response: any) {
|
||||||
|
Assert.equal(response.statusCode, returnCode);
|
||||||
|
return BluebirdPromise.resolve();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Then("the following endpoints reply with:", function (dataTable: TableDefinition) {
|
||||||
|
const promises = [];
|
||||||
|
for (let i = 0; i < dataTable.rows().length; i++) {
|
||||||
|
const url: string = (dataTable.hashes() as any)[i].url;
|
||||||
|
const method: string = (dataTable.hashes() as any)[i].method;
|
||||||
|
const code: number = (dataTable.hashes() as any)[i].code;
|
||||||
|
promises.push(endpointReplyWith(this, url, method, code));
|
||||||
|
}
|
||||||
|
return BluebirdPromise.all(promises);
|
||||||
});
|
});
|
|
@ -1,20 +1,18 @@
|
||||||
import Cucumber = require("cucumber");
|
import {Then} from "cucumber";
|
||||||
import seleniumWebdriver = require("selenium-webdriver");
|
import seleniumWebdriver = require("selenium-webdriver");
|
||||||
import CustomWorld = require("../support/world");
|
import CustomWorld = require("../support/world");
|
||||||
import Util = require("util");
|
import Util = require("util");
|
||||||
import BluebirdPromise = require("bluebird");
|
import BluebirdPromise = require("bluebird");
|
||||||
|
|
||||||
Cucumber.defineSupportCode(function ({ Given, When, Then }) {
|
Then("I see header {string} set to {string}",
|
||||||
Then("I see header {stringInDoubleQuotes} set to {stringInDoubleQuotes}",
|
{ timeout: 5000 },
|
||||||
{ timeout: 5000 },
|
function (expectedHeaderName: string, expectedValue: string) {
|
||||||
function (expectedHeaderName: string, expectedValue: string) {
|
return this.driver.findElement(seleniumWebdriver.By.tagName("body")).getText()
|
||||||
return this.driver.findElement(seleniumWebdriver.By.tagName("body")).getText()
|
.then(function (txt: string) {
|
||||||
.then(function (txt: string) {
|
const expectedLine = Util.format("\"%s\": \"%s\"", expectedHeaderName, expectedValue);
|
||||||
const expectedLine = Util.format("\"%s\": \"%s\"", expectedHeaderName, expectedValue);
|
if (txt.indexOf(expectedLine) > 0)
|
||||||
if (txt.indexOf(expectedLine) > 0)
|
return BluebirdPromise.resolve();
|
||||||
return BluebirdPromise.resolve();
|
else
|
||||||
else
|
return BluebirdPromise.reject(new Error(Util.format("No such header or with unexpected value.")));
|
||||||
return BluebirdPromise.reject(new Error(Util.format("No such header or with unexpected value.")));
|
});
|
||||||
});
|
})
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import Cucumber = require("cucumber");
|
import {setDefaultTimeout, After, Before} from "cucumber";
|
||||||
import fs = require("fs");
|
import fs = require("fs");
|
||||||
import BluebirdPromise = require("bluebird");
|
import BluebirdPromise = require("bluebird");
|
||||||
import ChildProcess = require("child_process");
|
import ChildProcess = require("child_process");
|
||||||
|
@ -8,133 +8,137 @@ import { MongoConnector } from "../../../server/src/lib/connectors/mongo/MongoCo
|
||||||
import { IMongoClient } from "../../../server/src/lib/connectors/mongo/IMongoClient";
|
import { IMongoClient } from "../../../server/src/lib/connectors/mongo/IMongoClient";
|
||||||
import { TotpHandler } from "../../../server/src/lib/authentication/totp/TotpHandler";
|
import { TotpHandler } from "../../../server/src/lib/authentication/totp/TotpHandler";
|
||||||
import Speakeasy = require("speakeasy");
|
import Speakeasy = require("speakeasy");
|
||||||
|
import Request = require("request-promise");
|
||||||
|
|
||||||
Cucumber.defineSupportCode(function ({ setDefaultTimeout }) {
|
setDefaultTimeout(20 * 1000);
|
||||||
setDefaultTimeout(20 * 1000);
|
|
||||||
|
const exec = BluebirdPromise.promisify<any, any>(ChildProcess.exec);
|
||||||
|
|
||||||
|
Before(function () {
|
||||||
|
this.jar = Request.jar();
|
||||||
|
})
|
||||||
|
|
||||||
|
After(function () {
|
||||||
|
return this.driver.quit();
|
||||||
});
|
});
|
||||||
|
|
||||||
Cucumber.defineSupportCode(function ({ After, Before }) {
|
function createRegulationConfiguration(): BluebirdPromise<void> {
|
||||||
const exec = BluebirdPromise.promisify<any, any>(ChildProcess.exec);
|
return exec("\
|
||||||
|
cat config.template.yml | \
|
||||||
|
sed 's/find_time: [0-9]\\+/find_time: 15/' | \
|
||||||
|
sed 's/ban_time: [0-9]\\+/ban_time: 4/' > config.test.yml \
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
After(function () {
|
function createInactivityConfiguration(): BluebirdPromise<void> {
|
||||||
return this.driver.quit();
|
return exec("\
|
||||||
|
cat config.template.yml | \
|
||||||
|
sed 's/expiration: [0-9]\\+/expiration: 10000/' | \
|
||||||
|
sed 's/inactivity: [0-9]\\+/inactivity: 5000/' > config.test.yml \
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSingleFactorConfiguration(): BluebirdPromise<void> {
|
||||||
|
return exec("\
|
||||||
|
cat config.template.yml | \
|
||||||
|
sed 's/default_method: two_factor/default_method: single_factor/' > config.test.yml \
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
|
function createCustomTotpIssuerConfiguration(): BluebirdPromise<void> {
|
||||||
|
return exec("\
|
||||||
|
cat config.template.yml > config.test.yml && \
|
||||||
|
echo 'totp:' >> config.test.yml && \
|
||||||
|
echo ' issuer: custom.com' >> config.test.yml \
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
|
function declareNeedsConfiguration(tag: string, cb: () => BluebirdPromise<void>) {
|
||||||
|
Before({ tags: "@needs-" + tag + "-config", timeout: 20 * 1000 }, function () {
|
||||||
|
return cb()
|
||||||
|
.then(function () {
|
||||||
|
return exec("./scripts/example-commit/dc-example.sh -f " +
|
||||||
|
"./example/authelia/docker-compose.test.yml up -d authelia &&" +
|
||||||
|
" sleep 1");
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
function createRegulationConfiguration(): BluebirdPromise<void> {
|
After({ tags: "@needs-" + tag + "-config", timeout: 20 * 1000 }, function () {
|
||||||
return exec("\
|
return exec("rm config.test.yml")
|
||||||
cat config.template.yml | \
|
|
||||||
sed 's/find_time: [0-9]\\+/find_time: 15/' | \
|
|
||||||
sed 's/ban_time: [0-9]\\+/ban_time: 4/' > config.test.yml \
|
|
||||||
");
|
|
||||||
}
|
|
||||||
|
|
||||||
function createInactivityConfiguration(): BluebirdPromise<void> {
|
|
||||||
return exec("\
|
|
||||||
cat config.template.yml | \
|
|
||||||
sed 's/expiration: [0-9]\\+/expiration: 10000/' | \
|
|
||||||
sed 's/inactivity: [0-9]\\+/inactivity: 5000/' > config.test.yml \
|
|
||||||
");
|
|
||||||
}
|
|
||||||
|
|
||||||
function createSingleFactorConfiguration(): BluebirdPromise<void> {
|
|
||||||
return exec("\
|
|
||||||
cat config.template.yml | \
|
|
||||||
sed 's/default_method: two_factor/default_method: single_factor/' > config.test.yml \
|
|
||||||
");
|
|
||||||
}
|
|
||||||
|
|
||||||
function createCustomTotpIssuerConfiguration(): BluebirdPromise<void> {
|
|
||||||
return exec("\
|
|
||||||
cat config.template.yml > config.test.yml && \
|
|
||||||
echo 'totp:' >> config.test.yml && \
|
|
||||||
echo ' issuer: custom.com' >> config.test.yml \
|
|
||||||
");
|
|
||||||
}
|
|
||||||
|
|
||||||
function declareNeedsConfiguration(tag: string, cb: () => BluebirdPromise<void>) {
|
|
||||||
Before({ tags: "@needs-" + tag + "-config", timeout: 20 * 1000 }, function () {
|
|
||||||
return cb()
|
|
||||||
.then(function () {
|
|
||||||
return exec("./scripts/example-commit/dc-example.sh -f " +
|
|
||||||
"./example/authelia/docker-compose.test.yml up -d authelia &&" +
|
|
||||||
" sleep 1");
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
After({ tags: "@needs-" + tag + "-config", timeout: 20 * 1000 }, function () {
|
|
||||||
return exec("rm config.test.yml")
|
|
||||||
.then(function () {
|
|
||||||
return exec("./scripts/example-commit/dc-example.sh up -d authelia && sleep 1");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
declareNeedsConfiguration("regulation", createRegulationConfiguration);
|
|
||||||
declareNeedsConfiguration("inactivity", createInactivityConfiguration);
|
|
||||||
declareNeedsConfiguration("single_factor", createSingleFactorConfiguration);
|
|
||||||
declareNeedsConfiguration("totp_issuer", createCustomTotpIssuerConfiguration);
|
|
||||||
|
|
||||||
function registerUser(context: any, username: string) {
|
|
||||||
let secret: Speakeasy.Key;
|
|
||||||
const mongoConnector = new MongoConnector("mongodb://localhost:27017/authelia");
|
|
||||||
return mongoConnector.connect()
|
|
||||||
.then(function (mongoClient: IMongoClient) {
|
|
||||||
const collectionFactory = CollectionFactoryFactory.createMongo(mongoClient);
|
|
||||||
const userDataStore = new UserDataStore(collectionFactory);
|
|
||||||
|
|
||||||
const generator = new TotpHandler(Speakeasy);
|
|
||||||
secret = generator.generate("user", "authelia.com");
|
|
||||||
return userDataStore.saveTOTPSecret(username, secret);
|
|
||||||
})
|
|
||||||
.then(function () {
|
.then(function () {
|
||||||
context.totpSecrets["REGISTERED"] = secret.base32;
|
return exec("./scripts/example-commit/dc-example.sh up -d authelia && sleep 1");
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function declareNeedRegisteredUserHooks(username: string) {
|
declareNeedsConfiguration("regulation", createRegulationConfiguration);
|
||||||
Before({ tags: "@need-registered-user-" + username, timeout: 15 * 1000 }, function () {
|
declareNeedsConfiguration("inactivity", createInactivityConfiguration);
|
||||||
return registerUser(this, username);
|
declareNeedsConfiguration("single_factor", createSingleFactorConfiguration);
|
||||||
|
declareNeedsConfiguration("totp_issuer", createCustomTotpIssuerConfiguration);
|
||||||
|
|
||||||
|
function registerUser(context: any, username: string) {
|
||||||
|
let secret: Speakeasy.Key;
|
||||||
|
const mongoConnector = new MongoConnector("mongodb://localhost:27017");
|
||||||
|
return mongoConnector.connect("authelia")
|
||||||
|
.then(function (mongoClient: IMongoClient) {
|
||||||
|
const collectionFactory = CollectionFactoryFactory.createMongo(mongoClient);
|
||||||
|
const userDataStore = new UserDataStore(collectionFactory);
|
||||||
|
|
||||||
|
const generator = new TotpHandler(Speakeasy);
|
||||||
|
secret = generator.generate("user", "authelia.com");
|
||||||
|
return userDataStore.saveTOTPSecret(username, secret);
|
||||||
|
})
|
||||||
|
.then(function () {
|
||||||
|
context.totpSecrets["REGISTERED"] = secret.base32;
|
||||||
|
return mongoConnector.close();
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
After({ tags: "@need-registered-user-" + username, timeout: 15 * 1000 }, function () {
|
function declareNeedRegisteredUserHooks(username: string) {
|
||||||
this.totpSecrets["REGISTERED"] = undefined;
|
Before({ tags: "@need-registered-user-" + username, timeout: 15 * 1000 }, function () {
|
||||||
|
return registerUser(this, username);
|
||||||
|
});
|
||||||
|
|
||||||
|
After({ tags: "@need-registered-user-" + username, timeout: 15 * 1000 }, function () {
|
||||||
|
this.totpSecrets["REGISTERED"] = undefined;
|
||||||
|
return BluebirdPromise.resolve();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function needAuthenticatedUser(context: any, username: string): BluebirdPromise<void> {
|
||||||
|
return context.visit("https://login.example.com:8080/logout")
|
||||||
|
.then(function () {
|
||||||
|
return context.visit("https://login.example.com:8080/");
|
||||||
|
})
|
||||||
|
.then(function () {
|
||||||
|
return registerUser(context, username);
|
||||||
|
})
|
||||||
|
.then(function () {
|
||||||
|
return context.loginWithUserPassword(username, "password");
|
||||||
|
})
|
||||||
|
.then(function () {
|
||||||
|
return context.useTotpTokenHandle("REGISTERED");
|
||||||
|
})
|
||||||
|
.then(function () {
|
||||||
|
return context.clickOnButton("Sign in");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function needAuthenticatedUser(context: any, username: string): BluebirdPromise<void> {
|
function declareNeedAuthenticatedUserHooks(username: string) {
|
||||||
return context.visit("https://login.example.com:8080/logout")
|
Before({ tags: "@need-authenticated-user-" + username, timeout: 15 * 1000 }, function () {
|
||||||
.then(function () {
|
return needAuthenticatedUser(this, username);
|
||||||
return context.visit("https://login.example.com:8080/");
|
});
|
||||||
})
|
|
||||||
.then(function () {
|
|
||||||
return registerUser(context, username);
|
|
||||||
})
|
|
||||||
.then(function () {
|
|
||||||
return context.loginWithUserPassword(username, "password");
|
|
||||||
})
|
|
||||||
.then(function () {
|
|
||||||
return context.useTotpTokenHandle("REGISTERED");
|
|
||||||
})
|
|
||||||
.then(function () {
|
|
||||||
return context.clickOnButton("Sign in");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function declareNeedAuthenticatedUserHooks(username: string) {
|
After({ tags: "@need-authenticated-user-" + username, timeout: 15 * 1000 }, function () {
|
||||||
Before({ tags: "@need-authenticated-user-" + username, timeout: 15 * 1000 }, function () {
|
this.totpSecrets["REGISTERED"] = undefined;
|
||||||
return needAuthenticatedUser(this, username);
|
return BluebirdPromise.resolve();
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
After({ tags: "@need-authenticated-user-" + username, timeout: 15 * 1000 }, function () {
|
function declareHooksForUser(username: string) {
|
||||||
this.totpSecrets["REGISTERED"] = undefined;
|
declareNeedRegisteredUserHooks(username);
|
||||||
});
|
declareNeedAuthenticatedUserHooks(username);
|
||||||
}
|
}
|
||||||
|
|
||||||
function declareHooksForUser(username: string) {
|
const users = ["harry", "john", "bob", "blackhat"];
|
||||||
declareNeedRegisteredUserHooks(username);
|
users.forEach(declareHooksForUser);
|
||||||
declareNeedAuthenticatedUserHooks(username);
|
|
||||||
}
|
|
||||||
|
|
||||||
const users = ["harry", "john", "bob", "blackhat"];
|
|
||||||
users.forEach(declareHooksForUser);
|
|
||||||
});
|
|
|
@ -1,26 +1,23 @@
|
||||||
import Cucumber = require("cucumber");
|
import {Then} from "cucumber";
|
||||||
import seleniumWebdriver = require("selenium-webdriver");
|
import seleniumWebdriver = require("selenium-webdriver");
|
||||||
import Assert = require("assert");
|
import Assert = require("assert");
|
||||||
import Fs = require("fs");
|
import Fs = require("fs");
|
||||||
import CustomWorld = require("../support/world");
|
import CustomWorld = require("../support/world");
|
||||||
|
|
||||||
Cucumber.defineSupportCode(function ({ Given, When, Then }) {
|
Then("I get a notification of type {string} with message {string}", { timeout: 10 * 1000 },
|
||||||
Then("I get a notification of type {stringInDoubleQuotes} with message {stringInDoubleQuotes}",
|
function (notificationType: string, notificationMessage: string) {
|
||||||
{ timeout: 10 * 1000 },
|
const that = this;
|
||||||
function (notificationType: string, notificationMessage: string) {
|
const notificationEl = this.driver.findElement(seleniumWebdriver.By.className("notification"));
|
||||||
const that = this;
|
return this.driver.wait(seleniumWebdriver.until.elementIsVisible(notificationEl), 5000)
|
||||||
const notificationEl = this.driver.findElement(seleniumWebdriver.By.className("notification"));
|
.then(function () {
|
||||||
return this.driver.wait(seleniumWebdriver.until.elementIsVisible(notificationEl), 5000)
|
return notificationEl.getText();
|
||||||
.then(function () {
|
})
|
||||||
return notificationEl.getText();
|
.then(function (txt: string) {
|
||||||
})
|
Assert.equal(notificationMessage, txt);
|
||||||
.then(function (txt: string) {
|
return notificationEl.getAttribute("class");
|
||||||
Assert.equal(notificationMessage, txt);
|
})
|
||||||
return notificationEl.getAttribute("class");
|
.then(function (classes: string) {
|
||||||
})
|
Assert(classes.indexOf(notificationType) > -1, "Class '" + notificationType + "' not found in notification element.");
|
||||||
.then(function (classes: string) {
|
return that.driver.sleep(500);
|
||||||
Assert(classes.indexOf(notificationType) > -1, "Class '" + notificationType + "' not found in notification element.");
|
|
||||||
return that.driver.sleep(500);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
|
@ -1,17 +1,15 @@
|
||||||
import Cucumber = require("cucumber");
|
import {Given, When, Then} from "cucumber";
|
||||||
import seleniumWebdriver = require("selenium-webdriver");
|
import seleniumWebdriver = require("selenium-webdriver");
|
||||||
import Assert = require("assert");
|
import Assert = require("assert");
|
||||||
|
|
||||||
Cucumber.defineSupportCode(function ({ Given, When, Then }) {
|
Given("I'm on {string}", function (link: string) {
|
||||||
Given("I'm on https://{string}", function (link: string) {
|
return this.driver.get(link);
|
||||||
return this.driver.get("https://" + link);
|
});
|
||||||
});
|
|
||||||
|
|
||||||
When("I click on the link to {string}", function (link: string) {
|
When("I click on the link to {string}", function (link: string) {
|
||||||
return this.driver.findElement(seleniumWebdriver.By.linkText(link)).click();
|
return this.driver.findElement(seleniumWebdriver.By.linkText(link)).click();
|
||||||
});
|
});
|
||||||
|
|
||||||
Then("I'm redirected to {stringInDoubleQuotes}", function (link: string) {
|
Then("I'm redirected to {string}", function (link: string) {
|
||||||
return this.waitUntilUrlContains(link);
|
return this.waitUntilUrlContains(link);
|
||||||
});
|
|
||||||
});
|
});
|
|
@ -1,15 +1,13 @@
|
||||||
import Cucumber = require("cucumber");
|
import {When} from "cucumber";
|
||||||
import seleniumWebdriver = require("selenium-webdriver");
|
import seleniumWebdriver = require("selenium-webdriver");
|
||||||
import Assert = require("assert");
|
import Assert = require("assert");
|
||||||
|
|
||||||
Cucumber.defineSupportCode(function ({ Given, When, Then }) {
|
When("the otpauth url has label {string} and issuer \
|
||||||
When("the otpauth url has label {stringInDoubleQuotes} and issuer \
|
{string}", function (label: string, issuer: string) {
|
||||||
{stringInDoubleQuotes}", function (label: string, issuer: string) {
|
return this.driver.findElement(seleniumWebdriver.By.id("qrcode"))
|
||||||
return this.driver.findElement(seleniumWebdriver.By.id("qrcode"))
|
.getAttribute("title")
|
||||||
.getAttribute("title")
|
.then(function (title: string) {
|
||||||
.then(function (title: string) {
|
const re = `^otpauth://totp/${label}\\?secret=[A-Z0-9]+&issuer=${issuer}$`;
|
||||||
const re = `^otpauth://totp/${label}\\?secret=[A-Z0-9]+&issuer=${issuer}$`;
|
Assert(new RegExp(re).test(title));
|
||||||
Assert(new RegExp(re).test(title));
|
})
|
||||||
})
|
});
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
import Cucumber = require("cucumber");
|
import {When} from "cucumber";
|
||||||
import seleniumWebdriver = require("selenium-webdriver");
|
import seleniumWebdriver = require("selenium-webdriver");
|
||||||
import Assert = require("assert");
|
import Assert = require("assert");
|
||||||
import Fs = require("fs");
|
import Fs = require("fs");
|
||||||
import CustomWorld = require("../support/world");
|
import CustomWorld = require("../support/world");
|
||||||
|
|
||||||
Cucumber.defineSupportCode(function ({ Given, When, Then }) {
|
When("I wait {int} seconds", { timeout: 10 * 1000 }, function (seconds: number) {
|
||||||
When("I wait {number} seconds", { timeout: 10 * 1000 }, function (seconds: number) {
|
return this.driver.sleep(seconds * 1000);
|
||||||
return this.driver.sleep(seconds * 1000);
|
});
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,18 +1,16 @@
|
||||||
import Cucumber = require("cucumber");
|
import {When} from "cucumber";
|
||||||
import seleniumWebdriver = require("selenium-webdriver");
|
import seleniumWebdriver = require("selenium-webdriver");
|
||||||
import Assert = require("assert");
|
import Assert = require("assert");
|
||||||
import Fs = require("fs");
|
import Fs = require("fs");
|
||||||
|
|
||||||
Cucumber.defineSupportCode(function ({ Given, When, Then }) {
|
When("I click on the link {string}", function (text: string) {
|
||||||
When("I click on the link {stringInDoubleQuotes}", function (text: string) {
|
return this.driver.findElement(seleniumWebdriver.By.linkText(text)).click();
|
||||||
return this.driver.findElement(seleniumWebdriver.By.linkText(text)).click();
|
});
|
||||||
});
|
|
||||||
|
|
||||||
When("I click on the link of the email", function () {
|
When("I click on the link of the email", function () {
|
||||||
const that = this;
|
const that = this;
|
||||||
return this.retrieveLatestMail()
|
return this.retrieveLatestMail()
|
||||||
.then(function (link: string) {
|
.then(function (link: string) {
|
||||||
return that.driver.get(link);
|
return that.driver.get(link);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
});
|
|
@ -1,12 +1,10 @@
|
||||||
import Cucumber = require("cucumber");
|
import {When} from "cucumber";
|
||||||
import seleniumWebdriver = require("selenium-webdriver");
|
import seleniumWebdriver = require("selenium-webdriver");
|
||||||
import Assert = require("assert");
|
import Assert = require("assert");
|
||||||
import ChildProcess = require("child_process");
|
import ChildProcess = require("child_process");
|
||||||
import BluebirdPromise = require("bluebird");
|
import BluebirdPromise = require("bluebird");
|
||||||
|
|
||||||
Cucumber.defineSupportCode(function ({ Given, When, Then }) {
|
When(/^the application restarts$/, {timeout: 15 * 1000}, function () {
|
||||||
When(/^the application restarts$/, {timeout: 15 * 1000}, function () {
|
const exec = BluebirdPromise.promisify(ChildProcess.exec);
|
||||||
const exec = BluebirdPromise.promisify(ChildProcess.exec);
|
return exec("./scripts/example-commit/dc-example.sh restart authelia && sleep 1");
|
||||||
return exec("./scripts/example-commit/dc-example.sh restart authelia && sleep 1");
|
|
||||||
});
|
|
||||||
});
|
});
|
|
@ -1,72 +1,69 @@
|
||||||
import Cucumber = require("cucumber");
|
import {Before, When, Then, TableDefinition} from "cucumber";
|
||||||
import seleniumWebdriver = require("selenium-webdriver");
|
import seleniumWebdriver = require("selenium-webdriver");
|
||||||
import Assert = require("assert");
|
import Assert = require("assert");
|
||||||
import Request = require("request-promise");
|
import Request = require("request-promise");
|
||||||
import Bluebird = require("bluebird");
|
import Bluebird = require("bluebird");
|
||||||
|
|
||||||
Cucumber.defineSupportCode(function ({ Given, When, Then, Before, After }) {
|
Before(function () {
|
||||||
Before(function () {
|
this.jar = Request.jar();
|
||||||
this.jar = Request.jar();
|
});
|
||||||
})
|
|
||||||
|
|
||||||
Then("I get an error {number}", function (code: number) {
|
Then("I get an error {int}", function (code: number) {
|
||||||
return this.getErrorPage(code);
|
return this.getErrorPage(code);
|
||||||
|
});
|
||||||
|
|
||||||
|
When("I request {string} with method {string}",
|
||||||
|
function (url: string, method: string) {
|
||||||
|
const that = this;
|
||||||
});
|
});
|
||||||
|
|
||||||
When("I request {stringInDoubleQuotes} with method {stringInDoubleQuotes}",
|
function requestAndExpectStatusCode(ctx: any, url: string, method: string,
|
||||||
function (url: string, method: string) {
|
expectedStatusCode: number) {
|
||||||
const that = this;
|
return Request(url, {
|
||||||
|
method: method,
|
||||||
})
|
jar: ctx.jar
|
||||||
|
|
||||||
function requestAndExpectStatusCode(ctx: any, url: string, method: string,
|
|
||||||
expectedStatusCode: number) {
|
|
||||||
return Request(url, {
|
|
||||||
method: method,
|
|
||||||
jar: ctx.jar
|
|
||||||
})
|
|
||||||
.then(function (body: string) {
|
|
||||||
return Bluebird.resolve(parseInt(body.match(/Error ([0-9]{3})/)[1]));
|
|
||||||
}, function (response: any) {
|
|
||||||
return Bluebird.resolve(response.statusCode)
|
|
||||||
})
|
|
||||||
.then(function (statusCode: number) {
|
|
||||||
try {
|
|
||||||
Assert.equal(statusCode, expectedStatusCode);
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
console.log(url);
|
|
||||||
console.log("%s (actual) != %s (expected)", statusCode,
|
|
||||||
expectedStatusCode);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
Then("I get the following status code when requesting:",
|
|
||||||
function (dataTable: Cucumber.TableDefinition) {
|
|
||||||
const promises: Bluebird<void>[] = [];
|
|
||||||
for (let i = 0; i < dataTable.rows().length; i++) {
|
|
||||||
const url: string = (dataTable.hashes() as any)[i].url;
|
|
||||||
const method: string = (dataTable.hashes() as any)[i].method;
|
|
||||||
const code: number = (dataTable.hashes() as any)[i].code;
|
|
||||||
promises.push(requestAndExpectStatusCode(this, url, method, code));
|
|
||||||
}
|
|
||||||
return Bluebird.all(promises);
|
|
||||||
})
|
|
||||||
|
|
||||||
When("I post {stringInDoubleQuotes} with body:", function (url: string,
|
|
||||||
dataTable: Cucumber.TableDefinition) {
|
|
||||||
const body = {};
|
|
||||||
for (let i = 0; i < dataTable.rows().length; i++) {
|
|
||||||
const key = (dataTable.hashes() as any)[i].key;
|
|
||||||
const value = (dataTable.hashes() as any)[i].value;
|
|
||||||
body[key] = value;
|
|
||||||
}
|
|
||||||
return Request.post(url, {
|
|
||||||
body: body,
|
|
||||||
jar: this.jar,
|
|
||||||
json: true
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
|
.then(function (body: string) {
|
||||||
|
return Bluebird.resolve(parseInt(body.match(/Error ([0-9]{3})/)[1]));
|
||||||
|
}, function (response: any) {
|
||||||
|
return Bluebird.resolve(response.statusCode)
|
||||||
|
})
|
||||||
|
.then(function (statusCode: number) {
|
||||||
|
try {
|
||||||
|
Assert.equal(statusCode, expectedStatusCode);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.log(url);
|
||||||
|
console.log("%s (actual) != %s (expected)", statusCode,
|
||||||
|
expectedStatusCode);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Then("I get the following status code when requesting:",
|
||||||
|
function (dataTable: TableDefinition) {
|
||||||
|
const promises: Bluebird<void>[] = [];
|
||||||
|
for (let i = 0; i < dataTable.rows().length; i++) {
|
||||||
|
const url: string = (dataTable.hashes() as any)[i].url;
|
||||||
|
const method: string = (dataTable.hashes() as any)[i].method;
|
||||||
|
const code: number = (dataTable.hashes() as any)[i].code;
|
||||||
|
promises.push(requestAndExpectStatusCode(this, url, method, code));
|
||||||
|
}
|
||||||
|
return Bluebird.all(promises);
|
||||||
|
})
|
||||||
|
|
||||||
|
When("I post {string} with body:", function (url: string,
|
||||||
|
dataTable: TableDefinition) {
|
||||||
|
const body = {};
|
||||||
|
for (let i = 0; i < dataTable.rows().length; i++) {
|
||||||
|
const key = (dataTable.hashes() as any)[i].key;
|
||||||
|
const value = (dataTable.hashes() as any)[i].value;
|
||||||
|
body[key] = value;
|
||||||
|
}
|
||||||
|
return Request.post(url, {
|
||||||
|
body: body,
|
||||||
|
jar: this.jar,
|
||||||
|
json: true
|
||||||
|
});
|
||||||
});
|
});
|
|
@ -1,8 +1,6 @@
|
||||||
import Cucumber = require("cucumber");
|
import {When} from "cucumber";
|
||||||
import seleniumWebdriver = require("selenium-webdriver");
|
import seleniumWebdriver = require("selenium-webdriver");
|
||||||
|
|
||||||
Cucumber.defineSupportCode(function ({ Given, When, Then }) {
|
When("I sleep for {int} seconds", function (seconds: number) {
|
||||||
When("I sleep for {number} seconds", function (seconds: number) {
|
return this.driver.sleep(seconds * 1000);
|
||||||
return this.driver.sleep(seconds * 1000);
|
});
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,39 +1,37 @@
|
||||||
import Cucumber = require("cucumber");
|
import {When, Then} from "cucumber";
|
||||||
import seleniumWebdriver = require("selenium-webdriver");
|
import seleniumWebdriver = require("selenium-webdriver");
|
||||||
import Request = require("request-promise");
|
import Request = require("request-promise");
|
||||||
import BluebirdPromise = require("bluebird");
|
import BluebirdPromise = require("bluebird");
|
||||||
import Util = require("util");
|
import Util = require("util");
|
||||||
|
|
||||||
Cucumber.defineSupportCode(function ({ Given, When, Then }) {
|
When("I request {string} with username {string}" +
|
||||||
When("I request {stringInDoubleQuotes} with username {stringInDoubleQuotes}" +
|
" and password {string} using basic authentication",
|
||||||
" and password {stringInDoubleQuotes} using basic authentication",
|
function (url: string, username: string, password: string) {
|
||||||
function (url: string, username: string, password: string) {
|
const that = this;
|
||||||
const that = this;
|
return Request(url, {
|
||||||
return Request(url, {
|
auth: {
|
||||||
auth: {
|
username: username,
|
||||||
username: username,
|
password: password
|
||||||
password: password
|
},
|
||||||
},
|
resolveWithFullResponse: true
|
||||||
resolveWithFullResponse: true
|
})
|
||||||
})
|
.then(function (response: any) {
|
||||||
.then(function (response: any) {
|
that.response = response;
|
||||||
that.response = response;
|
});
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
Then("I receive the secret page", function () {
|
|
||||||
if (this.response.body.match("This is a very important secret!"))
|
|
||||||
return BluebirdPromise.resolve();
|
|
||||||
return BluebirdPromise.reject(new Error("Secret page not received."));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Then("I received header {stringInDoubleQuotes} set to {stringInDoubleQuotes}",
|
Then("I receive the secret page", function () {
|
||||||
function (expectedHeaderName: string, expectedValue: string) {
|
if (this.response.body.match("This is a very important secret!"))
|
||||||
const expectedLine = Util.format("\"%s\": \"%s\"", expectedHeaderName,
|
return BluebirdPromise.resolve();
|
||||||
expectedValue);
|
return BluebirdPromise.reject(new Error("Secret page not received."));
|
||||||
if (this.response.body.indexOf(expectedLine) > 0)
|
});
|
||||||
return BluebirdPromise.resolve();
|
|
||||||
return BluebirdPromise.reject(new Error(
|
Then("I received header {string} set to {string}",
|
||||||
Util.format("No such header or with unexpected value.")));
|
function (expectedHeaderName: string, expectedValue: string) {
|
||||||
})
|
const expectedLine = Util.format("\"%s\": \"%s\"", expectedHeaderName,
|
||||||
});
|
expectedValue);
|
||||||
|
if (this.response.body.indexOf(expectedLine) > 0)
|
||||||
|
return BluebirdPromise.resolve();
|
||||||
|
return BluebirdPromise.reject(new Error(
|
||||||
|
Util.format("No such header or with unexpected value.")));
|
||||||
|
});
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
require("chromedriver");
|
require("chromedriver");
|
||||||
import seleniumWebdriver = require("selenium-webdriver");
|
import seleniumWebdriver = require("selenium-webdriver");
|
||||||
import Cucumber = require("cucumber");
|
import {setWorldConstructor, After} from "cucumber";
|
||||||
import Fs = require("fs");
|
import Fs = require("fs");
|
||||||
import Speakeasy = require("speakeasy");
|
import Speakeasy = require("speakeasy");
|
||||||
import Assert = require("assert");
|
import Assert = require("assert");
|
||||||
|
@ -63,7 +63,7 @@ function CustomWorld() {
|
||||||
this.waitUntilUrlContains = function (url: string) {
|
this.waitUntilUrlContains = function (url: string) {
|
||||||
const that = this;
|
const that = this;
|
||||||
return this.driver.wait(seleniumWebdriver.until.urlIs(url), 15000)
|
return this.driver.wait(seleniumWebdriver.until.urlIs(url), 15000)
|
||||||
.then(function () { }, function (err: Error) {
|
.then(function () {return BluebirdPromise.resolve(); }, function (err: Error) {
|
||||||
that.driver.getCurrentUrl()
|
that.driver.getCurrentUrl()
|
||||||
.then(function (current: string) {
|
.then(function (current: string) {
|
||||||
console.error("====> Error due to: %s (current) != %s (expected)", current, url);
|
console.error("====> Error due to: %s (current) != %s (expected)", current, url);
|
||||||
|
@ -176,6 +176,4 @@ function CustomWorld() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Cucumber.defineSupportCode(function ({ setWorldConstructor }) {
|
setWorldConstructor(CustomWorld);
|
||||||
setWorldConstructor(CustomWorld);
|
|
||||||
});
|
|
||||||
|
|
Loading…
Reference in New Issue