I have this function running in an azure function to get a sas token for a browser application to upload to azure blob storage:
var azure = require('azure-storage');
module.exports = function(context, req) {
if (req.body.container) {
// The following values can be used for permissions:
// "a" (Add), "r" (Read), "w" (Write), "d" (Delete), "l" (List)
// Concatenate multiple permissions, such as "rwa" = Read, Write, Add
context.res = generateSasToken(
context,
req.body.container,
req.body.blobName,
req.body.permissions
);
} else {
context.res = {
status: 400,
body: "Specify a value for 'container'"
};
}
context.done(null, context);
};
function generateSasToken(context, container, blobName, permissions) {
var connString = process.env.AzureWebJobsStorage;
var blobService = azure.createBlobService(connString);
// Create a SAS token that expires in an hour
// Set start time to five minutes ago to avoid clock skew.
var startDate = new Date();
startDate.setMinutes(startDate.getMinutes() - 5);
var expiryDate = new Date(startDate);
expiryDate.setMinutes(startDate.getMinutes() + 60);
permissions = azure.BlobUtilities.SharedAccessPermissions.READ +
azure.BlobUtilities.SharedAccessPermissions.WRITE +
azure.BlobUtilities.SharedAccessPermissions.DELETE +
azure.BlobUtilities.SharedAccessPermissions.LIST;
var sharedAccessPolicy = {
AccessPolicy: {
Permissions: permissions,
Start: startDate,
Expiry: expiryDate
}
};
var sasToken = blobService.generateSharedAccessSignature(
container,
blobName,
sharedAccessPolicy
);
context.log(sasToken);
return {
token: sasToken,
uri: blobService.getUrl(container, blobName, sasToken, true)
};
}
I am then calling this url in the client and I try and upload with this code:
const search = new URLSearchParams(`?${token}`);
const sig = encodeURIComponent(search.get('sig'));
const qs = `?sv=${search.get('sv')}&ss=b&srt=sco&sp=rwdlac&se=${search.get('sv')}&st=${search.get(
'st'
)}&spr=https&sig=${sig}`;
return `${url}/${containerName}/${filename}${qs}`;
Which generates a url like this:
https://mystorage.blob.core.windows.net/mycontainer/latest.png?sv=2018-03-28&ss=b&srt=sco&sp=rwdlac&se=2018-03-28&st=2019-01-30T19:11:10Z&spr=https&sig=g0sceq3EkiAQTvyaZ07C+C4SZQz9FaGTV4Zwq4HkAnc=
Which returns this error:
403 (Server failed to authenticate the request. Make sure the value of
Authorization header is formed correctly including the signature.)
If I generate the sas token from the azure portal it works, so the generated url looks like this:
https://mystorage.blob.core.windows.net/mycontainer/latest.png?sv=2018-03-28&ss=b&srt=sco&sp=rwdlac&se=2019-01-31T03:01:43Z&st=2019-01-30T19:01:43Z&spr=https&sig=ayE4gt%2FDfDzjv5DjMaD7AS%2F176Bi4Q6DWJNlnDzl%2FGc%3D
but my url looks like this:
https://mystorage.blob.core.windows.net/mycontainer/latest.png?sv=2018-03-28&ss=b&srt=sco&sp=rwdlac&se=2019-01-31T03:34:21Z&st=2019-01-30T19:34:21Z&spr=https&sig=Dx8Vm4XPnD1rn9uyzIAXZEfcdbWb0HjmOq%2BIq42Q%2FOM%3D
I have no idea what to do to get this working
Your Azure Function code is correct, and
var sasToken = blobService.generateSharedAccessSignature(
container,
blobName,
sharedAccessPolicy
);
is exactly the sasToken you need to upload blob. No need to process the token again(mishandle actually) as you have done in the 2nd code snippet.
It's expected that the sas token from the Azure portal(Account SAS) is different from the one generated in your code(Service SAS). Have a look at the doc.
To conclude,
Make sure the connection string belongs to the Storage you want to connect. You could avoid trouble and directly replace var connString = process.env.AzureWebJobsStorage; with var connString = "connectionStringGotFromPortal";
If 1 is confirmed, your Azure function code is correct and returns token as expected
{
token: sasToken,
uri: blobService.getUrl(container, blobName, sasToken, true)
};
Based on the 2nd code snippet you provide, you only need
return `${url}/${containerName}/${filename}?${token}`;
if the token is identical to what function returns.
The problem is that in your server-side code you're creating a Service SAS and then taking only signature portion of the code (sig) and creating an Account SAS on the client.
Since the parameters used to create token has now changed (in the original one, you didn't have parameters like ss, srt etc. but when you're creating your own URL, you're inserting these parameters), when you use the modified SAS URL you will get 403 error. This is happening because server again computes the signature based on the URL parameters and compare that with the signature passed in the URL. Since the two signatures won't match, you're getting the 403 error.
Since you're returning the SAS URL of the blob, there's no need for you to create the URL on the client. You can simply use the uri you're returning from your API layer on the client and use that to upload.
As Jerry Liu's answer explained your Azure function generates the correct token and already gives you the the correct uri to use which includes your blob name and token.
In your client side you can also use azure-sdk-for-js
// This is the response from your api with token and uri
const uri = response.uri;
const pipeline = StorageURL.newPipeline(new AnonymousCredential());
// Your uri already includes the full blob url with SAS signature
const blockBlobURL = BlockBlobURL.fromBlobURL(new BlobURL(uri, pipeline));
const uploadBlobResponse = await blockBlobURL.upload(
Aborter.none,
file,
file.size,
{ blobHTTPHeaders: { blobContentType: `${mime}; charset=utf-8`} }
);
Related
I've never received an error like this before,
I have a file that defines functions for making API calls, currently I'm reading the endpoint base URLs from the environment variables:
/**
* Prepended to request URL indicating base URL for API and the API version
*/
const VERSION_URL = `${process.env.NEXT_PUBLIC_API_BASE_URL}/${process.env.NEXT_PUBLIC_API_VERSION}`
I tried to make a quick workaround because environment variables weren't being loaded correctly, by hardcoding the URLS incase the variable wasn't defined.
/**
* Prepended to request URL indicating base URL for API and the API version
*/
const VERSION_URL = `${process.env.NEXT_PUBLIC_API_BASE_URL || 'https://hardcodedURL.com'}/${process.env.NEXT_PUBLIC_API_VERSION || 'v1'}`
In development and production mode when running on my local machine it works fine (docker container). However, as soon as it's pushed to production, I then get the following screen:
This is the console output:
framework-bb5c596eafb42b22.js:1 TypeError: Path must be a string. Received undefined
at t (137-10e3db828dbede8a.js:46:750)
at join (137-10e3db828dbede8a.js:46:2042)
at J (898-576b101442c0ef86.js:1:8158)
at G (898-576b101442c0ef86.js:1:10741)
at oo (framework-bb5c596eafb42b22.js:1:59416)
at Wo (framework-bb5c596eafb42b22.js:1:68983)
at Ku (framework-bb5c596eafb42b22.js:1:112707)
at Li (framework-bb5c596eafb42b22.js:1:98957)
at Ni (framework-bb5c596eafb42b22.js:1:98885)
at Pi (framework-bb5c596eafb42b22.js:1:98748)
cu # framework-bb5c596eafb42b22.js:1
main-f51d4d0442564de3.js:1 TypeError: Path must be a string. Received undefined
at t (137-10e3db828dbede8a.js:46:750)
at join (137-10e3db828dbede8a.js:46:2042)
at J (898-576b101442c0ef86.js:1:8158)
at G (898-576b101442c0ef86.js:1:10741)
at oo (framework-bb5c596eafb42b22.js:1:59416)
at Wo (framework-bb5c596eafb42b22.js:1:68983)
at Ku (framework-bb5c596eafb42b22.js:1:112707)
at Li (framework-bb5c596eafb42b22.js:1:98957)
at Ni (framework-bb5c596eafb42b22.js:1:98885)
at Pi (framework-bb5c596eafb42b22.js:1:98748)
re # main-f51d4d0442564de3.js:1
main-f51d4d0442564de3.js:1 A client-side exception has occurred, see here for more info: https://nextjs.org/docs/messages/client-side-exception-occurred
re # main-f51d4d0442564de3.js:1
Viewing the source at t (137-10e3db828dbede8a.js:46:750)
I'm completely at a lost at what this means or what is happening. Why would hardcoding in a string for the path result in this client error? The lack of a readable source code is making this impossible for me to understand what's happening.
Quick googling suggests that I should upgrade some package, but the error is so vague, I'm not even sure what package is giving the issue.
This is the roughly the how the version URL path is being used
/**
* Send a get request to a given endpoint
*
* **Returns a Promise**
*/
function GET(token, data, parent, api) {
return new Promise((resolve, reject) => {
try {
let req = new XMLHttpRequest()
let endpoint = `${VERSION_URL}/${parent}/${api}` // base url with the params not included
let params = new URLSearchParams() // URLSearchParams used for adding params to url
// put data in GET request params
for (const [key, value] of Object.entries(data)) {
params.set(key, value)
}
let query_url = endpoint + "?" + params.toString() // final query url
req.open("GET", query_url, true)
req.setRequestHeader("token", token) // put token into header
req.onloadend = () => {
if (req.status === 200) {
// success, return response
resolve([req.response, req.status])
} else {
reject([req.responseText, req.status])
}
}
req.onerror = () => {
reject([req.responseText, req.status])
}
req.send()
} catch (err) {
reject(["Exception", 0])
}
})
}
From my experience, this problem can happen for multiple reasons. The most common one is because you didn't put the data accessing checker properly when data comes from an API. Sometimes this things we don't see in browser but it gives such error when you deploy.
For example:
const response = fetch("some_url");
const companyId = response.data.companyId; ❌
const companyId = response?.data?.companyId; ✔️
I can make basic video call with temprory token from agora.io but when i create token from my server i get this error. I am using NgxAgora packet. I try to set areacode but there is no option for this in NgxAgora.
here is my angular code:
this.api.getmethod("appointment/gettoken/" + atob(this.acRouter.snapshot.params.id)).subscribe((data) => {
this.token = data['token']
this.client = this.ngxAgoraService.createClient({ mode: 'rtc', codec: 'h264' });
this.assignClientHandlers();
this.localStream = this.ngxAgoraService.createStream({ streamID: this.uid, audio: true, video: true, screen: false });
this.assignLocalStreamHandlers();
// Join and publish methods added in this step
this.initLocalStream(() => this.join(uid => this.publish(), error => console.error(error)));
})
and i use this function in web api side using C#:
public string createagoratoken(string appointmentUid,DateTime appointmentDate,int appointmentId)
{
var tokenbuilder = new AgoraEntegration.Media.AccessToken(AgoraEntegration.AgoraEnums.AppEnums.appId, AgoraEntegration.AgoraEnums.AppEnums.appCertificate, appointmentUid, appointmentId.ToString());
appointmentDate = appointmentDate.AddMinutes(20);
Int32 unixTimestamp = (Int32)(appointmentDate.Subtract(new DateTime(1970, 1, 1))).TotalSeconds;
tokenbuilder.addPrivilege(AgoraEntegration.Media.Privileges.kJoinChannel, (uint)unixTimestamp);
tokenbuilder.addPrivilege(AgoraEntegration.Media.Privileges.kInvitePublishAudioStream, (uint)unixTimestamp);
tokenbuilder.addPrivilege(AgoraEntegration.Media.Privileges.kInvitePublishVideoStream,(uint)unixTimestamp);
string token = tokenbuilder.build();
return token;
}
Also i use this library for acces token
Github access token for C#
CANNOT_MEET_AREA_DEMAND usually comes when: The connection fails because the user is outside the chosen region for connection. For example, if you set ClientConfig.areaCode as [AgoraRTC.AREAS.EUROPE], and a user tries to join the channel in North America, this error occurs. If ClientConfig.areaCode is not explicitly set, then by default the SDK requests servers across multiple regions and chooses an optimal connection, so the console log may print this error when a user joins the channel. In this case, you can ignore the error.
Try setting areaCode to GLOBAL:
this.rtc = AgoraRTC.createClient({
//
areaCode: ['GLOBAL']
});
I'm trying to implement Google Analytics Graphs via their Javascript API, as the example on their site link.
But I keep getting "401 Invalid Credentials" each time I try to execute gapi.analytics.googleCharts.DataChart
I'm getting the access token server side (C#) using the following code with data from the JSON generated for the Service Account
var cred = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(clientId)
{
Scopes = new[] { AnalyticsReportingService.Scope.AnalyticsReadonly },
User = clientEmail,
ProjectId = "projectID"
}.FromPrivateKey(privateKey));
var token = cred.GetAccessTokenForRequestAsync(authURI);
token.Wait();
var result = token.Result;
return result;
or (using the full json string, see Note too)
GoogleCredential cred;
var gCred = GoogleCredential.FromJson(json).UnderlyingCredential as
ServiceAccountCredential;
var token = gCred.GetAccessTokenForRequestAsync("https://accounts.google.com/o/oauth2/auth");
return token.Result;
While on the client side
gapi.analytics.auth.authorize({
'serverAuth': {
'access_token': '{{ ACCESS_TOKEN_FROM_SERVICE_ACCOUNT }}'
}
});
goes through and using gapi.analytics.auth.isAuthorized() returns true using any of the server side functions but it fails when trying to call
var dataChart1 = new gapi.analytics.googleCharts.DataChart(queryJson);
dataChart1.execute();
returns 401 "Invalid Credentials", the server side query returns values just fine so I think the user permissions is not the issue
NOTE: Using the same code as the second one (generating the credential using the json string without casting as a ServiceAccountCredential) I can get data from the API server side
cred = gCred.CreateScoped(scopes);
using (var reportingService = new AnalyticsReportingService(new BaseClientService.Initializer
{
HttpClientInitializer = cred
}))
...
var getReportsRequest = new GetReportsRequest
{
ReportRequests = new List<ReportRequest> { reportRequest }
};
var batchRequest = reportingService.Reports.BatchGet(getReportsRequest);
var response = batchRequest.Execute(); //This returns a response object with all the data I need
If anyone has the same issue:
You need to use GoogleCredential.GetApplicationDefault() to create the Credential object, in the case of Analytics you should get something like this
var credential = GoogleCredential.GetApplicationDefault().CreateScoped(AnalyticsReportingService.Scope.AnalyticsReadonly);
This will get the json file from the Environment Variables on Windows, so you need to set GOOGLE_APPLICATION_CREDENTIALS with the path to the json as a System Variable.
Initialize the service and set the service variable
using (var reportingService = new AnalyticsReportingService(new BaseClientService.Initializer { HttpClientInitializer = credential }))
{
var serviceCredential = cred.UnderlyingCredential as ServiceAccountCredential;
}
This is the first time I use Azure Storage JS API. I have followed instruction on this Microsoft tutorial.
I generate the SAS key on the node server with successful results but I still get the authentication failed error. I'm using the libraries provided by Microsoft Azure. How may I fix this?
function test() {
Restangular.all('cdn/sas').post({container: 'photos'}).then(function (sas) {
var blobUri = 'https://hamsar.blob.core.windows.net';
var blobService = AzureStorage.createBlobServiceWithSas(blobUri, sas.token);
blobService.listContainersSegmented(null, function (error, results) {
if (error) {
// List container error
} else {
// Deal with container object
}
});
}, function (error) {
console.log("Error generating SAS: ", error);
});
}
Error messages:
According to your error message, I found you create a Service SAS token. But if you want to list all the container name in your storage account. You need use account SAS token.
Notice: You could also use the blobService.listBlobsSegmented, you should make sure your service sas token has the permission to list the blob file and set the container name.
Like this:
blobService.listBlobsSegmented('mycontainer', null, function (error, results)
If you want to list all the container, I suggest you could follow these codes to generate the Account SAS.
Code like this :
var getPolicyWithFullPermissions = function(){
var startDate = new Date();
var expiryDate = new Date();
startDate.setTime(startDate.getTime() - 1000);
expiryDate.setTime(expiryDate.getTime() + 24*60*60*1000);
var sharedAccessPolicy = {
AccessPolicy: {
Services: AccountSasConstants.Services.BLOB +
AccountSasConstants.Services.FILE +
AccountSasConstants.Services.QUEUE +
AccountSasConstants.Services.TABLE,
ResourceTypes: AccountSasConstants.Resources.SERVICE +
AccountSasConstants.Resources.CONTAINER +
AccountSasConstants.Resources.OBJECT,
Permissions: AccountSasConstants.Permissions.READ +
AccountSasConstants.Permissions.ADD +
AccountSasConstants.Permissions.CREATE +
AccountSasConstants.Permissions.UPDATE +
AccountSasConstants.Permissions.PROCESS +
AccountSasConstants.Permissions.WRITE +
AccountSasConstants.Permissions.DELETE +
AccountSasConstants.Permissions.LIST,
Protocols: AccountSasConstants.Protocols.HTTPSORHTTP,
Start: startDate,
Expiry: expiryDate
}
};
return sharedAccessPolicy;
};
var sharedAccessSignature = azure.generateAccountSharedAccessSignature(environmentAzureStorageAccount, environmentAzureStorageAccessKey, getPolicyWithFullPermissions );
Then you could use the account SAS to list the account's container.
Result:
More details about the difference between service sas and account sas, you could refer to this article.
I'm making a request but it doesn't seem to work. If I copy code into my browser it works good, but in my console it shows up this :
{
"status" : "success",
"data" : {
"error_message" : "API access enabled, but unable to verify two-factor authentication code. If you need help with this, please contact support#bitskins.com."
}
}
What am I doing wrong? It's based on two-factor authentication that as I said works good while printing the url itself and when i'm copying it into my browser.
var url = 'https://bitskins.com/api/v1/get_item_price/?api_key='+bitskins.apikey+'&code='+bitskins.code+'&names='+encodeURIComponent(items[i].market_hash_name)+'&delimiter=!END!';
console.log(url);
request(url, function (error, response, body) {
if (!error) {
console.log(body)
}
});
In case you want, here is my api key module to generating it (api key deleted for security)
var TOTP = require('onceler').TOTP;
//Create a TOTP object with your secret
var totp = new TOTP('deleted');
// print out a code that's valid right now
// console.log(totp.now());
var code = totp.now();
module.exports = {
code: code,
apikey: 'deleted'
}
Founder of BitSkins, Inc. here. You need to have the following:
1) Your API Key
2) Your Secure Access Secret
You see the Secret when you enable Secure Access. If you do not have this, just disable/re-enable Secure Access and note the Secret down. The TOTP code you generate is with that Secret. Generate the TOTP code right before every API call and you'll be fine.
I think it should work. For me it works fine.
var API_KEY = ''; //It is very important
var SECRET_KEY = ''; //It is very important
var totp = new TOTP(SECRET_KEY);
var code = totp.now();
var options = {
url: 'https://bitskins.com/api/v1/get_item_price',
form: {
'api_key': API_KEY,
'names': 'Tec-9%20%7C%20Sandstorm%20(Minimal%20Wear)',
'delimiter': '!END!',
'code': code
}
};
function callback(error, response, body) {
if (!error) {
var info = JSON.parse(body);
console.log(info);
}
}
request.post(options, callback);
What npm package do you use to create 2FA code? I'm using "onceler" from example but I think it creates wrond codes. Here is my code:
var API_KEY = ''; //correct key from settings page
var SECRET_KEY = ''; // correct key which I copied from form with QR code.
var totp = new TOTP("SECRET_KEY");
var code = totp.now();
This code doesn't equal code which I can see in my mobile device and with this code I get error message like in author's question. But if I put code from my mobile in programm code - it works fine. So what package should I use to get correct codes?