Quellcode durchsuchen

:construction: Starting in on stage 2 of email verification

juan_spike
tomit4 vor 3 Jahren
Ursprung
Commit
d55dc3c810

+ 1
- 1
backend/lib/routes/user/email.js Datei anzeigen

@@ -24,7 +24,7 @@ module.exports = {
24 24
             const { userService } = request.server.services()
25 25
             const userEmail = request.payload.email
26 26
             try {
27
-                const emailSent = userService.emailSent(userEmail)
27
+                const emailSent = await userService.emailSent(userEmail)
28 28
                 return {
29 29
                     ok: true,
30 30
                     handler: pluginConfig.handlerType,

+ 0
- 1
backend/lib/routes/user/generatejwt.js Datei anzeigen

@@ -23,7 +23,6 @@ module.exports = {
23 23
         handler: async function (request, h) {
24 24
             const { userService } = request.server.services()
25 25
             const res = request.payload
26
-
27 26
             const token = await userService.createToken(res)
28 27
             try {
29 28
                 return {

+ 26
- 10
backend/lib/routes/user/verifyemail.js Datei anzeigen

@@ -14,7 +14,7 @@ const pluginConfig = {
14 14
 
15 15
 module.exports = {
16 16
     method: 'GET',
17
-    path: '/verify/{hash}',
17
+    path: '/verify/{hashedEmail}',
18 18
     options: {
19 19
         ...pluginConfig.docs.get,
20 20
         tags: ['api'],
@@ -22,18 +22,34 @@ module.exports = {
22 22
         cors: true,
23 23
         handler: async function (request, h) {
24 24
             const { userService } = request.server.services()
25
-            const hashToMatch = await userService.hashedEmail
26
-            const hash = request.params.hash
27
-            const hashesMatch = hashToMatch === hash ? true : false
25
+            const hash = request.params.hashedEmail
26
+
28 27
             try {
29
-                if (hashesMatch) {
30
-                    return {
31
-                        ok: true,
32
-                        handler: pluginConfig.handlerType,
33
-                        data: { hashesMatch: hashesMatch },
34
-                    }
28
+                const hashToMatch = Object.keys(userService.hashedEmails).find(
29
+                    email => {
30
+                        return email === hash
31
+                    },
32
+                )
33
+                const now = Date.now()
34
+                // TODO: convert this back to a date object
35
+                const expiration = userService.hashedEmails[hashToMatch]
36
+                if (now > expiration) {
37
+                    delete userService.hashedEmails[hashToMatch]
38
+                    throw new Error(
39
+                        'you took to long to respond to the email...',
40
+                    )
41
+                }
42
+                if (!hashToMatch) {
43
+                    throw new Error('no record of email in cache')
44
+                }
45
+
46
+                return {
47
+                    ok: true,
48
+                    handler: pluginConfig.handlerType,
49
+                    data: { hashesMatch: hashToMatch === hash },
35 50
                 }
36 51
             } catch (err) {
52
+                console.log('err :=>', err)
37 53
                 return {
38 54
                     ok: false,
39 55
                     handler: pluginConfig.handlerType,

+ 11
- 10
backend/lib/services/profile/profiler.js Datei anzeigen

@@ -9,7 +9,7 @@ class CompleteProfile {
9 9
     constructor(profile, includeResponses = false, type) {
10 10
         this.user_id = profile.user_id // int user_id
11 11
         this.profile_id = profile.profile_id // int profile_id
12
-        this.user_name = 'hidden_name'// string user_name
12
+        this.user_name = 'hidden_name' // string user_name
13 13
         this.user_email = 'hidden@email.com'
14 14
         this.responses = []
15 15
         this.user_type = type
@@ -48,15 +48,16 @@ class CompleteProfile {
48 48
                     r => r.response_key_id === prefsKeys[i],
49 49
                 )
50 50
             })
51
-            this.profile_description = this.responses.find(
52
-                r => r.response_key_id === config.blurbKey,
53
-            ).val
54
-            this.profile_media = this.responses
55
-                .filter(r => r.response_key_id === config.mediaKey)
56
-                .map(r => r.val)
57
-            this.profile_languages = this.responses
58
-                .filter(r => r.response_key_id === config.langKey)
59
-                .map(r => r.val)
51
+            // TODO: uncomment, changed to get Onboarding Auth working...
52
+            // this.profile_description = this.responses.find(
53
+            // r => r.response_key_id === config.blurbKey,
54
+            // ).val
55
+            // this.profile_media = this.responses
56
+            // .filter(r => r.response_key_id === config.mediaKey)
57
+            // .map(r => r.val)
58
+            // this.profile_languages = this.responses
59
+            // .filter(r => r.response_key_id === config.langKey)
60
+            // .map(r => r.val)
60 61
         }
61 62
     }
62 63
 }

+ 18
- 7
backend/lib/services/user.js Datei anzeigen

@@ -22,7 +22,7 @@ const hashEmail = async email => {
22 22
         return undefined
23 23
     }
24 24
 }
25
-
25
+// const emailsSent = {}
26 26
 const hasher = async (pwd, steak) => {
27 27
     const hash = await pwd.hash(steak)
28 28
     const result = await pwd.verify(steak, hash)
@@ -62,8 +62,12 @@ module.exports = class UserService extends Schmervice.Service {
62 62
     constructor(...args) {
63 63
         super(...args)
64 64
         const pwd = new SecurePassword()
65
-        this.hashedEmail = ''
66
-        // this.hashedEmails = []
65
+        // TODO: Invalidate this cache somehow after a certain time period has
66
+        // passed
67
+        this.hashedEmails = {
68
+            // NOTE: key is email hash and value is timestamp in ms
69
+            // abc123456: '123456689',
70
+        }
67 71
         this.pwd = {
68 72
             hash: Util.promisify(pwd.hash.bind(pwd)),
69 73
             verify: Util.promisify(pwd.verify.bind(pwd)),
@@ -191,6 +195,7 @@ module.exports = class UserService extends Schmervice.Service {
191 195
      * @param {User} user
192 196
      * @returns {Token}
193 197
      */
198
+
194 199
     createToken(user) {
195 200
         const key = this.server.registrations['main-app-plugin'].options.jwtKey
196 201
 
@@ -208,7 +213,8 @@ module.exports = class UserService extends Schmervice.Service {
208 213
             },
209 214
             {
210 215
                 // ttlSec: 4 * 60 * 60, // 7 days
211
-                ttlSec: 60 * 3, // 3 minutes
216
+                // ttlSec: 60 * 3, // 3 minutes
217
+                ttlSec: user.expiration,
212 218
             },
213 219
         )
214 220
     }
@@ -270,6 +276,13 @@ module.exports = class UserService extends Schmervice.Service {
270 276
      * @ returns {Object}
271 277
      */
272 278
     async emailSent(userEmail) {
279
+        const hashedEmail = await hashEmail(userEmail)
280
+        if (Object.keys(this.hashedEmails).includes(hashedEmail)) {
281
+            return new Error('email address already in cache!!')
282
+        }
283
+        // Set expiration time for five minutes from now
284
+        const duration = 1000 * 60 * 5
285
+        this.hashedEmails[hashedEmail] = Date.now() + duration
273 286
         const sendSmtpEmail = {
274 287
             to: [
275 288
                 {
@@ -279,7 +292,7 @@ module.exports = class UserService extends Schmervice.Service {
279 292
             templateId: 1,
280 293
             params: {
281 294
                 // TODO: Change this in production...
282
-                link: `localhost:3000/verify/${await hashEmail(userEmail)}`,
295
+                link: `localhost:3000/verify/${hashedEmail}`,
283 296
             },
284 297
         }
285 298
 
@@ -297,7 +310,5 @@ module.exports = class UserService extends Schmervice.Service {
297 310
                 }
298 311
             },
299 312
         )
300
-        this.hashedEmail = hashEmail(userEmail)
301
-        // this.hashedEmails.push(hashEmail(userEmail))
302 313
     }
303 314
 }

+ 7
- 7
backend/package-lock.json Datei anzeigen

@@ -12,7 +12,7 @@
12 12
                 "@hapi/glue": "^8.0.0",
13 13
                 "@hapi/hapi": "^20.1.3",
14 14
                 "@hapi/inert": "^6.0.3",
15
-                "@hapi/jwt": "^2.0.1",
15
+                "@hapi/jwt": "^2.2.0",
16 16
                 "@hapi/vision": "^6.0.1",
17 17
                 "@hapipal/confidence": "^6.0.1",
18 18
                 "@hapipal/schmervice": "^2.0.0",
@@ -776,9 +776,9 @@
776 776
             }
777 777
         },
778 778
         "node_modules/@hapi/jwt": {
779
-            "version": "2.0.1",
780
-            "resolved": "https://registry.npmjs.org/@hapi/jwt/-/jwt-2.0.1.tgz",
781
-            "integrity": "sha512-6/nX/yOIk9mvs+r72LFhF177yOB4yVv3e0Nqn7cIx2CU+VruBHxMKkHraARXx6oUAtiwNuyhW+trO5QeGm9ESQ==",
779
+            "version": "2.2.0",
780
+            "resolved": "https://registry.npmjs.org/@hapi/jwt/-/jwt-2.2.0.tgz",
781
+            "integrity": "sha512-hOzQ/E0O9XemapjYddGH4ReCG5JEHz62zLeNou4Mt282yx7JknCPTTsnsqkxRE+EPVWNGXGz2E3SDlST80hjMw==",
782 782
             "dependencies": {
783 783
                 "@hapi/b64": "5.x.x",
784 784
                 "@hapi/boom": "9.x.x",
@@ -9198,9 +9198,9 @@
9198 9198
             }
9199 9199
         },
9200 9200
         "@hapi/jwt": {
9201
-            "version": "2.0.1",
9202
-            "resolved": "https://registry.npmjs.org/@hapi/jwt/-/jwt-2.0.1.tgz",
9203
-            "integrity": "sha512-6/nX/yOIk9mvs+r72LFhF177yOB4yVv3e0Nqn7cIx2CU+VruBHxMKkHraARXx6oUAtiwNuyhW+trO5QeGm9ESQ==",
9201
+            "version": "2.2.0",
9202
+            "resolved": "https://registry.npmjs.org/@hapi/jwt/-/jwt-2.2.0.tgz",
9203
+            "integrity": "sha512-hOzQ/E0O9XemapjYddGH4ReCG5JEHz62zLeNou4Mt282yx7JknCPTTsnsqkxRE+EPVWNGXGz2E3SDlST80hjMw==",
9204 9204
             "requires": {
9205 9205
                 "@hapi/b64": "5.x.x",
9206 9206
                 "@hapi/boom": "9.x.x",

+ 1
- 1
backend/package.json Datei anzeigen

@@ -20,7 +20,7 @@
20 20
         "@hapi/glue": "^8.0.0",
21 21
         "@hapi/hapi": "^20.1.3",
22 22
         "@hapi/inert": "^6.0.3",
23
-        "@hapi/jwt": "^2.0.1",
23
+        "@hapi/jwt": "^2.2.0",
24 24
         "@hapi/vision": "^6.0.1",
25 25
         "@hapipal/confidence": "^6.0.1",
26 26
         "@hapipal/schmervice": "^2.0.0",

+ 12
- 12
frontend/src/components/onboarding/Auth.vue Datei anzeigen

@@ -44,20 +44,20 @@ export default {
44 44
         if (this.survey.hasMinResponsesToCreateProfile(this.answered)) {
45 45
             const newUser = await signupUser(this.answered)
46 46
             const newUserId = newUser.user_id
47
-            const newProfile = await createProfileForUserId(
48
-                newUserId,
49
-                this.responses,
50
-            )
51
-
52
-            // sets jwt authentication cookie to be used in VerifyView.vue
53
-            // TODO: Instead of having this.answered here, simply pass profile_id?
54
-            const jwt = await this.authenticator.generateJwt(this.answered)
47
+            await createProfileForUserId(newUserId, this.responses)
48
+            const jwt = await this.authenticator.generateJwt({
49
+                ...this.answered,
50
+                expiration: 60 * 10,
51
+            })
55 52
             document.cookie = `siimee_jwt=${jwt}; path=/verify`
56
-            // this.$emit('set-pid', newProfile.profile_id)
57
-
58
-            // sends authentication email
59
-            this.authenticator.sendAuthEmail(this.answered)
60 53
         }
54
+
55
+        // sets jwt authentication cookie to be used in VerifyView.vue and
56
+        // OnboardingView.vue
57
+        // TODO: Instead of having this.answered here, simply pass profile_id?
58
+
59
+        // sends authentication email
60
+        await this.authenticator.sendAuthEmail(this.answered)
61 61
     },
62 62
     methods: {
63 63
         // TODO: remove test button above and use a watcher instead to emit this

+ 4
- 4
frontend/src/services/auth.service.js Datei anzeigen

@@ -5,12 +5,12 @@ class Authenticator {
5 5
         this.curentUser = null
6 6
     }
7 7
     async sendAuthEmail(answered) {
8
-        const emailWasSent = await db.post(`/user/sendemail/`, answered)
9
-        console.log('emailwasSent :=>', emailWasSent)
8
+        const emailWasSent = await db.post('/user/sendemail/', answered)
9
+        return emailWasSent
10 10
     }
11 11
     // NOTE: these might be better suited as POST requests
12
-    async verifyAuthEmail(hash) {
13
-        const isVerified = await db.get(`/user/verify/${hash}`)
12
+    async verifyAuthEmail(hashedEmail) {
13
+        const isVerified = await db.get(`/user/verify/${hashedEmail}`)
14 14
         return isVerified.hashesMatch
15 15
     }
16 16
     // TODO: this needs to generate the JWT with the RAW email

+ 0
- 2
frontend/src/services/login.service.js Datei anzeigen

@@ -51,8 +51,6 @@ class Login {
51 51
      */
52 52
     get isComplete() {
53 53
         // TODO: remove once Vue Router guards allow to redirect via url
54
-        console.log('this.responses :=>', this.responses)
55
-        console.log('surveyFactory.questionsFromDb :=>', surveyFactory.questionsFromDb)
56 54
         return (
57 55
             this.responses.length == surveyFactory.questionsFromDb.length &&
58 56
             surveyFactory.questionsFromDb.length > 0

+ 48
- 27
frontend/src/views/OnboardingView.vue Datei anzeigen

@@ -15,7 +15,6 @@ main.view--onboarding
15 15
                 :responses='responses'
16 16
                 :survey='survey'
17 17
                 :currentStep='currentStep'
18
-                :jwt='jwt'
19 18
                 :surveyStepsCount='survey.steps.length'
20 19
                 @handle-submit='onSubmit'
21 20
                 @update-answers='updateAnswers'
@@ -60,7 +59,8 @@ export default {
60 59
         invalidResponse: false,
61 60
         // TODO: CURRENTLY WORKING ON**
62 61
         authenticator: {},
63
-        jwt: '',
62
+        sessionToken: '',
63
+        accessToken: '',
64 64
     }),
65 65
     computed: {
66 66
         // NOTE: perhaps start this off as returning nothing
@@ -68,46 +68,67 @@ export default {
68 68
         profile: () => currentProfile,
69 69
     },
70 70
     async created() {
71
-        // console.log('this.profile.id :=>', this.profile.id)
71
+        this.survey = await surveyFactory.createSurvey()
72
+        this.authenticator = new Authenticator()
73
+
72 74
         if (document.cookie.length) {
73 75
             // TODO: Remove this this.answered section when:
74 76
             // SurveyCompleteView calls all responses from db
77
+            // BUG: NEEDS BROWSER REFRESH TO BE HIT
75 78
             const siimeeAnswers = JSON.parse(
76 79
                 this.grabCookie(document.cookie, 'siimee_answered'),
77 80
             )
78
-            this.answered = {
79
-                name: siimeeAnswers.name,
80
-                email: siimeeAnswers.email,
81
-                seeking: siimeeAnswers.seeking,
82
-            }
83
-            // TODO: Instead, login using siimee_profile_id from cookie
84
-            const siimeeToken = this.grabCookie(document.cookie, 'siimee_jwt')
85
-            this.jwt = siimeeToken ? siimeeToken : ''
86
-        }
87
-
88
-        this.survey = await surveyFactory.createSurvey()
89
-        this.authenticator = new Authenticator()
90
-        if (this.jwt.length) {
91
-            // TODO: remove once currentProfile is established and user is logged in...
92
-            const jwt = await this.authenticator.validateJwt(this.jwt)
93
-            if (jwt.isValid) {
94
-                // grab profileId from jwt payload and login here
95
-                // use this profile to grab all responses in
96
-                // SurveyCompleteView.vue
81
+            this.sessionToken = this.grabCookie(
82
+                document.cookie,
83
+                'siimee_session',
84
+            )
85
+            const sessionTokenIsValid = await this.authenticator.validateJwt(
86
+                this.sessionToken,
87
+            )
88
+            this.accessToken = this.grabCookie(document.cookie, 'siimee_access')
89
+            if (sessionTokenIsValid.isValid) {
90
+                this.answered = {
91
+                    name: siimeeAnswers.name,
92
+                    email: siimeeAnswers.email,
93
+                    seeking: siimeeAnswers.seeking,
94
+                }
97 95
                 this.goToStep(6)
98
-            } else {
99
-                this.goToStep(0)
100
-                this.jwt = ''
101
-            }
96
+            } else this.goToStep(0)
102 97
         }
103 98
     },
104 99
     methods: {
105 100
         onSubmit() {
106 101
             console.log(JSON.stringify(this.answered))
107 102
         },
108
-        goToStep(num) {
103
+        async goToStep(num) {
104
+            if (num > 6) {
105
+                this.validateAccessToken()
106
+            }
109 107
             this.currentStep = num
110 108
         },
109
+        async validateAccessToken() {
110
+            const validatedAccessToken = await this.authenticator.validateJwt(
111
+                this.accessToken,
112
+            )
113
+            if (!validatedAccessToken.isValid) {
114
+                const sessionTokenIsValid = await this.validateSessionToken()
115
+                if (!sessionTokenIsValid) {
116
+                    this.goToStep(0)
117
+                }
118
+            }
119
+        },
120
+        async validateSessionToken() {
121
+            const validatedSessionToken = await this.authenticator.validateJwt(
122
+                this.sessionToken,
123
+            )
124
+            if (validatedSessionToken.isValid) {
125
+                this.accessToken = await this.authenticator.generateJwt({
126
+                    ...this.answered,
127
+                    expiration: 60 * 3,
128
+                })
129
+                return true
130
+            } else return false
131
+        },
111 132
         grabCookie(cookieString, cookieKey) {
112 133
             const cookies = cookieString.split('; ').reduce((prev, current) => {
113 134
                 const [name, ...value] = current.split('=')

+ 21
- 9
frontend/src/views/VerifyView.vue Datei anzeigen

@@ -10,24 +10,29 @@ export default {
10 10
     name: 'VerifyView',
11 11
     data: () => ({
12 12
         authenticator: {},
13
+        answers: {},
13 14
     }),
14 15
     async created() {
15 16
         this.authenticator = new Authenticator()
16 17
         const hashEmail = this.$route.params.email
18
+
19
+        // TODO: generate a token on the backend here and have it sent over in
20
+        // the headers intead of setting it directly with document.cookie
21
+
17 22
         const hashesMatch = await this.authenticator.verifyAuthEmail(hashEmail)
18 23
         const siimeeToken = this.grabToken(document.cookie)
19 24
 
20
-        // TODO: remove once currentProfile is established and user is logged in...
21
-        // NOTE: another siimee_jwt cookie for onboarding path instead of verify
22
-        document.cookie = `siimee_jwt=${siimeeToken}; path=/onboarding`
23
-
25
+        // TODO: Then send this token and receive a different token
24 26
         const jwt = await this.authenticator.validateJwt(siimeeToken)
25
-
27
+        this.answers = jwt.payload
28
+        const accessToken = await this.generateAccessToken()
26 29
         if (jwt.isValid && hashesMatch) {
27
-            // TODO: set jwt.payload.profile_id as siimee_profile_id
28
-            // remove answers, will query db at SurveyCompleteView.vue
29
-            const siimeeAnswers = JSON.stringify(jwt.payload)
30
-            document.cookie = `siimee_answered=${siimeeAnswers} ; path=/onboarding`
30
+            // TODO: establish session access token cookie here
31
+            const siimeeAnswers = JSON.stringify(this.answers)
32
+            document.cookie = `siimee_answered=${siimeeAnswers} ; path=/onboarding; secure`
33
+            // TODO: for session/access cookies, make sure to set http only flag and expiration
34
+            document.cookie = `siimee_session=${siimeeToken} ; path=/onboarding; secure`
35
+            document.cookie = `siimee_access=${accessToken}; path=/onboarding; secure`
31 36
             this.$router.push('/onboarding')
32 37
         }
33 38
         // else {
@@ -42,6 +47,13 @@ export default {
42 47
             }, {})
43 48
             return 'siimee_jwt' in cookies ? cookies['siimee_jwt'] : undefined
44 49
         },
50
+        async generateAccessToken() {
51
+            const accessJwt = await this.authenticator.generateJwt({
52
+                ...this.answers,
53
+                expiration: 60 * 3, // testing for now... extend to 1 hour?
54
+            })
55
+            return accessJwt
56
+        },
45 57
     },
46 58
 }
47 59
 </script>

Laden…
Abbrechen
Speichern