diff --git a/backend/db.coffee b/backend/db.coffee index edf11bd..795f1fe 100644 --- a/backend/db.coffee +++ b/backend/db.coffee @@ -7,219 +7,175 @@ sqlite3 = (require 'sqlite3').verbose() # bruteforce countermeasure saltRounds = 13 -FFTCGDB = (filename, truncate) -> - that = @ - @filename = filename +class FFTCGDB + constructor: (filename, truncate) -> + @filename = filename - @db = new sqlite3.Database @filename, (err) -> - if err - logger.error err.message - - else - logger.info "Connected to '#{that.filename}'" - - that.db.run 'PRAGMA foreign_keys = ON;', (err) -> - logger.error err.message if err - - if truncate == true - that.db.run 'DROP TABLE IF EXISTS users;', (err) -> - logger.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, - settings text, - UNIQUE(login) - ); - ''', (err) -> - logger.error err.message if err - - that.db.run 'DROP TABLE IF EXISTS decks;', (err) -> - logger.error err.message if err - that.db.run ''' - CREATE TABLE decks ( - deck integer PRIMARY KEY, - user integer NOT NULL, - json text, - FOREIGN KEY (user) REFERENCES users (user) - ON DELETE CASCADE - ); - ''', (err) -> - logger.error err.message if err - - logger.info 'recreated sqlite3 db' - - return - -FFTCGDB::close = -> - logger.info 'shutting down' - new Promise (resolve, reject) -> - @db.close (err) -> + @db = new sqlite3.Database @filename, (err) => if err - logger.error "Error closing: '#{err.message}'" - reject 'db' + logger.error err.message + else - logger.warn "Closed '#{@filename}'" - resolve 'ok' + logger.info "OK open '#{@filename}'" -FFTCGDB::register = (login, password) -> - that = @ + @db.run 'PRAGMA foreign_keys = ON;', (err) => + logger.error err.message if err - new Promise (resolve, reject) -> - # validate user input - if login == '' or password == '' - # no user name or password given - logger.info "reg: user name '#{login}' or password empty" - reject 'invalid' + if truncate == true + @db.run 'DROP TABLE IF EXISTS users;', (err) => + logger.error err.message if err + @db.run ''' + CREATE TABLE users ( + user integer PRIMARY KEY, + login text NOT NULL COLLATE NOCASE, + pwdhash text NOT NULL, + settings text, + UNIQUE(login) + ); + ''', (err) => + logger.error err.message if err - # hash password - bcrypt.hash password, saltRounds, (err, hash) -> - if err - logger.warn "reg: hash fail for name '#{login}'" - reject 'hash' + @db.run 'DROP TABLE IF EXISTS decks;', (err) => + logger.error err.message if err + @db.run ''' + CREATE TABLE decks ( + deck integer PRIMARY KEY, + user integer NOT NULL, + json text, + FOREIGN KEY (user) REFERENCES users (user) + ON DELETE CASCADE + ); + ''', (err) => + logger.error err.message if err - # try creating row in users table - stmt = that.db.prepare 'INSERT INTO users (login, pwdhash) VALUES (?, ?)' - stmt.run [login, hash], (err) -> + logger.info 'OK clear' + + close: -> + logger.info 'shutting down' + new Promise (resolve, reject) => + @db.close (err) -> if err - logger.warn "reg: DB fail '#{err.code}' for name '#{login}'" - stmt.finalize() - # reduce attack surface, don't disclose user names - reject 'db' # user already exists + logger.error "FAIL '#{err.message}'" + reject 'db' + else + logger.warn "OK close '#{@filename}'" + resolve 'ok' + + register: (login, password) -> + new Promise (resolve, reject) => + # validate user input + if login == '' or password == '' + # no user name or password given + logger.info "reg: FAIL empty '#{login}' or password" + reject 'invalid' + + # hash password + bcrypt.hash password, saltRounds, (err, hash) => + if err + logger.warn "reg: FAIL hash for '#{login}'" + reject 'hash' else - logger.info "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 - logger.warn "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) -> - logger.debug "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 - logger.warn "login: hash fail for name '#{login}'" - reject 'hash' - - if res == true - logger.debug "login: OK '#{row.login}'" + # try creating row in users table + stmt = @db.prepare 'INSERT INTO users (login, pwdhash) VALUES (?, ?)' + stmt.run [login, hash], (err) -> stmt.finalize() - # login successful - resolve - user: row.user - login: row.login + if err + logger.warn "reg: FAIL db '#{err.code}' for '#{login}'" + # reduce attack surface, don't disclose user names + reject 'db' # user already exists - else - logger.debug "login: wrong password for '#{login}'" - stmt.finalize() - # login failed - reject 'login' + else + logger.info "reg: OK '#{login}'" + # registration successful + resolve + user: @lastID + login: login -FFTCGDB::addDeck = (user, deckCards) -> - that = @ - - new Promise (resolve, reject) -> - # try creating row in decks table - stmt = that.db.prepare 'INSERT INTO decks (user, json) VALUES (?, ?)' - stmt.run [user, JSON.stringify deckCards], (err) -> - if err - logger.warn "addDeck: DB fail '#{err.code}' for id '#{user}'" + login: (login, password) -> + new Promise (resolve, reject) => + # get users table row + stmt = @db.prepare 'SELECT user, login, pwdhash FROM users WHERE login = ?' + stmt.get [login], (err, row) => stmt.finalize() - reject 'db' + if err + logger.warn "login: FAIL db '#{err.code}' for '#{login}'" + reject 'db' - else + else if not row + # hash the password for timing attack reasons + bcrypt.hash password, saltRounds, (err, hash) -> + logger.debug "login: FAIL nonexistent '#{login}'" + # reduce attack surface, don't disclose user names + reject 'login' # user doesnt exist + + else + bcrypt.compare password, row.pwdhash, (err, res) -> + if err + logger.warn "login: FAIL hash for '#{login}'" + reject 'hash' + + if res == true + logger.debug "login: OK '#{row.login}'" + # login successful + resolve + user: row.user + login: row.login + + else + logger.debug "login: FAIL password for '#{login}'" + # login failed + reject 'login' + + addDeck: (user, deckCards) -> + new Promise (resolve, reject) => + # try creating row in decks table + stmt = @db.prepare 'INSERT INTO decks (user, json) VALUES (?, ?)' + stmt.run [user, JSON.stringify deckCards], (err) -> stmt.finalize() - # deck added successfully, now add cards - that.modDeck(@lastID, deckCards) - .then (deckID) -> - resolve deckID - .catch (error) -> - reject error + if err + logger.warn "addDeck: FAIL db '#{err.code}' for '#{user}'" + reject 'db' -FFTCGDB::modDeck = (deckID, deckCards) -> - that = @ + else + logger.debug "addDeck: OK '#{@lastID}'" + resolve @lastID - new Promise (resolve, reject) -> - # delete old deck cards - stmt = that.db.prepare 'DELETE FROM decks_cards WHERE deck = ?' - stmt.run [deckID], (err) -> - stmt.finalize() - if err - logger.warn "modDeck: DB fail '#{err.code}' for deck '#{deckID}'" - reject 'db' - else - stmt = that.db.prepare 'INSERT INTO decks_cards (deck, card, quant) VALUES (?, ?, ?)' - # add new 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 - logger.warn "modDeck: 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 - logger.debug "modDeck: OK '#{deckID}'" - stmt.finalize() - resolve deckID + modDeck: (deckID, deckCards) -> + new Promise (resolve, reject) => + stmt = @db.prepare 'UPDATE decks SET json = ? WHERE deck = ?' + stmt.run [deckCards, deckID], (err) -> + stmt.finalize() + if err + logger.warn "modDeck: FAIL db '#{err.code}' for '#{deckID}'" + reject 'db' + else + logger.debug "modDeck: OK '#{deckID}'" + resolve deckID -FFTCGDB::getDecks = (user) -> - that = @ + getDecks: (user) -> + new Promise (resolve, reject) => + stmt = @db.prepare 'SELECT decks.deck, decks.json FROM decks INNER JOIN users ON decks.user = users.user WHERE users.user = ?' + stmt.all [user], (err, rows) -> + stmt.finalize() + if err + logger.warn "getDeck: FAIL db '#{err.code}' for '#{deckID}'" + reject 'db' + else + logger.debug "getDeck: OK '#{deckID}'" + resolve (id: row.deck, content: JSON.parse row.json for row, i in rows) - new Promise (resolve, reject) -> - # try deleting correct row in decks table - decks = {} - stmt = that.db.prepare 'SELECT decks.deck, decks.json FROM decks INNER JOIN users ON decks.user = users.user WHERE users.user = ?' - stmt.all [user], (err, rows) -> - stmt.finalize() - if err - logger.warn "getDeck: DB fail '#{err.code}' for deck '#{deckID}'" - reject 'db' - else - logger.debug "getDeck: OK '#{deckID}'" - for row in rows - decks[row.deck] = JSON.parse row.json - resolve decks - -FFTCGDB::delDeck = (deckID) -> - that = @ - - new Promise (resolve, reject) -> - # try deleting correct row in decks table - stmt = that.db.prepare 'DELETE FROM decks WHERE deck = ?' - stmt.run [deckID], (err) -> - stmt.finalize() - if err - logger.warn "delDeck: DB fail '#{err.code}' for deck '#{deckID}'" - reject 'db' - else - logger.debug "delDeck: OK '#{deckID}'" - resolve deckID + delDeck: (deckID) -> + new Promise (resolve, reject) => + stmt = @db.prepare 'DELETE FROM decks WHERE deck = ?' + stmt.run [deckID], (err) -> + stmt.finalize() + if err + logger.warn "delDeck: FAIL db '#{err.code}' for '#{deckID}'" + reject 'db' + else + logger.debug "delDeck: OK '#{deckID}'" + resolve deckID module.exports = new FFTCGDB path.resolve(__dirname, 'fftcg.db'), true diff --git a/backend/session.coffee b/backend/session.coffee index fbfa336..11ac365 100644 --- a/backend/session.coffee +++ b/backend/session.coffee @@ -17,7 +17,7 @@ class FFTCGSESSION host: 'redis' port: 6379 - @db.on 'error', (err) -> + @db.on 'error', (err) => logger.error err.message start: (data) ->