diff --git a/.github/ISSUE_TEMPLATE/story.md b/.github/ISSUE_TEMPLATE/story.md
new file mode 100644
index 000000000..db91f8c49
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/story.md
@@ -0,0 +1,23 @@
+---
+name: Story
+about: Persona needs for purpose.
+title: "[Story]"
+labels: ''
+assignees: ''
+
+---
+
+### Story
+* **Persona**:
+* **Need**:
+* **Purpose**:
+
+
+### Acceptance Criteria
+- [ ]
+
+### Open Questions
+
+### Context
+
+### Implementation
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 3bb4ed39d..6987b55f1 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -31,6 +31,21 @@ jobs:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
+ # / cipherduck start addition
+ - uses: actions/setup-java@v2
+ with:
+ distribution: 'temurin'
+ java-version: ${{ env.JAVA_VERSION }}
+ cache: 'maven'
+ - name: Generate openapi.json
+ working-directory: backend
+ run: >
+ mvn -B clean compile quarkus:build
+ - name: Check openapi.json
+ working-directory: backend
+ run: >
+ cat ../frontend/src/openapi/openapi.json
+ # \ cipherduck end addition
- name: Install npm dependencies
working-directory: frontend
run: npm install
@@ -40,58 +55,72 @@ jobs:
- name: Deploy frontend
working-directory: frontend
run: npm run dist
- - name: SonarCloud Scan Frontend
- uses: SonarSource/sonarcloud-github-action@master
- with:
- projectBaseDir: frontend
- args: >
- -Dsonar.organization=cryptomator
- -Dsonar.projectKey=cryptomator_hub_frontend
- -Dsonar.typescript.tsconfigPath=tsconfig.json
- -Dsonar.sources=src/
- -Dsonar.tests=test/
- -Dsonar.javascript.lcov.reportPaths=coverage/lcov.info
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
- SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+# / cipherduck start commented out
+# - name: SonarCloud Scan Frontend
+# uses: SonarSource/sonarcloud-github-action@master
+# with:
+# projectBaseDir: frontend
+# args: >
+# -Dsonar.organization=cryptomator
+# -Dsonar.projectKey=cryptomator_hub_frontend
+# -Dsonar.typescript.tsconfigPath=tsconfig.json
+# -Dsonar.sources=src/
+# -Dsonar.tests=test/
+# -Dsonar.javascript.lcov.reportPaths=coverage/lcov.info
+# env:
+# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
+# SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+# \ cipherduck end commented out
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: ${{ env.JAVA_VERSION }}
cache: 'maven'
- - name: Cache SonarCloud packages
- uses: actions/cache@v4
- with:
- path: ~/.sonar/cache
- key: ${{ runner.os }}-sonar
- restore-keys: ${{ runner.os }}-sonar
+# / cipherduck start commented out
+# - name: Cache SonarCloud packages
+# uses: actions/cache@v4
+# with:
+# path: ~/.sonar/cache
+# key: ${{ runner.os }}-sonar
+# restore-keys: ${{ runner.os }}-sonar
+# \ cipherduck end commented out
- name: Build and test backend
working-directory: backend
run: >
mvn -B clean verify
- org.sonarsource.scanner.maven:sonar-maven-plugin:sonar
- -Dsonar.projectKey=cryptomator_hub_backend
- -Dsonar.organization=cryptomator
- -Dsonar.host.url=https://sonarcloud.io
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+# / cipherduck start commented out
+# org.sonarsource.scanner.maven:sonar-maven-plugin:sonar
+# -Dsonar.projectKey=cryptomator_hub_backend
+# -Dsonar.organization=cryptomator
+# -Dsonar.host.url=https://sonarcloud.io
+# env:
+# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+# SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+# \ cipherduck end commented out
- id: get_tag
- if: inputs.tag != '' || github.ref_type == 'tag' || contains(github.event.head_commit.message, '[build image]')
+ # / cipherduck start commented out
+ # if: inputs.tag != '' || github.ref_type == 'tag' || contains(github.event.head_commit.message, '[build image]')
+ # \ cipherduck end commented out
run: |
if [[ ! -z "${{ inputs.tag }}" ]]; then
TAG="${{ inputs.tag }}"
elif [[ ${{ github.ref_type }} == 'tag' || ${{ github.ref_name }} == 'develop' ]]; then
TAG="${{ github.ref_name }}"
else
- TAG="commit-${{ github.sha }}"
+ # / cipherduck start modification
+ #TAG="commit-${{ github.sha }}"
+ # use latest by default as our container registry has limited capacity
+ TAG="latest"
+ # \ cipherduck end modification
fi
echo tag=${TAG} >> "$GITHUB_OUTPUT"
- name: Ensure to use tagged version
if: startsWith(github.ref, 'refs/tags/')
run: mvn versions:set --file ./backend/pom.xml -DnewVersion=${GITHUB_REF##*/}
- name: Build and push container image
- if: github.event.inputs.tag != '' || startsWith(github.ref, 'refs/tags/') || contains(github.event.head_commit.message, '[build image]')
+ # / cipherduck start commented out
+ #if: github.event.inputs.tag != '' || startsWith(github.ref, 'refs/tags/') || contains(github.event.head_commit.message, '[build image]')
+ # \ cipherduck end commented out
working-directory: backend
run: mvn -B clean package -DskipTests
env:
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..62c893550
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+.idea/
\ No newline at end of file
diff --git a/README.md b/README.md
index 58efdc6dd..d0fa138af 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,16 @@
[![CI Build](https://github.com/cryptomator/hub/actions/workflows/build.yml/badge.svg)](https://github.com/cryptomator/hub/actions/workflows/build.yml)
-# Cryptomator Hub
+# Cipherduck Hub: the secure and easy way to work in teams
-Hub consists of these components:
+Cipherduck Hub bring zero-config storage management and zero-knowledge key management for teams and organizations.
+
+It easily integrates into your existing identity management incl. OpenID Connect, SAML, and LDAP.
+As usual, your favorite cloud service remains your free choice [^1].
+
+[^1]: Currently, we support AWS S3 and MinIO S3.
+
+Cipherduck consists of Cipherduck Hub and Cipherduck Client. Cipherduck Client is based on [Mountain Duck](https://mountainduck.io/).
+Cipherduck Hub is based on [Cryptomator Hub](https://github.com/cryptomator/hub/), consisting of these components:
## Web Frontend
@@ -14,4 +22,6 @@ During development, run Quarkus from the `backend` dir as explained in [its READ
## Custom Keycloak Image
-We add a custom theme to the base keycloak image, as explained in [its README file](keycloak/README.md).:
\ No newline at end of file
+We add a custom theme to the base keycloak image, as explained in [its README file](keycloak/README.md).:
+
+
diff --git a/backend/.gitignore b/backend/.gitignore
index 35e9ee6fe..da1e16343 100644
--- a/backend/.gitignore
+++ b/backend/.gitignore
@@ -45,4 +45,4 @@ nb-configuration.xml
*.rej
# Local environment
-.env
+.env
\ No newline at end of file
diff --git a/backend/CIPHERDUCK.md b/backend/CIPHERDUCK.md
new file mode 100644
index 000000000..6cf043161
--- /dev/null
+++ b/backend/CIPHERDUCK.md
@@ -0,0 +1,296 @@
+Cipherduck hub setup admin documentation
+========================================
+
+
+MinIO
+-----
+
+### Setup MinIO
+
+Documentation
+
+* [MinIO OpenID Connect Access Management](https://min.io/docs/minio/linux/administration/identity-access-management/oidc-access-management.html)
+* [MinIO Client Reference `mc idp openid`](https://min.io/docs/minio/linux/reference/minio-mc/mc-idp-openid.html)
+* [MinIO Security Token Service `AssumeRoleWithWebIdentity](https://min.io/docs/minio/linux/developers/security-token-service/AssumeRoleWithWebIdentity.html)
+
+```
+minio server data --console-address :9001
+```
+
+Or containerized:
+
+```
+export MINIO_ROOT_USER=
+export MINIO_ROOT_PASSWORD=
+export MINIO_API_CORS_ALLOW_ORIGIN=testing.hub.cryptomator.org
+docker run -p 9000:9000 -p 9001:9001 -e MINIO_ROOT_USER=$MINIO_ROOT_USER -e MINIO_ROOT_PASSWORD=$MINIO_ROOT_PASSWORD -e MINIO_API_CORS_ALLOW_ORIGIN=$MINIO_API_CORS_ALLOW_ORIGIN quay.io/minio/minio server /data --console-address ":9001"
+```
+
+Side-note: MinIO does not support bucket CORS API,
+see [MinIO - Unsupported S3 Bucket APIs](https://min.io/docs/minio/linux/operations/concepts/thresholds.html#unsupported-s3-bucket-apis)
+
+#### Policy and OIDC provider for MinIO
+
+Add role for creating buckets with prefix `cipherduck` and uploading `vault.cryptomator`, as well as RW to access to
+buckets through `client_id` claim in JWT token. Adapt bucket prefix in
+
+* [setup/minio_sts/createbucketpolicy.json](setup%2Fminio_sts%2Fcreatebucketpolicy.json)
+
+Side-note: MinIO does not allow for multiple OIDC providers with the same client ID:
+
+> mc: Unable to add OpenID IDP config to server. Client ID XYZ is present with multiple OpenID configurations.
+
+This is not a problem as we leave the claim specifying the vault unset or pointing to a non-existing vault.
+
+```shell
+mc alias set myminio http://127.0.0.1:9000 minioadmin minioadmin
+mc admin policy create myminio cipherduckcreatebucket setup/minio_sts/createbucketpolicy.json
+mc admin policy create myminio cipherduckaccessbucket setup/minio_sts/accessbucketpolicy.json
+```
+
+Add a new OIDC provider, vault creation and vault access policy in MinIO:
+
+```shell
+WELL_KNOWN=https://testing.hub.cryptomator.org/kc/realms/cipherduck/.well-known/openid-configuration
+#WELL_KNOWN=http://localhost:8180/realms/cryptomator/.well-known/openid-configuration
+mc idp openid add myminio cryptomator \
+ config_url="$WELL_KNOWN" \
+ client_id="cryptomator" \
+ client_secret="ignore-me" \
+ role_policy="cipherduckcreatebucket"
+mc idp openid add myminio cryptomatorhub \
+ config_url="$WELL_KNOWN" \
+ client_id="cryptomatorhub" \
+ client_secret="ignore-me" \
+ role_policy="cipherduckcreatebucket"
+mc idp openid add myminio cryptomatorvaults \
+ config_url="$WELL_KNOWN" \
+ client_id="cryptomatorvaults" \
+ client_secret="ignore-me" \
+ role_policy="cipherduckaccessbucket"
+mc admin service restart myminio
+```
+
+Extract the policy ARN:
+
+```shell
+mc idp openid ls myminio
+╭──────────────────────────────────────────────────────────────────────────╮
+│ On? Name RoleARN │
+│ 🔴 (default) │
+│ 🟢 cryptomator arn:minio:iam:::role/IqZpDC5ahW_DCAvZPZA4ACjEnDE │
+│ 🟢 cryptomatorhub arn:minio:iam:::role/HGKdlY4eFFsXVvJmwlMYMhmbnDE │
+│ 🟢 cryptomatorvaults arn:minio:iam:::role/Hdms6XDZ6oOpuWYI3gu4gmgHN94 │
+╰──────────────────────────────────────────────────────────────────────────╯
+
+
+ mc idp openid info myminio cryptomator
+╭─────────────────────────────────────────────────────────────────────────────────────────────────────────╮
+│ client_id: cryptomator │
+│client_secret: ignore-me │
+│ config_url: https://testing.hub.cryptomator.org/kc/realms/cipherduck/.well-known/openid-configuration │
+│ enable: on │
+│ roleARN: arn:minio:iam:::role/IqZpDC5ahW_DCAvZPZA4ACjEnDE │
+│ role_policy: cipherduckcreatebucket │
+╰─────────────────────────────────────────────────────────────────────────────────────────────────────────╯
+
+```
+
+### Hub configuration
+
+See [application.properties](config%2Fapplication.properties)
+
+AWS
+---
+
+### Setup AWS
+
+#### Setup AWS: OIDC provider
+
+Documentation: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc_verify-thumbprint.html
+
+```shell
+openssl s_client -servername testing.hub.cryptomator.org -showcerts -connect testing.hub.cryptomator.org:443 > testing.hub.cryptomator.org.crt
+
+vi testing.hub.cryptomator.org.crt ...
+(remove the irrelevant parts from the chain)
+
+cat testing.hub.cryptomator.org.crt
+-----BEGIN CERTIFICATE-----
+MIIGBDCCBOygAwIBAgISA1CGKN3OkGJihg/qGhz2fl3fMA0GCSqGSIb3DQEBCwUA
+MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQD
+EwJSMzAeFw0yMzExMTIxMzAyMTdaFw0yNDAyMTAxMzAyMTZaMCYxJDAiBgNVBAMT
+G3Rlc3RpbmcuaHViLmNyeXB0b21hdG9yLm9yZzCCAiIwDQYJKoZIhvcNAQEBBQAD
+ggIPADCCAgoCggIBALWWmJr7lckOPCysl8p8FywJ2BwfCfdqMqTeb7KdOa3Zd9kb
+rb0dYUAs6cs4XKIxSBzKTDJAZiE5d2/iXUgHIBS8hDjG8U40EFaKDTc/JugOSovs
+HB6FQTi4YCMNfm3oMBiREMXYQTEKErBFfECbtGw8mTua2suT6Uc7lwj91qbPO6BN
+TROk0Az1NcifYOz8lMZhelg0WXEa10YfalaKGtjh4srMBv0rT85PpXaJXaNp58Ls
+4Psf/YlPjGJOhevnyAuqZouUD9sz7gZX8WvQ87y9uTXpDoarySh/0nppYLPZTDty
+sI3LeVwwrf4ir5jObVgjkH1CdS8kj/ueKLLW0BBqSX/9oji9o1zFJlBeRcWbeW08
+SD3+7292cy+zpNo3Y7xEFxGs0SVlJjTRk4cf6edkVq5QzTPqIF9FSn6tgXC6OTJi
+ISHnLGvkuSOzCieADPwjlYJiix3duK+0rpeN3xH3/NnyvPnncbWr/KLwwGE/tsHx
+orv1XLXkV0nmD9MDvE1gqRd7m7n3PwXEojz2Ih37i4bowFx2jYy6acAyY0KJSWwE
+3Rl2BRvOqXY1AOZC2MKOp7mb3hbryr8pzUPb0j4p3iOmOG9MgUQydKLyE97W1Ucd
+PRQMHdoG+EKnDeaauKdZ/3Lj0jMJ1CKlmYOB5qShHv1XCR5uimouioQkoJTFAgMB
+AAGjggIeMIICGjAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEG
+CCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFHkBSFhuApvRJvGqRHZg
+5t183UMCMB8GA1UdIwQYMBaAFBQusxe3WFbLrlAJQOYfr52LFMLGMFUGCCsGAQUF
+BwEBBEkwRzAhBggrBgEFBQcwAYYVaHR0cDovL3IzLm8ubGVuY3Iub3JnMCIGCCsG
+AQUFBzAChhZodHRwOi8vcjMuaS5sZW5jci5vcmcvMCYGA1UdEQQfMB2CG3Rlc3Rp
+bmcuaHViLmNyeXB0b21hdG9yLm9yZzATBgNVHSAEDDAKMAgGBmeBDAECATCCAQUG
+CisGAQQB1nkCBAIEgfYEgfMA8QB2ADtTd3U+LbmAToswWwb+QDtn2E/D9Me9AA0t
+cm/h+tQXAAABi8PXIB0AAAQDAEcwRQIhAPOlsQr63JOSMbTFWOM746oA7i4HQ+hl
+p7M3pRpG4HYQAiBKqLSDsx1FdI18Fax3k7zkCgsY8x96ZAQvVUfdch0xoAB3AO7N
+0GTV2xrOxVy3nbTNE6Iyh0Z8vOzew1FIWUZxH7WbAAABi8PXIBwAAAQDAEgwRgIh
+AOZskIE18A5sTthKz6w3wMvIocbaoj3UCTCIAXWVJJNzAiEAmMWS709vLq/WOPG0
+5hb6lBPn6NRnjizJaNEnj/ts71EwDQYJKoZIhvcNAQELBQADggEBADiSgsGpOKqZ
+0kzeIS9x7vJlc3I0lnScB9JjxJyLoZFs//T4SNWE18zFxnzVspWRnwu4NTmuGURv
+6RWJ8RAznYwjZCnVDdQREUSX7wahzGdz+3GalRaIYngkvwHOhT+aGLbrKRjz+Pfh
+13qMStwjlfA6iSofHqVeQFCf48itgeVjNbpdZKEOLwdiV+JMwpT4n/i0nfVwWkaG
+RcEWn8S4gfSq1iZ/LAhWdyB0QJ4EcCO6mx02wABxbQibPc5FM8Q64j37TizHniVu
+hs+X7qFNDF/jvbob3sL09e0BLjiZWxVasAHiAAaZONTRV0N5YYV56F5br/vnegic
+u3AvSS5HW70=
+-----END CERTIFICATE-----
+
+
+openssl x509 -in testing.hub.cryptomator.org.crt -fingerprint -sha1 -noout | sed -e 's/://g' | sed -e 's/[Ss][Hh][Aa]1 [Ff]ingerprint=//'
+BE21B29075BF9F3265353F8B85208A8981DAEC2A
+
+aws iam create-open-id-connect-provider --url https://testing.hub.cryptomator.org/kc/realms/cipherduck --client-id-list cryptomator cryptomatorhub --thumbprint-list BE21B29075BF9F3265353F8B85208A8981DAEC2A
+{
+ "OpenIDConnectProviderArn": "arn:aws:iam::930717317329:oidc-provider/testing.hub.cryptomator.org/kc/realms/cipherduck1"
+}
+
+aws iam list-open-id-connect-providers
+
+aws iam get-open-id-connect-provider --open-id-connect-provider-arn arn:aws:iam::930717317329:oidc-provider/testing.hub.cryptomator.org/kc/realms/cipherduck
+{
+ "Url": "testing.hub.cryptomator.org/kc/realms/cipherduck",
+ "ClientIDList": [
+ "cryptomatorhub",
+ "cryptomator"
+ ],
+ "ThumbprintList": [
+ "a053375bfe84e8b748782c7cee15827a6af5a405"
+ ],
+ "CreateDate": "2023-11-13T13:51:32.729000+00:00",
+ "Tags": []
+}
+```
+
+#### Setup AWS: roles
+
+Add role for creating buckets with prefix `cipherduck` and uploading `vault.cryptomator`, adapt OIDC provider in trust
+policy and bucket prefix in permission policy:
+
+* [aws/createbuckettrustpolicy.json](./setup%2Faws%2Fcreatebuckettrustpolicy.json)
+* [aws/createbucketpermissionpolicy.json](setup%2Faws%2Fcreatebucketpermissionpolicy.json)
+
+Add roles for role chaining, adapt OIDC provider in trust policy and bucket prefix in permission policy:
+
+* [aws/cipherduck_chain_01_trustpolicy.json](setup%2Faws%2Fcipherduck_chain_01_trustpolicy.json)
+* [aws/cipherduck_chain_01_permissionpolicy.json](setup%2Faws%2Fcipherduck_chain_01_permissionpolicy.json)
+* [aws/cipherduck_chain_02_trustpolicy.json](setup%2Faws%2Fcipherduck_chain_02_trustpolicy.json)
+* [aws/cipherduck_chain_02_permissionpolicy.json](setup%2Faws%2Fcipherduck_chain_02_permissionpolicy.json)
+
+```shell
+aws iam create-role --role-name cipherduck-createbucket --assume-role-policy-document file://src/main/resources/cipherduck/setup/aws_stscreatebuckettrustpolicy.json
+aws iam put-role-policy --role-name cipherduck-createbucket --policy-name cipherduck-createbucket --policy-document file://src/main/resources/cipherduck/setup/aws_stscreatebucketpermissionpolicy.json
+
+
+aws iam create-role --role-name cipherduck_chain_01 --assume-role-policy-document file://src/main/resources/cipherduck/setup/aws_stscipherduck_chain_01_trustpolicy.json
+aws iam put-role-policy --role-name cipherduck_chain_01 --policy-name cipherduck_chain_01 --policy-document file://src/main/resources/cipherduck/setup/aws_stscipherduck_chain_01_permissionpolicy.json
+
+sleep 10;
+
+aws iam create-role --role-name cipherduck_chain_02 --assume-role-policy-document file://src/main/resources/cipherduck/setup/aws_stscipherduck_chain_02_trustpolicy.json
+aws iam put-role-policy --role-name cipherduck_chain_02 --policy-name cipherduck_chain_02 --policy-document file://src/main/resources/cipherduck/setup/aws_stscipherduck_chain_02_permissionpolicy.json
+```
+
+Checking roles:
+
+```shell
+aws iam get-role --role-name cipherduck-createbucket
+aws iam get-role-policy --role-name cipherduck-createbucket --policy-name cipherduck-createbucket
+```
+
+```shell
+TOKEN=`curl -v -X POST https://testing.hub.cryptomator.org/kc/realms/cipherduck/protocol/openid-connect/token \
+ -H "Content-Type: application/x-www-form-urlencoded" \
+ -d "client_id=cryptomator" \
+ -d "scope=openid" \
+ -d "grant_type=password" \
+ -d "username=admin" \
+ -d "password=$PASSWORD" | jq ".id_token" | tr -d '"'`
+
+jwtd $TOKEN
+aws sts assume-role-with-web-identity --role-arn "arn:aws:iam::930717317329:role/cipherduck-createbucket" --role-session-name="blabla" --web-identity-token $TOKEN
+```
+
+### Hub configuration
+
+See [application.properties](config%2Fapplication.properties). The configured prefix must match the ones configured in
+the AWS/MinIO setup. Take the role arns from the AWS/MinIO setup.
+
+### AWS cleanup
+
+```shell
+aws iam delete-role-policy --role-name cipherduck-createbucket --policy-name cipherduck-createbucket
+aws iam delete-role --role-name cipherduck-createbucket
+aws iam delete-role-policy --role-name cipherduck_chain_01 --policy-name cipherduck_chain_01
+aws iam delete-role --role-name cipherduck_chain_01
+aws iam delete-role-policy --role-name cipherduck_chain_02 --policy-name cipherduck_chain_02
+aws iam delete-role --role-name cipherduck_chain_02
+```
+
+Storage Profiles
+----------------------------------------------------------
+
+### Introduction
+
+| Term | Description | Usage in Cipherdu^ck |
+|---------------------------------------------------------------------|----------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------|
+| [Bookmark](https://docs.cyberduck.io/cyberduck/bookmarks/) | Refers to connection profile/profile and adds properties like. | For hubs and vaults. |
+| [Protocol](https://docs.cyberduck.io/protocols/) | | `hub` and `s3-sts` |
+| [Connection Profile](https://docs.cyberduck.io/protocols/profiles/) | Refers to protocol and overrides properties | Used internally. |
+| Vault [JWE](https://datatracker.ietf.org/doc/html/rfc7516) | JSON Web Encryption for encrypted JSON-based data structures | Contains the vault masterkey for decrypting data plus all information required to create vault bookmarks. |
+
+Note that properties in `application.properties` use dashed notation instead of Camel Case in JWE and Java Dtos,
+see [Quarkus Config Reference Guide](https://quarkus.io/guides/config-reference) for details.
+
+### API documentation
+
+See http://localhost:8080/q/openapi?format=json or http://localhost:8080/q/swagger-ui/
+
+### Examples
+
+* [aws_sts_profile.json](setup%2Faws_sts%2Faws_sts_profile.json)
+* [minio_sts_profile.json](setup%2Fminio_sts%2Fminio_sts_profile.json)
+* [aws_static_profile.json](setup%2Faws_static%2Faws_static_profile.json)
+* [minio_static_profile.json](setup%2Fminio_static%2Fminio_static_profile.json)
+
+### Upload storage profiles
+
+You need to be a hub admin user. If direct access grant is enabled:
+
+```
+export HUB_API_BASE=http://localhost:8080/api
+export ACCESS_TOKEN=`curl -v -X POST http://localhost:8180/realms/cryptomator/protocol/openid-connect/token \
+ -H "Content-Type: application/x-www-form-urlencoded" \
+ -d "client_id=cryptomator" \
+ -d "grant_type=password" \
+ -d "username=admin" \
+ -d "password=admin" | jq ".access_token" | tr -d '"'`
+curl -X PUT $HUB_API_BASE/storageprofile/s3sts -d @setup/minio_sts/minio_sts_profile.json -v -H "Content-Type: application/json" -H "Authorization: Bearer $ACCESS_TOKEN"
+curl -X PUT $HUB_API_BASE/storageprofile/s3 -d @setup/minio_static/minio_static_profile.json -v -H "Content-Type: application/json" -H "Authorization: Bearer $ACCESS_TOKEN"
+curl -X PUT $HUB_API_BASE/storageprofile/s3sts -d @setup/aws_sts/aws_sts_profile.json -v -H "Content-Type: application/json" -H "Authorization: Bearer $ACCESS_TOKEN"
+curl -X PUT $HUB_API_BASE/storageprofile/s3 -d @setup/aws_static/aws_static_profile.json -v -H "Content-Type: application/json" -H "Authorization: Bearer $ACCESS_TOKEN"
+curl $HUB_API_BASE/storageprofile/ -H "Authorization: Bearer $ACCESS_TOKEN"
+```
+
+Else, use [hub-cli](https://github.com/cryptomator/hub-cli) to get the access token with Authorization Code flow:
+
+```
+hub login --client-id=cryptomator authorization-code --api-base $HUB_API_BASE | tee ACCESS_TOKEN.txt; export ACCESS_TOKEN=$(cat ACCESS_TOKEN.txt| tail -1)
+```
+
diff --git a/backend/pom.xml b/backend/pom.xml
index 9a5848800..9b0f24b3d 100644
--- a/backend/pom.xml
+++ b/backend/pom.xml
@@ -9,9 +9,13 @@
UTF-8
21
- UTF-8
- cryptomator
- hub
+
+
+ shift7-ch
+ cipherduck
+ ../frontend/src/openapi
+
+
3.8.2
eclipse-temurin:21-jre
4.4.0
@@ -31,6 +35,23 @@
pom
import
+
+
+
+ io.quarkus.platform
+ quarkus-bom
+ ${quarkus.platform.version}
+ pom
+ import
+
+
+ io.quarkus.platform
+ quarkus-amazon-services-bom
+ ${quarkus.platform.version}
+ pom
+ import
+
+
@@ -129,6 +150,25 @@
io.quarkus
quarkus-undertow
+
+
+
+ io.quarkiverse.amazonservices
+ quarkus-amazon-s3
+
+
+ io.quarkiverse.amazonservices
+ quarkus-amazon-iam
+
+
+ software.amazon.awssdk
+ url-connection-client
+
+
+ software.amazon.awssdk
+ aws-crt-client
+
+
diff --git a/backend/setup/aws_static/aws_static_profile.json b/backend/setup/aws_static/aws_static_profile.json
new file mode 100644
index 000000000..412a71261
--- /dev/null
+++ b/backend/setup/aws_static/aws_static_profile.json
@@ -0,0 +1,6 @@
+{
+ "id": "72736C19-283C-49D3-80A5-AB74B5202543",
+ "name": "AWS S3 static",
+ "region": "eu-central-1",
+ "protocol": "S3"
+}
\ No newline at end of file
diff --git a/backend/setup/aws_sts/aws_sts_profile.json b/backend/setup/aws_sts/aws_sts_profile.json
new file mode 100644
index 000000000..f7b22da2f
--- /dev/null
+++ b/backend/setup/aws_sts/aws_sts_profile.json
@@ -0,0 +1,16 @@
+{
+ "id": "844BD517-96D4-4787-BCFA-238E103149F6",
+ "name": "AWS S3 STS",
+ "bucketPrefix": "cipherduck",
+ "stsRoleArnHub": "arn:aws:iam::930717317329:role/cipherduck-createbucket",
+ "stsRoleArnClient": "arn:aws:iam::930717317329:role/cipherduck-createbucket",
+ "region": "eu-west-1",
+ "regions": [
+ "eu-west-1",
+ "eu-west-2",
+ "eu-west-3"
+ ],
+ "protocol": "S3STS",
+ "stsRoleArn": "arn:aws:iam::930717317329:role/cipherduck_chain_01",
+ "stsRoleArn2": "arn:aws:iam::930717317329:role/cipherduck_chain_02"
+}
\ No newline at end of file
diff --git a/backend/setup/aws_sts/cipherduck_chain_01_permissionpolicy.json b/backend/setup/aws_sts/cipherduck_chain_01_permissionpolicy.json
new file mode 100644
index 000000000..e0777a16f
--- /dev/null
+++ b/backend/setup/aws_sts/cipherduck_chain_01_permissionpolicy.json
@@ -0,0 +1,13 @@
+{
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Effect": "Allow",
+ "Action": [
+ "sts:AssumeRole",
+ "sts:TagSession"
+ ],
+ "Resource": "arn:aws:iam::930717317329:role/cipherduck_chain_02"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/backend/setup/aws_sts/cipherduck_chain_01_trustpolicy.json b/backend/setup/aws_sts/cipherduck_chain_01_trustpolicy.json
new file mode 100644
index 000000000..80b5d06de
--- /dev/null
+++ b/backend/setup/aws_sts/cipherduck_chain_01_trustpolicy.json
@@ -0,0 +1,19 @@
+{
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Effect": "Allow",
+ "Principal": {
+ "Federated": [
+ "arn:aws:iam::930717317329:oidc-provider/login1.staging.cryptomator.cloud/realms/cipherduck",
+ "arn:aws:iam::930717317329:oidc-provider/testing.hub.cryptomator.org/kc/realms/cipherduck"
+ ]
+ },
+ "Action": [
+ "sts:AssumeRoleWithWebIdentity",
+ "sts:TagSession"
+ ],
+ "Condition": {}
+ }
+ ]
+}
\ No newline at end of file
diff --git a/backend/setup/aws_sts/cipherduck_chain_02_permissionpolicy.json b/backend/setup/aws_sts/cipherduck_chain_02_permissionpolicy.json
new file mode 100644
index 000000000..0b3c74709
--- /dev/null
+++ b/backend/setup/aws_sts/cipherduck_chain_02_permissionpolicy.json
@@ -0,0 +1,26 @@
+{
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Effect": "Allow",
+ "Action": [
+ "s3:GetBucketLocation",
+ "s3:ListBucket",
+ "s3:ListBucketMultipartUploads",
+ "s3:GetBucketVersioning"
+ ],
+ "Resource": "arn:aws:s3:::cipherduck${aws:PrincipalTag/VaultRequested}"
+ },
+ {
+ "Effect": "Allow",
+ "Action": [
+ "s3:GetObject",
+ "s3:PutObject",
+ "s3:DeleteObject",
+ "s3:ListMultipartUploadParts",
+ "s3:AbortMultipartUpload"
+ ],
+ "Resource": "arn:aws:s3:::cipherduck${aws:PrincipalTag/VaultRequested}/*"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/backend/setup/aws_sts/cipherduck_chain_02_trustpolicy.json b/backend/setup/aws_sts/cipherduck_chain_02_trustpolicy.json
new file mode 100644
index 000000000..3b2292038
--- /dev/null
+++ b/backend/setup/aws_sts/cipherduck_chain_02_trustpolicy.json
@@ -0,0 +1,20 @@
+{
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Effect": "Allow",
+ "Principal": {
+ "AWS": "arn:aws:iam::930717317329:role/cipherduck_chain_01"
+ },
+ "Action": [
+ "sts:AssumeRole",
+ "sts:TagSession"
+ ],
+ "Condition": {
+ "ForAnyValue:StringEquals": {
+ "sts:TransitiveTagKeys": "${aws:RequestTag/VaultRequested}"
+ }
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/backend/setup/aws_sts/createbucketpermissionpolicy.json b/backend/setup/aws_sts/createbucketpermissionpolicy.json
new file mode 100644
index 000000000..801f70985
--- /dev/null
+++ b/backend/setup/aws_sts/createbucketpermissionpolicy.json
@@ -0,0 +1,29 @@
+{
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Effect": "Allow",
+ "Action": [
+ "s3:CreateBucket",
+ "s3:GetBucketPolicy",
+ "s3:PutBucketVersioning",
+ "s3:GetBucketVersioning",
+ "s3:GetAccelerateConfiguration",
+ "s3:PutAccelerateConfiguration",
+ "s3:GetEncryptionConfiguration",
+ "s3:PutEncryptionConfiguration"
+ ],
+ "Resource": "arn:aws:s3:::cipherduck*"
+ },
+ {
+ "Effect": "Allow",
+ "Action": [
+ "s3:PutObject"
+ ],
+ "Resource": [
+ "arn:aws:s3:::cipherduck*/vault.cryptomator",
+ "arn:aws:s3:::cipherduck*/*/"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/backend/setup/aws_sts/createbuckettrustpolicy.json b/backend/setup/aws_sts/createbuckettrustpolicy.json
new file mode 100644
index 000000000..6f31cb188
--- /dev/null
+++ b/backend/setup/aws_sts/createbuckettrustpolicy.json
@@ -0,0 +1,16 @@
+{
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Effect": "Allow",
+ "Principal": {
+ "Federated": [
+ "arn:aws:iam::930717317329:oidc-provider/testing.hub.cryptomator.org/kc/realms/cipherduck",
+ "arn:aws:iam::930717317329:oidc-provider/login1.staging.cryptomator.cloud/realms/cipherduck"
+ ]
+ },
+ "Action": "sts:AssumeRoleWithWebIdentity",
+ "Condition": {}
+ }
+ ]
+}
\ No newline at end of file
diff --git a/backend/setup/minio_static/minio_static_profile.json b/backend/setup/minio_static/minio_static_profile.json
new file mode 100644
index 000000000..5be63cd2a
--- /dev/null
+++ b/backend/setup/minio_static/minio_static_profile.json
@@ -0,0 +1,9 @@
+{
+ "id": "71B910E0-2ECC-46DE-A871-8DB28549677E",
+ "name": "MinIO S3 static",
+ "protocol": "S3",
+ "withPathStyleAccessEnabled": "true",
+ "hostname": "minio",
+ "port": "9000",
+ "scheme": "http"
+}
\ No newline at end of file
diff --git a/backend/setup/minio_sts/accessbucketpolicy.json b/backend/setup/minio_sts/accessbucketpolicy.json
new file mode 100644
index 000000000..09b5127b3
--- /dev/null
+++ b/backend/setup/minio_sts/accessbucketpolicy.json
@@ -0,0 +1,30 @@
+{
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Effect": "Allow",
+ "Action": [
+ "s3:GetBucketLocation",
+ "s3:GetBucketVersioning",
+ "s3:ListBucket",
+ "s3:ListBucketMultipartUploads"
+ ],
+ "Resource": [
+ "arn:aws:s3:::cipherduck${jwt:client_id}"
+ ]
+ },
+ {
+ "Effect": "Allow",
+ "Action": [
+ "s3:AbortMultipartUpload",
+ "s3:DeleteObject",
+ "s3:GetObject",
+ "s3:ListMultipartUploadParts",
+ "s3:PutObject"
+ ],
+ "Resource": [
+ "arn:aws:s3:::cipherduck${jwt:client_id}/*"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/backend/setup/minio_sts/createbucketpolicy.json b/backend/setup/minio_sts/createbucketpolicy.json
new file mode 100644
index 000000000..7a205f25d
--- /dev/null
+++ b/backend/setup/minio_sts/createbucketpolicy.json
@@ -0,0 +1,27 @@
+{
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Effect": "Allow",
+ "Action": [
+ "s3:CreateBucket",
+ "s3:GetBucketPolicy",
+ "s3:PutBucketVersioning",
+ "s3:GetBucketVersioning"
+ ],
+ "Resource": [
+ "arn:aws:s3:::cipherduck*/"
+ ]
+ },
+ {
+ "Effect": "Allow",
+ "Action": [
+ "s3:PutObject"
+ ],
+ "Resource": [
+ "arn:aws:s3:::cipherduck*/*/",
+ "arn:aws:s3:::cipherduck*/vault.cryptomator"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/backend/setup/minio_sts/minio_sts_profile.json b/backend/setup/minio_sts/minio_sts_profile.json
new file mode 100644
index 000000000..1d6b704e7
--- /dev/null
+++ b/backend/setup/minio_sts/minio_sts_profile.json
@@ -0,0 +1,26 @@
+{
+ "id": "732D43FA-3716-46C4-B931-66EA5405EF1C",
+ "name": "MinIO S3 STS",
+ "bucketPrefix": "cipherduck",
+ "region": "eu-central-1",
+ "regions": [
+ "eu-west-1",
+ "eu-west-2",
+ "eu-west-3",
+ "eu-north-1",
+ "eu-south-1",
+ "eu-south-2",
+ "eu-central-1",
+ "eu-central-2"
+ ],
+ "withPathStyleAccessEnabled": "true",
+ "stsRoleArnHub": "arn:minio:iam:::role/HGKdlY4eFFsXVvJmwlMYMhmbnDE",
+ "stsRoleArnClient": "arn:minio:iam:::role/IqZpDC5ahW_DCAvZPZA4ACjEnDE",
+ "stsEndpoint": "http://minio:9000",
+ "protocol": "S3STS",
+ "scheme": "http",
+ "hostname": "minio",
+ "port": "9000",
+ "stsRoleArn": "arn:minio:iam:::role/Hdms6XDZ6oOpuWYI3gu4gmgHN94",
+ "bucketAcceleration": null
+}
diff --git a/backend/src/main/java/org/cryptomator/hub/api/AuthorityDto.java b/backend/src/main/java/org/cryptomator/hub/api/AuthorityDto.java
index cfe468b25..b00a8046a 100644
--- a/backend/src/main/java/org/cryptomator/hub/api/AuthorityDto.java
+++ b/backend/src/main/java/org/cryptomator/hub/api/AuthorityDto.java
@@ -4,7 +4,22 @@
import org.cryptomator.hub.entities.Authority;
import org.cryptomator.hub.entities.Group;
import org.cryptomator.hub.entities.User;
-
+import org.eclipse.microprofile.openapi.annotations.media.DiscriminatorMapping;
+import org.eclipse.microprofile.openapi.annotations.media.Schema;
+
+// / start cipherduck extension
+// TODO review: backport @Schema upstream?
+@Schema(
+ title = "Authority",
+ oneOf = { UserDto.class, GroupDto.class, MemberDto.class },
+ discriminatorMapping = {
+ @DiscriminatorMapping( value = "USER", schema = UserDto.class ),
+ @DiscriminatorMapping( value = "GROUP", schema = GroupDto.class ),
+ @DiscriminatorMapping( value = "MEMBER", schema = MemberDto.class )
+ },
+ discriminatorProperty = "type"
+)
+// \ end cipherduck extension
abstract sealed class AuthorityDto permits UserDto, GroupDto, MemberDto {
public enum Type {
diff --git a/backend/src/main/java/org/cryptomator/hub/api/ConfigResource.java b/backend/src/main/java/org/cryptomator/hub/api/ConfigResource.java
index 5081b04cf..ea3b464e8 100644
--- a/backend/src/main/java/org/cryptomator/hub/api/ConfigResource.java
+++ b/backend/src/main/java/org/cryptomator/hub/api/ConfigResource.java
@@ -8,6 +8,7 @@
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
+import org.cryptomator.hub.entities.Settings;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import java.time.Instant;
@@ -32,6 +33,15 @@ public class ConfigResource {
@ConfigProperty(name = "hub.keycloak.oidc.cryptomator-client-id", defaultValue = "")
String keycloakClientIdCryptomator;
+ // / start cipherduck extension
+ @Inject
+ @ConfigProperty(name = "hub.keycloak.oidc.cryptomator-vaults-client-id", defaultValue = "")
+ String keycloakClientIdCryptomatorVaults;
+
+ @Inject
+ Settings.Repository settingsRepo;
+ // \ end cipherduck extension
+
@Inject
@ConfigProperty(name = "quarkus.oidc.auth-server-url")
String internalRealmUrl;
@@ -49,9 +59,15 @@ public ConfigDto getConfig() {
var authUri = replacePrefix(oidcConfData.getAuthorizationUri(), trimTrailingSlash(internalRealmUrl), publicRealmUri);
var tokenUri = replacePrefix(oidcConfData.getTokenUri(), trimTrailingSlash(internalRealmUrl), publicRealmUri);
- return new ConfigDto(keycloakPublicUrl, keycloakRealm, keycloakClientIdHub, keycloakClientIdCryptomator, authUri, tokenUri, Instant.now().truncatedTo(ChronoUnit.MILLIS), 3);
+ return new ConfigDto(keycloakPublicUrl, keycloakRealm, keycloakClientIdHub, keycloakClientIdCryptomator, authUri, tokenUri, Instant.now().truncatedTo(ChronoUnit.MILLIS), 3
+ // / start cipherduck extension
+ , keycloakClientIdCryptomatorVaults
+ , settingsRepo.get().getHubId()
+ // \ end cipherduck extension
+ );
}
+
//visible for testing
String replacePrefix(String str, String prefix, String replacement) {
int index = str.indexOf(prefix);
@@ -75,7 +91,12 @@ String trimTrailingSlash(String str) {
public record ConfigDto(@JsonProperty("keycloakUrl") String keycloakUrl, @JsonProperty("keycloakRealm") String keycloakRealm,
@JsonProperty("keycloakClientIdHub") String keycloakClientIdHub, @JsonProperty("keycloakClientIdCryptomator") String keycloakClientIdCryptomator,
@JsonProperty("keycloakAuthEndpoint") String authEndpoint, @JsonProperty("keycloakTokenEndpoint") String tokenEndpoint,
- @JsonProperty("serverTime") Instant serverTime, @JsonProperty("apiLevel") Integer apiLevel) {
+ @JsonProperty("serverTime") Instant serverTime, @JsonProperty("apiLevel") Integer apiLevel
+ // / start cipherduck extension
+ , @JsonProperty("keycloakClientIdCryptomatorVaults") String keycloakClientIdCryptomatorVaults
+ , @JsonProperty("uuid") String uuid
+ // \ end cipherduck extension
+ ) {
}
}
diff --git a/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java b/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java
index 30bd49270..a491ac8b5 100644
--- a/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java
+++ b/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java
@@ -31,6 +31,8 @@
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
+import org.cryptomator.hub.SyncerConfig;
+import org.cryptomator.hub.api.cipherduck.CipherduckConfig;
import org.cryptomator.hub.entities.AccessToken;
import org.cryptomator.hub.entities.Authority;
import org.cryptomator.hub.entities.EffectiveVaultAccess;
@@ -63,6 +65,9 @@
import java.util.UUID;
import java.util.stream.Stream;
+import static org.cryptomator.hub.cipherduck.KeycloakCryptomatorVaultsHelper.keycloakGrantAccessToVault;
+import static org.cryptomator.hub.cipherduck.KeycloakCryptomatorVaultsHelper.keycloakRemoveAccessToVault;
+
@Path("/vaults")
public class VaultResource {
@@ -94,6 +99,14 @@ public class VaultResource {
@Inject
LicenseHolder license;
+ // / start cipherduck extension
+ @Inject
+ CipherduckConfig cipherduckConfig;
+
+ @Inject
+ SyncerConfig syncerConfig;
+ // \ end cipherduck extension
+
@GET
@Path("/accessible")
@RolesAllowed("user")
@@ -169,6 +182,11 @@ public Response addUser(@PathParam("vaultId") UUID vaultId, @PathParam("userId")
var usedSeats = effectiveVaultAccessRepo.countSeatOccupyingUsers();
if (usedSeats < license.getSeats() // free seats available
|| effectiveVaultAccessRepo.isUserOccupyingSeat(userId)) { // or user already sitting
+
+ // / start cipherduck extension
+ keycloakGrantAccessToVault(syncerConfig, vaultId.toString(), userId, cipherduckConfig.keycloakClientIdCryptomatorVaults(), groupRepo);
+ // \ end cipherduck extension
+
return addAuthority(vault, user, role);
} else {
throw new PaymentRequiredException("License seats exceeded. Cannot add more users.");
@@ -198,6 +216,10 @@ public Response addGroup(@PathParam("vaultId") UUID vaultId, @PathParam("groupId
throw new PaymentRequiredException("Adding this group would exceed available license seats.");
}
+ // / start cipherduck extension
+ keycloakGrantAccessToVault(syncerConfig, vaultId.toString(), groupId, cipherduckConfig.keycloakClientIdCryptomatorVaults(), groupRepo);
+ // \ end cipherduck extension
+
return addAuthority(vault, group, role);
}
@@ -233,6 +255,16 @@ private Response addAuthority(Vault vault, Authority authority, VaultAccess.Role
public Response removeAuthority(@PathParam("vaultId") UUID vaultId, @PathParam("authorityId") @ValidId String authorityId) {
if (vaultAccessRepo.deleteById(new VaultAccess.Id(vaultId, authorityId))) {
eventLogger.logVaultMemberRemoved(jwt.getSubject(), vaultId, authorityId);
+
+ // / start cipherduck extension
+ // Decision: when resetting an account or archiving a vault, access to the bucket doesn't need to be revoked.
+ // - Account reset: same situation as for addUser() and addGroup() before being granted access (masterkey): in the STS case, users can technically already gain access to the data at the storage level if they know/guess the STS endpoint etc, however they cannot decrypt yet.
+ // - Archiving: removeAuthority is not called in this case, so users still can renew access (get new temporary S3 credentials) at the storage level in the STS case.
+ // However, they cannot get the masterkey any more (in all cases) nor the permanent storage credentials (in the non-STS case).
+ keycloakRemoveAccessToVault(syncerConfig, vaultId.toString(), authorityId, "cryptomatorvaults", groupRepo);
+ // \ end cipherduck extension
+
+
return Response.status(Response.Status.NO_CONTENT).build();
} else {
throw new NotFoundException();
diff --git a/backend/src/main/java/org/cryptomator/hub/api/cipherduck/AutomaticAccessGrant.java b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/AutomaticAccessGrant.java
new file mode 100644
index 000000000..33c64d0a6
--- /dev/null
+++ b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/AutomaticAccessGrant.java
@@ -0,0 +1,13 @@
+package org.cryptomator.hub.api.cipherduck;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public record AutomaticAccessGrant(
+ @JsonProperty(value = "enabled", defaultValue = "true")
+ boolean enabled,
+
+ // where -1 means "grant to anyone", where 0, 1, 2 would be the number of edges between any vault owner and the grantee. Exact algorithm tbd
+ @JsonProperty(value = "maxWotDepth", defaultValue = "-1")
+ int maxWotDepth) {
+
+}
diff --git a/backend/src/main/java/org/cryptomator/hub/api/cipherduck/CipherduckConfig.java b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/CipherduckConfig.java
new file mode 100644
index 000000000..5175e3497
--- /dev/null
+++ b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/CipherduckConfig.java
@@ -0,0 +1,86 @@
+package org.cryptomator.hub.api.cipherduck;
+
+import io.quarkus.oidc.OidcConfigurationMetadata;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+
+import java.util.ArrayList;
+import java.util.List;
+
+// TODO review: backport to ConfigResource.ConfigDto upstream?
+@ApplicationScoped
+public class CipherduckConfig {
+ @Inject
+ @ConfigProperty(name = "hub.keycloak.public-url", defaultValue = "")
+ String keycloakPublicUrl;
+
+ @Inject
+ @ConfigProperty(name = "hub.keycloak.realm", defaultValue = "")
+ String keycloakRealm;
+
+
+
+ @Inject
+ @ConfigProperty(name = "quarkus.oidc.client-id", defaultValue = "")
+ String keycloakClientIdHub;
+
+ @Inject
+ @ConfigProperty(name = "hub.keycloak.oidc.cryptomator-client-id", defaultValue = "")
+ String keycloakClientIdCryptomator;
+
+ @Inject
+ @ConfigProperty(name = "hub.keycloak.oidc.cryptomator-vaults-client-id", defaultValue = "")
+ String keycloakClientIdCryptomatorVaults;
+
+ @Inject
+ @ConfigProperty(name = "quarkus.oidc.auth-server-url")
+ String internalRealmUrl;
+
+ @Inject
+ OidcConfigurationMetadata oidcConfData;
+
+ String replacePrefix(String str, String prefix, String replacement) {
+ int index = str.indexOf(prefix);
+ if (index == 0) {
+ return replacement + str.substring(prefix.length());
+ } else {
+ return str;
+ }
+ }
+
+ String trimTrailingSlash(String str) {
+ if (str.endsWith("/")) {
+ return str.substring(0, str.length() - 1);
+ } else {
+ return str;
+ }
+
+ }
+
+ public String keycloakClientIdHub() {
+ return keycloakClientIdHub;
+ }
+
+ public String keycloakClientIdCryptomator() {
+ return keycloakClientIdCryptomator;
+ }
+
+ public String keycloakClientIdCryptomatorVaults() {
+ return keycloakClientIdCryptomatorVaults;
+ }
+
+ public String publicRealmUri() {
+ return trimTrailingSlash(keycloakPublicUrl + "/realms/" + keycloakRealm);
+ }
+
+ public String authEndpoint() {
+ return replacePrefix(oidcConfData.getAuthorizationUri(), trimTrailingSlash(internalRealmUrl), publicRealmUri());
+ }
+
+ public String tokenEndpoint() {
+ return replacePrefix(oidcConfData.getTokenUri(), trimTrailingSlash(internalRealmUrl), publicRealmUri());
+ }
+
+
+}
diff --git a/backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageDto.java b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageDto.java
new file mode 100644
index 000000000..513728afa
--- /dev/null
+++ b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageDto.java
@@ -0,0 +1,28 @@
+package org.cryptomator.hub.api.cipherduck;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.UUID;
+
+public record StorageDto(
+ @JsonProperty("vaultId")
+ String vaultId,
+ @JsonProperty("storageConfigId")
+ UUID storageConfigId,
+ @JsonProperty("vaultConfigToken")
+ String vaultConfigToken,
+ @JsonProperty("rootDirHash")
+ String rootDirHash,
+ @JsonProperty("awsAccessKey")
+ String awsAccessKey,
+ @JsonProperty("awsSecretKey")
+ String awsSecretKey,
+ @JsonProperty("sessionToken")
+ String sessionToken,
+ @JsonProperty("region")
+ String region
+
+) {
+
+}
+
diff --git a/backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageProfileDto.java b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageProfileDto.java
new file mode 100644
index 000000000..bbf2d6670
--- /dev/null
+++ b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageProfileDto.java
@@ -0,0 +1,91 @@
+package org.cryptomator.hub.api.cipherduck;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonValue;
+import jakarta.persistence.Id;
+import org.cryptomator.hub.entities.cipherduck.StorageProfile;
+import org.cryptomator.hub.entities.cipherduck.StorageProfileS3;
+import org.cryptomator.hub.entities.cipherduck.StorageProfileS3STS;
+import org.eclipse.microprofile.openapi.annotations.media.DiscriminatorMapping;
+import org.eclipse.microprofile.openapi.annotations.media.Schema;
+
+import java.util.UUID;
+
+@Schema(
+ title = "StorageProfile",
+ oneOf = {StorageProfileS3Dto.class, StorageProfileS3STSDto.class},
+ discriminatorMapping = {
+ @DiscriminatorMapping(value = "S3", schema = StorageProfileS3Dto.class),
+ @DiscriminatorMapping(value = "S3STS", schema = StorageProfileS3STSDto.class),
+ },
+ discriminatorProperty = "protocol"
+)
+// pro-memoria @Schema
+// - "required" is taken from @JSONProperty
+// - "defaultValue" needs to be repeated
+public abstract sealed class StorageProfileDto permits StorageProfileS3Dto {
+ public enum Protocol {
+ s3("S3"),
+ s3sts("S3STS");
+ private final String protocol;
+
+ private Protocol(final String protocol) {
+ this.protocol = protocol;
+ }
+
+ @JsonValue
+ public String getProtocol() {
+ return protocol;
+ }
+ }
+
+ @Id
+ @JsonProperty(value = "id", required = true)
+ @Schema(description = "Technical identifier for a storage profile. Must be unique UUID. Clients will use this as vendor in profile and provider in vault bookmark")
+ UUID id;
+
+ @JsonProperty(value = "name", required = true)
+ @Schema(description = "Displayed when choosing type of a new vault in dropdown.")
+ String name;
+
+ //======================================================================
+ // (3) client profile
+ //======================================================================
+
+ //----------------------------------------------------------------------
+ // (3a) STS and permanent client profile attributes
+ //----------------------------------------------------------------------
+ @JsonProperty(value = "protocol", required = true)
+ @Schema(description = "Storage protocol: S3 (permanent credentials) or S3STS (STS).")
+ Protocol protocol;
+
+ @JsonProperty(value = "archived", required = true, defaultValue = "false")
+ @Schema(description = "For archived storage profiles, no vaults can be created any more.")
+ boolean archived;
+
+ public StorageProfileDto() {
+ // jackson
+ }
+
+ public StorageProfileDto(final UUID id, final String name, final Protocol protocol, final boolean archived) {
+ this.id = id;
+ this.name = name;
+ this.protocol = protocol;
+ this.archived = archived;
+ }
+
+ static StorageProfileDto fromEntity(final StorageProfile storageProfile) {
+ // TODO refactor to JEP 441 in JDK 21
+ if (storageProfile instanceof StorageProfileS3STS storageProfileS3STS) {
+ return StorageProfileS3STSDto.fromEntity(storageProfileS3STS);
+ } else if (storageProfile instanceof StorageProfileS3 storageProfileS3) {
+ return StorageProfileS3Dto.fromEntity(storageProfileS3);
+ } else {
+ throw new IllegalStateException("StorageProfile is not of type StorageProfileS3 or StorageProfileS3STS");
+ }
+ }
+
+ public UUID id() {
+ return id;
+ }
+}
diff --git a/backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageProfileResource.java b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageProfileResource.java
new file mode 100644
index 000000000..5e2338f27
--- /dev/null
+++ b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageProfileResource.java
@@ -0,0 +1,144 @@
+package org.cryptomator.hub.api.cipherduck;
+
+import jakarta.annotation.Nullable;
+import jakarta.annotation.security.RolesAllowed;
+import jakarta.inject.Inject;
+import jakarta.transaction.Transactional;
+import jakarta.ws.rs.ClientErrorException;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.FormParam;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.NotFoundException;
+import jakarta.ws.rs.PUT;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import org.cryptomator.hub.entities.cipherduck.StorageProfile;
+import org.cryptomator.hub.entities.cipherduck.StorageProfileS3;
+import org.cryptomator.hub.entities.cipherduck.StorageProfileS3STS;
+import org.eclipse.microprofile.openapi.annotations.Operation;
+import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
+import org.hibernate.exception.ConstraintViolationException;
+
+import java.net.URI;
+import java.util.List;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+@Path("/storageprofile")
+public class StorageProfileResource {
+
+ @Inject
+ CipherduckConfig cipherduckConfig;
+
+ @PUT
+ @Path("/s3")
+ @RolesAllowed("admin")
+ @Transactional
+ @Consumes(MediaType.APPLICATION_JSON)
+ @APIResponse(responseCode = "200", description = "uploaded storage configuration")
+ @APIResponse(responseCode = "400", description = "Constraint violation")
+ @APIResponse(responseCode = "403", description = "not an admin")
+ @APIResponse(responseCode = "409", description = "Storage profile with ID already exists")
+ public Response uploadStorageProfile(final StorageProfileS3Dto c) {
+ try {
+ final StorageProfileS3 entity = c.toEntity();
+ if(StorageProfile.findByIdOptional(entity.id).isPresent()){
+ throw new ClientErrorException(Response.Status.CONFLICT);
+ }
+ entity.persistAndFlush();
+ return Response.created(URI.create(".")).build();
+ } catch (ConstraintViolationException e) {
+ return Response.status(Response.Status.BAD_REQUEST).entity(e).build();
+ }
+ }
+
+ @PUT
+ @Path("/s3sts")
+ @RolesAllowed("admin")
+ @Transactional
+ @Consumes(MediaType.APPLICATION_JSON)
+ @APIResponse(responseCode = "200", description = "uploaded storage configuration")
+ @APIResponse(responseCode = "400", description = "Constraint violation")
+ @APIResponse(responseCode = "403", description = "not an admin")
+ @APIResponse(responseCode = "409", description = "Storage profile with ID already exists")
+ public Response uploadStorageProfile(final StorageProfileS3STSDto c) {
+ try {
+ final StorageProfileS3STS entity = c.toEntity();
+ if(StorageProfile.findByIdOptional(entity.id).isPresent()){
+ throw new ClientErrorException(Response.Status.CONFLICT);
+ }
+ entity.persistAndFlush();
+ return Response.created(URI.create(".")).build();
+ } catch (ConstraintViolationException e) {
+ return Response.status(Response.Status.BAD_REQUEST).entity(e).build();
+ }
+ }
+
+ @GET
+ @Path("/")
+ @RolesAllowed("user")
+ @Produces(MediaType.APPLICATION_JSON)
+ @Transactional
+ @Operation(summary = "get configs for storage backends", description = "get list of configs for storage backends")
+ @APIResponse(responseCode = "200", description = "uploaded storage configuration")
+ @APIResponse(responseCode = "403", description = "not a user")
+ public List getStorageProfiles(@Nullable @QueryParam("archived") Boolean archived) {
+ return StorageProfile.findAll().stream().map(StorageProfileDto::fromEntity).filter(p -> (null == archived) || (archived.booleanValue() == p.archived)).collect(Collectors.toList());
+ }
+
+ @GET
+ @Path("/s3")
+ @RolesAllowed("user")
+ @Produces(MediaType.APPLICATION_JSON)
+ @Transactional
+ @Operation(summary = "get configs for storage backends", description = "get list of configs for storage backends")
+ @APIResponse(responseCode = "200", description = "uploaded storage configuration")
+ @APIResponse(responseCode = "403", description = "not a user")
+ public List getStorageProfilesS3() {
+ return StorageProfile.findAll().stream().map(StorageProfileDto::fromEntity).filter(StorageProfileS3Dto.class::isInstance).map(StorageProfileS3Dto.class::cast).collect(Collectors.toList());
+ }
+
+ @GET
+ @Path("/{profileId}")
+ @RolesAllowed("user")
+ @Produces(MediaType.APPLICATION_JSON)
+ @Transactional
+ @Operation(summary = "gets a storage profile")
+ @APIResponse(responseCode = "200")
+ @APIResponse(responseCode = "403", description = "not a user")
+ public StorageProfileDto get(@PathParam("profileId") UUID profileId) {
+ return StorageProfileDto.fromEntity(StorageProfile.findByIdOptional(profileId).orElseThrow(NotFoundException::new));
+ }
+
+ @PUT
+ @Path("/{profileId}")
+ @RolesAllowed("admin")
+ @Transactional
+ @Produces(MediaType.APPLICATION_FORM_URLENCODED)
+ @Operation(summary = "archive a storage profile")
+ @APIResponse(responseCode = "204", description = "storage profile archived")
+ @APIResponse(responseCode = "403", description = "not an admin")
+ public Response archive(@PathParam("profileId") UUID profileId, @FormParam("archived") final boolean archived) {
+ final StorageProfile storageProfile = StorageProfile.findByIdOptional(profileId).orElseThrow(NotFoundException::new);
+ storageProfile.setArchived(archived).persistAndFlush();
+ return Response.status(Response.Status.NO_CONTENT).build();
+ }
+
+ // TODO https://github.com/shift7-ch/cipherduck-hub/issues/19 refactor into uvf vault metadata
+ @GET
+ @Path("/meta")
+ @RolesAllowed("admin")
+ @Produces(MediaType.APPLICATION_JSON)
+ @Transactional
+ @Operation(summary = "get configs for storage backends", description = "get list of configs for storage backends")
+ @APIResponse(responseCode = "200", description = "uploaded storage configuration")
+ public VaultJWEPayloadDto getVaultJWEBackendDto(final StorageProfileDto.Protocol protocol) {
+ // N.B. temporary workaround to have VaultJWEBackendDto exposed in openapi.json for now....
+ return null;
+ }
+}
diff --git a/backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageProfileS3Dto.java b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageProfileS3Dto.java
new file mode 100644
index 000000000..ebb25add1
--- /dev/null
+++ b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageProfileS3Dto.java
@@ -0,0 +1,91 @@
+package org.cryptomator.hub.api.cipherduck;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.cryptomator.hub.entities.cipherduck.StorageProfileS3;
+import org.eclipse.microprofile.openapi.annotations.media.Schema;
+
+import java.util.UUID;
+
+public sealed class StorageProfileS3Dto extends StorageProfileDto permits StorageProfileS3STSDto {
+
+ public enum S3_STORAGE_CLASSES {
+ STANDARD, INTELLIGENT_TIERING, STANDARD_IA, ONEZONE_IA, REDUCED_REDUNDANCY, GLACIER, GLACIER_IR, DEEP_ARCHIVE
+ }
+
+ //======================================================================
+ // (1) STS and permanent:
+ // - bucket creation frontend/desktop client (STS)
+ // - template upload (STS and permanent)
+ // - client profile (STS and permanent)
+ //======================================================================
+
+ @JsonProperty(value = "scheme", defaultValue = "https")
+ @Schema(description = "Scheme of S3 endpoint for template upload/bucket creation. Defaults to default for protocol, i.e. https in most cases.", example = "https", nullable = true)
+ String scheme;
+
+ @JsonProperty("hostname")
+ @Schema(description = "Hostname S3 endpoint for template upload/bucket creation. Defaults to AWS SDK default.", example = "s3-us-gov-west-1.amazonaws.com", nullable = true)
+ String hostname;
+
+ @JsonProperty("port")
+ @Schema(description = "Port S3 endpoint for template upload/bucket creation. Defaults to default port for scheme.", example = "443", nullable = true)
+ Integer port;
+
+ @JsonProperty(value = "withPathStyleAccessEnabled")
+ @Schema(description = "Whether to use path style for S3 endpoint for template upload/bucket creation.", example = "false", defaultValue = "false")
+ Boolean withPathStyleAccessEnabled = false;
+
+ @JsonProperty(value = "storageClass")
+ @Schema(description = "Storage class for upload. Defaults to STANDARD", example = "STANDARD", required = true)
+ S3_STORAGE_CLASSES storageClass = S3_STORAGE_CLASSES.STANDARD;
+
+ public StorageProfileS3Dto() {
+ // jackson
+ }
+
+ public StorageProfileS3Dto(final UUID id, final String name, final Protocol protocol, final boolean archived, final String scheme, final String hostname, final Integer port, final boolean withPathStyleAccessEnabled, final S3_STORAGE_CLASSES storageClass) {
+ super(id, name, protocol, archived);
+ this.scheme = scheme;
+ this.hostname = hostname;
+ this.port = port;
+ this.withPathStyleAccessEnabled = withPathStyleAccessEnabled;
+ this.storageClass = storageClass;
+ }
+
+ static StorageProfileS3Dto fromEntity(final StorageProfileS3 storageProfile) {
+ return new StorageProfileS3Dto(storageProfile.id, storageProfile.name, Protocol.s3, storageProfile.archived, storageProfile.scheme, storageProfile.hostname, storageProfile.port, storageProfile.withPathStyleAccessEnabled, S3_STORAGE_CLASSES.valueOf(storageProfile.storageClass));
+ }
+
+ public StorageProfileS3 toEntity() {
+ final StorageProfileS3 storageProfile = new StorageProfileS3();
+ storageProfile.id = this.id;
+ storageProfile.name = this.name;
+ storageProfile.archived = this.archived;
+ storageProfile.scheme = this.scheme;
+ storageProfile.hostname = this.hostname;
+ storageProfile.port = this.port;
+ storageProfile.withPathStyleAccessEnabled = this.withPathStyleAccessEnabled;
+ storageProfile.storageClass = this.storageClass.name();
+ return storageProfile;
+ }
+
+ public String scheme() {
+ return scheme;
+ }
+
+ public String hostname() {
+ return hostname;
+ }
+
+ public Integer port() {
+ return port;
+ }
+
+ public Boolean withPathStyleAccessEnabled() {
+ return withPathStyleAccessEnabled;
+ }
+
+ public S3_STORAGE_CLASSES storageClass() {
+ return storageClass;
+ }
+}
diff --git a/backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageProfileS3STSDto.java b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageProfileS3STSDto.java
new file mode 100644
index 000000000..dd0b88ed1
--- /dev/null
+++ b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageProfileS3STSDto.java
@@ -0,0 +1,190 @@
+package org.cryptomator.hub.api.cipherduck;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.cryptomator.hub.entities.cipherduck.StorageProfileS3STS;
+import org.eclipse.microprofile.openapi.annotations.media.Schema;
+import software.amazon.awssdk.regions.Region;
+
+import java.util.List;
+import java.util.UUID;
+
+public final class StorageProfileS3STSDto extends StorageProfileS3Dto {
+
+ public enum S3_SERVERSIDE_ENCRYPTION {
+ NONE, SSE_AES256, SSE_KMS_DEFAULT
+ }
+
+ //======================================================================
+ // (2) STS only: bucket creation
+ //======================================================================
+ @JsonProperty(value = "region", required = true, defaultValue = "us-east-1")
+ @Schema(description = "Default region selected in the frontend/client to create bucket in.", example = "443", defaultValue = "us-east-1")
+ String region = "us-east-1";
+
+ @JsonProperty(value = "regions", required = true)
+ @Schema(description = "List of selectable regions in the frontend/client to create bucket in. Defaults to full list from AWS SDK.")
+ List regions = Region.regions().stream().map(Region::id).toList();
+
+ @JsonProperty(value = "bucketPrefix", required = true)
+ @Schema(description = "Buckets are create with name .", example = "cipherduck")
+ String bucketPrefix;
+
+ @JsonProperty(value = "stsRoleArnClient", required = true)
+ @Schema(description = "STS role for clients to assume to create buckets. Will be the same as stsRoleArnHub for AWS, different for MinIO.", example = "arn:aws:iam:::role/cipherduck-createbucket")
+ String stsRoleArnClient;
+
+ @JsonProperty(value = "stsRoleArnHub", required = true)
+ @Schema(description = "STS role for frontend to assume to create buckets (used with inline policy and passed to hub backend). Will be the same as stsRoleArnClient for AWS, different for MinIO.", example = "arn:aws:iam:::role/cipherduck-createbucket")
+ String stsRoleArnHub;
+
+ @JsonProperty("stsEndpoint")
+ @Schema(description = "STS endpoint to use for AssumeRoleWithWebIdentity and AssumeRole for getting a temporary access token passed to the backend. Defaults to AWS SDK default.", nullable = true)
+ String stsEndpoint;
+
+ @JsonProperty(value = "bucketVersioning", defaultValue = "true", required = true)
+ @Schema(description = "Enable bucket versioning upon bucket creation", defaultValue = "true", required = true)
+ Boolean bucketVersioning = true;
+
+ @JsonProperty(value = "bucketAcceleration", defaultValue = "true")
+ @Schema(description = "Enable bucket versioning upon bucket creation", defaultValue = "true")
+ Boolean bucketAcceleration = true;
+
+ @JsonProperty(value = "bucketEncryption", required = true)
+ @Schema(description = "Enable bucket versioning upon bucket creation", required = true)
+ S3_SERVERSIDE_ENCRYPTION bucketEncryption = S3_SERVERSIDE_ENCRYPTION.NONE;
+
+ //----------------------------------------------------------------------
+ // (3b) STS client profile custom properties
+ //----------------------------------------------------------------------
+ @JsonProperty(value = "stsRoleArn", required = true)
+ @Schema(description = "roleArn to for STS AssumeRoleWithWebIdentity (AWS and MinIO)", example = "arn:aws:iam::930717317329:role/cipherduck_chain_01")
+ String stsRoleArn;
+
+ @JsonProperty(value = "stsRoleArn2")
+ @Schema(description = "roleArn to assume for STS AssumeRole in role chaining (AWS only, not MinIO)", example = "arn:aws:iam::930717317329:role/cipherduck_chain_02", nullable = true)
+ String stsRoleArn2;
+
+
+ @JsonProperty(value = "stsDurationSeconds", required = false)
+ @Schema(description = "Token lifetime for STS tokens assumed. Defaults to AWS/MinIO defaults", nullable = true)
+ Integer stsDurationSeconds;
+
+ public StorageProfileS3STSDto() {
+ // jackson
+ }
+
+ public StorageProfileS3STSDto(final UUID id, final String name, final Protocol protocol, final boolean archived, final String scheme, final String hostname, final Integer port, final boolean withPathStyleAccessEnabled, final S3_STORAGE_CLASSES storageClass, final String region, final List regions, final String bucketPrefix, final String stsRoleArnClient, final String stsRoleArnHub, final String stsEndpoint, final boolean bucketVersioning, final Boolean bucketAcceleration, final S3_SERVERSIDE_ENCRYPTION bucketEncryption, final String stsRoleArn, final String stsRoleArn2, final Integer stsDurationSeconds) {
+ super(id, name, protocol, archived, scheme, hostname, port, withPathStyleAccessEnabled, storageClass);
+ this.region = region;
+ this.regions = regions;
+ this.bucketPrefix = bucketPrefix;
+ this.stsRoleArnClient = stsRoleArnClient;
+ this.stsRoleArnHub = stsRoleArnHub;
+ this.stsEndpoint = stsEndpoint;
+ this.bucketVersioning = bucketVersioning;
+ this.bucketAcceleration = bucketAcceleration;
+ this.bucketEncryption = bucketEncryption;
+ this.stsRoleArn = stsRoleArn;
+ this.stsRoleArn2 = stsRoleArn2;
+ this.stsDurationSeconds = stsDurationSeconds;
+ }
+
+ static StorageProfileS3STSDto fromEntity(final StorageProfileS3STS storageProfile) {
+ return new StorageProfileS3STSDto(
+ storageProfile.id,
+ storageProfile.name,
+ Protocol.s3sts,
+ storageProfile.archived,
+ storageProfile.scheme,
+ storageProfile.hostname,
+ storageProfile.port,
+ storageProfile.withPathStyleAccessEnabled,
+ S3_STORAGE_CLASSES.valueOf(storageProfile.storageClass),
+ storageProfile.region,
+ storageProfile.regions,
+ storageProfile.bucketPrefix,
+ storageProfile.stsRoleArnClient,
+ storageProfile.stsRoleArnHub,
+ storageProfile.stsEndpoint,
+ storageProfile.bucketVersioning,
+ storageProfile.bucketAcceleration,
+ S3_SERVERSIDE_ENCRYPTION.valueOf(storageProfile.bucketEncryption),
+ storageProfile.stsRoleArn,
+ storageProfile.stsRoleArn2,
+ storageProfile.stsDurationSeconds
+ );
+ }
+
+ public StorageProfileS3STS toEntity() {
+ final StorageProfileS3STS storageProfile = new StorageProfileS3STS();
+ storageProfile.id = this.id;
+ storageProfile.name = this.name;
+ storageProfile.archived = this.archived;
+ storageProfile.scheme = this.scheme;
+ storageProfile.hostname = this.hostname;
+ storageProfile.port = this.port;
+ storageProfile.withPathStyleAccessEnabled = this.withPathStyleAccessEnabled;
+ storageProfile.storageClass = this.storageClass.toString();
+ storageProfile.region = this.region;
+ storageProfile.regions = this.regions;
+ storageProfile.bucketPrefix = this.bucketPrefix;
+ storageProfile.stsRoleArnClient = this.stsRoleArnClient;
+ storageProfile.stsRoleArnHub = this.stsRoleArnHub;
+ storageProfile.stsEndpoint = this.stsEndpoint;
+ storageProfile.bucketVersioning = this.bucketVersioning;
+ storageProfile.bucketAcceleration = this.bucketAcceleration;
+ storageProfile.bucketEncryption = this.bucketEncryption.name();
+ storageProfile.stsRoleArn = this.stsRoleArn;
+ storageProfile.stsRoleArn2 = this.stsRoleArn2;
+ storageProfile.stsDurationSeconds = this.stsDurationSeconds;
+ return storageProfile;
+ }
+
+ public String region() {
+ return region;
+ }
+
+ public List regions() {
+ return regions;
+ }
+
+ public String bucketPrefix() {
+ return bucketPrefix;
+ }
+
+ public String stsRoleArnClient() {
+ return stsRoleArnClient;
+ }
+
+ public String stsRoleArnHub() {
+ return stsRoleArnHub;
+ }
+
+ public String stsEndpoint() {
+ return stsEndpoint;
+ }
+
+ public Boolean bucketVersioning() {
+ return bucketVersioning;
+ }
+
+ public Boolean bucketAcceleration() {
+ return bucketAcceleration;
+ }
+
+ public S3_SERVERSIDE_ENCRYPTION bucketEncryption() {
+ return bucketEncryption;
+ }
+
+ public String stsRoleArn() {
+ return stsRoleArn;
+ }
+
+ public String stsRoleArn2() {
+ return stsRoleArn2;
+ }
+
+ public Integer stsDurationSeconds() {
+ return stsDurationSeconds;
+ }
+}
diff --git a/backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageResource.java b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageResource.java
new file mode 100644
index 000000000..f609bfab2
--- /dev/null
+++ b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageResource.java
@@ -0,0 +1,85 @@
+package org.cryptomator.hub.api.cipherduck;
+
+import jakarta.annotation.security.RolesAllowed;
+import jakarta.inject.Inject;
+import jakarta.transaction.Transactional;
+import jakarta.ws.rs.ClientErrorException;
+import jakarta.ws.rs.PUT;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import org.cryptomator.hub.SyncerConfig;
+import org.cryptomator.hub.api.GoneException;
+import org.cryptomator.hub.entities.User;
+import org.cryptomator.hub.entities.Vault;
+import org.cryptomator.hub.entities.cipherduck.StorageProfile;
+import org.eclipse.microprofile.jwt.JsonWebToken;
+import org.eclipse.microprofile.openapi.annotations.Operation;
+import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
+import org.jboss.logging.Logger;
+
+import java.net.URI;
+import java.util.Map;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import static org.cryptomator.hub.api.cipherduck.storage.S3StorageHelper.makeS3Bucket;
+import static org.cryptomator.hub.cipherduck.KeycloakCryptomatorVaultsHelper.keycloakGrantAccessToVault;
+import static org.cryptomator.hub.cipherduck.KeycloakCryptomatorVaultsHelper.keycloakPrepareVault;
+
+@Path("/storage")
+public class StorageResource {
+ private static final Logger LOG = Logger.getLogger(StorageResource.class);
+
+ @Inject
+ SyncerConfig syncerConfig;
+
+ @Inject
+ CipherduckConfig cipherduckConfig;
+
+ @Inject
+ JsonWebToken jwt;
+
+
+ @PUT
+ @Path("/{vaultId}")
+ @RolesAllowed("user")
+ @Produces(MediaType.APPLICATION_JSON)
+ @Transactional
+ @Operation(summary = "creates bucket and policy", description = "creates an S3 bucket and uploads policy for it.")
+ @APIResponse(responseCode = "200", description = "Bucket and Keycloak config created")
+ @APIResponse(responseCode = "400", description = "Could not create bucket")
+ @APIResponse(responseCode = "409", description = "Vault with this ID or bucket with this name already exists")
+ @APIResponse(responseCode = "410", description = "Storage profile is archived")
+ public Response createBucket(@PathParam("vaultId") UUID vaultId, final StorageDto storage) {
+ Optional vault = Vault.findByIdOptional(vaultId);
+ if (vault.isPresent()) {
+ throw new ClientErrorException(String.format("Vault with ID %s already exists", vaultId), Response.Status.CONFLICT);
+ }
+
+ final Map storageConfigs = StorageProfile.findAll().stream().map(StorageProfileDto::fromEntity).collect(Collectors.toMap(StorageProfileDto::id, Function.identity()));
+ if (!storageConfigs.containsKey(storage.storageConfigId())) {
+ return Response.status(Response.Status.BAD_REQUEST).entity(String.format("Storage profile %s not found on this server", storage.storageConfigId())).build();
+ }
+ final StorageProfileDto storageProfileDto = storageConfigs.get(storage.storageConfigId());
+ if (storageProfileDto.archived) {
+ throw new GoneException("Storage profile is archived.");
+ }
+ if (!(storageProfileDto instanceof StorageProfileS3STSDto)) {
+ return Response.status(Response.Status.BAD_REQUEST).entity(String.format("Storage profile must be StorageProfileS3STSDto. Found %s", storageProfileDto.getClass().getName())).build();
+ }
+
+ // N.B. if the bucket already exists, this will fail, so we do not prevent calling this method several times.
+ makeS3Bucket((StorageProfileS3STSDto) storageProfileDto, storage);
+
+ final User currentUser = User.findById(jwt.getSubject());
+ keycloakGrantAccessToVault(syncerConfig, vaultId.toString(), currentUser.id, cipherduckConfig.keycloakClientIdCryptomatorVaults());
+ keycloakPrepareVault(syncerConfig, vaultId.toString(), (StorageProfileS3STSDto) storageProfileDto, jwt.getSubject(), cipherduckConfig.keycloakClientIdCryptomatorVaults());
+
+ return Response.created(URI.create(".")).build();
+ }
+}
diff --git a/backend/src/main/java/org/cryptomator/hub/api/cipherduck/VaultJWEBackendDto.java b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/VaultJWEBackendDto.java
new file mode 100644
index 000000000..ff391e263
--- /dev/null
+++ b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/VaultJWEBackendDto.java
@@ -0,0 +1,30 @@
+package org.cryptomator.hub.api.cipherduck;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Part of vault JWE specifying the vault bookmark.
+ * Allows to create a bookmark in the client referencing the vendor in the storage profiles.
+ * This Java record is unused in hub, only its ts counterpart in `backend.ts`.
+ * It will used in Cipherduck client in the OpenAPI generator.
+ */
+public record VaultJWEBackendDto(
+
+ @JsonProperty(value = "provider", required = true)
+ // references id in StorageProfileDto (aka. vendor in client profile)
+ String provider,
+ @JsonProperty(value = "defaultPath", required = true)
+ String defaultPath,
+ @JsonProperty(value = "nickname", required = true)
+ String nickname,
+ @JsonProperty(value = "region", required = true)
+ String region,
+
+ @JsonProperty(value = "username")
+ // for non-STS
+ String username,
+ @JsonProperty(value = "password")
+ // for non-STS
+ String password
+) {
+}
\ No newline at end of file
diff --git a/backend/src/main/java/org/cryptomator/hub/api/cipherduck/VaultJWEPayloadDto.java b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/VaultJWEPayloadDto.java
new file mode 100644
index 000000000..a3b7dcb85
--- /dev/null
+++ b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/VaultJWEPayloadDto.java
@@ -0,0 +1,17 @@
+package org.cryptomator.hub.api.cipherduck;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public record VaultJWEPayloadDto(
+
+ @JsonProperty(value = "key", required = true)
+ // masterkey
+ String key,
+
+ @JsonProperty(value = "backend", required = true)
+ VaultJWEBackendDto backend,
+
+ @JsonProperty(value = "automaticAccessGrant", required = true)
+ AutomaticAccessGrant automaticAccessGrant
+) {
+}
diff --git a/backend/src/main/java/org/cryptomator/hub/api/cipherduck/storage/S3StorageHelper.java b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/storage/S3StorageHelper.java
new file mode 100644
index 000000000..f816f7240
--- /dev/null
+++ b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/storage/S3StorageHelper.java
@@ -0,0 +1,191 @@
+package org.cryptomator.hub.api.cipherduck.storage;
+
+
+import jakarta.ws.rs.ClientErrorException;
+import jakarta.ws.rs.core.Response;
+import org.cryptomator.hub.api.cipherduck.CreateS3STSBucketDto;
+import org.cryptomator.hub.api.cipherduck.StorageProfileS3STSDto;
+import org.jboss.logging.Logger;
+import software.amazon.awssdk.auth.credentials.AwsSessionCredentials;
+import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
+import software.amazon.awssdk.core.sync.RequestBody;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.s3.S3Client;
+import software.amazon.awssdk.services.s3.S3ClientBuilder;
+import software.amazon.awssdk.services.s3.S3Configuration;
+import software.amazon.awssdk.services.s3.model.AccelerateConfiguration;
+import software.amazon.awssdk.services.s3.model.BucketAccelerateStatus;
+import software.amazon.awssdk.services.s3.model.BucketVersioningStatus;
+import software.amazon.awssdk.services.s3.model.CreateBucketRequest;
+import software.amazon.awssdk.services.s3.model.GetBucketAccelerateConfigurationRequest;
+import software.amazon.awssdk.services.s3.model.GetBucketAccelerateConfigurationResponse;
+import software.amazon.awssdk.services.s3.model.GetBucketEncryptionRequest;
+import software.amazon.awssdk.services.s3.model.GetBucketEncryptionResponse;
+import software.amazon.awssdk.services.s3.model.GetBucketVersioningRequest;
+import software.amazon.awssdk.services.s3.model.GetBucketVersioningResponse;
+import software.amazon.awssdk.services.s3.model.HeadBucketRequest;
+import software.amazon.awssdk.services.s3.model.NoSuchBucketException;
+import software.amazon.awssdk.services.s3.model.PutBucketAccelerateConfigurationRequest;
+import software.amazon.awssdk.services.s3.model.PutBucketEncryptionRequest;
+import software.amazon.awssdk.services.s3.model.PutBucketVersioningRequest;
+import software.amazon.awssdk.services.s3.model.PutObjectRequest;
+import software.amazon.awssdk.services.s3.model.S3Exception;
+import software.amazon.awssdk.services.s3.model.ServerSideEncryption;
+import software.amazon.awssdk.services.s3.model.ServerSideEncryptionByDefault;
+import software.amazon.awssdk.services.s3.model.ServerSideEncryptionConfiguration;
+import software.amazon.awssdk.services.s3.model.ServerSideEncryptionRule;
+import software.amazon.awssdk.services.s3.model.VersioningConfiguration;
+
+import java.net.URI;
+import java.util.Collections;
+
+public class S3StorageHelper {
+ private static final Logger log = Logger.getLogger(S3StorageHelper.class);
+
+ public static void makeS3Bucket(
+ final StorageProfileS3STSDto storageConfig,
+ final CreateS3STSBucketDto dto
+ ) {
+
+ if (log.isInfoEnabled()) {
+ log.info(String.format("Make S3 bucket %s for profile %s", dto, storageConfig));
+ }
+
+ final String bucketName = storageConfig.bucketPrefix() + dto.vaultId();
+ // https://github.com/awsdocs/aws-doc-sdk-examples/blob/main/java/example_code/s3/src/main/java/aws/example/s3/CreateBucket.java
+ final String region = dto.region();
+
+ S3ClientBuilder s3Builder = S3Client.builder()
+ .credentialsProvider(StaticCredentialsProvider.create(AwsSessionCredentials.create(dto.awsAccessKey(), dto.awsSecretKey(), dto.sessionToken())));
+ if (storageConfig.stsEndpoint() != null) {
+ s3Builder = s3Builder
+ .endpointOverride(URI.create(storageConfig.stsEndpoint()))
+ .serviceConfiguration(S3Configuration.builder()
+ .pathStyleAccessEnabled(storageConfig.withPathStyleAccessEnabled() != null ? storageConfig.withPathStyleAccessEnabled() : false)
+ .build());
+ } else if (region != null) {
+ s3Builder = s3Builder.region(Region.of(region));
+ }
+ final S3Client s3 = s3Builder.build();
+ boolean okToCreate = true;
+ try {
+ s3.headBucket(HeadBucketRequest.builder().bucket(bucketName).build());
+ } catch (final NoSuchBucketException e) {
+ okToCreate = true;
+ } catch (final S3Exception e) {
+ if (e.statusCode() == 403) {
+ // ignore
+ log.info("Ignoring 403 on bucket head", e);
+ okToCreate = true;
+ }
+ }
+ if (!okToCreate) {
+ throw new ClientErrorException(String.format("Bucket %s already exists or no permission to list.", bucketName), Response.Status.CONFLICT);
+ }
+
+ s3.createBucket(CreateBucketRequest.builder().bucket(bucketName).build());
+ if (log.isInfoEnabled()) {
+ log.info(String.format("Upload vault template to %s (%s, %s)", bucketName, dto, storageConfig));
+ }
+ s3.putObject(PutObjectRequest.builder()
+ .bucket(bucketName)
+ .key("vault.uvf")
+ .build(),
+ RequestBody.fromString(dto.vaultUvf()));
+
+ // See https://github.com/cryptomator/hub/blob/develop/frontend/src/common/vaultconfig.ts
+ // zip.file('vault.uvf', this.vaultUvf);
+ // zip.folder('d')?.folder(this.rootDirHash.substring(0, 2))?.folder(this.rootDirHash.substring(2));
+ // create meta-data for your folder and set content-length to 0
+ final PutObjectRequest request2 = PutObjectRequest.builder()
+ .bucket(bucketName)
+ .key(String.format("d/%s/%s/", dto.rootDirHash().substring(0, 2), dto.rootDirHash().substring(2)))
+ .contentLength(0L)
+ .build();
+ s3.putObject(request2, RequestBody.empty());
+
+ // TODO https://github.com/shift7-ch/cipherduck-hub/issues/44 CORS?
+ // enable versioning on the bucket.
+ {
+ if (log.isInfoEnabled()) {
+ log.info(String.format("Enable/disable bucket versioning on %s (%s, %s)", bucketName, dto, storageConfig));
+ }
+ s3.putBucketVersioning(PutBucketVersioningRequest.builder()
+ .bucket(bucketName)
+ .versioningConfiguration(VersioningConfiguration.builder().status(storageConfig.bucketVersioning() ? BucketVersioningStatus.ENABLED : BucketVersioningStatus.SUSPENDED).build())
+ .build());
+ final GetBucketVersioningResponse conf = s3.getBucketVersioning(GetBucketVersioningRequest.builder().bucket(bucketName).build());
+ if (log.isInfoEnabled()) {
+ log.info(String.format("Enabled/disabled bucket versioning on %s (%s, %s) with status %s", bucketName, dto, storageConfig, conf.statusAsString()));
+ }
+ }
+
+ // enable/disable bucket acceleration on the bucket. Skip if not set (e.g. MinIO which has no bucket acceleration API)
+ if (storageConfig.bucketAcceleration() != null) {
+ if (log.isInfoEnabled()) {
+ log.info(String.format("Enable/disable bucket acceleration on %s (%s, %s)", bucketName, dto, storageConfig));
+ }
+ s3.putBucketAccelerateConfiguration(PutBucketAccelerateConfigurationRequest.builder()
+ .bucket(bucketName)
+ .accelerateConfiguration(AccelerateConfiguration.builder()
+ .status(storageConfig.bucketAcceleration() ? BucketAccelerateStatus.ENABLED : BucketAccelerateStatus.SUSPENDED)
+ .build())
+ .build());
+ final GetBucketAccelerateConfigurationResponse conf = s3.getBucketAccelerateConfiguration(GetBucketAccelerateConfigurationRequest.builder().bucket(bucketName).build());
+ if (log.isInfoEnabled()) {
+ log.info(String.format("Enabled/disabled bucket acceleration on %s (%s, %s) with status %s", bucketName, dto, storageConfig, conf.status()));
+ }
+ }
+
+ // enable/disable bucket encryption on the bucket
+ {
+ if (log.isInfoEnabled()) {
+ log.info(String.format("Enable/disable bucket encryption on %s (%s, %s)", bucketName, dto, storageConfig));
+ }
+ switch (storageConfig.bucketEncryption()) {
+ case NONE -> {
+ }
+ case SSE_AES256 -> s3.putBucketEncryption(
+ PutBucketEncryptionRequest.builder()
+ .bucket(bucketName)
+ .serverSideEncryptionConfiguration(ServerSideEncryptionConfiguration.builder()
+ .rules(Collections.singleton(
+ ServerSideEncryptionRule.builder()
+ .applyServerSideEncryptionByDefault(ServerSideEncryptionByDefault.builder()
+ .sseAlgorithm(ServerSideEncryption.AES256).build())
+ .build()))
+
+ .build())
+ .build());
+ case SSE_KMS_DEFAULT -> s3.putBucketEncryption(
+ PutBucketEncryptionRequest.builder()
+ .bucket(bucketName)
+ .serverSideEncryptionConfiguration(ServerSideEncryptionConfiguration.builder()
+ .rules(Collections.singleton(
+ ServerSideEncryptionRule.builder()
+ .applyServerSideEncryptionByDefault(ServerSideEncryptionByDefault.builder()
+ .sseAlgorithm(ServerSideEncryption.AWS_KMS).build())
+ .build()))
+
+ .build())
+ .build());
+ }
+ switch (storageConfig.bucketEncryption()) {
+ case NONE:
+ // MinIO does not support bucket acceleration nor encryption -> TODO https://github.com/shift7-ch/cipherduck-hub/issues/44 should we make bucketEncryption attribute in storage profile nullable instead?
+ // https://min.io/docs/minio/linux/administration/identity-access-management/policy-based-access-control.html
+ // https://github.com/minio/minio/blob/master/cmd/api-router.go
+ // https://github.com/minio/minio/issues/14586
+ break;
+ case SSE_AES256:
+ case SSE_KMS_DEFAULT:
+ final GetBucketEncryptionResponse conf = s3.getBucketEncryption(GetBucketEncryptionRequest.builder().bucket(bucketName).build());
+ if (log.isInfoEnabled()) {
+ log.info(String.format("Enabled/disabled bucket encryption on %s (%s, %s) with configuration %s", bucketName, dto, storageConfig, conf.serverSideEncryptionConfiguration()));
+ }
+ }
+ }
+ // TODO https://github.com/shift7-ch/cipherduck-hub/issues/44 CORS?
+ //s3.setBucketCrossOriginConfiguration();
+ }
+}
diff --git a/backend/src/main/java/org/cryptomator/hub/cipherduck/KeycloakCryptomatorVaultsHelper.java b/backend/src/main/java/org/cryptomator/hub/cipherduck/KeycloakCryptomatorVaultsHelper.java
new file mode 100644
index 000000000..478baebf6
--- /dev/null
+++ b/backend/src/main/java/org/cryptomator/hub/cipherduck/KeycloakCryptomatorVaultsHelper.java
@@ -0,0 +1,235 @@
+package org.cryptomator.hub.cipherduck;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.ws.rs.core.Response;
+import org.cryptomator.hub.SyncerConfig;
+import org.cryptomator.hub.api.VaultResource;
+import org.cryptomator.hub.api.cipherduck.StorageProfileS3STSDto;
+import org.cryptomator.hub.entities.Group;
+import org.cryptomator.hub.entities.Vault;
+import org.jboss.logging.Logger;
+import org.jboss.resteasy.reactive.ClientWebApplicationException;
+import org.keycloak.admin.client.Keycloak;
+import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.admin.client.resource.ClientScopeResource;
+import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.ClientScopeRepresentation;
+import org.keycloak.representations.idm.ProtocolMapperRepresentation;
+import org.keycloak.representations.idm.RoleRepresentation;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@ApplicationScoped
+public class KeycloakCryptomatorVaultsHelper {
+
+ private static final Logger LOG = Logger.getLogger(KeycloakCryptomatorVaultsHelper.class);
+
+ public static void keycloakPrepareVault(final SyncerConfig syncerConfig, final String vaultId, final StorageProfileS3STSDto storageConfig, final String userOrGroupId, final String clientId) {
+
+ // N.B. quarkus has no means to provide empty string as value, interpreted as no value, see https://github.com/quarkusio/quarkus/issues/2765
+ // TODO review better solution than using sentinel string "empty"?
+ if ("empty".equals(syncerConfig.getKeycloakUrl())) {
+ LOG.error(String.format("Could not grant access to vault %s for user %s as keycloak URL is not defined.", vaultId, userOrGroupId));
+ return;
+ }
+
+ try (final Keycloak keycloak = Keycloak.getInstance(syncerConfig.getKeycloakUrl(), syncerConfig.getKeycloakRealm(), syncerConfig.getUsername(), syncerConfig.getPassword(), syncerConfig.getKeycloakClientId())) {
+
+ // https://www.keycloak.org/docs-api/21.1.1/rest-api
+ final RealmResource realm = keycloak.realm(syncerConfig.getKeycloakRealm());
+
+ final boolean minio = storageConfig.stsRoleArn() != null && storageConfig.stsRoleArn2() == null;
+ final boolean aws = storageConfig.stsRoleArn() != null && storageConfig.stsRoleArn2() != null;
+
+ ClientScopeResource clientScopeResource = realm.clientScopes().get(vaultId);
+
+ if (minio) {
+ ProtocolMapperRepresentation minioProtocolMapper = new ProtocolMapperRepresentation();
+ minioProtocolMapper.setName(String.format("Hard-coded mapper for vault %s (MinIO)", vaultId));
+ minioProtocolMapper.setProtocolMapper("oidc-hardcoded-claim-mapper");
+ minioProtocolMapper.setProtocol("openid-connect");
+
+ Map minioConfig = new HashMap<>();
+ minioConfig.put("jsonType.label", "String");
+
+ minioConfig.put("userinfo.token.claim", "false");
+ minioConfig.put("id.token.claim", "false");
+ minioConfig.put("access.token.claim", "true");
+ minioConfig.put("access.tokenResponse.claim", "false");
+
+ // exhaustive list of jwt claims evaluated in MinIO: https://min.io/docs/minio/linux/administration/identity-access-management/policy-based-access-control.html#policy-variables
+ // let's use client_id, as aud etc. are already use by standard mappers
+ minioConfig.put("claim.name", "client_id");
+ minioConfig.put("claim.value", vaultId);
+
+ minioProtocolMapper.setConfig(minioConfig);
+
+ clientScopeResource.getProtocolMappers().createMapper(Arrays.asList(minioProtocolMapper));
+ }
+ if (aws) {
+ ProtocolMapperRepresentation awsProtocolMapper = new ProtocolMapperRepresentation();
+ awsProtocolMapper.setName(String.format("Hard-coded mapper for vault %s (AWS)", vaultId));
+ awsProtocolMapper.setProtocolMapper("oidc-hardcoded-claim-mapper");
+ awsProtocolMapper.setProtocol("openid-connect");
+
+ Map awsConfig = new HashMap<>();
+ awsConfig.put("jsonType.label", "JSON");
+
+ awsConfig.put("userinfo.token.claim", "false");
+ awsConfig.put("id.token.claim", "false");
+ awsConfig.put("access.token.claim", "true");
+ awsConfig.put("access.tokenResponse.claim", "false");
+
+ awsConfig.put("claim.name", "https://aws\\.amazon\\.com/tags");
+ awsConfig.put("claim.value", String.format("{\"principal_tags\":{\"%s\":[\"\"]},\"transitive_tag_keys\":[\"%s\"]}", vaultId, vaultId));
+
+ awsProtocolMapper.setConfig(awsConfig);
+
+ clientScopeResource.getProtocolMappers().createMapper(Arrays.asList(awsProtocolMapper));
+ }
+ }
+ }
+
+ public static void keycloakGrantAccessToVault(final SyncerConfig syncerConfig, final String vaultId, final String userOrGroupId, final String clientId, final Group.Repository groupRepo) {
+ // N.B. quarkus has no means to provide empty string as value, interpreted as no value, see https://github.com/quarkusio/quarkus/issues/2765
+ // TODO review better solution than using sentinel string "empty"?
+ if ("empty".equals(syncerConfig.getKeycloakUrl())) {
+ LOG.error(String.format("Could not grant access to vault %s for user %s as keycloak URL is not defined.", vaultId, userOrGroupId));
+ return;
+ }
+
+ var group = groupRepo.findByIdOptional(userOrGroupId);
+ final boolean isGroup = group.isPresent();
+
+ try (final Keycloak keycloak = Keycloak.getInstance(syncerConfig.getKeycloakUrl(), syncerConfig.getKeycloakRealm(), syncerConfig.getUsername(), syncerConfig.getPassword(), syncerConfig.getKeycloakClientId())) {
+
+ // https://www.keycloak.org/docs-api/21.1.1/rest-api
+ final RealmResource realm = keycloak.realm(syncerConfig.getKeycloakRealm());
+
+ List byClientId = realm.clients().findByClientId(clientId);
+ if (byClientId.size() != 1) {
+ throw new RuntimeException(String.format("There are %s clients with clientId %s, expected to found exactly one.", byClientId.size(), clientId));
+ }
+ final ClientRepresentation cryptomatorVaultsClientRepresentation = byClientId.get(0);
+ ClientResource cryptomatorVaultsClientResource = realm.clients().get(cryptomatorVaultsClientRepresentation.getId());
+
+ // create client scope (if necessary)
+ if (realm.clientScopes().findAll().stream().map(clientScopeRepresentation -> clientScopeRepresentation.getId()).noneMatch(vaultId::equals)) {
+ ClientScopeRepresentation vaultClientScope = new ClientScopeRepresentation();
+ vaultClientScope.setId(vaultId);
+ vaultClientScope.setName(vaultId);
+ vaultClientScope.setDescription(String.format("Client scope for vault %s", vaultId));
+ vaultClientScope.setAttributes(new HashMap<>());
+ vaultClientScope.setProtocol("openid-connect");
+
+ Response response = realm.clientScopes().create(vaultClientScope);
+ if (response.getStatus() != 201) {
+ throw new RuntimeException(String.format("Failed to create client for vault %s. %s", vaultId, response.getStatusInfo().getReasonPhrase()));
+ }
+ }
+
+ // add client scope to "cryptomatorvaults" client
+ // -> requires role_manage-clients
+ cryptomatorVaultsClientResource.addOptionalClientScope(vaultId);
+
+
+ // create client role (if necessary)
+ // -> requires role_manage-clients
+ if (cryptomatorVaultsClientResource.roles().list().stream().map(role -> role.getName()).noneMatch(vaultId::equals)) {
+ RoleRepresentation vaultRole = new RoleRepresentation();
+ vaultRole.setName(vaultId);
+ vaultRole.setDescription(String.format("Role for vault %s", vaultId));
+ vaultRole.setClientRole(true);
+
+ cryptomatorVaultsClientResource.roles().create(vaultRole);
+ }
+
+ // scope the client scope to the client role for the vault
+ realm.clientScopes().get(vaultId).getScopeMappings().clientLevel(cryptomatorVaultsClientRepresentation.getId()).add(List.of(cryptomatorVaultsClientResource.roles().get(vaultId).toRepresentation()));
+
+
+ // add client role to user/group
+ // -> requires role_manage-users
+ if (!isGroup) {
+ realm.users().get(userOrGroupId).roles().clientLevel(cryptomatorVaultsClientRepresentation.getId()).add(List.of(cryptomatorVaultsClientResource.roles().get(vaultId).toRepresentation()));
+ } else {
+ realm.groups().group(userOrGroupId).roles().clientLevel(cryptomatorVaultsClientRepresentation.getId()).add(List.of(cryptomatorVaultsClientResource.roles().get(vaultId).toRepresentation()));
+ }
+ }
+ }
+
+
+ public static void keycloakRemoveAccessToVault(final SyncerConfig syncerConfig, final String vaultId, final String userOrGroupId, final String clientId, final Group.Repository groupRepo) {
+ // N.B. quarkus has no means to provide empty string as value, interpreted as no value, see https://github.com/quarkusio/quarkus/issues/2765
+ // TODO review better solution than using sentinel string "empty"?
+ if ("empty".equals(syncerConfig.getKeycloakUrl())) {
+ LOG.error(String.format("Could not grant access to vault %s for user %s as keycloak URL is not defined.", vaultId, userOrGroupId));
+ return;
+ }
+
+ final boolean isGroup = groupRepo.findByIdOptional(userOrGroupId).isPresent();
+
+ try (final Keycloak keycloak = Keycloak.getInstance(syncerConfig.getKeycloakUrl(), syncerConfig.getKeycloakRealm(), syncerConfig.getUsername(), syncerConfig.getPassword(), syncerConfig.getKeycloakClientId())) {
+
+ // https://www.keycloak.org/docs-api/21.1.1/rest-api
+ final RealmResource realm = keycloak.realm(syncerConfig.getKeycloakRealm());
+
+ // add client scope to "cryptomatorvaults" client
+ // -> requires role_manage-clients
+ List byClientId = realm.clients().findByClientId(clientId);
+ if (byClientId.size() != 1) {
+ throw new RuntimeException(String.format("There are %s clients with clientId %s, expected to found exactly one.", byClientId.size(), clientId));
+ }
+ final ClientRepresentation cryptomatorVaultsClientRepresentation = byClientId.get(0);
+ ClientResource cryptomatorVaultsClientResource = realm.clients().get(cryptomatorVaultsClientRepresentation.getId());
+ cryptomatorVaultsClientResource.addOptionalClientScope(vaultId);
+
+ // remove client role from user/group
+ // -> requires role_manage-users
+ if (!isGroup) {
+ realm.users().get(userOrGroupId).roles().clientLevel(cryptomatorVaultsClientRepresentation.getId()).remove(List.of(cryptomatorVaultsClientResource.roles().get(vaultId).toRepresentation()));
+ } else {
+ realm.groups().group(userOrGroupId).roles().clientLevel(cryptomatorVaultsClientRepresentation.getId()).remove(List.of(cryptomatorVaultsClientResource.roles().get(vaultId).toRepresentation()));
+ }
+ }
+ }
+
+ // TODO review: this loop might not be safe enough to run in production - should we just disable this feature or remove from code entirely?
+ // Deleting the cryptomatorvaults client also deletes the client roles under the client, however, the client scopes are at the realm level and will not be removed by this procedure.
+ // Although safe, this can quickly become a mess in developing scenarios.
+ public static void keycloakCleanupDanglingCryptomatorVaultsRoles(final SyncerConfig syncerConfig, final String clientId, final Vault.Repository vaultRepo) {
+ Set existingVaultIds = vaultRepo.findAll().stream().map(VaultResource.VaultDto::fromEntity).map(vdto -> vdto.id().toString()).collect(Collectors.toSet());
+ try (final Keycloak keycloak = Keycloak.getInstance(syncerConfig.getKeycloakUrl(), syncerConfig.getKeycloakRealm(), syncerConfig.getUsername(), syncerConfig.getPassword(), syncerConfig.getKeycloakClientId())) {
+
+ // https://www.keycloak.org/docs-api/21.1.1/rest-api
+ final RealmResource realm = keycloak.realm(syncerConfig.getKeycloakRealm());
+
+ List byClientId = realm.clients().findByClientId(clientId);
+ if (byClientId.size() != 1) {
+ throw new RuntimeException(String.format("There are %s clients with clientId %s, expected to found exactly one.", byClientId.size(), clientId));
+ }
+ final ClientRepresentation cryptomatorVaultsClientRepresentation = byClientId.get(0);
+ ClientResource cryptomatorVaultsClientResource = realm.clients().get(cryptomatorVaultsClientRepresentation.getId());
+
+ for (RoleRepresentation roleRepresentation : cryptomatorVaultsClientResource.roles().list()) {
+ final String vaultId = roleRepresentation.getName();
+ if (!existingVaultIds.contains(vaultId)) {
+ cryptomatorVaultsClientResource.roles().deleteRole(vaultId);
+ try {
+ realm.clientScopes().get(vaultId).remove();
+ } catch (ClientWebApplicationException e) {
+ if (LOG.isInfoEnabled()) {
+ LOG.info(String.format("Could not delete client scope %s", vaultId), e);
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/backend/src/main/java/org/cryptomator/hub/cipherduck/KeycloakCryptomatorVaultsSyncer.java b/backend/src/main/java/org/cryptomator/hub/cipherduck/KeycloakCryptomatorVaultsSyncer.java
new file mode 100644
index 000000000..50c769070
--- /dev/null
+++ b/backend/src/main/java/org/cryptomator/hub/cipherduck/KeycloakCryptomatorVaultsSyncer.java
@@ -0,0 +1,28 @@
+package org.cryptomator.hub.cipherduck;
+
+
+import io.quarkus.scheduler.Scheduled;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import org.cryptomator.hub.SyncerConfig;
+import org.cryptomator.hub.api.cipherduck.CipherduckConfig;
+import org.cryptomator.hub.entities.Vault;
+
+@ApplicationScoped
+public class KeycloakCryptomatorVaultsSyncer {
+
+ @Inject
+ SyncerConfig syncerConfig;
+
+ @Inject
+ CipherduckConfig cipherduckConfig;
+
+ @Inject
+ Vault.Repository vaultRepo;
+
+ @Scheduled(every = "{hub.keycloak.syncer-period}")
+ void sync() {
+ KeycloakCryptomatorVaultsHelper.keycloakCleanupDanglingCryptomatorVaultsRoles(syncerConfig, cipherduckConfig.keycloakClientIdCryptomatorVaults(), vaultRepo);
+ }
+
+}
diff --git a/backend/src/main/java/org/cryptomator/hub/entities/cipherduck/StorageProfile.java b/backend/src/main/java/org/cryptomator/hub/entities/cipherduck/StorageProfile.java
new file mode 100644
index 000000000..dc9fbf44a
--- /dev/null
+++ b/backend/src/main/java/org/cryptomator/hub/entities/cipherduck/StorageProfile.java
@@ -0,0 +1,33 @@
+package org.cryptomator.hub.entities.cipherduck;
+
+import io.quarkus.hibernate.orm.panache.PanacheEntityBase;
+import jakarta.persistence.Column;
+import jakarta.persistence.DiscriminatorColumn;
+import jakarta.persistence.Entity;
+import jakarta.persistence.Id;
+import jakarta.persistence.Inheritance;
+import jakarta.persistence.InheritanceType;
+import jakarta.persistence.Table;
+
+import java.util.UUID;
+
+@Entity
+@Table(name = "storage_profile")
+@Inheritance(strategy = InheritanceType.JOINED)
+@DiscriminatorColumn(name = "protocol")
+public class StorageProfile extends PanacheEntityBase { // TODO make sealed?
+ @Id
+ @Column(name = "id", nullable = false)
+ public UUID id;
+
+ @Column(name = "name", nullable = false)
+ public String name;
+
+ @Column(name = "archived", nullable = false)
+ public boolean archived;
+
+ public StorageProfile setArchived(boolean archived) {
+ this.archived = archived;
+ return this;
+ }
+}
diff --git a/backend/src/main/java/org/cryptomator/hub/entities/cipherduck/StorageProfileS3.java b/backend/src/main/java/org/cryptomator/hub/entities/cipherduck/StorageProfileS3.java
new file mode 100644
index 000000000..711238a9e
--- /dev/null
+++ b/backend/src/main/java/org/cryptomator/hub/entities/cipherduck/StorageProfileS3.java
@@ -0,0 +1,35 @@
+package org.cryptomator.hub.entities.cipherduck;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.DiscriminatorValue;
+import jakarta.persistence.Entity;
+import jakarta.persistence.Table;
+
+import java.util.UUID;
+
+@Entity
+@Table(name = "storage_profile_s3")
+@DiscriminatorValue("S3")
+public class StorageProfileS3 extends StorageProfile {// TODO make sealed?
+
+ //======================================================================
+ // (1) STS and permanent:
+ // - bucket creation frontend/desktop client (STS)
+ // - template upload (STS and permanent)
+ // - client profile (STS and permanent)
+ //======================================================================
+ @Column
+ public String scheme;
+
+ @Column
+ public String hostname;
+
+ @Column
+ public Integer port;
+
+ @Column
+ public Boolean withPathStyleAccessEnabled = false;
+
+ @Column
+ public String storageClass = "STANDARD";
+}
diff --git a/backend/src/main/java/org/cryptomator/hub/entities/cipherduck/StorageProfileS3STS.java b/backend/src/main/java/org/cryptomator/hub/entities/cipherduck/StorageProfileS3STS.java
new file mode 100644
index 000000000..c098fbc65
--- /dev/null
+++ b/backend/src/main/java/org/cryptomator/hub/entities/cipherduck/StorageProfileS3STS.java
@@ -0,0 +1,61 @@
+package org.cryptomator.hub.entities.cipherduck;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import jakarta.persistence.Column;
+import jakarta.persistence.DiscriminatorValue;
+import jakarta.persistence.Entity;
+import jakarta.persistence.Table;
+import org.cryptomator.hub.api.cipherduck.StorageProfileS3STSDto;
+import org.eclipse.microprofile.openapi.annotations.media.Schema;
+
+import java.util.List;
+import java.util.UUID;
+
+@Entity
+@Table(name = "storage_profile_s3_sts")
+@DiscriminatorValue("S3STS")
+public class StorageProfileS3STS extends StorageProfileS3 { // TODO make sealed/final?
+
+ //======================================================================
+ // (2) STS only: bucket creation
+ //======================================================================
+ @Column
+ public String region;
+
+ @Column
+ public List regions;
+
+ @Column
+ public String bucketPrefix;
+
+ @Column
+ public String stsRoleArnClient;
+
+ @Column
+ public String stsRoleArnHub;
+
+ @Column
+ public String stsEndpoint = null;
+
+ @Column
+ public Boolean bucketVersioning = true;
+
+ @Column
+ public Boolean bucketAcceleration = true;
+
+ @Column
+ public String bucketEncryption;
+
+ //----------------------------------------------------------------------
+ // (3b) STS client profile custom properties
+ //----------------------------------------------------------------------
+ @Column
+ public String stsRoleArn;
+
+ @Column
+ public String stsRoleArn2;
+
+ @Column
+ public Integer stsDurationSeconds = null;
+
+}
diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties
index 248bcdab4..354b8111e 100644
--- a/backend/src/main/resources/application.properties
+++ b/backend/src/main/resources/application.properties
@@ -13,6 +13,10 @@ hub.public-root-path=${quarkus.http.root-path}
# `public-url` is used in the frontend (js), `local-url` in the backend. Maybe the same URL, but does not have to be.
hub.keycloak.public-url=http://localhost:8180
hub.keycloak.local-url=http://localhost:8180
+
+# TODO review better solution than using sentinel string "empty"?, see KeycloakGrantAccessToVault:keycloakGrantAccessToVault
+%test.hub.keycloak.local-url=empty
+
hub.keycloak.realm=cryptomator
hub.managed-instance=false
@@ -21,16 +25,25 @@ quarkus.resteasy-reactive.path=/api
%test.quarkus.resteasy-reactive.path=/
quarkus.http.port=8080
+quarkus.http.access-log.enabled=true
+%dev.quarkus.log.level=DEBUG
quarkus.oidc.application-type=service
quarkus.oidc.client-id=cryptomatorhub
hub.keycloak.oidc.cryptomator-client-id=cryptomator
+hub.keycloak.oidc.cryptomator-vaults-client-id=cryptomatorvaults
+
# Keycloak dev service
%dev.quarkus.keycloak.devservices.realm-path=dev-realm.json
# TODO: realm-path needs to be in class path, i.e. under src/main/resources -> we might not want to include it in production jar though, so make use of maven profiles and specify optional resources https://github.com/quarkusio/quarkus-quickstarts/blob/f3f4939df30bcff062be126faaaeb58cb7c79fb6/security-keycloak-authorization-quickstart/pom.xml#L68-L75
%dev.quarkus.keycloak.devservices.realm-name=cryptomator
-%dev.quarkus.keycloak.devservices.start-command=start-dev
+# TODO review add to Dockerfile as well?
+# https://github.com/quarkusio/quarkus/blob/596d9ae7a76cf529d24594a82b7c540030799dac/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/DevServicesConfig.java#L30
+# https://github.com/quarkusio/quarkus/blob/main/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java#L110
+# https://github.com/cryptomator/cryptomator.github.io/blob/52ac36a1db04ce1aa6db41f0aeb0a0f2b76b68b5/assets/js/hubsetup.js#L112
+# https://github.com/keycloak/keycloak/issues/20582
+%dev.quarkus.keycloak.devservices.start-command=start-dev --import-realm --features=token-exchange,admin-fine-grained-authz
%dev.quarkus.keycloak.devservices.port=8180
%dev.quarkus.keycloak.devservices.service-name=quarkus-cryptomator-hub
%dev.quarkus.keycloak.devservices.image-name=ghcr.io/cryptomator/keycloak:24.0.4
@@ -62,6 +75,10 @@ quarkus.hibernate-orm.database.globally-quoted-identifiers=true
quarkus.flyway.migrate-at-start=true
quarkus.flyway.locations=classpath:org/cryptomator/hub/flyway
%dev.quarkus.flyway.ignore-missing-migrations=true
+%dev.quarkus.flyway.validate-migration-naming=true
+# https://quarkus.io/guides/databases-dev-services
+# https://stackoverflow.com/questions/44654216/correct-way-to-install-psql-without-full-postgres-on-macos
+# psql -h localhost -p 54082 -U quarkus -d quarkus
# log Hibernate SQL statements including values, for dev-purpose only
%dev.quarkus.log.min-level=TRACE
@@ -83,6 +100,8 @@ quarkus.http.header."Referrer-Policy".value=no-referrer
quarkus.http.header."Strict-Transport-Security".value=max-age=31536000; includeSubDomains
quarkus.http.header."X-Content-Type-Options".value=nosniff
quarkus.http.header."X-Frame-Options".value=deny
+# dev-ui needs very permissive xfo:
+#%dev.quarkus.http.header."X-Frame-Options".value=sameorigin
quarkus.http.header."X-Permitted-Cross-Domain-Policies".value=none
quarkus.http.header."Cross-Origin-Embedder-Policy".value=credentialless
quarkus.http.header."Cross-Origin-Opener-Policy".value=same-origin
@@ -112,12 +131,12 @@ quarkus.http.filter.static.matches=/(favicon.ico|logo.svg)
# Container Image Adjustments
quarkus.container-image.registry=ghcr.io
-quarkus.container-image.group=cryptomator
-quarkus.container-image.name=hub
+quarkus.container-image.group=shift7-ch
+quarkus.container-image.name=cipherduck
quarkus.container-image.tag=latest
-quarkus.container-image.labels."org.opencontainers.image.title"=Cryptomator Hub
+quarkus.container-image.labels."org.opencontainers.image.title"=Cipherduck Hub
quarkus.container-image.labels."org.opencontainers.image.description"=Centralized Zero-Knowledge Key Management for using Cryptomator in Teams and Organizations
-quarkus.container-image.labels."org.opencontainers.image.vendor"=Skymatic GmbH
-quarkus.container-image.labels."org.opencontainers.image.url"=https://cryptomator.org/hub
-quarkus.container-image.labels."org.opencontainers.image.source"=https://github.com/cryptomator/hub
+quarkus.container-image.labels."org.opencontainers.image.vendor"=Shift7 GmbH
+quarkus.container-image.labels."org.opencontainers.image.url"=https://github.com/shift7-ch/cipherduck-hub
+quarkus.container-image.labels."org.opencontainers.image.source"=https://github.com/shift7-ch/cipherduck-hub
quarkus.container-image.labels."org.opencontainers.image.licenses"=AGPL-3.0-or-later
diff --git a/backend/src/main/resources/dev-realm.json b/backend/src/main/resources/dev-realm.json
index f8554550c..df3b072cf 100644
--- a/backend/src/main/resources/dev-realm.json
+++ b/backend/src/main/resources/dev-realm.json
@@ -39,12 +39,190 @@
"client": {
"realm-management": [
"view-users",
- "view-clients"
+ "view-clients",
+ "manage-users",
+ "manage-clients"
]
}
}
}
- ]
+ ],
+ "client": {
+ "realm-management": [
+ {
+ "name": "view-clients",
+ "description": "${role_view-clients}",
+ "composite": true,
+ "composites": {
+ "client": {
+ "realm-management": [
+ "query-clients"
+ ]
+ }
+ },
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "view-identity-providers",
+ "description": "${role_view-identity-providers}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "view-events",
+ "description": "${role_view-events}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "manage-clients",
+ "description": "${role_manage-clients}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "manage-identity-providers",
+ "description": "${role_manage-identity-providers}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "create-client",
+ "description": "${role_create-client}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "query-users",
+ "description": "${role_query-users}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "view-realm",
+ "description": "${role_view-realm}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "view-users",
+ "description": "${role_view-users}",
+ "composite": true,
+ "composites": {
+ "client": {
+ "realm-management": [
+ "query-users",
+ "query-groups"
+ ]
+ }
+ },
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "query-groups",
+ "description": "${role_query-groups}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "manage-users",
+ "description": "${role_manage-users}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "manage-events",
+ "description": "${role_manage-events}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "view-authorization",
+ "description": "${role_view-authorization}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "manage-realm",
+ "description": "${role_manage-realm}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "realm-admin",
+ "description": "${role_realm-admin}",
+ "composite": true,
+ "composites": {
+ "client": {
+ "realm-management": [
+ "view-clients",
+ "view-identity-providers",
+ "view-events",
+ "manage-clients",
+ "manage-identity-providers",
+ "create-client",
+ "view-users",
+ "query-users",
+ "view-realm",
+ "query-groups",
+ "manage-users",
+ "manage-events",
+ "view-authorization",
+ "manage-realm",
+ "query-realms",
+ "manage-authorization",
+ "query-clients",
+ "impersonation"
+ ]
+ }
+ },
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "manage-authorization",
+ "description": "${role_manage-authorization}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "query-realms",
+ "description": "${role_query-realms}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "impersonation",
+ "description": "${role_impersonation}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "query-clients",
+ "description": "${role_query-clients}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ }
+ ]
+ }
},
"users": [
{
@@ -185,6 +363,7 @@
"attributes": {
"pkce.code.challenge.method": "S256"
},
+ "directAccessGrantsEnabled": false,
"protocolMappers": [
{
"name": "realm roles",
@@ -199,18 +378,30 @@
}
},
{
- "name": "client roles",
+ "name": "aud",
"protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-client-role-mapper",
+ "protocolMapper": "oidc-audience-mapper",
"consentRequired": false,
"config": {
+ "included.client.audience": "cryptomatorhub",
+ "id.token.claim": "false",
"access.token.claim": "true",
- "claim.name": "resource_access.${client_id}.roles",
- "jsonType.label": "String",
- "multivalued": "true",
- "usermodel.clientRoleMapping.clientId": "cryptomatorhub"
+ "userinfo.token.claim": "false"
}
}
+ ],
+ "defaultClientScopes": [
+ "web-origins",
+ "phone",
+ "profile",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "acr",
+ "address",
+ "roles",
+ "offline_access",
+ "microprofile-jwt"
]
},
{
@@ -220,16 +411,140 @@
"name": "Cryptomator App",
"enabled": true,
"redirectUris": [
- "http://127.0.0.1/*"
+ "http://127.0.0.1/*",
+ "x-cipherduck-action:oauth"
+ ],
+ "webOrigins": [
+ "+"
+ ],
+ "bearerOnly": false,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {
+ "pkce.code.challenge.method": "S256"
+ },
+ "directAccessGrantsEnabled": false,
+ "protocolMappers": [
+ {
+ "name": "realm roles",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-realm-role-mapper",
+ "consentRequired": false,
+ "config": {
+ "access.token.claim": "true",
+ "claim.name": "realm_access.roles",
+ "jsonType.label": "String",
+ "multivalued": "true"
+ }
+ },
+ {
+ "name": "aud",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-audience-mapper",
+ "consentRequired": false,
+ "config": {
+ "included.client.audience": "cryptomator",
+ "id.token.claim": "false",
+ "access.token.claim": "true",
+ "userinfo.token.claim": "false"
+ }
+ }
+ ],
+ "defaultClientScopes": [
+ "web-origins",
+ "phone",
+ "profile",
+ "email"
],
+ "optionalClientScopes": [
+ "acr",
+ "address",
+ "offline_access",
+ "microprofile-jwt",
+ "roles"
+ ]
+ },
+ {
+ "id": "367e3049-23ee-4714-a7ed-75e61d027d02",
+ "clientId": "cryptomatorvaults",
+ "name": "Cryptomator Vaults (Token Exchange)",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "redirectUris": [],
"webOrigins": [
"+"
],
+ "notBefore": 0,
"bearerOnly": false,
+ "consentRequired": false,
+ "standardFlowEnabled": true,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": false,
+ "serviceAccountsEnabled": false,
+ "publicClient": true,
"frontchannelLogout": false,
"protocol": "openid-connect",
"attributes": {
+ "post.logout.redirect.uris": "+",
"pkce.code.challenge.method": "S256"
+ },
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": true,
+ "nodeReRegistrationTimeout": -1,
+ "defaultClientScopes": [],
+ "optionalClientScopes": []
+ },
+ {
+ "clientId": "realm-management",
+ "authorizationSettings": {
+ "allowRemoteResourceManagement": false,
+ "policyEnforcementMode": "ENFORCING",
+ "resources": [
+ {
+ "name": "client.resource.367e3049-23ee-4714-a7ed-75e61d027d02",
+ "type": "Client",
+ "ownerManagedAccess": false,
+ "attributes": {},
+ "_id": "67da8209-a6fb-438f-8ee7-f936b47aedf4",
+ "uris": [],
+ "scopes": [
+ {
+ "name": "token-exchange"
+ }
+ ]
+ }
+ ],
+ "policies": [
+ {
+ "name": "exchange",
+ "description": "",
+ "type": "client",
+ "logic": "POSITIVE",
+ "decisionStrategy": "UNANIMOUS",
+ "config": {
+ "clients": "[\"cryptomator\"]"
+ }
+ },
+ {
+ "name": "token-exchange.permission.client.367e3049-23ee-4714-a7ed-75e61d027d02",
+ "type": "scope",
+ "logic": "POSITIVE",
+ "decisionStrategy": "UNANIMOUS",
+ "config": {
+ "resources": "[\"client.resource.367e3049-23ee-4714-a7ed-75e61d027d02\"]",
+ "scopes": "[\"token-exchange\"]",
+ "applyPolicies": "[\"exchange\"]"
+ }
+ }
+ ],
+ "scopes": [
+ {
+ "name": "token-exchange"
+ }
+ ],
+ "decisionStrategy": "UNANIMOUS"
}
},
{
diff --git a/backend/src/main/resources/org/cryptomator/hub/flyway/V80__StorageProfile.sql b/backend/src/main/resources/org/cryptomator/hub/flyway/V80__StorageProfile.sql
new file mode 100644
index 000000000..7c26924f1
--- /dev/null
+++ b/backend/src/main/resources/org/cryptomator/hub/flyway/V80__StorageProfile.sql
@@ -0,0 +1,55 @@
+CREATE TABLE "storage_profile"
+(
+ "id" UUID NOT NULL,
+ "name" VARCHAR,
+
+ -- (3) client profile
+ -- (3a) client profile attributes
+ "protocol" VARCHAR NOT NULL,
+ "archived" bool NOT NULL,
+
+ CONSTRAINT "STORAGE_PROFILE_PK" PRIMARY KEY ("id")
+);
+
+CREATE TABLE "storage_profile_s3"
+(
+ "id" UUID NOT NULL,
+
+ -- (1) bucket creation, template upload and client profile
+ "scheme" VARCHAR,
+ "hostname" VARCHAR,
+ "port" INT4,
+ "withPathStyleAccessEnabled"
+ bool NOT NULL,
+ "storageClass" VARCHAR NOT NULL,
+
+
+ CONSTRAINT "STORAGE_PROFILE_S3_PK" PRIMARY KEY ("id"),
+ CONSTRAINT "STORAGE_PROFILE_S3_FK_STORAGE_PROFILE" FOREIGN KEY ("id") REFERENCES "storage_profile" ("id") ON DELETE CASCADE,
+ CONSTRAINT "STORAGE_PROFILE_S3_CHK_STORAGE_CLASS" CHECK ("storageClass" = 'STANDARD' OR "storageClass" = 'INTELLIGENT_TIERING' OR "storageClass" = 'STANDARD_IA' OR "storageClass" = 'ONEZONE_IA' OR "storageClass" = 'GLACIER' OR "storageClass" = 'GLACIER_IR' OR "storageClass" = 'DEEP_ARCHIVE')
+);
+
+CREATE TABLE "storage_profile_s3_sts"
+(
+ "id" UUID NOT NULL,
+
+ -- (2) bucket creation only (i.e. STS-case)
+ "region" VARCHAR,
+ "regions" text[],
+ "bucketPrefix" VARCHAR NOT NULL,
+ "stsRoleArnClient" VARCHAR NOT NULL,
+ "stsRoleArnHub" VARCHAR NOT NULL,
+ "stsEndpoint" VARCHAR,
+ "bucketVersioning" bool NOT NULL,
+ "bucketAcceleration" bool,
+ "bucketEncryption" VARCHAR NOT NULL,
+
+ -- (3b) client profile custom properties
+ "stsRoleArn" VARCHAR NOT NULL,
+ "stsRoleArn2" VARCHAR,
+ "stsDurationSeconds" INT4,
+
+ CONSTRAINT "STORAGE_PROFILE_S3_STS_PK" PRIMARY KEY ("id"),
+ CONSTRAINT "STORAGE_PROFILE_S3_STS_FK_STORAGE_PROFILE_S3" FOREIGN KEY ("id") REFERENCES "storage_profile_s3" ("id") ON DELETE CASCADE,
+ CONSTRAINT "STORAGE_PROFILE_S3_CHK_BUCKET_ENCRYPTION" CHECK ("bucketEncryption" = 'NONE' OR "bucketEncryption" = 'SSE_AES256' OR "bucketEncryption" = 'SSE_KMS_DEFAULT')
+);
\ No newline at end of file
diff --git a/frontend/index.html b/frontend/index.html
index f71ff158c..25685e754 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -5,7 +5,7 @@
- Cryptomator Hub
+ Cipherduck
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 4876c9edf..926575192 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -9,6 +9,8 @@
"version": "1.4.0",
"license": "AGPL-3.0-or-later",
"dependencies": {
+ "@aws-sdk/client-s3": "^3.454.0",
+ "@aws-sdk/client-sts": "^3.421.0",
"@headlessui/tailwindcss": "^0.2.0",
"@headlessui/vue": "^1.7.20",
"@heroicons/vue": "^2.1.3",
@@ -88,6 +90,886 @@
"node": ">=6.0.0"
}
},
+ "node_modules/@aws-crypto/crc32": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-3.0.0.tgz",
+ "integrity": "sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==",
+ "dependencies": {
+ "@aws-crypto/util": "^3.0.0",
+ "@aws-sdk/types": "^3.222.0",
+ "tslib": "^1.11.1"
+ }
+ },
+ "node_modules/@aws-crypto/crc32c": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-3.0.0.tgz",
+ "integrity": "sha512-ENNPPManmnVJ4BTXlOjAgD7URidbAznURqD0KvfREyc4o20DPYdEldU1f5cQ7Jbj0CJJSPaMIk/9ZshdB3210w==",
+ "dependencies": {
+ "@aws-crypto/util": "^3.0.0",
+ "@aws-sdk/types": "^3.222.0",
+ "tslib": "^1.11.1"
+ }
+ },
+ "node_modules/@aws-crypto/ie11-detection": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-3.0.0.tgz",
+ "integrity": "sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==",
+ "dependencies": {
+ "tslib": "^1.11.1"
+ }
+ },
+ "node_modules/@aws-crypto/sha1-browser": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-3.0.0.tgz",
+ "integrity": "sha512-NJth5c997GLHs6nOYTzFKTbYdMNA6/1XlKVgnZoaZcQ7z7UJlOgj2JdbHE8tiYLS3fzXNCguct77SPGat2raSw==",
+ "dependencies": {
+ "@aws-crypto/ie11-detection": "^3.0.0",
+ "@aws-crypto/supports-web-crypto": "^3.0.0",
+ "@aws-crypto/util": "^3.0.0",
+ "@aws-sdk/types": "^3.222.0",
+ "@aws-sdk/util-locate-window": "^3.0.0",
+ "@aws-sdk/util-utf8-browser": "^3.0.0",
+ "tslib": "^1.11.1"
+ }
+ },
+ "node_modules/@aws-crypto/sha256-browser": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-3.0.0.tgz",
+ "integrity": "sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==",
+ "dependencies": {
+ "@aws-crypto/ie11-detection": "^3.0.0",
+ "@aws-crypto/sha256-js": "^3.0.0",
+ "@aws-crypto/supports-web-crypto": "^3.0.0",
+ "@aws-crypto/util": "^3.0.0",
+ "@aws-sdk/types": "^3.222.0",
+ "@aws-sdk/util-locate-window": "^3.0.0",
+ "@aws-sdk/util-utf8-browser": "^3.0.0",
+ "tslib": "^1.11.1"
+ }
+ },
+ "node_modules/@aws-crypto/sha256-js": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-3.0.0.tgz",
+ "integrity": "sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==",
+ "dependencies": {
+ "@aws-crypto/util": "^3.0.0",
+ "@aws-sdk/types": "^3.222.0",
+ "tslib": "^1.11.1"
+ }
+ },
+ "node_modules/@aws-crypto/supports-web-crypto": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-3.0.0.tgz",
+ "integrity": "sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==",
+ "dependencies": {
+ "tslib": "^1.11.1"
+ }
+ },
+ "node_modules/@aws-crypto/util": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-3.0.0.tgz",
+ "integrity": "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==",
+ "dependencies": {
+ "@aws-sdk/types": "^3.222.0",
+ "@aws-sdk/util-utf8-browser": "^3.0.0",
+ "tslib": "^1.11.1"
+ }
+ },
+ "node_modules/@aws-sdk/client-s3": {
+ "version": "3.454.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.454.0.tgz",
+ "integrity": "sha512-vBx4iB1c4mEz+twMnl6angC1/IpnmXaT8L6Kl9uNiurFsb6N4tCyrJ24kECdsOSTM7ePXvE1fKst8zAh6MW5ZA==",
+ "dependencies": {
+ "@aws-crypto/sha1-browser": "3.0.0",
+ "@aws-crypto/sha256-browser": "3.0.0",
+ "@aws-crypto/sha256-js": "3.0.0",
+ "@aws-sdk/client-sts": "3.454.0",
+ "@aws-sdk/core": "3.451.0",
+ "@aws-sdk/credential-provider-node": "3.451.0",
+ "@aws-sdk/middleware-bucket-endpoint": "3.451.0",
+ "@aws-sdk/middleware-expect-continue": "3.451.0",
+ "@aws-sdk/middleware-flexible-checksums": "3.451.0",
+ "@aws-sdk/middleware-host-header": "3.451.0",
+ "@aws-sdk/middleware-location-constraint": "3.451.0",
+ "@aws-sdk/middleware-logger": "3.451.0",
+ "@aws-sdk/middleware-recursion-detection": "3.451.0",
+ "@aws-sdk/middleware-sdk-s3": "3.451.0",
+ "@aws-sdk/middleware-signing": "3.451.0",
+ "@aws-sdk/middleware-ssec": "3.451.0",
+ "@aws-sdk/middleware-user-agent": "3.451.0",
+ "@aws-sdk/region-config-resolver": "3.451.0",
+ "@aws-sdk/signature-v4-multi-region": "3.451.0",
+ "@aws-sdk/types": "3.451.0",
+ "@aws-sdk/util-endpoints": "3.451.0",
+ "@aws-sdk/util-user-agent-browser": "3.451.0",
+ "@aws-sdk/util-user-agent-node": "3.451.0",
+ "@aws-sdk/xml-builder": "3.310.0",
+ "@smithy/config-resolver": "^2.0.18",
+ "@smithy/eventstream-serde-browser": "^2.0.13",
+ "@smithy/eventstream-serde-config-resolver": "^2.0.13",
+ "@smithy/eventstream-serde-node": "^2.0.13",
+ "@smithy/fetch-http-handler": "^2.2.6",
+ "@smithy/hash-blob-browser": "^2.0.14",
+ "@smithy/hash-node": "^2.0.15",
+ "@smithy/hash-stream-node": "^2.0.15",
+ "@smithy/invalid-dependency": "^2.0.13",
+ "@smithy/md5-js": "^2.0.15",
+ "@smithy/middleware-content-length": "^2.0.15",
+ "@smithy/middleware-endpoint": "^2.2.0",
+ "@smithy/middleware-retry": "^2.0.20",
+ "@smithy/middleware-serde": "^2.0.13",
+ "@smithy/middleware-stack": "^2.0.7",
+ "@smithy/node-config-provider": "^2.1.5",
+ "@smithy/node-http-handler": "^2.1.9",
+ "@smithy/protocol-http": "^3.0.9",
+ "@smithy/smithy-client": "^2.1.15",
+ "@smithy/types": "^2.5.0",
+ "@smithy/url-parser": "^2.0.13",
+ "@smithy/util-base64": "^2.0.1",
+ "@smithy/util-body-length-browser": "^2.0.0",
+ "@smithy/util-body-length-node": "^2.1.0",
+ "@smithy/util-defaults-mode-browser": "^2.0.19",
+ "@smithy/util-defaults-mode-node": "^2.0.25",
+ "@smithy/util-endpoints": "^1.0.4",
+ "@smithy/util-retry": "^2.0.6",
+ "@smithy/util-stream": "^2.0.20",
+ "@smithy/util-utf8": "^2.0.2",
+ "@smithy/util-waiter": "^2.0.13",
+ "fast-xml-parser": "4.2.5",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/client-s3/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@aws-sdk/client-sso": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.451.0.tgz",
+ "integrity": "sha512-KkYSke3Pdv3MfVH/5fT528+MKjMyPKlcLcd4zQb0x6/7Bl7EHrPh1JZYjzPLHelb+UY5X0qN8+cb8iSu1eiwIQ==",
+ "dependencies": {
+ "@aws-crypto/sha256-browser": "3.0.0",
+ "@aws-crypto/sha256-js": "3.0.0",
+ "@aws-sdk/core": "3.451.0",
+ "@aws-sdk/middleware-host-header": "3.451.0",
+ "@aws-sdk/middleware-logger": "3.451.0",
+ "@aws-sdk/middleware-recursion-detection": "3.451.0",
+ "@aws-sdk/middleware-user-agent": "3.451.0",
+ "@aws-sdk/region-config-resolver": "3.451.0",
+ "@aws-sdk/types": "3.451.0",
+ "@aws-sdk/util-endpoints": "3.451.0",
+ "@aws-sdk/util-user-agent-browser": "3.451.0",
+ "@aws-sdk/util-user-agent-node": "3.451.0",
+ "@smithy/config-resolver": "^2.0.18",
+ "@smithy/fetch-http-handler": "^2.2.6",
+ "@smithy/hash-node": "^2.0.15",
+ "@smithy/invalid-dependency": "^2.0.13",
+ "@smithy/middleware-content-length": "^2.0.15",
+ "@smithy/middleware-endpoint": "^2.2.0",
+ "@smithy/middleware-retry": "^2.0.20",
+ "@smithy/middleware-serde": "^2.0.13",
+ "@smithy/middleware-stack": "^2.0.7",
+ "@smithy/node-config-provider": "^2.1.5",
+ "@smithy/node-http-handler": "^2.1.9",
+ "@smithy/protocol-http": "^3.0.9",
+ "@smithy/smithy-client": "^2.1.15",
+ "@smithy/types": "^2.5.0",
+ "@smithy/url-parser": "^2.0.13",
+ "@smithy/util-base64": "^2.0.1",
+ "@smithy/util-body-length-browser": "^2.0.0",
+ "@smithy/util-body-length-node": "^2.1.0",
+ "@smithy/util-defaults-mode-browser": "^2.0.19",
+ "@smithy/util-defaults-mode-node": "^2.0.25",
+ "@smithy/util-endpoints": "^1.0.4",
+ "@smithy/util-retry": "^2.0.6",
+ "@smithy/util-utf8": "^2.0.2",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/client-sso/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@aws-sdk/client-sts": {
+ "version": "3.454.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.454.0.tgz",
+ "integrity": "sha512-0fDvr8WeB6IYO8BUCzcivWmahgGl/zDbaYfakzGnt4mrl5ztYaXE875WI6b7+oFcKMRvN+KLvwu5TtyFuNY+GQ==",
+ "dependencies": {
+ "@aws-crypto/sha256-browser": "3.0.0",
+ "@aws-crypto/sha256-js": "3.0.0",
+ "@aws-sdk/core": "3.451.0",
+ "@aws-sdk/credential-provider-node": "3.451.0",
+ "@aws-sdk/middleware-host-header": "3.451.0",
+ "@aws-sdk/middleware-logger": "3.451.0",
+ "@aws-sdk/middleware-recursion-detection": "3.451.0",
+ "@aws-sdk/middleware-sdk-sts": "3.451.0",
+ "@aws-sdk/middleware-signing": "3.451.0",
+ "@aws-sdk/middleware-user-agent": "3.451.0",
+ "@aws-sdk/region-config-resolver": "3.451.0",
+ "@aws-sdk/types": "3.451.0",
+ "@aws-sdk/util-endpoints": "3.451.0",
+ "@aws-sdk/util-user-agent-browser": "3.451.0",
+ "@aws-sdk/util-user-agent-node": "3.451.0",
+ "@smithy/config-resolver": "^2.0.18",
+ "@smithy/fetch-http-handler": "^2.2.6",
+ "@smithy/hash-node": "^2.0.15",
+ "@smithy/invalid-dependency": "^2.0.13",
+ "@smithy/middleware-content-length": "^2.0.15",
+ "@smithy/middleware-endpoint": "^2.2.0",
+ "@smithy/middleware-retry": "^2.0.20",
+ "@smithy/middleware-serde": "^2.0.13",
+ "@smithy/middleware-stack": "^2.0.7",
+ "@smithy/node-config-provider": "^2.1.5",
+ "@smithy/node-http-handler": "^2.1.9",
+ "@smithy/protocol-http": "^3.0.9",
+ "@smithy/smithy-client": "^2.1.15",
+ "@smithy/types": "^2.5.0",
+ "@smithy/url-parser": "^2.0.13",
+ "@smithy/util-base64": "^2.0.1",
+ "@smithy/util-body-length-browser": "^2.0.0",
+ "@smithy/util-body-length-node": "^2.1.0",
+ "@smithy/util-defaults-mode-browser": "^2.0.19",
+ "@smithy/util-defaults-mode-node": "^2.0.25",
+ "@smithy/util-endpoints": "^1.0.4",
+ "@smithy/util-retry": "^2.0.6",
+ "@smithy/util-utf8": "^2.0.2",
+ "fast-xml-parser": "4.2.5",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/client-sts/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@aws-sdk/core": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.451.0.tgz",
+ "integrity": "sha512-SamWW2zHEf1ZKe3j1w0Piauryl8BQIlej0TBS18A4ACzhjhWXhCs13bO1S88LvPR5mBFXok3XOT6zPOnKDFktw==",
+ "dependencies": {
+ "@smithy/smithy-client": "^2.1.15",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/core/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@aws-sdk/credential-provider-env": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.451.0.tgz",
+ "integrity": "sha512-9dAav7DcRgaF7xCJEQR5ER9ErXxnu/tdnVJ+UPmb1NPeIZdESv1A3lxFDEq1Fs8c4/lzAj9BpshGyJVIZwZDKg==",
+ "dependencies": {
+ "@aws-sdk/types": "3.451.0",
+ "@smithy/property-provider": "^2.0.0",
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/credential-provider-env/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@aws-sdk/credential-provider-ini": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.451.0.tgz",
+ "integrity": "sha512-TySt64Ci5/ZbqFw1F9Z0FIGvYx5JSC9e6gqDnizIYd8eMnn8wFRUscRrD7pIHKfrhvVKN5h0GdYovmMO/FMCBw==",
+ "dependencies": {
+ "@aws-sdk/credential-provider-env": "3.451.0",
+ "@aws-sdk/credential-provider-process": "3.451.0",
+ "@aws-sdk/credential-provider-sso": "3.451.0",
+ "@aws-sdk/credential-provider-web-identity": "3.451.0",
+ "@aws-sdk/types": "3.451.0",
+ "@smithy/credential-provider-imds": "^2.0.0",
+ "@smithy/property-provider": "^2.0.0",
+ "@smithy/shared-ini-file-loader": "^2.0.6",
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/credential-provider-ini/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@aws-sdk/credential-provider-node": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.451.0.tgz",
+ "integrity": "sha512-AEwM1WPyxUdKrKyUsKyFqqRFGU70e4qlDyrtBxJnSU9NRLZI8tfEZ67bN7fHSxBUBODgDXpMSlSvJiBLh5/3pw==",
+ "dependencies": {
+ "@aws-sdk/credential-provider-env": "3.451.0",
+ "@aws-sdk/credential-provider-ini": "3.451.0",
+ "@aws-sdk/credential-provider-process": "3.451.0",
+ "@aws-sdk/credential-provider-sso": "3.451.0",
+ "@aws-sdk/credential-provider-web-identity": "3.451.0",
+ "@aws-sdk/types": "3.451.0",
+ "@smithy/credential-provider-imds": "^2.0.0",
+ "@smithy/property-provider": "^2.0.0",
+ "@smithy/shared-ini-file-loader": "^2.0.6",
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/credential-provider-node/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@aws-sdk/credential-provider-process": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.451.0.tgz",
+ "integrity": "sha512-HQywSdKeD5PErcLLnZfSyCJO+6T+ZyzF+Lm/QgscSC+CbSUSIPi//s15qhBRVely/3KBV6AywxwNH+5eYgt4lQ==",
+ "dependencies": {
+ "@aws-sdk/types": "3.451.0",
+ "@smithy/property-provider": "^2.0.0",
+ "@smithy/shared-ini-file-loader": "^2.0.6",
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/credential-provider-process/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@aws-sdk/credential-provider-sso": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.451.0.tgz",
+ "integrity": "sha512-Usm/N51+unOt8ID4HnQzxIjUJDrkAQ1vyTOC0gSEEJ7h64NSSPGD5yhN7il5WcErtRd3EEtT1a8/GTC5TdBctg==",
+ "dependencies": {
+ "@aws-sdk/client-sso": "3.451.0",
+ "@aws-sdk/token-providers": "3.451.0",
+ "@aws-sdk/types": "3.451.0",
+ "@smithy/property-provider": "^2.0.0",
+ "@smithy/shared-ini-file-loader": "^2.0.6",
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/credential-provider-sso/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@aws-sdk/credential-provider-web-identity": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.451.0.tgz",
+ "integrity": "sha512-Xtg3Qw65EfDjWNG7o2xD6sEmumPfsy3WDGjk2phEzVg8s7hcZGxf5wYwe6UY7RJvlEKrU0rFA+AMn6Hfj5oOzg==",
+ "dependencies": {
+ "@aws-sdk/types": "3.451.0",
+ "@smithy/property-provider": "^2.0.0",
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@aws-sdk/middleware-bucket-endpoint": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.451.0.tgz",
+ "integrity": "sha512-KWyZ1JGnYz2QbHuJtYTP1BVnMOfVopR8rP8dTinVb/JR5HfAYz4imICJlJUbOYRjN7wpA3PrRI8dNRjrSBjWJg==",
+ "dependencies": {
+ "@aws-sdk/types": "3.451.0",
+ "@aws-sdk/util-arn-parser": "3.310.0",
+ "@smithy/node-config-provider": "^2.1.5",
+ "@smithy/protocol-http": "^3.0.9",
+ "@smithy/types": "^2.5.0",
+ "@smithy/util-config-provider": "^2.0.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/middleware-bucket-endpoint/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@aws-sdk/middleware-expect-continue": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.451.0.tgz",
+ "integrity": "sha512-vwG8o2Uk6biLDlOZnqXemsO4dS2HvrprUdxyouwu6hlzLFskg8nL122butn19JqXJKgcVLuSSLzT+xwqBWy2Rg==",
+ "dependencies": {
+ "@aws-sdk/types": "3.451.0",
+ "@smithy/protocol-http": "^3.0.9",
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/middleware-expect-continue/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@aws-sdk/middleware-flexible-checksums": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.451.0.tgz",
+ "integrity": "sha512-eOkpcC2zgAvqs1w7Yp5nsk9LBIj6qLU5kaZuZEBOiFbNKIrTnPo6dQuhgvDcKHD6Y5W/cUjSBiFMs/ROb5aoug==",
+ "dependencies": {
+ "@aws-crypto/crc32": "3.0.0",
+ "@aws-crypto/crc32c": "3.0.0",
+ "@aws-sdk/types": "3.451.0",
+ "@smithy/is-array-buffer": "^2.0.0",
+ "@smithy/protocol-http": "^3.0.9",
+ "@smithy/types": "^2.5.0",
+ "@smithy/util-utf8": "^2.0.2",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@aws-sdk/middleware-host-header": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.451.0.tgz",
+ "integrity": "sha512-j8a5jAfhWmsK99i2k8oR8zzQgXrsJtgrLxc3js6U+525mcZytoiDndkWTmD5fjJ1byU1U2E5TaPq+QJeDip05Q==",
+ "dependencies": {
+ "@aws-sdk/types": "3.451.0",
+ "@smithy/protocol-http": "^3.0.9",
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/middleware-host-header/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@aws-sdk/middleware-location-constraint": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.451.0.tgz",
+ "integrity": "sha512-R4U2G7mybP0BMiQBJWTcB47g49F4PSXTiCsvMDp5WOEhpWvGQuO1ZIhTxCl5s5lgTSne063Os8W6KSdK2yG2TQ==",
+ "dependencies": {
+ "@aws-sdk/types": "3.451.0",
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/middleware-location-constraint/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@aws-sdk/middleware-logger": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.451.0.tgz",
+ "integrity": "sha512-0kHrYEyVeB2QBfP6TfbI240aRtatLZtcErJbhpiNUb+CQPgEL3crIjgVE8yYiJumZ7f0jyjo8HLPkwD1/2APaw==",
+ "dependencies": {
+ "@aws-sdk/types": "3.451.0",
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/middleware-logger/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@aws-sdk/middleware-recursion-detection": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.451.0.tgz",
+ "integrity": "sha512-J6jL6gJ7orjHGM70KDRcCP7so/J2SnkN4vZ9YRLTeeZY6zvBuHDjX8GCIgSqPn/nXFXckZO8XSnA7u6+3TAT0w==",
+ "dependencies": {
+ "@aws-sdk/types": "3.451.0",
+ "@smithy/protocol-http": "^3.0.9",
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/middleware-recursion-detection/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@aws-sdk/middleware-sdk-s3": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.451.0.tgz",
+ "integrity": "sha512-XF4Cw8HrYUwGLKOqKtWs6ss1WXoxvQUcgGLACGSqn9a0p51446NiS5671x7qJUsfBuygdKlIKcOc8pPr9a+5Ow==",
+ "dependencies": {
+ "@aws-sdk/types": "3.451.0",
+ "@aws-sdk/util-arn-parser": "3.310.0",
+ "@smithy/protocol-http": "^3.0.9",
+ "@smithy/smithy-client": "^2.1.15",
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@aws-sdk/middleware-sdk-sts": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.451.0.tgz",
+ "integrity": "sha512-UJ6UfVUEgp0KIztxpAeelPXI5MLj9wUtUCqYeIMP7C1ZhoEMNm3G39VLkGN43dNhBf1LqjsV9jkKMZbVfYXuwg==",
+ "dependencies": {
+ "@aws-sdk/middleware-signing": "3.451.0",
+ "@aws-sdk/types": "3.451.0",
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/middleware-sdk-sts/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@aws-sdk/middleware-signing": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.451.0.tgz",
+ "integrity": "sha512-s5ZlcIoLNg1Huj4Qp06iKniE8nJt/Pj1B/fjhWc6cCPCM7XJYUCejCnRh6C5ZJoBEYodjuwZBejPc1Wh3j+znA==",
+ "dependencies": {
+ "@aws-sdk/types": "3.451.0",
+ "@smithy/property-provider": "^2.0.0",
+ "@smithy/protocol-http": "^3.0.9",
+ "@smithy/signature-v4": "^2.0.0",
+ "@smithy/types": "^2.5.0",
+ "@smithy/util-middleware": "^2.0.6",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/middleware-signing/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@aws-sdk/middleware-ssec": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.451.0.tgz",
+ "integrity": "sha512-hDkeBUiRsvuDbvsPha0/uJHE680WDzjAOoE6ZnLBoWsw7ry+Bw1ULMj0sCmpBVrQ7Gpivi/6zbezhClVmt3ITw==",
+ "dependencies": {
+ "@aws-sdk/types": "3.451.0",
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/middleware-ssec/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@aws-sdk/middleware-user-agent": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.451.0.tgz",
+ "integrity": "sha512-8NM/0JiKLNvT9wtAQVl1DFW0cEO7OvZyLSUBLNLTHqyvOZxKaZ8YFk7d8PL6l76LeUKRxq4NMxfZQlUIRe0eSA==",
+ "dependencies": {
+ "@aws-sdk/types": "3.451.0",
+ "@aws-sdk/util-endpoints": "3.451.0",
+ "@smithy/protocol-http": "^3.0.9",
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/middleware-user-agent/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@aws-sdk/region-config-resolver": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.451.0.tgz",
+ "integrity": "sha512-3iMf4OwzrFb4tAAmoROXaiORUk2FvSejnHIw/XHvf/jjR4EqGGF95NZP/n/MeFZMizJWVssrwS412GmoEyoqhg==",
+ "dependencies": {
+ "@smithy/node-config-provider": "^2.1.5",
+ "@smithy/types": "^2.5.0",
+ "@smithy/util-config-provider": "^2.0.0",
+ "@smithy/util-middleware": "^2.0.6",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/region-config-resolver/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@aws-sdk/signature-v4-multi-region": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.451.0.tgz",
+ "integrity": "sha512-qQKY7/txeNUTLyRL3WxUWEwaZ5sf76EIZgu9kLaR96cAYSxwQi/qQB3ijbfD6u7sJIA8aROMxeYK0VmRsQg0CA==",
+ "dependencies": {
+ "@aws-sdk/types": "3.451.0",
+ "@smithy/protocol-http": "^3.0.9",
+ "@smithy/signature-v4": "^2.0.0",
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/signature-v4-multi-region/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@aws-sdk/token-providers": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.451.0.tgz",
+ "integrity": "sha512-ij1L5iUbn6CwxVOT1PG4NFjsrsKN9c4N1YEM0lkl6DwmaNOscjLKGSNyj9M118vSWsOs1ZDbTwtj++h0O/BWrQ==",
+ "dependencies": {
+ "@aws-crypto/sha256-browser": "3.0.0",
+ "@aws-crypto/sha256-js": "3.0.0",
+ "@aws-sdk/middleware-host-header": "3.451.0",
+ "@aws-sdk/middleware-logger": "3.451.0",
+ "@aws-sdk/middleware-recursion-detection": "3.451.0",
+ "@aws-sdk/middleware-user-agent": "3.451.0",
+ "@aws-sdk/region-config-resolver": "3.451.0",
+ "@aws-sdk/types": "3.451.0",
+ "@aws-sdk/util-endpoints": "3.451.0",
+ "@aws-sdk/util-user-agent-browser": "3.451.0",
+ "@aws-sdk/util-user-agent-node": "3.451.0",
+ "@smithy/config-resolver": "^2.0.18",
+ "@smithy/fetch-http-handler": "^2.2.6",
+ "@smithy/hash-node": "^2.0.15",
+ "@smithy/invalid-dependency": "^2.0.13",
+ "@smithy/middleware-content-length": "^2.0.15",
+ "@smithy/middleware-endpoint": "^2.2.0",
+ "@smithy/middleware-retry": "^2.0.20",
+ "@smithy/middleware-serde": "^2.0.13",
+ "@smithy/middleware-stack": "^2.0.7",
+ "@smithy/node-config-provider": "^2.1.5",
+ "@smithy/node-http-handler": "^2.1.9",
+ "@smithy/property-provider": "^2.0.0",
+ "@smithy/protocol-http": "^3.0.9",
+ "@smithy/shared-ini-file-loader": "^2.0.6",
+ "@smithy/smithy-client": "^2.1.15",
+ "@smithy/types": "^2.5.0",
+ "@smithy/url-parser": "^2.0.13",
+ "@smithy/util-base64": "^2.0.1",
+ "@smithy/util-body-length-browser": "^2.0.0",
+ "@smithy/util-body-length-node": "^2.1.0",
+ "@smithy/util-defaults-mode-browser": "^2.0.19",
+ "@smithy/util-defaults-mode-node": "^2.0.25",
+ "@smithy/util-endpoints": "^1.0.4",
+ "@smithy/util-retry": "^2.0.6",
+ "@smithy/util-utf8": "^2.0.2",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/token-providers/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@aws-sdk/types": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.451.0.tgz",
+ "integrity": "sha512-rhK+qeYwCIs+laJfWCcrYEjay2FR/9VABZJ2NRM89jV/fKqGVQR52E5DQqrI+oEIL5JHMhhnr4N4fyECMS35lw==",
+ "dependencies": {
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/types/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@aws-sdk/util-arn-parser": {
+ "version": "3.310.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.310.0.tgz",
+ "integrity": "sha512-jL8509owp/xB9+Or0pvn3Fe+b94qfklc2yPowZZIFAkFcCSIdkIglz18cPDWnYAcy9JGewpMS1COXKIUhZkJsA==",
+ "dependencies": {
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/util-arn-parser/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@aws-sdk/util-endpoints": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.451.0.tgz",
+ "integrity": "sha512-giqLGBTnRIcKkDqwU7+GQhKbtJ5Ku35cjGQIfMyOga6pwTBUbaK0xW1Sdd8sBQ1GhApscnChzI9o/R9x0368vw==",
+ "dependencies": {
+ "@aws-sdk/types": "3.451.0",
+ "@smithy/util-endpoints": "^1.0.4",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/util-endpoints/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@aws-sdk/util-locate-window": {
+ "version": "3.310.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.310.0.tgz",
+ "integrity": "sha512-qo2t/vBTnoXpjKxlsC2e1gBrRm80M3bId27r0BRB2VniSSe7bL1mmzM+/HFtujm0iAxtPM+aLEflLJlJeDPg0w==",
+ "dependencies": {
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/util-locate-window/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@aws-sdk/util-user-agent-browser": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.451.0.tgz",
+ "integrity": "sha512-Ws5mG3J0TQifH7OTcMrCTexo7HeSAc3cBgjfhS/ofzPUzVCtsyg0G7I6T7wl7vJJETix2Kst2cpOsxygPgPD9w==",
+ "dependencies": {
+ "@aws-sdk/types": "3.451.0",
+ "@smithy/types": "^2.5.0",
+ "bowser": "^2.11.0",
+ "tslib": "^2.5.0"
+ }
+ },
+ "node_modules/@aws-sdk/util-user-agent-browser/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@aws-sdk/util-user-agent-node": {
+ "version": "3.451.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.451.0.tgz",
+ "integrity": "sha512-TBzm6P+ql4mkGFAjPlO1CI+w3yUT+NulaiALjl/jNX/nnUp6HsJsVxJf4nVFQTG5KRV0iqMypcs7I3KIhH+LmA==",
+ "dependencies": {
+ "@aws-sdk/types": "3.451.0",
+ "@smithy/node-config-provider": "^2.1.5",
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "aws-crt": ">=1.0.0"
+ },
+ "peerDependenciesMeta": {
+ "aws-crt": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@aws-sdk/util-user-agent-node/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@aws-sdk/util-utf8-browser": {
+ "version": "3.259.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz",
+ "integrity": "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==",
+ "dependencies": {
+ "tslib": "^2.3.1"
+ }
+ },
+ "node_modules/@aws-sdk/util-utf8-browser/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@aws-sdk/xml-builder": {
+ "version": "3.310.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.310.0.tgz",
+ "integrity": "sha512-TqELu4mOuSIKQCqj63fGVs86Yh+vBx5nHRpWKNUNhB2nPTpfbziTs5c1X358be3peVWA4wPxW7Nt53KIg1tnNw==",
+ "dependencies": {
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/xml-builder/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
"node_modules/@babel/code-frame": {
"version": "7.24.2",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz",
@@ -1437,223 +2319,1079 @@
},
"engines": {
"node": ">=14.0.0"
- },
- "peerDependencies": {
- "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
- },
- "peerDependenciesMeta": {
- "rollup": {
- "optional": true
- }
+ },
+ "peerDependencies": {
+ "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.16.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.16.4.tgz",
+ "integrity": "sha512-GkhjAaQ8oUTOKE4g4gsZ0u8K/IHU1+2WQSgS1TwTcYvL+sjbaQjNHFXbOJ6kgqGHIO1DfUhI/Sphi9GkRT9K+Q==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.16.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.16.4.tgz",
+ "integrity": "sha512-Bvm6D+NPbGMQOcxvS1zUl8H7DWlywSXsphAeOnVeiZLQ+0J6Is8T7SrjGTH29KtYkiY9vld8ZnpV3G2EPbom+w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.16.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.16.4.tgz",
+ "integrity": "sha512-i5d64MlnYBO9EkCOGe5vPR/EeDwjnKOGGdd7zKFhU5y8haKhQZTN2DgVtpODDMxUr4t2K90wTUJg7ilgND6bXw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.16.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.16.4.tgz",
+ "integrity": "sha512-WZupV1+CdUYehaZqjaFTClJI72fjJEgTXdf4NbW69I9XyvdmztUExBtcI2yIIU6hJtYvtwS6pkTkHJz+k08mAQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.16.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.16.4.tgz",
+ "integrity": "sha512-ADm/xt86JUnmAfA9mBqFcRp//RVRt1ohGOYF6yL+IFCYqOBNwy5lbEK05xTsEoJq+/tJzg8ICUtS82WinJRuIw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.16.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.16.4.tgz",
+ "integrity": "sha512-tJfJaXPiFAG+Jn3cutp7mCs1ePltuAgRqdDZrzb1aeE3TktWWJ+g7xK9SNlaSUFw6IU4QgOxAY4rA+wZUT5Wfg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.16.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.16.4.tgz",
+ "integrity": "sha512-7dy1BzQkgYlUTapDTvK997cgi0Orh5Iu7JlZVBy1MBURk7/HSbHkzRnXZa19ozy+wwD8/SlpJnOOckuNZtJR9w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.16.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.16.4.tgz",
+ "integrity": "sha512-zsFwdUw5XLD1gQe0aoU2HVceI6NEW7q7m05wA46eUAyrkeNYExObfRFQcvA6zw8lfRc5BHtan3tBpo+kqEOxmg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
+ "version": "4.16.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.16.4.tgz",
+ "integrity": "sha512-p8C3NnxXooRdNrdv6dBmRTddEapfESEUflpICDNKXpHvTjRRq1J82CbU5G3XfebIZyI3B0s074JHMWD36qOW6w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.16.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.16.4.tgz",
+ "integrity": "sha512-Lh/8ckoar4s4Id2foY7jNgitTOUQczwMWNYi+Mjt0eQ9LKhr6sK477REqQkmy8YHY3Ca3A2JJVdXnfb3Rrwkng==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.16.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.16.4.tgz",
+ "integrity": "sha512-1xwwn9ZCQYuqGmulGsTZoKrrn0z2fAur2ujE60QgyDpHmBbXbxLaQiEvzJWDrscRq43c8DnuHx3QorhMTZgisQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.16.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.16.4.tgz",
+ "integrity": "sha512-LuOGGKAJ7dfRtxVnO1i3qWc6N9sh0Em/8aZ3CezixSTM+E9Oq3OvTsvC4sm6wWjzpsIlOCnZjdluINKESflJLA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.16.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.16.4.tgz",
+ "integrity": "sha512-ch86i7KkJKkLybDP2AtySFTRi5fM3KXp0PnHocHuJMdZwu7BuyIKi35BE9guMlmTpwwBTB3ljHj9IQXnTCD0vA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.16.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.16.4.tgz",
+ "integrity": "sha512-Ma4PwyLfOWZWayfEsNQzTDBVW8PZ6TUUN1uFTBQbF2Chv/+sjenE86lpiEwj2FiviSmSZ4Ap4MaAfl1ciF4aSA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.16.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.16.4.tgz",
+ "integrity": "sha512-9m/ZDrQsdo/c06uOlP3W9G2ENRVzgzbSXmXHT4hwVaDQhYcRpi9bgBT0FTG9OhESxwK0WjQxYOSfv40cU+T69w==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.16.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.16.4.tgz",
+ "integrity": "sha512-YunpoOAyGLDseanENHmbFvQSfVL5BxW3k7hhy0eN4rb3gS/ct75dVD0EXOWIqFT/nE8XYW6LP6vz6ctKRi0k9A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@smithy/abort-controller": {
+ "version": "2.0.13",
+ "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-2.0.13.tgz",
+ "integrity": "sha512-eeOPD+GF9BzF/Mjy3PICLePx4l0f3rG/nQegQHRLTloN5p1lSJJNZsyn+FzDnW8P2AduragZqJdtKNCxXozB1Q==",
+ "dependencies": {
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/abort-controller/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@smithy/chunked-blob-reader": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-2.0.0.tgz",
+ "integrity": "sha512-k+J4GHJsMSAIQPChGBrjEmGS+WbPonCXesoqP9fynIqjn7rdOThdH8FAeCmokP9mxTYKQAKoHCLPzNlm6gh7Wg==",
+ "dependencies": {
+ "tslib": "^2.5.0"
+ }
+ },
+ "node_modules/@smithy/chunked-blob-reader-native": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-2.0.1.tgz",
+ "integrity": "sha512-N2oCZRglhWKm7iMBu7S6wDzXirjAofi7tAd26cxmgibRYOBS4D3hGfmkwCpHdASZzwZDD8rluh0Rcqw1JeZDRw==",
+ "dependencies": {
+ "@smithy/util-base64": "^2.0.1",
+ "tslib": "^2.5.0"
+ }
+ },
+ "node_modules/@smithy/chunked-blob-reader-native/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@smithy/chunked-blob-reader/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@smithy/config-resolver": {
+ "version": "2.0.18",
+ "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-2.0.18.tgz",
+ "integrity": "sha512-761sJSgNbvsqcsKW6/WZbrZr4H+0Vp/QKKqwyrxCPwD8BsiPEXNHyYnqNgaeK9xRWYswjon0Uxbpe3DWQo0j/g==",
+ "dependencies": {
+ "@smithy/node-config-provider": "^2.1.5",
+ "@smithy/types": "^2.5.0",
+ "@smithy/util-config-provider": "^2.0.0",
+ "@smithy/util-middleware": "^2.0.6",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/config-resolver/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@smithy/credential-provider-imds": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-2.1.1.tgz",
+ "integrity": "sha512-gw5G3FjWC6sNz8zpOJgPpH5HGKrpoVFQpToNAwLwJVyI/LJ2jDJRjSKEsM6XI25aRpYjMSE/Qptxx305gN1vHw==",
+ "dependencies": {
+ "@smithy/node-config-provider": "^2.1.5",
+ "@smithy/property-provider": "^2.0.14",
+ "@smithy/types": "^2.5.0",
+ "@smithy/url-parser": "^2.0.13",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/credential-provider-imds/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@smithy/eventstream-codec": {
+ "version": "2.0.13",
+ "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-2.0.13.tgz",
+ "integrity": "sha512-CExbelIYp+DxAHG8RIs0l9QL7ElqhG4ym9BNoSpkPa4ptBQfzJdep3LbOSVJIE2VUdBAeObdeL6EDB3Jo85n3g==",
+ "dependencies": {
+ "@aws-crypto/crc32": "3.0.0",
+ "@smithy/types": "^2.5.0",
+ "@smithy/util-hex-encoding": "^2.0.0",
+ "tslib": "^2.5.0"
+ }
+ },
+ "node_modules/@smithy/eventstream-codec/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@smithy/eventstream-serde-browser": {
+ "version": "2.0.13",
+ "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-2.0.13.tgz",
+ "integrity": "sha512-OJ/2g/VxkzA+mYZxV102oX3CsiE+igTSmqq/ir3oEVG2kSIdRC00ryttj/lmL14W06ExNi0ysmfLxQkL8XrAZQ==",
+ "dependencies": {
+ "@smithy/eventstream-serde-universal": "^2.0.13",
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/eventstream-serde-browser/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@smithy/eventstream-serde-config-resolver": {
+ "version": "2.0.13",
+ "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-2.0.13.tgz",
+ "integrity": "sha512-2BI1CbnYuEvAYoWSeWJtPNygbIKiWeSLxCmDLnyM6wQV32Of7VptiQlaFXPxXp4zqn/rs3ocZ/T29rxE4s4Gsg==",
+ "dependencies": {
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/eventstream-serde-config-resolver/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@smithy/eventstream-serde-node": {
+ "version": "2.0.13",
+ "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-2.0.13.tgz",
+ "integrity": "sha512-7NbFwPafb924elFxCBDvm48jy/DeSrpFbFQN0uN2ThuY5HrEeubikS0t7WMva4Z4EnRoivpbuT0scb9vUIJKoA==",
+ "dependencies": {
+ "@smithy/eventstream-serde-universal": "^2.0.13",
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/eventstream-serde-node/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@smithy/eventstream-serde-universal": {
+ "version": "2.0.13",
+ "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-2.0.13.tgz",
+ "integrity": "sha512-j0yFd5UfftM+ia9dxLRbheJDCkCZBHpcEzCsPO8BxVOTbdcX/auVJCv6ov/yvpCKsf4Hv3mOqi0Is1YogM2g3Q==",
+ "dependencies": {
+ "@smithy/eventstream-codec": "^2.0.13",
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/eventstream-serde-universal/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@smithy/fetch-http-handler": {
+ "version": "2.2.6",
+ "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-2.2.6.tgz",
+ "integrity": "sha512-PStY3XO1Ksjwn3wMKye5U6m6zxXpXrXZYqLy/IeCbh3nM9QB3Jgw/B0PUSLUWKdXg4U8qgEu300e3ZoBvZLsDg==",
+ "dependencies": {
+ "@smithy/protocol-http": "^3.0.9",
+ "@smithy/querystring-builder": "^2.0.13",
+ "@smithy/types": "^2.5.0",
+ "@smithy/util-base64": "^2.0.1",
+ "tslib": "^2.5.0"
+ }
+ },
+ "node_modules/@smithy/fetch-http-handler/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@smithy/hash-blob-browser": {
+ "version": "2.0.14",
+ "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-2.0.14.tgz",
+ "integrity": "sha512-yWdghyPJIEqLYsaE7YVgd3YhM7jN4Pv6eJQvTomnMsz5K2qRBlpjUx3T9fKlElp1qdeQ7DNc3sAat4i9CUBO7Q==",
+ "dependencies": {
+ "@smithy/chunked-blob-reader": "^2.0.0",
+ "@smithy/chunked-blob-reader-native": "^2.0.1",
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ }
+ },
+ "node_modules/@smithy/hash-blob-browser/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@smithy/hash-node": {
+ "version": "2.0.15",
+ "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-2.0.15.tgz",
+ "integrity": "sha512-t/qjEJZu/G46A22PAk1k/IiJZT4ncRkG5GOCNWN9HPPy5rCcSZUbh7gwp7CGKgJJ7ATMMg+0Td7i9o1lQTwOfQ==",
+ "dependencies": {
+ "@smithy/types": "^2.5.0",
+ "@smithy/util-buffer-from": "^2.0.0",
+ "@smithy/util-utf8": "^2.0.2",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/hash-node/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@smithy/hash-stream-node": {
+ "version": "2.0.15",
+ "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-2.0.15.tgz",
+ "integrity": "sha512-ZZ6kC/pHt5Dc2goXIIyC8uA7A4GUMSzdCynAabnZ3CSSaV6ctP8mlvVkqjPph0O3XzHlx/80gdLrNqi1GDPUsA==",
+ "dependencies": {
+ "@smithy/types": "^2.5.0",
+ "@smithy/util-utf8": "^2.0.2",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/hash-stream-node/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@smithy/invalid-dependency": {
+ "version": "2.0.13",
+ "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-2.0.13.tgz",
+ "integrity": "sha512-XsGYhVhvEikX1Yz0kyIoLssJf2Rs6E0U2w2YuKdT4jSra5A/g8V2oLROC1s56NldbgnpesTYB2z55KCHHbKyjw==",
+ "dependencies": {
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ }
+ },
+ "node_modules/@smithy/invalid-dependency/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@smithy/is-array-buffer": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.0.0.tgz",
+ "integrity": "sha512-z3PjFjMyZNI98JFRJi/U0nGoLWMSJlDjAW4QUX2WNZLas5C0CmVV6LJ01JI0k90l7FvpmixjWxPFmENSClQ7ug==",
+ "dependencies": {
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/is-array-buffer/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@smithy/md5-js": {
+ "version": "2.0.15",
+ "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-2.0.15.tgz",
+ "integrity": "sha512-pAZaokib56XvhU0t/R9vAcr3L3bMhIakhF25X7EMSQ7LAURiLfce/tgON8I3x/dIbnZUyeRi8f2cx2azu6ATew==",
+ "dependencies": {
+ "@smithy/types": "^2.5.0",
+ "@smithy/util-utf8": "^2.0.2",
+ "tslib": "^2.5.0"
+ }
+ },
+ "node_modules/@smithy/md5-js/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@smithy/middleware-content-length": {
+ "version": "2.0.15",
+ "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-2.0.15.tgz",
+ "integrity": "sha512-xH4kRBw01gJgWiU+/mNTrnyFXeozpZHw39gLb3JKGsFDVmSrJZ8/tRqu27tU/ki1gKkxr2wApu+dEYjI3QwV1Q==",
+ "dependencies": {
+ "@smithy/protocol-http": "^3.0.9",
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/middleware-content-length/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@smithy/middleware-endpoint": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-2.2.0.tgz",
+ "integrity": "sha512-tddRmaig5URk2106PVMiNX6mc5BnKIKajHHDxb7K0J5MLdcuQluHMGnjkv18iY9s9O0tF+gAcPd/pDXA5L9DZw==",
+ "dependencies": {
+ "@smithy/middleware-serde": "^2.0.13",
+ "@smithy/node-config-provider": "^2.1.5",
+ "@smithy/shared-ini-file-loader": "^2.2.4",
+ "@smithy/types": "^2.5.0",
+ "@smithy/url-parser": "^2.0.13",
+ "@smithy/util-middleware": "^2.0.6",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/middleware-endpoint/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@smithy/middleware-retry": {
+ "version": "2.0.20",
+ "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-2.0.20.tgz",
+ "integrity": "sha512-X2yrF/SHDk2WDd8LflRNS955rlzQ9daz9UWSp15wW8KtzoTXg3bhHM78HbK1cjr48/FWERSJKh9AvRUUGlIawg==",
+ "dependencies": {
+ "@smithy/node-config-provider": "^2.1.5",
+ "@smithy/protocol-http": "^3.0.9",
+ "@smithy/service-error-classification": "^2.0.6",
+ "@smithy/types": "^2.5.0",
+ "@smithy/util-middleware": "^2.0.6",
+ "@smithy/util-retry": "^2.0.6",
+ "tslib": "^2.5.0",
+ "uuid": "^8.3.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/middleware-retry/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@smithy/middleware-serde": {
+ "version": "2.0.13",
+ "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-2.0.13.tgz",
+ "integrity": "sha512-tBGbeXw+XsE6pPr4UaXOh+UIcXARZeiA8bKJWxk2IjJcD1icVLhBSUQH9myCIZLNNzJIH36SDjUX8Wqk4xJCJg==",
+ "dependencies": {
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/middleware-serde/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@smithy/middleware-stack": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-2.0.7.tgz",
+ "integrity": "sha512-L1KLAAWkXbGx1t2jjCI/mDJ2dDNq+rp4/ifr/HcC6FHngxho5O7A5bQLpKHGlkfATH6fUnOEx0VICEVFA4sUzw==",
+ "dependencies": {
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/middleware-stack/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@smithy/node-config-provider": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-2.1.5.tgz",
+ "integrity": "sha512-3Omb5/h4tOCuKRx4p4pkYTvEYRCYoKk52bOYbKUyz/G/8gERbagsN8jFm4FjQubkrcIqQEghTpQaUw6uk+0edw==",
+ "dependencies": {
+ "@smithy/property-provider": "^2.0.14",
+ "@smithy/shared-ini-file-loader": "^2.2.4",
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/node-config-provider/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@smithy/node-http-handler": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-2.1.9.tgz",
+ "integrity": "sha512-+K0q3SlNcocmo9OZj+fz67gY4lwhOCvIJxVbo/xH+hfWObvaxrMTx7JEzzXcluK0thnnLz++K3Qe7Z/8MDUreA==",
+ "dependencies": {
+ "@smithy/abort-controller": "^2.0.13",
+ "@smithy/protocol-http": "^3.0.9",
+ "@smithy/querystring-builder": "^2.0.13",
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/node-http-handler/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@smithy/property-provider": {
+ "version": "2.0.14",
+ "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-2.0.14.tgz",
+ "integrity": "sha512-k3D2qp9o6imTrLaXRj6GdLYEJr1sXqS99nLhzq8fYmJjSVOeMg/G+1KVAAc7Oxpu71rlZ2f8SSZxcSxkevuR0A==",
+ "dependencies": {
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/property-provider/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@smithy/protocol-http": {
+ "version": "3.0.9",
+ "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-3.0.9.tgz",
+ "integrity": "sha512-U1wl+FhYu4/BC+rjwh1lg2gcJChQhytiNQSggREgQ9G2FzmoK9sACBZvx7thyWMvRyHQTE22mO2d5UM8gMKDBg==",
+ "dependencies": {
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/protocol-http/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@smithy/querystring-builder": {
+ "version": "2.0.13",
+ "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-2.0.13.tgz",
+ "integrity": "sha512-JhXKwp3JtsFUe96XLHy/nUPEbaXqn6r7xE4sNaH8bxEyytE5q1fwt0ew/Ke6+vIC7gP87HCHgQpJHg1X1jN2Fw==",
+ "dependencies": {
+ "@smithy/types": "^2.5.0",
+ "@smithy/util-uri-escape": "^2.0.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/querystring-builder/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@smithy/querystring-parser": {
+ "version": "2.0.13",
+ "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-2.0.13.tgz",
+ "integrity": "sha512-TEiT6o8CPZVxJ44Rly/rrsATTQsE+b/nyBVzsYn2sa75xAaZcurNxsFd8z1haoUysONiyex24JMHoJY6iCfLdA==",
+ "dependencies": {
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/querystring-parser/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@smithy/service-error-classification": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-2.0.6.tgz",
+ "integrity": "sha512-fCQ36frtYra2fqY2/DV8+3/z2d0VB/1D1hXbjRcM5wkxTToxq6xHbIY/NGGY6v4carskMyG8FHACxgxturJ9Pg==",
+ "dependencies": {
+ "@smithy/types": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/shared-ini-file-loader": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-2.2.4.tgz",
+ "integrity": "sha512-9dRknGgvYlRIsoTcmMJXuoR/3ekhGwhRq4un3ns2/byre4Ql5hyUN4iS0x8eITohjU90YOnUCsbRwZRvCkbRfw==",
+ "dependencies": {
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/shared-ini-file-loader/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@smithy/signature-v4": {
+ "version": "2.0.15",
+ "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-2.0.15.tgz",
+ "integrity": "sha512-SRTEJSEhQYVlBKIIdZ9SZpqW+KFqxqcNnEcBX+8xkDdWx+DItme9VcCDkdN32yTIrICC+irUufnUdV7mmHPjoA==",
+ "dependencies": {
+ "@smithy/eventstream-codec": "^2.0.13",
+ "@smithy/is-array-buffer": "^2.0.0",
+ "@smithy/types": "^2.5.0",
+ "@smithy/util-hex-encoding": "^2.0.0",
+ "@smithy/util-middleware": "^2.0.6",
+ "@smithy/util-uri-escape": "^2.0.0",
+ "@smithy/util-utf8": "^2.0.2",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/signature-v4/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@smithy/smithy-client": {
+ "version": "2.1.15",
+ "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-2.1.15.tgz",
+ "integrity": "sha512-rngZcQu7Jvs9UbHihK1EI67RMPuzkc3CJmu4MBgB7D7yBnMGuFR86tq5rqHfL2gAkNnMelBN/8kzQVvZjNKefQ==",
+ "dependencies": {
+ "@smithy/middleware-stack": "^2.0.7",
+ "@smithy/types": "^2.5.0",
+ "@smithy/util-stream": "^2.0.20",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/smithy-client/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@smithy/types": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.5.0.tgz",
+ "integrity": "sha512-/a31lYofrMBkJb3BuPlYJTMKDj0hUmKUP6JFZQu6YVuQVoAjubiY0A52U9S0Uysd33n/djexCUSNJ+G9bf3/aA==",
+ "dependencies": {
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/types/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@smithy/url-parser": {
+ "version": "2.0.13",
+ "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-2.0.13.tgz",
+ "integrity": "sha512-okWx2P/d9jcTsZWTVNnRMpFOE7fMkzloSFyM53fA7nLKJQObxM2T4JlZ5KitKKuXq7pxon9J6SF2kCwtdflIrA==",
+ "dependencies": {
+ "@smithy/querystring-parser": "^2.0.13",
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ }
+ },
+ "node_modules/@smithy/url-parser/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@smithy/util-base64": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-2.0.1.tgz",
+ "integrity": "sha512-DlI6XFYDMsIVN+GH9JtcRp3j02JEVuWIn/QOZisVzpIAprdsxGveFed0bjbMRCqmIFe8uetn5rxzNrBtIGrPIQ==",
+ "dependencies": {
+ "@smithy/util-buffer-from": "^2.0.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/util-base64/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@smithy/util-body-length-browser": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-2.0.0.tgz",
+ "integrity": "sha512-JdDuS4ircJt+FDnaQj88TzZY3+njZ6O+D3uakS32f2VNnDo3vyEuNdBOh/oFd8Df1zSZOuH1HEChk2AOYDezZg==",
+ "dependencies": {
+ "tslib": "^2.5.0"
+ }
+ },
+ "node_modules/@smithy/util-body-length-browser/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@smithy/util-body-length-node": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-2.1.0.tgz",
+ "integrity": "sha512-/li0/kj/y3fQ3vyzn36NTLGmUwAICb7Jbe/CsWCktW363gh1MOcpEcSO3mJ344Gv2dqz8YJCLQpb6hju/0qOWw==",
+ "dependencies": {
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/util-body-length-node/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@smithy/util-buffer-from": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.0.0.tgz",
+ "integrity": "sha512-/YNnLoHsR+4W4Vf2wL5lGv0ksg8Bmk3GEGxn2vEQt52AQaPSCuaO5PM5VM7lP1K9qHRKHwrPGktqVoAHKWHxzw==",
+ "dependencies": {
+ "@smithy/is-array-buffer": "^2.0.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/util-buffer-from/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@smithy/util-config-provider": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-2.0.0.tgz",
+ "integrity": "sha512-xCQ6UapcIWKxXHEU4Mcs2s7LcFQRiU3XEluM2WcCjjBtQkUN71Tb+ydGmJFPxMUrW/GWMgQEEGipLym4XG0jZg==",
+ "dependencies": {
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@smithy/util-config-provider/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@smithy/util-defaults-mode-browser": {
+ "version": "2.0.19",
+ "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-2.0.19.tgz",
+ "integrity": "sha512-VHP8xdFR7/orpiABJwgoTB0t8Zhhwpf93gXhNfUBiwAE9O0rvsv7LwpQYjgvbOUDDO8JfIYQB2GYJNkqqGWsXw==",
+ "dependencies": {
+ "@smithy/property-provider": "^2.0.14",
+ "@smithy/smithy-client": "^2.1.15",
+ "@smithy/types": "^2.5.0",
+ "bowser": "^2.11.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/@smithy/util-defaults-mode-browser/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@smithy/util-defaults-mode-node": {
+ "version": "2.0.25",
+ "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-2.0.25.tgz",
+ "integrity": "sha512-jkmep6/JyWmn2ADw9VULDeGbugR4N/FJCKOt+gYyVswmN1BJOfzF2umaYxQ1HhQDvna3kzm1Dbo1qIfBW4iuHA==",
+ "dependencies": {
+ "@smithy/config-resolver": "^2.0.18",
+ "@smithy/credential-provider-imds": "^2.1.1",
+ "@smithy/node-config-provider": "^2.1.5",
+ "@smithy/property-provider": "^2.0.14",
+ "@smithy/smithy-client": "^2.1.15",
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/@smithy/util-defaults-mode-node/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@smithy/util-endpoints": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-1.0.4.tgz",
+ "integrity": "sha512-FPry8j1xye5yzrdnf4xKUXVnkQErxdN7bUIaqC0OFoGsv2NfD9b2UUMuZSSt+pr9a8XWAqj0HoyVNUfPiZ/PvQ==",
+ "dependencies": {
+ "@smithy/node-config-provider": "^2.1.5",
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@smithy/util-endpoints/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/@smithy/util-hex-encoding": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-2.0.0.tgz",
+ "integrity": "sha512-c5xY+NUnFqG6d7HFh1IFfrm3mGl29lC+vF+geHv4ToiuJCBmIfzx6IeHLg+OgRdPFKDXIw6pvi+p3CsscaMcMA==",
+ "dependencies": {
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
}
},
- "node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.16.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.16.4.tgz",
- "integrity": "sha512-GkhjAaQ8oUTOKE4g4gsZ0u8K/IHU1+2WQSgS1TwTcYvL+sjbaQjNHFXbOJ6kgqGHIO1DfUhI/Sphi9GkRT9K+Q==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-android-arm64": {
- "version": "4.16.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.16.4.tgz",
- "integrity": "sha512-Bvm6D+NPbGMQOcxvS1zUl8H7DWlywSXsphAeOnVeiZLQ+0J6Is8T7SrjGTH29KtYkiY9vld8ZnpV3G2EPbom+w==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.16.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.16.4.tgz",
- "integrity": "sha512-i5d64MlnYBO9EkCOGe5vPR/EeDwjnKOGGdd7zKFhU5y8haKhQZTN2DgVtpODDMxUr4t2K90wTUJg7ilgND6bXw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.16.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.16.4.tgz",
- "integrity": "sha512-WZupV1+CdUYehaZqjaFTClJI72fjJEgTXdf4NbW69I9XyvdmztUExBtcI2yIIU6hJtYvtwS6pkTkHJz+k08mAQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "darwin"
- ]
+ "node_modules/@smithy/util-hex-encoding/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
},
- "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.16.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.16.4.tgz",
- "integrity": "sha512-ADm/xt86JUnmAfA9mBqFcRp//RVRt1ohGOYF6yL+IFCYqOBNwy5lbEK05xTsEoJq+/tJzg8ICUtS82WinJRuIw==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ]
+ "node_modules/@smithy/util-middleware": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-2.0.6.tgz",
+ "integrity": "sha512-7W4uuwBvSLgKoLC1x4LfeArCVcbuHdtVaC4g30kKsD1erfICyQ45+tFhhs/dZNeQg+w392fhunCm/+oCcb6BSA==",
+ "dependencies": {
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
},
- "node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.16.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.16.4.tgz",
- "integrity": "sha512-tJfJaXPiFAG+Jn3cutp7mCs1ePltuAgRqdDZrzb1aeE3TktWWJ+g7xK9SNlaSUFw6IU4QgOxAY4rA+wZUT5Wfg==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ]
+ "node_modules/@smithy/util-middleware/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
},
- "node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.16.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.16.4.tgz",
- "integrity": "sha512-7dy1BzQkgYlUTapDTvK997cgi0Orh5Iu7JlZVBy1MBURk7/HSbHkzRnXZa19ozy+wwD8/SlpJnOOckuNZtJR9w==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ]
+ "node_modules/@smithy/util-retry": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-2.0.6.tgz",
+ "integrity": "sha512-PSO41FofOBmyhPQJwBQJ6mVlaD7Sp9Uff9aBbnfBJ9eqXOE/obrqQjn0PNdkfdvViiPXl49BINfnGcFtSP4kYw==",
+ "dependencies": {
+ "@smithy/service-error-classification": "^2.0.6",
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
},
- "node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.16.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.16.4.tgz",
- "integrity": "sha512-zsFwdUw5XLD1gQe0aoU2HVceI6NEW7q7m05wA46eUAyrkeNYExObfRFQcvA6zw8lfRc5BHtan3tBpo+kqEOxmg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ]
+ "node_modules/@smithy/util-retry/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
},
- "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
- "version": "4.16.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.16.4.tgz",
- "integrity": "sha512-p8C3NnxXooRdNrdv6dBmRTddEapfESEUflpICDNKXpHvTjRRq1J82CbU5G3XfebIZyI3B0s074JHMWD36qOW6w==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ]
+ "node_modules/@smithy/util-stream": {
+ "version": "2.0.20",
+ "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-2.0.20.tgz",
+ "integrity": "sha512-tT8VASuD8jJu0yjHEMTCPt1o5E3FVzgdsxK6FQLAjXKqVv5V8InCnc0EOsYrijgspbfDqdAJg7r0o2sySfcHVg==",
+ "dependencies": {
+ "@smithy/fetch-http-handler": "^2.2.6",
+ "@smithy/node-http-handler": "^2.1.9",
+ "@smithy/types": "^2.5.0",
+ "@smithy/util-base64": "^2.0.1",
+ "@smithy/util-buffer-from": "^2.0.0",
+ "@smithy/util-hex-encoding": "^2.0.0",
+ "@smithy/util-utf8": "^2.0.2",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
},
- "node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.16.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.16.4.tgz",
- "integrity": "sha512-Lh/8ckoar4s4Id2foY7jNgitTOUQczwMWNYi+Mjt0eQ9LKhr6sK477REqQkmy8YHY3Ca3A2JJVdXnfb3Rrwkng==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ]
+ "node_modules/@smithy/util-stream/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
},
- "node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.16.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.16.4.tgz",
- "integrity": "sha512-1xwwn9ZCQYuqGmulGsTZoKrrn0z2fAur2ujE60QgyDpHmBbXbxLaQiEvzJWDrscRq43c8DnuHx3QorhMTZgisQ==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ]
+ "node_modules/@smithy/util-uri-escape": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-2.0.0.tgz",
+ "integrity": "sha512-ebkxsqinSdEooQduuk9CbKcI+wheijxEb3utGXkCoYQkJnwTnLbH1JXGimJtUkQwNQbsbuYwG2+aFVyZf5TLaw==",
+ "dependencies": {
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
},
- "node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.16.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.16.4.tgz",
- "integrity": "sha512-LuOGGKAJ7dfRtxVnO1i3qWc6N9sh0Em/8aZ3CezixSTM+E9Oq3OvTsvC4sm6wWjzpsIlOCnZjdluINKESflJLA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ]
+ "node_modules/@smithy/util-uri-escape/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
},
- "node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.16.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.16.4.tgz",
- "integrity": "sha512-ch86i7KkJKkLybDP2AtySFTRi5fM3KXp0PnHocHuJMdZwu7BuyIKi35BE9guMlmTpwwBTB3ljHj9IQXnTCD0vA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ]
+ "node_modules/@smithy/util-utf8": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.0.2.tgz",
+ "integrity": "sha512-qOiVORSPm6Ce4/Yu6hbSgNHABLP2VMv8QOC3tTDNHHlWY19pPyc++fBTbZPtx6egPXi4HQxKDnMxVxpbtX2GoA==",
+ "dependencies": {
+ "@smithy/util-buffer-from": "^2.0.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
},
- "node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.16.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.16.4.tgz",
- "integrity": "sha512-Ma4PwyLfOWZWayfEsNQzTDBVW8PZ6TUUN1uFTBQbF2Chv/+sjenE86lpiEwj2FiviSmSZ4Ap4MaAfl1ciF4aSA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "win32"
- ]
+ "node_modules/@smithy/util-utf8/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
},
- "node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.16.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.16.4.tgz",
- "integrity": "sha512-9m/ZDrQsdo/c06uOlP3W9G2ENRVzgzbSXmXHT4hwVaDQhYcRpi9bgBT0FTG9OhESxwK0WjQxYOSfv40cU+T69w==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "win32"
- ]
+ "node_modules/@smithy/util-waiter": {
+ "version": "2.0.13",
+ "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-2.0.13.tgz",
+ "integrity": "sha512-YovIQatiuM7giEsRFotqJa2i3EbU2EE3PgtpXgtLgpx5rXiZMAwPxXYDFVFhuO0lbqvc/Zx4n+ZIisXOHPSqyg==",
+ "dependencies": {
+ "@smithy/abort-controller": "^2.0.13",
+ "@smithy/types": "^2.5.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
},
- "node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.16.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.16.4.tgz",
- "integrity": "sha512-YunpoOAyGLDseanENHmbFvQSfVL5BxW3k7hhy0eN4rb3gS/ct75dVD0EXOWIqFT/nE8XYW6LP6vz6ctKRi0k9A==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "win32"
- ]
+ "node_modules/@smithy/util-waiter/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
},
"node_modules/@tailwindcss/forms": {
"version": "0.5.7",
@@ -2370,6 +4108,11 @@
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
"dev": true
},
+ "node_modules/bowser": {
+ "version": "2.11.0",
+ "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz",
+ "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA=="
+ },
"node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
@@ -3239,6 +4982,27 @@
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
"dev": true
},
+ "node_modules/fast-xml-parser": {
+ "version": "4.2.5",
+ "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.5.tgz",
+ "integrity": "sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==",
+ "funding": [
+ {
+ "type": "paypal",
+ "url": "https://paypal.me/naturalintelligence"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/NaturalIntelligence"
+ }
+ ],
+ "dependencies": {
+ "strnum": "^1.0.5"
+ },
+ "bin": {
+ "fxparser": "src/cli/cli.js"
+ }
+ },
"node_modules/fastq": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
@@ -5822,6 +7586,11 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/strnum": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz",
+ "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA=="
+ },
"node_modules/sucrase": {
"version": "3.35.0",
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
@@ -6161,6 +7930,11 @@
"node": ">=0.3.1"
}
},
+ "node_modules/tslib": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
+ },
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -6347,7 +8121,6 @@
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
- "dev": true,
"bin": {
"uuid": "dist/bin/uuid"
}
diff --git a/frontend/package.json b/frontend/package.json
index 6d8cf1f1f..a56a302be 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -57,6 +57,8 @@
"vue-tsc": "^2.0.14"
},
"dependencies": {
+ "@aws-sdk/client-s3": "^3.454.0",
+ "@aws-sdk/client-sts": "^3.421.0",
"@headlessui/tailwindcss": "^0.2.0",
"@headlessui/vue": "^1.7.20",
"@heroicons/vue": "^2.1.3",
diff --git a/frontend/src/common/backend.ts b/frontend/src/common/backend.ts
index 8fcf9061b..d251802c2 100644
--- a/frontend/src/common/backend.ts
+++ b/frontend/src/common/backend.ts
@@ -125,6 +125,74 @@ export class LicenseUserInfoDto {
return this.usedSeats > this.licensedSeats;
}
}
+// / start cipherduck extension
+export type StorageDto = {
+ vaultId: string;
+ storageConfigId: string;
+ vaultConfigToken: string;
+ rootDirHash: string;
+ awsAccessKey: string;
+ awsSecretKey: string;
+ sessionToken: string;
+ region: string;
+}
+
+export type ConfigDto = {
+ keycloakUrl: string;
+ keycloakRealm: string;
+ keycloakClientIdHub: string;
+ keycloakClientIdCryptomator: string;
+ keycloakAuthEndpoint: string;
+ keycloakTokenEndpoint: string;
+ serverTime: string;
+ apiLevel: number;
+ uuid: string;
+}
+
+export type StorageProfileDto = {
+ id: string;
+ name: string;
+ protocol: string;
+ bucketPrefix: string;
+ stsRoleArnClient: string;
+ stsRoleArnHub: string;
+ stsEndpoint: string;
+ bucketVersioning: string;
+ bucketAcceleration: string;
+ bucketEncryption: string;
+ region: string;
+ regions: string[];
+ withPathStyleAccessEnabled: boolean;
+ storageClass: string;
+ scheme: string;
+ hostname: string;
+ port: number;
+ stsRoleArn: string;
+ stsRoleArn2: string;
+ stsDurationSeconds: number;
+ archived: boolean;
+ // TODO https://github.com/shift7-ch/cipherduck-hub/issues/44 add bucketVersioning/bucketAcceleration/bucketEncryption
+}
+
+export type AutomaticAccessGrant = {
+ enabled: boolean,
+ maxWotDepth: number
+}
+
+export type VaultJWEBackendDto = {
+ provider: string;
+
+ defaultPath: string;
+ nickname: string;
+
+ region: string;
+
+ username?: string;
+ password?: string;
+}
+// \ end cipherduck extension
+
+/* Services */
export interface VaultIdHeader extends JWTHeader {
vaultId: string;
@@ -334,6 +402,36 @@ class VersionService {
}
}
+// / start cipherduck extension
+class StorageService {
+ public async put(vaultId: string, dto: StorageDto): Promise {
+ return axiosAuth.put(`/storage/${vaultId}/`, dto);
+ }
+}
+class StorageProfileService {
+ public async get(archived?: boolean): Promise {
+ let query = '';
+ if(archived !== undefined){
+ query = `?archived=${archived}`;
+ }
+ return axiosAuth.get(`/storageprofile${query}`)
+ .then(response => response.data);
+ }
+
+ public async getSingle(storageprofileId: string): Promise {
+ return axiosAuth.get(`/storageprofile/${storageprofileId}`)
+ .then(response => response.data);
+ }
+}
+export const axiosUnAuth = AxiosStatic.create(axiosBaseCfg)
+class ConfigService {
+ public async config(): Promise {
+ return axiosUnAuth.get('/config')
+ .then(response => response.data);
+ }
+}
+// \ end cipherduck extension
+
/**
* Note: Each service can thrown an {@link UnauthorizedError} when the access token is expired!
*/
@@ -345,6 +443,12 @@ const services = {
billing: new BillingService(),
version: new VersionService(),
license: new LicenseService()
+
+ // / start cipherduck extension
+ ,storage: new StorageService()
+ ,storageprofiles: new StorageProfileService()
+ ,config: new ConfigService()
+ // \ end cipherduck extension
};
export default services;
diff --git a/frontend/src/common/settings.ts b/frontend/src/common/settings.ts
new file mode 100644
index 000000000..2481e2e7b
--- /dev/null
+++ b/frontend/src/common/settings.ts
@@ -0,0 +1,2 @@
+// TODO review is there a standard way for injecting developer properties?
+export const showVaultIDs = true;
\ No newline at end of file
diff --git a/frontend/src/components/CreateVault.vue b/frontend/src/components/CreateVault.vue
index b356da777..85a92af69 100644
--- a/frontend/src/components/CreateVault.vue
+++ b/frontend/src/components/CreateVault.vue
@@ -124,6 +124,146 @@
class="mt-1 focus:ring-primary focus:border-primary block w-full shadow-sm sm:text-sm border-gray-300 rounded-md disabled:bg-gray-200"
>
+
+
+
+
+
+
{ setRegionsOnSelectStorage(value);}"
+ >
+
+ {{ selectedBackend ? selectedBackend.name : '' }}
+
+
+
+
+
+
+
+
+
+
+ {{ backend.name }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ selectedRegion }}
+
+
+
+
+
+
+
+
+
+
+ {{ region }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -135,6 +275,7 @@
{{ t('createVault.error.formValidationFailed') }}
{{ t('common.unexpectedError', [onCreateError.message]) }}
+ {{ onCreateError.codehint }}