Added moderator impersonation
This commit is contained in:
parent
a651c60808
commit
42c65ae06f
|
@ -12,7 +12,7 @@ class ImpersonationController extends Controller
|
||||||
{
|
{
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->middleware('admin');
|
$this->middleware('moderator');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function impersonate($identifier) {
|
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.'
|
'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
|
// Be this user
|
||||||
|
if (auth()->user()->moderator) {
|
||||||
|
$token = auth()->claims([
|
||||||
|
'impersonating' => true,
|
||||||
|
'impersonator_id' => auth()->id(),
|
||||||
|
])->login($user);
|
||||||
|
} else {
|
||||||
$token = auth()->login($user);
|
$token = auth()->login($user);
|
||||||
|
}
|
||||||
return $this->success([
|
return $this->success([
|
||||||
'token' => $token
|
'token' => $token
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -5,7 +5,9 @@ namespace App\Http;
|
||||||
use App\Http\Middleware\AcceptsJsonMiddleware;
|
use App\Http\Middleware\AcceptsJsonMiddleware;
|
||||||
use App\Http\Middleware\AuthenticateJWT;
|
use App\Http\Middleware\AuthenticateJWT;
|
||||||
use App\Http\Middleware\CustomDomainRestriction;
|
use App\Http\Middleware\CustomDomainRestriction;
|
||||||
|
use App\Http\Middleware\ImpersonationMiddleware;
|
||||||
use App\Http\Middleware\IsAdmin;
|
use App\Http\Middleware\IsAdmin;
|
||||||
|
use App\Http\Middleware\IsModerator;
|
||||||
use App\Http\Middleware\IsNotSubscribed;
|
use App\Http\Middleware\IsNotSubscribed;
|
||||||
use App\Http\Middleware\IsSubscribed;
|
use App\Http\Middleware\IsSubscribed;
|
||||||
use Illuminate\Foundation\Http\Kernel as HttpKernel;
|
use Illuminate\Foundation\Http\Kernel as HttpKernel;
|
||||||
|
@ -58,6 +60,7 @@ class Kernel extends HttpKernel
|
||||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||||
\App\Http\Middleware\EncryptCookies::class,
|
\App\Http\Middleware\EncryptCookies::class,
|
||||||
\Illuminate\Session\Middleware\StartSession::class,
|
\Illuminate\Session\Middleware\StartSession::class,
|
||||||
|
ImpersonationMiddleware::class,
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -72,6 +75,7 @@ class Kernel extends HttpKernel
|
||||||
'auth' => \App\Http\Middleware\Authenticate::class,
|
'auth' => \App\Http\Middleware\Authenticate::class,
|
||||||
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
|
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
|
||||||
'admin' => IsAdmin::class,
|
'admin' => IsAdmin::class,
|
||||||
|
'moderator' => IsModerator::class,
|
||||||
'subscribed' => IsSubscribed::class,
|
'subscribed' => IsSubscribed::class,
|
||||||
'not-subscribed' => IsNotSubscribed::class,
|
'not-subscribed' => IsNotSubscribed::class,
|
||||||
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::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,
|
'is_subscribed' => $this->is_subscribed,
|
||||||
'has_enterprise_subscription' => $this->has_enterprise_subscription,
|
'has_enterprise_subscription' => $this->has_enterprise_subscription,
|
||||||
'admin' => $this->admin,
|
'admin' => $this->admin,
|
||||||
|
'moderator' => $this->moderator,
|
||||||
'template_editor' => $this->template_editor,
|
'template_editor' => $this->template_editor,
|
||||||
'has_customer_id' => $this->has_customer_id,
|
'has_customer_id' => $this->has_customer_id,
|
||||||
'has_forms' => $this->has_forms,
|
'has_forms' => $this->has_forms,
|
||||||
|
|
|
@ -102,6 +102,11 @@ class User extends Authenticatable implements JWTSubject
|
||||||
return in_array($this->email, config('opnform.admin_emails'));
|
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()
|
public function getTemplateEditorAttribute()
|
||||||
{
|
{
|
||||||
return $this->admin || in_array($this->email, config('opnform.template_editor_emails'));
|
return $this->admin || in_array($this->email, config('opnform.template_editor_emails'));
|
||||||
|
|
|
@ -100,6 +100,15 @@
|
||||||
Settings
|
Settings
|
||||||
</NuxtLink>
|
</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="#"
|
<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"
|
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"
|
@click.prevent="logout"
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
export default defineNuxtRouteMiddleware((to, from) => {
|
export default defineNuxtRouteMiddleware((to, from) => {
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
if (authStore.check && !authStore.user?.admin) {
|
if (authStore.check && !authStore.user?.admin) {
|
||||||
console.log('redirecting to home')
|
|
||||||
return navigateTo({ name: '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";
|
import {fetchAllWorkspaces} from "~/stores/workspaces.js";
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
middleware: "admin"
|
middleware: "moderator"
|
||||||
})
|
})
|
||||||
|
|
||||||
useOpnSeoMeta({
|
useOpnSeoMeta({
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'admin_emails' => explode(",", env('ADMIN_EMAILS') ?? ''),
|
'admin_emails' => explode(",", env('ADMIN_EMAILS') ?? ''),
|
||||||
|
'moderator_emails' => explode(",", env('MODERATOR_EMAILS') ?? ''),
|
||||||
'template_editor_emails' => explode(",", env('TEMPLATE_EDITOR_EMAILS') ?? ''),
|
'template_editor_emails' => explode(",", env('TEMPLATE_EDITOR_EMAILS') ?? ''),
|
||||||
'extra_pro_users_emails' => explode(",", env('EXTRA_PRO_USERS_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::group(['middleware' => 'auth:api'], function () {
|
||||||
Route::post('logout', [LoginController::class, 'logout']);
|
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::delete('user', [UserController::class, 'deleteAccount']);
|
||||||
|
|
||||||
Route::patch('settings/profile', [ProfileController::class, 'update']);
|
Route::patch('settings/profile', [ProfileController::class, 'update']);
|
||||||
|
|
Loading…
Reference in New Issue