diff --git a/src/.gitignore b/src/.gitignore index 29988b9e..e7357141 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -1,23 +1,24 @@ *.swp database/database.sqlite node_modules/ package-lock.json public/css/*.css public/hot public/js/*.js public/storage/ storage/*.key storage/export/ tests/report/ vendor .env .env.backup .env.local .env.testing .phpunit.result.cache Homestead.json Homestead.yaml npm-debug.log yarn-error.log composer.lock resources/countries.php +resources/js/ts.js diff --git a/src/resources/build/before.php b/src/resources/build/before.php new file mode 100644 index 00000000..59a95cc4 --- /dev/null +++ b/src/resources/build/before.php @@ -0,0 +1,9 @@ + diff --git a/src/tests/Browser/LogonTest.php b/src/tests/Browser/LogonTest.php index b1cae78a..def8c463 100644 --- a/src/tests/Browser/LogonTest.php +++ b/src/tests/Browser/LogonTest.php @@ -1,238 +1,239 @@ browse(function (Browser $browser) { $browser->visit(new Home()) ->within(new Menu(), function ($browser) { - $browser->assertMenuItems(['signup', 'explore', 'blog', 'support', 'login']); + $browser->assertMenuItems(['signup', 'explore', 'blog', 'support', 'login']) + ->assertSeeIn('#footer-copyright', '@ Apheleia IT AG, ' . date('Y')); }); if ($browser->isDesktop()) { $browser->within(new Menu('footer'), function ($browser) { $browser->assertMenuItems(['signup', 'explore', 'blog', 'support', 'tos', 'login']); }); } else { $browser->assertMissing('#footer-menu .navbar-nav'); } $browser->assertSeeLink('Forgot password?') ->assertSeeLink('Webmail'); }); } /** * Test redirect to /login if user is unauthenticated */ public function testRequiredAuth(): void { $this->browse(function (Browser $browser) { $browser->visit('/dashboard'); // Checks if we're really on the login page $browser->waitForLocation('/login') ->on(new Home()); }); } /** * Logon with wrong password/user test */ public function testLogonWrongCredentials(): void { $this->browse(function (Browser $browser) { $browser->visit(new Home()) ->submitLogon('john@kolab.org', 'wrong'); // Error message $browser->assertToast(Toast::TYPE_ERROR, 'Invalid username or password.'); // Checks if we're still on the logon page $browser->on(new Home()); }); } /** * Successful logon test */ public function testLogonSuccessful(): void { $this->browse(function (Browser $browser) { $browser->visit(new Home()) ->submitLogon('john@kolab.org', 'simple123', true) // Checks if we're really on Dashboard page ->on(new Dashboard()) ->assertVisible('@links a.link-profile') ->assertVisible('@links a.link-domains') ->assertVisible('@links a.link-users') ->assertVisible('@links a.link-wallet') ->assertVisible('@links a.link-webmail') ->within(new Menu(), function ($browser) { $browser->assertMenuItems(['explore', 'blog', 'support', 'dashboard', 'logout']); }); if ($browser->isDesktop()) { $browser->within(new Menu('footer'), function ($browser) { $browser->assertMenuItems(['explore', 'blog', 'support', 'tos', 'dashboard', 'logout']); }); } else { $browser->assertMissing('#footer-menu .navbar-nav'); } $browser->assertUser('john@kolab.org'); // Assert no "Account status" for this account $browser->assertMissing('@status'); // Goto /domains and assert that the link on logo element // leads to the dashboard $browser->visit('/domains') ->waitForText('Domains') ->click('a.navbar-brand') ->on(new Dashboard()); // Test that visiting '/' with logged in user does not open logon form // but "redirects" to the dashboard $browser->visit('/') ->waitForLocation('/dashboard') ->on(new Dashboard()); }); } /** * Logout test * * @depends testLogonSuccessful */ public function testLogout(): void { $this->browse(function (Browser $browser) { $browser->on(new Dashboard()); // Click the Logout button $browser->within(new Menu(), function ($browser) { $browser->clickMenuItem('logout'); }); // We expect the logon page $browser->waitForLocation('/login') ->on(new Home()); // with default menu $browser->within(new Menu(), function ($browser) { $browser->assertMenuItems(['signup', 'explore', 'blog', 'support', 'login']); }); // Success toast message $browser->assertToast(Toast::TYPE_SUCCESS, 'Successfully logged out'); }); } /** * Logout by URL test */ public function testLogoutByURL(): void { $this->browse(function (Browser $browser) { $browser->visit(new Home()) ->submitLogon('john@kolab.org', 'simple123', true); // Checks if we're really on Dashboard page $browser->on(new Dashboard()); // Use /logout url, and expect the logon page $browser->visit('/logout') ->waitForLocation('/login') ->on(new Home()); // with default menu $browser->within(new Menu(), function ($browser) { $browser->assertMenuItems(['signup', 'explore', 'blog', 'support', 'login']); }); // Success toast message $browser->assertToast(Toast::TYPE_SUCCESS, 'Successfully logged out'); }); } /** * Test 2-Factor Authentication * * @depends testLogoutByURL */ public function test2FA(): void { $this->browse(function (Browser $browser) { // Test missing 2fa code $browser->on(new Home()) ->type('@email-input', 'ned@kolab.org') ->type('@password-input', 'simple123') ->press('form button') ->waitFor('@second-factor-input.is-invalid + .invalid-feedback') ->assertSeeIn( '@second-factor-input.is-invalid + .invalid-feedback', 'Second factor code is required.' ) ->assertFocused('@second-factor-input') ->assertToast(Toast::TYPE_ERROR, 'Form validation error'); // Test invalid code $browser->type('@second-factor-input', '123456') ->press('form button') ->waitUntilMissing('@second-factor-input.is-invalid') ->waitFor('@second-factor-input.is-invalid + .invalid-feedback') ->assertSeeIn( '@second-factor-input.is-invalid + .invalid-feedback', 'Second factor code is invalid.' ) ->assertFocused('@second-factor-input') ->assertToast(Toast::TYPE_ERROR, 'Form validation error'); $code = \App\Auth\SecondFactor::code('ned@kolab.org'); // Test valid (TOTP) code $browser->type('@second-factor-input', $code) ->press('form button') ->waitUntilMissing('@second-factor-input.is-invalid') ->waitForLocation('/dashboard') ->on(new Dashboard()); }); } /** * Test redirect to the requested page after logon * * @depends test2FA */ public function testAfterLogonRedirect(): void { $this->browse(function (Browser $browser) { // User is logged in $browser->visit(new UserProfile()); // Test redirect if the token is invalid $browser->script("localStorage.setItem('token', '123')"); $browser->refresh() ->on(new Home()) ->submitLogon('john@kolab.org', 'simple123', false) ->waitForLocation('/profile'); }); } } diff --git a/src/webpack.mix.js b/src/webpack.mix.js index 5599af72..056e0f3e 100644 --- a/src/webpack.mix.js +++ b/src/webpack.mix.js @@ -1,39 +1,44 @@ /* |-------------------------------------------------------------------------- | Mix Asset Management |-------------------------------------------------------------------------- | | Mix provides a clean, fluent API for defining some Webpack build steps | for your Laravel application. By default, we are compiling the Sass | file for the application as well as bundling up all the JS files. | */ +const { exec } = require('child_process'); const fs = require('fs'); const glob = require('glob'); const mix = require('laravel-mix'); mix.webpackConfig({ resolve: { alias: { 'jquery$': 'jquery/dist/jquery.slim.js', } } }) +mix.before(() => { + exec('php resources/build/before.php') +}) + mix.js('resources/js/user.js', 'public/js').vue() .js('resources/js/admin.js', 'public/js').vue() glob.sync('resources/themes/*/', {}).forEach(fromDir => { const toDir = fromDir.replace('resources/themes/', 'public/themes/') mix.sass(fromDir + 'app.scss', toDir) .sass(fromDir + 'document.scss', toDir); fs.stat(fromDir + 'images', {}, (err, stats) => { if (stats) { mix.copyDirectory(fromDir + 'images', toDir + 'images') } }) })