diff --git a/app/Http/Controllers/Admin/ImpersonationController.php b/app/Http/Controllers/Admin/ImpersonationController.php
index f076b9c..8f72753 100644
--- a/app/Http/Controllers/Admin/ImpersonationController.php
+++ b/app/Http/Controllers/Admin/ImpersonationController.php
@@ -12,7 +12,7 @@ class ImpersonationController extends Controller
{
public function __construct()
{
- $this->middleware('admin');
+ $this->middleware('moderator');
}
public function impersonate($identifier) {
@@ -29,12 +29,33 @@ class ImpersonationController extends Controller
}
}
- if (!$user) return $this->error([
- 'message'=> 'User not found.'
+ if (!$user) {
+ return $this->error([
+ 'message'=> 'User not found.'
+ ]);
+ } else if ($user->admin) {
+ return $this->error([
+ 'message' => 'You cannot impersonate an admin.',
+ ]);
+ }
+
+
+ \Log::warning('Impersonation started',[
+ 'from_id' => auth()->id(),
+ 'from_email' => auth()->user()->email,
+ 'target_id' => $user->id,
+ 'target_email' => $user->id,
]);
// Be this user
- $token = auth()->login($user);
+ if (auth()->user()->moderator) {
+ $token = auth()->claims([
+ 'impersonating' => true,
+ 'impersonator_id' => auth()->id(),
+ ])->login($user);
+ } else {
+ $token = auth()->login($user);
+ }
return $this->success([
'token' => $token
]);
diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php
index c2311a2..1f2eb9d 100644
--- a/app/Http/Kernel.php
+++ b/app/Http/Kernel.php
@@ -5,7 +5,9 @@ namespace App\Http;
use App\Http\Middleware\AcceptsJsonMiddleware;
use App\Http\Middleware\AuthenticateJWT;
use App\Http\Middleware\CustomDomainRestriction;
+use App\Http\Middleware\ImpersonationMiddleware;
use App\Http\Middleware\IsAdmin;
+use App\Http\Middleware\IsModerator;
use App\Http\Middleware\IsNotSubscribed;
use App\Http\Middleware\IsSubscribed;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
@@ -58,6 +60,7 @@ class Kernel extends HttpKernel
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Session\Middleware\StartSession::class,
+ ImpersonationMiddleware::class,
],
];
@@ -72,6 +75,7 @@ class Kernel extends HttpKernel
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'admin' => IsAdmin::class,
+ 'moderator' => IsModerator::class,
'subscribed' => IsSubscribed::class,
'not-subscribed' => IsNotSubscribed::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
diff --git a/app/Http/Middleware/ImpersonationMiddleware.php b/app/Http/Middleware/ImpersonationMiddleware.php
new file mode 100644
index 0000000..9399f24
--- /dev/null
+++ b/app/Http/Middleware/ImpersonationMiddleware.php
@@ -0,0 +1,94 @@
+check() ||
+ !auth()->payload()->get('impersonating')) {
+ return $next($request);
+ }
+ // Check that route is allowed
+ $routeName = $request->route()->getName();
+ if (!in_array($routeName, self::ALLOWED_ROUTES)) {
+ return response([
+ 'message' => 'Unauthorized when impersonating',
+ 'route' => $routeName,
+ 'impersonator' => auth()->payload()->get('impersonator_id'),
+ 'impersonated_account' => auth()->id(),
+ 'url' => $request->fullUrl(),
+ 'payload' => $request->all()
+ ], 403);
+ } else if (in_array($routeName, self::LOG_ROUTES)) {
+ \Log::warning(self::ADMIN_LOG_PREFIX . 'Impersonator action', [
+ 'route' => $routeName,
+ 'url' => $request->fullUrl(),
+ 'impersonated_account' => auth()->id(),
+ 'impersonator' => auth()->payload()->get('impersonator_id'),
+ 'payload' => $request->all()
+ ]);
+ }
+
+ return $next($request);
+ }
+}
diff --git a/app/Http/Middleware/IsModerator.php b/app/Http/Middleware/IsModerator.php
new file mode 100644
index 0000000..8c0150b
--- /dev/null
+++ b/app/Http/Middleware/IsModerator.php
@@ -0,0 +1,32 @@
+user() && !$request->user()->moderator) {
+ // This user is not a paying customer...
+ if ($request->expectsJson()) {
+ return response([
+ 'message' => 'You are not allowed.',
+ 'type' => 'error',
+ ], 403);
+ }
+ return redirect('home');
+ }
+
+ return $next($request);
+ }
+}
diff --git a/app/Http/Resources/UserResource.php b/app/Http/Resources/UserResource.php
index 5e8de1d..411da8a 100644
--- a/app/Http/Resources/UserResource.php
+++ b/app/Http/Resources/UserResource.php
@@ -18,6 +18,7 @@ class UserResource extends JsonResource
'is_subscribed' => $this->is_subscribed,
'has_enterprise_subscription' => $this->has_enterprise_subscription,
'admin' => $this->admin,
+ 'moderator' => $this->moderator,
'template_editor' => $this->template_editor,
'has_customer_id' => $this->has_customer_id,
'has_forms' => $this->has_forms,
diff --git a/app/Models/User.php b/app/Models/User.php
index 0d826f5..317d37a 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -102,6 +102,11 @@ class User extends Authenticatable implements JWTSubject
return in_array($this->email, config('opnform.admin_emails'));
}
+ public function getModeratorAttribute()
+ {
+ return in_array($this->email, config('opnform.moderator_emails')) || $this->admin;
+ }
+
public function getTemplateEditorAttribute()
{
return $this->admin || in_array($this->email, config('opnform.template_editor_emails'));
diff --git a/client/components/global/Navbar.vue b/client/components/global/Navbar.vue
index 3fc0d0e..8338e18 100644
--- a/client/components/global/Navbar.vue
+++ b/client/components/global/Navbar.vue
@@ -100,6 +100,15 @@
Settings
+
+
+ Admin
+
+
{
const authStore = useAuthStore()
if (authStore.check && !authStore.user?.admin) {
- console.log('redirecting to home')
return navigateTo({ name: 'home' })
}
})
diff --git a/client/middleware/moderator.js b/client/middleware/moderator.js
new file mode 100644
index 0000000..d6822c5
--- /dev/null
+++ b/client/middleware/moderator.js
@@ -0,0 +1,6 @@
+export default defineNuxtRouteMiddleware((to, from) => {
+ const authStore = useAuthStore()
+ if (authStore.check && !authStore.user?.moderator) {
+ return navigateTo({ name: 'home' })
+ }
+})
diff --git a/client/pages/settings/admin.vue b/client/pages/settings/admin.vue
index a6f0c33..fa6d1a2 100644
--- a/client/pages/settings/admin.vue
+++ b/client/pages/settings/admin.vue
@@ -40,7 +40,7 @@ import {opnFetch} from "~/composables/useOpnApi.js";
import {fetchAllWorkspaces} from "~/stores/workspaces.js";
definePageMeta({
- middleware: "admin"
+ middleware: "moderator"
})
useOpnSeoMeta({
diff --git a/config/opnform.php b/config/opnform.php
index 92ce264..2bf9ff0 100644
--- a/config/opnform.php
+++ b/config/opnform.php
@@ -2,6 +2,7 @@
return [
'admin_emails' => explode(",", env('ADMIN_EMAILS') ?? ''),
+ 'moderator_emails' => explode(",", env('MODERATOR_EMAILS') ?? ''),
'template_editor_emails' => explode(",", env('TEMPLATE_EDITOR_EMAILS') ?? ''),
'extra_pro_users_emails' => explode(",", env('EXTRA_PRO_USERS_EMAILS') ?? ''),
];
diff --git a/routes/api.php b/routes/api.php
index 589a09d..b381f5d 100644
--- a/routes/api.php
+++ b/routes/api.php
@@ -37,7 +37,7 @@ use Illuminate\Support\Facades\Route;
Route::group(['middleware' => 'auth:api'], function () {
Route::post('logout', [LoginController::class, 'logout']);
- Route::get('user', [UserController::class, 'current']);
+ Route::get('user', [UserController::class, 'current'])->name('user.current');
Route::delete('user', [UserController::class, 'deleteAccount']);
Route::patch('settings/profile', [ProfileController::class, 'update']);