Hello Experts,
I have configured Jira’s external OAuth with SurveySparrow. The app consists of three modules: Project Page
, Issue Context
, and Issue Panel
. I have successfully initiated OAuth on the Project Page
, and it works as expected.
Additionally, I have added an event handler to listen for Jira events. Based on these events, I need to make an API call to the SurveySparrow provider. However, when I attempt to make the API call, it throws a 401 Unauthorized
error.
I would like to know if it is possible to use an external OAuth provider to make API calls inside an event handler. If yes, how can I ensure proper authentication for these calls?
Could someone please help me resolve this issue or provide guidance on the best approach to handle external OAuth for making API calls in this scenario?
Thank you in advance!
FYI Adding the event Handler :
// src/eventHandler.js
import api, { storage, route } from '@forge/api';
const STRINGS = {
EVENT_TYPES: {
ISSUE_CREATED: "created",
ISSUE_UPDATED: "updated",
ISSUE_DELETED: "deleted"
},
PROVIDERS: {
SURVEYSPARROW: "surveysparrow",
SURVEYSPARROW_API: "surveysparrow-api",
},
JIRA_EVENTS: {
CREATED: "avi:jira:created:issue",
UPDATED: "avi:jira:updated:issue",
DELETED: "avi:jira:deleted:issue"
},
API: {
CHANNELS: "/3/channels",
CONTENT_TYPE: "application/json",
CONTENT_TYPE_KEY: "Content-Type",
METHODS: {
PUT: "PUT",
GET: "GET"
}
},
STORAGE_KEYS: {
DASHBOARD_DATA: "DashboardData",
TRIGGER: "Trigger",
TOKEN: "Token"
},
};
const getIssueDetails = async (issueKey) => {
try {
const response = await api.asApp().requestJira(route`/rest/api/3/issue/${issueKey}`, {
method: STRINGS.API.METHODS.GET,
});
if (!response.ok) {
throw new Error(`Failed to fetch issue details: ${response.status} ${response.statusText}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Error fetching issue details:", error);
throw error;
}
};
const isMatchedTrigger = (events, type) => {
return events.some(event => event.value.includes(type))
}
const IsConditionSatisfied = async (details, conditions) => {
const { fields } = details;
for (const condition of conditions?.childConditions) {
const { leftOperand, rightOperand, operator } = condition;
const fieldValue = leftOperand?.value.includes('.')
? leftOperand.value.split('.').reduce((obj, key) => obj?.[key], fields)
: fields[leftOperand.value];
const isConditionMet = (() => {
switch (operator.value) {
case "is_equal_to":
return fieldValue === rightOperand?.value;
case "not_equal_to":
return fieldValue !== rightOperand?.value;
case "contains":
return Array.isArray(fieldValue) && fieldValue.includes(rightOperand?.value);
case "not_contains":
return Array.isArray(fieldValue) && !fieldValue.includes(rightOperand?.value);
default:
return false;
}
})();
if (!isConditionMet) {
return false;
}
}
return true;
}
const surveysparrowFetch = async (url,options) => {
const surveysparrow = api
.asUser()
.withProvider(
STRINGS.PROVIDERS.SURVEYSPARROW,
STRINGS.PROVIDERS.SURVEYSPARROW_API
);
if(surveysparrow.hasCredentials()){
console.log("unauthorized !")
await surveysparrow.requestCredentials()
} else {
return await surveysparrow.fetch(url, options)
}
}
const handleEvent = async (details, type, name) => {
try {
const triggers = await storage.get(STRINGS.STORAGE_KEYS.TRIGGER)
let validTriggers = []
await triggers.map((trigger)=> {
if(isMatchedTrigger(trigger?.events, type)){
validTriggers = [...validTriggers, trigger]
}
})
if(validTriggers.length){
await validTriggers?.map(async (validTirgger) => {
const isValid = IsConditionSatisfied(details, validTirgger?.conditions)
if(isValid){
const payload = {
survey_id: validTirgger?.survey?.value,
contacts: [
{
email: "heisenbergbb1729@gmail.com"
}
]
}
const options = {
method: STRINGS.API.METHODS.PUT,
headers: {
[STRINGS.API.CONTENT_TYPE_KEY] : STRINGS.API.CONTENT_TYPE
},
body: JSON.stringify(payload)
}
await surveysparrowFetch(`${STRINGS.API.CHANNELS}/${validTirgger?.shareChannel?.value}`, options)
}
})
}
return true
} catch (error) {
console.error('Trigger Event Error', error);
throw error;
}
};
export async function handler(event, context) {
const {
eventType,
issue: { fields: { issuetype: { name } } }
} = event;
const details = await getIssueDetails(event?.issue?.key)
const eventMap = {
[STRINGS.JIRA_EVENTS.CREATED]: STRINGS.EVENT_TYPES.ISSUE_CREATED,
[STRINGS.JIRA_EVENTS.UPDATED]: STRINGS.EVENT_TYPES.ISSUE_UPDATED,
[STRINGS.JIRA_EVENTS.DELETED]: STRINGS.EVENT_TYPES.ISSUE_DELETED
};
const eventTypeMapping = eventMap[eventType];
if (eventTypeMapping) {
return await handleEvent(details, eventTypeMapping, name);
}
console.log(`Unhandled event type: ${eventType}`);
}
And the error is
INFO 15:51:32.199 aeaec663-6a3f-4019-b9fe-df4be9913d70 unauthorized !
INFO 15:51:32.200 aeaec663-6a3f-4019-b9fe-df4be9913d70 unauthorized !
ERROR 15:51:32.219 aeaec663-6a3f-4019-b9fe-df4be9913d70 UnhandledPromiseRejection with reason: Authentication Required
ERROR 15:51:32.220 aeaec663-6a3f-4019-b9fe-df4be9913d70 UnhandledPromiseRejection with reason: Authentication Required