feat(docs): expand authorization documentation for custom Alpine.js components; include manual protection patterns and implementation guidelines

This commit is contained in:
Andras Bacsai
2025-08-25 11:33:27 +02:00
parent 0831905443
commit be47884ee0

View File

@@ -227,6 +227,159 @@ public function __construct(
- New authorization parameters are optional
- Legacy @can/@else patterns still function but are discouraged
### Custom Component Authorization Patterns
When dealing with **custom Alpine.js components** or complex UI elements that don't use the standard `x-forms.*` components, manual authorization protection is required since the automatic `canGate` system only applies to enhanced form components.
#### Common Custom Components Requiring Manual Protection
**⚠️ Custom Components That Need Manual Authorization:**
- Custom dropdowns/selects with Alpine.js
- Complex form widgets with JavaScript interactions
- Multi-step wizards or dynamic forms
- Third-party component integrations
- Custom date/time pickers
- File upload components with drag-and-drop
#### Manual Authorization Pattern
**✅ Proper Manual Authorization:**
```html
<!-- Custom timezone dropdown example -->
<div class="w-full">
<div class="flex items-center mb-1">
<label for="customComponent">Component Label</label>
<x-helper helper="Component description" />
</div>
@can('update', $resource)
<!-- Full interactive component for authorized users -->
<div x-data="{
open: false,
value: '{{ $currentValue }}',
options: @js($options),
init() { /* Alpine.js initialization */ }
}">
<input x-model="value" @focus="open = true"
wire:model="propertyName" class="w-full input">
<div x-show="open">
<!-- Interactive dropdown content -->
<template x-for="option in options" :key="option">
<div @click="value = option; open = false; $wire.submit()"
x-text="option"></div>
</template>
</div>
</div>
@else
<!-- Read-only version for unauthorized users -->
<div class="relative">
<input readonly disabled autocomplete="off"
class="w-full input opacity-50 cursor-not-allowed"
value="{{ $currentValue ?: 'No value set' }}">
<svg class="absolute right-0 mr-2 w-4 h-4 opacity-50">
<!-- Disabled icon -->
</svg>
</div>
@endcan
</div>
```
#### Implementation Checklist
When implementing authorization for custom components:
**🔍 1. Identify Custom Components:**
- Look for Alpine.js `x-data` declarations
- Find components not using `x-forms.*` prefix
- Check for JavaScript-heavy interactions
- Review complex form widgets
**🛡️ 2. Wrap with Authorization:**
- Use `@can('gate', $resource)` / `@else` / `@endcan` structure
- Provide full functionality in the `@can` block
- Create disabled/readonly version in the `@else` block
**🎨 3. Design Disabled State:**
- Apply `readonly disabled` attributes to inputs
- Add `opacity-50 cursor-not-allowed` classes for visual feedback
- Remove interactive JavaScript behaviors
- Show current value or appropriate placeholder
**🔒 4. Backend Protection:**
- Ensure corresponding Livewire methods check authorization
- Add `$this->authorize('gate', $resource)` in relevant methods
- Validate permissions before processing any changes
#### Real-World Examples
**Custom Date Range Picker:**
```html
@can('update', $application)
<div x-data="dateRangePicker()" class="date-picker">
<!-- Interactive date picker with calendar -->
</div>
@else
<div class="flex gap-2">
<input readonly disabled value="{{ $startDate }}" class="input opacity-50">
<input readonly disabled value="{{ $endDate }}" class="input opacity-50">
</div>
@endcan
```
**Multi-Select Component:**
```html
@can('update', $server)
<div x-data="multiSelect({ options: @js($options) })">
<!-- Interactive multi-select with checkboxes -->
</div>
@else
<div class="space-y-2">
@foreach($selectedValues as $value)
<div class="px-3 py-1 bg-gray-100 rounded text-sm opacity-50">
{{ $value }}
</div>
@endforeach
</div>
@endcan
```
**File Upload Widget:**
```html
@can('update', $application)
<div x-data="fileUploader()" @drop.prevent="handleDrop">
<!-- Drag-and-drop file upload interface -->
</div>
@else
<div class="border-2 border-dashed border-gray-300 rounded-lg p-6 text-center opacity-50">
<p class="text-gray-500">File upload restricted</p>
@if($currentFile)
<p class="text-sm">Current: {{ $currentFile }}</p>
@endif
</div>
@endcan
```
#### Key Principles
**🎯 Consistency:**
- Maintain similar visual styling between enabled/disabled states
- Use consistent disabled patterns across the application
- Apply the same opacity and cursor styling
**🔐 Security First:**
- Always implement backend authorization checks
- Never rely solely on frontend hiding/disabling
- Validate permissions on every server action
**💡 User Experience:**
- Show current values in disabled state when appropriate
- Provide clear visual feedback about restricted access
- Maintain layout stability between states
**🚀 Performance:**
- Minimize Alpine.js initialization for disabled components
- Avoid loading unnecessary JavaScript for unauthorized users
- Use simple HTML structures for read-only states
### Team-Based Multi-Tenancy
- **[Team.php](mdc:app/Models/Team.php)** - Multi-tenant organization structure (8.9KB, 308 lines)
- **[TeamInvitation.php](mdc:app/Models/TeamInvitation.php)** - Secure team collaboration