Parcourir la source

:sparkles: Implemented login

tags/0.0.4
tomit4 il y a 2 ans
Parent
révision
f7ec41949d

+ 2
- 0
backend/lib/plugins/user.js Voir le fichier

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

+ 30
- 5
backend/lib/routes/user/login.js Voir le fichier

@@ -19,7 +19,6 @@ const validators = {
19 19
             user_email: Joi.string(),
20 20
             password: Joi.string(),
21 21
         }),
22
-        
23 22
     },
24 23
     user: userSchema.single,
25 24
     error: errorSchema.single,
@@ -34,7 +33,8 @@ module.exports = {
34 33
         auth: false,
35 34
         handler: async function (request, h) {
36 35
             try {
37
-                const { userService } = request.services()
36
+                const { userService, profileService } =
37
+                    request.server.services()
38 38
 
39 39
                 const res = request.payload
40 40
 
@@ -51,12 +51,32 @@ module.exports = {
51 51
 
52 52
                 // Bound context from your plugin server declaration
53 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 68
                 return {
57 69
                     ok: true,
58 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 81
             } catch (err) {
62 82
                 console.error(err)
@@ -75,7 +95,12 @@ module.exports = {
75 95
                     handler: Joi.string(),
76 96
                     data: Joi.object({
77 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 105
                 }).label('login_res'),
81 106
                 409: Joi.object({

+ 3
- 0
backend/lib/routes/user/verifyactivesession.js Voir le fichier

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

+ 26
- 12
backend/lib/services/user.js Voir le fichier

@@ -15,15 +15,6 @@ apiKey.apiKey = process.env.BREVO_KEY
15 15
 
16 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 18
 const hasher = async (pwd, steak) => {
28 19
     const hash = await pwd.hash(steak)
29 20
     const result = await pwd.verify(steak, hash)
@@ -136,6 +127,15 @@ module.exports = class UserService extends Schmervice.Service {
136 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 140
      * Signup function
141 141
      * @param {*} param0
@@ -208,7 +208,6 @@ module.exports = class UserService extends Schmervice.Service {
208 208
             .throwIfNotFound()
209 209
             .first()
210 210
             .where({ user_email: email })
211
-
212 211
         const bufferPepper = Buffer.from(process.env.PEPPER + password)
213 212
 
214 213
         /** Uncomment to run password check using SecurePassword */
@@ -260,6 +259,9 @@ module.exports = class UserService extends Schmervice.Service {
260 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 265
         const accessToken = userSession.accessToken
264 266
         const accessTokenIsValid = this.validateToken(accessToken)
265 267
         return {
@@ -267,6 +269,16 @@ module.exports = class UserService extends Schmervice.Service {
267 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 283
      * Use knex to try to change password entry
272 284
      * @param {number} id
@@ -297,7 +309,6 @@ module.exports = class UserService extends Schmervice.Service {
297 309
         const passwordRow = await Auth.query(txn)
298 310
             .where('user_email', email)
299 311
             .first()
300
-
301 312
         return passwordRow ? passwordRow.token : null
302 313
     }
303 314
 
@@ -306,7 +317,9 @@ module.exports = class UserService extends Schmervice.Service {
306 317
      * @ returns {Object}
307 318
      */
308 319
     async emailSent(userCredentials) {
309
-        const hashedAccessToken = await hashToken(userCredentials.accessToken)
320
+        const hashedAccessToken = await this.hashToken(
321
+            userCredentials.accessToken,
322
+        )
310 323
         if (Object.keys(this.activeSessions).includes(hashedAccessToken)) {
311 324
             return new Error('session already in cache!!')
312 325
         }
@@ -319,6 +332,7 @@ module.exports = class UserService extends Schmervice.Service {
319 332
             seeking: userCredentials.seeking,
320 333
             accessToken: userCredentials.accessToken,
321 334
             expiration: Date.now() + duration,
335
+            emailWasRespondedTo: false,
322 336
             sessionToken: null,
323 337
         }
324 338
 

+ 1
- 1
frontend/src/App.vue Voir le fichier

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

+ 6
- 0
frontend/src/services/auth.service.js Voir le fichier

@@ -16,6 +16,12 @@ class Authenticator {
16 16
     async validateSession(hashedAccessToken) {
17 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 26
 const authenticator = new Authenticator()
21 27
 

+ 24
- 2
frontend/src/views/HomeView.vue Voir le fichier

@@ -22,7 +22,11 @@ import PairingButton from '../components/PairingButton.vue'
22 22
 
23 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 30
 import { mixins } from '../utils'
27 31
 
28 32
 const notificationOpts = {
@@ -91,11 +95,29 @@ export default {
91 95
             )
92 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 110
         async logout() {
95 111
             if (currentProfile.isLoggedIn) {
96 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 121
             this.$router.push('/onboarding')
100 122
         },
101 123
         // this can be placed in utils/notification.js

+ 46
- 7
frontend/src/views/LoginView.vue Voir le fichier

@@ -1,21 +1,60 @@
1 1
 <template lang="pug">
2 2
 main.view--login
3
-
4 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 14
 </template>
12 15
 
13 16
 <script>
17
+import { authenticator } from '../services'
14 18
 export default {
15 19
     data: () => ({
16 20
         form: {
17 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 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>

Chargement…
Annuler
Enregistrer