Home Projects Portfolio Dashboard Export PDF Log in

Building a Secure, Scalable Admin Dashboard for Content Moderation

Introduction

In the realm of dynamic web applications, user-generated content is a double-edged sword: it drives engagement but demands robust moderation. Efficiently reviewing and managing content is critical for maintaining platform quality and user safety. The challenge lies in building a system that is both secure and scalable, allowing administrators to moderate content without friction.

This post dives into how we engineered a dedicated admin dashboard for the justifai project, specifically to streamline the review process for documents marked with a 'REVIEW' status. We’ll explore the architecture behind this solution, from secure authentication to efficient data retrieval, ensuring administrators have the tools they need to maintain a pristine content ecosystem.

The Challenge: Streamlining Content Review

Initially, identifying documents that required administrative review could be a cumbersome process. Directly scanning large datasets for specific statuses is inefficient and can become prohibitively slow as data scales. We needed a system that offered:

  • Secure Access: Only authorized administrators should be able to view and modify document statuses.
  • Efficient Retrieval: Quick access to all documents awaiting review, ordered by recency.
  • Intuitive Interface: A frontend dashboard where admins can easily validate or reject documents.
  • Scalability: The solution must perform well under growing data volumes and user traffic.

Architecting the Solution

Our approach leveraged AWS serverless components and modern frontend frameworks to create a performant and secure review workflow. The core components involved are Cognito for identity, API Gateway for secure endpoints, AWS Lambda for backend logic, and DynamoDB for persistent storage, all orchestrated by a React frontend.

1. Secure Admin Access with Cognito

Security was paramount. We integrated AWS Cognito to manage user identities and defined an admin user group. Upon successful authentication, a JSON Web Token (JWT) is issued. This JWT contains claims, including cognito:groups, which is crucial for authorization. While API Gateway's JWT authorizer validates the token's authenticity, the specific check for the admin group membership is performed within our Lambda function, providing granular control.

2. Backend API: The Admin Lambda

At the heart of our backend is a dedicated Lambda function, admin-documents. This microservice exposes two critical endpoints:

  • GET /documents?status=REVIEW: Fetches a list of documents awaiting review. This query leverages a Global Secondary Index (GSI) for optimal performance.
  • PATCH /documents/{documentId}: Allows an administrator to update a document's status (e.g., from REVIEW to VALIDATED or REJECTED).

This Lambda implements the least-privilege principle, only having Query permissions on the GSI and UpdateItem/GetItem on the main DynamoDB table.

3. Efficient Data Access with DynamoDB GSI

To address the performance challenge of querying documents by status, we introduced a Global Secondary Index (GSI) on our DynamoDB table. This status-index allows the Lambda to efficiently Query for documents based on their status attribute, along with a creationDate for ordering, avoiding costly Scan operations on the primary table.

4. Frontend: The Admin Dashboard

The user-facing component is a React AdminDashboard view. This dashboard is conditionally rendered only if the logged-in user's JWT indicates membership in the admin group. It displays the list of documents fetched from the GET endpoint and provides intuitive 'Validate' and 'Reject' buttons that trigger the PATCH endpoint, simplifying the moderation workflow.

Implementation Details

The Lambda function performs a crucial check to ensure only admin users can access its functionality. This snippet illustrates how cognito:groups from the JWT claim is validated:

// In admin-documents/index.mjs
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, QueryCommand, UpdateCommand } from '@aws-sdk/lib-dynamodb';

const client = new DynamoDBClient({});
const ddbDocClient = DynamoDBDocumentClient.from(client);

export const handler = async (event) => {
    const userGroups = event.requestContext.authorizer.jwt.claims['cognito:groups'];

    if (!userGroups || !userGroups.includes('admin')) {
        return {
            statusCode: 403,
            body: JSON.stringify({ message: 'Unauthorized: Admin access required' }),
        };
    }

    const { httpMethod, pathParameters, queryStringParameters, body } = event;

    if (httpMethod === 'GET' && queryStringParameters && queryStringParameters.status === 'REVIEW') {
        const command = new QueryCommand({
            TableName: 'YourDocumentsTable',
            IndexName: 'status-index',
            KeyConditionExpression: '#status = :statusValue',
            ExpressionAttributeNames: { '#status': 'status' },
            ExpressionAttributeValues: { ':statusValue': 'REVIEW' },
            ScanIndexForward: false, // Most recent first
        });
        const { Items } = await ddbDocClient.send(command);
        return { statusCode: 200, body: JSON.stringify(Items) };
    }

    if (httpMethod === 'PATCH' && pathParameters && pathParameters.documentId && body) {
        const { newStatus } = JSON.parse(body);
        const command = new UpdateCommand({
            TableName: 'YourDocumentsTable',
            Key: { documentId: pathParameters.documentId },
            UpdateExpression: 'SET #status = :newStatus, updatedAt = :updatedAt',
            ExpressionAttributeNames: { '#status': 'status' },
            ExpressionAttributeValues: {
                ':newStatus': newStatus,
                ':updatedAt': new Date().toISOString(),
            },
            ReturnValues: 'ALL_NEW',
        });
        const { Attributes } = await ddbDocClient.send(command);
        return { statusCode: 200, body: JSON.stringify(Attributes) };
    }

    return { statusCode: 400, body: JSON.stringify({ message: 'Invalid request' }) };
};

This JavaScript example demonstrates the core logic of checking for admin privileges using JWT claims and then performing either a Query on the GSI or an UpdateCommand on the main table, based on the HTTP method and parameters.

Outcome: A Powerful Review Workflow

The implementation of this admin dashboard and its supporting backend has significantly improved the efficiency of content moderation for justifai. Administrators now have a secure, responsive, and intuitive platform to manage documents, reducing review times and ensuring content quality at scale. The use of a DynamoDB GSI has eliminated performance bottlenecks, making the process smooth even as the number of documents grows.

Next Steps

Future enhancements could include implementing more sophisticated audit logging for administrative actions, adding search and filter capabilities within the dashboard beyond just status, or integrating a notification system to alert admins to new documents requiring review. Exploring server-side rendering for the frontend dashboard could also be considered for improved initial load times and SEO if public visibility becomes a concern.


Generated with Gitvlg.com

Building a Secure, Scalable Admin Dashboard for Content Moderation
Seydina Limamou Laye Yade

Seydina Limamou Laye Yade

Author

Share: