@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,372 @@
1
+ import { User, Session } from "@open-mercato/core/modules/auth/data/entities";
2
+ import { computeEmailHash } from "@open-mercato/core/modules/auth/lib/emailHash";
3
+ import { findOneWithDecryption, findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
4
+ import { SsoIdentity, SsoUserDeactivation, ScimProvisioningLog } from "../data/entities.js";
5
+ import { toScimUserResource, fromScimUserPayload } from "../lib/scim-mapper.js";
6
+ import { coerceBoolean } from "../lib/scim-utils.js";
7
+ import { parseScimFilter, scimFilterToWhere } from "../lib/scim-filter.js";
8
+ import { buildListResponse } from "../lib/scim-response.js";
9
+ class ScimService {
10
+ constructor(em) {
11
+ this.em = em;
12
+ }
13
+ async createUser(payload, scope, baseUrl) {
14
+ const parsed = fromScimUserPayload(payload);
15
+ const email = parsed.email ?? parsed.userName;
16
+ if (!email) {
17
+ throw new ScimServiceError(400, "userName or emails[0].value is required");
18
+ }
19
+ if (parsed.externalId) {
20
+ const existingIdentity = await this.em.findOne(SsoIdentity, {
21
+ ssoConfigId: scope.ssoConfigId,
22
+ externalId: parsed.externalId,
23
+ deletedAt: null
24
+ });
25
+ if (existingIdentity) {
26
+ const existingUser2 = await findOneWithDecryption(
27
+ this.em,
28
+ User,
29
+ { id: existingIdentity.userId, deletedAt: null },
30
+ {},
31
+ { tenantId: scope.tenantId ?? "", organizationId: scope.organizationId }
32
+ );
33
+ if (existingUser2) {
34
+ const deactivation = await this.em.findOne(SsoUserDeactivation, {
35
+ userId: existingUser2.id,
36
+ ssoConfigId: scope.ssoConfigId
37
+ });
38
+ await this.log(scope, "CREATE", existingIdentity.id, parsed.externalId, 200);
39
+ return {
40
+ resource: toScimUserResource(existingUser2, existingIdentity, baseUrl, deactivation),
41
+ status: 200
42
+ };
43
+ }
44
+ }
45
+ }
46
+ const emailHash = computeEmailHash(email);
47
+ const where = {
48
+ organizationId: scope.organizationId,
49
+ deletedAt: null,
50
+ $or: [{ email }, { emailHash }]
51
+ };
52
+ const existingUser = await findOneWithDecryption(
53
+ this.em,
54
+ User,
55
+ where,
56
+ {},
57
+ { tenantId: scope.tenantId ?? "", organizationId: scope.organizationId }
58
+ );
59
+ if (existingUser) {
60
+ const existingLink = await this.em.findOne(SsoIdentity, {
61
+ ssoConfigId: scope.ssoConfigId,
62
+ userId: existingUser.id,
63
+ deletedAt: null
64
+ });
65
+ if (existingLink) {
66
+ throw new ScimServiceError(409, `User with email ${email} is already linked to this SSO configuration`);
67
+ }
68
+ const now = /* @__PURE__ */ new Date();
69
+ const identity = this.em.create(SsoIdentity, {
70
+ tenantId: scope.tenantId ?? null,
71
+ organizationId: scope.organizationId,
72
+ ssoConfigId: scope.ssoConfigId,
73
+ userId: existingUser.id,
74
+ idpSubject: parsed.externalId ?? email,
75
+ idpEmail: email,
76
+ idpName: buildDisplayName(parsed),
77
+ idpGroups: [],
78
+ externalId: parsed.externalId ?? null,
79
+ provisioningMethod: "scim",
80
+ createdAt: now,
81
+ updatedAt: now
82
+ });
83
+ await this.em.persistAndFlush(identity);
84
+ const deactivation = parsed.active === false ? await this.createDeactivation(existingUser.id, scope) : null;
85
+ await this.log(scope, "CREATE", identity.id, parsed.externalId, 201);
86
+ return {
87
+ resource: toScimUserResource(existingUser, identity, baseUrl, deactivation),
88
+ status: 201
89
+ };
90
+ }
91
+ return this.em.transactional(async (txEm) => {
92
+ const user = txEm.create(User, {
93
+ tenantId: scope.tenantId ?? null,
94
+ organizationId: scope.organizationId,
95
+ email,
96
+ emailHash: computeEmailHash(email),
97
+ name: buildDisplayName(parsed) ?? void 0,
98
+ passwordHash: null,
99
+ isConfirmed: true,
100
+ createdAt: /* @__PURE__ */ new Date()
101
+ });
102
+ await txEm.persistAndFlush(user);
103
+ const now = /* @__PURE__ */ new Date();
104
+ const identity = txEm.create(SsoIdentity, {
105
+ tenantId: scope.tenantId ?? null,
106
+ organizationId: scope.organizationId,
107
+ ssoConfigId: scope.ssoConfigId,
108
+ userId: user.id,
109
+ idpSubject: parsed.externalId ?? email,
110
+ idpEmail: email,
111
+ idpName: buildDisplayName(parsed),
112
+ idpGroups: [],
113
+ externalId: parsed.externalId ?? null,
114
+ provisioningMethod: "scim",
115
+ createdAt: now,
116
+ updatedAt: now
117
+ });
118
+ await txEm.persistAndFlush(identity);
119
+ const deactivation = parsed.active === false ? await this.createDeactivationTx(txEm, user.id, scope) : null;
120
+ await this.logTx(txEm, scope, "CREATE", identity.id, parsed.externalId, 201);
121
+ return {
122
+ resource: toScimUserResource(user, identity, baseUrl, deactivation),
123
+ status: 201
124
+ };
125
+ });
126
+ }
127
+ async getUser(scimId, scope, baseUrl) {
128
+ const identity = await this.em.findOne(SsoIdentity, {
129
+ id: scimId,
130
+ ssoConfigId: scope.ssoConfigId,
131
+ organizationId: scope.organizationId,
132
+ deletedAt: null
133
+ });
134
+ if (!identity) throw new ScimServiceError(404, "User not found");
135
+ const user = await findOneWithDecryption(
136
+ this.em,
137
+ User,
138
+ { id: identity.userId, deletedAt: null },
139
+ {},
140
+ { tenantId: scope.tenantId ?? "", organizationId: scope.organizationId }
141
+ );
142
+ if (!user) throw new ScimServiceError(404, "User not found");
143
+ const deactivation = await this.em.findOne(SsoUserDeactivation, {
144
+ userId: user.id,
145
+ ssoConfigId: scope.ssoConfigId
146
+ });
147
+ return toScimUserResource(user, identity, baseUrl, deactivation);
148
+ }
149
+ async listUsers(filter, startIndex, count, scope, baseUrl) {
150
+ const conditions = parseScimFilter(filter);
151
+ const where = scimFilterToWhere(conditions, scope.ssoConfigId, scope.organizationId);
152
+ const offset = Math.max(0, startIndex - 1);
153
+ const [identities, total] = await this.em.findAndCount(SsoIdentity, where, {
154
+ orderBy: { createdAt: "asc" },
155
+ limit: count,
156
+ offset
157
+ });
158
+ const userIds = identities.map((i) => i.userId);
159
+ const users = userIds.length > 0 ? await findWithDecryption(
160
+ this.em,
161
+ User,
162
+ { id: { $in: userIds }, deletedAt: null },
163
+ {},
164
+ { tenantId: scope.tenantId ?? "", organizationId: scope.organizationId }
165
+ ) : [];
166
+ const userMap = new Map(users.map((u) => [u.id, u]));
167
+ const deactivations = userIds.length > 0 ? await this.em.find(SsoUserDeactivation, {
168
+ userId: { $in: userIds },
169
+ ssoConfigId: scope.ssoConfigId
170
+ }) : [];
171
+ const deactivationMap = new Map(deactivations.map((d) => [d.userId, d]));
172
+ const resources = [];
173
+ for (const identity of identities) {
174
+ const user = userMap.get(identity.userId);
175
+ if (!user) continue;
176
+ const deactivation = deactivationMap.get(user.id) ?? null;
177
+ resources.push(toScimUserResource(user, identity, baseUrl, deactivation));
178
+ }
179
+ return buildListResponse(resources, total, startIndex, resources.length);
180
+ }
181
+ async patchUser(scimId, operations, scope, baseUrl) {
182
+ const identity = await this.em.findOne(SsoIdentity, {
183
+ id: scimId,
184
+ ssoConfigId: scope.ssoConfigId,
185
+ organizationId: scope.organizationId,
186
+ deletedAt: null
187
+ });
188
+ if (!identity) throw new ScimServiceError(404, "User not found");
189
+ const user = await findOneWithDecryption(
190
+ this.em,
191
+ User,
192
+ { id: identity.userId, deletedAt: null },
193
+ {},
194
+ { tenantId: scope.tenantId ?? "", organizationId: scope.organizationId }
195
+ );
196
+ if (!user) throw new ScimServiceError(404, "User not found");
197
+ for (const op of operations) {
198
+ const normalizedOp = op.op.toLowerCase();
199
+ if (normalizedOp === "replace" || normalizedOp === "add") {
200
+ this.applyPatchValue(user, identity, op.path, op.value);
201
+ }
202
+ if (normalizedOp === "remove" && op.path) {
203
+ this.applyPatchValue(user, identity, op.path, null);
204
+ }
205
+ }
206
+ const activeOp = operations.find(
207
+ (op) => op.path?.toLowerCase() === "active" || !op.path && op.value && typeof op.value === "object" && "active" in op.value
208
+ );
209
+ if (activeOp) {
210
+ const activeValue = activeOp.path ? coerceBoolean(activeOp.value) : coerceBoolean(activeOp.value.active);
211
+ if (activeValue === false) {
212
+ await this.deactivateUser(user.id, scope);
213
+ } else if (activeValue === true) {
214
+ await this.reactivateUser(user.id, scope);
215
+ }
216
+ }
217
+ await this.em.flush();
218
+ const deactivation = await this.em.findOne(SsoUserDeactivation, {
219
+ userId: user.id,
220
+ ssoConfigId: scope.ssoConfigId
221
+ });
222
+ await this.log(scope, "PATCH", identity.id, identity.externalId, 200);
223
+ return toScimUserResource(user, identity, baseUrl, deactivation);
224
+ }
225
+ async deleteUser(scimId, scope) {
226
+ const identity = await this.em.findOne(SsoIdentity, {
227
+ id: scimId,
228
+ ssoConfigId: scope.ssoConfigId,
229
+ organizationId: scope.organizationId,
230
+ deletedAt: null
231
+ });
232
+ if (!identity) throw new ScimServiceError(404, "User not found");
233
+ await this.deactivateUser(identity.userId, scope);
234
+ await this.log(scope, "DELETE", identity.id, identity.externalId, 204);
235
+ }
236
+ applyPatchValue(user, identity, path, value) {
237
+ if (!path) {
238
+ if (value && typeof value === "object") {
239
+ const obj = value;
240
+ for (const [key, val] of Object.entries(obj)) {
241
+ this.applyPatchValue(user, identity, key, val);
242
+ }
243
+ }
244
+ return;
245
+ }
246
+ const normalizedPath = path.toLowerCase();
247
+ switch (normalizedPath) {
248
+ case "displayname":
249
+ user.name = value || void 0;
250
+ identity.idpName = value ?? null;
251
+ break;
252
+ case "name.givenname": {
253
+ const currentParts = (user.name ?? "").split(" ");
254
+ currentParts[0] = value ?? "";
255
+ user.name = currentParts.join(" ").trim() || void 0;
256
+ break;
257
+ }
258
+ case "name.familyname": {
259
+ const currentParts = (user.name ?? "").split(" ");
260
+ const given = currentParts[0] ?? "";
261
+ user.name = value ? `${given} ${value}`.trim() : given || void 0;
262
+ break;
263
+ }
264
+ case "username":
265
+ identity.idpEmail = value ?? identity.idpEmail;
266
+ break;
267
+ case "externalid":
268
+ identity.externalId = value ?? null;
269
+ break;
270
+ case "active":
271
+ break;
272
+ }
273
+ }
274
+ async deactivateUser(userId, scope) {
275
+ let deactivation = await this.em.findOne(SsoUserDeactivation, {
276
+ userId,
277
+ ssoConfigId: scope.ssoConfigId
278
+ });
279
+ if (deactivation) {
280
+ deactivation.deactivatedAt = /* @__PURE__ */ new Date();
281
+ deactivation.reactivatedAt = null;
282
+ } else {
283
+ deactivation = this.em.create(SsoUserDeactivation, {
284
+ tenantId: scope.tenantId ?? null,
285
+ organizationId: scope.organizationId,
286
+ userId,
287
+ ssoConfigId: scope.ssoConfigId,
288
+ deactivatedAt: /* @__PURE__ */ new Date()
289
+ });
290
+ this.em.persist(deactivation);
291
+ }
292
+ await this.em.flush();
293
+ const sessionWhere = { user: userId };
294
+ await this.em.nativeDelete(Session, sessionWhere);
295
+ }
296
+ async reactivateUser(userId, scope) {
297
+ const deactivation = await this.em.findOne(SsoUserDeactivation, {
298
+ userId,
299
+ ssoConfigId: scope.ssoConfigId
300
+ });
301
+ if (deactivation && !deactivation.reactivatedAt) {
302
+ deactivation.reactivatedAt = /* @__PURE__ */ new Date();
303
+ await this.em.flush();
304
+ }
305
+ }
306
+ async createDeactivation(userId, scope) {
307
+ const deactivation = this.em.create(SsoUserDeactivation, {
308
+ tenantId: scope.tenantId ?? null,
309
+ organizationId: scope.organizationId,
310
+ userId,
311
+ ssoConfigId: scope.ssoConfigId,
312
+ deactivatedAt: /* @__PURE__ */ new Date()
313
+ });
314
+ await this.em.persistAndFlush(deactivation);
315
+ return deactivation;
316
+ }
317
+ async createDeactivationTx(txEm, userId, scope) {
318
+ const deactivation = txEm.create(SsoUserDeactivation, {
319
+ tenantId: scope.tenantId ?? null,
320
+ organizationId: scope.organizationId,
321
+ userId,
322
+ ssoConfigId: scope.ssoConfigId,
323
+ deactivatedAt: /* @__PURE__ */ new Date()
324
+ });
325
+ await txEm.persistAndFlush(deactivation);
326
+ return deactivation;
327
+ }
328
+ async log(scope, operation, resourceId, externalId, responseStatus, errorMessage) {
329
+ const entry = this.em.create(ScimProvisioningLog, {
330
+ tenantId: scope.tenantId ?? null,
331
+ organizationId: scope.organizationId,
332
+ ssoConfigId: scope.ssoConfigId,
333
+ operation,
334
+ resourceType: "User",
335
+ resourceId: resourceId ?? null,
336
+ scimExternalId: externalId ?? null,
337
+ responseStatus,
338
+ errorMessage: errorMessage ?? null
339
+ });
340
+ await this.em.persistAndFlush(entry);
341
+ }
342
+ async logTx(txEm, scope, operation, resourceId, externalId, responseStatus) {
343
+ const entry = txEm.create(ScimProvisioningLog, {
344
+ tenantId: scope.tenantId ?? null,
345
+ organizationId: scope.organizationId,
346
+ ssoConfigId: scope.ssoConfigId,
347
+ operation,
348
+ resourceType: "User",
349
+ resourceId: resourceId ?? null,
350
+ scimExternalId: externalId ?? null,
351
+ responseStatus
352
+ });
353
+ await txEm.persistAndFlush(entry);
354
+ }
355
+ }
356
+ function buildDisplayName(parsed) {
357
+ if (parsed.displayName) return parsed.displayName;
358
+ const parts = [parsed.givenName, parsed.familyName].filter(Boolean);
359
+ return parts.length > 0 ? parts.join(" ") : null;
360
+ }
361
+ class ScimServiceError extends Error {
362
+ constructor(statusCode, message) {
363
+ super(message);
364
+ this.statusCode = statusCode;
365
+ this.name = "ScimServiceError";
366
+ }
367
+ }
368
+ export {
369
+ ScimService,
370
+ ScimServiceError
371
+ };
372
+ //# sourceMappingURL=scimService.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/sso/services/scimService.ts"],
4
+ "sourcesContent": ["import { EntityManager, type FilterQuery, type RequiredEntityData } from '@mikro-orm/postgresql'\nimport { User, Session } from '@open-mercato/core/modules/auth/data/entities'\nimport { computeEmailHash } from '@open-mercato/core/modules/auth/lib/emailHash'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { SsoIdentity, SsoUserDeactivation, ScimProvisioningLog } from '../data/entities'\nimport { toScimUserResource, fromScimUserPayload, type ScimUserResource, type ScimUserPayload } from '../lib/scim-mapper'\nimport { coerceBoolean } from '../lib/scim-utils'\nimport { parseScimFilter, scimFilterToWhere } from '../lib/scim-filter'\nimport { buildListResponse } from '../lib/scim-response'\nimport type { ScimScope } from '../api/scim/context'\nimport type { ScimPatchOperation } from '../lib/scim-patch'\n\nexport class ScimService {\n constructor(private em: EntityManager) {}\n\n async createUser(\n payload: Record<string, unknown>,\n scope: ScimScope,\n baseUrl: string,\n ): Promise<{ resource: ScimUserResource; status: number }> {\n const parsed = fromScimUserPayload(payload)\n const email = parsed.email ?? parsed.userName\n if (!email) {\n throw new ScimServiceError(400, 'userName or emails[0].value is required')\n }\n\n // Idempotency: if externalId already exists for this config, return existing\n if (parsed.externalId) {\n const existingIdentity = await this.em.findOne(SsoIdentity, {\n ssoConfigId: scope.ssoConfigId,\n externalId: parsed.externalId,\n deletedAt: null,\n })\n if (existingIdentity) {\n const existingUser = await findOneWithDecryption(\n this.em, User,\n { id: existingIdentity.userId, deletedAt: null },\n {},\n { tenantId: scope.tenantId ?? '', organizationId: scope.organizationId },\n )\n if (existingUser) {\n const deactivation = await this.em.findOne(SsoUserDeactivation, {\n userId: existingUser.id, ssoConfigId: scope.ssoConfigId,\n })\n await this.log(scope, 'CREATE', existingIdentity.id, parsed.externalId, 200)\n return {\n resource: toScimUserResource(existingUser, existingIdentity, baseUrl, deactivation),\n status: 200,\n }\n }\n }\n }\n\n // Check if user already exists by email\n const emailHash = computeEmailHash(email)\n const where: FilterQuery<User> = {\n organizationId: scope.organizationId,\n deletedAt: null,\n $or: [{ email }, { emailHash }],\n }\n const existingUser = await findOneWithDecryption(\n this.em, User,\n where,\n {},\n { tenantId: scope.tenantId ?? '', organizationId: scope.organizationId },\n )\n\n if (existingUser) {\n // Check if already linked to this SSO config\n const existingLink = await this.em.findOne(SsoIdentity, {\n ssoConfigId: scope.ssoConfigId,\n userId: existingUser.id,\n deletedAt: null,\n })\n if (existingLink) {\n throw new ScimServiceError(409, `User with email ${email} is already linked to this SSO configuration`)\n }\n\n // Auto-link: create SsoIdentity for existing user\n const now = new Date()\n const identity = this.em.create(SsoIdentity, {\n tenantId: scope.tenantId ?? null,\n organizationId: scope.organizationId,\n ssoConfigId: scope.ssoConfigId,\n userId: existingUser.id,\n idpSubject: parsed.externalId ?? email,\n idpEmail: email,\n idpName: buildDisplayName(parsed),\n idpGroups: [],\n externalId: parsed.externalId ?? null,\n provisioningMethod: 'scim',\n createdAt: now,\n updatedAt: now,\n } as RequiredEntityData<SsoIdentity>)\n await this.em.persistAndFlush(identity)\n\n const deactivation = parsed.active === false\n ? await this.createDeactivation(existingUser.id, scope)\n : null\n\n await this.log(scope, 'CREATE', identity.id, parsed.externalId, 201)\n return {\n resource: toScimUserResource(existingUser, identity, baseUrl, deactivation),\n status: 201,\n }\n }\n\n // Create new user + identity\n return this.em.transactional(async (txEm) => {\n const user = txEm.create(User, {\n tenantId: scope.tenantId ?? null,\n organizationId: scope.organizationId,\n email,\n emailHash: computeEmailHash(email),\n name: buildDisplayName(parsed) ?? undefined,\n passwordHash: null,\n isConfirmed: true,\n createdAt: new Date(),\n })\n await txEm.persistAndFlush(user)\n\n const now = new Date()\n const identity = txEm.create(SsoIdentity, {\n tenantId: scope.tenantId ?? null,\n organizationId: scope.organizationId,\n ssoConfigId: scope.ssoConfigId,\n userId: user.id,\n idpSubject: parsed.externalId ?? email,\n idpEmail: email,\n idpName: buildDisplayName(parsed),\n idpGroups: [],\n externalId: parsed.externalId ?? null,\n provisioningMethod: 'scim',\n createdAt: now,\n updatedAt: now,\n } as RequiredEntityData<SsoIdentity>)\n await txEm.persistAndFlush(identity)\n\n const deactivation = parsed.active === false\n ? await this.createDeactivationTx(txEm, user.id, scope)\n : null\n\n await this.logTx(txEm, scope, 'CREATE', identity.id, parsed.externalId, 201)\n return {\n resource: toScimUserResource(user, identity, baseUrl, deactivation),\n status: 201,\n }\n })\n }\n\n async getUser(scimId: string, scope: ScimScope, baseUrl: string): Promise<ScimUserResource> {\n const identity = await this.em.findOne(SsoIdentity, {\n id: scimId,\n ssoConfigId: scope.ssoConfigId,\n organizationId: scope.organizationId,\n deletedAt: null,\n })\n if (!identity) throw new ScimServiceError(404, 'User not found')\n\n const user = await findOneWithDecryption(\n this.em, User,\n { id: identity.userId, deletedAt: null },\n {},\n { tenantId: scope.tenantId ?? '', organizationId: scope.organizationId },\n )\n if (!user) throw new ScimServiceError(404, 'User not found')\n\n const deactivation = await this.em.findOne(SsoUserDeactivation, {\n userId: user.id, ssoConfigId: scope.ssoConfigId,\n })\n\n return toScimUserResource(user, identity, baseUrl, deactivation)\n }\n\n async listUsers(\n filter: string | null,\n startIndex: number,\n count: number,\n scope: ScimScope,\n baseUrl: string,\n ): Promise<Record<string, unknown>> {\n const conditions = parseScimFilter(filter)\n const where = scimFilterToWhere(conditions, scope.ssoConfigId, scope.organizationId)\n\n const offset = Math.max(0, startIndex - 1)\n const [identities, total] = await this.em.findAndCount(SsoIdentity, where, {\n orderBy: { createdAt: 'asc' },\n limit: count,\n offset,\n })\n\n const userIds = identities.map((i) => i.userId)\n\n const users = userIds.length > 0\n ? await findWithDecryption(\n this.em, User,\n { id: { $in: userIds }, deletedAt: null },\n {},\n { tenantId: scope.tenantId ?? '', organizationId: scope.organizationId },\n )\n : []\n const userMap = new Map(users.map((u) => [u.id, u]))\n\n const deactivations = userIds.length > 0\n ? await this.em.find(SsoUserDeactivation, {\n userId: { $in: userIds }, ssoConfigId: scope.ssoConfigId,\n })\n : []\n const deactivationMap = new Map(deactivations.map((d) => [d.userId, d]))\n\n const resources: ScimUserResource[] = []\n for (const identity of identities) {\n const user = userMap.get(identity.userId)\n if (!user) continue\n\n const deactivation = deactivationMap.get(user.id) ?? null\n resources.push(toScimUserResource(user, identity, baseUrl, deactivation))\n }\n\n return buildListResponse(resources, total, startIndex, resources.length)\n }\n\n async patchUser(\n scimId: string,\n operations: ScimPatchOperation[],\n scope: ScimScope,\n baseUrl: string,\n ): Promise<ScimUserResource> {\n const identity = await this.em.findOne(SsoIdentity, {\n id: scimId,\n ssoConfigId: scope.ssoConfigId,\n organizationId: scope.organizationId,\n deletedAt: null,\n })\n if (!identity) throw new ScimServiceError(404, 'User not found')\n\n const user = await findOneWithDecryption(\n this.em, User,\n { id: identity.userId, deletedAt: null },\n {},\n { tenantId: scope.tenantId ?? '', organizationId: scope.organizationId },\n )\n if (!user) throw new ScimServiceError(404, 'User not found')\n\n for (const op of operations) {\n const normalizedOp = op.op.toLowerCase()\n if (normalizedOp === 'replace' || normalizedOp === 'add') {\n this.applyPatchValue(user, identity, op.path, op.value)\n }\n // 'remove' operations on optional fields \u2014 set to null\n if (normalizedOp === 'remove' && op.path) {\n this.applyPatchValue(user, identity, op.path, null)\n }\n }\n\n // Handle active status changes\n const activeOp = operations.find((op) =>\n op.path?.toLowerCase() === 'active' ||\n (!op.path && op.value && typeof op.value === 'object' && 'active' in (op.value as Record<string, unknown>)),\n )\n\n if (activeOp) {\n const activeValue = activeOp.path\n ? coerceBoolean(activeOp.value)\n : coerceBoolean((activeOp.value as Record<string, unknown>).active)\n\n if (activeValue === false) {\n await this.deactivateUser(user.id, scope)\n } else if (activeValue === true) {\n await this.reactivateUser(user.id, scope)\n }\n }\n\n await this.em.flush()\n\n const deactivation = await this.em.findOne(SsoUserDeactivation, {\n userId: user.id, ssoConfigId: scope.ssoConfigId,\n })\n\n await this.log(scope, 'PATCH', identity.id, identity.externalId, 200)\n return toScimUserResource(user, identity, baseUrl, deactivation)\n }\n\n async deleteUser(scimId: string, scope: ScimScope): Promise<void> {\n const identity = await this.em.findOne(SsoIdentity, {\n id: scimId,\n ssoConfigId: scope.ssoConfigId,\n organizationId: scope.organizationId,\n deletedAt: null,\n })\n if (!identity) throw new ScimServiceError(404, 'User not found')\n\n await this.deactivateUser(identity.userId, scope)\n await this.log(scope, 'DELETE', identity.id, identity.externalId, 204)\n }\n\n private applyPatchValue(\n user: User,\n identity: SsoIdentity,\n path: string | undefined,\n value: unknown,\n ): void {\n if (!path) {\n // No path means value is an object with attribute keys\n if (value && typeof value === 'object') {\n const obj = value as Record<string, unknown>\n for (const [key, val] of Object.entries(obj)) {\n this.applyPatchValue(user, identity, key, val)\n }\n }\n return\n }\n\n const normalizedPath = path.toLowerCase()\n switch (normalizedPath) {\n case 'displayname':\n user.name = (value as string) || undefined\n identity.idpName = (value as string) ?? null\n break\n case 'name.givenname': {\n const currentParts = (user.name ?? '').split(' ')\n currentParts[0] = (value as string) ?? ''\n user.name = currentParts.join(' ').trim() || undefined\n break\n }\n case 'name.familyname': {\n const currentParts = (user.name ?? '').split(' ')\n const given = currentParts[0] ?? ''\n user.name = value ? `${given} ${value}`.trim() : given || undefined\n break\n }\n case 'username':\n identity.idpEmail = (value as string) ?? identity.idpEmail\n break\n case 'externalid':\n identity.externalId = (value as string) ?? null\n break\n case 'active':\n // Handled separately via deactivation logic\n break\n }\n }\n\n private async deactivateUser(userId: string, scope: ScimScope): Promise<void> {\n let deactivation = await this.em.findOne(SsoUserDeactivation, {\n userId, ssoConfigId: scope.ssoConfigId,\n })\n\n if (deactivation) {\n deactivation.deactivatedAt = new Date()\n deactivation.reactivatedAt = null\n } else {\n deactivation = this.em.create(SsoUserDeactivation, {\n tenantId: scope.tenantId ?? null,\n organizationId: scope.organizationId,\n userId,\n ssoConfigId: scope.ssoConfigId,\n deactivatedAt: new Date(),\n } as RequiredEntityData<SsoUserDeactivation>)\n this.em.persist(deactivation)\n }\n await this.em.flush()\n\n // Revoke all active sessions\n const sessionWhere: FilterQuery<Session> = { user: userId }\n await this.em.nativeDelete(Session, sessionWhere)\n }\n\n private async reactivateUser(userId: string, scope: ScimScope): Promise<void> {\n const deactivation = await this.em.findOne(SsoUserDeactivation, {\n userId, ssoConfigId: scope.ssoConfigId,\n })\n if (deactivation && !deactivation.reactivatedAt) {\n deactivation.reactivatedAt = new Date()\n await this.em.flush()\n }\n }\n\n private async createDeactivation(userId: string, scope: ScimScope): Promise<SsoUserDeactivation> {\n const deactivation = this.em.create(SsoUserDeactivation, {\n tenantId: scope.tenantId ?? null,\n organizationId: scope.organizationId,\n userId,\n ssoConfigId: scope.ssoConfigId,\n deactivatedAt: new Date(),\n } as RequiredEntityData<SsoUserDeactivation>)\n await this.em.persistAndFlush(deactivation)\n return deactivation\n }\n\n private async createDeactivationTx(txEm: EntityManager, userId: string, scope: ScimScope): Promise<SsoUserDeactivation> {\n const deactivation = txEm.create(SsoUserDeactivation, {\n tenantId: scope.tenantId ?? null,\n organizationId: scope.organizationId,\n userId,\n ssoConfigId: scope.ssoConfigId,\n deactivatedAt: new Date(),\n } as RequiredEntityData<SsoUserDeactivation>)\n await txEm.persistAndFlush(deactivation)\n return deactivation\n }\n\n private async log(\n scope: ScimScope,\n operation: string,\n resourceId: string | null | undefined,\n externalId: string | null | undefined,\n responseStatus: number,\n errorMessage?: string,\n ): Promise<void> {\n const entry = this.em.create(ScimProvisioningLog, {\n tenantId: scope.tenantId ?? null,\n organizationId: scope.organizationId,\n ssoConfigId: scope.ssoConfigId,\n operation,\n resourceType: 'User',\n resourceId: resourceId ?? null,\n scimExternalId: externalId ?? null,\n responseStatus,\n errorMessage: errorMessage ?? null,\n } as RequiredEntityData<ScimProvisioningLog>)\n await this.em.persistAndFlush(entry)\n }\n\n private async logTx(\n txEm: EntityManager,\n scope: ScimScope,\n operation: string,\n resourceId: string | null | undefined,\n externalId: string | null | undefined,\n responseStatus: number,\n ): Promise<void> {\n const entry = txEm.create(ScimProvisioningLog, {\n tenantId: scope.tenantId ?? null,\n organizationId: scope.organizationId,\n ssoConfigId: scope.ssoConfigId,\n operation,\n resourceType: 'User',\n resourceId: resourceId ?? null,\n scimExternalId: externalId ?? null,\n responseStatus,\n } as RequiredEntityData<ScimProvisioningLog>)\n await txEm.persistAndFlush(entry)\n }\n}\n\nfunction buildDisplayName(parsed: ScimUserPayload): string | null {\n if (parsed.displayName) return parsed.displayName\n const parts = [parsed.givenName, parsed.familyName].filter(Boolean)\n return parts.length > 0 ? parts.join(' ') : null\n}\n\nexport class ScimServiceError extends Error {\n constructor(\n public readonly statusCode: number,\n message: string,\n ) {\n super(message)\n this.name = 'ScimServiceError'\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["existingUser"]
7
+ }
@@ -0,0 +1,94 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { hash, compare } from "bcryptjs";
3
+ import { ScimToken, SsoConfig } from "../data/entities.js";
4
+ const BCRYPT_COST = 10;
5
+ const TOKEN_PREFIX = "omscim_";
6
+ class ScimTokenService {
7
+ constructor(em) {
8
+ this.em = em;
9
+ }
10
+ async generateToken(ssoConfigId, name, scope) {
11
+ const where = { id: ssoConfigId, deletedAt: null };
12
+ if (!scope.isSuperAdmin && scope.organizationId) {
13
+ where.organizationId = scope.organizationId;
14
+ }
15
+ const config = await this.em.findOne(SsoConfig, where);
16
+ if (!config) throw new ScimTokenError("SSO configuration not found", 404);
17
+ if (config.jitEnabled) {
18
+ throw new ScimTokenError("Cannot create SCIM tokens while JIT provisioning is enabled. Disable JIT first.", 409);
19
+ }
20
+ const raw = TOKEN_PREFIX + randomBytes(32).toString("hex");
21
+ const tokenHash = await hash(raw, BCRYPT_COST);
22
+ const tokenPrefix = raw.slice(0, 12);
23
+ const token = this.em.create(ScimToken, {
24
+ ssoConfigId,
25
+ name,
26
+ tokenHash,
27
+ tokenPrefix,
28
+ isActive: true,
29
+ createdBy: null,
30
+ tenantId: scope.tenantId,
31
+ organizationId: scope.organizationId
32
+ });
33
+ await this.em.persistAndFlush(token);
34
+ return { id: token.id, token: raw, prefix: tokenPrefix, name };
35
+ }
36
+ async verifyToken(rawToken) {
37
+ const prefix = rawToken.slice(0, 12);
38
+ const candidates = await this.em.find(ScimToken, {
39
+ tokenPrefix: prefix,
40
+ isActive: true
41
+ });
42
+ if (candidates.length === 0) {
43
+ await hash(rawToken, BCRYPT_COST);
44
+ return null;
45
+ }
46
+ for (const candidate of candidates) {
47
+ const isValid = await compare(rawToken, candidate.tokenHash);
48
+ if (isValid) {
49
+ return {
50
+ ssoConfigId: candidate.ssoConfigId,
51
+ organizationId: candidate.organizationId,
52
+ tenantId: candidate.tenantId ?? null
53
+ };
54
+ }
55
+ }
56
+ return null;
57
+ }
58
+ async revokeToken(tokenId, scope) {
59
+ const where = { id: tokenId };
60
+ if (!scope.isSuperAdmin) where.organizationId = scope.organizationId;
61
+ const token = await this.em.findOne(ScimToken, where);
62
+ if (!token) throw new ScimTokenError("SCIM token not found", 404);
63
+ token.isActive = false;
64
+ await this.em.flush();
65
+ }
66
+ async listTokens(ssoConfigId, scope) {
67
+ const where = { ssoConfigId };
68
+ if (!scope.isSuperAdmin) where.organizationId = scope.organizationId;
69
+ const tokens = await this.em.find(ScimToken, where, {
70
+ orderBy: { createdAt: "desc" }
71
+ });
72
+ return tokens.map((t) => ({
73
+ id: t.id,
74
+ ssoConfigId: t.ssoConfigId,
75
+ name: t.name,
76
+ tokenPrefix: t.tokenPrefix,
77
+ isActive: t.isActive,
78
+ createdBy: t.createdBy ?? null,
79
+ createdAt: t.createdAt
80
+ }));
81
+ }
82
+ }
83
+ class ScimTokenError extends Error {
84
+ constructor(message, statusCode) {
85
+ super(message);
86
+ this.statusCode = statusCode;
87
+ this.name = "ScimTokenError";
88
+ }
89
+ }
90
+ export {
91
+ ScimTokenError,
92
+ ScimTokenService
93
+ };
94
+ //# sourceMappingURL=scimTokenService.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/sso/services/scimTokenService.ts"],
4
+ "sourcesContent": ["import { randomBytes } from 'node:crypto'\nimport type { RequiredEntityData } from '@mikro-orm/core'\nimport { EntityManager } from '@mikro-orm/postgresql'\nimport { hash, compare } from 'bcryptjs'\nimport { ScimToken, SsoConfig } from '../data/entities'\nimport type { SsoAdminScope } from './ssoConfigService'\n\nconst BCRYPT_COST = 10\nconst TOKEN_PREFIX = 'omscim_'\n\nexport interface ScimTokenPublic {\n id: string\n ssoConfigId: string\n name: string\n tokenPrefix: string\n isActive: boolean\n createdBy: string | null\n createdAt: Date\n}\n\nexport interface ScimTokenCreateResult {\n id: string\n token: string\n prefix: string\n name: string\n}\n\nexport class ScimTokenService {\n constructor(private em: EntityManager) {}\n\n async generateToken(\n ssoConfigId: string,\n name: string,\n scope: SsoAdminScope,\n ): Promise<ScimTokenCreateResult> {\n const where: Record<string, unknown> = { id: ssoConfigId, deletedAt: null }\n if (!scope.isSuperAdmin && scope.organizationId) {\n where.organizationId = scope.organizationId\n }\n const config = await this.em.findOne(SsoConfig, where)\n if (!config) throw new ScimTokenError('SSO configuration not found', 404)\n if (config.jitEnabled) {\n throw new ScimTokenError('Cannot create SCIM tokens while JIT provisioning is enabled. Disable JIT first.', 409)\n }\n\n const raw = TOKEN_PREFIX + randomBytes(32).toString('hex')\n const tokenHash = await hash(raw, BCRYPT_COST)\n const tokenPrefix = raw.slice(0, 12)\n\n const token = this.em.create(ScimToken, {\n ssoConfigId,\n name,\n tokenHash,\n tokenPrefix,\n isActive: true,\n createdBy: null,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId!,\n } as RequiredEntityData<ScimToken>)\n\n await this.em.persistAndFlush(token)\n\n return { id: token.id, token: raw, prefix: tokenPrefix, name }\n }\n\n async verifyToken(rawToken: string): Promise<{\n ssoConfigId: string\n organizationId: string\n tenantId: string | null\n } | null> {\n const prefix = rawToken.slice(0, 12)\n\n const candidates = await this.em.find(ScimToken, {\n tokenPrefix: prefix,\n isActive: true,\n })\n\n if (candidates.length === 0) {\n await hash(rawToken, BCRYPT_COST)\n return null\n }\n\n for (const candidate of candidates) {\n const isValid = await compare(rawToken, candidate.tokenHash)\n if (isValid) {\n return {\n ssoConfigId: candidate.ssoConfigId,\n organizationId: candidate.organizationId,\n tenantId: candidate.tenantId ?? null,\n }\n }\n }\n\n return null\n }\n\n async revokeToken(tokenId: string, scope: SsoAdminScope): Promise<void> {\n const where: Record<string, unknown> = { id: tokenId }\n if (!scope.isSuperAdmin) where.organizationId = scope.organizationId\n\n const token = await this.em.findOne(ScimToken, where)\n if (!token) throw new ScimTokenError('SCIM token not found', 404)\n\n token.isActive = false\n await this.em.flush()\n }\n\n async listTokens(ssoConfigId: string, scope: SsoAdminScope): Promise<ScimTokenPublic[]> {\n const where: Record<string, unknown> = { ssoConfigId }\n if (!scope.isSuperAdmin) where.organizationId = scope.organizationId\n\n const tokens = await this.em.find(ScimToken, where, {\n orderBy: { createdAt: 'desc' },\n })\n\n return tokens.map((t) => ({\n id: t.id,\n ssoConfigId: t.ssoConfigId,\n name: t.name,\n tokenPrefix: t.tokenPrefix,\n isActive: t.isActive,\n createdBy: t.createdBy ?? null,\n createdAt: t.createdAt,\n }))\n }\n}\n\nexport class ScimTokenError extends Error {\n constructor(\n message: string,\n public readonly statusCode: number,\n ) {\n super(message)\n this.name = 'ScimTokenError'\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }