├── .dockerignore
├── .env.example
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── config.yml
│ └── feature_request.md
├── dependabot.yml
├── pull_request_template.md
└── workflows
│ └── main.yml
├── .gitignore
├── .prettierignore
├── .prettierrc.js
├── .release-it.json
├── .vscode
├── extensions.json
└── settings.json
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE
├── Procfile
├── README.md
├── SECURITY.md
├── _dev
├── docker-compose-cockroachdb.yml
├── docker-compose.yml
└── saml_config
│ ├── boxyhq.js
│ └── boxyhq.xml
├── app.json
├── check-locale.js
├── components
├── LicenseRequired.tsx
├── Navbar.tsx
├── PoweredBy.tsx
├── Sidebar.tsx
├── Toaster.tsx
├── connection
│ ├── ConnectionList.tsx
│ ├── CreateConnection.tsx
│ └── EditConnection.tsx
├── dsync
│ ├── CreateDirectory.tsx
│ ├── DirectoryList.tsx
│ └── EditDirectory.tsx
├── layouts
│ ├── AccountLayout.tsx
│ ├── SetupLinkLayout.tsx
│ └── index.ts
├── logo
│ ├── AuditLogs.tsx
│ ├── DSync.tsx
│ └── SSO.tsx
├── retraced
│ ├── AddProject.tsx
│ ├── CodeSnippet.tsx
│ ├── LogsViewer.tsx
│ └── ProjectDetails.tsx
├── setup-link-instructions
│ ├── CreateSSOConnection.tsx
│ ├── Footer.tsx
│ ├── NextButton.tsx
│ ├── PreviousButton.tsx
│ ├── SelectIdentityProviders.tsx
│ ├── auth0
│ │ ├── 1.mdx
│ │ ├── 2.mdx
│ │ └── 3.mdx
│ ├── azure
│ │ ├── 1.mdx
│ │ ├── 2.mdx
│ │ ├── 3.mdx
│ │ └── 4.mdx
│ ├── generic-oidc
│ │ ├── 1.mdx
│ │ └── 2.mdx
│ ├── generic-saml
│ │ ├── 1.mdx
│ │ ├── 2.mdx
│ │ └── 3.mdx
│ ├── google
│ │ ├── 1.mdx
│ │ ├── 2.mdx
│ │ ├── 3.mdx
│ │ └── 4.mdx
│ ├── jumpcloud
│ │ ├── 1.mdx
│ │ ├── 2.mdx
│ │ ├── 3.mdx
│ │ └── 4.mdx
│ ├── microsoft-adfs
│ │ ├── 1.mdx
│ │ ├── 2.mdx
│ │ ├── 3.mdx
│ │ └── 4.mdx
│ ├── okta
│ │ ├── 1.mdx
│ │ ├── 2.mdx
│ │ ├── 3.mdx
│ │ └── 4.mdx
│ ├── onelogin
│ │ ├── 1.mdx
│ │ ├── 2.mdx
│ │ ├── 3.mdx
│ │ └── 4.mdx
│ ├── pingone
│ │ ├── 1.mdx
│ │ ├── 2.mdx
│ │ ├── 3.mdx
│ │ └── 4.mdx
│ └── rippling
│ │ ├── 1.mdx
│ │ ├── 2.mdx
│ │ └── 3.mdx
├── setup-link
│ ├── InvalidSetupLinkAlert.tsx
│ └── StepProgressBar.tsx
└── styles.ts
├── docker-compose.yml
├── e2e
├── .gitignore
├── api
│ ├── helpers
│ │ ├── api.ts
│ │ ├── directories.ts
│ │ ├── groups.ts
│ │ ├── index.ts
│ │ ├── oauth.ts
│ │ ├── sso.ts
│ │ ├── users.ts
│ │ └── utils.ts
│ ├── scim
│ │ └── v2.0
│ │ │ ├── groups.spec.ts
│ │ │ └── users.spec.ts
│ └── v1
│ │ ├── directory-sync
│ │ ├── directories.spec.ts
│ │ ├── events.spec.ts
│ │ ├── groups-members.spec.ts
│ │ ├── groups.spec.ts
│ │ ├── setup-links.spec.ts
│ │ └── users.spec.ts
│ │ ├── identity-federation
│ │ └── app.spec.ts
│ │ └── sso
│ │ ├── jwks.spec.ts
│ │ ├── oidc-discovery.spec.ts
│ │ ├── oidc.spec.ts
│ │ ├── saml.spec.ts
│ │ ├── setup-links.spec.ts
│ │ └── sso-traces.spec.ts
├── support
│ ├── data
│ │ └── dsync
│ │ │ ├── azure.ts
│ │ │ ├── index.ts
│ │ │ └── okta.ts
│ ├── fixtures
│ │ ├── dsync-page.ts
│ │ ├── identity-federation.ts
│ │ ├── index.ts
│ │ ├── portal.ts
│ │ ├── setuplink-ds-page.ts
│ │ ├── setuplink-page.ts
│ │ └── sso-page.ts
│ ├── globalSetup.ts
│ ├── nextAuth.constants.ts
│ └── pretest.ts
└── ui
│ ├── Directory Sync
│ ├── connections.spec.ts
│ └── setup_link_ds.spec.ts
│ ├── Enterprise SSO
│ ├── error.spec.ts
│ ├── oidc.spec.ts
│ ├── oidc_saml.spec.ts
│ ├── saml.spec.ts
│ └── setup_link.spec.ts
│ ├── Identity Federation
│ ├── error.spec.ts
│ ├── oidc.spec.ts
│ └── saml.spec.ts
│ └── settings
│ └── sso.spec.ts
├── ee
├── ENTERPRISE.md
├── LICENSE
├── branding
│ ├── api
│ │ ├── admin
│ │ │ └── index.ts
│ │ └── index.ts
│ ├── pages
│ │ └── index.tsx
│ └── utils.ts
├── identity-federation
│ ├── api
│ │ ├── admin
│ │ │ ├── [id]
│ │ │ │ └── index.ts
│ │ │ └── index.ts
│ │ ├── metadata.ts
│ │ ├── oidc
│ │ │ └── idp-login
│ │ │ │ └── [fedAppId].ts
│ │ ├── sso.ts
│ │ └── v1
│ │ │ ├── index.ts
│ │ │ └── product.ts
│ └── pages
│ │ ├── edit.tsx
│ │ ├── index.tsx
│ │ ├── metadata.tsx
│ │ └── new.tsx
└── product
│ └── api
│ ├── [productId].ts
│ └── index.ts
├── eslint.config.cjs
├── find-dupe-locale.js
├── helm
├── jackson
│ ├── .helmignore
│ ├── Chart.yaml
│ ├── templates
│ │ ├── _helpers.tpl
│ │ └── dep-helm.yaml
│ └── values.yaml
└── source
│ └── dep-helm.yaml
├── i18next.d.ts
├── internal-ui
├── .gitignore
├── LICENSE
├── README.md
├── dev
│ └── verdaccio.sh
├── package-lock.json
├── package.json
├── postcss.config.js
├── src
│ ├── branding
│ │ ├── BrandingForm.tsx
│ │ └── index.ts
│ ├── dsync
│ │ ├── DirectoryGroupInfo.tsx
│ │ ├── DirectoryGroups.tsx
│ │ ├── DirectoryInfo.tsx
│ │ ├── DirectoryTab.tsx
│ │ ├── DirectoryUserInfo.tsx
│ │ ├── DirectoryUsers.tsx
│ │ ├── DirectoryWebhookLogInfo.tsx
│ │ ├── DirectoryWebhookLogs.tsx
│ │ └── index.ts
│ ├── hooks
│ │ ├── index.ts
│ │ ├── useDirectory.tsx
│ │ ├── useFetch.ts
│ │ ├── usePaginate.tsx
│ │ └── useRouter.tsx
│ ├── identity-federation
│ │ ├── AttributesMapping.tsx
│ │ ├── Edit.tsx
│ │ ├── EditAttributesMapping.tsx
│ │ ├── EditIdentityFederationApp.tsx
│ │ ├── IdentityFederationApps.tsx
│ │ ├── NewIdentityFederationApp.tsx
│ │ └── index.ts
│ ├── index.ts
│ ├── provider.tsx
│ ├── setup-link
│ │ ├── DSyncForm.tsx
│ │ ├── NewSetupLink.tsx
│ │ ├── SSOForm.tsx
│ │ ├── SetupLinkInfo.tsx
│ │ ├── SetupLinkInfoModal.tsx
│ │ ├── SetupLinks.tsx
│ │ └── index.ts
│ ├── shared
│ │ ├── Alert.tsx
│ │ ├── Badge.tsx
│ │ ├── ButtonBase.tsx
│ │ ├── ButtonDanger.tsx
│ │ ├── ButtonOutline.tsx
│ │ ├── ButtonPrimary.tsx
│ │ ├── Card.tsx
│ │ ├── ConfirmationModal.tsx
│ │ ├── DeleteCard.tsx
│ │ ├── EmptyState.tsx
│ │ ├── Error.tsx
│ │ ├── IconButton.tsx
│ │ ├── InputWithCopyButton.tsx
│ │ ├── LinkBack.tsx
│ │ ├── LinkBase.tsx
│ │ ├── LinkOutline.tsx
│ │ ├── LinkPrimary.tsx
│ │ ├── Loading.tsx
│ │ ├── Modal.tsx
│ │ ├── PageHeader.tsx
│ │ ├── Pagination.tsx
│ │ ├── PrismLoader.tsx
│ │ ├── Table.tsx
│ │ └── index.ts
│ ├── sso-traces
│ │ ├── SSOTraceInfo.tsx
│ │ ├── SSOTraces.tsx
│ │ └── index.ts
│ ├── types.ts
│ ├── utils.ts
│ └── well-known
│ │ ├── WellKnownURLs.tsx
│ │ └── index.ts
├── tsconfig.json
├── vite-env.d.ts
└── vite.config.ts
├── kustomize
├── base
│ ├── jackson-deployment.yaml
│ ├── kustomization.yaml
│ ├── migration
│ │ ├── kustomization.yaml
│ │ └── migratepg-job.yaml
│ ├── namespace.yaml
│ └── services
│ │ ├── internal
│ │ ├── jackson-internal-service.yaml
│ │ └── kustomization.yaml
│ │ ├── jackson-service.yaml
│ │ └── kustomization.yaml
└── overlays
│ ├── dbs
│ ├── dynamodb
│ │ ├── dynamodb.yaml
│ │ └── kustomization.yaml
│ ├── mariadb
│ │ ├── kustomization.yaml
│ │ └── mariadb.yaml
│ ├── mongo
│ │ ├── kustomization.yaml
│ │ └── mongo.yaml
│ ├── mssql
│ │ ├── kustomization.yaml
│ │ └── mssql.yaml
│ ├── mysql
│ │ ├── kustomization.yaml
│ │ └── mysql.yaml
│ └── postgres
│ │ ├── kustomization.yaml
│ │ └── postgres.yaml
│ ├── demo
│ ├── .gitignore
│ ├── jackson-deployment.yaml
│ ├── kustomization.yaml
│ ├── migratepg-job.yaml
│ ├── mocksaml
│ │ ├── kustomization.yaml
│ │ └── mocksaml-deployment.yaml
│ └── services
│ │ ├── jackson-service.yaml
│ │ ├── kustomization.yaml
│ │ └── mocksaml
│ │ ├── kustomization.yaml
│ │ └── mocksaml-service.yaml
│ ├── dynamodb
│ ├── kustomization.yaml
│ └── secrets.yaml
│ ├── mariadb
│ ├── kustomization.yaml
│ └── secrets.yaml
│ ├── mongo
│ ├── kustomization.yaml
│ └── secrets.yaml
│ ├── mssql
│ ├── kustomization.yaml
│ └── secrets.yaml
│ ├── mysql
│ ├── kustomization.yaml
│ └── secrets.yaml
│ ├── postgres
│ ├── kustomization.yaml
│ ├── migratepg-job.yaml
│ └── secrets.yaml
│ └── prod-eu
│ ├── .gitignore
│ ├── jackson-deployment.yaml
│ ├── kustomization.yaml
│ ├── migratepg-job.yaml
│ ├── mocksaml
│ ├── kustomization.yaml
│ └── mocksaml-deployment.yaml
│ └── services
│ ├── jackson-service.yaml
│ ├── kustomization.yaml
│ └── mocksaml
│ ├── kustomization.yaml
│ └── mocksaml-service.yaml
├── lib
├── api
│ ├── default.ts
│ ├── index.ts
│ └── utils.ts
├── auth.ts
├── color.ts
├── constants.ts
├── development-mode.ts
├── env.ts
├── error.ts
├── jackson.ts
├── logger.ts
├── metrics.ts
├── middleware.ts
├── nextAuthAdapter.ts
├── retraced.ts
├── ui
│ ├── hooks
│ │ ├── useIdpEntityID.ts
│ │ ├── usePaginate.ts
│ │ └── useSetupLink.ts
│ ├── retraced.ts
│ └── utils.ts
└── utils.ts
├── locales
└── en
│ └── common.json
├── middleware.ts
├── migrate.sh
├── next-env.d.ts
├── next-i18next.config.js
├── next.config.js
├── npm
├── .eslintignore
├── .gitignore
├── LICENSE
├── README.md
├── map.js
├── migrate-mongo-config.js
├── migration
│ ├── cockroachdb
│ │ └── 1733850225778-pg_Initial.ts
│ ├── mariadb
│ │ ├── 1669204927486-md_Initial.ts
│ │ ├── 1692767993709-md_namespace.ts
│ │ ├── 1695120700240-md_sortorder.ts
│ │ └── 1714417013715-md_namespace.ts
│ ├── mongo
│ │ └── 20230824060000-namespace.js
│ ├── mssql
│ │ ├── 1669211007419-mss_Initial.ts
│ │ ├── 1692767993709-mss_namespace.ts
│ │ ├── 1692817789888-mss_namespace.ts
│ │ └── 1714421718208-mss_namespace.ts
│ ├── mysql
│ │ ├── 1669204920448-ms_Initial.ts
│ │ ├── 1692767993709-ms_namespace.ts
│ │ ├── 1695120580071-ms_sortorder.ts
│ │ └── 1714419315556-ms_namespace.ts
│ ├── planetscale
│ │ ├── 1653746497237-ms_nocascade.ts
│ │ ├── 1692767993709-ms_namespace.ts
│ │ ├── 1695120599689-ms_sortorder.ts
│ │ └── 1714457285484-ms_namespace.ts
│ ├── postgres
│ │ ├── 1640877103193-Initial.ts
│ │ ├── 1644332647279-createdAt.ts
│ │ ├── 1692767993709-pg_namespace.ts
│ │ └── 1714452929542-pg_namespace.ts
│ ├── sql
│ │ └── 1692817789888-namespace.ts
│ └── sqlite
│ │ └── 1716476500487-sqlite_Initial.ts
├── package-lock.json
├── package.json
├── src
│ ├── controller
│ │ ├── admin.ts
│ │ ├── analytics.ts
│ │ ├── api.ts
│ │ ├── connection
│ │ │ ├── oidc.ts
│ │ │ └── saml.ts
│ │ ├── error.ts
│ │ ├── logout.ts
│ │ ├── oauth.ts
│ │ ├── oauth
│ │ │ ├── allowed.ts
│ │ │ ├── code-verifier.ts
│ │ │ ├── oidc-client.ts
│ │ │ └── redirect.ts
│ │ ├── oidc-discovery.ts
│ │ ├── setup-link.ts
│ │ ├── sp-config.ts
│ │ ├── sso-handler.ts
│ │ └── utils.ts
│ ├── cron
│ │ └── lock.ts
│ ├── db
│ │ ├── db.ts
│ │ ├── defaultDb.ts
│ │ ├── dynamoDb.ts
│ │ ├── encrypter.ts
│ │ ├── mem.ts
│ │ ├── mongo.ts
│ │ ├── planetscale
│ │ │ └── entity
│ │ │ │ ├── JacksonIndex.ts
│ │ │ │ ├── JacksonStore.ts
│ │ │ │ └── JacksonTTL.ts
│ │ ├── redis.ts
│ │ ├── sql
│ │ │ ├── entity
│ │ │ │ ├── JacksonIndex.ts
│ │ │ │ ├── JacksonStore.ts
│ │ │ │ └── JacksonTTL.ts
│ │ │ ├── mariadb
│ │ │ │ └── entity
│ │ │ │ │ ├── JacksonIndex.ts
│ │ │ │ │ ├── JacksonStore.ts
│ │ │ │ │ └── JacksonTTL.ts
│ │ │ ├── mssql.ts
│ │ │ ├── mssql
│ │ │ │ └── entity
│ │ │ │ │ ├── JacksonIndex.ts
│ │ │ │ │ ├── JacksonStore.ts
│ │ │ │ │ └── JacksonTTL.ts
│ │ │ ├── sql.ts
│ │ │ └── sqlite
│ │ │ │ └── entity
│ │ │ │ ├── JacksonIndex.ts
│ │ │ │ ├── JacksonStore.ts
│ │ │ │ └── JacksonTTL.ts
│ │ ├── store.ts
│ │ └── utils.ts
│ ├── directory-sync
│ │ ├── batch-events
│ │ │ └── queue.ts
│ │ ├── index.ts
│ │ ├── non-scim
│ │ │ ├── google
│ │ │ │ ├── api.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── oauth.ts
│ │ │ ├── index.ts
│ │ │ ├── syncGroupMembers.ts
│ │ │ ├── syncGroups.ts
│ │ │ ├── syncUsers.ts
│ │ │ └── utils.ts
│ │ ├── request.ts
│ │ ├── scim
│ │ │ ├── Base.ts
│ │ │ ├── DirectoryConfig.ts
│ │ │ ├── DirectoryGroups.ts
│ │ │ ├── DirectoryUsers.ts
│ │ │ ├── Groups.ts
│ │ │ ├── Users.ts
│ │ │ ├── WebhookEventsLogger.ts
│ │ │ ├── transform.ts
│ │ │ └── utils.ts
│ │ ├── types.ts
│ │ └── utils.ts
│ ├── ee
│ │ ├── LICENSE
│ │ ├── branding
│ │ │ └── index.ts
│ │ ├── common
│ │ │ └── checkLicense.ts
│ │ ├── identity-federation
│ │ │ ├── app.ts
│ │ │ ├── idp-login.ts
│ │ │ ├── index.ts
│ │ │ ├── sso.ts
│ │ │ └── types.ts
│ │ ├── ory
│ │ │ └── ory.ts
│ │ └── product
│ │ │ └── index.ts
│ ├── event
│ │ ├── axios.ts
│ │ ├── index.ts
│ │ ├── types.ts
│ │ ├── utils.ts
│ │ └── webhook.ts
│ ├── index.ts
│ ├── loadConnection.ts
│ ├── opentelemetry
│ │ └── metrics.ts
│ ├── saml
│ │ ├── claims.ts
│ │ ├── lib.ts
│ │ └── x509.ts
│ ├── sso-traces
│ │ ├── index.ts
│ │ └── types.ts
│ └── typings.ts
├── test
│ ├── db
│ │ └── db.test.ts
│ ├── dsync
│ │ ├── batch
│ │ │ └── webhooks.test.ts
│ │ ├── data
│ │ │ ├── directories.ts
│ │ │ ├── group-requests.ts
│ │ │ ├── groups.ts
│ │ │ ├── user-requests.ts
│ │ │ └── users.ts
│ │ ├── directories.test.ts
│ │ ├── google_api.test.ts
│ │ ├── google_oauth.test.ts
│ │ ├── groups.test.ts
│ │ ├── membership.test.ts
│ │ ├── users.test.ts
│ │ └── webhooks.test.ts
│ ├── event
│ │ └── index.test.ts
│ ├── identity-federation
│ │ ├── app.test.ts
│ │ ├── constants.ts
│ │ ├── data
│ │ │ ├── metadata.xml
│ │ │ ├── request.xml
│ │ │ └── response.xml
│ │ └── sso.test.ts
│ ├── setup-link.test.ts
│ ├── sso-traces
│ │ └── tracer.test.ts
│ ├── sso
│ │ ├── data
│ │ │ ├── logout_response.xml
│ │ │ ├── logout_response_failed.xml
│ │ │ ├── metadata
│ │ │ │ ├── boxyhq.js
│ │ │ │ ├── boxyhq.xml
│ │ │ │ ├── example.js
│ │ │ │ ├── example.oidc.js
│ │ │ │ ├── example.xml
│ │ │ │ ├── invalidSSODescriptor
│ │ │ │ │ ├── invalidssodescriptor.js
│ │ │ │ │ └── invalidssodescriptor.xml
│ │ │ │ ├── nobinding
│ │ │ │ │ ├── boxyhq-nobinding.js
│ │ │ │ │ └── boxyhq-nobinding.xml
│ │ │ │ └── noentityID
│ │ │ │ │ ├── boxyhq-noentityID.js
│ │ │ │ │ └── boxyhq-noentityID.xml
│ │ │ └── saml_response
│ │ ├── fixture.ts
│ │ ├── logout.test.ts
│ │ ├── oidc_idp_api.test.ts
│ │ ├── oidc_idp_oauth.test.ts
│ │ ├── saml_idp_api.test.ts
│ │ ├── saml_idp_oauth.test.ts
│ │ └── saml_idp_oauth_flattened.test.ts
│ └── utils.ts
├── tsconfig.build.json
├── tsconfig.json
└── typeorm.ts
├── package-lock.json
├── package.json
├── pages
├── _app.tsx
├── _document.tsx
├── admin
│ ├── auth
│ │ ├── idp-login.tsx
│ │ └── login.tsx
│ ├── dashboard.tsx
│ ├── directory-sync
│ │ ├── [directoryId]
│ │ │ ├── edit.tsx
│ │ │ ├── events
│ │ │ │ ├── [eventId].tsx
│ │ │ │ └── index.tsx
│ │ │ ├── groups
│ │ │ │ ├── [groupId].tsx
│ │ │ │ └── index.tsx
│ │ │ ├── index.tsx
│ │ │ └── users
│ │ │ │ ├── [userId].tsx
│ │ │ │ └── index.tsx
│ │ ├── index.tsx
│ │ └── new.tsx
│ ├── identity-federation
│ │ ├── [id]
│ │ │ └── edit.tsx
│ │ ├── index.tsx
│ │ └── new.tsx
│ ├── retraced
│ │ ├── index.tsx
│ │ └── projects
│ │ │ ├── [id]
│ │ │ ├── events.tsx
│ │ │ └── index.tsx
│ │ │ ├── index.tsx
│ │ │ └── new.tsx
│ ├── settings
│ │ ├── branding
│ │ │ └── index.tsx
│ │ └── sso-connection
│ │ │ ├── edit
│ │ │ └── [id].tsx
│ │ │ ├── index.tsx
│ │ │ └── new.tsx
│ ├── setup-link
│ │ ├── index.tsx
│ │ └── new.tsx
│ ├── sso-connection
│ │ ├── edit
│ │ │ └── [id].tsx
│ │ ├── index.tsx
│ │ └── new.tsx
│ └── sso-traces
│ │ ├── [traceId]
│ │ └── inspect.tsx
│ │ └── index.tsx
├── api
│ ├── admin
│ │ ├── branding.ts
│ │ ├── connections
│ │ │ ├── [clientId].ts
│ │ │ ├── idp-entityid.ts
│ │ │ └── index.ts
│ │ ├── directory-sync
│ │ │ ├── [directoryId]
│ │ │ │ ├── events
│ │ │ │ │ ├── [eventId].ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── groups
│ │ │ │ │ ├── [groupId].ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── users
│ │ │ │ │ ├── [userId].ts
│ │ │ │ │ └── index.ts
│ │ │ ├── index.ts
│ │ │ └── providers.ts
│ │ ├── identity-federation
│ │ │ ├── [id]
│ │ │ │ └── index.ts
│ │ │ └── index.ts
│ │ ├── retraced
│ │ │ └── projects
│ │ │ │ ├── [id]
│ │ │ │ ├── groups.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── viewer-token.ts
│ │ │ │ └── index.ts
│ │ ├── setup-links
│ │ │ └── index.ts
│ │ └── sso-traces
│ │ │ ├── [traceId]
│ │ │ └── index.ts
│ │ │ └── index.ts
│ ├── auth
│ │ └── [...nextauth].ts
│ ├── branding.ts
│ ├── federated-saml
│ │ └── sso.ts
│ ├── health.ts
│ ├── hello.ts
│ ├── identity-federation
│ │ ├── oidc
│ │ │ └── idp-login
│ │ │ │ └── [fedAppId].ts
│ │ └── sso.ts
│ ├── import-hack.ts
│ ├── internals
│ │ └── product
│ │ │ ├── [productId].ts
│ │ │ └── index.ts
│ ├── logout
│ │ ├── callback.ts
│ │ └── index.ts
│ ├── oauth
│ │ ├── authorize.ts
│ │ ├── jwks.ts
│ │ ├── oidc.ts
│ │ ├── saml.ts
│ │ ├── token.ts
│ │ └── userinfo.ts
│ ├── scim
│ │ ├── oauth
│ │ │ ├── authorize.ts
│ │ │ └── callback.ts
│ │ └── v2.0
│ │ │ └── [...directory].ts
│ ├── setup
│ │ └── [token]
│ │ │ ├── directory-sync
│ │ │ ├── [directoryId]
│ │ │ │ └── index.ts
│ │ │ ├── index.ts
│ │ │ └── providers.ts
│ │ │ ├── index.ts
│ │ │ └── sso-connection
│ │ │ ├── [id].ts
│ │ │ ├── idp-entityid.ts
│ │ │ └── index.ts
│ ├── v1
│ │ ├── dsync
│ │ │ ├── [directoryId].ts
│ │ │ ├── cron
│ │ │ │ ├── process-events.ts
│ │ │ │ └── sync-google.ts
│ │ │ ├── events
│ │ │ │ ├── [eventId].ts
│ │ │ │ └── index.ts
│ │ │ ├── groups
│ │ │ │ ├── [groupId]
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── members.ts
│ │ │ │ └── index.ts
│ │ │ ├── index.ts
│ │ │ ├── product.ts
│ │ │ ├── setuplinks
│ │ │ │ ├── index.ts
│ │ │ │ └── product.ts
│ │ │ └── users
│ │ │ │ ├── [userId].ts
│ │ │ │ └── index.ts
│ │ ├── identity-federation
│ │ │ ├── index.ts
│ │ │ └── product.ts
│ │ ├── saml
│ │ │ ├── config.ts
│ │ │ └── config
│ │ │ │ └── exists.ts
│ │ ├── sso-traces
│ │ │ ├── index.ts
│ │ │ └── product
│ │ │ │ ├── count.ts
│ │ │ │ └── index.ts
│ │ ├── sso
│ │ │ ├── exists.ts
│ │ │ ├── index.ts
│ │ │ ├── product.ts
│ │ │ └── setuplinks
│ │ │ │ ├── index.ts
│ │ │ │ └── product.ts
│ │ └── stats
│ │ │ ├── index.ts
│ │ │ └── product.ts
│ └── well-known
│ │ ├── idp-metadata.ts
│ │ ├── openid-configuration.ts
│ │ ├── saml.cer.ts
│ │ └── sp-metadata.ts
├── error.tsx
├── idp
│ └── select.tsx
├── index.tsx
├── setup
│ └── [token]
│ │ ├── directory-sync
│ │ ├── [directoryId]
│ │ │ ├── edit.tsx
│ │ │ └── index.tsx
│ │ ├── index.tsx
│ │ └── new.tsx
│ │ ├── index.tsx
│ │ └── sso-connection
│ │ ├── edit
│ │ └── [id].tsx
│ │ ├── index.tsx
│ │ └── new.tsx
└── well-known
│ ├── idp-configuration.tsx
│ ├── index.tsx
│ ├── oidc-configuration.tsx
│ └── saml-configuration.tsx
├── performance-test
├── Readme.md
└── k6
│ ├── dsync-load-test.js
│ ├── idFed-load-test.js
│ ├── scim-load-test.js
│ └── sso-load-test.js
├── playwright.config.ts
├── postbuild.ts
├── postcss.config.js
├── public
├── favicon.ico
└── logo.png
├── samljackson480.gif
├── skaffold-demo-mocksaml-services.yaml
├── skaffold-demo-mocksaml.yaml
├── skaffold-demo-services.yaml
├── skaffold-demo.yaml
├── skaffold-dynamodb.yaml
├── skaffold-mariadb.yaml
├── skaffold-mongo.yaml
├── skaffold-mssql.yaml
├── skaffold-mysql.yaml
├── skaffold-postgres.yaml
├── skaffold-prod-eu-mocksaml-services.yaml
├── skaffold-prod-eu-mocksaml.yaml
├── skaffold-prod-eu-services.yaml
├── skaffold-prod-eu.yaml
├── styles
├── globals.css
└── sdk-override.module.css
├── swagger
├── swagger-definition.js
└── swagger.json
├── tailwind.config.js
├── tsconfig.json
└── types
├── base.ts
├── index.ts
└── retraced.ts
/.dockerignore:
--------------------------------------------------------------------------------
1 | Dockerfile
2 | .dockerignore
3 | **/node_modules
4 | npm-debug.log
5 | README.md
6 | .next
7 | .git
8 | .github
9 | _dev
10 | .vscode
11 | swagger
12 | .husky
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Report any issues with the platform
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 | ---
8 |
9 | Found a bug? Please fill out the sections below. 👍
10 |
11 | ### Issue Summary
12 |
13 | A summary of the issue. This needs to be a clear detailed-rich summary.
14 |
15 | ### Steps to Reproduce
16 |
17 | 1. (for example) Went to ...
18 | 2. Clicked on...
19 | 3. ...
20 |
21 | Any other relevant information. For example, why do you consider this a bug and what did you expect to happen instead?
22 |
23 | ### Technical details
24 |
25 | - Browser version: You can use https://www.whatsmybrowser.org/ to find this out.
26 | - Node.js version
27 | - Anything else that you think could be an issue.
28 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: Questions
4 | url: https://github.com/ory/polis/discussions
5 | about: Ask a general question about the project on our GitHub Discussion page
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | _config
4 | .eslintcache
5 |
6 | # dependencies
7 | /node_modules
8 | /.pnp
9 | .pnp.js
10 |
11 | # testing
12 | /coverage
13 |
14 | # next.js
15 | /.next/
16 | /out/
17 |
18 | # production
19 | /build
20 |
21 | # misc
22 | .DS_Store
23 | *.pem
24 |
25 | # debug
26 | npm-debug.log*
27 | yarn-debug.log*
28 | yarn-error.log*
29 |
30 | # local env files
31 | .env.local
32 | .env.development.local
33 | .env.test.local
34 | .env.production.local
35 |
36 | # vercel
37 | .vercel
38 |
39 | # typescript
40 | *.tsbuildinfo
41 | .nyc_output/**
42 |
43 | npmversion.txt
44 | publishTag.txt
45 | .env
46 | _dev/docker/dynamodb/shared-local-instance.db
47 | public/terminus/sprites.png
48 | **/.tap/**
49 |
50 | internal-ui/dist
51 | _dev/docker/libsql/iku.db
52 |
53 | # tests
54 | /report
55 | /test-results/
56 | /playwright-report/
57 | /playwright/.cache/
58 | .idea
59 | _dev/cockroach-data
60 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .next
3 | public
4 | **/**/node_modules
5 | **/**/.next
6 | **/**/public
7 | npm/migration/**
8 | npm/dist/**
9 | npm/.nyc_output/**
10 | .vscode/**
11 |
12 | npm/package-lock.json
13 |
14 | *.lock
15 | *.log
16 |
17 | .gitignore
18 | .npmignore
19 | .prettierignore
20 | .DS_Store
21 | .eslintignore
22 |
23 | **/LICENSE
24 | swagger/swagger.json
25 | e2e/state.json
26 |
27 | internal-ui/dist/**
28 |
29 | helm
30 | eslint.config.cjs
31 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | bracketSpacing: true,
3 | bracketSameLine: true,
4 | singleQuote: true,
5 | jsxSingleQuote: true,
6 | trailingComma: 'es5',
7 | semi: true,
8 | printWidth: 110,
9 | arrowParens: 'always',
10 | overrides: [
11 | {
12 | files: ['tsconfig.json', 'jsconfig.json'],
13 | options: {
14 | parser: 'jsonc',
15 | },
16 | },
17 | ],
18 | };
19 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "yzhang.markdown-all-in-one", // nicer markdown support
4 | "esbenp.prettier-vscode", // prettier plugin
5 | "dbaeumer.vscode-eslint", // eslint plugin
6 | "bradlc.vscode-tailwindcss", // hinting / autocompletion for tailwind
7 | "ban.spellright", // Spell check for docs
8 | "fabianlauer.vs-code-xml-format" // xml formatter
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.formatOnSave": true,
3 | "files.exclude": {
4 | "**/.next": true,
5 | "**/.nyc_output": true,
6 | "**/dist": true,
7 | "**/node_modules": true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: npm start
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Responsible Disclosure
2 |
3 | ## Reporting a Vulnerability
4 |
5 | We strive to stay ahead of security vulnerabilities but would love to get the community's help in making us aware of the ones we miss.
6 |
7 | Please email `security@boxyhq.com` to report security vulnerabilities and exploits.
8 |
9 | We will acknowledge legitimate reports and address it according to the severity.
10 |
--------------------------------------------------------------------------------
/_dev/docker-compose-cockroachdb.yml:
--------------------------------------------------------------------------------
1 | services:
2 | cockroachdb:
3 | image: cockroachdb/cockroach:v24.3.0
4 | platform: linux/amd64
5 | ports:
6 | - '26257:26257'
7 | - '8081:8080'
8 | command: start-single-node --insecure
9 |
--------------------------------------------------------------------------------
/_dev/saml_config/boxyhq.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | defaultRedirectUrl: 'http://localhost:3366/login/saml',
3 | redirectUrl: '["http://localhost:3366/*"]',
4 | tenant: 'boxyhq.com',
5 | product: 'saml-demo.boxyhq.com',
6 | name: 'Mock SAML',
7 | };
8 |
--------------------------------------------------------------------------------
/components/LicenseRequired.tsx:
--------------------------------------------------------------------------------
1 | import { EmptyState } from '@boxyhq/internal-ui';
2 |
3 | const LicenseRequired = () => {
4 | return (
5 |
9 | );
10 | };
11 |
12 | export default LicenseRequired;
13 |
--------------------------------------------------------------------------------
/components/PoweredBy.tsx:
--------------------------------------------------------------------------------
1 | import { useTranslation } from 'next-i18next';
2 |
3 | export const PoweredBy = () => {
4 | const { t } = useTranslation('common');
5 |
6 | return (
7 |
8 |
9 | {t('boxyhq_powered_by')}
10 |
11 |
12 | );
13 | };
14 |
--------------------------------------------------------------------------------
/components/layouts/index.ts:
--------------------------------------------------------------------------------
1 | export { AccountLayout } from './AccountLayout';
2 | export { SetupLinkLayout } from './SetupLinkLayout';
3 |
--------------------------------------------------------------------------------
/components/setup-link-instructions/Footer.tsx:
--------------------------------------------------------------------------------
1 | import NextButton from '@components/setup-link-instructions/NextButton';
2 | import PreviousButton from '@components/setup-link-instructions/PreviousButton';
3 |
4 | interface FooterProps {
5 | hasNext?: boolean;
6 | }
7 |
8 | const Footer = ({ hasNext = true }: FooterProps) => {
9 | return (
10 |
11 |
12 | {hasNext &&
}
13 |
14 | );
15 | };
16 |
17 | export default Footer;
18 |
--------------------------------------------------------------------------------
/components/setup-link-instructions/NextButton.tsx:
--------------------------------------------------------------------------------
1 | import { useRouter } from 'next/router';
2 | import { useTranslation } from 'next-i18next';
3 | import { ButtonPrimary } from '@boxyhq/internal-ui';
4 |
5 | const NextButton = () => {
6 | const router = useRouter();
7 | const { t } = useTranslation('common');
8 |
9 | const onClick = () => {
10 | const { idp, step, token } = router.query as { idp: string; step: string; token: string };
11 |
12 | router.push({
13 | pathname: router.pathname,
14 | query: {
15 | idp,
16 | step: parseInt(step) + 1,
17 | token,
18 | },
19 | });
20 | };
21 |
22 | return (
23 |
24 | {t('next_step')}
25 |
26 | );
27 | };
28 |
29 | export default NextButton;
30 |
--------------------------------------------------------------------------------
/components/setup-link-instructions/PreviousButton.tsx:
--------------------------------------------------------------------------------
1 | import { useRouter } from 'next/router';
2 | import { useTranslation } from 'next-i18next';
3 | import { ButtonOutline } from '@boxyhq/internal-ui';
4 |
5 | const PreviousButton = () => {
6 | const router = useRouter();
7 | const { t } = useTranslation('common');
8 |
9 | const onClick = () => {
10 | const { idp, step, token } = router.query as { idp: string; step: string; token: string };
11 |
12 | router.push({
13 | pathname: router.pathname,
14 | query: {
15 | idp,
16 | step: parseInt(step) - 1,
17 | token,
18 | },
19 | });
20 | };
21 |
22 | return (
23 |
24 | {t('previous_step')}
25 |
26 | );
27 | };
28 |
29 | export default PreviousButton;
30 |
--------------------------------------------------------------------------------
/components/setup-link-instructions/SelectIdentityProviders.tsx:
--------------------------------------------------------------------------------
1 | import { useRouter } from 'next/router';
2 |
3 | import { identityProviders } from '@lib/constants';
4 |
5 | const SelectIdentityProviders = () => {
6 | const router = useRouter();
7 |
8 | const onClick = (id: string) => {
9 | const params = new URLSearchParams({
10 | idp: id,
11 | step: '1',
12 | });
13 |
14 | router.push(router.asPath + '?' + params.toString());
15 | };
16 |
17 | return (
18 |
19 | {identityProviders.map((provider) => (
20 | onClick(provider.id)}>
24 | {provider.name}
25 |
26 | ))}
27 |
28 | );
29 | };
30 |
31 | export default SelectIdentityProviders;
32 |
--------------------------------------------------------------------------------
/components/setup-link-instructions/auth0/1.mdx:
--------------------------------------------------------------------------------
1 | ## Step 1: Create Application
2 |
3 | From your Auth0 dashboard, click **Applications** from the left navigation menu.
4 |
5 | If your application is already created, choose it from the list and move to the next step.
6 |
7 | 
8 |
9 | If you haven't created a SAML application, click the **Create Application** button to create a new application.
10 |
11 | Give your application a **Name** and click **Create**.
12 |
13 | 
14 |
15 |
16 |
--------------------------------------------------------------------------------
/components/setup-link-instructions/auth0/2.mdx:
--------------------------------------------------------------------------------
1 | ## Step 2: Configure Application
2 |
3 | Click the tab **Addons** and enable **SAML2 WEB APP** addon.
4 |
5 | 
6 |
7 | Enter your **Application Callback URL** on the next screen and click **Enable**.
8 |
9 |
10 |
11 |
12 |
13 | 
14 |
15 | Click the tab **Usage** and download the **Identity Provider Metadata**.
16 |
17 | 
18 |
19 |
20 |
--------------------------------------------------------------------------------
/components/setup-link-instructions/auth0/3.mdx:
--------------------------------------------------------------------------------
1 | ## Step 3: Create SAML Connection
2 |
3 | Enter the Identity Provider Metadata below. You can either enter the metadata URL or paste the XML file content directly.
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/components/setup-link-instructions/azure/1.mdx:
--------------------------------------------------------------------------------
1 | ## Step 1: Create Application
2 |
3 | From your Azure Admin console, click **Enterprise applications** from the left navigation menu.
4 |
5 | 
6 |
7 | If your application is already created, choose it from the list and move to the next step.
8 |
9 | If you haven't created a SAML application, click **New application** from the top to create a new application.
10 |
11 | 
12 |
13 | From the next screen, click **Create your own application**.
14 |
15 | Give your application a **Name** and click **Create**.
16 |
17 | 
18 |
19 |
20 |
--------------------------------------------------------------------------------
/components/setup-link-instructions/azure/2.mdx:
--------------------------------------------------------------------------------
1 | ## Step 2: Configure Application
2 |
3 | Select **Single Sign On** from the **Manage** section of your app and then **SAML**.
4 |
5 | 
6 |
7 | Click **Edit** on the **Basic SAML Configuration** section.
8 |
9 | 
10 |
11 | Enter the following values in the **Basic SAML Configuration** section on the next screen:
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | Click **Save** to save your changes.
23 |
24 | 
25 |
26 |
27 |
--------------------------------------------------------------------------------
/components/setup-link-instructions/azure/3.mdx:
--------------------------------------------------------------------------------
1 | ## Step 3: Attribute Mapping
2 |
3 | Click **Edit** on the **Attributes & Claims** section.
4 |
5 | 
6 |
7 | You have to configure the following attributes under the **Attributes & Claims** section:
8 |
9 | | Name | Value |
10 | | -------------------------------------------------------------------- | ---------------------- |
11 | | `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress` | user.mail |
12 | | `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname` | user.givenname |
13 | | `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name` | user.userprincipalname |
14 | | `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname` | user.surname |
15 |
16 | 
17 |
18 | Go to the section **SAML Signing Certificate** section and download the **Federation Metadata XML**.
19 |
20 | 
21 |
22 |
23 |
--------------------------------------------------------------------------------
/components/setup-link-instructions/azure/4.mdx:
--------------------------------------------------------------------------------
1 | ## Step 4: Create SAML Connection
2 |
3 | Enter the Identity Provider Metadata below. You can either enter the metadata URL or paste the XML file content directly.
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/components/setup-link-instructions/generic-oidc/1.mdx:
--------------------------------------------------------------------------------
1 | ## Step 1: Create Application
2 |
3 |
8 |
9 | **Client/Application ID**
10 |
11 | The OIDC Identity Provider normally will generate a unique identifier for the registered App. Make a note of this to be used later when creating the SSO connection with Polis.
12 |
13 | **Client Secret**
14 |
15 | Along with the Client ID, the IdP also generates a client secret which is used to authenticate the client while issuing tokens. Make a note of this to be used later when creating the SSO connection with Polis.
16 |
17 | _Since Polis acts as a proxy between the app and the OIDC Identity Provider, here the application/client is Polis._
18 |
19 |
20 |
--------------------------------------------------------------------------------
/components/setup-link-instructions/generic-oidc/2.mdx:
--------------------------------------------------------------------------------
1 | ## Step 2: Create OIDC Connection
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/components/setup-link-instructions/generic-saml/1.mdx:
--------------------------------------------------------------------------------
1 | ## Step 1: Configuration SAML Application
2 |
3 | Your Identity Provider (IdP) will ask for the following information while configuring the SAML application.
4 |
5 | Please do not add a trailing slash at the end of the URLs.
6 |
7 | Create them exactly as shown below:
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | - Response: `Signed`
19 |
20 | - Assertion Signature: `Signed`
21 |
22 | - Signature Algorithm: `RSA-SHA256`
23 |
24 | - Assertion Encryption: `Unencrypted`
25 |
26 | The deployed Polis service has a Service Provider (SP) endpoint that exposes the above metadata and the same can be accessed at [SAML Configuration](/.well-known/saml-configuration).
27 |
28 |
29 |
--------------------------------------------------------------------------------
/components/setup-link-instructions/generic-saml/2.mdx:
--------------------------------------------------------------------------------
1 | ## Step 2: SAML Profile/Claims/Attributes Mapping
2 |
3 | We try and support 4 attributes in the SAML claims:
4 |
5 | - **id**
6 | - **email**
7 | - **firstName**
8 | - **lastName**
9 |
10 | This is how the common SAML attributes map over for most providers, but some providers have custom mappings. Please refer to the documentation on Identity Provider to understand the exact mapping.
11 |
12 | | SAML Attribute | Polis Mapping |
13 | | ---------------------------------------------------------------------- | ------------- |
14 | | `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier` | id |
15 | | `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress` | email |
16 | | `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname` | firstName |
17 | | `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname` | lastName |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/components/setup-link-instructions/generic-saml/3.mdx:
--------------------------------------------------------------------------------
1 | ## Step 3: Create SAML Connection
2 |
3 | Enter the Identity Provider Metadata below. You can either enter the metadata URL or paste the XML file content directly.
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/components/setup-link-instructions/google/1.mdx:
--------------------------------------------------------------------------------
1 | ## Step 1: Create Application
2 |
3 | From your Google Admin console, click **Apps** from the sidebar then click **Web and mobile apps** from the list.
4 |
5 | If your application is already created, choose it from the list and move to the next step.
6 |
7 | If you haven't created a SAML application, click **Add custom SAML app** from the menu.
8 |
9 | 
10 |
11 | Give your application an **App name** and click **Continue**.
12 |
13 | 
14 |
15 |
16 |
--------------------------------------------------------------------------------
/components/setup-link-instructions/google/2.mdx:
--------------------------------------------------------------------------------
1 | ## Step 2: Configure Application
2 |
3 | From the next screen, click **DOWNLOAD METADATA** to download the metadata XML file, then click **Continue**.
4 |
5 | 
6 |
7 | Enter the following values in the **Service provider details** section:
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | 
21 |
22 |
23 |
--------------------------------------------------------------------------------
/components/setup-link-instructions/google/3.mdx:
--------------------------------------------------------------------------------
1 | ## Step 3: Attribute Mapping
2 |
3 | Under the **Attributes** section, you have to configure the following attributes:
4 |
5 | | App attributes | Google directory attributes |
6 | | -------------- | --------------------------- |
7 | | email | Primary email |
8 | | firstName | First name |
9 | | lastName | Last name |
10 |
11 | After you have configured the attributes, click **Finish** to save the configuration.
12 |
13 | 
14 |
15 | From the next screen, click **User access** to configure the application to allow users to log in.
16 |
17 | 
18 |
19 | Check the **ON for everyone** checkbox and click **Save**.
20 |
21 | 
22 |
23 |
24 |
--------------------------------------------------------------------------------
/components/setup-link-instructions/google/4.mdx:
--------------------------------------------------------------------------------
1 | ## Step 4: Create SAML Connection
2 |
3 | Enter the Identity Provider Metadata below. You can either enter the metadata URL or paste the XML file content directly.
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/components/setup-link-instructions/jumpcloud/1.mdx:
--------------------------------------------------------------------------------
1 | ## Step 1: Create Application
2 |
3 | From your JumpCloud Admin console, click **SSO** from the left navigation menu.
4 |
5 | If your application is already created, choose it from the list and move to the next step.
6 |
7 | If you haven't created a SAML application, click plus icon and then **Custom SAML App** to create a new application
8 |
9 | 
10 |
11 | Give your application a **Display Label**.
12 |
13 | 
14 |
15 |
16 |
--------------------------------------------------------------------------------
/components/setup-link-instructions/jumpcloud/2.mdx:
--------------------------------------------------------------------------------
1 | ## Step 2: Configure Application
2 |
3 | Next click on the **SSO** tab and enter the following values:
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | 
20 |
21 |
22 |
--------------------------------------------------------------------------------
/components/setup-link-instructions/jumpcloud/3.mdx:
--------------------------------------------------------------------------------
1 | ## Step 3: Attribute Mapping
2 |
3 | Under the **Attributes** section, you have to configure the following attributes:
4 |
5 | | Service Provider Attribute Name | JumpCloud Attribute Name |
6 | | ------------------------------- | ------------------------ |
7 | | email | email |
8 | | firstName | firstname |
9 | | lastName | lastname |
10 |
11 | Make sure you have checked the **Declare Redirect Endpoint** checkbox.
12 |
13 | Finally, click **Activate** to save the application configuration.
14 |
15 | 
16 |
17 | Now go back to the SAML app you just created, click the tab **SSO**, and click the button **Export Metadata** to download the metadata XML file.
18 |
19 | 
20 |
21 |
22 |
--------------------------------------------------------------------------------
/components/setup-link-instructions/jumpcloud/4.mdx:
--------------------------------------------------------------------------------
1 | ## Step 4: Create SAML Connection
2 |
3 | Enter the Identity Provider Metadata below. You can either enter the metadata URL or paste the XML file content directly.
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/components/setup-link-instructions/microsoft-adfs/2.mdx:
--------------------------------------------------------------------------------
1 | ## Step 2: Attribute Mapping
2 |
3 | On the **Configure Claim Rule** screen, enter a **Claim Rule Name** of your choice, select **Active Directory** as the **Attribute Store**, then add the following mapping:
4 |
5 | | LDAP Attribute | Outgoing Claim Type |
6 | | ------------------- | ------------------- |
7 | | E-Mail-Addresses | E-Mail Address |
8 | | Given-Name | Given Name |
9 | | Surname | Surname |
10 | | User-Principal-Name | Name ID |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/components/setup-link-instructions/microsoft-adfs/3.mdx:
--------------------------------------------------------------------------------
1 | ## Step 3: Transform Rule
2 |
3 | Create a transform rule mapping the incoming **Email-Address** to outgoing **NameID** (of type **Email**), ADFS by default sends **NameID** as **Unspecified** which results in an **InvalidNameIDPolicy** error if this step is missed.
4 |
5 | 
6 |
7 | If you'd rather use Claim rule language then the following rule can be applied:
8 |
9 | ```sh
10 | c:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"] => issue(Type = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", Issuer = c.Issuer, OriginalIssuer = c.OriginalIssuer, Value = c.Value, ValueType = c.ValueType, Properties["http://schemas.xmlsoap.org/ws/2005/05/identity/claimproperties/format"] = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress");
11 | ```
12 |
13 | Finally open Windows PowerShell as an administrator, then run the following command:
14 |
15 | ```sh
16 | Set-ADFSRelyingPartyTrust -TargetName -SamlResponseSignature "MessageAndAssertion"
17 | ```
18 |
19 |
20 |
--------------------------------------------------------------------------------
/components/setup-link-instructions/microsoft-adfs/4.mdx:
--------------------------------------------------------------------------------
1 | ## Step 4: Create SAML Connection
2 |
3 | Enter the Identity Provider Metadata below. You can either enter the metadata URL or paste the XML file content directly.
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/components/setup-link-instructions/okta/1.mdx:
--------------------------------------------------------------------------------
1 | ## Step 1: Create Application
2 |
3 | From your Okta account, click **Applications** from the left navigation menu.
4 |
5 | If your application is already created, choose it from the list and move to the next step.
6 |
7 | If you haven't created a SAML application, click the **Create App Integration** button to create a new application.
8 |
9 | 
10 |
11 | Choose **SAML 2.0** from the next screen and click **Next**.
12 |
13 | 
14 |
15 | Give your application an **App Name** and click **Next**.
16 |
17 | 
18 |
19 |
20 |
--------------------------------------------------------------------------------
/components/setup-link-instructions/okta/2.mdx:
--------------------------------------------------------------------------------
1 | ## Step 2: Configure Application
2 |
3 | Enter the following values in the **SAML Settings** section on the next screen:
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | Select **EmailAddress** from the **Name ID format** dropdown.
17 |
18 | 
19 |
20 |
21 |
--------------------------------------------------------------------------------
/components/setup-link-instructions/okta/3.mdx:
--------------------------------------------------------------------------------
1 | ## Step 3: Attribute Mapping
2 |
3 | Under the **Attribute Statements** section, you have to configure the following attributes:
4 |
5 | | Name | Value |
6 | | --------- | -------------- |
7 | | id | user.id |
8 | | email | user.email |
9 | | firstName | user.firstName |
10 | | lastName | user.lastName |
11 |
12 | 
13 |
14 | On the next screen select **I'm an Okta customer adding an internal app** and click **Finish**.
15 |
16 | 
17 |
18 | From your application, click **Sign On** tab and go to the section **SAML Signing Certificates**
19 |
20 | Click the **Actions** dropdown for the correct certificate and click **View IdP metadata**.
21 |
22 | A separate window will open with the metadata XML file, you can copy it to your clipboard.
23 |
24 | 
25 |
26 |
27 |
--------------------------------------------------------------------------------
/components/setup-link-instructions/okta/4.mdx:
--------------------------------------------------------------------------------
1 | ## Step 4: Create SAML Connection
2 |
3 | Enter the Identity Provider Metadata below. You can either enter the metadata URL or paste the XML file content directly.
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/components/setup-link-instructions/onelogin/1.mdx:
--------------------------------------------------------------------------------
1 | ## Step 1: Create Application
2 |
3 | From your OneLogin account, click **Applications** from the top navigation menu.
4 |
5 | If your application is already created, choose it from the list and move to the next step.
6 |
7 | If you haven't created a SAML application, click the **Add App** button to create a new application.
8 |
9 | 
10 |
11 | Search for **SAML Test Connector** in the **Find Applications** section. Select **SAML Custom Connector (Advanced)** from the search results.
12 |
13 | 
14 |
15 | Give your application a **Display Name** and click **Save**.
16 |
17 | 
18 |
19 |
20 |
--------------------------------------------------------------------------------
/components/setup-link-instructions/onelogin/2.mdx:
--------------------------------------------------------------------------------
1 | ## Step 2: Configure Application
2 |
3 | From your application, click the **Configuration** tab on the left to configure the application.
4 |
5 | You have to enter the following values in the **Application details** section:
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | 
22 |
23 | Select the **Service Provider** from the **SAML initiator** dropdown.
24 |
25 | 
26 |
27 | Click **Save** to save the configuration.
28 |
29 | Click the dropdown menu **More Actions** from the top right corner and click **SAML Metadata** to download the metadata XML file.
30 |
31 | 
32 |
33 |
34 |
--------------------------------------------------------------------------------
/components/setup-link-instructions/onelogin/3.mdx:
--------------------------------------------------------------------------------
1 | ## Step 3: Attribute Mapping
2 |
3 | From your application, click the **Parameters** tab on the left.
4 |
5 | You have to configure the following attributes:
6 |
7 | | SAML Custom Connector Field | Value |
8 | | --------------------------- | ---------- |
9 | | id | UUID |
10 | | email | Email |
11 | | firstName | First Name |
12 | | lastName | Last Name |
13 |
14 | 
15 |
16 | See the below screenshot to see how to map the **id** attribute to **UUID**.
17 |
18 | Enter **id** in the **Field name** input and check the **Include in SAML assertion** checkbox. Click **Save** to continue.
19 |
20 | 
21 |
22 | On the next screen, select **UUID** from the **Value** dropdown and click **Save**.
23 |
24 | 
25 |
26 | Do the same for the other attributes (email, firstName, lastName).
27 |
28 |
29 |
--------------------------------------------------------------------------------
/components/setup-link-instructions/onelogin/4.mdx:
--------------------------------------------------------------------------------
1 | ## Step 4: Create SAML Connection
2 |
3 | Enter the Identity Provider Metadata below. You can either enter the metadata URL or paste the XML file content directly.
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/components/setup-link-instructions/pingone/1.mdx:
--------------------------------------------------------------------------------
1 | ## Step 1: Create Application
2 |
3 | From your PingOne account, click **Connections** > **Applications** from left navigation menu.
4 |
5 | If your application is already created, choose it from the list and move to the next step.
6 |
7 | If you haven't created a SAML application, click plus button to create a new application.
8 |
9 | 
10 |
11 | Give your application a **Application Name**, choose **SAML Application** from the **Application Type** and click **Configure**.
12 |
13 |
14 |
--------------------------------------------------------------------------------
/components/setup-link-instructions/pingone/2.mdx:
--------------------------------------------------------------------------------
1 | ## Step 2: Configure Application
2 |
3 | 
4 |
5 | From the next screen, you have to enter the following values in the **SAML Configuration** section:
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | Click **Save** to save the configuration.
19 |
20 | 
21 |
22 | Click the **Configuration** tab from the top and click **Download Metadata** to download the metadata XML file.
23 |
24 | 
25 |
26 |
27 |
--------------------------------------------------------------------------------
/components/setup-link-instructions/pingone/3.mdx:
--------------------------------------------------------------------------------
1 | ## Step 3: Attribute Mapping
2 |
3 | Click the **Attribute Mappings** tab from the top and you have to configure the following attributes:
4 |
5 | | SAML App | PingOne |
6 | | --------- | ------------- |
7 | | id | User ID |
8 | | email | Email Address |
9 | | firstName | Given Name |
10 | | lastName | Family Name |
11 |
12 | 
13 |
14 | Make sure you have enabled your app so that it can be used by the users. You can do this by clicking the **Toggle** button next to your app.
15 |
16 | 
17 |
18 |
19 |
--------------------------------------------------------------------------------
/components/setup-link-instructions/pingone/4.mdx:
--------------------------------------------------------------------------------
1 | ## Step 4: Create SAML Connection
2 |
3 | Enter the Identity Provider Metadata below. You can either enter the metadata URL or paste the XML file content directly.
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/components/setup-link-instructions/rippling/1.mdx:
--------------------------------------------------------------------------------
1 | ## Step 1: Create Application
2 |
3 | You'll need to create a new **Custom App** from your Rippling account if the app is not already in the Rippling app directory.
4 |
5 | Go to **IT Management** > **Custom App** from the left navigation menu.
6 |
7 | Click **Create New App** button to create a new application.
8 |
9 | From the next screen, fill in the following fields:
10 |
11 | - **App Name**
12 | - **Select Categories**
13 | - **Upload Logo**
14 | - **What type of app would you like to create?** - Make sure you select **Single Sign-On (SAML)** from the list.
15 |
16 | 
17 |
18 |
19 |
--------------------------------------------------------------------------------
/components/setup-link-instructions/rippling/3.mdx:
--------------------------------------------------------------------------------
1 | ## Step 3: Create SAML Connection
2 |
3 | Enter the Identity Provider Metadata below. You can either enter the metadata URL or paste the XML file content directly.
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/components/setup-link/InvalidSetupLinkAlert.tsx:
--------------------------------------------------------------------------------
1 | import { useTranslation } from 'next-i18next';
2 |
3 | const InvalidSetupLinkAlert = ({ message }: { message: string }) => {
4 | const { t } = useTranslation('common');
5 |
6 | return (
7 |
8 |
{message}
9 |
{t('invalid_setup_link_alert')}
10 |
11 | );
12 | };
13 |
14 | export default InvalidSetupLinkAlert;
15 |
--------------------------------------------------------------------------------
/components/setup-link/StepProgressBar.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const StepProgressBar = ({ currentStep, totalSteps, steps }) => {
4 | return (
5 |
6 |
7 | {Array.from({ length: totalSteps }, (_, index) => (
8 |
9 |
12 | {index + 1}
13 |
14 |
15 | {steps[index]}
16 |
17 |
18 | ))}
19 |
20 |
21 | );
22 | };
23 |
24 | export default StepProgressBar;
25 |
--------------------------------------------------------------------------------
/components/styles.ts:
--------------------------------------------------------------------------------
1 | import styles from '@styles/sdk-override.module.css';
2 |
3 | export const BOXYHQ_UI_CSS = {
4 | button: {
5 | ctoa: 'btn btn-primary',
6 | destructive: 'btn btn-md btn-error',
7 | },
8 | input: `${styles['sdk-input']} input input-bordered`,
9 | select: styles['sdk-select'],
10 | textarea: styles['sdk-input'],
11 | confirmationPrompt: {
12 | button: {
13 | ctoa: 'btn btn-md',
14 | cancel: 'btn btn-md btn-outline',
15 | },
16 | },
17 | secretInput: 'input input-bordered',
18 | section: 'mb-8',
19 | };
20 |
--------------------------------------------------------------------------------
/e2e/.gitignore:
--------------------------------------------------------------------------------
1 | state.json
--------------------------------------------------------------------------------
/e2e/api/helpers/api.ts:
--------------------------------------------------------------------------------
1 | export const options = {
2 | extraHTTPHeaders: {
3 | Authorization: `Api-Key ${process.env.JACKSON_API_KEYS}`,
4 | 'Content-Type': 'application/json',
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/e2e/api/helpers/index.ts:
--------------------------------------------------------------------------------
1 | export * from './users';
2 | export * from './groups';
3 |
--------------------------------------------------------------------------------
/e2e/api/helpers/oauth.ts:
--------------------------------------------------------------------------------
1 | import { type APIRequestContext, expect } from '@playwright/test';
2 | import { OAuthReq } from 'npm/src';
3 |
4 | // Make oauth autorize request
5 | export const oauthAuthorize = async (request: APIRequestContext, data: OAuthReq, isFailure = false) => {
6 | try {
7 | const response = await request.post('/api/oauth/authorize', {
8 | data,
9 | });
10 |
11 | expect(response.ok()).toBe(true);
12 | if (!isFailure) {
13 | expect(response.status()).toBe(302);
14 | }
15 | } catch (ex: any) {
16 | if (isFailure) {
17 | expect(ex.message).toBeDefined();
18 | } else {
19 | throw ex;
20 | }
21 | }
22 | };
23 |
--------------------------------------------------------------------------------
/e2e/api/helpers/utils.ts:
--------------------------------------------------------------------------------
1 | import type { Directory } from '@boxyhq/saml-jackson';
2 |
3 | export function scimOpUrl(directory: Directory, opPath: string) {
4 | let endpoint = `${directory.scim.endpoint}/${opPath}`;
5 | if (directory.type === 'azure-scim-v2') {
6 | const [_main, aadOpts] = directory.scim.endpoint!.split('?');
7 | endpoint = `${_main}${opPath}?${aadOpts}`;
8 | }
9 | return endpoint;
10 | }
11 |
--------------------------------------------------------------------------------
/e2e/api/v1/sso/jwks.spec.ts:
--------------------------------------------------------------------------------
1 | import { test, expect } from '@playwright/test';
2 |
3 | test('should return public key in jwk format', async ({ request }) => {
4 | const response = await request.get('/oauth/jwks');
5 | const jwks = await response.json();
6 | const jose = await import('jose');
7 |
8 | const spki = Buffer.from(process.env.OPENID_RSA_PUBLIC_KEY || '', 'base64').toString('ascii');
9 | const importedPublicKey = await jose.importSPKI(spki, process.env.OPENID_JWS_ALG || '');
10 |
11 | const publicKeyJWK = await jose.exportJWK(importedPublicKey);
12 | const jwkThumbprint = await jose.calculateJwkThumbprint(publicKeyJWK);
13 |
14 | expect(jwks).toStrictEqual({
15 | keys: [
16 | {
17 | ...publicKeyJWK,
18 | kid: jwkThumbprint,
19 | alg: process.env.OPENID_JWS_ALG,
20 | use: 'sig',
21 | },
22 | ],
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/e2e/api/v1/sso/oidc-discovery.spec.ts:
--------------------------------------------------------------------------------
1 | import { test, expect } from '@playwright/test';
2 |
3 | test('should return oidc discovery configuration', async ({ request, baseURL }) => {
4 | const response = await request.get('/.well-known/openid-configuration');
5 | const oidcDiscovery = await response.json();
6 |
7 | expect(oidcDiscovery).toStrictEqual({
8 | issuer: baseURL,
9 | authorization_endpoint: `${baseURL}/api/oauth/authorize`,
10 | token_endpoint: `${baseURL}/api/oauth/token`,
11 | userinfo_endpoint: `${baseURL}/api/oauth/userinfo`,
12 | jwks_uri: `${baseURL}/oauth/jwks`,
13 | response_types_supported: ['code'],
14 | subject_types_supported: ['public'],
15 | id_token_signing_alg_values_supported: ['RS256'],
16 | grant_types_supported: ['authorization_code'],
17 | code_challenge_methods_supported: ['plain', 'S256'],
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/e2e/api/v1/sso/oidc.spec.ts:
--------------------------------------------------------------------------------
1 | import { test } from '@playwright/test';
2 | import { options } from '../../helpers/api';
3 |
4 | test.use(options);
5 |
6 | test.describe('OIDC SSO Connection', () => {
7 | //
8 | });
9 |
--------------------------------------------------------------------------------
/e2e/support/data/dsync/index.ts:
--------------------------------------------------------------------------------
1 | export * from './azure';
2 | export * from './okta';
3 |
4 | export enum DirectorySyncProviders {
5 | 'azure-scim-v2' = 'Entra ID SCIM v2.0',
6 | 'onelogin-scim-v2' = 'OneLogin SCIM v2.0',
7 | 'okta-scim-v2' = 'Okta SCIM v2.0',
8 | 'jumpcloud-scim-v2' = 'JumpCloud v2.0',
9 | 'generic-scim-v2' = 'Generic SCIM v2.0',
10 | 'google' = 'Google',
11 | }
12 |
--------------------------------------------------------------------------------
/e2e/support/fixtures/index.ts:
--------------------------------------------------------------------------------
1 | export * from './portal';
2 | export * from './sso-page';
3 | export * from './dsync-page';
4 | export * from './setuplink-page';
5 | export * from './setuplink-ds-page';
6 |
--------------------------------------------------------------------------------
/e2e/support/nextAuth.constants.ts:
--------------------------------------------------------------------------------
1 | // const ONE_DAY_IN_SECONDS = 86400;
2 | // const EXPIRES = new Date(Date.now() + 1000000 * ONE_DAY_IN_SECONDS * 1000);
3 | export const EXPIRES = new Date(88043913172731); /* Sat Jan 02 4760 00:02:52 GMT+0530 (India Standard Time) */
4 | // const TOKEN = randomBytes(32).toString('hex');
5 | export const TOKEN = '94b773327570de9bef4779a40529c467b5b5226f15b1d28b00e7fba629a0921b';
6 | export const IDENTIFIER = 'headless@boxyhq.com';
7 |
--------------------------------------------------------------------------------
/e2e/support/pretest.ts:
--------------------------------------------------------------------------------
1 | import { createHash } from 'crypto';
2 | import { initNextAuthDB } from '@lib/nextAuthAdapter';
3 | import { IDENTIFIER as identifier, EXPIRES as expires, TOKEN as token } from './nextAuth.constants';
4 |
5 | export function hashToken(token: string) {
6 | return createHash('sha256').update(`${token}${process.env.NEXTAUTH_SECRET}`).digest('hex');
7 | }
8 |
9 | async function seedAuthDb() {
10 | const store = await initNextAuthDB();
11 |
12 | const verificationToken = {
13 | identifier,
14 | expires,
15 | token: hashToken(token),
16 | };
17 |
18 | await (await store).put(verificationToken.identifier, verificationToken);
19 | }
20 |
21 | (async function setup() {
22 | await seedAuthDb();
23 | console.log(`seeding auth db ... COMPLETE`);
24 | process.exit(0);
25 | })();
26 |
--------------------------------------------------------------------------------
/ee/ENTERPRISE.md:
--------------------------------------------------------------------------------
1 | # Enterprise Edition
2 |
3 | Welcome to the Enterprise Edition ("/ee") of BoxyHQ.
4 |
5 | The [/ee](https://github.com/ory/polis/tree/main/ee) subfolder is the place for all the **Enterprise** features for this repository.
6 |
7 | > _❗ NOTE: This section is copyrighted (unlike the rest of our [repository](https://github.com/ory/polis)). You are not allowed to use this code without obtaining a proper [license](https://ory.sh/pricing) first.❗_
8 |
--------------------------------------------------------------------------------
/ee/LICENSE:
--------------------------------------------------------------------------------
1 | The BoxyHQ Enterprise Edition (EE) license (the “EE License”)
2 |
--------------------------------------------------------------------------------
/ee/branding/api/admin/index.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from 'next';
2 |
3 | import jackson from '@lib/jackson';
4 | import { defaultHandler } from '@lib/api';
5 |
6 | const handler = async (req: NextApiRequest, res: NextApiResponse) => {
7 | await defaultHandler(req, res, {
8 | GET: handleGET,
9 | POST: handlePOST,
10 | });
11 | };
12 |
13 | const handlePOST = async (req: NextApiRequest, res: NextApiResponse) => {
14 | const { brandingController } = await jackson();
15 |
16 | const { logoUrl, faviconUrl, companyName, primaryColor } = req.body;
17 |
18 | res.json({
19 | data: await brandingController.update({ logoUrl, faviconUrl, companyName, primaryColor }),
20 | });
21 | };
22 |
23 | const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
24 | const { brandingController } = await jackson();
25 |
26 | res.json({ data: await brandingController.get() });
27 | };
28 |
29 | export default handler;
30 |
--------------------------------------------------------------------------------
/ee/identity-federation/api/v1/product.ts:
--------------------------------------------------------------------------------
1 | import { NextApiRequest, NextApiResponse } from 'next';
2 |
3 | import jackson from '@lib/jackson';
4 | import { parsePaginateApiParams } from '@lib/utils';
5 | import { defaultHandler } from '@lib/api';
6 |
7 | export default async function handler(req: NextApiRequest, res: NextApiResponse) {
8 | await defaultHandler(req, res, {
9 | GET: handleGET,
10 | });
11 | }
12 |
13 | // Get SAML federated apps filtered by the product
14 | const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
15 | const { identityFederationController } = await jackson();
16 |
17 | const { product } = req.query as {
18 | product: string;
19 | };
20 |
21 | const { pageOffset, pageLimit, pageToken } = parsePaginateApiParams(req.query);
22 |
23 | const apps = await identityFederationController.app.getByProduct({
24 | product,
25 | pageOffset,
26 | pageLimit,
27 | pageToken,
28 | });
29 |
30 | res.json(apps);
31 | };
32 |
--------------------------------------------------------------------------------
/ee/identity-federation/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import router from 'next/router';
2 | import LicenseRequired from '@components/LicenseRequired';
3 | import { IdentityFederationApps } from '@boxyhq/internal-ui';
4 |
5 | const AppsList = ({ hasValidLicense }: { hasValidLicense: boolean }) => {
6 | if (!hasValidLicense) {
7 | return ;
8 | }
9 |
10 | return (
11 | router.push(`/admin/identity-federation/${app.id}/edit`)}
14 | actions={{
15 | newApp: '/admin/identity-federation/new',
16 | samlConfiguration: '/.well-known/idp-configuration',
17 | oidcConfiguration: '/.well-known/openid-configuration',
18 | }}
19 | />
20 | );
21 | };
22 |
23 | export default AppsList;
24 |
--------------------------------------------------------------------------------
/ee/product/api/index.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from 'next';
2 |
3 | import jackson from '@lib/jackson';
4 |
5 | const handler = async (req: NextApiRequest, res: NextApiResponse) => {
6 | try {
7 | switch (req.method) {
8 | case 'PATCH':
9 | await handlePATCH(req, res);
10 | break;
11 | default:
12 | res.setHeader('Allow', 'POST');
13 | res.status(405).json({ error: { message: `Method ${req.method} Not Allowed` } });
14 | }
15 | } catch (err: any) {
16 | const { message, statusCode = 500 } = err;
17 | res.status(statusCode).json({ error: { message } });
18 | }
19 | };
20 |
21 | // Update product configuration
22 | const handlePATCH = async (req: NextApiRequest, res: NextApiResponse) => {
23 | const { productController } = await jackson();
24 |
25 | await productController.upsert(req.body);
26 |
27 | res.end();
28 | };
29 |
30 | export default handler;
31 |
--------------------------------------------------------------------------------
/find-dupe-locale.js:
--------------------------------------------------------------------------------
1 | const allValues = {};
2 |
3 | const localeFile = require('./locales/en/common.json');
4 |
5 | for (const [key, value] of Object.entries(localeFile)) {
6 | const arr = allValues[value] || [];
7 | allValues[value] = arr.concat(key);
8 | }
9 |
10 | const dupValues = {};
11 |
12 | for (const [key, value] of Object.entries(allValues)) {
13 | if (value.length > 1) {
14 | dupValues[key] = value;
15 | }
16 | }
17 |
18 | if (Object.keys(dupValues).length) {
19 | console.error(`Duplicate values found in locale file: ${Object.keys(dupValues).length}`);
20 | for (const [key, value] of Object.entries(dupValues)) {
21 | console.error(`${value}: ${key}`);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/helm/jackson/.helmignore:
--------------------------------------------------------------------------------
1 | # Patterns to ignore when building packages.
2 | # This supports shell glob matching, relative path matching, and
3 | # negation (prefixed with !). Only one pattern per line.
4 | .DS_Store
5 | # Common VCS dirs
6 | .git/
7 | .gitignore
8 | .bzr/
9 | .bzrignore
10 | .hg/
11 | .hgignore
12 | .svn/
13 | # Common backup files
14 | *.swp
15 | *.bak
16 | *.tmp
17 | *.orig
18 | *~
19 | # Various IDEs
20 | .project
21 | .idea/
22 | *.tmproj
23 | .vscode/
24 |
--------------------------------------------------------------------------------
/helm/jackson/values.yaml:
--------------------------------------------------------------------------------
1 | internal:
2 | ports:
3 | - name: original
4 | port: 5225
5 | targetPort: 5225
6 | type: ClusterIP
7 | jackson:
8 | dbManualMigration: "true"
9 | dbUrl: postgres://postgres:password@jackson-postgres:5432/postgres
10 | jackson:
11 | image:
12 | repository: boxyhq/jackson
13 | tag: 1.28.2
14 | imagePullPolicy: IfNotPresent
15 | resources:
16 | limits:
17 | cpu: 500m
18 | requests:
19 | cpu: 100m
20 | jacksonApiKeys: secret
21 | nextauthSecret: secret
22 | nextauthUrl: http://localhost:5225
23 | ports:
24 | - name: original
25 | port: 5225
26 | targetPort: 5225
27 | replicas: 1
28 | retracedAdminRootToken: dev
29 | retracedExternalUrl: http://localhost:3000/auditlog
30 | retracedHostUrl: http://jackson-api:3000/auditlog
31 | type: LoadBalancer
32 | kubernetesClusterDomain: cluster.local
33 | migratePg:
34 | db:
35 | image:
36 | repository: boxyhq/jackson
37 | tag: 1.28.2
38 | imagePullPolicy: IfNotPresent
39 |
40 |
--------------------------------------------------------------------------------
/i18next.d.ts:
--------------------------------------------------------------------------------
1 | // i18next.d.ts
2 | import 'i18next';
3 |
4 | declare module 'i18next' {
5 | interface CustomTypeOptions {
6 | returnNull: false;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/internal-ui/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/internal-ui/README.md:
--------------------------------------------------------------------------------
1 | # Shared React UI components for BoxyHQ SaaS and Admin Portal
2 |
--------------------------------------------------------------------------------
/internal-ui/dev/verdaccio.sh:
--------------------------------------------------------------------------------
1 | #!/bin/zsh -ex
2 |
3 | # TODO: Make this generic so everyone can run it
4 |
5 | VERSION=0.0.0
6 |
7 | # Unpublish the current version
8 | npm unpublish --registry http://localhost:4873/ @boxyhq/internal-ui@$VERSION
9 |
10 | # Build the package
11 | rm -rf dist
12 | npm run build
13 |
14 | # Publish
15 | npm publish --registry http://localhost:4873/
16 |
17 | # Install the published version in `boxyhq/jackson`
18 | # cd ../../jackson
19 | # npm uninstall @boxyhq/internal-ui
20 | # npm i --save-exact --registry http://localhost:4873/ @boxyhq/internal-ui@$VERSION
21 | # rm -rf .next
22 |
23 | # Install the published version in `boxyhq/saas-app`
24 | cd ../../saas-app
25 | npm uninstall @boxyhq/internal-ui
26 | npm i --save-exact --registry http://localhost:4873/ @boxyhq/internal-ui@$VERSION --force
27 | rm -rf .next
--------------------------------------------------------------------------------
/internal-ui/postcss.config.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line import/no-anonymous-default-export
2 | export default {
3 | plugins: {},
4 | };
5 |
--------------------------------------------------------------------------------
/internal-ui/src/branding/index.ts:
--------------------------------------------------------------------------------
1 | export { BrandingForm } from './BrandingForm';
2 |
3 | // BoxyHQ branding
4 | export const boxyhqBranding = {
5 | logoUrl: '/logo.png',
6 | faviconUrl: '/favicon.ico',
7 | companyName: 'BoxyHQ',
8 | primaryColor: '#25c2a0',
9 | };
10 |
--------------------------------------------------------------------------------
/internal-ui/src/dsync/index.ts:
--------------------------------------------------------------------------------
1 | export { DirectoryUsers } from './DirectoryUsers';
2 | export { DirectoryUserInfo } from './DirectoryUserInfo';
3 | export { DirectoryGroups } from './DirectoryGroups';
4 | export { DirectoryGroupInfo } from './DirectoryGroupInfo';
5 | export { DirectoryWebhookLogs } from './DirectoryWebhookLogs';
6 | export { DirectoryWebhookLogInfo } from './DirectoryWebhookLogInfo';
7 | export { DirectoryTab } from './DirectoryTab';
8 | export { DirectoryInfo } from './DirectoryInfo';
9 |
--------------------------------------------------------------------------------
/internal-ui/src/hooks/index.ts:
--------------------------------------------------------------------------------
1 | export { usePaginate } from './usePaginate';
2 | export { useDirectory } from './useDirectory';
3 | export { useRouter } from './useRouter';
4 | export { useFetch, parseResponseContent } from './useFetch';
5 |
--------------------------------------------------------------------------------
/internal-ui/src/hooks/useDirectory.tsx:
--------------------------------------------------------------------------------
1 | import useSWR from 'swr';
2 | import { fetcher } from '../utils';
3 | import type { Directory } from '../types';
4 |
5 | export const useDirectory = (getDirectoryUrl: string) => {
6 | const { data, error, isLoading } = useSWR(getDirectoryUrl, fetcher);
7 |
8 | return {
9 | directory: data?.data as Directory & { google_authorized?: boolean },
10 | isLoadingDirectory: isLoading,
11 | directoryError: error,
12 | };
13 | };
14 |
--------------------------------------------------------------------------------
/internal-ui/src/hooks/usePaginate.tsx:
--------------------------------------------------------------------------------
1 | import type { NextRouter } from 'next/router';
2 | import { useState, useEffect } from 'react';
3 |
4 | // TODO:
5 | // https://nextjs.org/docs/messages/next-router-not-mounted
6 | // Accepting router is a temp workaround to handle Router not mounted error
7 |
8 | export const usePaginate = (router: NextRouter) => {
9 | const offset = router.query.offset ? Number(router.query.offset) : 0;
10 |
11 | const [paginate, setPaginate] = useState({ offset });
12 | const [pageTokenMap, setPageTokenMap] = useState({});
13 |
14 | useEffect(() => {
15 | // Prevent pushing the same URL to the history
16 | if (offset === paginate.offset) {
17 | return;
18 | }
19 |
20 | const path = router.asPath.split('?')[0];
21 |
22 | router.push(`${path}?offset=${paginate.offset}`, undefined, { shallow: true });
23 | // eslint-disable-next-line react-hooks/exhaustive-deps
24 | }, [paginate]);
25 |
26 | useEffect(() => {
27 | setPaginate({ offset });
28 | }, [offset]);
29 |
30 | return {
31 | paginate,
32 | setPaginate,
33 | pageTokenMap,
34 | setPageTokenMap,
35 | };
36 | };
37 |
--------------------------------------------------------------------------------
/internal-ui/src/hooks/useRouter.tsx:
--------------------------------------------------------------------------------
1 | import { useContext } from 'react';
2 | import { BUIContext } from '../provider';
3 |
4 | export const useRouter = () => {
5 | const { router } = useContext(BUIContext);
6 | return { router };
7 | };
8 |
--------------------------------------------------------------------------------
/internal-ui/src/identity-federation/index.ts:
--------------------------------------------------------------------------------
1 | export { NewIdentityFederationApp } from './NewIdentityFederationApp';
2 | export { EditIdentityFederationApp } from './EditIdentityFederationApp';
3 | export { IdentityFederationApps } from './IdentityFederationApps';
4 |
--------------------------------------------------------------------------------
/internal-ui/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './well-known';
2 | export * from './identity-federation';
3 | export * from './shared';
4 | export * from './dsync';
5 | export * from './provider';
6 | export * from './sso-traces';
7 | export * from './setup-link';
8 | export * from './branding';
9 |
--------------------------------------------------------------------------------
/internal-ui/src/provider.tsx:
--------------------------------------------------------------------------------
1 | import { createContext } from 'react';
2 | import type { NextRouter } from 'next/router';
3 |
4 | export const BUIContext = createContext<{ router: NextRouter | null }>({ router: null });
5 |
6 | export const BUIProvider = BUIContext.Provider;
7 |
--------------------------------------------------------------------------------
/internal-ui/src/setup-link/SetupLinkInfo.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from 'react-daisyui';
2 | import { useTranslation } from 'next-i18next';
3 |
4 | import { Card } from '../shared';
5 | import type { SetupLink } from '../types';
6 | import { InputWithCopyButton } from '../shared/InputWithCopyButton';
7 |
8 | export const SetupLinkInfo = ({ setupLink, onClose }: { setupLink: SetupLink; onClose: () => void }) => {
9 | const { t } = useTranslation('common');
10 |
11 | return (
12 | <>
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | {t('bui-shared-close')}
21 |
22 |
23 |
24 |
25 | >
26 | );
27 | };
28 |
--------------------------------------------------------------------------------
/internal-ui/src/setup-link/index.ts:
--------------------------------------------------------------------------------
1 | export { NewSetupLink } from './NewSetupLink';
2 | export { SetupLinkInfo } from './SetupLinkInfo';
3 | export { SetupLinks } from './SetupLinks';
4 |
--------------------------------------------------------------------------------
/internal-ui/src/shared/Alert.tsx:
--------------------------------------------------------------------------------
1 | type AlertType = 'error' | 'success' | 'warning';
2 |
3 | const variants = {
4 | error: 'alert-error',
5 | success: 'alert-success',
6 | warning: 'alert-warning',
7 | } as const;
8 |
9 | export const Alert = ({
10 | variant,
11 | children,
12 | className,
13 | }: {
14 | variant: AlertType;
15 | children: React.ReactNode;
16 | className?: string;
17 | }) => {
18 | return (
19 |
20 | {children}
21 |
22 | );
23 | };
24 |
--------------------------------------------------------------------------------
/internal-ui/src/shared/Badge.tsx:
--------------------------------------------------------------------------------
1 | import classNames from 'classnames';
2 | import { BadgeProps, Badge as BaseBadge } from 'react-daisyui';
3 |
4 | export const Badge = ({ children, className, ...props }: BadgeProps) => {
5 | return (
6 | <>
7 |
8 | {children}
9 |
10 | >
11 | );
12 | };
13 |
--------------------------------------------------------------------------------
/internal-ui/src/shared/ButtonBase.tsx:
--------------------------------------------------------------------------------
1 | import classNames from 'classnames';
2 | import { Button, type ButtonProps } from 'react-daisyui';
3 |
4 | export interface ButtonBaseProps extends ButtonProps {
5 | Icon?: any;
6 | }
7 |
8 | export const ButtonBase = ({ Icon, children, ...others }: ButtonBaseProps) => {
9 | return (
10 |
11 | {Icon && }
12 | {children}
13 |
14 | );
15 | };
16 |
--------------------------------------------------------------------------------
/internal-ui/src/shared/ButtonDanger.tsx:
--------------------------------------------------------------------------------
1 | import { ButtonBase, type ButtonBaseProps } from './ButtonBase';
2 |
3 | export const ButtonDanger = ({ children, ...other }: ButtonBaseProps) => {
4 | return (
5 |
6 | {children}
7 |
8 | );
9 | };
10 |
--------------------------------------------------------------------------------
/internal-ui/src/shared/ButtonOutline.tsx:
--------------------------------------------------------------------------------
1 | import { ButtonBase, type ButtonBaseProps } from './ButtonBase';
2 |
3 | export const ButtonOutline = ({ children, ...other }: ButtonBaseProps) => {
4 | return (
5 |
6 | {children}
7 |
8 | );
9 | };
10 |
--------------------------------------------------------------------------------
/internal-ui/src/shared/ButtonPrimary.tsx:
--------------------------------------------------------------------------------
1 | import { ButtonBase, type ButtonBaseProps } from './ButtonBase';
2 |
3 | export const ButtonPrimary = ({ children, ...other }: ButtonBaseProps) => {
4 | return (
5 |
6 | {children}
7 |
8 | );
9 | };
10 |
--------------------------------------------------------------------------------
/internal-ui/src/shared/DeleteCard.tsx:
--------------------------------------------------------------------------------
1 | import { Card, Button } from 'react-daisyui';
2 |
3 | export const DeleteCard = ({
4 | title,
5 | description,
6 | buttonLabel,
7 | onClick,
8 | }: {
9 | title: string;
10 | description: string;
11 | buttonLabel: string;
12 | onClick: () => void;
13 | }) => {
14 | return (
15 |
16 |
17 |
18 |
19 |
{title}
20 |
{description}
21 |
22 |
23 |
24 | {buttonLabel}
25 |
26 |
27 |
28 |
29 |
30 | );
31 | };
32 |
--------------------------------------------------------------------------------
/internal-ui/src/shared/EmptyState.tsx:
--------------------------------------------------------------------------------
1 | import InformationCircleIcon from '@heroicons/react/24/outline/InformationCircleIcon';
2 |
3 | export const EmptyState = ({ title, description }: { title: string; description?: string | null }) => {
4 | return (
5 |
6 |
7 |
{title}
8 | {description &&
{description}
}
9 |
10 | );
11 | };
12 |
--------------------------------------------------------------------------------
/internal-ui/src/shared/Error.tsx:
--------------------------------------------------------------------------------
1 | import { Card } from 'react-daisyui';
2 |
3 | export const Error = ({ message }: { message: string }) => {
4 | return (
5 |
6 |
7 | {message}
8 |
9 |
10 | );
11 | };
12 |
--------------------------------------------------------------------------------
/internal-ui/src/shared/IconButton.tsx:
--------------------------------------------------------------------------------
1 | import classNames from 'classnames';
2 |
3 | export const IconButton = ({ Icon, tooltip, onClick, className, ...other }) => {
4 | return (
5 |
6 |
7 |
8 |
9 |
10 | );
11 | };
12 |
--------------------------------------------------------------------------------
/internal-ui/src/shared/LinkBack.tsx:
--------------------------------------------------------------------------------
1 | import ArrowLeftIcon from '@heroicons/react/24/outline/ArrowLeftIcon';
2 | import { useTranslation } from 'next-i18next';
3 | import { ButtonOutline } from './ButtonOutline';
4 | import { LinkOutline } from './LinkOutline';
5 |
6 | export const LinkBack = ({
7 | href,
8 | onClick,
9 | className,
10 | }: {
11 | href?: string;
12 | onClick?: () => void;
13 | className?: string;
14 | }) => {
15 | const { t } = useTranslation('common');
16 |
17 | if (href) {
18 | return (
19 |
20 | {t('back')}
21 |
22 | );
23 | }
24 |
25 | if (onClick) {
26 | return (
27 |
28 | {t('back')}
29 |
30 | );
31 | }
32 |
33 | return null;
34 | };
35 |
--------------------------------------------------------------------------------
/internal-ui/src/shared/LinkBase.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 | import classNames from 'classnames';
3 | import type { LinkProps } from 'react-daisyui';
4 |
5 | export interface LinkBaseProps extends LinkProps {
6 | href: string;
7 | Icon?: any;
8 | }
9 |
10 | export const LinkBase = ({ children, href, className, Icon, ...others }: LinkBaseProps) => {
11 | return (
12 |
13 | {Icon && }
14 | {children}
15 |
16 | );
17 | };
18 |
--------------------------------------------------------------------------------
/internal-ui/src/shared/LinkOutline.tsx:
--------------------------------------------------------------------------------
1 | import classNames from 'classnames';
2 | import { LinkBase, type LinkBaseProps } from './LinkBase';
3 |
4 | export const LinkOutline = ({ children, className, ...others }: LinkBaseProps) => {
5 | return (
6 |
7 | {children}
8 |
9 | );
10 | };
11 |
--------------------------------------------------------------------------------
/internal-ui/src/shared/LinkPrimary.tsx:
--------------------------------------------------------------------------------
1 | import classNames from 'classnames';
2 | import { LinkBase, type LinkBaseProps } from './LinkBase';
3 |
4 | export const LinkPrimary = ({ children, className, ...others }: LinkBaseProps) => {
5 | return (
6 |
7 | {children}
8 |
9 | );
10 | };
11 |
--------------------------------------------------------------------------------
/internal-ui/src/shared/Modal.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useState, useEffect } from 'react';
3 |
4 | export const Modal = ({
5 | visible,
6 | title,
7 | description,
8 | children,
9 | }: {
10 | visible: boolean;
11 | title: string;
12 | description?: string;
13 | children?: React.ReactNode;
14 | }) => {
15 | const [open, setOpen] = useState(visible ? visible : false);
16 |
17 | useEffect(() => {
18 | setOpen(visible);
19 | }, [visible]);
20 |
21 | return (
22 |
23 |
24 |
25 |
{title}
26 | {description &&
{description}
}
27 |
{children}
28 |
29 |
30 |
31 | );
32 | };
33 |
--------------------------------------------------------------------------------
/internal-ui/src/shared/PageHeader.tsx:
--------------------------------------------------------------------------------
1 | export const PageHeader = ({
2 | title,
3 | description,
4 | actions,
5 | className,
6 | }: {
7 | title: string;
8 | description?: string;
9 | actions?: any;
10 | className?: string;
11 | }) => {
12 | return (
13 |
14 |
15 |
{title}
16 | {actions &&
{actions}
}
17 |
18 | {description &&
{description}
}
19 |
20 | );
21 | };
22 |
--------------------------------------------------------------------------------
/internal-ui/src/shared/PrismLoader.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import Prism from 'prismjs';
3 | import 'prismjs/themes/prism-okaidia.css';
4 | import 'prismjs/components/prism-javascript';
5 | import 'prismjs/components/prism-typescript';
6 | // import 'prismjs/components/prism-shell';
7 | import 'prismjs/components/prism-json';
8 | // import 'prismjs/components/prism-xml';
9 |
10 | export function PrismLoader() {
11 | useEffect(() => {
12 | Prism.highlightAll();
13 | }, []);
14 | return
;
15 | }
16 |
--------------------------------------------------------------------------------
/internal-ui/src/shared/index.ts:
--------------------------------------------------------------------------------
1 | export { Loading } from './Loading';
2 | export { DeleteCard } from './DeleteCard';
3 | export { Card } from './Card';
4 | export { Table } from './Table';
5 | export { EmptyState } from './EmptyState';
6 | export { Error } from './Error';
7 | export { Badge } from './Badge';
8 | export { Pagination } from './Pagination';
9 | export { Modal } from './Modal';
10 | export { ConfirmationModal } from './ConfirmationModal';
11 | export { PageHeader } from './PageHeader';
12 | export { LinkOutline } from './LinkOutline';
13 | export { LinkPrimary } from './LinkPrimary';
14 | export { LinkBack } from './LinkBack';
15 | export { pageLimit } from './Pagination';
16 | export { ButtonBase } from './ButtonBase';
17 | export { ButtonPrimary } from './ButtonPrimary';
18 | export { ButtonDanger } from './ButtonDanger';
19 | export { ButtonOutline } from './ButtonOutline';
20 | export { Alert } from './Alert';
21 | export { InputWithCopyButton, CopyToClipboardButton } from './InputWithCopyButton';
22 | export { IconButton } from './IconButton';
23 | export { PrismLoader } from './PrismLoader';
24 |
--------------------------------------------------------------------------------
/internal-ui/src/sso-traces/index.ts:
--------------------------------------------------------------------------------
1 | export { SSOTraces } from './SSOTraces';
2 | export { SSOTraceInfo } from './SSOTraceInfo';
3 |
--------------------------------------------------------------------------------
/internal-ui/src/well-known/index.ts:
--------------------------------------------------------------------------------
1 | export { WellKnownURLs } from './WellKnownURLs';
2 |
--------------------------------------------------------------------------------
/internal-ui/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es6",
4 | "baseUrl": ".",
5 | "jsx": "react-jsx",
6 | "moduleResolution": "Node",
7 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
8 | "types": ["vite/client"],
9 | "esModuleInterop": true,
10 | "strict": true,
11 | "noImplicitAny": false,
12 | "declaration": true,
13 | "noUnusedParameters": true,
14 | "strictNullChecks": true,
15 | "experimentalDecorators": true,
16 | "paths": {
17 | "react": ["./node_modules/@types/react"],
18 | },
19 | },
20 | "include": ["./src/**/*"],
21 | "exclude": ["dist", "build", "node_modules"],
22 | }
23 |
--------------------------------------------------------------------------------
/internal-ui/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/internal-ui/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { resolve } from 'path';
2 | import { defineConfig } from 'vite';
3 | import react from '@vitejs/plugin-react';
4 | import viteTsconfigPaths from 'vite-tsconfig-paths';
5 | import typescript from '@rollup/plugin-typescript';
6 |
7 | export default defineConfig({
8 | build: {
9 | lib: {
10 | entry: resolve(__dirname, 'src/index.ts'),
11 | fileName: 'index',
12 | formats: ['es'],
13 | },
14 | rollupOptions: {
15 | external: [
16 | 'react',
17 | 'react/jsx-runtime',
18 | 'react-dom',
19 | 'next',
20 | 'next-i18next',
21 | 'swr',
22 | /@heroicons\/react\/.*/,
23 | 'classnames',
24 | 'formik',
25 | 'react-daisyui',
26 | 'react-tagsinput',
27 | '@boxyhq/react-ui',
28 | ],
29 | },
30 | },
31 | plugins: [
32 | react(),
33 | viteTsconfigPaths(),
34 | typescript({
35 | declaration: true,
36 | emitDeclarationOnly: true,
37 | noForceEmit: true,
38 | declarationDir: resolve(__dirname, 'dist/'),
39 | rootDir: resolve(__dirname, 'src'),
40 | }),
41 | ],
42 | });
43 |
--------------------------------------------------------------------------------
/kustomize/base/kustomization.yaml:
--------------------------------------------------------------------------------
1 | resources:
2 | - ./jackson-deployment.yaml
3 |
4 | namespace: default
5 |
--------------------------------------------------------------------------------
/kustomize/base/migration/kustomization.yaml:
--------------------------------------------------------------------------------
1 | resources:
2 | - ./migratepg-job.yaml
3 |
4 | namespace: default
5 |
--------------------------------------------------------------------------------
/kustomize/base/migration/migratepg-job.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: batch/v1
2 | kind: Job
3 | metadata:
4 | name: jackson-migrate-pg
5 | spec:
6 | ttlSecondsAfterFinished: 0
7 | template:
8 | spec:
9 | restartPolicy: 'OnFailure'
10 | containers:
11 | - name: db
12 | image: boxyhq/jackson-local
13 | imagePullPolicy: IfNotPresent
14 | command:
15 | - /bin/sh
16 | - migrate.sh
17 | envFrom:
18 | - secretRef:
19 | name: jackson
20 |
--------------------------------------------------------------------------------
/kustomize/base/namespace.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Namespace
3 | metadata:
4 | name: '{{repl ConfigOption "namespace"}}'
5 | labels:
6 | app: jackson
7 |
--------------------------------------------------------------------------------
/kustomize/base/services/internal/jackson-internal-service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: jackson-internal
5 | namespace: '{{repl ConfigOption "namespace"}}'
6 | labels:
7 | app: jackson-internal
8 | tier: jackson-internal
9 | spec:
10 | ports:
11 | - name: original
12 | port: 5225
13 | targetPort: 5225
14 | selector:
15 | app: jackson
16 | tier: jackson
17 |
--------------------------------------------------------------------------------
/kustomize/base/services/internal/kustomization.yaml:
--------------------------------------------------------------------------------
1 | resources:
2 | - ./jackson-internal-service.yaml
3 |
4 | namespace: default
5 |
--------------------------------------------------------------------------------
/kustomize/base/services/jackson-service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: jackson
5 | namespace: '{{repl ConfigOption "namespace"}}'
6 | labels:
7 | app: jackson
8 | tier: jackson
9 | spec:
10 | type: LoadBalancer
11 | ports:
12 | - name: original
13 | port: 5225
14 | targetPort: 5225
15 | selector:
16 | app: jackson
17 | tier: jackson
18 |
--------------------------------------------------------------------------------
/kustomize/base/services/kustomization.yaml:
--------------------------------------------------------------------------------
1 | resources:
2 | - ./internal
3 | - ./jackson-service.yaml
4 |
5 | namespace: default
6 |
--------------------------------------------------------------------------------
/kustomize/overlays/dbs/dynamodb/kustomization.yaml:
--------------------------------------------------------------------------------
1 | resources:
2 | - ./dynamodb.yaml
3 |
--------------------------------------------------------------------------------
/kustomize/overlays/dbs/mariadb/kustomization.yaml:
--------------------------------------------------------------------------------
1 | resources:
2 | - ./mariadb.yaml
3 |
--------------------------------------------------------------------------------
/kustomize/overlays/dbs/mongo/kustomization.yaml:
--------------------------------------------------------------------------------
1 | resources:
2 | - ./mongo.yaml
3 |
--------------------------------------------------------------------------------
/kustomize/overlays/dbs/mssql/kustomization.yaml:
--------------------------------------------------------------------------------
1 | resources:
2 | - ./mssql.yaml
3 |
--------------------------------------------------------------------------------
/kustomize/overlays/dbs/mysql/kustomization.yaml:
--------------------------------------------------------------------------------
1 | resources:
2 | - ./mysql.yaml
3 |
--------------------------------------------------------------------------------
/kustomize/overlays/dbs/postgres/kustomization.yaml:
--------------------------------------------------------------------------------
1 | resources:
2 | - ./postgres.yaml
3 |
--------------------------------------------------------------------------------
/kustomize/overlays/demo/.gitignore:
--------------------------------------------------------------------------------
1 | **/*-secrets.yaml
2 | **/secrets.yaml
--------------------------------------------------------------------------------
/kustomize/overlays/demo/jackson-deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: jackson
5 | spec:
6 | replicas: 1
7 | template:
8 | spec:
9 | containers:
10 | - name: jackson
11 | image: boxyhq/jackson:tagwillbereplaced
12 | imagePullPolicy: IfNotPresent
13 |
--------------------------------------------------------------------------------
/kustomize/overlays/demo/kustomization.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | resources:
3 | - ../../base
4 | - ../../base/migration
5 | - ./secrets.yaml
6 |
7 | patches:
8 | - target:
9 | group: apps
10 | version: v1
11 | kind: Deployment
12 | name: jackson
13 | path: ./jackson-deployment.yaml
14 | - target:
15 | group: batch
16 | version: v1
17 | kind: Job
18 | name: jackson-migrate-pg
19 | path: ./migratepg-job.yaml
20 |
21 | images:
22 | - name: boxyhq/jackson
23 | newTag: 1.44.0
24 |
--------------------------------------------------------------------------------
/kustomize/overlays/demo/migratepg-job.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: batch/v1
2 | kind: Job
3 | metadata:
4 | name: jackson-migrate-pg
5 | spec:
6 | template:
7 | spec:
8 | containers:
9 | - name: db
10 | image: boxyhq/jackson:tagwillbereplaced
11 | imagePullPolicy: IfNotPresent
12 |
--------------------------------------------------------------------------------
/kustomize/overlays/demo/mocksaml/kustomization.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | resources:
3 | - ./mocksaml-secrets.yaml
4 | - ./mocksaml-deployment.yaml
5 |
6 | images:
7 | - name: boxyhq/mock-saml
8 | newTag: 1.4.1
9 |
--------------------------------------------------------------------------------
/kustomize/overlays/demo/services/jackson-service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: jackson
5 | labels:
6 | app: jackson
7 | tier: jackson
8 | annotations:
9 | service.beta.kubernetes.io/aws-load-balancer-type: nlb
10 | service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
11 | service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
12 | service.beta.kubernetes.io/aws-load-balancer-ssl-cert: 'arn:aws:acm:us-west-1:511214097407:certificate/3cd477cb-f54b-4701-89f1-9416216cb1c9'
13 | service.beta.kubernetes.io/aws-load-balancer-ssl-ports: '443'
14 | service.beta.kubernetes.io/aws-load-balancer-ssl-negotiation-policy: ELBSecurityPolicy-TLS13-1-2-2021-06
15 | spec:
16 | externalTrafficPolicy: Cluster
17 | type: LoadBalancer
18 | ports:
19 | - name: https
20 | port: 443
21 | targetPort: 5225
22 | selector:
23 | app: jackson
24 | tier: jackson
25 |
--------------------------------------------------------------------------------
/kustomize/overlays/demo/services/kustomization.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | resources:
3 | - ../../../base/services/internal
4 | - ./jackson-service.yaml
5 |
--------------------------------------------------------------------------------
/kustomize/overlays/demo/services/mocksaml/kustomization.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | resources:
3 | - ./mocksaml-service.yaml
4 |
--------------------------------------------------------------------------------
/kustomize/overlays/demo/services/mocksaml/mocksaml-service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: mocksaml
5 | labels:
6 | app: mocksaml
7 | tier: mocksaml
8 | annotations:
9 | service.beta.kubernetes.io/aws-load-balancer-type: nlb
10 | service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
11 | service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
12 | service.beta.kubernetes.io/aws-load-balancer-ssl-cert: 'arn:aws:acm:us-west-1:511214097407:certificate/10249968-7517-424c-a7c7-cd66b4310752'
13 | service.beta.kubernetes.io/aws-load-balancer-ssl-ports: '443'
14 | service.beta.kubernetes.io/aws-load-balancer-ssl-negotiation-policy: ELBSecurityPolicy-TLS13-1-2-2021-06
15 | spec:
16 | externalTrafficPolicy: Cluster
17 | type: LoadBalancer
18 | ports:
19 | - name: https
20 | port: 443
21 | targetPort: 4000
22 | selector:
23 | app: mocksaml
24 | tier: mocksaml
25 |
--------------------------------------------------------------------------------
/kustomize/overlays/dynamodb/kustomization.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | resources:
3 | - ../../base
4 | - ../../base/services
5 | - ../dbs/dynamodb
6 | - ./secrets.yaml
7 |
8 | commonLabels:
9 | jacksondev: '1'
10 |
11 | images:
12 | - name: boxyhq/jackson
13 | newTag: 1.27.1
14 |
--------------------------------------------------------------------------------
/kustomize/overlays/mariadb/kustomization.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | resources:
3 | - ../../base
4 | - ../../base/services
5 | - ../dbs/mariadb
6 | - ./secrets.yaml
7 |
8 | commonLabels:
9 | jacksondev: '1'
10 |
11 | images:
12 | - name: boxyhq/jackson
13 | newTag: 1.27.1
14 |
--------------------------------------------------------------------------------
/kustomize/overlays/mongo/kustomization.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | resources:
3 | - ../../base
4 | - ../../base/services
5 | - ../dbs/mongo
6 | - ./secrets.yaml
7 |
8 | commonLabels:
9 | jacksondev: '1'
10 |
11 | images:
12 | - name: boxyhq/jackson
13 | newTag: 1.27.1
14 |
--------------------------------------------------------------------------------
/kustomize/overlays/mssql/kustomization.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | resources:
3 | - ../../base
4 | - ../../base/services
5 | - ../dbs/mssql
6 | - ./secrets.yaml
7 |
8 | commonLabels:
9 | jacksondev: '1'
10 |
11 | images:
12 | - name: boxyhq/jackson
13 | newTag: 1.27.1
14 |
--------------------------------------------------------------------------------
/kustomize/overlays/mysql/kustomization.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | resources:
3 | - ../../base
4 | - ../../base/services
5 | - ../dbs/mysql
6 | - ./secrets.yaml
7 |
8 | commonLabels:
9 | jacksondev: '1'
10 |
11 | images:
12 | - name: boxyhq/jackson
13 | newTag: 1.27.1
14 |
--------------------------------------------------------------------------------
/kustomize/overlays/postgres/kustomization.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | resources:
3 | - ../../base
4 | - ../../base/services
5 | - ../dbs/postgres
6 | - ../../base/migration
7 | - ./secrets.yaml
8 |
9 | commonLabels:
10 | jacksondev: '1'
11 |
12 | images:
13 | - name: boxyhq/jackson
14 | newTag: 1.27.1
15 |
--------------------------------------------------------------------------------
/kustomize/overlays/postgres/migratepg-job.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: batch/v1
2 | kind: Job
3 | metadata:
4 | name: jackson-migrate-pg
5 | spec:
6 | template:
7 | spec:
8 | restartPolicy: 'OnFailure'
9 | containers:
10 | - name: db
11 | image: boxyhq/jackson-local
12 | imagePullPolicy: IfNotPresent
13 | command:
14 | - /bin/sh
15 | - migrate.sh
16 | envFrom:
17 | - secretRef:
18 | name: jackson
19 |
--------------------------------------------------------------------------------
/kustomize/overlays/prod-eu/.gitignore:
--------------------------------------------------------------------------------
1 | **/*-secrets.yaml
2 | **/secrets.yaml
--------------------------------------------------------------------------------
/kustomize/overlays/prod-eu/jackson-deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: jackson
5 | spec:
6 | replicas: 1
7 | template:
8 | spec:
9 | containers:
10 | - name: jackson
11 | image: boxyhq/jackson:tagwillbereplaced
12 | imagePullPolicy: IfNotPresent
13 |
--------------------------------------------------------------------------------
/kustomize/overlays/prod-eu/kustomization.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | resources:
3 | - ../../base
4 | - ../../base/migration
5 | - ./secrets.yaml
6 |
7 | patches:
8 | - target:
9 | group: apps
10 | version: v1
11 | kind: Deployment
12 | name: jackson
13 | path: ./jackson-deployment.yaml
14 | - target:
15 | group: batch
16 | version: v1
17 | kind: Job
18 | name: jackson-migrate-pg
19 | path: ./migratepg-job.yaml
20 |
21 | images:
22 | - name: boxyhq/jackson
23 | newTag: 1.44.0
24 |
--------------------------------------------------------------------------------
/kustomize/overlays/prod-eu/migratepg-job.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: batch/v1
2 | kind: Job
3 | metadata:
4 | name: jackson-migrate-pg
5 | spec:
6 | template:
7 | spec:
8 | containers:
9 | - name: db
10 | image: boxyhq/jackson:tagwillbereplaced
11 | imagePullPolicy: IfNotPresent
12 |
--------------------------------------------------------------------------------
/kustomize/overlays/prod-eu/mocksaml/kustomization.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | resources:
3 | - ./mocksaml-secrets.yaml
4 | - ./mocksaml-deployment.yaml
5 |
6 | images:
7 | - name: boxyhq/mock-saml
8 | newTag: 1.4.1
9 |
--------------------------------------------------------------------------------
/kustomize/overlays/prod-eu/services/jackson-service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: jackson
5 | labels:
6 | app: jackson
7 | tier: jackson
8 | annotations:
9 | service.beta.kubernetes.io/aws-load-balancer-type: nlb
10 | service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
11 | service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
12 | service.beta.kubernetes.io/aws-load-balancer-ssl-cert: 'arn:aws:acm:eu-central-1:511214097407:certificate/3fc4272a-d97c-4bf0-8f8a-3425fda27e31'
13 | service.beta.kubernetes.io/aws-load-balancer-ssl-ports: '443'
14 | service.beta.kubernetes.io/aws-load-balancer-ssl-negotiation-policy: ELBSecurityPolicy-TLS13-1-2-2021-06
15 | spec:
16 | externalTrafficPolicy: Cluster
17 | type: LoadBalancer
18 | ports:
19 | - name: https
20 | port: 443
21 | targetPort: 5225
22 | selector:
23 | app: jackson
24 | tier: jackson
25 |
--------------------------------------------------------------------------------
/kustomize/overlays/prod-eu/services/kustomization.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | resources:
3 | - ../../../base/services/internal
4 | - ./jackson-service.yaml
5 |
--------------------------------------------------------------------------------
/kustomize/overlays/prod-eu/services/mocksaml/kustomization.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | resources:
3 | - ./mocksaml-service.yaml
4 |
--------------------------------------------------------------------------------
/kustomize/overlays/prod-eu/services/mocksaml/mocksaml-service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: mocksaml
5 | labels:
6 | app: mocksaml
7 | tier: mocksaml
8 | annotations:
9 | service.beta.kubernetes.io/aws-load-balancer-type: nlb
10 | service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
11 | service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
12 | service.beta.kubernetes.io/aws-load-balancer-ssl-cert: 'arn:aws:acm:eu-central-1:511214097407:certificate/7eab394e-9a01-46c4-b941-82288a4479e9'
13 | service.beta.kubernetes.io/aws-load-balancer-ssl-ports: '443'
14 | service.beta.kubernetes.io/aws-load-balancer-ssl-negotiation-policy: ELBSecurityPolicy-TLS13-1-2-2021-06
15 | spec:
16 | externalTrafficPolicy: Cluster
17 | type: LoadBalancer
18 | ports:
19 | - name: https
20 | port: 443
21 | targetPort: 4000
22 | selector:
23 | app: mocksaml
24 | tier: mocksaml
25 |
--------------------------------------------------------------------------------
/lib/api/default.ts:
--------------------------------------------------------------------------------
1 | import { logger } from '@lib/logger';
2 | import { ApiError } from '../error';
3 | import type { NextApiRequest, NextApiResponse } from 'next';
4 |
5 | type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
6 |
7 | type Handlers = {
8 | [method in HTTPMethod]?: (req: NextApiRequest, res: NextApiResponse) => Promise;
9 | };
10 |
11 | export const defaultHandler = async (req: NextApiRequest, res: NextApiResponse, handlers: Handlers) => {
12 | try {
13 | // Get the handler for the request
14 | const handler = handlers[req.method as HTTPMethod];
15 | const allowedMethods = Object.keys(handlers).join(', ');
16 |
17 | if (!handler) {
18 | res.setHeader('Allow', allowedMethods);
19 | throw new ApiError(`Method ${req.method} not allowed.`, 405);
20 | }
21 |
22 | // Call the handler
23 | await handler(req, res);
24 | return;
25 | } catch (err: any) {
26 | const message = err.message || 'Internal Server Error';
27 | const status = err.statusCode || 500;
28 |
29 | logger.error(`${req.method} ${req.url} - ${status} - ${message}`);
30 |
31 | res.status(status).json({ error: { message } });
32 | }
33 | };
34 |
--------------------------------------------------------------------------------
/lib/api/index.ts:
--------------------------------------------------------------------------------
1 | export { defaultHandler } from './default';
2 |
--------------------------------------------------------------------------------
/lib/api/utils.ts:
--------------------------------------------------------------------------------
1 | export function normalizeBooleanParam(param) {
2 | if (param === 'false' || param === false) {
3 | return false;
4 | }
5 |
6 | return true;
7 | }
8 |
--------------------------------------------------------------------------------
/lib/auth.ts:
--------------------------------------------------------------------------------
1 | import { apiKeys } from '@lib/env';
2 |
3 | export const validateApiKey = (token: string | null) => {
4 | if (!token) {
5 | return false;
6 | }
7 |
8 | return apiKeys.includes(token);
9 | };
10 |
11 | export const extractAuthToken = (req): string | null => {
12 | let authHeader = '';
13 |
14 | if (typeof req.headers.get === 'function') {
15 | authHeader = req.headers.get('authorization') || '';
16 | } else {
17 | authHeader = req.headers.authorization || '';
18 | }
19 |
20 | const parts = authHeader.split(' ');
21 |
22 | return parts.length > 1 ? parts[1] : null;
23 | };
24 |
--------------------------------------------------------------------------------
/lib/color.ts:
--------------------------------------------------------------------------------
1 | import chroma from 'chroma-js';
2 |
3 | export const hexToOklch = (hexColor: string) => {
4 | const [l, c, h] = chroma(hexColor).oklch();
5 | return [l, c, h].join(' ');
6 | };
7 |
--------------------------------------------------------------------------------
/lib/error.ts:
--------------------------------------------------------------------------------
1 | export class ApiError extends Error {
2 | statusCode: number;
3 |
4 | constructor(message: string, statusCode: number) {
5 | super(message);
6 | this.statusCode = statusCode;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/lib/jackson.ts:
--------------------------------------------------------------------------------
1 | import type { SAMLJackson } from '@boxyhq/saml-jackson';
2 |
3 | import jackson from '@boxyhq/saml-jackson';
4 | import { jacksonOptions } from '@lib/env';
5 | import '@lib/metrics';
6 | import { logger } from './logger';
7 |
8 | const g = global as any;
9 |
10 | const jacksonOptionsWithLogger = {
11 | ...jacksonOptions,
12 | logger: {
13 | info: (msg: string, err?: any) => logger.info(err, msg),
14 | error: (msg: string, err?: any) => logger.error(err, msg),
15 | warn: (msg: string, err?: any) => logger.warn(err, msg),
16 | },
17 | };
18 |
19 | export default async function init() {
20 | if (!g.jacksonInstance) {
21 | g.jacksonInstance = new Promise((resolve, reject) => {
22 | jackson(jacksonOptionsWithLogger).then(resolve).catch(reject);
23 | });
24 | }
25 |
26 | return (await g.jacksonInstance) as SAMLJackson;
27 | }
28 |
--------------------------------------------------------------------------------
/lib/metrics.ts:
--------------------------------------------------------------------------------
1 | import packageInfo from '../package.json';
2 | import { initializeMetrics } from '@boxyhq/metrics';
3 |
4 | const g = global as any;
5 |
6 | if (!g.metricsInit) {
7 | initializeMetrics({ name: packageInfo.name, version: packageInfo.version });
8 | g.metricsInit = true;
9 | }
10 |
--------------------------------------------------------------------------------
/lib/middleware.ts:
--------------------------------------------------------------------------------
1 | import Cors from 'cors';
2 | import { NextApiRequest, NextApiResponse } from 'next';
3 |
4 | // Initializing the cors middleware
5 | const corsFunction = Cors({
6 | methods: ['GET', 'HEAD'],
7 | });
8 |
9 | // Helper method to wait for a middleware to execute before continuing
10 | // And to throw an error when an error happens in a middleware
11 | function runMiddleware(req: NextApiRequest, res: NextApiResponse, fn: any) {
12 | return new Promise((resolve, reject) => {
13 | fn(req, res, (result: any) => {
14 | if (result instanceof Error) {
15 | return reject(result);
16 | }
17 |
18 | return resolve(result);
19 | });
20 | });
21 | }
22 |
23 | export async function cors(req: NextApiRequest, res: NextApiResponse) {
24 | return await runMiddleware(req, res, corsFunction);
25 | }
26 |
--------------------------------------------------------------------------------
/lib/retraced.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | import type { AdminToken } from 'types/retraced';
4 | import { retracedOptions } from './env';
5 | import { getToken as getNextAuthToken } from 'next-auth/jwt';
6 | import type { NextApiRequest } from 'next';
7 | import { sessionName } from './constants';
8 |
9 | export const getToken = async (req: NextApiRequest): Promise => {
10 | const token = await getNextAuthToken({
11 | req,
12 | cookieName: sessionName,
13 | });
14 |
15 | const { data } = await axios.post<{ adminToken: AdminToken }>(
16 | `${retracedOptions?.hostUrl}/admin/v1/user/_login`,
17 | {
18 | claims: {
19 | upstreamToken: 'ADMIN_ROOT_TOKEN',
20 | email: token!.email,
21 | },
22 | },
23 | {
24 | headers: {
25 | Authorization: `token=${retracedOptions?.adminToken}`,
26 | 'Content-Type': 'application/json',
27 | },
28 | }
29 | );
30 |
31 | return data.adminToken;
32 | };
33 |
--------------------------------------------------------------------------------
/lib/ui/hooks/useIdpEntityID.ts:
--------------------------------------------------------------------------------
1 | import useSWR from 'swr';
2 | import { useRouter } from 'next/router';
3 |
4 | import { fetcher } from '@lib/ui/utils';
5 | import type { ApiError, ApiSuccess } from 'types';
6 |
7 | const useIdpEntityID = (setupLinkToken?: string) => {
8 | const { query, isReady } = useRouter();
9 |
10 | const token = setupLinkToken || (isReady ? query.token : null);
11 |
12 | const { data, error, isLoading } = useSWR, ApiError>(
13 | token ? `/api/setup/${token}/sso-connection/idp-entityid` : null,
14 | fetcher
15 | );
16 |
17 | return {
18 | idpEntityID: data?.data.idpEntityID,
19 | isLoading,
20 | error,
21 | };
22 | };
23 |
24 | export default useIdpEntityID;
25 |
--------------------------------------------------------------------------------
/lib/ui/hooks/usePaginate.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import { useRouter } from 'next/router';
3 |
4 | const usePaginate = () => {
5 | const router = useRouter();
6 |
7 | const offset = router.query.offset ? Number(router.query.offset) : 0;
8 |
9 | const [paginate, setPaginate] = useState({ offset });
10 | // store that maps the pageToken for the next page with the current offset
11 | const [pageTokenMap, setPageTokenMap] = useState({});
12 |
13 | useEffect(() => {
14 | // Prevent pushing the same URL to the history
15 | if (offset === paginate.offset) {
16 | return;
17 | }
18 |
19 | const path = router.asPath.split('?')[0];
20 |
21 | router.push(`${path}?offset=${paginate.offset}`, undefined, { shallow: true });
22 | // eslint-disable-next-line react-hooks/exhaustive-deps
23 | }, [paginate]);
24 |
25 | useEffect(() => {
26 | setPaginate({ offset });
27 | }, [offset]);
28 |
29 | return {
30 | paginate,
31 | setPaginate,
32 | pageTokenMap,
33 | setPageTokenMap,
34 | };
35 | };
36 |
37 | export default usePaginate;
38 |
--------------------------------------------------------------------------------
/lib/ui/hooks/useSetupLink.ts:
--------------------------------------------------------------------------------
1 | import useSWR from 'swr';
2 | import type { AdminPortalBranding, SetupLink } from '@boxyhq/saml-jackson';
3 | import type { ApiError, ApiSuccess } from 'types';
4 | import { fetcher } from '@lib/ui/utils';
5 |
6 | const useSetupLink = (setupLinkToken: string) => {
7 | const url = setupLinkToken ? `/api/setup/${setupLinkToken}` : null;
8 |
9 | const { data, error, isLoading } = useSWR, ApiError>(
10 | url,
11 | fetcher
12 | );
13 |
14 | return {
15 | setupLink: data?.data,
16 | isLoading,
17 | error,
18 | };
19 | };
20 |
21 | export default useSetupLink;
22 |
--------------------------------------------------------------------------------
/lib/ui/utils.ts:
--------------------------------------------------------------------------------
1 | // returns the cookie with the given name,
2 | // or undefined if not found
3 | export function getErrorCookie() {
4 | const matches = document.cookie.match(
5 | new RegExp('(?:^|; )' + 'jackson_error'.replace(/([.$?*|{}()[]\\\/\+^])/g, '\\$1') + '=([^;]*)')
6 | );
7 | return matches ? decodeURIComponent(matches[1]) : undefined;
8 | }
9 |
10 | export const fetcher = async (url: string, queryParams = '') => {
11 | const res = await fetch(`${url}${queryParams}`);
12 |
13 | let resContent, pageToken;
14 |
15 | try {
16 | resContent = await res.clone().json();
17 | pageToken = res.headers.get('jackson-pagetoken');
18 | if (pageToken !== null) {
19 | return { ...resContent, pageToken };
20 | }
21 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
22 | } catch (e) {
23 | resContent = await res.clone().text();
24 | }
25 |
26 | if (!res.ok) {
27 | const error = new Error(
28 | (resContent.error.message as string) || 'An error occurred while fetching the data.'
29 | );
30 |
31 | throw error;
32 | }
33 |
34 | return resContent;
35 | };
36 |
--------------------------------------------------------------------------------
/migrate.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | echo "Initiating Migration..."
4 | export NODE_PATH=$(npm root -g)
5 |
6 | cd ./npm
7 | if [ "$DB_ENGINE" = "mongo" ]
8 | then
9 | migrate-mongo up
10 | else
11 | ts-node --transpile-only --project tsconfig.json $NODE_PATH/typeorm/cli.js migration:run -d ./typeorm.ts
12 | fi
13 | if [ $? -eq 1 ]
14 | then
15 | echo "Migration Failed..."
16 | exit 1
17 | fi
18 | echo "Migration Finished..."
19 |
20 | cd ..
21 |
22 |
--------------------------------------------------------------------------------
/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
6 |
--------------------------------------------------------------------------------
/next-i18next.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | i18n: {
5 | defaultLocale: 'en',
6 | locales: ['en'],
7 | },
8 | localePath: path.resolve('./locales'),
9 | };
10 |
--------------------------------------------------------------------------------
/npm/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | npm/dist
--------------------------------------------------------------------------------
/npm/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | .vscode
4 | .nyc_output
5 | _config
6 | dist
7 | .DS_Store
8 | /node_modules
9 | **/node_modules/**
10 |
--------------------------------------------------------------------------------
/npm/README.md:
--------------------------------------------------------------------------------
1 | # Polis NPM
2 |
3 | SAML 2.0 library
4 |
5 | Polis implements the SAML login flow as an OAuth 2.0 flow, abstracting away all the complexities of the SAML protocol.
6 |
7 | ## Install as an npm library
8 |
9 | Polis is available as an [npm package](https://www.npmjs.com/package/@boxyhq/saml-jackson) that can be integrated into any web application framework (like Express.js for example). Please file an issue or submit a PR if you encounter any issues with your choice of framework.
10 |
11 | ```bash
12 | npm install @boxyhq/saml-jackson
13 | ```
14 |
15 | ## Documentation
16 |
17 | For full documentation, visit [/docs/polis/guides/npm-library](https://www.ory.sh/docs/polis/guides/npm-library)
18 |
19 | ## License
20 |
21 | [Apache 2.0 License](https://github.com/ory/polis/blob/main/LICENSE)
22 |
--------------------------------------------------------------------------------
/npm/migrate-mongo-config.js:
--------------------------------------------------------------------------------
1 | const config = {
2 | mongodb: {
3 | url: process.env.DB_URL || 'mongodb://localhost:27017/jackson',
4 | options: {
5 | useNewUrlParser: true, // removes a deprecation warning when connecting
6 | useUnifiedTopology: true, // removes a deprecating warning when connecting
7 | // connectTimeoutMS: 3600000, // increase connection timeout to 1 hour
8 | // socketTimeoutMS: 3600000, // increase socket timeout to 1 hour
9 | },
10 | },
11 |
12 | migrationsDir: 'migration/mongo',
13 | changelogCollectionName: 'changelog',
14 | migrationFileExtension: '.js',
15 | // Enable the algorithm to create a checksum of the file contents and use that in the comparison to determine
16 | // if the file should be run. Requires that scripts are coded to be run multiple times.
17 | useFileHash: false,
18 | // Don't change this, unless you know what you're doing
19 | moduleSystem: 'commonjs',
20 | };
21 |
22 | module.exports = config;
23 |
--------------------------------------------------------------------------------
/npm/migration/mariadb/1692767993709-md_namespace.ts:
--------------------------------------------------------------------------------
1 | import { MigrationInterface, QueryRunner } from "typeorm";
2 |
3 | export class MdNamespace1692767993709 implements MigrationInterface {
4 | name = 'MdNamespace1692767993709'
5 |
6 | public async up(queryRunner: QueryRunner): Promise {
7 | await queryRunner.query(`ALTER TABLE \`jackson_store\` ADD \`namespace\` varchar(64) NULL`);
8 | await queryRunner.query(`CREATE INDEX \`_jackson_store_namespace\` ON \`jackson_store\` (\`namespace\`)`);
9 | }
10 |
11 | public async down(queryRunner: QueryRunner): Promise {
12 | await queryRunner.query(`DROP INDEX \`_jackson_store_namespace\` ON \`jackson_store\``);
13 | await queryRunner.query(`ALTER TABLE \`jackson_store\` DROP COLUMN \`namespace\``);
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/npm/migration/mariadb/1695120700240-md_sortorder.ts:
--------------------------------------------------------------------------------
1 | import { MigrationInterface, QueryRunner } from "typeorm";
2 |
3 | export class MdSortorder1695120700240 implements MigrationInterface {
4 | name = 'MdSortorder1695120700240'
5 |
6 | public async up(queryRunner: QueryRunner): Promise {
7 | await queryRunner.query(`ALTER TABLE \`jackson_store\` CHANGE \`createdAt\` \`createdAt\` timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6)`);
8 | await queryRunner.query(`ALTER TABLE \`jackson_store\` CHANGE \`modifiedAt\` \`modifiedAt\` timestamp(6) NULL`);
9 | }
10 |
11 | public async down(queryRunner: QueryRunner): Promise {
12 | await queryRunner.query(`ALTER TABLE \`jackson_store\` CHANGE \`modifiedAt\` \`modifiedAt\` timestamp(0) NULL`);
13 | await queryRunner.query(`ALTER TABLE \`jackson_store\` CHANGE \`createdAt\` \`createdAt\` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP()`);
14 | }
15 |
16 | }
--------------------------------------------------------------------------------
/npm/migration/mariadb/1714417013715-md_namespace.ts:
--------------------------------------------------------------------------------
1 | import { MigrationInterface, QueryRunner } from "typeorm";
2 |
3 | export class MdNamespace1714417013715 implements MigrationInterface {
4 | name = 'MdNamespace1714417013715'
5 |
6 | public async up(queryRunner: QueryRunner): Promise {
7 | await queryRunner.query(`ALTER TABLE \`jackson_store\` MODIFY \`namespace\` varchar(256) NULL`);
8 | }
9 |
10 | public async down(queryRunner: QueryRunner): Promise {
11 | await queryRunner.query(`ALTER TABLE \`jackson_store\` MODIFY \`namespace\` varchar(64) NULL`);
12 | }
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/npm/migration/mongo/20230824060000-namespace.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | async up(db, client) {
3 | const collection = db.collection('jacksonStore');
4 | const response = await collection.distinct('_id', {});
5 | const searchTerm = ':';
6 | for (const k in response) {
7 | const key = response[k].toString();
8 | const tokens2 = key.split(searchTerm).slice(0, 2);
9 | const value = tokens2.join(searchTerm);
10 | await db.collection('jacksonStore').updateOne({ _id: key }, {$set: { namespace: value }});
11 | }
12 |
13 | },
14 |
15 | async down(db, client) {
16 | const collection = db.collection('jacksonStore');
17 | const response = await collection.distinct('_id', {});
18 | for (const k in response) {
19 | const key = response[k].toString();
20 | await db.collection('jacksonStore').updateOne({ _id: key }, {$set: { namespace: '' }});
21 | }
22 | }
23 | };
24 |
--------------------------------------------------------------------------------
/npm/migration/mssql/1692767993709-mss_namespace.ts:
--------------------------------------------------------------------------------
1 | import { MigrationInterface, QueryRunner } from "typeorm";
2 |
3 | export class MssNamespace1692767993709 implements MigrationInterface {
4 | name = 'MssNamespace1692767993709'
5 |
6 | public async up(queryRunner: QueryRunner): Promise {
7 | await queryRunner.query(`ALTER TABLE "jackson_store" ADD "namespace" varchar(64)`);
8 | await queryRunner.query(`CREATE INDEX "_jackson_store_namespace" ON "jackson_store" ("namespace") `);
9 | }
10 |
11 | public async down(queryRunner: QueryRunner): Promise {
12 | await queryRunner.query(`DROP INDEX "_jackson_store_namespace" ON "jackson_store"`);
13 | await queryRunner.query(`ALTER TABLE "jackson_store" DROP COLUMN "namespace"`);
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/npm/migration/mssql/1692817789888-mss_namespace.ts:
--------------------------------------------------------------------------------
1 | import { MigrationInterface, QueryRunner } from "typeorm"
2 |
3 | export class namespace1692817789888 implements MigrationInterface {
4 | name = 'namespace1692817789888'
5 |
6 | public async up(queryRunner: QueryRunner): Promise {
7 | const response = await queryRunner.query("select jackson.[key] from jackson_store jackson")
8 | const searchTerm = ':';
9 | for (const k in response) {
10 | const key = response[k].key;
11 | const tokens2 = key.split(searchTerm).slice(0, 2);
12 | const value = tokens2.join(searchTerm);
13 | queryRunner.query(`update jackson_store set namespace = '${value}' where jackson_store.[key] = '${key}'`)
14 | }
15 | }
16 |
17 | public async down(queryRunner: QueryRunner): Promise {
18 | const response = await queryRunner.query("select jackson.[key] from jackson_store jackson")
19 | for (const k in response) {
20 | const key = response[k].key;
21 | queryRunner.query(`update jackson_store set namespace = NULL where jackson_store.[key] = '${key}'`)
22 | }
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/npm/migration/mssql/1714421718208-mss_namespace.ts:
--------------------------------------------------------------------------------
1 | import { MigrationInterface, QueryRunner } from "typeorm";
2 |
3 | export class MssNamespace1714421718208 implements MigrationInterface {
4 | name = 'MssNamespace1714421718208'
5 |
6 | public async up(queryRunner: QueryRunner): Promise {
7 | await queryRunner.query(`ALTER TABLE "jackson_store" ALTER COLUMN "namespace" varchar(256)`);
8 | }
9 |
10 | public async down(queryRunner: QueryRunner): Promise {
11 | await queryRunner.query(`ALTER TABLE "jackson_store" ALTER COLUMN "namespace" varchar(64)`);
12 | }
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/npm/migration/mysql/1692767993709-ms_namespace.ts:
--------------------------------------------------------------------------------
1 | import { MigrationInterface, QueryRunner } from "typeorm";
2 |
3 | export class msNamespace1692767993709 implements MigrationInterface {
4 | name = 'msNamespace1692767993709'
5 |
6 | public async up(queryRunner: QueryRunner): Promise {
7 | await queryRunner.query(`ALTER TABLE \`jackson_store\` ADD \`namespace\` varchar(64) NULL`);
8 | await queryRunner.query(`CREATE INDEX \`_jackson_store_namespace\` ON \`jackson_store\` (\`namespace\`)`);
9 | }
10 |
11 | public async down(queryRunner: QueryRunner): Promise {
12 | await queryRunner.query(`DROP INDEX \`_jackson_store_namespace\` ON \`jackson_store\``);
13 | await queryRunner.query(`ALTER TABLE \`jackson_store\` DROP COLUMN \`namespace\``);
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/npm/migration/mysql/1695120580071-ms_sortorder.ts:
--------------------------------------------------------------------------------
1 | import { MigrationInterface, QueryRunner } from "typeorm";
2 |
3 | export class MsSortorder1695120580071 implements MigrationInterface {
4 | name = 'MsSortorder1695120580071'
5 |
6 | public async up(queryRunner: QueryRunner): Promise {
7 | await queryRunner.query(`ALTER TABLE \`jackson_store\` CHANGE \`createdAt\` \`createdAt\` timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6)`);
8 | await queryRunner.query(`ALTER TABLE \`jackson_store\` CHANGE \`modifiedAt\` \`modifiedAt\` timestamp(6) NULL`);
9 | }
10 |
11 | public async down(queryRunner: QueryRunner): Promise {
12 | await queryRunner.query(`ALTER TABLE \`jackson_store\` CHANGE \`modifiedAt\` \`modifiedAt\` timestamp(0) NULL`);
13 | await queryRunner.query(`ALTER TABLE \`jackson_store\` CHANGE \`createdAt\` \`createdAt\` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP`);
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/npm/migration/mysql/1714419315556-ms_namespace.ts:
--------------------------------------------------------------------------------
1 | import { MigrationInterface, QueryRunner } from "typeorm";
2 |
3 | export class MsNamespace1714419315556 implements MigrationInterface {
4 | name = 'MsNamespace1714419315556'
5 |
6 | public async up(queryRunner: QueryRunner): Promise {
7 | await queryRunner.query(`ALTER TABLE \`jackson_store\` MODIFY \`namespace\` varchar(256) NULL`);
8 | }
9 |
10 | public async down(queryRunner: QueryRunner): Promise {
11 | await queryRunner.query(`ALTER TABLE \`jackson_store\` MODIFY \`namespace\` varchar(64) NULL`);
12 | }
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/npm/migration/planetscale/1692767993709-ms_namespace.ts:
--------------------------------------------------------------------------------
1 | import { MigrationInterface, QueryRunner } from "typeorm";
2 |
3 | export class MsNamespace1692767993709 implements MigrationInterface {
4 | name = 'MsNamespace1692767993709'
5 |
6 | public async up(queryRunner: QueryRunner): Promise {
7 | await queryRunner.query(`ALTER TABLE \`jackson_store\` ADD \`namespace\` varchar(64) NULL`);
8 | await queryRunner.query(`CREATE INDEX \`_jackson_store_namespace\` ON \`jackson_store\` (\`namespace\`)`);
9 | }
10 |
11 | public async down(queryRunner: QueryRunner): Promise {
12 | await queryRunner.query(`DROP INDEX \`_jackson_store_namespace\` ON \`jackson_store\``);
13 | await queryRunner.query(`ALTER TABLE \`jackson_store\` DROP COLUMN \`namespace\``);
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/npm/migration/planetscale/1695120599689-ms_sortorder.ts:
--------------------------------------------------------------------------------
1 | import { MigrationInterface, QueryRunner } from "typeorm";
2 |
3 | export class MsSortorder1695120599689 implements MigrationInterface {
4 | name = 'MsSortorder1695120599689'
5 |
6 | public async up(queryRunner: QueryRunner): Promise {
7 | await queryRunner.query(`ALTER TABLE \`jackson_store\` CHANGE \`createdAt\` \`createdAt\` timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6)`);
8 | await queryRunner.query(`ALTER TABLE \`jackson_store\` CHANGE \`modifiedAt\` \`modifiedAt\` timestamp(6) NULL`);
9 | }
10 |
11 | public async down(queryRunner: QueryRunner): Promise {
12 | await queryRunner.query(`ALTER TABLE \`jackson_store\` CHANGE \`modifiedAt\` \`modifiedAt\` timestamp(0) NULL`);
13 | await queryRunner.query(`ALTER TABLE \`jackson_store\` CHANGE \`createdAt\` \`createdAt\` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP`);
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/npm/migration/planetscale/1714457285484-ms_namespace.ts:
--------------------------------------------------------------------------------
1 | import { MigrationInterface, QueryRunner } from "typeorm";
2 |
3 | export class MsNamespace1714457285484 implements MigrationInterface {
4 | name = 'MsNamespace1714457285484'
5 |
6 | public async up(queryRunner: QueryRunner): Promise {
7 | await queryRunner.query(`ALTER TABLE \`jackson_store\` MODIFY \`namespace\` varchar(256) NULL`);
8 | }
9 |
10 | public async down(queryRunner: QueryRunner): Promise {
11 | await queryRunner.query(`ALTER TABLE \`jackson_store\` MODIFY \`namespace\` varchar(64) NULL`);
12 | }
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/npm/migration/postgres/1644332647279-createdAt.ts:
--------------------------------------------------------------------------------
1 | import {MigrationInterface, QueryRunner} from "typeorm";
2 |
3 | export class createdAt1644332647279 implements MigrationInterface {
4 | name = 'createdAt1644332647279'
5 |
6 | public async up(queryRunner: QueryRunner): Promise {
7 | await queryRunner.query(`ALTER TABLE "jackson_store" ADD "createdAt" TIMESTAMP NOT NULL DEFAULT now()`);
8 | await queryRunner.query(`ALTER TABLE "jackson_store" ADD "modifiedAt" TIMESTAMP`);
9 | }
10 |
11 | public async down(queryRunner: QueryRunner): Promise {
12 | await queryRunner.query(`ALTER TABLE "jackson_store" DROP COLUMN "modifiedAt"`);
13 | await queryRunner.query(`ALTER TABLE "jackson_store" DROP COLUMN "createdAt"`);
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/npm/migration/postgres/1692767993709-pg_namespace.ts:
--------------------------------------------------------------------------------
1 | import { MigrationInterface, QueryRunner } from "typeorm";
2 |
3 | export class PgNamespace1692767993709 implements MigrationInterface {
4 | name = 'PgNamespace1692767993709'
5 |
6 | public async up(queryRunner: QueryRunner): Promise {
7 | await queryRunner.query(`ALTER TABLE "jackson_store" ADD "namespace" character varying(64)`);
8 | await queryRunner.query(`CREATE INDEX "_jackson_store_namespace" ON "jackson_store" ("namespace") `);
9 | }
10 |
11 | public async down(queryRunner: QueryRunner): Promise {
12 | await queryRunner.query(`DROP INDEX "public"."_jackson_store_namespace"`);
13 | await queryRunner.query(`ALTER TABLE "jackson_store" DROP COLUMN "namespace"`);
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/npm/migration/postgres/1714452929542-pg_namespace.ts:
--------------------------------------------------------------------------------
1 | import { MigrationInterface, QueryRunner } from "typeorm";
2 |
3 | export class PgNamespace1714452929542 implements MigrationInterface {
4 | name = 'PgNamespace1714452929542'
5 |
6 | public async up(queryRunner: QueryRunner): Promise {
7 | await queryRunner.query(`ALTER TABLE "jackson_store" ALTER COLUMN "namespace" TYPE VARCHAR(256)`);
8 | }
9 |
10 | public async down(queryRunner: QueryRunner): Promise {
11 | await queryRunner.query(`ALTER TABLE "jackson_store" ALTER COLUMN "namespace" TYPE VARCHAR(64)`);
12 | }
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/npm/migration/sql/1692817789888-namespace.ts:
--------------------------------------------------------------------------------
1 | import { MigrationInterface, QueryRunner } from "typeorm"
2 |
3 | export class namespace1692817789888 implements MigrationInterface {
4 | name = 'namespace1692817789888'
5 |
6 | public async up(queryRunner: QueryRunner): Promise {
7 | const response = await queryRunner.query("select jackson.key from jackson_store jackson")
8 | const searchTerm = ':';
9 | for (const k in response) {
10 | const key = response[k].key;
11 | const tokens2 = key.split(searchTerm).slice(0, 2);
12 | const value = tokens2.join(searchTerm);
13 | queryRunner.query(`update jackson_store set namespace = '${value}' where jackson_store.key = '${key}'`)
14 | }
15 | }
16 |
17 | public async down(queryRunner: QueryRunner): Promise {
18 | const response = await queryRunner.query("select jackson.key from jackson_store jackson")
19 | for (const k in response) {
20 | const key = response[k].key;
21 | queryRunner.query(`update jackson_store set namespace = NULL where jackson_store.key = '${key}'`)
22 | }
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/npm/src/controller/error.ts:
--------------------------------------------------------------------------------
1 | import { ApiError } from '../typings';
2 |
3 | export class JacksonError extends Error {
4 | public name: string;
5 | public statusCode: number;
6 | public internalError?: string;
7 |
8 | constructor(message: string, statusCode = 500, internalError: string = '') {
9 | super(message);
10 |
11 | this.name = this.constructor.name;
12 | this.statusCode = statusCode;
13 | if (internalError) {
14 | this.internalError = internalError;
15 | }
16 |
17 | Error.captureStackTrace(this, this.constructor);
18 | }
19 | }
20 |
21 | export const apiError = (err: any) => {
22 | const { message, statusCode = 500 } = err;
23 |
24 | return { data: null, error: { message, code: statusCode } as ApiError };
25 | };
26 |
--------------------------------------------------------------------------------
/npm/src/controller/oauth/allowed.ts:
--------------------------------------------------------------------------------
1 | const redirectUrlPlaceholder = 'http://_boxyhq_redirect_not_in_use';
2 |
3 | export const redirect = (redirectUrl: string, redirectUrls: string[]): boolean => {
4 | // Don't allow redirect to URL placeholder
5 | if (redirectUrl === redirectUrlPlaceholder) {
6 | return false;
7 | }
8 |
9 | const url: URL = new URL(redirectUrl);
10 |
11 | for (const idx in redirectUrls) {
12 | const rUrl: URL = new URL(redirectUrls[idx]);
13 |
14 | let hostname = url.hostname;
15 | let hostNameAllowed = rUrl.hostname;
16 |
17 | // allow subdomain globbing *.example.com only
18 | try {
19 | if (rUrl.hostname.startsWith('*.')) {
20 | hostNameAllowed = rUrl.hostname.slice(2);
21 | hostname = hostname.slice(hostname.indexOf('.') + 1);
22 | }
23 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
24 | } catch (e) {
25 | // no-op
26 | }
27 |
28 | // TODO: Check pathname, for now pathname is ignored
29 |
30 | if (rUrl.protocol === url.protocol && hostNameAllowed === hostname && rUrl.port === url.port) {
31 | return true;
32 | }
33 | }
34 |
35 | return false;
36 | };
37 |
--------------------------------------------------------------------------------
/npm/src/controller/oauth/code-verifier.ts:
--------------------------------------------------------------------------------
1 | import crypto from 'crypto';
2 |
3 | const transformBase64 = (input: string): string => {
4 | return input.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
5 | };
6 |
7 | export const encode = (code_challenge: string): string => {
8 | return transformBase64(crypto.createHash('sha256').update(code_challenge).digest('base64'));
9 | };
10 |
--------------------------------------------------------------------------------
/npm/src/controller/oauth/redirect.ts:
--------------------------------------------------------------------------------
1 | export const success = (
2 | redirectUrl: string,
3 | params: Record
4 | ): string => {
5 | const url: URL = new URL(redirectUrl);
6 |
7 | for (const [key, value] of Object.entries(params)) {
8 | if (Array.isArray(value)) {
9 | value.forEach((v) => url.searchParams.append(key, v));
10 | } else if (value !== undefined) {
11 | url.searchParams.set(key, value);
12 | }
13 | }
14 |
15 | return url.href;
16 | };
17 |
--------------------------------------------------------------------------------
/npm/src/db/defaultDb.ts:
--------------------------------------------------------------------------------
1 | import { JacksonOption } from '../typings';
2 |
3 | export default function defaultDb(opts: JacksonOption) {
4 | opts.db = opts.db || {};
5 | opts.db.manualMigration = opts.db.manualMigration || false;
6 | opts.db.ttl = (opts.db.ttl || 300) * 1; // TTL for the code, session and token stores (in seconds)
7 | opts.db.cleanupLimit = (opts.db.cleanupLimit || 1000) * 1; // Limit cleanup of TTL entries to this many items at a time
8 | if ('driver' in opts.db) {
9 | return opts;
10 | }
11 |
12 | opts.db.engine = opts.db.engine || 'sql';
13 | opts.db.url = opts.db.url || 'postgresql://postgres:postgres@localhost:5432/postgres';
14 | opts.db.type = opts.db.type || 'postgres'; // Only needed if DB_ENGINE is sql.
15 | opts.db.dynamodb = opts.db.dynamodb || {};
16 | opts.db.dynamodb.region = opts.db.dynamodb.region || 'us-east-1';
17 | opts.db.dynamodb.readCapacityUnits = opts.db.dynamodb.readCapacityUnits || 5;
18 | opts.db.dynamodb.writeCapacityUnits = opts.db.dynamodb.writeCapacityUnits || 5;
19 |
20 | return opts;
21 | }
22 |
--------------------------------------------------------------------------------
/npm/src/db/encrypter.ts:
--------------------------------------------------------------------------------
1 | import crypto from 'crypto';
2 | import { Encrypted, EncryptionKey } from '../typings';
3 |
4 | const ALGO = 'aes-256-gcm';
5 | const BLOCK_SIZE = 12; // 96 bit
6 |
7 | export const encrypt = (text: string, key: EncryptionKey): Encrypted => {
8 | const iv = crypto.randomBytes(BLOCK_SIZE);
9 | const cipher = crypto.createCipheriv(ALGO, key, iv);
10 |
11 | let ciphertext = cipher.update(text, 'utf8', 'base64');
12 | ciphertext += cipher.final('base64');
13 |
14 | return {
15 | iv: iv.toString('base64'),
16 | tag: cipher.getAuthTag().toString('base64'),
17 | value: ciphertext,
18 | };
19 | };
20 |
21 | export const decrypt = (ciphertext: string, iv: string, tag: string, key: EncryptionKey): string => {
22 | const decipher = crypto.createDecipheriv(ALGO, key, Buffer.from(iv, 'base64'));
23 | decipher.setAuthTag(Buffer.from(tag, 'base64'));
24 |
25 | let cleartext = decipher.update(ciphertext, 'base64', 'utf8');
26 | cleartext += decipher.final('utf8');
27 |
28 | return cleartext;
29 | };
30 |
--------------------------------------------------------------------------------
/npm/src/db/planetscale/entity/JacksonIndex.ts:
--------------------------------------------------------------------------------
1 | import { Entity, PrimaryGeneratedColumn, Column, Index } from 'typeorm';
2 |
3 | @Index('_jackson_index_key_store', ['key', 'storeKey'])
4 | @Entity({ name: 'jackson_index' })
5 | export class JacksonIndex {
6 | @PrimaryGeneratedColumn()
7 | id!: number;
8 |
9 | @Index('_jackson_index_key')
10 | @Column({
11 | type: 'varchar',
12 | length: 250,
13 | })
14 | key!: string;
15 |
16 | @Index('_jackson_index_store')
17 | @Column({
18 | type: 'varchar',
19 | length: 250,
20 | })
21 | storeKey!: string;
22 | }
23 |
--------------------------------------------------------------------------------
/npm/src/db/planetscale/entity/JacksonStore.ts:
--------------------------------------------------------------------------------
1 | import { Entity, Column, Index } from 'typeorm';
2 |
3 | @Entity({ name: 'jackson_store' })
4 | export class JacksonStore {
5 | @Column({
6 | primary: true,
7 | type: 'varchar',
8 | length: 250,
9 | })
10 | key!: string;
11 |
12 | @Column({
13 | type: 'text',
14 | })
15 | value!: string;
16 |
17 | @Column({
18 | type: 'varchar',
19 | length: 64,
20 | nullable: true,
21 | })
22 | iv?: string;
23 |
24 | @Column({
25 | type: 'varchar',
26 | length: 64,
27 | nullable: true,
28 | })
29 | tag?: string;
30 |
31 | @Column({
32 | type: 'timestamp',
33 | precision: 6,
34 | default: () => 'CURRENT_TIMESTAMP(6)',
35 | nullable: false,
36 | })
37 | createdAt?: Date;
38 |
39 | @Column({
40 | type: 'timestamp',
41 | precision: 6,
42 | nullable: true,
43 | })
44 | modifiedAt?: string;
45 |
46 | @Index('_jackson_store_namespace')
47 | @Column({
48 | type: 'varchar',
49 | length: 256,
50 | nullable: true,
51 | })
52 | namespace?: string;
53 | }
54 |
--------------------------------------------------------------------------------
/npm/src/db/planetscale/entity/JacksonTTL.ts:
--------------------------------------------------------------------------------
1 | import { Entity, Column, Index } from 'typeorm';
2 |
3 | @Entity({ name: 'jackson_ttl' })
4 | export class JacksonTTL {
5 | @Column({
6 | primary: true,
7 | type: 'varchar',
8 | length: 250,
9 | })
10 | key!: string;
11 |
12 | @Index('_jackson_ttl_expires_at')
13 | @Column({
14 | type: 'bigint',
15 | })
16 | expiresAt!: number;
17 | }
18 |
--------------------------------------------------------------------------------
/npm/src/db/sql/entity/JacksonIndex.ts:
--------------------------------------------------------------------------------
1 | import { JacksonStore } from './JacksonStore';
2 |
3 | import { Entity, PrimaryGeneratedColumn, Column, Index, ManyToOne } from 'typeorm';
4 |
5 | @Index('_jackson_index_key_store', ['key', 'storeKey'])
6 | @Entity({ name: 'jackson_index' })
7 | export class JacksonIndex {
8 | @PrimaryGeneratedColumn()
9 | id!: number;
10 |
11 | @Index('_jackson_index_key')
12 | @Column({
13 | type: 'varchar',
14 | length: 1500,
15 | })
16 | key!: string;
17 |
18 | @Column({
19 | type: 'varchar',
20 | length: 1500,
21 | })
22 | storeKey!: string;
23 |
24 | @ManyToOne(() => JacksonStore, undefined, {
25 | //inverseSide: 'in',
26 | eager: true,
27 | onDelete: 'CASCADE',
28 | })
29 | store?: JacksonStore;
30 | }
31 |
--------------------------------------------------------------------------------
/npm/src/db/sql/entity/JacksonStore.ts:
--------------------------------------------------------------------------------
1 | import { Entity, Column, Index } from 'typeorm';
2 |
3 | @Entity({ name: 'jackson_store' })
4 | export class JacksonStore {
5 | @Column({
6 | primary: true,
7 | type: 'varchar',
8 | length: 1500,
9 | })
10 | key!: string;
11 |
12 | @Column({
13 | type: 'text',
14 | })
15 | value!: string;
16 |
17 | @Column({
18 | type: 'varchar',
19 | length: 64,
20 | nullable: true,
21 | })
22 | iv?: string;
23 |
24 | @Column({
25 | type: 'varchar',
26 | length: 64,
27 | nullable: true,
28 | })
29 | tag?: string;
30 |
31 | @Column({
32 | type: 'timestamp',
33 | default: () => 'CURRENT_TIMESTAMP',
34 | nullable: false,
35 | })
36 | createdAt?: Date;
37 |
38 | @Column({
39 | type: 'timestamp',
40 | nullable: true,
41 | })
42 | modifiedAt?: string;
43 |
44 | @Index('_jackson_store_namespace')
45 | @Column({
46 | type: 'varchar',
47 | length: 256,
48 | nullable: true,
49 | })
50 | namespace?: string;
51 | }
52 |
--------------------------------------------------------------------------------
/npm/src/db/sql/entity/JacksonTTL.ts:
--------------------------------------------------------------------------------
1 | import { Entity, Column, Index } from 'typeorm';
2 |
3 | @Entity({ name: 'jackson_ttl' })
4 | export class JacksonTTL {
5 | @Column({
6 | primary: true,
7 | type: 'varchar',
8 | length: 1500,
9 | })
10 | key!: string;
11 |
12 | @Index('_jackson_ttl_expires_at')
13 | @Column({
14 | type: 'bigint',
15 | })
16 | expiresAt!: number;
17 | }
18 |
--------------------------------------------------------------------------------
/npm/src/db/sql/mariadb/entity/JacksonIndex.ts:
--------------------------------------------------------------------------------
1 | import { JacksonStore } from './JacksonStore';
2 |
3 | import { Entity, PrimaryGeneratedColumn, Column, Index, ManyToOne } from 'typeorm';
4 |
5 | @Index('_jackson_index_key_store', ['key', 'storeKey'])
6 | @Entity({ name: 'jackson_index' })
7 | export class JacksonIndex {
8 | @PrimaryGeneratedColumn()
9 | id!: number;
10 |
11 | @Index('_jackson_index_key')
12 | @Column({
13 | type: 'varchar',
14 | length: 250,
15 | })
16 | key!: string;
17 |
18 | @Column({
19 | type: 'varchar',
20 | length: 250,
21 | })
22 | storeKey!: string;
23 |
24 | @ManyToOne(() => JacksonStore, undefined, {
25 | //inverseSide: 'in',
26 | eager: true,
27 | onDelete: 'CASCADE',
28 | })
29 | store?: JacksonStore;
30 | }
31 |
--------------------------------------------------------------------------------
/npm/src/db/sql/mariadb/entity/JacksonStore.ts:
--------------------------------------------------------------------------------
1 | import { Entity, Column, Index } from 'typeorm';
2 |
3 | @Entity({ name: 'jackson_store' })
4 | export class JacksonStore {
5 | @Column({
6 | primary: true,
7 | type: 'varchar',
8 | length: 250,
9 | })
10 | key!: string;
11 |
12 | @Column({
13 | type: 'text',
14 | })
15 | value!: string;
16 |
17 | @Column({
18 | type: 'varchar',
19 | length: 64,
20 | nullable: true,
21 | })
22 | iv?: string;
23 |
24 | @Column({
25 | type: 'varchar',
26 | length: 64,
27 | nullable: true,
28 | })
29 | tag?: string;
30 |
31 | @Column({
32 | type: 'timestamp',
33 | precision: 6,
34 | default: () => 'CURRENT_TIMESTAMP(6)',
35 | nullable: false,
36 | })
37 | createdAt?: Date;
38 |
39 | @Column({
40 | type: 'timestamp',
41 | precision: 6,
42 | nullable: true,
43 | })
44 | modifiedAt?: string;
45 |
46 | @Index('_jackson_store_namespace')
47 | @Column({
48 | type: 'varchar',
49 | length: 256,
50 | nullable: true,
51 | })
52 | namespace?: string;
53 | }
54 |
--------------------------------------------------------------------------------
/npm/src/db/sql/mariadb/entity/JacksonTTL.ts:
--------------------------------------------------------------------------------
1 | import { Entity, Column, Index } from 'typeorm';
2 |
3 | @Entity({ name: 'jackson_ttl' })
4 | export class JacksonTTL {
5 | @Column({
6 | primary: true,
7 | type: 'varchar',
8 | length: 250,
9 | })
10 | key!: string;
11 |
12 | @Index('_jackson_ttl_expires_at')
13 | @Column({
14 | type: 'bigint',
15 | })
16 | expiresAt!: number;
17 | }
18 |
--------------------------------------------------------------------------------
/npm/src/db/sql/mssql/entity/JacksonIndex.ts:
--------------------------------------------------------------------------------
1 | import { JacksonStore } from './JacksonStore';
2 |
3 | import { Entity, PrimaryGeneratedColumn, Column, Index, ManyToOne } from 'typeorm';
4 |
5 | @Index('_jackson_index_key_store', ['key', 'storeKey'])
6 | @Entity({ name: 'jackson_index' })
7 | export class JacksonIndex {
8 | @PrimaryGeneratedColumn()
9 | id!: number;
10 |
11 | @Index('_jackson_index_key')
12 | @Column({
13 | type: 'varchar',
14 | length: 250,
15 | })
16 | key!: string;
17 |
18 | @Column({
19 | type: 'varchar',
20 | length: 250,
21 | })
22 | storeKey!: string;
23 |
24 | @ManyToOne(() => JacksonStore, undefined, {
25 | //inverseSide: 'in',
26 | eager: true,
27 | onDelete: 'CASCADE',
28 | })
29 | store?: JacksonStore;
30 | }
31 |
--------------------------------------------------------------------------------
/npm/src/db/sql/mssql/entity/JacksonStore.ts:
--------------------------------------------------------------------------------
1 | import { Entity, Column, Index } from 'typeorm';
2 |
3 | @Entity({ name: 'jackson_store' })
4 | export class JacksonStore {
5 | @Column({
6 | primary: true,
7 | type: 'varchar',
8 | length: 250,
9 | })
10 | key!: string;
11 |
12 | @Column({
13 | type: 'text',
14 | })
15 | value!: string;
16 |
17 | @Column({
18 | type: 'varchar',
19 | length: 64,
20 | nullable: true,
21 | })
22 | iv?: string;
23 |
24 | @Column({
25 | type: 'varchar',
26 | length: 64,
27 | nullable: true,
28 | })
29 | tag?: string;
30 |
31 | @Column({
32 | type: 'datetime',
33 | default: () => 'CURRENT_TIMESTAMP',
34 | nullable: false,
35 | })
36 | createdAt?: Date;
37 |
38 | @Column({
39 | type: 'datetime',
40 | nullable: true,
41 | })
42 | modifiedAt?: string;
43 |
44 | @Index('_jackson_store_namespace')
45 | @Column({
46 | type: 'varchar',
47 | length: 256,
48 | nullable: true,
49 | })
50 | namespace?: string;
51 | }
52 |
--------------------------------------------------------------------------------
/npm/src/db/sql/mssql/entity/JacksonTTL.ts:
--------------------------------------------------------------------------------
1 | import { Entity, Column, Index } from 'typeorm';
2 |
3 | @Entity({ name: 'jackson_ttl' })
4 | export class JacksonTTL {
5 | @Column({
6 | primary: true,
7 | type: 'varchar',
8 | length: 250,
9 | })
10 | key!: string;
11 |
12 | @Index('_jackson_ttl_expires_at')
13 | @Column({
14 | type: 'bigint',
15 | })
16 | expiresAt!: number;
17 | }
18 |
--------------------------------------------------------------------------------
/npm/src/db/sql/sqlite/entity/JacksonIndex.ts:
--------------------------------------------------------------------------------
1 | import { JacksonStore } from './JacksonStore';
2 |
3 | import { Entity, PrimaryGeneratedColumn, Column, Index, ManyToOne } from 'typeorm';
4 |
5 | @Index('_jackson_index_key_store', ['key', 'storeKey'])
6 | @Entity({ name: 'jackson_index' })
7 | export class JacksonIndex {
8 | @PrimaryGeneratedColumn()
9 | id!: number;
10 |
11 | @Index('_jackson_index_key')
12 | @Column({
13 | type: 'varchar',
14 | length: 250,
15 | })
16 | key!: string;
17 |
18 | @Column({
19 | type: 'varchar',
20 | length: 250,
21 | })
22 | storeKey!: string;
23 |
24 | @ManyToOne(() => JacksonStore, undefined, {
25 | //inverseSide: 'in',
26 | eager: true,
27 | onDelete: 'CASCADE',
28 | })
29 | store?: JacksonStore;
30 | }
31 |
--------------------------------------------------------------------------------
/npm/src/db/sql/sqlite/entity/JacksonStore.ts:
--------------------------------------------------------------------------------
1 | import { Entity, Column, Index } from 'typeorm';
2 |
3 | @Entity({ name: 'jackson_store' })
4 | export class JacksonStore {
5 | @Column({
6 | primary: true,
7 | type: 'varchar',
8 | length: 1500,
9 | })
10 | key!: string;
11 |
12 | @Column({
13 | type: 'text',
14 | })
15 | value!: string;
16 |
17 | @Column({
18 | type: 'varchar',
19 | length: 64,
20 | nullable: true,
21 | })
22 | iv?: string;
23 |
24 | @Column({
25 | type: 'varchar',
26 | length: 64,
27 | nullable: true,
28 | })
29 | tag?: string;
30 |
31 | @Column({
32 | type: 'datetime',
33 | default: () => "(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW'))",
34 | nullable: false,
35 | })
36 | createdAt?: Date;
37 |
38 | @Column({
39 | type: 'datetime',
40 | nullable: true,
41 | })
42 | modifiedAt?: string;
43 |
44 | @Index('_jackson_store_namespace')
45 | @Column({
46 | type: 'varchar',
47 | length: 256,
48 | nullable: true,
49 | })
50 | namespace?: string;
51 | }
52 |
--------------------------------------------------------------------------------
/npm/src/db/sql/sqlite/entity/JacksonTTL.ts:
--------------------------------------------------------------------------------
1 | import { Entity, Column, Index } from 'typeorm';
2 |
3 | @Entity({ name: 'jackson_ttl' })
4 | export class JacksonTTL {
5 | @Column({
6 | primary: true,
7 | type: 'varchar',
8 | length: 250,
9 | })
10 | key!: string;
11 |
12 | @Index('_jackson_ttl_expires_at')
13 | @Column({
14 | type: 'bigint',
15 | })
16 | expiresAt!: number;
17 | }
18 |
--------------------------------------------------------------------------------
/npm/src/directory-sync/non-scim/google/index.ts:
--------------------------------------------------------------------------------
1 | import { GoogleAuth } from './oauth';
2 | import { GoogleProvider } from './api';
3 | import type { IDirectoryConfig, JacksonOption } from '../../../typings';
4 |
5 | interface NewGoogleProviderParams {
6 | directories: IDirectoryConfig;
7 | opts: JacksonOption;
8 | }
9 |
10 | export const newGoogleProvider = (params: NewGoogleProviderParams) => {
11 | const { directories, opts } = params;
12 |
13 | return {
14 | directory: new GoogleProvider({ opts, directories }),
15 | oauth: new GoogleAuth({ opts, directories }),
16 | };
17 | };
18 |
--------------------------------------------------------------------------------
/npm/src/directory-sync/scim/Base.ts:
--------------------------------------------------------------------------------
1 | import type { Storable, DB } from '../../typings';
2 | import { storeNamespacePrefix } from '../../controller/utils';
3 |
4 | export class Base {
5 | protected db: DB;
6 | protected tenant: null | string = null;
7 | protected product: null | string = null;
8 | protected bulkDeleteBatchSize = 500;
9 |
10 | constructor({ db }: { db: DB }) {
11 | this.db = db;
12 | }
13 |
14 | // Return the database store
15 | store(type: 'groups' | 'members' | 'users' | 'logs' | 'events', ttl?: number): Storable {
16 | if (!this.tenant || !this.product) {
17 | throw new Error('Set tenant and product before using store.');
18 | }
19 |
20 | return this.db.store(`${storeNamespacePrefix.dsync[type]}:${this.tenant}:${this.product}`, ttl);
21 | }
22 |
23 | // Set the tenant and product
24 | setTenantAndProduct(tenant: string, product: string): this {
25 | this.tenant = tenant;
26 | this.product = product;
27 |
28 | return this;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/npm/src/ee/LICENSE:
--------------------------------------------------------------------------------
1 | The BoxyHQ Enterprise Edition (EE) license (the “EE License”)
2 |
--------------------------------------------------------------------------------
/npm/src/ee/common/checkLicense.ts:
--------------------------------------------------------------------------------
1 | import { JacksonError } from '../../controller/error';
2 |
3 | const checkLicense = async (license: string | undefined): Promise => {
4 | if (!license) {
5 | return false;
6 | }
7 |
8 | return license === 'dummy-license';
9 | };
10 |
11 | export const throwIfInvalidLicense = async (license: string | undefined): Promise => {
12 | if (!(await checkLicense(license))) {
13 | throw new JacksonError(
14 | 'Enterprise License not found. This is an enterprise feature, please add a valid license to use this feature.',
15 | 403
16 | );
17 | }
18 | };
19 |
20 | export default checkLicense;
21 |
--------------------------------------------------------------------------------
/npm/src/ee/identity-federation/types.ts:
--------------------------------------------------------------------------------
1 | import IdentityFederation from '.';
2 |
3 | export type IIdentityFederationController = Awaited>;
4 |
5 | export type AttributeMapping = {
6 | key: string;
7 | value: string;
8 | };
9 |
10 | export type IdentityFederationApp = {
11 | id: string;
12 | type?: string;
13 | clientID?: string;
14 | clientSecret?: string;
15 | redirectUrl?: string[] | string;
16 | name: string;
17 | tenant: string;
18 | product: string;
19 | acsUrl: string;
20 | entityId: string;
21 | logoUrl: string | null;
22 | faviconUrl: string | null;
23 | primaryColor: string | null;
24 | tenants?: string[]; // To support multiple tenants for a single app
25 | mappings?: AttributeMapping[] | null;
26 | };
27 |
28 | export type IdentityFederationAppWithMetadata = IdentityFederationApp & {
29 | metadata: {
30 | entityId: string;
31 | ssoUrl: string;
32 | x509cert: string;
33 | xml: string;
34 | };
35 | };
36 |
37 | export type AppRequestParams =
38 | | {
39 | id: string;
40 | }
41 | | {
42 | tenant: string;
43 | product: string;
44 | type?: string;
45 | };
46 |
--------------------------------------------------------------------------------
/npm/test/dsync/data/directories.ts:
--------------------------------------------------------------------------------
1 | import { Directory, DirectoryType } from '../../../src/typings';
2 | import { faker } from '@faker-js/faker';
3 |
4 | export const getFakeDirectory = () => {
5 | return {
6 | name: faker.company.name(),
7 | tenant: faker.internet.domainName(),
8 | product: faker.commerce.productName(),
9 | type: 'okta-scim-v2' as DirectoryType,
10 | log_webhook_events: false,
11 | } as Directory;
12 | };
13 |
--------------------------------------------------------------------------------
/npm/test/dsync/data/groups.ts:
--------------------------------------------------------------------------------
1 | const groups = [
2 | {
3 | schemas: ['urn:ietf:params:scim:schemas:core:2.0:Group'],
4 | displayName: 'Developers',
5 | members: [],
6 | },
7 | {
8 | schemas: ['urn:ietf:params:scim:schemas:core:2.0:Group'],
9 | displayName: 'Designers',
10 | members: [],
11 | },
12 | ];
13 |
14 | export default groups;
15 |
--------------------------------------------------------------------------------
/npm/test/identity-federation/constants.ts:
--------------------------------------------------------------------------------
1 | import * as dbutils from '../../src/db/utils';
2 |
3 | const tenant = 'boxyhq';
4 | const product = 'flex';
5 |
6 | const serviceProvider = {
7 | acsUrl: 'https://twilio.com/saml2/acs',
8 | entityId: 'https://twilio.com/saml2/entityId',
9 | };
10 |
11 | const appId = dbutils.keyDigest(dbutils.keyFromParts(tenant, product));
12 |
13 | export { tenant, product, serviceProvider, appId };
14 |
--------------------------------------------------------------------------------
/npm/test/identity-federation/data/request.xml:
--------------------------------------------------------------------------------
1 | https://twilio.com/saml2/entityId urn:oasis:names:tc:SAML:2.0:ac:classes:Password
--------------------------------------------------------------------------------
/npm/test/sso/data/metadata/boxyhq.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | defaultRedirectUrl: 'http://localhost:3366/sso/oauth/completed',
3 | redirectUrl: '["http://localhost:3366"]',
4 | tenant: 'boxyhq.com',
5 | product: 'crm',
6 | label: 'internal label',
7 | name: 'testConfig',
8 | description: 'Just a test configuration',
9 | };
10 |
--------------------------------------------------------------------------------
/npm/test/sso/data/metadata/example.js:
--------------------------------------------------------------------------------
1 | // For SLO
2 | module.exports = {
3 | defaultRedirectUrl: 'http://localhost:3366/sso/oauth/completed',
4 | redirectUrl: '["http://localhost:3366"]',
5 | tenant: 'example.com',
6 | product: 'crm',
7 | name: 'SAML Metadata for example.com',
8 | description: 'SAML Metadata with SingleLogoutService URLs',
9 | };
10 |
--------------------------------------------------------------------------------
/npm/test/sso/data/metadata/example.oidc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | defaultRedirectUrl: 'http://localhost:3366/sso/oauth/oidc',
3 | redirectUrl: '["http://localhost:3366"]',
4 | tenant: 'oidc.example.com',
5 | product: 'crm',
6 | name: 'OIDC Metadata for oidc.example.com',
7 | description: 'OIDC Metadata for oidc.example.com',
8 | oidcDiscoveryUrl: 'https://accounts.google.com/.well-known/openid-configuration',
9 | oidcClientId: 'dummyClientId',
10 | oidcClientSecret: 'dummyClientSecret',
11 | };
12 |
--------------------------------------------------------------------------------
/npm/test/sso/data/metadata/invalidSSODescriptor/invalidssodescriptor.js:
--------------------------------------------------------------------------------
1 | // Invalid SSO Descriptor
2 | module.exports = {
3 | defaultRedirectUrl: 'http://localhost:3366/sso/oauth/completed',
4 | redirectUrl: '["http://localhost:3366"]',
5 | tenant: 'invalidssodescriptor.com',
6 | product: 'crm',
7 | name: 'testConfig',
8 | description: 'Just a test configuration',
9 | };
10 |
--------------------------------------------------------------------------------
/npm/test/sso/data/metadata/nobinding/boxyhq-nobinding.js:
--------------------------------------------------------------------------------
1 | // Without POST/Redirect bindings
2 | module.exports = {
3 | defaultRedirectUrl: 'http://localhost:3366/sso/oauth/completed',
4 | redirectUrl: '["http://localhost:3366"]',
5 | tenant: 'boxyhqnobinding.com',
6 | product: 'crm',
7 | name: 'testConfig',
8 | description: 'Just a test configuration',
9 | };
10 |
--------------------------------------------------------------------------------
/npm/test/sso/data/metadata/noentityID/boxyhq-noentityID.js:
--------------------------------------------------------------------------------
1 | // Without Entity ID in XML
2 | module.exports = {
3 | defaultRedirectUrl: 'http://localhost:3366/sso/oauth/completed',
4 | redirectUrl: '["http://localhost:3366"]',
5 | tenant: 'boxyhqnoentityID.com',
6 | product: 'crm',
7 | name: 'testConfig',
8 | description: 'Just a test configuration',
9 | };
10 |
--------------------------------------------------------------------------------
/npm/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "exclude": ["node_modules", "test/**/*"]
4 | }
5 |
--------------------------------------------------------------------------------
/npm/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "sourceMap": true,
4 | "outDir": "./dist",
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "module": "CommonJS",
8 | "target": "es6", //same as es2015
9 | "forceConsistentCasingInFileNames": true,
10 | "noImplicitAny": false,
11 | "strict": true,
12 | "noImplicitThis": false,
13 | "resolveJsonModule": true,
14 | "esModuleInterop": true,
15 | "declaration": true,
16 | "noEmitOnError": false,
17 | "noUnusedParameters": true,
18 | "removeComments": false,
19 | "strictNullChecks": true,
20 | "allowSyntheticDefaultImports": true,
21 | "experimentalDecorators": true,
22 | "downlevelIteration": true,
23 | },
24 | "include": ["./src/**/*"],
25 | "exclude": ["node_modules"],
26 | "ts-node": {
27 | "files": true,
28 | },
29 | }
30 |
--------------------------------------------------------------------------------
/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import Document, { Html, Head, Main, NextScript } from 'next/document';
2 |
3 | class MyDocument extends Document {
4 | render() {
5 | return (
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | );
14 | }
15 | }
16 |
17 | export default MyDocument;
18 |
--------------------------------------------------------------------------------
/pages/admin/auth/idp-login.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import { useRouter } from 'next/router';
3 | import { signIn } from 'next-auth/react';
4 |
5 | export default function Page() {
6 | const router = useRouter();
7 |
8 | const { isReady, query } = router;
9 |
10 | useEffect(() => {
11 | if (!isReady) return;
12 |
13 | signIn('boxyhq-saml-idplogin', {
14 | code: query?.code,
15 | callbackUrl: '/',
16 | });
17 | }, [isReady, query?.code]);
18 |
19 | return null;
20 | }
21 |
--------------------------------------------------------------------------------
/pages/admin/dashboard.tsx:
--------------------------------------------------------------------------------
1 | import { WellKnownURLs } from '@boxyhq/internal-ui';
2 | import type { GetServerSidePropsContext, InferGetServerSidePropsType, NextPage } from 'next';
3 | import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
4 | import { adminPortal } from '@lib/env';
5 |
6 | const Dashboard: NextPage> = ({
7 | hideIdentityFederation,
8 | }) => {
9 | return ;
10 | };
11 |
12 | export async function getStaticProps({ locale }: GetServerSidePropsContext) {
13 | return {
14 | props: {
15 | hideIdentityFederation: adminPortal.hideIdentityFederation,
16 | ...(locale ? await serverSideTranslations(locale, ['common']) : {}),
17 | },
18 | };
19 | }
20 |
21 | export default Dashboard;
22 |
--------------------------------------------------------------------------------
/pages/admin/directory-sync/[directoryId]/edit.tsx:
--------------------------------------------------------------------------------
1 | import type { NextPage, GetServerSidePropsContext } from 'next';
2 | import { useRouter } from 'next/router';
3 | import React from 'react';
4 | import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
5 | import EditDirectory from '@components/dsync/EditDirectory';
6 |
7 | const DirectoryEditPage: NextPage = () => {
8 | const router = useRouter();
9 |
10 | const { directoryId } = router.query as { directoryId: string };
11 |
12 | return ;
13 | };
14 |
15 | export const getServerSideProps = async (context: GetServerSidePropsContext) => {
16 | const { locale } = context;
17 |
18 | return {
19 | props: {
20 | ...(locale ? await serverSideTranslations(locale, ['common']) : {}),
21 | },
22 | };
23 | };
24 |
25 | export default DirectoryEditPage;
26 |
--------------------------------------------------------------------------------
/pages/admin/directory-sync/[directoryId]/index.tsx:
--------------------------------------------------------------------------------
1 | import { useRouter } from 'next/router';
2 | import type { NextPage, GetServerSidePropsContext } from 'next';
3 | import { DirectoryInfo, LinkBack } from '@boxyhq/internal-ui';
4 | import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
5 |
6 | const DirectoryInfoPage: NextPage = () => {
7 | const router = useRouter();
8 |
9 | const { directoryId } = router.query as { directoryId: string };
10 |
11 | return (
12 | <>
13 |
14 |
20 | >
21 | );
22 | };
23 |
24 | export const getServerSideProps = async (context: GetServerSidePropsContext) => {
25 | const { locale } = context;
26 |
27 | return {
28 | props: {
29 | ...(locale ? await serverSideTranslations(locale, ['common']) : {}),
30 | },
31 | };
32 | };
33 |
34 | export default DirectoryInfoPage;
35 |
--------------------------------------------------------------------------------
/pages/admin/directory-sync/index.tsx:
--------------------------------------------------------------------------------
1 | import type { GetStaticPropsContext, NextPage } from 'next';
2 | import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
3 | import DirectoryList from '@components/dsync/DirectoryList';
4 |
5 | const DirectoryIndexPage: NextPage = () => {
6 | return ;
7 | };
8 |
9 | export const getStaticProps = async (context: GetStaticPropsContext) => {
10 | const { locale } = context;
11 |
12 | return {
13 | props: {
14 | ...(locale ? await serverSideTranslations(locale, ['common']) : {}),
15 | },
16 | };
17 | };
18 |
19 | export default DirectoryIndexPage;
20 |
--------------------------------------------------------------------------------
/pages/admin/directory-sync/new.tsx:
--------------------------------------------------------------------------------
1 | import type { NextPage, InferGetServerSidePropsType, GetServerSidePropsContext } from 'next';
2 | import React from 'react';
3 | import CreateDirectory from '@components/dsync/CreateDirectory';
4 | import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
5 | import { jacksonOptions } from '@lib/env';
6 |
7 | const DirectoryCreatePage: NextPage> = (props) => {
8 | const { defaultWebhookEndpoint, defaultWebhookSecret } = props;
9 |
10 | return (
11 |
15 | );
16 | };
17 |
18 | export const getServerSideProps = async ({ locale }: GetServerSidePropsContext) => {
19 | return {
20 | props: {
21 | ...(locale ? await serverSideTranslations(locale, ['common']) : {}),
22 | defaultWebhookEndpoint: jacksonOptions.webhook?.endpoint,
23 | defaultWebhookSecret: jacksonOptions.webhook?.secret,
24 | },
25 | };
26 | };
27 |
28 | export default DirectoryCreatePage;
29 |
--------------------------------------------------------------------------------
/pages/admin/identity-federation/[id]/edit.tsx:
--------------------------------------------------------------------------------
1 | import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
2 |
3 | import jackson from '@lib/jackson';
4 | import { jacksonOptions } from '@lib/env';
5 |
6 | export { default } from '@ee/identity-federation/pages/edit';
7 |
8 | export async function getServerSideProps({ locale }) {
9 | const { checkLicense } = await jackson();
10 |
11 | return {
12 | props: {
13 | ...(await serverSideTranslations(locale, ['common'])),
14 | hasValidLicense: await checkLicense(),
15 | jacksonUrl: jacksonOptions.externalUrl,
16 | },
17 | };
18 | }
19 |
--------------------------------------------------------------------------------
/pages/admin/identity-federation/index.tsx:
--------------------------------------------------------------------------------
1 | import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
2 |
3 | import jackson from '@lib/jackson';
4 |
5 | export { default } from '@ee/identity-federation/pages/index';
6 |
7 | export async function getServerSideProps({ locale }) {
8 | const { checkLicense } = await jackson();
9 |
10 | return {
11 | props: {
12 | ...(await serverSideTranslations(locale, ['common'])),
13 | hasValidLicense: await checkLicense(),
14 | },
15 | };
16 | }
17 |
--------------------------------------------------------------------------------
/pages/admin/identity-federation/new.tsx:
--------------------------------------------------------------------------------
1 | import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
2 |
3 | import jackson from '@lib/jackson';
4 | import { jacksonOptions } from '@lib/env';
5 |
6 | export { default } from '@ee/identity-federation/pages/new';
7 |
8 | export async function getServerSideProps({ locale }) {
9 | const { checkLicense } = await jackson();
10 |
11 | return {
12 | props: {
13 | ...(await serverSideTranslations(locale, ['common'])),
14 | hasValidLicense: await checkLicense(),
15 | samlAudience: jacksonOptions.samlAudience || 'https://saml.boxyhq.com',
16 | },
17 | };
18 | }
19 |
--------------------------------------------------------------------------------
/pages/admin/retraced/projects/new.tsx:
--------------------------------------------------------------------------------
1 | import type { NextPage } from 'next';
2 | import AddProject from '@components/retraced/AddProject';
3 | import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
4 |
5 | const NewProject: NextPage = () => {
6 | return ;
7 | };
8 |
9 | export async function getServerSideProps({ locale }) {
10 | return {
11 | props: {
12 | ...(await serverSideTranslations(locale, ['common'])),
13 | },
14 | };
15 | }
16 |
17 | export default NewProject;
18 |
--------------------------------------------------------------------------------
/pages/admin/settings/branding/index.tsx:
--------------------------------------------------------------------------------
1 | import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
2 |
3 | import jackson from '@lib/jackson';
4 |
5 | export { default } from 'ee/branding/pages/index';
6 |
7 | export async function getServerSideProps({ locale }) {
8 | const { checkLicense } = await jackson();
9 |
10 | return {
11 | props: {
12 | ...(await serverSideTranslations(locale, ['common'])),
13 | hasValidLicense: await checkLicense(),
14 | },
15 | };
16 | }
17 |
--------------------------------------------------------------------------------
/pages/admin/settings/sso-connection/index.tsx:
--------------------------------------------------------------------------------
1 | import type { GetServerSidePropsContext, NextPage } from 'next';
2 | import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
3 | import ConnectionList from '@components/connection/ConnectionList';
4 |
5 | const ConnectionsIndexPageForSettings: NextPage = () => {
6 | return ;
7 | };
8 |
9 | export default ConnectionsIndexPageForSettings;
10 |
11 | export async function getStaticProps({ locale }: GetServerSidePropsContext) {
12 | return {
13 | props: {
14 | ...(locale ? await serverSideTranslations(locale, ['common']) : {}),
15 | },
16 | };
17 | }
18 |
--------------------------------------------------------------------------------
/pages/admin/settings/sso-connection/new.tsx:
--------------------------------------------------------------------------------
1 | import type { GetServerSidePropsContext, NextPage } from 'next';
2 | import CreateConnection from '@components/connection/CreateConnection';
3 | import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
4 | import type { AdminPortalSSODefaults } from '@lib/utils';
5 | import { adminPortalSSODefaults } from '@lib/env';
6 |
7 | type Props = {
8 | adminPortalSSODefaults: AdminPortalSSODefaults;
9 | };
10 |
11 | const NewSSOConnection: NextPage = ({ adminPortalSSODefaults }) => {
12 | return ;
13 | };
14 |
15 | export async function getServerSideProps({ locale }: GetServerSidePropsContext) {
16 | return {
17 | props: {
18 | ...(locale ? await serverSideTranslations(locale, ['common']) : {}),
19 | adminPortalSSODefaults,
20 | },
21 | };
22 | }
23 |
24 | export default NewSSOConnection;
25 |
--------------------------------------------------------------------------------
/pages/admin/sso-connection/index.tsx:
--------------------------------------------------------------------------------
1 | import type { GetServerSidePropsContext, NextPage } from 'next';
2 | import ConnectionList from '@components/connection/ConnectionList';
3 | import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
4 |
5 | const ConnectionsIndexPage: NextPage = () => {
6 | return ;
7 | };
8 |
9 | export async function getStaticProps({ locale }: GetServerSidePropsContext) {
10 | return {
11 | props: {
12 | ...(locale ? await serverSideTranslations(locale, ['common']) : {}),
13 | },
14 | };
15 | }
16 |
17 | export default ConnectionsIndexPage;
18 |
--------------------------------------------------------------------------------
/pages/admin/sso-connection/new.tsx:
--------------------------------------------------------------------------------
1 | import type { GetServerSidePropsContext, NextPage } from 'next';
2 | import CreateConnection from '@components/connection/CreateConnection';
3 | import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
4 |
5 | const NewConnection: NextPage = () => {
6 | return ;
7 | };
8 |
9 | export async function getStaticProps({ locale }: GetServerSidePropsContext) {
10 | return {
11 | props: {
12 | ...(locale ? await serverSideTranslations(locale, ['common']) : {}),
13 | },
14 | };
15 | }
16 |
17 | export default NewConnection;
18 |
--------------------------------------------------------------------------------
/pages/admin/sso-traces/[traceId]/inspect.tsx:
--------------------------------------------------------------------------------
1 | import { NextPage } from 'next';
2 | import { useRouter } from 'next/router';
3 | import { SSOTraceInfo, LinkBack } from '@boxyhq/internal-ui';
4 | import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
5 |
6 | const SSOTraceInspector: NextPage = () => {
7 | const router = useRouter();
8 |
9 | const { traceId } = router.query as { traceId: string };
10 |
11 | return (
12 |
13 |
14 |
15 |
16 | );
17 | };
18 |
19 | export default SSOTraceInspector;
20 |
21 | export async function getServerSideProps({ locale }) {
22 | return {
23 | props: {
24 | ...(await serverSideTranslations(locale, ['common'])),
25 | },
26 | };
27 | }
28 |
--------------------------------------------------------------------------------
/pages/admin/sso-traces/index.tsx:
--------------------------------------------------------------------------------
1 | import type { NextPage } from 'next';
2 | import { useRouter } from 'next/router';
3 | import { SSOTraces } from '@boxyhq/internal-ui';
4 | import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
5 |
6 | const SSOTraceViewer: NextPage = () => {
7 | const router = useRouter();
8 |
9 | return (
10 | router.push(`/admin/sso-traces/${trace.traceId}/inspect`)}
13 | />
14 | );
15 | };
16 |
17 | export default SSOTraceViewer;
18 |
19 | export async function getServerSideProps({ locale }) {
20 | return {
21 | props: {
22 | ...(await serverSideTranslations(locale, ['common'])),
23 | },
24 | };
25 | }
26 |
--------------------------------------------------------------------------------
/pages/api/admin/branding.ts:
--------------------------------------------------------------------------------
1 | export { default } from 'ee/branding/api/admin/index';
2 |
--------------------------------------------------------------------------------
/pages/api/admin/connections/[clientId].ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from 'next';
2 |
3 | import jackson from '@lib/jackson';
4 | import { defaultHandler } from '@lib/api';
5 | import { ApiError } from '@lib/error';
6 |
7 | const handler = async (req: NextApiRequest, res: NextApiResponse) => {
8 | await defaultHandler(req, res, {
9 | GET: handleGET,
10 | });
11 | };
12 |
13 | // Get connection by clientID
14 | const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
15 | const { connectionAPIController } = await jackson();
16 |
17 | const { clientId } = req.query as {
18 | clientId: string;
19 | };
20 |
21 | const connections = await connectionAPIController.getConnections({ clientID: clientId });
22 |
23 | if (!connections || connections.length === 0) {
24 | throw new ApiError('Connection not found', 404);
25 | }
26 |
27 | res.json(connections);
28 | };
29 |
30 | export default handler;
31 |
--------------------------------------------------------------------------------
/pages/api/admin/connections/idp-entityid.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from 'next';
2 | import jackson from '@lib/jackson';
3 | import { defaultHandler } from '@lib/api';
4 |
5 | const handler = async (req: NextApiRequest, res: NextApiResponse) => {
6 | await defaultHandler(req, res, {
7 | GET: handleGET,
8 | });
9 | };
10 |
11 | const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
12 | const { connectionAPIController } = await jackson();
13 |
14 | const idpEntityID = connectionAPIController.getIDPEntityID({
15 | tenant: req.body.tenant,
16 | product: req.body.product,
17 | });
18 |
19 | res.json({ data: { idpEntityID } });
20 | };
21 |
22 | export default handler;
23 |
--------------------------------------------------------------------------------
/pages/api/admin/directory-sync/[directoryId]/events/[eventId].ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from 'next';
2 | import jackson from '@lib/jackson';
3 | import { defaultHandler } from '@lib/api';
4 | import { ApiError } from '@lib/error';
5 |
6 | const handler = async (req: NextApiRequest, res: NextApiResponse) => {
7 | await defaultHandler(req, res, {
8 | GET: handleGET,
9 | });
10 | };
11 |
12 | const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
13 | const { directorySyncController } = await jackson();
14 |
15 | const { directoryId, eventId } = req.query as { directoryId: string; eventId: string };
16 |
17 | const { data: directory, error } = await directorySyncController.directories.get(directoryId);
18 |
19 | if (error) {
20 | throw new ApiError(error.message, error.code);
21 | }
22 |
23 | const event = await directorySyncController.webhookLogs
24 | .setTenantAndProduct(directory.tenant, directory.product)
25 | .get(eventId);
26 |
27 | res.json({ data: event });
28 | };
29 |
30 | export default handler;
31 |
--------------------------------------------------------------------------------
/pages/api/admin/directory-sync/providers.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from 'next';
2 | import jackson from '@lib/jackson';
3 | import { defaultHandler } from '@lib/api';
4 |
5 | const handler = async (req: NextApiRequest, res: NextApiResponse) => {
6 | await defaultHandler(req, res, {
7 | GET: handleGET,
8 | });
9 | };
10 |
11 | // Get the directory providers
12 | const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
13 | const { directorySyncController } = await jackson();
14 |
15 | const providers = directorySyncController.providers();
16 |
17 | res.json({ data: providers });
18 | };
19 |
20 | export default handler;
21 |
--------------------------------------------------------------------------------
/pages/api/admin/identity-federation/[id]/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from '@ee/identity-federation/api/admin/[id]/index';
2 |
--------------------------------------------------------------------------------
/pages/api/admin/identity-federation/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from '@ee/identity-federation/api/admin/index';
2 |
--------------------------------------------------------------------------------
/pages/api/admin/retraced/projects/[id]/groups.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from 'next';
2 | import axios from 'axios';
3 |
4 | import { getToken } from '@lib/retraced';
5 | import { retracedOptions } from '@lib/env';
6 | import { defaultHandler } from '@lib/api';
7 |
8 | async function handler(req: NextApiRequest, res: NextApiResponse) {
9 | await defaultHandler(req, res, {
10 | GET: getGroups,
11 | });
12 | }
13 |
14 | const getGroups = async (req: NextApiRequest, res: NextApiResponse) => {
15 | const token = await getToken(req);
16 |
17 | const { id: projectId, environmentId } = req.query;
18 |
19 | const { data } = await axios.get(
20 | `${retracedOptions?.hostUrl}/admin/v1/project/${projectId}/groups?environment_id=${environmentId}`,
21 | {
22 | headers: {
23 | Authorization: `id=${token.id} token=${token.token} admin_token=${retracedOptions.adminToken}`,
24 | },
25 | data: {
26 | query: {
27 | length: 10,
28 | offset: 0,
29 | },
30 | },
31 | }
32 | );
33 |
34 | res.json({
35 | data,
36 | error: null,
37 | });
38 | };
39 |
40 | export default handler;
41 |
--------------------------------------------------------------------------------
/pages/api/admin/retraced/projects/[id]/index.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from 'next';
2 | import axios from 'axios';
3 |
4 | import type { Project } from 'types/retraced';
5 | import { getToken } from '@lib/retraced';
6 | import { retracedOptions } from '@lib/env';
7 | import { defaultHandler } from '@lib/api';
8 |
9 | async function handler(req: NextApiRequest, res: NextApiResponse) {
10 | await defaultHandler(req, res, {
11 | GET: getProject,
12 | });
13 | }
14 |
15 | const getProject = async (req: NextApiRequest, res: NextApiResponse) => {
16 | const token = await getToken(req);
17 |
18 | const { id } = req.query;
19 |
20 | const { data } = await axios.get<{ project: Project }>(
21 | `${retracedOptions?.hostUrl}/admin/v1/project/${id}`,
22 | {
23 | headers: {
24 | Authorization: `id=${token.id} token=${token.token} admin_token=${retracedOptions.adminToken}`,
25 | },
26 | }
27 | );
28 |
29 | res.status(201).json({
30 | data,
31 | error: null,
32 | });
33 | };
34 |
35 | export default handler;
36 |
--------------------------------------------------------------------------------
/pages/api/admin/retraced/projects/[id]/viewer-token.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from 'next';
2 | import * as Retraced from '@retracedhq/retraced';
3 |
4 | import { retracedOptions } from '@lib/env';
5 | import { defaultHandler } from '@lib/api';
6 |
7 | async function handler(req: NextApiRequest, res: NextApiResponse) {
8 | await defaultHandler(req, res, {
9 | GET: getViewerToken,
10 | });
11 | }
12 |
13 | // Get A viewer token and send it to the client, the client will use this token to initialize the logs-viewer
14 | const getViewerToken = async (req: NextApiRequest, res: NextApiResponse) => {
15 | const { id: projectId, groupId, token } = req.query;
16 |
17 | // TODO: Move to global
18 | const retraced = new Retraced.Client({
19 | apiKey: token as string,
20 | projectId: projectId as string,
21 | endpoint: retracedOptions?.hostUrl,
22 | viewLogAction: 'audit.log.view',
23 | });
24 |
25 | const viewerToken = await retraced.getViewerToken(groupId as string, 'Admin-Portal', true);
26 |
27 | res.json({
28 | data: {
29 | viewerToken,
30 | },
31 | error: null,
32 | });
33 | };
34 |
35 | export default handler;
36 |
--------------------------------------------------------------------------------
/pages/api/admin/sso-traces/[traceId]/index.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from 'next';
2 | import jackson from '@lib/jackson';
3 | import { defaultHandler } from '@lib/api';
4 |
5 | const handler = async (req: NextApiRequest, res: NextApiResponse) => {
6 | await defaultHandler(req, res, {
7 | GET: handleGET,
8 | });
9 | };
10 |
11 | // Get SAML Trace by traceId
12 | const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
13 | const { adminController } = await jackson();
14 |
15 | const { traceId } = req.query as { traceId: string };
16 |
17 | const trace = await adminController.getSSOTraceById(traceId);
18 |
19 | res.json({ data: trace });
20 | };
21 |
22 | export default handler;
23 |
--------------------------------------------------------------------------------
/pages/api/admin/sso-traces/index.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from 'next';
2 | import jackson from '@lib/jackson';
3 | import { defaultHandler } from '@lib/api';
4 | import { parsePaginateApiParams } from '@lib/utils';
5 |
6 | const handler = async (req: NextApiRequest, res: NextApiResponse) => {
7 | await defaultHandler(req, res, {
8 | GET: handleGET,
9 | });
10 | };
11 |
12 | // Get SAML Traces
13 | const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
14 | const { adminController } = await jackson();
15 |
16 | const { pageOffset, pageLimit, pageToken } = parsePaginateApiParams(req.query);
17 |
18 | const tracesPaginated = await adminController.getAllSSOTraces(pageOffset, pageLimit, pageToken);
19 |
20 | if (tracesPaginated.pageToken) {
21 | res.setHeader('jackson-pagetoken', tracesPaginated.pageToken);
22 | }
23 |
24 | res.json({ data: tracesPaginated.data });
25 | };
26 |
27 | export default handler;
28 |
--------------------------------------------------------------------------------
/pages/api/branding.ts:
--------------------------------------------------------------------------------
1 | export { default } from 'ee/branding/api/index';
2 |
--------------------------------------------------------------------------------
/pages/api/federated-saml/sso.ts:
--------------------------------------------------------------------------------
1 | export { default } from '@ee/identity-federation/api/sso';
2 |
--------------------------------------------------------------------------------
/pages/api/health.ts:
--------------------------------------------------------------------------------
1 | import { NextApiRequest, NextApiResponse } from 'next';
2 |
3 | import jackson from '@lib/jackson';
4 | import packageInfo from '../../package.json';
5 | import { logger } from '@lib/logger';
6 |
7 | export default async function handler(req: NextApiRequest, res: NextApiResponse) {
8 | try {
9 | if (req.method !== 'GET') {
10 | throw new Error('Method not allowed');
11 | }
12 | await jackson();
13 | res.status(200).json({
14 | version: packageInfo.version,
15 | });
16 | } catch (err: any) {
17 | logger.error(err, 'HealthCheck failed');
18 | const { statusCode = 503 } = err;
19 | res.status(statusCode).json({});
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/pages/api/hello.ts:
--------------------------------------------------------------------------------
1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
2 | import type { NextApiRequest, NextApiResponse } from 'next';
3 |
4 | type Data = {
5 | name: string;
6 | };
7 |
8 | export default function handler(req: NextApiRequest, res: NextApiResponse) {
9 | res.status(200).json({ name: 'Jules Winnfield' });
10 | }
11 |
--------------------------------------------------------------------------------
/pages/api/identity-federation/oidc/idp-login/[fedAppId].ts:
--------------------------------------------------------------------------------
1 | export { default } from '@ee/identity-federation/api/oidc/idp-login/[fedAppId]';
2 |
--------------------------------------------------------------------------------
/pages/api/identity-federation/sso.ts:
--------------------------------------------------------------------------------
1 | export { default } from '@ee/identity-federation/api/sso';
2 |
--------------------------------------------------------------------------------
/pages/api/import-hack.ts:
--------------------------------------------------------------------------------
1 | // Leave the openid-client import to get nextjs to leave the library in node_modules after build
2 | import * as dummy from 'openid-client';
3 | import * as jose from 'jose';
4 |
5 | import type { NextApiRequest, NextApiResponse } from 'next';
6 |
7 | export default function handler(req: NextApiRequest, res: NextApiResponse) {
8 | const unused = dummy; // eslint-disable-line @typescript-eslint/no-unused-vars
9 | const unused2 = jose; // eslint-disable-line @typescript-eslint/no-unused-vars
10 | res.status(200).json({});
11 | }
12 |
--------------------------------------------------------------------------------
/pages/api/internals/product/[productId].ts:
--------------------------------------------------------------------------------
1 | export { default } from 'ee/product/api/[productId]';
2 |
--------------------------------------------------------------------------------
/pages/api/internals/product/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from '@ee/product/api/index';
2 |
--------------------------------------------------------------------------------
/pages/api/logout/callback.ts:
--------------------------------------------------------------------------------
1 | import jackson from '@lib/jackson';
2 | import { NextApiRequest, NextApiResponse } from 'next';
3 |
4 | export default async function handler(req: NextApiRequest, res: NextApiResponse) {
5 | if (req.method !== 'POST' && req.method !== 'GET') {
6 | throw { message: 'Method not allowed', statusCode: 405 };
7 | }
8 |
9 | let body = req.body;
10 | if (req.method === 'GET') {
11 | body = req.query;
12 | }
13 |
14 | const { SAMLResponse, RelayState } = body;
15 |
16 | try {
17 | const { logoutController } = await jackson();
18 |
19 | const { redirectUrl } = await logoutController.handleResponse({
20 | SAMLResponse,
21 | RelayState,
22 | });
23 |
24 | res.redirect(302, redirectUrl);
25 | } catch (err: any) {
26 | const { message, statusCode = 500 } = err;
27 |
28 | res.status(statusCode).send(message);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/pages/api/logout/index.ts:
--------------------------------------------------------------------------------
1 | import jackson from '@lib/jackson';
2 | import { logger } from '@lib/logger';
3 | import { NextApiRequest, NextApiResponse } from 'next';
4 |
5 | export default async function handler(req: NextApiRequest, res: NextApiResponse) {
6 | if (req.method !== 'GET') {
7 | throw { message: 'Method not allowed', statusCode: 405 };
8 | }
9 |
10 | const { nameId, tenant, product, redirectUrl } = req.query;
11 |
12 | try {
13 | const { logoutController } = await jackson();
14 |
15 | const { logoutUrl, logoutForm } = await logoutController.createRequest({
16 | nameId: nameId,
17 | tenant: tenant,
18 | product: product,
19 | redirectUrl: redirectUrl,
20 | });
21 |
22 | if (logoutUrl) {
23 | res.redirect(302, logoutUrl);
24 | } else {
25 | res.setHeader('Content-Type', 'text/html; charset=utf-8');
26 | res.send(logoutForm);
27 | }
28 | } catch (err: any) {
29 | logger.error(err, 'error in creating SAML SLO request');
30 | const { message, statusCode = 500 } = err;
31 |
32 | res.status(statusCode).send(message);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/pages/api/oauth/jwks.ts:
--------------------------------------------------------------------------------
1 | import { NextApiRequest, NextApiResponse } from 'next';
2 | import jackson from '@lib/jackson';
3 |
4 | export default async function handler(req: NextApiRequest, res: NextApiResponse) {
5 | if (req.method !== 'GET') {
6 | throw { message: 'Method not allowed', statusCode: 405 };
7 | }
8 | const { oidcDiscoveryController } = await jackson();
9 | const jwks = await oidcDiscoveryController.jwks();
10 |
11 | const response = JSON.stringify(jwks, null, 2);
12 | res.status(200).setHeader('Content-Type', 'application/json').send(response);
13 | }
14 |
--------------------------------------------------------------------------------
/pages/api/oauth/token.ts:
--------------------------------------------------------------------------------
1 | import { NextApiRequest, NextApiResponse } from 'next';
2 |
3 | import jackson from '@lib/jackson';
4 | import { cors } from '@lib/middleware';
5 | import { logger } from '@lib/logger';
6 |
7 | export default async function handler(req: NextApiRequest, res: NextApiResponse) {
8 | try {
9 | await cors(req, res);
10 |
11 | if (req.method !== 'POST') {
12 | throw { message: 'Method not allowed', statusCode: 405 };
13 | }
14 |
15 | const { oauthController } = await jackson();
16 | const authHeader = req.headers['authorization'];
17 | const result = await oauthController.token(req.body, authHeader);
18 |
19 | res.json(result);
20 | } catch (err: any) {
21 | logger.error(err, 'Token error');
22 | const { message, statusCode = 500 } = err;
23 |
24 | res.status(statusCode).send(message);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/pages/api/scim/oauth/authorize.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from 'next';
2 |
3 | import jackson from '@lib/jackson';
4 |
5 | const handler = async (req: NextApiRequest, res: NextApiResponse) => {
6 | const { method } = req;
7 |
8 | if (method !== 'GET') {
9 | return res
10 | .setHeader('Allow', 'GET')
11 | .status(405)
12 | .json({ error: { message: `Method ${method} Not Allowed` } });
13 | }
14 |
15 | const { directoryId } = req.query as { directoryId: string };
16 |
17 | try {
18 | const { directorySyncController } = await jackson();
19 |
20 | const { data, error } = await directorySyncController.google.generateAuthorizationUrl({
21 | directoryId,
22 | });
23 |
24 | if (error) {
25 | throw error;
26 | }
27 |
28 | res.redirect(302, data.authorizationUrl).end();
29 | return;
30 | } catch (err: any) {
31 | const { message, statusCode = 500 } = err;
32 |
33 | return res.status(statusCode).json({ error: { message } });
34 | }
35 | };
36 |
37 | export default handler;
38 |
--------------------------------------------------------------------------------
/pages/api/setup/[token]/directory-sync/providers.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from 'next';
2 | import jackson from '@lib/jackson';
3 |
4 | const handler = async (req: NextApiRequest, res: NextApiResponse) => {
5 | const { method } = req;
6 |
7 | switch (method) {
8 | case 'GET':
9 | return await handleGET(req, res);
10 | default:
11 | res.setHeader('Allow', 'GET');
12 | res.status(405).json({ error: { message: `Method ${method} Not Allowed` } });
13 | }
14 | };
15 |
16 | // Get the directory providers
17 | const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
18 | const { directorySyncController } = await jackson();
19 |
20 | const providers = directorySyncController.providers();
21 |
22 | return res.json({ data: providers });
23 | };
24 |
25 | export default handler;
26 |
--------------------------------------------------------------------------------
/pages/api/v1/dsync/cron/process-events.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from 'next';
2 |
3 | import jackson from '@lib/jackson';
4 |
5 | // Process the dsync events queue in Jackson
6 | const handler = async (req: NextApiRequest, res: NextApiResponse) => {
7 | try {
8 | const { directorySyncController } = await jackson();
9 |
10 | directorySyncController.events.batch.process();
11 |
12 | res.json({ message: 'Processing started' });
13 | } catch (e: any) {
14 | res.status(500).json({ message: e.message || 'Processing failed' });
15 | }
16 | };
17 |
18 | export default handler;
19 |
--------------------------------------------------------------------------------
/pages/api/v1/dsync/cron/sync-google.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from 'next';
2 |
3 | import jackson from '@lib/jackson';
4 |
5 | // Sync Google Workspace with Jackson
6 | const handler = async (req: NextApiRequest, res: NextApiResponse) => {
7 | try {
8 | const { directorySyncController } = await jackson();
9 |
10 | directorySyncController.sync();
11 |
12 | res.json({ message: 'Sync started' });
13 | } catch (e: any) {
14 | res.status(500).json({ message: e.message || 'Sync failed' });
15 | }
16 | };
17 |
18 | export default handler;
19 |
--------------------------------------------------------------------------------
/pages/api/v1/dsync/product.ts:
--------------------------------------------------------------------------------
1 | import { defaultHandler } from '@lib/api';
2 | import jackson from '@lib/jackson';
3 | import { parsePaginateApiParams } from '@lib/utils';
4 | import { NextApiRequest, NextApiResponse } from 'next';
5 |
6 | export default async function handler(req: NextApiRequest, res: NextApiResponse) {
7 | await defaultHandler(req, res, {
8 | GET: handleGET,
9 | });
10 | }
11 |
12 | // Get the connections filtered by the product
13 | const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
14 | const { directorySyncController } = await jackson();
15 |
16 | const { product } = req.query as {
17 | product: string;
18 | };
19 |
20 | if (!product) {
21 | throw new Error('Please provide a product');
22 | }
23 |
24 | const { pageOffset, pageLimit, pageToken } = parsePaginateApiParams(req.query);
25 |
26 | const connections = await directorySyncController.directories.filterBy({
27 | product,
28 | pageOffset,
29 | pageLimit,
30 | pageToken,
31 | });
32 |
33 | return res.status(200).json(connections);
34 | };
35 |
--------------------------------------------------------------------------------
/pages/api/v1/dsync/setuplinks/product.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from 'next';
2 | import type { SetupLinkService } from '@boxyhq/saml-jackson';
3 | import jackson from '@lib/jackson';
4 | import { parsePaginateApiParams } from '@lib/utils';
5 | import { defaultHandler } from '@lib/api';
6 |
7 | const service: SetupLinkService = 'dsync';
8 |
9 | export default async function handler(req: NextApiRequest, res: NextApiResponse) {
10 | await defaultHandler(req, res, {
11 | GET: handleGET,
12 | });
13 | }
14 |
15 | // Get the setup links filtered by the product
16 | const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
17 | const { setupLinkController } = await jackson();
18 |
19 | const { product } = req.query as {
20 | product: string;
21 | };
22 |
23 | if (!product) {
24 | throw new Error('Please provide a product');
25 | }
26 |
27 | const { pageOffset, pageLimit, pageToken } = parsePaginateApiParams(req.query);
28 |
29 | const setupLinks = await setupLinkController.filterBy({
30 | product,
31 | service,
32 | pageOffset,
33 | pageLimit,
34 | pageToken,
35 | });
36 |
37 | res.json(setupLinks);
38 | };
39 |
--------------------------------------------------------------------------------
/pages/api/v1/identity-federation/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from '@ee/identity-federation/api/v1/index';
2 |
--------------------------------------------------------------------------------
/pages/api/v1/identity-federation/product.ts:
--------------------------------------------------------------------------------
1 | export { default } from '@ee/identity-federation/api/v1/product';
2 |
--------------------------------------------------------------------------------
/pages/api/v1/saml/config/exists.ts:
--------------------------------------------------------------------------------
1 | // Maintain /config path for backward compatibility
2 |
3 | import jackson from '@lib/jackson';
4 | import { NextApiRequest, NextApiResponse } from 'next';
5 | import type { GetConfigQuery } from '@boxyhq/saml-jackson';
6 | import { logger } from '@lib/logger';
7 |
8 | export default async function handler(req: NextApiRequest, res: NextApiResponse) {
9 | try {
10 | const { connectionAPIController } = await jackson();
11 | if (req.method === 'GET') {
12 | const rsp = await connectionAPIController.getConfig(req.query as GetConfigQuery);
13 | if (Object.keys(rsp).length === 0) {
14 | res.status(404).send({});
15 | } else {
16 | res.status(204).end();
17 | }
18 | } else {
19 | throw { message: 'Method not allowed', statusCode: 405 };
20 | }
21 | } catch (err: any) {
22 | logger.error(err, 'config api error');
23 | const { message, statusCode = 500 } = err;
24 |
25 | res.status(statusCode).send(message);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/pages/api/v1/sso-traces/index.ts:
--------------------------------------------------------------------------------
1 | import { defaultHandler } from '@lib/api';
2 | import jackson from '@lib/jackson';
3 | import { NextApiRequest, NextApiResponse } from 'next';
4 |
5 | export default async function handler(req: NextApiRequest, res: NextApiResponse) {
6 | await defaultHandler(req, res, {
7 | GET: handleGET,
8 | });
9 | }
10 |
11 | // Get the saml trace by id
12 | const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
13 | const { adminController } = await jackson();
14 |
15 | const { id } = req.query as { id: string };
16 |
17 | const trace = await adminController.getSSOTraceById(id);
18 |
19 | res.json({ data: trace });
20 | };
21 |
--------------------------------------------------------------------------------
/pages/api/v1/sso-traces/product/count.ts:
--------------------------------------------------------------------------------
1 | import { defaultHandler } from '@lib/api';
2 | import jackson from '@lib/jackson';
3 | import { NextApiRequest, NextApiResponse } from 'next';
4 |
5 | export default async function handler(req: NextApiRequest, res: NextApiResponse) {
6 | await defaultHandler(req, res, {
7 | GET: handleGET,
8 | });
9 | }
10 |
11 | // Get the sso traces filtered by the product
12 | const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
13 | const { adminController } = await jackson();
14 |
15 | const { product } = req.query as {
16 | product: string;
17 | };
18 |
19 | const count = await adminController.countByProduct(product);
20 |
21 | res.json({ count });
22 | };
23 |
--------------------------------------------------------------------------------
/pages/api/v1/sso/exists.ts:
--------------------------------------------------------------------------------
1 | import jackson from '@lib/jackson';
2 | import { NextApiRequest, NextApiResponse } from 'next';
3 | import type { GetConnectionsQuery } from '@boxyhq/saml-jackson';
4 | import { defaultHandler } from '@lib/api';
5 |
6 | export default async function handler(req: NextApiRequest, res: NextApiResponse) {
7 | await defaultHandler(req, res, {
8 | GET: handleGET,
9 | });
10 | }
11 |
12 | // Check if a connection exists
13 | const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
14 | const { connectionAPIController } = await jackson();
15 |
16 | const connections = await connectionAPIController.getConnections(req.query as GetConnectionsQuery);
17 |
18 | if (connections.length === 0) {
19 | return res.status(404).send({});
20 | } else {
21 | res.status(204).end();
22 | return;
23 | }
24 | };
25 |
--------------------------------------------------------------------------------
/pages/api/v1/sso/product.ts:
--------------------------------------------------------------------------------
1 | import { defaultHandler } from '@lib/api';
2 | import jackson from '@lib/jackson';
3 | import { parsePaginateApiParams } from '@lib/utils';
4 | import { NextApiRequest, NextApiResponse } from 'next';
5 |
6 | export default async function handler(req: NextApiRequest, res: NextApiResponse) {
7 | await defaultHandler(req, res, {
8 | GET: handleGET,
9 | });
10 | }
11 |
12 | // Get the connections filtered by the product
13 | const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
14 | const { connectionAPIController } = await jackson();
15 |
16 | const { product } = req.query as {
17 | product: string;
18 | };
19 |
20 | const { pageOffset, pageLimit, pageToken } = parsePaginateApiParams(req.query);
21 |
22 | const connections = await connectionAPIController.getConnectionsByProduct({
23 | product,
24 | pageOffset,
25 | pageLimit,
26 | pageToken,
27 | });
28 |
29 | return res.status(200).json(connections.data);
30 | };
31 |
--------------------------------------------------------------------------------
/pages/api/v1/sso/setuplinks/product.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from 'next';
2 | import type { SetupLinkService } from '@boxyhq/saml-jackson';
3 | import jackson from '@lib/jackson';
4 | import { parsePaginateApiParams } from '@lib/utils';
5 | import { defaultHandler } from '@lib/api';
6 |
7 | const service: SetupLinkService = 'sso';
8 |
9 | export default async function handler(req: NextApiRequest, res: NextApiResponse) {
10 | await defaultHandler(req, res, {
11 | GET: handleGET,
12 | });
13 | }
14 |
15 | // Get the setup links filtered by the product
16 | const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
17 | const { setupLinkController } = await jackson();
18 |
19 | const { product } = req.query as {
20 | product: string;
21 | };
22 |
23 | if (!product) {
24 | throw new Error('Please provide a product');
25 | }
26 |
27 | const { pageOffset, pageLimit, pageToken } = parsePaginateApiParams(req.query);
28 |
29 | const setupLinks = await setupLinkController.filterBy({
30 | product,
31 | service,
32 | pageOffset,
33 | pageLimit,
34 | pageToken,
35 | });
36 |
37 | res.json(setupLinks);
38 | };
39 |
--------------------------------------------------------------------------------
/pages/api/v1/stats/index.ts:
--------------------------------------------------------------------------------
1 | import { defaultHandler } from '@lib/api';
2 | import jackson from '@lib/jackson';
3 | import { NextApiRequest, NextApiResponse } from 'next';
4 |
5 | export default async function handler(req: NextApiRequest, res: NextApiResponse) {
6 | await defaultHandler(req, res, {
7 | GET: handleGET,
8 | });
9 | }
10 |
11 | const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
12 | const { connectionAPIController, directorySyncController, identityFederationController } = await jackson();
13 |
14 | const sso_connections_count = await connectionAPIController.getCount();
15 | const dsync_connections_count = await directorySyncController.directories.getCount();
16 | const identity_federation_count = await identityFederationController.app.getCount();
17 |
18 | return res.json({
19 | data: {
20 | sso_connections: sso_connections_count,
21 | dsync_connections: dsync_connections_count,
22 | identity_federation_apps: identity_federation_count,
23 | },
24 | });
25 | };
26 |
--------------------------------------------------------------------------------
/pages/api/well-known/idp-metadata.ts:
--------------------------------------------------------------------------------
1 | export { default } from '@ee/identity-federation/api/metadata';
2 |
--------------------------------------------------------------------------------
/pages/api/well-known/openid-configuration.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from 'next';
2 | import jackson from '@lib/jackson';
3 | import { cors } from '@lib/middleware';
4 |
5 | export default async function handler(req: NextApiRequest, res: NextApiResponse) {
6 | if (req.method !== 'GET') {
7 | throw { message: 'Method not allowed', statusCode: 405 };
8 | }
9 |
10 | await cors(req, res);
11 |
12 | const { oidcDiscoveryController } = await jackson();
13 | const config = await oidcDiscoveryController.openidConfig();
14 |
15 | const response = JSON.stringify(config, null, 2);
16 | res.status(200).setHeader('Content-Type', 'application/json').send(response);
17 | }
18 |
--------------------------------------------------------------------------------
/pages/api/well-known/saml.cer.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from 'next';
2 | import jackson from '@lib/jackson';
3 |
4 | export default async function handler(req: NextApiRequest, res: NextApiResponse) {
5 | if (req.method !== 'GET') {
6 | throw { message: 'Method not allowed', statusCode: 405 };
7 | }
8 |
9 | const { spConfig } = await jackson();
10 | const config = await spConfig.get();
11 |
12 | res.status(200).setHeader('Content-Type', 'application/x-x509-ca-cert').send(config.publicKey);
13 | }
14 |
--------------------------------------------------------------------------------
/pages/api/well-known/sp-metadata.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from 'next';
2 | import jackson from '@lib/jackson';
3 |
4 | export default async function handler(req: NextApiRequest, res: NextApiResponse) {
5 | if (req.method !== 'GET') {
6 | throw { message: 'Method not allowed', statusCode: 405 };
7 | }
8 |
9 | const { encryption = 'false' } = req.query;
10 |
11 | const { spConfig } = await jackson();
12 |
13 | res
14 | .status(200)
15 | .setHeader('Content-Type', 'text/xml')
16 | .send(await spConfig.toXMLMetadata(encryption === 'true'));
17 | }
18 |
--------------------------------------------------------------------------------
/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import { Loading } from '@boxyhq/internal-ui';
2 | import type { NextPage } from 'next';
3 | import { useRouter } from 'next/router';
4 | import { useEffect } from 'react';
5 |
6 | const Home: NextPage = () => {
7 | const router = useRouter();
8 |
9 | useEffect(() => {
10 | router.push('/admin/sso-connection');
11 | }, [router]);
12 |
13 | return (
14 |
15 |
16 |
17 | );
18 | };
19 |
20 | export default Home;
21 |
--------------------------------------------------------------------------------
/pages/setup/[token]/directory-sync/[directoryId]/edit.tsx:
--------------------------------------------------------------------------------
1 | import type { NextPage } from 'next';
2 | import { useRouter } from 'next/router';
3 | import React from 'react';
4 | import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
5 | import EditDirectory from '@components/dsync/EditDirectory';
6 |
7 | const DirectoryEditPage: NextPage = () => {
8 | const router = useRouter();
9 |
10 | const { token, directoryId } = router.query as { token: string; directoryId: string };
11 |
12 | return ;
13 | };
14 |
15 | export const getServerSideProps = async (context) => {
16 | const { locale } = context;
17 |
18 | return {
19 | props: {
20 | ...(await serverSideTranslations(locale, ['common'])),
21 | },
22 | };
23 | };
24 |
25 | export default DirectoryEditPage;
26 |
--------------------------------------------------------------------------------
/pages/setup/[token]/directory-sync/index.tsx:
--------------------------------------------------------------------------------
1 | import type { NextPage } from 'next';
2 | import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
3 | import { useRouter } from 'next/router';
4 | import DirectoryList from '@components/dsync/DirectoryList';
5 |
6 | const DirectoryIndexPage: NextPage = () => {
7 | const router = useRouter();
8 |
9 | const { token } = router.query as { token: string };
10 |
11 | return ;
12 | };
13 |
14 | export const getServerSideProps = async (context) => {
15 | const { locale } = context;
16 |
17 | return {
18 | props: {
19 | ...(await serverSideTranslations(locale, ['common'])),
20 | },
21 | };
22 | };
23 |
24 | export default DirectoryIndexPage;
25 |
--------------------------------------------------------------------------------
/pages/setup/[token]/directory-sync/new.tsx:
--------------------------------------------------------------------------------
1 | import type { NextPage, InferGetServerSidePropsType, GetServerSidePropsContext } from 'next';
2 | import React from 'react';
3 | import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
4 | import { useRouter } from 'next/router';
5 | import CreateDirectory from '@components/dsync/CreateDirectory';
6 |
7 | const DirectoryCreatePage: NextPage> = () => {
8 | const router = useRouter();
9 |
10 | const { token } = router.query as { token: string };
11 |
12 | return ;
13 | };
14 |
15 | export const getServerSideProps = async ({ locale }: GetServerSidePropsContext) => {
16 | return {
17 | props: {
18 | ...(locale ? await serverSideTranslations(locale, ['common']) : {}),
19 | },
20 | };
21 | };
22 |
23 | export default DirectoryCreatePage;
24 |
--------------------------------------------------------------------------------
/pages/setup/[token]/sso-connection/index.tsx:
--------------------------------------------------------------------------------
1 | import type { NextPage } from 'next';
2 | import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
3 | import ConnectionList from '@components/connection/ConnectionList';
4 | import { useRouter } from 'next/router';
5 | import useIdpEntityID from '@lib/ui/hooks/useIdpEntityID';
6 |
7 | const ConnectionsIndexPage: NextPage = () => {
8 | const router = useRouter();
9 | const { idpEntityID } = useIdpEntityID();
10 |
11 | const { token } = router.query as { token: string };
12 |
13 | return ;
14 | };
15 |
16 | export async function getServerSideProps({ locale }) {
17 | return {
18 | props: {
19 | ...(await serverSideTranslations(locale, ['common'])),
20 | },
21 | };
22 | }
23 |
24 | export default ConnectionsIndexPage;
25 |
--------------------------------------------------------------------------------
/pages/well-known/idp-configuration.tsx:
--------------------------------------------------------------------------------
1 | import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
2 | import jackson from '@lib/jackson';
3 |
4 | export { default } from '@ee/identity-federation/pages/metadata';
5 |
6 | export async function getServerSideProps({ locale }) {
7 | const { identityFederationController, checkLicense } = await jackson();
8 |
9 | const metadata = await identityFederationController.app.getMetadata();
10 |
11 | return {
12 | props: {
13 | ...(await serverSideTranslations(locale, ['common'])),
14 | metadata,
15 | hasValidLicense: await checkLicense(),
16 | },
17 | };
18 | }
19 |
--------------------------------------------------------------------------------
/pages/well-known/index.tsx:
--------------------------------------------------------------------------------
1 | import { WellKnownURLs } from '@boxyhq/internal-ui';
2 | import type { NextPage, GetServerSidePropsContext, InferGetServerSidePropsType } from 'next';
3 | import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
4 | import { adminPortal } from '@lib/env';
5 |
6 | const WellKnownURLsIndex: NextPage> = ({
7 | hideIdentityFederation,
8 | }) => {
9 | return (
10 |
11 |
12 |
13 | );
14 | };
15 |
16 | export async function getStaticProps({ locale }: GetServerSidePropsContext) {
17 | return {
18 | props: {
19 | hideIdentityFederation: adminPortal.hideIdentityFederation,
20 | ...(locale ? await serverSideTranslations(locale, ['common']) : {}),
21 | },
22 | };
23 | }
24 |
25 | export default WellKnownURLsIndex;
26 |
--------------------------------------------------------------------------------
/postbuild.ts:
--------------------------------------------------------------------------------
1 | import path from 'node:path';
2 | import { cpSync } from 'node:fs';
3 |
4 | const folders = [
5 | { src: 'public', dst: path.join('.next', 'standalone', 'public') },
6 | { src: path.join('.next', 'static'), dst: path.join('.next', 'standalone', '.next', 'static') },
7 | ];
8 |
9 | try {
10 | folders.forEach(({ src, dst }) => cpSync(src, dst, { recursive: true }));
11 | console.log(`copied public/static assets to standalone build`);
12 | } catch (err) {
13 | console.error(`failed copying public/static assets to standalone build`, err);
14 | }
15 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boxyhq/jackson/9e4b811cf405fe95e0a05647fab25e54e16127f2/public/favicon.ico
--------------------------------------------------------------------------------
/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boxyhq/jackson/9e4b811cf405fe95e0a05647fab25e54e16127f2/public/logo.png
--------------------------------------------------------------------------------
/samljackson480.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boxyhq/jackson/9e4b811cf405fe95e0a05647fab25e54e16127f2/samljackson480.gif
--------------------------------------------------------------------------------
/skaffold-demo-mocksaml-services.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: skaffold/v4beta10
2 | kind: Config
3 | manifests:
4 | kustomize:
5 | paths:
6 | - ./kustomize/overlays/demo/services/mocksaml
7 |
--------------------------------------------------------------------------------
/skaffold-demo-mocksaml.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: skaffold/v4beta10
2 | kind: Config
3 | manifests:
4 | kustomize:
5 | paths:
6 | - ./kustomize/overlays/demo/mocksaml
7 |
--------------------------------------------------------------------------------
/skaffold-demo-services.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: skaffold/v4beta10
2 | kind: Config
3 | manifests:
4 | kustomize:
5 | paths:
6 | - ./kustomize/overlays/demo/services
7 |
--------------------------------------------------------------------------------
/skaffold-demo.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: skaffold/v4beta10
2 | kind: Config
3 | manifests:
4 | kustomize:
5 | paths:
6 | - ./kustomize/overlays/demo
7 |
--------------------------------------------------------------------------------
/skaffold-dynamodb.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: skaffold/v4beta10
2 | kind: Config
3 | build:
4 | local:
5 | push: false
6 | platforms: ['linux/amd64', 'linux/arm64']
7 | artifacts:
8 | - image: boxyhq/jackson-local
9 | context: ./
10 | docker:
11 | dockerfile: ./Dockerfile
12 | manifests:
13 | kustomize:
14 | paths:
15 | - ./kustomize/overlays/dynamodb
16 |
--------------------------------------------------------------------------------
/skaffold-mariadb.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: skaffold/v4beta10
2 | kind: Config
3 | build:
4 | local:
5 | push: false
6 | platforms: ['linux/amd64', 'linux/arm64']
7 | artifacts:
8 | - image: boxyhq/jackson-local
9 | context: ./
10 | docker:
11 | dockerfile: ./Dockerfile
12 | manifests:
13 | kustomize:
14 | paths:
15 | - ./kustomize/overlays/mariadb
16 |
--------------------------------------------------------------------------------
/skaffold-mongo.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: skaffold/v4beta10
2 | kind: Config
3 | build:
4 | local:
5 | push: false
6 | platforms: ['linux/amd64', 'linux/arm64']
7 | artifacts:
8 | - image: boxyhq/jackson-local
9 | context: ./
10 | docker:
11 | dockerfile: ./Dockerfile
12 | manifests:
13 | kustomize:
14 | paths:
15 | - ./kustomize/overlays/mongo
16 |
--------------------------------------------------------------------------------
/skaffold-mssql.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: skaffold/v4beta10
2 | kind: Config
3 | build:
4 | local:
5 | push: false
6 | platforms: ['linux/amd64', 'linux/arm64']
7 | artifacts:
8 | - image: boxyhq/jackson-local
9 | context: ./
10 | docker:
11 | dockerfile: ./Dockerfile
12 | manifests:
13 | kustomize:
14 | paths:
15 | - ./kustomize/overlays/mssql
16 |
--------------------------------------------------------------------------------
/skaffold-mysql.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: skaffold/v4beta10
2 | kind: Config
3 | build:
4 | local:
5 | push: false
6 | platforms: ['linux/amd64', 'linux/arm64']
7 | artifacts:
8 | - image: boxyhq/jackson-local
9 | context: ./
10 | docker:
11 | dockerfile: ./Dockerfile
12 | manifests:
13 | kustomize:
14 | paths:
15 | - ./kustomize/overlays/mysql
16 |
--------------------------------------------------------------------------------
/skaffold-postgres.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: skaffold/v4beta10
2 | kind: Config
3 | build:
4 | local:
5 | push: false
6 | platforms: ['linux/amd64', 'linux/arm64']
7 | artifacts:
8 | - image: boxyhq/jackson-local
9 | context: ./
10 | docker:
11 | dockerfile: ./Dockerfile
12 | manifests:
13 | kustomize:
14 | paths:
15 | - ./kustomize/overlays/postgres
16 |
--------------------------------------------------------------------------------
/skaffold-prod-eu-mocksaml-services.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: skaffold/v4beta10
2 | kind: Config
3 | manifests:
4 | kustomize:
5 | paths:
6 | - ./kustomize/overlays/prod-eu/services/mocksaml
7 |
--------------------------------------------------------------------------------
/skaffold-prod-eu-mocksaml.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: skaffold/v4beta10
2 | kind: Config
3 | manifests:
4 | kustomize:
5 | paths:
6 | - ./kustomize/overlays/prod-eu/mocksaml
7 |
--------------------------------------------------------------------------------
/skaffold-prod-eu-services.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: skaffold/v4beta10
2 | kind: Config
3 | manifests:
4 | kustomize:
5 | paths:
6 | - ./kustomize/overlays/prod-eu/services
7 |
--------------------------------------------------------------------------------
/skaffold-prod-eu.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: skaffold/v4beta10
2 | kind: Config
3 | manifests:
4 | kustomize:
5 | paths:
6 | - ./kustomize/overlays/prod-eu
7 |
--------------------------------------------------------------------------------
/styles/sdk-override.module.css:
--------------------------------------------------------------------------------
1 | /* Override SDK styles */
2 | .sdk-input:focus,
3 | .sdk-select:focus-visible + span {
4 | /* Below styles copied from the tailwindcss/forms plugin */
5 | outline: 2px solid oklch(var(--bc) / 0.2);
6 | outline-offset: 2px;
7 | }
8 |
--------------------------------------------------------------------------------
/swagger/swagger-definition.js:
--------------------------------------------------------------------------------
1 | const packageInfo = require('../package.json');
2 |
3 | module.exports = {
4 | openapi: '3.0.3',
5 | info: {
6 | title: 'Enterprise SSO & Directory Sync',
7 | version: packageInfo.version,
8 | description: 'This is the API documentation for Polis.',
9 | termsOfService: '/tos',
10 | contact: {
11 | email: 'support@ory.sh',
12 | name: 'Polis API Support',
13 | },
14 | license: {
15 | name: 'Apache 2.0',
16 | url: 'https://www.apache.org/licenses/LICENSE-2.0.html',
17 | },
18 | },
19 | security: [
20 | {
21 | apiKey: [],
22 | },
23 | ],
24 | servers: [
25 | {
26 | url: 'http://localhost:5225',
27 | description: 'Local',
28 | },
29 | ],
30 | };
31 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | mode: 'jit',
3 | darkMode: 'class',
4 | content: [
5 | './pages/**/*.{js,ts,jsx,tsx}',
6 | './components/**/*.{js,ts,jsx,tsx}',
7 | './ee/**/*.{js,ts,jsx,tsx}',
8 | 'node_modules/daisyui/dist/**/*.js',
9 | 'node_modules/react-daisyui/dist/**/*.js',
10 | './internal-ui/src/**/*.{js,ts,jsx,tsx}',
11 | ],
12 | daisyui: {
13 | themes: [
14 | {
15 | boxyhq: {
16 | primary: '#25c2a0',
17 | secondary: '#303846',
18 | accent: '#570DF8',
19 | neutral: '#3D4451',
20 | 'base-100': '#FFFFFF',
21 | info: '#3ABFF8',
22 | success: '#36D399',
23 | warning: '#FBBD23',
24 | error: '#F87272',
25 | },
26 | },
27 | ],
28 | },
29 | plugins: [require('@tailwindcss/typography'), require('daisyui')],
30 | };
31 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "sourceMap": true,
4 | "target": "es5",
5 | "lib": ["dom", "dom.iterable", "esnext"],
6 | "allowJs": true,
7 | "skipLibCheck": true,
8 | "noImplicitAny": false,
9 | "strict": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "noEmit": true,
12 | "esModuleInterop": true,
13 | "module": "esnext",
14 | "moduleResolution": "node",
15 | "resolveJsonModule": true,
16 | "isolatedModules": true,
17 | "jsx": "preserve",
18 | "incremental": true,
19 | "experimentalDecorators": true,
20 | "baseUrl": ".",
21 | "downlevelIteration": true,
22 | "paths": {
23 | "react": ["./node_modules/@types/react"],
24 | "@components/*": ["components/*"],
25 | "@lib/*": ["lib/*"],
26 | "@styles/*": ["styles/*"],
27 | "@ee/*": ["ee/*"],
28 | },
29 | },
30 | "include": ["next-env.d.ts", "types/*.d.ts", "**/*.ts", "**/*.tsx"],
31 | "exclude": ["node_modules", "npm/typeorm.ts", "sdk"],
32 | "ts-node": {
33 | "compilerOptions": {
34 | "module": "CommonJS",
35 | },
36 | "require": ["tsconfig-paths/register"],
37 | },
38 | }
39 |
--------------------------------------------------------------------------------
/types/base.ts:
--------------------------------------------------------------------------------
1 | export type ApiSuccess = { data: T; pageToken?: string };
2 |
3 | export interface ApiError extends Error {
4 | info?: string;
5 | status: number;
6 | }
7 |
8 | export type PaginateApiParams = { pageOffset: number; pageLimit: number } & { pageToken?: string };
9 |
--------------------------------------------------------------------------------
/types/index.ts:
--------------------------------------------------------------------------------
1 | export * from './retraced';
2 | export * from './base';
3 |
--------------------------------------------------------------------------------
/types/retraced.ts:
--------------------------------------------------------------------------------
1 | export type AdminToken = {
2 | id: string;
3 | token: string;
4 | userId: string;
5 | disabled: boolean;
6 | };
7 |
8 | export type APIKey = {
9 | name: string;
10 | created: string;
11 | disabled: boolean;
12 | environment_id: string;
13 | project_id: string;
14 | token: string;
15 | };
16 |
17 | export type Environment = {
18 | id: string;
19 | name: string;
20 | };
21 |
22 | export type Project = {
23 | id: string;
24 | name: string;
25 | created: string;
26 | environments: Environment[];
27 | tokens: APIKey[];
28 | url?: string;
29 | };
30 |
31 | export type Group = {
32 | group_id: string;
33 | name: string;
34 | };
35 |
--------------------------------------------------------------------------------