Implement security logic to limit access to data based on configuration - javascript

I'm trying to implement logic to restrict download access:
const userPermittedFutures = {};
app.get('/futures', checkAuthenticated, async (req, res) => {
const query = {
name: 'fetch-futures-portfolios',
text: 'SELECT ' +
'future.instrument.symbol ' +
.......
'WHERE future.exchange.name = $1',
values: ['Futures'],
};
const futuresPortfolios = await pgClient.query(query).catch((err) => {
console.log("Failed to select futures portfolios: " + err.message);
res.status(503).send({
status: 'fail',
message: err.message
});
return;
});
let allowedFuturesAssetsForUser = futuresPortfolios.rows;
let currentUser = initializePassport.getCurrentUser();
let traderId = traderIdMnemonicMapping[currentUser]
if (!getPermittedFuturesAssets[traderId]) {
console.log("Couldn't find Futures contracts for " + currentUser)
return;
} else {
console.log("Specific Futures contracts are set for user " + currentUser);
allowedFuturesAssetsForUser = Object.keys(getPermittedFuturesAssets[traderId]);
}
res.status(200).send(allowedFuturesAssetsForUser);
});
const getPermittedFuturesAssets = (traderId) => {
if (!pgClient) {
setTimeout(function () {
getPermittedFuturesAssets()
}, 1000);
return;
}
const query = {
name: 'fetch-futures-assets-permissions',
text: 'SELECT trader_id, pair, pair_limit ' +
'FROM common.accounts_pairs_permits app ' +
'WHERE exists(SELECT * FROM future.instrument ap WHERE ap.symbol = app.pair) AND trader_id = $1',
values: [traderId],
};
pgClient.query(query, (err, res) => {
if (err) {
console.error("Can't get account permitted futures permits. Retrying in a second...");
setTimeout(function () {
getPermittedFuturesAssets();
}, 1000);
return;
}
for (let row of res.rows) {
addUserFuturesConstraints(row.trader_id, row.pair, row.pair_limit);
}
});
}
function addUserFuturesConstraints(trading_id, futuresAsset, limit) {
if (!userPermittedFutures[trading_id]) {
let user_permitted_limits = {}
user_permitted_limits[futuresAsset] = createUserFuturesAssetsConstraints(limit);
userPermittedFutures[trading_id] = user_permitted_limits;
} else {
userPermittedFutures[trading_id][futuresAsset] = createUserFuturesAssetsConstraints(limit);
}
}
function isUserAbleToTradeFutures(req, res, next) {
if (adminUsers[req.user.username]) { // admin users can trade with all pairs without limits
return next();
}
if (!isFuturesAssetAllowedForUser(req.user.username, req.body.selectedPair)) {
return res.status(503).send({status: "Failed to execute because Futures Asset is not allowed for user."});
}
if (isUserFuturesLimitReached(req.user.username, req.body.amount, req.body.selectedPair)) {
return res.status(503).send({status: "Failed to execute because the order exceeds the user's trading limit for Futures Asset."});
}
return next();
}
function isFuturesAssetAllowedForUser(username, selectedFutureAsset) {
let traderId = traderIdMnemonicMapping[username];
if (!userPermittedFutures[traderId]) {
return false;
}
for (let futureAsset of Object.entries(userPermittedFutures[traderId])) {
if (futureAsset[0] === selectedFutureAsset) {
return true;
}
}
return false;
}
function isUserFuturesLimitReached(username, amount, selectedFuturesAsset) {
let traderId = traderIdMnemonicMapping[username]
if (!userPermittedFutures[traderId][selectedFuturesAsset]) { // if limit is undefined then it is unlimited.
return false;
}
return parseFloat(amount) > userPermittedFutures[traderId][selectedFuturesAsset].pairQuantityLimit;
}
function createUserFuturesAssetsConstraints(pairQuantityLimit) {
return {
pairQuantityLimit: parseFloat(pairQuantityLimit),
splitConstraints: [],
}
}
But I'm stuck how to implement createUserFuturesAssetsConstraints.
When user tries to access endpoint and make request see should be limited to an asset for example asset1 based on the username.
How I can implement the logic so that user can can access only a defined list of items based on his username?

I would implore you to isolate logic in clear containers; for instance by creating repository modules or classes that are responsible for fetching data, service modules for calling the repository and executing business rules related to the authorization etc.
The endpoint /futures appears to imply that you want to return a list of futures (which I assume refers to a list of financial products). The middleware in your source code is defined as checkAuthenticated; however, authentication and authorization are two different things. Whereas authentication refers to the verification of whether a person is who he says he is, while authorization refers to the verification of necessary user rights to access the resource.
Therefore I would suggest using different layers (read: chained middleware functions); i.e. separate authentication and authorization as separate layers, returning an adequate http status response code for each (isAuthenticated: 401 authentication, isAuthorized: 403 authorization for error codes).
Authorizations are usually stored in the database according to an agreed upon RBAC (role based access control). This allows roles to be assigned according to domain specific parameters, and can later on be managed dynamically by admins.
The middleware you apply for verifying authorizations is therefore simple and based on the role the user was assigned.
In your source code you hint to certain constraints, which might imply you want to create a constraints table and link those constraints to a role.
Without understanding the data model you're working with, it is difficult to assess, but with the information provided you should be able to come up with a model that works for you.
If there is any misunderstanding on the question, don't hesitate to comment.

Related

Auth Privledge FQL query faunaDB create permission denied with udr for collection

I have the following user defined role in security with a predicate function on the create for a collection called formEntryData. Now I can create if I don't have the function which is below.
Under the Create function option
Lambda("values", Equals(Identity(), Select(["data"], Var("values"))))
Now I am creating a request with the following code, which works when the create is just set to all via checking the box, but if I use the function above it fails with permission denied. I must be doing somethign wrong
import { query as q } from "faunadb";
import { serverClient } from "../../utils/fauna-auth";
import { authClient } from "../../utils/fauna-auth";
import { getAuthCookie } from "../../utils/auth-cookies";
export default async (req, res) => {
// const { firstName, lastName, telephone, creditCardNumber } = req.body;
const token = getAuthCookie(req);
console.log(token);
const data = req.body.data;
var element = req.body.data;
element["FormID"] = req.body.id;
try {
await authClient(token).query(
q.Create(q.Collection("FormEntryData"), {
data: element,
})
);
res.status(200).end();
} catch (e) {
res.status(500).json({ error: e.message });
}
};
Any help would be greatly appreciated.
UPDATE: I have also added a index for the collection and given it read permissions in the Role
This was also asked on the Fauna community forums: https://forums.fauna.com/t/roles-membership-auth-token-permissions-denied/1681/4
It looks like two things were needed:
update the create predicate to match the data.user field: Lambda("values", Equals(CurrentIdentity(), Select(["data", "user"], Var("values")))), and
a user field needs to be provided in order to pass the provided predicate.
The answer in the forums used two requests: One to retrieve the calling user document (with CurrentIdentity()), and another to create the FormEntryData document. This can be (should be) done with a single request to limit cost (Every request to Fauna will take at least one Transactional Compute Op), and of course network time for two round trips. Consider the following:
await authClient(token).query(
Let(
{
userRef: q.CurrentIdentity(),
},
q.Create(q.Collection("FormEntryData"), {
data: {
...element,
user: q.Var("userRef")
}
})
)
);

Cloud Function Cannot Read Property of Undefined

New to Cloud Functions and trying to understand my error here from the log. It says cannot read property 'uid' of undefined. I am trying to match users together. onCreate will call matching function to check if a user exists under live-Channels and if so will set channel value under both users in live-Users to uid+uid2. Does the log also say which line the error is from? Confused where it shows that.
const functions = require('firebase-functions');
//every time user added to liveLooking node
exports.command = functions.database
.ref('/liveLooking/{uid}')
.onCreate(event => {
const uid = event.params.uid
console.log(`${uid} this is the uid`)
const root = event.data.adminRef.root
//match with another user
let pr_cmd = match(root, uid)
const pr_remove = event.data.adminRef.remove()
return Promise.all([pr_cmd, pr_remove])
})
function match(root, uid) {
let m1uid, m2uid
return root.child('liveChannels').transaction((data) => {
//if no existing channels then add user to liveChannels
if (data === null) {
console.log(`${uid} waiting for match`)
return { uid: uid }
}
else {
m1uid = data.uid
m2uid = uid
if (m1uid === m2uid) {
console.log(`$m1uid} tried to match with self!`)
return
}
//match user with liveChannel user
else {
console.log(`matched ${m1uid} with ${m2uid}`)
return {}
}
}
},
(error, committed, snapshot) => {
if (error) {
throw error
}
else {
return {
committed: committed,
snapshot: snapshot
}
}
},
false)
.then(result => {
// Add channels for each user matched
const channel_id = m1uid+m2uid
console.log(`starting channel ${channel_id} with m1uid: ${m1uid}, m2uid: ${m2uid}`)
const m_state1 = root.child(`liveUsers/${m1uid}`).set({
channel: channel_id
})
const m_state2 = root.child(`liveUsers/${m2uid}`).set({
channel: channel_id
})
return Promise.all([m_state1, m_state2])
})
}
You are referring to a very old version of the Cloud Functions API. Whatever site or tutorial you might be looking it, it's showing examples that are no longer relevant.
In modern Cloud Functions for Firebase, Realtime Database onCreate triggers receive two parameters, a DataSnapshot, and a Context. It no longer receives an "event" as the only parameter. You're going to have to port the code you're using now to the new way of doing things. I strongly suggest reviewing the product documentation for modern examples.
If you want to get the wildcard parameters as you are trying with the code const uid = event.params.uid, you will have to use the second context parameter as illustrated in the docs. To access the data from snapshot, use the first parameter.

Add custom field to billing agreement using paypal node sdk

Im attempting to add a custom field to a billing agreement so I know what user is starting or stopping their subscription when I recieve he IPN POST call.
const billingAgreementAttributes = {
"name": "TEST Web App",
"description": "TEST WEB APP.",
"start_date": null,
"plan": {
"id": ""
},
"custom": "string"
"payer": {
"payment_method": "paypal",
},
};
api.post('/create', authCheck, (req, res) => {
const body = req.body
const type = body.type // default basic
const user = body.user
let isoDate = new Date();
isoDate.setSeconds(isoDate.getSeconds() + 120);
isoDate.toISOString().slice(0, 19) + 'Z';
let agreement = billingAgreementAttributes
agreement.plan.id = type ? basic : unlimited
agreement.start_date = isoDate
// Use activated billing plan to create agreement
paypal.billingAgreement.create(agreement, function (error, billingAgreement) {
if (error) {
console.error(JSON.stringify(error));
res.json(error)
} else {
console.log("Create Billing Agreement Response");
//console.log(billingAgreement);
for (var index = 0; index < billingAgreement.links.length; index++) {
if (billingAgreement.links[index].rel === 'approval_url') {
var approval_url = billingAgreement.links[index].href;
console.log("For approving subscription via Paypal, first redirect user to");
console.log(approval_url);
res.json({ approval_url })
// See billing_agreements/execute.js to see example for executing agreement
// after you have payment token
}
}
}
});
})
When I add to the billing agreement I get malformed json error.
api.post('/execute', authCheck, (req, res) => {
const token = req.body
console.log(token)
paypal.billingAgreement.execute(token.token, {"custom": "foobar"}, function (error, billingAgreement) {
if (error) {
console.error(JSON.stringify(error));
res.json(error)
} else {
res.json(billingAgreement)
console.log(JSON.stringify(billingAgreement));
console.log('Billing Agreement Created Successfully');
}
});
})
If I try to add it in the data parameter when executing the agreement it is never returned anywhere. So currently if a user were to start or stop a subscription I wont know what user did it.
I am not sure what version of the Subscriptions API and Billing Agreements you are using, but the SDKs do not support the latest version, and a custom parameter is not supported.
You must associate Subscription/billing agreement IDs with a user at creation time, when a user creates one via your site/application. This user-associated Subscription/billing agreement id should be persisted in your own database. Then, any later events on that id can be looked up and matched with the user (although really you want to think of users as having active Subscription/BA ids on file as an object)

Only authenticate with Google Auth Provider if user exists

I am using Firebase to authenticate users in our app using GoogleAuthProvider. But I don't want a new user to sign in if they are not already an authenticated user.
If the user exists then sign them in and console.log('user ' + user.email + ' does exist!');.
However, if the user does not exist. Then do not allow authentication and console.log('user ' + user.email + ' does not exist!')
var googleProvider = new firebase.auth.GoogleAuthProvider();
export const doSignInWithGoogle = () => auth.signInWithPopup(googleProvider);
googleLogin = () => {
auth
.doSignInWithGoogle()
.then(result => {
var user = result.user;
const userRef = db.collection('users').doc(user.uid);
userRef.get().then(docSnapshot => {
if (docSnapshot.exists) {
userRef.onSnapshot(() => {
console.log('user ' + user.email + ' does exist!');
});
} else {
console.log('user ' + user.email + ' does not exist!');
}
});
})
.catch(error => {
this.setState(updateByPropertyName('error', error));
});
};
I thought referencing the user records in Firestore would be a simple approach to this. However, perhaps Firebase Auth already have a way to do this. I cannot find documentation or any example.
In the above code, nothing gets logged and the user is either created or logged in.
How can I stop new users from signing up, whilst still allowing current users to sign in?
If you really want to use signInWithPopup method, you have this option,
but it's not the best way. when you are signing in with google, signInWithPopup method returns a promise. you can access the isNewUser property in additionalUserInfo from resulting object. then delete the user you just created.
firebase.auth().signInWithPopup(provider).then(
function (result) {
var token = result.credential.accessToken;
var user = result.user;
//this is what you need
var isNewUser = result.additionalUserInfo.isNewUser;
if (isNewUser) {
//delete the created user
result.user.delete();
} else {
// your sign in flow
console.log('user ' + user.email + ' does exist!');
}
}).catch(function (error) {
// Handle Errors here.
});
This is the easy way but deleting after creating is not the best practice. There is another option,
you can use, signInAndRetrieveDataWithCredential method for this. according to the docs,
auth/user-not-found will be
Thrown if signing in with a credential from
firebase.auth.EmailAuthProvider.credential and there is no user
corresponding to the given email.
function googleSignInWithCredentials(id_token) {
// Build Firebase credential with the Google ID token.
var credential = firebase.auth.GoogleAuthProvider.credential(id_token);
// Sign in with credential from the Google user.
firebase.auth().signInAndRetrieveDataWithCredential(credential)
.then(function (userCredential) {
//sign in
console.log(userCredential.additionalUserInfo.username);
}).catch(function (error) {
// Handle Errors here.
var errorCode = error.code;
if (errorCode === 'auth/user-not-found') {
//handle this
} else {
console.error(error);
}
});
}
here is an example from firebase github repo.
with Firebase security rules, can only check if keys exist - therefore searching in the users table is not an option:
"emails": {
"example1#gmail.com": true,
"example2#gmail.com": true
}
and then one can check with security rules, if the auth.token.email exists as a key:
{
"rules": {
".read": "root.child('emails').child(auth.token.email).exists(),
".write": false,
}
}
in the client, this should throw an "The read failed: Permission denied error" error then, to be handled accordingly. hooking into the Firebase sign-up isn't possible - but while they cannot log-in, this has the same effort (except that on has to clean up the user-database from time to time); eg. with a Cloud Function, which deletes users, which do not have their email as key in the emails "table".
in Firestore security rules, one can check with:
request.auth.token.email & request.auth.token.email_verified
for example, with a collection called emails and a collection called content:
match /databases/{database}/documents {
function userMatchesId(userId) {
return request.auth != null && request.auth.uid == userId
}
function readAllowed(email) {
return if get(/databases/$(database)/documents/emails/$(request.auth.token.email)).data != null
}
match /users/{userId} {
allow get: if userMatchesId(userId)
}
match /content {
allow get: if readAllowed(request.auth.token.email)
}
}
The object you receive from firebase after login has additionalUserInfo where you have the property isNewUser.
You can find the reference here: https://firebase.google.com/docs/reference/js/firebase.auth.html#.AdditionalUserInfo

About how the value is returned using app.set() and app.get()

I am releasing access to pages using connect-roles and loopback but I have a pertinent question about how I can collect the customer's role and through the connect-roles to read the session and respond to a route.
Example, when the client logs in I load a string containing the client's role and access it in a function that controls access to pages.
I have this doubt because I'm finalizing a large scale service that usually there are multiple client sessions that are accessed instantly using a same storage and check function.
It would be efficient to store the customer's role using app.set() and app.get()?
app.get('/session-details', function (req, res) {
var AccessToken = app.models.AccessToken;
AccessToken.findForRequest(req, {}, function (aux, accesstoken) {
// console.log(aux, accesstoken);
if (accesstoken == undefined) {
res.status(401);
res.send({
'Error': 'Unauthorized',
'Message': 'You need to be authenticated to access this endpoint'
});
} else {
var UserModel = app.models.user;
UserModel.findById(accesstoken.userId, function (err, user) {
// console.log(user);
res.status(200);
res.json(user);
// storage employee role
app.set('employeeRole', user.accessLevel);
});
}
});
});
Until that moment everything happens as desired I collect the string loaded with the role of the client and soon after I create a connect-roles function to validate all this.
var dsConfig = require('../datasources.json');
var path = require('path');
module.exports = function (app) {
var User = app.models.user;
var ConnectRoles = require('connect-roles');
const employeeFunction = 'Developer';
var user = new ConnectRoles({
failureHandler: function (req, res, action) {
// optional function to customise code that runs when
// user fails authorisation
var accept = req.headers.accept || '';
res.status(403);
if (~accept.indexOf('ejs')) {
res.send('Access Denied - You don\'t have permission to: ' + action);
} else {
res.render('access-denied', {action: action});
// here
console.log(app.get('employeeRole'));
}
}
});
user.use('authorize access private page', function (req) {
if (employeeFunction === 'Manager') {
return true;
}
});
app.get('/private/page', user.can('authorize access private page'), function (req, res) {
res.render('channel-new');
});
app.use(user.middleware());
};
Look especially at this moment, when I use the
console.log(app.get('employeeRole')); will not I have problems with simultaneous connections?
app.get('/private/page', user.can('authorize access private page'), function (req, res) {
res.render('channel-new');
});
Example client x and y connect at the same time and use the same function to store data about your session?
Being more specific when I print the string in the console.log(app.get('employeeRole')); if correct my doubt, that I have no problem with simultaneous connections I will load a new variable var employeeFunction = app.get('employeeRole'); so yes my function can use the object containing the role of my client in if (employeeFunction === 'Any Role') if the role that is loaded in the string contain the required role the route it frees the page otherwise it uses the callback of failureHandler.
My test environment is limited to this type of test so I hope you help me on this xD
Instead of using app.set you can create a session map(like hashmaps). I have integrated the same in one of my projects and it is working flawlessly. Below is the code for it and how you can access it:
hashmap.js
var hashmapSession = {};
exports.auth = auth = {
set : function(key, value){
hashmapSession[key] = value;
},
get : function(key){
return hashmapSession[key];
},
delete : function(key){
delete hashmapSession[key];
},
all : function(){
return hashmapSession;
}
};
app.js
var hashmap = require('./hashmap');
var testObj = { id : 1, name : "john doe" };
hashmap.auth.set('employeeRole', testObj);
hashmap.auth.get('employeeRole');
hashmap.auth.all();
hashmap.auth.delete('employeeRole');

Categories