Browse Source

:construction: Continued working on email auth/save save survey state

tags/0.0.3^2
tomit4 3 years ago
parent
commit
73432c4c5b

+ 4
- 0
backend/lib/plugins/user.js View File

@@ -14,6 +14,8 @@ const UserLoginRoute = require('../routes/user/login')
14 14
 const UserSignupRoute = require('../routes/user/signup')
15 15
 const UserEmailRoute = require('../routes/user/email.js')
16 16
 const UserVerifyEmailRoute = require('../routes/user/verifyemail.js')
17
+const UserGenerateJWTRoute = require('../routes/user/generatejwt.js')
18
+const UserValidateJWTRoute = require('../routes/user/validatejwt.js')
17 19
 const UserPassword = require('../routes/user/authentication')
18 20
 
19 21
 const UserService = require('../services/user')
@@ -53,6 +55,8 @@ module.exports = {
53 55
         await server.route(UserProfilesListRoute)
54 56
         await server.route(UserEmailRoute)
55 57
         await server.route(UserVerifyEmailRoute)
58
+        await server.route(UserGenerateJWTRoute)
59
+        await server.route(UserValidateJWTRoute)
56 60
         await server.route(UserPassword)
57 61
     },
58 62
 }

+ 58
- 0
backend/lib/routes/user/generatejwt.js View File

@@ -0,0 +1,58 @@
1
+'use strict'
2
+
3
+const Joi = require('joi')
4
+
5
+const pluginConfig = {
6
+    handlerType: 'email',
7
+    docs: {
8
+        get: {
9
+            description: 'generates jwt after verifying email',
10
+            notes: 'Generates jwt after validating email',
11
+        },
12
+    },
13
+}
14
+
15
+module.exports = {
16
+    method: 'GET',
17
+    path: '/generatejwt/{hash}',
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 hash = request.params.hash
26
+            const user = {
27
+                user_email: hash,
28
+            }
29
+            const token = await userService.createToken(user)
30
+            try {
31
+                return {
32
+                    ok: true,
33
+                    handler: pluginConfig.handlerType,
34
+                    data: { jwt: token },
35
+                }
36
+            } catch (err) {
37
+                return {
38
+                    ok: false,
39
+                    handler: pluginConfig.handlerType,
40
+                    data: {
41
+                        error: err,
42
+                    },
43
+                }
44
+            }
45
+        },
46
+        validate: {
47
+            failAction: 'log',
48
+        },
49
+        response: {
50
+            schema: Joi.object({
51
+                ok: Joi.bool(),
52
+                handler: Joi.string(),
53
+                data: Joi.object(),
54
+            }).label('generate_jwt_res'),
55
+            failAction: 'log',
56
+        },
57
+    },
58
+}

+ 56
- 0
backend/lib/routes/user/validatejwt.js View File

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

+ 20
- 1
backend/lib/services/user.js View File

@@ -204,11 +204,30 @@ module.exports = class UserService extends Schmervice.Service {
204 204
                 algorithm: 'HS256',
205 205
             },
206 206
             {
207
-                ttlSec: 4 * 60 * 60, // 7 days
207
+                // ttlSec: 4 * 60 * 60, // 7 days
208
+                ttlSec: 60 * 3, // 3 minutes
208 209
             },
209 210
         )
210 211
     }
211 212
 
213
+    /**
214
+     * Validates whether a token has expired or not
215
+     * @param {User} user
216
+     * @returns {Token}
217
+     */
218
+    validateToken(token) {
219
+        const key = this.server.registrations['main-app-plugin'].options.jwtKey
220
+        const decodedToken = Jwt.token.decode(token)
221
+        // NOTE: reveals email...perhaps unhashed email belongs here instead...
222
+        // console.log('decodedToken :=>', decodedToken)
223
+        try {
224
+            Jwt.token.verify(decodedToken, key)
225
+            return { isValid: true }
226
+        } catch (err) {
227
+            return { isValid: false, error: err.message }
228
+        }
229
+    }
230
+
212 231
     /**
213 232
      * Use knex to try to change password entry
214 233
      * @param {number} id

+ 21
- 2
frontend/src/components/onboarding/Auth.vue View File

@@ -9,6 +9,7 @@
9 9
 
10 10
 <script>
11 11
 import { Authenticator } from '../../services/auth.service.js'
12
+import { createProfileForUserId } from '../../services/profile.service'
12 13
 import { signupUser } from '../../services/user.service.js'
13 14
 
14 15
 export default {
@@ -23,14 +24,32 @@ export default {
23 24
             type: String,
24 25
             default: '',
25 26
         },
27
+        responses: {
28
+            type: String,
29
+            default: '',
30
+        },
31
+        survey: {
32
+            required: true,
33
+            type: Object,
34
+            default: () => {},
35
+        },
26 36
     },
27 37
     emits: ['update-answers'],
28 38
     data: () => ({
29 39
         authenticator: {},
30 40
     }),
31
-    created() {
41
+    async created() {
32 42
         this.authenticator = new Authenticator()
33
-        console.log('this.answered :=>', this.answered)
43
+        if (this.survey.hasMinResponsesToCreateProfile(this.answered)) {
44
+            const newUser = await signupUser(this.answered)
45
+            const newUserId = newUser.user_id
46
+            try {
47
+                await createProfileForUserId(newUserId, this.responses)
48
+            } catch (err) {
49
+                console.error('ERROR :=>', err)
50
+            }
51
+        }
52
+
34 53
         this.authenticator.sendAuthEmail(this.answered)
35 54
         // TODO: Consider waiting until email hash is verified...(i.e. not in created)
36 55
         signupUser(this.answered)

+ 5
- 2
frontend/src/entities/survey/survey.js View File

@@ -44,9 +44,12 @@ class Survey extends _baseRecord {
44 44
     }
45 45
 
46 46
     hasMinResponsesToCreateProfile(responses) {
47
-        if (responses.name &&
47
+        if (
48
+            responses.name &&
48 49
             responses.email &&
49
-            responses.seeking) {
50
+            responses.seeking &&
51
+            responses.password
52
+        ) {
50 53
             return true
51 54
         } else return false
52 55
     }

+ 1
- 1
frontend/src/router/index.js View File

@@ -61,7 +61,7 @@ const routes = [
61 61
     // TODO: remove after better implementation is found for verifying email
62 62
     // hash
63 63
     {
64
-        path: `/onboarding/:hash?`,
64
+        path: `/onboarding/`,
65 65
         component: OnboardingView,
66 66
         name: `OnboardingView`,
67 67
         meta: { requiresAuth: true, requiresCompleteProfile: false },

+ 9
- 0
frontend/src/services/auth.service.js View File

@@ -8,10 +8,19 @@ class Authenticator {
8 8
         const emailWasSent = await db.post(`/user/sendemail/`, answered)
9 9
         console.log('emailwasSent :=>', emailWasSent)
10 10
     }
11
+    // NOTE: these might be better suited as POST requests
11 12
     async verifyAuthEmail(hash) {
12 13
         const isVerified = await db.get(`/user/verify/${hash}`)
13 14
         return isVerified.hashesMatch
14 15
     }
16
+    async generateJwt(hash) {
17
+        const token = await db.get(`/user/generatejwt/${hash}`)
18
+        return token.jwt
19
+    }
20
+    async validateJwt(jwt) {
21
+        const validateJwt = await db.get(`/user/validatejwt/${jwt}`)
22
+        return validateJwt.isValid
23
+    }
15 24
 }
16 25
 
17 26
 export { Authenticator }

+ 1
- 0
frontend/src/services/user.service.js View File

@@ -8,6 +8,7 @@ const signupUser = async user => {
8 8
     const payload = {
9 9
         user_name: user.name,
10 10
         user_email: user.email,
11
+        user_pass: user.password,
11 12
         is_poster: user.seeking == 'position' ? 0 : 1,
12 13
     }
13 14
     return await db.post(`/user/signup`, payload)

+ 1
- 1
frontend/src/utils/lang.js View File

@@ -34,8 +34,8 @@ const allSteps = {
34 34
     usa: {
35 35
         email: 'email',
36 36
         name: 'name',
37
-        password: 'password',
38 37
         seeking: 'seeking',
38
+        password: 'password',
39 39
         aspect01: 'aspect-1',
40 40
         aspect02: 'aspect-2',
41 41
         aspect03: 'aspect-3',

+ 1
- 1
frontend/src/utils/survey.js View File

@@ -84,7 +84,7 @@ class SurveyFactory {
84 84
         // Splash page is placed at beginning of survey
85 85
         mutatedResponseKeys.unshift(splash)
86 86
         // Auth page is placed after email/password
87
-        mutatedResponseKeys.splice(3, 0, auth)
87
+        mutatedResponseKeys.splice(5, 0, auth)
88 88
         return mutatedResponseKeys
89 89
     }
90 90
     async getQuestions() {

+ 21
- 13
frontend/src/views/OnboardingView.vue View File

@@ -12,11 +12,12 @@ main.view--onboarding
12 12
                 :is='step.component'
13 13
                 :question='step'
14 14
                 :answered='answered'
15
+                :responses='responses'
16
+                :survey='survey'
15 17
                 :currentStep='currentStep'
16 18
                 :surveyStepsCount='survey.steps.length'
17 19
                 @handle-submit='onSubmit'
18 20
                 @update-answers='updateAnswers'
19
-                @resume-survey='resumeSurvey'
20 21
                 v-if='step && currentStep == i'
21 22
             ) 
22 23
         .invalidResponseMessage(
@@ -36,7 +37,6 @@ main.view--onboarding
36 37
 import { Authenticator } from '../services/auth.service.js'
37 38
 import { surveyFactory } from '@/utils'
38 39
 import stepViews from '@/components/onboarding'
39
-import VerifyView from './VerifyView.vue'
40 40
 import SurveyCompleteView from './SurveyCompleteView.vue'
41 41
 
42 42
 // import savesurveybyprofileid - call it on submit
@@ -56,20 +56,23 @@ export default {
56 56
         currentProfileId: null,
57 57
         invalidResponse: false,
58 58
         // TODO: CURRENTLY WORKING ON**
59
-        isAuthenticated: false,
60 59
         authenticator: {},
60
+        jwt: '',
61 61
     }),
62 62
     async created() {
63
+        if (document.cookie.length) {
64
+            const siimeeToken = this.grabToken(document.cookie)
65
+            this.jwt = siimeeToken ? siimeeToken : ''
66
+        }
63 67
         this.survey = await surveyFactory.createSurvey()
64 68
         this.authenticator = new Authenticator()
65
-        // TODO: Replace with better implementation, hacky workaround for now...
66
-        if (this.$route.params.hash) {
67
-            const hashEmail = this.$route.params.hash
68
-            const hashesMatch = this.authenticator.verifyAuthEmail(hashEmail)
69
-            if (hashesMatch) {
70
-                this.currentStep = 4
69
+        if (this.jwt.length) {
70
+            const jwtStillValid = await this.authenticator.validateJwt(this.jwt)
71
+            if (jwtStillValid) {
72
+                this.goToStep(6)
71 73
             } else {
72
-                console.log('nope, not at all :=>')
74
+                this.goToStep(0)
75
+                this.jwt = ''
73 76
             }
74 77
         }
75 78
     },
@@ -80,8 +83,13 @@ export default {
80 83
         goToStep(num) {
81 84
             this.currentStep = num
82 85
         },
83
-        resumeSurvey() {
84
-            this.goToStep(4)
86
+        grabToken(cookieString) {
87
+            const cookies = cookieString.split('; ').reduce((prev, current) => {
88
+                const [name, ...value] = current.split('=')
89
+                prev[name] = value.join('=')
90
+                return prev
91
+            }, {})
92
+            return 'siimee_jwt' in cookies ? cookies['siimee_jwt'] : undefined
85 93
         },
86 94
         async updateAnswers(payload) {
87 95
             // null payload is passed on splash page
@@ -96,7 +104,7 @@ export default {
96 104
                 }
97 105
                 //
98 106
                 // once validated, don't log password in answered object
99
-                this.answered[k] = k === 'password' ? undefined : payload.input
107
+                // this.answered[k] = k === 'password' ? undefined : payload.input
100 108
 
101 109
                 // formats initial responses for response table
102 110
                 const response = {}

+ 5
- 5
frontend/src/views/VerifyView.vue View File

@@ -8,17 +8,17 @@
8 8
 import { Authenticator } from '../services/auth.service.js'
9 9
 export default {
10 10
     name: 'VerifyView',
11
-    emits: ['resume-survey'],
12 11
     data: () => ({
13 12
         authenticator: {},
14 13
     }),
15
-    created() {
14
+    async created() {
16 15
         this.authenticator = new Authenticator()
17 16
         const hashEmail = this.$route.params.email
18
-        const hashesMatch = this.authenticator.verifyAuthEmail(hashEmail)
17
+        const hashesMatch = await this.authenticator.verifyAuthEmail(hashEmail)
19 18
         if (hashesMatch) {
20
-            // this.$router.push(`/onboarding/${hashEmail}`)
21
-            this.$emit('resume-survey')
19
+            const jwt = await this.authenticator.generateJwt(hashEmail)
20
+            document.cookie = `siimee_jwt=${jwt}; path=/onboarding`
21
+            this.$router.push('/onboarding')
22 22
         }
23 23
         // else {
24 24
         // render ERROR message above or redirect to 404 (or both?)

Loading…
Cancel
Save