Compare commits
No commits in common. "366339fc9a511af0061fb6f6320197fb3c751b66" and "1505667e1eab619f012610bc8b292c87412595cb" have entirely different histories.
366339fc9a
...
1505667e1e
10 changed files with 92 additions and 167 deletions
|
|
@ -7,14 +7,6 @@ sqlite3 = (require 'sqlite3').verbose()
|
||||||
# bruteforce countermeasure
|
# bruteforce countermeasure
|
||||||
saltRounds = 13
|
saltRounds = 13
|
||||||
|
|
||||||
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'
|
|
||||||
|
|
||||||
class FFTCGDB
|
class FFTCGDB
|
||||||
constructor: (filename, truncate) ->
|
constructor: (filename, truncate) ->
|
||||||
@filename = filename
|
@filename = filename
|
||||||
|
|
@ -64,91 +56,74 @@ class FFTCGDB
|
||||||
@db.close (err) ->
|
@db.close (err) ->
|
||||||
if err
|
if err
|
||||||
logger.error "FAIL '#{err.message}'"
|
logger.error "FAIL '#{err.message}'"
|
||||||
reject null
|
reject 'db'
|
||||||
else
|
else
|
||||||
logger.warn "OK close '#{@filename}'"
|
logger.warn "OK close '#{@filename}'"
|
||||||
resolve null
|
resolve 'ok'
|
||||||
|
|
||||||
validate: (login, password) ->
|
|
||||||
defined = (value) -> value? and value isnt ''
|
|
||||||
|
|
||||||
new Promise (resolve, reject) =>
|
|
||||||
if (defined login) and (defined password)
|
|
||||||
# both are defined
|
|
||||||
resolve null
|
|
||||||
else
|
|
||||||
# no user name or password given
|
|
||||||
logger.info "validate: FAIL empty '#{login}' or password"
|
|
||||||
reject null
|
|
||||||
|
|
||||||
register: (login, password) ->
|
register: (login, password) ->
|
||||||
new Promise (resolve, reject) =>
|
new Promise (resolve, reject) =>
|
||||||
# validate user input
|
# validate user input
|
||||||
@validate login, password
|
if login == '' or password == ''
|
||||||
.then =>
|
# no user name or password given
|
||||||
# hash password
|
logger.info "reg: FAIL empty '#{login}' or password"
|
||||||
bcrypt.hash password, saltRounds, (err, hash) =>
|
reject 'invalid'
|
||||||
if err
|
|
||||||
logger.warn "reg: FAIL hash for '#{login}'"
|
|
||||||
reject messages.hash
|
|
||||||
|
|
||||||
else
|
# hash password
|
||||||
# try creating row in users table
|
bcrypt.hash password, saltRounds, (err, hash) =>
|
||||||
stmt = @db.prepare 'INSERT INTO users (login, pwdhash) VALUES (?, ?)'
|
if err
|
||||||
stmt.run [login, hash], (err) ->
|
logger.warn "reg: FAIL hash for '#{login}'"
|
||||||
stmt.finalize()
|
reject 'hash'
|
||||||
if err
|
|
||||||
logger.warn "reg: FAIL db '#{err.code}' for '#{login}'"
|
|
||||||
reject messages.exists # user already exists
|
|
||||||
|
|
||||||
else
|
else
|
||||||
logger.info "reg: OK '#{login}'"
|
# try creating row in users table
|
||||||
# registration successful
|
stmt = @db.prepare 'INSERT INTO users (login, pwdhash) VALUES (?, ?)'
|
||||||
resolve
|
stmt.run [login, hash], (err) ->
|
||||||
user: @lastID
|
stmt.finalize()
|
||||||
login: login
|
if err
|
||||||
|
logger.warn "reg: FAIL db '#{err.code}' for '#{login}'"
|
||||||
|
reject 'existence' # user already exists
|
||||||
|
|
||||||
.catch ->
|
else
|
||||||
reject messages.empty
|
logger.info "reg: OK '#{login}'"
|
||||||
|
# registration successful
|
||||||
|
resolve
|
||||||
|
user: @lastID
|
||||||
|
login: login
|
||||||
|
|
||||||
login: (login, password) ->
|
login: (login, password) ->
|
||||||
new Promise (resolve, reject) =>
|
new Promise (resolve, reject) =>
|
||||||
# validate user input
|
# get users table row
|
||||||
@validate login, password
|
stmt = @db.prepare 'SELECT user, login, pwdhash FROM users WHERE login = ?'
|
||||||
.then =>
|
stmt.get [login], (err, row) =>
|
||||||
# get users table row
|
stmt.finalize()
|
||||||
stmt = @db.prepare 'SELECT user, login, pwdhash FROM users WHERE login = ?'
|
if err
|
||||||
stmt.get [login], (err, row) =>
|
logger.warn "login: FAIL db '#{err.code}' for '#{login}'"
|
||||||
stmt.finalize()
|
reject 'db'
|
||||||
if err
|
|
||||||
logger.warn "login: FAIL db '#{err.code}' for '#{login}'"
|
|
||||||
reject messages.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) ->
|
||||||
logger.debug "login: FAIL nonexistent '#{login}'"
|
logger.debug "login: FAIL nonexistent '#{login}'"
|
||||||
reject messages.noexists # user doesnt exist
|
reject 'existence' # user doesnt exist
|
||||||
|
|
||||||
else
|
else
|
||||||
bcrypt.compare password, row.pwdhash, (err, res) ->
|
bcrypt.compare password, row.pwdhash, (err, res) ->
|
||||||
if err
|
if err
|
||||||
logger.warn "login: FAIL hash for '#{login}'"
|
logger.warn "login: FAIL hash for '#{login}'"
|
||||||
reject messages.hash
|
reject 'hash'
|
||||||
|
|
||||||
if res == true
|
if res == true
|
||||||
logger.debug "login: OK '#{row.login}'"
|
logger.debug "login: OK '#{row.login}'"
|
||||||
# login successful
|
# login successful
|
||||||
resolve
|
resolve
|
||||||
user: row.user
|
user: row.user
|
||||||
login: row.login
|
login: row.login
|
||||||
|
|
||||||
else
|
else
|
||||||
logger.debug "login: FAIL password for '#{login}'"
|
logger.debug "login: FAIL password for '#{login}'"
|
||||||
reject messages.password # login failed
|
# login failed
|
||||||
|
reject 'login'
|
||||||
.catch ->
|
|
||||||
reject messages.empty
|
|
||||||
|
|
||||||
addDeck: (user, deckCards) ->
|
addDeck: (user, deckCards) ->
|
||||||
new Promise (resolve, reject) =>
|
new Promise (resolve, reject) =>
|
||||||
|
|
@ -158,7 +133,7 @@ class FFTCGDB
|
||||||
stmt.finalize()
|
stmt.finalize()
|
||||||
if err
|
if err
|
||||||
logger.warn "addDeck: FAIL db '#{err.code}' for '#{user}'"
|
logger.warn "addDeck: FAIL db '#{err.code}' for '#{user}'"
|
||||||
reject messages.db
|
reject 'db'
|
||||||
|
|
||||||
else
|
else
|
||||||
logger.debug "addDeck: OK '#{@lastID}'"
|
logger.debug "addDeck: OK '#{@lastID}'"
|
||||||
|
|
@ -171,7 +146,7 @@ class FFTCGDB
|
||||||
stmt.finalize()
|
stmt.finalize()
|
||||||
if err
|
if err
|
||||||
logger.warn "modDeck: FAIL db '#{err.code}' for '#{deckID}'"
|
logger.warn "modDeck: FAIL db '#{err.code}' for '#{deckID}'"
|
||||||
reject messages.db
|
reject 'db'
|
||||||
else
|
else
|
||||||
logger.debug "modDeck: OK '#{deckID}'"
|
logger.debug "modDeck: OK '#{deckID}'"
|
||||||
resolve deckID
|
resolve deckID
|
||||||
|
|
@ -183,7 +158,7 @@ class FFTCGDB
|
||||||
stmt.finalize()
|
stmt.finalize()
|
||||||
if err
|
if err
|
||||||
logger.warn "getDeck: FAIL db '#{err.code}' for '#{deckID}'"
|
logger.warn "getDeck: FAIL db '#{err.code}' for '#{deckID}'"
|
||||||
reject messages.db
|
reject 'db'
|
||||||
else
|
else
|
||||||
logger.debug "getDeck: OK '#{deckID}'"
|
logger.debug "getDeck: OK '#{deckID}'"
|
||||||
resolve (id: row.deck, content: JSON.parse row.json for row, i in rows)
|
resolve (id: row.deck, content: JSON.parse row.json for row, i in rows)
|
||||||
|
|
@ -195,7 +170,7 @@ class FFTCGDB
|
||||||
stmt.finalize()
|
stmt.finalize()
|
||||||
if err
|
if err
|
||||||
logger.warn "delDeck: FAIL db '#{err.code}' for '#{deckID}'"
|
logger.warn "delDeck: FAIL db '#{err.code}' for '#{deckID}'"
|
||||||
reject messages.db
|
reject 'db'
|
||||||
else
|
else
|
||||||
logger.debug "delDeck: OK '#{deckID}'"
|
logger.debug "delDeck: OK '#{deckID}'"
|
||||||
resolve deckID
|
resolve deckID
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,8 @@ module.exports =
|
||||||
# login successful: start new session
|
# login successful: start new session
|
||||||
logger.info "OK '#{request.body.login}'"
|
logger.info "OK '#{request.body.login}'"
|
||||||
session.start user
|
session.start user
|
||||||
.then (cookie_data) ->
|
.then (session_id) ->
|
||||||
resolve cookie_data
|
resolve session_id
|
||||||
|
|
||||||
.catch (err) ->
|
.catch (err) ->
|
||||||
# login failed
|
# login failed
|
||||||
|
|
@ -34,8 +34,8 @@ module.exports =
|
||||||
success: false
|
success: false
|
||||||
message: err
|
message: err
|
||||||
|
|
||||||
.then (cookie_data) ->
|
.then (session_id) ->
|
||||||
# login or resume successful
|
# login or resume successful
|
||||||
reply.send
|
reply.send
|
||||||
success: true
|
success: true
|
||||||
message: JSON.stringify cookie_data
|
message: session_id
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ logger = (require 'logging').default 'logout'
|
||||||
|
|
||||||
# session storage (volatile data)
|
# session storage (volatile data)
|
||||||
session = (require '../../session')
|
session = (require '../../session')
|
||||||
|
# fftcg.db (persistent data)
|
||||||
|
fftcgdb = (require '../../db')
|
||||||
|
|
||||||
module.exports =
|
module.exports =
|
||||||
url: '/user/logout'
|
url: '/user/logout'
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,12 @@ redis = (require 'redis')
|
||||||
crypto = (require 'crypto')
|
crypto = (require 'crypto')
|
||||||
logger = (require 'logging').default 'session'
|
logger = (require 'logging').default 'session'
|
||||||
|
|
||||||
# expiry times in days
|
# expiry times in seconds
|
||||||
EXPIRY =
|
EXPIRY =
|
||||||
# games expire 1 week after creation
|
# games expire 1 week after creation
|
||||||
game: 7
|
game: 1 * 60 * 60 * 24 * 7
|
||||||
# logins expire 1 month after last action
|
# logins expire 1 month after last action
|
||||||
login: 30
|
login: 1 * 60 * 60 * 24 * 30
|
||||||
|
|
||||||
|
|
||||||
class FFTCGSESSION
|
class FFTCGSESSION
|
||||||
|
|
@ -29,13 +29,9 @@ class FFTCGSESSION
|
||||||
logger.debug 'digest', digest
|
logger.debug 'digest', digest
|
||||||
|
|
||||||
# push (hash, data) into DB for the configured timespan
|
# push (hash, data) into DB for the configured timespan
|
||||||
@db.setex digest, EXPIRY.login * 86400, (JSON.stringify data), (err) ->
|
@db.setex digest, EXPIRY.login, (JSON.stringify data), (err) ->
|
||||||
logger.info "OK '#{digest}' created"
|
logger.info "OK '#{digest}' created"
|
||||||
# return cookie data
|
resolve digest
|
||||||
resolve
|
|
||||||
value: digest
|
|
||||||
properties:
|
|
||||||
expires: EXPIRY.login
|
|
||||||
|
|
||||||
destroy: (digest) ->
|
destroy: (digest) ->
|
||||||
new Promise (resolve, reject) =>
|
new Promise (resolve, reject) =>
|
||||||
|
|
@ -50,7 +46,7 @@ class FFTCGSESSION
|
||||||
check: (digest) ->
|
check: (digest) ->
|
||||||
new Promise (resolve, reject) =>
|
new Promise (resolve, reject) =>
|
||||||
# refresh expiry timer on digest
|
# refresh expiry timer on digest
|
||||||
@db.expire digest, EXPIRY.login * 86400, (err, res) =>
|
@db.expire digest, EXPIRY.login, (err, res) =>
|
||||||
if res == 0
|
if res == 0
|
||||||
reject null
|
reject null
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
<template>
|
|
||||||
<v-flex mb-4>
|
|
||||||
<h1 class="display-2 font-weight-bold mb-3">
|
|
||||||
Hello World!
|
|
||||||
</h1>
|
|
||||||
<p class="subheading font-weight-regular">
|
|
||||||
App under development, please don't submit any valuable data!
|
|
||||||
</p>
|
|
||||||
</v-flex>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: "Header"
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
@ -5,13 +5,6 @@
|
||||||
</v-btn>
|
</v-btn>
|
||||||
|
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-snackbar v-model="snackbar.visible" :timeout="6000" :color="snackbar.color" absolute top>
|
|
||||||
{{ snackbar.text }}
|
|
||||||
<v-btn @click.native="snackbar.visible = false" fab flat icon>
|
|
||||||
<v-icon>close</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</v-snackbar>
|
|
||||||
|
|
||||||
<v-form
|
<v-form
|
||||||
ref="form"
|
ref="form"
|
||||||
v-model="valid"
|
v-model="valid"
|
||||||
|
|
@ -30,7 +23,7 @@
|
||||||
</v-btn>
|
</v-btn>
|
||||||
|
|
||||||
<v-btn color="error" @click.native="dialog = false">
|
<v-btn color="error" @click.native="dialog = false">
|
||||||
Close
|
Cancel
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-form>
|
</v-form>
|
||||||
|
|
@ -43,12 +36,7 @@ export default {
|
||||||
name: 'FormDialog',
|
name: 'FormDialog',
|
||||||
data: () => ({
|
data: () => ({
|
||||||
dialog: false,
|
dialog: false,
|
||||||
valid: true,
|
valid: true
|
||||||
snackbar: {
|
|
||||||
visible: false,
|
|
||||||
color: '',
|
|
||||||
text: ''
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
|
|
@ -58,20 +46,8 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
validate() {
|
validate() {
|
||||||
if (this.$refs.form.validate()) {
|
if (this.$refs.form.validate()) {
|
||||||
this.$emit('validated')
|
this.$emit('confirm')
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
showSnackbar(text, color) {
|
|
||||||
if (text == '') return
|
|
||||||
|
|
||||||
this.snackbar.visible = false
|
|
||||||
|
|
||||||
window.setTimeout(() => {
|
|
||||||
this.snackbar.text = text
|
|
||||||
this.snackbar.color = color
|
|
||||||
this.snackbar.visible = true
|
|
||||||
}, 100)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<FormDialog ref="main" buttonText="Login" @validated="doLogin">
|
<FormDialog buttonText="Login" @confirm="doLogin">
|
||||||
<v-card-title class="headline">
|
<v-card-title class="headline">
|
||||||
Log In
|
Log In
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
|
|
@ -8,14 +8,12 @@
|
||||||
<v-text-field
|
<v-text-field
|
||||||
ref="autofocus"
|
ref="autofocus"
|
||||||
v-model="login"
|
v-model="login"
|
||||||
:rules="loginRules"
|
|
||||||
label="User name"
|
label="User name"
|
||||||
required
|
required
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
|
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="password"
|
v-model="password"
|
||||||
:rules="passwordRules"
|
|
||||||
:append-icon="showPassword ? 'visibility' : 'visibility_off'"
|
:append-icon="showPassword ? 'visibility' : 'visibility_off'"
|
||||||
@click:append="showPassword = !showPassword"
|
@click:append="showPassword = !showPassword"
|
||||||
:type="showPassword ? 'text' : 'password'"
|
:type="showPassword ? 'text' : 'password'"
|
||||||
|
|
@ -40,11 +38,8 @@ export default {
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
login: '',
|
login: '',
|
||||||
loginRules: [v => !!v || 'Please enter user name'],
|
|
||||||
|
|
||||||
password: '',
|
password: '',
|
||||||
showPassword: false,
|
showPassword: false
|
||||||
passwordRules: [v => !!v || 'Please enter password'],
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -56,14 +51,12 @@ export default {
|
||||||
password: this.password
|
password: this.password
|
||||||
})
|
})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
|
// this.$refs.form.reset()
|
||||||
console.log('login', response.data)
|
console.log('login', response.data)
|
||||||
if (response.data.success) {
|
if (response.data.success) {
|
||||||
let cookie_data = JSON.parse(response.data.message)
|
Cookies.set('session', response.data.message, { expires: 30 })
|
||||||
Cookies.set('session', cookie_data.value, cookie_data.properties)
|
console.log('cookie', Cookies.get())
|
||||||
this.$refs.main.showSnackbar("Login successful!", 'success')
|
|
||||||
this.$router.push('about')
|
this.$router.push('about')
|
||||||
} else {
|
|
||||||
this.$refs.main.showSnackbar(response.data.message, 'error')
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<FormDialog ref="main" buttonText="Register" @validated="doRegister">
|
<FormDialog buttonText="Register" @confirm="doRegister">
|
||||||
<v-card-title class="headline">
|
<v-card-title class="headline">
|
||||||
Register
|
Register
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
|
|
@ -75,12 +75,8 @@ export default {
|
||||||
password: this.password
|
password: this.password
|
||||||
})
|
})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
|
// this.$refs.form.reset()
|
||||||
console.log('register', response.data)
|
console.log('register', response.data)
|
||||||
if (response.data.success) {
|
|
||||||
this.$refs.main.showSnackbar("Registration successful!", 'success')
|
|
||||||
} else {
|
|
||||||
this.$refs.main.showSnackbar(response.data.message, 'error')
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<v-container>
|
<v-container>
|
||||||
<Header />
|
<v-flex mb-4>
|
||||||
|
<h1 class="display-2 font-weight-bold mb-3">Hello World!</h1>
|
||||||
|
<p class="subheading font-weight-regular">
|
||||||
|
App under development, please don't submit any valuable data!
|
||||||
|
</p>
|
||||||
|
</v-flex>
|
||||||
<p>user session: {{ sessionID }}</p>
|
<p>user session: {{ sessionID }}</p>
|
||||||
<v-btn @click.native="logout">Logout</v-btn>
|
<v-btn @click.native="logout">Logout</v-btn>
|
||||||
</v-container>
|
</v-container>
|
||||||
|
|
@ -11,15 +15,9 @@
|
||||||
import * as Cookies from 'js-cookie'
|
import * as Cookies from 'js-cookie'
|
||||||
import axios from '@/plugins/axios'
|
import axios from '@/plugins/axios'
|
||||||
|
|
||||||
import Header from '@/components/Header.vue'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'About',
|
name: 'About',
|
||||||
|
|
||||||
components: {
|
|
||||||
Header
|
|
||||||
},
|
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
sessionID: ''
|
sessionID: ''
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<v-container>
|
<v-container>
|
||||||
<Header />
|
<v-flex mb-4>
|
||||||
|
<h1 class="display-2 font-weight-bold mb-3">
|
||||||
|
Hello World!
|
||||||
|
</h1>
|
||||||
|
<p class="subheading font-weight-regular">
|
||||||
|
App under development, please don't submit any valuable data!
|
||||||
|
</p>
|
||||||
|
</v-flex>
|
||||||
|
|
||||||
<LoginForm />
|
<LoginForm />
|
||||||
<RegisterForm />
|
<RegisterForm />
|
||||||
|
|
@ -11,7 +18,6 @@
|
||||||
import * as Cookies from 'js-cookie'
|
import * as Cookies from 'js-cookie'
|
||||||
import axios from '@/plugins/axios'
|
import axios from '@/plugins/axios'
|
||||||
|
|
||||||
import Header from '@/components/Header.vue'
|
|
||||||
import LoginForm from '@/components/forms/Login.vue'
|
import LoginForm from '@/components/forms/Login.vue'
|
||||||
import RegisterForm from '@/components/forms/Register.vue'
|
import RegisterForm from '@/components/forms/Register.vue'
|
||||||
|
|
||||||
|
|
@ -25,7 +31,6 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
Header,
|
|
||||||
LoginForm,
|
LoginForm,
|
||||||
RegisterForm
|
RegisterForm
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Reference in a new issue