@open-mercato/enterprise 0.6.4-develop.4210.1.d412061cfe → 0.6.4-develop.4236.1.9fa6806b34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/modules/sso/services/scimService.js +19 -18
- package/dist/modules/sso/services/scimService.js.map +2 -2
- package/dist/modules/sso/subscribers/user-deleted-cleanup.js +9 -7
- package/dist/modules/sso/subscribers/user-deleted-cleanup.js.map +2 -2
- package/package.json +5 -5
- package/src/modules/sso/services/scimService.ts +21 -20
- package/src/modules/sso/subscribers/user-deleted-cleanup.ts +9 -7
|
@@ -272,26 +272,27 @@ class ScimService {
|
|
|
272
272
|
}
|
|
273
273
|
}
|
|
274
274
|
async deactivateUser(userId, scope) {
|
|
275
|
-
|
|
276
|
-
|
|
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,
|
|
275
|
+
await this.em.transactional(async (txEm) => {
|
|
276
|
+
let deactivation = await txEm.findOne(SsoUserDeactivation, {
|
|
286
277
|
userId,
|
|
287
|
-
ssoConfigId: scope.ssoConfigId
|
|
288
|
-
deactivatedAt: /* @__PURE__ */ new Date()
|
|
278
|
+
ssoConfigId: scope.ssoConfigId
|
|
289
279
|
});
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
280
|
+
if (deactivation) {
|
|
281
|
+
deactivation.deactivatedAt = /* @__PURE__ */ new Date();
|
|
282
|
+
deactivation.reactivatedAt = null;
|
|
283
|
+
} else {
|
|
284
|
+
deactivation = txEm.create(SsoUserDeactivation, {
|
|
285
|
+
tenantId: scope.tenantId ?? null,
|
|
286
|
+
organizationId: scope.organizationId,
|
|
287
|
+
userId,
|
|
288
|
+
ssoConfigId: scope.ssoConfigId,
|
|
289
|
+
deactivatedAt: /* @__PURE__ */ new Date()
|
|
290
|
+
});
|
|
291
|
+
txEm.persist(deactivation);
|
|
292
|
+
}
|
|
293
|
+
const sessionWhere = { user: userId };
|
|
294
|
+
await txEm.nativeDelete(Session, sessionWhere);
|
|
295
|
+
});
|
|
295
296
|
}
|
|
296
297
|
async reactivateUser(userId, scope) {
|
|
297
298
|
const deactivation = await this.em.findOne(SsoUserDeactivation, {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
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.persist(identity).flush()\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.persist(user).flush()\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.persist(identity).flush()\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.persist(deactivation).flush()\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.persist(deactivation).flush()\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.persist(entry).flush()\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.persist(entry).flush()\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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
|
|
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.persist(identity).flush()\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.persist(user).flush()\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.persist(identity).flush()\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 await this.em.transactional(async (txEm) => {\n let deactivation = await txEm.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 = 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 txEm.persist(deactivation)\n }\n\n // Revoke all active sessions atomically with the deactivation upsert\n const sessionWhere: FilterQuery<Session> = { user: userId }\n await txEm.nativeDelete(Session, sessionWhere)\n })\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.persist(deactivation).flush()\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.persist(deactivation).flush()\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.persist(entry).flush()\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.persist(entry).flush()\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": "AACA,SAAS,MAAM,eAAe;AAC9B,SAAS,wBAAwB;AACjC,SAAS,uBAAuB,0BAA0B;AAC1D,SAAS,aAAa,qBAAqB,2BAA2B;AACtE,SAAS,oBAAoB,2BAAwE;AACrG,SAAS,qBAAqB;AAC9B,SAAS,iBAAiB,yBAAyB;AACnD,SAAS,yBAAyB;AAI3B,MAAM,YAAY;AAAA,EACvB,YAAoB,IAAmB;AAAnB;AAAA,EAAoB;AAAA,EAExC,MAAM,WACJ,SACA,OACA,SACyD;AACzD,UAAM,SAAS,oBAAoB,OAAO;AAC1C,UAAM,QAAQ,OAAO,SAAS,OAAO;AACrC,QAAI,CAAC,OAAO;AACV,YAAM,IAAI,iBAAiB,KAAK,yCAAyC;AAAA,IAC3E;AAGA,QAAI,OAAO,YAAY;AACrB,YAAM,mBAAmB,MAAM,KAAK,GAAG,QAAQ,aAAa;AAAA,QAC1D,aAAa,MAAM;AAAA,QACnB,YAAY,OAAO;AAAA,QACnB,WAAW;AAAA,MACb,CAAC;AACD,UAAI,kBAAkB;AACpB,cAAMA,gBAAe,MAAM;AAAA,UACzB,KAAK;AAAA,UAAI;AAAA,UACT,EAAE,IAAI,iBAAiB,QAAQ,WAAW,KAAK;AAAA,UAC/C,CAAC;AAAA,UACD,EAAE,UAAU,MAAM,YAAY,IAAI,gBAAgB,MAAM,eAAe;AAAA,QACzE;AACA,YAAIA,eAAc;AAChB,gBAAM,eAAe,MAAM,KAAK,GAAG,QAAQ,qBAAqB;AAAA,YAC9D,QAAQA,cAAa;AAAA,YAAI,aAAa,MAAM;AAAA,UAC9C,CAAC;AACD,gBAAM,KAAK,IAAI,OAAO,UAAU,iBAAiB,IAAI,OAAO,YAAY,GAAG;AAC3E,iBAAO;AAAA,YACL,UAAU,mBAAmBA,eAAc,kBAAkB,SAAS,YAAY;AAAA,YAClF,QAAQ;AAAA,UACV;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAGA,UAAM,YAAY,iBAAiB,KAAK;AACxC,UAAM,QAA2B;AAAA,MAC/B,gBAAgB,MAAM;AAAA,MACtB,WAAW;AAAA,MACX,KAAK,CAAC,EAAE,MAAM,GAAG,EAAE,UAAU,CAAC;AAAA,IAChC;AACA,UAAM,eAAe,MAAM;AAAA,MACzB,KAAK;AAAA,MAAI;AAAA,MACT;AAAA,MACA,CAAC;AAAA,MACD,EAAE,UAAU,MAAM,YAAY,IAAI,gBAAgB,MAAM,eAAe;AAAA,IACzE;AAEA,QAAI,cAAc;AAEhB,YAAM,eAAe,MAAM,KAAK,GAAG,QAAQ,aAAa;AAAA,QACtD,aAAa,MAAM;AAAA,QACnB,QAAQ,aAAa;AAAA,QACrB,WAAW;AAAA,MACb,CAAC;AACD,UAAI,cAAc;AAChB,cAAM,IAAI,iBAAiB,KAAK,mBAAmB,KAAK,8CAA8C;AAAA,MACxG;AAGA,YAAM,MAAM,oBAAI,KAAK;AACrB,YAAM,WAAW,KAAK,GAAG,OAAO,aAAa;AAAA,QAC3C,UAAU,MAAM,YAAY;AAAA,QAC5B,gBAAgB,MAAM;AAAA,QACtB,aAAa,MAAM;AAAA,QACnB,QAAQ,aAAa;AAAA,QACrB,YAAY,OAAO,cAAc;AAAA,QACjC,UAAU;AAAA,QACV,SAAS,iBAAiB,MAAM;AAAA,QAChC,WAAW,CAAC;AAAA,QACZ,YAAY,OAAO,cAAc;AAAA,QACjC,oBAAoB;AAAA,QACpB,WAAW;AAAA,QACX,WAAW;AAAA,MACb,CAAoC;AACpC,YAAM,KAAK,GAAG,QAAQ,QAAQ,EAAE,MAAM;AAEtC,YAAM,eAAe,OAAO,WAAW,QACnC,MAAM,KAAK,mBAAmB,aAAa,IAAI,KAAK,IACpD;AAEJ,YAAM,KAAK,IAAI,OAAO,UAAU,SAAS,IAAI,OAAO,YAAY,GAAG;AACnE,aAAO;AAAA,QACL,UAAU,mBAAmB,cAAc,UAAU,SAAS,YAAY;AAAA,QAC1E,QAAQ;AAAA,MACV;AAAA,IACF;AAGA,WAAO,KAAK,GAAG,cAAc,OAAO,SAAS;AAC3C,YAAM,OAAO,KAAK,OAAO,MAAM;AAAA,QAC7B,UAAU,MAAM,YAAY;AAAA,QAC5B,gBAAgB,MAAM;AAAA,QACtB;AAAA,QACA,WAAW,iBAAiB,KAAK;AAAA,QACjC,MAAM,iBAAiB,MAAM,KAAK;AAAA,QAClC,cAAc;AAAA,QACd,aAAa;AAAA,QACb,WAAW,oBAAI,KAAK;AAAA,MACtB,CAAC;AACD,YAAM,KAAK,QAAQ,IAAI,EAAE,MAAM;AAE/B,YAAM,MAAM,oBAAI,KAAK;AACrB,YAAM,WAAW,KAAK,OAAO,aAAa;AAAA,QACxC,UAAU,MAAM,YAAY;AAAA,QAC5B,gBAAgB,MAAM;AAAA,QACtB,aAAa,MAAM;AAAA,QACnB,QAAQ,KAAK;AAAA,QACb,YAAY,OAAO,cAAc;AAAA,QACjC,UAAU;AAAA,QACV,SAAS,iBAAiB,MAAM;AAAA,QAChC,WAAW,CAAC;AAAA,QACZ,YAAY,OAAO,cAAc;AAAA,QACjC,oBAAoB;AAAA,QACpB,WAAW;AAAA,QACX,WAAW;AAAA,MACb,CAAoC;AACpC,YAAM,KAAK,QAAQ,QAAQ,EAAE,MAAM;AAEnC,YAAM,eAAe,OAAO,WAAW,QACnC,MAAM,KAAK,qBAAqB,MAAM,KAAK,IAAI,KAAK,IACpD;AAEJ,YAAM,KAAK,MAAM,MAAM,OAAO,UAAU,SAAS,IAAI,OAAO,YAAY,GAAG;AAC3E,aAAO;AAAA,QACL,UAAU,mBAAmB,MAAM,UAAU,SAAS,YAAY;AAAA,QAClE,QAAQ;AAAA,MACV;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,QAAQ,QAAgB,OAAkB,SAA4C;AAC1F,UAAM,WAAW,MAAM,KAAK,GAAG,QAAQ,aAAa;AAAA,MAClD,IAAI;AAAA,MACJ,aAAa,MAAM;AAAA,MACnB,gBAAgB,MAAM;AAAA,MACtB,WAAW;AAAA,IACb,CAAC;AACD,QAAI,CAAC,SAAU,OAAM,IAAI,iBAAiB,KAAK,gBAAgB;AAE/D,UAAM,OAAO,MAAM;AAAA,MACjB,KAAK;AAAA,MAAI;AAAA,MACT,EAAE,IAAI,SAAS,QAAQ,WAAW,KAAK;AAAA,MACvC,CAAC;AAAA,MACD,EAAE,UAAU,MAAM,YAAY,IAAI,gBAAgB,MAAM,eAAe;AAAA,IACzE;AACA,QAAI,CAAC,KAAM,OAAM,IAAI,iBAAiB,KAAK,gBAAgB;AAE3D,UAAM,eAAe,MAAM,KAAK,GAAG,QAAQ,qBAAqB;AAAA,MAC9D,QAAQ,KAAK;AAAA,MAAI,aAAa,MAAM;AAAA,IACtC,CAAC;AAED,WAAO,mBAAmB,MAAM,UAAU,SAAS,YAAY;AAAA,EACjE;AAAA,EAEA,MAAM,UACJ,QACA,YACA,OACA,OACA,SACkC;AAClC,UAAM,aAAa,gBAAgB,MAAM;AACzC,UAAM,QAAQ,kBAAkB,YAAY,MAAM,aAAa,MAAM,cAAc;AAEnF,UAAM,SAAS,KAAK,IAAI,GAAG,aAAa,CAAC;AACzC,UAAM,CAAC,YAAY,KAAK,IAAI,MAAM,KAAK,GAAG,aAAa,aAAa,OAAO;AAAA,MACzE,SAAS,EAAE,WAAW,MAAM;AAAA,MAC5B,OAAO;AAAA,MACP;AAAA,IACF,CAAC;AAED,UAAM,UAAU,WAAW,IAAI,CAAC,MAAM,EAAE,MAAM;AAE9C,UAAM,QAAQ,QAAQ,SAAS,IAC3B,MAAM;AAAA,MACJ,KAAK;AAAA,MAAI;AAAA,MACT,EAAE,IAAI,EAAE,KAAK,QAAQ,GAAG,WAAW,KAAK;AAAA,MACxC,CAAC;AAAA,MACD,EAAE,UAAU,MAAM,YAAY,IAAI,gBAAgB,MAAM,eAAe;AAAA,IACzE,IACA,CAAC;AACL,UAAM,UAAU,IAAI,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;AAEnD,UAAM,gBAAgB,QAAQ,SAAS,IACnC,MAAM,KAAK,GAAG,KAAK,qBAAqB;AAAA,MACtC,QAAQ,EAAE,KAAK,QAAQ;AAAA,MAAG,aAAa,MAAM;AAAA,IAC/C,CAAC,IACD,CAAC;AACL,UAAM,kBAAkB,IAAI,IAAI,cAAc,IAAI,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC;AAEvE,UAAM,YAAgC,CAAC;AACvC,eAAW,YAAY,YAAY;AACjC,YAAM,OAAO,QAAQ,IAAI,SAAS,MAAM;AACxC,UAAI,CAAC,KAAM;AAEX,YAAM,eAAe,gBAAgB,IAAI,KAAK,EAAE,KAAK;AACrD,gBAAU,KAAK,mBAAmB,MAAM,UAAU,SAAS,YAAY,CAAC;AAAA,IAC1E;AAEA,WAAO,kBAAkB,WAAW,OAAO,YAAY,UAAU,MAAM;AAAA,EACzE;AAAA,EAEA,MAAM,UACJ,QACA,YACA,OACA,SAC2B;AAC3B,UAAM,WAAW,MAAM,KAAK,GAAG,QAAQ,aAAa;AAAA,MAClD,IAAI;AAAA,MACJ,aAAa,MAAM;AAAA,MACnB,gBAAgB,MAAM;AAAA,MACtB,WAAW;AAAA,IACb,CAAC;AACD,QAAI,CAAC,SAAU,OAAM,IAAI,iBAAiB,KAAK,gBAAgB;AAE/D,UAAM,OAAO,MAAM;AAAA,MACjB,KAAK;AAAA,MAAI;AAAA,MACT,EAAE,IAAI,SAAS,QAAQ,WAAW,KAAK;AAAA,MACvC,CAAC;AAAA,MACD,EAAE,UAAU,MAAM,YAAY,IAAI,gBAAgB,MAAM,eAAe;AAAA,IACzE;AACA,QAAI,CAAC,KAAM,OAAM,IAAI,iBAAiB,KAAK,gBAAgB;AAE3D,eAAW,MAAM,YAAY;AAC3B,YAAM,eAAe,GAAG,GAAG,YAAY;AACvC,UAAI,iBAAiB,aAAa,iBAAiB,OAAO;AACxD,aAAK,gBAAgB,MAAM,UAAU,GAAG,MAAM,GAAG,KAAK;AAAA,MACxD;AAEA,UAAI,iBAAiB,YAAY,GAAG,MAAM;AACxC,aAAK,gBAAgB,MAAM,UAAU,GAAG,MAAM,IAAI;AAAA,MACpD;AAAA,IACF;AAGA,UAAM,WAAW,WAAW;AAAA,MAAK,CAAC,OAChC,GAAG,MAAM,YAAY,MAAM,YAC1B,CAAC,GAAG,QAAQ,GAAG,SAAS,OAAO,GAAG,UAAU,YAAY,YAAa,GAAG;AAAA,IAC3E;AAEA,QAAI,UAAU;AACZ,YAAM,cAAc,SAAS,OACzB,cAAc,SAAS,KAAK,IAC5B,cAAe,SAAS,MAAkC,MAAM;AAEpE,UAAI,gBAAgB,OAAO;AACzB,cAAM,KAAK,eAAe,KAAK,IAAI,KAAK;AAAA,MAC1C,WAAW,gBAAgB,MAAM;AAC/B,cAAM,KAAK,eAAe,KAAK,IAAI,KAAK;AAAA,MAC1C;AAAA,IACF;AAEA,UAAM,KAAK,GAAG,MAAM;AAEpB,UAAM,eAAe,MAAM,KAAK,GAAG,QAAQ,qBAAqB;AAAA,MAC9D,QAAQ,KAAK;AAAA,MAAI,aAAa,MAAM;AAAA,IACtC,CAAC;AAED,UAAM,KAAK,IAAI,OAAO,SAAS,SAAS,IAAI,SAAS,YAAY,GAAG;AACpE,WAAO,mBAAmB,MAAM,UAAU,SAAS,YAAY;AAAA,EACjE;AAAA,EAEA,MAAM,WAAW,QAAgB,OAAiC;AAChE,UAAM,WAAW,MAAM,KAAK,GAAG,QAAQ,aAAa;AAAA,MAClD,IAAI;AAAA,MACJ,aAAa,MAAM;AAAA,MACnB,gBAAgB,MAAM;AAAA,MACtB,WAAW;AAAA,IACb,CAAC;AACD,QAAI,CAAC,SAAU,OAAM,IAAI,iBAAiB,KAAK,gBAAgB;AAE/D,UAAM,KAAK,eAAe,SAAS,QAAQ,KAAK;AAChD,UAAM,KAAK,IAAI,OAAO,UAAU,SAAS,IAAI,SAAS,YAAY,GAAG;AAAA,EACvE;AAAA,EAEQ,gBACN,MACA,UACA,MACA,OACM;AACN,QAAI,CAAC,MAAM;AAET,UAAI,SAAS,OAAO,UAAU,UAAU;AACtC,cAAM,MAAM;AACZ,mBAAW,CAAC,KAAK,GAAG,KAAK,OAAO,QAAQ,GAAG,GAAG;AAC5C,eAAK,gBAAgB,MAAM,UAAU,KAAK,GAAG;AAAA,QAC/C;AAAA,MACF;AACA;AAAA,IACF;AAEA,UAAM,iBAAiB,KAAK,YAAY;AACxC,YAAQ,gBAAgB;AAAA,MACtB,KAAK;AACH,aAAK,OAAQ,SAAoB;AACjC,iBAAS,UAAW,SAAoB;AACxC;AAAA,MACF,KAAK,kBAAkB;AACrB,cAAM,gBAAgB,KAAK,QAAQ,IAAI,MAAM,GAAG;AAChD,qBAAa,CAAC,IAAK,SAAoB;AACvC,aAAK,OAAO,aAAa,KAAK,GAAG,EAAE,KAAK,KAAK;AAC7C;AAAA,MACF;AAAA,MACA,KAAK,mBAAmB;AACtB,cAAM,gBAAgB,KAAK,QAAQ,IAAI,MAAM,GAAG;AAChD,cAAM,QAAQ,aAAa,CAAC,KAAK;AACjC,aAAK,OAAO,QAAQ,GAAG,KAAK,IAAI,KAAK,GAAG,KAAK,IAAI,SAAS;AAC1D;AAAA,MACF;AAAA,MACA,KAAK;AACH,iBAAS,WAAY,SAAoB,SAAS;AAClD;AAAA,MACF,KAAK;AACH,iBAAS,aAAc,SAAoB;AAC3C;AAAA,MACF,KAAK;AAEH;AAAA,IACJ;AAAA,EACF;AAAA,EAEA,MAAc,eAAe,QAAgB,OAAiC;AAC5E,UAAM,KAAK,GAAG,cAAc,OAAO,SAAS;AAC1C,UAAI,eAAe,MAAM,KAAK,QAAQ,qBAAqB;AAAA,QACzD;AAAA,QAAQ,aAAa,MAAM;AAAA,MAC7B,CAAC;AAED,UAAI,cAAc;AAChB,qBAAa,gBAAgB,oBAAI,KAAK;AACtC,qBAAa,gBAAgB;AAAA,MAC/B,OAAO;AACL,uBAAe,KAAK,OAAO,qBAAqB;AAAA,UAC9C,UAAU,MAAM,YAAY;AAAA,UAC5B,gBAAgB,MAAM;AAAA,UACtB;AAAA,UACA,aAAa,MAAM;AAAA,UACnB,eAAe,oBAAI,KAAK;AAAA,QAC1B,CAA4C;AAC5C,aAAK,QAAQ,YAAY;AAAA,MAC3B;AAGA,YAAM,eAAqC,EAAE,MAAM,OAAO;AAC1D,YAAM,KAAK,aAAa,SAAS,YAAY;AAAA,IAC/C,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,eAAe,QAAgB,OAAiC;AAC5E,UAAM,eAAe,MAAM,KAAK,GAAG,QAAQ,qBAAqB;AAAA,MAC9D;AAAA,MAAQ,aAAa,MAAM;AAAA,IAC7B,CAAC;AACD,QAAI,gBAAgB,CAAC,aAAa,eAAe;AAC/C,mBAAa,gBAAgB,oBAAI,KAAK;AACtC,YAAM,KAAK,GAAG,MAAM;AAAA,IACtB;AAAA,EACF;AAAA,EAEA,MAAc,mBAAmB,QAAgB,OAAgD;AAC/F,UAAM,eAAe,KAAK,GAAG,OAAO,qBAAqB;AAAA,MACvD,UAAU,MAAM,YAAY;AAAA,MAC5B,gBAAgB,MAAM;AAAA,MACtB;AAAA,MACA,aAAa,MAAM;AAAA,MACnB,eAAe,oBAAI,KAAK;AAAA,IAC1B,CAA4C;AAC5C,UAAM,KAAK,GAAG,QAAQ,YAAY,EAAE,MAAM;AAC1C,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,qBAAqB,MAAqB,QAAgB,OAAgD;AACtH,UAAM,eAAe,KAAK,OAAO,qBAAqB;AAAA,MACpD,UAAU,MAAM,YAAY;AAAA,MAC5B,gBAAgB,MAAM;AAAA,MACtB;AAAA,MACA,aAAa,MAAM;AAAA,MACnB,eAAe,oBAAI,KAAK;AAAA,IAC1B,CAA4C;AAC5C,UAAM,KAAK,QAAQ,YAAY,EAAE,MAAM;AACvC,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,IACZ,OACA,WACA,YACA,YACA,gBACA,cACe;AACf,UAAM,QAAQ,KAAK,GAAG,OAAO,qBAAqB;AAAA,MAChD,UAAU,MAAM,YAAY;AAAA,MAC5B,gBAAgB,MAAM;AAAA,MACtB,aAAa,MAAM;AAAA,MACnB;AAAA,MACA,cAAc;AAAA,MACd,YAAY,cAAc;AAAA,MAC1B,gBAAgB,cAAc;AAAA,MAC9B;AAAA,MACA,cAAc,gBAAgB;AAAA,IAChC,CAA4C;AAC5C,UAAM,KAAK,GAAG,QAAQ,KAAK,EAAE,MAAM;AAAA,EACrC;AAAA,EAEA,MAAc,MACZ,MACA,OACA,WACA,YACA,YACA,gBACe;AACf,UAAM,QAAQ,KAAK,OAAO,qBAAqB;AAAA,MAC7C,UAAU,MAAM,YAAY;AAAA,MAC5B,gBAAgB,MAAM;AAAA,MACtB,aAAa,MAAM;AAAA,MACnB;AAAA,MACA,cAAc;AAAA,MACd,YAAY,cAAc;AAAA,MAC1B,gBAAgB,cAAc;AAAA,MAC9B;AAAA,IACF,CAA4C;AAC5C,UAAM,KAAK,QAAQ,KAAK,EAAE,MAAM;AAAA,EAClC;AACF;AAEA,SAAS,iBAAiB,QAAwC;AAChE,MAAI,OAAO,YAAa,QAAO,OAAO;AACtC,QAAM,QAAQ,CAAC,OAAO,WAAW,OAAO,UAAU,EAAE,OAAO,OAAO;AAClE,SAAO,MAAM,SAAS,IAAI,MAAM,KAAK,GAAG,IAAI;AAC9C;AAEO,MAAM,yBAAyB,MAAM;AAAA,EAC1C,YACkB,YAChB,SACA;AACA,UAAM,OAAO;AAHG;AAIhB,SAAK,OAAO;AAAA,EACd;AACF;",
|
|
6
6
|
"names": ["existingUser"]
|
|
7
7
|
}
|
|
@@ -6,13 +6,15 @@ const metadata = {
|
|
|
6
6
|
};
|
|
7
7
|
async function handle(payload, ctx) {
|
|
8
8
|
const em = ctx.resolve("em");
|
|
9
|
-
await em.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
await em.transactional(async (txEm) => {
|
|
10
|
+
await txEm.nativeUpdate(
|
|
11
|
+
SsoIdentity,
|
|
12
|
+
{ userId: payload.userId, deletedAt: null },
|
|
13
|
+
{ deletedAt: /* @__PURE__ */ new Date() }
|
|
14
|
+
);
|
|
15
|
+
await txEm.nativeDelete(SsoRoleGrant, { userId: payload.userId });
|
|
16
|
+
await txEm.nativeDelete(SsoUserDeactivation, { userId: payload.userId });
|
|
17
|
+
});
|
|
16
18
|
}
|
|
17
19
|
export {
|
|
18
20
|
handle as default,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/sso/subscribers/user-deleted-cleanup.ts"],
|
|
4
|
-
"sourcesContent": ["import type { EntityData } from '@mikro-orm/core'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { SsoIdentity, SsoRoleGrant, SsoUserDeactivation } from '../data/entities'\n\nexport const metadata = {\n event: 'auth.user.deleted',\n persistent: true,\n id: 'sso:user-deleted-cleanup',\n}\n\ntype UserDeletedPayload = {\n userId: string\n tenantId: string\n organizationId: string\n}\n\ntype ResolverContext = {\n resolve: <T = unknown>(name: string) => T\n}\n\nexport default async function handle(payload: UserDeletedPayload, ctx: ResolverContext) {\n const em = ctx.resolve<EntityManager>('em')\n\n await em.nativeUpdate(\n
|
|
5
|
-
"mappings": "AAEA,SAAS,aAAa,cAAc,2BAA2B;AAExD,MAAM,WAAW;AAAA,EACtB,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,IAAI;AACN;AAYA,eAAO,OAA8B,SAA6B,KAAsB;AACtF,QAAM,KAAK,IAAI,QAAuB,IAAI;AAE1C,QAAM,GAAG;AAAA,
|
|
4
|
+
"sourcesContent": ["import type { EntityData } from '@mikro-orm/core'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { SsoIdentity, SsoRoleGrant, SsoUserDeactivation } from '../data/entities'\n\nexport const metadata = {\n event: 'auth.user.deleted',\n persistent: true,\n id: 'sso:user-deleted-cleanup',\n}\n\ntype UserDeletedPayload = {\n userId: string\n tenantId: string\n organizationId: string\n}\n\ntype ResolverContext = {\n resolve: <T = unknown>(name: string) => T\n}\n\nexport default async function handle(payload: UserDeletedPayload, ctx: ResolverContext) {\n const em = ctx.resolve<EntityManager>('em')\n\n await em.transactional(async (txEm) => {\n await txEm.nativeUpdate(\n SsoIdentity,\n { userId: payload.userId, deletedAt: null },\n { deletedAt: new Date() } as EntityData<SsoIdentity>,\n )\n\n await txEm.nativeDelete(SsoRoleGrant, { userId: payload.userId })\n\n await txEm.nativeDelete(SsoUserDeactivation, { userId: payload.userId })\n })\n}\n"],
|
|
5
|
+
"mappings": "AAEA,SAAS,aAAa,cAAc,2BAA2B;AAExD,MAAM,WAAW;AAAA,EACtB,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,IAAI;AACN;AAYA,eAAO,OAA8B,SAA6B,KAAsB;AACtF,QAAM,KAAK,IAAI,QAAuB,IAAI;AAE1C,QAAM,GAAG,cAAc,OAAO,SAAS;AACrC,UAAM,KAAK;AAAA,MACT;AAAA,MACA,EAAE,QAAQ,QAAQ,QAAQ,WAAW,KAAK;AAAA,MAC1C,EAAE,WAAW,oBAAI,KAAK,EAAE;AAAA,IAC1B;AAEA,UAAM,KAAK,aAAa,cAAc,EAAE,QAAQ,QAAQ,OAAO,CAAC;AAEhE,UAAM,KAAK,aAAa,qBAAqB,EAAE,QAAQ,QAAQ,OAAO,CAAC;AAAA,EACzE,CAAC;AACH;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/enterprise",
|
|
3
|
-
"version": "0.6.4-develop.
|
|
3
|
+
"version": "0.6.4-develop.4236.1.9fa6806b34",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -64,8 +64,8 @@
|
|
|
64
64
|
}
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"@open-mercato/core": "0.6.4-develop.
|
|
68
|
-
"@open-mercato/ui": "0.6.4-develop.
|
|
67
|
+
"@open-mercato/core": "0.6.4-develop.4236.1.9fa6806b34",
|
|
68
|
+
"@open-mercato/ui": "0.6.4-develop.4236.1.9fa6806b34",
|
|
69
69
|
"@simplewebauthn/browser": "^13.3.0",
|
|
70
70
|
"@simplewebauthn/server": "^13.3.0",
|
|
71
71
|
"@simplewebauthn/types": "^12.0.0",
|
|
@@ -75,12 +75,12 @@
|
|
|
75
75
|
"qrcode": "^1.5.4"
|
|
76
76
|
},
|
|
77
77
|
"peerDependencies": {
|
|
78
|
-
"@open-mercato/shared": "0.6.4-develop.
|
|
78
|
+
"@open-mercato/shared": "0.6.4-develop.4236.1.9fa6806b34",
|
|
79
79
|
"react": "^19.0.0",
|
|
80
80
|
"react-dom": "^19.0.0"
|
|
81
81
|
},
|
|
82
82
|
"devDependencies": {
|
|
83
|
-
"@open-mercato/shared": "0.6.4-develop.
|
|
83
|
+
"@open-mercato/shared": "0.6.4-develop.4236.1.9fa6806b34",
|
|
84
84
|
"@types/jest": "^30.0.0",
|
|
85
85
|
"@types/react": "^19.2.15",
|
|
86
86
|
"@types/react-dom": "^19.2.3",
|
|
@@ -342,28 +342,29 @@ export class ScimService {
|
|
|
342
342
|
}
|
|
343
343
|
|
|
344
344
|
private async deactivateUser(userId: string, scope: ScimScope): Promise<void> {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
345
|
+
await this.em.transactional(async (txEm) => {
|
|
346
|
+
let deactivation = await txEm.findOne(SsoUserDeactivation, {
|
|
347
|
+
userId, ssoConfigId: scope.ssoConfigId,
|
|
348
|
+
})
|
|
348
349
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
await this.em.flush()
|
|
350
|
+
if (deactivation) {
|
|
351
|
+
deactivation.deactivatedAt = new Date()
|
|
352
|
+
deactivation.reactivatedAt = null
|
|
353
|
+
} else {
|
|
354
|
+
deactivation = txEm.create(SsoUserDeactivation, {
|
|
355
|
+
tenantId: scope.tenantId ?? null,
|
|
356
|
+
organizationId: scope.organizationId,
|
|
357
|
+
userId,
|
|
358
|
+
ssoConfigId: scope.ssoConfigId,
|
|
359
|
+
deactivatedAt: new Date(),
|
|
360
|
+
} as RequiredEntityData<SsoUserDeactivation>)
|
|
361
|
+
txEm.persist(deactivation)
|
|
362
|
+
}
|
|
363
363
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
364
|
+
// Revoke all active sessions atomically with the deactivation upsert
|
|
365
|
+
const sessionWhere: FilterQuery<Session> = { user: userId }
|
|
366
|
+
await txEm.nativeDelete(Session, sessionWhere)
|
|
367
|
+
})
|
|
367
368
|
}
|
|
368
369
|
|
|
369
370
|
private async reactivateUser(userId: string, scope: ScimScope): Promise<void> {
|
|
@@ -21,13 +21,15 @@ type ResolverContext = {
|
|
|
21
21
|
export default async function handle(payload: UserDeletedPayload, ctx: ResolverContext) {
|
|
22
22
|
const em = ctx.resolve<EntityManager>('em')
|
|
23
23
|
|
|
24
|
-
await em.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
await em.transactional(async (txEm) => {
|
|
25
|
+
await txEm.nativeUpdate(
|
|
26
|
+
SsoIdentity,
|
|
27
|
+
{ userId: payload.userId, deletedAt: null },
|
|
28
|
+
{ deletedAt: new Date() } as EntityData<SsoIdentity>,
|
|
29
|
+
)
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
await txEm.nativeDelete(SsoRoleGrant, { userId: payload.userId })
|
|
31
32
|
|
|
32
|
-
|
|
33
|
+
await txEm.nativeDelete(SsoUserDeactivation, { userId: payload.userId })
|
|
34
|
+
})
|
|
33
35
|
}
|