Appearance
Roles & Permissions
MyWarranties uses role-based access control (RBAC) to manage user permissions and secure API endpoints.
User Roles
ROLE_CUSTOMER
Default role for customers managing their warranties.
Permissions:
- Create, read, update, delete own products
- Create warranty claims
- View and chat in own claims
- Close own claims
- Upload files and attachments to claims
- View own profile and update it
- Receive notifications
Cannot:
- View other customers' products or claims
- View supplier or admin features
- Assign claims to suppliers
- View customer details of other users
ROLE_SUPPLIER
Role for warranty suppliers handling customer claims.
Permissions:
- View claims assigned to them
- Chat with customers in claims
- Close claims (assigned to them)
- Upload files and attachments to claims
- View customer details (only for their open claims)
- Access supplier dashboard
- Update claim status (open, in_progress, resolved, closed)
Cannot:
- Create products for customers
- View unassigned claims
- Access admin features
- View customer details for closed claims
- Manage user roles
ROLE_RESELLER
Role for resellers who sell products and handle warranty claims.
Permissions:
- All ROLE_SUPPLIER permissions
- Register products for customers
- Create products on behalf of customers
- View products sold by them
- Handle warranty claims for products they sold
- Access reseller dashboard
- View sales reports
Cannot:
- View products sold by other resellers
- Access admin features
- Manage user roles
ROLE_ADMIN
Full system administration access.
Permissions:
- All ROLE_RESELLER and ROLE_SUPPLIER permissions
- View all products across all users
- View and manage all claims
- Create, update, delete users
- Assign and modify user roles
- Assign claims to suppliers
- View all customer details
- Access all endpoints
- View system analytics
- Export reports
- Manage system configuration
Checking Roles
In Controllers
php
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[IsGranted('ROLE_USER')]
class ProductController extends AbstractController
{
#[Route('/api/products', methods: ['GET'])]
public function index(): JsonResponse
{
// Only authenticated users can access
}
#[IsGranted('ROLE_ADMIN')]
#[Route('/api/admin/products', methods: ['GET'])]
public function adminIndex(): JsonResponse
{
// Only admins can access
}
}Manual Check
php
if (!$this->isGranted('ROLE_ADMIN')) {
throw new AccessDeniedException('Admin access required');
}Check in Twig (if using)
twig
{% if is_granted('ROLE_ADMIN') %}
<a href="/admin">Admin Panel</a>
{% endif %}Claim Workflow & Permissions
Creating Claims
- Customer creates a claim for their product
- Claim is automatically assigned to product's supplier (if known)
- Both customer and supplier receive notification
Viewing Claims
- Customer: Can see their own claims
- Supplier: Can see claims assigned to them
- Reseller: Can see claims for products they sold
- Admin: Can see all claims
Chatting in Claims
- Customer: Can send messages in their claims
- Supplier: Can send messages in assigned claims
- Both can see full conversation history
- Real-time updates via Mercure
Uploading Files
- Customer: Can upload files (photos, documents) to their claims
- Supplier: Can upload files to assigned claims
- Supported formats: JPG, PNG, PDF
- Max file size: 10MB per file
Closing Claims
- Customer: Can close their own claims
- Supplier: Can close assigned claims
- Admin: Can close any claim
- Once closed, claim becomes read-only
- Closed claims can be reopened by admin only
Viewing Customer Details
Supplier can view when claim is open:
- Customer name
- Customer email
- Customer phone (if provided)
- Customer address (if provided)
Supplier CANNOT view when claim is closed:
- Customer details are hidden after claim closes
- Only claim history remains visible
Role Hierarchy
Roles do NOT inherit from each other - each role has specific permissions:
yaml
# config/packages/security.yaml
security:
role_hierarchy:
ROLE_ADMIN: [ROLE_CUSTOMER, ROLE_SUPPLIER, ROLE_RESELLER]This means:
- Each role is independent
- Admin has access to all role permissions
- Supplier and Reseller are separate roles
- Users can have multiple roles (e.g., both CUSTOMER and SUPPLIER)
API Endpoints by Role
Public (No Authentication)
| Endpoint | Method | Description |
|---|---|---|
/api/send-code | POST | Send verification code |
/api/verify-code | POST | Verify code and login |
ROLE_CUSTOMER
| Endpoint | Method | Description |
|---|---|---|
/api/me | GET | Get user profile |
/api/me | PUT | Update profile |
/api/products | GET | List user's products |
/api/products | POST | Create product |
/api/products/{id} | GET, PUT, DELETE | Manage product |
/api/claims | GET | List user's claims |
/api/claims | POST | Create claim |
/api/claims/{id} | GET | View claim details |
/api/claims/{id}/close | POST | Close own claim |
/api/claims/messages/{id} | GET | View claim messages |
/api/claims/messages/{id} | POST | Send message + upload files |
ROLE_SUPPLIER
| Endpoint | Method | Description |
|---|---|---|
/api/claims/assigned | GET | Get assigned claims |
/api/claims/{id} | GET | View assigned claim (includes customer details) |
/api/claims/{id}/close | POST | Close assigned claim |
/api/claims/{id}/status | PUT | Update claim status |
/api/claims/messages/{id} | GET | View claim messages |
/api/claims/messages/{id} | POST | Send message + upload files |
/api/supplier/dashboard | GET | Supplier metrics |
ROLE_RESELLER
All ROLE_SUPPLIER endpoints plus:
| Endpoint | Method | Description |
|---|---|---|
/api/products | POST | Create product for customer |
/api/reseller/products | GET | Products sold by reseller |
/api/reseller/claims | GET | Claims for reseller's products |
/api/reseller/dashboard | GET | Reseller metrics |
ROLE_ADMIN
All endpoints plus:
| Endpoint | Method | Description |
|---|---|---|
/api/admin/products | GET | All products |
/api/admin/claims | GET | All claims |
/api/admin/claims/{id}/reopen | POST | Reopen closed claim |
/api/admin/users | GET, POST | Manage users |
/api/admin/users/{id} | GET, PUT, DELETE | User details |
/api/admin/users/{id}/roles | PUT | Update roles |
/api/admin/reports | GET | System reports |
User Account Deletion
When a user deletes their account:
What Gets Deleted
- User profile information
- Login credentials
- Personal data (name, email, phone)
- Notification preferences
What Gets Kept
- All warranty claims (including closed ones)
- Claim messages and conversation history
- Product information (anonymized)
- Uploaded files (receipts, photos)
Anonymization
Deleted user accounts are anonymized:
- User email becomes:
deleted-user-{id}@deleted.local - User name becomes:
[Deleted User] - Claims remain visible with anonymized customer info
- Suppliers can still access claim history
Why Keep Claims?
Claims contain important business and legal information:
- Warranty dispute history
- Communication records
- Resolution documentation
- Statistical data for suppliers
Technical Implementation
php
// User deletion does NOT cascade to claims
$user->setEmail("deleted-user-{$user->getId()}@deleted.local");
$user->setName('[Deleted User]');
$user->setRoles(['ROLE_DELETED']);
$user->setDeletedAt(new \DateTime());
$entityManager->flush();
// Claims remain intact with foreign key to userAssigning Roles
Via Console Command
bash
# Add supplier role
php bin/console app:user:roles user@example.com --add=ROLE_SUPPLIER
# Remove supplier role
php bin/console app:user:roles user@example.com --remove=ROLE_SUPPLIER
# Set specific roles (removes all others)
php bin/console app:user:roles user@example.com --set=ROLE_ADMIN
# List user roles
php bin/console app:user:roles user@example.com --listVia API (Admin Only)
http
PUT /api/admin/users/123/roles
Authorization: Bearer <admin-jwt-token>
Content-Type: application/json
{
"roles": ["ROLE_USER", "ROLE_SUPPLIER"]
}Response:
json
{
"id": 123,
"email": "user@example.com",
"roles": ["ROLE_USER", "ROLE_SUPPLIER"]
}Programmatically
php
use App\Entity\User;
$user = $userRepository->find($userId);
$user->setRoles(['ROLE_USER', 'ROLE_SUPPLIER']);
$entityManager->flush();Access Control Examples
Product Access
php
// Users can only access their own products
if ($product->getUser() !== $this->getUser()) {
throw new AccessDeniedException('Not your product');
}
// Unless they're an admin
if (!$this->isGranted('ROLE_ADMIN')) {
if ($product->getUser() !== $this->getUser()) {
throw new AccessDeniedException('Not your product');
}
}Claim Access
php
// Customers can access their own claims
if ($claim->getCustomer() === $this->getUser()) {
return $claim;
}
// Suppliers can access claims assigned to them
if ($this->isGranted('ROLE_SUPPLIER') && $claim->getSupplier() === $this->getUser()) {
// Include customer details only if claim is open
if ($claim->getStatus() !== 'closed') {
$claim->setCustomerDetails([
'name' => $claim->getCustomer()->getName(),
'email' => $claim->getCustomer()->getEmail(),
'phone' => $claim->getCustomer()->getPhone(),
'address' => $claim->getCustomer()->getAddress()
]);
}
return $claim;
}
// Resellers can access claims for products they sold
if ($this->isGranted('ROLE_RESELLER')) {
$product = $claim->getProduct();
if ($product->getReseller() === $this->getUser()) {
return $claim;
}
}
// Admins can access all claims
if ($this->isGranted('ROLE_ADMIN')) {
return $claim;
}
throw new AccessDeniedException('Cannot access this claim');Closing Claims
php
// Check if user can close this claim
public function canCloseClaim(WarrantyClaim $claim, User $user): bool
{
// Customer can close their own claims
if ($claim->getCustomer() === $user) {
return true;
}
// Supplier can close assigned claims
if ($user->hasRole('ROLE_SUPPLIER') && $claim->getSupplier() === $user) {
return true;
}
// Admin can close any claim
if ($user->hasRole('ROLE_ADMIN')) {
return true;
}
return false;
}File Upload Access
php
// Check if user can upload files to claim
public function canUploadToClaim(WarrantyClaim $claim, User $user): bool
{
// Customer can upload to their claims
if ($claim->getCustomer() === $user) {
return true;
}
// Supplier can upload to assigned claims
if ($user->hasRole('ROLE_SUPPLIER') && $claim->getSupplier() === $user) {
return true;
}
// Admin can upload to any claim
if ($user->hasRole('ROLE_ADMIN')) {
return true;
}
return false;
}Error Responses
401 Unauthorized
User is not authenticated (no JWT token or invalid token).
json
{
"error": "Unauthorized",
"message": "Invalid JWT Token"
}403 Forbidden
User is authenticated but lacks required role.
json
{
"error": "Forbidden",
"message": "Access Denied. Requires ROLE_ADMIN."
}Best Practices
Principle of Least Privilege
- Assign minimum required roles
- Use specific role checks, not just "authenticated"
Check Ownership
- Always verify resource ownership
- Don't rely solely on roles
Multiple Roles
- Users can have multiple roles
- Check for any applicable role, not all
Frontend Guards
- Hide/disable UI elements based on roles
- Always validate on backend too
Audit Logging
- Log role changes
- Track admin actions
- Monitor suspicious activity
Testing Roles
In PHPUnit
php
public function testAdminCanAccessAllProducts(): void
{
$admin = $this->createUser('admin@example.com', ['ROLE_ADMIN']);
$client = $this->createAuthenticatedClient($admin);
$client->request('GET', '/api/admin/products');
$this->assertResponseIsSuccessful();
}
public function testUserCannotAccessAdminEndpoint(): void
{
$user = $this->createUser('user@example.com', ['ROLE_USER']);
$client = $this->createAuthenticatedClient($user);
$client->request('GET', '/api/admin/products');
$this->assertResponseStatusCodeSame(403);
}Common Scenarios
Multi-Tenant Access
php
// User can be both customer and supplier
$user->setRoles(['ROLE_USER', 'ROLE_SUPPLIER']);
// Show different dashboards based on active role
if ($this->isGranted('ROLE_SUPPLIER')) {
return $this->render('supplier/dashboard.html.twig');
}
return $this->render('user/dashboard.html.twig');Temporary Elevation
php
// Grant temporary admin access
$user->setRoles([...$user->getRoles(), 'ROLE_ADMIN']);
// After specific task
$user->setRoles(array_diff($user->getRoles(), ['ROLE_ADMIN']));Role-Based Views
php
// Return different data based on role
if ($this->isGranted('ROLE_ADMIN')) {
return $this->json($product, context: ['groups' => ['product:admin']]);
}
return $this->json($product, context: ['groups' => ['product:user']]);Security Considerations
- Never trust client-side role checks - Always validate on server
- JWT tokens contain roles - Changing roles requires new token
- Cache invalidation - Clear user cache when roles change
- Session management - Force re-login after role changes
- Audit trail - Log all role modifications
Related
- Authentication - JWT and login
- Error Codes - Error handling
- API Reference - Endpoint documentation
