OpenFGA Studio – An Open Source Authorization Modeling Interface

OpenFGA Studio

Understanding OpenFGA

OpenFGA (Fine-Grained Authorization) is a high-performance authorization engine built for developers and inspired by Google’s Zanzibar paper. It excels in handling complex authorization scenarios with features that make it stand out:

  • Relationship-Based Authorization: Model complex access patterns through relationships
  • High Performance: Process millions of authorization checks per second
  • Flexibility: Support for RBAC, ABAC, and ReBAC models
  • Time-Based Access: Define temporal access rules with built-in support
  • Proven Architecture: Based on Google’s battle-tested Zanzibar system

While OpenFGA Playground provides a hosted application for experimentation, it comes with limitations:

  • Not open source
  • Cannot be deployed in air-gapped environments
  • Limited customization options
  • Dependency on external services

Why This Project?

I built OpenFGA Studio to address these limitations and provide:

  • A fully open-source solution deployable anywhere
  • Enhanced user experience for authorization modeling
  • Complete control over your authorization data
  • Customizable interface for specific needs
  • Seamless integration with existing systems

OpenFGA Studio streamlines the process of creating, testing, and managing authorization models, making complex authorization logic more accessible and manageable. Built with modern web technologies including React, TypeScript, and Material-UI, it provides a robust and intuitive interface for working with OpenFGA.

OpenFGA Studio Dark Mode OpenFGA Studio Light Mode

Key Features

1. Store Management

The interface provides a straightforward way for managing OpenFGA stores. You can:

  • Create new authorization stores
  • Switch between existing stores
  • View and manage store configurations
  • Track model versions and changes

Store Creation Interface

2. Visual Authorization Model Editor

The model editor is a powerful interface for defining authorization rules with features including:

  • Syntax highlighting for better readability
  • Real-time validation against OpenFGA schema
  • Support for both DSL and JSON formats
  • Error highlighting and suggestions
  • Easy switching between model versions

3. Interactive Graph Visualization

Understand your authorization model at a glance with:

  • Visual representation of relationships between types
  • Interactive node exploration
  • Relationship flow visualization
  • Dynamic updates as you modify the model
  • Zoom and pan controls for large models

4. Advanced Tuple Management

The tuple management interface makes it easy to define and manage relationships:

Adding Basic Tuple

  • Assisted tuple creation with type suggestions
  • Support for direct (freeform) tuple input
  • Batch operations for efficient management
  • Reverse chronological listing of tuples
  • Quick delete operations

5. Conditional Relationship Support

Handle complex authorization scenarios with conditional relationships:

Adding Tuple with Conditions

  • Dynamic condition parameter inputs
  • Type-aware parameter validation
  • Timestamp and duration support
  • Visual feedback for condition state

6. Comprehensive Query Testing

Test your authorization rules with an intuitive interface:

Basic Access Validation

Features include:

  • Visual query builder
  • Direct query input support
  • Real-time query validation
  • Historical query tracking
  • Quick query replay

7. Conditional Access Testing

Test complex conditional access patterns:

Conditional Access Validation

  • Test time-based access rules
  • Validate contextual conditions
  • Dynamic parameter inputs
  • Clear success/failure indicators

8. Developer-Friendly Features

OpenFGA Studio is built with developers in mind:

  • Clean, modern UI with dark mode support
  • Keyboard shortcuts for common operations
  • Copy/paste support for all fields
  • Detailed error messages
  • Response timing information

Technical Implementation

OpenFGA Studio is built with modern web technologies:

  • Frontend: React with TypeScript for type safety
  • UI Framework: Material-UI for consistent, responsive design
  • State Management: React hooks and context for efficient state handling
  • Build Tool: Vite for fast development and optimized production builds
  • API Integration: Axios for reliable API communication
  • Graph Visualization: React Flow for interactive model visualization

Common Use Cases

  1. Authorization Modeling

    • Design role-based access control (RBAC) systems
    • Implement attribute-based access control (ABAC)
    • Model relationship-based authorization
  2. Testing and Validation

    • Verify access control rules
    • Test time-based permissions
    • Validate complex conditional access
  3. Development and Debugging

    • Debug authorization issues
    • Prototype authorization models
    • Document access control patterns

Conclusion

OpenFGA Studio simplifies the complex task of fine-grained authorization modeling and testing. Whether you’re designing a new authorization system or maintaining an existing one, this tool provides the features needed to work efficiently with OpenFGA.

The combination of visual tools, intuitive interfaces, and powerful testing capabilities makes it an essential tool for developers working with authorization systems. The tool continues to evolve with new features and improvements based on community feedback and real-world usage patterns.

Feel free to contribute or request features at OpenFGA Studio

Enhancing the VS Code Agent Mode to integrate with Local tools using Model Context Protocol (MCP)

Building a Todo List Server with Model Context Protocol (MCP)

GitHub Repository Node Version MCP SDK

This blog post walks through creating a Todo List server using the Model Context Protocol (MCP), demonstrating how to build AI-friendly tools that integrate seamlessly with VS Code.

πŸ”— Source Code: The complete implementation is available on GitHub

Table of Contents

The Evolution of AI-Assisted Development

I have been using VS Code with GitHub Copilot for development purposes. The introduction of text-based chat, which brought GPT capabilities directly into the IDE, was revolutionary.

GitHub Copilot and the Local Tools Gap

GitHub Copilot has revolutionized how developers write code by providing intelligent code suggestions and completions. While it excels at understanding code context and generating relevant snippets, there has been a notable gap in its ability to interact with local development tools, intranet KBs, and execute actions in the development environment. This limitation means that while Copilot can suggest code, it cannot directly help with tasks like running commands, managing files, or interacting with local services.

Agent Mode: Bridging the Gap

The introduction of Agent Mode in GitHub Copilot represents a significant step forward in AI-assisted development. It enables Copilot to:

  • Execute terminal commands
  • Modify files directly
  • Interact with the VS Code environment
  • Handle project-specific tasks

This advancement transformed Copilot from a passive code suggestion tool into an active development partner that can help manage your entire development workflow. Here are some powerful capabilities and example interactions:

1. Build and Test Automation

Trigger Maven builds with specific profiles:

"Run mvn clean install with the 'production' profile for my project"

Execute JUnit test suites:

"Execute all JUnit tests in the UserServiceTest class"

Run code quality tools:

"Run ESLint on all JavaScript files in the src directory"
"Start a local Sonar analysis with coverage and security scan"

2. Documentation and Release Management

Generate release documentation:

"Generate release notes for changes between tag v1.2.0 and v1.3.0"

Technical documentation:

"Create a technical design document for the authentication service"
"Update the API documentation in Confluence for the new endpoints"

3. Project Management Integration

JIRA ticket management:

"Create a JIRA ticket for the memory leak bug we found in the login service"
"Convert all TODO comments in AuthService.java to JIRA tickets"

Sprint management:

"Update the status of PROJ-123 to 'In Review' and add a comment with the PR link"
"Show me all JIRA tickets assigned to me that are blocking the current sprint"

4. Cross-Repository Operations

Multi-repo analysis:

"Check if the latest changes in the API repo are compatible with our client library"
"Run integration tests across both the frontend and backend repositories"

While these capabilities demonstrate the power of Agent Mode, they highlight a crucial challenge: the need for external API integrations. Each of these tasks requires:

  • Authentication with external services (JIRA, Confluence, Sonar)
  • Managing different API versions and endpoints
  • Handling various authentication methods
  • Maintaining connection states and sessions
  • Coordinating operations across multiple systems

This complexity creates significant overhead for developers who need to:

  1. Implement and maintain integration code for each service
  2. Handle authentication and authorization
  3. Manage API versioning and changes
  4. Deal with different response formats and error handling

MCP: The New Standard for AI Tool Integration

Model Context Protocol (MCP) emerges as the next evolution in this space, providing a standardized way for AI models to interact with development tools and services. Unlike traditional approaches where AI assistants are limited to suggesting code, MCP enables:

  1. Direct Tool Integration

    • AI models can directly invoke local tools
    • Real-time interaction with development environment
    • Standardized communication protocol
  2. Extensible Architecture

    • Custom tool definitions
    • Plugin-based system
    • Easy integration with existing services
  3. Development Environment Awareness

    • Context-aware assistance
    • Access to local resources
    • Real-time feedback loop

What is Model Context Protocol (MCP)?

Model Context Protocol (MCP) is a specification that enables AI models to interact with external tools and services in a standardized way. It defines how tools can expose their functionality through a structured interface that AI models can understand and use.

Key benefits of MCP that I have personally benefited from:

  • Standardized tool definitions with JSON Schema
  • Real-time interaction capabilities
  • Session management
  • Built-in VS Code integration

More about MCP Architecture Documentation

How MCP Tools Work

Each MCP tool follows a standardized structure:

{
  name: "toolName",
  description: "What the tool does",
  parameters: {
    // JSON Schema definition of inputs
  },
  returns: {
    // JSON Schema definition of outputs
  }
}

When an AI model wants to use a tool:

  1. It sends a request with the tool name and parameters
  2. The MCP server validates the request
  3. The tool executes with the provided parameters
  4. Results are returned in a standardized format

This structured approach ensures:

  • Consistent tool behavior
  • Type safety throughout the system
  • Easy tool discovery and documentation
  • Predictable error handling

Architecture Overview

Here’s how the different components interact in our MCP Todo implementation:

graph TD
    A[GitHub Copilot] -->|Natural Language Commands| B[VS Code]
    B -->|MCP Protocol| C[MCP Todo Server]
    C -->|CRUD Operations| D[(LowDB/Database)]
    C -->|Real-time Updates| B
    B -->|Command Results| A

TODO MCP Server Components

Prerequisites

To follow along, you’ll need:

  • Node.js (v22 or higher)
  • VS Code
  • Basic understanding of Express.js
  • npm or yarn package manager

Setting Up the Project

  1. First, create a new project and initialize npm:
mkdir mcp-todo-server
cd mcp-todo-server
npm init -y
  1. Install required dependencies:
npm install @modelcontextprotocol/sdk express lowdb zod

For this demonstration, we’re using lowdb to manage tasks in a JSON file without actual integration with an external system. In a production environment, the lowdb functions can be replaced with actual JIRA CRUD API calls for end-to-end implementation.

  1. Create the basic directory structure:
mcp-todo-server/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ config/
β”‚   β”œβ”€β”€ tools/
β”‚   β”œβ”€β”€ utils/
β”‚   └── server.js
└── package.json

Implementing the MCP Server

1. Basic Server Setup

We started with a basic Express server that implements the MCP protocol. The server uses StreamableHTTP for real-time communication and session management.

Key components in server.js:

  • Express server setup
  • MCP SDK integration
  • StreamableHTTP transport configuration
  • Session management for maintaining tool state

2. Database Configuration

We used lowdb, a lightweight JSON database, to persist our todos. The database configuration in config/db.js handles:

  • JSON file storage
  • Basic CRUD operations
  • Data persistence between server restarts

3. Implementing Todo Tools

We implemented four main tools for managing todos:

  1. createTodo

    • Creates new todo items
    • Validates input using Zod schema
    • Returns the created todo with a unique ID
  2. listTodos

    • Lists all todos or filters by completion status
    • Formats output for easy reading
    • Supports real-time updates
  3. updateTodo

    • Updates todo completion status
    • Validates input parameters
    • Returns updated todo information
  4. deleteTodo

    • Removes todos by ID
    • Provides completion confirmation
    • Handles error cases gracefully

VS Code Integration

To enable VS Code to use our MCP server, follow these steps:

  1. Enable Agent mode in VS Code. Click on the drop down just before the model listing and select agent from it.

Enable Agent Mode in VS Code

  1. Then click on the Gear icon next to the speaker icon in the above image and select β€œAdd more tools” then select β€œAdd MCP Server”

Add MCP Server

  1. Then select HTTP or Server-Sent Events and provide the URL based on the server we created. In this case, it’s http://localhost:3000. Then select a name for the server.

Select HTTP or Server-Sent events

Provide name

Select settings

  1. Alternatively you can Press Ctrl+Shift+P (Windows/Linux) or Cmd+Shift+P (Mac) to open the Command Palette. Type β€œOpen Settings (JSON)” and select it and add the following configuration:
{
    "mcp": {
        "servers": {
            "my-mcp-server": {
                "url": "http://localhost:3000/mcp"
            }
        }
    }
}

You can use the 4th step to verify if the server is added correctly after the first 3 steps are done. The User Settings option has Start, Stop, and Restart options. This step helped me identify if there are any issues with the MCP tools server effectively.

Settings JSON

  1. Reload VS Code to apply the changes or use the Start, Stop, Restart options in the settings.json as shown above.

  2. After successful addition of the MCP server, you should see the tools listed when you click the gear icon in the Copilot chat window.

Tools listed successfully

Using the Todo MCP Server

Here are some example prompts you can use in VS Code with GitHub Copilot to interact with the todo server. Each example includes a screenshot of the actual interaction:

  1. Creating a Todo

    Prompt: "Create a new todo item called 'Review PR #123'"
    Response: Successfully created todo "Review PR #123"
    

    Creating a new todo Execute Add Create TODO success

  2. Listing Todos

    Prompt: "Show me all my todos"
    Response: Here are your todos:
    - Review PR #123 (Not completed)
    - Update documentation (Completed)
    - Setup test environment (Not completed)
    

    Listing all TODOs execute Listing all TODOs Listing all TODOs

  3. Updating a Todo

    Prompt: "Mark the todo about PR review as completed"
    Response: Updated "Review PR #123" to completed
    
  4. Deleting a Todo

    Prompt: "Delete the todo about documentation"
    Response: Successfully deleted "Update documentation"
    
  5. Filtering Todos

    Prompt: "Show me only completed todos"
    Response: Completed todos:
    - Review PR #123
    

Next Steps and Improvements

Potential enhancements for the project:

  1. Authentication

    • Add user authentication
    • Implement role-based access
  2. Advanced Features

    • Due dates for todos
    • Categories/tags
    • Priority levels
  3. Performance

    • Caching
    • Database optimization
    • Rate limiting
  4. Testing

    • Unit tests
    • Integration tests
    • Load testing

Troubleshooting

Common Issues and Solutions

  1. Server Connection Issues

    • Verify the server is running on port 3000
    • Check VS Code settings for correct server URL
    • Ensure no firewall blocking the connection
  2. Tool Registration Problems

    Error: Tool 'createTodo' not found
    Solution: Check if server is properly initializing tools in server.js
    
  3. Schema Validation Errors

    • Ensure todo items match the required schema
    • Check Zod validation rules in tool implementations
    • Verify JSON payload format
  4. Real-time Updates Not Working

    • Confirm SSE (Server-Sent Events) connection is established
    • Check browser console for connection errors
    • Verify StreamableHTTP transport configuration

Source Code Reference

Key implementation files:

Conclusion

We’ve successfully built a fully functional MCP-compatible Todo server that:

  • Implements CRUD operations
  • Maintains persistent storage
  • Provides real-time updates
  • Integrates seamlessly with VS Code

This implementation serves as a great starting point for building more complex MCP tools and understanding how AI models can interact with custom tools through the Model Context Protocol.

Resources

mcp-todo-server

This project is a simple MCP server that manages a todo list using the Model Context Protocol TypeScript SDK. It provides a RESTful API for creating, updating, and deleting todo items.

Project Structure

mcp-todo-server
β”œβ”€β”€ src
β”‚   β”œβ”€β”€ resources
β”‚   β”‚   └── todos.js
β”‚   β”œβ”€β”€ tools
β”‚   β”‚   β”œβ”€β”€ createTodo.js
β”‚   β”‚   β”œβ”€β”€ updateTodo.js
β”‚   β”‚   └── deleteTodo.js
β”‚   β”œβ”€β”€ config
β”‚   β”‚   └── db.js
β”‚   β”œβ”€β”€ utils
β”‚   β”‚   └── sessionManager.js
β”‚   └── server.js
β”œβ”€β”€ db.json
β”œβ”€β”€ package.json
β”œβ”€β”€ .gitignore
└── README.md

Installation

  1. Clone the repository:

    git clone https://github.com/prakashm88/mcp-todo-server.git
    cd mcp-todo-server
    
  2. Install the dependencies:

    npm install
    

Usage

To start the server, run:

npm start

The server will listen on port 3000.

API Endpoints

  • POST /mcp: Handles client-to-server communication for creating and managing todos.
  • GET /mcp: Retrieves server-to-client notifications.
  • DELETE /mcp: Terminates a session.

Database

The project uses lowdb to manage the todo items, stored in db.json.

Contributing

Contributions are welcome! Here’s how you can help:

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/my-new-feature
  3. Commit your changes: git commit -am 'Add new feature'
  4. Push to the branch: git push origin feature/my-new-feature
  5. Submit a pull request

You can also:

  • Report bugs by creating issues
  • Suggest improvements through discussions
  • Help improve documentation

Please read our Contributing Guidelines for more details.

Email or Website Summarization Browser Extension

Browser Extensions

Continuing on the journey of browser extension, lets see how a browser extension can help with Email or Website summarization using a Generative AI API integration.

I used NodeJS as my backend to create a API based on VertexAI for summarization. Here is the a documentation to create an API using VertexAI.

Now getting into extension development, basic development content is already available in the blog post here and the important part if the popup.js and content.js

popup.js

document.getElementById("summarizeEmail").addEventListener("click", () => {
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
chrome.tabs.sendMessage(tabs[0].id, { action: "getEmailContent" }, (response) => {
if (response.emailContent) {
chrome.runtime.sendMessage({ action: "summarizeEmail", emailContent: response.emailContent }, (response) => {
document.getElementById("summary").innerText = response.summary;
});
}
});
});
});

document.getElementById("summarizeText").addEventListener("click", () => {
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
chrome.scripting.executeScript(
{
target: { tabId: tabs[0].id },
function: getSelectedText,
},
(results) => {
if (results && results[0] && results[0].result) {
chrome.runtime.sendMessage({ action: "summarizeText", textContent: results[0].result }, (response) => {
document.getElementById("summary").innerText = response.summary;
});
}
}
);
});
});

function getSelectedText() {
return window.getSelection().toString();
}

content.js

// Function to inject the "AI Summary" button into Gmail
const injectAISummaryButton = () => {
const existingButton = document.getElementById("ai-summary-button");
if (existingButton) {
return; // Button already exists, do not add it again
}

const targetElement = document.querySelector(".AO");
if (targetElement) {
const aiSummaryButton = document.createElement("button");
aiSummaryButton.id = "ai-summary-button";
aiSummaryButton.innerText = "AI Summary";
aiSummaryButton.style.position = "absolute";
aiSummaryButton.style.top = "10px";
aiSummaryButton.style.right = "10px";
aiSummaryButton.style.zIndex = 10000;
aiSummaryButton.style.backgroundColor = "#007bff";
aiSummaryButton.style.color = "#ffffff";
aiSummaryButton.style.border = "none";
aiSummaryButton.style.padding = "10px";
aiSummaryButton.style.cursor = "pointer";

aiSummaryButton.addEventListener("click", () => {
const emailContent = getEmailContent();
if (emailContent) {
chrome.runtime.sendMessage({ action: "summarizeEmail", emailContent: emailContent }, (response) => {
showAISummaryOverlay(response.summary);
});
}
});

targetElement.prepend(aiSummaryButton);
}
};

// Function to extract email content from Gmail's DOM
const getEmailContent = () => {
const emailContentElement = document.querySelector(".AO"); // Selector for the email body content
return emailContentElement ? emailContentElement.innerText : "";
};

// Function to create and show the AI Summary overlay
const showAISummaryOverlay = (summary) => {
// Remove existing overlay if present
const existingOverlay = document.getElementById("ai-summary-overlay");
if (existingOverlay) {
existingOverlay.remove();
}

// Create overlay elements
const overlay = document.createElement("div");
overlay.id = "ai-summary-overlay";
overlay.style.position = "fixed";
overlay.style.top = "0";
overlay.style.left = "0";
overlay.style.width = "100%";
overlay.style.height = "100%";
overlay.style.backgroundColor = "rgba(0, 0, 0, 0.7)";
overlay.style.zIndex = 10000;
overlay.style.display = "flex";
overlay.style.alignItems = "center";
overlay.style.justifyContent = "center";

const content = document.createElement("div");
content.style.backgroundColor = "white";
content.style.padding = "20px";
content.style.borderRadius = "10px";
content.style.maxWidth = "500px";
content.style.boxShadow = "0 0 10px rgba(0, 0, 0, 0.5)";

const title = document.createElement("h2");
title.innerText = "AI Summary";
title.style.marginTop = "0";

const summaryText = document.createElement("p");
summaryText.innerText = summary;

const closeButton = document.createElement("button");
closeButton.innerText = "Close";
closeButton.style.marginTop = "20px";
closeButton.style.padding = "10px";
closeButton.style.backgroundColor = "#007bff";
closeButton.style.color = "white";
closeButton.style.border = "none";
closeButton.style.borderRadius = "5px";
closeButton.style.cursor = "pointer";

closeButton.addEventListener("click", () => {
overlay.remove();
});

// Append elements
content.appendChild(title);
content.appendChild(summaryText);
content.appendChild(closeButton);
overlay.appendChild(content);
document.body.appendChild(overlay);
};

// Inject the "AI Summary" button when the content script is loaded
injectAISummaryButton();

// Observe changes in the Gmail DOM to inject the button when necessary
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.type === "childList") {
injectAISummaryButton();
}
}
});

observer.observe(document.body, { childList: true, subtree: true });

// Listen for messages from the popup or background script
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === "getEmailContent") {
const emailContent = getEmailContent();
sendResponse({ emailContent: emailContent });
}
});

background.js

const API_ENDPOINT = "http://localhost:3001/secure/ai";

const getSummary = (content, callback) => {
const encodedContent = encodeURIComponent(content);

fetch(API_ENDPOINT + "/summary", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ prompt: "Please summarize the below uri encoded content. \n\n" + content }),
})
.then((response) => {
if (!response.ok) {
throw new Error("Network response was not ok " + response.statusText);
}
return response.json();
})
.then((data) => {
if (callback && typeof callback === "function") {
callback(data);
}
})
.catch((error) => {
console.error("Error: " + error);
if (callback && typeof callback === "function") {
callback({ summary: "Error fetching summary." });
}
});
};

chrome.runtime.onInstalled.addListener(() => {
chrome.contextMenus.create({
id: "summarizeText",
title: "AI Summary",
contexts: ["selection"],
});
});

chrome.contextMenus.onClicked.addListener((info, tab) => {
if (info.menuItemId === "summarizeText") {
const selectedText = info.selectionText;

console.log("Selected Text: " + selectedText);

getSummary(selectedText, (data) => {
chrome.scripting.executeScript({
target: { tabId: tab.id },
func: (summary) => {
const summaryElement = document.createElement("div");
summaryElement.style.position = "fixed";
summaryElement.style.bottom = "10px";
summaryElement.style.right = "10px";
summaryElement.style.backgroundColor = "white";
summaryElement.style.border = "1px solid black";
summaryElement.style.padding = "10px";
summaryElement.style.zIndex = 10000;
summaryElement.innerText = summary;
document.body.appendChild(summaryElement);
},
args: [data.message],
});
});
}
});

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === "summarizeEmail") {
const selectedText = request.emailContent;

console.log("Selected Email Content: " + selectedText);

getSummary(selectedText, (data) => {
sendResponse({ summary: data.message });
});

return true; // Will respond asynchronously.
}
});

Explanation on whats happening,

  1. Listen to the gmail page for the page loads, Once the page is loaded invoke the injectAISummaryButton in the content.js
  2. “AI Summary button will be injected at the top right corner of the page”
  3. A context menu is also added when a text is selected. The click event i registered in the popup.js.
  4. Upon selecting the text in any web page and clicking on the context menu. or clicking on the “AI Summary” on gmail page will send the data of the email over the api and show the summarized response to the user.

Refer the full chrome extension plugin at GitHub

Browser extension sample – Chrome/Edge – HttpRequestViewer

Browser Extensions

The Evolution of Browser Extensions: From Web Customization to Advanced Development Tools – Part 2
We discussed about The evolution of the Browser extensions in the previous post. Lets quick learn how to create a Chrome/Edge/Firefox extension. I have mentioned “Advanced development tools” in the title, but never got chance to explore those capabilities earlier. We will create a simple extension to explore the power of it.

Creating a browser extension has never been easier, thanks to the comprehensive documentation and support provided by browser vendors. Below, we’ll walk through the steps to create a simple extension for both Chrome and Microsoft Edge using Manifest V3. We will use this tool to print the list of HTTP requests that are fired in a given browser and list it in the page.

Basics of extensions:

Manifests – A manifest is a JSON file that contains metadata about a browser extension, such as its name, version, permissions, and the files it uses. It serves as the blueprint for the extension, informing the browser about the extension’s capabilities and how it should be loaded.

Key Components of a Manifest File:

Here are the key components typically found in a Manifest V3 file:

1. Manifest Version: There are different versions of the manifest file, with Manifest V3 being the latest and most widely adopted version. Manifest V3 introduces several changes aimed at improving security, privacy, and performance with lot of controversies around it. Read more about the controversies at Ghostery.
2. Name and Version: These fields define the name and version of the extension. Choose a unique name and version. An excellent guide of version semantics is available here.
3. Description: A short description of the extension’s functionality.
4. Action: Defines the default popup and icon for the browser action (e.g., toolbar button).
5. Background: Specifies the background script that runs in the background and can handle events like network requests and alarms.
6. Content Scripts: Defines scripts and stylesheets to be injected into matching web pages.
7. Permissions: Lists the permissions the extension needs to operate, such as access to tabs, storage, and specific websites.
8. Icons: Specifies the icons for the extension in different sizes. For this post I created a simple icon using Microsoft Designer. I gave a simple prompt with the description above and I got the below image. Extension requires different sizes for showing it in different places. I used Chrome Extension Icon Generator and generated different sizes as needed.

Β  Β  Β 

9. Web Accessible Resources: Defines which resources can be accessed by web pages.

Create a project structure as follows:

HttpRequestViewer/
|-- manifest.json
|-- popup.html
|-- popup.js
|-- background.js
|-- history.html
|-- history.js
|-- popup.css
|-- styles.css
|-- icons/
    |-- icon.png
    |-- icon16.png
    |-- icon32.png
    |-- icon48.png
    |-- icon128.png

Manifest.json

{
  "name": "API Request Recorder",
  "description": "Extension to record all the HTTP request from a webpage.",
  "version": "0.0.1",
  "manifest_version": 3,
  "host_permissions": [""],
  "permissions": ["activeTab", "webRequest", "storage"],
  "action": {
    "default_popup": "popup.html",
    "default_icon": "icons/icon.png"
  },
  "background": {
    "service_worker": "background.js"
  },
  "icons": {
    "16": "icons/icon16.png",
    "32": "icons/icon32.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  },
  "content_security_policy": {
    "extension_pages": "script-src 'self'; object-src 'self';"
  },
  "web_accessible_resources": [{ "resources": ["images/*.png"], "matches": ["https://*/*"] }]
}

popup.html
We have two options with the extension.

1. A button with record option to start recording all the HTTP requests
2. Link to view the history of HTTP Requests recorded

<!DOCTYPE html>
<html>
  <head>
    <title>API Request Recorder</title>

    <link rel="stylesheet" href="popup.css" />
  </head>
  <body>
    <div class="heading">
      <img class="logo" src="icons/icon48.png" />
      <h1>API Request Recorder</h1>
    </div>
    <button id="startStopRecord">Record</button>

    <div class="button-group">
      <a href="#" id="history">View Requests</a>
    </div>

    <script src="popup.js"></script>
  </body>
</html>

popup.js
Two event listeners are registered for recording (with start / stop) and viewing history.
First event is used to send a message to the background.js, while the second one instructs chrome to open the history page in new tab.

document.getElementById("startStopRecord").addEventListener("click", () => {
  chrome.runtime.sendMessage({ action: "startStopRecord" });
});

document.getElementById("history").addEventListener("click", () => {
  chrome.tabs.create({ url: chrome.runtime.getURL("/history.html") });
});

history.html

 
<!DOCTYPE html>
<html>
  <head>
    <title>History</title>
    <link rel="stylesheet" href="styles.css" />
  </head>
  <body>
    <h1>History Page</h1>
    <table>
      <thead>
        <tr>
		  <th>Method</th>
          <th>URL</th>
          <th>Body</th>
        </tr>
      </thead>
      <tbody id="recorded-data-body">
        <!-- Data will be populated here -->
      </tbody>
    </table>
    <script src="history.js"></script>
  </body>
</html>

history.js
Requests background.js to “getRecordedData” and renders the result in the html format.

document.addEventListener("DOMContentLoaded", () => {
  chrome.runtime.sendMessage({ action: "getRecordedData" }, (response) => {
    const tableBody = document.getElementById("recorded-data-body");
    response.forEach((record) => {
      const row = document.createElement("tr");
      const urlCell = document.createElement("td");
      const methodCell = document.createElement("td");
      const bodyCell = document.createElement("td");

      urlCell.textContent = record.url;
      methodCell.textContent = record.method;
      bodyCell.textContent = record.body;

      row.appendChild(methodCell);
      row.appendChild(urlCell);
      row.appendChild(bodyCell);
      tableBody.appendChild(row);
    });
  });
});

background.js
Background JS works as a service worker for this extension, listening and handling events.
The background script does not have access to directly manipulate the user page content, but can post results back for the popup/history script to handle the cosmetic changes.

let isRecording = false;
let recordedDataList = [];

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  console.log("Obtined message: ", message);
  if (message.action === "startStopRecord") {
    if (isRecording) {
      isRecording = false;
      console.log("Recording stopped...");
      sendResponse({ recorder: { status: "stopped" } });
    } else {
      isRecording = true;
      console.log("Recording started...");
      sendResponse({ recorder: { status: "started" } });
    }
  } else if (message.action === "getRecordedData") {
    sendResponse(recordedDataList);
  } else {
    console.log("Unhandled action ...");
  }
});

chrome.webRequest.onBeforeRequest.addListener(
  (details) => {
    if (isRecording) {
      let requestBody = "";
      if (details.requestBody) {
        if (details.requestBody.formData) {
          requestBody = JSON.stringify(details.requestBody.formData);
        } else if (details.requestBody.raw) {
          requestBody = new TextDecoder().decode(new Uint8Array(details.requestBody.raw[0].bytes));
        }
      }
      recordedDataList.push({
        url: details.url,
        method: details.method,
        body: requestBody,
      });
      console.log("Recorded Request:", {
        url: details.url,
        method: details.method,
        body: requestBody,
      });
    }
  },
  { urls: [""] },
  ["requestBody"]
);

Lets load the Extension

All set, now lets load the extension and test it.

  • Open Chrome/Edge and go to chrome://extensions/ or edge://extensions/ based on your browser.
  • Enable “Developer mode” using the toggle in the top right corner.
  • Click “Load unpacked” and select the directory of your extension.

Load extensionupload extension

  • Your extension should now be loaded, and you can interact with it using the popup.
  • When you click the “Record” button, it will start logging API requests to the console.

  • Click the “Record” button again and hit the “View requests” link in the popup to view the history of APIs.

I have a sample page (https://itechgenie.com/demos/apitesting/index.html) with 4 API calls, which also loads images based on the API responses. You could see all the API requests that is fired from the page including the JS, CSS, Images and API calls.


Now its up to the developers imagination to build the extension to handle these APIs request and response data and give different experience.

Code is available in GitHub at HttpRequestViewer

The Evolution of Browser Extensions: From Web Customization to Advanced Development Tools

Browser Extensions

It’s been a while that I published a post. A week before, I created a new Chrome extension and shared with my team and noticed the new developers didn’t have knowledge on how powerful the browser extensions can be. It pushed me to write a short post about the history and power of browser extensions.

A Brief History

Browser extensions have dramatically transformed how users interact with the internet, offering a plethora of customization options and functionalities that enhance productivity, security, streamline workflows and user experience. These small software modules, integrated into web browsers like Chrome, Edge, Firefox etc., enable users and developers to tailor their browsing experience, automate tasks, and access additional features not available in standard browser installations. The evolution of browser extensions has marked a significant milestone in web development, fostering a community of developers who continuously innovate and simplify complex tasks.

The Early Days

Browser extensions trace their origins back to the early days of web browsers. The first notable implementation was by Internet Explorer in the late 1990s, which allowed for basic plugins to extend browser capabilities. Early days of these extensions allowed developers to add custom menu bars(also known as Browser Bands and Communication Bands), context menu options for seamless integration with extensions. CricInfo cricket score ticker was a popular toolbar that I have used in the early days. Internet download manager extension is one another toolbar which allowed the download of audios and videos, changed the life of lot of Dial-up connection users.

The Rise of Firefox

However, it was Mozilla Firefox that popularized the concept of extensions by providing a dedicated platform for the developers to create and submit the add-ons. Themes and skins are a fun part of extensions world. Greasemonkey,one of the early add-ons, allowed users to write custom code on top of extensions was a boon for customization. I used my first AdBlocker script from UserScripts.org installed using Greasemonkey. This Add-on allowed me to create customize my own scripts without taking the hassle of publishing. Firefox’s various components like Add-ons, Extensions, and Plugins (Flash, Java, SilverLight, etc.) eventually evolved into standardized extensions.

The Chrome Era

Then came the days of Chrome. Google Chrome, introduced in 2008, revolutionized the extension landscape by offering streamlined APIs and a dedicated web store for its extensions. This facilitated easier development and distribution of extensions, leading to a surge in their popularity. The Chrome Web Store, launched in 2010, became a central hub for users to discover and install extensions, further solidifying their importance in the web ecosystem.

Extensions like Web Developer and React Developer Tools provide essential utilities for debugging, testing, and optimizing web applications. By leveraging browser APIs, developers can create tools that integrate seamlessly into their development environment, automating repetitive tasks and offering real-time insights into application performance.

Essential Extensions for Users

Some of the most used extensions include:
AdBlock / AdBlock Plus / uBlock Origin: Blocks ads on websites, improving load times and reducing clutter.
Microsoft Editor / Grammarly: Enhances writing by checking grammar, spelling, and style.
Honey: Automatically finds and applies coupon codes at checkout.
Bitwarden / LastPass: A password manager that stores and auto-fills passwords securely.
Momentum: Replaces the new tab page with a personal dashboard featuring a to-do list, weather, and inspirational quotes.
Dark Reader: Applies a dark theme to websites, reducing eye strain.

Must-Have Extensions for Developers

From a developer’s perspective, extensions are a boon. Some popular developer-friendly extensions are:
TamperMonkey – Modify website layouts, add/remove features, or automate actions – Alternative to Greasemonkey supporting userscripts.
React Developer Tools / Vue.js / – Provides debugging and inspection tools for React and Vue.js applications.
Redux DevTools – Allows developers to inspect every state and action payload for Redux applications.
Postman – A powerful tool for testing APIs by making HTTP requests.
JSON Viewer – Formats JSON data to make it more readable.
XPath Helper – Helps to find XPath expressions for elements on a webpage.
ColorZilla – Advanced color picker and gradient generator.
WhatFont – Identifies fonts used on a webpage.

We will see how to create a simple browser extension in the next post –Browser extension sample – Chrome/Edge – HttpRequestViewer

Best operating system to run older configuration PC – Lubuntu

Choosing Lubuntu:

LatelyI was pushed to use my 7 year old PC for an emergency which had Windows 7 in it. It was very tough for me to use the old PC as compared to my latest one, even though it had 1.5GB or RAM and Dual Core Processor. I even had thoughts to install Windows XP to achive better performance.

Before starting anything, did some research to if I can find a Linux distro that could consume very less resources.Β I was always fond of the Ubuntu distros for as long as 10 years.
I was very fond of receiving the Ubuntu distro CDs that was transported freely to my remote village earlier in 2004 or 2005.
With that fondness I searched the latest distro and then came the Lubuntu (Lite-Ubuntu). I installed it in my PC with dual boot configuration. I should say that the performance is very impressive.

About Lubuntu:

Lubuntu is a good operating system for many old computers, but not for all of them.
Some computers have too little horsepower or memory. A rule of thumb is that the computer should not be more than 10 years old.
Lubuntu is recognized as a member the Ubuntu family by the developers of Ubuntu and has the same release nomenclature.

System Requirements, as per Lubuntu site:

We have done many tests and we've found out that Lubuntu can be installed on a Pentium II or Celeron system with 128 MB of RAM, but such a system would not perform well enough for daily use.
With 256MB - 384MB of RAM, the performance will be better and the system will be more usable.
With 512MB of RAM, you don't need to worry much.

If you like to use the system for normal activities like general browsing, viewing mails etc., the above config would be great.
But if you intend to use it for video watching like using Youtube, I must warn you, the browser you use could eat up all all memory.

Differences between Lubuntu and Ubuntu:

1. Different Desktop Environment (DE) – Lubuntu uses LXDE (Lightweight X11 Desktop Environment) while Ubuntu uses Unity as the default DE.

  • Both Lubuntu and Ubuntu share two major important things: same Core System and same Repositories.
  • Lubuntu and Ubuntu belong to the same family and talking about each as totally different two systems is not correct since they have some things in common.
  • They even share the same Forum Area and share many Wiki Pages. Other than that, they are the same.
  • The DE is what makes Lubuntu a lightweight OS, and of course the selected applications too because we make sure to use the lightest applications which are not resource hungry.
  • However, you are still free to use any application available in Ubuntu’s repositories, as long as your computer can run it.

2. Different Default Applications

Lubuntu Application Function
Xpad Stickies
Evince PDF Viewer
Gnumeric Spreadsheet
Abiword Docs
Simple-scan Scanner
Gnome-disk-utility Partition Editor
Light-locker Screensaver
Guvcview Webcam Utility
Gucharmap Character Map
Scrot Screenshot
Hardinfo System info
Mtpaint Image Editing
Xfburn Cd Burning
Pcmanfm File Manager
Gcalculator Calculator
Audacious Audio Player
Gnome-mplayer Video Player
Transmission Torrent
Pidgin Instant Messaging
Sylpheed Email Client
Mozilla Firefox Web browser
Leafpad Editor
File-roller (De)Compress files
Lxterminal Terminal
Gpicview Image Viewer

Download Lubuntu:

http://lubuntu.me/downloads/
https://help.ubuntu.com/community/Lubuntu/GetLubuntu

Official Websites:

http://lubuntu.me/
http://lubuntu.net/
https://wiki.ubuntu.com/Lubuntu

Installing OpenStack on AWS

1. Prerequisites – Minimal requirements for hosting in AWS, but not limited to:

  • Ubuntu Server 14.04.3 LTS – 64bit
  • Minimum 2VCPU – Cores
  • Minimum 8 GB RAM for just OpenStack (m4.large), Minimum 16 GB RAM for Sahara and clustering (m4.xlarge)
  • Atleast 40 GB of diskspace

2. Install Ubuntu if you dont have one

3. Verify installed version using

lsb_release -d
free -m
df -h

4.Β Update to the latest binaries

sudo apt-get update

5.Β Create a SUDO user – alternatively you can use the /devstack/tools/create-stack-user.sh to create a user after step 8

sudo -i
adduser stack			
	Enter new UNIX password:
	Retype new UNIX password:
	passwd: password updated successfully
	Changing the user information for username
	Enter the new value, or press ENTER for the default
	Full Name []:stack
	Room Number []:
	Work Phone []:
	Home Phone []:
	Other []:
	Is the information correct? [Y/n] Y

6. Add user to SUDOERs group

usermod -aG sudo stack 
echo "stack ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers

7.Β Switch to the new stack user

su - stack
check if the user can do sudo operations without password prompts
sudo ls -la /root

8. Switch to user home and install GIT and checkout devstack

cd ~
sudo apt-get install git
git clone https://github.com/openstack-dev/devstack

9.Β Configure devstack – update local.config and move to /devstack

cd /devstack
cp sample/local.conf .

*Β Update the passwords for the accounts

ADMIN_PASSWORD=St@ckNewPwd1
DATABASE_PASSWORD=St@ckNewPwd1
RABBIT_PASSWORD=St@ckNewPwd1
SERVICE_PASSWORD=St@ckNewPwd1

*Β If you are running on a physical machine with a static IP you can update the following property. On AWS its better to leave it commented as the local IP will be changed on each restart, unless the Elastic IP is assigned to the instance

HOST_IP=172.31.26.172

* And add the following line at the end of the file. These entries will add the Sahara plugin (Data Processing) in OpenStack UI

echo "enable_plugin sahara git://github.com/openstack/sahara" >> local.conf
echo "enable_plugin sahara-dashboard git://github.com/openstack/sahara-dashboard" >> local.conf
echo "enable_plugin ceilometer git://github.com/openstack/ceilometer" >> local.conf

10. Start the stack services

./stack.sh

* This takes up sometime and logs will be available at /opt/stack/logs. On successful completion you will find details something similar as below.

=========================
DevStack Component Timing
=========================
Total runtime         1169
run_process            57
test_with_retry         3
apt-get-update          3
pip_install           299
restart_apache_server  10
wait_for_service       11
git_timed             244
apt-get                69
=========================

This is your host IP address: 172.31.26.172
This is your host IPv6 address: ::1
Horizon is now available at http://172.31.26.172/dashboard
Keystone is serving at http://172.31.26.172/identity/
The default users are: admin and demo
The password: St@ckNewPwd1
stack@ip-172-31-26-172:~/devstack$

11.Β To access the dashboard hit http://172.31.26.172/dashboard in browser (with the ip as displayed in the above step). If you are running in local PC you can directly access with the above url. But if you are running on AWS, this is the internal IP and will not be available to the outside world. In this case, allow HTTP access on 80 port for the outside world and access the service with the Public IP or DNS hostname allocated to your instance. This in my case http://54.23.123.43/dashboard.

Enable Registration in WordPress

In WordPress custom installations, the option for Registration of new users is disabled by default. To enable the registrations and allow logins for public you can follow the below steps.

Single site installation:

As soon as the installation is completed go to the WordPress Dashboard -> Settings

You will find an option Allow new registrations, you may need to select the Default Role for the newly registered members.

wp-user-registeration-single-site

Multi-site/Network installation:

In case if you have installed WordPress in Multi site mode go to the Network Admin -> Settings and select the options under Allow new registrations section.

wp-user-registeration-multi-site

Once the registration is enabled it may be needed to add login options at places to find it easily. Follow these methods to do the same.

Method 1:

Add Meta widget in the sidebars or in footers. Select Appearance -> Widgets and select the Meta Widget.Β  Note that the menu will be available in Individual Site Dashboard, not in the Network Admin dashboard in case of Multi-site installation.

wp-user-login-widget

 

Method 2:

Add the login link in your Posts/Pages and it should be in the pattern, http(s)://HOSTNAME(:PORT)/(SUBDOMAINS)/wp-login.php.

Examples:

  1. http://itechgenie.com/myblog/wp-login.php – MultiSite installation
  2. http://itechgenie.com/wp-login.php – SingleSite installation

Maven and Cloud Foundry Integration

Cloud Fountry provides as easy integration plugins to move the build packages to its servers through Maven. Here is the sample configuration.

Add Servers to settings.xml

<settings> 
	...
	<servers>
		... 
		<server>
			<id>cloud-foundry-credentials</id>
			<username>cf_user_id_you_created</username>
			<password>cf_password_you_created</password>
		</server>
	</servers> 
</settings>

You can encrypt you password in MAVEN settings. Check out here on how to do it.

Add Dependency and Plugin settings in pom.xml

</project>
	...	
	<build>
		...
		<plugins>
				<groupId>org.cloudfoundry</groupId>
				<artifactId>cf-maven-plugin</artifactId>
				<version>1.1.3</version>
				<configuration>
					<server>cloud-foundry-credentials</server>
					<target>https://url.to.cloud.foundry.com</target>
					<memory>512</memory>
					<appname>application-name</appname>
					<org>ORG_NAME</org>
					<space>SPACE_NAME</space>
					<instances>1</instances>
				</configuration>
				<executions>
					<execution>
						<phase>package</phase>
						<goals>
							<goal>push</goal>
						</goals>
					</execution>
				</executions>
		</plugins>
	</build>
</project>

Thats it !!. Build you project using Maven and check if you application gets deployed into your Cloud Foundry server.

Activiti REST in Weblogic

When trying to deploy the Activiti REST WAR in WebLogic servers, we found that the requests are reaching the server, but are filtered by the WebLogic server. To make sure your requests reach the Application bypassing the WebLogic filters, you must set the enforce-valid-basic-auth-credentials property in config.xml -> security-configuration to false

...
<enforce-valid-basic-auth-credentials>false</enforce-valid-basic-auth-credentials>
</security-configuration>

From WebLogic servers 9.2 and above, client requests that use the HTTP BASIC authentication must pass WebLogic Server authentication, even if access control is not enabled on the target resource.

The setting of the Security Configuration MBean flag enforce-valid-basic-auth-credentials determines this behavior. (The DomainMBean can return the new Security Configuration MBean for the domain.) It specifies whether or not the system should allow requests with invalid HTTP BASIC authentication credentials to access unsecured resources.

More info at “Understanding BASIC Authentication with Unsecured Resources“.