customer-registration 0.0.24 → 0.0.48
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/.medusa/server/src/api/auth/customer/emailpass/route.js +58 -0
- package/.medusa/server/src/api/store/customers/email/otp/resend/route.js +78 -0
- package/.medusa/server/src/api/store/customers/email/otp/verify/route.js +54 -40
- package/.medusa/server/src/api/store/customers/forget-password/otp/resend/route.js +52 -0
- package/.medusa/server/src/api/store/customers/forget-password/otp/verify/route.js +42 -0
- package/.medusa/server/src/api/store/customers/phone/otp/resend/route.js +81 -0
- package/.medusa/server/src/api/store/customers/phone/otp/verify/route.js +60 -41
- package/.medusa/server/src/errors/otp-errors.js +29 -0
- package/.medusa/server/src/loaders/index.js +29 -9
- package/.medusa/server/src/modules/customer-registration/index.js +44 -9
- package/.medusa/server/src/modules/customer-registration/migrations/Migration20251122112915AddEmailPhoneVerifiedColumns.js +67 -0
- package/.medusa/server/src/modules/customer-registration/migrations/Migration20251122112916CreateCustomerOtpTable.js +56 -0
- package/.medusa/server/src/modules/customer-registration/models/customer-otp.js +65 -32
- package/.medusa/server/src/modules/customer-registration/services/otp-service.js +226 -0
- package/.medusa/server/src/services/notification-service.js +81 -0
- package/.medusa/server/src/subscribers/customer-created.js +42 -0
- package/.medusa/server/src/types/plugin-options.js +30 -0
- package/.medusa/server/src/utils/crypto.js +52 -0
- package/.medusa/server/src/utils/customer-update.js +48 -0
- package/.medusa/server/src/utils/otp-generator.js +27 -0
- package/.medusa/server/src/utils/token-generator.js +11 -0
- package/README.md +156 -32
- package/package.json +5 -1
- package/.medusa/server/src/api/store/customers/phone/otp/send/route.js +0 -48
- package/.medusa/server/src/api/store/customers/route.js +0 -77
- package/.medusa/server/src/modules/customer-registration/__tests__/config.spec.js +0 -61
- package/.medusa/server/src/modules/customer-registration/config.js +0 -73
- package/.medusa/server/src/modules/customer-registration/constants.js +0 -5
- package/.medusa/server/src/modules/customer-registration/migrations/Migration20250118000000AddEmailVerifiedColumn.js +0 -21
- package/.medusa/server/src/modules/customer-registration/migrations/Migration20250118001000CreateCustomerOtpTable.js +0 -48
- package/.medusa/server/src/modules/customer-registration/service.js +0 -242
- package/.medusa/server/src/modules/customer-registration/types.js +0 -3
|
@@ -1,12 +1,32 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
exports.default =
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
const plugin_options_1 = require("../types/plugin-options");
|
|
7
|
+
const customer_registration_1 = __importDefault(require("../modules/customer-registration"));
|
|
8
|
+
const customer_registration_2 = require("../modules/customer-registration");
|
|
9
|
+
exports.default = {
|
|
10
|
+
name: "customer-registration",
|
|
11
|
+
resolve: "customer-registration",
|
|
12
|
+
options: {},
|
|
13
|
+
moduleService: customer_registration_1.default,
|
|
14
|
+
load: async (container, options) => {
|
|
15
|
+
// Normalize and validate plugin options
|
|
16
|
+
const normalizedOptions = (0, plugin_options_1.normalizePluginOptions)(options);
|
|
17
|
+
// Register plugin options in container
|
|
18
|
+
container.register({
|
|
19
|
+
pluginOptions: {
|
|
20
|
+
resolve: () => normalizedOptions,
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
// Register module
|
|
24
|
+
if (customer_registration_1.default.load) {
|
|
25
|
+
customer_registration_1.default.load(container, normalizedOptions);
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
[customer_registration_2.CUSTOMER_REGISTRATION_MODULE]: container.resolve(customer_registration_2.CUSTOMER_REGISTRATION_MODULE),
|
|
29
|
+
};
|
|
30
|
+
},
|
|
11
31
|
};
|
|
12
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
32
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvbG9hZGVycy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7OztBQUFBLDREQUFvRjtBQUNwRiw2RkFBeUU7QUFDekUsNEVBQStFO0FBRS9FLGtCQUFlO0lBQ2IsSUFBSSxFQUFFLHVCQUF1QjtJQUM3QixPQUFPLEVBQUUsdUJBQXVCO0lBQ2hDLE9BQU8sRUFBRSxFQUFtQjtJQUM1QixhQUFhLEVBQUUsK0JBQTBCO0lBQ3pDLElBQUksRUFBRSxLQUFLLEVBQUUsU0FBYyxFQUFFLE9BQVksRUFBRSxFQUFFO1FBQzNDLHdDQUF3QztRQUN4QyxNQUFNLGlCQUFpQixHQUFHLElBQUEsdUNBQXNCLEVBQUMsT0FBd0IsQ0FBQyxDQUFBO1FBRTFFLHVDQUF1QztRQUN2QyxTQUFTLENBQUMsUUFBUSxDQUFDO1lBQ2pCLGFBQWEsRUFBRTtnQkFDYixPQUFPLEVBQUUsR0FBRyxFQUFFLENBQUMsaUJBQWlCO2FBQ2pDO1NBQ0YsQ0FBQyxDQUFBO1FBRUYsa0JBQWtCO1FBQ2xCLElBQUksK0JBQTBCLENBQUMsSUFBSSxFQUFFLENBQUM7WUFDcEMsK0JBQTBCLENBQUMsSUFBSSxDQUFDLFNBQVMsRUFBRSxpQkFBaUIsQ0FBQyxDQUFBO1FBQy9ELENBQUM7UUFFRCxPQUFPO1lBQ0wsQ0FBQyxvREFBNEIsQ0FBQyxFQUFFLFNBQVMsQ0FBQyxPQUFPLENBQUMsb0RBQTRCLENBQUM7U0FDaEYsQ0FBQTtJQUNILENBQUM7Q0FDRixDQUFBIn0=
|
|
@@ -1,13 +1,48 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var
|
|
3
|
-
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
4
15
|
};
|
|
5
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.CUSTOMER_REGISTRATION_MODULE = void 0;
|
|
17
|
+
exports.OTPService = exports.CustomerOtp = exports.CUSTOMER_REGISTRATION_MODULE = void 0;
|
|
7
18
|
const utils_1 = require("@medusajs/framework/utils");
|
|
8
|
-
const
|
|
9
|
-
exports
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
19
|
+
const otp_service_1 = require("./services/otp-service");
|
|
20
|
+
Object.defineProperty(exports, "OTPService", { enumerable: true, get: function () { return otp_service_1.OTPService; } });
|
|
21
|
+
const customer_otp_1 = require("./models/customer-otp");
|
|
22
|
+
Object.defineProperty(exports, "CustomerOtp", { enumerable: true, get: function () { return customer_otp_1.CustomerOtp; } });
|
|
23
|
+
exports.CUSTOMER_REGISTRATION_MODULE = "customerRegistration";
|
|
24
|
+
exports.default = {
|
|
25
|
+
key: exports.CUSTOMER_REGISTRATION_MODULE,
|
|
26
|
+
service: otp_service_1.OTPService,
|
|
27
|
+
load: (container, options) => {
|
|
28
|
+
const config = options;
|
|
29
|
+
container.register({
|
|
30
|
+
[exports.CUSTOMER_REGISTRATION_MODULE]: {
|
|
31
|
+
resolve: (cradle) => {
|
|
32
|
+
const customerService = cradle[utils_1.Modules.CUSTOMER];
|
|
33
|
+
const notificationService = cradle[utils_1.Modules.NOTIFICATION];
|
|
34
|
+
const manager = cradle.manager;
|
|
35
|
+
return new otp_service_1.OTPService({
|
|
36
|
+
customerService,
|
|
37
|
+
notificationService,
|
|
38
|
+
manager,
|
|
39
|
+
config,
|
|
40
|
+
});
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
__exportStar(require("./migrations/Migration20251122112915AddEmailPhoneVerifiedColumns"), exports);
|
|
47
|
+
__exportStar(require("./migrations/Migration20251122112916CreateCustomerOtpTable"), exports);
|
|
48
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9zcmMvbW9kdWxlcy9jdXN0b21lci1yZWdpc3RyYXRpb24vaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQSxxREFBbUQ7QUFDbkQsd0RBQW1EO0FBaUM3QiwyRkFqQ2Isd0JBQVUsT0FpQ2E7QUFoQ2hDLHdEQUFtRDtBQWdDMUMsNEZBaENBLDBCQUFXLE9BZ0NBO0FBM0JQLFFBQUEsNEJBQTRCLEdBQUcsc0JBQXNCLENBQUE7QUFFbEUsa0JBQWU7SUFDYixHQUFHLEVBQUUsb0NBQTRCO0lBQ2pDLE9BQU8sRUFBRSx3QkFBVTtJQUNuQixJQUFJLEVBQUUsQ0FBQyxTQUFjLEVBQUUsT0FBWSxFQUFFLEVBQUU7UUFDckMsTUFBTSxNQUFNLEdBQUcsT0FBa0MsQ0FBQTtRQUVqRCxTQUFTLENBQUMsUUFBUSxDQUFDO1lBQ2pCLENBQUMsb0NBQTRCLENBQUMsRUFBRTtnQkFDOUIsT0FBTyxFQUFFLENBQUMsTUFBVyxFQUFFLEVBQUU7b0JBQ3ZCLE1BQU0sZUFBZSxHQUFHLE1BQU0sQ0FBQyxlQUFPLENBQUMsUUFBUSxDQUFDLENBQUE7b0JBQ2hELE1BQU0sbUJBQW1CLEdBQUcsTUFBTSxDQUFDLGVBQU8sQ0FBQyxZQUFZLENBQUMsQ0FBQTtvQkFDeEQsTUFBTSxPQUFPLEdBQUcsTUFBTSxDQUFDLE9BQU8sQ0FBQTtvQkFFOUIsT0FBTyxJQUFJLHdCQUFVLENBQUM7d0JBQ3BCLGVBQWU7d0JBQ2YsbUJBQW1CO3dCQUNuQixPQUFPO3dCQUNQLE1BQU07cUJBQ1AsQ0FBQyxDQUFBO2dCQUNKLENBQUM7YUFDRjtTQUNGLENBQUMsQ0FBQTtJQUNKLENBQUM7Q0FDRixDQUFBO0FBR0QsbUdBQWdGO0FBQ2hGLDZGQUEwRSJ9
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Migration20251122112915AddEmailPhoneVerifiedColumns = void 0;
|
|
4
|
+
const migrations_1 = require("@mikro-orm/migrations");
|
|
5
|
+
class Migration20251122112915AddEmailPhoneVerifiedColumns extends migrations_1.Migration {
|
|
6
|
+
async up() {
|
|
7
|
+
// Add email_verified column if it doesn't exist
|
|
8
|
+
this.addSql(`
|
|
9
|
+
do $$
|
|
10
|
+
begin
|
|
11
|
+
if not exists (
|
|
12
|
+
select 1 from information_schema.columns
|
|
13
|
+
where table_name = 'customer' and column_name = 'email_verified'
|
|
14
|
+
) then
|
|
15
|
+
alter table "customer"
|
|
16
|
+
add column "email_verified" boolean not null default false;
|
|
17
|
+
end if;
|
|
18
|
+
end $$;
|
|
19
|
+
`);
|
|
20
|
+
// Add phone_verified column if it doesn't exist
|
|
21
|
+
this.addSql(`
|
|
22
|
+
do $$
|
|
23
|
+
begin
|
|
24
|
+
if not exists (
|
|
25
|
+
select 1 from information_schema.columns
|
|
26
|
+
where table_name = 'customer' and column_name = 'phone_verified'
|
|
27
|
+
) then
|
|
28
|
+
alter table "customer"
|
|
29
|
+
add column "phone_verified" boolean not null default false;
|
|
30
|
+
end if;
|
|
31
|
+
end $$;
|
|
32
|
+
`);
|
|
33
|
+
// Create indexes for better query performance
|
|
34
|
+
this.addSql(`
|
|
35
|
+
create index if not exists "customer_email_verified_index" on "customer" ("email_verified");
|
|
36
|
+
`);
|
|
37
|
+
this.addSql(`
|
|
38
|
+
create index if not exists "customer_phone_verified_index" on "customer" ("phone_verified");
|
|
39
|
+
`);
|
|
40
|
+
}
|
|
41
|
+
async down() {
|
|
42
|
+
this.addSql(`
|
|
43
|
+
do $$
|
|
44
|
+
begin
|
|
45
|
+
if exists (
|
|
46
|
+
select 1 from information_schema.columns
|
|
47
|
+
where table_name = 'customer' and column_name = 'email_verified'
|
|
48
|
+
) then
|
|
49
|
+
alter table "customer" drop column "email_verified";
|
|
50
|
+
end if;
|
|
51
|
+
end $$;
|
|
52
|
+
`);
|
|
53
|
+
this.addSql(`
|
|
54
|
+
do $$
|
|
55
|
+
begin
|
|
56
|
+
if exists (
|
|
57
|
+
select 1 from information_schema.columns
|
|
58
|
+
where table_name = 'customer' and column_name = 'phone_verified'
|
|
59
|
+
) then
|
|
60
|
+
alter table "customer" drop column "phone_verified";
|
|
61
|
+
end if;
|
|
62
|
+
end $$;
|
|
63
|
+
`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
exports.Migration20251122112915AddEmailPhoneVerifiedColumns = Migration20251122112915AddEmailPhoneVerifiedColumns;
|
|
67
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTWlncmF0aW9uMjAyNTExMjIxMTI5MTVBZGRFbWFpbFBob25lVmVyaWZpZWRDb2x1bW5zLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vc3JjL21vZHVsZXMvY3VzdG9tZXItcmVnaXN0cmF0aW9uL21pZ3JhdGlvbnMvTWlncmF0aW9uMjAyNTExMjIxMTI5MTVBZGRFbWFpbFBob25lVmVyaWZpZWRDb2x1bW5zLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLHNEQUFpRDtBQUVqRCxNQUFhLG1EQUFvRCxTQUFRLHNCQUFTO0lBQ2hGLEtBQUssQ0FBQyxFQUFFO1FBQ04sZ0RBQWdEO1FBQ2hELElBQUksQ0FBQyxNQUFNLENBQUM7Ozs7Ozs7Ozs7O0tBV1gsQ0FBQyxDQUFBO1FBRUYsZ0RBQWdEO1FBQ2hELElBQUksQ0FBQyxNQUFNLENBQUM7Ozs7Ozs7Ozs7O0tBV1gsQ0FBQyxDQUFBO1FBRUYsOENBQThDO1FBQzlDLElBQUksQ0FBQyxNQUFNLENBQUM7O0tBRVgsQ0FBQyxDQUFBO1FBRUYsSUFBSSxDQUFDLE1BQU0sQ0FBQzs7S0FFWCxDQUFDLENBQUE7SUFDSixDQUFDO0lBRUQsS0FBSyxDQUFDLElBQUk7UUFDUixJQUFJLENBQUMsTUFBTSxDQUFDOzs7Ozs7Ozs7O0tBVVgsQ0FBQyxDQUFBO1FBRUYsSUFBSSxDQUFDLE1BQU0sQ0FBQzs7Ozs7Ozs7OztLQVVYLENBQUMsQ0FBQTtJQUNKLENBQUM7Q0FDRjtBQWpFRCxrSEFpRUMifQ==
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Migration20251122112916CreateCustomerOtpTable = void 0;
|
|
4
|
+
const migrations_1 = require("@mikro-orm/migrations");
|
|
5
|
+
class Migration20251122112916CreateCustomerOtpTable extends migrations_1.Migration {
|
|
6
|
+
async up() {
|
|
7
|
+
this.addSql(`
|
|
8
|
+
create table if not exists "customer_registration_otp" (
|
|
9
|
+
"id" text not null,
|
|
10
|
+
"customer_id" text null,
|
|
11
|
+
"channel_type" text check ("channel_type" in ('email', 'phone')) not null,
|
|
12
|
+
"address" text not null,
|
|
13
|
+
"otp_hash" text not null,
|
|
14
|
+
"otp_type" text check ("otp_type" in ('email_verification', 'phone_verification', 'forget_password')) not null,
|
|
15
|
+
"token" text not null,
|
|
16
|
+
"expires_at" timestamptz not null,
|
|
17
|
+
"verified_at" timestamptz null,
|
|
18
|
+
"attempts" integer not null default 0,
|
|
19
|
+
"created_at" timestamptz not null default now(),
|
|
20
|
+
"updated_at" timestamptz not null default now(),
|
|
21
|
+
constraint "customer_registration_otp_pkey" primary key ("id")
|
|
22
|
+
);
|
|
23
|
+
`);
|
|
24
|
+
this.addSql(`
|
|
25
|
+
create unique index if not exists "customer_registration_otp_token_unique" on "customer_registration_otp" ("token");
|
|
26
|
+
`);
|
|
27
|
+
this.addSql(`
|
|
28
|
+
create index if not exists "customer_registration_otp_customer_id_index" on "customer_registration_otp" ("customer_id");
|
|
29
|
+
`);
|
|
30
|
+
this.addSql(`
|
|
31
|
+
create index if not exists "customer_registration_otp_address_index" on "customer_registration_otp" ("address");
|
|
32
|
+
`);
|
|
33
|
+
this.addSql(`
|
|
34
|
+
create index if not exists "customer_registration_otp_channel_type_index" on "customer_registration_otp" ("channel_type");
|
|
35
|
+
`);
|
|
36
|
+
this.addSql(`
|
|
37
|
+
create index if not exists "customer_registration_otp_otp_type_index" on "customer_registration_otp" ("otp_type");
|
|
38
|
+
`);
|
|
39
|
+
// Add foreign key constraint to customer table if it exists
|
|
40
|
+
this.addSql(`
|
|
41
|
+
do $$
|
|
42
|
+
begin
|
|
43
|
+
if exists (select 1 from information_schema.tables where table_name = 'customer') then
|
|
44
|
+
alter table "customer_registration_otp"
|
|
45
|
+
add constraint "customer_registration_otp_customer_id_foreign"
|
|
46
|
+
foreign key ("customer_id") references "customer" ("id") on delete cascade;
|
|
47
|
+
end if;
|
|
48
|
+
end $$;
|
|
49
|
+
`);
|
|
50
|
+
}
|
|
51
|
+
async down() {
|
|
52
|
+
this.addSql(`drop table if exists "customer_registration_otp" cascade;`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
exports.Migration20251122112916CreateCustomerOtpTable = Migration20251122112916CreateCustomerOtpTable;
|
|
56
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTWlncmF0aW9uMjAyNTExMjIxMTI5MTZDcmVhdGVDdXN0b21lck90cFRhYmxlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vc3JjL21vZHVsZXMvY3VzdG9tZXItcmVnaXN0cmF0aW9uL21pZ3JhdGlvbnMvTWlncmF0aW9uMjAyNTExMjIxMTI5MTZDcmVhdGVDdXN0b21lck90cFRhYmxlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLHNEQUFpRDtBQUVqRCxNQUFhLDZDQUE4QyxTQUFRLHNCQUFTO0lBQzFFLEtBQUssQ0FBQyxFQUFFO1FBQ04sSUFBSSxDQUFDLE1BQU0sQ0FBQzs7Ozs7Ozs7Ozs7Ozs7OztLQWdCWCxDQUFDLENBQUE7UUFFRixJQUFJLENBQUMsTUFBTSxDQUFDOztLQUVYLENBQUMsQ0FBQTtRQUVGLElBQUksQ0FBQyxNQUFNLENBQUM7O0tBRVgsQ0FBQyxDQUFBO1FBRUYsSUFBSSxDQUFDLE1BQU0sQ0FBQzs7S0FFWCxDQUFDLENBQUE7UUFFRixJQUFJLENBQUMsTUFBTSxDQUFDOztLQUVYLENBQUMsQ0FBQTtRQUVGLElBQUksQ0FBQyxNQUFNLENBQUM7O0tBRVgsQ0FBQyxDQUFBO1FBRUYsNERBQTREO1FBQzVELElBQUksQ0FBQyxNQUFNLENBQUM7Ozs7Ozs7OztLQVNYLENBQUMsQ0FBQTtJQUNKLENBQUM7SUFFRCxLQUFLLENBQUMsSUFBSTtRQUNSLElBQUksQ0FBQyxNQUFNLENBQUMsMkRBQTJELENBQUMsQ0FBQTtJQUMxRSxDQUFDO0NBQ0Y7QUF4REQsc0dBd0RDIn0=
|
|
@@ -1,35 +1,68 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
2
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
12
|
exports.CustomerOtp = void 0;
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
type:
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
},
|
|
34
|
-
|
|
35
|
-
|
|
13
|
+
const core_1 = require("@mikro-orm/core");
|
|
14
|
+
let CustomerOtp = class CustomerOtp {
|
|
15
|
+
};
|
|
16
|
+
exports.CustomerOtp = CustomerOtp;
|
|
17
|
+
__decorate([
|
|
18
|
+
(0, core_1.PrimaryKey)({ type: "text" }),
|
|
19
|
+
__metadata("design:type", String)
|
|
20
|
+
], CustomerOtp.prototype, "id", void 0);
|
|
21
|
+
__decorate([
|
|
22
|
+
(0, core_1.Property)({ type: "text", nullable: true }),
|
|
23
|
+
__metadata("design:type", String)
|
|
24
|
+
], CustomerOtp.prototype, "customer_id", void 0);
|
|
25
|
+
__decorate([
|
|
26
|
+
(0, core_1.Enum)(() => ["email", "phone"]),
|
|
27
|
+
__metadata("design:type", String)
|
|
28
|
+
], CustomerOtp.prototype, "channel_type", void 0);
|
|
29
|
+
__decorate([
|
|
30
|
+
(0, core_1.Property)({ type: "text" }),
|
|
31
|
+
__metadata("design:type", String)
|
|
32
|
+
], CustomerOtp.prototype, "address", void 0);
|
|
33
|
+
__decorate([
|
|
34
|
+
(0, core_1.Property)({ type: "text" }),
|
|
35
|
+
__metadata("design:type", String)
|
|
36
|
+
], CustomerOtp.prototype, "otp_hash", void 0);
|
|
37
|
+
__decorate([
|
|
38
|
+
(0, core_1.Enum)(() => ["email_verification", "phone_verification", "forget_password"]),
|
|
39
|
+
__metadata("design:type", String)
|
|
40
|
+
], CustomerOtp.prototype, "otp_type", void 0);
|
|
41
|
+
__decorate([
|
|
42
|
+
(0, core_1.Property)({ type: "text", unique: true }),
|
|
43
|
+
__metadata("design:type", String)
|
|
44
|
+
], CustomerOtp.prototype, "token", void 0);
|
|
45
|
+
__decorate([
|
|
46
|
+
(0, core_1.Property)({ type: "timestamptz" }),
|
|
47
|
+
__metadata("design:type", Date)
|
|
48
|
+
], CustomerOtp.prototype, "expires_at", void 0);
|
|
49
|
+
__decorate([
|
|
50
|
+
(0, core_1.Property)({ type: "timestamptz", nullable: true }),
|
|
51
|
+
__metadata("design:type", Date)
|
|
52
|
+
], CustomerOtp.prototype, "verified_at", void 0);
|
|
53
|
+
__decorate([
|
|
54
|
+
(0, core_1.Property)({ type: "integer", default: 0 }),
|
|
55
|
+
__metadata("design:type", Number)
|
|
56
|
+
], CustomerOtp.prototype, "attempts", void 0);
|
|
57
|
+
__decorate([
|
|
58
|
+
(0, core_1.Property)({ type: "timestamptz" }),
|
|
59
|
+
__metadata("design:type", Date)
|
|
60
|
+
], CustomerOtp.prototype, "created_at", void 0);
|
|
61
|
+
__decorate([
|
|
62
|
+
(0, core_1.Property)({ type: "timestamptz", onUpdate: () => new Date() }),
|
|
63
|
+
__metadata("design:type", Date)
|
|
64
|
+
], CustomerOtp.prototype, "updated_at", void 0);
|
|
65
|
+
exports.CustomerOtp = CustomerOtp = __decorate([
|
|
66
|
+
(0, core_1.Entity)({ tableName: "customer_registration_otp" })
|
|
67
|
+
], CustomerOtp);
|
|
68
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3VzdG9tZXItb3RwLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vc3JjL21vZHVsZXMvY3VzdG9tZXItcmVnaXN0cmF0aW9uL21vZGVscy9jdXN0b21lci1vdHAudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7O0FBQUEsMENBS3dCO0FBTWpCLElBQU0sV0FBVyxHQUFqQixNQUFNLFdBQVc7Q0FvQ3ZCLENBQUE7QUFwQ1ksa0NBQVc7QUFFdEI7SUFEQyxJQUFBLGlCQUFVLEVBQUMsRUFBRSxJQUFJLEVBQUUsTUFBTSxFQUFFLENBQUM7O3VDQUNsQjtBQUdYO0lBREMsSUFBQSxlQUFRLEVBQUMsRUFBRSxJQUFJLEVBQUUsTUFBTSxFQUFFLFFBQVEsRUFBRSxJQUFJLEVBQUUsQ0FBQzs7Z0RBQ3ZCO0FBR3BCO0lBREMsSUFBQSxXQUFJLEVBQUMsR0FBRyxFQUFFLENBQUMsQ0FBQyxPQUFPLEVBQUUsT0FBTyxDQUFDLENBQUM7O2lEQUNMO0FBRzFCO0lBREMsSUFBQSxlQUFRLEVBQUMsRUFBRSxJQUFJLEVBQUUsTUFBTSxFQUFFLENBQUM7OzRDQUNYO0FBR2hCO0lBREMsSUFBQSxlQUFRLEVBQUMsRUFBRSxJQUFJLEVBQUUsTUFBTSxFQUFFLENBQUM7OzZDQUNWO0FBR2pCO0lBREMsSUFBQSxXQUFJLEVBQUMsR0FBRyxFQUFFLENBQUMsQ0FBQyxvQkFBb0IsRUFBRSxvQkFBb0IsRUFBRSxpQkFBaUIsQ0FBQyxDQUFDOzs2Q0FDMUQ7QUFHbEI7SUFEQyxJQUFBLGVBQVEsRUFBQyxFQUFFLElBQUksRUFBRSxNQUFNLEVBQUUsTUFBTSxFQUFFLElBQUksRUFBRSxDQUFDOzswQ0FDM0I7QUFHZDtJQURDLElBQUEsZUFBUSxFQUFDLEVBQUUsSUFBSSxFQUFFLGFBQWEsRUFBRSxDQUFDOzhCQUNyQixJQUFJOytDQUFBO0FBR2pCO0lBREMsSUFBQSxlQUFRLEVBQUMsRUFBRSxJQUFJLEVBQUUsYUFBYSxFQUFFLFFBQVEsRUFBRSxJQUFJLEVBQUUsQ0FBQzs4QkFDcEMsSUFBSTtnREFBQTtBQUdsQjtJQURDLElBQUEsZUFBUSxFQUFDLEVBQUUsSUFBSSxFQUFFLFNBQVMsRUFBRSxPQUFPLEVBQUUsQ0FBQyxFQUFFLENBQUM7OzZDQUN6QjtBQUdqQjtJQURDLElBQUEsZUFBUSxFQUFDLEVBQUUsSUFBSSxFQUFFLGFBQWEsRUFBRSxDQUFDOzhCQUNyQixJQUFJOytDQUFBO0FBR2pCO0lBREMsSUFBQSxlQUFRLEVBQUMsRUFBRSxJQUFJLEVBQUUsYUFBYSxFQUFFLFFBQVEsRUFBRSxHQUFHLEVBQUUsQ0FBQyxJQUFJLElBQUksRUFBRSxFQUFFLENBQUM7OEJBQ2pELElBQUk7K0NBQUE7c0JBbkNOLFdBQVc7SUFEdkIsSUFBQSxhQUFNLEVBQUMsRUFBRSxTQUFTLEVBQUUsMkJBQTJCLEVBQUUsQ0FBQztHQUN0QyxXQUFXLENBb0N2QiJ9
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OTPService = void 0;
|
|
4
|
+
const utils_1 = require("@medusajs/framework/utils");
|
|
5
|
+
const customer_otp_1 = require("../models/customer-otp");
|
|
6
|
+
const otp_generator_1 = require("../../../utils/otp-generator");
|
|
7
|
+
const crypto_1 = require("../../../utils/crypto");
|
|
8
|
+
const token_generator_1 = require("../../../utils/token-generator");
|
|
9
|
+
const otp_errors_1 = require("../../../errors/otp-errors");
|
|
10
|
+
class OTPService {
|
|
11
|
+
constructor({ customerService, notificationService, manager, config, }) {
|
|
12
|
+
this.customerService_ = customerService;
|
|
13
|
+
this.notificationService_ = notificationService;
|
|
14
|
+
this.manager_ = manager;
|
|
15
|
+
this.config_ = config;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Get the EntityManager instance (for use in API routes)
|
|
19
|
+
*/
|
|
20
|
+
getManager() {
|
|
21
|
+
return this.manager_;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Generate OTP based on length and charset
|
|
25
|
+
*/
|
|
26
|
+
generateOTP(length, charset) {
|
|
27
|
+
if (charset === "numeric") {
|
|
28
|
+
return (0, otp_generator_1.generateNumericOTP)(length);
|
|
29
|
+
}
|
|
30
|
+
return (0, otp_generator_1.generateAlphanumericOTP)(length);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Hash OTP
|
|
34
|
+
*/
|
|
35
|
+
async hashOTP(otp) {
|
|
36
|
+
return await (0, crypto_1.hashOTP)(otp);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Verify OTP
|
|
40
|
+
*/
|
|
41
|
+
async verifyOTP(input, hash) {
|
|
42
|
+
return await (0, crypto_1.compareOTP)(input, hash);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Create and store OTP record
|
|
46
|
+
*/
|
|
47
|
+
async createOTP(customerId, channelType, address, otpType, config) {
|
|
48
|
+
try {
|
|
49
|
+
if (!address || address.trim().length === 0) {
|
|
50
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "Address (email or phone) is required to create OTP");
|
|
51
|
+
}
|
|
52
|
+
// Generate OTP
|
|
53
|
+
const otp = this.generateOTP(config.otpLength, config.otpCharset);
|
|
54
|
+
const otpHash = await this.hashOTP(otp);
|
|
55
|
+
// Generate unique token
|
|
56
|
+
const token = (0, token_generator_1.generateToken)();
|
|
57
|
+
// Calculate expiry
|
|
58
|
+
const expiresAt = new Date();
|
|
59
|
+
expiresAt.setMinutes(expiresAt.getMinutes() + config.otpExpiryMinutes);
|
|
60
|
+
// Create OTP record
|
|
61
|
+
const otpRecord = this.manager_.create(customer_otp_1.CustomerOtp, {
|
|
62
|
+
id: `otp_${Date.now()}_${Math.random().toString(36).substring(7)}`,
|
|
63
|
+
customer_id: customerId ?? undefined,
|
|
64
|
+
channel_type: channelType,
|
|
65
|
+
address: address.trim(),
|
|
66
|
+
otp_hash: otpHash,
|
|
67
|
+
otp_type: otpType,
|
|
68
|
+
token,
|
|
69
|
+
expires_at: expiresAt,
|
|
70
|
+
attempts: 0,
|
|
71
|
+
created_at: new Date(),
|
|
72
|
+
updated_at: new Date(),
|
|
73
|
+
});
|
|
74
|
+
await this.manager_.persistAndFlush(otpRecord);
|
|
75
|
+
return { otp, token, expiresAt };
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
if (error instanceof utils_1.MedusaError) {
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.UNEXPECTED_STATE, `Failed to create OTP: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Verify OTP using token
|
|
86
|
+
*/
|
|
87
|
+
async verifyOTPByToken(token, code, otpType, config) {
|
|
88
|
+
try {
|
|
89
|
+
if (!token || !code) {
|
|
90
|
+
throw new otp_errors_1.OTPInvalidError("Token and code are required");
|
|
91
|
+
}
|
|
92
|
+
const otpRecord = await this.manager_.findOne(customer_otp_1.CustomerOtp, {
|
|
93
|
+
token,
|
|
94
|
+
otp_type: otpType,
|
|
95
|
+
});
|
|
96
|
+
if (!otpRecord) {
|
|
97
|
+
throw new otp_errors_1.OTPInvalidError("OTP not found or invalid token");
|
|
98
|
+
}
|
|
99
|
+
// Check if expired
|
|
100
|
+
if (new Date() > otpRecord.expires_at) {
|
|
101
|
+
throw new otp_errors_1.OTPExpiredError("OTP has expired. Please request a new one.");
|
|
102
|
+
}
|
|
103
|
+
// Check if already verified
|
|
104
|
+
if (otpRecord.verified_at) {
|
|
105
|
+
throw new otp_errors_1.OTPInvalidError("OTP has already been verified");
|
|
106
|
+
}
|
|
107
|
+
// Check max attempts
|
|
108
|
+
if (otpRecord.attempts >= config.maxAttempts) {
|
|
109
|
+
throw new otp_errors_1.OTPMaxAttemptsError(`Maximum verification attempts (${config.maxAttempts}) exceeded. Please request a new OTP.`);
|
|
110
|
+
}
|
|
111
|
+
// Verify OTP
|
|
112
|
+
const isValid = await this.verifyOTP(code, otpRecord.otp_hash);
|
|
113
|
+
// Increment attempts before checking validity to prevent timing attacks
|
|
114
|
+
otpRecord.attempts += 1;
|
|
115
|
+
await this.manager_.flush();
|
|
116
|
+
if (!isValid) {
|
|
117
|
+
const remainingAttempts = config.maxAttempts - otpRecord.attempts;
|
|
118
|
+
throw new otp_errors_1.OTPInvalidError(remainingAttempts > 0
|
|
119
|
+
? `Invalid OTP code. ${remainingAttempts} attempt(s) remaining.`
|
|
120
|
+
: "Invalid OTP code. Maximum attempts reached.");
|
|
121
|
+
}
|
|
122
|
+
// Mark as verified
|
|
123
|
+
otpRecord.verified_at = new Date();
|
|
124
|
+
await this.manager_.flush();
|
|
125
|
+
return {
|
|
126
|
+
verified: true,
|
|
127
|
+
customerId: otpRecord.customer_id ?? null,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
// Re-throw custom errors as-is
|
|
132
|
+
if (error instanceof otp_errors_1.OTPExpiredError ||
|
|
133
|
+
error instanceof otp_errors_1.OTPInvalidError ||
|
|
134
|
+
error instanceof otp_errors_1.OTPMaxAttemptsError ||
|
|
135
|
+
error instanceof otp_errors_1.OTPThrottleError) {
|
|
136
|
+
throw error;
|
|
137
|
+
}
|
|
138
|
+
// Wrap unexpected errors
|
|
139
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.UNEXPECTED_STATE, `Failed to verify OTP: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Resend OTP for existing token
|
|
144
|
+
*/
|
|
145
|
+
async resendOTP(token, config) {
|
|
146
|
+
try {
|
|
147
|
+
if (!token) {
|
|
148
|
+
throw new otp_errors_1.OTPInvalidError("Token is required to resend OTP");
|
|
149
|
+
}
|
|
150
|
+
const existingRecord = await this.manager_.findOne(customer_otp_1.CustomerOtp, { token });
|
|
151
|
+
if (!existingRecord) {
|
|
152
|
+
throw new otp_errors_1.OTPInvalidError("OTP token not found");
|
|
153
|
+
}
|
|
154
|
+
// Check throttle
|
|
155
|
+
await this.checkThrottle(existingRecord.channel_type, existingRecord.address, existingRecord.otp_type, config);
|
|
156
|
+
// Generate new OTP
|
|
157
|
+
const otp = this.generateOTP(config.otpLength, config.otpCharset);
|
|
158
|
+
const otpHash = await this.hashOTP(otp);
|
|
159
|
+
// Generate new token
|
|
160
|
+
const newToken = (0, token_generator_1.generateToken)();
|
|
161
|
+
// Calculate new expiry
|
|
162
|
+
const expiresAt = new Date();
|
|
163
|
+
expiresAt.setMinutes(expiresAt.getMinutes() + config.otpExpiryMinutes);
|
|
164
|
+
// Update existing record
|
|
165
|
+
existingRecord.otp_hash = otpHash;
|
|
166
|
+
existingRecord.token = newToken;
|
|
167
|
+
existingRecord.expires_at = expiresAt;
|
|
168
|
+
existingRecord.attempts = 0;
|
|
169
|
+
existingRecord.verified_at = undefined;
|
|
170
|
+
existingRecord.updated_at = new Date();
|
|
171
|
+
await this.manager_.flush();
|
|
172
|
+
return { otp, token: newToken, expiresAt };
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
// Re-throw custom errors as-is
|
|
176
|
+
if (error instanceof otp_errors_1.OTPInvalidError ||
|
|
177
|
+
error instanceof otp_errors_1.OTPThrottleError) {
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
// Wrap unexpected errors
|
|
181
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.UNEXPECTED_STATE, `Failed to resend OTP: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Check if resend is allowed (throttle check)
|
|
186
|
+
*/
|
|
187
|
+
async checkThrottle(channelType, address, otpType, config) {
|
|
188
|
+
let throttleSeconds;
|
|
189
|
+
switch (otpType) {
|
|
190
|
+
case "email_verification":
|
|
191
|
+
throttleSeconds = config.email.resendThrottleSeconds;
|
|
192
|
+
break;
|
|
193
|
+
case "phone_verification":
|
|
194
|
+
throttleSeconds = config.phone.resendThrottleSeconds;
|
|
195
|
+
break;
|
|
196
|
+
case "forget_password":
|
|
197
|
+
throttleSeconds = config.forgetPassword.resendThrottleSeconds;
|
|
198
|
+
break;
|
|
199
|
+
default:
|
|
200
|
+
throttleSeconds = 60;
|
|
201
|
+
}
|
|
202
|
+
const throttleTime = new Date();
|
|
203
|
+
throttleTime.setSeconds(throttleTime.getSeconds() - throttleSeconds);
|
|
204
|
+
const recentOTP = await this.manager_.findOne(customer_otp_1.CustomerOtp, {
|
|
205
|
+
channel_type: channelType,
|
|
206
|
+
address,
|
|
207
|
+
otp_type: otpType,
|
|
208
|
+
created_at: { $gte: throttleTime },
|
|
209
|
+
}, { orderBy: { created_at: "DESC" } });
|
|
210
|
+
if (recentOTP) {
|
|
211
|
+
throw new otp_errors_1.OTPThrottleError(`Please wait ${throttleSeconds} seconds before requesting a new OTP`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Cleanup expired OTPs
|
|
216
|
+
*/
|
|
217
|
+
async cleanupExpiredOTPs() {
|
|
218
|
+
const now = new Date();
|
|
219
|
+
await this.manager_.nativeDelete(customer_otp_1.CustomerOtp, {
|
|
220
|
+
expires_at: { $lt: now },
|
|
221
|
+
verified_at: null,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
exports.OTPService = OTPService;
|
|
226
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3RwLXNlcnZpY2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9zcmMvbW9kdWxlcy9jdXN0b21lci1yZWdpc3RyYXRpb24vc2VydmljZXMvb3RwLXNlcnZpY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQ0EscURBQWdFO0FBRWhFLHlEQUEwRTtBQUMxRSxnRUFBMEY7QUFDMUYsa0RBQTJEO0FBQzNELG9FQUE4RDtBQUU5RCwyREFLbUM7QUFFbkMsTUFBYSxVQUFVO0lBTXJCLFlBQ0UsRUFDRSxlQUFlLEVBQ2YsbUJBQW1CLEVBQ25CLE9BQU8sRUFDUCxNQUFNLEdBTVA7UUFFRCxJQUFJLENBQUMsZ0JBQWdCLEdBQUcsZUFBZSxDQUFBO1FBQ3ZDLElBQUksQ0FBQyxvQkFBb0IsR0FBRyxtQkFBbUIsQ0FBQTtRQUMvQyxJQUFJLENBQUMsUUFBUSxHQUFHLE9BQU8sQ0FBQTtRQUN2QixJQUFJLENBQUMsT0FBTyxHQUFHLE1BQU0sQ0FBQTtJQUN2QixDQUFDO0lBRUQ7O09BRUc7SUFDSCxVQUFVO1FBQ1IsT0FBTyxJQUFJLENBQUMsUUFBUSxDQUFBO0lBQ3RCLENBQUM7SUFFRDs7T0FFRztJQUNILFdBQVcsQ0FBQyxNQUFjLEVBQUUsT0FBbUI7UUFDN0MsSUFBSSxPQUFPLEtBQUssU0FBUyxFQUFFLENBQUM7WUFDMUIsT0FBTyxJQUFBLGtDQUFrQixFQUFDLE1BQU0sQ0FBQyxDQUFBO1FBQ25DLENBQUM7UUFDRCxPQUFPLElBQUEsdUNBQXVCLEVBQUMsTUFBTSxDQUFDLENBQUE7SUFDeEMsQ0FBQztJQUVEOztPQUVHO0lBQ0gsS0FBSyxDQUFDLE9BQU8sQ0FBQyxHQUFXO1FBQ3ZCLE9BQU8sTUFBTSxJQUFBLGdCQUFPLEVBQUMsR0FBRyxDQUFDLENBQUE7SUFDM0IsQ0FBQztJQUVEOztPQUVHO0lBQ0gsS0FBSyxDQUFDLFNBQVMsQ0FBQyxLQUFhLEVBQUUsSUFBWTtRQUN6QyxPQUFPLE1BQU0sSUFBQSxtQkFBVSxFQUFDLEtBQUssRUFBRSxJQUFJLENBQUMsQ0FBQTtJQUN0QyxDQUFDO0lBRUQ7O09BRUc7SUFDSCxLQUFLLENBQUMsU0FBUyxDQUNiLFVBQXlCLEVBQ3pCLFdBQXdCLEVBQ3hCLE9BQWUsRUFDZixPQUFnQixFQUNoQixNQUErQjtRQUUvQixJQUFJLENBQUM7WUFDSCxJQUFJLENBQUMsT0FBTyxJQUFJLE9BQU8sQ0FBQyxJQUFJLEVBQUUsQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFLENBQUM7Z0JBQzVDLE1BQU0sSUFBSSxtQkFBVyxDQUNuQixtQkFBVyxDQUFDLEtBQUssQ0FBQyxZQUFZLEVBQzlCLG9EQUFvRCxDQUNyRCxDQUFBO1lBQ0gsQ0FBQztZQUVELGVBQWU7WUFDZixNQUFNLEdBQUcsR0FBRyxJQUFJLENBQUMsV0FBVyxDQUFDLE1BQU0sQ0FBQyxTQUFTLEVBQUUsTUFBTSxDQUFDLFVBQVUsQ0FBQyxDQUFBO1lBQ2pFLE1BQU0sT0FBTyxHQUFHLE1BQU0sSUFBSSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsQ0FBQTtZQUV2Qyx3QkFBd0I7WUFDeEIsTUFBTSxLQUFLLEdBQUcsSUFBQSwrQkFBYSxHQUFFLENBQUE7WUFFN0IsbUJBQW1CO1lBQ25CLE1BQU0sU0FBUyxHQUFHLElBQUksSUFBSSxFQUFFLENBQUE7WUFDNUIsU0FBUyxDQUFDLFVBQVUsQ0FBQyxTQUFTLENBQUMsVUFBVSxFQUFFLEdBQUcsTUFBTSxDQUFDLGdCQUFnQixDQUFDLENBQUE7WUFFdEUsb0JBQW9CO1lBQ3BCLE1BQU0sU0FBUyxHQUFHLElBQUksQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLDBCQUFXLEVBQUU7Z0JBQ2xELEVBQUUsRUFBRSxPQUFPLElBQUksQ0FBQyxHQUFHLEVBQUUsSUFBSSxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsRUFBRTtnQkFDbEUsV0FBVyxFQUFFLFVBQVUsSUFBSSxTQUFTO2dCQUNwQyxZQUFZLEVBQUUsV0FBVztnQkFDekIsT0FBTyxFQUFFLE9BQU8sQ0FBQyxJQUFJLEVBQUU7Z0JBQ3ZCLFFBQVEsRUFBRSxPQUFPO2dCQUNqQixRQUFRLEVBQUUsT0FBTztnQkFDakIsS0FBSztnQkFDTCxVQUFVLEVBQUUsU0FBUztnQkFDckIsUUFBUSxFQUFFLENBQUM7Z0JBQ1gsVUFBVSxFQUFFLElBQUksSUFBSSxFQUFFO2dCQUN0QixVQUFVLEVBQUUsSUFBSSxJQUFJLEVBQUU7YUFDdkIsQ0FBQyxDQUFBO1lBRUYsTUFBTSxJQUFJLENBQUMsUUFBUSxDQUFDLGVBQWUsQ0FBQyxTQUFTLENBQUMsQ0FBQTtZQUU5QyxPQUFPLEVBQUUsR0FBRyxFQUFFLEtBQUssRUFBRSxTQUFTLEVBQUUsQ0FBQTtRQUNsQyxDQUFDO1FBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztZQUNmLElBQUksS0FBSyxZQUFZLG1CQUFXLEVBQUUsQ0FBQztnQkFDakMsTUFBTSxLQUFLLENBQUE7WUFDYixDQUFDO1lBQ0QsTUFBTSxJQUFJLG1CQUFXLENBQ25CLG1CQUFXLENBQUMsS0FBSyxDQUFDLGdCQUFnQixFQUNsQyx5QkFBeUIsS0FBSyxZQUFZLEtBQUssQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsZUFBZSxFQUFFLENBQ3BGLENBQUE7UUFDSCxDQUFDO0lBQ0gsQ0FBQztJQUVEOztPQUVHO0lBQ0gsS0FBSyxDQUFDLGdCQUFnQixDQUNwQixLQUFhLEVBQ2IsSUFBWSxFQUNaLE9BQWdCLEVBQ2hCLE1BQStCO1FBRS9CLElBQUksQ0FBQztZQUNILElBQUksQ0FBQyxLQUFLLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztnQkFDcEIsTUFBTSxJQUFJLDRCQUFlLENBQUMsNkJBQTZCLENBQUMsQ0FBQTtZQUMxRCxDQUFDO1lBRUQsTUFBTSxTQUFTLEdBQUcsTUFBTSxJQUFJLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQywwQkFBVyxFQUFFO2dCQUN6RCxLQUFLO2dCQUNMLFFBQVEsRUFBRSxPQUFPO2FBQ2xCLENBQUMsQ0FBQTtZQUVGLElBQUksQ0FBQyxTQUFTLEVBQUUsQ0FBQztnQkFDZixNQUFNLElBQUksNEJBQWUsQ0FBQyxnQ0FBZ0MsQ0FBQyxDQUFBO1lBQzdELENBQUM7WUFFRCxtQkFBbUI7WUFDbkIsSUFBSSxJQUFJLElBQUksRUFBRSxHQUFHLFNBQVMsQ0FBQyxVQUFVLEVBQUUsQ0FBQztnQkFDdEMsTUFBTSxJQUFJLDRCQUFlLENBQUMsNENBQTRDLENBQUMsQ0FBQTtZQUN6RSxDQUFDO1lBRUQsNEJBQTRCO1lBQzVCLElBQUksU0FBUyxDQUFDLFdBQVcsRUFBRSxDQUFDO2dCQUMxQixNQUFNLElBQUksNEJBQWUsQ0FBQywrQkFBK0IsQ0FBQyxDQUFBO1lBQzVELENBQUM7WUFFRCxxQkFBcUI7WUFDckIsSUFBSSxTQUFTLENBQUMsUUFBUSxJQUFJLE1BQU0sQ0FBQyxXQUFXLEVBQUUsQ0FBQztnQkFDN0MsTUFBTSxJQUFJLGdDQUFtQixDQUMzQixrQ0FBa0MsTUFBTSxDQUFDLFdBQVcsdUNBQXVDLENBQzVGLENBQUE7WUFDSCxDQUFDO1lBRUQsYUFBYTtZQUNiLE1BQU0sT0FBTyxHQUFHLE1BQU0sSUFBSSxDQUFDLFNBQVMsQ0FBQyxJQUFJLEVBQUUsU0FBUyxDQUFDLFFBQVEsQ0FBQyxDQUFBO1lBRTlELHdFQUF3RTtZQUN4RSxTQUFTLENBQUMsUUFBUSxJQUFJLENBQUMsQ0FBQTtZQUN2QixNQUFNLElBQUksQ0FBQyxRQUFRLENBQUMsS0FBSyxFQUFFLENBQUE7WUFFM0IsSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFDO2dCQUNiLE1BQU0saUJBQWlCLEdBQUcsTUFBTSxDQUFDLFdBQVcsR0FBRyxTQUFTLENBQUMsUUFBUSxDQUFBO2dCQUNqRSxNQUFNLElBQUksNEJBQWUsQ0FDdkIsaUJBQWlCLEdBQUcsQ0FBQztvQkFDbkIsQ0FBQyxDQUFDLHFCQUFxQixpQkFBaUIsd0JBQXdCO29CQUNoRSxDQUFDLENBQUMsNkNBQTZDLENBQ2xELENBQUE7WUFDSCxDQUFDO1lBRUQsbUJBQW1CO1lBQ25CLFNBQVMsQ0FBQyxXQUFXLEdBQUcsSUFBSSxJQUFJLEVBQUUsQ0FBQTtZQUNsQyxNQUFNLElBQUksQ0FBQyxRQUFRLENBQUMsS0FBSyxFQUFFLENBQUE7WUFFM0IsT0FBTztnQkFDTCxRQUFRLEVBQUUsSUFBSTtnQkFDZCxVQUFVLEVBQUUsU0FBUyxDQUFDLFdBQVcsSUFBSSxJQUFJO2FBQzFDLENBQUE7UUFDSCxDQUFDO1FBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztZQUNmLCtCQUErQjtZQUMvQixJQUNFLEtBQUssWUFBWSw0QkFBZTtnQkFDaEMsS0FBSyxZQUFZLDRCQUFlO2dCQUNoQyxLQUFLLFlBQVksZ0NBQW1CO2dCQUNwQyxLQUFLLFlBQVksNkJBQWdCLEVBQ2pDLENBQUM7Z0JBQ0QsTUFBTSxLQUFLLENBQUE7WUFDYixDQUFDO1lBQ0QseUJBQXlCO1lBQ3pCLE1BQU0sSUFBSSxtQkFBVyxDQUNuQixtQkFBVyxDQUFDLEtBQUssQ0FBQyxnQkFBZ0IsRUFDbEMseUJBQXlCLEtBQUssWUFBWSxLQUFLLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLGVBQWUsRUFBRSxDQUNwRixDQUFBO1FBQ0gsQ0FBQztJQUNILENBQUM7SUFFRDs7T0FFRztJQUNILEtBQUssQ0FBQyxTQUFTLENBQ2IsS0FBYSxFQUNiLE1BQStCO1FBRS9CLElBQUksQ0FBQztZQUNILElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQztnQkFDWCxNQUFNLElBQUksNEJBQWUsQ0FBQyxpQ0FBaUMsQ0FBQyxDQUFBO1lBQzlELENBQUM7WUFFRCxNQUFNLGNBQWMsR0FBRyxNQUFNLElBQUksQ0FBQyxRQUFRLENBQUMsT0FBTyxDQUFDLDBCQUFXLEVBQUUsRUFBRSxLQUFLLEVBQUUsQ0FBQyxDQUFBO1lBRTFFLElBQUksQ0FBQyxjQUFjLEVBQUUsQ0FBQztnQkFDcEIsTUFBTSxJQUFJLDRCQUFlLENBQUMscUJBQXFCLENBQUMsQ0FBQTtZQUNsRCxDQUFDO1lBRUQsaUJBQWlCO1lBQ2pCLE1BQU0sSUFBSSxDQUFDLGFBQWEsQ0FDdEIsY0FBYyxDQUFDLFlBQVksRUFDM0IsY0FBYyxDQUFDLE9BQU8sRUFDdEIsY0FBYyxDQUFDLFFBQVEsRUFDdkIsTUFBTSxDQUNQLENBQUE7WUFFRCxtQkFBbUI7WUFDbkIsTUFBTSxHQUFHLEdBQUcsSUFBSSxDQUFDLFdBQVcsQ0FBQyxNQUFNLENBQUMsU0FBUyxFQUFFLE1BQU0sQ0FBQyxVQUFVLENBQUMsQ0FBQTtZQUNqRSxNQUFNLE9BQU8sR0FBRyxNQUFNLElBQUksQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLENBQUE7WUFFdkMscUJBQXFCO1lBQ3JCLE1BQU0sUUFBUSxHQUFHLElBQUEsK0JBQWEsR0FBRSxDQUFBO1lBRWhDLHVCQUF1QjtZQUN2QixNQUFNLFNBQVMsR0FBRyxJQUFJLElBQUksRUFBRSxDQUFBO1lBQzVCLFNBQVMsQ0FBQyxVQUFVLENBQUMsU0FBUyxDQUFDLFVBQVUsRUFBRSxHQUFHLE1BQU0sQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFBO1lBRXRFLHlCQUF5QjtZQUN6QixjQUFjLENBQUMsUUFBUSxHQUFHLE9BQU8sQ0FBQTtZQUNqQyxjQUFjLENBQUMsS0FBSyxHQUFHLFFBQVEsQ0FBQTtZQUMvQixjQUFjLENBQUMsVUFBVSxHQUFHLFNBQVMsQ0FBQTtZQUNyQyxjQUFjLENBQUMsUUFBUSxHQUFHLENBQUMsQ0FBQTtZQUMzQixjQUFjLENBQUMsV0FBVyxHQUFHLFNBQVMsQ0FBQTtZQUN0QyxjQUFjLENBQUMsVUFBVSxHQUFHLElBQUksSUFBSSxFQUFFLENBQUE7WUFFdEMsTUFBTSxJQUFJLENBQUMsUUFBUSxDQUFDLEtBQUssRUFBRSxDQUFBO1lBRTNCLE9BQU8sRUFBRSxHQUFHLEVBQUUsS0FBSyxFQUFFLFFBQVEsRUFBRSxTQUFTLEVBQUUsQ0FBQTtRQUM1QyxDQUFDO1FBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztZQUNmLCtCQUErQjtZQUMvQixJQUNFLEtBQUssWUFBWSw0QkFBZTtnQkFDaEMsS0FBSyxZQUFZLDZCQUFnQixFQUNqQyxDQUFDO2dCQUNELE1BQU0sS0FBSyxDQUFBO1lBQ2IsQ0FBQztZQUNELHlCQUF5QjtZQUN6QixNQUFNLElBQUksbUJBQVcsQ0FDbkIsbUJBQVcsQ0FBQyxLQUFLLENBQUMsZ0JBQWdCLEVBQ2xDLHlCQUF5QixLQUFLLFlBQVksS0FBSyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxlQUFlLEVBQUUsQ0FDcEYsQ0FBQTtRQUNILENBQUM7SUFDSCxDQUFDO0lBRUQ7O09BRUc7SUFDSCxLQUFLLENBQUMsYUFBYSxDQUNqQixXQUF3QixFQUN4QixPQUFlLEVBQ2YsT0FBZ0IsRUFDaEIsTUFBK0I7UUFFL0IsSUFBSSxlQUF1QixDQUFBO1FBRTNCLFFBQVEsT0FBTyxFQUFFLENBQUM7WUFDaEIsS0FBSyxvQkFBb0I7Z0JBQ3ZCLGVBQWUsR0FBRyxNQUFNLENBQUMsS0FBSyxDQUFDLHFCQUFxQixDQUFBO2dCQUNwRCxNQUFLO1lBQ1AsS0FBSyxvQkFBb0I7Z0JBQ3ZCLGVBQWUsR0FBRyxNQUFNLENBQUMsS0FBSyxDQUFDLHFCQUFxQixDQUFBO2dCQUNwRCxNQUFLO1lBQ1AsS0FBSyxpQkFBaUI7Z0JBQ3BCLGVBQWUsR0FBRyxNQUFNLENBQUMsY0FBYyxDQUFDLHFCQUFxQixDQUFBO2dCQUM3RCxNQUFLO1lBQ1A7Z0JBQ0UsZUFBZSxHQUFHLEVBQUUsQ0FBQTtRQUN4QixDQUFDO1FBRUQsTUFBTSxZQUFZLEdBQUcsSUFBSSxJQUFJLEVBQUUsQ0FBQTtRQUMvQixZQUFZLENBQUMsVUFBVSxDQUFDLFlBQVksQ0FBQyxVQUFVLEVBQUUsR0FBRyxlQUFlLENBQUMsQ0FBQTtRQUVwRSxNQUFNLFNBQVMsR0FBRyxNQUFNLElBQUksQ0FBQyxRQUFRLENBQUMsT0FBTyxDQUMzQywwQkFBVyxFQUNYO1lBQ0UsWUFBWSxFQUFFLFdBQVc7WUFDekIsT0FBTztZQUNQLFFBQVEsRUFBRSxPQUFPO1lBQ2pCLFVBQVUsRUFBRSxFQUFFLElBQUksRUFBRSxZQUFZLEVBQUU7U0FDbkMsRUFDRCxFQUFFLE9BQU8sRUFBRSxFQUFFLFVBQVUsRUFBRSxNQUFNLEVBQUUsRUFBRSxDQUNwQyxDQUFBO1FBRUQsSUFBSSxTQUFTLEVBQUUsQ0FBQztZQUNkLE1BQU0sSUFBSSw2QkFBZ0IsQ0FDeEIsZUFBZSxlQUFlLHNDQUFzQyxDQUNyRSxDQUFBO1FBQ0gsQ0FBQztJQUNILENBQUM7SUFFRDs7T0FFRztJQUNILEtBQUssQ0FBQyxrQkFBa0I7UUFDdEIsTUFBTSxHQUFHLEdBQUcsSUFBSSxJQUFJLEVBQUUsQ0FBQTtRQUN0QixNQUFNLElBQUksQ0FBQyxRQUFRLENBQUMsWUFBWSxDQUFDLDBCQUFXLEVBQUU7WUFDNUMsVUFBVSxFQUFFLEVBQUUsR0FBRyxFQUFFLEdBQUcsRUFBRTtZQUN4QixXQUFXLEVBQUUsSUFBSTtTQUNsQixDQUFDLENBQUE7SUFDSixDQUFDO0NBQ0Y7QUE1VEQsZ0NBNFRDIn0=
|