Compare commits

..

5 commits

8 changed files with 158 additions and 58 deletions

11
inc/console.coffee Normal file
View file

@ -0,0 +1,11 @@
FFTCGLOG = (unit) ->
@unit = unit
return
FFTCGLOG::log = (msg) ->
console.log "[#{@unit}] #{msg}"
FFTCGLOG::error = (msg) ->
console.error "[#{@unit}] #{msg}"
module.exports = FFTCGLOG

View file

@ -1,6 +1,7 @@
# libraries # libraries
bcrypt = (require 'bcrypt') bcrypt = (require 'bcrypt')
sqlite3 = (require 'sqlite3').verbose() sqlite3 = (require 'sqlite3').verbose()
FFTCGLOG = new (require './console')('FFTCGDB')
# bruteforce countermeasure # bruteforce countermeasure
saltRounds = 13 saltRounds = 13
@ -11,17 +12,17 @@ FFTCGDB = (filename, truncate) ->
@db = new sqlite3.Database @filename, (err) -> @db = new sqlite3.Database @filename, (err) ->
if err if err
console.error err.message FFTCGLOG.error err.message
else else
console.log "[FFTCGDB] Connected to '#{that.filename}'" FFTCGLOG.log "Connected to '#{that.filename}'"
that.db.run 'PRAGMA foreign_keys = ON;', (err) -> that.db.run 'PRAGMA foreign_keys = ON;', (err) ->
console.error err.message if err FFTCGLOG.error err.message if err
if truncate == true if truncate == true
that.db.run 'DROP TABLE IF EXISTS users;', (err) -> that.db.run 'DROP TABLE IF EXISTS users;', (err) ->
console.error err.message if err FFTCGLOG.error err.message if err
that.db.run ''' that.db.run '''
CREATE TABLE users ( CREATE TABLE users (
user integer PRIMARY KEY, user integer PRIMARY KEY,
@ -30,35 +31,22 @@ FFTCGDB = (filename, truncate) ->
UNIQUE(login) UNIQUE(login)
); );
''', (err) -> ''', (err) ->
console.error err.message if err FFTCGLOG.error err.message if err
that.db.run 'DROP TABLE IF EXISTS decks;', (err) -> that.db.run 'DROP TABLE IF EXISTS decks;', (err) ->
console.error err.message if err FFTCGLOG.error err.message if err
that.db.run ''' that.db.run '''
CREATE TABLE decks ( CREATE TABLE decks (
deck integer PRIMARY KEY, deck integer PRIMARY KEY,
user integer NOT NULL, user integer NOT NULL,
json text,
FOREIGN KEY (user) REFERENCES users (user) FOREIGN KEY (user) REFERENCES users (user)
ON DELETE CASCADE ON DELETE CASCADE
); );
''', (err) -> ''', (err) ->
console.error err.message if err FFTCGLOG.error err.message if err
that.db.run 'DROP TABLE IF EXISTS decks_cards;', (err) -> FFTCGLOG.log 'recreated DB'
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 return
@ -66,9 +54,11 @@ FFTCGDB::close = ->
new Promise (resolve, reject) -> new Promise (resolve, reject) ->
@db.close (err) -> @db.close (err) ->
if err if err
resolve "[FFTCGDB] Error closing: '#{err.message}'" FFTCGLOG.log "Error closing: '#{err.message}'"
resolve 'ok'
else else
reject "[FFTCGDB] Closed '#{@filename}'" FFTCGLOG.error "Closed '#{@filename}'"
reject 'db'
FFTCGDB::register = (login, password) -> FFTCGDB::register = (login, password) ->
that = @ that = @
@ -77,26 +67,26 @@ FFTCGDB::register = (login, password) ->
# validate user input # validate user input
if login == '' or password == '' if login == '' or password == ''
# no user name or password given # no user name or password given
console.log "[FFTCGDB] reg: user name '#{login}' or password empty" FFTCGLOG.log "reg: user name '#{login}' or password empty"
reject 'invalid' reject 'invalid'
# hash password # hash password
bcrypt.hash password, saltRounds, (err, hash) -> bcrypt.hash password, saltRounds, (err, hash) ->
if err if err
console.log "[FFTCGDB] reg: hash fail for name '#{login}'" FFTCGLOG.log "reg: hash fail for name '#{login}'"
reject 'hash' reject 'hash'
# try creating row in users table # try creating row in users table
stmt = that.db.prepare 'INSERT INTO users (login, pwdhash) VALUES (?, ?)' stmt = that.db.prepare 'INSERT INTO users (login, pwdhash) VALUES (?, ?)'
stmt.run [login, hash], (err) -> stmt.run [login, hash], (err) ->
if err if err
console.log "[FFTCGDB] reg: DB fail '#{err.code}' for name '#{login}'" FFTCGLOG.log "reg: DB fail '#{err.code}' for name '#{login}'"
stmt.finalize() stmt.finalize()
# reduce attack surface, don't disclose user names # reduce attack surface, don't disclose user names
reject 'db' # user already exists reject 'db' # user already exists
else else
console.log "[FFTCGDB] reg: OK '#{login}'" FFTCGLOG.log "reg: OK '#{login}'"
stmt.finalize() stmt.finalize()
# registration successful # registration successful
resolve resolve
@ -111,14 +101,14 @@ FFTCGDB::login = (login, password) ->
stmt = that.db.prepare 'SELECT user, login, pwdhash FROM users WHERE login = ?' stmt = that.db.prepare 'SELECT user, login, pwdhash FROM users WHERE login = ?'
stmt.get [login], (err, row) -> stmt.get [login], (err, row) ->
if err if err
console.log "[FFTCGDB] login: DB fail '#{err.code}' for name '#{login}'" FFTCGLOG.log "login: DB fail '#{err.code}' for name '#{login}'"
stmt.finalize() stmt.finalize()
reject 'db' reject 'db'
else if not row else if not row
# hash the password for timing attack reasons # hash the password for timing attack reasons
bcrypt.hash password, saltRounds, (err, hash) -> bcrypt.hash password, saltRounds, (err, hash) ->
console.log "[FFTCGDB] login: nonexistent '#{login}'" FFTCGLOG.log "login: nonexistent '#{login}'"
stmt.finalize() stmt.finalize()
# reduce attack surface, don't disclose user names # reduce attack surface, don't disclose user names
reject 'login' # user doesnt exist reject 'login' # user doesnt exist
@ -126,11 +116,11 @@ FFTCGDB::login = (login, password) ->
else else
bcrypt.compare password, row.pwdhash, (err, res) -> bcrypt.compare password, row.pwdhash, (err, res) ->
if err if err
console.log "[FFTCGDB] login: hash fail for name '#{login}'" FFTCGLOG.log "login: hash fail for name '#{login}'"
reject 'hash' reject 'hash'
if res == true if res == true
console.log "[FFTCGDB] login: OK '#{row.login}'" FFTCGLOG.log "login: OK '#{row.login}'"
stmt.finalize() stmt.finalize()
# login successful # login successful
resolve resolve
@ -138,7 +128,7 @@ FFTCGDB::login = (login, password) ->
login: row.login login: row.login
else else
console.log "[FFTCGDB] login: wrong password for '#{login}'" FFTCGLOG.log "login: wrong password for '#{login}'"
stmt.finalize() stmt.finalize()
# login failed # login failed
reject 'login' reject 'login'
@ -148,43 +138,85 @@ FFTCGDB::addDeck = (user, deckCards) ->
new Promise (resolve, reject) -> new Promise (resolve, reject) ->
# try creating row in decks table # try creating row in decks table
stmt = that.db.prepare 'INSERT INTO decks (user) VALUES (?)' stmt = that.db.prepare 'INSERT INTO decks (user, json) VALUES (?, ?)'
stmt.run [user], (err) -> stmt.run [user, JSON.stringify deckCards], (err) ->
if err if err
console.log "[FFTCGDB] addDeck: DB fail '#{err.code}' for id '#{user}'" FFTCGLOG.log "addDeck: DB fail '#{err.code}' for id '#{user}'"
stmt.finalize() stmt.finalize()
reject 'db' reject 'db'
else else
stmt.finalize() stmt.finalize()
# deck added successfully # deck added successfully, now add cards
deckID = @lastID that.modDeck(@lastID, deckCards)
.then (deckID) ->
resolve deckID
.catch (error) ->
reject error
FFTCGDB::modDeck = (deckID, deckCards) ->
that = @
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
FFTCGLOG.log "modDeck: DB fail '#{err.code}' for deck '#{deckID}'"
reject 'db'
else
stmt = that.db.prepare 'INSERT INTO decks_cards (deck, card, quant) VALUES (?, ?, ?)' stmt = that.db.prepare 'INSERT INTO decks_cards (deck, card, quant) VALUES (?, ?, ?)'
# add individual cards # add new cards
that.db.parallelize -> that.db.parallelize ->
# needs to be done in several queries # needs to be done in several queries
promiseCount = deckCards.length promiseCount = deckCards.length
deckCards.forEach (card) -> deckCards.forEach (card) ->
stmt.run [deckID, card.id, card.quant], (err) -> stmt.run [deckID, card.id, card.quant], (err) ->
if err if err
console.log "[FFTCGDB] addDeck: DB fail '#{err.code}' for card '#{deckID}', '#{card.id}', '#{card.quant}'" FFTCGLOG.log "modDeck: DB fail '#{err.code}' for card '#{deckID}', '#{card.id}', '#{card.quant}'"
stmt.finalize() stmt.finalize()
reject 'db' reject 'db'
else else
# check if all queries are done # check if all queries are done
promiseCount -= 1 promiseCount -= 1
if promiseCount == 0 if promiseCount == 0
console.log "[FFTCGDB] addDeck: OK '#{user}'" FFTCGLOG.log "modDeck: OK '#{deckID}'"
stmt.finalize() stmt.finalize()
resolve deckID resolve deckID
FFTCGDB::delDeck = (deck) -> FFTCGDB::getDecks = (user) ->
that = @ that = @
new Promise (resolve, reject) -> new Promise (resolve, reject) ->
# try creating row in decks table # 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
FFTCGLOG.log "getDeck: DB fail '#{err.code}' for deck '#{deckID}'"
reject 'db'
else
FFTCGLOG.log "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 = that.db.prepare 'DELETE FROM decks WHERE deck = ?'
stmt.run [deck], (err) stmt.run [deckID], (err) ->
stmt.finalize()
if err
FFTCGLOG.log "delDeck: DB fail '#{err.code}' for deck '#{deckID}'"
reject 'db'
else
FFTCGLOG.log "delDeck: OK '#{deckID}'"
resolve deckID
module.exports = FFTCGDB module.exports = FFTCGDB

View file

@ -3,7 +3,8 @@ express = (require 'express')
path = (require 'path') path = (require 'path')
# my libraries # my libraries
FFTCGDB = (require './fftcgdb') FFTCGDB = (require './db')
FFTCGLOG = new (require './console')('FFTCGROUTER')
# open fftcg db # open fftcg db
fftcgdb = new FFTCGDB path.resolve(__dirname, '../fftcg.db') fftcgdb = new FFTCGDB path.resolve(__dirname, '../fftcg.db')
@ -14,9 +15,9 @@ FFTCGROUTER = express.Router()
# request logging # request logging
FFTCGROUTER.use (req, res, next) -> FFTCGROUTER.use (req, res, next) ->
if req.session.user if req.session.user
console.log "[FFTCGROUTER] user '#{req.session.user.login}' requested '#{req.url}'" FFTCGLOG.log "user '#{req.session.user.login}' requested '#{req.url}'"
else else
console.log "[FFTCGROUTER] requested '#{req.url}'" FFTCGLOG.log "requested '#{req.url}'"
next() next()
@ -65,7 +66,6 @@ FFTCGROUTER.get '/:template.html', (req, res) ->
if req.session.user and req.params.template == 'index' if req.session.user and req.params.template == 'index'
return res.redirect '/usercp.html' return res.redirect '/usercp.html'
# render requested template # render requested template
res.render (req.params.template + '.pug'), (err, html) -> res.render (req.params.template + '.pug'), (err, html) ->
# redirect invalid requests to index # redirect invalid requests to index

View file

@ -1,6 +1,7 @@
# node libraries # node libraries
socketio = (require 'socket.io') socketio = (require 'socket.io')
path = (require 'path') path = (require 'path')
FFTCGLOG = new (require './console')('FFTCGSOCKET')
# my libraries # my libraries
@ -14,17 +15,17 @@ FFTCGSOCKET = (http, session) ->
# on new connection # on new connection
@io.on 'connection', (socket) -> @io.on 'connection', (socket) ->
@session = socket.handshake.session @session = socket.handshake.session
console.log "session '#{@session.id}' connected" FFTCGLOG.log "session '#{@session.id}' connected"
console.log "is user '#{@session.userID}'" if @session.userID FFTCGLOG.log "is user '#{@session.userID}'" if @session.userID
socket.on 'disconnect', -> socket.on 'disconnect', ->
console.log "session '#{that.session.id}' disconnected" FFTCGLOG.log "session '#{that.session.id}' disconnected"
console.log "is user '#{that.session.userID}'" if that.session.userID FFTCGLOG.log "is user '#{that.session.userID}'" if that.session.userID
return return
FFTCGSOCKET::close = -> FFTCGSOCKET::close = ->
console.log '[FFTCGSOCKET] shutting down' FFTCGLOG.log 'shutting down'
if @db if @db
@db.close() @db.close()
.then (msg) -> .then (msg) ->

View file

@ -7,9 +7,10 @@ http = (require 'http')
path = (require 'path') path = (require 'path')
# my libraries # my libraries
FFTCGSOCKET = (require './inc/fftcgsocket') FFTCGSOCKET = (require './inc/socket')
FFTCGSESSION = (require './inc/fftcgsession') FFTCGSESSION = (require './inc/session')
FFTCGROUTER = (require './inc/fftcgrouter') FFTCGROUTER = (require './inc/router')
FFTCGLOG = new (require './inc/console')('FFTCG')
# express framework # express framework
app = express() app = express()
@ -30,10 +31,10 @@ socket = new FFTCGSOCKET web, sharedSession sessionMiddleware
# Create server # Create server
web.listen 3000, -> web.listen 3000, ->
console.log '[FFTCG] Listening on port 3000 ...' FFTCGLOG.log 'Listening on port 3000 ...'
# Handle termination # Handle termination
process.on 'SIGINT', -> process.on 'SIGINT', ->
socket.close() socket.close()
console.log '[FFTCG] shutting down after SIGINT' FFTCGLOG.log 'shutting down after SIGINT'
process.exit() process.exit()

View file

@ -4,6 +4,7 @@ window.$ = require('jquery')
# import bootstrap # import bootstrap
require './style/custom.scss' require './style/custom.scss'
require 'bootstrap/js/dist/alert' require 'bootstrap/js/dist/alert'
require 'bootstrap/js/dist/collapse'
# on load # on load
$ -> $ ->

View file

@ -4,3 +4,57 @@ html
title Crafty Things title Crafty Things
script(src='/usercp.bundle.js') script(src='/usercp.bundle.js')
body body
header.jumbotron.jumbotron-fluid.py-4.bg-primary.text-light.text-center
div.container
h1 Hello World!
h2 App under development, please don't submit any valuable data!
div.container.bg-light
h3 Yavook!FFTCG
div#alert-area
div.row
div.col-md-8
h4 My Decks
ul.list-group#my-decks
li.list-group-item
div.align-middle
div.d-inline-block.btn-group(role="group")
button.btn.btn-secondary(data-toggle="collapse" data-target="#modDeck0") Edit
button.btn.btn-secondary#delDeck0 Delete
div.d-inline-block.pl-2 item1
form.float-none.collapse#modDeck0(name="modDeck0")
div.form-group
label(for="deck") Decklist:
textarea.form-control.text-monospace(name="deck" required)
div.form-group
button.btn.btn-primary.w-100(type="submit") Add
li.list-group-item
div.align-middle
div.d-inline-block.btn-group(role="group")
button.btn.btn-secondary(data-toggle="collapse" data-target="#modDeck1") Edit
button.btn.btn-secondary#delDeck1 Delete
div.d-inline-block.pl-2 item2
form.float-none.collapse#modDeck1(name="modDeck1")
div.form-group
label(for="deck") Decklist:
textarea.form-control.text-monospace(name="deck" required)
div.form-group
button.btn.btn-primary.w-100(type="submit") Add
div.col-md-4
h4 New Deck
form(name="addDeck")
div.form-group
label(for="deck") Paste decklist:
textarea.form-control.text-monospace(name="deck" required)
div.form-group
button.btn.btn-primary.w-100(type="submit") Add
p Hello