From dacdce6c50142894ffd17bd87e3613ca4a9b4b2d Mon Sep 17 00:00:00 2001 From: Clement Michaud Date: Tue, 17 Oct 2017 00:38:10 +0200 Subject: [PATCH] Implement session inactivity timeout This timeout will prevent an attacker from using a session that has been inactive for too long. This inactivity timeout combined with the timeout before expiration makes a good combination of security mechanisms to prevent session theft. If no activity timeout is provided, then the feature is disabled and only session expiration remains as a protection. --- .gitignore | 1 + Gruntfile.js | 4 +- config.template.yml | 3 + config.test.yml | 196 ------------------ scripts/run-cucumber.sh | 3 + server/src/lib/AuthenticationSession.ts | 4 +- .../src/lib/configuration/Configuration.d.ts | 1 + .../lib/configuration/ConfigurationParser.ts | 1 + server/src/lib/routes/verify/get.ts | 58 +++++- .../test/SessionConfigurationBuilder.test.ts | 1 + .../configuration/ConfigurationParser.test.ts | 40 +++- server/test/routes/verify/get.test.ts | 60 +++++- test/features/redirection.feature | 2 - test/features/regulation.feature | 3 +- test/features/session-timeout.feature | 24 +++ test/features/step_definitions/hooks.ts | 52 ++++- test/features/step_definitions/resilience.ts | 2 +- .../step_definitions/session-timeout.ts | 8 + 18 files changed, 219 insertions(+), 244 deletions(-) delete mode 100644 config.test.yml create mode 100755 scripts/run-cucumber.sh create mode 100644 test/features/session-timeout.feature create mode 100644 test/features/step_definitions/session-timeout.ts diff --git a/.gitignore b/.gitignore index f9cbd72b5..fc2aac8b3 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,7 @@ dist/ # Specific files /config.yml +/config.test.yml example/ldap/private.ldif diff --git a/Gruntfile.js b/Gruntfile.js index e0400f536..200799537 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -42,8 +42,8 @@ module.exports = function (grunt) { args: ['--colors', '--compilers', 'ts:ts-node/register', '--recursive', 'client/test'] }, "test-int": { - cmd: "./node_modules/.bin/cucumber-js", - args: ["--colors", "--compiler", "ts:ts-node/register", "./test/features"] + cmd: "./scripts/run-cucumber.sh", + args: ["./test/features"] }, "docker-build": { cmd: "docker", diff --git a/config.template.yml b/config.template.yml index 5505e2f20..a712a6728 100644 --- a/config.template.yml +++ b/config.template.yml @@ -156,6 +156,9 @@ session: # The time in ms before the cookie expires and session is reset. expiration: 3600000 # 1 hour + # The inactivity time in ms before the session is reset. + inactivity: 300000 # 5 minutes + # The domain to protect. # Note: the authenticator must also be in that domain. If empty, the cookie # is restricted to the subdomain of the issuer. diff --git a/config.test.yml b/config.test.yml deleted file mode 100644 index 8d1597dbb..000000000 --- a/config.test.yml +++ /dev/null @@ -1,196 +0,0 @@ -############################################################### -# Authelia configuration # -############################################################### - -# The port to listen on -port: 80 - -# Log level -# -# Level of verbosity for logs -logs_level: debug - -# LDAP configuration -# -# Example: for user john, the DN will be cn=john,ou=users,dc=example,dc=com -ldap: - # The url of the ldap server - url: ldap://openldap - - # The base dn for every entries - base_dn: dc=example,dc=com - - # An additional dn to define the scope to all users - additional_users_dn: ou=users - - # The users filter. - # {0} is the matcher replaced by username. - # 'cn={0}' by default. - users_filter: cn={0} - - # An additional dn to define the scope of groups - additional_groups_dn: ou=groups - - # The groups filter used for retrieving groups of a given user. - # {0} is a matcher replaced by username. - # {dn} is a matcher replaced by user DN. - # 'member={dn}' by default. - groups_filter: (&(member={dn})(objectclass=groupOfNames)) - - # The attribute holding the name of the group - group_name_attribute: cn - - # The attribute holding the mail address of the user - mail_attribute: mail - - # The username and password of the admin user. - user: cn=admin,dc=example,dc=com - password: password - -# Authentication methods -# -# Authentication methods can be defined per subdomain. -# There are currently two available methods: "basic_auth" and "two_factor" -# -# Note: by default a domain uses "two_factor" method. -# -# Note: 'per_subdomain_methods' is a dictionary where keys must be subdomains and -# values must be one of the two possible methods. -# -# Note: 'per_subdomain_methods' is optional. -authentication_methods: - default_method: two_factor - per_subdomain_methods: - basicauth.test.local: basic_auth - -# Access Control -# -# Access control is a set of rules you can use to restrict the user access. -# Default (anyone), per-user or per-group rules can be defined. -# -# If 'access_control' is not defined, ACL rules are disabled and a default policy -# is applied, i.e., access is allowed to anyone. Otherwise restrictions follow -# the rules defined below. -# If no rule is provided, all domains are denied. -# -# One can use the wildcard * to match any subdomain. -# Note 1: It must stand at the beginning of the pattern. (example: *.mydomain.com) -# Note 2: You must put the pattern in simple quotes when using the wildcard. -access_control: - # Default policy can either be `allow` or `deny`. - # It is the policy applied to any resource if it has not been overriden - # in the `any`, `groups` or `users` category. - default_policy: deny - - # The rules that apply to anyone. - # The value is a list of rules. - any: - - domain: public.test.local - policy: allow - - # Group-based rules. The key is a group name and the value - # is a list of rules. - groups: - admin: - # All resources in all domains - - domain: '*.test.local' - policy: allow - # Except mx2.mail.test.local (it restricts the first rule) - - domain: 'mx2.mail.test.local' - policy: deny - dev: - - domain: dev.test.local - policy: allow - resources: - - '^/groups/dev/.*$' - - # User-based rules. The key is a user name and the value - # is a list of rules. - users: - john: - - domain: dev.test.local - policy: allow - resources: - - '^/users/john/.*$' - harry: - - domain: dev.test.local - policy: allow - resources: - - '^/users/harry/.*$' - bob: - - domain: '*.mail.test.local' - policy: allow - - domain: 'dev.test.local' - policy: allow - resources: - - '^/users/bob/.*$' - -# Configuration of session cookies -# -# The session cookies identify the user once logged in. -session: - # The secret to encrypt the session cookie. - secret: unsecure_secret - - # The time before the cookie expires. - expiration: 10000 - - # The domain to protect. - # Note: the authenticator must also be in that domain. If empty, the cookie - # is restricted to the subdomain of the issuer. - domain: test.local - - # The redis connection details - redis: - host: redis - port: 6379 - -# Configuration of the authentication regulation mechanism. -# -# This mechanism prevents attackers from brute forcing the first factor. -# It bans the user if too many attempts are done in a short period of -# time. -regulation: - # The number of failed login attempts before user is banned. - # Set it to 0 for disabling regulation. - max_retries: 3 - - # The length of time between login attempts before user is banned. - find_time: 15 - - # The length of time before a banned user can login again. - ban_time: 4 - -# Configuration of the storage backend used to store data and secrets. -# -# You must use only an available configuration: local, mongo -storage: - # The directory where the DB files will be saved - # local: /var/lib/authelia/store - - # Settings to connect to mongo server - mongo: - url: mongodb://mongo/authelia - -# Configuration of the notification system. -# -# Notifications are sent to users when they require a password reset, a u2f -# registration or a TOTP registration. -# Use only an available configuration: filesystem, gmail -notifier: - # Use your email account to send the notifications. You can use an app password. - # List of valid services can be found here: https://nodemailer.com/smtp/well-known/ - # email: - # username: user@example.com - # password: yourpassword - # sender: admin@example.com - # service: gmail - - # Use a SMTP server for sending notifications - smtp: - username: test - password: test - secure: false - host: 'smtp' - port: 1025 - sender: admin@example.com diff --git a/scripts/run-cucumber.sh b/scripts/run-cucumber.sh new file mode 100755 index 000000000..af64ff08c --- /dev/null +++ b/scripts/run-cucumber.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +./node_modules/.bin/cucumber-js --colors --compiler ts:ts-node/register $* diff --git a/server/src/lib/AuthenticationSession.ts b/server/src/lib/AuthenticationSession.ts index ad692f96f..93317132b 100644 --- a/server/src/lib/AuthenticationSession.ts +++ b/server/src/lib/AuthenticationSession.ts @@ -9,7 +9,7 @@ export interface AuthenticationSession { userid: string; first_factor: boolean; second_factor: boolean; - last_activity_datetime: Date; + last_activity_datetime: number; identity_check?: { challenge: string; userid: string; @@ -40,7 +40,7 @@ export function reset(req: express.Request): void { req.session.auth = Object.assign({}, INITIAL_AUTHENTICATION_SESSION, {}); // Initialize last activity with current time - req.session.auth.last_activity_datetime = new Date(); + req.session.auth.last_activity_datetime = new Date().getTime(); } export function get(req: express.Request): BluebirdPromise { diff --git a/server/src/lib/configuration/Configuration.d.ts b/server/src/lib/configuration/Configuration.d.ts index cda35e14f..7582ea0ff 100644 --- a/server/src/lib/configuration/Configuration.d.ts +++ b/server/src/lib/configuration/Configuration.d.ts @@ -62,6 +62,7 @@ export interface SessionRedisOptions { interface SessionCookieConfiguration { secret: string; expiration?: number; + inactivity?: number; domain?: string; redis?: SessionRedisOptions; } diff --git a/server/src/lib/configuration/ConfigurationParser.ts b/server/src/lib/configuration/ConfigurationParser.ts index bbbe9acf1..186ce5dae 100644 --- a/server/src/lib/configuration/ConfigurationParser.ts +++ b/server/src/lib/configuration/ConfigurationParser.ts @@ -71,6 +71,7 @@ function adaptFromUserConfiguration(userConfiguration: UserConfiguration) domain: ObjectPath.get(userConfiguration, "session.domain"), secret: ObjectPath.get(userConfiguration, "session.secret"), expiration: get_optional(userConfiguration, "session.expiration", 3600000), // in ms + inactivity: get_optional(userConfiguration, "session.inactivity", undefined), redis: ObjectPath.get(userConfiguration, "session.redis") }, storage: { diff --git a/server/src/lib/routes/verify/get.ts b/server/src/lib/routes/verify/get.ts index 1afb53852..13a86c0f1 100644 --- a/server/src/lib/routes/verify/get.ts +++ b/server/src/lib/routes/verify/get.ts @@ -13,6 +13,7 @@ import Util = require("util"); import { DomainExtractor } from "../../utils/DomainExtractor"; import { ServerVariables } from "../../ServerVariables"; import { AuthenticationMethodCalculator } from "../../AuthenticationMethodCalculator"; +import { IRequestLogger } from "../../logging/IRequestLogger"; const FIRST_FACTOR_NOT_VALIDATED_MESSAGE = "First factor not yet validated"; const SECOND_FACTOR_NOT_VALIDATED_MESSAGE = "Second factor not yet validated"; @@ -20,17 +21,48 @@ const SECOND_FACTOR_NOT_VALIDATED_MESSAGE = "Second factor not yet validated"; const REMOTE_USER = "Remote-User"; const REMOTE_GROUPS = "Remote-Groups"; +function verify_inactivity(req: express.Request, + authSession: AuthenticationSession.AuthenticationSession, + configuration: AppConfiguration, logger: IRequestLogger) + : BluebirdPromise { + + const lastActivityTime = authSession.last_activity_datetime; + const currentTime = new Date().getTime(); + authSession.last_activity_datetime = currentTime; + + // If inactivity is not specified, then inactivity timeout does not apply + if (!configuration.session.inactivity) { + return BluebirdPromise.resolve(); + } + + const inactivityPeriodMs = currentTime - lastActivityTime; + logger.debug(req, "Inactivity period was %s s and max period was %s.", + inactivityPeriodMs / 1000, configuration.session.inactivity / 1000); + if (inactivityPeriodMs < configuration.session.inactivity) { + return BluebirdPromise.resolve(); + } + + logger.debug(req, "Session has been reset after too long inactivity period."); + AuthenticationSession.reset(req); + return BluebirdPromise.reject(new Error("Inactivity period exceeded.")); +} + function verify_filter(req: express.Request, res: express.Response, vars: ServerVariables): BluebirdPromise { + let _authSession: AuthenticationSession.AuthenticationSession; + let username: string; + let groups: string[]; return AuthenticationSession.get(req) .then(function (authSession) { + _authSession = authSession; + username = _authSession.userid; + groups = _authSession.groups; + res.set("Redirect", encodeURIComponent("https://" + req.headers["host"] + req.headers["x-original-uri"])); - const username = authSession.userid; - const groups = authSession.groups; - if (!authSession.userid) + if (!_authSession.userid) return BluebirdPromise.reject( new exceptions.AccessDeniedError(FIRST_FACTOR_NOT_VALIDATED_MESSAGE)); @@ -44,23 +76,27 @@ function verify_filter(req: express.Request, res: express.Response, vars.logger.debug(req, "domain=%s, path=%s, user=%s, groups=%s", domain, path, username, groups.join(",")); - if (!authSession.first_factor) + if (!_authSession.first_factor) return BluebirdPromise.reject( new exceptions.AccessDeniedError(FIRST_FACTOR_NOT_VALIDATED_MESSAGE)); + if (authenticationMethod == "two_factor" && !_authSession.second_factor) + return BluebirdPromise.reject( + new exceptions.AccessDeniedError(SECOND_FACTOR_NOT_VALIDATED_MESSAGE)); + const isAllowed = vars.accessController.isAccessAllowed(domain, path, username, groups); if (!isAllowed) return BluebirdPromise.reject( new exceptions.DomainAccessDenied(Util.format("User '%s' does not have access to '%s'", username, domain))); - - if (authenticationMethod == "two_factor" && !authSession.second_factor) - return BluebirdPromise.reject( - new exceptions.AccessDeniedError(SECOND_FACTOR_NOT_VALIDATED_MESSAGE)); - + return BluebirdPromise.resolve(); + }) + .then(function () { + return verify_inactivity(req, _authSession, + vars.config, vars.logger); + }) + .then(function () { res.setHeader(REMOTE_USER, username); res.setHeader(REMOTE_GROUPS, groups.join(",")); - - return BluebirdPromise.resolve(); }); } diff --git a/server/test/SessionConfigurationBuilder.test.ts b/server/test/SessionConfigurationBuilder.test.ts index c5a8cd91d..6273382f0 100644 --- a/server/test/SessionConfigurationBuilder.test.ts +++ b/server/test/SessionConfigurationBuilder.test.ts @@ -113,6 +113,7 @@ describe("test session configuration builder", function () { domain: "example.com", expiration: 3600, secret: "secret", + inactivity: 4000, redis: { host: "redis.example.com", port: 6379 diff --git a/server/test/configuration/ConfigurationParser.test.ts b/server/test/configuration/ConfigurationParser.test.ts index 4134cec38..c31e18b63 100644 --- a/server/test/configuration/ConfigurationParser.test.ts +++ b/server/test/configuration/ConfigurationParser.test.ts @@ -61,17 +61,35 @@ describe("test config parser", function () { }); }); - it("should get the session attributes", function () { - const yaml_config = buildYamlConfig(); - yaml_config.session = { - domain: "example.com", - secret: "secret", - expiration: 3600 - }; - const config = ConfigurationParser.parse(yaml_config); - Assert.equal(config.session.domain, "example.com"); - Assert.equal(config.session.secret, "secret"); - Assert.equal(config.session.expiration, 3600); + describe("test session configuration", function() { + it("should get the session attributes", function () { + const yaml_config = buildYamlConfig(); + yaml_config.session = { + domain: "example.com", + secret: "secret", + expiration: 3600, + inactivity: 4000 + }; + const config = ConfigurationParser.parse(yaml_config); + Assert.equal(config.session.domain, "example.com"); + Assert.equal(config.session.secret, "secret"); + Assert.equal(config.session.expiration, 3600); + Assert.equal(config.session.inactivity, 4000); + }); + + it("should be ok not specifying inactivity", function () { + const yaml_config = buildYamlConfig(); + yaml_config.session = { + domain: "example.com", + secret: "secret", + expiration: 3600 + }; + const config = ConfigurationParser.parse(yaml_config); + Assert.equal(config.session.domain, "example.com"); + Assert.equal(config.session.secret, "secret"); + Assert.equal(config.session.expiration, 3600); + Assert.equal(config.session.inactivity, undefined); + }); }); it("should get the log level", function () { diff --git a/server/test/routes/verify/get.test.ts b/server/test/routes/verify/get.test.ts index cc89a2fee..61c8e053d 100644 --- a/server/test/routes/verify/get.test.ts +++ b/server/test/routes/verify/get.test.ts @@ -79,7 +79,7 @@ describe("test /verify endpoint", function () { } describe("given user tries to access a 2-factor endpoint", function () { - before(function() { + before(function () { mocks.accessController.isAccessAllowedMock.returns(true); }); @@ -91,7 +91,7 @@ describe("test /verify endpoint", function () { second_factor: false, email: undefined, groups: [], - last_activity_datetime: new Date() + last_activity_datetime: new Date().getTime() }); }); @@ -102,7 +102,7 @@ describe("test /verify endpoint", function () { second_factor: true, email: undefined, groups: [], - last_activity_datetime: new Date() + last_activity_datetime: new Date().getTime() }); }); @@ -113,7 +113,7 @@ describe("test /verify endpoint", function () { second_factor: false, email: undefined, groups: [], - last_activity_datetime: new Date() + last_activity_datetime: new Date().getTime() }); }); @@ -124,7 +124,7 @@ describe("test /verify endpoint", function () { second_factor: false, email: undefined, groups: [], - last_activity_datetime: new Date() + last_activity_datetime: new Date().getTime() }); }); @@ -147,7 +147,7 @@ describe("test /verify endpoint", function () { userid: "user", groups: ["group1", "group2"], email: undefined, - last_activity_datetime: new Date() + last_activity_datetime: new Date().getTime() }); }); }); @@ -191,5 +191,53 @@ describe("test /verify endpoint", function () { }); }); }); + + describe("inactivity period", function () { + it("should update last inactivity period on requests on /verify", function () { + mocks.config.session.inactivity = 200000; + mocks.accessController.isAccessAllowedMock.returns(true); + const currentTime = new Date().getTime() - 1000; + AuthenticationSession.reset(req as any); + return AuthenticationSession.get(req as any) + .then(function (authSession: AuthenticationSession.AuthenticationSession) { + authSession.first_factor = true; + authSession.second_factor = true; + authSession.userid = "myuser"; + authSession.groups = ["mygroup", "othergroup"]; + authSession.last_activity_datetime = currentTime; + return VerifyGet.default(vars)(req as express.Request, res as any); + }) + .then(function () { + return AuthenticationSession.get(req as any); + }) + .then(function (authSession) { + Assert(authSession.last_activity_datetime > currentTime); + }); + }); + + it("should reset session when max inactivity period has been reached", function () { + mocks.config.session.inactivity = 1; + mocks.accessController.isAccessAllowedMock.returns(true); + const currentTime = new Date().getTime() - 1000; + AuthenticationSession.reset(req as any); + return AuthenticationSession.get(req as any) + .then(function (authSession: AuthenticationSession.AuthenticationSession) { + authSession.first_factor = true; + authSession.second_factor = true; + authSession.userid = "myuser"; + authSession.groups = ["mygroup", "othergroup"]; + authSession.last_activity_datetime = currentTime; + return VerifyGet.default(vars)(req as express.Request, res as any); + }) + .then(function () { + return AuthenticationSession.get(req as any); + }) + .then(function (authSession) { + Assert.equal(authSession.first_factor, false); + Assert.equal(authSession.second_factor, false); + Assert.equal(authSession.userid, undefined); + }); + }); + }); }); diff --git a/test/features/redirection.feature b/test/features/redirection.feature index 12fba93b2..1693357c1 100644 --- a/test/features/redirection.feature +++ b/test/features/redirection.feature @@ -20,8 +20,6 @@ Feature: User is correctly redirected And I visit "https://admin.test.local:8080/secret.html" Then I get an error 403 - - Scenario: Redirection URL is propagated from restricted page to first factor When I visit "https://public.test.local:8080/secret.html" Then I'm redirected to "https://auth.test.local:8080/?redirect=https%3A%2F%2Fpublic.test.local%3A8080%2Fsecret.html" diff --git a/test/features/regulation.feature b/test/features/regulation.feature index 69e5a69ba..baac96a2b 100644 --- a/test/features/regulation.feature +++ b/test/features/regulation.feature @@ -1,6 +1,6 @@ +@needs-regulation-config Feature: Authelia regulates authentication to avoid brute force - @needs-test-config @need-registered-user-blackhat Scenario: Attacker tries too many authentication in a short period of time and get banned Given I visit "https://auth.test.local:8080/" @@ -18,7 +18,6 @@ Feature: Authelia regulates authentication to avoid brute force And I click on "Sign in" Then I get a notification of type "error" with message "Authentication failed. Please check your credentials." - @needs-test-config @need-registered-user-blackhat Scenario: User is unbanned after a configured amount of time Given I visit "https://auth.test.local:8080/?redirect=https%3A%2F%2Fpublic.test.local%3A8080%2Fsecret.html" diff --git a/test/features/session-timeout.feature b/test/features/session-timeout.feature new file mode 100644 index 000000000..ebf429a42 --- /dev/null +++ b/test/features/session-timeout.feature @@ -0,0 +1,24 @@ +@needs-inactivity-config +Feature: Session is closed after a certain amount of time + + @need-authenticated-user-john + Scenario: An authenticated user is disconnected after a certain inactivity period + Given I have access to: + | url | + | https://public.test.local:8080/secret.html | + When I sleep for 6 seconds + And I visit "https://public.test.local:8080/secret.html" + Then I'm redirected to "https://auth.test.local:8080/?redirect=https%3A%2F%2Fpublic.test.local%3A8080%2Fsecret.html" + + @need-authenticated-user-john + Scenario: An authenticated user is disconnected after session expiration period + Given I have access to: + | url | + | https://public.test.local:8080/secret.html | + When I sleep for 4 seconds + And I visit "https://public.test.local:8080/secret.html" + And I sleep for 4 seconds + And I visit "https://public.test.local:8080/secret.html" + And I sleep for 4 seconds + And I visit "https://public.test.local:8080/secret.html" + Then I'm redirected to "https://auth.test.local:8080/?redirect=https%3A%2F%2Fpublic.test.local%3A8080%2Fsecret.html" \ No newline at end of file diff --git a/test/features/step_definitions/hooks.ts b/test/features/step_definitions/hooks.ts index b8dcddfcd..b2ff9f75a 100644 --- a/test/features/step_definitions/hooks.ts +++ b/test/features/step_definitions/hooks.ts @@ -6,7 +6,7 @@ import { UserDataStore } from "../../../server/src/lib/storage/UserDataStore"; import { CollectionFactoryFactory } from "../../../server/src/lib/storage/CollectionFactoryFactory"; import { MongoConnector } from "../../../server/src/lib/connectors/mongo/MongoConnector"; import { IMongoClient } from "../../../server/src/lib/connectors/mongo/IMongoClient"; -import { TOTPGenerator } from "../../../server/src/lib/TOTPGenerator"; +import { TotpHandler } from "../../../server/src/lib/authentication/totp/TotpHandler"; import Speakeasy = require("speakeasy"); Cucumber.defineSupportCode(function ({ setDefaultTimeout }) { @@ -14,19 +14,46 @@ Cucumber.defineSupportCode(function ({ setDefaultTimeout }) { }); Cucumber.defineSupportCode(function ({ After, Before }) { - const exec = BluebirdPromise.promisify(ChildProcess.exec); + const exec = BluebirdPromise.promisify(ChildProcess.exec); After(function () { return this.driver.quit(); }); - Before({ tags: "@needs-test-config", timeout: 20 * 1000 }, function () { - return exec("./scripts/example-commit/dc-example.sh -f docker-compose.test.yml up -d authelia && sleep 2"); - }); + function createRegulationConfiguration(): BluebirdPromise { + 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({ tags: "@needs-test-config", timeout: 20 * 1000 }, function () { - return exec("./scripts/example-commit/dc-example.sh up -d authelia && sleep 2"); - }); + function createInactivityConfiguration(): BluebirdPromise { + return exec("\ + cat config.template.yml | \ + sed 's/expiration: [0-9]\\+/expiration: 10000/' | \ + sed 's/inactivity: [0-9]\\+/inactivity: 5000/' > config.test.yml \ + "); + } + + function declareNeedsConfiguration(tag: string, cb: () => BluebirdPromise) { + Before({ tags: "@needs-" + tag + "-config", timeout: 20 * 1000 }, function () { + return cb() + .then(function () { + return exec("./scripts/example-commit/dc-example.sh -f 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); function registerUser(context: any, username: string) { let secret: Speakeasy.Key; @@ -36,7 +63,7 @@ Cucumber.defineSupportCode(function ({ After, Before }) { const collectionFactory = CollectionFactoryFactory.createMongo(mongoClient); const userDataStore = new UserDataStore(collectionFactory); - const generator = new TOTPGenerator(Speakeasy); + const generator = new TotpHandler(Speakeasy); secret = generator.generate(); return userDataStore.saveTOTPSecret(username, secret); }) @@ -56,7 +83,10 @@ Cucumber.defineSupportCode(function ({ After, Before }) { } function needAuthenticatedUser(context: any, username: string): BluebirdPromise { - return context.visit("https://auth.test.local:8080/") + return context.visit("https://auth.test.local:8080/logout") + .then(function () { + return context.visit("https://auth.test.local:8080/"); + }) .then(function () { return registerUser(context, username); }) @@ -66,7 +96,7 @@ Cucumber.defineSupportCode(function ({ After, Before }) { .then(function () { return context.useTotpTokenHandle("REGISTERED"); }) - .then(function() { + .then(function () { return context.clickOnButton("TOTP"); }); } diff --git a/test/features/step_definitions/resilience.ts b/test/features/step_definitions/resilience.ts index ad75a1437..509308416 100644 --- a/test/features/step_definitions/resilience.ts +++ b/test/features/step_definitions/resilience.ts @@ -7,6 +7,6 @@ import BluebirdPromise = require("bluebird"); Cucumber.defineSupportCode(function ({ Given, When, Then }) { When(/^the application restarts$/, {timeout: 15 * 1000}, function () { const exec = BluebirdPromise.promisify(ChildProcess.exec); - return exec("./scripts/example-commit/dc-example.sh restart authelia && sleep 2"); + return exec("./scripts/example-commit/dc-example.sh restart authelia && sleep 1"); }); }); \ No newline at end of file diff --git a/test/features/step_definitions/session-timeout.ts b/test/features/step_definitions/session-timeout.ts new file mode 100644 index 000000000..98131274b --- /dev/null +++ b/test/features/step_definitions/session-timeout.ts @@ -0,0 +1,8 @@ +import Cucumber = require("cucumber"); +import seleniumWebdriver = require("selenium-webdriver"); + +Cucumber.defineSupportCode(function ({ Given, When, Then }) { + When("I sleep for {number} seconds", function (seconds: number) { + return this.driver.sleep(seconds * 1000); + }); +}); \ No newline at end of file