浏览代码

feat(webapp): adds reset password form

Nils Wisiol 5 年之前
父节点
当前提交
ddf24742ce
共有 4 个文件被更改,包括 231 次插入1 次删除
  1. 5 0
      webapp/src/App.vue
  2. 5 0
      webapp/src/router/index.js
  3. 220 0
      webapp/src/views/ResetPassword.vue
  4. 1 1
      webapp/src/views/Welcome.vue

+ 5 - 0
webapp/src/App.vue

@@ -118,6 +118,11 @@ export default {
         'icon': 'mdi-gift-outline',
         'text': 'Donate',
       },
+      'reset-password': {
+        'name': 'reset-password',
+        'icon': 'mdi-lock-reset',
+        'text': 'Reset Account Password',
+      },
     }
   }),
 }

+ 5 - 0
webapp/src/router/index.js

@@ -33,6 +33,11 @@ const routes = [
     name: 'docs',
     beforeEnter(to) { location.href = to.path },
   },
+  {
+    path: '/reset-password/:email?',
+    name: 'reset-password',
+    component: () => import(/* webpackChunkName: "signup" */ '../views/ResetPassword.vue')
+  },
   {
     path: '/donate/',
     name: 'donate',

+ 220 - 0
webapp/src/views/ResetPassword.vue

@@ -0,0 +1,220 @@
+<template>
+    <v-container
+            class="fill-height"
+            fluid
+    >
+        <v-row
+                align="center"
+                justify="center"
+        >
+            <v-col
+                    cols="12"
+                    sm="8"
+                    md="6"
+            >
+                <v-form @submit.prevent="resetPassword" ref="form">
+                    <v-card class="elevation-12 pb-4">
+                        <v-toolbar
+                                color="primary"
+                                dark
+                                flat
+                        >
+                            <v-toolbar-title>Reset Account Password</v-toolbar-title>
+                        </v-toolbar>
+                        <v-card-text>
+                            <v-alert :value="!!(errors && errors.length)" type="error">
+                                <div v-if="errors.length > 1">
+                                    <li v-for="error of errors" :key="error.message">
+                                        <b>{{ error.message }}</b>
+                                        {{ error }}
+                                    </li>
+                                </div>
+                                <div v-else>
+                                    {{ errors[0] }}
+                                </div>
+                            </v-alert>
+                            <v-alert v-if="done" type="success">
+                                <p>
+                                    We received the password reset request. If an account with this email address exists
+                                    in our database, we sent an email with password reset instructions. If you did not
+                                    receive an email, you can
+                                    <router-link :to="{name: 'signup', params: email ? {email: email} : {}}">
+                                        create an account
+                                    </router-link>
+                                    .
+                                </p>
+                            </v-alert>
+
+                            <v-text-field
+                                    v-model="email"
+                                    label="Email"
+                                    prepend-icon="mdi-email"
+                                    outline
+                                    required
+                                    :disabled="working"
+                                    :rules="email_rules"
+                                    :error-messages="email_errors"
+                                    @change="email_errors=[]"
+                                    validate-on-blur
+                                    ref="emailField"
+                                    tabindex="1"
+                            />
+
+                            <v-layout>
+                                <v-text-field
+                                            v-model="captchaSolution"
+                                            label="Type CAPTCHA text here"
+                                            prepend-icon="mdi-account-check"
+                                            outline
+                                            required
+                                            :disabled="working"
+                                            :rules="captcha_rules"
+                                            :error-messages="captcha_errors"
+                                            @change="captcha_errors=[]"
+                                            @keypress="captcha_errors=[]"
+                                            class="uppercase"
+                                            ref="captchaField"
+                                            tabindex="2"
+                                />
+                                <div class="ml-4 text-center">
+                                    <v-progress-circular
+                                            indeterminate
+                                            v-if="captchaWorking"
+                                    ></v-progress-circular>
+                                    <img
+                                            v-if="captcha && !captchaWorking"
+                                            :src="'data:image/png;base64,'+captcha.challenge"
+                                            alt="Passwords can also be reset by sending an email to our support."
+                                    >
+                                    <br/>
+                                    <v-btn text outlined @click="getCaptcha(true)" :disabled="captchaWorking">New Captcha
+                                    </v-btn>
+                                </div>
+                            </v-layout>
+                        </v-card-text>
+                        <v-card-actions class="justify-center">
+                            <v-btn
+                                    depressed
+                                    color="primary"
+                                    type="submit"
+                                    :disabled="working"
+                                    :loading="working"
+                                    tabindex="3"
+                            >Reset Password
+                            </v-btn>
+                        </v-card-actions>
+                    </v-card>
+                </v-form>
+            </v-col>
+        </v-row>
+    </v-container>
+</template>
+
+<script>
+  import axios from 'axios';
+  import {email_pattern} from '../validation';
+
+  const HTTP = axios.create({
+    baseURL: '/api/v1/',
+    headers: {},
+  });
+
+  export default {
+    name: 'SignUp',
+    data: () => ({
+      valid: false,
+      working: false,
+      done: false,
+      captchaWorking: true,
+      errors: [],
+      captcha: null,
+
+      /* email field */
+      email: '',
+      email_rules: [v => !!email_pattern.test(v || '') || 'We need an email address for account recovery and technical support.'],
+      email_errors: [],
+
+      /* captcha field */
+      captchaSolution: '',
+      captcha_rules: [v => !!v || 'Please enter the text displayed in the picture so we are (somewhat) convinced you are human'],
+      captcha_errors: [],
+    }),
+    async mounted() {
+      if ('email' in this.$route.params && this.$route.params.email !== undefined) {
+        this.email = this.$route.params.email;
+      }
+      this.getCaptcha();
+      this.initialFocus();
+    },
+    methods: {
+      async getCaptcha(focus = false) {
+        this.captchaWorking = true;
+        this.captchaSolution = "";
+        try {
+          this.captcha = (await HTTP.post('captcha/')).data;
+          if(focus) {
+            this.$refs.captchaField.focus()
+          }
+        } finally {
+          this.captchaWorking = false;
+        }
+      },
+      async initialFocus() {
+        return this.$refs.emailField.focus();
+      },
+      async resetPassword() {
+        if (!this.$refs.form.validate()) {
+          return;
+        }
+        this.working = true;
+        this.errors = [];
+        try {
+          await HTTP.post('auth/account/reset-password/', {
+            email: this.email.toLowerCase(),
+            captcha: {
+              id: this.captcha.id,
+              solution: this.captchaSolution,
+            },
+          });
+          this.done = true;
+        } catch (error) {
+          if (error.response) {
+            // status is not 2xx
+            if (error.response.status < 500 && typeof error.response.data === 'object') {
+              // 3xx or 4xx
+              let extracted = false;
+              this.getCaptcha(true);
+              if ('captcha' in error.response.data) {
+                if ('non_field_errors' in error.response.data.captcha) {
+                  this.captcha_errors = [error.response.data.captcha.non_field_errors[0]];
+                  extracted = true;
+                }
+                if ('solution' in error.response.data.captcha) {
+                  this.captcha_errors = error.response.data.captcha.solution;
+                  extracted = true;
+                }
+              }
+              if (!extracted) {
+                this.errors = error.response;
+              }
+            } else {
+              // 5xx
+              this.errors = ['Something went wrong at the server, but we currently do not know why. The customer support was already notified.'];
+            }
+          } else if (error.request) {
+            this.errors = ['Cannot contact our servers. Are you offline?'];
+          } else {
+            this.errors = [error.message];
+          }
+        }
+        this.working = false;
+      },
+    },
+  };
+</script>
+
+<style lang="scss">
+    .uppercase input {
+        text-transform: uppercase;
+    }
+</style>

+ 1 - 1
webapp/src/views/Welcome.vue

@@ -26,7 +26,7 @@
               Thank you for your interest in deSEC.
               To create your deSEC account, please click the verification link in the email that we sent you.
               If you did not receive an email, please check your spam folder or if you already have an account
-              by using the <router-link to="/">account recovery form</router-link>. <!-- TODO -->
+              by using the <router-link :to="{name: 'reset-password'}">password reset form</router-link>.
             </v-alert>
           </v-card-text>
           <v-card-actions>