@cdmbase/wiki-browser 12.0.18-alpha.5
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/LICENSE +21 -0
- package/lib/components/Logo.d.ts +4 -0
- package/lib/components/Logo.d.ts.map +1 -0
- package/lib/components/Logo.js +16 -0
- package/lib/components/Logo.js.map +1 -0
- package/lib/components/help/SidebarSearch.d.ts +8 -0
- package/lib/components/help/SidebarSearch.d.ts.map +1 -0
- package/lib/components/help/SidebarSearch.js +111 -0
- package/lib/components/help/SidebarSearch.js.map +1 -0
- package/lib/components/help/index.d.ts +2 -0
- package/lib/components/help/index.d.ts.map +1 -0
- package/lib/components/landing/FeatureCard.d.ts +13 -0
- package/lib/components/landing/FeatureCard.d.ts.map +1 -0
- package/lib/components/landing/FeatureCard.js +85 -0
- package/lib/components/landing/FeatureCard.js.map +1 -0
- package/lib/components/landing/QuickLinkCard.d.ts +8 -0
- package/lib/components/landing/QuickLinkCard.d.ts.map +1 -0
- package/lib/components/landing/QuickLinkCard.js +26 -0
- package/lib/components/landing/QuickLinkCard.js.map +1 -0
- package/lib/components/landing/SearchInput.d.ts +10 -0
- package/lib/components/landing/SearchInput.d.ts.map +1 -0
- package/lib/components/landing/SearchInput.js +223 -0
- package/lib/components/landing/SearchInput.js.map +1 -0
- package/lib/components/landing/index.d.ts +4 -0
- package/lib/components/landing/index.d.ts.map +1 -0
- package/lib/components/welcome.d.ts +3 -0
- package/lib/components/welcome.d.ts.map +1 -0
- package/lib/compute.d.ts +4 -0
- package/lib/compute.d.ts.map +1 -0
- package/lib/compute.js +96 -0
- package/lib/compute.js.map +1 -0
- package/lib/config/env-config.d.ts +4 -0
- package/lib/config/env-config.d.ts.map +1 -0
- package/lib/config/env-config.js +7 -0
- package/lib/config/env-config.js.map +1 -0
- package/lib/docs.config.d.ts +48 -0
- package/lib/docs.config.d.ts.map +1 -0
- package/lib/index.d.ts +4 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +2 -0
- package/lib/index.js.map +1 -0
- package/lib/loaders/search.d.ts +1 -0
- package/lib/loaders/search.d.ts.map +1 -0
- package/lib/module.d.ts +4 -0
- package/lib/module.d.ts.map +1 -0
- package/lib/module.js +11 -0
- package/lib/module.js.map +1 -0
- package/lib/pages/ArticlePage/ArticlePage.d.ts +4 -0
- package/lib/pages/ArticlePage/ArticlePage.d.ts.map +1 -0
- package/lib/pages/ArticlePage/ArticlePage.js +222 -0
- package/lib/pages/ArticlePage/ArticlePage.js.map +1 -0
- package/lib/pages/ArticlePage/index.d.ts +3 -0
- package/lib/pages/ArticlePage/index.d.ts.map +1 -0
- package/lib/pages/ArticlePage/index.js +3 -0
- package/lib/pages/ArticlePage/index.js.map +1 -0
- package/lib/pages/CategoryCollection/CategoryCollection.d.ts +4 -0
- package/lib/pages/CategoryCollection/CategoryCollection.d.ts.map +1 -0
- package/lib/pages/CategoryCollection/CategoryCollection.js +103 -0
- package/lib/pages/CategoryCollection/CategoryCollection.js.map +1 -0
- package/lib/pages/CategoryCollection/index.d.ts +3 -0
- package/lib/pages/CategoryCollection/index.d.ts.map +1 -0
- package/lib/pages/CategoryCollection/index.js +3 -0
- package/lib/pages/CategoryCollection/index.js.map +1 -0
- package/lib/pages/Help/HelpIndex.d.ts +4 -0
- package/lib/pages/Help/HelpIndex.d.ts.map +1 -0
- package/lib/pages/Help/HelpIndex.js +44 -0
- package/lib/pages/Help/HelpIndex.js.map +1 -0
- package/lib/pages/Help/index.d.ts +4 -0
- package/lib/pages/Help/index.d.ts.map +1 -0
- package/lib/pages/Help/index.js +226 -0
- package/lib/pages/Help/index.js.map +1 -0
- package/lib/pages/Landing/index.d.ts +3 -0
- package/lib/pages/Landing/index.d.ts.map +1 -0
- package/lib/pages/Landing/index.js +281 -0
- package/lib/pages/Landing/index.js.map +1 -0
- package/lib/routes.json +2533 -0
- package/lib/seo.d.ts +22 -0
- package/lib/seo.d.ts.map +1 -0
- package/lib/slot-fill/FooterFill.d.ts +3 -0
- package/lib/slot-fill/FooterFill.d.ts.map +1 -0
- package/lib/slot-fill/FooterFill.js +18 -0
- package/lib/slot-fill/FooterFill.js.map +1 -0
- package/lib/slot-fill/LogoFill.d.ts +5 -0
- package/lib/slot-fill/LogoFill.d.ts.map +1 -0
- package/lib/slot-fill/LogoFill.js +74 -0
- package/lib/slot-fill/LogoFill.js.map +1 -0
- package/lib/slot-fill/consts.d.ts +5 -0
- package/lib/slot-fill/consts.d.ts.map +1 -0
- package/lib/slot-fill/consts.js +1 -0
- package/lib/slot-fill/consts.js.map +1 -0
- package/lib/slot-fill/index.d.ts +4 -0
- package/lib/slot-fill/index.d.ts.map +1 -0
- package/lib/templates/assets/images/add-link-frontend.png +0 -0
- package/lib/templates/assets/images/add-package-backend.png +0 -0
- package/lib/templates/assets/images/add-to-backend-module.png +0 -0
- package/lib/templates/assets/images/add-upload-client-frontend.png +0 -0
- package/lib/templates/assets/images/additional-parameters.png +0 -0
- package/lib/templates/assets/images/aeh-implementation.png +0 -0
- package/lib/templates/assets/images/aeh-usage.png +0 -0
- package/lib/templates/assets/images/apollo-client/recommendation_cache_mgmt.png +0 -0
- package/lib/templates/assets/images/app-deploy-new-version/jenkins1.PNG +0 -0
- package/lib/templates/assets/images/app-deploy-new-version/jenkins2.PNG +0 -0
- package/lib/templates/assets/images/auth-wrapper-code.png +0 -0
- package/lib/templates/assets/images/cdebase.png +0 -0
- package/lib/templates/assets/images/cdm-locales-directory.png +0 -0
- package/lib/templates/assets/images/client-settings.png +0 -0
- package/lib/templates/assets/images/codegen_file_update.png +0 -0
- package/lib/templates/assets/images/configuration.png +0 -0
- package/lib/templates/assets/images/copy-plugin.png +0 -0
- package/lib/templates/assets/images/docusaurus.png +0 -0
- package/lib/templates/assets/images/error-link.png +0 -0
- package/lib/templates/assets/images/error-sample.png +0 -0
- package/lib/templates/assets/images/extension copy.png +0 -0
- package/lib/templates/assets/images/extension.png +0 -0
- package/lib/templates/assets/images/graphql/graphql-folder-backend.png +0 -0
- package/lib/templates/assets/images/graphql/graphql-folder-with-gql.png +0 -0
- package/lib/templates/assets/images/i18n-config.png +0 -0
- package/lib/templates/assets/images/image.png +0 -0
- package/lib/templates/assets/images/logo.svg +10 -0
- package/lib/templates/assets/images/logo1.svg +1 -0
- package/lib/templates/assets/images/modify-upload-false-server.png +0 -0
- package/lib/templates/assets/images/navigation-auth-enabled.png +0 -0
- package/lib/templates/assets/images/org-dashboard-navigation.png +0 -0
- package/lib/templates/assets/images/org-navigation.png +0 -0
- package/lib/templates/assets/images/preferences_graphql_type.png +0 -0
- package/lib/templates/assets/images/provider.png +0 -0
- package/lib/templates/assets/images/route-config.png +0 -0
- package/lib/templates/assets/images/service-accounts.png +0 -0
- package/lib/templates/assets/images/source-code/source-code-environments.png +0 -0
- package/lib/templates/assets/images/source-code/source-code-organization.png +0 -0
- package/lib/templates/assets/images/spin-clone-develop-deployment/jenkins-changes.png +0 -0
- package/lib/templates/assets/images/spin-clone-develop-deployment/lerna-changes.png +0 -0
- package/lib/templates/assets/images/spin-clone-develop-deployment/root-package-json-changes.png +0 -0
- package/lib/templates/assets/images/spin-clone-develop-deployment/values-dev-changes.png +0 -0
- package/lib/templates/assets/images/sso-mappers.png +0 -0
- package/lib/templates/assets/images/sso-picture-mapper.png +0 -0
- package/lib/templates/assets/images/sso-settings.png +0 -0
- package/lib/templates/assets/images/timesheet_apollo_cache.png +0 -0
- package/lib/templates/assets/images/timesheet_query.png +0 -0
- package/lib/templates/assets/images/tutorial/docsVersionDropdown.png +0 -0
- package/lib/templates/assets/images/tutorial/localeDropdown.png +0 -0
- package/lib/templates/assets/images/unauthenticated.png +0 -0
- package/lib/templates/assets/images/undraw_docusaurus_mountain.svg +170 -0
- package/lib/templates/assets/images/undraw_docusaurus_react.svg +169 -0
- package/lib/templates/assets/images/undraw_docusaurus_tree.svg +1 -0
- package/lib/templates/assets/images/vite-plugin-config.png +0 -0
- package/lib/templates/content/docs/Generators/Project/generate-fullproject.md +12 -0
- package/lib/templates/content/docs/LLM/Logger.llm.md +194 -0
- package/lib/templates/content/docs/LLM/backend-proxies-services-llm.md +2687 -0
- package/lib/templates/content/docs/LLM/backend-service-llm.md +3384 -0
- package/lib/templates/content/docs/LLM/db_migration_llm.md +954 -0
- package/lib/templates/content/docs/LLM/frontend/REMIX-15.3-upgrade-llm.md +1245 -0
- package/lib/templates/content/docs/LLM/inngest/INNGEST_FUNCTION_DEVELOPMENT_GUIDE_LLM.md +1241 -0
- package/lib/templates/content/docs/LLM/inngest/INNGEST_NAMESPACE_LLM.md +384 -0
- package/lib/templates/content/docs/LLM/llm_workflow_namespace.md +384 -0
- package/lib/templates/content/docs/LLM/organization-components-form-llm.md +1395 -0
- package/lib/templates/content/docs/LLM/page-component-llm.md +173 -0
- package/lib/templates/content/docs/LLM/preferences-settings-llm.md +2781 -0
- package/lib/templates/content/docs/LLM/tailwind-css-llm.md +502 -0
- package/lib/templates/content/docs/UI/SchemaBasedUI.md +334 -0
- package/lib/templates/content/docs/UI/SlotFillComponent.md +334 -0
- package/lib/templates/content/docs/adminide-modules/account/auth0-login.md +31 -0
- package/lib/templates/content/docs/adminide-modules/account/index.md +14 -0
- package/lib/templates/content/docs/adminide-modules/account/keycloak-remix-setup.md +86 -0
- package/lib/templates/content/docs/adminide-modules/account/remix-auth-setup.md +79 -0
- package/lib/templates/content/docs/adminide-modules/account/various-auth-qatest.md +157 -0
- package/lib/templates/content/docs/adminide-modules/api-builders/graphql.md +906 -0
- package/lib/templates/content/docs/adminide-modules/billing/payments/index.md +14 -0
- package/lib/templates/content/docs/adminide-modules/billing/payments/stripe/index.md +14 -0
- package/lib/templates/content/docs/adminide-modules/billing/payments/stripe/settingup-stripe-locally.md +25 -0
- package/lib/templates/content/docs/adminide-modules/billing/tier-config.md +293 -0
- package/lib/templates/content/docs/adminide-modules/connectors/Connector.md +207 -0
- package/lib/templates/content/docs/adminide-modules/file-upload/index.md +16 -0
- package/lib/templates/content/docs/adminide-modules/file-upload/setup.md +435 -0
- package/lib/templates/content/docs/adminide-modules/file-upload/upload-file-using-signed-url.md +161 -0
- package/lib/templates/content/docs/adminide-modules/preferences/AddAdditionalPermissions.md +151 -0
- package/lib/templates/content/docs/adminide-modules/preferences/Configuration.md +241 -0
- package/lib/templates/content/docs/adminide-modules/preferences/Policy-Configuration.md +61 -0
- package/lib/templates/content/docs/adminide-modules/preferences/UI-components/ResourceSettingsLoader.md +319 -0
- package/lib/templates/content/docs/adminide-modules/preferences/contribute_scope_target.md +280 -0
- package/lib/templates/content/docs/adminide-modules/preferences/generate-urii.md +94 -0
- package/lib/templates/content/docs/adminide-modules/preferences/index.md +28 -0
- package/lib/templates/content/docs/adminide-modules/preferences/machine-configuration.md +157 -0
- package/lib/templates/content/docs/adminide-modules/preferences/pageSettings/generateCdecodeUri.md +1289 -0
- package/lib/templates/content/docs/adminide-modules/preferences/pageSettings/migratingFromUseSettings.md +215 -0
- package/lib/templates/content/docs/adminide-modules/preferences/permissions/Roles-Permissions.md +72 -0
- package/lib/templates/content/docs/adminide-modules/preferences/permissions/settingUserPermissions.md +139 -0
- package/lib/templates/content/docs/adminide-modules/preferences/preference-dependency.md +138 -0
- package/lib/templates/content/docs/adminide-modules/preferences/route-based-configuration.md +41 -0
- package/lib/templates/content/docs/adminide-modules/preferences/schema-configuration.md +71 -0
- package/lib/templates/content/docs/adminide-modules/preferences/supported.md +24 -0
- package/lib/templates/content/docs/adminide-modules/preferences/useSettingsLoader.md +248 -0
- package/lib/templates/content/docs/adminide-modules/project-tools/auth-providers.md +1317 -0
- package/lib/templates/content/docs/adminide-modules/project-tools/keycloak-guide.md +543 -0
- package/lib/templates/content/docs/adminide-modules/project-tools/tenant-management/tenant-based-authentication.md +846 -0
- package/lib/templates/content/docs/adminide-modules/project-tools/tenant-management/tenant-management.md +708 -0
- package/lib/templates/content/docs/adminide-modules/project-tools/tenant-management/tenants.md +1117 -0
- package/lib/templates/content/docs/chrome-extension/index.md +14 -0
- package/lib/templates/content/docs/chrome-extension/setup.md +30 -0
- package/lib/templates/content/docs/contributing/adding-package.md +23 -0
- package/lib/templates/content/docs/contributing/adding_new_modules.md +99 -0
- package/lib/templates/content/docs/contributing/architecture-updates.md +19 -0
- package/lib/templates/content/docs/contributing/avoid-using-promises-ui.md +116 -0
- package/lib/templates/content/docs/contributing/coding-guidelines.md +111 -0
- package/lib/templates/content/docs/contributing/do-and-dont.md +42 -0
- package/lib/templates/content/docs/contributing/faq.md +22 -0
- package/lib/templates/content/docs/contributing/folder-setup/browser.md +12 -0
- package/lib/templates/content/docs/contributing/folder-setup/config.md +12 -0
- package/lib/templates/content/docs/contributing/folder-setup/containers-server.md +12 -0
- package/lib/templates/content/docs/contributing/folder-setup/core.md +12 -0
- package/lib/templates/content/docs/contributing/folder-setup/graphql.md +12 -0
- package/lib/templates/content/docs/contributing/folder-setup/index.md +30 -0
- package/lib/templates/content/docs/contributing/folder-setup/module.md +12 -0
- package/lib/templates/content/docs/contributing/folder-setup/server.md +12 -0
- package/lib/templates/content/docs/contributing/folder-setup/services.md +12 -0
- package/lib/templates/content/docs/contributing/folder-setup/store.md +12 -0
- package/lib/templates/content/docs/contributing/frontend-coding.md +30 -0
- package/lib/templates/content/docs/contributing/git-subtree-sharing.md +73 -0
- package/lib/templates/content/docs/contributing/graphql-subscriptions.md +69 -0
- package/lib/templates/content/docs/contributing/how-to-contribute.md +30 -0
- package/lib/templates/content/docs/contributing/how_to_check_pure_esm.md +29 -0
- package/lib/templates/content/docs/contributing/index.md +60 -0
- package/lib/templates/content/docs/contributing/installation-issues.md +23 -0
- package/lib/templates/content/docs/contributing/keyboard-shortcut.md +131 -0
- package/lib/templates/content/docs/contributing/language/locale-support.md +12 -0
- package/lib/templates/content/docs/contributing/lerna-build-tools.md +516 -0
- package/lib/templates/content/docs/contributing/lerna-yarn-workspaces.md +95 -0
- package/lib/templates/content/docs/contributing/lint-and-formatter.md +20 -0
- package/lib/templates/content/docs/contributing/mobile-setup.md +16 -0
- package/lib/templates/content/docs/contributing/project-setup.md +233 -0
- package/lib/templates/content/docs/contributing/react/index.md +14 -0
- package/lib/templates/content/docs/contributing/react/lazy-component.md +70 -0
- package/lib/templates/content/docs/contributing/run-various-options.md +124 -0
- package/lib/templates/content/docs/contributing/schema-first-graphql-types.md +37 -0
- package/lib/templates/content/docs/contributing/source-code-organization.md +57 -0
- package/lib/templates/content/docs/contributing/staging-docker.md +88 -0
- package/lib/templates/content/docs/contributing/third-party/apollo-client-v3-tutorials.md +28 -0
- package/lib/templates/content/docs/contributing/third-party/index.md +18 -0
- package/lib/templates/content/docs/contributing/typescript-contribution.md +16 -0
- package/lib/templates/content/docs/devops/app-deploy-new-version.md +30 -0
- package/lib/templates/content/docs/devops/index.md +14 -0
- package/lib/templates/content/docs/devops/mobile-jenkins-build.md +40 -0
- package/lib/templates/content/docs/devops/versioning-the-project.md +128 -0
- package/lib/templates/content/docs/error-handler/application-error-handler.md +40 -0
- package/lib/templates/content/docs/error-handler/error-handling.md +26 -0
- package/lib/templates/content/docs/error-handler/index.md +16 -0
- package/lib/templates/content/docs/error-handler/logging-errors.md +14 -0
- package/lib/templates/content/docs/feature-api/copy-operation.md +427 -0
- package/lib/templates/content/docs/feature-api/feature-browser/assets.md +46 -0
- package/lib/templates/content/docs/feature-api/feature-browser/auth-permissions.md +12 -0
- package/lib/templates/content/docs/feature-api/feature-browser/feature.md +131 -0
- package/lib/templates/content/docs/feature-api/feature-browser/index.md +22 -0
- package/lib/templates/content/docs/feature-api/feature-browser/routes-menu.md +110 -0
- package/lib/templates/content/docs/feature-api/feature-browser/routing-convention.md +124 -0
- package/lib/templates/content/docs/feature-api/feature-browser/routing.md +338 -0
- package/lib/templates/content/docs/feature-api/feature-mobile/auth-permissions.md +20 -0
- package/lib/templates/content/docs/feature-api/feature-mobile/feature.md +130 -0
- package/lib/templates/content/docs/feature-api/feature-mobile/index.md +18 -0
- package/lib/templates/content/docs/feature-api/feature-mobile/navigation.md +187 -0
- package/lib/templates/content/docs/feature-api/feature-server/Scheduling.md +44 -0
- package/lib/templates/content/docs/feature-api/feature-server/dataloader.md +320 -0
- package/lib/templates/content/docs/feature-api/feature-server/dependency-injection.md +81 -0
- package/lib/templates/content/docs/feature-api/feature-server/feature.md +65 -0
- package/lib/templates/content/docs/feature-api/feature-server/generic-dataloader.md +135 -0
- package/lib/templates/content/docs/feature-api/feature-server/index.md +40 -0
- package/lib/templates/content/docs/feature-api/feature-server/migration.md +127 -0
- package/lib/templates/content/docs/feature-api/feature-server/mongo-model.md +72 -0
- package/lib/templates/content/docs/feature-api/feature-server/permissions.md +12 -0
- package/lib/templates/content/docs/feature-api/feature-server/policies.md +57 -0
- package/lib/templates/content/docs/feature-api/feature-server/preferences.md +57 -0
- package/lib/templates/content/docs/feature-api/feature-server/repositories.md +114 -0
- package/lib/templates/content/docs/feature-api/feature-server/resolvers.md +126 -0
- package/lib/templates/content/docs/feature-api/feature-server/rules.md +132 -0
- package/lib/templates/content/docs/feature-api/feature-server/schema.md +12 -0
- package/lib/templates/content/docs/feature-api/feature-server/services.md +102 -0
- package/lib/templates/content/docs/feature-api/feature-server/setup-resource-crud.md +359 -0
- package/lib/templates/content/docs/feature-api/index.md +18 -0
- package/lib/templates/content/docs/graphql/apolloClient-mutation.md +94 -0
- package/lib/templates/content/docs/graphql/index.md +14 -0
- package/lib/templates/content/docs/graphql/scalars.md +15 -0
- package/lib/templates/content/docs/help/index.md +14 -0
- package/lib/templates/content/docs/help/intro.md +16 -0
- package/lib/templates/content/docs/intl/ant-design-menu-translation.md +74 -0
- package/lib/templates/content/docs/intl/intl-namespace.md +129 -0
- package/lib/templates/content/docs/intl/vite-plugin-intl.md +87 -0
- package/lib/templates/content/docs/intl/webpack-plugin-intl.md +12 -0
- package/lib/templates/content/docs/intro.md +18 -0
- package/lib/templates/content/docs/knowledge/basic-fullstack.md +238 -0
- package/lib/templates/content/docs/mailing/index.md +14 -0
- package/lib/templates/content/docs/mailing/mailing-template.md +148 -0
- package/lib/templates/content/docs/mobile/App-navigation-generator.md +410 -0
- package/lib/templates/content/docs/mobile/MobileTestCases.md +264 -0
- package/lib/templates/content/docs/mobile/eas-profile-build.md +107 -0
- package/lib/templates/content/docs/mobile/expo-push-notification-setup.md +216 -0
- package/lib/templates/content/docs/mobile/index.md +14 -0
- package/lib/templates/content/docs/mobile/routes.md +83 -0
- package/lib/templates/content/docs/organization/adding-account-context.md +116 -0
- package/lib/templates/content/docs/organization/adding-org-mobile-navigation.md +22 -0
- package/lib/templates/content/docs/organization/adding-org-web-navigation.md +12 -0
- package/lib/templates/content/docs/organization/index.md +20 -0
- package/lib/templates/content/docs/organization/initialization.md +20 -0
- package/lib/templates/content/docs/organization/organization-resource-vs-resource.md +112 -0
- package/lib/templates/content/docs/remix/configuration/component-structure-best-practices.md +152 -0
- package/lib/templates/content/docs/remix/configuration/configurations.md +218 -0
- package/lib/templates/content/docs/remix/configuration/css-import-and-stylesheets.md +142 -0
- package/lib/templates/content/docs/remix/configuration/dont-subcomponent-network.md +166 -0
- package/lib/templates/content/docs/remix/configuration/generated-data-loaders.md +122 -0
- package/lib/templates/content/docs/remix/configuration/generated-resource-loaders.md +257 -0
- package/lib/templates/content/docs/remix/configuration/query-params-generator.md +216 -0
- package/lib/templates/content/docs/remix/configuration/routes-extra-icons.md +103 -0
- package/lib/templates/content/docs/remix/configuration/routes-json-advanced.md +86 -0
- package/lib/templates/content/docs/remix/configuration/routes-json-auth.md +113 -0
- package/lib/templates/content/docs/remix/configuration/routes-json-best-practices.md +55 -0
- package/lib/templates/content/docs/remix/configuration/routes-json-fields.md +79 -0
- package/lib/templates/content/docs/remix/configuration/routes-json-graphql.md +79 -0
- package/lib/templates/content/docs/remix/configuration/routes-json-index.md +112 -0
- package/lib/templates/content/docs/remix/configuration/routes-json-loaders.md +165 -0
- package/lib/templates/content/docs/remix/configuration/routes-json-middleware.md +196 -0
- package/lib/templates/content/docs/remix/configuration/routes-json-overview.md +53 -0
- package/lib/templates/content/docs/remix/data-loaders.md +43 -0
- package/lib/templates/content/docs/remix/devtools/remix-devtools.md +58 -0
- package/lib/templates/content/docs/remix/examples/changes-using-servercode.md +79 -0
- package/lib/templates/content/docs/remix/extra-icons.md +62 -0
- package/lib/templates/content/docs/remix/extra-links.md +65 -0
- package/lib/templates/content/docs/remix/generated-data-loaders.md +114 -0
- package/lib/templates/content/docs/remix/queryParamsGenerator.md +89 -0
- package/lib/templates/content/docs/remix/resources.md +16 -0
- package/lib/templates/content/docs/remix/styles.md +132 -0
- package/lib/templates/content/docs/remix/wiki.md +12 -0
- package/lib/templates/content/docs/security/auth-wrapper/auth-wrapper.md +24 -0
- package/lib/templates/content/docs/security/index.md +18 -0
- package/lib/templates/content/docs/security/secure-button-mobilenative.md +88 -0
- package/lib/templates/content/docs/security/secure-button-web.md +89 -0
- package/lib/templates/content/docs/server-side/account-customization.md +82 -0
- package/lib/templates/content/docs/server-side/apollo/caching.md +164 -0
- package/lib/templates/content/docs/server-side/backend-architecture/FINAL-DECISION.md +209 -0
- package/lib/templates/content/docs/server-side/backend-architecture/TRUE-FINAL-ARCHITECTURE.md +603 -0
- package/lib/templates/content/docs/server-side/backend-architecture/index1.md +0 -0
- package/lib/templates/content/docs/server-side/backend-coding.md +839 -0
- package/lib/templates/content/docs/server-side/e2b/manageing-template.md +197 -0
- package/lib/templates/content/docs/server-side/index.md +14 -0
- package/lib/templates/content/docs/server-side/inngest-functions-module.md +309 -0
- package/lib/templates/content/docs/server-side/listen-stripe-events.md +43 -0
- package/lib/templates/content/docs/server-side/slug-service.md +323 -0
- package/lib/templates/content/docs/tests/index.md +18 -0
- package/lib/templates/content/docs/tests/jest-test-debug-vscode.md +40 -0
- package/lib/templates/content/docs/tests/known-errors.md +116 -0
- package/lib/templates/content/docs/tests/service-test-template.md +118 -0
- package/lib/templates/content/docs/tests/test-setup.md +93 -0
- package/lib/templates/content/docs/xstate.md +23 -0
- package/lib/types.d.ts +37 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/utils/docsNavigation.d.ts +9 -0
- package/lib/utils/docsNavigation.d.ts.map +1 -0
- package/lib/utils/docsNavigation.js +37 -0
- package/lib/utils/docsNavigation.js.map +1 -0
- package/lib/utils/helpCenterUtils.d.ts +26 -0
- package/lib/utils/helpCenterUtils.d.ts.map +1 -0
- package/lib/utils/index.d.ts +3 -0
- package/lib/utils/index.d.ts.map +1 -0
- package/lib/utils/index.js +3 -0
- package/lib/utils/index.js.map +1 -0
- package/lib/utils/markdownLoader.d.ts +36 -0
- package/lib/utils/markdownLoader.d.ts.map +1 -0
- package/lib/utils/markdownLoader.js +2242 -0
- package/lib/utils/markdownLoader.js.map +1 -0
- package/package.json +71 -0
|
@@ -0,0 +1,846 @@
|
|
|
1
|
+
@@ -0,0 +1,860 @@
|
|
2
|
+
|
|
3
|
+
# Tenant Authentication System - Technical Guide
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This document provides a **technical deep-dive** into how tenant authentication works in the system, specifically focusing on the `cachedTenantInfoMiddleware`.
|
|
8
|
+
|
|
9
|
+
**Location:** `packages-modules/user-auth0/server/src/modules/user-tenants/middleware/cached-tenant-info.middleware.ts`
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Table of Contents
|
|
14
|
+
|
|
15
|
+
1. [Authentication Flow](#authentication-flow)
|
|
16
|
+
2. [Middleware Implementation](#middleware-implementation)
|
|
17
|
+
3. [Keycloak Validation](#keycloak-validation)
|
|
18
|
+
4. [Request Headers](#request-headers)
|
|
19
|
+
5. [Error Handling](#error-handling)
|
|
20
|
+
6. [Security Considerations](#security-considerations)
|
|
21
|
+
7. [Integration Guide](#integration-guide)
|
|
22
|
+
8. [Testing & Debugging](#testing--debugging)
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Authentication Flow
|
|
27
|
+
|
|
28
|
+
### High-Level Flow Diagram
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
32
|
+
│ CLIENT APPLICATION │
|
|
33
|
+
└───────────────────────────────┬─────────────────────────────────────┘
|
|
34
|
+
│
|
|
35
|
+
│ 1. HTTP Request with Headers:
|
|
36
|
+
│ x-tenant-id: {tenantId}
|
|
37
|
+
│ x-tenant-key: base64(clientId:secret)
|
|
38
|
+
│
|
|
39
|
+
▼
|
|
40
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
41
|
+
│ CACHED TENANT INFO MIDDLEWARE │
|
|
42
|
+
│ │
|
|
43
|
+
│ ┌──────────────────────────────────────────────────────────────┐ │
|
|
44
|
+
│ │ STEP 1: Extract Headers │ │
|
|
45
|
+
│ │ - Read x-tenant-id │ │
|
|
46
|
+
│ │ - Read x-tenant-key │ │
|
|
47
|
+
│ └──────────────────────────────────────────────────────────────┘ │
|
|
48
|
+
│ │ │
|
|
49
|
+
│ ▼ │
|
|
50
|
+
│ ┌──────────────────────────────────────────────────────────────┐ │
|
|
51
|
+
│ │ STEP 2: Lookup Tenant in MongoDB │ │
|
|
52
|
+
│ │ - Connect to database │ │
|
|
53
|
+
│ │ - Find tenant by MongoDB _id (tenantId from header) │ │
|
|
54
|
+
│ │ - Retrieve tenant data with clientConfigurations │ │
|
|
55
|
+
│ └──────────────────────────────────────────────────────────────┘ │
|
|
56
|
+
│ │ │
|
|
57
|
+
│ ▼ │
|
|
58
|
+
│ ┌──────────────────────────────────────────────────────────────┐ │
|
|
59
|
+
│ │ STEP 3: Decode Credentials │ │
|
|
60
|
+
│ │ - Base64 decode x-tenant-key │ │
|
|
61
|
+
│ │ - Split into clientId:clientSecret │ │
|
|
62
|
+
│ │ - Validate format │ │
|
|
63
|
+
│ └──────────────────────────────────────────────────────────────┘ │
|
|
64
|
+
│ │ │
|
|
65
|
+
│ ▼ │
|
|
66
|
+
│ ┌──────────────────────────────────────────────────────────────┐ │
|
|
67
|
+
│ │ STEP 4: Match Client Configuration │ │
|
|
68
|
+
│ │ - Find clientConfig with matching clientId │ │
|
|
69
|
+
│ │ - Ensure client belongs to this tenant │ │
|
|
70
|
+
│ └──────────────────────────────────────────────────────────────┘ │
|
|
71
|
+
│ │ │
|
|
72
|
+
│ ▼ │
|
|
73
|
+
│ ┌──────────────────────────────────────────────────────────────┐ │
|
|
74
|
+
│ │ STEP 5: Validate with Keycloak │ │
|
|
75
|
+
│ │ - Call keycloakAdminService.verifyClientCredentials() │ │
|
|
76
|
+
│ │ - Verify clientId and clientSecret are valid │ │
|
|
77
|
+
│ │ - Check client is enabled │ │
|
|
78
|
+
│ └──────────────────────────────────────────────────────────────┘ │
|
|
79
|
+
│ │ │
|
|
80
|
+
└────────────────────────────────┼────────────────────────────────────┘
|
|
81
|
+
│
|
|
82
|
+
┌────────────┴─────────────┐
|
|
83
|
+
│ │
|
|
84
|
+
▼ ▼
|
|
85
|
+
┌──────────────────┐ ┌──────────────────────┐
|
|
86
|
+
│ ✅ SUCCESS │ │ ❌ FAILURE │
|
|
87
|
+
│ │ │ │
|
|
88
|
+
│ - Set req.tenant │ │ - Return 401 Error │
|
|
89
|
+
│ - Call next() │ │ - Block request │
|
|
90
|
+
└──────────────────┘ └──────────────────────┘
|
|
91
|
+
│
|
|
92
|
+
▼
|
|
93
|
+
┌──────────────────────┐
|
|
94
|
+
│ APPLICATION │
|
|
95
|
+
│ ROUTE HANDLER │
|
|
96
|
+
│ │
|
|
97
|
+
│ - Access req.tenant │
|
|
98
|
+
│ - Process request │
|
|
99
|
+
└──────────────────────┘
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Middleware Implementation
|
|
105
|
+
|
|
106
|
+
### Full Source Code with Annotations
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
/* packages-modules/user-auth0/server/src/modules/user-tenants/middleware/cached-tenant-info.middleware.ts */
|
|
110
|
+
|
|
111
|
+
import type { CustomRequest } from '@adminide-stack/auth0-server-core';
|
|
112
|
+
import { NextFunction, Response } from 'express';
|
|
113
|
+
import { IClientConfigurations, ITenantModel } from 'common/server';
|
|
114
|
+
import { keycloakAdminService } from '@adminide-stack/auth0-server-core';
|
|
115
|
+
import { model, connect } from 'mongoose';
|
|
116
|
+
import { config } from '../../../config';
|
|
117
|
+
import { TenantSchema } from '../store';
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Validate tenant credentials against Keycloak
|
|
121
|
+
*
|
|
122
|
+
* @param encodedCredentials - Base64 encoded "clientId:secret"
|
|
123
|
+
* @param tenantData - Tenant document from MongoDB
|
|
124
|
+
* @returns true if valid, false otherwise
|
|
125
|
+
*/
|
|
126
|
+
const validateTenantWithKeycloak = async (encodedCredentials: string, tenantData: ITenantModel): Promise<boolean> => {
|
|
127
|
+
try {
|
|
128
|
+
// 1. Decode Base64 credentials
|
|
129
|
+
const decodedCredentials = Buffer.from(encodedCredentials, 'base64').toString('utf-8');
|
|
130
|
+
|
|
131
|
+
// 2. Split into clientId and clientSecret
|
|
132
|
+
const [clientId, clientSecret] = decodedCredentials.split(':');
|
|
133
|
+
|
|
134
|
+
// 3. Validate credential format
|
|
135
|
+
if (!clientId || !clientSecret) {
|
|
136
|
+
return false; // Invalid format
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// 4. Ensure tenant exists
|
|
140
|
+
if (!tenantData) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// 5. Find matching client configuration in tenant
|
|
145
|
+
const clientConfig = (tenantData.clientConfigurations as unknown as IClientConfigurations[])?.find(
|
|
146
|
+
(config) => config.clientId === clientId,
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
if (!clientConfig) {
|
|
150
|
+
return false; // Client doesn't belong to this tenant
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 6. Verify credentials with Keycloak
|
|
154
|
+
const isValid = await keycloakAdminService.verifyClientCredentials(clientId, clientSecret);
|
|
155
|
+
|
|
156
|
+
return isValid.valid;
|
|
157
|
+
} catch (error) {
|
|
158
|
+
console.error('Error validating tenant with Keycloak:', error);
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Middleware: Authenticate tenant using header-based credentials
|
|
165
|
+
*
|
|
166
|
+
* Workflow:
|
|
167
|
+
* 1. Extract x-tenant-id and x-tenant-key headers
|
|
168
|
+
* 2. Lookup tenant in MongoDB
|
|
169
|
+
* 3. Validate credentials with Keycloak
|
|
170
|
+
* 4. Attach tenant to req.tenant if valid
|
|
171
|
+
* 5. Call next() or return 401
|
|
172
|
+
*/
|
|
173
|
+
export const cachedTenantInfoMiddleware = async (req: CustomRequest, res: Response, next: NextFunction) => {
|
|
174
|
+
try {
|
|
175
|
+
// ============================================
|
|
176
|
+
// STEP 1: Extract Request Headers
|
|
177
|
+
// ============================================
|
|
178
|
+
const tenantId = req.headers['x-tenant-id'] as string;
|
|
179
|
+
const tenantKey = req.headers['x-tenant-key'] as string;
|
|
180
|
+
|
|
181
|
+
// Only process if both headers are present
|
|
182
|
+
if (tenantId && tenantKey) {
|
|
183
|
+
// ============================================
|
|
184
|
+
// STEP 2: Connect to MongoDB
|
|
185
|
+
// ============================================
|
|
186
|
+
const mongoUrl = config.MONGO_URL;
|
|
187
|
+
await connect(mongoUrl);
|
|
188
|
+
|
|
189
|
+
// ============================================
|
|
190
|
+
// STEP 3: Lookup Tenant by ID
|
|
191
|
+
// ============================================
|
|
192
|
+
const TenantModel = model('UserTenant', TenantSchema);
|
|
193
|
+
const tenantData = (await TenantModel.findById(tenantId).exec()) as ITenantModel;
|
|
194
|
+
|
|
195
|
+
// ============================================
|
|
196
|
+
// STEP 4: Validate with Keycloak
|
|
197
|
+
// ============================================
|
|
198
|
+
const isValid = await validateTenantWithKeycloak(tenantKey, tenantData);
|
|
199
|
+
|
|
200
|
+
// ============================================
|
|
201
|
+
// STEP 5: Handle Validation Result
|
|
202
|
+
// ============================================
|
|
203
|
+
if (!isValid) {
|
|
204
|
+
// ❌ Invalid credentials
|
|
205
|
+
return res.status(401).json({
|
|
206
|
+
error: 'Unauthorized',
|
|
207
|
+
message: 'Invalid tenant credentials',
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ✅ Valid credentials - attach tenant to request
|
|
212
|
+
if (tenantData) {
|
|
213
|
+
req.tenant = tenantData as ITenantModel;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return next(); // Continue to route handler
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ============================================
|
|
220
|
+
// STEP 6: No Headers - Continue Without Auth
|
|
221
|
+
// ============================================
|
|
222
|
+
// If no tenant headers, skip validation
|
|
223
|
+
// (Other auth methods may handle this)
|
|
224
|
+
return next();
|
|
225
|
+
} catch (error) {
|
|
226
|
+
// ============================================
|
|
227
|
+
// STEP 7: Handle Errors
|
|
228
|
+
// ============================================
|
|
229
|
+
console.error('Error in cached tenant middleware:', error);
|
|
230
|
+
return res.status(500).json({
|
|
231
|
+
error: 'Internal Server Error',
|
|
232
|
+
message: 'Failed to process tenant information',
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Keycloak Validation
|
|
241
|
+
|
|
242
|
+
### `keycloakAdminService.verifyClientCredentials()`
|
|
243
|
+
|
|
244
|
+
This method validates client credentials against Keycloak.
|
|
245
|
+
|
|
246
|
+
**Implementation (Conceptual):**
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
class KeycloakAdminService {
|
|
250
|
+
async verifyClientCredentials(clientId: string, clientSecret: string): Promise<{ valid: boolean }> {
|
|
251
|
+
try {
|
|
252
|
+
// 1. Get Keycloak admin client
|
|
253
|
+
const adminClient = await this.getAdminClient();
|
|
254
|
+
|
|
255
|
+
// 2. Find client by clientId
|
|
256
|
+
const client = await adminClient.clients.find({
|
|
257
|
+
clientId: clientId,
|
|
258
|
+
realm: this.config.KEYCLOAK_REALM,
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
if (!client || client.length === 0) {
|
|
262
|
+
return { valid: false };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// 3. Get client secret from Keycloak
|
|
266
|
+
const secretData = await adminClient.clients.getClientSecret({
|
|
267
|
+
id: client[0].id,
|
|
268
|
+
realm: this.config.KEYCLOAK_REALM,
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// 4. Compare secrets
|
|
272
|
+
const isValid = secretData.value === clientSecret;
|
|
273
|
+
|
|
274
|
+
// 5. Check if client is enabled
|
|
275
|
+
const isEnabled = client[0].enabled === true;
|
|
276
|
+
|
|
277
|
+
return { valid: isValid && isEnabled };
|
|
278
|
+
} catch (error) {
|
|
279
|
+
console.error('Keycloak validation error:', error);
|
|
280
|
+
return { valid: false };
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
**What it checks:**
|
|
287
|
+
|
|
288
|
+
- ✅ Client exists in Keycloak
|
|
289
|
+
- ✅ ClientId matches
|
|
290
|
+
- ✅ ClientSecret matches
|
|
291
|
+
- ✅ Client is enabled (not disabled)
|
|
292
|
+
- ✅ Client is in correct realm
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
## Request Headers
|
|
297
|
+
|
|
298
|
+
### Required Headers
|
|
299
|
+
|
|
300
|
+
| Header Name | Type | Format | Description |
|
|
301
|
+
| -------------- | ------ | ------------------------------- | ---------------------------------------------------- |
|
|
302
|
+
| `x-tenant-id` | String | MongoDB ObjectId (24 hex chars) | Identifies the tenant (MongoDB `_id`) |
|
|
303
|
+
| `x-tenant-key` | String | Base64 encoded | Encoded credentials: `base64(clientId:clientSecret)` |
|
|
304
|
+
|
|
305
|
+
### Header Format Examples
|
|
306
|
+
|
|
307
|
+
**Valid `x-tenant-id`:**
|
|
308
|
+
|
|
309
|
+
```
|
|
310
|
+
64f3a1b2c9d8e7f6a5b4c3d2
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
**Valid `x-tenant-key`:**
|
|
314
|
+
|
|
315
|
+
```bash
|
|
316
|
+
# Original credentials
|
|
317
|
+
clientId: "abc123-def456-ghi789"
|
|
318
|
+
clientSecret: "mySecret123ABC"
|
|
319
|
+
|
|
320
|
+
# Encoded format
|
|
321
|
+
echo -n "abc123-def456-ghi789:mySecret123ABC" | base64
|
|
322
|
+
# Output: YWJjMTIzLWRlZjQ1Ni1naGk3ODk6bXlTZWNyZXQxMjNBQkM=
|
|
323
|
+
|
|
324
|
+
# Header value
|
|
325
|
+
x-tenant-key: YWJjMTIzLWRlZjQ1Ni1naGk3ODk6bXlTZWNyZXQxMjNBQkM=
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### Complete HTTP Request Example
|
|
329
|
+
|
|
330
|
+
```http
|
|
331
|
+
POST /graphql HTTP/1.1
|
|
332
|
+
Host: api.yourapp.com
|
|
333
|
+
Content-Type: application/json
|
|
334
|
+
x-tenant-id: 64f3a1b2c9d8e7f6a5b4c3d2
|
|
335
|
+
x-tenant-key: YWJjMTIzLWRlZjQ1Ni1naGk3ODk6bXlTZWNyZXQxMjNBQkM=
|
|
336
|
+
|
|
337
|
+
{
|
|
338
|
+
"query": "query { myData { id name } }"
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
## Error Handling
|
|
345
|
+
|
|
346
|
+
### Error Scenarios and Responses
|
|
347
|
+
|
|
348
|
+
#### 1. Missing Headers
|
|
349
|
+
|
|
350
|
+
**Condition:** No `x-tenant-id` or `x-tenant-key` provided
|
|
351
|
+
|
|
352
|
+
**Behavior:** Middleware passes through (no error)
|
|
353
|
+
|
|
354
|
+
**Reason:** Other authentication methods may handle the request
|
|
355
|
+
|
|
356
|
+
**Code Path:**
|
|
357
|
+
|
|
358
|
+
```typescript
|
|
359
|
+
if (tenantId && tenantKey) {
|
|
360
|
+
// Validation logic
|
|
361
|
+
} else {
|
|
362
|
+
return next(); // ← Pass through
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
#### 2. Invalid Tenant ID
|
|
367
|
+
|
|
368
|
+
**Condition:** Tenant not found in MongoDB
|
|
369
|
+
|
|
370
|
+
**Response:**
|
|
371
|
+
|
|
372
|
+
```json
|
|
373
|
+
{
|
|
374
|
+
"error": "Unauthorized",
|
|
375
|
+
"message": "Invalid tenant credentials"
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
**HTTP Status:** `401 Unauthorized`
|
|
380
|
+
|
|
381
|
+
**Reason:** `tenantData` is null/undefined
|
|
382
|
+
|
|
383
|
+
#### 3. Malformed Credentials
|
|
384
|
+
|
|
385
|
+
**Condition:** `x-tenant-key` not in `clientId:secret` format
|
|
386
|
+
|
|
387
|
+
**Response:**
|
|
388
|
+
|
|
389
|
+
```json
|
|
390
|
+
{
|
|
391
|
+
"error": "Unauthorized",
|
|
392
|
+
"message": "Invalid tenant credentials"
|
|
393
|
+
}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
**HTTP Status:** `401 Unauthorized`
|
|
397
|
+
|
|
398
|
+
**Triggered by:**
|
|
399
|
+
|
|
400
|
+
```typescript
|
|
401
|
+
const [clientId, clientSecret] = decodedCredentials.split(':');
|
|
402
|
+
if (!clientId || !clientSecret) {
|
|
403
|
+
return false; // ← Validation fails
|
|
404
|
+
}
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
#### 4. Client Not in Tenant
|
|
408
|
+
|
|
409
|
+
**Condition:** `clientId` doesn't match any client in tenant's `clientConfigurations`
|
|
410
|
+
|
|
411
|
+
**Response:**
|
|
412
|
+
|
|
413
|
+
```json
|
|
414
|
+
{
|
|
415
|
+
"error": "Unauthorized",
|
|
416
|
+
"message": "Invalid tenant credentials"
|
|
417
|
+
}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
**HTTP Status:** `401 Unauthorized`
|
|
421
|
+
|
|
422
|
+
**Reason:** Prevents using valid Keycloak client for wrong tenant
|
|
423
|
+
|
|
424
|
+
#### 5. Invalid Keycloak Credentials
|
|
425
|
+
|
|
426
|
+
**Condition:** Client exists but secret is wrong
|
|
427
|
+
|
|
428
|
+
**Response:**
|
|
429
|
+
|
|
430
|
+
```json
|
|
431
|
+
{
|
|
432
|
+
"error": "Unauthorized",
|
|
433
|
+
"message": "Invalid tenant credentials"
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
**HTTP Status:** `401 Unauthorized`
|
|
438
|
+
|
|
439
|
+
**Keycloak Check:**
|
|
440
|
+
|
|
441
|
+
```typescript
|
|
442
|
+
const isValid = await keycloakAdminService.verifyClientCredentials(clientId, clientSecret);
|
|
443
|
+
|
|
444
|
+
if (!isValid.valid) {
|
|
445
|
+
return false; // ← Validation fails
|
|
446
|
+
}
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
#### 6. System Error
|
|
450
|
+
|
|
451
|
+
**Condition:** Database connection fails, Keycloak unreachable, etc.
|
|
452
|
+
|
|
453
|
+
**Response:**
|
|
454
|
+
|
|
455
|
+
```json
|
|
456
|
+
{
|
|
457
|
+
"error": "Internal Server Error",
|
|
458
|
+
"message": "Failed to process tenant information"
|
|
459
|
+
}
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
**HTTP Status:** `500 Internal Server Error`
|
|
463
|
+
|
|
464
|
+
**Triggered by:** Exception in try/catch block
|
|
465
|
+
|
|
466
|
+
---
|
|
467
|
+
|
|
468
|
+
## Security Considerations
|
|
469
|
+
|
|
470
|
+
### 🔒 Security Features
|
|
471
|
+
|
|
472
|
+
1. **Multi-Layer Validation**
|
|
473
|
+
- Database lookup (tenant exists)
|
|
474
|
+
- Client ownership check (client belongs to tenant)
|
|
475
|
+
- Keycloak verification (credentials valid)
|
|
476
|
+
|
|
477
|
+
2. **No Secret Storage**
|
|
478
|
+
- Secrets not stored in MongoDB
|
|
479
|
+
- Always validated against Keycloak (source of truth)
|
|
480
|
+
- Regenerated deterministically when needed
|
|
481
|
+
|
|
482
|
+
3. **Base64 Encoding**
|
|
483
|
+
- Credentials encoded in transit
|
|
484
|
+
- Not encrypted (use HTTPS!)
|
|
485
|
+
- Prevents accidental logging
|
|
486
|
+
|
|
487
|
+
4. **Tenant Isolation**
|
|
488
|
+
- Client must belong to tenant's `clientConfigurations`
|
|
489
|
+
- Prevents cross-tenant credential reuse
|
|
490
|
+
|
|
491
|
+
### ⚠️ Security Best Practices
|
|
492
|
+
|
|
493
|
+
#### DO ✅
|
|
494
|
+
|
|
495
|
+
- Always use **HTTPS** in production
|
|
496
|
+
- Rotate client secrets regularly
|
|
497
|
+
- Log authentication failures for monitoring
|
|
498
|
+
- Rate-limit authentication attempts
|
|
499
|
+
- Use separate clients per service/environment
|
|
500
|
+
|
|
501
|
+
#### DON'T ❌
|
|
502
|
+
|
|
503
|
+
- Never expose credentials in frontend code
|
|
504
|
+
- Don't hardcode credentials in source code
|
|
505
|
+
- Don't share credentials between environments
|
|
506
|
+
- Don't disable HTTPS "for testing"
|
|
507
|
+
- Don't log decoded credentials
|
|
508
|
+
|
|
509
|
+
### 🛡️ Attack Mitigation
|
|
510
|
+
|
|
511
|
+
| Attack Type | Mitigation |
|
|
512
|
+
| ----------------------- | ---------------------------------------- |
|
|
513
|
+
| **Credential Stuffing** | Rate limiting, Keycloak lockout policies |
|
|
514
|
+
| **MITM** | HTTPS enforcement |
|
|
515
|
+
| **Replay Attacks** | Short-lived tokens (if implemented) |
|
|
516
|
+
| **Cross-Tenant Access** | Client ownership validation |
|
|
517
|
+
| **SQL Injection** | Mongoose parameterization |
|
|
518
|
+
| **Brute Force** | Keycloak client lockout |
|
|
519
|
+
|
|
520
|
+
---
|
|
521
|
+
|
|
522
|
+
## Integration Guide
|
|
523
|
+
|
|
524
|
+
### Adding Middleware to Express App
|
|
525
|
+
|
|
526
|
+
```typescript
|
|
527
|
+
import express from 'express';
|
|
528
|
+
import { cachedTenantInfoMiddleware } from './middleware/cached-tenant-info.middleware';
|
|
529
|
+
|
|
530
|
+
const app = express();
|
|
531
|
+
|
|
532
|
+
// Apply globally to all routes
|
|
533
|
+
app.use(cachedTenantInfoMiddleware);
|
|
534
|
+
|
|
535
|
+
// Apply to specific routes
|
|
536
|
+
app.use('/api/protected', cachedTenantInfoMiddleware, (req, res) => {
|
|
537
|
+
// Access tenant data
|
|
538
|
+
if (req.tenant) {
|
|
539
|
+
res.json({
|
|
540
|
+
message: `Welcome, ${req.tenant.name}`,
|
|
541
|
+
tenantId: req.tenant.tenantId,
|
|
542
|
+
});
|
|
543
|
+
} else {
|
|
544
|
+
res.status(401).json({ error: 'No tenant authenticated' });
|
|
545
|
+
}
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
app.listen(3000);
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
### Accessing Tenant in Route Handlers
|
|
552
|
+
|
|
553
|
+
```typescript
|
|
554
|
+
import { CustomRequest } from '@adminide-stack/auth0-server-core';
|
|
555
|
+
import { Response } from 'express';
|
|
556
|
+
|
|
557
|
+
app.get('/api/data', cachedTenantInfoMiddleware, async (req: CustomRequest, res: Response) => {
|
|
558
|
+
// Tenant is available in req.tenant
|
|
559
|
+
const tenant = req.tenant;
|
|
560
|
+
|
|
561
|
+
if (!tenant) {
|
|
562
|
+
return res.status(401).json({ error: 'Tenant required' });
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// Use tenant data
|
|
566
|
+
console.log(`Request from tenant: ${tenant.name}`);
|
|
567
|
+
console.log(`Tenant ID: ${tenant.tenantId}`);
|
|
568
|
+
console.log(`Organization: ${tenant.organization}`);
|
|
569
|
+
|
|
570
|
+
// Query data scoped to tenant
|
|
571
|
+
const data = await DataModel.find({
|
|
572
|
+
tenantId: tenant.tenantId,
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
res.json({ data });
|
|
576
|
+
});
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
### GraphQL Context Integration
|
|
580
|
+
|
|
581
|
+
```typescript
|
|
582
|
+
import { ApolloServer } from 'apollo-server-express';
|
|
583
|
+
import { CustomRequest } from '@adminide-stack/auth0-server-core';
|
|
584
|
+
|
|
585
|
+
const server = new ApolloServer({
|
|
586
|
+
typeDefs,
|
|
587
|
+
resolvers,
|
|
588
|
+
context: ({ req }: { req: CustomRequest }) => ({
|
|
589
|
+
// Tenant attached by middleware
|
|
590
|
+
tenant: req.tenant,
|
|
591
|
+
tenantId: req.tenant?.tenantId,
|
|
592
|
+
tenantName: req.tenant?.name,
|
|
593
|
+
}),
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
// In resolvers
|
|
597
|
+
const resolvers = {
|
|
598
|
+
Query: {
|
|
599
|
+
myData: async (_, __, { tenant }) => {
|
|
600
|
+
if (!tenant) {
|
|
601
|
+
throw new Error('Tenant authentication required');
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
return DataModel.find({
|
|
605
|
+
tenantId: tenant.tenantId,
|
|
606
|
+
});
|
|
607
|
+
},
|
|
608
|
+
},
|
|
609
|
+
};
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
---
|
|
613
|
+
|
|
614
|
+
## Testing & Debugging
|
|
615
|
+
|
|
616
|
+
### Manual Testing with cURL
|
|
617
|
+
|
|
618
|
+
```bash
|
|
619
|
+
#!/bin/bash
|
|
620
|
+
|
|
621
|
+
# Configuration
|
|
622
|
+
TENANT_ID="64f3a1b2c9d8e7f6a5b4c3d2"
|
|
623
|
+
CLIENT_ID="your-keycloak-client-id"
|
|
624
|
+
CLIENT_SECRET="your-client-secret"
|
|
625
|
+
|
|
626
|
+
# Encode credentials
|
|
627
|
+
TENANT_KEY=$(echo -n "${CLIENT_ID}:${CLIENT_SECRET}" | base64)
|
|
628
|
+
|
|
629
|
+
# Make request
|
|
630
|
+
curl -X POST https://api.yourapp.com/graphql \
|
|
631
|
+
-H "Content-Type: application/json" \
|
|
632
|
+
-H "x-tenant-id: ${TENANT_ID}" \
|
|
633
|
+
-H "x-tenant-key: ${TENANT_KEY}" \
|
|
634
|
+
-d '{
|
|
635
|
+
"query": "query { tenant(id: \"'${TENANT_ID}'\") { id name } }"
|
|
636
|
+
}' \
|
|
637
|
+
-v # Verbose output for debugging
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
### Automated Tests
|
|
641
|
+
|
|
642
|
+
```typescript
|
|
643
|
+
import request from 'supertest';
|
|
644
|
+
import app from '../app';
|
|
645
|
+
|
|
646
|
+
describe('Tenant Authentication Middleware', () => {
|
|
647
|
+
const validTenantId = '64f3a1b2c9d8e7f6a5b4c3d2';
|
|
648
|
+
const validClientId = 'test-client-id';
|
|
649
|
+
const validClientSecret = 'test-client-secret';
|
|
650
|
+
|
|
651
|
+
const encodeCredentials = (clientId: string, secret: string) => {
|
|
652
|
+
return Buffer.from(`${clientId}:${secret}`).toString('base64');
|
|
653
|
+
};
|
|
654
|
+
|
|
655
|
+
it('should authenticate valid tenant credentials', async () => {
|
|
656
|
+
const response = await request(app)
|
|
657
|
+
.post('/api/protected')
|
|
658
|
+
.set('x-tenant-id', validTenantId)
|
|
659
|
+
.set('x-tenant-key', encodeCredentials(validClientId, validClientSecret))
|
|
660
|
+
.expect(200);
|
|
661
|
+
|
|
662
|
+
expect(response.body.tenant).toBeDefined();
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
it('should reject invalid tenant ID', async () => {
|
|
666
|
+
await request(app)
|
|
667
|
+
.post('/api/protected')
|
|
668
|
+
.set('x-tenant-id', 'invalid-id')
|
|
669
|
+
.set('x-tenant-key', encodeCredentials(validClientId, validClientSecret))
|
|
670
|
+
.expect(401);
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
it('should reject invalid credentials', async () => {
|
|
674
|
+
await request(app)
|
|
675
|
+
.post('/api/protected')
|
|
676
|
+
.set('x-tenant-id', validTenantId)
|
|
677
|
+
.set('x-tenant-key', encodeCredentials(validClientId, 'wrong-secret'))
|
|
678
|
+
.expect(401);
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
it('should pass through when no headers provided', async () => {
|
|
682
|
+
await request(app).post('/api/public').expect(200); // Or whatever your public route returns
|
|
683
|
+
});
|
|
684
|
+
});
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
### Debugging Checklist
|
|
688
|
+
|
|
689
|
+
When authentication fails, check:
|
|
690
|
+
|
|
691
|
+
1. **Headers Present?**
|
|
692
|
+
|
|
693
|
+
```bash
|
|
694
|
+
# Check request headers
|
|
695
|
+
curl -v ... | grep "x-tenant"
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
2. **Valid Base64?**
|
|
699
|
+
|
|
700
|
+
```bash
|
|
701
|
+
# Decode and inspect
|
|
702
|
+
echo "YWJjOmRlZg==" | base64 -d
|
|
703
|
+
# Should output: "abc:def"
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
3. **Tenant Exists?**
|
|
707
|
+
|
|
708
|
+
```javascript
|
|
709
|
+
// Check MongoDB
|
|
710
|
+
db.UserTenant.findById('64f3a1b2c9d8e7f6a5b4c3d2');
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
4. **Client in Tenant?**
|
|
714
|
+
|
|
715
|
+
```javascript
|
|
716
|
+
// Check clientConfigurations
|
|
717
|
+
db.UserTenant.findOne({ _id: '64f3a1b2c9d8e7f6a5b4c3d2' }, { clientConfigurations: 1 });
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
5. **Keycloak Client Valid?**
|
|
721
|
+
|
|
722
|
+
```bash
|
|
723
|
+
# Check Keycloak admin console
|
|
724
|
+
# Realm > Clients > Search for clientId
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
6. **Keycloak Reachable?**
|
|
728
|
+
```bash
|
|
729
|
+
curl https://keycloak.yourapp.com/health
|
|
730
|
+
```
|
|
731
|
+
|
|
732
|
+
### Logging for Debugging
|
|
733
|
+
|
|
734
|
+
Add detailed logging to middleware:
|
|
735
|
+
|
|
736
|
+
```typescript
|
|
737
|
+
const validateTenantWithKeycloak = async (encodedCredentials: string, tenantData: ITenantModel): Promise<boolean> => {
|
|
738
|
+
try {
|
|
739
|
+
console.log('[AUTH] Starting validation');
|
|
740
|
+
|
|
741
|
+
const decodedCredentials = Buffer.from(encodedCredentials, 'base64').toString('utf-8');
|
|
742
|
+
|
|
743
|
+
console.log('[AUTH] Decoded credentials format:', decodedCredentials.includes(':') ? 'valid' : 'invalid');
|
|
744
|
+
|
|
745
|
+
const [clientId, clientSecret] = decodedCredentials.split(':');
|
|
746
|
+
console.log('[AUTH] ClientId:', clientId ? 'present' : 'missing');
|
|
747
|
+
console.log('[AUTH] ClientSecret:', clientSecret ? 'present' : 'missing');
|
|
748
|
+
|
|
749
|
+
if (!clientId || !clientSecret) {
|
|
750
|
+
console.log('[AUTH] Invalid credential format');
|
|
751
|
+
return false;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
const clientConfig = (tenantData.clientConfigurations as unknown as IClientConfigurations[])?.find(
|
|
755
|
+
(config) => config.clientId === clientId,
|
|
756
|
+
);
|
|
757
|
+
|
|
758
|
+
console.log('[AUTH] Client found in tenant:', !!clientConfig);
|
|
759
|
+
|
|
760
|
+
if (!clientConfig) {
|
|
761
|
+
console.log('[AUTH] Client not in tenant configurations');
|
|
762
|
+
return false;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
console.log('[AUTH] Verifying with Keycloak...');
|
|
766
|
+
const isValid = await keycloakAdminService.verifyClientCredentials(clientId, clientSecret);
|
|
767
|
+
|
|
768
|
+
console.log('[AUTH] Keycloak validation result:', isValid.valid);
|
|
769
|
+
|
|
770
|
+
return isValid.valid;
|
|
771
|
+
} catch (error) {
|
|
772
|
+
console.error('[AUTH] Validation error:', error);
|
|
773
|
+
return false;
|
|
774
|
+
}
|
|
775
|
+
};
|
|
776
|
+
```
|
|
777
|
+
|
|
778
|
+
---
|
|
779
|
+
|
|
780
|
+
## Performance Considerations
|
|
781
|
+
|
|
782
|
+
### Database Connection Pooling
|
|
783
|
+
|
|
784
|
+
**Current Implementation:**
|
|
785
|
+
|
|
786
|
+
```typescript
|
|
787
|
+
await connect(mongoUrl); // Creates new connection each time
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
**Recommended Optimization:**
|
|
791
|
+
|
|
792
|
+
```typescript
|
|
793
|
+
// Create connection pool at startup
|
|
794
|
+
let connection: Connection;
|
|
795
|
+
|
|
796
|
+
export const getConnection = async () => {
|
|
797
|
+
if (!connection) {
|
|
798
|
+
connection = await connect(mongoUrl, {
|
|
799
|
+
maxPoolSize: 10,
|
|
800
|
+
minPoolSize: 2,
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
return connection;
|
|
804
|
+
};
|
|
805
|
+
|
|
806
|
+
// Use in middleware
|
|
807
|
+
const connection = await getConnection();
|
|
808
|
+
const TenantModel = connection.model('UserTenant', TenantSchema);
|
|
809
|
+
```
|
|
810
|
+
|
|
811
|
+
### Caching Tenant Data
|
|
812
|
+
|
|
813
|
+
Consider adding Redis cache:
|
|
814
|
+
|
|
815
|
+
```typescript
|
|
816
|
+
import { Redis } from 'ioredis';
|
|
817
|
+
|
|
818
|
+
const redis = new Redis();
|
|
819
|
+
|
|
820
|
+
const getCachedTenant = async (tenantId: string): Promise<ITenantModel | null> => {
|
|
821
|
+
// Try cache first
|
|
822
|
+
const cached = await redis.get(`tenant:${tenantId}`);
|
|
823
|
+
if (cached) {
|
|
824
|
+
return JSON.parse(cached);
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// Fallback to database
|
|
828
|
+
const tenant = await TenantModel.findById(tenantId);
|
|
829
|
+
if (tenant) {
|
|
830
|
+
// Cache for 5 minutes
|
|
831
|
+
await redis.setex(`tenant:${tenantId}`, 300, JSON.stringify(tenant));
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
return tenant;
|
|
835
|
+
};
|
|
836
|
+
```
|
|
837
|
+
|
|
838
|
+
---
|
|
839
|
+
|
|
840
|
+
## Related Documentation
|
|
841
|
+
|
|
842
|
+
- [Complete Setup Guide](./TENANT_SETUP_GUIDE.md) - End-user documentation
|
|
843
|
+
- [Project & Vault Setup](./PROJECT_VAULT_SETUP.md) - Project configuration
|
|
844
|
+
- [Keycloak Documentation](https://www.keycloak.org/docs/latest/) - External reference
|
|
845
|
+
|
|
846
|
+
---
|