The MarsAI platform implements a three-tier role-based access control (RBAC) system to manage permissions across the film festival workflow.
Role Types
The platform supports three distinct user roles defined in the User model:
// /back/src/models/User.js:155-159
role : {
type : DataTypes . ENUM ( 'ADMIN' , 'JURY' , 'PRODUCER' ),
allowNull : false ,
defaultValue : 'PRODUCER'
}
Administrator Role Full platform access with administrative privileges. Permissions:
View all films and submissions
Modify film metadata and status
Assign jury members to films
Manage categories and awards
Configure film selection status
Force status transitions
Delete any content
Access dashboard analytics
Key Responsibilities:
Film curation and status management
Jury assignment and coordination
Award configuration
Platform administration
Example Use Cases:
Change film status from “submitted” to “assigned”
Assign multiple jury members to evaluate a film
Create and assign awards to winning films
Override status transition rules with force_transition
Jury Member Role Evaluates assigned films through multi-stage voting process. Permissions:
View assigned films only
Cast votes (YES/NO/TO DISCUSS) on assigned films
Add comments to votes
Modify votes during second round
Promote films to candidate status (from to_discuss)
View vote history
Key Responsibilities:
Evaluate films in assigned categories
Participate in two-stage voting process
Provide constructive feedback
Identify award-worthy films
Example Use Cases:
Review assigned films and cast initial votes
Update vote during second round discussion
Promote promising film to candidate status
View voting history and comments
Access Restrictions:
Cannot view unassigned films
Cannot modify film metadata
Cannot assign themselves to films
Limited to one vote modification per film
Film Producer Role Submits and manages their own film submissions. Permissions:
Submit new films
View own films only
Update own film metadata (before review)
Add/edit collaborators
Upload media files (video, thumbnails, subtitles)
View submission status
Key Responsibilities:
Film submission and documentation
Collaborator information management
Keep track of submission status
Example Use Cases:
Register account with film submission
Upload film files and thumbnails
Add collaborator credits
Check selection status
Access Restrictions:
Cannot view other producers’ films
Cannot vote on films
Cannot modify assigned jury members
Cannot change selection status
Cannot access admin dashboard
Role Assignment
During Registration
Roles are assigned when users register:
// Default role for new users
const userRole = role || "PRODUCER" ;
const newUser = await User . create ({
first_name: userFirstName ,
last_name: userLastName ,
email ,
password: hashedPassword ,
role: userRole // ADMIN, JURY, or PRODUCER
});
New users default to the PRODUCER role unless explicitly specified during registration. ADMIN and JURY roles are typically assigned manually.
Role Verification in JWT
The user’s role is embedded in the JWT token:
// /back/src/controllers/AuthController.js:36-40
const token = jwt . sign (
{ id: user . id_user , role: user . role }, // Role included in payload
process . env . JWT_SECRET ,
{ expiresIn: process . env . JWT_EXPIRES_IN || "1h" }
);
Backend Authorization
Middleware Protection
Routes are protected using role-based middleware:
import AuthMiddleware from "./middlewares/AuthMiddleware.js" ;
// Admin-only routes
app . put ( "/movies/:id" , AuthMiddleware ([ "ADMIN" ]), MovieController . updateMovie );
app . delete ( "/movies/:id" , AuthMiddleware ([ "ADMIN" ]), MovieController . deleteMovie );
// Jury routes
app . get ( "/movies/assigned" , AuthMiddleware ([ "JURY" ]), MovieController . getAssignedMovies );
app . post ( "/votes/:id_movie/jury" , AuthMiddleware ([ "JURY" ]), VoteController . createOrUpdateMyVote );
// Producer routes
app . get ( "/movies/my" , AuthMiddleware ([ "PRODUCER" ]), MovieController . getMyMovies );
app . post ( "/movies" , AuthMiddleware ([ "PRODUCER" ]), MovieController . createMovie );
// Multiple roles allowed
app . get ( "/movies/:id" , AuthMiddleware ([ "ADMIN" , "JURY" , "PRODUCER" ]), MovieController . getMovieById );
Role Check Logic
// /back/src/middlewares/AuthMiddleware.js:40-51
if ( ! user || ( roles . length && ! roles . includes ( user . role ))) {
return res . status ( 401 ). json ({
error: "User not found or unauthorized"
});
}
if ( roles . length && ! roles . includes ( user . role )) {
return res . status ( 403 ). json ({
error: "Permission denied, you are not authorized to access this resource"
});
}
req . user = user ; // Attach authenticated user to request
return next ();
Frontend Role Guards
RoleGuard Component
Protect routes on the frontend:
// /front/src/middlewares/RoleGuard.jsx:18-46
export function RoleGuard ({ allowedRoles , children }) {
const [ auth , setAuth ] = useState (() => ({
userRole: localStorage . getItem ( "role" ),
token: localStorage . getItem ( "token" )
}));
useEffect (() => {
// Listen for localStorage changes
const onStorage = () =>
setAuth ({
userRole: localStorage . getItem ( "role" ),
token: localStorage . getItem ( "token" )
});
window . addEventListener ( "storage" , onStorage );
return () => window . removeEventListener ( "storage" , onStorage );
}, []);
if ( ! auth . token ) {
return < Navigate to = "/auth/login" replace /> ;
}
// Check if user role is in allowed roles
if ( allowedRoles . includes ( auth . userRole )) {
return children ;
} else {
return (
< div className = "p-6 text-red-600 font-bold" >
Accès refusé - Vous n'avez pas les permissions nécessaires
</ div >
);
}
}
Usage in Routes
import { RoleGuard } from "./middlewares/RoleGuard" ;
< Route path = "/admin" element = {
< RoleGuard allowedRoles = { [ "ADMIN" ] } >
< AdminDashboard />
</ RoleGuard >
} />
< Route path = "/jury" element = {
< RoleGuard allowedRoles = { [ "JURY" ] } >
< JuryDashboard />
</ RoleGuard >
} />
< Route path = "/producer" element = {
< RoleGuard allowedRoles = { [ "PRODUCER" ] } >
< ProducerDashboard />
</ RoleGuard >
} />
Permission Matrix
Action ADMIN JURY PRODUCER View all films ✓ ✗ ✗ View assigned films ✓ ✓ ✗ View own films ✓ ✗ ✓ Submit new film ✓ ✗ ✓ Edit film metadata ✓ ✗ ✓* Delete film ✓ ✗ ✗ Change film status ✓ ✗ ✗ Assign jury members ✓ ✗ ✗ Assign categories ✓ ✗ ✗ Vote on films ✗ ✓ ✗ Modify own vote ✗ ✓** ✗ Create awards ✓ ✗ ✗ Assign awards ✓ ✗ ✗ View dashboard analytics ✓ ✗ ✗ Promote to candidate ✗ ✓*** ✗
* Producers can only edit their own films
** Jury can modify votes once during second round
*** Jury can only promote films from “to_discuss” status
Role-Specific Workflows
Film Review : View all submitted films
Jury Assignment : Assign jury members to films and update status to “assigned”
Category Assignment : Tag films with appropriate categories
Status Management : Move films through selection pipeline
Award Configuration : Create and assign awards to winning films
View Assignments : See films assigned by admin
First Round Voting : Cast initial vote (YES/NO/TO DISCUSS)
Second Round : Modify vote if film status is “to_discuss”
Candidate Promotion : Promote worthy films to candidate status
Track History : Review vote history and comments
Registration : Create account with PRODUCER role
Film Submission : Submit film with metadata and files
Add Collaborators : Credit team members
Track Status : Monitor submission through selection process
Receive Notifications : Get updates on acceptance/rejection
Security Considerations
Important Security Rules:
Roles are verified on every request via JWT
Backend always validates role permissions (never trust frontend)
Users cannot change their own roles
Role changes require database-level access
Role Escalation Prevention
Role is stored in database and JWT payload
JWT signature prevents tampering
Middleware validates role against database on each request
Frontend role checks are for UX only, not security
Producers : Can only access their own filmsconst id_user = req . user . id_user ;
const movies = await Movie . findAll ({ where: { id_user } });
Jury : Can only access assigned filmsconst movies = await Movie . findAll ({
include: [{
model: User ,
as: "Juries" ,
where: { id_user: req . user . id_user }
}]
});
Admin : No restrictions (full access)
Testing Roles
To test different role permissions:
# Login as different users
curl -X POST http://localhost:3000/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "admin@example.com", "password": "password"}'
# Use returned token in requests
curl http://localhost:3000/movies \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
Common Patterns
Owner Verification
// Allow ADMIN or owner to update
if ( req . user . role !== "ADMIN" && movie . id_user !== req . user . id_user ) {
return res . status ( 403 ). json ({ error: "Accès refusé" });
}
Multi-Role Access
// Allow multiple roles
app . get ( "/movies" ,
AuthMiddleware ([ "ADMIN" , "JURY" ]),
MovieController . getMovies
);
Role-Based Redirection
// Redirect after login based on role
switch ( userData ?. role ) {
case "ADMIN" :
navigate ( "/admin" );
break ;
case "JURY" :
navigate ( "/jury" );
break ;
case "PRODUCER" :
navigate ( "/producer" );
break ;
}