Parcourir la source

:construction: Retrying with auth during survey

juan_spike
tomit4 il y a 3 ans
Parent
révision
c0d4659786

+ 4
- 0
backend/lib/plugins/user.js Voir le fichier

16
 const UserVerifyEmailRoute = require('../routes/user/verifyemail.js')
16
 const UserVerifyEmailRoute = require('../routes/user/verifyemail.js')
17
 const UserGenerateJWTRoute = require('../routes/user/generatejwt.js')
17
 const UserGenerateJWTRoute = require('../routes/user/generatejwt.js')
18
 const UserValidateJWTRoute = require('../routes/user/validatejwt.js')
18
 const UserValidateJWTRoute = require('../routes/user/validatejwt.js')
19
+const UserCheckCache = require('../routes/user/check-cache.js')
20
+const UserByEmail = require('../routes/user/user-by-email.js')
19
 const UserPassword = require('../routes/user/authentication')
21
 const UserPassword = require('../routes/user/authentication')
20
 
22
 
21
 const UserService = require('../services/user')
23
 const UserService = require('../services/user')
57
         await server.route(UserVerifyEmailRoute)
59
         await server.route(UserVerifyEmailRoute)
58
         await server.route(UserGenerateJWTRoute)
60
         await server.route(UserGenerateJWTRoute)
59
         await server.route(UserValidateJWTRoute)
61
         await server.route(UserValidateJWTRoute)
62
+        await server.route(UserCheckCache)
63
+        await server.route(UserByEmail)
60
         await server.route(UserPassword)
64
         await server.route(UserPassword)
61
     },
65
     },
62
 }
66
 }

+ 57
- 0
backend/lib/routes/user/check-cache.js Voir le fichier

1
+'use strict'
2
+
3
+const Joi = require('joi')
4
+
5
+const pluginConfig = {
6
+    handlerType: 'email',
7
+    docs: {
8
+        get: {
9
+            description: 'checks if user email is in cache',
10
+            notes: 'Checks if user email is in email cache and returns boolean',
11
+        },
12
+    },
13
+}
14
+
15
+module.exports = {
16
+    method: 'POST',
17
+    path: '/checkcache/',
18
+    options: {
19
+        ...pluginConfig.docs.get,
20
+        tags: ['api'],
21
+        auth: false,
22
+        cors: true,
23
+        handler: async function (request, h) {
24
+            const { userService } = request.server.services()
25
+            const userEmail = request.payload
26
+            try {
27
+                const emailIsInCache = await userService.checkEmailCache(
28
+                    userEmail,
29
+                )
30
+                return {
31
+                    ok: true,
32
+                    handler: pluginConfig.handlerType,
33
+                    data: { emailIsInCache: emailIsInCache },
34
+                }
35
+            } catch (err) {
36
+                return {
37
+                    ok: false,
38
+                    handler: pluginConfig.handlerType,
39
+                    data: {
40
+                        error: err,
41
+                    },
42
+                }
43
+            }
44
+        },
45
+        validate: {
46
+            failAction: 'log',
47
+        },
48
+        response: {
49
+            schema: Joi.object({
50
+                ok: Joi.bool(),
51
+                handler: Joi.string(),
52
+                data: Joi.object(),
53
+            }).label('email_res'),
54
+            failAction: 'log',
55
+        },
56
+    },
57
+}

+ 55
- 0
backend/lib/routes/user/user-by-email.js Voir le fichier

1
+'use strict'
2
+
3
+const Joi = require('joi')
4
+
5
+const pluginConfig = {
6
+    handlerType: 'email',
7
+    docs: {
8
+        get: {
9
+            description: 'Fetches User Data by Email',
10
+            notes: 'Grabs the User Data by Email for use in survey validation',
11
+        },
12
+    },
13
+}
14
+
15
+module.exports = {
16
+    method: 'GET',
17
+    path: '/fetchbymail/{email}',
18
+    options: {
19
+        ...pluginConfig.docs.get,
20
+        tags: ['api'],
21
+        auth: false,
22
+        cors: true,
23
+        handler: async function (request, h) {
24
+            const email = request.params.email
25
+            const { userService } = request.server.services()
26
+            const data = await userService.findByUserEmail(email)
27
+            try {
28
+                return {
29
+                    ok: true,
30
+                    handler: pluginConfig.handlerType,
31
+                    data: data,
32
+                }
33
+            } catch (err) {
34
+                return {
35
+                    ok: false,
36
+                    handler: pluginConfig.handlerType,
37
+                    data: {
38
+                        error: err,
39
+                    },
40
+                }
41
+            }
42
+        },
43
+        validate: {
44
+            failAction: 'log',
45
+        },
46
+        response: {
47
+            schema: Joi.object({
48
+                ok: Joi.bool(),
49
+                handler: Joi.string(),
50
+                data: Joi.object(),
51
+            }).label('fetchbymail'),
52
+            failAction: 'log',
53
+        },
54
+    },
55
+}

+ 43
- 2
backend/lib/services/user.js Voir le fichier

104
             .where({ user_name: username })
104
             .where({ user_name: username })
105
     }
105
     }
106
 
106
 
107
+    /**
108
+     * Use knew to find first user with useremail
109
+     * @param {*} username
110
+     * @param {*} txn
111
+     * @returns
112
+     */
113
+    async findByUserEmail(userEmail, txn) {
114
+        console.log('userEmail :=>', userEmail)
115
+        const { User } = this.server.models()
116
+        const user = await User.query(txn)
117
+            .throwIfNotFound()
118
+            .first()
119
+            .where({ user_email: userEmail })
120
+        console.log('user :=>', user)
121
+        return user
122
+    }
123
+
107
     /**
124
     /**
108
      * Signup function
125
      * Signup function
109
      * @param {*} param0
126
      * @param {*} param0
199
     createToken(user) {
216
     createToken(user) {
200
         const key = this.server.registrations['main-app-plugin'].options.jwtKey
217
         const key = this.server.registrations['main-app-plugin'].options.jwtKey
201
 
218
 
202
-        return Jwt.token.generate(
219
+        const token = Jwt.token.generate(
203
             {
220
             {
204
                 aud: 'urn:audience:test',
221
                 aud: 'urn:audience:test',
205
                 iss: 'urn:issuer:test',
222
                 iss: 'urn:issuer:test',
206
                 email: user.email,
223
                 email: user.email,
207
                 name: user.name,
224
                 name: user.name,
208
                 seeking: user.seeking,
225
                 seeking: user.seeking,
209
-                profile_id: user.profile_id,
226
+                // profile_id: user.profile_id,
210
             },
227
             },
211
             {
228
             {
212
                 key: key,
229
                 key: key,
218
                 ttlSec: user.expiration,
235
                 ttlSec: user.expiration,
219
             },
236
             },
220
         )
237
         )
238
+        return token
221
     }
239
     }
222
 
240
 
223
     /**
241
     /**
271
         return passwordRow ? passwordRow.token : null
289
         return passwordRow ? passwordRow.token : null
272
     }
290
     }
273
 
291
 
292
+    async checkEmailCache(userEmail) {
293
+        const hashedEmail = await hashEmail(userEmail)
294
+        const now = Date.now()
295
+        const expiration = this.hashedEmails[hashedEmail]
296
+        console.log('this.hashedEmails :=>', this.hashedEmails)
297
+        const emailIsInCache = Object.keys(this.hashedEmails).includes(
298
+            hashedEmail,
299
+        )
300
+        const emailIsExpired = now > expiration ? true : false
301
+        console.log('emailIsInCache :=>', emailIsInCache)
302
+        console.log('emailIsExpired :=>', emailIsExpired)
303
+        if (emailIsInCache && !emailIsExpired) {
304
+            return true
305
+        } else {
306
+            // try {
307
+            // delete this.hashedEmails[hashedEmail]
308
+            // } catch (err) {
309
+            // console.error('ERROR :=>', err)
310
+            // }
311
+            return false
312
+        }
313
+    }
314
+
274
     /**
315
     /**
275
      * Sends a Transactional Email via Brevo
316
      * Sends a Transactional Email via Brevo
276
      * @ returns {Object}
317
      * @ returns {Object}

+ 3
- 6
frontend/src/components/onboarding/Auth.vue Voir le fichier

50
                 password: userPass.val,
50
                 password: userPass.val,
51
             })
51
             })
52
             const newUserId = newUser.user_id
52
             const newUserId = newUser.user_id
53
-            const newProfile = await createProfileForUserId(
54
-                newUserId,
55
-                this.responses,
56
-            )
53
+            await createProfileForUserId(newUserId, this.responses)
57
             const jwt = await this.authenticator.generateJwt({
54
             const jwt = await this.authenticator.generateJwt({
58
                 ...this.answered,
55
                 ...this.answered,
59
                 expiration: 60 * 10,
56
                 expiration: 60 * 10,
60
-                profile_id: newProfile.profile_id,
61
             })
57
             })
62
-            document.cookie = `siimee_jwt=${jwt}; path=/verify; secure`
58
+            console.log('jwt :=>', jwt)
59
+            document.cookie = `siimee_session_verify=${jwt}; max-age=600; path=/verify; secure`
63
             await this.authenticator.sendAuthEmail(this.answered)
60
             await this.authenticator.sendAuthEmail(this.answered)
64
         } else {
61
         } else {
65
             console.error('ERROR :=>')
62
             console.error('ERROR :=>')

+ 4
- 2
frontend/src/services/auth.service.js Voir le fichier

8
         const emailWasSent = await db.post('/user/sendemail/', answered)
8
         const emailWasSent = await db.post('/user/sendemail/', answered)
9
         return emailWasSent
9
         return emailWasSent
10
     }
10
     }
11
-    // NOTE: these might be better suited as POST requests
11
+    async checkEmailCache(email) {
12
+        const emailIsInCache = await db.post('/user/checkcache/', email)
13
+        return emailIsInCache.emailIsInCache
14
+    }
12
     async verifyAuthEmail(hashedEmail) {
15
     async verifyAuthEmail(hashedEmail) {
13
         const isVerified = await db.get(`/user/verify/${hashedEmail}`)
16
         const isVerified = await db.get(`/user/verify/${hashedEmail}`)
14
         return isVerified.hashesMatch
17
         return isVerified.hashesMatch
15
     }
18
     }
16
-    // TODO: this needs to generate the JWT with the RAW email
17
     async generateJwt(res) {
19
     async generateJwt(res) {
18
         const token = await db.post('/user/generatejwt', res)
20
         const token = await db.post('/user/generatejwt', res)
19
         return token.jwt
21
         return token.jwt

+ 7
- 1
frontend/src/services/user.service.js Voir le fichier

1
+import { when } from 'joi'
1
 import { db } from '../utils/db.js'
2
 import { db } from '../utils/db.js'
2
 
3
 
3
 /**
4
 /**
14
     return await db.post(`/user/signup`, payload)
15
     return await db.post(`/user/signup`, payload)
15
 }
16
 }
16
 
17
 
17
-export { signupUser }
18
+const fetchUserByEmail = async userEmail => {
19
+    console.log('userEmail :=>', userEmail)
20
+    return await db.get(`/user/fetchbymail/${userEmail}`)
21
+}
22
+
23
+export { signupUser, fetchUserByEmail }

+ 37
- 86
frontend/src/views/OnboardingView.vue Voir le fichier

35
 
35
 
36
 <script>
36
 <script>
37
 import { Authenticator } from '../services/auth.service.js'
37
 import { Authenticator } from '../services/auth.service.js'
38
+import { fetchUserByEmail } from '../services/user.service.js'
39
+import {
40
+    fetchProfilesByUserId,
41
+    fetchProfileByProfileId,
42
+} from '../services/profile.service.js'
38
 import { surveyFactory } from '@/utils'
43
 import { surveyFactory } from '@/utils'
39
 import { currentProfile } from '../services/'
44
 import { currentProfile } from '../services/'
40
 import stepViews from '@/components/onboarding'
45
 import stepViews from '@/components/onboarding'
54
         responses: [],
59
         responses: [],
55
         currentStep: 0,
60
         currentStep: 0,
56
         survey: null,
61
         survey: null,
57
-        currentProfileId: null,
58
         invalidResponse: false,
62
         invalidResponse: false,
59
         authenticator: {},
63
         authenticator: {},
60
-        sessionToken: '',
61
-        accessToken: '',
62
     }),
64
     }),
63
     computed: {
65
     computed: {
64
         cP() {
66
         cP() {
68
     async created() {
70
     async created() {
69
         this.survey = await surveyFactory.createSurvey()
71
         this.survey = await surveyFactory.createSurvey()
70
         this.authenticator = new Authenticator()
72
         this.authenticator = new Authenticator()
71
-
72
         if (document.cookie.length) {
73
         if (document.cookie.length) {
73
-            // TODO: Heavy Refactor needed, obvious code smells
74
-            // BUG: NEEDS BROWSER REFRESH AFTER VERIFYING EMAIL AND REDIRECT BACK TO ONBOARDING
75
-            // BUG: CURRENT IMPLEMENTATION HAS COOKIES THAT NEVER EXPIRE
76
-            const siimeeAnswered = this.grabCookie('siimee_answered')
77
-            const myCurrentStep = this.grabCookie('siimee_current_step')
78
-            const myCurrentAnswers = this.grabCookie('siimee_cache_answered')
79
-            const myCurrentResponses = this.grabCookie('siimee_cache_responses')
80
-            this.sessionToken = this.grabCookie('siimee_session') || ''
81
-            // TODO: START REFACTOR HERE...
82
-            if (siimeeAnswered) {
83
-                const siimeeAnswers = JSON.parse(siimeeAnswered)
84
-                const sessionTokenIsValid =
85
-                    await this.authenticator.validateJwt(this.sessionToken)
86
-                this.accessToken = this.grabCookie('siimee_access')
87
-                if (sessionTokenIsValid.isValid) {
88
-                    this.answered = {
89
-                        name: siimeeAnswers.name,
90
-                        email: siimeeAnswers.email,
91
-                        seeking: siimeeAnswers.seeking,
74
+            const sessionToken = this.grabCookie('siimee_session_onboarding')
75
+            if (sessionToken) {
76
+                const sessionData = await this.authenticator.validateJwt(
77
+                    sessionToken,
78
+                )
79
+                // NOTE: Left off here, INCOMPLETE, no ACCESS TOKEN yet, crazy amount of logic here...
80
+                if (sessionToken.isValid) {
81
+                    const userEmail = sessionData.payload.email
82
+                    const emailIsInCache =
83
+                        await this.authenticator.checkEmailCache(userEmail)
84
+                    if (emailIsInCache) {
85
+                        const user = await fetchUserByEmail(userEmail)
86
+                        const userId = user.user_id
87
+                        const profilesFromUserId = await fetchProfilesByUserId(
88
+                            userId,
89
+                        )
90
+                        let profileId
91
+                        if (profilesFromUserId.length === 1) {
92
+                            profileId = profilesFromUserId[0].profile_id
93
+                        }
94
+                        const profile = await fetchProfileByProfileId(profileId)
95
+                        profile.responses.forEach(response => {
96
+                            this.responses.push({
97
+                                response_key_id: response.response_key_id,
98
+                                val: response.val,
99
+                            })
100
+                        })
101
+                        this.currentStep = 6
102
+                        this.goToStep(this.currentStep)
92
                     }
103
                     }
93
-                    this.currentProfileId = siimeeAnswers.profile_id
94
-                    this.responses = [
95
-                        { response_key_id: 8, val: siimeeAnswers.email },
96
-                        { response_key_id: 7, val: siimeeAnswers.name },
97
-                        { response_key_id: 11, val: siimeeAnswers.seeking },
98
-                    ]
99
-                    document.cookie = `siimee_current_step=${this.currentStep}; max-age=600 ; path=/onboarding ; secure`
100
-                    document.cookie = `siimee_cache_answered=${JSON.stringify(
101
-                        this.answered,
102
-                    )}; max-age=600 ; path=/onboarding ; secure`
103
-                    document.cookie = `siimee_cache_responses=${JSON.stringify(
104
-                        this.responses,
105
-                    )}; max-age=600 ; path=/onboarding ; secure`
106
-                    document.cookie = 'siimee_answered='
107
-                    this.currentStep = 6
108
-                    this.goToStep(this.currentStep)
109
                 }
104
                 }
110
-            } else if (myCurrentStep) {
111
-                this.answered = JSON.parse(myCurrentAnswers)
112
-                this.responses = JSON.parse(myCurrentResponses)
113
-                this.currentStep = myCurrentStep
114
-                this.goToStep(Number(myCurrentStep) + 1)
115
-            } else {
116
-                this.currentStep = 0
117
-                this.goToStep(this.currentStep)
118
             }
105
             }
119
         }
106
         }
120
     },
107
     },
122
         onSubmit() {
109
         onSubmit() {
123
             console.log(JSON.stringify(this.answered))
110
             console.log(JSON.stringify(this.answered))
124
         },
111
         },
125
-        async goToStep(num, maxAge) {
126
-            maxAge = 600 // temp measure
127
-            document.cookie = `siimee_current_step=${Number(
128
-                this.currentStep,
129
-            )}; max-age=${maxAge} ; path=/onboarding ; secure`
130
-            document.cookie = `siimee_cache_answered=${JSON.stringify(
131
-                this.answered,
132
-            )}; max-age=${maxAge} ; path=/onboarding ; secure`
133
-            document.cookie = `siimee_cache_responses=${JSON.stringify(
134
-                this.responses,
135
-            )}; max-age=${maxAge} ; path=/onboarding ; secure`
136
-
137
-            if (num > 6) {
138
-                this.validateAccessToken()
139
-            }
112
+        async goToStep(num) {
140
             this.currentStep = num
113
             this.currentStep = num
141
         },
114
         },
142
-        // TODO: Refactor to use cookie's max-age attribute instead of network call for jwt auth
143
-        async validateAccessToken() {
144
-            const validatedAccessToken = await this.authenticator.validateJwt(
145
-                this.accessToken,
146
-            )
147
-            if (!validatedAccessToken || !validatedAccessToken.isValid) {
148
-                const sessionTokenIsValid = await this.validateSessionToken()
149
-                if (!sessionTokenIsValid) {
150
-                    this.goToStep(0)
151
-                }
152
-            }
153
-        },
154
-        async validateSessionToken() {
155
-            const validatedSessionToken = await this.authenticator.validateJwt(
156
-                this.sessionToken,
157
-            )
158
-            if (!validatedSessionToken || validatedSessionToken.isValid) {
159
-                this.accessToken = await this.authenticator.generateJwt({
160
-                    ...this.answered,
161
-                    expiration: 60 * 3,
162
-                })
163
-                return true
164
-            } else return false
165
-        },
166
         grabCookie(cookieKey) {
115
         grabCookie(cookieKey) {
167
             const cookies = document.cookie
116
             const cookies = document.cookie
168
                 .split('; ')
117
                 .split('; ')
194
                 response.response_key_id = payload.question.response_key_id
143
                 response.response_key_id = payload.question.response_key_id
195
                 response.val = payload.input
144
                 response.val = payload.input
196
                 this.responses.push(response)
145
                 this.responses.push(response)
146
+                console.log('this.answered :=>', this.answered)
147
+                console.log('this.responses :=>', this.responses)
197
 
148
 
198
                 // sends latest survey response to db
149
                 // sends latest survey response to db
199
                 if (this.currentProfileId) {
150
                 if (this.currentProfileId) {

+ 21
- 26
frontend/src/views/VerifyView.vue Voir le fichier

5
 </template>
5
 </template>
6
 
6
 
7
 <script>
7
 <script>
8
-// NOTE: If the httponly flag is to be used with these cookies,
9
-// this file will need to be rewritten as an .html file due to the way
10
-// that Hapi sets cookies via the h.state() method
11
 import { Authenticator } from '../services/auth.service.js'
8
 import { Authenticator } from '../services/auth.service.js'
12
 export default {
9
 export default {
13
     name: 'VerifyView',
10
     name: 'VerifyView',
19
         this.authenticator = new Authenticator()
16
         this.authenticator = new Authenticator()
20
         const hashEmail = this.$route.params.email
17
         const hashEmail = this.$route.params.email
21
 
18
 
22
-        // TODO: generate a token on the backend here and have it sent over in
23
-        // the headers intead of setting it directly with document.cookie (see note above)
24
-
25
         const hashesMatch = await this.authenticator.verifyAuthEmail(hashEmail)
19
         const hashesMatch = await this.authenticator.verifyAuthEmail(hashEmail)
26
-        const siimeeToken = this.grabToken(document.cookie)
20
+        const siimeeToken = this.grabCookie('siimee_session_verify')
27
 
21
 
28
-        // TODO: Then send this token and receive a different token
29
-        const jwt = await this.authenticator.validateJwt(siimeeToken)
30
-        this.answers = jwt.payload
31
-        const accessToken = await this.generateAccessToken()
32
-        if (jwt.isValid && hashesMatch) {
33
-            const siimeeAnswers = JSON.stringify(this.answers)
34
-            document.cookie = `siimee_answered=${siimeeAnswers}; max-age=360 ; path=/onboarding; secure`
35
-            document.cookie = `siimee_session=${siimeeToken} ; max-age=360 ; path=/onboarding; secure`
36
-            document.cookie = `siimee_access=${accessToken}; max-age=360 ; path=/onboarding; secure`
37
-            this.$router.push('/onboarding')
22
+        if (siimeeToken) {
23
+            const jwt = await this.authenticator.validateJwt(siimeeToken)
24
+            if (jwt.isValid && hashesMatch) {
25
+                document.cookie = `siimee_session_onboarding=${siimeeToken}; max-age=600; path=/onboarding; secure`
26
+                document.cookie = 'siimee_session_verify='
27
+                this.$router.push('/onboarding')
28
+            }
29
+        } else {
30
+            console.error('ERROR :=>')
38
         }
31
         }
39
-        // else {
40
-        // render ERROR message above or redirect to 404 (or both?)
41
     },
32
     },
42
     methods: {
33
     methods: {
43
-        grabToken(cookieString) {
44
-            const cookies = cookieString.split('; ').reduce((prev, current) => {
45
-                const [name, ...value] = current.split('=')
46
-                prev[name] = value.join('=')
47
-                return prev
48
-            }, {})
49
-            return 'siimee_jwt' in cookies ? cookies['siimee_jwt'] : undefined
34
+        grabCookie(cookieKey) {
35
+            const cookies = document.cookie
36
+                .split('; ')
37
+                .reduce((prev, current) => {
38
+                    const [name, ...value] = current.split('=')
39
+                    prev[name] = value.join('=')
40
+                    return prev
41
+                }, {})
42
+            return `${cookieKey}` in cookies
43
+                ? cookies[`${cookieKey}`]
44
+                : undefined
50
         },
45
         },
51
         async generateAccessToken() {
46
         async generateAccessToken() {
52
             const accessJwt = await this.authenticator.generateJwt({
47
             const accessJwt = await this.authenticator.generateJwt({

Chargement…
Annuler
Enregistrer