1
0
mirror of https://github.com/Bubka/2FAuth.git synced 2025-06-30 12:40:04 +02:00

Apply Laravel Pint fixes

This commit is contained in:
Bubka
2022-11-22 15:15:52 +01:00
parent d84dd6659e
commit d6fd8e3c52
178 changed files with 2409 additions and 2899 deletions
app
Api
Console
Events
Exceptions
Extensions
Facades
Factories
Helpers
Http
Listeners
Models
Notifications
Providers
Rules
Services
pint.json
tests
Api
Classes
CreatesApplication.php
Feature
FeatureTestCase.phpModelTestCase.phpTestCase.php
Unit

@ -2,18 +2,16 @@
namespace App\Api\v1\Controllers; namespace App\Api\v1\Controllers;
use App\Models\Group;
use App\Facades\Groups;
use App\Api\v1\Requests\GroupStoreRequest;
use App\Api\v1\Requests\GroupAssignRequest; use App\Api\v1\Requests\GroupAssignRequest;
use App\Api\v1\Requests\GroupStoreRequest;
use App\Api\v1\Resources\GroupResource; use App\Api\v1\Resources\GroupResource;
use App\Api\v1\Resources\TwoFAccountCollection; use App\Api\v1\Resources\TwoFAccountCollection;
use App\Facades\Groups;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\App; use App\Models\Group;
class GroupController extends Controller class GroupController extends Controller
{ {
/** /**
* Display a listing of the resource. * Display a listing of the resource.
* *
@ -26,7 +24,6 @@ class GroupController extends Controller
return GroupResource::collection($groups); return GroupResource::collection($groups);
} }
/** /**
* Store a newly created resource in storage. * Store a newly created resource in storage.
* *
@ -44,7 +41,6 @@ class GroupController extends Controller
->setStatusCode(201); ->setStatusCode(201);
} }
/** /**
* Display the specified resource. * Display the specified resource.
* *
@ -56,12 +52,11 @@ class GroupController extends Controller
return new GroupResource($group); return new GroupResource($group);
} }
/** /**
* Update the specified resource in storage. * Update the specified resource in storage.
* *
* @param \App\Api\v1\Requests\GroupStoreRequest $request * @param \App\Api\v1\Requests\GroupStoreRequest $request
* @param \App\Models\Group $group * @param \App\Models\Group $group
* @return \App\Api\v1\Resources\GroupResource * @return \App\Api\v1\Resources\GroupResource
*/ */
public function update(GroupStoreRequest $request, Group $group) public function update(GroupStoreRequest $request, Group $group)
@ -71,10 +66,8 @@ class GroupController extends Controller
Groups::update($group, $validated); Groups::update($group, $validated);
return new GroupResource($group); return new GroupResource($group);
} }
/** /**
* Associate the specified accounts with the group * Associate the specified accounts with the group
* *
@ -87,12 +80,10 @@ class GroupController extends Controller
$validated = $request->validated(); $validated = $request->validated();
Groups::assign($validated['ids'], $group); Groups::assign($validated['ids'], $group);
return new GroupResource($group); return new GroupResource($group);
} }
/** /**
* Get accounts assign to the group * Get accounts assign to the group
* *
@ -102,12 +93,10 @@ class GroupController extends Controller
public function accounts(Group $group) public function accounts(Group $group)
{ {
$twofaccounts = Groups::getAccounts($group); $twofaccounts = Groups::getAccounts($group);
return new TwoFAccountCollection($twofaccounts); return new TwoFAccountCollection($twofaccounts);
} }
/** /**
* Remove the specified resource from storage. * Remove the specified resource from storage.
* *
@ -120,5 +109,4 @@ class GroupController extends Controller
return response()->json(null, 204); return response()->json(null, 204);
} }
} }

@ -2,12 +2,11 @@
namespace App\Api\v1\Controllers; namespace App\Api\v1\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Services\LogoService; use App\Services\LogoService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Storage;
class IconController extends Controller class IconController extends Controller
{ {
@ -31,7 +30,6 @@ class IconController extends Controller
: response()->json(['message' => __('errors.file_upload_failed')], 500); : response()->json(['message' => __('errors.file_upload_failed')], 500);
} }
/** /**
* Fetch a logo * Fetch a logo
* *
@ -43,26 +41,25 @@ class IconController extends Controller
$this->validate($request, [ $this->validate($request, [
'service' => 'string|regex:/^[^:]+$/i', 'service' => 'string|regex:/^[^:]+$/i',
]); ]);
$logoService = App::make(LogoService::class); $logoService = App::make(LogoService::class);
$icon = $logoService->getIcon($request->service); $icon = $logoService->getIcon($request->service);
return $icon return $icon
? response()->json(['filename' => $icon], 201) ? response()->json(['filename' => $icon], 201)
: response()->json(null, 204); : response()->json(null, 204);
} }
/** /**
* delete an icon * delete an icon
* *
* @param string $icon * @param string $icon
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
*/ */
public function delete(string $icon) public function delete(string $icon)
{ {
Storage::disk('icons')->delete($icon); Storage::disk('icons')->delete($icon);
return response()->json(null, 204); return response()->json(null, 204);
} }
} }

@ -2,15 +2,13 @@
namespace App\Api\v1\Controllers; namespace App\Api\v1\Controllers;
use App\Models\TwoFAccount;
use App\Facades\QrCode;
use App\Api\v1\Requests\QrCodeDecodeRequest; use App\Api\v1\Requests\QrCodeDecodeRequest;
use App\Facades\QrCode;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\TwoFAccount;
class QrCodeController extends Controller class QrCodeController extends Controller
{ {
/** /**
* Show a QR code image * Show a QR code image
* *
@ -24,7 +22,6 @@ class QrCodeController extends Controller
return response()->json(['qrcode' => QrCode::encode($uri)], 200); return response()->json(['qrcode' => QrCode::encode($uri)], 200);
} }
/** /**
* Decode an uploaded QR Code image * Decode an uploaded QR Code image
* *
@ -39,5 +36,4 @@ class QrCodeController extends Controller
? response()->json(['data' => QrCode::decode($file)], 200) ? response()->json(['data' => QrCode::decode($file)], 200)
: response()->json(['message' => __('errors.file_upload_failed')], 500); : response()->json(['message' => __('errors.file_upload_failed')], 500);
} }
}
}

@ -2,38 +2,36 @@
namespace App\Api\v1\Controllers; namespace App\Api\v1\Controllers;
use App\Facades\Settings;
use App\Api\v1\Requests\SettingStoreRequest; use App\Api\v1\Requests\SettingStoreRequest;
use App\Api\v1\Requests\SettingUpdateRequest; use App\Api\v1\Requests\SettingUpdateRequest;
use App\Facades\Settings;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
class SettingController extends Controller class SettingController extends Controller
{ {
/** /**
* List all settings * List all settings
* *
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
*/ */
public function index() public function index()
{ {
$settings = Settings::all(); $settings = Settings::all();
$settingsResources = collect([]); $settingsResources = collect([]);
$settings->each(function (mixed $item, string $key) use ($settingsResources) { $settings->each(function (mixed $item, string $key) use ($settingsResources) {
$settingsResources->push([ $settingsResources->push([
'key' => $key, 'key' => $key,
'value' => $item 'value' => $item,
]); ]);
}); });
return response()->json($settingsResources->all(), 200); return response()->json($settingsResources->all(), 200);
} }
/** /**
* Display a setting * Display a setting
* *
* @param string $settingName * @param string $settingName
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
*/ */
public function show($settingName) public function show($settingName)
@ -45,16 +43,15 @@ class SettingController extends Controller
} }
return response()->json([ return response()->json([
'key' => $settingName, 'key' => $settingName,
'value' => $setting 'value' => $setting,
], 200); ], 200);
} }
/** /**
* Store a setting * Store a setting
* *
* @param \App\Api\v1\Requests\SettingStoreRequest $request * @param \App\Api\v1\Requests\SettingStoreRequest $request
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
*/ */
public function store(SettingStoreRequest $request) public function store(SettingStoreRequest $request)
@ -64,16 +61,15 @@ class SettingController extends Controller
Settings::set($validated['key'], $validated['value']); Settings::set($validated['key'], $validated['value']);
return response()->json([ return response()->json([
'key' => $validated['key'], 'key' => $validated['key'],
'value' => $validated['value'] 'value' => $validated['value'],
], 201); ], 201);
} }
/** /**
* Update a setting * Update a setting
* *
* @param \App\Api\v1\Requests\SettingUpdateRequest $request * @param \App\Api\v1\Requests\SettingUpdateRequest $request
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
*/ */
public function update(SettingUpdateRequest $request, string $settingName) public function update(SettingUpdateRequest $request, string $settingName)
@ -83,17 +79,15 @@ class SettingController extends Controller
Settings::set($settingName, $validated['value']); Settings::set($settingName, $validated['value']);
return response()->json([ return response()->json([
'key' => $settingName, 'key' => $settingName,
'value' => $validated['value'] 'value' => $validated['value'],
], 200); ], 200);
} }
/** /**
* Delete a setting * Delete a setting
* *
* @param string $settingName * @param string $settingName
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
*/ */
public function destroy(string $settingName) public function destroy(string $settingName)
@ -105,16 +99,15 @@ class SettingController extends Controller
} }
$optionsConfig = config('2fauth.options'); $optionsConfig = config('2fauth.options');
if(array_key_exists($settingName, $optionsConfig)) { if (array_key_exists($settingName, $optionsConfig)) {
return response()->json( return response()->json(
['message' => 'bad request', ['message' => 'bad request',
'reason' => [__('errors.delete_user_setting_only')] 'reason' => [__('errors.delete_user_setting_only')],
], 400); ], 400);
} }
Settings::delete($settingName); Settings::delete($settingName);
return response()->json(null, 204); return response()->json(null, 204);
} }
} }

@ -2,26 +2,25 @@
namespace App\Api\v1\Controllers; namespace App\Api\v1\Controllers;
use App\Models\TwoFAccount; use App\Api\v1\Requests\TwoFAccountBatchRequest;
use App\Api\v1\Requests\TwoFAccountDynamicRequest;
use App\Api\v1\Requests\TwoFAccountImportRequest;
use App\Api\v1\Requests\TwoFAccountReorderRequest; use App\Api\v1\Requests\TwoFAccountReorderRequest;
use App\Api\v1\Requests\TwoFAccountStoreRequest; use App\Api\v1\Requests\TwoFAccountStoreRequest;
use App\Api\v1\Requests\TwoFAccountUpdateRequest; use App\Api\v1\Requests\TwoFAccountUpdateRequest;
use App\Api\v1\Requests\TwoFAccountImportRequest;
use App\Api\v1\Requests\TwoFAccountBatchRequest;
use App\Api\v1\Requests\TwoFAccountUriRequest; use App\Api\v1\Requests\TwoFAccountUriRequest;
use App\Api\v1\Requests\TwoFAccountDynamicRequest;
use App\Api\v1\Resources\TwoFAccountCollection; use App\Api\v1\Resources\TwoFAccountCollection;
use App\Api\v1\Resources\TwoFAccountReadResource; use App\Api\v1\Resources\TwoFAccountReadResource;
use App\Api\v1\Resources\TwoFAccountStoreResource; use App\Api\v1\Resources\TwoFAccountStoreResource;
use App\Facades\Groups; use App\Facades\Groups;
use App\Facades\TwoFAccounts; use App\Facades\TwoFAccounts;
use Illuminate\Support\Arr;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\TwoFAccount;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
class TwoFAccountController extends Controller class TwoFAccountController extends Controller
{ {
/** /**
* List all resources * List all resources
* *
@ -32,12 +31,10 @@ class TwoFAccountController extends Controller
return new TwoFAccountCollection(TwoFAccount::ordered()->get()); return new TwoFAccountCollection(TwoFAccount::ordered()->get());
} }
/** /**
* Display a 2FA account * Display a 2FA account
* *
* @param \App\Models\TwoFAccount $twofaccount * @param \App\Models\TwoFAccount $twofaccount
*
* @return \App\Api\v1\Resources\TwoFAccountReadResource * @return \App\Api\v1\Resources\TwoFAccountReadResource
*/ */
public function show(TwoFAccount $twofaccount) public function show(TwoFAccount $twofaccount)
@ -45,7 +42,6 @@ class TwoFAccountController extends Controller
return new TwoFAccountReadResource($twofaccount); return new TwoFAccountReadResource($twofaccount);
} }
/** /**
* Store a new 2FA account * Store a new 2FA account
* *
@ -60,13 +56,12 @@ class TwoFAccountController extends Controller
// - The advanced form has been used and all individual parameters // - The advanced form has been used and all individual parameters
// -> We use the parameters array to define the account // -> We use the parameters array to define the account
$validated = $request->validated(); $validated = $request->validated();
$twofaccount = new TwoFAccount; $twofaccount = new TwoFAccount;
if (Arr::has($validated, 'uri')) { if (Arr::has($validated, 'uri')) {
$twofaccount->fillWithURI($validated['uri'], Arr::get($validated, 'custom_otp') === TwoFAccount::STEAM_TOTP); $twofaccount->fillWithURI($validated['uri'], Arr::get($validated, 'custom_otp') === TwoFAccount::STEAM_TOTP);
} } else {
else {
$twofaccount->fillWithOtpParameters($validated); $twofaccount->fillWithOtpParameters($validated);
} }
$twofaccount->save(); $twofaccount->save();
@ -79,8 +74,6 @@ class TwoFAccountController extends Controller
->setStatusCode(201); ->setStatusCode(201);
} }
/** /**
* Update a 2FA account * Update a 2FA account
* *
@ -98,10 +91,8 @@ class TwoFAccountController extends Controller
return (new TwoFAccountReadResource($twofaccount)) return (new TwoFAccountReadResource($twofaccount))
->response() ->response()
->setStatusCode(200); ->setStatusCode(200);
} }
/** /**
* Convert a migration resource to a valid TwoFAccounts collection * Convert a migration resource to a valid TwoFAccounts collection
* *
@ -114,17 +105,15 @@ class TwoFAccountController extends Controller
if (Arr::has($validated, 'file')) { if (Arr::has($validated, 'file')) {
$migrationResource = $request->file('file'); $migrationResource = $request->file('file');
return $migrationResource instanceof \Illuminate\Http\UploadedFile return $migrationResource instanceof \Illuminate\Http\UploadedFile
? new TwoFAccountCollection(TwoFAccounts::migrate($migrationResource->get())) ? new TwoFAccountCollection(TwoFAccounts::migrate($migrationResource->get()))
: response()->json(['message' => __('errors.file_upload_failed')], 500); : response()->json(['message' => __('errors.file_upload_failed')], 500);
} } else {
else {
return new TwoFAccountCollection(TwoFAccounts::migrate($request->payload)); return new TwoFAccountCollection(TwoFAccounts::migrate($request->payload));
} }
} }
/** /**
* Save 2FA accounts order * Save 2FA accounts order
* *
@ -140,10 +129,9 @@ class TwoFAccountController extends Controller
return response()->json(['message' => 'order saved'], 200); return response()->json(['message' => 'order saved'], 200);
} }
/** /**
* Preview account using an uri, without any db moves * Preview account using an uri, without any db moves
* *
* @param \App\Api\v1\Requests\TwoFAccountUriRequest $request * @param \App\Api\v1\Requests\TwoFAccountUriRequest $request
* @return \App\Api\v1\Resources\TwoFAccountStoreResource * @return \App\Api\v1\Resources\TwoFAccountStoreResource
*/ */
@ -155,12 +143,11 @@ class TwoFAccountController extends Controller
return new TwoFAccountStoreResource($twofaccount); return new TwoFAccountStoreResource($twofaccount);
} }
/** /**
* Get a One-Time Password * Get a One-Time Password
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param string|null $id * @param string|null $id
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
*/ */
public function otp(Request $request, $id = null) public function otp(Request $request, $id = null)
@ -173,17 +160,16 @@ class TwoFAccountController extends Controller
} }
// The request input is an uri // The request input is an uri
else if ( $request->has('uri') ) { elseif ($request->has('uri')) {
// return 404 if uri is provided with any parameter other than otp_type // return 404 if uri is provided with any parameter other than otp_type
if ((count($inputs) == 2 && $request->missing('custom_otp')) || count($inputs) > 2) { if ((count($inputs) == 2 && $request->missing('custom_otp')) || count($inputs) > 2) {
return response()->json([ return response()->json([
'message' => 'bad request', 'message' => 'bad request',
'reason' => ['uri' => __('validation.onlyCustomOtpWithUri')] 'reason' => ['uri' => __('validation.onlyCustomOtpWithUri')],
], 400); ], 400);
} } else {
else {
$validatedData = $request->validate((new TwoFAccountUriRequest)->rules()); $validatedData = $request->validate((new TwoFAccountUriRequest)->rules());
$twofaccount = new TwoFAccount; $twofaccount = new TwoFAccount;
$twofaccount->fillWithURI($validatedData['uri'], Arr::get($validatedData, 'custom_otp') === TwoFAccount::STEAM_TOTP, true); $twofaccount->fillWithURI($validatedData['uri'], Arr::get($validatedData, 'custom_otp') === TwoFAccount::STEAM_TOTP, true);
} }
} }
@ -191,14 +177,13 @@ class TwoFAccountController extends Controller
// The request inputs should define an account // The request inputs should define an account
else { else {
$validatedData = $request->validate((new TwoFAccountStoreRequest)->rules()); $validatedData = $request->validate((new TwoFAccountStoreRequest)->rules());
$twofaccount = new TwoFAccount(); $twofaccount = new TwoFAccount();
$twofaccount->fillWithOtpParameters($validatedData, true); $twofaccount->fillWithOtpParameters($validatedData, true);
} }
return response()->json($twofaccount->getOTP(), 200); return response()->json($twofaccount->getOTP(), 200);
} }
/** /**
* A simple and light method to get the account count. * A simple and light method to get the account count.
* *
@ -207,33 +192,30 @@ class TwoFAccountController extends Controller
*/ */
public function count(Request $request) public function count(Request $request)
{ {
return response()->json([ 'count' => TwoFAccount::count() ], 200); return response()->json(['count' => TwoFAccount::count()], 200);
} }
/** /**
*
* Withdraw one or more accounts from their group * Withdraw one or more accounts from their group
* *
* @param \App\Api\v1\Requests\TwoFAccountBatchRequest $request * @param \App\Api\v1\Requests\TwoFAccountBatchRequest $request
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
*/ */
public function withdraw(TwoFAccountBatchRequest $request) public function withdraw(TwoFAccountBatchRequest $request)
{ {
$validated = $request->validated(); $validated = $request->validated();
if ($this->tooManyIds($validated['ids'])) { if ($this->tooManyIds($validated['ids'])) {
return response()->json([ return response()->json([
'message' => 'bad request', 'message' => 'bad request',
'reason' => [__('errors.too_many_ids')] 'reason' => [__('errors.too_many_ids')],
], 400); ], 400);
} }
TwoFAccounts::withdraw($validated['ids']); TwoFAccounts::withdraw($validated['ids']);
return response()->json([ 'message' => 'accounts withdrawn' ], 200);
}
return response()->json(['message' => 'accounts withdrawn'], 200);
}
/** /**
* Remove the specified resource from storage. * Remove the specified resource from storage.
@ -248,7 +230,6 @@ class TwoFAccountController extends Controller
return response()->json(null, 204); return response()->json(null, 204);
} }
/** /**
* Remove the specified resources from storage. * Remove the specified resources from storage.
* *
@ -262,7 +243,7 @@ class TwoFAccountController extends Controller
if ($this->tooManyIds($validated['ids'])) { if ($this->tooManyIds($validated['ids'])) {
return response()->json([ return response()->json([
'message' => 'bad request', 'message' => 'bad request',
'reason' => [__('errors.too_many_ids')] 'reason' => [__('errors.too_many_ids')],
], 400); ], 400);
} }
@ -271,19 +252,17 @@ class TwoFAccountController extends Controller
return response()->json(null, 204); return response()->json(null, 204);
} }
/** /**
* Checks ids length * Checks ids length
* *
* @param string $ids comma-separated ids * @param string $ids comma-separated ids
* @return bool whether or not the number of ids is acceptable * @return bool whether or not the number of ids is acceptable
*/ */
private function tooManyIds(string $ids) : bool private function tooManyIds(string $ids) : bool
{ {
$arIds = explode(',', $ids, 100); $arIds = explode(',', $ids, 100);
$nb = count($arIds); $nb = count($arIds);
return $nb > 99 ? true : false; return $nb > 99 ? true : false;
} }
} }

@ -2,16 +2,16 @@
namespace App\Api\v1\Controllers; namespace App\Api\v1\Controllers;
use App\Models\User;
use App\Api\v1\Resources\UserResource; use App\Api\v1\Resources\UserResource;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request; use Illuminate\Http\Request;
class UserController extends Controller class UserController extends Controller
{ {
/** /**
* Get detailed information about a user * Get detailed information about a user
* *
* @return \App\Api\v1\Resources\UserResource|\Illuminate\Http\JsonResponse * @return \App\Api\v1\Resources\UserResource|\Illuminate\Http\JsonResponse
*/ */
public function show(Request $request) public function show(Request $request)
@ -24,6 +24,5 @@ class UserController extends Controller
return $user return $user
? new UserResource($user) ? new UserResource($user)
: response()->json(['name' => null], 200); : response()->json(['name' => null], 200);
} }
} }

@ -25,8 +25,8 @@ class GroupAssignRequest extends FormRequest
public function rules() public function rules()
{ {
return [ return [
'ids' => 'required|array', 'ids' => 'required|array',
'ids.*' => 'integer' 'ids.*' => 'integer',
]; ];
} }
} }

@ -28,4 +28,4 @@ class QrCodeDecodeRequest extends FormRequest
'qrcode' => 'required|image', 'qrcode' => 'required|image',
]; ];
} }
} }

@ -25,7 +25,7 @@ class SettingStoreRequest extends FormRequest
public function rules() public function rules()
{ {
return [ return [
'key' => 'required|alpha|max:128|unique:options,key', 'key' => 'required|alpha|max:128|unique:options,key',
'value' => 'required', 'value' => 'required',
]; ];
} }

@ -28,4 +28,4 @@ class TwoFAccountBatchRequest extends FormRequest
'ids' => 'required|string|regex:/^\d+(,{1}\d+)*$/i', 'ids' => 'required|string|regex:/^\d+(,{1}\d+)*$/i',
]; ];
} }
} }

@ -2,27 +2,27 @@
namespace App\Api\v1\Requests; namespace App\Api\v1\Requests;
use Illuminate\Support\Arr;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
class TwoFAccountDynamicRequest extends FormRequest class TwoFAccountDynamicRequest extends FormRequest
{ {
/** /**
* Determine if the user is authorized to make this request. * Determine if the user is authorized to make this request.
* *
* @return bool * @return bool
*/ */
public function authorize() public function authorize()
{ {
return Auth::check(); return Auth::check();
} }
/** /**
* Get the validation rules that apply to the request. * Get the validation rules that apply to the request.
* *
* @return array * @return array
*/ */
public function rules() public function rules()
{ {
$rules = Arr::has($this->validationData(), 'uri') $rules = Arr::has($this->validationData(), 'uri')
@ -32,7 +32,6 @@ class TwoFAccountDynamicRequest extends FormRequest
return $rules; return $rules;
} }
/** /**
* Prepare the data for validation. * Prepare the data for validation.
* *
@ -41,8 +40,8 @@ class TwoFAccountDynamicRequest extends FormRequest
protected function prepareForValidation() protected function prepareForValidation()
{ {
$this->merge([ $this->merge([
'otp_type' => strtolower($this->otp_type), 'otp_type' => strtolower($this->otp_type),
'algorithm' => strtolower($this->algorithm), 'algorithm' => strtolower($this->algorithm),
]); ]);
} }
} }

@ -26,7 +26,7 @@ class TwoFAccountImportRequest extends FormRequest
{ {
return [ return [
'payload' => 'required_without:file|string', 'payload' => 'required_without:file|string',
'file' => 'required_without:payload|mimes:txt,json,csv', 'file' => 'required_without:payload|mimes:txt,json,csv',
]; ];
} }
} }

@ -25,19 +25,18 @@ class TwoFAccountStoreRequest extends FormRequest
public function rules() public function rules()
{ {
return [ return [
'service' => 'nullable|string|regex:/^[^:]+$/i', 'service' => 'nullable|string|regex:/^[^:]+$/i',
'account' => 'required|string|regex:/^[^:]+$/i', 'account' => 'required|string|regex:/^[^:]+$/i',
'icon' => 'nullable|string', 'icon' => 'nullable|string',
'otp_type' => 'required|string|in:totp,hotp,steamtotp', 'otp_type' => 'required|string|in:totp,hotp,steamtotp',
'secret' => ['string', 'bail', new \App\Rules\IsBase32Encoded], 'secret' => ['string', 'bail', new \App\Rules\IsBase32Encoded],
'digits' => 'nullable|integer|between:5,10', 'digits' => 'nullable|integer|between:5,10',
'algorithm' => 'nullable|string|in:sha1,sha256,sha512,md5', 'algorithm' => 'nullable|string|in:sha1,sha256,sha512,md5',
'period' => 'nullable|integer|min:1', 'period' => 'nullable|integer|min:1',
'counter' => 'nullable|integer|min:0', 'counter' => 'nullable|integer|min:0',
]; ];
} }
/** /**
* Prepare the data for validation. * Prepare the data for validation.
* *
@ -46,7 +45,7 @@ class TwoFAccountStoreRequest extends FormRequest
protected function prepareForValidation() protected function prepareForValidation()
{ {
$this->merge([ $this->merge([
'otp_type' => strtolower($this->otp_type), 'otp_type' => strtolower($this->otp_type),
'algorithm' => strtolower($this->algorithm), 'algorithm' => strtolower($this->algorithm),
]); ]);
} }

@ -25,19 +25,18 @@ class TwoFAccountUpdateRequest extends FormRequest
public function rules() public function rules()
{ {
return [ return [
'service' => 'present|nullable|string|regex:/^[^:]+$/i', 'service' => 'present|nullable|string|regex:/^[^:]+$/i',
'account' => 'required|string|regex:/^[^:]+$/i', 'account' => 'required|string|regex:/^[^:]+$/i',
'icon' => 'present|nullable|string', 'icon' => 'present|nullable|string',
'otp_type' => 'required|string|in:totp,hotp,steamtotp', 'otp_type' => 'required|string|in:totp,hotp,steamtotp',
'secret' => ['present', 'string', 'bail', new \App\Rules\IsBase32Encoded], 'secret' => ['present', 'string', 'bail', new \App\Rules\IsBase32Encoded],
'digits' => 'present|integer|between:5,10', 'digits' => 'present|integer|between:5,10',
'algorithm' => 'present|string|in:sha1,sha256,sha512,md5', 'algorithm' => 'present|string|in:sha1,sha256,sha512,md5',
'period' => 'nullable|integer|min:1', 'period' => 'nullable|integer|min:1',
'counter' => 'nullable|integer|min:0', 'counter' => 'nullable|integer|min:0',
]; ];
} }
/** /**
* Prepare the data for validation. * Prepare the data for validation.
* *
@ -46,7 +45,7 @@ class TwoFAccountUpdateRequest extends FormRequest
protected function prepareForValidation() protected function prepareForValidation()
{ {
$this->merge([ $this->merge([
'otp_type' => strtolower($this->otp_type), 'otp_type' => strtolower($this->otp_type),
'algorithm' => strtolower($this->algorithm), 'algorithm' => strtolower($this->algorithm),
]); ]);
} }

@ -30,7 +30,6 @@ class TwoFAccountUriRequest extends FormRequest
]; ];
} }
/** /**
* Prepare the data for validation. * Prepare the data for validation.
* *
@ -42,4 +41,4 @@ class TwoFAccountUriRequest extends FormRequest
'custom_otp' => strtolower($this->custom_otp), 'custom_otp' => strtolower($this->custom_otp),
]); ]);
} }
} }

@ -20,9 +20,9 @@ class GroupResource extends JsonResource
public function toArray($request) public function toArray($request)
{ {
return [ return [
'id' => $this->id, 'id' => $this->id,
'name' => $this->name, 'name' => $this->name,
'twofaccounts_count' => is_null($this->twofaccounts_count) ? 0 : $this->twofaccounts_count, 'twofaccounts_count' => is_null($this->twofaccounts_count) ? 0 : $this->twofaccounts_count,
]; ];
} }
} }

@ -3,7 +3,6 @@
namespace App\Api\v1\Resources; namespace App\Api\v1\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection; use Illuminate\Http\Resources\Json\ResourceCollection;
use App\Api\v1\Resources\TwoFAccountReadResource;
class TwoFAccountCollection extends ResourceCollection class TwoFAccountCollection extends ResourceCollection
{ {
@ -14,7 +13,6 @@ class TwoFAccountCollection extends ResourceCollection
*/ */
public $collects = TwoFAccountReadResource::class; public $collects = TwoFAccountReadResource::class;
/** /**
* Transform the resource collection into an array. * Transform the resource collection into an array.
* *
@ -27,10 +25,10 @@ class TwoFAccountCollection extends ResourceCollection
// The underlying TwoFAccountReadResource hides the secret only when withSecret == false. // The underlying TwoFAccountReadResource hides the secret only when withSecret == false.
// When withSecret is provided the underlying resource will return secret according to the parameter value // When withSecret is provided the underlying resource will return secret according to the parameter value
// If no withSecret is set we force it to false to ensure the secret will not being returned. // If no withSecret is set we force it to false to ensure the secret will not being returned.
if (!$request->has('withSecret')) { if (! $request->has('withSecret')) {
$request->merge(['withSecret' => false]); $request->merge(['withSecret' => false]);
} }
return $this->collection; return $this->collection;
} }
} }

@ -18,10 +18,10 @@ class TwoFAccountReadResource extends TwoFAccountStoreResource
{ {
return array_merge( return array_merge(
[ [
'id' => (int) $this->id, 'id' => (int) $this->id,
'group_id' => is_null($this->group_id) ? null : (int) $this->group_id, 'group_id' => is_null($this->group_id) ? null : (int) $this->group_id,
], ],
parent::toArray($request) parent::toArray($request)
); );
} }
} }

@ -26,18 +26,18 @@ class TwoFAccountStoreResource extends JsonResource
public function toArray($request) public function toArray($request)
{ {
return [ return [
'otp_type' => $this->otp_type, 'otp_type' => $this->otp_type,
'account' => $this->account, 'account' => $this->account,
'service' => $this->service, 'service' => $this->service,
'icon' => $this->icon, 'icon' => $this->icon,
'secret' => $this->when( 'secret' => $this->when(
!$request->has('withSecret') || (int) filter_var($request->input('withSecret'), FILTER_VALIDATE_BOOLEAN) == 1, ! $request->has('withSecret') || (int) filter_var($request->input('withSecret'), FILTER_VALIDATE_BOOLEAN) == 1,
$this->secret $this->secret
), ),
'digits' => (int) $this->digits, 'digits' => (int) $this->digits,
'algorithm' => $this->algorithm, 'algorithm' => $this->algorithm,
'period' => is_null($this->period) ? null : (int)$this->period, 'period' => is_null($this->period) ? null : (int) $this->period,
'counter' => is_null($this->counter) ? null : (int)$this->counter 'counter' => is_null($this->counter) ? null : (int) $this->counter,
]; ];
} }
} }

@ -20,9 +20,9 @@ class UserResource extends JsonResource
public function toArray($request) public function toArray($request)
{ {
return [ return [
'id' => $this->when(!is_null($request->user()), $this->id), 'id' => $this->when(! is_null($request->user()), $this->id),
'name' => $this->name, 'name' => $this->name,
'email' => $this->when(!is_null($request->user()), $this->email), 'email' => $this->when(! is_null($request->user()), $this->email),
]; ];
} }
} }

@ -4,7 +4,6 @@ namespace App\Console\Commands;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
class CheckDbConnection extends Command class CheckDbConnection extends Command
{ {
@ -44,9 +43,10 @@ class CheckDbConnection extends Command
try { try {
DB::connection()->getPDO(); DB::connection()->getPDO();
$this->line(DB::connection()->getDatabaseName()); $this->line(DB::connection()->getDatabaseName());
return 1; return 1;
} catch (\Exception $e) { } catch (\Exception $e) {
return 0; return 0;
} }
} }
} }

@ -42,12 +42,13 @@ class FixUnsplittedAccounts extends Command
*/ */
public function handle() public function handle()
{ {
if (! Schema::hasColumn('twofaccounts', 'legacy_uri')) {
if (!Schema::hasColumn('twofaccounts', 'legacy_uri')) {
$this->comment('2fauth:fix-unsplitted-accounts is useful only after SplitTwofaccountsUriInMultipleColumns migration ran'); $this->comment('2fauth:fix-unsplitted-accounts is useful only after SplitTwofaccountsUriInMultipleColumns migration ran');
return; return;
} else {
$this->line('Fetching accounts...');
} }
else $this->line('Fetching accounts...');
$twofaccounts = TwoFAccount::where('otp_type', '') $twofaccounts = TwoFAccount::where('otp_type', '')
->where('secret', '') ->where('secret', '')
@ -61,24 +62,23 @@ class FixUnsplittedAccounts extends Command
if ($twofaccounts->count() == 0) { if ($twofaccounts->count() == 0) {
$this->info('Nothing to fix'); $this->info('Nothing to fix');
return; return;
} }
$this->line('Try to fix them...'); $this->line('Try to fix them...');
foreach ($twofaccounts as $twofaccount) { foreach ($twofaccounts as $twofaccount) {
if ($twofaccount->legacy_uri === __('errors.indecipherable')) { if ($twofaccount->legacy_uri === __('errors.indecipherable')) {
$this->error(sprintf('Account #%d cannot be deciphered', $twofaccount->id)); $this->error(sprintf('Account #%d cannot be deciphered', $twofaccount->id));
} } else {
else {
try { try {
// Get a consistent account // Get a consistent account
$twofaccount->fillWithURI($twofaccount->legacy_uri, false, true); $twofaccount->fillWithURI($twofaccount->legacy_uri, false, true);
$twofaccount->save(); $twofaccount->save();
$this->info(sprintf('Account #%d fixed', $twofaccount->id)); $this->info(sprintf('Account #%d fixed', $twofaccount->id));
} } catch (\Exception $ex) {
catch (\Exception $ex) {
$this->error(sprintf('Error while updating account #%d', $twofaccount->id)); $this->error(sprintf('Error while updating account #%d', $twofaccount->id));
} }
} }
@ -86,4 +86,4 @@ class FixUnsplittedAccounts extends Command
$this->line('Task completed'); $this->line('Task completed');
} }
} }

@ -2,8 +2,8 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Console\Commands\Utils\ResetTrait; use App\Console\Commands\Utils\ResetTrait;
use Illuminate\Console\Command;
class ResetDemo extends Command class ResetDemo extends Command
{ {
@ -40,15 +40,15 @@ class ResetDemo extends Command
*/ */
public function handle() public function handle()
{ {
if( !config('2fauth.config.isDemoApp') ) { if (! config('2fauth.config.isDemoApp')) {
$this->comment('2fauth:reset-demo can only run when isDemoApp option is On'); $this->comment('2fauth:reset-demo can only run when isDemoApp option is On');
return; return;
} }
if( $this->option('no-confirm') ) { if ($this->option('no-confirm')) {
$demo = 'demo'; $demo = 'demo';
} } else {
else {
$this->line('This will reset the app in order to run a clean and fresh demo.'); $this->line('This will reset the app in order to run a clean and fresh demo.');
$demo = $this->ask('To prevent any mistake please type the word "demo" to go on'); $demo = $this->ask('To prevent any mistake please type the word "demo" to go on');
} }
@ -57,9 +57,8 @@ class ResetDemo extends Command
$this->resetIcons(); $this->resetIcons();
$this->resetDB('DemoSeeder'); $this->resetDB('DemoSeeder');
$this->info('Demo app refreshed'); $this->info('Demo app refreshed');
} } else {
else {
$this->comment('Bad confirmation word, nothing appened'); $this->comment('Bad confirmation word, nothing appened');
} }
} }
} }

@ -40,15 +40,15 @@ class ResetTesting extends Command
*/ */
public function handle() public function handle()
{ {
if( !config('2fauth.config.isTestingApp') ) { if (! config('2fauth.config.isTestingApp')) {
$this->comment('2fauth:reset-testing can only run when isTestingApp option is On'); $this->comment('2fauth:reset-testing can only run when isTestingApp option is On');
return; return;
} }
if( $this->option('no-confirm') ) { if ($this->option('no-confirm')) {
$testing = 'testing'; $testing = 'testing';
} } else {
else {
$this->line('This will reset the app in order to run a clean and fresh testing app.'); $this->line('This will reset the app in order to run a clean and fresh testing app.');
$testing = $this->ask('To prevent any mistake please type the word "testing" to go on'); $testing = $this->ask('To prevent any mistake please type the word "testing" to go on');
} }
@ -58,10 +58,8 @@ class ResetTesting extends Command
$this->resetDB('TestingSeeder'); $this->resetDB('TestingSeeder');
$this->info('Testing app refreshed'); $this->info('Testing app refreshed');
} } else {
else {
$this->comment('Bad confirmation word, nothing appened'); $this->comment('Bad confirmation word, nothing appened');
} }
} }
}
}

File diff suppressed because one or more lines are too long

@ -2,7 +2,6 @@
namespace App\Console\Commands\Utils; namespace App\Console\Commands\Utils;
use App\Console\Commands\Utils\IconGenerator;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
@ -45,7 +44,7 @@ trait ResetTrait
$this->line('Icons regenerated'); $this->line('Icons regenerated');
} }
/** /**
* Reset DB * Reset DB
*/ */
@ -81,10 +80,9 @@ trait ResetTrait
protected function seedDB(string $seeder) : void protected function seedDB(string $seeder) : void
{ {
$this->callSilent('db:seed', [ $this->callSilent('db:seed', [
'--class' => $seeder '--class' => $seeder,
]); ]);
$this->line('Database seeded'); $this->line('Database seeded');
} }
} }

@ -27,7 +27,7 @@ class Kernel extends ConsoleKernel
*/ */
protected function commands() protected function commands()
{ {
$this->load(__DIR__.'/Commands'); $this->load(__DIR__ . '/Commands');
require base_path('routes/console.php'); require base_path('routes/console.php');
} }

@ -28,4 +28,4 @@ class TwoFAccountDeleted
$this->twofaccount = $twofaccount; $this->twofaccount = $twofaccount;
Log::info(sprintf('TwoFAccount #%s deleted', $twofaccount->id)); Log::info(sprintf('TwoFAccount #%s deleted', $twofaccount->id));
} }
} }

@ -11,4 +11,4 @@ use Exception;
*/ */
class DbEncryptionException extends Exception class DbEncryptionException extends Exception
{ {
} }

@ -11,4 +11,4 @@ use Exception;
*/ */
class EncryptedMigrationException extends Exception class EncryptedMigrationException extends Exception
{ {
} }

@ -14,7 +14,7 @@ class Handler extends ExceptionHandler
protected $levels = [ protected $levels = [
// //
]; ];
/** /**
* A list of the exception types that are not reported. * A list of the exception types that are not reported.
* *
@ -44,65 +44,72 @@ class Handler extends ExceptionHandler
{ {
$this->renderable(function (\Symfony\Component\HttpKernel\Exception\NotFoundHttpException $exception, $request) { $this->renderable(function (\Symfony\Component\HttpKernel\Exception\NotFoundHttpException $exception, $request) {
return response()->json([ return response()->json([
'message' => 'not found'], 404); 'message' => 'not found',
], 404);
}); });
$this->renderable(function (InvalidOtpParameterException $exception, $request) { $this->renderable(function (InvalidOtpParameterException $exception, $request) {
return response()->json([ return response()->json([
'message' => 'invalid OTP parameters', 'message' => 'invalid OTP parameters',
'reason' => [$exception->getMessage()] 'reason' => [$exception->getMessage()],
], 400); ], 400);
}); });
$this->renderable(function (InvalidQrCodeException $exception, $request) { $this->renderable(function (InvalidQrCodeException $exception, $request) {
return response()->json([ return response()->json([
'message' => 'not a valid QR code'], 400); 'message' => 'not a valid QR code', ], 400);
}); });
$this->renderable(function (InvalidSecretException $exception, $request) { $this->renderable(function (InvalidSecretException $exception, $request) {
return response()->json([ return response()->json([
'message' => 'not a valid base32 encoded secret'], 400); 'message' => 'not a valid base32 encoded secret', ], 400);
}); });
$this->renderable(function (DbEncryptionException $exception, $request) { $this->renderable(function (DbEncryptionException $exception, $request) {
return response()->json([ return response()->json([
'message' => $exception->getMessage()], 400); 'message' => $exception->getMessage(), ], 400);
}); });
$this->renderable(function (InvalidMigrationDataException $exception, $request) { $this->renderable(function (InvalidMigrationDataException $exception, $request) {
return response()->json([ return response()->json([
'message' => __('errors.invalid_x_migration', ['appname' => $exception->getMessage()])], 400); 'message' => __('errors.invalid_x_migration', ['appname' => $exception->getMessage()]),
], 400);
}); });
$this->renderable(function (UnsupportedMigrationException $exception, $request) { $this->renderable(function (UnsupportedMigrationException $exception, $request) {
return response()->json([ return response()->json([
'message' => __('errors.unsupported_migration')], 400); 'message' => __('errors.unsupported_migration'),
], 400);
}); });
$this->renderable(function (EncryptedMigrationException $exception, $request) { $this->renderable(function (EncryptedMigrationException $exception, $request) {
return response()->json([ return response()->json([
'message' => __('errors.encrypted_migration')], 400); 'message' => __('errors.encrypted_migration'),
], 400);
}); });
$this->renderable(function (UndecipherableException $exception, $request) { $this->renderable(function (UndecipherableException $exception, $request) {
return response()->json([ return response()->json([
'message' => __('errors.cannot_decipher_secret')], 400); 'message' => __('errors.cannot_decipher_secret'),
], 400);
}); });
$this->renderable(function (UnsupportedOtpTypeException $exception, $request) { $this->renderable(function (UnsupportedOtpTypeException $exception, $request) {
return response()->json([ return response()->json([
'message' => __('errors.unsupported_otp_type')], 400); 'message' => __('errors.unsupported_otp_type'),
], 400);
}); });
$this->renderable(function (\Illuminate\Auth\AuthenticationException $exception, $request) { $this->renderable(function (\Illuminate\Auth\AuthenticationException $exception, $request) {
if ($exception->guards() === ['reverse-proxy-guard']) { if ($exception->guards() === ['reverse-proxy-guard']) {
return response()->json([ return response()->json([
'message' => $exception->getMessage()], 407); 'message' => $exception->getMessage(),
} ], 407);
else { } else {
return response()->json([ return response()->json([
'message' => $exception->getMessage()], 401); 'message' => $exception->getMessage(),
], 401);
} }
}); });
} }
} }

@ -11,4 +11,4 @@ use Exception;
*/ */
class InvalidMigrationDataException extends Exception class InvalidMigrationDataException extends Exception
{ {
} }

@ -11,4 +11,4 @@ use Exception;
*/ */
class InvalidOtpParameterException extends Exception class InvalidOtpParameterException extends Exception
{ {
} }

@ -11,4 +11,4 @@ use Exception;
*/ */
class InvalidQrCodeException extends Exception class InvalidQrCodeException extends Exception
{ {
} }

@ -11,4 +11,4 @@ use Exception;
*/ */
class InvalidSecretException extends Exception class InvalidSecretException extends Exception
{ {
} }

@ -11,4 +11,4 @@ use Exception;
*/ */
class UndecipherableException extends Exception class UndecipherableException extends Exception
{ {
} }

@ -11,4 +11,4 @@ use Exception;
*/ */
class UnsupportedMigrationException extends Exception class UnsupportedMigrationException extends Exception
{ {
} }

@ -11,4 +11,4 @@ use Exception;
*/ */
class UnsupportedOtpTypeException extends Exception class UnsupportedOtpTypeException extends Exception
{ {
} }

@ -6,10 +6,10 @@
namespace App\Extensions; namespace App\Extensions;
use App\Models\User; use App\Models\User;
use Exception;
use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider; use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Exception;
class RemoteUserProvider implements UserProvider class RemoteUserProvider implements UserProvider
{ {
@ -23,7 +23,6 @@ class RemoteUserProvider implements UserProvider
// The downside of this approach is that we have to be sure that no change that needs // The downside of this approach is that we have to be sure that no change that needs
// to be persisted will be made to the user instance afterward (i.e through middlewares). // to be persisted will be made to the user instance afterward (i.e through middlewares).
/** /**
* The currently authenticated user. * The currently authenticated user.
* *
@ -31,26 +30,24 @@ class RemoteUserProvider implements UserProvider
*/ */
protected $user; protected $user;
/** /**
* Get the In-memory user * Get the In-memory user
* *
* @return \App\Models\User * @return \App\Models\User
*/ */
protected function getInMemoryUser() protected function getInMemoryUser()
{ {
if (is_null($this->user)) { if (is_null($this->user)) {
$this->user = new User; $this->user = new User;
$this->user->name = 'Remote User'; $this->user->name = 'Remote User';
$this->user->email = 'fake.email@do.not.use'; $this->user->email = 'fake.email@do.not.use';
} }
return $this->user; return $this->user;
} }
/** /**
* @inheritDoc * {@inheritDoc}
*/ */
public function retrieveById($identifier) public function retrieveById($identifier)
{ {
@ -67,8 +64,8 @@ class RemoteUserProvider implements UserProvider
} }
/** /**
* @inheritDoc * {@inheritDoc}
* *
* @codeCoverageIgnore * @codeCoverageIgnore
*/ */
public function retrieveByToken($identifier, $token) public function retrieveByToken($identifier, $token)
@ -77,8 +74,8 @@ class RemoteUserProvider implements UserProvider
} }
/** /**
* @inheritDoc * {@inheritDoc}
* *
* @codeCoverageIgnore * @codeCoverageIgnore
*/ */
public function updateRememberToken(Authenticatable $user, $token) public function updateRememberToken(Authenticatable $user, $token)
@ -87,8 +84,8 @@ class RemoteUserProvider implements UserProvider
} }
/** /**
* @inheritDoc * {@inheritDoc}
* *
* @codeCoverageIgnore * @codeCoverageIgnore
*/ */
public function retrieveByCredentials(array $credentials) public function retrieveByCredentials(array $credentials)
@ -97,12 +94,12 @@ class RemoteUserProvider implements UserProvider
} }
/** /**
* @inheritDoc * {@inheritDoc}
* *
* @codeCoverageIgnore * @codeCoverageIgnore
*/ */
public function validateCredentials(Authenticatable $user, array $credentials) public function validateCredentials(Authenticatable $user, array $credentials)
{ {
return true; return true;
} }
} }

@ -2,8 +2,8 @@
namespace App\Extensions; namespace App\Extensions;
use Closure;
use App\Models\WebAuthnAuthenticatable; use App\Models\WebAuthnAuthenticatable;
use Closure;
use Illuminate\Auth\Passwords\PasswordBroker; use Illuminate\Auth\Passwords\PasswordBroker;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract; use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
@ -14,14 +14,13 @@ class WebauthnCredentialBroker extends PasswordBroker
* *
* @param array $credentials * @param array $credentials
* @param \Closure|null $callback * @param \Closure|null $callback
*
* @return string * @return string
*/ */
public function sendResetLink(array $credentials, Closure $callback = null): string public function sendResetLink(array $credentials, Closure $callback = null) : string
{ {
$user = $this->getUser($credentials); $user = $this->getUser($credentials);
if (!$user instanceof WebAuthnAuthenticatable) { if (! $user instanceof WebAuthnAuthenticatable) {
return static::INVALID_USER; return static::INVALID_USER;
} }
@ -40,20 +39,18 @@ class WebauthnCredentialBroker extends PasswordBroker
return static::RESET_LINK_SENT; return static::RESET_LINK_SENT;
} }
/** /**
* Reset the password for the given token. * Reset the password for the given token.
* *
* @param array $credentials * @param array $credentials
* @param \Closure $callback * @param \Closure $callback
*
* @return \Illuminate\Contracts\Auth\CanResetPassword|string * @return \Illuminate\Contracts\Auth\CanResetPassword|string
*/ */
public function reset(array $credentials, Closure $callback) public function reset(array $credentials, Closure $callback)
{ {
$user = $this->validateReset($credentials); $user = $this->validateReset($credentials);
if (!$user instanceof CanResetPasswordContract || !$user instanceof WebAuthnAuthenticatable) { if (! $user instanceof CanResetPasswordContract || ! $user instanceof WebAuthnAuthenticatable) {
return $user; return $user;
} }

@ -11,4 +11,4 @@ class Groups extends Facade
{ {
return GroupService::class; return GroupService::class;
} }
} }

@ -11,4 +11,4 @@ class QrCode extends Facade
{ {
return QrCodeService::class; return QrCodeService::class;
} }
} }

@ -11,4 +11,4 @@ class Settings extends Facade
{ {
return SettingService::class; return SettingService::class;
} }
} }

@ -11,4 +11,4 @@ class TwoFAccounts extends Facade
{ {
return TwoFAccountService::class; return TwoFAccountService::class;
} }
} }

@ -2,67 +2,63 @@
namespace App\Factories; namespace App\Factories;
use App\Services\Migrators\GoogleAuthMigrator; use App\Exceptions\EncryptedMigrationException;
use App\Exceptions\UnsupportedMigrationException;
use App\Services\Migrators\AegisMigrator; use App\Services\Migrators\AegisMigrator;
use App\Services\Migrators\GoogleAuthMigrator;
use App\Services\Migrators\Migrator; use App\Services\Migrators\Migrator;
use App\Services\Migrators\PlainTextMigrator; use App\Services\Migrators\PlainTextMigrator;
use App\Services\Migrators\TwoFASMigrator; use App\Services\Migrators\TwoFASMigrator;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
use App\Exceptions\UnsupportedMigrationException;
use App\Exceptions\EncryptedMigrationException;
class MigratorFactory implements MigratorFactoryInterface class MigratorFactory implements MigratorFactoryInterface
{ {
/** /**
* Infer the type of migrator needed from a payload and create the migrator * Infer the type of migrator needed from a payload and create the migrator
* *
* @param string $migrationPayload The migration payload used to infer the migrator type * @param string $migrationPayload The migration payload used to infer the migrator type
* @return Migrator * @return Migrator
*/ */
public function create(string $migrationPayload) : Migrator public function create(string $migrationPayload) : Migrator
{ {
if ($this->isAegisJSON($migrationPayload)) { if ($this->isAegisJSON($migrationPayload)) {
return App::make(AegisMigrator::class); return App::make(AegisMigrator::class);
} } elseif ($this->is2FASv2($migrationPayload)) {
else if ($this->is2FASv2($migrationPayload)) {
return App::make(TwoFASMigrator::class); return App::make(TwoFASMigrator::class);
} } elseif ($this->isGoogleAuth($migrationPayload)) {
else if ($this->isGoogleAuth($migrationPayload)) {
return App::make(GoogleAuthMigrator::class); return App::make(GoogleAuthMigrator::class);
} } elseif ($this->isPlainText($migrationPayload)) {
else if ($this->isPlainText($migrationPayload)) {
return App::make(PlainTextMigrator::class); return App::make(PlainTextMigrator::class);
} else {
throw new UnsupportedMigrationException();
} }
else throw new UnsupportedMigrationException();
} }
/** /**
* Determine if a payload comes from Google Authenticator * Determine if a payload comes from Google Authenticator
* *
* @param string $migrationPayload The payload to analyse * @param string $migrationPayload The payload to analyse
* @return bool * @return bool
*/ */
private function isGoogleAuth(string $migrationPayload) : bool private function isGoogleAuth(string $migrationPayload) : bool
{ {
// - Google Auth migration URI : a string starting with otpauth-migration://offline?data= on a single line // - Google Auth migration URI : a string starting with otpauth-migration://offline?data= on a single line
$lines = preg_split('~\R~', $migrationPayload, -1 , PREG_SPLIT_NO_EMPTY); $lines = preg_split('~\R~', $migrationPayload, -1, PREG_SPLIT_NO_EMPTY);
if (!$lines || count($lines) != 1) if (! $lines || count($lines) != 1) {
return false; return false;
}
return preg_match('/^otpauth-migration:\/\/offline\?data=.+$/', $lines[0]) == 1; return preg_match('/^otpauth-migration:\/\/offline\?data=.+$/', $lines[0]) == 1;
} }
/** /**
* Determine if a payload is a plain text content * Determine if a payload is a plain text content
* *
* @param string $migrationPayload The payload to analyse * @param string $migrationPayload The payload to analyse
* @return bool * @return bool
*/ */
private function isPlainText(string $migrationPayload) : bool private function isPlainText(string $migrationPayload) : bool
@ -70,18 +66,17 @@ class MigratorFactory implements MigratorFactoryInterface
// - Plain text : one or more otpauth URIs (otpauth://[t|h]otp/...), one per line // - Plain text : one or more otpauth URIs (otpauth://[t|h]otp/...), one per line
return Validator::make( return Validator::make(
preg_split('~\R~', $migrationPayload, -1 , PREG_SPLIT_NO_EMPTY), preg_split('~\R~', $migrationPayload, -1, PREG_SPLIT_NO_EMPTY),
[ [
'*' => 'regex:/^otpauth:\/\/[h,t]otp\//i', '*' => 'regex:/^otpauth:\/\/[h,t]otp\//i',
] ]
)->passes(); )->passes();
} }
/** /**
* Determine if a payload comes from Aegis Authenticator in JSON format * Determine if a payload comes from Aegis Authenticator in JSON format
* *
* @param string $migrationPayload The payload to analyse * @param string $migrationPayload The payload to analyse
* @return bool * @return bool
*/ */
private function isAegisJSON(string $migrationPayload) : mixed private function isAegisJSON(string $migrationPayload) : mixed
@ -107,15 +102,14 @@ class MigratorFactory implements MigratorFactoryInterface
if (Arr::has($json, 'db')) { if (Arr::has($json, 'db')) {
if (is_string($json['db']) && is_array(Arr::get($json, 'header.slots'))) { if (is_string($json['db']) && is_array(Arr::get($json, 'header.slots'))) {
throw new EncryptedMigrationException(); throw new EncryptedMigrationException();
} } else {
else {
return count(Validator::validate( return count(Validator::validate(
$json, $json,
[ [
'db.entries.*.type' => 'required', 'db.entries.*.type' => 'required',
'db.entries.*.name' => 'required', 'db.entries.*.name' => 'required',
'db.entries.*.issuer' => 'required', 'db.entries.*.issuer' => 'required',
'db.entries.*.info' => 'required' 'db.entries.*.info' => 'required',
] ]
)) > 0; )) > 0;
} }
@ -124,11 +118,10 @@ class MigratorFactory implements MigratorFactoryInterface
return false; return false;
} }
/** /**
* Determine if a payload comes from 2FAS Authenticator * Determine if a payload comes from 2FAS Authenticator
* *
* @param string $migrationPayload The payload to analyse * @param string $migrationPayload The payload to analyse
* @return bool * @return bool
*/ */
private function is2FASv2(string $migrationPayload) : mixed private function is2FASv2(string $migrationPayload) : mixed
@ -155,18 +148,17 @@ class MigratorFactory implements MigratorFactoryInterface
// } // }
$json = json_decode($migrationPayload, true); $json = json_decode($migrationPayload, true);
if (Arr::get($json, 'schemaVersion') == 2 && (Arr::has($json, 'services') || Arr::has($json, 'servicesEncrypted'))) { if (Arr::get($json, 'schemaVersion') == 2 && (Arr::has($json, 'services') || Arr::has($json, 'servicesEncrypted'))) {
if (Arr::has($json, 'servicesEncrypted')) { if (Arr::has($json, 'servicesEncrypted')) {
throw new EncryptedMigrationException(); throw new EncryptedMigrationException();
} } else {
else {
return count(Validator::validate( return count(Validator::validate(
$json, $json,
[ [
'services.*.secret' => 'required', 'services.*.secret' => 'required',
'services.*.name' => 'required', 'services.*.name' => 'required',
'services.*.otp' => 'required' 'services.*.otp' => 'required',
] ]
)) > 0; )) > 0;
} }
@ -174,5 +166,4 @@ class MigratorFactory implements MigratorFactoryInterface
return false; return false;
} }
} }

@ -8,9 +8,9 @@ interface MigratorFactoryInterface
{ {
/** /**
* Infer the type of migrator needed from a payload and create the migrator * Infer the type of migrator needed from a payload and create the migrator
* *
* @param string $migrationPayload The migration payload used to infer the migrator type * @param string $migrationPayload The migration payload used to infer the migrator type
* @return Migrator * @return Migrator
*/ */
public function create(string $migrationPayload) : Migrator; public function create(string $migrationPayload) : Migrator;
} }

@ -8,17 +8,22 @@ class Helpers
{ {
/** /**
* Generate a unique filename * Generate a unique filename
* *
* @param string $extension * @param string $extension
* @return string The filename * @return string The filename
*/ */
public static function getUniqueFilename(string $extension): string public static function getUniqueFilename(string $extension) : string
{ {
return Str::random(40).'.'.$extension; return Str::random(40) . '.' . $extension;
} }
/**
public static function cleanVersionNumber(?string $release): string|false * Clean a version number string
*
* @param string|null $release
* @return string|false
*/
public static function cleanVersionNumber(?string $release) : string|false
{ {
return preg_match('/([[0-9][0-9\.]*[0-9])/', $release, $version) ? $version[0] : false; return preg_match('/([[0-9][0-9\.]*[0-9])/', $release, $version) ? $version[0] : false;
} }

@ -2,9 +2,9 @@
namespace App\Http\Controllers\Auth; namespace App\Http\Controllers\Auth;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails; use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
use Illuminate\Http\Request;
class ForgotPasswordController extends Controller class ForgotPasswordController extends Controller
{ {
@ -21,7 +21,6 @@ class ForgotPasswordController extends Controller
use SendsPasswordResetEmails; use SendsPasswordResetEmails;
/** /**
* Validate the email for the given request. * Validate the email for the given request.
* *

@ -2,17 +2,16 @@
namespace App\Http\Controllers\Auth; namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Http\Requests\LoginRequest;
use Carbon\Carbon;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\Response; use Illuminate\Http\Response;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Lang; use Illuminate\Support\Facades\Lang;
use App\Http\Requests\LoginRequest;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
class LoginController extends Controller class LoginController extends Controller
{ {
/* /*
@ -28,7 +27,6 @@ class LoginController extends Controller
use AuthenticatesUsers; use AuthenticatesUsers;
/** /**
* Handle a login request to the application. * Handle a login request to the application.
* *
@ -65,10 +63,10 @@ class LoginController extends Controller
return $this->sendFailedLoginResponse($request); return $this->sendFailedLoginResponse($request);
} }
/** /**
* log out current user * log out current user
* @param Request $request *
* @param Request $request
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
*/ */
public function logout(Request $request) public function logout(Request $request)
@ -79,7 +77,6 @@ class LoginController extends Controller
return response()->json(['message' => 'signed out'], Response::HTTP_OK); return response()->json(['message' => 'signed out'], Response::HTTP_OK);
} }
/** /**
* Send the response after the user was authenticated. * Send the response after the user was authenticated.
* *
@ -96,11 +93,10 @@ class LoginController extends Controller
return response()->json([ return response()->json([
'message' => 'authenticated', 'message' => 'authenticated',
'name' => $name 'name' => $name,
], Response::HTTP_OK); ], Response::HTTP_OK);
} }
/** /**
* Get the failed login response instance. * Get the failed login response instance.
* *
@ -111,7 +107,6 @@ class LoginController extends Controller
{ {
return response()->json(['message' => 'unauthorised'], Response::HTTP_UNAUTHORIZED); return response()->json(['message' => 'unauthorised'], Response::HTTP_UNAUTHORIZED);
} }
/** /**
* Redirect the user after determining they are locked out. * Redirect the user after determining they are locked out.
@ -128,7 +123,6 @@ class LoginController extends Controller
return response()->json(['message' => Lang::get('auth.throttle', ['seconds' => $seconds])], Response::HTTP_TOO_MANY_REQUESTS); return response()->json(['message' => Lang::get('auth.throttle', ['seconds' => $seconds])], Response::HTTP_TOO_MANY_REQUESTS);
} }
/** /**
* Get the needed authorization credentials from the request. * Get the needed authorization credentials from the request.
* *
@ -139,13 +133,12 @@ class LoginController extends Controller
{ {
$credentials = [ $credentials = [
$this->username() => strtolower($request->input($this->username())), $this->username() => strtolower($request->input($this->username())),
'password' => $request->get('password'), 'password' => $request->get('password'),
]; ];
return $credentials; return $credentials;
} }
/** /**
* The user has been authenticated. * The user has been authenticated.
* *
@ -160,4 +153,4 @@ class LoginController extends Controller
Log::info('User authenticated'); Log::info('User authenticated');
} }
} }

@ -2,8 +2,8 @@
namespace App\Http\Controllers\Auth; namespace App\Http\Controllers\Auth;
use App\Http\Requests\UserPatchPwdRequest;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\UserPatchPwdRequest;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
@ -13,19 +13,20 @@ class PasswordController extends Controller
/** /**
* Update the user's password. * Update the user's password.
* *
* @param \App\Http\Requests\UserPatchPwdRequest $request * @param \App\Http\Requests\UserPatchPwdRequest $request
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
*/ */
public function update(UserPatchPwdRequest $request) public function update(UserPatchPwdRequest $request)
{ {
$validated = $request->validated(); $validated = $request->validated();
if (!Hash::check( $validated['currentPassword'], Auth::user()->password) ) { if (! Hash::check($validated['currentPassword'], Auth::user()->password)) {
Log::notice('Password update failed: wrong password provided'); Log::notice('Password update failed: wrong password provided');
return response()->json(['message' => __('errors.wrong_current_password')], 400); return response()->json(['message' => __('errors.wrong_current_password')], 400);
} }
if (!config('2fauth.config.isDemoApp') ) { if (! config('2fauth.config.isDemoApp')) {
$request->user()->update([ $request->user()->update([
'password' => bcrypt($validated['password']), 'password' => bcrypt($validated['password']),
]); ]);
@ -34,4 +35,4 @@ class PasswordController extends Controller
return response()->json(['message' => __('auth.forms.password_successfully_changed')]); return response()->json(['message' => __('auth.forms.password_successfully_changed')]);
} }
} }

@ -2,12 +2,12 @@
namespace App\Http\Controllers\Auth; namespace App\Http\Controllers\Auth;
use App\Models\User;
use App\Http\Requests\UserStoreRequest;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Hash; use App\Http\Requests\UserStoreRequest;
use App\Models\User;
use Illuminate\Auth\Events\Registered; use Illuminate\Auth\Events\Registered;
use Illuminate\Foundation\Auth\RegistersUsers; use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
class RegisterController extends Controller class RegisterController extends Controller
@ -25,7 +25,6 @@ class RegisterController extends Controller
use RegistersUsers; use RegistersUsers;
/** /**
* Handle a registration request for the application. * Handle a registration request for the application.
* *
@ -42,11 +41,10 @@ class RegisterController extends Controller
return response()->json([ return response()->json([
'message' => 'account created', 'message' => 'account created',
'name' => $user->name, 'name' => $user->name,
], 201); ], 201);
} }
/** /**
* Create a new user instance after a valid registration. * Create a new user instance after a valid registration.
* *
@ -56,8 +54,8 @@ class RegisterController extends Controller
protected function create(array $data) protected function create(array $data)
{ {
return User::create([ return User::create([
'name' => $data['name'], 'name' => $data['name'],
'email' => $data['email'], 'email' => $data['email'],
'password' => Hash::make($data['password']), 'password' => Hash::make($data['password']),
]); ]);
} }

@ -19,5 +19,4 @@ class ResetPasswordController extends Controller
*/ */
use ResetsPasswords; use ResetsPasswords;
} }

@ -2,37 +2,38 @@
namespace App\Http\Controllers\Auth; namespace App\Http\Controllers\Auth;
use App\Http\Requests\UserUpdateRequest;
use App\Http\Requests\UserDeleteRequest;
use App\Api\v1\Resources\UserResource; use App\Api\v1\Resources\UserResource;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth; use App\Http\Requests\UserDeleteRequest;
use Illuminate\Support\Facades\Hash; use App\Http\Requests\UserUpdateRequest;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
class UserController extends Controller class UserController extends Controller
{ {
/** /**
* Update the user's profile information. * Update the user's profile information.
* *
* @param \App\Http\Requests\UserUpdateRequest $request * @param \App\Http\Requests\UserUpdateRequest $request
* @return \App\Api\v1\Resources\UserResource|\Illuminate\Http\JsonResponse * @return \App\Api\v1\Resources\UserResource|\Illuminate\Http\JsonResponse
*/ */
public function update(UserUpdateRequest $request) public function update(UserUpdateRequest $request)
{ {
$user = $request->user(); $user = $request->user();
$validated = $request->validated(); $validated = $request->validated();
if (!Hash::check( $request->password, Auth::user()->password) ) { if (! Hash::check($request->password, Auth::user()->password)) {
Log::notice('Account update failed: wrong password provided'); Log::notice('Account update failed: wrong password provided');
return response()->json(['message' => __('errors.wrong_current_password')], 400); return response()->json(['message' => __('errors.wrong_current_password')], 400);
} }
if (!config('2fauth.config.isDemoApp') ) { if (! config('2fauth.config.isDemoApp')) {
$user->update([ $user->update([
'name' => $validated['name'], 'name' => $validated['name'],
'email' => $validated['email'], 'email' => $validated['email'],
]); ]);
} }
@ -41,11 +42,10 @@ class UserController extends Controller
return new UserResource($user); return new UserResource($user);
} }
/** /**
* Delete the user's account. * Delete the user's account.
* *
* @param \App\Http\Requests\UserDeleteRequest $request * @param \App\Http\Requests\UserDeleteRequest $request
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
*/ */
public function delete(UserDeleteRequest $request) public function delete(UserDeleteRequest $request)
@ -53,7 +53,7 @@ class UserController extends Controller
Log::info('User deletion requested'); Log::info('User deletion requested');
$validated = $request->validated(); $validated = $request->validated();
if (!Hash::check( $validated['password'], Auth::user()->password) ) { if (! Hash::check($validated['password'], Auth::user()->password)) {
return response()->json(['message' => __('errors.wrong_current_password')], 400); return response()->json(['message' => __('errors.wrong_current_password')], 400);
} }
@ -79,6 +79,7 @@ class UserController extends Controller
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
catch (\Throwable $e) { catch (\Throwable $e) {
Log::error('User deletion failed'); Log::error('User deletion failed');
return response()->json(['message' => __('errors.user_deletion_failed')], 400); return response()->json(['message' => __('errors.user_deletion_failed')], 400);
} }
// @codeCoverageIgnoreEnd // @codeCoverageIgnoreEnd
@ -86,4 +87,4 @@ class UserController extends Controller
return response()->json(null, 204); return response()->json(null, 204);
} }
} }

@ -27,4 +27,4 @@
// * @var string // * @var string
// */ // */
// protected $redirectTo = RouteServiceProvider::HOME; // protected $redirectTo = RouteServiceProvider::HOME;
// } // }

@ -2,26 +2,25 @@
namespace App\Http\Controllers\Auth; namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
use App\Extensions\WebauthnCredentialBroker; use App\Extensions\WebauthnCredentialBroker;
use Illuminate\Foundation\Auth\ResetsPasswords; use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Password;
use App\Http\Requests\WebauthnDeviceLostRequest; use App\Http\Requests\WebauthnDeviceLostRequest;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;
use Illuminate\Validation\ValidationException;
class WebAuthnDeviceLostController extends Controller class WebAuthnDeviceLostController extends Controller
{ {
use ResetsPasswords; use ResetsPasswords;
/** /**
* Send a recovery email to the user. * Send a recovery email to the user.
* *
* @param \App\Http\Requests\WebauthnDeviceLostRequest $request * @param \App\Http\Requests\WebauthnDeviceLostRequest $request
* @param \App\Extensions\WebauthnCredentialBroker $broker * @param \App\Extensions\WebauthnCredentialBroker $broker
*
* @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse
*
* @throws \Illuminate\Validation\ValidationException * @throws \Illuminate\Validation\ValidationException
*/ */
public function sendRecoveryEmail(WebauthnDeviceLostRequest $request, WebauthnCredentialBroker $broker) public function sendRecoveryEmail(WebauthnDeviceLostRequest $request, WebauthnCredentialBroker $broker)
@ -35,14 +34,13 @@ class WebAuthnDeviceLostController extends Controller
: $this->sendRecoveryLinkFailedResponse($request, $response); : $this->sendRecoveryLinkFailedResponse($request, $response);
} }
/** /**
* Get the response for a failed account recovery link. * Get the response for a failed account recovery link.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param string $response * @param string $response
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
*
* @throws \Illuminate\Validation\ValidationException * @throws \Illuminate\Validation\ValidationException
*/ */
protected function sendRecoveryLinkFailedResponse(Request $request, string $response) protected function sendRecoveryLinkFailedResponse(Request $request, string $response)
@ -56,17 +54,15 @@ class WebAuthnDeviceLostController extends Controller
->withErrors(['email' => trans($response)]); ->withErrors(['email' => trans($response)]);
} }
/** /**
* Get the response for a successful account recovery link. * Get the response for a successful account recovery link.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param string $response * @param string $response
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
*/ */
protected function sendRecoveryLinkResponse(Request $request, string $response) protected function sendRecoveryLinkResponse(Request $request, string $response)
{ {
return response()->json(['message' => __('auth.webauthn.account_recovery_email_sent')]); return response()->json(['message' => __('auth.webauthn.account_recovery_email_sent')]);
} }
} }

@ -2,14 +2,14 @@
namespace App\Http\Controllers\Auth; namespace App\Http\Controllers\Auth;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\User;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Laragear\WebAuthn\Http\Requests\AssertionRequest;
use Laragear\WebAuthn\Http\Requests\AssertedRequest;
use Illuminate\Contracts\Support\Responsable; use Illuminate\Contracts\Support\Responsable;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
use Laragear\WebAuthn\Http\Requests\AssertedRequest;
use Laragear\WebAuthn\Http\Requests\AssertionRequest;
use Laragear\WebAuthn\WebAuthn; use Laragear\WebAuthn\WebAuthn;
class WebAuthnLoginController extends Controller class WebAuthnLoginController extends Controller
@ -31,13 +31,13 @@ class WebAuthnLoginController extends Controller
* @param \Laragear\WebAuthn\Http\Requests\AssertionRequest $request * @param \Laragear\WebAuthn\Http\Requests\AssertionRequest $request
* @return \Illuminate\Contracts\Support\Responsable|\Illuminate\Http\JsonResponse * @return \Illuminate\Contracts\Support\Responsable|\Illuminate\Http\JsonResponse
*/ */
public function options(AssertionRequest $request): Responsable|JsonResponse public function options(AssertionRequest $request) : Responsable|JsonResponse
{ {
switch (env('WEBAUTHN_USER_VERIFICATION')) { switch (env('WEBAUTHN_USER_VERIFICATION')) {
case WebAuthn::USER_VERIFICATION_DISCOURAGED: case WebAuthn::USER_VERIFICATION_DISCOURAGED:
$request = $request->fastLogin(); // Makes the authenticator to only check for user presence on registration $request = $request->fastLogin(); // Makes the authenticator to only check for user presence on registration
break; break;
case WebAuthn::USER_VERIFICATION_REQUIRED: case WebAuthn::USER_VERIFICATION_REQUIRED:
$request = $request->secureLogin(); // Makes the authenticator to always verify the user thoroughly on registration $request = $request->secureLogin(); // Makes the authenticator to always verify the user thoroughly on registration
break; break;
} }
@ -50,10 +50,9 @@ class WebAuthnLoginController extends Controller
return $user return $user
? $request->toVerify($user) ? $request->toVerify($user)
: response()->json([ : response()->json([
'message' => 'no registered user' 'message' => 'no registered user',
], 400); ], 400);
} }
/** /**
* Log the user in. * Log the user in.
@ -70,28 +69,27 @@ class WebAuthnLoginController extends Controller
// Some authenticators do not send a userHandle so we hack the response to be compliant // Some authenticators do not send a userHandle so we hack the response to be compliant
// with Larapass/webauthn-lib implementation that waits for a userHandle // with Larapass/webauthn-lib implementation that waits for a userHandle
if(!$response['userHandle']) { if (! $response['userHandle']) {
$response['userHandle'] = User::getFromCredentialId($request->id)?->userHandle(); $response['userHandle'] = User::getFromCredentialId($request->id)?->userHandle();
$request->merge(['response' => $response]); $request->merge(['response' => $response]);
} }
} }
$user = $request->login(); $user = $request->login();
if ($user) { if ($user) {
$this->authenticated($user); $this->authenticated($user);
return response()->noContent(); return response()->noContent();
} }
return response()->noContent(422); return response()->noContent(422);
} }
/** /**
* The user has been authenticated. * The user has been authenticated.
* *
* @param mixed $user * @param mixed $user
*
* @return void|\Illuminate\Http\JsonResponse * @return void|\Illuminate\Http\JsonResponse
*/ */
protected function authenticated($user) protected function authenticated($user)
@ -101,4 +99,4 @@ class WebAuthnLoginController extends Controller
Log::info('User authenticated via webauthn'); Log::info('User authenticated via webauthn');
} }
} }

@ -4,16 +4,15 @@ namespace App\Http\Controllers\Auth;
use App\Facades\Settings; use App\Facades\Settings;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Http\Requests\WebauthnRenameRequest; use App\Http\Requests\WebauthnRenameRequest;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
class WebAuthnManageController extends Controller class WebAuthnManageController extends Controller
{ {
/** /**
* List all WebAuthn registered credentials * List all WebAuthn registered credentials
* *
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
*/ */
public function index(Request $request) public function index(Request $request)
@ -23,12 +22,11 @@ class WebAuthnManageController extends Controller
return response()->json($allUserCredentials, 200); return response()->json($allUserCredentials, 200);
} }
/** /**
* Rename a WebAuthn credential * Rename a WebAuthn credential
* *
* @param \App\Http\Requests\WebauthnRenameRequest $request * @param \App\Http\Requests\WebauthnRenameRequest $request
* @param string $credential * @param string $credential
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
*/ */
public function rename(WebauthnRenameRequest $request, string $credential) public function rename(WebauthnRenameRequest $request, string $credential)
@ -38,17 +36,15 @@ class WebAuthnManageController extends Controller
abort_if(! $request->user()->renameCredential($credential, $validated['name']), 404); abort_if(! $request->user()->renameCredential($credential, $validated['name']), 404);
return response()->json([ return response()->json([
'name' => $validated['name'], 'name' => $validated['name'],
], 200); ], 200);
} }
/** /**
* Remove the specified credential from storage. * Remove the specified credential from storage.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param string|array $credential * @param string|array $credential
*
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
*/ */
public function delete(Request $request, $credential) public function delete(Request $request, $credential)
@ -71,4 +67,4 @@ class WebAuthnManageController extends Controller
return response()->json(null, 204); return response()->json(null, 204);
} }
} }

@ -2,22 +2,21 @@
namespace App\Http\Controllers\Auth; namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Http\Requests\WebauthnRecoveryRequest;
use App\Extensions\WebauthnCredentialBroker; use App\Extensions\WebauthnCredentialBroker;
use App\Facades\Settings; use App\Facades\Settings;
use App\Http\Controllers\Controller;
use App\Http\Requests\WebauthnRecoveryRequest;
use Illuminate\Auth\AuthenticationException; use Illuminate\Auth\AuthenticationException;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Support\Facades\Password; use Illuminate\Support\Facades\Password;
use Illuminate\Support\Facades\App; use Illuminate\Validation\ValidationException;
class WebAuthnRecoveryController extends Controller class WebAuthnRecoveryController extends Controller
{ {
use ResetsPasswords; use ResetsPasswords;
/** /**
* Let the user regain access to his account using email+password by resetting * Let the user regain access to his account using email+password by resetting
@ -25,8 +24,8 @@ class WebAuthnRecoveryController extends Controller
* *
* @param \App\Http\Requests\WebauthnRecoveryRequest $request * @param \App\Http\Requests\WebauthnRecoveryRequest $request
* @param \App\Extensions\WebauthnCredentialBroker $broker * @param \App\Extensions\WebauthnCredentialBroker $broker
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
*
* @throws \Illuminate\Validation\ValidationException * @throws \Illuminate\Validation\ValidationException
*/ */
public function recover(WebauthnRecoveryRequest $request, WebauthnCredentialBroker $broker) public function recover(WebauthnRecoveryRequest $request, WebauthnCredentialBroker $broker)
@ -54,66 +53,57 @@ class WebAuthnRecoveryController extends Controller
$user->flushCredentials(); $user->flushCredentials();
} }
Settings::delete('useWebauthnOnly'); Settings::delete('useWebauthnOnly');
} else {
throw new AuthenticationException();
} }
else throw new AuthenticationException();
} }
); );
return $response === Password::PASSWORD_RESET return $response === Password::PASSWORD_RESET
? $this->sendRecoveryResponse($request, $response) ? $this->sendRecoveryResponse($request, $response)
: $this->sendRecoveryFailedResponse($request, $response); : $this->sendRecoveryFailedResponse($request, $response);
} }
/** /**
* Check if the user has set to revoke all credentials. * Check if the user has set to revoke all credentials.
* *
* @param \App\Http\Requests\WebauthnRecoveryRequest $request * @param \App\Http\Requests\WebauthnRecoveryRequest $request
*
* @return bool|mixed * @return bool|mixed
*/ */
protected function shouldRevokeAllCredentials(WebauthnRecoveryRequest $request): mixed protected function shouldRevokeAllCredentials(WebauthnRecoveryRequest $request) : mixed
{ {
return filter_var($request->header('WebAuthn-Unique'), FILTER_VALIDATE_BOOLEAN) return filter_var($request->header('WebAuthn-Unique'), FILTER_VALIDATE_BOOLEAN)
?: $request->input('revokeAll', true); ?: $request->input('revokeAll', true);
} }
/** /**
* Get the response for a successful account recovery. * Get the response for a successful account recovery.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param string $response * @param string $response
*
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
*
*/ */
protected function sendRecoveryResponse(Request $request, string $response): JsonResponse protected function sendRecoveryResponse(Request $request, string $response) : JsonResponse
{ {
return response()->json(['message' => __('auth.webauthn.webauthn_login_disabled')]); return response()->json(['message' => __('auth.webauthn.webauthn_login_disabled')]);
} }
/** /**
* Get the response for a failed account recovery. * Get the response for a failed account recovery.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param string $response * @param string $response
*
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
*
* @throws \Illuminate\Validation\ValidationException * @throws \Illuminate\Validation\ValidationException
*
*/ */
protected function sendRecoveryFailedResponse(Request $request, string $response): JsonResponse protected function sendRecoveryFailedResponse(Request $request, string $response) : JsonResponse
{ {
switch ($response) { switch ($response) {
case Password::INVALID_TOKEN: case Password::INVALID_TOKEN:
throw ValidationException::withMessages(['token' => [__('auth.webauthn.invalid_reset_token')]]); throw ValidationException::withMessages(['token' => [__('auth.webauthn.invalid_reset_token')]]);
default: default:
throw ValidationException::withMessages(['email' => [trans($response)]]); throw ValidationException::withMessages(['email' => [trans($response)]]);
} }
} }
} }

@ -17,13 +17,13 @@ class WebAuthnRegisterController extends Controller
* @param \Laragear\WebAuthn\Http\Requests\AttestationRequest $request * @param \Laragear\WebAuthn\Http\Requests\AttestationRequest $request
* @return \Illuminate\Contracts\Support\Responsable * @return \Illuminate\Contracts\Support\Responsable
*/ */
public function options(AttestationRequest $request): Responsable public function options(AttestationRequest $request) : Responsable
{ {
switch (env('WEBAUTHN_USER_VERIFICATION')) { switch (env('WEBAUTHN_USER_VERIFICATION')) {
case WebAuthn::USER_VERIFICATION_DISCOURAGED: case WebAuthn::USER_VERIFICATION_DISCOURAGED:
$request = $request->fastRegistration(); // Makes the authenticator to only check for user presence on registration $request = $request->fastRegistration(); // Makes the authenticator to only check for user presence on registration
break; break;
case WebAuthn::USER_VERIFICATION_REQUIRED: case WebAuthn::USER_VERIFICATION_REQUIRED:
$request = $request->secureRegistration(); // Makes the authenticator to always verify the user thoroughly on registration $request = $request->secureRegistration(); // Makes the authenticator to always verify the user thoroughly on registration
break; break;
} }
@ -34,17 +34,16 @@ class WebAuthnRegisterController extends Controller
->toCreate(); ->toCreate();
} }
/** /**
* Registers a device for further WebAuthn authentication. * Registers a device for further WebAuthn authentication.
* *
* @param \Laragear\WebAuthn\Http\Requests\AttestedRequest $request * @param \Laragear\WebAuthn\Http\Requests\AttestedRequest $request
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function register(AttestedRequest $request): Response public function register(AttestedRequest $request) : Response
{ {
$request->save(); $request->save();
return response()->noContent(); return response()->noContent();
} }
} }

@ -2,10 +2,10 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
class Controller extends BaseController class Controller extends BaseController
{ {

@ -2,16 +2,15 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Events\ScanForNewReleaseCalled;
use App\Facades\Settings; use App\Facades\Settings;
use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\App;
use App\Events\ScanForNewReleaseCalled;
class SinglePageController extends Controller class SinglePageController extends Controller
{ {
/** /**
* return the main view * return the main view
*
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory * @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/ */
public function index() public function index()
@ -20,14 +19,14 @@ class SinglePageController extends Controller
return view('landing')->with([ return view('landing')->with([
'appSettings' => Settings::all()->toJson(), 'appSettings' => Settings::all()->toJson(),
'appConfig' => collect([ 'appConfig' => collect([
'proxyAuth' => config("auth.defaults.guard") === 'reverse-proxy-guard' ? true : false, 'proxyAuth' => config('auth.defaults.guard') === 'reverse-proxy-guard' ? true : false,
'proxyLogoutUrl' => config("2fauth.config.proxyLogoutUrl") ? config("2fauth.config.proxyLogoutUrl") : false, 'proxyLogoutUrl' => config('2fauth.config.proxyLogoutUrl') ? config('2fauth.config.proxyLogoutUrl') : false,
])->toJson(), ])->toJson(),
'lang' => App::currentLocale(), 'lang' => App::currentLocale(),
'isDemoApp' => config("2fauth.config.isDemoApp") ? 'true' : 'false', 'isDemoApp' => config('2fauth.config.isDemoApp') ? 'true' : 'false',
'isTestingApp' => config("2fauth.config.isTestingApp") ? 'true' : 'false', 'isTestingApp' => config('2fauth.config.isTestingApp') ? 'true' : 'false',
'locales' => collect(config("2fauth.locales"))->toJson() /** @phpstan-ignore-line */ 'locales' => collect(config('2fauth.locales'))->toJson(), /** @phpstan-ignore-line */
]); ]);
} }
} }

@ -2,9 +2,8 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Services\ReleaseRadarService;
use App\Http\Controllers\Controller;
use App\Facades\Settings; use App\Facades\Settings;
use App\Services\ReleaseRadarService;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
@ -12,35 +11,35 @@ class SystemController extends Controller
{ {
/** /**
* Get detailed information about the current installation * Get detailed information about the current installation
* *
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
*/ */
public function infos(Request $request) public function infos(Request $request)
{ {
$infos = array(); $infos = [];
$infos['Date'] = date(DATE_RFC2822); $infos['Date'] = date(DATE_RFC2822);
$infos['userAgent'] = $request->header('user-agent'); $infos['userAgent'] = $request->header('user-agent');
// App info // App info
$infos['Version'] = config('2fauth.version'); $infos['Version'] = config('2fauth.version');
$infos['Environment'] = config('app.env'); $infos['Environment'] = config('app.env');
$infos['Debug'] = var_export(config('app.debug'), true); $infos['Debug'] = var_export(config('app.debug'), true);
$infos['Cache driver'] = config('cache.default'); $infos['Cache driver'] = config('cache.default');
$infos['Log channel'] = config('logging.default'); $infos['Log channel'] = config('logging.default');
$infos['Log level'] = env('LOG_LEVEL'); $infos['Log level'] = env('LOG_LEVEL');
$infos['DB driver'] = DB::getDriverName(); $infos['DB driver'] = DB::getDriverName();
// PHP info // PHP info
$infos['PHP version'] = PHP_VERSION; $infos['PHP version'] = PHP_VERSION;
$infos['Operating system'] = PHP_OS; $infos['Operating system'] = PHP_OS;
$infos['interface'] = PHP_SAPI; $infos['interface'] = PHP_SAPI;
// Auth info // Auth info
if ($request->user()) { if ($request->user()) {
$infos['Auth guard'] = config('auth.defaults.guard'); $infos['Auth guard'] = config('auth.defaults.guard');
if ($infos['Auth guard'] === 'reverse-proxy-guard') { if ($infos['Auth guard'] === 'reverse-proxy-guard') {
$infos['Auth proxy header for user'] = config('auth.auth_proxy_headers.user'); $infos['Auth proxy header for user'] = config('auth.auth_proxy_headers.user');
$infos['Auth proxy header for email'] = config('auth.auth_proxy_headers.email'); $infos['Auth proxy header for email'] = config('auth.auth_proxy_headers.email');
} }
$infos['webauthn user verification'] = config('larapass.login_verify'); $infos['webauthn user verification'] = config('larapass.login_verify');
$infos['Trusted proxies'] = config('2fauth.trustedProxies') ?: 'none'; $infos['Trusted proxies'] = config('2fauth.trustedProxies') ?: 'none';
} }
// User info // User info
if ($request->user()) { if ($request->user()) {
@ -50,10 +49,9 @@ class SystemController extends Controller
return response()->json($infos); return response()->json($infos);
} }
/** /**
* Get latest release * Get latest release
* *
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
*/ */
public function latestRelease(Request $request, ReleaseRadarService $releaseRadar) public function latestRelease(Request $request, ReleaseRadarService $releaseRadar)

@ -71,13 +71,13 @@ class Kernel extends HttpKernel
* @var array * @var array
*/ */
protected $routeMiddleware = [ protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class, 'auth' => \App\Http\Middleware\Authenticate::class,
'guest' => \App\Http\Middleware\RejectIfAuthenticated::class, 'guest' => \App\Http\Middleware\RejectIfAuthenticated::class,
'SkipIfAuthenticated' => \App\Http\Middleware\SkipIfAuthenticated::class, 'SkipIfAuthenticated' => \App\Http\Middleware\SkipIfAuthenticated::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'rejectIfDemoMode' => \App\Http\Middleware\RejectIfDemoMode::class, 'rejectIfDemoMode' => \App\Http\Middleware\RejectIfDemoMode::class,
'rejectIfReverseProxy' => \App\Http\Middleware\RejectIfReverseProxy::class, 'rejectIfReverseProxy' => \App\Http\Middleware\RejectIfReverseProxy::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
// 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class, // 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
// 'signed' => \App\Http\Middleware\ValidateSignature::class, // 'signed' => \App\Http\Middleware\ValidateSignature::class,
]; ];

@ -20,9 +20,8 @@ class Authenticate extends Middleware
if (empty($guards)) { if (empty($guards)) {
// Will retreive the default guard // Will retreive the default guard
$guards = [null]; $guards = [null];
} } else {
else { // We replace routes guard by the reverse proxy guard if necessary
// We replace routes guard by the reverse proxy guard if necessary
$proxyGuard = 'reverse-proxy-guard'; $proxyGuard = 'reverse-proxy-guard';
if (config('auth.defaults.guard') === $proxyGuard) { if (config('auth.defaults.guard') === $proxyGuard) {
@ -33,11 +32,11 @@ class Authenticate extends Middleware
foreach ($guards as $guard) { foreach ($guards as $guard) {
if ($this->auth->guard($guard)->check()) { if ($this->auth->guard($guard)->check()) {
$this->auth->shouldUse($guard); $this->auth->shouldUse($guard);
return; return;
} }
} }
$this->unauthenticated($request, $guards); $this->unauthenticated($request, $guards);
} }
}
}

@ -6,7 +6,6 @@ use Laravel\Passport\Http\Middleware\CreateFreshApiToken as CreateFreshApiToken;
class CustomCreateFreshApiToken extends CreateFreshApiToken class CustomCreateFreshApiToken extends CreateFreshApiToken
{ {
/** /**
* Determine if the request should receive a fresh token. * Determine if the request should receive a fresh token.
* *
@ -15,6 +14,6 @@ class CustomCreateFreshApiToken extends CreateFreshApiToken
*/ */
protected function requestShouldReceiveFreshToken($request) protected function requestShouldReceiveFreshToken($request)
{ {
return !is_null($request->user($this->guard)); return ! is_null($request->user($this->guard));
} }
} }

@ -16,7 +16,7 @@ class ForceJsonResponse
public function handle($request, Closure $next) public function handle($request, Closure $next)
{ {
$request->headers->set('Accept', 'application/json'); $request->headers->set('Accept', 'application/json');
return $next($request); return $next($request);
} }
} }

@ -2,12 +2,12 @@
namespace App\Http\Middleware; namespace App\Http\Middleware;
use Closure; use App\Facades\Settings;
use Carbon\Carbon; use Carbon\Carbon;
use Closure;
use Illuminate\Http\Response; use Illuminate\Http\Response;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use App\Facades\Settings;
class KickOutInactiveUser class KickOutInactiveUser
{ {
@ -16,7 +16,7 @@ class KickOutInactiveUser
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param \Closure $next * @param \Closure $next
* @param string $guards * @param string $guards
* @return mixed * @return mixed
*/ */
public function handle($request, Closure $next, ...$guards) public function handle($request, Closure $next, ...$guards)
@ -28,9 +28,9 @@ class KickOutInactiveUser
if (Auth::guest() || $request->bearerToken() || config('auth.defaults.guard') === 'reverse-proxy-guard') { if (Auth::guest() || $request->bearerToken() || config('auth.defaults.guard') === 'reverse-proxy-guard') {
return $next($request); return $next($request);
} }
$user = Auth::user(); $user = Auth::user();
$now = Carbon::now(); $now = Carbon::now();
$inactiveFor = $now->diffInSeconds(Carbon::parse($user->last_seen_at)); $inactiveFor = $now->diffInSeconds(Carbon::parse($user->last_seen_at));
// Fetch all setting values // Fetch all setting values
@ -38,18 +38,17 @@ class KickOutInactiveUser
// If user has been inactive longer than the allowed inactivity period // If user has been inactive longer than the allowed inactivity period
if ($kickUserAfterXSecond > 0 && $inactiveFor > $kickUserAfterXSecond) { if ($kickUserAfterXSecond > 0 && $inactiveFor > $kickUserAfterXSecond) {
$user->last_seen_at = $now->format('Y-m-d H:i:s'); $user->last_seen_at = $now->format('Y-m-d H:i:s');
$user->save(); $user->save();
Log::info('Inactive user detected, authentication rejected'); Log::info('Inactive user detected, authentication rejected');
if (method_exists('Illuminate\Support\Facades\Auth', 'logout')) { if (method_exists('Illuminate\Support\Facades\Auth', 'logout')) {
Auth::logout(); Auth::logout();
} }
return response()->json(['message' => 'inactivity detected'], Response::HTTP_I_AM_A_TEAPOT); return response()->json(['message' => 'inactivity detected'], Response::HTTP_I_AM_A_TEAPOT);
} }
return $next($request); return $next($request);
} }
} }

@ -2,8 +2,8 @@
namespace App\Http\Middleware; namespace App\Http\Middleware;
use Closure;
use Carbon\Carbon; use Carbon\Carbon;
use Closure;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
class LogUserLastSeen class LogUserLastSeen
@ -13,7 +13,7 @@ class LogUserLastSeen
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param \Closure $next * @param \Closure $next
* @param string $guards * @param string $guards
* @return mixed * @return mixed
*/ */
public function handle($request, Closure $next, ...$guards) public function handle($request, Closure $next, ...$guards)
@ -25,7 +25,7 @@ class LogUserLastSeen
// - Guest // - Guest
// - User authenticated against a bearer token // - User authenticated against a bearer token
// - User authenticated via a reverse-proxy // - User authenticated via a reverse-proxy
if (Auth::guard($guard)->check() && !$request->bearerToken() && config('auth.defaults.guard') !== 'reverse-proxy-guard') { if (Auth::guard($guard)->check() && ! $request->bearerToken() && config('auth.defaults.guard') !== 'reverse-proxy-guard') {
Auth::guard($guard)->user()->last_seen_at = Carbon::now()->format('Y-m-d H:i:s'); Auth::guard($guard)->user()->last_seen_at = Carbon::now()->format('Y-m-d H:i:s');
Auth::guard($guard)->user()->save(); Auth::guard($guard)->user()->save();
break; break;

@ -14,4 +14,4 @@ class PreventRequestsDuringMaintenance extends Middleware
protected $except = [ protected $except = [
// //
]; ];
} }

@ -22,7 +22,7 @@ class RejectIfAuthenticated
foreach ($guards as $guard) { foreach ($guards as $guard) {
if (Auth::guard($guard)->check()) { if (Auth::guard($guard)->check()) {
return response()->json(['message' => __('auth.already_authenticated')], 400); return response()->json(['message' => __('auth.already_authenticated')], 400);
} }
} }

@ -17,8 +17,7 @@ class RejectIfDemoMode
*/ */
public function handle($request, Closure $next) public function handle($request, Closure $next)
{ {
if (config('2fauth.config.isDemoApp')) {
if( config('2fauth.config.isDemoApp') ) {
Log::info('Cannot request this action in Demo mode'); Log::info('Cannot request this action in Demo mode');
return response()->json(['message' => __('auth.forms.disabled_in_demo')], Response::HTTP_UNAUTHORIZED); return response()->json(['message' => __('auth.forms.disabled_in_demo')], Response::HTTP_UNAUTHORIZED);

@ -20,7 +20,8 @@ class RejectIfReverseProxy
Log::info('Cannot request this action in Demo mode'); Log::info('Cannot request this action in Demo mode');
return response()->json([ return response()->json([
'message' => __('errors.unsupported_with_reverseproxy')], 400); 'message' => __('errors.unsupported_with_reverseproxy'),
], 400);
} }
return $next($request); return $next($request);

@ -2,9 +2,9 @@
namespace App\Http\Middleware; namespace App\Http\Middleware;
use App\Facades\Settings;
use Closure; use Closure;
use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\App;
use App\Facades\Settings;
class SetLanguage class SetLanguage
{ {
@ -26,16 +26,17 @@ class SetLanguage
// FI: Settings::get() always returns a fallback value // FI: Settings::get() always returns a fallback value
$lang = Settings::get('lang'); $lang = Settings::get('lang');
if($lang === 'browser') { if ($lang === 'browser') {
$lang = config('app.fallback_locale'); $lang = config('app.fallback_locale');
$accepted = str_replace(' ', '', $request->header("Accept-Language")); $accepted = str_replace(' ', '', $request->header('Accept-Language'));
if ($accepted && $accepted !== '*') { if ($accepted && $accepted !== '*') {
$prefLocales = array_reduce( $prefLocales = array_reduce(
array_diff(explode(',', $accepted), ['*']), array_diff(explode(',', $accepted), ['*']),
function ($res, $el) { function ($res, $el) {
list($l, $q) = array_merge(explode(';q=', $el), [1]); [$l, $q] = array_merge(explode(';q=', $el), [1]);
$res[$l] = (float) $q; $res[$l] = (float) $q;
return $res; return $res;
}, },
[] []

@ -26,7 +26,7 @@ class SkipIfAuthenticated
return response()->json([ return response()->json([
'message' => 'authenticated', 'message' => 'authenticated',
'name' => $user 'name' => $user,
], 200); ], 200);
} }
} }

@ -19,8 +19,7 @@ class TrustProxies extends Middleware
* *
* @var int * @var int
*/ */
protected $headers = protected $headers = Request::HEADER_X_FORWARDED_FOR |
Request::HEADER_X_FORWARDED_FOR |
Request::HEADER_X_FORWARDED_HOST | Request::HEADER_X_FORWARDED_HOST |
Request::HEADER_X_FORWARDED_PORT | Request::HEADER_X_FORWARDED_PORT |
Request::HEADER_X_FORWARDED_PROTO | Request::HEADER_X_FORWARDED_PROTO |
@ -33,4 +32,4 @@ class TrustProxies extends Middleware
{ {
$this->proxies = (string) config('2fauth.config.trustedProxies'); $this->proxies = (string) config('2fauth.config.trustedProxies');
} }
} }

@ -2,10 +2,7 @@
namespace App\Http\Requests; namespace App\Http\Requests;
use Illuminate\Support\Facades\DB;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class LoginRequest extends FormRequest class LoginRequest extends FormRequest
{ {
@ -30,7 +27,7 @@ class LoginRequest extends FormRequest
'email' => [ 'email' => [
'required', 'required',
'email', 'email',
new \App\Rules\CaseInsensitiveEmailExists new \App\Rules\CaseInsensitiveEmailExists,
], ],
'password' => 'required|string', 'password' => 'required|string',
]; ];

@ -5,7 +5,6 @@ namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
class UserDeleteRequest extends FormRequest class UserDeleteRequest extends FormRequest
{ {
/** /**

@ -26,7 +26,7 @@ class UserPatchPwdRequest extends FormRequest
{ {
return [ return [
'currentPassword' => 'required', 'currentPassword' => 'required',
'password' => 'required|confirmed|string|min:8', 'password' => 'required|confirmed|string|min:8',
]; ];
} }
} }

@ -24,9 +24,9 @@ class UserStoreRequest extends FormRequest
public function rules() public function rules()
{ {
return [ return [
'name' => [new \App\Rules\FirstUser, 'required', 'string', 'max:255'], 'name' => [new \App\Rules\FirstUser, 'required', 'string', 'max:255'],
'email' => 'required|string|email|max:255', 'email' => 'required|string|email|max:255',
'password' => 'required|string|min:8|confirmed', 'password' => 'required|string|min:8|confirmed',
]; ];
} }
} }

@ -25,9 +25,9 @@ class UserUpdateRequest extends FormRequest
public function rules() public function rules()
{ {
return [ return [
'name' => 'required|string|max:255', 'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255', 'email' => 'required|string|email|max:255',
'password' => 'required', 'password' => 'required',
]; ];
} }
} }

@ -3,7 +3,6 @@
namespace App\Http\Requests; namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
class WebauthnDeviceLostRequest extends FormRequest class WebauthnDeviceLostRequest extends FormRequest
{ {
@ -28,7 +27,7 @@ class WebauthnDeviceLostRequest extends FormRequest
'email' => [ 'email' => [
'required', 'required',
'email', 'email',
new \App\Rules\CaseInsensitiveEmailExists new \App\Rules\CaseInsensitiveEmailExists,
], ],
]; ];
} }

@ -3,7 +3,6 @@
namespace App\Http\Requests; namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
class WebauthnRecoveryRequest extends FormRequest class WebauthnRecoveryRequest extends FormRequest
{ {
@ -25,8 +24,8 @@ class WebauthnRecoveryRequest extends FormRequest
public function rules() public function rules()
{ {
return [ return [
'token' => 'required', 'token' => 'required',
'email' => 'required|email', 'email' => 'required|email',
'password' => 'required', 'password' => 'required',
]; ];
} }

@ -28,4 +28,4 @@ class WebauthnRenameRequest extends FormRequest
'name' => 'required|string', 'name' => 'required|string',
]; ];
} }
} }

@ -29,4 +29,4 @@ class CleanIconStorage
Storage::disk('icons')->delete($event->twofaccount->icon ?? []); Storage::disk('icons')->delete($event->twofaccount->icon ?? []);
Log::info(sprintf('Icon cleaned for deleted TwoFAccount #%d', $event->twofaccount->id)); Log::info(sprintf('Icon cleaned for deleted TwoFAccount #%d', $event->twofaccount->id));
} }
} }

@ -2,8 +2,8 @@
namespace App\Listeners; namespace App\Listeners;
use App\Models\TwoFAccount;
use App\Events\GroupDeleting; use App\Events\GroupDeleting;
use App\Models\TwoFAccount;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
class DissociateTwofaccountFromGroup class DissociateTwofaccountFromGroup
@ -28,9 +28,9 @@ class DissociateTwofaccountFromGroup
{ {
TwoFAccount::where('group_id', $event->group->id) TwoFAccount::where('group_id', $event->group->id)
->update( ->update(
['group_id' => NULL] ['group_id' => null]
); );
Log::info(sprintf('TwoFAccounts dissociated from group #%d', $event->group->id)); Log::info(sprintf('TwoFAccounts dissociated from group #%d', $event->group->id));
} }
} }

@ -4,22 +4,19 @@ namespace App\Listeners;
use App\Events\ScanForNewReleaseCalled; use App\Events\ScanForNewReleaseCalled;
use App\Services\ReleaseRadarService; use App\Services\ReleaseRadarService;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
class ReleaseRadar class ReleaseRadar
{ {
/** /**
* @var ReleaseRadarService $releaseRadar * @var ReleaseRadarService
*/ */
protected $releaseRadar; protected $releaseRadar;
/** /**
* Create the event listener. * Create the event listener.
*
* @param \App\Services\ReleaseRadarService $releaseRadar
* *
* @param \App\Services\ReleaseRadarService $releaseRadar
* @return void * @return void
*/ */
public function __construct(ReleaseRadarService $releaseRadar) public function __construct(ReleaseRadarService $releaseRadar)
@ -27,7 +24,6 @@ class ReleaseRadar
$this->releaseRadar = $releaseRadar; $this->releaseRadar = $releaseRadar;
} }
/** /**
* Handle the event. * Handle the event.
* *
@ -39,4 +35,4 @@ class ReleaseRadar
$this->releaseRadar->scheduledScan(); $this->releaseRadar->scheduledScan();
Log::info('Scheduled release scan complete'); Log::info('Scheduled release scan complete');
} }
} }

@ -6,4 +6,4 @@ class HotpDto extends OtpDto
{ {
/* @var integer */ /* @var integer */
public int $counter; public int $counter;
} }

@ -9,4 +9,4 @@ class OtpDto
/* @var integer */ /* @var integer */
public string $otp_type; public string $otp_type;
} }

@ -9,4 +9,4 @@ class TotpDto extends OtpDto
/* @var integer */ /* @var integer */
public int $period; public int $period;
} }

@ -3,16 +3,15 @@
namespace App\Models; namespace App\Models;
use App\Events\GroupDeleting; use App\Events\GroupDeleting;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Database\Eloquent\Factories\HasFactory;
/** /**
* @property int $twofaccounts_count * @property int $twofaccounts_count
*/ */
class Group extends Model class Group extends Model
{ {
use HasFactory; use HasFactory;
/** /**
@ -22,7 +21,6 @@ class Group extends Model
*/ */
protected $fillable = ['name']; protected $fillable = ['name'];
/** /**
* The accessors to append to the model's array form. * The accessors to append to the model's array form.
* *
@ -30,7 +28,6 @@ class Group extends Model
*/ */
protected $appends = []; protected $appends = [];
/** /**
* The attributes that should be hidden for arrays. * The attributes that should be hidden for arrays.
* *
@ -38,7 +35,6 @@ class Group extends Model
*/ */
protected $hidden = ['created_at', 'updated_at']; protected $hidden = ['created_at', 'updated_at'];
/** /**
* The attributes that should be cast. * The attributes that should be cast.
* *
@ -48,7 +44,6 @@ class Group extends Model
'twofaccounts_count' => 'integer', 'twofaccounts_count' => 'integer',
]; ];
/** /**
* The event map for the model. * The event map for the model.
* *
@ -58,7 +53,6 @@ class Group extends Model
'deleting' => GroupDeleting::class, 'deleting' => GroupDeleting::class,
]; ];
/** /**
* Override The "booting" method of the model * Override The "booting" method of the model
* *
@ -75,10 +69,9 @@ class Group extends Model
}); });
} }
/** /**
* Get the TwoFAccounts of the group. * Get the TwoFAccounts of the group.
* *
* @return \Illuminate\Database\Eloquent\Relations\HasMany<TwoFAccount> * @return \Illuminate\Database\Eloquent\Relations\HasMany<TwoFAccount>
*/ */
public function twofaccounts() public function twofaccounts()

@ -4,7 +4,6 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
class Option extends Model class Option extends Model
{ {
/** /**
@ -17,7 +16,6 @@ class Option extends Model
'value', 'value',
]; ];
/** /**
* Indicates if the model should be timestamped. * Indicates if the model should be timestamped.
* *
@ -25,12 +23,10 @@ class Option extends Model
*/ */
public $timestamps = false; public $timestamps = false;
/** /**
* Casts. * Casts.
* *
* @var array<string, string> * @var array<string, string>
*/ */
protected $casts = []; protected $casts = [];
}
}

@ -2,9 +2,9 @@
namespace App\Models\Traits; namespace App\Models\Traits;
use Illuminate\Support\Str;
use App\Notifications\WebauthnRecoveryNotification; use App\Notifications\WebauthnRecoveryNotification;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Str;
/** /**
* @see \App\Models\WebAuthnAuthenticatable * @see \App\Models\WebAuthnAuthenticatable
@ -17,38 +17,36 @@ trait WebAuthnManageCredentials
* *
* @return string * @return string
*/ */
public function userHandle(): string public function userHandle() : string
{ {
// Laragear\WebAuthn uses Ramsey\Uuid\Uuid::fromString()->getHex()->toString() // Laragear\WebAuthn uses Ramsey\Uuid\Uuid::fromString()->getHex()->toString()
// to obtain a UUID v4 with dashes removed and uses it as user_id (aka userHandle) // to obtain a UUID v4 with dashes removed and uses it as user_id (aka userHandle)
// see https://github.com/ramsey/uuid/blob/4.x/src/Uuid.php#L379 // see https://github.com/ramsey/uuid/blob/4.x/src/Uuid.php#L379
// and Laragear\WebAuthn\Assertion\Validator\Pipes\CheckCredentialIsForUser::validateId() // and Laragear\WebAuthn\Assertion\Validator\Pipes\CheckCredentialIsForUser::validateId()
return $this->webAuthnCredentials()->value('user_id') return $this->webAuthnCredentials()->value('user_id')
?? str_replace('-', '', Str::uuid()->toString()); ?? str_replace('-', '', Str::uuid()->toString());
} }
/** /**
* Saves a new alias for a given WebAuthn credential. * Saves a new alias for a given WebAuthn credential.
* *
* @param string $id * @param string $id
* @param string $alias * @param string $alias
* @return bool * @return bool
*/ */
public function renameCredential(string $id, string $alias): bool public function renameCredential(string $id, string $alias) : bool
{ {
return boolval($this->webAuthnCredentials()->whereKey($id)->update(['alias' => $alias])); return boolval($this->webAuthnCredentials()->whereKey($id)->update(['alias' => $alias]));
} }
/** /**
* Removes one or more credentials previously registered. * Removes one or more credentials previously registered.
* *
* @param string|array $id * @param string|array $id
* @return void * @return void
*/ */
public function flushCredential($id): void public function flushCredential($id) : void
{ {
if (! $this->relationLoaded('webAuthnCredentials')) { if (! $this->relationLoaded('webAuthnCredentials')) {
$this->webAuthnCredentials()->whereKey($id)->delete(); $this->webAuthnCredentials()->whereKey($id)->delete();
@ -63,15 +61,13 @@ trait WebAuthnManageCredentials
} }
} }
/** /**
* Sends a webauthn recovery email to the user. * Sends a webauthn recovery email to the user.
* *
* @param string $token * @param string $token
*
* @return void * @return void
*/ */
public function sendWebauthnRecoveryNotification(string $token): void public function sendWebauthnRecoveryNotification(string $token) : void
{ {
// $accountRecoveryNotification = new WebauthnRecoveryNotification($token); // $accountRecoveryNotification = new WebauthnRecoveryNotification($token);
// $accountRecoveryNotification->toMailUsing(null); // $accountRecoveryNotification->toMailUsing(null);
@ -92,6 +88,5 @@ trait WebAuthnManageCredentials
// }); // });
$this->notify(new WebauthnRecoveryNotification($token)); $this->notify(new WebauthnRecoveryNotification($token));
} }
} }

@ -2,55 +2,62 @@
namespace App\Models; namespace App\Models;
use Exception;
use App\Services\LogoService;
use App\Facades\Settings;
use App\Models\Dto\TotpDto;
use App\Models\Dto\HotpDto;
use App\Events\TwoFAccountDeleted; use App\Events\TwoFAccountDeleted;
use App\Exceptions\InvalidSecretException;
use App\Exceptions\InvalidOtpParameterException; use App\Exceptions\InvalidOtpParameterException;
use App\Exceptions\UnsupportedOtpTypeException; use App\Exceptions\InvalidSecretException;
use App\Exceptions\UndecipherableException; use App\Exceptions\UndecipherableException;
use Illuminate\Validation\ValidationException; use App\Exceptions\UnsupportedOtpTypeException;
use Spatie\EloquentSortable\Sortable; use App\Facades\Settings;
use Spatie\EloquentSortable\SortableTrait; use App\Helpers\Helpers;
use OTPHP\TOTP; use App\Models\Dto\HotpDto;
use OTPHP\HOTP; use App\Models\Dto\TotpDto;
use OTPHP\Factory; use App\Services\LogoService;
use SteamTotp\SteamTotp; use Exception;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Crypt; use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Validation\ValidationException;
use OTPHP\Factory;
use OTPHP\HOTP;
use OTPHP\TOTP;
use ParagonIE\ConstantTime\Base32; use ParagonIE\ConstantTime\Base32;
use Illuminate\Support\Facades\App; use Spatie\EloquentSortable\Sortable;
use Illuminate\Support\Facades\Http; use Spatie\EloquentSortable\SortableTrait;
use App\Helpers\Helpers; use SteamTotp\SteamTotp;
class TwoFAccount extends Model implements Sortable class TwoFAccount extends Model implements Sortable
{ {
use SortableTrait, HasFactory; use SortableTrait, HasFactory;
const TOTP = 'totp'; const TOTP = 'totp';
const HOTP = 'hotp';
const HOTP = 'hotp';
const STEAM_TOTP = 'steamtotp'; const STEAM_TOTP = 'steamtotp';
const SHA1 = 'sha1'; const SHA1 = 'sha1';
const MD5 = 'md5';
const SHA256 = 'sha256'; const MD5 = 'md5';
const SHA512 = 'sha512';
const SHA256 = 'sha256';
const SHA512 = 'sha512';
const DEFAULT_PERIOD = 30; const DEFAULT_PERIOD = 30;
const DEFAULT_COUNTER = 0; const DEFAULT_COUNTER = 0;
const DEFAULT_DIGITS = 6; const DEFAULT_DIGITS = 6;
const DEFAULT_ALGORITHM = self::SHA1; const DEFAULT_ALGORITHM = self::SHA1;
const DUPLICATE_ID = -1; const DUPLICATE_ID = -1;
const FAKE_ID = -2; const FAKE_ID = -2;
private const IMAGELINK_STORAGE_PATH = 'imagesLink/'; private const IMAGELINK_STORAGE_PATH = 'imagesLink/';
@ -80,7 +87,6 @@ class TwoFAccount extends Model implements Sortable
// 'icon' // 'icon'
]; ];
/** /**
* The table associated with the model. * The table associated with the model.
* *
@ -88,26 +94,23 @@ class TwoFAccount extends Model implements Sortable
*/ */
protected $table = 'twofaccounts'; protected $table = 'twofaccounts';
/** /**
* The accessors to append to the model's array form. * The accessors to append to the model's array form.
* *
* @var array * @var array
*/ */
public $appends = []; public $appends = [];
/** /**
* The model's default values for attributes. * The model's default values for attributes.
* *
* @var array * @var array
*/ */
protected $attributes = [ protected $attributes = [
'digits' => 6, 'digits' => 6,
'algorithm' => self::SHA1, 'algorithm' => self::SHA1,
]; ];
/** /**
* The attributes that should be hidden for arrays. * The attributes that should be hidden for arrays.
* *
@ -115,7 +118,6 @@ class TwoFAccount extends Model implements Sortable
*/ */
protected $hidden = []; protected $hidden = [];
/** /**
* The attributes that should be cast. * The attributes that should be cast.
* *
@ -123,7 +125,6 @@ class TwoFAccount extends Model implements Sortable
*/ */
protected $casts = []; protected $casts = [];
/** /**
* The event map for the model. * The event map for the model.
* *
@ -133,7 +134,6 @@ class TwoFAccount extends Model implements Sortable
'deleted' => TwoFAccountDeleted::class, 'deleted' => TwoFAccountDeleted::class,
]; ];
/** /**
* Override The "booting" method of the model * Override The "booting" method of the model
* *
@ -144,9 +144,15 @@ class TwoFAccount extends Model implements Sortable
parent::boot(); parent::boot();
static::saving(function (TwoFAccount $twofaccount) { static::saving(function (TwoFAccount $twofaccount) {
if (!$twofaccount->legacy_uri) $twofaccount->legacy_uri = $twofaccount->getURI(); if (! $twofaccount->legacy_uri) {
if ($twofaccount->otp_type == TwoFAccount::TOTP && !$twofaccount->period) $twofaccount->period = TwoFAccount::DEFAULT_PERIOD; $twofaccount->legacy_uri = $twofaccount->getURI();
if ($twofaccount->otp_type == TwoFAccount::HOTP && !$twofaccount->counter) $twofaccount->counter = TwoFAccount::DEFAULT_COUNTER; }
if ($twofaccount->otp_type == TwoFAccount::TOTP && ! $twofaccount->period) {
$twofaccount->period = TwoFAccount::DEFAULT_PERIOD;
}
if ($twofaccount->otp_type == TwoFAccount::HOTP && ! $twofaccount->counter) {
$twofaccount->counter = TwoFAccount::DEFAULT_COUNTER;
}
}); });
// static::deleted(function ($model) { // static::deleted(function ($model) {
@ -154,18 +160,16 @@ class TwoFAccount extends Model implements Sortable
// }); // });
} }
/** /**
* Settings for @spatie/eloquent-sortable package * Settings for @spatie/eloquent-sortable package
* *
* @var array * @var array
*/ */
public $sortable = [ public $sortable = [
'order_column_name' => 'order_column', 'order_column_name' => 'order_column',
'sort_when_creating' => true, 'sort_when_creating' => true,
]; ];
/** /**
* The OTP generator. * The OTP generator.
* Instanciated as null to keep the model light * Instanciated as null to keep the model light
@ -174,7 +178,6 @@ class TwoFAccount extends Model implements Sortable
*/ */
protected $generator = null; protected $generator = null;
/** /**
* Get legacy_uri attribute * Get legacy_uri attribute
* *
@ -183,9 +186,9 @@ class TwoFAccount extends Model implements Sortable
*/ */
public function getLegacyUriAttribute($value) public function getLegacyUriAttribute($value)
{ {
return $this->decryptOrReturn($value); return $this->decryptOrReturn($value);
} }
/** /**
* Set legacy_uri attribute * Set legacy_uri attribute
* *
@ -198,7 +201,6 @@ class TwoFAccount extends Model implements Sortable
$this->attributes['legacy_uri'] = $this->encryptOrReturn($value); $this->attributes['legacy_uri'] = $this->encryptOrReturn($value);
} }
/** /**
* Get account attribute * Get account attribute
* *
@ -207,13 +209,13 @@ class TwoFAccount extends Model implements Sortable
*/ */
public function getAccountAttribute($value) public function getAccountAttribute($value)
{ {
return $this->decryptOrReturn($value); return $this->decryptOrReturn($value);
} }
/** /**
* Set account attribute * Set account attribute
* *
* @param string $value * @param string $value
* @return void * @return void
*/ */
public function setAccountAttribute($value) public function setAccountAttribute($value)
@ -222,7 +224,6 @@ class TwoFAccount extends Model implements Sortable
$this->attributes['account'] = $this->encryptOrReturn($value); $this->attributes['account'] = $this->encryptOrReturn($value);
} }
/** /**
* Get secret attribute * Get secret attribute
* *
@ -231,13 +232,13 @@ class TwoFAccount extends Model implements Sortable
*/ */
public function getSecretAttribute($value) public function getSecretAttribute($value)
{ {
return $this->decryptOrReturn($value); return $this->decryptOrReturn($value);
} }
/** /**
* Set secret attribute * Set secret attribute
* *
* @param string $value * @param string $value
* @return void * @return void
*/ */
public function setSecretAttribute($value) public function setSecretAttribute($value)
@ -246,47 +247,43 @@ class TwoFAccount extends Model implements Sortable
$this->attributes['secret'] = $this->encryptOrReturn($value); $this->attributes['secret'] = $this->encryptOrReturn($value);
} }
/** /**
* Set digits attribute * Set digits attribute
* *
* @param string $value * @param string $value
* @return void * @return void
*/ */
public function setDigitsAttribute($value) public function setDigitsAttribute($value)
{ {
$this->attributes['digits'] = !$value ? 6 : $value; $this->attributes['digits'] = ! $value ? 6 : $value;
} }
/** /**
* Set algorithm attribute * Set algorithm attribute
* *
* @param string $value * @param string $value
* @return void * @return void
*/ */
public function setAlgorithmAttribute($value) public function setAlgorithmAttribute($value)
{ {
$this->attributes['algorithm'] = !$value ? self::SHA1 : strtolower($value); $this->attributes['algorithm'] = ! $value ? self::SHA1 : strtolower($value);
} }
/** /**
* Set period attribute * Set period attribute
* *
* @param string $value * @param string $value
* @return void * @return void
*/ */
public function setPeriodAttribute($value) public function setPeriodAttribute($value)
{ {
$this->attributes['period'] = !$value && $this->otp_type === self::TOTP ? self::DEFAULT_PERIOD : $value; $this->attributes['period'] = ! $value && $this->otp_type === self::TOTP ? self::DEFAULT_PERIOD : $value;
} }
/** /**
* Set counter attribute * Set counter attribute
* *
* @param string $value * @param string $value
* @return void * @return void
*/ */
public function setCounterAttribute($value) public function setCounterAttribute($value)
@ -294,19 +291,19 @@ class TwoFAccount extends Model implements Sortable
$this->attributes['counter'] = blank($value) && $this->otp_type === self::HOTP ? self::DEFAULT_COUNTER : $value; $this->attributes['counter'] = blank($value) && $this->otp_type === self::HOTP ? self::DEFAULT_COUNTER : $value;
} }
/** /**
* Returns a One-Time Password with its parameters * Returns a One-Time Password with its parameters
* *
* @return TotpDto|HotpDto
*
* @throws InvalidSecretException The secret is not a valid base32 encoded string * @throws InvalidSecretException The secret is not a valid base32 encoded string
* @throws UndecipherableException The secret cannot be deciphered * @throws UndecipherableException The secret cannot be deciphered
* @throws UnsupportedOtpTypeException The defined OTP type is not supported * @throws UnsupportedOtpTypeException The defined OTP type is not supported
* @throws InvalidOtpParameterException One OTP parameter is invalid * @throws InvalidOtpParameterException One OTP parameter is invalid
* @return TotpDto|HotpDto
*/ */
public function getOTP() public function getOTP()
{ {
Log::info(sprintf('OTP requested for TwoFAccount (%s)', $this->id ? 'id:'.$this->id: 'preview')); Log::info(sprintf('OTP requested for TwoFAccount (%s)', $this->id ? 'id:' . $this->id : 'preview'));
// Early exit if the model has an undecipherable secret // Early exit if the model has an undecipherable secret
if (strtolower($this->secret) === __('errors.indecipherable')) { if (strtolower($this->secret) === __('errors.indecipherable')) {
@ -316,38 +313,33 @@ class TwoFAccount extends Model implements Sortable
} }
$this->initGenerator(); $this->initGenerator();
try {
if ( $this->otp_type === self::HOTP ) {
$OtpDto = new HotpDto(); try {
$OtpDto->otp_type = $this->otp_type; if ($this->otp_type === self::HOTP) {
$counter = $this->generator->getParameter('counter'); $OtpDto = new HotpDto();
$OtpDto->password = $this->generator->at($counter); $OtpDto->otp_type = $this->otp_type;
$OtpDto->counter = $this->counter = $counter + 1; $counter = $this->generator->getParameter('counter');
$OtpDto->password = $this->generator->at($counter);
$OtpDto->counter = $this->counter = $counter + 1;
// The updated HOTP counter must be saved to db for persisted account only // The updated HOTP counter must be saved to db for persisted account only
if ($this->id) { if ($this->id) {
$this->save(); $this->save();
} }
} } else {
else { $OtpDto = new TotpDto();
$OtpDto->otp_type = $this->otp_type;
$OtpDto = new TotpDto(); $OtpDto->generated_at = time();
$OtpDto->otp_type = $this->otp_type; $OtpDto->password = $this->otp_type === self::TOTP
$OtpDto->generated_at = time();
$OtpDto->password = $this->otp_type === self::TOTP
? $this->generator->at($OtpDto->generated_at) ? $this->generator->at($OtpDto->generated_at)
: SteamTotp::getAuthCode(base64_encode(Base32::decodeUpper($this->secret))); : SteamTotp::getAuthCode(base64_encode(Base32::decodeUpper($this->secret)));
$OtpDto->period = $this->period; $OtpDto->period = $this->period;
} }
Log::info(sprintf('New OTP generated for TwoFAccount (%s)', $this->id ? 'id:'.$this->id: 'preview')); Log::info(sprintf('New OTP generated for TwoFAccount (%s)', $this->id ? 'id:' . $this->id : 'preview'));
return $OtpDto;
} return $OtpDto;
catch (\Exception|\Throwable $ex) { } catch (\Exception|\Throwable $ex) {
Log::error('An error occured, OTP generation aborted'); Log::error('An error occured, OTP generation aborted');
// Currently a secret issue is the only possible exception thrown by OTPHP for this stack // Currently a secret issue is the only possible exception thrown by OTPHP for this stack
// so it is Ok to send the corresponding 2FAuth exception. // so it is Ok to send the corresponding 2FAuth exception.
@ -356,52 +348,50 @@ class TwoFAccount extends Model implements Sortable
} }
} }
/** /**
* Fill the model using an array of OTP parameters. * Fill the model using an array of OTP parameters.
* Missing parameters will be set with default values * Missing parameters will be set with default values
* *
* @return $this * @return $this
*/ */
public function fillWithOtpParameters(array $parameters, bool $skipIconFetching = false) public function fillWithOtpParameters(array $parameters, bool $skipIconFetching = false)
{ {
$this->otp_type = strtolower(Arr::get($parameters, 'otp_type')); $this->otp_type = strtolower(Arr::get($parameters, 'otp_type'));
$this->account = Arr::get($parameters, 'account'); $this->account = Arr::get($parameters, 'account');
$this->service = Arr::get($parameters, 'service'); $this->service = Arr::get($parameters, 'service');
$this->icon = Arr::get($parameters, 'icon'); $this->icon = Arr::get($parameters, 'icon');
$this->secret = Arr::get($parameters, 'secret'); $this->secret = Arr::get($parameters, 'secret');
$this->algorithm = strtolower(Arr::get($parameters, 'algorithm', self::SHA1)); $this->algorithm = strtolower(Arr::get($parameters, 'algorithm', self::SHA1));
$this->digits = Arr::get($parameters, 'digits', self::DEFAULT_DIGITS); $this->digits = Arr::get($parameters, 'digits', self::DEFAULT_DIGITS);
$this->period = Arr::get($parameters, 'period', $this->otp_type == self::TOTP ? self::DEFAULT_PERIOD : null); $this->period = Arr::get($parameters, 'period', $this->otp_type == self::TOTP ? self::DEFAULT_PERIOD : null);
$this->counter = Arr::get($parameters, 'counter', $this->otp_type == self::HOTP ? self::DEFAULT_COUNTER : null); $this->counter = Arr::get($parameters, 'counter', $this->otp_type == self::HOTP ? self::DEFAULT_COUNTER : null);
$this->initGenerator(); $this->initGenerator();
// The generator could have been initialized without a secret, in that case it generates one on the fly. // The generator could have been initialized without a secret, in that case it generates one on the fly.
// The secret attribute has thus to be updated // The secret attribute has thus to be updated
$this->secret = $this->secret ?: $this->generator->getSecret(); $this->secret = $this->secret ?: $this->generator->getSecret();
if ($this->otp_type === self::STEAM_TOTP || strtolower($this->service) === 'steam') { if ($this->otp_type === self::STEAM_TOTP || strtolower($this->service) === 'steam') {
$this->enforceAsSteam(); $this->enforceAsSteam();
} }
if (!$this->icon && $skipIconFetching) { if (! $this->icon && $skipIconFetching) {
$this->icon = $this->getDefaultIcon(); $this->icon = $this->getDefaultIcon();
} }
if (!$this->icon && Settings::get('getOfficialIcons') && !$skipIconFetching) { if (! $this->icon && Settings::get('getOfficialIcons') && ! $skipIconFetching) {
$this->icon = $this->getDefaultIcon(); $this->icon = $this->getDefaultIcon();
} }
Log::info(sprintf('TwoFAccount filled with OTP parameters')); Log::info(sprintf('TwoFAccount filled with OTP parameters'));
return $this; return $this;
} }
/** /**
* Fill the model by parsing an otpauth URI * Fill the model by parsing an otpauth URI
* *
* @return $this * @return $this
*/ */
public function fillWithURI(string $uri, bool $isSteamTotp = false, bool $skipIconFetching = false) public function fillWithURI(string $uri, bool $isSteamTotp = false, bool $skipIconFetching = false)
@ -409,33 +399,32 @@ class TwoFAccount extends Model implements Sortable
// First we instanciate the OTP generator // First we instanciate the OTP generator
try { try {
$this->generator = Factory::loadFromProvisioningUri($uri); $this->generator = Factory::loadFromProvisioningUri($uri);
} } catch (\Assert\AssertionFailedException|\Assert\InvalidArgumentException|\Exception|\Throwable $ex) {
catch (\Assert\AssertionFailedException|\Assert\InvalidArgumentException|\Exception|\Throwable $ex) {
throw ValidationException::withMessages([ throw ValidationException::withMessages([
'uri' => __('validation.custom.uri.regex', ['attribute' => 'uri']) 'uri' => __('validation.custom.uri.regex', ['attribute' => 'uri']),
]); ]);
} }
// As loadFromProvisioningUri() accept URI without label (nor account nor service) we check // As loadFromProvisioningUri() accept URI without label (nor account nor service) we check
// that the account is set // that the account is set
if ( ! $this->generator->getLabel() ) { if (! $this->generator->getLabel()) {
Log::error('URI passed to fillWithURI() must contain a label'); Log::error('URI passed to fillWithURI() must contain a label');
throw ValidationException::withMessages([ throw ValidationException::withMessages([
'label' => __('validation.custom.label.required') 'label' => __('validation.custom.label.required'),
]); ]);
} }
$this->otp_type = $this->getGeneratorOtpType(); $this->otp_type = $this->getGeneratorOtpType();
$this->account = $this->generator->getLabel(); $this->account = $this->generator->getLabel();
$this->secret = $this->generator->getSecret(); $this->secret = $this->generator->getSecret();
$this->service = $this->generator->getIssuer(); $this->service = $this->generator->getIssuer();
$this->algorithm = $this->generator->getDigest(); $this->algorithm = $this->generator->getDigest();
$this->digits = $this->generator->getDigits(); $this->digits = $this->generator->getDigits();
$this->period = $this->generator->hasParameter('period') ? $this->generator->getParameter('period') : null; $this->period = $this->generator->hasParameter('period') ? $this->generator->getParameter('period') : null;
$this->counter = $this->generator->hasParameter('counter') ? $this->generator->getParameter('counter') : null; $this->counter = $this->generator->hasParameter('counter') ? $this->generator->getParameter('counter') : null;
$this->legacy_uri = $uri; $this->legacy_uri = $uri;
if ($isSteamTotp || strtolower($this->service) === 'steam') { if ($isSteamTotp || strtolower($this->service) === 'steam') {
$this->enforceAsSteam(); $this->enforceAsSteam();
} }
@ -443,16 +432,15 @@ class TwoFAccount extends Model implements Sortable
$this->icon = $this->storeImageAsIcon($this->generator->getParameter('image')); $this->icon = $this->storeImageAsIcon($this->generator->getParameter('image'));
} }
if (!$this->icon && Settings::get('getOfficialIcons') && !$skipIconFetching) { if (! $this->icon && Settings::get('getOfficialIcons') && ! $skipIconFetching) {
$this->icon = $this->getDefaultIcon(); $this->icon = $this->getDefaultIcon();
} }
Log::info(sprintf('TwoFAccount filled with an URI')); Log::info(sprintf('TwoFAccount filled with an URI'));
return $this; return $this;
} }
/** /**
* Sets model attributes to STEAM values * Sets model attributes to STEAM values
*/ */
@ -462,14 +450,13 @@ class TwoFAccount extends Model implements Sortable
$this->digits = 5; $this->digits = 5;
$this->algorithm = self::SHA1; $this->algorithm = self::SHA1;
$this->period = 30; $this->period = 30;
Log::info(sprintf('TwoFAccount configured as Steam account')); Log::info(sprintf('TwoFAccount configured as Steam account'));
} }
/** /**
* Returns the OTP type of the instanciated OTP generator * Returns the OTP type of the instanciated OTP generator
* *
* @return mixed * @return mixed
*/ */
private function getGeneratorOtpType() private function getGeneratorOtpType()
@ -477,7 +464,6 @@ class TwoFAccount extends Model implements Sortable
return Arr::get($this->generatorClassMap, get_class($this->generator)); return Arr::get($this->generatorClassMap, get_class($this->generator));
} }
/** /**
* Returns an otpauth URI built with model attribute values * Returns an otpauth URI built with model attribute values
*/ */
@ -488,9 +474,9 @@ class TwoFAccount extends Model implements Sortable
return $this->generator->getProvisioningUri(); return $this->generator->getProvisioningUri();
} }
/** /**
* Instanciates the OTP generator with model attribute values * Instanciates the OTP generator with model attribute values
*
* @throws UnsupportedOtpTypeException The defined OTP type is not supported * @throws UnsupportedOtpTypeException The defined OTP type is not supported
* @throws InvalidOtpParameterException One OTP parameter is invalid * @throws InvalidOtpParameterException One OTP parameter is invalid
*/ */
@ -519,77 +505,76 @@ class TwoFAccount extends Model implements Sortable
$this->digits ?: self::DEFAULT_DIGITS $this->digits ?: self::DEFAULT_DIGITS
); );
break; break;
default: default:
throw new UnsupportedOtpTypeException(); throw new UnsupportedOtpTypeException();
} }
if ($this->service) $this->generator->setIssuer($this->service); if ($this->service) {
if ($this->account) $this->generator->setLabel($this->account); $this->generator->setIssuer($this->service);
} }
catch (UnsupportedOtpTypeException $exception) { if ($this->account) {
$this->generator->setLabel($this->account);
}
} catch (UnsupportedOtpTypeException $exception) {
Log::error(sprintf('%s is not an OTP type supported by the current generator', $this->otp_type)); Log::error(sprintf('%s is not an OTP type supported by the current generator', $this->otp_type));
throw $exception; throw $exception;
} } catch (\Exception|\Throwable $exception) {
catch (\Exception|\Throwable $exception) {
throw new InvalidOtpParameterException($exception->getMessage()); throw new InvalidOtpParameterException($exception->getMessage());
} }
} }
/** /**
* Gets the image resource pointed by the image url and store it as an icon * Gets the image resource pointed by the image url and store it as an icon
* *
* @return string|null The filename of the stored icon or null if the operation fails * @return string|null The filename of the stored icon or null if the operation fails
*/ */
private function storeImageAsIcon(string $url) private function storeImageAsIcon(string $url)
{ {
try { try {
$path_parts = pathinfo($url); $path_parts = pathinfo($url);
$newFilename = Helpers::getUniqueFilename($path_parts['extension']); //Str::random(40).'.'.$path_parts['extension']; $newFilename = Helpers::getUniqueFilename($path_parts['extension']);
$imageFile = self::IMAGELINK_STORAGE_PATH . $newFilename; $imageFile = self::IMAGELINK_STORAGE_PATH . $newFilename;
try { try {
$response = Http::retry(3, 100)->get($url); $response = Http::retry(3, 100)->get($url);
if ($response->successful()) { if ($response->successful()) {
Storage::disk('imagesLink')->put($newFilename, $response->body()); Storage::disk('imagesLink')->put($newFilename, $response->body());
} }
} } catch (\Exception $exception) {
catch (\Exception $exception) {
Log::error(sprintf('Cannot fetch imageLink at "%s"', $url)); Log::error(sprintf('Cannot fetch imageLink at "%s"', $url));
} }
if ( in_array(Storage::mimeType($imageFile), ['image/png', 'image/jpeg', 'image/webp', 'image/bmp']) if (in_array(Storage::mimeType($imageFile), ['image/png', 'image/jpeg', 'image/webp', 'image/bmp'])
&& getimagesize(storage_path() . '/app/' . $imageFile) ) && getimagesize(storage_path() . '/app/' . $imageFile)) {
{
// Should be a valid image, we move it to the icons disk // Should be a valid image, we move it to the icons disk
if (Storage::disk('icons')->put($newFilename, Storage::disk('imagesLink')->get($newFilename))) { if (Storage::disk('icons')->put($newFilename, Storage::disk('imagesLink')->get($newFilename))) {
Storage::disk('imagesLink')->delete($newFilename); Storage::disk('imagesLink')->delete($newFilename);
} }
Log::info(sprintf('Icon file %s stored', $newFilename)); Log::info(sprintf('Icon file %s stored', $newFilename));
} } else {
else {
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
Storage::disk('imagesLink')->delete($newFilename); Storage::disk('imagesLink')->delete($newFilename);
throw new \Exception('Unsupported mimeType or missing image on storage'); throw new \Exception('Unsupported mimeType or missing image on storage');
// @codeCoverageIgnoreEnd // @codeCoverageIgnoreEnd
} }
return $newFilename; return $newFilename;
} }
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
catch (\Exception|\Throwable $ex) { catch (\Exception|\Throwable $ex) {
Log::error(sprintf('Icon storage failed: %s', $ex->getMessage())); Log::error(sprintf('Icon storage failed: %s', $ex->getMessage()));
return null; return null;
} }
// @codeCoverageIgnoreEnd // @codeCoverageIgnoreEnd
} }
/** /**
* Fetch a logo in the tfa directory and store it as a new stand alone icon * Fetch a logo in the tfa directory and store it as a new stand alone icon
* *
* @return string|null The icon * @return string|null The icon
*/ */
private function getDefaultIcon() private function getDefaultIcon()
@ -599,28 +584,23 @@ class TwoFAccount extends Model implements Sortable
return Settings::get('getOfficialIcons') ? $logoService->getIcon($this->service) : null; return Settings::get('getOfficialIcons') ? $logoService->getIcon($this->service) : null;
} }
/** /**
* Returns an acceptable value * Returns an acceptable value
*/ */
private function decryptOrReturn(mixed $value) : mixed private function decryptOrReturn(mixed $value) : mixed
{ {
// Decipher when needed // Decipher when needed
if ( Settings::get('useEncryption') && $value ) if (Settings::get('useEncryption') && $value) {
{
try { try {
return Crypt::decryptString($value); return Crypt::decryptString($value);
} } catch (Exception $ex) {
catch (Exception $ex) {
return __('errors.indecipherable'); return __('errors.indecipherable');
} }
} } else {
else {
return $value; return $value;
} }
} }
/** /**
* Encrypt a value * Encrypt a value
*/ */
@ -629,5 +609,4 @@ class TwoFAccount extends Model implements Sortable
// should be replaced by laravel 8 attribute encryption casting // should be replaced by laravel 8 attribute encryption casting
return Settings::get('useEncryption') ? Crypt::encryptString($value) : $value; return Settings::get('useEncryption') ? Crypt::encryptString($value) : $value;
} }
}
}

@ -2,14 +2,14 @@
namespace App\Models; namespace App\Models;
use Illuminate\Auth\Notifications\ResetPassword;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Passport\HasApiTokens;
use Illuminate\Support\Facades\Log;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Laragear\WebAuthn\WebAuthnAuthentication;
use App\Models\Traits\WebAuthnManageCredentials; use App\Models\Traits\WebAuthnManageCredentials;
use Illuminate\Auth\Notifications\ResetPassword;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Log;
use Laragear\WebAuthn\WebAuthnAuthentication;
use Laravel\Passport\HasApiTokens;
class User extends Authenticatable implements WebAuthnAuthenticatable class User extends Authenticatable implements WebAuthnAuthenticatable
{ {
@ -53,27 +53,27 @@ class User extends Authenticatable implements WebAuthnAuthenticatable
public function sendPasswordResetNotification($token) public function sendPasswordResetNotification($token)
{ {
$this->notify(new ResetPassword($token)); $this->notify(new ResetPassword($token));
Log::info('Password reset token sent'); Log::info('Password reset token sent');
} }
/** /**
* set Email attribute * set Email attribute
* @param string $value *
* @param string $value
*/ */
public function setEmailAttribute($value) : void public function setEmailAttribute($value) : void
{ {
$this->attributes['email'] = strtolower($value); $this->attributes['email'] = strtolower($value);
} }
/** /**
* Returns an WebAuthnAuthenticatable user from a given Credential ID. * Returns an WebAuthnAuthenticatable user from a given Credential ID.
* *
* @param string $id * @param string $id
* @return WebAuthnAuthenticatable|null * @return WebAuthnAuthenticatable|null
*/ */
public static function getFromCredentialId(string $id): ?WebAuthnAuthenticatable public static function getFromCredentialId(string $id) : ?WebAuthnAuthenticatable
{ {
return static::whereHas( return static::whereHas(
'webauthnCredentials', 'webauthnCredentials',

@ -11,18 +11,16 @@ interface WebAuthnAuthenticatable extends Authenticatable
* *
* @return string * @return string
*/ */
public function userHandle(): string; public function userHandle() : string;
/** /**
* Saves a new alias for a given WebAuthn credential. * Saves a new alias for a given WebAuthn credential.
* *
* @param string $id * @param string $id
* @param string $alias * @param string $alias
* @return bool * @return bool
*/ */
public function renameCredential(string $id, string $alias): bool; public function renameCredential(string $id, string $alias) : bool;
/** /**
* Removes one or more credentials previously registered. * Removes one or more credentials previously registered.
@ -30,14 +28,13 @@ interface WebAuthnAuthenticatable extends Authenticatable
* @param string|array $id * @param string|array $id
* @return void * @return void
*/ */
public function flushCredential($id): void; public function flushCredential($id) : void;
/** /**
* Sends a webauthn recovery email to the user. * Sends a webauthn recovery email to the user.
* *
* @param string $token * @param string $token
* @return void * @return void
*/ */
public function sendWebauthnRecoveryNotification(string $token): void; public function sendWebauthnRecoveryNotification(string $token) : void;
} }

@ -66,16 +66,16 @@ class WebauthnRecoveryNotification extends Notification
// if (static::$createUrlCallback) { // if (static::$createUrlCallback) {
// $url = call_user_func(static::$createUrlCallback, $notifiable, $this->token); // $url = call_user_func(static::$createUrlCallback, $notifiable, $this->token);
// } else { // } else {
$url = url( $url = url(
route( route(
'webauthn.recover', 'webauthn.recover',
[ [
'token' => $this->token, 'token' => $this->token,
'email' => $notifiable->getEmailForPasswordReset(), 'email' => $notifiable->getEmailForPasswordReset(),
], ],
false false
) )
); );
// } // }
return (new MailMessage) return (new MailMessage)

@ -2,15 +2,14 @@
namespace App\Providers; namespace App\Providers;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Facades\Blade; use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use Illuminate\Http\Resources\Json\JsonResource;
use Laravel\Passport\Console\ClientCommand; use Laravel\Passport\Console\ClientCommand;
use Laravel\Passport\Console\InstallCommand; use Laravel\Passport\Console\InstallCommand;
use Laravel\Passport\Console\KeysCommand; use Laravel\Passport\Console\KeysCommand;
class AppServiceProvider extends ServiceProvider class AppServiceProvider extends ServiceProvider
{ {
/** /**

@ -2,16 +2,15 @@
namespace App\Providers; namespace App\Providers;
use App\Extensions\RemoteUserProvider;
use App\Extensions\WebauthnCredentialBroker;
use App\Facades\Settings;
use App\Services\Auth\ReverseProxyGuard;
use Illuminate\Auth\Passwords\DatabaseTokenRepository;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use App\Services\Auth\ReverseProxyGuard;
use App\Extensions\RemoteUserProvider;
use App\Facades\Settings;
use Illuminate\Support\Facades\Config;
use RuntimeException;
use App\Extensions\WebauthnCredentialBroker;
use Illuminate\Auth\Passwords\DatabaseTokenRepository;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use RuntimeException;
class AuthServiceProvider extends ServiceProvider class AuthServiceProvider extends ServiceProvider
{ {
@ -24,20 +23,19 @@ class AuthServiceProvider extends ServiceProvider
// 'App\Models\Model' => 'App\Policies\ModelPolicy', // 'App\Models\Model' => 'App\Policies\ModelPolicy',
]; ];
/** /**
* Register the service provider. * Register the service provider.
* *
* @return void * @return void
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException * @throws \Illuminate\Contracts\Container\BindingResolutionException
*/ */
public function register(): void public function register() : void
{ {
$this->app->singleton( $this->app->singleton(
WebauthnCredentialBroker::class, WebauthnCredentialBroker::class,
static function ($app) { static function ($app) {
if (!$config = $app['config']['auth.passwords.webauthn']) { if (! $config = $app['config']['auth.passwords.webauthn']) {
throw new RuntimeException('You must set the [webauthn] key broker in [auth] config.'); throw new RuntimeException('You must set the [webauthn] key broker in [auth] config.');
} }
@ -62,7 +60,6 @@ class AuthServiceProvider extends ServiceProvider
); );
} }
/** /**
* Register any authentication / authorization services. * Register any authentication / authorization services.
* *
@ -75,18 +72,17 @@ class AuthServiceProvider extends ServiceProvider
// Register a custom provider for reverse-proxy authentication // Register a custom provider for reverse-proxy authentication
Auth::provider('remote-user', function ($app, array $config) { Auth::provider('remote-user', function ($app, array $config) {
// Return an instance of Illuminate\Contracts\Auth\UserProvider... // Return an instance of Illuminate\Contracts\Auth\UserProvider...
return new RemoteUserProvider; return new RemoteUserProvider;
}); });
// Register a custom driver for reverse-proxy authentication // Register a custom driver for reverse-proxy authentication
Auth::extend('reverse-proxy', function ($app, string $name, array $config) { Auth::extend('reverse-proxy', function ($app, string $name, array $config) {
// Return an instance of Illuminate\Contracts\Auth\Guard... // Return an instance of Illuminate\Contracts\Auth\Guard...
return new ReverseProxyGuard(Auth::createUserProvider($config['provider'])); return new ReverseProxyGuard(Auth::createUserProvider($config['provider']));
}); });
// Previously we were using a custom user provider derived from the Larapass user provider // Previously we were using a custom user provider derived from the Larapass user provider
// in order to honor the "useWebauthnOnly" user option. // in order to honor the "useWebauthnOnly" user option.
// Since Laragear\WebAuthn now replaces DarkGhostHunter\Larapass, the new approach is // Since Laragear\WebAuthn now replaces DarkGhostHunter\Larapass, the new approach is
@ -94,7 +90,7 @@ class AuthServiceProvider extends ServiceProvider
// with a custom closure that uses the "useWebauthnOnly" user option // with a custom closure that uses the "useWebauthnOnly" user option
Auth::provider( Auth::provider(
'eloquent-webauthn', 'eloquent-webauthn',
static function (\Illuminate\Contracts\Foundation\Application $app, array $config): \Laragear\WebAuthn\Auth\WebAuthnUserProvider { static function (\Illuminate\Contracts\Foundation\Application $app, array $config) : \Laragear\WebAuthn\Auth\WebAuthnUserProvider {
return new \Laragear\WebAuthn\Auth\WebAuthnUserProvider( return new \Laragear\WebAuthn\Auth\WebAuthnUserProvider(
$app->make('hash'), $app->make('hash'),
$config['model'], $config['model'],
@ -104,11 +100,10 @@ class AuthServiceProvider extends ServiceProvider
} }
); );
// Normally we should set the Passport routes here using Passport::routes(). // Normally we should set the Passport routes here using Passport::routes().
// If so the passport routes would be set for both 'web' and 'api' middlewares without // If so the passport routes would be set for both 'web' and 'api' middlewares without
// possibility to exclude the web middleware (we can only pass additional middlewares to Passport::routes()) // possibility to exclude the web middleware (we can only pass additional middlewares to Passport::routes())
// //
// The problem is that 2Fauth front-end uses the Laravel FreshApiToken to consum its API as a first party app. // The problem is that 2Fauth front-end uses the Laravel FreshApiToken to consum its API as a first party app.
// So we have a laravel_token cookie added to each response to perform the authentication. // So we have a laravel_token cookie added to each response to perform the authentication.
// //

@ -2,8 +2,8 @@
namespace App\Providers; namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Broadcast; use Illuminate\Support\Facades\Broadcast;
use Illuminate\Support\ServiceProvider;
class BroadcastServiceProvider extends ServiceProvider class BroadcastServiceProvider extends ServiceProvider
{ {

@ -3,11 +3,11 @@
namespace App\Providers; namespace App\Providers;
use App\Events\GroupDeleting; use App\Events\GroupDeleting;
use App\Events\TwoFAccountDeleted;
use App\Events\ScanForNewReleaseCalled; use App\Events\ScanForNewReleaseCalled;
use App\Listeners\ReleaseRadar; use App\Events\TwoFAccountDeleted;
use App\Listeners\CleanIconStorage; use App\Listeners\CleanIconStorage;
use App\Listeners\DissociateTwofaccountFromGroup; use App\Listeners\DissociateTwofaccountFromGroup;
use App\Listeners\ReleaseRadar;
use Illuminate\Auth\Events\Registered; use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification; use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;

Some files were not shown because too many files have changed in this diff Show More