Skip to content

Instantly share code, notes, and snippets.

@katowulf
Last active October 31, 2017 16:41
Show Gist options
  • Save katowulf/248860e7581124b44361 to your computer and use it in GitHub Desktop.
Save katowulf/248860e7581124b44361 to your computer and use it in GitHub Desktop.
Validating client-only auction bids in Firebase using security rules and transactions.
{
"auctions": {
"$auction_id": {
"current_bid": {
".validate": "newData.hasChildren(['uid', 'amount'])",
"uid": {
".validate": "newData.val() === auth.uid"
},
"amount": {
".validate": "newData.isNumber() && newData.val() > data.val() && newData.val() === root.child('auctions/'+$auction_id+'/bids/'+auth.uid+'/amount').val()"
}
},
"bids": {
"$uid": {
".validate": "$uid === auth.uid && newData.hasChildren(['amount', 'timestamp'])",
"amount": {
".validate": "newData.isNumber() && newData.val() === root.child('auctions/' + $auction_id + '/current_bid').val()"
},
"timestamp": {
".validate": "newData.val() > now - 500 && newData.val() <= now"
}
}
}
}
}
}
// create the bid entry
function bid(auctionId, uid, amount, doneCallback) {
var ref = fbRef.child('auctions').child(auctionId).child('bids').child(uid);
ref.set({
amount: amount,
timestamp: Firebase.ServerValue.TIMESTAMP
}, doneCallback);
}
// try to update the current bid to be mine
// if this fails, it means someone else has swooped in with a better deal
function claimCurrentBid(auctionId, uid, amount) {
var ref = fbRef.child('auctions').child(auctionId).child('current_bid');
ref.transaction(function(currentBid) {
if( currentBid === null ) { currentBid = { amount: 0 }; }
if( currentBid.amount < amount ) {
return { amount: amount, uid: uid };
}
// otherwise abort the attempt to claim it since someone has already bidded higher
return undefined;
});
}
var fbRef = new Firebase(...);
var userId = 'kato123';
var bidAmount = 123.50;
var auctionId = 'leatherPants123';
bid(auctionId, userId, bidAmount, function(error) {
if( error ) { throw error; }
claimCurrentBid(auctionId, userId, bidAmount);
});
@katowulf
Copy link
Author

Structure the data to include a "current bid" to track the current winning item. Not only will this make it easy to listen on the currently selected bid, it also provides a base for some security rules.

First, write the user's bid into the bids/ path, stored by user id. Then use a transaction() to update the current bid. If the update fails, this means someone else has concurrently submitted a better bid.

Depending on the app's constraints, use case, security requirements, and server-side capabilities, there are several optimizations and security layers to could consider:

  1. Queue bids into a list that is processed by a worker thread on a server. The worker would receive the bids, attempt to apply them in order, and reject any bids that aren’t valid due to concurrent changes. This isn’t a requirement as most of your needs can likely be handled by security rules, it adds the complexity of a hosted script, but provides quite a few more options for processing, validating, and intermediating the process of determining winners.

  2. Add an audit trail and validate that an entry is put into the audit path before any changes are made to the bids/ path.

A good understanding of the fundamentals is critical to success with a client-only app of this complexity. Read and grok the guide: https://www.firebase.com/docs/web/guide/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment