diff --git a/client/src/components/FirstFactorForm/FirstFactorForm.tsx b/client/src/components/FirstFactorForm/FirstFactorForm.tsx index b07ff8fb..ec702213 100644 --- a/client/src/components/FirstFactorForm/FirstFactorForm.tsx +++ b/client/src/components/FirstFactorForm/FirstFactorForm.tsx @@ -16,7 +16,7 @@ export interface StateProps { } export interface DispatchProps { - onAuthenticationRequested(username: string, password: string): void; + onAuthenticationRequested(username: string, password: string, rememberMe: boolean): void; } export type Props = StateProps & DispatchProps; @@ -131,7 +131,8 @@ class FirstFactorForm extends Component { private authenticate() { this.props.onAuthenticationRequested( this.state.username, - this.state.password); + this.state.password, + this.state.rememberMe); } } diff --git a/client/src/containers/components/FirstFactorForm/FirstFactorForm.ts b/client/src/containers/components/FirstFactorForm/FirstFactorForm.ts index 6cf4fa27..8ea64a54 100644 --- a/client/src/containers/components/FirstFactorForm/FirstFactorForm.ts +++ b/client/src/containers/components/FirstFactorForm/FirstFactorForm.ts @@ -15,12 +15,12 @@ const mapStateToProps = (state: RootState): StateProps => { } function onAuthenticationRequested(dispatch: Dispatch) { - return async (username: string, password: string) => { + return async (username: string, password: string, rememberMe: boolean) => { let err, res; // Validate first factor dispatch(authenticate()); - [err, res] = await to(AutheliaService.postFirstFactorAuth(username, password)); + [err, res] = await to(AutheliaService.postFirstFactorAuth(username, password, rememberMe)); if (err) { await dispatch(authenticateFailure(err.message)); diff --git a/client/src/services/AutheliaService.ts b/client/src/services/AutheliaService.ts index d9138685..e1338529 100644 --- a/client/src/services/AutheliaService.ts +++ b/client/src/services/AutheliaService.ts @@ -22,7 +22,8 @@ export async function fetchState() { }); } -export async function postFirstFactorAuth(username: string, password: string) { +export async function postFirstFactorAuth(username: string, password: string, + rememberMe: boolean) { return fetchSafe('/api/firstfactor', { method: 'POST', headers: { @@ -32,6 +33,7 @@ export async function postFirstFactorAuth(username: string, password: string) { body: JSON.stringify({ username: username, password: password, + keepMeLoggedIn: rememberMe, }) }); } diff --git a/scripts/authelia-scripts-serve b/scripts/authelia-scripts-serve index a9bec2d5..de3795a0 100755 --- a/scripts/authelia-scripts-serve +++ b/scripts/authelia-scripts-serve @@ -3,6 +3,7 @@ var program = require('commander'); var execSync = require('child_process').execSync; var spawn = require('child_process').spawn; +var fs = require('fs'); program .option('-c, --config ', 'Configuration file to run Authelia with.') @@ -30,10 +31,14 @@ else { server = spawn('/usr/bin/env', ['node', 'dist/server/src/index.js', config]); } +var logStream = fs.createWriteStream('/tmp/authelia-server.log', {flags: 'a'}); + server.stdout.on('data', (data) => { process.stdout.write(`${data}`); }); +server.stdout.pipe(logStream); server.stderr.on('data', (data) => { process.stderr.write(`${data}`); }); +server.stderr.pipe(logStream); diff --git a/server/src/lib/routes/firstfactor/post.ts b/server/src/lib/routes/firstfactor/post.ts index fec32f8a..35b7e84c 100644 --- a/server/src/lib/routes/firstfactor/post.ts +++ b/server/src/lib/routes/firstfactor/post.ts @@ -15,8 +15,7 @@ export default function (vars: ServerVariables) { : BluebirdPromise { const username: string = req.body.username; const password: string = req.body.password; - const keepMeLoggedIn: boolean = req.body.keepMeLoggedIn && - req.body.keepMeLoggedIn === "true"; + const keepMeLoggedIn: boolean = req.body.keepMeLoggedIn; let authSession: AuthenticationSession; if (keepMeLoggedIn) { diff --git a/test/helpers/FillLoginPageAndClick.ts b/test/helpers/FillLoginPageAndClick.ts index cf636153..9ffe5d50 100644 --- a/test/helpers/FillLoginPageAndClick.ts +++ b/test/helpers/FillLoginPageAndClick.ts @@ -10,8 +10,7 @@ export default async function( await driver.findElement(SeleniumWebdriver.By.id("username")).sendKeys(username); await driver.findElement(SeleniumWebdriver.By.id("password")).sendKeys(password); if (keepMeLoggedIn) { - await driver.findElement(SeleniumWebdriver.By.id("keep_me_logged_in")).click(); - return; + await driver.findElement(SeleniumWebdriver.By.id("remember-checkbox")).click(); } await driver.findElement(SeleniumWebdriver.By.tagName("button")).click(); }; \ No newline at end of file diff --git a/test/helpers/context/WithDriver.ts b/test/helpers/context/WithDriver.ts index fa7bbd3a..52148ccd 100644 --- a/test/helpers/context/WithDriver.ts +++ b/test/helpers/context/WithDriver.ts @@ -8,7 +8,7 @@ export default function() { beforeEach(function() { const driver = new SeleniumWebdriver.Builder() .forBrowser("chrome") - .setChromeOptions(new chrome.Options().headless()) + // .setChromeOptions(new chrome.Options().headless()) .build(); this.driver = driver; }); diff --git a/test/inactivity/00-suite.ts b/test/inactivity/00-suite.ts deleted file mode 100644 index 0bb67bf6..00000000 --- a/test/inactivity/00-suite.ts +++ /dev/null @@ -1,37 +0,0 @@ -require("chromedriver"); -import Bluebird = require("bluebird"); -import Configuration = require("../configuration"); -import Environment = require("../environment"); - -import ChildProcess = require('child_process'); -const execAsync = Bluebird.promisify(ChildProcess.exec); - -const includes = [ - "docker-compose.test.yml", - "example/compose/docker-compose.base.yml", - "example/compose/nginx/minimal/docker-compose.yml", - "example/compose/smtp/docker-compose.yml", -] - - -before(function() { - this.timeout(20000); - this.environment = new Environment.Environment(includes); - this.configuration = new Configuration.Configuration(); - - return this.configuration.setup( - "config.minimal.yml", - "config.test.yml", - conf => { - conf.session.inactivity = 2000; - }) - .then(() => execAsync("cp users_database.yml users_database.test.yml")) - .then(() => this.environment.setup(2000)); -}); - -after(function() { - this.timeout(30000); - return this.configuration.cleanup() - .then(() => execAsync("rm users_database.test.yml")) - .then(() => this.environment.cleanup()); -}); \ No newline at end of file diff --git a/test/inactivity/keep_me_logged_in.ts b/test/inactivity/keep_me_logged_in.ts deleted file mode 100644 index 58bfb107..00000000 --- a/test/inactivity/keep_me_logged_in.ts +++ /dev/null @@ -1,48 +0,0 @@ -import Bluebird = require("bluebird"); -import LoginAndRegisterTotp from "../helpers/LoginAndRegisterTotp"; -import VisitPage from "../helpers/VisitPage"; -import FillLoginPageWithUserAndPasswordAndClick from "../helpers/FillLoginPageAndClick"; -import WithDriver from "../helpers/context/WithDriver"; -import ValidateTotp from "../helpers/ValidateTotp"; -import WaitRedirected from "../helpers/WaitRedirected"; - -describe("Keep me logged in", function() { - this.timeout(15000); - WithDriver(); - - before(function() { - const that = this; - return LoginAndRegisterTotp(this.driver, "john", true) - .then(function(secret: string) { - that.secret = secret; - if(!secret) return Bluebird.reject(new Error("No secret!")); - return Bluebird.resolve(); - }); - }); - - it("should disconnect user after inactivity period", function() { - const that = this; - const driver = this.driver; - return VisitPage(driver, "https://login.example.com:8080/?rd=https://admin.example.com:8080/secret.html") - .then(() => FillLoginPageWithUserAndPasswordAndClick(driver, 'john', 'password', false)) - .then(() => ValidateTotp(driver, that.secret)) - .then(() => WaitRedirected(driver, "https://admin.example.com:8080/secret.html")) - .then(() => VisitPage(driver, "https://home.example.com:8080/")) - .then(() => driver.sleep(3000)) - .then(() => driver.get("https://admin.example.com:8080/secret.html")) - .then(() => WaitRedirected(driver, "https://login.example.com:8080/?rd=https://admin.example.com:8080/secret.html")) - }); - - it.only("should keep user logged in after inactivity period", function() { - const that = this; - const driver = this.driver; - return VisitPage(driver, "https://login.example.com:8080/?rd=https://admin.example.com:8080/secret.html") - .then(() => FillLoginPageWithUserAndPasswordAndClick(driver, 'john', 'password', true)) - .then(() => ValidateTotp(driver, that.secret)) - .then(() => WaitRedirected(driver, "https://admin.example.com:8080/secret.html")) - .then(() => VisitPage(driver, "https://home.example.com:8080/")) - .then(() => driver.sleep(5000)) - .then(() => driver.get("https://admin.example.com:8080/secret.html")) - .then(() => WaitRedirected(driver, "https://admin.example.com:8080/secret.html")) - }); -}); \ No newline at end of file diff --git a/test/suites/minimal-config/config.yml b/test/suites/minimal-config/config.yml new file mode 100644 index 00000000..11512290 --- /dev/null +++ b/test/suites/minimal-config/config.yml @@ -0,0 +1,107 @@ +############################################################### +# Authelia minimal configuration # +############################################################### + +port: 9091 + +logs_level: debug + +authentication_backend: + file: + path: ./users_database.yml + +session: + secret: unsecure_session_secret + domain: example.com + inactivity: 5000 + +# Configuration of the storage backend used to store data and secrets. i.e. totp data +storage: + local: + path: /var/lib/authelia + +# TOTP Issuer Name +# +# This will be the issuer name displayed in Google Authenticator +# See: https://github.com/google/google-authenticator/wiki/Key-Uri-Format for more info on issuer names +totp: + issuer: example.com + +# Access Control +# +# Access control is a set of rules you can use to restrict user access to certain +# resources. +access_control: + # Default policy can either be `bypass`, `one_factor`, `two_factor` or `deny`. + default_policy: deny + + rules: + - domain: single_factor.example.com + policy: one_factor + + - domain: '*.example.com' + subject: "group:admins" + policy: two_factor + + - domain: dev.example.com + resources: + - '^/users/john/.*$' + subject: "user:john" + policy: two_factor + + - domain: dev.example.com + resources: + - '^/users/harry/.*$' + subject: "user:harry" + policy: two_factor + + - domain: '*.mail.example.com' + subject: "user:bob" + policy: two_factor + + - domain: dev.example.com + resources: + - '^/users/bob/.*$' + subject: "user:bob" + policy: two_factor + + +# Configuration of the authentication regulation mechanism. +regulation: + # Set it to 0 to disable max_retries. + max_retries: 3 + + # The user is banned if the authenticaction failed `max_retries` times in a `find_time` seconds window. + find_time: 120 + + # The length of time before a banned user can login again. + ban_time: 300 + +# Default redirection URL +# +# Note: this parameter is optional. If not provided, user won't +# be redirected upon successful authentication. +#default_redirection_url: https://authelia.example.domain + +notifier: + # For testing purpose, notifications can be sent in a file + # filesystem: + # filename: /tmp/authelia/notification.txt + + # 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: password + secure: false + host: 127.0.0.1 + port: 1025 + sender: admin@example.com + diff --git a/test/suites/minimal-config/index.ts b/test/suites/minimal-config/index.ts index ffe7649c..d986f3e7 100644 --- a/test/suites/minimal-config/index.ts +++ b/test/suites/minimal-config/index.ts @@ -6,10 +6,11 @@ import BadPassword from "./scenarii/BadPassword"; import RegisterTotp from './scenarii/RegisterTotp'; import ResetPassword from './scenarii/ResetPassword'; import TOTPValidation from './scenarii/TOTPValidation'; +import Inactivity from './scenarii/Inactivity'; const execAsync = Bluebird.promisify(ChildProcess.exec); -AutheliaSuite('Minimal configuration', 'config.minimal.yml', function() { +AutheliaSuite('Minimal configuration', __dirname + '/config.yml', function() { this.timeout(10000); beforeEach(function() { return execAsync("cp users_database.example.yml users_database.yml"); @@ -20,4 +21,6 @@ AutheliaSuite('Minimal configuration', 'config.minimal.yml', function() { describe('TOTP Registration', RegisterTotp); describe('TOTP Validation', TOTPValidation); + + describe('Inactivity period', Inactivity); }); \ No newline at end of file diff --git a/test/suites/minimal-config/scenarii/Inactivity.ts b/test/suites/minimal-config/scenarii/Inactivity.ts new file mode 100644 index 00000000..959bbfde --- /dev/null +++ b/test/suites/minimal-config/scenarii/Inactivity.ts @@ -0,0 +1,38 @@ +import Bluebird = require("bluebird"); +import LoginAndRegisterTotp from "../../../helpers/LoginAndRegisterTotp"; +import VisitPage from "../../../helpers/VisitPage"; +import FillLoginPageWithUserAndPasswordAndClick from "../../../helpers/FillLoginPageAndClick"; +import ValidateTotp from "../../../helpers/ValidateTotp"; +import WaitRedirected from "../../../helpers/WaitRedirected"; + +export default function(this: Mocha.ISuiteCallbackContext) { + this.timeout(15000); + + beforeEach(async function() { + this.secret = await LoginAndRegisterTotp(this.driver, "john", true); + }); + + it("should disconnect user after inactivity period", async function() { + const driver = this.driver; + await VisitPage(driver, "https://login.example.com:8080/?rd=https://admin.example.com:8080/secret.html"); + await FillLoginPageWithUserAndPasswordAndClick(driver, 'john', 'password', false); + await ValidateTotp(driver, this.secret); + await WaitRedirected(driver, "https://admin.example.com:8080/secret.html"); + await VisitPage(driver, "https://home.example.com:8080/"); + await driver.sleep(6000); + await driver.get("https://admin.example.com:8080/secret.html"); + await WaitRedirected(driver, "https://login.example.com:8080/?rd=https://admin.example.com:8080/secret.html"); + }); + + it("should keep user logged in after inactivity period", async function() { + const driver = this.driver; + await VisitPage(driver, "https://login.example.com:8080/?rd=https://admin.example.com:8080/secret.html"); + await FillLoginPageWithUserAndPasswordAndClick(driver, 'john', 'password', true); + await ValidateTotp(driver, this.secret); + await WaitRedirected(driver, "https://admin.example.com:8080/secret.html"); + await VisitPage(driver, "https://home.example.com:8080/"); + await driver.sleep(6000); + await driver.get("https://admin.example.com:8080/secret.html"); + await WaitRedirected(driver, "https://admin.example.com:8080/secret.html"); + }); +} \ No newline at end of file