Compare commits

..

2 commits

10 changed files with 223 additions and 166 deletions

View file

@ -1,4 +1,4 @@
# libraries # node libraries
bcrypt = (require 'bcrypt') bcrypt = (require 'bcrypt')
sqlite3 = (require 'sqlite3').verbose() sqlite3 = (require 'sqlite3').verbose()
logger = (require 'logging').default 'db' logger = (require 'logging').default 'db'
@ -28,10 +28,11 @@ FFTCGDB = (filename, truncate) ->
user integer PRIMARY KEY, user integer PRIMARY KEY,
login text NOT NULL COLLATE NOCASE, login text NOT NULL COLLATE NOCASE,
pwdhash text NOT NULL, pwdhash text NOT NULL,
settings text,
UNIQUE(login) UNIQUE(login)
); );
''', (err) -> ''', (err) ->
FFTCGLOG.error err.message if err logger.error err.message if err
that.db.run 'DROP TABLE IF EXISTS decks;', (err) -> that.db.run 'DROP TABLE IF EXISTS decks;', (err) ->
logger.error err.message if err logger.error err.message if err
@ -46,7 +47,7 @@ FFTCGDB = (filename, truncate) ->
''', (err) -> ''', (err) ->
logger.error err.message if err logger.error err.message if err
logger.info 'recreated DB' logger.info 'recreated sqlite3 db'
return return
@ -74,7 +75,7 @@ FFTCGDB::register = (login, password) ->
# hash password # hash password
bcrypt.hash password, saltRounds, (err, hash) -> bcrypt.hash password, saltRounds, (err, hash) ->
if err if err
logger.info "reg: hash fail for name '#{login}'" logger.warn "reg: hash fail for name '#{login}'"
reject 'hash' reject 'hash'
# try creating row in users table # try creating row in users table
@ -117,7 +118,7 @@ FFTCGDB::login = (login, password) ->
else else
bcrypt.compare password, row.pwdhash, (err, res) -> bcrypt.compare password, row.pwdhash, (err, res) ->
if err if err
logger.info "login: hash fail for name '#{login}'" logger.warn "login: hash fail for name '#{login}'"
reject 'hash' reject 'hash'
if res == true if res == true

View file

@ -22,6 +22,7 @@
"fastify-static": "^1.1.0", "fastify-static": "^1.1.0",
"fastify-ws": "^1.0.0", "fastify-ws": "^1.0.0",
"logging": "^3.2.0", "logging": "^3.2.0",
"redis": "^2.8.0",
"sqlite3": "^4.0.4" "sqlite3": "^4.0.4"
} }
} }

View file

@ -1,60 +0,0 @@
# node libraries
express = (require 'express')
path = (require 'path')
# my libraries
FFTCGDB = (require './db')
logger = (require 'logging').default 'router'
# open fftcg db
fftcgdb = new FFTCGDB path.resolve(__dirname, 'fftcg.db')
# create router
FFTCGROUTER = express.Router()
# request logging
FFTCGROUTER.use (req, res, next) ->
if req.session.user
logger.debug "user '#{req.session.user.login}' requested '#{req.url}'"
else
logger.debug "requested '#{req.url}'"
next()
# register user
FFTCGROUTER.post '/register', (req, res) ->
fftcgdb.register req.body.login, req.body.password
.then (user) ->
# registration successful, return JSON status
res.json
status: 'ok'
user: user.user
login: user.login
.catch (err) ->
# registration failed, return JSON status
res.json
status: 'fail'
text: err
# log in user
FFTCGROUTER.post '/login', (req, res) ->
fftcgdb.login req.body.login, req.body.password
.then (user) ->
# login successful, save stuff in session
req.session.user = user
req.session.save()
# return JSON status
res.json
status: 'ok'
user: user.user
login: user.login
.catch (err) ->
# login failed, return JSON status
res.json
status: 'fail'
text: err
module.exports = FFTCGROUTER

75
backend/routes.coffee Normal file
View file

@ -0,0 +1,75 @@
# node libraries
path = (require 'path')
# my libraries
FFTCGDB = (require './db')
FFTCGSESSION = (require './session')
logger = (require 'logging').default 'routes'
# open fftcg.db (persistent data)
fftcgdb = new FFTCGDB path.resolve(__dirname, 'fftcg.db'), true
# open session storage (volatile data)
session = new FFTCGSESSION
module.exports = [
# test
url: '/test'
method: 'POST'
handler: (request, reply) ->
logger.info 'Cookies', request.cookies
logger.info 'Body', request.body
logger.info 'Query', request.query
logger.info 'Params', request.params
reply.setCookie 'foo', 'foo'
reply.send
hello: 'world'
,
# register user
url: '/user/register'
method: 'POST'
handler: (request, reply) ->
fftcgdb.register request.body.login, request.body.password
.then (user) ->
# registration successful
reply.send
status: 'ok'
user: user.user
login: user.login
.catch (err) ->
# registration failed
reply.send
status: 'fail'
text: err
,
# log in user
url: '/user/login'
method: 'POST'
handler: (request, reply) ->
session_id = request.cookies.session
logger.info session_id
session.action session_id
.then (user) ->
logger.info user
.catch (err) ->
logger.error err
fftcgdb.login request.body.login, request.body.password
.then (user) ->
# login successful
reply.setCookie 'user', JSON.stringify user
# return JSON status
reply.send
status: 'ok'
user: user.user
login: user.login
.catch (err) ->
# login failed
reply.send
status: 'fail'
text: err
,
]

View file

@ -4,9 +4,9 @@ fastify = (require 'fastify') logger: logger
path = (require 'path') path = (require 'path')
# my libraries # my libraries
# FFTCGSOCKET = (require './socket') socket = (require './socket')
# FFTCGSESSION = (require './session') # FFTCGSESSION = (require './session')
# FFTCGROUTER = (require './router') routes = (require './routes')
# fastify framework # fastify framework
fastify.register (require 'fastify-cookie') fastify.register (require 'fastify-cookie')
@ -16,29 +16,15 @@ fastify.register (require 'fastify-static'), root: path.join __dirname, 'tmpfron
fastify.ready() fastify.ready()
.then -> .then ->
fastify.ws.on 'connection', (socket) -> fastify.ws.on 'connection', socket
logger.info 'Client connected.'
socket.on 'message', (msg) ->
# echo server
logger.info "Echo '#{msg}'."
socket.send "Re: #{msg}"
socket.on 'close', ->
logger.info 'Client disconnected.'
.catch (err) -> .catch (err) ->
logger.error err logger.error err
process.exit 1 process.exit 1
fastify.get '/api', (request, reply) -> for route in routes
logger.info request.cookies fastify.route route
reply
.setCookie 'foo', 'bar'
.send hello: 'world'
fastify.listen 3001, '0.0.0.0' fastify.listen 3001, '0.0.0.0'
.catch (err) -> .catch (err) ->

View file

@ -1,40 +0,0 @@
# node libraries
bodyParser = (require 'body-parser')
express = (require 'express')
sharedSession = (require 'express-socket.io-session')
helmet = (require 'helmet')
http = (require 'http')
path = (require 'path')
logger = (require 'logging').default 'FFTCG'
# my libraries
FFTCGSOCKET = (require './socket')
FFTCGSESSION = (require './session')
FFTCGROUTER = (require './router')
# express framework
app = express()
app.use helmet()
app.use bodyParser.urlencoded
extended: true
# sessions
sessionMiddleware = FFTCGSESSION(app)
app.use sessionMiddleware
# routes
app.use FFTCGROUTER
# socket.io
web = http.Server app
socket = new FFTCGSOCKET web, sharedSession sessionMiddleware
# Create server
web.listen 3001, ->
logger.info 'Listening on port 3001 ...'
# Handle termination
process.on 'SIGINT', ->
socket.close()
logger.info 'shutting down after SIGINT'
process.exit()

View file

@ -1,22 +1,66 @@
# node libraries # node libraries
expressSession = (require 'express-session') redis = (require 'redis')
RedisStore = (require 'connect-redis')(expressSession) crypto = (require 'crypto')
logger = (require 'logging').default 'session'
module.exports = (app) -> # expiry times in seconds
session = TIMES =
secret: 'keyboard cat' minute: 60
store: new RedisStore hour: 60 * 60
host: 'redis' day: 60 * 60 * 24
port: 6379 week: 60 * 60 * 24 * 7
cookie: month: 60 * 60 * 24 * 7 * 4
httpOnly: true
sameSite: 'strict'
proxy: true
resave: true
saveUninitialized: true
if app.get 'env' == 'production' EXPIRY =
app.set 'trust proxy', 1 # games expire 1 week after last action
session.cookie.secure = true game: 1 * TIMES.week
# logins expire 1 month after last action
login: 1 * TIMES.month
expressSession session
FFTCGSESSION = () ->
@db = redis.createClient 6379, 'redis'
@db.on 'error', (err) ->
logger.error err.message
return
FFTCGSESSION::login = (login) ->
that = @
new Promise (resolve, reject) ->
hmac = crypto.createHmac 'sha256', Math.random().toString()
hmac.update login
digest = hmac.digest 'hex'
that.db.setex digest, EXPIRY.login, login, (err) ->
if err
reject err
else
resolve digest
FFTCGSESSION::action = (digest) ->
that = @
new Promise (resolve, reject) ->
that.db.get digest (err, res) ->
logger.info 'err', err, 'res', res
if err
reject err
else if res == 0
resolve null
else
that.db.expire digest, EXPIRY.login, (err, res) ->
if err
reject err
else
resolve res
module.exports = FFTCGSESSION

View file

@ -1,32 +1,42 @@
# node libraries # node libraries
socketio = (require 'socket.io')
path = (require 'path') path = (require 'path')
logger = (require 'logging').default 'socket' logger = (require 'logging').default 'socket'
# my libraries # my libraries
FFTCGSOCKET = (http, session) -> module.exports = (socket) ->
that = @ logger.info 'Client connected.'
# create server socket socket.on 'message', (msg) ->
@io = socketio http # echo server
@io.use session logger.info "Echo '#{msg}'."
socket.send "Re: #{msg}"
# on new connection socket.on 'close', ->
@io.on 'connection', (socket) -> logger.info 'Client disconnected.'
@session = socket.handshake.session
logger.debug "session '#{@session.id}' connected"
logger.debug "is user '#{@session.userID}'" if @session.userID
socket.on 'disconnect', -> # FFTCGSOCKET = (http, session) ->
logger.debug "session '#{that.session.id}' disconnected" # that = @
logger.debug "is user '#{that.session.userID}'" if that.session.userID #
# # create server socket
return # @io = socketio http
# @io.use session
#
FFTCGSOCKET::close = -> # # on new connection
logger.info 'shutting down' # @io.on 'connection', (socket) ->
# @session = socket.handshake.session
# logger.debug "session '#{@session.id}' connected"
module.exports = FFTCGSOCKET # logger.debug "is user '#{@session.userID}'" if @session.userID
#
# socket.on 'disconnect', ->
# logger.debug "session '#{that.session.id}' disconnected"
# logger.debug "is user '#{that.session.userID}'" if that.session.userID
#
# return
#
#
# FFTCGSOCKET::close = ->
# logger.info 'shutting down'
#
#
# module.exports = FFTCGSOCKET

View file

@ -6,6 +6,7 @@
</head> </head>
<body> <body>
Hello World Hello World
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script type="text/javascript"> <script type="text/javascript">
const host = location.origin.replace(/^http/, 'ws') const host = location.origin.replace(/^http/, 'ws')
const ws = new WebSocket(host) const ws = new WebSocket(host)
@ -16,7 +17,22 @@
ws.send('Ping') // Send the message 'Ping' to the server ws.send('Ping') // Send the message 'Ping' to the server
} }
console.log(JSON.stringify(document.cookie)) axios.post('/user/register',{
login: 'jmm',
password: '123'
})
.then( (response) => {
console.log('register', response)
})
axios.post('/user/login',{
login: 'jmm',
password: '123'
})
.then( (response) => {
console.log('login', response)
console.log('cookie', document.cookie)
})
</script> </script>
</body> </body>
</html> </html>

View file

@ -586,6 +586,11 @@ dot-prop@^4.1.0:
dependencies: dependencies:
is-obj "^1.0.0" is-obj "^1.0.0"
double-ended-queue@^2.1.0-0:
version "2.1.0-0"
resolved "https://registry.yarnpkg.com/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz#103d3527fd31528f40188130c841efdd78264e5c"
integrity sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw=
duplexer3@^0.1.4: duplexer3@^0.1.4:
version "0.1.4" version "0.1.4"
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
@ -1996,6 +2001,25 @@ readdirp@^2.2.1:
micromatch "^3.1.10" micromatch "^3.1.10"
readable-stream "^2.0.2" readable-stream "^2.0.2"
redis-commands@^1.2.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.4.0.tgz#52f9cf99153efcce56a8f86af986bd04e988602f"
integrity sha512-cu8EF+MtkwI4DLIT0x9P8qNTLFhQD4jLfxLR0cCNkeGzs87FN6879JOJwNQR/1zD7aSYNbU0hgsV9zGY71Itvw==
redis-parser@^2.6.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-2.6.0.tgz#52ed09dacac108f1a631c07e9b69941e7a19504b"
integrity sha1-Uu0J2srBCPGmMcB+m2mUHnoZUEs=
redis@^2.8.0:
version "2.8.0"
resolved "https://registry.yarnpkg.com/redis/-/redis-2.8.0.tgz#202288e3f58c49f6079d97af7a10e1303ae14b02"
integrity sha512-M1OkonEQwtRmZv4tEWF2VgpG0JWJ8Fv1PhlgT5+B+uNq2cA3Rt1Yt/ryoR+vQNOQcIEgdCdfH0jr3bDpihAw1A==
dependencies:
double-ended-queue "^2.1.0-0"
redis-commands "^1.2.0"
redis-parser "^2.6.0"
regex-not@^1.0.0, regex-not@^1.0.2: regex-not@^1.0.0, regex-not@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c"