Support additional mime types in hapi - mime-types

I am working on a graphql + relay app served on top of hapi and would like to support requests to the graphql endpoint with the application/graphql mime type.
Here you can see me sending POST requests to the graphql endpoint.
~> curl -X POST --header "Content-Type:application/json" -d '{"query":"{content(id:\"13381672\"){title,id}}"}' http://127.0.0.1:8000/graphql
{"data":{"content":{"title":"Example Title","id":"13381672"}}}
~> curl -X POST --header "Content-Type:application/graphql" -d '{"query":"{content(id:\"13381672\"){title,id}}"}' http://127.0.0.1:8000/graphql
{"statusCode":415,"error":"Unsupported Media Type"}
I do not see any place in my hapi server options where there is any explicit configuration for mime types and other than some terse documentation here.
I have set up an options mime configuration as per the below, passing options into the server instantiation, but I am still seeing the "Unsupported Media Type" error.
options.mime = {
override: {
'application/graphql': {
charset: 'UTF-8',
compressible: true,
type: 'application/graphql'
}
}
};
Does anybody else here have this kind of experience with hapi?

Each route has a payload config option which takes an allow property that lets hapi know which mimetypes to allow for that route. If you set it to application/graphql, and the parse option to false, your requests will work.
Unfortunately, you'll have to parse the payload yourself.
Here's an example route:
server.route({
method: ['POST', 'PUT'],
path: '/graphql',
config: {
payload: {
parse: false,
allow: 'application/graphql'
}
},
handler: function(request, reply) {
reply(request.payload)
}
})

Related

AWS bucket can't show pdf files on specific routes

My nextjs app (v12.1.3) allows users to upload files. The server stores them in an AWS s3 bucket.
I store them in this way:
import _ from 'lodash';
import AWS from 'aws-sdk';
import { v4 as uuidv4 } from 'uuid';
import { config } from '../config';
// file is in base64 format
const uploadFileToS3 = async (file: string) => {
const s3 = new AWS.S3({
accessKeyId: config.AWS_ACCESS_KEY_ID,
secretAccessKey: config.AWS_SECRET_ACCESS_KEY,
signatureVersion: 'v4',
region: 'eu-south-1',
});
const contentType = file.split(':')[1].split(';')[0];
const params = {
Key: uuidv4(),
Body: Buffer.from(file.replace(/^data:.+;base64,/, ''), 'base64'),
Bucket: config.AWS_S3_BUCKET_NAME,
ContentType: contentType,
};
return await s3.upload(params).promise();
};
export default uploadFileToS3;
My bucket settings:
Block public access (bucket settings) -> OFF
Bucket policy
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-test-bucket/*"
}
]
}
Cross-origin resource sharing (CORS)
[
{
"AllowedHeaders": [],
"AllowedMethods": [
"GET"
],
"AllowedOrigins": [
"*"
],
"ExposeHeaders": []
}
]
I store in my mongo DB the url returned from uploadFileToS3 method.
Client side, instead, I use this url to view images and pdf files. The problem is that on a certain route /generic-entity/{id} I cannot preview the .pdf files (there is no problems with .jpeg or .png files).
I get this error (even it is a warning):
Cross-Origin Read Blocking (CORB) blocked cross-origin response https://xxxxxx.execute-api.us-east-2.amazonaws.com/default/xxxxxx with MIME type application/pdf. See https://www.chromestatus.com/feature/5629709824032768 for more details.
and I looked around and found out it is a new web platform security feature that helps mitigate the threat of side-channel attacks
Instead on the route /generic-entity I can preview the pdf files. The difference is that on this last one there is a automatic GET to the aws like this one:
curl 'https://my-test-bucket.s3.eu-south-1.amazonaws.com/e751b456-c996-4747-bcef-03021cd80357__foo__Doc%2520base.pdf' \
-H 'Accept: */*' \
-H 'Accept-Language: en,en-US;q=0.9,it-IT;q=0.8,it;q=0.7' \
-H 'Connection: keep-alive' \
-H 'If-Modified-Since: Sun, 26 Jun 2022 19:21:16 GMT' \
-H 'If-None-Match: "9b1027ca72e993b2607ba5b54e735c64"' \
-H 'Origin: http://localhost:3000' \
-H 'Referer: http://localhost:3000/' \
-H 'Sec-Fetch-Dest: empty' \
-H 'Sec-Fetch-Mode: cors' \
-H 'Sec-Fetch-Site: cross-site' \
-H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36' \
-H 'sec-ch-ua: ".Not/A)Brand";v="99", "Google Chrome";v="103", "Chromium";v="103"' \
-H 'sec-ch-ua-mobile: ?0' \
-H 'sec-ch-ua-platform: "macOS"' \
--compressed
on the other one instead there's is none. I tried to do it manually but it didn't solve the problem.
For major details here's the react component I use to preview the pdf file:
// import default react-pdf entry
import { Document, Page, pdfjs } from 'react-pdf';
// import pdf worker as a url, see `next.config.js` and `pdf-worker.js`
import workerSrc from '../../pdf-worker';
pdfjs.GlobalWorkerOptions.workerSrc = workerSrc;
interface Props {
file: any;
className?: string;
}
//in this case file is a url.
const PDFPreview: React.FC<Props> = ({ file, className }) => {
function onDocumentLoadSuccess() {
document
.getElementById('react-pdf__Page__canvas')
?.style.setProperty('display', 'flex', 'important');
}
return (
<div
className={`flex overflow-y-hidden rounded-lg cursor-pointer ${className}`}
>
<Document file={file} onLoadSuccess={onDocumentLoadSuccess}>
<Page
key={`page_${1}`}
pageNumber={1}
renderAnnotationLayer={false}
renderTextLayer={false}
className="flex w-full"
style={{ display: 'flex' }}
/>
</Document>
</div>
);
};
export default PDFPreview;
Do you have any suggestion on how to fix this?
P.S. I also replicated this error in this github repo and I deployed it on vercel. You can see that on the route /my-entity it works, while on the route /my-entity/1 doesn't using the same url and the same react component

What is the correct syntax for adding the xframe header module to cra rewired?

I'm trying to return the X-Frame-Options in my create react app (rewired) but I'm not sure of the correct syntax to use to add the function to my existing override. How do I do this properly?
module.exports = override(
fixBabelImports("react-bulma-components", {
libraryName: "react-bulma-components",
libraryDirectory: "lib/components"
}),
{devServer: function(configFunction) {
return function(proxy, allowedHost) {
const config = configFunction(proxy, allowedHost)
config.headers = {
'X-Frame-Options': 'Deny'
}
return config
}
}},
...addCompressions()
);
The front end is a CRA (rewired) non-static webapp
The backend is a node app hosted seperately
I've also tried changing the buildpack to this buildback in order to add the configuration in a static.json file, but that breaks a lot of different things, uses a different server etc.
The proper way of doing this, is by not doing it... is useless, dont waste your time. Let's remember that yow CRA Page will executed on the browser and it won't send you headers/data or anything, instead it will be send as well by Nginx/Apache/NodeJs something else.
Firefox even says the same: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
Note: Setting X-Frame-Options inside the element is useless! For instance, has no effect. Do not use it! X-Frame-Options works only by setting through the HTTP header, as in the examples below.
Heroku
You can configure different options for your static application by writing a static.json in the root folder of your application.
Custom Headers
Using the headers key, you can set custom response headers. It uses the same operators for pathing as Custom Routes.
{
"headers": {
"/": {
"Cache-Control": "no-store, no-cache"
},
"/assets/**": {
"Cache-Control": "public, max-age=512000"
},
"/assets/webfonts/*": {
"Access-Control-Allow-Origin": "*"
}
}
}
https://blog.heroku.com/using-http-headers-to-secure-your-site

No overload matches this call in Cors TypeScript

I want to create API using Express TypeScript, but when I want to use cors it shown error like this:
No overload matches this call.
The last overload gave the following error.
Argument of type 'RequestHandler<any>' is not assignable to parameter of type 'PathParams'.
Type 'RequestHandler<any>' is missing the following properties from type '(string | RegExp)[]': length, pop, push, concat, and 26 more.
I installed the package using yarn add cors express and yarn add #types/cors #types/express
This is my code:
const api = express();
const main = express();
api.use(cors({ origin: true }));
main.use(bodyParser.json());
main.use(bodyParser.urlencoded({ extended: false }));
main.use("/api/v1", api);
export const webApi = functions.https.onRequest(main);
The error is in app.use(cors({ origin: true });
Specifying the path is optional for use, and the cors docs show exactly what you did as a valid example. IOW, your code is great! There's no problem there.
There was recently a change to the express type definitions. It seems like there are two sets: #types/express and #types/express-serve-static-core. The former depends on the latter, and there seems to be some breaking changes in express-serve-static-core that #types/express hasn't quite caught up with yet.
In that thread, a couple of workarounds are proposed.
You can pin the version of #types/express in your package.json to version 4.17.0. Or you can disable library checking with the Typescript compiler's skipLibCheck flag.
What worked for me it was just declaring a variable before:
const corstOpts = cors({ origin: true })
api.use(corstOpts);
I beleive you are calling it the wrong way. According to the official docs here https://expressjs.com/en/resources/middleware/cors.html you "use" it like thus api.use(cors())
And the you pass in the options when you "call" cors like something like this
api.get('/products/:id', cors(corsOptions), function (req, res, next) {
res.json({msg: 'This is CORS-enabled for only example.com.'})
})
So your case it would then be something like
api.get('/products/:id', cors({ origin: true}), function (req, res, next) {
res.json({msg: 'This is CORS-enabled for only example.com.'})
})
This solution works. Adding a variable before defining cors like so:
const corsOptions = {
origin: '*',
credentials: true
};
and then use the variable like so:
app.use(cors(corsOptions))

How to send Authorization header with a request in swagger-ui-react?

I use swagger-ui-react in my application. But I don't know how to config to add the authorization into api requests.
I had found an answer use in swagger ui from here:
window.swaggerUi = new SwaggerUi({...})
...
swaggerUi.api.clientAuthorizations.add("key", new SwaggerClient.ApiKeyAuthorization("Authorization", "Basic dXNlcm5hbWU6cGFzc3dvcmQ=", "header"));
But I don't know how to use in swagger-ui-react. Here is my code:
import styles from './index.less';
import React from 'react';
// tslint:disable
import SwaggerUI from 'swagger-ui-react';
import 'swagger-ui-react/swagger-ui.css';
// tslint:able
const SwaggerComp = params => {
const auth = params.authorization;
return (
<div className={styles.wrapper}>
<SwaggerUI
url="/v2/swagger-file-url"
withCredentials
/>
</div>
)
};
export default SwaggerComp;
To send the Authorization header in "try it out" requests, your API definition file (/v2/swagger-file-url) must define the appropriate security for operations. Users will need to click the "Authorize" button to enter the authentication information (such as the username and password for Basic auth) before doing "try it out".
OpenAPI 3.0 example:
openapi: 3.0.2
components:
securitySchemes:
basicAuth:
type: http
scheme: basic
security:
- basicAuth: []
OpenAPI 2.0 example:
swagger: '2.0'
securityDefinitions:
basicAuth:
type: basic
security:
- basicAuth: []
For more information, see:
Authentication guide for OpenAPI 3.0
Authentication guide for OpenAPI 2.0
In react-swagger-ui, they have a network property called requestInterceptor, in here you can modify the request json before passing it to the api call, so you can add the authorization header in there, and remember to return the modified json in requestInterceptor.
To set up the authentication header in the json, there are a couple ways to do it, I was using sigv4 for authorization header, and I used Amplify Signer to generator all the required header(Authorization, X-Amz-Date..) for me before the api call, this is the link here.

From where does Relay fetch the Schema of GraphQL?

I am having a running GraphQL based server and I am able to test it properly using GraphiQL. But I am unable to understand the implementation of Relay.
As GraphQL is on the server side, then how its schema is transferred to relay over network layer at the client end? Or how it is pointing to the same GraphQL?
It takes a little extra work. Basically you have to dump a serialized version of the schema to a file on the server side, and then move that file over to the client and include it during babel compilation. Facebook has a babel plugin that takes this file and builds it into the bundle in order for Relay to know about the schema.
EDIT: here is a snippet for how to dump the schema file to JSON
import { graphql } from 'graphql'
import { introspectionQuery, printSchema } from 'graphql/utilities'
/*
generates json of our schema for use by relay
*/
export default function (schema) {
return new Promise((resolve, reject) => {
graphql(schema, introspectionQuery).then(result => {
if (result.errors) {
console.error(`ERROR introspecting schema: ${result.errors}`)
reject(new Error(result.errors))
} else {
resolve({ json: result, graphql: printSchema(schema) })
}
})
})
}
After you obtain that, you have to npm install babel-relay-plugin and include that in your babel plugins (probably in the webpack config, if you're using that).

Resources