feat(docs): expand authorization documentation for custom Alpine.js components; include manual protection patterns and implementation guidelines
This commit is contained in:
@@ -227,6 +227,159 @@ public function __construct(
|
|||||||
- New authorization parameters are optional
|
- New authorization parameters are optional
|
||||||
- Legacy @can/@else patterns still function but are discouraged
|
- 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-Based Multi-Tenancy
|
||||||
- **[Team.php](mdc:app/Models/Team.php)** - Multi-tenant organization structure (8.9KB, 308 lines)
|
- **[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
|
- **[TeamInvitation.php](mdc:app/Models/TeamInvitation.php)** - Secure team collaboration
|
||||||
|
|||||||
Reference in New Issue
Block a user