how to prevent from others to fetch my website url - node.js

I have a route in express app like this:
router.get('/search_documents_for_home_page', async (req, res) => {
var responses = [];
await Article.find({}).select('image title body').limit(4).sort({ _id:-1 }).then(articles=>{
responses.push([articles]);
});
await Image.find({}).limit(4).sort({ _id:-1 }).then(images=>{
responses.push([images]);
});
await Video.find({}).limit(4).sort({ _id:-1 }).then(videos=>{
responses.push([videos]);
});
await Project.find({}).limit(4).sort({ _id:-1 }).then(projects=>{
responses.push([projects]);
});
res.json(responses);
});
And when the user goes to the home page, a fetch request is sended:
await fetch('/api/search_documents_for_home_page').then(result=>{
return result.json();
}).then(articles=>{
// show the users all of the documents
});
But I want that only my server can fetch this url.
How do I do that?
Im also using pugjs

You can secure your api by requiring some type of authentication
You can add a check to make sure request is coming from your front end, depending on server this can be handled differently (i.e. window.location.origin)
Enable CORS, only prevents browser>browser calls

Related

How to only allow function to be ran on frontend after backend authenticates?

I have a frontend in react which displays two buttons currently one for authenticating and one for display data once authenticated
frontend:
const auth = async () => {
window.open("callbackApiRouteOnBackend")
}
const displayData = async () => {
// this function displays our data on frontend
}
return (
<>
<button onClick={auth}>Authenticate</button>
<button onClick={displayData}>Display data</button>
</>
)
authentication is done on the backend using oauth2 to get access token and refresh token this all works fine the only issue is right now the button can be clicked on frontend to display the data when the user isn't authenticated but it will not work as the fetch does not work as no access token. I get that I can use conditional rendering to hide the button when the user is authenticated but I cannot seem to figure out the way to determine on the frontend whether or not the user is authenticated
this is my backend to do the authentication in node js and express:
export const callback = async (req, res) => {
res.redirect("placeholder for external login page");
// then redirect to our redirect function to get access token
};
export const redirect = async (req, res) => {
const link = "external api link to get access token"
try {
const response = await axios.post(link);
// This is where it gets access token
// after access token is got how can i set a state to know the user is authenticated
// and then redirect them back to frontend with the frontend also knowing if
the user is authenticated or not
} catch (error) {
console.log(error);
}
res.redirect(307, "http://localhost:3000");
};
what is the best way to go about this because after the user is authenticated from backend I redirect to frontend but there is no way to tell on the backend or the frontend if the user is actually authenticated or not.
You can send the response to the front end:
res.status(200).send({
accessToken: response.access_token //Not sure what the response object looks like
});
From the front end set that token to localStorage or sessionStorage and check if the token is valid.
Read more: https://tasoskakour.com/blog/react-use-oauth2

Maintaining express sessions when moving between static and dynamic pages on client side

I am trying to set up express sessions for user authentication.
I have a node.js backend, and angular and static page front end (two front ends).
My node backend accepts username and password for authentication on my route
as a post request http://localhost:3000/users/login
My journey is as follows:
1. User is presented with a static front end login page, (this is designed by injecting vue.js with axios on a static html page), wherein he requests a log in with his credentials. The client frontend is hosted on http://localhost:3011/
2. The application will now send a post request to http://localhost:3000/users/login to verify the credentials.
3. If the credentials are valid, the server will create a new session (express-session) using req.session. In this session, I also store my token that I use to authenticate the user for any subsequent requests (this token is saved in the db and verified for all further requests). The server will then respond with a 200 OKAY status.
4. Once the vue application gets a positive response, the client will redirect the application to http://localhost:3000/ where I have angular application in the dist folder.
If, in POSTMAN, I do a post to http://localhost:3000/users/login and then do a GET on http://localhost:3000/, I can see the token in the logs when the server responds to the GET request.
But, if i send a post request to http://localhost:3000/users/login from my vue application, and then redirect to http://localhost:3000/ on successful authentication, I cannot see the token in the logs.
Code Snippet on the client side (Vue)
submit() {
this.errMsg = "";
axios
.post("http://localhost:3000/users/login", {
user: this.user,
password: this.password
})
.then(response => {
window.location = "http://localhost:3000/";
})
.catch(err => {
console.log(err);
this.errMsg = err.data.statusText;
});
}
Code inside login on server side
router.route("/login").post((req, res) => {
let { user, password } = req.body;
merchant_table
.findOne({ attributes: ["id"], where: { email, password } })
.then(result => {
if (result) {
let token = 12345;
token_table
.create({ m_id: result.id, token })
.then(result => {
req.session.user = result.token;
console.log("session is", req.session); //THIS SHOWS THE TOKEN
res.status(200).json({status:"OKAY!"})
});
} else {
res.status(401).json({status:"Invalid User!"})
}
})
.catch(error => console.log(error));
});
Code inside the request API for http://localhost:3000/
app.use("/", function(req, res) {
console.log("Session is", req.session); //THIS SHOWS THE TOKEN FOR POSTMAN, NOT WHEN WORKING WITH VUE
res.sendFile(__dirname + "/dist/index.html");
});
Since the axios post request and the subsequent redirect is to http://localhost:3000/ I expected the token to be maintained. But it seem to assume this as a new connection. Is this approach wrong? Is there a way to keep track of the token on redirect? I need the token only once, because then I will store it in the angular application and send it for any other requests that I need.
How do I go about with this?

express js can't redirect user

var express = require('express');
var firebase = require('firebase');
var admin = require('firebase-admin');
var router = express.Router();
router.post('/api/login', function(req, res) {
var email = req.body.email;
var pass = req.body.password;
login(email, pass);
});
function login(email, pass){
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.NONE);
firebase.auth().signInWithEmailAndPassword(email, pass).then(function (user) {
if(user){
console.log('user');
res.redirect('/signup');
};
}).then(() => {
// A page redirect would suffice as the persistence is set to NONE.
return firebase.auth().signOut();
}).then(() => {
window.location.assign('/signup');
}).catch(function (error) {
var errorMessage = error.message;
}); // auth catch End
};// login fun
............................................................................................................................................................
if user not equal to null then i want to redirect user to signup
If the POST request is coming from an Ajax call in a browser web page, then the response to the request goes only to your script that made the Ajax call. The browser does not see it at all, thus the browser does nothing itself with the response to the POST. The response just goes to your script. So, if you want the page to redirect after an Ajax call, then you need to return a response that the script can identify and the script then needs to do a client-side redirect such as:
window.location = "http://someURL";
In cases where your script is looking for a redirect response, you can either have the script look at the status and header to see the redirect or you can, instead just return some JSON that tells the script what to do.
If the POST request comes from an HTML form in the browser (without Javascript involved), then the browser will look at the response from the POST and either display the resulting response (if it's a 2xxx response) or follow the redirect if it's a 3xx response.

Client.calls.create() doesn't start a call, but no error logged in Twilio or Heroku

I'm hosting a Twilio app on Heroku. I want Twilio to start a separate outbound call and put the user into a conference, if the user selects a certain menu option.
Here is my handler.js:
const VoiceResponse = require('twilio').twiml.VoiceResponse;
const client=require('twilio')(
process.env.TWILIO_ACCOUNT_SID,
process.env.TWILIO_AUTH_TOKEN
);
var request = require('request');
exports.menu = function menu(digit,sid) {
var responseTwiml;
switch(digit){
case '1':
console.log("menu: chose 1");
responseTwiml=guestCallsHost(sid);
break;
default:
doSomethingElse();
break;
}
return responseTwiml;
};
function guestCallsHost(sid){
baseUrl='/ivr/callHost';
console.log("guestCallsHost: baseUrl "+baseUrl);
conferenceName=sid;
url='/ivr/callHost?conferenceName='+sid;
var call=client.calls.create({
url:url,
to: process.env.CELL_PHONE_NUMBER,
from: process.env.TWILIO_PHONE_NUMBER,
method: 'GET'
});
const response = new VoiceResponse();
const dial = response.dial();
dial.conference(sid);
responseStr=response.toString();
return responseStr;
};
exports.callHost=function callHost(conferenceName){
const response=new VoiceResponse();
response.say("testing outbound call to host");
return response.toString();
};
And here is the router.js that defines the /ivr/ endpoints:
const Router = require('express').Router;
const {
menu,
callHost,
} = require('./handler');
const router = new Router();
// GET: /ivr/menu
router.get('/menu', (req, res) => {
const digit = req.query.Digits;
const sid=req.query.sid;
console.log("/ivr/menu: digit "+digit);
console.log("/ivr/menu: sid "+sid);
res.send(menu(digit,sid));
});
// GET: /ivr/callHost
router.get('/callHost', (req, res) => {
console.log("reached callHost endpoint");
const conferenceName=req.query.conferenceName;
res.send(callHost(conferenceName));
});
The problem is that the client.calls.create() call is not being made, but no error appears in either the Twilio debugger or the Heroku log. And the /ivr/callHost endpoint is not being visited, despite it being set as the url for client.calls.create(). I know that client has been successfully instantiated as a Twilio object (i think) because when I list all its methods using:
console.log(Object.getOwnPropertyNames(client));
then all the expected Twilio API methods are listed. Or maybe that just means that the object has been created but hasn't been successfully connected to Twilio using the Account SID and Auth Token?
I built this by modifying the app at https://github.com/TwilioDevEd/ivr-phone-tree-node, and I'm able to successfully use client.calls.create() to launch a phone call in another app where I call it in the same index.js file that I'm actually running to start the node app, so maybe in this case the handler.js file isn't actually able to successfully submit the Account SID and Auth Token from where it is in the app structure?
So I'm confused, why can't I make an outbound phone call from inside handler.js? I can transfer inbound calls, so clearly I can modify a live call.
EDIT: I checked whether client is able to make API calls, using:
client
.calls(sid)
.fetch()
.then(call => console.log("call.to "+call.to)).catch(function(error){
console.log("error: "+error.toString());
});
The current inbound call's TO number was successfully retrieved, so apparently client has been instantiated and can connect to Twilio's server.
I solved the problem. It turns out that client.calls.create() requires a full URL with hostname, not just a relative URL, per the documentation for making calls.
I changed:
baseUrl='/ivr/callHost';
to
baseUrl='https://appurl.com/ivr/callHost';
and now the outbound call is successfully generated.

Authentication on Server side routes in Meteor

What is the best way (most secure and easiest) to authenticate a user for a server side route?
Software/Versions
I'm using the latest Iron Router 1.* and Meteor 1.* and to begin, I'm just using accounts-password.
Reference code
I have a simple server side route that renders a pdf to the screen:
both/routes.js
Router.route('/pdf-server', function() {
var filePath = process.env.PWD + "/server/.files/users/test.pdf";
console.log(filePath);
var fs = Npm.require('fs');
var data = fs.readFileSync(filePath);
this.response.write(data);
this.response.end();
}, {where: 'server'});
As an example, I'd like to do something close to what this SO answer suggested:
On the server:
var Secrets = new Meteor.Collection("secrets");
Meteor.methods({
getSecretKey: function () {
if (!this.userId)
// check if the user has privileges
throw Meteor.Error(403);
return Secrets.insert({_id: Random.id(), user: this.userId});
},
});
And then in client code:
testController.events({
'click button[name=get-pdf]': function () {
Meteor.call("getSecretKey", function (error, response) {
if (error) throw error;
if (response)
Router.go('/pdf-server');
});
}
});
But even if I somehow got this method working, I'd still be vulnerable to users just putting in a URL like '/pdf-server' unless the route itself somehow checked the Secrets collection right?
In the Route, I could get the request, and somehow get the header information?
Router.route('/pdf-server', function() {
var req = this.request;
var res = this.response;
}, {where: 'server'});
And from the client pass a token over the HTTP header, and then in the route check if the token is good from the Collection?
In addition to using url tokens as the other answer you could also use cookies:
Add in some packages that allow you to set cookies and read them server side:
meteor add mrt:cookies thepumpinglemma:cookies
Then you could have something that syncs the cookies up with your login status
Client Side
Tracker.autorun(function() {
//Update the cookie whenever they log in or out
Cookie.set("meteor_user_id", Meteor.userId());
Cookie.set("meteor_token", localStorage.getItem("Meteor.loginToken"));
});
Server Side
On the server side you just need to check this cookie is valid (with iron router)
Router.route('/somepath/:fileid', function() {
//Check the values in the cookies
var cookies = new Cookies( this.request ),
userId = cookies.get("meteor_user_id") || "",
token = cookies.get("meteor_token") || "";
//Check a valid user with this token exists
var user = Meteor.users.findOne({
_id: userId,
'services.resume.loginTokens.hashedToken' : Accounts._hashLoginToken(token)
});
//If they're not logged in tell them
if(!user) return this.response.end("Not allowed");
//Theyre logged in!
this.response.end("You're logged in!");
}, {where:'server'});
I think I have a secure and easy solution for doing this from within IronRouter.route(). The request must be made with a valid user ID and auth token in the header. I call this function from within Router.route(), which then gives me access to this.user, or responds with a 401 if the authentication fails:
// Verify the request is being made by an actively logged in user
// #context: IronRouter.Router.route()
authenticate = ->
// Get the auth info from header
userId = this.request.headers['x-user-id']
loginToken = this.request.headers['x-auth-token']
// Get the user from the database
if userId and loginToken
user = Meteor.users.findOne {'_id': userId, 'services.resume.loginTokens.token': loginToken}
// Return an error if the login token does not match any belonging to the user
if not user
respond.call this, {success: false, message: "You must be logged in to do this."}, 401
// Attach the user to the context so they can be accessed at this.user within route
this.user = user
// Respond to an HTTP request
// #context: IronRouter.Router.route()
respond = (body, statusCode=200, headers) ->
this.response.statusCode statusCode
this.response.setHeader 'Content-Type', 'text/json'
this.response.writeHead statusCode, headers
this.response.write JSON.stringify(body)
this.response.end()
And something like this from the client:
Meteor.startup ->
HTTP.get "http://yoursite.com/pdf-server",
headers:
'X-Auth-Token': Accounts._storedLoginToken()
'X-User-Id': Meteor.userId()
(error, result) -> // This callback triggered once http response received
console.log result
This code was heavily inspired by RestStop and RestStop2. It's part of a meteor package for writing REST APIs in Meteor 0.9.0+ (built on top of Iron Router). You can check out the complete source code here:
https://github.com/krose72205/meteor-restivus
Because server-side routes act as simple REST endpoints, they don't have access to user authentication data (e.g. they can't call Meteor.user()). Therefore you need to devise an alternative authentication scheme. The most straightforward way to accomplish this is with some form of key exchange as discussed here and here.
Example implementation:
server/app.js
// whenever the user logs in, update her apiKey
Accounts.onLogin(function(info) {
// generate a new apiKey
var apiKey = Random.id();
// add the apiKey to the user's document
Meteor.users.update(info.user._id, {$set: {apiKey: apiKey}});
});
// auto-publish the current user's apiKey
Meteor.publish(null, function() {
return Meteor.users.find(this.userId, {fields: {apiKey: 1}});
});
lib/routes.js
// example route using the apiKey
Router.route('/secret/:apiKey', {name: 'secret', where: 'server'})
.get(function() {
// fetch the user with this key
// note you may want to add an index on apiKey so this is fast
var user = Meteor.users.findOne({apiKey: this.params.apiKey});
if (user) {
// we have authenticated the user - do something useful here
this.response.statusCode = 200;
return this.response.end('ok');
} else {
// the key is invalid or not provided so return an error
this.response.statusCode = 403;
return this.response.end('not allowed');
}
});
client/app.html
<template name="myTemplate">
{{#with currentUser}}
secret
{{/with}}
</template>
Notes
Make /secret only accessible via HTTPS.
While it's very likely that the user requesting /secret is currently connected, there is no guarantee that she is. The user could have logged in, copied her key, closed the tab, and initiated the request sometime later.
This is a simple means of user authentication. I would explore more sophisticated mechanisms (see the links above) if the server-route reveals high-value data (SSNs, credit cards, etc.).
See this question for more details on sending static content from the server.
I truly believe using HTTP headers are the best solution to this problem because they're simple and don't require messing about with cookies or developing a new authentication scheme.
I loved #kahmali's answer, so I wrote it to work with WebApp and a simple XMLHttpRequest. This has been tested on Meteor 1.6.
Client
import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';
// Skipping ahead to the upload logic
const xhr = new XMLHttpRequest();
const form = new FormData();
// Add files
files.forEach((file) => {
form.append(file.name,
// So BusBoy sees as file instead of field, use Blob
new Blob([file.data], { type: 'text/plain' })); // w/e your mime type is
});
// XHR progress, load, error, and readystatechange event listeners here
// Open Connection
xhr.open('POST', '/path/to/upload', true);
// Meteor authentication details (must happen *after* xhr.open)
xhr.setRequestHeader('X-Auth-Token', Accounts._storedLoginToken());
xhr.setRequestHeader('X-User-Id', Meteor.userId());
// Send
xhr.send(form);
Server
import { Meteor } from 'meteor/meteor';
import { WebApp } from 'meteor/webapp';
import { Roles } from 'meteor/alanning:roles'; // optional
const BusBoy = require('connect-busboy');
const crypto = require('crypto'); // built-in Node library
WebApp.connectHandlers
.use(BusBoy())
.use('/path/to/upload', (req, res) => {
const user = req.headers['x-user-id'];
// We have to get a base64 digest of the sha256 hashed login token
// I'm not sure when Meteor changed to hashed tokens, but this is
// one of the major differences from #kahmali's answer
const hash = crypto.createHash('sha256');
hash.update(req.headers['x-auth-token']);
// Authentication (is user logged-in)
if (!Meteor.users.findOne({
_id: user,
'services.resume.loginTokens.hashedToken': hash.digest('base64'),
})) {
// User not logged in; 401 Unauthorized
res.writeHead(401);
res.end();
return;
}
// Authorization
if (!Roles.userIsInRole(user, 'whatever')) {
// User is not authorized; 403 Forbidden
res.writeHead(403);
res.end();
return;
}
if (req.busboy) {
// Handle file upload
res.writeHead(201); // eventually
res.end();
} else {
// Something went wrong
res.writeHead(500); // server error
res.end();
}
});
I hope this helps someone!
Since Meteor doesn't use session cookies, client must explicitly include some sort of user identification when making a HTTP request to a server route.
The easiest way to do it is to pass userId in the query string of the URL. Obviously, you also need to add a security token that will prove that the user is really who the claim they are. Obtaining this token can be done via a Meteor method.
Meteor by itself doesn't provide such mechanism, so you need some custom implementation. I wrote a Meteor package called mhagmajer:server-route which was thoroughly tested. You can learn more about it here: https://blog.hagmajer.com/server-side-routing-with-authentication-in-meteor-6625ed832a94

Resources