{"_id":"@clementblaise/catalog-backend-module-okta","_rev":"6-4ee5f00e6e4a222ae3c2ee8f06a77ded","time":{"created":"2024-04-15T10:02:38.693Z","0.10.0":"2024-04-15T09:12:56.538Z","modified":"2024-04-15T10:08:52.775Z","0.10.1":"2024-04-15T10:02:38.929Z","0.10.2":"2024-04-15T10:05:05.349Z","0.10.3":"2024-04-15T10:06:15.981Z","0.10.4":"2024-04-15T10:08:52.616Z"},"name":"@clementblaise/catalog-backend-module-okta","dist-tags":{"latest":"0.10.4"},"versions":{"0.10.1":{"name":"@clementblaise/catalog-backend-module-okta","description":"A set of Backstage catalog providers for Okta","version":"0.10.1","main":"src/index.ts","types":"src/index.ts","license":"Apache-2.0","private":false,"publishConfig":{"access":"public","main":"dist/index.cjs.js","types":"dist/index.d.ts"},"backstage":{"role":"backend-plugin-module"},"homepage":"https://roadie.io","repository":{"type":"git","url":"git+https://github.com/clementblaise/roadie-backstage-plugins.git","directory":"plugins/catalog-backend-module-okta"},"keywords":["backstage"],"scripts":{"build":"backstage-cli package build","lint":"backstage-cli package lint","test":"backstage-cli package test","prepack":"backstage-cli package prepack","postpack":"backstage-cli package postpack","clean":"backstage-cli package clean","start":"backstage-cli package start"},"dependencies":{"@backstage/backend-common":"^0.21.6","@backstage/backend-plugin-api":"^0.6.16","@backstage/backend-tasks":"^0.5.21","@backstage/catalog-client":"^1.6.3","@backstage/catalog-model":"^1.4.5","@backstage/config":"^1.2.0","@backstage/errors":"^1.2.4","@backstage/plugin-catalog-backend":"^1.21.0","@backstage/plugin-catalog-node":"^1.11.0","@backstage/types":"^1.1.1","@okta/okta-sdk-nodejs":"^6.6.0","crypto-js":"^4.1.1","lodash":"^4.17.21","p-limit":"^3.0.2","uuid":"^9.0.1","winston":"^3.2.1"},"devDependencies":{"@backstage/cli":"^0.26.2","@types/crypto-js":"^4.1.1","@types/lodash":"^4.14.151","yaml":"^2.2.2"},"configSchema":"config.d.ts","gitHead":"0ac02b95632510e6c2bb76c3c59be0bb98fcbbcc","bugs":{"url":"https://github.com/clementblaise/roadie-backstage-plugins/issues"},"_id":"@clementblaise/catalog-backend-module-okta@0.10.1","_nodeVersion":"18.16.0","_npmVersion":"9.5.1","dist":{"integrity":"sha512-FmsM/MjINshqzEMM9IDEQc7nO6UnGzopdILMV6TgDo6Eew2qWmYsTnuu+WBg7h7w/xjg+bhb146tZ0NK3NmR2A==","shasum":"f010731ea2bef5c822639020b18b6e48ae127d48","tarball":"https://registry.npmjs.org/@clementblaise/catalog-backend-module-okta/-/catalog-backend-module-okta-0.10.1.tgz","fileCount":6,"unpackedSize":125344,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCICKTIVe2OSaD6B9gcEp5r2Uun4cOKnZotbTjGIlct1NIAiEA1sRs8iiB5m07GwVQU5xMW9S4BQCtHd1zaQ24nVm6HCw="}]},"_npmUser":{"name":"clementblaise","email":"clementblaise@me.com"},"directories":{},"maintainers":[{"name":"clementblaise","email":"clementblaise@me.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/catalog-backend-module-okta_0.10.1_1713175358773_0.3450074187380938"},"_hasShrinkwrap":false},"0.10.2":{"name":"@clementblaise/catalog-backend-module-okta","description":"A set of Backstage catalog providers for Okta","version":"0.10.2","main":"src/index.ts","types":"src/index.ts","license":"Apache-2.0","private":false,"publishConfig":{"access":"public","main":"dist/index.cjs.js","types":"dist/index.d.ts"},"backstage":{"role":"backend-plugin-module"},"homepage":"https://roadie.io","repository":{"type":"git","url":"git+https://github.com/clementblaise/roadie-backstage-plugins.git","directory":"plugins/catalog-backend-module-okta"},"keywords":["backstage"],"scripts":{"build":"backstage-cli package build","lint":"backstage-cli package lint","test":"backstage-cli package test","prepack":"backstage-cli package prepack","postpack":"backstage-cli package postpack","clean":"backstage-cli package clean","start":"backstage-cli package start"},"dependencies":{"@backstage/backend-common":"^0.21.6","@backstage/backend-plugin-api":"^0.6.16","@backstage/backend-tasks":"^0.5.21","@backstage/catalog-client":"^1.6.3","@backstage/catalog-model":"^1.4.5","@backstage/config":"^1.2.0","@backstage/errors":"^1.2.4","@backstage/plugin-catalog-backend":"^1.21.0","@backstage/plugin-catalog-node":"^1.11.0","@backstage/types":"^1.1.1","@okta/okta-sdk-nodejs":"^6.6.0","crypto-js":"^4.1.1","lodash":"^4.17.21","p-limit":"^3.0.2","uuid":"^9.0.1","winston":"^3.2.1"},"devDependencies":{"@backstage/cli":"^0.26.2","@types/crypto-js":"^4.1.1","@types/lodash":"^4.14.151","yaml":"^2.2.2"},"typesVersions":{"*":{"alpha":["src/alpha.ts"],"package.json":["package.json"]}},"configSchema":"config.d.ts","gitHead":"0ac02b95632510e6c2bb76c3c59be0bb98fcbbcc","bugs":{"url":"https://github.com/clementblaise/roadie-backstage-plugins/issues"},"_id":"@clementblaise/catalog-backend-module-okta@0.10.2","_nodeVersion":"18.16.0","_npmVersion":"9.5.1","dist":{"integrity":"sha512-H5VuSoJp5UgXIk3LwhxijY6Nli/w8Br1RcnnmM3OWZaPQkQhL7mkDI1IzCBYyi6AMm+bWutcm/2oeIlK46VKYw==","shasum":"be65001175521e43408b3dd8786c72869d42370c","tarball":"https://registry.npmjs.org/@clementblaise/catalog-backend-module-okta/-/catalog-backend-module-okta-0.10.2.tgz","fileCount":6,"unpackedSize":125344,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCk/iCGbcYQqYhmtKc6zEfcLwiNuCWjIaPv3EW5kpQuyQIhAIHKYFiD5MKEkI4/16SOGwfJAc/JBS0zHd+WLWAisGfP"}]},"_npmUser":{"name":"clementblaise","email":"clementblaise@me.com"},"directories":{},"maintainers":[{"name":"clementblaise","email":"clementblaise@me.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/catalog-backend-module-okta_0.10.2_1713175505174_0.6336500181409479"},"_hasShrinkwrap":false},"0.10.3":{"name":"@clementblaise/catalog-backend-module-okta","description":"A set of Backstage catalog providers for Okta","version":"0.10.3","main":"src/index.ts","types":"src/index.ts","license":"Apache-2.0","exports":{".":"./src/index.ts","./alpha":"./src/alpha.ts","./package.json":"./package.json"},"private":false,"publishConfig":{"access":"public","main":"dist/index.cjs.js","types":"dist/index.d.ts"},"backstage":{"role":"backend-plugin-module"},"homepage":"https://roadie.io","repository":{"type":"git","url":"git+https://github.com/clementblaise/roadie-backstage-plugins.git","directory":"plugins/catalog-backend-module-okta"},"keywords":["backstage"],"scripts":{"build":"backstage-cli package build","lint":"backstage-cli package lint","test":"backstage-cli package test","prepack":"backstage-cli package prepack","postpack":"backstage-cli package postpack","clean":"backstage-cli package clean","start":"backstage-cli package start"},"dependencies":{"@backstage/backend-common":"^0.21.6","@backstage/backend-plugin-api":"^0.6.16","@backstage/backend-tasks":"^0.5.21","@backstage/catalog-client":"^1.6.3","@backstage/catalog-model":"^1.4.5","@backstage/config":"^1.2.0","@backstage/errors":"^1.2.4","@backstage/plugin-catalog-backend":"^1.21.0","@backstage/plugin-catalog-node":"^1.11.0","@backstage/types":"^1.1.1","@okta/okta-sdk-nodejs":"^6.6.0","crypto-js":"^4.1.1","lodash":"^4.17.21","p-limit":"^3.0.2","uuid":"^9.0.1","winston":"^3.2.1"},"devDependencies":{"@backstage/cli":"^0.26.2","@types/crypto-js":"^4.1.1","@types/lodash":"^4.14.151","yaml":"^2.2.2"},"typesVersions":{"*":{"alpha":["src/alpha.ts"],"package.json":["package.json"]}},"configSchema":"config.d.ts","gitHead":"0ac02b95632510e6c2bb76c3c59be0bb98fcbbcc","bugs":{"url":"https://github.com/clementblaise/roadie-backstage-plugins/issues"},"_id":"@clementblaise/catalog-backend-module-okta@0.10.3","_nodeVersion":"18.16.0","_npmVersion":"9.5.1","dist":{"integrity":"sha512-K47kP+Fb5fNFSsko8S3jxZ7DWnC/FZfVLqq5hanFoMrz8gEUT8revutzYU0vRtkRPL8mUBkjTfOzyM3HDGYSRg==","shasum":"27ad204e95940068d71964f315f1c27a32a62008","tarball":"https://registry.npmjs.org/@clementblaise/catalog-backend-module-okta/-/catalog-backend-module-okta-0.10.3.tgz","fileCount":7,"unpackedSize":125650,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCyfJXZ46L966gMDHa3qIFZy93TCyB1qLkxCqj4CWap8AIhAKpP11BpHVqyQ2/Bz+UAveUQSOfQczuCwRsxVOyPYHlG"}]},"_npmUser":{"name":"clementblaise","email":"clementblaise@me.com"},"directories":{},"maintainers":[{"name":"clementblaise","email":"clementblaise@me.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/catalog-backend-module-okta_0.10.3_1713175575830_0.8398021505871685"},"_hasShrinkwrap":false},"0.10.4":{"name":"@clementblaise/catalog-backend-module-okta","description":"A set of Backstage catalog providers for Okta","version":"0.10.4","main":"src/index.ts","types":"src/index.ts","license":"Apache-2.0","exports":{".":"./src/index.ts","./alpha":"./src/alpha.ts","./package.json":"./package.json"},"private":false,"publishConfig":{"access":"public","main":"dist/index.cjs.js","types":"dist/index.d.ts"},"backstage":{"role":"backend-plugin-module"},"homepage":"https://roadie.io","repository":{"type":"git","url":"git+https://github.com/clementblaise/roadie-backstage-plugins.git","directory":"plugins/catalog-backend-module-okta"},"keywords":["backstage"],"scripts":{"build":"backstage-cli package build","lint":"backstage-cli package lint","test":"backstage-cli package test","prepack":"backstage-cli package prepack","postpack":"backstage-cli package postpack","clean":"backstage-cli package clean","start":"backstage-cli package start"},"dependencies":{"@backstage/backend-common":"^0.21.6","@backstage/backend-plugin-api":"^0.6.16","@backstage/backend-tasks":"^0.5.21","@backstage/catalog-client":"^1.6.3","@backstage/catalog-model":"^1.4.5","@backstage/config":"^1.2.0","@backstage/errors":"^1.2.4","@backstage/plugin-catalog-backend":"^1.21.0","@backstage/plugin-catalog-node":"^1.11.0","@backstage/types":"^1.1.1","@okta/okta-sdk-nodejs":"^6.6.0","crypto-js":"^4.1.1","lodash":"^4.17.21","p-limit":"^3.0.2","uuid":"^9.0.1","winston":"^3.2.1"},"devDependencies":{"@backstage/cli":"^0.26.2","@types/crypto-js":"^4.1.1","@types/lodash":"^4.14.151","yaml":"^2.2.2"},"typesVersions":{"*":{"alpha":["src/alpha.ts"],"package.json":["package.json"]}},"configSchema":"config.d.ts","gitHead":"0ac02b95632510e6c2bb76c3c59be0bb98fcbbcc","bugs":{"url":"https://github.com/clementblaise/roadie-backstage-plugins/issues"},"_id":"@clementblaise/catalog-backend-module-okta@0.10.4","_nodeVersion":"18.16.0","_npmVersion":"9.5.1","dist":{"integrity":"sha512-Ak/cx3omMhGyONzo1VsPwW835y5YQCy2i2/71aKenpz67GnODjumJ8wlqc+7tQmXu9xa4UYsKEORYC9XtOAB3Q==","shasum":"e53eb9f1b563c09b8f2598f0e147302af66024a8","tarball":"https://registry.npmjs.org/@clementblaise/catalog-backend-module-okta/-/catalog-backend-module-okta-0.10.4.tgz","fileCount":13,"unpackedSize":138318,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIFT4Jpy8u/3UBKgtJukaRQQZVyc4vpxNMZ3Xq9UvbFQKAiEAsQJlwZ1DUNTl2qhYjn6X81EWqtNaWm0/nEkb0BbdF0k="}]},"_npmUser":{"name":"clementblaise","email":"clementblaise@me.com"},"directories":{},"maintainers":[{"name":"clementblaise","email":"clementblaise@me.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/catalog-backend-module-okta_0.10.4_1713175732429_0.7381533943753367"},"_hasShrinkwrap":false}},"maintainers":[{"name":"clementblaise","email":"clementblaise@me.com"}],"description":"A set of Backstage catalog providers for Okta","homepage":"https://roadie.io","keywords":["backstage"],"repository":{"type":"git","url":"git+https://github.com/clementblaise/roadie-backstage-plugins.git","directory":"plugins/catalog-backend-module-okta"},"bugs":{"url":"https://github.com/clementblaise/roadie-backstage-plugins/issues"},"license":"Apache-2.0","readme":"# Catalog Backend Module for Okta\n\nThis is an extension module to the plugin-catalog-backend plugin, providing\nentity providers to read Okta Group and User objects as Backstage Entities.\n\nTo setup the Okta providers you will need an [Okta API Token](https://developer.okta.com/docs/guides/create-an-api-token/main/)\n\n## App Config\n\nYou will need to configure your okta credentials in the `app-config.yaml`.\n\n### Basic Config via an API Token\n\n```yaml\ncatalog:\n  providers:\n    okta:\n      orgUrl: 'https://tenant.okta.com'\n      token: ${OKTA_TOKEN}\n```\n\n### Multiple Configs\n\nYou can define multiple config\n\n```yaml\ncatalog:\n  providers:\n    okta:\n      org-one:\n        orgUrl: 'https://tenant-one.okta.com'\n        token: ${OKTA_TOKEN}\n      org-two:\n        orgUrl: 'https://tenant-two.okta.com'\n        token: ${OKTA_TOKEN}\n```\n\n### OAuth 2.0 Scoped Authentication\n\n[Create an OAuth app](https://developer.okta.com/docs/guides/implement-oauth-for-okta/main/#create-an-oauth-2-0-app-in-okta) in Okta. You will need to grant it with the `okta.groups.read` and `okta.users.read` scopes as a bare minimum. In the following example the `oauth.privateKey` may be passed as either a string encoded PEM or stringified JWK.\n\n```yaml\ncatalog:\n  providers:\n    okta:\n      orgUrl: 'https://tenant.okta.com'\n      oauth:\n        clientId: ${OKTA_OAUTH_CLIENT_ID},\n        keyId: ${OKTA_OAUTH_KEY_ID},\n        privateKey: ${OKTA_OAUTH_PRIVATE_KEY},\n```\n\nNote: `keyId` is optional but _must_ be passed wen using a PEM as the `privateKey`\n\n### Filter Users and Groups\n\nThe provider allows configuring Okta search filtering for users and groups. See here for more details on what is possible: https://developer.okta.com/docs/reference/core-okta-api/#filter\n\n```yaml\ncatalog:\n  providers:\n    okta:\n      orgUrl: 'https://tenant.okta.com'\n      token: ${OKTA_TOKEN}\n      userFilter: profile.department eq \"engineering\"\n      groupFilter: profile.name eq \"Everyone\"\n```\n\nThere are two ways that you can configure the Entity providers. You can either use the `OktaOrgEntityProvider` which loads both users and groups. Or you can load user or groups separately user the `OktaUserEntityProvider` and `OktaGroupEntityProvider` providers.\n\n## Load Users and Groups Together\n\n### OktaOrgEntityProvider\n\nYou can configure the provider with different naming strategies. The configured strategy will be used to generate the discovered entity's `metadata.name` field. The currently supported strategies are the following:\n\nUser naming stategies:\n\n- id (default) | User entities will be named by the user id.\n- kebab-case-email | User entities will be named by their profile email converted to kebab case.\n- strip-domain-email | User entities will be named by their profile email without the domain part.\n\nYou may also choose to implement a custom naming strategy by providing a function.\n\n```typescript jsx\nexport const customUserNamingStrategy: UserNamingStrategy = user =>\n  user.profile.customField;\n```\n\nGroup naming strategies:\n\n- id (default) | Group entities will be named by the group id.\n- kebab-case-name | Group entities will be named by their group profile name converted to kebab case.\n- profile-name | Group entities will be named exactly as their group profile name. ⚠ The Okta field supports characters not supported as [entity names in backstage](https://backstage.io/docs/features/software-catalog/descriptor-format#name-required). ⚠\n\nYou may also choose to implement a custom naming strategy by providing a function.\n\n```typescript jsx\nexport const customGroupNamingStrategy: GroupNamingStrategy = group =>\n  group.profile.customField;\n```\n\n### Example configuration:\n\n```typescript\nimport { OktaOrgEntityProvider } from '@roadiehq/catalog-backend-module-okta';\n\nexport default async function createPlugin(\n  env: PluginEnvironment,\n): Promise<Router> {\n  const builder = await CatalogBuilder.create(env);\n\n  const orgProvider = OktaOrgEntityProvider.fromConfig(env.config, {\n    logger: env.logger,\n    userNamingStrategy: 'strip-domain-email',\n    groupNamingStrategy: 'kebab-case-name',\n  })[0];\n\n  builder.addEntityProvider(orgProvider);\n\n  const { processingEngine, router } = await builder.build();\n\n  orgProvider.run();\n\n  await processingEngine.start();\n\n  // ...\n\n  return router;\n}\n```\n\nYou can optionally provide the ability to create a hierarchy of groups by providing `hierarchyConfig`.\n\n```typescript\nimport { OktaOrgEntityProvider } from '@roadiehq/catalog-backend-module-okta';\n\nexport default async function createPlugin(\n  env: PluginEnvironment,\n): Promise<Router> {\n  const builder = await CatalogBuilder.create(env);\n\n  const orgProvider = OktaOrgEntityProvider.fromConfig(env.config, {\n    logger: env.logger,\n    userNamingStrategy: 'strip-domain-email',\n    groupNamingStrategy: 'kebab-case-name',\n    hierarchyConfig: {\n      key: 'profile.orgId',\n      parentKey: 'profile.parentOrgId',\n    },\n  })[0];\n\n  builder.addEntityProvider(orgProvider);\n\n  const { processingEngine, router } = await builder.build();\n\n  orgProvider.run();\n\n  await processingEngine.start();\n\n  // ...\n\n  return router;\n}\n```\n\nIn case you want to customize the emitted entities, the provider allows to pass custom transformers for users and groups by providing `userTransformer` and `groupTransformer`.\n\n1. Create a transformer:\n\n```typescript\nimport { GroupNamingStrategy } from '@roadiehq/catalog-backend-module-okta';\nimport { GroupEntity } from '@backstage/catalog-model';\nimport { Group } from '@okta/okta-sdk-nodejs';\n\nfunction myGroupTransformer(\n  group: Group,\n  namingStrategy: GroupNamingStrategy,\n  parentGroup: Group | undefined,\n  options: {\n    annotations: Record<string, string>;\n    members: string[];\n  },\n): GroupEntity {\n  // Enrich it with your logic\n  const groupEntity: GroupEntity = {\n    kind: 'Group',\n    apiVersion: 'backstage.io/v1alpha1',\n    metadata: {\n      annotations: {\n        ...options.annotations,\n      },\n      name: namingStrategy(group),\n      title: group.profile.name,\n      title: group.profile.description || group.profile.name,\n      description: group.profile.description || '',\n    },\n    spec: {\n      members: options.members,\n      type: 'group',\n      children: [],\n    },\n  };\n\n  if (parentGroup) {\n    groupEntity.spec.parent = namingStrategy(parentGroup);\n  }\n  return groupEntity;\n}\n```\n\n2. Configure the provider with the transformer:\n\n```typescript\nimport { OktaOrgEntityProvider } from '@roadiehq/catalog-backend-module-okta';\nimport { myGroupTransformer } from './myGroupTransformer';\n\nexport default async function createPlugin(\n  env: PluginEnvironment,\n): Promise<Router> {\n  const builder = await CatalogBuilder.create(env);\n\n  const orgProvider = OktaOrgEntityProvider.fromConfig(env.config, {\n    logger: env.logger,\n    userNamingStrategy: 'strip-domain-email',\n    groupNamingStrategy: 'kebab-case-name',\n    groupTransformer: myGroupTransformer,\n  })[0];\n\n  builder.addEntityProvider(orgProvider);\n\n  const { processingEngine, router } = await builder.build();\n\n  orgProvider.run();\n\n  await processingEngine.start();\n\n  // ...\n\n  return router;\n}\n```\n\n## Load Users and Groups Separately\n\n### OktaUserEntityProvider\n\nYou can configure the provider with different naming strategies. The configured strategy will be used to generate the discovered entity's `metadata.name` field. The currently supported strategies are the following:\n\n- id (default) | User entities will be named by the user id.\n- kebab-case-email | User entities will be named by their profile email converted to kebab case.\n- strip-domain-email | User entities will be named by their profile email without the domain part.\n\nYou may also choose to implement a custom naming strategy by providing a function.\n\n```typescript jsx\nexport const customUserNamingStrategy: UserNamingStrategy = user =>\n  user.profile.customField;\n```\n\nIn case you want to customize the emitted entities, the provider allows to pass custom transformer by providing `userTransformer`.\n\n1. Create a transformer:\n\n```typescript\nimport { UserEntity } from '@backstage/catalog-model';\nimport { User } from '@okta/okta-sdk-nodejs';\nimport { UserNamingStrategy } from '@roadiehq/catalog-backend-module-okta';\n\nfunction myUserTransformer(\n  user: User,\n  namingStrategy: UserNamingStrategy,\n  options: { annotations: Record<string, string> },\n): UserEntity {\n  // Enrich it with your logic\n  return {\n    kind: 'User',\n    apiVersion: 'backstage.io/v1alpha1',\n    metadata: {\n      annotations: { ...options.annotations },\n      name: namingStrategy(user),\n      title: user.profile.email,\n    },\n    spec: {\n      profile: {\n        displayName: user.profile.displayName,\n        email: user.profile.email,\n      },\n      memberOf: [],\n    },\n  };\n}\n```\n\n2. Configure the provider with the transformer:\n\n```typescript\nimport { OktaUserEntityProvider } from '@roadiehq/catalog-backend-module-okta';\nimport { myUserTransformer } from './myUserTransformer';\n\nexport default async function createPlugin(\n  env: PluginEnvironment,\n): Promise<Router> {\n  const builder = await CatalogBuilder.create(env);\n\n  const userProvider = OktaUserEntityProvider.fromConfig(env.config, {\n    logger: env.logger,\n    namingStrategy: 'strip-domain-email',\n    userTransformer: myUserTransformer,\n  })[0];\n\n  builder.addEntityProvider(userProvider);\n\n  const { processingEngine, router } = await builder.build();\n\n  userProvider.run();\n\n  await processingEngine.start();\n\n  // ...\n\n  return router;\n}\n```\n\n### OktaGroupEntityProvider\n\nYou can configure the provider with different naming strategies. The configured strategy will be used to generate the discovered entities `metadata.name` field. The currently supported strategies are the following:\n\nUser naming stategies:\n\n- id (default) | User entities will be named by the user id.\n- kebab-case-email | User entities will be named by their profile email converted to kebab case.\n- strip-domain-email | User entities will be named by their profile email without the domain part.\n\nYou may also choose to implement a custom naming strategy by providing a function.\n\n```typescript jsx\nexport const customUserNamingStrategy: UserNamingStrategy = user =>\n  user.profile.customField;\n```\n\nGroup naming strategies:\n\n- id (default) | Group entities will be named by the group id.\n- kebab-case-name | Group entities will be named by their group profile name converted to kebab case.\n- profile-name | Group entities will be named exactly as their group profile name. ⚠ The Okta field supports characters not supported as [entity names in backstage](https://backstage.io/docs/features/software-catalog/descriptor-format#name-required). ⚠\n\nYou may also choose to implement a custom naming strategy by providing a function.\n\n```typescript jsx\nexport const customGroupNamingStrategy: GroupNamingStrategy = group =>\n  group.profile.customField;\n```\n\nMake sure you use the OktaUserEntityProvider's naming strategy for the OktaGroupEntityProvider's user naming strategy.\n\n### Example configuration:\n\n```typescript\nimport {\n  OktaUserEntityProvider,\n  OktaGroupEntityProvider,\n} from '@roadiehq/catalog-backend-module-okta';\n\nexport default async function createPlugin(\n  env: PluginEnvironment,\n): Promise<Router> {\n  const builder = await CatalogBuilder.create(env);\n\n  const oktaConfig =\n    env.config.getOptionalConfigArray('catalog.providers.okta') || [];\n  const userProvider = OktaUserEntityProvider.fromConfig(oktaConfig[0], {\n    logger: env.logger,\n    namingStrategy: 'strip-domain-email',\n  });\n  const groupProvider = OktaGroupEntityProvider.fromConfig(oktaConfig[0], {\n    logger: env.logger,\n    userNamingStrategy: 'strip-domain-email',\n    groupNamingStrategy: 'kebab-case-name',\n  });\n\n  builder.addEntityProvider(userProvider);\n  builder.addEntityProvider(groupProvider);\n\n  const { processingEngine, router } = await builder.build();\n\n  userProvider.run();\n  groupProvider.run();\n\n  await processingEngine.start();\n\n  // ...\n\n  return router;\n}\n```\n\nYou can optionally provide the ability to create a hierarchy of groups by providing the `hierarchyConfig`.\n\n```typescript\nimport {\n  OktaUserEntityProvider,\n  OktaGroupEntityProvider,\n} from '@roadiehq/catalog-backend-module-okta';\n\nexport default async function createPlugin(\n  env: PluginEnvironment,\n): Promise<Router> {\n  const builder = await CatalogBuilder.create(env);\n\n  const oktaConfig = env.config.getOptionalConfigArray(\n    'catalog.providers.okta',\n  );\n  const userProvider = OktaUserEntityProvider.fromConfig(oktaConfig[0], {\n    logger: env.logger,\n    namingStrategy: 'strip-domain-email',\n  });\n  const groupProvider = OktaGroupEntityProvider.fromConfig(oktaConfig[0], {\n    logger: env.logger,\n    userNamingStrategy: 'strip-domain-email',\n    groupNamingStrategy: 'kebab-case-name',\n    hierarchyConfig: {\n      key: 'profile.orgId',\n      parentKey: 'profile.parentOrgId',\n    },\n  });\n\n  builder.addEntityProvider(userProvider);\n  builder.addEntityProvider(groupProvider);\n\n  const { processingEngine, router } = await builder.build();\n\n  userProvider.run();\n  groupProvider.run();\n\n  await processingEngine.start();\n\n  // ...\n\n  return router;\n}\n```\n\nIn case you want to customize the emitted entities, the provider allows to pass custom transformer by providing `groupTransformer`.\n\n1. Create a transformer:\n\n```typescript\nimport { GroupNamingStrategy } from '@roadiehq/catalog-backend-module-okta';\nimport { GroupEntity } from '@backstage/catalog-model';\nimport { Group } from '@okta/okta-sdk-nodejs';\n\nfunction myGroupTransformer(\n  group: Group,\n  namingStrategy: GroupNamingStrategy,\n  parentGroup: Group | undefined,\n  options: {\n    annotations: Record<string, string>;\n    members: string[];\n  },\n): GroupEntity {\n  // Enrich it with your logic\n  const groupEntity: GroupEntity = {\n    kind: 'Group',\n    apiVersion: 'backstage.io/v1alpha1',\n    metadata: {\n      annotations: {\n        ...options.annotations,\n      },\n      name: namingStrategy(group),\n      title: group.profile.name,\n      title: group.profile.description || group.profile.name,\n      description: group.profile.description || '',\n    },\n    spec: {\n      members: options.members,\n      type: 'group',\n      children: [],\n    },\n  };\n\n  if (parentGroup) {\n    groupEntity.spec.parent = namingStrategy(parentGroup);\n  }\n  return groupEntity;\n}\n```\n\n2. Configure the provider with the transformer:\n\n```typescript\nimport { OktaGroupEntityProvider } from '@roadiehq/catalog-backend-module-okta';\nimport { myGroupTransformer } from './myGroupTransformer';\n\nexport default async function createPlugin(\n  env: PluginEnvironment,\n): Promise<Router> {\n  const builder = await CatalogBuilder.create(env);\n\n  const groupProvider = OktaGroupEntityProvider.fromConfig(env.config, {\n    logger: env.logger,\n    userNamingStrategy: 'strip-domain-email',\n    groupNamingStrategy: 'kebab-case-name',\n    groupTransformer: myGroupTransformer,\n  });\n\n  builder.addEntityProvider(groupProvider);\n\n  const { processingEngine, router } = await builder.build();\n\n  groupProvider.run();\n\n  await processingEngine.start();\n\n  // ...\n\n  return router;\n}\n```\n","readmeFilename":"README.md"}