@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,146 @@
|
|
|
1
|
+
{
|
|
2
|
+
"common.activating": "Aktywowanie...",
|
|
3
|
+
"common.add": "Dodaj",
|
|
4
|
+
"common.back": "Wstecz",
|
|
5
|
+
"common.cancel": "Anuluj",
|
|
6
|
+
"common.copied": "Skopiowano do schowka",
|
|
7
|
+
"common.copy": "Kopiuj",
|
|
8
|
+
"common.create": "Utwórz",
|
|
9
|
+
"common.creating": "Tworzenie...",
|
|
10
|
+
"common.delete": "Usuń",
|
|
11
|
+
"common.disabled": "Wyłączone",
|
|
12
|
+
"common.dismiss": "Zamknij",
|
|
13
|
+
"common.edit": "Edytuj",
|
|
14
|
+
"common.enabled": "Włączone",
|
|
15
|
+
"common.loading": "Ładowanie...",
|
|
16
|
+
"common.next": "Dalej",
|
|
17
|
+
"common.notFound": "Nie znaleziono",
|
|
18
|
+
"common.remove": "Usuń",
|
|
19
|
+
"common.save": "Zapisz",
|
|
20
|
+
"common.saving": "Zapisywanie...",
|
|
21
|
+
"settings.sections.auth": "Autoryzacja",
|
|
22
|
+
"sso.admin.action.activate": "Aktywuj",
|
|
23
|
+
"sso.admin.action.deactivate": "Dezaktywuj",
|
|
24
|
+
"sso.admin.action.test": "Zweryfikuj Discovery",
|
|
25
|
+
"sso.admin.activated": "Konfiguracja SSO aktywowana",
|
|
26
|
+
"sso.admin.activity.empty": "Brak aktywności logowania SSO. Aktywność pojawi się tutaj, gdy użytkownicy zaczną logować się przez SSO.",
|
|
27
|
+
"sso.admin.banner.activateNow": "Aktywuj teraz",
|
|
28
|
+
"sso.admin.banner.created": "Konfiguracja SSO została utworzona. Czy chcesz ją teraz aktywować?",
|
|
29
|
+
"sso.admin.banner.notYet": "Jeszcze nie",
|
|
30
|
+
"sso.admin.column.created": "Utworzono",
|
|
31
|
+
"sso.admin.column.domains": "Domeny",
|
|
32
|
+
"sso.admin.column.name": "Nazwa",
|
|
33
|
+
"sso.admin.column.protocol": "Protokół",
|
|
34
|
+
"sso.admin.column.status": "Status",
|
|
35
|
+
"sso.admin.create.title": "Skonfiguruj SSO",
|
|
36
|
+
"sso.admin.created": "Konfiguracja SSO została utworzona",
|
|
37
|
+
"sso.admin.deactivated": "Konfiguracja SSO dezaktywowana",
|
|
38
|
+
"sso.admin.delete.confirm": "Czy na pewno? Spowoduje to usunięcie konfiguracji SSO. Użytkownicy z powiązanymi tożsamościami SSO będą musieli logować się hasłem.",
|
|
39
|
+
"sso.admin.delete.success": "Konfiguracja SSO została usunięta",
|
|
40
|
+
"sso.admin.delete.title": "Usuń konfigurację SSO",
|
|
41
|
+
"sso.admin.detail.backToList": "Powrót do SSO",
|
|
42
|
+
"sso.admin.detail.title": "Konfiguracja SSO",
|
|
43
|
+
"sso.admin.domains.empty": "Brak skonfigurowanych domen. Dodaj co najmniej jedną domenę przed aktywacją SSO.",
|
|
44
|
+
"sso.admin.empty.cta": "Skonfiguruj SSO",
|
|
45
|
+
"sso.admin.empty.description": "Skonfiguruj Single Sign-On, aby umożliwić użytkownikom logowanie przez dostawcę tożsamości.",
|
|
46
|
+
"sso.admin.empty.title": "Brak konfiguracji SSO",
|
|
47
|
+
"sso.admin.error.activationFailed": "Nie udało się zmienić statusu aktywacji",
|
|
48
|
+
"sso.admin.error.alreadyExists": "Konfiguracja SSO już istnieje dla tej organizacji",
|
|
49
|
+
"sso.admin.error.createFailed": "Nie udało się utworzyć konfiguracji SSO",
|
|
50
|
+
"sso.admin.error.deleteActive": "Nie można usunąć aktywnej konfiguracji SSO — najpierw ją dezaktywuj",
|
|
51
|
+
"sso.admin.error.deleteFailed": "Nie udało się usunąć konfiguracji SSO",
|
|
52
|
+
"sso.admin.error.domainAddFailed": "Nie udało się dodać domeny",
|
|
53
|
+
"sso.admin.error.domainRemoveFailed": "Nie udało się usunąć domeny",
|
|
54
|
+
"sso.admin.error.loadFailed": "Nie udało się załadować konfiguracji SSO",
|
|
55
|
+
"sso.admin.error.noDomainsForActivation": "Dodaj co najmniej jedną dozwoloną domenę e-mail przed aktywacją",
|
|
56
|
+
"sso.admin.error.saveFailed": "Nie udało się zapisać konfiguracji SSO",
|
|
57
|
+
"sso.admin.error.testFailed": "Test połączenia nie powiódł się",
|
|
58
|
+
"sso.admin.field.autoLinkByEmail": "Automatyczne łączenie po e-mailu",
|
|
59
|
+
"sso.admin.field.autoLinkByEmailDesc": "Automatycznie łącz istniejących użytkowników po adresie e-mail",
|
|
60
|
+
"sso.admin.field.changeSecret": "Zmień",
|
|
61
|
+
"sso.admin.field.clientId": "Client ID",
|
|
62
|
+
"sso.admin.field.clientSecret": "Client Secret",
|
|
63
|
+
"sso.admin.field.issuer": "Adres URL wystawcy",
|
|
64
|
+
"sso.admin.field.jitDisabledByScim": "Niedostępne — synchronizacja katalogów SCIM jest aktywna. Odwołaj tokeny SCIM, aby włączyć JIT.",
|
|
65
|
+
"sso.admin.field.jitEnabled": "Provisioning Just-in-Time",
|
|
66
|
+
"sso.admin.field.jitEnabledDesc": "Automatycznie twórz konta użytkowników przy pierwszym logowaniu SSO",
|
|
67
|
+
"sso.admin.field.name": "Nazwa konfiguracji",
|
|
68
|
+
"sso.admin.field.protocol": "Protokół",
|
|
69
|
+
"sso.admin.field.secretPlaceholder": "Wprowadź nowy secret, aby zastąpić istniejący",
|
|
70
|
+
"sso.admin.field.secretRequired": "Wprowadź client secret",
|
|
71
|
+
"sso.admin.field.secretSet": "Client secret jest skonfigurowany",
|
|
72
|
+
"sso.admin.new": "Nowa konfiguracja SSO",
|
|
73
|
+
"sso.admin.roles.description": "Mapuj nazwy ról IdP na lokalne role. Przy każdym logowaniu SSO role źródłowe SSO są synchronizowane — role nieotrzymane od IdP są usuwane, role przypisane ręcznie są zachowane.",
|
|
74
|
+
"sso.admin.roles.empty": "Brak skonfigurowanych mapowań ról. Nazwy ról IdP będą dopasowywane bezpośrednio do nazw lokalnych ról.",
|
|
75
|
+
"sso.admin.roles.error.duplicate": "Ta rola IdP jest już zmapowana",
|
|
76
|
+
"sso.admin.roles.error.emptyIdpRole": "Nazwa roli IdP jest wymagana",
|
|
77
|
+
"sso.admin.roles.error.emptyLocalRole": "Wybierz lokalną rolę",
|
|
78
|
+
"sso.admin.roles.error.saveFailed": "Nie udało się zapisać mapowań ról",
|
|
79
|
+
"sso.admin.roles.idpRole": "Nazwa roli IdP",
|
|
80
|
+
"sso.admin.roles.idpRolePlaceholder": "np. OpenMercato.Admin",
|
|
81
|
+
"sso.admin.roles.localRole": "Lokalna rola",
|
|
82
|
+
"sso.admin.roles.saved": "Mapowania ról zapisane",
|
|
83
|
+
"sso.admin.saved": "Konfiguracja SSO zapisana",
|
|
84
|
+
"sso.admin.scim.endpointCopied": "Adres URL endpointu SCIM skopiowany",
|
|
85
|
+
"sso.admin.scim.endpointUrl": "Adres URL endpointu SCIM",
|
|
86
|
+
"sso.admin.scim.error.createFailed": "Nie udało się utworzyć tokena SCIM",
|
|
87
|
+
"sso.admin.scim.error.revokeFailed": "Nie udało się odwołać tokena",
|
|
88
|
+
"sso.admin.scim.generateToken": "Wygeneruj token",
|
|
89
|
+
"sso.admin.scim.googleNotSupported": "Google Workspace nie obsługuje provisioningu SCIM. Użytkownicy są tworzeni przez Just-In-Time (JIT) przy pierwszym logowaniu.",
|
|
90
|
+
"sso.admin.scim.jitActiveWarning": "Provisioning SCIM jest niedostępny, gdy provisioning JIT jest włączony. Wyłącz JIT w zakładce Ogólne, aby skonfigurować SCIM.",
|
|
91
|
+
"sso.admin.scim.log.error": "Błąd",
|
|
92
|
+
"sso.admin.scim.log.operation": "Operacja",
|
|
93
|
+
"sso.admin.scim.log.resource": "Zasób",
|
|
94
|
+
"sso.admin.scim.log.status": "Status",
|
|
95
|
+
"sso.admin.scim.log.time": "Czas",
|
|
96
|
+
"sso.admin.scim.noTokens": "Provisioning SCIM nie jest skonfigurowany. Wygeneruj token bearer, aby umożliwić dostawcy tożsamości automatyczną synchronizację użytkowników.",
|
|
97
|
+
"sso.admin.scim.recentActivity": "Ostatnia aktywność provisioningu",
|
|
98
|
+
"sso.admin.scim.revoke.action": "Odwołaj",
|
|
99
|
+
"sso.admin.scim.revoke.confirm": "Czy na pewno? Ten token nie będzie już uwierzytelniał żądań SCIM.",
|
|
100
|
+
"sso.admin.scim.revoke.title": "Odwołaj token",
|
|
101
|
+
"sso.admin.scim.revoked": "Token odwołany",
|
|
102
|
+
"sso.admin.scim.tokenActive": "Aktywny",
|
|
103
|
+
"sso.admin.scim.tokenCopied": "Token skopiowany do schowka",
|
|
104
|
+
"sso.admin.scim.tokenCreated": "Twój token SCIM został utworzony. Skopiuj go teraz — nie będzie ponownie wyświetlony.",
|
|
105
|
+
"sso.admin.scim.tokenNamePlaceholder": "Nazwa tokena (np. Entra ID Produkcja)",
|
|
106
|
+
"sso.admin.scim.tokenRevoked": "Odwołany",
|
|
107
|
+
"sso.admin.scim.tokens": "Tokeny Bearer",
|
|
108
|
+
"sso.admin.search": "Szukaj po nazwie lub wystawcy...",
|
|
109
|
+
"sso.admin.section.allowedDomains": "Dozwolone domeny",
|
|
110
|
+
"sso.admin.section.oidcSettings": "Ustawienia OIDC",
|
|
111
|
+
"sso.admin.status.active": "Aktywny",
|
|
112
|
+
"sso.admin.status.inactive": "Nieaktywny",
|
|
113
|
+
"sso.admin.tab.activity": "Aktywność",
|
|
114
|
+
"sso.admin.tab.domains": "Domeny",
|
|
115
|
+
"sso.admin.tab.general": "Ogólne",
|
|
116
|
+
"sso.admin.tab.roles": "Mapowanie ról",
|
|
117
|
+
"sso.admin.tab.scim": "Provisioning",
|
|
118
|
+
"sso.admin.test.failed": "Weryfikacja Discovery nie powiodła się",
|
|
119
|
+
"sso.admin.test.success": "Weryfikacja Discovery udana — wystawca jest osiągalny",
|
|
120
|
+
"sso.admin.title": "Single Sign-On",
|
|
121
|
+
"sso.admin.wizard.credentials.callbackUrl": "Redirect URI (skopiuj do swojego IdP)",
|
|
122
|
+
"sso.admin.wizard.credentials.namePlaceholder": "np. Zitadel Produkcja",
|
|
123
|
+
"sso.admin.wizard.credentials.title": "Dane uwierzytelniające OIDC",
|
|
124
|
+
"sso.admin.wizard.domain.duplicate": "Domena już dodana",
|
|
125
|
+
"sso.admin.wizard.domain.invalid": "Nieprawidłowy format domeny",
|
|
126
|
+
"sso.admin.wizard.domain.limit": "Maksymalnie 20 domen na konfigurację",
|
|
127
|
+
"sso.admin.wizard.domains.description": "Użytkownicy z adresami e-mail pasującymi do tych domen będą przekierowywani do dostawcy SSO.",
|
|
128
|
+
"sso.admin.wizard.domains.placeholder": "example.com",
|
|
129
|
+
"sso.admin.wizard.domains.title": "Dozwolone domeny e-mail",
|
|
130
|
+
"sso.admin.wizard.options.title": "Opcje",
|
|
131
|
+
"sso.admin.wizard.protocol.oidcDesc": "Działa z Zitadel, Microsoft Entra ID, Google Workspace, Okta i innymi",
|
|
132
|
+
"sso.admin.wizard.protocol.samlDesc": "Wkrótce",
|
|
133
|
+
"sso.admin.wizard.protocol.title": "Wybierz protokół",
|
|
134
|
+
"sso.admin.wizard.review.note": "Konfiguracja zostanie utworzona jako nieaktywna. Możesz ją aktywować na stronie szczegółów po zweryfikowaniu poprawności ustawień.",
|
|
135
|
+
"sso.admin.wizard.review.save": "Utwórz konfigurację",
|
|
136
|
+
"sso.admin.wizard.review.testing": "Testowanie...",
|
|
137
|
+
"sso.admin.wizard.review.title": "Przegląd i zapis",
|
|
138
|
+
"sso.login.continueWithSso": "Kontynuuj przez SSO",
|
|
139
|
+
"sso.login.errors.emailNotVerified": "Twój adres e-mail nie został zweryfikowany przez dostawcę tożsamości. Zweryfikuj swój e-mail i spróbuj ponownie.",
|
|
140
|
+
"sso.login.errors.failed": "Logowanie SSO nie powiodło się. Spróbuj ponownie.",
|
|
141
|
+
"sso.login.errors.idpError": "Dostawca tożsamości zwrócił błąd. Spróbuj ponownie lub skontaktuj się z administratorem.",
|
|
142
|
+
"sso.login.errors.missingConfig": "SSO nie jest skonfigurowane dla tego konta.",
|
|
143
|
+
"sso.login.errors.missingParams": "Callback SSO był niekompletny. Spróbuj ponownie.",
|
|
144
|
+
"sso.login.errors.stateMissing": "Sesja SSO wygasła. Spróbuj ponownie.",
|
|
145
|
+
"sso.login.ssoEnabled": "SSO jest włączone dla tego konta"
|
|
146
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/modules/sso/index.ts"],
|
|
4
|
+
"sourcesContent": ["export const metadata = {\n id: 'sso',\n version: '0.1.0',\n enterprise: true,\n} as const\n\nexport { features } from './acl'\n"],
|
|
5
|
+
"mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,IAAI;AAAA,EACJ,SAAS;AAAA,EACT,YAAY;AACd;AAEA,SAAS,gBAAgB;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const DOMAIN_REGEX = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$/;
|
|
2
|
+
const MAX_DOMAINS_PER_CONFIG = 20;
|
|
3
|
+
function normalizeDomain(domain) {
|
|
4
|
+
return domain.trim().toLowerCase();
|
|
5
|
+
}
|
|
6
|
+
function validateDomain(domain) {
|
|
7
|
+
const normalized = normalizeDomain(domain);
|
|
8
|
+
if (!normalized) return { valid: false, error: "Domain cannot be empty" };
|
|
9
|
+
if (normalized.length > 253) return { valid: false, error: "Domain exceeds maximum length of 253 characters" };
|
|
10
|
+
if (!DOMAIN_REGEX.test(normalized)) return { valid: false, error: "Invalid domain format \u2014 only DNS hostnames are accepted" };
|
|
11
|
+
if (!normalized.includes(".")) return { valid: false, error: "Domain must include at least one dot (e.g., example.com)" };
|
|
12
|
+
return { valid: true };
|
|
13
|
+
}
|
|
14
|
+
function uniqueDomains(domains) {
|
|
15
|
+
return [...new Set(domains.map(normalizeDomain).filter(Boolean))];
|
|
16
|
+
}
|
|
17
|
+
function checkDomainLimit(currentCount, adding) {
|
|
18
|
+
if (currentCount + adding > MAX_DOMAINS_PER_CONFIG) {
|
|
19
|
+
return { ok: false, error: `Maximum ${MAX_DOMAINS_PER_CONFIG} domains per SSO configuration` };
|
|
20
|
+
}
|
|
21
|
+
return { ok: true };
|
|
22
|
+
}
|
|
23
|
+
export {
|
|
24
|
+
MAX_DOMAINS_PER_CONFIG,
|
|
25
|
+
checkDomainLimit,
|
|
26
|
+
normalizeDomain,
|
|
27
|
+
uniqueDomains,
|
|
28
|
+
validateDomain
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=domains.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/sso/lib/domains.ts"],
|
|
4
|
+
"sourcesContent": ["const DOMAIN_REGEX = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$/\n\nconst MAX_DOMAINS_PER_CONFIG = 20\n\nexport function normalizeDomain(domain: string): string {\n return domain.trim().toLowerCase()\n}\n\nexport function validateDomain(domain: string): { valid: boolean; error?: string } {\n const normalized = normalizeDomain(domain)\n\n if (!normalized) return { valid: false, error: 'Domain cannot be empty' }\n if (normalized.length > 253) return { valid: false, error: 'Domain exceeds maximum length of 253 characters' }\n if (!DOMAIN_REGEX.test(normalized)) return { valid: false, error: 'Invalid domain format \u2014 only DNS hostnames are accepted' }\n if (!normalized.includes('.')) return { valid: false, error: 'Domain must include at least one dot (e.g., example.com)' }\n\n return { valid: true }\n}\n\nexport function uniqueDomains(domains: string[]): string[] {\n return [...new Set(domains.map(normalizeDomain).filter(Boolean))]\n}\n\nexport function checkDomainLimit(currentCount: number, adding: number): { ok: boolean; error?: string } {\n if (currentCount + adding > MAX_DOMAINS_PER_CONFIG) {\n return { ok: false, error: `Maximum ${MAX_DOMAINS_PER_CONFIG} domains per SSO configuration` }\n }\n return { ok: true }\n}\n\nexport { MAX_DOMAINS_PER_CONFIG }\n"],
|
|
5
|
+
"mappings": "AAAA,MAAM,eAAe;AAErB,MAAM,yBAAyB;AAExB,SAAS,gBAAgB,QAAwB;AACtD,SAAO,OAAO,KAAK,EAAE,YAAY;AACnC;AAEO,SAAS,eAAe,QAAoD;AACjF,QAAM,aAAa,gBAAgB,MAAM;AAEzC,MAAI,CAAC,WAAY,QAAO,EAAE,OAAO,OAAO,OAAO,yBAAyB;AACxE,MAAI,WAAW,SAAS,IAAK,QAAO,EAAE,OAAO,OAAO,OAAO,kDAAkD;AAC7G,MAAI,CAAC,aAAa,KAAK,UAAU,EAAG,QAAO,EAAE,OAAO,OAAO,OAAO,+DAA0D;AAC5H,MAAI,CAAC,WAAW,SAAS,GAAG,EAAG,QAAO,EAAE,OAAO,OAAO,OAAO,2DAA2D;AAExH,SAAO,EAAE,OAAO,KAAK;AACvB;AAEO,SAAS,cAAc,SAA6B;AACzD,SAAO,CAAC,GAAG,IAAI,IAAI,QAAQ,IAAI,eAAe,EAAE,OAAO,OAAO,CAAC,CAAC;AAClE;AAEO,SAAS,iBAAiB,cAAsB,QAAiD;AACtG,MAAI,eAAe,SAAS,wBAAwB;AAClD,WAAO,EAAE,IAAI,OAAO,OAAO,WAAW,sBAAsB,iCAAiC;AAAA,EAC/F;AACA,SAAO,EAAE,IAAI,KAAK;AACpB;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import * as client from "openid-client";
|
|
2
|
+
class OidcProvider {
|
|
3
|
+
constructor() {
|
|
4
|
+
this.protocol = "oidc";
|
|
5
|
+
}
|
|
6
|
+
async buildAuthUrl(config, params) {
|
|
7
|
+
const oidcConfig = await this.discover(config, params.clientSecret);
|
|
8
|
+
const codeChallenge = params.codeVerifier ? await client.calculatePKCECodeChallenge(params.codeVerifier) : void 0;
|
|
9
|
+
const authUrl = client.buildAuthorizationUrl(oidcConfig, {
|
|
10
|
+
redirect_uri: params.redirectUri,
|
|
11
|
+
scope: "openid email profile",
|
|
12
|
+
state: params.state,
|
|
13
|
+
nonce: params.nonce,
|
|
14
|
+
...codeChallenge ? { code_challenge: codeChallenge, code_challenge_method: "S256" } : {}
|
|
15
|
+
});
|
|
16
|
+
return authUrl.href;
|
|
17
|
+
}
|
|
18
|
+
async handleCallback(config, params) {
|
|
19
|
+
const oidcConfig = await this.discover(config, params.clientSecret);
|
|
20
|
+
const callbackUrl = new URL(params.redirectUri);
|
|
21
|
+
for (const [key, value] of Object.entries(params.callbackParams)) {
|
|
22
|
+
callbackUrl.searchParams.set(key, value);
|
|
23
|
+
}
|
|
24
|
+
const tokens = await client.authorizationCodeGrant(oidcConfig, callbackUrl, {
|
|
25
|
+
pkceCodeVerifier: params.codeVerifier,
|
|
26
|
+
expectedState: params.expectedState,
|
|
27
|
+
expectedNonce: params.expectedNonce
|
|
28
|
+
});
|
|
29
|
+
const claims = tokens.claims();
|
|
30
|
+
if (!claims) {
|
|
31
|
+
throw new Error("No ID token claims received from IdP");
|
|
32
|
+
}
|
|
33
|
+
const mergedClaims = await mergeWithUserInfoClaims(oidcConfig, tokens, claims);
|
|
34
|
+
const subject = String(mergedClaims.sub ?? claims.sub ?? "");
|
|
35
|
+
const rawEmail = mergedClaims.email;
|
|
36
|
+
const upnCandidate = mergedClaims.upn ?? mergedClaims.unique_name;
|
|
37
|
+
const upnAsEmail = typeof upnCandidate === "string" && upnCandidate.includes("@") ? upnCandidate : void 0;
|
|
38
|
+
const email = rawEmail ?? upnAsEmail;
|
|
39
|
+
if (!email) {
|
|
40
|
+
throw new Error("IdP did not return an email claim (checked: email, upn, unique_name)");
|
|
41
|
+
}
|
|
42
|
+
const emailVerified = mergedClaims.email_verified === true;
|
|
43
|
+
const groups = extractIdentityGroups(mergedClaims);
|
|
44
|
+
return {
|
|
45
|
+
subject,
|
|
46
|
+
email,
|
|
47
|
+
emailVerified,
|
|
48
|
+
name: mergedClaims.name ?? void 0,
|
|
49
|
+
groups
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
async validateConfig(config, params) {
|
|
53
|
+
try {
|
|
54
|
+
await this.discover(config, params?.clientSecret);
|
|
55
|
+
return { ok: true };
|
|
56
|
+
} catch (err) {
|
|
57
|
+
return {
|
|
58
|
+
ok: false,
|
|
59
|
+
error: err instanceof Error ? err.message : "Discovery failed"
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async discover(config, clientSecret) {
|
|
64
|
+
if (!config.issuer) {
|
|
65
|
+
throw new Error("SSO config is missing issuer URL");
|
|
66
|
+
}
|
|
67
|
+
if (!config.clientId) {
|
|
68
|
+
throw new Error("SSO config is missing client ID");
|
|
69
|
+
}
|
|
70
|
+
return client.discovery(
|
|
71
|
+
new URL(config.issuer),
|
|
72
|
+
config.clientId,
|
|
73
|
+
clientSecret ?? void 0
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async function mergeWithUserInfoClaims(oidcConfig, tokens, claims) {
|
|
78
|
+
const accessToken = tokens.access_token;
|
|
79
|
+
if (!accessToken) return claims;
|
|
80
|
+
try {
|
|
81
|
+
const userInfo = await client.fetchUserInfo(
|
|
82
|
+
oidcConfig,
|
|
83
|
+
accessToken,
|
|
84
|
+
client.skipSubjectCheck
|
|
85
|
+
);
|
|
86
|
+
return { ...userInfo, ...claims };
|
|
87
|
+
} catch {
|
|
88
|
+
return claims;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function extractIdentityGroups(claims) {
|
|
92
|
+
const groups = /* @__PURE__ */ new Set();
|
|
93
|
+
const add = (value) => {
|
|
94
|
+
for (const group of coerceClaimValues(value)) {
|
|
95
|
+
groups.add(group);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
add(claims.groups);
|
|
99
|
+
add(claims.roles);
|
|
100
|
+
add(claims.role);
|
|
101
|
+
for (const [key, value] of Object.entries(claims)) {
|
|
102
|
+
if (!key.endsWith(":roles")) continue;
|
|
103
|
+
add(value);
|
|
104
|
+
}
|
|
105
|
+
return groups.size > 0 ? Array.from(groups) : void 0;
|
|
106
|
+
}
|
|
107
|
+
function coerceClaimValues(value) {
|
|
108
|
+
if (typeof value === "string") {
|
|
109
|
+
const normalized = value.trim();
|
|
110
|
+
return normalized ? [normalized] : [];
|
|
111
|
+
}
|
|
112
|
+
if (Array.isArray(value)) {
|
|
113
|
+
return value.flatMap((entry) => coerceClaimValues(entry));
|
|
114
|
+
}
|
|
115
|
+
if (value && typeof value === "object") {
|
|
116
|
+
const entries = Object.entries(value);
|
|
117
|
+
const out = /* @__PURE__ */ new Set();
|
|
118
|
+
for (const [key, nested] of entries) {
|
|
119
|
+
const normalizedKey = key.trim();
|
|
120
|
+
if (normalizedKey) out.add(normalizedKey);
|
|
121
|
+
if (typeof nested === "string") {
|
|
122
|
+
const normalizedNested = nested.trim();
|
|
123
|
+
if (normalizedNested) out.add(normalizedNested);
|
|
124
|
+
} else if (nested && typeof nested === "object") {
|
|
125
|
+
const nestedName = nested.name;
|
|
126
|
+
if (typeof nestedName === "string" && nestedName.trim()) {
|
|
127
|
+
out.add(nestedName.trim());
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return Array.from(out);
|
|
132
|
+
}
|
|
133
|
+
return [];
|
|
134
|
+
}
|
|
135
|
+
export {
|
|
136
|
+
OidcProvider,
|
|
137
|
+
coerceClaimValues,
|
|
138
|
+
extractIdentityGroups
|
|
139
|
+
};
|
|
140
|
+
//# sourceMappingURL=oidc-provider.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/sso/lib/oidc-provider.ts"],
|
|
4
|
+
"sourcesContent": ["import * as client from 'openid-client'\nimport type { SsoConfig } from '../data/entities'\nimport type { SsoIdentityPayload, SsoProtocolProvider } from './types'\n\nexport class OidcProvider implements SsoProtocolProvider {\n readonly protocol = 'oidc' as const\n\n async buildAuthUrl(\n config: SsoConfig,\n params: {\n state: string\n nonce: string\n redirectUri: string\n codeVerifier?: string\n clientSecret?: string\n },\n ): Promise<string> {\n const oidcConfig = await this.discover(config, params.clientSecret)\n\n const codeChallenge = params.codeVerifier\n ? await client.calculatePKCECodeChallenge(params.codeVerifier)\n : undefined\n\n const authUrl = client.buildAuthorizationUrl(oidcConfig, {\n redirect_uri: params.redirectUri,\n scope: 'openid email profile',\n state: params.state,\n nonce: params.nonce,\n ...(codeChallenge\n ? { code_challenge: codeChallenge, code_challenge_method: 'S256' }\n : {}),\n })\n\n return authUrl.href\n }\n\n async handleCallback(\n config: SsoConfig,\n params: {\n callbackParams: Record<string, string>\n redirectUri: string\n expectedState: string\n expectedNonce: string\n codeVerifier?: string\n clientSecret?: string\n },\n ): Promise<SsoIdentityPayload> {\n const oidcConfig = await this.discover(config, params.clientSecret)\n\n const callbackUrl = new URL(params.redirectUri)\n for (const [key, value] of Object.entries(params.callbackParams)) {\n callbackUrl.searchParams.set(key, value)\n }\n\n const tokens = await client.authorizationCodeGrant(oidcConfig, callbackUrl, {\n pkceCodeVerifier: params.codeVerifier,\n expectedState: params.expectedState,\n expectedNonce: params.expectedNonce,\n })\n\n const claims = tokens.claims()\n if (!claims) {\n throw new Error('No ID token claims received from IdP')\n }\n\n const mergedClaims = await mergeWithUserInfoClaims(oidcConfig, tokens, claims)\n\n const subject = String(mergedClaims.sub ?? claims.sub ?? '')\n const rawEmail = mergedClaims.email as string | undefined\n const upnCandidate = (mergedClaims.upn ?? mergedClaims.unique_name) as string | undefined\n const upnAsEmail = typeof upnCandidate === 'string' && upnCandidate.includes('@') ? upnCandidate : undefined\n const email = rawEmail ?? upnAsEmail\n if (!email) {\n throw new Error('IdP did not return an email claim (checked: email, upn, unique_name)')\n }\n\n const emailVerified = mergedClaims.email_verified === true\n const groups = extractIdentityGroups(mergedClaims)\n\n \n\n return {\n subject,\n email,\n emailVerified,\n name: (mergedClaims.name as string) ?? undefined,\n groups,\n }\n }\n\n async validateConfig(\n config: SsoConfig,\n params?: { clientSecret?: string },\n ): Promise<{ ok: boolean; error?: string }> {\n try {\n await this.discover(config, params?.clientSecret)\n return { ok: true }\n } catch (err) {\n return {\n ok: false,\n error: err instanceof Error ? err.message : 'Discovery failed',\n }\n }\n }\n\n private async discover(\n config: SsoConfig,\n clientSecret?: string,\n ): Promise<client.Configuration> {\n if (!config.issuer) {\n throw new Error('SSO config is missing issuer URL')\n }\n if (!config.clientId) {\n throw new Error('SSO config is missing client ID')\n }\n\n return client.discovery(\n new URL(config.issuer),\n config.clientId,\n clientSecret ?? undefined,\n )\n }\n}\n\nasync function mergeWithUserInfoClaims(\n oidcConfig: client.Configuration,\n tokens: client.TokenEndpointResponse,\n claims: Record<string, unknown>,\n): Promise<Record<string, unknown>> {\n const accessToken = tokens.access_token\n if (!accessToken) return claims\n\n try {\n const userInfo = await client.fetchUserInfo(\n oidcConfig,\n accessToken,\n client.skipSubjectCheck,\n )\n return { ...(userInfo as Record<string, unknown>), ...claims }\n } catch {\n return claims\n }\n}\n\nexport function extractIdentityGroups(claims: Record<string, unknown>): string[] | undefined {\n const groups = new Set<string>()\n\n const add = (value: unknown) => {\n for (const group of coerceClaimValues(value)) {\n groups.add(group)\n }\n }\n\n add(claims.groups)\n add(claims.roles)\n add(claims.role)\n\n for (const [key, value] of Object.entries(claims)) {\n if (!key.endsWith(':roles')) continue\n add(value)\n }\n\n return groups.size > 0 ? Array.from(groups) : undefined\n}\n\nexport function coerceClaimValues(value: unknown): string[] {\n if (typeof value === 'string') {\n const normalized = value.trim()\n return normalized ? [normalized] : []\n }\n\n if (Array.isArray(value)) {\n return value.flatMap((entry) => coerceClaimValues(entry))\n }\n\n if (value && typeof value === 'object') {\n const entries = Object.entries(value as Record<string, unknown>)\n const out = new Set<string>()\n for (const [key, nested] of entries) {\n const normalizedKey = key.trim()\n if (normalizedKey) out.add(normalizedKey)\n if (typeof nested === 'string') {\n const normalizedNested = nested.trim()\n if (normalizedNested) out.add(normalizedNested)\n } else if (nested && typeof nested === 'object') {\n const nestedName = (nested as Record<string, unknown>).name\n if (typeof nestedName === 'string' && nestedName.trim()) {\n out.add(nestedName.trim())\n }\n }\n }\n return Array.from(out)\n }\n\n return []\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class SsoProviderRegistry {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.providers = /* @__PURE__ */ new Map();
|
|
4
|
+
}
|
|
5
|
+
register(provider) {
|
|
6
|
+
this.providers.set(provider.protocol, provider);
|
|
7
|
+
}
|
|
8
|
+
resolve(protocol) {
|
|
9
|
+
return this.providers.get(protocol);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export {
|
|
13
|
+
SsoProviderRegistry
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/sso/lib/registry.ts"],
|
|
4
|
+
"sourcesContent": ["import type { SsoProtocolProvider } from './types'\n\nexport class SsoProviderRegistry {\n private providers = new Map<string, SsoProtocolProvider>()\n\n register(provider: SsoProtocolProvider): void {\n this.providers.set(provider.protocol, provider)\n }\n\n resolve(protocol: string): SsoProtocolProvider | undefined {\n return this.providers.get(protocol)\n }\n}\n"],
|
|
5
|
+
"mappings": "AAEO,MAAM,oBAAoB;AAAA,EAA1B;AACL,SAAQ,YAAY,oBAAI,IAAiC;AAAA;AAAA,EAEzD,SAAS,UAAqC;AAC5C,SAAK,UAAU,IAAI,SAAS,UAAU,QAAQ;AAAA,EAChD;AAAA,EAEA,QAAQ,UAAmD;AACzD,WAAO,KAAK,UAAU,IAAI,QAAQ;AAAA,EACpC;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
function parseScimFilter(filter) {
|
|
2
|
+
if (!filter || !filter.trim()) return [];
|
|
3
|
+
const conditions = [];
|
|
4
|
+
const parts = filter.split(/\s+and\s+/i);
|
|
5
|
+
for (const part of parts) {
|
|
6
|
+
const match = part.trim().match(/^(\S+)\s+eq\s+"([^"]*)"$/i);
|
|
7
|
+
if (!match) continue;
|
|
8
|
+
const [, attribute, value] = match;
|
|
9
|
+
const normalizedAttr = attribute.toLowerCase();
|
|
10
|
+
const allowed = ["username", "externalid", "displayname", "active"];
|
|
11
|
+
if (!allowed.includes(normalizedAttr)) continue;
|
|
12
|
+
conditions.push({ attribute: normalizedAttr, value });
|
|
13
|
+
}
|
|
14
|
+
return conditions;
|
|
15
|
+
}
|
|
16
|
+
function scimFilterToWhere(conditions, ssoConfigId, organizationId) {
|
|
17
|
+
const where = {
|
|
18
|
+
ssoConfigId,
|
|
19
|
+
organizationId,
|
|
20
|
+
deletedAt: null
|
|
21
|
+
};
|
|
22
|
+
for (const { attribute, value } of conditions) {
|
|
23
|
+
switch (attribute) {
|
|
24
|
+
case "username":
|
|
25
|
+
where.idpEmail = value;
|
|
26
|
+
break;
|
|
27
|
+
case "externalid":
|
|
28
|
+
where.externalId = value;
|
|
29
|
+
break;
|
|
30
|
+
case "displayname":
|
|
31
|
+
where.idpName = value;
|
|
32
|
+
break;
|
|
33
|
+
case "active":
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return where;
|
|
38
|
+
}
|
|
39
|
+
export {
|
|
40
|
+
parseScimFilter,
|
|
41
|
+
scimFilterToWhere
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=scim-filter.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/sso/lib/scim-filter.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Minimal SCIM filter parser supporting `eq` operator with `and` combinator.\n * Supports: userName, externalId, displayName, active\n */\n\nexport interface ScimFilterCondition {\n attribute: string\n value: string\n}\n\nexport function parseScimFilter(filter: string | null | undefined): ScimFilterCondition[] {\n if (!filter || !filter.trim()) return []\n\n const conditions: ScimFilterCondition[] = []\n const parts = filter.split(/\\s+and\\s+/i)\n\n for (const part of parts) {\n const match = part.trim().match(/^(\\S+)\\s+eq\\s+\"([^\"]*)\"$/i)\n if (!match) continue\n\n const [, attribute, value] = match\n const normalizedAttr = attribute.toLowerCase()\n\n const allowed = ['username', 'externalid', 'displayname', 'active']\n if (!allowed.includes(normalizedAttr)) continue\n\n conditions.push({ attribute: normalizedAttr, value })\n }\n\n return conditions\n}\n\nexport function scimFilterToWhere(\n conditions: ScimFilterCondition[],\n ssoConfigId: string,\n organizationId: string,\n): Record<string, unknown> {\n const where: Record<string, unknown> = {\n ssoConfigId,\n organizationId,\n deletedAt: null,\n }\n\n for (const { attribute, value } of conditions) {\n switch (attribute) {\n case 'username':\n where.idpEmail = value\n break\n case 'externalid':\n where.externalId = value\n break\n case 'displayname':\n where.idpName = value\n break\n case 'active':\n // Handled at application level (requires SsoUserDeactivation lookup)\n break\n }\n }\n\n return where\n}\n"],
|
|
5
|
+
"mappings": "AAUO,SAAS,gBAAgB,QAA0D;AACxF,MAAI,CAAC,UAAU,CAAC,OAAO,KAAK,EAAG,QAAO,CAAC;AAEvC,QAAM,aAAoC,CAAC;AAC3C,QAAM,QAAQ,OAAO,MAAM,YAAY;AAEvC,aAAW,QAAQ,OAAO;AACxB,UAAM,QAAQ,KAAK,KAAK,EAAE,MAAM,2BAA2B;AAC3D,QAAI,CAAC,MAAO;AAEZ,UAAM,CAAC,EAAE,WAAW,KAAK,IAAI;AAC7B,UAAM,iBAAiB,UAAU,YAAY;AAE7C,UAAM,UAAU,CAAC,YAAY,cAAc,eAAe,QAAQ;AAClE,QAAI,CAAC,QAAQ,SAAS,cAAc,EAAG;AAEvC,eAAW,KAAK,EAAE,WAAW,gBAAgB,MAAM,CAAC;AAAA,EACtD;AAEA,SAAO;AACT;AAEO,SAAS,kBACd,YACA,aACA,gBACyB;AACzB,QAAM,QAAiC;AAAA,IACrC;AAAA,IACA;AAAA,IACA,WAAW;AAAA,EACb;AAEA,aAAW,EAAE,WAAW,MAAM,KAAK,YAAY;AAC7C,YAAQ,WAAW;AAAA,MACjB,KAAK;AACH,cAAM,WAAW;AACjB;AAAA,MACF,KAAK;AACH,cAAM,aAAa;AACnB;AAAA,MACF,KAAK;AACH,cAAM,UAAU;AAChB;AAAA,MACF,KAAK;AAEH;AAAA,IACJ;AAAA,EACF;AAEA,SAAO;AACT;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { coerceBoolean } from "./scim-utils.js";
|
|
2
|
+
const SCIM_USER_SCHEMA = "urn:ietf:params:scim:schemas:core:2.0:User";
|
|
3
|
+
function toScimUserResource(user, identity, baseUrl, deactivation) {
|
|
4
|
+
const isActive = !deactivation || deactivation.reactivatedAt != null;
|
|
5
|
+
const nameParts = (user.name ?? "").split(" ");
|
|
6
|
+
const givenName = nameParts[0] || void 0;
|
|
7
|
+
const familyName = nameParts.length > 1 ? nameParts.slice(1).join(" ") : void 0;
|
|
8
|
+
return {
|
|
9
|
+
schemas: [SCIM_USER_SCHEMA],
|
|
10
|
+
id: identity.id,
|
|
11
|
+
...identity.externalId ? { externalId: identity.externalId } : {},
|
|
12
|
+
userName: identity.idpEmail,
|
|
13
|
+
displayName: user.name ?? void 0,
|
|
14
|
+
name: givenName || familyName ? { givenName, familyName, formatted: user.name ?? void 0 } : void 0,
|
|
15
|
+
emails: [{ value: identity.idpEmail, primary: true, type: "work" }],
|
|
16
|
+
active: isActive,
|
|
17
|
+
meta: {
|
|
18
|
+
resourceType: "User",
|
|
19
|
+
created: identity.createdAt.toISOString(),
|
|
20
|
+
lastModified: identity.updatedAt.toISOString(),
|
|
21
|
+
location: `${baseUrl}/api/sso/scim/v2/Users/${identity.id}`
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function fromScimUserPayload(payload) {
|
|
26
|
+
const result = {};
|
|
27
|
+
if (typeof payload.userName === "string") result.userName = payload.userName;
|
|
28
|
+
if (typeof payload.externalId === "string") result.externalId = payload.externalId;
|
|
29
|
+
if (typeof payload.displayName === "string") result.displayName = payload.displayName;
|
|
30
|
+
if (payload.active !== void 0) {
|
|
31
|
+
result.active = coerceBoolean(payload.active);
|
|
32
|
+
}
|
|
33
|
+
const name = payload.name;
|
|
34
|
+
if (name && typeof name === "object") {
|
|
35
|
+
if (typeof name.givenName === "string") result.givenName = name.givenName;
|
|
36
|
+
if (typeof name.familyName === "string") result.familyName = name.familyName;
|
|
37
|
+
}
|
|
38
|
+
const emails = payload.emails;
|
|
39
|
+
if (Array.isArray(emails) && emails.length > 0) {
|
|
40
|
+
const primary = emails.find((e) => e.primary === true) ?? emails[0];
|
|
41
|
+
if (typeof primary?.value === "string") result.email = primary.value;
|
|
42
|
+
}
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
export {
|
|
46
|
+
fromScimUserPayload,
|
|
47
|
+
toScimUserResource
|
|
48
|
+
};
|
|
49
|
+
//# sourceMappingURL=scim-mapper.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/sso/lib/scim-mapper.ts"],
|
|
4
|
+
"sourcesContent": ["import type { User } from '@open-mercato/core/modules/auth/data/entities'\nimport type { SsoIdentity, SsoUserDeactivation } from '../data/entities'\nimport { coerceBoolean } from './scim-utils'\n\nconst SCIM_USER_SCHEMA = 'urn:ietf:params:scim:schemas:core:2.0:User'\n\nexport interface ScimUserResource {\n schemas: string[]\n id: string\n externalId?: string\n userName: string\n displayName?: string\n name?: { givenName?: string; familyName?: string; formatted?: string }\n emails?: Array<{ value: string; primary: boolean; type: string }>\n active: boolean\n meta: {\n resourceType: string\n created: string\n lastModified: string\n location: string\n }\n}\n\nexport function toScimUserResource(\n user: User,\n identity: SsoIdentity,\n baseUrl: string,\n deactivation?: SsoUserDeactivation | null,\n): ScimUserResource {\n const isActive = !deactivation || deactivation.reactivatedAt != null\n\n const nameParts = (user.name ?? '').split(' ')\n const givenName = nameParts[0] || undefined\n const familyName = nameParts.length > 1 ? nameParts.slice(1).join(' ') : undefined\n\n return {\n schemas: [SCIM_USER_SCHEMA],\n id: identity.id,\n ...(identity.externalId ? { externalId: identity.externalId } : {}),\n userName: identity.idpEmail,\n displayName: user.name ?? undefined,\n name: (givenName || familyName) ? { givenName, familyName, formatted: user.name ?? undefined } : undefined,\n emails: [{ value: identity.idpEmail, primary: true, type: 'work' }],\n active: isActive,\n meta: {\n resourceType: 'User',\n created: identity.createdAt.toISOString(),\n lastModified: identity.updatedAt.toISOString(),\n location: `${baseUrl}/api/sso/scim/v2/Users/${identity.id}`,\n },\n }\n}\n\nexport interface ScimUserPayload {\n userName?: string\n externalId?: string\n displayName?: string\n givenName?: string\n familyName?: string\n email?: string\n active?: boolean\n}\n\nexport function fromScimUserPayload(payload: Record<string, unknown>): ScimUserPayload {\n const result: ScimUserPayload = {}\n\n if (typeof payload.userName === 'string') result.userName = payload.userName\n if (typeof payload.externalId === 'string') result.externalId = payload.externalId\n if (typeof payload.displayName === 'string') result.displayName = payload.displayName\n\n if (payload.active !== undefined) {\n result.active = coerceBoolean(payload.active)\n }\n\n const name = payload.name as Record<string, unknown> | undefined\n if (name && typeof name === 'object') {\n if (typeof name.givenName === 'string') result.givenName = name.givenName\n if (typeof name.familyName === 'string') result.familyName = name.familyName\n }\n\n const emails = payload.emails as Array<Record<string, unknown>> | undefined\n if (Array.isArray(emails) && emails.length > 0) {\n const primary = emails.find((e) => e.primary === true) ?? emails[0]\n if (typeof primary?.value === 'string') result.email = primary.value\n }\n\n return result\n}\n"],
|
|
5
|
+
"mappings": "AAEA,SAAS,qBAAqB;AAE9B,MAAM,mBAAmB;AAmBlB,SAAS,mBACd,MACA,UACA,SACA,cACkB;AAClB,QAAM,WAAW,CAAC,gBAAgB,aAAa,iBAAiB;AAEhE,QAAM,aAAa,KAAK,QAAQ,IAAI,MAAM,GAAG;AAC7C,QAAM,YAAY,UAAU,CAAC,KAAK;AAClC,QAAM,aAAa,UAAU,SAAS,IAAI,UAAU,MAAM,CAAC,EAAE,KAAK,GAAG,IAAI;AAEzE,SAAO;AAAA,IACL,SAAS,CAAC,gBAAgB;AAAA,IAC1B,IAAI,SAAS;AAAA,IACb,GAAI,SAAS,aAAa,EAAE,YAAY,SAAS,WAAW,IAAI,CAAC;AAAA,IACjE,UAAU,SAAS;AAAA,IACnB,aAAa,KAAK,QAAQ;AAAA,IAC1B,MAAO,aAAa,aAAc,EAAE,WAAW,YAAY,WAAW,KAAK,QAAQ,OAAU,IAAI;AAAA,IACjG,QAAQ,CAAC,EAAE,OAAO,SAAS,UAAU,SAAS,MAAM,MAAM,OAAO,CAAC;AAAA,IAClE,QAAQ;AAAA,IACR,MAAM;AAAA,MACJ,cAAc;AAAA,MACd,SAAS,SAAS,UAAU,YAAY;AAAA,MACxC,cAAc,SAAS,UAAU,YAAY;AAAA,MAC7C,UAAU,GAAG,OAAO,0BAA0B,SAAS,EAAE;AAAA,IAC3D;AAAA,EACF;AACF;AAYO,SAAS,oBAAoB,SAAmD;AACrF,QAAM,SAA0B,CAAC;AAEjC,MAAI,OAAO,QAAQ,aAAa,SAAU,QAAO,WAAW,QAAQ;AACpE,MAAI,OAAO,QAAQ,eAAe,SAAU,QAAO,aAAa,QAAQ;AACxE,MAAI,OAAO,QAAQ,gBAAgB,SAAU,QAAO,cAAc,QAAQ;AAE1E,MAAI,QAAQ,WAAW,QAAW;AAChC,WAAO,SAAS,cAAc,QAAQ,MAAM;AAAA,EAC9C;AAEA,QAAM,OAAO,QAAQ;AACrB,MAAI,QAAQ,OAAO,SAAS,UAAU;AACpC,QAAI,OAAO,KAAK,cAAc,SAAU,QAAO,YAAY,KAAK;AAChE,QAAI,OAAO,KAAK,eAAe,SAAU,QAAO,aAAa,KAAK;AAAA,EACpE;AAEA,QAAM,SAAS,QAAQ;AACvB,MAAI,MAAM,QAAQ,MAAM,KAAK,OAAO,SAAS,GAAG;AAC9C,UAAM,UAAU,OAAO,KAAK,CAAC,MAAM,EAAE,YAAY,IAAI,KAAK,OAAO,CAAC;AAClE,QAAI,OAAO,SAAS,UAAU,SAAU,QAAO,QAAQ,QAAQ;AAAA,EACjE;AAEA,SAAO;AACT;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { coerceBoolean } from "./scim-utils.js";
|
|
2
|
+
const ALLOWED_PATHS = /* @__PURE__ */ new Set([
|
|
3
|
+
"active",
|
|
4
|
+
"displayname",
|
|
5
|
+
"name.givenname",
|
|
6
|
+
"name.familyname",
|
|
7
|
+
"username",
|
|
8
|
+
"externalid"
|
|
9
|
+
]);
|
|
10
|
+
function parseScimPatchOperations(body) {
|
|
11
|
+
const operations = body.Operations;
|
|
12
|
+
if (!Array.isArray(operations)) {
|
|
13
|
+
throw new ScimPatchError("PatchOp body must contain Operations array");
|
|
14
|
+
}
|
|
15
|
+
return operations.map((rawOp) => {
|
|
16
|
+
const op = String(rawOp.op ?? "").toLowerCase();
|
|
17
|
+
if (!["add", "replace", "remove"].includes(op)) {
|
|
18
|
+
throw new ScimPatchError(`Unsupported SCIM PatchOp: ${rawOp.op}`);
|
|
19
|
+
}
|
|
20
|
+
const path = rawOp.path ? String(rawOp.path) : void 0;
|
|
21
|
+
if (path) {
|
|
22
|
+
const normalizedPath = path.toLowerCase();
|
|
23
|
+
if (!ALLOWED_PATHS.has(normalizedPath)) {
|
|
24
|
+
return { op, path, value: void 0 };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const value = normalizePatchValue(rawOp.value, path);
|
|
28
|
+
return { op, path, value };
|
|
29
|
+
}).filter((op) => {
|
|
30
|
+
if (op.op !== "remove" && op.value === void 0 && op.path) return false;
|
|
31
|
+
return true;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function normalizePatchValue(value, path) {
|
|
35
|
+
if (value === void 0 || value === null) return value;
|
|
36
|
+
if (path && path.toLowerCase() === "active") {
|
|
37
|
+
return coerceBoolean(value);
|
|
38
|
+
}
|
|
39
|
+
if (!path && typeof value === "object" && value !== null) {
|
|
40
|
+
const obj = value;
|
|
41
|
+
const normalized = {};
|
|
42
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
43
|
+
if (key.toLowerCase() === "active") {
|
|
44
|
+
normalized[key] = coerceBoolean(val);
|
|
45
|
+
} else {
|
|
46
|
+
normalized[key] = val;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return normalized;
|
|
50
|
+
}
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
class ScimPatchError extends Error {
|
|
54
|
+
constructor(message) {
|
|
55
|
+
super(message);
|
|
56
|
+
this.name = "ScimPatchError";
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export {
|
|
60
|
+
ScimPatchError,
|
|
61
|
+
parseScimPatchOperations
|
|
62
|
+
};
|
|
63
|
+
//# sourceMappingURL=scim-patch.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/sso/lib/scim-patch.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * SCIM PatchOp parser with Entra ID quirks:\n * - Case-insensitive `op` (e.g., \"Replace\" vs \"replace\")\n * - Boolean leniency (\"False\"/\"True\" strings \u2192 boolean)\n * - Strict attribute allowlist\n */\n\nimport { coerceBoolean } from './scim-utils'\n\nexport interface ScimPatchOperation {\n op: string\n path?: string\n value?: unknown\n}\n\nconst ALLOWED_PATHS = new Set([\n 'active',\n 'displayname',\n 'name.givenname',\n 'name.familyname',\n 'username',\n 'externalid',\n])\n\nexport function parseScimPatchOperations(body: Record<string, unknown>): ScimPatchOperation[] {\n const operations = body.Operations as Array<Record<string, unknown>> | undefined\n if (!Array.isArray(operations)) {\n throw new ScimPatchError('PatchOp body must contain Operations array')\n }\n\n return operations.map((rawOp) => {\n const op = String(rawOp.op ?? '').toLowerCase()\n if (!['add', 'replace', 'remove'].includes(op)) {\n throw new ScimPatchError(`Unsupported SCIM PatchOp: ${rawOp.op}`)\n }\n\n const path = rawOp.path ? String(rawOp.path) : undefined\n\n // Validate path against allowlist if present\n if (path) {\n const normalizedPath = path.toLowerCase()\n if (!ALLOWED_PATHS.has(normalizedPath)) {\n // Silently ignore unsupported attributes (Entra sends many)\n return { op, path, value: undefined }\n }\n }\n\n const value = normalizePatchValue(rawOp.value, path)\n\n return { op, path, value }\n }).filter((op) => {\n // Filter out no-ops (unsupported paths where value was set to undefined)\n if (op.op !== 'remove' && op.value === undefined && op.path) return false\n return true\n })\n}\n\nfunction normalizePatchValue(value: unknown, path?: string): unknown {\n if (value === undefined || value === null) return value\n\n // Handle boolean leniency for the `active` attribute\n if (path && path.toLowerCase() === 'active') {\n return coerceBoolean(value)\n }\n\n // Handle value objects (no-path operations)\n if (!path && typeof value === 'object' && value !== null) {\n const obj = value as Record<string, unknown>\n const normalized: Record<string, unknown> = {}\n for (const [key, val] of Object.entries(obj)) {\n if (key.toLowerCase() === 'active') {\n normalized[key] = coerceBoolean(val)\n } else {\n normalized[key] = val\n }\n }\n return normalized\n }\n\n return value\n}\n\nexport class ScimPatchError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'ScimPatchError'\n }\n}\n"],
|
|
5
|
+
"mappings": "AAOA,SAAS,qBAAqB;AAQ9B,MAAM,gBAAgB,oBAAI,IAAI;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAEM,SAAS,yBAAyB,MAAqD;AAC5F,QAAM,aAAa,KAAK;AACxB,MAAI,CAAC,MAAM,QAAQ,UAAU,GAAG;AAC9B,UAAM,IAAI,eAAe,4CAA4C;AAAA,EACvE;AAEA,SAAO,WAAW,IAAI,CAAC,UAAU;AAC/B,UAAM,KAAK,OAAO,MAAM,MAAM,EAAE,EAAE,YAAY;AAC9C,QAAI,CAAC,CAAC,OAAO,WAAW,QAAQ,EAAE,SAAS,EAAE,GAAG;AAC9C,YAAM,IAAI,eAAe,6BAA6B,MAAM,EAAE,EAAE;AAAA,IAClE;AAEA,UAAM,OAAO,MAAM,OAAO,OAAO,MAAM,IAAI,IAAI;AAG/C,QAAI,MAAM;AACR,YAAM,iBAAiB,KAAK,YAAY;AACxC,UAAI,CAAC,cAAc,IAAI,cAAc,GAAG;AAEtC,eAAO,EAAE,IAAI,MAAM,OAAO,OAAU;AAAA,MACtC;AAAA,IACF;AAEA,UAAM,QAAQ,oBAAoB,MAAM,OAAO,IAAI;AAEnD,WAAO,EAAE,IAAI,MAAM,MAAM;AAAA,EAC3B,CAAC,EAAE,OAAO,CAAC,OAAO;AAEhB,QAAI,GAAG,OAAO,YAAY,GAAG,UAAU,UAAa,GAAG,KAAM,QAAO;AACpE,WAAO;AAAA,EACT,CAAC;AACH;AAEA,SAAS,oBAAoB,OAAgB,MAAwB;AACnE,MAAI,UAAU,UAAa,UAAU,KAAM,QAAO;AAGlD,MAAI,QAAQ,KAAK,YAAY,MAAM,UAAU;AAC3C,WAAO,cAAc,KAAK;AAAA,EAC5B;AAGA,MAAI,CAAC,QAAQ,OAAO,UAAU,YAAY,UAAU,MAAM;AACxD,UAAM,MAAM;AACZ,UAAM,aAAsC,CAAC;AAC7C,eAAW,CAAC,KAAK,GAAG,KAAK,OAAO,QAAQ,GAAG,GAAG;AAC5C,UAAI,IAAI,YAAY,MAAM,UAAU;AAClC,mBAAW,GAAG,IAAI,cAAc,GAAG;AAAA,MACrC,OAAO;AACL,mBAAW,GAAG,IAAI;AAAA,MACpB;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAEO,MAAM,uBAAuB,MAAM;AAAA,EACxC,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const SCIM_CONTENT_TYPE = "application/scim+json";
|
|
2
|
+
const SCIM_ERROR_SCHEMA = "urn:ietf:params:scim:api:messages:2.0:Error";
|
|
3
|
+
const SCIM_LIST_SCHEMA = "urn:ietf:params:scim:api:messages:2.0:ListResponse";
|
|
4
|
+
function scimJson(data, status = 200) {
|
|
5
|
+
return new Response(JSON.stringify(data), {
|
|
6
|
+
status,
|
|
7
|
+
headers: { "Content-Type": SCIM_CONTENT_TYPE }
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
function buildScimError(status, detail, scimType) {
|
|
11
|
+
const body = {
|
|
12
|
+
schemas: [SCIM_ERROR_SCHEMA],
|
|
13
|
+
status: String(status),
|
|
14
|
+
detail
|
|
15
|
+
};
|
|
16
|
+
if (scimType) body.scimType = scimType;
|
|
17
|
+
return body;
|
|
18
|
+
}
|
|
19
|
+
function buildListResponse(resources, totalResults, startIndex, itemsPerPage) {
|
|
20
|
+
return {
|
|
21
|
+
schemas: [SCIM_LIST_SCHEMA],
|
|
22
|
+
totalResults,
|
|
23
|
+
startIndex,
|
|
24
|
+
itemsPerPage,
|
|
25
|
+
Resources: resources
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export {
|
|
29
|
+
SCIM_CONTENT_TYPE,
|
|
30
|
+
buildListResponse,
|
|
31
|
+
buildScimError,
|
|
32
|
+
scimJson
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=scim-response.js.map
|