Ver código fonte

:sparkles: Implemented login

tags/0.0.4
tomit4 2 anos atrás
pai
commit
f7ec41949d

+ 2
- 0
backend/lib/plugins/user.js Ver arquivo

16
 const UserVerifyActiveRoute = require('../routes/user/verifyactivesession.js')
16
 const UserVerifyActiveRoute = require('../routes/user/verifyactivesession.js')
17
 const UserGetAccessRoute = require('../routes/user/getaccess.js')
17
 const UserGetAccessRoute = require('../routes/user/getaccess.js')
18
 const UserValidateSessionRoute = require('../routes/user/validatesession.js')
18
 const UserValidateSessionRoute = require('../routes/user/validatesession.js')
19
+const UserRemoveSessionRoute = require('../routes/user/removesession.js')
19
 const UserPassword = require('../routes/user/authentication')
20
 const UserPassword = require('../routes/user/authentication')
20
 
21
 
21
 const UserService = require('../services/user')
22
 const UserService = require('../services/user')
57
         await server.route(UserVerifyActiveRoute)
58
         await server.route(UserVerifyActiveRoute)
58
         await server.route(UserGetAccessRoute)
59
         await server.route(UserGetAccessRoute)
59
         await server.route(UserValidateSessionRoute)
60
         await server.route(UserValidateSessionRoute)
61
+        await server.route(UserRemoveSessionRoute)
60
         await server.route(UserPassword)
62
         await server.route(UserPassword)
61
     },
63
     },
62
 }
64
 }

+ 30
- 5
backend/lib/routes/user/login.js Ver arquivo

19
             user_email: Joi.string(),
19
             user_email: Joi.string(),
20
             password: Joi.string(),
20
             password: Joi.string(),
21
         }),
21
         }),
22
-        
23
     },
22
     },
24
     user: userSchema.single,
23
     user: userSchema.single,
25
     error: errorSchema.single,
24
     error: errorSchema.single,
34
         auth: false,
33
         auth: false,
35
         handler: async function (request, h) {
34
         handler: async function (request, h) {
36
             try {
35
             try {
37
-                const { userService } = request.services()
36
+                const { userService, profileService } =
37
+                    request.server.services()
38
 
38
 
39
                 const res = request.payload
39
                 const res = request.payload
40
 
40
 
51
 
51
 
52
                 // Bound context from your plugin server declaration
52
                 // Bound context from your plugin server declaration
53
                 const user = await h.context.transaction(login)
53
                 const user = await h.context.transaction(login)
54
-                const token = userService.createToken(user)
54
+                const rawUser = await userService.findByUserEmail(
55
+                    res.user_email,
56
+                )
57
+                // Uses Same Logic Behind Initial Sign Up,
58
+                // passing expected credentials to be used for logging in
59
+                const userCredentials = {
60
+                    email: rawUser.user_email,
61
+                    name: rawUser.user_name,
62
+                    seeking: rawUser.is_poster === 1 ? 'poster' : 'seeker',
63
+                }
64
+                const token = userService.createToken({
65
+                    payload: userCredentials,
66
+                })
55
 
67
 
56
                 return {
68
                 return {
57
                     ok: true,
69
                     ok: true,
58
                     handler: pluginConfig.handlerType,
70
                     handler: pluginConfig.handlerType,
59
-                    data: { user_email: user.user_email, jwtToken: token },
71
+                    data: {
72
+                        user_email: user.user_email,
73
+                        jwt: token,
74
+                        answered: {
75
+                            email: userCredentials.email,
76
+                            name: userCredentials.name,
77
+                            seeking: userCredentials.seeking,
78
+                        },
79
+                    },
60
                 }
80
                 }
61
             } catch (err) {
81
             } catch (err) {
62
                 console.error(err)
82
                 console.error(err)
75
                     handler: Joi.string(),
95
                     handler: Joi.string(),
76
                     data: Joi.object({
96
                     data: Joi.object({
77
                         user_email: Joi.string(),
97
                         user_email: Joi.string(),
78
-                        jwtToken: Joi.string(),
98
+                        jwt: Joi.string(),
99
+                        answered: Joi.object({
100
+                            email: Joi.string(),
101
+                            name: Joi.string(),
102
+                            seeking: Joi.string(),
103
+                        }),
79
                     }),
104
                     }),
80
                 }).label('login_res'),
105
                 }).label('login_res'),
81
                 409: Joi.object({
106
                 409: Joi.object({

+ 3
- 0
backend/lib/routes/user/verifyactivesession.js Ver arquivo

45
                 if (!hashToMatch) {
45
                 if (!hashToMatch) {
46
                     throw new Error('no record of email in cache')
46
                     throw new Error('no record of email in cache')
47
                 }
47
                 }
48
+                userService.activeSessions[
49
+                    hashToMatch
50
+                ].emailWasRespondedTo = true
48
                 return {
51
                 return {
49
                     ok: true,
52
                     ok: true,
50
                     handler: pluginConfig.handlerType,
53
                     handler: pluginConfig.handlerType,

+ 26
- 12
backend/lib/services/user.js Ver arquivo

15
 
15
 
16
 const apiInstance = new SibApiV3Sdk.TransactionalEmailsApi()
16
 const apiInstance = new SibApiV3Sdk.TransactionalEmailsApi()
17
 
17
 
18
-const hashToken = async token => {
19
-    const salt = process.env.APP_SESSION_SALT
20
-    try {
21
-        return crypto.createHmac('sha256', salt).update(token).digest('hex')
22
-    } catch (err) {
23
-        throw new Error(err.message)
24
-    }
25
-}
26
-
27
 const hasher = async (pwd, steak) => {
18
 const hasher = async (pwd, steak) => {
28
     const hash = await pwd.hash(steak)
19
     const hash = await pwd.hash(steak)
29
     const result = await pwd.verify(steak, hash)
20
     const result = await pwd.verify(steak, hash)
136
         return user
127
         return user
137
     }
128
     }
138
 
129
 
130
+    async hashToken(token) {
131
+        const salt = process.env.APP_SESSION_SALT
132
+        try {
133
+            return crypto.createHmac('sha256', salt).update(token).digest('hex')
134
+        } catch (err) {
135
+            throw new Error(err.message)
136
+        }
137
+    }
138
+
139
     /**
139
     /**
140
      * Signup function
140
      * Signup function
141
      * @param {*} param0
141
      * @param {*} param0
208
             .throwIfNotFound()
208
             .throwIfNotFound()
209
             .first()
209
             .first()
210
             .where({ user_email: email })
210
             .where({ user_email: email })
211
-
212
         const bufferPepper = Buffer.from(process.env.PEPPER + password)
211
         const bufferPepper = Buffer.from(process.env.PEPPER + password)
213
 
212
 
214
         /** Uncomment to run password check using SecurePassword */
213
         /** Uncomment to run password check using SecurePassword */
260
                 'hashedSessionToken not in activeSessions registry!',
259
                 'hashedSessionToken not in activeSessions registry!',
261
             )
260
             )
262
         }
261
         }
262
+        if (!userSession.emailWasRespondedTo) {
263
+            throw new Error('email was never responded to!')
264
+        }
263
         const accessToken = userSession.accessToken
265
         const accessToken = userSession.accessToken
264
         const accessTokenIsValid = this.validateToken(accessToken)
266
         const accessTokenIsValid = this.validateToken(accessToken)
265
         return {
267
         return {
267
             accessToken: this.activeSessions[hashedAccessToken].accessToken,
269
             accessToken: this.activeSessions[hashedAccessToken].accessToken,
268
         }
270
         }
269
     }
271
     }
272
+    removeSession(hashedAccessToken) {
273
+        const userSession = this.activeSessions[hashedAccessToken]
274
+        if (!userSession) {
275
+            throw new Error(
276
+                'hashedSessionToken not in activeSessions registry!',
277
+            )
278
+        } else {
279
+            delete this.activeSessions[hashedAccessToken]
280
+        }
281
+    }
270
     /**
282
     /**
271
      * Use knex to try to change password entry
283
      * Use knex to try to change password entry
272
      * @param {number} id
284
      * @param {number} id
297
         const passwordRow = await Auth.query(txn)
309
         const passwordRow = await Auth.query(txn)
298
             .where('user_email', email)
310
             .where('user_email', email)
299
             .first()
311
             .first()
300
-
301
         return passwordRow ? passwordRow.token : null
312
         return passwordRow ? passwordRow.token : null
302
     }
313
     }
303
 
314
 
306
      * @ returns {Object}
317
      * @ returns {Object}
307
      */
318
      */
308
     async emailSent(userCredentials) {
319
     async emailSent(userCredentials) {
309
-        const hashedAccessToken = await hashToken(userCredentials.accessToken)
320
+        const hashedAccessToken = await this.hashToken(
321
+            userCredentials.accessToken,
322
+        )
310
         if (Object.keys(this.activeSessions).includes(hashedAccessToken)) {
323
         if (Object.keys(this.activeSessions).includes(hashedAccessToken)) {
311
             return new Error('session already in cache!!')
324
             return new Error('session already in cache!!')
312
         }
325
         }
319
             seeking: userCredentials.seeking,
332
             seeking: userCredentials.seeking,
320
             accessToken: userCredentials.accessToken,
333
             accessToken: userCredentials.accessToken,
321
             expiration: Date.now() + duration,
334
             expiration: Date.now() + duration,
335
+            emailWasRespondedTo: false,
322
             sessionToken: null,
336
             sessionToken: null,
323
         }
337
         }
324
 
338
 

+ 1
- 1
frontend/src/App.vue Ver arquivo

13
 import SideBar from './components/SideBar.vue'
13
 import SideBar from './components/SideBar.vue'
14
 import TopNav from './components/TopNav.vue'
14
 import TopNav from './components/TopNav.vue'
15
 
15
 
16
-import { currentProfile } from './services'
16
+import { currentProfile, authenticator } from './services'
17
 import { surveyFactory } from './utils'
17
 import { surveyFactory } from './utils'
18
 
18
 
19
 const DEV_MODE = import.meta.env.VITE_DEV == 'true'
19
 const DEV_MODE = import.meta.env.VITE_DEV == 'true'

+ 6
- 0
frontend/src/services/auth.service.js Ver arquivo

16
     async validateSession(hashedAccessToken) {
16
     async validateSession(hashedAccessToken) {
17
         return await db.post('/user/validatesession', hashedAccessToken, true)
17
         return await db.post('/user/validatesession', hashedAccessToken, true)
18
     }
18
     }
19
+    async authenticateLoginCredentials(credentials) {
20
+        return await db.post('/user/login', credentials)
21
+    }
22
+    async removeSession(hashedAccessToken) {
23
+        return await db.post('/user/removesession', hashedAccessToken, true)
24
+    }
19
 }
25
 }
20
 const authenticator = new Authenticator()
26
 const authenticator = new Authenticator()
21
 
27
 

+ 24
- 2
frontend/src/views/HomeView.vue Ver arquivo

22
 
22
 
23
 import { Card } from '../entities'
23
 import { Card } from '../entities'
24
 
24
 
25
-import { currentProfile, fetchQueueByProfileId } from '../services'
25
+import {
26
+    currentProfile,
27
+    authenticator,
28
+    fetchQueueByProfileId,
29
+} from '../services'
26
 import { mixins } from '../utils'
30
 import { mixins } from '../utils'
27
 
31
 
28
 const notificationOpts = {
32
 const notificationOpts = {
91
             )
95
             )
92
             this.fetchedCards.push(...newQueue) // update fetchedCards => recalculate cards
96
             this.fetchedCards.push(...newQueue) // update fetchedCards => recalculate cards
93
         },
97
         },
98
+        grabStoredCookie(cookieKey) {
99
+            const cookies = document.cookie
100
+                .split('; ')
101
+                .reduce((prev, current) => {
102
+                    const [name, ...value] = current.split('=')
103
+                    prev[name] = value.join('=')
104
+                    return prev
105
+                }, {})
106
+            const cookieVal =
107
+                cookieKey in cookies ? cookies[`${cookieKey}`] : undefined
108
+            return cookieVal
109
+        },
94
         async logout() {
110
         async logout() {
95
             if (currentProfile.isLoggedIn) {
111
             if (currentProfile.isLoggedIn) {
96
                 currentProfile.logout()
112
                 currentProfile.logout()
97
             }
113
             }
98
-            document.cookie = `siimee_access=''; max-age=600; path=/; secure`
114
+            const hashedAccessToken = this.grabStoredCookie('siimee_access')
115
+            const removedSession = await authenticator.removeSession(
116
+                hashedAccessToken,
117
+            )
118
+            if (removedSession.error)
119
+                console.error('ERROR :=>', removedSession.error)
120
+            document.cookie = `siimee_access=''; max-age=0; path=/; secure`
99
             this.$router.push('/onboarding')
121
             this.$router.push('/onboarding')
100
         },
122
         },
101
         // this can be placed in utils/notification.js
123
         // this can be placed in utils/notification.js

+ 46
- 7
frontend/src/views/LoginView.vue Ver arquivo

1
 <template lang="pug">
1
 <template lang="pug">
2
 main.view--login
2
 main.view--login
3
-
4
     article.pa12
3
     article.pa12
5
-        form
6
-            w-input.mb4(label="User E-mail" tile outline v-model="form.profileId" inner-icon-left='icon-envelope')
7
-            w-input(label="Password" type="password" tile outline inner-icon-left='icon-eye')
8
-
9
-            //- Emit up an event so we can sync App pid with currentProfile.id
10
-            w-button.xs12.mt12(@click="$emit('updatePid', form.profileId)" type="submit") submit
4
+        div(v-if='emailSentSuccessfully === null')
5
+            form
6
+                w-input.mb4(label="User E-mail" tile outline v-model="form.email" inner-icon-left='icon-envelope')
7
+                w-input(label="Password" v-model="form.password" type="password" tile outline inner-icon-left='icon-eye')
8
+                w-button.xs12.mt12(@click="login") submit
9
+        div(v-else-if='emailSentSuccessfully === false')
10
+            p.verify-message Email Was Not Sent Successfully, please contact your Email Service Provider or Systems Administrator.
11
+        div(v-else)
12
+            p.verify-message Thanks for logging in!
13
+            p.verify-message We'll just need you to verify your email address to continue. Please check your email!
11
 </template>
14
 </template>
12
 
15
 
13
 <script>
16
 <script>
17
+import { authenticator } from '../services'
14
 export default {
18
 export default {
15
     data: () => ({
19
     data: () => ({
16
         form: {
20
         form: {
17
             profileId: null,
21
             profileId: null,
22
+            email: null,
23
+            password: null,
18
         },
24
         },
25
+        emailSentSuccessfully: null,
19
     }),
26
     }),
27
+    methods: {
28
+        async login() {
29
+            const loginCredentials = {
30
+                user_email: this.form.email,
31
+                password: this.form.password,
32
+            }
33
+            const credentials =
34
+                await authenticator.authenticateLoginCredentials(
35
+                    loginCredentials,
36
+                )
37
+            // emailSentSuccessfully: emailSent.wasSuccessfull,
38
+            const sessionInfo = await authenticator.sendAuthEmail({
39
+                ...credentials.answered,
40
+                accessToken: credentials.jwt,
41
+            })
42
+            if (sessionInfo.emailSentSuccessfully) {
43
+                this.emailSentSuccessfully = true
44
+            }
45
+            document.cookie = `siimee_access=${sessionInfo.hashedAccessToken}; max-age=600; path=/; secure`
46
+        },
47
+    },
20
 }
48
 }
21
 </script>
49
 </script>
50
+
51
+<style>
52
+.verify-message {
53
+    display: flex;
54
+    justify-content: center;
55
+    text-align: center;
56
+    margin: 0 auto;
57
+    font-weight: 700;
58
+    font-size: 160%;
59
+}
60
+</style>

Carregando…
Cancelar
Salvar