Kaynağa Gözat

:construction: Retrying with auth during survey

tags/0.0.3^2
tomit4 3 yıl önce
ebeveyn
işleme
9023557114

+ 4
- 0
backend/lib/plugins/user.js Dosyayı Görüntüle

@@ -16,6 +16,8 @@ const UserEmailRoute = require('../routes/user/email.js')
16 16
 const UserVerifyEmailRoute = require('../routes/user/verifyemail.js')
17 17
 const UserGenerateJWTRoute = require('../routes/user/generatejwt.js')
18 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 21
 const UserPassword = require('../routes/user/authentication')
20 22
 
21 23
 const UserService = require('../services/user')
@@ -57,6 +59,8 @@ module.exports = {
57 59
         await server.route(UserVerifyEmailRoute)
58 60
         await server.route(UserGenerateJWTRoute)
59 61
         await server.route(UserValidateJWTRoute)
62
+        await server.route(UserCheckCache)
63
+        await server.route(UserByEmail)
60 64
         await server.route(UserPassword)
61 65
     },
62 66
 }

+ 57
- 0
backend/lib/routes/user/check-cache.js Dosyayı Görüntüle

@@ -0,0 +1,57 @@
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 Dosyayı Görüntüle

@@ -0,0 +1,55 @@
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 Dosyayı Görüntüle

@@ -104,6 +104,23 @@ module.exports = class UserService extends Schmervice.Service {
104 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 125
      * Signup function
109 126
      * @param {*} param0
@@ -199,14 +216,14 @@ module.exports = class UserService extends Schmervice.Service {
199 216
     createToken(user) {
200 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 221
                 aud: 'urn:audience:test',
205 222
                 iss: 'urn:issuer:test',
206 223
                 email: user.email,
207 224
                 name: user.name,
208 225
                 seeking: user.seeking,
209
-                profile_id: user.profile_id,
226
+                // profile_id: user.profile_id,
210 227
             },
211 228
             {
212 229
                 key: key,
@@ -218,6 +235,7 @@ module.exports = class UserService extends Schmervice.Service {
218 235
                 ttlSec: user.expiration,
219 236
             },
220 237
         )
238
+        return token
221 239
     }
222 240
 
223 241
     /**
@@ -271,6 +289,29 @@ module.exports = class UserService extends Schmervice.Service {
271 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 316
      * Sends a Transactional Email via Brevo
276 317
      * @ returns {Object}

+ 3
- 6
frontend/src/components/onboarding/Auth.vue Dosyayı Görüntüle

@@ -50,16 +50,13 @@ export default {
50 50
                 password: userPass.val,
51 51
             })
52 52
             const newUserId = newUser.user_id
53
-            const newProfile = await createProfileForUserId(
54
-                newUserId,
55
-                this.responses,
56
-            )
53
+            await createProfileForUserId(newUserId, this.responses)
57 54
             const jwt = await this.authenticator.generateJwt({
58 55
                 ...this.answered,
59 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 60
             await this.authenticator.sendAuthEmail(this.answered)
64 61
         } else {
65 62
             console.error('ERROR :=>')

+ 4
- 2
frontend/src/services/auth.service.js Dosyayı Görüntüle

@@ -8,12 +8,14 @@ class Authenticator {
8 8
         const emailWasSent = await db.post('/user/sendemail/', answered)
9 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 15
     async verifyAuthEmail(hashedEmail) {
13 16
         const isVerified = await db.get(`/user/verify/${hashedEmail}`)
14 17
         return isVerified.hashesMatch
15 18
     }
16
-    // TODO: this needs to generate the JWT with the RAW email
17 19
     async generateJwt(res) {
18 20
         const token = await db.post('/user/generatejwt', res)
19 21
         return token.jwt

+ 7
- 1
frontend/src/services/user.service.js Dosyayı Görüntüle

@@ -1,3 +1,4 @@
1
+import { when } from 'joi'
1 2
 import { db } from '../utils/db.js'
2 3
 
3 4
 /**
@@ -14,4 +15,9 @@ const signupUser = async user => {
14 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 Dosyayı Görüntüle

@@ -35,6 +35,11 @@ main.view--onboarding
35 35
 
36 36
 <script>
37 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 43
 import { surveyFactory } from '@/utils'
39 44
 import { currentProfile } from '../services/'
40 45
 import stepViews from '@/components/onboarding'
@@ -54,11 +59,8 @@ export default {
54 59
         responses: [],
55 60
         currentStep: 0,
56 61
         survey: null,
57
-        currentProfileId: null,
58 62
         invalidResponse: false,
59 63
         authenticator: {},
60
-        sessionToken: '',
61
-        accessToken: '',
62 64
     }),
63 65
     computed: {
64 66
         cP() {
@@ -68,53 +70,38 @@ export default {
68 70
     async created() {
69 71
         this.survey = await surveyFactory.createSurvey()
70 72
         this.authenticator = new Authenticator()
71
-
72 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,47 +109,9 @@ export default {
122 109
         onSubmit() {
123 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 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 115
         grabCookie(cookieKey) {
167 116
             const cookies = document.cookie
168 117
                 .split('; ')
@@ -194,6 +143,8 @@ export default {
194 143
                 response.response_key_id = payload.question.response_key_id
195 144
                 response.val = payload.input
196 145
                 this.responses.push(response)
146
+                console.log('this.answered :=>', this.answered)
147
+                console.log('this.responses :=>', this.responses)
197 148
 
198 149
                 // sends latest survey response to db
199 150
                 if (this.currentProfileId) {

+ 21
- 26
frontend/src/views/VerifyView.vue Dosyayı Görüntüle

@@ -5,9 +5,6 @@
5 5
 </template>
6 6
 
7 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 8
 import { Authenticator } from '../services/auth.service.js'
12 9
 export default {
13 10
     name: 'VerifyView',
@@ -19,34 +16,32 @@ export default {
19 16
         this.authenticator = new Authenticator()
20 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 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 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 46
         async generateAccessToken() {
52 47
             const accessJwt = await this.authenticator.generateJwt({

Loading…
İptal
Kaydet