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

View File

@ -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 @@ public function index()
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 @@ public function store(GroupStoreRequest $request)
->setStatusCode(201); ->setStatusCode(201);
} }
/** /**
* Display the specified resource. * Display the specified resource.
* *
@ -56,12 +52,11 @@ public function show(Group $group)
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 @@ public function update(GroupStoreRequest $request, Group $group)
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 @@ public function assignAccounts(GroupAssignRequest $request, Group $group)
$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 @@ public function assignAccounts(GroupAssignRequest $request, Group $group)
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 @@ public function destroy(Group $group)
return response()->json(null, 204); return response()->json(null, 204);
} }
} }

View File

@ -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 @@ public function upload(Request $request)
: 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 @@ public function fetch(Request $request)
$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);
} }
} }

View File

@ -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 @@ public function show(TwoFAccount $twofaccount)
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 @@ public function decode(QrCodeDecodeRequest $request)
? 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);
} }
}
}

View File

@ -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 @@ public function show($settingName)
} }
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 @@ public function store(SettingStoreRequest $request)
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 @@ public function update(SettingUpdateRequest $request, string $settingName)
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 @@ public function destroy(string $settingName)
} }
$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);
} }
} }

View File

@ -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 @@ public function index(Request $request)
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 @@ public function show(TwoFAccount $twofaccount)
return new TwoFAccountReadResource($twofaccount); return new TwoFAccountReadResource($twofaccount);
} }
/** /**
* Store a new 2FA account * Store a new 2FA account
* *
@ -60,13 +56,12 @@ public function store(TwoFAccountDynamicRequest $request)
// - 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 @@ public function store(TwoFAccountDynamicRequest $request)
->setStatusCode(201); ->setStatusCode(201);
} }
/** /**
* Update a 2FA account * Update a 2FA account
* *
@ -98,10 +91,8 @@ public function update(TwoFAccountUpdateRequest $request, TwoFAccount $twofaccou
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 @@ public function migrate(TwoFAccountImportRequest $request)
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 @@ public function reorder(TwoFAccountReorderRequest $request)
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 @@ public function preview(TwoFAccountUriRequest $request)
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 @@ public function otp(Request $request, $id = null)
} }
// 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 @@ public function otp(Request $request, $id = null)
// 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 @@ public function otp(Request $request, $id = null)
*/ */
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 @@ public function destroy(TwoFAccount $twofaccount)
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 @@ public function batchDestroy(TwoFAccountBatchRequest $request)
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 @@ public function batchDestroy(TwoFAccountBatchRequest $request)
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;
} }
} }

View File

@ -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 @@ public function show(Request $request)
return $user return $user
? new UserResource($user) ? new UserResource($user)
: response()->json(['name' => null], 200); : response()->json(['name' => null], 200);
} }
} }

View File

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

View File

@ -28,4 +28,4 @@ public function rules()
'qrcode' => 'required|image', 'qrcode' => 'required|image',
]; ];
} }
} }

View File

@ -25,7 +25,7 @@ public function authorize()
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',
]; ];
} }

View File

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

View File

@ -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 @@ public function rules()
return $rules; return $rules;
} }
/** /**
* Prepare the data for validation. * Prepare the data for validation.
* *
@ -41,8 +40,8 @@ public function rules()
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),
]); ]);
} }
} }

View File

@ -26,7 +26,7 @@ public function rules()
{ {
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',
]; ];
} }
} }

View File

@ -25,19 +25,18 @@ public function authorize()
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 @@ public function rules()
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),
]); ]);
} }

View File

@ -25,19 +25,18 @@ public function authorize()
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 @@ public function rules()
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),
]); ]);
} }

View File

@ -30,7 +30,6 @@ public function rules()
]; ];
} }
/** /**
* Prepare the data for validation. * Prepare the data for validation.
* *
@ -42,4 +41,4 @@ protected function prepareForValidation()
'custom_otp' => strtolower($this->custom_otp), 'custom_otp' => strtolower($this->custom_otp),
]); ]);
} }
} }

View File

@ -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,
]; ];
} }
} }

View File

@ -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 @@ public function toArray($request)
// 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;
} }
} }

View File

@ -18,10 +18,10 @@ public function toArray($request)
{ {
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)
); );
} }
} }

View File

@ -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,
]; ];
} }
} }

View File

@ -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),
]; ];
} }
} }

View File

@ -4,7 +4,6 @@
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 @@ public function handle() : int
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;
} }
} }
} }

View File

@ -42,12 +42,13 @@ public function __construct()
*/ */
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 @@ public function handle()
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 @@ public function handle()
$this->line('Task completed'); $this->line('Task completed');
} }
} }

View File

@ -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 @@ public function __construct()
*/ */
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 @@ public function handle()
$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');
} }
} }
} }

View File

@ -40,15 +40,15 @@ public function __construct()
*/ */
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 @@ public function handle()
$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

View File

@ -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 @@ protected function generateIcons() : void
$this->line('Icons regenerated'); $this->line('Icons regenerated');
} }
/** /**
* Reset DB * Reset DB
*/ */
@ -81,10 +80,9 @@ protected function flushDB() : void
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');
} }
} }

View File

@ -27,7 +27,7 @@ protected function schedule(Schedule $schedule)
*/ */
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');
} }

View File

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

View File

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

View File

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

View File

@ -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 @@ public function register()
{ {
$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);
} }
}); });
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 @@ public function retrieveById($identifier)
} }
/** /**
* @inheritDoc * {@inheritDoc}
* *
* @codeCoverageIgnore * @codeCoverageIgnore
*/ */
public function retrieveByToken($identifier, $token) public function retrieveByToken($identifier, $token)
@ -77,8 +74,8 @@ public function retrieveByToken($identifier, $token)
} }
/** /**
* @inheritDoc * {@inheritDoc}
* *
* @codeCoverageIgnore * @codeCoverageIgnore
*/ */
public function updateRememberToken(Authenticatable $user, $token) public function updateRememberToken(Authenticatable $user, $token)
@ -87,8 +84,8 @@ public function updateRememberToken(Authenticatable $user, $token)
} }
/** /**
* @inheritDoc * {@inheritDoc}
* *
* @codeCoverageIgnore * @codeCoverageIgnore
*/ */
public function retrieveByCredentials(array $credentials) public function retrieveByCredentials(array $credentials)
@ -97,12 +94,12 @@ public function retrieveByCredentials(array $credentials)
} }
/** /**
* @inheritDoc * {@inheritDoc}
* *
* @codeCoverageIgnore * @codeCoverageIgnore
*/ */
public function validateCredentials(Authenticatable $user, array $credentials) public function validateCredentials(Authenticatable $user, array $credentials)
{ {
return true; return true;
} }
} }

View File

@ -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 @@ public function sendResetLink(array $credentials, Closure $callback = null): str
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;
} }

View File

@ -11,4 +11,4 @@ protected static function getFacadeAccessor()
{ {
return GroupService::class; return GroupService::class;
} }
} }

View File

@ -11,4 +11,4 @@ protected static function getFacadeAccessor()
{ {
return QrCodeService::class; return QrCodeService::class;
} }
} }

View File

@ -11,4 +11,4 @@ protected static function getFacadeAccessor()
{ {
return SettingService::class; return SettingService::class;
} }
} }

View File

@ -11,4 +11,4 @@ protected static function getFacadeAccessor()
{ {
return TwoFAccountService::class; return TwoFAccountService::class;
} }
} }

View File

@ -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 @@ private function isPlainText(string $migrationPayload) : bool
// - 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 @@ private function isAegisJSON(string $migrationPayload) : mixed
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 @@ private function isAegisJSON(string $migrationPayload) : mixed
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 @@ private function is2FASv2(string $migrationPayload) : mixed
// } // }
$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 @@ private function is2FASv2(string $migrationPayload) : mixed
return false; return false;
} }
} }

View File

@ -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;
} }

View File

@ -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;
} }

View File

@ -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.
* *

View File

@ -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 @@ public function login(LoginRequest $request)
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 @@ public function logout(Request $request)
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 @@ protected function sendLoginResponse(Request $request)
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 @@ protected function sendFailedLoginResponse(Request $request)
{ {
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 @@ protected function sendLockoutResponse(Request $request)
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 @@ protected function credentials(Request $request)
{ {
$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 @@ protected function authenticated(Request $request, $user)
Log::info('User authenticated'); Log::info('User authenticated');
} }
} }

View File

@ -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 @@ public function update(UserPatchPwdRequest $request)
return response()->json(['message' => __('auth.forms.password_successfully_changed')]); return response()->json(['message' => __('auth.forms.password_successfully_changed')]);
} }
} }

View File

@ -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 @@ public function register(UserStoreRequest $request)
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 @@ public function register(UserStoreRequest $request)
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']),
]); ]);
} }

View File

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

View File

@ -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 @@ public function update(UserUpdateRequest $request)
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 @@ public function delete(UserDeleteRequest $request)
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 @@ public function delete(UserDeleteRequest $request)
// @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 @@ public function delete(UserDeleteRequest $request)
return response()->json(null, 204); return response()->json(null, 204);
} }
} }

View File

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

View File

@ -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 @@ public function sendRecoveryEmail(WebauthnDeviceLostRequest $request, WebauthnCr
: $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 @@ protected function sendRecoveryLinkFailedResponse(Request $request, string $resp
->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')]);
} }
} }

View File

@ -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 @@ public function options(AssertionRequest $request): Responsable|JsonResponse
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 @@ public function login(AssertedRequest $request)
// 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 @@ protected function authenticated($user)
Log::info('User authenticated via webauthn'); Log::info('User authenticated via webauthn');
} }
} }

View File

@ -4,16 +4,15 @@
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 @@ public function index(Request $request)
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 @@ public function rename(WebauthnRenameRequest $request, string $credential)
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 @@ public function delete(Request $request, $credential)
return response()->json(null, 204); return response()->json(null, 204);
} }
} }

View File

@ -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 @@ function ($user) use ($request) {
$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)]]);
} }
} }
} }

View File

@ -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 @@ public function options(AttestationRequest $request): Responsable
->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();
} }
} }

View File

@ -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
{ {

View File

@ -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 @@ public function index()
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 */
]); ]);
} }
} }

View File

@ -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 @@ public function infos(Request $request)
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)

View File

@ -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,
]; ];

View File

@ -20,9 +20,8 @@ protected function authenticate($request, array $guards)
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 @@ protected function authenticate($request, array $guards)
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);
} }
}
}

View File

@ -6,7 +6,6 @@
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));
} }
} }

View File

@ -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);
} }
} }

View File

@ -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 @@ public function handle($request, Closure $next, ...$guards)
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 @@ public function handle($request, Closure $next, ...$guards)
// 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);
} }
} }

View File

@ -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 @@ public function handle($request, Closure $next, ...$guards)
// - 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;

View File

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

View File

@ -22,7 +22,7 @@ public function handle(Request $request, Closure $next, ...$guards)
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);
} }
} }

View File

@ -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);

View File

@ -20,7 +20,8 @@ public function handle($request, Closure $next)
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);

View File

@ -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 @@ public function handle($request, Closure $next)
// 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;
}, },
[] []

View File

@ -26,7 +26,7 @@ public function handle(Request $request, Closure $next, ...$guards)
return response()->json([ return response()->json([
'message' => 'authenticated', 'message' => 'authenticated',
'name' => $user 'name' => $user,
], 200); ], 200);
} }
} }

View File

@ -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 @@ public function __construct()
{ {
$this->proxies = (string) config('2fauth.config.trustedProxies'); $this->proxies = (string) config('2fauth.config.trustedProxies');
} }
} }

View File

@ -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 @@ public function rules()
'email' => [ 'email' => [
'required', 'required',
'email', 'email',
new \App\Rules\CaseInsensitiveEmailExists new \App\Rules\CaseInsensitiveEmailExists,
], ],
'password' => 'required|string', 'password' => 'required|string',
]; ];

View File

@ -5,7 +5,6 @@
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
{ {
/** /**

View File

@ -26,7 +26,7 @@ public function rules()
{ {
return [ return [
'currentPassword' => 'required', 'currentPassword' => 'required',
'password' => 'required|confirmed|string|min:8', 'password' => 'required|confirmed|string|min:8',
]; ];
} }
} }

View File

@ -24,9 +24,9 @@ public function authorize()
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',
]; ];
} }
} }

View File

@ -25,9 +25,9 @@ public function authorize()
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',
]; ];
} }
} }

View File

@ -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 @@ public function rules()
'email' => [ 'email' => [
'required', 'required',
'email', 'email',
new \App\Rules\CaseInsensitiveEmailExists new \App\Rules\CaseInsensitiveEmailExists,
], ],
]; ];
} }

View File

@ -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 @@ public function authorize()
public function rules() public function rules()
{ {
return [ return [
'token' => 'required', 'token' => 'required',
'email' => 'required|email', 'email' => 'required|email',
'password' => 'required', 'password' => 'required',
]; ];
} }

View File

@ -28,4 +28,4 @@ public function rules()
'name' => 'required|string', 'name' => 'required|string',
]; ];
} }
} }

View File

@ -29,4 +29,4 @@ public function handle(TwoFAccountDeleted $event)
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));
} }
} }

View File

@ -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 @@ public function handle(GroupDeleting $event)
{ {
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));
} }
} }

View File

@ -4,22 +4,19 @@
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 @@ public function __construct(ReleaseRadarService $releaseRadar)
$this->releaseRadar = $releaseRadar; $this->releaseRadar = $releaseRadar;
} }
/** /**
* Handle the event. * Handle the event.
* *
@ -39,4 +35,4 @@ public function handle(ScanForNewReleaseCalled $event)
$this->releaseRadar->scheduledScan(); $this->releaseRadar->scheduledScan();
Log::info('Scheduled release scan complete'); Log::info('Scheduled release scan complete');
} }
} }

View File

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

View File

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

View File

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

View File

@ -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 @@ protected static function boot()
}); });
} }
/** /**
* 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()

View File

@ -4,7 +4,6 @@
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 = [];
}
}

View File

@ -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 @@ 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
{ {
// $accountRecoveryNotification = new WebauthnRecoveryNotification($token); // $accountRecoveryNotification = new WebauthnRecoveryNotification($token);
// $accountRecoveryNotification->toMailUsing(null); // $accountRecoveryNotification->toMailUsing(null);
@ -92,6 +88,5 @@ public function sendWebauthnRecoveryNotification(string $token): void
// }); // });
$this->notify(new WebauthnRecoveryNotification($token)); $this->notify(new WebauthnRecoveryNotification($token));
} }
} }

View File

@ -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 @@ protected static function boot()
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 @@ protected static function boot()
// }); // });
} }
/** /**
* 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 @@ protected static function boot()
*/ */
protected $generator = null; protected $generator = null;
/** /**
* Get legacy_uri attribute * Get legacy_uri attribute
* *
@ -183,9 +186,9 @@ protected static function boot()
*/ */
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 @@ public function setLegacyUriAttribute($value)
$this->attributes['legacy_uri'] = $this->encryptOrReturn($value); $this->attributes['legacy_uri'] = $this->encryptOrReturn($value);
} }
/** /**
* Get account attribute * Get account attribute
* *
@ -207,13 +209,13 @@ public function setLegacyUriAttribute($value)
*/ */
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 @@ public function setAccountAttribute($value)
$this->attributes['account'] = $this->encryptOrReturn($value); $this->attributes['account'] = $this->encryptOrReturn($value);
} }
/** /**
* Get secret attribute * Get secret attribute
* *
@ -231,13 +232,13 @@ public function setAccountAttribute($value)
*/ */
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 @@ public function setSecretAttribute($value)
$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 @@ public function setCounterAttribute($value)
$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 @@ public function getOTP()
} }
$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 @@ public function getOTP()
} }
} }
/** /**
* 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 @@ public function fillWithURI(string $uri, bool $isSteamTotp = false, bool $skipIc
// 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 @@ public function fillWithURI(string $uri, bool $isSteamTotp = false, bool $skipIc
$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 @@ private function enforceAsSteam() : void
$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 @@ private function getGeneratorOtpType()
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 @@ public function getURI() : string
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 @@ private function initGenerator() : void
$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 @@ private function getDefaultIcon()
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 @@ private function encryptOrReturn(mixed $value) : mixed
// 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;
} }
}
}

View File

@ -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',

View File

@ -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 @@ public function renameCredential(string $id, string $alias): bool;
* @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;
} }

View File

@ -66,16 +66,16 @@ public function toMail($notifiable)
// 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)

View File

@ -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
{ {
/** /**

View File

@ -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 @@ static function ($app) {
); );
} }
/** /**
* Register any authentication / authorization services. * Register any authentication / authorization services.
* *
@ -75,18 +72,17 @@ public function boot()
// 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 @@ public function boot()
// 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 @@ static function (\Illuminate\Contracts\Foundation\Application $app, array $confi
} }
); );
// 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.
// //

View File

@ -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
{ {

View File

@ -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