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

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

tags/0.0.3^2
tomit4 2 лет назад
Родитель
Сommit
c609c4789a

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

@@ -22,15 +22,27 @@ module.exports = {
22 22
         cors: true,
23 23
         handler: async function (request, h) {
24 24
             const { userService } = request.server.services()
25
-            const userEmail = request.payload.email
25
+            const userCredentials = request.payload
26 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 36
                 return {
29 37
                     ok: true,
30 38
                     handler: pluginConfig.handlerType,
31
-                    data: { emailSentSuccessfully: emailSent.wasSuccessfull },
39
+                    data: {
40
+                        emailSentSuccessfully: emailSent.wasSuccessfull,
41
+                        hashedSessionToken: hashedSessionToken,
42
+                    },
32 43
                 }
33 44
             } catch (err) {
45
+                console.log('err :=>', err)
34 46
                 return {
35 47
                     ok: false,
36 48
                     handler: pluginConfig.handlerType,

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

@@ -25,21 +25,30 @@ module.exports = {
25 25
         },
26 26
         handler: async function (request, h) {
27 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 31
                 // NOTE: Set Expiration Time for Access Token Here
32 32
                 // expires: 60 * 2,
33 33
                 // TESTING:
34 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 45
             try {
37 46
                 const response = h.response({
38 47
                     ok: true,
39 48
                     handler: pluginConfig.handlerType,
40
-                    data: token,
49
+                    data: accessTokenInHashedSessions,
41 50
                 })
42
-                response.header('Authorization', token)
51
+                // response.header('Authorization', token)
43 52
                 return response
44 53
             } catch (err) {
45 54
                 return {

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

@@ -14,15 +14,18 @@ const pluginConfig = {
14 14
 }
15 15
 
16 16
 module.exports = {
17
-    method: 'GET',
17
+    method: 'POST',
18 18
     path: '/validatesession',
19 19
     options: {
20 20
         ...pluginConfig.docs.get,
21 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 27
         handler: async function (request, h) {
25
-            const sessionToken = request.headers.authorization
28
+            const sessionToken = request.payload
26 29
             const { userService } = request.server.services()
27 30
             try {
28 31
                 const validatedSessionToken =

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

@@ -14,7 +14,7 @@ const pluginConfig = {
14 14
 
15 15
 module.exports = {
16 16
     method: 'GET',
17
-    path: '/verify/{hashedEmail}',
17
+    path: '/verify/{hashedSessionToken}',
18 18
     options: {
19 19
         ...pluginConfig.docs.get,
20 20
         tags: ['api'],
@@ -22,23 +22,19 @@ module.exports = {
22 22
         cors: true,
23 23
         handler: async function (request, h) {
24 24
             const { userService } = request.server.services()
25
-            const hash = request.params.hashedEmail
26
-
25
+            const hash = request.params.hashedSessionToken
27 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 32
                 const now = Date.now()
38 33
                 // TODO: convert this back to a date object
39
-                const expiration = userService.hashedEmails[hashToMatch]
34
+                const expiration =
35
+                    userService.activeSessions[`${hash}`].expiration
40 36
                 if (now > expiration) {
41
-                    delete userService.hashedEmails[hashToMatch]
37
+                    delete userService.activeSessions[hashToMatch]
42 38
                     throw new Error(
43 39
                         'you took to long to respond to the email...',
44 40
                     )
@@ -50,7 +46,11 @@ module.exports = {
50 46
                 return {
51 47
                     ok: true,
52 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 55
             } catch (err) {
56 56
                 console.log('err :=>', err)

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

@@ -14,14 +14,17 @@ apiKey.apiKey = process.env.BREVO_KEY
14 14
 
15 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 20
     try {
19
-        return crypto.createHmac('sha256', '').update(email).digest('hex')
21
+        return crypto.createHmac('sha256', salt).update(token).digest('hex')
20 22
     } catch (err) {
21 23
         // console.error('ERROR :=>', err)
22 24
         throw new Error(err.message)
23 25
     }
24 26
 }
27
+
25 28
 const hasher = async (pwd, steak) => {
26 29
     const hash = await pwd.hash(steak)
27 30
     const result = await pwd.verify(steak, hash)
@@ -41,7 +44,8 @@ const hasher = async (pwd, steak) => {
41 44
             try {
42 45
                 squirtle = await pwd.hash(steak)
43 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 49
                 return squirtle
46 50
             } catch (err) {
47 51
                 console.error(
@@ -61,25 +65,19 @@ module.exports = class UserService extends Schmervice.Service {
61 65
     constructor(...args) {
62 66
         super(...args)
63 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 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 81
         this.pwd = {
84 82
             hash: Util.promisify(pwd.hash.bind(pwd)),
85 83
             verify: Util.promisify(pwd.verify.bind(pwd)),
@@ -230,20 +228,6 @@ module.exports = class UserService extends Schmervice.Service {
230 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 232
      * Validates whether a token has expired or not
249 233
      * @param {User} user
@@ -293,6 +277,7 @@ module.exports = class UserService extends Schmervice.Service {
293 277
         return passwordRow ? passwordRow.token : null
294 278
     }
295 279
 
280
+    // TODO: rewrite for new activeSessions object
296 281
     async checkEmailRegistry(userEmail) {
297 282
         const hashedEmail = await hashEmail(userEmail)
298 283
         const now = Date.now()
@@ -316,32 +301,37 @@ module.exports = class UserService extends Schmervice.Service {
316 301
      * Sends a Transactional Email via Brevo
317 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 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 321
         const sendSmtpEmail = {
332 322
             to: [
333 323
                 {
334
-                    email: userEmail,
324
+                    email: userCredentials.email,
335 325
                 },
336 326
             ],
337 327
             templateId: 1,
338 328
             params: {
339 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 335
             data => {
346 336
                 return { wasSuccessfull: true, data: data }
347 337
             },

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

@@ -51,9 +51,11 @@ export default {
51 51
             const sessionToken = await this.getSessionToken({
52 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 59
         } catch (err) {
58 60
             // TODO: render an error page in this component displaying which
59 61
             // error occurred and how to reach out to staff

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

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

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

@@ -11,19 +11,18 @@ class Authenticator {
11 11
     async checkIfEmailIsRegistered(email) {
12 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 17
     async getSessionToken(req) {
19 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 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,6 +62,11 @@ export default {
62 62
         invalidResponse: false,
63 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 70
     async created() {
66 71
         this.survey = await surveyFactory.createSurvey()
67 72
         this.authenticator = new Authenticator()
@@ -109,8 +114,10 @@ export default {
109 114
         },
110 115
         // TODO: Possible Security issue, returned .payload has user email in plain text...
111 116
         async verifyBothTokens() {
117
+            // Validate both tokens on the backend at the same time
112 118
             const sessionTokenIsValid = await this.verifySessionToken(
113 119
                 sessionToken,
120
+                accessToken,
114 121
             )
115 122
             const accessTokenIsValid = await this.verifyAccessToken(accessToken)
116 123
             if (

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

@@ -15,13 +15,13 @@ export default {
15 15
     }),
16 16
     async created() {
17 17
         this.authenticator = new Authenticator()
18
-        hash = this.$route.params.email
18
+        hash = this.$route.params.hashedToken
19 19
         sessionToken = this.grabCookie('siimee_session')
20 20
         try {
21 21
             this.isHashInUrl(hash)
22
-            await this.doesEmailMatch(hash)
23 22
             await this.doesSessionTokenExist(sessionToken)
24
-            await this.isSessionTokenValid(sessionToken)
23
+            const rawSessionToken = await this.grabTokenFromHash(hash)
24
+            await this.isSessionTokenValid(hash, rawSessionToken)
25 25
         } catch (err) {
26 26
             console.error(err)
27 27
         }
@@ -40,37 +40,33 @@ export default {
40 40
                 ? cookies[`${cookieKey}`]
41 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 43
         isHashInUrl(hash) {
51 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 46
         async doesSessionTokenExist(sessionToken) {
63 47
             if (!sessionToken)
64 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 61
             const sessionTokenIsValid =
68 62
                 await this.authenticator.validateSession(sessionToken)
63
+            console.log('sessionTokenIsValid :=>', sessionTokenIsValid)
69 64
             if (sessionTokenIsValid.error) {
70 65
                 throw new Error(sessionTokenIsValid.error)
71 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
     },

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