Catalyst App within Zoho CRM by extension using Zoho Sigma Part 2
Our whole working application was ready and hosted on Catalyst in the last part. In this blog, we will create a plugin out of it with Zoho Sigma and install it in our CRM as a web view, adding authentication to it with permission management. Let’s get started without any delay. What is Zoho Sigma?...
Published on February 22, 2024
Our whole working application was ready and hosted on Catalyst in the last part. In this blog, we will create a plugin out of it with Zoho Sigma and install it in our CRM as a web view, adding authentication to it with permission management. Let’s get started without any delay.
What is Zoho Sigma?
Zoho Sigma serves as an extension development platform, empowering developers to create and host extensions for various Zoho applications. These extensions, or software add-ons, enhance the functionality of Zoho services. Developers can craft extensions using languages like HTML, JavaScript, and CSS. Sigma offers tools for managing server-side operations, enabling seamless integration with Zoho apps and third-party applications.
Creating a Zoho Sigma extension.
Before proceeding, make sure you have access to Sigma. This is how your dashboard will look like after logging in. Click on the New Extension Button on the top right.
Then add the information about the extension and select Zoho CRM as the service for which we create an extension.
Sigma allows you to create native as well as third-party extensions. For our purpose, our extension will be counted as a third party even though it is hosted on Catalyst which is part of Zoho. This is because if you are working on a real project and a development team is developing on Catalyst, the deployment pipeline would already be set. We can just provide a link to host as a web tab and the headache of updating the extension would go away. This keeps things isolated and automated.
Let us see how we can create a web tab which is a module of extension. You will find it inside the module page of the extension.
We are creating the web tab as a widget and Sigma requires you to create a connected application first. Move to Connected Apps in the left menu bar and fill in the required information.
You have to paste the URL where our Catalyst project is hosted. Finally, click on save, and now you can create a web tab widget as follows.
With this, our widget is ready and now, let us install it in our CRM. This is fairly simple thanks to the simplicity of Sigma.
Voila!
Installing the plugin is just as straightforward as publishing it. We will copy the link and paste it into the browser. If you are logged in to the CRM, you will be prompted to the install extension screen automatically. This is how the window will look.
For now, install with default settings and we will show how to configure the permissions for the extension.
And our extension is finally installed.
To verify the installation of the extension, you will see it in the tab section in the upper right corner.
Extension Authentication.
In the previous part, we had already added authentication but that was for our backend but for the frontend of extensions, we also need some authentication mechanisms. Since we are using CRM data in our extensions, authentication becomes necessary on the front end as well. Sigma provides us the option to add Auth0 to our extensions. In this section, we will see its implementation. This is what our connected app section looks like.
In the last part, we had self client for our authentication. In this section, we will register our plugin’s front end as a client-side application from the Zoho API console.
You can add the relevant details for your project and click on CREATE to get your client secret and client ID.
We will make some changes to our front-end code from the last blog.
import React, { useState, useEffect } from "react";
interface Policy {
id: number;
Name: string;
dateOfIssue: string;
dateOfExpiration: string;
type: string;
amount: number;
annualPremium: number;
state: string;
}
interface Contact {
Full_Name: string;
id: number;
}
const App = () => {
const url = new URL(window.location.href);
const scopeCode = url.searchParams.get("code") ?? "";
const [token, setToken] = useState("");
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [user, setUser] = useState({} as any);
const [policies, setPolicies] = useState<Policy[]>([]);
const [contacts, setContacts] = useState<Contact[]>([]);
const [formData, setFormData] = useState<Policy>({
Name: "",
dateOfIssue: "",
dateOfExpiration: "",
type: "",
amount: 0,
annualPremium: 0,
state: "",
id: 0,
});
useEffect(() => {
if (isAuthenticated) {
fetchData();
}
}, [isAuthenticated]);
useEffect(() => {
const fetchToken = async () => {
try {
const tokenResponse = await fetch(
"/server/policy_tracker_function/token",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ code: scopeCode }),
}
);
const tokenData = await tokenResponse.json();
if (tokenData.token) {
setToken(tokenData.token);
const userResponse = await fetch(
"/server/policy_tracker_function/user",
{
method: "GET",
headers: {
"Content-Type": "application/json",
authorization: `${tokenData.token}`,
},
}
);
const userData = await userResponse.json();
setUser(userData);
setIsAuthenticated(true);
}
} catch (error) {
console.error("Error fetching token:", error);
}
};
if (scopeCode) {
fetchToken();
}
}, [scopeCode]);
const fetchData = async () => {
try {
const policiesResponse = await fetch(
"/server/policy_tracker_function/policies",
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
);
const policiesData = await policiesResponse.json();
setPolicies(policiesData);
const contactsResponse = await fetch(
"/server/policy_tracker_function/contacts",
{
method: "GET",
headers: {
"Content-Type": "application/json",
authorization: `${token}`,
},
}
);
const contactsData = await contactsResponse.json();
setContacts(contactsData);
} catch (error) {
console.error("Error fetching data:", error);
}
};
const handleChange = (e: any) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
const handleSubmit = async (e: any) => {
e.preventDefault();
try {
await fetch("/server/policy_tracker_function/policies", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(formData),
});
setFormData({
Name: "",
dateOfIssue: "",
dateOfExpiration: "",
type: "",
amount: 0,
annualPremium: 0,
state: "",
id: 0,
});
fetchData();
} catch (error) {
console.error("Error adding policy:", error);
}
};
const redirectUrl =
"https://accounts.zoho.com/oauth/v2/auth?scope=ZohoCRM.users.READ,ZohoCRM.modules.contacts.READ&client_id=1000.OPCE1PD7P6UTQ124J96JFASMQLI68Q&response_type=code&access_type=online&redirect_uri=https://policytracker-841309168.development.catalystserverless.com/app/";
return (
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
height: "100vh",
width: "100vw",
}}
>
<a
href={redirectUrl}
style={{
display: isAuthenticated ? "none" : "block",
backgroundColor: "blue",
color: "white",
padding: "10px",
borderRadius: "10px",
cursor: "pointer",
textDecoration: "none",
}}
>
Login With Zoho
</a>
{isAuthenticated && (
<>
<h1>Insurance Policies</h1>
<div>
{policies?.length === 0 && (
<p>No policies found. Add a new policy</p>
)}
{policies?.map((policy) => (
<div
key={policy.id}
style={{
border: "1px solid black",
padding: "10px",
margin: "10px",
borderRadius: "10px",
width: "fit-content",
}}
>
<h3>{contacts?.find((c) => c.id === policy.id)?.Full_Name}</h3>
<p>
<strong>Date of Issue:</strong> {policy.dateOfIssue}
</p>
<p>
<strong>Date of Expiration:</strong> {policy.dateOfExpiration}
</p>
<p>
<strong>Type:</strong> {policy.type}
</p>
<p>
<strong>Amount:</strong> {policy.amount} $
</p>
<p>
<strong>Annual Premium:</strong> {policy.annualPremium} $
</p>
<p>
<strong>State:</strong> {policy.state}
</p>
<br />
</div>
))}
</div>
<h2>Add Policy</h2>
<form onSubmit={handleSubmit}>
<label>
Name:
<select name="id" onChange={handleChange} value={formData.id}>
<option value=""></option>
{contacts?.map((contact) => (
<option key={contact.id} value={contact.id}>
{contact.Full_Name}
{console.log(contact.Full_Name)}
</option>
))}
</select>
</label>
<br />
<br />
<label>
Date of Issue:
<input
type="date"
name="dateOfIssue"
onChange={handleChange}
value={formData.dateOfIssue}
/>
</label>
<br />
<label>
Date of Expiration:
<input
type="date"
name="dateOfExpiration"
onChange={handleChange}
value={formData.dateOfExpiration}
/>
</label>
<br />
<label>
Type:
<select onChange={handleChange} name="type" id="type">
<option value=""></option>
<option value="Term">Term</option>
<option value="Whole">Whole</option>
</select>
</label>
<br />
<label>
Amount:
<input
type="number"
name="amount"
onChange={handleChange}
value={formData.amount}
/>
</label>
<br />
<label>
Annual Premium:
<input
type="number"
name="annualPremium"
onChange={handleChange}
value={formData.annualPremium}
/>
</label>
<br />
<label>
State:
<select onChange={handleChange} name="state" id="state">
<option value="CA">California</option>
<option value="TX">Texas</option>
<option value="FL">Florida</option>
<option value="NY">New York</option>
<option value="PA">Pennsylvania</option>
<option value="IL">Illinois</option>
<option value="OH">Ohio</option>
<option value="GA">Georgia</option>
<option value="NC">North Carolina</option>
<option value="MI">Michigan</option>
</select>
</label>
<br />
<button type="submit">Add Policy</button>
</form>
</>
)}
</div>
);
};
export default App;
Here, we have created a login button that will redirect you to the Zoho authentication page where you can log in with your Zoho credentials. After a successful login, you will be redirected to the redirect URL that was specified during client registration along with grant_code, which can be used to get your access token similarly.
The frontend will take this grant_code and request our serverless function for the token. We will also be fetching the user details from this token.
Let us create the respective endpoints in the backend for this purpose.
const express = require("express");
const axios = require("axios");
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware for parsing JSON requests
app.use(express.json());
// Zoho CRM API configuration
const ZOHO_API_ENDPOINT =
"https://www.zohoapis.com/crm/v6/Contacts?fields=Full_Name";
let policyData = [];
// Function to fetch contacts from Zoho CRM
const fetchContacts = async (token) => {
try {
const response = await axios.get(ZOHO_API_ENDPOINT, {
headers: {
Authorization: `Bearer ${token}`,
},
});
console.log(response.data.data);
return response.data.data;
} catch (error) {
throw error;
}
};
app.post("/token", async (req, res) => {
clg;
const grantCode = req.body.code;
const formData = new FormData();
formData.append("grant_type", "authorization_code");
formData.append("client_id", "your_client_id");
formData.append(
"client_secret",
"your_client_secret"
);
formData.append(
"redirect_uri",
"https://policytracker-841309168.development.catalystserverless.com/app/"
);
formData.append("code", grantCode);
await axios
.post("https://accounts.zoho.com/oauth/v2/token", formData, {
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
})
.then((response) => {
console.log(response.data);
res.json({ token: response.data.access_token });
})
.catch((error) => {
console.error("Error fetching contacts from Zoho CRM:", error);
res.status(500).send("Error fetching contacts from Zoho CRM");
});
});
app.get("/currentUser", async (req, res) => {
const response = await axios.get(
ZOHO_API_ENDPOINT,
{
headers: {
Authorization: `Bearer ${req.headers.authorization}`,
},
}
);
const userData = response.data.users[0];
res.json(userData);
});
app.get("/policies", async (req, res) => {
try {
res.json(policyData);
} catch (error) {
res.status(500).send("Error fetching contacts from Zoho CRM");
}
});
app.post("/policies", (req, res) => {
const newPolicy = req.body;
policyData.push(newPolicy);
res.status(201).json(newPolicy);
});
app.get("/contacts", async (req, res) => {
const authorizationToken = req.headers.authorization;
try {
const contacts = await fetchContacts(authorizationToken);
res.json(contacts);
} catch (error) {
res.status(401).send("Error fetching contacts from Zoho CRM");
}
});
// Start the server
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});
module.exports = app;
Compared to the last blog, we are now getting the token from frontend to make requests to our Zoho CRM. We have introduced two new REST API endpoints.
/currentUser: Responsible for getting the current user who made the request.
/token: For generation of access token.
The next section is managing data level and view level permissions. We will test the whole flow all together!
Managing extension permissions.
View level permissions
As we have successfully created and installed our extension, we need to know more about the permissions. Extension visibility and functionality depend on the needs of the organization. Sometimes, an extension is required for every team while in other cases, some teams or users do not have to see or use the extension. This is where extension permissions come into play.
To test this properly, we have created two additional profiles in our CRM which can be seen below.
We also created a testing user that currently belongs to the Insurance Analysts profile.
The view level permissions will work as follows.
For the profiles who are allowed to see the policy manager, only for them, the policy manager tab will be visible in the nav bar.
Let us head back to the extension settings, go to the change permission page, and permit the Insurance Analysts profile.
Now, if you access CRM from the test account, you will see the Policy Manager extension there as well.
In the test account
Profile: Insurance Analysts.
Let us try removing permission or assigning this profile to another group and coming back to the CRM. Our test user belongs to the Email Team now.
We will just refresh the page where our test user is logged in and see the results reflected!
The web tab is gone!
Data level permissions.
With our API, we can also implement data-level permissions as sometimes you do not want specific data visible to all users. For example, an admin can view everything but some teams will have restricted information access. We already have two types of user profiles: Administrator and Insurance analysts.
Let us define the permission rights for these profiles
Adminstrator: Has all viewing rights.
Insurance analysts: Cannot view Annual Premium or create Annual Premium
To make things clear, we have a diagram.
When the Administrator is logged in, he will be able to see both amounts for policies but our insurance analyst profiles will only be able to see one. To make this work, we will introduce some more changes to our code.
Frontend
const policiesResponse = await fetch(
"/server/policy_tracker_function/policies",
{
method: "GET",
headers: {
"Content-Type": "application/json",
authorization: `${token}`,
userProfile: user.profile.name,
},
}
);
///previous code
{user.profile.name !== "Insurance Analyst" ? (
<label>
Annual Premium:
<input
type="number"
name="annualPremium"
onChange={handleChange}
value={formData.annualPremium}
/>
</label>
) : (
""
)}
Backend
app.get("/policies", async (req, res) => {
const userProfile = req.headers.userProfile;
try {
if (userProfile === “Insurance Analyst”) {
res.json(
policyData.map((policy) => ({ …policy, annualPremium: “*****” }))
);
return;
}
res.json(policyData);
} catch (error) {
res.status(500).send(“Error fetching contacts from Zoho CRM”);
}
});
We are sending the userProfile header which is simply the name of user’s profile we get from the CRM and in the backend, we are hiding the annualPremium information from the Insurance Analyst profile. Make sure you deploy everything with catalyst deploy
Testing everything.
Moments of truth, let us test our auth and data level permissions!
The account having the profile name as Insurance Analyst.
Now with the Administrator.
As you can see above, only the relevant data is visible to the respective user!
Conclusion
In this final blog, we discovered different aspects of extensions like authentication, installation of a Sigma plugin along permission management inside the CRM. We hope that this has made your Zoho concepts even stronger!