mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-09-19 16:01:34 +02:00
Request for fail2ban log support and two factor authentication support (2FA) #1657
This commit is contained in:
parent
570e72c083
commit
478de96d81
@ -47,7 +47,7 @@ class AccountApiController extends BaseAPIController
|
||||
$account = $this->accountRepo->create($request->first_name, $request->last_name, $request->email, $request->password);
|
||||
$user = $account->users()->first();
|
||||
|
||||
Auth::login($user, true);
|
||||
Auth::login($user);
|
||||
event(new UserSignedUp());
|
||||
|
||||
return $this->processLogin($request);
|
||||
@ -69,6 +69,7 @@ class AccountApiController extends BaseAPIController
|
||||
}
|
||||
return $this->processLogin($request);
|
||||
} else {
|
||||
error_log('login failed');
|
||||
if ($user) {
|
||||
$user->failed_logins = $user->failed_logins + 1;
|
||||
$user->save();
|
||||
|
@ -1123,6 +1123,11 @@ class AccountController extends BaseController
|
||||
}
|
||||
|
||||
$rules = ['email' => 'email|required|unique:users,email,'.$user->id.',id'];
|
||||
|
||||
if ($user->google_2fa_secret) {
|
||||
$rules['phone'] = 'required';
|
||||
}
|
||||
|
||||
$validator = Validator::make(Input::all(), $rules);
|
||||
|
||||
if ($validator->fails()) {
|
||||
@ -1144,6 +1149,10 @@ class AccountController extends BaseController
|
||||
$user->notify_approved = Input::get('notify_approved');
|
||||
}
|
||||
|
||||
if ($user->google_2fa_secret && ! Input::get('enable_two_factor')) {
|
||||
$user->google_2fa_secret = null;
|
||||
}
|
||||
|
||||
if (Utils::isNinja()) {
|
||||
if (Input::get('referral_code') && ! $user->referral_code) {
|
||||
$user->referral_code = strtolower(str_random(RANDOM_KEY_LENGTH));
|
||||
|
@ -14,6 +14,9 @@ use Illuminate\Http\Request;
|
||||
use Lang;
|
||||
use Session;
|
||||
use Utils;
|
||||
use Cache;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use App\Http\Requests\ValidateTwoFactorRequest;
|
||||
|
||||
class AuthController extends Controller
|
||||
{
|
||||
@ -151,20 +154,12 @@ class AuthController extends Controller
|
||||
|
||||
if ($user && $user->failed_logins >= MAX_FAILED_LOGINS) {
|
||||
Session::flash('error', trans('texts.invalid_credentials'));
|
||||
|
||||
return redirect()->to('login');
|
||||
}
|
||||
|
||||
$response = self::postLogin($request);
|
||||
|
||||
if (Auth::check()) {
|
||||
if ($user && $user->failed_logins > 0) {
|
||||
$user->failed_logins = 0;
|
||||
$user->save();
|
||||
}
|
||||
|
||||
Event::fire(new UserLoggedIn());
|
||||
|
||||
/*
|
||||
$users = false;
|
||||
// we're linking a new account
|
||||
@ -176,10 +171,8 @@ class AuthController extends Controller
|
||||
$users = $this->accountRepo->loadAccounts(Auth::user()->id);
|
||||
}
|
||||
*/
|
||||
|
||||
$users = $this->accountRepo->loadAccounts(Auth::user()->id);
|
||||
Session::put(SESSION_USER_ACCOUNTS, $users);
|
||||
} elseif ($user) {
|
||||
error_log('login failed');
|
||||
$user->failed_logins = $user->failed_logins + 1;
|
||||
$user->save();
|
||||
}
|
||||
@ -187,6 +180,60 @@ class AuthController extends Controller
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the post-authentication response.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Illuminate\Contracts\Auth\Authenticatable $user
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
private function authenticated(Request $request, Authenticatable $user)
|
||||
{
|
||||
if ($user->google_2fa_secret) {
|
||||
Auth::logout();
|
||||
$request->session()->put('2fa:user:id', $user->id);
|
||||
return redirect('/validate_two_factor/' . $user->account->account_key);
|
||||
}
|
||||
|
||||
Event::fire(new UserLoggedIn());
|
||||
|
||||
return redirect()->intended($this->redirectTo);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function getValidateToken()
|
||||
{
|
||||
if (session('2fa:user:id')) {
|
||||
return view('auth.two_factor');
|
||||
}
|
||||
|
||||
return redirect('login');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param App\Http\Requests\ValidateSecretRequest $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function postValidateToken(ValidateTwoFactorRequest $request)
|
||||
{
|
||||
//get user id and create cache key
|
||||
$userId = $request->session()->pull('2fa:user:id');
|
||||
$key = $userId . ':' . $request->totp;
|
||||
|
||||
//use cache to store token to blacklist
|
||||
Cache::add($key, true, 4);
|
||||
|
||||
//login and redirect user
|
||||
Auth::loginUsingId($userId);
|
||||
Event::fire(new UserLoggedIn());
|
||||
|
||||
return redirect()->intended($this->redirectTo);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use Event;
|
||||
use App\Events\UserLoggedIn;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Foundation\Auth\ResetsPasswords;
|
||||
|
||||
@ -18,7 +20,9 @@ class PasswordController extends Controller
|
||||
|
|
||||
*/
|
||||
|
||||
use ResetsPasswords;
|
||||
use ResetsPasswords {
|
||||
getResetSuccessResponse as protected traitGetResetSuccessResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var string
|
||||
@ -49,4 +53,18 @@ class PasswordController extends Controller
|
||||
|
||||
return $this->getEmail();
|
||||
}
|
||||
|
||||
protected function getResetSuccessResponse($response)
|
||||
{
|
||||
$user = auth()->user();
|
||||
|
||||
if ($user->google_2fa_secret) {
|
||||
auth()->logout();
|
||||
session(['2fa:user:id' => $user->id]);
|
||||
return redirect('/validate_two_factor/' . $user->account->account_key);
|
||||
} else {
|
||||
Event::fire(new UserLoggedIn());
|
||||
return $this->traitGetResetSuccessResponse($response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
37
app/Http/Controllers/Google2FAController.php
Normal file
37
app/Http/Controllers/Google2FAController.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use PragmaRX\Google2FA\Google2FA;
|
||||
use Crypt;
|
||||
|
||||
class Google2FAController extends Controller
|
||||
{
|
||||
public function enableTwoFactor()
|
||||
{
|
||||
$user = auth()->user();
|
||||
|
||||
if ($user->google_2fa_secret) {
|
||||
return redirect('/settings/user_details');
|
||||
}
|
||||
|
||||
$google2fa = new Google2FA();
|
||||
$secret = $google2fa->generateSecretKey();
|
||||
|
||||
$user->google_2fa_secret = Crypt::encrypt($secret);
|
||||
$user->save();
|
||||
|
||||
$qrCode = $google2fa->getQRCodeGoogleUrl(
|
||||
APP_NAME,
|
||||
$user->email,
|
||||
$secret
|
||||
);
|
||||
|
||||
$data = [
|
||||
'secret' => $secret,
|
||||
'qrCode' => $qrCode,
|
||||
];
|
||||
|
||||
return view('users.two_factor', $data);
|
||||
}
|
||||
}
|
@ -36,5 +36,6 @@ class Kernel extends HttpKernel
|
||||
'guest' => 'App\Http\Middleware\RedirectIfAuthenticated',
|
||||
'api' => 'App\Http\Middleware\ApiCheck',
|
||||
'cors' => '\Barryvdh\Cors\HandleCors',
|
||||
'throttle' => 'Illuminate\Routing\Middleware\ThrottleRequests',
|
||||
];
|
||||
}
|
||||
|
78
app/Http/Requests/ValidateTwoFactorRequest.php
Normal file
78
app/Http/Requests/ValidateTwoFactorRequest.php
Normal file
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Cache;
|
||||
use Crypt;
|
||||
use Google2FA;
|
||||
use App\Models\User;
|
||||
use App\Http\Requests\Request;
|
||||
use Illuminate\Validation\Factory as ValidatonFactory;
|
||||
|
||||
class ValidateTwoFactorRequest extends Request
|
||||
{
|
||||
/**
|
||||
*
|
||||
* @var \App\User
|
||||
*/
|
||||
private $user;
|
||||
|
||||
/**
|
||||
* Create a new FormRequest instance.
|
||||
*
|
||||
* @param \Illuminate\Validation\Factory $factory
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(ValidatonFactory $factory)
|
||||
{
|
||||
$factory->extend(
|
||||
'valid_token',
|
||||
function ($attribute, $value, $parameters, $validator) {
|
||||
$secret = Crypt::decrypt($this->user->google_2fa_secret);
|
||||
|
||||
return Google2FA::verifyKey($secret, $value);
|
||||
},
|
||||
trans('texts.invalid_code')
|
||||
);
|
||||
|
||||
$factory->extend(
|
||||
'used_token',
|
||||
function ($attribute, $value, $parameters, $validator) {
|
||||
$key = $this->user->id . ':' . $value;
|
||||
|
||||
return !Cache::has($key);
|
||||
},
|
||||
trans('texts.invalid_code')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
try {
|
||||
$this->user = User::findOrFail(
|
||||
session('2fa:user:id')
|
||||
);
|
||||
} catch (Exception $exc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'totp' => 'bail|required|digits:6|valid_token|used_token',
|
||||
];
|
||||
}
|
||||
}
|
@ -79,6 +79,8 @@ Route::group(['middleware' => 'lookup:postmark'], function () {
|
||||
Route::group(['middleware' => 'lookup:account'], function () {
|
||||
Route::post('/payment_hook/{account_key}/{gateway_id}', 'OnlinePaymentController@handlePaymentWebhook');
|
||||
Route::match(['GET', 'POST', 'OPTIONS'], '/buy_now/{gateway_type?}', 'OnlinePaymentController@handleBuyNow');
|
||||
Route::get('validate_two_factor/{account_key}', 'Auth\AuthController@getValidateToken');
|
||||
Route::post('validate_two_factor/{account_key}', ['middleware' => 'throttle:5', 'uses' => 'Auth\AuthController@postValidateToken']);
|
||||
});
|
||||
|
||||
//Route::post('/hook/bot/{platform?}', 'BotController@handleMessage');
|
||||
@ -141,6 +143,8 @@ Route::group(['middleware' => ['lookup:user', 'auth:user']], function () {
|
||||
Route::post('settings/user_details', 'AccountController@saveUserDetails');
|
||||
Route::post('settings/payment_gateway_limits', 'AccountGatewayController@savePaymentGatewayLimits');
|
||||
Route::post('users/change_password', 'UserController@changePassword');
|
||||
Route::get('settings/enable_two_factor', 'Google2FAController@enableTwoFactor');
|
||||
Route::get('settings/disable_two_factor', 'Google2FAController@disableTwoFactor');
|
||||
|
||||
Route::resource('clients', 'ClientController');
|
||||
Route::get('api/clients', 'ClientController@getDatatable');
|
||||
|
@ -41,7 +41,8 @@ class HandleUserLoggedIn
|
||||
*/
|
||||
public function handle(UserLoggedIn $event)
|
||||
{
|
||||
$account = Auth::user()->account;
|
||||
$user = auth()->user();
|
||||
$account = $user->account;
|
||||
|
||||
if (! Utils::isNinja() && empty($account->last_login)) {
|
||||
event(new UserSignedUp());
|
||||
@ -50,6 +51,11 @@ class HandleUserLoggedIn
|
||||
$account->last_login = Carbon::now()->toDateTimeString();
|
||||
$account->save();
|
||||
|
||||
if ($user->failed_logins > 0) {
|
||||
$user->failed_logins = 0;
|
||||
$user->save();
|
||||
}
|
||||
|
||||
$users = $this->accountRepo->loadAccounts(Auth::user()->id);
|
||||
Session::put(SESSION_USER_ACCOUNTS, $users);
|
||||
HistoryUtils::loadHistory($users ?: Auth::user()->id);
|
||||
|
@ -59,7 +59,15 @@ class User extends Authenticatable
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $hidden = ['password', 'remember_token', 'confirmation_code'];
|
||||
protected $hidden = [
|
||||
'password',
|
||||
'remember_token',
|
||||
'confirmation_code',
|
||||
'oauth_user_id',
|
||||
'oauth_provider_id',
|
||||
'google_2fa_secret',
|
||||
'google_2fa_phone',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
|
@ -83,10 +83,15 @@ class AuthService
|
||||
}
|
||||
} else {
|
||||
LookupUser::setServerByField('oauth_user_key', $providerId . '-' . $oauthUserId);
|
||||
|
||||
\Log::info("Find user: $providerId, $oauthUserId");
|
||||
if ($user = $this->accountRepo->findUserByOauth($providerId, $oauthUserId)) {
|
||||
Auth::login($user, true);
|
||||
event(new UserLoggedIn());
|
||||
if ($user->google_2fa_secret) {
|
||||
session(['2fa:user:id' => $user->id]);
|
||||
return redirect('/validate_two_factor/' . $user->account->account_key);
|
||||
} else {
|
||||
Auth::login($user);
|
||||
event(new UserLoggedIn());
|
||||
}
|
||||
} else {
|
||||
Session::flash('error', trans('texts.invalid_credentials'));
|
||||
|
||||
|
@ -17,11 +17,13 @@
|
||||
"ext-gd": "*",
|
||||
"ext-gmp": "*",
|
||||
"Dwolla/omnipay-dwolla": "dev-master",
|
||||
"abdala/omnipay-pagseguro": "0.2",
|
||||
"agmscode/omnipay-agms": "~1.0",
|
||||
"alfaproject/omnipay-skrill": "dev-master",
|
||||
"anahkiasen/former": "4.0.*@dev",
|
||||
"andreas22/omnipay-fasapay": "1.*",
|
||||
"asgrim/ofxparser": "^1.1",
|
||||
"bacon/bacon-qr-code": "^1.0",
|
||||
"barracudanetworks/archivestream-php": "^1.0",
|
||||
"barryvdh/laravel-cors": "^0.9.1",
|
||||
"barryvdh/laravel-debugbar": "~2.2",
|
||||
@ -70,6 +72,7 @@
|
||||
"mpdf/mpdf": "6.1.3",
|
||||
"nwidart/laravel-modules": "^1.14",
|
||||
"omnipay/2checkout": "dev-master#e9c079c2dde0d7ba461903b3b7bd5caf6dee1248",
|
||||
"omnipay/authorizenet": "dev-solution-id as 2.5.0",
|
||||
"omnipay/bitpay": "dev-master",
|
||||
"omnipay/braintree": "~2.0@dev",
|
||||
"omnipay/gocardless": "dev-master",
|
||||
@ -77,6 +80,7 @@
|
||||
"omnipay/omnipay": "~2.3",
|
||||
"omnipay/stripe": "dev-master",
|
||||
"patricktalmadge/bootstrapper": "5.5.x",
|
||||
"pragmarx/google2fa-laravel": "^0.1.2",
|
||||
"predis/predis": "^1.1",
|
||||
"simshaun/recurr": "dev-master",
|
||||
"softcommerce/omnipay-paytrace": "~1.0",
|
||||
@ -86,10 +90,8 @@
|
||||
"webpatser/laravel-countries": "dev-master",
|
||||
"websight/l5-google-cloud-storage": "dev-master",
|
||||
"wepay/php-sdk": "^0.2",
|
||||
"wildbit/laravel-postmark-provider": "3.0",
|
||||
"abdala/omnipay-pagseguro": "0.2",
|
||||
"omnipay/authorizenet": "dev-solution-id as 2.5.0"
|
||||
},
|
||||
"wildbit/laravel-postmark-provider": "3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"codeception/c3": "~2.0",
|
||||
"codeception/codeception": "2.3.3",
|
||||
|
247
composer.lock
generated
247
composer.lock
generated
@ -4,8 +4,8 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"hash": "13e8b7032220fe1181aed50719a55a11",
|
||||
"content-hash": "eb01266703f4233d8fa703516a43bba8",
|
||||
"hash": "80f6d3f1c26c0eb84d0d9ee5aa88aaa1",
|
||||
"content-hash": "0891783210a35e9c83addbcdbb691993",
|
||||
"packages": [
|
||||
{
|
||||
"name": "abdala/omnipay-pagseguro",
|
||||
@ -458,6 +458,52 @@
|
||||
],
|
||||
"time": "2017-10-17 19:51:40"
|
||||
},
|
||||
{
|
||||
"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-17 09:59:25"
|
||||
},
|
||||
{
|
||||
"name": "barracudanetworks/archivestream-php",
|
||||
"version": "1.0.5",
|
||||
@ -7181,6 +7227,68 @@
|
||||
],
|
||||
"time": "2017-06-15 07:04:25"
|
||||
},
|
||||
{
|
||||
"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-22 14:55:37"
|
||||
},
|
||||
{
|
||||
"name": "paragonie/random_compat",
|
||||
"version": "v1.4.2",
|
||||
@ -7437,6 +7545,137 @@
|
||||
],
|
||||
"time": "2017-06-05 06:31:10"
|
||||
},
|
||||
{
|
||||
"name": "pragmarx/google2fa",
|
||||
"version": "v2.0.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/antonioribeiro/google2fa.git",
|
||||
"reference": "bc2d654305e4d09254125f8cd390a7fbc4742d46"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/bc2d654305e4d09254125f8cd390a7fbc4742d46",
|
||||
"reference": "bc2d654305e4d09254125f8cd390a7fbc4742d46",
|
||||
"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",
|
||||
"phpspec/phpspec": "~2.1",
|
||||
"phpunit/phpunit": "~4"
|
||||
},
|
||||
"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/"
|
||||
}
|
||||
},
|
||||
"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": "2017-09-12 06:55:05"
|
||||
},
|
||||
{
|
||||
"name": "pragmarx/google2fa-laravel",
|
||||
"version": "v0.1.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/antonioribeiro/google2fa-laravel.git",
|
||||
"reference": "ebc24520bea1f1ef5659e39d4024a3275ce49a49"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/antonioribeiro/google2fa-laravel/zipball/ebc24520bea1f1ef5659e39d4024a3275ce49a49",
|
||||
"reference": "ebc24520bea1f1ef5659e39d4024a3275ce49a49",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"laravel/framework": "~5",
|
||||
"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."
|
||||
},
|
||||
"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/"
|
||||
}
|
||||
},
|
||||
"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": [
|
||||
"Authentication",
|
||||
"Two Factor Authentication",
|
||||
"google2fa",
|
||||
"laravel"
|
||||
],
|
||||
"time": "2017-06-23 00:45:24"
|
||||
},
|
||||
{
|
||||
"name": "predis/predis",
|
||||
"version": "v1.1.1",
|
||||
@ -12192,14 +12431,14 @@
|
||||
"meebio/omnipay-creditcall": 20,
|
||||
"meebio/omnipay-secure-trading": 20,
|
||||
"omnipay/2checkout": 20,
|
||||
"omnipay/authorizenet": 20,
|
||||
"omnipay/bitpay": 20,
|
||||
"omnipay/braintree": 20,
|
||||
"omnipay/gocardless": 20,
|
||||
"omnipay/stripe": 20,
|
||||
"simshaun/recurr": 20,
|
||||
"webpatser/laravel-countries": 20,
|
||||
"websight/l5-google-cloud-storage": 20,
|
||||
"omnipay/authorizenet": 20
|
||||
"websight/l5-google-cloud-storage": 20
|
||||
},
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
|
@ -158,6 +158,7 @@ return [
|
||||
Codedge\Updater\UpdaterServiceProvider::class,
|
||||
Nwidart\Modules\LaravelModulesServiceProvider::class,
|
||||
Barryvdh\Cors\ServiceProvider::class,
|
||||
PragmaRX\Google2FALaravel\ServiceProvider::class,
|
||||
|
||||
/*
|
||||
* Application Service Providers...
|
||||
@ -171,6 +172,7 @@ return [
|
||||
|
||||
'Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider',
|
||||
'Davibennun\LaravelPushNotification\LaravelPushNotificationServiceProvider',
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
@ -266,6 +268,8 @@ return [
|
||||
'DateUtils' => App\Libraries\DateUtils::class,
|
||||
'HTMLUtils' => App\Libraries\HTMLUtils::class,
|
||||
'Domain' => App\Constants\Domain::class,
|
||||
'Google2FA' => PragmaRX\Google2FALaravel\Facade::class,
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
|
68
config/google2fa.php
Normal file
68
config/google2fa.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
* Auth container binding
|
||||
*/
|
||||
|
||||
'enabled' => true,
|
||||
|
||||
/*
|
||||
* 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' => 1,
|
||||
|
||||
/*
|
||||
* 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' => 'google2fa.index',
|
||||
|
||||
/*
|
||||
* One Time Password error message
|
||||
*/
|
||||
'error_messages' => [
|
||||
'wrong_otp' => "The 'One Time Password' typed was wrong.",
|
||||
],
|
||||
|
||||
];
|
@ -28,6 +28,10 @@ class AddDefaultRates extends Migration
|
||||
$table->date('partial_due_date')->nullable();
|
||||
});
|
||||
|
||||
Schema::table('users', function ($table) {
|
||||
$table->string('google_2fa_secret')->nullable();
|
||||
});
|
||||
|
||||
// Add 'Four Months' frequency option
|
||||
if (DB::table('frequencies')->count() == 8) {
|
||||
DB::table('frequencies')->where('id', '=', 7)->update(['name' => 'Four months']);
|
||||
@ -61,5 +65,9 @@ class AddDefaultRates extends Migration
|
||||
Schema::table('invoices', function ($table) {
|
||||
$table->dropColumn('partial_due_date');
|
||||
});
|
||||
|
||||
Schema::table('users', function ($table) {
|
||||
$table->dropColumn('google_2fa_secret');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@ -2510,6 +2510,14 @@ $LANG = array(
|
||||
'product_fields_help' => 'Drag and drop fields to change their order',
|
||||
'custom_value1' => 'Custom Value',
|
||||
'custom_value2' => 'Custom Value',
|
||||
'enable_two_factor' => 'Two-Factor Authentication',
|
||||
'enable_two_factor_help' => 'Use your phone to confirm your identity when logging in',
|
||||
'two_factor_setup' => 'Two-Factor Setup',
|
||||
'two_factor_setup_help' => 'Scan the bar code with a :link compatible app or enter the following code: :code',
|
||||
'return_to_settings' => 'Return to Settings',
|
||||
'one_time_password' => 'One Time Password',
|
||||
'set_phone_for_two_factor' => 'Set your phone number to enable.',
|
||||
|
||||
|
||||
);
|
||||
|
||||
|
@ -6,7 +6,8 @@
|
||||
{!! Former::open_for_files()->addClass('warn-on-exit')->rules(array(
|
||||
'first_name' => 'required',
|
||||
'last_name' => 'required',
|
||||
'email' => 'email|required'
|
||||
'email' => 'email|required',
|
||||
'phone' => $user->google_2fa_secret ? 'required' : ''
|
||||
)) !!}
|
||||
|
||||
{{ Former::populate($account) }}
|
||||
@ -15,6 +16,7 @@
|
||||
{{ Former::populateField('email', $user->email) }}
|
||||
{{ Former::populateField('phone', $user->phone) }}
|
||||
{{ Former::populateField('dark_mode', intval($user->dark_mode)) }}
|
||||
{{ Former::populateField('enable_two_factor', $user->google_2fa_secret ? 1 : 0) }}
|
||||
|
||||
@if (Input::has('affiliate'))
|
||||
{{ Former::populateField('referral_code', true) }}
|
||||
@ -48,6 +50,21 @@
|
||||
!!}
|
||||
@endif
|
||||
|
||||
@if ($user->google_2fa_secret)
|
||||
{!! Former::checkbox('enable_two_factor')
|
||||
->help(trans('texts.enable_two_factor_help'))
|
||||
->text(trans('texts.enable'))
|
||||
->value(1) !!}
|
||||
@elseif ($user->phone)
|
||||
{!! Former::plaintext('enable_two_factor')->value(
|
||||
Button::primary(trans('texts.enable'))->asLinkTo(url('settings/enable_two_factor'))->small()
|
||||
)->help('enable_two_factor_help') !!}
|
||||
@else
|
||||
{!! Former::plaintext('enable_two_factor')
|
||||
->value('<span class="text-muted">' . trans('texts.set_phone_for_two_factor') . '</span>') !!}
|
||||
@endif
|
||||
|
||||
|
||||
{!! Former::checkbox('dark_mode')
|
||||
->help(trans('texts.dark_mode_help'))
|
||||
->text(trans('texts.enable'))
|
||||
|
@ -47,6 +47,11 @@
|
||||
<script type="text/javascript">
|
||||
$(function() {
|
||||
$('#email').focus();
|
||||
|
||||
$('.form-signin').submit(function() {
|
||||
$('button.btn-success').prop('disabled', true);
|
||||
});
|
||||
|
||||
})
|
||||
</script>
|
||||
|
||||
|
41
resources/views/auth/two_factor.blade.php
Normal file
41
resources/views/auth/two_factor.blade.php
Normal file
@ -0,0 +1,41 @@
|
||||
@extends('login')
|
||||
|
||||
@section('form')
|
||||
|
||||
@include('partials.warn_session', ['redirectTo' => '/logout?reason=inactive'])
|
||||
|
||||
<div class="container">
|
||||
|
||||
{!! Former::open()
|
||||
->rules(['totp' => 'required'])
|
||||
->addClass('form-signin') !!}
|
||||
|
||||
<h2 class="form-signin-heading">
|
||||
{{ trans('texts.enable_two_factor') }}
|
||||
</h2>
|
||||
<hr class="green">
|
||||
|
||||
{!! Former::text('totp')
|
||||
->placeholder(trans('texts.one_time_password'))
|
||||
->autofocus()
|
||||
->forceValue('')
|
||||
->raw() !!}
|
||||
|
||||
{!! Button::success(trans('texts.submit'))
|
||||
->withAttributes(['id' => 'loginButton', 'class' => 'green'])
|
||||
->large()->submit()->block() !!}
|
||||
|
||||
@if (count($errors->all()))
|
||||
<br/>
|
||||
<div class="alert alert-danger">
|
||||
@foreach ($errors->all() as $error)
|
||||
<li>{{ $error }}</li>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{!! Former::close() !!}
|
||||
|
||||
</div>
|
||||
|
||||
@endsection
|
@ -41,4 +41,13 @@
|
||||
|
||||
{!! Former::close() !!}
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
<script type="text/javascript">
|
||||
$(function() {
|
||||
$('.form-signin').submit(function() {
|
||||
$('button.btn-success').prop('disabled', true);
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
@endsection
|
||||
|
31
resources/views/users/two_factor.blade.php
Normal file
31
resources/views/users/two_factor.blade.php
Normal file
@ -0,0 +1,31 @@
|
||||
@extends('header')
|
||||
|
||||
@section('content')
|
||||
@parent
|
||||
|
||||
|
||||
@if (Utils::isAdmin())
|
||||
@include('accounts.nav', ['selected' => ACCOUNT_USER_DETAILS])
|
||||
@endif
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">{!! trans('texts.two_factor_setup') !!}</h3>
|
||||
</div>
|
||||
<div class="panel-body form-padding-right">
|
||||
<div class="text-center">
|
||||
<img src="{{ $qrCode }}" alt="">
|
||||
<p>{!! trans('texts.two_factor_setup_help', ['code' => $secret, 'link' => link_to('https://github.com/antonioribeiro/google2fa#google-authenticator-apps', 'Google Authenticator', ['target' => '_blank'])]) !!}</p>
|
||||
</div>
|
||||
<p> </p>
|
||||
<center>
|
||||
{!! Button::normal(trans('texts.return_to_settings'))->large()->asLinkTo(url('settings/user_details')) !!}
|
||||
</center>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@stop
|
Loading…
Reference in New Issue
Block a user