How to record user IP address to database? - meteor

I am working on a simple blog application using Meteor. However, users will be allowed to post anonymously. I want to record IP addresses of the senders along with their blog posts.
I could not find any way to access IP address. How can I achieve that?

if (Meteor.isServer) {
var app = __meteor_bootstrap__.app,
Fiber = Npm.require('fibers'),
headers = {};
app.use(function(req, res, next) {Fiber(function () {
headers.remoteAddress = req.connection.remoteAddress;
console.info(req.connection.remoteAddress);
console.info(res.socket._peername);
next();
}).run();
});
}

Related

Next.js - how to pass a visitor ip into the frontend

I have to send a user IP into the logging service on page load. I use static mode in my next.js app.
I have an idea to use an edge function to get visitor IP, pass it as header and then read this value on the frontend. How can I read it? Is there any other reasonable option to pass information like IP or geo into the frontend?
Thanks!
As I know you can get IP by using getServerSideProps but this approach change page from static to server render more info. You use it inside the page file like this:
export async function getServerSideProps(context) {
return {
props: {}, // will be passed to the page component as props
}
}
context in this case contains user ip you can get it like this:
const ip = context.req.headers["x-forwarded-for"] || context.req.socket.remoteAddress;
and than you can pass it to the frontend or send another request with this ip:
export async function getServerSideProps(context) {
const ip = context.req.headers["x-forwarded-for"] || context.req.socket.remoteAddress;
return {
props: { ip }, // will be passed to the page component as props
}
}
and then catch it and use it in your component:
export default function MyPage({ ip }) {...}
Sending ip from getServerSideProps to the server
you can get ip also on you backend if you using Node.js and express inside req:
const myFn = (req, res, next) => {
const ip = req.headers["x-forwarded-for"] || req.socket.remoteAddress;
}
but if you send request from getServerSideProps there will be your Next.js server ip not user ip. So you need manually add header with ip when you sending request from the getServerSideProps for example with axios (it is better to use interceptors for convenience):
axios.post("/some/route", {...},{
headers: { "client-ip": ctx.req.headers["x-forwarded-for"] || ctx.req.socket.remoteAddress },
});
and than on the Node.js backend:
const myFn = (req, res, next) => {
const ip = req.headers["client-ip"] || req.headers["x-forwarded-for"] || req.socket.remoteAddress;
}
and with all this you can get your user ip in all scenarios.
Note
When you working with localhost you will get ::1 as ip but in production it works as expected.

Sharing cookies between sites on the same domain - Headless / Decoupled CMS

The context of my challenge
I'm building a headless WordPress / WooCommerce Store.
If you're not familiar with the concept of a headless CMS, I pull the store's content (Products, and their images, text) over the WordPress / WooCommerce REST API. This way, I have the benefit of a CMS dashboard for my client whilst I get to develop in a modern language / library, in my case - React!
If possible I'd like to keep the checkout in WordPress/WooCommerce/PHP. Depending on the project I apply this code / boilerplate to I suspect that I'll have to chop and change payment gateways, and making this secure and PCI compliant will be much easier in PHP/WordPress - there's a whole host of plugins for this.
This means the entire store / front-end will live in React, with the exception of the cart in which the user will be redirected to the CMS front-end (WordPress, PHP) when they wish to complete their order.
The Challenge
This makes managing cookies for the session rather unintuitive and unorthodox. When the user is redirected from the store (React site) to the checkout (WooCommerce/PHP site) the cart session has to persist between the two sites.
Additionally, requests to WooCommerce are routed through the Node/Express server which my React client sits ons. I do this because I want to keep the WordPress address obscured, and so I can apply GraphQL to clean up my requests & responses. This issue is that in this process, the cookies are lost because my client and my CMS are communicating through a middle man (my Node server) - I require extra logic to manually manage my cookies.
The Code
When I attempt to add something to a cart, from an action creator (I'm using Redux for state management) I hit the api corresponding endpoint on my Node/Express server:
export const addToCart = (productId, quantity) => async (dispatch) => {
dispatch({type: ADD_TO_CART});
try {
// Manually append cookies somewhere here
const payload = await axios.get(`${ROOT_API}/addtocart?productId=${productId}&quantity=${quantity}`, {
withCredentials: true
});
dispatch(addToSuccess(payload));
} catch (error) {
dispatch(addToCartFailure(error));
}
};
Then on the Node/Express server I make my request to WooCommerce:
app.get('/api/addtocart', async (req, res) => {
try {
// Manually retrieve & append cookies somewhere here
const productId = parseInt(req.query.productId);
const quantity = parseInt(req.query.quantity);
const response = await axios.post(`${WP_API}/wc/v2/cart/add`, {
product_id: productId,
quantity
});
return res.json(response.data);
} catch (error) {
// Handle error
return res.json(error);
}
});
With the clues given by #TarunLalwani (thanks a million!) in his comments, I've managed to formulate a solution.
Cookie Domain Setting
Since I was working with two seperate sites, in order for this to work I had to ensure they were both on the same domain, and that the domain was set in all cookies. This ensured cookies were included in my requests between the Node / Express server (sitting on eg. somedomain.com) and the WooCommerce CMS (sitting on eg. wp.somedomain.com), rather than being exclusive to the wp.somedomain subdomain. This was achieved by setting define( 'COOKIE_DOMAIN', 'somedomain.com' ); in my wp-config.php on the CMS.
Manually Getting and Setting Cookies
My code needed significant additional logic in order for cookies to be included whilst requests were routed through my Node / Express server through the client.
In React I had to check if the cookie existed, and if it did I had to send it through in the header of my GET request to the Node / Express server.
import Cookies from 'js-cookie';
export const getSessionData = () => {
// WooCommerce session cookies are appended with a random hash.
// Here I am tracking down the key of the session cookie.
const cookies = Cookies.get();
if (cookies) {
const cookieKeys = Object.keys(cookies);
for (const key of cookieKeys) {
if (key.includes('wp_woocommerce_session_')) {
return `${key}=${Cookies.get(key)};`;
}
}
}
return false;
};
export const addToCart = (productId, quantity) => async (dispatch) => {
dispatch({type: ADD_TO_CART});
const sessionData = getSessionData();
const config = {};
if (sessionData) config['session-data'] = sessionData;
console.log('config', config);
try {
const payload = await axios.get(`${ROOT_API}/addtocart?productId=${productId}&quantity=${quantity}`, {
withCredentials: true,
headers: config
});
dispatch(addToSuccess(payload));
} catch (error) {
dispatch(addToCartFailure(error));
}
};
On the Node / Express Server I had to check if I had included a cookie (saved in req.headers with the key session-data - it was illegal to use Cookie as a key here) from the client, and if I did, append that to the header of my request going to my CMS.
If I didn't find an appended cookie, it meant this was the first request in the session, so I had to manually grab the cookie from the response I got back from the CMS and save it to the client (setCookieFunc).
app.get('/api/addtocart', async (req, res) => {
try {
const productId = parseInt(req.query.productId);
const quantity = parseInt(req.query.quantity);
const sessionData = req.headers['session-data'];
const headers = {};
if (sessionData) headers.Cookie = sessionData;
const response = await axios.post(`${WP_API}/wc/v2/cart/add`, {
product_id: productId,
quantity
}, { headers });
if (!sessionData) {
const cookies = response.headers['set-cookie'];
const setCookieFunc = (cookie) => {
const [cookieKeyValue, ...cookieOptionsArr] = cookie.split('; ');
const cookieKey = cookieKeyValue.split('=')[0];
const cookieValue = decodeURIComponent(cookieKeyValue.split('=')[1]);
const cookieOptions = { };
cookieOptionsArr.forEach(option => (cookieOptions[option.split('=')[0]] = option.split('=')[1]));
if (cookieOptions.expires) {
const expires = new Date(cookieOptions.expires);
cookieOptions.expires = expires;
}
res.cookie(cookieKey, cookieValue, cookieOptions);
};
cookies.map(cookie => setCookieFunc(cookie));
}
return res.json(response.data);
} catch (error) {
// Handle error
return res.json(error);
}
});
I'm not sure if this is the most elegant solution to the problem, but it worked for me.
Notes
I used the js-cookie library for interacting with cookies on my React client.
Gotchas
If you're trying to make this work in your development environment (using localhost) there's some extra work to be done. See Cookies on localhost with explicit domain

How to get client IP address in a Firebase cloud function?

When saving data to Firebase database with a Firebase cloud function, I'd like to also write the IP address where the request comes from.
However, req.connection.remoteAddress always returns ::ffff:0.0.0.0. Is there a way to get the actual IP address of the client that makes the request?
The clients IP is in request.ip.
Example:
export const pay = functions.https.onRequest((request, response) => {
console.log(`My IP is ${request.ip}`);
});
If you are looking for the client ip thru firebase hosting you should use the header fastly-client-ip there will be the real client ip.
The IP address seems to be available in req.headers["x-forwarded-for"].
See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For
Note that if there are proxies in between the interim ip addresses are concatenated towards the end:
X-Forwarded-For: <client_ip>, <proxy_1 : actual-ip-as-seen-by-google> ...
This worked for me:
const express = require('express');
const logIP = async (req : any, res : any, next : any) => {
const clientIP = req.headers['x-forwarded-for'] || req.connection.remoteAddress || req.headers['fastly-client-ip'];
}
const logIPApp = express();
logIPApp.use(logIP);
exports.reportIP = functions.https.onRequest(logIPApp);
If you are looking for ip address or headers in cloud callable function, then you can get information inside context object.
for ex:
exports.testUser = async (data, context) => {
console.log('------------------------::context::------------------');
if(context.rawRequest && context.rawRequest.headers){
console.log(context.rawRequest.headers);
}
}
In the headers you can get ip : header { 'x-appengine-user-ip' : 'xxip' } etc.
This is what worked for me using Firebase Cloud Functions:
const clientIP = req.headers['x-appengine-user-ip'] || req.header['x-forwarded-for']
Note, that this doesn't work locally!

Iron Route - Server Side, access this.params from onBeforeAction()

I need to serve some REST API Endpoints from my meteor application.
Endpoints must be accessible on the server side, so I'm using Iron router for server side routing.
All works great, but now I need access to the this.params for permission checking.
My current route:
Router.route('myServerRoute', {
where: "server",
path: '/api/v1/doit/:partner',
onBeforeAction: function(req, res, next) {
API.beforeAction(req, res, next, ['admin','API']);
}
})
The API.beforeAction is a function I'm using to validate the user token (This token is in one of the headers)
This function check if the token is valid and if that user have one of the roles from the 4th parameter.
The :partner is the name of the partner that use the API.
Let say that :partner is 'store1' (/api/v1/doit/store1)
I want to verify that only users that have the store1 role will be able to access the /api/v1/doit/store1 URL
So I want to pass the value of the :partner parameter to the API.beforeAction function
On the onBeforeAction function, I don't have access to the this.params (it is empty)
Some suggested to access the params using Router.current()
But this is a client call, and it is not available server side.
I can use req.url, parse it and get the partner name. but I don't like to do the parsing myself when I know that Iron Route already parsed this URL
Any suggestions how to get the URL parameters inside the onBeforeAction?
You don't need to do permission checking in your onBeforeAction. I implemented my API with Iron Router.
In the example bellow I handle a get request with an API key and return informations or error code.
Router.route('/api/thing/:apikey', { where: 'server' })
.get(function getThing () {
if (typeof this.params.apikey === 'undefined' || this.params.apikey.length != 16 || !Userprofile.findOne({ apiKey: this.params.apikey })) {
this.response.statusCode = 403;
return this.response.end('Not authorized');
}
const things = Thing.find({ owner: Userprofile.findOne({ apiKey: this.params.apikey }).owner }).fetch();
if (things.length > 0) {
this.response.statusCode = 200;
return this.response.end(JSON.stringify(things));
} else {
this.response.statusCode = 200;
return this.response.end('No things found');
}
});

How to get Meteor.user() values from backend server?

I have two meteor app using database for frontend and backend. Due to some bulk operation. frontend app calls backend server. works fine in many methods. in few method I should check authentication.
frontend
let remote = DDP.connect(<url>);
remote.call('methodName',argument, function(err,res){
});
backend
Meteor.methods({
methodName: function(argument) {
Meteor.user() // null value
}
});
How secure suppose I send userId as parameter?
You have to login in a way or another.
You can do something like this:
var remote = DDP.connect(url);
result = remote.call('login', {
user: user,
password: {digest: SHA256(password), algorithm: 'sha-256' }
});
Sources:
https://forums.meteor.com/t/different-interfaces-based-on-devices/264
You can get user data on server-side by:
var userData = Meteor.users.findOne(Meteor.userId());

Resources