@open-mercato/enterprise 0.5.1-develop.2691.d8a0934b37 → 0.5.1-develop.2694.732417c5ec
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/record_locks/data/entities.js +2 -1
- package/dist/modules/record_locks/data/entities.js.map +2 -2
- package/dist/modules/record_locks/lib/recordLockService.js +19 -15
- package/dist/modules/record_locks/lib/recordLockService.js.map +2 -2
- package/dist/modules/security/data/entities.js +1 -1
- package/dist/modules/security/data/entities.js.map +1 -1
- package/dist/modules/sso/data/entities.js +1 -1
- package/dist/modules/sso/data/entities.js.map +2 -2
- package/dist/modules/sso/services/accountLinkingService.js +4 -4
- package/dist/modules/sso/services/accountLinkingService.js.map +2 -2
- package/dist/modules/sso/services/hrdService.js +3 -2
- package/dist/modules/sso/services/hrdService.js.map +2 -2
- package/dist/modules/sso/services/scimService.js +7 -7
- package/dist/modules/sso/services/scimService.js.map +2 -2
- package/dist/modules/sso/services/scimTokenService.js +1 -1
- package/dist/modules/sso/services/scimTokenService.js.map +2 -2
- package/dist/modules/sso/services/ssoConfigService.js +1 -1
- package/dist/modules/sso/services/ssoConfigService.js.map +2 -2
- package/dist/modules/sso/setup.js +1 -1
- package/dist/modules/sso/setup.js.map +2 -2
- package/jest.config.cjs +4 -2
- package/package.json +5 -5
- package/src/modules/record_locks/data/entities.ts +2 -1
- package/src/modules/record_locks/lib/recordLockService.ts +33 -28
- package/src/modules/security/data/entities.ts +1 -1
- package/src/modules/sso/data/entities.ts +1 -1
- package/src/modules/sso/services/accountLinkingService.ts +4 -4
- package/src/modules/sso/services/hrdService.ts +10 -7
- package/src/modules/sso/services/scimService.ts +7 -7
- package/src/modules/sso/services/scimTokenService.ts +1 -1
- package/src/modules/sso/services/ssoConfigService.ts +1 -1
- package/src/modules/sso/setup.ts +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/security/data/entities.ts"],
|
|
4
|
-
"sourcesContent": ["import { Entity, Index, PrimaryKey, Property } from '@mikro-orm/
|
|
4
|
+
"sourcesContent": ["import { Entity, Index, PrimaryKey, Property } from '@mikro-orm/decorators/legacy'\nimport {\n ChallengeMethod,\n EnforcementScope,\n MfaMethodType,\n SudoChallengeMethodUsed,\n} from './constants'\n\nexport {\n ChallengeMethod,\n EnforcementScope,\n MfaMethodType,\n SudoChallengeMethodUsed,\n} from './constants'\n\n@Entity({ tableName: 'user_mfa_methods' })\n@Index({ name: 'idx_user_mfa_methods_user_type', properties: ['userId', 'type', 'isActive'] })\n@Index({ name: 'idx_user_mfa_methods_tenant', properties: ['tenantId'] })\nexport class UserMfaMethod {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'user_id', type: 'uuid' })\n userId!: string\n\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n @Property({ name: 'organization_id', type: 'uuid', nullable: true })\n organizationId?: string | null\n\n @Property({ type: 'text' })\n type!: string\n\n @Property({ type: 'text', nullable: true })\n label?: string | null\n\n @Property({ type: 'text', nullable: true })\n secret?: string | null\n\n @Property({ name: 'provider_metadata', type: 'jsonb', nullable: true })\n providerMetadata?: Record<string, unknown> | null\n\n @Property({ name: 'is_active', type: 'boolean', default: true })\n isActive: boolean = true\n\n @Property({ name: 'last_used_at', type: Date, nullable: true })\n lastUsedAt?: Date | null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onCreate: () => new Date(), onUpdate: () => new Date() })\n updatedAt: Date = new Date()\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'mfa_recovery_codes' })\n@Index({ name: 'idx_mfa_recovery_codes_user', properties: ['userId', 'isUsed'] })\nexport class MfaRecoveryCode {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'user_id', type: 'uuid' })\n userId!: string\n\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n @Property({ name: 'code_hash', type: 'text' })\n codeHash!: string\n\n @Property({ name: 'is_used', type: 'boolean', default: false })\n isUsed: boolean = false\n\n @Property({ name: 'used_at', type: Date, nullable: true })\n usedAt?: Date | null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n}\n\n@Entity({ tableName: 'mfa_enforcement_policies' })\n@Index({ name: 'idx_mfa_enforcement_scope', properties: ['scope', 'tenantId'] })\nexport class MfaEnforcementPolicy {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ type: 'text' })\n scope!: EnforcementScope\n\n @Property({ name: 'tenant_id', type: 'uuid', nullable: true })\n tenantId?: string | null\n\n @Property({ name: 'organization_id', type: 'uuid', nullable: true })\n organizationId?: string | null\n\n @Property({ name: 'is_enforced', type: 'boolean', default: true })\n isEnforced: boolean = true\n\n @Property({ name: 'allowed_methods', type: 'jsonb', nullable: true })\n allowedMethods?: string[] | null\n\n @Property({ name: 'enforcement_deadline', type: Date, nullable: true })\n enforcementDeadline?: Date | null\n\n @Property({ name: 'enforced_by', type: 'uuid' })\n enforcedBy!: string\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onCreate: () => new Date(), onUpdate: () => new Date() })\n updatedAt: Date = new Date()\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'sudo_challenge_configs' })\n@Index({ name: 'idx_sudo_configs_target', properties: ['targetIdentifier'] })\nexport class SudoChallengeConfig {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'tenant_id', type: 'uuid', nullable: true })\n tenantId?: string | null\n\n @Property({ name: 'organization_id', type: 'uuid', nullable: true })\n organizationId?: string | null\n\n @Property({ name: 'label', type: 'text', nullable: true })\n label: string | null = null\n\n @Property({ name: 'target_identifier', type: 'text' })\n targetIdentifier!: string\n\n @Property({ name: 'is_enabled', type: 'boolean', default: true })\n isEnabled: boolean = true\n\n @Property({ name: 'is_developer_default', type: 'boolean', default: false })\n isDeveloperDefault: boolean = false\n\n @Property({ name: 'ttl_seconds', type: 'integer', default: 300 })\n ttlSeconds: number = 300\n\n @Property({ name: 'challenge_method', type: 'text', default: 'auto' })\n challengeMethod: ChallengeMethod = ChallengeMethod.AUTO\n\n @Property({ name: 'configured_by', type: 'uuid', nullable: true })\n configuredBy?: string | null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onCreate: () => new Date(), onUpdate: () => new Date() })\n updatedAt: Date = new Date()\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'sudo_sessions' })\n@Index({ name: 'idx_sudo_sessions_token', properties: ['sessionToken', 'expiresAt'] })\nexport class SudoSession {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'user_id', type: 'uuid' })\n userId!: string\n\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n @Property({ name: 'session_token', type: 'text' })\n sessionToken!: string\n\n @Property({ name: 'challenge_method', type: 'text' })\n challengeMethod!: string\n\n @Property({ name: 'expires_at', type: Date })\n expiresAt!: Date\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n}\n\n@Entity({ tableName: 'mfa_challenges' })\n@Index({ name: 'idx_mfa_challenges_lookup', properties: ['id', 'expiresAt'] })\nexport class MfaChallenge {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'user_id', type: 'uuid' })\n userId!: string\n\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n @Property({ name: 'otp_code_hash', type: 'text', nullable: true })\n otpCodeHash?: string | null\n\n @Property({ name: 'method_type', type: 'text', nullable: true })\n methodType?: string | null\n\n @Property({ name: 'method_id', type: 'uuid', nullable: true })\n methodId?: string | null\n\n @Property({ name: 'provider_challenge', type: 'jsonb', nullable: true })\n providerChallenge?: Record<string, unknown> | null\n\n @Property({ type: 'integer', default: 0 })\n attempts: number = 0\n\n @Property({ name: 'expires_at', type: Date })\n expiresAt!: Date\n\n @Property({ name: 'verified_at', type: Date, nullable: true })\n verifiedAt?: Date | null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n}\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": ["ChallengeMethod", "EnforcementScope", "MfaMethodType", "SudoChallengeMethodUsed"]
|
|
7
7
|
}
|
|
@@ -8,7 +8,7 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
8
8
|
if (kind && result) __defProp(target, key, result);
|
|
9
9
|
return result;
|
|
10
10
|
};
|
|
11
|
-
import { Entity, PrimaryKey, Property, Unique
|
|
11
|
+
import { Entity, Index, PrimaryKey, Property, Unique } from "@mikro-orm/decorators/legacy";
|
|
12
12
|
let SsoConfig = class {
|
|
13
13
|
constructor() {
|
|
14
14
|
this.allowedDomains = [];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/sso/data/entities.ts"],
|
|
4
|
-
"sourcesContent": ["import { Entity, PrimaryKey, Property, Unique
|
|
5
|
-
"mappings": ";;;;;;;;;;AAAA,SAAS,QAAQ,YAAY,UAAU,
|
|
4
|
+
"sourcesContent": ["import { Entity, Index, PrimaryKey, Property, Unique } from '@mikro-orm/decorators/legacy'\n\n@Entity({ tableName: 'sso_configs' })\n// Unique index on organization_id (partial: WHERE deleted_at IS NULL) \u2014 managed by migration\nexport class SsoConfig {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'tenant_id', type: 'uuid', nullable: true })\n tenantId?: string | null\n\n @Property({ name: 'organization_id', type: 'uuid' })\n organizationId!: string\n\n @Property({ type: 'text', nullable: true })\n name?: string | null\n\n @Property({ type: 'text' })\n protocol!: string\n\n @Property({ type: 'text', nullable: true })\n issuer?: string | null\n\n @Property({ name: 'client_id', type: 'text', nullable: true })\n clientId?: string | null\n\n @Property({ name: 'client_secret_enc', type: 'text', nullable: true })\n clientSecretEnc?: string | null\n\n @Property({ name: 'allowed_domains', type: 'jsonb', default: '[]' })\n allowedDomains: string[] = []\n\n @Property({ name: 'jit_enabled', type: 'boolean', default: true })\n jitEnabled: boolean = true\n\n @Property({ name: 'auto_link_by_email', type: 'boolean', default: true })\n autoLinkByEmail: boolean = true\n\n @Property({ name: 'is_active', type: 'boolean', default: false })\n isActive: boolean = false\n\n @Property({ name: 'sso_required', type: 'boolean', default: false })\n ssoRequired: boolean = false\n\n @Property({ name: 'app_role_mappings', type: 'jsonb', default: '{}' })\n appRoleMappings: Record<string, string> = {}\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onCreate: () => new Date(), onUpdate: () => new Date() })\n updatedAt: Date = new Date()\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'sso_identities' })\n// Unique indexes (partial: WHERE deleted_at IS NULL) \u2014 managed by migration\nexport class SsoIdentity {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'tenant_id', type: 'uuid', nullable: true })\n tenantId?: string | null\n\n @Property({ name: 'organization_id', type: 'uuid' })\n organizationId!: string\n\n @Property({ name: 'sso_config_id', type: 'uuid' })\n @Index({ name: 'sso_identities_config_id_idx' })\n ssoConfigId!: string\n\n @Property({ name: 'user_id', type: 'uuid' })\n @Index({ name: 'sso_identities_user_id_idx' })\n userId!: string\n\n @Property({ name: 'idp_subject', type: 'text' })\n idpSubject!: string\n\n @Property({ name: 'idp_email', type: 'text' })\n idpEmail!: string\n\n @Property({ name: 'idp_name', type: 'text', nullable: true })\n idpName?: string | null\n\n @Property({ name: 'idp_groups', type: 'jsonb', default: '[]' })\n idpGroups: string[] = []\n\n @Property({ name: 'external_id', type: 'text', nullable: true })\n externalId?: string | null\n\n @Property({ name: 'provisioning_method', type: 'text' })\n provisioningMethod!: string\n\n @Property({ name: 'first_login_at', type: Date, nullable: true })\n firstLoginAt?: Date | null\n\n @Property({ name: 'last_login_at', type: Date, nullable: true })\n lastLoginAt?: Date | null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onCreate: () => new Date(), onUpdate: () => new Date() })\n updatedAt: Date = new Date()\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'scim_tokens' })\n@Index({ name: 'scim_tokens_token_prefix_idx', properties: ['tokenPrefix'] })\nexport class ScimToken {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'tenant_id', type: 'uuid', nullable: true })\n tenantId?: string | null\n\n @Property({ name: 'organization_id', type: 'uuid' })\n organizationId!: string\n\n @Property({ name: 'sso_config_id', type: 'uuid' })\n @Index({ name: 'scim_tokens_sso_config_id_idx' })\n ssoConfigId!: string\n\n @Property({ type: 'text' })\n name!: string\n\n @Property({ name: 'token_hash', type: 'text' })\n tokenHash!: string\n\n @Property({ name: 'token_prefix', type: 'text' })\n tokenPrefix!: string\n\n @Property({ name: 'is_active', type: 'boolean', default: true })\n isActive: boolean = true\n\n @Property({ name: 'created_by', type: 'uuid', nullable: true })\n createdBy?: string | null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onCreate: () => new Date(), onUpdate: () => new Date() })\n updatedAt: Date = new Date()\n}\n\n@Entity({ tableName: 'sso_user_deactivations' })\n@Unique({ properties: ['userId', 'ssoConfigId'], name: 'sso_user_deactivations_user_config_unique' })\nexport class SsoUserDeactivation {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'tenant_id', type: 'uuid', nullable: true })\n tenantId?: string | null\n\n @Property({ name: 'organization_id', type: 'uuid' })\n organizationId!: string\n\n @Property({ name: 'user_id', type: 'uuid' })\n @Index({ name: 'sso_user_deactivations_user_id_idx' })\n userId!: string\n\n @Property({ name: 'sso_config_id', type: 'uuid' })\n ssoConfigId!: string\n\n @Property({ name: 'deactivated_at', type: Date })\n deactivatedAt: Date = new Date()\n\n @Property({ name: 'reactivated_at', type: Date, nullable: true })\n reactivatedAt?: Date | null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n}\n\n@Entity({ tableName: 'scim_provisioning_log' })\n@Index({ name: 'scim_provisioning_log_config_created_idx', properties: ['ssoConfigId', 'createdAt'] })\nexport class ScimProvisioningLog {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'tenant_id', type: 'uuid', nullable: true })\n tenantId?: string | null\n\n @Property({ name: 'organization_id', type: 'uuid' })\n organizationId!: string\n\n @Property({ name: 'sso_config_id', type: 'uuid' })\n ssoConfigId!: string\n\n @Property({ type: 'text' })\n operation!: string\n\n @Property({ name: 'resource_type', type: 'text' })\n resourceType!: string\n\n @Property({ name: 'resource_id', type: 'uuid', nullable: true })\n resourceId?: string | null\n\n @Property({ name: 'scim_external_id', type: 'text', nullable: true })\n scimExternalId?: string | null\n\n @Property({ name: 'response_status', type: 'integer' })\n responseStatus!: number\n\n @Property({ name: 'error_message', type: 'text', nullable: true })\n errorMessage?: string | null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n}\n\n@Entity({ tableName: 'sso_role_grants' })\n@Unique({ properties: ['userId', 'roleId', 'ssoConfigId'], name: 'sso_role_grants_user_role_config_unique' })\nexport class SsoRoleGrant {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'tenant_id', type: 'uuid', nullable: true })\n tenantId?: string | null\n\n @Property({ name: 'organization_id', type: 'uuid' })\n organizationId!: string\n\n @Property({ name: 'user_id', type: 'uuid' })\n @Index({ name: 'sso_role_grants_user_id_idx' })\n userId!: string\n\n @Property({ name: 'role_id', type: 'uuid' })\n roleId!: string\n\n @Property({ name: 'sso_config_id', type: 'uuid' })\n ssoConfigId!: string\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -92,7 +92,7 @@ class AccountLinkingService {
|
|
|
92
92
|
createdAt: now,
|
|
93
93
|
updatedAt: now
|
|
94
94
|
});
|
|
95
|
-
await this.em.
|
|
95
|
+
await this.em.persist(identity).flush();
|
|
96
96
|
void emitSsoEvent("sso.identity.linked", {
|
|
97
97
|
id: identity.id,
|
|
98
98
|
tenantId,
|
|
@@ -112,7 +112,7 @@ class AccountLinkingService {
|
|
|
112
112
|
isConfirmed: true,
|
|
113
113
|
createdAt: /* @__PURE__ */ new Date()
|
|
114
114
|
});
|
|
115
|
-
await txEm.
|
|
115
|
+
await txEm.persist(user).flush();
|
|
116
116
|
await this.assignRolesFromSso(txEm, user, config, tenantId, idpPayload.groups);
|
|
117
117
|
const now = /* @__PURE__ */ new Date();
|
|
118
118
|
const identity = txEm.create(SsoIdentity, {
|
|
@@ -130,7 +130,7 @@ class AccountLinkingService {
|
|
|
130
130
|
createdAt: now,
|
|
131
131
|
updatedAt: now
|
|
132
132
|
});
|
|
133
|
-
await txEm.
|
|
133
|
+
await txEm.persist(identity).flush();
|
|
134
134
|
void emitSsoEvent("sso.identity.created", {
|
|
135
135
|
id: identity.id,
|
|
136
136
|
tenantId,
|
|
@@ -219,7 +219,7 @@ class AccountLinkingService {
|
|
|
219
219
|
});
|
|
220
220
|
if (existingLink) return;
|
|
221
221
|
const userRole = em.create(UserRole, { user, role, createdAt: /* @__PURE__ */ new Date() });
|
|
222
|
-
await em.
|
|
222
|
+
await em.persist(userRole).flush();
|
|
223
223
|
}
|
|
224
224
|
}
|
|
225
225
|
function resolveRoleNamesFromIdpGroups(idpGroups, configMappings) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/sso/services/accountLinkingService.ts"],
|
|
4
|
-
"sourcesContent": ["import { EntityManager, type FilterQuery, type RequiredEntityData } from '@mikro-orm/postgresql'\nimport { User, UserRole, Role } from '@open-mercato/core/modules/auth/data/entities'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { computeEmailHash } from '@open-mercato/core/modules/auth/lib/emailHash'\nimport { SsoConfig, SsoIdentity, SsoRoleGrant, ScimToken } from '../data/entities'\nimport { emitSsoEvent } from '../events'\nimport type { SsoIdentityPayload } from '../lib/types'\n\nexport class AccountLinkingService {\n constructor(private em: EntityManager) {}\n\n async resolveUser(\n config: SsoConfig,\n idpPayload: SsoIdentityPayload,\n tenantId: string,\n ): Promise<{ user: User; identity: SsoIdentity }> {\n const existing = await this.findExistingLink(config.id, idpPayload.subject, tenantId, config.organizationId)\n if (existing) {\n await this.assignRolesFromSso(this.em, existing.user, config, tenantId, idpPayload.groups)\n return existing\n }\n\n if (idpPayload.emailVerified === false) {\n throw new Error('IdP explicitly reported email as unverified \u2014 cannot link or provision account')\n }\n\n const emailDomain = idpPayload.email.split('@')[1]?.toLowerCase()\n if (!emailDomain || !config.allowedDomains.some((d) => d.toLowerCase() === emailDomain)) {\n throw new Error('Email domain is not in the allowed domains for this SSO configuration')\n }\n\n const emailLinked = config.autoLinkByEmail\n ? await this.linkByEmail(config, idpPayload, tenantId)\n : null\n if (emailLinked) {\n await this.assignRolesFromSso(this.em, emailLinked.user, config, tenantId, idpPayload.groups)\n return emailLinked\n }\n\n if (config.jitEnabled) {\n const scimActive = await this.em.count(ScimToken, { ssoConfigId: config.id, isActive: true }) > 0\n if (scimActive) {\n throw new Error('JIT provisioning is disabled because SCIM directory sync is active')\n }\n return this.jitProvision(config, idpPayload, tenantId)\n }\n\n throw new Error('No matching user found and JIT provisioning is disabled')\n }\n\n private async findExistingLink(\n ssoConfigId: string,\n idpSubject: string,\n tenantId: string,\n organizationId: string,\n ): Promise<{ user: User; identity: SsoIdentity } | null> {\n const identity = await findOneWithDecryption(\n this.em,\n SsoIdentity,\n { ssoConfigId, idpSubject, deletedAt: null },\n {},\n { tenantId, organizationId },\n )\n if (!identity) return null\n\n const user = await findOneWithDecryption(\n this.em,\n User,\n { id: identity.userId, deletedAt: null },\n {},\n { tenantId, organizationId },\n )\n if (!user) {\n identity.deletedAt = new Date()\n await this.em.flush()\n return null\n }\n\n identity.lastLoginAt = new Date()\n await this.em.flush()\n\n return { user, identity }\n }\n\n private async linkByEmail(\n config: SsoConfig,\n idpPayload: SsoIdentityPayload,\n tenantId: string,\n ): Promise<{ user: User; identity: SsoIdentity } | null> {\n const emailHash = computeEmailHash(idpPayload.email)\n const user = await findOneWithDecryption(\n this.em,\n User,\n {\n organizationId: config.organizationId,\n deletedAt: null,\n $or: [\n { email: idpPayload.email },\n { emailHash },\n ],\n } as FilterQuery<User>,\n {},\n { tenantId, organizationId: config.organizationId },\n )\n if (!user) return null\n\n const now = new Date()\n const identity = this.em.create(SsoIdentity, {\n tenantId,\n organizationId: config.organizationId,\n ssoConfigId: config.id,\n userId: user.id,\n idpSubject: idpPayload.subject,\n idpEmail: idpPayload.email,\n idpName: idpPayload.name ?? null,\n idpGroups: idpPayload.groups ?? [],\n provisioningMethod: 'manual',\n firstLoginAt: now,\n lastLoginAt: now,\n createdAt: now,\n updatedAt: now,\n } as RequiredEntityData<SsoIdentity>)\n await this.em.persistAndFlush(identity)\n\n void emitSsoEvent('sso.identity.linked', {\n id: identity.id,\n tenantId,\n organizationId: config.organizationId,\n }).catch((e) => console.error('[SSO Event]', e))\n\n return { user, identity }\n }\n\n private async jitProvision(\n config: SsoConfig,\n idpPayload: SsoIdentityPayload,\n tenantId: string,\n ): Promise<{ user: User; identity: SsoIdentity }> {\n return this.em.transactional(async (txEm) => {\n const user = txEm.create(User, {\n tenantId,\n organizationId: config.organizationId,\n email: idpPayload.email,\n emailHash: computeEmailHash(idpPayload.email),\n name: idpPayload.name ?? null,\n passwordHash: null,\n isConfirmed: true,\n createdAt: new Date(),\n })\n await txEm.persistAndFlush(user)\n\n await this.assignRolesFromSso(txEm, user, config, tenantId, idpPayload.groups)\n\n const now = new Date()\n const identity = txEm.create(SsoIdentity, {\n tenantId,\n organizationId: config.organizationId,\n ssoConfigId: config.id,\n userId: user.id,\n idpSubject: idpPayload.subject,\n idpEmail: idpPayload.email,\n idpName: idpPayload.name ?? null,\n idpGroups: idpPayload.groups ?? [],\n provisioningMethod: 'jit',\n firstLoginAt: now,\n lastLoginAt: now,\n createdAt: now,\n updatedAt: now,\n } as RequiredEntityData<SsoIdentity>)\n await txEm.persistAndFlush(identity)\n\n void emitSsoEvent('sso.identity.created', {\n id: identity.id,\n tenantId,\n organizationId: config.organizationId,\n }).catch((e) => console.error('[SSO Event]', e))\n\n return { user, identity }\n })\n }\n\n private async assignRolesFromSso(\n em: EntityManager,\n user: User,\n config: SsoConfig,\n tenantId: string,\n idpGroups?: string[],\n ): Promise<void> {\n const hasMappings = config.appRoleMappings && Object.keys(config.appRoleMappings).length > 0\n if (!hasMappings) return\n\n await this.syncMappedRoles(em, user, config, tenantId, idpGroups)\n\n const hasAnySsoRole = await em.findOne(SsoRoleGrant, {\n userId: user.id,\n ssoConfigId: config.id,\n })\n if (!hasAnySsoRole) {\n throw new Error('No roles could be resolved from IdP groups \u2014 login denied. Configure role mappings or ensure the IdP sends matching group claims.')\n }\n }\n\n /**\n * Sync/replace SSO-sourced roles: on each login, SSO-managed roles are replaced\n * with what the IdP sends, while manually-assigned roles are preserved.\n */\n private async syncMappedRoles(\n em: EntityManager,\n user: User,\n config: SsoConfig,\n tenantId: string,\n idpGroups?: string[],\n ): Promise<void> {\n const resolvedTenantId = tenantId || user.tenantId || ''\n if (!resolvedTenantId) return\n\n const allRoles = await em.find(Role, { tenantId: resolvedTenantId, deletedAt: null } as FilterQuery<Role>)\n const roleByNormalizedName = new Map<string, Role>()\n for (const role of allRoles) {\n const normalized = normalizeToken(role.name)\n if (normalized) roleByNormalizedName.set(normalized, role)\n }\n\n // Resolve desired role IDs from IdP groups using merged mappings\n const desiredRoleNames = resolveRoleNamesFromIdpGroups(idpGroups, config.appRoleMappings)\n const desiredRoleIds = new Set<string>()\n for (const roleName of desiredRoleNames) {\n const role = roleByNormalizedName.get(roleName)\n if (role) desiredRoleIds.add(role.id)\n }\n\n // Query current SSO grants for this user+config\n const existingGrants = await em.find(SsoRoleGrant, {\n userId: user.id,\n ssoConfigId: config.id,\n })\n const existingGrantedRoleIds = new Set(existingGrants.map((g) => g.roleId))\n\n // Compute diff\n const toAdd = [...desiredRoleIds].filter((id) => !existingGrantedRoleIds.has(id))\n const toRemove = existingGrants.filter((g) => !desiredRoleIds.has(g.roleId))\n\n // Add new roles\n for (const roleId of toAdd) {\n const role = allRoles.find((r) => r.id === roleId)\n if (!role) continue\n await this.ensureUserRole(em, user, role)\n const grant = em.create(SsoRoleGrant, {\n tenantId: resolvedTenantId,\n organizationId: config.organizationId,\n userId: user.id,\n roleId,\n ssoConfigId: config.id,\n } as RequiredEntityData<SsoRoleGrant>)\n em.persist(grant)\n }\n\n // Remove stale SSO-sourced roles\n for (const grant of toRemove) {\n const userRole = await em.findOne(UserRole, {\n user: user.id,\n role: grant.roleId,\n deletedAt: null,\n } as FilterQuery<UserRole>)\n if (userRole) {\n em.remove(userRole)\n }\n em.remove(grant)\n }\n\n // Clean up orphaned soft-deleted UserRole rows (ghost rows from previous soft-delete logic)\n const allUserRoles = await em.find(UserRole, { user: user.id } as FilterQuery<UserRole>)\n for (const ur of allUserRoles) {\n if (ur.deletedAt) {\n em.remove(ur)\n }\n }\n\n if (toAdd.length > 0 || toRemove.length > 0 || allUserRoles.some((ur) => ur.deletedAt)) {\n await em.flush()\n }\n }\n\n private async ensureUserRole(em: EntityManager, user: User, role: Role): Promise<void> {\n const existingLink = await em.findOne(UserRole, {\n user: user.id,\n role: role.id,\n deletedAt: null,\n } as FilterQuery<UserRole>)\n if (existingLink) return\n\n const userRole = em.create(UserRole, { user, role, createdAt: new Date() })\n await em.persistAndFlush(userRole)\n }\n}\n\nfunction resolveRoleNamesFromIdpGroups(\n idpGroups?: string[],\n configMappings?: Record<string, string>,\n): string[] {\n if (!Array.isArray(idpGroups) || idpGroups.length === 0) return []\n\n const normalizedGroups = idpGroups\n .map((group) => normalizeToken(group))\n .filter((group): group is string => group !== null)\n if (normalizedGroups.length === 0) return []\n\n const mergedMappings = loadMergedMappings(configMappings)\n const roleNames = new Set<string>()\n\n for (const group of normalizedGroups) {\n const mapped = mergedMappings.get(group)\n if (mapped?.length) {\n for (const role of mapped) roleNames.add(role)\n continue\n }\n\n roleNames.add(group)\n const segmented = group.split(/[\\\\/:]/).map((part) => normalizeToken(part)).filter((part): part is string => part !== null)\n for (const candidate of segmented) {\n roleNames.add(candidate)\n }\n }\n\n return Array.from(roleNames)\n}\n\nfunction loadMergedMappings(configMappings?: Record<string, string>): Map<string, string[]> {\n const envMappings = loadGroupRoleMappingsFromEnv()\n\n // Per-config mappings take precedence over env var\n if (configMappings && Object.keys(configMappings).length > 0) {\n for (const [group, roleName] of Object.entries(configMappings)) {\n const normalizedGroup = normalizeToken(group)\n if (!normalizedGroup) continue\n const normalizedRole = normalizeToken(roleName)\n if (!normalizedRole) continue\n envMappings.set(normalizedGroup, [normalizedRole])\n }\n }\n\n return envMappings\n}\n\nfunction loadGroupRoleMappingsFromEnv(): Map<string, string[]> {\n const raw = process.env.SSO_GROUP_ROLE_MAP\n if (!raw) return new Map()\n\n try {\n const parsed = JSON.parse(raw) as Record<string, unknown>\n const out = new Map<string, string[]>()\n for (const [group, roleValue] of Object.entries(parsed)) {\n const normalizedGroup = normalizeToken(group)\n if (!normalizedGroup) continue\n const roles = normalizeRoleList(roleValue)\n if (roles.length > 0) out.set(normalizedGroup, roles)\n }\n return out\n } catch {\n return new Map()\n }\n}\n\nfunction normalizeRoleList(value: unknown): string[] {\n if (typeof value === 'string') {\n const token = normalizeToken(value)\n return token ? [token] : []\n }\n\n if (Array.isArray(value)) {\n const out = new Set<string>()\n for (const entry of value) {\n const token = normalizeToken(entry)\n if (token) out.add(token)\n }\n return Array.from(out)\n }\n\n return []\n}\n\nfunction normalizeToken(value: unknown): string | null {\n if (typeof value !== 'string') return null\n const normalized = value.trim().toLowerCase()\n return normalized.length > 0 ? normalized : null\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import { EntityManager, type FilterQuery, type RequiredEntityData } from '@mikro-orm/postgresql'\nimport { User, UserRole, Role } from '@open-mercato/core/modules/auth/data/entities'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { computeEmailHash } from '@open-mercato/core/modules/auth/lib/emailHash'\nimport { SsoConfig, SsoIdentity, SsoRoleGrant, ScimToken } from '../data/entities'\nimport { emitSsoEvent } from '../events'\nimport type { SsoIdentityPayload } from '../lib/types'\n\nexport class AccountLinkingService {\n constructor(private em: EntityManager) {}\n\n async resolveUser(\n config: SsoConfig,\n idpPayload: SsoIdentityPayload,\n tenantId: string,\n ): Promise<{ user: User; identity: SsoIdentity }> {\n const existing = await this.findExistingLink(config.id, idpPayload.subject, tenantId, config.organizationId)\n if (existing) {\n await this.assignRolesFromSso(this.em, existing.user, config, tenantId, idpPayload.groups)\n return existing\n }\n\n if (idpPayload.emailVerified === false) {\n throw new Error('IdP explicitly reported email as unverified \u2014 cannot link or provision account')\n }\n\n const emailDomain = idpPayload.email.split('@')[1]?.toLowerCase()\n if (!emailDomain || !config.allowedDomains.some((d) => d.toLowerCase() === emailDomain)) {\n throw new Error('Email domain is not in the allowed domains for this SSO configuration')\n }\n\n const emailLinked = config.autoLinkByEmail\n ? await this.linkByEmail(config, idpPayload, tenantId)\n : null\n if (emailLinked) {\n await this.assignRolesFromSso(this.em, emailLinked.user, config, tenantId, idpPayload.groups)\n return emailLinked\n }\n\n if (config.jitEnabled) {\n const scimActive = await this.em.count(ScimToken, { ssoConfigId: config.id, isActive: true }) > 0\n if (scimActive) {\n throw new Error('JIT provisioning is disabled because SCIM directory sync is active')\n }\n return this.jitProvision(config, idpPayload, tenantId)\n }\n\n throw new Error('No matching user found and JIT provisioning is disabled')\n }\n\n private async findExistingLink(\n ssoConfigId: string,\n idpSubject: string,\n tenantId: string,\n organizationId: string,\n ): Promise<{ user: User; identity: SsoIdentity } | null> {\n const identity = await findOneWithDecryption(\n this.em,\n SsoIdentity,\n { ssoConfigId, idpSubject, deletedAt: null },\n {},\n { tenantId, organizationId },\n )\n if (!identity) return null\n\n const user = await findOneWithDecryption(\n this.em,\n User,\n { id: identity.userId, deletedAt: null },\n {},\n { tenantId, organizationId },\n )\n if (!user) {\n identity.deletedAt = new Date()\n await this.em.flush()\n return null\n }\n\n identity.lastLoginAt = new Date()\n await this.em.flush()\n\n return { user, identity }\n }\n\n private async linkByEmail(\n config: SsoConfig,\n idpPayload: SsoIdentityPayload,\n tenantId: string,\n ): Promise<{ user: User; identity: SsoIdentity } | null> {\n const emailHash = computeEmailHash(idpPayload.email)\n const user = await findOneWithDecryption(\n this.em,\n User,\n {\n organizationId: config.organizationId,\n deletedAt: null,\n $or: [\n { email: idpPayload.email },\n { emailHash },\n ],\n } as FilterQuery<User>,\n {},\n { tenantId, organizationId: config.organizationId },\n )\n if (!user) return null\n\n const now = new Date()\n const identity = this.em.create(SsoIdentity, {\n tenantId,\n organizationId: config.organizationId,\n ssoConfigId: config.id,\n userId: user.id,\n idpSubject: idpPayload.subject,\n idpEmail: idpPayload.email,\n idpName: idpPayload.name ?? null,\n idpGroups: idpPayload.groups ?? [],\n provisioningMethod: 'manual',\n firstLoginAt: now,\n lastLoginAt: now,\n createdAt: now,\n updatedAt: now,\n } as RequiredEntityData<SsoIdentity>)\n await this.em.persist(identity).flush()\n\n void emitSsoEvent('sso.identity.linked', {\n id: identity.id,\n tenantId,\n organizationId: config.organizationId,\n }).catch((e) => console.error('[SSO Event]', e))\n\n return { user, identity }\n }\n\n private async jitProvision(\n config: SsoConfig,\n idpPayload: SsoIdentityPayload,\n tenantId: string,\n ): Promise<{ user: User; identity: SsoIdentity }> {\n return this.em.transactional(async (txEm) => {\n const user = txEm.create(User, {\n tenantId,\n organizationId: config.organizationId,\n email: idpPayload.email,\n emailHash: computeEmailHash(idpPayload.email),\n name: idpPayload.name ?? null,\n passwordHash: null,\n isConfirmed: true,\n createdAt: new Date(),\n })\n await txEm.persist(user).flush()\n\n await this.assignRolesFromSso(txEm, user, config, tenantId, idpPayload.groups)\n\n const now = new Date()\n const identity = txEm.create(SsoIdentity, {\n tenantId,\n organizationId: config.organizationId,\n ssoConfigId: config.id,\n userId: user.id,\n idpSubject: idpPayload.subject,\n idpEmail: idpPayload.email,\n idpName: idpPayload.name ?? null,\n idpGroups: idpPayload.groups ?? [],\n provisioningMethod: 'jit',\n firstLoginAt: now,\n lastLoginAt: now,\n createdAt: now,\n updatedAt: now,\n } as RequiredEntityData<SsoIdentity>)\n await txEm.persist(identity).flush()\n\n void emitSsoEvent('sso.identity.created', {\n id: identity.id,\n tenantId,\n organizationId: config.organizationId,\n }).catch((e) => console.error('[SSO Event]', e))\n\n return { user, identity }\n })\n }\n\n private async assignRolesFromSso(\n em: EntityManager,\n user: User,\n config: SsoConfig,\n tenantId: string,\n idpGroups?: string[],\n ): Promise<void> {\n const hasMappings = config.appRoleMappings && Object.keys(config.appRoleMappings).length > 0\n if (!hasMappings) return\n\n await this.syncMappedRoles(em, user, config, tenantId, idpGroups)\n\n const hasAnySsoRole = await em.findOne(SsoRoleGrant, {\n userId: user.id,\n ssoConfigId: config.id,\n })\n if (!hasAnySsoRole) {\n throw new Error('No roles could be resolved from IdP groups \u2014 login denied. Configure role mappings or ensure the IdP sends matching group claims.')\n }\n }\n\n /**\n * Sync/replace SSO-sourced roles: on each login, SSO-managed roles are replaced\n * with what the IdP sends, while manually-assigned roles are preserved.\n */\n private async syncMappedRoles(\n em: EntityManager,\n user: User,\n config: SsoConfig,\n tenantId: string,\n idpGroups?: string[],\n ): Promise<void> {\n const resolvedTenantId = tenantId || user.tenantId || ''\n if (!resolvedTenantId) return\n\n const allRoles = await em.find(Role, { tenantId: resolvedTenantId, deletedAt: null } as FilterQuery<Role>)\n const roleByNormalizedName = new Map<string, Role>()\n for (const role of allRoles) {\n const normalized = normalizeToken(role.name)\n if (normalized) roleByNormalizedName.set(normalized, role)\n }\n\n // Resolve desired role IDs from IdP groups using merged mappings\n const desiredRoleNames = resolveRoleNamesFromIdpGroups(idpGroups, config.appRoleMappings)\n const desiredRoleIds = new Set<string>()\n for (const roleName of desiredRoleNames) {\n const role = roleByNormalizedName.get(roleName)\n if (role) desiredRoleIds.add(role.id)\n }\n\n // Query current SSO grants for this user+config\n const existingGrants = await em.find(SsoRoleGrant, {\n userId: user.id,\n ssoConfigId: config.id,\n })\n const existingGrantedRoleIds = new Set(existingGrants.map((g) => g.roleId))\n\n // Compute diff\n const toAdd = [...desiredRoleIds].filter((id) => !existingGrantedRoleIds.has(id))\n const toRemove = existingGrants.filter((g) => !desiredRoleIds.has(g.roleId))\n\n // Add new roles\n for (const roleId of toAdd) {\n const role = allRoles.find((r) => r.id === roleId)\n if (!role) continue\n await this.ensureUserRole(em, user, role)\n const grant = em.create(SsoRoleGrant, {\n tenantId: resolvedTenantId,\n organizationId: config.organizationId,\n userId: user.id,\n roleId,\n ssoConfigId: config.id,\n } as RequiredEntityData<SsoRoleGrant>)\n em.persist(grant)\n }\n\n // Remove stale SSO-sourced roles\n for (const grant of toRemove) {\n const userRole = await em.findOne(UserRole, {\n user: user.id,\n role: grant.roleId,\n deletedAt: null,\n } as FilterQuery<UserRole>)\n if (userRole) {\n em.remove(userRole)\n }\n em.remove(grant)\n }\n\n // Clean up orphaned soft-deleted UserRole rows (ghost rows from previous soft-delete logic)\n const allUserRoles = await em.find(UserRole, { user: user.id } as FilterQuery<UserRole>)\n for (const ur of allUserRoles) {\n if (ur.deletedAt) {\n em.remove(ur)\n }\n }\n\n if (toAdd.length > 0 || toRemove.length > 0 || allUserRoles.some((ur) => ur.deletedAt)) {\n await em.flush()\n }\n }\n\n private async ensureUserRole(em: EntityManager, user: User, role: Role): Promise<void> {\n const existingLink = await em.findOne(UserRole, {\n user: user.id,\n role: role.id,\n deletedAt: null,\n } as FilterQuery<UserRole>)\n if (existingLink) return\n\n const userRole = em.create(UserRole, { user, role, createdAt: new Date() })\n await em.persist(userRole).flush()\n }\n}\n\nfunction resolveRoleNamesFromIdpGroups(\n idpGroups?: string[],\n configMappings?: Record<string, string>,\n): string[] {\n if (!Array.isArray(idpGroups) || idpGroups.length === 0) return []\n\n const normalizedGroups = idpGroups\n .map((group) => normalizeToken(group))\n .filter((group): group is string => group !== null)\n if (normalizedGroups.length === 0) return []\n\n const mergedMappings = loadMergedMappings(configMappings)\n const roleNames = new Set<string>()\n\n for (const group of normalizedGroups) {\n const mapped = mergedMappings.get(group)\n if (mapped?.length) {\n for (const role of mapped) roleNames.add(role)\n continue\n }\n\n roleNames.add(group)\n const segmented = group.split(/[\\\\/:]/).map((part) => normalizeToken(part)).filter((part): part is string => part !== null)\n for (const candidate of segmented) {\n roleNames.add(candidate)\n }\n }\n\n return Array.from(roleNames)\n}\n\nfunction loadMergedMappings(configMappings?: Record<string, string>): Map<string, string[]> {\n const envMappings = loadGroupRoleMappingsFromEnv()\n\n // Per-config mappings take precedence over env var\n if (configMappings && Object.keys(configMappings).length > 0) {\n for (const [group, roleName] of Object.entries(configMappings)) {\n const normalizedGroup = normalizeToken(group)\n if (!normalizedGroup) continue\n const normalizedRole = normalizeToken(roleName)\n if (!normalizedRole) continue\n envMappings.set(normalizedGroup, [normalizedRole])\n }\n }\n\n return envMappings\n}\n\nfunction loadGroupRoleMappingsFromEnv(): Map<string, string[]> {\n const raw = process.env.SSO_GROUP_ROLE_MAP\n if (!raw) return new Map()\n\n try {\n const parsed = JSON.parse(raw) as Record<string, unknown>\n const out = new Map<string, string[]>()\n for (const [group, roleValue] of Object.entries(parsed)) {\n const normalizedGroup = normalizeToken(group)\n if (!normalizedGroup) continue\n const roles = normalizeRoleList(roleValue)\n if (roles.length > 0) out.set(normalizedGroup, roles)\n }\n return out\n } catch {\n return new Map()\n }\n}\n\nfunction normalizeRoleList(value: unknown): string[] {\n if (typeof value === 'string') {\n const token = normalizeToken(value)\n return token ? [token] : []\n }\n\n if (Array.isArray(value)) {\n const out = new Set<string>()\n for (const entry of value) {\n const token = normalizeToken(entry)\n if (token) out.add(token)\n }\n return Array.from(out)\n }\n\n return []\n}\n\nfunction normalizeToken(value: unknown): string | null {\n if (typeof value !== 'string') return null\n const normalized = value.trim().toLowerCase()\n return normalized.length > 0 ? normalized : null\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sql } from "kysely";
|
|
1
2
|
import { SsoConfig } from "../data/entities.js";
|
|
2
3
|
class HrdService {
|
|
3
4
|
constructor(em) {
|
|
@@ -6,8 +7,8 @@ class HrdService {
|
|
|
6
7
|
async findActiveConfigByEmailDomain(email) {
|
|
7
8
|
const domain = email.split("@")[1]?.toLowerCase();
|
|
8
9
|
if (!domain) return null;
|
|
9
|
-
const
|
|
10
|
-
const row = await
|
|
10
|
+
const db = this.em.getKysely();
|
|
11
|
+
const row = await db.selectFrom("sso_configs").selectAll().where(sql`allowed_domains @> ${JSON.stringify([domain])}::jsonb`).where("is_active", "=", true).where("deleted_at", "is", null).executeTakeFirst();
|
|
11
12
|
if (!row) return null;
|
|
12
13
|
return this.em.map(SsoConfig, row);
|
|
13
14
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/sso/services/hrdService.ts"],
|
|
4
|
-
"sourcesContent": ["import { EntityManager } from '@mikro-orm/postgresql'\nimport { SsoConfig } from '../data/entities'\n\nexport class HrdService {\n constructor(private em: EntityManager) {}\n\n async findActiveConfigByEmailDomain(email: string): Promise<SsoConfig | null> {\n const domain = email.split('@')[1]?.toLowerCase()\n if (!domain) return null\n\n const
|
|
5
|
-
"mappings": "AACA,SAAS,iBAAiB;AAEnB,MAAM,WAAW;AAAA,EACtB,YAAoB,IAAmB;AAAnB;AAAA,EAAoB;AAAA,EAExC,MAAM,8BAA8B,OAA0C;AAC5E,UAAM,SAAS,MAAM,MAAM,GAAG,EAAE,CAAC,GAAG,YAAY;AAChD,QAAI,CAAC,OAAQ,QAAO;AAEpB,UAAM,
|
|
4
|
+
"sourcesContent": ["import { EntityManager } from '@mikro-orm/postgresql'\nimport { type Kysely, sql } from 'kysely'\nimport { SsoConfig } from '../data/entities'\n\nexport class HrdService {\n constructor(private em: EntityManager) {}\n\n async findActiveConfigByEmailDomain(email: string): Promise<SsoConfig | null> {\n const domain = email.split('@')[1]?.toLowerCase()\n if (!domain) return null\n\n const db = (this.em as any).getKysely() as Kysely<any>\n const row = await db\n .selectFrom('sso_configs' as any)\n .selectAll()\n .where(sql<boolean>`allowed_domains @> ${JSON.stringify([domain])}::jsonb`)\n .where('is_active' as any, '=', true)\n .where('deleted_at' as any, 'is', null as any)\n .executeTakeFirst()\n\n if (!row) return null\n\n return this.em.map(SsoConfig, row as Record<string, unknown>)\n }\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAsB,WAAW;AACjC,SAAS,iBAAiB;AAEnB,MAAM,WAAW;AAAA,EACtB,YAAoB,IAAmB;AAAnB;AAAA,EAAoB;AAAA,EAExC,MAAM,8BAA8B,OAA0C;AAC5E,UAAM,SAAS,MAAM,MAAM,GAAG,EAAE,CAAC,GAAG,YAAY;AAChD,QAAI,CAAC,OAAQ,QAAO;AAEpB,UAAM,KAAM,KAAK,GAAW,UAAU;AACtC,UAAM,MAAM,MAAM,GACf,WAAW,aAAoB,EAC/B,UAAU,EACV,MAAM,yBAAkC,KAAK,UAAU,CAAC,MAAM,CAAC,CAAC,SAAS,EACzE,MAAM,aAAoB,KAAK,IAAI,EACnC,MAAM,cAAqB,MAAM,IAAW,EAC5C,iBAAiB;AAEpB,QAAI,CAAC,IAAK,QAAO;AAEjB,WAAO,KAAK,GAAG,IAAI,WAAW,GAA8B;AAAA,EAC9D;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -80,7 +80,7 @@ class ScimService {
|
|
|
80
80
|
createdAt: now,
|
|
81
81
|
updatedAt: now
|
|
82
82
|
});
|
|
83
|
-
await this.em.
|
|
83
|
+
await this.em.persist(identity).flush();
|
|
84
84
|
const deactivation = parsed.active === false ? await this.createDeactivation(existingUser.id, scope) : null;
|
|
85
85
|
await this.log(scope, "CREATE", identity.id, parsed.externalId, 201);
|
|
86
86
|
return {
|
|
@@ -99,7 +99,7 @@ class ScimService {
|
|
|
99
99
|
isConfirmed: true,
|
|
100
100
|
createdAt: /* @__PURE__ */ new Date()
|
|
101
101
|
});
|
|
102
|
-
await txEm.
|
|
102
|
+
await txEm.persist(user).flush();
|
|
103
103
|
const now = /* @__PURE__ */ new Date();
|
|
104
104
|
const identity = txEm.create(SsoIdentity, {
|
|
105
105
|
tenantId: scope.tenantId ?? null,
|
|
@@ -115,7 +115,7 @@ class ScimService {
|
|
|
115
115
|
createdAt: now,
|
|
116
116
|
updatedAt: now
|
|
117
117
|
});
|
|
118
|
-
await txEm.
|
|
118
|
+
await txEm.persist(identity).flush();
|
|
119
119
|
const deactivation = parsed.active === false ? await this.createDeactivationTx(txEm, user.id, scope) : null;
|
|
120
120
|
await this.logTx(txEm, scope, "CREATE", identity.id, parsed.externalId, 201);
|
|
121
121
|
return {
|
|
@@ -311,7 +311,7 @@ class ScimService {
|
|
|
311
311
|
ssoConfigId: scope.ssoConfigId,
|
|
312
312
|
deactivatedAt: /* @__PURE__ */ new Date()
|
|
313
313
|
});
|
|
314
|
-
await this.em.
|
|
314
|
+
await this.em.persist(deactivation).flush();
|
|
315
315
|
return deactivation;
|
|
316
316
|
}
|
|
317
317
|
async createDeactivationTx(txEm, userId, scope) {
|
|
@@ -322,7 +322,7 @@ class ScimService {
|
|
|
322
322
|
ssoConfigId: scope.ssoConfigId,
|
|
323
323
|
deactivatedAt: /* @__PURE__ */ new Date()
|
|
324
324
|
});
|
|
325
|
-
await txEm.
|
|
325
|
+
await txEm.persist(deactivation).flush();
|
|
326
326
|
return deactivation;
|
|
327
327
|
}
|
|
328
328
|
async log(scope, operation, resourceId, externalId, responseStatus, errorMessage) {
|
|
@@ -337,7 +337,7 @@ class ScimService {
|
|
|
337
337
|
responseStatus,
|
|
338
338
|
errorMessage: errorMessage ?? null
|
|
339
339
|
});
|
|
340
|
-
await this.em.
|
|
340
|
+
await this.em.persist(entry).flush();
|
|
341
341
|
}
|
|
342
342
|
async logTx(txEm, scope, operation, resourceId, externalId, responseStatus) {
|
|
343
343
|
const entry = txEm.create(ScimProvisioningLog, {
|
|
@@ -350,7 +350,7 @@ class ScimService {
|
|
|
350
350
|
scimExternalId: externalId ?? null,
|
|
351
351
|
responseStatus
|
|
352
352
|
});
|
|
353
|
-
await txEm.
|
|
353
|
+
await txEm.persist(entry).flush();
|
|
354
354
|
}
|
|
355
355
|
}
|
|
356
356
|
function buildDisplayName(parsed) {
|
|
@@ -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.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": "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,
|
|
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": "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,QAAI,eAAe,MAAM,KAAK,GAAG,QAAQ,qBAAqB;AAAA,MAC5D;AAAA,MAAQ,aAAa,MAAM;AAAA,IAC7B,CAAC;AAED,QAAI,cAAc;AAChB,mBAAa,gBAAgB,oBAAI,KAAK;AACtC,mBAAa,gBAAgB;AAAA,IAC/B,OAAO;AACL,qBAAe,KAAK,GAAG,OAAO,qBAAqB;AAAA,QACjD,UAAU,MAAM,YAAY;AAAA,QAC5B,gBAAgB,MAAM;AAAA,QACtB;AAAA,QACA,aAAa,MAAM;AAAA,QACnB,eAAe,oBAAI,KAAK;AAAA,MAC1B,CAA4C;AAC5C,WAAK,GAAG,QAAQ,YAAY;AAAA,IAC9B;AACA,UAAM,KAAK,GAAG,MAAM;AAGpB,UAAM,eAAqC,EAAE,MAAM,OAAO;AAC1D,UAAM,KAAK,GAAG,aAAa,SAAS,YAAY;AAAA,EAClD;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
|
}
|
|
@@ -30,7 +30,7 @@ class ScimTokenService {
|
|
|
30
30
|
tenantId: scope.tenantId,
|
|
31
31
|
organizationId: scope.organizationId
|
|
32
32
|
});
|
|
33
|
-
await this.em.
|
|
33
|
+
await this.em.persist(token).flush();
|
|
34
34
|
return { id: token.id, token: raw, prefix: tokenPrefix, name };
|
|
35
35
|
}
|
|
36
36
|
async verifyToken(rawToken) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
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.
|
|
5
|
-
"mappings": "AAAA,SAAS,mBAAmB;AAG5B,SAAS,MAAM,eAAe;AAC9B,SAAS,WAAW,iBAAiB;AAGrC,MAAM,cAAc;AACpB,MAAM,eAAe;AAmBd,MAAM,iBAAiB;AAAA,EAC5B,YAAoB,IAAmB;AAAnB;AAAA,EAAoB;AAAA,EAExC,MAAM,cACJ,aACA,MACA,OACgC;AAChC,UAAM,QAAiC,EAAE,IAAI,aAAa,WAAW,KAAK;AAC1E,QAAI,CAAC,MAAM,gBAAgB,MAAM,gBAAgB;AAC/C,YAAM,iBAAiB,MAAM;AAAA,IAC/B;AACA,UAAM,SAAS,MAAM,KAAK,GAAG,QAAQ,WAAW,KAAK;AACrD,QAAI,CAAC,OAAQ,OAAM,IAAI,eAAe,+BAA+B,GAAG;AACxE,QAAI,OAAO,YAAY;AACrB,YAAM,IAAI,eAAe,mFAAmF,GAAG;AAAA,IACjH;AAEA,UAAM,MAAM,eAAe,YAAY,EAAE,EAAE,SAAS,KAAK;AACzD,UAAM,YAAY,MAAM,KAAK,KAAK,WAAW;AAC7C,UAAM,cAAc,IAAI,MAAM,GAAG,EAAE;AAEnC,UAAM,QAAQ,KAAK,GAAG,OAAO,WAAW;AAAA,MACtC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU;AAAA,MACV,WAAW;AAAA,MACX,UAAU,MAAM;AAAA,MAChB,gBAAgB,MAAM;AAAA,IACxB,CAAkC;AAElC,UAAM,KAAK,GAAG,
|
|
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.persist(token).flush()\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
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -83,7 +83,7 @@ class SsoConfigService {
|
|
|
83
83
|
ssoRequired: false,
|
|
84
84
|
appRoleMappings: input.appRoleMappings ?? {}
|
|
85
85
|
});
|
|
86
|
-
await this.em.
|
|
86
|
+
await this.em.persist(config).flush();
|
|
87
87
|
void emitSsoEvent("sso.config.created", {
|
|
88
88
|
id: config.id,
|
|
89
89
|
tenantId: config.tenantId,
|