@endora-commerce/mod-customer-accounts 0.100.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.
Files changed (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +59 -0
  3. package/dist/admin/api/customer-groups-client.d.ts +27 -0
  4. package/dist/admin/api/customer-groups-client.d.ts.map +1 -0
  5. package/dist/admin/api/customer-groups-client.js +17 -0
  6. package/dist/admin/api/customer-groups-client.js.map +1 -0
  7. package/dist/admin/index.d.ts +33 -0
  8. package/dist/admin/index.d.ts.map +1 -0
  9. package/dist/admin/index.js +38 -0
  10. package/dist/admin/index.js.map +1 -0
  11. package/dist/admin/pages/CustomerGroupsPage.d.ts +18 -0
  12. package/dist/admin/pages/CustomerGroupsPage.d.ts.map +1 -0
  13. package/dist/admin/pages/CustomerGroupsPage.js +108 -0
  14. package/dist/admin/pages/CustomerGroupsPage.js.map +1 -0
  15. package/dist/backend/entities/customer-account.entity.d.ts +111 -0
  16. package/dist/backend/entities/customer-account.entity.d.ts.map +1 -0
  17. package/dist/backend/entities/customer-account.entity.js +230 -0
  18. package/dist/backend/entities/customer-account.entity.js.map +1 -0
  19. package/dist/backend/entities/customer-group.entity.d.ts +29 -0
  20. package/dist/backend/entities/customer-group.entity.d.ts.map +1 -0
  21. package/dist/backend/entities/customer-group.entity.js +70 -0
  22. package/dist/backend/entities/customer-group.entity.js.map +1 -0
  23. package/dist/backend/entities/password-reset-token.entity.d.ts +16 -0
  24. package/dist/backend/entities/password-reset-token.entity.d.ts.map +1 -0
  25. package/dist/backend/entities/password-reset-token.entity.js +59 -0
  26. package/dist/backend/entities/password-reset-token.entity.js.map +1 -0
  27. package/dist/backend/index.d.ts +144 -0
  28. package/dist/backend/index.d.ts.map +1 -0
  29. package/dist/backend/index.js +407 -0
  30. package/dist/backend/index.js.map +1 -0
  31. package/dist/backend/routes.admin.d.ts +26 -0
  32. package/dist/backend/routes.admin.d.ts.map +1 -0
  33. package/dist/backend/routes.admin.js +35 -0
  34. package/dist/backend/routes.admin.js.map +1 -0
  35. package/dist/backend/services/audit-references.d.ts +22 -0
  36. package/dist/backend/services/audit-references.d.ts.map +1 -0
  37. package/dist/backend/services/audit-references.js +46 -0
  38. package/dist/backend/services/audit-references.js.map +1 -0
  39. package/dist/backend/services/customer-account-lifecycle-ports.d.ts +168 -0
  40. package/dist/backend/services/customer-account-lifecycle-ports.d.ts.map +1 -0
  41. package/dist/backend/services/customer-account-lifecycle-ports.js +420 -0
  42. package/dist/backend/services/customer-account-lifecycle-ports.js.map +1 -0
  43. package/dist/backend/services/customer-account-ports.d.ts +143 -0
  44. package/dist/backend/services/customer-account-ports.d.ts.map +1 -0
  45. package/dist/backend/services/customer-account-ports.js +422 -0
  46. package/dist/backend/services/customer-account-ports.js.map +1 -0
  47. package/dist/backend/services/customer-auth-service.d.ts +62 -0
  48. package/dist/backend/services/customer-auth-service.d.ts.map +1 -0
  49. package/dist/backend/services/customer-auth-service.js +105 -0
  50. package/dist/backend/services/customer-auth-service.js.map +1 -0
  51. package/dist/backend/services/customer-group-read-port.d.ts +24 -0
  52. package/dist/backend/services/customer-group-read-port.d.ts.map +1 -0
  53. package/dist/backend/services/customer-group-read-port.js +44 -0
  54. package/dist/backend/services/customer-group-read-port.js.map +1 -0
  55. package/dist/backend/services/customer-group-service.d.ts +21 -0
  56. package/dist/backend/services/customer-group-service.d.ts.map +1 -0
  57. package/dist/backend/services/customer-group-service.js +97 -0
  58. package/dist/backend/services/customer-group-service.js.map +1 -0
  59. package/dist/backend/services/customer-rollup-scope.d.ts +30 -0
  60. package/dist/backend/services/customer-rollup-scope.d.ts.map +1 -0
  61. package/dist/backend/services/customer-rollup-scope.js +42 -0
  62. package/dist/backend/services/customer-rollup-scope.js.map +1 -0
  63. package/dist/backend/services/password-reset-service.d.ts +15 -0
  64. package/dist/backend/services/password-reset-service.d.ts.map +1 -0
  65. package/dist/backend/services/password-reset-service.js +90 -0
  66. package/dist/backend/services/password-reset-service.js.map +1 -0
  67. package/dist/backend/services/role-service.d.ts +22 -0
  68. package/dist/backend/services/role-service.d.ts.map +1 -0
  69. package/dist/backend/services/role-service.js +83 -0
  70. package/dist/backend/services/role-service.js.map +1 -0
  71. package/dist/backend/services/two-factor-enrolments.d.ts +18 -0
  72. package/dist/backend/services/two-factor-enrolments.d.ts.map +1 -0
  73. package/dist/backend/services/two-factor-enrolments.js +2 -0
  74. package/dist/backend/services/two-factor-enrolments.js.map +1 -0
  75. package/dist/manifest.d.ts +169 -0
  76. package/dist/manifest.d.ts.map +1 -0
  77. package/dist/manifest.js +212 -0
  78. package/dist/manifest.js.map +1 -0
  79. package/dist/migrations/20260425T055041_customer_accounts_password_reset_tokens.d.ts +6 -0
  80. package/dist/migrations/20260425T055041_customer_accounts_password_reset_tokens.d.ts.map +1 -0
  81. package/dist/migrations/20260425T055041_customer_accounts_password_reset_tokens.js +25 -0
  82. package/dist/migrations/20260425T055041_customer_accounts_password_reset_tokens.js.map +1 -0
  83. package/dist/migrations/20260611T140351_customer_accounts_organization_optional.d.ts +34 -0
  84. package/dist/migrations/20260611T140351_customer_accounts_organization_optional.d.ts.map +1 -0
  85. package/dist/migrations/20260611T140351_customer_accounts_organization_optional.js +43 -0
  86. package/dist/migrations/20260611T140351_customer_accounts_organization_optional.js.map +1 -0
  87. package/dist/migrations/20260611T140403_customer_accounts_lifecycle.d.ts +54 -0
  88. package/dist/migrations/20260611T140403_customer_accounts_lifecycle.d.ts.map +1 -0
  89. package/dist/migrations/20260611T140403_customer_accounts_lifecycle.js +97 -0
  90. package/dist/migrations/20260611T140403_customer_accounts_lifecycle.js.map +1 -0
  91. package/dist/migrations/20260718T200341_customer_accounts_customer_account_custom_field_values.d.ts +12 -0
  92. package/dist/migrations/20260718T200341_customer_accounts_customer_account_custom_field_values.d.ts.map +1 -0
  93. package/dist/migrations/20260718T200341_customer_accounts_customer_account_custom_field_values.js +16 -0
  94. package/dist/migrations/20260718T200341_customer_accounts_customer_account_custom_field_values.js.map +1 -0
  95. package/dist/migrations/20260720T044255_customer_accounts_customer_subtree_rollup.d.ts +14 -0
  96. package/dist/migrations/20260720T044255_customer_accounts_customer_subtree_rollup.d.ts.map +1 -0
  97. package/dist/migrations/20260720T044255_customer_accounts_customer_subtree_rollup.js +18 -0
  98. package/dist/migrations/20260720T044255_customer_accounts_customer_subtree_rollup.js.map +1 -0
  99. package/dist/migrations/20260819T074816_customer_accounts_password_set_at.d.ts +22 -0
  100. package/dist/migrations/20260819T074816_customer_accounts_password_set_at.d.ts.map +1 -0
  101. package/dist/migrations/20260819T074816_customer_accounts_password_set_at.js +26 -0
  102. package/dist/migrations/20260819T074816_customer_accounts_password_set_at.js.map +1 -0
  103. package/dist/migrations/20260819T142837_customer_accounts_fold_email_case.d.ts +46 -0
  104. package/dist/migrations/20260819T142837_customer_accounts_fold_email_case.d.ts.map +1 -0
  105. package/dist/migrations/20260819T142837_customer_accounts_fold_email_case.js +97 -0
  106. package/dist/migrations/20260819T142837_customer_accounts_fold_email_case.js.map +1 -0
  107. package/dist/migrations/20260825T124759_customer_accounts_drop_legacy_two_factor_secret.d.ts +31 -0
  108. package/dist/migrations/20260825T124759_customer_accounts_drop_legacy_two_factor_secret.d.ts.map +1 -0
  109. package/dist/migrations/20260825T124759_customer_accounts_drop_legacy_two_factor_secret.js +35 -0
  110. package/dist/migrations/20260825T124759_customer_accounts_drop_legacy_two_factor_secret.js.map +1 -0
  111. package/dist/migrations/20260825T141659_customer_accounts_organization_required.d.ts +58 -0
  112. package/dist/migrations/20260825T141659_customer_accounts_organization_required.d.ts.map +1 -0
  113. package/dist/migrations/20260825T141659_customer_accounts_organization_required.js +133 -0
  114. package/dist/migrations/20260825T141659_customer_accounts_organization_required.js.map +1 -0
  115. package/dist/migrations/20260912T094701_customer_accounts_sales_channel_customer_accounts.d.ts +27 -0
  116. package/dist/migrations/20260912T094701_customer_accounts_sales_channel_customer_accounts.d.ts.map +1 -0
  117. package/dist/migrations/20260912T094701_customer_accounts_sales_channel_customer_accounts.js +44 -0
  118. package/dist/migrations/20260912T094701_customer_accounts_sales_channel_customer_accounts.js.map +1 -0
  119. package/dist/migrations/index.d.ts +36 -0
  120. package/dist/migrations/index.d.ts.map +1 -0
  121. package/dist/migrations/index.js +47 -0
  122. package/dist/migrations/index.js.map +1 -0
  123. package/docs/customer_accounts.md +38 -0
  124. package/i18n/en.json +30 -0
  125. package/i18n/pl.json +30 -0
  126. package/package.json +94 -0
  127. package/tailwind.css +14 -0
@@ -0,0 +1,143 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import { type CustomerAccountCreateInput, type CustomerAccountLookupOptions, type CustomerAccountMemberWritePort, type CustomerAccountProfilePatch, type CustomerAccountReadPort, type CustomerAccountRecord, type CustomerAccountRole, type CustomerAuthPort, type CustomerPasswordStatePort, type CustomerPasswordVerificationPort, type CustomerRolePort } from '@endora-commerce/contracts';
3
+ import type { AuditPort } from '@endora-commerce/platform/kernel';
4
+ import { CustomerAccount } from '../entities/customer-account.entity.js';
5
+ import type { CustomerAuthService } from './customer-auth-service.js';
6
+ import type { RoleService } from './role-service.js';
7
+ import type { TwoFactorEnrolmentReader } from './two-factor-enrolments.js';
8
+ /**
9
+ * The published face of `customer_accounts` (feature 075, Phase P).
10
+ *
11
+ * Nineteen modules read this module's table today, 51 of the sites on the
12
+ * entity class itself. What they ask for is narrow — an account by id, by ids,
13
+ * by e-mail, by organisation, and the two counting questions the "an
14
+ * organisation keeps at least one admin" rule is spelled with — so that is
15
+ * what the read port is, rather than the table.
16
+ *
17
+ * The mapping below is the point of the exercise and not ceremony:
18
+ * `passwordHash` does not survive it. It is read by exactly one module, and a
19
+ * record that carried it would turn every consumer into a place a credential
20
+ * can leak from.
21
+ */
22
+ /**
23
+ * The published record, with `twoFactorEnabled` taken from `mfa` rather than
24
+ * from this module's `two_factor_confirmed_at` column.
25
+ *
26
+ * The second parameter is required and has no default on purpose. That column's
27
+ * only non-null writer was the superseded TOTP path deleted on 2026-08-25, so
28
+ * the field was a **provably constant `false`** — on the admin customer detail,
29
+ * on both organisation member panels, and on `GET /api/v1/me/customer`, which
30
+ * is the buyer's own account page telling them their account is unprotected. A
31
+ * default would let the next call site reintroduce that silently; making the
32
+ * caller answer means the compiler asks.
33
+ */
34
+ export declare function toCustomerAccountRecord(account: CustomerAccount, twoFactorEnabled: boolean): CustomerAccountRecord;
35
+ /**
36
+ * One `mfa` read for a whole page of accounts, rather than one per row.
37
+ *
38
+ * Every caller is a list or a single row, so batching here keeps the admin
39
+ * customer list at two queries instead of one per customer, and leaves the
40
+ * single-row callers spelling the same thing.
41
+ */
42
+ export declare function toCustomerAccountRecords(twoFactorEnrolments: TwoFactorEnrolmentReader, accounts: readonly CustomerAccount[]): Promise<CustomerAccountRecord[]>;
43
+ /** The single-row spelling of {@link toCustomerAccountRecords}. */
44
+ export declare function toOneCustomerAccountRecord(twoFactorEnrolments: TwoFactorEnrolmentReader, account: CustomerAccount): Promise<CustomerAccountRecord>;
45
+ export declare class CustomerAccountReadService implements CustomerAccountReadPort {
46
+ private readonly emFactory;
47
+ private readonly twoFactorEnrolments;
48
+ constructor(emFactory: () => EntityManager, twoFactorEnrolments: TwoFactorEnrolmentReader);
49
+ findById(id: string, options?: CustomerAccountLookupOptions): Promise<CustomerAccountRecord | null>;
50
+ findByIds(ids: readonly string[], options?: CustomerAccountLookupOptions): Promise<CustomerAccountRecord[]>;
51
+ findByEmail(email: string, options?: CustomerAccountLookupOptions): Promise<CustomerAccountRecord | null>;
52
+ findInOrganization(id: string, organizationId: string, options?: CustomerAccountLookupOptions): Promise<CustomerAccountRecord | null>;
53
+ listByOrganization(organizationId: string, options?: CustomerAccountLookupOptions): Promise<CustomerAccountRecord[]>;
54
+ countByOrganizationRole(organizationId: string, role: CustomerAccountRole, options?: {
55
+ excludeCustomerAccountId?: string;
56
+ activeOnly?: boolean;
57
+ notBlocked?: boolean;
58
+ }): Promise<number>;
59
+ searchByEmail(query: string, limit: number): Promise<CustomerAccountRecord[]>;
60
+ searchIdsByName(query: string): Promise<string[]>;
61
+ listAll(): Promise<CustomerAccountRecord[]>;
62
+ }
63
+ /**
64
+ * Issue #222 — the one credential fact that crosses this boundary, and it
65
+ * crosses as a date rather than as anything derived from the hash.
66
+ *
67
+ * Separate from {@link CustomerAccountReadService} on purpose: that read port
68
+ * is what nineteen modules resolve, and the mapping above deliberately drops
69
+ * `passwordHash`. "Does this account have a password its holder can use" is a
70
+ * question one module asks, so it gets a port one module resolves.
71
+ *
72
+ * An unknown id answers `null`, not a throw: the caller's question is about a
73
+ * credential, and "no account" and "no password on record" want the same
74
+ * refusal from every caller there could be.
75
+ */
76
+ export declare class CustomerPasswordStateService implements CustomerPasswordStatePort {
77
+ private readonly emFactory;
78
+ constructor(emFactory: () => EntityManager);
79
+ passwordSetAt(customerAccountId: string): Promise<Date | null>;
80
+ }
81
+ /**
82
+ * Step-up re-verification, for `mfa` (feature 080, T052).
83
+ *
84
+ * The read is this module's own row and the comparison is the platform
85
+ * hasher's, so both halves are on this side of the seam. The composition roots
86
+ * held them instead: each read `passwordHash` off the entity and called
87
+ * `verifyPassword` itself, which is a credential column and a hash comparison
88
+ * living in a file that owns neither.
89
+ *
90
+ * The hash never leaves this class — that is the difference between this port
91
+ * and a `passwordHash` field on `CustomerAccountRecord`, and it is the whole
92
+ * reason the port exists.
93
+ */
94
+ export declare class CustomerPasswordVerificationService implements CustomerPasswordVerificationPort {
95
+ private readonly emFactory;
96
+ constructor(emFactory: () => EntityManager);
97
+ verifyPassword(customerAccountId: string, password: string): Promise<boolean>;
98
+ }
99
+ /**
100
+ * The member lifecycle `organizations` runs over this module's table
101
+ * (feature 075, Phase C).
102
+ *
103
+ * It ran it by creating and mutating the `CustomerAccount` entity in its own
104
+ * services and route handlers — seven write sites across five files. They are
105
+ * six methods here, each one unit of work on this table alone, because a write
106
+ * port that took the caller's `EntityManager` would publish the coupling
107
+ * instead of removing it (D-78).
108
+ *
109
+ * **Each write audits itself**, in the same unit of work, exactly as
110
+ * `RoleService` and `addresses`' `AddressService` — the two write surfaces
111
+ * `organizations` already reaches across this boundary — have always done. The
112
+ * host's own row is a different fact and stays where it is: "an operator edited
113
+ * this member on the organisation panel" is not "this account's e-mail
114
+ * changed", and the role endpoint has recorded both for as long as it has
115
+ * existed.
116
+ */
117
+ export declare class CustomerAccountMemberWriteService implements CustomerAccountMemberWritePort {
118
+ #private;
119
+ private readonly emFactory;
120
+ private readonly twoFactorEnrolments;
121
+ private readonly auditLog?;
122
+ constructor(emFactory: () => EntityManager, twoFactorEnrolments: TwoFactorEnrolmentReader, auditLog?: AuditPort | undefined);
123
+ create(input: CustomerAccountCreateInput): Promise<CustomerAccountRecord>;
124
+ updateProfile(customerAccountId: string, patch: CustomerAccountProfilePatch): Promise<CustomerAccountRecord>;
125
+ setSubtreeRollup(customerAccountId: string, enabled: boolean): Promise<CustomerAccountRecord>;
126
+ promoteToOrganizationAdmin(customerAccountId: string): Promise<CustomerAccountRecord>;
127
+ markEmailVerified(customerAccountId: string, verifiedAt: Date): Promise<CustomerAccountRecord>;
128
+ attachToOrganization(customerAccountId: string, organizationId: string): Promise<CustomerAccountRecord>;
129
+ }
130
+ /**
131
+ * `CustomerAuthService.login` resolves to a `LoginResult` carrying the
132
+ * `CustomerAccount` entity. The port hands back the record instead — the same
133
+ * substitution the read service makes, for the same reason.
134
+ *
135
+ * The service arrives as a **getter** rather than a value so the adapter reads
136
+ * it from the container per call. Constructing a second `CustomerAuthService`
137
+ * here is precisely the divergence this module's conversion removed: the two
138
+ * composition roots each built one, and their MFA argument differed.
139
+ */
140
+ export declare function createCustomerAuthPort(getService: () => CustomerAuthService, twoFactorEnrolments: TwoFactorEnrolmentReader): CustomerAuthPort;
141
+ /** Same substitution for the member-management surface `organizations` calls. */
142
+ export declare function createCustomerRolePort(getService: () => RoleService, twoFactorEnrolments: TwoFactorEnrolmentReader): CustomerRolePort;
143
+ //# sourceMappingURL=customer-account-ports.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"customer-account-ports.d.ts","sourceRoot":"","sources":["../../../src/backend/services/customer-account-ports.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,422 @@
1
+ import { UniqueConstraintViolationException } from '@mikro-orm/core';
2
+ import { ERROR_CODES, normalizeEmailAddress, } from '@endora-commerce/contracts';
3
+ import { recordAuditFromContext } from '@endora-commerce/platform/commands';
4
+ import { HttpError } from '@endora-commerce/platform/http';
5
+ import { hashPassword, verifyPassword } from '@endora-commerce/platform/kernel';
6
+ import { CustomerAccount } from '../entities/customer-account.entity.js';
7
+ /**
8
+ * The published face of `customer_accounts` (feature 075, Phase P).
9
+ *
10
+ * Nineteen modules read this module's table today, 51 of the sites on the
11
+ * entity class itself. What they ask for is narrow — an account by id, by ids,
12
+ * by e-mail, by organisation, and the two counting questions the "an
13
+ * organisation keeps at least one admin" rule is spelled with — so that is
14
+ * what the read port is, rather than the table.
15
+ *
16
+ * The mapping below is the point of the exercise and not ceremony:
17
+ * `passwordHash` does not survive it. It is read by exactly one module, and a
18
+ * record that carried it would turn every consumer into a place a credential
19
+ * can leak from.
20
+ */
21
+ /**
22
+ * The published record, with `twoFactorEnabled` taken from `mfa` rather than
23
+ * from this module's `two_factor_confirmed_at` column.
24
+ *
25
+ * The second parameter is required and has no default on purpose. That column's
26
+ * only non-null writer was the superseded TOTP path deleted on 2026-08-25, so
27
+ * the field was a **provably constant `false`** — on the admin customer detail,
28
+ * on both organisation member panels, and on `GET /api/v1/me/customer`, which
29
+ * is the buyer's own account page telling them their account is unprotected. A
30
+ * default would let the next call site reintroduce that silently; making the
31
+ * caller answer means the compiler asks.
32
+ */
33
+ export function toCustomerAccountRecord(account, twoFactorEnabled) {
34
+ return {
35
+ id: account.id,
36
+ organizationId: account.organizationId ?? null,
37
+ email: account.email,
38
+ firstName: account.firstName,
39
+ lastName: account.lastName,
40
+ role: account.role,
41
+ emailVerifiedAt: account.emailVerifiedAt ?? null,
42
+ twoFactorEnabled,
43
+ lastLoginAt: account.lastLoginAt ?? null,
44
+ createdAt: account.createdAt,
45
+ updatedAt: account.updatedAt,
46
+ customFieldValues: account.customFieldValues ?? {},
47
+ deletedAt: account.deletedAt ?? null,
48
+ customerGroupId: account.customerGroupId ?? null,
49
+ subtreeRollupEnabled: account.subtreeRollupEnabled,
50
+ blockedAt: account.blockedAt ?? null,
51
+ blockReason: account.blockReason ?? null,
52
+ blockSource: account.blockSource ?? null,
53
+ blockedByAdminUserId: account.blockedByAdminUserId ?? null,
54
+ blockedByCustomerAccountId: account.blockedByCustomerAccountId ?? null,
55
+ deletionRequestedByAdminUserId: account.deletionRequestedByAdminUserId ?? null,
56
+ anonymizedAt: account.anonymizedAt ?? null,
57
+ };
58
+ }
59
+ /**
60
+ * One `mfa` read for a whole page of accounts, rather than one per row.
61
+ *
62
+ * Every caller is a list or a single row, so batching here keeps the admin
63
+ * customer list at two queries instead of one per customer, and leaves the
64
+ * single-row callers spelling the same thing.
65
+ */
66
+ export async function toCustomerAccountRecords(twoFactorEnrolments, accounts) {
67
+ if (accounts.length === 0)
68
+ return [];
69
+ const enrolled = await twoFactorEnrolments(accounts.map((account) => account.id));
70
+ return accounts.map((account) => toCustomerAccountRecord(account, enrolled.has(account.id)));
71
+ }
72
+ /** The single-row spelling of {@link toCustomerAccountRecords}. */
73
+ export async function toOneCustomerAccountRecord(twoFactorEnrolments, account) {
74
+ return (await toCustomerAccountRecords(twoFactorEnrolments, [account]))[0];
75
+ }
76
+ /** `{ deletedAt: null }` when the caller asked for it, nothing otherwise. */
77
+ function activeFilter(options) {
78
+ return options?.activeOnly ? { deletedAt: null } : {};
79
+ }
80
+ export class CustomerAccountReadService {
81
+ emFactory;
82
+ twoFactorEnrolments;
83
+ constructor(emFactory, twoFactorEnrolments) {
84
+ this.emFactory = emFactory;
85
+ this.twoFactorEnrolments = twoFactorEnrolments;
86
+ }
87
+ async findById(id, options) {
88
+ const account = await this.emFactory().findOne(CustomerAccount, {
89
+ id,
90
+ ...activeFilter(options),
91
+ });
92
+ return account ? toOneCustomerAccountRecord(this.twoFactorEnrolments, account) : null;
93
+ }
94
+ async findByIds(ids, options) {
95
+ if (ids.length === 0)
96
+ return [];
97
+ const accounts = await this.emFactory().find(CustomerAccount, {
98
+ id: { $in: [...ids] },
99
+ ...activeFilter(options),
100
+ });
101
+ return toCustomerAccountRecords(this.twoFactorEnrolments, accounts);
102
+ }
103
+ async findByEmail(email, options) {
104
+ // The fold is here rather than at the four callers: this is the one
105
+ // question "is this address taken" is asked through, and a caller that
106
+ // folded its own way (or forgot to) is what made a registered account
107
+ // unfindable by the address its holder had typed.
108
+ const account = await this.emFactory().findOne(CustomerAccount, {
109
+ email: normalizeEmailAddress(email),
110
+ ...activeFilter(options),
111
+ });
112
+ return account ? toOneCustomerAccountRecord(this.twoFactorEnrolments, account) : null;
113
+ }
114
+ async findInOrganization(id, organizationId, options) {
115
+ const account = await this.emFactory().findOne(CustomerAccount, {
116
+ id,
117
+ organizationId,
118
+ ...activeFilter(options),
119
+ });
120
+ return account ? toOneCustomerAccountRecord(this.twoFactorEnrolments, account) : null;
121
+ }
122
+ async listByOrganization(organizationId, options) {
123
+ const accounts = await this.emFactory().find(CustomerAccount, { organizationId, ...activeFilter(options) }, { orderBy: { role: 'asc', createdAt: 'asc' } });
124
+ return toCustomerAccountRecords(this.twoFactorEnrolments, accounts);
125
+ }
126
+ async countByOrganizationRole(organizationId, role, options) {
127
+ return this.emFactory().count(CustomerAccount, {
128
+ organizationId,
129
+ role,
130
+ ...(options?.activeOnly ? { deletedAt: null } : {}),
131
+ ...(options?.notBlocked ? { blockedAt: null } : {}),
132
+ ...(options?.excludeCustomerAccountId
133
+ ? { id: { $ne: options.excludeCustomerAccountId } }
134
+ : {}),
135
+ });
136
+ }
137
+ async searchByEmail(query, limit) {
138
+ const trimmed = query.trim();
139
+ const accounts = await this.emFactory().find(CustomerAccount, trimmed ? { email: { $ilike: `%${trimmed}%` } } : {}, { orderBy: { email: 'asc' }, limit });
140
+ return toCustomerAccountRecords(this.twoFactorEnrolments, accounts);
141
+ }
142
+ async searchIdsByName(query) {
143
+ const trimmed = query.trim();
144
+ if (!trimmed)
145
+ return [];
146
+ const like = `%${trimmed}%`;
147
+ const accounts = await this.emFactory().find(CustomerAccount, {
148
+ $or: [
149
+ { email: { $ilike: like } },
150
+ { firstName: { $ilike: like } },
151
+ { lastName: { $ilike: like } },
152
+ ],
153
+ }, { fields: ['id'] });
154
+ return accounts.map((account) => account.id);
155
+ }
156
+ async listAll() {
157
+ const accounts = await this.emFactory().find(CustomerAccount, {}, { orderBy: { email: 'asc' } });
158
+ return toCustomerAccountRecords(this.twoFactorEnrolments, accounts);
159
+ }
160
+ }
161
+ /**
162
+ * Issue #222 — the one credential fact that crosses this boundary, and it
163
+ * crosses as a date rather than as anything derived from the hash.
164
+ *
165
+ * Separate from {@link CustomerAccountReadService} on purpose: that read port
166
+ * is what nineteen modules resolve, and the mapping above deliberately drops
167
+ * `passwordHash`. "Does this account have a password its holder can use" is a
168
+ * question one module asks, so it gets a port one module resolves.
169
+ *
170
+ * An unknown id answers `null`, not a throw: the caller's question is about a
171
+ * credential, and "no account" and "no password on record" want the same
172
+ * refusal from every caller there could be.
173
+ */
174
+ export class CustomerPasswordStateService {
175
+ emFactory;
176
+ constructor(emFactory) {
177
+ this.emFactory = emFactory;
178
+ }
179
+ async passwordSetAt(customerAccountId) {
180
+ // A full load rather than a projection: a partially-loaded entity in the
181
+ // identity map is a hazard for whatever flushes next in the same unit of
182
+ // work, and only the date leaves this method either way.
183
+ const account = await this.emFactory().findOne(CustomerAccount, { id: customerAccountId });
184
+ return account?.passwordSetAt ?? null;
185
+ }
186
+ }
187
+ /**
188
+ * Step-up re-verification, for `mfa` (feature 080, T052).
189
+ *
190
+ * The read is this module's own row and the comparison is the platform
191
+ * hasher's, so both halves are on this side of the seam. The composition roots
192
+ * held them instead: each read `passwordHash` off the entity and called
193
+ * `verifyPassword` itself, which is a credential column and a hash comparison
194
+ * living in a file that owns neither.
195
+ *
196
+ * The hash never leaves this class — that is the difference between this port
197
+ * and a `passwordHash` field on `CustomerAccountRecord`, and it is the whole
198
+ * reason the port exists.
199
+ */
200
+ export class CustomerPasswordVerificationService {
201
+ emFactory;
202
+ constructor(emFactory) {
203
+ this.emFactory = emFactory;
204
+ }
205
+ async verifyPassword(customerAccountId, password) {
206
+ const account = await this.emFactory().findOne(CustomerAccount, { id: customerAccountId });
207
+ // The bare call is the imported hasher: a class method is not in scope as
208
+ // an identifier, so it shadows nothing.
209
+ return account ? verifyPassword(account.passwordHash, password) : false;
210
+ }
211
+ }
212
+ /**
213
+ * The member lifecycle `organizations` runs over this module's table
214
+ * (feature 075, Phase C).
215
+ *
216
+ * It ran it by creating and mutating the `CustomerAccount` entity in its own
217
+ * services and route handlers — seven write sites across five files. They are
218
+ * six methods here, each one unit of work on this table alone, because a write
219
+ * port that took the caller's `EntityManager` would publish the coupling
220
+ * instead of removing it (D-78).
221
+ *
222
+ * **Each write audits itself**, in the same unit of work, exactly as
223
+ * `RoleService` and `addresses`' `AddressService` — the two write surfaces
224
+ * `organizations` already reaches across this boundary — have always done. The
225
+ * host's own row is a different fact and stays where it is: "an operator edited
226
+ * this member on the organisation panel" is not "this account's e-mail
227
+ * changed", and the role endpoint has recorded both for as long as it has
228
+ * existed.
229
+ */
230
+ export class CustomerAccountMemberWriteService {
231
+ emFactory;
232
+ twoFactorEnrolments;
233
+ auditLog;
234
+ constructor(emFactory, twoFactorEnrolments, auditLog) {
235
+ this.emFactory = emFactory;
236
+ this.twoFactorEnrolments = twoFactorEnrolments;
237
+ this.auditLog = auditLog;
238
+ }
239
+ async create(input) {
240
+ const em = this.emFactory();
241
+ const email = normalizeEmailAddress(input.email);
242
+ const existing = await em.findOne(CustomerAccount, { email });
243
+ if (existing) {
244
+ throw new HttpError(409, ERROR_CODES.EMAIL_ALREADY_REGISTERED, 'An account with this email already exists.');
245
+ }
246
+ const account = em.create(CustomerAccount, {
247
+ organizationId: input.organizationId,
248
+ email,
249
+ passwordHash: await hashPassword(input.password),
250
+ // Issue #222 — a password somebody supplied, so the account has one on
251
+ // record from its first moment. This is the write both organisation
252
+ // registration (the holder typed it) and the member admin surface (an
253
+ // operator set it and hands it over) go through.
254
+ passwordSetAt: new Date(),
255
+ firstName: input.firstName,
256
+ lastName: input.lastName,
257
+ role: input.role,
258
+ ...(input.emailVerified ? { emailVerifiedAt: new Date() } : {}),
259
+ });
260
+ this.#audit(em, 'customer_account.create', account.id, null, {
261
+ organizationId: account.organizationId ?? null,
262
+ email: account.email,
263
+ role: account.role,
264
+ });
265
+ try {
266
+ await em.persistAndFlush(account);
267
+ }
268
+ catch (err) {
269
+ // The unique index is the real arbiter; the pre-check above only buys a
270
+ // better message for the common case. Two registrations racing on one
271
+ // address must both answer 409 rather than one answering 500.
272
+ if (err instanceof UniqueConstraintViolationException) {
273
+ throw new HttpError(409, ERROR_CODES.EMAIL_ALREADY_REGISTERED, 'An account with this email already exists.');
274
+ }
275
+ throw err;
276
+ }
277
+ return toOneCustomerAccountRecord(this.twoFactorEnrolments, account);
278
+ }
279
+ async updateProfile(customerAccountId, patch) {
280
+ const em = this.emFactory();
281
+ const account = await this.#loadLive(em, customerAccountId);
282
+ const before = {
283
+ email: account.email,
284
+ firstName: account.firstName,
285
+ lastName: account.lastName,
286
+ };
287
+ if (patch.email !== undefined) {
288
+ const nextEmail = normalizeEmailAddress(patch.email);
289
+ if (nextEmail !== account.email) {
290
+ const taken = await em.findOne(CustomerAccount, { email: nextEmail, deletedAt: null });
291
+ if (taken && taken.id !== account.id) {
292
+ throw new HttpError(409, ERROR_CODES.EMAIL_ALREADY_REGISTERED, 'That email is already used by another account.');
293
+ }
294
+ account.email = nextEmail;
295
+ account.emailVerifiedAt = null;
296
+ }
297
+ }
298
+ if (patch.firstName !== undefined)
299
+ account.firstName = patch.firstName;
300
+ if (patch.lastName !== undefined)
301
+ account.lastName = patch.lastName;
302
+ this.#audit(em, 'customer_account.update_profile', account.id, before, {
303
+ email: account.email,
304
+ firstName: account.firstName,
305
+ lastName: account.lastName,
306
+ });
307
+ await em.flush();
308
+ return toOneCustomerAccountRecord(this.twoFactorEnrolments, account);
309
+ }
310
+ async setSubtreeRollup(customerAccountId, enabled) {
311
+ const em = this.emFactory();
312
+ const account = await this.#loadLive(em, customerAccountId);
313
+ const before = { subtreeRollupEnabled: account.subtreeRollupEnabled };
314
+ account.subtreeRollupEnabled = enabled;
315
+ this.#audit(em, 'customer_account.set_subtree_rollup', account.id, before, {
316
+ subtreeRollupEnabled: enabled,
317
+ });
318
+ await em.flush();
319
+ return toOneCustomerAccountRecord(this.twoFactorEnrolments, account);
320
+ }
321
+ async promoteToOrganizationAdmin(customerAccountId) {
322
+ const em = this.emFactory();
323
+ const account = await this.#loadLive(em, customerAccountId);
324
+ const before = { role: account.role };
325
+ account.role = 'organization_admin';
326
+ this.#audit(em, 'customer_account.promote_organization_admin', account.id, before, {
327
+ role: account.role,
328
+ });
329
+ await em.flush();
330
+ return toOneCustomerAccountRecord(this.twoFactorEnrolments, account);
331
+ }
332
+ async markEmailVerified(customerAccountId, verifiedAt) {
333
+ const em = this.emFactory();
334
+ const account = await em.findOne(CustomerAccount, { id: customerAccountId });
335
+ if (!account) {
336
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'Customer account not found.');
337
+ }
338
+ // Idempotent: a retried verification keeps the first timestamp, and records
339
+ // nothing the second time — an audit row per retry would say a change
340
+ // happened that did not.
341
+ if (!account.emailVerifiedAt) {
342
+ account.emailVerifiedAt = verifiedAt;
343
+ this.#audit(em, 'customer_account.email_verified', account.id, { emailVerifiedAt: null }, {
344
+ emailVerifiedAt: verifiedAt.toISOString(),
345
+ });
346
+ await em.flush();
347
+ }
348
+ return toOneCustomerAccountRecord(this.twoFactorEnrolments, account);
349
+ }
350
+ async attachToOrganization(customerAccountId, organizationId) {
351
+ const em = this.emFactory();
352
+ const account = await em.findOne(CustomerAccount, { id: customerAccountId });
353
+ if (!account) {
354
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'Customer account not found.');
355
+ }
356
+ const before = { organizationId: account.organizationId ?? null };
357
+ account.organizationId = organizationId;
358
+ this.#audit(em, 'customer_account.attach_organization', account.id, before, {
359
+ organizationId,
360
+ });
361
+ await em.flush();
362
+ return toOneCustomerAccountRecord(this.twoFactorEnrolments, account);
363
+ }
364
+ #audit(em, action, objectId, stateBefore, stateAfter) {
365
+ if (!this.auditLog)
366
+ return;
367
+ recordAuditFromContext(this.auditLog, em, {
368
+ action,
369
+ objectType: 'customer_account',
370
+ objectId,
371
+ stateBefore,
372
+ stateAfter,
373
+ });
374
+ }
375
+ async #loadLive(em, id) {
376
+ const account = await em.findOne(CustomerAccount, { id, deletedAt: null });
377
+ if (!account) {
378
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'Customer account not found.');
379
+ }
380
+ return account;
381
+ }
382
+ }
383
+ /**
384
+ * `CustomerAuthService.login` resolves to a `LoginResult` carrying the
385
+ * `CustomerAccount` entity. The port hands back the record instead — the same
386
+ * substitution the read service makes, for the same reason.
387
+ *
388
+ * The service arrives as a **getter** rather than a value so the adapter reads
389
+ * it from the container per call. Constructing a second `CustomerAuthService`
390
+ * here is precisely the divergence this module's conversion removed: the two
391
+ * composition roots each built one, and their MFA argument differed.
392
+ */
393
+ export function createCustomerAuthPort(getService, twoFactorEnrolments) {
394
+ return {
395
+ async login(input) {
396
+ const outcome = await getService().login(input);
397
+ if (outcome.status !== 'authenticated')
398
+ return outcome;
399
+ return {
400
+ status: 'authenticated',
401
+ customerAccount: await toOneCustomerAccountRecord(twoFactorEnrolments, outcome.customerAccount),
402
+ sessionCookieValue: outcome.sessionCookieValue,
403
+ sessionExpiresAt: outcome.sessionExpiresAt,
404
+ };
405
+ },
406
+ changePassword: (customerAccountId, currentPassword, newPassword) => getService().changePassword(customerAccountId, currentPassword, newPassword),
407
+ logout: (sessionId) => getService().logout(sessionId),
408
+ };
409
+ }
410
+ /** Same substitution for the member-management surface `organizations` calls. */
411
+ export function createCustomerRolePort(getService, twoFactorEnrolments) {
412
+ return {
413
+ async listMembers(organizationId) {
414
+ return toCustomerAccountRecords(twoFactorEnrolments, await getService().listMembers(organizationId));
415
+ },
416
+ async changeRole(organizationId, targetCustomerAccountId, newRole) {
417
+ return toOneCustomerAccountRecord(twoFactorEnrolments, await getService().changeRole(organizationId, targetCustomerAccountId, newRole));
418
+ },
419
+ removeMember: (organizationId, targetCustomerAccountId) => getService().removeMember(organizationId, targetCustomerAccountId),
420
+ };
421
+ }
422
+ //# sourceMappingURL=customer-account-ports.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"customer-account-ports.js","sourceRoot":"","sources":["../../../src/backend/services/customer-account-ports.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import { type AuthSessionPort, type MfaLoginPort } from '@endora-commerce/contracts';
3
+ import { CustomerAccount } from '../entities/customer-account.entity.js';
4
+ import type { AuditPort } from '@endora-commerce/platform/kernel';
5
+ /**
6
+ * Customer-side auth flows (T119; two-step login added in feature 042).
7
+ *
8
+ * - login: email + password → first factor. When an `MfaLoginPort` is injected
9
+ * and the account has active 2FA (or 2FA is enforced for the scope), login
10
+ * returns an `mfaRequired` / `mfaSetupRequired` outcome WITHOUT a session;
11
+ * the second step is completed by the MFA module's verify endpoint. When no
12
+ * port is present (or it returns `proceed`), login issues the session exactly
13
+ * as before (password-only fallback, FR-033).
14
+ * - logout: destroy session.
15
+ * - changePassword: argon2 verify of `currentPassword`; reject with
16
+ * 401 CURRENT_PASSWORD_INVALID otherwise; rehash + persist.
17
+ *
18
+ * Feature 075, Phase C — the three things this service needed from `auth` are
19
+ * now named where they belong rather than in `auth`'s directory. Sessions come
20
+ * over {@link AuthSessionPort}, so a session is minted or destroyed through the
21
+ * surface `auth` publishes and never through its `Session` entity. The MFA seam
22
+ * is `auth`'s *shape* implemented by `mfa`, published in `@endora-commerce/contracts` so
23
+ * this service depends on neither module for it. And the password hash is a
24
+ * pure function that moved to `src/kernel/crypto/`: an operator switching a
25
+ * module off must not make "hash this string" answer 503.
26
+ */
27
+ export interface LoginResult {
28
+ customerAccount: CustomerAccount;
29
+ /** Value to put into the Set-Cookie header. */
30
+ sessionCookieValue: string;
31
+ sessionExpiresAt: Date;
32
+ }
33
+ /** Discriminated outcome of the first login step (feature 042). */
34
+ export type CustomerLoginOutcome = ({
35
+ status: 'authenticated';
36
+ } & LoginResult) | {
37
+ status: 'mfaRequired';
38
+ challengeId: string;
39
+ } | {
40
+ status: 'mfaSetupRequired';
41
+ setupTicket: string;
42
+ };
43
+ export declare class CustomerAuthService {
44
+ private readonly emFactory;
45
+ private readonly sessionService;
46
+ /** Lazily resolved so composition can late-bind the MFA module. */
47
+ private readonly getMfaLoginPort?;
48
+ private readonly auditLog?;
49
+ constructor(emFactory: () => EntityManager, sessionService: AuthSessionPort,
50
+ /** Lazily resolved so composition can late-bind the MFA module. */
51
+ getMfaLoginPort?: (() => MfaLoginPort | undefined) | undefined, auditLog?: AuditPort | undefined);
52
+ login(input: {
53
+ email: string;
54
+ password: string;
55
+ ip?: string;
56
+ userAgent?: string;
57
+ salesChannelId?: string | null;
58
+ }): Promise<CustomerLoginOutcome>;
59
+ changePassword(customerAccountId: string, currentPassword: string, newPassword: string): Promise<void>;
60
+ logout(sessionId: string): Promise<void>;
61
+ }
62
+ //# sourceMappingURL=customer-auth-service.d.ts.map