diff --git a/app/config/app.php b/app/config/app.php
index 539138aa..8f8dd215 100644
--- a/app/config/app.php
+++ b/app/config/app.php
@@ -135,6 +135,7 @@ return [
'Thujohn\Rss\RssServiceProvider',
'Jenssegers\Date\DateServiceProvider',
'McCool\LaravelAutoPresenter\LaravelAutoPresenterServiceProvider',
+ 'PragmaRX\Google2FA\Vendor\Laravel\ServiceProvider',
/*
* Application Service Providers...
diff --git a/app/database/migrations/2015_01_09_083419_AlterTableUsersAdd2FA.php b/app/database/migrations/2015_01_09_083419_AlterTableUsersAdd2FA.php
new file mode 100644
index 00000000..3e933caf
--- /dev/null
+++ b/app/database/migrations/2015_01_09_083419_AlterTableUsersAdd2FA.php
@@ -0,0 +1,32 @@
+string('google_2fa_secret')->after('remember_token');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table('users', function (Blueprint $table) {
+ $table->dropColumn('google_2fa_secret');
+ });
+ }
+}
diff --git a/app/lang/en/dashboard.php b/app/lang/en/dashboard.php
index c1c31aba..45963149 100644
--- a/app/lang/en/dashboard.php
+++ b/app/lang/en/dashboard.php
@@ -108,9 +108,10 @@ return [
// Login
'login' => [
- 'login' => 'Login',
- 'logged_in' => "You're logged in.",
- 'welcome' => 'Welcome Back!',
+ 'login' => 'Login',
+ 'logged_in' => 'You\'re logged in.',
+ 'welcome' => 'Welcome Back!',
+ 'two-factor' => 'Please enter your token.',
],
// Sidebar footer
diff --git a/app/lang/en/forms.php b/app/lang/en/forms.php
index 705d2247..a7612b7a 100644
--- a/app/lang/en/forms.php
+++ b/app/lang/en/forms.php
@@ -4,19 +4,21 @@ return [
// Setup form fields
'setup' => [
- 'email' => 'Email',
- 'username' => 'Username',
- 'password' => 'Password',
- 'site_name' => 'Site Name',
- 'site_domain' => 'Site Domain',
- 'site_timezone' => 'Select your timezone',
- 'site_locale' => 'Select your language',
+ 'email' => 'Email',
+ 'username' => 'Username',
+ 'password' => 'Password',
+ 'site_name' => 'Site Name',
+ 'site_domain' => 'Site Domain',
+ 'site_timezone' => 'Select your timezone',
+ 'site_locale' => 'Select your language',
+ 'enable_google2fa' => 'Enable Google Two Factor Authentication',
],
// Login form fields
'login' => [
'email' => 'Email',
'password' => 'Password',
+ '2fauth' => 'Authentication Code',
],
// Incidents form fields
@@ -79,6 +81,9 @@ return [
'password' => 'Password',
'api-key' => 'API Key',
'api-key-help' => 'Regenerating your API key will revoke all existing applications.',
+ '2fa' => [
+ 'help' => 'Enabling two factor authentication increases security of your account. You will need to download Google Authenticator or a similar app on to your mobile device. When you login you will be asked to provide a token generated by the app.',
+ ],
],
// Buttons
diff --git a/app/lang/fr/dashboard.php b/app/lang/fr/dashboard.php
index 74064660..a521b110 100644
--- a/app/lang/fr/dashboard.php
+++ b/app/lang/fr/dashboard.php
@@ -108,9 +108,10 @@ return [
// Login
'login' => [
- 'login' => 'Connexion',
- 'logged_in' => "Vous êtes connecté.",
- 'welcome' => 'Re-bonjour !',
+ 'login' => 'Connexion',
+ 'logged_in' => "Vous êtes connecté.",
+ 'welcome' => 'Re-bonjour !',
+ 'two-factor' => 'Please enter your token.',
],
// Sidebar footer
diff --git a/app/lang/fr/forms.php b/app/lang/fr/forms.php
index 8c8fbdf9..3ed6b82f 100644
--- a/app/lang/fr/forms.php
+++ b/app/lang/fr/forms.php
@@ -4,19 +4,21 @@ return [
// Setup form fields
'setup' => [
- 'email' => 'Adresse email',
- 'username' => 'Identifiant',
- 'password' => 'Mot de passe',
- 'site_name' => 'Nom du site',
- 'site_domain' => 'Domaine du site',
- 'site_timezone' => 'Sélectionnez votre fuseau horaire',
- 'site_locale' => 'Sélectionnez votre langue',
+ 'email' => 'Adresse email',
+ 'username' => 'Identifiant',
+ 'password' => 'Mot de passe',
+ 'site_name' => 'Nom du site',
+ 'site_domain' => 'Domaine du site',
+ 'site_timezone' => 'Sélectionnez votre fuseau horaire',
+ 'site_locale' => 'Sélectionnez votre langue',
+ 'enable_google2fa' => 'Enable Google Two Factor Authentication',
],
// Login form fields
'login' => [
'email' => 'Adresse email',
'password' => 'Mot de passe',
+ '2fauth' => 'Authentication Code',
],
// Incidents form fields
@@ -79,6 +81,9 @@ return [
'password' => 'Mot de passe',
'api-key' => 'Clé API',
'api-key-help' => 'Regénérer votre clé API révoquera toutes les applications existantes.',
+ '2fa' => [
+ 'help' => 'Enabling two factor authentication increases security of your account. You will need to download Google Authenticator or a similar app on to your mobile device. When you login you will be asked to provide a token generated by the app.',
+ ],
],
// Buttons
diff --git a/app/lang/pt-BR/dashboard.php b/app/lang/pt-BR/dashboard.php
index 82d22f97..6f474d50 100755
--- a/app/lang/pt-BR/dashboard.php
+++ b/app/lang/pt-BR/dashboard.php
@@ -1,6 +1,7 @@
'Dashboard',
// Incidents
@@ -97,7 +98,7 @@ return [
'stylesheet' => 'Folha de estilo',
],
'theme' => [
- 'theme' => ' Tema',
+ 'theme' => 'Tema',
],
'edit' => [
'success' => 'Configurações salvas.',
@@ -107,9 +108,10 @@ return [
// Login
'login' => [
- 'login' => 'Login',
- 'logged_in' => "Você está logado.",
- 'welcome' => 'Bem-vindo de volta!',
+ 'login' => 'Login',
+ 'logged_in' => "Você está logado.",
+ 'welcome' => 'Bem-vindo de volta!',
+ 'two-factor' => 'Please enter your token.',
],
// Sidebar footer
diff --git a/app/lang/pt-BR/forms.php b/app/lang/pt-BR/forms.php
index 7b125e08..29db5137 100755
--- a/app/lang/pt-BR/forms.php
+++ b/app/lang/pt-BR/forms.php
@@ -3,19 +3,21 @@
return [
// Setup form fields
'setup' => [
- 'email' => 'Email',
- 'username' => 'Usuário',
- 'password' => 'Senha',
- 'site_name' => 'Nome do site',
- 'site_domain' => 'Domínio do site',
- 'site_timezone' => 'Select your timezone',
- 'site_locale' => 'Select your language',
+ 'email' => 'Email',
+ 'username' => 'Usuário',
+ 'password' => 'Senha',
+ 'site_name' => 'Nome do site',
+ 'site_domain' => 'Domínio do site',
+ 'site_timezone' => 'Select your timezone',
+ 'site_locale' => 'Select your language',
+ 'enable_google2fa' => 'Enable Google Two Factor Authentication',
],
// Login form fields
'login' => [
'email' => 'Email',
'password' => 'Senha',
+ '2fauth' => 'Authentication Code',
],
// Incidents form fields
@@ -78,6 +80,9 @@ return [
'password' => 'Senha',
'api-key' => 'Chave da API',
'api-key-help' => 'Regenerar sua chave de API irá revogar todos os aplicativos existentes.',
+ '2fa' => [
+ 'help' => 'Enabling two factor authentication increases security of your account. You will need to download Google Authenticator or a similar app on to your mobile device. When you login you will be asked to provide a token generated by the app.',
+ ],
],
// Buttons
diff --git a/app/routes/auth.php b/app/routes/auth.php
index 1eeef9f1..2263566c 100644
--- a/app/routes/auth.php
+++ b/app/routes/auth.php
@@ -1,22 +1,33 @@
'has_setting:app_name', 'namespace' => 'CachetHQ\Cachet\Http\Controllers'], function () {
- // Login routes
- Route::get('/auth/login', [
- 'before' => 'guest',
- 'as' => 'login',
- 'uses' => 'AuthController@showLogin',
- ]);
- Route::post('/auth/login', [
- 'before' => 'guest|csrf|login_throttling',
- 'as' => 'logout',
- 'uses' => 'AuthController@postLogin',
- ]);
-});
+Route::group(['prefix' => 'auth', 'namespace' => 'CachetHQ\Cachet\Http\Controllers'], function () {
+ Route::group(['before' => 'has_setting:app_name'], function () {
+ // Login routes
+ Route::get('login', [
+ 'before' => 'guest',
+ 'as' => 'login',
+ 'uses' => 'AuthController@showLogin',
+ ]);
-Route::group(['before' => 'auth', 'namespace' => 'CachetHQ\Cachet\Http\Controllers'], function () {
- Route::get('/auth/logout', [
- 'as' => 'logout',
- 'uses' => 'AuthController@logoutAction',
- ]);
+ Route::post('login', [
+ 'before' => 'guest|csrf|login_throttling',
+ 'as' => 'logout',
+ 'uses' => 'AuthController@postLogin',
+ ]);
+
+ // Two factor authorization
+ Route::get('2fa', [
+ 'as' => 'two-factor',
+ 'uses' => 'AuthController@showTwoFactorAuth',
+ ]);
+
+ Route::post('2fa', 'AuthController@postTwoFactor');
+ });
+
+ Route::group(['before' => 'auth'], function () {
+ Route::get('logout', [
+ 'as' => 'logout',
+ 'uses' => 'AuthController@logoutAction',
+ ]);
+ });
});
diff --git a/app/views/auth/two-factor-auth.blade.php b/app/views/auth/two-factor-auth.blade.php
new file mode 100644
index 00000000..95474428
--- /dev/null
+++ b/app/views/auth/two-factor-auth.blade.php
@@ -0,0 +1,31 @@
+@extends('layout.clean')
+
+@section('content')
+
+
+
+
 }})
+
+ {{ Form::open() }}
+
+ {{ Form::close() }}
+
+
+@stop
diff --git a/app/views/dashboard/team/edit.blade.php b/app/views/dashboard/team/edit.blade.php
index a753b5c4..7c867933 100644
--- a/app/views/dashboard/team/edit.blade.php
+++ b/app/views/dashboard/team/edit.blade.php
@@ -6,7 +6,7 @@
- {{ trans('dashboard.user.user') }}
+ {{ trans('dashboard.team.member') }}
@@ -15,9 +15,9 @@
@if($updated = Session::get('updated'))
@if($updated)
- {{ sprintf("%s %s", trans('dashboard.notifications.awesome'), trans('dashboard.user.edit.success')) }}
+ {{ sprintf("%s %s", trans('dashboard.notifications.awesome'), trans('dashboard.team.edit.success')) }}
@else
- {{ sprintf("%s %s", trans('dashboard.notifications.whoops'), trans('dashboard.user.edit.failure')) }}
+ {{ sprintf("%s %s", trans('dashboard.notifications.whoops'), trans('dashboard.team.edit.failure')) }}
@endif
@endif
diff --git a/app/views/dashboard/team/index.blade.php b/app/views/dashboard/team/index.blade.php
index a9a6cf6e..ba62e098 100644
--- a/app/views/dashboard/team/index.blade.php
+++ b/app/views/dashboard/team/index.blade.php
@@ -21,7 +21,7 @@
@foreach($teamMembers as $member)
-
+
{{ $member->username }}
diff --git a/app/views/dashboard/user/index.blade.php b/app/views/dashboard/user/index.blade.php
index c284c239..c9c7026f 100644
--- a/app/views/dashboard/user/index.blade.php
+++ b/app/views/dashboard/user/index.blade.php
@@ -15,9 +15,9 @@
@if($updated = Session::get('updated'))
@if($updated)
- {{ sprintf("%s %s", trans('dashboard.notifications.awesome'), trans('dashboard.user.edit.success')) }}
+ {{ sprintf("%s %s", trans('dashboard.notifications.awesome'), trans('dashboard.team.edit.success')) }}
@else
- {{ sprintf("%s %s", trans('dashboard.notifications.whoops'), trans('dashboard.user.edit.failure')) }}
+ {{ sprintf("%s %s", trans('dashboard.notifications.whoops'), trans('dashboard.team.edit.failure')) }}
@endif
@endif
@@ -42,6 +42,27 @@
{{ trans('forms.user.api-key-help') }}
+
+
+
+
+ @if(Auth::user()->hasTwoFactor)
+
+ @endif
diff --git a/composer.json b/composer.json
index bbc971c6..86394b5d 100644
--- a/composer.json
+++ b/composer.json
@@ -23,7 +23,8 @@
"thujohn/rss": "~1.0",
"watson/validating": "0.10.*",
"jenssegers/date": "~2.0",
- "mccool/laravel-auto-presenter": "~2.2@dev"
+ "mccool/laravel-auto-presenter": "~2.2@dev",
+ "pragmarx/google2fa": "~0.1.0"
},
"require-dev": {
"phpunit/phpunit": "~4.3",
diff --git a/composer.lock b/composer.lock
index fcc65d29..4f915d11 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
- "hash": "b561fa017bd12ef775f7765fe28d8839",
+ "hash": "54919d71db11cf852620b58cd12a982b",
"packages": [
{
"name": "classpreloader/classpreloader",
@@ -1829,6 +1829,58 @@
],
"time": "2014-11-10 03:08:59"
},
+ {
+ "name": "pragmarx/google2fa",
+ "version": "v0.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/antonioribeiro/google2fa.git",
+ "reference": "705c2ff3e59212636fdfe31405e0e667a4f31514"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/705c2ff3e59212636fdfe31405e0e667a4f31514",
+ "reference": "705c2ff3e59212636fdfe31405e0e667a4f31514",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.7"
+ },
+ "require-dev": {
+ "phpspec/phpspec": "~2.1@dev"
+ },
+ "type": "library",
+ "extra": {
+ "component": "package",
+ "frameworks": [
+ "Laravel"
+ ]
+ },
+ "autoload": {
+ "psr-4": {
+ "PragmaRX\\Google2FA\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "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",
+ "Two Factor Authentication",
+ "google2fa",
+ "laravel"
+ ],
+ "time": "2014-09-22 15:53:31"
+ },
{
"name": "predis/predis",
"version": "v0.8.7",
diff --git a/src/Http/Controllers/AuthController.php b/src/Http/Controllers/AuthController.php
index fe5ace84..779d35f8 100644
--- a/src/Http/Controllers/AuthController.php
+++ b/src/Http/Controllers/AuthController.php
@@ -8,7 +8,9 @@ use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Support\Facades\Request;
+use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\View;
+use PragmaRX\Google2FA\Vendor\Laravel\Facade as Google2FA;
/**
* Logs users into their account.
@@ -32,7 +34,22 @@ class AuthController extends Controller
*/
public function postLogin()
{
- if (Auth::attempt(Binput::only(['email', 'password']))) {
+ $loginData = Binput::only(['email', 'password']);
+ // Validate login credentials.
+ if (Auth::validate($loginData)) {
+ // Log the user in for one request.
+ Auth::once($loginData);
+ // Do we have Two Factor Auth enabled?
+ if (Auth::user()->hasTwoFactor) {
+ // Temporarily store the user.
+ Session::put('2fa_id', Auth::user()->id);
+
+ return Redirect::route('two-factor');
+ }
+
+ // We probably wan't to add support for "Remember me" here.
+ Auth::attempt(Binput::only(['email', 'password']));
+
return Redirect::intended('dashboard');
}
@@ -43,6 +60,47 @@ class AuthController extends Controller
->with('error', 'Invalid email or password');
}
+ /**
+ * Shows the two-factor-auth view.
+ *
+ * @return \Illuminate\View\View
+ */
+ public function showTwoFactorAuth()
+ {
+ return View::make('auth.two-factor-auth');
+ }
+
+ /**
+ * Validates the Two Factor token.
+ *
+ * This feels very hacky, but we have to juggle authentication and codes.
+ *
+ * @return \Illuminate\Http\RedirectResponse
+ */
+ public function postTwoFactor()
+ {
+ // Check that we have a session.
+ if ($userId = Session::pull('2fa_id')) {
+ $code = Binput::get('code');
+
+ // Maybe a temp login here.
+ Auth::loginUsingId($userId);
+
+ $valid = Google2FA::verifyKey(Auth::user()->google_2fa_secret, $code);
+
+ if ($valid) {
+ return Redirect::intended('dashboard');
+ } else {
+ // Failed login, log back out.
+ Auth::logout();
+
+ return Redirect::route('login')->with('error', 'Invalid token');
+ }
+ }
+
+ return Redirect::route('login')->with('error', 'Invalid token');
+ }
+
/**
* Logs the user out, deleting their session etc.
*
diff --git a/src/Http/Controllers/DashUserController.php b/src/Http/Controllers/DashUserController.php
index 9692de04..59c49826 100644
--- a/src/Http/Controllers/DashUserController.php
+++ b/src/Http/Controllers/DashUserController.php
@@ -8,6 +8,7 @@ use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Support\Facades\View;
+use PragmaRX\Google2FA\Vendor\Laravel\Facade as Google2FA;
class DashUserController extends Controller
{
@@ -32,6 +33,20 @@ class DashUserController extends Controller
{
$items = Binput::all();
+ $passwordChange = array_get($items, 'password');
+ $enable2FA = (bool) array_pull($items, 'google2fa');
+
+ // Let's enable/disable auth
+ if ($enable2FA && ! Auth::user()->hasTwoFactor) {
+ $items['google_2fa_secret'] = Google2FA::generateSecretKey();
+ } elseif (! $enable2FA) {
+ $items['google_2fa_secret'] = '';
+ }
+
+ if (trim($passwordChange) === '') {
+ unset($items['password']);
+ }
+
$updated = Auth::user()->update($items);
return Redirect::back()->with('updated', $updated);
diff --git a/src/Http/Controllers/SetupController.php b/src/Http/Controllers/SetupController.php
index 2ac4f043..e74a87ec 100644
--- a/src/Http/Controllers/SetupController.php
+++ b/src/Http/Controllers/SetupController.php
@@ -126,7 +126,6 @@ class SetupController extends Controller
// Pull the user details out.
$userDetails = array_pull($postData, 'user');
- // TODO: Do we want to just use Model::unguard() here?
$user = User::create([
'username' => $userDetails['username'],
'email' => $userDetails['email'],
diff --git a/src/Models/User.php b/src/Models/User.php
index 59917a8d..0cfbaca0 100644
--- a/src/Models/User.php
+++ b/src/Models/User.php
@@ -44,7 +44,7 @@ class User extends Model implements UserInterface, RemindableInterface
*
* @var string[]
*/
- protected $hidden = ['password', 'remember_token'];
+ protected $hidden = ['password', 'remember_token', 'google_2fa_secret'];
/**
* The properties that cannot be mass assigned.
@@ -133,4 +133,14 @@ class User extends Model implements UserInterface, RemindableInterface
{
return (bool) $this->level;
}
+
+ /**
+ * Returns if a user has enabled two factor authentication.
+ *
+ * @return bool
+ */
+ public function getHasTwoFactorAttribute()
+ {
+ return trim($this->google_2fa_secret) !== '';
+ }
}