diff --git a/app/Api/v1/Resources/UserResource.php b/app/Api/v1/Resources/UserResource.php index 9f906263..297ef48a 100644 --- a/app/Api/v1/Resources/UserResource.php +++ b/app/Api/v1/Resources/UserResource.php @@ -17,7 +17,7 @@ public function toArray($request) { return [ 'name' => $this->name, - 'email' => $this->when(Auth::guard('api')->user(), $this->email), + 'email' => $this->when(Auth::guard()->check(), $this->email), ]; } } \ No newline at end of file diff --git a/app/Extensions/RemoteUserProvider.php b/app/Extensions/RemoteUserProvider.php new file mode 100644 index 00000000..94757c39 --- /dev/null +++ b/app/Extensions/RemoteUserProvider.php @@ -0,0 +1,67 @@ +first(); + + // if (null === $user) { + // $user = User::create( + // [ + // 'name' => $identifier, + // 'email' => $identifier, + // 'password' => bcrypt(Str::random(64)), + // ] + // ); + // } + + return $user; + } + + /** + * @inheritDoc + */ + public function retrieveByToken($identifier, $token) + { + throw new Exception(sprintf('No implementation for %s', __METHOD__)); + } + + /** + * @inheritDoc + */ + public function updateRememberToken(Authenticatable $user, $token) + { + throw new Exception(sprintf('No implementation for %s', __METHOD__)); + } + + /** + * @inheritDoc + */ + public function retrieveByCredentials(array $credentials) + { + throw new Exception(sprintf('No implementation for %s', __METHOD__)); + } + + /** + * @inheritDoc + */ + public function validateCredentials(Authenticatable $user, array $credentials) + { + throw new Exception(sprintf('No implementation for %s', __METHOD__)); + } +} \ No newline at end of file diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 5151c346..1f938f64 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -39,6 +39,7 @@ class Kernel extends HttpKernel \Illuminate\View\Middleware\ShareErrorsFromSession::class, \App\Http\Middleware\VerifyCsrfToken::class, \Illuminate\Routing\Middleware\SubstituteBindings::class, + \App\Http\Middleware\LogUserLastSeen::class, \App\Http\Middleware\CustomCreateFreshApiToken::class, ], diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php index 4ff95e92..9bd67e54 100644 --- a/app/Http/Middleware/Authenticate.php +++ b/app/Http/Middleware/Authenticate.php @@ -6,17 +6,5 @@ class Authenticate extends Middleware { - /** - * Get the path the user should be redirected to when they are not authenticated. - * - * @param \Illuminate\Http\Request $request - * @return string - * @codeCoverageIgnore - */ - protected function redirectTo($request) - { - if (! $request->expectsJson()) { - return route('login'); - } - } + } diff --git a/app/Http/Middleware/LogUserLastSeen.php b/app/Http/Middleware/LogUserLastSeen.php index 6b0a82c4..9abe0a13 100644 --- a/app/Http/Middleware/LogUserLastSeen.php +++ b/app/Http/Middleware/LogUserLastSeen.php @@ -13,14 +13,20 @@ class LogUserLastSeen * * @param \Illuminate\Http\Request $request * @param \Closure $next + * @param string|null $guard * @return mixed */ - public function handle($request, Closure $next) + public function handle($request, Closure $next, ...$quards) { + $guards = empty($guards) ? [null] : $guards; - if( Auth::guard('api')->check() && !$request->bearerToken()) { - Auth::guard('api')->user()->last_seen_at = Carbon::now()->format('Y-m-d H:i:s'); - Auth::guard('api')->user()->save(); + foreach ($guards as $guard) { + // Activity coming from a client authenticated with a personal access token is not logged + if( Auth::guard($guard)->check() && !$request->bearerToken()) { + Auth::guard($guard)->user()->last_seen_at = Carbon::now()->format('Y-m-d H:i:s'); + Auth::guard($guard)->user()->save(); + break; + } } return $next($request); diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index 5999a07e..e7cf2566 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -5,7 +5,9 @@ use Illuminate\Support\Facades\Gate; use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; use Illuminate\Support\Facades\Auth; +use App\Services\Auth\ReverseProxyGuard; use App\Extensions\EloquentTwoFAuthProvider; +use App\Extensions\RemoteUserProvider; use DarkGhostHunter\Larapass\WebAuthn\WebAuthnAssertValidator; use Illuminate\Contracts\Hashing\Hasher; @@ -46,6 +48,19 @@ static function ($app, $config) { ); }); + // + Auth::provider('remote-user', function ($app, array $config) { + // Return an instance of Illuminate\Contracts\Auth\UserProvider... + + return new RemoteUserProvider; + }); + + Auth::extend('reverse-proxy', function ($app, string $name, array $config) { + // Return an instance of Illuminate\Contracts\Auth\Guard... + + return new ReverseProxyGuard(Auth::createUserProvider($config['provider'])); + }); + // 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 diff --git a/app/Services/Auth/ReverseProxyGuard.php b/app/Services/Auth/ReverseProxyGuard.php new file mode 100644 index 00000000..341c5070 --- /dev/null +++ b/app/Services/Auth/ReverseProxyGuard.php @@ -0,0 +1,109 @@ +provider = $provider; + } + + /** + * @inheritDoc + */ + public function check(): bool + { + return !is_null($this->user()); + } + + /** + * @inheritDoc + */ + public function guest(): bool + { + return !$this->check(); + } + + /** + * @inheritDoc + */ + public function user() + { + // If we've already retrieved the user for the current request we can just + // return it back immediately. We do not want to fetch the user data on + // every call to this method because that would be tremendously slow. + if (!is_null($this->user)) { + return $this->user; + } + + $user = null; + + // Get the user identifier from $_SERVER or apache filtered headers + $header = config('auth.guard_header', 'REMOTE_USER'); + $userID = request()->server($header) ?? apache_request_headers()[$header] ?? null; + + if (null === $userID) { + Log::error(sprintf('No user in header "%s".', $header)); + return $this->user = null; + // throw new Exception('The guard header was unexpectedly empty. See the logs.'); + } + + $user = $this->provider->retrieveById($userID); + + return $this->user = $user; + } + + /** + * @inheritDoc + */ + public function id() + { + return $this->user; + } + + /** + * Validate a user's credentials. + * + * @param array $credentials + * @return Exception + */ + public function validate(array $credentials = []) + { + throw new Exception('No implementation for RemoteUserGuard::validate()'); + } + + /** + * @inheritDoc + */ + public function setUser(Authenticatable $user) + { + $this->user = $user; + } +} diff --git a/config/auth.php b/config/auth.php index 0af93c18..42d29d60 100644 --- a/config/auth.php +++ b/config/auth.php @@ -14,9 +14,11 @@ */ 'defaults' => [ - 'guard' => 'web', + 'guard' => env('AUTHENTICATION_GUARD', 'web'), 'passwords' => 'users', ], + 'guard_header' => env('AUTHENTICATION_GUARD_HEADER', 'REMOTE_USER'), + // 'guard_email' => env('AUTHENTICATION_GUARD_EMAIL_HEADER', null), /* |-------------------------------------------------------------------------- @@ -46,6 +48,11 @@ 'provider' => 'users', 'hash' => false, ], + + 'reverse-proxy' => [ + 'driver' => 'reverse-proxy', + 'provider' => 'remote-user', + ], ], /* @@ -70,6 +77,10 @@ 'driver' => 'eloquent-2fauth', 'model' => App\Models\User::class, ], + 'remote-user' => [ + 'driver' => 'remote-user', + 'model' => App\Models\User::class, + ], ], /* diff --git a/resources/js/routes.js b/resources/js/routes.js index b97130e0..be70b917 100644 --- a/resources/js/routes.js +++ b/resources/js/routes.js @@ -17,7 +17,7 @@ import Register from './views/auth/Register' import PasswordRequest from './views/auth/password/Request' import PasswordReset from './views/auth/password/Reset' import WebauthnLost from './views/auth/webauthn/Lost' -import WebauthnRecover from './views/auth/webauthn/Recover' +import WebauthnRecover from './views/auth/webauthn/Recover' import SettingsOptions from './views/settings/Options' import SettingsAccount from './views/settings/Account' import SettingsOAuth from './views/settings/OAuth' diff --git a/resources/js/views/auth/Login.vue b/resources/js/views/auth/Login.vue index c9197afb..9f360331 100644 --- a/resources/js/views/auth/Login.vue +++ b/resources/js/views/auth/Login.vue @@ -129,6 +129,9 @@ const { data } = await vm.axios.get('api/v1/user/name') if( data.name ) { + if( data.email ) { + return next({ name: 'accounts' }); + } vm.username = data.name } else { diff --git a/routes/api/v1.php b/routes/api/v1.php index 4ff138bc..8a7eff14 100644 --- a/routes/api/v1.php +++ b/routes/api/v1.php @@ -13,11 +13,9 @@ | */ -Route::group(['middleware' => 'guest:api'], function () { - Route::get('user/name', 'UserController@show')->name('user.show.name'); -}); +Route::get('user/name', 'UserController@show')->name('user.show.name'); -Route::group(['middleware' => 'auth:api'], function() { +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'); diff --git a/routes/web.php b/routes/web.php index 32763ca8..a428faf3 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,6 +1,6 @@ name('user.register'); +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/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::get('twofaccount/{TwoFAccount}', 'TwoFAccountController@show'); - -Route::group(['middleware' => 'guest:web'], function () { - Route::post('user', 'Auth\RegisterController@register')->name('user.register'); - 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/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:web'], function () { +Route::group(['middleware' => 'auth:reverse-proxy,web'], function () { Route::put('user', 'Auth\UserController@update')->name('user.update'); Route::patch('user/password', 'Auth\PasswordController@update')->name('user.password.update'); Route::get('user/logout', 'Auth\LoginController@logout')->name('user.logout');