I've been running into issues on a legacy project that fetches ical feeds.
I am getting a response of "Bad request 400" when trying to get a calendar via any outlook.office365 url.
I have tested all the urls using PostMan and an online ics validator so I know that it has nothing to do with the calendars themselves not being available.
I am using the npm package 'request' to get the calendars and it's working with any url that doesn't come from the outlook.office365.com host.
For privacy reasons i'm not able to share any of the urls used.
Here is where the request is sent.
async.waterfall([
cb => {
request.get(url, {}, function (err, r, data) {
console.log('response', r.statusCode); // this will be 400 for any outlook.office365 ics url but not for others.
if (err) return cb(err, null);
try {
...
} catch (err) {
...
}
Are there any headers that need to be attached in order to receive outlook.office365 calendars? I can't find anything online about what is required
I had the same issue.
I compared the request headers in Postman and tried to mimic these in my application.
Adding the Postman User Agent string made it work for me:
HttpClient myHttpClient = new HttpClient();
myHttpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("*/*"));
myHttpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("PostmanRuntime","7.30.1"));
var response = myHttpClient.GetAsync(calendarUrl).Result;
In my development environment, I have a local copy of Dynamodb set up, using Reactjs to connect to it. I'm using the AWS SDK, in particular the query method for Dynamodb, to make queries.
When the query is properly structured, it runs fine. However, if the query is poorly structured, I get a cryptic response. The Chrome console gives me a:
POST http://localhost:8000/ 400 (Bad Request)
Access to XMLHttpRequest at 'http://localhost:8000/' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
where localhost:8000 points to the local Dynamodb instance and localhost:3000 points to the react instance.
Most importantly, I don't get an error message dealing specifically with the mistake I've made. For instance, I might project a field with a reserved word, which then should correctly not work. However, I would expect an error saying "such and such word is reserved", not this CORS business.
Furthermore, when I run the same query from my node server, I do get a proper error message. This makes me wonder if the problem is related to reactjs somehow.
Is there any way to get a proper error message in my development environment as I've described? Thanks.
Edit:
As requested in the comments, here's a typical way I grab the data. Please note that this is a working query, so this is not generating an error. As I'm developing, though, this would be the type of query where I'd be making mistakes and not seeing good feedback.
// define tableName, indexName
async componentDidMount() {
// properties include an initiated docClient and organizationId
const [posts] = await Promise.all([ // typically I will fetch more than just posts (omitted for brevity)
new Promise((resolve, reject) =>
this.props.docClient.query(
{
TableName: tableName,
IndexName: indexName,
KeyConditionExpression: 'SortKey = :sortKey',
ExpressionAttributeValues: {
':sortKey': this.props.organizationId + delimiter + 'POST-Date'
},
ProjectionExpression: 'chatTitle, creatorName, creatorEmail, PostId, creationTime, #text, #data, lastModifiedTime, #status, mentions, attachments, chatType, creatorType',
ExpressionAttributeNames: { '#text': 'text', '#status': 'status', '#data': 'Data' }
},
(err, data) => {
if (err) {
reject(err);
return;
}
resolve(data.Items);
}
)
)
]);
// do something with posts, etc...
}
docClient is generated in a component using:
new AWS.DynamoDB.DocumentClient({apiVersion: '2012-08-10'})
where AWS is the aws-sdk module
In my code I am trying to send a POST request to the IFTTT service webhooks (maker).
I'm using a couple of libraries, mainly WiFi101
I am using an Arduino MKR1000.
I have updated the firmware, and added a certificate for https://maker.ifttt.com:443.
When in the following code I call sslClient.connect(host, 443); It fails to make the connection. I have tried bypassing this and just trying to print data to the host, however this also didn't work.
It takes about 10-20 seconds for the function to return as false, if I change the host to an incorrect variable, then it returns as false immediately. I'm assuming this is a good sign since the arduino is trying to connect?
wifiSetup() Runs well, connection is established reasonably quickly.
The code I am refering to is below:
Globally defined
//WiFi router setup
char ssid[] = "-----"; //network SSID (aka WiFi name)
char pass[] = "-----"; //network password
int status = WL_IDLE_STATUS;
const char* host = "https://maker.ifttt.com";
WiFiSSLClient sslClient;
Wifi setup procedure: This runs without problems
void wifiSetup() {
// Check for the presence of the shield
Serial.print("WiFi101 shield: ");
if (WiFi.status() == WL_NO_SHIELD) {
Serial.println("NOT PRESENT");
return; // don't continue
}
Serial.println("DETECTED");
// attempt to connect to Wifi network:
while ( status != WL_CONNECTED) {
Serial.print("Attempting to connect to Network named: ");
Serial.println(ssid); // print the network name (SSID);
// Connect to WPA/WPA2 network. Change this line if using open or WEP network:
status = WiFi.begin(ssid, pass);
// wait 10 seconds for connection:
delay(10000);
}
printWifiStatus(); // you're connected now, so print out the status
}
The code below is the one causing problems
void sendMessage() {
if (sslClient.connect(host, 443)) {
//change this to your Maker setting from https://ifttt.com/services/maker/settings
String data = "randomdata";
sslClient.println("POST /trigger/tank_empty/with/key/bxa");
sslClient.println("Host: https://maker.ifttt.com");
sslClient.println("Content-Type: application/json");
sslClient.print("Content-Length: ");
sslClient.println(data.length());
sslClient.println();
sslClient.print(data);
sslClient.stop();
Serial.println("IFTTT request Sucessful");
}
else {
Serial.println("IFTTT request failed");
}
delay(20000000);
}
Does anyone have any solutions, or things to troubleshoot?
Thanks for your help all,
Let me know if you need any extra information.
https://maker.ifttt.com is not a valid host. A valid host is either an IP address or a domain. https:// is not a part of the domain, but an URL.
You are also missing the HTTP protocol version (HTTP/1.1), which could potentially cause problems.
const char* host = "maker.ifttt.com";
sslClient.println("POST /trigger/tank_empty/with/key/bxa HTTP/1.1");
sslClient.print("Host: ");
sslClient.println(host); // non hardcoded host header
sslClient.println("Content-Type: application/json");
sslClient.print("Content-Length: ");
sslClient.println(data.length());
sslClient.println();
sslClient.print(data);
sslClient.stop();
I have been working on a simple production management process based around smartsheet. The code that I have been running has been working fine on my Ubuntu machine, then I copied it over to my Parrot Linux machine running the same node version and it won't find a row that exists. Below is the request:
var copyRow = {
"rowIds": artToProdRowsToCopy,
"to": {
"sheetId": productionId
}
};
// Set options
var options = {
sheetId: artId,
body: copyRow,
queryParameters: {
include: "all"
}
};
console.log(options);
// Copy the normal engraved rows from art to production
smartsheet.sheets.copyRowToAnotherSheet(options)
.then(function(results) {
callback1(null, results);
})
.catch(function(error) {
console.log(error);
});
The log output of options:
{ sheetId: 8129017524546436,
body:
{ rowIds: [ 8886954296800644 ],
to: { sheetId: 6941481487333252 } },
queryParameters: { include: 'all' } }
The error:
{ statusCode: 404,
errorCode: 1006,
message: 'Not Found',
refId: 'zjl2z56296l9' }
I'm running node v8.9.1, on Parrot Linux 3.9.
I've checked that each of these sheet and row ID #'s are correct and they all are (the ones in the examples are not real however). Any help would be appreciated.
Edit: Adding debug info:
[Smartsheet] 2017-11-20T20:22:55.876Z[ INFO] POST https://api.smartsheet.com/2.0/sheets/8129017124546436/rows//copy?include=all
[Smartsheet] 2017-11-20T20:22:55.876Z[VERBOSE] Request Payload (preview): {"rowIds":[2759271070885764,3212501789763460,4576920289470340,8982631438149508,2962733838690180,8886959296800644],"to":{"sheetId":6941441487333252}}
[Smartsheet] 2017-11-20T20:22:55.876Z[ DEBUG] Request Payload (full): {"rowIds":[2759271070885764,3212501789763460,4576920289470340,8982631438149508,2962733838690180,8886959296800644],"to":{"sheetId":6941441487333252}}
[Smartsheet] 2017-11-20T20:22:56.155Z[ ERROR] Request failed after 0 retries
[Smartsheet] 2017-11-20T20:22:56.156Z[ ERROR] POST https://api.smartsheet.com/2.0/sheets/8129017124546436/rows//copy?include=all
[Smartsheet] 2017-11-20T20:22:56.156Z[ ERROR] Response: Failure (HTTP 404)
Error Code: 1006 - Not Found
Ref ID: 85bn0m2j8oki
I don't see any obvious issues with your request structure. Typically, the 404 Not Found error is related to an issue with the request URI, rather than the contents of the request itself. i.e., a 404 Not Found error means that, for some reason or another, the request URI is not reachable.
The URI for the Copy Row(s) request is:
POST /sheets/{sheetId}/rows/copy
A few troubleshooting suggestions:
Verify that the casing of all characters in the request URI are lowercase.
Verify that the sheet corresponding to the sheetId value in the request URI exists.
Verify that the user who owns the API Access token that you're specifying in the Authorization header of the Copy Row(s) API request does indeed have access to the sheet corresponding to the sheetId value in the request URI.
As described in the Troubleshooting section of the API docs, I'd suggest that you use a tool like Fiddler or Charles HTTP Proxy to examine the raw HTTP request that your app is sending, then you can investigate/verify the items I've listed above.
Update #1
Thanks for updating your post with debugging info. Based on that, it looks like your request URI contains an extra slash between the words rows and copy:
POST https://api.smartsheet.com/2.0/sheets/8129017124546436/rows//copy?include=all
Perhaps this is causing your problem?
Update #2
I've been able to reproduce the Not Found error in Postman if my Request URI contains two slashes between the words rows and copy(like your debug output shows). Removing one of these slashes fixes the issue. That is, your request should look like this (only one slash between the words rows and copy).
POST https://api.smartsheet.com/2.0/sheets/8129017124546436/rows/copy?include=all
Looks like our SDK bug. Stay tuned for a fix.
Fixed in version 1.0.3 - now on Github and npm.
https://www.npmjs.com/package/smartsheet
https://github.com/smartsheet-platform/smartsheet-javascript-sdk/releases/tag/v1.0.3
Looks like it's easy to add custom HTTP headers to your websocket client with any HTTP header client which supports this, but I can't find how to do it with the web platform's WebSocket API.
Anyone has a clue on how to achieve it?
var ws = new WebSocket("ws://example.com/service");
Specifically, I need to be able to send an HTTP Authorization header.
Updated 2x
Short answer: No, only the path and protocol field can be specified.
Longer answer:
There is no method in the JavaScript WebSockets API for specifying additional headers for the client/browser to send. The HTTP path ("GET /xyz") and protocol header ("Sec-WebSocket-Protocol") can be specified in the WebSocket constructor.
The Sec-WebSocket-Protocol header (which is sometimes extended to be used in websocket specific authentication) is generated from the optional second argument to the WebSocket constructor:
var ws = new WebSocket("ws://example.com/path", "protocol");
var ws = new WebSocket("ws://example.com/path", ["protocol1", "protocol2"]);
The above results in the following headers:
Sec-WebSocket-Protocol: protocol
and
Sec-WebSocket-Protocol: protocol1, protocol2
A common pattern for achieving WebSocket authentication/authorization is to implement a ticketing system where the page hosting the WebSocket client requests a ticket from the server and then passes this ticket during WebSocket connection setup either in the URL/query string, in the protocol field, or required as the first message after the connection is established. The server then only allows the connection to continue if the ticket is valid (exists, has not been already used, client IP encoded in ticket matches, timestamp in ticket is recent, etc). Here is a summary of WebSocket security information: https://devcenter.heroku.com/articles/websocket-security
Basic authentication was formerly an option but this has been deprecated and modern browsers don't send the header even if it is specified.
Basic Auth Info (Deprecated - No longer functional):
NOTE: the following information is no longer accurate in any modern browsers.
The Authorization header is generated from the username and password (or just username) field of the WebSocket URI:
var ws = new WebSocket("ws://username:password#example.com")
The above results in the following header with the string "username:password" base64 encoded:
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
I have tested basic auth in Chrome 55 and Firefox 50 and verified that the basic auth info is indeed negotiated with the server (this may not work in Safari).
Thanks to Dmitry Frank's for the basic auth answer
More of an alternate solution, but all modern browsers send the domain cookies along with the connection, so using:
var authToken = 'R3YKZFKBVi';
document.cookie = 'X-Authorization=' + authToken + '; path=/';
var ws = new WebSocket(
'wss://localhost:9000/wss/'
);
End up with the request connection headers:
Cookie: X-Authorization=R3YKZFKBVi
Sending Authorization header is not possible.
Attaching a token query parameter is an option. However, in some circumstances, it may be undesirable to send your main login token in plain text as a query parameter because it is more opaque than using a header and will end up being logged whoknowswhere. If this raises security concerns for you, an alternative is to use a secondary JWT token just for the web socket stuff.
Create a REST endpoint for generating this JWT, which can of course only be accessed by users authenticated with your primary login token (transmitted via header). The web socket JWT can be configured differently than your login token, e.g. with a shorter timeout, so it's safer to send around as query param of your upgrade request.
Create a separate JwtAuthHandler for the same route you register the SockJS eventbusHandler on. Make sure your auth handler is registered first, so you can check the web socket token against your database (the JWT should be somehow linked to your user in the backend).
HTTP Authorization header problem can be addressed with the following:
var ws = new WebSocket("ws://username:password#example.com/service");
Then, a proper Basic Authorization HTTP header will be set with the provided username and password. If you need Basic Authorization, then you're all set.
I want to use Bearer however, and I resorted to the following trick: I connect to the server as follows:
var ws = new WebSocket("ws://my_token#example.com/service");
And when my code at the server side receives Basic Authorization header with non-empty username and empty password, then it interprets the username as a token.
You cannot add headers but, if you just need to pass values to the server at the moment of the connection, you can specify a query string part on the url:
var ws = new WebSocket("ws://example.com/service?key1=value1&key2=value2");
That URL is valid but - of course - you'll need to modify your server code to parse it.
You can not send custom header when you want to establish WebSockets connection using JavaScript WebSockets API.
You can use Subprotocols headers by using the second WebSocket class constructor:
var ws = new WebSocket("ws://example.com/service", "soap");
and then you can get the Subprotocols headers using Sec-WebSocket-Protocol key on the server.
There is also a limitation, your Subprotocols headers values can not contain a comma (,) !
For those still struggling in 2021, Node JS global web sockets class has an additional options field in the constructor. if you go to the implementation of the the WebSockets class, you will find this variable declaration. You can see it accepts three params url, which is required, protocols(optional), which is either a string, an array of strings or null. Then a third param which is options. our interest, an object and (still optional). see ...
declare var WebSocket: {
prototype: WebSocket;
new (
uri: string,
protocols?: string | string[] | null,
options?: {
headers: { [headerName: string]: string };
[optionName: string]: any;
} | null,
): WebSocket;
readonly CLOSED: number;
readonly CLOSING: number;
readonly CONNECTING: number;
readonly OPEN: number;
};
If you are using a Node Js library like react , react-native. here is an example of how you can do it.
const ws = new WebSocket(WEB_SOCKETS_URL, null, {
headers: {
['Set-Cookie']: cookie,
},
});
Notice for the protocols I have passed null. If you are using jwt, you can pass the Authorisation header with Bearer + token
Disclaimer, this might not be supported by all browsers outside the box, from the MDN web docs you can see only two params are documented.
see https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/WebSocket#syntax
Totally hacked it like this, thanks to kanaka's answer.
Client:
var ws = new WebSocket(
'ws://localhost:8080/connect/' + this.state.room.id,
store('token') || cookie('token')
);
Server (using Koa2 in this example, but should be similar wherever):
var url = ctx.websocket.upgradeReq.url; // can use to get url/query params
var authToken = ctx.websocket.upgradeReq.headers['sec-websocket-protocol'];
// Can then decode the auth token and do any session/user stuff...
In my situation (Azure Time Series Insights wss://)
Using the ReconnectingWebsocket wrapper and was able to achieve adding headers with a simple solution:
socket.onopen = function(e) {
socket.send(payload);
};
Where payload in this case is:
{
"headers": {
"Authorization": "Bearer TOKEN",
"x-ms-client-request-id": "CLIENT_ID"
},
"content": {
"searchSpan": {
"from": "UTCDATETIME",
"to": "UTCDATETIME"
},
"top": {
"sort": [
{
"input": {"builtInProperty": "$ts"},
"order": "Asc"
}],
"count": 1000
}}}
to all future debugger - until today i.e 15-07-21
Browser also don't support sending customer headers to the server, so any such code
import * as sock from 'websocket'
const headers = {
Authorization: "bearer " + token
};
console.log(headers);
const wsclient = new sock.w3cwebsocket(
'wss://' + 'myserver.com' + '/api/ws',
'',
'',
headers,
null
);
This is not going to work in browser. The reason behind that is browser native Websocket constructor does not accept headers.
You can easily get misguided because w3cwebsocket contractor accepts headers as i have shown above. This works in node.js however.
The recommended way to do this is through URL query parameters
// authorization: Basic abc123
// content-type: application/json
let ws = new WebSocket(
"ws://example.com/service?authorization=basic%20abc123&content-type=application%2Fjson"
);
This is considered a safe best-practice because:
Headers aren't supported by WebSockets
Headers are advised against during the HTTP -> WebSocket upgrade because CORS is not enforced
SSL encrypts query paramaters
Browsers don't cache WebSocket connections the same way they do with URLs
What I have found works best is to send your jwt to the server just like a regular message. Have the server listening for this message and verify at that point. If valid add it to your stored list of connections. Otherwise send back a message saying it was invalid and close the connection. Here is the client side code. For context the backend is a nestjs server using Websockets.
socket.send(
JSON.stringify({
event: 'auth',
data: jwt
})
);
My case:
I want to connect to a production WS server a www.mycompany.com/api/ws...
using real credentials (a session cookie)...
from a local page (localhost:8000).
Setting document.cookie = "sessionid=foobar;path=/" won't help as domains don't match.
The solution:
Add 127.0.0.1 wsdev.company.com to /etc/hosts.
This way your browser will use cookies from mycompany.com when connecting to www.mycompany.com/api/ws as you are connecting from a valid subdomain wsdev.company.com.
You can pass the headers as a key-value in the third parameter (options) inside an object.
Example with Authorization token. Left the protocol (second parameter) as null
ws = new WebSocket(‘ws://localhost’, null, { headers: { Authorization: token }})
Edit: Seems that this approach only works with nodejs library not with standard browser implementation. Leaving it because it might be useful to some people.
Technically, you will be sending these headers through the connect function before the protocol upgrade phase. This worked for me in a nodejs project:
var WebSocketClient = require('websocket').client;
var ws = new WebSocketClient();
ws.connect(url, '', headers);