Last active
October 31, 2017 16:41
-
-
Save katowulf/248860e7581124b44361 to your computer and use it in GitHub Desktop.
Validating client-only auction bids in Firebase using security rules and transactions.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"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" | |
} | |
} | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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:
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.
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/