@ossy/users 3.8.0 → 3.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ossy/users",
3
3
  "description": "User domain - aggregate, events, and validators for the Ossy user model",
4
- "version": "3.8.0",
4
+ "version": "3.11.1",
5
5
  "private": false,
6
6
  "type": "module",
7
7
  "main": "./src/index.js",
@@ -10,6 +10,7 @@
10
10
  ".": "./src/index.js",
11
11
  "./server": "./src/server.js",
12
12
  "./update-email.flow.js": "./src/update-email.flow.js",
13
+ "./update-profile-name.flow.js": "./src/update-profile-name.flow.js",
13
14
  "./update-profile-details.schema.js": "./src/update-profile-details.schema.js"
14
15
  },
15
16
  "scripts": {
@@ -48,5 +49,5 @@
48
49
  "/src",
49
50
  "README.md"
50
51
  ],
51
- "gitHead": "14548dfc2019e7258e8c75db527acbf66fe829bd"
52
+ "gitHead": "f279de3535b6bc76631cbc6b943dae8f92a1565f"
52
53
  }
@@ -14,6 +14,7 @@ export async function run ({ payload, req }) {
14
14
  type: TokenSchema.token,
15
15
  'state.subject': userId,
16
16
  'state.type': 'Api',
17
+ 'state.status': 'Active',
17
18
  }, { state: true }).toArray().then(docs => docs.map(d => d.state))
18
19
 
19
20
  return tokens.map(({ token: _secret, ...meta }) => meta)
@@ -0,0 +1,50 @@
1
+ import signUpFlow from '@ossy/authentication/sign-up.flow.js'
2
+ import { metadata as EditProfileDetails } from './edit-profile-details.action.js'
3
+ import { metadata as SaveProfileDetails } from './save-profile-details.action.js'
4
+ import { metadata as UpdateProfileDetailsForm } from './update-profile-details.form.js'
5
+
6
+ export const metadata = {
7
+ id: '@ossy/users/flows/update-profile-name',
8
+ feature: 'users',
9
+ requires: ['server', 'database'],
10
+ }
11
+
12
+ /**
13
+ * Happy path for issue #5 — change first/last name from profile without
14
+ * requesting an email change (email stays the sign-up address).
15
+ */
16
+ export default {
17
+ title: 'Update profile name',
18
+ description:
19
+ 'Signed-in user edits profile name only, saves, and sees the updated name on profile',
20
+ steps: [
21
+ ...signUpFlow.steps,
22
+ { page: '@profile' },
23
+ { result: { action: EditProfileDetails, timeout: 10000 } },
24
+ { action: EditProfileDetails },
25
+ { result: { action: SaveProfileDetails, timeout: 10000 } },
26
+ // Keep sign-up email so RequestEmailChange does not run — name-only path.
27
+ {
28
+ form: UpdateProfileDetailsForm,
29
+ fields: {
30
+ firstName: 'E2E',
31
+ lastName: 'UpdatedName',
32
+ email: '$email',
33
+ },
34
+ },
35
+ { action: SaveProfileDetails },
36
+ {
37
+ result: {
38
+ selector: '[data-user-name="E2E UpdatedName"]',
39
+ timeout: 15000,
40
+ },
41
+ },
42
+ {
43
+ result: {
44
+ selector: '[data-action="@ossy/users/actions/save-profile-details"]',
45
+ hidden: true,
46
+ timeout: 10000,
47
+ },
48
+ },
49
+ ],
50
+ }
@@ -221,6 +221,130 @@ describe('[authentication/actions/request-sign-in]', () => {
221
221
 
222
222
  })
223
223
 
224
+ describe('[authentication/actions/verify-sign-in]', () => {
225
+
226
+ describe('given email + code from request-sign-in', () => {
227
+ it('must return a WebAuth session and set an auth cookie via POST /actions', async () => {
228
+ const email = casual.email
229
+
230
+ await TestUtil.AssertActionResponse({
231
+ actionId: '@ossy/authentication/actions/sign-up',
232
+ body: TestUtil.signUpBody({ email }),
233
+ expectedResponseStatus: 200,
234
+ expectedResponseBody: { ok: true },
235
+ })
236
+
237
+ await TestUtil.AssertActionResponse({
238
+ actionId: '@ossy/authentication/actions/request-sign-in',
239
+ payload: { email },
240
+ expectedResponseStatus: 200,
241
+ expectedResponseBody: { ok: true },
242
+ })
243
+
244
+ const createdEvent = await TestUtil.GetEvent({
245
+ type: USER_SCHEMA,
246
+ event: 'Created',
247
+ 'payload.email': email,
248
+ })
249
+
250
+ const code = await TestUtil.getLatestVerificationCodeForSubject(createdEvent.resourceId)
251
+
252
+ const response = await TestUtil.InvokeAction({
253
+ actionId: '@ossy/authentication/actions/verify-sign-in',
254
+ payload: { email, code },
255
+ })
256
+
257
+ const body = await response.json()
258
+ const signInVerifiedEvent = await TestUtil.GetEvent({
259
+ type: USER_SCHEMA,
260
+ resourceId: createdEvent.resourceId,
261
+ event: 'SignInVerified',
262
+ })
263
+
264
+ expect(response.status).toBe(200)
265
+ expect(body).toMatchObject({ sub: createdEvent.resourceId, token: expect.any(String) })
266
+ expect(!!signInVerifiedEvent).toBe(true)
267
+ expect(getSetCookieHeader(response).includes(signInVerifiedEvent.payload.token)).toEqual(true)
268
+ })
269
+
270
+ it('must reject reuse of the same code with 401', async () => {
271
+ const email = casual.email
272
+
273
+ await TestUtil.AssertActionResponse({
274
+ actionId: '@ossy/authentication/actions/sign-up',
275
+ body: TestUtil.signUpBody({ email }),
276
+ expectedResponseStatus: 200,
277
+ expectedResponseBody: { ok: true },
278
+ })
279
+
280
+ await TestUtil.AssertActionResponse({
281
+ actionId: '@ossy/authentication/actions/request-sign-in',
282
+ payload: { email },
283
+ expectedResponseStatus: 200,
284
+ expectedResponseBody: { ok: true },
285
+ })
286
+
287
+ const createdEvent = await TestUtil.GetEvent({
288
+ type: USER_SCHEMA,
289
+ event: 'Created',
290
+ 'payload.email': email,
291
+ })
292
+
293
+ const code = await TestUtil.getLatestVerificationCodeForSubject(createdEvent.resourceId)
294
+
295
+ const first = await TestUtil.InvokeAction({
296
+ actionId: '@ossy/authentication/actions/verify-sign-in',
297
+ payload: { email, code },
298
+ })
299
+ expect(first.status).toBe(200)
300
+
301
+ const second = await TestUtil.InvokeAction({
302
+ actionId: '@ossy/authentication/actions/verify-sign-in',
303
+ payload: { email, code },
304
+ })
305
+ expect(second.status).toBe(401)
306
+ })
307
+
308
+ it('must reject the magic link JWT after sign-in via code with 401', async () => {
309
+ const email = casual.email
310
+
311
+ await TestUtil.AssertActionResponse({
312
+ actionId: '@ossy/authentication/actions/sign-up',
313
+ body: TestUtil.signUpBody({ email }),
314
+ expectedResponseStatus: 200,
315
+ expectedResponseBody: { ok: true },
316
+ })
317
+
318
+ await TestUtil.AssertActionResponse({
319
+ actionId: '@ossy/authentication/actions/request-sign-in',
320
+ payload: { email },
321
+ expectedResponseStatus: 200,
322
+ expectedResponseBody: { ok: true },
323
+ })
324
+
325
+ const createdEvent = await TestUtil.GetEvent({
326
+ type: USER_SCHEMA,
327
+ event: 'Created',
328
+ 'payload.email': email,
329
+ })
330
+
331
+ const code = await TestUtil.getLatestVerificationCodeForSubject(createdEvent.resourceId)
332
+ const verificationJwt = await TestUtil.getLatestVerificationJwtForSubject(createdEvent.resourceId)
333
+
334
+ const codeResponse = await TestUtil.InvokeAction({
335
+ actionId: '@ossy/authentication/actions/verify-sign-in',
336
+ payload: { email, code },
337
+ })
338
+ expect(codeResponse.status).toBe(200)
339
+
340
+ const linkResponse = await fetch(
341
+ `${getApiTestBaseUrl()}/users/verify-sign-in?token=${verificationJwt}`,
342
+ { method: 'GET' },
343
+ )
344
+ expect(linkResponse.status).toBe(401)
345
+ })
346
+ })
347
+
224
348
  describe('[/users/verify-sign-in][GET]', () => {
225
349
 
226
350
  describe('given a valid token created from a Created event', () => {
@@ -333,6 +457,13 @@ describe('[/users/verify-sign-in][GET]', () => {
333
457
  }))
334
458
  })
335
459
 
460
+ it('must return 400 when email+code are provided without a token (codes are POST-only)', () => TestUtil.AssertResponse({
461
+ endpoint: '/users/verify-sign-in?email=test@example.com&code=123456',
462
+ method: 'GET',
463
+ expectedResponseStatus: 400,
464
+ expectedResponseBody: '',
465
+ }))
466
+
336
467
  })
337
468
 
338
469
  })