declarativeNetRequest.updateDynamicRules are not updating, how do I properly remove and add rules? - google-chrome-extension

update: I noticed that the token is beinig passed on the first time I run itm, but no new tokens are being appended to my rules.
I have this chrome extension on Gmail where the user logs in and it return an access token, the token is passed to our API through HTTP Request, it works fine the first time around passing the access token, but if I don't refresh Gmail, after 1 hour the access token expires and I get 401 errors on my application. I have a function interceptURL that will match the url and give a refreshed token before the HTTP Request is made (or so I thought).
Maybe after 1h the access token is expired so calling refresh token wont generate a new token?
background script
function interceptURL(requestDetails: chrome.webRequest.WebRequestBodyDetails) {
console.log('intercepted: ' + requestDetails.url);
if (requestDetails.url.includes(liveApiUrl) || requestDetails.url.includes(testApiUrl)) {
chrome.runtime.sendMessage({ "message": "refresh_token" }, (token: string) => {
if (token == undefined) {
chrome.runtime.sendMessage({ "message": "get_token" });
}
});
}
}
chrome.webRequest.onBeforeRequest.addListener(
interceptURL,
{ urls: [liveApiUrl, testApiUrl] }
)
Here are my Rules
function GetInterceptRules(token: string) {
const allResourceTypes =
Object.values(chrome.declarativeNetRequest.ResourceType);
return [
{
id: 1,
priority: 1,
action: {
type: chrome.declarativeNetRequest.RuleActionType.MODIFY_HEADERS,
requestHeaders: [
{
operation: chrome.declarativeNetRequest.HeaderOperation.SET,
header: 'Authorization',
value: 'Bearer ' + token,
},
]
},
condition: {
urlFilter: liveApiUrl,
initiatorDomains: ["mail.google.com"],
resourceTypes: allResourceTypes,
}
},
{
id: 2,
priority: 1,
action: {
type: chrome.declarativeNetRequest.RuleActionType.MODIFY_HEADERS,
requestHeaders: [
{
operation: chrome.declarativeNetRequest.HeaderOperation.SET,
header: 'Authorization',
value: 'Bearer ' + token,
},
]
},
condition: {
urlFilter: testApiUrl,
initiatorDomains: ["mail.google.com"],
resourceTypes: allResourceTypes,
}
}
];
My thought was:
1 - I give it a refresh token before every HTTP Request so when I update the dynamic rules, it would pass the new token. (that's what I current have)
2 - I could check when the access token was created and just make sure the code to get token run before the 1 hour ends. (Maybe not the best approach?)
To get the access token
chrome.identity.launchWebAuthFlow(
{
url: azureTokenAuthUrl,
interactive: isInteractive
},
(redirectURL: string) => {
let token: string = '';
if (redirectURL != null) {
let params = new URLSearchParams(redirectURL);
token = params.get("access_token");
}
console.log("acces_token", token);
console.log(redirectURL)
UpdateIntercept(token)
callback(token)
}
Manifest V3
"permissions": [
"webRequest",
"declarativeNetRequest",
"declarativeNetRequestWithHostAccess",
"identity",
"identity.email"
],
"background": {
"service_worker": "/static/js/Background.js"
},
"content_scripts": [
{
"matches": [ "<all_urls>" ],
"css": [ "/css/bootstrap-iso.css" ],
"js": [ "react.production.min.js", "react-bootstrap.min.js", "react-dom.production.min.js" ]
},
{
"matches": [ "*://mail.google.com/*" ],
"css": [ "/css/AuthButton.css" ],
"js": [ "/static/js/AuthButton.js" ]
},
{
"matches": [ "*://mail.google.com/*" ],
"js": [ "/static/js/PushNotification.js" ]
}
],
I've been searching around but can't seem to find a solution for my problem.
I tried using JWT to decode so I know it's expired.

Added the launchAuthFlow with interactivity false to my listener where I check if an email has been opened, if so, trigger launchAuthFlow appending the token to the HTTP Request

Related

Why does a rule added and immediately removed with declarativeNetRequest.updateDynamicRules not get removed even after the extension is reloaded?

I am trying to write an extension in Manifest Version 3, where I want to modify cookie headers for certain requests. Since the rule will only be applied to specific requests that meets my conditions,
I thought of adding a dynamic rule temporarily for that request, modify the cookie header, and immediately remove it. Here's the function for that rule.
if (condition) {
function makeNewRule(url) {
chrome.declarativeNetRequest.updateDynamicRules(
{
addRules:
[
{
"id": 1000,
"priority": 100,
"action": {
"type": "modifyHeaders",
"requestHeaders": [
{
"header": "cookie",
"operation": "set",
"value": "Modified cookie value 1"
}
]
},
"condition": {
"urlFilter" : url,
"resourceTypes":
["csp_report", "font", "image",
"main_frame", "media", "object",
"other", "ping", "script",
"stylesheet", "sub_frame",
"webbundle", "websocket",
"webtransport"]
}
}
],
removeRuleIds: [1000],
});
}
}
While this works for all requests that meet my condition, and the cookies are being modified observed in the chrome developers tool network window, the rule persists for a later session, even if I reload/update the unpacked extension. If I change the value of the cookie header to ""Modified cookie value 2", the developers tools still shows the previous "Modified cookie value 1". Therefore, I am assuming that the rule that I added is not being removed, and it is persisting across browser sessions. I tried cleaning the cache and reloading the browser. Additionally,
chrome.declarativeNetRequest.getDynamicRules(
e => console.log(e)
);
The snippet above shows the existence of the rule even when removed. How do I remove the rule that I added dynamically within that session?
I update my dynamic rules by removing and then adding them, I'll share my code with you, I hope it helps.
To update rules
function UpdateIntercept(token: string) {
chrome.declarativeNetRequest.updateDynamicRules({
removeRuleIds: GetInterceptRules(token).map((rule) => rule.id), // remove existing rules
addRules: GetInterceptRules(token)
});
chrome.declarativeNetRequest.getDynamicRules(
e => console.log(e)
);
}
then when I intercept the url
function GetInterceptRules(token: string) {
const allResourceTypes =
Object.values(chrome.declarativeNetRequest.ResourceType);
return [
{
id: 1,
priority: 1,
action: {
type: chrome.declarativeNetRequest.RuleActionType.MODIFY_HEADERS,
requestHeaders: [
{
operation: chrome.declarativeNetRequest.HeaderOperation.SET,
header: 'Authorization',
value: 'Bearer ' + token,
},
]
},
condition: {
urlFilter: liveApiUrl,
initiatorDomains: ["mail.google.com"],
resourceTypes: allResourceTypes,
}
},
{
id: 2,
priority: 1,
action: {
type: chrome.declarativeNetRequest.RuleActionType.MODIFY_HEADERS,
requestHeaders: [
{
operation: chrome.declarativeNetRequest.HeaderOperation.SET,
header: 'Authorization',
value: 'Bearer ' + token,
},
]
},
condition: {
urlFilter: testApiUrl,
initiatorDomains: ["mail.google.com"],
resourceTypes: allResourceTypes,
}
}
];
}
To intercept the url you want
function interceptURL(requestDetails: chrome.webRequest.WebRequestBodyDetails) {
console.log('intercepted: ' + requestDetails.url);
if (requestDetails.url.includes(liveApiUrl) || requestDetails.url.includes(testApiUrl)) { //maybe the inclue
chrome.runtime.sendMessage({ "message": "refresh_token" }, (token: string) => {
console.log('refreshed token: ' + token)
if (token == undefined) {
chrome.runtime.sendMessage({ "message": "get_token" });
}
});
}
}
I use onBeforeRequest
chrome.webRequest.onBeforeRequest.addListener(
interceptURL,
{ urls: [liveApiUrl, testApiUrl] }
)
In my case, I need to pass a refresh token everytime I intercept that url
still have a couple of things to work on, but hopefully it gives you some direction.

NodeJS OIDC Provider getting aud and resource server errors upgrade from 6.x to 7.x

I am trying to upgrade [node-oidc-provider]https://github.com/panva/node-oidc-provider from version 6.x to version 7.x. I am using the authorization code flow for a React application.
I am getting an error regarding aud(audience) being a required field for JWT tokens:
Error: JWT Access Tokens must contain an audience, for Access Tokens without audience (only usable at the userinfo_endpoint) use an opaque format
Looking at the code and documentation, I tried to update the aud field by defining a function in formats.jwt.customizers.
I am not sure if this is the right solution as after doing that, I faced an issue regarding invalid resource server configuration:
Error: invalid Resource Server jwt configuration
Below is my existing configuration (provided file is support/configuration.js):
module.exports = {
clients: [
{
"application_type": "web",
"grant_types": [
"authorization_code"
],
"id_token_signed_response_alg": "RS256",
"post_logout_redirect_uris": [
"http://localhost:3001"
],
"require_auth_time": false,
"response_types": [
"code"
],
"subject_type": "public",
"token_endpoint_auth_method": "none",
"introspection_endpoint_auth_method": "none",
"revocation_endpoint_auth_method": "none",
"request_uris": [],
"client_id_issued_at": 1622600472.0,
"client_id": "my_client_id",
"client_name": "Sample client application",
"client_secret_expires_at": 0.0,
"client_secret": "my_client_secret" ,
"redirect_uris": [
"http://localhost:3001/callback"
],
"client_background_uri": "/public/img/default.png",
"app_id": "sample_app"
}
],
clientBasedCORS: (ctx, origin, client)=>{
return true
},
interactions: {
url(ctx, interaction) { // eslint-disable-line no-unused-vars
return `/interaction/${interaction.uid}`;
},
},
cookies: {
keys: ['some secret key', 'and also the old rotated away some time ago', 'and one more'],
},
formats:{
AccessToken :'jwt',
customizers: { jwt: async(ctx, token, jwt)=>{
jwt.payload.aud = jwt.payload.iss
}}
},
claims: {
address: ['address'],
email: ['email', 'email_verified'],
phone: ['phone_number', 'phone_number_verified'],
profile: ['birthdate', 'family_name', 'gender', 'given_name', 'locale', 'middle_name', 'name',
'nickname', 'picture', 'preferred_username', 'profile', 'updated_at', 'website', 'zoneinfo'],
},
features: {
devInteractions: { enabled: false }, // defaults to true
resourceIndicators: {
enabled: true,
async useGrantedResource(ctx) {
return ctx.oidc.body && ctx.oidc.body.usegranted;
},
getResourceServerInfo(ctx, resource) {
if (resource.includes('wl')) {
return {
audience: resource,
scope: 'api:read api:write',
};
}
throw new errors.InvalidTarget();
},
defaultResource(ctx) {
if (ctx.oidc.body && ctx.oidc.body.nodefault) {
return undefined;
}
return 'urn:wl:default';
},
},
deviceFlow: { enabled: true }, // defaults to false
revocation: { enabled: true }, // defaults to false
},
jwks: {
keys: [/* keys left out for privacy*/]
},
};
This is working with me using resourceIndicators configuration that looks like:
resourceIndicators: {
enabled: true,
getResourceServerInfo: async (ctx, resourceIndicator, client) => {
return {
scope: 'api:read api:write',
audience: resourceIndicator,
accessTokenTTL: 2 * 60 * 60, // 2 hours
accessTokenFormat: 'jwt',
jwt: {
sign: { alg: 'RS256' },
},
}
}

Microsoft Teams task module with URL not working for external url

trigger a task module that will display a web page. All I was able to get is an empty Task Module with the title, while the specified height and width do not showing URL displayed.
i want to redirect the url from the api.,but api is giving a url but not redirecting
let requestUrl = await getRedirectUrlForSubmitAction(tokenResponse) ===> api call to get the url
const response: MessagingExtensionActionResponse = <MessagingExtensionActionResponse>{
task: {
type: "continue",
value: {
title: "Send recognition",
url: `${request.data.value}`, //url from api call
height: "large"
}
}
};
return Promise.resolve(response);
please check this below code for external url redirecting
<html>
<head>
<title>Redirecting</title>
<script src='https://statics.teams.cdn.office.net/sdk/v1.6.0/js/MicrosoftTeams.min.js'></script>
</head>
<body>
<div id='app'>
<header style="display: flex; justify-content: center;align-items: center; font-size: 1rem;">
<h1>Redirecting <em>....</em></h1>
</header>
</div>
<script type="text/javascript">
function login() {
microsoftTeams.initialize();
if (window.location.href.includes("redirectUrl.action")) {
let token = localStorage.getItem("appToken");
let urlStr = window.location.href.split('?url=')[1]
fetch(`${urlStr}`, {
method: 'GET',
headers: new Headers({
"content-type": "application/json",
"originated": "teams",
"post-type": "ajax",
"outlookauth": `${token}`
}),
})
.then(res => res.json())
.then(
(result) => {
console.log("result", result);
location.href = result.url
//return result.url
},
(error) => {
console.log("Error", error);
}
)
} else {
//balance
const host = window.location.href.split('&host=')[1].split('&')[0]
const appcode = window.location.href.split('&appCode=')[1]
const token = localStorage.getItem("appToken");
const urlType = window.location.href.split('?url=')[1]
const urlStr = `https://${host}/${appcode}/mobileapp/teams/teamsShopRedirectUrl.action?${urlType}`
fetch(`${urlStr}`, {
method: 'GET',
headers: new Headers({
"content-type": "application/json",
"originated": "teams",
"post-type": "ajax",
"outlookauth": `${token}`
}),
})
.then(res => res.json())
.then(
(result) => {
window.location.href = result.url
},
(error) => {
console.log("Error", error);
}
)
}
}
window.onload = login();
</script>
</body>
</html>
manifest file
{
"$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.8/MicrosoftTeams.schema.json",
"manifestVersion": "1.8",
"id": "{{APPLICATION_ID}}",
"version": "{{VERSION}}",
"packageName": "{{PACKAGE_NAME}}",
"developer": {
"name": "name",
"websiteUrl": "https://{{HOSTNAME}}/now",
"privacyUrl": "https://{{HOSTNAME}}/now/privacy.html",
"termsOfUseUrl": "https://{{HOSTNAME}}/now/tou.html"
},
"name": {
"short": "Now",
"full": "Now"
},
"description": {
"short": "for Teams",
"full": "."
},
"icons": {
"outline": "icon-outline.png",
"color": "icon-color.png"
},
"accentColor": "#FFFFFF",
"configurableTabs": [],
"staticTabs": [],
"bots": [
{
"botId": "{{MICROSOFT_APP_ID}}",
"needsChannelSelector": true,
"isNotificationOnly": false,
"scopes": [
"team",
"personal",
"groupchat"
],
"commandLists": [
{
"scopes": [
"team",
"personal"
],
"commands": [
{
"title": "test1",
"description": "test1"
},
{
"title": "test2",
"description": "test2 "
}
]
}
],
"supportsFiles": true,
"supportsCalling": true,
"supportsVideo": true
}
],
"connectors": [],
"composeExtensions": [
{
"botId": "{{MICROSOFT_APP_ID}}",
"canUpdateConfiguration": true,
"messageHandlers": [
{
"type": "link",
"value": {
"domains": [
"{{HOSTNAME}}",
"avidanpprd.performnet.com",
"youtube.com"
]
}
}
],
"commands": [
{
"id": "MessageExtension",
"title": "title",
"description": "Add a clever description here",
"initialRun": true,
"type": "action",
"context": [
"compose"
],
"fetchTask": true
}
]
}
],
"permissions": [
"identity",
"messageTeamMembers"
],
"validDomains": [
"{{HOSTNAME}}",
"avidanpprd.performnet.com",
"youtube.com"
],
"showLoadingIndicator": true,
"isFullScreen": false
}
This is probably a combination of things:
You need to make sure that the website you're showing is listed as a 'safe' domain in your manifest for your Teams app. I think you need something in both the messageHandlers > value > domain section as well as the validDomains section in the root level of the manifest.
However, depending on what you're trying to embed, it might not work because the external site has to have some basic Teams integration. See this heading, where it says:
For your page to display in Teams, you must include the Microsoft Teams JavaScript client SDK and include a call to microsoftTeams.initialize() after your page loads.
As a result, if this is an external site you don't control, you might need to have your own page that you host in the task module, which simpyly iframes in the external website.
This issue might come if the URL doesn't support iframe embedding.
To help you here you can refer the sample and try to bind the URL in iframe like below in your page.
<iframe width="700" height="700" src="https://www.example.com/embed/QPSaLnaU" allow="autoplay; encrypted-media"></iframe>
I have been recently working on something similar.
I have a message extension (build using Adaptive cards) and on one of the button clicks, I want to trigger Upload Document functionality. Unfortunately, an adaptive card doesn't provide.
So, I build an separate angular web-page, which will act as an upload application.
Note: The domain of the webpage should be added correctly under validDomains in the manifest file.
"validDomains": [
"*.example.com",
],
On button click in message extension (NodeJS):
...
return {
task: {
type: 'continue',
value: {
height: 400,
width: 400,
title: 'Task module WebView',
url: `https://show.example.com`
}
}
};
For more info visit:
Upload attachment from messages extension in MS Teams

Backend error 500 when accessing Chrome Web Store licensing API

I'm trying to make a chrome extension, which will have a free trial.
Following the documentation here, the first thing to do is enabling Chrome Identity API. As far as i can tell, there's no such thing...
Anyway... I've done every other steps, and ends up with a 500 error.
Here is part of what i've done.
Of course i changed values of all ids, keys, token, etc..
manifest.json
{
"name": "The name of my app",
"version": "1.0.9",
"key": "my_very_long_key",
"description": "A description",
"manifest_version": 2,
"permissions": [ "activeTab", "storage", "declarativeContent", "identity", "https://www.googleapis.com/" ],
"oauth2": {
"client_id": "the_client_id_i_setup_in_Credentials_oauth2_section.apps.googleusercontent.com",
"scopes": [
"https://www.googleapis.com/auth/chromewebstore.readonly"
]
},
// other stuff...
code
chrome.identity.getAuthToken({
'interactive': true
}, (token) => {
console.log("Token: %o", token);
console.log("chrome.runtime.id: %o", chrome.runtime.id);
var CWS_LICENSE_API_URL = 'https://www.googleapis.com/chromewebstore/v1.1/userlicenses/';
var req = new XMLHttpRequest();
req.open('GET', CWS_LICENSE_API_URL + chrome.runtime.id);
req.setRequestHeader('Authorization', 'Bearer ' + token);
req.setRequestHeader('Content-Type', 'application/json');
req.onreadystatechange = () => {
if (req.readyState == 4) {
var license = JSON.parse(req.responseText);
console.log(license);
}
}
req.send();
});
And here is an example of output.
Token: "ya29.GlzqBp1FaFegsgm.oihohjbrbznghdfgmgighnzxfvxz3ve5G8GQ4VxZ653FqBa8aqq-JXil-VS5IGeknneZ6KnKbyknw-gXw"
chrome.runtime.id: "asdflhlkrfhuilerdfb"
Object
error:
code: 500
errors: Array(1)
0:
domain: "global"
message: "Backend Error"
reason: "backendError"
__proto__: Object
length: 1
__proto__: Array(0)
message: "Backend Error"
__proto__: Object
__proto__: Object
So i'm able to obtain the access token, but then calling the API with it doesn't seem to lead to anything.
Reopened bug: https://issuetracker.google.com/issues/140188619
Please, star it on issuetracker.google.com, if you have the same issue to speed up Google)
UPDATE:
Issue was fixed by Google!
https://bugs.chromium.org/p/chromium/issues/detail?id=940478#c18

Atlassian Connect-Express: JIRA REST API authentication within the JIRA plugin

i am using the atlassian-connect-express toolkit for creating Atlassian Connect based Add-ons with Node.js.
It provides Automatic JWT authentication of inbound requests as well as JWT signing for outbound requests back to the host.
The add-on is authenticated when i install it in the JIRA dashboard and return the following pay-load:
{ key: 'my-add-on',
clientKey: '*****',
publicKey: '********'
sharedSecret: '*****'
serverVersion: '100082',
pluginsVersion: '1.3.491',
baseUrl: 'https://myaccount.atlassian.net',
productType: 'jira',
description: 'Atlassian JIRA at https://myaccount.atlassian.net ',
eventType: 'installed' }
But i am not able to authenticate the JIRA Rest Api with the JWT token generated by the framework. It throws below error message.
404 '{"errorMessages":["Issue does not exist or you do not have permission to see it."],"errors":{}}'
below is the code when i send a GET request:
app.get('/getissue', addon.authenticate(), function(req, res){
var request = require('request');
request({
url: 'https://myaccount.atlassian.net/rest/api/2/issue/ABC-1',
method: 'GET',
}, function(error, response, body){
if(error){
console.log("error!");
}else{
console.log(response.statusCode, body);
}
});
res.render('getissue');
});
Below is the code for my app descriptor file:
{
"key": "my-add-on",
"name": "Ping Pong",
"description": "My very first add-on",
"vendor": {
"name": "Ping Pong",
"url": "https://www.example.com"
},
"baseUrl": "{{localBaseUrl}}",
"links": {
"self": "{{localBaseUrl}}/atlassian-connect.json",
"homepage": "{{localBaseUrl}}/atlassian-connect.json"
},
"authentication": {
"type": "jwt"
},
"lifecycle": {
"installed": "/installed"
},
"scopes": [
"READ",
"WRITE"
],
"modules": {
"generalPages": [
{
"key": "hello-world-page-jira",
"location": "system.top.navigation.bar",
"name": {
"value": "Hello World"
},
"url": "/hello-world",
"conditions": [{
"condition": "user_is_logged_in"
}]
},
{
"key": "getissue-jira",
"location": "system.top.navigation.bar",
"name": {
"value": "Get Issue"
},
"url": "/getissue",
"conditions": [{
"condition": "user_is_logged_in"
}]
}
]
}
}
I am pretty sure this is not the correct way i am doing, Either i should use OAuth. But i want to make the JWT method for authentication work here.
Got it working by checking in here Atlassian Connect for Node.js Express Docs
Within JIRA ADD-On Signed HTTP Requests works like below. GET and POST both.
GET:
app.get('/getissue', addon.authenticate(), function(req, res){
var httpClient = addon.httpClient(req);
httpClient.get('rest/api/2/issue/ABC-1',
function(err, resp, body) {
Response = JSON.parse(body);
if(err){
console.log(err);
}else {
console.log('Sucessful')
}
});
res.send(response);
});
POST:
var httpClient = addon.httpClient(req);
var postdata = {
"fields": {
"project":
{
"key": "MYW"
},
"summary": "My Story Name",
"description":"My Story Description",
"issuetype": {
"name": "Story"
}
}
}
httpClient.post({
url: '/rest/api/2/issue/' ,
headers: {
'X-Atlassian-Token': 'nocheck'
},
json: postdata
},function (err, httpResponse, body) {
if (err) {
return console.error('Error', err);
}
console.log('Response',+httpResponse)
});
You should be using global variable 'AP' that's initialized by JIRA along with your add-on execution. You may explore it with Chrome/Firefox Debug.
Have you tried calling ?
AP.request(..,...);
instead of "var request = require('request');"
You may set at the top of the script follwing to pass JS hinters and IDE validations:
/* global AP */
And when using AP the URL should look like:
url: /rest/api/2/issue/ABC-1
instead of:
url: https://myaccount.atlassian.net/rest/api/2/issue/ABC-1
My assumption is that ABC-1 issue and user credentials are verified and the user is able to access ABC-1 through JIRA UI.
Here is doc for ref.: https://developer.atlassian.com/cloud/jira/software/jsapi/request/

Resources