Working on 2FA

This commit is contained in:
= 2021-02-20 11:45:20 +11:00
parent 4b1030f881
commit 087129788b
6 changed files with 321 additions and 1 deletions

View File

@ -0,0 +1,63 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Controllers;
use PragmaRX\Google2FA\Google2FA;
use Crypt;
class TwoFactorController extends BaseController
{
public function setupTwoFactor()
{
$user = auth()->user();
if ($user->google_2fa_secret)
return response()->json(['message' => '2FA already enabled'], 400);
elseif(! $user->phone)
return response()->json(['message' => ctrans('texts.set_phone_for_two_factor')], 400);
elseif(! $user->confirmed)
return response()->json(['message' => 'Please confirm your account first'], 400);
$google2fa = new Google2FA();
$secret = $google2fa->generateSecretKey();
$qr_code = $google2fa->getQRCodeGoogleUrl(
config('ninja.app_name')
$user->email,
$secret
);
$data = [
'secret' => $secret,
'qrCode' => $qrCode,
];
return response()->json(['data' => $data], 200);
}
public function enableTwoFactor()
{
$user = auth()->user();
$secret = request()->input('secret');
$oneTimePassword = request()->input('one_time_password');
if (! $secret || ! \Google2FA::verifyKey($secret, $oneTimePassword)) {
return response()->json('message' > ctrans('texts.invalid_one_time_password'));
} elseif (! $user->google_2fa_secret && $user->phone && $user->confirmed) {
$user->google_2fa_secret = encrypt($secret);
$user->save();
}
return response()->json(['message' => ctrans('texts.enabled_two_factor')], 200);
}
}

View File

@ -23,11 +23,16 @@ use App\Http\Requests\User\CreateUserRequest;
use App\Http\Requests\User\DestroyUserRequest;
use App\Http\Requests\User\DetachCompanyUserRequest;
use App\Http\Requests\User\EditUserRequest;
use App\Http\Requests\User\ReconfirmUserRequest;
use App\Http\Requests\User\ShowUserRequest;
use App\Http\Requests\User\StoreUserRequest;
use App\Http\Requests\User\UpdateUserRequest;
use App\Jobs\Company\CreateCompanyToken;
use App\Jobs\Mail\NinjaMailer;
use App\Jobs\Mail\NinjaMailerJob;
use App\Jobs\Mail\NinjaMailerObject;
use App\Jobs\User\UserEmailChanged;
use App\Mail\Admin\VerifyUserObject;
use App\Models\CompanyUser;
use App\Models\User;
use App\Repositories\UserRepository;
@ -685,4 +690,70 @@ class UserController extends BaseController
return response()->json(['message' => ctrans('texts.user_detached')], 200);
}
/**
* Detach an existing user to a company.
*
* @OA\Post(
* path="/api/v1/users/{user}/reconfirm",
* operationId="reconfirmUser",
* tags={"users"},
* summary="Reconfirm an existing user to a company",
* description="Reconfirm an existing user from a company",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(
* name="user",
* in="path",
* description="The user hashed_id",
* example="FD767dfd7",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Success response",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
* @param ReconfirmUserRequest $request
* @param User $user
* @return \Illuminate\Http\JsonResponse
*/
public function reconfirm(ReconfirmUserRequest $request, User $user)
{
$user->confirmation_code = $this->createDbHash($user->company()->db);
$user->save();
$nmo = new NinjaMailerObject;
$nmo->mailable = new NinjaMailer((new VerifyUserObject($user, $user->company()))->build());
$nmo->company = $user->company();
$nmo->to_user = $user;
$nmo->settings = $user->company->settings;
NinjaMailerJob::dispatch($nmo);
return response()->json(['message' => ctrans('texts.confirmation_resent')], 200);
}
}

View File

@ -0,0 +1,28 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Requests\User;
use App\Http\Requests\Request;
use App\Models\User;
class ReconfirmUserRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->Admin();
}
}

View File

@ -32,6 +32,7 @@
"ext-libxml": "*",
"asgrim/ofxparser": "^1.2",
"authorizenet/authorizenet": "^2.0",
"bacon/bacon-qr-code": "^2.0",
"beganovich/snappdf": "^1.0",
"checkout/checkout-sdk-php": "^1.0",
"cleverit/ubl_invoice": "^1.3",
@ -59,6 +60,7 @@
"maennchen/zipstream-php": "^1.2",
"nwidart/laravel-modules": "^8.0",
"omnipay/paypal": "^3.0",
"pragmarx/google2fa": "^8.0",
"predis/predis": "^1.1",
"sentry/sentry-laravel": "^2",
"stripe/stripe-php": "^7.50",

154
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "7b93ec16ae5791e0767c92eaf7061cc7",
"content-hash": "fd9ff106339b9e720558827e1c1ede1a",
"packages": [
{
"name": "asgrim/ofxparser",
@ -204,6 +204,59 @@
},
"time": "2021-01-29T19:17:51+00:00"
},
{
"name": "bacon/bacon-qr-code",
"version": "2.0.3",
"source": {
"type": "git",
"url": "https://github.com/Bacon/BaconQrCode.git",
"reference": "3e9d791b67d0a2912922b7b7c7312f4b37af41e4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/3e9d791b67d0a2912922b7b7c7312f4b37af41e4",
"reference": "3e9d791b67d0a2912922b7b7c7312f4b37af41e4",
"shasum": ""
},
"require": {
"dasprid/enum": "^1.0.3",
"ext-iconv": "*",
"php": "^7.1 || ^8.0"
},
"require-dev": {
"phly/keep-a-changelog": "^1.4",
"phpunit/phpunit": "^7 | ^8 | ^9",
"squizlabs/php_codesniffer": "^3.4"
},
"suggest": {
"ext-imagick": "to generate QR code images"
},
"type": "library",
"autoload": {
"psr-4": {
"BaconQrCode\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-2-Clause"
],
"authors": [
{
"name": "Ben Scholzen 'DASPRiD'",
"email": "mail@dasprids.de",
"homepage": "https://dasprids.de/",
"role": "Developer"
}
],
"description": "BaconQrCode is a QR code generator for PHP.",
"homepage": "https://github.com/Bacon/BaconQrCode",
"support": {
"issues": "https://github.com/Bacon/BaconQrCode/issues",
"source": "https://github.com/Bacon/BaconQrCode/tree/2.0.3"
},
"time": "2020-10-30T02:02:47+00:00"
},
{
"name": "beganovich/snappdf",
"version": "v1.5.0",
@ -1012,6 +1065,53 @@
},
"time": "2020-07-03T08:02:12+00:00"
},
{
"name": "dasprid/enum",
"version": "1.0.3",
"source": {
"type": "git",
"url": "https://github.com/DASPRiD/Enum.git",
"reference": "5abf82f213618696dda8e3bf6f64dd042d8542b2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/DASPRiD/Enum/zipball/5abf82f213618696dda8e3bf6f64dd042d8542b2",
"reference": "5abf82f213618696dda8e3bf6f64dd042d8542b2",
"shasum": ""
},
"require-dev": {
"phpunit/phpunit": "^7 | ^8 | ^9",
"squizlabs/php_codesniffer": "^3.4"
},
"type": "library",
"autoload": {
"psr-4": {
"DASPRiD\\Enum\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-2-Clause"
],
"authors": [
{
"name": "Ben Scholzen 'DASPRiD'",
"email": "mail@dasprids.de",
"homepage": "https://dasprids.de/",
"role": "Developer"
}
],
"description": "PHP 7.1 enum implementation",
"keywords": [
"enum",
"map"
],
"support": {
"issues": "https://github.com/DASPRiD/Enum/issues",
"source": "https://github.com/DASPRiD/Enum/tree/1.0.3"
},
"time": "2020-10-02T16:03:48+00:00"
},
{
"name": "dnoegel/php-xdg-base-dir",
"version": "v0.1.1",
@ -5360,6 +5460,58 @@
],
"time": "2021-01-25T19:02:05+00:00"
},
{
"name": "pragmarx/google2fa",
"version": "8.0.0",
"source": {
"type": "git",
"url": "https://github.com/antonioribeiro/google2fa.git",
"reference": "26c4c5cf30a2844ba121760fd7301f8ad240100b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/26c4c5cf30a2844ba121760fd7301f8ad240100b",
"reference": "26c4c5cf30a2844ba121760fd7301f8ad240100b",
"shasum": ""
},
"require": {
"paragonie/constant_time_encoding": "^1.0|^2.0",
"php": "^7.1|^8.0"
},
"require-dev": {
"phpstan/phpstan": "^0.12.18",
"phpunit/phpunit": "^7.5.15|^8.5|^9.0"
},
"type": "library",
"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": [
"2fa",
"Authentication",
"Two Factor Authentication",
"google2fa"
],
"support": {
"issues": "https://github.com/antonioribeiro/google2fa/issues",
"source": "https://github.com/antonioribeiro/google2fa/tree/8.0.0"
},
"time": "2020-04-05T10:47:18+00:00"
},
{
"name": "predis/predis",
"version": "v1.1.6",

View File

@ -146,6 +146,9 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a
Route::resource('tokens', 'TokenController')->middleware('password_protected'); // name = (tokens. index / create / show / update / destroy / edit
Route::post('tokens/bulk', 'TokenController@bulk')->name('tokens.bulk')->middleware('password_protected');
Route::get('settings/enable_two_factor', 'TwoFactorController@setupTwoFactor');
Route::post('settings/enable_two_factor', 'TwoFactorController@enableTwoFactor');
Route::resource('vendors', 'VendorController'); // name = (vendors. index / create / show / update / destroy / edit
Route::post('vendors/bulk', 'VendorController@bulk')->name('vendors.bulk');
Route::put('vendors/{vendor}/upload', 'VendorController@upload');
@ -156,6 +159,7 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a
Route::post('users/{user}/attach_to_company', 'UserController@attach')->middleware('password_protected');
Route::delete('users/{user}/detach_from_company', 'UserController@detach')->middleware('password_protected');
Route::post('users/bulk', 'UserController@bulk')->name('users.bulk')->middleware('password_protected');
Route::post('/user/{user}/reconfirm', 'UserController@reconfirm')->middleware('password_protected');
Route::resource('webhooks', 'WebhookController');
Route::post('webhooks/bulk', 'WebhookController@bulk')->name('webhooks.bulk');