소스 검색

Added Forgot Password Feature

master
30117125 4 년 전
부모
커밋
2d70d8df06

+ 13
- 0
package-lock.json 파일 보기

@@ -3616,6 +3616,11 @@
3616 3616
         "randomfill": "^1.0.3"
3617 3617
       }
3618 3618
     },
3619
+    "crypto-js": {
3620
+      "version": "4.0.0",
3621
+      "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.0.0.tgz",
3622
+      "integrity": "sha512-bzHZN8Pn+gS7DQA6n+iUmBfl0hO5DJq++QP3U6uTucDtk/0iGpXd/Gg7CGR0p8tJhofJyaKoWBuJI4eAO00BBg=="
3623
+    },
3619 3624
     "css-color-names": {
3620 3625
       "version": "0.0.4",
3621 3626
       "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz",
@@ -12315,6 +12320,14 @@
12315 12320
       "integrity": "sha512-TigfiZUs7SN3Z6uxKilqJUtYxte8vp0F4QxabCli6hkKPqU97JzAZc3P7AL6omkRAd2DMI26fOrIGjuALTvXww==",
12316 12321
       "dev": true
12317 12322
     },
12323
+    "vue-cryptojs": {
12324
+      "version": "2.1.5",
12325
+      "resolved": "https://registry.npmjs.org/vue-cryptojs/-/vue-cryptojs-2.1.5.tgz",
12326
+      "integrity": "sha512-bllwNCd+jgQ6MOnhXuv+zMwisS7A+h7juFOJQjfrLlSo/Ld7/DTYlGS7OgYAW8bI3DZG3F6otE0iqKseVR2dlQ==",
12327
+      "requires": {
12328
+        "crypto-js": "^4.0.0"
12329
+      }
12330
+    },
12318 12331
     "vue-currency-input": {
12319 12332
       "version": "1.22.3",
12320 12333
       "resolved": "https://registry.npmjs.org/vue-currency-input/-/vue-currency-input-1.22.3.tgz",

+ 1
- 0
package.json 파일 보기

@@ -28,6 +28,7 @@
28 28
     "v-file-upload": "^3.1.7",
29 29
     "vue": "^2.6.11",
30 30
     "vue-carousel": "^0.18.0",
31
+    "vue-cryptojs": "^2.1.5",
31 32
     "vue-currency-input": "^1.22.3",
32 33
     "vue-disable-autocomplete": "0.0.4",
33 34
     "vue-eva-icons": "^1.1.1",

+ 1
- 1
src/components/shared/navBar.vue 파일 보기

@@ -191,7 +191,7 @@
191 191
                       <li v-if="ROLE === 'Super Admin'">
192 192
                         <router-link to="/status/list">Status</router-link>
193 193
                       </li>-->
194
-                      <li v-if="ROLE === 'Super Admin'" class="menu-has-children">
194
+                      <li class="menu-has-children">
195 195
                         <div
196 196
                           @mouseover="submenu1Class = 'ts-display'"
197 197
                           @mouseleave="submenu1Class = 'no-display'"

+ 11
- 5
src/components/timeshare/faqPage.vue 파일 보기

@@ -84,9 +84,9 @@
84 84
               <div id="collapse5" class="panel-collapse collapse">
85 85
                 <div class="panel-body">
86 86
                   You can complete the on-line details which you will find under the “To Sell” tab.
87
-                  There will be a listing fee payable of R380 including VAT. The details submitted
88
-                  will go through a verification process. Subject to there being no complications,
89
-                  the listing will be processed and published.<br /><br />
87
+                  There will be a listing fee payable of R{{ getListingFee.amount }} including VAT.
88
+                  The details submitted will go through a verification process. Subject to there
89
+                  being no complications, the listing will be processed and published.<br /><br />
90 90
                 </div>
91 91
               </div>
92 92
             </div>
@@ -116,8 +116,10 @@
116 116
               <div id="collapse7" class="panel-collapse collapse">
117 117
                 <div class="panel-body">
118 118
                   Through Uni-Vate Properties your week will be marketed extensively to all of their
119
-                  available channels. Uni-Vate will only charge a listing Fee of R380 including VAT
120
-                  which is due when listing.<br /><br />
119
+                  available channels. Uni-Vate will only charge a listing Fee of R{{
120
+                    getListingFee.amount
121
+                  }}
122
+                  including VAT which is due when listing.<br /><br />
121 123
                 </div>
122 124
               </div>
123 125
             </div>
@@ -264,9 +266,13 @@
264 266
 <script>
265 267
 import AOS from "aos";
266 268
 import "aos/dist/aos.css";
269
+import { mapGetters } from "vuex";
267 270
 export default {
268 271
   created() {
269 272
     AOS.init;
273
+  },
274
+  computed: {
275
+    ...mapGetters("fees", ["getListingFee"])
270 276
   }
271 277
 };
272 278
 </script>

+ 66
- 0
src/components/user/forgotPassword.vue 파일 보기

@@ -0,0 +1,66 @@
1
+<template>
2
+  <main id="main" style="padding-top:10px; padding-bottom:50px">
3
+    <div class="container">
4
+      <div class="row">
5
+        <div class="col">
6
+          <div class="section-header">
7
+            <h2>Forgot Password</h2>
8
+          </div>
9
+        </div>
10
+      </div>
11
+      <div class="row mb-4">
12
+        <div class="col">
13
+          <alert :text="message" :type="status" />
14
+        </div>
15
+      </div>
16
+      <div class="row">
17
+        <div class="col-md-6">
18
+          <float-label>
19
+            <input v-model="email" type="text" placeholder="EMAIL" class="form-control uniInput" />
20
+          </float-label>
21
+        </div>
22
+      </div>
23
+      <div class="row mt-2">
24
+        <div class="col-md-6">
25
+          <button class="btn-solid-blue" @click="checkMail()">SEND</button>
26
+        </div>
27
+      </div>
28
+    </div>
29
+  </main>
30
+</template>
31
+
32
+<script>
33
+import { mapActions } from "vuex";
34
+/* eslint-disable */
35
+
36
+import alert from "../shared/alert";
37
+
38
+export default {
39
+  components: {
40
+    alert
41
+  },
42
+  data() {
43
+    return {
44
+      email: "",
45
+      message: "",
46
+      status: ""
47
+    };
48
+  },
49
+  methods: {
50
+    ...mapActions("register", ["forgotPasswordCheck"]),
51
+    checkMail() {
52
+      this.forgotPasswordCheck(this.email)
53
+        .then(res => {
54
+          this.message = "An email has been sent with instructions to reset your password.";
55
+          this.status = "SUCCESS";
56
+        })
57
+        .catch(e => {
58
+          this.message = "The email was not found";
59
+          this.status = "ERROR";
60
+        });
61
+    }
62
+  }
63
+};
64
+</script>
65
+
66
+<style lang="scss" scoped></style>

+ 236
- 0
src/components/user/forgotPasswordReset.vue 파일 보기

@@ -0,0 +1,236 @@
1
+<template>
2
+  <main id="main" style="margin-top:200px; padding-bottom:50px">
3
+    <div class="container">
4
+      <div class="row">
5
+        <div class="col">
6
+          <div class="section-header">
7
+            <h2>Reset Password</h2>
8
+          </div>
9
+        </div>
10
+      </div>
11
+      <div v-if="validLink">
12
+        <div v-if="invalidToken">
13
+          <div class="row mb-4">
14
+            <div class="col">
15
+              <alert
16
+                :text="
17
+                  'This link has been used previously to update your password, please request a new one'
18
+                "
19
+                :type="'ERROR'"
20
+              />
21
+            </div>
22
+          </div>
23
+        </div>
24
+        <div v-else>
25
+          <div class="row mb-4">
26
+            <div class="col">
27
+              <alert :text="message" :type="status" />
28
+            </div>
29
+          </div>
30
+          <div class="row">
31
+            <div class="col-md-6">
32
+              <input
33
+                v-model="password"
34
+                type="password"
35
+                class="form-control uniInput"
36
+                placeholder="PASSWORD"
37
+              />
38
+            </div>
39
+            <div class="col-md-6">
40
+              <input
41
+                v-model="confirmPass"
42
+                type="password"
43
+                class="form-control uniInput"
44
+                placeholder="CONFIRM PASSWORD"
45
+              />
46
+            </div>
47
+          </div>
48
+          <div class="row mt-4">
49
+            <div class="col">
50
+              <button @click="pushToApi" class="btn-solid-blue">
51
+                UPDATE PASSWORD
52
+              </button>
53
+            </div>
54
+          </div>
55
+        </div>
56
+      </div>
57
+      <div v-else>
58
+        <div class="row mb-4">
59
+          <div class="col">
60
+            <alert
61
+              :text="'This link is old. Please request a new link to update your password.'"
62
+              :type="'ERROR'"
63
+            />
64
+          </div>
65
+        </div>
66
+      </div>
67
+    </div>
68
+  </main>
69
+</template>
70
+
71
+<script>
72
+/* eslint-disable */
73
+import axios from "axios";
74
+import alert from "../shared/alert";
75
+
76
+export default {
77
+  components: {
78
+    alert
79
+  },
80
+  data() {
81
+    return {
82
+      timeDiff: 0,
83
+      validLink: false,
84
+      mail: "",
85
+      confirmPass: "",
86
+      password: "",
87
+      passwordType: "password",
88
+      passwordIcon: "eye-slash",
89
+      passwordConfirmType: "password",
90
+      passwordConfirmIcon: "eye-slash",
91
+      message: "",
92
+      status: "",
93
+      invalidToken: false
94
+    };
95
+  },
96
+  created() {
97
+    this.getTimeFromToken();
98
+  },
99
+  methods: {
100
+    pushToApi() {
101
+      if (this.password === this.confirmPass) {
102
+        var userObj = {
103
+          email: this.mail,
104
+          password: this.password
105
+        };
106
+        axios.put("api/register/passwordUpdate", userObj).then(() => {
107
+          this.message = "Password Updated, you can now Log in";
108
+          this.status = "SUCCESS";
109
+          setTimeout(() => {
110
+            this.$router.push("/user/login");
111
+          }, 2000);
112
+        });
113
+      } else {
114
+        this.message = "Passwords do not match";
115
+        this.status = "ERROR";
116
+        //console.log("Passwords do not match");
117
+      }
118
+    },
119
+    getTimeFromToken() {
120
+      var decryptedBase64 = this.decrypt();
121
+      var bytes = [];
122
+      for (let index = 0; index < atob(decryptedBase64).length; index++) {
123
+        var code = atob(decryptedBase64).charCodeAt(index);
124
+        bytes = bytes.concat([code & 0xff, (code / 256) >>> 0]);
125
+      }
126
+      var decryptedAll = bytes.join();
127
+      var timeArr = [];
128
+      var mailArr = [];
129
+      for (let i = 0; i < bytes.length; i++) {
130
+        if (bytes[i] === 0) {
131
+          bytes.splice(i, 1);
132
+        }
133
+      }
134
+
135
+      for (let index = 0; index < bytes.length; index++) {
136
+        if (index < 16) {
137
+          timeArr.push(bytes[index]);
138
+        }
139
+      }
140
+
141
+      for (let index = 32; index < bytes.length; index++) {
142
+        mailArr.push(bytes[index]);
143
+      }
144
+
145
+      this.byteArrayToMail().then(res => {
146
+        this.mail = res;
147
+      });
148
+
149
+      var Tokendate = new Date(this.byteArrayToLong(timeArr));
150
+      var d = new Date();
151
+      var date = new Date(d.getTime());
152
+      var upperDate = new Date(Tokendate.setHours(Tokendate.getHours() + 2));
153
+
154
+      if (date < upperDate) {
155
+        this.validLink = true;
156
+      } else {
157
+        console.log("Link is old");
158
+      }
159
+    },
160
+
161
+    async byteArrayToMail() {
162
+      var token = await this.setToken();
163
+      var fptokenObj = {
164
+        token: token
165
+      };
166
+
167
+      //getting email from API by searching using the forgot Password token; then decription
168
+      //of the string is not constant.
169
+      const result = await axios.post("api/register/fptoken", fptokenObj);
170
+      if (result.status === 200) {
171
+        var mail = result.data.email;
172
+        return mail;
173
+      } else {
174
+        this.invalidToken = true;
175
+      }
176
+
177
+      // for (var i = 0; i < byteArr.length; i++) {
178
+      //   result += String.fromCharCode(parseInt(byteArr[i]))
179
+      // }
180
+      //return result
181
+    },
182
+    async setToken() {
183
+      return this.$route.params.token;
184
+    },
185
+    byteArrayToLong(byteArray) {
186
+      var length = byteArray.length;
187
+
188
+      var result = "";
189
+      for (var i = 0; i < length; i++) {
190
+        result += String.fromCharCode(parseInt(byteArray[i]));
191
+      }
192
+      var finResult = result.replace(",", ".");
193
+
194
+      result = parseFloat(finResult);
195
+      return result;
196
+    },
197
+    decrypt() {
198
+      const searchRegExp = /!/g;
199
+      var actualEncryptStr = this.$route.params.token.replace(searchRegExp, "/");
200
+      var key = "EGV~A8pn;:9&zC]6";
201
+
202
+      //Creating the Vector Key
203
+      var iv = this.$CryptoJS.enc.Hex.parse("e84ad660c4721ae0e84ad660c4721ae0");
204
+
205
+      //Encoding the Password in from UTF8 to byte array
206
+      var Pass = this.$CryptoJS.enc.Utf8.parse(key);
207
+
208
+      //Encoding the Salt in from UTF8 to byte array
209
+      var Salt = this.$CryptoJS.enc.Utf8.parse("insight123resultxyz");
210
+
211
+      //Creating the key in PBKDF2 format to be used during the decryption
212
+      var key128Bits1000Iterations = this.$CryptoJS.PBKDF2(
213
+        Pass.toString(this.$CryptoJS.enc.Utf8),
214
+        Salt,
215
+        { keySize: 128 / 32, iterations: 1000 }
216
+      );
217
+
218
+      //Enclosing the test to be decrypted in a CipherParams object as supported by the CryptoJS libarary
219
+      var cipherParams = this.$CryptoJS.lib.CipherParams.create({
220
+        ciphertext: this.$CryptoJS.enc.Base64.parse(actualEncryptStr)
221
+      });
222
+
223
+      //Decrypting the string contained in cipherParams using the PBKDF2 key
224
+      var decrypted = this.$CryptoJS.AES.decrypt(cipherParams, key128Bits1000Iterations, {
225
+        mode: this.$CryptoJS.mode.CBC,
226
+        iv: iv,
227
+        padding: this.$CryptoJS.pad.Pkcs7
228
+      });
229
+
230
+      return decrypted.toString(this.$CryptoJS.enc.Utf8);
231
+    }
232
+  }
233
+};
234
+</script>
235
+
236
+<style lang="scss" scoped></style>

+ 4
- 1
src/components/user/loginPage.vue 파일 보기

@@ -9,7 +9,6 @@
9 9
                 <div class="card card-signin my-5">
10 10
                   <div class="card-body">
11 11
                     <h3 class="card-title text-center">Login</h3>
12
-
13 12
                     <div v-if="error">
14 13
                       <alert
15 14
                         :text="'User doesn\'t exist or Username and Password is incorrect'"
@@ -46,9 +45,13 @@
46 45
                         >Remember password?</label
47 46
                       >
48 47
                     </div>
48
+
49 49
                     <button v-on:click="Login()" class="btn-solid-blue" type="submit">
50 50
                       LOGIN
51 51
                     </button>
52
+                    <router-link style="float:right" to="/user/forgotPassword"
53
+                      >Forgot Password?</router-link
54
+                    >
52 55
                   </div>
53 56
                 </div>
54 57
               </div>

+ 42
- 20
src/components/user/registerIndividual.vue 파일 보기

@@ -11,7 +11,7 @@
11 11
           <input
12 12
             type="text"
13 13
             id="inputName"
14
-            v-model="registerIndividual.name"
14
+            v-model="indivUser.name"
15 15
             class="form-control uniInput my-2"
16 16
             placeholder="Name"
17 17
             required
@@ -21,7 +21,7 @@
21 21
           <input
22 22
             type="text"
23 23
             id="inputSurname"
24
-            v-model="registerIndividual.surname"
24
+            v-model="indivUser.surname"
25 25
             class="form-control uniInput my-2"
26 26
             placeholder="Surname"
27 27
             required
@@ -31,7 +31,7 @@
31 31
           <input
32 32
             type="email"
33 33
             id="inputEmail"
34
-            v-model="registerIndividual.email"
34
+            v-model="indivUser.email"
35 35
             class="form-control uniInput my-2"
36 36
             placeholder="Email"
37 37
             required
@@ -42,7 +42,7 @@
42 42
           <input
43 43
             type="text"
44 44
             id="inputCellphone"
45
-            v-model="registerIndividual.cellNumber"
45
+            v-model="indivUser.cellNumber"
46 46
             class="form-control uniInput my-2"
47 47
             placeholder="Cellphone Number"
48 48
             required
@@ -52,7 +52,7 @@
52 52
           <input
53 53
             type="text"
54 54
             id="inputLandLine"
55
-            v-model="registerIndividual.telephone"
55
+            v-model="indivUser.telephone"
56 56
             class="form-control uniInput my-2"
57 57
             placeholder="Landline Number"
58 58
             required
@@ -66,7 +66,7 @@
66 66
           <input
67 67
             type="text"
68 68
             id="inputUsername"
69
-            v-model="registerIndividual.username"
69
+            v-model="indivUser.username"
70 70
             class="form-control uniInput my-2"
71 71
             placeholder="Username"
72 72
             required
@@ -79,7 +79,7 @@
79 79
           <input
80 80
             type="password"
81 81
             placeholder="Password "
82
-            v-model="registerIndividual.password"
82
+            v-model="indivUser.password"
83 83
             id="inputPassword"
84 84
             class="form-control uniInput my-2"
85 85
             required
@@ -89,7 +89,7 @@
89 89
           <input
90 90
             type="password"
91 91
             placeholder="Confirm Password"
92
-            v-model="registerIndividual.confirmpassword"
92
+            v-model="indivUser.confirmpassword"
93 93
             id="inputPasswordConfirm"
94 94
             class="form-control uniInput my-2"
95 95
             required
@@ -102,19 +102,13 @@
102 102
             ><router-link to="/termsConditionsView">Terms & Conditions</router-link></label
103 103
           >
104 104
 
105
-          <input
106
-            name="tc"
107
-            type="checkbox"
108
-            class="ml-3 mt-2"
109
-            v-model="registerIndividual.acceptedTerms"
110
-          />
105
+          <input name="tc" type="checkbox" class="ml-3 mt-2" v-model="terms" />
111 106
         </div>
112 107
       </div>
113
-
114 108
       <button
115
-        v-if="registerIndividual.acceptedTerms"
109
+        :disabled="isDisabled"
116 110
         v-on:click="SubmitData()"
117
-        class="btn-solid-blue"
111
+        :class="isDisabled ? 'btn-disabled' : 'btn-solid-blue'"
118 112
         type="submit"
119 113
       >
120 114
         SUBMIT
@@ -150,13 +144,18 @@ export default {
150 144
       textErrors: "Some error with the field",
151 145
       text: "",
152 146
       showPassword: false,
153
-      boolMessage: false
147
+      boolMessage: false,
148
+      terms: false,
149
+      indivUser: {}
154 150
     };
155 151
   },
156 152
   computed: {
157 153
     ...mapState("register", ["registerIndividual"]),
158 154
     Header() {
159 155
       return this.RegisterHeader ? "Agency Administrator Details" : "Private Individual";
156
+    },
157
+    isDisabled: function() {
158
+      return !this.terms;
160 159
     }
161 160
   },
162 161
   methods: {
@@ -175,7 +174,8 @@ export default {
175 174
       this.isPasswordShown = "password";
176 175
     },
177 176
     SubmitData() {
178
-      this.saveIndividual(this.registerIndividual)
177
+      this.indivUser.acceptedTerms = this.terms;
178
+      this.saveIndividual(this.indivUser)
179 179
         .then(res => {
180 180
           this.boolMessage = true;
181 181
           setTimeout(() => {
@@ -193,4 +193,26 @@ export default {
193 193
 };
194 194
 </script>
195 195
 
196
-<style lang="scss" scoped></style>
196
+<style lang="scss" scoped>
197
+.btn-disabled {
198
+  font-family: "Muli";
199
+  font-size: 15px;
200
+  letter-spacing: 1px;
201
+  display: inline-block;
202
+  padding: 10px 32px;
203
+  border-radius: 2px;
204
+  transition: 0.5s;
205
+  margin: 10px;
206
+  color: #fff;
207
+  background: grey;
208
+  border-color: black;
209
+  color: black;
210
+  cursor: not-allowed;
211
+}
212
+.btn-disabled :hover {
213
+  background: grey;
214
+  border-color: black;
215
+  color: black;
216
+  cursor: not-allowed;
217
+}
218
+</style>

+ 1
- 1
src/components/user/updateProfileInfo.vue 파일 보기

@@ -140,7 +140,7 @@
140 140
               placeholder="CELL NUMBER"
141 141
               data-rule="minlen:4"
142 142
               data-msg="Please enter your cell number"
143
-              v-model="individual.cellNumner"
143
+              v-model="individual.cellNumber"
144 144
             />
145 145
           </float-label>
146 146
 

+ 2
- 0
src/main.js 파일 보기

@@ -11,11 +11,13 @@ import Vuetify from "vuetify";
11 11
 import VueSocialSharing from "vue-social-sharing";
12 12
 import VueCurrencyInput from "vue-currency-input";
13 13
 import VueFloatLabel from "vue-float-label";
14
+import VueCryptojs from "vue-cryptojs"
14 15
 
15 16
 Vue.use(VueSocialSharing);
16 17
 Vue.use(EvaIcons);
17 18
 Vue.use(Vuetify);
18 19
 Vue.use(VueFloatLabel);
20
+Vue.use(VueCryptojs)
19 21
 
20 22
 Vue.use(VueGoogleMaps, {
21 23
   load: {

+ 13
- 1
src/router/index.js 파일 보기

@@ -14,6 +14,8 @@ import Login from "../components/user/loginPage.vue";
14 14
 import PrivateIndividual from "../components/user/registerPage.vue";
15 15
 import Agency from "../components/user/registerAgencySection.vue";
16 16
 import UpdateInfo from "../components/user/updateProfileInfo.vue";
17
+import ForgotPassword from '../components/user/forgotPassword.vue';
18
+import ForgotPasswordReset from '../components/user/forgotPasswordReset.vue'
17 19
 
18 20
 import PropertySearch from "../components/property/propertySearchPage.vue";
19 21
 import PropertyPage from "../components/property/propertyPage.vue";
@@ -484,6 +486,16 @@ export default new Router({
484 486
         ...route.params
485 487
       }),
486 488
       component: MyWeeksEdit
487
-    }
489
+    },
490
+    {
491
+      path: "/user/forgotPassword",
492
+      name: "forgotPassword",
493
+      component: ForgotPassword
494
+    },
495
+    {
496
+      path: "/forgotPasswordReset/:token",
497
+      name: "ForgotPasswordReset",
498
+      component: ForgotPasswordReset,
499
+    },
488 500
   ]
489 501
 });

+ 11
- 1
src/store/modules/user/register.js 파일 보기

@@ -1,5 +1,6 @@
1 1
 /* eslint-disable */
2 2
 import axios from "axios";
3
+import promise from "core-js/fn/promise";
3 4
 
4 5
 export default {
5 6
   namespaced: true,
@@ -72,7 +73,8 @@ export default {
72 73
         email: "",
73 74
         cellNumber: "",
74 75
         username: "",
75
-        password: ""
76
+        password: "",
77
+        acceptedTerms: false
76 78
       };
77 79
     },
78 80
     clearAgency(state) {
@@ -102,6 +104,14 @@ export default {
102 104
   },
103 105
   getters: {},
104 106
   actions: {
107
+    async forgotPasswordCheck({commit}, mail){
108
+      const response = await axios.post('api/register/forgotPassword/'+ mail)
109
+      if(response.status === 200){
110
+        return Promise.resolve()
111
+      }else{
112
+        return Promise.reject()
113
+      }
114
+    },
105 115
     getIndividuals({ commit }) {
106 116
       axios
107 117
         .get("/api/individual")

Loading…
취소
저장