Works via Postman but not in Flutter: API call with GCS pre-signed URL - flutter

I'm trying to upload a video file to GCS using a pre-signed url. I've managed to create the url via Google but now I am facing a problem using it.
Upload works in Postman, got response 200.
postman body, postman params
Code copied from Postman results in 403 Forbidden (SignatureDoesNotMatch):
Future<http.StreamedResponse> uploadVideo(
{required String uploadURL, required String filePath}) async {
var headers = {'Content-Type': 'application/octet-stream'};
var request = http.MultipartRequest('PUT', Uri.parse(uploadURL));
request.files.add(await http.MultipartFile.fromPath('file', filePath));
request.headers.addAll(headers);
http.StreamedResponse response = await request.send();
if (response.statusCode == 200) {
print(await response.stream.bytesToString());
} else {
print(response.reasonPhrase);
}
return response;
}
This is the error I am getting from Google:
<?xml version='1.0' encoding='UTF-8'?><Error><Code>SignatureDoesNotMatch</Code><Message>The request signature we calculated does not match the signature you provided. Check your Google secret key and signing method.</Message><StringToSign>GOOG4-RSA-SHA256
20210803T082850Z
20210803/auto/storage/goog4_request
6d513846a3db49f949b0d2eea8f04b90f918b3b94588c3ed55ed3620b7d7e1f6</StringToSign><CanonicalRequest>PUT
/phonedo-interviews/app-test/007/2.mp4
X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=interviews%40interviews-317011.iam.gserviceaccount.com%2F20210803%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20210803T082850Z&X-Goog-Expires=900&X-Goog-SignedHeaders=content-type%3Bhost
content-type:multipart/form-data; boundary=dart-http-boundary-6w1yq6BQN3EkGBrhHZnwidOXZsBecsgSwTT3nBjB9vQCToHt0cg
host:storage.googleapis.com
content-type;host
UNSIGNED-PAYLOAD</CanonicalRequest></Error>
Note: I needed Content-Type to be application/octet-stream so I disabled that header in Postman's automatic headers and added Content-Type manually. When I didn't do that I also got 403.

The solution was to send the file in binary.
Here is the working code:
Future<http.Response> uploadVideo(
{required String uploadURL, required String filePath}) async {
var response = await http.put(
Uri.parse(uploadURL),
headers: {'content-type': 'application/octet-stream'},
body: File(filePath).readAsBytesSync(),
);

In your Postman headers, a Token is given to GCS (first line). Given that you need authorization, Postman probably has this Token saved somewhere application-wise.
In this flutter code, the headers you're giving do not include an Auth token and therefore you're receiving a 403 error.

Related

Flutter post request headers do not appear on Google Cloud Function

I am doing a post request in flutter to a google cloud function:
final uri = Uri.parse(
'https://example.cloudfunctions.net/send_to_queue');
final bearer = 'Bearer ${await user.getIdToken()}';
final response = await http.post(uri, body: json.encode(data),
headers: {HttpHeaders.authorizationHeader: bearer, 'Content-Type': 'application/json'});
In the Google Cloud I print(request.headers) I see a bunch of headers but no Authorization or Content-Type headers.
What should I do?
P.S. Same issue in here Flutter calling firebase cloud function admin.auth.updateUser but I don't want to use a callable function
The browser was sending an OPTIONS request (preflight) before the POST.
I needed to change the Google Cloud Function to handle this:
def main(request):
# Set CORS headers for the preflight request
if request.method == 'OPTIONS':
# Allows GET requests from any origin with the Content-Type
# header and caches preflight response for an 3600s
headers = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Access-Control-Max-Age': '3600'
}
return ('', 204, headers)
# Get token from request
token = request.headers.get('Authorization').split('Bearer ')[1]
etc..

CORS error: Request header field authorization is not allowed by Access-Control-Allow-Headers in preflight response

I'm trying to fetch an image resource that's part of a conversation message.
I've tried both FETCH as well as using AXIOS but I'm getting the same error message.
Here's an example of my FETCH request
const token = `${accountSid}:${authToken}`;
const encodedToken = Buffer.from(token).toString('base64');
let response = await fetch('https://mcs.us1.twilio.com/v1/Services/<SERVICE_SID>/Media/<MEDIA_SID>',
{
method:'GET',
headers: {
'Authorization': `Basic ${encodedToken}`,
}
});
let data = await response.json();
console.log(data);
And here's what Axios looked like
let config = {
method: 'get',
crossdomain: true,
url: 'https://mcs.us1.twilio.com/v1/Services/<SERVICE_SID>/Media/<MEDIA_SID>',
headers: {
'Authorization': `Basic ${encodedToken}`,
},
};
try {
const media = await axios(config);
console.dir(media);
} catch(err) {
console.error(err);
}
Both ways are NOT working.
After looking into it more, I found out that Chrome makes a pre-flight request and as part of that requests the allowed headers from the server.
The response that came back was this
as you can see, in the "Response Headers" I don't see the Access-Control-Allow-Headers which should have been set to Authorization
What am I missing here?
I have made sure that my id/password as well as the URL i'm using are fine. In fact, I've ran this request through POSTMAN on my local machine and that returned the results just fine. The issue is ONLY happening when I do it in my code and run it in the browser.
I figured it out.
I don't have to make an http call to get the URL. It can be retrieved by simply
media.getContentTemporaryUrl();

When making a request to the Vision API Product Search an error occurs "message": "The request is missing a valid API key."

When I register a service account for the Vision API Product Search there's a json file downloaded into my desktop that has the private key. However, when making a request into this api there's no place to send that JSON. I'll show you the documentation and my code.
I didn't understand also what is the curl request and how to send it using the http post request.
And This is my code:
Future<void> uploadProductSet() async {
var projectId = 'estoOne';
var locationId = 'europe-west1';
var url = 'https://vision.googleapis.com/v1/projects/$projectId/locations/$locationId/productSets';
final responseOne = await http
.post(Uri.parse(url),
body: json.encode({
'displayName': 'Product-Set-One',
}))
.catchError((error) {
throw error;
});
print(resoinseOne.body);
}
You have to send your access token with the Authorization header.
The API seems to use the Bearer authentication method.
So set the following header in your http request: Bearer $authToken
You should get the auth-token from the credentials file you've downloaded
So your code should look something like this: (untested)
await http.post(Uri.parse(url),
headers: { 'Authorization': 'Bearer $authToken' },
body: json.encode({
'displayName': 'Product-Set-One',
})).catchError((error) {
throw error
})

How to remove Authorization header on redirect on any Flutter/Dart http client

I'm currently working on a project which like a lot of other projects works with s3 storage. In this case the storage is linked via the back-end.
The situation is like this, I can get the 'attachment' via an URL, lets say example.com/api/attachments/{uuid}. If the user is authorized (via the header Authorization) it should return a 302 statuscode and redirect to the s3 url. The problem is that after the redirect the Authorization header persists and the http client return a 400 response and it's because of the persisting Authorization header. Is there any way I can remove the Authorization header after redirect without catching the first request and firing a new one?
My http client code currently looks like this:
#override
Future get({
String url,
Map<String, dynamic> data,
Map<String, String> parameters,
}) async {
await _refreshClient();
try {
final response = await dio.get(
url,
data: json.encode(data),
queryParameters: parameters,
);
return response.data;
} on DioError catch (e) {
throw ServerException(
statusCode: e.response.statusCode,
message: e.response.statusMessage,
);
}
}
Future<void> _refreshClient() async {
final token = await auth.token;
dio.options.baseUrl = config.baseUrl;
dio.options.headers.addAll({
'Authorization': 'Bearer $token',
'Accept': 'application/json',
});
dio.options.contentType = 'application/json';
}
Good news! This has been fixed recently with Dart 2.16 / Flutter v2.10!
Related bugs in dart issue tracker:
https://github.com/dart-lang/sdk/issues/47246
https://github.com/dart-lang/sdk/issues/45410
Official announcement:
https://medium.com/dartlang/dart-2-16-improved-tooling-and-platform-handling-dd87abd6bad1
TLDR: upgrade to Flutter v2.10!
Looking at the Dio docs, it seems like this is intentional behaviour.
All headers added to the request will be added to the redirection request(s). However, any body send with the request will not be part of the redirection request(s).
https://api.flutter.dev/flutter/dart-io/HttpClientRequest/followRedirects.html
However, I understand (and agree!) that this is generally undesirable behaviour. My solution is to manually follow the redirects myself, which is not very nice but works in a pinch.
Response<String> response;
try {
response = await dio.get(
url,
options: Options(
// Your headers here, which might be your auth headers
headers: ...,
// This is the key - avoid following redirects automatically and handle it ourselves
followRedirects: false,
),
);
} on DioError catch (e) {
final initialResponse = e.response;
// You can modify this to understand other kinds of redirects like 301 or 307
if (initialResponse != null && initialResponse.statusCode == 302) {
response = await dio.get(
initialResponse.headers.value("location")!, // We must get a location header if we got a redirect
),
);
} else {
// Rethrow here in all other cases
throw e;
}
}

Node Js Restapi - Calling Post Method in Flutter Doesn't Work

I have local api and jwt by node js i want to send my username and password from flutter and send it to node js to give me this token and later i will save it in local storage but when i tell flutter post to the api doesn't post
my code :
You can use http method here like below Future example
Future postContact(access_token,name,email,phone,message) async {
String apiUrl = '$_apiUrl/user/contact-us';
http.Response response = await http.post(apiUrl,headers: {
"Accept": "application/json",
"Accesstoken": "Bearer $access_token"
}, body: {'name':'$name','email':'$email','phone':'$phone','message':'$message'});
print("Result: ${response.body}");
return json.decode(response.body);
}