Test cloud functions with authentication context - firebase

I have a cloud function that uses the context field that contains the user authentication context:
export const updateLocation = functions
.region(functionsRegion)
.https.onCall(async (
data : { location : firestore.GeoPoint },
context
) => { ... }
How do I specify that context with the GCP cloud function testing tab or Postman? The first only lets me specify the data segment, the second doesn't recognize an auth field, i.e. a body of:
{
"data": {
"location": [50.021376, 14.464792]
},
"auth": {
"uid": "1Rxn217sSoffpGXfzdWU"
}
}
results in Request body has extra fields: auth; Invalid request, unable to process.

Related

RTK-Query meta response always empty object

When using RTK-Query to call an API, I need to access a custom header value. I've tried various options for accessing the response headers from the documentation, but all attempts result in an empty object returned:
meta: {
"request": {},
"response":{}
}
I can see in the network tab, that the headers are provided in the response. This is part of a refactor from using raw Axios calls where the header was available from the response object as the headers property.
I've tried accessing the headers through the meta parameter on the transformResponse function of the createApi
transformResponse: (response, meta, arg) => {
console.log(`Transform -> meta: ${JSON.stringify(meta)}`)
// dataKey: meta.response.headers['x-company-data-key'],
return {
...response,
// dataKey
}
}
I've also tried accessing the headers via the meta property of the action parameter from the extraReducers function in the feature slice:
extraReducers: (builder) => {
builder.addMatcher(companyApi.endpoints.getSomeData.matchFulfilled, (state, action) => {
console.log(`action meta: ${JSON.stringify(action.meta)}`)
state.result = action.payload.result
// state.dataKey = action.meta.response.headers['x-company-data-key']
})
}
Both instances result in the meta object that looks like this:
meta: {
"request": {},
"response": {}
}
The API's base query looks like this:
baseQuery: fetchBaseQuery({
baseUrl: process.env.REACT_APP_API_ENDPOINT,
prepareHeaders: ( (headers, { getState }) => {
const realm = getState().companyAuth.realm
if (realm) {
const token = readToken(realm)
if (token)
headers.set('Authorization', `Bearer ${token.access_token}`)
}
return headers
})
}),
and finally, the endpoints definition for this API endpoint:
getCompanyData: builder.query({
query: (params) => {
const { location, page, pageSize } = params
return {
url: `/companies/${location}`,
params: { page, pageSize }
}
}
})
Based on what I could find in the documentation, GitHub issues, and PRs, it seems that the meta property should automatically add the headers, status, and other additional HTTP properties. I'm not sure what I've missed where the response object is completely empty. Is there an additional setting I need to add to the baseQuery?
I should note, that the response data is working properly. I just need the additional information that is returned in the custom header.

AWS Amplify Show AppSync/GraphQL Subscription Connection Status

After creating an AWS AppSync/GraphQL subscription -- how do I get the subscriptions connection status and any future connection status changes?
The AWS Amplify documentation is clear on how to create a subscription:
const subscription = API.graphql(
graphqlOperation(subscriptions.onCreateTodo)
).subscribe({
next: ({ provider, value }) => console.log({ provider, value }),
error: (error) => console.warn(error)
});
The documentation is also clear on how to get subscription connection statuses using hub:
Hub.listen('api', (data: any) => {
const { payload } = data;
if (payload.event === CONNECTION_STATE_CHANGE) {
const connectionState = payload.data.connectionState as ConnectionState;
console.log(connectionState);
}
});
But this hub example listens to all GraphQL subscriptions. How can I listen to a specific subscription? Or when listening to all subscriptions -- is there a way to associate a hub connection with a specific GraphQL subscription?
A simplified example: In an app, I have a chat page and a GraphQL subscription that listens for new chats. I also have a page that shows the results of a long running backend process and a GraphQL subscription that listens for process complete. Hub listens to all GraphQL subscriptions and one of the subscriptions has been disconnected. Which GraphQL subscription has been disconnected so the app can display a message to the user?
The object returned by hub looks like this. I'm guessing the _linked properties link back to the specific GraphQL subscription. But I'm not seeing any human readable properties to determine which GraphQL subscription it's linked to.
{
"channel": "api",
"payload": {
"event": "ConnectionStateChange",
"data": {
"provider": {
"socketStatus": 0,
"keepAliveTimeout": 300000,
"subscriptionObserverMap": {},
"promiseArray": [],
"connectionStateMonitor": {
"_linkedConnectionState": {
"networkState": "connected",
"connectionState": "disconnected",
"intendedConnectionState": "disconnected",
"keepAliveState": "healthy"
},
"_linkedConnectionStateObservable": {},
"_linkedConnectionStateObserver": {
"_subscription": {
"_observer": {},
"_state": "ready"
}
}
},
"keepAliveTimeoutId": 108,
"keepAliveAlertTimeoutId": 109
},
"connectionState": "Connecting"
},
"message": "Connection state is Connecting"
},
"source": "PubSub",
"patternInfo": []
}
The GraphQL subscription returns a provider object that looks like this. It also has a connectionStateMonitor property. But when trying to display this property it errors with: Property 'connectionStateMonitor' is private and only accessible within class 'AWSAppSyncRealTimeProvider'
{
"provider": {
"socketStatus": 0,
"keepAliveTimeout": 300000,
"subscriptionObserverMap": {},
"promiseArray": [],
"connectionStateMonitor": {
"_linkedConnectionState": {
"networkState": "connected",
"connectionState": "disconnected",
"intendedConnectionState": "disconnected",
"keepAliveState": "healthy"
},
"_linkedConnectionStateObservable": {},
"_linkedConnectionStateObserver": {
"_subscription": {
"_observer": {},
"_state": "ready"
}
}
},
"keepAliveTimeoutId": 1056,
"keepAliveAlertTimeoutId": 1057
},
"value": {
"data": {
"BSubscribeToTest": {
"matchIt": 10,
"item": {
"rateRequestId": "123"
}
}
}
}
}
You need a combination of:
Hub.listen('some-channel', callback); and
Hub.dispatch('some-channel', {event, data, message});
That channel name (first parameter) can be added dynamically so that you are monitoring multiple channels in an application. Each channel can then be listened to and subscribed to separately and you can check the status, data, etc for each one.
Because you are listening on the example "api' channel above you would need to add that channel to this line:
graphqlOperation(subscriptions.onCreateTodo, 'api') Take note of the 'api' at the end.

Firestore HTTP Insomnia query : Stream error in the HTTP/2 framing layer

I'm trying to query my Firestore database using an HTTP query via the Insomnia API:
https://firestore.googleapis.com/v1/projects/typebot/databases/(default)/documents/users
with this body:
{
"structuredQuery": {
"from": [
{
"collectionId": "users"
}
],
"where": {
"fieldFilter": {
"field": {
"fieldPath": "email"
},
"op": "EQUAL",
"value": {
"stringValue": "email#test.com"
}
}
}
}
}
And I get the following error:
HTTP query: Stream error in the HTTP/2 framing layer
Any idea what's wrong?
May try to change "GET" to "POST"
I had a similar problem performing a GET request:
GET https://us-central1-my-project-id.cloudfunctions.net/getUsers HTTP/1.1
content-type: application/json
{
"minAge": "18"
}
against an endpoint defined by this Firestore HTTP Cloud Function:
exports.getUsers = functions.https.onRequest(async (req, res) => {
// find matching users by age limit.
const ageLimit = req.body.age;
...
});
Turns out, based on this other SO post, that a request body with GET does not have any meaning in the sense that the HTTP spec recommends that the "message-body SHOULD be ignored when handling the request" (presumably, by the server, and that the Firestore server implements this behavior). Oddly, I didn't catch this issue running the exact same function locally with the Functions emulator, so it is likely the local server ignores this recommendation.
To fix the issue, I changed my function to parse the query params instead of a request body:
exports.getUsers = functions.https.onRequest(async (req, res) => {
// find matching users by age limit.
const ageLimit = req.query.age; // extract the age as a query param
...
});
and the request:
GET https://us-central1-my-project-id.cloudfunctions.net/getUsers?age=18

AppSync client query not returning complete data response

I am having an issue getting results back from my AppSync API via AWSAppSyncClient. I can run the query in the AWS AppSync console and get the complete results, however when I run the query from my client the portion of the results I am looking for returns an empty array.
I have tried slimming down the query to return less results, as I read at one point that dynamo will run a filter on the results being returned if you do not provide your own. I have also read this could have something to do with the partition keys used in the dynamoDB table, however AppSync provisioned that resource for me and handled the initial config. I am new to working with AppSync so I am sort of drawing a blank on where to even start looking for the issue because there is not even an error message.
The Query I am running
export const getUserConversations = `query getUser($id: ID!) {
getUser(id: $id) {
id
conversations {
items {
conversation{
id
associated{
items{
convoLinkUserId
}
}
}
}
}
}
}
`;
Call being made in a redux actions file
export const getUserConvos = (id) => async dispatch => {
AppSyncClient.query({
query: gql(getUserConversations),
variables: {
id: id
}
}).then(res => {
console.log("RES FROM CONVO QUERY", res)
})
}
This is the response I am getting in the browser
Notice conversations.items returns an empty array.
getUser:
conversations:
items: []
__typename: "ModelConvoLinkConnection"
__proto__: Object
id: "HIDDEN_ID"
__typename: "User"
__proto__: Object
__proto__: Object
However if i run the exact same query in the playground on the AppSync console I get this...
{
"data": {
"getUser": {
"id": "HIDDEN_ID",
"conversations": {
"items": [
{
"conversation": {
"id": "HIDDEN_ID",
"associated": {
"items": [
{
"convoLinkUserId": "HIDDEN_ID"
},
{
"convoLinkUserId": "HIDDEN_ID"
}
]
}
}
},
{
"conversation": {
"id": "HIDDEN_ID",
"associated": {
"items": [
{
"convoLinkUserId": "HIDDEN_ID"
},
{
"convoLinkUserId": "HIDDEN_ID"
}
]
}
}
}
]
}
}
}
}
*HIDDEN_ID is a placeholder
I know that the objects are in my DB, however if i run the query via my react application I get nothing, and if I run it in the console on AWS I get another. I need to be able to have access to these conversations via the client. What could be causing this?

Firebase.auth error with 400 bad request

I found a strange error while I developing system using Firebase with service url contains user data.
User data is below.
{
"uid": "kt9Hcp2FbYbBvvIeSHHa1RbvHcv2",
"displayName": "Anonymous 901",
"photoURL": null,
"email": null,
"emailVerified": false,
"identifierNumber": null,
"isAnonymous": true,
"providerData": [
],
"apiKey": "MyApiKeyString",
"appName": "MyAppName",
"authDomain": "my.auth.domain",
"stsTokenManager": {
"apiKey": "MyApiKeyString",
"refreshToken": "refreshTokenString",
"accessToken": "accessTokenString",
"expirationTime": 1532451863076
},
"redirectEventId": null
}
I encode the above anonymous user data and include it in the service url.
( http://myserviceurl?userdata=encodedUserData )
Inside the system receives that url, firebase creates a user object with that user data contained in the url.
The purpose of this url is to use specific user's information in any browser.
However, when I call that service url, sometimes system creates user object well, sometimes got error -
400 Bad request errors with
https://www.googleapis.com/identitytoolkit/v3/relyingparty/setAccountInfo?key=MyApiKeyString
And error data is below,
{
"error": {
"code": 400,
"message": "TOKEN_EXPIRED",
"errors": [
{
"message": "TOKEN_EXPIRED",
"domain": "global",
"reason": "invalid"
}
]
}
}
Few hours later it works well, I changed nothing though.
I could not find the exact error point, but I suspect error occurs while observing authentication state or before this step.
Here is code snipets
#bind
private makeUserLoadingPromise(): Promise<void> {
let unSubscribe: () => void;
return new Promise<void>((resolve, _reject) => {
const onInitialized = this.makeOnInitializedAuthStateChanged(resolve);
unSubscribe = this.auth.onAuthStateChanged(onInitialized);
}).then(() => {
unSubscribe();
this.auth.onAuthStateChanged(this.onAuthStateChanged);
});
}
#bind
private makeOnInitializedAuthStateChanged(resolve: () => void) {
return (user: firebase.User | null) => {
this.user = user;
resolve();
};
}
#bind
private onAuthStateChanged(user: firebase.User | null) {
this.user = user;
}
Or maybe it relates with expirationTime?
I couldn't find any hints about this situation.
Any advice would be appreciated.
It is not clear what you are doing, but it appears that you are using the API incorrectly and insecurely. The plain user object contains a refresh token that is indefinite. Passing it around via URL is a really bad idea.
First don't rely on internal implementations, it is subject to change.
To get the user's information on your backend, the right way to do it, is to get the user's ID token using officially supported API, eg user.getIdToken(), then pass it to your server.
On your server, you verify it via the Firebase Admin SDK: admin.auth().verifyIdToken(idToken). Then you know this is a real authenticated user. If you need the full user info, you can then look it up using the decoded user id in the token: admin.auth().getUser(decodedIdToken.sub).

Resources