Fix code review follow-up issues
- EnsureUserNotSuspended: bypass for impersonation stop, also check banned - FlashProps: add info and new_password keys - AccountLayout: impersonation stop link uses account domain (not admin) - withCount alias: billingInvoices as invoices_count for frontend compat - VPS Show: add secure password dialog with copy button for reset-password Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -27,7 +27,7 @@ class CustomerController extends Controller
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$query = User::role('customer')
|
||||
->withCount(['services', 'billingInvoices'])
|
||||
->withCount(['services', 'billingInvoices as invoices_count'])
|
||||
->with('subscriptions');
|
||||
|
||||
// Search by name or email
|
||||
|
||||
@@ -12,10 +12,21 @@ class EnsureUserNotSuspended
|
||||
{
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if ($request->user()?->isSuspended()) {
|
||||
// Allow impersonation stop even for suspended users
|
||||
if ($request->is('impersonate/stop') && $request->session()->has('impersonator_id')) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
$user = $request->user();
|
||||
|
||||
if ($user?->isSuspended()) {
|
||||
abort(403, 'Your account has been suspended.');
|
||||
}
|
||||
|
||||
if (method_exists($user, 'isBanned') && $user?->isBanned()) {
|
||||
abort(403, 'Your account has been banned.');
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ const paletteItems = computed(() => {
|
||||
<span class="font-weight-bold">Impersonation Active</span>
|
||||
</div>
|
||||
<Link
|
||||
:href="adminUrl + '/impersonate/stop'"
|
||||
href="/impersonate/stop"
|
||||
method="post"
|
||||
as="button"
|
||||
class="text-decoration-none"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { Link, useForm } from '@inertiajs/vue3'
|
||||
import { Link, useForm, usePage } from '@inertiajs/vue3'
|
||||
import AccountLayout from '@/Layouts/AccountLayout.vue'
|
||||
import {
|
||||
resolveServiceStatusColor,
|
||||
@@ -18,6 +18,10 @@ defineOptions({ layout: AccountLayout })
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const page = usePage()
|
||||
const newPassword = computed(() => (page.props as Record<string, unknown>).flash as Record<string, string> | undefined)
|
||||
const showPasswordDialog = ref(!!newPassword.value?.new_password)
|
||||
|
||||
// Forms for VPS operations
|
||||
const powerForm = useForm({})
|
||||
const rebuildDialog = ref(false)
|
||||
@@ -119,6 +123,7 @@ function resetPassword() {
|
||||
if (confirm('Are you sure you want to reset the root password? This will generate a new random password.')) {
|
||||
powerForm.post(`/services/${props.service.id}/vps/reset-password`, {
|
||||
preserveScroll: true,
|
||||
onSuccess: () => { showPasswordDialog.value = true },
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -907,6 +912,29 @@ function submitRebuild() {
|
||||
</VCardActions>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
|
||||
<!-- New Password Dialog -->
|
||||
<VDialog v-model="showPasswordDialog" max-width="440" persistent>
|
||||
<VCard>
|
||||
<VCardTitle class="text-h6 pa-4">New Root Password</VCardTitle>
|
||||
<VCardText>
|
||||
<VAlert type="warning" variant="tonal" class="mb-4">
|
||||
Copy this password now. It will not be shown again.
|
||||
</VAlert>
|
||||
<VTextField
|
||||
:model-value="newPassword?.new_password ?? ''"
|
||||
readonly
|
||||
variant="outlined"
|
||||
append-inner-icon="tabler-copy"
|
||||
@click:append-inner="navigator.clipboard.writeText(newPassword?.new_password ?? '')"
|
||||
/>
|
||||
</VCardText>
|
||||
<VCardActions>
|
||||
<VSpacer />
|
||||
<VBtn color="primary" @click="showPasswordDialog = false">Done</VBtn>
|
||||
</VCardActions>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -35,6 +35,8 @@ export interface DomainProps {
|
||||
export interface FlashProps {
|
||||
success?: string
|
||||
error?: string
|
||||
info?: string
|
||||
new_password?: string
|
||||
}
|
||||
|
||||
export interface SharedPageProps {
|
||||
|
||||
Reference in New Issue
Block a user