Merge pull request #215 from clems4ever/update-npm-deps

Update NPM dependencies
pull/212/head
Clément Michaud 2018-03-29 23:50:02 +02:00 committed by GitHub
commit d0954a202a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 3472 additions and 1936 deletions

19
CHANGELOG.md 100644
View File

@ -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

View File

@ -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']);

View File

@ -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);
}); });
}); });
} }

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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.
# #

View File

@ -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

4257
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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": {

View File

@ -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 $*

View File

@ -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);

View File

@ -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));

View File

@ -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 {

View File

@ -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
}); });

View File

@ -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); });
} }

View File

@ -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>;
} }

View File

@ -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();
}
} }

View File

@ -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();
}); });
}); });
}); });

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -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 |

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);
});
});

View File

@ -1,15 +1,10 @@
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 () {
this.jar = Request.jar();
})
When("I query {stringInDoubleQuotes}", function (url: string) {
const that = this; const that = this;
return Request(url, { followRedirect: false }) return Request(url, { followRedirect: false })
.then(function(response) { .then(function(response) {
@ -19,9 +14,9 @@ Cucumber.defineSupportCode(function ({ Given, When, Then, Before, After }) {
.catch(function(err: Error) { .catch(function(err: Error) {
that.error = err; that.error = err;
}) })
}); });
Then("I get error code 401", function() { Then("I get error code 401", function() {
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 == 401) {
@ -34,9 +29,9 @@ Cucumber.defineSupportCode(function ({ Given, When, Then, Before, After }) {
reject(new Error("Error code != 401")); reject(new Error("Error code != 401"));
} }
}); });
}); });
Then("I get redirected to {stringInDoubleQuotes}", function(url: string) { 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 == 302 if(that.error && that.error.statusCode == 302
@ -47,5 +42,4 @@ Cucumber.defineSupportCode(function ({ Given, When, Then, Before, After }) {
reject(new Error("Not redirected")); reject(new Error("Not redirected"));
} }
}); });
})
}); });

View File

@ -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,39 +7,38 @@ 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) { When("I set field {string} to {string}", function (fieldName: string, content: string) {
return this.setFieldTo(fieldName, content); return this.setFieldTo(fieldName, content);
}); });
When("I clear field {stringInDoubleQuotes}", function (fieldName: string) { When("I clear field {string}", function (fieldName: string) {
return this.clearField(fieldName); return this.clearField(fieldName);
}); });
When("I click on {stringInDoubleQuotes}", function (text: string) { When("I click on {string}", function (text: string) {
return this.clickOnButton(text); return this.clickOnButton(text);
}); });
Given("I login with user {stringInDoubleQuotes} and password {stringInDoubleQuotes}", Given("I login with user {string} and password {string}",
function (username: string, password: string) { function (username: string, password: string) {
return this.loginWithUserPassword(username, password); return this.loginWithUserPassword(username, password);
}); });
Given("I login with user {stringInDoubleQuotes} and password {stringInDoubleQuotes} \ Given("I login with user {string} and password {string} \
and I use TOTP token handle {stringInDoubleQuotes}", and I use TOTP token handle {string}",
function (username: string, password: string, totpTokenHandle: string) { function (username: string, password: string, totpTokenHandle: string) {
const that = this; const that = this;
return this.loginWithUserPassword(username, password) return this.loginWithUserPassword(username, password)
@ -48,19 +47,19 @@ and I use TOTP token handle {stringInDoubleQuotes}",
}); });
}); });
Given("I register a TOTP secret called {stringInDoubleQuotes}", function (handle: string) { Given("I register a TOTP secret called {string}", function (handle: string) {
return this.registerTotpSecret(handle); return this.registerTotpSecret(handle);
}); });
Given("I use {stringInDoubleQuotes} as TOTP token", function (token: string) { Given("I use {string} as TOTP token", function (token: string) {
return this.useTotpToken(token); return this.useTotpToken(token);
}); });
Given("I use {stringInDoubleQuotes} as TOTP token handle", function (handle) { Given("I use {string} as TOTP token handle", function (handle) {
return this.useTotpTokenHandle(handle); return this.useTotpTokenHandle(handle);
}); });
When("I visit {stringInDoubleQuotes} and get redirected {stringInDoubleQuotes}", When("I visit {string} and get redirected {string}",
function (url: string, redirectUrl: string) { function (url: string, redirectUrl: string) {
const that = this; const that = this;
return this.driver.get(url) return this.driver.get(url)
@ -69,44 +68,12 @@ and I use TOTP token handle {stringInDoubleQuotes}",
}); });
}); });
Given("I register TOTP and login with user {stringInDoubleQuotes} and password {stringInDoubleQuotes}", Given("I register TOTP and login with user {string} and password {string}",
function (username: string, password: string) { function (username: string, password: string) {
return this.registerTotpAndSignin(username, password); return this.registerTotpAndSignin(username, password);
}); });
function hasAccessToSecret(link: string, that: any) { function endpointReplyWith(context: any, link: string, method: string,
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) { returnCode: number) {
return Request(link, { return Request(link, {
method: method method: method
@ -118,9 +85,9 @@ and I use TOTP token handle {stringInDoubleQuotes}",
Assert.equal(response.statusCode, returnCode); Assert.equal(response.statusCode, returnCode);
return BluebirdPromise.resolve(); return BluebirdPromise.resolve();
}); });
} }
Then("the following endpoints reply with:", function (dataTable: Cucumber.TableDefinition) { Then("the following endpoints reply with:", function (dataTable: TableDefinition) {
const promises = []; const promises = [];
for (let i = 0; i < dataTable.rows().length; i++) { for (let i = 0; i < dataTable.rows().length; i++) {
const url: string = (dataTable.hashes() as any)[i].url; const url: string = (dataTable.hashes() as any)[i].url;
@ -129,5 +96,4 @@ and I use TOTP token handle {stringInDoubleQuotes}",
promises.push(endpointReplyWith(this, url, method, code)); promises.push(endpointReplyWith(this, url, method, code));
} }
return BluebirdPromise.all(promises); return BluebirdPromise.all(promises);
});
}); });

View File

@ -1,11 +1,10 @@
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()
@ -17,4 +16,3 @@ Cucumber.defineSupportCode(function ({ Given, When, Then }) {
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.")));
}); });
}) })
});

View File

@ -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,50 +8,52 @@ 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);
After(function () {
return this.driver.quit();
});
function createRegulationConfiguration(): BluebirdPromise<void> {
return exec("\ return exec("\
cat config.template.yml | \ cat config.template.yml | \
sed 's/find_time: [0-9]\\+/find_time: 15/' | \ sed 's/find_time: [0-9]\\+/find_time: 15/' | \
sed 's/ban_time: [0-9]\\+/ban_time: 4/' > config.test.yml \ sed 's/ban_time: [0-9]\\+/ban_time: 4/' > config.test.yml \
"); ");
} }
function createInactivityConfiguration(): BluebirdPromise<void> { function createInactivityConfiguration(): BluebirdPromise<void> {
return exec("\ return exec("\
cat config.template.yml | \ cat config.template.yml | \
sed 's/expiration: [0-9]\\+/expiration: 10000/' | \ sed 's/expiration: [0-9]\\+/expiration: 10000/' | \
sed 's/inactivity: [0-9]\\+/inactivity: 5000/' > config.test.yml \ sed 's/inactivity: [0-9]\\+/inactivity: 5000/' > config.test.yml \
"); ");
} }
function createSingleFactorConfiguration(): BluebirdPromise<void> { function createSingleFactorConfiguration(): BluebirdPromise<void> {
return exec("\ return exec("\
cat config.template.yml | \ cat config.template.yml | \
sed 's/default_method: two_factor/default_method: single_factor/' > config.test.yml \ sed 's/default_method: two_factor/default_method: single_factor/' > config.test.yml \
"); ");
} }
function createCustomTotpIssuerConfiguration(): BluebirdPromise<void> { function createCustomTotpIssuerConfiguration(): BluebirdPromise<void> {
return exec("\ return exec("\
cat config.template.yml > config.test.yml && \ cat config.template.yml > config.test.yml && \
echo 'totp:' >> config.test.yml && \ echo 'totp:' >> config.test.yml && \
echo ' issuer: custom.com' >> config.test.yml \ echo ' issuer: custom.com' >> config.test.yml \
"); ");
} }
function declareNeedsConfiguration(tag: string, cb: () => BluebirdPromise<void>) { function declareNeedsConfiguration(tag: string, cb: () => BluebirdPromise<void>) {
Before({ tags: "@needs-" + tag + "-config", timeout: 20 * 1000 }, function () { Before({ tags: "@needs-" + tag + "-config", timeout: 20 * 1000 }, function () {
return cb() return cb()
.then(function () { .then(function () {
@ -67,17 +69,17 @@ Cucumber.defineSupportCode(function ({ After, Before }) {
return exec("./scripts/example-commit/dc-example.sh up -d authelia && sleep 1"); return exec("./scripts/example-commit/dc-example.sh up -d authelia && sleep 1");
}); });
}); });
} }
declareNeedsConfiguration("regulation", createRegulationConfiguration); declareNeedsConfiguration("regulation", createRegulationConfiguration);
declareNeedsConfiguration("inactivity", createInactivityConfiguration); declareNeedsConfiguration("inactivity", createInactivityConfiguration);
declareNeedsConfiguration("single_factor", createSingleFactorConfiguration); declareNeedsConfiguration("single_factor", createSingleFactorConfiguration);
declareNeedsConfiguration("totp_issuer", createCustomTotpIssuerConfiguration); declareNeedsConfiguration("totp_issuer", createCustomTotpIssuerConfiguration);
function registerUser(context: any, username: string) { function registerUser(context: any, username: string) {
let secret: Speakeasy.Key; let secret: Speakeasy.Key;
const mongoConnector = new MongoConnector("mongodb://localhost:27017/authelia"); const mongoConnector = new MongoConnector("mongodb://localhost:27017");
return mongoConnector.connect() return mongoConnector.connect("authelia")
.then(function (mongoClient: IMongoClient) { .then(function (mongoClient: IMongoClient) {
const collectionFactory = CollectionFactoryFactory.createMongo(mongoClient); const collectionFactory = CollectionFactoryFactory.createMongo(mongoClient);
const userDataStore = new UserDataStore(collectionFactory); const userDataStore = new UserDataStore(collectionFactory);
@ -88,20 +90,22 @@ Cucumber.defineSupportCode(function ({ After, Before }) {
}) })
.then(function () { .then(function () {
context.totpSecrets["REGISTERED"] = secret.base32; context.totpSecrets["REGISTERED"] = secret.base32;
return mongoConnector.close();
}); });
} }
function declareNeedRegisteredUserHooks(username: string) { function declareNeedRegisteredUserHooks(username: string) {
Before({ tags: "@need-registered-user-" + username, timeout: 15 * 1000 }, function () { Before({ tags: "@need-registered-user-" + username, timeout: 15 * 1000 }, function () {
return registerUser(this, username); return registerUser(this, username);
}); });
After({ tags: "@need-registered-user-" + username, timeout: 15 * 1000 }, function () { After({ tags: "@need-registered-user-" + username, timeout: 15 * 1000 }, function () {
this.totpSecrets["REGISTERED"] = undefined; this.totpSecrets["REGISTERED"] = undefined;
return BluebirdPromise.resolve();
}); });
} }
function needAuthenticatedUser(context: any, username: string): BluebirdPromise<void> { function needAuthenticatedUser(context: any, username: string): BluebirdPromise<void> {
return context.visit("https://login.example.com:8080/logout") return context.visit("https://login.example.com:8080/logout")
.then(function () { .then(function () {
return context.visit("https://login.example.com:8080/"); return context.visit("https://login.example.com:8080/");
@ -118,23 +122,23 @@ Cucumber.defineSupportCode(function ({ After, Before }) {
.then(function () { .then(function () {
return context.clickOnButton("Sign in"); return context.clickOnButton("Sign in");
}); });
} }
function declareNeedAuthenticatedUserHooks(username: string) { function declareNeedAuthenticatedUserHooks(username: string) {
Before({ tags: "@need-authenticated-user-" + username, timeout: 15 * 1000 }, function () { Before({ tags: "@need-authenticated-user-" + username, timeout: 15 * 1000 }, function () {
return needAuthenticatedUser(this, username); return needAuthenticatedUser(this, username);
}); });
After({ tags: "@need-authenticated-user-" + username, timeout: 15 * 1000 }, function () { After({ tags: "@need-authenticated-user-" + username, timeout: 15 * 1000 }, function () {
this.totpSecrets["REGISTERED"] = undefined; this.totpSecrets["REGISTERED"] = undefined;
return BluebirdPromise.resolve();
}); });
} }
function declareHooksForUser(username: string) { function declareHooksForUser(username: string) {
declareNeedRegisteredUserHooks(username); declareNeedRegisteredUserHooks(username);
declareNeedAuthenticatedUserHooks(username); declareNeedAuthenticatedUserHooks(username);
} }
const users = ["harry", "john", "bob", "blackhat"]; const users = ["harry", "john", "bob", "blackhat"];
users.forEach(declareHooksForUser); users.forEach(declareHooksForUser);
});

View File

@ -1,13 +1,11 @@
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 },
function (notificationType: string, notificationMessage: string) {
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), 5000) return this.driver.wait(seleniumWebdriver.until.elementIsVisible(notificationEl), 5000)
@ -22,5 +20,4 @@ Cucumber.defineSupportCode(function ({ Given, When, Then }) {
Assert(classes.indexOf(notificationType) > -1, "Class '" + notificationType + "' not found in notification element."); Assert(classes.indexOf(notificationType) > -1, "Class '" + notificationType + "' not found in notification element.");
return that.driver.sleep(500); return that.driver.sleep(500);
}); });
});
}); });

View File

@ -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 {string}", function (link: string) {
Then("I'm redirected to {stringInDoubleQuotes}", function (link: string) { return this.waitUntilUrlContains(link);
return this.waitUntilUrlContains(link);
});
}); });

View File

@ -1,10 +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");
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) {
@ -12,4 +11,3 @@ Cucumber.defineSupportCode(function ({ Given, When, Then }) {
Assert(new RegExp(re).test(title)); Assert(new RegExp(re).test(title));
}) })
}); });
});

View File

@ -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);
});
}); });

View File

@ -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);
}); });
});
}); });

View File

@ -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");
});
}); });

View File

@ -1,25 +1,23 @@
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 {stringInDoubleQuotes} with method {stringInDoubleQuotes}", When("I request {string} with method {string}",
function (url: string, method: string) { function (url: string, method: string) {
const that = this; const that = this;
});
}) function requestAndExpectStatusCode(ctx: any, url: string, method: string,
function requestAndExpectStatusCode(ctx: any, url: string, method: string,
expectedStatusCode: number) { expectedStatusCode: number) {
return Request(url, { return Request(url, {
method: method, method: method,
@ -41,10 +39,10 @@ Cucumber.defineSupportCode(function ({ Given, When, Then, Before, After }) {
throw e; throw e;
} }
}) })
} }
Then("I get the following status code when requesting:", Then("I get the following status code when requesting:",
function (dataTable: Cucumber.TableDefinition) { function (dataTable: TableDefinition) {
const promises: Bluebird<void>[] = []; const promises: Bluebird<void>[] = [];
for (let i = 0; i < dataTable.rows().length; i++) { for (let i = 0; i < dataTable.rows().length; i++) {
const url: string = (dataTable.hashes() as any)[i].url; const url: string = (dataTable.hashes() as any)[i].url;
@ -55,8 +53,8 @@ Cucumber.defineSupportCode(function ({ Given, When, Then, Before, After }) {
return Bluebird.all(promises); return Bluebird.all(promises);
}) })
When("I post {stringInDoubleQuotes} with body:", function (url: string, When("I post {string} with body:", function (url: string,
dataTable: Cucumber.TableDefinition) { dataTable: TableDefinition) {
const body = {}; const body = {};
for (let i = 0; i < dataTable.rows().length; i++) { for (let i = 0; i < dataTable.rows().length; i++) {
const key = (dataTable.hashes() as any)[i].key; const key = (dataTable.hashes() as any)[i].key;
@ -68,5 +66,4 @@ Cucumber.defineSupportCode(function ({ Given, When, Then, Before, After }) {
jar: this.jar, jar: this.jar,
json: true json: true
}); });
})
}); });

View File

@ -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);
});
}); });

View File

@ -1,12 +1,11 @@
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, {
@ -21,13 +20,13 @@ Cucumber.defineSupportCode(function ({ Given, When, Then }) {
}); });
}); });
Then("I receive the secret page", function () { Then("I receive the secret page", function () {
if (this.response.body.match("This is a very important secret!")) if (this.response.body.match("This is a very important secret!"))
return BluebirdPromise.resolve(); return BluebirdPromise.resolve();
return BluebirdPromise.reject(new Error("Secret page not received.")); return BluebirdPromise.reject(new Error("Secret page not received."));
}); });
Then("I received header {stringInDoubleQuotes} set to {stringInDoubleQuotes}", Then("I received header {string} set to {string}",
function (expectedHeaderName: string, expectedValue: string) { function (expectedHeaderName: string, expectedValue: string) {
const expectedLine = Util.format("\"%s\": \"%s\"", expectedHeaderName, const expectedLine = Util.format("\"%s\": \"%s\"", expectedHeaderName,
expectedValue); expectedValue);
@ -35,5 +34,4 @@ Cucumber.defineSupportCode(function ({ Given, When, Then }) {
return BluebirdPromise.resolve(); return BluebirdPromise.resolve();
return BluebirdPromise.reject(new Error( return BluebirdPromise.reject(new Error(
Util.format("No such header or with unexpected value."))); Util.format("No such header or with unexpected value.")));
}) });
});

View File

@ -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);
});