# libraries bcrypt = (require 'bcrypt') sqlite3 = (require 'sqlite3').verbose() # bruteforce countermeasure saltRounds = 13 FFTCGDB = (filename, truncate) -> that = @ @filename = filename @db = new sqlite3.Database @filename, (err) -> if err console.error err.message else console.log "[FFTCGDB] Connected to '#{that.filename}'" that.db.run 'PRAGMA foreign_keys = ON;', (err) -> console.error err.message if err if truncate == true that.db.run 'DROP TABLE IF EXISTS users;', (err) -> console.error err.message if err that.db.run ''' CREATE TABLE users ( user integer PRIMARY KEY, login text NOT NULL COLLATE NOCASE, pwdhash text NOT NULL, UNIQUE(login) ); ''', (err) -> console.error err.message if err that.db.run 'DROP TABLE IF EXISTS decks;', (err) -> console.error err.message if err that.db.run ''' CREATE TABLE decks ( deck integer PRIMARY KEY, user integer NOT NULL, FOREIGN KEY (user) REFERENCES users (user) ON DELETE CASCADE ); ''', (err) -> console.error err.message if err that.db.run 'DROP TABLE IF EXISTS decks_cards;', (err) -> console.error err.message if err that.db.run ''' CREATE TABLE decks_cards ( deck integer NOT NULL, card text NOT NULL, quant integer NOT NULL, UNIQUE(deck, card), FOREIGN KEY (deck) REFERENCES decks (deck) ON DELETE CASCADE ); ''', (err) -> console.error err.message if err console.log "[FFTCGDB] recreated DB" return FFTCGDB::close = -> new Promise (resolve, reject) -> @db.close (err) -> if err resolve "[FFTCGDB] Error closing: '#{err.message}'" else reject "[FFTCGDB] Closed '#{@filename}'" FFTCGDB::register = (login, password) -> that = @ new Promise (resolve, reject) -> # validate user input if login == '' or password == '' # no user name or password given console.log "[FFTCGDB] reg: user name '#{login}' or password empty" reject 'invalid' # hash password bcrypt.hash password, saltRounds, (err, hash) -> if err console.log "[FFTCGDB] reg: hash fail for name '#{login}'" reject 'hash' # try creating row in users table stmt = that.db.prepare 'INSERT INTO users (login, pwdhash) VALUES (?, ?)' stmt.run [login, hash], (err) -> if err console.log "[FFTCGDB] reg: DB fail '#{err.code}' for name '#{login}'" stmt.finalize() # reduce attack surface, don't disclose user names reject 'db' # user already exists else console.log "[FFTCGDB] reg: OK '#{login}'" stmt.finalize() # registration successful resolve user: @lastID login: login FFTCGDB::login = (login, password) -> that = @ new Promise (resolve, reject) -> # get users table row stmt = that.db.prepare 'SELECT user, login, pwdhash FROM users WHERE login = ?' stmt.get [login], (err, row) -> if err console.log "[FFTCGDB] login: DB fail '#{err.code}' for name '#{login}'" stmt.finalize() reject 'db' else if not row # hash the password for timing attack reasons bcrypt.hash password, saltRounds, (err, hash) -> console.log "[FFTCGDB] login: nonexistent '#{login}'" stmt.finalize() # reduce attack surface, don't disclose user names reject 'login' # user doesnt exist else bcrypt.compare password, row.pwdhash, (err, res) -> if err console.log "[FFTCGDB] login: hash fail for name '#{login}'" reject 'hash' if res == true console.log "[FFTCGDB] login: OK '#{row.login}'" stmt.finalize() # login successful resolve user: row.user login: row.login else console.log "[FFTCGDB] login: wrong password for '#{login}'" stmt.finalize() # login failed reject 'login' FFTCGDB::addDeck = (user, deckCards) -> that = @ new Promise (resolve, reject) -> # try creating row in decks table stmt = that.db.prepare 'INSERT INTO decks (user) VALUES (?)' stmt.run [user], (err) -> if err console.log "[FFTCGDB] addDeck: DB fail '#{err.code}' for id '#{user}'" stmt.finalize() reject 'db' else stmt.finalize() # deck added successfully deckID = @lastID stmt = that.db.prepare 'INSERT INTO decks_cards (deck, card, quant) VALUES (?, ?, ?)' # add individual cards that.db.parallelize -> # needs to be done in several queries promiseCount = deckCards.length deckCards.forEach (card) -> stmt.run [deckID, card.id, card.quant], (err) -> if err console.log "[FFTCGDB] addDeck: DB fail '#{err.code}' for card '#{deckID}', '#{card.id}', '#{card.quant}'" stmt.finalize() reject 'db' else # check if all queries are done promiseCount -= 1 if promiseCount == 0 console.log "[FFTCGDB] addDeck: OK '#{user}'" stmt.finalize() resolve deckID FFTCGDB::delDeck = (deck) -> that = @ new Promise (resolve, reject) -> # try creating row in decks table stmt = that.db.prepare 'DELETE FROM decks WHERE deck = ?' stmt.run [deck], (err) module.exports = FFTCGDB