How do I send a HEAD request for a static sendFile in fastify? - fastify

When I try to send a HEAD request for sendFile I get the following error:
app.head(filePath, { logLevel: LOG_LEVEL }, async (request, reply) => {
console.log('head');
try {
const { '*': uriPath } = request.params;
const isFile = !!uriPath.match(/\.[a-zA-Z0-9]{1,5}(\?.*)?/);
if (isFile) {
setCacheControl(reply, FILE_CACHE_CONTROL_MAX_AGE);
reply.sendFile(uriPath);
} else {
const indexPath = 'index.html';
const indexStr = await fs.readFile(path.join(serveRoot, indexPath), {
encoding: 'utf-8',
});
const indexPayload = await injectEnv(indexStr);
setCacheControl(reply, INDEX_CACHE_CONTROL_MAX_AGE);
reply.type('text/html');
reply.send(indexPayload);
}
} catch (e) {
console.error(e);
}
});
web_1 | {"level":50,"time":1580244056047,"pid":1,"hostname":"3ee631923a16","reqId":5,"err":{"type":"FastifyError","message":"FST_ERR_PROMISE_NOT_FULLFILLED: Promise may not be fulfilled with 'undefined' when statusCode is not 204","stack":"FastifyError [FST_ERR_PROMISE_NOT_FULLFILLED]: FST_ERR_PROMISE_NOT_FULLFILLED: Promise may not be fulfilled with 'undefined' when statusCode is not 204\n at /usr/src/server/node_modules/fastify/lib/wrapThenable.js:34:30\n at processTicksAndRejections (internal/process/task_queues.js:85:5)","name":"FastifyError [FST_ERR_PROMISE_NOT_FULLFILLED]","code":"FST_ERR_PROMISE_NOT_FULLFILLED","statusCode":500},"msg":"Promise may not be fulfilled with 'undefined' when statusCode is not 204","v":1}
The way that express handles this is by simply passing through HEAD requests to the GET method, and then letting send (the underlying package that sends responses for both fastify and express) handle it here by not sending the output but sending the headers.
But fastify seems to incorrectly mark this as an error here

Here a working example:
const fs = require('fs').promises
const path = require('path')
const app = require('fastify')({ logger: true })
app.head('/', async (request, reply) => {
request.log.debug('head')
try {
const indexStr = await fs.readFile(path.join(__dirname, 'index.html'), { encoding: 'utf-8' })
reply.type('text/html')
return indexStr
} catch (e) {
request.log.error(e)
return e
}
})
app.listen(3000)
// curl --location --head 'http://localhost:3000/'
When an error is thrown and caught the promise is fulfilled with undefined that is causing the error you linked in the source code.
Moreover, when you use async functions as handlers, you should return what you want to send in the body or use return reply.send(content)
Anyway, consider to don't use HEAD methot because the standard says:
A response to a HEAD method should not have a body. If so, it must be ignored. Even so, entity headers describing the content of the body, like Content-Length may be included in the response. They don't relate to the body of the HEAD response, which should be empty, but to the body which a similar request using the GET method would have returned as a response.
So your body will be empty:
HTTP/1.1 200 OK
content-type: text/html
content-length: 41
Date: Wed, ---
Connection: keep-alive

Solved it. The key is to return the binary stream for filesreturn fs.readFile(uriPath); and the string for string responses return indexPayload;
import path from 'path';
import fastify from 'fastify';
import staticServe from 'fastify-static';
import { promises as fs } from 'fs';
(async () => {
const app = fastify({
logger: !!LOG_LEVEL,
});
const filePath = `${ROOT_PATH}*`;
const serveRoot = path.resolve(SERVE_PATH);
app.register(staticServe, {
root: serveRoot,
serve: false,
});
// #ts-ignore
const getHandler = async (request, reply) => {
try {
const { '*': uriPath } = request.params;
const isFile = !!uriPath.match(/\.[a-zA-Z0-9]{1,5}(\?.*)?/);
if (isFile) {
setCacheControl(reply, FILE_CACHE_CONTROL_MAX_AGE);
reply.sendFile(uriPath);
return fs.readFile(uriPath);
} else {
const indexPath = 'index.html';
const indexStr = await fs.readFile(path.join(serveRoot, indexPath), {
encoding: 'utf-8',
});
const indexPayload = await injectEnv(indexStr);
setCacheControl(reply, INDEX_CACHE_CONTROL_MAX_AGE);
reply.type('text/html');
return indexPayload;
}
} catch (e) {
request.log.error(e);
return e;
}
};
app.get(filePath, { logLevel: LOG_LEVEL }, getHandler);
// More info here on how it works
// https://github.com/fastify/fastify/issues/2061
app.head(filePath, { logLevel: LOG_LEVEL }, getHandler);
app.listen(Number.parseInt(PORT, 10), '0.0.0.0', (err: Error) => {
if (err) {
throw err;
}
});
})();

Related

how to set headers in axios patch request in react js

Can someone tell me what mistake I am making or tell me how to set the header in axios patch request. when I am running the API through postman, everything is working fine but when I connect it with the front end, an error comes up saying that the JWT is not provided on the backend
here is the frond end code :
import React, { useEffect } from 'react';
import { useParams } from 'react-router';
import axios from 'axios';
const Loader = () => {
const parmas = useParams();
const { id } = parmas;
console.log(id);
useEffect(() => {
const fetchBags = async () => {
try {
const res = await axios.patch('http://localhost:4001/public/verify', {
headers: {
'Content-Type': 'application/json',
Token: id,
},
});
console.log(res);
console.log('CBM', { res });
} catch (error) {
console.log(error);
}
};
fetchBags();
}, []);
return <div>this is loader</div>;
};
export default Loader;
below is my backend code:
export const verifyUser = async (data) => {
const token1 = data.header("Token");
try {
const verified = jwt.verify(token1, getTokenSecret());
console.log(verified)
await userModel.verifyUser(verified);
return {
message: "success",
};
} catch (error) {
console.log(`Auth Service > verifyUser > ${error.toString()}`);
throw error;
}
};
this error is comming:
Error
From docs
axios.patch(url[, data[, config]])
As you can see you pass config in 3rd argument not 2nd.
const res = await axios.patch(
'http://localhost:4001/public/verify',
{}, // data (2nd argument)
{
headers: {
'Content-Type': 'application/json',
Token: id,
},
} // config (3rd argument)
)

Steam authentication with API Gateway and lambda

I'm trying to create the authentication of my website using
https://github.com/LeeviHalme/node-steam-openid.
Steam OpenID: https://partner.steamgames.com/doc/features/auth
I have an API Gateway with these two endpoints:
/login
// the steamAuth file is the same module as node-steam-openid but for ts
import { SteamAuth } from "../utils/steamAuth";
export const login = async () => {
const client = new SteamAuth(
'http://localhost:3000',
`${process.env.API_URL}/consume`,
process.env.STEAM_API_KEY,
);
try {
const redirectUrl = await client.getRedirectUrl();
return {
statusCode: 302,
headers: { Location: redirectUrl }
};
} catch (e) {
console.log(e);
return {
statusCode: 500,
message: 'Internal server error'
};
}
}
/consume
import { APIGatewayEvent } from 'aws-lambda';
import { SteamAuth } from "../utils/steamAuth";
export const consume = async (event: APIGatewayEvent) => {
const client = new SteamAuth(
'http://localhost:3000',
`${process.env.API_URL}/consume`,
process.env.STEAM_API_KEY,
);
console.log(event);
try {
const user = await client.authenticate(event);
console.log('success', user);
} catch (e) {
console.log('error', e);
}
return {
statusCode: 302,
headers: { Location: 'http://localhost:3000/' },
};
}
The thing is I get this error in /consume endpoint
error TypeError: Cannot read property 'toUpperCase' of undefined
at Object.openid.verifyAssertion (/var/task/node_modules/openid/openid.js:905:28)
at openid.RelyingParty.verifyAssertion (/var/task/node_modules/openid/openid.js:68:10)
at /var/task/src/utils/steamAuth.js:60:31
at new Promise (<anonymous>)
at SteamAuth.authenticate (/var/task/src/utils/steamAuth.js:59:16)
at Runtime.consume [as handler] (/var/task/src/lambda/consume.js:9:35)
at Runtime.handleOnceNonStreaming (/var/runtime/Runtime.js:73:25)
I believe this error occurs because the verifyAssertion is waiting for an express request while it is provided an API Gateway one.
Link to the code with the mentioned function is here
Should I use another module to do the authentication as I don't really want to modify the source code of the module? I didn't find anything at the moment
Thanks!
I found a workaround using express in lambda. As expected, the openid module used by node-steam-openid is expecting an express request and not a lambda event.
import { SteamAuth } from "../utils/steamAuth";
const express = require('express');
const serverless = require('serverless-http');
const app = express();
app.get('/verify', async (req: any, res: any) => {
const client = new SteamAuth(
process.env.HOSTNAME,
`${process.env.API_URL}/verify`,
process.env.STEAM_API_KEY,
);
try {
const user: any = await client.authenticate(req);
} catch (e) {
throw new Error(e.message);
}
});
module.exports.verify = serverless(app);

pass httponly cookies through apollo graphlql server

GOAL: have my api gateway get the httponly cookies being returned from my rest endpoints and pass it along to frontend, also the front end should be able to pass the cookies through.
httpO=httponly
SPA(react) apiGateway(apolloQL) restEndpoint
httpO-cookies----> <-----(httpO)cookies-----> <-----(httpO)cookies
current the resolvers I have are able to see the "set-cookies" in the response from the endpoints but throughout the response lifecycle the header's are lost.
const apolloServer: ApolloServer = new ApolloServer({
context: ({ res }) => {
// console.log(res,"res");
return ({
res
});
},
formatError,
resolvers,
typeDefs,
formatResponse: (response: GraphQLResponse) => {
console.log(response.http?.headers, "header?");
return {
headers: {
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Allow-Credentials': 'true',
},
data: response.data,
errors: response.errors,
};
}
});
ex. of resolver:
const signupUser = async (parent: any, args: any, context: any, info: any) => {
try {
const response = await UserService.createUser(args);
console.log(response , "response");
return response;
} catch (error) {
console.log(error
}
};
in this example lets assume the UserService.createUser return the entire response Object, not response.data.
const signupUser = async (parent: any, args: any, context: any, info: any) => {
try {
//call get the Express response from the context
const contextResponse = context.res
//or
const {res} = context
//then set the cookies to the response
const response = await UserService.createUser(args);
contextResponse.header('set-cookie', response?.headers['set-cookie']);
//return the original data
return response?.data;
} catch (error) {
console.log(error
}
};

TestCafe Triggering Test By POST Request In Express

I had a question that doesn't seem to be answered anywhere.
I am running tests from within my Express.js api. I set up a page that has a button and a field to enter a keyword intended to be used during a testcafe test. My endpoint I set up is /testcafe. But after sending a post request to /testcafe, there is a long delay while test runs and so my question is what is the best next step besides hanging?
Also, can my post request body, which contains the keyword, be directly used in a test like this? Keep in mind it's this pattern:
frontend -> POST request -> Express server -> /testcafe endpoint - test
My problem is after it reaches test, I currently have it attempting to call fetch from within the request logger. Is this right?
import { ClientFunction, Selector } from 'testcafe';
import { RequestLogger, RequestHook } from 'testcafe';
import zlib from 'zlib';
import fetch from 'isomorphic-unfetch';
const url = 'https://www.mysitetesturl.com/page';
class MyRequestHook extends RequestHook {
constructor (requestFilterRules, responseEventConfigureOpts) {
super(requestFilterRules, responseEventConfigureOpts);
}
onRequest (e) {
console.log('in onRequest!')
console.log('========================')
console.log('Request Body')
let buf = e._requestContext.reqBody
console.log(buf.toLocaleString())
}
onResponse (e) {
let buf = Buffer(e.body)
let unzippedBody = Buffer(zlib.gunzipSync(buf))
let payload = unzippedBody.toLocaleString()
fetch('http://myapiipaddress/api/testcafe',
method: 'PUT',
body: JSON.stringify(payload)
)
.then((err, doc) => {
if(err) {
console.log(err)
} else {
console.log(doc)
}
})
}
}
const myRequestHook = new MyRequestHook({
url: url,
method:'get'},
{
includeHeaders: true,
includeBody: true
}
);
fetch('http://myapiipaddress/api/testcafe',
method: 'GET'
)
.then((err, doc) => {
if(err) {
console.log(err)
} else {
fixture`myfixture`
.page(doc.url)
.requestHooks(myRequestHook);
test(`mytest`, async t => {
const inputField = Selector('input');
await t
await t
.wait(5000)
.typeText(inputField, doc.text)
.wait(5000)
}
);
}
})
According to your scheme, you need to organize your code in a different way:
const createTestCafe = require('testcafe');
....
// Choose the necessary body parser for express application
// https://github.com/expressjs/body-parser
app.use(bodyParser.urlencoded({ extended: true }));
...
app.post('/', function (req, res) {
createTestCafe('localhost', 1337, 1338, void 0, true)
.then(testcafe => {
const runner = testcafe.createRunner();
return runner
.src('/tests')
.browsers('chrome')
.run();
})
.then(failedCount => {
testcafe.close();
res.end();
});
})

Using loopback4 and graphQL together

import {Lb4Application} from './application';
import {ApplicationConfig} from '#loopback/core';
import graphqlHTTP from 'express-graphql';
import {createGraphQLSchema} from 'openapi-to-graphql';
import {Oas3} from 'openapi-to-graphql/lib/types/oas3';
export {Lb4Application};
export async function main(options: ApplicationConfig = {}) {
const app = new Lb4Application(options);
await app.boot();
await app.start();
const url: string = <string>app.restServer.url;
console.log(`REST API Server is running at ${url}`);
const graphqlPath = '/graphql';
const oas: Oas3 = <Oas3>(<unknown>app.restServer.getApiSpec());
const {schema} = await createGraphQLSchema(oas, {
strict: false,
viewer: false,
baseUrl: url,
headers: {
'X-Origin': 'GraphQL',
},
tokenJSONpath: '$.jwt',
});
const handler: graphqlHTTP.Middleware = graphqlHTTP(
(request, response, graphQLParams) => ({
schema,
pretty: true,
graphiql: true,
context: {jwt: getJwt(request)},
}),
);
// Get the jwt from the Authorization header and place in context.jwt, which is then referenced in tokenJSONpath
function getJwt(req: any) {
if (req.headers && req.headers.authorization) {
return req.headers.authorization.replace(/^Bearer /, '');
}
}
app.mountExpressRouter(graphqlPath, handler);
console.log(`Graphql API: ${url}${graphqlPath}`);
return app;
}
I have taken this code from this github issue, and I still cannot seem to get it to run.
The error is get is
Error: Invalid specification provided
When i just use an express server, and run npx openapi-to-graphql --port=3001 http://localhost:3000/openapi.json --fillEmptyResponses The graphql is served correctly.
I need to get the example code running, as it seems to be the only way to pass JWT token headers correctly when using loopback4 and graphql together
This is how i solved it for anyone that needs help:
/* eslint-disable #typescript-eslint/no-explicit-any */
import {Lb4GraphqlPocApplication} from './application';
import {ApplicationConfig} from '#loopback/core';
const graphqlHTTP = require('express-graphql');
const {createGraphQLSchema} = require('openapi-to-graphql');
const fetch = require('node-fetch');
export {Lb4GraphqlPocApplication};
export async function main(options: ApplicationConfig = {}) {
console.log('hello world!')
const app = new Lb4GraphqlPocApplication(options);
await app.boot();
await app.start();
const url = app.restServer.url;
const graphqlPath = '/graphql';
console.log(`REST Server is running at ${url}`);
console.log(`Try ${url}/ping`);
// replace with process.env.{active-environment} once deployments setup
const openApiSchema = 'http://localhost:3000/openapi.json';
const oas = await fetch(openApiSchema)
.then((res: any) => {
console.log(`JSON schema loaded successfully from ${openApiSchema}`);
return res.json();
})
.catch((err: any) => {
console.error('ERROR: ', err);
throw err;
});
const {schema} = await createGraphQLSchema(oas, {
strict: false,
viewer: true,
baseUrl: url,
headers: {
'X-Origin': 'GraphQL',
},
tokenJSONpath: '$.jwt',
});
const handler = graphqlHTTP(
(request: any, response: any, graphQLParams: any) => ({
schema,
pretty: true,
graphiql: true,
context: {jwt: getJwt(request)},
}),
);
// Get the jwt from the Authorization header and place in context.jwt, which is then referenced in tokenJSONpath
function getJwt(req: any) {
if (req.headers && req.headers.authorization) {
return req.headers.authorization.replace(/^Bearer /, '');
}
}
app.mountExpressRouter(graphqlPath, handler);
console.log(`Graphql API: ${url}${graphqlPath}`);
return app;
}

Resources