summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--server/api/users.js33
-rw-r--r--server/lib/authentication.js48
-rw-r--r--webapp/src/components/AccountModule.vue148
-rw-r--r--webapp/src/components/StartPageSetup.vue8
-rw-r--r--webapp/src/main.js10
5 files changed, 199 insertions, 48 deletions
diff --git a/server/api/users.js b/server/api/users.js
index dc8df46..1a724ac 100644
--- a/server/api/users.js
+++ b/server/api/users.js
@@ -61,6 +61,39 @@ router.post('/:id/password', (req, res) => {
authentication.changePassword(req, res)
})
+// Post request for chaning the user info. (name, email)
+router.post('/:id', (req, res) => {
+ if (req.params.id !== 'current') {
+ // Check if the user has the permission for chaning those userdata. Else return.
+ return res.status(500).end()
+ }
+ const id = req.params.id === 'current' ? req.user.id : req.params.id
+
+ let email = req.body.email
+ if (!authentication.validateEmail(req.body.email)) return res.status(500).send({ status: 'EMAIL_INVALID', error_message: 'The provided email is invalid.' })
+ db.user.findOne({ where: { id } }).then(user => {
+ user.update({
+ name: req.body.name,
+ email
+ }).then(() => {
+ res.send(200)
+ })
+ })
+})
+
+router.delete('/:id/', (req, res) => {
+ // Check if the user has the permission for chaning those userdata. Else return.
+ if (req.params.id !== 'current') {
+ return res.status(500).end()
+ }
+ const id = req.params.id === 'current' ? req.user.id : req.params.id
+
+ // Every user can delete his own account.
+ db.user.destroy({ where: { id } }).then(() => {
+ res.status(200).end()
+ })
+})
+
// ############################################################################
// ############################################################################
diff --git a/server/lib/authentication.js b/server/lib/authentication.js
index 7b616d4..f412e31 100644
--- a/server/lib/authentication.js
+++ b/server/lib/authentication.js
@@ -6,7 +6,7 @@ var db = require(path.join(__appdir, 'lib', 'sequelize'))
var securePassword = require('secure-password')
var pwd = securePassword()
-module.exports = { loginCookie, loginToken, logout, verifyToken, signup, changePassword }
+module.exports = { loginCookie, loginToken, logout, verifyToken, signup, changePassword, validateEmail }
// Authentifivation method for the frontend using secure httpOnly cookies. (POST)
function loginCookie (req, res) {
@@ -39,7 +39,7 @@ function signup (req, res) {
var params = req.body
if (!params.username) return res.status(500).send({ auth: false, status: 'USER_MISSING', error_message: 'This service requires an username.' })
if (!params.password) return res.status(500).send({ auth: false, status: 'PASSWORD_MISSING', error_message: 'This services requires a password.' })
- if (!params.email) return res.status(500).send({ auth: false, status: 'EMAIL_MISSING', error_message: 'This services requires an email.' })
+ // if (!params.email) return res.status(500).send({ auth: false, status: 'EMAIL_MISSING', error_message: 'This services requires an email.' })
// Database and user validation.
db.user.findOne({ where: { username: params.username } }).then(userDb => {
@@ -121,14 +121,14 @@ function verifyToken (req, res, next) {
} else if (req.cookies.jwt_hp && req.cookies.jwt_s) {
token = req.cookies.jwt_hp + '.' + req.cookies.jwt_s
} else {
- if (res) return res.status(403).send({ auth: false, status: 'TOKEN_MISSING', error_message: 'This service requires a token.' })
+ if (res) return res.status(401).send({ auth: false, status: 'TOKEN_MISSING', error_message: 'This service requires a token.' })
else return next(new Error('TOKEN_MISSING'))
}
// Verify the token with the secret.
jwt.verify(token, config.secret, err => {
if (err) {
- if (res) return res.status(500).send({ auth: false, status: 'TOKEN_INVALID', error_message: 'The provided token is invalid.' })
+ if (res) return res.status(401).send({ auth: false, status: 'TOKEN_INVALID', error_message: 'The provided token is invalid.' })
else return next(new Error('TOKEN_INVALID'))
}
req.token = token
@@ -138,23 +138,36 @@ function verifyToken (req, res, next) {
// Check weather the user exists.
db.user.findOne({ where: { id: req.user.id } }).then(user => {
if (user) next()
- else return res.status(500).send({ auth: false, status: 'TOKEN_INVALID', error_message: 'The token is from an invalid userid.' })
+ else {
+ if (res) return res.status(401).send({ auth: false, status: 'TOKEN_INVALID', error_message: 'The token is from an invalid userid.' })
+ else return next(new Error('TOKEN_INVALID'))
+ }
})
})
}
+// Function for validating the e-mail.
+function validateEmail (email) {
+ // TODO: Remove if email is not optional
+ if (email === '') return true
+
+ // Removed escape before [ because eslint told me so.
+ var re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
+ return re.test(String(email).toLowerCase())
+}
+
// ################################################
// ############## Helper function #################
// ################################################
// The function for verifying a user. Callback only gets called if the user gets verified.
function verifyUser (res, username, password, callback) {
- if (!username) return res.status(500).send({ auth: false, status: 'USER_MISSING', error_message: 'This service requires an username.' })
- if (!password) return res.status(500).send({ auth: false, status: 'PASSWORD_MISSING', error_message: 'This services requires a password.' })
+ if (!username) return res.status(401).send({ auth: false, status: 'USER_MISSING', error_message: 'This service requires an username.' })
+ if (!password) return res.status(401).send({ auth: false, status: 'PASSWORD_MISSING', error_message: 'This services requires a password.' })
db.user.findOne({ where: { username: username } }).then(userDb => {
if (!userDb) {
- return res.status(404).send({ auth: false, status: 'USER_NOTFOUND', error_message: 'User does not exist.' })
+ return res.status(401).send({ auth: false, status: 'USER_NOTFOUND', error_message: 'User does not exist.' })
}
var user = {}
user.id = userDb.id
@@ -164,7 +177,7 @@ function verifyUser (res, username, password, callback) {
// Verify & improving the hash.
verifyHash(res, userPassword, hash, user.id, () => {
jwt.sign({ user }, config.secret, { expiresIn: '12h' }, (err, token) => {
- if (err) return res.status(500).send({ auth: false, status: 'JWT_ERROR', error_message: 'Jwt sign failed.' })
+ if (err) return res.status(401).send({ auth: false, status: 'JWT_ERROR', error_message: 'Jwt sign failed.' })
return callback(token)
})
})
@@ -175,17 +188,17 @@ function verifyUser (res, username, password, callback) {
function verifyHash (res, password, hash, userId, callback) {
// Check if the hash in the database fullfills the requirements needed for pwd.verify.
// Hash will be a Buffer of length SecurePassword.HASH_BYTES.
- if (hash.length !== securePassword.HASH_BYTES) return res.status(500).send({ auth: false, status: 'DATABASE_HASH_INVALID', error_message: 'The hash in the database is corrupted.' })
+ if (hash.length !== securePassword.HASH_BYTES) return res.status(401).send({ auth: false, status: 'DATABASE_HASH_INVALID', error_message: 'The hash in the database is corrupted.' })
// Password must be a Buffer of length SecurePassword.PASSWORD_BYTES_MIN - SecurePassword.PASSWORD_BYTES_MAX.
- if (password.length < securePassword.PASSWORD_BYTES_MIN || password.length > securePassword.PASSWORD_BYTES_MAX) return res.status(500).send({ auth: false, status: 'PASSWORD_INVALID', error_message: 'The provided password has an invalid length.' })
+ if (password.length < securePassword.PASSWORD_BYTES_MIN || password.length > securePassword.PASSWORD_BYTES_MAX) return res.status(401).send({ auth: false, status: 'PASSWORD_INVALID', error_message: 'The provided password has an invalid length.' })
// Verification of the password. Rehash if needed.
pwd.verify(password, hash, function (err, result) {
- if (err) return res.status(500).send({ auth: false, status: 'PASSWORD_VERIFY_ERROR', error_message: 'Verifying the password failed.' })
+ if (err) return res.status(401).send({ auth: false, status: 'PASSWORD_VERIFY_ERROR', error_message: 'Verifying the password failed.' })
// Check the state of the verification.
- if (result === securePassword.INVALID_UNRECOGNIZED_HASH) return res.status(500).send({ auth: false, status: 'INVALID_UNRECOGNIZED_HASH', error_message: 'This hash was not made with secure-password. Attempt legacy algorithm.' })
- if (result === securePassword.INVALID) return res.status(500).send({ auth: false, status: 'PASSWORD_INVALID', error_message: 'The provided password is invalid.' })
+ if (result === securePassword.INVALID_UNRECOGNIZED_HASH) return res.status(401).send({ auth: false, status: 'INVALID_UNRECOGNIZED_HASH', error_message: 'This hash was not made with secure-password. Attempt legacy algorithm.' })
+ if (result === securePassword.INVALID) return res.status(401).send({ auth: false, status: 'PASSWORD_INVALID', error_message: 'The provided password is invalid.' })
if (result === securePassword.VALID) callback()
if (result === securePassword.VALID_NEEDS_REHASH) {
pwd.hash(password, function (err, improvedHash) {
@@ -203,13 +216,6 @@ function verifyHash (res, password, hash, userId, callback) {
})
}
-// Function for validating the e-mail.
-function validateEmail (email) {
-// Removed escape before [ because eslint told me so.
- var re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
- return re.test(String(email).toLowerCase())
-}
-
// Function for validating the password. Password requirements are implemented here.
function validatePassword (password) {
// TODO: implement pw requirements like in the frontend. (SetupPage)
diff --git a/webapp/src/components/AccountModule.vue b/webapp/src/components/AccountModule.vue
index 6844afa..0b3839b 100644
--- a/webapp/src/components/AccountModule.vue
+++ b/webapp/src/components/AccountModule.vue
@@ -2,8 +2,12 @@
{
"en": {
"accountInfo": "Info",
- "name": "Name",
+ "deleteAccount": "Delete Account",
+ "deleteAccountMsg": "Are you sure you want to permnanently delete your account?",
+ "deleteAccountMsg2": "This action cannot be undone",
"email": "E-Mail",
+ "emailError": "E-mail must be valid.",
+ "name": "Name",
"password": "Password",
"passwordConfirm": "Retype password",
"passwordChanged": "Password successfully changed.",
@@ -21,12 +25,17 @@
"passwordRequirement5": "0+ [!\"§$%&/()=?+#*.:,;]",
"passwordRequirements": "Password requirements",
"security": "Security",
- "username": "Username"
+ "username": "Username",
+ "userInfoChanged": "User informations successfully changed."
},
"de": {
"accountInfo": "Info",
- "name": "Name",
+ "deleteAccount": "Account Löschen",
+ "deleteAccountMsg": "Sind sie sicher, dass sie ihren Account entgültig Löschen wollen?",
+ "deleteAccountMsg2": "Diese Aktion kann nicht rückgängig gemacht werden.",
"email": "E-Mail",
+ "emailError": "Keine gültige E-Mail.",
+ "name": "Name",
"password": "Passwort",
"passwordChanged": "Passwort wurde erfolgreich geändert.",
"passwordConfirm": "Passwort wiederholen",
@@ -44,7 +53,8 @@
"passwordRequirement5": "0+ [!\"§$%&/()=?+#*'-_.:,;]",
"passwordRequirements": "Passwort anforderungen",
"security": "Sicherheit",
- "username": "Benutzername"
+ "username": "Benutzername",
+ "userInfoChanged": "Benutzer Informationen wurden erfolgreich geändert"
}
}
</i18n>
@@ -70,7 +80,8 @@
<v-card>
<v-card-text>
<v-layout wrap>
- <v-flex lg4 md6 sm6 xs12 order-lg1 order-xs1>
+
+ <v-flex lg3 md6 sm6 xs12 order-lg2 order-xs2>
<v-layout column>
<div class="info-input">
<div class="body-2 info-heading"><v-icon>account_circle</v-icon><span>{{ $t('username') }}</span></div>
@@ -78,22 +89,43 @@
</div>
</v-layout>
</v-flex>
- <v-flex lg4 md6 sm6 xs12 order-lg2 order-xs2>
+
+ <v-flex lg3 md6 sm6 xs12 order-lg4 order-xs4>
<v-layout column>
<div class="info-input">
<div class="body-2 info-heading"><v-icon>label</v-icon><span>{{ $t('name') }}</span></div>
- <div class="info-text">{{ user.name }}</div>
+ <div v-if="!editInfoMode" class="info-text">{{ user.name }}</div>
+ <v-text-field v-else v-model="infoName"></v-text-field>
</div>
</v-layout>
</v-flex>
- <v-flex lg4 md6 sm6 xs12 order-lg2 order-xs3>
+
+ <v-flex lg3 md6 sm6 xs12 order-lg6 order-xs6>
<v-layout column>
<div class="info-input">
<div class="body-2 info-heading"><v-icon>email</v-icon><span>{{ $t('email') }}</span></div>
- <div class="info-text">{{ user.email }}</div>
+ <div v-if="!editInfoMode" class="info-text">{{ user.email }}</div>
+ <v-text-field v-else v-model="infoEmail" :rules="emailRules" ref="emailField" validate-on-blur></v-text-field>
</div>
</v-layout>
</v-flex>
+
+ <v-flex lg3 md6 sm6 xs12 order-lg7 order-md3 order-sm3 order-xs1 class="text-xs-right">
+ <div class="info-input">
+ <div v-if="!editInfoMode">
+ <v-btn color="primary" flat @click="editInfo" class="info-buttons">
+ <v-icon left>create</v-icon>{{ $t('edit') }}
+ </v-btn>
+ </div>
+ <div v-else>
+ <v-btn color="primary" flat @click="cancelEditInfo" class="info-buttons">{{ $t('cancel') }}</v-btn>
+ <v-btn color="primary" @click="submitInfo" class="info-buttons">
+ <v-icon left>save</v-icon>{{ $t('save') }}
+ </v-btn>
+ </div>
+ </div>
+ </v-flex>
+
</v-layout>
</v-card-text>
</v-card>
@@ -109,7 +141,7 @@
<div class="body-2 info-heading"><v-icon>lock</v-icon><span>{{ $t('password') }}</span></div>
<div v-if="!editPasswordMode" class="info-text">********</div>
<div v-else>
- <v-form class="password-form" ref="passwordForm" v-model="valid" lazy-validation>
+ <v-form ref="passwordForm" v-model="valid" lazy-validation>
<v-text-field
type="password"
v-model="passwordCurrent"
@@ -137,9 +169,9 @@
</v-layout>
</v-flex>
- <v-flex lg4 md6 sm6 xs12 order-lg1 order-xs2>
+ <v-flex lg4 md6 sm6 xs12 order-lg1 order-xs3 v-if="editPasswordMode">
<v-layout column>
- <div class="info-input" v-if="editPasswordMode">
+ <div class="info-input">
<div class="body-2 info-heading"><v-icon>ballot</v-icon><span>{{ $t('passwordRequirements') }}</span></div>
<div class="info-text">{{ $t('passwordRequirement1') }}</div>
<div class="info-text">{{ $t('passwordRequirement2') }}</div>
@@ -150,17 +182,20 @@
</v-layout>
</v-flex>
- <v-flex lg4 xs12 order-lg3 order-xs1 class="text-xs-right">
+ <v-flex v-if="!editPasswordMode" lg8 sm6 xs12 order-lg3 order-sm2 order-xs1 class="text-xs-right">
<div class="info-input">
- <v-btn v-if="!editPasswordMode" color="primary" flat @click="editPassword" class="info-buttons">
+ <v-btn color="primary" flat @click="editPassword" class="info-buttons">
<v-icon left>create</v-icon>{{ $t('edit') }}
</v-btn>
- <div v-else>
+ </div>
+ </v-flex>
+
+ <v-flex v-else lg4 sm12 xs12 order-lg3 order-sm1 order-xs1 class="text-xs-right">
+ <div class="info-input">
<v-btn color="primary" flat @click="cancelEditPassword" class="info-buttons">{{ $t('cancel') }}</v-btn>
<v-btn color="primary" @click="submitPassword" class="info-buttons">
<v-icon left>save</v-icon>{{ $t('save') }}
</v-btn>
- </div>
</div>
</v-flex>
@@ -175,12 +210,42 @@
</div>
</v-card>
+ <v-subheader>{{ $t('delete') }}</v-subheader>
+ <v-card>
+ <div style="display: flex; justify-content: center;">
+ <v-btn color="error" class="button" @click="dialog = true"><v-icon left>delete</v-icon>{{ $t('deleteAccount') }}</v-btn>
+ </div>
+ </v-card>
+
</v-tab-item>
</v-tabs-items>
</v-flex>
</v-layout>
- </v-container>
+ <v-dialog
+ :value="dialog"
+ scrollable
+ max-width="500px"
+ :fullscreen="$vuetify.breakpoint.xsOnly"
+ >
+ <v-card>
+ <v-card-title primary-title class="elevation-3">
+ <div class="headline">{{ $t('deleteAccount') }}</div>
+ </v-card-title>
+ <v-card-text>
+ <div>{{ $t('deleteAccountMsg') }}</div>
+ <span class="grey--text">{{ $t('deleteAccountMsg2') }}</span>
+ </v-card-text>
+ <v-divider></v-divider>
+ <v-card-actions>
+ <v-spacer></v-spacer>
+ <v-btn flat="flat" @click="dialog = false">{{ $t('cancel') }}</v-btn>
+ <v-btn color="error" @click="deleteAccount">{{ $t('delete') }}</v-btn>
+ </v-card-actions>
+ </v-card>
+ </v-dialog>
+
+ </v-container>
</template>
<script>
@@ -190,8 +255,12 @@ export default {
data () {
return {
tab: 0,
+ dialog: false,
valid: true,
user: '',
+ editInfoMode: false,
+ infoName: '',
+ infoEmail: '',
passwordConfirm: '',
passwordCurrent: '',
passwordNew: '',
@@ -201,6 +270,9 @@ export default {
v => !!v || this.$t('passwordEmptyError'),
v => !this.invalidPasswordError || this.$t('passwordInvalidError')
],
+ emailRules: [
+ v => this.$validateEmail(v) || this.$t('emailError')
+ ],
passwordRules: [
v => !!v || this.$t('passwordEmptyError'),
v => v.length >= 8 || this.$t('passwordLengthError'),
@@ -216,9 +288,28 @@ export default {
...mapGetters(['tabsDark', 'tabsColor', 'tabsSliderColor'])
},
methods: {
+ deleteAccount () {
+ this.dialog = false
+ this.$http.delete('/api/users/current').then(response => {
+ this.$http.post('/api/authentication/logout').then(response => {
+ this.$router.push('/login')
+ this.$socket.close()
+ })
+ })
+ },
clearCurrentPasswordErrors () {
this.invalidPasswordError = false
},
+ submitInfo () {
+ if (!this.$refs.emailField.validate()) return
+
+ // Axios request to submit info (username, name, email)
+ this.$http.post('/api/users/current', { name: this.infoName, email: this.infoEmail }).then(response => {
+ this.editInfoMode = false
+ this.getUserData()
+ this.$snackbar({ color: 'success', text: this.$t('userInfoChanged') })
+ })
+ },
submitPassword () {
if (this.$refs.passwordForm.validate()) {
this.$http.post('/api/users/' + this.user.id + '/password', { passwordCurrent: this.passwordCurrent, passwordNew: this.passwordNew }).then(response => {
@@ -234,30 +325,47 @@ export default {
})
}
},
+ editInfo () {
+ this.infoName = this.user.name
+ this.infoEmail = this.user.email
+ this.editInfoMode = true
+ },
editPassword () {
this.editPasswordMode = true
},
+ cancelEditInfo () {
+ this.editInfoMode = false
+ },
cancelEditPassword () {
this.editPasswordMode = false
this.passwordCurrent = ''
this.passwordNew = ''
this.passwordConfirm = ''
},
+ getUserData () {
+ this.$http('/api/users/current').then(response => {
+ this.user = response.data
+ })
+ },
newAlert () {
this.$alert({ type: 'success', text: 'aaaaaaaaaaaaaaaaaaaaaaaaaas das dsad asdpioipoidijoijoawiojdiojijowaijo d o wiadijo oiawio jdi aaaaaaaaaaaaaaaaaaaaaa uo iashdoiuas dhuas hduioash diuash diuash diuash diuh test ' + this.testId })
this.testId++
}
},
created () {
- this.$http('/api/users/current').then(response => {
- this.user = response.data
- })
+ this.getUserData()
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
+.button {
+ margin-top: 20px;
+ margin-bottom: 20px;
+ width: 95%;
+}
+
.info-buttons {
margin: 0;
}
diff --git a/webapp/src/components/StartPageSetup.vue b/webapp/src/components/StartPageSetup.vue
index 71ead8f..32dd1a3 100644
--- a/webapp/src/components/StartPageSetup.vue
+++ b/webapp/src/components/StartPageSetup.vue
@@ -104,18 +104,12 @@ export default {
confirmPasswordError: false,
email: '',
emailRules: [
- v => this.validateEmail(v) || this.$t('emailError')
+ v => this.$validateEmail(v) || this.$t('emailError')
],
name: ''
}
},
methods: {
- // Function for validating the e-mail.
- validateEmail (email) {
- // Removed escape before [ because eslint told me so.
- var re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
- return re.test(String(email).toLowerCase())
- },
setup () {
if (this.$refs.form.validate()) {
this.$http.post('/api/authentication/setup', { username: this.username, password: this.password, name: this.name, email: this.email }).then(response => {
diff --git a/webapp/src/main.js b/webapp/src/main.js
index 6ed1d53..2665c0a 100644
--- a/webapp/src/main.js
+++ b/webapp/src/main.js
@@ -117,6 +117,16 @@ Vue.prototype.$loop = function (count, chunksize, iteration, callback = () => {}
return loop
}
+// Function for validating an email address.
+Vue.prototype.$validateEmail = email => {
+ // TODO: Remove if email is not optional
+ if (email === '') return true
+
+ // Removed escape before [ because eslint told me so.
+ var re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
+ return re.test(String(email).toLowerCase())
+}
+
new Vue({
store,
router,