in my project I use some cloud functions: one of them sets custom claims, it works properly, I have a second function that when fired should write some data on the realtime Db,but once is fired I get a CORS Error; this is the faulty function:
exports.insertUser = functions.https.onCall((data)=>{
const db = admin.database();
const reference = "userProfile";
return db.ref(reference).push(data.user).then(()=>{
return {message: "utente inserito"};
}).catch((error)=>{
return error;
})
});
this is the error that I get:
Access to fetch at 'https://us-central1-trasportostudenti-
bc19c.cloudfunctions.net/insertUser' from origin 'http://localhost:8100' has been blocked by CORS policy: Response to preflight request doesn't pass access control check
firebase's log states that the problem is in my code; any suggestions to fix this error?
Related
I am trying to call a 3rd party service that uses Oauth2 Password Credentials to get an authentication token. Ballerina is returning the following messages.
2020-04-23 15:07:35,414 ERROR [ballerina/oauth2] - Received an invalid response with status-code: 406; and payload: {"fault":{"faultstring":"Raising fault. Fault name : RF.Raise-406-Exception","detail":{"errorcode":"steps.raisefault.RaiseFault"}}}
2020-04-23 15:07:35,418 ERROR [ballerina/oauth2] - Failed to generate OAuth2 token. : error {ballerina/oauth2}Error message=Received an invalid response with status-code: 406; and payload: {"fault":{"faultstring":"Raising fault. Fault name : RF.Raise-406-Exception","detail":{"errorcode":"steps.raisefault.RaiseFault"}}}
error {ballerina/http}AuthenticationFailed message=Failed to prepare request at bearer auth handler. cause=error {ballerina/auth}Error message=Failed to generate OAuth2 token. cause=error {ballerina/oauth2}Error message=Received an invalid response with status-code: 406; and payload: {"fault":{"faultstring":"Raising fault. Fault name : RF.Raise-406-Exception","detail":{"errorcode":"steps.raisefault.RaiseFault"}}}
It's the 406 code that is confusing me as I have set both the content type & accept headers to "application/json" which is what the service requires.
However, the second message says "Failed to generate OAuth2 token" so could it be the call to get the oauth token that is returning the 406? If so how do I set the accept header on the token service call?
Using Ballerina I have called the token endpoint and successfully got a token but if I try to call a service using a PasswordGrantConfig those are the errors I get. I've tried everything I can think of and have successfully got other services using ClientCredentialsGrantConfig to work.
Any help gratefully received.
The relevant code is below. The three sections below are parts of the code in 3 different .bal files.
// configure the Oauth2 Config
import ballerina/config;
import ballerina/http;
import ballerina/oauth2;
public function getOauth2Handler() returns http:BearerAuthHandler {
oauth2:PasswordGrantConfig passwordGrantConfig = {
tokenUrl: config:getAsString("experian.authentication.tokenUrl"),
username: config:getAsString("experian.authentication.username"),
password: config:getAsString("experian.authentication.password"),
clientId: config:getAsString("experian.authentication.clientId"),
clientSecret: config:getAsString("experian.authentication.clientSecret"),
credentialBearer: http:AUTH_HEADER_BEARER
};
oauth2:OutboundOAuth2Provider oauth2Provider = new (passwordGrantConfig);
return new (oauth2Provider);
}
// Configure the API Client
http:ClientConfiguration delphiSelectClientConfig = {
auth: {
authHandler: experian:getOauth2Handler()
}
};
experian:DelphiSelectClientConfig delphiSelectConfig = {
serviceUrl: config:getAsString("experian.services.delphi-select.serviceUrl"),
clientConfig: delphiSelectClientConfig
};
experian:DelphiSelectClient delphiSelectClient = new (delphiSelectConfig);
// Call the endpoint using the Oath2 configuration
import ballerina/http;
import ballerina/io;
public type DelphiSelectClientConfig record {
string serviceUrl;
http:ClientConfiguration clientConfig;
};
//==============================
//============Client============
//==============================
public type DelphiSelectClient client object {
public http:Client clientEp;
public http:ClientConfiguration config;
public function __init(DelphiSelectClientConfig config) {
http:Client httpEp = new (config.serviceUrl, {auth: config.clientConfig.auth});
self.clientEp = httpEp;
self.config = config.clientConfig;
}
public remote function newApplication() returns #untainted json|error {
io:println("In newApplication function");
http:Request request = new;
json requestBody = newApplicationBody; // get test data from json in another file
request.setJsonPayload(requestBody);
var response = check self.clientEp->post("/application", request);
var payload = check response.getJsonPayload();
return payload;
}
};
I have also modified my test code to call the token EP and deliberately set accept to an unacceptable value, for example, "text/csv". In this case I get the same error response. However setting accept to "*/*" does work. Final test; accept of "" (empty) also fails so I suspect that the BearerAuthHandler is not setting any value for accept.
So can I force the BearerAuthHandler to set an accept of "application/json"?
Thanks.
See picture below.
Also, the example in the Oath2 spec you referenced shows a content-type value being set. Even a value of “*/*” would work but I suspect Ballerina leaves it blank.
I have raised the GitHub issue Need to be able to set http header values for OutboundOAuth2Provider
The main objective of http:OutboundAuthHandler objects are to prepare the http:Request with authentication information that needs to be authenticated with external endpoint you are calling to.
The http:BearerAuthHandler is responsible for adding Authorization header with the value of Bearer <token>. "token" is prepared with the provided information. So, there is no option to force http:BearerAuthHandler to set any header for the request.
But in this case, if the API successfully respond if there is Accept header with the value of application/json, you can simply add that header to the http:Request before calling the POST request as follow:
request.addHeader("Accept", "application/json");
Is there a way to find if a request is XHR or fetch while using Workbox.
const matchCb = ({url, event}) => {
if(event.type === 'xhr')
{
return true;
}
return false;
};
workbox.routing.registerRoute(
matchCb,
workbox.strategies.networkOnly()
);
I have put a check so that the above route is used only for XHR calls.
Although network Tab of the browser shows a certain request to be of the type xhr it is coming out to be fetch on debugging the above code . Am i doing something wrong? Is there some other way to check it?
There's no way to determine that from within Workbox or inside of the service worker. (I'm also not sure why you would want to?)
One thing that you can do, however, is add in an extra request header when you make your request, and then check for that header inside of your service worker. If it's really important for you to distinguish between requests that originated via XHR and va fetch(), you could use the header for that.
Inside your web app:
const request = new Request('/api', {headers: {'X-Source': 'fetch'}});
const response = await fetch(request);
Inside your service worker, using Workbox:
workbox.routing.registerRoute(
// This matchCallback will only be true if the request
// has an X-Source header set to 'fetch':
({event}) => event.request.headers.get('X-Source') === 'fetch',
workbox.strategies.networkOnly()
);
Note that if you're making a cors request, you may need to delete that X-Source request header before sending it to the network, since extra request headers can trigger CORS preflight checks.
I've setup datatables.net with server side processing on a MVC application using the DataTables.Aspnet.Core & DataTables.AspNet.WebApi2 nugets. All works well and the data gets loaded and displayed fine.
If there is an issue on the server side I catch the exception and return a result containing my custom error message:
// IDataTablesRequest tableRequest
return new DataTablesJsonResult(Response.Create(tableRequest, "my custom error message"), Request);
On the client side I've registered to the error event as well:
$.fn.dataTable.ext.errMode = 'none';
this.$dataTable.on('error.dt', this.onDataTableError.bind(this));
This also works fine, I can parse the response and check for my custom message.
Now the issue: After handling the error I always get the dreaded Uncaught TypeError: Cannot read property 'length' of undefined I assume this is the case because I don't send any data to the client?
How can I prevent this error?
I could not manage to fix this on the server side by adding an empty dataset as this would not be passed with the json.
What I ended up doing is ensuring there is always a data array on the client side:
// register to ajax request event
this.$dataTable.on('xhr.dt', this.afterAjax.bind(this));
// set empty array to make sure we have no issue
// with null references on data.length for loop
afterAjax(event: JQueryEventObject, settings: any, payload: any, response: any): void {
payload.data = payload.data || [];
}
To use Events API for Slack App development, there is a setting for "Events API Request URLs" as described in doc:
In the Events API, your Events API Request URL is the target location
where all the events your application is subscribed to will be
delivered, regardless of the team or event type.
There is a UI for changing the URL "manually" at api.slack.com under
"Event Subscriptions" section in settings. There is also url_verification event after changing the Request URL described here.
My question - Is there an API call (method) so I can update the endpoint (Request URL) from my server code?
For example, in Facebook API there is a call named subscriptions where I can change webhook URL after initial setup - link
Making a POST request with the callback_url, verify_token, and object
fields will reactivate the subscription.
PS. To give a background, this is needed for development using outbound tunnel with dynamic endpoint URL, e.g. ngrok free subscription. By the way, ngrok is referenced in sample "onboarding" app by slack here
Update. I checked Microsoft Bot Framework, and they seems to use RTM (Real Time Messaging) for slack which doesn't require Request URL setup, and not Events API. Same time, e.g. for Facebook they (MS Bot) instruct me to manually put their generated URL to webhook settings of a FB app, so there is no automation on that.
Since this question was originally asked, Slack has introduced app manifests, which enable API calls to change app configurations. This can be used to update URLs and other parameters, or create/delete apps.
At the time of writing, the manifest / manifest API is in beta:
Beta API — this API is in beta, and is subject to change without the usual notice period for changes.
so the this answer might not exactly fit the latest syntax as they make changes.
A programatic workflow might look as follows:
Pull a 'template' manifest from an existing version of the application, with most of the settings as intended (scopes, name, etc.)
Change parts of the manifest to meet the needs of development
Verify the manifest
Update a slack app or create a new one for testing
API List
Basic API list
Export a manifest as JSON: apps.manifest.export
Validate a manifest JSON: apps.manifest.validate
Update an existing app: apps.manifest.update
Create a new app from manifest: apps.manifest.create
Delete an app: apps.manifest.delete
Most of these API requests are Tier 1 requests, so only on the order of 1+ per minute.
API Access
You'll need to create and maintain "App Configuration Tokens". They're created in the "Your Apps" dashboard. More info about them here.
Example NodeJS Code
const axios = require('axios');
// Change these values:
const TEMPLATE_APP_ID = 'ABC1234XYZ';
const PUBLIC_URL = 'https://www.example.com/my/endpoint';
let access = {
slackConfigToken: "xoxe.xoxp-1-MYTOKEN",
slackConfigRefreshToken: "xoxe-1-MYREFRESHTOKEN",
slackConfigTokenExp: 1648550283
};
// Helpers ------------------------------------------------------------------------------------------------------
// Get a new access token with the refresh token
async function refreshTokens() {
let response = await axios.get(`https://slack.com/api/tooling.tokens.rotate?refresh_token=${access.slackConfigRefreshToken}`);
if (response.data.ok === true) {
access.slackConfigToken = response.data.token;
access.slackConfigRefreshToken = response.data.refresh_token;
access.slackConfigTokenExp = response.data.exp;
console.log(access);
} else {
console.error('> [error] The token could not be refreshed. Visit https://api.slack.com/apps and generate tokens.');
process.exit(1);
}
}
// Get an app manifest from an existing slack app
async function getManifest(applicationID) {
const config = {headers: { Authorization: `Bearer ${access.slackConfigToken}` }};
let response = await axios.get(`https://slack.com/api/apps.manifest.export?app_id=${applicationID}`, config);
if (response.data.ok === true) return response.data.manifest;
else {
console.error('> [error] Invalid could not get manifest:', response.data.error);
process.exit(1);
}
}
// Create a slack application with the given manifest
async function createDevApp(manifest) {
const config = {headers: { Authorization: `Bearer ${access.slackConfigToken}` }};
let response = await axios.get(`https://slack.com/api/apps.manifest.create?manifest=${encodeURIComponent(JSON.stringify(manifest))}`, config);
if (response.data.ok === true) return response.data;
else {
console.error('> [error] Invalid could not create app:', response.data.error);
process.exit(1);
}
}
// Verify that a manifest is valid
async function verifyManifest(manifest) {
const config = {headers: { Authorization: `Bearer ${access.slackConfigToken}` }};
let response = await axios.get(`https://slack.com/api/apps.manifest.validate?manifest=${encodeURIComponent(JSON.stringify(manifest))}`, config);
if (response.data.ok !== true) {
console.error('> [error] Manifest did not verify:', response.data.error);
process.exit(1);
}
}
// Main ---------------------------------------------------------------------------------------------------------
async function main() {
// [1] Check token expiration time ------------
if (access.slackConfigTokenExp < Math.floor(new Date().getTime() / 1000))
// Token has expired. Refresh it.
await refreshTokens();
// [2] Load a manifest from an existing slack app to use as a template ------------
const templateManifest = await getManifest(TEMPLATE_APP_ID);
// [3] Update URLS and data in the template ------------
let devApp = { name: 'Review App', slashCommand: '/myslashcommand' };
templateManifest.settings.interactivity.request_url = `${PUBLIC_URL}/slack/events`;
templateManifest.settings.interactivity.message_menu_options_url = `${PUBLIC_URL}/slack/events`;
templateManifest.features.slash_commands[0].url = `${PUBLIC_URL}/slack/events`;
templateManifest.oauth_config.redirect_urls[0] = `${PUBLIC_URL}/slack/oauth_redirect`;
templateManifest.settings.event_subscriptions.request_url = `${PUBLIC_URL}/slack/events`;
templateManifest.display_information.name = devApp.name;
templateManifest.features.bot_user.display_name = devApp.name;
templateManifest.features.slash_commands[0].command = devApp.slashCommand;
// [5] Verify that the manifest is still valid ------------
await verifyManifest(templateManifest);
// [6] Create our new slack dev application ------------
devApp.data = await createDevApp(templateManifest);
console.log(devApp);
}
main();
Hope this helps anyone else looking to update Slack applications programatically.
No, such a method does not exist in the official documentation. There might be an unofficial method - there are quite a few of them actually - but personally I doubt it.
But you don't need this feature for developing Slack apps. Just simulate the POST calls from Slack on your local dev machine with a script and then do a final test together with Slack on your webserver on the Internet.
event.respondWith(caches.match(event.request).then(function (response) {
if (response) {
return response;
}
//return fetch(event.reuqest, { credentials: 'include' });
//event.respondWith(fetch(event.request, { credentials: 'include' }));
}));
This is a common code for handling request via serviceworkers , if the url is in cache then return cache response or fetch it from server .
But my doubt is regarding the 2 commented lines , we need to use one of them for fetching the response .
My doubt is, when i use event.respondWith(fetch(event.request, { credentials: 'include' for fetching a page , i get the following error
DOMException: Failed to execute 'respondWith' on 'FetchEvent': The fetch event has already been responded to.
But the page is finally rendered , definitely browser is finally fetching the response , but when i use sam for fetching an image , i get the same error and on top of that the image is not fetched .
if i use the second option that return fetch(event.reuqest, { credentials: 'include' }); , then it works fine for both image as well as page.
I am not able to figure out what is the reason of that error , and also why it is behaving differently for file and page .
My another doubt is , do i actually need the credential parameter here ,i added it because most of the implementations i saw in web have used it,but what i have observed is that the request object already has a credential property with it , now it is not always
include
sometime it is
same-origin
too.
So could it happen that i am actually overriding the actual credential value by adding it .If that is not the case , then there is no difference in including it or not.It does not matter .
But if it is other way around , then we should not overwrite the credential value, which can have bad side effects.
You already have a call to event.respondWith, you don't need to call it twice.
Your first call is going to use the promise returned by:
caches.match(event.request).then(function(response) {
if (response) {
return response;
}
return fetch(event.reuqest, { credentials: 'include' });
})
This promise resolves to:
response, if the request is in the cache;
the promise returned by the call to fetch, otherwise.
The promise returned by fetch will resolve to a response, which is then going to be used by respondWith.