This is edited from original post:
From the docs:
Signing a Message The CB-ACCESS-SIGN header is generated by creating a
sha256 HMAC using the base64-decoded secret key on the prehash string
timestamp + method + requestPath + body (where + represents string
concatenation) and base64-encode the output. The timestamp value is
the same as the CB-ACCESS-TIMESTAMP header.
Here is information from a key I deleted. This is from Coinbase Pro Sandbox:
publicKey:
06057d5b5e03d0f8587a248330402b21
passPhrase:
gcgs6k6rp0f
secretKey: EFAToD5heo66GIgZlT2TIZzJf8TYlmxyeRxRYDHTBv3lTt9XN6uaNS0RNAy0os/caR47x6EiPDOV3Ik+YzrfEA==
I'm using angular, specifically the node.js crypto-js library:
private generateSignaturePro(timestamp: string, method: string, resourceUrl: string, requestBody: string): string {
var prehash: string = timestamp + method + resourceUrl + requestBody;
var key = (Buffer.from(this.secretKey, 'base64')).toString();
return crypto.enc.Base64.stringify(crypto.HmacSHA256(prehash, key));
}
Server time is Time: 2019-05-20T19:01:38.711Z Epoch: 1558378898.711 (from /time endpoint)
here is my request and the server response:
Request:
Request URL: https://api-public.sandbox.pro.coinbase.com/accounts
Request Method: GET
Status Code: 400
Remote Address: 104.16.161.226:443
Referrer Policy: no-referrer-when-downgrade
Request Headers:
Provisional headers are shown
Accept: application/json, text/plain, */*
CB-ACCESS-KEY: 06057d5b5e03d0f8587a248330402b21
CB-ACCESS-PASSPHRASE: gcgs6k6rp0f
CB-ACCESS-SIGN: 0cc2BnQYdUhLucXSPwMTjpHjJ32G3RXSH44rSsEopvjAtY90uRCMVy6xUrzg/A/aRJBLqx390fcZc7lmJeP++g==
CB-ACCESS-TIMESTAMP: 1558378899
Referer: https://localhost:44342/dashboard
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Safari/537.36
Response Headers:
access-control-allow-headers: Content-Type, Accept, cb-session, cb-fp
access-control-allow-methods: GET,POST,DELETE,PUT
access-control-allow-origin: *
access-control-expose-headers: cb-before, cb-after, cb-gdpr
access-control-max-age: 7200
cache-control: no-store
cf-cache-status: MISS
cf-ray: 4da08f74ba97cf68-IAD
content-length: 31
content-type: application/json; charset=utf-8
date: Mon, 20 May 2019 19:01:38 GMT
etag: W/"1f-4RjKVp8I05+xcnQ5/G16yRoMSKU"
expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
server: cloudflare
status: 400
strict-transport-security: max-age=15552000; includeSubDomains
vary: Accept-Encoding
x-content-type-options: nosniff
x-dns-prefetch-control: off
x-download-options: noopen
x-frame-options: SAMEORIGIN
x-xss-protection: 1; mode=block
Response:
{"message":"invalid signature"}
What am I doing wrong?
EDIT: Changed method to the SHA 256 version. Still doesn't work.
I ran into same issue and my code was same as yours basically. I changed to the following (c#) and it finally worked. Weird thing is coinbase pro is only exchange i have had issues with so far with the signature. In any case here is the code that worked for me. Hope this helps. Would have saved me hours
public string ComputeSignature(
HttpMethod httpMethod,
string secret,
double timestamp,
string requestUri,
string contentBody = "")
{
var convertedString = System.Convert.FromBase64String(secret);
var prehash = timestamp.ToString("F0", CultureInfo.InvariantCulture) + httpMethod.ToString().ToUpper() + requestUri + contentBody;
return HashString(prehash, convertedString);
}
private string HashString(string str, byte[] secret)
{
var bytes = Encoding.UTF8.GetBytes(str);
using (var hmaccsha = new HMACSHA256(secret))
{
return System.Convert.ToBase64String(hmaccsha.ComputeHash(bytes));
}
}
From the gdax-java (as it was named prior to "coinbase pro") library the generate signature method is:
String prehash = timestamp + method.toUpperCase() + requestPath + body;
byte[] secretDecoded = Base64.getDecoder().decode(secretKey);
keyspec = new SecretKeySpec(secretDecoded, "HmacSHA256");
sha256 = (Mac) GdaxConstants.SHARED_MAC.clone();
sha256.init(keyspec);
return Base64.getEncoder().encodeToString(sha256.doFinal(prehash.getBytes()));
At least on initial inspection the code you're using specifies using SHA512 rather than HmacSHA256, so I'd suspect that to be a probabale cause.
There is also more help with NodeJS in the right hand column here for generating the signatures. https://docs.pro.coinbase.com/#creating-a-request
Had the same issue here. For me the answer was to use luxon DateTime instead of the native js Date functions as shown in the coinbase docs.
Here is the typescript that works for me. You can use the results of this function to populate your request headers.
import crypto from 'crypto';
import { DateTime } from 'luxon';
export const auth = (
method: 'GET' | 'POST',
path: string,
body?: Record<string, unknown>
) => {
const timestamp = DateTime.utc().toMillis() / 1000;
let message = timestamp + method + path;
if (body) {
message += JSON.stringify(body);
}
const secret = Buffer.from('YOUR_SECRET','base64');
const hmac = crypto.createHmac('sha256', secret);
return {
'CB-ACCESS-KEY': 'YOUR_KEY',
'CB-ACCESS-PASSPHRASE': 'YOUR_PASSPHRASE',
'CB-ACCESS-SIGN': hmac.update(message).digest('base64'),
'CB-ACCESS-TIMESTAMP': timestamp.toString()
};
};
Related
I have been trying to use React to make API calls, I am able to successfully run GET and POST requests but when i ran PATCH, it return 400 error which is bad request. But i am able to run the same request successfully on Postman.
I have a feeling I am not passing the data in the right format, but the body and the headers are being passed in the same form as in the POST method which is working fine.
Also, the preflight request is being passed as OK (200) but the connection closes at PATCH request
This is my request, where the Id is being received from another component which is then passed to the API.
class AddPatients extends React.Component {
constructor(props)
{
super(props);
this.state = {message :''};
console.log(this.props.list_id);
}
addPatients(url)
{
let xhr = new XMLHttpRequest();
xhr.open('PATCH',url, true);
xhr.setRequestHeader("Authorization", "Auth_key");
xhr.setRequestHeader("X-OHP-Developer-API-Key", "API_Key ");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.setRequestHeader("Accept", "application/json");
let body = {
data:
[
{
//record 1
},
{
//record 2
}
]
}
const res = xhr.send(JSON.stringify(body));
console.log('the response is', res);
}
onButtonClick()
{
var url = 'the url link/' + this.props.list_id;
this.addPatients(url);
}
render() {
return(
<div>
<button onClick={this.onButtonClick.bind(this)}> Add patients to the list </button>
{this.props.list_id}
</div>
);
}
}
export default AddPatients;
It has nothing to do with the OPTIONS request i think because it returns OPTIONS IN Allow-Methods options like:
Request Method: OPTIONS
Status Code: 200 OK
Remote Address: 18.236.241.179:443
Referrer Policy: no-referrer-when-downgrade
HTTP/1.1 200 OK
Date: Tue, 31 Dec 2019 01:03:22 GMT
Server: Apache
Strict-Transport-Security: max-age=63072000; includeSubDomains
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET,POST,HEAD,DELETE,PUT,OPTIONS,PATCH
Access-Control-Allow-Headers: authorization,x-requested-with,x-ohp-developer-api-key,content-type
Vary: Accept-Encoding
Content-Encoding: gzip
Content-Length: 336
Keep-Alive: timeout=15, max=98
Connection: Keep-Alive
Content-Type: text/html; charset=iso-8859-1
Sorry, I resolved it. Was passing wring syntax in the JSON body, stupid mistake.
I am trying to send the Alert/Remainder via POSTMan to my skill.
Option 1: Authentication token API with Scope "alexa:skill_messaging"
POST /auth/o2/token HTTP/1.1
Host: api.amazon.com
Content-Type: application/x-www-form-urlencoded
User-Agent: PostmanRuntime/7.20.1
Accept: */*
Cache-Control: no-cache
Postman-Token: 2ae7afa3-c3f8-493f-b6e3-2db1e44e3a17,a4e45e8e-d0eb-4b3f-a612-e7d1959fdbe6
Host: api.amazon.com
Accept-Encoding: gzip, deflate
Content-Length: 236
Connection: keep-alive
cache-control: no-cache
grant_type=client_credentials&client_id=******************&client_secret=***********17a4f7b348982bdb4&scope=alexa%3Askill_messaging
Screenshote:
option 2: Authentication token API with Scope "alexa::alerts:reminders:skill:readwrite"
POST /auth/o2/token HTTP/1.1
Host: api.amazon.com
Content-Type: application/x-www-form-urlencoded
User-Agent: PostmanRuntime/7.20.1
Accept: */*
Cache-Control: no-cache
Postman-Token: 2ae7afa3-c3f8-493f-b6e3-2db1e44e3a17,c6765f77-6e35-419f-b614-780dae20ad4e
Host: api.amazon.com
Accept-Encoding: gzip, deflate
Content-Length: 236
Connection: keep-alive
cache-control: no-cache
grant_type=client_credentials&client_id=**************************&client_secret=************************&scope=alexa%3A%3Aalerts%3Areminders%3Askill%3Areadwrite
Step 2: Submitting the Alert request using token generated by Scope "alexa:skill_messaging" getting Invalide Bearer token
Let me know if I am missing anything and also where can find different scope for Alexa Authenictaion Token API
Unfortunately,
"That's a limitation of the Reminders API - you need to use the in-session access token to create reminders. You can run GET, UPDATE and DELETE operations out of session as well, so check this out for more information."
Only speaking with the device is possible get in-session access token to create reminders.
Out-session - Get reminders created by the skill (Skill Messaging API):
const IncomingMessageHandler = {
canHandle(handlerInput) {
const request = handlerInput.requestEnvelope.request;
return request.type === 'Messaging.MessageReceived'
},
async handle(handlerInput) {
const { requestEnvelope, context } = handlerInput;
console.log(`Message content: ${JSON.stringify(requestEnvelope.request.message)}`);
try {
const client = handlerInput.serviceClientFactory.getReminderManagementServiceClient();
const remindersResponse = await client.getReminders();
console.log(JSON.stringify(remindersResponse));
} catch (error) {
console.log(`error message: ${error.message}`);
console.log(`error stack: ${error.stack}`);
console.log(`error status code: ${error.statusCode}`);
console.log(`error response: ${error.response}`);
}
context.succeed();
}
}
https://developer.amazon.com/docs/smapi/alexa-reminders-api-reference.html#in-session-and-out-of-session-behavior-for-alexa-reminders-api
https://forums.developer.amazon.com/questions/196445/reminders-can-only-be-created-in-session.html#answer-196860
https://developer.amazon.com/pt-BR/docs/alexa/smapi/skill-messaging-api-reference.html
So I have been looking around. Obviously there's a fair amt of on the web about these "Download Network Failded" problems. I feel our problem is somewhat unique in that we have one file. a 160kb tiff file (really it's a blog that we append a tiff extension too). I just stumbled on this when testing. It's a random image on my machine. I have much bigger and smaller files that process fine through the app. When debugging, the response looks good in fiddler, like any other good response. Also tracking the response through our React app it looks good all the way through. So the problem happens somewhere in Chrome and just for this one file. We've tried all standard stuff found here.
https://productforums.google.com/forum/#!topic/chrome/7XBU6g6_Ktc
Mainly fiddling with extensions (disabling them), download locations, reinstalling, etc. But the idea that is one smaller jpg file we are sending for conversion (the app is a basic convertor) has me perplexed. Has anyone ever seen something like this??
So here is how we handle the file in our redux action.
WE use these packages
import dataURLtoBlob from 'dataurl-to-blob';
import FileSaver from 'file-saver';
And we have a dispatch function we pass in for a response in our thunk (the fetch)
export function saveFile(data, fileName) {
return (dispatch) => {
var ie = navigator.userAgent.match(/MSIE\s([\d.]+)/),
ie11 = navigator.userAgent.match(/Trident\/7.0/) && navigator.userAgent.match(/rv:11/),
ieEDGE = navigator.userAgent.match(/Edge/g),
ieVer = (ie ? ie[1] : (ie11 ? 11 : (ieEDGE ? 12 : -1)));
if (ie && ieVer < 10) {
console.log("No blobs on IE ver<10");
return;
}
var mimeType = data.split(',')[0].split(':')[1].split(';')[0];
var extension = '';
if (mimeType.includes("zip")) {
extension = "zip"
}
else {
extension = mimeType.substr(mimeType.lastIndexOf('/') + 1);
}
var npmBlob = dataURLtoBlob(data);
if (ieVer > -1) {
FileSaver.saveAs(npmBlob, fileName + "." + extension);
} else {
var downloadLink = document.createElement("a");
document.body.appendChild(downloadLink);
downloadLink.style.display = "none";
downloadLink.href = data;
downloadLink.download = fileName;
downloadLink.click();
}
}
}
Relevant part of the fetch itself
}).then(response => {
//debugger;
var responseObj = JSON.parse(response);
//handle multi-retrieve
if (targetExtension.includes("/File/Retrieve")) {
for (let array of responseObj) {
if (array.ReturnDocument) {
if (responseObj.length > 1) {
dispatch(saveFile(responseObj[0].ReturnDocument, "testFiles_download"));
} else {
dispatch(saveFile(responseObj[0].ReturnDocument, responseObj[0].ticketID));
}
}
}
}
var returnObject = { returnResult: responseObj, loading: false };
return callback(returnObject);
Everything looks good. http status codes are 200 and all other files are working. There is really nothing special about this jpg we send in as far as we can tell. And it looks good coming back.
Here is the request sent in:
POST http://redacted/api/File/Convert HTTP/1.1
Host: redacted-dev
Connection: keep-alive
Content-Length: 168078
Origin: http://redacted-dev
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryhgZddb45UOHBhsgs
Accept: */*
Referer: http://redacted-dev/ui/Convert
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Here is the raw response
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.5
X-AspNet-Version: 4.0.30319
Persistent-Auth: true
X-Powered-By: ASP.NET
Date: Tue, 02 Jan 2018 14:12:17 GMT
Content-Length: 3707173
Here is what the blob looks like when we get it back(abbreviated):
ReturnDocument=data:image/tiff;base64,SUkqAAg+............
You can file saver package to download a blob object.
Usage Example is as below:
// FileSaver Usage
import FileSaver from 'file-saver';
fetch('/records/download', {
method: 'post',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(data)
}).then(function(response) {
return response.blob();
}).then(function(blob) {
FileSaver.saveAs(blob, 'fileName.zip');
})
One more way to download a file is that you make a get request which sends file from the server.
Then you can simply do the following:
window.open('full server link');
Then your file will get start downloading.
How can I display pdf on a new tab in angularjs when a pdf is being returned as a stream.
pdf stream header captured by fiddler is
HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: *
Vary: Origin
Connection: close
Content-Type: application/pdf
Content-Disposition: inline; filename="report.pdf"
File-Extension: pdf
Number-Of-Pages: 1
X-XSS-Protection: 0
Content-Length: 18126
AngularJS code
externalService.getReport(template).then(function (data, error) {
if (data) {
type = 'application/pdf';
var newTab = $window.open('about:blank', '_blank');
newTab.document.write("<object width='400' height='400' data='" + data + "' type='" + type + "' ></object>");
}
}), function (error) {
if (error) {
}
}
There ya go: https://mozilla.github.io/pdf.js/examples/; just pass in the URL.
Hi after some struggle I am finally got past the angular js hurdle to pass the proper parameters to my server, but the web api 2 service fails to accept it.
below is the sample code
[RoutePrefix("api/v2/bids")]
public class BidsController : ApiController
{
[Route("{quoteId:long}/accept")]
public HttpResponseMessage AcceptQuote(long quoteId,[FromBody] string remarks)
{
HttpResponseMessage response;
response = Request.CreateResponse(HttpStatusCode.Accepted, quoteId);
return response;
}
}
if you notice i have both the route parameter and also a post parameter of type sting. When I post using fiddler with the following:
POST http://127.0.0.1:81/api/v2/Bids/101/accept? HTTP/1.1
Authorization: Basic a2lyYW5AYWJjc2hpcHBlci5jb206a2lyYW5AYWJjc2hpcHBlci5jb20=
Accept: application/json, text/plain, */*
Content-Type: application/json;charset=utf-8
Referer: http://127.0.0.1:81/shipper/
Accept-Language: en-US
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0; EIE10;ENUSWOL)
Host: 127.0.0.1:81
Content-Length: 40
DNT: 1
Connection: Keep-Alive
Pragma: no-cache
{"remarks":"Accepting this as the best"}
or use angularjs function:
function acceptQuote(quoteId, accept_remarks, fnSuccess, fnError) {
return $resource("/api/v2/Bids/:id/accept", { quoteId: "#id"},
{ "AcceptQuote": { method: "POST", isArray: false } })
.AcceptQuote({ id: quoteId }, { remarks: accept_remarks }, fnSuccess, fnError);
}
returns the following error:
{"Message":"The request is invalid.","ModelState":{"remarks":["Error reading string. Unexpected token: StartObject. Path '', line 1, position 1."]}}
i expected that using [FromBody] was sufficient to pass the simple types as post parameters, any ideas to what else I am missing here.
The [FromBody] is working a bit differently. Please, check this Parameter Binding in ASP.NET Web API. If you'd like to get the string [FromBody] string remarks, then your body must look like:
"Accepting this as the best"
Not a JSON. On the other hand, if the body contains the JSON, the most natural way how to consume that with ASP.NET Web API, is via the Entity/Object. So, we can create this
public class MyObject
{
public string remarks { get; set; }
}
And the Controller action should look like this:
[Route("{quoteId:long}/accept")]
public HttpResponseMessage AcceptQuote(long quoteId, MyObject myObject)
{
var remarks = myObject.remarks;