summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--server/api/users.js7
-rw-r--r--server/lib/authentication.js36
-rw-r--r--webapp/src/assets/styles.css2
-rw-r--r--webapp/src/components/AccountModule.vue244
-rw-r--r--webapp/src/components/IpxeBuilderModule.vue2
-rw-r--r--webapp/src/components/StartPageLogin.vue2
6 files changed, 277 insertions, 16 deletions
diff --git a/server/api/users.js b/server/api/users.js
index d7c290a..dc8df46 100644
--- a/server/api/users.js
+++ b/server/api/users.js
@@ -24,7 +24,10 @@ router.getAsync('/:id', async (req, res) => {
const id = req.params.id === 'current' ? req.user.id : req.params.id
const user = await db.user.findOne({ where: { id } })
if (user) {
- res.status(200).send(user)
+ // Remove the hased password.
+ let u = user.dataValues
+ delete u.password
+ res.status(200).send(u)
} else {
res.status(404).end()
}
@@ -55,7 +58,7 @@ router.post('/', (req, res) => {
// Post request for changing the password.
router.post('/:id/password', (req, res) => {
- authentication.changepassword(req, res)
+ authentication.changePassword(req, res)
})
// ############################################################################
diff --git a/server/lib/authentication.js b/server/lib/authentication.js
index 76e8b60..7b616d4 100644
--- a/server/lib/authentication.js
+++ b/server/lib/authentication.js
@@ -11,7 +11,7 @@ module.exports = { loginCookie, loginToken, logout, verifyToken, signup, changeP
// Authentifivation method for the frontend using secure httpOnly cookies. (POST)
function loginCookie (req, res) {
var params = req.body
- verifyUser(res, params.username, params.password, function (token) {
+ verifyUser(res, params.username, params.password, token => {
// The token has the form header.payload.signature
// We split the cookie in header.payload and signature in two seperate cookies.
// The signature cookie is httpOnly so JavaScript never has access to the full cookie.
@@ -55,7 +55,7 @@ function signup (req, res) {
pwd.hash(userPassword, function (err, hash) {
if (err) return res.status(500).send({ auth: false, status: 'PASSWORD_HASH_ERROR', error_message: 'Hashing the password failed.' })
// Saving the non improved hash and creating the user in the db.
- db.user.create({ username: params.username, password: hash, email: params.email, name: params.name }).then((userDb) => {
+ db.user.create({ username: params.username, password: hash, email: params.email, name: params.name }).then(userDb => {
// TODO: Username could also be used because those are unique as well.
var userId = userDb.id
// Verify & improving the hash.
@@ -78,7 +78,33 @@ function logout (req, res) {
}
function changePassword (req, res) {
- // TODO: IMPLEMENT
+ // Check if the new password is different.
+ if (req.body.passwordCurrent === req.body.passwordNew) return res.status(500).send({ auth: false, status: 'PASSWORD_ERROR', error_message: 'The provided password must be different than the old password.' })
+
+ // 1. Get the user and verify it's existence.
+ db.user.findOne({ where: { id: req.params.id } }).then(user => {
+ if (user) {
+ const pwCurrent = Buffer.from(req.body.passwordCurrent)
+ const pwNew = Buffer.from(req.body.passwordNew)
+ // 2. Verify the current hast with the provided current password.
+ verifyHash(res, pwCurrent, Buffer.from(user.password), user.id, () => {
+ // 3. Check if the new provided password fullfills the requirements
+ if (validatePassword(req.body.passwordNew)) {
+ // 4. Calculate the new password hash.
+ pwd.hash(pwNew, (err, hash) => {
+ if (err) return res.status(500).send({ auth: false, status: 'PASSWORD_HASH_ERROR', error_message: 'Hashing the password failed.' })
+ // 5. Write the hash in the db
+ user.update({ password: hash }).then(() => {
+ // 6. Verify & improving the hash.
+ verifyHash(res, pwNew, hash, user.id, () => {
+ res.status(200).send({ auth: true, status: 'VALID' })
+ })
+ })
+ })
+ } else res.send({ status: 'PASSWORD_REQUIREMENTS', error_message: 'The provided password doesn\'t fullfill the requirements' })
+ })
+ } else res.send({ status: 'INVALID_USER', error_message: 'There is no user with the provided id.' })
+ })
}
// Middleware function.
@@ -100,7 +126,7 @@ function verifyToken (req, res, next) {
}
// Verify the token with the secret.
- jwt.verify(token, config.secret, function (err) {
+ 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.' })
else return next(new Error('TOKEN_INVALID'))
@@ -136,7 +162,7 @@ function verifyUser (res, username, password, callback) {
var hash = Buffer.from(userDb.password)
// Verify & improving the hash.
- verifyHash(res, userPassword, hash, user.id, function () {
+ 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.' })
return callback(token)
diff --git a/webapp/src/assets/styles.css b/webapp/src/assets/styles.css
index 22bb716..59bd978 100644
--- a/webapp/src/assets/styles.css
+++ b/webapp/src/assets/styles.css
@@ -45,7 +45,7 @@ html {
.tabbar-card {
position: sticky;
top: 64px;
- z-index: 2;
+ z-index: 9;
}
.no-animation {
diff --git a/webapp/src/components/AccountModule.vue b/webapp/src/components/AccountModule.vue
index e9a4cce..3ac6af7 100644
--- a/webapp/src/components/AccountModule.vue
+++ b/webapp/src/components/AccountModule.vue
@@ -1,39 +1,269 @@
<i18n>
{
"en": {
+ "accountInfo": "Info",
+ "passwordConfirm": "Retype password",
+ "passwordChanged": "Password successfully changed.",
+ "passwordCurrent": "Current password",
+ "passwordEmptyError": "Password can not be empty.",
+ "passwordInvalidError": "Password incorrect.",
+ "passwordLengthError": "Minimum 8 characters required.",
+ "passwordMatchError": "Passwords do not match.",
+ "passwordNew": "New password",
+ "passwordNotDifferentError": "New password must be different than the current password.",
+ "passwordRequirement1": ">= 8 characters",
+ "passwordRequirement2": "0+ [0-9]",
+ "passwordRequirement3": "0+ [a-z]",
+ "passwordRequirement4": "0+ [A-Z]",
+ "passwordRequirement5": "0+ [!\"§$%&/()=?+#*.:,;]",
+ "passwordRequirements": "Password requirements"
},
"de": {
+ "accountInfo": "Info",
+ "passwordChanged": "Passwort wurde erfolgreich geändert.",
+ "passwordConfirm": "Passwort wiederholen",
+ "passwordCurrent": "Aktuelles Passwort",
+ "passwordEmptyError": "Passwort kann nicht leer sein.",
+ "passwordInvalidError": "Falsches Passwort.",
+ "passwordLengthError": "Es sind mindestens 8 Zeichen erforderlich.",
+ "passwordMatchError": "Passwörter stimmen nicht überein.",
+ "passwordNew": "Neues Password",
+ "passwordNotDifferentError": "Das neue Passwort muss sich vom alten unterscheiden.",
+ "passwordRequirement1": ">= 8 Zeichen",
+ "passwordRequirement2": "0+ [0-9]",
+ "passwordRequirement3": "0+ [a-z]",
+ "passwordRequirement4": "0+ [A-Z]",
+ "passwordRequirement5": "0+ [!\"§$%&/()=?+#*'-_.:,;]",
+ "passwordRequirements": "Passwort anforderungen"
}
}
</i18n>
<template>
- <div class="account-page">
- <div style="display: flex; justify-content: center; margin-top: 40px">
- <v-btn color="primary" @click="newAlert">New Alert</v-btn>
- </div>
- </div>
+ <v-container fill-height>
+ <v-layout>
+ <v-flex class="tabs-wrapper" xl10 offset-xl1 lg12>
+ <v-card>
+ <v-tabs :dark="tabsDark" :color="tabsColor" :slider-color="tabsSliderColor"
+ v-model="tab"
+ centered
+ >
+ <v-tab>
+ {{ $t('accountInfo') }}
+ </v-tab>
+ </v-tabs>
+ </v-card>
+ <v-tabs-items v-model="tab">
+ <v-tab-item>
+
+ <v-subheader>{{ $t('accountInfo') }}</v-subheader>
+ <v-card>
+ <v-card-text>
+ <v-layout wrap>
+ <v-flex lg4 md6 xs12 order-lg1 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>
+ <div class="info-text">{{ user.username }}</div>
+ </div>
+ </v-layout>
+ </v-flex>
+ <v-flex lg4 md6 xs12 order-lg2 order-xs3>
+ <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-layout>
+ </v-flex>
+ <v-flex lg4 md6 xs12 order-lg2 order-xs3>
+ <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-layout>
+ </v-flex>
+ </v-layout>
+ </v-card-text>
+ </v-card>
+
+ <v-subheader>{{ $t('security') }}</v-subheader>
+ <v-card>
+ <v-card-text>
+ <v-layout wrap>
+
+ <v-flex lg4 md6 xs12 order-lg1 order-xs2>
+ <v-layout column>
+ <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>
+ <div class="info-text">{{ $t('passwordRequirement3') }}</div>
+ <div class="info-text">{{ $t('passwordRequirement4') }}</div>
+ <div class="info-text">{{ $t('passwordRequirement5') }}</div>
+ </div>
+ </v-layout>
+ </v-flex>
+
+ <v-flex lg4 md6 xs12 order-lg1 order-xs2>
+ <v-layout column>
+ <div class="info-input">
+ <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-text-field
+ type="password"
+ v-model="passwordCurrent"
+ :label="$t('passwordCurrent')"
+ :rules="currentPasswordRules"
+ @input="clearCurrentPasswordErrors"
+ ></v-text-field>
+ <v-text-field
+ validate-on-blur
+ type="password"
+ v-model="passwordNew"
+ :label="$t('passwordNew')"
+ :rules="passwordRules"
+ ></v-text-field>
+ <v-text-field
+ validate-on-blur
+ type="password"
+ v-model="passwordConfirm"
+ :label="$t('passwordConfirm')"
+ :rules="confirmPasswordRules"
+ ></v-text-field>
+ </v-form>
+ </div>
+ </div>
+ </v-layout>
+ </v-flex>
+
+ <v-flex lg4 xs12 order-lg3 order-xs1 class="text-xs-right">
+ <div class="info-input">
+ <v-btn v-if="!editPasswordMode" color="primary" flat @click="editPassword" class="info-buttons">
+ <v-icon left>create</v-icon>{{ $t('edit') }}
+ </v-btn>
+ <div v-else>
+ <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>
+
+ </v-layout>
+ </v-card-text>
+ </v-card>
+
+ <v-subheader>{{ $t('TODO REMOVE: Testing stuff') }}</v-subheader>
+ <v-card>
+ <div style="display: flex; justify-content: center;">
+ <v-btn color="primary" @click="newAlert">New Alert</v-btn>
+ </div>
+ </v-card>
+
+ </v-tab-item>
+ </v-tabs-items>
+ </v-flex>
+ </v-layout>
+ </v-container>
+
</template>
<script>
-
+import { mapGetters } from 'vuex'
export default {
name: 'AccountPage',
data () {
return {
+ tab: 0,
+ valid: true,
+ user: '',
+ passwordConfirm: '',
+ passwordCurrent: '',
+ passwordNew: '',
+ invalidPasswordError: false,
+ editPasswordMode: false,
+ currentPasswordRules: [
+ v => !!v || this.$t('passwordEmptyError'),
+ v => !this.invalidPasswordError || this.$t('passwordInvalidError')
+ ],
+ passwordRules: [
+ v => !!v || this.$t('passwordEmptyError'),
+ v => v.length >= 8 || this.$t('passwordLengthError'),
+ v => v !== this.passwordCurrent || this.$t('passwordNotDifferentError')
+ ],
+ confirmPasswordRules: [
+ v => this.passwordConfirm === this.passwordNew || this.$t('passwordMatchError')
+ ],
testId: 0
}
},
+ computed: {
+ ...mapGetters(['tabsDark', 'tabsColor', 'tabsSliderColor'])
+ },
methods: {
+ clearCurrentPasswordErrors () {
+ this.invalidPasswordError = false
+ },
+ submitPassword () {
+ if (this.$refs.passwordForm.validate()) {
+ this.$http.post('/api/users/' + this.user.id + '/password', { passwordCurrent: this.passwordCurrent, passwordNew: this.passwordNew }).then(response => {
+ this.cancelEditPassword()
+ this.$snackbar({ color: 'success', text: this.$t('passwordChanged') })
+ }).catch(error => {
+ if (error.response.data.status === 'PASSWORD_INVALID') {
+ this.invalidPasswordError = true
+ this.passwordNew = ''
+ this.passwordConfirm = ''
+ }
+ this.$refs.passwordForm.validate()
+ })
+ }
+ },
+ editPassword () {
+ this.editPasswordMode = true
+ },
+ cancelEditPassword () {
+ this.editPasswordMode = false
+ this.passwordCurrent = ''
+ this.passwordNew = ''
+ this.passwordConfirm = ''
+ },
newAlert () {
this.$alert({ type: 'success', text: 'test ' + this.testId })
this.testId++
}
+ },
+ created () {
+ this.$http('/api/users/current').then(response => {
+ this.user = response.data
+ })
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
-
+.info-buttons {
+ margin: 0;
+}
+.info-input {
+ margin: 20px;
+}
+.info-heading {
+ display: flex;
+ align-items: center;
+ margin-bottom: 10px;
+}
+.info-heading > span {
+ margin-left: 10px;
+}
+.info-text {
+ margin-left: 34px;
+ font-family: 'Roboto Mono';
+}
</style>
diff --git a/webapp/src/components/IpxeBuilderModule.vue b/webapp/src/components/IpxeBuilderModule.vue
index 9cb9bea..fde333f 100644
--- a/webapp/src/components/IpxeBuilderModule.vue
+++ b/webapp/src/components/IpxeBuilderModule.vue
@@ -15,7 +15,7 @@
<v-container fill-height>
<v-layout>
<v-flex class="tabs-wrapper" xl10 offset-xl1 lg12>
- <v-card>
+ <v-card class="tabbar-card">
<v-tabs v-model="tabs" centered :dark="tabsDark" :color="tabsColor" :slider-color="tabsSliderColor">
<v-tab>{{ $t('efi') }}</v-tab>
<v-tab>{{ $t('bios') }}</v-tab>
diff --git a/webapp/src/components/StartPageLogin.vue b/webapp/src/components/StartPageLogin.vue
index b9ed5ca..87d4054 100644
--- a/webapp/src/components/StartPageLogin.vue
+++ b/webapp/src/components/StartPageLogin.vue
@@ -87,6 +87,8 @@ export default {
this.usernameError = true
} else if (error.response.data.status === 'PASSWORD_INVALID') {
this.passwordError = true
+ } else {
+ this.$snackbar({ color: 'error', text: error.response.data.error_message })
}
this.$refs.form.validate()
})