├── .cursor └── rules │ ├── make-commands.mdc │ └── terraform-commands.mdc ├── .github ├── ISSUE_TEMPLATE.md ├── dependabot.yml └── workflows │ ├── bump-version.yml │ ├── golangci-lint.yaml │ ├── release.yml │ └── test.yml ├── .gitignore ├── .golangci.yml ├── .goreleaser.yml ├── CONTRIBUTING.md ├── GNUmakefile ├── LICENSE ├── README.md ├── docs ├── data-sources │ └── permission.md ├── index.md └── resources │ ├── allowed_origin.md │ ├── application.md │ ├── application_tenant_assignment.md │ ├── auth0_user_source.md │ ├── cognito_user_source.md │ ├── custom_code_user_source.md │ ├── email_provider.md │ ├── feature.md │ ├── firebase_user_source.md │ ├── permission.md │ ├── permission_category.md │ ├── plan.md │ ├── plan_feature.md │ ├── redirect_uri.md │ ├── role.md │ ├── secret.md │ ├── tenant.md │ ├── user.md │ ├── webhook.md │ └── workspace.md ├── examples ├── README.md ├── basic │ └── main.tf ├── data-sources │ └── frontegg_permission │ │ └── data-source.tf ├── provider │ ├── .terraform.lock.hcl │ └── provider.tf └── resources │ ├── frontegg_allowed_origin │ └── resource.tf │ ├── frontegg_application │ └── resource.tf │ ├── frontegg_application_tenant_assignment │ └── resource.tf │ ├── frontegg_auth0_user_source │ └── resource.tf │ ├── frontegg_cognito_user_source │ └── resource.tf │ ├── frontegg_custom_code_user_source │ └── resource.tf │ ├── frontegg_email_providers │ └── resource.tf │ ├── frontegg_firebase_user_source │ └── resource.tf │ ├── frontegg_permission │ └── resource.tf │ ├── frontegg_permission_category │ └── resource.tf │ ├── frontegg_plan │ └── resource.tf │ ├── frontegg_role │ └── resource.tf │ ├── frontegg_webhook │ └── resource.tf │ └── frontegg_workspace │ └── resource.tf ├── go.mod ├── go.sum ├── internal └── restclient │ ├── clientholder.go │ └── restclient.go ├── main.go ├── provider ├── data_source_frontegg_permission.go ├── provider.go ├── provider_test.go ├── resource_frontegg_allowed_origin.go ├── resource_frontegg_application.go ├── resource_frontegg_application_tenant_assignment.go ├── resource_frontegg_auth0_user_source.go ├── resource_frontegg_cognito_user_source.go ├── resource_frontegg_custom_code_user_source.go ├── resource_frontegg_email_providers.go ├── resource_frontegg_feature.go ├── resource_frontegg_firebase_user_source.go ├── resource_frontegg_permission.go ├── resource_frontegg_permission_category.go ├── resource_frontegg_plan.go ├── resource_frontegg_plan_feature.go ├── resource_frontegg_redirect_uri.go ├── resource_frontegg_role.go ├── resource_frontegg_secret.go ├── resource_frontegg_tenant.go ├── resource_frontegg_user.go ├── resource_frontegg_webhook.go ├── resource_frontegg_workspace.go ├── user_source_utils.go ├── util.go └── validators │ └── validator_email_providers.go ├── templates └── index.md.tmpl └── tools └── tools.go /.cursor/rules/make-commands.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: Make commands for provider development 3 | globs: 4 | alwaysApply: true 5 | --- 6 | 7 | # Make Commands 8 | 9 | - `make install` - Build and install provider to local Terraform plugins directory 10 | - `make test` - Run acceptance tests for the provider 11 | - `make capply` - Install provider and run terraform init + apply (relevant only after provider. code change) 12 | - `build install` - Build and install the Terraform provider using makefile 13 | - `build test` - Run provider acceptance tests using makefile -------------------------------------------------------------------------------- /.cursor/rules/terraform-commands.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: Basic Terraform commands with auto-approve 3 | globs: 4 | alwaysApply: true 5 | --- 6 | 7 | # Terraform Commands 8 | 9 | - `terraform apply` - Run terraform apply with auto-approve 10 | - `terraform destroy` - Run terraform destroy with auto-approve 11 | - `tf init` - Initialize Terraform with plugin upgrades 12 | - `tf plan` - Run terraform plan 13 | - `tf capply` - Install provider and apply changes (shortcut to make capply) -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Thank you for opening an issue. Please note that we try to keep the our issue tracker reserved for bug reports and feature requests. For general usage questions, please see: https://www.terraform.io/community.html. 2 | 3 | ### Terraform version 4 | 5 | Run `terraform -v` to show the version. If you are not running the latest 6 | version of Terraform, please upgrade because your issue may have already been 7 | fixed. 8 | 9 | ### Affected resources 10 | 11 | Please list the resources as a list, for example: 12 | 13 | * opc_instance 14 | * opc_storage_volume 15 | 16 | If this issue appears to affect multiple resources, it may be an issue with 17 | Terraform's core, so please mention this. 18 | 19 | ### Terraform configuration files 20 | 21 | ```hcl 22 | # Copy-paste your Terraform configurations here - for large Terraform configs, 23 | # please use a service like Dropbox and share a link to the ZIP file. 24 | ``` 25 | 26 | ### Debug Output 27 | 28 | Please provider a link to a GitHub Gist containing the complete debug output: https://www.terraform.io/docs/internals/debugging.html. Please do NOT paste the debug output in the issue; just paste a link to the Gist. 29 | 30 | ### Panic output 31 | 32 | If Terraform produced a panic, please provide a link to a GitHub Gist containing the output of the `crash.log`. 33 | 34 | ### Expected Behavior 35 | 36 | What should have happened? 37 | 38 | ### Actual Behavior 39 | 40 | What actually happened? 41 | 42 | ### Steps to reproduce 43 | 44 | Please list the steps required to reproduce the issue, for example: 45 | 46 | 1. `terraform apply` 47 | 48 | ### Important factoids 49 | 50 | Are there anything atypical about your accounts that we should know? For example: Running in EC2 Classic? Custom version of OpenStack? Tight ACLs? 51 | 52 | ### References 53 | 54 | Are there any other GitHub issues (open or closed) or pull requests that should 55 | be linked here? For example: 56 | 57 | - GH-1234 58 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: daily 7 | - package-ecosystem: gomod 8 | directory: / 9 | schedule: 10 | interval: daily 11 | -------------------------------------------------------------------------------- /.github/workflows/bump-version.yml: -------------------------------------------------------------------------------- 1 | name: Bump Version 2 | 3 | on: 4 | pull_request_target: 5 | types: 6 | - opened 7 | - synchronize 8 | 9 | permissions: 10 | contents: write 11 | 12 | jobs: 13 | update_version: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v4 18 | with: 19 | fetch-depth: 0 20 | 21 | - name: Read and bump version in GNUmakefile 22 | run: | 23 | # Read current version from GNUmakefile 24 | CURRENT_VERSION=$(grep -E "^VERSION\s*=" GNUmakefile | awk -F '=' '{print $2}' | xargs) 25 | echo "Current version: $CURRENT_VERSION" 26 | 27 | # Increment the patch version 28 | IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT_VERSION" 29 | PATCH=$((PATCH + 1)) 30 | NEW_VERSION="$MAJOR.$MINOR.$PATCH" 31 | echo "Bumping version to: $NEW_VERSION" 32 | 33 | # Update the GNUmakefile with the new version 34 | sed -i "s/^VERSION\s*=.*/VERSION = $NEW_VERSION/" GNUmakefile 35 | 36 | echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_ENV 37 | echo "Bumping version to: $NEW_VERSION" 38 | 39 | - name: Commit and push changes 40 | run: | 41 | # Check out the correct branch 42 | git branch 43 | git checkout ${{ github.event.pull_request.head.ref }} # Checkout the PR branch 44 | echo "Checked out branch: ${{ github.event.pull_request.head.ref }}" 45 | 46 | # Configure Git 47 | git config user.name "GitHub Actions" 48 | git config user.email "actions@github.com" 49 | # Commit and push changes 50 | git commit -am "Bump version to ${{ env.NEW_VERSION }}" 51 | git push origin ${{ github.event.pull_request.head.ref }} 52 | -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yaml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - main 7 | pull_request: 8 | 9 | permissions: 10 | contents: read 11 | pull-requests: read 12 | 13 | jobs: 14 | golangci: 15 | name: lint 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: actions/setup-go@v4 20 | with: 21 | go-version: "1.21.11" 22 | cache: false 23 | - name: golangci-lint 24 | uses: golangci/golangci-lint-action@v3 25 | with: 26 | # Require: The version of golangci-lint to use. 27 | # When `install-mode` is `binary` (default) the value can be v1.2 or v1.2.3 or `latest` to use the latest version. 28 | # When `install-mode` is `goinstall` the value can be v1.2.3, `latest`, or the hash of a commit. 29 | version: v1.59.1 30 | 31 | # Optional: working directory, useful for monorepos 32 | # working-directory: somedir 33 | 34 | # Optional: golangci-lint command line arguments. 35 | # 36 | # Note: By default, the `.golangci.yml` file should be at the root of the repository. 37 | # The location of the configuration file can be changed by using `--config=` 38 | # args: --timeout=30m --config=/my/path/.golangci.yml --issues-exit-code=0 39 | args: --timeout=5m 40 | 41 | # Optional: show only new issues if it's a pull request. The default value is `false`. 42 | # only-new-issues: true 43 | 44 | # Optional: if set to true, then all caching functionality will be completely disabled, 45 | # takes precedence over all other caching options. 46 | # skip-cache: true 47 | 48 | # Optional: if set to true, then the action won't cache or restore ~/go/pkg. 49 | # skip-pkg-cache: true 50 | 51 | # Optional: if set to true, then the action won't cache or restore ~/.cache/go-build. 52 | # skip-build-cache: true 53 | 54 | # Optional: The mode to install golangci-lint. It can be 'binary' or 'goinstall'. 55 | # install-mode: "goinstall" 56 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # This GitHub action can publish assets for release when a tag is created. 2 | # Currently its setup to run on any tag that matches the pattern "v*" (ie. v0.1.0). 3 | # 4 | # This uses an action (hashicorp/ghaction-import-gpg) that assumes you set your 5 | # private key in the `GPG_PRIVATE_KEY` secret and passphrase in the `PASSPHRASE` 6 | # secret. If you would rather own your own GPG handling, please fork this action 7 | # or use an alternative one for key handling. 8 | # 9 | # You will need to pass the `--batch` flag to `gpg` in your signing step 10 | # in `goreleaser` to indicate this is being used in a non-interactive mode. 11 | # 12 | name: release 13 | on: 14 | push: 15 | tags: 16 | - "v*" 17 | 18 | permissions: 19 | contents: write 20 | 21 | jobs: 22 | goreleaser: 23 | runs-on: ubuntu-latest 24 | steps: 25 | - name: Checkout 26 | uses: actions/checkout@v4 27 | with: 28 | # Allow goreleaser to access older tag information. 29 | fetch-depth: 0 30 | - name: Unshallow 31 | run: git fetch --prune 32 | - name: Set up Go 33 | uses: actions/setup-go@v4 34 | with: 35 | go-version-file: "go.mod" 36 | cache: true 37 | - name: Import GPG key 38 | uses: crazy-max/ghaction-import-gpg@v6 39 | id: import_gpg 40 | with: 41 | gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} 42 | passphrase: ${{ secrets.PASSPHRASE }} 43 | - name: Run GoReleaser 44 | uses: goreleaser/goreleaser-action@v5.1.0 45 | with: 46 | version: '~> v1' 47 | args: release --clean 48 | env: 49 | GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} 50 | # GitHub sets this automatically 51 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 52 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | on: 3 | pull_request: 4 | paths-ignore: 5 | - README.md 6 | push: 7 | branches: 8 | - master 9 | - main 10 | paths-ignore: 11 | - README.md 12 | schedule: 13 | - cron: "0 13 * * *" 14 | jobs: 15 | test: 16 | runs-on: ubuntu-latest 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | terraform: 21 | - "1.0.3" 22 | steps: 23 | - uses: actions/checkout@v4 24 | - uses: actions/setup-go@v4 25 | with: 26 | go-version: "1.21.11" 27 | - run: go mod download 28 | - run: go test -v -cover ./provider/ 29 | env: 30 | TF_ACC: "1" 31 | TF_ACC_TERRAFORM_VERSION: ${{ matrix.terraform }} 32 | FRONTEGG_API_KEY: ${{ secrets.FRONTEGG_API_KEY }} 33 | FRONTEGG_CLIENT_ID: ${{ secrets.FRONTEGG_CLIENT_ID }} 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.dll 2 | *.exe 3 | .DS_Store 4 | example.tf 5 | terraform.tfplan 6 | terraform.tfstate 7 | main.tf 8 | bin/ 9 | dist/ 10 | modules-dev/ 11 | /pkg/ 12 | website/.vagrant 13 | website/.bundle 14 | website/build 15 | website/node_modules 16 | .vagrant/ 17 | *.backup 18 | ./*.tfstate 19 | .terraform/ 20 | *.log 21 | *.bak 22 | *~ 23 | .*.swp 24 | .idea 25 | *.iml 26 | *.test 27 | *.iml 28 | .terraform.lock.hcl 29 | .dccache 30 | .vscode/ 31 | 32 | website/vendor 33 | 34 | # Test exclusions 35 | !command/test-fixtures/**/*.tfstate 36 | !command/test-fixtures/**/.terraform/ 37 | 38 | # Keep windows files with windows line endings 39 | *.winfile eol=crlf 40 | 41 | #local 42 | local -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | # Visit https://golangci-lint.run/ for usage documentation 2 | # and information on other useful linters 3 | issues: 4 | max-same-issues: 0 5 | 6 | linters: 7 | disable-all: true 8 | # TODO: enable all linters and fix all issues 9 | enable: 10 | - durationcheck 11 | - errcheck 12 | - exportloopref 13 | # - forcetypeassert 14 | - godot 15 | - gofmt 16 | - gosimple 17 | - ineffassign 18 | - makezero 19 | - misspell 20 | - nilerr 21 | - predeclared 22 | - staticcheck 23 | - tenv 24 | - unconvert 25 | - unparam 26 | - unused 27 | - govet 28 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | # Visit https://goreleaser.com for documentation on how to customize this 2 | # behavior. 3 | before: 4 | hooks: 5 | # this is just an example and not a requirement for provider building/publishing 6 | - go mod tidy 7 | 8 | builds: 9 | - env: 10 | # goreleaser does not work with CGO, it could also complicate 11 | # usage by users in CI/CD systems like Terraform Cloud where 12 | # they are unable to install libraries. 13 | - CGO_ENABLED=0 14 | mod_timestamp: "{{ .CommitTimestamp }}" 15 | flags: 16 | - -trimpath 17 | ldflags: 18 | - "-s -w -X main.version={{.Version}} -X main.commit={{.Commit}}" 19 | goos: 20 | - freebsd 21 | - windows 22 | - linux 23 | - darwin 24 | goarch: 25 | - amd64 26 | - "386" 27 | - arm 28 | - arm64 29 | ignore: 30 | - goos: darwin 31 | goarch: "386" 32 | - goos: windows 33 | goarch: "arm64" 34 | binary: "{{ .ProjectName }}_v{{ .Version }}" 35 | archives: 36 | - format: zip 37 | name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" 38 | checksum: 39 | name_template: "{{ .ProjectName }}_{{ .Version }}_SHA256SUMS" 40 | algorithm: sha256 41 | signs: 42 | - artifacts: checksum 43 | args: 44 | # if you are using this in a GitHub action or some other automated pipeline, you 45 | # need to pass the batch flag to indicate its not interactive. 46 | - "--batch" 47 | - "--local-user" 48 | - "{{ .Env.GPG_FINGERPRINT }}" # set this environment variable for your signing key 49 | - "--output" 50 | - "${signature}" 51 | - "--detach-sign" 52 | - "${artifact}" 53 | release: 54 | # If you want to manually examine the release before its live, uncomment this line: 55 | # draft: true 56 | changelog: 57 | skip: false 58 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Developing the provider 4 | 5 | If you wish to work on the provider, you'll first need 6 | [Go](http://www.golang.org) installed on your machine (see 7 | [Requirements](#requirements) above). 8 | 9 | **pay attention to install platform compatible version** 10 | 11 | `git clone` this repository and `cd` into its directory. 12 | To compile the provider, run `make install`. This will build the provider and 13 | put the provider binary in the correct location within `~/.terraform.d` so that 14 | Terraform can find the plugin. 15 | 16 | To generate or update documentation, run `go generate`. 17 | 18 | To run the full suite of acceptance tests, run `make testacc`. 19 | 20 | *Note:* Acceptance tests create real resources, and often cost money to run. 21 | 22 | ```sh 23 | $ make testacc 24 | ``` 25 | 26 | ## Debugging 27 | Terraform has detailed logs that you can enable by setting the `TF_LOG` environment variable to any value. Enabling this setting causes detailed logs to appear on `stderr`. 28 | 29 | ### Iteration cycle 30 | 31 | This provider does not yet have an acceptance test suite. Instead, developers 32 | test changes manually by editing the basic example and running `terraform 33 | apply`. Following are the commands to run to efficiently manually test your 34 | changes. 35 | 36 | ``` 37 | export FRONTEGG_CLIENT_ID= 38 | export FRONTEGG_SECRET_KEY= 39 | cd examples/basic 40 | # Recompile the provider, reinitialize Terraform, then apply the current state. 41 | # Run repeatedly as you make changes to the provider or to the basic example. 42 | (cd ../.. && make install) && rm .terraform.lock.hcl && terraform init && terraform apply -auto-approve 43 | ``` 44 | 45 | ### Adding dependencies 46 | 47 | This provider uses [Go modules](https://github.com/golang/go/wiki/Modules). 48 | Please see the Go documentation for the most up to date information about using 49 | Go modules. 50 | 51 | To add a new dependency: 52 | 53 | ``` 54 | go get github.com/author/dependency 55 | go mod tidy 56 | ``` 57 | 58 | Then commit the changes to `go.mod` and `go.sum`. 59 | 60 | [Frontegg]: https://frontegg.com 61 | -------------------------------------------------------------------------------- /GNUmakefile: -------------------------------------------------------------------------------- 1 | PLATFORM ?= $(shell go env GOOS)_$(shell go env GOARCH) 2 | VERSION = 1.0.23 3 | 4 | default: testacc 5 | 6 | install: 7 | @go build -o ~/.terraform.d/plugins/registry.terraform.io/frontegg/frontegg/$(VERSION)/$(PLATFORM)/terraform-provider-frontegg 8 | @rm .terraform.lock.hcl 9 | 10 | .PHONY: testacc 11 | testacc: 12 | @TF_ACC=1 go test ./... -v $(TESTARGS) -timeout 120m 13 | 14 | .PHONY: lint 15 | lint: 16 | @docker run --rm -v $(CURDIR):/app -w /app golangci/golangci-lint:v1.59.1 golangci-lint run --timeout=5m 17 | 18 | lint-fix: 19 | @docker run --rm -v $(CURDIR):/app -w /app golangci/golangci-lint:v1.59.1 golangci-lint run --fix --timeout=5m 20 | 21 | capply: install 22 | @terraform init 23 | @terraform apply 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Terraform Provider for Frontegg 2 | 3 | This repository contains a Terraform provider for the [Frontegg] user management 4 | platform. 5 | 6 | ## Requirements 7 | 8 | - [Terraform](https://www.terraform.io/downloads.html) >= 1.0.3 9 | - [Go](https://golang.org/doc/install) >= 1.20 10 | **pay attention to install platform compatible version** 11 | 12 | ## Using the provider 13 | 14 | See the Terraform Registry: . 15 | 16 | ## Importing existing resources 17 | 18 | ### Workspaces 19 | 20 | To import an existing workspace, first add a shim resource definition to your 21 | Terraform project: 22 | 23 | ```tf 24 | # main.tf 25 | resource "frontegg_workspace" "example" {} 26 | ``` 27 | 28 | Then run `terraform import`, specifying the address of the resource you declared 29 | above (`frontegg_workspace.example`) and your workspace ID (i.e., your API 30 | client ID): 31 | 32 | ```shell 33 | $ terraform import frontegg_workspace.example 65e2d503-c187-4d55-8ba5-816bd4a15f96 34 | frontegg_workspace.example: Importing from ID "65e2d503-c187-4d55-8ba5-816bd4a15f96"... 35 | frontegg_workspace.example: Import prepared! 36 | Prepared frontegg_workspace for import 37 | frontegg_workspace.example: Refreshing state... [id=65e2d503-c187-4d55-8ba5-816bd4a15f96] 38 | 39 | Import successful! 40 | 41 | The resources that were imported are shown above. These resources are now in 42 | your Terraform state and will henceforth be managed by Terraform. 43 | ``` 44 | 45 | Next, run `terraform state show` to show the configuration values Terraform has 46 | imported: 47 | 48 | ```shell 49 | $ terraform state show frontegg_workspace.example 50 | # frontegg_workspace.example: 51 | resource "frontegg_workspace" "example" { 52 | allowed_origins = [ 53 | "https://yourcompany.com", 54 | ] 55 | backend_stack = "Python" 56 | country = "US" 57 | frontegg_domain = "yourcompany.frontegg.com" 58 | # ... 59 | } 60 | ``` 61 | 62 | Finally, you can copy that output back into your `main.tf` file (or equivalent). 63 | Beware that you may need to manually remove some output properties from the 64 | resource, like `jwt_public_key`. 65 | 66 | You should verify that `terraform plan` reports no diffs. 67 | 68 | ### Roles, permissions, and permission categories 69 | 70 | The procedure is the same as above, except that it is tricky to discover the ID 71 | for the role, permission, or permission category. IDs for these objects are 72 | UUIDs. 73 | 74 | You can either query the [Frontegg API](https://docs.frontegg.com/reference) 75 | yourself to find these IDs, or you can use your browser's developer tools to 76 | sniff the IDs out of the network requests as you browse the [Frontegg 77 | Portal](https://portal.frontegg.com). 78 | 79 | ### Contact us 80 | 81 | Please note that this provider may not offer full support for all Frontegg capabilities. If you require assistance or support for a specific functionality, please contact us at support@frontegg.com. 82 | -------------------------------------------------------------------------------- /docs/data-sources/permission.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "frontegg_permission Data Source - terraform-provider-frontegg" 4 | subcategory: "" 5 | description: |- 6 | 7 | --- 8 | 9 | # frontegg_permission (Data Source) 10 | 11 | 12 | 13 | ## Example Usage 14 | 15 | ```terraform 16 | data "frontegg_permission" "read_users" { 17 | key = "fe.secure.read.users" 18 | } 19 | 20 | output "permission_id" { 21 | value = data.frontegg_permission.read_users.id 22 | } 23 | ``` 24 | 25 | 26 | ## Schema 27 | 28 | ### Required 29 | 30 | - `key` (String) A human-readable identifier for the permission. 31 | 32 | ### Read-Only 33 | 34 | - `category_id` (String) The identifier of the category to which this permission belongs. 35 | - `created_at` (String) The timestamp at which the permission was created. 36 | - `description` (String) A human-readable description of the permission. 37 | - `id` (String) The ID of this resource. 38 | - `name` (String) A human-readable name for the permission. 39 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: "" 3 | page_title: "Provider: Frontegg" 4 | description: |- 5 | The Frontegg provider provides resources to interact with the Frontegg user 6 | management platform. 7 | --- 8 | 9 | # Frontegg Provider 10 | 11 | The Frontegg provider provides resources to interact with the [Frontegg] user 12 | management platform. 13 | 14 | The provider works with only one workspace at a time. To provision multiple 15 | workspaces, you will need to configure multiple copies of the provider. 16 | 17 | Note that the client ID and secret key are *not* the client ID and secret key 18 | that appear in "Workspace Settings". You need to generate a workspace API key 19 | and secret specifically for the Terraform provider's use in the administration 20 | portal: 21 | 22 | ![API key generation example](https://user-images.githubusercontent.com/882976/132739276-bc72aa75-8c30-452c-b929-85a8d7ffa4d0.png) 23 | 24 | In order to interact with specific environment management capabilities you can 25 | provide environment ID, that is displayed on environment settings at the Frontegg 26 | portal. To configure multiple environments you will need to configure multiple 27 | copies of provider with one environment ID per each. If no environment ID was 28 | provided the configuration will be cross-environments. 29 | 30 | ## Example Usage 31 | 32 | ```terraform 33 | provider "frontegg" { 34 | client_id = "[your-personal-token-client-id]" 35 | secret_key = "[your-personal-token-api-key]" 36 | environment_id = "[your-environment-id]" 37 | 38 | api_base_url = "https://api.frontegg.com" 39 | portal_base_url = "https://frontegg-portal.frontegg.com" 40 | } 41 | ``` 42 | 43 | 44 | ## Schema 45 | 46 | ### Required 47 | 48 | - `client_id` (String) The client ID for a Frontegg portal API key. 49 | - `secret_key` (String, Sensitive) The corresponding secret key for the API key. 50 | 51 | ### Optional 52 | 53 | - `api_base_url` (String) The Frontegg api url. Override to change region. Defaults to EU url. 54 | - `environment_id` (String, Sensitive) The client ID from environment settings. 55 | - `portal_base_url` (String) The Frontegg portal url. Override to change region. Defaults to EU url. 56 | 57 | [Frontegg]: https://frontegg.com 58 | -------------------------------------------------------------------------------- /docs/resources/allowed_origin.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "frontegg_allowed_origin Resource - terraform-provider-frontegg" 4 | subcategory: "" 5 | description: |- 6 | Configures a Frontegg allowed origin. 7 | --- 8 | 9 | # frontegg_allowed_origin (Resource) 10 | 11 | Configures a Frontegg allowed origin. 12 | 13 | 14 | 15 | 16 | ## Schema 17 | 18 | ### Required 19 | 20 | - `allowed_origin` (String) The allowed origin URI. 21 | 22 | ### Read-Only 23 | 24 | - `id` (String) The ID of this resource. 25 | -------------------------------------------------------------------------------- /docs/resources/application.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "frontegg_application Resource - terraform-provider-frontegg" 4 | subcategory: "" 5 | description: |- 6 | Configures a Frontegg application. 7 | --- 8 | 9 | # frontegg_application (Resource) 10 | 11 | Configures a Frontegg application. 12 | 13 | 14 | 15 | 16 | ## Schema 17 | 18 | ### Required 19 | 20 | - `app_url` (String) The URL of the application. 21 | - `login_url` (String) The login URL of the application. 22 | - `name` (String) The name of the application. 23 | 24 | ### Optional 25 | 26 | - `access_type` (String) The access type of the application. 27 | - `description` (String) A description of the application. 28 | - `frontend_stack` (String) The frontend stack used by the application. 29 | - `is_active` (Boolean) Whether the application is active. 30 | - `is_default` (Boolean) Whether this is the default application. 31 | - `logo_url` (String) The URL of the application's logo. 32 | - `type` (String) The type of the application. 33 | 34 | ### Read-Only 35 | 36 | - `client_secret` (String, Sensitive) The client secret of the application. 37 | - `created_at` (String) When the application was created. 38 | - `id` (String) The ID of this resource. 39 | - `integration_finished_at` (String) When the integration was finished. 40 | - `shared_secret` (String, Sensitive) The shared secret of the application. 41 | - `updated_at` (String) When the application was last updated. 42 | -------------------------------------------------------------------------------- /docs/resources/application_tenant_assignment.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "frontegg_application_tenant_assignment Resource - terraform-provider-frontegg" 4 | subcategory: "" 5 | description: |- 6 | Configures a Frontegg application tenant assignment. 7 | --- 8 | 9 | # frontegg_application_tenant_assignment (Resource) 10 | 11 | Configures a Frontegg application tenant assignment. 12 | 13 | 14 | 15 | 16 | ## Schema 17 | 18 | ### Required 19 | 20 | - `app_id` (String) The ID of the application. 21 | - `tenant_id` (String) The ID of the tenant. 22 | 23 | ### Read-Only 24 | 25 | - `id` (String) The ID of this resource. 26 | -------------------------------------------------------------------------------- /docs/resources/auth0_user_source.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "frontegg_auth0_user_source Resource - terraform-provider-frontegg" 4 | subcategory: "" 5 | description: |- 6 | Configures a Frontegg Auth0 user source. 7 | --- 8 | 9 | # frontegg_auth0_user_source (Resource) 10 | 11 | Configures a Frontegg Auth0 user source. 12 | 13 | 14 | 15 | 16 | ## Schema 17 | 18 | ### Required 19 | 20 | - `client_id` (String) The Auth0 application client ID. 21 | - `domain` (String) The Auth0 domain. 22 | - `index` (Number) The user source index. 23 | - `name` (String) The user source name. 24 | - `secret` (String, Sensitive) The Auth0 application secret. 25 | - `tenant_resolver_type` (String) The tenant resolver type (dynamic, static, or new). 26 | 27 | ### Optional 28 | 29 | - `app_ids` (Set of String) The application IDs to assign to this user source. 30 | - `description` (String) The user source description. 31 | - `is_migrated` (Boolean) Whether to migrate the users. 32 | - `sync_on_login` (Boolean) Whether to sync user profile attributes on each login. 33 | - `tenant_id` (String) The tenant ID for static tenant resolver type. 34 | - `tenant_id_field_name` (String) The attribute name from which the tenant ID would be taken for dynamic tenant resolver type. 35 | 36 | ### Read-Only 37 | 38 | - `id` (String) The ID of this resource. 39 | -------------------------------------------------------------------------------- /docs/resources/cognito_user_source.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "frontegg_cognito_user_source Resource - terraform-provider-frontegg" 4 | subcategory: "" 5 | description: |- 6 | Configures a Frontegg Cognito user source. 7 | --- 8 | 9 | # frontegg_cognito_user_source (Resource) 10 | 11 | Configures a Frontegg Cognito user source. 12 | 13 | 14 | 15 | 16 | ## Schema 17 | 18 | ### Required 19 | 20 | - `access_key_id` (String) The access key of the AWS account. 21 | - `client_id` (String) The Cognito app client ID. 22 | - `index` (Number) The user source index. 23 | - `name` (String) The user source name. 24 | - `region` (String) The AWS region of the Cognito user pool. 25 | - `secret_access_key` (String, Sensitive) The secret of the AWS account. 26 | - `tenant_resolver_type` (String) The tenant resolver type (dynamic, static, or new). 27 | - `user_pool_id` (String) The ID of the Cognito user pool. 28 | 29 | ### Optional 30 | 31 | - `app_ids` (Set of String) The application IDs to assign to this user source. 32 | - `client_secret` (String, Sensitive) The Cognito application client secret, required if the app client is configured with a client secret. 33 | - `description` (String) The user source description. 34 | - `is_migrated` (Boolean) Whether to migrate the users. 35 | - `sync_on_login` (Boolean) Whether to sync user profile attributes on each login. 36 | - `tenant_id` (String) The tenant ID for static tenant resolver type. 37 | - `tenant_id_field_name` (String) The attribute name from which the tenant ID would be taken for dynamic tenant resolver type. 38 | 39 | ### Read-Only 40 | 41 | - `id` (String) The ID of this resource. 42 | -------------------------------------------------------------------------------- /docs/resources/custom_code_user_source.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "frontegg_custom_code_user_source Resource - terraform-provider-frontegg" 4 | subcategory: "" 5 | description: |- 6 | Configures a Frontegg Custom-Code user source. 7 | --- 8 | 9 | # frontegg_custom_code_user_source (Resource) 10 | 11 | Configures a Frontegg Custom-Code user source. 12 | 13 | 14 | 15 | 16 | ## Schema 17 | 18 | ### Required 19 | 20 | - `code_payload` (String) The custom code that will be executed to authenticate the user. 21 | - `index` (Number) The user source index. 22 | - `name` (String) The user source name. 23 | - `tenant_resolver_type` (String) The tenant resolver type (dynamic, static, or new). 24 | 25 | ### Optional 26 | 27 | - `app_ids` (Set of String) The application IDs to assign to this user source. 28 | - `description` (String) The user source description. 29 | - `get_user_code_payload` (String) The custom code that will be executed to get user details. 30 | - `is_migrated` (Boolean) Whether to migrate the users. 31 | - `sync_on_login` (Boolean) Whether to sync user profile attributes on each login. 32 | - `tenant_id` (String) The tenant ID for static tenant resolver type. 33 | - `tenant_id_field_name` (String) The attribute name from which the tenant ID would be taken for dynamic tenant resolver type. 34 | 35 | ### Read-Only 36 | 37 | - `id` (String) The ID of this resource. 38 | -------------------------------------------------------------------------------- /docs/resources/email_provider.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "frontegg_email_provider Resource - terraform-provider-frontegg" 4 | subcategory: "" 5 | description: |- 6 | Configures a Frontegg Email provider. 7 | --- 8 | 9 | # frontegg_email_provider (Resource) 10 | 11 | Configures a Frontegg Email provider. 12 | 13 | 14 | 15 | 16 | ## Schema 17 | 18 | ### Required 19 | 20 | - `provider_name` (String) Name of the email provider (If the provider is changed, the old provider's configuration will be deleted). 21 | - `secret` (String) A secret to be included with the event. 22 | 23 | ### Optional 24 | 25 | - `domain` (String) Required for Mailgun (required only for Mailgun). 26 | - `provider_id` (String) Provider ID (required only for AWS SES). 27 | - `region` (String) Required for AWS SES or Mailgun. 28 | 29 | ### Read-Only 30 | 31 | - `created_at` (String) The timestamp at which the permission was created. 32 | - `id` (String) The ID of this resource. 33 | - `updated_at` (String) The timestamp at which the permission was updated. 34 | -------------------------------------------------------------------------------- /docs/resources/feature.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "frontegg_feature Resource - terraform-provider-frontegg" 4 | subcategory: "" 5 | description: |- 6 | Configures a Frontegg feature. 7 | --- 8 | 9 | # frontegg_feature (Resource) 10 | 11 | Configures a Frontegg feature. 12 | 13 | 14 | 15 | 16 | ## Schema 17 | 18 | ### Required 19 | 20 | - `key` (String) The key of the feature. 21 | - `name` (String) The name of the feature. 22 | 23 | ### Optional 24 | 25 | - `description` (String) A description of the feature. 26 | - `metadata` (String) Metadata for the feature. 27 | - `permissions` (Block List) The permissions for the feature. (see [below for nested schema](#nestedblock--permissions)) 28 | 29 | ### Read-Only 30 | 31 | - `created_at` (String) When the feature was created. 32 | - `id` (String) The ID of the feature (UUID). 33 | - `updated_at` (String) When the feature was last updated. 34 | 35 | 36 | ### Nested Schema for `permissions` 37 | 38 | Required: 39 | 40 | - `permission_id` (String) The ID of the permission 41 | - `permission_key` (String) The key of the permission 42 | -------------------------------------------------------------------------------- /docs/resources/firebase_user_source.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "frontegg_firebase_user_source Resource - terraform-provider-frontegg" 4 | subcategory: "" 5 | description: |- 6 | Configures a Frontegg Firebase user source. 7 | --- 8 | 9 | # frontegg_firebase_user_source (Resource) 10 | 11 | Configures a Frontegg Firebase user source. 12 | 13 | 14 | 15 | 16 | ## Schema 17 | 18 | ### Required 19 | 20 | - `api_key` (String) The Firebase Web API Key. 21 | - `client_email` (String) Firebase service account client email. 22 | - `client_id` (String) Firebase service account client ID. 23 | - `index` (Number) The user source index. 24 | - `name` (String) The user source name. 25 | - `private_key` (String, Sensitive) Firebase service account private key. 26 | - `private_key_id` (String) Firebase service account private key ID. 27 | - `project_id` (String) Firebase project ID. 28 | - `service_account_type` (String) Firebase service account type. 29 | - `tenant_resolver_type` (String) The tenant resolver type (dynamic, static, or new). 30 | - `universe_domain` (String) Firebase service account universe domain. 31 | 32 | ### Optional 33 | 34 | - `app_ids` (Set of String) The application IDs to assign to this user source. 35 | - `auth_provider_x509_cert_url` (String) Firebase service account auth provider x509 cert URL. 36 | - `auth_uri` (String) Firebase service account auth URI. 37 | - `client_x509_cert_url` (String) Firebase service account client x509 cert URL. 38 | - `description` (String) The user source description. 39 | - `is_migrated` (Boolean) Whether to migrate the users. 40 | - `sync_on_login` (Boolean) Whether to sync user profile attributes on each login. 41 | - `tenant_id` (String) The tenant ID for static tenant resolver type. 42 | - `tenant_id_field_name` (String) The attribute name from which the tenant ID would be taken for dynamic tenant resolver type. 43 | - `token_uri` (String) Firebase service account token URI. 44 | 45 | ### Read-Only 46 | 47 | - `id` (String) The ID of this resource. 48 | -------------------------------------------------------------------------------- /docs/resources/permission.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "frontegg_permission Resource - terraform-provider-frontegg" 4 | subcategory: "" 5 | description: |- 6 | Configures a Frontegg permission. 7 | --- 8 | 9 | # frontegg_permission (Resource) 10 | 11 | Configures a Frontegg permission. 12 | 13 | ## Example Usage 14 | 15 | ```terraform 16 | data "frontegg_permission" "read_users" { 17 | key = "fe.secure.read.users" 18 | } 19 | ``` 20 | 21 | 22 | ## Schema 23 | 24 | ### Required 25 | 26 | - `category_id` (String) The identifier of the category to which this permission belongs. 27 | - `description` (String) A human-readable description of the permission. 28 | - `key` (String) A human-readable identifier for the permission. 29 | - `name` (String) A human-readable name for the permission. 30 | 31 | ### Read-Only 32 | 33 | - `created_at` (String) The timestamp at which the permission was created. 34 | - `id` (String) The ID of this resource. 35 | -------------------------------------------------------------------------------- /docs/resources/permission_category.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "frontegg_permission_category Resource - terraform-provider-frontegg" 4 | subcategory: "" 5 | description: |- 6 | Configures a Frontegg permission category. 7 | --- 8 | 9 | # frontegg_permission_category (Resource) 10 | 11 | Configures a Frontegg permission category. 12 | 13 | ## Example Usage 14 | 15 | ```terraform 16 | resource "frontegg_permission_category" "example" { 17 | name = "Example" 18 | description = "An example of a permission category" 19 | } 20 | ``` 21 | 22 | 23 | ## Schema 24 | 25 | ### Required 26 | 27 | - `description` (String) A human-readable description of the permission category. 28 | - `name` (String) A human-readable name for the permission category. 29 | 30 | ### Read-Only 31 | 32 | - `created_at` (String) The timestamp at which the permission category was created. 33 | - `id` (String) The ID of this resource. 34 | -------------------------------------------------------------------------------- /docs/resources/plan.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "frontegg_plan Resource - terraform-provider-frontegg" 4 | subcategory: "" 5 | description: |- 6 | Configures a Frontegg plan. 7 | --- 8 | 9 | # frontegg_plan (Resource) 10 | 11 | Configures a Frontegg plan. 12 | 13 | 14 | 15 | 16 | ## Schema 17 | 18 | ### Required 19 | 20 | - `name` (String) The name of the plan. 21 | 22 | ### Optional 23 | 24 | - `assign_on_signup` (Boolean) Whether the plan is assigned automatically upon signup. 25 | - `default_time_limitation` (Number) Default time limitation in days for auto-assigned plans. 26 | - `default_treatment` (String) The default treatment for the plan. 27 | - `description` (String) A description of the plan. 28 | - `feature_keys` (List of String) Array of feature keys to be applied on the plan. 29 | - `rules` (List of Map of String) Set of conditions targeting the plan. 30 | 31 | ### Read-Only 32 | 33 | - `created_at` (String) When the plan was created. 34 | - `id` (String) The ID of this resource. 35 | - `updated_at` (String) When the plan was last updated. 36 | - `vendor_id` (String) The vendor ID for the plan. 37 | -------------------------------------------------------------------------------- /docs/resources/plan_feature.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "frontegg_plan_feature Resource - terraform-provider-frontegg" 4 | subcategory: "" 5 | description: |- 6 | Links features to a Frontegg plan. 7 | --- 8 | 9 | # frontegg_plan_feature (Resource) 10 | 11 | Links features to a Frontegg plan. 12 | 13 | 14 | 15 | 16 | ## Schema 17 | 18 | ### Required 19 | 20 | - `feature_ids` (Set of String) The IDs of the features to link to the plan. 21 | - `plan_id` (String) The ID of the plan. 22 | 23 | ### Read-Only 24 | 25 | - `id` (String) The ID of this resource. 26 | -------------------------------------------------------------------------------- /docs/resources/redirect_uri.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "frontegg_redirect_uri Resource - terraform-provider-frontegg" 4 | subcategory: "" 5 | description: |- 6 | Configures a Frontegg Redirect URI. 7 | --- 8 | 9 | # frontegg_redirect_uri (Resource) 10 | 11 | Configures a Frontegg Redirect URI. 12 | 13 | 14 | 15 | 16 | ## Schema 17 | 18 | ### Required 19 | 20 | - `redirect_uri` (String) The redirect URI. 21 | 22 | ### Read-Only 23 | 24 | - `id` (String) The ID of this resource. 25 | - `key` (String) The redirect URI key. 26 | -------------------------------------------------------------------------------- /docs/resources/role.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "frontegg_role Resource - terraform-provider-frontegg" 4 | subcategory: "" 5 | description: |- 6 | Configures a Frontegg role. 7 | --- 8 | 9 | # frontegg_role (Resource) 10 | 11 | Configures a Frontegg role. 12 | 13 | ## Example Usage 14 | 15 | ```terraform 16 | resource "frontegg_role" "example" { 17 | name = "Example" 18 | key = "example" 19 | description = "An example of a role" 20 | default = true 21 | level = 0 22 | permission_ids = [ 23 | resource.frontegg_permission.example.id, 24 | data.frontegg_permission.read_users.id, 25 | ] 26 | } 27 | ``` 28 | 29 | 30 | ## Schema 31 | 32 | ### Required 33 | 34 | - `default` (Boolean) Whether the role should be applied to new users by default. 35 | - `description` (String) A human-readable description of the role. 36 | - `key` (String) A human-readable identifier for the role. 37 | - `level` (Number) The level of the role in the role hierarchy. 38 | - `name` (String) A human-readable name for the role. 39 | - `permission_ids` (Set of String) The IDs of the permissions that the role confers to its members. 40 | 41 | ### Optional 42 | 43 | - `first_user` (Boolean) Whether the role should be applied to the first user in the tenant (new tenants only). 44 | - `tenant_id` (String) The ID of the tenant that owns the role. 45 | 46 | ### Read-Only 47 | 48 | - `created_at` (String) The timestamp at which the role was created. 49 | - `id` (String) The ID of this resource. 50 | - `vendor_id` (String) The ID of the vendor that owns the role. 51 | -------------------------------------------------------------------------------- /docs/resources/secret.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "frontegg_secret Resource - terraform-provider-frontegg" 4 | subcategory: "" 5 | description: |- 6 | Configures a Frontegg secret. 7 | --- 8 | 9 | # frontegg_secret (Resource) 10 | 11 | Configures a Frontegg secret. 12 | 13 | 14 | 15 | 16 | ## Schema 17 | 18 | ### Required 19 | 20 | - `key` (String) The key of the secret. 21 | - `value` (String, Sensitive) The value of the secret. 22 | 23 | ### Read-Only 24 | 25 | - `id` (String) The ID of this resource. 26 | -------------------------------------------------------------------------------- /docs/resources/tenant.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "frontegg_tenant Resource - terraform-provider-frontegg" 4 | subcategory: "" 5 | description: |- 6 | Configures a Frontegg tenant. 7 | --- 8 | 9 | # frontegg_tenant (Resource) 10 | 11 | Configures a Frontegg tenant. 12 | 13 | 14 | 15 | 16 | ## Schema 17 | 18 | ### Required 19 | 20 | - `key` (String) A human-readable identifier for the tenant. 21 | - `name` (String) A human-readable name for the tenant. 22 | 23 | ### Optional 24 | 25 | - `application_uri` (String) The application URI for this tenant. 26 | - `selected_metadata` (Map of String) Metadata to set and manage; will be merged with upstream metadata fields set outside of terraform. 27 | 28 | ### Read-Only 29 | 30 | - `id` (String) The ID of this resource. 31 | -------------------------------------------------------------------------------- /docs/resources/user.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "frontegg_user Resource - terraform-provider-frontegg" 4 | subcategory: "" 5 | description: |- 6 | Configures a Frontegg user. 7 | --- 8 | 9 | # frontegg_user (Resource) 10 | 11 | Configures a Frontegg user. 12 | 13 | 14 | 15 | 16 | ## Schema 17 | 18 | ### Required 19 | 20 | - `email` (String) The user's email address. 21 | - `role_ids` (Set of String) List of the role IDs that the user has in the tenant 22 | - `tenant_id` (String) The tenant ID for this user. 23 | 24 | ### Optional 25 | 26 | - `automatically_verify` (Boolean) Whether the user gets verified upon creation. 27 | - `password` (String, Sensitive) The user's login password. 28 | - `skip_invite_email` (Boolean) Skip sending the invite email. If true, user is automatically verified on creation. 29 | - `superuser` (Boolean) Whether the user is a super user. 30 | 31 | ### Read-Only 32 | 33 | - `id` (String) The ID of this resource. 34 | -------------------------------------------------------------------------------- /docs/resources/webhook.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "frontegg_webhook Resource - terraform-provider-frontegg" 4 | subcategory: "" 5 | description: |- 6 | Configures a Frontegg webhook. 7 | --- 8 | 9 | # frontegg_webhook (Resource) 10 | 11 | Configures a Frontegg webhook. 12 | 13 | ## Example Usage 14 | 15 | ```terraform 16 | resource "frontegg_webhook" "example" { 17 | enabled = true 18 | name = "Example webhook" 19 | description = "An example of a webhook" 20 | url = "https://example.com/webhook" 21 | secret = "example-secret" 22 | events = [ 23 | "frontegg.user.authenticated" 24 | ] 25 | } 26 | ``` 27 | 28 | 29 | ## Schema 30 | 31 | ### Required 32 | 33 | - `description` (String) A human-readable description of the webhook. 34 | - `enabled` (Boolean) Whether the webhook is enabled. 35 | - `events` (Set of String) The names of the events to subscribe to. 36 | - `secret` (String) A secret to include with the event. 37 | - `url` (String) The URL to send events to. 38 | 39 | ### Optional 40 | 41 | - `name` (String) A human-readable name for the webhook. 42 | 43 | ### Read-Only 44 | 45 | - `created_at` (String) The timestamp at which the webhook was created. 46 | - `id` (String) The ID of this resource. 47 | - `type` (String) The type of the webhook. 48 | - `vendor_id` (String) The ID of the vendor that owns the webhook. 49 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | This directory contains examples that are mostly used for documentation, but can also be run/tested manually via the Terraform CLI. 4 | 5 | The document generation tool looks for files in the following locations by default. All other *.tf files besides the ones mentioned below are ignored by the documentation tool. This is useful for creating examples that can run and/or ar testable even if some parts are not relevant for the documentation. 6 | 7 | * **provider/provider.tf** example file for the provider index page 8 | * **data-sources//data-source.tf** example file for the named data source page 9 | * **resources//resource.tf** example file for the named data source page 10 | -------------------------------------------------------------------------------- /examples/basic/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | frontegg = { 4 | source = "frontegg/frontegg" 5 | } 6 | } 7 | } 8 | 9 | resource "frontegg_workspace" "example" { 10 | name = "Your Company" 11 | country = "US" 12 | backend_stack = "Python" 13 | frontend_stack = "React" 14 | open_saas_installed = false 15 | 16 | frontegg_domain = "example.frontegg.com" 17 | allowed_origins = ["https://yourcompany.com"] 18 | 19 | auth_policy { 20 | allow_unverified_users = false 21 | allow_signups = true 22 | enable_api_tokens = true 23 | enable_roles = true 24 | jwt_algorithm = "RS256" 25 | jwt_access_token_expiration = 86400 # 1 day 26 | jwt_refresh_token_expiration = 2592000 # 30 days 27 | same_site_cookie_policy = "strict" 28 | allow_tenant_invitations = true 29 | auth_strategy = "EmailAndPassword" 30 | } 31 | 32 | mfa_policy { 33 | allow_remember_device = true 34 | device_expiration = 604800 # 7 days 35 | enforce = "unless-saml" 36 | } 37 | 38 | mfa_authentication_app { 39 | service_name = "Your Company" 40 | } 41 | 42 | lockout_policy { 43 | max_attempts = 10 44 | } 45 | 46 | password_policy { 47 | allow_passphrases = false 48 | min_length = 10 49 | max_length = 128 50 | min_tests = 2 51 | min_phrase_length = 6 52 | history = 2 53 | } 54 | 55 | captcha_policy { 56 | site_key = "fake-site-key" 57 | secret_key = "fake-secret-key" 58 | min_score = 0.5 59 | } 60 | 61 | hosted_login { 62 | allowed_redirect_urls = [ 63 | "http://example.com/a", 64 | "http://example.com/b", 65 | ] 66 | } 67 | 68 | facebook_social_login { 69 | client_id = "fake-client-id" 70 | redirect_url = "fake-redirect-url" 71 | secret = "fake-secret" 72 | } 73 | 74 | github_social_login { 75 | client_id = "fake-client-id" 76 | redirect_url = "fake-redirect-url" 77 | secret = "fake-secret" 78 | } 79 | 80 | google_social_login { 81 | client_id = "fake-client-id" 82 | redirect_url = "fake-redirect-url" 83 | secret = "fake-secret" 84 | } 85 | 86 | microsoft_social_login { 87 | client_id = "fake-client-id" 88 | redirect_url = "fake-redirect-url" 89 | secret = "fake-secret" 90 | } 91 | 92 | saml { 93 | acs_url = "https://mycompany.com/saml" 94 | sp_entity_id = "my-company" 95 | redirect_url = "http://localhost:3000" 96 | } 97 | 98 | oidc { 99 | redirect_url = "http://localhost:3000" 100 | } 101 | 102 | sso_multi_tenant_policy { 103 | unspecified_tenant_strategy = "BLOCK" 104 | use_active_tenant = true 105 | } 106 | 107 | sso_domain_policy { 108 | allow_verified_users_to_add_domains = false 109 | skip_domain_verification = false 110 | bypass_domain_cross_validation = true 111 | } 112 | 113 | reset_password_email { 114 | from_address = "me@company.com" 115 | from_name = "Your Company" 116 | subject = "Reset Your Company Password" 117 | html_template = "Reset your password! {{redirectURL}}" 118 | redirect_url = "https://yourcompany.com/reset" 119 | } 120 | 121 | admin_portal { 122 | enable_account_settings = false 123 | enable_api_tokens = false 124 | enable_audit_logs = false 125 | enable_groups = false 126 | enable_personal_api_tokens = false 127 | enable_privacy = false 128 | enable_profile = false 129 | enable_provisioning = false 130 | enable_roles = false 131 | enable_security = false 132 | enable_sso = false 133 | enable_subscriptions = false 134 | enable_usage = false 135 | enable_users = false 136 | enable_webhooks = false 137 | 138 | palette { 139 | error { 140 | contrast_text = "#eeeef0" 141 | dark = "#ae402c" 142 | light = "#FFEEEA" 143 | main = "#E1583E" 144 | } 145 | info { 146 | contrast_text = "#eeeef0" 147 | dark = "#3c6492" 148 | light = "#E2EEF9" 149 | main = "#5587C0" 150 | } 151 | primary { 152 | active = "#278854" 153 | contrast_text = "#eeeef0" 154 | dark = "#36A76A" 155 | hover = "#32A265" 156 | light = "#A2E1BF" 157 | main = "#43BB7A" 158 | } 159 | secondary { 160 | active = "#E6ECF4" 161 | contrast_text = "#eeeef0" 162 | dark = "#E6ECF4" 163 | hover = "#F0F3F8" 164 | light = "#FBFBFC" 165 | main = "#FBFBFC" 166 | } 167 | success { 168 | contrast_text = "#eeeef0" 169 | dark = "#1d7c30" 170 | light = "#E1F5E2" 171 | main = "#2CA744" 172 | } 173 | warning { 174 | contrast_text = "#eeeef0" 175 | dark = "#EAE1C2" 176 | light = "#F9F4E2" 177 | main = "#A79D7B" 178 | } 179 | } 180 | } 181 | } 182 | 183 | resource "frontegg_webhook" "example" { 184 | enabled = true 185 | name = "Example webhook" 186 | description = "An example of a webhook" 187 | url = "https://example.com/webhook" 188 | secret = "example-sekret" 189 | events = [ 190 | "frontegg.user.authenticated" 191 | ] 192 | } 193 | 194 | resource "frontegg_permission_category" "example" { 195 | name = "Example" 196 | description = "An example of a permission category" 197 | } 198 | 199 | resource "frontegg_permission" "example" { 200 | name = "Example" 201 | key = "example" 202 | description = "An example of a permission" 203 | category_id = resource.frontegg_permission_category.example.id 204 | } 205 | 206 | data "frontegg_permission" "read_users" { 207 | key = "fe.secure.read.users" 208 | } 209 | 210 | resource "frontegg_role" "example" { 211 | name = "Example" 212 | key = "example" 213 | default = true 214 | first_user = true 215 | description = "An example of a role" 216 | level = 0 217 | permission_ids = [ 218 | resource.frontegg_permission.example.id, 219 | data.frontegg_permission.read_users.id, 220 | ] 221 | } 222 | 223 | resource "frontegg_tenant" "example" { 224 | name = "Example" 225 | key = "example-tenant-id" 226 | 227 | selected_metadata = { 228 | "selected_metadata_key" : "selected_metadata_value", 229 | } 230 | } 231 | 232 | output "public_key" { 233 | value = resource.frontegg_workspace.example.auth_policy.0.jwt_public_key 234 | } 235 | -------------------------------------------------------------------------------- /examples/data-sources/frontegg_permission/data-source.tf: -------------------------------------------------------------------------------- 1 | data "frontegg_permission" "read_users" { 2 | key = "fe.secure.read.users" 3 | } 4 | 5 | output "permission_id" { 6 | value = data.frontegg_permission.read_users.id 7 | } 8 | -------------------------------------------------------------------------------- /examples/provider/.terraform.lock.hcl: -------------------------------------------------------------------------------- 1 | # This file is maintained automatically by "terraform init". 2 | # Manual edits may be lost in future updates. 3 | 4 | provider "registry.terraform.io/benesch/frontegg" { 5 | version = "1.0.0" 6 | hashes = [ 7 | "h1:QIrxhPE/WUJpZ/Wp2vchPs6SNgbnOxPfCigAqGO91vU=", 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /examples/provider/provider.tf: -------------------------------------------------------------------------------- 1 | provider "frontegg" { 2 | client_id = "[your-personal-token-client-id]" 3 | secret_key = "[your-personal-token-api-key]" 4 | environment_id = "[your-environment-id]" 5 | 6 | api_base_url = "https://api.frontegg.com" 7 | portal_base_url = "https://frontegg-portal.frontegg.com" 8 | } -------------------------------------------------------------------------------- /examples/resources/frontegg_allowed_origin/resource.tf: -------------------------------------------------------------------------------- 1 | resource "frontegg_allowed_origin" "example" { 2 | allowed_origin = "https://example.com" 3 | } 4 | -------------------------------------------------------------------------------- /examples/resources/frontegg_application/resource.tf: -------------------------------------------------------------------------------- 1 | resource "frontegg_application" "example" { 2 | name = "Example Application" 3 | app_url = "https://app.example.com" 4 | login_url = "https://app.example.com/login" 5 | logo_url = "https://app.example.com/logo.png" 6 | access_type = "FREE_ACCESS" 7 | is_default = false 8 | is_active = true 9 | type = "web" 10 | frontend_stack = "react" 11 | description = "An example application" 12 | } 13 | -------------------------------------------------------------------------------- /examples/resources/frontegg_application_tenant_assignment/resource.tf: -------------------------------------------------------------------------------- 1 | resource "frontegg_application_tenant_assignment" "example" { 2 | app_id = "app-1234567890" 3 | tenant_id = "tenant-1234567890" 4 | } 5 | -------------------------------------------------------------------------------- /examples/resources/frontegg_auth0_user_source/resource.tf: -------------------------------------------------------------------------------- 1 | resource "frontegg_auth0_user_source" "example" { 2 | name = "Example Auth0 User Source" 3 | description = "An example Auth0 user source" 4 | index = 1 5 | sync_on_login = true 6 | is_migrated = false 7 | domain = "example.auth0.com" 8 | client_id = "auth0-client-id" 9 | secret = "auth0-client-secret" 10 | tenant_resolver_type = "static" 11 | tenant_id = "tenant-1234567890" 12 | 13 | app_ids = [ 14 | "app-1234567890" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /examples/resources/frontegg_cognito_user_source/resource.tf: -------------------------------------------------------------------------------- 1 | resource "frontegg_cognito_user_source" "example" { 2 | name = "Example Cognito User Source" 3 | description = "An example Cognito user source" 4 | index = 1 5 | sync_on_login = true 6 | is_migrated = false 7 | region = "us-west-2" 8 | client_id = "cognito-client-id" 9 | user_pool_id = "us-west-2_abcdefghi" 10 | access_key_id = "AKIAIOSFODNN7EXAMPLE" 11 | secret_access_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" 12 | client_secret = "cognito-client-secret" 13 | tenant_resolver_type = "static" 14 | tenant_id = "tenant-1234567890" 15 | 16 | app_ids = [ 17 | "app-1234567890" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /examples/resources/frontegg_custom_code_user_source/resource.tf: -------------------------------------------------------------------------------- 1 | resource "frontegg_custom_code_user_source" "example" { 2 | name = "Example Custom Code User Source" 3 | description = "An example custom code user source" 4 | index = 1 5 | sync_on_login = true 6 | is_migrated = false 7 | tenant_resolver_type = "static" 8 | tenant_id = "tenant-1234567890" 9 | 10 | code_payload = <<-EOT 11 | function authenticate(email, password) { 12 | // Custom authentication logic 13 | if (email === 'user@example.com' && password === 'password123') { 14 | return { 15 | id: 'user-123', 16 | email: 'user@example.com', 17 | name: 'Example User' 18 | }; 19 | } 20 | return null; 21 | } 22 | EOT 23 | 24 | get_user_code_payload = <<-EOT 25 | function getUser(userId) { 26 | // Custom user retrieval logic 27 | if (userId === 'user-123') { 28 | return { 29 | id: 'user-123', 30 | email: 'user@example.com', 31 | name: 'Example User', 32 | metadata: { 33 | role: 'admin' 34 | } 35 | }; 36 | } 37 | return null; 38 | } 39 | EOT 40 | 41 | app_ids = [ 42 | "app-1234567890" 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /examples/resources/frontegg_email_providers/resource.tf: -------------------------------------------------------------------------------- 1 | # AWS SES example 2 | resource "frontegg_email_providers" "ses_example" { 3 | provider_name = "ses" 4 | provider_id = "AKIAIOSFODNN7EXAMPLE" 5 | region = "us-west-2" 6 | secret = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" 7 | } 8 | 9 | # Mailgun example 10 | resource "frontegg_email_providers" "mailgun_example" { 11 | provider_name = "mailgun" 12 | domain = "mail.example.com" 13 | region = "us" 14 | secret = "key-abcdef123456789" 15 | } 16 | 17 | # SendGrid example 18 | resource "frontegg_email_providers" "sendgrid_example" { 19 | provider_name = "sendgrid" 20 | secret = "SG.abcdefghijklmnopqrstuvwxyz" 21 | } 22 | 23 | # AWS SES with IAM Role example 24 | resource "frontegg_email_providers" "ses_role_example" { 25 | provider_name = "ses-role" 26 | secret = "arn:aws:iam::123456789012:role/EmailSenderRole" 27 | } 28 | -------------------------------------------------------------------------------- /examples/resources/frontegg_firebase_user_source/resource.tf: -------------------------------------------------------------------------------- 1 | resource "frontegg_firebase_user_source" "example" { 2 | name = "Example Firebase User Source" 3 | description = "An example Firebase user source" 4 | index = 1 5 | sync_on_login = true 6 | is_migrated = false 7 | tenant_resolver_type = "static" 8 | tenant_id = "tenant-1234567890" 9 | 10 | api_key = "API_KEY" 11 | service_account_type = "service_account" 12 | project_id = "example-project-123456" 13 | private_key_id = "1234567890abcdef1234567890abcdef12345678" 14 | private_key = "-----BEGIN PRIVATE KEY-----\nEXAMPLE_PRIVATE_KEY\n-----END PRIVATE KEY-----\n" 15 | client_email = "firebase-adminsdk-abc123@example-project-123456.iam.gserviceaccount.com" 16 | client_id = "123456789012345678901" 17 | auth_uri = "https://accounts.google.com/o/oauth2/auth" 18 | token_uri = "https://oauth2.googleapis.com/token" 19 | auth_provider_x509_cert_url = "https://www.googleapis.com/oauth2/v1/certs" 20 | client_x509_cert_url = "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-abc123%40example-project-123456.iam.gserviceaccount.com" 21 | universe_domain = "googleapis.com" 22 | 23 | app_ids = [ 24 | "app-1234567890" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /examples/resources/frontegg_permission/resource.tf: -------------------------------------------------------------------------------- 1 | data "frontegg_permission" "read_users" { 2 | key = "fe.secure.read.users" 3 | } 4 | -------------------------------------------------------------------------------- /examples/resources/frontegg_permission_category/resource.tf: -------------------------------------------------------------------------------- 1 | resource "frontegg_permission_category" "example" { 2 | name = "Example" 3 | description = "An example of a permission category" 4 | } 5 | -------------------------------------------------------------------------------- /examples/resources/frontegg_plan/resource.tf: -------------------------------------------------------------------------------- 1 | resource "frontegg_plan" "example" { 2 | name = "Example Plan" 3 | description = "An example plan" 4 | default_treatment = "true" 5 | default_time_limitation = 30 6 | assign_on_signup = true 7 | 8 | feature_keys = [ 9 | "feature-1", 10 | "feature-2" 11 | ] 12 | 13 | rules = [ 14 | { 15 | "key" = "tenant.region" 16 | "operator" = "equals" 17 | "value" = "us-west" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /examples/resources/frontegg_role/resource.tf: -------------------------------------------------------------------------------- 1 | resource "frontegg_role" "example" { 2 | name = "Example" 3 | key = "example" 4 | description = "An example of a role" 5 | default = true 6 | level = 0 7 | permission_ids = [ 8 | resource.frontegg_permission.example.id, 9 | data.frontegg_permission.read_users.id, 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /examples/resources/frontegg_webhook/resource.tf: -------------------------------------------------------------------------------- 1 | resource "frontegg_webhook" "example" { 2 | enabled = true 3 | name = "Example webhook" 4 | description = "An example of a webhook" 5 | url = "https://example.com/webhook" 6 | secret = "example-secret" 7 | events = [ 8 | "frontegg.user.authenticated" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /examples/resources/frontegg_workspace/resource.tf: -------------------------------------------------------------------------------- 1 | resource "frontegg_workspace" "example" { 2 | name = "Your Company" 3 | country = "US" 4 | backend_stack = "Python" 5 | frontend_stack = "React" 6 | open_saas_installed = false 7 | 8 | # If you've configured CNAME record, 9 | # you can use that custom domain like so: 10 | # custom_domains = ["frontegg.yourcompany.com"] 11 | 12 | frontegg_domain = "blah.frontegg.com" 13 | allowed_origins = ["https://yourcompany.com"] 14 | 15 | auth_policy { 16 | allow_unverified_users = true 17 | allow_signups = true 18 | enable_api_tokens = true 19 | enable_roles = true 20 | jwt_algorithm = "RS256" 21 | machine_to_machine_auth_strategy = "ClientCredentials" 22 | jwt_access_token_expiration = 86400 # 1 day 23 | jwt_refresh_token_expiration = 2592000 # 30 days 24 | same_site_cookie_policy = "strict" 25 | auth_strategy = "EmailAndPassword" 26 | allow_tenant_invitations = true 27 | } 28 | 29 | mfa_policy { 30 | allow_remember_device = true 31 | device_expiration = 604800 # 7 days 32 | enforce = "unless-saml" 33 | } 34 | 35 | mfa_authentication_app { 36 | service_name = "Your Company" 37 | } 38 | 39 | lockout_policy { 40 | max_attempts = 10 41 | } 42 | 43 | password_policy { 44 | allow_passphrases = false 45 | min_length = 10 46 | max_length = 128 47 | min_tests = 2 48 | min_phrase_length = 6 49 | history = 2 50 | } 51 | 52 | captcha_policy { 53 | site_key = "fake-site-key" 54 | secret_key = "fake-secret-key" 55 | min_score = 0.5 56 | } 57 | 58 | hosted_login { 59 | allowed_redirect_urls = [ 60 | "http://example.com/a", 61 | "http://example.com/b", 62 | ] 63 | } 64 | 65 | facebook_social_login { 66 | client_id = "fake-client-id" 67 | redirect_url = "fake-redirect-url" 68 | secret = "fake-secret" 69 | customised = false 70 | } 71 | 72 | github_social_login { 73 | client_id = "fake-client-id" 74 | redirect_url = "fake-redirect-url" 75 | secret = "fake-secret" 76 | customised = false 77 | } 78 | 79 | google_social_login { 80 | client_id = "fake-client-id" 81 | redirect_url = "fake-redirect-url" 82 | secret = "fake-secret" 83 | customised = false 84 | } 85 | 86 | microsoft_social_login { 87 | client_id = "fake-client-id" 88 | redirect_url = "fake-redirect-url" 89 | secret = "fake-secret" 90 | customised = false 91 | } 92 | 93 | saml { 94 | acs_url = "https://mycompany.com/saml" 95 | sp_entity_id = "my-company" 96 | redirect_url = "http://localhost:3000" 97 | } 98 | 99 | oidc { 100 | redirect_url = "http://localhost:3000" 101 | } 102 | 103 | reset_password_email { 104 | from_address = "me@company.com" 105 | from_name = "Your Company" 106 | subject = "Reset Your Company Password" 107 | html_template = "Reset your password! {{redirectURL}}" 108 | redirect_url = "https://yourcompany.com/reset" 109 | } 110 | 111 | admin_portal { 112 | enable_account_settings = false 113 | enable_api_tokens = false 114 | enable_audit_logs = false 115 | enable_personal_api_tokens = false 116 | enable_privacy = false 117 | enable_profile = false 118 | enable_roles = false 119 | enable_security = false 120 | enable_sso = false 121 | enable_subscriptions = false 122 | enable_usage = false 123 | enable_users = false 124 | enable_webhooks = false 125 | enable_groups = false 126 | enable_provisioning = false 127 | 128 | palette_admin_portal { 129 | error { 130 | contrast_text = "#eeeef0" 131 | dark = "#ae402c" 132 | light = "#FFEEEA" 133 | main = "#E1583E" 134 | } 135 | info { 136 | contrast_text = "#eeeef0" 137 | dark = "#3c6492" 138 | light = "#E2EEF9" 139 | main = "#5587C0" 140 | } 141 | primary { 142 | active = "#278854" 143 | contrast_text = "#eeeef0" 144 | dark = "#36A76A" 145 | hover = "#32A265" 146 | light = "#A2E1BF" 147 | main = "#43BB7A" 148 | } 149 | secondary { 150 | active = "#E6ECF4" 151 | contrast_text = "#eeeef0" 152 | dark = "#E6ECF4" 153 | hover = "#F0F3F8" 154 | light = "#FBFBFC" 155 | main = "#FBFBFC" 156 | } 157 | success { 158 | contrast_text = "#eeeef0" 159 | dark = "#1d7c30" 160 | light = "#E1F5E2" 161 | main = "#2CA744" 162 | } 163 | warning { 164 | contrast_text = "#eeeef0" 165 | dark = "#EAE1C2" 166 | light = "#F9F4E2" 167 | main = "#A79D7B" 168 | } 169 | } 170 | 171 | palette_login_box { 172 | error { 173 | contrast_text = "#eeeef0" 174 | dark = "#ae402c" 175 | light = "#FFEEEA" 176 | main = "#E1583E" 177 | } 178 | info { 179 | contrast_text = "#eeeef0" 180 | dark = "#3c6492" 181 | light = "#E2EEF9" 182 | main = "#5587C0" 183 | } 184 | primary { 185 | active = "#278854" 186 | contrast_text = "#eeeef0" 187 | dark = "#36A76A" 188 | hover = "#32A265" 189 | light = "#A2E1BF" 190 | main = "#43BB7A" 191 | } 192 | secondary { 193 | active = "#E6ECF4" 194 | contrast_text = "#eeeef0" 195 | dark = "#E6ECF4" 196 | hover = "#F0F3F8" 197 | light = "#FBFBFC" 198 | main = "#FBFBFC" 199 | } 200 | success { 201 | contrast_text = "#eeeef0" 202 | dark = "#1d7c30" 203 | light = "#E1F5E2" 204 | main = "#2CA744" 205 | } 206 | warning { 207 | contrast_text = "#eeeef0" 208 | dark = "#EAE1C2" 209 | light = "#F9F4E2" 210 | main = "#A79D7B" 211 | } 212 | } 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/frontegg/terraform-provider-frontegg 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/hashicorp/terraform-plugin-docs v0.16.0 7 | github.com/hashicorp/terraform-plugin-sdk/v2 v2.31.0 8 | ) 9 | 10 | require ( 11 | github.com/Masterminds/goutils v1.1.1 // indirect 12 | github.com/Masterminds/semver/v3 v3.1.1 // indirect 13 | github.com/Masterminds/sprig/v3 v3.2.2 // indirect 14 | github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect 15 | github.com/agext/levenshtein v1.2.2 // indirect 16 | github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect 17 | github.com/armon/go-radix v1.0.0 // indirect 18 | github.com/bgentry/speakeasy v0.1.0 // indirect 19 | github.com/cloudflare/circl v1.3.7 // indirect 20 | github.com/fatih/color v1.13.0 // indirect 21 | github.com/golang/protobuf v1.5.3 // indirect 22 | github.com/google/go-cmp v0.6.0 // indirect 23 | github.com/google/uuid v1.3.1 // indirect 24 | github.com/hashicorp/errwrap v1.1.0 // indirect 25 | github.com/hashicorp/go-checkpoint v0.5.0 // indirect 26 | github.com/hashicorp/go-cleanhttp v0.5.2 // indirect 27 | github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 // indirect 28 | github.com/hashicorp/go-hclog v1.5.0 // indirect 29 | github.com/hashicorp/go-multierror v1.1.1 // indirect 30 | github.com/hashicorp/go-plugin v1.6.0 // indirect 31 | github.com/hashicorp/go-uuid v1.0.3 // indirect 32 | github.com/hashicorp/go-version v1.6.0 // indirect 33 | github.com/hashicorp/hc-install v0.6.2 // indirect 34 | github.com/hashicorp/hcl/v2 v2.19.1 // indirect 35 | github.com/hashicorp/logutils v1.0.0 // indirect 36 | github.com/hashicorp/terraform-exec v0.19.0 // indirect 37 | github.com/hashicorp/terraform-json v0.18.0 // indirect 38 | github.com/hashicorp/terraform-plugin-go v0.20.0 // indirect 39 | github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect 40 | github.com/hashicorp/terraform-registry-address v0.2.3 // indirect 41 | github.com/hashicorp/terraform-svchost v0.1.1 // indirect 42 | github.com/hashicorp/yamux v0.1.1 // indirect 43 | github.com/huandu/xstrings v1.3.2 // indirect 44 | github.com/imdario/mergo v0.3.15 // indirect 45 | github.com/mattn/go-colorable v0.1.13 // indirect 46 | github.com/mattn/go-isatty v0.0.16 // indirect 47 | github.com/mitchellh/cli v1.1.5 // indirect 48 | github.com/mitchellh/copystructure v1.2.0 // indirect 49 | github.com/mitchellh/go-testing-interface v1.14.1 // indirect 50 | github.com/mitchellh/go-wordwrap v1.0.0 // indirect 51 | github.com/mitchellh/mapstructure v1.5.0 // indirect 52 | github.com/mitchellh/reflectwalk v1.0.2 // indirect 53 | github.com/oklog/run v1.0.0 // indirect 54 | github.com/posener/complete v1.2.3 // indirect 55 | github.com/russross/blackfriday v1.6.0 // indirect 56 | github.com/shopspring/decimal v1.3.1 // indirect 57 | github.com/spf13/cast v1.5.0 // indirect 58 | github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect 59 | github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect 60 | github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect 61 | github.com/zclconf/go-cty v1.14.1 // indirect 62 | golang.org/x/crypto v0.17.0 // indirect 63 | golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect 64 | golang.org/x/mod v0.18.0 // indirect 65 | golang.org/x/net v0.18.0 // indirect 66 | golang.org/x/sys v0.15.0 // indirect 67 | golang.org/x/text v0.14.0 // indirect 68 | google.golang.org/appengine v1.6.8 // indirect 69 | google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 // indirect 70 | google.golang.org/grpc v1.60.0 // indirect 71 | google.golang.org/protobuf v1.31.0 // indirect 72 | ) 73 | -------------------------------------------------------------------------------- /internal/restclient/clientholder.go: -------------------------------------------------------------------------------- 1 | package restclient 2 | 3 | type ClientHolder struct { 4 | ApiClient Client 5 | PortalClient Client 6 | } 7 | -------------------------------------------------------------------------------- /internal/restclient/restclient.go: -------------------------------------------------------------------------------- 1 | package restclient 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "fmt" 8 | "io" 9 | "log" 10 | "net/http" 11 | ) 12 | 13 | type Client struct { 14 | token string 15 | client http.Client 16 | baseURL string 17 | conflictRetryMethod string 18 | ignore404 bool 19 | environmentId string 20 | } 21 | 22 | func MakeRestClient(baseURL string, environmentId string) Client { 23 | return Client{ 24 | client: http.Client{}, 25 | baseURL: baseURL, 26 | environmentId: environmentId, 27 | } 28 | } 29 | 30 | func (c *Client) Authenticate(token string) { 31 | c.token = token 32 | } 33 | 34 | func (c *Client) ConflictRetryMethod(method string) { 35 | c.conflictRetryMethod = method 36 | } 37 | 38 | func (c *Client) Ignore404() { 39 | c.ignore404 = true 40 | } 41 | 42 | func (c *Client) DeleteWithHeaders(ctx context.Context, url string, headers http.Header, out interface{}) error { 43 | return c.RequestWithHeaders(ctx, "DELETE", url, nil, headers, out) 44 | } 45 | 46 | func (c *Client) GetWithHeaders(ctx context.Context, url string, headers http.Header, out interface{}) error { 47 | return c.RequestWithHeaders(ctx, "GET", url, headers, nil, out) 48 | } 49 | 50 | func (c *Client) PatchWithHeaders(ctx context.Context, url string, headers http.Header, in interface{}, out interface{}) error { 51 | return c.RequestWithHeaders(ctx, "PATCH", url, headers, in, out) 52 | } 53 | 54 | func (c *Client) PostWithHeaders(ctx context.Context, url string, headers http.Header, in interface{}, out interface{}) error { 55 | return c.RequestWithHeaders(ctx, "POST", url, headers, in, out) 56 | } 57 | 58 | func (c *Client) PutWithHeaders(ctx context.Context, url string, headers http.Header, in interface{}, out interface{}) error { 59 | return c.RequestWithHeaders(ctx, "PUT", url, headers, in, out) 60 | } 61 | 62 | func (c *Client) Delete(ctx context.Context, url string, out interface{}) error { 63 | return c.RequestWithHeaders(ctx, "DELETE", url, nil, nil, out) 64 | } 65 | 66 | func (c *Client) Get(ctx context.Context, url string, out interface{}) error { 67 | return c.RequestWithHeaders(ctx, "GET", url, nil, nil, out) 68 | } 69 | 70 | func (c *Client) Patch(ctx context.Context, url string, in interface{}, out interface{}) error { 71 | return c.RequestWithHeaders(ctx, "PATCH", url, nil, in, out) 72 | } 73 | 74 | func (c *Client) Post(ctx context.Context, url string, in interface{}, out interface{}) error { 75 | return c.RequestWithHeaders(ctx, "POST", url, nil, in, out) 76 | } 77 | 78 | func (c *Client) Put(ctx context.Context, url string, in interface{}, out interface{}) error { 79 | return c.RequestWithHeaders(ctx, "PUT", url, nil, in, out) 80 | } 81 | 82 | func (c *Client) RequestWithHeaders(ctx context.Context, method string, url string, headers http.Header, in interface{}, out interface{}) error { 83 | conflictRetryMethod := c.conflictRetryMethod 84 | c.conflictRetryMethod = "" 85 | ignore404 := c.ignore404 86 | c.ignore404 = false 87 | var reqBody io.Reader 88 | if in != nil { 89 | b, err := json.Marshal(in) 90 | if err != nil { 91 | return fmt.Errorf("restclient: failed to serialize JSON request: %w", err) 92 | } 93 | reqBody = bytes.NewBuffer(b) 94 | } 95 | req, err := http.NewRequestWithContext(ctx, method, c.baseURL+url, reqBody) 96 | if err != nil { 97 | return fmt.Errorf("restclient: failed to construct request: %w", err) 98 | } 99 | for k, vals := range headers { 100 | for _, v := range vals { 101 | req.Header.Add(k, v) 102 | } 103 | } 104 | req.Header.Set("Content-Type", "application/json") 105 | if c.token != "" { 106 | req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.token)) 107 | } 108 | if c.environmentId != "" { 109 | req.Header.Set("frontegg-environment-id", c.environmentId) 110 | } 111 | log.Printf("[TRACE] Sending request %+v", req) 112 | res, err := c.client.Do(req) 113 | if err != nil { 114 | return fmt.Errorf("restclient: failed sending request: %w", err) 115 | } 116 | resBody, err := io.ReadAll(res.Body) 117 | if err != nil { 118 | return fmt.Errorf("restclient: failed to read response: %w", err) 119 | } 120 | if res.StatusCode == 404 && ignore404 { 121 | return nil 122 | } else if res.StatusCode == 409 && conflictRetryMethod != "" { 123 | return c.RequestWithHeaders(ctx, conflictRetryMethod, url, headers, in, out) 124 | } else if res.StatusCode < 200 || res.StatusCode >= 300 { 125 | return fmt.Errorf( 126 | "restclient: request failed: %s %s: %s: %v: %s", 127 | req.Method, req.URL, res.Status, res.Header, resBody, 128 | ) 129 | } 130 | log.Printf("[TRACE] Received response data %q", string(resBody)) 131 | if out != nil { 132 | if err := json.Unmarshal(resBody, out); err != nil { 133 | return fmt.Errorf("restclient: failed to decode JSON response %#v: %w", string(resBody), err) 134 | } 135 | } 136 | return nil 137 | } 138 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | 6 | "github.com/frontegg/terraform-provider-frontegg/provider" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/plugin" 8 | ) 9 | 10 | //go:generate terraform fmt -recursive ./examples/ 11 | //go:generate go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs 12 | 13 | var ( 14 | // This value is injected by goreleaser during a release. 15 | version string = "dev" 16 | ) 17 | 18 | func main() { 19 | var debugMode bool 20 | 21 | flag.BoolVar(&debugMode, "debug", false, "run the provider with support for debuggers") 22 | flag.Parse() 23 | 24 | opts := &plugin.ServeOpts{ProviderFunc: provider.New(version), Debug: debugMode} 25 | 26 | plugin.Serve(opts) 27 | } 28 | -------------------------------------------------------------------------------- /provider/data_source_frontegg_permission.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/frontegg/terraform-provider-frontegg/internal/restclient" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 9 | ) 10 | 11 | func dataSourceFronteggPermission() *schema.Resource { 12 | s := resourceFronteggPermission().Schema 13 | for _, field := range s { 14 | field.Required = false 15 | field.Computed = true 16 | } 17 | s["key"].Computed = false 18 | s["key"].Required = true 19 | return &schema.Resource{ 20 | ReadContext: dataSourceFronteggPermissionRead, 21 | Schema: s, 22 | } 23 | } 24 | 25 | func dataSourceFronteggPermissionRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 26 | clientHolder := meta.(*restclient.ClientHolder) 27 | var out []fronteggPermission 28 | if err := clientHolder.ApiClient.Get(ctx, fronteggPermissionPath, &out); err != nil { 29 | return diag.FromErr(err) 30 | } 31 | key := d.Get("key").(string) 32 | for _, c := range out { 33 | if c.Key == key { 34 | if err := resourceFronteggPermissionDeserialize(d, c); err != nil { 35 | return diag.FromErr(err) 36 | } 37 | return diag.Diagnostics{} 38 | } 39 | } 40 | return diag.Errorf("unable to find permission with key %s", key) 41 | } 42 | -------------------------------------------------------------------------------- /provider/provider.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/frontegg/terraform-provider-frontegg/internal/restclient" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 9 | ) 10 | 11 | func init() { 12 | schema.DescriptionKind = schema.StringMarkdown 13 | } 14 | 15 | func New(version string) func() *schema.Provider { 16 | return func() *schema.Provider { 17 | return &schema.Provider{ 18 | Schema: map[string]*schema.Schema{ 19 | "api_base_url": { 20 | Description: "The Frontegg api url. Override to change region. Defaults to EU url.", 21 | Type: schema.TypeString, 22 | Optional: true, 23 | Default: "https://api.frontegg.com", 24 | DefaultFunc: schema.EnvDefaultFunc("FRONTEGG_API_BASE_URL", nil), 25 | }, 26 | "portal_base_url": { 27 | Description: "The Frontegg portal url. Override to change region. Defaults to EU url.", 28 | Type: schema.TypeString, 29 | Optional: true, 30 | Default: "https://frontegg-prod.frontegg.com", 31 | DefaultFunc: schema.EnvDefaultFunc("FRONTEGG_PORTAL_BASE_URL", nil), 32 | }, 33 | "client_id": { 34 | Description: "The client ID for a Frontegg portal API key.", 35 | Type: schema.TypeString, 36 | Required: true, 37 | DefaultFunc: schema.EnvDefaultFunc("FRONTEGG_CLIENT_ID", nil), 38 | }, 39 | "secret_key": { 40 | Description: "The corresponding secret key for the API key.", 41 | Type: schema.TypeString, 42 | Required: true, 43 | Sensitive: true, 44 | DefaultFunc: schema.EnvDefaultFunc("FRONTEGG_SECRET_KEY", nil), 45 | }, 46 | "environment_id": { 47 | Description: "The client ID from environment settings.", 48 | Type: schema.TypeString, 49 | Optional: true, 50 | Sensitive: true, 51 | }, 52 | }, 53 | DataSourcesMap: map[string]*schema.Resource{ 54 | "frontegg_permission": dataSourceFronteggPermission(), 55 | }, 56 | ResourcesMap: map[string]*schema.Resource{ 57 | "frontegg_permission": resourceFronteggPermission(), 58 | "frontegg_permission_category": resourceFronteggPermissionCategory(), 59 | "frontegg_role": resourceFronteggRole(), 60 | "frontegg_webhook": resourceFronteggWebhook(), 61 | "frontegg_workspace": resourceFronteggWorkspace(), 62 | "frontegg_tenant": resourceFronteggTenant(), 63 | "frontegg_user": resourceFronteggUser(), 64 | "frontegg_redirect_uri": resourceFronteggRedirectUri(), 65 | "frontegg_allowed_origin": resourceFronteggAllowedOrigin(), 66 | "frontegg_email_provider": resourceFronteggEmailProvider(), 67 | "frontegg_application": resourceFronteggApplication(), 68 | "frontegg_application_tenant_assignment": resourceFronteggApplicationTenantAssignment(), 69 | "frontegg_auth0_user_source": resourceFronteggAuth0UserSource(), 70 | "frontegg_cognito_user_source": resourceFronteggCognitoUserSource(), 71 | "frontegg_firebase_user_source": resourceFronteggFirebaseUserSource(), 72 | "frontegg_custom_code_user_source": resourceFronteggCustomCodeUserSource(), 73 | "frontegg_feature": resourceFronteggFeature(), 74 | "frontegg_plan": resourceFronteggPlan(), 75 | "frontegg_plan_feature": resourceFronteggPlanFeature(), 76 | "frontegg_secret": resourceFronteggSecret(), 77 | }, 78 | ConfigureContextFunc: func(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) { 79 | environmentId := d.Get("environment_id").(string) 80 | apiClient := restclient.MakeRestClient(d.Get("api_base_url").(string), environmentId) 81 | portalClient := restclient.MakeRestClient(d.Get("portal_base_url").(string), environmentId) 82 | { 83 | in := struct { 84 | ClientId string `json:"clientId"` 85 | SecretKey string `json:"secret"` 86 | }{ 87 | ClientId: d.Get("client_id").(string), 88 | SecretKey: d.Get("secret_key").(string), 89 | } 90 | var out struct { 91 | AccessToken string `json:"token"` 92 | } 93 | err := apiClient.Post(ctx, "/auth/vendor", in, &out) 94 | if err != nil { 95 | return nil, diag.Errorf("unable to authenticate with frontegg: %s", err) 96 | } 97 | portalClient.Authenticate(out.AccessToken) 98 | apiClient.Authenticate(out.AccessToken) 99 | } 100 | return &restclient.ClientHolder{ 101 | ApiClient: apiClient, 102 | PortalClient: portalClient, 103 | }, nil 104 | }, 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /provider/provider_test.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 7 | ) 8 | 9 | func TestValidateProviderSchema(t *testing.T) { 10 | scm := schema.InternalMap(New("0.0.0")().Schema) 11 | if err := scm.InternalValidate(nil); err != nil { 12 | t.Errorf("Schema failed to validate: %v", err) 13 | } 14 | } 15 | 16 | func TestValidateResourceSchemas(t *testing.T) { 17 | prov := New("0.0.0")() 18 | for name, res := range prov.ResourcesMap { 19 | test := res 20 | t.Run(name, func(t *testing.T) { 21 | t.Parallel() 22 | scm := schema.InternalMap(test.Schema) 23 | if err := scm.InternalValidate(nil); err != nil { 24 | t.Errorf("Schema failed to validate: %v", err) 25 | } 26 | }) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /provider/resource_frontegg_allowed_origin.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/frontegg/terraform-provider-frontegg/internal/restclient" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 10 | ) 11 | 12 | const fronteggAllowedOriginPath = "/vendors" 13 | 14 | type fronteggAllowedOrigins struct { 15 | AllowedOrigins []string `json:"allowedOrigins,omitempty"` 16 | } 17 | 18 | func resourceFronteggAllowedOrigin() *schema.Resource { 19 | return &schema.Resource{ 20 | Description: `Configures a Frontegg allowed origin.`, 21 | 22 | CreateContext: resourceFronteggAllowedOriginCreate, 23 | ReadContext: resourceFronteggAllowedOriginRead, 24 | DeleteContext: resourceFronteggAllowedOriginDelete, 25 | Importer: &schema.ResourceImporter{ 26 | StateContext: schema.ImportStatePassthroughContext, 27 | }, 28 | 29 | Schema: map[string]*schema.Schema{ 30 | "allowed_origin": { 31 | Description: "The allowed origin URI.", 32 | Type: schema.TypeString, 33 | Required: true, 34 | ForceNew: true, 35 | }, 36 | }, 37 | } 38 | } 39 | 40 | func resourceFronteggAllowedOriginCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 41 | allowedOrigins, err := getAllowedOrigins(ctx, meta) 42 | if err != nil { 43 | return diag.FromErr(err) 44 | } 45 | 46 | newOrigin := d.Get("allowed_origin").(string) 47 | if containsAllowedOrigin(allowedOrigins, newOrigin) { 48 | return diag.FromErr(fmt.Errorf("origin '%s' already exists", newOrigin)) 49 | } 50 | 51 | allowedOrigins.AllowedOrigins = append(allowedOrigins.AllowedOrigins, newOrigin) 52 | 53 | if err := updateAllowedOrigins(ctx, meta, allowedOrigins); err != nil { 54 | return diag.FromErr(err) 55 | } 56 | 57 | d.SetId(newOrigin) 58 | if err := d.Set("allowed_origin", newOrigin); err != nil { 59 | return diag.FromErr(err) 60 | } 61 | 62 | return nil 63 | } 64 | 65 | func resourceFronteggAllowedOriginRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 66 | allowedOrigins, err := getAllowedOrigins(ctx, meta) 67 | if err != nil { 68 | return diag.FromErr(err) 69 | } 70 | 71 | origin := d.Get("allowed_origin").(string) 72 | if !containsAllowedOrigin(allowedOrigins, origin) { 73 | d.SetId("") 74 | return nil 75 | } 76 | 77 | d.SetId(origin) 78 | if err := d.Set("allowed_origin", origin); err != nil { 79 | return diag.FromErr(err) 80 | } 81 | 82 | return nil 83 | } 84 | 85 | func resourceFronteggAllowedOriginDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 86 | allowedOrigins, err := getAllowedOrigins(ctx, meta) 87 | if err != nil { 88 | return diag.FromErr(err) 89 | } 90 | 91 | originToDelete := d.Get("allowed_origin").(string) 92 | if !containsAllowedOrigin(allowedOrigins, originToDelete) { 93 | return diag.FromErr(fmt.Errorf("origin '%s' does not exist", originToDelete)) 94 | } 95 | 96 | newOrigins := make([]string, 0, len(allowedOrigins.AllowedOrigins)-1) 97 | for _, origin := range allowedOrigins.AllowedOrigins { 98 | if origin != originToDelete { 99 | newOrigins = append(newOrigins, origin) 100 | } 101 | } 102 | allowedOrigins.AllowedOrigins = newOrigins 103 | 104 | if err := updateAllowedOrigins(ctx, meta, allowedOrigins); err != nil { 105 | return diag.FromErr(err) 106 | } 107 | 108 | return nil 109 | } 110 | 111 | func getAllowedOrigins(ctx context.Context, meta interface{}) (*fronteggAllowedOrigins, error) { 112 | clientHolder := meta.(*restclient.ClientHolder) 113 | var out fronteggAllowedOrigins 114 | if err := clientHolder.ApiClient.Get(ctx, fronteggAllowedOriginPath, &out); err != nil { 115 | return nil, err 116 | } 117 | 118 | return &out, nil 119 | } 120 | 121 | func updateAllowedOrigins(ctx context.Context, meta interface{}, origins *fronteggAllowedOrigins) error { 122 | clientHolder := meta.(*restclient.ClientHolder) 123 | if err := clientHolder.ApiClient.Put(ctx, fronteggAllowedOriginPath, origins, nil); err != nil { 124 | return err 125 | } 126 | 127 | return nil 128 | } 129 | 130 | func containsAllowedOrigin(origins *fronteggAllowedOrigins, newOrigin string) bool { 131 | for _, origin := range origins.AllowedOrigins { 132 | if origin == newOrigin { 133 | return true 134 | } 135 | } 136 | 137 | return false 138 | } 139 | -------------------------------------------------------------------------------- /provider/resource_frontegg_application.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/frontegg/terraform-provider-frontegg/internal/restclient" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" 11 | ) 12 | 13 | const fronteggApplicationPath = "/applications/resources/applications/v1" 14 | 15 | type fronteggApplication struct { 16 | ID string `json:"id,omitempty"` 17 | Name string `json:"name"` 18 | AppURL string `json:"appURL"` 19 | LoginURL string `json:"loginURL"` 20 | LogoURL string `json:"logoURL,omitempty"` 21 | AccessType string `json:"accessType,omitempty"` 22 | IsDefault bool `json:"isDefault"` 23 | IsActive bool `json:"isActive"` 24 | Type string `json:"type,omitempty"` 25 | FrontendStack string `json:"frontendStack,omitempty"` 26 | Description string `json:"description,omitempty"` 27 | IntegrationFinishedAt string `json:"integrationFinishedAt,omitempty"` 28 | CreatedAt string `json:"createdAt,omitempty"` 29 | UpdatedAt string `json:"updatedAt,omitempty"` 30 | } 31 | 32 | type fronteggApplicationCredentials struct { 33 | ClientSecret string `json:"clientSecret"` 34 | SharedSecret string `json:"sharedSecret"` 35 | } 36 | 37 | func resourceFronteggApplication() *schema.Resource { 38 | return &schema.Resource{ 39 | Description: `Configures a Frontegg application.`, 40 | 41 | CreateContext: resourceFronteggApplicationCreate, 42 | ReadContext: resourceFronteggApplicationRead, 43 | UpdateContext: resourceFronteggApplicationUpdate, 44 | DeleteContext: resourceFronteggApplicationDelete, 45 | Importer: &schema.ResourceImporter{ 46 | StateContext: schema.ImportStatePassthroughContext, 47 | }, 48 | 49 | Schema: map[string]*schema.Schema{ 50 | "name": { 51 | Description: "The name of the application.", 52 | Type: schema.TypeString, 53 | Required: true, 54 | }, 55 | "app_url": { 56 | Description: "The URL of the application.", 57 | Type: schema.TypeString, 58 | Required: true, 59 | }, 60 | "login_url": { 61 | Description: "The login URL of the application.", 62 | Type: schema.TypeString, 63 | Required: true, 64 | }, 65 | "logo_url": { 66 | Description: "The URL of the application's logo.", 67 | Type: schema.TypeString, 68 | Optional: true, 69 | }, 70 | "access_type": { 71 | Description: "The access type of the application.", 72 | Type: schema.TypeString, 73 | Optional: true, 74 | ValidateFunc: validation.StringInSlice([]string{ 75 | "FREE_ACCESS", 76 | "MANAGED_ACCESS", 77 | }, false), 78 | }, 79 | "is_default": { 80 | Description: "Whether this is the default application.", 81 | Type: schema.TypeBool, 82 | Optional: true, 83 | Default: false, 84 | }, 85 | "is_active": { 86 | Description: "Whether the application is active.", 87 | Type: schema.TypeBool, 88 | Optional: true, 89 | Default: true, 90 | }, 91 | "type": { 92 | Description: "The type of the application.", 93 | Type: schema.TypeString, 94 | Optional: true, 95 | Default: "web", 96 | ValidateFunc: validation.StringInSlice([]string{ 97 | "web", 98 | "mobile-ios", 99 | "mobile-android", 100 | "other", 101 | }, false), 102 | }, 103 | "frontend_stack": { 104 | Description: "The frontend stack used by the application.", 105 | Type: schema.TypeString, 106 | Optional: true, 107 | Default: "react", 108 | ValidateFunc: validation.StringInSlice([]string{ 109 | "react", 110 | "vue", 111 | "angular", 112 | "next.js", 113 | "vanilla.js", 114 | "ionic", 115 | "flutter", 116 | "react-native", 117 | "kotlin", 118 | "swift", 119 | }, false), 120 | }, 121 | "description": { 122 | Description: "A description of the application.", 123 | Type: schema.TypeString, 124 | Optional: true, 125 | }, 126 | "integration_finished_at": { 127 | Description: "When the integration was finished.", 128 | Type: schema.TypeString, 129 | Computed: true, 130 | }, 131 | "created_at": { 132 | Description: "When the application was created.", 133 | Type: schema.TypeString, 134 | Computed: true, 135 | }, 136 | "updated_at": { 137 | Description: "When the application was last updated.", 138 | Type: schema.TypeString, 139 | Computed: true, 140 | }, 141 | "client_secret": { 142 | Description: "The client secret of the application.", 143 | Type: schema.TypeString, 144 | Computed: true, 145 | Sensitive: true, 146 | }, 147 | "shared_secret": { 148 | Description: "The shared secret of the application.", 149 | Type: schema.TypeString, 150 | Computed: true, 151 | Sensitive: true, 152 | }, 153 | }, 154 | } 155 | } 156 | 157 | func resourceFronteggApplicationSerialize(d *schema.ResourceData) fronteggApplication { 158 | return fronteggApplication{ 159 | Name: d.Get("name").(string), 160 | AppURL: d.Get("app_url").(string), 161 | LoginURL: d.Get("login_url").(string), 162 | LogoURL: d.Get("logo_url").(string), 163 | AccessType: d.Get("access_type").(string), 164 | IsDefault: d.Get("is_default").(bool), 165 | IsActive: d.Get("is_active").(bool), 166 | Type: d.Get("type").(string), 167 | FrontendStack: d.Get("frontend_stack").(string), 168 | Description: d.Get("description").(string), 169 | } 170 | } 171 | 172 | func resourceFronteggApplicationDeserialize(d *schema.ResourceData, f fronteggApplication) error { 173 | d.SetId(f.ID) 174 | if err := d.Set("name", f.Name); err != nil { 175 | return err 176 | } 177 | if err := d.Set("app_url", f.AppURL); err != nil { 178 | return err 179 | } 180 | if err := d.Set("login_url", f.LoginURL); err != nil { 181 | return err 182 | } 183 | if err := d.Set("logo_url", f.LogoURL); err != nil { 184 | return err 185 | } 186 | if err := d.Set("access_type", f.AccessType); err != nil { 187 | return err 188 | } 189 | if err := d.Set("is_default", f.IsDefault); err != nil { 190 | return err 191 | } 192 | if err := d.Set("is_active", f.IsActive); err != nil { 193 | return err 194 | } 195 | if err := d.Set("type", f.Type); err != nil { 196 | return err 197 | } 198 | if err := d.Set("frontend_stack", f.FrontendStack); err != nil { 199 | return err 200 | } 201 | if err := d.Set("description", f.Description); err != nil { 202 | return err 203 | } 204 | if err := d.Set("integration_finished_at", f.IntegrationFinishedAt); err != nil { 205 | return err 206 | } 207 | if err := d.Set("created_at", f.CreatedAt); err != nil { 208 | return err 209 | } 210 | if err := d.Set("updated_at", f.UpdatedAt); err != nil { 211 | return err 212 | } 213 | return nil 214 | } 215 | 216 | func resourceFronteggApplicationCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 217 | clientHolder := meta.(*restclient.ClientHolder) 218 | 219 | in := resourceFronteggApplicationSerialize(d) 220 | var out fronteggApplication 221 | if err := clientHolder.ApiClient.Post(ctx, fronteggApplicationPath, in, &out); err != nil { 222 | return diag.FromErr(err) 223 | } 224 | 225 | if err := resourceFronteggApplicationDeserialize(d, out); err != nil { 226 | return diag.FromErr(err) 227 | } 228 | 229 | // Get credentials after creation 230 | var creds fronteggApplicationCredentials 231 | if err := clientHolder.ApiClient.Get(ctx, fmt.Sprintf("%s/credentials/%s", fronteggApplicationPath, out.ID), &creds); err != nil { 232 | return diag.FromErr(err) 233 | } 234 | 235 | if err := d.Set("client_secret", creds.ClientSecret); err != nil { 236 | return diag.FromErr(err) 237 | } 238 | if err := d.Set("shared_secret", creds.SharedSecret); err != nil { 239 | return diag.FromErr(err) 240 | } 241 | 242 | return nil 243 | } 244 | 245 | func resourceFronteggApplicationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 246 | clientHolder := meta.(*restclient.ClientHolder) 247 | 248 | var out fronteggApplication 249 | if err := clientHolder.ApiClient.Get(ctx, fmt.Sprintf("%s/%s", fronteggApplicationPath, d.Id()), &out); err != nil { 250 | return diag.FromErr(err) 251 | } 252 | 253 | if err := resourceFronteggApplicationDeserialize(d, out); err != nil { 254 | return diag.FromErr(err) 255 | } 256 | 257 | // Get credentials 258 | var creds fronteggApplicationCredentials 259 | if err := clientHolder.ApiClient.Get(ctx, fmt.Sprintf("%s/credentials/%s", fronteggApplicationPath, d.Id()), &creds); err != nil { 260 | return diag.FromErr(err) 261 | } 262 | 263 | if err := d.Set("client_secret", creds.ClientSecret); err != nil { 264 | return diag.FromErr(err) 265 | } 266 | if err := d.Set("shared_secret", creds.SharedSecret); err != nil { 267 | return diag.FromErr(err) 268 | } 269 | 270 | return nil 271 | } 272 | 273 | func resourceFronteggApplicationUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 274 | clientHolder := meta.(*restclient.ClientHolder) 275 | 276 | in := resourceFronteggApplicationSerialize(d) 277 | // Don't try to unmarshal response - API returns empty response but updates succeed 278 | if err := clientHolder.ApiClient.Patch(ctx, fmt.Sprintf("%s/%s", fronteggApplicationPath, d.Id()), in, nil); err != nil { 279 | return diag.FromErr(err) 280 | } 281 | 282 | // Refresh state by reading the resource after update 283 | return resourceFronteggApplicationRead(ctx, d, meta) 284 | } 285 | 286 | func resourceFronteggApplicationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 287 | clientHolder := meta.(*restclient.ClientHolder) 288 | 289 | if err := clientHolder.ApiClient.Delete(ctx, fmt.Sprintf("%s/%s", fronteggApplicationPath, d.Id()), nil); err != nil { 290 | return diag.FromErr(err) 291 | } 292 | 293 | return nil 294 | } 295 | -------------------------------------------------------------------------------- /provider/resource_frontegg_application_tenant_assignment.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/frontegg/terraform-provider-frontegg/internal/restclient" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 11 | ) 12 | 13 | const fronteggApplicationTenantAssignmentPath = "/applications/resources/applications/tenant-assignments/v1" 14 | 15 | type fronteggApplicationTenantAssignment struct { 16 | TenantID string `json:"tenantId"` 17 | AppIDs []string `json:"appIds"` 18 | } 19 | 20 | // For handling the API response format in Read when it returns an object. 21 | type fronteggApplicationTenantIds struct { 22 | TenantIds []string `json:"tenantIds"` 23 | } 24 | 25 | func resourceFronteggApplicationTenantAssignment() *schema.Resource { 26 | return &schema.Resource{ 27 | Description: `Configures a Frontegg application tenant assignment.`, 28 | 29 | CreateContext: resourceFronteggApplicationTenantAssignmentCreate, 30 | ReadContext: resourceFronteggApplicationTenantAssignmentRead, 31 | DeleteContext: resourceFronteggApplicationTenantAssignmentDelete, 32 | Importer: &schema.ResourceImporter{ 33 | StateContext: resourceFronteggApplicationTenantAssignmentImport, 34 | }, 35 | 36 | Schema: map[string]*schema.Schema{ 37 | "app_id": { 38 | Description: "The ID of the application.", 39 | Type: schema.TypeString, 40 | Required: true, 41 | ForceNew: true, 42 | }, 43 | "tenant_id": { 44 | Description: "The ID of the tenant.", 45 | Type: schema.TypeString, 46 | Required: true, 47 | ForceNew: true, 48 | }, 49 | }, 50 | } 51 | } 52 | 53 | func resourceFronteggApplicationTenantAssignmentImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { 54 | parts := strings.Split(d.Id(), ":") 55 | if len(parts) != 2 { 56 | return nil, fmt.Errorf("invalid import format, expected app_id:tenant_id, got: %s", d.Id()) 57 | } 58 | 59 | appID := parts[0] 60 | tenantID := parts[1] 61 | 62 | if err := d.Set("app_id", appID); err != nil { 63 | return nil, err 64 | } 65 | if err := d.Set("tenant_id", tenantID); err != nil { 66 | return nil, err 67 | } 68 | 69 | // Return the resource with the ID set 70 | return []*schema.ResourceData{d}, nil 71 | } 72 | 73 | func resourceFronteggApplicationTenantAssignmentCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 74 | clientHolder := meta.(*restclient.ClientHolder) 75 | appID := d.Get("app_id").(string) 76 | tenantID := d.Get("tenant_id").(string) 77 | 78 | // First check if the assignment already exists using the Read function 79 | diags := resourceFronteggApplicationTenantAssignmentRead(ctx, d, meta) 80 | if len(diags) == 0 && d.Id() != "" { 81 | // Assignment already exists, just return 82 | return nil 83 | } 84 | 85 | // If we get here, the assignment doesn't exist, so create it 86 | in := struct { 87 | TenantID string `json:"tenantId"` 88 | }{ 89 | TenantID: tenantID, 90 | } 91 | 92 | var out fronteggApplicationTenantAssignment 93 | if err := clientHolder.ApiClient.Post(ctx, fmt.Sprintf("%s/%s", fronteggApplicationTenantAssignmentPath, appID), in, &out); err != nil { 94 | return diag.FromErr(err) 95 | } 96 | 97 | d.SetId(fmt.Sprintf("%s:%s", appID, tenantID)) 98 | return nil 99 | } 100 | 101 | func resourceFronteggApplicationTenantAssignmentRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 102 | clientHolder := meta.(*restclient.ClientHolder) 103 | appID := d.Get("app_id").(string) 104 | tenantID := d.Get("tenant_id").(string) 105 | 106 | var objectResponse fronteggApplicationTenantIds 107 | err := clientHolder.ApiClient.Get(ctx, fmt.Sprintf("%s/%s", fronteggApplicationTenantAssignmentPath, appID), &objectResponse) 108 | if err == nil { 109 | found := false 110 | for _, id := range objectResponse.TenantIds { 111 | if id == tenantID { 112 | found = true 113 | break 114 | } 115 | } 116 | if !found { 117 | d.SetId("") 118 | } 119 | return nil 120 | } 121 | 122 | return diag.FromErr(err) 123 | } 124 | 125 | func resourceFronteggApplicationTenantAssignmentDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 126 | clientHolder := meta.(*restclient.ClientHolder) 127 | appID := d.Get("app_id").(string) 128 | tenantID := d.Get("tenant_id").(string) 129 | 130 | if err := clientHolder.ApiClient.Delete(ctx, fmt.Sprintf("%s/%s/%s", fronteggApplicationTenantAssignmentPath, appID, tenantID), nil); err != nil { 131 | return diag.FromErr(err) 132 | } 133 | 134 | return nil 135 | } 136 | -------------------------------------------------------------------------------- /provider/resource_frontegg_auth0_user_source.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/frontegg/terraform-provider-frontegg/internal/restclient" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 10 | ) 11 | 12 | type fronteggAuth0UserSourceConfig struct { 13 | SyncOnLogin bool `json:"syncOnLogin"` 14 | IsMigrated bool `json:"isMigrated"` 15 | Domain string `json:"domain"` 16 | ClientID string `json:"clientId"` 17 | Secret string `json:"secret"` 18 | TenantConfig interface{} `json:"tenantConfig"` // Can be UserSourceDynamicTenantConfig, UserSourceStaticTenantConfig, or UserSourceNewTenantConfig 19 | } 20 | 21 | type fronteggAuth0UserSourceRequest struct { 22 | Name string `json:"name"` 23 | Configuration fronteggAuth0UserSourceConfig `json:"configuration"` 24 | AppIDs []string `json:"appIds,omitempty"` 25 | Index int `json:"index"` 26 | Description string `json:"description,omitempty"` 27 | } 28 | 29 | const fronteggAuth0UserSourcePath = "/identity/resources/user-sources/v1/external/auth0" 30 | 31 | func resourceFronteggAuth0UserSource() *schema.Resource { 32 | baseSchema := userSourceBaseSchema() 33 | 34 | // Add Auth0-specific fields 35 | baseSchema["domain"] = &schema.Schema{ 36 | Description: "The Auth0 domain.", 37 | Type: schema.TypeString, 38 | Required: true, 39 | } 40 | baseSchema["client_id"] = &schema.Schema{ 41 | Description: "The Auth0 application client ID.", 42 | Type: schema.TypeString, 43 | Required: true, 44 | } 45 | baseSchema["secret"] = &schema.Schema{ 46 | Description: "The Auth0 application secret.", 47 | Type: schema.TypeString, 48 | Required: true, 49 | Sensitive: true, 50 | } 51 | 52 | return &schema.Resource{ 53 | Description: `Configures a Frontegg Auth0 user source.`, 54 | 55 | CreateContext: resourceFronteggAuth0UserSourceCreate, 56 | ReadContext: resourceFronteggAuth0UserSourceRead, 57 | UpdateContext: resourceFronteggAuth0UserSourceUpdate, 58 | DeleteContext: resourceFronteggAuth0UserSourceDelete, 59 | Importer: &schema.ResourceImporter{ 60 | StateContext: schema.ImportStatePassthroughContext, 61 | }, 62 | Schema: baseSchema, 63 | } 64 | } 65 | 66 | func resourceFronteggAuth0UserSourceSerialize(d *schema.ResourceData) (fronteggAuth0UserSourceRequest, error) { 67 | appIDs := extractAppIDs(d) 68 | 69 | tenantConfig, err := buildUserSourceTenantConfig(d) 70 | if err != nil { 71 | return fronteggAuth0UserSourceRequest{}, err 72 | } 73 | 74 | config := fronteggAuth0UserSourceConfig{ 75 | SyncOnLogin: d.Get("sync_on_login").(bool), 76 | IsMigrated: d.Get("is_migrated").(bool), 77 | Domain: d.Get("domain").(string), 78 | ClientID: d.Get("client_id").(string), 79 | Secret: d.Get("secret").(string), 80 | TenantConfig: tenantConfig, 81 | } 82 | 83 | return fronteggAuth0UserSourceRequest{ 84 | Name: d.Get("name").(string), 85 | Configuration: config, 86 | AppIDs: appIDs, 87 | Index: d.Get("index").(int), 88 | Description: d.Get("description").(string), 89 | }, nil 90 | } 91 | 92 | func resourceFronteggAuth0UserSourceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 93 | clientHolder := meta.(*restclient.ClientHolder) 94 | in, err := resourceFronteggAuth0UserSourceSerialize(d) 95 | if err != nil { 96 | return diag.FromErr(err) 97 | } 98 | 99 | var out fronteggBaseUserSourceResponse 100 | if err := clientHolder.ApiClient.Post(ctx, fronteggAuth0UserSourcePath, in, &out); err != nil { 101 | return diag.FromErr(err) 102 | } 103 | 104 | if err := deserializeUserSourceResponse(d, out); err != nil { 105 | return diag.FromErr(err) 106 | } 107 | 108 | return nil 109 | } 110 | 111 | func resourceFronteggAuth0UserSourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 112 | return readUserSource(ctx, d, meta, deserializeUserSourceResponse) 113 | } 114 | 115 | func resourceFronteggAuth0UserSourceUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 116 | clientHolder := meta.(*restclient.ClientHolder) 117 | in, err := resourceFronteggAuth0UserSourceSerialize(d) 118 | if err != nil { 119 | return diag.FromErr(err) 120 | } 121 | 122 | if err := clientHolder.ApiClient.Put(ctx, fmt.Sprintf("%s/%s", fronteggAuth0UserSourcePath, d.Id()), in, nil); err != nil { 123 | return diag.FromErr(err) 124 | } 125 | 126 | return resourceFronteggAuth0UserSourceRead(ctx, d, meta) 127 | } 128 | 129 | func resourceFronteggAuth0UserSourceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 130 | return deleteUserSource(ctx, d, meta) 131 | } 132 | -------------------------------------------------------------------------------- /provider/resource_frontegg_cognito_user_source.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/frontegg/terraform-provider-frontegg/internal/restclient" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 10 | ) 11 | 12 | type fronteggCognitoUserSourceConfig struct { 13 | SyncOnLogin bool `json:"syncOnLogin"` 14 | IsMigrated bool `json:"isMigrated"` 15 | Region string `json:"region"` 16 | ClientID string `json:"clientId"` 17 | UserPoolID string `json:"userPoolId"` 18 | AccessKeyID string `json:"accessKeyId"` 19 | SecretAccessKey string `json:"secretAccessKey"` 20 | ClientSecret string `json:"clientSecret,omitempty"` 21 | TenantConfig interface{} `json:"tenantConfig"` 22 | } 23 | 24 | type fronteggCognitoUserSourceRequest struct { 25 | Name string `json:"name"` 26 | Configuration fronteggCognitoUserSourceConfig `json:"configuration"` 27 | AppIDs []string `json:"appIds,omitempty"` 28 | Index int `json:"index"` 29 | Description string `json:"description,omitempty"` 30 | } 31 | 32 | const fronteggCognitoUserSourcePath = "/identity/resources/user-sources/v1/external/cognito" 33 | 34 | func resourceFronteggCognitoUserSource() *schema.Resource { 35 | baseSchema := userSourceBaseSchema() 36 | 37 | // Add Cognito-specific fields 38 | baseSchema["region"] = &schema.Schema{ 39 | Description: "The AWS region of the Cognito user pool.", 40 | Type: schema.TypeString, 41 | Required: true, 42 | } 43 | baseSchema["client_id"] = &schema.Schema{ 44 | Description: "The Cognito app client ID.", 45 | Type: schema.TypeString, 46 | Required: true, 47 | } 48 | baseSchema["user_pool_id"] = &schema.Schema{ 49 | Description: "The ID of the Cognito user pool.", 50 | Type: schema.TypeString, 51 | Required: true, 52 | } 53 | baseSchema["access_key_id"] = &schema.Schema{ 54 | Description: "The access key of the AWS account.", 55 | Type: schema.TypeString, 56 | Required: true, 57 | } 58 | baseSchema["secret_access_key"] = &schema.Schema{ 59 | Description: "The secret of the AWS account.", 60 | Type: schema.TypeString, 61 | Required: true, 62 | Sensitive: true, 63 | } 64 | baseSchema["client_secret"] = &schema.Schema{ 65 | Description: "The Cognito application client secret, required if the app client is configured with a client secret.", 66 | Type: schema.TypeString, 67 | Optional: true, 68 | Sensitive: true, 69 | } 70 | 71 | return &schema.Resource{ 72 | Description: `Configures a Frontegg Cognito user source.`, 73 | 74 | CreateContext: resourceFronteggCognitoUserSourceCreate, 75 | ReadContext: resourceFronteggCognitoUserSourceRead, 76 | UpdateContext: resourceFronteggCognitoUserSourceUpdate, 77 | DeleteContext: resourceFronteggCognitoUserSourceDelete, 78 | Importer: &schema.ResourceImporter{ 79 | StateContext: schema.ImportStatePassthroughContext, 80 | }, 81 | Schema: baseSchema, 82 | } 83 | } 84 | 85 | func resourceFronteggCognitoUserSourceSerialize(d *schema.ResourceData) (fronteggCognitoUserSourceRequest, error) { 86 | appIDs := extractAppIDs(d) 87 | 88 | tenantConfig, err := buildUserSourceTenantConfig(d) 89 | if err != nil { 90 | return fronteggCognitoUserSourceRequest{}, err 91 | } 92 | 93 | config := fronteggCognitoUserSourceConfig{ 94 | SyncOnLogin: d.Get("sync_on_login").(bool), 95 | IsMigrated: d.Get("is_migrated").(bool), 96 | Region: d.Get("region").(string), 97 | ClientID: d.Get("client_id").(string), 98 | UserPoolID: d.Get("user_pool_id").(string), 99 | AccessKeyID: d.Get("access_key_id").(string), 100 | SecretAccessKey: d.Get("secret_access_key").(string), 101 | ClientSecret: d.Get("client_secret").(string), 102 | TenantConfig: tenantConfig, 103 | } 104 | 105 | return fronteggCognitoUserSourceRequest{ 106 | Name: d.Get("name").(string), 107 | Configuration: config, 108 | AppIDs: appIDs, 109 | Index: d.Get("index").(int), 110 | Description: d.Get("description").(string), 111 | }, nil 112 | } 113 | 114 | func resourceFronteggCognitoUserSourceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 115 | clientHolder := meta.(*restclient.ClientHolder) 116 | in, err := resourceFronteggCognitoUserSourceSerialize(d) 117 | if err != nil { 118 | return diag.FromErr(err) 119 | } 120 | 121 | var out fronteggBaseUserSourceResponse 122 | if err := clientHolder.ApiClient.Post(ctx, fronteggCognitoUserSourcePath, in, &out); err != nil { 123 | return diag.FromErr(err) 124 | } 125 | 126 | if err := deserializeUserSourceResponse(d, out); err != nil { 127 | return diag.FromErr(err) 128 | } 129 | 130 | return nil 131 | } 132 | 133 | func resourceFronteggCognitoUserSourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 134 | return readUserSource(ctx, d, meta, deserializeUserSourceResponse) 135 | } 136 | 137 | func resourceFronteggCognitoUserSourceUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 138 | clientHolder := meta.(*restclient.ClientHolder) 139 | in, err := resourceFronteggCognitoUserSourceSerialize(d) 140 | if err != nil { 141 | return diag.FromErr(err) 142 | } 143 | 144 | if err := clientHolder.ApiClient.Put(ctx, fmt.Sprintf("%s/%s", fronteggCognitoUserSourcePath, d.Id()), in, nil); err != nil { 145 | return diag.FromErr(err) 146 | } 147 | 148 | return resourceFronteggCognitoUserSourceRead(ctx, d, meta) 149 | } 150 | 151 | func resourceFronteggCognitoUserSourceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 152 | return deleteUserSource(ctx, d, meta) 153 | } 154 | -------------------------------------------------------------------------------- /provider/resource_frontegg_custom_code_user_source.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/frontegg/terraform-provider-frontegg/internal/restclient" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 10 | ) 11 | 12 | type fronteggCustomCodeUserSourceConfig struct { 13 | SyncOnLogin bool `json:"syncOnLogin"` 14 | IsMigrated bool `json:"isMigrated"` 15 | TenantConfig interface{} `json:"tenantConfig"` 16 | CodePayload string `json:"codePayload"` 17 | GetUserCodePayload string `json:"getUserCodePayload,omitempty"` 18 | } 19 | 20 | type fronteggCustomCodeUserSourceRequest struct { 21 | Name string `json:"name"` 22 | Configuration fronteggCustomCodeUserSourceConfig `json:"configuration"` 23 | AppIDs []string `json:"appIds,omitempty"` 24 | Index int `json:"index"` 25 | Description string `json:"description,omitempty"` 26 | } 27 | 28 | const fronteggCustomCodeUserSourcePath = "/identity/resources/user-sources/v1/external/custom-code" 29 | 30 | func resourceFronteggCustomCodeUserSource() *schema.Resource { 31 | baseSchema := userSourceBaseSchema() 32 | 33 | // Add Custom-Code specific fields 34 | baseSchema["code_payload"] = &schema.Schema{ 35 | Description: "The custom code that will be executed to authenticate the user.", 36 | Type: schema.TypeString, 37 | Required: true, 38 | } 39 | 40 | baseSchema["get_user_code_payload"] = &schema.Schema{ 41 | Description: "The custom code that will be executed to get user details.", 42 | Type: schema.TypeString, 43 | Optional: true, 44 | } 45 | 46 | return &schema.Resource{ 47 | Description: `Configures a Frontegg Custom-Code user source.`, 48 | 49 | CreateContext: resourceFronteggCustomCodeUserSourceCreate, 50 | ReadContext: resourceFronteggCustomCodeUserSourceRead, 51 | UpdateContext: resourceFronteggCustomCodeUserSourceUpdate, 52 | DeleteContext: resourceFronteggCustomCodeUserSourceDelete, 53 | Importer: &schema.ResourceImporter{ 54 | StateContext: schema.ImportStatePassthroughContext, 55 | }, 56 | Schema: baseSchema, 57 | } 58 | } 59 | 60 | func resourceFronteggCustomCodeUserSourceSerialize(d *schema.ResourceData) (fronteggCustomCodeUserSourceRequest, error) { 61 | appIDs := extractAppIDs(d) 62 | 63 | tenantConfig, err := buildUserSourceTenantConfig(d) 64 | if err != nil { 65 | return fronteggCustomCodeUserSourceRequest{}, err 66 | } 67 | 68 | config := fronteggCustomCodeUserSourceConfig{ 69 | SyncOnLogin: d.Get("sync_on_login").(bool), 70 | IsMigrated: d.Get("is_migrated").(bool), 71 | TenantConfig: tenantConfig, 72 | CodePayload: d.Get("code_payload").(string), 73 | } 74 | 75 | // Add the optional getUserCodePayload 76 | if getUserCode, ok := d.GetOk("get_user_code_payload"); ok { 77 | config.GetUserCodePayload = getUserCode.(string) 78 | } 79 | 80 | return fronteggCustomCodeUserSourceRequest{ 81 | Name: d.Get("name").(string), 82 | Configuration: config, 83 | AppIDs: appIDs, 84 | Index: d.Get("index").(int), 85 | Description: d.Get("description").(string), 86 | }, nil 87 | } 88 | 89 | func resourceFronteggCustomCodeUserSourceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 90 | clientHolder := meta.(*restclient.ClientHolder) 91 | in, err := resourceFronteggCustomCodeUserSourceSerialize(d) 92 | if err != nil { 93 | return diag.FromErr(err) 94 | } 95 | 96 | var out fronteggBaseUserSourceResponse 97 | if err := clientHolder.ApiClient.Post(ctx, fronteggCustomCodeUserSourcePath, in, &out); err != nil { 98 | return diag.FromErr(err) 99 | } 100 | 101 | if err := deserializeUserSourceResponse(d, out); err != nil { 102 | return diag.FromErr(err) 103 | } 104 | 105 | return nil 106 | } 107 | 108 | func resourceFronteggCustomCodeUserSourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 109 | return readUserSource(ctx, d, meta, deserializeUserSourceResponse) 110 | } 111 | 112 | func resourceFronteggCustomCodeUserSourceUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 113 | clientHolder := meta.(*restclient.ClientHolder) 114 | in, err := resourceFronteggCustomCodeUserSourceSerialize(d) 115 | if err != nil { 116 | return diag.FromErr(err) 117 | } 118 | 119 | if err := clientHolder.ApiClient.Put(ctx, fmt.Sprintf("%s/%s", fronteggCustomCodeUserSourcePath, d.Id()), in, nil); err != nil { 120 | return diag.FromErr(err) 121 | } 122 | 123 | return resourceFronteggCustomCodeUserSourceRead(ctx, d, meta) 124 | } 125 | 126 | func resourceFronteggCustomCodeUserSourceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 127 | return deleteUserSource(ctx, d, meta) 128 | } 129 | -------------------------------------------------------------------------------- /provider/resource_frontegg_email_providers.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/frontegg/terraform-provider-frontegg/internal/restclient" 8 | "github.com/frontegg/terraform-provider-frontegg/provider/validators" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 11 | ) 12 | 13 | const ( 14 | fronteggEmailPorivderPath = "/identity/resources/mail/v2/configurations" 15 | fronteggEmailPorivderPathV1 = "/identity/resources/mail/v1/configurations" 16 | ) 17 | 18 | type fronteggEmailProviderPayload struct { 19 | Id string `json:"id,omitempty"` 20 | Region string `json:"region,omitempty"` 21 | Domain string `json:"domain,omitempty"` 22 | Secret string `json:"secret,omitempty"` 23 | Provider string `json:"provider,omitempty"` 24 | } 25 | 26 | type fronteggEmailProvider struct { 27 | Payload fronteggEmailProviderPayload `json:"payload,omitempty"` 28 | } 29 | 30 | type Extension struct { 31 | ExtensionName string `json:"extensionName,omitempty"` 32 | ExtensionValue string `json:"extensionValue,omitempty"` 33 | } 34 | 35 | type fronteggEmailProviderResponse struct { 36 | Secret string `json:"secret,omitempty"` 37 | CreatedAt string `json:"createdAt,omitempty"` 38 | UpdatedAt string `json:"updatedAt,omitempty"` 39 | Extension []Extension `json:"extension,omitempty"` 40 | } 41 | 42 | func resourceFronteggEmailProvider() *schema.Resource { 43 | return &schema.Resource{ 44 | Description: `Configures a Frontegg Email provider.`, 45 | 46 | CreateContext: resourceFronteggEmailProviderCreate, 47 | ReadContext: resourceFronteggEmailProviderRead, 48 | UpdateContext: resourceFronteggEmailProviderUpdate, 49 | DeleteContext: resourceFronteggEmailProviderDelete, 50 | Importer: &schema.ResourceImporter{ 51 | StateContext: schema.ImportStatePassthroughContext, 52 | }, 53 | 54 | Schema: map[string]*schema.Schema{ 55 | "provider_id": { 56 | Description: "Provider ID (required only for AWS SES).", 57 | Type: schema.TypeString, 58 | Optional: true, 59 | }, 60 | "region": { 61 | Description: "Required for AWS SES or Mailgun.", 62 | Type: schema.TypeString, 63 | Optional: true, 64 | }, 65 | "domain": { 66 | Description: "Required for Mailgun (required only for Mailgun).", 67 | Type: schema.TypeString, 68 | Optional: true, 69 | }, 70 | "secret": { 71 | Description: "A secret to be included with the event.", 72 | Type: schema.TypeString, 73 | Required: true, 74 | }, 75 | "provider_name": { 76 | Description: "Name of the email provider (If the provider is changed, the old provider's configuration will be deleted).", 77 | Type: schema.TypeString, 78 | Required: true, 79 | ValidateFunc: validators.ValidateProvider, 80 | }, 81 | "created_at": { 82 | Description: "The timestamp at which the permission was created.", 83 | Type: schema.TypeString, 84 | Computed: true, 85 | }, 86 | "updated_at": { 87 | Description: "The timestamp at which the permission was updated.", 88 | Type: schema.TypeString, 89 | Computed: true, 90 | }, 91 | }, 92 | CustomizeDiff: validators.ValidateRequiredFields, 93 | } 94 | 95 | } 96 | 97 | func resourceFronteggEmailProviderSerialize(d *schema.ResourceData) fronteggEmailProvider { 98 | return fronteggEmailProvider{ 99 | Payload: fronteggEmailProviderPayload{ 100 | Secret: d.Get("secret").(string), 101 | Provider: d.Get("provider_name").(string), 102 | Id: d.Get("provider_id").(string), 103 | Region: d.Get("region").(string), 104 | Domain: d.Get("domain").(string), 105 | }, 106 | } 107 | } 108 | 109 | func resourceFronteggEmailProviderDeserialize(d *schema.ResourceData, f *fronteggEmailProviderResponse) error { 110 | 111 | if err := d.Set("provider_name", d.Id()); err != nil { 112 | return err 113 | } 114 | 115 | fields := map[string]string{ 116 | "secret": f.Secret, 117 | "updated_at": f.UpdatedAt, 118 | "created_at": f.CreatedAt, 119 | } 120 | 121 | for key, value := range fields { 122 | if err := d.Set(key, value); err != nil { 123 | return fmt.Errorf("error setting %s: %s", key, err) 124 | } 125 | } 126 | 127 | return nil 128 | } 129 | 130 | func resourceFronteggEmailProviderCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 131 | clientHolder := meta.(*restclient.ClientHolder) 132 | in := resourceFronteggEmailProviderSerialize(d) 133 | provider := d.Get("provider_name").(string) 134 | d.SetId(provider) 135 | if err := clientHolder.ApiClient.Post(ctx, fronteggEmailPorivderPath, in, nil); err != nil { 136 | return diag.FromErr(err) 137 | } 138 | return nil 139 | } 140 | 141 | func resourceFronteggEmailProviderRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 142 | clientHolder := meta.(*restclient.ClientHolder) 143 | 144 | var out fronteggEmailProviderResponse 145 | if err := clientHolder.ApiClient.Get(ctx, fronteggEmailPorivderPathV1, &out); err != nil { 146 | return diag.FromErr(err) 147 | } 148 | if err := resourceFronteggEmailProviderDeserialize(d, &out); err != nil { 149 | return diag.FromErr(err) 150 | } 151 | 152 | return nil 153 | } 154 | 155 | func resourceFronteggEmailProviderUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 156 | clientHolder := meta.(*restclient.ClientHolder) 157 | in := resourceFronteggEmailProviderSerialize(d) 158 | providerName := "provider_name" 159 | 160 | provider := d.Get(providerName).(string) 161 | d.SetId(provider) 162 | 163 | if d.HasChange(providerName) { 164 | err := clientHolder.ApiClient.Delete(ctx, fronteggEmailPorivderPathV1, nil) 165 | if err != nil { 166 | return diag.FromErr(err) 167 | } 168 | } 169 | 170 | if err := clientHolder.ApiClient.Post(ctx, fronteggEmailPorivderPath, in, nil); err != nil { 171 | return diag.FromErr(err) 172 | } 173 | 174 | return nil 175 | } 176 | 177 | func resourceFronteggEmailProviderDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 178 | clientHolder := meta.(*restclient.ClientHolder) 179 | err := clientHolder.ApiClient.Delete(ctx, fronteggEmailPorivderPathV1, nil) 180 | if err != nil { 181 | return diag.FromErr(err) 182 | } 183 | 184 | return nil 185 | } 186 | -------------------------------------------------------------------------------- /provider/resource_frontegg_feature.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "sort" 7 | 8 | "github.com/frontegg/terraform-provider-frontegg/internal/restclient" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 11 | ) 12 | 13 | const fronteggFeaturePathPrefix = "/entitlements/resources/features" 14 | const fronteggFeaturePathV1 = fronteggFeaturePathPrefix + "/v1" 15 | const fronteggFeaturePathV2 = fronteggFeaturePathPrefix + "/v2" 16 | 17 | type fronteggFeature struct { 18 | ID string `json:"id"` 19 | Name string `json:"name"` 20 | Key string `json:"key"` 21 | Metadata map[string]interface{} `json:"metadata,omitempty"` 22 | Description string `json:"description,omitempty"` 23 | CreatedAt string `json:"createdAt,omitempty"` 24 | UpdatedAt string `json:"updatedAt,omitempty"` 25 | FeatureFlag *featureFlagThin `json:"featureFlag,omitempty"` 26 | } 27 | 28 | type fronteggFeatureV2 struct { 29 | fronteggFeature 30 | Permissions []permissionObject `json:"permissions,omitempty"` 31 | } 32 | 33 | type fronteggFeatureV1 struct { 34 | fronteggFeature 35 | Permissions []string `json:"permissions,omitempty"` 36 | } 37 | 38 | type permissionObject struct { 39 | PermissionKey string `json:"permissionKey"` 40 | PermissionID string `json:"permissionId"` 41 | } 42 | 43 | type featureFlagThin struct { 44 | ID string `json:"id"` 45 | Name string `json:"name"` 46 | On bool `json:"on"` 47 | OffTreatment string `json:"offTreatment"` 48 | DefaultTreatment string `json:"defaultTreatment"` 49 | Description string `json:"description,omitempty"` 50 | UpdatedAt string `json:"updatedAt,omitempty"` 51 | CreatedAt string `json:"createdAt,omitempty"` 52 | } 53 | 54 | func resourceFronteggFeature() *schema.Resource { 55 | return &schema.Resource{ 56 | Description: `Configures a Frontegg feature.`, 57 | 58 | CreateContext: resourceFronteggFeatureCreate, 59 | ReadContext: resourceFronteggFeatureRead, 60 | UpdateContext: resourceFronteggFeatureUpdate, 61 | DeleteContext: resourceFronteggFeatureDelete, 62 | Importer: &schema.ResourceImporter{ 63 | StateContext: schema.ImportStatePassthroughContext, 64 | }, 65 | 66 | Schema: map[string]*schema.Schema{ 67 | "id": { 68 | Description: "The ID of the feature (UUID).", 69 | Type: schema.TypeString, 70 | Computed: true, 71 | }, 72 | "name": { 73 | Description: "The name of the feature.", 74 | Type: schema.TypeString, 75 | Required: true, 76 | }, 77 | "key": { 78 | Description: "The key of the feature.", 79 | Type: schema.TypeString, 80 | Required: true, 81 | }, 82 | "description": { 83 | Description: "A description of the feature.", 84 | Type: schema.TypeString, 85 | Optional: true, 86 | }, 87 | "permissions": { 88 | Description: "The permissions for the feature.", 89 | Type: schema.TypeList, 90 | Optional: true, 91 | Elem: &schema.Resource{ 92 | Schema: map[string]*schema.Schema{ 93 | "permission_key": { 94 | Type: schema.TypeString, 95 | Required: true, 96 | Description: "The key of the permission", 97 | }, 98 | "permission_id": { 99 | Type: schema.TypeString, 100 | Required: true, 101 | Description: "The ID of the permission", 102 | }, 103 | }, 104 | }, 105 | }, 106 | "metadata": { 107 | Description: "Metadata for the feature.", 108 | Type: schema.TypeString, 109 | Optional: true, 110 | }, 111 | "created_at": { 112 | Description: "When the feature was created.", 113 | Type: schema.TypeString, 114 | Computed: true, 115 | }, 116 | "updated_at": { 117 | Description: "When the feature was last updated.", 118 | Type: schema.TypeString, 119 | Computed: true, 120 | }, 121 | }, 122 | } 123 | } 124 | 125 | func resourceFronteggFeatureSerialize(d *schema.ResourceData) fronteggFeatureV2 { 126 | feature := fronteggFeatureV2{ 127 | fronteggFeature: fronteggFeature{ 128 | Name: d.Get("name").(string), 129 | Key: d.Get("key").(string), 130 | Description: d.Get("description").(string), 131 | }, 132 | } 133 | 134 | // Handle permissions 135 | if permissions, ok := d.GetOk("permissions"); ok { 136 | permissionsList := permissions.([]interface{}) 137 | feature.Permissions = make([]permissionObject, len(permissionsList)) 138 | for i, perm := range permissionsList { 139 | permObj := perm.(map[string]interface{}) 140 | feature.Permissions[i] = permissionObject{ 141 | PermissionKey: permObj["permission_key"].(string), 142 | PermissionID: permObj["permission_id"].(string), 143 | } 144 | } 145 | // Sort permissions by key for consistent ordering 146 | sort.Slice(feature.Permissions, func(i, j int) bool { 147 | return feature.Permissions[i].PermissionKey < feature.Permissions[j].PermissionKey 148 | }) 149 | } 150 | 151 | // Handle metadata 152 | if metadata, ok := d.GetOk("metadata"); ok { 153 | metadataStr := metadata.(string) 154 | if metadataStr != "" { 155 | feature.Metadata = make(map[string]interface{}) 156 | } 157 | } 158 | 159 | return feature 160 | } 161 | 162 | func resourceFronteggFeatureDeserializeCommon(d *schema.ResourceData, name, key, description, createdAt, updatedAt string) error { 163 | d.SetId(d.Id()) 164 | if err := d.Set("name", name); err != nil { 165 | return err 166 | } 167 | if err := d.Set("key", key); err != nil { 168 | return err 169 | } 170 | if err := d.Set("description", description); err != nil { 171 | return err 172 | } 173 | if err := d.Set("created_at", createdAt); err != nil { 174 | return err 175 | } 176 | if err := d.Set("updated_at", updatedAt); err != nil { 177 | return err 178 | } 179 | return nil 180 | } 181 | 182 | // getPermissionsData fetches all permissions and returns a map of key to permission. 183 | func getPermissionsData(ctx context.Context, client *restclient.ClientHolder) (map[string]fronteggPermission, error) { 184 | var permissions []fronteggPermission 185 | if err := client.ApiClient.Get(ctx, fronteggPermissionPath, &permissions); err != nil { 186 | return nil, err 187 | } 188 | 189 | // Create a map of permission key to permission data 190 | permissionsMap := make(map[string]fronteggPermission) 191 | for _, p := range permissions { 192 | permissionsMap[p.Key] = p 193 | } 194 | return permissionsMap, nil 195 | } 196 | 197 | func resourceFronteggFeatureDeserializeV1(d *schema.ResourceData, f fronteggFeatureV1, client *restclient.ClientHolder, ctx context.Context) error { 198 | if err := resourceFronteggFeatureDeserializeCommon(d, f.Name, f.Key, f.Description, f.CreatedAt, f.UpdatedAt); err != nil { 199 | return err 200 | } 201 | 202 | // Handle permissions 203 | if len(f.Permissions) > 0 { 204 | // Get current permissions data 205 | permissionsMap, err := getPermissionsData(ctx, client) 206 | if err != nil { 207 | return err 208 | } 209 | 210 | // Get existing permissions from resource to maintain order 211 | existingPerms := d.Get("permissions").([]interface{}) 212 | permissions := make([]map[string]interface{}, len(f.Permissions)) 213 | for i, perm := range existingPerms { 214 | permMap := perm.(map[string]interface{}) 215 | //add only if exists in f.Permissions 216 | if _, ok := permissionsMap[permMap["permission_key"].(string)]; ok { 217 | permissions[i] = permMap 218 | } 219 | } 220 | 221 | if err := d.Set("permissions", permissions); err != nil { 222 | return err 223 | } 224 | } 225 | 226 | // Handle metadata 227 | if f.Metadata != nil { 228 | if err := d.Set("metadata", f.Metadata); err != nil { 229 | return err 230 | } 231 | } 232 | 233 | return nil 234 | } 235 | 236 | // findFeatureByKey attempts to find a feature by its key. 237 | func findFeatureByKey(ctx context.Context, client *restclient.ClientHolder, key string) (*fronteggFeatureV1, error) { 238 | type pageResponse struct { 239 | Items []fronteggFeatureV1 `json:"items"` 240 | HasNext bool `json:"hasNext"` 241 | } 242 | 243 | url := fmt.Sprintf("%s?key=%s&limit=1", fronteggFeaturePathV1, key) 244 | 245 | var searchResult pageResponse 246 | if err := client.ApiClient.Get(ctx, url, &searchResult); err != nil { 247 | return nil, err 248 | } 249 | 250 | if len(searchResult.Items) == 0 { 251 | return nil, nil 252 | } 253 | 254 | return &searchResult.Items[0], nil 255 | } 256 | 257 | func resourceFronteggFeatureCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 258 | clientHolder := meta.(*restclient.ClientHolder) 259 | key := d.Get("key").(string) 260 | 261 | // Check if feature exists 262 | existingFeature, err := findFeatureByKey(ctx, clientHolder, key) 263 | if err != nil { 264 | return diag.FromErr(err) 265 | } 266 | 267 | // If feature exists, update it 268 | if existingFeature != nil { 269 | d.SetId(existingFeature.ID) 270 | 271 | // Update the existing feature 272 | in := resourceFronteggFeatureSerialize(d) 273 | if err := clientHolder.ApiClient.Patch(ctx, fmt.Sprintf("%s/%s", fronteggFeaturePathV2, existingFeature.ID), in, nil); err != nil { 274 | return diag.FromErr(err) 275 | } 276 | 277 | return nil 278 | } 279 | 280 | // Feature doesn't exist, create new one 281 | in := resourceFronteggFeatureSerialize(d) 282 | var out fronteggFeatureV1 283 | if err := clientHolder.ApiClient.Post(ctx, fronteggFeaturePathV2, in, &out); err != nil { 284 | return diag.FromErr(err) 285 | } 286 | 287 | if err := resourceFronteggFeatureDeserializeV1(d, out, clientHolder, ctx); err != nil { 288 | return diag.FromErr(err) 289 | } 290 | 291 | return nil 292 | } 293 | 294 | func resourceFronteggFeatureRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 295 | clientHolder := meta.(*restclient.ClientHolder) 296 | 297 | // Create a struct to hold the paginated response 298 | type pageResponse struct { 299 | Items []fronteggFeatureV1 `json:"items"` 300 | HasNext bool `json:"hasNext"` 301 | } 302 | 303 | // Build the URL with query parameters 304 | url := fmt.Sprintf("%s?featureIds=%s&limit=1", fronteggFeaturePathV1, d.Id()) 305 | 306 | var out pageResponse 307 | if err := clientHolder.ApiClient.Get(ctx, url, &out); err != nil { 308 | return diag.FromErr(err) 309 | } 310 | 311 | // Check if we found the feature 312 | if len(out.Items) == 0 { 313 | return diag.Errorf("Feature with ID %s not found", d.Id()) 314 | } 315 | 316 | // Deserialize the first (and should be only) item 317 | if err := resourceFronteggFeatureDeserializeV1(d, out.Items[0], clientHolder, ctx); err != nil { 318 | return diag.FromErr(err) 319 | } 320 | 321 | return nil 322 | } 323 | 324 | func resourceFronteggFeatureUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 325 | clientHolder := meta.(*restclient.ClientHolder) 326 | 327 | in := resourceFronteggFeatureSerialize(d) 328 | if err := clientHolder.ApiClient.Patch(ctx, fmt.Sprintf("%s/%s", fronteggFeaturePathV2, d.Id()), in, nil); err != nil { 329 | return diag.FromErr(err) 330 | } 331 | 332 | // Refresh state by reading the resource after update 333 | return resourceFronteggFeatureRead(ctx, d, meta) 334 | } 335 | 336 | func resourceFronteggFeatureDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 337 | clientHolder := meta.(*restclient.ClientHolder) 338 | 339 | if err := clientHolder.ApiClient.Delete(ctx, fmt.Sprintf("%s/%s", fronteggFeaturePathV1, d.Id()), nil); err != nil { 340 | return diag.FromErr(err) 341 | } 342 | 343 | return nil 344 | } 345 | -------------------------------------------------------------------------------- /provider/resource_frontegg_firebase_user_source.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/frontegg/terraform-provider-frontegg/internal/restclient" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 10 | ) 11 | 12 | type fronteggFirebaseServiceAccountConfig struct { 13 | Type string `json:"type"` 14 | ProjectID string `json:"project_id"` 15 | PrivateKeyID string `json:"private_key_id"` 16 | PrivateKey string `json:"private_key"` 17 | ClientEmail string `json:"client_email"` 18 | ClientID string `json:"client_id"` 19 | AuthURI string `json:"auth_uri,omitempty"` 20 | TokenURI string `json:"token_uri,omitempty"` 21 | AuthProvider string `json:"auth_provider_x509_cert_url,omitempty"` 22 | ClientCert string `json:"client_x509_cert_url,omitempty"` 23 | UniverseDomain string `json:"universe_domain"` 24 | } 25 | 26 | type fronteggFirebaseUserSourceConfig struct { 27 | SyncOnLogin bool `json:"syncOnLogin"` 28 | IsMigrated bool `json:"isMigrated"` 29 | TenantConfig interface{} `json:"tenantConfig"` 30 | APIKey string `json:"apiKey"` 31 | ServiceAccount fronteggFirebaseServiceAccountConfig `json:"serviceAccount"` 32 | } 33 | 34 | type fronteggFirebaseUserSourceRequest struct { 35 | Name string `json:"name"` 36 | Configuration fronteggFirebaseUserSourceConfig `json:"configuration"` 37 | AppIDs []string `json:"appIds,omitempty"` 38 | Index int `json:"index"` 39 | Description string `json:"description,omitempty"` 40 | } 41 | 42 | const fronteggFirebaseUserSourcePath = "/identity/resources/user-sources/v1/external/firebase" 43 | 44 | func resourceFronteggFirebaseUserSource() *schema.Resource { 45 | baseSchema := userSourceBaseSchema() 46 | 47 | // Add Firebase-specific fields 48 | baseSchema["api_key"] = &schema.Schema{ 49 | Description: "The Firebase Web API Key.", 50 | Type: schema.TypeString, 51 | Required: true, 52 | } 53 | baseSchema["service_account_type"] = &schema.Schema{ 54 | Description: "Firebase service account type.", 55 | Type: schema.TypeString, 56 | Required: true, 57 | } 58 | baseSchema["project_id"] = &schema.Schema{ 59 | Description: "Firebase project ID.", 60 | Type: schema.TypeString, 61 | Required: true, 62 | } 63 | baseSchema["private_key_id"] = &schema.Schema{ 64 | Description: "Firebase service account private key ID.", 65 | Type: schema.TypeString, 66 | Required: true, 67 | } 68 | baseSchema["private_key"] = &schema.Schema{ 69 | Description: "Firebase service account private key.", 70 | Type: schema.TypeString, 71 | Required: true, 72 | Sensitive: true, 73 | } 74 | baseSchema["client_email"] = &schema.Schema{ 75 | Description: "Firebase service account client email.", 76 | Type: schema.TypeString, 77 | Required: true, 78 | } 79 | baseSchema["client_id"] = &schema.Schema{ 80 | Description: "Firebase service account client ID.", 81 | Type: schema.TypeString, 82 | Required: true, 83 | } 84 | baseSchema["auth_uri"] = &schema.Schema{ 85 | Description: "Firebase service account auth URI.", 86 | Type: schema.TypeString, 87 | Optional: true, 88 | } 89 | baseSchema["token_uri"] = &schema.Schema{ 90 | Description: "Firebase service account token URI.", 91 | Type: schema.TypeString, 92 | Optional: true, 93 | } 94 | baseSchema["auth_provider_x509_cert_url"] = &schema.Schema{ 95 | Description: "Firebase service account auth provider x509 cert URL.", 96 | Type: schema.TypeString, 97 | Optional: true, 98 | } 99 | baseSchema["client_x509_cert_url"] = &schema.Schema{ 100 | Description: "Firebase service account client x509 cert URL.", 101 | Type: schema.TypeString, 102 | Optional: true, 103 | } 104 | baseSchema["universe_domain"] = &schema.Schema{ 105 | Description: "Firebase service account universe domain.", 106 | Type: schema.TypeString, 107 | Required: true, 108 | } 109 | 110 | return &schema.Resource{ 111 | Description: `Configures a Frontegg Firebase user source.`, 112 | 113 | CreateContext: resourceFronteggFirebaseUserSourceCreate, 114 | ReadContext: resourceFronteggFirebaseUserSourceRead, 115 | UpdateContext: resourceFronteggFirebaseUserSourceUpdate, 116 | DeleteContext: resourceFronteggFirebaseUserSourceDelete, 117 | Importer: &schema.ResourceImporter{ 118 | StateContext: schema.ImportStatePassthroughContext, 119 | }, 120 | Schema: baseSchema, 121 | } 122 | } 123 | 124 | func resourceFronteggFirebaseUserSourceSerialize(d *schema.ResourceData) (fronteggFirebaseUserSourceRequest, error) { 125 | appIDs := extractAppIDs(d) 126 | 127 | tenantConfig, err := buildUserSourceTenantConfig(d) 128 | if err != nil { 129 | return fronteggFirebaseUserSourceRequest{}, err 130 | } 131 | 132 | serviceAccountConfig := fronteggFirebaseServiceAccountConfig{ 133 | Type: d.Get("service_account_type").(string), 134 | ProjectID: d.Get("project_id").(string), 135 | PrivateKeyID: d.Get("private_key_id").(string), 136 | PrivateKey: d.Get("private_key").(string), 137 | ClientEmail: d.Get("client_email").(string), 138 | ClientID: d.Get("client_id").(string), 139 | AuthURI: d.Get("auth_uri").(string), 140 | TokenURI: d.Get("token_uri").(string), 141 | AuthProvider: d.Get("auth_provider_x509_cert_url").(string), 142 | ClientCert: d.Get("client_x509_cert_url").(string), 143 | UniverseDomain: d.Get("universe_domain").(string), 144 | } 145 | 146 | config := fronteggFirebaseUserSourceConfig{ 147 | SyncOnLogin: d.Get("sync_on_login").(bool), 148 | IsMigrated: d.Get("is_migrated").(bool), 149 | APIKey: d.Get("api_key").(string), 150 | TenantConfig: tenantConfig, 151 | ServiceAccount: serviceAccountConfig, 152 | } 153 | 154 | return fronteggFirebaseUserSourceRequest{ 155 | Name: d.Get("name").(string), 156 | Configuration: config, 157 | AppIDs: appIDs, 158 | Index: d.Get("index").(int), 159 | Description: d.Get("description").(string), 160 | }, nil 161 | } 162 | 163 | func resourceFronteggFirebaseUserSourceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 164 | clientHolder := meta.(*restclient.ClientHolder) 165 | in, err := resourceFronteggFirebaseUserSourceSerialize(d) 166 | if err != nil { 167 | return diag.FromErr(err) 168 | } 169 | 170 | var out fronteggBaseUserSourceResponse 171 | if err := clientHolder.ApiClient.Post(ctx, fronteggFirebaseUserSourcePath, in, &out); err != nil { 172 | return diag.FromErr(err) 173 | } 174 | 175 | if err := deserializeUserSourceResponse(d, out); err != nil { 176 | return diag.FromErr(err) 177 | } 178 | 179 | return nil 180 | } 181 | 182 | func resourceFronteggFirebaseUserSourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 183 | return readUserSource(ctx, d, meta, deserializeUserSourceResponse) 184 | } 185 | 186 | func resourceFronteggFirebaseUserSourceUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 187 | clientHolder := meta.(*restclient.ClientHolder) 188 | in, err := resourceFronteggFirebaseUserSourceSerialize(d) 189 | if err != nil { 190 | return diag.FromErr(err) 191 | } 192 | 193 | if err := clientHolder.ApiClient.Put(ctx, fmt.Sprintf("%s/%s", fronteggFirebaseUserSourcePath, d.Id()), in, nil); err != nil { 194 | return diag.FromErr(err) 195 | } 196 | 197 | return resourceFronteggFirebaseUserSourceRead(ctx, d, meta) 198 | } 199 | 200 | func resourceFronteggFirebaseUserSourceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 201 | return deleteUserSource(ctx, d, meta) 202 | } 203 | -------------------------------------------------------------------------------- /provider/resource_frontegg_permission.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/frontegg/terraform-provider-frontegg/internal/restclient" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 10 | ) 11 | 12 | const fronteggPermissionPath = "/identity/resources/permissions/v1" 13 | 14 | type fronteggPermission struct { 15 | ID string `json:"id,omitempty"` 16 | CategoryID string `json:"categoryId,omitempty"` 17 | Name string `json:"name,omitempty"` 18 | Key string `json:"key,omitempty"` 19 | Description string `json:"description,omitempty"` 20 | CreatedAt string `json:"createdAt,omitempty"` 21 | } 22 | 23 | func resourceFronteggPermission() *schema.Resource { 24 | return &schema.Resource{ 25 | Description: `Configures a Frontegg permission.`, 26 | 27 | CreateContext: resourceFronteggPermissionCreate, 28 | ReadContext: resourceFronteggPermissionRead, 29 | UpdateContext: resourceFronteggPermissionUpdate, 30 | DeleteContext: resourceFronteggPermissionDelete, 31 | Importer: &schema.ResourceImporter{ 32 | StateContext: schema.ImportStatePassthroughContext, 33 | }, 34 | 35 | Schema: map[string]*schema.Schema{ 36 | "name": { 37 | Description: "A human-readable name for the permission.", 38 | Type: schema.TypeString, 39 | Required: true, 40 | }, 41 | "key": { 42 | Description: "A human-readable identifier for the permission.", 43 | Type: schema.TypeString, 44 | Required: true, 45 | }, 46 | "category_id": { 47 | Description: "The identifier of the category to which this permission belongs.", 48 | Type: schema.TypeString, 49 | Required: true, 50 | }, 51 | "description": { 52 | Description: "A human-readable description of the permission.", 53 | Type: schema.TypeString, 54 | Required: true, 55 | }, 56 | "created_at": { 57 | Description: "The timestamp at which the permission was created.", 58 | Type: schema.TypeString, 59 | Computed: true, 60 | }, 61 | }, 62 | } 63 | } 64 | 65 | func resourceFronteggPermissionSerialize(d *schema.ResourceData) fronteggPermission { 66 | return fronteggPermission{ 67 | Name: d.Get("name").(string), 68 | Key: d.Get("key").(string), 69 | CategoryID: d.Get("category_id").(string), 70 | Description: d.Get("description").(string), 71 | } 72 | } 73 | 74 | func resourceFronteggPermissionDeserialize(d *schema.ResourceData, f fronteggPermission) error { 75 | d.SetId(f.ID) 76 | if err := d.Set("name", f.Name); err != nil { 77 | return err 78 | } 79 | if err := d.Set("key", f.Key); err != nil { 80 | return err 81 | } 82 | if err := d.Set("category_id", f.CategoryID); err != nil { 83 | return err 84 | } 85 | if err := d.Set("description", f.Description); err != nil { 86 | return err 87 | } 88 | if err := d.Set("created_at", f.CreatedAt); err != nil { 89 | return err 90 | } 91 | return nil 92 | } 93 | 94 | func resourceFronteggPermissionCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 95 | clientHolder := meta.(*restclient.ClientHolder) 96 | in := []fronteggPermission{resourceFronteggPermissionSerialize(d)} 97 | var out []fronteggPermission 98 | if err := clientHolder.ApiClient.Post(ctx, fronteggPermissionPath, in, &out); err != nil { 99 | return diag.FromErr(err) 100 | } 101 | if len(out) != 1 { 102 | return diag.Errorf("server returned unexpected number of results when creating permission: %d", len(out)) 103 | } 104 | if err := resourceFronteggPermissionDeserialize(d, out[0]); err != nil { 105 | return diag.FromErr(err) 106 | } 107 | return nil 108 | } 109 | 110 | func resourceFronteggPermissionRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 111 | clientHolder := meta.(*restclient.ClientHolder) 112 | var out []fronteggPermission 113 | if err := clientHolder.ApiClient.Get(ctx, fronteggPermissionPath, &out); err != nil { 114 | return diag.FromErr(err) 115 | } 116 | for _, c := range out { 117 | if c.ID == d.Id() { 118 | if err := resourceFronteggPermissionDeserialize(d, c); err != nil { 119 | return diag.FromErr(err) 120 | } 121 | return diag.Diagnostics{} 122 | } 123 | } 124 | d.SetId("") 125 | return nil 126 | } 127 | 128 | func resourceFronteggPermissionUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 129 | clientHolder := meta.(*restclient.ClientHolder) 130 | in := resourceFronteggPermissionSerialize(d) 131 | if err := clientHolder.ApiClient.Patch(ctx, fmt.Sprintf("%s/%s", fronteggPermissionPath, d.Id()), in, nil); err != nil { 132 | return diag.FromErr(err) 133 | } 134 | return resourceFronteggPermissionRead(ctx, d, meta) 135 | } 136 | 137 | func resourceFronteggPermissionDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 138 | clientHolder := meta.(*restclient.ClientHolder) 139 | if err := clientHolder.ApiClient.Delete(ctx, fmt.Sprintf("%s/%s", fronteggPermissionPath, d.Id()), nil); err != nil { 140 | return diag.FromErr(err) 141 | } 142 | return nil 143 | } 144 | -------------------------------------------------------------------------------- /provider/resource_frontegg_permission_category.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/frontegg/terraform-provider-frontegg/internal/restclient" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 10 | ) 11 | 12 | const fronteggPermissionCategoryPath = "/identity/resources/permissions/v1/categories" 13 | 14 | type fronteggPermissionCategory struct { 15 | ID string `json:"id,omitempty"` 16 | Name string `json:"name,omitempty"` 17 | Description string `json:"description,omitempty"` 18 | CreatedAt string `json:"createdAt,omitempty"` 19 | } 20 | 21 | func resourceFronteggPermissionCategory() *schema.Resource { 22 | return &schema.Resource{ 23 | Description: `Configures a Frontegg permission category.`, 24 | 25 | CreateContext: resourceFronteggPermissionCategoryCreate, 26 | ReadContext: resourceFronteggPermissionCategoryRead, 27 | UpdateContext: resourceFronteggPermissionCategoryUpdate, 28 | DeleteContext: resourceFronteggPermissionCategoryDelete, 29 | Importer: &schema.ResourceImporter{ 30 | StateContext: schema.ImportStatePassthroughContext, 31 | }, 32 | 33 | Schema: map[string]*schema.Schema{ 34 | "name": { 35 | Description: "A human-readable name for the permission category.", 36 | Type: schema.TypeString, 37 | Required: true, 38 | }, 39 | "description": { 40 | Description: "A human-readable description of the permission category.", 41 | Type: schema.TypeString, 42 | Required: true, 43 | }, 44 | "created_at": { 45 | Description: "The timestamp at which the permission category was created.", 46 | Type: schema.TypeString, 47 | Computed: true, 48 | }, 49 | }, 50 | } 51 | } 52 | 53 | func resourceFronteggPermissionCategorySerialize(d *schema.ResourceData) fronteggPermissionCategory { 54 | return fronteggPermissionCategory{ 55 | Name: d.Get("name").(string), 56 | Description: d.Get("description").(string), 57 | } 58 | } 59 | 60 | func resourceFronteggPermissionCategoryDeserialize(d *schema.ResourceData, f fronteggPermissionCategory) error { 61 | d.SetId(f.ID) 62 | if err := d.Set("name", f.Name); err != nil { 63 | return err 64 | } 65 | if err := d.Set("description", f.Description); err != nil { 66 | return err 67 | } 68 | if err := d.Set("created_at", f.CreatedAt); err != nil { 69 | return err 70 | } 71 | return nil 72 | } 73 | 74 | func resourceFronteggPermissionCategoryCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 75 | clientHolder := meta.(*restclient.ClientHolder) 76 | in := resourceFronteggPermissionCategorySerialize(d) 77 | var out fronteggPermissionCategory 78 | if err := clientHolder.ApiClient.Post(ctx, fronteggPermissionCategoryPath, in, &out); err != nil { 79 | return diag.FromErr(err) 80 | } 81 | if err := resourceFronteggPermissionCategoryDeserialize(d, out); err != nil { 82 | return diag.FromErr(err) 83 | } 84 | return nil 85 | } 86 | 87 | func resourceFronteggPermissionCategoryRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 88 | clientHolder := meta.(*restclient.ClientHolder) 89 | var out []fronteggPermissionCategory 90 | if err := clientHolder.ApiClient.Get(ctx, fronteggPermissionCategoryPath, &out); err != nil { 91 | return diag.FromErr(err) 92 | } 93 | for _, c := range out { 94 | if c.ID == d.Id() { 95 | if err := resourceFronteggPermissionCategoryDeserialize(d, c); err != nil { 96 | return diag.FromErr(err) 97 | } 98 | return diag.Diagnostics{} 99 | } 100 | } 101 | d.SetId("") 102 | return nil 103 | } 104 | 105 | func resourceFronteggPermissionCategoryUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 106 | clientHolder := meta.(*restclient.ClientHolder) 107 | in := resourceFronteggPermissionCategorySerialize(d) 108 | if err := clientHolder.ApiClient.Patch(ctx, fmt.Sprintf("%s/%s", fronteggPermissionCategoryPath, d.Id()), in, nil); err != nil { 109 | return diag.FromErr(err) 110 | } 111 | return resourceFronteggPermissionCategoryRead(ctx, d, meta) 112 | } 113 | 114 | func resourceFronteggPermissionCategoryDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 115 | clientHolder := meta.(*restclient.ClientHolder) 116 | if err := clientHolder.ApiClient.Delete(ctx, fmt.Sprintf("%s/%s", fronteggPermissionCategoryPath, d.Id()), nil); err != nil { 117 | return diag.FromErr(err) 118 | } 119 | return nil 120 | } 121 | -------------------------------------------------------------------------------- /provider/resource_frontegg_plan.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/frontegg/terraform-provider-frontegg/internal/restclient" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" 11 | ) 12 | 13 | const fronteggPlanPath = "/entitlements/resources/plans/v1" 14 | 15 | type fronteggPlan struct { 16 | ID string `json:"id,omitempty"` 17 | VendorID string `json:"vendorId,omitempty"` 18 | Name string `json:"name"` 19 | DefaultTreatment string `json:"defaultTreatment,omitempty"` 20 | Rules []map[string]interface{} `json:"rules,omitempty"` 21 | Description string `json:"description,omitempty"` 22 | DefaultTimeLimitation int `json:"defaultTimeLimitation,omitempty"` 23 | AssignOnSignup bool `json:"assignOnSignup"` 24 | CreatedAt string `json:"createdAt,omitempty"` 25 | UpdatedAt string `json:"updatedAt,omitempty"` 26 | FeatureKeys []string `json:"featureKeys,omitempty"` 27 | } 28 | 29 | func resourceFronteggPlan() *schema.Resource { 30 | return &schema.Resource{ 31 | Description: `Configures a Frontegg plan.`, 32 | 33 | CreateContext: resourceFronteggPlanCreate, 34 | ReadContext: resourceFronteggPlanRead, 35 | UpdateContext: resourceFronteggPlanUpdate, 36 | DeleteContext: resourceFronteggPlanDelete, 37 | Importer: &schema.ResourceImporter{ 38 | StateContext: schema.ImportStatePassthroughContext, 39 | }, 40 | 41 | Schema: map[string]*schema.Schema{ 42 | "name": { 43 | Description: "The name of the plan.", 44 | Type: schema.TypeString, 45 | Required: true, 46 | }, 47 | "default_treatment": { 48 | Description: "The default treatment for the plan.", 49 | Type: schema.TypeString, 50 | Optional: true, 51 | ValidateFunc: validation.StringInSlice([]string{ 52 | "true", 53 | "false", 54 | }, false), 55 | }, 56 | "rules": { 57 | Description: "Set of conditions targeting the plan.", 58 | Type: schema.TypeList, 59 | Optional: true, 60 | Elem: &schema.Schema{ 61 | Type: schema.TypeMap, 62 | Elem: &schema.Schema{ 63 | Type: schema.TypeString, 64 | }, 65 | }, 66 | }, 67 | "description": { 68 | Description: "A description of the plan.", 69 | Type: schema.TypeString, 70 | Optional: true, 71 | }, 72 | "default_time_limitation": { 73 | Description: "Default time limitation in days for auto-assigned plans.", 74 | Type: schema.TypeInt, 75 | Optional: true, 76 | }, 77 | "assign_on_signup": { 78 | Description: "Whether the plan is assigned automatically upon signup.", 79 | Type: schema.TypeBool, 80 | Optional: true, 81 | Default: false, 82 | }, 83 | "feature_keys": { 84 | Description: "Array of feature keys to be applied on the plan.", 85 | Type: schema.TypeList, 86 | Optional: true, 87 | Elem: &schema.Schema{ 88 | Type: schema.TypeString, 89 | }, 90 | }, 91 | "vendor_id": { 92 | Description: "The vendor ID for the plan.", 93 | Type: schema.TypeString, 94 | Computed: true, 95 | }, 96 | "created_at": { 97 | Description: "When the plan was created.", 98 | Type: schema.TypeString, 99 | Computed: true, 100 | }, 101 | "updated_at": { 102 | Description: "When the plan was last updated.", 103 | Type: schema.TypeString, 104 | Computed: true, 105 | }, 106 | }, 107 | } 108 | } 109 | 110 | func resourceFronteggPlanSerialize(d *schema.ResourceData) fronteggPlan { 111 | var rules []map[string]interface{} 112 | if v, ok := d.GetOk("rules"); ok { 113 | rawRules := v.([]interface{}) 114 | rules = make([]map[string]interface{}, len(rawRules)) 115 | for i, r := range rawRules { 116 | rules[i] = r.(map[string]interface{}) 117 | } 118 | } 119 | 120 | var featureKeys []string 121 | if v, ok := d.GetOk("feature_keys"); ok { 122 | rawFeatureKeys := v.([]interface{}) 123 | featureKeys = make([]string, len(rawFeatureKeys)) 124 | for i, k := range rawFeatureKeys { 125 | featureKeys[i] = k.(string) 126 | } 127 | } 128 | 129 | return fronteggPlan{ 130 | Name: d.Get("name").(string), 131 | DefaultTreatment: d.Get("default_treatment").(string), 132 | Rules: rules, 133 | Description: d.Get("description").(string), 134 | DefaultTimeLimitation: d.Get("default_time_limitation").(int), 135 | AssignOnSignup: d.Get("assign_on_signup").(bool), 136 | FeatureKeys: featureKeys, 137 | } 138 | } 139 | 140 | func resourceFronteggPlanDeserialize(d *schema.ResourceData, f fronteggPlan) error { 141 | d.SetId(f.ID) 142 | if err := d.Set("name", f.Name); err != nil { 143 | return err 144 | } 145 | if err := d.Set("default_treatment", f.DefaultTreatment); err != nil { 146 | return err 147 | } 148 | if err := d.Set("description", f.Description); err != nil { 149 | return err 150 | } 151 | if err := d.Set("default_time_limitation", f.DefaultTimeLimitation); err != nil { 152 | return err 153 | } 154 | if err := d.Set("assign_on_signup", f.AssignOnSignup); err != nil { 155 | return err 156 | } 157 | if err := d.Set("vendor_id", f.VendorID); err != nil { 158 | return err 159 | } 160 | if err := d.Set("created_at", f.CreatedAt); err != nil { 161 | return err 162 | } 163 | if err := d.Set("updated_at", f.UpdatedAt); err != nil { 164 | return err 165 | } 166 | 167 | // Handle rules which is a slice of maps 168 | if f.Rules != nil { 169 | if err := d.Set("rules", f.Rules); err != nil { 170 | return err 171 | } 172 | } 173 | 174 | // Feature keys are not returned in the GET response format, so we need to 175 | // preserve the value that's in the state 176 | 177 | return nil 178 | } 179 | 180 | func resourceFronteggPlanCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 181 | clientHolder := meta.(*restclient.ClientHolder) 182 | 183 | in := resourceFronteggPlanSerialize(d) 184 | 185 | // Check if a plan with this name already exists 186 | planName := d.Get("name").(string) 187 | existingPlan, err := fetchFronteggPlanByName(ctx, planName, clientHolder) 188 | if err != nil { 189 | return diag.FromErr(err) 190 | } 191 | 192 | // If plan already exists, set the ID and update it 193 | if existingPlan != nil { 194 | d.SetId(existingPlan.ID) 195 | return resourceFronteggPlanUpdate(ctx, d, meta) 196 | } 197 | 198 | var out fronteggPlan 199 | if err := clientHolder.ApiClient.Post(ctx, fronteggPlanPath, in, &out); err != nil { 200 | return diag.FromErr(err) 201 | } 202 | 203 | if err := resourceFronteggPlanDeserialize(d, out); err != nil { 204 | return diag.FromErr(err) 205 | } 206 | 207 | return nil 208 | } 209 | 210 | func fetchFronteggPlan(ctx context.Context, planID string, clientHolder *restclient.ClientHolder) (*fronteggPlan, error) { 211 | var out fronteggPlan 212 | if err := clientHolder.ApiClient.Get(ctx, fmt.Sprintf("%s/%s", fronteggPlanPath, planID), &out); err != nil { 213 | return nil, err 214 | } 215 | return &out, nil 216 | } 217 | 218 | func fetchAllFronteggPlans(ctx context.Context, clientHolder *restclient.ClientHolder) ([]fronteggPlan, error) { 219 | // Response structure from entitlements API 220 | type pageResponse struct { 221 | Items []fronteggPlan `json:"items"` 222 | HasNext bool `json:"hasNext"` 223 | } 224 | 225 | var allPlans []fronteggPlan 226 | offset := 0 227 | limit := 10 228 | 229 | for { 230 | var response pageResponse 231 | url := fmt.Sprintf("%s?offset=%d&limit=%d", fronteggPlanPath, offset, limit) 232 | 233 | if err := clientHolder.ApiClient.Get(ctx, url, &response); err != nil { 234 | return nil, err 235 | } 236 | 237 | allPlans = append(allPlans, response.Items...) 238 | 239 | if !response.HasNext { 240 | break 241 | } 242 | 243 | offset += limit 244 | } 245 | 246 | return allPlans, nil 247 | } 248 | 249 | func fetchFronteggPlanByName(ctx context.Context, planName string, clientHolder *restclient.ClientHolder) (*fronteggPlan, error) { 250 | plans, err := fetchAllFronteggPlans(ctx, clientHolder) 251 | if err != nil { 252 | return nil, err 253 | } 254 | 255 | for _, plan := range plans { 256 | if plan.Name == planName { 257 | return &plan, nil 258 | } 259 | } 260 | 261 | return nil, nil // Plan not found 262 | } 263 | 264 | func resourceFronteggPlanRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 265 | clientHolder := meta.(*restclient.ClientHolder) 266 | 267 | plan, err := fetchFronteggPlan(ctx, d.Id(), clientHolder) 268 | if err != nil { 269 | return diag.FromErr(err) 270 | } 271 | 272 | if err := resourceFronteggPlanDeserialize(d, *plan); err != nil { 273 | return diag.FromErr(err) 274 | } 275 | 276 | return nil 277 | } 278 | 279 | func resourceFronteggPlanUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 280 | clientHolder := meta.(*restclient.ClientHolder) 281 | in := resourceFronteggPlanSerialize(d) 282 | var out fronteggPlan 283 | if err := clientHolder.ApiClient.Patch(ctx, fmt.Sprintf("%s/%s", fronteggPlanPath, d.Id()), in, &out); err != nil { 284 | return diag.FromErr(err) 285 | } 286 | 287 | if err := resourceFronteggPlanDeserialize(d, out); err != nil { 288 | return diag.FromErr(err) 289 | } 290 | 291 | return nil 292 | } 293 | 294 | func resourceFronteggPlanDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 295 | clientHolder := meta.(*restclient.ClientHolder) 296 | 297 | if err := clientHolder.ApiClient.Delete(ctx, fmt.Sprintf("%s/%s", fronteggPlanPath, d.Id()), nil); err != nil { 298 | return diag.FromErr(err) 299 | } 300 | 301 | return nil 302 | } 303 | -------------------------------------------------------------------------------- /provider/resource_frontegg_plan_feature.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/frontegg/terraform-provider-frontegg/internal/restclient" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 11 | ) 12 | 13 | func resourceFronteggPlanFeature() *schema.Resource { 14 | return &schema.Resource{ 15 | Description: `Links features to a Frontegg plan.`, 16 | 17 | CreateContext: resourceFronteggPlanFeatureCreate, 18 | ReadContext: resourceFronteggPlanFeatureRead, 19 | DeleteContext: resourceFronteggPlanFeatureDelete, 20 | Importer: &schema.ResourceImporter{ 21 | StateContext: schema.ImportStatePassthroughContext, 22 | }, 23 | 24 | Schema: map[string]*schema.Schema{ 25 | "plan_id": { 26 | Description: "The ID of the plan.", 27 | Type: schema.TypeString, 28 | Required: true, 29 | ForceNew: true, 30 | }, 31 | "feature_ids": { 32 | Description: "The IDs of the features to link to the plan.", 33 | Type: schema.TypeSet, 34 | Elem: &schema.Schema{ 35 | Type: schema.TypeString, 36 | }, 37 | Required: true, 38 | ForceNew: true, 39 | }, 40 | }, 41 | } 42 | } 43 | 44 | func resourceFronteggPlanFeatureCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 45 | clientHolder := meta.(*restclient.ClientHolder) 46 | 47 | planID := d.Get("plan_id").(string) 48 | featureIDsSet := d.Get("feature_ids").(*schema.Set) 49 | featureIDs := make([]string, 0, featureIDsSet.Len()) 50 | 51 | for _, v := range featureIDsSet.List() { 52 | featureIDs = append(featureIDs, v.(string)) 53 | } 54 | 55 | // API expects an array of feature IDs 56 | in := struct { 57 | FeatureIds []string `json:"featuresIds"` 58 | }{ 59 | FeatureIds: featureIDs, 60 | } 61 | 62 | // Link the features to the plan 63 | if err := clientHolder.ApiClient.Patch(ctx, fmt.Sprintf("/entitlements/resources/plans/v1/%s/features/link", planID), in, nil); err != nil { 64 | return diag.FromErr(err) 65 | } 66 | 67 | // Set only the plan ID as the resource ID 68 | d.SetId(planID) 69 | 70 | return nil 71 | } 72 | 73 | func resourceFronteggPlanFeatureRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 74 | clientHolder := meta.(*restclient.ClientHolder) 75 | 76 | // Use plan ID directly as the resource ID 77 | planID := d.Id() 78 | 79 | // Set the plan ID in the state 80 | if err := d.Set("plan_id", planID); err != nil { 81 | return diag.FromErr(err) 82 | } 83 | 84 | // Get the list of features for the plan - using only plan ID as per OpenAPI spec 85 | var features struct { 86 | Items []struct { 87 | ID string `json:"id"` 88 | } `json:"items"` 89 | } 90 | 91 | if err := clientHolder.ApiClient.Get(ctx, fmt.Sprintf("/entitlements/resources/plans/v1/%s/features", planID), &features); err != nil { 92 | return diag.FromErr(err) 93 | } 94 | 95 | // Get the current feature IDs from the state 96 | featureIDsSet := d.Get("feature_ids").(*schema.Set) 97 | stateFeatureIDs := make([]string, 0, featureIDsSet.Len()) 98 | for _, v := range featureIDsSet.List() { 99 | stateFeatureIDs = append(stateFeatureIDs, v.(string)) 100 | } 101 | 102 | // Create a map of feature IDs from the API response 103 | existingFeatureIDs := make(map[string]bool) 104 | for _, feature := range features.Items { 105 | existingFeatureIDs[feature.ID] = true 106 | } 107 | 108 | // Check if all the features exist in the list 109 | allFeaturesFound := true 110 | for _, featureID := range stateFeatureIDs { 111 | if !existingFeatureIDs[featureID] { 112 | allFeaturesFound = false 113 | break 114 | } 115 | } 116 | 117 | // If not all features are found, mark the resource as gone 118 | if !allFeaturesFound { 119 | d.SetId("") 120 | } 121 | 122 | return nil 123 | } 124 | 125 | func resourceFronteggPlanFeatureDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 126 | clientHolder := meta.(*restclient.ClientHolder) 127 | 128 | // Use plan ID directly as the resource ID 129 | planID := d.Id() 130 | 131 | // Get feature IDs from the state 132 | featureIDsSet := d.Get("feature_ids").(*schema.Set) 133 | featureIDs := make([]string, 0, featureIDsSet.Len()) 134 | for _, v := range featureIDsSet.List() { 135 | featureIDs = append(featureIDs, v.(string)) 136 | } 137 | 138 | // API expects an array of feature IDs 139 | in := struct { 140 | FeatureIds []string `json:"featuresIds"` 141 | }{ 142 | FeatureIds: featureIDs, 143 | } 144 | 145 | // Unlink the features from the plan 146 | err := clientHolder.ApiClient.Patch(ctx, fmt.Sprintf("/entitlements/resources/plans/v1/%s/features/unlink", planID), in, nil) 147 | if err != nil { 148 | if err.Error() != "" && strings.Contains(err.Error(), "Feature Bundle not found") { 149 | return nil 150 | } 151 | return diag.FromErr(err) 152 | } 153 | 154 | return nil 155 | } 156 | 157 | // These functions are no longer needed since we're using only the plan ID 158 | // Keeping them commented out in case they need to be referenced later 159 | /* 160 | // formatPlanFeaturesID creates a composite ID in the format planID:featureID1,featureID2,... 161 | func formatPlanFeaturesID(planID string, featureIDs []string) string { 162 | return fmt.Sprintf("%s:%s", planID, strings.Join(featureIDs, ",")) 163 | } 164 | 165 | // parsePlanFeaturesID parses the composite ID (planID:featureID1,featureID2,...) 166 | func parsePlanFeaturesID(id string) (string, []string, error) { 167 | // Split the ID by colon 168 | parts := strings.Split(id, ":") 169 | if len(parts) != 2 { 170 | return "", nil, fmt.Errorf("invalid ID format: %s (expected plan_id:feature_id1,feature_id2,...)", id) 171 | } 172 | 173 | planID := parts[0] 174 | featureIDs := strings.Split(parts[1], ",") 175 | 176 | return planID, featureIDs, nil 177 | } 178 | */ 179 | -------------------------------------------------------------------------------- /provider/resource_frontegg_redirect_uri.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/frontegg/terraform-provider-frontegg/internal/restclient" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 10 | ) 11 | 12 | const fronteggRedirectUriPath = "/oauth/resources/configurations/v1/redirect-uri" 13 | 14 | type fronteggRedirectUri struct { 15 | RedirectUri string `json:"redirectUri,omitempty"` 16 | Key string `json:"id,omitempty"` 17 | } 18 | 19 | func resourceFronteggRedirectUri() *schema.Resource { 20 | return &schema.Resource{ 21 | Description: `Configures a Frontegg Redirect URI.`, 22 | 23 | CreateContext: resourceFronteggRedirectUriCreate, 24 | ReadContext: resourceFronteggRedirectUriRead, 25 | DeleteContext: resourceFronteggRedirectUriDelete, 26 | Importer: &schema.ResourceImporter{ 27 | StateContext: schema.ImportStatePassthroughContext, 28 | }, 29 | 30 | Schema: map[string]*schema.Schema{ 31 | "redirect_uri": { 32 | Description: "The redirect URI.", 33 | Type: schema.TypeString, 34 | Required: true, 35 | ForceNew: true, 36 | }, 37 | "key": { 38 | Description: "The redirect URI key.", 39 | Type: schema.TypeString, 40 | Computed: true, 41 | }, 42 | }, 43 | } 44 | } 45 | 46 | func resourceFronteggRedirectUriSerialize(d *schema.ResourceData) fronteggRedirectUri { 47 | return fronteggRedirectUri{ 48 | Key: d.Get("key").(string), 49 | RedirectUri: d.Get("redirect_uri").(string), 50 | } 51 | } 52 | 53 | func resourceFronteggRedirectUriDeserialize(d *schema.ResourceData, f fronteggRedirectUri) error { 54 | d.SetId(f.Key) 55 | if err := d.Set("key", f.Key); err != nil { 56 | return err 57 | } 58 | if err := d.Set("redirect_uri", f.RedirectUri); err != nil { 59 | return err 60 | } 61 | return nil 62 | } 63 | 64 | func resourceFronteggRedirectUriCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 65 | clientHolder := meta.(*restclient.ClientHolder) 66 | in := resourceFronteggRedirectUriSerialize(d) 67 | if err := clientHolder.ApiClient.Post(ctx, fronteggRedirectUriPath, in, nil); err != nil { 68 | return diag.FromErr(err) 69 | } 70 | var out struct { 71 | RedirectURIs []fronteggRedirectUri `json:"redirectUris"` 72 | } 73 | if err := clientHolder.ApiClient.Get(ctx, fronteggRedirectUriPath, &out); err != nil { 74 | return diag.FromErr(err) 75 | } 76 | for _, c := range out.RedirectURIs { 77 | if c.RedirectUri == in.RedirectUri || c.RedirectUri == fmt.Sprintf("%s/", in.RedirectUri) { 78 | if err := resourceFronteggRedirectUriDeserialize(d, c); err != nil { 79 | return diag.FromErr(err) 80 | } 81 | return nil 82 | } 83 | } 84 | return nil 85 | } 86 | 87 | func resourceFronteggRedirectUriRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 88 | clientHolder := meta.(*restclient.ClientHolder) 89 | var out struct { 90 | RedirectURIs []fronteggRedirectUri `json:"redirectUris"` 91 | } 92 | if err := clientHolder.ApiClient.Get(ctx, fronteggRedirectUriPath, &out); err != nil { 93 | return diag.FromErr(err) 94 | } 95 | for _, c := range out.RedirectURIs { 96 | if c.Key == d.Id() { 97 | if err := resourceFronteggRedirectUriDeserialize(d, c); err != nil { 98 | return diag.FromErr(err) 99 | } 100 | return nil 101 | } 102 | } 103 | d.SetId("") 104 | return nil 105 | } 106 | 107 | func resourceFronteggRedirectUriDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 108 | clientHolder := meta.(*restclient.ClientHolder) 109 | if err := clientHolder.ApiClient.Delete(ctx, fmt.Sprintf("%s/%s", fronteggRedirectUriPath, d.Id()), nil); err != nil { 110 | return diag.FromErr(err) 111 | } 112 | return nil 113 | } 114 | -------------------------------------------------------------------------------- /provider/resource_frontegg_role.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | 8 | "github.com/frontegg/terraform-provider-frontegg/internal/restclient" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 11 | ) 12 | 13 | const fronteggRolePath = "/identity/resources/roles/v1" 14 | 15 | type fronteggRole struct { 16 | ID string `json:"id,omitempty"` 17 | Name string `json:"name,omitempty"` 18 | Key string `json:"key,omitempty"` 19 | Description string `json:"description,omitempty"` 20 | Level int `json:"level"` 21 | IsDefault bool `json:"isDefault"` 22 | FirstUserRole bool `json:"firstUserRole"` 23 | Permissions []string `json:"permissions"` 24 | TenantID string `json:"tenantId,omitempty"` 25 | VendorID string `json:"vendorId,omitempty"` 26 | CreatedAt string `json:"createdAt,omitempty"` 27 | } 28 | 29 | type fronteggRolePermissions struct { 30 | PermissionIDs []string `json:"permissionIds"` 31 | } 32 | 33 | func resourceFronteggRole() *schema.Resource { 34 | return &schema.Resource{ 35 | Description: `Configures a Frontegg role.`, 36 | 37 | CreateContext: resourceFronteggRoleCreate, 38 | ReadContext: resourceFronteggRoleRead, 39 | UpdateContext: resourceFronteggRoleUpdate, 40 | DeleteContext: resourceFronteggRoleDelete, 41 | Importer: &schema.ResourceImporter{ 42 | StateContext: schema.ImportStatePassthroughContext, 43 | }, 44 | 45 | Schema: map[string]*schema.Schema{ 46 | "name": { 47 | Description: "A human-readable name for the role.", 48 | Type: schema.TypeString, 49 | Required: true, 50 | }, 51 | "key": { 52 | Description: "A human-readable identifier for the role.", 53 | Type: schema.TypeString, 54 | Required: true, 55 | }, 56 | "description": { 57 | Description: "A human-readable description of the role.", 58 | Type: schema.TypeString, 59 | Required: true, 60 | }, 61 | "default": { 62 | Description: "Whether the role should be applied to new users by default.", 63 | Type: schema.TypeBool, 64 | Required: true, 65 | }, 66 | "first_user": { 67 | Description: "Whether the role should be applied to the first user in the tenant (new tenants only).", 68 | Type: schema.TypeBool, 69 | Optional: true, 70 | }, 71 | "level": { 72 | Description: "The level of the role in the role hierarchy.", 73 | Type: schema.TypeInt, 74 | Required: true, 75 | }, 76 | "permission_ids": { 77 | Description: "The IDs of the permissions that the role confers to its members.", 78 | Type: schema.TypeSet, 79 | Elem: &schema.Schema{Type: schema.TypeString}, 80 | Required: true, 81 | }, 82 | "tenant_id": { 83 | Description: "The ID of the tenant that owns the role.", 84 | Type: schema.TypeString, 85 | Optional: true, 86 | }, 87 | "vendor_id": { 88 | Description: "The ID of the vendor that owns the role.", 89 | Type: schema.TypeString, 90 | Computed: true, 91 | }, 92 | "created_at": { 93 | Description: "The timestamp at which the role was created.", 94 | Type: schema.TypeString, 95 | Computed: true, 96 | }, 97 | }, 98 | } 99 | } 100 | 101 | func resourceFronteggRoleSerialize(d *schema.ResourceData) fronteggRole { 102 | return fronteggRole{ 103 | Name: d.Get("name").(string), 104 | IsDefault: d.Get("default").(bool), 105 | FirstUserRole: d.Get("first_user").(bool), 106 | Key: d.Get("key").(string), 107 | Description: d.Get("description").(string), 108 | Level: d.Get("level").(int), 109 | } 110 | } 111 | 112 | func resourceFronteggRolePermissionsSerialize(d *schema.ResourceData) fronteggRolePermissions { 113 | return fronteggRolePermissions{ 114 | PermissionIDs: stringSetToList(d.Get("permission_ids").(*schema.Set)), 115 | } 116 | } 117 | 118 | func resourceFronteggRoleDeserialize(d *schema.ResourceData, f fronteggRole) error { 119 | d.SetId(f.ID) 120 | if err := d.Set("name", f.Name); err != nil { 121 | return err 122 | } 123 | if err := d.Set("key", f.Key); err != nil { 124 | return err 125 | } 126 | if err := d.Set("description", f.Description); err != nil { 127 | return err 128 | } 129 | if err := d.Set("default", f.IsDefault); err != nil { 130 | return err 131 | } 132 | if err := d.Set("first_user", f.FirstUserRole); err != nil { 133 | return err 134 | } 135 | if err := d.Set("level", f.Level); err != nil { 136 | return err 137 | } 138 | if err := d.Set("permission_ids", f.Permissions); err != nil { 139 | return err 140 | } 141 | if err := d.Set("tenant_id", f.TenantID); err != nil { 142 | return err 143 | } 144 | if err := d.Set("vendor_id", f.VendorID); err != nil { 145 | return err 146 | } 147 | if err := d.Set("created_at", f.CreatedAt); err != nil { 148 | return err 149 | } 150 | return nil 151 | } 152 | 153 | func getTenantIdHeaders(d *schema.ResourceData) http.Header { 154 | headers := http.Header{} 155 | tenant_id := d.Get("tenant_id").(string) 156 | if tenant_id != "" { 157 | headers.Add("frontegg-tenant-id", tenant_id) 158 | } else { 159 | headers = nil 160 | } 161 | return headers 162 | } 163 | 164 | func resourceFronteggRoleCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 165 | headers := getTenantIdHeaders(d) 166 | clientHolder := meta.(*restclient.ClientHolder) 167 | var id string 168 | { 169 | in := []fronteggRole{resourceFronteggRoleSerialize(d)} 170 | var out []fronteggRole 171 | if err := clientHolder.ApiClient.PostWithHeaders(ctx, fronteggRolePath, headers, in, &out); err != nil { 172 | return diag.FromErr(err) 173 | } 174 | if len(out) != 1 { 175 | return diag.Errorf("server returned unexpected number of results when creating Role: %d", len(out)) 176 | } 177 | id = out[0].ID 178 | } 179 | var out fronteggRole 180 | in := resourceFronteggRolePermissionsSerialize(d) 181 | if err := clientHolder.ApiClient.PutWithHeaders(ctx, fmt.Sprintf("%s/%s/permissions", fronteggRolePath, id), headers, in, &out); err != nil { 182 | return diag.FromErr(err) 183 | } 184 | if err := resourceFronteggRoleDeserialize(d, out); err != nil { 185 | return diag.FromErr(err) 186 | } 187 | return nil 188 | } 189 | 190 | func resourceFronteggRoleRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 191 | headers := getTenantIdHeaders(d) 192 | clientHolder := meta.(*restclient.ClientHolder) 193 | var out []fronteggRole 194 | if err := clientHolder.ApiClient.GetWithHeaders(ctx, fronteggRolePath, headers, &out); err != nil { 195 | return diag.FromErr(err) 196 | } 197 | for _, c := range out { 198 | if c.ID == d.Id() { 199 | if err := resourceFronteggRoleDeserialize(d, c); err != nil { 200 | return diag.FromErr(err) 201 | } 202 | return diag.Diagnostics{} 203 | } 204 | } 205 | d.SetId("") 206 | return nil 207 | } 208 | 209 | func resourceFronteggRoleUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 210 | headers := getTenantIdHeaders(d) 211 | clientHolder := meta.(*restclient.ClientHolder) 212 | { 213 | in := resourceFronteggRoleSerialize(d) 214 | if err := clientHolder.ApiClient.PatchWithHeaders(ctx, fmt.Sprintf("%s/%s", fronteggRolePath, d.Id()), headers, in, nil); err != nil { 215 | return diag.FromErr(err) 216 | } 217 | } 218 | var out fronteggRole 219 | in := resourceFronteggRolePermissionsSerialize(d) 220 | if err := clientHolder.ApiClient.PutWithHeaders(ctx, fmt.Sprintf("%s/%s/permissions", fronteggRolePath, d.Id()), headers, in, &out); err != nil { 221 | return diag.FromErr(err) 222 | } 223 | if err := resourceFronteggRoleDeserialize(d, out); err != nil { 224 | return diag.FromErr(err) 225 | } 226 | return nil 227 | } 228 | 229 | func resourceFronteggRoleDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 230 | headers := getTenantIdHeaders(d) 231 | clientHolder := meta.(*restclient.ClientHolder) 232 | if err := clientHolder.ApiClient.DeleteWithHeaders(ctx, fmt.Sprintf("%s/%s", fronteggRolePath, d.Id()), headers, nil); err != nil { 233 | return diag.FromErr(err) 234 | } 235 | return nil 236 | } 237 | -------------------------------------------------------------------------------- /provider/resource_frontegg_secret.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/frontegg/terraform-provider-frontegg/internal/restclient" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 10 | ) 11 | 12 | const fronteggSecretPath = "/custom-code/resources/secrets/v1" 13 | 14 | type fronteggSecret struct { 15 | ID string `json:"id,omitempty"` 16 | Key string `json:"key,omitempty"` 17 | Value string `json:"value,omitempty"` 18 | } 19 | 20 | func resourceFronteggSecret() *schema.Resource { 21 | return &schema.Resource{ 22 | Description: `Configures a Frontegg secret.`, 23 | 24 | CreateContext: resourceFronteggSecretCreate, 25 | ReadContext: resourceFronteggSecretRead, 26 | UpdateContext: resourceFronteggSecretUpdate, 27 | DeleteContext: resourceFronteggSecretDelete, 28 | Importer: &schema.ResourceImporter{ 29 | StateContext: schema.ImportStatePassthroughContext, 30 | }, 31 | 32 | Schema: map[string]*schema.Schema{ 33 | "key": { 34 | Description: "The key of the secret.", 35 | Type: schema.TypeString, 36 | Required: true, 37 | }, 38 | "value": { 39 | Description: "The value of the secret.", 40 | Type: schema.TypeString, 41 | Required: true, 42 | Sensitive: true, 43 | }, 44 | }, 45 | } 46 | } 47 | 48 | func resourceFronteggSecretSerialize(d *schema.ResourceData) fronteggSecret { 49 | return fronteggSecret{ 50 | Key: d.Get("key").(string), 51 | Value: d.Get("value").(string), 52 | } 53 | } 54 | 55 | func resourceFronteggSecretDeserialize(d *schema.ResourceData, f fronteggSecret) error { 56 | d.SetId(f.ID) 57 | if err := d.Set("key", f.Key); err != nil { 58 | return err 59 | } 60 | // Note: We don't set the value back from the API as it's not returned for security reasons 61 | return nil 62 | } 63 | 64 | func resourceFronteggSecretCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 65 | clientHolder := meta.(*restclient.ClientHolder) 66 | in := resourceFronteggSecretSerialize(d) 67 | var out fronteggSecret 68 | if err := clientHolder.ApiClient.Post(ctx, fronteggSecretPath, in, &out); err != nil { 69 | return diag.FromErr(err) 70 | } 71 | if err := resourceFronteggSecretDeserialize(d, out); err != nil { 72 | return diag.FromErr(err) 73 | } 74 | return nil 75 | } 76 | 77 | func resourceFronteggSecretRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 78 | clientHolder := meta.(*restclient.ClientHolder) 79 | var out struct { 80 | Items []fronteggSecret `json:"items"` 81 | } 82 | if err := clientHolder.ApiClient.Get(ctx, fronteggSecretPath, &out); err != nil { 83 | return diag.FromErr(err) 84 | } 85 | for _, s := range out.Items { 86 | if s.ID == d.Id() { 87 | if err := resourceFronteggSecretDeserialize(d, s); err != nil { 88 | return diag.FromErr(err) 89 | } 90 | return nil 91 | } 92 | } 93 | d.SetId("") 94 | return nil 95 | } 96 | 97 | func resourceFronteggSecretUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 98 | clientHolder := meta.(*restclient.ClientHolder) 99 | in := fronteggSecret{ 100 | Value: d.Get("value").(string), 101 | } 102 | if err := clientHolder.ApiClient.Patch(ctx, fmt.Sprintf("%s/%s", fronteggSecretPath, d.Id()), in, nil); err != nil { 103 | return diag.FromErr(err) 104 | } 105 | return nil 106 | } 107 | 108 | func resourceFronteggSecretDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 109 | clientHolder := meta.(*restclient.ClientHolder) 110 | if err := clientHolder.ApiClient.Delete(ctx, fmt.Sprintf("%s/%s", fronteggSecretPath, d.Id()), nil); err != nil { 111 | return diag.FromErr(err) 112 | } 113 | return nil 114 | } 115 | -------------------------------------------------------------------------------- /provider/resource_frontegg_tenant.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | 8 | "github.com/frontegg/terraform-provider-frontegg/internal/restclient" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 11 | ) 12 | 13 | const fronteggTenantPath = "/tenants/resources/tenants/v1" 14 | 15 | type fronteggTenant struct { 16 | Key string `json:"tenantId,omitempty"` 17 | Name string `json:"name,omitempty"` 18 | ApplicationUri string `json:"applicationUrl,omitempty"` 19 | // `Metadata` is only populated when deserializing an API response from JSON, 20 | // since metadata is returned as a jsonified-string. 21 | Metadata string `json:"metadata,omitempty"` 22 | } 23 | 24 | type fronteggTenantMetadata struct { 25 | Metadata map[string]string `json:"metadata,omitempty"` 26 | } 27 | 28 | func resourceFronteggTenant() *schema.Resource { 29 | return &schema.Resource{ 30 | Description: `Configures a Frontegg tenant.`, 31 | 32 | CreateContext: resourceFronteggTenantCreate, 33 | ReadContext: resourceFronteggTenantRead, 34 | UpdateContext: resourceFronteggTenantUpdate, 35 | DeleteContext: resourceFronteggTenantDelete, 36 | Importer: &schema.ResourceImporter{ 37 | StateContext: schema.ImportStatePassthroughContext, 38 | }, 39 | 40 | Schema: map[string]*schema.Schema{ 41 | "name": { 42 | Description: "A human-readable name for the tenant.", 43 | Type: schema.TypeString, 44 | Required: true, 45 | }, 46 | "key": { 47 | Description: "A human-readable identifier for the tenant.", 48 | Type: schema.TypeString, 49 | Required: true, 50 | ForceNew: true, 51 | }, 52 | "application_uri": { 53 | Description: "The application URI for this tenant.", 54 | Type: schema.TypeString, 55 | Optional: true, 56 | }, 57 | "selected_metadata": { 58 | Description: "Metadata to set and manage; will be merged with upstream metadata fields set outside of terraform.", 59 | Type: schema.TypeMap, 60 | Optional: true, 61 | Elem: &schema.Schema{ 62 | Type: schema.TypeString, 63 | }, 64 | }, 65 | }, 66 | } 67 | } 68 | 69 | func resourceFronteggTenantSerialize(d *schema.ResourceData) fronteggTenant { 70 | return fronteggTenant{ 71 | Name: d.Get("name").(string), 72 | Key: d.Get("key").(string), 73 | ApplicationUri: d.Get("application_uri").(string), 74 | // Don't serialize 'Metadata' here, since it will overwrite all upstream metadata. 75 | } 76 | } 77 | 78 | func resourceFronteggTenantDeserialize(d *schema.ResourceData, f fronteggTenant) error { 79 | d.SetId(f.Key) 80 | if err := d.Set("name", f.Name); err != nil { 81 | return err 82 | } 83 | if err := d.Set("key", f.Key); err != nil { 84 | return err 85 | } 86 | if err := d.Set("application_uri", f.ApplicationUri); err != nil { 87 | return err 88 | } 89 | 90 | if len(f.Metadata) > 0 { 91 | if err := resourceFronteggTenantMetadataDeserialize(d, f.Metadata); err != nil { 92 | return err 93 | } 94 | } 95 | return nil 96 | } 97 | 98 | func resourceFronteggTenantMetadataDeserialize(d *schema.ResourceData, metadata string) error { 99 | // We only manage keys that are explicitly selected. 100 | selectedMetadata := castResourceStringMap(d.Get("selected_metadata")) 101 | var allUpstreamMetadata map[string]interface{} 102 | if err := json.Unmarshal([]byte(metadata), &allUpstreamMetadata); err != nil { 103 | return err 104 | } 105 | for key := range selectedMetadata { 106 | if newValue, ok := allUpstreamMetadata[key]; ok { 107 | // All metadata keys managed by this provider must use string values 108 | // so ignore the upstream value if it is not a string 109 | newValueString, ok := newValue.(string) 110 | if ok { 111 | selectedMetadata[key] = newValueString 112 | } 113 | } else { 114 | delete(selectedMetadata, key) 115 | } 116 | } 117 | if err := d.Set("selected_metadata", selectedMetadata); err != nil { 118 | return err 119 | } 120 | 121 | return nil 122 | } 123 | 124 | func resourceFronteggTenantCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 125 | clientHolder := meta.(*restclient.ClientHolder) 126 | in := resourceFronteggTenantSerialize(d) 127 | var out fronteggTenant 128 | if err := clientHolder.ApiClient.Post(ctx, fronteggTenantPath, in, &out); err != nil { 129 | return diag.FromErr(err) 130 | } 131 | if err := resourceFronteggTenantDeserialize(d, out); err != nil { 132 | return diag.FromErr(err) 133 | } 134 | 135 | // Metadata: 136 | if selectedMetadata, is_set := d.GetOk("selected_metadata"); is_set { 137 | in := fronteggTenantMetadata{castResourceStringMap(selectedMetadata)} 138 | var out fronteggTenant 139 | if err := clientHolder.ApiClient.Post(ctx, fmt.Sprintf("%s/%s/metadata", fronteggTenantPath, d.Id()), in, &out); err != nil { 140 | return diag.FromErr(err) 141 | } 142 | if err := resourceFronteggTenantMetadataDeserialize(d, out.Metadata); err != nil { 143 | return diag.FromErr(err) 144 | } 145 | } 146 | return nil 147 | } 148 | 149 | func resourceFronteggTenantRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 150 | clientHolder := meta.(*restclient.ClientHolder) 151 | var out []fronteggTenant 152 | if err := clientHolder.ApiClient.Get(ctx, fmt.Sprintf("%s/%s", fronteggTenantPath, d.Id()), &out); err != nil { 153 | return diag.FromErr(err) 154 | } 155 | for _, c := range out { 156 | if c.Key == d.Id() { 157 | if err := resourceFronteggTenantDeserialize(d, c); err != nil { 158 | return diag.FromErr(err) 159 | } 160 | return nil 161 | } 162 | } 163 | d.SetId("") 164 | return nil 165 | } 166 | 167 | func resourceFronteggTenantUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 168 | clientHolder := meta.(*restclient.ClientHolder) 169 | 170 | // Metadata: 171 | if d.HasChange("selected_metadata") { 172 | oldMetadata, newMetadata := d.GetChange("selected_metadata") 173 | newMetadataAsStringMap := castResourceStringMap(newMetadata) 174 | for key := range castResourceStringMap(oldMetadata) { 175 | if _, ok := newMetadataAsStringMap[key]; !ok { 176 | if err := clientHolder.ApiClient.Delete(ctx, fmt.Sprintf("%s/%s/metadata/%s", fronteggTenantPath, d.Id(), key), nil); err != nil { 177 | return diag.FromErr(err) 178 | } 179 | } 180 | } 181 | 182 | // Update all the keys in newMeta 183 | in := fronteggTenantMetadata{newMetadataAsStringMap} 184 | var out fronteggTenant 185 | if err := clientHolder.ApiClient.Post(ctx, fmt.Sprintf("%s/%s/metadata", fronteggTenantPath, d.Id()), in, &out); err != nil { 186 | return diag.FromErr(err) 187 | } 188 | if err := resourceFronteggTenantMetadataDeserialize(d, out.Metadata); err != nil { 189 | return diag.FromErr(err) 190 | } 191 | } 192 | 193 | var out fronteggTenant 194 | in := resourceFronteggTenantSerialize(d) 195 | if err := clientHolder.ApiClient.Put(ctx, fmt.Sprintf("%s/%s", fronteggTenantPath, d.Id()), in, &out); err != nil { 196 | return diag.FromErr(err) 197 | } 198 | if err := resourceFronteggTenantDeserialize(d, out); err != nil { 199 | return diag.FromErr(err) 200 | } 201 | return nil 202 | } 203 | 204 | func resourceFronteggTenantDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 205 | clientHolder := meta.(*restclient.ClientHolder) 206 | if err := clientHolder.ApiClient.Delete(ctx, fmt.Sprintf("%s/%s", fronteggTenantPath, d.Id()), nil); err != nil { 207 | return diag.FromErr(err) 208 | } 209 | return nil 210 | } 211 | -------------------------------------------------------------------------------- /provider/resource_frontegg_user.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "net/http" 8 | 9 | "github.com/frontegg/terraform-provider-frontegg/internal/restclient" 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 11 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 12 | ) 13 | 14 | type fronteggSuperUser struct { 15 | SuperUser bool `json:"superUser"` 16 | } 17 | 18 | type fronteggUserRole struct { 19 | Id string `json:"id,omitempty"` 20 | Key string `json:"key,omitempty"` 21 | } 22 | 23 | type fronteggUser struct { 24 | Key string `json:"id,omitempty"` 25 | Email string `json:"email,omitempty"` 26 | Password string `json:"password,omitempty"` 27 | CreateRoleIDs []interface{} `json:"roleIds,omitempty"` 28 | ReadRoleIDs []fronteggUserRole `json:"roles,omitempty"` 29 | SkipInviteEmail bool `json:"skipInviteEmail,omitempty"` 30 | Verified bool `json:"verified,omitempty"` 31 | SuperUser bool `json:"superUser,omitempty"` 32 | } 33 | 34 | const fronteggUserPath = "/identity/resources/users/v2" 35 | const fronteggUserPathV1 = "/identity/resources/users/v1" 36 | 37 | func resourceFronteggUser() *schema.Resource { 38 | return &schema.Resource{ 39 | Description: `Configures a Frontegg user.`, 40 | 41 | CreateContext: resourceFronteggUserCreate, 42 | ReadContext: resourceFronteggUserRead, 43 | DeleteContext: resourceFronteggUserDelete, 44 | UpdateContext: resourceFronteggUserUpdate, 45 | Importer: &schema.ResourceImporter{ 46 | StateContext: schema.ImportStatePassthroughContext, 47 | }, 48 | 49 | Schema: map[string]*schema.Schema{ 50 | "email": { 51 | Description: "The user's email address.", 52 | Type: schema.TypeString, 53 | Required: true, 54 | }, 55 | "password": { 56 | Description: "The user's login password.", 57 | Type: schema.TypeString, 58 | Sensitive: true, 59 | Optional: true, 60 | }, 61 | "skip_invite_email": { 62 | Description: "Skip sending the invite email. If true, user is automatically verified on creation.", 63 | Type: schema.TypeBool, 64 | Optional: true, 65 | }, 66 | "automatically_verify": { 67 | Description: "Whether the user gets verified upon creation.", 68 | Type: schema.TypeBool, 69 | Optional: true, 70 | }, 71 | "role_ids": { 72 | Description: "List of the role IDs that the user has in the tenant", 73 | Type: schema.TypeSet, 74 | Elem: &schema.Schema{ 75 | Type: schema.TypeString, 76 | }, 77 | MinItems: 1, 78 | Required: true, 79 | }, 80 | "tenant_id": { 81 | Description: "The tenant ID for this user.", 82 | Type: schema.TypeString, 83 | Required: true, 84 | }, 85 | "superuser": { 86 | Description: "Whether the user is a super user.", 87 | Type: schema.TypeBool, 88 | Optional: true, 89 | }, 90 | }, 91 | } 92 | } 93 | 94 | func resourceFronteggUserSerialize(d *schema.ResourceData) fronteggUser { 95 | log.Printf("role IDs: %#v", d.Get("role_ids").(*schema.Set).List()) 96 | return fronteggUser{ 97 | Email: d.Get("email").(string), 98 | Password: d.Get("password").(string), 99 | SkipInviteEmail: d.Get("skip_invite_email").(bool), 100 | CreateRoleIDs: d.Get("role_ids").(*schema.Set).List(), 101 | SuperUser: d.Get("superuser").(bool), 102 | } 103 | } 104 | 105 | func resourceFronteggUserDeserialize(d *schema.ResourceData, f fronteggUser) error { 106 | d.SetId(f.Key) 107 | if err := d.Set("email", f.Email); err != nil { 108 | return err 109 | } 110 | var roleIDs []string 111 | for _, roleID := range f.ReadRoleIDs { 112 | roleIDs = append(roleIDs, roleID.Id) 113 | } 114 | if err := d.Set("role_ids", roleIDs); err != nil { 115 | return err 116 | } 117 | return nil 118 | } 119 | 120 | func resourceFronteggUserCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 121 | clientHolder := meta.(*restclient.ClientHolder) 122 | in := resourceFronteggUserSerialize(d) 123 | var out fronteggUser 124 | headers := http.Header{} 125 | headers.Add("frontegg-tenant-id", d.Get("tenant_id").(string)) 126 | if err := clientHolder.ApiClient.RequestWithHeaders(ctx, "POST", fronteggUserPath, headers, in, &out); err != nil { 127 | return diag.FromErr(err) 128 | } 129 | 130 | if err := resourceFronteggUserDeserialize(d, out); err != nil { 131 | return diag.FromErr(err) 132 | } 133 | 134 | superUser := d.Get("superuser").(bool) 135 | if superUser { 136 | in := fronteggSuperUser{ 137 | SuperUser: superUser, 138 | } 139 | if err := clientHolder.ApiClient.Put(ctx, fmt.Sprintf("%s/%s/superuser", fronteggUserPathV1, out.Key), in, nil); err != nil { 140 | return diag.FromErr(err) 141 | } 142 | } 143 | 144 | if !d.Get("automatically_verify").(bool) { 145 | return nil 146 | } 147 | if err := clientHolder.ApiClient.Post(ctx, fmt.Sprintf("%s/%s/verify", fronteggUserPathV1, out.Key), nil, nil); err != nil { 148 | return diag.FromErr(err) 149 | } 150 | 151 | return nil 152 | } 153 | 154 | func resourceFronteggUserRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 155 | clientHolder := meta.(*restclient.ClientHolder) 156 | client := clientHolder.ApiClient 157 | client.Ignore404() 158 | var out fronteggUser 159 | headers := http.Header{} 160 | headers.Add("frontegg-tenant-id", d.Get("tenant_id").(string)) 161 | if err := client.RequestWithHeaders(ctx, "GET", fmt.Sprintf("%s/%s", fronteggUserPathV1, d.Id()), headers, nil, &out); err != nil { 162 | return diag.FromErr(err) 163 | } 164 | if out.Key == "" { 165 | d.SetId("") 166 | return nil 167 | } 168 | 169 | if err := resourceFronteggUserDeserialize(d, out); err != nil { 170 | return diag.FromErr(err) 171 | } 172 | return nil 173 | } 174 | 175 | func resourceFronteggUserDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 176 | clientHolder := meta.(*restclient.ClientHolder) 177 | if err := clientHolder.ApiClient.Delete(ctx, fmt.Sprintf("%s/%s", fronteggUserPathV1, d.Id()), nil); err != nil { 178 | return diag.FromErr(err) 179 | } 180 | return nil 181 | } 182 | 183 | func resourceFronteggUserUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 184 | clientHolder := meta.(*restclient.ClientHolder) 185 | // TODO: fields like phone number and avatar URL need https://docs.frontegg.com/reference/userscontrollerv1_updateuser 186 | 187 | // Email address: 188 | if d.HasChange("email") { 189 | email := d.Get("email").(string) 190 | if err := clientHolder.ApiClient.Put(ctx, fmt.Sprintf("%s/%s/email", fronteggUserPathV1, d.Id()), struct { 191 | Email string `json:"email"` 192 | }{email}, nil); err != nil { 193 | return diag.FromErr(err) 194 | } 195 | 196 | if err := d.Set("email", email); err != nil { 197 | return diag.FromErr(err) 198 | } 199 | } 200 | 201 | // Password: 202 | if d.HasChange("password") { 203 | headers := http.Header{} 204 | headers.Add("frontegg-user-id", d.Id()) 205 | 206 | oldI, newI := d.GetChange("password") 207 | oldPw := oldI.(string) 208 | newPw := newI.(string) 209 | 210 | if err := clientHolder.ApiClient.RequestWithHeaders(ctx, "POST", fmt.Sprintf("%s/passwords/change", fronteggUserPathV1), headers, struct { 211 | OldPW string `json:"password"` 212 | NewPW string `json:"newPassword"` 213 | }{oldPw, newPw}, nil); err != nil { 214 | return diag.FromErr(err) 215 | } 216 | 217 | if err := d.Set("password", newPw); err != nil { 218 | return diag.FromErr(err) 219 | } 220 | } 221 | 222 | // Super User: 223 | if d.HasChange("superuser") { 224 | superUser := d.Get("superuser").(bool) 225 | in := fronteggSuperUser{ 226 | SuperUser: superUser, 227 | } 228 | if err := clientHolder.ApiClient.Put(ctx, fmt.Sprintf("%s/%s/superuser", fronteggUserPathV1, d.Id()), in, nil); err != nil { 229 | return diag.FromErr(err) 230 | } 231 | } 232 | 233 | // Roles: 234 | if d.HasChange("role_ids") { 235 | headers := http.Header{} 236 | headers.Add("frontegg-tenant-id", d.Get("tenant_id").(string)) 237 | 238 | oldsI, newsI := d.GetChange("role_ids") 239 | olds := oldsI.(*schema.Set) 240 | news := newsI.(*schema.Set) 241 | 242 | toAddSet := news.Difference(olds) 243 | toDelSet := olds.Difference(news) 244 | 245 | var toAdd, toDel []string 246 | 247 | for _, add := range toAddSet.List() { 248 | toAdd = append(toAdd, add.(string)) 249 | } 250 | for _, del := range toDelSet.List() { 251 | toDel = append(toDel, del.(string)) 252 | } 253 | 254 | if len(toAdd) > 0 { 255 | if err := clientHolder.ApiClient.RequestWithHeaders(ctx, "POST", fmt.Sprintf("%s/%s/roles", fronteggUserPathV1, d.Id()), headers, struct { 256 | RoleIds []string `json:"roleIds"` 257 | }{toAdd}, nil); err != nil { 258 | return diag.FromErr(err) 259 | } 260 | } 261 | if len(toDel) > 0 { 262 | if err := clientHolder.ApiClient.RequestWithHeaders(ctx, "DELETE", fmt.Sprintf("%s/%s/roles", fronteggUserPathV1, d.Id()), headers, struct { 263 | RoleIds []string `json:"roleIds"` 264 | }{toDel}, nil); err != nil { 265 | return diag.FromErr(err) 266 | } 267 | } 268 | 269 | if err := d.Set("role_ids", news); err != nil { 270 | return diag.FromErr(err) 271 | } 272 | } 273 | 274 | d.SetId(d.Id()) 275 | return nil 276 | 277 | } 278 | -------------------------------------------------------------------------------- /provider/resource_frontegg_webhook.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "regexp" 7 | 8 | "github.com/frontegg/terraform-provider-frontegg/internal/restclient" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 11 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" 12 | ) 13 | 14 | const fronteggWebhookPath = "/webhook" 15 | 16 | type fronteggWebhook struct { 17 | ID string `json:"_id,omitempty"` 18 | DisplayName string `json:"displayName,omitempty"` 19 | Description string `json:"description,omitempty"` 20 | URL string `json:"url,omitempty"` 21 | Secret string `json:"secret,omitempty"` 22 | EventKeys []string `json:"eventKeys,omitempty"` 23 | IsActive bool `json:"isActive"` 24 | Type string `json:"type,omitempty"` 25 | VendorID string `json:"vendorId,omitempty"` 26 | CreatedAt string `json:"createdAt,omitempty"` 27 | } 28 | 29 | func resourceFronteggWebhook() *schema.Resource { 30 | return &schema.Resource{ 31 | Description: `Configures a Frontegg webhook.`, 32 | 33 | CreateContext: resourceFronteggWebhookCreate, 34 | ReadContext: resourceFronteggWebhookRead, 35 | UpdateContext: resourceFronteggWebhookUpdate, 36 | DeleteContext: resourceFronteggWebhookDelete, 37 | Importer: &schema.ResourceImporter{ 38 | StateContext: schema.ImportStatePassthroughContext, 39 | }, 40 | 41 | Schema: map[string]*schema.Schema{ 42 | "enabled": { 43 | Description: "Whether the webhook is enabled.", 44 | Type: schema.TypeBool, 45 | Required: true, 46 | }, 47 | "name": { 48 | Description: "A human-readable name for the webhook.", 49 | Type: schema.TypeString, 50 | Optional: true, 51 | }, 52 | "description": { 53 | Description: "A human-readable description of the webhook.", 54 | Type: schema.TypeString, 55 | Required: true, 56 | }, 57 | "url": { 58 | Description: "The URL to send events to.", 59 | Type: schema.TypeString, 60 | Required: true, 61 | }, 62 | "secret": { 63 | Description: "A secret to include with the event.", 64 | Type: schema.TypeString, 65 | Required: true, 66 | }, 67 | "events": { 68 | Description: "The names of the events to subscribe to.", 69 | Type: schema.TypeSet, 70 | Elem: &schema.Schema{ 71 | Type: schema.TypeString, 72 | ValidateFunc: validation.StringMatch( 73 | regexp.MustCompile(`^frontegg\..*`), 74 | "event name must start with 'frontegg.'", 75 | ), 76 | }, 77 | Required: true, 78 | }, 79 | "type": { 80 | Description: "The type of the webhook.", 81 | Type: schema.TypeString, 82 | Computed: true, 83 | }, 84 | "vendor_id": { 85 | Description: "The ID of the vendor that owns the webhook.", 86 | Type: schema.TypeString, 87 | Computed: true, 88 | }, 89 | "created_at": { 90 | Description: "The timestamp at which the webhook was created.", 91 | Type: schema.TypeString, 92 | Computed: true, 93 | }, 94 | }, 95 | } 96 | } 97 | 98 | func resourceFronteggWebhookSerialize(d *schema.ResourceData) fronteggWebhook { 99 | return fronteggWebhook{ 100 | IsActive: d.Get("enabled").(bool), 101 | DisplayName: d.Get("name").(string), 102 | Description: d.Get("description").(string), 103 | URL: d.Get("url").(string), 104 | Secret: d.Get("secret").(string), 105 | EventKeys: stringSetToList(d.Get("events").(*schema.Set)), 106 | } 107 | } 108 | 109 | func resourceFronteggWebhookDeserialize(d *schema.ResourceData, f fronteggWebhook) error { 110 | d.SetId(f.ID) 111 | if err := d.Set("enabled", f.IsActive); err != nil { 112 | return err 113 | } 114 | if err := d.Set("name", f.DisplayName); err != nil { 115 | return err 116 | } 117 | if err := d.Set("description", f.Description); err != nil { 118 | return err 119 | } 120 | if err := d.Set("url", f.URL); err != nil { 121 | return err 122 | } 123 | if err := d.Set("secret", f.Secret); err != nil { 124 | return err 125 | } 126 | if err := d.Set("events", f.EventKeys); err != nil { 127 | return err 128 | } 129 | if err := d.Set("events", f.EventKeys); err != nil { 130 | return err 131 | } 132 | if err := d.Set("type", f.Type); err != nil { 133 | return err 134 | } 135 | if err := d.Set("vendor_id", f.VendorID); err != nil { 136 | return err 137 | } 138 | if err := d.Set("created_at", f.CreatedAt); err != nil { 139 | return err 140 | } 141 | return nil 142 | } 143 | 144 | func resourceFronteggWebhookCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 145 | clientHolder := meta.(*restclient.ClientHolder) 146 | in := resourceFronteggWebhookSerialize(d) 147 | var out fronteggWebhook 148 | if err := clientHolder.PortalClient.Post(ctx, fronteggWebhookPath+"/custom", in, &out); err != nil { 149 | return diag.FromErr(err) 150 | } 151 | if err := resourceFronteggWebhookDeserialize(d, out); err != nil { 152 | return diag.FromErr(err) 153 | } 154 | return nil 155 | } 156 | 157 | func resourceFronteggWebhookRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 158 | clientHolder := meta.(*restclient.ClientHolder) 159 | var out []fronteggWebhook 160 | if err := clientHolder.PortalClient.Get(ctx, fronteggWebhookPath, &out); err != nil { 161 | return diag.FromErr(err) 162 | } 163 | for _, c := range out { 164 | if c.ID == d.Id() { 165 | if err := resourceFronteggWebhookDeserialize(d, c); err != nil { 166 | return diag.FromErr(err) 167 | } 168 | return diag.Diagnostics{} 169 | } 170 | } 171 | return nil 172 | } 173 | 174 | func resourceFronteggWebhookUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 175 | clientHolder := meta.(*restclient.ClientHolder) 176 | in := resourceFronteggWebhookSerialize(d) 177 | var out fronteggWebhook 178 | if err := clientHolder.PortalClient.Patch(ctx, fmt.Sprintf("%s/%s", fronteggWebhookPath, d.Id()), in, &out); err != nil { 179 | return diag.FromErr(err) 180 | } 181 | if err := resourceFronteggWebhookDeserialize(d, out); err != nil { 182 | return diag.FromErr(err) 183 | } 184 | return nil 185 | } 186 | 187 | func resourceFronteggWebhookDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 188 | clientHolder := meta.(*restclient.ClientHolder) 189 | 190 | // Configure the client to ignore 404 errors 191 | clientHolder.PortalClient.Ignore404() 192 | 193 | // Attempt to delete the webhook 194 | err := clientHolder.PortalClient.Delete(ctx, fmt.Sprintf("%s/%s", fronteggWebhookPath, d.Id()), nil) 195 | 196 | // Handle errors other than 404 197 | if err != nil { 198 | return diag.FromErr(err) 199 | } 200 | 201 | return nil 202 | } 203 | -------------------------------------------------------------------------------- /provider/user_source_utils.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/frontegg/terraform-provider-frontegg/internal/restclient" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 10 | ) 11 | 12 | // Common paths for user sources. 13 | const fronteggUserSourceBasePath = "/identity/resources/user-sources/v1" 14 | 15 | // Common response type for all user sources. 16 | type fronteggBaseUserSourceResponse struct { 17 | ID string `json:"id"` 18 | Name string `json:"name"` 19 | Type string `json:"type"` 20 | AppIDs []string `json:"appIds"` 21 | Description string `json:"description"` 22 | Index int `json:"index"` 23 | } 24 | 25 | // TenantConfig interfaces. 26 | type UserSourceDynamicTenantConfig struct { 27 | TenantIDFieldName string `json:"tenantIdFieldName"` 28 | TenantResolverType string `json:"tenantResolverType"` 29 | } 30 | 31 | type UserSourceStaticTenantConfig struct { 32 | TenantID string `json:"tenantId"` 33 | TenantResolverType string `json:"tenantResolverType"` 34 | } 35 | 36 | type UserSourceNewTenantConfig struct { 37 | TenantResolverType string `json:"tenantResolverType"` 38 | } 39 | 40 | type UserSourceFromSourceTenantConfig struct { 41 | TenantResolverType string `json:"tenantResolverType"` 42 | } 43 | 44 | // Builds the tenant configuration based on the terraform resource data. 45 | func buildUserSourceTenantConfig(d *schema.ResourceData) (interface{}, error) { 46 | resolverType := d.Get("tenant_resolver_type").(string) 47 | 48 | switch resolverType { 49 | case "dynamic": 50 | fieldName := d.Get("tenant_id_field_name").(string) 51 | if fieldName == "" { 52 | return nil, fmt.Errorf("tenant_id_field_name is required when tenant_resolver_type is dynamic") 53 | } 54 | return UserSourceDynamicTenantConfig{ 55 | TenantResolverType: "dynamic", 56 | TenantIDFieldName: fieldName, 57 | }, nil 58 | case "static": 59 | tenantID := d.Get("tenant_id").(string) 60 | if tenantID == "" { 61 | return nil, fmt.Errorf("tenant_id is required when tenant_resolver_type is static") 62 | } 63 | return UserSourceStaticTenantConfig{ 64 | TenantResolverType: "static", 65 | TenantID: tenantID, 66 | }, nil 67 | case "new": 68 | return UserSourceNewTenantConfig{ 69 | TenantResolverType: "new", 70 | }, nil 71 | case "from-source": 72 | return UserSourceFromSourceTenantConfig{ 73 | TenantResolverType: "from-source", 74 | }, nil 75 | default: 76 | return nil, fmt.Errorf("unsupported tenant_resolver_type: %s", resolverType) 77 | } 78 | } 79 | 80 | // Common schema fields for all user sources. 81 | func userSourceBaseSchema() map[string]*schema.Schema { 82 | return map[string]*schema.Schema{ 83 | "name": { 84 | Description: "The user source name.", 85 | Type: schema.TypeString, 86 | Required: true, 87 | }, 88 | "description": { 89 | Description: "The user source description.", 90 | Type: schema.TypeString, 91 | Optional: true, 92 | }, 93 | "index": { 94 | Description: "The user source index.", 95 | Type: schema.TypeInt, 96 | Required: true, 97 | }, 98 | "app_ids": { 99 | Description: "The application IDs to assign to this user source.", 100 | Type: schema.TypeSet, 101 | Elem: &schema.Schema{ 102 | Type: schema.TypeString, 103 | }, 104 | Optional: true, 105 | }, 106 | "sync_on_login": { 107 | Description: "Whether to sync user profile attributes on each login.", 108 | Type: schema.TypeBool, 109 | Optional: true, 110 | Default: false, 111 | }, 112 | "is_migrated": { 113 | Description: "Whether to migrate the users.", 114 | Type: schema.TypeBool, 115 | Optional: true, 116 | Default: false, 117 | }, 118 | "tenant_resolver_type": { 119 | Description: "The tenant resolver type (dynamic, static, or new).", 120 | Type: schema.TypeString, 121 | Required: true, 122 | ValidateFunc: func(val interface{}, key string) (warns []string, errs []error) { 123 | v := val.(string) 124 | validTypes := map[string]bool{ 125 | "dynamic": true, 126 | "static": true, 127 | "new": true, 128 | } 129 | if !validTypes[v] { 130 | errs = append(errs, fmt.Errorf("%q must be one of 'dynamic', 'static', or 'new', got: %s", key, v)) 131 | } 132 | return 133 | }, 134 | }, 135 | "tenant_id": { 136 | Description: "The tenant ID for static tenant resolver type.", 137 | Type: schema.TypeString, 138 | Optional: true, 139 | }, 140 | "tenant_id_field_name": { 141 | Description: "The attribute name from which the tenant ID would be taken for dynamic tenant resolver type.", 142 | Type: schema.TypeString, 143 | Optional: true, 144 | }, 145 | } 146 | } 147 | 148 | // Extract app IDs from schema resource data. 149 | func extractAppIDs(d *schema.ResourceData) []string { 150 | appIDsSet := d.Get("app_ids").(*schema.Set) 151 | var appIDs []string 152 | 153 | for _, appID := range appIDsSet.List() { 154 | appIDs = append(appIDs, appID.(string)) 155 | } 156 | 157 | return appIDs 158 | } 159 | 160 | // Deserialize common user source response fields. 161 | func deserializeUserSourceResponse(d *schema.ResourceData, source fronteggBaseUserSourceResponse) error { 162 | d.SetId(source.ID) 163 | if err := d.Set("name", source.Name); err != nil { 164 | return err 165 | } 166 | if err := d.Set("description", source.Description); err != nil { 167 | return err 168 | } 169 | if err := d.Set("index", source.Index); err != nil { 170 | return err 171 | } 172 | if err := d.Set("app_ids", source.AppIDs); err != nil { 173 | return err 174 | } 175 | return nil 176 | } 177 | 178 | // Common Read function for all user sources. 179 | func readUserSource(ctx context.Context, d *schema.ResourceData, meta interface{}, deserializeFunc func(*schema.ResourceData, fronteggBaseUserSourceResponse) error) diag.Diagnostics { 180 | clientHolder := meta.(*restclient.ClientHolder) 181 | client := clientHolder.ApiClient 182 | client.Ignore404() 183 | 184 | var out fronteggBaseUserSourceResponse 185 | if err := client.Get(ctx, fmt.Sprintf("%s/%s", fronteggUserSourceBasePath, d.Id()), &out); err != nil { 186 | return diag.FromErr(err) 187 | } 188 | 189 | if out.ID == "" { 190 | d.SetId("") 191 | return nil 192 | } 193 | 194 | if err := deserializeFunc(d, out); err != nil { 195 | return diag.FromErr(err) 196 | } 197 | 198 | return nil 199 | } 200 | 201 | // Common Delete function for all user sources. 202 | func deleteUserSource(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 203 | clientHolder := meta.(*restclient.ClientHolder) 204 | if err := clientHolder.ApiClient.Delete(ctx, fmt.Sprintf("%s/%s", fronteggUserSourceBasePath, d.Id()), nil); err != nil { 205 | return diag.FromErr(err) 206 | } 207 | return nil 208 | } 209 | -------------------------------------------------------------------------------- /provider/util.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 7 | ) 8 | 9 | func stringSetToList(set *schema.Set) []string { 10 | return stringSetToListWithRightTrim(set, "") 11 | } 12 | 13 | func stringSetToListWithRightTrim(set *schema.Set, trimRight string) []string { 14 | out := make([]string, 0, set.Len()) 15 | for _, v := range set.List() { 16 | trimmed := strings.TrimRight(v.(string), trimRight) 17 | out = append(out, trimmed) 18 | } 19 | return out 20 | } 21 | 22 | func castResourceStringMap(resourceMapValue interface{}) map[string]string { 23 | newStringMap := make(map[string]string) 24 | for key, val := range resourceMapValue.(map[string]interface{}) { 25 | newStringMap[key] = val.(string) 26 | } 27 | return newStringMap 28 | } 29 | 30 | func stringInSlice(target string, slice []string) bool { 31 | for _, item := range slice { 32 | if item == target { 33 | return true 34 | } 35 | } 36 | return false 37 | } 38 | -------------------------------------------------------------------------------- /provider/validators/validator_email_providers.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "slices" 7 | "strings" 8 | 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 10 | ) 11 | 12 | const ( 13 | Mailgun = "mailgun" 14 | SES = "ses" 15 | Sendgrid = "sendgrid" 16 | SES_ROLE = "ses-role" 17 | ) 18 | 19 | func ValidateProvider(val interface{}, key string) (warns []string, errs []error) { 20 | v := val.(string) 21 | ps := []string{Mailgun, SES, Sendgrid, SES_ROLE} 22 | 23 | contain := slices.Contains(ps, v) 24 | 25 | if !contain { 26 | errs = append(errs, fmt.Errorf("%q must be either %v ,got: %s", key, strings.Join(ps, ", "), v)) 27 | } 28 | return warns, errs 29 | } 30 | 31 | func ValidateRequiredFields(ctx context.Context, rd *schema.ResourceDiff, i interface{}) error { 32 | 33 | provider := rd.Get("provider_name").(string) 34 | 35 | requiredFieldError := func(field, provider string) error { 36 | return fmt.Errorf("%s is required when provider is '%s'", field, provider) 37 | } 38 | 39 | if provider == SES { 40 | if region, ok := rd.GetOk("region"); !ok || region.(string) == "" { 41 | return requiredFieldError("region", SES) 42 | } 43 | 44 | if id, ok := rd.GetOk("provider_id"); !ok || id.(string) == "" { 45 | return requiredFieldError("provider_id", SES) 46 | } 47 | } 48 | 49 | if provider == Mailgun { 50 | if domain, ok := rd.GetOk("domain"); !ok || domain.(string) == "" { 51 | return requiredFieldError("domain", Mailgun) 52 | } 53 | if region, ok := rd.GetOk("region"); !ok || region.(string) == "" { 54 | return requiredFieldError("region", Mailgun) 55 | } 56 | } 57 | 58 | return nil 59 | } 60 | -------------------------------------------------------------------------------- /templates/index.md.tmpl: -------------------------------------------------------------------------------- 1 | --- 2 | layout: "" 3 | page_title: "Provider: Frontegg" 4 | description: |- 5 | The Frontegg provider provides resources to interact with the Frontegg user 6 | management platform. 7 | --- 8 | 9 | # Frontegg Provider 10 | 11 | The Frontegg provider provides resources to interact with the [Frontegg] user 12 | management platform. 13 | 14 | The provider works with only one workspace at a time. To provision multiple 15 | workspaces, you will need to configure multiple copies of the provider. 16 | 17 | Note that the client ID and secret key are *not* the client ID and secret key 18 | that appear in "Workspace Settings". You need to generate a workspace API key 19 | and secret specifically for the Terraform provider's use in the administration 20 | portal: 21 | 22 | ![API key generation example](https://user-images.githubusercontent.com/882976/132739276-bc72aa75-8c30-452c-b929-85a8d7ffa4d0.png) 23 | 24 | In order to interact with specific environment management capabilities you can 25 | provide environment ID, that is displayed on environment settings at the Frontegg 26 | portal. To configure multiple environments you will need to configure multiple 27 | copies of provider with one environment ID per each. If no environment ID was 28 | provided the configuration will be cross-environments. 29 | 30 | ## Example Usage 31 | 32 | {{tffile "examples/provider/provider.tf"}} 33 | 34 | {{ .SchemaMarkdown | trimspace }} 35 | 36 | [Frontegg]: https://frontegg.com 37 | -------------------------------------------------------------------------------- /tools/tools.go: -------------------------------------------------------------------------------- 1 | //go:build tools 2 | // +build tools 3 | 4 | package tools 5 | 6 | import ( 7 | // document generation 8 | _ "github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs" 9 | ) 10 | --------------------------------------------------------------------------------