Trying to implement shopify webhooks but getting 'InternalServerError: stream is not readable' - next.js

I'm building an app for shopify and need to add the GDPR webhooks. My back end is handled using next.js and I'm writing a webhook handler to verify them. The docs havent been very helpful because they dont show how to do it with node. This is my verification function.
export function verifiedShopifyWebhookHandler(
next: (req, res, body) => Promise
): NextApiHandler {
return async (req, res) => {
const hmacHeader = req.headers['x-shopify-hmac-sha256'];
const rawBody = await getRawBody(req);
const digest = crypto.createHmac('sha256', process.env.SHOPIFY_API_SECRET).update(rawBody).digest('base64');
if (digest === hmacHeader) {
return next(req, res, rawBody);
}
const webhookId = req.headers['x-shopify-webhook-id'];
return res.status(401).end();
};
}
But I get this Error: error - InternalServerError: stream is not readable
I think it has to do with now Next.js parses the incoming requests before they are sent to my api. Any ideas?

I discovered the answer. Next.js was pre parsing the body in the context which made it so that I couldn't use the raw body parser to parse it. By setting this:
export const config = {
api: {
bodyParser: false
}
};
above the api function in the api file it prevented next from parsing it and causing the issue. I found the answer because people had the same issue integrating swipe and using the bodyParser.

Related

Accessing Google Cloud Function from Vercel Serverless Function

I am seeking the best manner in which this should be done.
I have a https based GCF Function such as:
// google function
exports.someFunction = async (req, res) => {
try {
...
// some logic and access
res.status(200).send(data)
}
catch(error) {
res.status(400).send(error.message)
}
}
The API serverless function in Next.js is using axios. Is that the recommended method?
// next.js pages/api/call-google-func.js
async function handler(req, res) {
try {
const url = '....' //https://gcp-zone-project-xx834.cloudfunctions.net/someFunc
const res = await axios.get(url)
const resdata = res.data
res.status(200).send(resdata)
}
catch(error) {
res.status(400).send(error)
}
}
The problem with this method is that the GCF must have public access. How can we set up to access the GCF from Next.js by passing credentials as environment variables. Thanks
I think for this situation where a Vercel Serverless Function must communicate with the outside world, a Google Cloud Function, you'd want to create a JWT token on Vercel's side to pass to Google's side which you would then need to verify. I think Exchanging a self-signed JWT for a Google-signed ID token would be what you need.
Since either side doesn't know about the other, Google's IAM normal cloud privileges for allowing GCG<>GCF communication wouldn't apply here.

Get values from SvelteKit's $app/stores outside of the lifecycle of a component

My Svelte components import readable stores like this:
import { classes, locations, schedule } from 'stores.ts'
In stores.ts, I want to build the URL for fetch dynamically using page.host from $app/stores.
// Note: this is not a Svelte component; it's stores.ts
import { readable } from 'svelte/store'
import { getStores } from '$app/stores'
const { page } = getStores()
let FQDN
page.subscribe(({ host }) => {
FQDN = host
})
const getArray = async (url) => {
const response: Response = await fetch(url)
if (!response.ok) throw new Error(`Bad response trying to retrieve from ${url}.`)
return await response.json()
}
const getReadableStore = (url: string) => readable([], set => {
getArray(`http://${FQDN}${url}`)
.then(set)
.catch(err => console.error('Failed API call:', err))
return () => {}
})
export const classes = getReadableStore('/api/class/public.json')
export const locations = getReadableStore('/api/location/public.json')
export const schedule = getReadableStore('/api/schedule/public.json')
The sixth line throws this error...
Error: Function called outside component initialization
at get_current_component (/Users/nates/dev/shy-svelte/node_modules/svelte/internal/index.js:652:15)
at Proxy.getContext (/Users/nates/dev/shy-svelte/node_modules/svelte/internal/index.js:685:12)
at Module.getStores (/.svelte-kit/dev/runtime/app/stores.js:17:26)
at eval (/src/stores.ts:6:38)
at instantiateModule (/Users/nates/dev/shy-svelte/node_modules/#sveltejs/kit/node_modules/vite/dist/node/chunks/dep-e9a16784.js:68197:166)
Two questions...
What is the correct way to get page values from $app/stores outside of the context of a component? Is this possible? Answer from below: No, this is not possible outside the context of a component.
If I'm accessing a SvelteKit site, let's say http://localhost:3000/something or https://example.com and a Svelte component loads a readable store from stores.ts, is there a way in stores.ts to determine whether the original page request that loaded the component (which loaded from stores.ts) was http or https? Answer from below: No, this is not possible in stores.ts - only from a component.
UPDATE: Based on the feedback, I'm going to set a value in my .env called VITE_WEB_URL=http://localhost:3000 and change it for the production system. This cuts down on the number of lines of code and may be a better practice (comments welcome)...
// revised stores.ts
import { readable } from 'svelte/store'
const { VITE_WEB_URL } = import.meta.env
const getArray = async (url) => {
const response: Response = await fetch(url)
if (!response.ok) throw new Error(`Bad response trying to retrieve from ${url}.`)
return await response.json()
}
const getReadableStore = (url: string) => readable([], set => {
getArray(`${VITE_WEB_URL}${url}`)
.then(set)
.catch(err => console.error('Failed API call:', err))
return () => {}
})
export const classes = getReadableStore('/api/class/public.json')
export const locations = getReadableStore('/api/location/public.json')
export const schedule = getReadableStore('/api/schedule/public.json')
Extract from https://kit.svelte.dev/docs#modules-$app-stores
Because of that, the stores are not free-floating objects: they must be accessed during component initialisation, like anything else that would be accessed with getContext.
Therefore, since the readable store is bound to the context of a svelte component, I suggest you subscribe either way ($ or .subscribe) inside the component of the SvelteKit website and then send the protocol value (http or https) as parameter when it updates so that stores.ts stores it in a variable.
However, it looks like SvelteKit does not provide the protocol value, so parse the client side window.location.href in the page subscription and then send it.
Referencing a svelte store can be done everywhere.
Using the $: shorthand syntax, however, only works within a component.
$: BASE = `http://${$page.host}`
SvelteKit appears to delegate this to fetch indeed

Issues with NextJs SSR and cookies in Apollo

I would like to use cookies for authentication in my nextjs app. I have a bug in my code where the SSR won't work because somewhere in the execution process of the code it does not find the cookie on the first render of the page so it will throw an error. I have played with the code a lot now and have gotten it to a state where the data will eventually load but will not be a SSR page. Has anyone else dealt with this problem?
I am using next, apollo client and apollo server express.
When you do an SSR, the code runs on the server. The cookies you added in browser are not available as default. You can access then in getInitialProps or getServerSideProps via req.headers.cookie and pass it to the authentication code again.
Alternately, you can use an npm module like react-cookie https://www.npmjs.com/package/react-cookie which support isomorphic cookies. More examples on integration are available on the link.
We can custom the headers before sending.
Please check my full answer at this link https://github.com/apollographql/apollo-client/issues/5089#issuecomment-749301669
async function getHeaders(ctx) {
if (ctx?.req?.cookies) {
const cookieItems = []
for (let key of Object.keys(ctx?.req?.cookies)) {
cookieItems.push(`${key}=${ctx.req.cookies[key]}`)
}
return {
cookie: cookieItems.join('; ')
}
}
return {
}
}
WithApollo.getInitialProps = async (ctx) => {
const { AppTree } = ctx
// Initialize ApolloClient, add it to the ctx object so
// we can use it in `PageComponent.getInitialProp`.
const apolloClient = (ctx.apolloClient = initApolloClient(null, await getHeaders(ctx)))
// Run wrapped getInitialProps methods
let pageProps = {}
if (PageComponent.getInitialProps) {
pageProps = await PageComponent.getInitialProps(ctx)
}
............
}
}

Using Google Cloud Speech to Text in Firebase Cloud Functions

Google Cloud Speech to Text documentation dictates that you can access it by:
const client = new speech.SpeechClient();
const [operation] = await client.longRunningRecognize({
config: {
encoding: 'LINEAR16',
sampleRateHertz: 16000,
languageCode: 'en-US'
},
audio: {
uri: `gs://${bucket}/${name}`
}
});
const [response] = await operation.promise();
response.results.forEach(result => {
console.log(`Transcription: ${result.alternatives[0].transcript}`);
});
Now, I wanna run this code in a Firebase Cloud Function. Unfortunately, Cloud Functions run on a version of Node that does not yet support async and await functions.
Some things I've tried:
Trying TypeScript, which supports async and await: Ran into a bunch of problems with some of the other APIs I'm using.
Upgrading all my functions to Node 8 (beta), which supports async and await: Again, ran into quite a bit of bugs from the Firebase side doing this.
"Translating" the code manually (is this even a thing?): I tried to treat the code to expect a promise.
That didn't work too well either, this is how it looks:
exports.onStorageObjectFinalize = functions.storage.object()
.onFinalize((object) => {
const client = new speech.SpeechClient();
return client.longRunningRecognize({
config: {
encoding: 'LINEAR16',
sampleRateHertz: 16000,
languageCode: 'en-US'
},
audio: {
uri: `gs://${object.bucket}/${object.name}`
}
})
.then(r1 => {
const [operations] = r1;
return operations.promise();
})
.then(r2 => {
const [response] = r2;
// response.results...
return true;
});
});
Edit: When the above function runs, it says there's no operations.promise(). In fact, after taking a look at the whole operations object, the structure doesn't look like its the same function. I did found there's a promise property in operations._callOptions, so I tried returning operations._callOptions.promise() but I got a strange error: TypeError: #<CallSettings> is not a promise at client.longRunningRecognize.then.r1.
Did I mess the translation code up or would this never work anyways?
Any other things I can try or are TypeScript and Node 8 my only two options here?
Thanks, much appreciated.

Firebase : How to secure content sent without login?

I'm building a hybrid mobile app with Firebase as my backend. I want to let users post on a wall any message they want without authentication, but I feel concerned about spam possibilities. I mean, if users don't have to be authenticated to be able to post, my security rules are basically empty and anyone who gets the endpoint can post an infinite amount of content. And I don't see what I could do against it.
So I know about anonymous auth, but I'm not sure if it really fix the issue. The endpoint remains open, after all, just behind the necessity to call a method before. It adds a little complexity but not much, I think.
What I wonder is if there is a possibility to check for the call origin, to make sure it comes from my app and nothing else. Or, if you have another idea to get this more secure, I'm open to everything. Thanks!
You can accomplish this using a combination of recaptcha on the client, and firebase cloud functions on the backend.
You send the message you want to add to the store along with the captcha to the cloud function. In the cloud function, we first verify the captcha. If this one is ok, we add the message to the store. This works, because when adding items to the store via a cloud function, firebase authentication rules are ignored.
Here's an example cloud function:
const functions = require('firebase-functions')
const admin = require('firebase-admin')
const rp = require('request-promise')
const cors = require('cors')({
origin: true,
});
admin.initializeApp();
exports.createUser = functions.https.onRequest(function (req, res) {
cors(req, res, () => {
// the body is a json of form {message: Message, captcha: string}
const body = req.body;
// here we verify whether the captcha is ok. We need a remote server for
// for this so you might need a paid plan
rp({
uri: 'https://recaptcha.google.com/recaptcha/api/siteverify',
method: 'POST',
formData: {
secret: '<SECRET>',
response: body.captcha
},
json: true
}).then(result => {
if (result.success) {
// the captcha is ok! we can now send the message to the store
admin.firestore()
.collection('messages')
.add(body.message)
.then(writeResult => {
res.json({result: `Message with ID: ${writeResult.id} added.`});
});
} else {
res.send({success: false, msg: "Recaptcha verification failed."})
}
}).catch(reason => {
res.send({success: false, msg: "Recaptcha request failed."})
})
});
})
And here's some more info: https://firebase.googleblog.com/2017/08/guard-your-web-content-from-abuse-with.html

Resources