Files
password-reset-web/src/SetPassword.vue

257 lines
11 KiB
Vue

<template>
<div>
<h1>ECG account</h1>
<h2> Set new password</h2>
<div v-if="validationRequestActive" class="alert alert-info">
Validating password reset token
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
</div>
<template v-else>
<div v-if="!tokenIsValid" class="alert alert-danger" role="alert">
<template v-if="validationRequestError">
<h3>Failed to validate password reset token</h3>
<p>{{ validationRequestError }}</p>
</template>
<template v-else>
<h3>Invalid token provided</h3>
<p>This password reset token isn't valid (anymore). Please
<router-link :to="{ name: 'request' }" tag="a">send the password reset request</router-link> again.</p>
</template>
</div>
<div v-else>
<div v-if="setRequestFinished" class="alert alert-success" role="alert">
✊ Your password has been set <strong>successfully</strong>! The new password takes effect immediately.
</div>
<form v-else method="POST" class="col-md-10 mb-lg-5" @submit.prevent="onSubmit">
<p>Please provide a new password.</p>
<fieldset :disabled="setRequestIsWorking">
<div class="row">
<label for="password1" class="col-sm-3 col-form-label"><strong>New password:</strong></label>
<div class="col-sm-9">
<input id="password1" type="password" class="form-control" required="true" v-model="password1"
aria-label="New password" ref="password1" minlength="8"
autocomplete="new-password">
</div>
</div>
<div class="mb-3 row">
<label for="password2" class="col-sm-3 col-form-label"><strong>Repeat password:</strong></label>
<div class="col-sm-9">
<input id="password2" type="password" class="form-control" required="true"
aria-label="New password again" v-model="password2"
autocomplete="new-password">
</div>
</div>
<div class="mb-3 row">
<p>
Please read the guidelines on <a href="#faq-how-to-choose-good-pwd">How to choose a strong password?</a> below. You can only procees as soon as the following progress bar <strong>turns green</strong>.
</p>
<label for="password-quality" class="col-sm-3 col-form-label">Password quality:</label>
<div class="col-sm-9">
<password-strength :password="password1" class="mt-2"
v-on:score="qualityScore = $event"
v-on:feedback="qualityFeedback = $event"
></password-strength>
</div>
</div>
<div v-if="setRequestError" class="alert alert-danger" role="alert">{{ setRequestError }}</div>
<div class="text-muted float-start">{{ qualityFeedback }}</div>
<div class="text-end" :title="setButtonTitle">
<button class="btn btn-outline-danger" type="submit" id="button-submit"
:disabled="setButtonTitle !== null">
<span v-if="!setRequestIsWorking">⮷ Set password</span>
<span v-else>
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
</span>
</button>
</div>
</fieldset>
</form>
</div>
</template>
<h3 class="text-muted" id="faq-how-to-choose-good-pwd">How to choose a strong password?</h3>
<p>As of today (2021) there are two approved strategies:</p>
<ol>
<li>Choose a <strong>reasonably complex password</strong> using <strong>multiple character classes</strong> (like uppercase, lowercase, special characters). Downside: They are hard to remember for users and potentionally lead to passwords being
reused across different services.</li>
<li>Choose a <strong>long password</strong> that consists of a personal sentence which is hard to break for machines and easy
to remember for users.</li>
<!--<li>Just for the sake of completeness: Choose a second factor to get the best possible security. This is not
yet supported by the ECG services. But it's on our radar.</li>-->
</ol>
<p>If you want to create a good password you can refer to this service: <a href="https://passwordcreator.org/nl.html" target="_blank">Dutch</a> | <a href="https://passwordcreator.org/fr.html" target="_blank">French</a> | <a href="https://passwordcreator.org" target="_blank">English</a> | <a href="https://passwordcreator.org/de.html" target="_blank">German</a> | <a href="https://passwordcreator.org/es.html" target="_blank">Spanish</a>
<p>Although currently not prevented technically it's <strong>not allowed to reuse your previous password</strong>.</p>
<p>Furthermore we recommend using a password manager like <a href="https://keepass.info/" target="_blank">KeePass</a>.</p>
<br />
<h5>In case of a problem</h5>
<p>
You find <a href="https://wiki.ecogood.org/x/DYQjB" target="_blank"> a detailed explanation of the whole password reset process</a> in the ECG Wiki.<br />For further help just open a ticket at the <a href="https://wiki.ecogood.org/display/PUBLIC/IT-Support" target="_blank">IT support</a>.
</p>
</div>
</template>
<script>
"use strict";
import PasswordStrength from './PasswordStrength';
import axios from 'axios';
import zxcvbn from 'zxcvbn';
let cssClasses = {
0: 'bg-danger',
1: 'bg-danger',
2: 'bg-warning',
3: 'bg-warning',
4: 'bg-success'
};
export default {
components: {
PasswordStrength
},
data: function () {
return {
// step 1: validate the given token
token: this.$route.params.token,
validationRequestActive: true,
validationRequestError: null,
tokenIsValid: false,
// step 2: set the new password
password1: "",
password2: "",
setRequestIsWorking: false,
setRequestFinished: false,
setRequestError: null,
qualityScore: 0,
qualityFeedback: null,
};
},
computed: {
setButtonTitle: function () {
if (this.qualityScore < 4) {
return 'The password quality is too low';
} else if (this.password1 !== this.password2) {
return "The two passwords do not match";
} else {
return null;
}
}
},
methods: {
validateToken: function () {
this.validationRequestActive = true;
let tokenEncoded = encodeURIComponent(this.token);
axios
.get(`password/token/${tokenEncoded}`)
.then(
() => this.tokenIsValid = true,
(error) => {
let containsResponse = typeof error.response !== 'undefined';
this.validationRequestError = containsResponse && error.response.status === 404
? null
: error.message;
})
.catch(() => this.validationRequestError = "validation request error")
.finally(() => this.validationRequestActive = false);
},
onSubmit: function () {
this.setRequestIsWorking = true;
let tokenEncoded = encodeURIComponent(this.token);
let config = {
headers: {
'Content-Type': 'text/plain'
},
responseType: 'text'
};
axios
.post(`password/${tokenEncoded}`, this.password1, config)
.then(() => {
this.setRequestFinished = true;
this.setRequestError = null;
localStorage.removeItem('timestamp');
}, (error) => {
let containsResponse = typeof error.response !== 'undefined';
this.setRequestError = containsResponse && error.response.status === 400
? "Insufficient password quality!"
: error.message;
})
.finally(() => {
this.setRequestIsWorking = false;
});
},
onUserTest: function () {
let uidEncoded = encodeURIComponent(this.uid);
let config = {
headers: {
'Content-Type': 'text/plain'
},
responseType: 'text'
};
this.testResult = null;
this.testIsWorking = true;
axios
.post(`password/test/${uidEncoded}`, this.password, config)
.then((response) => {
this.testRequestFinished = true;
this.testRequestError = null;
this.testResult = true;
}, (error) => {
this.testResult = false;
this.testRequestError = error.message;
})
.finally(() => {
this.testIsWorking = false;
});
},
onScoreUpdate: function (score) {
this.qualityScore = score;
}
},
mounted() {
this.validateToken();
}
};
</script>
<style scoped>
#button-submit {
min-width: 100px;
}
#button-submit:enabled {
animation: pulse 0.3s 1 alternate;
}
@keyframes pulse {
0% {
transform: scale(0.95);
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.7);
}
70% {
transform: scale(1);
box-shadow: 0 0 0 10px rgba(0, 0, 0, 0);
}
100% {
transform: scale(0.95);
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0);
}
}
</style>