mirror of
https://github.com/Bubka/2FAuth.git
synced 2025-01-23 06:38:34 +01:00
Fix and complete reverse-proxy support & Adjust front-end views
This commit is contained in:
parent
911e18c9c4
commit
725c012042
@ -6,6 +6,7 @@
|
|||||||
use App\Api\v1\Requests\UserUpdateRequest;
|
use App\Api\v1\Requests\UserUpdateRequest;
|
||||||
use App\Api\v1\Resources\UserResource;
|
use App\Api\v1\Resources\UserResource;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class UserController extends Controller
|
class UserController extends Controller
|
||||||
{
|
{
|
||||||
@ -14,9 +15,12 @@ class UserController extends Controller
|
|||||||
*
|
*
|
||||||
* @return \App\Api\v1\Resources\UserResource
|
* @return \App\Api\v1\Resources\UserResource
|
||||||
*/
|
*/
|
||||||
public function show()
|
public function show(Request $request)
|
||||||
{
|
{
|
||||||
$user = User::first();
|
// 2 cases:
|
||||||
|
// - The method is called from a protected route > we return the request's authenticated user
|
||||||
|
// - The method is called from a guest route > we fetch a possible registered user
|
||||||
|
$user = $request->user() ?: User::first();
|
||||||
|
|
||||||
return $user
|
return $user
|
||||||
? new UserResource($user)
|
? new UserResource($user)
|
||||||
|
@ -16,8 +16,9 @@ class UserResource extends JsonResource
|
|||||||
public function toArray($request)
|
public function toArray($request)
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
'id' => $this->when($request->user(), $this->id),
|
||||||
'name' => $this->name,
|
'name' => $this->name,
|
||||||
'email' => $this->when(Auth::guard()->check(), $this->email),
|
'email' => $this->when($request->user(), $this->email),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -65,5 +65,10 @@ public function register()
|
|||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => $exception->getMessage()], 400);
|
'message' => $exception->getMessage()], 400);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$this->renderable(function (UnsupportedWithReverseProxyException $exception, $request) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => __('errors.unsupported_with_reverseproxy')], 400);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
14
app/Exceptions/UnsupportedWithReverseProxyException.php
Normal file
14
app/Exceptions/UnsupportedWithReverseProxyException.php
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class UnsupportedWithReverseProxyException.
|
||||||
|
*
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
|
class UnsupportedWithReverseProxyException extends Exception
|
||||||
|
{
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
// Part of Firefly III (https://github.com/firefly-iii)
|
// Largely inspired by Firefly III remote user implementation (https://github.com/firefly-iii)
|
||||||
// see https://github.com/firefly-iii/firefly-iii/blob/main/app/Support/Authentication/RemoteUserProvider.php
|
// see https://github.com/firefly-iii/firefly-iii/blob/main/app/Support/Authentication/RemoteUserProvider.php
|
||||||
|
|
||||||
namespace App\Extensions;
|
namespace App\Extensions;
|
||||||
@ -8,7 +8,7 @@
|
|||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Contracts\Auth\Authenticatable;
|
use Illuminate\Contracts\Auth\Authenticatable;
|
||||||
use Illuminate\Contracts\Auth\UserProvider;
|
use Illuminate\Contracts\Auth\UserProvider;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Arr;
|
||||||
use Exception;
|
use Exception;
|
||||||
|
|
||||||
class RemoteUserProvider implements UserProvider
|
class RemoteUserProvider implements UserProvider
|
||||||
@ -16,19 +16,20 @@ class RemoteUserProvider implements UserProvider
|
|||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
public function retrieveById($identifier): User
|
public function retrieveById($identifier)
|
||||||
{
|
{
|
||||||
$user = User::where('email', $identifier)->first();
|
// 2FAuth is single user by design and domain data are not coupled to the user model.
|
||||||
|
// So we provide a non-persisted user, dynamically instanciated using data
|
||||||
|
// from the auth proxy.
|
||||||
|
// This way no matter the user account used at proxy level, 2FAuth will always
|
||||||
|
// authenticate a request from the proxy and will return domain data without restriction.
|
||||||
|
//
|
||||||
|
// 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).
|
||||||
|
|
||||||
// if (null === $user) {
|
$user = new User;
|
||||||
// $user = User::create(
|
$user->name = $identifier['user'];
|
||||||
// [
|
$user->email = Arr::has($identifier, 'email') ? $identifier['email'] : $identifier['user'];
|
||||||
// 'name' => $identifier,
|
|
||||||
// 'email' => $identifier,
|
|
||||||
// 'password' => bcrypt(Str::random(64)),
|
|
||||||
// ]
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
return $user;
|
return $user;
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
use Illuminate\Http\Request;
|
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 App\Exceptions\UnsupportedWithReverseProxyException;
|
||||||
|
|
||||||
class ForgotPasswordController extends Controller
|
class ForgotPasswordController extends Controller
|
||||||
{
|
{
|
||||||
@ -21,6 +22,20 @@ class ForgotPasswordController extends Controller
|
|||||||
|
|
||||||
use SendsPasswordResetEmails;
|
use SendsPasswordResetEmails;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new controller instance.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$authGuard = config('auth.defaults.guard');
|
||||||
|
|
||||||
|
if ($authGuard === 'reverse-proxy-guard') {
|
||||||
|
throw new UnsupportedWithReverseProxyException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate the email for the given request.
|
* Validate the email for the given request.
|
||||||
*
|
*
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
use Illuminate\Validation\ValidationException;
|
use Illuminate\Validation\ValidationException;
|
||||||
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
|
use App\Exceptions\UnsupportedWithReverseProxyException;
|
||||||
|
|
||||||
|
|
||||||
class LoginController extends Controller
|
class LoginController extends Controller
|
||||||
@ -28,6 +29,20 @@ class LoginController extends Controller
|
|||||||
|
|
||||||
use AuthenticatesUsers;
|
use AuthenticatesUsers;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new controller instance.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$authGuard = config('auth.defaults.guard');
|
||||||
|
|
||||||
|
if ($authGuard === 'reverse-proxy-guard') {
|
||||||
|
throw new UnsupportedWithReverseProxyException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle a login request to the application.
|
* Handle a login request to the application.
|
||||||
*
|
*
|
||||||
|
@ -6,10 +6,25 @@
|
|||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use App\Exceptions\UnsupportedWithReverseProxyException;
|
||||||
|
|
||||||
class PasswordController extends Controller
|
class PasswordController extends Controller
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new controller instance.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$authGuard = config('auth.defaults.guard');
|
||||||
|
|
||||||
|
if ($authGuard === 'reverse-proxy-guard') {
|
||||||
|
throw new UnsupportedWithReverseProxyException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the user's password.
|
* Update the user's password.
|
||||||
*
|
*
|
||||||
|
29
app/Http/Controllers/Auth/PersonalAccessTokenController.php
Normal file
29
app/Http/Controllers/Auth/PersonalAccessTokenController.php
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Laravel\Passport\Http\Controllers\PersonalAccessTokenController as PassportPersonalAccessTokenController;
|
||||||
|
|
||||||
|
class PersonalAccessTokenController extends PassportPersonalAccessTokenController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get all of the personal access tokens for the authenticated user.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @return \Illuminate\Database\Eloquent\Collection
|
||||||
|
*/
|
||||||
|
public function forUser(Request $request)
|
||||||
|
{
|
||||||
|
// WebAuthn is useless when authentication is handle by
|
||||||
|
// a reverse proxy so we return a 202 response to tell the
|
||||||
|
// client nothing more will happen
|
||||||
|
if (config('auth.defaults.guard') === 'reverse-proxy-guard') {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'no personal access token with reverse proxy'], 202);
|
||||||
|
}
|
||||||
|
|
||||||
|
parent::forUser($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -9,6 +9,7 @@
|
|||||||
// use Illuminate\Support\Facades\Auth;
|
// use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Auth\Events\Registered;
|
use Illuminate\Auth\Events\Registered;
|
||||||
use Illuminate\Foundation\Auth\RegistersUsers;
|
use Illuminate\Foundation\Auth\RegistersUsers;
|
||||||
|
use App\Exceptions\UnsupportedWithReverseProxyException;
|
||||||
|
|
||||||
class RegisterController extends Controller
|
class RegisterController extends Controller
|
||||||
{
|
{
|
||||||
@ -26,6 +27,19 @@ class RegisterController extends Controller
|
|||||||
use RegistersUsers;
|
use RegistersUsers;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new controller instance.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$authGuard = config('auth.defaults.guard');
|
||||||
|
|
||||||
|
if ($authGuard === 'reverse-proxy-guard') {
|
||||||
|
throw new UnsupportedWithReverseProxyException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle a registration request for the application.
|
* Handle a registration request for the application.
|
||||||
*
|
*
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use Illuminate\Foundation\Auth\ResetsPasswords;
|
use Illuminate\Foundation\Auth\ResetsPasswords;
|
||||||
|
use App\Exceptions\UnsupportedWithReverseProxyException;
|
||||||
|
|
||||||
class ResetPasswordController extends Controller
|
class ResetPasswordController extends Controller
|
||||||
{
|
{
|
||||||
@ -21,4 +22,17 @@ class ResetPasswordController extends Controller
|
|||||||
|
|
||||||
use ResetsPasswords;
|
use ResetsPasswords;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new controller instance.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$authGuard = config('auth.defaults.guard');
|
||||||
|
|
||||||
|
if ($authGuard === 'reverse-proxy-guard') {
|
||||||
|
throw new UnsupportedWithReverseProxyException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -8,9 +8,23 @@
|
|||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use App\Exceptions\UnsupportedWithReverseProxyException;
|
||||||
|
|
||||||
class UserController extends Controller
|
class UserController extends Controller
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Create a new controller instance.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$authGuard = config('auth.defaults.guard');
|
||||||
|
|
||||||
|
if ($authGuard === 'reverse-proxy-guard') {
|
||||||
|
throw new UnsupportedWithReverseProxyException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the user's profile information.
|
* Update the user's profile information.
|
||||||
*
|
*
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Providers\RouteServiceProvider;
|
use App\Providers\RouteServiceProvider;
|
||||||
use DarkGhostHunter\Larapass\Http\ConfirmsWebAuthn;
|
use DarkGhostHunter\Larapass\Http\ConfirmsWebAuthn;
|
||||||
|
use App\Exceptions\UnsupportedWithReverseProxyException;
|
||||||
|
|
||||||
class WebAuthnConfirmController extends Controller
|
class WebAuthnConfirmController extends Controller
|
||||||
{
|
{
|
||||||
@ -35,7 +36,10 @@ class WebAuthnConfirmController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->middleware('auth');
|
$authGuard = config('auth.defaults.guard');
|
||||||
$this->middleware('throttle:10,1')->only('options', 'confirm');
|
|
||||||
|
if ($authGuard === 'reverse-proxy-guard') {
|
||||||
|
throw new UnsupportedWithReverseProxyException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -6,6 +6,7 @@
|
|||||||
use DarkGhostHunter\Larapass\Http\SendsWebAuthnRecoveryEmail;
|
use DarkGhostHunter\Larapass\Http\SendsWebAuthnRecoveryEmail;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Validation\ValidationException;
|
use Illuminate\Validation\ValidationException;
|
||||||
|
use App\Exceptions\UnsupportedWithReverseProxyException;
|
||||||
|
|
||||||
class WebAuthnDeviceLostController extends Controller
|
class WebAuthnDeviceLostController extends Controller
|
||||||
{
|
{
|
||||||
@ -22,9 +23,16 @@ class WebAuthnDeviceLostController extends Controller
|
|||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new controller instance.
|
||||||
|
*/
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
// $this->middleware('guest');
|
$authGuard = config('auth.defaults.guard');
|
||||||
|
|
||||||
|
if ($authGuard === 'reverse-proxy-guard') {
|
||||||
|
throw new UnsupportedWithReverseProxyException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use DarkGhostHunter\Larapass\Http\AuthenticatesWebAuthn;
|
use DarkGhostHunter\Larapass\Http\AuthenticatesWebAuthn;
|
||||||
|
use App\Exceptions\UnsupportedWithReverseProxyException;
|
||||||
|
|
||||||
class WebAuthnLoginController extends Controller
|
class WebAuthnLoginController extends Controller
|
||||||
{
|
{
|
||||||
@ -26,9 +27,16 @@ class WebAuthnLoginController extends Controller
|
|||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new controller instance.
|
||||||
|
*/
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
// $this->middleware(['guest', 'throttle:10,1']);
|
$authGuard = config('auth.defaults.guard');
|
||||||
|
|
||||||
|
if ($authGuard === 'reverse-proxy-guard') {
|
||||||
|
throw new UnsupportedWithReverseProxyException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use App\Http\Requests\WebauthnRenameRequest;
|
use App\Http\Requests\WebauthnRenameRequest;
|
||||||
use DarkGhostHunter\Larapass\Eloquent\WebAuthnCredential;
|
use DarkGhostHunter\Larapass\Eloquent\WebAuthnCredential;
|
||||||
|
use App\Exceptions\UnsupportedWithReverseProxyException;
|
||||||
|
|
||||||
class WebAuthnManageController extends Controller
|
class WebAuthnManageController extends Controller
|
||||||
{
|
{
|
||||||
@ -34,6 +35,14 @@ public function __construct()
|
|||||||
*/
|
*/
|
||||||
public function index(Request $request)
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
|
// WebAuthn is useless when authentication is handle by
|
||||||
|
// a reverse proxy so we return a 202 response to tell the
|
||||||
|
// client nothing more will happen
|
||||||
|
if (config('auth.defaults.guard') === 'reverse-proxy-guard') {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'no webauthn with reverse proxy'], 202);
|
||||||
|
}
|
||||||
|
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
$allUserCredentials = $user->webAuthnCredentials()
|
$allUserCredentials = $user->webAuthnCredentials()
|
||||||
->enabled()
|
->enabled()
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Validation\ValidationException;
|
use Illuminate\Validation\ValidationException;
|
||||||
|
use App\Exceptions\UnsupportedWithReverseProxyException;
|
||||||
|
|
||||||
class WebAuthnRecoveryController extends Controller
|
class WebAuthnRecoveryController extends Controller
|
||||||
{
|
{
|
||||||
@ -32,15 +33,17 @@ class WebAuthnRecoveryController extends Controller
|
|||||||
*/
|
*/
|
||||||
protected $redirectTo = RouteServiceProvider::HOME;
|
protected $redirectTo = RouteServiceProvider::HOME;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new controller instance.
|
* Create a new controller instance.
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
// $this->middleware('guest');
|
$authGuard = config('auth.defaults.guard');
|
||||||
// $this->middleware('throttle:10,1')->only('options', 'recover');
|
|
||||||
|
if ($authGuard === 'reverse-proxy-guard') {
|
||||||
|
throw new UnsupportedWithReverseProxyException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use DarkGhostHunter\Larapass\Http\RegistersWebAuthn;
|
use DarkGhostHunter\Larapass\Http\RegistersWebAuthn;
|
||||||
|
use App\Exceptions\UnsupportedWithReverseProxyException;
|
||||||
|
|
||||||
class WebAuthnRegisterController extends Controller
|
class WebAuthnRegisterController extends Controller
|
||||||
{
|
{
|
||||||
@ -22,11 +23,13 @@ class WebAuthnRegisterController extends Controller
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new controller instance.
|
* Create a new controller instance.
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
// $this->middleware('auth');
|
$authGuard = config('auth.defaults.guard');
|
||||||
|
|
||||||
|
if ($authGuard === 'reverse-proxy-guard') {
|
||||||
|
throw new UnsupportedWithReverseProxyException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -36,10 +36,19 @@ class Kernel extends HttpKernel
|
|||||||
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
||||||
\Illuminate\Session\Middleware\StartSession::class,
|
\Illuminate\Session\Middleware\StartSession::class,
|
||||||
// \Illuminate\Session\Middleware\AuthenticateSession::class,
|
// \Illuminate\Session\Middleware\AuthenticateSession::class,
|
||||||
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
|
||||||
\App\Http\Middleware\VerifyCsrfToken::class,
|
\App\Http\Middleware\VerifyCsrfToken::class,
|
||||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||||
\App\Http\Middleware\LogUserLastSeen::class,
|
\App\Http\Middleware\CustomCreateFreshApiToken::class,
|
||||||
|
],
|
||||||
|
|
||||||
|
'behind-auth' => [
|
||||||
|
\App\Http\Middleware\EncryptCookies::class,
|
||||||
|
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
||||||
|
\Illuminate\Session\Middleware\StartSession::class,
|
||||||
|
\App\Http\Middleware\VerifyCsrfToken::class,
|
||||||
|
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||||
|
\App\Http\Middleware\Authenticate::class,
|
||||||
|
\App\Http\Middleware\KickOutInactiveUser::class,
|
||||||
\App\Http\Middleware\CustomCreateFreshApiToken::class,
|
\App\Http\Middleware\CustomCreateFreshApiToken::class,
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -3,8 +3,43 @@
|
|||||||
namespace App\Http\Middleware;
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
use Illuminate\Auth\Middleware\Authenticate as Middleware;
|
use Illuminate\Auth\Middleware\Authenticate as Middleware;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
|
||||||
class Authenticate extends Middleware
|
class Authenticate extends Middleware
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is logged in to any of the given guards.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @param array $guards
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @throws \Illuminate\Auth\AuthenticationException
|
||||||
|
*/
|
||||||
|
protected function authenticate($request, array $guards)
|
||||||
|
{
|
||||||
|
if (empty($guards)) {
|
||||||
|
// Will retreive the default guard
|
||||||
|
$guards = [null];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// We inject the reserve-proxy guard to ensure it will be available for every routes
|
||||||
|
// besides their declared guards. This way we ensure priority to declared guards and
|
||||||
|
// a fallback to the reverse-proxy guard
|
||||||
|
$proxyGuard = 'reverse-proxy-guard';
|
||||||
|
|
||||||
|
if (config('auth.defaults.guard') === $proxyGuard && !Arr::has($guards, $proxyGuard)) {
|
||||||
|
$guards[] = $proxyGuard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($guards as $guard) {
|
||||||
|
if ($this->auth->guard($guard)->check()) {
|
||||||
|
return $this->auth->shouldUse($guard);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->unauthenticated($request, $guards);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -18,10 +18,13 @@ class KickOutInactiveUser
|
|||||||
* @param \Closure $next
|
* @param \Closure $next
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function handle($request, Closure $next, $guard = null)
|
public function handle($request, Closure $next, ...$quards)
|
||||||
{
|
{
|
||||||
// We do not track activity of guest or user authenticated against a bearer token
|
// We do not track activity of:
|
||||||
if (Auth::guest() || $request->bearerToken()) {
|
// - Guest
|
||||||
|
// - User authenticated against a bearer token
|
||||||
|
// - User authenticated via a reverse-proxy
|
||||||
|
if (Auth::guest() || $request->bearerToken() || config('auth.defaults.guard') === 'reverse-proxy-guard') {
|
||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,8 +21,11 @@ public function handle($request, Closure $next, ...$quards)
|
|||||||
$guards = empty($guards) ? [null] : $guards;
|
$guards = empty($guards) ? [null] : $guards;
|
||||||
|
|
||||||
foreach ($guards as $guard) {
|
foreach ($guards as $guard) {
|
||||||
// Activity coming from a client authenticated with a personal access token is not logged
|
// We do not track activity of:
|
||||||
if( Auth::guard($guard)->check() && !$request->bearerToken()) {
|
// - Guest
|
||||||
|
// - User authenticated against a bearer token
|
||||||
|
// - User authenticated via a reverse-proxy
|
||||||
|
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;
|
||||||
|
@ -2,12 +2,11 @@
|
|||||||
|
|
||||||
namespace App\Http\Middleware;
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
use App\Providers\RouteServiceProvider;
|
|
||||||
use Closure;
|
use Closure;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
class RedirectIfAuthenticated
|
class RejectIfAuthenticated
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Handle an incoming request.
|
* Handle an incoming request.
|
@ -18,9 +18,9 @@ class AuthServiceProvider extends ServiceProvider
|
|||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $policies = [
|
// protected $policies = [
|
||||||
// 'App\Models\Model' => 'App\Policies\ModelPolicy',
|
// 'App\Models\Model' => 'App\Policies\ModelPolicy',
|
||||||
];
|
// ];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register any authentication / authorization services.
|
* Register any authentication / authorization services.
|
||||||
@ -46,15 +46,17 @@ static function ($app, $config) {
|
|||||||
$app[Hasher::class],
|
$app[Hasher::class],
|
||||||
$config['model']
|
$config['model']
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
//
|
// 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
|
||||||
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...
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ private function getApiNamespace($version)
|
|||||||
protected function configureRateLimiting()
|
protected function configureRateLimiting()
|
||||||
{
|
{
|
||||||
RateLimiter::for('api', function (Request $request) {
|
RateLimiter::for('api', function (Request $request) {
|
||||||
return Limit::perMinute(60)->by(optional($request->user())->id ?: $request->ip());
|
return Limit::perMinute(60)->by($request->ip());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
// Largely inspired by Firefly III remote user implementation (https://github.com/firefly-iii)
|
||||||
|
// See https://github.com/firefly-iii/firefly-iii/blob/main/app/Support/Authentication/RemoteUserGuard.php
|
||||||
|
|
||||||
namespace App\Services\Auth;
|
namespace App\Services\Auth;
|
||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
@ -66,16 +69,26 @@ public function user()
|
|||||||
$user = null;
|
$user = null;
|
||||||
|
|
||||||
// Get the user identifier from $_SERVER or apache filtered headers
|
// Get the user identifier from $_SERVER or apache filtered headers
|
||||||
$header = config('auth.guard_header', 'REMOTE_USER');
|
$remoteUserHeader = config('auth.auth_proxy_headers.user', 'REMOTE_USER');
|
||||||
$userID = request()->server($header) ?? apache_request_headers()[$header] ?? null;
|
$identifier['user'] = request()->server($remoteUserHeader) ?? apache_request_headers()[$remoteUserHeader] ?? null;
|
||||||
|
|
||||||
if (null === $userID) {
|
if ($identifier['user'] === null) {
|
||||||
Log::error(sprintf('No user in header "%s".', $header));
|
Log::error(sprintf('No user in header "%s".', $remoteUserHeader));
|
||||||
return $this->user = null;
|
return $this->user = null;
|
||||||
// throw new Exception('The guard header was unexpectedly empty. See the logs.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$user = $this->provider->retrieveById($userID);
|
// Get the email identifier from $_SERVER
|
||||||
|
$remoteEmailHeader = config('auth.auth_proxy_headers.email');
|
||||||
|
|
||||||
|
if ($remoteEmailHeader) {
|
||||||
|
$remoteEmail = (string)(request()->server($remoteEmailHeader) ?? apache_request_headers()[$remoteEmailHeader] ?? null);
|
||||||
|
|
||||||
|
if ($remoteEmail) {
|
||||||
|
$identifier['email'] = $remoteEmail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = $this->provider->retrieveById($identifier);
|
||||||
|
|
||||||
return $this->user = $user;
|
return $this->user = $user;
|
||||||
}
|
}
|
||||||
|
@ -14,11 +14,24 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
'defaults' => [
|
'defaults' => [
|
||||||
'guard' => env('AUTHENTICATION_GUARD', 'web'),
|
'guard' => env('AUTHENTICATION_GUARD', 'web-guard'),
|
||||||
'passwords' => 'users',
|
'passwords' => 'users',
|
||||||
],
|
],
|
||||||
'guard_header' => env('AUTHENTICATION_GUARD_HEADER', 'REMOTE_USER'),
|
|
||||||
// 'guard_email' => env('AUTHENTICATION_GUARD_EMAIL_HEADER', null),
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Authentication Proxy Headers
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When using a reverse proxy for authentication this option controls the
|
||||||
|
| default name of the headers sent by the proxy.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'auth_proxy_headers' => [
|
||||||
|
'user' => env('AUTH_PROXY_HEADER_FOR_USER', 'REMOTE_USER'),
|
||||||
|
'email' => env('AUTH_PROXY_HEADER_FOR_EMAIL', null),
|
||||||
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
@ -38,18 +51,18 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
'guards' => [
|
'guards' => [
|
||||||
'web' => [
|
'web-guard' => [
|
||||||
'driver' => 'session',
|
'driver' => 'session',
|
||||||
'provider' => 'users',
|
'provider' => 'users',
|
||||||
],
|
],
|
||||||
|
|
||||||
'api' => [
|
'api-guard' => [
|
||||||
'driver' => 'passport',
|
'driver' => 'passport',
|
||||||
'provider' => 'users',
|
'provider' => 'users',
|
||||||
'hash' => false,
|
'hash' => false,
|
||||||
],
|
],
|
||||||
|
|
||||||
'reverse-proxy' => [
|
'reverse-proxy-guard' => [
|
||||||
'driver' => 'reverse-proxy',
|
'driver' => 'reverse-proxy',
|
||||||
'provider' => 'remote-user',
|
'provider' => 'remote-user',
|
||||||
],
|
],
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<input :id="fieldName" type="checkbox" :name="fieldName" class="is-checkradio is-info" v-model="form[fieldName]" v-on:change="$emit(fieldName, form[fieldName])" >
|
<input :id="fieldName" type="checkbox" :name="fieldName" class="is-checkradio is-info" v-model="form[fieldName]" v-on:change="$emit(fieldName, form[fieldName])" v-bind="$attrs">
|
||||||
<label :for="fieldName" class="label" v-html="label"></label>
|
<label :for="fieldName" class="label" v-html="label"></label>
|
||||||
<p class="help" v-html="help" v-if="help"></p>
|
<p class="help" v-html="help" v-if="help"></p>
|
||||||
</div>
|
</div>
|
||||||
@ -9,6 +9,7 @@
|
|||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'FormCheckbox',
|
name: 'FormCheckbox',
|
||||||
|
inheritAttrs: false,
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -129,6 +129,8 @@
|
|||||||
const { data } = await vm.axios.get('api/v1/user/name')
|
const { data } = await vm.axios.get('api/v1/user/name')
|
||||||
|
|
||||||
if( data.name ) {
|
if( data.name ) {
|
||||||
|
// The email property is only sent when the user is logged in.
|
||||||
|
// In this case we push the user to the index view.
|
||||||
if( data.email ) {
|
if( data.email ) {
|
||||||
return next({ name: 'accounts' });
|
return next({ name: 'accounts' });
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<!-- webauthn registration -->
|
<!-- webauthn registration -->
|
||||||
<form-wrapper v-if="showWebauthnRegistration" :title="$t('auth.authentication')" :punchline="$t('auth.webauthn.enforce_security_using_webauthn')">
|
<form-wrapper v-if="showWebauthnRegistration" :title="$t('auth.authentication')" :punchline="$t('auth.webauthn.enhance_security_using_webauthn')">
|
||||||
<div v-if="deviceRegistered" class="field">
|
<div v-if="deviceRegistered" class="field">
|
||||||
<label class="label mb-5">{{ $t('auth.webauthn.device_successfully_registered') }} <font-awesome-icon :icon="['fas', 'check']" /></label>
|
<label class="label mb-5">{{ $t('auth.webauthn.device_successfully_registered') }} <font-awesome-icon :icon="['fas', 'check']" /></label>
|
||||||
<form @submit.prevent="handleDeviceSubmit" @keydown="deviceForm.onKeydown($event)">
|
<form @submit.prevent="handleDeviceSubmit" @keydown="deviceForm.onKeydown($event)">
|
||||||
|
@ -4,18 +4,23 @@
|
|||||||
<div class="options-tabs">
|
<div class="options-tabs">
|
||||||
<form-wrapper>
|
<form-wrapper>
|
||||||
<form @submit.prevent="submitProfile" @keydown="formProfile.onKeydown($event)">
|
<form @submit.prevent="submitProfile" @keydown="formProfile.onKeydown($event)">
|
||||||
|
<div v-if="isRemoteUser" class="notification is-warning has-text-centered" v-html="$t('auth.user_account_controlled_by_proxy')" />
|
||||||
<h4 class="title is-4 has-text-grey-light">{{ $t('settings.profile') }}</h4>
|
<h4 class="title is-4 has-text-grey-light">{{ $t('settings.profile') }}</h4>
|
||||||
|
<fieldset :disabled="isRemoteUser">
|
||||||
<form-field :form="formProfile" fieldName="name" :label="$t('auth.forms.name')" autofocus />
|
<form-field :form="formProfile" fieldName="name" :label="$t('auth.forms.name')" autofocus />
|
||||||
<form-field :form="formProfile" fieldName="email" inputType="email" :label="$t('auth.forms.email')" />
|
<form-field :form="formProfile" fieldName="email" inputType="email" :label="$t('auth.forms.email')" />
|
||||||
<form-field :form="formProfile" fieldName="password" inputType="password" :label="$t('auth.forms.current_password.label')" :help="$t('auth.forms.current_password.help')" />
|
<form-field :form="formProfile" fieldName="password" inputType="password" :label="$t('auth.forms.current_password.label')" :help="$t('auth.forms.current_password.help')" />
|
||||||
<form-buttons :isBusy="formProfile.isBusy" :caption="$t('commons.update')" />
|
<form-buttons :isBusy="formProfile.isBusy" :caption="$t('commons.update')" />
|
||||||
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
<form @submit.prevent="submitPassword" @keydown="formPassword.onKeydown($event)">
|
<form @submit.prevent="submitPassword" @keydown="formPassword.onKeydown($event)">
|
||||||
<h4 class="title is-4 pt-6 has-text-grey-light">{{ $t('settings.change_password') }}</h4>
|
<h4 class="title is-4 pt-6 has-text-grey-light">{{ $t('settings.change_password') }}</h4>
|
||||||
|
<fieldset :disabled="isRemoteUser">
|
||||||
<form-field :form="formPassword" fieldName="password" inputType="password" :label="$t('auth.forms.new_password')" />
|
<form-field :form="formPassword" fieldName="password" inputType="password" :label="$t('auth.forms.new_password')" />
|
||||||
<form-field :form="formPassword" fieldName="password_confirmation" inputType="password" :label="$t('auth.forms.confirm_new_password')" />
|
<form-field :form="formPassword" fieldName="password_confirmation" inputType="password" :label="$t('auth.forms.confirm_new_password')" />
|
||||||
<form-field :form="formPassword" fieldName="currentPassword" inputType="password" :label="$t('auth.forms.current_password.label')" :help="$t('auth.forms.current_password.help')" />
|
<form-field :form="formPassword" fieldName="currentPassword" inputType="password" :label="$t('auth.forms.current_password.label')" :help="$t('auth.forms.current_password.help')" />
|
||||||
<form-buttons :isBusy="formPassword.isBusy" :caption="$t('auth.forms.change_password')" />
|
<form-buttons :isBusy="formPassword.isBusy" :caption="$t('auth.forms.change_password')" />
|
||||||
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
</form-wrapper>
|
</form-wrapper>
|
||||||
</div>
|
</div>
|
||||||
@ -46,13 +51,16 @@
|
|||||||
currentPassword : '',
|
currentPassword : '',
|
||||||
password : '',
|
password : '',
|
||||||
password_confirmation : '',
|
password_confirmation : '',
|
||||||
})
|
}),
|
||||||
|
isRemoteUser: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async mounted() {
|
async mounted() {
|
||||||
const { data } = await this.formProfile.get('/api/v1/user')
|
const { data } = await this.formProfile.get('/api/v1/user')
|
||||||
|
|
||||||
|
if( data.id === null ) this.isRemoteUser = true
|
||||||
|
|
||||||
this.formProfile.fill(data)
|
this.formProfile.fill(data)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -2,16 +2,16 @@
|
|||||||
<div>
|
<div>
|
||||||
<setting-tabs :activeTab="'settings.oauth'"></setting-tabs>
|
<setting-tabs :activeTab="'settings.oauth'"></setting-tabs>
|
||||||
<div class="options-tabs">
|
<div class="options-tabs">
|
||||||
<div class="columns is-centered">
|
<form-wrapper>
|
||||||
<div class="form-column column is-two-thirds-tablet is-half-desktop is-one-third-widescreen is-one-third-fullhd">
|
<div v-if="isRemoteUser" class="notification is-warning has-text-centered" v-html="$t('auth.auth_handled_by_proxy')" />
|
||||||
<h4 class="title is-4 has-text-grey-light">{{ $t('settings.personal_access_tokens') }}</h4>
|
<h4 class="title is-4 has-text-grey-light">{{ $t('settings.personal_access_tokens') }}</h4>
|
||||||
<div class="is-size-7-mobile">
|
<div class="is-size-7-mobile">
|
||||||
{{ $t('settings.token_legend')}}
|
{{ $t('settings.token_legend')}}
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
<router-link class="is-link" :to="{ name: 'settings.oauth.generatePAT' }">
|
<a class="is-link" @click="createToken()">
|
||||||
<font-awesome-icon :icon="['fas', 'plus-circle']" /> {{ $t('settings.generate_new_token')}}
|
<font-awesome-icon :icon="['fas', 'plus-circle']" /> {{ $t('settings.generate_new_token')}}
|
||||||
</router-link>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="tokens.length > 0">
|
<div v-if="tokens.length > 0">
|
||||||
<div v-for="token in tokens" :key="token.id" class="group-item has-text-light is-size-5 is-size-6-mobile">
|
<div v-for="token in tokens" :key="token.id" class="group-item has-text-light is-size-5 is-size-6-mobile">
|
||||||
@ -50,8 +50,7 @@
|
|||||||
<router-link :to="{ name: 'accounts', params: { toRefresh: false } }" class="button is-dark is-rounded">{{ $t('commons.close') }}</router-link>
|
<router-link :to="{ name: 'accounts', params: { toRefresh: false } }" class="button is-dark is-rounded">{{ $t('commons.close') }}</router-link>
|
||||||
</p>
|
</p>
|
||||||
</vue-footer>
|
</vue-footer>
|
||||||
</div>
|
</form-wrapper>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -67,7 +66,8 @@
|
|||||||
isFetching: false,
|
isFetching: false,
|
||||||
form: new Form({
|
form: new Form({
|
||||||
token : '',
|
token : '',
|
||||||
})
|
}),
|
||||||
|
isRemoteUser: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -84,7 +84,12 @@
|
|||||||
|
|
||||||
this.isFetching = true
|
this.isFetching = true
|
||||||
|
|
||||||
await this.axios.get('/api/v1/oauth/personal-access-tokens').then(response => {
|
await this.axios.get('/oauth/personal-access-tokens').then(response => {
|
||||||
|
if (response.status === 202) {
|
||||||
|
this.isRemoteUser = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const tokens = []
|
const tokens = []
|
||||||
|
|
||||||
response.data.forEach((data) => {
|
response.data.forEach((data) => {
|
||||||
@ -124,7 +129,17 @@
|
|||||||
this.$notify({ type: 'is-success', text: this.$t('settings.token_revoked') })
|
this.$notify({ type: 'is-success', text: this.$t('settings.token_revoked') })
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open the PAT creation view
|
||||||
|
*/
|
||||||
|
createToken() {
|
||||||
|
if (this.isRemoteUser) {
|
||||||
|
this.$notify({ type: 'is-warning', text: this.$t('errors.unsupported_with_reverseproxy') })
|
||||||
}
|
}
|
||||||
|
else this.$router.push({ name: 'settings.oauth.generatePAT' })
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
@ -3,6 +3,7 @@
|
|||||||
<setting-tabs :activeTab="'settings.webauthn'"></setting-tabs>
|
<setting-tabs :activeTab="'settings.webauthn'"></setting-tabs>
|
||||||
<div class="options-tabs">
|
<div class="options-tabs">
|
||||||
<form-wrapper>
|
<form-wrapper>
|
||||||
|
<div v-if="isRemoteUser" class="notification is-warning has-text-centered" v-html="$t('auth.auth_handled_by_proxy')" />
|
||||||
<h4 class="title is-4 has-text-grey-light">{{ $t('auth.webauthn.security_devices') }}</h4>
|
<h4 class="title is-4 has-text-grey-light">{{ $t('auth.webauthn.security_devices') }}</h4>
|
||||||
<div class="is-size-7-mobile">
|
<div class="is-size-7-mobile">
|
||||||
{{ $t('auth.webauthn.security_devices_legend')}}
|
{{ $t('auth.webauthn.security_devices_legend')}}
|
||||||
@ -37,9 +38,9 @@
|
|||||||
<h4 class="title is-4 pt-6 has-text-grey-light">{{ $t('settings.options') }}</h4>
|
<h4 class="title is-4 pt-6 has-text-grey-light">{{ $t('settings.options') }}</h4>
|
||||||
<form>
|
<form>
|
||||||
<!-- use webauthn only -->
|
<!-- use webauthn only -->
|
||||||
<form-checkbox v-on:useWebauthnOnly="saveSetting('useWebauthnOnly', $event)" :form="form" fieldName="useWebauthnOnly" :label="$t('auth.webauthn.use_webauthn_only.label')" :help="$t('auth.webauthn.use_webauthn_only.help')" />
|
<form-checkbox v-on:useWebauthnOnly="saveSetting('useWebauthnOnly', $event)" :form="form" fieldName="useWebauthnOnly" :label="$t('auth.webauthn.use_webauthn_only.label')" :help="$t('auth.webauthn.use_webauthn_only.help')" :disabled="isRemoteUser" />
|
||||||
<!-- default sign in method -->
|
<!-- default sign in method -->
|
||||||
<form-checkbox v-on:useWebauthnAsDefault="saveSetting('useWebauthnAsDefault', $event)" :form="form" fieldName="useWebauthnAsDefault" :label="$t('auth.webauthn.use_webauthn_as_default.label')" :help="$t('auth.webauthn.use_webauthn_as_default.help')" />
|
<form-checkbox v-on:useWebauthnAsDefault="saveSetting('useWebauthnAsDefault', $event)" :form="form" fieldName="useWebauthnAsDefault" :label="$t('auth.webauthn.use_webauthn_as_default.label')" :help="$t('auth.webauthn.use_webauthn_as_default.help')" :disabled="isRemoteUser" />
|
||||||
</form>
|
</form>
|
||||||
<!-- footer -->
|
<!-- footer -->
|
||||||
<vue-footer :showButtons="true">
|
<vue-footer :showButtons="true">
|
||||||
@ -66,6 +67,7 @@
|
|||||||
}),
|
}),
|
||||||
credentials: [],
|
credentials: [],
|
||||||
isFetching: false,
|
isFetching: false,
|
||||||
|
isRemoteUser: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -102,7 +104,10 @@
|
|||||||
this.isFetching = true
|
this.isFetching = true
|
||||||
|
|
||||||
await this.axios.get('/webauthn/credentials').then(response => {
|
await this.axios.get('/webauthn/credentials').then(response => {
|
||||||
this.credentials = response.data
|
if (response.status === 202) {
|
||||||
|
this.isRemoteUser = true
|
||||||
|
}
|
||||||
|
else this.credentials = response.data
|
||||||
})
|
})
|
||||||
|
|
||||||
this.isFetching = false
|
this.isFetching = false
|
||||||
@ -113,6 +118,12 @@
|
|||||||
* Register a new security device
|
* Register a new security device
|
||||||
*/
|
*/
|
||||||
async register() {
|
async register() {
|
||||||
|
|
||||||
|
if (this.isRemoteUser) {
|
||||||
|
this.$notify({ type: 'is-warning', text: this.$t('errors.unsupported_with_reverseproxy') })
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Check https context
|
// Check https context
|
||||||
if (!window.isSecureContext) {
|
if (!window.isSecureContext) {
|
||||||
this.$notify({ type: 'is-danger', text: this.$t('errors.https_required') })
|
this.$notify({ type: 'is-danger', text: this.$t('errors.https_required') })
|
||||||
|
@ -28,6 +28,8 @@
|
|||||||
'already_authenticated' => 'Already authenticated',
|
'already_authenticated' => 'Already authenticated',
|
||||||
'authentication' => 'Authentication',
|
'authentication' => 'Authentication',
|
||||||
'maybe_later' => 'Maybe later',
|
'maybe_later' => 'Maybe later',
|
||||||
|
'user_account_controlled_by_proxy' => 'User account made available by an authentication proxy.<br />Manage the account at proxy level.',
|
||||||
|
'auth_handled_by_proxy' => 'Authentication handled by a reverse proxy, below settings are disabled.<br />Manage authentication at proxy level.',
|
||||||
'confirm' => [
|
'confirm' => [
|
||||||
'logout' => 'Are you sure you want to log out?',
|
'logout' => 'Are you sure you want to log out?',
|
||||||
'revoke_device' => 'Are you sure you want to revoke this device?',
|
'revoke_device' => 'Are you sure you want to revoke this device?',
|
||||||
@ -36,7 +38,7 @@
|
|||||||
'security_device' => 'a security device',
|
'security_device' => 'a security device',
|
||||||
'security_devices' => 'Security devices',
|
'security_devices' => 'Security devices',
|
||||||
'security_devices_legend' => 'Authentication devices you can use to sign in 2FAuth, like security keys (i.e Yubikey) or smartphones with biometric capabilities (i.e. Apple FaceId/TouchId)',
|
'security_devices_legend' => 'Authentication devices you can use to sign in 2FAuth, like security keys (i.e Yubikey) or smartphones with biometric capabilities (i.e. Apple FaceId/TouchId)',
|
||||||
'enforce_security_using_webauthn' => 'You can enforce the security of your 2FAuth account by enabling WebAuthn authentication.<br /><br />
|
'enhance_security_using_webauthn' => 'You can enhance the security of your 2FAuth account by enabling WebAuthn authentication.<br /><br />
|
||||||
WebAuthn allows you to use trusted devices (like Yubikeys or smartphones with biometric capabilities) to sign in quickly and more securely.',
|
WebAuthn allows you to use trusted devices (like Yubikeys or smartphones with biometric capabilities) to sign in quickly and more securely.',
|
||||||
'use_security_device_to_sign_in' => 'Get ready to authenticate yourself using (one of) your security devices. Plug your key in, remove face mask or gloves, etc.',
|
'use_security_device_to_sign_in' => 'Get ready to authenticate yourself using (one of) your security devices. Plug your key in, remove face mask or gloves, etc.',
|
||||||
'lost_your_device' => 'Lost your device?',
|
'lost_your_device' => 'Lost your device?',
|
||||||
@ -57,7 +59,7 @@
|
|||||||
'unknown_device' => 'Unknown device',
|
'unknown_device' => 'Unknown device',
|
||||||
'use_webauthn_only' => [
|
'use_webauthn_only' => [
|
||||||
'label' => 'Use WebAuthn only (recommended)',
|
'label' => 'Use WebAuthn only (recommended)',
|
||||||
'help' => 'Make WebAuthn the only available method to sign in 2FAuth. This is the recommended setup to take advantage of the WebAuthn enforced security.<br />
|
'help' => 'Make WebAuthn the only available method to sign in 2FAuth. This is the recommended setup to take advantage of the WebAuthn enhanced security.<br />
|
||||||
In case of device lost you will always be able to register a new security device to recover your account.'
|
In case of device lost you will always be able to register a new security device to recover your account.'
|
||||||
],
|
],
|
||||||
'use_webauthn_as_default' => [
|
'use_webauthn_as_default' => [
|
||||||
|
@ -35,4 +35,5 @@
|
|||||||
'browser_does_not_support_webauthn' => 'Your device does not support webauthn. Try again later using a more modern browser',
|
'browser_does_not_support_webauthn' => 'Your device does not support webauthn. Try again later using a more modern browser',
|
||||||
'aborted_by_user' => 'Aborted by user',
|
'aborted_by_user' => 'Aborted by user',
|
||||||
'security_device_unsupported' => 'Security device unsupported',
|
'security_device_unsupported' => 'Security device unsupported',
|
||||||
|
'unsupported_with_reverseproxy' => 'Not applicable when using an auth proxy',
|
||||||
];
|
];
|
@ -1,27 +1,24 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| API Routes
|
| API Routes
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
| Here is where you can register API routes for your application. These
|
|
||||||
| routes are loaded by the RouteServiceProvider within a group which
|
|
||||||
| is assigned the "api" middleware group. Enjoy building your API!
|
|
||||||
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unprotected routes
|
||||||
|
*/
|
||||||
Route::get('user/name', 'UserController@show')->name('user.show.name');
|
Route::get('user/name', 'UserController@show')->name('user.show.name');
|
||||||
|
|
||||||
Route::group(['middleware' => 'auth:reverse-proxy,api'], function() {
|
|
||||||
|
|
||||||
Route::get('oauth/personal-access-tokens', '\Laravel\Passport\Http\Controllers\PersonalAccessTokenController@forUser')->name('passport.personal.tokens.index');
|
/**
|
||||||
Route::post('oauth/personal-access-tokens', '\Laravel\Passport\Http\Controllers\PersonalAccessTokenController@store')->name('passport.personal.tokens.store');
|
* Routes protected by the api authentication guard
|
||||||
Route::delete('oauth/personal-access-tokens/{token_id}', '\Laravel\Passport\Http\Controllers\PersonalAccessTokenController@destroy')->name('passport.personal.tokens.destroy');
|
*/
|
||||||
|
Route::group(['middleware' => 'auth:api-guard'], function () {
|
||||||
Route::get('user', 'UserController@show')->name('user.show');
|
Route::get('user', 'UserController@show')->name('user.show'); // Returns email address in addition to the username
|
||||||
|
|
||||||
Route::get('settings/{settingName}', 'SettingController@show')->name('settings.show');
|
Route::get('settings/{settingName}', 'SettingController@show')->name('settings.show');
|
||||||
Route::get('settings', 'SettingController@index')->name('settings.index');
|
Route::get('settings', 'SettingController@index')->name('settings.index');
|
||||||
@ -47,5 +44,4 @@
|
|||||||
|
|
||||||
Route::post('icons', 'IconController@upload')->name('icons.upload');
|
Route::post('icons', 'IconController@upload')->name('icons.upload');
|
||||||
Route::delete('icons/{icon}', 'IconController@delete')->name('icons.delete');
|
Route::delete('icons/{icon}', 'IconController@delete')->name('icons.delete');
|
||||||
|
|
||||||
});
|
});
|
@ -11,24 +11,42 @@
|
|||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Web Routes
|
| Web Routes
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
| Here is where you can register web routes for your application. These
|
|
||||||
| routes are loaded by the RouteServiceProvider within a group which
|
|
||||||
| contains the "web" middleware group. Now create something great!
|
|
||||||
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Route::post('user', 'Auth\RegisterController@register')->name('user.register');
|
/**
|
||||||
Route::post('user/password/lost', 'Auth\ForgotPasswordController@sendResetLinkEmail')->middleware('AvoidResetPassword')->name('user.password.lost');;
|
* Routes that only work for unauthenticated user (return an error otherwise)
|
||||||
Route::post('user/password/reset', 'Auth\ResetPasswordController@reset')->name('user.password.reset');
|
*/
|
||||||
Route::post('webauthn/lost', [WebAuthnDeviceLostController::class, 'sendRecoveryEmail'])->name('webauthn.lost');
|
Route::group(['middleware' => 'guest'], function () {
|
||||||
Route::post('webauthn/recover/options', [WebAuthnRecoveryController::class, 'options'])->name('webauthn.recover.options');
|
Route::post('user', 'Auth\RegisterController@register')->name('user.register');
|
||||||
Route::post('webauthn/recover', [WebAuthnRecoveryController::class, 'recover'])->name('webauthn.recover');
|
Route::post('user/password/lost', 'Auth\ForgotPasswordController@sendResetLinkEmail')->middleware('AvoidResetPassword')->name('user.password.lost');;
|
||||||
|
Route::post('user/password/reset', 'Auth\ResetPasswordController@reset')->name('user.password.reset');
|
||||||
|
Route::post('webauthn/login/options', [WebAuthnLoginController::class, 'options'])->name('webauthn.login.options');
|
||||||
|
Route::post('webauthn/lost', [WebAuthnDeviceLostController::class, 'sendRecoveryEmail'])->name('webauthn.lost');
|
||||||
|
Route::post('webauthn/recover/options', [WebAuthnRecoveryController::class, 'options'])->name('webauthn.recover.options');
|
||||||
|
Route::post('webauthn/recover', [WebAuthnRecoveryController::class, 'recover'])->name('webauthn.recover');
|
||||||
|
});
|
||||||
|
|
||||||
Route::group(['middleware' => 'auth:reverse-proxy,web'], function () {
|
/**
|
||||||
|
* Routes that only work for unauthenticated user (return an error otherwise)
|
||||||
|
* that can be requested max 10 times per minute by the same IP
|
||||||
|
*/
|
||||||
|
Route::group(['middleware' => ['guest', 'throttle:10,1']], function () {
|
||||||
|
Route::post('user/login', 'Auth\LoginController@login')->name('user.login');
|
||||||
|
Route::post('webauthn/login', [WebAuthnLoginController::class, 'login'])->name('webauthn.login');
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Routes protected by an authentication guard
|
||||||
|
*/
|
||||||
|
Route::group(['middleware' => 'behind-auth'], function () {
|
||||||
Route::put('user', 'Auth\UserController@update')->name('user.update');
|
Route::put('user', 'Auth\UserController@update')->name('user.update');
|
||||||
Route::patch('user/password', 'Auth\PasswordController@update')->name('user.password.update');
|
Route::patch('user/password', 'Auth\PasswordController@update')->name('user.password.update');
|
||||||
Route::get('user/logout', 'Auth\LoginController@logout')->name('user.logout');
|
Route::get('user/logout', 'Auth\LoginController@logout')->name('user.logout');
|
||||||
|
|
||||||
|
Route::get('oauth/personal-access-tokens', 'Auth\PersonalAccessTokenController@forUser')->name('passport.personal.tokens.index');
|
||||||
|
Route::post('oauth/personal-access-tokens', 'Auth\PersonalAccessTokenController@store')->name('passport.personal.tokens.store');
|
||||||
|
Route::delete('oauth/personal-access-tokens/{token_id}', 'Auth\PersonalAccessTokenController@destroy')->name('passport.personal.tokens.destroy');
|
||||||
|
|
||||||
Route::post('webauthn/register/options', [WebAuthnRegisterController::class, 'options'])->name('webauthn.register.options');
|
Route::post('webauthn/register/options', [WebAuthnRegisterController::class, 'options'])->name('webauthn.register.options');
|
||||||
Route::post('webauthn/register', [WebAuthnRegisterController::class, 'register'])->name('webauthn.register');
|
Route::post('webauthn/register', [WebAuthnRegisterController::class, 'register'])->name('webauthn.register');
|
||||||
Route::get('webauthn/credentials', [WebAuthnManageController::class, 'index'])->name('webauthn.credentials.index');
|
Route::get('webauthn/credentials', [WebAuthnManageController::class, 'index'])->name('webauthn.credentials.index');
|
||||||
@ -36,10 +54,7 @@
|
|||||||
Route::delete('webauthn/credentials/{credential}', [WebAuthnManageController::class, 'delete'])->name('webauthn.credentials.delete');
|
Route::delete('webauthn/credentials/{credential}', [WebAuthnManageController::class, 'delete'])->name('webauthn.credentials.delete');
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::group(['middleware' => ['guest:web', 'throttle:10,1']], function () {
|
/**
|
||||||
Route::post('user/login', 'Auth\LoginController@login')->name('user.login');
|
* Route for the main landing view
|
||||||
Route::post('webauthn/login/options', [WebAuthnLoginController::class, 'options'])->name('webauthn.login.options');
|
*/
|
||||||
Route::post('webauthn/login', [WebAuthnLoginController::class, 'login'])->name('webauthn.login');
|
|
||||||
});
|
|
||||||
|
|
||||||
Route::get('/{any}', 'SinglePageController@index')->where('any', '.*')->name('landing');
|
Route::get('/{any}', 'SinglePageController@index')->where('any', '.*')->name('landing');
|
Loading…
Reference in New Issue
Block a user