2019-02-14 16:47:47 +00:00
# node libraries
2018-12-07 09:38:46 +00:00
bcrypt = ( require ' bcrypt ' )
2019-02-07 16:03:20 +00:00
logger = ( require ' logging ' ) . default ' db '
2019-02-19 18:57:22 +00:00
path = ( require ' path ' )
sqlite3 = ( require ' sqlite3 ' ) . verbose ( )
2018-12-07 09:38:46 +00:00
2018-12-16 01:37:00 +00:00
# bruteforce countermeasure
saltRounds = 13
2019-05-08 19:35:11 +00:00
messages =
empty: ' Empty user name or password '
hash: ' Failed to process your data, try again later '
exists: ' User name is already taken '
noexists: ' Wrong user name or password '
password: ' Wrong user name or password '
db: ' Failed to access the database, try again later '
2019-02-19 20:36:14 +00:00
class FFTCGDB
constructor: (filename, truncate) ->
@filename = filename
2018-12-14 06:03:03 +00:00
2019-02-19 20:36:14 +00:00
@db = new sqlite3 . Database @ filename , (err) =>
2018-12-16 21:51:08 +00:00
if err
2019-02-19 20:36:14 +00:00
logger . error err . message
2018-12-16 21:51:08 +00:00
2019-02-19 20:36:14 +00:00
else
logger . info " OK open ' #{ @ filename } ' "
@ db . run ' PRAGMA foreign_keys = ON; ' , (err) =>
logger . error err . message if err
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
2019-05-10 12:34:58 +00:00
@ 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
@ db . run ''' INSERT INTO users VALUES(1, ' jmm ' , ' $2b$13$jgDdHHDWqq1RV6PXxf7aOO6AbxqY6tbxIADyIO0FeXt2BlKQCCMzS ' ,NULL); '''
2019-05-13 15:35:32 +00:00
@ db . run ''' INSERT INTO decks VALUES(1,1, ' { " name " : " Antipode Bomb Version 6.0 " , " note " : " As Seen In Tournament: The North American Water Cup " , " cards " :[{ " count " :1, " serial " : " 1-192 " },{ " count " :2, " serial " : " 7-132 " },{ " count " :2, " serial " : " 8-037 " },{ " count " :2, " serial " : " 8-139 " },{ " count " :1, " serial " : " 5-036 " },{ " count " :3, " serial " : " 4-048 " },{ " count " :1, " serial " : " 2-026 " },{ " count " :3, " serial " : " 8-043 " },{ " count " :3, " serial " : " 4-021 " },{ " count " :3, " serial " : " 3-033 " },{ " count " :1, " serial " : " 8-014 " },{ " count " :2, " serial " : " 8-006 " },{ " count " :1, " serial " : " 8-042 " },{ " count " :1, " serial " : " 6-027 " },{ " count " :3, " serial " : " 5-019 " },{ " count " :2, " serial " : " 2-019 " },{ " count " :2, " serial " : " 5-032 " },{ " count " :3, " serial " : " 4-026 " },{ " count " :3, " serial " : " 1-057 " },{ " count " :1, " serial " : " 1-048 " },{ " count " :2, " serial " : " 8-036 " },{ " count " :3, " serial " : " 8-005 " },{ " count " :3, " serial " : " 2-005 " },{ " count " :1, " serial " : " 7-017 " },{ " count " :1, " serial " : " 8-007 " }]} ' ); '''
2019-02-19 20:36:14 +00:00
logger . info ' OK clear '
close: ->
logger . info ' shutting down '
new Promise (resolve, reject) =>
@ db . close (err) ->
2018-12-14 12:31:07 +00:00
if err
2019-02-19 20:36:14 +00:00
logger . error " FAIL ' #{ err . message } ' "
2019-05-08 19:35:11 +00:00
reject null
2019-02-19 20:36:14 +00:00
else
logger . warn " OK close ' #{ @ filename } ' "
2019-05-08 19:35:11 +00:00
resolve null
validate: (login, password) ->
defined = (value) -> value ? and value isnt ' '
2019-02-19 20:36:14 +00:00
new Promise (resolve, reject) =>
2019-05-08 19:35:11 +00:00
if ( defined login ) and ( defined password )
# both are defined
resolve null
else
2019-02-19 20:36:14 +00:00
# no user name or password given
2019-05-08 19:35:11 +00:00
logger . info " validate: FAIL empty ' #{ login } ' or password "
reject null
2019-02-19 20:36:14 +00:00
2019-05-08 19:35:11 +00:00
register: (login, password) ->
new Promise (resolve, reject) =>
# validate user input
@ validate login , password
. then =>
# hash password
bcrypt . hash password , saltRounds , (err, hash) =>
if err
logger . warn " reg: FAIL hash for ' #{ login } ' "
reject messages . hash
else
# try creating row in users table
stmt = @ db . prepare ' INSERT INTO users (login, pwdhash) VALUES (?, ?) '
stmt . run [ login , hash ] , (err) ->
stmt . finalize ( )
if err
logger . warn " reg: FAIL db ' #{ err . code } ' for ' #{ login } ' "
2019-05-09 12:33:43 +00:00
# user already exists
reject messages . exists
2019-05-08 19:35:11 +00:00
else
logger . info " reg: OK ' #{ login } ' "
# registration successful
2019-05-09 12:33:43 +00:00
resolve null
2019-05-08 19:35:11 +00:00
. catch ->
reject messages . empty
2019-02-19 20:36:14 +00:00
login: (login, password) ->
new Promise (resolve, reject) =>
2019-05-08 19:35:11 +00:00
# validate user input
@ validate login , password
. then =>
# get users table row
2019-05-09 12:33:43 +00:00
stmt = @ db . prepare ' SELECT * FROM users WHERE login = ? '
2019-05-08 19:35:11 +00:00
stmt . get [ login ] , (err, row) =>
stmt . finalize ( )
if err
logger . warn " login: FAIL db ' #{ err . code } ' for ' #{ login } ' "
reject messages . db
else if not row
# hash the password for timing attack reasons
bcrypt . hash password , saltRounds , (err, hash) ->
logger . debug " login: FAIL nonexistent ' #{ login } ' "
reject messages . noexists # user doesnt exist
else
bcrypt . compare password , row . pwdhash , (err, res) ->
if err
logger . warn " login: FAIL hash for ' #{ login } ' "
reject messages . hash
if res == true
logger . debug " login: OK ' #{ row . login } ' "
# login successful
2019-05-09 16:03:35 +00:00
resolve row . user
2019-05-08 19:35:11 +00:00
else
logger . debug " login: FAIL password for ' #{ login } ' "
reject messages . password # login failed
. catch ->
reject messages . empty
2019-02-19 20:36:14 +00:00
2019-05-13 15:35:15 +00:00
getUser: (userID) ->
2019-05-09 16:03:35 +00:00
new Promise (resolve, reject) =>
# get users table row
stmt = @ db . prepare ' SELECT * FROM users WHERE user = ? '
2019-05-13 15:35:15 +00:00
stmt . get [ userID ] , (err, row) =>
2019-05-09 16:03:35 +00:00
stmt . finalize ( )
if err
2019-05-13 15:35:15 +00:00
logger . warn " get: FAIL db ' #{ err . code } ' for ' #{ userID } ' "
2019-05-09 16:03:35 +00:00
reject messages . db
else if not row
2019-05-13 15:35:15 +00:00
logger . debug " get: FAIL nonexistent ' #{ userID } ' "
2019-05-09 16:03:35 +00:00
reject messages . noexists # user doesnt exist
else
resolve
user: row . user
login: row . login
settings: row . settings
2019-05-13 15:35:15 +00:00
addDeck: (userID, deckCards) ->
2019-02-19 20:36:14 +00:00
new Promise (resolve, reject) =>
# try creating row in decks table
stmt = @ db . prepare ' INSERT INTO decks (user, json) VALUES (?, ?) '
2019-05-13 15:35:15 +00:00
stmt . run [ userID , JSON . stringify deckCards ] , (err) ->
2019-02-19 20:36:14 +00:00
stmt . finalize ( )
if err
2019-05-13 15:35:15 +00:00
logger . warn " addDeck: FAIL db ' #{ err . code } ' for ' #{ userID } ' "
2019-05-08 19:35:11 +00:00
reject messages . db
2018-12-16 21:51:08 +00:00
2019-02-19 20:36:14 +00:00
else
logger . debug " addDeck: OK ' #{ @ lastID } ' "
resolve @ lastID
2018-12-14 12:31:07 +00:00
2019-05-13 15:35:15 +00:00
modDeck: (userID, deckID, deckCards) ->
2019-02-19 20:36:14 +00:00
new Promise (resolve, reject) =>
2019-05-13 15:35:15 +00:00
stmt = @ db . prepare ' UPDATE decks SET json = ? WHERE deck = ? AND user = ? '
2019-05-20 15:02:39 +00:00
stmt . run [ ( JSON . stringify deckCards ) , deckID , userID ] , (err) ->
2019-02-19 20:36:14 +00:00
stmt . finalize ( )
if err
logger . warn " modDeck: FAIL db ' #{ err . code } ' for ' #{ deckID } ' "
2019-05-08 19:35:11 +00:00
reject messages . db
2019-05-20 15:02:39 +00:00
else if @ changes == 0
logger . warn " no changes for input ( #{ userID } , #{ deckID } , #{ JSON . stringify deckCards } )! "
reject messages . db
2019-02-19 20:36:14 +00:00
else
resolve deckID
2018-12-27 02:03:22 +00:00
2019-05-13 15:35:15 +00:00
getDecks: (userID) ->
2019-02-19 20:36:14 +00:00
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 = ? '
2019-05-13 15:35:15 +00:00
stmt . all [ userID ] , (err, rows) ->
2018-12-27 02:03:22 +00:00
stmt . finalize ( )
2019-02-19 20:36:14 +00:00
if err
2019-05-13 15:35:15 +00:00
logger . warn " getDecks: FAIL db ' #{ err . code } ' for ' #{ userID } ' "
2019-05-08 19:35:11 +00:00
reject messages . db
2019-02-19 20:36:14 +00:00
else
2019-05-13 15:35:15 +00:00
logger . debug " getDecks: OK ' #{ userID } ' "
2019-02-19 20:36:14 +00:00
resolve ( id: row . deck , content: JSON . parse row . json for row , i in rows )
2018-12-27 02:03:22 +00:00
2019-05-24 11:41:48 +00:00
delDeck: (userID, deckID) ->
2019-02-19 20:36:14 +00:00
new Promise (resolve, reject) =>
2019-05-24 11:41:48 +00:00
stmt = @ db . prepare ' DELETE FROM decks WHERE deck = ? AND user = ? '
stmt . run [ deckID , userID ] , (err) ->
2018-12-27 02:03:22 +00:00
stmt . finalize ( )
2019-02-19 20:36:14 +00:00
if err
logger . warn " delDeck: FAIL db ' #{ err . code } ' for ' #{ deckID } ' "
2019-05-08 19:35:11 +00:00
reject messages . db
2019-02-19 20:36:14 +00:00
else
logger . debug " delDeck: OK ' #{ deckID } ' "
resolve deckID
2018-12-27 02:03:22 +00:00
2018-12-16 21:51:08 +00:00
2019-02-19 18:57:22 +00:00
module.exports = new FFTCGDB path . resolve ( __dirname , ' fftcg.db ' ) , true