Pārlūkot izejas kodu

:memo: Began working rough draft of onboarding lifecycle docs

brian_lifecycle_docs
tomit4 2 gadus atpakaļ
vecāks
revīzija
3ad49af895
1 mainītis faili ar 394 papildinājumiem un 0 dzēšanām
  1. 394
    0
      docs/onboarding/onboarding.md

+ 394
- 0
docs/onboarding/onboarding.md Parādīt failu

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

Notiek ielāde…
Atcelt
Saglabāt