@labdigital/commercetools-mock 2.23.1 → 2.24.0

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@labdigital/commercetools-mock",
3
- "version": "2.23.1",
3
+ "version": "2.24.0",
4
4
  "license": "MIT",
5
5
  "author": "Michael van Tellingen",
6
6
  "type": "module",
@@ -2,6 +2,7 @@ import { Customer } from "@commercetools/platform-sdk";
2
2
  import { v4 as uuidv4 } from "uuid";
3
3
 
4
4
  const PWRESET_SECRET = "pwreset";
5
+ const EMAIL_VERIFY_SECRET = "emailverifysecret";
5
6
 
6
7
  export const validatePassword = (
7
8
  clearPassword: string,
@@ -16,6 +17,11 @@ export const createPasswordResetToken = (customer: Customer) =>
16
17
  "base64",
17
18
  );
18
19
 
20
+ export const createEmailVerifyToken = (customer: Customer) =>
21
+ Buffer.from(`${customer.id}:${EMAIL_VERIFY_SECRET}:${uuidv4()}`).toString(
22
+ "base64",
23
+ );
24
+
19
25
  export const validatePasswordResetToken = (token: string) => {
20
26
  const items = Buffer.from(token, "base64").toString("utf-8").split(":");
21
27
  const [customerId, secret] = items;
@@ -25,3 +31,12 @@ export const validatePasswordResetToken = (token: string) => {
25
31
 
26
32
  return customerId;
27
33
  };
34
+ export const validateEmailVerifyToken = (token: string) => {
35
+ const items = Buffer.from(token, "base64").toString("utf-8").split(":");
36
+ const [customerId, secret] = items;
37
+ if (secret !== EMAIL_VERIFY_SECRET) {
38
+ return undefined;
39
+ }
40
+
41
+ return customerId;
42
+ };
@@ -7,7 +7,11 @@ import type {
7
7
  } from "@commercetools/platform-sdk";
8
8
  import { CommercetoolsError } from "~src/exceptions";
9
9
  import { generateRandomString, getBaseResourceProperties } from "~src/helpers";
10
- import { createPasswordResetToken, hashPassword } from "~src/lib/password";
10
+ import {
11
+ createEmailVerifyToken,
12
+ createPasswordResetToken,
13
+ hashPassword,
14
+ } from "~src/lib/password";
11
15
  import { AbstractStorage } from "~src/storage/abstract";
12
16
  import {
13
17
  AbstractResourceRepository,
@@ -110,4 +114,29 @@ export class CustomerRepository extends AbstractResourceRepository<"customer"> {
110
114
  value: token,
111
115
  };
112
116
  }
117
+
118
+ verifyEmailToken(context: RepositoryContext, id: string): CustomerToken {
119
+ const results = this._storage.query(context.projectKey, this.getTypeId(), {
120
+ where: [`id="${id.toLocaleLowerCase()}"`],
121
+ });
122
+ if (results.count === 0) {
123
+ throw new CommercetoolsError<ResourceNotFoundError>({
124
+ code: "ResourceNotFound",
125
+ message: `The Customer with ID '${id}' was not found.`,
126
+ });
127
+ }
128
+ const expiresAt = new Date(Date.now() + 30 * 60);
129
+ const customer = results.results[0] as Customer;
130
+ const rest = getBaseResourceProperties();
131
+
132
+ const token = createEmailVerifyToken(customer);
133
+ return {
134
+ id: rest.id,
135
+ createdAt: rest.createdAt,
136
+ lastModifiedAt: rest.lastModifiedAt,
137
+ customerId: customer.id,
138
+ expiresAt: expiresAt.toISOString(),
139
+ value: token,
140
+ };
141
+ }
113
142
  }
@@ -2,11 +2,16 @@ import {
2
2
  Customer,
3
3
  InvalidCurrentPasswordError,
4
4
  MyCustomerChangePassword,
5
+ MyCustomerEmailVerify,
5
6
  ResourceNotFoundError,
6
7
  type MyCustomerResetPassword,
7
8
  } from "@commercetools/platform-sdk";
8
9
  import { CommercetoolsError } from "~src/exceptions";
9
- import { hashPassword, validatePasswordResetToken } from "../lib/password";
10
+ import {
11
+ hashPassword,
12
+ validateEmailVerifyToken,
13
+ validatePasswordResetToken,
14
+ } from "../lib/password";
10
15
  import { Writable } from "../types";
11
16
  import { type RepositoryContext } from "./abstract";
12
17
  import { CustomerRepository } from "./customer";
@@ -45,6 +50,41 @@ export class MyCustomerRepository extends CustomerRepository {
45
50
  return customer;
46
51
  }
47
52
 
53
+ confirmEmail(
54
+ context: RepositoryContext,
55
+ resetPassword: MyCustomerEmailVerify,
56
+ ) {
57
+ const { tokenValue } = resetPassword;
58
+
59
+ const customerId = validateEmailVerifyToken(tokenValue);
60
+ if (!customerId) {
61
+ throw new CommercetoolsError<ResourceNotFoundError>({
62
+ code: "ResourceNotFound",
63
+ message: `The Customer with ID 'Token(${tokenValue})' was not found.`,
64
+ });
65
+ }
66
+
67
+ const customer = this._storage.get(
68
+ context.projectKey,
69
+ "customer",
70
+ customerId,
71
+ ) as Writable<Customer> | undefined;
72
+
73
+ if (!customer) {
74
+ throw new CommercetoolsError<ResourceNotFoundError>({
75
+ code: "ResourceNotFound",
76
+ message: `The Customer with ID 'Token(${tokenValue})' was not found.`,
77
+ });
78
+ }
79
+
80
+ customer.isEmailVerified = true;
81
+ customer.version += 1;
82
+
83
+ // Update storage
84
+ this._storage.add(context.projectKey, "customer", customer);
85
+ return customer;
86
+ }
87
+
48
88
  deleteMe(context: RepositoryContext): Customer | undefined {
49
89
  // grab the first customer you can find for now. In the future we should
50
90
  // use the customer id from the scope of the token
@@ -39,5 +39,14 @@ export class CustomerService extends AbstractService {
39
39
  );
40
40
  return response.status(200).send(token);
41
41
  });
42
+
43
+ parent.post("/email-token", (request, response) => {
44
+ const id = request.body.id;
45
+ const token = this.repository.verifyEmailToken(
46
+ getRepositoryContext(request),
47
+ id,
48
+ );
49
+ return response.status(200).send(token);
50
+ });
42
51
  }
43
52
  }
@@ -224,6 +224,53 @@ describe("/me", () => {
224
224
  });
225
225
  });
226
226
 
227
+ test("verify email flow", async () => {
228
+ const customer = {
229
+ ...getBaseResourceProperties(),
230
+ id: "customer-uuid",
231
+ email: "user@example.com",
232
+ password: hashPassword("p4ssw0rd"),
233
+ addresses: [],
234
+ isEmailVerified: false,
235
+ authenticationMode: "Password", //default in Commercetools
236
+ version: 1,
237
+ };
238
+ ctMock.project("dummy").add("customer", customer);
239
+
240
+ const token = await supertest(ctMock.app)
241
+ .post("/dummy/customers/email-token")
242
+ .send({
243
+ id: "customer-uuid",
244
+ })
245
+ .then((response) => response.body as CustomerToken);
246
+
247
+ const response = await supertest(ctMock.app)
248
+ .post("/dummy/me/email/confirm")
249
+ .send({
250
+ tokenValue: token.value,
251
+ });
252
+ expect(response.status).toBe(200);
253
+ });
254
+
255
+ test("fail verify email flow", async () => {
256
+ const response = await supertest(ctMock.app)
257
+ .post("/dummy/me/email/confirm")
258
+ .send({
259
+ tokenValue: "invalid-token",
260
+ });
261
+ expect(response.status).toBe(400);
262
+ expect(response.body).toEqual({
263
+ message: `The Customer with ID 'Token(invalid-token)' was not found.`,
264
+ statusCode: 400,
265
+ errors: [
266
+ {
267
+ code: "ResourceNotFound",
268
+ message: `The Customer with ID 'Token(invalid-token)' was not found.`,
269
+ },
270
+ ],
271
+ });
272
+ });
273
+
227
274
  test("setCustomField", async () => {
228
275
  const response = await supertest(ctMock.app)
229
276
  .post(`/dummy/me`)
@@ -35,6 +35,7 @@ export class MyCustomerService extends AbstractService {
35
35
  router.post("/login", this.signIn.bind(this));
36
36
  router.post("/password", this.changePassword.bind(this));
37
37
  router.post("/password/reset", this.resetPassword.bind(this));
38
+ router.post("/email/confirm", this.emailConfirm.bind(this));
38
39
 
39
40
  parent.use(`/${basePath}`, router);
40
41
  }
@@ -105,6 +106,15 @@ export class MyCustomerService extends AbstractService {
105
106
  return response.status(200).send(customer);
106
107
  }
107
108
 
109
+ emailConfirm(request: Request, response: Response) {
110
+ const customer = this.repository.confirmEmail(
111
+ getRepositoryContext(request),
112
+ request.body,
113
+ );
114
+
115
+ return response.status(200).send(customer);
116
+ }
117
+
108
118
  signIn(request: Request, response: Response) {
109
119
  const { email, password } = request.body;
110
120
  const encodedPassword = hashPassword(password);