Firebase and Algolia secured search no search hits - javascript

I'm having some issues implementing a secured search with Firebase and Algolia.
I've been following the guide at https://firebase.google.com/docs/firestore/solutions/search -> Adding security
My problem is that everything seems to be working fine, however I do not receive any search hits.
When a user searches for an item, it invokes a function which first hits a firebase function to generate a Secured API Key, which also includes the security filter for the queries.
The firebase function:
app.use(getFirebaseUser);
app.get('/', (req, res) => {
// #ts-ignore
const uid = req.user.uid;
// Create the params object as described in the Algolia documentation:
// https://www.algolia.com/doc/guides/security/api-keys/#generating-api-keys
const params = {
// This filter ensures that only documents where author == uid will be readable
filters: `ownerID:${uid}`,
// We also proxy the uid as a unique token for this key.
userToken: uid,
};
// Call the Algolia API to generate a unique key based on our search key
const key = client.generateSecuredApiKey(ALGOLIA_SEARCH_KEY, params);
// Then return this key as {key: '...key'}
res.json({key});
});
This is literally copy/pasted from the firebase example, except I've replaced "author" with "ownerID", which is the term I want to match in my indices.
The getFirebaseUser() is an exact copy/paste from the official firebase example.
Once the client has received the secured API key, it get passed to algolia with the query:
const getSearchResults = async (query: string) => {
const index = client.initIndex('things');
try {
const userToken = await auth.currentUser?.getIdToken();
const response = await fetch(GOOGLE_URL, {
headers: { Authorization: `Bearer ${userToken}` },
});
const data = await response.json();
const client = algoliasearch(ALGOLIA_APP_ID, data.key);
const responses = await index.search(query);
console.log(responses);
} catch (err) {
console.error(err.message);
}
};
This is also the same function as the example, except written with async/await. (I did try an exact copy/paste, and the result is the same).
Also, the example shows index.search({query}) where the query string gets passed in as an object. This does not work either, and Typescript states that a string is expected.
If I try to change the initIndex to an index that doesn't exist, I get an error stating that it doesn't exist. This tells me the connection between my app and algolia is working as expected.
The returned object looks like this:
exhaustiveNbHits: true
hits: []
hitsPerPage: 20
nbHits: 0
nbPages: 0
page: 0
params: "query=test&filters=ownerID%3AKK3oXEkrIKb31BECDSJPMdgvTBz2&userToken=KK3oXEkrIKb31BECDSJPMdgvTBz2"
processingTimeMS: 1
query: "test"
If I do a regular search:
const getSearchResults = async (query: string) => {
const client = algoliasearch(ALGOLIA_APP_ID, ALGOLIA_SEARCH_API_KEY);
const index = client.initIndex('things');
const responses = await index.search(query);
console.log(responses);
};
I do get hits:
exhaustiveNbHits: true
hits: Array(1)
0: {productName: "test ja", purchaseDate: "15.03.2021", purchasedFrom: "ok", ownerID: "KK3oXEkrIKb31BECDSJPMdgvTBz2", objectID: "25BadjQxUjRjkjH9ZnwW", …}
length: 1
__proto__: Array(0)
hitsPerPage: 20
nbHits: 1
nbPages: 1
page: 0
params: "query=test"
processingTimeMS: 1
query: "test"
This is literally bypassing the whole cloud function, which means the problem must be with generating the Secured API Key, the parameters or something.
The index data stored in Algolia looks like this:
objectID "25BadjQxUjRjkjH9ZnwW"
productName "test ja"
purchaseDate "15.03.2021"
purchasedFrom "ok"
ownerID "KK3oXEkrIKb31BECDSJPMdgvTBz2"
Which means I should get hits on ownerID, which I have in the cloud function.
EDIT:
When setting the filter from the parameter object to an empty string, I receive search hits. There might be something wrong with the way the filter is implemented.
Thanks for looking into this. I would appreciate all the help I can get!
Stephan Valois

I was in contact with algolia, and figured out what was wrong.
There was nothing wrong with the code, or the way the search was set up.
One thing that wasn't mentioned in the documentation is that the attribute I was matching (ownerID) wasn't declared as an attribute for faceting.
By going to Configuration -> Facets in the Algolia app, and adding the object key there, everything works as expected. (Sat it as 'not searchable'). This attribute is not supposed to be searchable. It should only be used to match the index to the correct user.
If anyone is using Algolia for secure search, whether or not you're using a Firebase function or another backend, the attribute in your filter must be set in Facets.

Related

Using ethereum.request to Retrieve Data from Smart Contract in JavaScript

Using javascript, I am currently able to write data to a smart contract I built on the Ethereum Rovan test network using this code (taken from the MetaMask docs)
const transactionParameters = {
to: '0xacb241f59e1a8c7a61f0781aed7ad067269feb26',
from: account,
data: '0xfcc74f71aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
};
const txHash = await ethereum.request({
method: 'eth_sendTransaction',
params: [ transactionParameters ],
});
However, I am not able to read data. How can I do this? The hex code of the method for reading data is 0x1f1bd692, so I thought using these parameters would work:
to: '0xacb241f59e1a8c7a61f0781aed7ad067269feb26',
from: account,
data: '0x1f1bd692',
Unfortunately, this just returns the transaction hash, not the data I want from the smart contract.
Note: If possible, please do not suggest any libraries.
Here is the solution:
const data = await ethereum.request({
method: 'eth_getStorageAt',
params: [ '0xacb241f59e1a8c7a61f0781aed7ad067269feb26', '0x0' ],
})
The first argument in params[] is the address of the contract. The second argument is the index of the item in storage you want to retrieve. It must be a hex number starting with '0x'.

GraphQL/Apollo Client: get data from cache if a query is a subset of a previous query

I'm new to GraphQL and I thought it would do this automatically.
E.g. if I fetch a list of posts, then open an individual post, the data for that post is already in memory. I expected GraphQL to be able to re-use the data from cache, but it makes another network request.
The schema is something like:
type Query {
posts: [Post]
post(id: ID): Post
}
I'm using React. In PostsRoute, I have:
useQuery(gql`
query {
posts {
id
title
}
}
`);
In PostRoute, I have:
useQuery(gql`
query {
post(id: ${postId}) {
id
title
}
}
`);
Is there something I need to configure for Apollo to use the cached data automatically? Or is this something that Apollo doesn't support by default?
Apollo 2
If you are using Apollo 2, what you're looking for is probably cacheRedirects. It is used to intercept a query and resolve it using the cache. The linked document actually explains exactly your use-case so I recommend you look at it, but for posterity, here's how I do it personally (adapted to your situation).
In your InMemoryCache options when you instantiate it, add a cacheRedirects field and specify a custom resolver for your post query, like so:
const cache = new InMemoryCache({
cacheRedirects: {
Query: {
post: (_, {id}, {getCacheKey}) => getCacheKey({__typename: "Post", id}),
},
},
});
This assumes that the __typename of your post is Post. First argument of the custom resolver is the result of the ROOT_QUERY, which we don't use here. Second argument is the arguments that are passed to the query ({id: "some_id"} here) and the third one is the context, containing client, cache and getCacheKey. Only getCacheKey is useful to us here. That method take a __typename and an id, and returns its key in the cache.
This completes the custom resolver. Now when you query post(id: some_id) it will look at the custom resolver, receive the cache key and return the result from the cache matching that key.
Apollo 3
cacheRedirects has been removed in Apollo 3, but the same functionality can be achieved with field policies. You still need to set it up when instantiating InMemoryCache, but it's slightly different:
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
post: (_, {args, toReference}) => toReference({__typename: "Post", id: args.id}),
},
},
},
});
We define a field policy read on the field post on type Query. Note that the second argument now includes both args and the helper utilities. This includes toReference, that's basically the new getCacheKey.

Running query from inside Cloud Function using request parameters

I am having troubles running queries from Cloud Functions using the request parameters to build the query form HTTP calls. In the past, I have ran queries from cloud functions fine with no error. My problem arises when I try to run the query using parameters gotten from the request.
When I hardcode the location of the document in the function, it works fine but when I try to build a query, it returns status code of 200. I have also logged the the built query and it is logging out the right thing but no data is being returned. It only returns data when the document path is hardcoded. See code below.
Query looks like this
https://us-central1-<project-id>.cloudfunctions.net/getData/CollectionName/DocumentName
export const getData = functions.https.onRequest((request, response) => {
const params = request.url.split("/");
console.log("the params 0 "+params[0]);
console.log("the params 1 "+params[1]);
console.log("the params 2 "+params[2]);
//Build up the document path
const theQuery = "\'"+params[1]+"\/"+params[2]+"\'";
console.log("the query "+theQuery); <-- logs out right result in the form 'Collection/Document'
//Fetch the document
const promise = admin.firestore().doc("\'"+params[1]+"\/"+params[2]+"\'").get() <---- This doesnt work, building the query
//const promise = admin.firestore().doc('collectionName/DocID').get() <---- This hard coded and it works
promise.then(snapshot => {
const data = snapshot.data()
response.send(data)
}).catch(error => {
console.log(error)
response.status(500).send(error);
})
});
I tried using a different approach and giving the datafields a names as seen below
Query looks like this
https://us-central1-<project-id>.cloudfunctions.net/getData?CollectionName=CName&DocumentID=Dname
export const getData = functions.https.onRequest((request, response) => {
const collectName = request.query.CollectionName;
const DocId = request.query.DocumentName;
//Build up the document path
const theQuery = "'"+collectName+"\/"+collectName+"'";
console.log("the query "+theQuery); <---Logs out correct result
//Fetch the document
const promise = admin.firestore().doc(theQuery).get() <-- Building the query does not work
//const promise = admin.firestore().doc('collectionName/DocID').get() <---- This hard coded and it works
promise.then(snapshot => {
const data = snapshot.data()
response.send(data)
}).catch(error => {
console.log(error)
response.status(500).send(error);
})
});
In both cases, when the request is build from the URL, it does not return any data and it does not return any errors. And I am sure the documents I am trying to fetch exsist in the database. Am I missing anything ?
Try request.path. Then you can obtain the path components, e.g. request.path.split("/")[1]
The syntax for request.query is valid when using Express. This is referenced in some of the docs, but not made explicit that Express is required. It's confusing.
To properly handle the dynamic inputs, you may have more luck working with Express and creating routes and handlers. This Firebase page has links to some projects using it.
Walkthough set-up using Express on Firebase.

Axios get with param array

I create one get request with axios:
this.$axios.get('/cidade/',{
params: {
q: result,
}
})
.then((response) => {
this.cidades = response.data;
})
.catch(function (error) {
// handle error
// eslint-disable-next-line
console.log(error);
})
.then(function () {
// always executed
});
but my result is an array [123asdas1,asdasd2312] and when the axios excute the request he create that url:
http://localhost:8081/cidade/?q[]=5b9cfd0170607a2e968b0a1e&q[]=5b9cfd0170607a2e968b0a1d
so It's possible to remove the [] from q param? how?
tks
When composing a query string where one field has multiple values (i.e. if it were an array), then there is no standard that says how it must be encoded in the query string, however most web servers accept this syntax:
http://localhost:8081/cidade/?q[]=value1&q[]=value2
which is why axios defaults to it. Check your web server to see if it is reading the parameter as an array correctly.
If you want to force it to be encoded in some other way, just convert the array to a string in whatever format you want and send it as a single value:
this.$axios.get('/cidade/', {
params: {
q: JSON.stringify(result)
}
})
http://localhost:8081/cidade/?q=[value1,value2]
(The [ and ] characters may be percent-encoded.)
In general, this syntax cannot distinguish between the string "[value1,value2]" and the array [value1, value2], so the web server will have to choose one or the other. Again, this is all dependent on your web server.

get instagram public media using new api

I am trying to get the 10 latest instagram photos of a public profile in my nodejs app:
const request = require('request');
const accessToken = '1234567890123456789012345678901234567890';
const url = `https://api.instagram.com/v1/users/1470414259/media/recent/?access_token=${accessToken}&count=10`;
request.get(url, { json: true }, (err, res, body) => {
console.log('print the body: ', body);
});
my attempt is not successful, and i get no results.
Could somebody help me get this going?
i am trying to follow the example here : https://www.instagram.com/developer/endpoints/users/#get_users_media_recent
update:
i updated the username to user ID, but i am still getting no response:
{ meta:
{ code: 400,
error_type: 'APINotFoundError',
error_message: 'this user does not exist' } }
update 2:
actually i realize that my access token has some restrictions:
{"meta": {"code": 400, "error_type": "OAuthPermissionsException", "error_message": "This request requires scope=public_content, but this access token is not authorized with this scope. The user must re-authorize your application with scope=public_content to be granted this permissions."}}
why is this happening? i am just trying to get some public profile feeds.
update 3:
is there any other way to bypass these access token permissions and just get the 10 media of a public user?
take a look at the json that this link is returning:
https://www.instagram.com/aa/?__a=1
body.user.media[0] will give you the last photo object
body.user.media[1] will give you the photo before the last one object
.. and so on.
what you have to do here is change aa in the url I provided to your desired username.
ps. you might use some json viewer tools to organize the json if you browser doesn't support that
You have two problems there:
First of all the endpoint receives the user ID as param (not the username):
const url = `https://api.instagram.com/v1/users/${userId}/media/recent/?access_token=${accessToken}&count=10`;
Besides that, the endpoint responds with an object that has only one element: data, that is an array. So I don't know what data you are trying to get but you won't get it in body.user, you should go through the array and ask for the user in the corresponding position, for example try this: console.log(body.data[0].user).
And you of course must define a valid access token!

Categories

Resources