Added moderator impersonation
This commit is contained in:
parent
a651c60808
commit
42c65ae06f
|
@ -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([
|
||||
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
|
||||
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
|
||||
]);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ImpersonationMiddleware
|
||||
{
|
||||
public const ADMIN_LOG_PREFIX = '[admin_action] ';
|
||||
const LOG_ROUTES = [
|
||||
'open.forms.store',
|
||||
'open.forms.update',
|
||||
'open.forms.duplicate',
|
||||
'open.forms.regenerate-link',
|
||||
];
|
||||
|
||||
const ALLOWED_ROUTES = [
|
||||
'logout',
|
||||
|
||||
// Forms
|
||||
'forms.ai.generate',
|
||||
'forms.ai.show',
|
||||
'forms.assets.show',
|
||||
'forms.show',
|
||||
'forms.answer',
|
||||
'forms.fetchSubmission',
|
||||
'forms.users.index',
|
||||
'open.forms.index-all',
|
||||
'open.forms.store',
|
||||
'open.forms.assets.upload',
|
||||
'open.forms.update',
|
||||
'open.forms.duplicate',
|
||||
'open.forms.regenerate-link',
|
||||
'open.forms.submissions',
|
||||
'open.forms.submissions.file',
|
||||
|
||||
// Workspaces
|
||||
'open.workspaces.index',
|
||||
'open.workspaces.create',
|
||||
'open.workspaces.delete',
|
||||
'open.workspaces.save-custom-domains',
|
||||
'open.workspaces.databases.search',
|
||||
'open.workspaces.databases.show',
|
||||
'open.workspaces.form.stats',
|
||||
'open.workspaces.forms.index',
|
||||
'open.workspaces.users.index',
|
||||
|
||||
'templates.index',
|
||||
'templates.create',
|
||||
'templates.update',
|
||||
'templates.show',
|
||||
|
||||
'user.current',
|
||||
'local.temp',
|
||||
];
|
||||
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
|
||||
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
if (!auth()->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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class IsModerator
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
if ($request->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);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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'));
|
||||
|
|
|
@ -100,6 +100,15 @@
|
|||
Settings
|
||||
</NuxtLink>
|
||||
|
||||
<NuxtLink :to="{ name: 'settings-admin' }" v-if="user.moderator"
|
||||
class="block block px-4 py-2 text-md text-gray-700 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4 mr-2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75 11.25 15 15 9.75m-3-7.036A11.959 11.959 0 0 1 3.598 6 11.99 11.99 0 0 0 3 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285Z" />
|
||||
</svg>
|
||||
Admin
|
||||
</NuxtLink>
|
||||
|
||||
<a href="#"
|
||||
class="block block px-4 py-2 text-md text-gray-700 dark:text-white hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
|
||||
@click.prevent="logout"
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
export default defineNuxtRouteMiddleware((to, from) => {
|
||||
const authStore = useAuthStore()
|
||||
if (authStore.check && !authStore.user?.admin) {
|
||||
console.log('redirecting to home')
|
||||
return navigateTo({ name: 'home' })
|
||||
}
|
||||
})
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
export default defineNuxtRouteMiddleware((to, from) => {
|
||||
const authStore = useAuthStore()
|
||||
if (authStore.check && !authStore.user?.moderator) {
|
||||
return navigateTo({ name: 'home' })
|
||||
}
|
||||
})
|
|
@ -40,7 +40,7 @@ import {opnFetch} from "~/composables/useOpnApi.js";
|
|||
import {fetchAllWorkspaces} from "~/stores/workspaces.js";
|
||||
|
||||
definePageMeta({
|
||||
middleware: "admin"
|
||||
middleware: "moderator"
|
||||
})
|
||||
|
||||
useOpnSeoMeta({
|
||||
|
|
|
@ -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') ?? ''),
|
||||
];
|
||||
|
|
|
@ -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']);
|
||||
|
|
Loading…
Reference in New Issue