diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..7bf452f
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,4 @@
+build
+.*
+secret*
+data
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..1327eff
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+gradlew text eol=lf
\ No newline at end of file
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..9c91aa9
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,40 @@
+name: CI/CD Workflow for Walt.ID Wallet Kit
+
+on:
+ push:
+ branches:
+ - master
+ pull_request:
+ branches:
+ - master
+
+jobs:
+ verify-wrapper:
+ name: "Verification"
+ runs-on: "ubuntu-latest"
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v2
+ - name: Validate gradle wrapper
+ uses: gradle/wrapper-validation-action@v1
+
+ gradle:
+ needs: verify-wrapper
+ name: "Build"
+ strategy:
+ matrix:
+ # os: [ubuntu-latest, macos-latest]
+ os: [ubuntu-latest]
+ runs-on: ${{ matrix.os }}
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v2
+ - name: Setup java
+ uses: actions/setup-java@v2.1.0
+ with:
+ distribution: 'adopt-hotspot'
+ java-version: '16'
+ - name: Running gradle build
+ uses: eskatos/gradle-command-action@v1.3.3
+ with:
+ arguments: build --no-daemon
diff --git a/.github/workflows/snapshot.yml b/.github/workflows/snapshot.yml
new file mode 100644
index 0000000..60b9443
--- /dev/null
+++ b/.github/workflows/snapshot.yml
@@ -0,0 +1,55 @@
+name: Snapshot release workflow for Walt.ID Wallet Kit
+
+on:
+ push:
+ branches:
+ - master
+
+jobs:
+ verify-wrapper:
+ name: "Verification"
+ runs-on: "ubuntu-latest"
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v2
+ - name: Validate gradle wrapper
+ uses: gradle/wrapper-validation-action@v1
+
+ gradle:
+ needs: verify-wrapper
+ name: "Build"
+ strategy:
+ matrix:
+ os: [ubuntu-latest]
+ runs-on: ${{ matrix.os }}
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v2
+ - name: Setup java
+ uses: actions/setup-java@v2.1.0
+ with:
+ distribution: 'adopt-hotspot'
+ java-version: '16'
+ - name: Running gradle build
+ uses: eskatos/gradle-command-action@v1.3.3
+ env:
+ MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
+ MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
+ with:
+ # arguments: build publish --no-daemon
+ arguments: build --no-daemon
+ - name: Docker Build and Push SNAPSHOT
+ uses: philpotisk/github-action-docker-build-push@master
+ env:
+ DOCKER_USERNAME: ${{secrets.DOCKER_USERNAME}}
+ DOCKER_PASSWORD: ${{secrets.DOCKER_PASSWORD}}
+ DOCKER_FILE: Dockerfile
+ CONTAINER_TAG: waltid/walletkit:latest
+ - name: Prepare CD
+ run: sed "s/_DEFAULT_DEPLOYMENT_/$GITHUB_SHA/g" k8s/deployment-dev.yaml > k8s/deployment_mod.yaml
+ - name: Continuous deployment
+ uses: actions-hub/kubectl@master
+ env:
+ KUBE_CONFIG: ${{ secrets.KUBE_CONFIG }}
+ with:
+ args: apply -n dev -f k8s/deployment_mod.yaml
diff --git a/.github/workflows/tag.yml b/.github/workflows/tag.yml
new file mode 100644
index 0000000..e379a89
--- /dev/null
+++ b/.github/workflows/tag.yml
@@ -0,0 +1,42 @@
+name: Snapshot release workflow for Walt.ID Wallet Kit
+
+on:
+ push:
+ tags:
+ - 'v*'
+
+jobs:
+ verify-wrapper:
+ name: "Verification"
+ runs-on: "ubuntu-latest"
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v2
+ - name: Validate gradle wrapper
+ uses: gradle/wrapper-validation-action@v1
+
+ gradle:
+ needs: verify-wrapper
+ name: "Build"
+ strategy:
+ matrix:
+ os: [ubuntu-latest]
+ runs-on: ${{ matrix.os }}
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v2
+ - name: Docker Build and Push SNAPSHOT
+ uses: philpotisk/github-action-docker-build-push@master
+ env:
+ DOCKER_USERNAME: ${{secrets.DOCKER_USERNAME}}
+ DOCKER_PASSWORD: ${{secrets.DOCKER_PASSWORD}}
+ DOCKER_FILE: Dockerfile
+ CONTAINER_TAG: ${{ format('waltid/walletkit:{0}', github.ref_name) }}
+ - name: Prepare CD
+ run: sed "s/_DEFAULT_DEPLOYMENT_/$GITHUB_SHA/g; s/_VERSION_TAG_/$GITHUB_REF_NAME/g" k8s/deployment-prod.yaml > k8s/deployment_mod.yaml
+ - name: Continuous deployment
+ uses: actions-hub/kubectl@master
+ env:
+ KUBE_CONFIG: ${{ secrets.KUBE_CONFIG }}
+ with:
+ args: apply -n default -f k8s/deployment_mod.yaml
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..acd0884
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,31 @@
+# Intellij
+.idea/
+*.iml
+*.iws
+out
+data
+
+# Mac
+.DS_Store
+
+# Maven
+log/
+target/
+*.log
+
+# Gradle
+.gradle
+build/
+gradle-app.setting
+!gradle-wrapper.jar
+.gradletasknamecache
+**/build/
+docker/data
+docker/data_
+issuers-secret.json
+/secret_maven_password.txt
+/secret_maven_username.txt
+
+# Env
+.*.env
+.env
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..fcbc0a4
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,8 @@
+# Changelog
+
+Notable changes since the last release of the [walt.id Wallet - Kit](https://github.com/walt-id/waltid-walletkit/).
+
+## [Unreleased]
+- https://github.com/walt-id/waltid-walletkit/issues/73
+- https://github.com/walt-id/waltid-walletkit/issues/74
+- https://github.com/walt-id/waltid-walletkit/issues/78
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..1e6d5a8
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,56 @@
+### Configuration
+
+# set --build-args SKIP_TESTS=true to use
+ARG SKIP_TESTS
+
+# --- dos2unix-env # convert line endings from Windows machines
+FROM docker.io/rkimf1/dos2unix@sha256:60f78cd8bf42641afdeae3f947190f98ae293994c0443741a2b3f3034998a6ed as dos2unix-env
+WORKDIR /convert
+COPY gradlew .
+RUN dos2unix ./gradlew
+
+# --- build-env
+FROM docker.io/gradle:7.6-jdk as build-env
+
+ARG SKIP_TESTS
+
+WORKDIR /appbuild
+
+COPY . /appbuild
+
+# copy converted Windows line endings files
+COPY --from=dos2unix-env /convert/gradlew .
+
+# cache Gradle dependencies
+VOLUME /home/gradle/.gradle
+
+RUN if [ -z "$SKIP_TESTS" ]; \
+ then echo "* Running full build" && gradle -i clean build installDist; \
+ else echo "* Building but skipping tests" && gradle -i clean installDist -x test; \
+ fi
+
+# --- opa-env
+FROM docker.io/openpolicyagent/opa:0.46.1-static as opa-env
+
+# --- iota-env
+FROM docker.io/waltid/waltid_iota_identity_wrapper:latest as iota-env
+
+# --- app-env
+FROM docker.io/eclipse-temurin:19 AS app-env
+
+WORKDIR /app
+
+COPY --from=opa-env /opa /usr/local/bin/opa
+
+COPY --from=iota-env /usr/local/lib/libwaltid_iota_identity_wrapper.so /usr/local/lib/libwaltid_iota_identity_wrapper.so
+RUN ldconfig
+
+COPY --from=build-env /appbuild/build/install/waltid-walletkit /app/
+COPY --from=build-env /appbuild/service-matrix.properties /app/
+COPY --from=build-env /appbuild/config /app/config
+
+
+### Execution
+EXPOSE 7000 7001 7002 7003 7004 7010
+
+ENTRYPOINT ["/app/bin/waltid-walletkit"]
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..261eeb9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..b71b972
--- /dev/null
+++ b/README.md
@@ -0,0 +1,237 @@
+
+
Wallet Kit
+
by walt.id
+
Supercharge your app with SSI, NFTs or fungible tokens
+
+[![CI/CD Workflow for Walt.ID Wallet Kit](https://github.com/walt-id/waltid-walletkit/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/walt-id/waltid-walletkit/actions/workflows/ci.yml)
+
+
+
+
+
+
+
+
+
+
+## Getting Started
+
+- [REST Api](https://docs.walt.id/v/web-wallet/getting-started/rest-apis) - Use the functionality of the Wallet Kit via an REST api.
+- [Maven/Gradle Dependency](https://docs.walt.id/v/web-wallet/getting-started/dependency-jvm) - Use the functions of the Wallet Kit in a Kotlin/Java project.
+
+The Wallet Kit on its own gives you, the backend infrastructure to build a custom wallet solution. However, in conjunction with our pre-build frontend components,
+you can even have a full solution. Get started with the full solution, using:
+- [Docker Compose](https://docs.walt.id/v/web-wallet/getting-started/local-build/docker-build/docker-compose#docker-compose)
+- [Local Docker Build](https://docs.walt.id/v/web-wallet/getting-started/local-build#docker-build)
+- [Local Build](https://docs.walt.id/v/web-wallet/getting-started/local-build/local-build)
+
+Checkout the [Official Documentation](https://docs.walt.id/v/web-wallet/wallet-kit/readme), to find out more.
+
+## What is the Wallet Kit?
+
+It is the API and backend business logic for the walt.id web wallet.
+Additionally, it includes a reference implementation of a Verifier and Issuer Portal backend.
+
+
+## Services
+### Web walletkit
+* **User management**
+ * Authorization is currently mocked and not production ready
+ * User-context switching and user-specific encapsulated data storage
+* **Basic user data management**
+ * List dids
+ * List credentials
+* **Verifiable Credential and Presentation exchange**
+ * Support for credential presentation exchange based on OIDC-SIOPv2 spec
+
+### Verifier portal backend
+* **Wallet configuration**
+ * Possibility to configure list of supported wallets (defaults to walt.id web wallet)
+* **Presentation exchange**
+ * Support for presentation exchange based on OIDC-SIOPv2 spec
+
+### Issuer portal backend
+* **Wallet configuration**
+ * Possibility to configure list of supported wallets (defaults to walt.id web wallet)
+* **Verifiable credential issuance**
+ * Support for issuing verifiable credentials to the web wallet, based on OIDC-SIOPv2 spec
+
+## Join the community
+
+* Connect and get the latest updates: [Discord](https://discord.gg/AW8AgqJthZ) | [Newsletter](https://walt.id/newsletter) | [YouTube](https://www.youtube.com/channel/UCXfOzrv3PIvmur_CmwwmdLA) | [Twitter](https://mobile.twitter.com/walt_id)
+* Get help, request features and report bugs: [GitHub Discussions](https://github.com/walt-id/.github/discussions)
+
+
+## Related components | Full Solution
+* [Web Wallet](https://github.com/walt-id/waltid-web-wallet) - The frontend solution for holders
+* [Verifier Portal](https://github.com/walt-id/waltid-verifier-portal) - The frontend solution for verifiers
+* [Issuer Portal](https://github.com/walt-id/waltid-issuer-portal) - The frontend solution for issuers
+
+## Test deployment
+
+The snap-shot version of this repository is automatically deployed for testing purpose. Feel free to access the test system at the following endpoints:
+
+* https://issuer.walt.id
+* https://wallet.walt.id
+* https://verifier.walt.id
+
+## Usage
+
+Configuration and data are kept in sub folders of the data root:
+* `config/`
+* `data/`
+
+Data root is by default the current **working directory**.
+
+It can be overridden by specifying the **environment variable**:
+
+`WALTID_DATA_ROOT`
+
+### Verifier portal and wallet configuration:
+
+**config/verifier-config.json**
+
+```
+{
+ "verifierUiUrl": "http://localhost:4000", # URL of verifier portal UI
+ "verifierApiUrl": "http://localhost:8080/verifier-api", # URL of verifier portal API
+ "wallets": { # wallet configuration
+ "walt.id": { # wallet configuration key
+ "id": "walt.id", # wallet ID
+ "url": "http://localhost:3000", # URL of wallet UI
+ "presentPath": "CredentialRequest", # URL subpath for a credential presentation request
+ "receivePath" : "ReceiveCredential/", # URL subpath for a credential issuance request
+ "description": "walt.id web wallet" # Wallet description
+ }
+ }
+}
+```
+
+### Issuer portal and wallet configuration:
+
+**config/issuer-config.json**
+
+```
+{
+ "issuerUiUrl": "http://localhost:5000", # URL of issuer portal UI
+ "issuerApiUrl": "http://localhost:8080/issuer-api", # URL of issuer portal API (needs to be accessible from the walletkit)
+ "wallets": { # wallet configuration
+ "walt.id": { # wallet configuration key
+ "id": "walt.id", # wallet ID
+ "url": "http://localhost:3000", # URL of wallet UI
+ "presentPath": "CredentialRequest", # URL subpath for a credential presentation request
+ "receivePath" : "ReceiveCredential/", # URL subpath for a credential issuance request
+ "description": "walt.id web wallet" # Wallet description
+ }
+ }
+}
+```
+
+### Wallet backend configuration
+
+User data (dids, keys, credentials) are currently stored under
+
+`data/`
+
+It is planned to allow users to define their own storage preferences, in the future.
+
+### APIs
+
+The APIs are launched on port 8080.
+
+A **swagger documentation** is available under
+
+`/api/swagger`
+
+**Wallet API** is available under the context path `/api/`
+
+**Verifier portal API** is available under the context path `/verifier-api/`
+
+**Issuer portal API** is available under the context path `/issuer-api/`
+
+## Build & run the Web Wallet Kit
+
+_Gradle_ or _Docker_ can be used to build this project independently. Once running, one can access the Swagger API at http://localhost:8080/api/swagger
+
+### Gradle
+
+ gradle build
+
+unzip package under build/distributions and switch into the new folder. Copy config-files _service-matrix.properties_ and _signatory.conf_ from the root folder and run the bash-script:
+
+ ./bin/waltid-walletkit
+
+To run the backend you will execute:
+ ```waltid-walletkit run```
+To have issuers, you will have to execute:
+ ```waltid-walletkit --init-issuer```
+
+### Docker
+
+ docker build -t waltid/walletkit .
+
+ docker run -it -p 8080:8080 waltid/walletkit
+
+## Running all components with Docker Compose
+
+To spawn the **backend** together with the **wallet frontend**, the **issuer-** and the **verifier-portal**, one can make use of the docker-compose configuration located in folder:
+
+`./docker/`
+
+In order to simply run everything, enter:
+
+ docker-compose up
+
+This configuration will publish the following endpoints by default:
+* **web wallet** on _**[HOSTNAME]:8080**_
+ * wallet frontend: http://[HOSTNAME]:8080/
+ * wallet API: http://[HOSTNAME]:8080/api/
+* **verifier portal** on _**[HOSTNAME]:8081**_
+ * verifier frontend: http://[HOSTNAME]:8081/
+ * verifier API: http://[HOSTNAME]:8081/verifier-api/
+* **issuer portal** on _**[HOSTNAME]:8082**_
+ * issuer frontend: http://[HOSTNAME]:8082/
+ * issuer API: http://[HOSTNAME]:8082/issuer-api/
+
+*Note*
+
+**[HOSTNAME]** is your local computer name. Using **localhost**, not all features will work correctly.
+
+Visit the `./docker`. folder for adjusting the system config in the following files
+* **docker-compose.yaml** - Docker config for launching containers, volumes & networking
+* **ingress.conf** - Routing config
+* **config/verifier-config.json** - verifier portal configuration
+* **config/issuer-config.json** - issuer portal configuration
+
+## Initializing Wallet Kit as EBSI/ESSIF Issuer
+
+By specifying the optional startup parameter **--init-issuer** the walletkit can be initialized as issuer-backend in line with the EBSI/ESSIF ecosystem. Note that this is for demo-purpose only.
+
+```
+cd docker
+docker pull waltid/walletkit
+docker run -it -v $PWD:/waltid-walletkit/data-root -e WALTID_DATA_ROOT=./data-root waltid/walletkit --init-issuer
+
+# For the DID-method enter: "ebsi"
+# For the bearer token copy/paste the value from: https://app.preprod.ebsi.eu/users-onboarding
+```
+
+The initialization routine will output the DID, which it registered on the EBSI/ESSIF ecosystem.
+
+
+## Relevant Standards
+
+- [Self-Issued OpenID Provider v2](https://openid.bitbucket.io/connect/openid-connect-self-issued-v2-1_0.html)
+- [OpenID Connect for Verifiable Presentations](https://openid.net/specs/openid-connect-4-verifiable-presentations-1_0-07.html)
+- [OpenID Connect for Verifiable Credential Issuance](https://tlodderstedt.github.io/openid-connect-4-verifiable-credential-issuance-1_0-01.html)
+- [EBSI Wallet Conformance](https://ec.europa.eu/digital-building-blocks/wikis/display/EBSIDOC/EBSI+Wallet+Conformance+Testing)
+- [Verifiable Credentials Data Model 1.0](https://www.w3.org/TR/vc-data-model/)
+- [Decentralized Identifiers (DIDs) v1.0](https://w3c.github.io/did-core/)
+- [DID Method Rubric](https://w3c.github.io/did-rubric/)
+- [did:web Decentralized Identifier Method Specification](https://w3c-ccg.github.io/did-method-web/)
+- [The did:key Method v0.7](https://w3c-ccg.github.io/did-method-key/)
+
+
+## License
+
+Licensed under the [Apache License, Version 2.0](https://github.com/walt-id/waltid-walletkit/blob/master/LICENSE)
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 0000000..ef7ed1e
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,130 @@
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+plugins {
+ kotlin("jvm") version "1.7.10"
+ kotlin("plugin.serialization") version "1.6.10"
+ application
+ `maven-publish`
+}
+
+group = "id.walt"
+version = "0.7.0-SNAPSHOT"
+
+repositories {
+ mavenLocal()
+ mavenCentral()
+ maven("https://jitpack.io")
+ maven("https://maven.walt.id/repository/waltid/")
+ maven("https://maven.walt.id/repository/waltid-ssi-kit/")
+ maven("https://repo.danubetech.com/repository/maven-public/")
+}
+
+dependencies {
+ // SSIKIT
+ implementation("id.walt:waltid-ssikit:1.2302271609.0")
+ //implementation("id.walt:waltid-ssikit-vclib:1.24.2")
+
+ // Metaco
+ // implementation("com.metaco:sdk:2.1.0")
+
+ implementation("io.javalin:javalin-bundle:4.6.4")
+ implementation("com.github.kmehrunes:javalin-jwt:0.3")
+ implementation("com.beust:klaxon:5.6")
+ implementation("com.nimbusds:oauth2-oidc-sdk:9.43.1")
+
+ // CLI
+ implementation("com.github.ajalt.clikt:clikt-jvm:3.5.1")
+ implementation("com.github.ajalt.clikt:clikt:3.5.1")
+
+ // Service-Matrix
+ implementation("id.walt.servicematrix:WaltID-ServiceMatrix:1.1.3")
+
+ // Logging
+ //implementation("org.slf4j:slf4j-api:2.0.5")
+ implementation("org.slf4j:slf4j-simple:2.0.5")
+ implementation("io.github.microutils:kotlin-logging-jvm:3.0.4")
+
+ // Ktor
+ implementation("io.ktor:ktor-client-jackson:2.2.1")
+ implementation("io.ktor:ktor-client-content-negotiation:2.2.1")
+ implementation("io.ktor:ktor-serialization-kotlinx-json:2.2.1")
+ implementation("io.ktor:ktor-client-core:2.2.1")
+ implementation("io.ktor:ktor-client-cio:2.2.1")
+ implementation("io.ktor:ktor-client-logging:2.2.1")
+ implementation("io.ktor:ktor-client-auth:2.2.1")
+
+ // Cache
+ implementation("io.github.pavleprica:kotlin-cache:1.2.0")
+
+ // Testing
+ //testImplementation(kotlin("test-junit"))
+ testImplementation("io.mockk:mockk:1.13.2")
+
+ testImplementation("io.kotest:kotest-runner-junit5:5.5.4")
+ testImplementation("io.kotest:kotest-assertions-core:5.5.4")
+ testImplementation("io.kotest:kotest-assertions-json:5.5.4")
+
+ // HTTP
+ implementation("io.ktor:ktor-client-core:2.2.1")
+ implementation("io.ktor:ktor-client-content-negotiation:2.2.1")
+ implementation("io.ktor:ktor-client-cio:2.2.1")
+ implementation("io.ktor:ktor-client-logging:2.2.1")
+ implementation(kotlin("stdlib-jdk8"))
+
+ // VC lib for custom credentials
+ implementation("id.walt:waltid-ssikit-vclib:1.24.0")
+}
+
+tasks.withType {
+ kotlinOptions.jvmTarget = "16"
+}
+
+java {
+ toolchain {
+ languageVersion.set(JavaLanguageVersion.of(17))
+ }
+}
+
+tasks.withType {
+ useJUnitPlatform()
+}
+
+application {
+ mainClass.set("id.walt.MainKt")
+}
+
+publishing {
+ publications {
+ create("mavenJava") {
+ pom {
+ name.set("walt.id SSI Wallet Kit")
+ description.set("Kotlin/Java wallet API backend, including issuer and verifier API backends.")
+ url.set("https://walt.id")
+ }
+ from(components["java"])
+ }
+ }
+
+ repositories {
+ maven {
+ url = uri("https://maven.walt.id/repository/waltid-ssi-kit/")
+ val usernameFile = File("secret_maven_username.txt")
+ val passwordFile = File("secret_maven_password.txt")
+ val secretMavenUsername = System.getenv()["MAVEN_USERNAME"] ?: if (usernameFile.isFile) { usernameFile.readLines()[0] } else { "" }
+ val secretMavenPassword = System.getenv()["MAVEN_PASSWORD"] ?: if (passwordFile.isFile) { passwordFile.readLines()[0] } else { "" }
+
+ credentials {
+ username = secretMavenUsername
+ password = secretMavenPassword
+ }
+ }
+ }
+}
+val compileKotlin: KotlinCompile by tasks
+compileKotlin.kotlinOptions {
+ jvmTarget = "16"
+}
+val compileTestKotlin: KotlinCompile by tasks
+compileTestKotlin.kotlinOptions {
+ jvmTarget = "16"
+}
diff --git a/cheqd-credential-template.json b/cheqd-credential-template.json
new file mode 100644
index 0000000..5e932ea
--- /dev/null
+++ b/cheqd-credential-template.json
@@ -0,0 +1,19 @@
+{
+ "@context": [ "https://www.w3.org/2018/credentials/v1" ],
+ "type": [
+ "VerifiableCredential",
+ "TestCheqdCredential"
+ ],
+
+ "id": "testCheqdCredential123",
+ "issuer": "did:cheqd:testnet:abc",
+
+ "issuanceDate": "2020-11-03T00:00:00Z",
+ "validFrom": "2020-11-03T00:00:00Z",
+
+ "some-stuff": "is here",
+ "credentialSubject": {
+ "id": "did:key:abc123",
+ "customAttribute": "some stuff"
+ }
+}
diff --git a/config/crypto.conf b/config/crypto.conf
new file mode 100644
index 0000000..cdc4c32
--- /dev/null
+++ b/config/crypto.conf
@@ -0,0 +1,4 @@
+encryption-at-rest-key: "CHANGE-ME" # your secret key
+key-format: PEM # [PEM / BASE64_DER / BASE64_RAW]
+keys-root: keys # default: "keys"
+alias-root: alias # default: "alias"
diff --git a/config/did/cheqd.jwk.json b/config/did/cheqd.jwk.json
new file mode 100644
index 0000000..f5a61b1
--- /dev/null
+++ b/config/did/cheqd.jwk.json
@@ -0,0 +1,9 @@
+{
+ "kty": "OKP",
+ "d": "SKNkmI0Fs2vyo7owHQMRzC9OqhACmPadD-tsAv4HQ3M",
+ "use": "sig",
+ "crv": "Ed25519",
+ "kid": "259282edee7858a54cf59ca04bca1a37cc00cf332c77254b3f98828afc8acdbe",
+ "x": "JZKC7e54WKVM9ZygS8oaN8wAzzMsdyVLP5iCivyKzb4",
+ "alg": "EdDSA"
+}
diff --git a/config/did/did-cheqd.json b/config/did/did-cheqd.json
new file mode 100644
index 0000000..fb6b703
--- /dev/null
+++ b/config/did/did-cheqd.json
@@ -0,0 +1,18 @@
+{
+ "@context": "https://w3id.org/did-resolution/v1",
+ "id": "did:cheqd:testnet:z3XffJBaKvAqi2RL",
+ "controller": [
+ "did:cheqd:testnet:z3XffJBaKvAqi2RL"
+ ],
+ "authentication": [
+ "did:cheqd:testnet:z3XffJBaKvAqi2RL#key-1"
+ ],
+ "verificationMethod": [
+ {
+ "id": "did:cheqd:testnet:z3XffJBaKvAqi2RL#key-1",
+ "type": "Ed25519VerificationKey2020",
+ "controller": "did:cheqd:testnet:z3XffJBaKvAqi2RL",
+ "publicKeyMultibase": "z3XffJBaKvAqi2RLLroggoq1MJW3Dy7NP4DYJuFqFjdyF"
+ }
+ ]
+}
diff --git a/config/fsStore.conf b/config/fsStore.conf
new file mode 100644
index 0000000..ccbc15f
--- /dev/null
+++ b/config/fsStore.conf
@@ -0,0 +1 @@
+dataRoot: "./data"
diff --git a/config/issuer-config.json b/config/issuer-config.json
new file mode 100644
index 0000000..a40bbde
--- /dev/null
+++ b/config/issuer-config.json
@@ -0,0 +1,13 @@
+{
+ "issuerUiUrl": "http://localhost:8082",
+ "issuerApiUrl": "http://localhost:8080/issuer-api/default",
+ "wallets": {
+ "waltid": {
+ "id": "waltid",
+ "url": "http://localhost:8080",
+ "presentPath": "api/siop/initiatePresentation/",
+ "receivePath" : "api/siop/initiateIssuance/",
+ "description": "walt.id web wallet"
+ }
+ }
+}
diff --git a/config/s3Store.conf b/config/s3Store.conf
new file mode 100644
index 0000000..79cd6c5
--- /dev/null
+++ b/config/s3Store.conf
@@ -0,0 +1,4 @@
+endpoint: "http://localhost:9000"
+bucket: "ssikit"
+access_key: "minioadmin"
+secret_key: "minioadmin"
diff --git a/config/signatory.conf b/config/signatory.conf
new file mode 100644
index 0000000..025f4fe
--- /dev/null
+++ b/config/signatory.conf
@@ -0,0 +1,8 @@
+proofConfig {
+ issuerDid="foobar"
+ issuerVerificationMethod="foobar"
+ proofType="LD_PROOF"
+ domain="foobar"
+ nonce="foobar"
+}
+templatesFolder: "vc-templates-runtime"
diff --git a/config/verifier-config.json b/config/verifier-config.json
new file mode 100644
index 0000000..3a42183
--- /dev/null
+++ b/config/verifier-config.json
@@ -0,0 +1,23 @@
+{
+ "verifierUiUrl": "http://localhost:8081",
+ "verifierApiUrl": "http://localhost:8080/verifier-api/default",
+ "additionalPolicies": [
+ ],
+ "wallets": {
+ "waltid": {
+ "id": "waltid",
+ "url": "http://localhost:8080",
+ "presentPath": "api/siop/initiatePresentation/",
+ "receivePath" : "api/siop/initiateIssuance/",
+ "description": "walt.id web wallet"
+ },
+ "local": {
+ "id": "local",
+ "url": "http://localhost:8080",
+ "presentPath": "api/siop/initiatePresentation/",
+ "receivePath" : "api/siop/initiateIssuance/",
+ "description": "local wallet"
+ }
+ },
+ "allowedWebhookHosts": [ "http://localhost", "http://wallet.local" ]
+}
diff --git a/config/wallet-config.json b/config/wallet-config.json
new file mode 100644
index 0000000..cdd1b87
--- /dev/null
+++ b/config/wallet-config.json
@@ -0,0 +1,16 @@
+{
+ "walletUiUrl": "http://localhost:4201",
+ "walletApiUrl": "http://localhost:8080/api",
+ "issuers": {
+ "waltid": {
+ "id": "waltid",
+ "url": "http://localhost:8080/issuer-api/default/oidc",
+ "description": "walt.id Issuer Portal"
+ },
+ "onboarding@walt.id": {
+ "id": "onboarding@walt.id",
+ "url": "http://localhost:8080/onboarding-api/oidc",
+ "description": "walt.id On-Boarding service"
+ }
+ }
+}
diff --git a/docker/config/issuer-config.json b/docker/config/issuer-config.json
new file mode 100644
index 0000000..d95937b
--- /dev/null
+++ b/docker/config/issuer-config.json
@@ -0,0 +1,13 @@
+{
+ "issuerUiUrl": "http://$EXTERNAL_HOSTNAME:8082",
+ "issuerApiUrl": "http://$EXTERNAL_HOSTNAME:8082/issuer-api/default",
+ "wallets": {
+ "walt.id": {
+ "id": "walt.id",
+ "url": "http://$EXTERNAL_HOSTNAME:8080",
+ "presentPath": "api/siop/initiatePresentation/",
+ "receivePath" : "api/siop/initiateIssuance/",
+ "description": "walt.id web wallet"
+ }
+ }
+}
diff --git a/docker/config/verifier-config.json b/docker/config/verifier-config.json
new file mode 100644
index 0000000..30a9983
--- /dev/null
+++ b/docker/config/verifier-config.json
@@ -0,0 +1,13 @@
+{
+ "verifierUiUrl": "http://$EXTERNAL_HOSTNAME:8081",
+ "verifierApiUrl": "http://$EXTERNAL_HOSTNAME:8081/verifier-api/default",
+ "wallets": {
+ "walt.id": {
+ "id": "walt.id",
+ "url": "http://$EXTERNAL_HOSTNAME:8080",
+ "presentPath": "api/siop/initiatePresentation/",
+ "receivePath" : "api/siop/initiateIssuance/",
+ "description": "walt.id web wallet"
+ }
+ }
+}
diff --git a/docker/config/wallet-config.json b/docker/config/wallet-config.json
new file mode 100644
index 0000000..f1a2d03
--- /dev/null
+++ b/docker/config/wallet-config.json
@@ -0,0 +1,11 @@
+{
+ "walletUiUrl": "http://$EXTERNAL_HOSTNAME:8080",
+ "walletApiUrl": "http://$EXTERNAL_HOSTNAME:8080/api",
+ "issuers": {
+ "walt.id": {
+ "id": "walt.id",
+ "url": "http://$EXTERNAL_HOSTNAME:8082/issuer-api/default/oidc",
+ "description": "walt.id Issuer Portal"
+ }
+ }
+}
diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml
new file mode 100644
index 0000000..cb94a65
--- /dev/null
+++ b/docker/docker-compose.yaml
@@ -0,0 +1,37 @@
+version: "3.3"
+services:
+ walletkit:
+ image: waltid/walletkit:latest # backend docker image
+ command:
+ - run
+ environment:
+ WALTID_DATA_ROOT: . #./data-root
+ WALTID_WALLET_BACKEND_BIND_ADDRESS: 0.0.0.0
+ EXTERNAL_HOSTNAME: $HOSTNAME$COMPUTERNAME
+ volumes:
+ - .:/waltid-walletkit/data-root # data store volume incl. config files.
+ extra_hosts:
+ - "$HOSTNAME$COMPUTERNAME:host-gateway"
+ wallet-ui:
+ image: waltid/ssikit-web-wallet:latest # wallet web ui docker image
+ verifier-ui:
+ image: waltid/ssikit-verifier-portal:latest # verifier web ui docker image
+ issuer-ui:
+ image: waltid/ssikit-issuer-portal:latest # issuer web ui docker image
+ ingress:
+ image: nginx:1.15.10-alpine
+ ports:
+ - target: 80
+ published: 8080 # wallet ui publish port
+ protocol: tcp
+ mode: host
+ - target: 81
+ published: 8081 # verifier ui publish port
+ protocol: tcp
+ mode: host
+ - target: 82
+ published: 8082 # issuer ui publish port
+ protocol: tcp
+ mode: host
+ volumes:
+ - ./ingress.conf:/etc/nginx/conf.d/default.conf # API gateway configuration
diff --git a/docker/ingress.conf b/docker/ingress.conf
new file mode 100644
index 0000000..8067e82
--- /dev/null
+++ b/docker/ingress.conf
@@ -0,0 +1,30 @@
+
+server {
+ listen 80;
+ location ~* /(api|webjars|verifier-api|issuer-api)/ {
+ proxy_pass http://walletkit:8080;
+ }
+ location / {
+ proxy_pass http://wallet-ui:80/;
+ }
+}
+
+server {
+ listen 81;
+ location ~* /(api|webjars|verifier-api|issuer-api)/ {
+ proxy_pass http://walletkit:8080;
+ }
+ location / {
+ proxy_pass http://verifier-ui:80/;
+ }
+}
+
+server {
+ listen 82;
+ location ~* /(api|webjars|verifier-api|issuer-api)/ {
+ proxy_pass http://walletkit:8080;
+ }
+ location / {
+ proxy_pass http://issuer-ui:80/;
+ }
+}
diff --git a/fsStore.conf b/fsStore.conf
new file mode 100644
index 0000000..ccbc15f
--- /dev/null
+++ b/fsStore.conf
@@ -0,0 +1 @@
+dataRoot: "./data"
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..7fc6f1f
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1 @@
+kotlin.code.style=official
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..7454180
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..f72df95
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.1-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..1b6c787
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,234 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+APP_NAME="Gradle"
+APP_BASE_NAME=${0##*/}
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+# Collect all arguments for the java command;
+# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+# shell script including quotes and variable substitutions, so put them in
+# double quotes to make sure that they get re-expanded; and
+# * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..107acd3
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/k8s/dashboard-ingress.yaml b/k8s/dashboard-ingress.yaml
new file mode 100644
index 0000000..ccc0eea
--- /dev/null
+++ b/k8s/dashboard-ingress.yaml
@@ -0,0 +1,28 @@
+---
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: dashboard-k8s
+ namespace: kubernetes-dashboard
+ annotations:
+ nginx.ingress.kubernetes.io/secure-backends: "true"
+ nginx.ingress.kubernetes.io/ssl-passthrough: "true"
+ nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
+ kubernetes.io/ingress.class: "nginx"
+ cert-manager.io/cluster-issuer: letsencrypt-prod
+spec:
+ tls:
+ - hosts:
+ - kube.walt.id
+ secretName: dashboard-tls-secret
+ rules:
+ - host: kube.walt.id
+ http:
+ paths:
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: kubernetes-dashboard
+ port:
+ number: 443
diff --git a/k8s/deployment-cheqd.yaml b/k8s/deployment-cheqd.yaml
new file mode 100644
index 0000000..4417c72
--- /dev/null
+++ b/k8s/deployment-cheqd.yaml
@@ -0,0 +1,364 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: wallet-config
+data:
+ issuer-config.json: |
+ {
+ "issuerUiUrl": "https://issuer.cheqd.walt-test.cloud",
+ "issuerApiUrl": "https://issuer.cheqd.walt-test.cloud/issuer-api/default",
+ "issuerClientName": "walt.id Issuer Portal",
+ "wallets": {
+ "waltid": {
+ "id": "waltid",
+ "url": "https://wallet.cheqd.walt-test.cloud",
+ "presentPath": "api/siop/initiatePresentation/",
+ "receivePath" : "api/siop/initiateIssuance/",
+ "description": "walt.id web wallet"
+ }
+ }
+ }
+ verifier-config.json: |
+ {
+ "verifierUiUrl": "https://verifier.cheqd.walt-test.cloud",
+ "verifierApiUrl": "https://verifier.cheqd.walt-test.cloud/verifier-api/default",
+ "wallets": {
+ "waltid": {
+ "id": "waltid",
+ "url": "https://wallet.cheqd.walt-test.cloud",
+ "presentPath": "api/siop/initiatePresentation/",
+ "receivePath" : "api/siop/initiateIssuance/",
+ "description": "walt.id web wallet"
+ }
+ },
+ "allowedWebhookHosts": [ "https://integrations.cheqd.walt-test.cloud/callback/" ]
+ }
+ wallet-config.json: |
+ {
+ "walletUiUrl": "https://wallet.cheqd.walt-test.cloud",
+ "walletApiUrl": "https://wallet.cheqd.walt-test.cloud/api",
+ "issuers": {
+ "waltid": {
+ "id": "waltid",
+ "url": "https://issuer.cheqd.walt-test.cloud/issuer-api/default/oidc",
+ "description": "walt.id Issuer Portal"
+ },
+ "yes.com": {
+ "id": "yes.com",
+ "url": "https://demo.sandbox.yes.com/essif/issuer/c2id",
+ "description": "yes.com Bank ID issuer"
+ },
+ "onboarding@walt.id": {
+ "id": "onboarding@walt.id",
+ "url": "https://issuer.cheqd.walt-test.cloud/onboarding-api/oidc",
+ "description": "walt.id On-Boarding service"
+ }
+ }
+ }
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+ name: wallet-data-volume-claim
+spec:
+ accessModes:
+ - ReadWriteMany
+ resources:
+ requests:
+ storage: 10Gi
+---
+kind: Deployment
+apiVersion: apps/v1
+metadata:
+ name: walletkit
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: walletkit
+ template:
+ metadata:
+ labels:
+ app: walletkit
+ annotations:
+ deployment/id: "_DEFAULT_DEPLOYMENT_"
+ spec:
+ containers:
+ - name: walletkit
+ image: waltid/walletkit:latest
+ volumeMounts:
+ - name: wallet-config
+ mountPath: "/waltid/wallet/config/"
+ readOnly: true
+ - mountPath: "/waltid/wallet/data/"
+ name: wallet-data
+ env:
+ - name: WALTID_DATA_ROOT
+ value: "/waltid/wallet"
+ - name: WALTID_WALLET_BACKEND_BIND_ADDRESS
+ value: 0.0.0.0
+ - name: WALTID_WALLET_AUTH_SECRET
+ value: 0b218176-d8f3-4a58-83db-fd328defc30f
+ args:
+ - run
+ ports:
+ - containerPort: 8080
+ name: http-api
+ volumes:
+ - name: wallet-config
+ configMap:
+ name: wallet-config
+ - name: issuers-secret
+ secret:
+ secretName: issuers-secret
+ - name: wallet-data
+ persistentVolumeClaim:
+ claimName: wallet-data-volume-claim
+---
+kind: Service
+apiVersion: v1
+metadata:
+ name: walletkit
+spec:
+ ports:
+ - name: http
+ port: 80
+ targetPort: http-api
+ protocol: TCP
+ selector:
+ app: walletkit
+---
+kind: Deployment
+apiVersion: apps/v1
+metadata:
+ name: web-wallet
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: web-wallet
+ template:
+ metadata:
+ labels:
+ app: web-wallet
+ annotations:
+ deployment/id: "_DEFAULT_DEPLOYMENT_"
+ spec:
+ containers:
+ - name: ssikit-web-wallet
+ image: waltid/ssikit-web-wallet:latest
+ ports:
+ - containerPort: 80
+ name: http-api
+---
+kind: Service
+apiVersion: v1
+metadata:
+ name: web-wallet
+spec:
+ ports:
+ - name: http
+ port: 80
+ targetPort: http-api
+ protocol: TCP
+ selector:
+ app: web-wallet
+---
+kind: Deployment
+apiVersion: apps/v1
+metadata:
+ name: verifier-portal
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: verifier-portal
+ template:
+ metadata:
+ labels:
+ app: verifier-portal
+ annotations:
+ deployment/id: "_DEFAULT_DEPLOYMENT_"
+ spec:
+ containers:
+ - name: ssikit-verifier-portal
+ image: waltid/ssikit-verifier-portal:latest
+ ports:
+ - containerPort: 80
+ name: http-api
+---
+kind: Service
+apiVersion: v1
+metadata:
+ name: verifier-portal
+spec:
+ ports:
+ - name: http
+ port: 80
+ targetPort: http-api
+ protocol: TCP
+ selector:
+ app: verifier-portal
+---
+kind: Deployment
+apiVersion: apps/v1
+metadata:
+ name: issuer-portal
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: issuer-portal
+ template:
+ metadata:
+ labels:
+ app: issuer-portal
+ annotations:
+ deployment/id: "_DEFAULT_DEPLOYMENT_"
+ spec:
+ containers:
+ - name: ssikit-issuer-portal
+ image: waltid/ssikit-issuer-portal:latest
+ ports:
+ - containerPort: 80
+ name: http-api
+---
+kind: Service
+apiVersion: v1
+metadata:
+ name: issuer-portal
+spec:
+ ports:
+ - name: http
+ port: 80
+ targetPort: http-api
+ protocol: TCP
+ selector:
+ app: issuer-portal
+---
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: walletkit
+ annotations:
+ kubernetes.io/ingress.class: "nginx"
+ cert-manager.io/cluster-issuer: letsencrypt-prod
+ ingress.kubernetes.io/configuration-snippet: |
+ if ($host ~ ^(.+)\.waltid\.org$) {
+ return 301 https://$1.cheqd.walt-test.cloud$request_uri;
+ }
+spec:
+ tls:
+ - hosts:
+ - wallet.cheqd.walt-test.cloud
+ - verifier.cheqd.walt-test.cloud
+ - issuer.cheqd.walt-test.cloud
+ secretName: wallet-tls-secret
+ rules:
+ - host: wallet.cheqd.walt-test.cloud
+ http:
+ paths:
+ - path: /api/
+ pathType: Prefix
+ backend:
+ service:
+ name: walletkit
+ port:
+ number: 80
+ - path: /verifier-api/
+ pathType: Prefix
+ backend:
+ service:
+ name: walletkit
+ port:
+ number: 80
+ - path: /issuer-api/
+ pathType: Prefix
+ backend:
+ service:
+ name: walletkit
+ port:
+ number: 80
+ - path: /onboarding-api/
+ pathType: Prefix
+ backend:
+ service:
+ name: walletkit
+ port:
+ number: 80
+ - path: /webjars
+ pathType: Prefix
+ backend:
+ service:
+ name: walletkit
+ port:
+ number: 80
+ - path: /v2/nftkit/nft/
+ pathType: Prefix
+ backend:
+ service:
+ name: nftkit
+ port:
+ number: 80
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: web-wallet
+ port:
+ number: 80
+ - host: verifier.cheqd.walt-test.cloud
+ http:
+ paths:
+ - path: /verifier-api/
+ pathType: Prefix
+ backend:
+ service:
+ name: walletkit
+ port:
+ number: 80
+ - path: /api/
+ pathType: Prefix
+ backend:
+ service:
+ name: walletkit
+ port:
+ number: 80
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: verifier-portal
+ port:
+ number: 80
+ - host: issuer.cheqd.walt-test.cloud
+ http:
+ paths:
+ - path: /issuer-api/
+ pathType: Prefix
+ backend:
+ service:
+ name: walletkit
+ port:
+ number: 80
+ - path: /onboarding-api/
+ pathType: Prefix
+ backend:
+ service:
+ name: walletkit
+ port:
+ number: 80
+ - path: /api/
+ pathType: Prefix
+ backend:
+ service:
+ name: walletkit
+ port:
+ number: 80
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: issuer-portal
+ port:
+ number: 80
diff --git a/k8s/deployment-dev.yaml b/k8s/deployment-dev.yaml
new file mode 100644
index 0000000..999fb82
--- /dev/null
+++ b/k8s/deployment-dev.yaml
@@ -0,0 +1,364 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: wallet-config
+data:
+ issuer-config.json: |
+ {
+ "issuerUiUrl": "https://issuer.walt-test.cloud",
+ "issuerApiUrl": "https://issuer.walt-test.cloud/issuer-api/default",
+ "issuerClientName": "walt.id Issuer Portal",
+ "wallets": {
+ "walt.id": {
+ "id": "walt.id",
+ "url": "https://wallet.walt-test.cloud",
+ "presentPath": "api/siop/initiatePresentation/",
+ "receivePath" : "api/siop/initiateIssuance/",
+ "description": "walt.id web wallet"
+ }
+ }
+ }
+ verifier-config.json: |
+ {
+ "verifierUiUrl": "https://verifier.walt-test.cloud",
+ "verifierApiUrl": "https://verifier.walt-test.cloud/verifier-api/default",
+ "wallets": {
+ "walt.id": {
+ "id": "walt.id",
+ "url": "https://wallet.walt-test.cloud",
+ "presentPath": "api/siop/initiatePresentation/",
+ "receivePath" : "api/siop/initiateIssuance/",
+ "description": "walt.id web wallet"
+ }
+ }
+ }
+ wallet-config.json: |
+ {
+ "walletUiUrl": "https://wallet.walt-test.cloud",
+ "walletApiUrl": "https://wallet.walt-test.cloud/api",
+ "issuers": {
+ "walt.id": {
+ "id": "walt.id",
+ "url": "https://issuer.walt-test.cloud/issuer-api/default/oidc",
+ "description": "walt.id Issuer Portal"
+ },
+ "yes.com": {
+ "id": "yes.com",
+ "url": "https://demo.sandbox.yes.com/essif/issuer/c2id",
+ "description": "yes.com Bank ID issuer"
+ },
+ "onboarding@walt.id": {
+ "id": "onboarding@walt.id",
+ "url": "https://issuer.walt-test.cloud/onboarding-api/oidc",
+ "description": "walt.id On-Boarding service"
+ }
+ }
+ }
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+ name: wallet-data-volume-claim
+spec:
+ accessModes:
+ - ReadWriteMany
+ resources:
+ requests:
+ storage: 10Gi
+---
+kind: Deployment
+apiVersion: apps/v1
+metadata:
+ name: walletkit
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: walletkit
+ template:
+ metadata:
+ labels:
+ app: walletkit
+ annotations:
+ deployment/id: "_DEFAULT_DEPLOYMENT_"
+ spec:
+ containers:
+ - name: walletkit
+ image: waltid/walletkit:latest
+ volumeMounts:
+ - name: wallet-config
+ mountPath: "/waltid/wallet/config/"
+ readOnly: true
+ - name: issuers-secret
+ mountPath: "/waltid/wallet/secrets"
+ readOnly: true
+ - mountPath: "/waltid/wallet/data/"
+ name: wallet-data
+ env:
+ - name: WALTID_DATA_ROOT
+ value: "/waltid/wallet"
+ - name: WALTID_WALLET_BACKEND_BIND_ADDRESS
+ value: 0.0.0.0
+ args:
+ - run
+ ports:
+ - containerPort: 8080
+ name: http-api
+ volumes:
+ - name: wallet-config
+ configMap:
+ name: wallet-config
+ - name: issuers-secret
+ secret:
+ secretName: issuers-secret
+ - name: wallet-data
+ persistentVolumeClaim:
+ claimName: wallet-data-volume-claim
+---
+kind: Service
+apiVersion: v1
+metadata:
+ name: walletkit
+spec:
+ ports:
+ - name: http
+ port: 80
+ targetPort: http-api
+ protocol: TCP
+ selector:
+ app: walletkit
+---
+kind: Deployment
+apiVersion: apps/v1
+metadata:
+ name: web-wallet
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: web-wallet
+ template:
+ metadata:
+ labels:
+ app: web-wallet
+ annotations:
+ deployment/id: "_DEFAULT_DEPLOYMENT_"
+ spec:
+ containers:
+ - name: ssikit-web-wallet
+ image: waltid/ssikit-web-wallet:latest
+ ports:
+ - containerPort: 80
+ name: http-api
+---
+kind: Service
+apiVersion: v1
+metadata:
+ name: web-wallet
+spec:
+ ports:
+ - name: http
+ port: 80
+ targetPort: http-api
+ protocol: TCP
+ selector:
+ app: web-wallet
+---
+kind: Deployment
+apiVersion: apps/v1
+metadata:
+ name: verifier-portal
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: verifier-portal
+ template:
+ metadata:
+ labels:
+ app: verifier-portal
+ annotations:
+ deployment/id: "_DEFAULT_DEPLOYMENT_"
+ spec:
+ containers:
+ - name: ssikit-verifier-portal
+ image: waltid/ssikit-verifier-portal:latest
+ ports:
+ - containerPort: 80
+ name: http-api
+---
+kind: Service
+apiVersion: v1
+metadata:
+ name: verifier-portal
+spec:
+ ports:
+ - name: http
+ port: 80
+ targetPort: http-api
+ protocol: TCP
+ selector:
+ app: verifier-portal
+---
+kind: Deployment
+apiVersion: apps/v1
+metadata:
+ name: issuer-portal
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: issuer-portal
+ template:
+ metadata:
+ labels:
+ app: issuer-portal
+ annotations:
+ deployment/id: "_DEFAULT_DEPLOYMENT_"
+ spec:
+ containers:
+ - name: ssikit-issuer-portal
+ image: waltid/ssikit-issuer-portal:latest
+ ports:
+ - containerPort: 80
+ name: http-api
+---
+kind: Service
+apiVersion: v1
+metadata:
+ name: issuer-portal
+spec:
+ ports:
+ - name: http
+ port: 80
+ targetPort: http-api
+ protocol: TCP
+ selector:
+ app: issuer-portal
+---
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: walletkit
+ annotations:
+ kubernetes.io/ingress.class: "nginx"
+ cert-manager.io/cluster-issuer: letsencrypt-prod
+ ingress.kubernetes.io/configuration-snippet: |
+ if ($host ~ ^(.+)\.waltid\.org$) {
+ return 301 https://$1.walt-test.cloud$request_uri;
+ }
+spec:
+ tls:
+ - hosts:
+ - wallet.walt-test.cloud
+ - verifier.walt-test.cloud
+ - issuer.walt-test.cloud
+ secretName: wallet-tls-secret
+ rules:
+ - host: wallet.walt-test.cloud
+ http:
+ paths:
+ - path: /api/
+ pathType: Prefix
+ backend:
+ service:
+ name: walletkit
+ port:
+ number: 80
+ - path: /verifier-api/
+ pathType: Prefix
+ backend:
+ service:
+ name: walletkit
+ port:
+ number: 80
+ - path: /issuer-api/
+ pathType: Prefix
+ backend:
+ service:
+ name: walletkit
+ port:
+ number: 80
+ - path: /onboarding-api/
+ pathType: Prefix
+ backend:
+ service:
+ name: walletkit
+ port:
+ number: 80
+ - path: /webjars
+ pathType: Prefix
+ backend:
+ service:
+ name: walletkit
+ port:
+ number: 80
+ - path: /v2/nftkit/nft/
+ pathType: Prefix
+ backend:
+ service:
+ name: nftkit
+ port:
+ number: 80
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: web-wallet
+ port:
+ number: 80
+ - host: verifier.walt-test.cloud
+ http:
+ paths:
+ - path: /verifier-api/
+ pathType: Prefix
+ backend:
+ service:
+ name: walletkit
+ port:
+ number: 80
+ - path: /api/
+ pathType: Prefix
+ backend:
+ service:
+ name: walletkit
+ port:
+ number: 80
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: verifier-portal
+ port:
+ number: 80
+ - host: issuer.walt-test.cloud
+ http:
+ paths:
+ - path: /issuer-api/
+ pathType: Prefix
+ backend:
+ service:
+ name: walletkit
+ port:
+ number: 80
+ - path: /onboarding-api/
+ pathType: Prefix
+ backend:
+ service:
+ name: walletkit
+ port:
+ number: 80
+ - path: /api/
+ pathType: Prefix
+ backend:
+ service:
+ name: walletkit
+ port:
+ number: 80
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: issuer-portal
+ port:
+ number: 80
\ No newline at end of file
diff --git a/k8s/deployment-jff.yaml b/k8s/deployment-jff.yaml
new file mode 100644
index 0000000..980ee79
--- /dev/null
+++ b/k8s/deployment-jff.yaml
@@ -0,0 +1,169 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: jff-config
+data:
+ issuer-config.json: |
+ {
+ "issuerUiUrl": "https://jff.walt.id",
+ "issuerApiUrl": "https://jff.walt.id/issuer-api/default",
+ "issuerClientName": "walt.id JFF Issuer Portal",
+ "issuerDid": "did:jwk:eyJrdHkiOiJFQyIsInVzZSI6InNpZyIsImNydiI6IlAtMjU2Iiwia2lkIjoiOWUzZTI3MjliZWMzNDU3YTgzMGQ3MGFkNDNmZmMzYzkiLCJ4IjoiMTlFWXV4aWJ2bGpWUTdORXo3SFNwRjlTcnZPTTJmMkJPaE9UWGlsa0I3OCIsInkiOiJUdGI4WTdQVmhReGZ4UURWQkFIYklvbUNhWWo0VGt3ZEZ3OHMwVWxJOVFZIiwiYWxnIjoiRVMyNTYifQ",
+ "wallets": {
+ "walt.id": {
+ "id": "walt.id",
+ "url": "https://wallet.walt-test.cloud",
+ "presentPath": "api/siop/initiatePresentation/",
+ "receivePath" : "api/siop/initiateIssuance/",
+ "description": "walt.id web wallet"
+ }
+ }
+ }
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+ name: jff-data-volume-claim
+spec:
+ accessModes:
+ - ReadWriteMany
+ resources:
+ requests:
+ storage: 10Gi
+---
+kind: Deployment
+apiVersion: apps/v1
+metadata:
+ name: walletkit
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: walletkit
+ template:
+ metadata:
+ labels:
+ app: walletkit
+ annotations:
+ deployment/id: "_DEFAULT_DEPLOYMENT_"
+ spec:
+ containers:
+ - name: walletkit
+ image: waltid/walletkit:latest
+ volumeMounts:
+ - name: jff-config
+ mountPath: "/waltid/wallet/config/"
+ readOnly: true
+ - mountPath: "/waltid/wallet/data/"
+ name: jff-data
+ env:
+ - name: WALTID_DATA_ROOT
+ value: "/waltid/wallet"
+ - name: WALTID_WALLET_BACKEND_BIND_ADDRESS
+ value: 0.0.0.0
+ args:
+ - run
+ ports:
+ - containerPort: 8080
+ name: http-api
+ volumes:
+ - name: jff-config
+ configMap:
+ name: jff-config
+ - name: jff-data
+ persistentVolumeClaim:
+ claimName: jff-data-volume-claim
+---
+kind: Service
+apiVersion: v1
+metadata:
+ name: walletkit
+spec:
+ ports:
+ - name: http
+ port: 80
+ targetPort: http-api
+ protocol: TCP
+ selector:
+ app: walletkit
+---
+kind: Deployment
+apiVersion: apps/v1
+metadata:
+ name: issuer-portal
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: issuer-portal
+ template:
+ metadata:
+ labels:
+ app: issuer-portal
+ annotations:
+ deployment/id: "_DEFAULT_DEPLOYMENT_"
+ spec:
+ containers:
+ - name: ssikit-issuer-portal
+ image: waltid/ssikit-issuer-portal:latest
+ ports:
+ - containerPort: 80
+ name: http-api
+---
+kind: Service
+apiVersion: v1
+metadata:
+ name: issuer-portal
+spec:
+ ports:
+ - name: http
+ port: 80
+ targetPort: http-api
+ protocol: TCP
+ selector:
+ app: issuer-portal
+---
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: jffingress
+ annotations:
+ kubernetes.io/ingress.class: "nginx"
+ cert-manager.io/cluster-issuer: letsencrypt-prod
+spec:
+ tls:
+ - hosts:
+ - jff.walt.id
+ secretName: jff-tls-secret
+ rules:
+ - host: jff.walt.id
+ http:
+ paths:
+ - path: /api/
+ pathType: Prefix
+ backend:
+ service:
+ name: walletkit
+ port:
+ number: 80
+ - path: /issuer-api/
+ pathType: Prefix
+ backend:
+ service:
+ name: walletkit
+ port:
+ number: 80
+ - path: /webjars
+ pathType: Prefix
+ backend:
+ service:
+ name: walletkit
+ port:
+ number: 80
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: issuer-portal
+ port:
+ number: 80
diff --git a/k8s/deployment-prod.yaml b/k8s/deployment-prod.yaml
new file mode 100644
index 0000000..0128e5c
--- /dev/null
+++ b/k8s/deployment-prod.yaml
@@ -0,0 +1,335 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: wallet-config
+data:
+ issuer-config.json: |
+ {
+ "issuerUiUrl": "https://issuer.walt.id",
+ "issuerApiUrl": "https://issuer.walt.id/issuer-api/default",
+ "issuerClientName": "walt.id Issuer Portal",
+ "wallets": {
+ "walt.id": {
+ "id": "walt.id",
+ "url": "https://wallet.walt.id",
+ "presentPath": "api/siop/initiatePresentation/",
+ "receivePath" : "api/siop/initiateIssuance/",
+ "description": "walt.id web wallet"
+ }
+ }
+ }
+ verifier-config.json: |
+ {
+ "verifierUiUrl": "https://verifier.walt.id",
+ "verifierApiUrl": "https://verifier.walt.id/verifier-api/default",
+ "wallets": {
+ "walt.id": {
+ "id": "walt.id",
+ "url": "https://wallet.walt.id",
+ "presentPath": "api/siop/initiatePresentation/",
+ "receivePath" : "api/siop/initiateIssuance/",
+ "description": "walt.id web wallet"
+ }
+ }
+ }
+ wallet-config.json: |
+ {
+ "walletUiUrl": "https://wallet.walt.id",
+ "walletApiUrl": "https://wallet.walt.id/api",
+ "issuers": {
+ "walt.id": {
+ "id": "walt.id",
+ "url": "https://wallet.walt.id/issuer-api/default/oidc",
+ "description": "walt.id Issuer Portal"
+ },
+ "yes.com": {
+ "id": "yes.com",
+ "url": "https://demo.sandbox.yes.com/essif/issuer/c2id",
+ "description": "yes.com Bank ID issuer"
+ }
+ }
+ }
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+ name: wallet-data-volume-claim
+ namespace: default
+spec:
+ accessModes:
+ - ReadWriteMany
+ resources:
+ requests:
+ storage: 10Gi
+---
+kind: Deployment
+apiVersion: apps/v1
+metadata:
+ name: walletkit
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: walletkit
+ template:
+ metadata:
+ labels:
+ app: walletkit
+ annotations:
+ deployment/id: "_DEFAULT_DEPLOYMENT_"
+ spec:
+ containers:
+ - name: walletkit
+ image: waltid/walletkit:_VERSION_TAG_
+ volumeMounts:
+ - name: wallet-config
+ mountPath: "/waltid/wallet/config/"
+ readOnly: true
+ - name: issuers-secret
+ mountPath: "/waltid/wallet/secrets"
+ readOnly: true
+ - mountPath: "/waltid/wallet/data/"
+ name: wallet-data
+ env:
+ - name: WALTID_DATA_ROOT
+ value: "/waltid/wallet"
+ - name: WALTID_WALLET_BACKEND_BIND_ADDRESS
+ value: 0.0.0.0
+ args:
+ - run
+ ports:
+ - containerPort: 8080
+ name: http-api
+ volumes:
+ - name: wallet-config
+ configMap:
+ name: wallet-config
+ - name: issuers-secret
+ secret:
+ secretName: issuers-secret
+ - name: wallet-data
+ persistentVolumeClaim:
+ claimName: wallet-data-volume-claim
+---
+kind: Service
+apiVersion: v1
+metadata:
+ name: walletkit
+spec:
+ ports:
+ - name: http
+ port: 80
+ targetPort: http-api
+ protocol: TCP
+ selector:
+ app: walletkit
+---
+kind: Deployment
+apiVersion: apps/v1
+metadata:
+ name: web-wallet
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: web-wallet
+ template:
+ metadata:
+ labels:
+ app: web-wallet
+ annotations:
+ deployment/id: "_DEFAULT_DEPLOYMENT_"
+ spec:
+ containers:
+ - name: ssikit-web-wallet
+ image: waltid/ssikit-web-wallet:_VERSION_TAG_
+ ports:
+ - containerPort: 80
+ name: http-api
+---
+kind: Service
+apiVersion: v1
+metadata:
+ name: web-wallet
+spec:
+ ports:
+ - name: http
+ port: 80
+ targetPort: http-api
+ protocol: TCP
+ selector:
+ app: web-wallet
+---
+kind: Deployment
+apiVersion: apps/v1
+metadata:
+ name: verifier-portal
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: verifier-portal
+ template:
+ metadata:
+ labels:
+ app: verifier-portal
+ annotations:
+ deployment/id: "_DEFAULT_DEPLOYMENT_"
+ spec:
+ containers:
+ - name: ssikit-verifier-portal
+ image: waltid/ssikit-verifier-portal:_VERSION_TAG_
+ ports:
+ - containerPort: 80
+ name: http-api
+---
+kind: Service
+apiVersion: v1
+metadata:
+ name: verifier-portal
+spec:
+ ports:
+ - name: http
+ port: 80
+ targetPort: http-api
+ protocol: TCP
+ selector:
+ app: verifier-portal
+---
+kind: Deployment
+apiVersion: apps/v1
+metadata:
+ name: issuer-portal
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: issuer-portal
+ template:
+ metadata:
+ labels:
+ app: issuer-portal
+ annotations:
+ deployment/id: "_DEFAULT_DEPLOYMENT_"
+ spec:
+ containers:
+ - name: ssikit-issuer-portal
+ image: waltid/ssikit-issuer-portal:_VERSION_TAG_
+ ports:
+ - containerPort: 80
+ name: http-api
+---
+kind: Service
+apiVersion: v1
+metadata:
+ name: issuer-portal
+spec:
+ ports:
+ - name: http
+ port: 80
+ targetPort: http-api
+ protocol: TCP
+ selector:
+ app: issuer-portal
+---
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: walletkit
+ annotations:
+ kubernetes.io/ingress.class: "nginx"
+ cert-manager.io/cluster-issuer: letsencrypt-prod
+spec:
+ tls:
+ - hosts:
+ - wallet.walt.id
+ - verifier.walt.id
+ - issuer.walt.id
+ secretName: wallet-tls-secret
+ rules:
+ - host: wallet.walt.id
+ http:
+ paths:
+ - path: /api/
+ pathType: Prefix
+ backend:
+ service:
+ name: walletkit
+ port:
+ number: 80
+ - path: /verifier-api/
+ pathType: Prefix
+ backend:
+ service:
+ name: walletkit
+ port:
+ number: 80
+ - path: /issuer-api/
+ pathType: Prefix
+ backend:
+ service:
+ name: walletkit
+ port:
+ number: 80
+ - path: /webjars
+ pathType: Prefix
+ backend:
+ service:
+ name: walletkit
+ port:
+ number: 80
+ - path: /v2/nftkit/nft/
+ pathType: Prefix
+ backend:
+ service:
+ name: nftkit
+ port:
+ number: 80
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: web-wallet
+ port:
+ number: 80
+ - host: verifier.walt.id
+ http:
+ paths:
+ - path: /verifier-api/
+ pathType: Prefix
+ backend:
+ service:
+ name: walletkit
+ port:
+ number: 80
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: verifier-portal
+ port:
+ number: 80
+ - host: issuer.walt.id
+ http:
+ paths:
+ - path: /issuer-api/
+ pathType: Prefix
+ backend:
+ service:
+ name: walletkit
+ port:
+ number: 80
+ - path: /api/
+ pathType: Prefix
+ backend:
+ service:
+ name: walletkit
+ port:
+ number: 80
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: issuer-portal
+ port:
+ number: 80
diff --git a/sample-data/severin@walt.id/did/created/did%3Akey%3Az6MkjbTEQNiDiRHMytsXSG5CeZZLaqQ7HPRRLzKEVasoMJjt b/sample-data/severin@walt.id/did/created/did%3Akey%3Az6MkjbTEQNiDiRHMytsXSG5CeZZLaqQ7HPRRLzKEVasoMJjt
new file mode 100644
index 0000000..3f86b83
--- /dev/null
+++ b/sample-data/severin@walt.id/did/created/did%3Akey%3Az6MkjbTEQNiDiRHMytsXSG5CeZZLaqQ7HPRRLzKEVasoMJjt
@@ -0,0 +1,33 @@
+{
+ "assertionMethod" : [
+ "z6MkjbTEQNiDiRHMytsXSG5CeZZLaqQ7HPRRLzKEVasoMJjt#z6MkjbTEQNiDiRHMytsXSG5CeZZLaqQ7HPRRLzKEVasoMJjt"
+ ],
+ "authentication" : [
+ "z6MkjbTEQNiDiRHMytsXSG5CeZZLaqQ7HPRRLzKEVasoMJjt#z6MkjbTEQNiDiRHMytsXSG5CeZZLaqQ7HPRRLzKEVasoMJjt"
+ ],
+ "capabilityDelegation" : [
+ "z6MkjbTEQNiDiRHMytsXSG5CeZZLaqQ7HPRRLzKEVasoMJjt#z6MkjbTEQNiDiRHMytsXSG5CeZZLaqQ7HPRRLzKEVasoMJjt"
+ ],
+ "capabilityInvocation" : [
+ "z6MkjbTEQNiDiRHMytsXSG5CeZZLaqQ7HPRRLzKEVasoMJjt#z6MkjbTEQNiDiRHMytsXSG5CeZZLaqQ7HPRRLzKEVasoMJjt"
+ ],
+ "@context" : "https://w3id.org/did/v1",
+ "id" : "did:key:z6MkjbTEQNiDiRHMytsXSG5CeZZLaqQ7HPRRLzKEVasoMJjt",
+ "keyAgreement" : [
+ "z6MkjbTEQNiDiRHMytsXSG5CeZZLaqQ7HPRRLzKEVasoMJjt#z6LSiWG8f5y2ED2LfMESDaKd4joqWFXXya2sa953LwSKgSXa"
+ ],
+ "verificationMethod" : [
+ {
+ "controller" : "did:key:z6MkjbTEQNiDiRHMytsXSG5CeZZLaqQ7HPRRLzKEVasoMJjt",
+ "id" : "z6MkjbTEQNiDiRHMytsXSG5CeZZLaqQ7HPRRLzKEVasoMJjt#z6MkjbTEQNiDiRHMytsXSG5CeZZLaqQ7HPRRLzKEVasoMJjt",
+ "publicKeyBase58" : "69CBp8TnNsntsQ2pkh7MoU1LmG8FsWB4eyQJfJunS5xW",
+ "type" : "Ed25519VerificationKey2018"
+ },
+ {
+ "controller" : "did:key:z6MkjbTEQNiDiRHMytsXSG5CeZZLaqQ7HPRRLzKEVasoMJjt",
+ "id" : "z6MkjbTEQNiDiRHMytsXSG5CeZZLaqQ7HPRRLzKEVasoMJjt#z6LSiWG8f5y2ED2LfMESDaKd4joqWFXXya2sa953LwSKgSXa",
+ "publicKeyBase58" : "7q5y8nAA8kJbZxrfgvofk9bMf6zRGxrihAMMrUnny4kp",
+ "type" : "X25519KeyAgreementKey2019"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/sample-data/severin@walt.id/keystore/alias/a964ad8a6d384bbfbf9f5e8b5d46e1bc b/sample-data/severin@walt.id/keystore/alias/a964ad8a6d384bbfbf9f5e8b5d46e1bc
new file mode 100644
index 0000000..8eb9ce5
--- /dev/null
+++ b/sample-data/severin@walt.id/keystore/alias/a964ad8a6d384bbfbf9f5e8b5d46e1bc
@@ -0,0 +1 @@
+a964ad8a6d384bbfbf9f5e8b5d46e1bc
\ No newline at end of file
diff --git a/sample-data/severin@walt.id/keystore/alias/did%3Akey%3Az6MkjbTEQNiDiRHMytsXSG5CeZZLaqQ7HPRRLzKEVasoMJjt b/sample-data/severin@walt.id/keystore/alias/did%3Akey%3Az6MkjbTEQNiDiRHMytsXSG5CeZZLaqQ7HPRRLzKEVasoMJjt
new file mode 100644
index 0000000..8eb9ce5
--- /dev/null
+++ b/sample-data/severin@walt.id/keystore/alias/did%3Akey%3Az6MkjbTEQNiDiRHMytsXSG5CeZZLaqQ7HPRRLzKEVasoMJjt
@@ -0,0 +1 @@
+a964ad8a6d384bbfbf9f5e8b5d46e1bc
\ No newline at end of file
diff --git a/sample-data/severin@walt.id/keystore/keys/a964ad8a6d384bbfbf9f5e8b5d46e1bc/aliases b/sample-data/severin@walt.id/keystore/keys/a964ad8a6d384bbfbf9f5e8b5d46e1bc/aliases
new file mode 100644
index 0000000..2d51ab0
--- /dev/null
+++ b/sample-data/severin@walt.id/keystore/keys/a964ad8a6d384bbfbf9f5e8b5d46e1bc/aliases
@@ -0,0 +1,2 @@
+a964ad8a6d384bbfbf9f5e8b5d46e1bc
+did:key:z6MkjbTEQNiDiRHMytsXSG5CeZZLaqQ7HPRRLzKEVasoMJjt
\ No newline at end of file
diff --git a/sample-data/severin@walt.id/keystore/keys/a964ad8a6d384bbfbf9f5e8b5d46e1bc/enc-privkey b/sample-data/severin@walt.id/keystore/keys/a964ad8a6d384bbfbf9f5e8b5d46e1bc/enc-privkey
new file mode 100644
index 0000000..8a45d00
--- /dev/null
+++ b/sample-data/severin@walt.id/keystore/keys/a964ad8a6d384bbfbf9f5e8b5d46e1bc/enc-privkey
@@ -0,0 +1,3 @@
+-----BEGIN PRIVATE KEY-----
+MC4CAQAwBQYDK2VwBCIEICW6AMuorx657CJNYjm1KKQEnJ8sS+SedyM9pXlNvB+R
+-----END PRIVATE KEY-----
\ No newline at end of file
diff --git a/sample-data/severin@walt.id/keystore/keys/a964ad8a6d384bbfbf9f5e8b5d46e1bc/enc-pubkey b/sample-data/severin@walt.id/keystore/keys/a964ad8a6d384bbfbf9f5e8b5d46e1bc/enc-pubkey
new file mode 100644
index 0000000..4053abc
--- /dev/null
+++ b/sample-data/severin@walt.id/keystore/keys/a964ad8a6d384bbfbf9f5e8b5d46e1bc/enc-pubkey
@@ -0,0 +1,3 @@
+-----BEGIN PUBLIC KEY-----
+MCowBQYDK2VwAyEATGO6yUbN3/jo4KXOdsILos8E+o4qBsppQ5dNleU115s=
+-----END PUBLIC KEY-----
\ No newline at end of file
diff --git a/sample-data/severin@walt.id/keystore/keys/a964ad8a6d384bbfbf9f5e8b5d46e1bc/meta b/sample-data/severin@walt.id/keystore/keys/a964ad8a6d384bbfbf9f5e8b5d46e1bc/meta
new file mode 100644
index 0000000..34b0129
--- /dev/null
+++ b/sample-data/severin@walt.id/keystore/keys/a964ad8a6d384bbfbf9f5e8b5d46e1bc/meta
@@ -0,0 +1 @@
+EdDSA_Ed25519;SUN
\ No newline at end of file
diff --git a/sample-data/severin@walt.id/vc/custodian/identity%23VerifiableDiploma%2366c4c129-fe6e-4498-ba73-1f145a2794b2 b/sample-data/severin@walt.id/vc/custodian/identity%23VerifiableDiploma%2366c4c129-fe6e-4498-ba73-1f145a2794b2
new file mode 100644
index 0000000..eb768ee
--- /dev/null
+++ b/sample-data/severin@walt.id/vc/custodian/identity%23VerifiableDiploma%2366c4c129-fe6e-4498-ba73-1f145a2794b2
@@ -0,0 +1 @@
+{"@context" : ["https://www.w3.org/2018/credentials/v1"], "credentialSchema" : {"id" : "https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd", "type" : "JsonSchemaValidator2018"}, "credentialSubject" : {"awardingOpportunity" : {"awardingBody" : {"homepage" : "https://leaston.bcdiploma.com/", "id" : "did:key:z6MkjbTEQNiDiRHMytsXSG5CeZZLaqQ7HPRRLzKEVasoMJjt", "preferredName" : "Leaston University", "registration" : "0597065J"}, "endedAtTime" : "2020-06-26T00:00:00Z", "id" : "https://leaston.bcdiploma.com/law-economics-management#AwardingOpportunity", "identifier" : "https://certificate-demo.bcdiploma.com/check/87ED2F2270E6C41456E94B86B9D9115B4E35BCCAD200A49B846592C14F79C86BV1Fnbllta0NZTnJkR3lDWlRmTDlSRUJEVFZISmNmYzJhUU5sZUJ5Z2FJSHpWbmZZ", "location" : "AUSTRIA", "startedAtTime" : "2019-09-02T00:00:00Z"}, "dateOfBirth" : "1983-07-05", "familyName" : "STAMPLER", "givenNames" : "Severin", "gradingScheme" : {"id" : "https://leaston.bcdiploma.com/law-economics-management#GradingScheme", "title" : "Lower Second-Class Honours"}, "id" : "did:key:z6MkjbTEQNiDiRHMytsXSG5CeZZLaqQ7HPRRLzKEVasoMJjt", "identifier" : "0904008084H", "learningAchievement" : {"additionalNote" : ["DISTRIBUTION MANAGEMENT"], "description" : "MARKETING AND SALES", "id" : "https://leaston.bcdiploma.com/law-economics-management#LearningAchievment", "title" : "MASTERS LAW, ECONOMICS AND MANAGEMENT"}, "learningSpecification" : {"ectsCreditPoints" : 120, "eqfLevel" : 7, "id" : "https://leaston.bcdiploma.com/law-economics-management#LearningSpecification", "iscedfCode" : ["7"], "nqfLevel" : ["7"]}}, "id" : "identity#VerifiableDiploma#66c4c129-fe6e-4498-ba73-1f145a2794b2", "issuanceDate" : "2021-11-04T13:21:09Z", "issuer" : "did:key:z6MkjbTEQNiDiRHMytsXSG5CeZZLaqQ7HPRRLzKEVasoMJjt", "proof" : {"created" : "2021-11-04T12:21:51Z", "creator" : "did:key:z6MkjbTEQNiDiRHMytsXSG5CeZZLaqQ7HPRRLzKEVasoMJjt", "domain" : "https://api.preprod.ebsi.eu", "jws" : "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..2fm-tErR_vPnWP-MmXnhRF175hxxXStqG-LfOvFrYWTPf1Iefv2GNVJxC23c4Wjk-eREdNHYsGB_2VJ7pWVFBw", "nonce" : "d81ab826-5bbb-453f-868d-ab9fd6e66a27", "type" : "Ed25519Signature2018"}, "validFrom" : "2021-11-04T13:21:09Z", "type" : ["VerifiableCredential", "VerifiableAttestation", "VerifiableDiploma"]}
\ No newline at end of file
diff --git a/sample-data/severin@walt.id/vc/custodian/identity%23VerifiableId%238965c872-dba0-4560-81dd-8a049b3d6506 b/sample-data/severin@walt.id/vc/custodian/identity%23VerifiableId%238965c872-dba0-4560-81dd-8a049b3d6506
new file mode 100644
index 0000000..b3569e0
--- /dev/null
+++ b/sample-data/severin@walt.id/vc/custodian/identity%23VerifiableId%238965c872-dba0-4560-81dd-8a049b3d6506
@@ -0,0 +1 @@
+{"@context" : ["https://www.w3.org/2018/credentials/v1"], "credentialSchema" : {"id" : "https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0x2488fd38783d65e4fd46e7889eb113743334dbc772b05df382b8eadce763101b", "type" : "JsonSchemaValidator2018"}, "credentialSubject" : {"currentAddress" : "Siebenbrunnengasse 10/1/7, 1050 Wien, AUSTRIA", "dateOfBirth" : "1983-07-05", "familyName" : "STAMPLER", "firstName" : "Severin", "gender" : "MALE", "id" : "did:key:z6MkjbTEQNiDiRHMytsXSG5CeZZLaqQ7HPRRLzKEVasoMJjt", "nameAndFamilyNameAtBirth" : "Jane DOE", "personalIdentifier" : "0904008084H", "placeOfBirth" : "GRAZ, AUSTRIA"}, "evidence" : {"documentPresence" : ["Physical"], "evidenceDocument" : ["Passport"], "subjectPresence" : "Physical", "type" : ["DocumentVerification"], "verifier" : "did:ebsi:2A9BZ9SUe6BatacSpvs1V5CdjHvLpQ7bEsi2Jb6LdHKnQxaN"}, "id" : "identity#VerifiableId#8965c872-dba0-4560-81dd-8a049b3d6506", "issuanceDate" : "2021-08-31T00:00:00Z", "issuer" : "did:key:z6MkjbTEQNiDiRHMytsXSG5CeZZLaqQ7HPRRLzKEVasoMJjt", "proof" : {"created" : "2021-10-19T14:23:28Z", "creator" : "did:key:z6MkjbTEQNiDiRHMytsXSG5CeZZLaqQ7HPRRLzKEVasoMJjt", "domain" : "https://api.preprod.ebsi.eu", "jws" : "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..FOw7spReOih7p-2V5G7rRFbo-0aEmy0Dz8C5chFvjaFp_etOQjHo7bSeaHYl5pOeB24v5kwTBPZ-UG7m2A1bDQ", "nonce" : "bb6bc626-f0c3-4153-97d3-f9edf16ab117", "type" : "Ed25519Signature2018"}, "validFrom" : "2021-08-31T00:00:00Z", "type" : ["VerifiableCredential", "VerifiableAttestation", "VerifiableId"]}
\ No newline at end of file
diff --git a/sample-data/severin@walt.id/vc/signatory/identity%23VerifiableId%238965c872-dba0-4560-81dd-8a049b3d6506 b/sample-data/severin@walt.id/vc/signatory/identity%23VerifiableId%238965c872-dba0-4560-81dd-8a049b3d6506
new file mode 100644
index 0000000..b3569e0
--- /dev/null
+++ b/sample-data/severin@walt.id/vc/signatory/identity%23VerifiableId%238965c872-dba0-4560-81dd-8a049b3d6506
@@ -0,0 +1 @@
+{"@context" : ["https://www.w3.org/2018/credentials/v1"], "credentialSchema" : {"id" : "https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0x2488fd38783d65e4fd46e7889eb113743334dbc772b05df382b8eadce763101b", "type" : "JsonSchemaValidator2018"}, "credentialSubject" : {"currentAddress" : "Siebenbrunnengasse 10/1/7, 1050 Wien, AUSTRIA", "dateOfBirth" : "1983-07-05", "familyName" : "STAMPLER", "firstName" : "Severin", "gender" : "MALE", "id" : "did:key:z6MkjbTEQNiDiRHMytsXSG5CeZZLaqQ7HPRRLzKEVasoMJjt", "nameAndFamilyNameAtBirth" : "Jane DOE", "personalIdentifier" : "0904008084H", "placeOfBirth" : "GRAZ, AUSTRIA"}, "evidence" : {"documentPresence" : ["Physical"], "evidenceDocument" : ["Passport"], "subjectPresence" : "Physical", "type" : ["DocumentVerification"], "verifier" : "did:ebsi:2A9BZ9SUe6BatacSpvs1V5CdjHvLpQ7bEsi2Jb6LdHKnQxaN"}, "id" : "identity#VerifiableId#8965c872-dba0-4560-81dd-8a049b3d6506", "issuanceDate" : "2021-08-31T00:00:00Z", "issuer" : "did:key:z6MkjbTEQNiDiRHMytsXSG5CeZZLaqQ7HPRRLzKEVasoMJjt", "proof" : {"created" : "2021-10-19T14:23:28Z", "creator" : "did:key:z6MkjbTEQNiDiRHMytsXSG5CeZZLaqQ7HPRRLzKEVasoMJjt", "domain" : "https://api.preprod.ebsi.eu", "jws" : "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..FOw7spReOih7p-2V5G7rRFbo-0aEmy0Dz8C5chFvjaFp_etOQjHo7bSeaHYl5pOeB24v5kwTBPZ-UG7m2A1bDQ", "nonce" : "bb6bc626-f0c3-4153-97d3-f9edf16ab117", "type" : "Ed25519Signature2018"}, "validFrom" : "2021-08-31T00:00:00Z", "type" : ["VerifiableCredential", "VerifiableAttestation", "VerifiableId"]}
\ No newline at end of file
diff --git a/secrets/issuers.json b/secrets/issuers.json
new file mode 100644
index 0000000..f381aef
--- /dev/null
+++ b/secrets/issuers.json
@@ -0,0 +1,8 @@
+{
+ "secrets": {
+ "walt.id": {
+ "client_id": "FOO",
+ "client_secret": "BAR"
+ }
+ }
+}
diff --git a/service-matrix.properties b/service-matrix.properties
new file mode 100644
index 0000000..aa16fe4
--- /dev/null
+++ b/service-matrix.properties
@@ -0,0 +1,16 @@
+id.walt.services.ecosystems.essif.didebsi.DidEbsiService=id.walt.services.ecosystems.essif.didebsi.WaltIdDidEbsiService
+id.walt.services.ecosystems.essif.jsonrpc.JsonRpcService=id.walt.services.ecosystems.essif.jsonrpc.WaltIdJsonRpcService
+id.walt.services.vc.JsonLdCredentialService=id.walt.services.vc.WaltIdJsonLdCredentialService
+id.walt.services.vc.JwtCredentialService=id.walt.services.vc.WaltIdJwtCredentialService
+id.walt.services.crypto.CryptoService=id.walt.services.crypto.SunCryptoService
+id.walt.services.keystore.KeyStoreService=id.walt.services.keystore.SqlKeyStoreService
+id.walt.services.key.KeyService=id.walt.services.key.WaltIdKeyService
+id.walt.services.jwt.JwtService=id.walt.services.jwt.WaltIdJwtService
+id.walt.services.vcstore.VcStoreService=id.walt.services.vcstore.FileSystemVcStoreService
+id.walt.services.hkvstore.HKVStoreService=id.walt.services.hkvstore.FileSystemHKVStore:config/fsStore.conf
+id.walt.services.context.ContextManager=id.walt.services.context.WaltIdContextManager
+id.walt.signatory.Signatory=id.walt.signatory.WaltIdSignatory:config/signatory.conf
+id.walt.custodian.Custodian=id.walt.custodian.WaltIdCustodian
+id.walt.auditor.Auditor=id.walt.auditor.WaltIdAuditor
+id.walt.services.ecosystems.gaiax.GaiaxService=id.walt.services.ecosystems.gaiax.WaltIdGaiaxService
+id.walt.verifier.backend.VerifierManager=id.walt.verifier.backend.DefaultVerifierManager
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 0000000..df0c3fa
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,3 @@
+
+rootProject.name = "waltid-walletkit"
+
diff --git a/signatory.conf b/signatory.conf
new file mode 100644
index 0000000..33edfc9
--- /dev/null
+++ b/signatory.conf
@@ -0,0 +1,9 @@
+
+
+proofConfig {
+ issuerDid="todo"
+ issuerVerificationMethod="todo"
+ proofType="LD_PROOF"
+ domain="todo"
+ nonce="todo"
+}
diff --git a/src/main/kotlin/id/walt/Main.kt b/src/main/kotlin/id/walt/Main.kt
new file mode 100644
index 0000000..2d8877f
--- /dev/null
+++ b/src/main/kotlin/id/walt/Main.kt
@@ -0,0 +1,74 @@
+package id.walt
+
+import com.github.ajalt.clikt.core.subcommands
+import id.walt.cli.*
+import id.walt.multitenancy.ConfigureTenantCmd
+import id.walt.multitenancy.TenantCmd
+import id.walt.servicematrix.ServiceMatrix
+import id.walt.servicematrix.ServiceRegistry
+import id.walt.services.context.ContextManager
+import id.walt.webwallet.backend.cli.ConfigCmd
+import id.walt.webwallet.backend.cli.RunCmd
+import id.walt.webwallet.backend.cli.WalletCmd
+import id.walt.webwallet.backend.context.WalletContextManager
+import kotlinx.coroutines.runBlocking
+
+
+val WALTID_WALLET_BACKEND_PORT = System.getenv("WALTID_WALLET_BACKEND_PORT")?.toIntOrNull() ?: 8080
+var WALTID_WALLET_BACKEND_BIND_ADDRESS = System.getenv("WALTID_WALLET_BACKEND_BIND_ADDRESS") ?: "0.0.0.0"
+
+val WALTID_DATA_ROOT = /*System.getenv("WALTID_DATA_ROOT") ?:*/ "."
+
+fun main(args: Array): Unit = runBlocking {
+
+ ServiceMatrix("service-matrix.properties")
+ ServiceRegistry.registerService(WalletContextManager)
+ WalletCmd().subcommands(
+ RunCmd(),
+ ConfigCmd().subcommands(
+ KeyCommand().subcommands(
+ GenKeyCommand(),
+ ListKeysCommand(),
+ ImportKeyCommand(),
+ ExportKeyCommand()
+ ),
+ DidCommand().subcommands(
+ CreateDidCommand(),
+ ResolveDidCommand(),
+ ListDidsCommand(),
+ ImportDidCommand()
+ ),
+ EssifCommand().subcommands(
+ EssifOnboardingCommand(),
+ EssifAuthCommand(),
+// EssifVcIssuanceCommand(),
+// EssifVcExchangeCommand(),
+ EssifDidCommand().subcommands(
+ EssifDidRegisterCommand()
+ )
+ ),
+ VcCommand().subcommands(
+ VcIssueCommand(),
+ PresentVcCommand(),
+ VerifyVcCommand(),
+ ListVcCommand(),
+ VerificationPoliciesCommand().subcommands(
+ ListVerificationPoliciesCommand(),
+ CreateDynamicVerificationPolicyCommand(),
+ RemoveDynamicVerificationPolicyCommand()
+ ),
+ VcTemplatesCommand().subcommands(
+ VcTemplatesListCommand(),
+ VcTemplatesImportCommand(),
+ VcTemplatesExportCommand(),
+ VcTemplatesRemoveCommand()
+ ),
+ VcImportCommand()
+ ),
+ TenantCmd().subcommands(
+ ConfigureTenantCmd()
+ ),
+ ServeCommand()
+ )
+ ).main(args)
+}
diff --git a/src/main/kotlin/id/walt/customTemplates/EHIC.kt b/src/main/kotlin/id/walt/customTemplates/EHIC.kt
new file mode 100644
index 0000000..b7dccb5
--- /dev/null
+++ b/src/main/kotlin/id/walt/customTemplates/EHIC.kt
@@ -0,0 +1,138 @@
+package id.walt.customTemplates
+
+import com.beust.klaxon.Json
+import id.walt.vclib.model.*
+import id.walt.vclib.registry.VerifiableCredentialMetadata
+import model.*
+
+
+data class EHIC(
+ @Json(name = "@context") var context: List? = listOf("https://www.w3.org/2018/credentials/v1"),
+ @Json(serializeNull = false) var credentialStatus: CredentialStatus? = null,
+ @Json(serializeNull = false) override var credentialSubject: CredentialSubject? = null,
+ @Json(serializeNull = false) override var expirationDate: String? = null, // "2024-01-01T20:38:38Z"
+ @Json(serializeNull = false) override var id: String? = "did:ebsi:zsSgDXeYPhZ3AuKhTFneDf1", // "urn:uuid:27e83c2d-d230-43a7-9229-2e72d3570a27"
+ @Json(serializeNull = false) override var issued: String? = "2022-12-30T15:25:20Z", // "2022-12-30T15:25:20Z"
+ @Json(serializeNull = false) override var issuer: String? = "did:ebsi:z25Hi99z7n2tyKnfkU3p6SyW", // "did:ebsi:z25Hi99z7n2tyKnfkU3p6SyW"
+ @Json(serializeNull = false) override val credentialSchema: CredentialSchema? = null,
+ @Json(serializeNull = false) override var validFrom: String? = null,
+ @Json(serializeNull = false) override var proof: Proof? = null
+) : AbstractVerifiableCredential(type) {
+ data class CredentialStatus(
+ @Json(serializeNull = false) var id: String? = null, // "https://essif.europa.eu/status/identity#verifiableID#1dee355d-0432-4910-ac9c-70d89e8d674e"
+ @Json(serializeNull = false) var type: String? = null // "CredentialStatusList2020"
+ )
+
+ data class CredentialSubject(
+ @Json(serializeNull = false) override var id: String? = "did:ebsi:zsSgDXeYPhZ3AuKhTFneDf1", // "urn:uuid:27e83c2d-d230-43a7-9229-2e72d3570a27",
+ @Json(serializeNull = false) var name: String? = null, // "Amaador"
+ @Json(serializeNull = false) var givenNames: String? = null, // "Soufiane"
+ @Json(serializeNull = false) var dateOfBirth: String? = null, // "02/04/1999"
+ @Json(serializeNull = false) var personalIdentificationNumber: String? = null, // "012345678"
+ @Json(serializeNull = false) var identificationOfTheInstitution: String? = null, // "3311 - Zilveren Kruis"
+ @Json(serializeNull = false) var identificationNumberOfTheCard: String? = null, // "01234567890123456789"
+ @Json(serializeNull = false) var expiryDate: String? = null, // "12/12/2024"
+ @Json(serializeNull = false) var insurer: model.Insurer? = null,
+ @Json(serializeNull = false) var bankDetails: model.BankDetails? = null,
+ @Json(serializeNull = false) var address: model.Address? = null,
+ @Json(serializeNull = false) var telephone: model.Telephone? = null
+ ) : id.walt.vclib.model.CredentialSubject() {
+ data class Insurer(
+ @Json(serializeNull = false) var identificationNumber: String? = null, // "3311"
+ @Json(serializeNull = false) var organisationName: String? = null, // "Zilveren Kruis"
+ @Json(serializeNull = false) var polisNumber: String? = null, // "12345678"
+ @Json(serializeNull = false, name = "Insurance") var insurance: Insurance? = null,
+ @Json(serializeNull = false, name = "Address") var address: Address? = null,
+ @Json(serializeNull = false, name = "Telephone") var telephone: Telephone? = null
+ ) {
+ data class Insurance(
+ @Json(serializeNull = false) var startDate: String? = null, // "01-01-2023"
+ @Json(serializeNull = false) var endDate: String? = null, // "31-01-2024"
+ @Json(serializeNull = false) var insuranceType: String? = null // "Basic Insured"
+ )
+ }
+
+ data class BankDetails(
+ @Json(serializeNull = false) var bankName: String? = null, // "ING"
+ @Json(serializeNull = false) var bankCode: String? = null, // "INGBNL2A"
+ @Json(serializeNull = false) var accountNumber: String? = null // "NL85INGB0001234567"
+ )
+
+ data class Address(
+ @Json(serializeNull = false) var street: String? = null, // "1e Jacob van Campenstr"
+ @Json(serializeNull = false) var houseNumber: String? = null, // "15"
+ @Json(serializeNull = false) var postcode: String? = null, // "1012 NX "
+ @Json(serializeNull = false) var residence: String? = null, // "Hoogmade"
+ @Json(serializeNull = false) var municipality: String? = null, // "Kaag en Braassem"
+ @Json(serializeNull = false) var country: String? = null, // "Netherlands"
+ @Json(serializeNull = false) var addressType: String? = null // "Residential/residence address"
+ )
+
+ data class Telephone(
+ @Json(serializeNull = false) var phoneNumber: String? = null, // "+31505233333"
+ @Json(serializeNull = false) var numberType: String? = null // "Business"
+ )
+
+ }
+ companion object : VerifiableCredentialMetadata(
+ type = listOf("VerifiableCredential", "EuropeanHealthInsuranceCard"),
+ template = {
+ EHIC(
+ credentialStatus = CredentialStatus(
+ id = "https://api-pilot.ebsi.eu/trusted-schemas-registry/v1/schemas/",
+ type = "CredentialStatusList2020"
+ ),
+ credentialSubject = CredentialSubject(
+ id = "did:ebsi:123456789",
+ name ="Doe",
+ givenNames = "John",
+ dateOfBirth = "1999-04-02",
+ personalIdentificationNumber = "012345678",
+ identificationOfTheInstitution = "3311 - Zilveren Kruis",
+ identificationNumberOfTheCard = "01234567890123456789",
+ insurer = Insurer(
+ identificationNumber = "3311",
+ organisationName = "Zilveren Kruis",
+ polisNumber = "12345678",
+ insurance = Insurance(
+ startDate = "2023-01-01",
+ endDate = "2024-01-31",
+ insuranceType = "Basic Insured"
+ ),
+ address = Address(
+ street = "Postbus",
+ houseNumber = "34000",
+ postcode = "7500 KC",
+ residence = "Enschede",
+ country = "Netherlands"
+ ),
+ telephone = Telephone(
+ phoneNumber = "+31505233333",
+ numberType = "Business"
+ )
+ ),
+ expiryDate = "2024-01-31",
+ bankDetails = BankDetails(
+ bankName = "ING",
+ bankCode = "INGBNL2A",
+ accountNumber = "NL85INGB0001234567"
+ ),
+ address = Address(
+ street = "1e Jacob van Campenstr",
+ houseNumber = "15",
+ postcode = "1012 NX",
+ residence = "Hoogmade",
+ municipality = "Kaag en Braasem",
+ addressType = "Residential/residence address",
+ country = "Netherlands"
+ ),
+ telephone = Telephone(
+ phoneNumber = "+311725233111",
+ numberType = "private"
+ )
+
+ ),
+ issuer = "did:ebsi:zr2rWDHHrUCdZAW7wsSb5nQ", //SIGNATORY DID
+ )
+ })
+}
\ No newline at end of file
diff --git a/src/main/kotlin/id/walt/customTemplates/Insurer.kt b/src/main/kotlin/id/walt/customTemplates/Insurer.kt
new file mode 100644
index 0000000..4ccdeb2
--- /dev/null
+++ b/src/main/kotlin/id/walt/customTemplates/Insurer.kt
@@ -0,0 +1,10 @@
+package model
+
+class Insurer (
+ var identificationNumber: String,
+ var organisationName: String,
+ var polisNumber: String,
+ var insurance: Insurance,
+ var address: Address,
+ var telephone: Telephone
+ )
\ No newline at end of file
diff --git a/src/main/kotlin/id/walt/customTemplates/User.kt b/src/main/kotlin/id/walt/customTemplates/User.kt
new file mode 100644
index 0000000..2d50758
--- /dev/null
+++ b/src/main/kotlin/id/walt/customTemplates/User.kt
@@ -0,0 +1,21 @@
+package model
+
+import com.fasterxml.jackson.annotation.JsonProperty
+import java.time.LocalDate
+
+data class User(
+ @JsonProperty("name") var name: String,
+ @JsonProperty var givenNames: String,
+ @JsonProperty var dateOfBirth: LocalDate,
+ @JsonProperty var personalIdentificationNumber: String,
+ @JsonProperty var identificationOfTheInstitution: String,
+ @JsonProperty var identificationNumberOfTheCard: String,
+ @JsonProperty var insurer: Insurer,
+ @JsonProperty var expiryDate: LocalDate,
+ @JsonProperty var bankDetails: BankDetails,
+ @JsonProperty var address: Address,
+ @JsonProperty var telephone: Telephone
+)
+
+
+
diff --git a/src/main/kotlin/id/walt/customTemplates/UserMetaData.kt b/src/main/kotlin/id/walt/customTemplates/UserMetaData.kt
new file mode 100644
index 0000000..55132d1
--- /dev/null
+++ b/src/main/kotlin/id/walt/customTemplates/UserMetaData.kt
@@ -0,0 +1,28 @@
+package model
+
+data class Address(
+ var street: String,
+ var houseNumber: String,
+ var postcode: String,
+ var residence: String,
+ var municipality: String? = null,
+ var country: String,
+ var addressType: String ? = null
+)
+
+data class BankDetails(
+ var bankName: String,
+ var bankCode: String,
+ var accountNumber: String
+)
+
+data class Insurance(
+ var startDate: String,
+ var endDate: String,
+ var insuranceType: String
+)
+
+data class Telephone(
+ var phoneNumber: String,
+ var numberType: String
+)
\ No newline at end of file
diff --git a/src/main/kotlin/id/walt/issuer/backend/IssuableCredential.kt b/src/main/kotlin/id/walt/issuer/backend/IssuableCredential.kt
new file mode 100644
index 0000000..5d1432a
--- /dev/null
+++ b/src/main/kotlin/id/walt/issuer/backend/IssuableCredential.kt
@@ -0,0 +1,53 @@
+package id.walt.issuer.backend
+
+import id.walt.credentials.w3c.*
+import id.walt.credentials.w3c.templates.VcTemplateManager
+import id.walt.customTemplates.EHIC
+import id.walt.model.oidc.CredentialAuthorizationDetails
+import id.walt.signatory.rest.SignatoryController
+
+data class IssuableCredential(
+ val type: String,
+ val credentialData: Map? = null
+) {
+ companion object {
+ fun fromTemplateId(templateId: String): IssuableCredential {
+
+ val tmpl = VcTemplateManager.getTemplate(templateId, true).template!!
+
+ return IssuableCredential(
+ tmpl.type.last(),
+ mapOf(
+ Pair(
+ "credentialSubject",
+ JsonConverter.fromJsonElement(tmpl.credentialSubject!!.toJsonObject()) as Map<*, *>
+ )
+ )
+ )
+ }
+ }
+}
+
+
+data class Issuables(
+ val credentials: List
+) {
+
+ /* DEPRECATED: Does not work for issuing two credentials of the same type (but with different data)
+ val credentialsByType
+ get() = credentials.associateBy { it.type }
+ */
+
+ companion object {
+ fun fromCredentialAuthorizationDetails(credentialDetails: List): Issuables {
+ return Issuables(
+ credentials = credentialDetails.map { IssuableCredential.fromTemplateId(it.credential_type) }
+ )
+ }
+ }
+}
+
+data class NonceResponse(
+ val p_nonce: String,
+ val expires_in: String? = null
+)
diff --git a/src/main/kotlin/id/walt/issuer/backend/IssuanceSession.kt b/src/main/kotlin/id/walt/issuer/backend/IssuanceSession.kt
new file mode 100644
index 0000000..70d0d8c
--- /dev/null
+++ b/src/main/kotlin/id/walt/issuer/backend/IssuanceSession.kt
@@ -0,0 +1,16 @@
+package id.walt.issuer.backend
+
+import com.nimbusds.oauth2.sdk.AuthorizationRequest
+import id.walt.model.oidc.CredentialAuthorizationDetails
+
+data class IssuanceSession(
+ val id: String,
+ val credentialDetails: List,
+ val nonce: String,
+ val isPreAuthorized: Boolean,
+ var authRequest: AuthorizationRequest?,
+ var issuables: Issuables?,
+ var did: String? = null,
+ val userPin: String? = null,
+ var issuerDid: String? = null
+)
diff --git a/src/main/kotlin/id/walt/issuer/backend/IssuerConfig.kt b/src/main/kotlin/id/walt/issuer/backend/IssuerConfig.kt
new file mode 100644
index 0000000..c14e304
--- /dev/null
+++ b/src/main/kotlin/id/walt/issuer/backend/IssuerConfig.kt
@@ -0,0 +1,49 @@
+package id.walt.issuer.backend
+
+import com.beust.klaxon.Json
+import com.beust.klaxon.Klaxon
+import id.walt.multitenancy.TenantConfig
+import id.walt.multitenancy.TenantConfigFactory
+import id.walt.verifier.backend.WalletConfiguration
+import id.walt.webwallet.backend.config.ExternalHostnameUrl
+import id.walt.webwallet.backend.config.externalHostnameUrlValueConverter
+import java.io.File
+
+data class IssuerConfig(
+ @ExternalHostnameUrl val issuerUiUrl: String = "http://localhost:8082",
+ @ExternalHostnameUrl val issuerApiUrl: String = "http://localhost:8080/issuer-api/default",
+ @Json(serializeNull = false) val issuerClientName: String = "Walt.id Issuer Portal",
+ val wallets: Map = WalletConfiguration.getDefaultWalletConfigurations(),
+ val issuerDid: String? = null
+) : TenantConfig {
+ @Json(ignored = true)
+ val onboardingApiUrl
+ get() = issuerApiUrl.replace("/issuer-api", "/onboarding-api")
+
+ @Json(ignored = true)
+ val onboardingUiUrl
+ get() = "$issuerUiUrl/Onboarding/"
+
+ override fun toJson(): String {
+ return Klaxon().fieldConverter(ExternalHostnameUrl::class, externalHostnameUrlValueConverter).toJsonString(this)
+ }
+
+ companion object : TenantConfigFactory {
+
+ val CONFIG_FILE = "${id.walt.WALTID_DATA_ROOT}/config/issuer-config.json"
+
+ override fun fromJson(json: String): IssuerConfig {
+ return Klaxon().fieldConverter(ExternalHostnameUrl::class, externalHostnameUrlValueConverter)
+ .parse(json) ?: IssuerConfig()
+ }
+
+ override fun forDefaultTenant(): IssuerConfig {
+ val cf = File(CONFIG_FILE)
+ return if (cf.exists()) {
+ fromJson(cf.readText())
+ } else {
+ IssuerConfig()
+ }
+ }
+ }
+}
diff --git a/src/main/kotlin/id/walt/issuer/backend/IssuerController.kt b/src/main/kotlin/id/walt/issuer/backend/IssuerController.kt
new file mode 100644
index 0000000..b1f4738
--- /dev/null
+++ b/src/main/kotlin/id/walt/issuer/backend/IssuerController.kt
@@ -0,0 +1,390 @@
+package id.walt.issuer.backend
+
+import com.nimbusds.oauth2.sdk.*
+import com.nimbusds.oauth2.sdk.http.ServletUtils
+import com.nimbusds.oauth2.sdk.token.BearerAccessToken
+import com.nimbusds.oauth2.sdk.token.RefreshToken
+import com.nimbusds.openid.connect.sdk.OIDCTokenResponse
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata
+import com.nimbusds.openid.connect.sdk.token.OIDCTokens
+import id.walt.common.KlaxonWithConverters
+import id.walt.credentials.w3c.toVerifiableCredential
+import id.walt.model.oidc.CredentialRequest
+import id.walt.model.oidc.CredentialResponse
+import id.walt.multitenancy.Tenant
+import id.walt.multitenancy.TenantId
+import id.walt.rest.core.DidController
+import id.walt.rest.core.KeyController
+import id.walt.services.oidc.OIDC4CIService
+import id.walt.signatory.rest.SignatoryController
+import id.walt.verifier.backend.WalletConfiguration
+import id.walt.webwallet.backend.auth.JWTService
+import id.walt.webwallet.backend.auth.UserInfo
+import id.walt.webwallet.backend.context.WalletContextManager
+import id.walt.webwallet.backend.wallet.DidCreationRequest
+import id.walt.webwallet.backend.wallet.WalletController
+import io.javalin.apibuilder.ApiBuilder.*
+import io.javalin.http.*
+import io.javalin.plugin.openapi.dsl.OpenApiDocumentation
+import io.javalin.plugin.openapi.dsl.document
+import io.javalin.plugin.openapi.dsl.documented
+import mu.KotlinLogging
+import java.net.URI
+
+object IssuerController {
+ private val logger = KotlinLogging.logger { }
+ val routes
+ get() =
+ path("{tenantId}") {
+ before { ctx ->
+ logger.info { "Setting issuer API context: ${ctx.pathParam("tenantId")}" }
+ WalletContextManager.setCurrentContext(IssuerManager.getIssuerContext(ctx.pathParam("tenantId")))
+ }
+ after {
+ logger.info { "Resetting issuer API context" }
+ WalletContextManager.resetCurrentContext()
+ }
+ path("wallets") {
+ get("list", documented(
+ document().operation {
+ it.summary("List wallet configurations")
+ .addTagsItem("Issuer")
+ .operationId("listWallets")
+ }
+ .pathParam("tenantId") { it.example(TenantId.DEFAULT_TENANT) }
+ .jsonArray("200"),
+ IssuerController::listWallets,
+ ))
+ }
+ path("config") {
+ fun OpenApiDocumentation.describeTenantId() =
+ this.run { pathParam("tenantId") { it.example(TenantId.DEFAULT_TENANT) } }
+
+ path("did") {
+ post("create", documented(DidController.createDocs().describeTenantId(), DidController::create))
+ post("createAdvanced",
+ documented(document().operation {
+ it.summary("Create new DID")
+ .description("Creates and registers a DID. Currently the DID methods: key, web, ebsi (v1/v2) and iota are supported. For EBSI v1: a bearer token is required.")
+ .operationId("createAdvanced").addTagsItem("Issuer Configuration")
+ .addTagsItem("Decentralized Identifiers")
+ }
+ .body()
+ .result("200"),
+ WalletController::createDid
+ )
+ )
+ post("import", documented(DidController.importDocs().describeTenantId(), DidController::import))
+ get("list", documented(DidController.listDocs().describeTenantId(), DidController::list))
+ post("delete", documented(DidController.deleteDocs().describeTenantId(), DidController::delete))
+ }
+
+ path("key") {
+ post("gen", documented(KeyController.genDocs().describeTenantId(), KeyController::gen))
+ post("import", documented(KeyController.importDocs().describeTenantId(), KeyController::import))
+ post("export", documented(KeyController.exportDocs().describeTenantId(), KeyController::export))
+ delete("delete", documented(KeyController.deleteDocs().describeTenantId(), KeyController::delete))
+ get("list", documented(KeyController.listDocs().describeTenantId(), KeyController::list))
+ post("load", documented(KeyController.loadDocs().describeTenantId(), KeyController::load))
+ }
+
+ post("setConfiguration", documented(document().operation {
+ it.summary("Set configuration for this issuer tenant").operationId("setConfiguration")
+ .addTagsItem("Issuer Configuration")
+ }
+ .pathParam("tenantId") { it.example(TenantId.DEFAULT_TENANT) }
+ .body()
+ .json("200"), IssuerController::setConfiguration))
+ get("getConfiguration", documented(document().operation {
+ it.summary("Get configuration for this issuer tenant").operationId("getConfiguration")
+ .addTagsItem("Issuer Configuration")
+ }
+ .pathParam("tenantId") { it.example(TenantId.DEFAULT_TENANT) }
+ .json("200"), IssuerController::getConfiguration
+ ))
+ path("templates") {
+ get("", documented(document().operation {
+ it.summary("List templates").operationId("listTemplates").addTagsItem("Issuer Configuration")
+ }.json>("200"), SignatoryController::listTemplates))
+ get("{id}", documented(document().operation {
+ it.summary("Load a VC template").operationId("loadTemplate").addTagsItem("Issuer Configuration")
+ }.pathParam("id") { it.description("Retrieves a single VC template form the data store") }
+ .json("200"), SignatoryController::loadTemplate))
+ post(
+ "{id}", documented(
+ document().operation {
+ it.summary("Import a VC template").operationId("importTemplate")
+ .addTagsItem("Issuer Configuration")
+ }.pathParam("id").body(contentType = ContentType.JSON).result("200"),
+ //SignatoryController::importTemplate
+ SignatoryController::importTemplate
+ )
+ )
+ delete("{id}", documented(document().operation {
+ it.summary("Remove VC template").operationId("removeTemplate").addTagsItem("Issuer Configuration")
+ }.pathParam("id").result("200"), SignatoryController::removeTemplate))
+
+ }
+ }
+ path("credentials") {
+ get("listIssuables", documented(
+ document().operation {
+ it.summary("List issuable credentials")
+ .addTagsItem("Issuer")
+ .operationId("listIssuableCredentials")
+ }
+ .pathParam("tenantId") { it.example(TenantId.DEFAULT_TENANT) }
+ .queryParam("sessionId")
+ .json("200"),
+ IssuerController::listIssuableCredentials))
+ path("issuance") {
+ post("request", documented(
+ document().operation {
+ it.summary("Request issuance of selected credentials to wallet")
+ .addTagsItem("Issuer")
+ .operationId("requestIssuance")
+ }
+ .pathParam("tenantId") { it.example(TenantId.DEFAULT_TENANT) }
+ .queryParam("walletId")
+ .queryParam("sessionId")
+ .queryParam("isPreAuthorized")
+ .queryParam("userPin")
+ .queryParam("issuerDid")
+ .body()
+ .result("200"),
+ IssuerController::requestIssuance
+ ))
+ }
+ }
+ path("oidc") {
+ get(".well-known/openid-configuration", documented(
+ document().operation {
+ it.summary("get OIDC provider meta data")
+ .addTagsItem("Issuer")
+ .operationId("oidcProviderMeta")
+ }
+ .pathParam("tenantId") { it.example(TenantId.DEFAULT_TENANT) }
+ .json("200"),
+ IssuerController::oidcProviderMeta
+ ))
+ get(".well-known/openid-credential-issuer", documented(
+ document().operation {
+ it.summary("get OIDC provider meta data")
+ .addTagsItem("Issuer")
+ .operationId("oidcProviderMeta")
+ }
+ .pathParam("tenantId") { it.example(TenantId.DEFAULT_TENANT) }
+ .json("200"),
+ IssuerController::oidcProviderMeta
+ ))
+ post("par", documented(
+ document().operation {
+ it.summary("pushed authorization request")
+ .addTagsItem("Issuer")
+ .operationId("par")
+ }
+ .pathParam("tenantId") { it.example(TenantId.DEFAULT_TENANT) }
+ .formParam("response_type")
+ .formParam("client_id")
+ .formParam("redirect_uri")
+ .formParam("scope")
+ .formParam("claims")
+ .formParam("state")
+ .formParam("op_state")
+ .json("201"),
+ IssuerController::par
+ ))
+ get("fulfillPAR", documented(
+ document().operation { it.summary("fulfill PAR").addTagsItem("Issuer").operationId("fulfillPAR") }
+ .pathParam("tenantId") { it.example(TenantId.DEFAULT_TENANT) }
+ .queryParam("request_uri"),
+ IssuerController::fulfillPAR
+ ))
+ post("token", documented(
+ document().operation {
+ it.summary("token endpoint")
+ .addTagsItem("Issuer")
+ .operationId("token")
+ }
+ .pathParam("tenantId") { it.example(TenantId.DEFAULT_TENANT) }
+ .formParam("grant_type")
+ .formParam("code")
+ .formParam("pre-authorized_code")
+ .formParam("redirect_uri")
+ .formParam("user_pin")
+ .formParam("code_verifier")
+ .json("200"),
+ IssuerController::token
+ ))
+ post("credential", documented(
+ document().operation {
+ it.summary("Credential endpoint").operationId("credential").addTagsItem("Issuer")
+ }
+ .header("Authorization")
+ .pathParam("tenantId") { it.example(TenantId.DEFAULT_TENANT) }
+ .body()
+ .json("200"),
+ IssuerController::credential
+ ))
+ }
+ }
+
+ private fun getConfiguration(context: Context) {
+
+ try {
+ context.json(IssuerTenant.config)
+ } catch (nfe: Tenant.TenantNotFoundException) {
+ throw NotFoundResponse()
+ }
+ }
+
+ private fun setConfiguration(context: Context) {
+ val config = context.bodyAsClass()
+ IssuerTenant.setConfig(config)
+ }
+
+ fun listWallets(ctx: Context) {
+ ctx.json(IssuerTenant.config.wallets.values)
+ }
+
+ fun listIssuableCredentials(ctx: Context) {
+ val sessionId = ctx.queryParam("sessionId")
+
+ if (sessionId == null)
+ ctx.json(IssuerManager.listIssuableCredentials())
+ else
+ ctx.json(IssuerManager.getIssuanceSession(sessionId)?.issuables ?: Issuables(credentials = listOf()))
+ }
+
+ fun requestIssuance(ctx: Context) {
+ val wallet = ctx.queryParam("walletId")?.let { IssuerTenant.config.wallets.getOrDefault(it, null) }
+ ?: IssuerManager.getXDeviceWallet()
+ val session = ctx.queryParam("sessionId")?.let { IssuerManager.getIssuanceSession(it) }
+ val issuerDid = ctx.queryParam("issuerDid") // OPTIONAL
+
+ val selectedIssuables = ctx.bodyAsClass()
+ selectedIssuables.credentials.get(0).credentialData?.forEach { it -> print("key: ${it.key} \t value: ${it.value}") }
+ if (selectedIssuables.credentials.isEmpty()) {
+ ctx.status(HttpCode.BAD_REQUEST).result("No issuable credential selected")
+ return
+ }
+
+ if (session != null) {
+ val authRequest = session.authRequest ?: throw BadRequestResponse("No authorization request found for this session")
+ IssuerManager.updateIssuanceSession(session, selectedIssuables, issuerDid)
+ ctx.result("${authRequest.redirectionURI}?code=${IssuerManager.generateAuthorizationCodeFor(session)}&state=${authRequest.state.value}")
+ } else {
+ val userPin = ctx.queryParam("userPin")?.ifBlank { null }
+ val isPreAuthorized = ctx.queryParam("isPreAuthorized")?.toBoolean() ?: false
+ val initiationRequest =
+ IssuerManager.newIssuanceInitiationRequest(selectedIssuables, isPreAuthorized, userPin, issuerDid)
+ ctx.result("${wallet.url}${if (!wallet.url.endsWith("/")) "/" else ""}${wallet.receivePath}?${initiationRequest.toQueryString()}")
+ }
+ }
+
+ fun oidcProviderMeta(ctx: Context) {
+ ctx.json(IssuerManager.getOidcProviderMetadata().toJSONObject())
+ }
+
+ fun par(ctx: Context) {
+ val req = AuthorizationRequest.parse(ServletUtils.createHTTPRequest(ctx.req))
+ val session = if (req.customParameters.containsKey("op_state")) {
+ IssuerManager.getIssuanceSession(req.customParameters["op_state"]!!.first())?.apply {
+ authRequest = req
+ IssuerManager.updateIssuanceSession(this, issuables)
+ }
+ } else {
+ val authDetails = OIDC4CIService.getCredentialAuthorizationDetails(req)
+ if (authDetails.isEmpty()) {
+ ctx.status(HttpCode.BAD_REQUEST)
+ .json(
+ PushedAuthorizationErrorResponse(
+ ErrorObject(
+ "400",
+ "No credential authorization details given",
+ 400
+ )
+ )
+ )
+ return
+ }
+ authDetails.forEach { it -> println("format: ${it.format} credential type: ${it.credential_type} TYPE: ${it.type} LOCATIONS: ${it.locations.toString()}" ) }
+
+ IssuerManager.initializeIssuanceSession(authDetails, preAuthorized = false, req)
+ } ?: throw BadRequestResponse("Session given by op_state not found")
+ ctx.status(HttpCode.CREATED).json(
+ PushedAuthorizationSuccessResponse(
+ URI("urn:ietf:params:oauth:request_uri:${session.id}"),
+ IssuerState.EXPIRATION_TIME.seconds
+ ).toJSONObject()
+ )
+ }
+
+ fun fulfillPAR(ctx: Context) {
+ val parURI = ctx.queryParam("request_uri")!!
+ val sessionID = parURI.substringAfterLast("urn:ietf:params:oauth:request_uri:")
+ val session = IssuerManager.getIssuanceSession(sessionID)
+ if (session != null) {
+ ctx.status(HttpCode.FOUND).header("Location", "${IssuerTenant.config.issuerUiUrl}/?sessionId=${session.id}")
+ } else {
+ ctx.status(HttpCode.FOUND)
+ .header("Location", "${IssuerTenant.config.issuerUiUrl}/IssuanceError?message=Invalid issuance session")
+ }
+ }
+
+ fun token(ctx: Context) {
+ val tokenReq = TokenRequest.parse(ServletUtils.createHTTPRequest(ctx.req))
+ val code = when (tokenReq.authorizationGrant.type) {
+ GrantType.AUTHORIZATION_CODE -> (tokenReq.authorizationGrant as AuthorizationCodeGrant).authorizationCode
+ PreAuthorizedCodeGrant.GRANT_TYPE -> (tokenReq.authorizationGrant as PreAuthorizedCodeGrant).code
+ else -> throw BadRequestResponse("Unsupported grant type")
+ }
+ val sessionId = IssuerManager.validateAuthorizationCode(code.value)
+ val session = IssuerManager.getIssuanceSession(sessionId)
+ if (session == null) {
+ ctx.status(HttpCode.NOT_FOUND).json(TokenErrorResponse(OAuth2Error.INVALID_REQUEST).toJSONObject())
+ return
+ }
+ if (tokenReq.authorizationGrant.type == PreAuthorizedCodeGrant.GRANT_TYPE) {
+ val pinMatches =
+ session.userPin?.let { it == (tokenReq.authorizationGrant as PreAuthorizedCodeGrant).userPin } ?: true
+ if (!pinMatches) {
+ throw ForbiddenResponse("User PIN required")
+ }
+ }
+
+ ctx.json(
+ OIDCTokenResponse(
+ OIDCTokens(JWTService.toJWT(UserInfo(session.id)), BearerAccessToken(session.id), RefreshToken()), mapOf(
+ "expires_in" to IssuerState.EXPIRATION_TIME.seconds,
+ "c_nonce" to session.nonce
+ )
+ ).toJSONObject()
+ )
+ }
+
+ fun credential(ctx: Context) {
+ val session = ctx.header("Authorization")?.substringAfterLast("Bearer ")
+ ?.let { IssuerManager.getIssuanceSession(it) }
+ ?: throw ForbiddenResponse("Invalid or unknown access token")
+
+ val credentialRequest =
+ KlaxonWithConverters().parse(ctx.body())
+ ?: throw BadRequestResponse("Could not parse credential request body")
+
+ val credential = IssuerManager.fulfillIssuanceSession(session, credentialRequest)
+ if (credential.isNullOrEmpty()) {
+ ctx.status(HttpCode.NOT_FOUND).result("No issuable credential with the given type found")
+ return
+ }
+ val credObj = credential.toVerifiableCredential()
+ ctx.contentType(ContentType.JSON).result(
+ KlaxonWithConverters().toJsonString(
+ CredentialResponse(
+ if (credObj.jwt != null) "jwt_vc" else "ldp_vc",
+ credential.toVerifiableCredential()
+ )
+ )
+ )
+ }
+}
diff --git a/src/main/kotlin/id/walt/issuer/backend/IssuerManager.kt b/src/main/kotlin/id/walt/issuer/backend/IssuerManager.kt
new file mode 100644
index 0000000..2703009
--- /dev/null
+++ b/src/main/kotlin/id/walt/issuer/backend/IssuerManager.kt
@@ -0,0 +1,285 @@
+package id.walt.issuer.backend
+
+import com.nimbusds.jose.JWSAlgorithm
+import com.nimbusds.jwt.SignedJWT
+import com.nimbusds.oauth2.sdk.AuthorizationRequest
+import com.nimbusds.oauth2.sdk.GrantType
+import com.nimbusds.oauth2.sdk.PreAuthorizedCodeGrant
+import com.nimbusds.oauth2.sdk.id.Issuer
+import com.nimbusds.openid.connect.sdk.SubjectType
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata
+import id.walt.credentials.w3c.VerifiableCredential
+import id.walt.credentials.w3c.templates.VcTemplateManager
+import id.walt.crypto.LdSignatureType
+import id.walt.customTemplates.EHIC
+import id.walt.model.DidMethod
+import id.walt.model.DidUrl
+import id.walt.model.oidc.*
+import id.walt.multitenancy.TenantContext
+import id.walt.multitenancy.TenantContextManager
+import id.walt.multitenancy.TenantId
+import id.walt.multitenancy.TenantType
+import id.walt.services.did.DidService
+import id.walt.services.jwt.JwtService
+import id.walt.signatory.ProofConfig
+import id.walt.signatory.ProofType
+import id.walt.signatory.Signatory
+import id.walt.signatory.dataproviders.MergingDataProvider
+import id.walt.verifier.backend.WalletConfiguration
+import io.github.pavleprica.kotlin.cache.time.based.shortTimeBasedCache
+import io.javalin.http.BadRequestResponse
+import mu.KotlinLogging
+import java.net.URI
+import java.time.Instant
+import java.util.*
+
+const val URL_PATTERN = "^https?:\\/\\/(?!-.)[^\\s\\/\$.?#].[^\\s]*\$"
+fun isSchema(typeOrSchema: String): Boolean {
+ return Regex(URL_PATTERN).matches(typeOrSchema)
+}
+
+object IssuerManager {
+ val log = KotlinLogging.logger { }
+ val defaultDid: String
+ get() = IssuerTenant.config.issuerDid
+ ?: IssuerTenant.state.defaultDid
+ ?: DidService.create(DidMethod.key)
+ .also {
+ IssuerTenant.state.defaultDid = it
+ log.warn { "No issuer DID configured, created temporary did:key for issuing: $it" }
+ }
+
+ fun getIssuerContext(tenantId: String): TenantContext {
+ return TenantContextManager.getTenantContext(TenantId(TenantType.ISSUER, tenantId)) { IssuerState() }
+ }
+
+ fun listIssuableCredentials(): Issuables {
+ return Issuables(
+ credentials = listOf(
+ "VerifiableId",
+ "VerifiableDiploma",
+ "VerifiableVaccinationCertificate",
+ "ProofOfResidence",
+ "ParticipantCredential",
+ "Europass",
+ "OpenBadgeCredential"
+ )
+ .map { IssuableCredential.fromTemplateId(it) }
+ )
+ }
+
+ private fun prompt(prompt: String, default: String?): String? {
+ print("$prompt [$default]: ")
+ val input = readlnOrNull()
+ return when (input.isNullOrBlank()) {
+ true -> default
+ else -> input
+ }
+ }
+
+ fun getValidNonces(): Set {
+ return IssuerTenant.state.nonceCache.asMap().keys
+ }
+
+ fun newIssuanceInitiationRequest(
+ selectedIssuables: Issuables,
+ preAuthorized: Boolean,
+ userPin: String? = null,
+ issuerDid: String? = null
+ ): IssuanceInitiationRequest {
+ val issuerUri = URI.create("${IssuerTenant.config.issuerApiUrl}/oidc/")
+ val session = initializeIssuanceSession(
+ credentialDetails = selectedIssuables.credentials.map { issuable ->
+ CredentialAuthorizationDetails(issuable.type)
+ },
+ preAuthorized = preAuthorized,
+ authRequest = null,
+ userPin = userPin,
+ issuerDid = issuerDid
+ )
+ updateIssuanceSession(session, selectedIssuables, issuerDid)
+
+ return IssuanceInitiationRequest(
+ issuer_url = issuerUri.toString(),
+ credential_types = selectedIssuables.credentials.map { it.type },
+ pre_authorized_code = if (preAuthorized) generateAuthorizationCodeFor(session) else null,
+ user_pin_required = userPin != null,
+ op_state = if (!preAuthorized) session.id else null
+ )
+ }
+
+ fun initializeIssuanceSession(
+ credentialDetails: List,
+ preAuthorized: Boolean,
+ authRequest: AuthorizationRequest?,
+ userPin: String? = null,
+ issuerDid: String? = null
+ ): IssuanceSession {
+ val id = UUID.randomUUID().toString()
+ //TODO: validata/verify PAR request, claims, etc
+ val session = IssuanceSession(
+ id,
+ credentialDetails,
+ UUID.randomUUID().toString(),
+ isPreAuthorized = preAuthorized,
+ authRequest,
+ Issuables.fromCredentialAuthorizationDetails(credentialDetails),
+ userPin = userPin,
+ issuerDid = issuerDid
+ )
+ IssuerTenant.state.sessionCache.put(id, session)
+ return session
+ }
+
+ fun getIssuanceSession(id: String): IssuanceSession? {
+ return IssuerTenant.state.sessionCache.getIfPresent(id)
+ }
+
+ fun updateIssuanceSession(session: IssuanceSession, issuables: Issuables?, issuerDid: String? = null) {
+ session.issuables = issuables
+ issuerDid?.let { session.issuerDid = issuerDid }
+ IssuerTenant.state.sessionCache.put(session.id, session)
+ }
+
+ fun generateAuthorizationCodeFor(session: IssuanceSession): String {
+ return IssuerTenant.state.authCodeProvider.generateToken(session)
+ }
+
+ fun validateAuthorizationCode(code: String): String {
+ return IssuerTenant.state.authCodeProvider.validateToken(code).map { it.subject }
+ .orElseThrow { BadRequestResponse("Invalid authorization code given") }
+ }
+
+ private inline fun Iterable.allUniqueBy(transform: (T) -> R) =
+ HashSet().let { hs ->
+ all { hs.add(transform(it)) }
+ }
+
+ /**
+ * For multipleCredentialsOfSameType in session.issuables
+ */
+ private val sessionAccessCounterCache = shortTimeBasedCache>()
+ fun fulfillIssuanceSession(session: IssuanceSession, credentialRequest: CredentialRequest): String? {
+ val proof = credentialRequest.proof ?: throw BadRequestResponse("No proof given")
+ val parsedJwt = SignedJWT.parse(proof.jwt)
+ if (parsedJwt.header.keyID?.let { DidUrl.isDidUrl(it) } == false) throw BadRequestResponse("Proof is not DID signed")
+
+ if (!JwtService.getService().verify(proof.jwt)) throw BadRequestResponse("Proof invalid")
+
+ val did = DidUrl.from(parsedJwt.header.keyID).did
+ val now = Instant.now()
+ val issuables = session.issuables ?: throw BadRequestResponse("No issuables")
+
+ log.debug { "Issuance session ${session.id}: Session issuables: ${session.issuables}" }
+
+ val sessionLongId = "${session.id}${session.nonce}"
+
+
+ val multipleCredentialsOfSameType = !issuables.credentials.allUniqueBy { it.type }
+
+ if (multipleCredentialsOfSameType && sessionAccessCounterCache[sessionLongId].isEmpty) {
+ log.debug { "Issuance session ${session.id}: Setup multipleCredentialsOfSameType" }
+ sessionAccessCounterCache[sessionLongId] = HashMap()
+ }
+
+ val requestedType = credentialRequest.type
+ val credentialsOfRequestedType = issuables.credentials.filter { it.type == requestedType }
+
+ val credential = when {
+ !multipleCredentialsOfSameType -> credentialsOfRequestedType.firstOrNull()
+ else -> {
+ val accessCounter = sessionAccessCounterCache[sessionLongId].get()
+
+ if (!accessCounter.contains(requestedType))
+ accessCounter[requestedType] = -1
+
+ accessCounter[requestedType] = accessCounter[requestedType]!! + 1
+
+ log.info {
+ "Issuance session ${session.id}: multipleCredentialsOfSameType " +
+ "request ${accessCounter[requestedType]!! + 1}/${issuables.credentials.size}"
+ }
+
+ credentialsOfRequestedType.getOrElse(accessCounter[requestedType]!!) { credentialsOfRequestedType.lastOrNull() }
+ }
+ }
+
+ return credential?.let {
+ Signatory.getService().issue(it.type,
+ ProofConfig(
+ issuerDid = session.issuerDid ?: defaultDid,
+ proofType = when (credentialRequest.format) {
+ "jwt_vc" -> ProofType.JWT
+ else -> ProofType.LD_PROOF
+ },
+ subjectDid = did,
+ issueDate = now,
+ validDate = now
+ ),
+ dataProvider = it.credentialData?.let { cd -> MergingDataProvider(cd) })
+ }
+ }
+
+ fun getXDeviceWallet(): WalletConfiguration {
+ return WalletConfiguration(
+ id = "x-device",
+ url = "openid-initiate-issuance://",
+ presentPath = "",
+ receivePath = "",
+ description = "cross device"
+ )
+ }
+
+ fun getOidcProviderMetadata() = OIDCProviderMetadata(
+ Issuer(IssuerTenant.config.issuerApiUrl),
+ listOf(SubjectType.PUBLIC),
+ URI("${IssuerTenant.config.issuerApiUrl}/oidc")
+ ).apply {
+ authorizationEndpointURI = URI("${IssuerTenant.config.issuerApiUrl}/oidc/fulfillPAR")
+ pushedAuthorizationRequestEndpointURI = URI("${IssuerTenant.config.issuerApiUrl}/oidc/par")
+ tokenEndpointURI = URI("${IssuerTenant.config.issuerApiUrl}/oidc/token")
+ grantTypes = listOf(GrantType.AUTHORIZATION_CODE, PreAuthorizedCodeGrant.GRANT_TYPE)
+ setCustomParameter("credential_endpoint", "${IssuerTenant.config.issuerApiUrl}/oidc/credential")
+ setCustomParameter(
+ "credential_issuer", CredentialIssuer(
+ listOf(
+ CredentialIssuerDisplay(IssuerTenant.config.issuerApiUrl)
+ )
+ )
+ )
+ // Inject custom made Verifiable Credential
+ VcTemplateManager.register("EuropeanHealthInsuranceCard", VerifiableCredential.fromString(EHIC.template!!.invoke().encode()))
+ setCustomParameter(
+ "credentials_supported",
+ VcTemplateManager.listTemplates().map {
+ VcTemplateManager.getTemplate(it.name, true) }
+ .associateBy({ tmpl -> tmpl.template!!.type.last() }) { cred ->
+ CredentialMetadata(
+ formats = mapOf(
+ "ldp_vc" to CredentialFormat(
+ types = cred.template!!.type,
+ cryptographic_binding_methods_supported = listOf("did"),
+ cryptographic_suites_supported = LdSignatureType.values().map { it.name }
+ ),
+ "jwt_vc" to CredentialFormat(
+ types = cred.template!!.type,
+ cryptographic_binding_methods_supported = listOf("did"),
+ cryptographic_suites_supported = listOf(
+ JWSAlgorithm.ES256,
+ JWSAlgorithm.ES256K,
+ JWSAlgorithm.EdDSA,
+ JWSAlgorithm.RS256,
+ JWSAlgorithm.PS256
+ ).map { it.name }
+ )
+ ),
+ display = listOf(
+ CredentialDisplay(
+ name = cred.template!!.type.last()
+ )
+ )
+ )
+ }
+ )
+ }
+}
diff --git a/src/main/kotlin/id/walt/issuer/backend/IssuerState.kt b/src/main/kotlin/id/walt/issuer/backend/IssuerState.kt
new file mode 100644
index 0000000..bae6fdd
--- /dev/null
+++ b/src/main/kotlin/id/walt/issuer/backend/IssuerState.kt
@@ -0,0 +1,35 @@
+package id.walt.issuer.backend
+
+import com.auth0.jwt.JWT
+import com.auth0.jwt.algorithms.Algorithm
+import com.google.common.cache.CacheBuilder
+import id.walt.multitenancy.TenantState
+import javalinjwt.JWTProvider
+import java.time.Duration
+import java.util.*
+import java.util.concurrent.*
+
+class IssuerState : TenantState {
+ val nonceCache =
+ CacheBuilder.newBuilder().expireAfterWrite(EXPIRATION_TIME.seconds, TimeUnit.SECONDS).build()
+ val sessionCache =
+ CacheBuilder.newBuilder().expireAfterAccess(EXPIRATION_TIME.seconds, TimeUnit.SECONDS).build()
+
+ val authCodeSecret = System.getenv("WALTID_ISSUER_AUTH_CODE_SECRET") ?: UUID.randomUUID().toString()
+ val algorithm: Algorithm = Algorithm.HMAC256(authCodeSecret)
+
+ val authCodeProvider = JWTProvider(
+ algorithm,
+ { session: IssuanceSession, alg: Algorithm? ->
+ JWT.create().withSubject(session.id).withClaim("pre-authorized", session.isPreAuthorized).sign(alg)
+ },
+ JWT.require(algorithm).build()
+ )
+ var defaultDid: String? = null
+
+ companion object {
+ val EXPIRATION_TIME: Duration = Duration.ofMinutes(5)
+ }
+
+ override var config: IssuerConfig? = null
+}
diff --git a/src/main/kotlin/id/walt/issuer/backend/IssuerTenant.kt b/src/main/kotlin/id/walt/issuer/backend/IssuerTenant.kt
new file mode 100644
index 0000000..c36fe5a
--- /dev/null
+++ b/src/main/kotlin/id/walt/issuer/backend/IssuerTenant.kt
@@ -0,0 +1,5 @@
+package id.walt.issuer.backend
+
+import id.walt.multitenancy.Tenant
+
+object IssuerTenant : Tenant(IssuerConfig)
diff --git a/src/main/kotlin/id/walt/multitenancy/MultitenancyController.kt b/src/main/kotlin/id/walt/multitenancy/MultitenancyController.kt
new file mode 100644
index 0000000..d928266
--- /dev/null
+++ b/src/main/kotlin/id/walt/multitenancy/MultitenancyController.kt
@@ -0,0 +1,52 @@
+package id.walt.multitenancy
+
+import id.walt.common.KlaxonWithConverters
+import io.javalin.apibuilder.ApiBuilder.get
+import io.javalin.apibuilder.ApiBuilder.path
+import io.javalin.http.ContentType
+import io.javalin.http.Context
+import io.javalin.plugin.openapi.dsl.document
+import io.javalin.plugin.openapi.dsl.documented
+
+object MultitenancyController {
+ val routes
+ get() = path("multitenancy") {
+ get(
+ "listLoadedTenants", documented(
+ document().operation {
+ it.summary("List multitenancy *LOADED* tenants. If no tenants are loaded (e.g. right after a restart) this method will indeed return an empty list.")
+ .addTagsItem("Multitenancy")
+ .operationId("listLoadedTenants")
+ }.json>("200"),
+ MultitenancyController::listLoadedTenants
+ )
+ )
+
+ get(
+ "listAllTenantIdsByType/{TenantType}", documented(
+ document().operation {
+ it.summary(
+ "List multitenancy tenant IDs by tenant type. Available tenant types: ${
+ TenantType.values().map { it.name }
+ }"
+ )
+ .addTagsItem("Multitenancy")
+ .operationId("listAllTenantIdsByType")
+ }.json>("200"),
+ MultitenancyController::listAllTenantIdsByType
+ )
+ )
+ }
+
+ private fun listLoadedTenants(ctx: Context) {
+ val contextsJson = TenantContextManager.listLoadedContexts()
+ val json = KlaxonWithConverters().toJsonString(contextsJson)
+
+ ctx.status(200).result(json).contentType(ContentType.APPLICATION_JSON)
+ }
+
+ private fun listAllTenantIdsByType(ctx: Context) {
+ val tenantType = ctx.pathParam("TenantType")
+ ctx.json(TenantContextManager.listAllContextIdsByType(TenantType.valueOf(tenantType)))
+ }
+}
diff --git a/src/main/kotlin/id/walt/multitenancy/Tenant.kt b/src/main/kotlin/id/walt/multitenancy/Tenant.kt
new file mode 100644
index 0000000..457f8c3
--- /dev/null
+++ b/src/main/kotlin/id/walt/multitenancy/Tenant.kt
@@ -0,0 +1,71 @@
+package id.walt.multitenancy
+
+import id.walt.services.context.ContextManager
+import id.walt.services.hkvstore.HKVKey
+import id.walt.webwallet.backend.context.UserContext
+import id.walt.webwallet.backend.context.WalletContextManager
+import mu.KotlinLogging
+import kotlin.reflect.jvm.jvmName
+
+abstract class Tenant>(private val configFactory: TenantConfigFactory) {
+ class TenantNotFoundException(message: String) : Exception(message)
+
+ private val log = KotlinLogging.logger { }
+
+
+ val CONFIG_KEY = "config"
+
+
+ private fun waltContextStuffErrorsAgain(type: String, extra: String? = null): Nothing = throw WaltContextTenantSystemError(
+ "WaltContext system does not work (again)... " +
+ "Current context \"${WalletContextManager.currentContext::class.jvmName}\" was casted to TenantContext, but is ${
+ when (type) {
+ "otherClass" -> "a different class"
+ "wrongGenericType" -> "a TenantContext, but of different generics types"
+ else -> "UNKNOWN ERROR"
+ }
+ }${if (extra == null) "" else " $extra"}"
+ )
+
+ data class WaltContextTenantSystemError(override val message: String): Exception()
+
+ val context: TenantContext
+ get() {
+ val currentContext = WalletContextManager.currentContext
+
+ if (currentContext is UserContext) throw IllegalArgumentException("You are authenticated with a user context (authenticated using a user bearer token), but are probably accessing an endpoint meant for tenant contexts (set with {tenantId} in the URL). If you try to use a tenant context method, do not set a user context at the same time, leave the header 'Authentication: Bearer ' from your request.")
+
+ try {
+ return currentContext as? TenantContext ?: waltContextStuffErrorsAgain("wrongGenericType")
+ } catch (e: Exception) {
+ log.debug { "Current context: ${currentContext::class.simpleName}: $currentContext" }
+
+ when {
+ e is WaltContextTenantSystemError -> throw e
+ currentContext !is TenantContext<*, *> -> waltContextStuffErrorsAgain("otherClass", e.message)
+ else -> throw WaltContextTenantSystemError("Context/Tenant system error: ${e.message}")
+ }
+ }
+ }
+
+ val tenantId: TenantId
+ get() = context.tenantId
+
+ val config: C
+ get() = context.state.config ?: ContextManager.hkvStore.getAsString(HKVKey(CONFIG_KEY))
+ ?.let { configFactory.fromJson(it) } ?: if (context.tenantId.id == TenantId.DEFAULT_TENANT) {
+ configFactory.forDefaultTenant()
+ } else {
+ throw TenantNotFoundException("Tenant config not found")
+ }.also {
+ context.state.config = it
+ }
+
+ val state: S
+ get() = context.state
+
+ fun setConfig(config: C) {
+ context.state.config = config
+ ContextManager.hkvStore.put(HKVKey(CONFIG_KEY), config.toJson())
+ }
+}
diff --git a/src/main/kotlin/id/walt/multitenancy/TenantCmd.kt b/src/main/kotlin/id/walt/multitenancy/TenantCmd.kt
new file mode 100644
index 0000000..6d23c73
--- /dev/null
+++ b/src/main/kotlin/id/walt/multitenancy/TenantCmd.kt
@@ -0,0 +1,31 @@
+package id.walt.multitenancy
+
+import com.github.ajalt.clikt.core.CliktCommand
+import com.github.ajalt.clikt.parameters.arguments.argument
+import id.walt.issuer.backend.IssuerConfig
+import id.walt.issuer.backend.IssuerTenant
+import id.walt.webwallet.backend.context.WalletContextManager
+import java.io.File
+
+class TenantCmd : CliktCommand(name = "tenant", help = "Manage tenant for this issuer or verifier") {
+
+ override fun run() {
+ }
+}
+
+class ConfigureTenantCmd : CliktCommand(name = "configure", help = "Configure current issuer or verifier tenant") {
+ val config: String by argument("config", help = "Path to config file for this tenant")
+
+ override fun run() {
+ val configFile = File(config)
+ if (!configFile.exists()) {
+ throw Exception("Config file not found")
+ }
+
+ val tenantContext = WalletContextManager.currentContext as TenantContext<*, *>
+ when (tenantContext.tenantId.type) {
+ TenantType.ISSUER -> IssuerTenant.setConfig(IssuerConfig.fromJson(configFile.readText()))
+ else -> throw IllegalArgumentException("Tenant type not yet supported")
+ }
+ }
+}
diff --git a/src/main/kotlin/id/walt/multitenancy/TenantConfig.kt b/src/main/kotlin/id/walt/multitenancy/TenantConfig.kt
new file mode 100644
index 0000000..939063c
--- /dev/null
+++ b/src/main/kotlin/id/walt/multitenancy/TenantConfig.kt
@@ -0,0 +1,5 @@
+package id.walt.multitenancy
+
+interface TenantConfig {
+ fun toJson(): String
+}
diff --git a/src/main/kotlin/id/walt/multitenancy/TenantConfigFactory.kt b/src/main/kotlin/id/walt/multitenancy/TenantConfigFactory.kt
new file mode 100644
index 0000000..c7ce10e
--- /dev/null
+++ b/src/main/kotlin/id/walt/multitenancy/TenantConfigFactory.kt
@@ -0,0 +1,7 @@
+package id.walt.multitenancy
+
+
+interface TenantConfigFactory {
+ fun fromJson(json: String): C
+ fun forDefaultTenant(): C
+}
diff --git a/src/main/kotlin/id/walt/multitenancy/TenantContext.kt b/src/main/kotlin/id/walt/multitenancy/TenantContext.kt
new file mode 100644
index 0000000..14c30b8
--- /dev/null
+++ b/src/main/kotlin/id/walt/multitenancy/TenantContext.kt
@@ -0,0 +1,16 @@
+package id.walt.multitenancy
+
+import id.walt.services.context.Context
+import id.walt.services.hkvstore.HKVStoreService
+import id.walt.services.keystore.KeyStoreService
+import id.walt.services.vcstore.VcStoreService
+
+class TenantContext>(
+ val tenantId: TenantId,
+ override val hkvStore: HKVStoreService,
+ override val keyStore: KeyStoreService,
+ override val vcStore: VcStoreService,
+ val state: S
+) : Context {
+ override fun toString() = tenantId.toString()
+}
diff --git a/src/main/kotlin/id/walt/multitenancy/TenantContextManager.kt b/src/main/kotlin/id/walt/multitenancy/TenantContextManager.kt
new file mode 100644
index 0000000..ae23e7f
--- /dev/null
+++ b/src/main/kotlin/id/walt/multitenancy/TenantContextManager.kt
@@ -0,0 +1,43 @@
+package id.walt.multitenancy
+
+import id.walt.WALTID_DATA_ROOT
+import id.walt.common.prettyPrint
+import id.walt.services.hkvstore.FileSystemHKVStore
+import id.walt.services.hkvstore.FilesystemStoreConfig
+import id.walt.services.keystore.HKVKeyStoreService
+import id.walt.services.vcstore.HKVVcStoreService
+import io.javalin.core.util.RouteOverviewUtil.metaInfo
+import kotlin.io.path.Path
+import kotlin.io.path.listDirectoryEntries
+import kotlin.io.path.name
+
+object TenantContextManager {
+ private val contexts: MutableMap> = mutableMapOf()
+
+ // TODO: make context data stores configurable
+
+ fun listLoadedContexts(): List {
+ return listOf(
+ //IssuerManager.getIssuerContext(TenantId.DEFAULT_TENANT).tenantId, // Preload issuer context
+ //VerifierManager.getService().getVerifierContext(TenantId.DEFAULT_TENANT).tenantId, // Preload verifier context
+ *contexts.values.map { it.tenantId }.toTypedArray(),
+ ).distinct()
+ }
+
+ fun listAllContextIdsByType(tenantType: TenantType) = Path("$WALTID_DATA_ROOT/data/tenants/${tenantType}/")
+ .listDirectoryEntries()
+ .map { it.name }
+
+ fun > getTenantContext(tenantId: TenantId, createState: () -> S): TenantContext {
+ // TODO: create tenant context according to context configuration
+ return contexts[tenantId.toString()] as? TenantContext ?: TenantContext(
+ tenantId = tenantId,
+ hkvStore = FileSystemHKVStore(FilesystemStoreConfig("$WALTID_DATA_ROOT/data/tenants/${tenantId.type}/${tenantId.id}")),
+ keyStore = HKVKeyStoreService(),
+ vcStore = HKVVcStoreService(),
+ state = createState()
+ ).also {
+ contexts[tenantId.toString()] = it
+ }
+ }
+}
diff --git a/src/main/kotlin/id/walt/multitenancy/TenantId.kt b/src/main/kotlin/id/walt/multitenancy/TenantId.kt
new file mode 100644
index 0000000..df8a816
--- /dev/null
+++ b/src/main/kotlin/id/walt/multitenancy/TenantId.kt
@@ -0,0 +1,15 @@
+package id.walt.multitenancy
+
+data class TenantId(val type: TenantType, val id: String) {
+ override fun toString(): String {
+ return "$type/$id"
+ }
+
+ override fun equals(other: Any?): Boolean {
+ return other is TenantId && type == other.type && id == other.id
+ }
+
+ companion object {
+ const val DEFAULT_TENANT = "default"
+ }
+}
diff --git a/src/main/kotlin/id/walt/multitenancy/TenantState.kt b/src/main/kotlin/id/walt/multitenancy/TenantState.kt
new file mode 100644
index 0000000..d10bee6
--- /dev/null
+++ b/src/main/kotlin/id/walt/multitenancy/TenantState.kt
@@ -0,0 +1,5 @@
+package id.walt.multitenancy
+
+interface TenantState {
+ var config: C?
+}
diff --git a/src/main/kotlin/id/walt/multitenancy/TenantType.kt b/src/main/kotlin/id/walt/multitenancy/TenantType.kt
new file mode 100644
index 0000000..6686ed6
--- /dev/null
+++ b/src/main/kotlin/id/walt/multitenancy/TenantType.kt
@@ -0,0 +1,6 @@
+package id.walt.multitenancy
+
+enum class TenantType {
+ ISSUER,
+ VERIFIER,
+}
diff --git a/src/main/kotlin/id/walt/onboarding/backend/DomainOwnershipService.kt b/src/main/kotlin/id/walt/onboarding/backend/DomainOwnershipService.kt
new file mode 100644
index 0000000..397aa46
--- /dev/null
+++ b/src/main/kotlin/id/walt/onboarding/backend/DomainOwnershipService.kt
@@ -0,0 +1,37 @@
+package id.walt.onboarding.backend
+
+import id.walt.crypto.toHexString
+import java.security.MessageDigest
+import java.util.*
+import javax.naming.Context
+import javax.naming.directory.DirContext
+import javax.naming.directory.InitialDirContext
+
+object DomainOwnershipService {
+
+ /**
+ * Unique verification code for each domain and DID
+ */
+ fun generateWaltIdDomainVerificationCode(domain: String, did: String): String =
+ "ssi-onboarding-verification=" + MessageDigest.getInstance("SHA-1")
+ .digest(domain.plus(did).toByteArray()).toHexString().replace(" ", "")
+
+ fun checkWaltIdDomainVerificationCode(domain: String, did: String): Boolean =
+ checkDomainVerificationCode(domain, generateWaltIdDomainVerificationCode(domain, did))
+
+ fun checkDomainVerificationCode(domain: String, code: String): Boolean {
+ println("Checking domain: $domain (code: $code)")
+ val env: Hashtable = Hashtable()
+ env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory")
+ env.put(Context.PROVIDER_URL, "dns:")
+
+ val ctx: DirContext = InitialDirContext(env)
+ val attributes = ctx.getAttributes(domain, arrayOf("TXT")).all
+ while (attributes.hasMore()) {
+ if (attributes.next().contains(code)) {
+ return true
+ }
+ }
+ return false
+ }
+}
diff --git a/src/main/kotlin/id/walt/onboarding/backend/OnboardingController.kt b/src/main/kotlin/id/walt/onboarding/backend/OnboardingController.kt
new file mode 100644
index 0000000..62cfbde
--- /dev/null
+++ b/src/main/kotlin/id/walt/onboarding/backend/OnboardingController.kt
@@ -0,0 +1,278 @@
+package id.walt.onboarding.backend
+
+import com.beust.klaxon.Klaxon
+import com.nimbusds.jwt.JWTClaimsSet
+import com.nimbusds.oauth2.sdk.id.Issuer
+import com.nimbusds.openid.connect.sdk.SubjectType
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata
+import id.walt.auditor.Auditor
+import id.walt.auditor.ChallengePolicy
+import id.walt.auditor.ChallengePolicyArg
+import id.walt.auditor.SignaturePolicy
+import id.walt.issuer.backend.IssuableCredential
+import id.walt.issuer.backend.Issuables
+import id.walt.issuer.backend.IssuerManager
+import id.walt.issuer.backend.IssuerTenant
+import id.walt.model.DidMethod
+import id.walt.model.DidUrl
+import id.walt.model.dif.CredentialManifest
+import id.walt.model.dif.OutputDescriptor
+import id.walt.model.dif.PresentationDefinition
+import id.walt.multitenancy.TenantId
+import id.walt.services.jwt.JwtService
+import id.walt.services.oidc.OIDCUtils
+import id.walt.webwallet.backend.auth.JWTService
+import id.walt.webwallet.backend.auth.UserInfo
+import id.walt.webwallet.backend.auth.UserRole
+import id.walt.webwallet.backend.context.WalletContextManager
+import io.javalin.apibuilder.ApiBuilder.*
+import io.javalin.http.*
+import io.javalin.plugin.openapi.dsl.document
+import io.javalin.plugin.openapi.dsl.documented
+import java.net.URI
+
+data class GenerateDomainVerificationCodeRequest(val domain: String)
+data class CheckDomainVerificationCodeRequest(val domain: String)
+data class IssueParticipantCredentialRequest(val domain: String)
+
+object OnboardingController {
+ val routes
+ get() =
+ path("") {
+ before {
+ WalletContextManager.setCurrentContext(IssuerManager.getIssuerContext(TenantId.DEFAULT_TENANT))
+ }
+ after {
+ WalletContextManager.resetCurrentContext()
+ }
+ path("domain") {
+ path("generateDomainVerificationCode") {
+ post("", documented(
+ document().operation {
+ it.summary("Generate domain verification code")
+ .addTagsItem("Onboarding")
+ .operationId("generateDomainVerificationCode")
+ }
+ .body()
+ .result("200"),
+ OnboardingController::generateDomainVerificationCode
+ ), UserRole.AUTHORIZED)
+ }
+ path("checkDomainVerificationCode") {
+ post("", documented(
+ document().operation {
+ it.summary("Check domain verification code")
+ .addTagsItem("Onboarding")
+ .operationId("checkDomainVerificationCode")
+ }
+ .body()
+ .result("200"),
+ OnboardingController::checkDomainVerificationCode
+ ), UserRole.AUTHORIZED)
+ }
+ }
+ // provide customized oidc discovery document and authorize endpoint
+ path("oidc") {
+ get(".well-known/openid-configuration", documented(
+ document().operation {
+ it.summary("get OIDC provider meta data")
+ .addTagsItem("Onboarding")
+ .operationId("ob-oidcProviderMeta")
+ }
+ .json("200"),
+ OnboardingController::oidcProviderMeta
+ ))
+ get("fulfillPAR", documented(
+ document().operation {
+ it.summary("fulfill PAR").addTagsItem("Onboarding").operationId("fulfillPAR")
+ }
+ .queryParam("request_uri"),
+ OnboardingController::fulfillPAR
+ ))
+ }
+ path("auth") {
+ get(
+ "userToken", documented(
+ document().operation {
+ it.summary("get user token").addTagsItem("Onboarding").operationId("userToken")
+ }.json("200"),
+ OnboardingController::userToken
+ )
+ )
+ }
+ post(
+ "issue",
+ documented(document().operation {
+ it.summary("Issue participant credential to did:web-authorized user").addTagsItem("Onboarding")
+ }
+ .queryParam("sessionId").body(),
+ OnboardingController::issue),
+ UserRole.AUTHORIZED)
+ }
+
+ private fun generateDomainVerificationCode(ctx: Context) {
+ val did = checkAuthDid(ctx) ?: return
+ val domainReq = ctx.bodyAsClass()
+ ctx.result(DomainOwnershipService.generateWaltIdDomainVerificationCode(domainReq.domain, did))
+ }
+
+ private fun checkDomainVerificationCode(ctx: Context) {
+ val did = checkAuthDid(ctx) ?: return
+ val domainReq = ctx.bodyAsClass()
+ ctx.json(DomainOwnershipService.checkWaltIdDomainVerificationCode(domainReq.domain, did))
+ }
+
+ private fun checkAuthDid(ctx: Context): String? {
+ val userInfo = JWTService.getUserInfo(ctx)
+ if (userInfo == null) {
+ ctx.status(HttpCode.UNAUTHORIZED)
+ return null
+ } else if (userInfo.did == null) {
+ ctx.result("An authenticated DID is required for accessing this API")
+ ctx.status(HttpCode.UNAUTHORIZED)
+ return null
+ }
+ return userInfo.did!!
+ }
+
+ const val PARICIPANT_CREDENTIAL_SCHEMA_ID =
+ "https://raw.githubusercontent.com/walt-id/waltid-ssikit-vclib/master/src/test/resources/schemas/ParticipantCredential.json"
+
+ private fun oidcProviderMeta(ctx: Context) {
+ ctx.json(
+ OIDCProviderMetadata(
+ Issuer(IssuerTenant.config.onboardingApiUrl),
+ listOf(SubjectType.PAIRWISE, SubjectType.PUBLIC),
+ URI("http://blank")
+ ).apply {
+ authorizationEndpointURI = URI("${IssuerTenant.config.onboardingApiUrl}/oidc/fulfillPAR")
+ pushedAuthorizationRequestEndpointURI = URI("${IssuerTenant.config.issuerApiUrl}/oidc/par") // keep issuer-api
+ tokenEndpointURI = URI("${IssuerTenant.config.issuerApiUrl}/oidc/token") // keep issuer-api
+ setCustomParameter(
+ "credential_endpoint",
+ "${IssuerTenant.config.issuerApiUrl}/oidc/credential"
+ ) // keep issuer-api
+ setCustomParameter("nonce_endpoint", "${IssuerTenant.config.issuerApiUrl}/oidc/nonce") // keep issuer-api
+ setCustomParameter("credential_manifests", listOf(
+ CredentialManifest(
+ issuer = id.walt.model.dif.Issuer(IssuerManager.defaultDid, IssuerTenant.config.issuerClientName),
+ outputDescriptors = listOf(
+ OutputDescriptor(
+ "ParticipantCredential",
+ PARICIPANT_CREDENTIAL_SCHEMA_ID,
+ "ParticipantCredential"
+ )
+ ),
+ presentationDefinition = PresentationDefinition(
+ "1",
+ listOf()
+ ) // Request empty presentation to be sent along with issuance request
+ )
+ ).map { net.minidev.json.parser.JSONParser().parse(Klaxon().toJsonString(it)) }
+ )
+ }.toJSONObject()
+ )
+ }
+
+ fun fulfillPAR(ctx: Context) {
+ try {
+ val parURI = ctx.queryParam("request_uri") ?: throw BadRequestResponse("no request_uri specified")
+ val sessionID = parURI.substringAfterLast("urn:ietf:params:oauth:request_uri:")
+ val session = IssuerManager.getIssuanceSession(sessionID)
+ ?: throw BadRequestResponse("No session found for given sessionId, or session expired")
+ val authRequest = session.authRequest ?: throw BadRequestResponse("No authorization request found for this session")
+ // TODO: verify VP from auth request claims
+ val vcclaims = OIDCUtils.getVCClaims(authRequest)
+ val credClaim =
+ vcclaims.credentials?.filter { cred -> cred.type == PARICIPANT_CREDENTIAL_SCHEMA_ID }?.firstOrNull()
+ ?: throw BadRequestResponse("No participant credential claim found in authorization request")
+ val vp_token =
+ credClaim.vp_token
+ ?: authRequest.customParameters["vp_token"]?.flatMap {
+ OIDCUtils.fromVpToken(it)
+ } ?: listOf()
+ val vp = vp_token.firstOrNull() ?: throw BadRequestResponse("No VP token found on authorization request")
+
+ val verificationResult = Auditor.getService().verify(
+ vp.encode(),
+ listOf(SignaturePolicy(), ChallengePolicy(ChallengePolicyArg(IssuerManager.getValidNonces())))
+ )
+ if (!verificationResult.valid) {
+ throw BadRequestResponse("Invalid VP token given, signature (${verificationResult.policyResults["SignaturePolicy"]}) and/or challenge (${verificationResult.policyResults["ChallengePolicy"]}) could not be verified")
+ }
+ val subject = vp.subjectId
+
+ if (subject?.let { DidUrl.from(it).method } != DidMethod.web.name) throw BadRequestResponse("did:web is required for onboarding!")
+
+ session.did = subject
+ IssuerManager.updateIssuanceSession(session, session.issuables)
+
+ val access_token = JwtService.getService()
+ .sign(IssuerManager.defaultDid, JWTClaimsSet.Builder().subject(session.id).build().toString())
+
+ ctx.status(HttpCode.FOUND)
+ .header(
+ "Location",
+ "${IssuerTenant.config.onboardingUiUrl}?access_token=${access_token}&sessionId=${session.id}"
+ )
+ } catch (exc: Exception) {
+ exc.printStackTrace()
+ ctx.status(HttpCode.FOUND).header(
+ "Location",
+ "${IssuerTenant.config.issuerUiUrl}/IssuanceError/?message=${exc.message}"
+ )
+ }
+ }
+
+ fun userToken(ctx: Context) {
+ val accessToken = ctx.header("Authorization")?.let { it.substringAfterLast("Bearer ") }
+ ?: throw UnauthorizedResponse("No valid access token set on request")
+ val sessionId = if (JwtService.getService().verify(accessToken)) {
+ JwtService.getService().parseClaims(accessToken)!!["sub"].toString()
+ } else {
+ null
+ }
+ val session = sessionId?.let { IssuerManager.getIssuanceSession(it) }
+ ?: throw UnauthorizedResponse("Invalid access token or session expired")
+ val did = session.did ?: throw ForbiddenResponse("No DID specified on current session")
+
+ val userInfo = UserInfo(did)
+ ctx.json(userInfo.apply { token = JWTService.toJWT(userInfo) })
+ }
+
+ fun issue(ctx: Context) {
+ val userInfo = JWTService.getUserInfo(ctx) ?: throw UnauthorizedResponse()
+ if (userInfo.did?.let { DidUrl.from(it).method } != DidMethod.web.name) {
+ throw BadRequestResponse("User is not did:web-authorized")
+ }
+ val session = ctx.queryParam("sessionId")?.let { IssuerManager.getIssuanceSession(it) }
+ ?: throw BadRequestResponse("Session expired or not found")
+ val authRequest = session.authRequest ?: throw BadRequestResponse("No authorization request found for this session")
+ if (userInfo.did != session.did) {
+ throw BadRequestResponse("Session DID not matching authorized DID")
+ }
+ // Use the following if we should rely on the domain used in the did:web
+ // val domain = DidUrl.from(userInfo.did!!).identifier.substringBefore(":").let { URLDecoder.decode(it, StandardCharsets.UTF_8) }
+ val domain = ctx.bodyAsClass().domain
+ val selectedIssuables = Issuables(
+ credentials = listOf(
+ IssuableCredential(
+ type = "ParticipantCredential",
+ credentialData = mapOf(
+ "credentialSubject" to mapOf(
+ "domain" to domain
+ )
+ )
+ )
+ )
+ )
+ ctx.result(
+ "${authRequest.redirectionURI}?code=${
+ IssuerManager.updateIssuanceSession(
+ session,
+ selectedIssuables
+ )
+ }&state=${authRequest.state.value}"
+ )
+ }
+}
diff --git a/src/main/kotlin/id/walt/verifier/backend/PresentationRequestInfo.kt b/src/main/kotlin/id/walt/verifier/backend/PresentationRequestInfo.kt
new file mode 100644
index 0000000..a326782
--- /dev/null
+++ b/src/main/kotlin/id/walt/verifier/backend/PresentationRequestInfo.kt
@@ -0,0 +1,6 @@
+package id.walt.verifier.backend
+
+data class PresentationRequestInfo(
+ val requestId: String,
+ val url: String
+)
diff --git a/src/main/kotlin/id/walt/verifier/backend/SIOPResponseVerificationResult.kt b/src/main/kotlin/id/walt/verifier/backend/SIOPResponseVerificationResult.kt
new file mode 100644
index 0000000..f3519db
--- /dev/null
+++ b/src/main/kotlin/id/walt/verifier/backend/SIOPResponseVerificationResult.kt
@@ -0,0 +1,23 @@
+package id.walt.verifier.backend
+
+import id.walt.auditor.VerificationResult
+import id.walt.common.SingleVCObject
+import id.walt.common.VCObjectList
+import id.walt.credentials.w3c.VerifiableCredential
+import id.walt.credentials.w3c.VerifiablePresentation
+
+data class VPVerificationResult(
+ @SingleVCObject val vp: VerifiablePresentation,
+ @VCObjectList val vcs: List,
+ val verification_result: VerificationResult
+)
+
+data class SIOPResponseVerificationResult(
+ val state: String,
+ val subject: String?,
+ val vps: List,
+ var auth_token: String?
+) {
+ val isValid
+ get() = !subject.isNullOrEmpty() && vps.isNotEmpty() && vps.all { vp -> (vp.verification_result.valid) }
+}
diff --git a/src/main/kotlin/id/walt/verifier/backend/VerifierConfig.kt b/src/main/kotlin/id/walt/verifier/backend/VerifierConfig.kt
new file mode 100644
index 0000000..3d4af15
--- /dev/null
+++ b/src/main/kotlin/id/walt/verifier/backend/VerifierConfig.kt
@@ -0,0 +1,39 @@
+package id.walt.verifier.backend
+
+import com.beust.klaxon.Klaxon
+import id.walt.auditor.PolicyRequest
+import id.walt.multitenancy.TenantConfig
+import id.walt.multitenancy.TenantConfigFactory
+import id.walt.webwallet.backend.config.ExternalHostnameUrl
+import id.walt.webwallet.backend.config.externalHostnameUrlValueConverter
+import java.io.File
+
+data class VerifierConfig(
+ @ExternalHostnameUrl val verifierUiUrl: String = "http://localhost:8081",
+ @ExternalHostnameUrl val verifierApiUrl: String = "http://localhost:8080/verifier-api",
+ val wallets: Map = WalletConfiguration.getDefaultWalletConfigurations(),
+ val additionalPolicies: List? = null,
+ val allowedWebhookHosts: List? = null
+) : TenantConfig {
+ companion object : TenantConfigFactory {
+ val CONFIG_FILE = "${id.walt.WALTID_DATA_ROOT}/config/verifier-config.json"
+
+ override fun fromJson(json: String): VerifierConfig {
+ return Klaxon().fieldConverter(ExternalHostnameUrl::class, externalHostnameUrlValueConverter)
+ .parse(json) ?: VerifierConfig()
+ }
+
+ override fun forDefaultTenant(): VerifierConfig {
+ val cf = File(CONFIG_FILE)
+ return if (cf.exists()) {
+ fromJson(cf.readText())
+ } else {
+ VerifierConfig()
+ }
+ }
+ }
+
+ override fun toJson(): String {
+ return Klaxon().fieldConverter(ExternalHostnameUrl::class, externalHostnameUrlValueConverter).toJsonString(this)
+ }
+}
diff --git a/src/main/kotlin/id/walt/verifier/backend/VerifierController.kt b/src/main/kotlin/id/walt/verifier/backend/VerifierController.kt
new file mode 100644
index 0000000..ff432b7
--- /dev/null
+++ b/src/main/kotlin/id/walt/verifier/backend/VerifierController.kt
@@ -0,0 +1,517 @@
+package id.walt.verifier.backend
+
+import com.nimbusds.jose.util.Base64URL
+import com.nimbusds.oauth2.sdk.AuthorizationRequest
+import com.nimbusds.oauth2.sdk.ResponseMode
+import com.nimbusds.oauth2.sdk.ResponseType
+import com.nimbusds.oauth2.sdk.Scope
+import com.nimbusds.oauth2.sdk.id.ClientID
+import com.nimbusds.oauth2.sdk.id.State
+import com.nimbusds.openid.connect.sdk.OIDCScopeValue
+import id.walt.auditor.VerificationPolicy
+import id.walt.auditor.dynamic.DynamicPolicyArg
+import id.walt.common.KlaxonWithConverters
+import id.walt.issuer.backend.IssuerConfig
+import id.walt.model.dif.InputDescriptor
+import id.walt.model.dif.InputDescriptorConstraints
+import id.walt.model.dif.InputDescriptorField
+import id.walt.model.dif.PresentationDefinition
+import id.walt.model.oidc.SIOPv2Response
+import id.walt.multitenancy.Tenant
+import id.walt.multitenancy.TenantId
+import id.walt.rest.auditor.AuditorRestController
+import id.walt.services.oidc.OIDC4VPService
+import id.walt.services.oidc.OidcSchemeFixer
+import id.walt.services.oidc.OidcSchemeFixer.unescapeOpenIdScheme
+import id.walt.verifier.backend.VerifierManager.Companion.convertUUIDToBytes
+import id.walt.webwallet.backend.auth.JWTService
+import id.walt.webwallet.backend.auth.UserRole
+import id.walt.webwallet.backend.context.WalletContextManager
+import io.github.pavleprica.kotlin.cache.time.based.customTimeBasedCache
+import io.javalin.apibuilder.ApiBuilder.*
+import io.javalin.http.*
+import io.javalin.plugin.openapi.dsl.document
+import io.javalin.plugin.openapi.dsl.documented
+import mu.KotlinLogging
+import java.net.URI
+import java.net.URLEncoder
+import java.nio.charset.StandardCharsets
+import java.util.*
+
+object VerifierController {
+
+ private val log = KotlinLogging.logger { }
+
+ val routes
+ get() =
+ path("{tenantId}") {
+ before { ctx ->
+ //log.info { "Setting verifier API context: ${ctx.pathParam("tenantId")}" }
+ WalletContextManager.setCurrentContext(
+ VerifierManager.getService().getVerifierContext(ctx.pathParam("tenantId"))
+ )
+ }
+ after {
+ //log.info { "Resetting verifier API context" }
+ WalletContextManager.resetCurrentContext()
+ }
+ path("wallets") {
+ get("list", documented(
+ document().operation {
+ it.summary("List wallet configurations")
+ .addTagsItem("Verifier")
+ .operationId("listWallets")
+ }
+ .pathParam("tenantId") { it.example(TenantId.DEFAULT_TENANT) }
+ .jsonArray("200"),
+ VerifierController::listWallets,
+ ))
+ }
+ path("present") {
+ get(documented(
+ document().operation {
+ it.summary("Present Verifiable ID")
+ .addTagsItem("Verifier")
+ .operationId("presentVID")
+ }
+ .pathParam("tenantId") { it.example(TenantId.DEFAULT_TENANT) }
+ .queryParam("walletId")
+ .queryParam("schemaUri", isRepeatable = true)
+ .queryParam("vcType", isRepeatable = true)
+ .queryParam("pdByReference") { it.description("true: include presentation definition by reference, else by value (default: false)") }
+ .result("302"),
+ VerifierController::presentCredential
+ ))
+ }
+ path("presentXDevice") {
+ get(documented(
+ document().operation {
+ it.summary("Present Verifiable ID cross-device")
+ .addTagsItem("Verifier")
+ .operationId("presentXDevice")
+ }
+ .pathParam("tenantId") { it.example(TenantId.DEFAULT_TENANT) }
+ .queryParam("schemaUri", isRepeatable = true)
+ .queryParam("vcType", isRepeatable = true)
+ .queryParam("pdByReference") { it.description("true: include presentation definition by reference, else by value (default: false)") }
+ .result("200"),
+ VerifierController::presentCredentialXDevice
+ ))
+ }
+ path("presentLegacy") {
+ get(documented(
+ document().operation {
+ it.summary("Present Verifiable ID cross-device in legacy format")
+ .addTagsItem("Verifier")
+ .operationId("presentLegacy")
+ }
+ .pathParam("tenantId") { it.example(TenantId.DEFAULT_TENANT) }
+ .queryParam("walletId")
+ .queryParam("schemaUri", isRepeatable = true)
+ .queryParam("vcType", isRepeatable = true)
+ .result("200"),
+ VerifierController::presentCredentialLegacy
+ ))
+ }
+ path("presentXDeviceLegacy") {
+ get(documented(
+ document().operation {
+ it.summary("Present Verifiable ID cross-device in legacy format")
+ .addTagsItem("Verifier")
+ .operationId("presentXDeviceLegacy")
+ }
+ .pathParam("tenantId") { it.example(TenantId.DEFAULT_TENANT) }
+ .queryParam("schemaUri", isRepeatable = true)
+ .queryParam("vcType", isRepeatable = true)
+ .result("200"),
+ VerifierController::presentCredentialXDeviceLegacy
+ ))
+ }
+ get("pd/{id}", documented(document().operation {
+ it.summary("Get presentation definition from cache").operationId("pdFromCache").addTagsItem("Verifier")
+ }
+ .pathParam("tenantId") { it.example(TenantId.DEFAULT_TENANT) }
+ .pathParam("id")
+ .json("200"),
+ VerifierController::getPresentationDefinitionFromCache))
+ path("verify") {
+ post(documented(
+ document().operation {
+ it.summary("SIOPv2 request verification callback")
+ .addTagsItem("Verifier")
+ .operationId("verifySIOPv2Request")
+ }
+ .pathParam("tenantId") { it.example(TenantId.DEFAULT_TENANT) }
+ .queryParam("state")
+ .formParamBody { }
+ .result("302"),
+ VerifierController::verifySIOPResponse
+ ))
+ get("isVerified",
+ documented(
+ document().operation {
+ it.summary("SIOPv2 request verification callback receiver")
+ .addTagsItem("Verifier")
+ .operationId("isVerified")
+ }
+ .pathParam("tenantId") { it.example(TenantId.DEFAULT_TENANT) }
+ .queryParam("state")
+ .result("404")
+ .result("200"),
+ VerifierController::hasRecentlyVerified)
+ )
+ }
+ path("config") {
+ post("setConfiguration", documented(document().operation {
+ it.summary("Set configuration for this verifier tenant").operationId("setConfiguration")
+ .addTagsItem("Verifier Configuration")
+ }
+ .pathParam("tenantId") { it.example(TenantId.DEFAULT_TENANT) }
+ .body()
+ .json("200"), VerifierController::setConfiguration))
+ get("getConfiguration", documented(document().operation {
+ it.summary("Get configuration for this verifier tenant").operationId("getConfiguration")
+ .addTagsItem("Verifier Configuration")
+ }
+ .pathParam("tenantId") { it.example(TenantId.DEFAULT_TENANT) }
+ .json("200"), VerifierController::getConfiguration
+ ))
+ path("policies") {
+ get(
+ "list",
+ documented(document().operation {
+ it.summary("List verification policies").operationId("listPolicies")
+ .addTagsItem("Verifier Configuration")
+ }.pathParam("tenantId") { it.example(TenantId.DEFAULT_TENANT) }
+ .json>("200"), AuditorRestController::listPolicies)
+ )
+ post(
+ "create/{name}",
+ documented(
+ document().operation {
+ it.summary("Create dynamic verification policy").operationId("createDynamicPolicy")
+ .addTagsItem("Verifier Configuration")
+ }
+ .pathParam("tenantId") { it.example(TenantId.DEFAULT_TENANT) }
+ .pathParam("name")
+ .queryParam("update")
+ .queryParam("downloadPolicy")
+ .body()
+ .json("200"),
+ AuditorRestController::createDynamicPolicy
+ )
+ )
+ delete(
+ "delete/{name}",
+ documented(
+ document().operation {
+ it.summary("Delete a dynamic verification policy").operationId("deletePolicy")
+ .addTagsItem("Verifier Configuration")
+ }.pathParam("tenantId") { it.example(TenantId.DEFAULT_TENANT) }
+ .pathParam("name"),
+ AuditorRestController::deleteDynamicPolicy
+ )
+ )
+ }
+ }
+ path("auth") {
+ get(documented(
+ document().operation {
+ it.summary("Complete authentication by siopv2 verification")
+ .addTagsItem("Verifier")
+ .operationId("completeAuthentication")
+ }
+ .pathParam("tenantId") { it.example(TenantId.DEFAULT_TENANT) }
+ .queryParam("access_token")
+ .json