diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index f7448abe2..798ffa7da 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -3,3 +3,8 @@ FROM node:${VARIANT} # not much in here, could acheive this another way for sure... # but this allows us a prepared place to add other things to the container OS. + +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y \ + # For interactive git rebases + vim diff --git a/.devcontainer/README.md b/.devcontainer/README.md index 1bc39036b..16954b1b1 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -1,8 +1,9 @@ # CHEFS Development with Dev Container -The following guide will get you up and running and developing/debugging CHEFS as quickly as possible. + +The following guide will get you up and running and developing/debugging CHEFS as quickly as possible. We provide a [`devcontainer`](https://containers.dev) and will use [`VS Code`](https://code.visualstudio.com) to illustrate. -By no means is CHEFS development limited to these tools; they are merely examples. +By no means is CHEFS development limited to these tools; they are merely examples. ## Caveats @@ -11,6 +12,7 @@ The primary use case for this `devcontainer` is for developing, debugging and un There are limitations running this devcontainer, such as all networking is within this container. This container has [docker-in-docker](https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/docker-in-docker.md) which allows running demos, building docker images, running `docker compose` all within this container. ## Files + The `.devcontainer` folder contains the `devcontainer.json` file which defines this container. We are using a `Dockerfile` and `post-install.sh` to build and configure the container run image. The `Dockerfile` is simple but in place for simplifying image enhancements. The `post-install.sh` will install the required node libraries for CHEFS including the frontend and formio components. In order to run CHEFS you require Keycloak (configured), Postgresql (seeded) and the CHEFS backend/API and frontend/UX. Previously, this was a series of downloads and configuration updates and numerous commands to run. See `.devcontainer/chefs_local` files. @@ -21,42 +23,46 @@ Also included are convenient launch tasks to run and debug CHEFS. ## Open CHEFS in the devcontainer -To open CHEFS in a devcontainer, we open the *root* of this repository. We can open in 2 ways: +To open CHEFS in a devcontainer, we open the _root_ of this repository. We can open in 2 ways: 1. Open Visual Studio Code, and use the Command Palette and use `Dev Containers: Open Folder in Container...` 2. Open Visual Studio Code and `File|Open Folder...`, you should be prompted to `Reopen in Container`. - ## Running CHEFS locally + Keycloak and Postgresql will be launched using docker compose. These will run inside of the devcontainer (docker-in-docker) but the ports are forwarded to the host machine and are accessible on the local host. CHEFS API and Frontend are running as node applications on the devcontainer - again, ports are forwarded to the host. ### Configuring CHEFS locally -When the devcontainer is built, it copies `.devcontainer/chefs_local/local.json.sample` and `.devcontainer/chefs_local/realm-export.json.sample` to `.devcontainer/chefs_local/local.json` and `.devcontainer/chefs_local/realm-export.json` respectively. These copies are not checked in and allow the developer to make changes and tweaks without impacting other developers or accidentially committing passwords. + +When the devcontainer is built, it copies `.devcontainer/chefs_local/local.json.sample` and `.devcontainer/chefs_local/realm-export.json.sample` to `.devcontainer/chefs_local/local.json` and `.devcontainer/chefs_local/realm-export.json` respectively. These copies are not checked in and allow the developer to make changes and tweaks without impacting other developers or accidentially committing passwords. ### Authorization Prerequisites -1. An IDIR account is required to access CHEFS. -2. Request an SSO Integration from the Common Hosted Single Sign-on (CSS) page in order to obtain a resource and secret that will be used for authentication when building CHEFS. View the [detailed documentation](https://bcdevex.atlassian.net/wiki/spaces/CCP/pages/961675282) about requesting the Pathfinder SSO integration. -3. Open realm-export.json located at chefs_build/docker/imports/keycloak and search for `XXXXXXXXXXXX`. This value must match the `clientSecret` value in `local.json` so that the CHEFS API can connect to your Keycloak instance. By default, these are set to be equal and don’t need to be altered. -4. Navigate to the CSS page, login with your IDIR, and download the ‘Development’ Installation JSON from your SSO Integration. -5. Back in the `realm-export.json` file, search for all instances of `YYYYYYYYYYYY` and replace it with the `resource` you obtained from the downloaded JSON file. Search for all instances of `ZZZZZZZZZZZZ` and replace it with the `secret`. + +1. An IDIR account is required to access CHEFS. +2. Request an SSO Integration from the Common Hosted Single Sign-on (CSS) page in order to obtain a resource and secret that will be used for authentication when building CHEFS. View the [detailed documentation](https://bcdevex.atlassian.net/wiki/spaces/CCP/pages/961675282) about requesting the Pathfinder SSO integration. +3. Open realm-export.json located at build/docker/imports/keycloak and search for `XXXXXXXXXXXX`. This value must match the `clientSecret` value in `local.json` so that the CHEFS API can connect to your Keycloak instance. By default, these are set to be equal and don’t need to be altered. +4. Navigate to the CSS page, login with your IDIR, and download the ‘Development’ Installation JSON from your SSO Integration. +5. Back in the `realm-export.json` file, search for all instances of `YYYYYYYYYYYY` and replace it with the `resource` you obtained from the downloaded JSON file. Search for all instances of `ZZZZZZZZZZZZ` and replace it with the `secret`. ### Run/Debug -1. start Keycloak and Postgresql. Many ways to start... - - right click on `.devcontainer/chefs_local/docker-compose.yml` and select `Compose up` - - or use command palette `Docker: Compose Up` then select `.devcontainer/chefs_local/docker-compose.yml` - - or `Terminal | Run Task...|chefs_local up` + +1. start Keycloak and Postgresql. Many ways to start... + - right click on `.devcontainer/chefs_local/docker-compose.yml` and select `Compose up` + - or use command palette `Docker: Compose Up` then select `.devcontainer/chefs_local/docker-compose.yml` + - or `Terminal | Run Task...|chefs_local up` 2. start CHEFS - - Run and Debug, select 'CHEFS' which will start both the API and the frontend. + - Run and Debug, select 'CHEFS' which will start both the API and the frontend. 3. debug Frontend with Chrome - - Run and Debug, select 'CHEFS Frontend - chrome' which will start a Chrome browser against the frontend, will allow breakpoints in `/app/frontend/src` -4. stop Keycloak and Postgresql. Many ways to stop... - - right click on `.devcontainer/chefs_local/docker-compose.yml` and select `Compose down` - - or use command palette `Docker: Compose Down` then select `.devcontainer/chefs_local/docker-compose.yml` - - or `Terminal | Run Task...|chefs_local down` + - Run and Debug, select 'CHEFS Frontend - chrome' which will start a Chrome browser against the frontend, will allow breakpoints in `/app/frontend/src` +4. stop Keycloak and Postgresql. Many ways to stop... + - right click on `.devcontainer/chefs_local/docker-compose.yml` and select `Compose down` + - or use command palette `Docker: Compose Down` then select `.devcontainer/chefs_local/docker-compose.yml` + - or `Terminal | Run Task...|chefs_local down` + +_Notes_ -*Notes* - `CHEFS Frontend` launch configuration is using the `chefs-frontend-local` client in Keycloak, not `chefs-frontend` client as we do in production. - `CHEFS API` will use the configuration found at `.devcontainer/chefs_local/local.json` - `Postgres DB`: localhost:5432 @@ -65,6 +71,7 @@ When the devcontainer is built, it copies `.devcontainer/chefs_local/local.json. - `CHEFS API`: http://localhost:5173/app/api/v1 ## Formio Components + If you are developing the formio components, you should build and redeploy them before running your local debug instances of CHEFS. Use tasks `Components build` and `Components Deploy`. ## KNEX - Database tools @@ -99,9 +106,11 @@ knex migrate:rollback Please review the [knex](https://knexjs.org) for more detail and how to leverage the tool. ## Troubleshooting + All development machines are unique and here we will document problems that have been encountered and how to fix them. ### Failure during load of devcontainer when running webpack (Segmentation Fault) + Encountered on Mac Ventura 13.6, with Mac Docker Desktop 4.26.1 when running `npm run build:formio` on load, we hit a `Segmentation Fault`. The issue was resolved when turning off the virtualization settings in Docker Desktop. -Under Settings, select `gRPC Fuse` instead of `VirtioFS` then unselect `Use Virtualization framework`. Restart Docker and VS Code. \ No newline at end of file +Under Settings, select `gRPC Fuse` instead of `VirtioFS` then unselect `Use Virtualization framework`. Restart Docker and VS Code. diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 7d7b8355f..6f7cebd58 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,58 +1,55 @@ // For format details, see https://aka.ms/devcontainer.json. For config options, see the { - "name": "common-hosted-form-service", - - "build": { - "dockerfile": "Dockerfile", - "context": "..", - "args": { - "VARIANT": "18.18.2-bullseye" - } - }, + "name": "common-hosted-form-service", - "customizations": { - "vscode": { - "extensions": [ - "cweijan.vscode-postgresql-client2", - "Vue.volar", - "esbenp.prettier-vscode" - ], - "settings": { - "database-client.telemetry.usesOnlineServices": false, - "editor.defaultFormatter": null, - "editor.formatOnSave": false, - "[javascript]": { - "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.formatOnSave": true - }, - "prettier.configPath": "${containerWorkspaceFolder}/app/frontend/.prettierrc", - "prettier.documentSelectors": ["${containerWorkspaceFolder}/app/frontend/**/*.{js,vue}"] - } - } - }, + "build": { + "dockerfile": "Dockerfile", + "context": "..", + "args": { + "VARIANT": "18.18.2-bullseye" + } + }, - "features": { - "ghcr.io/devcontainers/features/docker-in-docker:2": {} - }, + "features": { + "ghcr.io/devcontainers/features/docker-in-docker:2": {} + }, - "containerEnv": { - "NODE_CONFIG_DIR": "${containerWorkspaceFolder}/.devcontainer/chefs_local" - }, - - // Use this environment variable if you need to bind mount your local source code into a new container. - "remoteEnv": { - "LOCAL_WORKSPACE_FOLDER": "${localWorkspaceFolder}" - }, + // Use this environment variable if you need to bind mount your local source code into a new container. + "remoteEnv": { + "LOCAL_WORKSPACE_FOLDER": "${localWorkspaceFolder}" + }, - // Use 'forwardPorts' to make a list of ports inside the container available locally. - "forwardPorts": [8082, 8081, 8080, 5432, 5173], + // Use 'forwardPorts' to make a list of ports inside the container available locally. + "forwardPorts": [ + 5173, // CHEFS Frontend + 5432, // PostgreSQL + 8080, // CHEFS Backend + 8081, + 8082 // Keycloak + ], - // Use 'postCreateCommand' to run commands after the container is created. - "postCreateCommand": "bash ./.devcontainer/post-install.sh", + // Use 'postCreateCommand' to run commands after the container is created. + "postCreateCommand": "bash ./.devcontainer/post-install.sh", - // Configure tool-specific properties. - // "customizations": {}, + // Configure tool-specific properties. + "customizations": { + "vscode": { + "extensions": [ + "cweijan.vscode-postgresql-client2", // PostgreSQL client + "dbaeumer.vscode-eslint", // ESLint to catch problems early + "esbenp.prettier-vscode", // Prettier to format files on save + "postman.postman-for-vscode", // Postman for integration tests + "redocly.openapi-vs-code", // ReDocly to catch OpenAPI errors + "vue.volar" // Vue 3 recommended extension + ], + "settings": { + "database-client.telemetry.usesOnlineServices": false, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true + } + } + } - // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. - //"remoteUser": "root" + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + //"remoteUser": "root" } diff --git a/.github/actions/build-push-container/action.yaml b/.github/actions/build-push-container/action.yaml index 3306d76e3..c1e589f2b 100644 --- a/.github/actions/build-push-container/action.yaml +++ b/.github/actions/build-push-container/action.yaml @@ -69,15 +69,16 @@ runs: using: composite steps: - name: Checkout repository from pull request - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ inputs.ref }} if: ${{ inputs.ref != '' }} - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 if: ${{ inputs.ref == '' }} - name: Set variables + shell: bash run: | echo "SHA=sha-$(git rev-parse --short HEAD)" >> $GITHUB_ENV echo "IMAGE_REVISION=$(git rev-parse HEAD)" >> $GITHUB_ENV @@ -148,9 +149,8 @@ runs: latest=true # Creates tags based off of branch names and semver tags tags: | - type=raw,value=ghcr.io/${{ env.GH_USERNAME }}/${{ inputs.image_name }}:${{ env.IMAGE_VERSION }} - type=raw,value=ghcr.io/${{ env.GH_USERNAME }}/${{ inputs.image_name }}:${{ env.SHA }} - type=raw,value=ghcr.io/${{ env.GH_USERNAME }}/${{ inputs.image_name }}:latest + type=raw,value=${{ env.IMAGE_VERSION }} + type=raw,value=${{ env.SHA }} labels: | org.opencontainers.image.revision=${{ env.IMAGE_REVISION }} org.opencontainers.image.version=${{ env.IMAGE_VERSION }} diff --git a/.github/workflows/.close-pr.yaml b/.github/workflows/.close-pr.yaml index 0c5f94f9f..bc0ac2d69 100644 --- a/.github/workflows/.close-pr.yaml +++ b/.github/workflows/.close-pr.yaml @@ -30,7 +30,7 @@ jobs: timeout-minutes: 12 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Login to OpenShift Cluster uses: redhat-actions/oc-login@v1 with: @@ -48,4 +48,4 @@ jobs: with: header: release delete: true - number: ${{ github.event.inputs.pr-number }} \ No newline at end of file + number: ${{ github.event.inputs.pr-number }} diff --git a/.github/workflows/.deploy.yaml b/.github/workflows/.deploy.yaml index 1fed1c4e1..149bab13e 100644 --- a/.github/workflows/.deploy.yaml +++ b/.github/workflows/.deploy.yaml @@ -7,18 +7,9 @@ on: workflow_dispatch: inputs: pr-number: - description: Pull request number, leave blank for dev/test/prod deployment + description: Pull request number required: false type: string - environment: - description: Environment name; choose dev for PR - required: true - type: choice - options: - - dev - - test - - prod - default: dev concurrency: group: ${{ github.workflow }}-${{ github.event.inputs.pr-number || github.ref }} @@ -30,7 +21,6 @@ jobs: runs-on: ubuntu-latest outputs: APP_TITLE: ${{ steps.vars.outputs.APP_TITLE }} - ENVIRONMENT: ${{ steps.vars.outputs.ENVIRONMENT }} JOB_NAME: ${{ steps.vars.outputs.JOB_NAME }} ROUTE_PATH: ${{ steps.vars.outputs.ROUTE_PATH }} URL: ${{ steps.vars.outputs.URL }} @@ -40,14 +30,12 @@ jobs: id: default-vars env: PR_NUMBER: ${{ github.event.inputs.pr-number }} - ENVIRONMENT: ${{ github.event.inputs.environment }} ACRONYM: ${{ env.ACRONYM }} run: | echo "APP_TITLE=Common Hosted Forms" >> "$GITHUB_OUTPUT" - echo "ENVIRONMENT=$ENVIRONMENT" >> "$GITHUB_OUTPUT" echo "JOB_NAME=master" >> "$GITHUB_OUTPUT" echo "ROUTE_PATH=/app" >> "$GITHUB_OUTPUT" - echo "URL=https://$ACRONYM-$ENVIRONMENT.apps.silver.devops.gov.bc.ca" >> "$GITHUB_OUTPUT" + echo "URL=https://$ACRONYM-dev.apps.silver.devops.gov.bc.ca" >> "$GITHUB_OUTPUT" - name: Final variables id: vars env: @@ -58,13 +46,11 @@ jobs: echo "ref=$REF" >> $GITHUB_OUTPUT if [[ "$PR_NUMBER" != '' ]]; then echo "APP_TITLE=${{ steps.default-vars.outputs.APP_TITLE }} - PR-$PR_NUMBER" >> "$GITHUB_OUTPUT" - echo "ENVIRONMENT=pr" >> "$GITHUB_OUTPUT" echo "JOB_NAME=pr-$PR_NUMBER" >> "$GITHUB_OUTPUT" echo "ROUTE_PATH=/pr-$PR_NUMBER" >> "$GITHUB_OUTPUT" echo "URL=${{ steps.default-vars.outputs.URL }}/pr-$PR_NUMBER" >> "$GITHUB_OUTPUT" else echo "APP_TITLE=${{ steps.default-vars.outputs.APP_TITLE }}" >> "$GITHUB_OUTPUT" - echo "ENVIRONMENT=${{ steps.default-vars.outputs.ENVIRONMENT }}" >> "$GITHUB_OUTPUT" echo "JOB_NAME=${{ steps.default-vars.outputs.JOB_NAME }}" >> "$GITHUB_OUTPUT" echo "ROUTE_PATH=${{ steps.default-vars.outputs.ROUTE_PATH }}" >> "$GITHUB_OUTPUT" echo "URL=${{ steps.default-vars.outputs.URL }}/app" >> "$GITHUB_OUTPUT" @@ -77,12 +63,12 @@ jobs: timeout-minutes: 10 steps: - name: Checkout repository from pull request - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ needs.set-vars.outputs.ref }} if: ${{ needs.set-vars.outputs.ref != '' }} - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 if: ${{ needs.set-vars.outputs.ref == '' }} - name: Build & Push uses: ./.github/actions/build-push-container @@ -100,29 +86,29 @@ jobs: deploy: name: Deploys to selected environment environment: - name: ${{ needs.set-vars.outputs.ENVIRONMENT }} + name: pr url: ${{ needs.set-vars.outputs.URL }} runs-on: ubuntu-latest needs: [set-vars, build] timeout-minutes: 12 steps: - name: Checkout repository from pull request - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ needs.set-vars.outputs.ref }} if: ${{ needs.set-vars.outputs.ref != '' }} - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 if: ${{ needs.set-vars.outputs.ref == '' }} - name: Deploy to environment uses: ./.github/actions/deploy-to-environment with: app_name: ${{ vars.APP_NAME }} acronym: ${{ env.ACRONYM }} - environment: ${{ needs.set-vars.outputs.ENVIRONMENT }} + environment: pr job_name: ${{ needs.set-vars.outputs.JOB_NAME }} namespace_prefix: ${{ vars.NAMESPACE_PREFIX }} - namespace_environment: ${{ github.event.inputs.environment }} + namespace_environment: dev openshift_server: ${{ secrets.OPENSHIFT_SERVER }} openshift_token: ${{ secrets.OPENSHIFT_TOKEN }} server_host: ${{ vars.SERVER_HOST }} @@ -131,8 +117,9 @@ jobs: ref: ${{ needs.set-vars.outputs.ref }} - name: Release Comment on PR uses: marocchino/sticky-pull-request-comment@v2 - if: github.event.inputs.pr-number != '' && success() + if: ${{ github.event.inputs.pr-number }} != '' && success() with: header: release message: | - Release ${{ github.sha }} deployed at \ No newline at end of file + Release ${{ github.sha }} deployed at + number: ${{ github.event.inputs.pr-number }} diff --git a/.github/workflows/on_push.yaml b/.github/workflows/on_push.yaml new file mode 100644 index 000000000..ecddda514 --- /dev/null +++ b/.github/workflows/on_push.yaml @@ -0,0 +1,115 @@ +name: Push + +env: + ACRONYM: chefs + +on: + push: + branches: + - main + tags: + - v*.*.* + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + name: Build & Push + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Build & Push + uses: ./.github/actions/build-push-container + with: + context: . + image_name: ${{ vars.APP_NAME }} + github_username: ${{ github.repository_owner }} + github_token: ${{ secrets.GITHUB_TOKEN }} + app_contact: ${{ secrets.VITE_CONTACT }} + + deploy-dev: + name: Deploy to Dev + environment: + name: dev + url: https://${{ env.ACRONYM }}-dev.apps.silver.devops.gov.bc.ca/app + runs-on: ubuntu-latest + needs: build + timeout-minutes: 12 + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Deploy to Dev + uses: ./.github/actions/deploy-to-environment + with: + app_name: ${{ vars.APP_NAME }} + acronym: ${{ env.ACRONYM }} + environment: dev + job_name: master + namespace_prefix: ${{ vars.NAMESPACE_PREFIX }} + namespace_environment: dev + openshift_server: ${{ secrets.OPENSHIFT_SERVER }} + openshift_token: ${{ secrets.OPENSHIFT_TOKEN }} + server_host: ${{ vars.SERVER_HOST }} + route_path: /app + route_prefix: ${{ vars.ROUTE_PREFIX }} + + deploy-test: + name: Deploy to Test + environment: + name: test + url: https://${{ env.ACRONYM }}-test.apps.silver.devops.gov.bc.ca/app + runs-on: ubuntu-latest + needs: + - build + - deploy-dev + timeout-minutes: 12 + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Deploy to Test + uses: ./.github/actions/deploy-to-environment + with: + app_name: ${{ vars.APP_NAME }} + acronym: ${{ env.ACRONYM }} + environment: test + job_name: master + namespace_prefix: ${{ vars.NAMESPACE_PREFIX }} + namespace_environment: test + openshift_server: ${{ secrets.OPENSHIFT_SERVER }} + openshift_token: ${{ secrets.OPENSHIFT_TOKEN }} + server_host: ${{ vars.SERVER_HOST }} + route_path: /app + route_prefix: ${{ vars.ROUTE_PREFIX }} + + deploy-prod: + name: Deploy to Prod + environment: + name: prod + url: https://${{ env.ACRONYM }}.apps.silver.devops.gov.bc.ca/app + runs-on: ubuntu-latest + needs: + - build + - deploy-dev + - deploy-test + timeout-minutes: 12 + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Deploy to Prod + uses: ./.github/actions/deploy-to-environment + with: + app_name: ${{ vars.APP_NAME }} + acronym: ${{ env.ACRONYM }} + environment: prod + job_name: master + namespace_prefix: ${{ vars.NAMESPACE_PREFIX }} + namespace_environment: prod + openshift_server: ${{ secrets.OPENSHIFT_SERVER }} + openshift_token: ${{ secrets.OPENSHIFT_TOKEN }} + server_host: ${{ vars.SERVER_HOST }} + route_path: /app + route_prefix: ${{ vars.ROUTE_PREFIX }} diff --git a/app/frontend/src/components/forms/manage/ApiKey.vue b/app/frontend/src/components/forms/manage/ApiKey.vue index 75782ec56..9d1aef3e5 100644 --- a/app/frontend/src/components/forms/manage/ApiKey.vue +++ b/app/frontend/src/components/forms/manage/ApiKey.vue @@ -17,6 +17,7 @@ export default { showConfirmationDialog: false, showDeleteDialog: false, showSecret: false, + filesApiAccess: false, }; }, computed: { @@ -55,6 +56,7 @@ export default { 'deleteApiKey', 'generateApiKey', 'readApiKey', + 'filesApiKeyAccess', ]), async createKey() { this.loading = true; @@ -67,6 +69,7 @@ export default { async deleteKey() { this.loading = true; await this.deleteApiKey(this.form.id); + this.filesApiAccess = false; this.loading = false; this.showDeleteDialog = false; }, @@ -74,8 +77,16 @@ export default { async readKey() { this.loading = true; await this.readApiKey(this.form.id); + this.filesApiAccess = this.apiKey?.filesApiAccess; this.loading = false; }, + + async updateKey() { + this.loading = true; + await this.filesApiKeyAccess(this.form.id, this.filesApiAccess); + this.loading = false; + }, + showHideKey() { this.showSecret = !this.showSecret; }, @@ -100,6 +111,9 @@ export default {
  • {{ $t('trans.apiKey.infoC') }}
  • +
  • + {{ $t('trans.apiKey.infoD') }} +
  • @@ -234,4 +248,14 @@ export default { + + + + + diff --git a/app/frontend/src/internationalization/trans/chefs/ar/ar.json b/app/frontend/src/internationalization/trans/chefs/ar/ar.json index 3bd8bb3f6..abee5e0cc 100644 --- a/app/frontend/src/internationalization/trans/chefs/ar/ar.json +++ b/app/frontend/src/internationalization/trans/chefs/ar/ar.json @@ -229,6 +229,7 @@ "infoA": "تأكد من تخزين سر مفتاح واجهة برمجة التطبيقات في مكان آمن (أي مخزن المفاتيح).", "infoB": "يمنحك مفتاح API وصولاً غير مقيد إلى النموذج الخاص بك. لا تعطي مفتاح API الخاص بك لأي شخص.", "infoC": "يجب استخدام مفتاح API فقط لتفاعلات النظام المؤتمتة. لا تستخدم مفتاح API الخاص بك للوصول المستند إلى المستخدم", + "infoD": "إذا كنت ترغب في الوصول إلى الملفات المرسلة عبر مفتاح API، فيرجى تمكين مربع الاختيار التالي بعد إنشاء المفتاح.", "deleteKey": "مفتاح الحذف", "apiKey": "مفتاح API", "hideSecret": "إخفاء السر", @@ -245,7 +246,8 @@ "formOwnerKeyAcess": "يجب أن تكون مالك النموذج لإدارة مفاتيح API.", "regenerate": "تجديد", "generate": "يولد", - "secret": "سر" + "secret": "سر", + "filesAPIAccess":"السماح لمفتاح API هذا بالوصول إلى الملفات المرسلة" }, "manageVersions": { "important": "مهم!", diff --git a/app/frontend/src/internationalization/trans/chefs/de/de.json b/app/frontend/src/internationalization/trans/chefs/de/de.json index 331949d4f..892b3d7f7 100644 --- a/app/frontend/src/internationalization/trans/chefs/de/de.json +++ b/app/frontend/src/internationalization/trans/chefs/de/de.json @@ -229,6 +229,7 @@ "infoA": "Stellen Sie sicher, dass Ihr API-Schlüsselgeheimnis an einem sicheren Ort (d. h. im Schlüsseltresor) gespeichert wird.", "infoB": "Ihr API-Schlüssel gewährt uneingeschränkten Zugriff auf Ihr Formular. Geben Sie Ihren API-Schlüssel nicht an Dritte weiter.", "infoC": "Der API-Schlüssel sollte NUR für automatisierte Systeminteraktionen verwendet werden. Verwenden Sie Ihren API-Schlüssel nicht für den benutzerbasierten Zugriff", + "infoD": "Wenn Sie über den API-Schlüssel auf übermittelte Dateien zugreifen möchten, aktivieren Sie bitte nach der Schlüsselgenerierung das folgende Kontrollkästchen.", "deleteKey": "Schlüssel löschen", "apiKey": "API-Schlüssel", "hideSecret": "Geheimnis verbergen", @@ -245,7 +246,8 @@ "formOwnerKeyAcess": "Sie müssen der Formulareigentümer sein, um API-Schlüssel verwalten zu können.", "regenerate": "Regenerieren", "generate": "Generieren", - "secret": "Geheimnis" + "secret": "Geheimnis", + "filesAPIAccess": "Erlauben Sie diesem API-Schlüssel den Zugriff auf übermittelte Dateien" }, "manageVersions": { "important": "WICHTIG!", diff --git a/app/frontend/src/internationalization/trans/chefs/en/en.json b/app/frontend/src/internationalization/trans/chefs/en/en.json index 577e92fe8..68afe9851 100644 --- a/app/frontend/src/internationalization/trans/chefs/en/en.json +++ b/app/frontend/src/internationalization/trans/chefs/en/en.json @@ -225,6 +225,7 @@ "infoA": "Ensure that your API key secret is stored in a secure location (i.e. key vault).", "infoB": "Your API key grants unrestricted access to your form. Do not give out your API key to anyone.", "infoC": "The API key should ONLY be used for automated system interactions. Do not use your API key for user based access", + "infoD": "If you wish to access submitted files via the API Key, please enable the following checkbox after generating the key.", "deleteKey": "Delete Key", "apiKey": "api key", "hideSecret": "Hide Secret", @@ -241,7 +242,8 @@ "formOwnerKeyAcess": "You must be the Form Owner to manage API Keys.", "regenerate": "Regenerate", "generate": "Generate", - "secret": "Secret" + "secret": "Secret", + "filesAPIAccess": "Allow this API key to access submitted files" }, "manageVersions": { "important": "IMPORTANT!", diff --git a/app/frontend/src/internationalization/trans/chefs/es/es.json b/app/frontend/src/internationalization/trans/chefs/es/es.json index 372873551..7c667a4fe 100644 --- a/app/frontend/src/internationalization/trans/chefs/es/es.json +++ b/app/frontend/src/internationalization/trans/chefs/es/es.json @@ -229,6 +229,7 @@ "infoA": "Asegúrese de que el secreto de su clave de API esté almacenado en una ubicación segura (es decir, un almacén de claves).", "infoB": "Su clave API otorga acceso sin restricciones a su formulario. No proporcione su clave API a nadie.", "infoC": "La clave API SÓLO debe usarse para interacciones automatizadas del sistema. No utilice su clave API para el acceso basado en usuarios", + "infoD": "Si desea acceder a los archivos enviados a través de la clave API, habilite la siguiente casilla de verificación después de generar la clave.", "deleteKey": "Eliminar clave", "apiKey": "Clave API", "hideSecret": "Ocultar secreto", @@ -245,7 +246,8 @@ "formOwnerKeyAcess": "Debe ser el propietario del formulario para administrar las claves API.", "regenerate": "Regenerado", "generate": "Generar", - "secret": "Secreto" + "secret": "Secreto", + "filesAPIAccess": "Permitir que esta clave API acceda a los archivos enviados" }, "manageVersions": { "important": "¡IMPORTANTE!", diff --git a/app/frontend/src/internationalization/trans/chefs/fa/fa.json b/app/frontend/src/internationalization/trans/chefs/fa/fa.json index f899dbf92..7e7360b8c 100644 --- a/app/frontend/src/internationalization/trans/chefs/fa/fa.json +++ b/app/frontend/src/internationalization/trans/chefs/fa/fa.json @@ -229,6 +229,7 @@ "infoA": "اطمینان حاصل کنید که رمز کلید API شما در یک مکان امن (به عنوان مثال صندوق کلید) ذخیره شده است.", "infoB": "کلید API شما به فرم شما دسترسی نامحدود می دهد. کلید API خود را به کسی ندهید.", "infoC": "کلید API فقط باید برای تعاملات سیستم خودکار استفاده شود. از کلید API خود برای دسترسی مبتنی بر کاربر استفاده نکنید", + "infoD": "اگر می‌خواهید از طریق کلید API به فایل‌های ارسالی دسترسی داشته باشید، لطفاً پس از تولید کلید، کادر زیر را فعال کنید.", "deleteKey": "حذف کلید", "apiKey": "کلید ای پی ای", "hideSecret": "پنهان کردن راز", @@ -245,7 +246,8 @@ "formOwnerKeyAcess": "برای مدیریت کلیدهای API باید مالک فرم باشید.", "regenerate": "بازسازی کنید", "generate": "تولید می کنند", - "secret": "راز" + "secret": "راز", + "filesAPIAccess":"به این کلید API اجازه دهید به فایل های ارسالی دسترسی داشته باشد" }, "manageVersions": { "important": "مهم!", diff --git a/app/frontend/src/internationalization/trans/chefs/fr/fr.json b/app/frontend/src/internationalization/trans/chefs/fr/fr.json index b0013d276..38a9aeaf4 100644 --- a/app/frontend/src/internationalization/trans/chefs/fr/fr.json +++ b/app/frontend/src/internationalization/trans/chefs/fr/fr.json @@ -229,6 +229,7 @@ "infoA": "Assurez-vous que la clé secrète de votre API est stockée dans un emplacement sécurisé (c'est-à-dire un coffre de clés).", "infoB": "Votre clé API accorde un accès illimité à votre formulaire. Ne donnez votre clé API à personne.", "infoC": "La clé API doit UNIQUEMENT être utilisée pour les interactions système automatisées. N'utilisez pas votre clé API pour un accès basé sur l'utilisateur", + "infoD": "Si vous souhaitez accéder aux fichiers soumis via la clé API, veuillez cocher la case suivante après avoir généré la clé.", "deleteKey": "Supprimer la clé", "apiKey": "clé API", "hideSecret": "Cacher le secret", @@ -245,7 +246,8 @@ "formOwnerKeyAcess": "Vous devez être le propriétaire du formulaire pour gérer les clés API.", "regenerate": "Régénérer", "generate": "Générer", - "secret": "Secret" + "secret": "Secret", + "filesAPIAccess":"Autoriser cette clé API à accéder aux fichiers soumis" }, "manageVersions": { "important": "IMPORTANT!", diff --git a/app/frontend/src/internationalization/trans/chefs/hi/hi.json b/app/frontend/src/internationalization/trans/chefs/hi/hi.json index d3333ac31..2e44d5369 100644 --- a/app/frontend/src/internationalization/trans/chefs/hi/hi.json +++ b/app/frontend/src/internationalization/trans/chefs/hi/hi.json @@ -229,6 +229,7 @@ "infoA": "सुनिश्चित करें कि आपकी एपीआई कुंजी रहस्य एक सुरक्षित स्थान (यानी कुंजी वॉल्ट) में संग्रहीत है।", "infoB": "आपकी एपीआई कुंजी आपके फॉर्म तक अप्रतिबंधित पहुंच प्रदान करती है। अपनी एपीआई कुंजी किसी को न दें।", "infoC": "एपीआई कुंजी का उपयोग केवल स्वचालित सिस्टम इंटरैक्शन के लिए किया जाना चाहिए। उपयोगकर्ता आधारित पहुंच के लिए अपनी एपीआई कुंजी का उपयोग न करें", + "infoD": "यदि आप एपीआई कुंजी के माध्यम से सबमिट की गई फ़ाइलों तक पहुंच चाहते हैं, तो कृपया कुंजी उत्पन्न करने के बाद निम्नलिखित चेकबॉक्स को सक्षम करें।", "deleteKey": "कुंजी हटाएँ", "apiKey": "एपीआई कुंजी", "hideSecret": "गुप्त छिपाएँ", @@ -245,7 +246,8 @@ "formOwnerKeyAcess": "एपीआई कुंजी प्रबंधित करने के लिए आपको फॉर्म स्वामी होना चाहिए।", "regenerate": "पुनः जेनरेट", "generate": "बनाना", - "secret": "गुप्त" + "secret": "गुप्त", + "filesAPIAccess": "इस एपीआई कुंजी को सबमिट की गई फ़ाइलों तक पहुंचने की अनुमति दें" }, "manageVersions": { "important": "महत्वपूर्ण!", diff --git a/app/frontend/src/internationalization/trans/chefs/it/it.json b/app/frontend/src/internationalization/trans/chefs/it/it.json index c0556044d..2d1a7f4f8 100644 --- a/app/frontend/src/internationalization/trans/chefs/it/it.json +++ b/app/frontend/src/internationalization/trans/chefs/it/it.json @@ -229,6 +229,7 @@ "infoA": "Assicurati che il segreto della tua chiave API sia archiviato in un luogo sicuro (ad es. Key Vault).", "infoB": "La tua chiave API garantisce l'accesso illimitato al tuo modulo. Non dare la tua chiave API a nessuno.", "infoC": "La chiave API deve essere utilizzata SOLO per le interazioni di sistema automatizzate. Non utilizzare la chiave API per l'accesso basato sull'utente", + "infoD":"Se desideri accedere ai file inviati tramite la chiave API, abilita la seguente casella di controllo dopo aver generato la chiave.", "deleteKey": "Elimina chiave", "apiKey": "chiave API", "hideSecret": "Nascondi segreto", @@ -245,7 +246,8 @@ "formOwnerKeyAcess": "Devi essere il proprietario del modulo per gestire le chiavi API.", "regenerate": "Rigenerare", "generate": "creare", - "secret": "Segreto" + "secret": "Segreto", + "filesAPIAccess": "Consenti a questa chiave API di accedere ai file inviati" }, "manageVersions": { "important": "IMPORTANTE!", diff --git a/app/frontend/src/internationalization/trans/chefs/ja/ja.json b/app/frontend/src/internationalization/trans/chefs/ja/ja.json index 1f7ed1f81..258f2ada7 100644 --- a/app/frontend/src/internationalization/trans/chefs/ja/ja.json +++ b/app/frontend/src/internationalization/trans/chefs/ja/ja.json @@ -229,6 +229,7 @@ "infoA": "API キー シークレットが安全な場所 (キー コンテナーなど) に保存されていることを確認してください。", "infoB": "API キーにより、フォームへの無制限のアクセスが許可されます。 API キーを誰にも渡さないでください。", "infoC": "API キーは、自動化されたシステム操作にのみ使用してください。ユーザーベースのアクセスには API キーを使用しないでください", + "infoD": "API キーを介して送信されたファイルにアクセスしたい場合は、キーを生成した後、次のチェックボックスをオンにしてください。", "deleteKey": "キーの削除", "apiKey": "APIキー", "hideSecret": "秘密を隠す", @@ -245,7 +246,8 @@ "formOwnerKeyAcess": "API キーを管理するには、フォーム所有者である必要があります。", "regenerate": "再生する", "generate": "生成", - "secret": "ひみつ" + "secret": "ひみつ", + "filesAPIAccess": "この API キーに送信されたファイルへのアクセスを許可します" }, "manageVersions": { "important": "重要!", diff --git a/app/frontend/src/internationalization/trans/chefs/ko/ko.json b/app/frontend/src/internationalization/trans/chefs/ko/ko.json index 6e7b08627..afa90e71c 100644 --- a/app/frontend/src/internationalization/trans/chefs/ko/ko.json +++ b/app/frontend/src/internationalization/trans/chefs/ko/ko.json @@ -229,6 +229,7 @@ "infoA": "API 키 비밀이 안전한 위치(예: 키 자격 증명 모음)에 저장되어 있는지 확인합니다.", "infoB": "API 키는 양식에 대한 무제한 액세스 권한을 부여합니다. 누구에게도 API 키를 제공하지 마십시오.", "infoC": "API 키는 자동화된 시스템 상호 작용에만 사용해야 합니다. 사용자 기반 액세스에 API 키를 사용하지 마십시오.", + "infoD": "API 키를 통해 제출된 파일에 액세스하려면 키를 생성한 후 다음 확인란을 활성화하세요.", "deleteKey": "키 삭제", "apiKey": "API 키", "hideSecret": "비밀 숨기기", @@ -245,7 +246,8 @@ "formOwnerKeyAcess": "API 키를 관리하려면 양식 소유자 여야 합니다.", "regenerate": "재생하다", "generate": "생성하다", - "secret": "비밀" + "secret": "비밀", + "filesAPIAccess": "이 API 키가 제출된 파일에 액세스하도록 허용" }, "manageVersions": { "important": "중요한!", diff --git a/app/frontend/src/internationalization/trans/chefs/pa/pa.json b/app/frontend/src/internationalization/trans/chefs/pa/pa.json index f05c92ef0..a738ac4db 100644 --- a/app/frontend/src/internationalization/trans/chefs/pa/pa.json +++ b/app/frontend/src/internationalization/trans/chefs/pa/pa.json @@ -229,6 +229,7 @@ "infoA": "ਯਕੀਨੀ ਬਣਾਓ ਕਿ ਤੁਹਾਡੀ API ਕੁੰਜੀ ਗੁਪਤ ਇੱਕ ਸੁਰੱਖਿਅਤ ਸਥਾਨ (ਜਿਵੇਂ ਕਿ ਕੁੰਜੀ ਵਾਲਟ) ਵਿੱਚ ਸਟੋਰ ਕੀਤੀ ਗਈ ਹੈ।", "infoB": "ਤੁਹਾਡੀ API ਕੁੰਜੀ ਤੁਹਾਡੇ ਫਾਰਮ ਤੱਕ ਅਪ੍ਰਬੰਧਿਤ ਪਹੁੰਚ ਪ੍ਰਦਾਨ ਕਰਦੀ ਹੈ। ਆਪਣੀ API ਕੁੰਜੀ ਕਿਸੇ ਨੂੰ ਨਾ ਦਿਓ।", "infoC": "API ਕੁੰਜੀ ਦੀ ਵਰਤੋਂ ਸਿਰਫ਼ ਆਟੋਮੇਟਿਡ ਸਿਸਟਮ ਪਰਸਪਰ ਕ੍ਰਿਆਵਾਂ ਲਈ ਕੀਤੀ ਜਾਣੀ ਚਾਹੀਦੀ ਹੈ। ਉਪਭੋਗਤਾ ਅਧਾਰਤ ਪਹੁੰਚ ਲਈ ਆਪਣੀ API ਕੁੰਜੀ ਦੀ ਵਰਤੋਂ ਨਾ ਕਰੋ", + "infoD": "ਜੇਕਰ ਤੁਸੀਂ API ਕੁੰਜੀ ਰਾਹੀਂ ਸਪੁਰਦ ਕੀਤੀਆਂ ਫਾਈਲਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ, ਤਾਂ ਕਿਰਪਾ ਕਰਕੇ ਕੁੰਜੀ ਬਣਾਉਣ ਤੋਂ ਬਾਅਦ ਹੇਠਾਂ ਦਿੱਤੇ ਚੈਕਬਾਕਸ ਨੂੰ ਸਮਰੱਥ ਬਣਾਓ।", "deleteKey": "ਕੁੰਜੀ ਮਿਟਾਓ", "apiKey": "api ਕੁੰਜੀ", "hideSecret": "ਗੁਪਤ ਲੁਕਾਓ", @@ -245,7 +246,8 @@ "formOwnerKeyAcess": "API ਕੁੰਜੀਆਂ ਦਾ ਪ੍ਰਬੰਧਨ ਕਰਨ ਲਈ ਤੁਹਾਨੂੰ ਫਾਰਮ ਦਾ ਮਾਲਕ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ।", "regenerate": "ਪੁਨਰਜਨਮ", "generate": "ਪੈਦਾ ਕਰੋ", - "secret": "ਗੁਪਤ" + "secret": "ਗੁਪਤ", + "filesAPIAccess": "ਇਸ API ਕੁੰਜੀ ਨੂੰ ਸਪੁਰਦ ਕੀਤੀਆਂ ਫ਼ਾਈਲਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦਿਓ" }, "manageVersions": { "important": "ਮਹੱਤਵਪੂਰਨ!", diff --git a/app/frontend/src/internationalization/trans/chefs/pt/pt.json b/app/frontend/src/internationalization/trans/chefs/pt/pt.json index 6d30dd28d..23c33a6cd 100644 --- a/app/frontend/src/internationalization/trans/chefs/pt/pt.json +++ b/app/frontend/src/internationalization/trans/chefs/pt/pt.json @@ -229,6 +229,7 @@ "infoA": "Certifique-se de que o segredo da sua chave de API esteja armazenado em um local seguro (ou seja, cofre de chaves).", "infoB": "Sua chave de API concede acesso irrestrito ao seu formulário. Não dê sua chave de API para ninguém.", "infoC": "A chave de API APENAS deve ser usada para interações automatizadas do sistema. Não use sua chave de API para acesso baseado no usuário", + "infoD": "Se você deseja acessar os arquivos enviados por meio da chave API, marque a caixa de seleção a seguir após gerar a chave.", "deleteKey": "Excluir chave", "apiKey": "Chave API", "hideSecret": "Ocultar Segredo", @@ -245,7 +246,8 @@ "formOwnerKeyAcess": "Você deve ser o proprietário do formulário para gerenciar chaves de API.", "regenerate": "Regenerado", "generate": "Gerar", - "secret": "Segredo" + "secret": "Segredo", + "filesAPIAccess": "Permitir que esta chave de API acesse os arquivos enviados" }, "manageVersions": { "important": "IMPORTANTE!", diff --git a/app/frontend/src/internationalization/trans/chefs/ru/ru.json b/app/frontend/src/internationalization/trans/chefs/ru/ru.json index 5ab21646e..cc36b8aa3 100644 --- a/app/frontend/src/internationalization/trans/chefs/ru/ru.json +++ b/app/frontend/src/internationalization/trans/chefs/ru/ru.json @@ -229,6 +229,7 @@ "infoA": "Убедитесь, что ваш секрет ключа API хранится в безопасном месте (например, в хранилище ключей).", "infoB": "Ваш ключ API предоставляет неограниченный доступ к вашей форме. Никому не передавайте свой ключ API.", "infoC": "Ключ API следует использовать ТОЛЬКО для взаимодействия с автоматизированной системой. Не используйте свой ключ API для пользовательского доступа", + "infoD": "Если вы хотите получить доступ к отправленным файлам через ключ API, установите следующий флажок после создания ключа.", "deleteKey": "Удалить ключ", "apiKey": "API-ключ", "hideSecret": "Скрыть секрет", @@ -245,7 +246,8 @@ "formOwnerKeyAcess": "Вы должны быть владельцем формы для управления ключами API.", "regenerate": "Регенерировать", "generate": "Создать", - "secret": "Секрет" + "secret": "Секрет", + "filesAPIAccess": "Разрешить этому ключу API доступ к отправленным файлам" }, "manageVersions": { "important": "ВАЖНО!", diff --git a/app/frontend/src/internationalization/trans/chefs/tl/tl.json b/app/frontend/src/internationalization/trans/chefs/tl/tl.json index 647ca319d..e9a3320a0 100644 --- a/app/frontend/src/internationalization/trans/chefs/tl/tl.json +++ b/app/frontend/src/internationalization/trans/chefs/tl/tl.json @@ -229,6 +229,7 @@ "infoA": "Tiyaking nakaimbak ang iyong lihim ng API key sa isang secure na lokasyon (ibig sabihin, key vault).", "infoB": "Ang iyong API key ay nagbibigay ng walang limitasyong pag-access sa iyong form. Huwag ibigay ang iyong API key sa sinuman.", "infoC": "Dapat LAMANG gamitin ang API key para sa mga automated na pakikipag-ugnayan ng system. Huwag gamitin ang iyong API key para sa user based na access", + "infoD": "Kung gusto mong i-access ang mga isinumiteng file sa pamamagitan ng API Key, mangyaring paganahin ang sumusunod na checkbox pagkatapos mabuo ang key.", "deleteKey": "Tanggalin ang Susi", "apiKey": "susi ng api", "hideSecret": "Itago ang Lihim", @@ -245,7 +246,8 @@ "formOwnerKeyAcess": "Ikaw dapat ang May-ari ng Form para pamahalaan ang Mga API Key.", "regenerate": "Magbagong-buhay", "generate": "Bumuo", - "secret": "Lihim" + "secret": "Lihim", + "filesAPIAccess": "Payagan ang API key na ito na ma-access ang mga isinumiteng file" }, "manageVersions": { "important": "MAHALAGA!", diff --git a/app/frontend/src/internationalization/trans/chefs/uk/uk.json b/app/frontend/src/internationalization/trans/chefs/uk/uk.json index 695452f64..37c32bb38 100644 --- a/app/frontend/src/internationalization/trans/chefs/uk/uk.json +++ b/app/frontend/src/internationalization/trans/chefs/uk/uk.json @@ -229,6 +229,7 @@ "infoA": "Переконайтеся, що ваш секретний ключ API зберігається в безпечному місці (тобто в сховищі ключів).", "infoB": "Ваш ключ API надає необмежений доступ до вашої форми. Нікому не передавайте свій ключ API.", "infoC": "Ключ API слід використовувати ЛИШЕ для автоматизованої взаємодії з системою. Не використовуйте свій ключ API для доступу на основі користувача", + "infoD": "Якщо ви бажаєте отримати доступ до надісланих файлів за допомогою ключа API, увімкніть наступний прапорець після створення ключа.", "deleteKey": "Видалити ключ", "apiKey": "ключ API", "hideSecret": "Приховати секрет", @@ -245,7 +246,8 @@ "formOwnerKeyAcess": "Ви повинні бути власником форми , щоб керувати ключами API.", "regenerate": "Регенерувати", "generate": "Генерувати", - "secret": "Секрет" + "secret": "Секрет", + "filesAPIAccess": "Дозволити цьому ключу API доступ до надісланих файлів" }, "manageVersions": { "important": "ВАЖЛИВО!", diff --git a/app/frontend/src/internationalization/trans/chefs/vi/vi.json b/app/frontend/src/internationalization/trans/chefs/vi/vi.json index 73872a2e7..6412a09a2 100644 --- a/app/frontend/src/internationalization/trans/chefs/vi/vi.json +++ b/app/frontend/src/internationalization/trans/chefs/vi/vi.json @@ -229,6 +229,7 @@ "infoA": "Đảm bảo rằng khóa bí mật API của bạn được lưu trữ ở một vị trí an toàn (tức là kho khóa).", "infoB": "Khóa API của bạn cấp quyền truy cập không hạn chế vào biểu mẫu của bạn. Không cung cấp khóa API của bạn cho bất kỳ ai.", "infoC": "Khóa API CHỈ nên được sử dụng cho các tương tác hệ thống tự động. Không sử dụng khóa API của bạn để truy cập dựa trên người dùng", + "infoD": "Nếu bạn muốn truy cập các tệp đã gửi qua Khóa API, vui lòng bật hộp kiểm sau sau khi tạo khóa.", "deleteKey": "Phím xoá", "apiKey": "Mã API", "hideSecret": "Ẩn bí mật", @@ -245,7 +246,8 @@ "formOwnerKeyAcess": "Bạn phải là Chủ sở hữu biểu mẫu để quản lý Khóa API.", "regenerate": "tái tạo", "generate": "Phát ra", - "secret": "Bí mật" + "secret": "Bí mật", + "filesAPIAccess": "Cho phép khóa API này truy cập vào các tệp đã gửi" }, "manageVersions": { "important": "QUAN TRỌNG!", diff --git a/app/frontend/src/internationalization/trans/chefs/zh/zh.json b/app/frontend/src/internationalization/trans/chefs/zh/zh.json index 74aaa5222..ff9de7376 100644 --- a/app/frontend/src/internationalization/trans/chefs/zh/zh.json +++ b/app/frontend/src/internationalization/trans/chefs/zh/zh.json @@ -229,6 +229,7 @@ "infoA": "确保您的 API 密钥秘密存储在安全位置(即密钥保管库)。", "infoB": "您的 API 密钥可以不受限制地访问您的表单。请勿将您的 API 密钥透露给任何人。", "infoC": "API 密钥只能用于自动化系统交互。请勿使用您的 API 密钥进行基于用户的访问", + "infoD": "如果您希望通过 API 密钥访问提交的文件,请在生成密钥后启用以下复选框。", "deleteKey": "删除键", "apiKey": "API密钥", "hideSecret": "隐藏秘密", @@ -245,7 +246,8 @@ "formOwnerKeyAcess": "您必须是表单所有者才能管理 API 密钥。", "regenerate": "再生", "generate": "产生", - "secret": "秘密" + "secret": "秘密", + "filesAPIAccess": "允许此 API 密钥访问提交的文件" }, "manageVersions": { "important": "重要的!", diff --git a/app/frontend/src/internationalization/trans/chefs/zhTW/zh-TW.json b/app/frontend/src/internationalization/trans/chefs/zhTW/zh-TW.json index 88d856e10..b2ed03d4e 100644 --- a/app/frontend/src/internationalization/trans/chefs/zhTW/zh-TW.json +++ b/app/frontend/src/internationalization/trans/chefs/zhTW/zh-TW.json @@ -229,6 +229,7 @@ "infoA": "確保您的 API 密鑰秘密存儲在安全位置(即密鑰保管庫)。", "infoB": "您的 API 密鑰可以不受限制地訪問您的表單。請勿將您的 API 密鑰透露給任何人。", "infoC": "API 密鑰只能用於自動化系統交互。請勿使用您的 API 密鑰進行基於用戶的訪問", + "infoD": "如果您希望透過 API 金鑰存取提交的文件,請在產生金鑰後啟用下列複選框。", "deleteKey": "刪除鍵", "apiKey": "API密鑰", "hideSecret": "隱藏秘密", @@ -245,7 +246,8 @@ "formOwnerKeyAcess": "您必須是表單所有者才能管理 API 密鑰。", "regenerate": "再生", "generate": "產生", - "secret": "秘密" + "secret": "秘密", + "filesAPIAccess": "允許此 API 金鑰存取提交的文件" }, "manageVersions": { "important": "重要的!", diff --git a/app/frontend/src/services/apiKeyService.js b/app/frontend/src/services/apiKeyService.js index 175a288a7..8e4887ff1 100644 --- a/app/frontend/src/services/apiKeyService.js +++ b/app/frontend/src/services/apiKeyService.js @@ -31,4 +31,17 @@ export default { deleteApiKey(formId) { return appAxios().delete(`${ApiRoutes.FORMS}/${formId}${ApiRoutes.APIKEY}`); }, + + /** + * @function filesApiKeyAccess + * Set the boolean for the API key to access files + * @param {string} formId The form uuid, {boolean} filesApiAcces true/false to allow/deny access + * @returns {Promise} An axios response + */ + filesApiKeyAccess(formId, filesApiAccess) { + return appAxios().put( + `${ApiRoutes.FORMS}/${formId}${ApiRoutes.APIKEY}${ApiRoutes.FILES_API_ACCESS}`, + { filesApiAccess } + ); + }, }; diff --git a/app/frontend/src/store/form.js b/app/frontend/src/store/form.js index 0df4cd129..cbe93e100 100644 --- a/app/frontend/src/store/form.js +++ b/app/frontend/src/store/form.js @@ -741,6 +741,26 @@ export const useFormStore = defineStore('form', { }); } }, + async filesApiKeyAccess(formId, filesApiAccess) { + const notificationStore = useNotificationStore(); + try { + const { data } = await apiKeyService.filesApiKeyAccess( + formId, + filesApiAccess + ); + this.apiKey = data; + notificationStore.addNotification({ + text: 'API Key updated successfully.', + ...NotificationTypes.SUCCESS, + }); + } catch (error) { + const notificationStore = useNotificationStore(); + notificationStore.addNotification({ + text: 'An error occurred while trying to update the API Key.', + consoleError: `Error updating API Key for form ${formId}: ${error}`, + }); + } + }, async getFCProactiveHelpImageUrl(componentId) { try { diff --git a/app/frontend/src/utils/constants.js b/app/frontend/src/utils/constants.js index a55cae016..c026c9d4f 100755 --- a/app/frontend/src/utils/constants.js +++ b/app/frontend/src/utils/constants.js @@ -13,6 +13,7 @@ export const ApiRoutes = Object.freeze({ USERS: '/users', FILES: '/files', UTILS: '/utils', + FILES_API_ACCESS: '/filesApiAccess', }); /** Roles a user can have on a form. These are defined in the DB and sent from the API */ diff --git a/app/frontend/tests/unit/utils/constants.spec.js b/app/frontend/tests/unit/utils/constants.spec.js index 7540826cf..eb4df08e6 100644 --- a/app/frontend/tests/unit/utils/constants.spec.js +++ b/app/frontend/tests/unit/utils/constants.spec.js @@ -13,6 +13,7 @@ describe('Constants', () => { SUBMISSION: '/submissions', USERS: '/users', UTILS: '/utils', + FILES_API_ACCESS: '/filesApiAccess', }); }); diff --git a/app/src/db/migrations/20240115201832_files-api-access.js b/app/src/db/migrations/20240115201832_files-api-access.js new file mode 100644 index 000000000..0e6b773ca --- /dev/null +++ b/app/src/db/migrations/20240115201832_files-api-access.js @@ -0,0 +1,21 @@ +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.up = function(knex) { + return Promise.resolve() + .then(() => knex.schema.alterTable('form_api_key', table => { + table.boolean('filesApiAccess').defaultTo(false).comment('Keeps track of whether files can be accessed using the API key'); + })); +}; + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.down = function(knex) { + return Promise.resolve() + .then(() => knex.schema.alterTable('form_api_key', table => { + table.dropColumn('filesApiAccess'); + })); +}; diff --git a/app/src/docs/v1.api-spec.yaml b/app/src/docs/v1.api-spec.yaml index f32dde7b7..d42b1cb76 100755 --- a/app/src/docs/v1.api-spec.yaml +++ b/app/src/docs/v1.api-spec.yaml @@ -337,6 +337,32 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' + /forms/{formId}/apiKey/filesApiAccess: + put: + summary: Set the API key access to submitted files + description: Enable/disable access to submitted files using the API key. + operationId: filesApiAccess + tags: + - Form API + parameters: + - $ref: '#/components/parameters/formIdParam' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/FormApiKey' + '400': + $ref: '#/components/responses/BadRequest' + '404': + $ref: '#/components/responses/NotFound' + default: + description: Unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' /forms/{formId}/export: get: summary: Export submissions for a form @@ -3198,6 +3224,9 @@ components: secret: type: string example: dd7d1699-61ec-4037-aa33-727f8aa79c0a + filesApiAccess: + type: boolean + example: true - $ref: '#/components/schemas/TimeStampUserData' FormApiDetails: allOf: diff --git a/app/src/forms/auth/middleware/apiAccess.js b/app/src/forms/auth/middleware/apiAccess.js index 8821e544d..b93d7aa24 100644 --- a/app/src/forms/auth/middleware/apiAccess.js +++ b/app/src/forms/auth/middleware/apiAccess.js @@ -7,6 +7,7 @@ const submissionService = require('../../submission/service'); const fileService = require('../../file/service'); const HTTP_401_DETAIL = 'Invalid authorization credentials.'; +const HTTP_403_DETAIL = 'You do not have access to this resource.'; /** * Gets the Form ID from the request parameters. Handles the cases where instead @@ -93,9 +94,9 @@ module.exports = async (req, res, next) => { throw new Problem(401, { detail: HTTP_401_DETAIL }); } - // if (params.id && apiKey.filesApiAccess === false) { - // throw new Problem(401, { detail: HTTP_401_DETAIL }); - // } + if (params.id && apiKey.filesApiAccess === false) { + throw new Problem(403, { detail: HTTP_403_DETAIL }); + } const secret = apiKey.secret; diff --git a/app/src/forms/common/models/tables/formApiKey.js b/app/src/forms/common/models/tables/formApiKey.js index f7a3b72db..87ac808bc 100644 --- a/app/src/forms/common/models/tables/formApiKey.js +++ b/app/src/forms/common/models/tables/formApiKey.js @@ -26,6 +26,7 @@ class FormApiKey extends Timestamps(Model) { id: { type: 'integer' }, formId: { type: 'string', pattern: Regex.UUID }, secret: { type: 'string', pattern: Regex.UUID }, + filesApiAccess: { type: 'boolean', default: false }, ...stamps, }, additionalProperties: false, diff --git a/app/src/forms/form/controller.js b/app/src/forms/form/controller.js index 74712539a..77515823d 100644 --- a/app/src/forms/form/controller.js +++ b/app/src/forms/form/controller.js @@ -250,6 +250,14 @@ module.exports = { next(error); } }, + filesApiKeyAccess: async (req, res, next) => { + try { + const response = await service.filesApiKeyAccess(req.params.formId, req.body.filesApiAccess); + res.status(200).json(response); + } catch (error) { + next(error); + } + }, deleteApiKey: async (req, res, next) => { try { const response = await service.deleteApiKey(req.params.formId); diff --git a/app/src/forms/form/service.js b/app/src/forms/form/service.js index 2507c076d..01ad71249 100644 --- a/app/src/forms/form/service.js +++ b/app/src/forms/form/service.js @@ -729,6 +729,7 @@ const service = { formId: formId, secret: uuidv4(), updatedBy: currentUser.usernameIdp, + filesApiAccess: false, }); } else { // Add new API key for the form @@ -736,6 +737,7 @@ const service = { formId: formId, secret: uuidv4(), createdBy: currentUser.usernameIdp, + filesApiAccess: false, }); } @@ -747,6 +749,33 @@ const service = { } }, + // Set the filesApiAccess boolean for the api key + filesApiKeyAccess: async (formId, filesApiAccess) => { + let trx; + try { + if (typeof filesApiAccess !== 'boolean') { + throw new Problem(400, `filesApiAccess must be a boolean`); + } + const currentKey = await service.readApiKey(formId); + trx = await FormApiKey.startTransaction(); + + if (currentKey) { + await FormApiKey.query(trx).modify('filterFormId', formId).update({ + formId: formId, + filesApiAccess: filesApiAccess, + }); + } else { + throw new Problem(404, `No API key found for form ${formId}`); + } + + await trx.commit(); + return service.readApiKey(formId); + } catch (err) { + if (trx) await trx.rollback(); + throw err; + } + }, + // Hard delete the current key for a form deleteApiKey: async (formId) => { const currentKey = await service.readApiKey(formId); diff --git a/app/tests/unit/forms/auth/middleware/apiAccess.spec.js b/app/tests/unit/forms/auth/middleware/apiAccess.spec.js index abb6e01a1..dd41280e5 100644 --- a/app/tests/unit/forms/auth/middleware/apiAccess.spec.js +++ b/app/tests/unit/forms/auth/middleware/apiAccess.spec.js @@ -417,5 +417,39 @@ describe('apiAccess', () => { expect(next).toHaveBeenCalledTimes(1); expect(next).toHaveBeenCalledWith(); }); + + it('should be forbidden if filesApiAccess is false', async () => { + mockReadApiKey.mockResolvedValue({ secret: secret, filesApiAccess: false }); + fileService.read = jest.fn().mockResolvedValue({ formSubmissionId: formSubmissionId }); + submissionService.read = jest.fn().mockResolvedValue({ form: { id: formId } }); + const req = { + headers: { authorization: authHeader }, + params: { id: fileId }, + }; + const { res, next } = getMockRes(); + + await apiAccess(req, res, next); + + expect(next).toHaveBeenCalledTimes(1); + expect(next).toHaveBeenCalledWith(expect.objectContaining({ status: 403 })); + expect(res.status).not.toHaveBeenCalled(); + expect(req.apiUser).toBeUndefined(); + expect(mockReadApiKey).toHaveBeenCalledTimes(1); + }); + + it('should allow access to files if filesAPIAccess is true', async () => { + mockReadApiKey.mockResolvedValue({ secret: secret, filesAPIAccess: true }); + const req = { + headers: { authorization: authHeader }, + params: { formId: formId }, + }; + const { res, next } = getMockRes(); + + await apiAccess(req, res, next); + + expect(next).toHaveBeenCalledTimes(1); + expect(mockReadApiKey).toHaveBeenCalledTimes(1); + expect(req.apiUser).toBeTruthy(); + }); }); }); diff --git a/vetur.config.js b/vetur.config.js deleted file mode 100644 index 8beed2455..000000000 --- a/vetur.config.js +++ /dev/null @@ -1,10 +0,0 @@ -/** @type {import('vls').VeturConfig} */ -module.exports = { - settings: { - 'vetur.useWorkspaceDependencies': true, - 'vetur.experimental.templateInterpolationService': true - }, - projects: [ - './app/frontend' - ] -};