diff --git a/.env.example b/.env.example index 968dba7a9d6..5bf6c28aed7 100644 --- a/.env.example +++ b/.env.example @@ -98,3 +98,6 @@ AWS_SECRET= AWS_REGION=us-east-1 AWS_BUCKET= AWS_SERVER= + +# Enable Two Factor Authentication +2FA_ENABLED=false diff --git a/.env.travis b/.env.travis index abcb881ecc3..4fe186d8863 100644 --- a/.env.travis +++ b/.env.travis @@ -10,3 +10,5 @@ DB_TEST_PASSWORD= CACHE_DRIVER=array SESSION_DRIVER=array QUEUE_DRIVER=sync + +2FA_ENABLED=false diff --git a/.gitignore b/.gitignore index a9da65036f3..b21a91426d8 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ monica.sublime-workspace /storage/oauth-private.key /storage/oauth-public.key .DS_Store +npm-debug.log.* diff --git a/CHANGELOG b/CHANGELOG index 6d8de9a309b..162a26540d5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,7 @@ UNRELEASED CHANGES: * Display missing page when loading a contact that does not exist * Add ability to filter contacts by more than one tag * Change the structure of the dashboard +* Add two factor authentication ability RELEASED VERSIONS: diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 41a6dbb4327..649a83b7524 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -24,4 +24,4 @@ Scott Williams @scott-joe Craig Davison @davisonio Michael Heap @mheap Matthew Du Pont @mattdp -Alexis Saettler @asbiin +Alexis Saettler @asbiin diff --git a/app/Console/Commands/Deactivate2FA.php b/app/Console/Commands/Deactivate2FA.php new file mode 100644 index 00000000000..95526b2e18e --- /dev/null +++ b/app/Console/Commands/Deactivate2FA.php @@ -0,0 +1,83 @@ +option('email'); + + // if no email was passed to the option, prompt the user to enter the email + if (! $email) { + $email = $this->ask('what is the user\'s email?'); + } + + // retrieve the user with the specified email + $user = User::where('email', $email)->first(); + + if (! $user) { + // show an error and exist if the user does not exist + $this->error('No user with that email.'); + + return; + } + if (is_null($user->google2fa_secret)) { + // show an error and exist if the user does not exist + $this->error('2FA is currently not activated for this user.'); + + return; + } + + // Print a warning + $this->info('2FA will be deactivated for '.$user->email); + $this->info('This action can\'t be cancelled.'); + + // ask for confirmation if not forced + if (! $this->option('force') && ! $this->confirm('Do you wish to continue?')) { + return; + } + + // remove google2fa_secret key + $user->google2fa_secret = null; + + // save the user + $user->save(); + + // show the new secret key + $this->info('2FA has been deactivated for '.$user->email); + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index b2c5b7d0442..7158ca5db37 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -21,6 +21,7 @@ class Kernel extends ConsoleKernel 'App\Console\Commands\ImportVCards', 'App\Console\Commands\PingVersionServer', 'App\Console\Commands\SetupTest', + 'App\Console\Commands\Deactivate2FA', 'App\Console\Commands\GetVersion', ]; diff --git a/app/Http/Controllers/Auth/PasswordChangeController.php b/app/Http/Controllers/Auth/PasswordChangeController.php index e888596f7d8..96638fe3f96 100644 --- a/app/Http/Controllers/Auth/PasswordChangeController.php +++ b/app/Http/Controllers/Auth/PasswordChangeController.php @@ -17,7 +17,7 @@ class PasswordChangeController extends Controller { use RedirectsUsers; - protected $redirectTo = '/settings'; + protected $redirectTo = '/settings/security'; /** * Get usefull parameters from request. diff --git a/app/Http/Controllers/Settings/MultiFAController.php b/app/Http/Controllers/Settings/MultiFAController.php new file mode 100644 index 00000000000..eb16866855d --- /dev/null +++ b/app/Http/Controllers/Settings/MultiFAController.php @@ -0,0 +1,144 @@ +middleware('web'); + } + + /** + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + public function enableTwoFactor(Request $request) + { + //generate new secret + $secret = $this->generateSecret(); + + $user = $request->user(); + + //generate image for QR barcode + $imageDataUri = Google2FA::getQRCodeInline( + $request->getHttpHost(), + $user->email, + $secret, + 200 + ); + + $request->session()->put(self::SESSION_TFA_SECRET, $secret); + + return view('settings.security.2fa-enable', ['image' => $imageDataUri, 'secret' => $secret]); + } + + /** + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + public function validateTwoFactor(Request $request) + { + $this->validate($request, [ + 'one_time_password' => 'required', + ]); + + //retrieve secret + $secret = $request->session()->pull(self::SESSION_TFA_SECRET); + + $authenticator = app(Authenticator::class)->boot($request); + + if ($authenticator->verifyGoogle2FA($secret, $request['one_time_password'])) { + //get user + $user = $request->user(); + + //encrypt and then save secret + $user->google2fa_secret = $secret; + $user->save(); + + $authenticator->login(); + + return redirect($this->redirectPath()) + ->with('status', trans('settings.2fa_enable_success')); + } + + $authenticator->logout(); + + return redirect($this->redirectPath()) + ->withErrors(trans('settings.2fa_enable_error')); + } + + /** + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + public function disableTwoFactor(Request $request) + { + return view('settings.security.2fa-disable'); + } + + /** + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + public function deactivateTwoFactor(Request $request) + { + $this->validate($request, [ + 'one_time_password' => 'required', + ]); + + $user = $request->user(); + + //retrieve secret + $secret = $user->google2fa_secret; + + $authenticator = app(Authenticator::class)->boot($request); + + if ($authenticator->verifyGoogle2FA($secret, $request['one_time_password'])) { + + //make secret column blank + $user->google2fa_secret = null; + $user->save(); + + $authenticator->logout(); + + return redirect($this->redirectPath()) + ->with('status', trans('settings.2fa_disable_success')); + } + + return redirect($this->redirectPath()) + ->withErrors(trans('settings.2fa_disable_error')); + } + + /** + * Generate a secret key in Base32 format. + * + * @return string + */ + private function generateSecret() + { + $google2fa = app('pragmarx.google2fa'); + + return $google2fa->generateSecretKey(32); + } +} diff --git a/app/Http/Controllers/SettingsController.php b/app/Http/Controllers/SettingsController.php index 00dc88a4da4..6aecfc57991 100644 --- a/app/Http/Controllers/SettingsController.php +++ b/app/Http/Controllers/SettingsController.php @@ -18,6 +18,7 @@ use App\Http\Requests\SettingsRequest; use Illuminate\Support\Facades\Storage; use App\Http\Requests\InvitationRequest; +use PragmaRX\Google2FALaravel\Support\Authenticator; class SettingsController extends Controller { @@ -430,4 +431,9 @@ public function api() { return view('settings.api.index'); } + + public function security(Request $request) + { + return view('settings.security.index', ['is2FAActivated' => (new Authenticator($request))->isActivated()]); + } } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index fc226b47c70..2a3133f44fd 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -56,5 +56,6 @@ class Kernel extends HttpKernel //'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'throttle' => \App\Http\Middleware\ThrottleRequestsMiddleware::class, 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, + '2fa' => \PragmaRX\Google2FALaravel\Middleware::class, ]; } diff --git a/app/User.php b/app/User.php index f71e149fe27..7ce7ea0ec81 100644 --- a/app/User.php +++ b/app/User.php @@ -18,7 +18,7 @@ class User extends Authenticatable * @var array */ protected $fillable = [ - 'first_name', 'last_name', 'email', 'password', 'timezone', 'locale', 'currency_id', 'fluid_container', 'name_order', + 'first_name', 'last_name', 'email', 'password', 'timezone', 'locale', 'currency_id', 'fluid_container', 'name_order', 'google2fa_secret', ]; /** @@ -32,7 +32,7 @@ class User extends Authenticatable * @var array */ protected $hidden = [ - 'password', 'remember_token', + 'password', 'remember_token', 'google2fa_secret', ]; /** @@ -145,4 +145,30 @@ public function hasAlreadyRatedToday() return true; } + + /** + * Ecrypt the user's google_2fa secret. + * + * @param string $value + * @return string + */ + public function setGoogle2faSecretAttribute($value) + { + $this->attributes['google2fa_secret'] = encrypt($value); + } + + /** + * Decrypt the user's google_2fa secret. + * + * @param string $value + * @return string + */ + public function getGoogle2faSecretAttribute($value) + { + if (is_null($value)) { + return $value; + } + + return decrypt($value); + } } diff --git a/composer.json b/composer.json index ebd1095e876..81379e57720 100644 --- a/composer.json +++ b/composer.json @@ -4,26 +4,37 @@ "license": "AGPL", "keywords": ["prm", "crm", "social"], "type": "project", + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/asbiin/google2fa-laravel" + } + ], "require": { "php": ">=7.0.0", - "laravel/framework": "5.5.*", - "barryvdh/laravel-debugbar": "^2.2", - "jenssegers/date": "^3.2", "ext-intl": "*", - "predis/predis": "^1.1", - "guzzlehttp/guzzle": "^6.2", - "fzaninotto/faker": "^1.6", + "bacon/bacon-qr-code": "^1.0", + "barryvdh/laravel-debugbar": "^2.2", "doctrine/dbal": "^2.5", - "laravel/socialite": "^3.0", - "intervention/image": "^2.3", - "sabre/vobject": "^4.1", "erusev/parsedown": "~1.6", + "fzaninotto/faker": "^1.6", + "guzzlehttp/guzzle": "^6.2", + "intervention/image": "^2.3", + "jenssegers/date": "^3.2", "laravel/cashier": "~7.0", - "roave/security-advisories": "dev-master", "laravel/dusk": "^1.1", - "sentry/sentry-laravel": "^0.7.0", + "laravel/framework": "5.5.*", + "laravel/passport": "^4.0", + "laravel/socialite": "^3.0", "league/flysystem-aws-s3-v3": "~1.0", - "laravel/passport": "^4.0" + "paragonie/constant_time_encoding": "^2.2", + "pragmarx/google2fa": "^2.0", + "pragmarx/google2fa-laravel": "dev-create-authenticator-controller", + "pragmarx/recovery": "^0.1.0", + "predis/predis": "^1.1", + "roave/security-advisories": "dev-master", + "sabre/vobject": "^4.1", + "sentry/sentry-laravel": "^0.7.0" }, "require-dev": { "mockery/mockery": "0.9.*", diff --git a/composer.lock b/composer.lock index 604e6275384..91224810c8b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "de06ea7a7c43315808128d747ca21d33", + "content-hash": "3d8fc58c2a5cb5bdbcbdca5f0ce810b1", "packages": [ { "name": "aws/aws-sdk-php", @@ -86,6 +86,52 @@ ], "time": "2018-01-05T22:21:07+00:00" }, + { + "name": "bacon/bacon-qr-code", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/Bacon/BaconQrCode.git", + "reference": "5a91b62b9d37cee635bbf8d553f4546057250bee" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/5a91b62b9d37cee635bbf8d553f4546057250bee", + "reference": "5a91b62b9d37cee635bbf8d553f4546057250bee", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": "^5.4|^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8" + }, + "suggest": { + "ext-gd": "to generate QR code images" + }, + "type": "library", + "autoload": { + "psr-0": { + "BaconQrCode": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Ben Scholzen 'DASPRiD'", + "email": "mail@dasprids.de", + "homepage": "http://www.dasprids.de", + "role": "Developer" + } + ], + "description": "BaconQrCode is a QR code generator for PHP.", + "homepage": "https://github.com/Bacon/BaconQrCode", + "time": "2017-10-17T09:59:25+00:00" + }, { "name": "barryvdh/laravel-debugbar", "version": "v2.4.3", @@ -2352,6 +2398,68 @@ ], "time": "2017-01-16T07:55:07+00:00" }, + { + "name": "paragonie/constant_time_encoding", + "version": "v2.2.0", + "source": { + "type": "git", + "url": "https://github.com/paragonie/constant_time_encoding.git", + "reference": "9e7d88e6e4015c2f06a3fa22f06e1d5faa77e6c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/9e7d88e6e4015c2f06a3fa22f06e1d5faa77e6c4", + "reference": "9e7d88e6e4015c2f06a3fa22f06e1d5faa77e6c4", + "shasum": "" + }, + "require": { + "php": "^7" + }, + "require-dev": { + "phpunit/phpunit": "^6", + "vimeo/psalm": "^0.3|^1" + }, + "type": "library", + "autoload": { + "psr-4": { + "ParagonIE\\ConstantTime\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com", + "role": "Maintainer" + }, + { + "name": "Steve 'Sc00bz' Thomas", + "email": "steve@tobtu.com", + "homepage": "https://www.tobtu.com", + "role": "Original Developer" + } + ], + "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)", + "keywords": [ + "base16", + "base32", + "base32_decode", + "base32_encode", + "base64", + "base64_decode", + "base64_encode", + "bin2hex", + "encoding", + "hex", + "hex2bin", + "rfc4648" + ], + "time": "2017-09-22T14:55:37+00:00" + }, { "name": "paragonie/random_compat", "version": "v2.0.11", @@ -2569,6 +2677,267 @@ ], "time": "2017-11-29T06:38:08+00:00" }, + { + "name": "pragmarx/google2fa", + "version": "v2.0.7", + "source": { + "type": "git", + "url": "https://github.com/antonioribeiro/google2fa.git", + "reference": "5a818bda62fab0c0a79060b06d50d50b5525d631" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/5a818bda62fab0c0a79060b06d50d50b5525d631", + "reference": "5a818bda62fab0c0a79060b06d50d50b5525d631", + "shasum": "" + }, + "require": { + "paragonie/constant_time_encoding": "~1.0|~2.0", + "paragonie/random_compat": "~1.4|~2.0", + "php": ">=5.4", + "symfony/polyfill-php56": "~1.2" + }, + "require-dev": { + "bacon/bacon-qr-code": "~1.0", + "phpunit/phpunit": "~4|~5|~6" + }, + "suggest": { + "bacon/bacon-qr-code": "Required to generate inline QR Codes." + }, + "type": "library", + "extra": { + "component": "package", + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "PragmaRX\\Google2FA\\": "src/", + "PragmaRX\\Google2FA\\Tests\\": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Antonio Carlos Ribeiro", + "email": "acr@antoniocarlosribeiro.com", + "role": "Creator & Designer" + } + ], + "description": "A One Time Password Authentication package, compatible with Google Authenticator.", + "keywords": [ + "2fa", + "Authentication", + "Two Factor Authentication", + "google2fa", + "laravel" + ], + "time": "2018-01-06T16:21:07+00:00" + }, + { + "name": "pragmarx/google2fa-laravel", + "version": "dev-create-authenticator-controller", + "source": { + "type": "git", + "url": "https://github.com/asbiin/google2fa-laravel.git", + "reference": "8ac00145615458ca3d019def267ae57d96932948" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/asbiin/google2fa-laravel/zipball/8ac00145615458ca3d019def267ae57d96932948", + "reference": "8ac00145615458ca3d019def267ae57d96932948", + "shasum": "" + }, + "require": { + "laravel/framework": ">=5.2", + "php": ">=5.4", + "pragmarx/google2fa": "~2.0" + }, + "require-dev": { + "benconstable/phpspec-laravel": "~3.0", + "phpspec/phpspec": "~3" + }, + "suggest": { + "bacon/bacon-qr-code": "Required to generate inline QR Codes.", + "pragmarx/recovery": "Generate recovery codes." + }, + "type": "library", + "extra": { + "component": "package", + "frameworks": [ + "Laravel" + ], + "branch-alias": { + "dev-master": "0.1-dev" + }, + "laravel": { + "providers": [ + "PragmaRX\\Google2FALaravel\\ServiceProvider" + ], + "aliases": { + "Google2FA": "PragmaRX\\Google2FALaravel\\Facade" + } + } + }, + "autoload": { + "psr-4": { + "PragmaRX\\Google2FALaravel\\": "src/", + "spec\\PragmaRX\\Google2FALaravel\\": "tests/spec/" + } + }, + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Antonio Carlos Ribeiro", + "email": "acr@antoniocarlosribeiro.com", + "role": "Creator & Designer" + } + ], + "description": "A One Time Password Authentication package, compatible with Google Authenticator.", + "keywords": [ + "authentication", + "google2fa", + "laravel", + "two factor authentication" + ], + "support": { + "source": "https://github.com/asbiin/google2fa-laravel/tree/create-authenticator-controller" + }, + "time": "2017-12-27T12:15:03+00:00" + }, + { + "name": "pragmarx/random", + "version": "v0.2.2", + "source": { + "type": "git", + "url": "https://github.com/antonioribeiro/random.git", + "reference": "daf08a189c5d2d40d1a827db46364d3a741a51b7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/antonioribeiro/random/zipball/daf08a189c5d2d40d1a827db46364d3a741a51b7", + "reference": "daf08a189c5d2d40d1a827db46364d3a741a51b7", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "fzaninotto/faker": "~1.7", + "phpunit/phpunit": "~6.4", + "pragmarx/trivia": "~0.1", + "squizlabs/php_codesniffer": "^2.3" + }, + "suggest": { + "fzaninotto/faker": "Allows you to get dozens of randomized types", + "pragmarx/trivia": "For the trivia database" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "PragmaRX\\Random\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Antonio Carlos Ribeiro", + "email": "acr@antoniocarlosribeiro.com", + "homepage": "https://antoniocarlosribeiro.com", + "role": "Developer" + } + ], + "description": "Create random chars, numbers, strings", + "homepage": "https://github.com/antonioribeiro/random", + "keywords": [ + "Randomize", + "faker", + "pragmarx", + "random", + "random number", + "random pattern", + "random string" + ], + "time": "2017-11-21T05:26:22+00:00" + }, + { + "name": "pragmarx/recovery", + "version": "v0.1.0", + "source": { + "type": "git", + "url": "https://github.com/antonioribeiro/recovery.git", + "reference": "e16573a1ae5345cc3b100eec6d0296a1a15a90fe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/antonioribeiro/recovery/zipball/e16573a1ae5345cc3b100eec6d0296a1a15a90fe", + "reference": "e16573a1ae5345cc3b100eec6d0296a1a15a90fe", + "shasum": "" + }, + "require": { + "php": "~7.0", + "pragmarx/random": "~0.1" + }, + "require-dev": { + "phpunit/phpunit": ">=5.4.3", + "squizlabs/php_codesniffer": "^2.3", + "tightenco/collect": "^5" + }, + "suggest": { + "tightenco/collect": "Allows to generate recovery codes as collections" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "PragmaRX\\Recovery\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Antonio Carlos Ribeiro", + "email": "acr@antoniocarlosribeiro.com", + "homepage": "https://antoniocarlosribeiro.com", + "role": "Developer" + } + ], + "description": "Create recovery codes for two factor auth", + "homepage": "https://github.com/antonioribeiro/recovery", + "keywords": [ + "2fa", + "account recovery", + "auth", + "backup codes", + "google2fa", + "pragmarx", + "recovery", + "recovery codes", + "two factor auth" + ], + "time": "2017-09-19T16:58:00+00:00" + }, { "name": "predis/predis", "version": "v1.1.1", @@ -2901,12 +3270,12 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "21b3900483e9bc1536a298bc3eb3d647ab56d8a1" + "reference": "fe89fd2178cabef137b9a76fa91d64925474b956" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/21b3900483e9bc1536a298bc3eb3d647ab56d8a1", - "reference": "21b3900483e9bc1536a298bc3eb3d647ab56d8a1", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/fe89fd2178cabef137b9a76fa91d64925474b956", + "reference": "fe89fd2178cabef137b9a76fa91d64925474b956", "shasum": "" }, "conflict": { @@ -2989,7 +3358,7 @@ "symfony/web-profiler-bundle": ">=2,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4", "symfony/yaml": ">=2,<2.0.22|>=2.1,<2.1.7", "thelia/backoffice-default-template": ">=2.1,<2.1.2", - "thelia/thelia": ">=2.1,<2.1.2|>=2.1.0-beta1,<2.1.3", + "thelia/thelia": ">=2.1.0-beta1,<2.1.3|>=2.1,<2.1.2", "twig/twig": "<1.20", "typo3/cms": ">=6.2,<6.2.30|>=7,<7.6.22|>=8,<8.7.5", "typo3/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.10|>=3.1,<3.1.7|>=3.2,<3.2.7|>=3.3,<3.3.5", @@ -3038,7 +3407,7 @@ } ], "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it", - "time": "2018-01-03T11:17:07+00:00" + "time": "2018-01-07T00:56:33+00:00" }, { "name": "sabberworm/php-css-parser", @@ -4014,6 +4383,62 @@ ], "time": "2017-10-11T12:05:26+00:00" }, + { + "name": "symfony/polyfill-php56", + "version": "v1.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php56.git", + "reference": "265fc96795492430762c29be291a371494ba3a5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/265fc96795492430762c29be291a371494ba3a5b", + "reference": "265fc96795492430762c29be291a371494ba3a5b", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/polyfill-util": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php56\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 5.6+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2017-10-11T12:05:26+00:00" + }, { "name": "symfony/polyfill-php70", "version": "v1.6.0", @@ -4073,6 +4498,58 @@ ], "time": "2017-10-11T12:05:26+00:00" }, + { + "name": "symfony/polyfill-util", + "version": "v1.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-util.git", + "reference": "6e719200c8e540e0c0effeb31f96bdb344b94176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/6e719200c8e540e0c0effeb31f96bdb344b94176", + "reference": "6e719200c8e540e0c0effeb31f96bdb344b94176", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Util\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony utilities for portability of PHP codes", + "homepage": "https://symfony.com", + "keywords": [ + "compat", + "compatibility", + "polyfill", + "shim" + ], + "time": "2017-10-11T12:05:26+00:00" + }, { "name": "symfony/process", "version": "v3.4.3", @@ -6236,6 +6713,7 @@ "aliases": [], "minimum-stability": "stable", "stability-flags": { + "pragmarx/google2fa-laravel": 20, "roave/security-advisories": 20 }, "prefer-stable": false, diff --git a/config/google2fa.php b/config/google2fa.php new file mode 100644 index 00000000000..b013b430f7f --- /dev/null +++ b/config/google2fa.php @@ -0,0 +1,68 @@ + env('2FA_ENABLED', false), + + /* + * Lifetime in minutes. + * In case you need your users to be asked for a new one time passwords from time to time. + */ + + 'lifetime' => 0, // 0 = eternal + + /* + * Renew lifetime at every new request. + */ + + 'keep_alive' => true, + + /* + * Auth container binding + */ + + 'auth' => 'auth', + + /* + * 2FA verified session var + */ + + 'session_var' => 'google2fa', + + /* + * One Time Password request input name + */ + 'otp_input' => 'one_time_password', + + /* + * One Time Password Window + */ + 'window' => 8, + + /* + * Forbid user to reuse One Time Passwords. + */ + 'forbid_old_passwords' => false, + + /* + * User's table column for google2fa secret + */ + 'otp_secret_column' => 'google2fa_secret', + + /* + * One Time Password View + */ + 'view' => 'auth/validate2fa', + + /* + * One Time Password error message + */ + 'error_messages' => [ + 'wrong_otp' => 'The two factor authentication has failed.', + ], + +]; diff --git a/database/migrations/2017_12_21_170327_add_google2fa_secret_to_users.php b/database/migrations/2017_12_21_170327_add_google2fa_secret_to_users.php new file mode 100644 index 00000000000..71f0969dbed --- /dev/null +++ b/database/migrations/2017_12_21_170327_add_google2fa_secret_to_users.php @@ -0,0 +1,31 @@ +string('google2fa_secret')->after('remember_token')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('users', function ($table) { + $table->dropColumn('google2fa_secret'); + }); + } +} diff --git a/resources/assets/sass/marketing.scss b/resources/assets/sass/marketing.scss index 0678342e212..a96fff92562 100644 --- a/resources/assets/sass/marketing.scss +++ b/resources/assets/sass/marketing.scss @@ -201,6 +201,11 @@ width: 100%; } + a.action { + margin-top: 10px; + width: 100%; + } + .help { font-size: 13px; text-align: center; diff --git a/resources/lang/cz/app.php b/resources/lang/cz/app.php index 22c8f2f8fae..447652a2f74 100644 --- a/resources/lang/cz/app.php +++ b/resources/lang/cz/app.php @@ -11,6 +11,7 @@ 'close' => 'Zavřít', 'remove' => 'Odstranit', 'done' => 'Done', + 'verify' => 'Verify', 'for' => 'for', 'unknown' => 'I don\'t know', 'load_more' => 'Load more', @@ -61,6 +62,8 @@ 'breadcrumb_edit_note' => 'Upravit poznámku', 'breadcrumb_api' => 'API', 'breadcrumb_edit_introductions' => 'How did you meet', + 'breadcrumb_settings_security' => 'Security', + 'breadcrumb_settings_security_2fa' => 'Two Factor Authentication', 'gender_male' => 'Muž', 'gender_female' => 'Žena', diff --git a/resources/lang/cz/auth.php b/resources/lang/cz/auth.php index ad9e57f1be7..3c87978fea2 100644 --- a/resources/lang/cz/auth.php +++ b/resources/lang/cz/auth.php @@ -18,5 +18,9 @@ 'not_authorized' => 'Nejste oprávněni provést tuto akci', 'signup_disabled' => 'Nové registrace jsou aktuálně zastaveny', 'back_homepage' => 'Zpět na domovskou stránku', + '2fa_title' => 'Two Factor Authentication', + '2fa_wrong_validation' => 'The two factor authentication has failed.', + '2fa_one_time_password' => 'Authentication code', + '2fa_recuperation_code' => 'Enter a two factor recovery code', ]; diff --git a/resources/lang/cz/settings.php b/resources/lang/cz/settings.php index 24163e56eb5..1cbb797a1ff 100644 --- a/resources/lang/cz/settings.php +++ b/resources/lang/cz/settings.php @@ -7,6 +7,7 @@ 'sidebar_settings_subscriptions' => 'Odběry', 'sidebar_settings_import' => 'Importovat data', 'sidebar_settings_tags' => 'Správa tagů', + 'sidebar_settings_security' => 'Security', 'export_title' => 'Exportovat data účtu', 'export_be_patient' => 'Kliknout na tlačítko pro spuštění exportu. Zpracování exportu může zabrat až několik minut - buďte prosím trpěliví a neklikejte vícekrát.', @@ -46,6 +47,8 @@ 'locale_cz' => 'Čeština', 'locale_de' => 'Němčina', + 'security_title' => 'Security', + 'security_help' => 'Change security matters for your account.', 'password_change' => 'Password change', 'password_current' => 'Current password', 'password_current_placeholder' => 'Enter your current password', @@ -54,6 +57,18 @@ 'password_new2' => 'Confirmation', 'password_new2_placeholder' => 'Retype the new password', 'password_btn' => 'Change password', + '2fa_title' => 'Two Factor Authentication', + '2fa_enable_title' => 'Enable Two Factor Authentication', + '2fa_enable_description' => 'Enable Two Factor Authentication to increase security with your account.', + '2fa_enable_otp' => 'Open up your two factor authentication mobile app and scan the following QR barcode:', + '2fa_enable_otp_help' => 'If your two factor authentication mobile app does not support QR barcodes, enter in the following code:', + '2fa_enable_otp_validate' => 'Please validate the new device you\'ve just set:', + '2fa_enable_success' => 'Two Factor Authentication activated', + '2fa_enable_error' => 'Error when trying to activate Two Factor Authentication', + '2fa_disable_title' => 'Disable Two Factor Authentication', + '2fa_disable_description' => 'Disable Two Factor Authentication for your account. Be careful, your account will not be secured anymore !', + '2fa_disable_success' => 'Two Factor Authentication disabled', + '2fa_disable_error' => 'Error when trying to disable Two Factor Authentication', 'users_list_title' => 'Uživatelé s přístupem k tomuto účtu', 'users_list_add_user' => 'Pozvat nového uživatele', diff --git a/resources/lang/de/app.php b/resources/lang/de/app.php index e68c6d3e00f..5e0c405f3f5 100644 --- a/resources/lang/de/app.php +++ b/resources/lang/de/app.php @@ -10,6 +10,7 @@ 'upload' => 'Hochladen', 'close' => 'Schließen', 'done' => 'Done', + 'verify' => 'Verify', 'for' => 'for', 'unknown' => 'I don\'t know', 'load_more' => 'Load more', @@ -56,6 +57,8 @@ 'breadcrumb_settings_tags' => 'Tags', 'breadcrumb_api' => 'API', 'breadcrumb_edit_introductions' => 'How did you meet', + 'breadcrumb_settings_security' => 'Security', + 'breadcrumb_settings_security_2fa' => 'Two Factor Authentication', 'gender_male' => 'Männlich', 'gender_female' => 'Weiblich', diff --git a/resources/lang/de/auth.php b/resources/lang/de/auth.php index fd9d0c2f860..1d224a3564d 100644 --- a/resources/lang/de/auth.php +++ b/resources/lang/de/auth.php @@ -18,5 +18,9 @@ 'not_authorized' => 'Du hast keine Berechtigung diese Aktion auszuführen', 'signup_disabled' => 'Registrierung ist zur Zeit abgeschaltet', 'back_homepage' => 'Zurück zur Seite', + '2fa_title' => 'Two Factor Authentication', + '2fa_wrong_validation' => 'The two factor authentication has failed.', + '2fa_one_time_password' => 'Authentication code', + '2fa_recuperation_code' => 'Enter a two factor recovery code', ]; diff --git a/resources/lang/de/settings.php b/resources/lang/de/settings.php index 4cdcdd02cf2..590d01fcdfa 100644 --- a/resources/lang/de/settings.php +++ b/resources/lang/de/settings.php @@ -7,6 +7,7 @@ 'sidebar_settings_subscriptions' => 'Abonnement', 'sidebar_settings_import' => 'Daten importieren', 'sidebar_settings_tags' => 'Tags bearbeiten', + 'sidebar_settings_security' => 'Security', 'export_title' => 'Exportiere die Daten deines Kontos', 'export_be_patient' => 'Button klicken um den Export zu starten. Dies kann mehrere Minuten dauern - sei bitte geduldig und klicke nicht mehrfach auf den Button.', @@ -47,6 +48,8 @@ 'locale_it' => 'Italienisch', 'locale_de' => 'Deutsch', + 'security_title' => 'Security', + 'security_help' => 'Change security matters for your account.', 'password_change' => 'Password change', 'password_current' => 'Current password', 'password_current_placeholder' => 'Enter your current password', @@ -55,6 +58,18 @@ 'password_new2' => 'Confirmation', 'password_new2_placeholder' => 'Retype the new password', 'password_btn' => 'Change password', + '2fa_title' => 'Two Factor Authentication', + '2fa_enable_title' => 'Enable Two Factor Authentication', + '2fa_enable_description' => 'Enable Two Factor Authentication to increase security with your account.', + '2fa_enable_otp' => 'Open up your two factor authentication mobile app and scan the following QR barcode:', + '2fa_enable_otp_help' => 'If your two factor authentication mobile app does not support QR barcodes, enter in the following code:', + '2fa_enable_otp_validate' => 'Please validate the new device you\'ve just set:', + '2fa_enable_success' => 'Two Factor Authentication activated', + '2fa_enable_error' => 'Error when trying to activate Two Factor Authentication', + '2fa_disable_title' => 'Disable Two Factor Authentication', + '2fa_disable_description' => 'Disable Two Factor Authentication for your account. Be careful, your account will not be secured anymore !', + '2fa_disable_success' => 'Two Factor Authentication disabled', + '2fa_disable_error' => 'Error when trying to disable Two Factor Authentication', 'users_list_title' => 'Benutzer, die Zugriff auf dein Konto haben', 'users_list_add_user' => 'Einen Benutzer einladen', diff --git a/resources/lang/en/app.php b/resources/lang/en/app.php index 3d727decaa1..25165cf343b 100644 --- a/resources/lang/en/app.php +++ b/resources/lang/en/app.php @@ -11,6 +11,7 @@ 'close' => 'Close', 'remove' => 'Remove', 'done' => 'Done', + 'verify' => 'Verify', 'for' => 'for', 'unknown' => 'I don\'t know', 'load_more' => 'Load more', @@ -62,6 +63,8 @@ 'breadcrumb_api' => 'API', 'breadcrumb_edit_introductions' => 'How did you meet', 'breadcrumb_settings_personalization' => 'Personalization', + 'breadcrumb_settings_security' => 'Security', + 'breadcrumb_settings_security_2fa' => 'Two Factor Authentication', 'gender_male' => 'Man', 'gender_female' => 'Woman', diff --git a/resources/lang/en/auth.php b/resources/lang/en/auth.php index 632e879efb0..3690877c51f 100644 --- a/resources/lang/en/auth.php +++ b/resources/lang/en/auth.php @@ -18,5 +18,9 @@ 'not_authorized' => 'You are not authorized to execute this action', 'signup_disabled' => 'Registration is currently disabled', 'back_homepage' => 'Back to homepage', + '2fa_title' => 'Two Factor Authentication', + '2fa_wrong_validation' => 'The two factor authentication has failed.', + '2fa_one_time_password' => 'Authentication code', + '2fa_recuperation_code' => 'Enter a two factor recovery code', ]; diff --git a/resources/lang/en/settings.php b/resources/lang/en/settings.php index 5a2f9010ee9..aa61d5a8511 100644 --- a/resources/lang/en/settings.php +++ b/resources/lang/en/settings.php @@ -9,6 +9,7 @@ 'sidebar_settings_import' => 'Import data', 'sidebar_settings_tags' => 'Tags management', 'sidebar_settings_api' => 'API', + 'sidebar_settings_security' => 'Security', 'export_title' => 'Export your account data', 'export_be_patient' => 'Click the button to start the export. It might take several minutes to process the export - please be patient and do not spam the button.', @@ -49,6 +50,8 @@ 'locale_it' => 'Italian', 'locale_de' => 'German', + 'security_title' => 'Security', + 'security_help' => 'Change security matters for your account.', 'password_change' => 'Password change', 'password_current' => 'Current password', 'password_current_placeholder' => 'Enter your current password', @@ -57,6 +60,18 @@ 'password_new2' => 'Confirmation', 'password_new2_placeholder' => 'Retype the new password', 'password_btn' => 'Change password', + '2fa_title' => 'Two Factor Authentication', + '2fa_enable_title' => 'Enable Two Factor Authentication', + '2fa_enable_description' => 'Enable Two Factor Authentication to increase security with your account.', + '2fa_enable_otp' => 'Open up your two factor authentication mobile app and scan the following QR barcode:', + '2fa_enable_otp_help' => 'If your two factor authentication mobile app does not support QR barcodes, enter in the following code:', + '2fa_enable_otp_validate' => 'Please validate the new device you\'ve just set:', + '2fa_enable_success' => 'Two Factor Authentication activated', + '2fa_enable_error' => 'Error when trying to activate Two Factor Authentication', + '2fa_disable_title' => 'Disable Two Factor Authentication', + '2fa_disable_description' => 'Disable Two Factor Authentication for your account. Be careful, your account will not be secured anymore !', + '2fa_disable_success' => 'Two Factor Authentication disabled', + '2fa_disable_error' => 'Error when trying to disable Two Factor Authentication', 'users_list_title' => 'Users with access to your account', 'users_list_add_user' => 'Invite a new user', diff --git a/resources/lang/fr/app.php b/resources/lang/fr/app.php index 54b0a4915c9..ae9e0b337bd 100644 --- a/resources/lang/fr/app.php +++ b/resources/lang/fr/app.php @@ -11,6 +11,7 @@ 'close' => 'Fermer', 'remove' => 'Enlever', 'done' => 'C\'est fait', + 'verify' => 'Verify', 'for' => 'pour', 'unknown' => 'Je ne sais pas', 'load_more' => 'Charger plus', @@ -59,6 +60,8 @@ 'breadcrumb_edit_significant_other' => 'Mettre à jour un partenaire', 'breadcrumb_api' => 'API', 'breadcrumb_edit_introductions' => 'Comment vous vous êtes rencontrés', + 'breadcrumb_settings_security' => 'Security', + 'breadcrumb_settings_security_2fa' => 'Two Factor Authentication', 'gender_male' => 'Homme', 'gender_female' => 'Femme', diff --git a/resources/lang/fr/auth.php b/resources/lang/fr/auth.php index a9010dd393d..4003fa24408 100644 --- a/resources/lang/fr/auth.php +++ b/resources/lang/fr/auth.php @@ -17,4 +17,9 @@ 'throttle' => 'Trop de tentatives de connexion. Veuillez essayer de nouveau dans :seconds secondes.', 'not_authorized' => 'Vous n\'êtes pas autorisé à exécuter cette action', 'signup_disabled' => 'L\'inscription est actuellement désactivée', + '2fa_title' => 'Two Factor Authentication', + '2fa_wrong_validation' => 'The two factor authentication has failed.', + '2fa_one_time_password' => 'Authentication code', + '2fa_recuperation_code' => 'Enter a two factor recovery code', + ]; diff --git a/resources/lang/fr/settings.php b/resources/lang/fr/settings.php index 71b6dfd453a..0e97a039383 100644 --- a/resources/lang/fr/settings.php +++ b/resources/lang/fr/settings.php @@ -7,6 +7,7 @@ 'sidebar_settings_subscriptions' => 'Abonnement', 'sidebar_settings_import' => 'Importation de données', 'sidebar_settings_tags' => 'Gestion des tags', + 'sidebar_settings_security' => 'Security', 'export_title' => 'Exporter les données de votre compte', 'export_be_patient' => 'Cliquez sur le bouton pour commencer l\'export. Cela peut prendre plusieurs minutes pour préparer l\'export - merci d\'être patient et de ne pas spammer le bouton.', @@ -47,6 +48,8 @@ 'locale_it' => 'Italien', 'locale_de' => 'Allemand', + 'security_title' => 'Security', + 'security_help' => 'Change security matters for your account.', 'password_change' => 'Password change', 'password_current' => 'Current password', 'password_current_placeholder' => 'Enter your current password', @@ -55,6 +58,18 @@ 'password_new2' => 'Confirmation', 'password_new2_placeholder' => 'Retype the new password', 'password_btn' => 'Change password', + '2fa_title' => 'Two Factor Authentication', + '2fa_enable_title' => 'Enable Two Factor Authentication', + '2fa_enable_description' => 'Enable Two Factor Authentication to increase security with your account.', + '2fa_enable_otp' => 'Open up your two factor authentication mobile app and scan the following QR barcode:', + '2fa_enable_otp_help' => 'If your two factor authentication mobile app does not support QR barcodes, enter in the following code:', + '2fa_enable_otp_validate' => 'Please validate the new device you\'ve just set:', + '2fa_enable_success' => 'Two Factor Authentication activated', + '2fa_enable_error' => 'Error when trying to activate Two Factor Authentication', + '2fa_disable_title' => 'Disable Two Factor Authentication', + '2fa_disable_description' => 'Disable Two Factor Authentication for your account. Be careful, your account will not be secured anymore !', + '2fa_disable_success' => 'Two Factor Authentication disabled', + '2fa_disable_error' => 'Error when trying to disable Two Factor Authentication', 'users_list_title' => 'Utilisateurs avec accès à votre compte', 'users_list_add_user' => 'Invite a new user', diff --git a/resources/lang/it/app.php b/resources/lang/it/app.php index bbc642359db..41f083cc146 100644 --- a/resources/lang/it/app.php +++ b/resources/lang/it/app.php @@ -10,6 +10,7 @@ 'upload' => 'Carica', 'close' => 'Chiudi', 'done' => 'Fatto', + 'verify' => 'Verify', 'for' => 'per', 'unknown' => 'I don\'t know', 'load_more' => 'Load more', @@ -56,6 +57,8 @@ 'breadcrumb_settings_tags' => 'Etichette', 'breadcrumb_api' => 'API', 'breadcrumb_edit_introductions' => 'Come vi siete conosciuti', + 'breadcrumb_settings_security' => 'Security', + 'breadcrumb_settings_security_2fa' => 'Two Factor Authentication', 'gender_male' => 'Uomo', 'gender_female' => 'Donna', diff --git a/resources/lang/it/auth.php b/resources/lang/it/auth.php index 9add7341846..0b3314f34f4 100644 --- a/resources/lang/it/auth.php +++ b/resources/lang/it/auth.php @@ -18,5 +18,9 @@ 'not_authorized' => 'Non sei autorizzato a eseguire questa azione.', 'signup_disabled' => 'La registrazione è al momento disattivata', 'back_homepage' => 'Ritorna alla Home', + '2fa_title' => 'Two Factor Authentication', + '2fa_wrong_validation' => 'The two factor authentication has failed.', + '2fa_one_time_password' => 'Authentication code', + '2fa_recuperation_code' => 'Enter a two factor recovery code', ]; diff --git a/resources/lang/it/settings.php b/resources/lang/it/settings.php index 8b9babbb45e..8e71b517e82 100644 --- a/resources/lang/it/settings.php +++ b/resources/lang/it/settings.php @@ -7,6 +7,7 @@ 'sidebar_settings_subscriptions' => 'Sottoscrizioni', 'sidebar_settings_import' => 'Importa dati', 'sidebar_settings_tags' => 'Gestione etichette', + 'sidebar_settings_security' => 'Security', 'export_title' => 'Esporta i dati del tuo account', 'export_be_patient' => 'Clicca il pulsante per iniziare l\'esportazione. Potrebbe volerci qualche minuto - ti chiediamo di portare pazienza e non premere il pulsante a ripetizione.', @@ -47,6 +48,8 @@ 'locale_it' => 'Italiano', 'locale_de' => 'Tedesco', + 'security_title' => 'Security', + 'security_help' => 'Change security matters for your account.', 'password_change' => 'Password change', 'password_current' => 'Current password', 'password_current_placeholder' => 'Enter your current password', @@ -55,6 +58,18 @@ 'password_new2' => 'Confirmation', 'password_new2_placeholder' => 'Retype the new password', 'password_btn' => 'Change password', + '2fa_title' => 'Two Factor Authentication', + '2fa_enable_title' => 'Enable Two Factor Authentication', + '2fa_enable_description' => 'Enable Two Factor Authentication to increase security with your account.', + '2fa_enable_otp' => 'Open up your two factor authentication mobile app and scan the following QR barcode:', + '2fa_enable_otp_help' => 'If your two factor authentication mobile app does not support QR barcodes, enter in the following code:', + '2fa_enable_otp_validate' => 'Please validate the new device you\'ve just set:', + '2fa_enable_success' => 'Two Factor Authentication activated', + '2fa_enable_error' => 'Error when trying to activate Two Factor Authentication', + '2fa_disable_title' => 'Disable Two Factor Authentication', + '2fa_disable_description' => 'Disable Two Factor Authentication for your account. Be careful, your account will not be secured anymore !', + '2fa_disable_success' => 'Two Factor Authentication disabled', + '2fa_disable_error' => 'Error when trying to disable Two Factor Authentication', 'users_list_title' => 'Utenti con accesso al tuo account', 'users_list_add_user' => 'Invita un nouvo utente', diff --git a/resources/lang/pt-br/app.php b/resources/lang/pt-br/app.php index 9602f5724b5..2f8bba653b6 100644 --- a/resources/lang/pt-br/app.php +++ b/resources/lang/pt-br/app.php @@ -11,6 +11,7 @@ 'close' => 'Close', 'remove' => 'Remove', 'done' => 'Done', + 'verify' => 'Verify', 'for' => 'for', 'unknown' => 'I don\'t know', 'load_more' => 'Load more', @@ -59,6 +60,8 @@ 'breadcrumb_edit_significant_other' => 'Edit significant other', 'breadcrumb_api' => 'API', 'breadcrumb_edit_introductions' => 'How did you meet', + 'breadcrumb_settings_security' => 'Security', + 'breadcrumb_settings_security_2fa' => 'Two Factor Authentication', 'gender_male' => 'Homem', 'gender_female' => 'Mulher', diff --git a/resources/lang/pt-br/auth.php b/resources/lang/pt-br/auth.php index a09f130e07f..9dea0a57c9f 100644 --- a/resources/lang/pt-br/auth.php +++ b/resources/lang/pt-br/auth.php @@ -17,5 +17,9 @@ 'throttle' => 'Muitas tentativas de login. Por favor tente novamente em :seconds segundos.', 'not_authorized' => 'Você não está autorizado a executar esta ação', 'signup_disabled' => 'Atualmente o registro está desativado', + '2fa_title' => 'Two Factor Authentication', + '2fa_wrong_validation' => 'The two factor authentication has failed.', + '2fa_one_time_password' => 'Authentication code', + '2fa_recuperation_code' => 'Enter a two factor recovery code', ]; diff --git a/resources/lang/pt-br/settings.php b/resources/lang/pt-br/settings.php index 3db5a0b3059..26ec74b4b8a 100644 --- a/resources/lang/pt-br/settings.php +++ b/resources/lang/pt-br/settings.php @@ -7,6 +7,7 @@ 'sidebar_settings_subscriptions' => 'Subscription', 'sidebar_settings_import' => 'Import data', 'sidebar_settings_tags' => 'Tags management', + 'sidebar_settings_security' => 'Security', 'export_title' => 'Export your account data', 'export_be_patient' => 'Click the button to start the export. It might take several minutes to process the export - please be patient and do not spam the button.', @@ -46,6 +47,8 @@ 'locale_it' => 'Italiano', 'locale_de' => 'Alemão', + 'security_title' => 'Security', + 'security_help' => 'Change security matters for your account.', 'password_change' => 'Password change', 'password_current' => 'Current password', 'password_current_placeholder' => 'Enter your current password', @@ -54,6 +57,18 @@ 'password_new2' => 'Confirmation', 'password_new2_placeholder' => 'Retype the new password', 'password_btn' => 'Change password', + '2fa_title' => 'Two Factor Authentication', + '2fa_enable_title' => 'Enable Two Factor Authentication', + '2fa_enable_description' => 'Enable Two Factor Authentication to increase security with your account.', + '2fa_enable_otp' => 'Open up your two factor authentication mobile app and scan the following QR barcode:', + '2fa_enable_otp_help' => 'If your two factor authentication mobile app does not support QR barcodes, enter in the following code:', + '2fa_enable_otp_validate' => 'Please validate the new device you\'ve just set:', + '2fa_enable_success' => 'Two Factor Authentication activated', + '2fa_enable_error' => 'Error when trying to activate Two Factor Authentication', + '2fa_disable_title' => 'Disable Two Factor Authentication', + '2fa_disable_description' => 'Disable Two Factor Authentication for your account. Be careful, your account will not be secured anymore !', + '2fa_disable_success' => 'Two Factor Authentication disabled', + '2fa_disable_error' => 'Error when trying to disable Two Factor Authentication', 'users_list_title' => 'Users with access to your account', 'users_list_add_user' => 'Invite a new user', diff --git a/resources/lang/ru/app.php b/resources/lang/ru/app.php index 4d10eceba02..eb2d36ecad8 100644 --- a/resources/lang/ru/app.php +++ b/resources/lang/ru/app.php @@ -11,6 +11,7 @@ 'close' => 'Закрыть', 'remove' => 'Убрать', 'done' => 'Done', + 'verify' => 'Verify', 'for' => 'for', 'unknown' => 'I don\'t know', 'load_more' => 'Load more', @@ -59,6 +60,8 @@ 'breadcrumb_edit_significant_other' => 'Edit significant other', 'breadcrumb_api' => 'API', 'breadcrumb_edit_introductions' => 'How did you meet', + 'breadcrumb_settings_security' => 'Security', + 'breadcrumb_settings_security_2fa' => 'Two Factor Authentication', 'gender_male' => 'Мужской', 'gender_female' => 'Женский', diff --git a/resources/lang/ru/auth.php b/resources/lang/ru/auth.php index 35a246cbdda..fb59a426b97 100644 --- a/resources/lang/ru/auth.php +++ b/resources/lang/ru/auth.php @@ -18,5 +18,9 @@ 'throttle' => 'Слишком много попыток входа. Пожалуйста, попробуйте еще раз через :seconds секунд.', 'not_authorized' => 'Вам не разрешено выполнять это действие.', 'signup_disabled' => 'Регистрация сейчас выключена.', + '2fa_title' => 'Two Factor Authentication', + '2fa_wrong_validation' => 'The two factor authentication has failed.', + '2fa_one_time_password' => 'Authentication code', + '2fa_recuperation_code' => 'Enter a two factor recovery code', ]; diff --git a/resources/lang/ru/settings.php b/resources/lang/ru/settings.php index c3f00f177f0..3cf8ce9dcb0 100644 --- a/resources/lang/ru/settings.php +++ b/resources/lang/ru/settings.php @@ -7,6 +7,7 @@ 'sidebar_settings_subscriptions' => 'Подписка', 'sidebar_settings_import' => 'Импортировать данные', 'sidebar_settings_tags' => 'Управление тегами', + 'sidebar_settings_security' => 'Security', 'export_title' => 'Экспортировать данные вашего аккаунта', 'export_be_patient' => 'Click the button to start the export. It might take several minutes to process the export - please be patient and do not spam the button.', @@ -45,6 +46,8 @@ 'locale_it' => 'итальянский', 'locale_de' => 'немецкий', + 'security_title' => 'Security', + 'security_help' => 'Change security matters for your account.', 'password_change' => 'Password change', 'password_current' => 'Current password', 'password_current_placeholder' => 'Enter your current password', @@ -53,6 +56,18 @@ 'password_new2' => 'Confirmation', 'password_new2_placeholder' => 'Retype the new password', 'password_btn' => 'Change password', + '2fa_title' => 'Two Factor Authentication', + '2fa_enable_title' => 'Enable Two Factor Authentication', + '2fa_enable_description' => 'Enable Two Factor Authentication to increase security with your account.', + '2fa_enable_otp' => 'Open up your two factor authentication mobile app and scan the following QR barcode:', + '2fa_enable_otp_help' => 'If your two factor authentication mobile app does not support QR barcodes, enter in the following code:', + '2fa_enable_otp_validate' => 'Please validate the new device you\'ve just set:', + '2fa_enable_success' => 'Two Factor Authentication activated', + '2fa_enable_error' => 'Error when trying to activate Two Factor Authentication', + '2fa_disable_title' => 'Disable Two Factor Authentication', + '2fa_disable_description' => 'Disable Two Factor Authentication for your account. Be careful, your account will not be secured anymore !', + '2fa_disable_success' => 'Two Factor Authentication disabled', + '2fa_disable_error' => 'Error when trying to disable Two Factor Authentication', 'users_list_title' => 'Users with access to your account', 'users_list_add_user' => 'Invite a new user', diff --git a/resources/views/auth/validate2fa.blade.php b/resources/views/auth/validate2fa.blade.php new file mode 100644 index 00000000000..98552ed805d --- /dev/null +++ b/resources/views/auth/validate2fa.blade.php @@ -0,0 +1,60 @@ +@extends('marketing.skeleton') + +@section('content') + +
+
+
+
+ +
+ +
+
+ +@endsection diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php new file mode 100644 index 00000000000..8846226f59e --- /dev/null +++ b/resources/views/layouts/app.blade.php @@ -0,0 +1,80 @@ + + + + + + + + + + + {{ config('app.name', 'Laravel') }} + + + + + +
+ + + @yield('content') +
+ + + + + diff --git a/resources/views/settings/_sidebar.blade.php b/resources/views/settings/_sidebar.blade.php index f9f3335d2ee..ce9b4ca065e 100644 --- a/resources/views/settings/_sidebar.blade.php +++ b/resources/views/settings/_sidebar.blade.php @@ -98,5 +98,17 @@ {{ trans('settings.sidebar_settings_api') }} @endif + + @if (Route::currentRouteName() == 'settings.security') +
  • + + {{ trans('settings.sidebar_settings_security') }} +
  • + @else +
  • + + {{ trans('settings.sidebar_settings_security') }} +
  • + @endif diff --git a/resources/views/settings/index.blade.php b/resources/views/settings/index.blade.php index 9c92302b83b..f422e742049 100644 --- a/resources/views/settings/index.blade.php +++ b/resources/views/settings/index.blade.php @@ -250,27 +250,6 @@ -
    - {{ csrf_field() }} - -

    {{ trans('settings.password_change') }}

    - -
    - - -
    -
    - - -
    -
    - - -
    - - -
    -
    {{ csrf_field() }} diff --git a/resources/views/settings/security/2fa-disable.blade.php b/resources/views/settings/security/2fa-disable.blade.php new file mode 100644 index 00000000000..b5b060ac994 --- /dev/null +++ b/resources/views/settings/security/2fa-disable.blade.php @@ -0,0 +1,57 @@ +@extends('layouts.skeleton') + +@section('content') + +
    + + {{-- Breadcrumb --}} + + +
    +
    + + @include('settings._sidebar') + +
    + +

    {{ trans('settings.2fa_title') }}

    +

    {{ trans('settings.2fa_disable_description') }}

    + + @include('partials.errors') + + + {{ csrf_field() }} + + {{-- code --}} +
    + + +
    + + + {{ trans('app.cancel') }} + +
    +
    +
    +
    + +@endsection diff --git a/resources/views/settings/security/2fa-enable.blade.php b/resources/views/settings/security/2fa-enable.blade.php new file mode 100644 index 00000000000..22a7b05e8b8 --- /dev/null +++ b/resources/views/settings/security/2fa-enable.blade.php @@ -0,0 +1,69 @@ +@extends('layouts.skeleton') + +@section('content') + +
    + + {{-- Breadcrumb --}} + + +
    +
    + + @include('settings._sidebar') + +
    + +

    {{ trans('settings.2fa_title') }}

    +

    {{ trans('settings.2fa_enable_description') }}

    + + @include('partials.errors') + +
    + {{ csrf_field() }} + +
    + {{ trans('settings.2fa_enable_otp') }} +

    + Image of QR barcode +
    + {{ trans('settings.2fa_enable_otp_help') }} {{ $secret }} +

    +
    + + {{-- code --}} +
    +

    + {{ trans('settings.2fa_enable_otp_validate') }} +

    + + +
    + + + {{ trans('app.cancel') }} +
    +
    +
    +
    +
    + +@endsection diff --git a/resources/views/settings/security/index.blade.php b/resources/views/settings/security/index.blade.php new file mode 100644 index 00000000000..fe2cc4b8452 --- /dev/null +++ b/resources/views/settings/security/index.blade.php @@ -0,0 +1,95 @@ +@extends('layouts.skeleton') + +@section('content') + +
    + + {{-- Breadcrumb --}} + + +
    +
    + + @include('settings._sidebar') + +
    + + @include('partials.errors') + + @if (session('status')) +
    + {{ session('status') }} +
    + @endif + +

    {{ trans('settings.security_title') }}

    +

    {{ trans('settings.security_help') }}

    + +
    + {{ csrf_field() }} + +

    {{ trans('settings.password_change') }}

    + +
    + + +
    +
    + + +
    +
    + + +
    + + +
    + + @if (config('google2fa.enabled')===true) +
    +

    {{ trans('settings.2fa_title') }}

    + + +
    + + {{-- +
    +

    Recuperation codes

    + +
    +
    +
    + --}} + @endif + +
    +
    +
    +
    + +@endsection diff --git a/routes/web.php b/routes/web.php index 9f1100244c5..d9d73ad5147 100644 --- a/routes/web.php +++ b/routes/web.php @@ -23,15 +23,18 @@ Route::get('/invitations/accept/{key}', 'SettingsController@acceptInvitation'); Route::post('/invitations/accept/{key}', 'SettingsController@storeAcceptedInvitation'); -Route::group(['middleware' => 'auth'], function () { +Route::middleware(['auth'])->group(function () { Route::get('/logout', 'Auth\LoginController@logout'); +}); +Route::middleware(['auth', '2fa'])->group(function () { Route::group(['as' => 'dashboard'], function () { Route::get('/dashboard/', 'DashboardController@index')->name('.index'); Route::get('/dashboard/calls', 'DashboardController@calls'); Route::get('/dashboard/notes', 'DashboardController@notes'); Route::post('/dashboard/setTab', 'DashboardController@setTab'); }); + Route::post('/validate2fa', 'DashboardController@index'); Route::group(['as' => 'people'], function () { Route::get('/people/', 'ContactsController@index')->name('.index'); @@ -171,7 +174,6 @@ Route::post('/settings/delete', ['as' => '.delete', 'uses' => 'SettingsController@delete']); Route::post('/settings/reset', ['as' => '.reset', 'uses' => 'SettingsController@reset']); Route::post('/settings/save', 'SettingsController@save'); - Route::post('/settings/passwordChange', 'Auth\\PasswordChangeController@passwordChange'); Route::get('/settings/export', 'SettingsController@export')->name('.export'); Route::get('/settings/exportToSql', 'SettingsController@exportToSQL'); @@ -204,5 +206,13 @@ Route::delete('/settings/tags/{user}', ['as' => '.tags.delete', 'uses' => 'SettingsController@deleteTag']); Route::get('/settings/api', 'SettingsController@api')->name('.api'); + + // Security + Route::get('/settings/security', 'SettingsController@security')->name('.security'); + Route::post('/settings/security/passwordChange', 'Auth\\PasswordChangeController@passwordChange'); + Route::get('/settings/security/2fa-enable', 'Settings\\MultiFAController@enableTwoFactor')->name('.security.2fa-enable'); + Route::post('/settings/security/2fa-enable', 'Settings\\MultiFAController@validateTwoFactor'); + Route::get('/settings/security/2fa-disable', 'Settings\\MultiFAController@disableTwoFactor')->name('.security.2fa-disable'); + Route::post('/settings/security/2fa-disable', 'Settings\\MultiFAController@deactivateTwoFactor'); }); }); diff --git a/tests/Unit/Google2FATest.php b/tests/Unit/Google2FATest.php new file mode 100644 index 00000000000..96096e17f3c --- /dev/null +++ b/tests/Unit/Google2FATest.php @@ -0,0 +1,82 @@ +generateSecretKey(32); + + $authenticator = new Authenticator(request()); + + $result = $authenticator->verifyGoogle2FA($secret, 'aaaaaa'); + + $this->assertEquals(false, $result); + } + + /** + * A basic test example. + * + * @return void + */ + public function testGoogle2faGoodKey() + { + $google2fa = app('pragmarx.google2fa'); + + $secret = $google2fa->generateSecretKey(32); + $one_time_password = $google2fa->getCurrentOtp($secret); + + $authenticator = new Authenticator(request()); + + $result = $authenticator->verifyGoogle2FA($secret, $one_time_password); + + $this->assertEquals(true, $result); + } + + /** + * A basic test example. + * + * @return void + */ + public function testGoogle2faLogin() + { + config(['google2fa.enabled' => true]); + + $google2fa = app('pragmarx.google2fa'); + $secret = $google2fa->generateSecretKey(32); + + $user = factory(User::class)->create(); + $user->google2fa_secret = $secret; + $this->actingAs($user); + + $request = $this->app['request']; + // Avoid "Session store not set on request." - Exception! + $request->setLaravelSession(new Store('test', new NullSessionHandler)); + $request->getSession()->start(); + + $authenticator = new Authenticator($request); + + $this->assertEquals(false, $authenticator->canPassWithoutCheckingOTP()); + + $this->assertEquals(true, $authenticator->isActivated()); + + $authenticator->login(); + + $this->assertEquals(true, $authenticator->canPassWithoutCheckingOTP()); + } +}