├── .circleci └── config.yml ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.yml │ └── report_a_bug.yml ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml ├── stale.yml └── workflows │ └── semgrep.yml ├── .gitignore ├── .husky └── pre-commit ├── .npmignore ├── .prettierignore ├── .prettierrc.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── docs ├── authenticating-with-tenant.md ├── available-resource-config-formats.md ├── configuring-the-deploy-cli.md ├── excluding-from-management.md ├── how-to-contribute.md ├── keyword-replacement.md ├── multi-environment-workflow.md ├── resource-specific-documentation.md ├── terraform-provider.md ├── using-as-cli.md ├── using-as-node-module.md └── v8_MIGRATION_GUIDE.md ├── examples ├── directory │ ├── README.md │ ├── actions │ │ ├── action-example.json │ │ └── action-example │ │ │ └── code.js │ ├── clients │ │ ├── My Native app.json │ │ └── My SPA.json │ ├── config.json.example │ ├── connections │ │ └── facebook.json │ ├── database-connections │ │ └── users │ │ │ ├── change_email.js │ │ │ ├── change_password.js │ │ │ ├── create.js │ │ │ ├── database.json │ │ │ ├── delete.js │ │ │ ├── get_user.js │ │ │ ├── login.js │ │ │ └── verify.js │ ├── emails │ │ ├── blocked_account.html │ │ ├── blocked_account.json │ │ ├── enrollment_email.html │ │ ├── enrollment_email.json │ │ ├── mfa_oob_code.html │ │ ├── mfa_oob_code.json │ │ ├── provider.json │ │ ├── reset_email.html │ │ ├── reset_email.json │ │ ├── reset_email_by_code.html │ │ ├── reset_email_by_code.json │ │ ├── stolen_credentials.html │ │ ├── stolen_credentials.json │ │ ├── verify_email.html │ │ ├── verify_email.json │ │ ├── verify_email_by_code.html │ │ ├── verify_email_by_code.json │ │ ├── welcome_email.html │ │ └── welcome_email.json │ ├── flow-vault-connections │ │ └── New Auth0 M2M Con.json │ ├── flows │ │ └── Update KYC Name.json │ ├── forms │ │ └── New Kyc form.json │ ├── grants │ │ └── m2m_myapp_api.json │ ├── guardian │ │ ├── factors │ │ │ ├── email.json │ │ │ ├── otp.json │ │ │ ├── push-notification.json │ │ │ └── sms.json │ │ ├── phoneFactorMessageTypes.json │ │ ├── phoneFactorSelectedProvider.json │ │ ├── policies.json │ │ ├── providers │ │ │ └── sms-twilio.json │ │ └── templates │ │ │ └── sms.json │ ├── hooks │ │ ├── client-credentials-exchange.js │ │ └── client-credentials-exchange.json │ ├── network-acls │ │ └── Allow Specific Countries.json │ ├── pages │ │ ├── login.html │ │ ├── login.json │ │ ├── password_reset.html │ │ └── password_reset.json │ ├── phoneProviders │ │ └── provider.json │ ├── prompts │ │ ├── custom-text.json │ │ ├── prompts.json │ │ └── screenRenderSettings │ │ │ ├── login-id_login-id.json │ │ │ └── signup-id_signup-id.json │ ├── resource-servers │ │ └── My API.json │ ├── roles │ │ ├── Admin.json │ │ └── User.json │ ├── rules-configs │ │ └── SOME_SECRET.json │ ├── rules │ │ ├── Enrich-Identity-Token.js │ │ └── Enrich-Identity-Token.json │ ├── self-service-profiles │ │ └── self-service profile 1.json │ ├── tenant.json │ └── triggers │ │ └── triggers.json └── yaml │ ├── README.md │ ├── actions │ └── action-example │ │ └── code.js │ ├── config.json.example │ ├── databases │ └── users │ │ ├── change_email.js │ │ ├── change_password.js │ │ ├── create.js │ │ ├── delete.js │ │ ├── get_user.js │ │ ├── login.js │ │ └── verify.js │ ├── emails │ └── change_email.html │ ├── flows │ └── Update KYC Name.json │ ├── forms │ └── New Kyc form.json │ ├── hooks │ └── client-credentials-exchange.js │ ├── pages │ ├── error_page.html │ ├── guardian_multifactor.html │ ├── login.html │ └── password_reset.html │ ├── prompts │ └── screenRenderSettings │ │ ├── login-id_login-id.json │ │ └── signup-id_signup-id.json │ ├── rules │ └── enrich_tokens.js │ └── tenant.yaml ├── opslevel.yml ├── package-lock.json ├── package.json ├── src ├── args.ts ├── commands │ ├── export.ts │ ├── import.ts │ └── index.ts ├── configFactory.ts ├── context │ ├── defaults.ts │ ├── directory │ │ ├── handlers │ │ │ ├── actions.ts │ │ │ ├── attackProtection.ts │ │ │ ├── branding.ts │ │ │ ├── clientGrants.ts │ │ │ ├── clients.ts │ │ │ ├── connections.ts │ │ │ ├── customDomains.ts │ │ │ ├── databases.ts │ │ │ ├── emailProvider.ts │ │ │ ├── emailTemplates.ts │ │ │ ├── flowVaultConnections.ts │ │ │ ├── flows.ts │ │ │ ├── forms.ts │ │ │ ├── guardianFactorProviders.ts │ │ │ ├── guardianFactorTemplates.ts │ │ │ ├── guardianFactors.ts │ │ │ ├── guardianPhoneFactorMessageTypes.ts │ │ │ ├── guardianPhoneFactorSelectedProvider.ts │ │ │ ├── guardianPolicies.ts │ │ │ ├── hooks.ts │ │ │ ├── index.ts │ │ │ ├── logStreams.ts │ │ │ ├── networkACLs.ts │ │ │ ├── organizations.ts │ │ │ ├── pages.ts │ │ │ ├── phoneProvider.ts │ │ │ ├── prompts.ts │ │ │ ├── resourceServers.ts │ │ │ ├── roles.ts │ │ │ ├── rules.ts │ │ │ ├── rulesConfigs.ts │ │ │ ├── selfServiceProfiles.ts │ │ │ ├── tenant.ts │ │ │ ├── themes.ts │ │ │ └── triggers.ts │ │ └── index.ts │ ├── index.ts │ └── yaml │ │ ├── handlers │ │ ├── actions.ts │ │ ├── attackProtection.ts │ │ ├── branding.ts │ │ ├── clientGrants.ts │ │ ├── clients.ts │ │ ├── connections.ts │ │ ├── customDomains.ts │ │ ├── databases.ts │ │ ├── emailProvider.ts │ │ ├── emailTemplates.ts │ │ ├── flowVaultConnections.ts │ │ ├── flows.ts │ │ ├── forms.ts │ │ ├── guardianFactorProviders.ts │ │ ├── guardianFactorTemplates.ts │ │ ├── guardianFactors.ts │ │ ├── guardianPhoneFactorMessageTypes.ts │ │ ├── guardianPhoneFactorSelectedProvider.ts │ │ ├── guardianPolicies.ts │ │ ├── hooks.ts │ │ ├── index.ts │ │ ├── logStreams.ts │ │ ├── networkACLs.ts │ │ ├── organizations.ts │ │ ├── pages.ts │ │ ├── phoneProvider.ts │ │ ├── prompts.ts │ │ ├── resourceServers.ts │ │ ├── roles.ts │ │ ├── rules.ts │ │ ├── rulesConfigs.ts │ │ ├── selfServiceProfiles.ts │ │ ├── tenant.ts │ │ ├── themes.ts │ │ └── triggers.ts │ │ └── index.ts ├── index.ts ├── keywordPreservation.ts ├── logger.ts ├── readonly.ts ├── sessionDurationsToMinutes.ts ├── tools │ ├── auth0 │ │ ├── client.ts │ │ ├── handlers │ │ │ ├── actions.ts │ │ │ ├── attackProtection.ts │ │ │ ├── branding.ts │ │ │ ├── clientGrants.ts │ │ │ ├── clients.ts │ │ │ ├── connections.ts │ │ │ ├── customDomains.ts │ │ │ ├── databases.ts │ │ │ ├── default.ts │ │ │ ├── emailProvider.ts │ │ │ ├── emailTemplates.ts │ │ │ ├── flowVaultConnections.ts │ │ │ ├── flows.ts │ │ │ ├── forms.ts │ │ │ ├── guardianFactorProviders.ts │ │ │ ├── guardianFactorTemplates.ts │ │ │ ├── guardianFactors.ts │ │ │ ├── guardianPhoneFactorMessageTypes.ts │ │ │ ├── guardianPhoneFactorSelectedProvider.ts │ │ │ ├── guardianPolicies.ts │ │ │ ├── hooks.ts │ │ │ ├── index.ts │ │ │ ├── logStreams.ts │ │ │ ├── networkACLs.ts │ │ │ ├── organizations.ts │ │ │ ├── pages.ts │ │ │ ├── phoneProvider.ts │ │ │ ├── prompts.ts │ │ │ ├── resourceServers.ts │ │ │ ├── roles.ts │ │ │ ├── rules.ts │ │ │ ├── rulesConfigs.ts │ │ │ ├── scimHandler.ts │ │ │ ├── selfServiceProfiles.ts │ │ │ ├── tenant.ts │ │ │ ├── themes.ts │ │ │ └── triggers.ts │ │ ├── index.ts │ │ └── schema.ts │ ├── calculateChanges.ts │ ├── constants.ts │ ├── deploy.ts │ ├── index.ts │ ├── utils.ts │ └── validationError.ts ├── types.ts └── utils.ts ├── test ├── configFactory.test.ts ├── context │ ├── context.test.js │ ├── defaults.test.js │ ├── directory │ │ ├── actions.test.js │ │ ├── attackProtection.test.js │ │ ├── branding.test.js │ │ ├── clientGrants.test.js │ │ ├── clients.test.js │ │ ├── connections.test.js │ │ ├── context.test.js │ │ ├── databases.test.js │ │ ├── emailProvider.test.js │ │ ├── emailTemplates.test.js │ │ ├── flowVaultConnections.test.js │ │ ├── flows.test.js │ │ ├── forms.test.js │ │ ├── guardianFactorProviders.test.js │ │ ├── guardianFactorTemplates.test.js │ │ ├── guardianFactors.test.js │ │ ├── guardianPhoneFactorMessageTypes.test.js │ │ ├── guardianPhoneFactorSelectedProvider.test.js │ │ ├── guardianPolicies.test.js │ │ ├── hooks.test.js │ │ ├── networkACLs.test.ts │ │ ├── organizations.test.js │ │ ├── pages.test.js │ │ ├── phoneProviders.test.js │ │ ├── prompts.test.ts │ │ ├── resourceServers.js │ │ ├── roles.test.js │ │ ├── rules.test.js │ │ ├── rulesConfigs.js │ │ ├── selfServiceProfiles.test.js │ │ ├── tenant.test.js │ │ ├── themes.test.js │ │ └── triggers.test.js │ └── yaml │ │ ├── actions.test.js │ │ ├── attackProtection.test.js │ │ ├── branding.test.js │ │ ├── clientGrants.test.js │ │ ├── clients.test.js │ │ ├── connections.test.js │ │ ├── context.test.js │ │ ├── customDomains.test.ts │ │ ├── databases.test.js │ │ ├── emailProvider.test.js │ │ ├── emailTemplates.test.js │ │ ├── flowVaultConnections.test.js │ │ ├── flows.test.js │ │ ├── forms.test.js │ │ ├── guardianFactorProviders.test.js │ │ ├── guardianFactorTemplates.test.js │ │ ├── guardianFactors.test.js │ │ ├── guardianPhoneFactorMessageTypes.test.js │ │ ├── guardianPhoneFactorSelectedProvider.test.js │ │ ├── guardianPolicies.test.js │ │ ├── hooks.test.js │ │ ├── logStreams.test.js │ │ ├── networkACLs.test.ts │ │ ├── organizations.test.js │ │ ├── pages.test.js │ │ ├── phoneProviders.test.js │ │ ├── prompts.test.ts │ │ ├── resourceServers.test.js │ │ ├── roles.test.js │ │ ├── rules.test.js │ │ ├── rulesConfigs.test.js │ │ ├── selfServiceProfiles.test.js │ │ ├── tenant.test.js │ │ ├── themes.test.js │ │ └── triggers.test.js ├── e2e │ ├── e2e-cli.sh │ ├── e2e-utils.test.ts │ ├── e2e-utils.ts │ ├── e2e.test.ts │ ├── e2e_recordings.md │ ├── recordings │ │ ├── should-deploy-directory-(JSON)-config-with-keyword-replacements.json │ │ ├── should-deploy-while-deleting-resources-if-AUTH0_ALLOW_DELETE-is-true.json │ │ ├── should-deploy-without-deleting-resources-if-AUTH0_ALLOW_DELETE-is-false.json │ │ ├── should-deploy-without-throwing-an-error.json │ │ ├── should-deploy-yaml-config-with-keyword-replacements.json │ │ ├── should-dump-and-deploy-without-throwing-an-error.json │ │ ├── should-dump-without-throwing-an-error.json │ │ ├── should-only-dump-the-resources-listed-in-AUTH0_INCLUDED_ONLY.json │ │ ├── should-preserve-keywords-for-directory-format.json │ │ └── should-preserve-keywords-for-yaml-format.json │ └── testdata │ │ ├── empty-tenant │ │ └── tenant.yaml │ │ ├── keyword-replacements │ │ ├── directory │ │ │ ├── _reset.json │ │ │ └── tenant.json │ │ └── yaml │ │ │ ├── _reset.yaml │ │ │ └── tenant.yaml │ │ ├── lots-of-configuration │ │ ├── actions │ │ │ └── My Custom Action │ │ │ │ └── code.js │ │ ├── databases │ │ │ └── boo-baz-db-connection-test │ │ │ │ ├── change_password.js │ │ │ │ ├── create.js │ │ │ │ ├── delete.js │ │ │ │ ├── get_user.js │ │ │ │ ├── login.js │ │ │ │ └── verify.js │ │ ├── emailTemplates │ │ │ ├── verify_email.html │ │ │ └── welcome_email.html │ │ ├── forms │ │ │ └── Blank-form.json │ │ ├── pages │ │ │ ├── error_page.html │ │ │ ├── guardian_multifactor.html │ │ │ ├── login.html │ │ │ └── password_reset.html │ │ └── tenant.yaml │ │ ├── should-deploy-without-throwing-an-error │ │ └── tenant.yaml │ │ └── should-preserve-keywords │ │ ├── directory │ │ ├── clients │ │ │ └── Auth0 CLI - dev.json │ │ ├── emailTemplates │ │ │ ├── welcome_email.html │ │ │ └── welcome_email.json │ │ └── tenant.json │ │ └── yaml │ │ ├── emailTemplates │ │ └── welcome_email.html │ │ └── tenant.yaml ├── index.test.ts ├── keywordPreservation.test.ts ├── readonly.test.js ├── sessionDurationsAsMinutes.test.ts ├── tools │ ├── auth0 │ │ ├── client.tests.js │ │ ├── handlers │ │ │ ├── actions.tests.js │ │ │ ├── attackProtection.tests.js │ │ │ ├── branding.tests.js │ │ │ ├── clientGrants.tests.js │ │ │ ├── clients.tests.js │ │ │ ├── connections.tests.js │ │ │ ├── customDomains.test.ts │ │ │ ├── databases.tests.js │ │ │ ├── default.tests.ts │ │ │ ├── emailProvider.tests.js │ │ │ ├── emailTemplates.tests.js │ │ │ ├── flowVaultConnections.tests.js │ │ │ ├── flows.tests.js │ │ │ ├── forms.tests.js │ │ │ ├── guardianFactorProviders.tests.js │ │ │ ├── guardianFactorTemplates.tests.js │ │ │ ├── guardianFactors.tests.js │ │ │ ├── guardianPhoneFactorMessageTypes.tests.js │ │ │ ├── guardianPhoneFactorSelectedProvider.tests.js │ │ │ ├── guardianPolicies.tests.js │ │ │ ├── logStreams.test.ts │ │ │ ├── networkACLs.test.ts │ │ │ ├── organizations.tests.js │ │ │ ├── pages.tests.js │ │ │ ├── phoneProvider.test.ts │ │ │ ├── prompts.tests.ts │ │ │ ├── resourceServers.tests.js │ │ │ ├── roles.tests.js │ │ │ ├── scimHandler.tests.js │ │ │ ├── selfServiceProfiles.tests.js │ │ │ ├── tenant.tests.ts │ │ │ ├── themes.tests.js │ │ │ └── triggers.tests.js │ │ ├── index.test.ts │ │ └── validator.tests.js │ ├── calculateChanges.test.ts │ ├── constants.test.js │ ├── mocha.js │ ├── test.file.json │ ├── utils.test.js │ └── utils.test.ts ├── utils.js └── utils.test.js └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # http://editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | # Change these settings to your own preference 10 | indent_style = space 11 | indent_size = 2 12 | 13 | # We recommend you to keep these unchanged 14 | end_of_line = lf 15 | charset = utf-8 16 | trim_trailing_whitespace = true 17 | insert_final_newline = true 18 | 19 | # Unsupported options 20 | quote_type = single 21 | spaces_around_brackets = inside 22 | 23 | [*.md] 24 | trim_trailing_whitespace = false 25 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | examples/ 2 | test/e2e/testdata/**/** 3 | local/ 4 | node_modules/ 5 | lib/ 6 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "parserOptions": { 4 | "ecmaVersion": 2020 5 | }, 6 | "extends": ["airbnb-base", "plugin:import/errors", "plugin:import/warnings", "prettier"], 7 | "env": { 8 | "es2020": true, 9 | "node": true, 10 | "mocha": true 11 | }, 12 | "settings": { 13 | "import/resolver": { 14 | "node": { 15 | "extensions": [".js", ".ts"] 16 | } 17 | } 18 | }, 19 | "rules": { 20 | "max-len": 0, 21 | "react/display-name": 0, 22 | "class-methods-use-this": 0, 23 | "comma-dangle": 0, 24 | "eol-last": 2, 25 | "indent": [ 26 | 2, 27 | 2, 28 | { 29 | "SwitchCase": 1 30 | } 31 | ], 32 | "import/no-extraneous-dependencies": [ 33 | "error", 34 | { 35 | "devDependencies": true 36 | } 37 | ], 38 | "import/no-dynamic-require": 0, 39 | "prefer-arrow-callback": 0, 40 | "object-shorthand": 0, 41 | "prefer-template": 0, 42 | "func-names": 0, 43 | "new-cap": 0, 44 | "no-await-in-loop": 0, 45 | "no-param-reassign": 0, 46 | "no-multiple-empty-lines": 2, 47 | "no-plusplus": [ 48 | "error", 49 | { 50 | "allowForLoopAfterthoughts": true 51 | } 52 | ], 53 | "no-unused-vars": 2, 54 | "no-var": 0, 55 | "object-curly-spacing": [2, "always"], 56 | "quotes": [2, "single", "avoid-escape"], 57 | "semi": [2, "always"], 58 | "strict": 0, 59 | "space-before-blocks": [2, "always"], 60 | "import/extensions": [ 61 | "error", 62 | "ignorePackages", 63 | { 64 | "js": "never", 65 | "ts": "never" 66 | } 67 | ] 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @auth0/project-dx-sdks-engineer-codeowner 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: 🤔 Help & Questions 4 | url: https://community.auth0.com/c/sdks/5 5 | about: Ask general support or usage questions in the Auth0 Community forums 6 | - name: 📖 Deploy CLI Documentation 7 | url: https://github.com/auth0/auth0-deploy-cli#documentation 8 | about: Check the Deploy CLI documentation for in-depth overview of all the available commands 9 | - name: Take Our Survey! 10 | url: https://www.surveymonkey.com/r/LZKMPFN 11 | about: We're on a mission to make Auth0 Deploy CLI the best it can be, and we need YOUR help. We've put together a brief survey to understand how you use Deploy CLI, what you love about it, and where you think we can do better. 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: 🚀 Feature Request 2 | description: Suggest an idea or a feature for this project 3 | labels: [ "feature request" ] 4 | 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for taking the time to fill out this feature request! 10 | 11 | - type: checkboxes 12 | id: checklist 13 | attributes: 14 | label: Checklist 15 | options: 16 | - label: I agree to the terms within the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md). 17 | required: true 18 | 19 | - type: textarea 20 | id: description 21 | attributes: 22 | label: Describe the problem you'd like to have solved 23 | description: A clear and concise description of what the problem is. 24 | placeholder: My life would be a lot simpler if... 25 | validations: 26 | required: true 27 | 28 | - type: textarea 29 | id: ideal-solution 30 | attributes: 31 | label: Describe the ideal solution 32 | description: A clear and concise description of what you want to happen. 33 | validations: 34 | required: true 35 | 36 | - type: textarea 37 | id: alternatives-and-workarounds 38 | attributes: 39 | label: Alternatives and current workarounds 40 | description: A clear and concise description of any alternatives you've considered or any workarounds that are currently in place. 41 | validations: 42 | required: false 43 | 44 | - type: textarea 45 | id: additional-context 46 | attributes: 47 | label: Additional context 48 | description: Add any other context or screenshots about the feature request here. 49 | validations: 50 | required: false 51 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | ### 🔧 Changes 8 | 9 | 15 | 16 | ### 📚 References 17 | 18 | 28 | 29 | ### 🔬 Testing 30 | 31 | 34 | 35 | ### 📝 Checklist 36 | 37 | - [ ] All new/changed/fixed functionality is covered by tests (or N/A) 38 | - [ ] I have added documentation for all new/changed functionality (or N/A) 39 | 40 | 43 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" 9 | directory: "/" 10 | schedule: 11 | interval: "daily" 12 | ignore: 13 | - dependency-name: "*" 14 | update-types: ["version-update:semver-major", "version-update:semver-patch"] 15 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Configuration for https://github.com/auth0/.github/ 2 | _extends: .github 3 | -------------------------------------------------------------------------------- /.github/workflows/semgrep.yml: -------------------------------------------------------------------------------- 1 | name: Semgrep 2 | 3 | on: 4 | pull_request: {} 5 | push: 6 | branches: ["master", "main"] 7 | jobs: 8 | semgrep: 9 | name: Scan 10 | runs-on: ubuntu-latest 11 | container: 12 | image: returntocorp/semgrep 13 | if: (github.actor != 'dependabot[bot]' && github.actor != 'snyk-bot') 14 | steps: 15 | - uses: actions/checkout@v3 16 | - run: semgrep ci 17 | env: 18 | SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .nyc_output/ 2 | coverage/ 3 | local/ 4 | node_modules/ 5 | lib/ 6 | /npm-debug.log 7 | config*.json 8 | .env 9 | .idea 10 | .npmrc 11 | yarn-error.log 12 | *.pem 13 | *.pub 14 | Makefile 15 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | npm run format 2 | npm run lint:fix 3 | npx pretty-quick --staged 4 | 5 | # Add back modified files to staging 6 | git add -u 7 | 8 | npx tsc --noEmit # Ensure that code compiles 9 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | test/ 3 | local/ 4 | /npm-debug.log 5 | 6 | .nyc_output/ 7 | coverage/ 8 | examples/ 9 | docs/ 10 | config*.json 11 | .env 12 | .idea 13 | .npmrc 14 | yarn-error.log 15 | *.pem 16 | *.pub 17 | Makefile 18 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore artifacts: 2 | local 3 | lib 4 | .circleci 5 | .github 6 | .husky 7 | node_modules 8 | examples 9 | test/**/*.json 10 | test/**/*.yaml 11 | test/e2e/testdata 12 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "singleQuote": true 4 | } 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Commits 2 | 3 | All commits should be signed to enhance security, authorship, trust and compliance. 4 | 5 | [About commit signature verification](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification) 6 | 7 | ## Versioning 8 | 9 | Versioning is managed by npm. Npm versioning will execute scripts that uses kacl to manage the CHANGELOG. 10 | 11 | Ensure that before running the versioning scripts below, the Unreleased changelog is updated. 12 | 13 | ### Production Versioning 14 | 15 | ```sh 16 | npm version patch --no-git-tag-version 17 | ``` 18 | 19 | ### Beta Versioning 20 | 21 | ```sh 22 | npm version prerelease --preid beta --no-git-tag-version 23 | ``` 24 | 25 | ## Publishing 26 | 27 | Publishing to NPM has 2 different processes. Production process is automated by creating a git tag on master. 28 | 29 | Publishing the beta should be done manually: 30 | 31 | ```sh 32 | npm run build 33 | npm publish --tag beta 34 | ``` 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Auth0 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/authenticating-with-tenant.md: -------------------------------------------------------------------------------- 1 | # Authenticating with your Tenant 2 | 3 | There are three available methods of authenticating the Deploy CLI with your tenant: 4 | 5 | - [Client Credentials](#client-credentials) 6 | - [Private Key JWT](#private-key-JWT) 7 | - [Access Token](#access-token) 8 | 9 | ## Client Credentials 10 | 11 | Authenticating with a client ID and client secret pair. This option is straightforward and enables the quickest path to setup for the tool. In order to utilize, set both the `AUTH0_CLIENT_ID` and `AUTH0_CLIENT_SECRET` configuration values with the client ID and client secret respectively. These credentials can be found under the "Credentials" tab within the designated application used for the Deploy CLI. 12 | 13 | ## Private Key JWT 14 | 15 | Providing a private key to facilitate asymmetric key pair authentication. This requires the "Private Key JWT" authentication method for the designated client as well as a public key configured on the remote tenant. This may be appealing to developers who do not wish to have credentials stored remotely on Auth0. 16 | 17 | To utilize, pass the path of the private key through the `AUTH0_CLIENT_SIGNING_KEY_PATH` configuration property either as an environment variable or property in your `config.json` file. This path is relative to the working directory. Optionally, you can specify the signing algorithm through the `AUTH0_CLIENT_SIGNING_ALGORITHM` configuration property. 18 | 19 | **Example: ** 20 | 21 | ```json 22 | { 23 | "AUTH0_CLIENT_SIGNING_KEY_PATH": "./private.pem", 24 | "AUTH0_CLIENT_SIGNING_ALGORITHM": "RSA256" 25 | } 26 | ``` 27 | 28 | See [Configure Private Key JWT Authentication](https://auth0.com/docs/get-started/applications/configure-private-key-jwt) for further documentation 29 | 30 | ## Access Token 31 | 32 | Passing in an access token directly is also supported. This option puts more onus on the developers but can enable flexible and specific workflows when necessary. It can be leveraged by passing the Auth0 access token in via the `AUTH0_ACCESS_TOKEN` environment variable. 33 | 34 | [[table of contents]](../README.md#documentation) 35 | -------------------------------------------------------------------------------- /docs/available-resource-config-formats.md: -------------------------------------------------------------------------------- 1 | # Available Resource Config Formats 2 | 3 | Auth0 resource state is expressed in two available different configuration file formats: YAML and JSON (aka “directory”). When using the Deploy CLI’s export command, you will be prompted with the choice of one versus the other. 4 | 5 | ## YAML 6 | 7 | The YAML format is expressed mostly as a flat `tenant.yaml` file with supplemental code files for resources like actions and email templates. The single file makes tracking changes over time in version control more straightforward. Additionally, the single file eliminates a bit of ambiguity with directory and file names, which may not be immediately obvious. 8 | 9 | ## Directory (JSON) 10 | 11 | The directory format separates resource types into separate directories, with each single resource living inside a dedicated JSON file. This format allows for easier conceptual separation between each type of resource as well as the individual resources themselves. Also, the Deploy CLI closely mirrors the data shapes defined in the [Auth0 Management API](https://auth0.com/docs/api/management/v2), so referencing the JSON examples in the docs may provide useful when using this format. 12 | 13 | ## How to Choose 14 | 15 | The decision to select which format to use should be primarily made off of preference. Both formats are tenable solutions that achieve the same task, but with subtly different strengths and weaknesses described above. Be sure to evaluate each in the context of your context. Importantly, **this choice is not permanent**, and switching from one to the other via the import command is an option at your disposal. 16 | 17 | --- 18 | 19 | [[table of contents]](../README.md#documentation) 20 | -------------------------------------------------------------------------------- /docs/how-to-contribute.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | While we may not prioritize some issues and feature requests, we are much more inclined to address them if accompanied with a code contribution. Please feel empowered to make a code contribution through a Github pull request. Please read the following for the best contributor experience. 4 | 5 | ## Getting started with development 6 | 7 | The only requirement for development is having [node](https://nodejs.dev/) installed. Most modern versions will work but LTS is recommended. 8 | 9 | Once node is installed, fork the Deploy CLI repository. Once code is downloaded to local development machine, run the following commands: 10 | 11 | ```shell 12 | npm install # Installs all dependencies 13 | npm run dev # Runs compiler, continuously observes source file changes 14 | ``` 15 | 16 | ## Running tests 17 | 18 | In order to run the entire test suite, execute `npm run test`. 19 | 20 | To run a single test file or single test execute: 21 | 22 | ```shell 23 | npx ts-mocha --timeout=7500 -p tsconfig.json -g= 24 | ``` 25 | 26 | ### Examples 27 | 28 | ```shell 29 | # Runs all tests within a file 30 | npx ts-mocha --timeout=7500 -p tsconfig.json test/tools/auth0/handlers/actions.tests.js 31 | 32 | # Runs a single test within a file 33 | npx ts-mocha --timeout=7500 -p tsconfig.json \ 34 | test/tools/auth0/handlers/actions.tests.js \ 35 | -g="should create action" 36 | ``` 37 | 38 | ## Code Contribution Checklist 39 | 40 | Before finally submitting a PR, please ensure to complete the following: 41 | 42 | - [ ] All code written in Typescript and compiles 43 | - [ ] Accompanying tests for functional changes 44 | - [ ] Any necessary documentation changes to pages in the /docs directory 45 | - [ ] PR includes thorough description about what code does and why it was added 46 | 47 | --- 48 | 49 | [[table of contents]](../README.md#documentation) 50 | -------------------------------------------------------------------------------- /docs/terraform-provider.md: -------------------------------------------------------------------------------- 1 | # Auth0 Terraform Provider 2 | 3 | The Deploy CLI is not the only tool available for managing your Auth0 tenant configuration, there is also an [officially supported Terraform Provider](https://github.com/auth0/terraform-provider-auth0). [Terraform](https://terraform.io/) is a third-party tool for representing your cloud resources’s configurations as code. It has an established plug-in framework that supports a wide array of cloud providers, including Auth0. 4 | 5 | Both the Deploy CLI and Terraform Provider exist to help you manage your Auth0 tenant configurations, but each has their own set of pros and cons. 6 | 7 | You may want to consider the Auth0 Terraform Provider if: 8 | 9 | - Your development workflows already leverages Terraform 10 | - Your tenant management needs are granular or only pertain to a few specific resources 11 | 12 | You may **not** want to consider the Auth0 Terraform Provider if: 13 | 14 | - Your development workflow does not use Terraform, requiring extra setup upfront 15 | - Your development workflows are primarily concerned with managing your tenants in bulk 16 | - Your tenant has lots of existing resources, may require significant effort to “import” 17 | 18 | --- 19 | 20 | [[table of contents]](../README.md#documentation) 21 | -------------------------------------------------------------------------------- /docs/v8_MIGRATION_GUIDE.md: -------------------------------------------------------------------------------- 1 | # V8 Migration Guide 2 | 3 | Guide to migrating from `7.x` to `8.x` 4 | 5 | - [General](#general) 6 | - [Node 20(v20.18.1) or newer is required](<#node-20(v20.18.1)-or-newer-is-required>) 7 | - [Auth0 V4 Migration Guide](https://github.com/auth0/node-auth0/blob/master/v4_MIGRATION_GUIDE.md) 8 | - [Management Resources](#management-resources) 9 | - [EmailProvider](#emailProvider) 10 | - [Migrations](#migrations) 11 | 12 | ## General 13 | 14 | ### Node 20(v20.18.1) or newer is required 15 | 16 | Node 20(v20.18.1) LTS and newer LTS releases are supported. 17 | 18 | ## Management Resources 19 | 20 | | Resource | Change | Description | 21 | | ------------- | ---------------- | --------------------------------------------- | 22 | | emailProvider | delete operation | Delete operation is deprecated on auth0 `4.x` | 23 | | migrations | removed support | Not supported on auth0 `4.x` | 24 | 25 | #### Note: Other resources from `7.x` are not affected and no changes are required. 26 | 27 | #### emailProvider : 28 | 29 | The `delete` operation on the `emailProvider` resource will disable the email provider instead of deleting it. 30 | This is because the email provider deletion operation is not supported on auth0 `4.x`. User can disable the email provider 31 | by email provider setting the `enabled` property to `false` from the configuration file. 32 | 33 | ```yaml 34 | emailProvider: 35 | # other properties 36 | enabled: false 37 | ``` 38 | 39 | Rest of the operations on emailProvider resource will work the same as `7.x`. 40 | 41 | #### migrations : 42 | 43 | The `migrations` resource is not supported on auth0 `4.x`. It's recommended to remove the `migrations` resource from the 44 | configuration file. If it's not removed, the deploy CLI will ignore the `migrations` resource for operations. 45 | -------------------------------------------------------------------------------- /examples/directory/actions/action-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "action-example", 3 | "code": "directory/actions/action-example/code.js", 4 | "status": "built", 5 | "dependencies": [ 6 | { 7 | "name": "lodash", 8 | "version": "4.17.21" 9 | } 10 | ], 11 | "secrets": [ 12 | { 13 | "name": "MY_SECRET", 14 | "value": "_VALUE_NOT_SHOWN_" 15 | } 16 | ], 17 | "supported_triggers": [ 18 | { 19 | "id": "post-login", 20 | "version": "v1" 21 | } 22 | ], 23 | "deployed": true 24 | } -------------------------------------------------------------------------------- /examples/directory/actions/action-example/code.js: -------------------------------------------------------------------------------- 1 | /** @type {PostLoginAction} */ 2 | module.exports = async (event, context) => { 3 | return {}; 4 | }; 5 | -------------------------------------------------------------------------------- /examples/directory/clients/My Native app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "My Native app", 3 | "app_type": "native", 4 | "grant_types": [ 5 | "authorization_code", 6 | "implicit", 7 | "refresh_token" 8 | ], 9 | "mobile": { 10 | "android": { 11 | "app_package_name": "com.my.android.id" 12 | } 13 | }, 14 | "native_social_login": { 15 | "google": { 16 | "enabled": true 17 | }, 18 | "facebook": { 19 | "enabled": false 20 | }, 21 | "apple": { 22 | "enabled": false 23 | } 24 | }, 25 | "is_first_party": true, 26 | "sso_disabled": false, 27 | "cross_origin_auth": false, 28 | "oidc_conformant": false, 29 | "oidc_backchannel_logout": { 30 | "backchannel_logout_initiators": { 31 | "mode": "custom", 32 | "selected_initiators": [ 33 | "rp-logout", 34 | "idp-logout" 35 | ] 36 | } 37 | }, 38 | "jwt_configuration": { 39 | "lifetime_in_seconds": 36000, 40 | "secret_encoded": false 41 | }, 42 | "token_endpoint_auth_method": "none", 43 | "custom_login_page_on": true, 44 | "oidc_logout": { 45 | "backchannel_logout_initiators": { 46 | "mode": "custom", 47 | "selected_initiators": [ 48 | "rp-logout", 49 | "idp-logout" 50 | ] 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /examples/directory/clients/My SPA.json: -------------------------------------------------------------------------------- 1 | { 2 | "allowed_clients": [], 3 | "allowed_logout_urls": [ 4 | "https://##ENV##.myapp.com/logout" 5 | ], 6 | "allowed_origins": [ 7 | "https://##ENV##.myapp.com/" 8 | ], 9 | "app_type": "spa", 10 | "callback_url_template": false, 11 | "callbacks": [ 12 | "https://##ENV##.myapp.com/callback" 13 | ], 14 | "cross_origin_auth": false, 15 | "custom_login_page_on": true, 16 | "description": "My SPA Application", 17 | "global": false, 18 | "grant_types": [ 19 | "authorization_code", 20 | "implicit", 21 | "refresh_token" 22 | ], 23 | "is_first_party": true, 24 | "is_token_endpoint_ip_header_trusted": false, 25 | "jwt_configuration": { 26 | "alg": "RS256", 27 | "lifetime_in_seconds": 36000, 28 | "secret_encoded": false 29 | }, 30 | "logo_uri": "https://myapp.com/logo.png", 31 | "name": "My SPA", 32 | "oidc_conformant": true, 33 | "sso": true, 34 | "sso_disabled": false, 35 | "tenant": "##AUTH0_TENANT_NAME##", 36 | "token_endpoint_auth_method": "none", 37 | "web_origins": [ 38 | "https://##ENV##.myapp.com" 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /examples/directory/config.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "AUTH0_DOMAIN": ".auth0.com", 3 | "AUTH0_CLIENT_ID": "", 4 | "AUTH0_CLIENT_SECRET": "", 5 | "AUTH0_KEYWORD_REPLACE_MAPPINGS": { 6 | "AUTH0_TENANT_NAME": "", 7 | "ENV": "DEV", 8 | "SETUP_CONFIG": { 9 | "client_id": "", 10 | "client_secret": "", 11 | "domain": "", 12 | "type": "OAUTH_APP" 13 | } 14 | }, 15 | "AUTH0_ALLOW_DELETE": false, 16 | "INCLUDED_PROPS": { 17 | "clients": [ "client_secret" ] 18 | }, 19 | "AUTH0_EXCLUDED_RULES": [ 20 | "rule-1-name", 21 | "rule-2-name" 22 | ], 23 | "EXCLUDED_PROPS": { 24 | "connections": [ "options.client_secret" ] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/directory/connections/facebook.json: -------------------------------------------------------------------------------- 1 | { 2 | "options": { 3 | "scope": "email,user_birthday,user_hometown,user_location,user_friends,user_likes", 4 | "client_id": "123456789", 5 | "public_profile": false, 6 | "email": true, 7 | "user_birthday": true, 8 | "user_website": false, 9 | "user_hometown": true, 10 | "user_location": true, 11 | "user_work_history": false, 12 | "user_education_history": false, 13 | "user_about_me": false, 14 | "ads_management": false, 15 | "ads_read": false, 16 | "manage_pages": false, 17 | "pages_show_list": false, 18 | "pages_manage_cta": false, 19 | "pages_messaging": false, 20 | "pages_messaging_phone_number": false, 21 | "pages_messaging_subscriptions": false, 22 | "publish_actions": false, 23 | "publish_pages": false, 24 | "read_audience_network_insights": false, 25 | "read_custom_friendlists": false, 26 | "read_insights": false, 27 | "read_page_mailboxes": false, 28 | "rsvp_event": false, 29 | "user_actions-books": false, 30 | "user_actions-fitness": false, 31 | "user_actions-music": false, 32 | "user_actions-news": false, 33 | "user_actions-video": false, 34 | "user_events": false, 35 | "user_friends": true, 36 | "user_games_activity": false, 37 | "user_likes": true, 38 | "user_managed_groups": false, 39 | "user_tagged_places": false, 40 | "user_photos": false, 41 | "user_posts": false, 42 | "user_relationships": false, 43 | "user_relationship_details": false, 44 | "user_religion_politics": false, 45 | "user_status": false, 46 | "user_videos": false, 47 | "manage_notifications": false, 48 | "read_stream": false, 49 | "read_mailbox": false, 50 | "user_groups": false, 51 | "client_secret": "SOME_SECRET", 52 | "publish_video": false 53 | }, 54 | "strategy": "facebook", 55 | "name": "facebook", 56 | "is_domain_connection": false, 57 | "enabled_clients": [ 58 | "My SPA" 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /examples/directory/database-connections/users/change_email.js: -------------------------------------------------------------------------------- 1 | function changeEmail(email, callback) { 2 | const msg = 'Please implement the change email script for this database connection'; 3 | return callback(new Error(msg)); 4 | } 5 | -------------------------------------------------------------------------------- /examples/directory/database-connections/users/change_password.js: -------------------------------------------------------------------------------- 1 | function changePassword (email, newPassword, callback) { 2 | const msg = 'Please implement the change password script for this database connection'; 3 | return callback(new Error(msg)); 4 | } 5 | -------------------------------------------------------------------------------- /examples/directory/database-connections/users/create.js: -------------------------------------------------------------------------------- 1 | function login(user, callback) { 2 | const msg = 'Please implement the create script for this database connection'; 3 | return callback(new Error(msg)); 4 | } 5 | -------------------------------------------------------------------------------- /examples/directory/database-connections/users/database.json: -------------------------------------------------------------------------------- 1 | { 2 | "options": { 3 | "requires_username": false, 4 | "strategy_version": 2, 5 | "enabledDatabaseCustomization": true, 6 | "customScripts": { 7 | "login": "./login.js", 8 | "get_user": "./get_user.js" 9 | }, 10 | "import_mode": true 11 | }, 12 | "strategy": "auth0", 13 | "name": "users", 14 | "realms": [ 15 | "users" 16 | ], 17 | "enabled_clients": [ 18 | "My SPA" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /examples/directory/database-connections/users/delete.js: -------------------------------------------------------------------------------- 1 | function remove (id, callback) { 2 | const msg = 'Please implement the delete script for this database connection'; 3 | return callback(new Error(msg)); 4 | } 5 | -------------------------------------------------------------------------------- /examples/directory/database-connections/users/get_user.js: -------------------------------------------------------------------------------- 1 | function getUser(email, callback) { 2 | const msg = 'Please implement the get user script for this database connection'; 3 | return callback(new Error(msg)); 4 | } 5 | -------------------------------------------------------------------------------- /examples/directory/database-connections/users/login.js: -------------------------------------------------------------------------------- 1 | function login(email, password, callback) { 2 | const msg = 'Please implement the login script for this database connection'; 3 | return callback(new Error(msg)); 4 | } 5 | -------------------------------------------------------------------------------- /examples/directory/database-connections/users/verify.js: -------------------------------------------------------------------------------- 1 | function login(email, callback) { 2 | const msg = 'Please implement the verify script for this database connection'; 3 | return callback(new Error(msg)); 4 | } 5 | -------------------------------------------------------------------------------- /examples/directory/emails/blocked_account.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Example Email 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/directory/emails/blocked_account.json: -------------------------------------------------------------------------------- 1 | { 2 | "template": "blocked_account", 3 | "from": "", 4 | "subject": "", 5 | "resultUrl": "", 6 | "syntax": "liquid", 7 | "body": "./blocked_account.html", 8 | "urlLifetimeInSeconds": 432000, 9 | "enabled": true 10 | } -------------------------------------------------------------------------------- /examples/directory/emails/enrollment_email.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Example Email 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/directory/emails/enrollment_email.json: -------------------------------------------------------------------------------- 1 | { 2 | "template": "enrollment_email", 3 | "from": "", 4 | "subject": "", 5 | "syntax": "liquid", 6 | "body": "./enrollment_email.html", 7 | "enabled": true 8 | } -------------------------------------------------------------------------------- /examples/directory/emails/mfa_oob_code.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Example Email 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/directory/emails/mfa_oob_code.json: -------------------------------------------------------------------------------- 1 | { 2 | "template": "mfa_oob_code", 3 | "from": "", 4 | "subject": "", 5 | "syntax": "liquid", 6 | "body": "./mfa_oob_code.html", 7 | "enabled": true 8 | } -------------------------------------------------------------------------------- /examples/directory/emails/provider.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "smtp", 3 | "enabled": true, 4 | "credentials": { 5 | "smtp_host": "smtp.server.com", 6 | "smtp_port": 25, 7 | "smtp_user": "smtp_user", 8 | "smtp_pass": "smtp_secret_password" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/directory/emails/reset_email.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Example Email 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/directory/emails/reset_email.json: -------------------------------------------------------------------------------- 1 | { 2 | "template": "reset_email", 3 | "from": "", 4 | "subject": "", 5 | "resultUrl": "", 6 | "syntax": "liquid", 7 | "body": "./reset_email.html", 8 | "urlLifetimeInSeconds": 432000, 9 | "enabled": true 10 | } -------------------------------------------------------------------------------- /examples/directory/emails/reset_email_by_code.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Example Email 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/directory/emails/reset_email_by_code.json: -------------------------------------------------------------------------------- 1 | { 2 | "template": "reset_email_by_code", 3 | "from": "", 4 | "subject": "", 5 | "resultUrl": "", 6 | "syntax": "liquid", 7 | "body": "./reset_email_by_code.html", 8 | "urlLifetimeInSeconds": 432000, 9 | "enabled": true 10 | } 11 | -------------------------------------------------------------------------------- /examples/directory/emails/stolen_credentials.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Example Email 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/directory/emails/stolen_credentials.json: -------------------------------------------------------------------------------- 1 | { 2 | "template": "stolen_credentials", 3 | "from": "", 4 | "subject": "", 5 | "syntax": "liquid", 6 | "body": "./stolen_credentials.html", 7 | "enabled": true 8 | } -------------------------------------------------------------------------------- /examples/directory/emails/verify_email.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Example Email 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/directory/emails/verify_email.json: -------------------------------------------------------------------------------- 1 | { 2 | "template": "verify_email", 3 | "from": "", 4 | "subject": "", 5 | "resultUrl": "", 6 | "body": "./verify_email.html", 7 | "urlLifetimeInSeconds": 432000, 8 | "syntax": "liquid", 9 | "enabled": true 10 | } 11 | -------------------------------------------------------------------------------- /examples/directory/emails/verify_email_by_code.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Example Email 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/directory/emails/verify_email_by_code.json: -------------------------------------------------------------------------------- 1 | { 2 | "template": "verify_email_by_code", 3 | "from": "", 4 | "subject": "", 5 | "syntax": "liquid", 6 | "body": "./verify_email_by_code.html", 7 | "enabled": true 8 | } 9 | -------------------------------------------------------------------------------- /examples/directory/emails/welcome_email.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Example Email 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/directory/emails/welcome_email.json: -------------------------------------------------------------------------------- 1 | { 2 | "template": "welcome_email", 3 | "from": "", 4 | "subject": "", 5 | "body": "./welcome_email.html", 6 | "syntax": "liquid", 7 | "enabled": true 8 | } -------------------------------------------------------------------------------- /examples/directory/flow-vault-connections/New Auth0 M2M Con.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "New Auth0 M2M Con", 3 | "app_id": "AUTH0", 4 | "setup": "@@SETUP_CONFIG@@" 5 | } 6 | -------------------------------------------------------------------------------- /examples/directory/flows/Update KYC Name.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Update KYC Name", 3 | "actions": [ 4 | { 5 | "id": "update_user_metadata", 6 | "type": "AUTH0", 7 | "action": "UPDATE_USER", 8 | "allow_failure": false, 9 | "mask_output": false, 10 | "params": { 11 | "connection_id": "New Auth0 M2M Con", 12 | "user_id": "{{context.user.user_id}}", 13 | "changes": { 14 | "user_metadata": { 15 | "full_name": "{{fields.full_name}}" 16 | } 17 | } 18 | } 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /examples/directory/forms/New Kyc form.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "New Kyc form", 3 | "languages": { 4 | "primary": "en" 5 | }, 6 | "nodes": [ 7 | { 8 | "id": "step_ggeX", 9 | "type": "STEP", 10 | "coordinates": { 11 | "x": 500, 12 | "y": 0 13 | }, 14 | "alias": "New step", 15 | "config": { 16 | "components": [ 17 | { 18 | "id": "full_name", 19 | "category": "FIELD", 20 | "type": "TEXT", 21 | "label": "Your Name", 22 | "required": true, 23 | "sensitive": false, 24 | "config": { 25 | "multiline": false, 26 | "min_length": 1, 27 | "max_length": 50 28 | } 29 | }, 30 | { 31 | "id": "next_button_3FbA", 32 | "category": "BLOCK", 33 | "type": "NEXT_BUTTON", 34 | "config": { 35 | "text": "Continue" 36 | } 37 | } 38 | ], 39 | "next_node": "flow_nyxC" 40 | } 41 | }, 42 | { 43 | "id": "flow_nyxC", 44 | "type": "FLOW", 45 | "coordinates": { 46 | "x": 1000, 47 | "y": 0 48 | }, 49 | "alias": "update kyc name", 50 | "config": { 51 | "flow_id": "Update KYC Name", 52 | "next_node": "$ending" 53 | } 54 | } 55 | ], 56 | "start": { 57 | "next_node": "step_ggeX", 58 | "coordinates": { 59 | "x": 0, 60 | "y": 0 61 | } 62 | }, 63 | "ending": { 64 | "resume_flow": true, 65 | "coordinates": { 66 | "x": 1250, 67 | "y": 0 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /examples/directory/grants/m2m_myapp_api.json: -------------------------------------------------------------------------------- 1 | { 2 | "client_id": "My M2M", 3 | "audience": "https://##ENV##.myapp.com/api/v1", 4 | "scope": [ 5 | "update:account" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /examples/directory/guardian/factors/email.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "email", 3 | "enabled": true 4 | } -------------------------------------------------------------------------------- /examples/directory/guardian/factors/otp.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "otp", 3 | "enabled": true 4 | } -------------------------------------------------------------------------------- /examples/directory/guardian/factors/push-notification.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "push-notification", 3 | "enabled": true 4 | } -------------------------------------------------------------------------------- /examples/directory/guardian/factors/sms.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sms", 3 | "enabled": true 4 | } -------------------------------------------------------------------------------- /examples/directory/guardian/phoneFactorMessageTypes.json: -------------------------------------------------------------------------------- 1 | { 2 | "message-types": [ 3 | "sms", 4 | "voice" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /examples/directory/guardian/phoneFactorSelectedProvider.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "twilio" 3 | } 4 | -------------------------------------------------------------------------------- /examples/directory/guardian/policies.json: -------------------------------------------------------------------------------- 1 | { 2 | "policies": [ 3 | "all-applications" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /examples/directory/guardian/providers/sms-twilio.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sms", 3 | "provider": "twilio", 4 | "auth_token": "test", 5 | "sid": "test", 6 | "messaging_service_sid": "test" 7 | } 8 | -------------------------------------------------------------------------------- /examples/directory/guardian/templates/sms.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sms", 3 | "enrollment_message": "{{code}} is your verification code for {{tenant.friendly_name}}. Please enter this code to verify your enrollment.", 4 | "verification_message": "{{code}} is your verification code for {{tenant.friendly_name}}" 5 | } -------------------------------------------------------------------------------- /examples/directory/hooks/client-credentials-exchange.js: -------------------------------------------------------------------------------- 1 | /** 2 | @param {object} client - information about the client 3 | @param {string} client.name - name of client 4 | @param {string} client.id - client id 5 | @param {string} client.tenant - Auth0 tenant name 6 | @param {object} client.metadata - client metadata 7 | @param {array|undefined} scope - array of strings representing the scope claim or undefined 8 | @param {string} audience - token's audience claim 9 | @param {object} context - additional authorization context 10 | @param {object} context.webtask - webtask context 11 | @param {function} cb - function (error, accessTokenClaims) 12 | */ 13 | module.exports = function(client, scope, audience, context, cb) { 14 | var access_token = {}; 15 | access_token.scope = scope; 16 | 17 | // Modify scopes or add extra claims 18 | // access_token['https://example.com/claim'] = 'bar'; 19 | // access_token.scope.push('extra'); 20 | 21 | cb(null, access_token); 22 | }; 23 | -------------------------------------------------------------------------------- /examples/directory/hooks/client-credentials-exchange.json: -------------------------------------------------------------------------------- 1 | { 2 | "triggerId": "credentials-exchange", 3 | "name": "cce hook", 4 | "script": "./client-credentials-exchange.js", 5 | "enabled": true, 6 | "secrets": { 7 | "api-key": "my custom api key" 8 | }, 9 | "dependencies": { 10 | "bcrypt": "3.0.6" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/directory/network-acls/Allow Specific Countries.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Allow Specific Countries", 3 | "active": true, 4 | "priority": 2, 5 | "rule": { 6 | "action": { 7 | "allow": true 8 | }, 9 | "scope": "authentication", 10 | "match": { 11 | "geo_country_codes": ["US", "CA"] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/directory/pages/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

This is just an example, go update your login page :)

4 |

Env is @@ENV@@

5 | 6 | 7 | -------------------------------------------------------------------------------- /examples/directory/pages/login.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "login", 3 | "enabled": true 4 | } 5 | -------------------------------------------------------------------------------- /examples/directory/pages/password_reset.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

This is just an example, go update your password reset page :)

4 |

Env is @@ENV@@

5 | 6 | 7 | -------------------------------------------------------------------------------- /examples/directory/pages/password_reset.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "password_reset", 3 | "enabled": false 4 | } 5 | -------------------------------------------------------------------------------- /examples/directory/phoneProviders/provider.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "twilio", 4 | "disabled": true, 5 | "configuration": { 6 | "sid": "twilio_sid", 7 | "default_from": "+1234567890", 8 | "delivery_methods": [ 9 | "text", "voice" 10 | ] 11 | }, 12 | "credentials": { 13 | "auth_token": "some_auth_token" 14 | } 15 | } 16 | ] 17 | -------------------------------------------------------------------------------- /examples/directory/prompts/custom-text.json: -------------------------------------------------------------------------------- 1 | { 2 | "en": { 3 | "login-id": { 4 | "login-id": { 5 | "invalid-email-format": "Email is not valid." 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/directory/prompts/prompts.json: -------------------------------------------------------------------------------- 1 | { 2 | "universal_login_experience": "new", 3 | "identifier_first": false, 4 | "webauthn_platform_first_factor": true, 5 | "enable_ulp_wcag_compliance": false 6 | } 7 | -------------------------------------------------------------------------------- /examples/directory/prompts/screenRenderSettings/login-id_login-id.json: -------------------------------------------------------------------------------- 1 | { 2 | "prompt": "login-id", 3 | "screen": "login-id", 4 | "rendering_mode": "advanced", 5 | "context_configuration": [ 6 | "branding.settings", 7 | "branding.themes.default" 8 | ], 9 | "default_head_tags_disabled": true, 10 | "head_tags": [ 11 | { 12 | "tag": "script", 13 | "attributes": { 14 | "src": "http://127.0.0.1:8090/index.js", 15 | "defer": true 16 | } 17 | }, 18 | { 19 | "tag": "link", 20 | "attributes": { 21 | "rel": "stylesheet", 22 | "href": "http://127.0.0.1:8090/index.css" 23 | } 24 | }, 25 | { 26 | "tag": "meta", 27 | "attributes": { 28 | "name": "viewport", 29 | "content": "width=device-width, initial-scale=1" 30 | } 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /examples/directory/prompts/screenRenderSettings/signup-id_signup-id.json: -------------------------------------------------------------------------------- 1 | { 2 | "prompt": "signup-id", 3 | "screen": "signup-id", 4 | "rendering_mode": "advanced", 5 | "context_configuration": [], 6 | "default_head_tags_disabled": false, 7 | "head_tags": [ 8 | { 9 | "tag": "script", 10 | "attributes": { 11 | "src": "URL_TO_YOUR_ASSET", 12 | "async": true, 13 | "defer": true, 14 | "integrity": [ 15 | "ASSET_SHA" 16 | ] 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /examples/directory/resource-servers/My API.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "My API", 3 | "identifier": "https://##ENV##.myapp.com/api/v1", 4 | "allow_offline_access": false, 5 | "skip_consent_for_verifiable_first_party_clients": true, 6 | "token_lifetime": 86400, 7 | "token_lifetime_for_web": 7200, 8 | "signing_alg": "RS256", 9 | "scopes": [ 10 | { 11 | "value": "write:messages", 12 | "description": "write messages" 13 | }, 14 | { 15 | "value": "read:messages", 16 | "description": "read:messages" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /examples/directory/roles/Admin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Admin", 3 | "description": "App Admin", 4 | "permissions": [ 5 | { 6 | "permission_name": "write:messages", 7 | "resource_server_identifier": "https://##ENV##.myapp.com/api/v1" 8 | }, 9 | { 10 | "permission_name": "read:messages", 11 | "resource_server_identifier": "https://##ENV##.myapp.com/api/v1" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /examples/directory/roles/User.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "User", 3 | "description": "App User", 4 | "permissions": [ 5 | { 6 | "permission_name": "read:messages", 7 | "resource_server_identifier": "https://##ENV##.myapp.com/api/v1" 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /examples/directory/rules-configs/SOME_SECRET.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "THISDATA_API_KEY", 3 | "value": "##MY_SECRET##" 4 | } 5 | -------------------------------------------------------------------------------- /examples/directory/rules/Enrich-Identity-Token.js: -------------------------------------------------------------------------------- 1 | function (user, context, callback) { 2 | const env = @@ENV@@; 3 | 4 | console.log(`env is ${env}`); 5 | 6 | // Add Env to Token 7 | context.idToken['https://myapp.com/env'] = @@ENV@@; 8 | 9 | callback(new Error(`This is just an example from auth0-deploy-cli, don\'t use this rule!`), user, context); 10 | } 11 | -------------------------------------------------------------------------------- /examples/directory/rules/Enrich-Identity-Token.json: -------------------------------------------------------------------------------- 1 | { 2 | "enabled": true, 3 | "name": "Enrich-Identity-Token", 4 | "order": 1, 5 | "stage": "login_success", 6 | "script": "./Enrich-Identity-Token.js" 7 | } 8 | -------------------------------------------------------------------------------- /examples/directory/self-service-profiles/self-service profile 1.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "self-service profile 1", 3 | "description": "self-Service profile 1", 4 | "user_attributes": [ 5 | { 6 | "name": "email", 7 | "description": "Email of the User", 8 | "is_optional": false 9 | }, 10 | { 11 | "name": "name", 12 | "description": "Name of the User", 13 | "is_optional": true 14 | } 15 | ], 16 | "allowed_strategies": [ 17 | "google-apps", 18 | "keycloak-samlp", 19 | "okta" 20 | ], 21 | "branding": { 22 | "colors": { 23 | "primary": "#19aecc" 24 | } 25 | }, 26 | "customText": { 27 | "en": { 28 | "get-started": { 29 | "introduction": "Welcome! With

only a few steps

you'll be able to setup." 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/directory/tenant.json: -------------------------------------------------------------------------------- 1 | { 2 | "friendly_name": "My Company", 3 | "support_email": "support@company.com", 4 | "session_lifetime": 168, 5 | "default_directory": "users", 6 | "sandbox_version": "8", 7 | "idle_session_lifetime": 72 8 | } 9 | -------------------------------------------------------------------------------- /examples/directory/triggers/triggers.json: -------------------------------------------------------------------------------- 1 | { 2 | "post-login": [ 3 | { 4 | "action_name": "action-example", 5 | "display_name": "action-example" 6 | } 7 | ], 8 | "credentials-exchange": [], 9 | "pre-user-registration": [], 10 | "post-user-registration": [], 11 | "post-change-password": [], 12 | "send-phone-message": [ 13 | { 14 | "action_name": "ldoorz", 15 | "display_name": "ldoorz" 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /examples/yaml/actions/action-example/code.js: -------------------------------------------------------------------------------- 1 | /** @type {PostLoginAction} */ 2 | module.exports = async (event, context) => { 3 | return {}; 4 | }; 5 | -------------------------------------------------------------------------------- /examples/yaml/config.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "AUTH0_DOMAIN": "..auth0.com", 3 | "AUTH0_CLIENT_ID": "", 4 | "AUTH0_CLIENT_SECRET": "", 5 | "AUTH0_KEYWORD_REPLACE_MAPPINGS": { 6 | "AUTH0_TENANT_NAME": "", 7 | "ENV": "DEV", 8 | "SETUP_CONFIG": { 9 | "client_id": "", 10 | "client_secret": "", 11 | "domain": "", 12 | "type": "OAUTH_APP" 13 | } 14 | }, 15 | "AUTH0_ALLOW_DELETE": false, 16 | "INCLUDED_PROPS": { 17 | "clients": [ "client_secret" ] 18 | }, 19 | "AUTH0_EXCLUDED_RULES": [ 20 | "rule-1-name", 21 | "rule-2-name" 22 | ], 23 | "EXCLUDED_PROPS": { 24 | "connections": [ "options.client_secret" ] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/yaml/databases/users/change_email.js: -------------------------------------------------------------------------------- 1 | function changeEmail(email, callback) { 2 | const msg = 'Please implement the change email script for this database connection'; 3 | return callback(new Error(msg)); 4 | } 5 | -------------------------------------------------------------------------------- /examples/yaml/databases/users/change_password.js: -------------------------------------------------------------------------------- 1 | function changePassword (email, newPassword, callback) { 2 | const msg = 'Please implement the change password script for this database connection'; 3 | return callback(new Error(msg)); 4 | } 5 | -------------------------------------------------------------------------------- /examples/yaml/databases/users/create.js: -------------------------------------------------------------------------------- 1 | function login(user, callback) { 2 | const msg = 'Please implement the create script for this database connection'; 3 | return callback(new Error(msg)); 4 | } 5 | -------------------------------------------------------------------------------- /examples/yaml/databases/users/delete.js: -------------------------------------------------------------------------------- 1 | function remove (id, callback) { 2 | const msg = 'Please implement the delete script for this database connection'; 3 | return callback(new Error(msg)); 4 | } 5 | -------------------------------------------------------------------------------- /examples/yaml/databases/users/get_user.js: -------------------------------------------------------------------------------- 1 | function getUser(email, callback) { 2 | const msg = 'Please implement the get user script for this database connection'; 3 | return callback(new Error(msg)); 4 | } 5 | -------------------------------------------------------------------------------- /examples/yaml/databases/users/login.js: -------------------------------------------------------------------------------- 1 | function login(email, password, callback) { 2 | const msg = 'Please implement the login script for this database connection'; 3 | return callback(new Error(msg)); 4 | } 5 | -------------------------------------------------------------------------------- /examples/yaml/databases/users/verify.js: -------------------------------------------------------------------------------- 1 | function login(email, callback) { 2 | const msg = 'Please implement the verify script for this database connection'; 3 | return callback(new Error(msg)); 4 | } 5 | -------------------------------------------------------------------------------- /examples/yaml/emails/change_email.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | test email 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/yaml/flows/Update KYC Name.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Update KYC Name", 3 | "actions": [ 4 | { 5 | "id": "update_user_metadata", 6 | "type": "AUTH0", 7 | "action": "UPDATE_USER", 8 | "allow_failure": false, 9 | "mask_output": false, 10 | "params": { 11 | "connection_id": "New Auth0 M2M Con", 12 | "user_id": "{{context.user.user_id}}", 13 | "changes": { 14 | "user_metadata": { 15 | "full_name": "{{fields.full_name}}" 16 | } 17 | } 18 | } 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /examples/yaml/forms/New Kyc form.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "New Kyc form", 3 | "languages": { 4 | "primary": "en" 5 | }, 6 | "nodes": [ 7 | { 8 | "id": "step_ggeX", 9 | "type": "STEP", 10 | "coordinates": { 11 | "x": 500, 12 | "y": 0 13 | }, 14 | "alias": "New step", 15 | "config": { 16 | "components": [ 17 | { 18 | "id": "full_name", 19 | "category": "FIELD", 20 | "type": "TEXT", 21 | "label": "Your Name", 22 | "required": true, 23 | "sensitive": false, 24 | "config": { 25 | "multiline": false, 26 | "min_length": 1, 27 | "max_length": 50 28 | } 29 | }, 30 | { 31 | "id": "next_button_3FbA", 32 | "category": "BLOCK", 33 | "type": "NEXT_BUTTON", 34 | "config": { 35 | "text": "Continue" 36 | } 37 | } 38 | ], 39 | "next_node": "flow_nyxC" 40 | } 41 | }, 42 | { 43 | "id": "flow_nyxC", 44 | "type": "FLOW", 45 | "coordinates": { 46 | "x": 1000, 47 | "y": 0 48 | }, 49 | "alias": "update kyc name", 50 | "config": { 51 | "flow_id": "Update KYC Name", 52 | "next_node": "$ending" 53 | } 54 | } 55 | ], 56 | "start": { 57 | "next_node": "step_ggeX", 58 | "coordinates": { 59 | "x": 0, 60 | "y": 0 61 | } 62 | }, 63 | "ending": { 64 | "resume_flow": true, 65 | "coordinates": { 66 | "x": 1250, 67 | "y": 0 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /examples/yaml/hooks/client-credentials-exchange.js: -------------------------------------------------------------------------------- 1 | /** 2 | @param {object} client - information about the client 3 | @param {string} client.name - name of client 4 | @param {string} client.id - client id 5 | @param {string} client.tenant - Auth0 tenant name 6 | @param {object} client.metadata - client metadata 7 | @param {array|undefined} scope - array of strings representing the scope claim or undefined 8 | @param {string} audience - token's audience claim 9 | @param {object} context - additional authorization context 10 | @param {object} context.webtask - webtask context 11 | @param {function} cb - function (error, accessTokenClaims) 12 | */ 13 | module.exports = function(client, scope, audience, context, cb) { 14 | var access_token = {}; 15 | access_token.scope = scope; 16 | 17 | // Modify scopes or add extra claims 18 | // access_token['https://example.com/claim'] = 'bar'; 19 | // access_token.scope.push('extra'); 20 | 21 | cb(null, access_token); 22 | }; 23 | -------------------------------------------------------------------------------- /examples/yaml/pages/error_page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

This is just an example, go update your error page :)

4 |

Env is @@ENV@@

5 | 6 | 7 | -------------------------------------------------------------------------------- /examples/yaml/pages/guardian_multifactor.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

This is just an example, go update your MFA page :)

4 |

Env is @@ENV@@

5 | 6 | 7 | -------------------------------------------------------------------------------- /examples/yaml/pages/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

This is just an example, go update your login page :)

4 |

Env is @@ENV@@

5 | 6 | 7 | -------------------------------------------------------------------------------- /examples/yaml/pages/password_reset.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

This is just an example, go update your password reset page :)

4 |

Env is @@ENV@@

5 | 6 | 7 | -------------------------------------------------------------------------------- /examples/yaml/prompts/screenRenderSettings/login-id_login-id.json: -------------------------------------------------------------------------------- 1 | { 2 | "prompt": "login-id", 3 | "screen": "login-id", 4 | "rendering_mode": "advanced", 5 | "context_configuration": [ 6 | "branding.settings", 7 | "branding.themes.default" 8 | ], 9 | "default_head_tags_disabled": true, 10 | "head_tags": [ 11 | { 12 | "tag": "script", 13 | "attributes": { 14 | "src": "http://127.0.0.1:8090/index.js", 15 | "defer": true 16 | } 17 | }, 18 | { 19 | "tag": "link", 20 | "attributes": { 21 | "rel": "stylesheet", 22 | "href": "http://127.0.0.1:8090/index.css" 23 | } 24 | }, 25 | { 26 | "tag": "meta", 27 | "attributes": { 28 | "name": "viewport", 29 | "content": "width=device-width, initial-scale=1" 30 | } 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /examples/yaml/prompts/screenRenderSettings/signup-id_signup-id.json: -------------------------------------------------------------------------------- 1 | { 2 | "prompt": "signup-id", 3 | "screen": "signup-id", 4 | "rendering_mode": "advanced", 5 | "context_configuration": [], 6 | "default_head_tags_disabled": false, 7 | "head_tags": [ 8 | { 9 | "tag": "script", 10 | "attributes": { 11 | "src": "URL_TO_YOUR_ASSET", 12 | "async": true, 13 | "defer": true, 14 | "integrity": [ 15 | "ASSET_SHA" 16 | ] 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /examples/yaml/rules/enrich_tokens.js: -------------------------------------------------------------------------------- 1 | function (user, context, callback) { 2 | const env = @@ENV@@; 3 | 4 | console.log(`env is ${env}`); 5 | 6 | // Add Env to Token 7 | context.idToken['https://myapp.com/env'] = @@ENV@@; 8 | 9 | callback(new Error(`This is just an example from auth0-deploy-cli, don\'t use this rule!`), user, context); 10 | } 11 | -------------------------------------------------------------------------------- /opslevel.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 1 3 | repository: 4 | owner: dx_customer_developer_tools 5 | tier: 6 | tags: 7 | -------------------------------------------------------------------------------- /src/commands/export.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import nconf from 'nconf'; 3 | import mkdirp from 'mkdirp'; 4 | 5 | import log from '../logger'; 6 | import { isDirectory } from '../utils'; 7 | import { setupContext } from '../context/index'; 8 | import { Config } from '../types'; 9 | import { ExportParams } from '../args'; 10 | 11 | export default async function exportCMD(params: ExportParams) { 12 | const { 13 | output_folder: outputFolder, 14 | base_path: basePath, 15 | config_file: configFile, 16 | config: configObj, 17 | export_ids: exportIds, 18 | secret: clientSecret, 19 | env: shouldInheritEnv = false, 20 | experimental_ea: experimentalEA, 21 | } = params; 22 | 23 | if (shouldInheritEnv) { 24 | nconf.env().use('memory'); 25 | } 26 | 27 | if (configFile) { 28 | nconf.file(configFile); 29 | } 30 | 31 | const overrides: Partial = { 32 | AUTH0_INPUT_FILE: outputFolder, 33 | AUTH0_BASE_PATH: basePath, 34 | ...(configObj || {}), 35 | }; 36 | 37 | // Prepare configuration by initializing nconf, then passing that as the provider to the config object 38 | // Allow passed in secret to override the configured one 39 | if (clientSecret) { 40 | overrides.AUTH0_CLIENT_SECRET = clientSecret; 41 | } 42 | 43 | // Allow passed in export_ids to override the configured one 44 | if (exportIds) { 45 | overrides.AUTH0_EXPORT_IDENTIFIERS = exportIds; 46 | } 47 | 48 | // Overrides AUTH0_INCLUDE_EXPERIMENTAL_EA is experimental_ea passed in command line 49 | if (experimentalEA) { 50 | overrides.AUTH0_EXPERIMENTAL_EA = experimentalEA; 51 | 52 | // nconf.overrides() sometimes doesn't work, so we need to set it manually to ensure it's set 53 | nconf.set('AUTH0_EXPERIMENTAL_EA', experimentalEA); 54 | } 55 | 56 | // Check output folder 57 | if (!isDirectory(outputFolder)) { 58 | log.info(`Creating ${outputFolder}`); 59 | mkdirp.sync(outputFolder); 60 | } 61 | 62 | if (params.format === 'yaml') { 63 | overrides.AUTH0_INPUT_FILE = path.join(outputFolder, 'tenant.yaml'); 64 | } 65 | 66 | nconf.overrides(overrides); 67 | 68 | // Setup context and load 69 | const context = await setupContext(nconf.get(), 'export'); 70 | await context.dump(); 71 | 72 | log.info('Export Successful'); 73 | } 74 | -------------------------------------------------------------------------------- /src/commands/import.ts: -------------------------------------------------------------------------------- 1 | import nconf from 'nconf'; 2 | import { configFactory } from '../configFactory'; 3 | import { deploy as toolsDeploy } from '../tools'; 4 | import log from '../logger'; 5 | import { setupContext } from '../context'; 6 | import { ImportParams } from '../args'; 7 | 8 | export default async function importCMD(params: ImportParams) { 9 | const { 10 | input_file: inputFile, 11 | base_path: basePath, 12 | config_file: configFile, 13 | config: configObj, 14 | env: shouldInheritEnv = false, 15 | secret: clientSecret, 16 | experimental_ea: experimentalEA, 17 | } = params; 18 | 19 | if (shouldInheritEnv) { 20 | nconf.env().use('memory'); 21 | 22 | const mappings = nconf.get('AUTH0_KEYWORD_REPLACE_MAPPINGS') || {}; 23 | nconf.set('AUTH0_KEYWORD_REPLACE_MAPPINGS', Object.assign(mappings, process.env)); 24 | } 25 | 26 | if (configFile) { 27 | nconf.file(configFile); 28 | } 29 | 30 | const overrides = { 31 | AUTH0_INPUT_FILE: inputFile, 32 | AUTH0_BASE_PATH: basePath, 33 | AUTH0_KEYWORD_REPLACE_MAPPINGS: {}, 34 | ...(configObj || {}), 35 | }; 36 | 37 | // Prepare configuration by initializing nconf, then passing that as the provider to the config object 38 | // Allow passed in secret to override the configured one 39 | if (clientSecret) { 40 | overrides.AUTH0_CLIENT_SECRET = clientSecret; 41 | } 42 | 43 | // Overrides AUTH0_INCLUDE_EXPERIMENTAL_EA is experimental_ea passed in command line 44 | if (experimentalEA) { 45 | overrides.AUTH0_EXPERIMENTAL_EA = experimentalEA; 46 | 47 | // nconf.overrides() sometimes doesn't work, so we need to set it manually to ensure it's set 48 | nconf.set('AUTH0_EXPERIMENTAL_EA', experimentalEA); 49 | } 50 | 51 | nconf.overrides(overrides); 52 | 53 | // Setup context and load 54 | const context = await setupContext(nconf.get(), 'import'); 55 | await context.loadAssetsFromLocal(); 56 | 57 | const config = configFactory(); 58 | config.setProvider((key) => nconf.get(key)); 59 | 60 | //@ts-ignore because context and assets still need to be typed TODO: type assets and type context 61 | await toolsDeploy(context.assets, context.mgmtClient, config); 62 | 63 | log.info('Import Successful'); 64 | } 65 | -------------------------------------------------------------------------------- /src/commands/index.ts: -------------------------------------------------------------------------------- 1 | import importCMD from './import'; 2 | import exportCMD from './export'; 3 | 4 | export default { 5 | import: importCMD, 6 | export: exportCMD, 7 | deploy: importCMD, 8 | dump: exportCMD, 9 | }; 10 | -------------------------------------------------------------------------------- /src/configFactory.ts: -------------------------------------------------------------------------------- 1 | import { Config } from './types'; 2 | 3 | export type ConfigFunction = (arg0: keyof Config) => any; // TODO: refactor configFactory function to allow for more precise typing 4 | 5 | export const configFactory = () => { 6 | const settings: Partial = {}; 7 | let currentProvider: ConfigFunction | null = null; 8 | 9 | const config = function getConfig(key: keyof Config) { 10 | if (settings && settings[key]) { 11 | return settings[key]; 12 | } 13 | 14 | if (!currentProvider) { 15 | throw new Error('A configuration provider has not been set'); 16 | } 17 | 18 | return currentProvider(key); 19 | }; 20 | 21 | config.setProvider = function setProvider(providerFunction: ConfigFunction) { 22 | currentProvider = providerFunction; 23 | }; 24 | 25 | config.setValue = function setValue(key: keyof Config, value: any) { 26 | settings[key] = value; 27 | }; 28 | 29 | return config; 30 | }; 31 | -------------------------------------------------------------------------------- /src/context/directory/handlers/customDomains.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs-extra'; 3 | import { constants } from '../../../tools'; 4 | import { existsMustBeDir, dumpJSON, loadJSON } from '../../../utils'; 5 | import { DirectoryHandler } from '.'; 6 | import DirectoryContext from '..'; 7 | import { Asset, ParsedAsset } from '../../../types'; 8 | 9 | type ParsedCustomDomains = ParsedAsset<'customDomains', Asset[]>; 10 | 11 | const getCustomDomainsDirectory = (filePath: string) => 12 | path.join(filePath, constants.CUSTOM_DOMAINS_DIRECTORY); 13 | 14 | const getCustomDomainsFile = (filePath: string) => 15 | path.join(getCustomDomainsDirectory(filePath), 'custom-domains.json'); 16 | 17 | function parse(context: DirectoryContext): ParsedCustomDomains { 18 | const customDomainsDirectory = getCustomDomainsDirectory(context.filePath); 19 | if (!existsMustBeDir(customDomainsDirectory)) return { customDomains: null }; // Skip 20 | 21 | const customDomainsFile = getCustomDomainsFile(context.filePath); 22 | 23 | return { 24 | customDomains: loadJSON(customDomainsFile, { 25 | mappings: context.mappings, 26 | disableKeywordReplacement: context.disableKeywordReplacement, 27 | }), 28 | }; 29 | } 30 | 31 | async function dump(context: DirectoryContext): Promise { 32 | const { customDomains } = context.assets; 33 | 34 | if (!customDomains) return; // Skip, nothing to dump 35 | 36 | // Create Rules folder 37 | const customDomainsDirectory = getCustomDomainsDirectory(context.filePath); 38 | fs.ensureDirSync(customDomainsDirectory); 39 | 40 | const customDomainsFile = getCustomDomainsFile(context.filePath); 41 | dumpJSON(customDomainsFile, customDomains); 42 | } 43 | 44 | const customDomainsHandler: DirectoryHandler = { 45 | parse, 46 | dump, 47 | }; 48 | 49 | export default customDomainsHandler; 50 | -------------------------------------------------------------------------------- /src/context/directory/handlers/emailProvider.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs-extra'; 3 | import { constants } from '../../../tools'; 4 | 5 | import { existsMustBeDir, isFile, dumpJSON, loadJSON } from '../../../utils'; 6 | import { emailProviderDefaults } from '../../defaults'; 7 | import { DirectoryHandler } from '.'; 8 | import DirectoryContext from '..'; 9 | import { Asset, ParsedAsset } from '../../../types'; 10 | 11 | type ParsedEmailProvider = ParsedAsset<'emailProvider', Asset>; 12 | 13 | function parse(context: DirectoryContext): ParsedEmailProvider { 14 | const emailsFolder = path.join(context.filePath, constants.EMAIL_TEMPLATES_DIRECTORY); 15 | if (!existsMustBeDir(emailsFolder)) return { emailProvider: null }; // Skip 16 | 17 | const providerFile = path.join(emailsFolder, 'provider.json'); 18 | 19 | if (isFile(providerFile)) { 20 | return { 21 | emailProvider: loadJSON(providerFile, { 22 | mappings: context.mappings, 23 | disableKeywordReplacement: context.disableKeywordReplacement, 24 | }), 25 | }; 26 | } 27 | 28 | return { emailProvider: null }; 29 | } 30 | 31 | async function dump(context: DirectoryContext): Promise { 32 | if (!context.assets.emailProvider) return; // Skip, nothing to dump 33 | 34 | const emailProvider: typeof context.assets.emailProvider = (() => { 35 | const excludedDefaults = context.assets.exclude?.defaults || []; 36 | if (!excludedDefaults.includes('emailProvider')) { 37 | // Add placeholder for credentials as they cannot be exported 38 | return emailProviderDefaults(context.assets.emailProvider); 39 | } 40 | return context.assets.emailProvider; 41 | })(); 42 | 43 | const emailsFolder = path.join(context.filePath, constants.EMAIL_TEMPLATES_DIRECTORY); 44 | fs.ensureDirSync(emailsFolder); 45 | 46 | const emailProviderFile = path.join(emailsFolder, 'provider.json'); 47 | dumpJSON(emailProviderFile, emailProvider); 48 | } 49 | 50 | const emailProviderHandler: DirectoryHandler = { 51 | parse, 52 | dump, 53 | }; 54 | 55 | export default emailProviderHandler; 56 | -------------------------------------------------------------------------------- /src/context/directory/handlers/guardianFactorProviders.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs-extra'; 3 | import { constants } from '../../../tools'; 4 | 5 | import { getFiles, existsMustBeDir, dumpJSON, loadJSON } from '../../../utils'; 6 | import { DirectoryHandler } from '.'; 7 | import DirectoryContext from '..'; 8 | import { Asset, ParsedAsset } from '../../../types'; 9 | 10 | type ParsedGuardianFactorProviders = ParsedAsset<'guardianFactorProviders', Asset[]>; 11 | 12 | function parse(context: DirectoryContext): ParsedGuardianFactorProviders { 13 | const factorProvidersFolder = path.join( 14 | context.filePath, 15 | constants.GUARDIAN_DIRECTORY, 16 | constants.GUARDIAN_PROVIDERS_DIRECTORY 17 | ); 18 | if (!existsMustBeDir(factorProvidersFolder)) return { guardianFactorProviders: null }; // Skip 19 | 20 | const foundFiles = getFiles(factorProvidersFolder, ['.json']); 21 | 22 | const guardianFactorProviders = foundFiles 23 | .map((f) => 24 | loadJSON(f, { 25 | mappings: context.mappings, 26 | disableKeywordReplacement: context.disableKeywordReplacement, 27 | }) 28 | ) 29 | .filter((p) => Object.keys(p).length > 0); // Filter out empty factorProvidersFolder 30 | 31 | return { 32 | guardianFactorProviders, 33 | }; 34 | } 35 | 36 | async function dump(context: DirectoryContext): Promise { 37 | const { guardianFactorProviders } = context.assets; 38 | 39 | if (!guardianFactorProviders) return; // Skip, nothing to dump 40 | 41 | const factorProvidersFolder = path.join( 42 | context.filePath, 43 | constants.GUARDIAN_DIRECTORY, 44 | constants.GUARDIAN_PROVIDERS_DIRECTORY 45 | ); 46 | fs.ensureDirSync(factorProvidersFolder); 47 | 48 | guardianFactorProviders.forEach((factorProvider) => { 49 | const factorProviderFile = path.join( 50 | factorProvidersFolder, 51 | `${factorProvider.name}-${factorProvider.provider}.json` 52 | ); 53 | dumpJSON(factorProviderFile, factorProvider); 54 | }); 55 | } 56 | 57 | const guardianFactorProvidersHandler: DirectoryHandler = { 58 | parse, 59 | dump, 60 | }; 61 | 62 | export default guardianFactorProvidersHandler; 63 | -------------------------------------------------------------------------------- /src/context/directory/handlers/guardianFactorTemplates.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs-extra'; 3 | import { constants } from '../../../tools'; 4 | 5 | import { getFiles, existsMustBeDir, dumpJSON, loadJSON } from '../../../utils'; 6 | import { DirectoryHandler } from '.'; 7 | import DirectoryContext from '..'; 8 | import { Asset, ParsedAsset } from '../../../types'; 9 | 10 | type ParsedGuardianFactorTemplates = ParsedAsset<'guardianFactorTemplates', Asset[]>; 11 | 12 | function parse(context: DirectoryContext): ParsedGuardianFactorTemplates { 13 | const factorTemplatesFolder = path.join( 14 | context.filePath, 15 | constants.GUARDIAN_DIRECTORY, 16 | constants.GUARDIAN_TEMPLATES_DIRECTORY 17 | ); 18 | if (!existsMustBeDir(factorTemplatesFolder)) return { guardianFactorTemplates: null }; // Skip 19 | 20 | const foundFiles = getFiles(factorTemplatesFolder, ['.json']); 21 | 22 | const guardianFactorTemplates = foundFiles 23 | .map((f) => 24 | loadJSON(f, { 25 | mappings: context.mappings, 26 | disableKeywordReplacement: context.disableKeywordReplacement, 27 | }) 28 | ) 29 | .filter((p) => Object.keys(p).length > 0); // Filter out empty guardianFactorTemplates 30 | 31 | return { 32 | guardianFactorTemplates, 33 | }; 34 | } 35 | 36 | async function dump(context: DirectoryContext): Promise { 37 | const { guardianFactorTemplates } = context.assets; 38 | 39 | if (!guardianFactorTemplates) return; // Skip, nothing to dump 40 | 41 | const factorTemplatesFolder = path.join( 42 | context.filePath, 43 | constants.GUARDIAN_DIRECTORY, 44 | constants.GUARDIAN_TEMPLATES_DIRECTORY 45 | ); 46 | fs.ensureDirSync(factorTemplatesFolder); 47 | 48 | guardianFactorTemplates.forEach((factorTemplates) => { 49 | const factorTemplatesFile = path.join(factorTemplatesFolder, `${factorTemplates.name}.json`); 50 | dumpJSON(factorTemplatesFile, factorTemplates); 51 | }); 52 | } 53 | 54 | const guardianFactorTemplatesHandler: DirectoryHandler = { 55 | parse, 56 | dump, 57 | }; 58 | 59 | export default guardianFactorTemplatesHandler; 60 | -------------------------------------------------------------------------------- /src/context/directory/handlers/guardianFactors.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs-extra'; 3 | import { constants } from '../../../tools'; 4 | 5 | import { getFiles, existsMustBeDir, dumpJSON, loadJSON } from '../../../utils'; 6 | import { DirectoryHandler } from '.'; 7 | import DirectoryContext from '..'; 8 | import { Asset, ParsedAsset } from '../../../types'; 9 | 10 | type ParsedGuardianFactors = ParsedAsset<'guardianFactors', Asset[]>; 11 | 12 | function parse(context: DirectoryContext): ParsedGuardianFactors { 13 | const factorsFolder = path.join( 14 | context.filePath, 15 | constants.GUARDIAN_DIRECTORY, 16 | constants.GUARDIAN_FACTORS_DIRECTORY 17 | ); 18 | if (!existsMustBeDir(factorsFolder)) return { guardianFactors: null }; // Skip 19 | 20 | const foundFiles = getFiles(factorsFolder, ['.json']); 21 | 22 | const guardianFactors = foundFiles 23 | .map((f) => 24 | loadJSON(f, { 25 | mappings: context.mappings, 26 | disableKeywordReplacement: context.disableKeywordReplacement, 27 | }) 28 | ) 29 | .filter((p) => Object.keys(p).length > 0); // Filter out empty guardianFactors 30 | 31 | return { 32 | guardianFactors, 33 | }; 34 | } 35 | 36 | async function dump(context: DirectoryContext): Promise { 37 | const { guardianFactors } = context.assets; 38 | 39 | if (!guardianFactors) return; // Skip, nothing to dump 40 | 41 | const factorsFolder = path.join( 42 | context.filePath, 43 | constants.GUARDIAN_DIRECTORY, 44 | constants.GUARDIAN_FACTORS_DIRECTORY 45 | ); 46 | fs.ensureDirSync(factorsFolder); 47 | 48 | guardianFactors.forEach((factor) => { 49 | const factorFile = path.join(factorsFolder, `${factor.name}.json`); 50 | dumpJSON(factorFile, factor); 51 | }); 52 | } 53 | 54 | const guardianFactorsHandler: DirectoryHandler = { 55 | parse, 56 | dump, 57 | }; 58 | 59 | export default guardianFactorsHandler; 60 | -------------------------------------------------------------------------------- /src/context/directory/handlers/guardianPhoneFactorMessageTypes.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs-extra'; 3 | import { constants } from '../../../tools'; 4 | import { existsMustBeDir, dumpJSON, loadJSON, isFile } from '../../../utils'; 5 | import { DirectoryHandler } from '.'; 6 | import DirectoryContext from '..'; 7 | import { Asset, ParsedAsset } from '../../../types'; 8 | 9 | type ParsedGuardianFactorMessageTypes = ParsedAsset<'guardianPhoneFactorMessageTypes', Asset>; 10 | 11 | function parse(context: DirectoryContext): ParsedGuardianFactorMessageTypes { 12 | const guardianFolder = path.join(context.filePath, constants.GUARDIAN_DIRECTORY); 13 | if (!existsMustBeDir(guardianFolder)) return { guardianPhoneFactorMessageTypes: null }; // Skip 14 | 15 | const file = path.join(guardianFolder, 'phoneFactorMessageTypes.json'); 16 | 17 | if (!isFile(file)) { 18 | return { guardianPhoneFactorMessageTypes: null }; 19 | } 20 | 21 | return { 22 | guardianPhoneFactorMessageTypes: loadJSON(file, { 23 | mappings: context.mappings, 24 | disableKeywordReplacement: context.disableKeywordReplacement, 25 | }), 26 | }; 27 | } 28 | 29 | async function dump(context: DirectoryContext): Promise { 30 | const { guardianPhoneFactorMessageTypes } = context.assets; 31 | 32 | if (!guardianPhoneFactorMessageTypes) return; // Skip, nothing to dump 33 | 34 | const guardianFolder = path.join(context.filePath, constants.GUARDIAN_DIRECTORY); 35 | fs.ensureDirSync(guardianFolder); 36 | 37 | const file = path.join(guardianFolder, 'phoneFactorMessageTypes.json'); 38 | dumpJSON(file, guardianPhoneFactorMessageTypes); 39 | } 40 | 41 | const guardianFactorMessageTypesHandler: DirectoryHandler = { 42 | parse, 43 | dump, 44 | }; 45 | 46 | export default guardianFactorMessageTypesHandler; 47 | -------------------------------------------------------------------------------- /src/context/directory/handlers/guardianPhoneFactorSelectedProvider.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs-extra'; 3 | import { constants } from '../../../tools'; 4 | import { existsMustBeDir, dumpJSON, loadJSON, isFile } from '../../../utils'; 5 | import { DirectoryHandler } from '.'; 6 | import DirectoryContext from '..'; 7 | import { Asset, ParsedAsset } from '../../../types'; 8 | 9 | type ParsedGuardianFactorSelectedProvider = ParsedAsset< 10 | 'guardianPhoneFactorSelectedProvider', 11 | Asset 12 | >; 13 | 14 | function parse(context: DirectoryContext): ParsedGuardianFactorSelectedProvider { 15 | const guardianFolder = path.join(context.filePath, constants.GUARDIAN_DIRECTORY); 16 | if (!existsMustBeDir(guardianFolder)) return { guardianPhoneFactorSelectedProvider: null }; // Skip 17 | 18 | const file = path.join(guardianFolder, 'phoneFactorSelectedProvider.json'); 19 | 20 | if (!isFile(file)) { 21 | return { guardianPhoneFactorSelectedProvider: null }; 22 | } 23 | 24 | return { 25 | guardianPhoneFactorSelectedProvider: loadJSON(file, { 26 | mappings: context.mappings, 27 | disableKeywordReplacement: context.disableKeywordReplacement, 28 | }), 29 | }; 30 | } 31 | 32 | async function dump(context: DirectoryContext): Promise { 33 | const { guardianPhoneFactorSelectedProvider } = context.assets; 34 | 35 | if (!guardianPhoneFactorSelectedProvider) return; // Skip, nothing to dump 36 | 37 | const guardianFolder = path.join(context.filePath, constants.GUARDIAN_DIRECTORY); 38 | fs.ensureDirSync(guardianFolder); 39 | 40 | const file = path.join(guardianFolder, 'phoneFactorSelectedProvider.json'); 41 | dumpJSON(file, guardianPhoneFactorSelectedProvider); 42 | } 43 | 44 | const guardianFactorSelectedProviderHandler: DirectoryHandler = 45 | { 46 | parse, 47 | dump, 48 | }; 49 | 50 | export default guardianFactorSelectedProviderHandler; 51 | -------------------------------------------------------------------------------- /src/context/directory/handlers/guardianPolicies.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs-extra'; 3 | import { constants } from '../../../tools'; 4 | import { existsMustBeDir, dumpJSON, loadJSON, isFile } from '../../../utils'; 5 | import { DirectoryHandler } from '.'; 6 | import DirectoryContext from '..'; 7 | import { Asset, ParsedAsset } from '../../../types'; 8 | 9 | type ParsedGuardianPolicies = ParsedAsset<'guardianPolicies', Asset[]>; 10 | 11 | function parse(context: DirectoryContext): ParsedGuardianPolicies { 12 | const guardianFolder = path.join(context.filePath, constants.GUARDIAN_DIRECTORY); 13 | if (!existsMustBeDir(guardianFolder)) return { guardianPolicies: null }; // Skip 14 | 15 | const file = path.join(guardianFolder, 'policies.json'); 16 | 17 | if (!isFile(file)) { 18 | return { guardianPolicies: null }; 19 | } 20 | 21 | return { 22 | guardianPolicies: loadJSON(file, { 23 | mappings: context.mappings, 24 | disableKeywordReplacement: context.disableKeywordReplacement, 25 | }), 26 | }; 27 | } 28 | 29 | async function dump(context: DirectoryContext) { 30 | const { guardianPolicies } = context.assets; 31 | 32 | if (!guardianPolicies) return; // Skip, nothing to dump 33 | 34 | const guardianFolder = path.join(context.filePath, constants.GUARDIAN_DIRECTORY); 35 | fs.ensureDirSync(guardianFolder); 36 | 37 | const file = path.join(guardianFolder, 'policies.json'); 38 | dumpJSON(file, guardianPolicies); 39 | } 40 | 41 | const guardianPoliciesHandler: DirectoryHandler = { 42 | parse, 43 | dump, 44 | }; 45 | 46 | export default guardianPoliciesHandler; 47 | -------------------------------------------------------------------------------- /src/context/directory/handlers/hooks.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs-extra'; 3 | import { constants } from '../../../tools'; 4 | 5 | import { getFiles, existsMustBeDir, dumpJSON, loadJSON, sanitize } from '../../../utils'; 6 | import log from '../../../logger'; 7 | import { DirectoryHandler } from '.'; 8 | import DirectoryContext from '..'; 9 | import { Asset, ParsedAsset } from '../../../types'; 10 | 11 | type ParsedHooks = ParsedAsset<'hooks', Asset[]>; 12 | 13 | function parse(context: DirectoryContext): ParsedHooks { 14 | const hooksFolder = path.join(context.filePath, constants.HOOKS_DIRECTORY); 15 | if (!existsMustBeDir(hooksFolder)) return { hooks: null }; // Skip 16 | 17 | const files = getFiles(hooksFolder, ['.json']); 18 | 19 | const hooks = files.map((f) => { 20 | const hook = { 21 | ...loadJSON(f, { 22 | mappings: context.mappings, 23 | disableKeywordReplacement: context.disableKeywordReplacement, 24 | }), 25 | }; 26 | if (hook.script) { 27 | hook.script = context.loadFile(hook.script, constants.HOOKS_DIRECTORY); 28 | } 29 | 30 | hook.name = hook.name.toLowerCase().replace(/\s/g, '-'); 31 | 32 | return hook; 33 | }); 34 | 35 | return { 36 | hooks, 37 | }; 38 | } 39 | 40 | async function dump(context: DirectoryContext): Promise { 41 | const hooks = context.assets.hooks; 42 | 43 | if (!hooks) return; 44 | 45 | // Create Hooks folder 46 | const hooksFolder = path.join(context.filePath, constants.HOOKS_DIRECTORY); 47 | fs.ensureDirSync(hooksFolder); 48 | hooks.forEach((hook) => { 49 | // Dump script to file 50 | // For cases when hook does not have `meta['hook-name']` 51 | hook.name = hook.name || hook.id; 52 | const name = sanitize(hook.name); 53 | const hookCode = path.join(hooksFolder, `${name}.js`); 54 | log.info(`Writing ${hookCode}`); 55 | fs.writeFileSync(hookCode, hook.script); 56 | 57 | // Dump template metadata 58 | const hookFile = path.join(hooksFolder, `${name}.json`); 59 | dumpJSON(hookFile, { ...hook, script: `./${name}.js` }); 60 | }); 61 | } 62 | 63 | const hooksHandler: DirectoryHandler = { 64 | parse, 65 | dump, 66 | }; 67 | 68 | export default hooksHandler; 69 | -------------------------------------------------------------------------------- /src/context/directory/handlers/logStreams.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs-extra'; 3 | import { constants } from '../../../tools'; 4 | import { getFiles, existsMustBeDir, dumpJSON, loadJSON, sanitize } from '../../../utils'; 5 | import { DirectoryHandler } from '.'; 6 | import DirectoryContext from '..'; 7 | import { Asset, ParsedAsset } from '../../../types'; 8 | import { logStreamDefaults } from '../../defaults'; 9 | 10 | type ParsedLogStreams = ParsedAsset<'logStreams', Asset[]>; 11 | 12 | function parse(context: DirectoryContext): ParsedLogStreams { 13 | const logStreamsDirectory = path.join(context.filePath, constants.LOG_STREAMS_DIRECTORY); 14 | if (!existsMustBeDir(logStreamsDirectory)) return { logStreams: null }; // Skip 15 | 16 | const foundFiles = getFiles(logStreamsDirectory, ['.json']); 17 | 18 | const logStreams = foundFiles 19 | .map((f) => 20 | loadJSON(f, { 21 | mappings: context.mappings, 22 | disableKeywordReplacement: context.disableKeywordReplacement, 23 | }) 24 | ) 25 | .filter((p) => Object.keys(p).length > 0); // Filter out empty rulesConfigs 26 | 27 | return { 28 | logStreams, 29 | }; 30 | } 31 | 32 | async function dump(context: DirectoryContext): Promise { 33 | const { logStreams } = context.assets; 34 | 35 | if (!logStreams) return; // Skip, nothing to dump 36 | 37 | // Create Rules folder 38 | const logStreamsDirectory = path.join(context.filePath, constants.LOG_STREAMS_DIRECTORY); 39 | 40 | fs.ensureDirSync(logStreamsDirectory); 41 | 42 | // masked sensitive fields 43 | const maskedLogStreams = logStreamDefaults(logStreams); 44 | 45 | maskedLogStreams.forEach((logStream) => { 46 | const ruleFile = path.join(logStreamsDirectory, `${sanitize(logStream.name)}.json`); 47 | 48 | dumpJSON(ruleFile, logStream); 49 | }); 50 | } 51 | 52 | const logStreamsHandler: DirectoryHandler = { 53 | parse, 54 | dump, 55 | }; 56 | 57 | export default logStreamsHandler; 58 | -------------------------------------------------------------------------------- /src/context/directory/handlers/networkACLs.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs-extra'; 3 | import { constants } from '../../../tools'; 4 | import { getFiles, existsMustBeDir, dumpJSON, loadJSON, sanitize } from '../../../utils'; 5 | import { DirectoryHandler } from '.'; 6 | import DirectoryContext from '..'; 7 | import { ParsedAsset } from '../../../types'; 8 | import { NetworkACL } from '../../../tools/auth0/handlers/networkACLs'; 9 | import log from '../../../logger'; 10 | 11 | type ParsedNetworkACLs = ParsedAsset<'networkACLs', NetworkACL[]>; 12 | 13 | function parse(context: DirectoryContext): ParsedNetworkACLs { 14 | const networkACLsDirectory = path.join(context.filePath, constants.NETWORK_ACLS_DIRECTORY); 15 | if (!existsMustBeDir(networkACLsDirectory)) return { networkACLs: null }; // Skip 16 | 17 | const foundFiles = getFiles(networkACLsDirectory, ['.json']); 18 | 19 | const networkACLs = foundFiles 20 | .map((f) => 21 | loadJSON(f, { 22 | mappings: context.mappings, 23 | disableKeywordReplacement: context.disableKeywordReplacement, 24 | }) 25 | ) 26 | .filter((p) => Object.keys(p).length > 0); // Filter out empty configs 27 | 28 | return { 29 | networkACLs, 30 | }; 31 | } 32 | 33 | async function dump(context: DirectoryContext): Promise { 34 | const { networkACLs } = context.assets; 35 | 36 | if (!networkACLs) return; // Skip, nothing to dump 37 | 38 | if (Array.isArray(networkACLs) && networkACLs.length === 0) { 39 | log.info('No network ACLs available, skipping dump'); 40 | return; 41 | } 42 | 43 | // Create Network ACLs folder 44 | const networkACLsDirectory = path.join(context.filePath, constants.NETWORK_ACLS_DIRECTORY); 45 | fs.ensureDirSync(networkACLsDirectory); 46 | 47 | const removeKeysFromOutput = ['created_at', 'updated_at']; 48 | 49 | networkACLs.forEach((networkACL) => { 50 | removeKeysFromOutput.forEach((key) => { 51 | if (key in networkACL) { 52 | delete networkACL[key]; 53 | } 54 | }); 55 | const fileName = networkACL.description 56 | ? `${sanitize(networkACL.description)}-p-${networkACL.priority}` 57 | : `network-acl-p-${networkACL.priority}`; 58 | const filePath = path.join(networkACLsDirectory, `${fileName}.json`); 59 | dumpJSON(filePath, networkACL); 60 | }); 61 | } 62 | 63 | const networkACLsHandler: DirectoryHandler = { 64 | parse, 65 | dump, 66 | }; 67 | 68 | export default networkACLsHandler; 69 | -------------------------------------------------------------------------------- /src/context/directory/handlers/organizations.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs-extra'; 3 | 4 | import log from '../../../logger'; 5 | import { getFiles, existsMustBeDir, dumpJSON, loadJSON, sanitize } from '../../../utils'; 6 | import { DirectoryHandler } from '.'; 7 | import DirectoryContext from '..'; 8 | import { Asset, ParsedAsset } from '../../../types'; 9 | 10 | type ParsedOrganizations = ParsedAsset<'organizations', Asset[]>; 11 | 12 | function parse(context: DirectoryContext): ParsedOrganizations { 13 | const organizationsFolder = path.join(context.filePath, 'organizations'); 14 | 15 | if (!existsMustBeDir(organizationsFolder)) return { organizations: null }; // Skip 16 | 17 | const files = getFiles(organizationsFolder, ['.json']); 18 | 19 | const organizations = files.map((f) => { 20 | const org = { 21 | ...loadJSON(f, { 22 | mappings: context.mappings, 23 | disableKeywordReplacement: context.disableKeywordReplacement, 24 | }), 25 | }; 26 | return org; 27 | }); 28 | 29 | return { 30 | organizations, 31 | }; 32 | } 33 | 34 | async function dump(context: DirectoryContext): Promise { 35 | const { organizations } = context.assets; 36 | 37 | // API returns an empty object if no grants are present 38 | if (!organizations || organizations.constructor === Object) return; // Skip, nothing to dump 39 | 40 | const organizationsFolder = path.join(context.filePath, 'organizations'); 41 | fs.ensureDirSync(organizationsFolder); 42 | 43 | organizations.forEach((organization) => { 44 | const organizationFile = path.join(organizationsFolder, sanitize(`${organization.name}.json`)); 45 | log.info(`Writing ${organizationFile}`); 46 | 47 | if (organization.connections.length > 0) { 48 | organization.connections = organization.connections.map((c) => { 49 | // connection is a computed field 50 | const name = c.connection && c.connection.name; 51 | 52 | const conn = { 53 | name, 54 | ...c, 55 | }; 56 | 57 | delete conn.connection_id; 58 | delete conn.connection; 59 | 60 | return conn; 61 | }); 62 | } 63 | 64 | dumpJSON(organizationFile, organization); 65 | }); 66 | } 67 | 68 | const organizationsHandler: DirectoryHandler = { 69 | parse, 70 | dump, 71 | }; 72 | 73 | export default organizationsHandler; 74 | -------------------------------------------------------------------------------- /src/context/directory/handlers/phoneProvider.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs-extra'; 3 | import { constants } from '../../../tools'; 4 | 5 | import { existsMustBeDir, isFile, dumpJSON, loadJSON } from '../../../utils'; 6 | import { DirectoryHandler } from '.'; 7 | import DirectoryContext from '..'; 8 | import { ParsedAsset } from '../../../types'; 9 | import { PhoneProvider } from '../../../tools/auth0/handlers/phoneProvider'; 10 | import { phoneProviderDefaults } from '../../defaults'; 11 | 12 | type ParsedPhoneProvider = ParsedAsset<'phoneProviders', PhoneProvider[]>; 13 | 14 | function parse(context: DirectoryContext): ParsedPhoneProvider { 15 | const phoneProvidersFolder = path.join(context.filePath, constants.PHONE_PROVIDER_DIRECTORY); 16 | if (!existsMustBeDir(phoneProvidersFolder)) return { phoneProviders: null }; // Skip 17 | 18 | const providerFile = path.join(phoneProvidersFolder, 'provider.json'); 19 | 20 | if (isFile(providerFile)) { 21 | return { 22 | phoneProviders: loadJSON(providerFile, { 23 | mappings: context.mappings, 24 | disableKeywordReplacement: context.disableKeywordReplacement, 25 | }), 26 | }; 27 | } 28 | 29 | return { phoneProviders: null }; 30 | } 31 | 32 | async function dump(context: DirectoryContext): Promise { 33 | let { phoneProviders } = context.assets; 34 | 35 | if (!phoneProviders) { 36 | return; 37 | } // Skip, nothing to dump 38 | 39 | const phoneProvidersFolder = path.join(context.filePath, constants.PHONE_PROVIDER_DIRECTORY); 40 | fs.ensureDirSync(phoneProvidersFolder); 41 | 42 | const phoneProviderFile = path.join(phoneProvidersFolder, 'provider.json'); 43 | 44 | phoneProviders = phoneProviders.map((provider) => { 45 | provider = phoneProviderDefaults(provider); 46 | return provider; 47 | }); 48 | 49 | dumpJSON(phoneProviderFile, phoneProviders); 50 | } 51 | 52 | const phoneProvidersHandler: DirectoryHandler = { 53 | parse, 54 | dump, 55 | }; 56 | 57 | export default phoneProvidersHandler; 58 | -------------------------------------------------------------------------------- /src/context/directory/handlers/resourceServers.ts: -------------------------------------------------------------------------------- 1 | import { ResourceServer } from 'auth0'; 2 | import path from 'path'; 3 | import fs from 'fs-extra'; 4 | import { constants } from '../../../tools'; 5 | import { getFiles, existsMustBeDir, dumpJSON, loadJSON, sanitize } from '../../../utils'; 6 | import { DirectoryHandler } from '.'; 7 | import DirectoryContext from '..'; 8 | import { ParsedAsset } from '../../../types'; 9 | 10 | type ParsedResourceServers = ParsedAsset<'resourceServers', ResourceServer[]>; 11 | 12 | function parse(context: DirectoryContext): ParsedResourceServers { 13 | const resourceServersFolder = path.join(context.filePath, constants.RESOURCE_SERVERS_DIRECTORY); 14 | if (!existsMustBeDir(resourceServersFolder)) return { resourceServers: null }; // Skip 15 | 16 | const foundFiles = getFiles(resourceServersFolder, ['.json']); 17 | 18 | const resourceServers = foundFiles 19 | .map((f) => 20 | loadJSON(f, { 21 | mappings: context.mappings, 22 | disableKeywordReplacement: context.disableKeywordReplacement, 23 | }) 24 | ) 25 | .filter((p) => Object.keys(p).length > 0); // Filter out empty resourceServers 26 | 27 | return { 28 | resourceServers, 29 | }; 30 | } 31 | 32 | async function dump(context: DirectoryContext): Promise { 33 | const { resourceServers } = context.assets; 34 | 35 | if (!resourceServers) return; // Skip, nothing to dump 36 | 37 | const resourceServersFolder = path.join(context.filePath, constants.RESOURCE_SERVERS_DIRECTORY); 38 | fs.ensureDirSync(resourceServersFolder); 39 | 40 | resourceServers.forEach((resourceServer) => { 41 | const resourceServerFile = path.join( 42 | resourceServersFolder, 43 | sanitize(`${resourceServer.name}.json`) 44 | ); 45 | dumpJSON(resourceServerFile, resourceServer); 46 | }); 47 | } 48 | 49 | const resourceServersHandler: DirectoryHandler = { 50 | parse, 51 | dump, 52 | }; 53 | 54 | export default resourceServersHandler; 55 | -------------------------------------------------------------------------------- /src/context/directory/handlers/roles.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs-extra'; 3 | import { constants } from '../../../tools'; 4 | 5 | import log from '../../../logger'; 6 | import { getFiles, existsMustBeDir, dumpJSON, loadJSON, sanitize } from '../../../utils'; 7 | import { DirectoryHandler } from '.'; 8 | import DirectoryContext from '..'; 9 | import { Asset, ParsedAsset } from '../../../types'; 10 | 11 | type ParsedRoles = ParsedAsset<'roles', Asset[]>; 12 | 13 | function parse(context: DirectoryContext): ParsedRoles { 14 | const rolesFolder = path.join(context.filePath, constants.ROLES_DIRECTORY); 15 | if (!existsMustBeDir(rolesFolder)) return { roles: null }; // Skip 16 | 17 | const files = getFiles(rolesFolder, ['.json']); 18 | 19 | const roles = files.map((f) => { 20 | const role = { 21 | ...loadJSON(f, { 22 | mappings: context.mappings, 23 | disableKeywordReplacement: context.disableKeywordReplacement, 24 | }), 25 | }; 26 | return role; 27 | }); 28 | 29 | return { 30 | roles, 31 | }; 32 | } 33 | 34 | async function dump(context: DirectoryContext) { 35 | const { roles } = context.assets; 36 | 37 | // API returns an empty object if no grants are present 38 | if (!roles || roles.constructor === Object) return; // Skip, nothing to dump 39 | 40 | const rolesFolder = path.join(context.filePath, constants.ROLES_DIRECTORY); 41 | fs.ensureDirSync(rolesFolder); 42 | 43 | roles.forEach((role) => { 44 | const roleFile = path.join(rolesFolder, sanitize(`${role.name}.json`)); 45 | log.info(`Writing ${roleFile}`); 46 | 47 | // remove empty description 48 | if (role.description === null) { 49 | delete role.description; 50 | } 51 | 52 | dumpJSON(roleFile, role); 53 | }); 54 | } 55 | 56 | const rolesHandler: DirectoryHandler = { 57 | parse, 58 | dump, 59 | }; 60 | 61 | export default rolesHandler; 62 | -------------------------------------------------------------------------------- /src/context/directory/handlers/rules.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs-extra'; 3 | import { constants } from '../../../tools'; 4 | 5 | import log from '../../../logger'; 6 | import { getFiles, existsMustBeDir, dumpJSON, loadJSON, sanitize } from '../../../utils'; 7 | 8 | import { DirectoryHandler } from './index'; 9 | import DirectoryContext from '..'; 10 | import { Asset, ParsedAsset } from '../../../types'; 11 | 12 | type ParsedRules = ParsedAsset<'rules', Asset[]>; 13 | 14 | function parse(context: DirectoryContext): ParsedRules { 15 | const rulesFolder = path.join(context.filePath, constants.RULES_DIRECTORY); 16 | if (!existsMustBeDir(rulesFolder)) return { rules: null }; // Skip 17 | 18 | const files: string[] = getFiles(rulesFolder, ['.json']); 19 | 20 | const rules = files.map((f) => { 21 | const rule = { 22 | ...loadJSON(f, { 23 | mappings: context.mappings, 24 | disableKeywordReplacement: context.disableKeywordReplacement, 25 | }), 26 | }; 27 | if (rule.script) { 28 | rule.script = context.loadFile(rule.script, constants.RULES_DIRECTORY); 29 | } 30 | return rule; 31 | }); 32 | 33 | return { 34 | rules, 35 | }; 36 | } 37 | 38 | async function dump(context: DirectoryContext): Promise { 39 | const { rules } = context.assets; 40 | 41 | if (!rules) return; // Skip, nothing to dump 42 | 43 | // Create Rules folder 44 | const rulesFolder = path.join(context.filePath, constants.RULES_DIRECTORY); 45 | fs.ensureDirSync(rulesFolder); 46 | rules.forEach((rule) => { 47 | // Dump script to file 48 | const name = sanitize(rule.name); 49 | const ruleJS = path.join(rulesFolder, `${name}.js`); 50 | log.info(`Writing ${ruleJS}`); 51 | fs.writeFileSync(ruleJS, rule.script); 52 | 53 | // Dump template metadata 54 | const ruleFile = path.join(rulesFolder, `${name}.json`); 55 | dumpJSON(ruleFile, { ...rule, script: `./${name}.js` }); 56 | }); 57 | } 58 | 59 | const rulesHandler: DirectoryHandler = { 60 | parse, 61 | dump, 62 | }; 63 | 64 | export default rulesHandler; 65 | -------------------------------------------------------------------------------- /src/context/directory/handlers/rulesConfigs.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { constants } from '../../../tools'; 3 | 4 | import { getFiles, existsMustBeDir, loadJSON } from '../../../utils'; 5 | import { DirectoryHandler } from '.'; 6 | import DirectoryContext from '..'; 7 | import { Asset, ParsedAsset } from '../../../types'; 8 | 9 | type ParsedRulesConfigs = ParsedAsset<'rulesConfigs', Asset[]>; 10 | 11 | function parse(context: DirectoryContext): ParsedRulesConfigs { 12 | const rulesConfigsFolder = path.join(context.filePath, constants.RULES_CONFIGS_DIRECTORY); 13 | if (!existsMustBeDir(rulesConfigsFolder)) return { rulesConfigs: null }; // Skip 14 | 15 | const foundFiles: string[] = getFiles(rulesConfigsFolder, ['.json']); 16 | 17 | const rulesConfigs = foundFiles 18 | .map((f) => 19 | loadJSON(f, { 20 | mappings: context.mappings, 21 | disableKeywordReplacement: context.disableKeywordReplacement, 22 | }) 23 | ) 24 | .filter((p) => Object.keys(p).length > 0); // Filter out empty rulesConfigs 25 | 26 | return { 27 | rulesConfigs, 28 | }; 29 | } 30 | 31 | async function dump(): Promise { 32 | // do not export rulesConfigs as its values cannot be extracted 33 | return; 34 | } 35 | 36 | const rulesConfigsHandler: DirectoryHandler = { 37 | parse, 38 | dump, 39 | }; 40 | 41 | export default rulesConfigsHandler; 42 | -------------------------------------------------------------------------------- /src/context/directory/handlers/selfServiceProfiles.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs-extra'; 3 | import { constants } from '../../../tools'; 4 | import log from '../../../logger'; 5 | 6 | import { dumpJSON, existsMustBeDir, getFiles, loadJSON, sanitize } from '../../../utils'; 7 | import { DirectoryHandler } from '.'; 8 | import DirectoryContext from '..'; 9 | import { ParsedAsset } from '../../../types'; 10 | import { SsProfileWithCustomText } from '../../../tools/auth0/handlers/selfServiceProfiles'; 11 | 12 | type ParsedSelfServiceProfiles = ParsedAsset< 13 | 'selfServiceProfiles', 14 | Partial[] 15 | >; 16 | 17 | function parse(context: DirectoryContext): ParsedSelfServiceProfiles { 18 | const selfServiceProfilesFolder = path.join( 19 | context.filePath, 20 | constants.SELF_SERVICE_PROFILE_DIRECTORY 21 | ); 22 | if (!existsMustBeDir(selfServiceProfilesFolder)) return { selfServiceProfiles: null }; // Skip 23 | 24 | const files = getFiles(selfServiceProfilesFolder, ['.json']); 25 | 26 | const selfServiceProfiles = files.map((f) => { 27 | const ssProfiles = { 28 | ...loadJSON(f, { 29 | mappings: context.mappings, 30 | disableKeywordReplacement: context.disableKeywordReplacement, 31 | }), 32 | }; 33 | return ssProfiles; 34 | }); 35 | 36 | return { 37 | selfServiceProfiles, 38 | }; 39 | } 40 | 41 | async function dump(context: DirectoryContext): Promise { 42 | const { selfServiceProfiles } = context.assets; 43 | if (!selfServiceProfiles) return; 44 | 45 | const selfServiceProfilesFolder = path.join( 46 | context.filePath, 47 | constants.SELF_SERVICE_PROFILE_DIRECTORY 48 | ); 49 | fs.ensureDirSync(selfServiceProfilesFolder); 50 | 51 | selfServiceProfiles.forEach((profile) => { 52 | const ssProfileFile = path.join(selfServiceProfilesFolder, sanitize(`${profile.name}.json`)); 53 | log.info(`Writing ${ssProfileFile}`); 54 | 55 | if ('created_at' in profile) { 56 | delete profile.created_at; 57 | } 58 | 59 | if ('updated_at' in profile) { 60 | delete profile.updated_at; 61 | } 62 | 63 | dumpJSON(ssProfileFile, profile); 64 | }); 65 | } 66 | 67 | const emailProviderHandler: DirectoryHandler = { 68 | parse, 69 | dump, 70 | }; 71 | 72 | export default emailProviderHandler; 73 | -------------------------------------------------------------------------------- /src/context/directory/handlers/tenant.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { existsMustBeDir, isFile, dumpJSON, loadJSON, clearTenantFlags } from '../../../utils'; 3 | import { sessionDurationsToMinutes } from '../../../sessionDurationsToMinutes'; 4 | import { DirectoryHandler } from '.'; 5 | import DirectoryContext from '..'; 6 | import { Asset, ParsedAsset } from '../../../types'; 7 | 8 | type ParsedTenant = ParsedAsset< 9 | 'tenant', 10 | { 11 | session_lifetime: number; 12 | idle_session_lifetime: number; 13 | } & { 14 | [key: string]: Asset; 15 | } 16 | >; 17 | 18 | function parse(context: DirectoryContext): ParsedTenant { 19 | const baseFolder = path.join(context.filePath); 20 | if (!existsMustBeDir(baseFolder)) return { tenant: null }; // Skip 21 | 22 | const tenantFile = path.join(baseFolder, 'tenant.json'); 23 | 24 | if (!isFile(tenantFile)) { 25 | return { tenant: null }; 26 | } 27 | /* eslint-disable camelcase */ 28 | const { 29 | session_lifetime, 30 | idle_session_lifetime, 31 | ...tenant 32 | }: { 33 | session_lifetime?: number; 34 | idle_session_lifetime?: number; 35 | [key: string]: any; 36 | } = loadJSON(tenantFile, { 37 | mappings: context.mappings, 38 | disableKeywordReplacement: context.disableKeywordReplacement, 39 | }); 40 | 41 | clearTenantFlags(tenant); 42 | 43 | const sessionDurations = sessionDurationsToMinutes({ session_lifetime, idle_session_lifetime }); 44 | 45 | return { 46 | //@ts-ignore 47 | tenant: { 48 | ...tenant, 49 | ...sessionDurations, 50 | }, 51 | }; 52 | } 53 | 54 | async function dump(context: DirectoryContext): Promise { 55 | const { tenant } = context.assets; 56 | 57 | if (!tenant) return; // Skip, nothing to dump 58 | 59 | clearTenantFlags(tenant); 60 | 61 | const tenantFile = path.join(context.filePath, 'tenant.json'); 62 | dumpJSON(tenantFile, tenant); 63 | return; 64 | } 65 | 66 | const tenantHandler: DirectoryHandler = { 67 | parse, 68 | dump, 69 | }; 70 | 71 | export default tenantHandler; 72 | -------------------------------------------------------------------------------- /src/context/directory/handlers/themes.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { ensureDirSync } from 'fs-extra'; 3 | import { getFiles, dumpJSON, loadJSON, existsMustBeDir } from '../../../utils'; 4 | import { DirectoryHandler } from '.'; 5 | import DirectoryContext from '..'; 6 | import { ParsedAsset } from '../../../types'; 7 | import { constants } from '../../../tools'; 8 | import { Theme } from '../../../tools/auth0/handlers/themes'; 9 | 10 | type ParsedThemes = ParsedAsset<'themes', Theme[]>; 11 | 12 | function parse(context: DirectoryContext): ParsedThemes { 13 | const baseFolder = path.join(context.filePath, constants.THEMES_DIRECTORY); 14 | if (!existsMustBeDir(baseFolder)) { 15 | return { themes: null }; 16 | } 17 | 18 | const themeDefinitionsFiles = getFiles(baseFolder, ['.json']); 19 | if (!themeDefinitionsFiles.length) { 20 | return { themes: [] }; 21 | } 22 | 23 | const themes = themeDefinitionsFiles.map( 24 | (themeDefinitionsFile) => 25 | loadJSON(themeDefinitionsFile, { 26 | mappings: context.mappings, 27 | disableKeywordReplacement: context.disableKeywordReplacement, 28 | }) as Theme 29 | ); 30 | return { themes }; 31 | } 32 | 33 | async function dump(context: DirectoryContext): Promise { 34 | const { themes } = context.assets; 35 | if (!themes) { 36 | return; 37 | } 38 | 39 | const baseFolder = path.join(context.filePath, constants.THEMES_DIRECTORY); 40 | ensureDirSync(baseFolder); 41 | 42 | themes.forEach((themeDefinition, i) => { 43 | dumpJSON(path.join(baseFolder, `theme${i ? i : ''}.json`), themeDefinition); 44 | }); 45 | } 46 | 47 | const themesHandler: DirectoryHandler = { 48 | parse, 49 | dump, 50 | }; 51 | 52 | export default themesHandler; 53 | -------------------------------------------------------------------------------- /src/context/directory/handlers/triggers.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs-extra'; 3 | import { constants } from '../../../tools'; 4 | import { DirectoryHandler } from '.'; 5 | import DirectoryContext from '..'; 6 | 7 | import { getFiles, existsMustBeDir, loadJSON } from '../../../utils'; 8 | import log from '../../../logger'; 9 | import { Asset, ParsedAsset } from '../../../types'; 10 | 11 | type ParsedTriggers = ParsedAsset<'triggers', Asset[]>; 12 | 13 | function parse(context: DirectoryContext): ParsedTriggers { 14 | const triggersFolder = path.join(context.filePath, constants.TRIGGERS_DIRECTORY); 15 | 16 | if (!existsMustBeDir(triggersFolder)) return { triggers: null }; // Skip 17 | 18 | const files = getFiles(triggersFolder, ['.json']); 19 | 20 | const triggers = { 21 | ...loadJSON(files[0], { 22 | mappings: context.mappings, 23 | disableKeywordReplacement: context.disableKeywordReplacement, 24 | }), 25 | }; 26 | 27 | return { triggers }; 28 | } 29 | 30 | async function dump(context: DirectoryContext): Promise { 31 | const { triggers } = context.assets; 32 | 33 | if (!triggers) return; 34 | 35 | // Create triggers folder 36 | const triggersFolder = path.join(context.filePath, constants.TRIGGERS_DIRECTORY); 37 | fs.ensureDirSync(triggersFolder); 38 | const triggerFile = path.join(triggersFolder, 'triggers.json'); 39 | log.info(`Writing ${triggerFile}`); 40 | fs.writeFileSync(triggerFile, JSON.stringify(triggers, null, 2)); 41 | } 42 | 43 | const triggersHandler: DirectoryHandler = { 44 | parse, 45 | dump, 46 | }; 47 | 48 | export default triggersHandler; 49 | -------------------------------------------------------------------------------- /src/context/yaml/handlers/attackProtection.ts: -------------------------------------------------------------------------------- 1 | import { YAMLHandler } from '.'; 2 | import YAMLContext from '..'; 3 | import { Asset, ParsedAsset } from '../../../types'; 4 | 5 | type ParsedAttackProtection = ParsedAsset< 6 | 'attackProtection', 7 | { 8 | breachedPasswordDetection: Asset; 9 | bruteForceProtection: Asset; 10 | suspiciousIpThrottling: Asset; 11 | } 12 | >; 13 | 14 | async function parseAndDump(context: YAMLContext): Promise { 15 | const { attackProtection } = context.assets; 16 | 17 | if (!attackProtection) return { attackProtection: null }; 18 | 19 | const { suspiciousIpThrottling, breachedPasswordDetection, bruteForceProtection } = 20 | attackProtection; 21 | 22 | return { 23 | attackProtection: { 24 | suspiciousIpThrottling, 25 | breachedPasswordDetection, 26 | bruteForceProtection, 27 | }, 28 | }; 29 | } 30 | 31 | const attackProtectionHandler: YAMLHandler = { 32 | parse: parseAndDump, 33 | dump: parseAndDump, 34 | }; 35 | 36 | export default attackProtectionHandler; 37 | -------------------------------------------------------------------------------- /src/context/yaml/handlers/clientGrants.ts: -------------------------------------------------------------------------------- 1 | import { Client } from 'auth0'; 2 | import { convertClientIdToName } from '../../../utils'; 3 | import { YAMLHandler } from '.'; 4 | import YAMLContext from '..'; 5 | import { ParsedAsset } from '../../../types'; 6 | import { ClientGrant } from '../../../tools/auth0/handlers/clientGrants'; 7 | import { paginate } from '../../../tools/auth0/client'; 8 | 9 | type ParsedClientGrants = ParsedAsset<'clientGrants', ClientGrant[]>; 10 | 11 | async function parse(context: YAMLContext): Promise { 12 | const { clientGrants } = context.assets; 13 | 14 | if (!clientGrants) return { clientGrants: null }; 15 | 16 | return { 17 | clientGrants, 18 | }; 19 | } 20 | 21 | async function dump(context: YAMLContext): Promise { 22 | let { clients } = context.assets; 23 | const { clientGrants } = context.assets; 24 | 25 | if (!clientGrants) return { clientGrants: null }; 26 | 27 | if (clients === undefined) { 28 | clients = await paginate(context.mgmtClient.clients.getAll, { 29 | paginate: true, 30 | include_totals: true, 31 | }); 32 | } 33 | 34 | // Convert client_id to the client name for readability 35 | return { 36 | clientGrants: clientGrants.map((grant) => { 37 | const dumpGrant = { ...grant }; 38 | dumpGrant.client_id = convertClientIdToName(dumpGrant.client_id, clients || []); 39 | return dumpGrant; 40 | }), 41 | }; 42 | } 43 | 44 | const clientGrantsHandler: YAMLHandler = { 45 | parse, 46 | dump, 47 | }; 48 | 49 | export default clientGrantsHandler; 50 | -------------------------------------------------------------------------------- /src/context/yaml/handlers/clients.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs-extra'; 3 | import { constants } from '../../../tools'; 4 | import log from '../../../logger'; 5 | import { isFile, sanitize, clearClientArrays } from '../../../utils'; 6 | import { YAMLHandler } from '.'; 7 | import YAMLContext from '..'; 8 | import { ParsedAsset } from '../../../types'; 9 | import { Client } from '../../../tools/auth0/handlers/clients'; 10 | 11 | type ParsedClients = ParsedAsset<'clients', Client[]>; 12 | 13 | async function parse(context: YAMLContext): Promise { 14 | // Load the HTML file for custom_login_page 15 | 16 | const { clients } = context.assets; 17 | const clientsFolder = path.join(context.basePath, constants.CLIENTS_DIRECTORY); 18 | 19 | if (!clients) { 20 | return { clients: null }; 21 | } 22 | 23 | return { 24 | clients: [ 25 | ...clients.map((client) => { 26 | if (client.custom_login_page) { 27 | const htmlFileName = path.join(clientsFolder, client.custom_login_page); 28 | 29 | if (isFile(htmlFileName)) { 30 | client.custom_login_page = context.loadFile(htmlFileName); 31 | } 32 | } 33 | 34 | return client; 35 | }), 36 | ], 37 | }; 38 | } 39 | 40 | async function dump(context: YAMLContext): Promise { 41 | // Save custom_login_page to a separate html file 42 | const clientsFolder = path.join(context.basePath, constants.CLIENTS_DIRECTORY); 43 | 44 | const { clients } = context.assets; 45 | if (!clients) return { clients: null }; 46 | 47 | return { 48 | clients: [ 49 | ...clients.map((client) => { 50 | if (client.custom_login_page) { 51 | const clientName = sanitize(client.name); 52 | const html = client.custom_login_page; 53 | const customLoginHtml = path.join(clientsFolder, `${clientName}_custom_login_page.html`); 54 | 55 | log.info(`Writing ${customLoginHtml}`); 56 | fs.ensureDirSync(clientsFolder); 57 | fs.writeFileSync(customLoginHtml, html); 58 | 59 | client.custom_login_page = `./${clientName}_custom_login_page.html`; 60 | } 61 | 62 | return clearClientArrays(client) as Client; 63 | }), 64 | ], 65 | }; 66 | } 67 | 68 | const clientsHandler: YAMLHandler = { 69 | parse, 70 | dump, 71 | }; 72 | 73 | export default clientsHandler; 74 | -------------------------------------------------------------------------------- /src/context/yaml/handlers/customDomains.ts: -------------------------------------------------------------------------------- 1 | import { YAMLHandler } from '.'; 2 | import YAMLContext from '..'; 3 | import { Asset, ParsedAsset } from '../../../types'; 4 | 5 | type ParsedCustomDomains = ParsedAsset<'customDomains', Asset[]>; 6 | 7 | async function parseAndDump(context: YAMLContext): Promise { 8 | const { customDomains } = context.assets; 9 | 10 | if (!customDomains) return { customDomains: null }; 11 | 12 | return { 13 | customDomains, 14 | }; 15 | } 16 | 17 | const customDomainsHandler: YAMLHandler = { 18 | parse: parseAndDump, 19 | dump: parseAndDump, 20 | }; 21 | 22 | export default customDomainsHandler; 23 | -------------------------------------------------------------------------------- /src/context/yaml/handlers/emailProvider.ts: -------------------------------------------------------------------------------- 1 | import { emailProviderDefaults } from '../../defaults'; 2 | import { YAMLHandler } from '.'; 3 | import YAMLContext from '..'; 4 | import { Asset, ParsedAsset } from '../../../types'; 5 | 6 | type ParsedEmailProvider = ParsedAsset<'emailProvider', Asset>; 7 | 8 | async function parse(context: YAMLContext): Promise { 9 | const { emailProvider } = context.assets; 10 | 11 | if (!emailProvider) return { emailProvider: null }; 12 | 13 | return { 14 | emailProvider, 15 | }; 16 | } 17 | 18 | async function dump(context: YAMLContext): Promise { 19 | if (!context.assets.emailProvider) return { emailProvider: null }; 20 | 21 | const emailProvider = (() => { 22 | const { emailProvider } = context.assets; 23 | const excludedDefaults = context.assets.exclude?.defaults || []; 24 | if (emailProvider && !excludedDefaults.includes('emailProvider')) { 25 | // Add placeholder for credentials as they cannot be exported 26 | return emailProviderDefaults(emailProvider); 27 | } 28 | return emailProvider; 29 | })(); 30 | 31 | return { 32 | emailProvider, 33 | }; 34 | } 35 | 36 | const emailProviderHandler: YAMLHandler = { 37 | parse, 38 | dump, 39 | }; 40 | 41 | export default emailProviderHandler; 42 | -------------------------------------------------------------------------------- /src/context/yaml/handlers/emailTemplates.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs-extra'; 3 | import log from '../../../logger'; 4 | import { YAMLHandler } from '.'; 5 | import YAMLContext from '..'; 6 | import { Asset, ParsedAsset } from '../../../types'; 7 | 8 | type ParsedEmailTemplates = ParsedAsset<'emailTemplates', Asset[]>; 9 | 10 | async function parse(context: YAMLContext): Promise { 11 | // Load the HTML file for each page 12 | const { emailTemplates } = context.assets; 13 | 14 | if (!emailTemplates) return { emailTemplates: null }; 15 | 16 | return { 17 | emailTemplates: [ 18 | ...emailTemplates.map((et) => ({ 19 | ...et, 20 | body: context.loadFile(et.body), 21 | })), 22 | ], 23 | }; 24 | } 25 | 26 | async function dump(context: YAMLContext): Promise { 27 | let emailTemplates = context.assets.emailTemplates; 28 | 29 | if (!emailTemplates) { 30 | return { emailTemplates: null }; 31 | } 32 | 33 | // Create Templates folder 34 | const templatesFolder = path.join(context.basePath, 'emailTemplates'); 35 | fs.ensureDirSync(templatesFolder); 36 | emailTemplates = emailTemplates.map((template) => { 37 | // Dump template to file 38 | const templateFile = path.join(templatesFolder, `${template.template}.html`); 39 | log.info(`Writing ${templateFile}`); 40 | fs.writeFileSync(templateFile, template.body); 41 | return { ...template, body: `./emailTemplates/${template.template}.html` }; 42 | }); 43 | 44 | return { emailTemplates }; 45 | } 46 | 47 | const emailTemplatesHandler: YAMLHandler = { 48 | parse, 49 | dump, 50 | }; 51 | 52 | export default emailTemplatesHandler; 53 | -------------------------------------------------------------------------------- /src/context/yaml/handlers/flowVaultConnections.ts: -------------------------------------------------------------------------------- 1 | import { YAMLHandler } from '.'; 2 | import YAMLContext from '..'; 3 | import { ParsedAsset } from '../../../types'; 4 | import { FlowVaultConnection } from '../../../tools/auth0/handlers/flowVaultConnections'; 5 | import log from '../../../logger'; 6 | 7 | type ParsedParsedFlowVaults = ParsedAsset<'flowVaultConnections', FlowVaultConnection[]>; 8 | 9 | async function dump(context: YAMLContext): Promise { 10 | const { flowVaultConnections } = context.assets; 11 | 12 | if (!flowVaultConnections) return { flowVaultConnections: null }; 13 | 14 | // Check if there is any duplicate form name 15 | const vaultConnectionsNames = new Set(); 16 | const duplicateVaultConnectionsNames = new Set(); 17 | 18 | flowVaultConnections.forEach((form) => { 19 | if (vaultConnectionsNames.has(form.name)) { 20 | duplicateVaultConnectionsNames.add(form.name); 21 | } else { 22 | vaultConnectionsNames.add(form.name); 23 | } 24 | }); 25 | 26 | if (duplicateVaultConnectionsNames.size > 0) { 27 | const duplicatesArray = Array.from(duplicateVaultConnectionsNames).join(', '); 28 | log.error( 29 | `Duplicate flow vault connections names found: [${duplicatesArray}] , make sure to rename them to avoid conflicts` 30 | ); 31 | throw new Error(`Duplicate flow vault connections names found: ${duplicatesArray}`); 32 | } 33 | 34 | const removeKeysFromOutput = ['id', 'created_at', 'updated_at', 'refreshed_at', 'fingerprint']; 35 | flowVaultConnections.forEach((connection) => { 36 | removeKeysFromOutput.forEach((key) => { 37 | if (key in connection) { 38 | delete connection[key]; 39 | } 40 | }); 41 | }); 42 | 43 | console.warn( 44 | 'WARNING! Flow vault connections `setup` key does not support keyword preservation, `export` or `dump` commmand will not preserve `setup` key in local configuration file.' 45 | ); 46 | 47 | return { 48 | flowVaultConnections, 49 | }; 50 | } 51 | 52 | async function parse(context: YAMLContext): Promise { 53 | const { flowVaultConnections } = context.assets; 54 | 55 | if (!flowVaultConnections) return { flowVaultConnections: null }; 56 | 57 | return { 58 | flowVaultConnections, 59 | }; 60 | } 61 | 62 | const pagesHandler: YAMLHandler = { 63 | parse, 64 | dump, 65 | }; 66 | 67 | export default pagesHandler; 68 | -------------------------------------------------------------------------------- /src/context/yaml/handlers/guardianFactorProviders.ts: -------------------------------------------------------------------------------- 1 | import { YAMLHandler } from '.'; 2 | import YAMLContext from '..'; 3 | import { Asset, ParsedAsset } from '../../../types'; 4 | 5 | type ParsedGuardianFactorProviders = ParsedAsset<'guardianFactorProviders', Asset[]>; 6 | 7 | async function parseAndDump(context: YAMLContext): Promise { 8 | const { guardianFactorProviders } = context.assets; 9 | 10 | if (!guardianFactorProviders) return { guardianFactorProviders: null }; 11 | 12 | return { 13 | guardianFactorProviders, 14 | }; 15 | } 16 | 17 | const guardianFactorProvidersHandler: YAMLHandler = { 18 | parse: parseAndDump, 19 | dump: parseAndDump, 20 | }; 21 | 22 | export default guardianFactorProvidersHandler; 23 | -------------------------------------------------------------------------------- /src/context/yaml/handlers/guardianFactorTemplates.ts: -------------------------------------------------------------------------------- 1 | import { YAMLHandler } from '.'; 2 | import YAMLContext from '..'; 3 | import { Asset, ParsedAsset } from '../../../types'; 4 | 5 | type ParsedGuardianFactorTemplates = ParsedAsset<'guardianFactorTemplates', Asset[]>; 6 | 7 | async function parseAndDump(context: YAMLContext): Promise { 8 | const { guardianFactorTemplates } = context.assets; 9 | 10 | if (!guardianFactorTemplates) return { guardianFactorTemplates: null }; 11 | 12 | return { 13 | guardianFactorTemplates, 14 | }; 15 | } 16 | 17 | const guardianFactorTemplatesHandler: YAMLHandler = { 18 | parse: parseAndDump, 19 | dump: parseAndDump, 20 | }; 21 | 22 | export default guardianFactorTemplatesHandler; 23 | -------------------------------------------------------------------------------- /src/context/yaml/handlers/guardianFactors.ts: -------------------------------------------------------------------------------- 1 | import { YAMLHandler } from '.'; 2 | import YAMLContext from '..'; 3 | import { Asset, ParsedAsset } from '../../../types'; 4 | 5 | type ParsedGuardianFactors = ParsedAsset<'guardianFactors', Asset[]>; 6 | 7 | async function parseAndDump(context: YAMLContext): Promise { 8 | const { guardianFactors } = context.assets; 9 | 10 | if (!guardianFactors) return { guardianFactors: null }; 11 | 12 | return { 13 | guardianFactors, 14 | }; 15 | } 16 | 17 | const guardianFactorsHandler: YAMLHandler = { 18 | parse: parseAndDump, 19 | dump: parseAndDump, 20 | }; 21 | 22 | export default guardianFactorsHandler; 23 | -------------------------------------------------------------------------------- /src/context/yaml/handlers/guardianPhoneFactorMessageTypes.ts: -------------------------------------------------------------------------------- 1 | import { YAMLHandler } from '.'; 2 | import YAMLContext from '..'; 3 | import { Asset, ParsedAsset } from '../../../types'; 4 | 5 | type ParsedGuardianFactorMessageTypes = ParsedAsset<'guardianPhoneFactorMessageTypes', Asset>; 6 | 7 | async function parseAndDump(context: YAMLContext): Promise { 8 | const { guardianPhoneFactorMessageTypes } = context.assets; 9 | 10 | if (!guardianPhoneFactorMessageTypes) return { guardianPhoneFactorMessageTypes: null }; 11 | 12 | return { 13 | guardianPhoneFactorMessageTypes, 14 | }; 15 | } 16 | 17 | const guardianPhoneFactorMessageTypesHandler: YAMLHandler = { 18 | parse: parseAndDump, 19 | dump: parseAndDump, 20 | }; 21 | 22 | export default guardianPhoneFactorMessageTypesHandler; 23 | -------------------------------------------------------------------------------- /src/context/yaml/handlers/guardianPhoneFactorSelectedProvider.ts: -------------------------------------------------------------------------------- 1 | import { YAMLHandler } from '.'; 2 | import YAMLContext from '..'; 3 | import { Asset, ParsedAsset } from '../../../types'; 4 | 5 | type ParsedGuardianPhoneFactorSelectedProvider = ParsedAsset< 6 | 'guardianPhoneFactorSelectedProvider', 7 | Asset 8 | >; 9 | 10 | async function parseAndDump( 11 | context: YAMLContext 12 | ): Promise { 13 | const { guardianPhoneFactorSelectedProvider } = context.assets; 14 | 15 | if (!guardianPhoneFactorSelectedProvider) return { guardianPhoneFactorSelectedProvider: null }; 16 | 17 | return { 18 | guardianPhoneFactorSelectedProvider, 19 | }; 20 | } 21 | 22 | const guardianPhoneFactorSelectedProviderHandler: YAMLHandler = 23 | { 24 | parse: parseAndDump, 25 | dump: parseAndDump, 26 | }; 27 | 28 | export default guardianPhoneFactorSelectedProviderHandler; 29 | -------------------------------------------------------------------------------- /src/context/yaml/handlers/guardianPolicies.ts: -------------------------------------------------------------------------------- 1 | import { YAMLHandler } from '.'; 2 | import YAMLContext from '..'; 3 | import { ParsedAsset } from '../../../types'; 4 | 5 | type ParsedGuardianPolicies = ParsedAsset<'guardianPolicies', { policies: string[] }>; 6 | 7 | async function parseAndDump(context: YAMLContext): Promise { 8 | const { guardianPolicies } = context.assets; 9 | 10 | if (!guardianPolicies) return { guardianPolicies: null }; 11 | 12 | return { 13 | guardianPolicies, 14 | }; 15 | } 16 | 17 | const guardianPoliciesHandler: YAMLHandler = { 18 | parse: parseAndDump, 19 | dump: parseAndDump, 20 | }; 21 | 22 | export default guardianPoliciesHandler; 23 | -------------------------------------------------------------------------------- /src/context/yaml/handlers/hooks.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs-extra'; 3 | import { constants } from '../../../tools'; 4 | 5 | import { sanitize } from '../../../utils'; 6 | import log from '../../../logger'; 7 | 8 | import { YAMLHandler } from '.'; 9 | import YAMLContext from '..'; 10 | import { Asset, ParsedAsset } from '../../../types'; 11 | 12 | type ParsedHooks = ParsedAsset<'hooks', Asset[]>; 13 | 14 | async function parse(context: YAMLContext): Promise { 15 | const { hooks } = context.assets; 16 | if (!hooks) return { hooks: null }; 17 | 18 | return { 19 | hooks: [ 20 | ...hooks.map((hook) => { 21 | if (hook.script) { 22 | //@ts-ignore TODO: understand why two arguments are passed when context.loadFile only accepts one 23 | hook.script = context.loadFile(hook.script, constants.HOOKS_DIRECTORY); 24 | } 25 | 26 | hook.name = hook.name.toLowerCase().replace(/\s/g, '-'); 27 | 28 | return { ...hook }; 29 | }), 30 | ], 31 | }; 32 | } 33 | 34 | async function dump(context: YAMLContext): Promise { 35 | let hooks = context.assets.hooks; 36 | 37 | if (!hooks) { 38 | return { hooks: null }; 39 | } 40 | 41 | // Create hooks folder 42 | const hooksFolder = path.join(context.basePath, 'hooks'); 43 | fs.ensureDirSync(hooksFolder); 44 | 45 | hooks = hooks.map((hook) => { 46 | // Dump hook code to file 47 | // For cases when hook does not have `meta['hook-name']` 48 | hook.name = hook.name || hook.id; 49 | const codeName = sanitize(`${hook.name}.js`); 50 | const codeFile = path.join(hooksFolder, codeName); 51 | log.info(`Writing ${codeFile}`); 52 | fs.writeFileSync(codeFile, hook.script); 53 | 54 | return { ...hook, script: `./hooks/${codeName}` }; 55 | }); 56 | 57 | return { hooks }; 58 | } 59 | 60 | const hooksHandler: YAMLHandler = { 61 | parse, 62 | dump, 63 | }; 64 | 65 | export default hooksHandler; 66 | -------------------------------------------------------------------------------- /src/context/yaml/handlers/logStreams.ts: -------------------------------------------------------------------------------- 1 | import { YAMLHandler } from '.'; 2 | import YAMLContext from '..'; 3 | import { Asset, ParsedAsset } from '../../../types'; 4 | import { logStreamDefaults } from '../../defaults'; 5 | 6 | type ParsedLogStreams = ParsedAsset<'logStreams', Asset[]>; 7 | 8 | async function parse(context: YAMLContext): Promise { 9 | const { logStreams } = context.assets; 10 | 11 | if (!logStreams) return { logStreams: null }; 12 | 13 | return { 14 | logStreams, 15 | }; 16 | } 17 | 18 | async function dump(context: YAMLContext): Promise { 19 | const { logStreams } = context.assets; 20 | 21 | if (!logStreams) return { logStreams: null }; 22 | 23 | // masked sensitive fields 24 | const maskedLogStreams = logStreamDefaults(logStreams); 25 | 26 | return { 27 | logStreams: maskedLogStreams, 28 | }; 29 | } 30 | 31 | const logStreamsHandler: YAMLHandler = { 32 | parse: parse, 33 | dump: dump, 34 | }; 35 | 36 | export default logStreamsHandler; 37 | -------------------------------------------------------------------------------- /src/context/yaml/handlers/networkACLs.ts: -------------------------------------------------------------------------------- 1 | import { YAMLHandler } from '.'; 2 | import YAMLContext from '..'; 3 | import { ParsedAsset } from '../../../types'; 4 | import { NetworkACL } from '../../../tools/auth0/handlers/networkACLs'; 5 | import log from '../../../logger'; 6 | 7 | type ParsedNetworkACLs = ParsedAsset<'networkACLs', NetworkACL[]>; 8 | 9 | async function parse(context: YAMLContext): Promise { 10 | const { networkACLs } = context.assets; 11 | 12 | if (!networkACLs) return { networkACLs: null }; 13 | 14 | return { 15 | networkACLs, 16 | }; 17 | } 18 | 19 | async function dump(context: YAMLContext): Promise { 20 | let { networkACLs } = context.assets; 21 | 22 | if (!networkACLs) return { networkACLs: null }; 23 | 24 | if (Array.isArray(networkACLs) && networkACLs.length === 0) { 25 | log.info('No network ACLs available, skipping dump'); 26 | return { networkACLs: null }; 27 | } 28 | 29 | const removeKeysFromOutput = ['created_at', 'updated_at']; 30 | 31 | networkACLs = networkACLs.map((networkACL) => { 32 | removeKeysFromOutput.forEach((key) => { 33 | if (key in networkACL) { 34 | delete networkACL[key]; 35 | } 36 | }); 37 | 38 | return networkACL; 39 | }); 40 | 41 | return { 42 | networkACLs, 43 | }; 44 | } 45 | 46 | const networkACLsHandler: YAMLHandler = { 47 | parse, 48 | dump, 49 | }; 50 | 51 | export default networkACLsHandler; 52 | -------------------------------------------------------------------------------- /src/context/yaml/handlers/organizations.ts: -------------------------------------------------------------------------------- 1 | import { YAMLHandler } from '.'; 2 | import YAMLContext from '..'; 3 | import { Asset, ParsedAsset } from '../../../types'; 4 | 5 | type ParsedOrganizations = { 6 | organizations: Asset[] | null; 7 | }; 8 | 9 | async function parse(context: YAMLContext): Promise { 10 | const { organizations } = context.assets; 11 | 12 | if (!organizations) return { organizations: null }; 13 | 14 | return { 15 | organizations, 16 | }; 17 | } 18 | 19 | async function dump(context: YAMLContext): Promise { 20 | const { organizations } = context.assets; 21 | 22 | if (!organizations) return { organizations: null }; 23 | 24 | return { 25 | organizations: organizations.map((org) => { 26 | if (org.connections.length > 0) { 27 | org.connections = org.connections.map((c) => { 28 | // connection is a computed field 29 | const name = c.connection && c.connection.name; 30 | delete c.connection_id; 31 | delete c.connection; 32 | 33 | return { 34 | name, 35 | ...c, 36 | }; 37 | }); 38 | } 39 | 40 | return org; 41 | }), 42 | }; 43 | } 44 | 45 | const organizationsHandler: YAMLHandler = { 46 | parse, 47 | dump, 48 | }; 49 | 50 | export default organizationsHandler; 51 | -------------------------------------------------------------------------------- /src/context/yaml/handlers/pages.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs-extra'; 3 | 4 | import log from '../../../logger'; 5 | import { YAMLHandler } from '.'; 6 | import YAMLContext from '..'; 7 | import { ParsedAsset } from '../../../types'; 8 | import { Page } from '../../../tools/auth0/handlers/pages'; 9 | 10 | type ParsedPages = ParsedAsset<'pages', Page[]>; 11 | 12 | async function parse(context: YAMLContext): Promise { 13 | // Load the HTML file for each page 14 | const { pages } = context.assets; 15 | 16 | if (!pages) return { pages: null }; 17 | 18 | return { 19 | pages: [ 20 | ...pages.map((page) => ({ 21 | ...page, 22 | html: page.html ? context.loadFile(page.html) : '', 23 | })), 24 | ], 25 | }; 26 | } 27 | 28 | async function dump(context: YAMLContext): Promise { 29 | let pages = context.assets.pages; 30 | 31 | if (!pages) { 32 | return { pages: null }; 33 | } 34 | 35 | const pagesFolder = path.join(context.basePath, 'pages'); 36 | fs.ensureDirSync(pagesFolder); 37 | 38 | pages = pages.map((page) => { 39 | if (page.html === undefined) { 40 | return page; 41 | } 42 | 43 | const htmlFile = path.join(pagesFolder, `${page.name}.html`); 44 | log.info(`Writing ${htmlFile}`); 45 | fs.writeFileSync(htmlFile, page.html); 46 | return { 47 | ...page, 48 | html: `./pages/${page.name}.html`, 49 | }; 50 | }); 51 | 52 | return { pages }; 53 | } 54 | 55 | const pagesHandler: YAMLHandler = { 56 | parse, 57 | dump, 58 | }; 59 | 60 | export default pagesHandler; 61 | -------------------------------------------------------------------------------- /src/context/yaml/handlers/phoneProvider.ts: -------------------------------------------------------------------------------- 1 | import { YAMLHandler } from '.'; 2 | import YAMLContext from '..'; 3 | import { PhoneProvider } from '../../../tools/auth0/handlers/phoneProvider'; 4 | import { ParsedAsset } from '../../../types'; 5 | import { phoneProviderDefaults } from '../../defaults'; 6 | 7 | type ParsedPhoneProviders = ParsedAsset<'phoneProviders', PhoneProvider[]>; 8 | 9 | async function parse(context: YAMLContext): Promise { 10 | const { phoneProviders } = context.assets; 11 | 12 | if (!phoneProviders) return { phoneProviders: null }; 13 | 14 | return { 15 | phoneProviders, 16 | }; 17 | } 18 | 19 | async function dump(context: YAMLContext): Promise { 20 | if (!context.assets.phoneProviders) return { phoneProviders: null }; 21 | 22 | let { phoneProviders } = context.assets; 23 | 24 | phoneProviders = phoneProviders.map((provider) => { 25 | provider = phoneProviderDefaults(provider); 26 | return provider; 27 | }); 28 | 29 | return { 30 | phoneProviders, 31 | }; 32 | } 33 | 34 | const phoneProviderHandler: YAMLHandler = { 35 | parse, 36 | dump, 37 | }; 38 | 39 | export default phoneProviderHandler; 40 | -------------------------------------------------------------------------------- /src/context/yaml/handlers/resourceServers.ts: -------------------------------------------------------------------------------- 1 | import { ResourceServer } from 'auth0'; 2 | import { YAMLHandler } from '.'; 3 | import YAMLContext from '..'; 4 | import { ParsedAsset } from '../../../types'; 5 | 6 | type ParsedResourceServers = ParsedAsset<'resourceServers', ResourceServer[]>; 7 | 8 | async function dumpAndParse(context: YAMLContext): Promise { 9 | const { resourceServers } = context.assets; 10 | 11 | if (!resourceServers) { 12 | return { resourceServers: null }; 13 | } 14 | return { 15 | resourceServers, 16 | }; 17 | } 18 | 19 | const resourceServersHandler: YAMLHandler = { 20 | parse: dumpAndParse, 21 | dump: dumpAndParse, 22 | }; 23 | 24 | export default resourceServersHandler; 25 | -------------------------------------------------------------------------------- /src/context/yaml/handlers/roles.ts: -------------------------------------------------------------------------------- 1 | import { YAMLHandler } from '.'; 2 | import YAMLContext from '..'; 3 | import { Asset, ParsedAsset } from '../../../types'; 4 | 5 | type ParsedRoles = ParsedAsset<'roles', Asset[]>; 6 | 7 | async function parse(context: YAMLContext): Promise { 8 | const { roles } = context.assets; 9 | 10 | if (!roles) return { roles: null }; 11 | 12 | return { 13 | roles, 14 | }; 15 | } 16 | 17 | async function dump(context: YAMLContext): Promise { 18 | const { roles } = context.assets; 19 | 20 | if (!roles) return { roles: null }; 21 | 22 | return { 23 | roles: roles.map((role) => { 24 | if (role.description === null) { 25 | delete role.description; 26 | } 27 | 28 | return role; 29 | }), 30 | }; 31 | } 32 | 33 | const rolesHandler: YAMLHandler = { 34 | parse, 35 | dump, 36 | }; 37 | 38 | export default rolesHandler; 39 | -------------------------------------------------------------------------------- /src/context/yaml/handlers/rules.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs-extra'; 3 | 4 | import { sanitize } from '../../../utils'; 5 | import log from '../../../logger'; 6 | 7 | import { YAMLHandler } from '.'; 8 | import YAMLContext from '..'; 9 | import { Asset, ParsedAsset } from '../../../types'; 10 | 11 | type ParsedRules = ParsedAsset<'rules', Asset[]>; 12 | 13 | async function parse(context: YAMLContext): Promise { 14 | const { rules } = context.assets; 15 | 16 | if (!rules) return { rules: null }; 17 | 18 | return { 19 | rules: [ 20 | ...rules.map((rule) => ({ 21 | ...rule, 22 | script: context.loadFile(rule.script), 23 | })), 24 | ], 25 | }; 26 | } 27 | 28 | async function dump(context: YAMLContext): Promise { 29 | let { rules } = context.assets; 30 | 31 | if (!rules) { 32 | return { rules: null }; 33 | } 34 | 35 | // Create Rules folder 36 | const rulesFolder = path.join(context.basePath, 'rules'); 37 | fs.ensureDirSync(rulesFolder); 38 | 39 | rules = rules.map((rule) => { 40 | // Dump rule to file 41 | const scriptName = sanitize(`${rule.name}.js`); 42 | const scriptFile = path.join(rulesFolder, scriptName); 43 | log.info(`Writing ${scriptFile}`); 44 | fs.writeFileSync(scriptFile, rule.script); 45 | return { ...rule, script: `./rules/${scriptName}` }; 46 | }); 47 | 48 | return { rules }; 49 | } 50 | 51 | const rulesHandler: YAMLHandler = { 52 | parse, 53 | dump, 54 | }; 55 | 56 | export default rulesHandler; 57 | -------------------------------------------------------------------------------- /src/context/yaml/handlers/rulesConfigs.ts: -------------------------------------------------------------------------------- 1 | import { YAMLHandler } from '.'; 2 | import YAMLContext from '..'; 3 | import { Asset, ParsedAsset } from '../../../types'; 4 | 5 | type ParsedRulesConfigs = ParsedAsset<'rulesConfigs', Asset[]>; 6 | 7 | async function parse(context: YAMLContext): Promise { 8 | const { rulesConfigs } = context.assets; 9 | 10 | if (!rulesConfigs) return { rulesConfigs: null }; 11 | 12 | return { 13 | rulesConfigs, 14 | }; 15 | } 16 | 17 | async function dump(context: YAMLContext): Promise { 18 | const { rulesConfigs } = context.assets; 19 | 20 | if (!rulesConfigs) return { rulesConfigs: null }; 21 | 22 | return { 23 | rulesConfigs: [], // even if they exist, do not export rulesConfigs as its values cannot be extracted 24 | }; 25 | } 26 | 27 | const rulesConfigsHandler: YAMLHandler = { 28 | parse, 29 | dump, 30 | }; 31 | 32 | export default rulesConfigsHandler; 33 | -------------------------------------------------------------------------------- /src/context/yaml/handlers/selfServiceProfiles.ts: -------------------------------------------------------------------------------- 1 | import { YAMLHandler } from '.'; 2 | import YAMLContext from '..'; 3 | import { ParsedAsset } from '../../../types'; 4 | import { SsProfileWithCustomText } from '../../../tools/auth0/handlers/selfServiceProfiles'; 5 | 6 | type ParsedSelfServiceProfiles = ParsedAsset< 7 | 'selfServiceProfiles', 8 | Partial[] 9 | >; 10 | 11 | async function parse(context: YAMLContext): Promise { 12 | const { selfServiceProfiles } = context.assets; 13 | 14 | if (!selfServiceProfiles) return { selfServiceProfiles: null }; 15 | 16 | return { 17 | selfServiceProfiles, 18 | }; 19 | } 20 | 21 | async function dump(context: YAMLContext): Promise { 22 | let { selfServiceProfiles } = context.assets; 23 | if (!selfServiceProfiles) return { selfServiceProfiles: null }; 24 | 25 | selfServiceProfiles = selfServiceProfiles.map((profile) => { 26 | if ('created_at' in profile) { 27 | delete profile.created_at; 28 | } 29 | 30 | if ('updated_at' in profile) { 31 | delete profile.updated_at; 32 | } 33 | 34 | return { 35 | ...profile, 36 | }; 37 | }); 38 | 39 | return { 40 | selfServiceProfiles, 41 | }; 42 | } 43 | 44 | const selfServiceProfileHandler: YAMLHandler = { 45 | parse, 46 | dump, 47 | }; 48 | 49 | export default selfServiceProfileHandler; 50 | -------------------------------------------------------------------------------- /src/context/yaml/handlers/tenant.ts: -------------------------------------------------------------------------------- 1 | import { clearTenantFlags } from '../../../utils'; 2 | import { sessionDurationsToMinutes } from '../../../sessionDurationsToMinutes'; 3 | import { YAMLHandler } from '.'; 4 | import YAMLContext from '..'; 5 | import { Asset, ParsedAsset } from '../../../types'; 6 | 7 | type ParsedTenant = ParsedAsset<'tenant', Asset>; 8 | 9 | async function parse(context: YAMLContext): Promise { 10 | if (!context.assets.tenant) return { tenant: null }; 11 | 12 | /* eslint-disable camelcase */ 13 | const { 14 | session_lifetime, 15 | idle_session_lifetime, 16 | ...tenant 17 | }: { 18 | session_lifetime?: number; 19 | idle_session_lifetime?: number; 20 | [key: string]: any; 21 | } = context.assets.tenant; 22 | 23 | clearTenantFlags(tenant); 24 | 25 | const sessionDurations = sessionDurationsToMinutes({ session_lifetime, idle_session_lifetime }); 26 | 27 | return { 28 | tenant: { 29 | ...tenant, 30 | ...sessionDurations, 31 | }, 32 | }; 33 | } 34 | 35 | async function dump(context: YAMLContext): Promise { 36 | const tenant = context.assets.tenant; 37 | 38 | if (!tenant) return { tenant: null }; 39 | 40 | clearTenantFlags(tenant); 41 | 42 | return { tenant }; 43 | } 44 | 45 | const tenantHandler: YAMLHandler = { 46 | parse, 47 | dump, 48 | }; 49 | 50 | export default tenantHandler; 51 | -------------------------------------------------------------------------------- /src/context/yaml/handlers/themes.ts: -------------------------------------------------------------------------------- 1 | import { YAMLHandler } from '.'; 2 | import YAMLContext from '..'; 3 | import { Theme } from '../../../tools/auth0/handlers/themes'; 4 | import { ParsedAsset } from '../../../types'; 5 | 6 | type ParsedThemes = ParsedAsset<'themes', Theme[]>; 7 | 8 | async function parseAndDump(context: YAMLContext): Promise { 9 | const { themes } = context.assets; 10 | 11 | if (!themes) return { themes: null }; 12 | 13 | return { 14 | themes, 15 | }; 16 | } 17 | 18 | const themesHandler: YAMLHandler = { 19 | parse: parseAndDump, 20 | dump: parseAndDump, 21 | }; 22 | 23 | export default themesHandler; 24 | -------------------------------------------------------------------------------- /src/context/yaml/handlers/triggers.ts: -------------------------------------------------------------------------------- 1 | import { YAMLHandler } from '.'; 2 | import YAMLContext from '..'; 3 | import { Asset, ParsedAsset } from '../../../types'; 4 | 5 | type ParsedTriggers = ParsedAsset<'triggers', Asset[]>; 6 | 7 | async function parse(context: YAMLContext): Promise { 8 | // Load the script file for each action 9 | if (!context.assets.triggers) return { triggers: null }; 10 | return { 11 | triggers: context.assets.triggers, 12 | }; 13 | } 14 | 15 | async function dump(context: YAMLContext): Promise { 16 | const { triggers } = context.assets; 17 | // Nothing to do 18 | if (!triggers) return { triggers: null }; 19 | return { 20 | triggers: triggers, 21 | }; 22 | } 23 | 24 | const triggersHandler: YAMLHandler = { 25 | parse, 26 | dump, 27 | }; 28 | 29 | export default triggersHandler; 30 | -------------------------------------------------------------------------------- /src/logger.ts: -------------------------------------------------------------------------------- 1 | import { format, createLogger, transports } from 'winston'; 2 | 3 | const { combine, timestamp, colorize } = format; 4 | 5 | const logger = createLogger({ 6 | level: process.env.AUTH0_LOG || 'info', 7 | format: combine( 8 | colorize(), 9 | timestamp(), 10 | format.printf((info) => `${info.timestamp} - ${info.level}: ${info.message}`) 11 | ), 12 | transports: [new transports.Console()], 13 | exitOnError: false, 14 | }); 15 | 16 | export default logger; 17 | -------------------------------------------------------------------------------- /src/sessionDurationsToMinutes.ts: -------------------------------------------------------------------------------- 1 | function hoursToMinutes(hours: number): number { 2 | return Math.round(hours * 60); 3 | } 4 | 5 | export const sessionDurationsToMinutes = ({ 6 | session_lifetime, 7 | idle_session_lifetime, 8 | }: { 9 | session_lifetime?: number; 10 | idle_session_lifetime?: number; 11 | }): { session_lifetime_in_minutes?: number; idle_session_lifetime_in_minutes?: number } => { 12 | const sessionDurations: { 13 | session_lifetime_in_minutes?: number; 14 | idle_session_lifetime_in_minutes?: number; 15 | } = {}; 16 | 17 | if (!!session_lifetime) 18 | sessionDurations.session_lifetime_in_minutes = hoursToMinutes(session_lifetime); 19 | if (!!idle_session_lifetime) 20 | sessionDurations.idle_session_lifetime_in_minutes = hoursToMinutes(idle_session_lifetime); 21 | 22 | return sessionDurations; 23 | }; 24 | -------------------------------------------------------------------------------- /src/tools/auth0/handlers/emailProvider.ts: -------------------------------------------------------------------------------- 1 | import { EmailProviderCreate } from 'auth0'; 2 | import { isEmpty } from 'lodash'; 3 | import DefaultHandler, { order } from './default'; 4 | import { Asset, Assets } from '../../../types'; 5 | 6 | export const schema = { type: 'object' }; 7 | 8 | // The Management API requires the fields to be specified 9 | const defaultFields = ['name', 'enabled', 'credentials', 'settings', 'default_from_address']; 10 | 11 | export default class EmailProviderHandler extends DefaultHandler { 12 | constructor(options: DefaultHandler) { 13 | super({ 14 | ...options, 15 | type: 'emailProvider', 16 | }); 17 | } 18 | 19 | async getType(): Promise { 20 | try { 21 | const { data } = await this.client.emails.get({ 22 | include_fields: true, 23 | fields: defaultFields.join(','), 24 | }); 25 | return data; 26 | } catch (err) { 27 | if (err.statusCode === 404) return {}; 28 | throw err; 29 | } 30 | } 31 | 32 | objString(provider) { 33 | return super.objString({ name: provider.name, enabled: provider.enabled }); 34 | } 35 | 36 | @order('60') 37 | async processChanges(assets: Assets): Promise { 38 | const { emailProvider } = assets; 39 | 40 | if (!emailProvider) return; 41 | 42 | const existing = await this.getType(); 43 | 44 | // HTTP DELETE on emails/provider is not supported, as this is not part of our vNext SDK. 45 | if (Object.keys(emailProvider).length === 0) { 46 | if (this.config('AUTH0_ALLOW_DELETE') === true) { 47 | // await this.client.emails.delete(); is not supported 48 | if (isEmpty(existing.credentials)) { 49 | delete existing.credentials; 50 | } 51 | const updated = await this.client.emails.update(existing); 52 | this.updated += 1; 53 | this.didUpdate(updated); 54 | } 55 | return; 56 | } 57 | 58 | if (existing.name) { 59 | const updated = await this.client.emails.update(emailProvider); 60 | this.updated += 1; 61 | this.didUpdate(updated); 62 | } else { 63 | const created = await this.client.emails.configure(emailProvider as EmailProviderCreate); 64 | this.created += 1; 65 | this.didCreate(created); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/tools/auth0/handlers/guardianFactors.ts: -------------------------------------------------------------------------------- 1 | import { Factor, FactorNameEnum } from 'auth0'; 2 | import DefaultHandler from './default'; 3 | import constants from '../../constants'; 4 | import { Asset, Assets } from '../../../types'; 5 | import { isForbiddenFeatureError } from '../../utils'; 6 | 7 | export const schema = { 8 | type: 'array', 9 | items: { 10 | type: 'object', 11 | properties: { 12 | name: { type: 'string', enum: constants.GUARDIAN_FACTORS }, 13 | }, 14 | required: ['name'], 15 | }, 16 | }; 17 | 18 | export default class GuardianFactorsHandler extends DefaultHandler { 19 | existing: Asset[]; 20 | 21 | constructor(options: DefaultHandler) { 22 | super({ 23 | ...options, 24 | type: 'guardianFactors', 25 | id: 'name', 26 | }); 27 | } 28 | 29 | async getType(): Promise { 30 | if (this.existing) return this.existing; 31 | try { 32 | const { data } = await this.client.guardian.getFactors(); 33 | this.existing = data; 34 | return this.existing; 35 | } catch (err) { 36 | if (err.statusCode === 404 || err.statusCode === 501) { 37 | return null; 38 | } 39 | if (isForbiddenFeatureError(err, this.type)) { 40 | return null; 41 | } 42 | 43 | throw err; 44 | } 45 | } 46 | 47 | async processChanges(assets: Assets): Promise { 48 | // No API to delete or create guardianFactors, we can only update. 49 | const { guardianFactors } = assets; 50 | 51 | // Do nothing if not set 52 | if (!guardianFactors || !guardianFactors.length) return; 53 | 54 | // Process each factor 55 | await Promise.all( 56 | guardianFactors.map(async (factor: Factor) => { 57 | const data = { ...factor }; 58 | const params = { name: factor.name as FactorNameEnum }; 59 | delete data.name; 60 | await this.client.guardian.updateFactor(params, data); 61 | this.didUpdate(params); 62 | this.updated += 1; 63 | }) 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/tools/auth0/handlers/guardianPolicies.ts: -------------------------------------------------------------------------------- 1 | import DefaultHandler from './default'; 2 | import constants from '../../constants'; 3 | import { Assets } from '../../../types'; 4 | 5 | export const schema = { 6 | type: 'object', 7 | properties: { 8 | policies: { 9 | type: 'array', 10 | items: { 11 | type: 'string', 12 | enum: constants.GUARDIAN_POLICIES, 13 | }, 14 | }, 15 | }, 16 | additionalProperties: false, 17 | }; 18 | 19 | export default class GuardianPoliciesHandler extends DefaultHandler { 20 | existing: { 21 | policies: string[]; 22 | }; 23 | 24 | constructor(options) { 25 | super({ 26 | ...options, 27 | type: 'guardianPolicies', 28 | }); 29 | } 30 | 31 | //TODO: standardize empty object literal with more intentional empty indicator 32 | async getType(): Promise { 33 | // in case client version does not support the operation 34 | if (!this.client.guardian || typeof this.client.guardian.getPolicies !== 'function') { 35 | return {}; 36 | } 37 | 38 | if (this.existing) return this.existing; 39 | const { data: policies } = await this.client.guardian.getPolicies(); 40 | this.existing = { policies }; 41 | return this.existing; 42 | } 43 | 44 | async processChanges(assets: Assets): Promise { 45 | // No API to delete or create guardianPolicies, we can only update. 46 | const { guardianPolicies } = assets; 47 | 48 | // Do nothing if not set 49 | if (!guardianPolicies || !guardianPolicies.policies) return; 50 | 51 | const data = guardianPolicies.policies; 52 | await this.client.guardian.updatePolicies(data); 53 | this.updated += 1; 54 | this.didUpdate(guardianPolicies); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/tools/auth0/handlers/rulesConfigs.ts: -------------------------------------------------------------------------------- 1 | import { Assets, Asset, CalculatedChanges } from '../../../types'; 2 | import DefaultHandler from './default'; 3 | import log from '../../../logger'; 4 | import { isDeprecatedError } from '../../utils'; 5 | 6 | export const schema = { 7 | type: 'array', 8 | items: { 9 | type: 'object', 10 | properties: { 11 | key: { type: 'string', pattern: '^[A-Za-z0-9_-]*$' }, 12 | value: { type: 'string' }, 13 | }, 14 | required: ['key', 'value'], 15 | }, 16 | additionalProperties: false, 17 | }; 18 | 19 | export default class RulesConfigsHandler extends DefaultHandler { 20 | constructor(options: DefaultHandler) { 21 | super({ 22 | ...options, 23 | type: 'rulesConfigs', 24 | id: 'key', 25 | functions: { 26 | update: 'set', // Update or Creation of a ruleConfig is via set not update 27 | }, 28 | }); 29 | } 30 | 31 | async getType(): Promise { 32 | try { 33 | const { data } = await this.client.rulesConfigs.getAll(); 34 | return data; 35 | } catch (err) { 36 | if (isDeprecatedError(err)) return null; 37 | throw err; 38 | } 39 | } 40 | 41 | objString(item): string { 42 | return super.objString({ key: item.key }); 43 | } 44 | 45 | async calcChanges(assets: Assets): Promise { 46 | const { rulesConfigs } = assets; 47 | 48 | // Do nothing if not set 49 | if (!rulesConfigs || !rulesConfigs.length) 50 | return { 51 | del: [], 52 | update: [], 53 | create: [], 54 | conflicts: [], 55 | }; 56 | 57 | log.warn( 58 | 'Rules are deprecated, migrate to using actions instead. See: https://auth0.com/docs/customize/actions/migrate/migrate-from-rules-to-actions for more information.' 59 | ); 60 | 61 | // Intention is to not delete/cleanup old configRules, that needs to be handled manually. 62 | return { 63 | del: [], 64 | update: rulesConfigs, 65 | create: [], 66 | conflicts: [], 67 | }; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/tools/auth0/schema.ts: -------------------------------------------------------------------------------- 1 | import handlers from './handlers'; 2 | 3 | const typesSchema = Object.entries(handlers).reduce( 4 | (map: { [key: string]: Object }, [name, obj]) => { 5 | map[name] = obj.schema; //eslint-disable-line 6 | return map; 7 | }, 8 | {} 9 | ); 10 | 11 | const excludeSchema = Object.entries(handlers).reduce( 12 | (map: { [key: string]: Object }, [name, obj]) => { 13 | if (obj.excludeSchema) { 14 | map[name] = obj.excludeSchema; 15 | } 16 | return map; 17 | }, 18 | {} 19 | ); 20 | 21 | export default { 22 | type: 'object', 23 | $schema: 'http://json-schema.org/draft-07/schema#', 24 | properties: { 25 | ...typesSchema, 26 | exclude: { 27 | type: 'object', 28 | properties: { ...excludeSchema }, 29 | default: {}, 30 | }, 31 | }, 32 | additionalProperties: false, 33 | }; 34 | -------------------------------------------------------------------------------- /src/tools/deploy.ts: -------------------------------------------------------------------------------- 1 | import Auth0 from './auth0'; 2 | import log from '../logger'; 3 | import { ConfigFunction } from '../configFactory'; 4 | import { Assets, Auth0APIClient } from '../types'; 5 | 6 | export default async function deploy( 7 | assets: Assets, 8 | client: Auth0APIClient, 9 | config: ConfigFunction 10 | ) { 11 | // Setup log level 12 | log.level = process.env.AUTH0_DEBUG === 'true' ? 'debug' : 'info'; 13 | 14 | log.info( 15 | `Getting access token for ${ 16 | config('AUTH0_CLIENT_ID') !== undefined ? `${config('AUTH0_CLIENT_ID')}/` : '' 17 | }${config('AUTH0_DOMAIN')}` 18 | ); 19 | 20 | const auth0 = new Auth0(client, assets, config); 21 | 22 | // Validate Assets 23 | await auth0.validate(); 24 | 25 | // Process changes 26 | await auth0.processChanges(); 27 | 28 | return auth0.handlers.reduce((accum, h) => { 29 | accum[h.type] = { 30 | deleted: h.deleted, 31 | created: h.created, 32 | updated: h.updated, 33 | }; 34 | return accum; 35 | }, {}); 36 | } 37 | -------------------------------------------------------------------------------- /src/tools/index.ts: -------------------------------------------------------------------------------- 1 | import constants from './constants'; 2 | import deploy from './deploy'; 3 | import Auth0 from './auth0'; 4 | import { 5 | keywordReplace, 6 | loadFileAndReplaceKeywords, 7 | wrapArrayReplaceMarkersInQuotes, 8 | } from './utils'; 9 | 10 | export default { 11 | constants, 12 | deploy, 13 | keywordReplace, 14 | loadFileAndReplaceKeywords, 15 | wrapArrayReplaceMarkersInQuotes, 16 | Auth0, 17 | }; 18 | 19 | export { 20 | constants, 21 | deploy, 22 | keywordReplace, 23 | loadFileAndReplaceKeywords, 24 | wrapArrayReplaceMarkersInQuotes, 25 | Auth0, 26 | }; 27 | -------------------------------------------------------------------------------- /src/tools/validationError.ts: -------------------------------------------------------------------------------- 1 | function ValidationError(message: string) { 2 | Error.call(this, message); 3 | if (process.env.NODE_ENV !== 'production') { 4 | Error.captureStackTrace(this, this.constructor); 5 | } 6 | this.name = 'ValidationError'; 7 | this.message = message; 8 | this.status = 400; 9 | } 10 | 11 | ValidationError.prototype = Object.create(Error.prototype); 12 | ValidationError.prototype.constructor = ValidationError; 13 | 14 | export default ValidationError; 15 | -------------------------------------------------------------------------------- /test/configFactory.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { configFactory, ConfigFunction } from '../src/configFactory'; 3 | import { Config } from '../src/types'; 4 | 5 | describe('configFactory', () => { 6 | let config: ReturnType; 7 | 8 | beforeEach(() => { 9 | config = configFactory(); 10 | }); 11 | 12 | it('should set and get configuration values', () => { 13 | config.setValue('someKey' as keyof Config, 'someValue'); 14 | expect(config('someKey' as keyof Config)).to.equal('someValue'); 15 | }); 16 | 17 | it('should throw an error if no provider is set and key is not in settings', () => { 18 | expect(() => config('someKey' as keyof Config)).to.throw( 19 | 'A configuration provider has not been set' 20 | ); 21 | }); 22 | 23 | it('should use the provider function to get configuration values', () => { 24 | const providerFunction: ConfigFunction = (key) => { 25 | if ((key as string) === 'someKey') return 'providedValue'; 26 | return null; 27 | }; 28 | config.setProvider(providerFunction); 29 | expect(config('someKey' as keyof Config)).to.equal('providedValue'); 30 | }); 31 | 32 | it('should prioritize settings over provider function', () => { 33 | config.setValue('someKey' as keyof Config, 'someValue'); 34 | const providerFunction: ConfigFunction = (key) => { 35 | if ((key as string) === 'someKey') return 'providedValue'; 36 | return null; 37 | }; 38 | config.setProvider(providerFunction); 39 | expect(config('someKey' as keyof Config)).to.equal('someValue'); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /test/context/directory/guardianFactors.test.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { expect } from 'chai'; 3 | import { constants } from '../../../src/tools'; 4 | 5 | import Context from '../../../src/context/directory'; 6 | import { testDataDir, createDir, mockMgmtClient, cleanThenMkdir } from '../../utils'; 7 | import handler from '../../../src/context/directory/handlers/guardianFactors'; 8 | import { loadJSON } from '../../../src/utils'; 9 | 10 | describe('#directory context guardian factors provider', () => { 11 | it('should process guardianFactors', async () => { 12 | const guardianFactorsTest = { 13 | 'sms.json': `{ 14 | "name": "sms", 15 | "enabled": true 16 | }`, 17 | 'otp.json': `{ 18 | "name": "otp", 19 | "enabled": true 20 | }`, 21 | }; 22 | 23 | const folder = path.join(constants.GUARDIAN_DIRECTORY, constants.GUARDIAN_FACTORS_DIRECTORY); 24 | const repoDir = path.join(testDataDir, 'directory', 'guardianFactors'); 25 | createDir(repoDir, { [folder]: guardianFactorsTest }); 26 | 27 | const config = { AUTH0_INPUT_FILE: repoDir, AUTH0_KEYWORD_REPLACE_MAPPINGS: { env: 'test' } }; 28 | const context = new Context(config, mockMgmtClient()); 29 | await context.loadAssetsFromLocal(); 30 | 31 | expect(context.assets.guardianFactors).to.deep.equal([ 32 | { enabled: true, name: 'otp' }, 33 | { enabled: true, name: 'sms' }, 34 | ]); 35 | }); 36 | 37 | it('should dump guardian factors', async () => { 38 | const dir = path.join(testDataDir, 'directory', 'guardianFactorsDump'); 39 | cleanThenMkdir(dir); 40 | const context = new Context({ AUTH0_INPUT_FILE: dir }, mockMgmtClient()); 41 | 42 | context.assets.guardianFactors = [ 43 | { enabled: true, name: 'otp' }, 44 | { enabled: true, name: 'sms' }, 45 | ]; 46 | 47 | await handler.dump(context); 48 | const factorsFolder = path.join( 49 | dir, 50 | constants.GUARDIAN_DIRECTORY, 51 | constants.GUARDIAN_FACTORS_DIRECTORY 52 | ); 53 | expect(loadJSON(path.join(factorsFolder, 'sms.json'))).to.deep.equal({ 54 | enabled: true, 55 | name: 'sms', 56 | }); 57 | expect(loadJSON(path.join(factorsFolder, 'otp.json'))).to.deep.equal({ 58 | enabled: true, 59 | name: 'otp', 60 | }); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /test/context/directory/guardianPhoneFactorMessageTypes.test.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { expect } from 'chai'; 3 | import { constants } from '../../../src/tools'; 4 | 5 | import Context from '../../../src/context/directory'; 6 | import { testDataDir, createDir, mockMgmtClient, cleanThenMkdir } from '../../utils'; 7 | import handler from '../../../src/context/directory/handlers/guardianPhoneFactorMessageTypes'; 8 | import { loadJSON } from '../../../src/utils'; 9 | 10 | describe('#directory context guardian phone factor message types provider', () => { 11 | it('should process guardianPhoneFactorMessageTypes', async () => { 12 | const guardianPhoneFactorMessageTypesTest = { 13 | 'phoneFactorMessageTypes.json': `{ 14 | "message_types": [ "sms", "voice" ] 15 | }`, 16 | }; 17 | const repoDir = path.join(testDataDir, 'directory', 'guardianPhoneFactorMessageTypes'); 18 | createDir(repoDir, { [constants.GUARDIAN_DIRECTORY]: guardianPhoneFactorMessageTypesTest }); 19 | 20 | const config = { AUTH0_INPUT_FILE: repoDir, AUTH0_KEYWORD_REPLACE_MAPPINGS: { env: 'test' } }; 21 | const context = new Context(config, mockMgmtClient()); 22 | await context.loadAssetsFromLocal(); 23 | 24 | expect(context.assets.guardianPhoneFactorMessageTypes).to.deep.equal({ 25 | message_types: ['sms', 'voice'], 26 | }); 27 | }); 28 | 29 | it('should dump guardian phone factor message types', async () => { 30 | const dir = path.join(testDataDir, 'directory', 'guardianPhoneFactorMessageTypesDump'); 31 | cleanThenMkdir(dir); 32 | const context = new Context({ AUTH0_INPUT_FILE: dir }, mockMgmtClient()); 33 | 34 | context.assets.guardianPhoneFactorMessageTypes = { 35 | message_types: ['sms', 'voice'], 36 | }; 37 | 38 | await handler.dump(context); 39 | const guardianFolder = path.join(dir, constants.GUARDIAN_DIRECTORY); 40 | expect(loadJSON(path.join(guardianFolder, 'phoneFactorMessageTypes.json'))).to.deep.equal({ 41 | message_types: ['sms', 'voice'], 42 | }); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /test/context/directory/guardianPhoneFactorSelectedProvider.test.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { expect } from 'chai'; 3 | import { constants } from '../../../src/tools'; 4 | 5 | import Context from '../../../src/context/directory'; 6 | import { testDataDir, createDir, mockMgmtClient, cleanThenMkdir } from '../../utils'; 7 | import handler from '../../../src/context/directory/handlers/guardianPhoneFactorSelectedProvider'; 8 | import { loadJSON } from '../../../src/utils'; 9 | 10 | describe('#directory context guardian phone factor selected provider', () => { 11 | it('should process guardianPhoneFactorSelectedProvider', async () => { 12 | const guardianPhoneFactorSelectedProviderTest = { 13 | 'phoneFactorSelectedProvider.json': `{ 14 | "provider": "twilio" 15 | }`, 16 | }; 17 | const repoDir = path.join(testDataDir, 'directory', 'guardianPhoneFactorSelectedProvider'); 18 | createDir(repoDir, { [constants.GUARDIAN_DIRECTORY]: guardianPhoneFactorSelectedProviderTest }); 19 | 20 | const config = { AUTH0_INPUT_FILE: repoDir, AUTH0_KEYWORD_REPLACE_MAPPINGS: { env: 'test' } }; 21 | const context = new Context(config, mockMgmtClient()); 22 | await context.loadAssetsFromLocal(); 23 | 24 | expect(context.assets.guardianPhoneFactorSelectedProvider).to.deep.equal({ 25 | provider: 'twilio', 26 | }); 27 | }); 28 | 29 | it('should dump guardian phone factor selected provider', async () => { 30 | const dir = path.join(testDataDir, 'directory', 'guardianPhoneFactorSelectedProviderDump'); 31 | cleanThenMkdir(dir); 32 | const context = new Context({ AUTH0_INPUT_FILE: dir }, mockMgmtClient()); 33 | 34 | context.assets.guardianPhoneFactorSelectedProvider = { 35 | provider: 'twilio', 36 | }; 37 | 38 | await handler.dump(context); 39 | const guardianFolder = path.join(dir, constants.GUARDIAN_DIRECTORY); 40 | expect(loadJSON(path.join(guardianFolder, 'phoneFactorSelectedProvider.json'))).to.deep.equal({ 41 | provider: 'twilio', 42 | }); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /test/context/directory/guardianPolicies.test.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { expect } from 'chai'; 3 | import { constants } from '../../../src/tools'; 4 | 5 | import Context from '../../../src/context/directory'; 6 | import { testDataDir, createDir, mockMgmtClient, cleanThenMkdir } from '../../utils'; 7 | import handler from '../../../src/context/directory/handlers/guardianPolicies'; 8 | import { loadJSON } from '../../../src/utils'; 9 | 10 | describe('#directory context guardian policies provider', () => { 11 | it('should process guardianPolicies', async () => { 12 | const guardianPoliciesTest = { 13 | 'policies.json': `{ 14 | "policies": [ 15 | "all-applications" 16 | ] 17 | }`, 18 | }; 19 | const repoDir = path.join(testDataDir, 'directory', 'guardianPolicies'); 20 | createDir(repoDir, { [constants.GUARDIAN_DIRECTORY]: guardianPoliciesTest }); 21 | 22 | const config = { AUTH0_INPUT_FILE: repoDir, AUTH0_KEYWORD_REPLACE_MAPPINGS: { env: 'test' } }; 23 | const context = new Context(config, mockMgmtClient()); 24 | await context.loadAssetsFromLocal(); 25 | 26 | expect(context.assets.guardianPolicies).to.deep.equal({ 27 | policies: ['all-applications'], 28 | }); 29 | }); 30 | 31 | it('should dump guardian policies', async () => { 32 | const dir = path.join(testDataDir, 'directory', 'guardianPoliciesDump'); 33 | cleanThenMkdir(dir); 34 | const context = new Context({ AUTH0_INPUT_FILE: dir }, mockMgmtClient()); 35 | 36 | context.assets.guardianPolicies = { 37 | policies: ['all-applications'], 38 | }; 39 | 40 | await handler.dump(context); 41 | const guardianFolder = path.join(dir, constants.GUARDIAN_DIRECTORY); 42 | expect(loadJSON(path.join(guardianFolder, 'policies.json'))).to.deep.equal({ 43 | policies: ['all-applications'], 44 | }); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /test/context/yaml/customDomains.test.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs-extra'; 3 | import { expect } from 'chai'; 4 | 5 | import Context from '../../../src/context/yaml'; 6 | import handler from '../../../src/context/yaml/handlers/customDomains'; 7 | import { cleanThenMkdir, testDataDir, mockMgmtClient } from '../../utils'; 8 | 9 | describe('#YAML context custom domains', () => { 10 | it('should process custom domains', async () => { 11 | const dir = path.join(testDataDir, 'yaml', 'custom_domains'); 12 | cleanThenMkdir(dir); 13 | 14 | const yaml = ` 15 | customDomains: 16 | - custom_client_ip_header: cf-connecting-ip 17 | domain: auth.test-domain.com 18 | type: self_managed_certs 19 | `; 20 | 21 | const yamlFile = path.join(dir, 'config.yaml'); 22 | fs.writeFileSync(yamlFile, yaml); 23 | 24 | const config = { AUTH0_INPUT_FILE: yamlFile }; 25 | const context = new Context(config, mockMgmtClient()); 26 | await context.loadAssetsFromLocal(); 27 | 28 | expect(context.assets.customDomains).to.deep.equal([ 29 | { 30 | custom_client_ip_header: 'cf-connecting-ip', 31 | domain: 'auth.test-domain.com', 32 | type: 'self_managed_certs', 33 | }, 34 | ]); 35 | }); 36 | 37 | it('should dump tenant with custom domains', async () => { 38 | const context = new Context({ AUTH0_INPUT_FILE: './test.yml' }, mockMgmtClient()); 39 | context.assets.customDomains = [ 40 | { 41 | custom_client_ip_header: 'cf-connecting-ip', 42 | domain: 'auth.test-domain.com', 43 | type: 'self_managed_certs', 44 | }, 45 | ]; 46 | 47 | const dumped = await handler.dump(context); 48 | expect(dumped).to.deep.equal({ customDomains: context.assets.customDomains }); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /test/context/yaml/flowVaultConnections.test.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs-extra'; 3 | import { expect } from 'chai'; 4 | import { cloneDeep } from 'lodash'; 5 | 6 | import Context from '../../../src/context/yaml'; 7 | import handler from '../../../src/context/yaml/handlers/flowVaultConnections'; 8 | import { cleanThenMkdir, testDataDir, mockMgmtClient } from '../../utils'; 9 | 10 | describe('#YAML context flowVaultConnections', () => { 11 | it('should process flowVaultConnections', async () => { 12 | const dir = path.join(testDataDir, 'yaml', 'flowVaultConnections'); 13 | cleanThenMkdir(dir); 14 | 15 | const yaml = ` 16 | flowVaultConnections: 17 | - 18 | name: 'Auth0 M2M Con #1' 19 | account_name: domain_name 20 | app_id: AUTH0 21 | ready: true 22 | - 23 | name: 'HTTP Con #1' 24 | app_id: HTTP 25 | ready: false 26 | `; 27 | 28 | const target = [ 29 | { name: 'Auth0 M2M Con #1', account_name: 'domain_name', app_id: 'AUTH0', ready: true }, 30 | { name: 'HTTP Con #1', app_id: 'HTTP', ready: false }, 31 | ]; 32 | 33 | const yamlFile = path.join(dir, 'flowVaultConnection.yaml'); 34 | fs.writeFileSync(yamlFile, yaml); 35 | 36 | const config = { AUTH0_INPUT_FILE: yamlFile }; 37 | const context = new Context(config, mockMgmtClient()); 38 | await context.loadAssetsFromLocal(); 39 | expect(context.assets.flowVaultConnections).to.deep.equal(target); 40 | }); 41 | 42 | it('should dump flowVaultConnections', async () => { 43 | const dir = path.join(testDataDir, 'yaml', 'flowVaultConnections'); 44 | cleanThenMkdir(dir); 45 | const context = new Context( 46 | { AUTH0_INPUT_FILE: path.join(dir, './flowVaultConnections.yml') }, 47 | mockMgmtClient() 48 | ); 49 | 50 | const flowVaultConnections = [ 51 | { name: 'Auth0 M2M Con #1', account_name: 'domain_name', app_id: 'AUTH0', ready: true }, 52 | { name: 'HTTP Con #1', app_id: 'HTTP', ready: false }, 53 | ]; 54 | 55 | context.assets.flowVaultConnections = cloneDeep(flowVaultConnections); 56 | 57 | const dumped = await handler.dump(context); 58 | 59 | expect(dumped).to.deep.equal({ flowVaultConnections }); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /test/context/yaml/guardianFactorProviders.test.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs-extra'; 3 | import { expect } from 'chai'; 4 | 5 | import Context from '../../../src/context/yaml'; 6 | import handler from '../../../src/context/yaml/handlers/guardianFactorProviders'; 7 | import { cleanThenMkdir, testDataDir, mockMgmtClient } from '../../utils'; 8 | 9 | describe('#YAML context guardian factor provider provider', () => { 10 | it('should process guardian factor providers', async () => { 11 | const dir = path.join(testDataDir, 'yaml', 'guardianFactorProviders'); 12 | cleanThenMkdir(dir); 13 | 14 | const yaml = ` 15 | guardianFactorProviders: 16 | - name: sms 17 | provider: twilio 18 | auth_token: @@ENV@@ 19 | sid: test 20 | enabled: true 21 | `; 22 | 23 | const yamlFile = path.join(dir, 'config.yaml'); 24 | fs.writeFileSync(yamlFile, yaml); 25 | 26 | const target = [ 27 | { 28 | auth_token: 'test', 29 | enabled: true, 30 | name: 'sms', 31 | provider: 'twilio', 32 | sid: 'test', 33 | }, 34 | ]; 35 | 36 | const config = { AUTH0_INPUT_FILE: yamlFile, AUTH0_KEYWORD_REPLACE_MAPPINGS: { ENV: 'test' } }; 37 | const context = new Context(config, mockMgmtClient()); 38 | await context.loadAssetsFromLocal(); 39 | expect(context.assets.guardianFactorProviders).to.deep.equal(target); 40 | }); 41 | 42 | it('should dump guardian factor providers', async () => { 43 | const context = new Context({ AUTH0_INPUT_FILE: './test.yml' }, mockMgmtClient()); 44 | context.assets.guardianFactorProviders = [ 45 | { 46 | auth_token: 'test', 47 | enabled: true, 48 | name: 'sms', 49 | provider: 'twilio', 50 | sid: 'test', 51 | }, 52 | ]; 53 | 54 | const dumped = await handler.dump(context); 55 | expect(dumped).to.deep.equal({ 56 | guardianFactorProviders: [ 57 | { 58 | auth_token: 'test', 59 | enabled: true, 60 | name: 'sms', 61 | provider: 'twilio', 62 | sid: 'test', 63 | }, 64 | ], 65 | }); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /test/context/yaml/guardianFactorTemplates.test.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs-extra'; 3 | import { expect } from 'chai'; 4 | 5 | import Context from '../../../src/context/yaml'; 6 | import handler from '../../../src/context/yaml/handlers/guardianFactorTemplates'; 7 | import { cleanThenMkdir, testDataDir, mockMgmtClient } from '../../utils'; 8 | 9 | describe('#YAML context guardian factors templates provider', () => { 10 | it('should process guardian factor templates', async () => { 11 | const dir = path.join(testDataDir, 'yaml', 'guardianFactorTemplates'); 12 | cleanThenMkdir(dir); 13 | 14 | const yaml = ` 15 | guardianFactorTemplates: 16 | - name: sms 17 | enrollment_message: 'test message {{code}}' 18 | verification_message: '{{code}} is your verification code for {{tenant.friendly_name}}' 19 | `; 20 | 21 | const yamlFile = path.join(dir, 'config.yaml'); 22 | fs.writeFileSync(yamlFile, yaml); 23 | 24 | const target = [ 25 | { 26 | enrollment_message: 'test message {{code}}', 27 | name: 'sms', 28 | verification_message: '{{code}} is your verification code for {{tenant.friendly_name}}', 29 | }, 30 | ]; 31 | 32 | const config = { AUTH0_INPUT_FILE: yamlFile, AUTH0_KEYWORD_REPLACE_MAPPINGS: { ENV: 'test' } }; 33 | const context = new Context(config, mockMgmtClient()); 34 | await context.loadAssetsFromLocal(); 35 | expect(context.assets.guardianFactorTemplates).to.deep.equal(target); 36 | }); 37 | 38 | it('should dump guardian factor teplates', async () => { 39 | const context = new Context({ AUTH0_INPUT_FILE: './test.yml' }, mockMgmtClient()); 40 | context.assets.guardianFactorTemplates = [ 41 | { 42 | enrollment_message: 'test message {{code}}', 43 | name: 'sms', 44 | verification_message: '{{code}} is your verification code for {{tenant.friendly_name}}', 45 | }, 46 | ]; 47 | 48 | const dumped = await handler.dump(context); 49 | expect(dumped).to.deep.equal({ 50 | guardianFactorTemplates: [ 51 | { 52 | enrollment_message: 'test message {{code}}', 53 | name: 'sms', 54 | verification_message: '{{code}} is your verification code for {{tenant.friendly_name}}', 55 | }, 56 | ], 57 | }); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /test/context/yaml/guardianFactors.test.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs-extra'; 3 | import { expect } from 'chai'; 4 | 5 | import Context from '../../../src/context/yaml'; 6 | import handler from '../../../src/context/yaml/handlers/guardianFactors'; 7 | import { cleanThenMkdir, testDataDir, mockMgmtClient } from '../../utils'; 8 | 9 | describe('#YAML context guardian factors provider', () => { 10 | it('should process guardian factors', async () => { 11 | const dir = path.join(testDataDir, 'yaml', 'guardianFactors'); 12 | cleanThenMkdir(dir); 13 | 14 | const yaml = ` 15 | guardianFactors: 16 | - name: sms 17 | enabled: true 18 | - name: push-notification 19 | enabled: false 20 | - name: otp 21 | enabled: false 22 | - name: email 23 | enabled: false 24 | - name: duo 25 | enabled: false 26 | `; 27 | 28 | const yamlFile = path.join(dir, 'config.yaml'); 29 | fs.writeFileSync(yamlFile, yaml); 30 | 31 | const target = [ 32 | { enabled: true, name: 'sms' }, 33 | { enabled: false, name: 'push-notification' }, 34 | { enabled: false, name: 'otp' }, 35 | { enabled: false, name: 'email' }, 36 | { enabled: false, name: 'duo' }, 37 | ]; 38 | 39 | const config = { AUTH0_INPUT_FILE: yamlFile, AUTH0_KEYWORD_REPLACE_MAPPINGS: { ENV: 'test' } }; 40 | const context = new Context(config, mockMgmtClient()); 41 | await context.loadAssetsFromLocal(); 42 | expect(context.assets.guardianFactors).to.deep.equal(target); 43 | }); 44 | 45 | it('should dump guardian factors', async () => { 46 | const context = new Context({ AUTH0_INPUT_FILE: './test.yml' }, mockMgmtClient()); 47 | context.assets.guardianFactors = [ 48 | { enabled: true, name: 'sms' }, 49 | { enabled: false, name: 'push-notification' }, 50 | { enabled: false, name: 'otp' }, 51 | { enabled: false, name: 'email' }, 52 | { enabled: false, name: 'duo' }, 53 | ]; 54 | 55 | const dumped = await handler.dump(context); 56 | expect(dumped).to.deep.equal({ 57 | guardianFactors: [ 58 | { enabled: true, name: 'sms' }, 59 | { enabled: false, name: 'push-notification' }, 60 | { enabled: false, name: 'otp' }, 61 | { enabled: false, name: 'email' }, 62 | { enabled: false, name: 'duo' }, 63 | ], 64 | }); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /test/context/yaml/guardianPhoneFactorMessageTypes.test.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs-extra'; 3 | import { expect } from 'chai'; 4 | 5 | import Context from '../../../src/context/yaml'; 6 | import handler from '../../../src/context/yaml/handlers/guardianPhoneFactorMessageTypes'; 7 | import { cleanThenMkdir, testDataDir, mockMgmtClient } from '../../utils'; 8 | 9 | describe('#YAML context guardian phone factor message types provider', () => { 10 | it('should process guardian phone factor message types', async () => { 11 | const dir = path.join(testDataDir, 'yaml', 'guardianPhoneFactorMessageTypes'); 12 | cleanThenMkdir(dir); 13 | 14 | const yaml = ` 15 | guardianPhoneFactorMessageTypes: 16 | message_types: 17 | - sms 18 | - voice 19 | `; 20 | 21 | const yamlFile = path.join(dir, 'config.yaml'); 22 | fs.writeFileSync(yamlFile, yaml); 23 | 24 | const target = { 25 | message_types: ['sms', 'voice'], 26 | }; 27 | 28 | const config = { AUTH0_INPUT_FILE: yamlFile, AUTH0_KEYWORD_REPLACE_MAPPINGS: { ENV: 'test' } }; 29 | const context = new Context(config, mockMgmtClient()); 30 | await context.loadAssetsFromLocal(); 31 | expect(context.assets.guardianPhoneFactorMessageTypes).to.deep.equal(target); 32 | }); 33 | 34 | it('should dump guardian phone factor message types', async () => { 35 | const context = new Context({ AUTH0_INPUT_FILE: './test.yml' }, mockMgmtClient()); 36 | context.assets.guardianPhoneFactorMessageTypes = { 37 | message_types: ['sms', 'voice'], 38 | }; 39 | 40 | const dumped = await handler.dump(context); 41 | expect(dumped).to.deep.equal({ 42 | guardianPhoneFactorMessageTypes: { 43 | message_types: ['sms', 'voice'], 44 | }, 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /test/context/yaml/guardianPhoneFactorSelectedProvider.test.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs-extra'; 3 | import { expect } from 'chai'; 4 | 5 | import Context from '../../../src/context/yaml'; 6 | import handler from '../../../src/context/yaml/handlers/guardianPhoneFactorSelectedProvider'; 7 | import { cleanThenMkdir, testDataDir, mockMgmtClient } from '../../utils'; 8 | 9 | describe('#YAML context guardian phone factor selected provider', () => { 10 | it('should process guardian phone factor selected provider', async () => { 11 | const dir = path.join(testDataDir, 'yaml', 'guardianPhoneFactorSelectedProvider'); 12 | cleanThenMkdir(dir); 13 | 14 | const yaml = ` 15 | guardianPhoneFactorSelectedProvider: 16 | provider: twilio 17 | `; 18 | 19 | const yamlFile = path.join(dir, 'config.yaml'); 20 | fs.writeFileSync(yamlFile, yaml); 21 | 22 | const target = { 23 | provider: 'twilio', 24 | }; 25 | 26 | const config = { AUTH0_INPUT_FILE: yamlFile, AUTH0_KEYWORD_REPLACE_MAPPINGS: { ENV: 'test' } }; 27 | const context = new Context(config, mockMgmtClient()); 28 | await context.loadAssetsFromLocal(); 29 | expect(context.assets.guardianPhoneFactorSelectedProvider).to.deep.equal(target); 30 | }); 31 | 32 | it('should dump guardian phone factor selected provider', async () => { 33 | const context = new Context({ AUTH0_INPUT_FILE: './test.yml' }, mockMgmtClient()); 34 | context.assets.guardianPhoneFactorSelectedProvider = { 35 | provider: 'twilio', 36 | }; 37 | 38 | const dumped = await handler.dump(context); 39 | expect(dumped).to.deep.equal({ 40 | guardianPhoneFactorSelectedProvider: { 41 | provider: 'twilio', 42 | }, 43 | }); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /test/context/yaml/guardianPolicies.test.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs-extra'; 3 | import { expect } from 'chai'; 4 | 5 | import Context from '../../../src/context/yaml'; 6 | import handler from '../../../src/context/yaml/handlers/guardianPolicies'; 7 | import { cleanThenMkdir, testDataDir, mockMgmtClient } from '../../utils'; 8 | 9 | describe('#YAML context guardian policies provider', () => { 10 | it('should process guardian policies', async () => { 11 | const dir = path.join(testDataDir, 'yaml', 'guardianPolicies'); 12 | cleanThenMkdir(dir); 13 | 14 | const yaml = ` 15 | guardianPolicies: 16 | policies: 17 | - all-applications 18 | `; 19 | 20 | const yamlFile = path.join(dir, 'config.yaml'); 21 | fs.writeFileSync(yamlFile, yaml); 22 | 23 | const target = { 24 | policies: ['all-applications'], 25 | }; 26 | 27 | const config = { AUTH0_INPUT_FILE: yamlFile, AUTH0_KEYWORD_REPLACE_MAPPINGS: { ENV: 'test' } }; 28 | const context = new Context(config, mockMgmtClient()); 29 | await context.loadAssetsFromLocal(); 30 | expect(context.assets.guardianPolicies).to.deep.equal(target); 31 | }); 32 | 33 | it('should support empty guardian policies', async () => { 34 | const dir = path.join(testDataDir, 'yaml', 'guardianPolicies'); 35 | cleanThenMkdir(dir); 36 | 37 | const yaml = ` 38 | guardianPolicies: 39 | policies: [] 40 | `; 41 | 42 | const yamlFile = path.join(dir, 'config.yaml'); 43 | fs.writeFileSync(yamlFile, yaml); 44 | 45 | const target = { 46 | policies: [], 47 | }; 48 | 49 | const config = { AUTH0_INPUT_FILE: yamlFile, AUTH0_KEYWORD_REPLACE_MAPPINGS: { ENV: 'test' } }; 50 | const context = new Context(config, mockMgmtClient()); 51 | await context.loadAssetsFromLocal(); 52 | expect(context.assets.guardianPolicies).to.deep.equal(target); 53 | }); 54 | 55 | it('should dump guardian policies', async () => { 56 | const context = new Context({ AUTH0_INPUT_FILE: './test.yml' }, mockMgmtClient()); 57 | context.assets.guardianPolicies = { 58 | policies: ['all-applications'], 59 | }; 60 | 61 | const dumped = await handler.dump(context); 62 | expect(dumped).to.deep.equal({ 63 | guardianPolicies: { 64 | policies: ['all-applications'], 65 | }, 66 | }); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /test/context/yaml/rulesConfigs.test.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs-extra'; 3 | import { expect } from 'chai'; 4 | 5 | import Context from '../../../src/context/yaml'; 6 | import handler from '../../../src/context/yaml/handlers/rulesConfigs'; 7 | import { cleanThenMkdir, testDataDir, mockMgmtClient } from '../../utils'; 8 | 9 | describe('#YAML context rules configs', () => { 10 | it('should process rules configs', async () => { 11 | const dir = path.join(testDataDir, 'yaml', 'rulesConfigs'); 12 | cleanThenMkdir(dir); 13 | 14 | const yaml = ` 15 | rulesConfigs: 16 | - key: "SOME_SECRET" 17 | value: 'some_key' 18 | `; 19 | const yamlFile = path.join(dir, 'config.yaml'); 20 | fs.writeFileSync(yamlFile, yaml); 21 | 22 | const target = [{ key: 'SOME_SECRET', value: 'some_key' }]; 23 | 24 | const config = { AUTH0_INPUT_FILE: yamlFile }; 25 | const context = new Context(config, mockMgmtClient()); 26 | await context.loadAssetsFromLocal(); 27 | 28 | expect(context.assets.rulesConfigs).to.deep.equal(target); 29 | }); 30 | 31 | it('should dump rules configs', async () => { 32 | const context = new Context({ AUTH0_INPUT_FILE: './test.yml' }, mockMgmtClient()); 33 | const rulesConfigs = [{ key: 'SOME_SECRET', value: 'some_key' }]; 34 | context.assets.rulesConfigs = rulesConfigs; 35 | 36 | const dumped = await handler.dump(context); 37 | expect(dumped).to.deep.equal({ rulesConfigs: [] }); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /test/e2e/e2e-cli.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | WORK_DIR="./local/cli" 5 | CONFIG_FILE="./config-cli-e2e.json" 6 | 7 | npm ci #Install all dependencies to allow for building 8 | npm run build 9 | npm ci --omit=dev #Remove dev dependencies 10 | 11 | TARBALL_PATH=$(npm pack) 12 | sudo npm install -g $TARBALL_PATH 13 | 14 | echo "{ 15 | \"AUTH0_DOMAIN\": \"$AUTH0_E2E_TENANT_DOMAIN\", 16 | \"AUTH0_CLIENT_ID\": \"$AUTH0_E2E_CLIENT_ID\", 17 | \"AUTH0_CLIENT_SECRET\": \"$AUTH0_E2E_CLIENT_SECRET\" 18 | }" > $CONFIG_FILE 19 | 20 | a0deploy export --env=false --output_folder=$WORK_DIR --format=yaml -c=$CONFIG_FILE 21 | 22 | echo "-------- Beginning deploy/import phase --------" 23 | 24 | a0deploy import --env=false --input_file=$WORK_DIR --format=yaml -c=$CONFIG_FILE 25 | 26 | rm $CONFIG_FILE 27 | rm $TARBALL_PATH 28 | -------------------------------------------------------------------------------- /test/e2e/e2e_recordings.md: -------------------------------------------------------------------------------- 1 | # How to manage E2E testing recordings 2 | 3 | ## Re-recording 4 | 5 | Ensure `AUTH0_E2E_TENANT_DOMAIN`, `AUTH0_E2E_CLIENT_ID`, `AUTH0_E2E_CLIENT_SECRET` env vars are set. 6 | 7 | Then delete the recording file that you are attempting to re-record. Example: `rm test/e2e/recordings/should-deploy-while-deleting-resources-if-AUTH0_ALLOW_DELETE-is-true.json` 8 | 9 | Finally, run the following command: 10 | 11 | ```shell 12 | AUTH0_HTTP_RECORDINGS="record" npx ts-mocha --timeout=120000 -p tsconfig.json test/e2e/e2e.test.ts -g="" 13 | ``` 14 | 15 | **Example:** 16 | 17 | ```shell 18 | AUTH0_HTTP_RECORDINGS="record" npx ts-mocha --timeout=120000 -p tsconfig.json test/e2e/e2e.test.ts -g="AUTH0_ALLOW_DELETE is true" 19 | ``` 20 | 21 | ### Common Errors When Re-Recording 22 | 23 | - `access_denied: {"error":"access_denied","error_description":"Unauthorized"}` - The client ID/secret pair provided through `AUTH0_E2E_CLIENT_ID` and `AUTH0_E2E_CLIENT_SECRET` respectively is not valid, double-check their correct values. 24 | - `APIError: Nock: Disallowed net connect for "auth0-deploy-cli-e2e.us.auth0.com:443/oauth/token"` - Recordings already exist for this test, will need to remove the correlating recordings file and try again. Re-recording the entire file is necessary even if HTTP request changes are additive. 25 | 26 | ## Running Tests w/ Recordings 27 | 28 | **Run for all:** 29 | 30 | ```shell 31 | AUTH0_HTTP_RECORDINGS="lockdown" npm run test:e2e:node-module 32 | ``` 33 | 34 | **Run a specific test:** 35 | 36 | ```shell 37 | AUTH0_HTTP_RECORDINGS="lockdown" npx ts-mocha --timeout=120000 -p tsconfig.json test/e2e/e2e.test.ts -g="should deploy while deleting resources if AUTH0_ALLOW_DELETE is true" 38 | ``` 39 | -------------------------------------------------------------------------------- /test/e2e/testdata/keyword-replacements/directory/_reset.json: -------------------------------------------------------------------------------- 1 | { 2 | "enabled_locales": ["en"], 3 | "friendly_name": "Tenant friendly name", 4 | "flags": { 5 | "new_universal_login_experience_enabled": true 6 | }, 7 | "universal_login": {} 8 | } -------------------------------------------------------------------------------- /test/e2e/testdata/keyword-replacements/directory/tenant.json: -------------------------------------------------------------------------------- 1 | { 2 | "enabled_locales": ["en"], 3 | "friendly_name": "This is the ##COMPANY_NAME## Tenant", 4 | "flags": { 5 | "new_universal_login_experience_enabled": true 6 | }, 7 | "universal_login": {} 8 | } -------------------------------------------------------------------------------- /test/e2e/testdata/keyword-replacements/yaml/_reset.yaml: -------------------------------------------------------------------------------- 1 | #This configuration is to reset the tenant to a baseline state 2 | tenant: 3 | friendly_name: Tenant friendly name 4 | enabled_locales: ['en'] 5 | flags: 6 | new_universal_login_experience_enabled: true 7 | -------------------------------------------------------------------------------- /test/e2e/testdata/keyword-replacements/yaml/tenant.yaml: -------------------------------------------------------------------------------- 1 | tenant: 2 | friendly_name: This is the ##COMPANY_NAME## Tenant 3 | enabled_locales: @@LANGUAGES@@ 4 | flags: 5 | new_universal_login_experience_enabled: true 6 | -------------------------------------------------------------------------------- /test/e2e/testdata/lots-of-configuration/actions/My Custom Action/code.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Handler that will be called during the execution of a PostLogin flow. 3 | * 4 | * @param {Event} event - Details about the user and the context in which they are logging in. 5 | * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. 6 | */ 7 | exports.onExecutePostLogin = async (event, api) => { 8 | console.log('Some custom action!'); 9 | }; 10 | 11 | /** 12 | * Handler that will be invoked when this action is resuming after an external redirect. If your 13 | * onExecutePostLogin function does not perform a redirect, this function can be safely ignored. 14 | * 15 | * @param {Event} event - Details about the user and the context in which they are logging in. 16 | * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. 17 | */ 18 | // exports.onContinuePostLogin = async (event, api) => { 19 | // }; 20 | -------------------------------------------------------------------------------- /test/e2e/testdata/lots-of-configuration/databases/boo-baz-db-connection-test/change_password.js: -------------------------------------------------------------------------------- 1 | function changePassword(email, newPassword, callback) { 2 | // This script should change the password stored for the current user in your 3 | // database. It is executed when the user clicks on the confirmation link 4 | // after a reset password request. 5 | // The content and behavior of password confirmation emails can be customized 6 | // here: https://manage.auth0.com/#/emails 7 | // The `newPassword` parameter of this function is in plain text. It must be 8 | // hashed/salted to match whatever is stored in your database. 9 | // 10 | // There are three ways that this script can finish: 11 | // 1. The user's password was updated successfully: 12 | // callback(null, true); 13 | // 2. The user's password was not updated: 14 | // callback(null, false); 15 | // 3. Something went wrong while trying to reach your database: 16 | // callback(new Error("my error message")); 17 | // 18 | // If an error is returned, it will be passed to the query string of the page 19 | // where the user is being redirected to after clicking the confirmation link. 20 | // For example, returning `callback(new Error("error"))` and redirecting to 21 | // https://example.com would redirect to the following URL: 22 | // https://example.com?email=alice%40example.com&message=error&success=false 23 | 24 | const msg = 25 | 'Please implement the Change Password script for this database ' + 26 | 'connection at https://manage.auth0.com/#/connections/database'; 27 | return callback(new Error(msg)); 28 | } 29 | -------------------------------------------------------------------------------- /test/e2e/testdata/lots-of-configuration/databases/boo-baz-db-connection-test/create.js: -------------------------------------------------------------------------------- 1 | function create(user, callback) { 2 | // This script should create a user entry in your existing database. It will 3 | // be executed when a user attempts to sign up, or when a user is created 4 | // through the Auth0 dashboard or API. 5 | // When this script has finished executing, the Login script will be 6 | // executed immediately afterwards, to verify that the user was created 7 | // successfully. 8 | // 9 | // The user object will always contain the following properties: 10 | // * email: the user's email 11 | // * password: the password entered by the user, in plain text 12 | // * tenant: the name of this Auth0 account 13 | // * client_id: the client ID of the application where the user signed up, or 14 | // API key if created through the API or Auth0 dashboard 15 | // * connection: the name of this database connection 16 | // 17 | // There are three ways this script can finish: 18 | // 1. A user was successfully created 19 | // callback(null); 20 | // 2. This user already exists in your database 21 | // callback(new ValidationError("user_exists", "my error message")); 22 | // 3. Something went wrong while trying to reach your database 23 | // callback(new Error("my error message")); 24 | 25 | const msg = 26 | 'Please implement the Create script for this database connection ' + 27 | 'at https://manage.auth0.com/#/connections/database'; 28 | return callback(new Error(msg)); 29 | } 30 | -------------------------------------------------------------------------------- /test/e2e/testdata/lots-of-configuration/databases/boo-baz-db-connection-test/delete.js: -------------------------------------------------------------------------------- 1 | function remove(id, callback) { 2 | // This script remove a user from your existing database. 3 | // It is executed whenever a user is deleted from the API or Auth0 dashboard. 4 | // 5 | // There are two ways that this script can finish: 6 | // 1. The user was removed successfully: 7 | // callback(null); 8 | // 2. Something went wrong while trying to reach your database: 9 | // callback(new Error("my error message")); 10 | 11 | const msg = 12 | 'Please implement the Delete script for this database ' + 13 | 'connection at https://manage.auth0.com/#/connections/database'; 14 | return callback(new Error(msg)); 15 | } 16 | -------------------------------------------------------------------------------- /test/e2e/testdata/lots-of-configuration/databases/boo-baz-db-connection-test/get_user.js: -------------------------------------------------------------------------------- 1 | function getByEmail(email, callback) { 2 | // This script should retrieve a user profile from your existing database, 3 | // without authenticating the user. 4 | // It is used to check if a user exists before executing flows that do not 5 | // require authentication (signup and password reset). 6 | // 7 | // There are three ways this script can finish: 8 | // 1. A user was successfully found. The profile should be in the following 9 | // format: https://auth0.com/docs/users/normalized/auth0/normalized-user-profile-schema. 10 | // callback(null, profile); 11 | // 2. A user was not found 12 | // callback(null); 13 | // 3. Something went wrong while trying to reach your database: 14 | // callback(new Error("my error message")); 15 | 16 | const msg = 17 | 'Please implement the Get User script for this database connection ' + 18 | 'at https://manage.auth0.com/#/connections/database'; 19 | return callback(new Error(msg)); 20 | } 21 | -------------------------------------------------------------------------------- /test/e2e/testdata/lots-of-configuration/databases/boo-baz-db-connection-test/login.js: -------------------------------------------------------------------------------- 1 | function login(email, password, callback) { 2 | // This script should authenticate a user against the credentials stored in 3 | // your database. 4 | // It is executed when a user attempts to log in or immediately after signing 5 | // up (as a verification that the user was successfully signed up). 6 | // 7 | // Everything returned by this script will be set as part of the user profile 8 | // and will be visible by any of the tenant admins. Avoid adding attributes 9 | // with values such as passwords, keys, secrets, etc. 10 | // 11 | // The `password` parameter of this function is in plain text. It must be 12 | // hashed/salted to match whatever is stored in your database. For example: 13 | // 14 | // var bcrypt = require('bcrypt@0.8.5'); 15 | // bcrypt.compare(password, dbPasswordHash, function(err, res)) { ... } 16 | // 17 | // There are three ways this script can finish: 18 | // 1. The user's credentials are valid. The returned user profile should be in 19 | // the following format: https://auth0.com/docs/users/normalized/auth0/normalized-user-profile-schema 20 | // var profile = { 21 | // user_id: ..., // user_id is mandatory 22 | // email: ..., 23 | // [...] 24 | // }; 25 | // callback(null, profile); 26 | // 2. The user's credentials are invalid 27 | // callback(new WrongUsernameOrPasswordError(email, "my error message")); 28 | // 3. Something went wrong while trying to reach your database 29 | // callback(new Error("my error message")); 30 | // 31 | // A list of Node.js modules which can be referenced is available here: 32 | // 33 | // https://tehsis.github.io/webtaskio-canirequire/ 34 | console.log('AYYYYYE'); 35 | 36 | const msg = 37 | 'Please implement the Login script for this database connection ' + 38 | 'at https://manage.auth0.com/#/connections/database'; 39 | return callback(new Error(msg)); 40 | } 41 | -------------------------------------------------------------------------------- /test/e2e/testdata/lots-of-configuration/databases/boo-baz-db-connection-test/verify.js: -------------------------------------------------------------------------------- 1 | function verify(email, callback) { 2 | // This script should mark the current user's email address as verified in 3 | // your database. 4 | // It is executed whenever a user clicks the verification link sent by email. 5 | // These emails can be customized at https://manage.auth0.com/#/emails. 6 | // It is safe to assume that the user's email already exists in your database, 7 | // because verification emails, if enabled, are sent immediately after a 8 | // successful signup. 9 | // 10 | // There are two ways that this script can finish: 11 | // 1. The user's email was verified successfully 12 | // callback(null, true); 13 | // 2. Something went wrong while trying to reach your database: 14 | // callback(new Error("my error message")); 15 | // 16 | // If an error is returned, it will be passed to the query string of the page 17 | // where the user is being redirected to after clicking the verification link. 18 | // For example, returning `callback(new Error("error"))` and redirecting to 19 | // https://example.com would redirect to the following URL: 20 | // https://example.com?email=alice%40example.com&message=error&success=false 21 | 22 | const msg = 23 | 'Please implement the Verify script for this database connection ' + 24 | 'at https://manage.auth0.com/#/connections/database'; 25 | return callback(new Error(msg)); 26 | } 27 | -------------------------------------------------------------------------------- /test/e2e/testdata/lots-of-configuration/emailTemplates/welcome_email.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Welcome!

4 | 5 | 6 | -------------------------------------------------------------------------------- /test/e2e/testdata/lots-of-configuration/forms/Blank-form.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Blank-form", 3 | "languages": { 4 | "primary": "en" 5 | }, 6 | "nodes": [ 7 | { 8 | "id": "step_ggeX", 9 | "type": "STEP", 10 | "coordinates": { 11 | "x": 500, 12 | "y": 0 13 | }, 14 | "alias": "New step", 15 | "config": { 16 | "components": [ 17 | { 18 | "id": "full_name", 19 | "category": "FIELD", 20 | "type": "TEXT", 21 | "label": "Your Name", 22 | "required": true, 23 | "sensitive": false, 24 | "config": { 25 | "multiline": false, 26 | "min_length": 1, 27 | "max_length": 50 28 | } 29 | }, 30 | { 31 | "id": "next_button_3FbA", 32 | "category": "BLOCK", 33 | "type": "NEXT_BUTTON", 34 | "config": { 35 | "text": "Continue" 36 | } 37 | } 38 | ], 39 | "next_node": "$ending" 40 | } 41 | } 42 | ], 43 | "start": { 44 | "next_node": "step_ggeX", 45 | "coordinates": { 46 | "x": 0, 47 | "y": 0 48 | } 49 | }, 50 | "ending": { 51 | "resume_flow": true, 52 | "coordinates": { 53 | "x": 1250, 54 | "y": 0 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /test/e2e/testdata/lots-of-configuration/pages/error_page.html: -------------------------------------------------------------------------------- 1 | Error Page 2 | -------------------------------------------------------------------------------- /test/e2e/testdata/lots-of-configuration/pages/guardian_multifactor.html: -------------------------------------------------------------------------------- 1 | MFA 2 | -------------------------------------------------------------------------------- /test/e2e/testdata/lots-of-configuration/pages/login.html: -------------------------------------------------------------------------------- 1 | TEST123 2 | -------------------------------------------------------------------------------- /test/e2e/testdata/lots-of-configuration/pages/password_reset.html: -------------------------------------------------------------------------------- 1 | Change Password 2 | -------------------------------------------------------------------------------- /test/e2e/testdata/should-preserve-keywords/directory/clients/Auth0 CLI - dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Auth0 CLI - ##ENV##", 3 | "allowed_clients": [], 4 | "app_type": "non_interactive", 5 | "callbacks": [], 6 | "client_aliases": [], 7 | "cross_origin_auth": false, 8 | "custom_login_page_on": true, 9 | "grant_types": ["client_credentials"], 10 | "is_first_party": true, 11 | "is_token_endpoint_ip_header_trusted": false, 12 | "jwt_configuration": { 13 | "alg": "RS256", 14 | "lifetime_in_seconds": 36000, 15 | "secret_encoded": false 16 | }, 17 | "logo_uri": "https://##ENV##.assets.com/photos/foo", 18 | "native_social_login": { 19 | "apple":{ 20 | "enabled": false 21 | }, 22 | "facebook":{ 23 | "enabled": false 24 | } 25 | }, 26 | "refresh_token":{ 27 | "expiration_type": "non-expiring", 28 | "leeway": 0, 29 | "infinite_token_lifetime": true, 30 | "infinite_idle_token_lifetime": true, 31 | "token_lifetime": 31557600, 32 | "idle_token_lifetime": 2592000, 33 | "rotation_type": "non-rotating" 34 | }, 35 | "sso_disabled": false, 36 | "token_endpoint_auth_method": "client_secret_post" 37 | } -------------------------------------------------------------------------------- /test/e2e/testdata/should-preserve-keywords/directory/emailTemplates/welcome_email.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Welcome to ##TENANT_NAME##!

4 | 5 | 6 | -------------------------------------------------------------------------------- /test/e2e/testdata/should-preserve-keywords/directory/emailTemplates/welcome_email.json: -------------------------------------------------------------------------------- 1 | { 2 | "template": "welcome_email", 3 | "body": "./welcome_email.html", 4 | "from": "", 5 | "resultUrl": "https://##DOMAIN##/welcome", 6 | "subject": "Welcome", 7 | "syntax": "liquid", 8 | "urlLifetimeInSeconds": 3600, 9 | "enabled": false 10 | } 11 | -------------------------------------------------------------------------------- /test/e2e/testdata/should-preserve-keywords/directory/tenant.json: -------------------------------------------------------------------------------- 1 | { 2 | "allowed_logout_urls": [ 3 | "https://travel0.com/logoutCallback" 4 | ], 5 | "enabled_locales": "@@LANGUAGES@@", 6 | "flags": { 7 | "allow_legacy_delegation_grant_types": true, 8 | "allow_legacy_ro_grant_types": true, 9 | "change_pwd_flow_v1": false, 10 | "enable_apis_section": false, 11 | "enable_client_connections": false, 12 | "enable_custom_domain_in_emails": false, 13 | "enable_dynamic_client_registration": false, 14 | "enable_legacy_logs_search_v2": false, 15 | "enable_public_signup_user_exists_error": true, 16 | "new_universal_login_experience_enabled": true, 17 | "universal_login": true, 18 | "use_scope_descriptions_for_consent": false, 19 | "revoke_refresh_token_grant": false, 20 | "disable_clickjack_protection_headers": false, 21 | "enable_pipeline2": false 22 | }, 23 | "friendly_name": "##TENANT_NAME##", 24 | "idle_session_lifetime": 1, 25 | "picture_url": "https://upload.wikimedia.org/wikipedia/commons/0/0d/Grandmas_marathon_finishers.png", 26 | "sandbox_version": "12", 27 | "session_lifetime": 3.0166666666666666, 28 | "support_email": "support@##DOMAIN##", 29 | "support_url": "https://##DOMAIN##/support", 30 | "universal_login": { 31 | "colors": { 32 | "primary": "#F8F8F2", 33 | "page_background": "#222221" 34 | } 35 | }, 36 | "session_cookie": { 37 | "mode": "non-persistent" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /test/e2e/testdata/should-preserve-keywords/yaml/emailTemplates/welcome_email.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Welcome to ##TENANT_NAME##!

4 | 5 | 6 | -------------------------------------------------------------------------------- /test/index.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import a0deploy, { dump, deploy } from '../src'; 4 | 5 | describe('#index exports', () => { 6 | it('should expose functions for deploy, dump, import, and export', () => { 7 | expect(typeof a0deploy).to.equal('object'); 8 | expect(Object.keys(a0deploy)).to.have.all.members([ 9 | 'deploy', 10 | 'dump', 11 | 'import', 12 | 'export', 13 | 'tools', 14 | ]); 15 | 16 | [a0deploy.deploy, a0deploy.dump, a0deploy.export, a0deploy.import].forEach((fn) => { 17 | expect(typeof fn).to.equal('function'); 18 | }); 19 | 20 | expect(typeof dump).to.equal('function'); 21 | expect(typeof deploy).to.equal('function'); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /test/readonly.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import cleanAssets from '../src/readonly'; 4 | 5 | describe('#readonly', function () { 6 | it('should clear databases of options.configuration', () => { 7 | const assets = { 8 | databases: [ 9 | { 10 | name: 'db1', 11 | strategy: 'auth0', 12 | options: { 13 | configuration: { 14 | secret: 'i am actually a goose', 15 | }, 16 | requires_username: true, 17 | }, 18 | }, 19 | ], 20 | }; 21 | const target = { 22 | name: 'db1', 23 | strategy: 'auth0', 24 | options: { 25 | requires_username: true, 26 | }, 27 | }; 28 | const clear = cleanAssets(assets, {}); 29 | expect(clear.databases).is.deep.equal([target]); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /test/sessionDurationsAsMinutes.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import { sessionDurationsToMinutes } from '../src/sessionDurationsToMinutes'; 4 | 5 | describe('#sessionDurationsToMinutes', () => { 6 | it('should not convert session durations to minutes if initial durations not defined', () => { 7 | const sessionDurations = sessionDurationsToMinutes({}); 8 | 9 | expect(sessionDurations.idle_session_lifetime_in_minutes).to.be.undefined; 10 | expect(sessionDurations.session_lifetime_in_minutes).to.be.undefined; 11 | }); 12 | 13 | it('should convert session durations to minutes durations are defined', () => { 14 | const sessionDurations = sessionDurationsToMinutes({ 15 | session_lifetime: 1, 16 | idle_session_lifetime: 1, 17 | }); 18 | 19 | expect(sessionDurations.idle_session_lifetime_in_minutes).to.equal(60); 20 | expect(sessionDurations.session_lifetime_in_minutes).to.equal(60); 21 | }); 22 | 23 | it('should convert session durations to minutes durations are partially defined', () => { 24 | const sessionDurations1 = sessionDurationsToMinutes({ 25 | session_lifetime: undefined, 26 | idle_session_lifetime: 1, 27 | }); 28 | 29 | expect(sessionDurations1.idle_session_lifetime_in_minutes).to.equal(60); 30 | expect(sessionDurations1.session_lifetime_in_minutes).to.be.undefined; 31 | 32 | const sessionDurations2 = sessionDurationsToMinutes({ 33 | session_lifetime: 1, 34 | idle_session_lifetime: undefined, 35 | }); 36 | 37 | expect(sessionDurations2.idle_session_lifetime_in_minutes).to.be.undefined; 38 | expect(sessionDurations2.session_lifetime_in_minutes).to.equal(60); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /test/tools/auth0/client.tests.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import client from '../../../src/tools/auth0/client'; 3 | 4 | describe('#schema validation tests', async () => { 5 | it('should paginate a getAll query', async () => { 6 | const clients = []; 7 | const expectedNbClients = 1029; 8 | 9 | for (let i = 0; i < expectedNbClients; i++) { 10 | clients.push({ 11 | name: 'test-' + i + '-' + Math.round(Math.random() * 10000000000), 12 | }); 13 | } 14 | 15 | const mock = { 16 | clients: { 17 | getAll: async (args) => 18 | new Promise((resolve) => { 19 | const localArgs = { ...args }; 20 | setTimeout(() => { 21 | resolve({ 22 | data: { 23 | start: localArgs.page * localArgs.per_page, 24 | total: expectedNbClients, 25 | clients: clients.slice( 26 | localArgs.page * localArgs.per_page, 27 | (localArgs.page + 1) * localArgs.per_page 28 | ), 29 | }, 30 | }); 31 | }, 10); 32 | }), 33 | }, 34 | }; 35 | 36 | const pagedManager = client(mock); 37 | 38 | const allClients = await pagedManager.clients.getAll({ paginate: true }); 39 | 40 | expect(allClients.length).to.eq(expectedNbClients); 41 | }); 42 | 43 | it('should paginate a nested object with getAll', async () => { 44 | const permissions = []; 45 | const expectedNbItems = 150; 46 | 47 | for (let i = 0; i < expectedNbItems; i++) { 48 | permissions.push({ 49 | name: 'test-' + i + '-' + Math.round(Math.random() * 10000000000), 50 | }); 51 | } 52 | 53 | const mock = { 54 | roles: { 55 | permissions: { 56 | getAll: async (localArgs) => 57 | Promise.resolve({ 58 | data: { 59 | start: localArgs.page * localArgs.per_page, 60 | total: expectedNbItems, 61 | permissions: permissions.slice( 62 | localArgs.page * localArgs.per_page, 63 | (localArgs.page + 1) * localArgs.per_page 64 | ), 65 | }, 66 | }), 67 | }, 68 | }, 69 | }; 70 | 71 | const pagedManager = client(mock); 72 | 73 | const rolesPermissions = await pagedManager.roles.permissions.getAll({ paginate: true }); 74 | expect(rolesPermissions.length).to.eq(expectedNbItems); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /test/tools/auth0/handlers/guardianPolicies.tests.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai'); 2 | const guardianPolicies = require('../../../../src/tools/auth0/handlers/guardianPolicies'); 3 | 4 | describe('#guardianPolicies handler', () => { 5 | describe('#getType', () => { 6 | it('should support older version of auth0 client', async () => { 7 | const auth0 = { 8 | guardian: { 9 | // omitting getPolicies() 10 | }, 11 | }; 12 | 13 | const handler = new guardianPolicies.default({ client: auth0 }); 14 | const data = await handler.getType(); 15 | expect(data).to.deep.equal({}); 16 | }); 17 | 18 | it('should get guardian policies', async () => { 19 | const auth0 = { 20 | guardian: { 21 | getPolicies: () => ({ data: ['all-applications'] }), 22 | }, 23 | }; 24 | 25 | const handler = new guardianPolicies.default({ client: auth0 }); 26 | const data = await handler.getType(); 27 | expect(data).to.deep.equal({ 28 | policies: ['all-applications'], 29 | }); 30 | }); 31 | }); 32 | 33 | describe('#processChanges', () => { 34 | it('should update guardian policies settings', async () => { 35 | const auth0 = { 36 | guardian: { 37 | updatePolicies: (data) => { 38 | expect(data).to.be.an('array'); 39 | expect(data[0]).to.equal('all-applications'); 40 | return Promise.resolve({ data }); 41 | }, 42 | }, 43 | }; 44 | 45 | const handler = new guardianPolicies.default({ client: auth0 }); 46 | const stageFn = Object.getPrototypeOf(handler).processChanges; 47 | 48 | await stageFn.apply(handler, [ 49 | { 50 | guardianPolicies: { 51 | policies: ['all-applications'], 52 | }, 53 | }, 54 | ]); 55 | }); 56 | 57 | it('should skip processing if assets are empty', async () => { 58 | const auth0 = { 59 | guardian: { 60 | updatePolicies: () => { 61 | const err = new Error('updatePolicies() should not have been called'); 62 | return Promise.reject(err); 63 | }, 64 | }, 65 | }; 66 | 67 | const handler = new guardianPolicies.default({ client: auth0 }); 68 | const stageFn = Object.getPrototypeOf(handler).processChanges; 69 | 70 | await stageFn.apply(handler, [{ guardianPolicies: {} }]); 71 | }); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /test/tools/constants.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai'); 2 | const tools = require('../../src/tools/index'); 3 | 4 | describe('#constants', () => { 5 | it('should be exposed', () => { 6 | expect(tools.constants.PAGE_NAMES).to.be.an('array'); 7 | expect(tools.constants.RULES_STAGES).to.include('login_success'); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /test/tools/mocha.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | process.env.NODE_ENV = 'test'; 3 | -------------------------------------------------------------------------------- /test/tools/test.file.json: -------------------------------------------------------------------------------- 1 | { 2 | "a": 1, 3 | "string_key": @@string@@, 4 | "array_key": @@array@@, 5 | "object_key": @@object@@, 6 | "int_key": @@int@@, 7 | "simple_string_key": "Some ##string##", 8 | "simple_array_key": "Some'##array##", 9 | "simple_object_key": "Some ##object##", 10 | "simple_int_key": ##int## 11 | } 12 | -------------------------------------------------------------------------------- /test/tools/utils.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { getEnabledClients } from '../../src/tools/utils'; 3 | 4 | describe('#getEnabledClients', () => { 5 | const mockExclusions = {}; 6 | const mockExistingConnections = [ 7 | { name: 'Existing connection', enabled_clients: ['Client 1', 'Client 2'] }, 8 | ]; 9 | const mockClients = [ 10 | { name: 'Client 1', id: 'client-1-id' }, 11 | { name: 'Client 2', id: 'client-2-id' }, 12 | { name: 'Client 3', id: 'client-3-id' }, 13 | ]; 14 | 15 | it('should return no enabled clients if connection does not have any', () => { 16 | const mockConnection = { 17 | enabled_clients: [], // No enabled clients! 18 | name: 'Target connection', 19 | }; 20 | 21 | const enabledClients = getEnabledClients( 22 | mockExclusions, 23 | mockConnection, 24 | mockExistingConnections, 25 | mockClients 26 | ); 27 | 28 | expect(enabledClients).to.deep.equal([]); 29 | }); 30 | 31 | it('should return enabled clients when connection defines them', () => { 32 | const expectedEnabledClients = ['client-id-1', 'client-id-2']; // Two enabled clients! 33 | 34 | const mockConnection = { 35 | enabled_clients: expectedEnabledClients, 36 | name: 'Target connection', 37 | }; 38 | 39 | const enabledClients = getEnabledClients( 40 | mockExclusions, 41 | mockConnection, 42 | mockExistingConnections, 43 | mockClients 44 | ); 45 | 46 | expect(enabledClients).to.deep.equal(expectedEnabledClients); 47 | }); 48 | 49 | it('should return undefined when enable clients is not defined', () => { 50 | const mockConnection = { 51 | enabled_clients: undefined, // This connection has no defined clients enabled. See: GH issue #523 52 | name: 'Target connection', 53 | }; 54 | 55 | const enabledClients = getEnabledClients( 56 | mockExclusions, 57 | mockConnection, 58 | mockExistingConnections, 59 | mockClients 60 | ); 61 | 62 | expect(enabledClients).to.equal(undefined); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2016", 4 | "experimentalDecorators": true, 5 | "module": "commonjs", 6 | "rootDir": "./src", 7 | "allowJs": true, 8 | "outDir": "./lib", 9 | "sourceMap": true, 10 | "esModuleInterop": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "strict": false, 13 | "strictNullChecks": true, 14 | "skipLibCheck": true, 15 | "declaration": true 16 | }, 17 | "include": ["src/**/*"] 18 | } 19 | --------------------------------------------------------------------------------