Vue3/Javascript: Google Sheets Api - 403 Forbidden/PERMISSION_DENIED - javascript

I created a key in the google cloud console. I even tried remaking it and using a the new one.
Im trying to use it like so:
export const getSheet= async () => {
try {
const sheetId ='xxxxxxx'
const tabName = 'myTab'
const accountKey = 'xxxxxxx'
const url =' https://sheets.googleapis.com/v4/spreadsheets/'+ sheetId +'/values/'+tabName+'?key='+ accountKey
console.log(url)
const response = await fetch(url);
console.log(response);
return '';
} catch (error) {
console.log(error);
} finally {
console.log('finally');
}
};
The request being sent is:
https://sheets.googleapis.com/v4/spreadsheets/xxxxxxx/values/myTab?key=xxxxxxx
No matter what I do I get
error: {code: 403, message: "The caller does not have permission", status: "PERMISSION_DENIED"}
Ive refered to these stack overflow posts regarding the same issue with no luck
Error 403 on Google Sheets API
Google Sheets API V4 403 Error
Getting 403 from Google Sheets API using apikey
Has anyone come across this and was able to fix it?
Thanks
-Coffee

You can't use an API key to access (Google Workplace) user data such as sheets; you may (!?) be able to get away with only use an API Key if the sheet were public (anyone with the link).
The options are admittedly confusing but:
API keys authenticate apps
OAuth is used to authenticate users
Have a look at authentication & authorization and OAuth for client-side web apps.

You can look through the Javascript Quickstart guide for Sheets API for the OAuth Setup. And you can access the sheet using Spreadsheet.values.get method similar to this sample script on the provided URL reference.
async function listMajors() {
let response;
try {
// Fetch first 10 files
response = await gapi.client.sheets.spreadsheets.values.get({
spreadsheetId: 'SHEETID',
range: 'SHEETRANGE',
});
} catch (err) {
document.getElementById('content').innerText = err.message;
return;
}
const range = response.result;
if (!range || !range.values || range.values.length == 0) {
document.getElementById('content').innerText = 'No values found.';
return;
}
And just like what DazWilkin provided. Make sure that your app complies with Google's OAuth2 policies.
You can also test your request url first on a browser and see if it returns a JSON response of the sheet data. Tested this https://sheets.googleapis.com/v4/spreadsheets/xxxxxxx/values/myTab?key=xxxxxxx format on a public sheet, and it returned the sheet data as long as it is set to 'Anyone with the link'

Related

Cloudflare Workers: Request to Google Maps API failed

I have a javascript project that I need help, which is to build a cache layer using cloudflare workers and cloudflare KV.
These were my given tasks:
Send API requests to a new url on cloudflare like:  https://maps.shiply.com rather than  https://maps.googleapis.com/maps/api/place/js/AutocompletionService.GetPredictionsJson?1ssw&4sen-GB&5sGB&6m6&1m2&1d34.5626609&2d-8.649357199999999&2m2&1d60.8608663&2d33.9165549&9sgeocode&15e3&20sDDC1BA1D-F381-44DA-9F99-B6F465F95056ap5a8xfuid3p&21m1&2e1&callback=_xdc_.24ids0&key=APIKEY&token=TOKEN
The cloudflare worker script first checks the Cloudflare KV to see if the response has been saved in the past.
If it has been saved in the past, return the KV.
If it has not been saved in the past, forward the request onto Google Maps API, save the response in KV and return the response
export default {
async fetch(request, env) {
try {
const { pathname } = new URL(request.url);
const [, key, value] = pathname.split("/");
if (!key) {
return new Response("A key is required in the URL path", {
status: 400,
});
}
if (request.method === "GET") {
// Check if the response has been saved in the past
const savedValue = await env.MAPS.get(key, {
namespace: env.NAMESPACE_ID,
});
if (savedValue) {
// If it has been saved, return the saved value
return new Response(savedValue, { status: 200 });
} else {
// If it has not been saved, forward the request to Google Maps API
const response = await fetch(
request.url.replace(
"maps.mapior.workers.dev",
`maps.googleapis.com/maps/api/place/js/AutocompletionService.GetPredictionsJson?1s${key}&4sen-GB&5sGB&6m6&1m2&1d34.5626609&2d-8.649357199999999&2m2&1d60.8608663&2d33.9165549&9sgeocode&15e3&20sDDC1BA1D-F381-44DA-9F99-B6F465F95056ap5a8xfuid3p&21m1&2e1&callback=_xdc_._24ids0&key=API_KEY&token=MY_TOKEN`
)
);
if (!response.ok) {
return new Response(
`Request to Google Maps API failed with status code ${response.status}`,
{ status: response.status }
);
}
const responseText = await response.text();
// Save the response in the Key-Value store
await env.MAPS.put(key, responseText, {
namespace: env.NAMESPACE_ID,
});
// Return the response from Google Maps API
return new Response(responseText, { status: 200 });
}
} else if (request.method === "PUT") {
// Store the value in the Key-Value store
if (!value) {
return new Response("A value is required in the URL path", {
status: 400,
});
}
await env.MAPS.put(key, value, { namespace: env.NAMESPACE_ID });
return new Response(`Saved in KV: ${key} = ${value}`);
} else {
return new Response("Unsupported method", { status: 405 });
}
} catch (e) {
return new Response(e.stack, { status: 500 });
}
},
};
As you can see from my above script, i was able to complete most of the tasks, but i have no idea about the google map AutocompletionService, the documentation were very confusing for a beginner like me.
When i run the above code in cloudfare workers enviornment, i am getting this error due to the request failure to google maps api:
502 Bad Gateway, content-length:54 content-type:text/plain;charset=UTF-8 Request to Google Maps API failed with status code 502
A Screenshot from my cloudflare workers dashboard where i am currently working on: My Screenshot
Something is wrong with the URL or my implementation.
Please help, i am a beginner and haven't worked on something like this before, can you please help me out.
As an output i need to see a google maps api response and store it as a value in cloudflarekv like so.
From the perspective of how the Google Maps JavaScript API and its Autocomplete service work, there are at least two reasons why what you are attempting seems problematic (not recommended):
The JavaScript API is designed to run in the users' browsers and send requests directly to Google servers. Anything that interferes with that direct connection is bound to cause problems. Unpredictable problems. The sensible recommendation to avoid problems is to use the API only as described in the documentation. If it's not documented, it's not an API.
Caching is disallowed by the Google Maps Platform Terms of Service. There are a few exceptions in the Google Maps Platform Service Specific Terms but none that allow caching entire API responses.
If you are concerned about having sensible caching, note that the Place Autocomplete service already sets cache-control: private, max-age=300 so the users' browsers will cache API responses for a short time, so duplicate requests are not sent in the short term.

Catching Error With Custom Rest API For FireStore Javascript

I have been watching a tutorial on making a Rest API for Firestore which appears to work but I cannot figure out how to catch an error.
The code below basically uses an end point to retrieve a document id from the firestore database.
The client uses javascript fetch to call the API.
I am trying to workout how to return something back to the client from the API if the document id is not there. I thought I might get a 404 status returned but I always get status 200.
This is the API code I used
app.get("/api/read/:id", (req, res) => {
(async () => {
try {
const document = db.collection("users").doc(req.params.id);
let product = await document.get();
let response = product.data();
return res.status(200).send(response);
} catch (error) {
console.log(error);
return res.status(500).send(error);
}
})();
})
I'm fairly certain that the 404 message is for the server itself not being found (though I do need to brush up on my error codes).
However, if you're looking to check to see if a document exists there's a command specifically for that demonstrated in the examples in the firebase docs

How to get a valid access token for a custom Azure App API? (MSAL.js)

I am trying to build a small web app which shows me my name, my schedule and my grades for school.
My school mostly uses the services from Microsoft, which gave me the idea to use their Azure API endpoints (for the schedules and grades) in my project.
I have access to create an app registration in the Azure-portal, so I did that and got it working to login with my student email. Also I tried to fetch the Microsoft Graph API and that works absolutely great.
However, when I try to fetch the Grades endpoint, it returns a 401 Unauthorized error. I'm guessing this has to do with the scopes, but I'm not sure. It turns out that my access token isn't valid for those API endpoints.
So my question is, how do I get an access token that IS valid for those API's? Or is it even possible? Keep in mind that they're separate App registrations in the Azure-portal, and that I can only edit my own one, not the one of my school.
Here is my JavaScript file, with some comments:
const config = {
auth: {
clientId: "my_client_id_is_here",
authority: "https://login.microsoftonline.com/my_tenant_id_is_here",
redirectUri: "localhost"
}
};
async function login() {
console.log("Started..")
var client = new Msal.UserAgentApplication(config);
var request = {
scopes: [ 'User.Read' ]
};
let loginResponse = await client.loginPopup(request);
console.dir(loginResponse);
let tokenResponse = await client.acquireTokenSilent(request);
console.dir(tokenResponse);
// User REQUEST - Here I fetch the Graph API for some profile information, which works fine and writes it to the HTML perfectly.
let req = await fetch("https://graph.microsoft.com/v1.0/me/", {
headers: {
"Authorization": "Bearer " + tokenResponse.accessToken
}
});
let json = await req.json();
console.log(json);
document.write("Logged in as " + json.displayName);
document.write("<br>" + json.mail);
document.write("<br>" + json.jobTitle + " " + json.officeLocation);
// School Grades REQUEST - this is the part where I'm trying to fetch my School Grades, but it's not working since it gives me a 401 error..
let gradesReq = await fetch("https://myschool.azurewebsites.net/API/Grades/GetGrades", {
"headers": {
"authorization": "Bearer " + tokenResponse.accessToken
}
});
try {
let gradesJson = await gradesReq.json();
console.log(gradesJson);
} catch (err) {
document.write("An error occured while trying to get the school grades..")
}
}```
You're correct in your thinking. The reason you're getting this error is because you're using the access token acquired for a different scope (User.Read) with your API.
Fix is rather simple.
What you have to do is protect your API with Azure AD first. You may find this link helpful in implementing this functionality: https://learn.microsoft.com/en-us/azure/active-directory/develop/scenario-protected-web-api-overview.
Once you have done that, all you need to do then is to acquire token for your API. In that case, your scopes code will be something like the following:
var request = {
scopes: [ 'api://<your-application-id>/.default' ]
};
Once you acquire the token for this scope and use it with your API, you should not get 401 exception that you're getting.

How do you get client-side firebase cloud messaging token into google cloud function?

I'm working towards implementing push notifications that appear on change to a firebase firestore document. I'm using the react-native-firebase module. My google cloud function listens for changes to the firestore and then sends messages via firebase-admin.
google's reference says you can specify a single device to message with:
// This registration token comes from the client FCM SDKs.
var registrationToken = 'YOUR_REGISTRATION_TOKEN';
var message = {
data: {
score: '850',
time: '2:45'
},
token: registrationToken
};
// Send a message to the device corresponding to the provided
// registration token.
admin.messaging().send(message)
.then((response) => {
// Response is a message ID string.
console.log('Successfully sent message:', response);
})
.catch((error) => {
console.log('Error sending message:', error);
});
client-side in my react-native app I get a token using react-native-firebase:
function getToken() {
let fcmToken = await AsyncStorage.getItem("fcmToken");
if (!fcmToken) {
fcmToken = await firebase.messaging().getToken();
if (fcmToken) {
await AsyncStorage.setItem("fcmToken", fcmToken);
}
}
}
Do I have to store the google cloud messaging token somewhere other than async storage or is there a way to access it as is, inside my google cloud function? It seems like I should be storing the auth token inside firestore and accessing firestore with cloud functions. is this the best way to do this?
You don't need AsyncStorage to access the token, it is available right from fcmToken = await firebase.messaging().getToken(); in your code.
From there you can either send it to a callback Cloud Function with something like:
var sendMessage = firebase.functions().httpsCallable('sendMessage');
addMessage({ token: fcmToken }).then(function(result) {
// ...
});
This is based on the example in the documentation here. You can then use this value in your Cloud Functions code to send a message by calling the FCM API through the Admin SDK.
Or store it in a database, such as Cloud Firestore with something like this:
db.collection("tokens").add(docData).then(function() {
console.log("Token successfully written to database!");
});
Which is based on the example in the documentation here. You can then read this value from the database in your Cloud Function and use it to again send a message by calling the FCM API through the Admin SDK.

How to create Microsoft Account Sign-in to my website, similar to Google?

I'm working on a web project (HTML, CSS, JavaScript, with back-end in PHP). I've successfully got a Google Sign-in working, using their simple API, but can't get the Microsoft equivalent to function. The official online solutions to this seem to rely on .NET or PHP Composer. I'll try composer if that's the only way but a pure JS/PHP method would be easiest.
I've tried to use the following:
https://github.com/microsoftgraph/msgraph-sdk-javascript
https://github.com/AzureAD/microsoft-authentication-library-for-js
The code below is the closest I've come to a working solution. I can get some kind of user ID (which appears to be unique and constant for each user). This might be enough to set up the login system I want, but it would be ideal if I could also fetch their name and profile picture.
<script class="pre">
var userAgentApplication = new Msal.UserAgentApplication("MY CLIENT ID", null, function (errorDes, token, error, tokenType) {
// this callback is called after loginRedirect OR acquireTokenRedirect (not used for loginPopup/aquireTokenPopup)
})
userAgentApplication.loginPopup(["user.read"]).then(function (token) {
var user = userAgentApplication.getUser(); //this is good
//user.userIdentifier seems to be a unique ID
//I will store this and use it for future verification
console.log(user);
//START
// get an access token
userAgentApplication.acquireTokenSilent(["user.read"]).then(function (token) {
console.log("ATS promise resolved");
}, function (error) {
console.log(error);
// interaction required
if (error.indexOf("interaction_required") != -1) {
userAgentApplication.acquireTokenPopup(["user.read"]).then(function (token) {
// success
console.log("s2");
}, function (error) {
console.log("e2");
// error
});
}
});
//END
// signin successful
}, function (error) {
console.log(error);
// handle error
});
</script>
(this code won't run as I've pasted it because it relies on the MSAL script from the second github link, and needs an application client ID)
After getting the access token with scope user.read , you could call microsoft graph api to get sign-in user's profile information such as displayName , businessPhones :
https://graph.microsoft.com/v1.0/me
Content-Type:application/json
Authorization:Bearer {token}
To get user's profile photo :
GET https://graph.microsoft.com/v1.0/me/photo/$value
In addition , if you are using Microsoft Graph JavaScript Client Library in first link , you could get user's displayName and profile photo by :
client
.api('/me')
.select("displayName")
.get((err, res) => {
if (err) {
console.log(err);
return;
}
console.log(res);
});
// Example of downloading the user's profile photo and displaying it in an img tag
client
.api('/me/photo/$value')
.responseType('blob')
.get((err, res, rawResponse) => {
if (err) throw err;
const url = window.URL;
const blobUrl = url.createObjectURL(rawResponse.xhr.response);
document.getElementById("profileImg").setAttribute("src", blobUrl);
});
Please refer to code sample here .

Categories