#93 brian_lifecycle_docs

Відкрито
tomit4 хоче злити 2 комітів з brian_lifecycle_docs до dev

+ 434
- 0
docs/onboarding/initial-auth-check.md Переглянути файл

@@ -0,0 +1,434 @@
1
+### The LifeCycle Check Of Initial Auth Check For Siimee
2
+
3
+1. Upon first arrival at the Siimee application, the Vue Router's guards are
4
+   called in the router.beforeEach() method. A simple conditional checks to see
5
+   if the application is runnning in development mode. If DEV == false, then the
6
+   checkLoginStatus() method is called, imported from src/router/guards.js.
7
+
8
+```javascript
9
+// frontend/main.js
10
+router.beforeEach((to, from, next) => {
11
+  if (DEV) {
12
+    next();
13
+  } else {
14
+    checkLoginStatus(to, next);
15
+  }
16
+});
17
+```
18
+
19
+2. In the guards.js file, the checkLoginStatus method itself first calls the
20
+   loginIfToken() method. Within this loginIfToken() method, a sessionData
21
+   variable is created which is the asyncronous returned results of the
22
+   authenticator.verifySessionCookie() method:
23
+
24
+```javascript
25
+// src/router/guards.js
26
+const loginIfToken = async () => {
27
+  const sessionData = await authenticator.verifySessionCookie();
28
+  if (
29
+    sessionData?.profileId &&
30
+    sessionData?.sessionToken &&
31
+    !currentProfile.isLoggedIn
32
+  ) {
33
+    await currentProfile.login(
34
+      sessionData.profileId,
35
+      WaveUI.instance.notify,
36
+      sessionData.sessionToken,
37
+    );
38
+  }
39
+};
40
+```
41
+
42
+3. Following to the auth.service.js file from services directory, we find the
43
+   invoked verifySessionCookie() method, which itself calls the
44
+   validateSession() method:
45
+
46
+```javascript
47
+// src/services/auth.service.js
48
+    async verifySessionCookie() {
49
+        const validatedToken = await this.validateSession()
50
+        if (validatedToken.error)
51
+            return console.error('ERROR :=>', validatedToken.error)
52
+        return validatedToken
53
+    }
54
+```
55
+
56
+4. Which itself calls the grabStoredSessionToken()
57
+
58
+```javascript
59
+// src/services/auth.service.js
60
+    async validateSession() {
61
+        const hashedSessionToken = this.grabStoredSessionToken()
62
+        let validation
63
+        try {
64
+            validation = await db.post(
65
+                '/user/validate-session',
66
+                hashedSessionToken,
67
+                true,
68
+            )
69
+        } catch (error) {
70
+            console.error(error)
71
+        }
72
+        console.log('validatedSession :>> ', validation)
73
+        return validation
74
+    }
75
+```
76
+
77
+5. Finally, the cookie, 'siimee_session' is grabbed from the browser's window.document object.
78
+
79
+```javascript
80
+// src/services/auth.service.js
81
+    grabStoredSessionToken(cookieKey = 'siimee_session') {
82
+        const cookies = document.cookie.split('; ').reduce((prev, current) => {
83
+            const [name, ...value] = current.split('=')
84
+            prev[name] = value.join('=')
85
+            return prev
86
+        }, {})
87
+        if (!cookies[cookieKey])
88
+            return console.warn(
89
+                'WARNING :=> accessToken is not defined; There was problem with session cookie you are not logged in.',
90
+            )
91
+        return cookies[cookieKey]
92
+    }
93
+```
94
+
95
+6. Returning to the validateSession() method, we now should have a sessionToken
96
+   if there is one (otherwise we receive a warning, indicating that the user is
97
+   not logged in). This hashedSessionToken is now posted to a route on the
98
+   backend: '/user/validate-session', along with the boolean value of true.
99
+
100
+```javascript
101
+// src/services/auth.service.js
102
+    async validateSession() {
103
+        const hashedSessionToken = this.grabStoredSessionToken()
104
+        let validation
105
+        try {
106
+            validation = await db.post(
107
+                '/user/validate-session',
108
+                hashedSessionToken,
109
+                true,
110
+            )
111
+        } catch (error) {
112
+            console.error(error)
113
+        }
114
+        console.log('validatedSession :>> ', validation)
115
+        return validation
116
+    }
117
+```
118
+
119
+7. On the backend the /validate-session route is hit. First a variable is
120
+   instantiated called validatedSessionToken which holds the return value of the
121
+   userService.validateSession(hashedSessionToken) method:
122
+
123
+```javascript
124
+// backend/lib/routes/user/validate-session.js
125
+const validatedSessionToken = userService.validateSession(hashedSessionToken);
126
+```
127
+
128
+8. Tracking down the validateSession() method in the services/user.js file, we
129
+   find that the UserService class's activeSessions object is checked to see if
130
+   it has a key that matches the hashedSessionToken parameter passed to it (our
131
+   sessiontoken passed from the document's cookies). Two conditionals are
132
+   checked against whether or not the userSession even exists, as well as if the
133
+   emailWasRespondedTo was ever defined (emailWasRespondedTo originally set to
134
+   false when signing up). The sessionToken is then grabbed from the
135
+   userSession.sessionToken (destructuring could have been used here). The
136
+   validateToken(sessionToken) method is then called.
137
+
138
+```javascript
139
+// backend/lib/services/user.js, line 255
140
+   validateSession(hashedSessionToken) {
141
+       const userSession = this.activeSessions[hashedSessionToken]
142
+       if (!userSession) {
143
+           throw new Error(
144
+               'hashedSessionToken not in activeSessions registry!',
145
+           )
146
+       }
147
+       if (!userSession.emailWasRespondedTo) {
148
+           throw new Error('email was never responded to!')
149
+       }
150
+       const sessionToken = userSession.sessionToken
151
+       const sessionTokenIsValid = this.validateToken(sessionToken)
152
+       return {
153
+           ...sessionTokenIsValid.payload,
154
+           sessionToken: this.activeSessions[hashedSessionToken].sessionToken,
155
+       }
156
+   }
157
+```
158
+
159
+9. The validateToken method. Firstly the key is extracted from the
160
+   server.registrations['main-app-plugin'].options.jwtKey object, established in
161
+   the server/manifest.js file:
162
+
163
+```javascript
164
+// backend/lib/services/user.js, line 241
165
+    validateToken(token) {
166
+        const key = this.server.registrations['main-app-plugin'].options.jwtKey
167
+        try {
168
+            return JWT.verify(token, key)
169
+        } catch (err) {
170
+            return { payload: null, message: err.message }
171
+        }
172
+    }
173
+```
174
+
175
+```javascript
176
+// backend/server/manifest.js, line 72
177
+options: {
178
+  jwtKey: {
179
+    $filter: 'NODE_ENV',
180
+    $default: {
181
+      $param: 'APP_SECRET',
182
+      $default: 'app-secret',
183
+    },
184
+    // Use .env file in production
185
+    production: {
186
+      $param: 'APP_SECRET',
187
+    },
188
+  },
189
+}
190
+```
191
+
192
+10. The JWT, from the 'jsonwebtoken' package is used to verify the token passed
193
+    as well as the key. Should it fail, the return value of { payload: null,
194
+    message: err.message } is returned instead.
195
+
196
+11. Returning back to the validateSession(hashedSessionToken) in the services/user.js,
197
+    we then proceed to destructure the return value of this sessionToken's
198
+    payload, as well as the sessionToken from
199
+    this.activeSession[hashedSessionToken] object.
200
+
201
+```javascript
202
+// backend/lib/services/user.js, line 265
203
+const sessionToken = userSession.sessionToken;
204
+const sessionTokenIsValid = this.validateToken(sessionToken);
205
+return {
206
+  ...sessionTokenIsValid.payload,
207
+  sessionToken: this.activeSessions[hashedSessionToken].sessionToken,
208
+};
209
+```
210
+
211
+12. Assumming the user is within the userService.activeSession object, this
212
+    should return the extracted data within the JWT, via the destructured
213
+    ...sessionTokenIsValid.payload object. This should contain the email.
214
+    Returning back to our /validate-session route, we then pass this email
215
+    utilizing the userService.findByUserEmail method:
216
+
217
+```javascript
218
+// backend/lib/routes/user/validate-session.js, line 36
219
+const user = await userService.findByUserEmail(validatedSessionToken.email);
220
+```
221
+
222
+13. This leads us back to our services/user.js file, where we simply query our
223
+    users table to find the first user where that email matches:
224
+
225
+```javascript
226
+// backend/lib/services/user.js, line 104
227
+async findByUserEmail(userEmail, txn) {
228
+  const { User } = this.server.models()
229
+  const user = await User.query(txn)
230
+    .throwIfNotFound()
231
+    .first()
232
+    .where({ user_email: userEmail })
233
+  return user
234
+}
235
+```
236
+
237
+14. Returning back to our /validate-session route, a type variable is then
238
+    determined based off of the is_poster field returned from this query, where
239
+    it is assigned either the string 'poster' or 'seeker' based off of the
240
+    returned value of this is_poster field:
241
+
242
+```javascript
243
+// backend/lib/routes/user/validate-session.js, line 39
244
+const type = user.is_poster === 1 ? "poster" : "seeker";
245
+```
246
+
247
+15. Thie user.user_id as well as the type variables are then passed to the
248
+    profileService.getCompleteProfilesFor() method:
249
+
250
+```javascript
251
+// backend/lib/routes/user/validate-session.js, line 40
252
+const profiles = await profileService.getCompleteProfilesFor(
253
+  user.user_id,
254
+  type,
255
+);
256
+```
257
+
258
+16. This method is declared in the /services/profile.index.js file. It brings in
259
+    the Profile model to query the profiles table. The \_setTagLookup() method is
260
+    also invoked to set all tags upon application load. The userId argument is
261
+    then passed to grab the profileId of the user and assigned to the
262
+    dedupedProfileIds variable. These ids are then passsed to the Profile model,
263
+    which not only grabs the profile fields from the Profile table, but also
264
+    grabs the corresponding tags, responses, and user data from their respective
265
+    tables. Finally a series of helper functions from profiler.js are invoked
266
+    under the overarching method makeCompleteFromProfileEntries() method.
267
+
268
+```javascript
269
+// backend/lib/services/profile/index.js, line 98
270
+async getCompleteProfilesFor(userId, type) {
271
+  const { Profile } = this.server.models()
272
+  await this._setTagLookup()
273
+
274
+  const dedupedProfileIds = await this._getProfileIdsForUserId(userId)
275
+
276
+  const profilesEntries = await Profile.query()
277
+    .whereIn('profile_id', dedupedProfileIds)
278
+    .withGraphFetched('tags')
279
+    .withGraphFetched('responses')
280
+    .withGraphFetched('user')
281
+
282
+  return profiler.makeCompleteFromProfileEntries(
283
+    profilesEntries,
284
+    type,
285
+    this.tagLookup,
286
+  )
287
+}
288
+```
289
+
290
+17. Once again returning to the /validate-session route, the profile_id field is
291
+    then grabbed from the first entry of the return array from
292
+    getCompleteProfilesFor() method and assigned to the profileId variable.
293
+
294
+```javascript
295
+// backend/lib/routes/user/validate-session.js, line 45
296
+const profileId = profiles[0].profile_id;
297
+```
298
+
299
+18. Finally, should all methods succeed, the route returns the destructured
300
+    validatedSessionToken data, as well as the profileId back to the frontend.
301
+
302
+```javascript
303
+// backend/lib/routes/user/validate-session.js, line 46
304
+return {
305
+  ok: true,
306
+  handler: pluginConfig.handlerType,
307
+  data: {
308
+    ...validatedSessionToken,
309
+    profileId: profileId,
310
+  },
311
+};
312
+```
313
+
314
+19. At our frontend, the validateSession() method then returns this object as
315
+    the variable validation within the auth.service.js file.
316
+
317
+```javascript
318
+// src/services/auth.service.js, line 20
319
+async validateSession() {
320
+  const hashedSessionToken = this.grabStoredSessionToken()
321
+  let validation
322
+  try {
323
+    validation = await db.post(
324
+    '/user/validate-session',
325
+    hashedSessionToken,
326
+    true,
327
+    )
328
+  } catch (error) {
329
+    console.error(error)
330
+  }
331
+  console.log('validatedSession :>> ', validation)
332
+  return validation
333
+}
334
+```
335
+
336
+20. This then returns us back to the router/guards.js file where this returned validation
337
+    variable is assigned to the sessionData variable. It is then checked
338
+    alongside the currentProfile object to see if the sessionData.profileId, the
339
+    sessionData.sessionToken, and the currentProfile.isLoggedIn variables are
340
+    defined/truthy or not. Should all the conditionals pass, the
341
+    currentProfile.login() method is invoked, passing the sessionData.profileId,
342
+    as well as the sessionData.sessionToken variables to instantiate an
343
+    authenticated session:
344
+
345
+```javascript
346
+// src/router/guards.js, line 17
347
+const loginIfToken = async () => {
348
+  const sessionData = await authenticator.verifySessionCookie();
349
+  if (
350
+    sessionData?.profileId &&
351
+    sessionData?.sessionToken &&
352
+    !currentProfile.isLoggedIn
353
+  ) {
354
+    await currentProfile.login(
355
+      sessionData.profileId,
356
+      WaveUI.instance.notify,
357
+      sessionData.sessionToken,
358
+    );
359
+  }
360
+};
361
+```
362
+
363
+21. This loginIfToken method is called to establish a logged in, authenticated
364
+    session should there be a session-token cookie in the user's browser's
365
+    window.document.cookie object. The console.info() method is called within
366
+    the log() method to inform any developers monitoring the console of the
367
+    authenticated status of the user, and also where the user is then be
368
+    rerouted to by the Vue Router.$A
369
+
370
+```javascript
371
+// src/router/guards.js, line 34
372
+log(destination);
373
+```
374
+
375
+```javascript
376
+// src/router/guards.js, line 6
377
+async function log(to) {
378
+  if (!currentProfile.isLoggedIn || !currentProfile.isComplete) {
379
+    console.info(
380
+      `[Guard Status debug]: Profile: ${currentProfile.id.value} | Login: ${currentProfile.isLoggedIn} | Complete: ${currentProfile.isComplete}`,
381
+    );
382
+  }
383
+  console.info("[Guard Status debug]: being routed to:", to.fullPath);
384
+}
385
+```
386
+
387
+22. A series of conditional statements then check against each route within the
388
+    Vue Router's configuration file, router/index.js which each subsequently
389
+    check the to (called destination) route, which checks it's
390
+    meta.requiresCompleteProfile as well as the meta.requiresAuth fields.
391
+    Additionally, the currentProfile.isLoggedIn and currentProfile.isComplete
392
+    fields are also checked. Should the route require a complete profile, but
393
+    the user's is not logged in or the user's currentProfile not yet be
394
+    completed, the user is redirected to the /onboarding route, which for the
395
+    sake of this documentation, is the assumed state. Thusly let us move onto
396
+    the /onboarding route, the corresponding Vue component to our /onboarding route,
397
+    as defined in our router/index.js file is the OnboardingView component.
398
+
399
+```javascript
400
+// src/router/guards.js line 32
401
+const checkLoginStatus = async (destination, nextCb) => {
402
+  await loginIfToken();
403
+  log(destination);
404
+  if (DEV_MODE) {
405
+    nextCb();
406
+  } else if (
407
+    destination.meta.requiresCompleteProfile &&
408
+    !currentProfile.isLoggedIn &&
409
+    !currentProfile.isComplete
410
+  ) {
411
+    nextCb("/onboarding");
412
+  } else if (
413
+    destination.meta.requiresCompleteProfile &&
414
+    destination.meta.requiresAuth &&
415
+    !currentProfile.isLoggedIn
416
+  ) {
417
+    nextCb("/login");
418
+  } else {
419
+    nextCb();
420
+  }
421
+};
422
+
423
+export { checkLoginStatus };
424
+```
425
+
426
+```javascript
427
+// src/router/index.js, line 63
428
+{
429
+  path: `/onboarding/`,
430
+  component: OnboardingView,
431
+  name: `OnboardingView`,
432
+  meta: { requiresAuth: true, requiresCompleteProfile: false },
433
+},
434
+```

Завантаження…
Відмінити
Зберегти