@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/dist/index.cjs +76 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +76 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/lib/password.ts +15 -0
- package/src/repositories/customer/index.ts +30 -1
- package/src/repositories/my-customer.ts +41 -1
- package/src/services/customer.ts +9 -0
- package/src/services/my-customer.test.ts +47 -0
- package/src/services/my-customer.ts +10 -0
package/package.json
CHANGED
package/src/lib/password.ts
CHANGED
|
@@ -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 {
|
|
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 {
|
|
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
|
package/src/services/customer.ts
CHANGED
|
@@ -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);
|