I am migrating a project from Webpack to Vite and have run into an issue with proxying requests to one of the endpoints in the MVC.Net backend.
Due to circumstances of the existing project, I need to handle certain calls manually - such as on initial page load of login page, check whether user is already authenticated and redirect to the main page.
I am trying to figure out how to use server.proxy.configure to handle these requests. I am managing fine with the GET requests, but I cannot seem to receive the POST request's body data.
Here is what I have at the moment:
server: {
proxy: {
"/api": {
target: "https://my.local.environment/",
changeOrigin: true,
configure: (proxy: HttpProxy.Server, options: ProxyOptions) => {
proxy.on("proxyReq", (proxyReq, req, res, options) => {
if (req.method === "GET") {
//handle simple get requests. no problems here
//...
} else {
const buffer = [];
console.log("received post request");
proxyReq.on("data", (chunk) => {
console.log("received chunk");
buffer.push(chunk);
});
proxyReq.on("end", () => {
console.log("post request completed");
const body = Buffer.concat(buffer).toString();
const forwardReq = http.request(
{
host: "https://my.local.environment",
port: 443,
method: "POST",
path: req.url,
headers: {
"Content-Type": "application/json",
"Content-Length": data.length,
},
},
(result) => {
result.on("data", (d) => {
res.write(d);
res.end();
});
}
);
forwardReq.on("error", (error) => {
console.log(error);
});
forwardReq.write(data);
forwardReq.end();
});
}
});
},
secure: false,
},
}
}
The problem is that neither proxyReq.on("data", (chunk) => { nor proxyReq.on("end", (chunk) => { ever actually trigger.
Additionally, req.body is undefined.
I have absolutely no idea where I am supposed to be getting the POST request's body.
I ended up finding a different question about the bypass option and this gave me the solution I was looking for. Ended up only handling the specific GET requests that I need to handle locally instead of forwarding to my deployed environment, and everything else gets handled automatically by vite.
"/api": {
target: "https://my.local.environment/",
changeOrigin: true,
agent: new https.Agent({
keepAlive: true,
}),
bypass(req, res, proxyOptions) {
if (req.method === "GET") {
//... here I get what I need and write to the res object
// and of course call res.end()
}
//all other calls are handled automatically
},
secure: false,
},
Related
I am facing a timeout issue with nestJs Httpservice.
The error number is -60 and error code is 'ETIMEDOUT'.
I am basically trying to call one api after the previous one is successfully.
Here is the first api
getUaaToken(): Observable<any> {
//uaaUrlForClient is defined
return this.httpService
.post(
uaaUrlForClient,
{ withCredentials: true },
{
auth: {
username: this.configService.get('AUTH_USERNAME'),
password: this.configService.get('AUTH_PASSWORD'),
},
},
)
.pipe(
map((axiosResponse: AxiosResponse) => {
console.log(axiosResponse);
return this.getJwtToken(axiosResponse.data.access_token).subscribe();
}),
catchError((err) => {
throw new UnauthorizedException('failed to login to uaa');
}),
);
}
Here is the second api
getJwtToken(uaaToken: string): Observable<any> {
console.log('inside jwt method', uaaToken);
const jwtSignInUrl = `${awsBaseUrl}/api/v1/auth`;
return this.httpService
.post(
jwtSignInUrl,
{ token: uaaToken },
{
headers: {
'Access-Control-Allow-Origin': '*',
'Content-type': 'Application/json',
},
},
)
.pipe(
map((axiosResponse: AxiosResponse) => {
console.log('SUCUSUCSCUSS', axiosResponse);
return axiosResponse.data;
}),
catchError((err) => {
console.log('ERRRORRRORROR', err);
// return err;
throw new UnauthorizedException('failed to login for');
}),
);
}
Both files are in the same service file. Strangely, when i call the second api through the controller like below. It works fine
#Post('/signin')
#Grafana('Get JWT', '[POST] /v1/api/auth')
signin(#Body() tokenBody: { token: string }) {
return this.authService.getJwtToken(tokenBody.token);
}
When the two api's are called, however, the first one works, the second one that is chained is giving me the timeout issue.
Any ideas?
Two things that made it work: changed the http proxy settings and used switchMap.
I'm implementing graphql login mutation to authenticate user login credential. Mutation verifies the password with bcrypt then sends a cookie to the client, which will render user profile based on whether the cookie is a buyer or owner user).
GraphQL Login Mutation Code:
const Mutation = new GraphQLObjectType({
name: 'Mutation',
fields: {
loginUser: {
type: UserType,
args: {
email: { type: GraphQLString },
password: { type: GraphQLString }
},
resolve: function (parent, args, { req, res }) {
User.findOne({ email: args.email }, (err, user) => {
if (user) {
bcrypt.compare(args.password, user.password).then(isMatch => {
if (isMatch) {
if (!user.owner) {
res.cookie('cookie', "buyer", { maxAge: 900000, httpOnly: false, path: '/' });
} else {
res.cookie('cookie', "owner", { maxAge: 900000, httpOnly: false, path: '/' });
}
return res.status(200).json('Successful login');
} else {
console.log('Incorrect password');
}
});
}
});
}
}
}
});
Server.js:
app.use("/graphql",
(req, res) => {
return graphqlHTTP({
schema,
graphiql: true,
context: { req, res },
})(req, res);
});
Error message:
(node:10630) UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
[0] at ServerResponse.setHeader (_http_outgoing.js:470:11)
[0] at ServerResponse.header (/Users/xxx/xxx/server/node_modules/express/lib/response.js:771:10)
[0] at ServerResponse.append (/Users/xxx/xxx/server/node_modules/express/lib/response.js:732:15)
[0] at ServerResponse.res.cookie (/Users/xxx/xxx/server/node_modules/express/lib/response.js:857:8)
[0] at bcrypt.compare.then.isMatch (/Users/xxx/xxx/server/schema/schema.js:89:41)
I've done some research on this error, but can't seem to find a relevant answer. The issue seems to lie within response body being executing more than once, thus "cannot set headers after they are sent to the client". Since I'm sending both res.cookie() and res.status(200), how could I fix this problem?
express-graphql already sets the status and sends a response for you -- there's no need to call either res.status or res.json inside your resolver.
GraphQL always returns a status of 200, unless the requested query was invalid, in which case it returns a status of 400. If errors occur while executing the request, they will be included the response (in an errors array separate from the returned data) but the status will still be 200. This is all by design -- see additional discussion here.
Instead of calling res.json, your resolver should return a value of the appropriate type (in this particular case UserType), or a Promise that will resolve to this value.
Additionally, you shouldn't utilize callbacks inside resolvers since they are not compatible with Promises. If the bcrypt library you're using supports using Promises, use the appropriate API. If it doesn't, switch to a library that does (like bcryptjs) or wrap your callback inside a Promise. Ditto for whatever ORM you're using.
In the end, your resolver should look something like this:
resolve: function (parent, args, { req, res }) {
const user = await User.findOne({ email: args.email })
if (user) {
const isMatch = await bcrypt.compare(args.password, user.password)
if (isMatch) {
const cookieValue = user.owner ? 'owner' : 'buyer'
res.cookie('cookie', cookieValue, { maxAge: 900000, httpOnly: false, path: '/' })
return user
}
}
// If you want an error returned in the response, just throw it
throw new Error('Invalid credentials')
}
Is there any way to add custom headers in cloudflare?
We have some https ajax to cache static files,
but it's not handling headers like "Access-Control-Allow-Credentials" in response header and cause failure on chrome.
Scott Helme has published a way to do it using new recently released Cloudflare Workers.
https://scotthelme.co.uk/security-headers-cloudflare-worker/
let securityHeaders = {
"Content-Security-Policy": "upgrade-insecure-requests",
"Strict-Transport-Security": "max-age=1000",
"X-Xss-Protection": "1; mode=block",
"X-Frame-Options": "DENY",
"X-Content-Type-Options": "nosniff",
"Referrer-Policy": "strict-origin-when-cross-origin",
}
let sanitiseHeaders = {
"Server": "My New Server Header!!!",
}
let removeHeaders = [
"Public-Key-Pins",
"X-Powered-By",
"X-AspNet-Version",
]
addEventListener('fetch', event => {
event.respondWith(addHeaders(event.request))
})
async function addHeaders(req) {
let response = await fetch(req)
let newHdrs = new Headers(response.headers)
if (newHdrs.has("Content-Type") && !newHdrs.get("Content-Type").includes("text/html")) {
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: newHdrs
})
}
Object.keys(securityHeaders).map(function(name, index) {
newHdrs.set(name, securityHeaders[name]);
})
Object.keys(sanitiseHeaders).map(function(name, index) {
newHdrs.set(name, sanitiseHeaders[name]);
})
removeHeaders.forEach(function(name) {
newHdrs.delete(name)
})
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: newHdrs
})
}
To add custom headers, select Workers in Cloudflare.
To add custom headers such as Access-Control-Allow-Credentials or X-Frame-Options then add the following little script: -
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
let response = await fetch(request)
let newHeaders = new Headers(response.headers)
newHeaders.set("Access-Control-Allow-Credentials", "true")
newHeaders.set("X-Frame-Options", "SAMEORIGIN")
// ... and any more required headers
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: newHeaders
})
}
Once you have created your worker, you need to match it to a route e.g.
If you now test your endpoint using e.g. Chrome Dev tools, you will see the response headers.
cloudflare does not support this possibility
I'm trying to use the webpack-dev-server proxy configuration to send api requests to an external domain and I can't seem to get it working.
Here's my config:
var path = require('path')
module.exports = {
entry: './client/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'public/assets'),
publicPath: 'assets'
},
devServer: {
contentBase: 'public',
proxy:{
'/api/v1*': {
target: 'http://laravelandwebpack.demo/',
secure: false
}
}
}
}
So, anytime my app makes a request with the uri /api/v1... it should send that request to http://laravelandwebpack.demo.
In my Vue app, I'm using the vue-resource to make the requests and I'm defaulting all requests with the needed uri prefix:
var Vue = require('vue')
Vue.use(require('vue-resource'))
new Vue({
el: 'body',
http: {
root: '/api/v1', // prefix all requests with this
headers:{
test: 'testheader'
}
},
ready: function (){
this.$http({
url: 'tasks',
method: 'GET'
}).then(function (response){
console.log(response);
}, function (response){
console.error(response);
})
}
})
The URL's are being constructed correctly, but they're still pointing to localhost:8080 which is the webpack-dev-server:
I read and re-read the docs for webpack-dev-server and I can't figure out where I have it set up wrong. Any ideas?
#Linus Borg is right.
The URL's are being constructed correctly, but they're still pointing to localhost:8080 which is the webpack-dev-server:
This doesn't matter.
In my case, I want to get http://m.kugou.com/?json=true. And I am using #Vue/cli ^3.0.0-beta.15, maybe you need to modify your code according to situation.
So, here is what I did:
App.vue
axios.get('/proxy_api/?json=true').then(data => {
console.log('data', data)
})
vue.config.js
module.exports = {
devServer: {
proxy: {
// proxy all requests whose path starting with /proxy_api to http://m.kugou.com/proxy_api then remove '/proxy_api' string
'/proxy_api': {
target: 'http://m.kugou.com',
pathRewrite: {
'^/proxy_api': '/'
}
}
}
//or just change the origin to http://m.kugou.com
// proxy: 'http://m.kugou.com'
}
}
I use /proxy_api/?json=true then update it to http://m.kugou.com/?json=true by target and pathRewrite.
'/proxy_api' is used to distinguish if the url should be proxied.
Why would I use /proxy_api? Easy to distinguish.
I got the data from http://m.kugou.com/?json=true while the url in the dev-tool is http://localhost:8080/proxy_api/?json=true.
See? that doesn't matter.
I found a workaround solution for that issue. In my case I need to proxy requests to my backend for any /api/* path, so I'm bypassing any requests which does not starts with api.
Sample:
proxy: {
'*': {
target: 'http://localhost:8081',
secure: false,
rewrite: function(req) {
console.log('rewriting');
req.url = req.url.replace(/^\/api/, '');
},
bypass: function(req, res, proxyOptions) {
if (req.url.indexOf('api') !== 0) {
console.log('Skipping proxy for browser request.');
return '/index.html';
}else{
return false;
}
}
}
}
I am building an app in react native which makes fetch calls that rely on the most up to date information from the server. I have noticed that it seems to cache the response and if i run that fetch call again it returns the cached response rather than the new information from my server.
My function is as follows:
goToAll() {
AsyncStorage.getItem('FBId')
.then((value) => {
api.loadCurrentUser(value)
.then((res) => {
api.loadContent(res['RegisteredUser']['id'])
.then((res2) => {
console.log(res2);
this.props.navigator.push({
component: ContentList,
title: 'All',
passProps: {
content: res2,
user: res['RegisteredUser']['id']
}
})
});
});
})
.catch((error) => {console.log(error);})
.done();
}
and the function from api.js im calling is as follows:
loadContent(userid){
let url = `http://####.com/api/loadContent?User_id=${userid}`;
return fetch(url).then((response) => response.json());
}
You can set a Header to prevent the request from being cached.
Example below:
return fetch(url, {
headers: {
'Cache-Control': 'no-cache'
}
}).then(function (res) {
return res.json();
}).catch(function(error) {
console.warn('Request Failed: ', error);
});
Manosim's answer didn't work for me, but put me on the path to a solution that did work:
fetch(url, {
headers: {
'Cache-Control': 'no-cache, no-store, must-revalidate',
'Pragma': 'no-cache',
'Expires': 0
}
})
This nailed it.
I had a similar problem with react native (Android) and fetch using clojurescript (instead of js).
Adding :cache "no-store" (not in the header) stopped the behavior (caching fetch data on Android App).
I think the code in js should be something like:
fetch(url, {'cache':'no-store'})
specs fetch cache-mode