@open-mercato/enterprise 0.4.6-develop-15c18897fc → 0.4.6-develop-34aa847ce6

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 (195) hide show
  1. package/dist/index.js +1 -1
  2. package/dist/index.js.map +2 -2
  3. package/dist/modules/sso/acl.js +11 -0
  4. package/dist/modules/sso/acl.js.map +7 -0
  5. package/dist/modules/sso/api/admin-context.js +27 -0
  6. package/dist/modules/sso/api/admin-context.js.map +7 -0
  7. package/dist/modules/sso/api/callback/oidc/route.js +103 -0
  8. package/dist/modules/sso/api/callback/oidc/route.js.map +7 -0
  9. package/dist/modules/sso/api/config/[id]/activate/route.js +49 -0
  10. package/dist/modules/sso/api/config/[id]/activate/route.js.map +7 -0
  11. package/dist/modules/sso/api/config/[id]/domains/route.js +96 -0
  12. package/dist/modules/sso/api/config/[id]/domains/route.js.map +7 -0
  13. package/dist/modules/sso/api/config/[id]/route.js +103 -0
  14. package/dist/modules/sso/api/config/[id]/route.js.map +7 -0
  15. package/dist/modules/sso/api/config/[id]/test/route.js +41 -0
  16. package/dist/modules/sso/api/config/[id]/test/route.js.map +7 -0
  17. package/dist/modules/sso/api/config/route.js +83 -0
  18. package/dist/modules/sso/api/config/route.js.map +7 -0
  19. package/dist/modules/sso/api/error-handler.js +28 -0
  20. package/dist/modules/sso/api/error-handler.js.map +7 -0
  21. package/dist/modules/sso/api/hrd/route.js +52 -0
  22. package/dist/modules/sso/api/hrd/route.js.map +7 -0
  23. package/dist/modules/sso/api/initiate/route.js +66 -0
  24. package/dist/modules/sso/api/initiate/route.js.map +7 -0
  25. package/dist/modules/sso/api/scim/context.js +68 -0
  26. package/dist/modules/sso/api/scim/context.js.map +7 -0
  27. package/dist/modules/sso/api/scim/logs/route.js +65 -0
  28. package/dist/modules/sso/api/scim/logs/route.js.map +7 -0
  29. package/dist/modules/sso/api/scim/tokens/[id]/route.js +42 -0
  30. package/dist/modules/sso/api/scim/tokens/[id]/route.js.map +7 -0
  31. package/dist/modules/sso/api/scim/tokens/route.js +83 -0
  32. package/dist/modules/sso/api/scim/tokens/route.js.map +7 -0
  33. package/dist/modules/sso/api/scim/v2/ServiceProviderConfig/route.js +42 -0
  34. package/dist/modules/sso/api/scim/v2/ServiceProviderConfig/route.js.map +7 -0
  35. package/dist/modules/sso/api/scim/v2/Users/[id]/route.js +94 -0
  36. package/dist/modules/sso/api/scim/v2/Users/[id]/route.js.map +7 -0
  37. package/dist/modules/sso/api/scim/v2/Users/route.js +86 -0
  38. package/dist/modules/sso/api/scim/v2/Users/route.js.map +7 -0
  39. package/dist/modules/sso/backend/page.js +173 -0
  40. package/dist/modules/sso/backend/page.js.map +7 -0
  41. package/dist/modules/sso/backend/page.meta.js +31 -0
  42. package/dist/modules/sso/backend/page.meta.js.map +7 -0
  43. package/dist/modules/sso/backend/sso/config/[id]/page.js +749 -0
  44. package/dist/modules/sso/backend/sso/config/[id]/page.js.map +7 -0
  45. package/dist/modules/sso/backend/sso/config/[id]/page.meta.js +19 -0
  46. package/dist/modules/sso/backend/sso/config/[id]/page.meta.js.map +7 -0
  47. package/dist/modules/sso/backend/sso/config/new/page.js +381 -0
  48. package/dist/modules/sso/backend/sso/config/new/page.js.map +7 -0
  49. package/dist/modules/sso/backend/sso/config/new/page.meta.js +19 -0
  50. package/dist/modules/sso/backend/sso/config/new/page.meta.js.map +7 -0
  51. package/dist/modules/sso/data/entities.js +299 -0
  52. package/dist/modules/sso/data/entities.js.map +7 -0
  53. package/dist/modules/sso/data/validators.js +114 -0
  54. package/dist/modules/sso/data/validators.js.map +7 -0
  55. package/dist/modules/sso/di.js +26 -0
  56. package/dist/modules/sso/di.js.map +7 -0
  57. package/dist/modules/sso/events.js +24 -0
  58. package/dist/modules/sso/events.js.map +7 -0
  59. package/dist/modules/sso/i18n/de.json +146 -0
  60. package/dist/modules/sso/i18n/en.json +146 -0
  61. package/dist/modules/sso/i18n/es.json +146 -0
  62. package/dist/modules/sso/i18n/pl.json +146 -0
  63. package/dist/modules/sso/index.js +11 -0
  64. package/dist/modules/sso/index.js.map +7 -0
  65. package/dist/modules/sso/lib/domains.js +30 -0
  66. package/dist/modules/sso/lib/domains.js.map +7 -0
  67. package/dist/modules/sso/lib/oidc-provider.js +140 -0
  68. package/dist/modules/sso/lib/oidc-provider.js.map +7 -0
  69. package/dist/modules/sso/lib/registry.js +15 -0
  70. package/dist/modules/sso/lib/registry.js.map +7 -0
  71. package/dist/modules/sso/lib/scim-filter.js +43 -0
  72. package/dist/modules/sso/lib/scim-filter.js.map +7 -0
  73. package/dist/modules/sso/lib/scim-mapper.js +49 -0
  74. package/dist/modules/sso/lib/scim-mapper.js.map +7 -0
  75. package/dist/modules/sso/lib/scim-patch.js +63 -0
  76. package/dist/modules/sso/lib/scim-patch.js.map +7 -0
  77. package/dist/modules/sso/lib/scim-response.js +34 -0
  78. package/dist/modules/sso/lib/scim-response.js.map +7 -0
  79. package/dist/modules/sso/lib/scim-utils.js +9 -0
  80. package/dist/modules/sso/lib/scim-utils.js.map +7 -0
  81. package/dist/modules/sso/lib/state-cookie.js +67 -0
  82. package/dist/modules/sso/lib/state-cookie.js.map +7 -0
  83. package/dist/modules/sso/lib/types.js +1 -0
  84. package/dist/modules/sso/lib/types.js.map +7 -0
  85. package/dist/modules/sso/migrations/Migration20260219000000_sso.js +20 -0
  86. package/dist/modules/sso/migrations/Migration20260219000000_sso.js.map +7 -0
  87. package/dist/modules/sso/migrations/Migration20260222000000_sso_add_name.js +13 -0
  88. package/dist/modules/sso/migrations/Migration20260222000000_sso_add_name.js.map +7 -0
  89. package/dist/modules/sso/migrations/Migration20260222000001_sso_partial_unique_org.js +15 -0
  90. package/dist/modules/sso/migrations/Migration20260222000001_sso_partial_unique_org.js.map +7 -0
  91. package/dist/modules/sso/migrations/Migration20260223000000_scim_tables.js +22 -0
  92. package/dist/modules/sso/migrations/Migration20260223000000_scim_tables.js.map +7 -0
  93. package/dist/modules/sso/migrations/Migration20260224000000_sso_external_id.js +15 -0
  94. package/dist/modules/sso/migrations/Migration20260224000000_sso_external_id.js.map +7 -0
  95. package/dist/modules/sso/migrations/Migration20260224100000_sso_role_grants.js +17 -0
  96. package/dist/modules/sso/migrations/Migration20260224100000_sso_role_grants.js.map +7 -0
  97. package/dist/modules/sso/migrations/Migration20260224200000_drop_default_role_id.js +13 -0
  98. package/dist/modules/sso/migrations/Migration20260224200000_drop_default_role_id.js.map +7 -0
  99. package/dist/modules/sso/migrations/Migration20260225000000_sso_identities_partial_unique.js +23 -0
  100. package/dist/modules/sso/migrations/Migration20260225000000_sso_identities_partial_unique.js.map +7 -0
  101. package/dist/modules/sso/migrations/Migration20260305000000_sso_role_grants_org_id.js +14 -0
  102. package/dist/modules/sso/migrations/Migration20260305000000_sso_role_grants_org_id.js.map +7 -0
  103. package/dist/modules/sso/services/accountLinkingService.js +298 -0
  104. package/dist/modules/sso/services/accountLinkingService.js.map +7 -0
  105. package/dist/modules/sso/services/hrdService.js +18 -0
  106. package/dist/modules/sso/services/hrdService.js.map +7 -0
  107. package/dist/modules/sso/services/scimService.js +372 -0
  108. package/dist/modules/sso/services/scimService.js.map +7 -0
  109. package/dist/modules/sso/services/scimTokenService.js +94 -0
  110. package/dist/modules/sso/services/scimTokenService.js.map +7 -0
  111. package/dist/modules/sso/services/ssoConfigService.js +254 -0
  112. package/dist/modules/sso/services/ssoConfigService.js.map +7 -0
  113. package/dist/modules/sso/services/ssoService.js +125 -0
  114. package/dist/modules/sso/services/ssoService.js.map +7 -0
  115. package/dist/modules/sso/setup.js +47 -0
  116. package/dist/modules/sso/setup.js.map +7 -0
  117. package/dist/modules/sso/subscribers/user-deleted-cleanup.js +21 -0
  118. package/dist/modules/sso/subscribers/user-deleted-cleanup.js.map +7 -0
  119. package/dist/modules/sso/widgets/injection/login-sso/widget.client.js +106 -0
  120. package/dist/modules/sso/widgets/injection/login-sso/widget.client.js.map +7 -0
  121. package/dist/modules/sso/widgets/injection/login-sso/widget.js +16 -0
  122. package/dist/modules/sso/widgets/injection/login-sso/widget.js.map +7 -0
  123. package/dist/modules/sso/widgets/injection-table.js +14 -0
  124. package/dist/modules/sso/widgets/injection-table.js.map +7 -0
  125. package/package.json +5 -4
  126. package/src/index.ts +1 -1
  127. package/src/modules/sso/acl.ts +7 -0
  128. package/src/modules/sso/api/admin-context.ts +36 -0
  129. package/src/modules/sso/api/callback/oidc/route.ts +115 -0
  130. package/src/modules/sso/api/config/[id]/activate/route.ts +53 -0
  131. package/src/modules/sso/api/config/[id]/domains/route.ts +107 -0
  132. package/src/modules/sso/api/config/[id]/route.ts +114 -0
  133. package/src/modules/sso/api/config/[id]/test/route.ts +44 -0
  134. package/src/modules/sso/api/config/route.ts +88 -0
  135. package/src/modules/sso/api/error-handler.ts +36 -0
  136. package/src/modules/sso/api/hrd/route.ts +55 -0
  137. package/src/modules/sso/api/initiate/route.ts +70 -0
  138. package/src/modules/sso/api/scim/context.ts +85 -0
  139. package/src/modules/sso/api/scim/logs/route.ts +69 -0
  140. package/src/modules/sso/api/scim/tokens/[id]/route.ts +45 -0
  141. package/src/modules/sso/api/scim/tokens/route.ts +89 -0
  142. package/src/modules/sso/api/scim/v2/ServiceProviderConfig/route.ts +40 -0
  143. package/src/modules/sso/api/scim/v2/Users/[id]/route.ts +103 -0
  144. package/src/modules/sso/api/scim/v2/Users/route.ts +94 -0
  145. package/src/modules/sso/backend/page.meta.ts +29 -0
  146. package/src/modules/sso/backend/page.tsx +232 -0
  147. package/src/modules/sso/backend/sso/config/[id]/page.meta.ts +15 -0
  148. package/src/modules/sso/backend/sso/config/[id]/page.tsx +1024 -0
  149. package/src/modules/sso/backend/sso/config/new/page.meta.ts +15 -0
  150. package/src/modules/sso/backend/sso/config/new/page.tsx +463 -0
  151. package/src/modules/sso/data/entities.ts +240 -0
  152. package/src/modules/sso/data/validators.ts +140 -0
  153. package/src/modules/sso/di.ts +25 -0
  154. package/src/modules/sso/docs/entra-id-setup.md +281 -0
  155. package/src/modules/sso/docs/google-workspace-setup.md +174 -0
  156. package/src/modules/sso/docs/sso-overview.md +218 -0
  157. package/src/modules/sso/docs/sso-security-audit-2026-02-27.md +118 -0
  158. package/src/modules/sso/docs/zitadel-setup.md +195 -0
  159. package/src/modules/sso/events.ts +21 -0
  160. package/src/modules/sso/i18n/de.json +146 -0
  161. package/src/modules/sso/i18n/en.json +146 -0
  162. package/src/modules/sso/i18n/es.json +146 -0
  163. package/src/modules/sso/i18n/pl.json +146 -0
  164. package/src/modules/sso/index.ts +7 -0
  165. package/src/modules/sso/lib/domains.ts +31 -0
  166. package/src/modules/sso/lib/oidc-provider.ts +196 -0
  167. package/src/modules/sso/lib/registry.ts +13 -0
  168. package/src/modules/sso/lib/scim-filter.ts +62 -0
  169. package/src/modules/sso/lib/scim-mapper.ts +88 -0
  170. package/src/modules/sso/lib/scim-patch.ts +88 -0
  171. package/src/modules/sso/lib/scim-response.ts +40 -0
  172. package/src/modules/sso/lib/scim-utils.ts +5 -0
  173. package/src/modules/sso/lib/state-cookie.ts +79 -0
  174. package/src/modules/sso/lib/types.ts +50 -0
  175. package/src/modules/sso/migrations/.snapshot-open-mercato.json +912 -0
  176. package/src/modules/sso/migrations/Migration20260219000000_sso.ts +21 -0
  177. package/src/modules/sso/migrations/Migration20260222000000_sso_add_name.ts +13 -0
  178. package/src/modules/sso/migrations/Migration20260222000001_sso_partial_unique_org.ts +15 -0
  179. package/src/modules/sso/migrations/Migration20260223000000_scim_tables.ts +24 -0
  180. package/src/modules/sso/migrations/Migration20260224000000_sso_external_id.ts +15 -0
  181. package/src/modules/sso/migrations/Migration20260224100000_sso_role_grants.ts +18 -0
  182. package/src/modules/sso/migrations/Migration20260224200000_drop_default_role_id.ts +13 -0
  183. package/src/modules/sso/migrations/Migration20260225000000_sso_identities_partial_unique.ts +25 -0
  184. package/src/modules/sso/migrations/Migration20260305000000_sso_role_grants_org_id.ts +14 -0
  185. package/src/modules/sso/services/accountLinkingService.ts +386 -0
  186. package/src/modules/sso/services/hrdService.ts +22 -0
  187. package/src/modules/sso/services/scimService.ts +461 -0
  188. package/src/modules/sso/services/scimTokenService.ts +136 -0
  189. package/src/modules/sso/services/ssoConfigService.ts +337 -0
  190. package/src/modules/sso/services/ssoService.ts +167 -0
  191. package/src/modules/sso/setup.ts +56 -0
  192. package/src/modules/sso/subscribers/user-deleted-cleanup.ts +33 -0
  193. package/src/modules/sso/widgets/injection/login-sso/widget.client.tsx +130 -0
  194. package/src/modules/sso/widgets/injection/login-sso/widget.ts +16 -0
  195. package/src/modules/sso/widgets/injection-table.ts +12 -0
@@ -0,0 +1,298 @@
1
+ import { User, UserRole, Role } from "@open-mercato/core/modules/auth/data/entities";
2
+ import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
3
+ import { computeEmailHash } from "@open-mercato/core/modules/auth/lib/emailHash";
4
+ import { SsoIdentity, SsoRoleGrant, ScimToken } from "../data/entities.js";
5
+ import { emitSsoEvent } from "../events.js";
6
+ class AccountLinkingService {
7
+ constructor(em) {
8
+ this.em = em;
9
+ }
10
+ async resolveUser(config, idpPayload, tenantId) {
11
+ const existing = await this.findExistingLink(config.id, idpPayload.subject, tenantId, config.organizationId);
12
+ if (existing) {
13
+ await this.assignRolesFromSso(this.em, existing.user, config, tenantId, idpPayload.groups);
14
+ return existing;
15
+ }
16
+ if (idpPayload.emailVerified === false) {
17
+ throw new Error("IdP explicitly reported email as unverified \u2014 cannot link or provision account");
18
+ }
19
+ const emailDomain = idpPayload.email.split("@")[1]?.toLowerCase();
20
+ if (!emailDomain || !config.allowedDomains.some((d) => d.toLowerCase() === emailDomain)) {
21
+ throw new Error("Email domain is not in the allowed domains for this SSO configuration");
22
+ }
23
+ const emailLinked = config.autoLinkByEmail ? await this.linkByEmail(config, idpPayload, tenantId) : null;
24
+ if (emailLinked) {
25
+ await this.assignRolesFromSso(this.em, emailLinked.user, config, tenantId, idpPayload.groups);
26
+ return emailLinked;
27
+ }
28
+ if (config.jitEnabled) {
29
+ const scimActive = await this.em.count(ScimToken, { ssoConfigId: config.id, isActive: true }) > 0;
30
+ if (scimActive) {
31
+ throw new Error("JIT provisioning is disabled because SCIM directory sync is active");
32
+ }
33
+ return this.jitProvision(config, idpPayload, tenantId);
34
+ }
35
+ throw new Error("No matching user found and JIT provisioning is disabled");
36
+ }
37
+ async findExistingLink(ssoConfigId, idpSubject, tenantId, organizationId) {
38
+ const identity = await findOneWithDecryption(
39
+ this.em,
40
+ SsoIdentity,
41
+ { ssoConfigId, idpSubject, deletedAt: null },
42
+ {},
43
+ { tenantId, organizationId }
44
+ );
45
+ if (!identity) return null;
46
+ const user = await findOneWithDecryption(
47
+ this.em,
48
+ User,
49
+ { id: identity.userId, deletedAt: null },
50
+ {},
51
+ { tenantId, organizationId }
52
+ );
53
+ if (!user) {
54
+ identity.deletedAt = /* @__PURE__ */ new Date();
55
+ await this.em.flush();
56
+ return null;
57
+ }
58
+ identity.lastLoginAt = /* @__PURE__ */ new Date();
59
+ await this.em.flush();
60
+ return { user, identity };
61
+ }
62
+ async linkByEmail(config, idpPayload, tenantId) {
63
+ const emailHash = computeEmailHash(idpPayload.email);
64
+ const user = await findOneWithDecryption(
65
+ this.em,
66
+ User,
67
+ {
68
+ organizationId: config.organizationId,
69
+ deletedAt: null,
70
+ $or: [
71
+ { email: idpPayload.email },
72
+ { emailHash }
73
+ ]
74
+ },
75
+ {},
76
+ { tenantId, organizationId: config.organizationId }
77
+ );
78
+ if (!user) return null;
79
+ const now = /* @__PURE__ */ new Date();
80
+ const identity = this.em.create(SsoIdentity, {
81
+ tenantId,
82
+ organizationId: config.organizationId,
83
+ ssoConfigId: config.id,
84
+ userId: user.id,
85
+ idpSubject: idpPayload.subject,
86
+ idpEmail: idpPayload.email,
87
+ idpName: idpPayload.name ?? null,
88
+ idpGroups: idpPayload.groups ?? [],
89
+ provisioningMethod: "manual",
90
+ firstLoginAt: now,
91
+ lastLoginAt: now,
92
+ createdAt: now,
93
+ updatedAt: now
94
+ });
95
+ await this.em.persistAndFlush(identity);
96
+ void emitSsoEvent("sso.identity.linked", {
97
+ id: identity.id,
98
+ tenantId,
99
+ organizationId: config.organizationId
100
+ }).catch((e) => console.error("[SSO Event]", e));
101
+ return { user, identity };
102
+ }
103
+ async jitProvision(config, idpPayload, tenantId) {
104
+ return this.em.transactional(async (txEm) => {
105
+ const user = txEm.create(User, {
106
+ tenantId,
107
+ organizationId: config.organizationId,
108
+ email: idpPayload.email,
109
+ emailHash: computeEmailHash(idpPayload.email),
110
+ name: idpPayload.name ?? null,
111
+ passwordHash: null,
112
+ isConfirmed: true,
113
+ createdAt: /* @__PURE__ */ new Date()
114
+ });
115
+ await txEm.persistAndFlush(user);
116
+ await this.assignRolesFromSso(txEm, user, config, tenantId, idpPayload.groups);
117
+ const now = /* @__PURE__ */ new Date();
118
+ const identity = txEm.create(SsoIdentity, {
119
+ tenantId,
120
+ organizationId: config.organizationId,
121
+ ssoConfigId: config.id,
122
+ userId: user.id,
123
+ idpSubject: idpPayload.subject,
124
+ idpEmail: idpPayload.email,
125
+ idpName: idpPayload.name ?? null,
126
+ idpGroups: idpPayload.groups ?? [],
127
+ provisioningMethod: "jit",
128
+ firstLoginAt: now,
129
+ lastLoginAt: now,
130
+ createdAt: now,
131
+ updatedAt: now
132
+ });
133
+ await txEm.persistAndFlush(identity);
134
+ void emitSsoEvent("sso.identity.created", {
135
+ id: identity.id,
136
+ tenantId,
137
+ organizationId: config.organizationId
138
+ }).catch((e) => console.error("[SSO Event]", e));
139
+ return { user, identity };
140
+ });
141
+ }
142
+ async assignRolesFromSso(em, user, config, tenantId, idpGroups) {
143
+ const hasMappings = config.appRoleMappings && Object.keys(config.appRoleMappings).length > 0;
144
+ if (!hasMappings) return;
145
+ await this.syncMappedRoles(em, user, config, tenantId, idpGroups);
146
+ const hasAnySsoRole = await em.findOne(SsoRoleGrant, {
147
+ userId: user.id,
148
+ ssoConfigId: config.id
149
+ });
150
+ if (!hasAnySsoRole) {
151
+ throw new Error("No roles could be resolved from IdP groups \u2014 login denied. Configure role mappings or ensure the IdP sends matching group claims.");
152
+ }
153
+ }
154
+ /**
155
+ * Sync/replace SSO-sourced roles: on each login, SSO-managed roles are replaced
156
+ * with what the IdP sends, while manually-assigned roles are preserved.
157
+ */
158
+ async syncMappedRoles(em, user, config, tenantId, idpGroups) {
159
+ const resolvedTenantId = tenantId || user.tenantId || "";
160
+ if (!resolvedTenantId) return;
161
+ const allRoles = await em.find(Role, { tenantId: resolvedTenantId, deletedAt: null });
162
+ const roleByNormalizedName = /* @__PURE__ */ new Map();
163
+ for (const role of allRoles) {
164
+ const normalized = normalizeToken(role.name);
165
+ if (normalized) roleByNormalizedName.set(normalized, role);
166
+ }
167
+ const desiredRoleNames = resolveRoleNamesFromIdpGroups(idpGroups, config.appRoleMappings);
168
+ const desiredRoleIds = /* @__PURE__ */ new Set();
169
+ for (const roleName of desiredRoleNames) {
170
+ const role = roleByNormalizedName.get(roleName);
171
+ if (role) desiredRoleIds.add(role.id);
172
+ }
173
+ const existingGrants = await em.find(SsoRoleGrant, {
174
+ userId: user.id,
175
+ ssoConfigId: config.id
176
+ });
177
+ const existingGrantedRoleIds = new Set(existingGrants.map((g) => g.roleId));
178
+ const toAdd = [...desiredRoleIds].filter((id) => !existingGrantedRoleIds.has(id));
179
+ const toRemove = existingGrants.filter((g) => !desiredRoleIds.has(g.roleId));
180
+ for (const roleId of toAdd) {
181
+ const role = allRoles.find((r) => r.id === roleId);
182
+ if (!role) continue;
183
+ await this.ensureUserRole(em, user, role);
184
+ const grant = em.create(SsoRoleGrant, {
185
+ tenantId: resolvedTenantId,
186
+ organizationId: config.organizationId,
187
+ userId: user.id,
188
+ roleId,
189
+ ssoConfigId: config.id
190
+ });
191
+ em.persist(grant);
192
+ }
193
+ for (const grant of toRemove) {
194
+ const userRole = await em.findOne(UserRole, {
195
+ user: user.id,
196
+ role: grant.roleId,
197
+ deletedAt: null
198
+ });
199
+ if (userRole) {
200
+ em.remove(userRole);
201
+ }
202
+ em.remove(grant);
203
+ }
204
+ const allUserRoles = await em.find(UserRole, { user: user.id });
205
+ for (const ur of allUserRoles) {
206
+ if (ur.deletedAt) {
207
+ em.remove(ur);
208
+ }
209
+ }
210
+ if (toAdd.length > 0 || toRemove.length > 0 || allUserRoles.some((ur) => ur.deletedAt)) {
211
+ await em.flush();
212
+ }
213
+ }
214
+ async ensureUserRole(em, user, role) {
215
+ const existingLink = await em.findOne(UserRole, {
216
+ user: user.id,
217
+ role: role.id,
218
+ deletedAt: null
219
+ });
220
+ if (existingLink) return;
221
+ const userRole = em.create(UserRole, { user, role, createdAt: /* @__PURE__ */ new Date() });
222
+ await em.persistAndFlush(userRole);
223
+ }
224
+ }
225
+ function resolveRoleNamesFromIdpGroups(idpGroups, configMappings) {
226
+ if (!Array.isArray(idpGroups) || idpGroups.length === 0) return [];
227
+ const normalizedGroups = idpGroups.map((group) => normalizeToken(group)).filter((group) => group !== null);
228
+ if (normalizedGroups.length === 0) return [];
229
+ const mergedMappings = loadMergedMappings(configMappings);
230
+ const roleNames = /* @__PURE__ */ new Set();
231
+ for (const group of normalizedGroups) {
232
+ const mapped = mergedMappings.get(group);
233
+ if (mapped?.length) {
234
+ for (const role of mapped) roleNames.add(role);
235
+ continue;
236
+ }
237
+ roleNames.add(group);
238
+ const segmented = group.split(/[\\/:]/).map((part) => normalizeToken(part)).filter((part) => part !== null);
239
+ for (const candidate of segmented) {
240
+ roleNames.add(candidate);
241
+ }
242
+ }
243
+ return Array.from(roleNames);
244
+ }
245
+ function loadMergedMappings(configMappings) {
246
+ const envMappings = loadGroupRoleMappingsFromEnv();
247
+ if (configMappings && Object.keys(configMappings).length > 0) {
248
+ for (const [group, roleName] of Object.entries(configMappings)) {
249
+ const normalizedGroup = normalizeToken(group);
250
+ if (!normalizedGroup) continue;
251
+ const normalizedRole = normalizeToken(roleName);
252
+ if (!normalizedRole) continue;
253
+ envMappings.set(normalizedGroup, [normalizedRole]);
254
+ }
255
+ }
256
+ return envMappings;
257
+ }
258
+ function loadGroupRoleMappingsFromEnv() {
259
+ const raw = process.env.SSO_GROUP_ROLE_MAP;
260
+ if (!raw) return /* @__PURE__ */ new Map();
261
+ try {
262
+ const parsed = JSON.parse(raw);
263
+ const out = /* @__PURE__ */ new Map();
264
+ for (const [group, roleValue] of Object.entries(parsed)) {
265
+ const normalizedGroup = normalizeToken(group);
266
+ if (!normalizedGroup) continue;
267
+ const roles = normalizeRoleList(roleValue);
268
+ if (roles.length > 0) out.set(normalizedGroup, roles);
269
+ }
270
+ return out;
271
+ } catch {
272
+ return /* @__PURE__ */ new Map();
273
+ }
274
+ }
275
+ function normalizeRoleList(value) {
276
+ if (typeof value === "string") {
277
+ const token = normalizeToken(value);
278
+ return token ? [token] : [];
279
+ }
280
+ if (Array.isArray(value)) {
281
+ const out = /* @__PURE__ */ new Set();
282
+ for (const entry of value) {
283
+ const token = normalizeToken(entry);
284
+ if (token) out.add(token);
285
+ }
286
+ return Array.from(out);
287
+ }
288
+ return [];
289
+ }
290
+ function normalizeToken(value) {
291
+ if (typeof value !== "string") return null;
292
+ const normalized = value.trim().toLowerCase();
293
+ return normalized.length > 0 ? normalized : null;
294
+ }
295
+ export {
296
+ AccountLinkingService
297
+ };
298
+ //# sourceMappingURL=accountLinkingService.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/sso/services/accountLinkingService.ts"],
4
+ "sourcesContent": ["import { EntityManager, type FilterQuery, type RequiredEntityData } from '@mikro-orm/postgresql'\nimport { User, UserRole, Role } from '@open-mercato/core/modules/auth/data/entities'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { computeEmailHash } from '@open-mercato/core/modules/auth/lib/emailHash'\nimport { SsoConfig, SsoIdentity, SsoRoleGrant, ScimToken } from '../data/entities'\nimport { emitSsoEvent } from '../events'\nimport type { SsoIdentityPayload } from '../lib/types'\n\nexport class AccountLinkingService {\n constructor(private em: EntityManager) {}\n\n async resolveUser(\n config: SsoConfig,\n idpPayload: SsoIdentityPayload,\n tenantId: string,\n ): Promise<{ user: User; identity: SsoIdentity }> {\n const existing = await this.findExistingLink(config.id, idpPayload.subject, tenantId, config.organizationId)\n if (existing) {\n await this.assignRolesFromSso(this.em, existing.user, config, tenantId, idpPayload.groups)\n return existing\n }\n\n if (idpPayload.emailVerified === false) {\n throw new Error('IdP explicitly reported email as unverified \u2014 cannot link or provision account')\n }\n\n const emailDomain = idpPayload.email.split('@')[1]?.toLowerCase()\n if (!emailDomain || !config.allowedDomains.some((d) => d.toLowerCase() === emailDomain)) {\n throw new Error('Email domain is not in the allowed domains for this SSO configuration')\n }\n\n const emailLinked = config.autoLinkByEmail\n ? await this.linkByEmail(config, idpPayload, tenantId)\n : null\n if (emailLinked) {\n await this.assignRolesFromSso(this.em, emailLinked.user, config, tenantId, idpPayload.groups)\n return emailLinked\n }\n\n if (config.jitEnabled) {\n const scimActive = await this.em.count(ScimToken, { ssoConfigId: config.id, isActive: true }) > 0\n if (scimActive) {\n throw new Error('JIT provisioning is disabled because SCIM directory sync is active')\n }\n return this.jitProvision(config, idpPayload, tenantId)\n }\n\n throw new Error('No matching user found and JIT provisioning is disabled')\n }\n\n private async findExistingLink(\n ssoConfigId: string,\n idpSubject: string,\n tenantId: string,\n organizationId: string,\n ): Promise<{ user: User; identity: SsoIdentity } | null> {\n const identity = await findOneWithDecryption(\n this.em,\n SsoIdentity,\n { ssoConfigId, idpSubject, deletedAt: null },\n {},\n { tenantId, organizationId },\n )\n if (!identity) return null\n\n const user = await findOneWithDecryption(\n this.em,\n User,\n { id: identity.userId, deletedAt: null },\n {},\n { tenantId, organizationId },\n )\n if (!user) {\n identity.deletedAt = new Date()\n await this.em.flush()\n return null\n }\n\n identity.lastLoginAt = new Date()\n await this.em.flush()\n\n return { user, identity }\n }\n\n private async linkByEmail(\n config: SsoConfig,\n idpPayload: SsoIdentityPayload,\n tenantId: string,\n ): Promise<{ user: User; identity: SsoIdentity } | null> {\n const emailHash = computeEmailHash(idpPayload.email)\n const user = await findOneWithDecryption(\n this.em,\n User,\n {\n organizationId: config.organizationId,\n deletedAt: null,\n $or: [\n { email: idpPayload.email },\n { emailHash },\n ],\n } as FilterQuery<User>,\n {},\n { tenantId, organizationId: config.organizationId },\n )\n if (!user) return null\n\n const now = new Date()\n const identity = this.em.create(SsoIdentity, {\n tenantId,\n organizationId: config.organizationId,\n ssoConfigId: config.id,\n userId: user.id,\n idpSubject: idpPayload.subject,\n idpEmail: idpPayload.email,\n idpName: idpPayload.name ?? null,\n idpGroups: idpPayload.groups ?? [],\n provisioningMethod: 'manual',\n firstLoginAt: now,\n lastLoginAt: now,\n createdAt: now,\n updatedAt: now,\n } as RequiredEntityData<SsoIdentity>)\n await this.em.persistAndFlush(identity)\n\n void emitSsoEvent('sso.identity.linked', {\n id: identity.id,\n tenantId,\n organizationId: config.organizationId,\n }).catch((e) => console.error('[SSO Event]', e))\n\n return { user, identity }\n }\n\n private async jitProvision(\n config: SsoConfig,\n idpPayload: SsoIdentityPayload,\n tenantId: string,\n ): Promise<{ user: User; identity: SsoIdentity }> {\n return this.em.transactional(async (txEm) => {\n const user = txEm.create(User, {\n tenantId,\n organizationId: config.organizationId,\n email: idpPayload.email,\n emailHash: computeEmailHash(idpPayload.email),\n name: idpPayload.name ?? null,\n passwordHash: null,\n isConfirmed: true,\n createdAt: new Date(),\n })\n await txEm.persistAndFlush(user)\n\n await this.assignRolesFromSso(txEm, user, config, tenantId, idpPayload.groups)\n\n const now = new Date()\n const identity = txEm.create(SsoIdentity, {\n tenantId,\n organizationId: config.organizationId,\n ssoConfigId: config.id,\n userId: user.id,\n idpSubject: idpPayload.subject,\n idpEmail: idpPayload.email,\n idpName: idpPayload.name ?? null,\n idpGroups: idpPayload.groups ?? [],\n provisioningMethod: 'jit',\n firstLoginAt: now,\n lastLoginAt: now,\n createdAt: now,\n updatedAt: now,\n } as RequiredEntityData<SsoIdentity>)\n await txEm.persistAndFlush(identity)\n\n void emitSsoEvent('sso.identity.created', {\n id: identity.id,\n tenantId,\n organizationId: config.organizationId,\n }).catch((e) => console.error('[SSO Event]', e))\n\n return { user, identity }\n })\n }\n\n private async assignRolesFromSso(\n em: EntityManager,\n user: User,\n config: SsoConfig,\n tenantId: string,\n idpGroups?: string[],\n ): Promise<void> {\n const hasMappings = config.appRoleMappings && Object.keys(config.appRoleMappings).length > 0\n if (!hasMappings) return\n\n await this.syncMappedRoles(em, user, config, tenantId, idpGroups)\n\n const hasAnySsoRole = await em.findOne(SsoRoleGrant, {\n userId: user.id,\n ssoConfigId: config.id,\n })\n if (!hasAnySsoRole) {\n throw new Error('No roles could be resolved from IdP groups \u2014 login denied. Configure role mappings or ensure the IdP sends matching group claims.')\n }\n }\n\n /**\n * Sync/replace SSO-sourced roles: on each login, SSO-managed roles are replaced\n * with what the IdP sends, while manually-assigned roles are preserved.\n */\n private async syncMappedRoles(\n em: EntityManager,\n user: User,\n config: SsoConfig,\n tenantId: string,\n idpGroups?: string[],\n ): Promise<void> {\n const resolvedTenantId = tenantId || user.tenantId || ''\n if (!resolvedTenantId) return\n\n const allRoles = await em.find(Role, { tenantId: resolvedTenantId, deletedAt: null } as FilterQuery<Role>)\n const roleByNormalizedName = new Map<string, Role>()\n for (const role of allRoles) {\n const normalized = normalizeToken(role.name)\n if (normalized) roleByNormalizedName.set(normalized, role)\n }\n\n // Resolve desired role IDs from IdP groups using merged mappings\n const desiredRoleNames = resolveRoleNamesFromIdpGroups(idpGroups, config.appRoleMappings)\n const desiredRoleIds = new Set<string>()\n for (const roleName of desiredRoleNames) {\n const role = roleByNormalizedName.get(roleName)\n if (role) desiredRoleIds.add(role.id)\n }\n\n // Query current SSO grants for this user+config\n const existingGrants = await em.find(SsoRoleGrant, {\n userId: user.id,\n ssoConfigId: config.id,\n })\n const existingGrantedRoleIds = new Set(existingGrants.map((g) => g.roleId))\n\n // Compute diff\n const toAdd = [...desiredRoleIds].filter((id) => !existingGrantedRoleIds.has(id))\n const toRemove = existingGrants.filter((g) => !desiredRoleIds.has(g.roleId))\n\n // Add new roles\n for (const roleId of toAdd) {\n const role = allRoles.find((r) => r.id === roleId)\n if (!role) continue\n await this.ensureUserRole(em, user, role)\n const grant = em.create(SsoRoleGrant, {\n tenantId: resolvedTenantId,\n organizationId: config.organizationId,\n userId: user.id,\n roleId,\n ssoConfigId: config.id,\n } as RequiredEntityData<SsoRoleGrant>)\n em.persist(grant)\n }\n\n // Remove stale SSO-sourced roles\n for (const grant of toRemove) {\n const userRole = await em.findOne(UserRole, {\n user: user.id,\n role: grant.roleId,\n deletedAt: null,\n } as FilterQuery<UserRole>)\n if (userRole) {\n em.remove(userRole)\n }\n em.remove(grant)\n }\n\n // Clean up orphaned soft-deleted UserRole rows (ghost rows from previous soft-delete logic)\n const allUserRoles = await em.find(UserRole, { user: user.id } as FilterQuery<UserRole>)\n for (const ur of allUserRoles) {\n if (ur.deletedAt) {\n em.remove(ur)\n }\n }\n\n if (toAdd.length > 0 || toRemove.length > 0 || allUserRoles.some((ur) => ur.deletedAt)) {\n await em.flush()\n }\n }\n\n private async ensureUserRole(em: EntityManager, user: User, role: Role): Promise<void> {\n const existingLink = await em.findOne(UserRole, {\n user: user.id,\n role: role.id,\n deletedAt: null,\n } as FilterQuery<UserRole>)\n if (existingLink) return\n\n const userRole = em.create(UserRole, { user, role, createdAt: new Date() })\n await em.persistAndFlush(userRole)\n }\n}\n\nfunction resolveRoleNamesFromIdpGroups(\n idpGroups?: string[],\n configMappings?: Record<string, string>,\n): string[] {\n if (!Array.isArray(idpGroups) || idpGroups.length === 0) return []\n\n const normalizedGroups = idpGroups\n .map((group) => normalizeToken(group))\n .filter((group): group is string => group !== null)\n if (normalizedGroups.length === 0) return []\n\n const mergedMappings = loadMergedMappings(configMappings)\n const roleNames = new Set<string>()\n\n for (const group of normalizedGroups) {\n const mapped = mergedMappings.get(group)\n if (mapped?.length) {\n for (const role of mapped) roleNames.add(role)\n continue\n }\n\n roleNames.add(group)\n const segmented = group.split(/[\\\\/:]/).map((part) => normalizeToken(part)).filter((part): part is string => part !== null)\n for (const candidate of segmented) {\n roleNames.add(candidate)\n }\n }\n\n return Array.from(roleNames)\n}\n\nfunction loadMergedMappings(configMappings?: Record<string, string>): Map<string, string[]> {\n const envMappings = loadGroupRoleMappingsFromEnv()\n\n // Per-config mappings take precedence over env var\n if (configMappings && Object.keys(configMappings).length > 0) {\n for (const [group, roleName] of Object.entries(configMappings)) {\n const normalizedGroup = normalizeToken(group)\n if (!normalizedGroup) continue\n const normalizedRole = normalizeToken(roleName)\n if (!normalizedRole) continue\n envMappings.set(normalizedGroup, [normalizedRole])\n }\n }\n\n return envMappings\n}\n\nfunction loadGroupRoleMappingsFromEnv(): Map<string, string[]> {\n const raw = process.env.SSO_GROUP_ROLE_MAP\n if (!raw) return new Map()\n\n try {\n const parsed = JSON.parse(raw) as Record<string, unknown>\n const out = new Map<string, string[]>()\n for (const [group, roleValue] of Object.entries(parsed)) {\n const normalizedGroup = normalizeToken(group)\n if (!normalizedGroup) continue\n const roles = normalizeRoleList(roleValue)\n if (roles.length > 0) out.set(normalizedGroup, roles)\n }\n return out\n } catch {\n return new Map()\n }\n}\n\nfunction normalizeRoleList(value: unknown): string[] {\n if (typeof value === 'string') {\n const token = normalizeToken(value)\n return token ? [token] : []\n }\n\n if (Array.isArray(value)) {\n const out = new Set<string>()\n for (const entry of value) {\n const token = normalizeToken(entry)\n if (token) out.add(token)\n }\n return Array.from(out)\n }\n\n return []\n}\n\nfunction normalizeToken(value: unknown): string | null {\n if (typeof value !== 'string') return null\n const normalized = value.trim().toLowerCase()\n return normalized.length > 0 ? normalized : null\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,18 @@
1
+ import { SsoConfig } from "../data/entities.js";
2
+ class HrdService {
3
+ constructor(em) {
4
+ this.em = em;
5
+ }
6
+ async findActiveConfigByEmailDomain(email) {
7
+ const domain = email.split("@")[1]?.toLowerCase();
8
+ if (!domain) return null;
9
+ const knex = this.em.getKnex();
10
+ const row = await knex("sso_configs").whereRaw("allowed_domains @> ?::jsonb", [JSON.stringify([domain])]).where("is_active", true).whereNull("deleted_at").first();
11
+ if (!row) return null;
12
+ return this.em.map(SsoConfig, row);
13
+ }
14
+ }
15
+ export {
16
+ HrdService
17
+ };
18
+ //# sourceMappingURL=hrdService.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/sso/services/hrdService.ts"],
4
+ "sourcesContent": ["import { EntityManager } from '@mikro-orm/postgresql'\nimport { SsoConfig } from '../data/entities'\n\nexport class HrdService {\n constructor(private em: EntityManager) {}\n\n async findActiveConfigByEmailDomain(email: string): Promise<SsoConfig | null> {\n const domain = email.split('@')[1]?.toLowerCase()\n if (!domain) return null\n\n const knex = this.em.getKnex()\n const row = await knex('sso_configs')\n .whereRaw(\"allowed_domains @> ?::jsonb\", [JSON.stringify([domain])])\n .where('is_active', true)\n .whereNull('deleted_at')\n .first()\n\n if (!row) return null\n\n return this.em.map(SsoConfig, row)\n }\n}\n"],
5
+ "mappings": "AACA,SAAS,iBAAiB;AAEnB,MAAM,WAAW;AAAA,EACtB,YAAoB,IAAmB;AAAnB;AAAA,EAAoB;AAAA,EAExC,MAAM,8BAA8B,OAA0C;AAC5E,UAAM,SAAS,MAAM,MAAM,GAAG,EAAE,CAAC,GAAG,YAAY;AAChD,QAAI,CAAC,OAAQ,QAAO;AAEpB,UAAM,OAAO,KAAK,GAAG,QAAQ;AAC7B,UAAM,MAAM,MAAM,KAAK,aAAa,EACjC,SAAS,+BAA+B,CAAC,KAAK,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAClE,MAAM,aAAa,IAAI,EACvB,UAAU,YAAY,EACtB,MAAM;AAET,QAAI,CAAC,IAAK,QAAO;AAEjB,WAAO,KAAK,GAAG,IAAI,WAAW,GAAG;AAAA,EACnC;AACF;",
6
+ "names": []
7
+ }