diff --git a/.github/workflows/canister-tests.yml b/.github/workflows/canister-tests.yml index a1250b5851..7ba24d54a3 100644 --- a/.github/workflows/canister-tests.yml +++ b/.github/workflows/canister-tests.yml @@ -337,12 +337,6 @@ jobs: steps: - uses: actions/checkout@v3 - # Required by the ic-test-state-machine - - name: Install openssl (macos) - if: ${{ matrix.os == 'macos-latest' }} - run: | - brew install openssl@3 - - name: Download nextest run: | set -euo pipefail diff --git a/.github/workflows/release-build-check.yml b/.github/workflows/release-build-check.yml index e0e72218bc..e80c4dfe6e 100644 --- a/.github/workflows/release-build-check.yml +++ b/.github/workflows/release-build-check.yml @@ -17,8 +17,8 @@ jobs: latest-release: outputs: ref: ${{ steps.release.outputs.ref }} - prod_sha256: ${{ steps.release.outputs.prod_sha256 }} - dev_sha256: ${{ steps.release.outputs.dev_sha256 }} + ii_prod_sha256: ${{ steps.release.outputs.ii_prod_sha256 }} + archive_sha256: ${{ steps.release.outputs.archive_sha256 }} runs-on: ubuntu-latest steps: - name: Get latest release information @@ -32,14 +32,18 @@ jobs: exit 1 fi curl --silent -SL "https://github.com/dfinity/internet-identity/releases/download/$latest_release_ref/internet_identity_production.wasm.gz" -o internet_identity_production.wasm.gz - latest_prod_release_sha256=$(shasum -a 256 ./internet_identity_production.wasm.gz | cut -d ' ' -f1) + curl --silent -SL "https://github.com/dfinity/internet-identity/releases/download/$latest_release_ref/archive.wasm.gz" -o archive.wasm.gz + latest_release_ii_prod_sha256=$(shasum -a 256 ./internet_identity_production.wasm.gz | cut -d ' ' -f1) + latest_release_archive_sha256=$(shasum -a 256 ./archive.wasm.gz | cut -d ' ' -f1) echo latest release is "$latest_release_ref" - echo latest prod release sha256 is "$latest_prod_release_sha256" + echo latest prod release sha256 is "$latest_release_ii_prod_sha256" + echo latest archive release sha256 is "$latest_release_archive_sha256" echo "ref=$latest_release_ref" >> "$GITHUB_OUTPUT" - echo "prod_sha256=$latest_prod_release_sha256" >> "$GITHUB_OUTPUT" + echo "ii_prod_sha256=$latest_release_ii_prod_sha256" >> "$GITHUB_OUTPUT" + echo "archive_sha256=$latest_release_archive_sha256" >> "$GITHUB_OUTPUT" id: release - # Then perform the build, using the release as checkout + # Perform the clean build (non-docker), using the release as checkout clean-build: runs-on: ${{ matrix.os }} needs: latest-release @@ -54,7 +58,7 @@ jobs: - uses: ./.github/actions/check-build with: # we check that ubuntu builds match the latest release build - sha256: ${{ startsWith(matrix.os, 'ubuntu') && needs.latest-release.outputs.prod_sha256 || '' }} + sha256: ${{ startsWith(matrix.os, 'ubuntu') && needs.latest-release.outputs.ii_prod_sha256 || '' }} # Since the release build check is a scheduled job, a failure won't be shown on any # PR status. To notify the team, we send a message to our Slack channel on failure. @@ -64,3 +68,40 @@ jobs: with: WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} MESSAGE: "Release build check failed" + + # Verify the hash using the verify-hash script, using the release as checkout. + # This runs the build using docker and should work on all platforms. + verify-build-dockerized: + runs-on: ${{ matrix.os }} + needs: latest-release + strategy: + matrix: + os: [ ubuntu-22.04, ubuntu-20.04, macos-11, macos-12 ] + steps: + - uses: actions/checkout@v3 + with: + ref: "refs/tags/${{ needs.latest-release.outputs.ref }}" + + - name: Setup docker (missing on MacOS) + if: runner.os == 'macos' + run: | + brew install docker + brew install docker-buildx + # The following 2 commands are taken from the post install instructions printed by `brew install docker-buildx` + mkdir -p ~/.docker/cli-plugins + ln -sfn /usr/local/opt/docker-buildx/bin/docker-buildx ~/.docker/cli-plugins/docker-buildx + colima start + + - name: "Verify Hash" + id: dfx-metadata + run: | + ./scripts/verify-hash --ii-hash ${{ needs.latest-release.outputs.ii_prod_sha256 }} --archive-hash ${{ needs.latest-release.outputs.archive_sha256 }} + + # Since the release build check is a scheduled job, a failure won't be shown on any + # PR status. To notify the team, we send a message to our Slack channel on failure. + - name: Notify Slack on failure + uses: ./.github/actions/slack + if: ${{ failure() }} + with: + WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + MESSAGE: "Verify hash check failed" diff --git a/.github/workflows/update-dapps.yml b/.github/workflows/update-dapps.yml index 89e2da4b80..9f5a19646f 100644 --- a/.github/workflows/update-dapps.yml +++ b/.github/workflows/update-dapps.yml @@ -12,15 +12,20 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + - uses: ./.github/actions/setup-node + - run: npm ci # Run the update - name: Check new dapps file id: update run: ./scripts/update-dapps + # Run the formatter so that the dapps.json file is formatted + - run: npm run format + # If the dapps changed, create a PR. + # This action creates a PR only if there are changes. - name: Create Pull Request - if: ${{ steps.update.outputs.updated == '1' }} uses: peter-evans/create-pull-request@v4 with: token: ${{ secrets.GIX_BOT_PAT }} diff --git a/.ic-commit b/.ic-commit index 78060de556..c444eac5a7 100644 --- a/.ic-commit +++ b/.ic-commit @@ -1,3 +1,3 @@ # the commit used to pull the state machine executable # see rust canister tests for more info -c74ce7317761e540d722d01fa6c26a046707f372 +4918bb79b1ff24defeec0d596c60796688b5ddec diff --git a/.node-version b/.node-version index 4a1f488b6c..02c8b485ed 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -18.17.1 +18.18.0 diff --git a/demos/using-dev-build/package-lock.json b/demos/using-dev-build/package-lock.json index 9a2382cf13..324e4654d0 100644 --- a/demos/using-dev-build/package-lock.json +++ b/demos/using-dev-build/package-lock.json @@ -15,7 +15,7 @@ "@wdio/local-runner": "^8.6.9", "@wdio/mocha-framework": "^8.6.8", "@wdio/spec-reporter": "^8.6.8", - "chromedriver": "^115.0.0", + "chromedriver": "^117.0.1", "prettier": "^2.7.1", "prettier-plugin-organize-imports": "^3.2.2", "proxy": "git+https://github.com/nmattia/dfx-proxy", @@ -2245,9 +2245,9 @@ } }, "node_modules/chromedriver": { - "version": "115.0.0", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-115.0.0.tgz", - "integrity": "sha512-mkPL+sXMLMUenoXCiKREw+cBl3ibfhiWxkiv9ByIPpqtrrInCt9zKdOolAsbmN/ndlH51WtT5ukUKbeRdrpikg==", + "version": "117.0.1", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-117.0.1.tgz", + "integrity": "sha512-n3gGFlSS8UAnk3ya2SWU6sN3k3GQsr8K/rWhKWse/NuzNDvLCYm7if9i1KnVx/x1Cp2C59mWN0w1DyhUSheNbA==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -2263,7 +2263,7 @@ "chromedriver": "bin/chromedriver" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/chromium-bidi": { diff --git a/demos/using-dev-build/package.json b/demos/using-dev-build/package.json index fc4d552c7a..420e218ce9 100644 --- a/demos/using-dev-build/package.json +++ b/demos/using-dev-build/package.json @@ -16,7 +16,7 @@ "@wdio/local-runner": "^8.6.9", "@wdio/mocha-framework": "^8.6.8", "@wdio/spec-reporter": "^8.6.8", - "chromedriver": "^115.0.0", + "chromedriver": "^117.0.1", "prettier": "^2.7.1", "prettier-plugin-organize-imports": "^3.2.2", "proxy": "git+https://github.com/nmattia/dfx-proxy", diff --git a/docker-test-env/docker-compose.yml b/docker-test-env/docker-compose.yml index aade3c96c0..4c0a247d73 100644 --- a/docker-test-env/docker-compose.yml +++ b/docker-test-env/docker-compose.yml @@ -28,7 +28,7 @@ services: # Selenium container with chromedriver and chrome selenium: # use the seleniarm image that provides multiple architecture variants including one for M1 chips - image: seleniarm/standalone-chromium:104.0 + image: seleniarm/standalone-chromium:116.0 ports: - "4444:4444" # port for the test runner to connect to chromedriver - "7900:7900" # port to access the page to watch what chrome is doing (http://localhost:7900, pw is secret) @@ -36,6 +36,8 @@ services: environment: # default number of sessions is 1. We need more because of flows involving multiple devices (which we simulate using parallel sessions). - SE_NODE_OVERRIDE_MAX_SESSIONS=true - SE_NODE_MAX_SESSIONS=5 + - SCREEN_WIDTH=1920 + - SCREEN_HEIGHT=1080 networks: - ic networks: diff --git a/package-lock.json b/package-lock.json index 12c992664b..61c1e67bf1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,6 +34,7 @@ "astro": "^2.4.1", "eslint": "8.47.0", "extract-zip": "^2.0.1", + "fake-indexeddb": "^4.0.2", "html-minifier-terser": "^7.2.0", "prettier": "2.8.0", "prettier-plugin-organize-imports": "^3.2.2", @@ -3049,6 +3050,15 @@ "node": ">= 0.6.0" } }, + "node_modules/base64-arraybuffer-es6": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/base64-arraybuffer-es6/-/base64-arraybuffer-es6-0.7.0.tgz", + "integrity": "sha512-ESyU/U1CFZDJUdr+neHRhNozeCv72Y7Vm0m1DCbjX3KBjT6eYocvAJlSk6+8+HkVwXlT1FNxhGW6q3UKAlCvvw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -4999,6 +5009,15 @@ "@types/yauzl": "^2.9.1" } }, + "node_modules/fake-indexeddb": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/fake-indexeddb/-/fake-indexeddb-4.0.2.tgz", + "integrity": "sha512-SdTwEhnakbgazc7W3WUXOJfGmhH0YfG4d+dRPOFoYDRTL6U5t8tvrmkf2W/C3W1jk2ylV7Wrnj44RASqpX/lEw==", + "dev": true, + "dependencies": { + "realistic-structured-clone": "^3.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -9153,6 +9172,32 @@ "node": ">=8.10.0" } }, + "node_modules/realistic-structured-clone": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/realistic-structured-clone/-/realistic-structured-clone-3.0.0.tgz", + "integrity": "sha512-rOjh4nuWkAqf9PWu6JVpOWD4ndI+JHfgiZeMmujYcPi+fvILUu7g6l26TC1K5aBIp34nV+jE1cDO75EKOfHC5Q==", + "dev": true, + "dependencies": { + "domexception": "^1.0.1", + "typeson": "^6.1.0", + "typeson-registry": "^1.0.0-alpha.20" + } + }, + "node_modules/realistic-structured-clone/node_modules/domexception": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", + "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", + "dev": true, + "dependencies": { + "webidl-conversions": "^4.0.2" + } + }, + "node_modules/realistic-structured-clone/node_modules/webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true + }, "node_modules/regexp.prototype.flags": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", @@ -10638,6 +10683,64 @@ "node": ">=14.17" } }, + "node_modules/typeson": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/typeson/-/typeson-6.1.0.tgz", + "integrity": "sha512-6FTtyGr8ldU0pfbvW/eOZrEtEkczHRUtduBnA90Jh9kMPCiFNnXIon3vF41N0S4tV1HHQt4Hk1j4srpESziCaA==", + "dev": true, + "engines": { + "node": ">=0.1.14" + } + }, + "node_modules/typeson-registry": { + "version": "1.0.0-alpha.39", + "resolved": "https://registry.npmjs.org/typeson-registry/-/typeson-registry-1.0.0-alpha.39.tgz", + "integrity": "sha512-NeGDEquhw+yfwNhguLPcZ9Oj0fzbADiX4R0WxvoY8nGhy98IbzQy1sezjoEFWOywOboj/DWehI+/aUlRVrJnnw==", + "dev": true, + "dependencies": { + "base64-arraybuffer-es6": "^0.7.0", + "typeson": "^6.0.0", + "whatwg-url": "^8.4.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/typeson-registry/node_modules/tr46": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", + "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", + "dev": true, + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/typeson-registry/node_modules/webidl-conversions": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", + "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", + "dev": true, + "engines": { + "node": ">=10.4" + } + }, + "node_modules/typeson-registry/node_modules/whatwg-url": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", + "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", + "dev": true, + "dependencies": { + "lodash": "^4.7.0", + "tr46": "^2.1.0", + "webidl-conversions": "^6.1.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/ua-parser-js": { "version": "1.0.35", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.35.tgz", @@ -14133,6 +14236,12 @@ "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz", "integrity": "sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ==" }, + "base64-arraybuffer-es6": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/base64-arraybuffer-es6/-/base64-arraybuffer-es6-0.7.0.tgz", + "integrity": "sha512-ESyU/U1CFZDJUdr+neHRhNozeCv72Y7Vm0m1DCbjX3KBjT6eYocvAJlSk6+8+HkVwXlT1FNxhGW6q3UKAlCvvw==", + "dev": true + }, "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -15553,6 +15662,15 @@ "yauzl": "^2.10.0" } }, + "fake-indexeddb": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/fake-indexeddb/-/fake-indexeddb-4.0.2.tgz", + "integrity": "sha512-SdTwEhnakbgazc7W3WUXOJfGmhH0YfG4d+dRPOFoYDRTL6U5t8tvrmkf2W/C3W1jk2ylV7Wrnj44RASqpX/lEw==", + "dev": true, + "requires": { + "realistic-structured-clone": "^3.0.0" + } + }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -18534,6 +18652,34 @@ "picomatch": "^2.2.1" } }, + "realistic-structured-clone": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/realistic-structured-clone/-/realistic-structured-clone-3.0.0.tgz", + "integrity": "sha512-rOjh4nuWkAqf9PWu6JVpOWD4ndI+JHfgiZeMmujYcPi+fvILUu7g6l26TC1K5aBIp34nV+jE1cDO75EKOfHC5Q==", + "dev": true, + "requires": { + "domexception": "^1.0.1", + "typeson": "^6.1.0", + "typeson-registry": "^1.0.0-alpha.20" + }, + "dependencies": { + "domexception": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", + "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", + "dev": true, + "requires": { + "webidl-conversions": "^4.0.2" + } + }, + "webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true + } + } + }, "regexp.prototype.flags": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", @@ -19611,6 +19757,51 @@ "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", "dev": true }, + "typeson": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/typeson/-/typeson-6.1.0.tgz", + "integrity": "sha512-6FTtyGr8ldU0pfbvW/eOZrEtEkczHRUtduBnA90Jh9kMPCiFNnXIon3vF41N0S4tV1HHQt4Hk1j4srpESziCaA==", + "dev": true + }, + "typeson-registry": { + "version": "1.0.0-alpha.39", + "resolved": "https://registry.npmjs.org/typeson-registry/-/typeson-registry-1.0.0-alpha.39.tgz", + "integrity": "sha512-NeGDEquhw+yfwNhguLPcZ9Oj0fzbADiX4R0WxvoY8nGhy98IbzQy1sezjoEFWOywOboj/DWehI+/aUlRVrJnnw==", + "dev": true, + "requires": { + "base64-arraybuffer-es6": "^0.7.0", + "typeson": "^6.0.0", + "whatwg-url": "^8.4.0" + }, + "dependencies": { + "tr46": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", + "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", + "dev": true, + "requires": { + "punycode": "^2.1.1" + } + }, + "webidl-conversions": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", + "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", + "dev": true + }, + "whatwg-url": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", + "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", + "dev": true, + "requires": { + "lodash": "^4.7.0", + "tr46": "^2.1.0", + "webidl-conversions": "^6.1.0" + } + } + } + }, "ua-parser-js": { "version": "1.0.35", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.35.tgz", diff --git a/package.json b/package.json index 6a7449ac1d..5173ae781e 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "astro": "^2.4.1", "eslint": "8.47.0", "extract-zip": "^2.0.1", + "fake-indexeddb": "^4.0.2", "html-minifier-terser": "^7.2.0", "prettier": "2.8.0", "prettier-plugin-organize-imports": "^3.2.2", diff --git a/scripts/update-dapps b/scripts/update-dapps index 4f4bd27b8a..6a8ba3776d 100755 --- a/scripts/update-dapps +++ b/scripts/update-dapps @@ -132,7 +132,7 @@ function update() { echo "converting '$old' -> '$new'" # if the file is a huge SVG (surprising, but why not) # then convert it to a raster webp (with transparency) - convert -background none -size 256x256 "$old" "$new" + convert -background none -resize 256x256 "$old" "$new" dapps_json_tmp=$(mktemp) jq <"$DAPPS_JSON" \ --arg old "$(basename "$old")" --arg new "$(basename "$new")" \ @@ -142,6 +142,24 @@ function update() { fi done < <(find "$II_LOGO_PREFIX" -mindepth 1 -type f -size +20k) + # This goes through all logos ending in jpg or jpeg and converts them to webp + while read -r jpg_logo + do + echo "found JPG logo '$jpg_logo'" + old="$jpg_logo" + new="${jpg_logo%.*}.webp" + + echo "converting '$old' -> '$new'" + # convert to webp + convert "$old" "$new" + dapps_json_tmp=$(mktemp) + jq <"$DAPPS_JSON" \ + --arg old "$(basename "$old")" --arg new "$(basename "$new")" \ + '[ .[] | .logo |= sub("^" + $old + "$"; $new) ]' > "$dapps_json_tmp" + mv "$dapps_json_tmp" "$DAPPS_JSON" + rm "$old" + done < <(find "$II_LOGO_PREFIX" -mindepth 1 -type f \( -name \*.jpg -o -name \*.jpeg \) ) + # Remove logos that are still too big # This goes through all logos bigger than a certain size and removes them while read -r big_logo diff --git a/src/frontend/assets/icons/arth_logo.png b/src/frontend/assets/icons/arth_logo.png new file mode 100644 index 0000000000..c000febe9d Binary files /dev/null and b/src/frontend/assets/icons/arth_logo.png differ diff --git a/src/frontend/assets/icons/bink_logo.png b/src/frontend/assets/icons/bink_logo.png deleted file mode 100644 index 3d38913278..0000000000 Binary files a/src/frontend/assets/icons/bink_logo.png and /dev/null differ diff --git a/src/frontend/assets/icons/cyqlio_logo.svg b/src/frontend/assets/icons/cyqlio_logo.svg new file mode 100644 index 0000000000..c0bfcc3bed --- /dev/null +++ b/src/frontend/assets/icons/cyqlio_logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/frontend/assets/icons/d-vote_logo.webp b/src/frontend/assets/icons/d-vote_logo.webp new file mode 100644 index 0000000000..9f803d2d7c Binary files /dev/null and b/src/frontend/assets/icons/d-vote_logo.webp differ diff --git a/src/frontend/assets/icons/departurelabs_logo.webp b/src/frontend/assets/icons/departurelabs_logo.webp deleted file mode 100644 index f1d52ccf02..0000000000 Binary files a/src/frontend/assets/icons/departurelabs_logo.webp and /dev/null differ diff --git a/src/frontend/assets/icons/dsign_logo.webp b/src/frontend/assets/icons/dsign_logo.webp new file mode 100644 index 0000000000..c23b77b7d5 Binary files /dev/null and b/src/frontend/assets/icons/dsign_logo.webp differ diff --git a/src/frontend/assets/icons/hot_or_not_logo.webp b/src/frontend/assets/icons/hot_or_not_logo.webp index 9becbb23d7..3daf99fd03 100644 Binary files a/src/frontend/assets/icons/hot_or_not_logo.webp and b/src/frontend/assets/icons/hot_or_not_logo.webp differ diff --git a/src/frontend/assets/icons/ichub_logo.png b/src/frontend/assets/icons/ichub_logo.png new file mode 100644 index 0000000000..325caa574f Binary files /dev/null and b/src/frontend/assets/icons/ichub_logo.png differ diff --git a/src/frontend/assets/icons/joined-africa_logo.webp b/src/frontend/assets/icons/joined-africa_logo.webp new file mode 100644 index 0000000000..b6764aa2ac Binary files /dev/null and b/src/frontend/assets/icons/joined-africa_logo.webp differ diff --git a/src/frontend/assets/icons/metaforo-icp_logo.png b/src/frontend/assets/icons/metaforo-icp_logo.png new file mode 100644 index 0000000000..2962fb3cab Binary files /dev/null and b/src/frontend/assets/icons/metaforo-icp_logo.png differ diff --git a/src/frontend/assets/icons/motokopilot_logo.png b/src/frontend/assets/icons/motokopilot_logo.png new file mode 100644 index 0000000000..3ef332e02f Binary files /dev/null and b/src/frontend/assets/icons/motokopilot_logo.png differ diff --git a/src/frontend/assets/icons/nobleblocks_logo.webp b/src/frontend/assets/icons/nobleblocks_logo.webp new file mode 100644 index 0000000000..77a115aa03 Binary files /dev/null and b/src/frontend/assets/icons/nobleblocks_logo.webp differ diff --git a/src/frontend/assets/icons/oisy_logo.svg b/src/frontend/assets/icons/oisy_logo.svg new file mode 100644 index 0000000000..65cb79bbfb --- /dev/null +++ b/src/frontend/assets/icons/oisy_logo.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/frontend/assets/icons/rakeoff_logo.webp b/src/frontend/assets/icons/rakeoff_logo.webp new file mode 100644 index 0000000000..e4f3ccb823 Binary files /dev/null and b/src/frontend/assets/icons/rakeoff_logo.webp differ diff --git a/src/frontend/assets/icons/signals_logo.webp b/src/frontend/assets/icons/signals_logo.webp new file mode 100644 index 0000000000..216b98a8e5 Binary files /dev/null and b/src/frontend/assets/icons/signals_logo.webp differ diff --git a/src/frontend/assets/icons/vibeverse_logo.png b/src/frontend/assets/icons/vibeverse_logo.png new file mode 100644 index 0000000000..9af743bc66 Binary files /dev/null and b/src/frontend/assets/icons/vibeverse_logo.png differ diff --git a/src/frontend/src/components/authenticateBox.ts b/src/frontend/src/components/authenticateBox.ts index 6b2ab52afa..ff1c2f4129 100644 --- a/src/frontend/src/components/authenticateBox.ts +++ b/src/frontend/src/components/authenticateBox.ts @@ -1,4 +1,12 @@ +import { withLoader } from "$src/components/loader"; +import { + PinIdentityMaterial, + pinIdentityToDerPubkey, + reconstructPinIdentity, +} from "$src/crypto/pinIdentity"; import { registerTentativeDevice } from "$src/flows/addDevice/welcomeView/registerTentativeDevice"; +import { idbRetrievePinIdentityMaterial } from "$src/flows/pin/idb"; +import { usePin } from "$src/flows/pin/usePin"; import { useRecovery } from "$src/flows/recovery/useRecovery"; import { getRegisterFlowOpts, registerFlow } from "$src/flows/register"; import { I18n } from "$src/i18n"; @@ -9,7 +17,12 @@ import { LoginFlowSuccess, apiResultToLoginFlowResult, } from "$src/utils/flowResult"; -import { Connection, LoginResult } from "$src/utils/iiConnection"; +import { + AuthenticatedConnection, + Connection, + LoginResult, + bufferEqual, +} from "$src/utils/iiConnection"; import { TemplateElement, withRef } from "$src/utils/lit-html"; import { getAnchors, @@ -30,10 +43,10 @@ import { secureIcon, signInIcon, } from "./icons"; -import { withLoader } from "./loader"; import { mainWindow } from "./mainWindow"; import { promptUserNumber } from "./promptUserNumber"; +import { DerEncodedPublicKey } from "@dfinity/agent"; import copyJson from "./authenticateBox.json"; /** Template used for rendering specific authentication screens. See `authnScreens` below @@ -62,13 +75,17 @@ export const authenticateBox = async ({ templates: AuthnTemplates; }): Promise => { const promptAuth = () => - authenticateBoxFlow({ + authenticateBoxFlow({ i18n, templates, addDevice: (userNumber) => asNewDevice(connection, userNumber), - login: (userNumber) => login({ connection, userNumber }), + loginPasskey: (userNumber) => loginPasskey({ connection, userNumber }), + loginPinIdentityMaterial: (opts) => + loginPinIdentityMaterial({ ...opts, connection }), recover: () => useRecovery(connection), registerFlowOpts: getRegisterFlowOpts({ connection }), + retrievePinIdentityMaterial: ({ userNumber }) => + retrievePinIdentityWithCheck(connection, userNumber), }); // Retry until user has successfully authenticated @@ -84,19 +101,37 @@ export const authenticateBox = async ({ /** Authentication box component which authenticates a user * to II or to another dapp */ -export const authenticateBoxFlow = async ({ +export const authenticateBoxFlow = async ({ i18n, templates, addDevice, - login, + loginPasskey, + loginPinIdentityMaterial, recover, registerFlowOpts, + retrievePinIdentityMaterial, }: { i18n: I18n; templates: AuthnTemplates; addDevice: (userNumber?: bigint) => Promise<{ alias: string }>; - login: (userNumber: bigint) => Promise | LoginFlowError>; + loginPasskey: ( + userNumber: bigint + ) => Promise | LoginFlowError>; + loginPinIdentityMaterial: ({ + userNumber, + pin, + pinIdentityMaterial, + }: { + userNumber: bigint; + pin: string; + pinIdentityMaterial: I; + }) => Promise | { tag: "err"; message: string }>; recover: () => Promise>; + retrievePinIdentityMaterial: ({ + userNumber, + }: { + userNumber: bigint; + }) => Promise; registerFlowOpts: Parameters>[0]; }): Promise & { newAnchor: boolean }> => { const pages = authnScreens(i18n, { ...templates }); @@ -120,14 +155,62 @@ export const authenticateBoxFlow = async ({ }; }; + const doLogin = async ({ + userNumber, + }: { + userNumber: bigint; + }): Promise & { newAnchor: boolean }> => { + const pinIdentityMaterial = await retrievePinIdentityMaterial({ + userNumber, + }); + + if (pinIdentityMaterial === undefined) { + // this user number does not have a browser storage identity + const result = await withLoader(() => loginPasskey(userNumber)); + return { newAnchor: false, ...result }; + } + + // Otherwise, attempt login with PIN + const result = await usePin({ + verifyPin: async (pin) => { + const result = await loginPinIdentityMaterial({ + userNumber, + pin, + pinIdentityMaterial, + }); + if (result.tag === "err") { + return { ok: false, error: result.message }; + } + return { ok: true, value: result }; + }, + }); + + if (result.kind === "canceled") { + return { + newAnchor: false, + tag: "canceled", + } as const; + } + + if (result.kind === "passkey") { + // User still decided to use a passkey + const result = await withLoader(() => loginPasskey(userNumber)); + return { newAnchor: false, ...result }; + } + + result satisfies { kind: "pin" }; + const { result: pinResult } = result; + + return { newAnchor: false, ...pinResult }; + }; + // Prompt for an identity number const doPrompt = async (): Promise< LoginFlowResult & { newAnchor: boolean } > => { const result = await pages.useExisting(); if (result.tag === "submit") { - const result2 = await withLoader(() => login(result.userNumber)); - return { newAnchor: false, ...result2 }; + return doLogin({ userNumber: result.userNumber }); } if (result.tag === "add_device") { @@ -159,8 +242,7 @@ export const authenticateBoxFlow = async ({ const result = await pages.pick({ anchors }); if (result.tag === "pick") { - const result1 = await withLoader(() => login(result.userNumber)); - return { newAnchor: false, ...result1 }; + return doLogin({ userNumber: result.userNumber }); } result satisfies { tag: "more_options" }; @@ -437,7 +519,7 @@ const page = (slot: TemplateResult) => { render(template, container); }; -const login = ({ +const loginPasskey = ({ connection, userNumber, }: { @@ -448,6 +530,36 @@ const login = ({ login: () => connection.login(userNumber), }); +const loginPinIdentityMaterial = ({ + connection, + userNumber, + pin, + pinIdentityMaterial, +}: { + connection: Connection; + userNumber: bigint; + pin: string; + pinIdentityMaterial: PinIdentityMaterial; +}): Promise => { + return withLoader(async () => { + try { + const identity = await reconstructPinIdentity({ + pin, + pinIdentityMaterial, + }); + return handleLogin({ + login: () => connection.fromIdentity(userNumber, identity), + }); + } catch { + // We handle all exceptions as wrong PIN because there is no nice way to check for that particular failure. + // The best we could do is check that the error is a DOMException and that the name is "OperationError". However, + // the "OperationError" names is still marked as experimental, so we should not rely on that. + // See https://developer.mozilla.org/en-US/docs/Web/API/DOMException + return { tag: "err", message: "Invalid PIN" }; + } + }); +}; + // Register this device as a new device with the anchor const asNewDevice = async ( connection: Connection, @@ -475,3 +587,28 @@ const asNewDevice = async ( connection ); }; + +// Retrieve the PIN identity material from the browser storage and check that it is still valid for the given user number. +const retrievePinIdentityWithCheck = async ( + connection: Connection, + userNumber: bigint +): Promise => { + const [pinIdentity, authenticators] = await Promise.all([ + idbRetrievePinIdentityMaterial({ userNumber }), + connection.lookupAuthenticators(userNumber), + ]); + if (nonNullish(pinIdentity)) { + const pinPubkeyDer = await pinIdentityToDerPubkey(pinIdentity); + // Check that the authenticator is still present on the identity. + const authenticator = authenticators.find((authenticator) => + bufferEqual( + new Uint8Array(authenticator.pubkey).buffer as DerEncodedPublicKey, + pinPubkeyDer + ) + ); + if (nonNullish(authenticator)) { + return pinIdentity; + } + } + return undefined; +}; diff --git a/src/frontend/src/components/icons.ts b/src/frontend/src/components/icons.ts index e909f78dac..3079675369 100644 --- a/src/frontend/src/components/icons.ts +++ b/src/frontend/src/components/icons.ts @@ -541,21 +541,28 @@ export const verifyIcon = html` `; -export const hackerIcon = html` - - +export const hackerIcon = html` + + +`; + +export const cypherIcon = html` + `; diff --git a/src/frontend/src/components/pinInput.test.ts b/src/frontend/src/components/pinInput.test.ts index 3ca8d1c3fd..bd3841c556 100644 --- a/src/frontend/src/components/pinInput.test.ts +++ b/src/frontend/src/components/pinInput.test.ts @@ -58,7 +58,7 @@ describe("pin input", () => { ); }); - test("writing to all chars triggers autosubmit only once", () => { + test("writing to all chars triggers submit on every dispatch", () => { const onSubmit: (pin: string) => void = vi.fn(); render(pinInputId({ onSubmit }).template, document.body); @@ -70,8 +70,8 @@ describe("pin input", () => { } expect(onSubmit).toHaveBeenCalledOnce(); - dispatchInput(inputs[0], "a"); // after an extra dispatch, the submit should still only have been called once - expect(onSubmit).toHaveBeenCalledOnce(); + dispatchInput(inputs[0], "a"); // after an extra dispatch the value should be submitted again + expect(onSubmit).toHaveBeenCalledTimes(2); }); test("writing to all chars removes focus", () => { diff --git a/src/frontend/src/components/pinInput.ts b/src/frontend/src/components/pinInput.ts index f0e4852f17..d8e0e672e6 100644 --- a/src/frontend/src/components/pinInput.ts +++ b/src/frontend/src/components/pinInput.ts @@ -1,4 +1,5 @@ import { toast } from "$src/components/toast"; +import type { DynamicKey } from "$src/utils/i18n"; import { mount, withRef } from "$src/utils/lit-html"; import { Chan, withInputElement, zip } from "$src/utils/utils"; import { isNullish, nonNullish } from "@dfinity/utils"; @@ -8,7 +9,7 @@ import { Ref, createRef, ref } from "lit-html/directives/ref.js"; export type PinResult = | { ok: true; value: T } - | { ok: false; error: string }; + | { ok: false; error: string | DynamicKey }; // A Pin Input component export const pinInput = ({ @@ -39,7 +40,7 @@ export const pinInput = ({ const listRoot: Ref = createRef(); // A currently displayed error, if any - const currentError = new Chan(undefined); + const currentError = new Chan(undefined); const hasError = currentError.map((e) => nonNullish(e)); // Called on submission, either autosubmit (when filled) or when '.submit()' is called @@ -55,17 +56,6 @@ export const pinInput = ({ onSubmit_(result.value); }; - // We autosubmit only once - let didAutosubmit = false; - const autosubmit = (pin: string) => { - if (didAutosubmit) { - return; - } - - didAutosubmit = true; - onSubmit(pin); - }; - const errorMessage = currentError.map((currentError) => nonNullish(currentError) ? html`

${currentError}

` @@ -88,7 +78,7 @@ export const pinInput = ({ const onInput_ = (evnt: InputEvent, element: HTMLInputElement) => withInputs((inputs) => { currentError.send(undefined); - onInput({ element, evnt, inputs, onFilled: autosubmit }); + onInput({ element, evnt, inputs, onFilled: onSubmit }); }); const onPaste_ = (evnt: ClipboardEvent, element: HTMLInputElement) => withInputs((inputs) => { @@ -123,7 +113,7 @@ export const pinInput = ({ /* XXX: we use a special class/font for the 'secret' PIN input instead of setting * type="password", otherwise the browser tries to save one char as a password */ (_, ix) => html` -
  • +