Custom app in Microsoft Teams can't connect to it's own API from desktop app - microsoft-teams

I am trying to embed our app into Teams. We have it working from the web version of MS Teams. However when we try it from the MS Teams desktop app, our app's tab is unable to communicate with our API saying net::ERR_BLOCKED_BY_RESPONSE for all XHR requests the app tries to make to the API. The OPTIONS request seems to get some response headers back but is still listed as failed (see header below)
What is blocking the requests in the Desktop app? We have tried various security header changes and disabling our apps service worker among other things but to no avail. The application loads fine (we have the iframe enabling headers set in the CSP - see below). It's just the communication with the API that is blocked.
Application details that we are embedding in the MS Teams APP as a custom app
Progressive web app
Angular v13 with
Angular routing strategy (useHash = false)
API is REST based
NB: The app tab works in the mobile app version of teams on iOS as well as on the web version
Response headers that are returned by the API we are trying to talk to on the OPTIONS request
access-control-allow-headers: Content-Type, Authorization, V, D
access-control-allow-methods: OPTIONS, GET, POST, DELETE, PUT
access-control-allow-origin: https://**redacted**
access-control-max-age: 86400
cache-control: no-cache
content-length: 0
date: Wed, 12 Jan 2022 00:14:44 GMT
permissions-policy: geolocation=()
referrer-policy: no-referrer
server: CloudFront
status: 200
strict-transport-security: max-age=31536000; includeSubDomains
vary: Origin
via: 1.1 **redacted**.cloudfront.net (CloudFront)
x-amz-cf-id: **redacted**
x-amz-cf-pop: SYD62-P1
x-cache: Miss from cloudfront
x-content-type-options: nosniff
x-xss-protection: 1; mode=block
Headers and CSP on the application (SPA)
age: 6
cache-control: no-cache
content-encoding: br
content-security-policy: default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://*.**redacted**.com https://**redacted**.app https://*.**redacted**.app; connect-src https://*.**redacted**.com https://**redacted**.app https://*.**redacted**.app; object-src 'none'; frame-ancestors teams.microsoft.com *.office.com outlook.office.com outlook.office365.com
content-type: text/html
cross-origin-embedder-policy: require-corp
cross-origin-opener-policy: same-origin
cross-origin-resource-policy: same-origin
date: Wed, 12 Jan 2022 00:22:58 GMT
etag: **redacted**
expect-ct: max-age=0, report-uri="https://**redacted**.report-uri.com/r/d/ct/reportOnly"
last-modified: Tue, 11 Jan 2022 23:27:02 GMT
permissions-policy: geolocation=()
referrer-policy: no-referrer-when-downgrade
server: CloudFront
status: 304
strict-transport-security: max-age=31536000; includeSubDomains
vary: Accept-Encoding
via: 1.1 **redacted**.cloudfront.net (CloudFront)
x-amz-cf-id: **redacted**
x-amz-cf-pop: SYD62-P1
x-cache: Hit from cloudfront
x-content-type-options: nosniff
x-xss-protection: 1; mode=block
The manifest, currently setup with a configure page that adds a tab after choosing our customer account name which generates the url for the tab.
{
"$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.11/MicrosoftTeams.schema.json",
"manifestVersion": "1.11",
"version": "4.1.8",
"id": "***",
"packageName": "app.***",
"developer": {
"name": "*** Pty Limited",
"websiteUrl": "https://www.***.com",
"privacyUrl": "https://www.***.com/privacy",
"termsOfUseUrl": "https://***.com/resources/20201019+***+Terms+and+Conditions.pdf"
},
"icons": {
"color": "color.png",
"outline": "outline.png"
},
"name": {
"short": "***",
"full": "*** For Microsoft Teams"
},
"description": {
"short": "***",
"full": "***"
},
"accentColor": "#F9F9FA",
"configurableTabs": [
{
"configurationUrl": "https://dev.***.app/ms-teams/teams-configure.html",
"canUpdateConfiguration": true,
"scopes": [
"team"
],
"context": [
"channelTab"
],
"supportedSharePointHosts": [
"sharePointFullPage"
]
}
],
"permissions": [
"identity",
"messageTeamMembers"
],
"validDomains": [
"dev.***.app",
"*.dev.***.app",
"*.teams.***.app",
"api.test.***.com",
"api.***.com",
"*.test.***.app",
"*.***.app"
]
}

Related

Multiple Set-Cookie headers ignored by API Gateway in combination with Lambda integration and CloudFront

My setup looks like this:
|––––––––––––| |–––––––––––––| |–––––––––––––––––|
| | <- origin 1 -> | API Gateway | <-> | Lambda function |
| | |–––––––––––––| |–––––––––––––––––|
| CloudFront |
| | |–––––––––––––|
| | <- origin 2 -> | S3 bucket |
|––––––––––––| |–––––––––––––|
I need CloudFront in front of the API Gateway to get automatic http->https redirection.
I'm using a custom login.example.com subdomain w/ CloudFront.
API Gateway's generated URL is the origin 1 for CloudFront distribution.
This all works as expected.
I can even return one Set-Cookie header from the lambda function and it will get passed on until it reaches the browser.
{
"statusCode": 302,
"body": "",
"headers": {
"location": "/test",
"surrogate-control": "no-store",
"cache-control": "no-store, no-cache, must-revalidate, proxy-revalidate",
"pragma": "no-cache",
"expires": "0",
"content-length": "0",
"date": "Fri, 19 Feb 2021 17:25:56 GMT",
"connection": "keep-alive",
"set-cookie": "cookie1=68abcdbefbef7d84c26e68; Max-Age=2592000; Domain=example.com; Path=/; HttpOnly; Secure; SameSite=Strict"
},
"isBase64Encoded": false
}
Adding another one isn't working - as expected when you look at the docs:
https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#apigateway-multivalue-headers-and-parameters
https://aws.amazon.com/blogs/compute/support-for-multi-value-parameters-in-amazon-api-gateway/
{
"statusCode": 302,
"headers": {
"location": "/test",
"set-cookie": [
"cookie1=68abcdbefbef7d84c26e68; Max-Age=2592000; Domain=example.com; Path=/; HttpOnly; Secure; SameSite=Strict",
"cookie2-login=; Max-Age=0; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly; Secure"
],
"surrogate-control": "no-store",
"cache-control": "no-store, no-cache, must-revalidate, proxy-revalidate",
"pragma": "no-cache",
"expires": "0",
"content-length": "0"
}
}
Both of these will be ignored/removed.
But even when I'm using the multiValueHeaders object to return more than one of the same kind like this:
{
"statusCode": 302,
"body": "",
"headers": {
"location": "/test",
"surrogate-control": "no-store",
"cache-control": "no-store, no-cache, must-revalidate, proxy-revalidate",
"pragma": "no-cache",
"expires": "0",
"content-length": "0",
"date": "Fri, 19 Feb 2021 17:25:56 GMT",
"connection": "keep-alive"
},
"isBase64Encoded": false,
"multiValueHeaders": {
"Set-Cookie": [
"cookie1=68abcdbefbef7d84c26e68; Max-Age=2592000; Domain=example.com; Path=/; HttpOnly; Secure; SameSite=Strict",
"cookie2-login=; Max-Age=0; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly; Secure"
]
}
}
the API Gateway removes/ignores them from the response it passes on to CloudFront.
What am I doing wrong?
Do I have to map something in the API Gateway when using the multiValueHeaders?
Normal headers['set-cookie'] is passed on automatically but multiValueHeaders not?
Are the additional attributes a problem?
Is it a problem that I'm trying to set the cookie for the root-domain and not the login.example.com domain?
Not sure how Philipp missed this, as it was on the same page he cited:
To customize the response, your Lambda function should return a response with the following format.
{
"cookies" : ["cookie1", "cookie2"],
"isBase64Encoded": true|false,
"statusCode": httpStatusCode,
"headers": { "headername": "headervalue", ... },
"body": "Hello from Lambda!"
}
So just return cookies: ['name=value', 'name=value']
Source: https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html#http-api-develop-integrations-lambda.v2
Finally found the answer myself:
The new payload format (2.0) does not support multiValueHeaders.
Working with AWS Lambda proxy integrations for HTTP APIs
[...] Format 2.0 doesn't have multiValueHeaders or multiValueQueryStringParameters fields. Duplicate headers are combined with commas and included in the headers field. Duplicate query strings are combined with commas and included in the queryStringParameters field. [...]
https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html
So I'm rewriting the set-cookie headers to different spellings:
Set-cookie
sEt-cookie
seT-cookie
That was the way you had to do it before there were multiValueHeaders - but it seems it is still the only when you're using the new payload format :(

Hyperledger composer REST API does not return transaction id

Why is my composer REST server not returning the transaction id of the request, or where can I find the id?
curl -X POST --header 'Content-Type: application/json' --header 'Accept: application/json' -d '{ \
"$class": "org.example.biznet.SampleAsset", \
"assetId": "1", \
"value": "103300" \
}' 'http://localhost:3000/api/SampleAsset'
Response:
{
"date": "Sun, 04 Feb 2018 15:59:10 GMT",
"x-content-type-options": "nosniff",
"etag": "W/\"52-0IlZYwJKBE6+rHqbXGCNVeELkQ0\"",
"x-download-options": "noopen",
"x-frame-options": "DENY",
"content-type": "application/json; charset=utf-8",
"access-control-allow-origin": "http://localhost:3000",
"access-control-allow-credentials": "true",
"connection": "keep-alive",
"vary": "Origin, Accept-Encoding",
"content-length": "82",
"x-xss-protection": "1; mode=block"
}
As I can see in the pull request it should be solved: https://github.com/hyperledger/composer/pull/843
My versions:
├── composer-cli#0.16.3
├── composer-playground#0.16.3
├── composer-rest-server#0.16.3
It looks like you have submitted a POST request to an Asset (SampleAsset) which being an Asset does not generate a Transaction ID. If you POST to a Transaction you will get a Transaction ID - and you will see it in the Response Body, not the Response Header. See example below.

Why doesn’t the form not POST in Excel 2016 for Mac?

We are testing an add-in for Excel that has a form that is POSTed to https://httpbin.org/anything. The form will not POST in Excel 2016 version 15.39 (171010) for Mac (High Sierra ver 10.13.1). Here are the HTML form’s essentials:
<script type="text/javascript">
//submit form
$("#testForm").submit();
</script>
</head>
<body>
<form method="POST" id="testForm" action="https://httpbin.org/anything" accept-charset="UTF-8" target="_blank">
<div>
<input type='hidden' name='mergeDataFormat' value='csv'>
</div>
<div>
<input type="hidden" name="mergeData" id="mergeData" value='Name,Street,"City, State",ZIP Code'>
</div>
<input type="submit" value="Send" id="submitForm" />
</form>
AppDomain was set in the Manifest:
<AppDomains>
<AppDomain>https://httpbin.org/</AppDomain>
...
</AppDomains>
This is what we see in Charles HTTP monitor ver 4.2:
GET /anything HTTP/1.1
Host httpbin.org
Connection keep-alive
User-Agent Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36
Upgrade-Insecure-Reque
sts 1
Accept text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding gzip, deflate, br
Accept-Language en-US,en;q=0.9
Cookie _gauges_unique_day=1; _gauges_unique_month=1; _gauges_unique_year=1; _gauges_unique=1
HTTP/1.1 200 OK
Server: meinheld/0.6.1
Date: Wed, 15 Nov 2017 18:07:11 GMT
Content-Type: application/json
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
X-Powered-By: Flask
X-Processed-Time: 0.00142621994019
Content-Length: 726
Via: 1.1 vegur
Connection: Keep-alive
{
"args": {},
"data": "",
"files": {},
"form": {},
"headers": {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "en-US,en;q=0.9",
"Connection": "close",
"Cookie": "_gauges_unique_day=1; _gauges_unique_month=1; _gauges_unique_year=1; _gauges_unique=1",
"Host": "httpbin.org",
"Upgrade-Insecure-Requests": "1",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36"
},
"json": null,
"method": "GET",
"origin": "<ip-address-here>",
"url": "https://httpbin.org/anything"
}
After pressing the Send button, the httpbin.org page pops up to show this:
{
"args": {},
"data": "",
"files": {},
"form": {},
"headers": {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "en-US,en;q=0.9",
"Connection": "close",
"Cookie": "_gauges_unique_day=1; _gauges_unique_month=1; _gauges_unique_year=1; _gauges_unique=1",
"Host": "httpbin.org",
"Upgrade-Insecure-Requests": "1",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36"
},
"json": null,
"method": "GET",
"origin": "<ip-address-here>",
"url": "https://httpbin.org/anything"
}
It seems like there was no POST to the URL. No errors seen in Excel. The form can be POSTed successfully to the same URL in a browser on the Mac. This issue is seen only in Excel 2016 for Mac. The same code works fine in Excel 2016 for Windows (7 and 10) and Excel online. Any insights to help resolve this is much appreciated.
See related question Is the Excel add-in incompatible with Excel 2016 for Mac when using POST method for forms?
Update Excel 2016 on Mac to version 16.9.0 (18011602). We are able to POST from Excel 2016 for Mac! Thanks to the entire MS team.
Please refer this post for latest on this. Seems to be a duplicate issue.

Facebook analytics doesn't recognize platform

I have FB analytics configured for iOS and android app. It does recognize iOS platform when viewing events correctly. But for android it only qualifies it as android about 30% of all.
Here's how those events are shown in FB analytics debugging screen: ('inne wartości' stands for 'other')
I wonder how FB does the recognition? By advertising id? Read docs through but didn't find info about this anywhere.
Here's a FB response that gets recognized as android: (obfuscated few details):
{
"req": {
"method": "POST",
"url": "https://graph.facebook.com/v2.6/000000000/activities",
"data": {
"advertiser_id": "2e9ab235-84c0-4b22-8421-xxxxxxxxxxxxx",
"advertiser_tracking_enabled": 1,
"application_tracking_enabled": 1,
"bundle_id": "com.myapp",
"bundle_short_version": "1.7.0",
"event": "CUSTOM_APP_EVENTS",
"custom_events": [
{
"_appVersion": "1.7.0",
"_eventName": "App Launched",
"_logTime": 1476295031,
"fb_currency": "USD"
}
]
},
"headers": {
"user-agent": "Segment.io/1.0",
"content-type": "application/json"
}
},
"header": {
"access-control-allow-origin": "*",
"pragma": "no-cache",
"cache-control": "private, no-cache, no-store, must-revalidate",
"facebook-api-version": "v2.6",
"expires": "Sat, 01 Jan 2000 00:00:00 GMT",
"content-type": "text/javascript; charset=UTF-8",
"x-fb-trace-id": "BH4kXNYDDL+",
"x-fb-rev": "2617769",
"x-fb-debug": "eNcBCpuFfVirTkclVvZ0WeYh60r+2tBIqg6lKPCrWNtGASVULq6jrVwQxqYulMUyCI3ZaBhZRQ64xdxdXOQg2w==",
"date": "Wed, 12 Oct 2016 17:57:24 GMT",
"connection": "close",
"content-length": "16"
},
"status": 200,
"text": "{\"success\":true}"
}
And this one is not recognized as android:
{
"req": {
"method": "POST",
"url": "https://graph.facebook.com/v2.6/000000000000/activities",
"data": {
"advertiser_id": "88697a4d-f658-41d3-84db-xxxxxxxxxx",
"advertiser_tracking_enabled": 1,
"application_tracking_enabled": 1,
"bundle_id": "com.myapp",
"bundle_short_version": "1.7.0",
"event": "CUSTOM_APP_EVENTS",
"custom_events": [
{
"_appVersion": "1.7.0",
"_eventName": "App Launched",
"_logTime": 1476206487,
"fb_currency": "USD"
}
]
},
"headers": {
"user-agent": "Segment.io/1.0",
"content-type": "application/json"
}
},
"header": {
"access-control-allow-origin": "*",
"pragma": "no-cache",
"cache-control": "private, no-cache, no-store, must-revalidate",
"facebook-api-version": "v2.6",
"expires": "Sat, 01 Jan 2000 00:00:00 GMT",
"content-type": "text/javascript; charset=UTF-8",
"x-fb-trace-id": "AqASb9qsUSx",
"x-fb-rev": "2613995",
"x-fb-debug": "aDLUDoC+7vmlVY28UsEtusYgzdkmxK8qVrn4gCo29ovLw9Us27Gh/Iy1163dfTDF5rKg/JWiv3xsfq3hugijlg==",
"date": "Tue, 11 Oct 2016 17:21:37 GMT",
"connection": "close",
"content-length": "16"
},
"status": 200,
"text": "{\"success\":true}"
}
I can't see any interesting changes that that would point to an android platform.
Update
As advised by Ramkumar, I tried posting events with FB sdk and it indeed worked. I'd really like to keep using segment, so keeping this as solution is not possible in my app.
I wonder what's missing from the params that segment sends to FB. Is this because of session_id which is in there when using FB SDK but is absent in segment request? See gist with dump from adb logcat when using FB SDK
From your post, it looks like you are using segment.io to send events to our servers. If you use the FB SDK to send events you will not see this problem - could you please give that a try?

FineUploaderS3: socket connection to the server was not read from or written to within the timeout period

We are using FineUploader 5.4.1's jQuery uploader. Periodically, our uploads to AWS S3 are failing. It's working consistently from one of our office locations and failing from the other.
Request:
Accept:/
Accept-Encoding:gzip, deflate
Accept-Language:en-US,en;q=0.8
Cache-Control:no-cache
Connection:keep-alive
Content-Length:1129759
Content-Type:multipart/form-data; boundary=----WebKitFormBoundarySNlCNy1xiPquuzCl
Host:s3.amazonaws.com
Origin:
Pragma:no-cache
Referer:
User-Agent:Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/47.0.2526.73 Chrome/47.0.2526.73 Safari/537.36
Request Payload
------WebKitFormBoundarySNlCNy1xiPquuzCl
Content-Disposition: form-data; name="key"
virginia/original/b71bb640-c771-44d9-9d28-44a075197bc3.png
------WebKitFormBoundarySNlCNy1xiPquuzCl
Content-Disposition: form-data; name="Content-Type"
image/png
------WebKitFormBoundarySNlCNy1xiPquuzCl
Content-Disposition: form-data; name="success_action_status"
200
------WebKitFormBoundarySNlCNy1xiPquuzCl
Content-Disposition: form-data; name="acl"
public-read
------WebKitFormBoundarySNlCNy1xiPquuzCl
Content-Disposition: form-data; name="x-amz-meta-qqparentuuid"
3d343555-7821-43d3-a954-aba2ce17f597
------WebKitFormBoundarySNlCNy1xiPquuzCl
Content-Disposition: form-data; name="x-amz-meta-qqparentsize"
983029
------WebKitFormBoundarySNlCNy1xiPquuzCl
Content-Disposition: form-data; name="x-amz-meta-qquuid"
b71bb640-c771-44d9-9d28-44a075197bc3
------WebKitFormBoundarySNlCNy1xiPquuzCl
Content-Disposition: form-data; name="x-amz-meta-qqfilename"
missing%20(o).png
------WebKitFormBoundarySNlCNy1xiPquuzCl
Content-Disposition: form-data; name="AWSAccessKeyId"
AKIAIVACH6FJDZWZ5LYA
------WebKitFormBoundarySNlCNy1xiPquuzCl
Content-Disposition: form-data; name="policy"
eyJleHBpcmF0aW9uIjoiMjAxNS0xMi0zMFQwNzoyMDowMy41NjBaIiwiY29uZGl0aW9ucyI6W3siYWNsIjoicHVibGljLXJlYWQifSx7ImJ1Y2tldCI6InZpcmdpbmlhLmlua2l2ZS5jb20ifSx7IkNvbnRlbnQtVHlwZSI6ImltYWdlXC9wbmcifSx7InN1Y2Nlc3NfYWN0aW9uX3N0YXR1cyI6IjIwMCJ9LHsia2V5IjoidmlyZ2luaWFcL29yaWdpbmFsXC9iNzFiYjY0MC1jNzcxLTQ0ZDktOWQyOC00NGEwNzUxOTdiYzMucG5nIn0seyJ4LWFtei1tZXRhLXFxcGFyZW50dXVpZCI6IjNkMzQzNTU1LTc4MjEtNDNkMy1hOTU0LWFiYTJjZTE3ZjU5NyJ9LHsieC1hbXotbWV0YS1xcXBhcmVudHNpemUiOiI5ODMwMjkifSx7IngtYW16LW1ldGEtcXF1dWlkIjoiYjcxYmI2NDAtYzc3MS00NGQ5LTlkMjgtNDRhMDc1MTk3YmMzIn0seyJ4LWFtei1tZXRhLXFxZmlsZW5hbWUiOiJtaXNzaW5nJTIwKG8pLnBuZyJ9LFsiY29udGVudC1sZW5ndGgtcmFuZ2UiLCIwIiwiMTUwMDAwMDAwIl1dfQ==
------WebKitFormBoundarySNlCNy1xiPquuzCl
Content-Disposition: form-data; name="signature"
J7XwHe5kmIVXznw+8cIiGwiyLbE=
------WebKitFormBoundarySNlCNy1xiPquuzCl
Content-Disposition: form-data; name="file"; filename="blob"
Content-Type: image/png
------WebKitFormBoundarySNlCNy1xiPquuzCl--
Response:
<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>RequestTimeout</Code><Message>Your socket connection to the server was not read from or written to within the timeout period. Idle connections will be closed.</Message><RequestId>C7E8E596E8FF701A</RequestId><HostId>aIePbPl+NPEr3/6LZkV9KlFzVpPTapnROfoNgI/DN+/t1zrttudxSADiPcCdwVnBq15BDKeodwY=</HostId></Error>
On the same network, AWS CLI managed to upload the same file successfully multiple times.
Our FineUploader settings:
{
uploaderType: 'basic',
maxConnections: 3,
request: {
endpoint: <OUR BUCKET>,
accessKey: <OUR UPLOAD KEY>
},
signature: {
endpoint: <OUR UPLOAD SIGNATURE ENDPOINT>
},
uploadSuccess: {
endpoint: <OUR UPLOAD SUCCESS ENDPOINT>,
params: {
param1: "Hello",
param2: "World"
}
},
iframeSupport: {
localBlankPagePath: <HTML FILE URL>
},
retry: {
showButton: true
},
chunking: {
enabled: true
},
resume: {
enabled: true
},
validation: {
sizeLimit: 150000000
},
messages: {
typeError: '{file} has an invalid extension. Valid extension(s): {extensions}.'
}
};
Tried with and without chunking.
Sounds like a pretty clear-cut occasional network issue at one of your offices. You can read more about potential causes at https://github.com/aws/aws-sdk-js/issues/281. That case suggests that the Content-Length, which is set by the browser and not Fine Uploader, is larger than the actual number of bytes sent. This to me suggests that a network issue is preventing all bytes from being sent in some cases.

Resources