Skip to main content
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 RoleFull 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

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

ActionADMINJURYPRODUCER
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

1
Admin Workflow
2
  • 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
  • 3
    Jury Workflow
    4
  • 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
  • 5
    Producer Workflow
    6
  • 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 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 films
    const id_user = req.user.id_user;
    const movies = await Movie.findAll({ where: { id_user } });
    
    Jury: Can only access assigned films
    const 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;
    }