Просмотр исходного кода

:construction: Heavy token logic refactor, mid way, Onboarding next...

juan_spike
tomit4 2 лет назад
Родитель
Сommit
abda02e6c3

+ 15
- 3
backend/lib/routes/user/email.js Просмотреть файл

22
         cors: true,
22
         cors: true,
23
         handler: async function (request, h) {
23
         handler: async function (request, h) {
24
             const { userService } = request.server.services()
24
             const { userService } = request.server.services()
25
-            const userEmail = request.payload.email
25
+            const userCredentials = request.payload
26
             try {
26
             try {
27
-                const emailSent = await userService.emailSent(userEmail)
27
+                const emailSent = await userService.emailSent(userCredentials)
28
+                const hashedSessionToken = Object.keys(
29
+                    userService.activeSessions,
30
+                ).find(hashedToken => {
31
+                    return (
32
+                        userService.activeSessions[`${hashedToken}`].email ===
33
+                        userCredentials.email
34
+                    )
35
+                })
28
                 return {
36
                 return {
29
                     ok: true,
37
                     ok: true,
30
                     handler: pluginConfig.handlerType,
38
                     handler: pluginConfig.handlerType,
31
-                    data: { emailSentSuccessfully: emailSent.wasSuccessfull },
39
+                    data: {
40
+                        emailSentSuccessfully: emailSent.wasSuccessfull,
41
+                        hashedSessionToken: hashedSessionToken,
42
+                    },
32
                 }
43
                 }
33
             } catch (err) {
44
             } catch (err) {
45
+                console.log('err :=>', err)
34
                 return {
46
                 return {
35
                     ok: false,
47
                     ok: false,
36
                     handler: pluginConfig.handlerType,
48
                     handler: pluginConfig.handlerType,

+ 14
- 5
backend/lib/routes/user/getaccess.js Просмотреть файл

25
         },
25
         },
26
         handler: async function (request, h) {
26
         handler: async function (request, h) {
27
             const { userService } = request.server.services()
27
             const { userService } = request.server.services()
28
-            const res = request.payload
29
-            const token = await userService.createToken({
30
-                ...res,
28
+            const hash = request.payload.hash
29
+            const accessToken = await userService.createToken({
30
+                ...hash,
31
                 // NOTE: Set Expiration Time for Access Token Here
31
                 // NOTE: Set Expiration Time for Access Token Here
32
                 // expires: 60 * 2,
32
                 // expires: 60 * 2,
33
                 // TESTING:
33
                 // TESTING:
34
                 expires: 30,
34
                 expires: 30,
35
             })
35
             })
36
+            userService.activeSessions[`${hash}`].accessToken = accessToken
37
+            const accessTokenInHashedSessions =
38
+                userService.activeSessions[`${hash}`].accessToken ===
39
+                accessToken
40
+                    ? true
41
+                    : false
42
+
43
+            // TODO: instead of putting the token in the return headers,
44
+            // simply put it in the activeSessions Object
36
             try {
45
             try {
37
                 const response = h.response({
46
                 const response = h.response({
38
                     ok: true,
47
                     ok: true,
39
                     handler: pluginConfig.handlerType,
48
                     handler: pluginConfig.handlerType,
40
-                    data: token,
49
+                    data: accessTokenInHashedSessions,
41
                 })
50
                 })
42
-                response.header('Authorization', token)
51
+                // response.header('Authorization', token)
43
                 return response
52
                 return response
44
             } catch (err) {
53
             } catch (err) {
45
                 return {
54
                 return {

+ 7
- 4
backend/lib/routes/user/validatesession.js Просмотреть файл

14
 }
14
 }
15
 
15
 
16
 module.exports = {
16
 module.exports = {
17
-    method: 'GET',
17
+    method: 'POST',
18
     path: '/validatesession',
18
     path: '/validatesession',
19
     options: {
19
     options: {
20
         ...pluginConfig.docs.get,
20
         ...pluginConfig.docs.get,
21
         tags: ['api'],
21
         tags: ['api'],
22
-        auth: 'default_jwt',
23
-        cors: true,
22
+        auth: false,
23
+        cors: {
24
+            headers: ['Authorization'],
25
+            exposedHeaders: ['Authorization', 'Access-Control-Expose-Headers'],
26
+        },
24
         handler: async function (request, h) {
27
         handler: async function (request, h) {
25
-            const sessionToken = request.headers.authorization
28
+            const sessionToken = request.payload
26
             const { userService } = request.server.services()
29
             const { userService } = request.server.services()
27
             try {
30
             try {
28
                 const validatedSessionToken =
31
                 const validatedSessionToken =

+ 15
- 15
backend/lib/routes/user/verifyemail.js Просмотреть файл

14
 
14
 
15
 module.exports = {
15
 module.exports = {
16
     method: 'GET',
16
     method: 'GET',
17
-    path: '/verify/{hashedEmail}',
17
+    path: '/verify/{hashedSessionToken}',
18
     options: {
18
     options: {
19
         ...pluginConfig.docs.get,
19
         ...pluginConfig.docs.get,
20
         tags: ['api'],
20
         tags: ['api'],
22
         cors: true,
22
         cors: true,
23
         handler: async function (request, h) {
23
         handler: async function (request, h) {
24
             const { userService } = request.server.services()
24
             const { userService } = request.server.services()
25
-            const hash = request.params.hashedEmail
26
-
25
+            const hash = request.params.hashedSessionToken
27
             try {
26
             try {
28
-                // Is there an userService.activeSession?
29
-                // If there is, issue a new access token
30
-                // We need the session token from the frontend
31
-                // If NOT, throw error (kicks back to login)
32
-                const hashToMatch = Object.keys(userService.hashedEmails).find(
33
-                    email => {
34
-                        return email === hash
35
-                    },
36
-                )
27
+                const hashToMatch = Object.keys(
28
+                    userService.activeSessions,
29
+                ).find(hashedToken => {
30
+                    return hashedToken === hash
31
+                })
37
                 const now = Date.now()
32
                 const now = Date.now()
38
                 // TODO: convert this back to a date object
33
                 // TODO: convert this back to a date object
39
-                const expiration = userService.hashedEmails[hashToMatch]
34
+                const expiration =
35
+                    userService.activeSessions[`${hash}`].expiration
40
                 if (now > expiration) {
36
                 if (now > expiration) {
41
-                    delete userService.hashedEmails[hashToMatch]
37
+                    delete userService.activeSessions[hashToMatch]
42
                     throw new Error(
38
                     throw new Error(
43
                         'you took to long to respond to the email...',
39
                         'you took to long to respond to the email...',
44
                     )
40
                     )
50
                 return {
46
                 return {
51
                     ok: true,
47
                     ok: true,
52
                     handler: pluginConfig.handlerType,
48
                     handler: pluginConfig.handlerType,
53
-                    data: { hashesMatch: hashToMatch === hash },
49
+                    data: {
50
+                        hashesMatch: hashToMatch === hash,
51
+                        sessionToken:
52
+                            userService.activeSessions[`${hash}`].sessionToken,
53
+                    },
54
                 }
54
                 }
55
             } catch (err) {
55
             } catch (err) {
56
                 console.log('err :=>', err)
56
                 console.log('err :=>', err)

+ 35
- 45
backend/lib/services/user.js Просмотреть файл

14
 
14
 
15
 const apiInstance = new SibApiV3Sdk.TransactionalEmailsApi()
15
 const apiInstance = new SibApiV3Sdk.TransactionalEmailsApi()
16
 
16
 
17
-const hashEmail = async email => {
17
+const hashToken = async token => {
18
+    // QUESTION: How to best create random salt...?
19
+    const salt = crypto.randomBytes(16).toString('base64')
18
     try {
20
     try {
19
-        return crypto.createHmac('sha256', '').update(email).digest('hex')
21
+        return crypto.createHmac('sha256', salt).update(token).digest('hex')
20
     } catch (err) {
22
     } catch (err) {
21
         // console.error('ERROR :=>', err)
23
         // console.error('ERROR :=>', err)
22
         throw new Error(err.message)
24
         throw new Error(err.message)
23
     }
25
     }
24
 }
26
 }
27
+
25
 const hasher = async (pwd, steak) => {
28
 const hasher = async (pwd, steak) => {
26
     const hash = await pwd.hash(steak)
29
     const hash = await pwd.hash(steak)
27
     const result = await pwd.verify(steak, hash)
30
     const result = await pwd.verify(steak, hash)
41
             try {
44
             try {
42
                 squirtle = await pwd.hash(steak)
45
                 squirtle = await pwd.hash(steak)
43
                 // console.log('improvedHash', squirtle)
46
                 // console.log('improvedHash', squirtle)
44
-                // const saveHash = Auth.insert({user_email: matchingEmails}).into('token')
47
+                // const saveHash = Auth.insert({user_email:
48
+                // matchingEmails}).into('token')
45
                 return squirtle
49
                 return squirtle
46
             } catch (err) {
50
             } catch (err) {
47
                 console.error(
51
                 console.error(
61
     constructor(...args) {
65
     constructor(...args) {
62
         super(...args)
66
         super(...args)
63
         const pwd = new SecurePassword()
67
         const pwd = new SecurePassword()
64
-        // TODO: Invalidate this application state somehow after a certain time period has passed
65
-        // TODO: Remove hashedEmails in preference of activeSessions
66
-        this.hashedEmails = {
67
-            // NOTE: key is email hash and value is timestamp in ms
68
+        // TODO: Invalidate this application state somehow after a
69
+        // certain time period has passed
70
+        this.activeSessions = {
68
             // abc123456: '123456689',
71
             // abc123456: '123456689',
72
+            // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...hashedSessionToken: {
73
+            // email: rawEmailString,
74
+            // name: 'Joe Doe',
75
+            // seeking: 'candidate'
76
+            // sessionToken: rawSessionToken, // use for expires instead of expires?
77
+            // expires: expirationTime in seconds
78
+            // }
69
         }
79
         }
70
 
80
 
71
-        // this.activeSessions = [
72
-        // {
73
-        // user: {
74
-        // useremail: email,
75
-        // hashedEmail: hashedEmail,
76
-        // username: name,
77
-        // },
78
-        // expiration: 1203984710234
79
-        // },
80
-        // token: 'tokenString + expirationDate + salt'
81
-        // ]
82
-
83
         this.pwd = {
81
         this.pwd = {
84
             hash: Util.promisify(pwd.hash.bind(pwd)),
82
             hash: Util.promisify(pwd.hash.bind(pwd)),
85
             verify: Util.promisify(pwd.verify.bind(pwd)),
83
             verify: Util.promisify(pwd.verify.bind(pwd)),
230
         return JWT.sign(obj, key, { expiresIn: data.expires })
228
         return JWT.sign(obj, key, { expiresIn: data.expires })
231
     }
229
     }
232
 
230
 
233
-    async registerSession(user, hashedEmail, token) {
234
-        const sessionRequester = {
235
-            user: user,
236
-            hashedEmail: hashedEmail,
237
-            token: token,
238
-        }
239
-
240
-        const alreadyExists = this.activeSessions.find(
241
-            sessionRequester => sessionRequester.hashedEmail === hashedEmail,
242
-        )
243
-        if (!alreadyExists) {
244
-            this.activeSessions.push(sessionRequester)
245
-        }
246
-    }
247
     /**
231
     /**
248
      * Validates whether a token has expired or not
232
      * Validates whether a token has expired or not
249
      * @param {User} user
233
      * @param {User} user
293
         return passwordRow ? passwordRow.token : null
277
         return passwordRow ? passwordRow.token : null
294
     }
278
     }
295
 
279
 
280
+    // TODO: rewrite for new activeSessions object
296
     async checkEmailRegistry(userEmail) {
281
     async checkEmailRegistry(userEmail) {
297
         const hashedEmail = await hashEmail(userEmail)
282
         const hashedEmail = await hashEmail(userEmail)
298
         const now = Date.now()
283
         const now = Date.now()
316
      * Sends a Transactional Email via Brevo
301
      * Sends a Transactional Email via Brevo
317
      * @ returns {Object}
302
      * @ returns {Object}
318
      */
303
      */
319
-    async emailSent(userEmail) {
320
-        const hashedEmail = await hashEmail(userEmail)
321
-        if (Object.keys(this.hashedEmails).includes(hashedEmail)) {
322
-            return new Error('email address already in cache!!')
304
+    async emailSent(userCredentials) {
305
+        const hashedSessionToken = await hashToken(userCredentials.sessionToken)
306
+        if (Object.keys(this.activeSessions).includes(hashedSessionToken)) {
307
+            return new Error('session already in cache!!')
323
         }
308
         }
324
         // Set expiration time for ten minutes from now
309
         // Set expiration time for ten minutes from now
325
-        const duration = 1000 * 60 * 10
310
+        // QUESTION: should we use the sessionToken's expiration time instead?
311
+        const duration = 600000
312
+
313
+        this.activeSessions[hashedSessionToken] = {
314
+            email: userCredentials.email,
315
+            name: userCredentials.name,
316
+            seeking: userCredentials.seeking,
317
+            sessionToken: userCredentials.sessionToken,
318
+            expiration: Date.now() + duration,
319
+        }
326
 
320
 
327
-        this.hashedEmails[hashedEmail] = Date.now() + duration
328
-        // TODO: See FrontEnd in Auth.vue and VerifyView.vue notes:
329
-        // if user closes browser, they'll need to be issued first session token based off of this:
330
-        // this.hashedEmails[hashedEmail][email] = userEmail
331
         const sendSmtpEmail = {
321
         const sendSmtpEmail = {
332
             to: [
322
             to: [
333
                 {
323
                 {
334
-                    email: userEmail,
324
+                    email: userCredentials.email,
335
                 },
325
                 },
336
             ],
326
             ],
337
             templateId: 1,
327
             templateId: 1,
338
             params: {
328
             params: {
339
                 // TODO: Change this in production...
329
                 // TODO: Change this in production...
340
-                link: `localhost:3000/verify/${hashedEmail}`,
330
+                link: `localhost:3000/verify/${hashedSessionToken}`,
341
             },
331
             },
342
         }
332
         }
343
 
333
 
344
-        await apiInstance.sendTransacEmail(sendSmtpEmail).then(
334
+        return await apiInstance.sendTransacEmail(sendSmtpEmail).then(
345
             data => {
335
             data => {
346
                 return { wasSuccessfull: true, data: data }
336
                 return { wasSuccessfull: true, data: data }
347
             },
337
             },

+ 5
- 3
frontend/src/components/onboarding/Auth.vue Просмотреть файл

51
             const sessionToken = await this.getSessionToken({
51
             const sessionToken = await this.getSessionToken({
52
                 ...this.answered,
52
                 ...this.answered,
53
             })
53
             })
54
-            // TODO: Flawed thinking, what if user closes browser and answers email later??
55
-            document.cookie = `siimee_session=${sessionToken}; max-age=600; path=/; secure`
56
-            await this.authenticator.sendAuthEmail(this.answered)
54
+            const sessionInfo = await this.authenticator.sendAuthEmail({
55
+                ...this.answered,
56
+                sessionToken: sessionToken,
57
+            })
58
+            document.cookie = `siimee_session=${sessionInfo.hashedSessionToken}; max-age=600; path=/; secure`
57
         } catch (err) {
59
         } catch (err) {
58
             // TODO: render an error page in this component displaying which
60
             // TODO: render an error page in this component displaying which
59
             // error occurred and how to reach out to staff
61
             // error occurred and how to reach out to staff

+ 1
- 1
frontend/src/router/index.js Просмотреть файл

67
         meta: { requiresAuth: true, requiresCompleteProfile: false },
67
         meta: { requiresAuth: true, requiresCompleteProfile: false },
68
     },
68
     },
69
     {
69
     {
70
-        path: `/verify/:email?`,
70
+        path: `/verify/:hashedToken?`,
71
         component: VerifyView,
71
         component: VerifyView,
72
         name: `VerifyView`,
72
         name: `VerifyView`,
73
         meta: { requiresAuth: true, requiresCompleteProfile: false },
73
         meta: { requiresAuth: true, requiresCompleteProfile: false },

+ 6
- 7
frontend/src/services/auth.service.js Просмотреть файл

11
     async checkIfEmailIsRegistered(email) {
11
     async checkIfEmailIsRegistered(email) {
12
         return await db.post('/user/checkemailregistry/', email)
12
         return await db.post('/user/checkemailregistry/', email)
13
     }
13
     }
14
-    async verifyAuthEmail(hashedEmail) {
15
-        const isVerified = await db.get(`/user/verify/${hashedEmail}`)
16
-        return isVerified.hashesMatch
14
+    async verifyAuthSession(hashedSessionToken) {
15
+        return await db.get(`/user/verify/${hashedSessionToken}`)
17
     }
16
     }
18
     async getSessionToken(req) {
17
     async getSessionToken(req) {
19
         return await db.post('/user/getsession', req, true)
18
         return await db.post('/user/getsession', req, true)
20
     }
19
     }
21
-    async getAccessToken(req) {
22
-        return await db.post('/user/getaccess', req, true)
20
+    //async getAccessToken(req) {
21
+    async assignAccessTokenToSession(req) {
22
+        return await db.post('/user/getaccess', req)
23
     }
23
     }
24
-    // TODO: Possible Security issue, returned .payload has user email in plain text...
25
     async validateSession(token) {
24
     async validateSession(token) {
26
-        return await db.get('/user/validatesession', token)
25
+        return await db.post('/user/validatesession', token, true)
27
     }
26
     }
28
 }
27
 }
29
 
28
 

+ 7
- 0
frontend/src/views/OnboardingView.vue Просмотреть файл

62
         invalidResponse: false,
62
         invalidResponse: false,
63
         authenticator: {},
63
         authenticator: {},
64
     }),
64
     }),
65
+    // TODO: Heavy refactor comes next, now only cookie is session_token,
66
+    // which is currently a hashed Key hashedSessions{} registry on the backend
67
+    // In VerifyView.vue we have  a grabTokenFromHash() method which we can
68
+    // use here to verify any routes
69
+    // AccessToken lives on backend within hashedSessions{} registry
65
     async created() {
70
     async created() {
66
         this.survey = await surveyFactory.createSurvey()
71
         this.survey = await surveyFactory.createSurvey()
67
         this.authenticator = new Authenticator()
72
         this.authenticator = new Authenticator()
109
         },
114
         },
110
         // TODO: Possible Security issue, returned .payload has user email in plain text...
115
         // TODO: Possible Security issue, returned .payload has user email in plain text...
111
         async verifyBothTokens() {
116
         async verifyBothTokens() {
117
+            // Validate both tokens on the backend at the same time
112
             const sessionTokenIsValid = await this.verifySessionToken(
118
             const sessionTokenIsValid = await this.verifySessionToken(
113
                 sessionToken,
119
                 sessionToken,
120
+                accessToken,
114
             )
121
             )
115
             const accessTokenIsValid = await this.verifyAccessToken(accessToken)
122
             const accessTokenIsValid = await this.verifyAccessToken(accessToken)
116
             if (
123
             if (

+ 18
- 22
frontend/src/views/VerifyView.vue Просмотреть файл

15
     }),
15
     }),
16
     async created() {
16
     async created() {
17
         this.authenticator = new Authenticator()
17
         this.authenticator = new Authenticator()
18
-        hash = this.$route.params.email
18
+        hash = this.$route.params.hashedToken
19
         sessionToken = this.grabCookie('siimee_session')
19
         sessionToken = this.grabCookie('siimee_session')
20
         try {
20
         try {
21
             this.isHashInUrl(hash)
21
             this.isHashInUrl(hash)
22
-            await this.doesEmailMatch(hash)
23
             await this.doesSessionTokenExist(sessionToken)
22
             await this.doesSessionTokenExist(sessionToken)
24
-            await this.isSessionTokenValid(sessionToken)
23
+            const rawSessionToken = await this.grabTokenFromHash(hash)
24
+            await this.isSessionTokenValid(hash, rawSessionToken)
25
         } catch (err) {
25
         } catch (err) {
26
             console.error(err)
26
             console.error(err)
27
         }
27
         }
40
                 ? cookies[`${cookieKey}`]
40
                 ? cookies[`${cookieKey}`]
41
                 : undefined
41
                 : undefined
42
         },
42
         },
43
-        // QUESTION: This will likely be needed in OnboardingView.vue
44
-        async getAccessToken(payload) {
45
-            const accessToken = await this.authenticator.getAccessToken({
46
-                payload,
47
-            })
48
-            document.cookie = `siimee_access=${accessToken}; max-age=600; path=/; secure`
49
-        },
50
         isHashInUrl(hash) {
43
         isHashInUrl(hash) {
51
             if (!hash) throw new Error('URL contains no hash!')
44
             if (!hash) throw new Error('URL contains no hash!')
52
         },
45
         },
53
-        async doesEmailMatch(hashEmail) {
54
-            const hashesMatch = await this.authenticator.verifyAuthEmail(
55
-                hashEmail,
56
-            )
57
-            if (!hashesMatch) throw new Error('Hash is not in registry!')
58
-        },
59
-        // TODO: Flawed thinking, what if user closed browser and then answered email?
60
-        // session token won't exist, it will need to be generated here using the hashEmail, problem is:
61
-        // hashEmail cannot access
62
         async doesSessionTokenExist(sessionToken) {
46
         async doesSessionTokenExist(sessionToken) {
63
             if (!sessionToken)
47
             if (!sessionToken)
64
                 throw new Error('sessionToken not in cookie store!')
48
                 throw new Error('sessionToken not in cookie store!')
65
         },
49
         },
66
-        async isSessionTokenValid(sessionToken) {
50
+        // TODO: Next is to put this into OnboardingView
51
+        // TODO: validate routes using sole SessionToken Grabbed from hash in cookie
52
+        async grabTokenFromHash(hashedToken) {
53
+            const sessionData = await this.authenticator.verifyAuthSession(
54
+                hashedToken,
55
+            )
56
+            if (!sessionData.hashesMatch)
57
+                throw new Error('Hash is not in registry!')
58
+            else return sessionData.sessionToken
59
+        },
60
+        async isSessionTokenValid(hash, sessionToken) {
67
             const sessionTokenIsValid =
61
             const sessionTokenIsValid =
68
                 await this.authenticator.validateSession(sessionToken)
62
                 await this.authenticator.validateSession(sessionToken)
63
+            console.log('sessionTokenIsValid :=>', sessionTokenIsValid)
69
             if (sessionTokenIsValid.error) {
64
             if (sessionTokenIsValid.error) {
70
                 throw new Error(sessionTokenIsValid.error)
65
                 throw new Error(sessionTokenIsValid.error)
71
             } else {
66
             } else {
72
-                // TODO: Does accessToken need sessionToken data?
73
-                await this.getAccessToken(sessionTokenIsValid.payload)
67
+                await this.authenticator.assignAccessTokenToSession({
68
+                    hash,
69
+                })
74
             }
70
             }
75
         },
71
         },
76
     },
72
     },

Загрузка…
Отмена
Сохранить