mirror of
https://github.com/Bubka/2FAuth.git
synced 2025-03-26 22:16:06 +01:00
Refactor Options to a Setting service bound with the service container
This commit is contained in:
parent
afaa1a0a7a
commit
10fc144246
@ -1,62 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes;
|
||||
|
||||
class Options
|
||||
{
|
||||
|
||||
/**
|
||||
* Compile both default and user options
|
||||
*
|
||||
* @return Options collection or a signle
|
||||
*/
|
||||
public static function get($option = null)
|
||||
{
|
||||
// Get a collection of user saved options
|
||||
$userOptions = \Illuminate\Support\Facades\DB::table('options')->pluck('value', 'key');
|
||||
|
||||
// We replace patterned string that represent booleans with real booleans
|
||||
$userOptions->transform(function ($item, $key) {
|
||||
if( $item === '{{}}' ) {
|
||||
return false;
|
||||
}
|
||||
else if( $item === '{{1}}' ) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return $item;
|
||||
}
|
||||
});
|
||||
|
||||
// Merge options from App configuration. It ensures we have a complete options collection with
|
||||
// fallback values for every options
|
||||
$options = collect(config('app.options'))->merge($userOptions);
|
||||
|
||||
if( $option ) {
|
||||
|
||||
return isset($options[$option]) ? $options[$option] : null;
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set user options
|
||||
*
|
||||
* @param array All options to store
|
||||
* @return void
|
||||
*/
|
||||
public static function store($userOptions)
|
||||
{
|
||||
foreach($userOptions as $opt => $val) {
|
||||
|
||||
// We replace boolean values by a patterned string in order to retrieve
|
||||
// them later (as the Laravel Options package do not support var type)
|
||||
// Not a beatufilly solution but, hey, it works ^_^
|
||||
option([$opt => is_bool($val) ? '{{' . $val . '}}' : $val]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -2,7 +2,6 @@
|
||||
|
||||
namespace App;
|
||||
|
||||
use App\Classes\Options;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Group extends Model
|
||||
|
164
app/Http/Controllers/SettingController.php
Normal file
164
app/Http/Controllers/SettingController.php
Normal file
@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\SettingStoreRequest;
|
||||
use App\Http\Requests\SettingUpdateRequest;
|
||||
use App\Services\SettingServiceInterface;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Classes\DbProtection;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
|
||||
class SettingController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* The Settings Service instance.
|
||||
*/
|
||||
protected SettingServiceInterface $settingService;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new controller instance.
|
||||
*
|
||||
*/
|
||||
public function __construct(SettingServiceInterface $SettingServiceInterface)
|
||||
{
|
||||
$this->settingService = $SettingServiceInterface;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* List all settings
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$settings = $this->settingService->all();
|
||||
$settingsResources = collect();
|
||||
$settings->each(function ($item, $key) use ($settingsResources) {
|
||||
$settingsResources->push([
|
||||
'name' => $key,
|
||||
'data' => $item
|
||||
]);
|
||||
});
|
||||
|
||||
// return SettingResource::collection($tata);
|
||||
return response()->json($settingsResources->all(), 200);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Display a resource
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return \App\Http\Resources\TwoFAccountReadResource
|
||||
*/
|
||||
public function show($name)
|
||||
{
|
||||
$setting = $this->settingService->get($name);
|
||||
|
||||
if (!$setting) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'name' => $name,
|
||||
'data' => $setting
|
||||
], 200);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Save options
|
||||
* @return [type] [description]
|
||||
*/
|
||||
public function store(SettingStoreRequest $request)
|
||||
{
|
||||
$validated = $request->validated();
|
||||
|
||||
$this->settingService->set($validated['name'], $validated['data']);
|
||||
|
||||
return response()->json([
|
||||
'name' => $validated['name'],
|
||||
'data' => $validated['data']
|
||||
], 201);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Save options
|
||||
* @return [type] [description]
|
||||
*/
|
||||
public function update(SettingUpdateRequest $request, $name)
|
||||
{
|
||||
$validated = $request->validated();
|
||||
|
||||
$setting = $this->settingService->get($name);
|
||||
|
||||
if (is_null($setting)) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
$setting = $this->settingService->set($name, $validated['data']);
|
||||
|
||||
return response()->json([
|
||||
'name' => $name,
|
||||
'data' => $validated['data']
|
||||
], 200);
|
||||
|
||||
// The useEncryption option impacts the [existing] content of the database.
|
||||
// Encryption/Decryption of the data is done only if the user change the value of the option
|
||||
// to prevent successive encryption
|
||||
|
||||
if( $request->has('useEncryption'))
|
||||
{
|
||||
if( $request->useEncryption && !$this->settingService->get('useEncryption') ) {
|
||||
|
||||
// user enabled the encryption
|
||||
if( !DbProtection::enable() ) {
|
||||
return response()->json(['message' => __('errors.error_during_encryption')], 400);
|
||||
}
|
||||
}
|
||||
else if( !$request->useEncryption && $this->settingService->get('useEncryption') ) {
|
||||
|
||||
// user disabled the encryption
|
||||
if( !DbProtection::disable() ) {
|
||||
return response()->json(['message' => __('errors.error_during_decryption')], 400);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Save options
|
||||
* @return [type] [description]
|
||||
*/
|
||||
public function destroy($name)
|
||||
{
|
||||
$setting = $this->settingService->get($name);
|
||||
|
||||
if (is_null($setting)) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
$optionsConfig = config('app.options');
|
||||
if(array_key_exists($name, $optionsConfig)) {
|
||||
return response()->json(
|
||||
['message' => 'bad request',
|
||||
'reason' => [__('errors.delete_user_setting_only')]
|
||||
], 400);
|
||||
}
|
||||
|
||||
$this->settingService->delete($name);
|
||||
|
||||
return response()->json(null, 204);
|
||||
}
|
||||
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Settings;
|
||||
|
||||
use App\Classes\Options;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Classes\DbProtection;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
class OptionController extends Controller
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* Get options
|
||||
* @return [type] [description]
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
// Fetch all setting values
|
||||
$settings = Options::get();
|
||||
|
||||
return response()->json(['settings' => $settings], 200);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Save options
|
||||
* @return [type] [description]
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
// The useEncryption option impacts the [existing] content of the database.
|
||||
// Encryption/Decryption of the data is done only if the user change the value of the option
|
||||
// to prevent successive encryption
|
||||
|
||||
if( isset($request->useEncryption))
|
||||
{
|
||||
if( $request->useEncryption && !Options::get('useEncryption') ) {
|
||||
|
||||
// user enabled the encryption
|
||||
if( !DbProtection::enable() ) {
|
||||
return response()->json(['message' => __('errors.error_during_encryption'), 'settings' => Options::get()], 400);
|
||||
}
|
||||
}
|
||||
else if( !$request->useEncryption && Options::get('useEncryption') ) {
|
||||
|
||||
// user disabled the encryption
|
||||
if( !DbProtection::disable() ) {
|
||||
return response()->json(['message' => __('errors.error_during_decryption'), 'settings' => Options::get()], 400);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Store all options
|
||||
Options::store($request->all());
|
||||
|
||||
return response()->json(['message' => __('settings.forms.setting_saved'), 'settings' => Options::get()], 200);
|
||||
}
|
||||
|
||||
}
|
@ -2,18 +2,35 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Classes\Options;
|
||||
use App\Services\SettingServiceInterface;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class SinglePageController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* The Settings Service instance.
|
||||
*/
|
||||
protected SettingServiceInterface $settingService;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new controller instance.
|
||||
*
|
||||
*/
|
||||
public function __construct(SettingServiceInterface $SettingServiceInterface)
|
||||
{
|
||||
$this->settingService = $SettingServiceInterface;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* return the main view
|
||||
* @return view
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
return view('landing')->with('appSettings', Options::get()->toJson());
|
||||
return view('landing')->with('appSettings', $this->settingService->all()->toJson());
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@
|
||||
|
||||
use App\Group;
|
||||
use App\TwoFAccount;
|
||||
use App\Classes\Options;
|
||||
use App\Http\Requests\TwoFAccountReorderRequest;
|
||||
use App\Http\Requests\TwoFAccountStoreRequest;
|
||||
use App\Http\Requests\TwoFAccountUpdateRequest;
|
||||
|
@ -5,7 +5,6 @@
|
||||
use Closure;
|
||||
use App\User;
|
||||
use Carbon\Carbon;
|
||||
use App\Classes\Options;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
@ -32,7 +31,8 @@ public function handle($request, Closure $next)
|
||||
$inactiveFor = $now->diffInSeconds(Carbon::parse($user->last_seen_at));
|
||||
|
||||
// Fetch all setting values
|
||||
$settings = Options::get();
|
||||
$settingService = resolve('App\Services\SettingServiceInterface');
|
||||
$settings = $settingService->all();
|
||||
|
||||
$kickUserAfterXSecond = intval($settings['kickUserAfter']) * 60;
|
||||
|
||||
|
31
app/Http/Requests/SettingStoreRequest.php
Normal file
31
app/Http/Requests/SettingStoreRequest.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class SettingStoreRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'name' => 'required|alpha|max:128|unique:options,key',
|
||||
'data' => 'required',
|
||||
];
|
||||
}
|
||||
}
|
30
app/Http/Requests/SettingUpdateRequest.php
Normal file
30
app/Http/Requests/SettingUpdateRequest.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class SettingUpdateRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'data' => 'required',
|
||||
];
|
||||
}
|
||||
}
|
29
app/Providers/TwoFAuthServiceProvider.php
Normal file
29
app/Providers/TwoFAuthServiceProvider.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Services\SettingServiceInterface;
|
||||
use App\Services\AppstractOptionsService;
|
||||
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
||||
|
||||
class TwoFAuthServiceProvider extends ServiceProvider
|
||||
{
|
||||
|
||||
/**
|
||||
* Register any events for your application.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Register stuff.
|
||||
*
|
||||
*/
|
||||
public function register() : void
|
||||
{
|
||||
$this->app->bind(SettingServiceInterface::class, AppstractOptionsService::class);
|
||||
}
|
||||
}
|
94
app/Services/AppstractOptionsService.php
Normal file
94
app/Services/AppstractOptionsService.php
Normal file
@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class AppstractOptionsService implements SettingServiceInterface
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function get(string $setting)
|
||||
{
|
||||
$value = option($setting, config('app.options' . $setting));
|
||||
$value = $this->restoreType($value);
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function all() : Collection
|
||||
{
|
||||
// Get a collection of user saved options
|
||||
$userOptions = DB::table('options')->pluck('value', 'key');
|
||||
$userOptions->transform(function ($item, $key) {
|
||||
return $this->restoreType($item);
|
||||
});
|
||||
$userOptions = collect(config('app.options'))->merge($userOptions);
|
||||
|
||||
return $userOptions;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function set($setting, $value = null) : void
|
||||
{
|
||||
$settings = is_array($setting) ? $setting : [$setting => $value];
|
||||
|
||||
foreach ($settings as $setting => $value) {
|
||||
$settings[$setting] = $this->replaceBoolean($value);
|
||||
}
|
||||
|
||||
option($settings);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function delete(string $name) : void
|
||||
{
|
||||
option()->remove($name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Replaces boolean by a patterned string as appstrack/laravel-options package does not support var type
|
||||
*
|
||||
* @param \Illuminate\Support\Collection $settings
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
private function replaceBoolean($value)
|
||||
{
|
||||
return is_bool($value) ? '{{' . $value . '}}' : $value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Replaces patterned string that represent booleans with real booleans
|
||||
*
|
||||
* @param \Illuminate\Support\Collection $settings
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
private function restoreType($value)
|
||||
{
|
||||
$value = is_numeric($value) ? (float) $value : $value;
|
||||
|
||||
if( $value === '{{}}' ) {
|
||||
return false;
|
||||
}
|
||||
else if( $value === '{{1}}' ) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
}
|
@ -4,12 +4,28 @@
|
||||
|
||||
use App\Group;
|
||||
use App\TwoFAccount;
|
||||
use App\Classes\Options;
|
||||
use App\Services\SettingServiceInterface;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class GroupService
|
||||
{
|
||||
|
||||
/**
|
||||
* The Settings Service instance.
|
||||
*/
|
||||
protected SettingServiceInterface $settingService;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new controller instance.
|
||||
*
|
||||
*/
|
||||
public function __construct(SettingServiceInterface $SettingServiceInterface)
|
||||
{
|
||||
$this->settingService = $SettingServiceInterface;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns all existing groups
|
||||
*
|
||||
@ -129,7 +145,7 @@ public function getAccounts(Group $group) : Collection
|
||||
*/
|
||||
private function destinationGroup() : Group
|
||||
{
|
||||
$id = Options::get('defaultGroup') === '-1' ? (int) Options::get('activeGroup') : (int) Options::get('defaultGroup');
|
||||
$id = $this->settingService->get('defaultGroup') === '-1' ? (int) $this->settingService->get('activeGroup') : (int) $this->settingService->get('defaultGroup');
|
||||
|
||||
return Group::find($id);
|
||||
}
|
||||
|
41
app/Services/SettingServiceInterface.php
Normal file
41
app/Services/SettingServiceInterface.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
interface SettingServiceInterface
|
||||
{
|
||||
/**
|
||||
* Get a setting
|
||||
*
|
||||
* @param string|array $setting A single setting name or an associative array of name:value settings
|
||||
* @return mixed string|int|boolean|null
|
||||
*/
|
||||
public function get(string $setting);
|
||||
|
||||
|
||||
/**
|
||||
* Get all settings
|
||||
*
|
||||
* @return mixed Collection of settings
|
||||
*/
|
||||
public function all() : Collection;
|
||||
|
||||
|
||||
/**
|
||||
* Set a setting
|
||||
*
|
||||
* @param string|array $setting A single setting name or an associative array of name:value settings
|
||||
* @param string|int|boolean|null $value The value for single setting
|
||||
*/
|
||||
public function set($setting, $value = null) : void;
|
||||
|
||||
|
||||
/**
|
||||
* Delete a setting
|
||||
*
|
||||
* @param string $name The setting name
|
||||
*/
|
||||
public function delete(string $name) : void;
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
namespace App;
|
||||
|
||||
use Exception;
|
||||
use OTPHP\TOTP;
|
||||
// use App\Services\SettingServiceInterface;
|
||||
use OTPHP\HOTP;
|
||||
use OTPHP\Factory;
|
||||
use App\Classes\Options;
|
||||
@ -195,8 +195,10 @@ public function setSecretAttribute($value)
|
||||
*/
|
||||
private function decryptOrReturn($value)
|
||||
{
|
||||
$settingService = resolve('App\Services\SettingServiceInterface');
|
||||
|
||||
// Decipher when needed
|
||||
if ( Options::get('useEncryption') )
|
||||
if ( $settingService->get('useEncryption') )
|
||||
{
|
||||
try {
|
||||
return Crypt::decryptString($value);
|
||||
@ -216,8 +218,10 @@ private function decryptOrReturn($value)
|
||||
*/
|
||||
private function encryptOrReturn($value)
|
||||
{
|
||||
$settingService = resolve('App\Services\SettingServiceInterface');
|
||||
|
||||
// should be replaced by laravel 8 attribute encryption casting
|
||||
return Options::get('useEncryption') ? Crypt::encryptString($value) : $value;
|
||||
return $settingService->get('useEncryption') ? Crypt::encryptString($value) : $value;
|
||||
}
|
||||
|
||||
}
|
@ -205,7 +205,8 @@
|
||||
App\Providers\AuthServiceProvider::class,
|
||||
// App\Providers\BroadcastServiceProvider::class,
|
||||
App\Providers\EventServiceProvider::class,
|
||||
App\Providers\RouteServiceProvider::class
|
||||
App\Providers\RouteServiceProvider::class,
|
||||
App\Providers\TwoFAuthServiceProvider::class
|
||||
|
||||
],
|
||||
|
||||
|
@ -29,4 +29,5 @@
|
||||
'error_during_decryption' => 'Decryption failed, your database is still protected. This is mainly caused by an integrity issue of encrypted data for one or more accounts.',
|
||||
'qrcode_cannot_be_read' => 'This QR code is unreadable',
|
||||
'too_many_ids' => 'too many ids were included in the query parameter, max 100 allowed',
|
||||
'delete_user_setting_only' => 'Only user-created setting can be deleted'
|
||||
];
|
@ -28,13 +28,20 @@
|
||||
|
||||
Route::post('auth/logout', 'Auth\LoginController@logout');
|
||||
|
||||
Route::prefix('settings')->group(function () {
|
||||
Route::get('account', 'Settings\AccountController@show');
|
||||
Route::patch('account', 'Settings\AccountController@update');
|
||||
Route::patch('password', 'Settings\PasswordController@update');
|
||||
Route::get('options', 'Settings\OptionController@index');
|
||||
Route::post('options', 'Settings\OptionController@store');
|
||||
});
|
||||
Route::get('settings/{name}', 'SettingController@show');
|
||||
Route::get('settings', 'SettingController@index');
|
||||
Route::post('settings', 'SettingController@store');
|
||||
Route::put('settings/{name}', 'SettingController@update');
|
||||
Route::delete('settings/{name}', 'SettingController@destroy');
|
||||
|
||||
// Route::prefix('settings')->group(function () {
|
||||
// Route::get('account', 'Settings\AccountController@show');
|
||||
// Route::patch('account', 'Settings\AccountController@update');
|
||||
// Route::patch('password', 'Settings\PasswordController@update');
|
||||
// Route::post('options', 'Settings\OptionController@store');
|
||||
// });
|
||||
|
||||
|
||||
|
||||
Route::delete('twofaccounts', 'TwoFAccountController@batchDestroy');
|
||||
Route::patch('twofaccounts/withdraw', 'TwoFAccountController@withdraw');
|
||||
@ -42,7 +49,7 @@
|
||||
Route::post('twofaccounts/preview', 'TwoFAccountController@preview');
|
||||
Route::get('twofaccounts/{twofaccount}/qrcode', 'QrCodeController@show');
|
||||
Route::get('twofaccounts/count', 'TwoFAccountController@count');
|
||||
Route::get('twofaccounts/{id}/otp', 'TwoFAccountController@otp')->where('id', '[0-9]+');;
|
||||
Route::get('twofaccounts/{id}/otp', 'TwoFAccountController@otp')->where('id', '[0-9]+');
|
||||
Route::post('twofaccounts/otp', 'TwoFAccountController@otp');
|
||||
Route::apiResource('twofaccounts', 'TwoFAccountController');
|
||||
Route::get('groups/{group}/twofaccounts', 'GroupController@accounts');
|
||||
|
Loading…
Reference in New Issue
Block a user