@endora-commerce/mod-mfa 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 (126) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +58 -0
  3. package/dist/admin/index.d.ts +37 -0
  4. package/dist/admin/index.d.ts.map +1 -0
  5. package/dist/admin/index.js +12 -0
  6. package/dist/admin/index.js.map +1 -0
  7. package/dist/admin/pages/AdminSecuritySettings.d.ts +3 -0
  8. package/dist/admin/pages/AdminSecuritySettings.d.ts.map +1 -0
  9. package/dist/admin/pages/AdminSecuritySettings.js +71 -0
  10. package/dist/admin/pages/AdminSecuritySettings.js.map +1 -0
  11. package/dist/backend/entities/mfa-enrolment.entity.d.ts +33 -0
  12. package/dist/backend/entities/mfa-enrolment.entity.d.ts.map +1 -0
  13. package/dist/backend/entities/mfa-enrolment.entity.js +95 -0
  14. package/dist/backend/entities/mfa-enrolment.entity.js.map +1 -0
  15. package/dist/backend/entities/mfa-organization-policy.entity.d.ts +19 -0
  16. package/dist/backend/entities/mfa-organization-policy.entity.d.ts.map +1 -0
  17. package/dist/backend/entities/mfa-organization-policy.entity.js +60 -0
  18. package/dist/backend/entities/mfa-organization-policy.entity.js.map +1 -0
  19. package/dist/backend/entities/mfa-recovery-code.entity.d.ts +17 -0
  20. package/dist/backend/entities/mfa-recovery-code.entity.d.ts.map +1 -0
  21. package/dist/backend/entities/mfa-recovery-code.entity.js +54 -0
  22. package/dist/backend/entities/mfa-recovery-code.entity.js.map +1 -0
  23. package/dist/backend/entities/mfa-social-identity.entity.d.ts +24 -0
  24. package/dist/backend/entities/mfa-social-identity.entity.d.ts.map +1 -0
  25. package/dist/backend/entities/mfa-social-identity.entity.js +73 -0
  26. package/dist/backend/entities/mfa-social-identity.entity.js.map +1 -0
  27. package/dist/backend/index.d.ts +131 -0
  28. package/dist/backend/index.d.ts.map +1 -0
  29. package/dist/backend/index.js +173 -0
  30. package/dist/backend/index.js.map +1 -0
  31. package/dist/backend/plugin.d.ts +99 -0
  32. package/dist/backend/plugin.d.ts.map +1 -0
  33. package/dist/backend/plugin.js +111 -0
  34. package/dist/backend/plugin.js.map +1 -0
  35. package/dist/backend/routes.admin.d.ts +23 -0
  36. package/dist/backend/routes.admin.d.ts.map +1 -0
  37. package/dist/backend/routes.admin.js +93 -0
  38. package/dist/backend/routes.admin.js.map +1 -0
  39. package/dist/backend/routes.oauth.d.ts +26 -0
  40. package/dist/backend/routes.oauth.d.ts.map +1 -0
  41. package/dist/backend/routes.oauth.js +155 -0
  42. package/dist/backend/routes.oauth.js.map +1 -0
  43. package/dist/backend/routes.org.d.ts +21 -0
  44. package/dist/backend/routes.org.d.ts.map +1 -0
  45. package/dist/backend/routes.org.js +17 -0
  46. package/dist/backend/routes.org.js.map +1 -0
  47. package/dist/backend/routes.public.d.ts +21 -0
  48. package/dist/backend/routes.public.d.ts.map +1 -0
  49. package/dist/backend/routes.public.js +118 -0
  50. package/dist/backend/routes.public.js.map +1 -0
  51. package/dist/backend/routes.self-service.d.ts +34 -0
  52. package/dist/backend/routes.self-service.d.ts.map +1 -0
  53. package/dist/backend/routes.self-service.js +113 -0
  54. package/dist/backend/routes.self-service.js.map +1 -0
  55. package/dist/backend/services/account-identity.d.ts +71 -0
  56. package/dist/backend/services/account-identity.d.ts.map +1 -0
  57. package/dist/backend/services/account-identity.js +87 -0
  58. package/dist/backend/services/account-identity.js.map +1 -0
  59. package/dist/backend/services/challenge-store.d.ts +40 -0
  60. package/dist/backend/services/challenge-store.d.ts.map +1 -0
  61. package/dist/backend/services/challenge-store.js +90 -0
  62. package/dist/backend/services/challenge-store.js.map +1 -0
  63. package/dist/backend/services/mfa-enrolment-count.service.d.ts +26 -0
  64. package/dist/backend/services/mfa-enrolment-count.service.d.ts.map +1 -0
  65. package/dist/backend/services/mfa-enrolment-count.service.js +34 -0
  66. package/dist/backend/services/mfa-enrolment-count.service.js.map +1 -0
  67. package/dist/backend/services/mfa-enrolment-service.d.ts +54 -0
  68. package/dist/backend/services/mfa-enrolment-service.d.ts.map +1 -0
  69. package/dist/backend/services/mfa-enrolment-service.js +217 -0
  70. package/dist/backend/services/mfa-enrolment-service.js.map +1 -0
  71. package/dist/backend/services/mfa-enrolment-state.service.d.ts +27 -0
  72. package/dist/backend/services/mfa-enrolment-state.service.d.ts.map +1 -0
  73. package/dist/backend/services/mfa-enrolment-state.service.js +33 -0
  74. package/dist/backend/services/mfa-enrolment-state.service.js.map +1 -0
  75. package/dist/backend/services/mfa-login-service.d.ts +56 -0
  76. package/dist/backend/services/mfa-login-service.d.ts.map +1 -0
  77. package/dist/backend/services/mfa-login-service.js +89 -0
  78. package/dist/backend/services/mfa-login-service.js.map +1 -0
  79. package/dist/backend/services/mfa-org-policy-service.d.ts +16 -0
  80. package/dist/backend/services/mfa-org-policy-service.d.ts.map +1 -0
  81. package/dist/backend/services/mfa-org-policy-service.js +43 -0
  82. package/dist/backend/services/mfa-org-policy-service.js.map +1 -0
  83. package/dist/backend/services/mfa-policy-resolver.d.ts +47 -0
  84. package/dist/backend/services/mfa-policy-resolver.d.ts.map +1 -0
  85. package/dist/backend/services/mfa-policy-resolver.js +82 -0
  86. package/dist/backend/services/mfa-policy-resolver.js.map +1 -0
  87. package/dist/backend/services/oauth-provider-service.d.ts +65 -0
  88. package/dist/backend/services/oauth-provider-service.d.ts.map +1 -0
  89. package/dist/backend/services/oauth-provider-service.js +80 -0
  90. package/dist/backend/services/oauth-provider-service.js.map +1 -0
  91. package/dist/backend/services/recovery-codes.d.ts +10 -0
  92. package/dist/backend/services/recovery-codes.d.ts.map +1 -0
  93. package/dist/backend/services/recovery-codes.js +18 -0
  94. package/dist/backend/services/recovery-codes.js.map +1 -0
  95. package/dist/backend/services/secret-cipher.d.ts +16 -0
  96. package/dist/backend/services/secret-cipher.d.ts.map +1 -0
  97. package/dist/backend/services/secret-cipher.js +52 -0
  98. package/dist/backend/services/secret-cipher.js.map +1 -0
  99. package/dist/backend/services/social-identity-service.d.ts +40 -0
  100. package/dist/backend/services/social-identity-service.d.ts.map +1 -0
  101. package/dist/backend/services/social-identity-service.js +69 -0
  102. package/dist/backend/services/social-identity-service.js.map +1 -0
  103. package/dist/backend/services/social-link-service.d.ts +63 -0
  104. package/dist/backend/services/social-link-service.d.ts.map +1 -0
  105. package/dist/backend/services/social-link-service.js +121 -0
  106. package/dist/backend/services/social-link-service.js.map +1 -0
  107. package/dist/backend/services/totp.d.ts +11 -0
  108. package/dist/backend/services/totp.d.ts.map +1 -0
  109. package/dist/backend/services/totp.js +46 -0
  110. package/dist/backend/services/totp.js.map +1 -0
  111. package/dist/manifest.d.ts +184 -0
  112. package/dist/manifest.d.ts.map +1 -0
  113. package/dist/manifest.js +264 -0
  114. package/dist/manifest.js.map +1 -0
  115. package/dist/migrations/20260611T140409_mfa_init.d.ts +22 -0
  116. package/dist/migrations/20260611T140409_mfa_init.d.ts.map +1 -0
  117. package/dist/migrations/20260611T140409_mfa_init.js +94 -0
  118. package/dist/migrations/20260611T140409_mfa_init.js.map +1 -0
  119. package/dist/migrations/index.d.ts +27 -0
  120. package/dist/migrations/index.d.ts.map +1 -0
  121. package/dist/migrations/index.js +29 -0
  122. package/dist/migrations/index.js.map +1 -0
  123. package/i18n/en.json +14 -0
  124. package/i18n/pl.json +14 -0
  125. package/package.json +102 -0
  126. package/tailwind.css +14 -0
@@ -0,0 +1,73 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ 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;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import { Entity, Index, OptionalProps, PrimaryKey, Property } from '@mikro-orm/core';
11
+ import { GlobalEntity } from '@endora-commerce/platform/tenancy';
12
+ import { randomUUID } from 'crypto';
13
+ /**
14
+ * Link between a platform account and an external provider identity
15
+ * (Google / Microsoft) — feature 042. Resolution is by the provider's
16
+ * verified primary email; the `providerSubject` (`sub` claim) is the stable
17
+ * provider-side id. Uniqueness constraints (declared in migration 067):
18
+ * - (provider, provider_subject): one platform account per IdP identity;
19
+ * - (subject_type, subject_id, provider): one link per provider per account.
20
+ */
21
+ let MfaSocialIdentity = class MfaSocialIdentity {
22
+ [OptionalProps];
23
+ id = randomUUID();
24
+ subjectType;
25
+ subjectId;
26
+ /** `'google'` | `'microsoft'`. */
27
+ provider;
28
+ /** The IdP `sub` claim — stable provider-side identifier. */
29
+ providerSubject;
30
+ /** Verified email at link time (used for resolution). */
31
+ email;
32
+ linkedAt = new Date();
33
+ lastUsedAt;
34
+ };
35
+ __decorate([
36
+ PrimaryKey({ type: 'uuid' }),
37
+ __metadata("design:type", String)
38
+ ], MfaSocialIdentity.prototype, "id", void 0);
39
+ __decorate([
40
+ Property({ type: 'string', length: 16 }),
41
+ __metadata("design:type", String)
42
+ ], MfaSocialIdentity.prototype, "subjectType", void 0);
43
+ __decorate([
44
+ Property({ type: 'uuid' }),
45
+ __metadata("design:type", String)
46
+ ], MfaSocialIdentity.prototype, "subjectId", void 0);
47
+ __decorate([
48
+ Property({ type: 'string', length: 16 }),
49
+ __metadata("design:type", String)
50
+ ], MfaSocialIdentity.prototype, "provider", void 0);
51
+ __decorate([
52
+ Property({ type: 'string', length: 255 }),
53
+ __metadata("design:type", String)
54
+ ], MfaSocialIdentity.prototype, "providerSubject", void 0);
55
+ __decorate([
56
+ Property({ type: 'string', length: 320 }),
57
+ Index(),
58
+ __metadata("design:type", String)
59
+ ], MfaSocialIdentity.prototype, "email", void 0);
60
+ __decorate([
61
+ Property({ type: 'datetime', onCreate: () => new Date() }),
62
+ __metadata("design:type", Date)
63
+ ], MfaSocialIdentity.prototype, "linkedAt", void 0);
64
+ __decorate([
65
+ Property({ type: 'datetime', nullable: true }),
66
+ __metadata("design:type", Object)
67
+ ], MfaSocialIdentity.prototype, "lastUsedAt", void 0);
68
+ MfaSocialIdentity = __decorate([
69
+ GlobalEntity(),
70
+ Entity({ tableName: 'mfa_social_identities' })
71
+ ], MfaSocialIdentity);
72
+ export { MfaSocialIdentity };
73
+ //# sourceMappingURL=mfa-social-identity.entity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mfa-social-identity.entity.js","sourceRoot":"","sources":["../../../src/backend/entities/mfa-social-identity.entity.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AACrF,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAEpC;;;;;;;GAOG;AAGI,IAAM,iBAAiB,GAAvB,MAAM,iBAAiB;IAC5B,CAAC,aAAa,CAAC,CAAoC;IAGnD,EAAE,GAAW,UAAU,EAAE,CAAC;IAG1B,WAAW,CAAU;IAGrB,SAAS,CAAU;IAEnB,kCAAkC;IAElC,QAAQ,CAAU;IAElB,6DAA6D;IAE7D,eAAe,CAAU;IAEzB,yDAAyD;IAGzD,KAAK,CAAU;IAGf,QAAQ,GAAS,IAAI,IAAI,EAAE,CAAC;IAG5B,UAAU,CAAe;CAC1B,CAAA;AA1BC;IADC,UAAU,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;6CACH;AAG1B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;;sDACpB;AAGrB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;oDACR;AAInB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;;mDACvB;AAIlB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;;0DACjB;AAKzB;IAFC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;IACzC,KAAK,EAAE;;gDACO;AAGf;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;8BACjD,IAAI;mDAAc;AAG5B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;qDACtB;AA7Bd,iBAAiB;IAF7B,YAAY,EAAE;IACd,MAAM,CAAC,EAAE,SAAS,EAAE,uBAAuB,EAAE,CAAC;GAClC,iBAAiB,CA8B7B"}
@@ -0,0 +1,131 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { FastifyRequest } from 'fastify';
3
+ import type { Redis } from 'ioredis';
4
+ import type { AuthSessionPort, MfaEnrolmentCountPort, MfaEnrolmentStatePort, MfaLoginPort } from '@endora-commerce/contracts';
5
+ import type { AuditPort } from '@endora-commerce/platform/kernel';
6
+ import type { CommandBus } from '@endora-commerce/platform/commands';
7
+ import type { ModuleContext } from '@endora-commerce/platform/kernel';
8
+ import type { RequireAdminFactory } from '@endora-commerce/platform/kernel';
9
+ import type { SettingsReader } from './services/mfa-policy-resolver.js';
10
+ import { type OAuthProviderPort } from './services/oauth-provider-service.js';
11
+ import type { SocialIdentityDeps } from './services/social-identity-service.js';
12
+ import { type MfaModuleHandle } from './plugin.js';
13
+ import { MfaEnrolment } from './entities/mfa-enrolment.entity.js';
14
+ import { MfaOrganizationPolicy } from './entities/mfa-organization-policy.entity.js';
15
+ import { MfaRecoveryCode } from './entities/mfa-recovery-code.entity.js';
16
+ import { MfaSocialIdentity } from './entities/mfa-social-identity.entity.js';
17
+ /**
18
+ * `mfa` — the widest option surface in the wave, and why it is three names
19
+ * rather than fifteen (feature 072, wave 1, T096;
20
+ * `specs/110-instance-repository/` T118c).
21
+ *
22
+ * `MfaModuleOptions` had twenty-four fields. Wave 1 grouped six of them into a
23
+ * single `MfaActorBridge` contribution on the reasoning that they were the
24
+ * *shape a composition gives an actor* and that the two roots genuinely
25
+ * disagreed about it — production reading `request.actor`, the harness
26
+ * `request.testActor`. **T118c re-derived the six and the premise held for none
27
+ * of them.**
28
+ *
29
+ * - `resolveCustomerActor` was a duplicate spelling of `customerActorResolver`,
30
+ * byte-identical to the platform's contribution in production and to the
31
+ * harness's in the harness. One question, two answers, agreeing by hand.
32
+ * - `resolveAdminActor` was `adminContextResolver` plus a `promoteAdminActor`
33
+ * call that every one of its call sites had already made: each sits behind
34
+ * `requireAdmin`, whose `auth` implementation promotes and then refuses a
35
+ * non-admin, so the bridge re-asked a question its own guard had answered.
36
+ * - `resolveOrgAdmin` was the actor resolver plus one
37
+ * `customerAccountReadPort.findById` and a role check.
38
+ * - `resolveOrganizationCustomerIds`, `resolveAccountEmail` and
39
+ * `verifyAccountPassword` were four identity ports and nothing else.
40
+ *
41
+ * So the module resolves them itself now: two contributed actor names it reads
42
+ * off the cradle like the eight other modules that read them, and four
43
+ * `lazyPort` reads over `admin_users`' and `customer_accounts`' published
44
+ * contracts — both already in this module's manifest `dependencies`, both
45
+ * `nonDeactivatable`, so no operator loses a control to the edge.
46
+ *
47
+ * **Two of the six were asserted by nothing**, which is what a bridge with an
48
+ * optional half buys: the harness omitted `resolveAccountEmail` and
49
+ * `verifyAccountPassword` and production supplied them, so under test an
50
+ * authenticator entry was labelled with a UUID instead of an e-mail address and
51
+ * the password branch of `reauthenticate` did not exist. Neither difference was
52
+ * reachable by any test in the tree. They are not optional any more — the
53
+ * module always answers — and `backend/test/integration/mfa/account-identity-wiring.test.ts`
54
+ * is where both are now asserted.
55
+ *
56
+ * The remaining three names are genuinely independent:
57
+ *
58
+ * - `mfaOauthProvider` — absent on a deployment with no social sign-in.
59
+ * - `mfaSocialAccountResolvers` — same, and separately omissible because a
60
+ * deployment can carry the provider without standalone registration.
61
+ * - `mfaDefaultChannelIdResolver` — defaults to `async () => null`, which is
62
+ * exactly the `?? null` both roots already spell at the call site.
63
+ *
64
+ * `requireCustomer` is resolved rather than passed, and it is **not** this
65
+ * module's to own: it is `auth`'s customer-side guard, which `auth` provides as
66
+ * a port beside `requireAdmin` since issue #43. Until then each root declared
67
+ * its own — `composition.ts` inline, the harness as `requireTestCustomer()` —
68
+ * and the two disagreed on which request property carried the actor. Unifying
69
+ * them was filed as its own task rather than folded in here, because it touched
70
+ * twenty-seven call sites that have nothing to do with MFA.
71
+ *
72
+ * The base URLs come from the environment, read here rather than threaded from
73
+ * a root — a packaged module reads its own configuration.
74
+ */
75
+ /**
76
+ * The origins MFA stamps into the links it sends and the OAuth redirects it
77
+ * builds. Defaults to the environment, because a packaged module reads its own
78
+ * configuration; contributed by a composition that has its own answer.
79
+ */
80
+ export interface MfaBaseUrls {
81
+ readonly backend: string | undefined;
82
+ readonly storefront: string | undefined;
83
+ readonly admin: string | undefined;
84
+ }
85
+ export interface MfaCradle {
86
+ readonly emFactory: () => EntityManager;
87
+ readonly redis: Redis;
88
+ readonly auditLogService: AuditPort;
89
+ readonly commandBus: CommandBus;
90
+ readonly authSessionPort: AuthSessionPort;
91
+ readonly settingsReadPort: SettingsReader;
92
+ readonly requireAdmin: RequireAdminFactory;
93
+ readonly requireCustomer: (request: FastifyRequest) => Promise<void>;
94
+ /**
95
+ * T118c — the two actor names the composition already contributes for every
96
+ * other module that asks who is calling. Declared structurally, as the eight
97
+ * other consumers declare them, so this module names no composition root.
98
+ */
99
+ readonly customerActorResolver: (request: FastifyRequest) => {
100
+ customerAccountId: string;
101
+ organizationId: string | null;
102
+ };
103
+ readonly adminContextResolver: (request: FastifyRequest) => {
104
+ adminUserId: string;
105
+ };
106
+ readonly mfaOauthProvider: OAuthProviderPort | undefined;
107
+ readonly mfaSocialAccountResolvers: SocialIdentityDeps | undefined;
108
+ readonly mfaDefaultChannelIdResolver: () => Promise<string | null>;
109
+ readonly mfaBaseUrls: MfaBaseUrls;
110
+ readonly mfa: {
111
+ handle: () => MfaModuleHandle;
112
+ plugin: unknown;
113
+ };
114
+ readonly mfaLoginPort: MfaLoginPort;
115
+ readonly mfaEnrolmentCountPort: MfaEnrolmentCountPort;
116
+ readonly mfaEnrolmentStatePort: MfaEnrolmentStatePort;
117
+ }
118
+ export declare function registerModule(ctx: ModuleContext): void;
119
+ /**
120
+ * The module's persisted entity classes, on the `./backend` subpath, as one
121
+ * array and **no named class export** (D-168).
122
+ *
123
+ * This is the shape the platform reads when the package is *installed*: the
124
+ * boot-time loader (`src/packages/package-runtime.ts`, `exported['entities']`)
125
+ * and the static declaration reader (`scripts/lib/package-declarations.ts`),
126
+ * which is the third source of `check:module-boundary`'s `table→owner` map and
127
+ * the package pass of `check-entity-tenant-classification`. A missing array is
128
+ * answered with `[]` — zero entities registered, no error anywhere.
129
+ */
130
+ export declare const entities: (typeof MfaOrganizationPolicy | typeof MfaEnrolment | typeof MfaSocialIdentity | typeof MfaRecoveryCode)[];
131
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,173 @@
1
+ import { lazyPort } from '@endora-commerce/platform/kernel';
2
+ import { OpenIdOAuthProvider, readOAuthConfigFromEnv, } from './services/oauth-provider-service.js';
3
+ import { createAccountEmailResolver, createAccountPasswordVerifier, createOrganizationAdminResolver, createOrganizationCustomerIdsResolver, } from './services/account-identity.js';
4
+ import { MfaEnrolmentCountService } from './services/mfa-enrolment-count.service.js';
5
+ import { MfaEnrolmentStateService } from './services/mfa-enrolment-state.service.js';
6
+ import { mfaModule } from './plugin.js';
7
+ import { MfaEnrolment } from './entities/mfa-enrolment.entity.js';
8
+ import { MfaOrganizationPolicy } from './entities/mfa-organization-policy.entity.js';
9
+ import { MfaRecoveryCode } from './entities/mfa-recovery-code.entity.js';
10
+ import { MfaSocialIdentity } from './entities/mfa-social-identity.entity.js';
11
+ export function registerModule(ctx) {
12
+ ctx.di.register({
13
+ /**
14
+ * Federated sign-in, defaulted to whatever this deployment's environment
15
+ * configures (T143c).
16
+ *
17
+ * It used to default to `undefined` and be contributed by a root, which
18
+ * meant `composition.ts` imported this module's provider class and its env
19
+ * reader to decide, on the module's behalf, whether the module had social
20
+ * sign-in — the shape the composition checklist rules out for a knob the
21
+ * module can read itself. `MFA_OAUTH_*` is this module's configuration and
22
+ * nothing else reads it.
23
+ *
24
+ * The seam it leaves is the one `organizations`' VAT clients left in T138 /
25
+ * T140: production takes the real `openid-client` provider by default, the
26
+ * test harness contributes a deterministic fake over the same name, and
27
+ * neither root names the class. `undefined` still means "no social sign-in
28
+ * on this deployment", which is what an unconfigured environment produces.
29
+ */
30
+ mfaOauthProvider: ctx
31
+ .asFunction(() => {
32
+ const config = readOAuthConfigFromEnv();
33
+ return config.google || config.microsoft ? new OpenIdOAuthProvider(config) : undefined;
34
+ })
35
+ .singleton(),
36
+ mfaSocialAccountResolvers: ctx
37
+ .asFunction(() => undefined)
38
+ .singleton(),
39
+ // `null` means "no system-default sales channel", which is what both roots
40
+ // already fall back to when the resolver finds none.
41
+ mfaDefaultChannelIdResolver: ctx
42
+ .asFunction(() => async () => null)
43
+ .singleton(),
44
+ mfaBaseUrls: ctx
45
+ .asFunction(() => ({
46
+ backend: process.env['BACKEND_PUBLIC_URL'],
47
+ storefront: process.env['STOREFRONT_BASE_URL'],
48
+ admin: process.env['ADMIN_BASE_URL'],
49
+ }))
50
+ .singleton(),
51
+ mfa: ctx
52
+ .asFunction(({ emFactory, redis, auditLogService, commandBus }) => {
53
+ const secretEncryptionKey = process.env['MFA_SECRET_ENCRYPTION_KEY'];
54
+ const { backend: backendBaseUrl, storefront: storefrontBaseUrl, admin: adminBaseUrl, } = ctx.cradle().mfaBaseUrls;
55
+ // Every cross-composition input below is read from the cradle at call
56
+ // time. Capturing them would freeze this module's view of the
57
+ // composition at the instant it happens to be constructed, which for
58
+ // a guard means it keeps admitting requests after `auth` goes away.
59
+ const { mfaOauthProvider: oauthProvider, mfaSocialAccountResolvers: socialAccountResolvers } = ctx.cradle();
60
+ // T118c — the four identity ports the retired bridge's members were
61
+ // built out of. `lazyPort` proxies, never resolved values: a captured
62
+ // gate keeps answering after its owner is switched off, and on an
63
+ // authentication path that is an account reachable without the factor
64
+ // it was supposed to require.
65
+ const customerAccounts = lazyPort(ctx, 'customerAccountReadPort');
66
+ const adminUsers = lazyPort(ctx, 'adminUserReadPort');
67
+ const resolveOrganizationAdmin = createOrganizationAdminResolver(customerAccounts);
68
+ return mfaModule({
69
+ emFactory,
70
+ redis,
71
+ // Deferred rather than destructured: both roots read `mfaLoginPort`
72
+ // to contribute the getter `customer_accounts` needs at login, and
73
+ // that read happens *before* either registers `settingsReadPort`.
74
+ // Taking it as a constructor argument would resolve this module at
75
+ // that moment and fail on a name that does not exist yet.
76
+ settingsService: {
77
+ get: (code, salesChannelId, schema) => ctx.cradle().settingsReadPort.get(code, salesChannelId, schema),
78
+ },
79
+ auditLogService,
80
+ commandBus,
81
+ // Issue #222 — `customer_accounts`' published answer to "does this
82
+ // account have a password on record". Resolved lazily like the
83
+ // session port beside it: a captured gate would keep answering
84
+ // after its owner went away, and this one decides whether somebody
85
+ // may remove their last way into their account.
86
+ customerPasswordState: lazyPort(ctx, 'customerPasswordStatePort'),
87
+ // Feature 075 Phase C — `auth`'s published session surface, not its
88
+ // `SessionService` class. The two fields the second factor reads
89
+ // (the cookie value and its expiry) travel as plain data; the
90
+ // `Session` entity stays behind the boundary, where its `tokenHash`
91
+ // belongs.
92
+ sessionService: lazyPort(ctx, 'authSessionPort'),
93
+ resolveDefaultChannelId: () => ctx.cradle().mfaDefaultChannelIdResolver(),
94
+ ...(secretEncryptionKey === undefined ? {} : { secretEncryptionKey }),
95
+ ...(backendBaseUrl === undefined ? {} : { backendBaseUrl }),
96
+ ...(storefrontBaseUrl === undefined ? {} : { storefrontBaseUrl }),
97
+ ...(adminBaseUrl === undefined ? {} : { adminBaseUrl }),
98
+ requireCustomer: (request) => ctx.cradle().requireCustomer(request),
99
+ requireAdmin: (permission) => async (req, reply) => ctx.cradle().requireAdmin(permission)(req, reply),
100
+ // T118c — read from the cradle at call time for the reason every
101
+ // other input here is: the composition's answer to "who is asking",
102
+ // not a second copy of it written in this module.
103
+ resolveCustomerActor: (request) => ctx.cradle().customerActorResolver(request),
104
+ // `adminContextResolver`, and no `promoteAdminActor` beside it:
105
+ // every call site is behind `requireAdmin`, whose `auth`
106
+ // implementation promotes and then refuses a non-admin, so the
107
+ // promotion has happened before this runs.
108
+ resolveAdminActor: (request) => ctx.cradle().adminContextResolver(request),
109
+ resolveOrgAdmin: (request) => resolveOrganizationAdmin(ctx.cradle().customerActorResolver(request).customerAccountId),
110
+ resolveOrganizationCustomerIds: createOrganizationCustomerIdsResolver(customerAccounts),
111
+ resolveAccountEmail: createAccountEmailResolver(customerAccounts, adminUsers),
112
+ verifyAccountPassword: createAccountPasswordVerifier(lazyPort(ctx, 'customerPasswordVerificationPort'), lazyPort(ctx, 'adminPasswordVerificationPort')),
113
+ // Spread rather than assigned: `exactOptionalPropertyTypes` makes
114
+ // "absent" and "present as undefined" different types, and these
115
+ // two are genuinely absent on a deployment without social sign-in.
116
+ ...(oauthProvider === undefined ? {} : { oauthProvider }),
117
+ ...(socialAccountResolvers === undefined ? {} : { socialAccountResolvers }),
118
+ });
119
+ })
120
+ .singleton(),
121
+ });
122
+ ctx.di.providePort('mfaLoginPort', ctx.asFunction(({ mfa }) => mfa.handle().mfaLoginPort).singleton());
123
+ /**
124
+ * The live half of the deactivation-confirmation dialog (owner ruling on
125
+ * D-96.5): how many people currently hold a second factor.
126
+ *
127
+ * Gated like every other port, and that is exactly right here — the dialog
128
+ * renders *before* the flip, while this module is still on, so the question
129
+ * is asked through an open gate. `/platform/modules` decides presence before
130
+ * it resolves this and renders "unavailable" rather than blocking the flip if
131
+ * the read fails.
132
+ */
133
+ ctx.di.providePort('mfaEnrolmentCountPort', ctx
134
+ .asFunction(({ emFactory }) => new MfaEnrolmentCountService(emFactory))
135
+ .singleton());
136
+ /**
137
+ * Who holds a second factor, for the two identity modules that publish
138
+ * `twoFactorEnabled`.
139
+ *
140
+ * Gated like every other port, and unlike `mfaEnrolmentCountPort` above it is
141
+ * read on ordinary request paths rather than in a dialog that precedes the
142
+ * flip — so both consumers ask `effectiveState.isPresent('mfa')` before they
143
+ * resolve it and report `false` when this module is absent. That is a
144
+ * declared degrade, not a caught gate: with `mfa` off nothing asks for a
145
+ * second factor at sign-in, so no account is protected by one and `false` is
146
+ * the answer rather than a substitute for one.
147
+ */
148
+ ctx.di.providePort('mfaEnrolmentStatePort', ctx
149
+ .asFunction(({ emFactory }) => new MfaEnrolmentStateService(emFactory))
150
+ .singleton());
151
+ ctx.routes(async (app) => {
152
+ const plugin = ctx.cradle().mfa.plugin;
153
+ await plugin(app);
154
+ });
155
+ }
156
+ /**
157
+ * The module's persisted entity classes, on the `./backend` subpath, as one
158
+ * array and **no named class export** (D-168).
159
+ *
160
+ * This is the shape the platform reads when the package is *installed*: the
161
+ * boot-time loader (`src/packages/package-runtime.ts`, `exported['entities']`)
162
+ * and the static declaration reader (`scripts/lib/package-declarations.ts`),
163
+ * which is the third source of `check:module-boundary`'s `table→owner` map and
164
+ * the package pass of `check-entity-tenant-classification`. A missing array is
165
+ * answered with `[]` — zero entities registered, no error anywhere.
166
+ */
167
+ export const entities = [
168
+ MfaEnrolment,
169
+ MfaOrganizationPolicy,
170
+ MfaRecoveryCode,
171
+ MfaSocialIdentity,
172
+ ];
173
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,99 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';
3
+ import type { Redis } from 'ioredis';
4
+ import type { AuthSessionPort, CustomerPasswordStatePort, MfaLoginPort } from '@endora-commerce/contracts';
5
+ import type { AuditPort } from '@endora-commerce/platform/kernel';
6
+ import type { CommandBus } from '@endora-commerce/platform/commands';
7
+ import { ChallengeStore } from './services/challenge-store.js';
8
+ import { MfaPolicyResolver, type SettingsReader } from './services/mfa-policy-resolver.js';
9
+ import { MfaEnrolmentService } from './services/mfa-enrolment-service.js';
10
+ import { type SocialIdentityDeps } from './services/social-identity-service.js';
11
+ import type { OAuthProviderPort } from './services/oauth-provider-service.js';
12
+ import type { RequireAdminFactory } from '@endora-commerce/platform/kernel';
13
+ /**
14
+ * The attach function this module hands its composition root.
15
+ *
16
+ * Typed on `fastify`'s own `FastifyInstance` rather than on the platform's
17
+ * `ModulePlugin`, which `contracts/host-package.md` §1.4g classifies **A**: the
18
+ * host does not publish it, so a packaged module cannot name it. The
19
+ * already-packaged `quote_requests` types its attach function the same way.
20
+ */
21
+ type ModuleAttach = (app: FastifyInstance) => Promise<void>;
22
+ /**
23
+ * MFA module composition root (feature 042).
24
+ *
25
+ * Owns 2FA enrolment, recovery codes, federated sign-in (later stories),
26
+ * policy resolution, and admin reset (later stories). Sibling services are
27
+ * injected so the module never imports another module's internals
28
+ * (Constitution Principle I). Exposes its `MfaLoginPort` via the handle for
29
+ * the per-surface login services.
30
+ *
31
+ * Enrolment requires `secretEncryptionKey`; when it is absent the module still
32
+ * composes and `beginLogin` works (password-only), but the enrolment + verify
33
+ * routes are not registered.
34
+ */
35
+ export interface MfaModuleOptions {
36
+ emFactory: () => EntityManager;
37
+ redis: Redis;
38
+ settingsService: SettingsReader;
39
+ auditLogService: AuditPort;
40
+ /** Constitution XIII — the unlink is a security-relevant, audited write. */
41
+ commandBus: CommandBus;
42
+ /**
43
+ * Issue #222 — whether a customer account has a password on record, which is
44
+ * what decides whether its last federated identity may be severed. Mandatory:
45
+ * a composition without it would have to guess, and the two guesses are "lock
46
+ * the holder out" and "refuse forever".
47
+ */
48
+ customerPasswordState: CustomerPasswordStatePort;
49
+ sessionService: AuthSessionPort;
50
+ /** Resolves the system-default sales-channel id for global setting reads. */
51
+ resolveDefaultChannelId?: () => Promise<string | null>;
52
+ /** base64 32-byte AES key for TOTP secrets at rest. */
53
+ secretEncryptionKey?: string | undefined;
54
+ /** Storefront customer guard + actor resolver (for self-service routes). */
55
+ requireCustomer: (req: FastifyRequest, reply: FastifyReply) => Promise<void>;
56
+ resolveCustomerActor: (req: FastifyRequest) => {
57
+ customerAccountId: string;
58
+ organizationId: string | null;
59
+ };
60
+ /** Admin guard factory + actor resolver (admin self-service US2 + reset US6). */
61
+ requireAdmin?: RequireAdminFactory;
62
+ resolveAdminActor: (req: FastifyRequest) => {
63
+ adminUserId: string;
64
+ };
65
+ /** Lists an organization's customer-account ids (US6 bulk reset). */
66
+ resolveOrganizationCustomerIds: (organizationId: string) => Promise<string[]>;
67
+ /** Resolves an org-admin customer's org + asserts the role (US3 storefront). */
68
+ resolveOrgAdmin: (req: FastifyRequest) => Promise<{
69
+ organizationId: string;
70
+ actor: string;
71
+ }>;
72
+ /** Federated sign-in (US4/US5) — provider port + account resolvers + URLs. */
73
+ oauthProvider?: OAuthProviderPort;
74
+ socialAccountResolvers?: SocialIdentityDeps;
75
+ backendBaseUrl?: string;
76
+ storefrontBaseUrl?: string;
77
+ adminBaseUrl?: string;
78
+ /**
79
+ * `specs/110-instance-repository/` T118c — required, where both were optional
80
+ * while a composition root supplied them. `mfa` resolves each from the owning
81
+ * identity module's published port now, so there is no composition that has
82
+ * one and not the other, and the optionality was a live divergence rather
83
+ * than a capability: the harness omitted both and no test could see it.
84
+ */
85
+ resolveAccountEmail: (subjectType: 'customer' | 'admin', subjectId: string) => Promise<string | null>;
86
+ verifyAccountPassword: (subjectType: 'customer' | 'admin', subjectId: string, password: string) => Promise<boolean>;
87
+ }
88
+ export interface MfaModuleHandle {
89
+ mfaLoginPort: MfaLoginPort;
90
+ challengeStore: ChallengeStore;
91
+ policyResolver: MfaPolicyResolver;
92
+ enrolmentService: MfaEnrolmentService | null;
93
+ }
94
+ export declare function mfaModule(options: MfaModuleOptions): {
95
+ plugin: ModuleAttach;
96
+ handle: () => MfaModuleHandle;
97
+ };
98
+ export {};
99
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/backend/plugin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,111 @@
1
+ import { ChallengeStore } from './services/challenge-store.js';
2
+ import { MfaPolicyResolver, } from './services/mfa-policy-resolver.js';
3
+ import { MfaLoginService } from './services/mfa-login-service.js';
4
+ import { MfaEnrolmentService } from './services/mfa-enrolment-service.js';
5
+ import { MfaOrgPolicyService } from './services/mfa-org-policy-service.js';
6
+ import { SocialIdentityService, } from './services/social-identity-service.js';
7
+ import { SocialLinkService } from './services/social-link-service.js';
8
+ import { SecretCipher } from './services/secret-cipher.js';
9
+ import { registerMfaPublicRoutes } from './routes.public.js';
10
+ import { registerMfaSelfServiceRoutes } from './routes.self-service.js';
11
+ import { registerMfaAdminRoutes } from './routes.admin.js';
12
+ import { registerMfaOrgRoutes } from './routes.org.js';
13
+ import { registerMfaOAuthRoutes } from './routes.oauth.js';
14
+ export function mfaModule(options) {
15
+ const challengeStore = new ChallengeStore(options.redis);
16
+ const policyResolver = new MfaPolicyResolver(options.settingsService, options.emFactory, options.resolveDefaultChannelId ?? (async () => null));
17
+ const cipher = options.secretEncryptionKey
18
+ ? new SecretCipher(options.secretEncryptionKey)
19
+ : null;
20
+ const enrolmentService = cipher
21
+ ? new MfaEnrolmentService(options.emFactory, cipher, options.auditLogService)
22
+ : null;
23
+ const loginService = new MfaLoginService(options.emFactory, challengeStore, policyResolver, enrolmentService ?? undefined);
24
+ const orgPolicyService = new MfaOrgPolicyService(options.emFactory, options.auditLogService);
25
+ // Deliberately not behind the `oauthProvider` check below: the links an
26
+ // account already holds stay listable and severable after an operator turns
27
+ // a provider off, which is precisely when somebody goes looking for them.
28
+ const socialLinkService = new SocialLinkService(options.emFactory, options.commandBus, options.customerPasswordState);
29
+ const plugin = async (app) => {
30
+ if (!enrolmentService)
31
+ return; // enrolment disabled without an encryption key
32
+ await registerMfaPublicRoutes(app, {
33
+ loginService,
34
+ sessionService: options.sessionService,
35
+ challengeStore,
36
+ enrolmentService,
37
+ auditLogService: options.auditLogService,
38
+ });
39
+ // Storefront customer self-service (US1).
40
+ await registerMfaSelfServiceRoutes(app, {
41
+ pathPrefix: '/api/v1/account/mfa',
42
+ subjectType: 'customer',
43
+ auditObjectType: 'customer_account',
44
+ requireGuard: options.requireCustomer,
45
+ resolveSubjectId: (req) => options.resolveCustomerActor(req).customerAccountId,
46
+ resolveOrganizationId: (req) => options.resolveCustomerActor(req).organizationId,
47
+ enrolmentService,
48
+ policyResolver,
49
+ socialLinkService,
50
+ auditLogService: options.auditLogService,
51
+ resolveAccountEmail: options.resolveAccountEmail,
52
+ verifyAccountPassword: options.verifyAccountPassword,
53
+ });
54
+ // Federated sign-in (US4 customer / US5 admin).
55
+ if (options.oauthProvider && options.socialAccountResolvers) {
56
+ const socialIdentityService = new SocialIdentityService(options.emFactory, options.socialAccountResolvers, options.auditLogService);
57
+ await registerMfaOAuthRoutes(app, {
58
+ oauthProvider: options.oauthProvider,
59
+ challengeStore,
60
+ policyResolver,
61
+ socialIdentityService,
62
+ sessionService: options.sessionService,
63
+ backendBaseUrl: options.backendBaseUrl ?? 'http://localhost:8080',
64
+ storefrontBaseUrl: options.storefrontBaseUrl ?? 'http://localhost:3000',
65
+ adminBaseUrl: options.adminBaseUrl ?? 'http://localhost:3002',
66
+ });
67
+ }
68
+ // Storefront org-admin enforcement (US3).
69
+ await registerMfaOrgRoutes(app, {
70
+ orgPolicyService,
71
+ auditLogService: options.auditLogService,
72
+ requireCustomer: options.requireCustomer,
73
+ resolveOrgAdmin: options.resolveOrgAdmin,
74
+ });
75
+ // Admin user self-service (US2) + admin reset (US6) + org enforcement (US3).
76
+ const { requireAdmin, resolveAdminActor } = options;
77
+ if (requireAdmin) {
78
+ await registerMfaSelfServiceRoutes(app, {
79
+ pathPrefix: '/api/v1/admin/account/mfa',
80
+ subjectType: 'admin',
81
+ auditObjectType: 'admin_user',
82
+ requireGuard: requireAdmin(),
83
+ resolveSubjectId: (req) => resolveAdminActor(req).adminUserId,
84
+ enrolmentService,
85
+ policyResolver,
86
+ socialLinkService,
87
+ auditLogService: options.auditLogService,
88
+ resolveAccountEmail: options.resolveAccountEmail,
89
+ verifyAccountPassword: options.verifyAccountPassword,
90
+ });
91
+ await registerMfaAdminRoutes(app, {
92
+ enrolmentService,
93
+ orgPolicyService,
94
+ auditLogService: options.auditLogService,
95
+ requireAdmin,
96
+ resolveAdminActor,
97
+ resolveOrganizationCustomerIds: options.resolveOrganizationCustomerIds,
98
+ });
99
+ }
100
+ };
101
+ return {
102
+ plugin,
103
+ handle: () => ({
104
+ mfaLoginPort: loginService,
105
+ challengeStore,
106
+ policyResolver,
107
+ enrolmentService,
108
+ }),
109
+ };
110
+ }
111
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/backend/plugin.ts"],"names":[],"mappings":"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"}