├── .env.example ├── .github └── workflows │ ├── build-api.yml │ ├── deploy-e-shop-backend.yml │ ├── publish-audit-trail-gw-dockerhub.yml │ ├── publish-audit-trail-gw-private-ecr.yml │ ├── publish-is-dashboard-dockerhub.yml │ ├── publish-is-dashboard-private-ecr.yml │ ├── publish-node-sdk-npmjs.yaml │ ├── publish-shared-modules-sdk-npmjs.yaml │ ├── publish-ssi-bridge-dockerhub.yml │ ├── publish-ssi-bridge-private-ecr.yml │ ├── remote-deploy-api.yml │ └── run-tests.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── MIGRATION.md ├── README.md ├── api ├── assets │ ├── Secure_Digital_Infrastructure.png │ ├── arch │ │ └── integration-services-diagrams.drawio │ ├── migrations │ │ └── data-migration-0.1.3.js │ └── requests │ │ └── Integration Services Requests.postman_collection.json ├── audit-trail-gw │ ├── .dockerignore │ ├── .env.example │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .prettierrc │ ├── CHANGELOG.md │ ├── Dockerfile │ ├── jest.config.js │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── database │ │ │ ├── channel-data.ts │ │ │ ├── channel-info.ts │ │ │ ├── concurrency-lock.ts │ │ │ ├── constants.ts │ │ │ ├── subscription-state.ts │ │ │ └── subscription.ts │ │ ├── index.ts │ │ ├── middlewares │ │ │ ├── api-key.test.ts │ │ │ ├── api-key.ts │ │ │ ├── authentication.test.ts │ │ │ ├── authentication.ts │ │ │ ├── concurrency-lock.test.ts │ │ │ ├── concurrency-lock.ts │ │ │ ├── error.ts │ │ │ ├── metrics.ts │ │ │ ├── mongodb-sanitizer.test.ts │ │ │ └── mongodb-sanitizer.ts │ │ ├── models │ │ │ ├── config │ │ │ │ └── index.ts │ │ │ └── open-api-schema.yaml │ │ ├── routers │ │ │ ├── channel-info │ │ │ │ └── index.ts │ │ │ ├── channel │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ ├── middlewares.ts │ │ │ ├── server-info │ │ │ │ └── index.ts │ │ │ ├── services.ts │ │ │ ├── subscription │ │ │ │ └── index.ts │ │ │ └── swagger.ts │ │ ├── routes │ │ │ ├── channel-info │ │ │ │ ├── index.test.ts │ │ │ │ └── index.ts │ │ │ ├── channel │ │ │ │ ├── index.ts │ │ │ │ └── tests │ │ │ │ │ ├── index.test.ts │ │ │ │ │ ├── reimport.test.ts │ │ │ │ │ └── validate.test.ts │ │ │ ├── server-info │ │ │ │ └── index.ts │ │ │ └── subscription │ │ │ │ ├── index.ts │ │ │ │ └── tests │ │ │ │ ├── authorize-subscription.test.ts │ │ │ │ ├── index.test.ts │ │ │ │ ├── request-subscription.test.ts │ │ │ │ └── revoke-subscription.test.ts │ │ ├── services │ │ │ ├── authorization-service.ts │ │ │ ├── channel-info-service.ts │ │ │ ├── channel-service.ts │ │ │ ├── configuration-service.ts │ │ │ ├── streams-service.ts │ │ │ ├── subscription-service.ts │ │ │ └── tests │ │ │ │ └── configuration-service.test.ts │ │ ├── setup │ │ │ ├── database-seeder.ts │ │ │ └── setup-manager.ts │ │ ├── test │ │ │ └── mocks │ │ │ │ ├── config.ts │ │ │ │ ├── identities.ts │ │ │ │ ├── key-collection.ts │ │ │ │ ├── logger.ts │ │ │ │ ├── service-mocks.ts │ │ │ │ └── streams.ts │ │ ├── tools │ │ │ ├── generate-secret-key │ │ │ │ └── index.ts │ │ │ ├── markdown-creator │ │ │ │ └── index.ts │ │ │ ├── migration │ │ │ │ └── migration-to-0.1.3.ts │ │ │ └── open-api-creator │ │ │ │ └── index.ts │ │ └── utils │ │ │ ├── channel-log-transformer │ │ │ ├── index.test.ts │ │ │ └── index.ts │ │ │ └── lock │ │ │ └── index.ts │ ├── tsconfig.dev.json │ └── tsconfig.json ├── shared-modules │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── jest.config.js │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── node │ │ │ ├── index.ts │ │ │ ├── services │ │ │ │ └── mongodb-service │ │ │ │ │ ├── index.test.ts │ │ │ │ │ └── index.ts │ │ │ └── utils │ │ │ │ ├── encryption │ │ │ │ ├── index.test.ts │ │ │ │ └── index.ts │ │ │ │ ├── logger │ │ │ │ └── index.ts │ │ │ │ └── text │ │ │ │ └── index.ts │ │ └── web │ │ │ ├── index.ts │ │ │ └── models │ │ │ ├── index.ts │ │ │ ├── schemas │ │ │ ├── channel-info.ts │ │ │ ├── identity.ts │ │ │ ├── index.ts │ │ │ ├── request-response-body │ │ │ │ ├── authentication-bodies.ts │ │ │ │ ├── channel-bodies.ts │ │ │ │ ├── identity-bodies.ts │ │ │ │ ├── index.ts │ │ │ │ ├── misc-bodies.ts │ │ │ │ ├── subscription-bodies.ts │ │ │ │ └── verification-bodies.ts │ │ │ ├── subscription.ts │ │ │ ├── user-types-helper.ts │ │ │ ├── user-types.ts │ │ │ └── user.ts │ │ │ └── types │ │ │ ├── channel-data.ts │ │ │ ├── channel-info.ts │ │ │ ├── concurrency.ts │ │ │ ├── identity.ts │ │ │ ├── index.ts │ │ │ ├── request-response-bodies.ts │ │ │ ├── subscription.ts │ │ │ ├── user.ts │ │ │ └── verification.ts │ └── tsconfig.json └── ssi-bridge │ ├── .dockerignore │ ├── .env.example │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .prettierrc │ ├── CHANGELOG.md │ ├── Dockerfile │ ├── jest.config.js │ ├── package-lock.json │ ├── package.json │ ├── src │ ├── config │ │ └── server-identity.json │ ├── database │ │ ├── auth.ts │ │ ├── concurrency-lock.ts │ │ ├── constants.ts │ │ ├── identity-keys.ts │ │ ├── revocation-bitmap.ts │ │ ├── trusted-roots.ts │ │ ├── user.ts │ │ └── verifiable-credentials.ts │ ├── index.ts │ ├── middlewares │ │ ├── api-key.test.ts │ │ ├── api-key.ts │ │ ├── authentication.test.ts │ │ ├── authentication.ts │ │ ├── concurrency-lock.test.ts │ │ ├── concurrency-lock.ts │ │ ├── error.ts │ │ ├── metrics.ts │ │ ├── mongodb-sanitizer.test.ts │ │ └── mongodb-sanitizer.ts │ ├── models │ │ ├── config │ │ │ ├── index.ts │ │ │ └── services.ts │ │ └── open-api-schema.yaml │ ├── routers │ │ ├── authentication │ │ │ └── index.ts │ │ ├── identity │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── middlewares.ts │ │ ├── server-info │ │ │ └── index.ts │ │ ├── services.ts │ │ ├── swagger.ts │ │ └── verification │ │ │ └── index.ts │ ├── routes │ │ ├── authentication │ │ │ ├── index.test.ts │ │ │ └── index.ts │ │ ├── identity │ │ │ ├── index.test.ts │ │ │ └── index.ts │ │ ├── server-info │ │ │ └── index.ts │ │ └── verification │ │ │ ├── index.ts │ │ │ └── tests │ │ │ ├── bundled.test.ts │ │ │ ├── check-vc.test.ts │ │ │ ├── check-vp.test.ts │ │ │ ├── revoke-vc.test.ts │ │ │ ├── trusted-roots.test.ts │ │ │ └── verify-user.test.ts │ ├── services │ │ ├── authentication-service.ts │ │ ├── authorization-service.ts │ │ ├── configuration-service.ts │ │ ├── ssi-service.ts │ │ ├── tests │ │ │ ├── configuration-service.test.ts │ │ │ ├── user-service.test.ts │ │ │ └── verification-service.test.ts │ │ ├── user-service.ts │ │ └── verification-service.ts │ ├── setup │ │ ├── database-seeder.ts │ │ ├── key-generator.test.ts │ │ ├── key-generator.ts │ │ └── setup-manager.ts │ ├── test │ │ └── mocks │ │ │ ├── bitmap.ts │ │ │ ├── config.ts │ │ │ ├── identities.ts │ │ │ ├── logger.ts │ │ │ └── service-mocks.ts │ ├── tools │ │ ├── generate-secret-key │ │ │ └── index.ts │ │ ├── markdown-creator │ │ │ └── index.ts │ │ ├── migration │ │ │ └── migration-to-0.1.3.ts │ │ ├── open-api-creator │ │ │ └── index.ts │ │ └── setup-admin-identity │ │ │ └── index.ts │ └── utils │ │ ├── jsonld │ │ └── index.ts │ │ ├── logger │ │ └── index.ts │ │ └── validator │ │ └── index.ts │ ├── tsconfig.dev.json │ └── tsconfig.json ├── clients ├── client-sdk │ ├── .gitignore │ ├── .npmignore │ ├── .prettierignore │ ├── .prettierrc │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── docs │ │ ├── README.md │ │ └── markdown.json │ ├── examples │ │ ├── .env.example │ │ ├── .gitignore │ │ ├── 0-MakeRootIdentityAdmin.ts │ │ ├── 1-CreateIdentityAndCredential.ts │ │ ├── 2-UpdateUser.ts │ │ ├── 3-DeleteUser.ts │ │ ├── 4-TrustedAuthorities.ts │ │ ├── 5-CreateChannel.ts │ │ ├── 6-AuthorizeToChannel.ts │ │ ├── 7-SearchChannelAndValidateData.ts │ │ ├── README.md │ │ ├── configuration.ts │ │ ├── externalData.ts │ │ ├── manager │ │ │ ├── index.ts │ │ │ └── manager-config.ts │ │ ├── package-lock.json │ │ └── package.json │ ├── jest.config.js │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── clients │ │ │ ├── base.ts │ │ │ ├── channel.ts │ │ │ └── identity.ts │ │ ├── index.ts │ │ ├── models │ │ │ ├── apiVersion.ts │ │ │ ├── clientConfig.ts │ │ │ ├── index.ts │ │ │ └── searchCriteria.ts │ │ └── tests │ │ │ ├── channel.test.ts │ │ │ └── test.data.ts │ ├── tools │ │ └── deleteLines.js │ └── tsconfig.json ├── e-shop-demo │ ├── e-shop-backend │ │ ├── .env-example │ │ ├── .eslintignore │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── jest.config.ts │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── src │ │ │ ├── index.ts │ │ │ ├── middlewares │ │ │ │ └── authentication.ts │ │ │ ├── routers │ │ │ │ ├── authentication.router.ts │ │ │ │ └── verification.router.ts │ │ │ └── routes │ │ │ │ ├── authentication.route.ts │ │ │ │ └── verification.route.ts │ │ ├── tsconfig.json │ │ └── vercel.json │ ├── e-shop-frontend │ │ ├── .env-example │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── config-overrides.js │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── public │ │ │ ├── assets │ │ │ │ ├── apple_juice.jpg │ │ │ │ ├── bourbon.jpg │ │ │ │ ├── cola.jpg │ │ │ │ └── wine.jpg │ │ │ ├── favicon.ico │ │ │ ├── index.html │ │ │ ├── manifest.json │ │ │ └── robots.txt │ │ ├── src │ │ │ ├── app.styles.tsx │ │ │ ├── app.tsx │ │ │ ├── assets │ │ │ │ ├── caixa_logo.png │ │ │ │ ├── ensuresec_logo.png │ │ │ │ ├── eu_logo.svg │ │ │ │ └── iota_logo.png │ │ │ ├── components │ │ │ │ ├── authenticate-credential │ │ │ │ │ └── authenticate-credential.component.tsx │ │ │ │ ├── checkout-iota │ │ │ │ │ ├── checkout-iota.component.tsx │ │ │ │ │ └── checkout-iota.styles.tsx │ │ │ │ ├── checkout-item │ │ │ │ │ ├── checkout-item.component.tsx │ │ │ │ │ └── checkout-item.styles.tsx │ │ │ │ ├── checkout-total-message │ │ │ │ │ ├── checkout-total-message.component.tsx │ │ │ │ │ └── checkout-total-message.styles.tsx │ │ │ │ ├── checkout-total │ │ │ │ │ ├── checkout-total.component.tsx │ │ │ │ │ └── checkout-total.styles.tsx │ │ │ │ ├── credential-display │ │ │ │ │ ├── credential-display.component.tsx │ │ │ │ │ └── credential-display.styles.tsx │ │ │ │ ├── error-boundary │ │ │ │ │ └── error-boundary.component.tsx │ │ │ │ ├── generate-nonce │ │ │ │ │ └── generate-nonce.component.tsx │ │ │ │ ├── header │ │ │ │ │ ├── header.component.tsx │ │ │ │ │ └── header.styles.tsx │ │ │ │ ├── list-item │ │ │ │ │ ├── list-item.component.tsx │ │ │ │ │ └── list-item.styles.tsx │ │ │ │ ├── message-box │ │ │ │ │ ├── message-box.component.tsx │ │ │ │ │ └── message-box.styles.tsx │ │ │ │ ├── switch-tour │ │ │ │ │ ├── switch-tour.component.tsx │ │ │ │ │ └── switch-tour.styles.tsx │ │ │ │ ├── tour │ │ │ │ │ └── tour.component.tsx │ │ │ │ └── verify-credential │ │ │ │ │ └── verify-credential.component.tsx │ │ │ ├── contexts │ │ │ │ ├── cart.provider.tsx │ │ │ │ ├── tour.provider.tsx │ │ │ │ └── user.provider.tsx │ │ │ ├── data │ │ │ │ ├── credential.json │ │ │ │ ├── credential_not_trusted_root.json │ │ │ │ ├── credential_under_age.json │ │ │ │ ├── items.json │ │ │ │ └── tooltips.ts │ │ │ ├── global.styles.tsx │ │ │ ├── index.css │ │ │ ├── index.tsx │ │ │ ├── models │ │ │ │ └── item.model.tsx │ │ │ ├── pages │ │ │ │ ├── checkout │ │ │ │ │ ├── checkout.component.tsx │ │ │ │ │ └── checkout.styles.tsx │ │ │ │ └── item-list │ │ │ │ │ ├── item-list.component.tsx │ │ │ │ │ └── item-list.styles.tsx │ │ │ ├── react-app-env.d.ts │ │ │ ├── reportWebVitals.ts │ │ │ ├── services │ │ │ │ ├── authentication.service.ts │ │ │ │ └── verify-credential.service.ts │ │ │ ├── setupTests.ts │ │ │ └── utils │ │ │ │ └── axios-client.ts │ │ ├── tsconfig.json │ │ └── vercel.json │ └── e-shop-nonce-signer │ │ ├── README.md │ │ ├── nonce-signer.js │ │ ├── package-lock.json │ │ └── package.json └── secant │ ├── device │ ├── .env-example │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc │ ├── README.md │ ├── jest.config.js │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── config │ │ │ ├── config.ts │ │ │ └── dataset.ts │ │ ├── index.ts │ │ ├── models │ │ │ └── config.model.ts │ │ ├── services │ │ │ ├── authentication.service.ts │ │ │ ├── channel.service.ts │ │ │ ├── identity.serivce.ts │ │ │ └── subscription.service.ts │ │ ├── setup │ │ │ └── setup.ts │ │ └── utils │ │ │ ├── client.ts │ │ │ └── encryption.ts │ ├── tsconfig.dev.json │ └── tsconfig.json │ └── ems-operator │ ├── .env-example │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc │ ├── README.md │ ├── jest.config.js │ ├── package-lock.json │ ├── package.json │ ├── src │ ├── config │ │ ├── config.ts │ │ └── dataset.ts │ ├── create-stream-channel │ │ ├── index.ts │ │ └── run.ts │ ├── index.ts │ ├── models │ │ └── config.model.ts │ ├── services │ │ ├── authentication.service.ts │ │ ├── channel.service.ts │ │ └── identity.service.ts │ ├── setup │ │ ├── setup-ems.ts │ │ └── write-messages.ts │ └── utils │ │ ├── client.ts │ │ └── encryption.ts │ ├── tsconfig.dev.json │ └── tsconfig.json ├── dashboard ├── .dockerignore ├── .env.example ├── .eslintrc.cjs ├── .gitignore ├── .npmrc ├── .prettierrc ├── CHANGELOG.md ├── Dockerfile ├── README.md ├── package-lock.json ├── package.json ├── src │ ├── app.d.ts │ ├── app.html │ ├── assets │ │ └── logo.png │ └── routes │ │ ├── __layout.svelte │ │ ├── history │ │ ├── [channelAddress].svelte │ │ └── index.svelte │ │ ├── identity-manager │ │ ├── [id].svelte │ │ └── index.svelte │ │ ├── index.svelte │ │ ├── streams-manager │ │ ├── [channelAddress].svelte │ │ └── index.svelte │ │ └── verify-credential.svelte ├── static │ └── favicon.png ├── svelte.config.js └── tsconfig.json ├── docker-compose.yml ├── kubernetes ├── audit-trail-deployment.yaml ├── init-ssi-bridge.yaml ├── is-config.yaml ├── is-dashboard-deployment.yaml ├── is-secrets.yaml ├── is-service.yaml ├── kong-gw │ ├── kong.yml │ ├── rate-limiting.yml │ └── setup.md ├── optional │ ├── mongo-deployment.yaml │ ├── mongo-init-config.yaml │ ├── mongo-secret.yaml │ └── mongo-service.yaml └── ssi-bridge-deployment.yaml └── mongo-init.js.example /.env.example: -------------------------------------------------------------------------------- 1 | PORT=3000 2 | IOTA_PERMA_NODE=https://chrysalis-chronicle.iota.org/api/mainnet/ 3 | IOTA_HORNET_NODE=https://chrysalis-nodes.iota.org:443 4 | 5 | DATABASE_NAME= 6 | MONGO_INITDB_ROOT_USERNAME= 7 | MONGO_INITDB_ROOT_PASSWORD= 8 | DATABASE_URL=mongodb://:@mongo:27017 9 | 10 | SERVER_SECRET= 11 | JWT_SECRET= 12 | API_KEY= 13 | COMMIT_HASH= 14 | 15 | GATEWAY_URL= 16 | -------------------------------------------------------------------------------- /.github/workflows/build-api.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: Build Api 4 | 5 | # Controls when the action will run. 6 | on: 7 | # Triggers the workflow on push or pull request events but only for the master branch 8 | pull_request: 9 | branches: [ master, develop ] 10 | paths: 11 | - "api/**" 12 | - ".github/workflows/build-api.yml" 13 | 14 | 15 | # Allows you to run this workflow manually from the Actions tab 16 | workflow_dispatch: 17 | 18 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 19 | jobs: 20 | # This workflow contains a single job called "build" 21 | build: 22 | # The type of runner that the job will run on 23 | runs-on: ubuntu-latest 24 | 25 | # Steps represent a sequence of tasks that will be executed as part of the job 26 | steps: 27 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 28 | - uses: actions/checkout@v2 29 | # Setup node and build api 30 | - name: Setup Node.js environment 31 | uses: actions/setup-node@v2.1.4 32 | with: 33 | node-version: '14.x' 34 | # build local package in advance 35 | - name: Build shared modules 36 | run: | 37 | cd api/shared-modules 38 | npm install 39 | npm run build 40 | - name: Build audit-trail-gw 41 | run: | 42 | cd api/audit-trail-gw 43 | npm install 44 | npm run build 45 | - name: Build ssi-bridge 46 | run: | 47 | cd api/ssi-bridge 48 | npm install 49 | npm run build 50 | -------------------------------------------------------------------------------- /.github/workflows/deploy-e-shop-backend.yml: -------------------------------------------------------------------------------- 1 | name: Deploy E-shop-backend 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | paths: 7 | - "clients/e-shop-demo/**" 8 | - ".github/workflows/deploy-e-shop-backend.yml" 9 | 10 | workflow_dispatch: 11 | 12 | jobs: 13 | deploy: 14 | runs-on: ubuntu-latest 15 | defaults: 16 | run: 17 | working-directory: ./clients/e-shop-demo/e-shop-backend 18 | env: 19 | VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} 20 | VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} 21 | VERCEL_PROJECT_NAME: ${{ secrets.VERCEL_PROJECT_NAME }} 22 | 23 | steps: 24 | - uses: actions/checkout@v2 25 | 26 | - uses: actions/setup-node@v2 27 | with: 28 | node-version: '14' 29 | 30 | - run: npm install 31 | 32 | - run: npm run build 33 | 34 | - name: Deploy backend 35 | run: | 36 | npx vercel --token ${{ secrets.VERCEL_TOKEN }} --scope ${{ secrets.VERCEL_SCOPE }} --force --build-env PORT=${{ secrets.ESHOP_BACKEND_PORT }} --build-env BASE_URL=${{ secrets.ESHOP_BACKEND_BASE_URL }} --build-env API_KEY=${{ secrets.ESHOP_BACKEND_API_KEY }} --prod -------------------------------------------------------------------------------- /.github/workflows/publish-audit-trail-gw-dockerhub.yml: -------------------------------------------------------------------------------- 1 | name: Publish Audit Trail GW on DockerHub 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'master' 7 | - 'develop' 8 | tags: 9 | - 'v*' 10 | - 'audit-trail-gw-v*' 11 | paths: 12 | - "api/audit-trail-gw/**" 13 | - ".github/workflows/publish-audit-trail-gw-dockerhub.yml" 14 | pull_request: 15 | branches: 16 | - 'master' 17 | paths: 18 | - "api/audit-trail-gw/**" 19 | - ".github/workflows/publish-audit-trail-gw-dockerhub.yml" 20 | workflow_dispatch: 21 | 22 | jobs: 23 | push_audit_trail_to_registry: 24 | name: Push Audit Trail image to Docker Hub 25 | runs-on: ubuntu-latest 26 | steps: 27 | - name: Check out the repo 28 | uses: actions/checkout@v2 29 | 30 | - name: Log in to Docker Hub 31 | uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 32 | with: 33 | username: ${{ secrets.IOTALEDGER_DOCKER_USERNAME }} 34 | password: ${{ secrets.IOTALEDGER_DOCKER_PASSWORD }} 35 | 36 | - name: Extract metadata (tags, labels) for Docker 37 | id: meta 38 | uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 39 | with: 40 | images: iotaledger/audit-trail-gw 41 | 42 | - name: Build and push Docker image 43 | uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc 44 | with: 45 | context: api/audit-trail-gw 46 | file: ./api/audit-trail-gw/Dockerfile 47 | push: true 48 | tags: ${{ steps.meta.outputs.tags }} 49 | labels: ${{ steps.meta.outputs.labels }} 50 | -------------------------------------------------------------------------------- /.github/workflows/publish-is-dashboard-dockerhub.yml: -------------------------------------------------------------------------------- 1 | name: Publish IS-Dashboard on DockerHub 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'master' 7 | - 'develop' 8 | tags: 9 | - 'v*' 10 | - 'is-dashboard-v*' 11 | paths: 12 | - "dashboard/**" 13 | - ".github/workflows/publish-is-dashboard-dockerhub.yml" 14 | pull_request: 15 | branches: 16 | - 'master' 17 | paths: 18 | - "dashboard/**" 19 | - ".github/workflows/publish-is-dashboard-dockerhub.yml" 20 | workflow_dispatch: 21 | 22 | jobs: 23 | push_is_dashboard_to_registry: 24 | name: Push IS Dashboard image to Docker Hub 25 | runs-on: ubuntu-latest 26 | steps: 27 | - name: Check out the repo 28 | uses: actions/checkout@v2 29 | 30 | - name: Log in to Docker Hub 31 | uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 32 | with: 33 | username: ${{ secrets.IOTALEDGER_DOCKER_USERNAME }} 34 | password: ${{ secrets.IOTALEDGER_DOCKER_PASSWORD }} 35 | 36 | - name: Extract metadata (tags, labels) for Docker 37 | id: meta 38 | uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 39 | with: 40 | images: iotaledger/is-dashboard 41 | 42 | - name: Build and push Docker image 43 | uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc 44 | with: 45 | context: dashboard/ 46 | file: ./dashboard/Dockerfile 47 | push: true 48 | tags: ${{ steps.meta.outputs.tags }} 49 | labels: ${{ steps.meta.outputs.labels }} 50 | -------------------------------------------------------------------------------- /.github/workflows/publish-node-sdk-npmjs.yaml: -------------------------------------------------------------------------------- 1 | name: Publish client-sdk to npmjs 2 | on: 3 | push: 4 | branches: [ develop ] 5 | paths: 6 | - "clients/client-sdk/**" 7 | - ".github/workflows/publish-node-sdk-npmjs.yml" 8 | workflow_dispatch: 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | # Setup .npmrc file to publish to npm 15 | - uses: actions/setup-node@v2 16 | with: 17 | node-version: '16.x' 18 | registry-url: 'https://registry.npmjs.org' 19 | # build local package in advance 20 | - run: | 21 | cd api/shared-modules 22 | npm install 23 | npm run build 24 | - run: cd clients/client-sdk && npm ci 25 | - run: cd clients/client-sdk && npm run build 26 | - run: | 27 | cd clients/client-sdk 28 | jq -r '"PACKAGE_JSON=\(.version)"' package.json >> $GITHUB_ENV 29 | - name: Stable version 30 | if: ${{ !contains(env.PACKAGE_JSON, 'alpha') }} 31 | run: cd clients/client-sdk && npm publish --access=public 32 | env: 33 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 34 | - name: Pre-release version 35 | if: ${{ contains(env.PACKAGE_JSON, 'alpha') }} 36 | run: cd clients/client-sdk && npm publish --tag next --access=public 37 | env: 38 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/publish-shared-modules-sdk-npmjs.yaml: -------------------------------------------------------------------------------- 1 | name: Publish shared-modules to npmjs 2 | on: 3 | push: 4 | branches: 5 | - 'master' 6 | - 'develop' 7 | paths: 8 | - "api/shared-modules/**" 9 | - ".github/workflows/publish-shared-modules-sdk-npmjs.yml" 10 | workflow_dispatch: 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | # Setup .npmrc file to publish to npm 17 | - uses: actions/setup-node@v2 18 | with: 19 | node-version: '16.x' 20 | registry-url: 'https://registry.npmjs.org' 21 | - run: | 22 | cd api/shared-modules 23 | jq -r '"PACKAGE_JSON=\(.version)"' package.json >> $GITHUB_ENV 24 | - name: Stable version 25 | if: ${{ !contains(env.PACKAGE_JSON, 'alpha') }} 26 | run: | 27 | cd api/shared-modules 28 | npm ci 29 | npm run build 30 | npm publish --access=public 31 | env: 32 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 33 | - name: Pre-release version 34 | if: ${{ contains(env.PACKAGE_JSON, 'alpha') }} 35 | run: | 36 | cd api/shared-modules 37 | npm ci 38 | npm run build 39 | npm publish --tag next --access=public 40 | env: 41 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/publish-ssi-bridge-dockerhub.yml: -------------------------------------------------------------------------------- 1 | name: Publish SSI Bridge on DockerHub 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'master' 7 | - 'develop' 8 | tags: 9 | - 'v*' 10 | - 'ssi-bridge-v*' 11 | paths: 12 | - "api/ssi-bridge/**" 13 | - ".github/workflows/publish-ssi-bridge-dockerhub.yml" 14 | pull_request: 15 | branches: 16 | - 'master' 17 | paths: 18 | - "api/ssi-bridge/**" 19 | - ".github/workflows/publish-ssi-bridge-dockerhub.yml" 20 | workflow_dispatch: 21 | 22 | jobs: 23 | push_ssi_bridge_to_registry: 24 | name: Push SSI image to Docker Hub 25 | runs-on: ubuntu-latest 26 | steps: 27 | - name: Check out the repo 28 | uses: actions/checkout@v2 29 | 30 | - name: Log in to Docker Hub 31 | uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 32 | with: 33 | username: ${{ secrets.IOTALEDGER_DOCKER_USERNAME }} 34 | password: ${{ secrets.IOTALEDGER_DOCKER_PASSWORD }} 35 | 36 | - name: Extract metadata (tags, labels) for Docker 37 | id: meta 38 | uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 39 | with: 40 | images: iotaledger/ssi-bridge 41 | 42 | - name: Build and push Docker image 43 | uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc 44 | with: 45 | context: api/ssi-bridge 46 | file: ./api/ssi-bridge/Dockerfile 47 | push: true 48 | tags: ${{ steps.meta.outputs.tags }} 49 | labels: ${{ steps.meta.outputs.labels }} 50 | -------------------------------------------------------------------------------- /.github/workflows/run-tests.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: Run Api Tests 4 | 5 | # Controls when the action will run. 6 | on: 7 | # Triggers the workflow on push or pull request events but only for the master branch 8 | pull_request: 9 | branches: [ master, develop ] 10 | paths: 11 | - "api/**" 12 | - ".github/workflows/build-api.yml" 13 | 14 | 15 | # Allows you to run this workflow manually from the Actions tab 16 | workflow_dispatch: 17 | 18 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 19 | jobs: 20 | # This workflow contains a single job called "build" 21 | build: 22 | # The type of runner that the job will run on 23 | runs-on: ubuntu-latest 24 | 25 | # Steps represent a sequence of tasks that will be executed as part of the job 26 | steps: 27 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 28 | - uses: actions/checkout@v2 29 | # Setup node and build api 30 | - name: Setup Node.js environment 31 | uses: actions/setup-node@v2.1.4 32 | with: 33 | node-version: '14.x' 34 | # build local package in advance 35 | - name: Build shared modules 36 | run: | 37 | cd api/shared-modules 38 | npm install 39 | npm run build 40 | - name: Test audit-trail-gw 41 | run: | 42 | cd api/audit-trail-gw 43 | npm install 44 | npm run test 45 | - name: Test ssi-bridge 46 | run: | 47 | cd api/ssi-bridge 48 | npm install 49 | npm run test 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | lib 4 | npm-debug.log 5 | .env 6 | mongo-init.js 7 | .DS_Store 8 | logs 9 | .vscode 10 | **/server-identity.json 11 | openApiSpecification.json 12 | adminIdentity.json 13 | monitoring 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ### v0.1.4 -> v0.1.5 4 | 5 | ##### Non breaking changes: 6 | 7 | - Adjust the `/history` endpoint to also support public channels 8 | - Add history UI to the dashboard 9 | 10 | ### v0.1.3 -> v0.1.4 11 | 12 | ##### Non breaking changes: 13 | 14 | - User interface (dashboard) to manage identities, credentials and channels 15 | - Split code base into `ssi-bridge` and `audit-trail-gw` 16 | - Audit trail gateway is now independent of ssi-bridge 17 | - API gateway for the micro services and dashboard 18 | - A channel can now have a `name` and a `description` 19 | - Identity chaining via `creator` field 20 | - Enhanced search for channel-info and identities 21 | - New endpoint: `/authentication/verify-jwt` to check whether a JWT is valid and signed by the IS 22 | - New guides to the wiki 23 | 24 | ### v0.1.2 -> v0.1.3 25 | 26 | ##### Breaking changes: 27 | - Rename `identityId` to `id` 28 | - Rename `identity-docs` collection to `identity-keys` 29 | - Adjust data model of the `identity-docs` collection 30 | 31 | #### __Use the migration script linked [here](./MIGRATION.md) to migrate the database and stored data models.__ 32 | 33 | ##### Non breaking changes: 34 | - Add wiki with detailed examples 35 | - NodeJs client sdk 36 | - Add several security features 37 | - Made API easier to scale by adding Kubernetes 38 | - Improve setup scripts 39 | - Dependencies upgrades 40 | 41 | -------------------------------------------------------------------------------- /MIGRATION.md: -------------------------------------------------------------------------------- 1 | # Migration 2 | 3 | > #### HowTo 4 | > For Data Migrations it is recommended to use __Robo 3T__ which let's you easily update several documents by inserting shell commands for each collection. 5 | 6 | ____ 7 | 8 | ## Data Migration from 0.1.2 -> 0.1.3 9 | 10 | In Version __0.1.3__ the data model was modified, in fact __identityId__ was renamed to __id__. Existing data and requests must be adjusted to support this data model. 11 | 12 | For the database migration there are two scripts which can be found in the following: 13 | 14 | ◈ [Link to the Robo3T Migration Script](./api/assets/migrations/data-migration-0.1.3.js) ◈ 15 | 16 | You can also run the following TypeScript tool to migrate the database. Make sure you've set the correct __DATABASE_URL__ and __DATABASE_NAME__ in the __.env__ file. 17 | 18 | ◈ [Link to the TypeScript Migration](./api/src/tools/migration/migration-to-0.1.3.ts) ◈ 19 | 20 | Run it by using: 21 | 22 | ```ts-node src/tools/migration/migration-to-0.1.3.ts``` 23 | 24 | --- 25 | 26 | -------------------------------------------------------------------------------- /api/assets/Secure_Digital_Infrastructure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger-archive/integration-services/be1bd31390d74d30fcd2cbd7ae5de53de27b028e/api/assets/Secure_Digital_Infrastructure.png -------------------------------------------------------------------------------- /api/audit-trail-gw/.dockerignore: -------------------------------------------------------------------------------- 1 | .env -------------------------------------------------------------------------------- /api/audit-trail-gw/.env.example: -------------------------------------------------------------------------------- 1 | PORT=3000 2 | IOTA_PERMA_NODE=https://chrysalis-chronicle.iota.org/api/mainnet/ 3 | IOTA_HORNET_NODE=https://chrysalis-nodes.iota.org:443 4 | 5 | DATABASE_NAME= 6 | MONGO_INITDB_ROOT_USERNAME= 7 | MONGO_INITDB_ROOT_PASSWORD= 8 | DATABASE_URL=mongodb://:@mongo:27017 9 | 10 | SSI_BRIDGE_URL= 11 | SSI_BRIDGE_API_KEY= 12 | 13 | SERVER_SECRET= 14 | JWT_SECRET= 15 | API_KEY= 16 | COMMIT_HASH= 17 | 18 | -------------------------------------------------------------------------------- /api/audit-trail-gw/.eslintignore: -------------------------------------------------------------------------------- 1 | .eslintrc.js 2 | *.d.ts 3 | # don't ever lint node_modules 4 | node_modules 5 | # don't lint build output (make sure it's set to your correct build folder name) 6 | dist 7 | # don't lint nyc coverage output 8 | coverage 9 | # streams wasm files 10 | wasm-node 11 | **/mocks -------------------------------------------------------------------------------- /api/audit-trail-gw/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | plugins: ['@typescript-eslint', 'prettier'], 5 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'], 6 | rules: { 7 | '@typescript-eslint/no-explicit-any': 'off', 8 | '@typescript-eslint/explicit-module-boundary-types': 'off', 9 | '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }] 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /api/audit-trail-gw/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "none", 4 | "singleQuote": true, 5 | "printWidth": 140, 6 | "tabWidth": 2, 7 | "useTabs": true 8 | } 9 | -------------------------------------------------------------------------------- /api/audit-trail-gw/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ### 0.1.8 (2022-07-25) 4 | 5 | - Updated to is-shared-modules version 0.1.20 -> Adjusted imports for new version 6 | 7 | ### 0.1.7 (2022-07-05) 8 | 9 | - Updated dependencies 10 | - Enhance channel info search 11 | - Add source when writing to a channel 12 | - Implement hidden channels -------------------------------------------------------------------------------- /api/audit-trail-gw/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16-alpine 2 | WORKDIR /usr/src/app 3 | 4 | COPY . . 5 | 6 | RUN npm install 7 | RUN npm run build 8 | 9 | EXPOSE 3000 10 | CMD [ "node", "dist/index.js", "server" ] -------------------------------------------------------------------------------- /api/audit-trail-gw/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: ['/src'], 3 | transform: { 4 | '^.+\\.tsx?$': 'ts-jest' 5 | }, 6 | testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$', 7 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'] 8 | }; 9 | -------------------------------------------------------------------------------- /api/audit-trail-gw/src/database/concurrency-lock.ts: -------------------------------------------------------------------------------- 1 | import { CollectionNames } from './constants'; 2 | import { MongoDbService } from '@iota/is-shared-modules/node'; 3 | import { ConcurrencyLock } from '@iota/is-shared-modules'; 4 | 5 | const collectionName = CollectionNames.concurrencyLocks; 6 | 7 | export const getLock = async (lock: string): Promise => { 8 | const query = { _id: lock }; 9 | return MongoDbService.getDocument(collectionName, query); 10 | }; 11 | 12 | export const insertLock = async (lock: string) => { 13 | const document = { 14 | _id: lock, 15 | lock, 16 | created: new Date() 17 | }; 18 | 19 | const res = await MongoDbService.insertDocument(collectionName, document); 20 | if (!res.result.n) { 21 | throw new Error('could not add the lock!'); 22 | } 23 | return res; 24 | }; 25 | 26 | export const removeLock = async (lock: string) => { 27 | const query = { _id: lock }; 28 | return MongoDbService.removeDocument(collectionName, query); 29 | }; 30 | -------------------------------------------------------------------------------- /api/audit-trail-gw/src/database/constants.ts: -------------------------------------------------------------------------------- 1 | export const enum CollectionNames { 2 | channelInfo = 'channel-info', 3 | channelData = 'channel-data', 4 | subscriptions = 'subscriptions', 5 | subscriptionStates = 'subscription-states', 6 | concurrencyLocks = 'concurrency-locks' 7 | } 8 | -------------------------------------------------------------------------------- /api/audit-trail-gw/src/middlewares/api-key.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from 'express'; 2 | import { StatusCodes } from 'http-status-codes'; 3 | 4 | export const hasValidApiKey = (serverApiKey: string) => (req: Request, res: Response, next: NextFunction) => { 5 | const decodeParam = (param: string): string | undefined => (param ? decodeURI(param) : undefined); 6 | const apiKey = req.query && decodeParam(req.query['api-key']); 7 | 8 | if (serverApiKey && apiKey !== serverApiKey) { 9 | return res.status(StatusCodes.UNAUTHORIZED).send({ error: 'no valid api key provided!' }); 10 | } 11 | 12 | next(); 13 | }; 14 | -------------------------------------------------------------------------------- /api/audit-trail-gw/src/middlewares/authentication.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from 'express'; 2 | import { StatusCodes } from 'http-status-codes'; 3 | import jwt from 'jsonwebtoken'; 4 | 5 | export const isAuth = (secret: string) => (req: Request, res: Response, next: NextFunction) => { 6 | const { authorization } = req.headers; 7 | 8 | if (!authorization || !authorization.startsWith('Bearer')) { 9 | return res.status(StatusCodes.UNAUTHORIZED).send({ error: 'not authenticated!' }); 10 | } 11 | 12 | const split = authorization.split('Bearer '); 13 | if (split.length !== 2) { 14 | return res.status(StatusCodes.UNAUTHORIZED).send({ error: 'not authenticated!' }); 15 | } 16 | 17 | const token = split[1]; 18 | const decodedToken: any = jwt.verify(token, secret); 19 | 20 | if (typeof decodedToken === 'string' || !decodedToken?.user) { 21 | return res.status(StatusCodes.UNAUTHORIZED).send({ error: 'not authenticated!' }); 22 | } 23 | 24 | (req as any).user = decodedToken.user; 25 | next(); 26 | }; 27 | -------------------------------------------------------------------------------- /api/audit-trail-gw/src/middlewares/error.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response, NextFunction } from 'express'; 2 | import { StatusCodes } from 'http-status-codes'; 3 | import { ValidationError } from 'express-json-validator-middleware'; 4 | import { Logger } from '@iota/is-shared-modules/node'; 5 | 6 | /** 7 | * Error middleware to log and return the error to the client. 8 | * 9 | * @param {Error} err Received error from endpoint 10 | * @param {Request} _req Client request 11 | * @param {Response} res Client response 12 | * @param {NextFunction} _next Next function 13 | */ 14 | export const errorMiddleware = (err: Error, _req: Request, res: Response, _next: NextFunction): void => { 15 | if (err instanceof ValidationError) { 16 | console.error(JSON.stringify(err)); 17 | res.status(StatusCodes.BAD_REQUEST); 18 | res.send({ error: 'No valid body provided!' }); 19 | return; 20 | } 21 | Logger.getInstance().error(err.message); 22 | res.status(StatusCodes.INTERNAL_SERVER_ERROR); 23 | res.send({ error: err.message }); 24 | }; 25 | -------------------------------------------------------------------------------- /api/audit-trail-gw/src/middlewares/mongodb-sanitizer.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from 'express'; 2 | import * as _ from 'lodash'; 3 | import { StatusCodes } from 'http-status-codes'; 4 | 5 | const BAD_CHAR = '$'; 6 | 7 | export const mongodbSanitizer = (req: Request, res: Response, next: NextFunction) => { 8 | const body = req.body; 9 | 10 | const val = hasBadCharacter(body, BAD_CHAR); 11 | 12 | if (val) { 13 | return res.status(StatusCodes.BAD_REQUEST).send({ error: `${BAD_CHAR} is not allowed as key prefix.` }); 14 | } 15 | 16 | next(); 17 | }; 18 | 19 | const hasBadCharacter = (data: any, character: string): boolean => { 20 | if (data == null) { 21 | return false; 22 | } 23 | 24 | if (_.isObject(data)) { 25 | const keys = Object.keys(data); 26 | const numberOfBadChars = keys?.filter((k) => k.startsWith(character))?.length; 27 | 28 | if (numberOfBadChars != 0) { 29 | return true; 30 | } 31 | 32 | const values = Object.values(data); 33 | return recursiveCall(values, character); 34 | } else if (_.isArray(data)) { 35 | return recursiveCall(data, character); 36 | } 37 | 38 | return false; 39 | }; 40 | 41 | const recursiveCall = (values: any, char: string): boolean => 42 | values ? values.map((val: any) => hasBadCharacter(val, char)).some((v: boolean) => v) : false; 43 | -------------------------------------------------------------------------------- /api/audit-trail-gw/src/models/config/index.ts: -------------------------------------------------------------------------------- 1 | export interface Config { 2 | port: number; 3 | apiVersion: string; 4 | databaseUrl: string; 5 | databaseName: string; 6 | streamsConfig: StreamsConfig; 7 | serverSecret: string; 8 | jwtSecret: string; 9 | apiKey: string | undefined; 10 | hornetNode: string; 11 | permaNode: string; 12 | commitHash: string; 13 | ssiBridgeUrl: string; 14 | ssiBridgeApiKey: string; 15 | } 16 | 17 | export interface StreamsConfig { 18 | password: string; 19 | node: string; 20 | permaNode: string; 21 | } 22 | -------------------------------------------------------------------------------- /api/audit-trail-gw/src/routers/index.ts: -------------------------------------------------------------------------------- 1 | export { channelRouter } from './channel'; 2 | export { channelInfoRouter } from './channel-info'; 3 | export { subscriptionRouter } from './subscription'; 4 | -------------------------------------------------------------------------------- /api/audit-trail-gw/src/routers/middlewares.ts: -------------------------------------------------------------------------------- 1 | import { Validator } from 'express-json-validator-middleware'; 2 | import { hasValidApiKey } from '../middlewares/api-key'; 3 | import { isAuth } from '../middlewares/authentication'; 4 | import { ConfigurationService } from '../services/configuration-service'; 5 | import { Logger } from '@iota/is-shared-modules/node'; 6 | 7 | const { jwtSecret, apiKey } = ConfigurationService.getInstance(Logger.getInstance()).config; 8 | 9 | export const validator = new Validator({ allErrors: true }); 10 | export const validate = validator.validate; 11 | 12 | export const authMiddleWare = isAuth(jwtSecret); 13 | export const apiKeyMiddleware = hasValidApiKey(apiKey); 14 | -------------------------------------------------------------------------------- /api/audit-trail-gw/src/routers/server-info/index.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import { ServerInfoRoutes } from '../../routes/server-info'; 3 | import { Logger } from '@iota/is-shared-modules/node'; 4 | import { ConfigurationService } from '../../services/configuration-service'; 5 | 6 | const serverInfoRoutes = new ServerInfoRoutes(Logger.getInstance(), ConfigurationService.getInstance(Logger.getInstance())); 7 | const { getServerInfo } = serverInfoRoutes; 8 | export const serverInfoRouter = Router(); 9 | 10 | /** 11 | * @openapi 12 | * /info: 13 | * get: 14 | * summary: Get information about the server 15 | * description: Get information about the server like commitHash, server identity id and api version 16 | * tags: 17 | * - server-info 18 | * servers: 19 | * - url: / 20 | * responses: 21 | * 200: 22 | * description: Returns information about the server 23 | * content: 24 | * application/json: 25 | * schema: 26 | * type: object 27 | * properties: 28 | * commitHash: 29 | * type: string 30 | * id: 31 | * type: string 32 | * version: 33 | * type: string 34 | * required: 35 | * - commitHash 36 | * - id 37 | * - version 38 | * 5XX: 39 | * description: Unexpected error 40 | * content: 41 | * application/json: 42 | * schema: 43 | * type: object 44 | * properties: 45 | * error: 46 | * type: string 47 | */ 48 | serverInfoRouter.get('/info', getServerInfo); 49 | -------------------------------------------------------------------------------- /api/audit-trail-gw/src/routers/services.ts: -------------------------------------------------------------------------------- 1 | import { AuthorizationService } from '../services/authorization-service'; 2 | import { ChannelInfoService } from '../services/channel-info-service'; 3 | import { ChannelService } from '../services/channel-service'; 4 | import { StreamsService } from '../services/streams-service'; 5 | import { SubscriptionService } from '../services/subscription-service'; 6 | import { Logger } from '@iota/is-shared-modules/node'; 7 | import { ConfigurationService } from '../services/configuration-service'; 8 | 9 | const logger = Logger.getInstance(); 10 | const configService = ConfigurationService.getInstance(logger); 11 | const { streamsConfig } = configService.config; 12 | 13 | export const authorizationService = new AuthorizationService(); 14 | export const channelInfoService = new ChannelInfoService(); 15 | export const streamsService = new StreamsService(streamsConfig, logger); 16 | export const subscriptionService = new SubscriptionService(streamsService, channelInfoService, streamsConfig); 17 | export const channelService = new ChannelService(streamsService, channelInfoService, subscriptionService, configService.config, logger); 18 | -------------------------------------------------------------------------------- /api/audit-trail-gw/src/routes/server-info/index.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from 'express'; 2 | import { StatusCodes } from 'http-status-codes'; 3 | import { ILogger } from '@iota/is-shared-modules/node'; 4 | import * as os from 'os'; 5 | import { ConfigurationService } from '../../services/configuration-service'; 6 | 7 | export class ServerInfoRoutes { 8 | constructor(private readonly logger: ILogger, private readonly configService: ConfigurationService) {} 9 | 10 | getServerInfo = async (req: Request, res: Response, next: NextFunction): Promise => { 11 | try { 12 | const config = this.configService.config; 13 | const hostname = os.hostname(); 14 | const commitHash = config.commitHash || 'not defined'; 15 | const version = config.apiVersion || 'not defined'; 16 | 17 | res.status(StatusCodes.OK).send({ 18 | commitHash, 19 | hostname, 20 | version 21 | }); 22 | } catch (error) { 23 | this.logger.error(error); 24 | next(new Error('could not get server info')); 25 | } 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /api/audit-trail-gw/src/services/authorization-service.ts: -------------------------------------------------------------------------------- 1 | import { AuthorizationCheck, CredentialTypes, User, UserType, UserRoles } from '@iota/is-shared-modules'; 2 | export class AuthorizationService { 3 | isAuthorized(requestUser: User, id: string): AuthorizationCheck { 4 | const isAuthorizedUser = this.isAuthorizedUser(requestUser.id, id); 5 | if (!isAuthorizedUser) { 6 | const isAuthorizedAdmin = this.isAuthorizedAdmin(requestUser); 7 | if (!isAuthorizedAdmin) { 8 | return { isAuthorized: false, error: new Error('not allowed!') }; 9 | } 10 | } 11 | return { isAuthorized: true, error: null }; 12 | } 13 | 14 | isAuthorizedUser(requestUserId: string, id: string): boolean { 15 | return requestUserId === id; 16 | } 17 | 18 | isAuthorizedAdmin(requestUser: User): boolean { 19 | const role = requestUser?.role; 20 | 21 | if (role === UserRoles.Admin) { 22 | return true; 23 | } 24 | return false; 25 | } 26 | 27 | hasAuthorizedUserType(type: string): boolean { 28 | return type === UserType.Person || type === UserType.Service || type === UserType.Organization; 29 | } 30 | 31 | hasVerificationCredentialType(type: string[]): boolean { 32 | return type.some((t) => t === CredentialTypes.VerifiedIdentityCredential); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /api/audit-trail-gw/src/services/tests/configuration-service.test.ts: -------------------------------------------------------------------------------- 1 | import { ConfigMock } from '../../test/mocks/config'; 2 | import { ConfigurationService } from '../configuration-service'; 3 | import { LoggerMock } from '../../test/mocks/logger'; 4 | 5 | describe('test configuration service', () => { 6 | let loggerSpy: jest.SpyInstance; 7 | beforeEach(() => { 8 | loggerSpy = jest.spyOn(LoggerMock, 'error'); 9 | }); 10 | 11 | it('should crash since env variables are missing', async () => { 12 | const mockExit = jest.spyOn(process, 'exit').mockImplementation(() => { return undefined as never }); 13 | ConfigurationService.getInstance(LoggerMock); 14 | expect(mockExit).toHaveBeenCalledWith(1); 15 | }); 16 | 17 | it('should start without errors if all env vars are set', async () => { 18 | process.env.SERVER_SECRET = ConfigMock.serverSecret; 19 | process.env.JWT_SECRET = ConfigMock.jwtSecret; 20 | process.env.DATABASE_URL = ConfigMock.databaseUrl; 21 | process.env.DATABASE_NAME = ConfigMock.databaseName; 22 | process.env.SERVER_SECRET = ConfigMock.serverSecret; 23 | process.env.IOTA_HORNET_NODE = ConfigMock.hornetNode; 24 | process.env.IOTA_PERMA_NODE = ConfigMock.permaNode; 25 | 26 | const start = () => ConfigurationService.getInstance(LoggerMock); 27 | expect(start).not.toThrow(); 28 | expect(loggerSpy).not.toHaveBeenCalled(); 29 | }); 30 | 31 | afterEach(() => { 32 | jest.clearAllMocks(); 33 | jest.restoreAllMocks(); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /api/audit-trail-gw/src/setup/database-seeder.ts: -------------------------------------------------------------------------------- 1 | import { CollectionNames } from '../database/constants'; 2 | import { ILogger, MongoDbService } from '@iota/is-shared-modules/node'; 3 | 4 | const LOCK_EXPIRATION_TIME_SEC = 55; 5 | 6 | export interface IDatabaseSeeder { 7 | seed(): void; 8 | } 9 | 10 | export class DatabaseSeeder { 11 | constructor(private readonly logger: ILogger) {} 12 | async seed() { 13 | const db = MongoDbService.db; 14 | const concurrencyCollection = db.collection(CollectionNames.concurrencyLocks); 15 | await concurrencyCollection.createIndex({ created: 1 }, { expireAfterSeconds: LOCK_EXPIRATION_TIME_SEC }); 16 | 17 | const channelCollection = db.collection(CollectionNames.channelInfo); 18 | await channelCollection.createIndex({ name: 1 }, { unique: true, partialFilterExpression: { name: { $exists: true } } }); 19 | 20 | this.logger.log('Database successfully seeded.'); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /api/audit-trail-gw/src/setup/setup-manager.ts: -------------------------------------------------------------------------------- 1 | import { DatabaseSeeder } from './database-seeder'; 2 | import { Logger, MongoDbService } from '@iota/is-shared-modules/node'; 3 | import { ConfigurationService } from '../services/configuration-service'; 4 | 5 | export class SetupManager { 6 | async runSetup() { 7 | const logger = Logger.getInstance(); 8 | const configService = ConfigurationService.getInstance(logger); 9 | const config = configService.config; 10 | 11 | await MongoDbService.connect(config.databaseUrl, config.databaseName); 12 | 13 | // seed the database with indexes 14 | const dbSeeder = new DatabaseSeeder(logger); 15 | await dbSeeder.seed(); 16 | 17 | await MongoDbService.disconnect(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /api/audit-trail-gw/src/test/mocks/config.ts: -------------------------------------------------------------------------------- 1 | import { Config, StreamsConfig } from '../../models/config'; 2 | 3 | export const StreamsConfigMock: StreamsConfig = { 4 | node: '', 5 | permaNode: '', 6 | password: 'veryvery-very-very-server-secret' 7 | }; 8 | 9 | export const ConfigMock: Config = { 10 | apiKey: 'test-v1', 11 | serverSecret: 'veryvery-very-very-server-secret', 12 | jwtSecret: 'veryvery-very-very-server-secret', 13 | databaseName: 'testdatabasename', 14 | databaseUrl: 'testdatabaseurl', 15 | apiVersion: '0.1', 16 | port: 3000, 17 | permaNode: 'testpermanodeurl', 18 | hornetNode: 'testhornetnodeurl', 19 | ssiBridgeApiKey: '', 20 | ssiBridgeUrl: 'http://localhost:3001/api/v0.2', 21 | streamsConfig: StreamsConfigMock, 22 | commitHash: '' 23 | }; 24 | -------------------------------------------------------------------------------- /api/audit-trail-gw/src/test/mocks/key-collection.ts: -------------------------------------------------------------------------------- 1 | export const KeyCollectionMock = { 2 | _id: 0, 3 | count: 8, 4 | index: 0, 5 | type: 'ed25519', 6 | publicKeyBase58: 'testpublickey', 7 | keys: [ 8 | { public: '8umCw8eGrcJvo2uEcfa4138LJaW3mebiQkDfEaR7hTpG', secret: 'fmgfdXAGDpc9AGnNVHUiktAi8KQnhi2cWeL5Lp1TWtm' }, 9 | { public: 'AKUEjuUumzF8h2qArNzPVko51z4XmSL6xNcDGFqzpiT7', secret: 'EHUYgZTk83xyeBNzYRVBb1StF6mDnzLjEwdX8PpCAtZ5' }, 10 | { public: '9doihy1Vi8Wombo4bXpzPfzH8dLd6rPxReXVVJgZZTLB', secret: '4rtQE2ntEFMMJuq8dThh7FBEZjhJrh3RdGVWvURfFv7J' }, 11 | { public: '4sTkdxkk8yPD8KuV8zEVWVdxYoup9JUQ8DgZE3yAGYF8', secret: '8Qc9akDgq6bQH7ZVCnGTcYwFTqCuzqzQ8X9oss1A85MN' }, 12 | { public: 'B6vuVrVtoBtEdGMT4oYKeWkZrpoT1Yu5DD9qaT7rbsV2', secret: '3MDDAgayhRsuBPjDtdGy9nUKqrC8XZghWTamFrRLSZ4s' }, 13 | { public: '9wZMHHmycPb47UtHkrWJaMPDpjEE8f7QnF6rxB8Th8pC', secret: '5ZVUCJXn4NKXp7xwo82Cb9TfasGTrdUJZzd7kgbaaej6' }, 14 | { public: '3hdnmH8vgc48nsRZt5HVtMrygHF6Ny3HDxPQQPvics9H', secret: 'FJ3eT8cixEkaunp69m1EtwraTvKQBZiRTXaUdQ4eKJkB' }, 15 | { public: 'Gvg1u3msZzxtPztpLYPi9LKxMrj6kymyEvfsYyWw3q3U', secret: 'FZJrYHUohUDmLtKLFLoP9LS1qpuNAvDctm6AwNAvZrRC' } 16 | ] 17 | }; 18 | -------------------------------------------------------------------------------- /api/audit-trail-gw/src/test/mocks/logger.ts: -------------------------------------------------------------------------------- 1 | import { ILogger } from '@iota/is-shared-modules/node'; 2 | 3 | export const LoggerMock: ILogger = { 4 | getExpressWinstonOptions: () => ({ 5 | transports: [], 6 | headerBlacklist: ['Authorization', 'authorization', 'cookie'], 7 | level: 'info' 8 | }), 9 | log: (message: string) => {}, 10 | error: (message: string) => {} 11 | }; 12 | -------------------------------------------------------------------------------- /api/audit-trail-gw/src/test/mocks/service-mocks.ts: -------------------------------------------------------------------------------- 1 | import { IConfigurationService } from '../../services/configuration-service'; 2 | import { ConfigMock } from './config'; 3 | import { ServerIdentityMock } from './identities'; 4 | 5 | export const ConfigurationServiceMock: IConfigurationService = { 6 | config: ConfigMock, 7 | streamsConfig: ConfigMock.streamsConfig, 8 | serverIdentityId: ServerIdentityMock.document.doc.id, 9 | getRootIdentityId: async () => { 10 | return ServerIdentityMock.document.doc.id; 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /api/audit-trail-gw/src/tools/generate-secret-key/index.ts: -------------------------------------------------------------------------------- 1 | import { randomSecretKey } from '@iota/is-shared-modules/node'; 2 | 3 | const scrt = randomSecretKey(); 4 | 5 | console.log('Please store the server secret in the .env file as following:'); 6 | 7 | console.log(`SERVER_SECRET=${scrt}`); 8 | -------------------------------------------------------------------------------- /api/audit-trail-gw/src/tools/markdown-creator/index.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import swaggerJSDoc from 'swagger-jsdoc'; 3 | import * as dotenv from 'dotenv'; 4 | dotenv.config(); 5 | import { openApiDefinition } from '../../routers/swagger'; 6 | 7 | // 8 | const converter = () => { 9 | const openapiSpecification = swaggerJSDoc(openApiDefinition); 10 | fs.writeFileSync('./src/tools/markdown-creator/openApiSpecification.json', JSON.stringify(openapiSpecification)); 11 | }; 12 | 13 | converter(); 14 | -------------------------------------------------------------------------------- /api/audit-trail-gw/src/utils/lock/index.ts: -------------------------------------------------------------------------------- 1 | import { Mutex, MutexInterface } from 'async-mutex'; 2 | 3 | export interface ILock { 4 | acquire(key: string): Promise; 5 | } 6 | 7 | export class Lock implements ILock { 8 | private locks: Map; 9 | private static instance: Lock; 10 | 11 | private constructor() { 12 | this.locks = new Map(); 13 | } 14 | 15 | public static getInstance(): Lock { 16 | if (!Lock.instance) { 17 | Lock.instance = new Lock(); 18 | } 19 | return Lock.instance; 20 | } 21 | 22 | acquire(key: string) { 23 | if (!this.locks.has(key)) { 24 | this.locks.set(key, new Mutex()); 25 | } 26 | return this.locks.get(key).acquire(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /api/audit-trail-gw/tsconfig.dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "es2015", "es2015.iterable"], 5 | "esModuleInterop": true, 6 | "module": "commonjs", 7 | "moduleResolution": "node", 8 | "noUnusedLocals": false, 9 | "typeRoots": ["node_modules/@types", "types"], 10 | "outDir": "dist", 11 | "resolveJsonModule": true 12 | }, 13 | "include": ["src"] 14 | } 15 | -------------------------------------------------------------------------------- /api/audit-trail-gw/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "noImplicitAny": true, 5 | "lib": ["dom", "es2015", "es2015.iterable"], 6 | "esModuleInterop": true, 7 | "module": "commonjs", 8 | "moduleResolution": "node", 9 | "noUnusedLocals": true, 10 | "typeRoots": ["node_modules/@types", "types"], 11 | "outDir": "dist", 12 | "resolveJsonModule": true, 13 | "allowJs": true, 14 | "skipLibCheck": true 15 | }, 16 | "include": ["src"] 17 | } 18 | -------------------------------------------------------------------------------- /api/shared-modules/.eslintignore: -------------------------------------------------------------------------------- 1 | .eslintrc.js 2 | *.d.ts 3 | # don't ever lint node_modules 4 | node_modules 5 | # don't lint build output (make sure it's set to your correct build folder name) 6 | dist 7 | # don't lint nyc coverage output 8 | coverage 9 | # streams wasm files 10 | wasm-node 11 | **/mocks -------------------------------------------------------------------------------- /api/shared-modules/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | plugins: ['@typescript-eslint', 'prettier'], 5 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'], 6 | rules: { 7 | '@typescript-eslint/no-explicit-any': 'off', 8 | '@typescript-eslint/explicit-module-boundary-types': 'off', 9 | '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }] 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /api/shared-modules/.gitignore: -------------------------------------------------------------------------------- 1 | /node -------------------------------------------------------------------------------- /api/shared-modules/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "none", 4 | "singleQuote": true, 5 | "printWidth": 140, 6 | "tabWidth": 2, 7 | "useTabs": true 8 | } 9 | -------------------------------------------------------------------------------- /api/shared-modules/README.md: -------------------------------------------------------------------------------- 1 | # IOTA Integration Services Shared Modules 2 | This packages contains modules used in the [SSI-Bridge](https://github.com/iotaledger/integration-services/tree/develop/api/ssi-bridge), [Audit-Trail](https://github.com/iotaledger/integration-services/tree/develop/api/audit-trail-gw) and [IS-Client](https://www.npmjs.com/package/@iota/is-client). 3 | 4 | ****This package should only be installed if access to the Integration Service's models is needed.**** -------------------------------------------------------------------------------- /api/shared-modules/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: ['/src'], 3 | transform: { 4 | '^.+\\.tsx?$': 'ts-jest' 5 | }, 6 | testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$', 7 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'] 8 | }; 9 | -------------------------------------------------------------------------------- /api/shared-modules/src/node/index.ts: -------------------------------------------------------------------------------- 1 | export * from './services/mongodb-service'; 2 | export * from './utils/encryption'; 3 | export * from './utils/logger'; 4 | export * from './utils/text'; 5 | -------------------------------------------------------------------------------- /api/shared-modules/src/node/services/mongodb-service/index.test.ts: -------------------------------------------------------------------------------- 1 | import { MongoDbService } from '.'; 2 | 3 | describe('test MongoDbService', () => { 4 | it('getPlainObject should not return values with null or undefined!', () => { 5 | const updateObject = MongoDbService.getPlainObject({ 6 | id: 'test-12334', 7 | fieldIsNull: null, 8 | fieldIsUndefined: undefined, 9 | age: 0, 10 | emptyString: '' 11 | }); 12 | const expectedPlainObject = { 13 | emptyString: '', 14 | age: 0, 15 | id: 'test-12334' 16 | }; 17 | expect(updateObject).toEqual(expectedPlainObject); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /api/shared-modules/src/node/utils/text/index.ts: -------------------------------------------------------------------------------- 1 | import { parseISO, formatISO } from 'date-fns'; 2 | 3 | export const getDateFromString = (dateString: string): Date | undefined => { 4 | return dateString ? parseISO(dateString) : undefined; 5 | }; 6 | 7 | export const getDateStringFromDate = (date: Date): string | null | undefined => { 8 | return date && formatISO(date); 9 | }; 10 | 11 | export function toBytes(str: string): Uint8Array { 12 | const bytes = new Uint8Array(str.length); 13 | for (let i = 0; i < str.length; ++i) { 14 | bytes[i] = str.charCodeAt(i); 15 | } 16 | return bytes; 17 | } 18 | 19 | export function fromBytes(bytes: Array | Uint8Array): string { 20 | let str = ''; 21 | for (let i = 0; i < bytes.length; ++i) { 22 | str += String.fromCharCode(bytes[i]); 23 | } 24 | return str; 25 | } 26 | -------------------------------------------------------------------------------- /api/shared-modules/src/web/index.ts: -------------------------------------------------------------------------------- 1 | export * from './models'; 2 | -------------------------------------------------------------------------------- /api/shared-modules/src/web/models/index.ts: -------------------------------------------------------------------------------- 1 | export * from './schemas/request-response-body'; 2 | export * from './schemas'; 3 | export * from './types'; 4 | -------------------------------------------------------------------------------- /api/shared-modules/src/web/models/schemas/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | ChannelAddressSchema, 3 | ChannelInfoSchema, 4 | ChannelInfoSearchSchema, 5 | ChannelLogRequestOptionsSchema, 6 | ChannelType, 7 | TopicSchema 8 | } from './channel-info'; 9 | export { 10 | Encoding, 11 | KeyTypes, 12 | IdentityDocumentSchema, 13 | KeysSchema, 14 | IdentityKeysSchema, 15 | IdentityKeyPairSchema, 16 | VerifiableCredentialSchema, 17 | VerifiablePresentationSchema, 18 | VerifiableCredentialSubjectSchema 19 | } from './identity'; 20 | export { AccessRights, SubscriptionSchema, SubscriptionType, SubscriptionStateSchema, SubscriptionUpdateSchema } from './subscription'; 21 | export { 22 | AggregateOfferSchema, 23 | AggregateRatingSchema, 24 | BrandSchema, 25 | DemandSchema, 26 | DeviceControlledProperty, 27 | DeviceDirection, 28 | DeviceProtocol, 29 | DistanceSchema, 30 | ItemAvailability, 31 | OfferItemConidition, 32 | OfferSchema, 33 | PostalAddressSchema, 34 | ProductEnum, 35 | QuantitativeValueSchema, 36 | ReviewRatingSchema, 37 | ReviewSchema, 38 | ServiceChannelSchema, 39 | StructuredValueSchema, 40 | ThingObject, 41 | ThingSchema, 42 | schemaDescriptionCreator 43 | } from './user-types-helper'; 44 | export { DeviceSchema, OrganizationSchema, PersonSchema, ProductSchema, ServiceSchema } from './user-types'; 45 | export { IdentitySchema, IdentityWithoutIdAndCredentialFields, IdentityWithoutIdFields } from './user'; 46 | -------------------------------------------------------------------------------- /api/shared-modules/src/web/models/schemas/request-response-body/authentication-bodies.ts: -------------------------------------------------------------------------------- 1 | import { Type } from '@sinclair/typebox'; 2 | 3 | export const ProveOwnershipPostBodySchema = Type.Object({ 4 | signedNonce: Type.String({ minLength: 128, maxLength: 128 }) 5 | }); 6 | 7 | export const VerifyJwtBodySchema = Type.Object({ 8 | jwt: Type.String({ minLength: 1 }) 9 | }); 10 | 11 | export const NonceSchema = Type.Object({ 12 | nonce: Type.String({ minLength: 40, maxLength: 40 }) 13 | }); 14 | -------------------------------------------------------------------------------- /api/shared-modules/src/web/models/schemas/request-response-body/identity-bodies.ts: -------------------------------------------------------------------------------- 1 | import { Type } from '@sinclair/typebox'; 2 | import { IdentityDocumentSchema } from '../identity'; 3 | import { IdentityWithoutIdAndCredentialFields, IdentityWithoutIdFields } from '../user'; 4 | 5 | export const CreateIdentityBodySchema = Type.Object({ 6 | storeIdentity: Type.Optional(Type.Boolean()), 7 | ...IdentityWithoutIdFields 8 | }); 9 | 10 | export const UpdateIdentityBodySchema = Type.Object({ 11 | id: Type.String({ minLength: 50, maxLength: 53 }) // did 12 | }); 13 | 14 | export const IdentitySearchBodySchema = Type.Object({ 15 | id: Type.String({ minLength: 50, maxLength: 53 }), // did 16 | numberOfCredentials: Type.Integer({ description: 'Number of credentials connected to this identity' }), 17 | ...IdentityWithoutIdAndCredentialFields 18 | }); 19 | 20 | export const LatestIdentityDocSchema = Type.Object({ 21 | document: IdentityDocumentSchema, 22 | messageId: Type.String() 23 | }); 24 | -------------------------------------------------------------------------------- /api/shared-modules/src/web/models/schemas/request-response-body/index.ts: -------------------------------------------------------------------------------- 1 | export { NonceSchema, ProveOwnershipPostBodySchema, VerifyJwtBodySchema } from './authentication-bodies'; 2 | export { 3 | AddChannelLogBodySchema, 4 | ChannelDataSchema, 5 | ChannelLogSchema, 6 | CreateChannelBodySchema, 7 | CreateChannelResponseSchema, 8 | ValidateBodySchema, 9 | ValidateResponseSchema 10 | } from './channel-bodies'; 11 | export { CreateIdentityBodySchema, IdentitySearchBodySchema, LatestIdentityDocSchema, UpdateIdentityBodySchema } from './identity-bodies'; 12 | export { ErrorResponseSchema, IdentityIdSchema } from './misc-bodies'; 13 | export { 14 | AuthorizeSubscriptionBodySchema, 15 | AuthorizeSubscriptionResponseSchema, 16 | RequestSubscriptionBodySchema, 17 | RequestSubscriptionResponseSchema, 18 | RevokeSubscriptionBodySchema 19 | } from './subscription-bodies'; 20 | export { 21 | ClaimSchema, 22 | CreateCredentialBodySchema, 23 | RevokeVerificationBodySchema, 24 | SubjectBodySchema, 25 | TrustedRootBodySchema 26 | } from './verification-bodies'; 27 | -------------------------------------------------------------------------------- /api/shared-modules/src/web/models/schemas/request-response-body/misc-bodies.ts: -------------------------------------------------------------------------------- 1 | import { Type } from "@sinclair/typebox"; 2 | 3 | export const ErrorResponseSchema = Type.Object({ 4 | error: Type.String() 5 | }); 6 | 7 | export const IdentityIdSchema = Type.String({ minLength: 50, maxLength: 53 }); 8 | -------------------------------------------------------------------------------- /api/shared-modules/src/web/models/schemas/request-response-body/subscription-bodies.ts: -------------------------------------------------------------------------------- 1 | import { Type } from '@sinclair/typebox'; 2 | import { AccessRights } from '../subscription'; 3 | 4 | export const AuthorizeSubscriptionBodySchema = Type.Object({ 5 | subscriptionLink: Type.Optional(Type.String({ minLength: 1 })), 6 | id: Type.Optional(Type.String({ minLength: 1 })) 7 | }); 8 | 9 | export const RevokeSubscriptionBodySchema = Type.Object({ 10 | subscriptionLink: Type.Optional(Type.String({ minLength: 1 })), 11 | id: Type.Optional(Type.String({ minLength: 1 })) 12 | }); 13 | 14 | export const RequestSubscriptionBodySchema = Type.Object({ 15 | seed: Type.Optional( 16 | Type.String({ 17 | minLength: 32, 18 | description: 19 | 'If left empty the api will generate a seed. Make sure you store the seed since the API will not store it. You can reuse your seed for different channels.' 20 | }) 21 | ), 22 | accessRights: Type.Optional(Type.Enum(AccessRights)), 23 | presharedKey: Type.Optional(Type.String({ maxLength: 32, minLength: 32 })) 24 | }); 25 | 26 | export const RequestSubscriptionResponseSchema = Type.Object({ 27 | seed: Type.String({ 28 | minLength: 32, 29 | maxLength: 72, 30 | description: 31 | 'Auto generated seed. Make sure you store the seed since the API will not store it. You can reuse your seed for different channels.' 32 | }), 33 | subscriptionLink: Type.String() 34 | }); 35 | 36 | export const AuthorizeSubscriptionResponseSchema = Type.Object({ 37 | keyloadLink: Type.String() 38 | }); 39 | -------------------------------------------------------------------------------- /api/shared-modules/src/web/models/schemas/request-response-body/verification-bodies.ts: -------------------------------------------------------------------------------- 1 | import { Type } from '@sinclair/typebox'; 2 | import { UserType } from '../../types/user'; 3 | import { VerifiableCredentialSchema } from '../identity'; 4 | 5 | export const ClaimSchema = Type.Object( 6 | { 7 | type: Type.Union([Type.Enum(UserType), Type.String({ minLength: 3 })]) 8 | }, 9 | { additionalProperties: true } 10 | ); 11 | 12 | export const SubjectBodySchema = Type.Object({ 13 | id: Type.String({ minLength: 50, maxLength: 53 }), // did 14 | credentialType: Type.String(), 15 | claim: ClaimSchema 16 | }); 17 | 18 | export const CreateCredentialBodySchema = Type.Object({ 19 | subject: SubjectBodySchema, 20 | initiatorVC: Type.Optional(VerifiableCredentialSchema) 21 | }); 22 | 23 | export const RevokeVerificationBodySchema = Type.Object({ 24 | signatureValue: Type.String() 25 | }); 26 | 27 | export const TrustedRootBodySchema = Type.Object({ 28 | trustedRootId: Type.String({ minLength: 50, maxLength: 53 }) // did 29 | }); 30 | -------------------------------------------------------------------------------- /api/shared-modules/src/web/models/schemas/subscription.ts: -------------------------------------------------------------------------------- 1 | import { Type } from '@sinclair/typebox'; 2 | 3 | export enum SubscriptionType { 4 | Author = 'Author', 5 | Subscriber = 'Subscriber' 6 | } 7 | 8 | export enum AccessRights { 9 | Audit = 'Audit', 10 | Read = 'Read', 11 | Write = 'Write', 12 | ReadAndWrite = 'ReadAndWrite' 13 | } 14 | 15 | export const SubscriptionSchema = Type.Object({ 16 | type: Type.Enum(SubscriptionType), 17 | channelAddress: Type.String({ minLength: 105, maxLength: 105 }), 18 | id: Type.String({ minLength: 50, maxLength: 53 }), 19 | subscriptionLink: Type.Optional(Type.String()), 20 | isAuthorized: Type.Boolean(), 21 | accessRights: Type.Enum(AccessRights), 22 | publicKey: Type.Optional(Type.String()), 23 | keyloadLink: Type.Optional(Type.String()), 24 | sequenceLink: Type.Optional(Type.String()), 25 | pskId: Type.Optional(Type.String()) 26 | }); 27 | 28 | export const SubscriptionStateSchema = Type.Object({ 29 | state: Type.String() 30 | }); 31 | 32 | export const SubscriptionUpdateSchema = Type.Object({ 33 | type: Type.Optional(Type.Enum(SubscriptionType)), 34 | channelAddress: Type.Optional(Type.String({ minLength: 105, maxLength: 105 })), 35 | id: Type.Optional(Type.String({ minLength: 50, maxLength: 53 })), 36 | subscriptionLink: Type.Optional(Type.String()), 37 | isAuthorized: Type.Optional(Type.Boolean()), 38 | accessRights: Type.Optional(Type.Enum(AccessRights)), 39 | publicKey: Type.Optional(Type.String()), 40 | keyloadLink: Type.Optional(Type.String()), 41 | sequenceLink: Type.Optional(Type.String()), 42 | pskId: Type.Optional(Type.String()) 43 | }); 44 | -------------------------------------------------------------------------------- /api/shared-modules/src/web/models/schemas/user.ts: -------------------------------------------------------------------------------- 1 | import { Type } from '@sinclair/typebox'; 2 | import { VerifiableCredentialSchema } from './identity'; 3 | import { ClaimSchema } from './request-response-body/verification-bodies'; 4 | 5 | export const IdentityWithoutIdAndCredentialFields = { 6 | username: Type.String({ minLength: 3 }), 7 | registrationDate: Type.Optional(Type.String({ format: 'date-time' })), 8 | creator: Type.Optional(Type.String({ minLength: 50, maxLength: 53 })), 9 | role: Type.Optional(Type.String()), 10 | claim: Type.Optional(ClaimSchema), 11 | hidden: Type.Optional(Type.Boolean({ default: false })), 12 | isServerIdentity: Type.Optional(Type.Boolean()) 13 | }; 14 | 15 | export const IdentityWithoutIdFields = { 16 | ...IdentityWithoutIdAndCredentialFields, 17 | verifiableCredentials: Type.Optional(Type.Array(VerifiableCredentialSchema)) 18 | }; 19 | 20 | export const IdentitySchema = Type.Object({ 21 | id: Type.String({ minLength: 50, maxLength: 53 }), // did 22 | ...IdentityWithoutIdFields 23 | }); 24 | -------------------------------------------------------------------------------- /api/shared-modules/src/web/models/types/channel-data.ts: -------------------------------------------------------------------------------- 1 | import { Static } from '@sinclair/typebox'; 2 | import { ChannelDataSchema, ChannelLogSchema } from '../schemas/request-response-body/channel-bodies'; 3 | 4 | export type ChannelLog = Static; 5 | export type ChannelData = Static; 6 | -------------------------------------------------------------------------------- /api/shared-modules/src/web/models/types/channel-info.ts: -------------------------------------------------------------------------------- 1 | import { Static } from '@sinclair/typebox'; 2 | import { TopicSchema, ChannelInfoSchema, ChannelInfoSearchSchema, ChannelLogRequestOptionsSchema } from '../schemas/channel-info'; 3 | 4 | export type Topic = Static; 5 | 6 | export type ChannelInfo = Static; 7 | 8 | type OmitedChannelInfo = Omit; 9 | 10 | export interface ChannelInfoPersistence extends OmitedChannelInfo { 11 | created: Date | null; 12 | latestMessage?: Date; 13 | } 14 | 15 | type ChannelInfoSearchDate = Static; 16 | export type ChannelInfoSearch = Omit & { created?: Date, latestMessage?: Date } 17 | 18 | export type ChannelLogRequestOptions = Static; -------------------------------------------------------------------------------- /api/shared-modules/src/web/models/types/concurrency.ts: -------------------------------------------------------------------------------- 1 | export enum ConcurrencyLocks { 2 | ChannelLock = 'channel-lock', 3 | CredentialLock = 'credential-lock' 4 | } 5 | 6 | export interface ConcurrencyLock { 7 | id: string; 8 | lock: string; 9 | created: Date; 10 | } 11 | -------------------------------------------------------------------------------- /api/shared-modules/src/web/models/types/identity.ts: -------------------------------------------------------------------------------- 1 | import { Static } from '@sinclair/typebox'; 2 | import { 3 | VerifiableCredentialSchema, 4 | VerifiablePresentationSchema, 5 | VerifiableCredentialSubjectSchema, 6 | IdentityDocumentSchema, 7 | IdentityKeyPairSchema 8 | } from '../schemas/identity'; 9 | import { 10 | CreateIdentityBodySchema, 11 | LatestIdentityDocSchema, 12 | IdentitySearchBodySchema 13 | } from '../schemas/request-response-body/identity-bodies'; 14 | import { IdentitySchema } from '../schemas/user'; 15 | import { IdentityKeysSchema, KeysSchema } from '../schemas/identity'; 16 | 17 | export interface Bitmap { 18 | id: string; 19 | index: number; 20 | serviceEndpoint: string | string[] | Map | Record; 21 | } 22 | 23 | export interface VerifiableCredentialPersistence { 24 | index: number; 25 | initiator: string; 26 | isRevoked: boolean; 27 | vc: VerifiableCredential; 28 | } 29 | 30 | export interface Credential { 31 | id: string; 32 | type: string; 33 | subject: T; 34 | } 35 | 36 | export type IdentitySearchBody = Static; 37 | 38 | export type IdentityInternal = Static; 39 | export type CreateIdentityBody = Static; 40 | 41 | export type VerifiableCredential = Static; 42 | export type VerifiablePresentation = Static; 43 | export type CredentialSubject = Static; 44 | 45 | export type IdentityDocument = Static; 46 | export type IdentityKeyPair = Static; 47 | export type LatestIdentity = Static; 48 | 49 | export type IdentityKeys = Static; 50 | export type Keys = Static; 51 | -------------------------------------------------------------------------------- /api/shared-modules/src/web/models/types/index.ts: -------------------------------------------------------------------------------- 1 | export { ChannelData, ChannelLog } from './channel-data'; 2 | export { ChannelInfo, ChannelInfoPersistence, ChannelInfoSearch, ChannelLogRequestOptions, Topic } from './channel-info'; 3 | export { ConcurrencyLock, ConcurrencyLocks } from './concurrency'; 4 | export { 5 | CreateIdentityBody, 6 | Credential, 7 | CredentialSubject, 8 | IdentityDocument, 9 | IdentityInternal, 10 | IdentityKeyPair, 11 | IdentitySearchBody, 12 | LatestIdentity, 13 | VerifiableCredential, 14 | VerifiablePresentation, 15 | VerifiableCredentialPersistence, 16 | Bitmap, 17 | IdentityKeys, 18 | Keys 19 | } from './identity'; 20 | export { 21 | AddChannelLogBody, 22 | AuthorizeSubscriptionBody, 23 | AuthorizeSubscriptionResponse, 24 | ChannelInfoBody, 25 | CreateChannelBody, 26 | CreateChannelResponse, 27 | CreateCredentialBody, 28 | IdentitySchemaBody, 29 | Nonce, 30 | ProveOwnershipPostBody, 31 | RequestSubscriptionBody, 32 | RequestSubscriptionResponse, 33 | RevokeSubscriptionBody, 34 | RevokeVerificationBody, 35 | TrustedRootBody, 36 | ValidateBody, 37 | ValidateResponse, 38 | VerifyJwtBody 39 | } from './request-response-bodies'; 40 | export { Subscription, SubscriptionState, SubscriptionUpdate } from './subscription'; 41 | export { 42 | Device, 43 | IdentityClaim, 44 | Organization, 45 | Person, 46 | Product, 47 | Service, 48 | User, 49 | UserPersistence, 50 | UserRoles, 51 | UserSearch, 52 | UserSearchResponse, 53 | UserType 54 | } from './user'; 55 | export { AuthenticatedRequest, AuthorizationCheck, CredentialTypes, Subject } from './verification'; 56 | -------------------------------------------------------------------------------- /api/shared-modules/src/web/models/types/subscription.ts: -------------------------------------------------------------------------------- 1 | import { Static } from '@sinclair/typebox'; 2 | import { SubscriptionSchema, SubscriptionUpdateSchema, SubscriptionStateSchema } from '../schemas/subscription'; 3 | 4 | export type Subscription = Static; 5 | export type SubscriptionState = Static; 6 | export type SubscriptionUpdate = Static; 7 | -------------------------------------------------------------------------------- /api/shared-modules/src/web/models/types/user.ts: -------------------------------------------------------------------------------- 1 | import { Static } from '@sinclair/typebox'; 2 | import { IdentitySearchBodySchema } from '../schemas/request-response-body/identity-bodies'; 3 | import { ClaimSchema } from '../schemas/request-response-body/verification-bodies'; 4 | import { IdentitySchema } from '../schemas/user'; 5 | import { OrganizationSchema, ServiceSchema, PersonSchema, ProductSchema, DeviceSchema } from '../schemas/user-types'; 6 | 7 | export type IdentityClaim = Static; 8 | export type User = Static; 9 | export type UserSearchResponse = Static; 10 | export type Organization = Static; 11 | export type Service = Static; 12 | export type Person = Static; 13 | export type Product = Static; 14 | export type Device = Static; 15 | 16 | export enum UserType { 17 | Organization = 'Organization', 18 | Service = 'Service', 19 | Person = 'Person', 20 | Device = 'Device', 21 | Product = 'Product', 22 | Unknown = 'Unknown' 23 | } 24 | 25 | export enum UserRoles { 26 | Admin = 'Admin', 27 | Manager = 'Manager', 28 | User = 'User' 29 | } 30 | 31 | export interface UserSearch { 32 | username?: string; 33 | creator?: string; 34 | registrationDate?: Date; 35 | type?: UserType | string; 36 | limit?: number; 37 | index?: number; 38 | ascending?: boolean; 39 | } 40 | 41 | type OmittedUser = Omit; 42 | 43 | export interface UserPersistence extends OmittedUser { 44 | role?: UserRoles; 45 | registrationDate?: Date; 46 | } 47 | -------------------------------------------------------------------------------- /api/shared-modules/src/web/models/types/verification.ts: -------------------------------------------------------------------------------- 1 | import { Static } from '@sinclair/typebox'; 2 | import Express from 'express'; 3 | import { SubjectBodySchema } from '../schemas/request-response-body/verification-bodies'; 4 | import { User } from './user'; 5 | 6 | export enum CredentialTypes { 7 | 'VerifiedIdentityCredential' = 'VerifiedIdentityCredential', 8 | 'BasicIdentityCredential' = 'BasicIdentityCredential' 9 | } 10 | 11 | export type Subject = Static; 12 | 13 | export interface AuthenticatedRequest extends Express.Request { 14 | user: User; 15 | } 16 | 17 | export interface AuthorizationCheck { 18 | isAuthorized: boolean; 19 | error?: Error | null; 20 | } 21 | -------------------------------------------------------------------------------- /api/shared-modules/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src/**/*"], 3 | "exclude": ["node_modules"], 4 | "compilerOptions": { 5 | "target": "es6", 6 | "module": "commonjs", 7 | "allowJs": false, 8 | "moduleResolution": "node", 9 | "noImplicitAny": true, 10 | "declaration": true, 11 | "declarationMap": true, 12 | "sourceMap": true, 13 | "outDir": "lib", 14 | "esModuleInterop": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "strict": true, 17 | "skipLibCheck": true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /api/ssi-bridge/.dockerignore: -------------------------------------------------------------------------------- 1 | .env -------------------------------------------------------------------------------- /api/ssi-bridge/.env.example: -------------------------------------------------------------------------------- 1 | PORT=3001 2 | IOTA_PERMA_NODE=https://chrysalis-chronicle.iota.org/api/mainnet/ 3 | IOTA_HORNET_NODE=https://chrysalis-nodes.iota.org:443 4 | 5 | DATABASE_NAME= 6 | MONGO_INITDB_ROOT_USERNAME= 7 | MONGO_INITDB_ROOT_PASSWORD= 8 | DATABASE_URL=mongodb://:@mongo:27017 9 | 10 | SERVER_SECRET= 11 | JWT_SECRET= 12 | 13 | API_KEY= 14 | JWT_EXPIRATION= 15 | COMMIT_HASH= 16 | 17 | -------------------------------------------------------------------------------- /api/ssi-bridge/.eslintignore: -------------------------------------------------------------------------------- 1 | .eslintrc.js 2 | *.d.ts 3 | # don't ever lint node_modules 4 | node_modules 5 | # don't lint build output (make sure it's set to your correct build folder name) 6 | dist 7 | # don't lint nyc coverage output 8 | coverage 9 | # streams wasm files 10 | wasm-node 11 | **/mocks -------------------------------------------------------------------------------- /api/ssi-bridge/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | plugins: ['@typescript-eslint', 'prettier'], 5 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'], 6 | rules: { 7 | '@typescript-eslint/no-explicit-any': 'off', 8 | '@typescript-eslint/explicit-module-boundary-types': 'off', 9 | '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }] 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /api/ssi-bridge/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "none", 4 | "singleQuote": true, 5 | "printWidth": 140, 6 | "tabWidth": 2, 7 | "useTabs": true 8 | } 9 | -------------------------------------------------------------------------------- /api/ssi-bridge/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ### 0.2.0 (2022-09-20) 4 | 5 | - Add `/identities/keys` endpoint 6 | 7 | ### 0.2.0 (2022-08-12) BREAKING CHANGES 8 | 9 | - Updated shared modules to version 0.2.1 and identity-wasm to 0.6.0 10 | 11 | ### 0.1.8 (2022-07-25) 12 | 13 | - Updated to is-shared-modules version 0.1.20 -> Adjusted imports for new version 14 | 15 | ### 0.1.6 (2022-07-05) 16 | 17 | - Updated dependencies 18 | - Reduce jwt overhead 19 | - Add Manager role 20 | - Implement hidden identities 21 | -------------------------------------------------------------------------------- /api/ssi-bridge/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16-alpine 2 | WORKDIR /usr/src/app 3 | 4 | COPY . . 5 | 6 | RUN npm install 7 | RUN npm run build 8 | 9 | EXPOSE 3000 10 | CMD [ "node", "dist/index.js", "server" ] -------------------------------------------------------------------------------- /api/ssi-bridge/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: ['/src'], 3 | transform: { 4 | '^.+\\.tsx?$': 'ts-jest' 5 | }, 6 | testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$', 7 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'] 8 | }; 9 | -------------------------------------------------------------------------------- /api/ssi-bridge/src/config/server-identity.json: -------------------------------------------------------------------------------- 1 | { 2 | "storeIdentity": true, 3 | "username": "root-identity", 4 | "isServerIdentity": true, 5 | "claim": { 6 | "type": "Service", 7 | "name": "Integration Service" 8 | } 9 | } -------------------------------------------------------------------------------- /api/ssi-bridge/src/database/auth.ts: -------------------------------------------------------------------------------- 1 | import { CollectionNames } from './constants'; 2 | import { MongoDbService } from '@iota/is-shared-modules/node'; 3 | 4 | const collectionName = CollectionNames.auth; 5 | 6 | export const getNonce = async (id: string): Promise<{ id: string; nonce: string }> => { 7 | const query = { _id: id }; 8 | return await MongoDbService.getDocument<{ id: string; nonce: string }>(collectionName, query); 9 | }; 10 | 11 | export const upsertNonce = async (id: string, nonce: string) => { 12 | const query = { _id: id }; 13 | const update = { 14 | $set: { _id: id, id, nonce } 15 | }; 16 | 17 | const res = await MongoDbService.updateDocument(collectionName, query, update, { upsert: true }); 18 | if (!res?.result?.n) { 19 | throw new Error('could not add or update the message to sign!'); 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /api/ssi-bridge/src/database/concurrency-lock.ts: -------------------------------------------------------------------------------- 1 | import { CollectionNames } from './constants'; 2 | import { ConcurrencyLock } from '@iota/is-shared-modules'; 3 | import { MongoDbService } from '@iota/is-shared-modules/node'; 4 | 5 | const collectionName = CollectionNames.concurrencyLocks; 6 | 7 | export const getLock = async (lock: string): Promise => { 8 | const query = { _id: lock }; 9 | return MongoDbService.getDocument(collectionName, query); 10 | }; 11 | 12 | export const insertLock = async (lock: string) => { 13 | const document = { 14 | _id: lock, 15 | lock, 16 | created: new Date() 17 | }; 18 | 19 | const res = await MongoDbService.insertDocument(collectionName, document); 20 | if (!res.result.n) { 21 | throw new Error('could not add the lock!'); 22 | } 23 | return res; 24 | }; 25 | 26 | export const removeLock = async (lock: string) => { 27 | const query = { _id: lock }; 28 | return MongoDbService.removeDocument(collectionName, query); 29 | }; 30 | -------------------------------------------------------------------------------- /api/ssi-bridge/src/database/constants.ts: -------------------------------------------------------------------------------- 1 | export const enum CollectionNames { 2 | users = 'users', 3 | revocationBitmap = 'revocation-bitmap', 4 | identityKeysCollection = 'identity-keys', 5 | verifiableCredentials = 'verifiable-credentials', 6 | auth = 'auth', 7 | concurrencyLocks = 'concurrency-locks', 8 | trustedRoots = 'trusted-roots' 9 | } 10 | -------------------------------------------------------------------------------- /api/ssi-bridge/src/database/revocation-bitmap.ts: -------------------------------------------------------------------------------- 1 | import { CollectionNames } from './constants'; 2 | import { MongoDbService } from '@iota/is-shared-modules/node'; 3 | import { Bitmap } from '@iota/is-shared-modules'; 4 | 5 | const collectionName = CollectionNames.revocationBitmap; 6 | const getIndex = (index: number, id: string) => `${id}-${index}`; 7 | 8 | export const getBitmap = async (index: number, serverId: string): Promise => { 9 | const query = { _id: getIndex(index, serverId) }; 10 | const bitmap = await MongoDbService.getDocument(collectionName, query); 11 | return bitmap; 12 | }; 13 | 14 | export const saveBitmap = async (bitmap: Bitmap, serverId: string) => { 15 | const document = { 16 | _id: getIndex(bitmap.index, serverId), 17 | ...bitmap, 18 | created: new Date() 19 | }; 20 | 21 | return MongoDbService.insertDocument(collectionName, document); 22 | }; 23 | -------------------------------------------------------------------------------- /api/ssi-bridge/src/database/trusted-roots.ts: -------------------------------------------------------------------------------- 1 | import { CollectionNames } from './constants'; 2 | import { MongoDbService } from '@iota/is-shared-modules/node'; 3 | 4 | const collectionName = CollectionNames.trustedRoots; 5 | 6 | export const getTrustedRootIds = async (): Promise<{ id: string }[]> => { 7 | const query = {}; 8 | return await MongoDbService.getDocuments<{ id: string }>(collectionName, query); 9 | }; 10 | 11 | export const addTrustedRootId = async (id: string) => { 12 | const document = { 13 | _id: id, 14 | id, 15 | created: new Date() 16 | }; 17 | 18 | const res = await MongoDbService.insertDocument(collectionName, document); 19 | if (!res?.result.n) { 20 | throw new Error('could not add the trusted root from the db'); 21 | } 22 | }; 23 | 24 | export const removeTrustedRootId = async (id: string) => { 25 | const query = { _id: id }; 26 | const res = await MongoDbService.removeDocument(collectionName, query); 27 | if (!res?.result.n) { 28 | throw new Error('could not remove the trusted root from the db'); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /api/ssi-bridge/src/middlewares/api-key.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from 'express'; 2 | import { StatusCodes } from 'http-status-codes'; 3 | 4 | export const hasValidApiKey = (serverApiKey: string) => (req: Request, res: Response, next: NextFunction) => { 5 | const decodeParam = (param: string): string | undefined => (param ? decodeURI(param) : undefined); 6 | const apiKey = req.query && decodeParam(req.query['api-key']); 7 | 8 | if (serverApiKey && apiKey !== serverApiKey) { 9 | return res.status(StatusCodes.UNAUTHORIZED).send({ error: 'no valid api key provided!' }); 10 | } 11 | 12 | next(); 13 | }; 14 | -------------------------------------------------------------------------------- /api/ssi-bridge/src/middlewares/authentication.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from 'express'; 2 | import { StatusCodes } from 'http-status-codes'; 3 | import jwt from 'jsonwebtoken'; 4 | 5 | export const isAuth = (secret: string) => (req: Request, res: Response, next: NextFunction) => { 6 | const { authorization } = req.headers; 7 | 8 | if (!authorization || !authorization.startsWith('Bearer')) { 9 | return res.status(StatusCodes.UNAUTHORIZED).send({ error: 'not authenticated!' }); 10 | } 11 | 12 | const split = authorization.split('Bearer '); 13 | if (split.length !== 2) { 14 | return res.status(StatusCodes.UNAUTHORIZED).send({ error: 'not authenticated!' }); 15 | } 16 | 17 | const token = split[1]; 18 | const decodedToken: any = jwt.verify(token, secret); 19 | 20 | if (typeof decodedToken === 'string' || !decodedToken?.user) { 21 | return res.status(StatusCodes.UNAUTHORIZED).send({ error: 'not authenticated!' }); 22 | } 23 | 24 | (req as any).user = decodedToken.user; 25 | next(); 26 | }; 27 | -------------------------------------------------------------------------------- /api/ssi-bridge/src/middlewares/error.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response, NextFunction } from 'express'; 2 | import { StatusCodes } from 'http-status-codes'; 3 | import { ValidationError } from 'express-json-validator-middleware'; 4 | import { Logger } from '../utils/logger'; 5 | 6 | /** 7 | * Error middleware to log and return the error to the client. 8 | * 9 | * @param {Error} err Received error from endpoint 10 | * @param {Request} _req Client request 11 | * @param {Response} res Client response 12 | * @param {NextFunction} _next Next function 13 | */ 14 | export const errorMiddleware = (err: Error, _req: Request, res: Response, _next: NextFunction): void => { 15 | if (err instanceof ValidationError) { 16 | console.error(JSON.stringify(err)); 17 | res.status(StatusCodes.BAD_REQUEST); 18 | res.send({ error: 'No valid body provided!' }); 19 | return; 20 | } 21 | Logger.getInstance().error(err.message); 22 | res.status(StatusCodes.INTERNAL_SERVER_ERROR); 23 | res.send({ error: err.message }); 24 | }; 25 | -------------------------------------------------------------------------------- /api/ssi-bridge/src/middlewares/mongodb-sanitizer.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from 'express'; 2 | import * as _ from 'lodash'; 3 | import { StatusCodes } from 'http-status-codes'; 4 | 5 | const BAD_CHAR = '$'; 6 | 7 | export const mongodbSanitizer = (req: Request, res: Response, next: NextFunction) => { 8 | const body = req.body; 9 | 10 | const val = hasBadCharacter(body, BAD_CHAR); 11 | 12 | if (val) { 13 | return res.status(StatusCodes.BAD_REQUEST).send({ error: `${BAD_CHAR} is not allowed as key prefix.` }); 14 | } 15 | 16 | next(); 17 | }; 18 | 19 | const hasBadCharacter = (data: any, character: string): boolean => { 20 | if (data == null) { 21 | return false; 22 | } 23 | 24 | if (_.isObject(data)) { 25 | const keys = Object.keys(data); 26 | const numberOfBadChars = keys?.filter((k) => k.startsWith(character))?.length; 27 | 28 | if (numberOfBadChars != 0) { 29 | return true; 30 | } 31 | 32 | const values = Object.values(data); 33 | return recursiveCall(values, character); 34 | } else if (_.isArray(data)) { 35 | return recursiveCall(data, character); 36 | } 37 | 38 | return false; 39 | }; 40 | 41 | const recursiveCall = (values: any, char: string): boolean => 42 | values ? values.map((val: any) => hasBadCharacter(val, char)).some((v: boolean) => v) : false; 43 | -------------------------------------------------------------------------------- /api/ssi-bridge/src/models/config/index.ts: -------------------------------------------------------------------------------- 1 | export interface Config { 2 | port: number; 3 | apiVersion: string; 4 | databaseUrl: string; 5 | databaseName: string; 6 | identityConfig: IdentityConfig; 7 | serverSecret: string; 8 | jwtSecret: string; 9 | apiKey: string | undefined; 10 | hornetNode: string; 11 | permaNode: string; 12 | jwtExpiration: string; 13 | commitHash: string; 14 | } 15 | 16 | export interface IdentityConfig { 17 | node: string; 18 | permaNode: string; 19 | bitmapTag: string; 20 | } 21 | -------------------------------------------------------------------------------- /api/ssi-bridge/src/models/config/services.ts: -------------------------------------------------------------------------------- 1 | export interface AuthenticationServiceConfig { 2 | jwtSecret: string; 3 | jwtExpiration: string; 4 | } 5 | -------------------------------------------------------------------------------- /api/ssi-bridge/src/routers/index.ts: -------------------------------------------------------------------------------- 1 | export { authenticationRouter } from './authentication'; 2 | export { identityRouter } from './identity'; 3 | export { verificationRouter } from './verification'; 4 | -------------------------------------------------------------------------------- /api/ssi-bridge/src/routers/middlewares.ts: -------------------------------------------------------------------------------- 1 | import { Validator } from 'express-json-validator-middleware'; 2 | import { hasValidApiKey } from '../middlewares/api-key'; 3 | import { isAuth } from '../middlewares/authentication'; 4 | import { ConfigurationService } from '../services/configuration-service'; 5 | import { Logger } from '../utils/logger'; 6 | 7 | const { jwtSecret, apiKey } = ConfigurationService.getInstance(Logger.getInstance()).config; 8 | 9 | export const validator = new Validator({ allErrors: true }); 10 | export const validate = validator.validate; 11 | 12 | export const authMiddleWare = isAuth(jwtSecret); 13 | export const apiKeyMiddleware = hasValidApiKey(apiKey); 14 | -------------------------------------------------------------------------------- /api/ssi-bridge/src/routers/server-info/index.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import { ServerInfoRoutes } from '../../routes/server-info'; 3 | import { Logger } from '../../utils/logger'; 4 | import { ConfigurationService } from '../../services/configuration-service'; 5 | 6 | const serverInfoRoutes = new ServerInfoRoutes(Logger.getInstance(), ConfigurationService.getInstance(Logger.getInstance())); 7 | const { getServerInfo } = serverInfoRoutes; 8 | export const serverInfoRouter = Router(); 9 | 10 | /** 11 | * @openapi 12 | * /info: 13 | * get: 14 | * summary: Get information about the server 15 | * description: Get information about the server like commitHash, server identity id and api version 16 | * tags: 17 | * - server-info 18 | * servers: 19 | * - url: / 20 | * responses: 21 | * 200: 22 | * description: Returns information about the server 23 | * content: 24 | * application/json: 25 | * schema: 26 | * type: object 27 | * properties: 28 | * commitHash: 29 | * type: string 30 | * id: 31 | * type: string 32 | * version: 33 | * type: string 34 | * required: 35 | * - commitHash 36 | * - id 37 | * - version 38 | * 5XX: 39 | * description: Unexpected error 40 | * content: 41 | * application/json: 42 | * schema: 43 | * type: object 44 | * properties: 45 | * error: 46 | * type: string 47 | */ 48 | serverInfoRouter.get('/info', getServerInfo); 49 | -------------------------------------------------------------------------------- /api/ssi-bridge/src/routers/services.ts: -------------------------------------------------------------------------------- 1 | import { AuthenticationService } from '../services/authentication-service'; 2 | import { AuthorizationService } from '../services/authorization-service'; 3 | import { SsiService } from '../services/ssi-service'; 4 | import { UserService } from '../services/user-service'; 5 | import { VerificationService } from '../services/verification-service'; 6 | import { Logger } from '../utils/logger'; 7 | import { ConfigurationService } from '../services/configuration-service'; 8 | 9 | const logger = Logger.getInstance(); 10 | const configService = ConfigurationService.getInstance(logger); 11 | const { serverSecret, identityConfig, jwtExpiration, jwtSecret } = configService.config; 12 | 13 | export const ssiService = SsiService.getInstance(identityConfig, logger); 14 | export const authorizationService = new AuthorizationService(); 15 | export const userService = new UserService(ssiService, serverSecret, logger); 16 | export const authenticationService = new AuthenticationService(userService, ssiService, { jwtExpiration, jwtSecret }, logger); 17 | 18 | export const verificationService = new VerificationService(ssiService, userService, logger, configService); 19 | -------------------------------------------------------------------------------- /api/ssi-bridge/src/routes/server-info/index.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from 'express'; 2 | import { StatusCodes } from 'http-status-codes'; 3 | import { ILogger } from '../../utils/logger'; 4 | import * as os from 'os'; 5 | import { ConfigurationService } from '../../services/configuration-service'; 6 | 7 | export class ServerInfoRoutes { 8 | constructor(private readonly logger: ILogger, private readonly configService: ConfigurationService) {} 9 | 10 | getServerInfo = async (req: Request, res: Response, next: NextFunction): Promise => { 11 | try { 12 | const config = this.configService.config; 13 | const hostname = os.hostname(); 14 | const commitHash = config.commitHash || 'not defined'; 15 | const id = this.configService.serverIdentityId || 'not defined'; 16 | const version = config.apiVersion || 'not defined'; 17 | 18 | res.status(StatusCodes.OK).send({ 19 | commitHash, 20 | hostname, 21 | id, 22 | version 23 | }); 24 | } catch (error) { 25 | this.logger.error(error); 26 | next(new Error('could not get server info')); 27 | } 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /api/ssi-bridge/src/services/authorization-service.ts: -------------------------------------------------------------------------------- 1 | import { AuthorizationCheck, CredentialTypes, User, UserType, UserRoles } from '@iota/is-shared-modules'; 2 | export class AuthorizationService { 3 | isAuthorized(requestUser: User, id: string): AuthorizationCheck { 4 | const isAuthorizedUser = this.isAuthorizedUser(requestUser.id, id); 5 | if (!isAuthorizedUser) { 6 | const isAuthorizedAdmin = this.isAuthorizedAdmin(requestUser); 7 | if (!isAuthorizedAdmin) { 8 | return { isAuthorized: false, error: new Error('not allowed!') }; 9 | } 10 | } 11 | return { isAuthorized: true, error: null }; 12 | } 13 | 14 | isAuthorizedUser(requestUserId: string, id: string): boolean { 15 | return requestUserId === id; 16 | } 17 | 18 | isAuthorizedAdmin(requestUser: User): boolean { 19 | const role = requestUser?.role; 20 | 21 | if (role === UserRoles.Admin) { 22 | return true; 23 | } 24 | return false; 25 | } 26 | 27 | isAuthorizedManager(requestUser: User): boolean { 28 | const role = requestUser?.role; 29 | 30 | if (role === UserRoles.Manager) { 31 | return true; 32 | } 33 | return false; 34 | } 35 | 36 | hasAuthorizedUserType(type: string): boolean { 37 | return type === UserType.Person || type === UserType.Service || type === UserType.Organization; 38 | } 39 | 40 | hasVerificationCredentialType(type: string[]): boolean { 41 | return type.some((t) => t === CredentialTypes.VerifiedIdentityCredential); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /api/ssi-bridge/src/services/tests/user-service.test.ts: -------------------------------------------------------------------------------- 1 | import { UserService } from '../user-service'; 2 | import { SsiService } from '../ssi-service'; 3 | import { LoggerMock } from '../../test/mocks/logger'; 4 | import { ConfigurationServiceMock } from '../../test/mocks/service-mocks'; 5 | import { KeyTypes } from '@iota/is-shared-modules'; 6 | 7 | describe('test user-service', () => { 8 | let ssiService: SsiService, userService: UserService; 9 | const serverSecret = ConfigurationServiceMock.config.serverSecret; 10 | 11 | beforeEach(() => { 12 | ssiService = SsiService.getInstance({} as any, LoggerMock); 13 | userService = new UserService(ssiService, serverSecret, LoggerMock); 14 | }); 15 | 16 | it('should not generate a x25519 keypair', async () => { 17 | const type = KeyTypes.x25519; 18 | const keyPair = userService.getKeyPair(type); 19 | 20 | expect(keyPair.type).toEqual(KeyTypes.x25519); 21 | expect(keyPair.public.length).toEqual(44); 22 | expect(keyPair.private.length).toEqual(44); 23 | expect(keyPair.encoding).toEqual('base58'); 24 | }); 25 | 26 | it('should not generate a ed25519 keypair', async () => { 27 | const type = KeyTypes.ed25519; 28 | const keyPair = userService.getKeyPair(type); 29 | 30 | expect(keyPair.type).toEqual(KeyTypes.ed25519); 31 | expect(keyPair.public.length).toEqual(44); 32 | expect(keyPair.private.length).toEqual(44); 33 | expect(keyPair.encoding).toEqual('base58'); 34 | }); 35 | 36 | afterEach(() => { 37 | jest.clearAllMocks(); 38 | jest.restoreAllMocks(); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /api/ssi-bridge/src/setup/database-seeder.ts: -------------------------------------------------------------------------------- 1 | import { CollectionNames } from '../database/constants'; 2 | import { ILogger } from '../utils/logger/index'; 3 | import { MongoDbService } from '@iota/is-shared-modules/node'; 4 | 5 | const LOCK_EXPIRATION_TIME_SEC = 55; 6 | 7 | export interface IDatabaseSeeder { 8 | seed(): void; 9 | } 10 | 11 | export class DatabaseSeeder { 12 | constructor(private readonly logger: ILogger) {} 13 | async seed() { 14 | const db = MongoDbService.db; 15 | const concurrencyCollection = db.collection(CollectionNames.concurrencyLocks); 16 | await concurrencyCollection.createIndex({ created: 1 }, { expireAfterSeconds: LOCK_EXPIRATION_TIME_SEC }); 17 | 18 | const usersCollection = db.collection(CollectionNames.users); 19 | await usersCollection.createIndex({ username: 1 }, { unique: true, partialFilterExpression: { username: { $exists: true } } }); 20 | this.logger.log('Database successfully seeded.'); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /api/ssi-bridge/src/setup/setup-manager.ts: -------------------------------------------------------------------------------- 1 | import { DatabaseSeeder } from './database-seeder'; 2 | import { KeyGenerator } from './key-generator'; 3 | import { Logger } from '../utils/logger/index'; 4 | import { MongoDbService } from '@iota/is-shared-modules/node'; 5 | import { ConfigurationService } from '../services/configuration-service'; 6 | 7 | export class SetupManager { 8 | async runSetup() { 9 | const logger = Logger.getInstance(); 10 | const configService = ConfigurationService.getInstance(logger); 11 | const config = configService.config; 12 | 13 | await MongoDbService.connect(config.databaseUrl, config.databaseName); 14 | 15 | // seed the database with indexes 16 | const dbSeeder = new DatabaseSeeder(logger); 17 | await dbSeeder.seed(); 18 | 19 | // create keys for root identity if not exists 20 | const keyGenerator = new KeyGenerator(configService, logger); 21 | await keyGenerator.keyGeneration(); 22 | 23 | await MongoDbService.disconnect(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /api/ssi-bridge/src/test/mocks/bitmap.ts: -------------------------------------------------------------------------------- 1 | import { Bitmap } from '@iota/is-shared-modules'; 2 | 3 | export const BitmapMock: Bitmap = { 4 | id: 'signature-bitmap-0', 5 | index: 0, 6 | serviceEndpoint: 'service-endpoint' 7 | }; 8 | -------------------------------------------------------------------------------- /api/ssi-bridge/src/test/mocks/config.ts: -------------------------------------------------------------------------------- 1 | import { Config, IdentityConfig } from '../../models/config'; 2 | 3 | export const IdentityConfigMock: IdentityConfig = { 4 | node: '', 5 | permaNode: '', 6 | bitmapTag: 'signature-bitmap' 7 | }; 8 | 9 | export const ConfigMock: Config = { 10 | jwtExpiration: '1 day', 11 | identityConfig: IdentityConfigMock, 12 | apiKey: 'test-v1', 13 | serverSecret: 'veryvery-very-very-server-secret', 14 | jwtSecret: 'veryvery-very-very-server-secret', 15 | databaseName: 'testdatabasename', 16 | databaseUrl: 'testdatabaseurl', 17 | apiVersion: '0.1', 18 | port: 3000, 19 | permaNode: 'testpermanodeurl', 20 | hornetNode: 'testhornetnodeurl', 21 | commitHash: '' 22 | }; 23 | -------------------------------------------------------------------------------- /api/ssi-bridge/src/test/mocks/logger.ts: -------------------------------------------------------------------------------- 1 | import { ILogger } from '../../utils/logger'; 2 | 3 | export const LoggerMock: ILogger = { 4 | getExpressWinstonOptions: () => ({ 5 | transports: [], 6 | headerBlacklist: ['Authorization', 'authorization', 'cookie'], 7 | level: 'info' 8 | }), 9 | log: (message: string) => {}, 10 | error: (message: string) => {} 11 | }; 12 | -------------------------------------------------------------------------------- /api/ssi-bridge/src/test/mocks/service-mocks.ts: -------------------------------------------------------------------------------- 1 | import { IConfigurationService } from '../../services/configuration-service'; 2 | import { ConfigMock } from './config'; 3 | import { ServerIdentityMock } from './identities'; 4 | 5 | export const ConfigurationServiceMock: IConfigurationService = { 6 | config: ConfigMock, 7 | identityConfig: ConfigMock.identityConfig, 8 | serverIdentityId: ServerIdentityMock.document.doc.id, 9 | getRootIdentityId: async () => { 10 | return ServerIdentityMock.document.doc.id; 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /api/ssi-bridge/src/tools/generate-secret-key/index.ts: -------------------------------------------------------------------------------- 1 | import { randomSecretKey } from '@iota/is-shared-modules/node'; 2 | 3 | const scrt = randomSecretKey(); 4 | 5 | console.log('Please store the server secret in the .env file as following:'); 6 | 7 | console.log(`SERVER_SECRET=${scrt}`); 8 | -------------------------------------------------------------------------------- /api/ssi-bridge/src/tools/markdown-creator/index.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import swaggerJSDoc from 'swagger-jsdoc'; 3 | import * as dotenv from 'dotenv'; 4 | dotenv.config(); 5 | import { openApiDefinition } from '../../routers/swagger'; 6 | 7 | // 8 | const converter = () => { 9 | const openapiSpecification = swaggerJSDoc(openApiDefinition); 10 | fs.writeFileSync('./src/tools/markdown-creator/openApiSpecification.json', JSON.stringify(openapiSpecification)); 11 | }; 12 | 13 | converter(); 14 | -------------------------------------------------------------------------------- /api/ssi-bridge/src/utils/jsonld/index.ts: -------------------------------------------------------------------------------- 1 | export class JsonldGenerator { 2 | contexts: { [key: string]: string | string[] } = { 3 | Organization: 'https://schema.org/', 4 | Person: 'https://schema.org/', 5 | Product: 'https://schema.org/', 6 | Device: ['https://smartdatamodels.org/context.jsonld'], 7 | Service: 'https://schema.org/' 8 | }; 9 | 10 | jsonldUserData(userType: string, data: any) { 11 | const context = this.contexts[userType]; 12 | if (!context) { 13 | return data; 14 | } 15 | return { 16 | '@context': context, 17 | ...data 18 | }; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /api/ssi-bridge/tsconfig.dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "es2015", "es2015.iterable"], 5 | "esModuleInterop": true, 6 | "module": "commonjs", 7 | "moduleResolution": "node", 8 | "noUnusedLocals": false, 9 | "typeRoots": ["node_modules/@types", "types"], 10 | "outDir": "dist", 11 | "resolveJsonModule": true 12 | }, 13 | "include": ["src"] 14 | } 15 | -------------------------------------------------------------------------------- /api/ssi-bridge/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "noImplicitAny": true, 5 | "lib": ["dom", "es2015", "es2015.iterable"], 6 | "esModuleInterop": true, 7 | "module": "commonjs", 8 | "moduleResolution": "node", 9 | "noUnusedLocals": true, 10 | "typeRoots": ["node_modules/@types", "types"], 11 | "outDir": "dist", 12 | "resolveJsonModule": true, 13 | "allowJs": true, 14 | "skipLibCheck": true 15 | }, 16 | "include": ["src"], 17 | "exclude": ["node_modules"] 18 | } 19 | -------------------------------------------------------------------------------- /clients/client-sdk/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | .env 4 | temp-docs -------------------------------------------------------------------------------- /clients/client-sdk/.npmignore: -------------------------------------------------------------------------------- 1 | examples 2 | node_modules 3 | src/tests 4 | dist/tests -------------------------------------------------------------------------------- /clients/client-sdk/.prettierignore: -------------------------------------------------------------------------------- 1 | lib -------------------------------------------------------------------------------- /clients/client-sdk/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "none", 4 | "printWidth": 100 5 | } 6 | -------------------------------------------------------------------------------- /clients/client-sdk/docs/markdown.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": "./README.md", 3 | "files": ["./docs/README.md"] 4 | } -------------------------------------------------------------------------------- /clients/client-sdk/examples/.env.example: -------------------------------------------------------------------------------- 1 | #----------------------- 2 | # For management purpose 3 | #----------------------- 4 | MONGO_URL= 5 | DB_NAME= 6 | SECRET_KEY= 7 | 8 | 9 | #--------------------- 10 | # Client configuration 11 | #--------------------- 12 | 13 | # You can either choose a IS_GATEWAY_URL (used in production) 14 | # or the SSI_BRIDGE_URL AND AUDIT_TRAIL_URL (for local development) 15 | 16 | API_KEY= 17 | IS_GATEWAY_URL= 18 | 19 | SSI_BRIDGE_URL= 20 | AUDIT_TRAIL_URL= 21 | 22 | #--------------------- 23 | # Optional variables 24 | #--------------------- 25 | USE_GATEWAY_URL= -------------------------------------------------------------------------------- /clients/client-sdk/examples/.gitignore: -------------------------------------------------------------------------------- 1 | adminIdentity.json 2 | .env 3 | myexample.ts -------------------------------------------------------------------------------- /clients/client-sdk/examples/0-MakeRootIdentityAdmin.ts: -------------------------------------------------------------------------------- 1 | import { IdentityClient, CredentialTypes, UserType } from '@iota/is-client'; 2 | import { defaultConfig, defaultManagerConfig } from './configuration'; 3 | import { writeFileSync } from 'fs'; 4 | import { Manager } from './manager'; 5 | import { UserRoles } from '@iota/is-shared-modules'; 6 | 7 | async function setup() { 8 | const identity = new IdentityClient(defaultConfig); 9 | 10 | const username = 'Admin-' + Math.ceil(Math.random() * 100000); 11 | const rootIdentity = await identity.create(username); 12 | 13 | writeFileSync('adminIdentity.json', JSON.stringify(rootIdentity, null, 2)); 14 | 15 | const manager = new Manager(defaultManagerConfig); 16 | 17 | await manager.setRole(rootIdentity.id, UserRoles.Admin); 18 | 19 | await identity.authenticate(rootIdentity.id, rootIdentity.keys.sign.private); 20 | 21 | await identity.createCredential( 22 | undefined, 23 | rootIdentity?.id, 24 | CredentialTypes.VerifiedIdentityCredential, 25 | UserType.Service, 26 | {} 27 | ); 28 | 29 | console.log('Identity created: ' + rootIdentity.id); 30 | } 31 | 32 | setup(); 33 | -------------------------------------------------------------------------------- /clients/client-sdk/examples/5-CreateChannel.ts: -------------------------------------------------------------------------------- 1 | import { IdentityClient, ChannelClient } from '@iota/is-client'; 2 | 3 | import { defaultConfig } from './configuration'; 4 | 5 | const channel = new ChannelClient(defaultConfig); 6 | const identity = new IdentityClient(defaultConfig); 7 | 8 | async function createChannel() { 9 | // Create a new user. The user is used for authentication only. 10 | const username = 'User-' + Math.ceil(Math.random() * 100000); 11 | const user = await identity.create(username); 12 | 13 | console.log('User', user); 14 | 15 | // Authenticate as the user 16 | await channel.authenticate(user.id, user.keys.sign.private); 17 | 18 | // Create a new channel for example data 19 | const logChannel = await channel.create({ 20 | name: `Channel-${Math.ceil(Math.random() * 100000)}`, 21 | topics: [{ type: 'example-data', source: 'data-creator' }] 22 | }); 23 | 24 | console.log('Log Channel', logChannel); 25 | 26 | // The channel address is used to read and write to channels 27 | const channelAddress = logChannel.channelAddress; 28 | console.log(`Channel address: ${channelAddress}`); 29 | 30 | // Writing 5 data packets to channel 31 | for (let i = 1; i <= 5; i++) { 32 | console.log(`Writing channel data ${i}`); 33 | await channel.write(channelAddress, { 34 | type: 'log', 35 | created: new Date().toISOString(), 36 | payload: { 37 | log: `This is log file #${i}` 38 | } 39 | }); 40 | } 41 | 42 | // Reading channel 43 | const channelData = await channel.read(channelAddress); 44 | console.log('Read from channel:'); 45 | channelData.forEach((data: any) => { 46 | console.log(data.log); 47 | }); 48 | } 49 | 50 | createChannel(); 51 | -------------------------------------------------------------------------------- /clients/client-sdk/examples/configuration.ts: -------------------------------------------------------------------------------- 1 | import { ApiVersion, ClientConfig } from '@iota/is-client'; 2 | 3 | import * as dotenv from 'dotenv'; 4 | import { ManagerConfig } from './manager/manager-config'; 5 | dotenv.config(); 6 | 7 | // You can either choose a gateway url and change useGatewayUrl to true (used in production) or the ssiBridgeUrl AND auditTrailUrl (for local testing) 8 | export const defaultConfig: ClientConfig = { 9 | isGatewayUrl: process.env.IS_GATEWAY_URL, 10 | ssiBridgeUrl: process.env.SSI_BRIDGE_URL, 11 | auditTrailUrl: process.env.AUDIT_TRAIL_URL, 12 | useGatewayUrl: process.env.USE_GATEWAY_URL === 'true' || false, 13 | apiKey: process.env.API_KEY, 14 | apiVersionAuditTrail: ApiVersion.v0_1, 15 | apiVersionSsiBridge: ApiVersion.v0_2 16 | }; 17 | 18 | export const defaultManagerConfig: ManagerConfig = { 19 | mongoURL: process.env.MONGO_URL!, 20 | databaseName: process.env.DB_NAME!, 21 | secretKey: process.env.SECRET_KEY! 22 | }; 23 | -------------------------------------------------------------------------------- /clients/client-sdk/examples/externalData.ts: -------------------------------------------------------------------------------- 1 | export const externalDriverCredential1 = { 2 | '@context': 'https://www.w3.org/2018/credentials/v1', 3 | id: 'did:iota:G5MfzLpMpRsTtmGochhrbXiSrbTKDvgC5Bgw7HdU85pV', 4 | type: ['VerifiableCredential', 'VerifiedIdentityCredential'], 5 | credentialSubject: { 6 | id: 'did:iota:G5MfzLpMpRsTtmGochhrbXiSrbTKDvgC5Bgw7HdU85pV', 7 | '@context': 'https://schema.org/', 8 | initiator: 'did:iota:AUKN9UkJrTGGBcTZiYC3Yg2FLPQWnA11X8z6D6DDn56Y', 9 | issuanceDate: '2021-12-08T06:47:29.668Z', 10 | type: 'Person' 11 | }, 12 | issuer: 'did:iota:BPvL2gG71pcNCnqpk1uqFYRK25ELxoVKLDW18reyCnfy', 13 | issuanceDate: '2022-08-24T13:14:08Z', 14 | credentialStatus: { 15 | id: 'did:iota:BPvL2gG71pcNCnqpk1uqFYRK25ELxoVKLDW18reyCnfy#signature-bitmap-0', 16 | type: 'RevocationBitmap2022', 17 | revocationBitmapIndex: '15' 18 | }, 19 | proof: { 20 | type: 'JcsEd25519Signature2020', 21 | verificationMethod: 'did:iota:BPvL2gG71pcNCnqpk1uqFYRK25ELxoVKLDW18reyCnfy#sign-0', 22 | signatureValue: 23 | '2dxcQ6NkypKhRKD21M19eUFaajdRg2cc2WjnKtYnhm2fZVuYLJ1brqz5gBa3xSyFfovs9U9Fq1zqZtM9wUBg7Ny7' 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /clients/client-sdk/examples/manager/index.ts: -------------------------------------------------------------------------------- 1 | import { MongoClient } from 'mongodb'; 2 | import { ManagerConfig } from './manager-config'; 3 | import { UserRoles } from '@iota/is-shared-modules'; 4 | 5 | export class Manager { 6 | private client: MongoClient; 7 | private connected: boolean; 8 | 9 | constructor(private config: ManagerConfig) { 10 | this.client = new MongoClient(this.config.mongoURL); 11 | this.connected = false; 12 | } 13 | 14 | async setRole(id: string, role: UserRoles): Promise { 15 | this.tryConnect(); 16 | const database = this.client.db(this.config.databaseName); 17 | const users = database.collection('users'); 18 | const user = await users.findOneAndUpdate( 19 | { 20 | id 21 | }, 22 | { 23 | $set: { role } 24 | }, 25 | { 26 | upsert: false 27 | } 28 | ); 29 | await this.close(); 30 | return !!user; 31 | } 32 | 33 | private async tryConnect() { 34 | if (this.connected) { 35 | return; 36 | } 37 | await this.client.connect(); 38 | this.connected = true; 39 | } 40 | 41 | private async close() { 42 | await this.client.close(); 43 | this.connected = false; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /clients/client-sdk/examples/manager/manager-config.ts: -------------------------------------------------------------------------------- 1 | export type ManagerConfig = { 2 | mongoURL: string; 3 | databaseName: string; 4 | secretKey: string; 5 | }; 6 | -------------------------------------------------------------------------------- /clients/client-sdk/examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "is-client-examples", 3 | "version": "0.0.1", 4 | "description": "Examples on how to use the javascript client for the integration services.", 5 | "scripts": { 6 | "example-0": "ts-node 0-MakeRootIdentityAdmin.ts", 7 | "example-1": "ts-node 1-CreateIdentityAndCredential.ts", 8 | "example-2": "ts-node 2-UpdateUser.ts", 9 | "example-3": "ts-node 3-DeleteUser.ts", 10 | "example-4": "ts-node 4-TrustedAuthorities.ts", 11 | "example-5": "ts-node 5-CreateChannel.ts", 12 | "example-6": "ts-node 6-AuthorizeToChannel.ts", 13 | "example-7": "ts-node 7-SearchChannelAndValidateData.ts" 14 | }, 15 | "author": { 16 | "name": "Tim Sigl", 17 | "email": "tim.sigl@iota.org" 18 | }, 19 | "contributors": [ 20 | { 21 | "name": "Mastrogiovanni", 22 | "email": "michele.mastrogiovanni@gmail.com" 23 | } 24 | ], 25 | "license": "ISC", 26 | "dependencies": { 27 | "@iota/is-client": "0.2.2", 28 | "@iota/is-shared-modules": "0.2.1", 29 | "dotenv": "^10.0.0", 30 | "mongodb": "^4.4.1" 31 | }, 32 | "devDependencies": { 33 | "ts-node": "^10.4.0", 34 | "typescript": "^4.6.2" 35 | } 36 | } -------------------------------------------------------------------------------- /clients/client-sdk/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: ['/src'], 3 | transform: { 4 | '^.+\\.tsx?$': 'ts-jest' 5 | }, 6 | testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$', 7 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'] 8 | }; 9 | -------------------------------------------------------------------------------- /clients/client-sdk/src/index.ts: -------------------------------------------------------------------------------- 1 | export { IdentityClient } from './clients/identity'; 2 | 3 | export { ChannelClient } from './clients/channel'; 4 | 5 | export { ApiVersion } from './models/apiVersion'; 6 | 7 | export * from './models'; 8 | -------------------------------------------------------------------------------- /clients/client-sdk/src/models/apiVersion.ts: -------------------------------------------------------------------------------- 1 | export enum ApiVersion { 2 | v0_1 = 'v0.1', 3 | v0_2 = 'v0.2' 4 | } 5 | -------------------------------------------------------------------------------- /clients/client-sdk/src/models/clientConfig.ts: -------------------------------------------------------------------------------- 1 | import { ApiVersion } from './apiVersion'; 2 | 3 | export type ClientConfig = { 4 | apiKey?: string; 5 | isGatewayUrl?: string; 6 | useGatewayUrl?: boolean; 7 | ssiBridgeUrl?: string; 8 | auditTrailUrl?: string; 9 | apiVersionAuditTrail: ApiVersion; 10 | apiVersionSsiBridge: ApiVersion; 11 | }; 12 | -------------------------------------------------------------------------------- /clients/client-sdk/src/models/index.ts: -------------------------------------------------------------------------------- 1 | export * from './searchCriteria'; 2 | 3 | export * from './clientConfig'; 4 | 5 | // Export channel types 6 | export { 7 | ChannelData, 8 | ChannelInfo, 9 | ChannelInfoSearch, 10 | AddChannelLogBody, 11 | CreateChannelBody, 12 | CreateChannelResponse, 13 | ValidateBody, 14 | ValidateResponse, 15 | AuthorizeSubscriptionBody, 16 | AuthorizeSubscriptionResponse, 17 | RequestSubscriptionBody, 18 | RequestSubscriptionResponse, 19 | RevokeSubscriptionBody, 20 | Subscription, 21 | SubscriptionUpdate, 22 | AccessRights, 23 | IdentityInternal, 24 | VerifiableCredential, 25 | RevokeVerificationBody, 26 | User, 27 | UserType, 28 | UserRoles, 29 | CredentialTypes, 30 | IdentityDocument, 31 | ChannelType, 32 | IdentityKeys 33 | } from '@iota/is-shared-modules'; 34 | -------------------------------------------------------------------------------- /clients/client-sdk/src/models/searchCriteria.ts: -------------------------------------------------------------------------------- 1 | export type SearchCriteria = { 2 | type?: string; 3 | username?: string; 4 | creator?: string; 5 | registrationDate?: Date; 6 | asc?: boolean; 7 | limit?: number; 8 | index?: number; 9 | }; 10 | -------------------------------------------------------------------------------- /clients/client-sdk/src/tests/test.data.ts: -------------------------------------------------------------------------------- 1 | import { ClientConfig } from '../models'; 2 | import { CreateChannelBody, ChannelType } from '@iota/is-shared-modules'; 3 | import { ApiVersion } from '../models/apiVersion'; 4 | 5 | export const apiConfig: ClientConfig = { 6 | isGatewayUrl: 'http://localhost:3000', 7 | useGatewayUrl: true, 8 | apiKey: '1cfa3bce-654d-41f6-a82a-94308dc4adf8', 9 | apiVersionAuditTrail: ApiVersion.v0_1, 10 | apiVersionSsiBridge: ApiVersion.v0_2 11 | }; 12 | 13 | export const adminUser = { 14 | id: 'did:iota:AucTBMLZQGXuNSUEpu19TFFCHU75DGrcQw4eYkxqfYRH', 15 | secretKey: 'ApunZiF7GpjCsZXZPzBBxeX3NDydm2S4smYuVpHHx48C' 16 | }; 17 | 18 | export const normalUser = { 19 | id: 'did:iota:BYZfez7JjvHJtirFNQ3LMHHwpvf7gxmbeTpNaNe1aJ6j', 20 | secretKey: '98cXSPbRtUPmWSkNAUvA2MkC1Sb33BTjDj3Ev2UbQYxt' 21 | }; 22 | 23 | export const testChannel: CreateChannelBody = { 24 | description: 'channelForTesting', 25 | type: ChannelType.private, 26 | hasPresharedKey: false, 27 | seed: `randomSeed-${Math.ceil(Math.random() * 100000)}`, 28 | name: `testChannel-${Math.ceil(Math.random() * 100000)}`, 29 | topics: [{ type: 'data', source: 'test' }] 30 | }; 31 | 32 | export const testChannelWrite = { 33 | type: 'test-channel-data', 34 | created: new Date().toISOString(), 35 | metadata: 'client-sdk-jest', 36 | publicPayload: 'This message was created within a test suit in the @iota/is-client', 37 | payload: 'This message was created within a test suit in the @iota/is-client' 38 | }; 39 | -------------------------------------------------------------------------------- /clients/client-sdk/tools/deleteLines.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | // Reads modules.md file and cuts of the first few lines till '### Type aliases' 4 | var data = fs.readFileSync('./temp-docs/modules.md', {encoding:'utf8'}); 5 | data = data.split('### Type Aliases')[1] 6 | fs.writeFileSync('./temp-docs/Types.md', data) 7 | -------------------------------------------------------------------------------- /clients/client-sdk/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src/**/*", "src/tests"], 3 | "exclude": ["examples/", "node_modules"], 4 | "compilerOptions": { 5 | "target": "es6", 6 | "module": "commonjs", 7 | "allowJs": false, 8 | "moduleResolution": "node", 9 | "noImplicitAny": true, 10 | "declaration": true, 11 | "declarationMap": true, 12 | "sourceMap": true, 13 | "outDir": "dist", 14 | "esModuleInterop": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "strict": true, 17 | "skipLibCheck": true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-backend/.env-example: -------------------------------------------------------------------------------- 1 | PORT=3005 2 | BASE_URL=http://localhost:3000/api/v1 3 | API_KEY= -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-backend/.eslintignore: -------------------------------------------------------------------------------- 1 | .eslintrc.js 2 | *.d.ts 3 | # don't ever lint node_modules 4 | node_modules 5 | # don't lint build output (make sure it's set to your correct build folder name) 6 | dist 7 | # don't lint nyc coverage output 8 | coverage 9 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-backend/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | plugins: ['@typescript-eslint', 'prettier'], 5 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'], 6 | rules: { 7 | '@typescript-eslint/no-explicit-any': 'off', 8 | '@typescript-eslint/explicit-module-boundary-types': 'off' 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-backend/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-backend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "none", 4 | "singleQuote": true, 5 | "printWidth": 150, 6 | "tabWidth": 2, 7 | "useTabs": true 8 | } 9 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-backend/README.md: -------------------------------------------------------------------------------- 1 | # E-Shop-Demo Backend 2 | This is the e-shop demo backend that forwards requests to the ensuresec API. 3 | 4 | ## Usage 5 | 1. Copy the `.env-example` and rename it to `.env` 6 | 2. Adjust the `BASE_URL` to the url the client shall connect to and adjust the `API_KEY`. If no api key is needed completely remove this variable from the `.env` file. 7 | 3. Run the backend using `npm install` -> `npm run build` -> `npm run start` 8 | 4. Run the backend for development using `npm install` -> `npm run serve` 9 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-backend/jest.config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | roots: ['/backend'], 3 | transform: { 4 | '^.+\\.tsx?$': 'ts-jest' 5 | }, 6 | testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$', 7 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'] 8 | }; 9 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-backend/src/index.ts: -------------------------------------------------------------------------------- 1 | import * as dotenv from 'dotenv'; 2 | dotenv.config(); 3 | import express from 'express'; 4 | import { authenticationRouter } from './routers/authentication.router'; 5 | import cors from 'cors'; 6 | import { verificationRouter } from './routers/verification.router'; 7 | 8 | const start = () => { 9 | const app = express(); 10 | app.use(cors()); 11 | app.use(express.json()); 12 | app.use(authenticationRouter); 13 | app.use(verificationRouter); 14 | 15 | const port = process.env.PORT || 3000; 16 | app.listen(port, () => { 17 | console.log(`App running on port ${port}`); 18 | }); 19 | }; 20 | 21 | start(); 22 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-backend/src/middlewares/authentication.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from 'express'; 2 | import { StatusCodes } from 'http-status-codes'; 3 | import jwt from 'jsonwebtoken'; 4 | 5 | export const authMiddleWare = () => (req: Request, res: Response, next: NextFunction) => { 6 | const { authorization } = req.headers; 7 | const serverSecret = process.env.SERVER_SECRET; 8 | if (!authorization || !authorization.startsWith('Bearer')) { 9 | return res.status(StatusCodes.UNAUTHORIZED).send({ error: 'not authenticated!' }); 10 | } 11 | 12 | const split = authorization.split('Bearer '); 13 | if (split.length !== 2) { 14 | return res.status(StatusCodes.UNAUTHORIZED).send({ error: 'not authenticated!' }); 15 | } 16 | 17 | const token = split[1]; 18 | const decodedToken: any = jwt.verify(token, serverSecret); 19 | 20 | if (typeof decodedToken === 'string' || !decodedToken?.user) { 21 | return res.status(StatusCodes.UNAUTHORIZED).send({ error: 'not authenticated!' }); 22 | } 23 | 24 | (req as any).user = decodedToken.user; 25 | next(); 26 | }; 27 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-backend/src/routers/authentication.router.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import { getJWT, getNonce } from '../routes/authentication.route'; 3 | 4 | export const authenticationRouter = Router(); 5 | 6 | authenticationRouter.get('/nonce/:id', getNonce); 7 | 8 | authenticationRouter.post('/authenticate/:id', getJWT); 9 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-backend/src/routers/verification.router.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import { verifyCredential } from '../routes/verification.route'; 3 | 4 | export const verificationRouter = Router(); 5 | 6 | verificationRouter.post('/check-credential', verifyCredential); 7 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-backend/src/routes/authentication.route.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { StatusCodes } from 'http-status-codes'; 3 | import { NextFunction, Request, Response } from 'express'; 4 | 5 | export const getNonce = async (req: Request, res: Response, next: NextFunction): Promise => { 6 | try { 7 | const id = req.params.id; 8 | const apiKey = process.env.API_KEY ? `?api-key=${process.env.API_KEY}` : ''; 9 | const response = await axios.get(`${process.env.BASE_URL}/authentication/prove-ownership/${id}${apiKey}`); 10 | return res.status(StatusCodes.OK).send(response.data); 11 | } catch (error) { 12 | console.log(error); 13 | next('could not authenticate'); 14 | } 15 | }; 16 | 17 | export const getJWT = async (req: Request, res: Response, next: NextFunction): Promise => { 18 | try { 19 | const id = req.params.id; 20 | const signedNonce = req.body; 21 | const apiKey = process.env.API_KEY ? `?api-key=${process.env.API_KEY}` : ''; 22 | const response = await axios.post(`${process.env.BASE_URL}/authentication/prove-ownership/${id}${apiKey}`, signedNonce); 23 | return res.status(StatusCodes.OK).send(response.data); 24 | } catch (error) { 25 | console.log(error); 26 | next('could not get jwt'); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-backend/src/routes/verification.route.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { NextFunction, Response } from 'express'; 3 | import { StatusCodes } from 'http-status-codes'; 4 | 5 | export const verifyCredential = async (req: any, res: Response, next: NextFunction): Promise => { 6 | try { 7 | const credential = req.body; 8 | const apiKey = process.env.API_KEY ? `?api-key=${process.env.API_KEY}` : ''; 9 | const response = await axios.post(`${process.env.BASE_URL}/verification/check-credential${apiKey}`, credential); 10 | return res.status(StatusCodes.OK).send(response?.data); 11 | } catch (error) { 12 | console.log(error); 13 | next('could not verify'); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-backend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "noImplicitAny": true, 5 | "lib": ["dom", "es2015", "es2015.iterable"], 6 | "esModuleInterop": true, 7 | "module": "commonjs", 8 | "moduleResolution": "node", 9 | "noUnusedLocals": true, 10 | "typeRoots": ["node_modules/@types", "types"], 11 | "outDir": "dist", 12 | "resolveJsonModule": true 13 | }, 14 | "include": ["src"] 15 | } 16 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-backend/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "builds": [ 3 | { 4 | "src": "dist/**", 5 | "use": "@vercel/node" 6 | } 7 | ], 8 | 9 | "rewrites": [ 10 | { 11 | "source": "/(.*)", 12 | "destination": "/dist/index.js" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/.env-example: -------------------------------------------------------------------------------- 1 | REACT_APP_E_SHOP_BACKEND_URL=http://localhost:3005 2 | REACT_APP_SECRET_KEY= -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "none", 4 | "singleQuote": true, 5 | "printWidth": 150, 6 | "tabWidth": 2, 7 | "useTabs": true 8 | } 9 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/README.md: -------------------------------------------------------------------------------- 1 | # E-Shop-Demo Frontend 2 | This is the e-shop demo frontend that provides the UI for ordering items and uploading your verifiable credential 3 | 4 | ## Usage 5 | 1. Copy the `.env-example` and rename it to `.env` 6 | 2. Adjust the `REACT_APP_E_SHOP_BACKEND_URL` if needed to the url the backend url (normally this must not be adjusted) 7 | 3. Run the frontend using `npm install` -> `npm run build` -> `npm run start` 8 | 4. Run the frontend for development using `npm install` -> `npm run serve` 9 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/config-overrides.js: -------------------------------------------------------------------------------- 1 | /* config-overrides.js */ 2 | const webpack = require('webpack'); 3 | module.exports = function override(config, env) { 4 | 5 | config.resolve.fallback = { 6 | crypto: require.resolve('crypto-browserify'), 7 | buffer: require.resolve('buffer'), 8 | stream: require.resolve('stream-browserify') 9 | }; 10 | config.plugins.push( 11 | new webpack.ProvidePlugin({ 12 | process: 'process/browser', 13 | Buffer: ['buffer', 'Buffer'], 14 | }), 15 | ); 16 | 17 | 18 | return config; 19 | } -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/public/assets/apple_juice.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger-archive/integration-services/be1bd31390d74d30fcd2cbd7ae5de53de27b028e/clients/e-shop-demo/e-shop-frontend/public/assets/apple_juice.jpg -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/public/assets/bourbon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger-archive/integration-services/be1bd31390d74d30fcd2cbd7ae5de53de27b028e/clients/e-shop-demo/e-shop-frontend/public/assets/bourbon.jpg -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/public/assets/cola.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger-archive/integration-services/be1bd31390d74d30fcd2cbd7ae5de53de27b028e/clients/e-shop-demo/e-shop-frontend/public/assets/cola.jpg -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/public/assets/wine.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger-archive/integration-services/be1bd31390d74d30fcd2cbd7ae5de53de27b028e/clients/e-shop-demo/e-shop-frontend/public/assets/wine.jpg -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger-archive/integration-services/be1bd31390d74d30fcd2cbd7ae5de53de27b028e/clients/e-shop-demo/e-shop-frontend/public/favicon.ico -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | E-Shop Demo 13 | 14 | 15 | 16 |
17 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/app.styles.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const LogoContainer = styled.div` 4 | display: flex; 5 | align-items: flex-end; 6 | flex-wrap: wrap; 7 | gap: 30px; 8 | margin: 230px 70px; 9 | justify-content: start; 10 | @media only screen and (max-width: 600px) { 11 | justify-content: center; 12 | margin: 50px 70px; 13 | } 14 | `; 15 | 16 | export const Logo = styled.img` 17 | height: 70px; 18 | width: auto; 19 | align-self: center; 20 | `; 21 | 22 | export const CaixaLogo = styled(Logo)` 23 | @media only screen and (max-width: 600px) { 24 | height: 40px; 25 | } 26 | `; 27 | 28 | export const LogoText = styled.p` 29 | padding: 0; 30 | width: 300px; 31 | align-self: center; 32 | text-align: justify; 33 | margin-bottom: 0; 34 | `; 35 | 36 | export const EuText = styled(LogoText)` 37 | width: 400px; 38 | `; 39 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/assets/caixa_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger-archive/integration-services/be1bd31390d74d30fcd2cbd7ae5de53de27b028e/clients/e-shop-demo/e-shop-frontend/src/assets/caixa_logo.png -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/assets/ensuresec_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger-archive/integration-services/be1bd31390d74d30fcd2cbd7ae5de53de27b028e/clients/e-shop-demo/e-shop-frontend/src/assets/ensuresec_logo.png -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/assets/iota_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger-archive/integration-services/be1bd31390d74d30fcd2cbd7ae5de53de27b028e/clients/e-shop-demo/e-shop-frontend/src/assets/iota_logo.png -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/components/checkout-iota/checkout-iota.styles.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const CheckoutWithIotaContainer = styled.div` 4 | color: white; 5 | border: none; 6 | padding: 35px; 7 | font-size: 16px; 8 | border-radius: 2px; 9 | margin: 20px; 10 | background-image: url('../../assets/iota_logo_white.png'); 11 | background-color: black; 12 | border-radius: 3px; 13 | 14 | @media screen and (max-width: 600px) { 15 | margin: 20px 0; 16 | padding: 25px 20px; 17 | } 18 | `; 19 | 20 | export const CheckoutWithIotaContainerHeading = styled.h3` 21 | margin-top: 0; 22 | `; 23 | 24 | export const CheckoutStepHeading = styled.h4` 25 | margin: 15px 0 10px 0; 26 | `; 27 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/components/checkout-item/checkout-item.component.tsx: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | import { CartContext } from '../../contexts/cart.provider'; 3 | import { SmallButton } from '../../global.styles'; 4 | import { Item } from '../../models/item.model'; 5 | import { CheckoutItemContainer, CheckoutItemImage, CheckoutItemName, CheckoutItemPrice } from './checkout-item.styles'; 6 | 7 | const CheckoutItem = ({ item, listIndex }: { item: Item; listIndex: number }) => { 8 | const { id, name, price, ageRestricted, imageUrl } = item; 9 | const { removeFromCart } = useContext(CartContext); 10 | 11 | return ( 12 | <> 13 | 14 | {imageUrl && } 15 | {name} 16 | {price} € 17 | {ageRestricted && 🔞} 18 | removeFromCart(listIndex)}> 19 | X 20 | 21 | 22 | 23 | ); 24 | }; 25 | 26 | export default CheckoutItem; 27 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/components/checkout-item/checkout-item.styles.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const CheckoutItemContainer = styled.div` 4 | background-color: ${(props) => props.theme.background}; 5 | padding: 5px; 6 | margin: 5px 15px; 7 | display: flex; 8 | align-items: center; 9 | border-radius: 3px; 10 | box-shadow: 5px 5px 16px 0px rgba(0, 0, 0, 0.4); 11 | 12 | @media screen and (max-width: 450px) { 13 | margin: 5px 0; 14 | } 15 | `; 16 | 17 | export const CheckoutItemImage = styled.img` 18 | width: 35px; 19 | height: auto; 20 | margin-left: 10px; 21 | @media screen and (max-width: 450px) { 22 | display: none; 23 | } 24 | `; 25 | 26 | export const CheckoutItemName = styled.span` 27 | font-size: larger; 28 | margin: 0 20px; 29 | `; 30 | 31 | export const CheckoutItemPrice = styled(CheckoutItemName)` 32 | @media screen and (max-width: 450px) { 33 | display: none; 34 | } 35 | `; 36 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/components/checkout-total-message/checkout-total-message.component.tsx: -------------------------------------------------------------------------------- 1 | import { CartContext } from '../../contexts/cart.provider'; 2 | import { useContext } from 'react'; 3 | import { CheckoutTotalMessageContainer } from './checkout-total-message.styles'; 4 | import { Item } from '../../models/item.model'; 5 | const CheckoutTotalMessage = () => { 6 | const { items } = useContext(CartContext); 7 | const cartHasAgeRestrictedItems = !!items.find((item: Item) => item.ageRestricted === true); 8 | return ( 9 | 10 | Total {items.reduce((sum: number, item: Item) => sum + item.price, 0)} € 11 | {cartHasAgeRestrictedItems ? ( 12 |
13 | Cart has items with age restriction 14 |
15 | ) : ( 16 |
17 | Cart has no items with age restriction 18 |
19 | )} 20 |
21 | ); 22 | }; 23 | 24 | export default CheckoutTotalMessage; 25 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/components/checkout-total-message/checkout-total-message.styles.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const CheckoutTotalMessageContainer = styled.div` 4 | margin: 20px; 5 | color: white; 6 | `; 7 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/components/checkout-total/checkout-total.styles.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const CheckoutTotalContainer = styled.div` 4 | background-color: ${(props) => props.theme.secondaryLight}; 5 | min-width: 400px; 6 | padding: 20px; 7 | flex: 1; 8 | border-radius: 5px; 9 | @media screen and (max-width: 600px) { 10 | min-width: unset; 11 | } 12 | `; 13 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/components/credential-display/credential-display.component.tsx: -------------------------------------------------------------------------------- 1 | import { VcDisplay } from './credential-display.styles'; 2 | 3 | const CredentialDisplay = ({ credentialFile, showCredential }: { credentialFile: any; showCredential: boolean }) => { 4 | return ( 5 | <> 6 | {credentialFile && showCredential && ( 7 | 8 | Selected Verifiable Credential: 9 |
10 | {JSON.stringify(credentialFile, null, 4)} 11 |
12 | )} 13 | 14 | ); 15 | }; 16 | 17 | export default CredentialDisplay; 18 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/components/credential-display/credential-display.styles.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const VcDisplay = styled.pre` 4 | margin-top: 20px; 5 | padding: 10px; 6 | color: black; 7 | border-radius: 3px; 8 | background-color: white; 9 | `; 10 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/components/error-boundary/error-boundary.component.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | class ErrorBoundary extends React.Component { 4 | constructor(props: any) { 5 | super(props); 6 | this.state = { hasError: false }; 7 | } 8 | 9 | static getDerivedStateFromError(error: any) { 10 | // Update state so the next render will show the fallback UI. 11 | return { hasError: true }; 12 | } 13 | 14 | componentDidCatch(error: any, errorInfo: any) {} 15 | 16 | render() { 17 | return this.props.children; 18 | } 19 | } 20 | 21 | export default ErrorBoundary; 22 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/components/generate-nonce/generate-nonce.component.tsx: -------------------------------------------------------------------------------- 1 | import { useContext, useEffect, useState } from 'react'; 2 | import { UserContext } from '../../contexts/user.provider'; 3 | import { generateNonce } from '../../services/authentication.service'; 4 | 5 | const GenerateNonce = () => { 6 | const [nonce, setNonce] = useState(); 7 | const { credential } = useContext(UserContext); 8 | 9 | useEffect(() => { 10 | async function getNonce() { 11 | const identityId = credential?.id; 12 | identityId && setNonce(await generateNonce(identityId)); 13 | } 14 | getNonce(); 15 | }, [credential]); 16 | 17 | return nonce ? ( 18 | <> 19 |

21 | Generated nonce: {nonce} 22 |

23 |

Sign this nonce with your secret key using the provided tool.

24 | 25 | ) : null; 26 | }; 27 | 28 | export default GenerateNonce; 29 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/components/header/header.component.tsx: -------------------------------------------------------------------------------- 1 | import { HeaderHeading, HeaderItem, HeaderLink, HeaderRight, HeaderWrapper } from './header.styles'; 2 | import { useContext } from 'react'; 3 | import { CartContext } from '../../contexts/cart.provider'; 4 | import { useLocation } from 'react-router-dom'; 5 | import { UserContext } from '../../contexts/user.provider'; 6 | 7 | const Header = () => { 8 | const { items } = useContext(CartContext); 9 | const { authenticated, logout } = useContext(UserContext); 10 | const location = useLocation(); 11 | const pageName = location.pathname.split('/')[1]; 12 | 13 | const title = pageName ? pageName.toUpperCase() : 'Shop'; 14 | return ( 15 | 16 | 17 | E-Shop 18 | 19 | 20 | {title} 21 | 22 | {authenticated && Logout} 23 | 24 | 25 | Cart ({items.length}) 26 | 27 | 28 | 29 | ); 30 | }; 31 | 32 | export default Header; 33 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/components/header/header.styles.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from 'react-router-dom'; 2 | import styled, { css } from 'styled-components'; 3 | import { Button } from '../../global.styles'; 4 | 5 | export const HeaderWrapper = styled.div` 6 | display: flex; 7 | flex-direction: row; 8 | background-color: ${(props) => props.theme.primary}; 9 | justify-content: space-between; 10 | align-items: center; 11 | `; 12 | 13 | const headerItemBaseStyle = css` 14 | color: white; 15 | padding: 10px; 16 | margin: 10px; 17 | text-decoration: none; 18 | font-weight: 600; 19 | cursor: pointer; 20 | 21 | &:visited, 22 | &:focus, 23 | &:link, 24 | &:active { 25 | color: white; 26 | } 27 | 28 | &:hover { 29 | color: ${(props) => props.theme.background}; 30 | } 31 | `; 32 | export const HeaderItem = styled.div` 33 | ${headerItemBaseStyle} 34 | `; 35 | 36 | export const HeaderLink = styled(Link)` 37 | ${headerItemBaseStyle} 38 | `; 39 | 40 | export const HeaderButton = styled(Button)` 41 | width: auto; 42 | `; 43 | 44 | export const HeaderHeading = styled.h3` 45 | color: white; 46 | font-size: x-large; 47 | @media screen and (max-width: 450px) { 48 | display: none; 49 | } 50 | `; 51 | 52 | export const HeaderRight = styled.div` 53 | display: flex; 54 | justify-content: end; 55 | `; 56 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/components/list-item/list-item.component.tsx: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | import { CartContext } from '../../contexts/cart.provider'; 3 | import { Button } from '../../global.styles'; 4 | import { Item as ItemModel } from '../../models/item.model'; 5 | import { Card, CardHeading, CardImage, CardText } from './list-item.styles'; 6 | 7 | const Item = ({ item }: any) => { 8 | const { name, imageUrl, price, ageRestricted } = item as ItemModel; 9 | const { addToCart } = useContext(CartContext); 10 | return ( 11 | 12 | {name} 13 | 14 | Price: {price}€ 15 | 16 | 17 | ); 18 | }; 19 | 20 | export default Item; 21 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/components/list-item/list-item.styles.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Card = styled.div` 4 | display: flex; 5 | flex-direction: column; 6 | maxhight: 400px; 7 | minwidth: 100px; 8 | border-radius: 3px; 9 | background-color: ${(props) => props.theme.secondaryLight}}; 10 | `; 11 | 12 | export const CardHeading = styled.h3` 13 | text-align: center; 14 | margin-top: 15px; 15 | color: white; 16 | `; 17 | 18 | export const CardImage = styled.img` 19 | width: 50%; 20 | height: auto; 21 | margin: auto; 22 | `; 23 | 24 | export const CardText = styled.p` 25 | text-align: center; 26 | padding: 10px; 27 | color: white; 28 | font-weight: 500; 29 | `; 30 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/components/message-box/message-box.component.tsx: -------------------------------------------------------------------------------- 1 | import { MessageBoxWrapper } from './message-box.styles'; 2 | import { FunctionComponent } from 'react'; 3 | 4 | interface Props { 5 | className: string; 6 | show: boolean; 7 | type: string; 8 | } 9 | const MessageBox: FunctionComponent = ({ className, show, type, children }) => { 10 | return show ? ( 11 | 12 | {children} 13 | 14 | ) : null; 15 | }; 16 | 17 | export default MessageBox; 18 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/components/message-box/message-box.styles.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const MessageBoxWrapper = styled.div<{ type: string }>` 4 | ${({ type }) => { 5 | if (type === 'success') { 6 | return `background-color: #bdcebe;`; 7 | } else { 8 | return `background-color: #eca1a6;`; 9 | } 10 | }} 11 | padding: 10px; 12 | color: black; 13 | border-radius: 3px; 14 | `; 15 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/components/switch-tour/switch-tour.component.tsx: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | import { Form } from 'react-bootstrap'; 3 | import { TourContext } from '../../contexts/tour.provider'; 4 | import { ToggleTourContainer } from './switch-tour.styles'; 5 | 6 | const SwitchTour = () => { 7 | const { disabled, setDisabled } = useContext(TourContext); 8 | 9 | return ( 10 | 11 | setDisabled(!disabled)} checked={disabled} type="switch" id="custom-switch" label="Disable tour" /> 12 | 13 | ); 14 | }; 15 | 16 | export default SwitchTour; 17 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/components/switch-tour/switch-tour.styles.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import { theme } from '../../global.styles'; 3 | 4 | export const ToggleTourContainer = styled.div` 5 | margin: 20px; 6 | display: flex; 7 | padding: 20px; 8 | background-color: ${theme.secondaryLight}; 9 | color: ${theme.background}; 10 | border-radius: 5px; 11 | `; 12 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/contexts/cart.provider.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, useState } from 'react'; 2 | import { Item } from '../models/item.model'; 3 | 4 | export const CartContext = createContext({} as any); 5 | 6 | const CartProvider = ({ children }: any) => { 7 | const [items, setItems] = useState([]); 8 | 9 | const addToCart = (item: Item) => { 10 | setItems([...items, item]); 11 | }; 12 | 13 | const removeFromCart = (listIndex: number) => { 14 | // A copy is needed to not splice the original array 15 | const itemsCopy = [...items]; 16 | itemsCopy.splice(listIndex, 1); 17 | setItems(itemsCopy); 18 | }; 19 | 20 | const emptyCart = () => { 21 | setItems([]); 22 | }; 23 | 24 | return ( 25 | 33 | {children} 34 | 35 | ); 36 | }; 37 | 38 | export default CartProvider; 39 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/contexts/tour.provider.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, useState } from 'react'; 2 | 3 | export const TourContext = createContext({} as any); 4 | 5 | const TourProvider = ({ children }: any) => { 6 | const [step, setStep] = useState(0); 7 | const [run, setRun] = useState(); 8 | const [disabled, setDisabled] = useState(false); 9 | 10 | return ( 11 | 21 | {children} 22 | 23 | ); 24 | }; 25 | 26 | export default TourProvider; 27 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/contexts/user.provider.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, useEffect, useState } from 'react'; 2 | import { removeAuthHeader } from '../utils/axios-client'; 3 | 4 | export const UserContext = createContext({} as any); 5 | const UserProvider = ({ children }: any) => { 6 | const [authenticated, setAuthenticated] = useState(); 7 | const [credential, setCredential] = useState(); 8 | const [isVerified, setIsVerified] = useState(); 9 | const [useOwnCredential, setUseOwnCredential] = useState(); 10 | 11 | useEffect(() => { 12 | const localStorage = window.localStorage; 13 | const jwt = localStorage.getItem('jwt'); 14 | const auth = jwt ? true : false; 15 | setAuthenticated(auth); 16 | }, []); 17 | 18 | const logout = () => { 19 | setAuthenticated(false); 20 | removeAuthHeader(); 21 | }; 22 | 23 | return ( 24 | 37 | {children} 38 | 39 | ); 40 | }; 41 | 42 | export default UserProvider; 43 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/data/credential.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "https://www.w3.org/2018/credentials/v1", 3 | "id": "did:iota:CBPNQxwcZFbJZNTnw6Xgd7DV2Wu15SpNdXL4oXLGrT3w", 4 | "type": ["VerifiableCredential", "BasicIdentityCredential"], 5 | "credentialSubject": { 6 | "id": "did:iota:CBPNQxwcZFbJZNTnw6Xgd7DV2Wu15SpNdXL4oXLGrT3w", 7 | "@context": "https://schema.org/", 8 | "birthDate": "1980-06-21", 9 | "familyName": "Smith", 10 | "givenName": "Sarah", 11 | "initiatorId": "did:iota:3d2aY6ok7z6oHdGHVoPSnmXizn18S3Y7VEY4GH7ecsis", 12 | "name": "Sarah Smith", 13 | "type": "Person" 14 | }, 15 | "issuer": "did:iota:FSAMVdZqbUTaTHnL6yNGzPzuxTNLuE5VEbz7SndkgYCP", 16 | "issuanceDate": "2021-06-21T14:46:00Z", 17 | "proof": { 18 | "type": "MerkleKeySignature2021", 19 | "verificationMethod": "#key-collection-0", 20 | "signatureValue": "EeCDzL3Z6V783bjS8Ja8g4RyMJ1Ug6LY95Kaqd5DfCxR.1116m1gDuQFRQomZ1LDWrHtJjm6AFjqnTEsHQQLnB6YTgjS1HvWcqeo4Y9nFKg6V9BBwyHiXgL7SmpjD6VAmhKPyebSm91kAKhPdgyLkj3jEje4iLQFQpJhkZJT4A9JeXghdirqUgE4mEMZ3zqad9xgj3nqUj7QyGAXWeRuVRb13xQEHskKeWiDU9T4DMbwxU6nwRJtKGS51hsjcbyChvGB4eAW4KdSXgrJpA2FYG86rzgcmjty6pJL6cTWzpq33MzaaXEFxzotT1TjaSYAVcSesPpDaMYrSRyMtBCdoo1f2So9t3Cdgb2fq5zszWGPNZ3AUQJo48eb5jmnKx5TZfzDiLttvf6Psy49U34C34h7T5gJ3DJDFcbbouzrV9kLEZJDdWm1tcRZHZ4iphgckPqftQFkWcF6kYZM1fFyA21PC7YX2oR2L7d7ECUqNsc1yMvFN1xj4UC7UWfCYvf1dge5pVr6q7LgeFpJ4ZvDpjqmRxsse9R1B5rvi4BDEeRgJkQ2LQoyxRULZHZtjCUWAk2MspjDULM4yW.RiJWAUFzAYuxBU3QBGtqVgpWqhT4XktM9RAkPg1rNXxArmCPvbFFz5PuwS12d1hAH5K3vSUwh9GcyPmj44nYChe" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/data/credential_not_trusted_root.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "https://www.w3.org/2018/credentials/v1", 3 | "id": "did:iota:CBPNQxwcZFbJZNTnw6Xgd7DV2Wu15SpNdXL4oXLGrT3w", 4 | "type": ["VerifiableCredential", "BasicIdentityCredential"], 5 | "credentialSubject": { 6 | "id": "did:iota:CBPNQxwcZFbJZNTnw6Xgd7DV2Wu15SpNdXL4oXLGrT3w", 7 | "@context": "https://schema.org/", 8 | "birthDate": "1980-06-21", 9 | "familyName": "Smith", 10 | "givenName": "Sarah", 11 | "initiatorId": "did:iota:3d2aY6ok7z6oHdGHVoPSnmXizn18S3Y7VEY4GH7ecsis", 12 | "name": "Sarah Smith", 13 | "type": "Person" 14 | }, 15 | "issuer": "did:iota:8zGhh2x8bX93641mWAxh7W61nek5E6VxKmQWND2ATQTH", 16 | "issuanceDate": "2021-06-21T14:46:00Z", 17 | "proof": { 18 | "type": "MerkleKeySignature2021", 19 | "verificationMethod": "#key-collection-0", 20 | "signatureValue": "EeCDzL3Z6V783bjS8Ja8g4RyMJ1Ug6LY95Kaqd5DfCxR.1116m1gDuQFRQomZ1LDWrHtJjm6AFjqnTEsHQQLnB6YTgjS1HvWcqeo4Y9nFKg6V9BBwyHiXgL7SmpjD6VAmhKPyebSm91kAKhPdgyLkj3jEje4iLQFQpJhkZJT4A9JeXghdirqUgE4mEMZ3zqad9xgj3nqUj7QyGAXWeRuVRb13xQEHskKeWiDU9T4DMbwxU6nwRJtKGS51hsjcbyChvGB4eAW4KdSXgrJpA2FYG86rzgcmjty6pJL6cTWzpq33MzaaXEFxzotT1TjaSYAVcSesPpDaMYrSRyMtBCdoo1f2So9t3Cdgb2fq5zszWGPNZ3AUQJo48eb5jmnKx5TZfzDiLttvf6Psy49U34C34h7T5gJ3DJDFcbbouzrV9kLEZJDdWm1tcRZHZ4iphgckPqftQFkWcF6kYZM1fFyA21PC7YX2oR2L7d7ECUqNsc1yMvFN1xj4UC7UWfCYvf1dge5pVr6q7LgeFpJ4ZvDpjqmRxsse9R1B5rvi4BDEeRgJkQ2LQoyxRULZHZtjCUWAk2MspjDULM4yW.RiJWAUFzAYuxBU3QBGtqVgpWqhT4XktM9RAkPg1rNXxArmCPvbFFz5PuwS12d1hAH5K3vSUwh9GcyPmj44nYChe" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/data/credential_under_age.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "https://www.w3.org/2018/credentials/v1", 3 | "id": "did:iota:CBPNQxwcZFbJZNTnw6Xgd7DV2Wu15SpNdXL4oXLGrT3w", 4 | "type": ["VerifiableCredential", "BasicIdentityCredential"], 5 | "credentialSubject": { 6 | "id": "did:iota:CBPNQxwcZFbJZNTnw6Xgd7DV2Wu15SpNdXL4oXLGrT3w", 7 | "@context": "https://schema.org/", 8 | "birthDate": "2005-06-21", 9 | "familyName": "Smith", 10 | "givenName": "Sarah", 11 | "initiatorId": "did:iota:3d2aY6ok7z6oHdGHVoPSnmXizn18S3Y7VEY4GH7ecsis", 12 | "name": "Sarah Smith", 13 | "type": "Person" 14 | }, 15 | "issuer": "did:iota:FSAMVdZqbUTaTHnL6yNGzPzuxTNLuE5VEbz7SndkgYCP", 16 | "issuanceDate": "2021-06-21T14:46:00Z", 17 | "proof": { 18 | "type": "MerkleKeySignature2021", 19 | "verificationMethod": "#key-collection-0", 20 | "signatureValue": "EeCDzL3Z6V783bjS8Ja8g4RyMJ1Ug6LY95Kaqd5DfCxR.1116m1gDuQFRQomZ1LDWrHtJjm6AFjqnTEsHQQLnB6YTgjS1HvWcqeo4Y9nFKg6V9BBwyHiXgL7SmpjD6VAmhKPyebSm91kAKhPdgyLkj3jEje4iLQFQpJhkZJT4A9JeXghdirqUgE4mEMZ3zqad9xgj3nqUj7QyGAXWeRuVRb13xQEHskKeWiDU9T4DMbwxU6nwRJtKGS51hsjcbyChvGB4eAW4KdSXgrJpA2FYG86rzgcmjty6pJL6cTWzpq33MzaaXEFxzotT1TjaSYAVcSesPpDaMYrSRyMtBCdoo1f2So9t3Cdgb2fq5zszWGPNZ3AUQJo48eb5jmnKx5TZfzDiLttvf6Psy49U34C34h7T5gJ3DJDFcbbouzrV9kLEZJDdWm1tcRZHZ4iphgckPqftQFkWcF6kYZM1fFyA21PC7YX2oR2L7d7ECUqNsc1yMvFN1xj4UC7UWfCYvf1dge5pVr6q7LgeFpJ4ZvDpjqmRxsse9R1B5rvi4BDEeRgJkQ2LQoyxRULZHZtjCUWAk2MspjDULM4yW.RiJWAUFzAYuxBU3QBGtqVgpWqhT4XktM9RAkPg1rNXxArmCPvbFFz5PuwS12d1hAH5K3vSUwh9GcyPmj44nYChe" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/data/items.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "name": "Red wine", 5 | "imageUrl": "wine.jpg", 6 | "price": 10.5, 7 | "ageRestricted": true 8 | }, 9 | { 10 | "id": 2, 11 | "name": "Bourbon", 12 | "imageUrl": "bourbon.jpg", 13 | "price": 16.5, 14 | "ageRestricted": true 15 | }, 16 | { 17 | "id": 3, 18 | "name": "Apple juice", 19 | "imageUrl": "apple_juice.jpg", 20 | "price": 4.0, 21 | "ageRestricted": false 22 | }, 23 | { 24 | "id": 4, 25 | "name": "Coca Cola", 26 | "imageUrl": "cola.jpg", 27 | "price": 4.5, 28 | "ageRestricted": false 29 | } 30 | ] 31 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/global.styles.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const theme = { 4 | primary: '#1e9a93', // tealish 5 | secondary: '#1c1e21', // black 6 | secondaryLight: '#696f76', // body copy 7 | accent: '#603f98', // blueberry 8 | accentDarker: '#4c3279', // blueberry 20% darker 9 | background: '#ffffff' // white 10 | }; 11 | 12 | export const Button = styled.button` 13 | background-color: ${theme.accent}; 14 | color: white; 15 | border: none; 16 | padding: 15px 32px; 17 | font-size: 16px; 18 | text-decoration: none; 19 | border-radius: 5px; 20 | margin: 10px; 21 | cursor: pointer; 22 | transition: 0.2s ease; 23 | transition-property: color, , border-color; 24 | &:hover { 25 | background-color: ${theme.accentDarker}; 26 | box-shadow: rgba(100, 100, 111, 0.2) 0px 7px 29px 0px; 27 | } 28 | `; 29 | 30 | export const SmallButton = styled(Button)` 31 | padding: 5px 20px; 32 | margin-left: 0; 33 | `; 34 | 35 | export const Background = styled.div` 36 | background-color: ${(props) => props.theme.background}; 37 | width: 100%; 38 | `; 39 | 40 | export const Input = styled.input` 41 | width: 100%; 42 | padding: 8px 15px; 43 | box-sizing: border-box; 44 | margin: 8px 0; 45 | border-radius: 3px; 46 | `; 47 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 4 | sans-serif; 5 | -webkit-font-smoothing: antialiased; 6 | -moz-osx-font-smoothing: grayscale; 7 | background-color: #ffffff !important; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; 12 | } 13 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './app'; 4 | import './index.css'; 5 | import reportWebVitals from './reportWebVitals'; 6 | import { ThemeProvider } from 'styled-components'; 7 | import { theme } from './global.styles'; 8 | import 'bootstrap/dist/css/bootstrap.min.css'; 9 | 10 | ReactDOM.render( 11 | 12 | 13 | 14 | 15 | , 16 | document.getElementById('root') 17 | ); 18 | 19 | // If you want to start measuring performance in your app, pass a function 20 | // to log results (for example: reportWebVitals(console.log)) 21 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 22 | reportWebVitals(); 23 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/models/item.model.tsx: -------------------------------------------------------------------------------- 1 | export interface Item { 2 | // id and imageUrl are optional because 'No items' placeholder does not have any of those properties 3 | id?: number; 4 | name: string; 5 | imageUrl?: string; 6 | price: number; 7 | ageRestricted: boolean; 8 | } 9 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/pages/checkout/checkout.component.tsx: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | import CheckoutItem from '../../components/checkout-item/checkout-item.component'; 3 | import CheckoutTotal from '../../components/checkout-total/checkout-total.component'; 4 | import { CartContext } from '../../contexts/cart.provider'; 5 | import { Item } from '../../models/item.model'; 6 | import { CheckoutContainer, CheckoutHeading, CheckoutItemsContainer } from './checkout.styles'; 7 | 8 | const Checkout = () => { 9 | const { items } = useContext(CartContext); 10 | return ( 11 | 12 | 13 | Cart 14 | {items.map((item: Item, index: number) => { 15 | return ; 16 | })} 17 | {/* If there are no items in cart, display fake item */} 18 | {items.length === 0 && ( 19 | 20 | )} 21 | 22 | 23 | 24 | ); 25 | }; 26 | 27 | export default Checkout; 28 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/pages/checkout/checkout.styles.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const CheckoutContainer = styled.div` 4 | display: flex; 5 | flex-direction: row; 6 | justify-content: space-between; 7 | margin: 20px 70px; 8 | gap: 20px; 9 | flex-wrap: wrap; 10 | 11 | @media screen and (max-width: 600px) { 12 | margin: 20px 5px; 13 | } 14 | `; 15 | 16 | export const CheckoutItemsContainer = styled.div` 17 | display: flex; 18 | flex-direction: column; 19 | padding: 20px; 20 | flex: 1; 21 | min-width: 400px; 22 | border-radius: 5px; 23 | background-color: ${(props) => props.theme.secondaryLight}; 24 | @media screen and (max-width: 600px) { 25 | min-width: unset; 26 | } 27 | `; 28 | 29 | export const CheckoutHeading = styled.h2` 30 | color: white; 31 | margin-top: 0; 32 | text-align: center; 33 | `; 34 | 35 | export const CheckoutTotal = styled.div` 36 | background-color: ${(props) => props.theme.brown}; 37 | min-width: 400px; 38 | padding: 20px; 39 | flex: 1; 40 | @media screen and (max-width: 600px) { 41 | min-width: unset; 42 | } 43 | `; 44 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/pages/item-list/item-list.component.tsx: -------------------------------------------------------------------------------- 1 | import { ItemsContainer } from './item-list.styles'; 2 | import items from '../../data/items.json'; 3 | import Item from '../../components/list-item/list-item.component'; 4 | 5 | const ItemList = () => { 6 | return ( 7 | 8 | {items.map((item) => { 9 | return ; 10 | })} 11 | 12 | ); 13 | }; 14 | 15 | export default ItemList; 16 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/pages/item-list/item-list.styles.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const ItemsContainer = styled.div` 4 | display: grid; 5 | grid-template-columns: 1fr 1fr 1fr 1fr; 6 | grid-gap: 30px; 7 | margin: 20px 70px; 8 | 9 | @media screen and (max-width: 950px) { 10 | grid-template-columns: 1fr 1fr; 11 | grid-gap: 15px; 12 | margin: 20px 20px; 13 | } 14 | 15 | @media screen and (max-width: 600px) { 16 | grid-template-columns: 1fr; 17 | grid-gap: 15px; 18 | margin: 20px 20px; 19 | } 20 | `; 21 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/services/authentication.service.ts: -------------------------------------------------------------------------------- 1 | import { client, setAuthHeader } from '../utils/axios-client'; 2 | import * as crypto from 'crypto'; 3 | import * as ed from '@noble/ed25519'; 4 | import bs58 from 'bs58'; 5 | 6 | export const generateNonce = async (id: any): Promise => { 7 | const url = `${process.env.REACT_APP_E_SHOP_BACKEND_URL}/nonce/${id}`; 8 | const response = await client.get(url); 9 | return response.data.nonce; 10 | }; 11 | // First method of authenticating via a self-signed nonce 12 | export const authSignedNonce = async (signedNonce: string, id: string): Promise => { 13 | try { 14 | const url = `${process.env.REACT_APP_E_SHOP_BACKEND_URL}/authenticate/${id}`; 15 | const response = await client.post(url, JSON.stringify({ signedNonce })); 16 | const jwt = response.data.jwt; 17 | setAuthHeader(jwt); 18 | return true; 19 | } catch (error) { 20 | console.log(error); 21 | return false; 22 | } 23 | }; 24 | 25 | // Second method of authentication via a supplied secret key 26 | export const authSecretKey = async (id: string, secretKey: string): Promise => { 27 | const nonce = await generateNonce(id); 28 | const encodedKey = getHexEncodedKey(secretKey); 29 | return await signNonce(encodedKey, nonce); 30 | }; 31 | 32 | const getHexEncodedKey = (base58Key: string) => { 33 | return bs58.decode(base58Key).toString('hex'); 34 | }; 35 | 36 | const signNonce = async (secretKey: string, nonce: string) => { 37 | if (nonce.length !== 40) { 38 | console.log('nonce does not match length of 40 characters!'); 39 | process.exit(); 40 | } 41 | const hash = crypto.createHash('sha256').update(nonce).digest().toString('hex'); 42 | const signedHash = await ed.sign(hash, secretKey); 43 | return ed.Signature.fromHex(signedHash).toHex(); 44 | }; 45 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/services/verify-credential.service.ts: -------------------------------------------------------------------------------- 1 | import { client } from '../utils/axios-client'; 2 | 3 | export const readFile = (file: File): Promise => { 4 | return new Promise((resolve, reject) => { 5 | try { 6 | const fileReader = new FileReader(); 7 | fileReader.onload = () => { 8 | try { 9 | const result = JSON.parse(fileReader.result as string); 10 | resolve(result); 11 | } catch (e: any) { 12 | resolve(JSON.parse('{"error": "No valid json selected" }')); 13 | } 14 | }; 15 | fileReader.onerror = () => reject; 16 | fileReader.readAsText(file); 17 | } catch (e: any) { 18 | reject(); 19 | } 20 | }); 21 | }; 22 | 23 | export const verifyCredential = async (credential: any): Promise => { 24 | try { 25 | const url = `${process.env.REACT_APP_E_SHOP_BACKEND_URL}/check-credential`; 26 | const response = await client.post(url, credential); 27 | const isVerified = response.data.isVerified; 28 | return isVerified; 29 | } catch (error: any) { 30 | console.log(error); 31 | if (error.response.status === 500) { 32 | return false; 33 | } 34 | throw new Error('Could not verify credential'); 35 | } 36 | }; 37 | 38 | export const isOverAgeRestriction = (credential: any, ageRestriction = 18) => { 39 | const oneYear = 1000 * 60 * 60 * 24 * 365; 40 | const birthDate = Date.parse(credential?.credentialSubject?.birthDate); 41 | const currentDate = new Date().getTime(); 42 | const dateDifference = currentDate - birthDate; 43 | const yearsOld = dateDifference / oneYear; 44 | const overAgeRestriction = yearsOld > ageRestriction; 45 | return overAgeRestriction; 46 | }; 47 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/src/utils/axios-client.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | export const client = axios.create(); 4 | 5 | client.defaults.headers.post['Content-Type'] = 'application/json'; 6 | 7 | export const setAuthHeader = (jwt: string) => { 8 | client.defaults.headers.common['Authorization'] = `Bearer ${jwt}`; 9 | const localStorage = window.localStorage; 10 | localStorage.setItem('jwt', jwt); 11 | }; 12 | 13 | export const removeAuthHeader = () => { 14 | delete client.defaults.headers.common['Authorization']; 15 | const localStorage = window.localStorage; 16 | localStorage.removeItem('jwt'); 17 | }; 18 | 19 | const localStorage = window.localStorage; 20 | const jwt = localStorage.getItem('jwt'); 21 | if (jwt) { 22 | setAuthHeader(jwt); 23 | } 24 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-frontend/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "github": { 3 | "silent": true 4 | } 5 | } -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-nonce-signer/README.md: -------------------------------------------------------------------------------- 1 | # E-Shop-Demo Nonce-Signer 2 | This is the e-shop demo nonce signer that is used for signing your nonce with your private key 3 | 4 | ## Usage 5 | 1. Run `npm install` 6 | 2. Start the nonce signer by running `npm run start` and follow the prompts from the terminal 7 | 8 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-nonce-signer/nonce-signer.js: -------------------------------------------------------------------------------- 1 | import * as crypto from "crypto"; 2 | import * as ed from "@noble/ed25519"; 3 | import bs58 from "bs58"; 4 | import readline from "readline"; 5 | 6 | const rl = readline.createInterface({ 7 | input: process.stdin, 8 | output: process.stdout, 9 | }); 10 | 11 | rl.question("Please enter your nonce: ", (nonce) => { 12 | rl.stdoutMuted = true; 13 | rl.query = "Please enter your secrect key: "; 14 | rl.question(rl.query, async (secretKey) => { 15 | const encodedKey = getHexEncodedKey(secretKey); 16 | const signedNonce = await signNonce(encodedKey, nonce); 17 | rl.stdoutMuted = false; 18 | console.log("---------------------------------------"); 19 | console.log(`Your signed nonce is: ${signedNonce}`); 20 | console.log("Use your signed nonce to log in to the e-shop."); 21 | rl.close(); 22 | }); 23 | }); 24 | 25 | rl._writeToOutput = function _writeToOutput(stringToWrite) { 26 | if (rl.stdoutMuted) 27 | rl.output.write( 28 | "\x1B[2K\x1B[200D" + 29 | rl.query + 30 | "[" + 31 | (rl.line.length % 2 == 1 ? "=-" : "-=") + 32 | "]" 33 | ); 34 | else rl.output.write(stringToWrite); 35 | }; 36 | const getHexEncodedKey = (base58Key) => { 37 | return bs58.decode(base58Key).toString("hex"); 38 | }; 39 | 40 | const signNonce = async (secretKey, nonce) => { 41 | if (nonce.length !== 40) { 42 | console.log("nonce does not match length of 40 characters!"); 43 | process.exit(); 44 | } 45 | const hash = crypto 46 | .createHash("sha256") 47 | .update(nonce) 48 | .digest() 49 | .toString("hex"); 50 | const signedHash = await ed.sign(hash, secretKey); 51 | return ed.Signature.fromHex(signedHash).toHex(); 52 | }; 53 | -------------------------------------------------------------------------------- /clients/e-shop-demo/e-shop-nonce-signer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "e-shop-nonce-signer", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "nonce-signer.js", 6 | "scripts": { 7 | "start": "node nonce-signer.js" 8 | }, 9 | "type": "module", 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "@noble/ed25519": "1.4.0", 15 | "bs58": "^4.0.1" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /clients/secant/device/.env-example: -------------------------------------------------------------------------------- 1 | BASE_URL=http://localhost:3000/api/v1 2 | CHANNEL_ADDRESS= 3 | API_KEY= -------------------------------------------------------------------------------- /clients/secant/device/.eslintignore: -------------------------------------------------------------------------------- 1 | .eslintrc.js 2 | *.d.ts 3 | # don't ever lint node_modules 4 | node_modules 5 | # don't lint build output (make sure it's set to your correct build folder name) 6 | dist 7 | # don't lint nyc coverage output 8 | coverage 9 | # streams wasm files 10 | wasm-node 11 | **/mocks -------------------------------------------------------------------------------- /clients/secant/device/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | plugins: ['@typescript-eslint', 'prettier'], 5 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'], 6 | rules: { 7 | '@typescript-eslint/no-explicit-any': 'off', 8 | '@typescript-eslint/explicit-module-boundary-types': 'off', 9 | '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], 10 | 'no-async-promise-executor': 'off' 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /clients/secant/device/.gitignore: -------------------------------------------------------------------------------- 1 | DeviceIdentity.json 2 | .env 3 | dist/ 4 | node_modules/ 5 | -------------------------------------------------------------------------------- /clients/secant/device/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "none", 4 | "singleQuote": true, 5 | "printWidth": 150, 6 | "tabWidth": 2, 7 | "useTabs": true 8 | } 9 | -------------------------------------------------------------------------------- /clients/secant/device/README.md: -------------------------------------------------------------------------------- 1 | # Device 2 | Device which creates and identity, authenticates at the api and subscribes to a channel. After it is authorized by the author, it writes payloads to the channel. 3 | 4 | ## Usage 5 | 1. Copy the .env-example and rename it to .env 6 | 2. Set the following environment variables to the appropriate values: 7 | 8 | ``` 9 | BASE_URL=http://localhost:3000/api/v1 10 | API_KEY= 11 | CHANNEL_ADDRESS= 12 | ``` 13 | 14 | 3. Run the device using `npm run serve` 15 | -------------------------------------------------------------------------------- /clients/secant/device/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: ['/src'], 3 | transform: { 4 | '^.+\\.tsx?$': 'ts-jest' 5 | }, 6 | testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$', 7 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'] 8 | }; 9 | -------------------------------------------------------------------------------- /clients/secant/device/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "secant", 3 | "version": "1.0.0", 4 | "description": "Secant client demonstration.", 5 | "main": "index.ts", 6 | "scripts": { 7 | "start": "node dist/index.js", 8 | "serve": "ts-node-dev src/index.ts", 9 | "build": "npm-run-all prettier tsc lint", 10 | "tsc": "tsc", 11 | "lint": "eslint src --ext .js,.jsx,.ts,.tsx", 12 | "prettier": "npx prettier --write ./src", 13 | "test": "jest" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/iotaledger/integration-services.git" 18 | }, 19 | "keywords": [ 20 | "ensuresec", 21 | "audit-log", 22 | "e-commerce", 23 | "iota", 24 | "iota-streams", 25 | "iota-identity" 26 | ], 27 | "author": "Dominic Zettl (IOTA Foundation) ", 28 | "contributors": [ 29 | "Tim Sigl (IOTA Foundation) " 30 | ], 31 | "license": "Apache-2.0", 32 | "bugs": { 33 | "url": "https://github.com/iotaledger/integration-services/issues" 34 | }, 35 | "homepage": "https://github.com/iotaledger/integration-services#readme", 36 | "dependencies": { 37 | "@noble/ed25519": "1.5.1", 38 | "ajv": "^8.8.2", 39 | "ajv-formats": "^2.1.1", 40 | "axios": "^0.24.0", 41 | "bs58": "^4.0.1", 42 | "dotenv": "^10.0.0" 43 | }, 44 | "devDependencies": { 45 | "@types/bs58": "^4.0.1", 46 | "@types/jest": "^26.0.20", 47 | "@typescript-eslint/eslint-plugin": "^4.14.1", 48 | "@typescript-eslint/parser": "^4.14.1", 49 | "eslint": "^7.18.0", 50 | "eslint-config-prettier": "^7.2.0", 51 | "eslint-plugin-prettier": "^3.3.1", 52 | "jest": "^26.6.3", 53 | "npm-run-all": "^4.1.5", 54 | "prettier": "^2.2.1", 55 | "ts-jest": "^26.5.0", 56 | "ts-node-dev": "^1.1.8", 57 | "typescript": "^4.1.3" 58 | } 59 | } -------------------------------------------------------------------------------- /clients/secant/device/src/config/config.ts: -------------------------------------------------------------------------------- 1 | import { Config } from '../models/config.model'; 2 | 3 | export const CONFIG: Config = { 4 | baseUrl: process.env.BASE_URL, 5 | apiKey: process.env.API_KEY 6 | }; 7 | 8 | export const DeviceIdentity = { 9 | username: 'ambulance-sensor-' + Math.ceil(Math.random() * 100000), 10 | claim: { 11 | type: 'Device', 12 | category: ['sensor'], 13 | controlledProperty: ['car'], 14 | controlledAsset: ['ambulance-log', 'patient-data'], 15 | ipAddress: ['192.14.56.78'], 16 | mcc: '214', 17 | mnc: '07', 18 | serialNumber: '9845A', 19 | dateFirstUsed: new Date().toISOString() 20 | } 21 | }; 22 | 23 | export const getChannelAddress = () => { 24 | const channelAddress = process.env.CHANNEL_ADDRESS; 25 | if (channelAddress === '' || !channelAddress) { 26 | console.error('Please create channel and insert channel address in .env !'); 27 | process.exit(); 28 | } 29 | return channelAddress; 30 | }; 31 | -------------------------------------------------------------------------------- /clients/secant/device/src/config/dataset.ts: -------------------------------------------------------------------------------- 1 | export const data1 = { 2 | description: 'Incident received: Left leg broken.', 3 | injured: 1 4 | }; 5 | 6 | export const data2 = { 7 | description: 'Patient picked up by ambulance.' // logged by ambulance device 8 | }; 9 | 10 | export const data3 = { 11 | description: 'Patient arrived at hospital.' // logged by ambulance device 12 | }; 13 | 14 | export const data4 = { 15 | description: 'Operation initiated by doctor.' // logged by ems provider 16 | }; 17 | 18 | export const data5 = { 19 | description: 'Operation successfull.' // logged by 20 | }; 21 | -------------------------------------------------------------------------------- /clients/secant/device/src/index.ts: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); // eslint-disable-line 2 | import { writeChannel } from './services/channel.service'; 3 | import { checkSubscriptionState } from './services/subscription.service'; 4 | 5 | import { setup } from './setup/setup'; 6 | import { data2, data3 } from './config/dataset'; 7 | 8 | const startDevice = async () => { 9 | console.log('Device demo started...'); 10 | console.log('--------------------------------------------------------'); 11 | console.log('--------------------------------------------------------'); 12 | const { id, channelAddress } = await setup(); 13 | await checkSubscriptionState(channelAddress, id); 14 | 15 | await writeChannel({ ...data2 }, 'ambulanceUpdate'); 16 | await writeChannel({ ...data3 }, 'ambulanceUpdate'); 17 | console.log('Device demo finished :)'); 18 | }; 19 | 20 | startDevice(); 21 | -------------------------------------------------------------------------------- /clients/secant/device/src/models/config.model.ts: -------------------------------------------------------------------------------- 1 | export interface Config { 2 | baseUrl: string; 3 | apiKey: string; 4 | } 5 | -------------------------------------------------------------------------------- /clients/secant/device/src/services/authentication.service.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import { CONFIG } from '../config/config'; 4 | import { getHexEncodedKey, signNonce } from '../utils/encryption'; 5 | import { axiosClient } from '../utils/client'; 6 | 7 | export const fetchAuth = async (): Promise => { 8 | const identityPath = path.join(__dirname, '..', 'config', 'DeviceIdentity.json'); 9 | let file, identity; 10 | try { 11 | file = fs.readFileSync(identityPath); 12 | identity = file && JSON.parse(file.toString()); 13 | } catch (e) { 14 | console.log('error when reading file'); 15 | } 16 | 17 | const apiKey = CONFIG.apiKey ? `?api-key=${CONFIG.apiKey}` : ''; 18 | 19 | if ((identity as any)?.id == null) { 20 | throw new Error('no identity found'); 21 | } 22 | 23 | const res = await axiosClient.get(`${CONFIG.baseUrl}/api/v0.2/authentication/prove-ownership/${identity.id}${apiKey}`); 24 | if (res.status !== 200) { 25 | console.error('Didnt receive status 200 on get request for prove-ownership!'); 26 | return; 27 | } 28 | const body = await res.data; 29 | const nonce: string = body.nonce; 30 | 31 | const encodedKey = await getHexEncodedKey(identity.keys.sign.private); 32 | const signedNonce = await signNonce(encodedKey, nonce); 33 | const response = await axiosClient.post( 34 | `${CONFIG.baseUrl}/api/v0.2/authentication/prove-ownership/${identity.id}${apiKey}`, 35 | JSON.stringify({ signedNonce }), 36 | { 37 | method: 'post', 38 | headers: { 'Content-Type': 'application/json' } 39 | } 40 | ); 41 | if (response?.status === 200) { 42 | console.log('Successfully authenticated!'); 43 | } 44 | 45 | return response; 46 | }; 47 | -------------------------------------------------------------------------------- /clients/secant/device/src/services/channel.service.ts: -------------------------------------------------------------------------------- 1 | import { CONFIG, getChannelAddress } from '../config/config'; 2 | import { axiosClient } from '../utils/client'; 3 | 4 | export const writeChannel = async (payload: any, type: string) => { 5 | try { 6 | console.log(`Writing ${type} to dlt...`); 7 | const apiKey = CONFIG.apiKey ? `?api-key=${CONFIG.apiKey}` : ''; 8 | const channelAddress = getChannelAddress(); 9 | const body = { 10 | type, 11 | payload 12 | }; 13 | 14 | const response = await axiosClient.post(`${CONFIG.baseUrl}/api/v0.1/channels/logs/${channelAddress}${apiKey}`, body); 15 | 16 | if (response?.status === 200) { 17 | console.log('Successfully written to dlt!'); 18 | console.log('--------------------------------------------------------'); 19 | console.log('--------------------------------------------------------'); 20 | } 21 | } catch (error) { 22 | console.log('Could not write dlt'); 23 | console.log(error); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /clients/secant/device/src/services/identity.serivce.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import { CONFIG, DeviceIdentity } from '../config/config'; 4 | import { axiosClient } from '../utils/client'; 5 | 6 | export const createIdentity = async (): Promise => { 7 | const identityPath = path.join(__dirname, '..', 'config', 'DeviceIdentity.json'); 8 | let file, identity; 9 | try { 10 | file = fs.readFileSync(identityPath); 11 | identity = file && JSON.parse(file.toString()); 12 | } catch (e) { 13 | console.log('no identity file found'); 14 | } 15 | 16 | if (identity?.id != null) { 17 | console.log('Identity already created!'); 18 | return identity?.id; 19 | } 20 | console.log('Creating the device identity...'); 21 | const apiKey = CONFIG.apiKey ? `?api-key=${CONFIG.apiKey}` : ''; 22 | 23 | const res = await axiosClient.post(`${CONFIG.baseUrl}/api/v0.2/identities/create${apiKey}`, JSON.stringify(DeviceIdentity)); 24 | 25 | if (res?.status === 201) { 26 | console.log('Successfully created the identity!'); 27 | console.log('###########################'); 28 | console.log('###########################'); 29 | const configPath = path.join(__dirname, '..', 'config'); 30 | if (!fs.existsSync(configPath)) fs.mkdirSync(configPath); 31 | fs.writeFileSync(identityPath, JSON.stringify(res.data)); 32 | 33 | return res.data.id; 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /clients/secant/device/src/services/subscription.service.ts: -------------------------------------------------------------------------------- 1 | import { CONFIG } from '../config/config'; 2 | import { axiosClient } from '../utils/client'; 3 | 4 | export const checkSubscriptionState = async (channelAddress: string, id: string) => { 5 | console.log('Checking subscription state...'); 6 | const apiKey = CONFIG.apiKey ? `?api-key=${CONFIG.apiKey}` : ''; 7 | const res = await axiosClient.get(`${CONFIG.baseUrl}/api/v0.1/subscriptions/${channelAddress}/${id}${apiKey}`); 8 | 9 | if (res?.status === 200) { 10 | if (res.data === '') { 11 | await requestSubscription(channelAddress); 12 | console.log(`Subscription requested. Please authorize via identity id: ${id}`); 13 | process.exit(); 14 | } else if (!res.data.isAuthorized) { 15 | console.log(`Subscription already requested. Please authorize via identity id: ${id}`); 16 | process.exit(); 17 | } else if (res.data.isAuthorized) { 18 | console.log('Subscription authorized!'); 19 | console.log('--------------------------------------------------------'); 20 | console.log('--------------------------------------------------------'); 21 | } 22 | } 23 | }; 24 | 25 | export const requestSubscription = async (channelAddress: string): Promise => { 26 | const apiKey = CONFIG.apiKey ? `?api-key=${CONFIG.apiKey}` : ''; 27 | const body = { 28 | accessRights: 'ReadAndWrite' 29 | }; 30 | console.log('Requesting subscription...'); 31 | const res = await axiosClient.post(`${CONFIG.baseUrl}/api/v0.1/subscriptions/request/${channelAddress}${apiKey}`, JSON.stringify(body)); 32 | 33 | if (res?.status === 201) { 34 | return res.data.subscriptionLink; 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /clients/secant/device/src/setup/setup.ts: -------------------------------------------------------------------------------- 1 | import { createIdentity } from '../services/identity.serivce'; 2 | 3 | import { getChannelAddress } from '../config/config'; 4 | 5 | export const setup = async () => { 6 | const id = await createIdentity(); 7 | const channelAddress = getChannelAddress(); 8 | return { id, channelAddress }; 9 | }; 10 | -------------------------------------------------------------------------------- /clients/secant/device/src/utils/client.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { fetchAuth } from '../services/authentication.service'; 3 | 4 | const getBearerToken = async () => { 5 | const response = await fetchAuth(); 6 | if (response.status === 200) { 7 | const bearerToken = 'Bearer ' + response.data?.jwt; 8 | return bearerToken; 9 | } 10 | }; 11 | 12 | const errFunc = async (error: any) => { 13 | const originalRequest = error.config; 14 | if (error?.response?.status === 401 && !originalRequest._retry) { 15 | originalRequest._retry = true; 16 | const token = await getBearerToken(); 17 | axiosClient.defaults.headers.common['Authorization'] = token; 18 | originalRequest.headers['Authorization'] = token; 19 | return axios(originalRequest); 20 | } else { 21 | if (error?.response?.data?.error) { 22 | console.log(`ERROR: ${error.response.data.error}`); 23 | } else if (error?.response?.status) { 24 | console.log(`ERROR: ${error?.response?.status}`, error); 25 | } else { 26 | console.log(`ERROR:`, error); 27 | } 28 | } 29 | }; 30 | 31 | export const axiosClient = axios.create({ 32 | headers: { 33 | 'Content-Type': 'application/json' 34 | } 35 | }); 36 | 37 | axiosClient.interceptors.response.use( 38 | (response) => response, 39 | (error) => errFunc(error) 40 | ); 41 | -------------------------------------------------------------------------------- /clients/secant/device/tsconfig.dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "es2015", "es2015.iterable"], 5 | "esModuleInterop": true, 6 | "module": "commonjs", 7 | "moduleResolution": "node", 8 | "noUnusedLocals": false, 9 | "typeRoots": ["node_modules/@types", "types"], 10 | "outDir": "dist", 11 | "resolveJsonModule": true 12 | }, 13 | "include": ["src"] 14 | } 15 | -------------------------------------------------------------------------------- /clients/secant/device/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "noImplicitAny": true, 5 | "lib": ["dom", "es2015", "es2015.iterable"], 6 | "esModuleInterop": true, 7 | "module": "commonjs", 8 | "moduleResolution": "node", 9 | "noUnusedLocals": true, 10 | "typeRoots": ["node_modules/@types", "types"], 11 | "outDir": "dist", 12 | "resolveJsonModule": true, 13 | "allowJs": true 14 | }, 15 | "include": ["src"] 16 | } 17 | -------------------------------------------------------------------------------- /clients/secant/ems-operator/.env-example: -------------------------------------------------------------------------------- 1 | BASE_URL=http://localhost:3000 2 | CHANNEL_ADDRESS= 3 | API_KEY= -------------------------------------------------------------------------------- /clients/secant/ems-operator/.eslintignore: -------------------------------------------------------------------------------- 1 | .eslintrc.js 2 | *.d.ts 3 | # don't ever lint node_modules 4 | node_modules 5 | # don't lint build output (make sure it's set to your correct build folder name) 6 | dist 7 | # don't lint nyc coverage output 8 | coverage 9 | # streams wasm files 10 | wasm-node 11 | **/mocks -------------------------------------------------------------------------------- /clients/secant/ems-operator/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | plugins: ['@typescript-eslint', 'prettier'], 5 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'], 6 | rules: { 7 | '@typescript-eslint/no-explicit-any': 'off', 8 | '@typescript-eslint/explicit-module-boundary-types': 'off', 9 | '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], 10 | 'no-async-promise-executor': 'off' 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /clients/secant/ems-operator/.gitignore: -------------------------------------------------------------------------------- 1 | DoctorIdentity.json 2 | DeviceIdentity.json 3 | .env 4 | dist/ 5 | node_modules/ 6 | Channel.json -------------------------------------------------------------------------------- /clients/secant/ems-operator/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "none", 4 | "singleQuote": true, 5 | "printWidth": 150, 6 | "tabWidth": 2, 7 | "useTabs": true 8 | } 9 | -------------------------------------------------------------------------------- /clients/secant/ems-operator/README.md: -------------------------------------------------------------------------------- 1 | # Device 2 | Device which creates and identity, authenticates at the api and subscribes to a channel. After it is authorized by the author, it writes payloads to the channel. 3 | 4 | ## Usage 5 | 1. Copy the .env-example and rename it to .env 6 | 2. Set the following environment variables to the appropriate values: 7 | 8 | ``` 9 | BASE_URL=http://localhost:3000/api/v1 10 | API_KEY= 11 | CHANNEL_ADDRESS= 12 | ``` 13 | 14 | 3. Run the device using `npm run serve` 15 | -------------------------------------------------------------------------------- /clients/secant/ems-operator/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: ['/src'], 3 | transform: { 4 | '^.+\\.tsx?$': 'ts-jest' 5 | }, 6 | testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$', 7 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'] 8 | }; 9 | -------------------------------------------------------------------------------- /clients/secant/ems-operator/src/config/config.ts: -------------------------------------------------------------------------------- 1 | import { Config } from '../models/config.model'; 2 | 3 | export const CONFIG: Config = { 4 | baseUrl: process.env.BASE_URL, 5 | apiKey: process.env.API_KEY 6 | }; 7 | 8 | export const ChannelConfig = { 9 | name: 'patient-logs-' + Math.ceil(Math.random() * 100000), 10 | topics: [ 11 | { type: 'operationUpdate', source: 'ems-device' }, 12 | { type: 'ambulanceUpdate', source: 'ambulance-device' } 13 | ], 14 | description: 'Logs of the incident.' 15 | }; 16 | 17 | export const EmsOperatorIdentity = { 18 | username: 'ems-operator' + Math.ceil(Math.random() * 100000), 19 | claim: { 20 | type: 'Person', 21 | name: 'Peter Peters', 22 | familyName: 'Peters', 23 | givenName: 'Peter', 24 | jobTitle: 'Doctor' 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /clients/secant/ems-operator/src/config/dataset.ts: -------------------------------------------------------------------------------- 1 | export const data1 = { 2 | description: 'Incident received: Left leg broken.', 3 | injured: 1 4 | }; 5 | 6 | export const data4 = { 7 | description: 'Operation initiated by doctor.' // logged by ems provider 8 | }; 9 | 10 | export const data5 = { 11 | description: 'Operation successfull.' // logged by 12 | }; 13 | -------------------------------------------------------------------------------- /clients/secant/ems-operator/src/create-stream-channel/index.ts: -------------------------------------------------------------------------------- 1 | import * as dotenv from 'dotenv'; 2 | dotenv.config(); 3 | import fs from 'fs'; 4 | 5 | import { ChannelConfig, CONFIG as Config } from '../config/config'; 6 | import { axiosClient } from '../utils/client'; 7 | 8 | export const createStreamChannel = async (): Promise => { 9 | const apiKey = Config.apiKey ? `?api-key=${Config.apiKey}` : ''; 10 | const res = await axiosClient.post(`${Config.baseUrl}/api/v0.1/channels/create${apiKey}`, JSON.stringify(ChannelConfig)); 11 | if (res?.status === 201) { 12 | console.log('successfully created channel!'); 13 | console.log('###########################'); 14 | console.log(`Channel address: ${res.data.channelAddress}`); 15 | fs.writeFileSync('./src/config/Channel.json', JSON.stringify(res.data)); 16 | return res.data.channelAddress; 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /clients/secant/ems-operator/src/create-stream-channel/run.ts: -------------------------------------------------------------------------------- 1 | import { createStreamChannel } from '.'; 2 | 3 | const run = () => { 4 | createStreamChannel(); 5 | }; 6 | 7 | run(); 8 | -------------------------------------------------------------------------------- /clients/secant/ems-operator/src/index.ts: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); // eslint-disable-line 2 | import { writeChannel } from './services/channel.service'; 3 | import { createDoctorIdentity } from './services/identity.service'; 4 | import { createStreamChannel } from './create-stream-channel/index'; 5 | import { data1 } from './config/dataset'; 6 | 7 | const startDevice = async () => { 8 | const id = await createDoctorIdentity(); 9 | const channelAddress = await createStreamChannel(); 10 | await writeChannel(channelAddress, { ...data1, id }, 'initialize'); 11 | console.log('Device demo finished :)'); 12 | }; 13 | 14 | startDevice(); 15 | -------------------------------------------------------------------------------- /clients/secant/ems-operator/src/models/config.model.ts: -------------------------------------------------------------------------------- 1 | export interface Config { 2 | baseUrl: string; 3 | apiKey: string; 4 | } 5 | -------------------------------------------------------------------------------- /clients/secant/ems-operator/src/services/authentication.service.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import { CONFIG } from '../config/config'; 4 | import { getHexEncodedKey, signNonce } from '../utils/encryption'; 5 | import { axiosClient } from '../utils/client'; 6 | 7 | export const fetchAuth = async (): Promise => { 8 | const identityPath = path.join(__dirname, '..', 'config', 'DoctorIdentity.json'); 9 | let file, identity; 10 | try { 11 | file = fs.readFileSync(identityPath); 12 | identity = file && JSON.parse(file.toString()); 13 | } catch (e) { 14 | console.log('error when reading file'); 15 | } 16 | 17 | const apiKey = CONFIG.apiKey ? `?api-key=${CONFIG.apiKey}` : ''; 18 | 19 | if ((identity as any)?.id == null) { 20 | throw new Error('no identity found'); 21 | } 22 | 23 | const res = await axiosClient.get(`${CONFIG.baseUrl}/api/v0.2/authentication/prove-ownership/${identity.id}${apiKey}`); 24 | if (res.status !== 200) { 25 | console.error('Didnt receive status 200 on get request for prove-ownership!'); 26 | return; 27 | } 28 | const body = await res.data; 29 | const nonce: string = body.nonce; 30 | 31 | const encodedKey = await getHexEncodedKey(identity.keys.sign.private); 32 | const signedNonce = await signNonce(encodedKey, nonce); 33 | const response = await axiosClient.post( 34 | `${CONFIG.baseUrl}/api/v0.2/authentication/prove-ownership/${identity.id}${apiKey}`, 35 | JSON.stringify({ signedNonce }), 36 | { 37 | method: 'post', 38 | headers: { 'Content-Type': 'application/json' } 39 | } 40 | ); 41 | if (response?.status === 200) { 42 | console.log('Successfully authenticated!'); 43 | } 44 | 45 | return response; 46 | }; 47 | -------------------------------------------------------------------------------- /clients/secant/ems-operator/src/services/channel.service.ts: -------------------------------------------------------------------------------- 1 | import { CONFIG } from '../config/config'; 2 | import { axiosClient } from '../utils/client'; 3 | import * as fs from 'fs'; 4 | import path from 'path'; 5 | 6 | export const getChannelAddress = () => { 7 | const channelPath = path.join(__dirname, '..', 'config', 'Channel.json'); 8 | let file, channel; 9 | try { 10 | file = fs.readFileSync(channelPath); 11 | channel = file && JSON.parse(file.toString()); 12 | } catch (e) { 13 | console.log('no identity file found'); 14 | } 15 | return channel.channelAddress; 16 | }; 17 | 18 | export const writeChannel = async (channelAddress: string, payload: any, type: string) => { 19 | try { 20 | console.log(`Writing ${type} to dlt...`); 21 | const apiKey = CONFIG.apiKey ? `?api-key=${CONFIG.apiKey}` : ''; 22 | const channelAddress = getChannelAddress(); 23 | const body = { 24 | type, 25 | payload 26 | }; 27 | 28 | const response = await axiosClient.post(`${CONFIG.baseUrl}/api/v0.1/channels/logs/${channelAddress}${apiKey}`, body); 29 | 30 | if (response?.status === 200) { 31 | console.log('Successfully written to dlt!'); 32 | console.log('--------------------------------------------------------'); 33 | console.log('--------------------------------------------------------'); 34 | } 35 | } catch (error) { 36 | console.log('Could not write dlt'); 37 | console.log(error); 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /clients/secant/ems-operator/src/services/identity.service.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import { CONFIG, EmsOperatorIdentity } from '../config/config'; 4 | import { axiosClient } from '../utils/client'; 5 | 6 | export const createDoctorIdentity = async (): Promise => { 7 | const identityPath = path.join(__dirname, '..', 'config', 'DoctorIdentity.json'); 8 | let file, identity; 9 | try { 10 | file = fs.readFileSync(identityPath); 11 | identity = file && JSON.parse(file.toString()); 12 | } catch (e) { 13 | console.log('no identity file found'); 14 | } 15 | 16 | if (identity?.id != null) { 17 | console.log('Identity already created!'); 18 | return identity?.id; 19 | } 20 | console.log('Creating the doctor identity...'); 21 | const apiKey = CONFIG.apiKey ? `?api-key=${CONFIG.apiKey}` : ''; 22 | 23 | const res = await axiosClient.post(`${CONFIG.baseUrl}/api/v0.2/identities/create${apiKey}`, JSON.stringify(EmsOperatorIdentity)); 24 | 25 | if (res?.status === 201) { 26 | console.log('Successfully created the identity!'); 27 | console.log('###########################'); 28 | console.log('###########################'); 29 | const configPath = path.join(__dirname, '..', 'config'); 30 | if (!fs.existsSync(configPath)) fs.mkdirSync(configPath); 31 | fs.writeFileSync(identityPath, JSON.stringify(res.data)); 32 | 33 | return res.data.id; 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /clients/secant/ems-operator/src/setup/setup-ems.ts: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); // eslint-disable-line 2 | import { createDoctorIdentity } from '../services/identity.service'; 3 | import { createStreamChannel } from '../create-stream-channel/index'; 4 | import { writeChannel } from '../services/channel.service'; 5 | import { data1 } from '../config/dataset'; 6 | 7 | export const setupEmsOperator = async () => { 8 | const id = await createDoctorIdentity(); 9 | const channelAddress = await createStreamChannel(); 10 | 11 | await writeChannel(channelAddress, { ...data1 }, 'init'); 12 | return { id, channelAddress }; 13 | }; 14 | 15 | setupEmsOperator(); 16 | -------------------------------------------------------------------------------- /clients/secant/ems-operator/src/setup/write-messages.ts: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); // eslint-disable-line 2 | import { createDoctorIdentity } from '../services/identity.service'; 3 | import { data4, data5 } from '../config/dataset'; 4 | import { writeChannel, getChannelAddress } from '../services/channel.service'; 5 | 6 | export const writeMessages = async () => { 7 | const id = await createDoctorIdentity(); 8 | const channelAddress = await getChannelAddress(); 9 | console.log('channelAddress', channelAddress); 10 | await writeChannel(channelAddress, { ...data4 }, 'operationUpdate'); 11 | await writeChannel(channelAddress, { ...data5 }, 'operationUpdate'); 12 | return { id, channelAddress }; 13 | }; 14 | 15 | writeMessages(); 16 | -------------------------------------------------------------------------------- /clients/secant/ems-operator/src/utils/client.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { fetchAuth } from '../services/authentication.service'; 3 | 4 | const getBearerToken = async () => { 5 | const response = await fetchAuth(); 6 | if (response.status === 200) { 7 | const bearerToken = 'Bearer ' + response.data?.jwt; 8 | return bearerToken; 9 | } 10 | }; 11 | 12 | const errFunc = async (error: any) => { 13 | const originalRequest = error.config; 14 | if (error?.response?.status === 401 && !originalRequest._retry) { 15 | originalRequest._retry = true; 16 | const token = await getBearerToken(); 17 | axiosClient.defaults.headers.common['Authorization'] = token; 18 | originalRequest.headers['Authorization'] = token; 19 | return axios(originalRequest); 20 | } else { 21 | if (error?.response?.data?.error) { 22 | console.log(`ERROR: ${error.response.data.error}`); 23 | } else if (error?.response?.status) { 24 | console.log(`ERROR: ${error?.response?.status}`, error); 25 | } else { 26 | console.log(`ERROR:`, error); 27 | } 28 | } 29 | }; 30 | 31 | export const axiosClient = axios.create({ 32 | headers: { 33 | 'Content-Type': 'application/json' 34 | } 35 | }); 36 | 37 | axiosClient.interceptors.response.use( 38 | (response) => response, 39 | (error) => errFunc(error) 40 | ); 41 | -------------------------------------------------------------------------------- /clients/secant/ems-operator/tsconfig.dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "es2015", "es2015.iterable"], 5 | "esModuleInterop": true, 6 | "module": "commonjs", 7 | "moduleResolution": "node", 8 | "noUnusedLocals": false, 9 | "typeRoots": ["node_modules/@types", "types"], 10 | "outDir": "dist", 11 | "resolveJsonModule": true 12 | }, 13 | "include": ["src"] 14 | } 15 | -------------------------------------------------------------------------------- /clients/secant/ems-operator/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "noImplicitAny": true, 5 | "lib": ["dom", "es2015", "es2015.iterable"], 6 | "esModuleInterop": true, 7 | "module": "commonjs", 8 | "moduleResolution": "node", 9 | "noUnusedLocals": true, 10 | "typeRoots": ["node_modules/@types", "types"], 11 | "outDir": "dist", 12 | "resolveJsonModule": true, 13 | "allowJs": true 14 | }, 15 | "include": ["src"] 16 | } 17 | -------------------------------------------------------------------------------- /dashboard/.dockerignore: -------------------------------------------------------------------------------- 1 | .env 2 | dist 3 | node_modules 4 | .svelte-kit -------------------------------------------------------------------------------- /dashboard/.env.example: -------------------------------------------------------------------------------- 1 | VITE_IOTA_IS_SDK_API_KEY= 2 | VITE_IOTA_IS_SDK_GATEWAY_URL= 3 | 4 | # set these if you want to avoid the gateway but connect to the services directly (for local development) 5 | #VITE_IOTA_SSI_BRIDGE_URL=http://localhost:3001 6 | #VITE_IOTA_AUDIT_TRAIL_URL=http://localhost:3002 7 | #VITE_IOTA_USE_GATEWAY_URL=false # if not set it will use the gateway url 8 | 9 | VITE_NETWORK_EXPLORER=https://explorer.iota.org/mainnet/message/ -------------------------------------------------------------------------------- /dashboard/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'], 5 | plugins: ['svelte3', '@typescript-eslint'], 6 | ignorePatterns: ['*.cjs'], 7 | overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }], 8 | settings: { 9 | 'svelte3/typescript': () => require('typescript') 10 | }, 11 | parserOptions: { 12 | sourceType: 'module', 13 | ecmaVersion: 2020 14 | }, 15 | env: { 16 | browser: true, 17 | es2017: true, 18 | node: true 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /dashboard/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | -------------------------------------------------------------------------------- /dashboard/.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /dashboard/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "none", 4 | "singleQuote": true, 5 | "printWidth": 140, 6 | "tabWidth": 2, 7 | "useTabs": true 8 | } 9 | -------------------------------------------------------------------------------- /dashboard/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ### v0.2.0 4 | 5 | - Updated `@iota/is-ui-components` to `v0.2.1` including upgrade to identity __v0.6__ 6 | - Breaking change to old identities created with the identity kit smaller than __v0.6__ these are no more compatible 7 | -------------------------------------------------------------------------------- /dashboard/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16.14.2 2 | 3 | WORKDIR /usr/src/app 4 | 5 | COPY package*.json ./ 6 | 7 | COPY . . 8 | 9 | RUN npm install 10 | -------------------------------------------------------------------------------- /dashboard/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dashboard", 3 | "version": "0.2.2", 4 | "scripts": { 5 | "prod": "node build/index.js", 6 | "dev": "svelte-kit dev --port ${PORT:-3055}", 7 | "build": "svelte-kit build", 8 | "package": "svelte-kit package", 9 | "preview": "svelte-kit preview --host=0.0.0.0", 10 | "prepare": "svelte-kit sync", 11 | "check": "svelte-check --tsconfig ./tsconfig.json", 12 | "check:watch": "svelte-check --tsconfig ./tsconfig.json --watch", 13 | "lint": "prettier --ignore-path .gitignore --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .", 14 | "format": "prettier --ignore-path .gitignore --write --plugin-search-dir=. ." 15 | }, 16 | "devDependencies": { 17 | "@sveltejs/adapter-auto": "next", 18 | "@sveltejs/kit": "next", 19 | "@typescript-eslint/eslint-plugin": "^5.10.1", 20 | "@typescript-eslint/parser": "^5.10.1", 21 | "eslint": "^7.32.0", 22 | "eslint-config-prettier": "^8.3.0", 23 | "eslint-plugin-svelte3": "^3.2.1", 24 | "node-sass": "^7.0.1", 25 | "prettier": "^2.5.1", 26 | "prettier-plugin-svelte": "^2.5.0", 27 | "sass": "^1.50.1", 28 | "svelte": "^3.49.0", 29 | "svelte-check": "^2.2.6", 30 | "svelte-preprocess": "^4.10.6", 31 | "tslib": "^2.3.1", 32 | "typescript": "~4.6.2" 33 | }, 34 | "type": "module", 35 | "dependencies": { 36 | "@iota/is-ui-components": "^0.2.8", 37 | "@sveltejs/adapter-node": "^1.0.0-next.73", 38 | "sveltestrap": "^5.9.0" 39 | } 40 | } -------------------------------------------------------------------------------- /dashboard/src/app.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // See https://kit.svelte.dev/docs/types#the-app-namespace 4 | // for information about these interfaces 5 | declare namespace App { 6 | // interface Locals {} 7 | // interface Platform {} 8 | // interface Session {} 9 | // interface Stuff {} 10 | } 11 | -------------------------------------------------------------------------------- /dashboard/src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | %svelte.head% 9 | 10 | 11 |
%svelte.body%
12 | 13 | 14 | -------------------------------------------------------------------------------- /dashboard/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger-archive/integration-services/be1bd31390d74d30fcd2cbd7ae5de53de27b028e/dashboard/src/assets/logo.png -------------------------------------------------------------------------------- /dashboard/src/routes/history/[channelAddress].svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 22 | 23 | 24 | History 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /dashboard/src/routes/history/index.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 9 | 10 | 11 | History 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /dashboard/src/routes/identity-manager/index.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 9 | 10 | 11 | Identity Manager 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /dashboard/src/routes/index.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 11 | 12 | 13 | Login / Register 14 | 15 | 16 | 17 | 18 | 19 | goto('/identity-manager')} /> 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /dashboard/src/routes/streams-manager/index.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 9 | 10 | 11 | Streams Manager 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /dashboard/src/routes/verify-credential.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 9 | 10 | 11 | Verify Credential 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /dashboard/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger-archive/integration-services/be1bd31390d74d30fcd2cbd7ae5de53de27b028e/dashboard/static/favicon.png -------------------------------------------------------------------------------- /dashboard/svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-node'; 2 | import preprocess from 'svelte-preprocess'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | // Consult https://github.com/sveltejs/svelte-preprocess 7 | // for more information about preprocessors 8 | preprocess: preprocess(), 9 | 10 | kit: { 11 | adapter: adapter() 12 | } 13 | }; 14 | 15 | export default config; 16 | -------------------------------------------------------------------------------- /dashboard/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /kubernetes/init-ssi-bridge.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: Job 3 | metadata: 4 | name: generate-key 5 | spec: 6 | backoffLimit: 50 7 | template: 8 | spec: 9 | restartPolicy: OnFailure 10 | containers: 11 | - name: create-root-identity 12 | image: iotaledger/ssi-bridge:develop 13 | imagePullPolicy: Always 14 | command: ["node", "dist/index.js", "setup-api"] 15 | env: 16 | - name: "DATABASE_NAME" 17 | valueFrom: 18 | configMapKeyRef: 19 | name: integration-service-api-config 20 | key: DATABASE_NAME 21 | - name: "DATABASE_URL" 22 | valueFrom: 23 | configMapKeyRef: 24 | name: integration-service-api-config 25 | key: DATABASE_URL 26 | - name: "IOTA_HORNET_NODE" 27 | valueFrom: 28 | configMapKeyRef: 29 | name: integration-service-api-config 30 | key: IOTA_HORNET_NODE 31 | - name: "IOTA_PERMA_NODE" 32 | valueFrom: 33 | configMapKeyRef: 34 | name: integration-service-api-config 35 | key: IOTA_PERMA_NODE 36 | - name: "SERVER_SECRET" 37 | valueFrom: 38 | secretKeyRef: 39 | name: integration-service-api-secrets 40 | key: SERVER_SECRET 41 | - name: "JWT_SECRET" 42 | valueFrom: 43 | secretKeyRef: 44 | name: integration-service-api-secrets 45 | key: JWT_SECRET 46 | -------------------------------------------------------------------------------- /kubernetes/is-config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: integration-service-api-config 5 | data: 6 | PORT: "3000" 7 | DATABASE_NAME: integration-service 8 | IOTA_PERMA_NODE: https://chrysalis-chronicle.iota.org/api/mainnet/ 9 | IOTA_HORNET_NODE: https://chrysalis-nodes.iota.org:443 10 | SSI_BRIDGE_URL: http://ssi-bridge:3000/api/v0.2 11 | DATABASE_URL: mongodb://username:password@mongodb-service.default.svc.cluster.local:27017/integration-service?appname=integration-service-api&ssl=false 12 | -------------------------------------------------------------------------------- /kubernetes/is-dashboard-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: is-dashboard 5 | spec: 6 | replicas: 1 7 | revisionHistoryLimit: 1 8 | selector: 9 | matchLabels: 10 | app: is-dashboard 11 | template: 12 | metadata: 13 | labels: 14 | app: is-dashboard 15 | spec: 16 | containers: 17 | - name: is-dashboard 18 | image: iotaledger/is-dashboard:develop 19 | imagePullPolicy: Always 20 | command: ['sh', '-c', 'npm run build && npm run prod'] 21 | resources: 22 | requests: 23 | memory: "1Gi" 24 | cpu: "500m" 25 | limits: 26 | memory: "1Gi" 27 | cpu: "500m" 28 | env: 29 | - name: "VITE_IOTA_IS_SDK_API_KEY" 30 | valueFrom: 31 | secretKeyRef: 32 | name: integration-service-api-secrets 33 | key: API_KEY 34 | - name: "VITE_IOTA_IS_SDK_GATEWAY_URL" 35 | value: "" 36 | ports: 37 | - protocol: TCP 38 | containerPort: 3000 39 | -------------------------------------------------------------------------------- /kubernetes/is-secrets.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: integration-service-api-secrets 5 | type: Opaque 6 | stringData: 7 | SERVER_SECRET: 7w9gfhb123jngh4gd53z465fewcs569e 8 | JWT_SECRET: 7w9gfhb123jngh4gd53z465fewcs569e 9 | API_KEY: 4ed59704-9a26-11ec-a749-3f57454709b9 10 | SSI_BRIDGE_API_KEY: 4ed59704-9a26-11ec-a749-3f57454709b9 11 | 12 | -------------------------------------------------------------------------------- /kubernetes/is-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: audit-trail-gw 5 | spec: 6 | selector: 7 | app: audit-trail-gw 8 | ports: 9 | - protocol: TCP 10 | port: 3000 11 | targetPort: 3000 12 | --- 13 | apiVersion: v1 14 | kind: Service 15 | metadata: 16 | name: ssi-bridge 17 | spec: 18 | selector: 19 | app: ssi-bridge 20 | ports: 21 | - protocol: TCP 22 | port: 3000 23 | targetPort: 3000 24 | --- 25 | apiVersion: v1 26 | kind: Service 27 | metadata: 28 | name: is-dashboard 29 | spec: 30 | selector: 31 | app: is-dashboard 32 | ports: 33 | - protocol: TCP 34 | port: 3000 35 | targetPort: 3000 -------------------------------------------------------------------------------- /kubernetes/kong-gw/rate-limiting.yml: -------------------------------------------------------------------------------- 1 | apiVersion: configuration.konghq.com/v1 2 | kind: KongClusterPlugin 3 | metadata: 4 | name: rl-by-ip 5 | annotations: 6 | kubernetes.io/ingress.class: kong 7 | labels: 8 | global: "true" 9 | config: 10 | minute: 70 11 | hour: 10000 12 | policy: local 13 | fault_tolerant: true 14 | hide_client_headers: false 15 | redis_ssl: false 16 | redis_ssl_verify: false 17 | plugin: rate-limiting -------------------------------------------------------------------------------- /kubernetes/optional/mongo-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: mongodb-deployment 5 | labels: 6 | app: mongodb 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: mongodb 12 | template: 13 | metadata: 14 | labels: 15 | app: mongodb 16 | spec: 17 | containers: 18 | - name: mongodb 19 | image: mongo:5.0.2 20 | ports: 21 | - containerPort: 27017 22 | volumeMounts: 23 | - name: db 24 | mountPath: /data/db 25 | - name: mongo-init 26 | mountPath: /docker-entrypoint-initdb.d 27 | env: 28 | - name: MONGO_INITDB_DATABASE 29 | value: "integration-service" 30 | - name: MONGO_INITDB_ROOT_USERNAME 31 | valueFrom: 32 | secretKeyRef: 33 | name: mongodb-secret 34 | key: mongo-root-username 35 | - name: MONGO_INITDB_ROOT_PASSWORD 36 | valueFrom: 37 | secretKeyRef: 38 | name: mongodb-secret 39 | key: mongo-root-password 40 | volumes: 41 | - name: db 42 | emptyDir: {} 43 | - name: mongo-init 44 | configMap: 45 | name: init-mongo-cfg 46 | items: 47 | - key: mongo-init.js 48 | path: mongo-init.js 49 | 50 | 51 | -------------------------------------------------------------------------------- /kubernetes/optional/mongo-init-config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: init-mongo-cfg 5 | data: 6 | mongo-init.js: | 7 | db.createUser( 8 | { 9 | user: "username", 10 | pwd: "password", 11 | roles: [ 12 | { 13 | role: "readWrite", 14 | db: "integration-service" 15 | } 16 | ] 17 | } 18 | ); 19 | -------------------------------------------------------------------------------- /kubernetes/optional/mongo-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: mongodb-secret 5 | type: Opaque 6 | stringData: 7 | mongo-root-username: root 8 | mongo-root-password: rootpassword 9 | -------------------------------------------------------------------------------- /kubernetes/optional/mongo-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: mongodb-service 5 | spec: 6 | selector: 7 | app: mongodb 8 | ports: 9 | - protocol: TCP 10 | port: 27017 11 | targetPort: 27017 12 | -------------------------------------------------------------------------------- /mongo-init.js.example: -------------------------------------------------------------------------------- 1 | db.createUser( 2 | { 3 | user: "", 4 | pwd: "", 5 | roles: [ 6 | { 7 | role: "readWrite", 8 | db: "" 9 | } 10 | ] 11 | } 12 | ); --------------------------------------------------------------------------------