@open-mercato/enterprise 0.4.6-develop-15c18897fc → 0.4.6-develop-34aa847ce6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1 -1
- package/dist/index.js.map +2 -2
- package/dist/modules/sso/acl.js +11 -0
- package/dist/modules/sso/acl.js.map +7 -0
- package/dist/modules/sso/api/admin-context.js +27 -0
- package/dist/modules/sso/api/admin-context.js.map +7 -0
- package/dist/modules/sso/api/callback/oidc/route.js +103 -0
- package/dist/modules/sso/api/callback/oidc/route.js.map +7 -0
- package/dist/modules/sso/api/config/[id]/activate/route.js +49 -0
- package/dist/modules/sso/api/config/[id]/activate/route.js.map +7 -0
- package/dist/modules/sso/api/config/[id]/domains/route.js +96 -0
- package/dist/modules/sso/api/config/[id]/domains/route.js.map +7 -0
- package/dist/modules/sso/api/config/[id]/route.js +103 -0
- package/dist/modules/sso/api/config/[id]/route.js.map +7 -0
- package/dist/modules/sso/api/config/[id]/test/route.js +41 -0
- package/dist/modules/sso/api/config/[id]/test/route.js.map +7 -0
- package/dist/modules/sso/api/config/route.js +83 -0
- package/dist/modules/sso/api/config/route.js.map +7 -0
- package/dist/modules/sso/api/error-handler.js +28 -0
- package/dist/modules/sso/api/error-handler.js.map +7 -0
- package/dist/modules/sso/api/hrd/route.js +52 -0
- package/dist/modules/sso/api/hrd/route.js.map +7 -0
- package/dist/modules/sso/api/initiate/route.js +66 -0
- package/dist/modules/sso/api/initiate/route.js.map +7 -0
- package/dist/modules/sso/api/scim/context.js +68 -0
- package/dist/modules/sso/api/scim/context.js.map +7 -0
- package/dist/modules/sso/api/scim/logs/route.js +65 -0
- package/dist/modules/sso/api/scim/logs/route.js.map +7 -0
- package/dist/modules/sso/api/scim/tokens/[id]/route.js +42 -0
- package/dist/modules/sso/api/scim/tokens/[id]/route.js.map +7 -0
- package/dist/modules/sso/api/scim/tokens/route.js +83 -0
- package/dist/modules/sso/api/scim/tokens/route.js.map +7 -0
- package/dist/modules/sso/api/scim/v2/ServiceProviderConfig/route.js +42 -0
- package/dist/modules/sso/api/scim/v2/ServiceProviderConfig/route.js.map +7 -0
- package/dist/modules/sso/api/scim/v2/Users/[id]/route.js +94 -0
- package/dist/modules/sso/api/scim/v2/Users/[id]/route.js.map +7 -0
- package/dist/modules/sso/api/scim/v2/Users/route.js +86 -0
- package/dist/modules/sso/api/scim/v2/Users/route.js.map +7 -0
- package/dist/modules/sso/backend/page.js +173 -0
- package/dist/modules/sso/backend/page.js.map +7 -0
- package/dist/modules/sso/backend/page.meta.js +31 -0
- package/dist/modules/sso/backend/page.meta.js.map +7 -0
- package/dist/modules/sso/backend/sso/config/[id]/page.js +749 -0
- package/dist/modules/sso/backend/sso/config/[id]/page.js.map +7 -0
- package/dist/modules/sso/backend/sso/config/[id]/page.meta.js +19 -0
- package/dist/modules/sso/backend/sso/config/[id]/page.meta.js.map +7 -0
- package/dist/modules/sso/backend/sso/config/new/page.js +381 -0
- package/dist/modules/sso/backend/sso/config/new/page.js.map +7 -0
- package/dist/modules/sso/backend/sso/config/new/page.meta.js +19 -0
- package/dist/modules/sso/backend/sso/config/new/page.meta.js.map +7 -0
- package/dist/modules/sso/data/entities.js +299 -0
- package/dist/modules/sso/data/entities.js.map +7 -0
- package/dist/modules/sso/data/validators.js +114 -0
- package/dist/modules/sso/data/validators.js.map +7 -0
- package/dist/modules/sso/di.js +26 -0
- package/dist/modules/sso/di.js.map +7 -0
- package/dist/modules/sso/events.js +24 -0
- package/dist/modules/sso/events.js.map +7 -0
- package/dist/modules/sso/i18n/de.json +146 -0
- package/dist/modules/sso/i18n/en.json +146 -0
- package/dist/modules/sso/i18n/es.json +146 -0
- package/dist/modules/sso/i18n/pl.json +146 -0
- package/dist/modules/sso/index.js +11 -0
- package/dist/modules/sso/index.js.map +7 -0
- package/dist/modules/sso/lib/domains.js +30 -0
- package/dist/modules/sso/lib/domains.js.map +7 -0
- package/dist/modules/sso/lib/oidc-provider.js +140 -0
- package/dist/modules/sso/lib/oidc-provider.js.map +7 -0
- package/dist/modules/sso/lib/registry.js +15 -0
- package/dist/modules/sso/lib/registry.js.map +7 -0
- package/dist/modules/sso/lib/scim-filter.js +43 -0
- package/dist/modules/sso/lib/scim-filter.js.map +7 -0
- package/dist/modules/sso/lib/scim-mapper.js +49 -0
- package/dist/modules/sso/lib/scim-mapper.js.map +7 -0
- package/dist/modules/sso/lib/scim-patch.js +63 -0
- package/dist/modules/sso/lib/scim-patch.js.map +7 -0
- package/dist/modules/sso/lib/scim-response.js +34 -0
- package/dist/modules/sso/lib/scim-response.js.map +7 -0
- package/dist/modules/sso/lib/scim-utils.js +9 -0
- package/dist/modules/sso/lib/scim-utils.js.map +7 -0
- package/dist/modules/sso/lib/state-cookie.js +67 -0
- package/dist/modules/sso/lib/state-cookie.js.map +7 -0
- package/dist/modules/sso/lib/types.js +1 -0
- package/dist/modules/sso/lib/types.js.map +7 -0
- package/dist/modules/sso/migrations/Migration20260219000000_sso.js +20 -0
- package/dist/modules/sso/migrations/Migration20260219000000_sso.js.map +7 -0
- package/dist/modules/sso/migrations/Migration20260222000000_sso_add_name.js +13 -0
- package/dist/modules/sso/migrations/Migration20260222000000_sso_add_name.js.map +7 -0
- package/dist/modules/sso/migrations/Migration20260222000001_sso_partial_unique_org.js +15 -0
- package/dist/modules/sso/migrations/Migration20260222000001_sso_partial_unique_org.js.map +7 -0
- package/dist/modules/sso/migrations/Migration20260223000000_scim_tables.js +22 -0
- package/dist/modules/sso/migrations/Migration20260223000000_scim_tables.js.map +7 -0
- package/dist/modules/sso/migrations/Migration20260224000000_sso_external_id.js +15 -0
- package/dist/modules/sso/migrations/Migration20260224000000_sso_external_id.js.map +7 -0
- package/dist/modules/sso/migrations/Migration20260224100000_sso_role_grants.js +17 -0
- package/dist/modules/sso/migrations/Migration20260224100000_sso_role_grants.js.map +7 -0
- package/dist/modules/sso/migrations/Migration20260224200000_drop_default_role_id.js +13 -0
- package/dist/modules/sso/migrations/Migration20260224200000_drop_default_role_id.js.map +7 -0
- package/dist/modules/sso/migrations/Migration20260225000000_sso_identities_partial_unique.js +23 -0
- package/dist/modules/sso/migrations/Migration20260225000000_sso_identities_partial_unique.js.map +7 -0
- package/dist/modules/sso/migrations/Migration20260305000000_sso_role_grants_org_id.js +14 -0
- package/dist/modules/sso/migrations/Migration20260305000000_sso_role_grants_org_id.js.map +7 -0
- package/dist/modules/sso/services/accountLinkingService.js +298 -0
- package/dist/modules/sso/services/accountLinkingService.js.map +7 -0
- package/dist/modules/sso/services/hrdService.js +18 -0
- package/dist/modules/sso/services/hrdService.js.map +7 -0
- package/dist/modules/sso/services/scimService.js +372 -0
- package/dist/modules/sso/services/scimService.js.map +7 -0
- package/dist/modules/sso/services/scimTokenService.js +94 -0
- package/dist/modules/sso/services/scimTokenService.js.map +7 -0
- package/dist/modules/sso/services/ssoConfigService.js +254 -0
- package/dist/modules/sso/services/ssoConfigService.js.map +7 -0
- package/dist/modules/sso/services/ssoService.js +125 -0
- package/dist/modules/sso/services/ssoService.js.map +7 -0
- package/dist/modules/sso/setup.js +47 -0
- package/dist/modules/sso/setup.js.map +7 -0
- package/dist/modules/sso/subscribers/user-deleted-cleanup.js +21 -0
- package/dist/modules/sso/subscribers/user-deleted-cleanup.js.map +7 -0
- package/dist/modules/sso/widgets/injection/login-sso/widget.client.js +106 -0
- package/dist/modules/sso/widgets/injection/login-sso/widget.client.js.map +7 -0
- package/dist/modules/sso/widgets/injection/login-sso/widget.js +16 -0
- package/dist/modules/sso/widgets/injection/login-sso/widget.js.map +7 -0
- package/dist/modules/sso/widgets/injection-table.js +14 -0
- package/dist/modules/sso/widgets/injection-table.js.map +7 -0
- package/package.json +5 -4
- package/src/index.ts +1 -1
- package/src/modules/sso/acl.ts +7 -0
- package/src/modules/sso/api/admin-context.ts +36 -0
- package/src/modules/sso/api/callback/oidc/route.ts +115 -0
- package/src/modules/sso/api/config/[id]/activate/route.ts +53 -0
- package/src/modules/sso/api/config/[id]/domains/route.ts +107 -0
- package/src/modules/sso/api/config/[id]/route.ts +114 -0
- package/src/modules/sso/api/config/[id]/test/route.ts +44 -0
- package/src/modules/sso/api/config/route.ts +88 -0
- package/src/modules/sso/api/error-handler.ts +36 -0
- package/src/modules/sso/api/hrd/route.ts +55 -0
- package/src/modules/sso/api/initiate/route.ts +70 -0
- package/src/modules/sso/api/scim/context.ts +85 -0
- package/src/modules/sso/api/scim/logs/route.ts +69 -0
- package/src/modules/sso/api/scim/tokens/[id]/route.ts +45 -0
- package/src/modules/sso/api/scim/tokens/route.ts +89 -0
- package/src/modules/sso/api/scim/v2/ServiceProviderConfig/route.ts +40 -0
- package/src/modules/sso/api/scim/v2/Users/[id]/route.ts +103 -0
- package/src/modules/sso/api/scim/v2/Users/route.ts +94 -0
- package/src/modules/sso/backend/page.meta.ts +29 -0
- package/src/modules/sso/backend/page.tsx +232 -0
- package/src/modules/sso/backend/sso/config/[id]/page.meta.ts +15 -0
- package/src/modules/sso/backend/sso/config/[id]/page.tsx +1024 -0
- package/src/modules/sso/backend/sso/config/new/page.meta.ts +15 -0
- package/src/modules/sso/backend/sso/config/new/page.tsx +463 -0
- package/src/modules/sso/data/entities.ts +240 -0
- package/src/modules/sso/data/validators.ts +140 -0
- package/src/modules/sso/di.ts +25 -0
- package/src/modules/sso/docs/entra-id-setup.md +281 -0
- package/src/modules/sso/docs/google-workspace-setup.md +174 -0
- package/src/modules/sso/docs/sso-overview.md +218 -0
- package/src/modules/sso/docs/sso-security-audit-2026-02-27.md +118 -0
- package/src/modules/sso/docs/zitadel-setup.md +195 -0
- package/src/modules/sso/events.ts +21 -0
- package/src/modules/sso/i18n/de.json +146 -0
- package/src/modules/sso/i18n/en.json +146 -0
- package/src/modules/sso/i18n/es.json +146 -0
- package/src/modules/sso/i18n/pl.json +146 -0
- package/src/modules/sso/index.ts +7 -0
- package/src/modules/sso/lib/domains.ts +31 -0
- package/src/modules/sso/lib/oidc-provider.ts +196 -0
- package/src/modules/sso/lib/registry.ts +13 -0
- package/src/modules/sso/lib/scim-filter.ts +62 -0
- package/src/modules/sso/lib/scim-mapper.ts +88 -0
- package/src/modules/sso/lib/scim-patch.ts +88 -0
- package/src/modules/sso/lib/scim-response.ts +40 -0
- package/src/modules/sso/lib/scim-utils.ts +5 -0
- package/src/modules/sso/lib/state-cookie.ts +79 -0
- package/src/modules/sso/lib/types.ts +50 -0
- package/src/modules/sso/migrations/.snapshot-open-mercato.json +912 -0
- package/src/modules/sso/migrations/Migration20260219000000_sso.ts +21 -0
- package/src/modules/sso/migrations/Migration20260222000000_sso_add_name.ts +13 -0
- package/src/modules/sso/migrations/Migration20260222000001_sso_partial_unique_org.ts +15 -0
- package/src/modules/sso/migrations/Migration20260223000000_scim_tables.ts +24 -0
- package/src/modules/sso/migrations/Migration20260224000000_sso_external_id.ts +15 -0
- package/src/modules/sso/migrations/Migration20260224100000_sso_role_grants.ts +18 -0
- package/src/modules/sso/migrations/Migration20260224200000_drop_default_role_id.ts +13 -0
- package/src/modules/sso/migrations/Migration20260225000000_sso_identities_partial_unique.ts +25 -0
- package/src/modules/sso/migrations/Migration20260305000000_sso_role_grants_org_id.ts +14 -0
- package/src/modules/sso/services/accountLinkingService.ts +386 -0
- package/src/modules/sso/services/hrdService.ts +22 -0
- package/src/modules/sso/services/scimService.ts +461 -0
- package/src/modules/sso/services/scimTokenService.ts +136 -0
- package/src/modules/sso/services/ssoConfigService.ts +337 -0
- package/src/modules/sso/services/ssoService.ts +167 -0
- package/src/modules/sso/setup.ts +56 -0
- package/src/modules/sso/subscribers/user-deleted-cleanup.ts +33 -0
- package/src/modules/sso/widgets/injection/login-sso/widget.client.tsx +130 -0
- package/src/modules/sso/widgets/injection/login-sso/widget.ts +16 -0
- package/src/modules/sso/widgets/injection-table.ts +12 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
|
|
3
|
+
export class Migration20260219000000_sso extends Migration {
|
|
4
|
+
|
|
5
|
+
override async up(): Promise<void> {
|
|
6
|
+
this.addSql(`create table "sso_configs" ("id" uuid not null default gen_random_uuid(), "tenant_id" uuid null, "organization_id" uuid not null, "protocol" text not null, "issuer" text null, "client_id" text null, "client_secret_enc" text null, "allowed_domains" jsonb not null default '[]', "jit_enabled" boolean not null default true, "auto_link_by_email" boolean not null default true, "is_active" boolean not null default false, "sso_required" boolean not null default false, "default_role_id" uuid null, "created_at" timestamptz not null, "updated_at" timestamptz not null, "deleted_at" timestamptz null, constraint "sso_configs_pkey" primary key ("id"));`);
|
|
7
|
+
this.addSql(`alter table "sso_configs" add constraint "sso_configs_organization_id_unique" unique ("organization_id");`);
|
|
8
|
+
|
|
9
|
+
this.addSql(`create table "sso_identities" ("id" uuid not null default gen_random_uuid(), "tenant_id" uuid null, "organization_id" uuid not null, "sso_config_id" uuid not null, "user_id" uuid not null, "idp_subject" text not null, "idp_email" text not null, "idp_name" text null, "idp_groups" jsonb not null default '[]', "provisioning_method" text not null, "first_login_at" timestamptz null, "last_login_at" timestamptz null, "created_at" timestamptz not null, "updated_at" timestamptz not null, "deleted_at" timestamptz null, constraint "sso_identities_pkey" primary key ("id"));`);
|
|
10
|
+
this.addSql(`alter table "sso_identities" add constraint "sso_identities_config_user_unique" unique ("sso_config_id", "user_id");`);
|
|
11
|
+
this.addSql(`alter table "sso_identities" add constraint "sso_identities_config_subject_unique" unique ("sso_config_id", "idp_subject");`);
|
|
12
|
+
this.addSql(`create index "sso_identities_config_id_idx" on "sso_identities" ("sso_config_id");`);
|
|
13
|
+
this.addSql(`create index "sso_identities_user_id_idx" on "sso_identities" ("user_id");`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
override async down(): Promise<void> {
|
|
17
|
+
this.addSql(`drop table if exists "sso_configs" cascade;`);
|
|
18
|
+
this.addSql(`drop table if exists "sso_identities" cascade;`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
|
|
3
|
+
export class Migration20260222000000_sso_add_name extends Migration {
|
|
4
|
+
|
|
5
|
+
override async up(): Promise<void> {
|
|
6
|
+
this.addSql(`alter table "sso_configs" add column "name" text null;`);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
override async down(): Promise<void> {
|
|
10
|
+
this.addSql(`alter table "sso_configs" drop column "name";`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
|
|
3
|
+
export class Migration20260222000001_sso_partial_unique_org extends Migration {
|
|
4
|
+
|
|
5
|
+
override async up(): Promise<void> {
|
|
6
|
+
this.addSql(`alter table "sso_configs" drop constraint "sso_configs_organization_id_unique";`);
|
|
7
|
+
this.addSql(`create unique index "sso_configs_organization_id_unique" on "sso_configs" ("organization_id") where "deleted_at" is null;`);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
override async down(): Promise<void> {
|
|
11
|
+
this.addSql(`drop index "sso_configs_organization_id_unique";`);
|
|
12
|
+
this.addSql(`alter table "sso_configs" add constraint "sso_configs_organization_id_unique" unique ("organization_id");`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
|
|
3
|
+
export class Migration20260223000000_scim_tables extends Migration {
|
|
4
|
+
|
|
5
|
+
override async up(): Promise<void> {
|
|
6
|
+
this.addSql(`create table "scim_tokens" ("id" uuid not null default gen_random_uuid(), "tenant_id" uuid null, "organization_id" uuid not null, "sso_config_id" uuid not null, "name" text not null, "token_hash" text not null, "token_prefix" text not null, "is_active" boolean not null default true, "created_by" uuid null, "created_at" timestamptz not null, "updated_at" timestamptz not null, constraint "scim_tokens_pkey" primary key ("id"));`);
|
|
7
|
+
this.addSql(`create index "scim_tokens_sso_config_id_idx" on "scim_tokens" ("sso_config_id");`);
|
|
8
|
+
this.addSql(`create index "scim_tokens_token_prefix_idx" on "scim_tokens" ("token_prefix");`);
|
|
9
|
+
|
|
10
|
+
this.addSql(`create table "sso_user_deactivations" ("id" uuid not null default gen_random_uuid(), "tenant_id" uuid null, "organization_id" uuid not null, "user_id" uuid not null, "sso_config_id" uuid not null, "deactivated_at" timestamptz not null, "reactivated_at" timestamptz null, "created_at" timestamptz not null, constraint "sso_user_deactivations_pkey" primary key ("id"));`);
|
|
11
|
+
this.addSql(`create index "sso_user_deactivations_user_id_idx" on "sso_user_deactivations" ("user_id");`);
|
|
12
|
+
this.addSql(`alter table "sso_user_deactivations" add constraint "sso_user_deactivations_user_config_unique" unique ("user_id", "sso_config_id");`);
|
|
13
|
+
|
|
14
|
+
this.addSql(`create table "scim_provisioning_log" ("id" uuid not null default gen_random_uuid(), "tenant_id" uuid null, "organization_id" uuid not null, "sso_config_id" uuid not null, "operation" text not null, "resource_type" text not null, "resource_id" uuid null, "scim_external_id" text null, "response_status" int not null, "error_message" text null, "created_at" timestamptz not null, constraint "scim_provisioning_log_pkey" primary key ("id"));`);
|
|
15
|
+
this.addSql(`create index "scim_provisioning_log_config_created_idx" on "scim_provisioning_log" ("sso_config_id", "created_at");`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
override async down(): Promise<void> {
|
|
19
|
+
this.addSql(`drop table if exists "scim_provisioning_log" cascade;`);
|
|
20
|
+
this.addSql(`drop table if exists "sso_user_deactivations" cascade;`);
|
|
21
|
+
this.addSql(`drop table if exists "scim_tokens" cascade;`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
|
|
3
|
+
export class Migration20260224000000_sso_external_id extends Migration {
|
|
4
|
+
|
|
5
|
+
override async up(): Promise<void> {
|
|
6
|
+
this.addSql(`alter table "sso_identities" add column "external_id" text null;`);
|
|
7
|
+
this.addSql(`alter table "sso_identities" add constraint "sso_identities_config_external_id_unique" unique ("sso_config_id", "external_id");`);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
override async down(): Promise<void> {
|
|
11
|
+
this.addSql(`alter table "sso_identities" drop constraint if exists "sso_identities_config_external_id_unique";`);
|
|
12
|
+
this.addSql(`alter table "sso_identities" drop column if exists "external_id";`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
|
|
3
|
+
export class Migration20260224100000_sso_role_grants extends Migration {
|
|
4
|
+
|
|
5
|
+
override async up(): Promise<void> {
|
|
6
|
+
this.addSql(`create table "sso_role_grants" ("id" uuid not null default gen_random_uuid(), "tenant_id" uuid null, "user_id" uuid not null, "role_id" uuid not null, "sso_config_id" uuid not null, "created_at" timestamptz not null, constraint "sso_role_grants_pkey" primary key ("id"));`);
|
|
7
|
+
this.addSql(`create index "sso_role_grants_user_id_idx" on "sso_role_grants" ("user_id");`);
|
|
8
|
+
this.addSql(`alter table "sso_role_grants" add constraint "sso_role_grants_user_role_config_unique" unique ("user_id", "role_id", "sso_config_id");`);
|
|
9
|
+
|
|
10
|
+
this.addSql(`alter table "sso_configs" add column "app_role_mappings" jsonb not null default '{}';`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
override async down(): Promise<void> {
|
|
14
|
+
this.addSql(`drop table if exists "sso_role_grants";`);
|
|
15
|
+
this.addSql(`alter table "sso_configs" drop column "app_role_mappings";`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
|
|
3
|
+
export class Migration20260224200000_drop_default_role_id extends Migration {
|
|
4
|
+
|
|
5
|
+
override async up(): Promise<void> {
|
|
6
|
+
this.addSql(`alter table "sso_configs" drop column if exists "default_role_id";`);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
override async down(): Promise<void> {
|
|
10
|
+
this.addSql(`alter table "sso_configs" add column "default_role_id" uuid null;`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
|
|
3
|
+
export class Migration20260225000000_sso_identities_partial_unique extends Migration {
|
|
4
|
+
|
|
5
|
+
override async up(): Promise<void> {
|
|
6
|
+
this.addSql(`alter table "sso_identities" drop constraint "sso_identities_config_user_unique";`);
|
|
7
|
+
this.addSql(`alter table "sso_identities" drop constraint "sso_identities_config_subject_unique";`);
|
|
8
|
+
this.addSql(`alter table "sso_identities" drop constraint "sso_identities_config_external_id_unique";`);
|
|
9
|
+
|
|
10
|
+
this.addSql(`create unique index "sso_identities_config_user_unique" on "sso_identities" ("sso_config_id", "user_id") where "deleted_at" is null;`);
|
|
11
|
+
this.addSql(`create unique index "sso_identities_config_subject_unique" on "sso_identities" ("sso_config_id", "idp_subject") where "deleted_at" is null;`);
|
|
12
|
+
this.addSql(`create unique index "sso_identities_config_external_id_unique" on "sso_identities" ("sso_config_id", "external_id") where "deleted_at" is null;`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
override async down(): Promise<void> {
|
|
16
|
+
this.addSql(`drop index "sso_identities_config_user_unique";`);
|
|
17
|
+
this.addSql(`drop index "sso_identities_config_subject_unique";`);
|
|
18
|
+
this.addSql(`drop index "sso_identities_config_external_id_unique";`);
|
|
19
|
+
|
|
20
|
+
this.addSql(`alter table "sso_identities" add constraint "sso_identities_config_user_unique" unique ("sso_config_id", "user_id");`);
|
|
21
|
+
this.addSql(`alter table "sso_identities" add constraint "sso_identities_config_subject_unique" unique ("sso_config_id", "idp_subject");`);
|
|
22
|
+
this.addSql(`alter table "sso_identities" add constraint "sso_identities_config_external_id_unique" unique ("sso_config_id", "external_id");`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
|
|
3
|
+
export class Migration20260305000000_sso_role_grants_org_id extends Migration {
|
|
4
|
+
|
|
5
|
+
override async up(): Promise<void> {
|
|
6
|
+
this.addSql(`alter table "sso_role_grants" add column "organization_id" uuid not null default '00000000-0000-0000-0000-000000000000';`);
|
|
7
|
+
this.addSql(`alter table "sso_role_grants" alter column "organization_id" drop default;`);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
override async down(): Promise<void> {
|
|
11
|
+
this.addSql(`alter table "sso_role_grants" drop column "organization_id";`);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
}
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import { EntityManager, type FilterQuery, type RequiredEntityData } from '@mikro-orm/postgresql'
|
|
2
|
+
import { User, UserRole, Role } from '@open-mercato/core/modules/auth/data/entities'
|
|
3
|
+
import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
4
|
+
import { computeEmailHash } from '@open-mercato/core/modules/auth/lib/emailHash'
|
|
5
|
+
import { SsoConfig, SsoIdentity, SsoRoleGrant, ScimToken } from '../data/entities'
|
|
6
|
+
import { emitSsoEvent } from '../events'
|
|
7
|
+
import type { SsoIdentityPayload } from '../lib/types'
|
|
8
|
+
|
|
9
|
+
export class AccountLinkingService {
|
|
10
|
+
constructor(private em: EntityManager) {}
|
|
11
|
+
|
|
12
|
+
async resolveUser(
|
|
13
|
+
config: SsoConfig,
|
|
14
|
+
idpPayload: SsoIdentityPayload,
|
|
15
|
+
tenantId: string,
|
|
16
|
+
): Promise<{ user: User; identity: SsoIdentity }> {
|
|
17
|
+
const existing = await this.findExistingLink(config.id, idpPayload.subject, tenantId, config.organizationId)
|
|
18
|
+
if (existing) {
|
|
19
|
+
await this.assignRolesFromSso(this.em, existing.user, config, tenantId, idpPayload.groups)
|
|
20
|
+
return existing
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (idpPayload.emailVerified === false) {
|
|
24
|
+
throw new Error('IdP explicitly reported email as unverified — cannot link or provision account')
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const emailDomain = idpPayload.email.split('@')[1]?.toLowerCase()
|
|
28
|
+
if (!emailDomain || !config.allowedDomains.some((d) => d.toLowerCase() === emailDomain)) {
|
|
29
|
+
throw new Error('Email domain is not in the allowed domains for this SSO configuration')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const emailLinked = config.autoLinkByEmail
|
|
33
|
+
? await this.linkByEmail(config, idpPayload, tenantId)
|
|
34
|
+
: null
|
|
35
|
+
if (emailLinked) {
|
|
36
|
+
await this.assignRolesFromSso(this.em, emailLinked.user, config, tenantId, idpPayload.groups)
|
|
37
|
+
return emailLinked
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (config.jitEnabled) {
|
|
41
|
+
const scimActive = await this.em.count(ScimToken, { ssoConfigId: config.id, isActive: true }) > 0
|
|
42
|
+
if (scimActive) {
|
|
43
|
+
throw new Error('JIT provisioning is disabled because SCIM directory sync is active')
|
|
44
|
+
}
|
|
45
|
+
return this.jitProvision(config, idpPayload, tenantId)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
throw new Error('No matching user found and JIT provisioning is disabled')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private async findExistingLink(
|
|
52
|
+
ssoConfigId: string,
|
|
53
|
+
idpSubject: string,
|
|
54
|
+
tenantId: string,
|
|
55
|
+
organizationId: string,
|
|
56
|
+
): Promise<{ user: User; identity: SsoIdentity } | null> {
|
|
57
|
+
const identity = await findOneWithDecryption(
|
|
58
|
+
this.em,
|
|
59
|
+
SsoIdentity,
|
|
60
|
+
{ ssoConfigId, idpSubject, deletedAt: null },
|
|
61
|
+
{},
|
|
62
|
+
{ tenantId, organizationId },
|
|
63
|
+
)
|
|
64
|
+
if (!identity) return null
|
|
65
|
+
|
|
66
|
+
const user = await findOneWithDecryption(
|
|
67
|
+
this.em,
|
|
68
|
+
User,
|
|
69
|
+
{ id: identity.userId, deletedAt: null },
|
|
70
|
+
{},
|
|
71
|
+
{ tenantId, organizationId },
|
|
72
|
+
)
|
|
73
|
+
if (!user) {
|
|
74
|
+
identity.deletedAt = new Date()
|
|
75
|
+
await this.em.flush()
|
|
76
|
+
return null
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
identity.lastLoginAt = new Date()
|
|
80
|
+
await this.em.flush()
|
|
81
|
+
|
|
82
|
+
return { user, identity }
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private async linkByEmail(
|
|
86
|
+
config: SsoConfig,
|
|
87
|
+
idpPayload: SsoIdentityPayload,
|
|
88
|
+
tenantId: string,
|
|
89
|
+
): Promise<{ user: User; identity: SsoIdentity } | null> {
|
|
90
|
+
const emailHash = computeEmailHash(idpPayload.email)
|
|
91
|
+
const user = await findOneWithDecryption(
|
|
92
|
+
this.em,
|
|
93
|
+
User,
|
|
94
|
+
{
|
|
95
|
+
organizationId: config.organizationId,
|
|
96
|
+
deletedAt: null,
|
|
97
|
+
$or: [
|
|
98
|
+
{ email: idpPayload.email },
|
|
99
|
+
{ emailHash },
|
|
100
|
+
],
|
|
101
|
+
} as FilterQuery<User>,
|
|
102
|
+
{},
|
|
103
|
+
{ tenantId, organizationId: config.organizationId },
|
|
104
|
+
)
|
|
105
|
+
if (!user) return null
|
|
106
|
+
|
|
107
|
+
const now = new Date()
|
|
108
|
+
const identity = this.em.create(SsoIdentity, {
|
|
109
|
+
tenantId,
|
|
110
|
+
organizationId: config.organizationId,
|
|
111
|
+
ssoConfigId: config.id,
|
|
112
|
+
userId: user.id,
|
|
113
|
+
idpSubject: idpPayload.subject,
|
|
114
|
+
idpEmail: idpPayload.email,
|
|
115
|
+
idpName: idpPayload.name ?? null,
|
|
116
|
+
idpGroups: idpPayload.groups ?? [],
|
|
117
|
+
provisioningMethod: 'manual',
|
|
118
|
+
firstLoginAt: now,
|
|
119
|
+
lastLoginAt: now,
|
|
120
|
+
createdAt: now,
|
|
121
|
+
updatedAt: now,
|
|
122
|
+
} as RequiredEntityData<SsoIdentity>)
|
|
123
|
+
await this.em.persistAndFlush(identity)
|
|
124
|
+
|
|
125
|
+
void emitSsoEvent('sso.identity.linked', {
|
|
126
|
+
id: identity.id,
|
|
127
|
+
tenantId,
|
|
128
|
+
organizationId: config.organizationId,
|
|
129
|
+
}).catch((e) => console.error('[SSO Event]', e))
|
|
130
|
+
|
|
131
|
+
return { user, identity }
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private async jitProvision(
|
|
135
|
+
config: SsoConfig,
|
|
136
|
+
idpPayload: SsoIdentityPayload,
|
|
137
|
+
tenantId: string,
|
|
138
|
+
): Promise<{ user: User; identity: SsoIdentity }> {
|
|
139
|
+
return this.em.transactional(async (txEm) => {
|
|
140
|
+
const user = txEm.create(User, {
|
|
141
|
+
tenantId,
|
|
142
|
+
organizationId: config.organizationId,
|
|
143
|
+
email: idpPayload.email,
|
|
144
|
+
emailHash: computeEmailHash(idpPayload.email),
|
|
145
|
+
name: idpPayload.name ?? null,
|
|
146
|
+
passwordHash: null,
|
|
147
|
+
isConfirmed: true,
|
|
148
|
+
createdAt: new Date(),
|
|
149
|
+
})
|
|
150
|
+
await txEm.persistAndFlush(user)
|
|
151
|
+
|
|
152
|
+
await this.assignRolesFromSso(txEm, user, config, tenantId, idpPayload.groups)
|
|
153
|
+
|
|
154
|
+
const now = new Date()
|
|
155
|
+
const identity = txEm.create(SsoIdentity, {
|
|
156
|
+
tenantId,
|
|
157
|
+
organizationId: config.organizationId,
|
|
158
|
+
ssoConfigId: config.id,
|
|
159
|
+
userId: user.id,
|
|
160
|
+
idpSubject: idpPayload.subject,
|
|
161
|
+
idpEmail: idpPayload.email,
|
|
162
|
+
idpName: idpPayload.name ?? null,
|
|
163
|
+
idpGroups: idpPayload.groups ?? [],
|
|
164
|
+
provisioningMethod: 'jit',
|
|
165
|
+
firstLoginAt: now,
|
|
166
|
+
lastLoginAt: now,
|
|
167
|
+
createdAt: now,
|
|
168
|
+
updatedAt: now,
|
|
169
|
+
} as RequiredEntityData<SsoIdentity>)
|
|
170
|
+
await txEm.persistAndFlush(identity)
|
|
171
|
+
|
|
172
|
+
void emitSsoEvent('sso.identity.created', {
|
|
173
|
+
id: identity.id,
|
|
174
|
+
tenantId,
|
|
175
|
+
organizationId: config.organizationId,
|
|
176
|
+
}).catch((e) => console.error('[SSO Event]', e))
|
|
177
|
+
|
|
178
|
+
return { user, identity }
|
|
179
|
+
})
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private async assignRolesFromSso(
|
|
183
|
+
em: EntityManager,
|
|
184
|
+
user: User,
|
|
185
|
+
config: SsoConfig,
|
|
186
|
+
tenantId: string,
|
|
187
|
+
idpGroups?: string[],
|
|
188
|
+
): Promise<void> {
|
|
189
|
+
const hasMappings = config.appRoleMappings && Object.keys(config.appRoleMappings).length > 0
|
|
190
|
+
if (!hasMappings) return
|
|
191
|
+
|
|
192
|
+
await this.syncMappedRoles(em, user, config, tenantId, idpGroups)
|
|
193
|
+
|
|
194
|
+
const hasAnySsoRole = await em.findOne(SsoRoleGrant, {
|
|
195
|
+
userId: user.id,
|
|
196
|
+
ssoConfigId: config.id,
|
|
197
|
+
})
|
|
198
|
+
if (!hasAnySsoRole) {
|
|
199
|
+
throw new Error('No roles could be resolved from IdP groups — login denied. Configure role mappings or ensure the IdP sends matching group claims.')
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Sync/replace SSO-sourced roles: on each login, SSO-managed roles are replaced
|
|
205
|
+
* with what the IdP sends, while manually-assigned roles are preserved.
|
|
206
|
+
*/
|
|
207
|
+
private async syncMappedRoles(
|
|
208
|
+
em: EntityManager,
|
|
209
|
+
user: User,
|
|
210
|
+
config: SsoConfig,
|
|
211
|
+
tenantId: string,
|
|
212
|
+
idpGroups?: string[],
|
|
213
|
+
): Promise<void> {
|
|
214
|
+
const resolvedTenantId = tenantId || user.tenantId || ''
|
|
215
|
+
if (!resolvedTenantId) return
|
|
216
|
+
|
|
217
|
+
const allRoles = await em.find(Role, { tenantId: resolvedTenantId, deletedAt: null } as FilterQuery<Role>)
|
|
218
|
+
const roleByNormalizedName = new Map<string, Role>()
|
|
219
|
+
for (const role of allRoles) {
|
|
220
|
+
const normalized = normalizeToken(role.name)
|
|
221
|
+
if (normalized) roleByNormalizedName.set(normalized, role)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Resolve desired role IDs from IdP groups using merged mappings
|
|
225
|
+
const desiredRoleNames = resolveRoleNamesFromIdpGroups(idpGroups, config.appRoleMappings)
|
|
226
|
+
const desiredRoleIds = new Set<string>()
|
|
227
|
+
for (const roleName of desiredRoleNames) {
|
|
228
|
+
const role = roleByNormalizedName.get(roleName)
|
|
229
|
+
if (role) desiredRoleIds.add(role.id)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Query current SSO grants for this user+config
|
|
233
|
+
const existingGrants = await em.find(SsoRoleGrant, {
|
|
234
|
+
userId: user.id,
|
|
235
|
+
ssoConfigId: config.id,
|
|
236
|
+
})
|
|
237
|
+
const existingGrantedRoleIds = new Set(existingGrants.map((g) => g.roleId))
|
|
238
|
+
|
|
239
|
+
// Compute diff
|
|
240
|
+
const toAdd = [...desiredRoleIds].filter((id) => !existingGrantedRoleIds.has(id))
|
|
241
|
+
const toRemove = existingGrants.filter((g) => !desiredRoleIds.has(g.roleId))
|
|
242
|
+
|
|
243
|
+
// Add new roles
|
|
244
|
+
for (const roleId of toAdd) {
|
|
245
|
+
const role = allRoles.find((r) => r.id === roleId)
|
|
246
|
+
if (!role) continue
|
|
247
|
+
await this.ensureUserRole(em, user, role)
|
|
248
|
+
const grant = em.create(SsoRoleGrant, {
|
|
249
|
+
tenantId: resolvedTenantId,
|
|
250
|
+
organizationId: config.organizationId,
|
|
251
|
+
userId: user.id,
|
|
252
|
+
roleId,
|
|
253
|
+
ssoConfigId: config.id,
|
|
254
|
+
} as RequiredEntityData<SsoRoleGrant>)
|
|
255
|
+
em.persist(grant)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Remove stale SSO-sourced roles
|
|
259
|
+
for (const grant of toRemove) {
|
|
260
|
+
const userRole = await em.findOne(UserRole, {
|
|
261
|
+
user: user.id,
|
|
262
|
+
role: grant.roleId,
|
|
263
|
+
deletedAt: null,
|
|
264
|
+
} as FilterQuery<UserRole>)
|
|
265
|
+
if (userRole) {
|
|
266
|
+
em.remove(userRole)
|
|
267
|
+
}
|
|
268
|
+
em.remove(grant)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Clean up orphaned soft-deleted UserRole rows (ghost rows from previous soft-delete logic)
|
|
272
|
+
const allUserRoles = await em.find(UserRole, { user: user.id } as FilterQuery<UserRole>)
|
|
273
|
+
for (const ur of allUserRoles) {
|
|
274
|
+
if (ur.deletedAt) {
|
|
275
|
+
em.remove(ur)
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (toAdd.length > 0 || toRemove.length > 0 || allUserRoles.some((ur) => ur.deletedAt)) {
|
|
280
|
+
await em.flush()
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
private async ensureUserRole(em: EntityManager, user: User, role: Role): Promise<void> {
|
|
285
|
+
const existingLink = await em.findOne(UserRole, {
|
|
286
|
+
user: user.id,
|
|
287
|
+
role: role.id,
|
|
288
|
+
deletedAt: null,
|
|
289
|
+
} as FilterQuery<UserRole>)
|
|
290
|
+
if (existingLink) return
|
|
291
|
+
|
|
292
|
+
const userRole = em.create(UserRole, { user, role, createdAt: new Date() })
|
|
293
|
+
await em.persistAndFlush(userRole)
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function resolveRoleNamesFromIdpGroups(
|
|
298
|
+
idpGroups?: string[],
|
|
299
|
+
configMappings?: Record<string, string>,
|
|
300
|
+
): string[] {
|
|
301
|
+
if (!Array.isArray(idpGroups) || idpGroups.length === 0) return []
|
|
302
|
+
|
|
303
|
+
const normalizedGroups = idpGroups
|
|
304
|
+
.map((group) => normalizeToken(group))
|
|
305
|
+
.filter((group): group is string => group !== null)
|
|
306
|
+
if (normalizedGroups.length === 0) return []
|
|
307
|
+
|
|
308
|
+
const mergedMappings = loadMergedMappings(configMappings)
|
|
309
|
+
const roleNames = new Set<string>()
|
|
310
|
+
|
|
311
|
+
for (const group of normalizedGroups) {
|
|
312
|
+
const mapped = mergedMappings.get(group)
|
|
313
|
+
if (mapped?.length) {
|
|
314
|
+
for (const role of mapped) roleNames.add(role)
|
|
315
|
+
continue
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
roleNames.add(group)
|
|
319
|
+
const segmented = group.split(/[\\/:]/).map((part) => normalizeToken(part)).filter((part): part is string => part !== null)
|
|
320
|
+
for (const candidate of segmented) {
|
|
321
|
+
roleNames.add(candidate)
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return Array.from(roleNames)
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function loadMergedMappings(configMappings?: Record<string, string>): Map<string, string[]> {
|
|
329
|
+
const envMappings = loadGroupRoleMappingsFromEnv()
|
|
330
|
+
|
|
331
|
+
// Per-config mappings take precedence over env var
|
|
332
|
+
if (configMappings && Object.keys(configMappings).length > 0) {
|
|
333
|
+
for (const [group, roleName] of Object.entries(configMappings)) {
|
|
334
|
+
const normalizedGroup = normalizeToken(group)
|
|
335
|
+
if (!normalizedGroup) continue
|
|
336
|
+
const normalizedRole = normalizeToken(roleName)
|
|
337
|
+
if (!normalizedRole) continue
|
|
338
|
+
envMappings.set(normalizedGroup, [normalizedRole])
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return envMappings
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function loadGroupRoleMappingsFromEnv(): Map<string, string[]> {
|
|
346
|
+
const raw = process.env.SSO_GROUP_ROLE_MAP
|
|
347
|
+
if (!raw) return new Map()
|
|
348
|
+
|
|
349
|
+
try {
|
|
350
|
+
const parsed = JSON.parse(raw) as Record<string, unknown>
|
|
351
|
+
const out = new Map<string, string[]>()
|
|
352
|
+
for (const [group, roleValue] of Object.entries(parsed)) {
|
|
353
|
+
const normalizedGroup = normalizeToken(group)
|
|
354
|
+
if (!normalizedGroup) continue
|
|
355
|
+
const roles = normalizeRoleList(roleValue)
|
|
356
|
+
if (roles.length > 0) out.set(normalizedGroup, roles)
|
|
357
|
+
}
|
|
358
|
+
return out
|
|
359
|
+
} catch {
|
|
360
|
+
return new Map()
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function normalizeRoleList(value: unknown): string[] {
|
|
365
|
+
if (typeof value === 'string') {
|
|
366
|
+
const token = normalizeToken(value)
|
|
367
|
+
return token ? [token] : []
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (Array.isArray(value)) {
|
|
371
|
+
const out = new Set<string>()
|
|
372
|
+
for (const entry of value) {
|
|
373
|
+
const token = normalizeToken(entry)
|
|
374
|
+
if (token) out.add(token)
|
|
375
|
+
}
|
|
376
|
+
return Array.from(out)
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return []
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function normalizeToken(value: unknown): string | null {
|
|
383
|
+
if (typeof value !== 'string') return null
|
|
384
|
+
const normalized = value.trim().toLowerCase()
|
|
385
|
+
return normalized.length > 0 ? normalized : null
|
|
386
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { EntityManager } from '@mikro-orm/postgresql'
|
|
2
|
+
import { SsoConfig } from '../data/entities'
|
|
3
|
+
|
|
4
|
+
export class HrdService {
|
|
5
|
+
constructor(private em: EntityManager) {}
|
|
6
|
+
|
|
7
|
+
async findActiveConfigByEmailDomain(email: string): Promise<SsoConfig | null> {
|
|
8
|
+
const domain = email.split('@')[1]?.toLowerCase()
|
|
9
|
+
if (!domain) return null
|
|
10
|
+
|
|
11
|
+
const knex = this.em.getKnex()
|
|
12
|
+
const row = await knex('sso_configs')
|
|
13
|
+
.whereRaw("allowed_domains @> ?::jsonb", [JSON.stringify([domain])])
|
|
14
|
+
.where('is_active', true)
|
|
15
|
+
.whereNull('deleted_at')
|
|
16
|
+
.first()
|
|
17
|
+
|
|
18
|
+
if (!row) return null
|
|
19
|
+
|
|
20
|
+
return this.em.map(SsoConfig, row)
|
|
21
|
+
}
|
|
22
|
+
}
|