From 438dc737c717e1dbe0bdaf0c6dec11ef6e3b6bfe Mon Sep 17 00:00:00 2001 From: gkatrakazas Date: Tue, 20 Oct 2026 14:37:17 +0300 Subject: [PATCH 1/6] chore(eslint): add ESLint config and disable rules with existing violations --- eslint.config.mjs | 48 ++++ package.json | 9 +- yarn.lock | 637 +++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 687 insertions(+), 7 deletions(-) create mode 100644 eslint.config.mjs diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..8c9efa3 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,48 @@ +import eslint from '@eslint/js'; +import { defineConfig } from 'eslint/config'; +import globals from 'globals'; +import tseslint from 'typescript-eslint'; + +const typescriptFiles = ['{config,src}/**/*.ts']; +const browserFiles = ['public/js/**/*.js']; + +export default defineConfig( + { + ignores: ['build/**', 'dist/**', 'node_modules/**'], + }, + { + linterOptions: { + reportUnusedDisableDirectives: 'error', + }, + }, + eslint.configs.recommended, + ...tseslint.configs.recommended, + { + files: typescriptFiles, + languageOptions: { + globals: { + ...globals.node, + }, + }, + rules: { + '@typescript-eslint/ban-ts-comment': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-unused-vars': 'off', + 'prefer-const': 'off', + 'no-undef': 'off', + }, + }, + { + files: browserFiles, + languageOptions: { + globals: { + ...globals.browser, + JSONEditor: 'readonly', + }, + }, + rules: { + '@typescript-eslint/no-unused-expressions': 'off', + '@typescript-eslint/no-unused-vars': 'off', + }, + }, +); diff --git a/package.json b/package.json index 1acb3ae..20eabdd 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,8 @@ "snapshot": "paketo", "migration:generate": "typeorm-ts-node-commonjs -d src/AppDataSource.ts migration:generate", "migration:run": "typeorm-ts-node-commonjs -d src/AppDataSource.ts migration:run", - "migration:revert": "typeorm-ts-node-commonjs -d src/AppDataSource.ts migration:revert" + "migration:revert": "typeorm-ts-node-commonjs -d src/AppDataSource.ts migration:revert", + "lint": "eslint . --max-warnings=0" }, "dependencies": { "ajv": "^8.11.0", @@ -38,6 +39,7 @@ "zod": "^3.20.6" }, "devDependencies": { + "@eslint/js": "^9.39.5", "@total-typescript/ts-reset": "^0.4.2", "@types/body-parser": "^1.19.2", "@types/cookie-parser": "^1.4.3", @@ -55,10 +57,13 @@ "@types/randomstring": "^1.1.8", "@types/winston": "^2.4.4", "@types/ws": "^8.5.3", + "eslint": "^9.39.5", + "globals": "^16.0.0", "nodemon": "^2.0.19", "source-map-support": "^0.5.21", "ts-node": "^10.9.2", - "typescript": "~5.9.3" + "typescript": "~5.9.3", + "typescript-eslint": "^8.53.0" }, "resolutions": { "@types/express": "4.17.13", diff --git a/yarn.lock b/yarn.lock index 71fd376..5d8cf9b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -78,6 +78,105 @@ enabled "2.0.x" kuler "^2.0.0" +"@eslint-community/eslint-utils@^4.8.0", "@eslint-community/eslint-utils@^4.9.1": + version "4.10.1" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz#8911bd72b2c3640a543609e0400b8c4d2e7e7cb6" + integrity sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg== + dependencies: + eslint-visitor-keys "^3.4.3" + +"@eslint-community/regexpp@^4.12.1", "@eslint-community/regexpp@^4.12.2": + version "4.12.2" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b" + integrity sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew== + +"@eslint/config-array@^0.21.2": + version "0.21.2" + resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.21.2.tgz#f29e22057ad5316cf23836cee9a34c81fffcb7e6" + integrity sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw== + dependencies: + "@eslint/object-schema" "^2.1.7" + debug "^4.3.1" + minimatch "^3.1.5" + +"@eslint/config-helpers@^0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.4.2.tgz#1bd006ceeb7e2e55b2b773ab318d300e1a66aeda" + integrity sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw== + dependencies: + "@eslint/core" "^0.17.0" + +"@eslint/core@^0.17.0": + version "0.17.0" + resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.17.0.tgz#77225820413d9617509da9342190a2019e78761c" + integrity sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ== + dependencies: + "@types/json-schema" "^7.0.15" + +"@eslint/eslintrc@^3.3.6": + version "3.3.7" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.3.7.tgz#76d3dedec4a30ea32df797bf8a0085c1311b7316" + integrity sha512-F42g89Qd5oAWtp0k0nnSrjziAKza7w8SVT4mStc18LZMaRb4J1HQAHLCalEtDCxrTuksx7NU9qsmeLwpOfPqWw== + dependencies: + ajv "^6.14.0" + debug "^4.3.2" + espree "^10.0.1" + globals "^14.0.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.3.2" + minimatch "^3.1.5" + strip-json-comments "^3.1.1" + +"@eslint/js@9.39.5", "@eslint/js@^9.39.5": + version "9.39.5" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.39.5.tgz#6f2fbcff75500d229d535e0a949ae13472c84787" + integrity sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A== + +"@eslint/object-schema@^2.1.7": + version "2.1.7" + resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.7.tgz#6e2126a1347e86a4dedf8706ec67ff8e107ebbad" + integrity sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA== + +"@eslint/plugin-kit@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz#9779e3fd9b7ee33571a57435cf4335a1794a6cb2" + integrity sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA== + dependencies: + "@eslint/core" "^0.17.0" + levn "^0.4.1" + +"@humanfs/core@^0.19.2": + version "0.19.2" + resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.2.tgz#a8272ca03b2acf492670222b2320b6c421bfde60" + integrity sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA== + dependencies: + "@humanfs/types" "^0.15.0" + +"@humanfs/node@^0.16.6": + version "0.16.8" + resolved "https://registry.yarnpkg.com/@humanfs/node/-/node-0.16.8.tgz#8f800cccc13f4f8cd3116e2d9c0a94939da3e3ed" + integrity sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ== + dependencies: + "@humanfs/core" "^0.19.2" + "@humanfs/types" "^0.15.0" + "@humanwhocodes/retry" "^0.4.0" + +"@humanfs/types@^0.15.0": + version "0.15.0" + resolved "https://registry.yarnpkg.com/@humanfs/types/-/types-0.15.0.tgz#f2a09f62012390b2bff3fc6fb248ddec8c09a090" + integrity sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q== + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/retry@^0.4.0", "@humanwhocodes/retry@^0.4.2": + version "0.4.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.3.tgz#c2b9d2e374ee62c586d3adbea87199b1d7a7a6ba" + integrity sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ== + "@isaacs/cliui@^8.0.2": version "8.0.2" resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" @@ -380,6 +479,11 @@ dependencies: "@types/express-serve-static-core" "*" +"@types/estree@^1.0.6": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.9.tgz#cf3f0e876d7bee15a93ab925b82bf570a3904a24" + integrity sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg== + "@types/express-serve-static-core@*", "@types/express-serve-static-core@4.17.43", "@types/express-serve-static-core@^4.17.18", "@types/express-serve-static-core@^4.17.33": version "4.17.43" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.43.tgz#10d8444be560cb789c4735aea5eac6e5af45df54" @@ -422,6 +526,11 @@ resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.5.tgz#5b749ab2b16ba113423feb1a64a95dcd30398472" integrity sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg== +"@types/json-schema@^7.0.15": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + "@types/lodash@^4.14.192": version "4.17.24" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.24.tgz#4ae334fc62c0e915ca8ed8e35dcc6d4eeb29215f" @@ -550,6 +659,102 @@ dependencies: "@types/node" "*" +"@typescript-eslint/eslint-plugin@8.72.0": + version "8.72.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.72.0.tgz#9855d73bcc4e9d074cac3a0b8c244a4966e519ba" + integrity sha512-aysKgysPX9XOv/dk95UkvUDe++Q3n3dJPG9CEWT3RUccxfkPJ+Qn6nhKPDhZXU6Ofo9kgwGWwKyQCnZIXTgngg== + dependencies: + "@eslint-community/regexpp" "^4.12.2" + "@typescript-eslint/scope-manager" "8.72.0" + "@typescript-eslint/type-utils" "8.72.0" + "@typescript-eslint/utils" "8.72.0" + "@typescript-eslint/visitor-keys" "8.72.0" + ignore "^7.0.5" + natural-compare "^1.4.0" + ts-api-utils "^2.5.0" + +"@typescript-eslint/parser@8.72.0": + version "8.72.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.72.0.tgz#e90d951ba97c6e2d8f1032f048292b7e39d8a019" + integrity sha512-0O+x3ynciD6zuXb1CKSu165haQV6ey53DKhHHUnujP7U+XlltJG+/keT0Xhd1HXCze+IHf7Tvt39ovyepX/LNw== + dependencies: + "@typescript-eslint/scope-manager" "8.72.0" + "@typescript-eslint/types" "8.72.0" + "@typescript-eslint/typescript-estree" "8.72.0" + "@typescript-eslint/visitor-keys" "8.72.0" + debug "^4.4.3" + +"@typescript-eslint/project-service@8.72.0": + version "8.72.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.72.0.tgz#7667745dee47f92eaf12082b0e33b76db4338423" + integrity sha512-bzHngBbmIgzj6TsTjVUCntGx6FFLgE7mPjpO0PzHycOaZHXqS43XnrkFIHRS16LLr/xO6FHSXG3GJFo/F4bSBQ== + dependencies: + "@typescript-eslint/tsconfig-utils" "^8.72.0" + "@typescript-eslint/types" "^8.72.0" + debug "^4.4.3" + +"@typescript-eslint/scope-manager@8.72.0": + version "8.72.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.72.0.tgz#673cf580ef6e2377a5ef42a7bee7c7808e0fb886" + integrity sha512-JewPyJOYcXBSAS7PIgufscHOwZqhTGyBRiQJhhuDln66tMchxvvt7nJ90JKKIoOelmdHA3kAQbm9cEUHc4JSEA== + dependencies: + "@typescript-eslint/types" "8.72.0" + "@typescript-eslint/visitor-keys" "8.72.0" + +"@typescript-eslint/tsconfig-utils@8.72.0", "@typescript-eslint/tsconfig-utils@^8.72.0": + version "8.72.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.72.0.tgz#a71e770c420ccbd7afc081f388bfa87d1958dc16" + integrity sha512-0vbxCf7xLVPSjcOdm6eQbgViH8EDJ8xnl1laLT9yoqSz2YmcSfSg5Vdl0JUvZf6rcuJ0ZivJIuMD4edTg8obaA== + +"@typescript-eslint/type-utils@8.72.0": + version "8.72.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.72.0.tgz#b66543c7a5cb3c1447bc455222b8c02b7499a8ea" + integrity sha512-gEXxmmbSTSOVTC37IYYvremOM31v+UN2W772bJxmVYPhAOPSUxtacyb7wkm1EhkYKU6KzFJsdU44DCV4to0LWw== + dependencies: + "@typescript-eslint/types" "8.72.0" + "@typescript-eslint/typescript-estree" "8.72.0" + "@typescript-eslint/utils" "8.72.0" + debug "^4.4.3" + ts-api-utils "^2.5.0" + +"@typescript-eslint/types@8.72.0", "@typescript-eslint/types@^8.72.0": + version "8.72.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.72.0.tgz#d90bb9954fefa13acbcdb43bb7c0155f74d60fca" + integrity sha512-Adfoy59kxNEjT5E5fnyrp7GpHYuWs8ONbdohu4ez6IU46qDq8LltG90tLmkucZB2uhYL8mqBjtXqGd8yhfjXqQ== + +"@typescript-eslint/typescript-estree@8.72.0": + version "8.72.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.72.0.tgz#e20f08b852484a49af7fe621d264e8e73138308b" + integrity sha512-gwLfFgWcw15tN2KXDCJvxEOQjPn+P3j/n3eSqkZ5sX4VpW9Cosodr+tWwBZRvJT3TcKx6HnPRGZMCNAiP1Xhug== + dependencies: + "@typescript-eslint/project-service" "8.72.0" + "@typescript-eslint/tsconfig-utils" "8.72.0" + "@typescript-eslint/types" "8.72.0" + "@typescript-eslint/visitor-keys" "8.72.0" + debug "^4.4.3" + minimatch "^10.2.2" + semver "^7.7.3" + tinyglobby "^0.2.15" + ts-api-utils "^2.5.0" + +"@typescript-eslint/utils@8.72.0": + version "8.72.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.72.0.tgz#12de2367030571a0b1d855999cdd5d614cfe7e8b" + integrity sha512-0up3oArJZBYnzOaEXKo8DmI43QvMZyPoFEt9LWzRJcyl/RcJjfKKPebknN1VbTgXBf3M/KggUh5uUMdURLnseg== + dependencies: + "@eslint-community/eslint-utils" "^4.9.1" + "@typescript-eslint/scope-manager" "8.72.0" + "@typescript-eslint/types" "8.72.0" + "@typescript-eslint/typescript-estree" "8.72.0" + +"@typescript-eslint/visitor-keys@8.72.0": + version "8.72.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.72.0.tgz#e96d7faafae685e0a0f276dc59b00e9c0ad95869" + integrity sha512-E2mroviasaWecDzTGiGMtuEguuLVOeRggnrIn93CJuxYJzQBHbyymlT08aVQydbBal5538o+JkBKXh2sRuCyIA== + dependencies: + "@typescript-eslint/types" "8.72.0" + eslint-visitor-keys "^5.0.0" + accepts@~1.3.8: version "1.3.8" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" @@ -558,6 +763,11 @@ accepts@~1.3.8: mime-types "~2.1.34" negotiator "0.6.3" +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + acorn-walk@^8.1.1: version "8.3.5" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.5.tgz#8a6b8ca8fc5b34685af15dabb44118663c296496" @@ -575,6 +785,11 @@ acorn@^8.11.0, acorn@^8.4.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.16.0.tgz#4ce79c89be40afe7afe8f3adb902a1f1ce9ac08a" integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw== +acorn@^8.15.0: + version "8.19.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.19.0.tgz#6b26fd97ce1cc9dc50bdf7ad305e9f1384d4f28a" + integrity sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw== + agent-base@6: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" @@ -582,6 +797,16 @@ agent-base@6: dependencies: debug "4" +ajv@^6.14.0: + version "6.15.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.15.0.tgz#07e982c74626167aa7a2495c53817892d7139492" + integrity sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + ajv@^8.11.0: version "8.20.0" resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.20.0.tgz#304b3636add88ba7d936760dd50ece006dea95f9" @@ -602,7 +827,7 @@ ansi-regex@^6.2.2: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.4.0.tgz#69093d6436d30dbb9b04fe7bdcbcfe16c697d8a1" integrity sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw== -ansi-styles@^4.0.0: +ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== @@ -637,6 +862,11 @@ arg@^4.1.0: resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" @@ -700,6 +930,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +balanced-match@^4.0.2: + version "4.0.4" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-4.0.4.tgz#bfb10662feed8196a2c62e7c68e17720c274179a" + integrity sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA== + base64-js@^1.3.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" @@ -760,6 +995,13 @@ brace-expansion@^2.0.2: dependencies: balanced-match "^1.0.0" +brace-expansion@^5.0.8: + version "5.0.12" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.12.tgz#995fbb4750a77c4d16a7dc942ff2ca6ef8e675ec" + integrity sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ== + dependencies: + balanced-match "^4.0.2" + braces@~3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" @@ -816,6 +1058,11 @@ call-bound@^1.0.2, call-bound@^1.0.3, call-bound@^1.0.4: call-bind-apply-helpers "^1.0.2" get-intrinsic "^1.3.0" +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + camelcase@^5.0.0: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" @@ -842,6 +1089,14 @@ cbor-x@^1.6.4: optionalDependencies: cbor-extract "^2.2.2" +chalk@^4.0.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + character-parser@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/character-parser/-/character-parser-2.2.0.tgz#c7ce28f36d4bcd9744e5ffc2c5fcde1c73261fc0" @@ -1022,7 +1277,7 @@ debug@2.6.9, debug@~2.6.9: dependencies: ms "2.0.0" -debug@4, debug@^4.4.3: +debug@4, debug@^4.3.1, debug@^4.3.2, debug@^4.4.3: version "4.4.3" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== @@ -1046,6 +1301,11 @@ dedent@^1.7.2: resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.7.2.tgz#34e2264ab538301e27cf7b07bf2369c19baa8dd9" integrity sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA== +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + define-data-property@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" @@ -1171,6 +1431,107 @@ escape-html@~1.0.3: resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-scope@^8.4.0: + version "8.4.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.4.0.tgz#88e646a207fad61436ffa39eb505147200655c82" + integrity sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint-visitor-keys@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz#4cfea60fe7dd0ad8e816e1ed026c1d5251b512c1" + integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== + +eslint-visitor-keys@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz#9e3c9489697824d2d4ce3a8ad12628f91e9f59be" + integrity sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA== + +eslint@^9.39.5: + version "9.39.5" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.39.5.tgz#2a4e3c8b0f753196efae943c8ffaa8730fc6a3fa" + integrity sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw== + dependencies: + "@eslint-community/eslint-utils" "^4.8.0" + "@eslint-community/regexpp" "^4.12.1" + "@eslint/config-array" "^0.21.2" + "@eslint/config-helpers" "^0.4.2" + "@eslint/core" "^0.17.0" + "@eslint/eslintrc" "^3.3.6" + "@eslint/js" "9.39.5" + "@eslint/plugin-kit" "^0.4.1" + "@humanfs/node" "^0.16.6" + "@humanwhocodes/module-importer" "^1.0.1" + "@humanwhocodes/retry" "^0.4.2" + "@types/estree" "^1.0.6" + ajv "^6.14.0" + chalk "^4.0.0" + cross-spawn "^7.0.6" + debug "^4.3.2" + escape-string-regexp "^4.0.0" + eslint-scope "^8.4.0" + eslint-visitor-keys "^4.2.1" + espree "^10.4.0" + esquery "^1.5.0" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^8.0.0" + find-up "^5.0.0" + glob-parent "^6.0.2" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + json-stable-stringify-without-jsonify "^1.0.1" + lodash.merge "^4.6.2" + minimatch "^3.1.5" + natural-compare "^1.4.0" + optionator "^0.9.3" + +espree@^10.0.1, espree@^10.4.0: + version "10.4.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-10.4.0.tgz#d54f4949d4629005a1fa168d937c3ff1f7e2a837" + integrity sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ== + dependencies: + acorn "^8.15.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^4.2.1" + +esquery@^1.5.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.7.0.tgz#08d048f261f0ddedb5bae95f46809463d9c9496d" + integrity sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + etag@~1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" @@ -1227,21 +1588,43 @@ express@^4.22.3: utils-merge "1.0.1" vary "~1.1.2" -fast-deep-equal@^3.1.3: +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + fast-uri@^3.0.1: version "3.1.8" resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.8.tgz#f7db8d942e20eead3cfe93b0beeb4eb0169e938b" integrity sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg== +fdir@^6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" + integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== + fecha@^4.2.0: version "4.2.3" resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.3.tgz#4d9ccdbc61e8629b259fdca67e65891448d569fd" integrity sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw== +file-entry-cache@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f" + integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== + dependencies: + flat-cache "^4.0.0" + fill-range@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" @@ -1270,6 +1653,27 @@ find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c" + integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.4" + +flatted@^3.2.9: + version "3.4.4" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.4.4.tgz#aeeca2a506303f0cee61c59e6c9f2a88d2f29fc6" + integrity sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q== + fn.name@1.x.x: version "1.1.0" resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" @@ -1355,6 +1759,13 @@ get-proto@^1.0.1: dunder-proto "^1.0.1" es-object-atoms "^1.0.0" +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -1374,6 +1785,16 @@ glob@^10.5.0: package-json-from-dist "^1.0.0" path-scurry "^1.11.1" +globals@^14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" + integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== + +globals@^16.0.0: + version "16.5.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-16.5.0.tgz#ccf1594a437b97653b2be13ed4d8f5c9f850cac1" + integrity sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ== + gopd@^1.0.1, gopd@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" @@ -1384,6 +1805,11 @@ has-flag@^3.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + has-property-descriptors@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" @@ -1453,6 +1879,29 @@ ignore-by-default@^1.0.1: resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== +ignore@^5.2.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + +ignore@^7.0.5: + version "7.0.13" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-7.0.13.tgz#f19dfb5374313cf84e1976bb4b650235ebfd948c" + integrity sha512-2E8Q6rh+1RRSq/YdaGsNfXsEZy9FVqv6BSSrkmZJP7BnaoPnRfKL+YaofPvP+5rzdk144qSm6i4+hvPgUcbV4Q== + +import-fresh@^3.2.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" + integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3, inherits@~2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" @@ -1500,7 +1949,7 @@ is-fullwidth-code-point@^3.0.0: resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== -is-glob@^4.0.1, is-glob@~4.0.1: +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== @@ -1578,11 +2027,33 @@ js-stringify@^1.0.2: resolved "https://registry.yarnpkg.com/js-stringify/-/js-stringify-1.0.2.tgz#1736fddfd9724f28a3682adc6230ae7e4e9679db" integrity sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g== +js-yaml@^4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.3.2.tgz#8e44fb14a2643c59726bb15787b5f1512cb3d3fb" + integrity sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA== + dependencies: + argparse "^2.0.1" + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + json-schema-traverse@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + jstransformer@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/jstransformer/-/jstransformer-1.0.0.tgz#ed8bf0921e2f3f1ed4d5c1a44f68709ed24722c3" @@ -1591,11 +2062,26 @@ jstransformer@1.0.0: is-promise "^2.0.0" promise "^7.0.1" +keyv@^4.5.4: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + kuler@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3" integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A== +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + locate-path@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" @@ -1603,6 +2089,18 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + lodash@^4.17.21: version "4.18.1" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.18.1.tgz#ff2b66c1f6326d59513de2407bf881439812771c" @@ -1667,7 +2165,14 @@ mime@1.6.0: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== -minimatch@^3.1.2: +minimatch@^10.2.2: + version "10.2.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.6.tgz#fd956bbe0b77241e9f15ac5dccb1c638060968ef" + integrity sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A== + dependencies: + brace-expansion "^5.0.8" + +minimatch@^3.1.2, minimatch@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.5.tgz#580c88f8d5445f2bd6aa8f3cadefa0de79fbd69e" integrity sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w== @@ -1717,6 +2222,11 @@ mysql@^2.18.1: safe-buffer "5.1.2" sqlstring "2.3.1" +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + negotiator@0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" @@ -1779,6 +2289,18 @@ one-time@^1.0.0: dependencies: fn.name "1.x.x" +optionator@^0.9.3: + version "0.9.4" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.5" + p-limit@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" @@ -1786,6 +2308,13 @@ p-limit@^2.2.0: dependencies: p-try "^2.0.0" +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + p-locate@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" @@ -1793,6 +2322,13 @@ p-locate@^4.1.0: dependencies: p-limit "^2.2.0" +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + p-try@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" @@ -1803,6 +2339,13 @@ package-json-from-dist@^1.0.0: resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" @@ -1841,6 +2384,11 @@ picomatch@^2.0.4, picomatch@^2.2.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.2.tgz#5a942915e26b372dc0f0e6753149a16e6b1c5601" integrity sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA== +picomatch@^4.0.4: + version "4.0.7" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.7.tgz#6313360034ccb36b3dc61ecbdff78121f90fe21f" + integrity sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA== + pkijs@^3.2.4: version "3.4.0" resolved "https://registry.yarnpkg.com/pkijs/-/pkijs-3.4.0.tgz#d9164def30ff6d97be2d88966d5e36192499ca9c" @@ -1863,6 +2411,11 @@ possible-typed-array-names@^1.0.0: resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz#93e3582bc0e5426586d9d07b79ee40fc841de4ae" integrity sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg== +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" @@ -1996,6 +2549,11 @@ pug@^3.0.2: pug-runtime "^3.0.1" pug-strip-comments "^2.0.0" +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + pvtsutils@^1.3.6: version "1.3.6" resolved "https://registry.yarnpkg.com/pvtsutils/-/pvtsutils-1.3.6.tgz#ec46e34db7422b9e4fdc5490578c1883657d6001" @@ -2094,6 +2652,11 @@ require-main-filename@^2.0.0: resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + resolve@^1.15.1: version "1.22.12" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.12.tgz#f5b2a680897c69c238a13cd16b15671f8b73549f" @@ -2129,6 +2692,11 @@ semver@^5.7.1: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== +semver@^7.7.3: + version "7.8.5" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.8.5.tgz#39b646037dd50c14fb451e7e4cac58ed8b863f69" + integrity sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA== + semver@~7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" @@ -2353,6 +2921,11 @@ strip-ansi@^7.0.1: dependencies: ansi-regex "^6.2.2" +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + supports-color@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" @@ -2360,6 +2933,13 @@ supports-color@^5.5.0: dependencies: has-flag "^3.0.0" +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" @@ -2370,6 +2950,14 @@ text-hex@1.0.x: resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== +tinyglobby@^0.2.15: + version "0.2.17" + resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.17.tgz#562a9a6c9eb2b3b123d39719f9af5bb44fcd7631" + integrity sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g== + dependencies: + fdir "^6.5.0" + picomatch "^4.0.4" + to-buffer@^1.2.0: version "1.2.2" resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.2.2.tgz#ffe59ef7522ada0a2d1cb5dfe03bb8abc3cdc133" @@ -2406,6 +2994,11 @@ triple-beam@^1.3.0: resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.4.1.tgz#6fde70271dc6e5d73ca0c3b24e2d92afb7441984" integrity sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg== +ts-api-utils@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.5.0.tgz#4acd4a155e22734990a5ed1fe9e97f113bcb37c1" + integrity sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA== + ts-node@^10.9.2: version "10.9.2" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" @@ -2442,6 +3035,13 @@ tsyringe@^4.10.0: dependencies: tslib "^1.9.3" +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" @@ -2480,6 +3080,16 @@ typeorm@^0.3.31: uuid "^11.1.1" yargs "^17.7.3" +typescript-eslint@^8.53.0: + version "8.72.0" + resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.72.0.tgz#50a8fd6d4304ffd1e7fd375f8a439c6f15488071" + integrity sha512-r26Fkc8As+mZvqp7SLsMmNFyYUup/6CjK7/eHBHmhOw6GH6n6YFxHVFrG0jN1c5CMpDzelLPxvgeCOzx7BjN+A== + dependencies: + "@typescript-eslint/eslint-plugin" "8.72.0" + "@typescript-eslint/parser" "8.72.0" + "@typescript-eslint/typescript-estree" "8.72.0" + "@typescript-eslint/utils" "8.72.0" + typescript@~5.9.3: version "5.9.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f" @@ -2512,6 +3122,13 @@ unpipe@~1.0.0: resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -2625,6 +3242,11 @@ with@^7.0.0: assert-never "^1.2.1" babel-walk "3.0.0-canary-5" +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" @@ -2719,6 +3341,11 @@ yn@3.1.1: resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + zod-validation-error@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/zod-validation-error/-/zod-validation-error-5.0.0.tgz#96db948070b7bfcb13bfec5134113e580f9eee38" From 9116bce058ce72dde160734f4010915ff5c2c8cb Mon Sep 17 00:00:00 2001 From: gkatrakazas Date: Tue, 20 Oct 2026 14:43:30 +0300 Subject: [PATCH 2/6] fix: use OpenID4VPResponseMode for the default response mode --- src/services/OpenidForPresentationReceivingService.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/services/OpenidForPresentationReceivingService.ts b/src/services/OpenidForPresentationReceivingService.ts index 693f3ed..7062dd9 100644 --- a/src/services/OpenidForPresentationReceivingService.ts +++ b/src/services/OpenidForPresentationReceivingService.ts @@ -23,8 +23,7 @@ const x5c = [ const ResponseModeSchema = z.nativeEnum(OpenID4VPResponseMode); -// @ts-ignore -const response_mode: ResponseMode = config?.presentationFlow?.response_mode ? ResponseModeSchema.parse(config?.presentationFlow?.response_mode) : ResponseMode.DIRECT_POST_JWT; +const response_mode: OpenID4VPResponseMode = config?.presentationFlow?.response_mode ? ResponseModeSchema.parse(config?.presentationFlow?.response_mode) : OpenID4VPResponseMode.DIRECT_POST_JWT; export class OpenidForPresentationsReceivingService implements OpenidForPresentationsReceivingInterface { private static rpStateKV: MemoryStore = new MemoryStore(); From 390a095a899f7baa8d888a394d155b3c2cc983d4 Mon Sep 17 00:00:00 2001 From: gkatrakazas Date: Tue, 20 Oct 2026 14:43:31 +0300 Subject: [PATCH 3/6] chore(format): configure Prettier and formatting scripts --- .editorconfig | 2 +- .prettierrc.yaml | 3 +++ eslint.config.mjs | 2 ++ package.json | 6 +++++- yarn.lock | 10 ++++++++++ 5 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 .prettierrc.yaml diff --git a/.editorconfig b/.editorconfig index 99325aa..5f03901 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,7 +8,7 @@ charset = utf-8 insert_final_newline = true trim_trailing_whitespace = true -[{*.{Dockerfile,js,jsx,ts,tsx},Dockerfile}] +[{*.{Dockerfile,js,jsx,mjs,ts,tsx},Dockerfile}] indent_style = tab [public/styles/**.css] diff --git a/.prettierrc.yaml b/.prettierrc.yaml new file mode 100644 index 0000000..1fa06a8 --- /dev/null +++ b/.prettierrc.yaml @@ -0,0 +1,3 @@ +singleQuote: true +trailingComma: 'all' +printWidth: 100 diff --git a/eslint.config.mjs b/eslint.config.mjs index 8c9efa3..d39c553 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -2,6 +2,7 @@ import eslint from '@eslint/js'; import { defineConfig } from 'eslint/config'; import globals from 'globals'; import tseslint from 'typescript-eslint'; +import prettierConfig from 'eslint-config-prettier/flat'; const typescriptFiles = ['{config,src}/**/*.ts']; const browserFiles = ['public/js/**/*.js']; @@ -45,4 +46,5 @@ export default defineConfig( '@typescript-eslint/no-unused-vars': 'off', }, }, + prettierConfig, ); diff --git a/package.json b/package.json index 20eabdd..43f3209 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,9 @@ "migration:generate": "typeorm-ts-node-commonjs -d src/AppDataSource.ts migration:generate", "migration:run": "typeorm-ts-node-commonjs -d src/AppDataSource.ts migration:run", "migration:revert": "typeorm-ts-node-commonjs -d src/AppDataSource.ts migration:revert", - "lint": "eslint . --max-warnings=0" + "lint": "eslint . --max-warnings=0", + "format": "prettier . --write", + "format:check": "prettier . --check" }, "dependencies": { "ajv": "^8.11.0", @@ -58,8 +60,10 @@ "@types/winston": "^2.4.4", "@types/ws": "^8.5.3", "eslint": "^9.39.5", + "eslint-config-prettier": "^10.1.8", "globals": "^16.0.0", "nodemon": "^2.0.19", + "prettier": "^3.8.0", "source-map-support": "^0.5.21", "ts-node": "^10.9.2", "typescript": "~5.9.3", diff --git a/yarn.lock b/yarn.lock index 5d8cf9b..5e5221e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1436,6 +1436,11 @@ escape-string-regexp@^4.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== +eslint-config-prettier@^10.1.8: + version "10.1.8" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz#15734ce4af8c2778cc32f0b01b37b0b5cd1ecb97" + integrity sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w== + eslint-scope@^8.4.0: version "8.4.0" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.4.0.tgz#88e646a207fad61436ffa39eb505147200655c82" @@ -2416,6 +2421,11 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== +prettier@^3.8.0: + version "3.9.10" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.9.10.tgz#ac378e432f1a7381518f2e080816bbaefa38d585" + integrity sha512-skGHTH1Yb9QvaTC4hWnRTzEYtCpApF0UMBPz+t/0ZPzP8ihefo/btKd+QA7pUE0fEwA1SbS/NmhyEcstdXOIdA== + process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" From e903b77b21bb4123b37a06b15cf2f8e47e6edc18 Mon Sep 17 00:00:00 2001 From: gkatrakazas Date: Tue, 20 Oct 2026 14:43:31 +0300 Subject: [PATCH 4/6] style: apply Prettier formatting --- .editorconfig-checker.json | 4 +- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- .github/ISSUE_TEMPLATE/feature_request.yml | 8 +- .github/workflows/code-formatting.yml | 16 +- .github/workflows/docker-image.yml | 5 +- .github/workflows/docker-push.yml | 4 +- .pre-commit-config.yaml | 2 +- README.md | 43 +- config/index.ts | 44 +- config/locale.ts | 176 +++--- config/sessionIdCookieConfig.ts | 12 +- config/titles.ts | 10 +- nodemon.json | 13 +- public/js/handle-response-code.js | 8 +- public/js/header.js | 34 +- public/js/presentation-request.js | 8 +- public/js/request-credentials.js | 78 +-- public/js/truncate-long-fields.js | 2 +- public/styles/base.css | 14 +- public/styles/checkbox.css | 20 +- public/styles/footer.css | 4 +- public/styles/header.css | 8 +- public/styles/presentation-request.css | 7 +- public/styles/presentation-success.css | 2 +- public/styles/public-definitions.css | 21 +- public/styles/variables.css | 8 +- src/AppDataSource.ts | 28 +- src/app.ts | 63 +-- src/entities/Audit.entity.ts | 40 +- src/middlewares/csrf.middleware.ts | 15 +- src/middlewares/language.middleware.ts | 17 +- .../1768995413499-create_audit_table.ts | 9 +- src/services/ExpressAppService.ts | 13 +- .../OpenidForPresentationReceivingService.ts | 129 +++-- src/services/VerifierConfigurationService.ts | 368 ++++++------- src/services/interfaces.ts | 11 +- ...penidForPresentationsConfiguration.type.ts | 2 +- src/types/index.d.ts | 2 +- src/types/language.type.ts | 2 +- src/util/generateRandomIdentifier.ts | 4 +- src/util/initializeCredentialEngine.ts | 72 ++- src/verifierRouter.ts | 511 ++++++++++-------- tsconfig.json | 32 +- 43 files changed, 999 insertions(+), 872 deletions(-) diff --git a/.editorconfig-checker.json b/.editorconfig-checker.json index a7def7f..022358b 100644 --- a/.editorconfig-checker.json +++ b/.editorconfig-checker.json @@ -1,5 +1,3 @@ { - "Exclude": [ - "^(node_modules|dist|build)/" - ] + "Exclude": ["^(node_modules|dist|build)/"] } diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index d8b58ae..969b503 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -13,7 +13,7 @@ body: id: to-reproduce attributes: label: To Reproduce - description: "Steps to reproduce the behavior" + description: 'Steps to reproduce the behavior' value: | 1. Go to '...' 2. Click on '....' diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 09f34f7..cbc0f86 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -5,15 +5,15 @@ body: id: problem-statement attributes: label: Problem Statement - description: "A clear and concise description of the problem or need you want this feature to address." + description: 'A clear and concise description of the problem or need you want this feature to address.' validations: - required: true + required: true - type: textarea id: proposed-solution attributes: label: Proposed Solution - description: "The solution you propose to address the problem." + description: 'The solution you propose to address the problem.' placeholder: | Describe your suggested solution in detail. Be sure to include: * The technical implementation and any key considerations. @@ -26,7 +26,7 @@ body: id: additional-information attributes: label: Additional Information - description: "Any other relevant details, such as context, screenshots, external references, or links to related issues or discussions." + description: 'Any other relevant details, such as context, screenshots, external references, or links to related issues or discussions.' - type: markdown attributes: diff --git a/.github/workflows/code-formatting.yml b/.github/workflows/code-formatting.yml index e25ec42..f0d3dd5 100644 --- a/.github/workflows/code-formatting.yml +++ b/.github/workflows/code-formatting.yml @@ -21,13 +21,13 @@ jobs: runs-on: ubuntu-latest steps: - - name: Check out code - uses: actions/checkout@v7 + - name: Check out code + uses: actions/checkout@v7 - - name: Set up editorconfig-checker - uses: editorconfig-checker/action-editorconfig-checker@v3.0.0 - with: - version: v4.0.2 + - name: Set up editorconfig-checker + uses: editorconfig-checker/action-editorconfig-checker@v3.0.0 + with: + version: v4.0.2 - - name: Check code formatting - run: editorconfig-checker -no-color + - name: Check code formatting + run: editorconfig-checker -no-color diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 7cc6ab8..b819090 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -13,11 +13,10 @@ concurrency: jobs: build: - # Permissions for GITHUB_TOKEN permissions: - contents: read # To check out repo - packages: read # To read NPM registry + contents: read # To check out repo + packages: read # To read NPM registry uses: wwwallet/wwwallet/.github/workflows/docker-build-push.yml@master secrets: inherit diff --git a/.github/workflows/docker-push.yml b/.github/workflows/docker-push.yml index 7a74d8b..0c11752 100644 --- a/.github/workflows/docker-push.yml +++ b/.github/workflows/docker-push.yml @@ -6,13 +6,11 @@ on: - v1.* - v2.* - jobs: build: - # Permissions for GITHUB_TOKEN permissions: - contents: read # To check out repo + contents: read # To check out repo packages: write # To read NPM registry and push Docker image uses: wwwallet/wwwallet/.github/workflows/docker-build-push.yml@master diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c189d53..f8cc346 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,5 +12,5 @@ repos: hooks: - id: editorconfig-checker name: Check .editorconfig rules - args: ["-no-color"] + args: ['-no-color'] types_or: [text] diff --git a/README.md b/README.md index 86f159c..cbfbcde 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # wwWallet Verifier + wwWallet Verifier is a standalone digital credential verifier implementing OpenID4VP. > [!NOTE] @@ -7,11 +8,13 @@ wwWallet Verifier is a standalone digital credential verifier implementing OpenI ## How to run Install dependencies + ``` yarn install ``` Run in dev mode + ``` yarn run dev ``` @@ -24,26 +27,26 @@ The verifier also expects a root CA certificate at `keys/ca.crt`, used as a trus Environment variables and how they are used: -| Variable | Purpose | Default / Notes | -| --- | --- | --- | -| `SERVICE_URL` | Full external URL of the verifier. | Default: `http://localhost:8005`. Used as the OpenID4VP client base URL, for the `direct_post` response URI and for callback URLs. | -| `SERVICE_PORT` | Port the HTTP server listens on. | Default: `8005`. | -| `APP_SECRET` | Secret used to sign the Express session cookie. | Has an insecure built-in default. Change it outside local development. | -| `WWWALLET_URL` | wwWallet frontend URL that authorization requests are sent to. | Default: `http://localhost:3000/cb`. | -| `DB_HOST` | MySQL/MariaDB host. | Default: `127.0.0.1`. | -| `DB_PORT` | MySQL/MariaDB port. | Default: `3307`. | -| `DB_USERNAME` | Database user. | Default: `root`. | -| `DB_PASSWORD` | Database password. | Default: `root`. | -| `DB_NAME` | Database name. | Default: `verifier`. | -| `PRESENTATION_FLOW_RESPONSE_MODE` | OpenID4VP `response_mode` used in authorization requests. | Default: `direct_post.jwt`. Supported values: `direct_post`, `direct_post.jwt`. | -| `TRUSTED_ISSUERS` | Comma-separated list of credential issuer identifiers whose certificates are added as trusted roots. | Default: `http://localhost:8003/openid`. For each issuer, the verifier fetches `/.well-known/openid-credential-issuer` and, if it has an `mdoc_iacas_uri`, trusts the certificates listed there (relevant for mso_mdoc). | -| `CLOCK_TOLERANCE` | Allowed clock skew (seconds) when validating credential and presentation timestamps. | Default: `60`. | -| `SESSION_ID_COOKIE_MAX_AGE` | Max age (milliseconds) of the `session_id` cookie. | Default: `900000` (15 minutes). | -| `SESSION_ID_COOKIE_SECURE` | Set the `Secure` flag on the `session_id` cookie. | Default: `false`. Only the exact value `true` enables it. Set it when served over HTTPS. | -| `SITE_NAME` | Web app manifest `name`. | Default: `wwWallet Verifier`. | -| `SITE_SHORT_NAME` | Web app manifest `short_name`. | Default: `wwWallet Verifier`. | -| `SITE_THEME_COLOR` | Web app manifest `theme_color`. | Default: `#4d7e3e`. Quote hex values in `.env` (`"#4d7e3e"`), otherwise `#` starts a comment. | -| `SITE_BACKGROUND_COLOR` | Web app manifest `background_color`. | Default: `#4d7e3e`. Quote hex values in `.env` (`"#4d7e3e"`), otherwise `#` starts a comment. | +| Variable | Purpose | Default / Notes | +| --------------------------------- | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `SERVICE_URL` | Full external URL of the verifier. | Default: `http://localhost:8005`. Used as the OpenID4VP client base URL, for the `direct_post` response URI and for callback URLs. | +| `SERVICE_PORT` | Port the HTTP server listens on. | Default: `8005`. | +| `APP_SECRET` | Secret used to sign the Express session cookie. | Has an insecure built-in default. Change it outside local development. | +| `WWWALLET_URL` | wwWallet frontend URL that authorization requests are sent to. | Default: `http://localhost:3000/cb`. | +| `DB_HOST` | MySQL/MariaDB host. | Default: `127.0.0.1`. | +| `DB_PORT` | MySQL/MariaDB port. | Default: `3307`. | +| `DB_USERNAME` | Database user. | Default: `root`. | +| `DB_PASSWORD` | Database password. | Default: `root`. | +| `DB_NAME` | Database name. | Default: `verifier`. | +| `PRESENTATION_FLOW_RESPONSE_MODE` | OpenID4VP `response_mode` used in authorization requests. | Default: `direct_post.jwt`. Supported values: `direct_post`, `direct_post.jwt`. | +| `TRUSTED_ISSUERS` | Comma-separated list of credential issuer identifiers whose certificates are added as trusted roots. | Default: `http://localhost:8003/openid`. For each issuer, the verifier fetches `/.well-known/openid-credential-issuer` and, if it has an `mdoc_iacas_uri`, trusts the certificates listed there (relevant for mso_mdoc). | +| `CLOCK_TOLERANCE` | Allowed clock skew (seconds) when validating credential and presentation timestamps. | Default: `60`. | +| `SESSION_ID_COOKIE_MAX_AGE` | Max age (milliseconds) of the `session_id` cookie. | Default: `900000` (15 minutes). | +| `SESSION_ID_COOKIE_SECURE` | Set the `Secure` flag on the `session_id` cookie. | Default: `false`. Only the exact value `true` enables it. Set it when served over HTTPS. | +| `SITE_NAME` | Web app manifest `name`. | Default: `wwWallet Verifier`. | +| `SITE_SHORT_NAME` | Web app manifest `short_name`. | Default: `wwWallet Verifier`. | +| `SITE_THEME_COLOR` | Web app manifest `theme_color`. | Default: `#4d7e3e`. Quote hex values in `.env` (`"#4d7e3e"`), otherwise `#` starts a comment. | +| `SITE_BACKGROUND_COLOR` | Web app manifest `background_color`. | Default: `#4d7e3e`. Quote hex values in `.env` (`"#4d7e3e"`), otherwise `#` starts a comment. | ## Pre-commit diff --git a/config/index.ts b/config/index.ts index f00df7d..cbccb82 100644 --- a/config/index.ts +++ b/config/index.ts @@ -1,39 +1,41 @@ -import fs from "node:fs"; -import path from "node:path"; +import fs from 'node:fs'; +import path from 'node:path'; import dotenv from 'dotenv'; dotenv.config(); -const ROOT_CA_PATH = path.resolve(process.cwd(), "./keys/ca.crt"); -const rootCa = fs.readFileSync(ROOT_CA_PATH, "utf8"); +const ROOT_CA_PATH = path.resolve(process.cwd(), './keys/ca.crt'); +const rootCa = fs.readFileSync(ROOT_CA_PATH, 'utf8'); export const config = { - url: String(process.env.SERVICE_URL || "http://localhost:8005"), - port: parseInt(process.env.SERVICE_PORT || "8005"), - appSecret: String(process.env.APP_SECRET || "dsfkwfkwfwdfdsfSaSe2e34r4frwr42rAFdsf2lfmfsmklfwmer"), + url: String(process.env.SERVICE_URL || 'http://localhost:8005'), + port: parseInt(process.env.SERVICE_PORT || '8005'), + appSecret: String( + process.env.APP_SECRET || 'dsfkwfkwfwdfdsfSaSe2e34r4frwr42rAFdsf2lfmfsmklfwmer', + ), db: { - host: String(process.env.DB_HOST || "127.0.0.1"), - port: String(process.env.DB_PORT || "3307"), - username: String(process.env.DB_USERNAME || "root"), - password: String(process.env.DB_PASSWORD || "root"), - dbname: String(process.env.DB_NAME || "verifier"), + host: String(process.env.DB_HOST || '127.0.0.1'), + port: String(process.env.DB_PORT || '3307'), + username: String(process.env.DB_USERNAME || 'root'), + password: String(process.env.DB_PASSWORD || 'root'), + dbname: String(process.env.DB_NAME || 'verifier'), }, presentationFlow: { - response_mode: String(process.env.PRESENTATION_FLOW_RESPONSE_MODE || "direct_post.jwt"), + response_mode: String(process.env.PRESENTATION_FLOW_RESPONSE_MODE || 'direct_post.jwt'), }, - wwwalletURL: process.env.WWWALLET_URL || "http://localhost:3000/cb", + wwwalletURL: process.env.WWWALLET_URL || 'http://localhost:3000/cb', trustedRootCertificates: [rootCa], trustedIssuers: process.env.TRUSTED_ISSUERS ? process.env.TRUSTED_ISSUERS.split(',') - : ["http://localhost:8003/openid"], + : ['http://localhost:8003/openid'], sessionIdCookieConfiguration: { - maxAge: parseInt(process.env.SESSION_ID_COOKIE_MAX_AGE || "900000", 10), + maxAge: parseInt(process.env.SESSION_ID_COOKIE_MAX_AGE || '900000', 10), secure: process.env.SESSION_ID_COOKIE_SECURE === 'true' ? true : false, }, - clockTolerance: parseInt(process.env.CLOCK_TOLERANCE || "60", 10), + clockTolerance: parseInt(process.env.CLOCK_TOLERANCE || '60', 10), siteConfig: { - name: process.env.SITE_NAME || "wwWallet Verifier", - short_name: process.env.SITE_SHORT_NAME || "wwWallet Verifier", - theme_color: process.env.SITE_THEME_COLOR || "#4d7e3e", - background_color: process.env.SITE_BACKGROUND_COLOR || "#4d7e3e", + name: process.env.SITE_NAME || 'wwWallet Verifier', + short_name: process.env.SITE_SHORT_NAME || 'wwWallet Verifier', + theme_color: process.env.SITE_THEME_COLOR || '#4d7e3e', + background_color: process.env.SITE_BACKGROUND_COLOR || '#4d7e3e', }, }; diff --git a/config/locale.ts b/config/locale.ts index 22c6c4a..c5cea73 100644 --- a/config/locale.ts +++ b/config/locale.ts @@ -1,137 +1,137 @@ -import { config } from "./index.js"; +import { config } from './index.js'; const locale = { en: { pageTitles: { - "manage-certificates": "Manage Trusted Certificates", - "public-definitions": "Credential Verification Requests", - "handle-response-code": "Processing Verification Response", - "error": "Error", - "presentation-success": "Credential Verification Results", - "request-credentials": "Select Credential Claims", - "request-custom-credential": "Create Custom Credential Request", - "presentation-request": "Present Credentials", + 'manage-certificates': 'Manage Trusted Certificates', + 'public-definitions': 'Credential Verification Requests', + 'handle-response-code': 'Processing Verification Response', + error: 'Error', + 'presentation-success': 'Credential Verification Results', + 'request-credentials': 'Select Credential Claims', + 'request-custom-credential': 'Create Custom Credential Request', + 'presentation-request': 'Present Credentials', }, definitions: { - title: "Present your Verifiable Credentials", - subtitle: "Choose a predefined request or create a custom one", - customRequestTitle: "Custom Request", - customRequestDescription: "Import the DCQL query of your choice", + title: 'Present your Verifiable Credentials', + subtitle: 'Choose a predefined request or create a custom one', + customRequestTitle: 'Custom Request', + customRequestDescription: 'Import the DCQL query of your choice', }, requestCredentials: { - title: "Configure your Presentation Request", - subtitle: "Choose format and fields, then request a QR", + title: 'Configure your Presentation Request', + subtitle: 'Choose format and fields, then request a QR', format: { - label: "Credential Format", - sdjwt: "SD-JWT", - mdoc: "mDoc", + label: 'Credential Format', + sdjwt: 'SD-JWT', + mdoc: 'mDoc', }, attributes: { - label: "Attributes to Request", - all: "Select all", - none: "Select none", + label: 'Attributes to Request', + all: 'Select all', + none: 'Select none', }, schema: { - label: "Edit Schema", - hint: "Keep the default unless you need a custom scheme", - default: "openid4vp://cb", + label: 'Edit Schema', + hint: 'Keep the default unless you need a custom scheme', + default: 'openid4vp://cb', }, clientIdPrefix: { - label: "Client ID Prefix" - } + label: 'Client ID Prefix', + }, }, customRequestCredential: { - title: "Edit Request", - subtitle: "Edit scheme and DCQL query, then submit", + title: 'Edit Request', + subtitle: 'Edit scheme and DCQL query, then submit', scheme: { - label: "Edit Schema", - default: "openid4vp://cb", + label: 'Edit Schema', + default: 'openid4vp://cb', }, dcqlQuery: { - label: "DCQL Query", - hint: "You can edit this template as needed", - } - + label: 'DCQL Query', + hint: 'You can edit this template as needed', + }, }, presentationRequest: { - title: "Present your Credentials", - subtitle: "Scan or open the request in your wallet", + title: 'Present your Credentials', + subtitle: 'Scan or open the request in your wallet', buttons: { - OpenWithwwwallet: "Open with wwWallet", - OpenWithNativeWallet: "Open with a native wallet", - } + OpenWithwwwallet: 'Open with wwWallet', + OpenWithNativeWallet: 'Open with a native wallet', + }, }, presentationSuccess: { loading: { - title: "Verifying Presentation...", - subtitle: "Please wait while we verify your credentials.", + title: 'Verifying Presentation...', + subtitle: 'Please wait while we verify your credentials.', }, - title: "Presentation Successful", - verifiedOn: "Verified on", + title: 'Presentation Successful', + verifiedOn: 'Verified on', claims: { - title: "Verified Claims", - name: "Name", - value: "Value", - additionalInfoTitle: "Additional Info", + title: 'Verified Claims', + name: 'Name', + value: 'Value', + additionalInfoTitle: 'Additional Info', descriptorFriendlyNames: { - CustomPid: "PID", - pidSdJwt: "PID (SD-JWT)", - minimalSdJwtPID: "PID (SD-JWT)", - pidMsoMdoc: "PID (mDoc)", - Bachelor: "Bachelor Diploma", - minimalBachelor: "Bachelor Diploma", - EuropeanHealthInsuranceCard: "European Health Insurance Card (EHIC)", - POR: "Proof of Representation (POR)", + CustomPid: 'PID', + pidSdJwt: 'PID (SD-JWT)', + minimalSdJwtPID: 'PID (SD-JWT)', + pidMsoMdoc: 'PID (mDoc)', + Bachelor: 'Bachelor Diploma', + minimalBachelor: 'Bachelor Diploma', + EuropeanHealthInsuranceCard: 'European Health Insurance Card (EHIC)', + POR: 'Proof of Representation (POR)', }, }, json: { - title: "JSON Dataset", - copy: "Copy", - copied: "Copied", + title: 'JSON Dataset', + copy: 'Copy', + copied: 'Copied', }, }, - requestCredential: "Request Credentials", + requestCredential: 'Request Credentials', error: { - title: "An error occurred", - return: "Return", + title: 'An error occurred', + return: 'Return', }, header: { - title: "", // set to empty if you don't want text next to logo - home: "Home", - requestCredentials: "Request Credentials", - manageCertificates: "Manage Certificates", + title: '', // set to empty if you don't want text next to logo + home: 'Home', + requestCredentials: 'Request Credentials', + manageCertificates: 'Manage Certificates', }, footer: { - services: "Services", - documentation: "Documentation", - adminLogin: "Verifier Panel", - contact: "Contact", + services: 'Services', + documentation: 'Documentation', + adminLogin: 'Verifier Panel', + contact: 'Contact', }, index: { - verifyCredential: "Request Credentials", + verifyCredential: 'Request Credentials', heading: config.siteConfig.name, - oid4vpProfile: "OpenID4VP Interoperability Profile", + oid4vpProfile: 'OpenID4VP Interoperability Profile', openid4vp: [ - ["Response Mode", "direct_post.jwt [OpenID4VP]"], - ["Request Method", "request_uri signed [JAR]"], - ["Client ID Prefix", "x509_san_dns, x509_hash"], - ["Credential Format", "dc+sd-jwt, mso_mdoc"], + ['Response Mode', 'direct_post.jwt [OpenID4VP]'], + ['Request Method', 'request_uri signed [JAR]'], + ['Client ID Prefix', 'x509_san_dns, x509_hash'], + ['Credential Format', 'dc+sd-jwt, mso_mdoc'], ], - paragraph: "This website is a proof-of-concept service designed to perform verifications of Verifiable Credentials (VCs) in SD-JWT or mDoc formats, supporting the wwWallet ecosystem. It conducts only sample verifications to showcase credential checks (not valid for real-world use).", + paragraph: + 'This website is a proof-of-concept service designed to perform verifications of Verifiable Credentials (VCs) in SD-JWT or mDoc formats, supporting the wwWallet ecosystem. It conducts only sample verifications to showcase credential checks (not valid for real-world use).', }, manageCertificates: { - title: "Manage Certificates", - subtitle: "View trusted root certificates and add new ones", - addCertificateLabel: "Add X.509 Certificate (PEM or Base64)", - certificatePlaceholder: "-----BEGIN CERTIFICATE----- ...", - certificateHint: "Paste the full PEM block or Base64 content", - addButton: "Add X.509 Certificate", - trustedRootsTitle: "Trusted Root Certificates", - noCertificates: "No trusted root certificates found.", - pemSummary: "X.509 Certificate PEM", - base64Summary: "X.509 Certificate Base64", + title: 'Manage Certificates', + subtitle: 'View trusted root certificates and add new ones', + addCertificateLabel: 'Add X.509 Certificate (PEM or Base64)', + certificatePlaceholder: '-----BEGIN CERTIFICATE----- ...', + certificateHint: 'Paste the full PEM block or Base64 content', + addButton: 'Add X.509 Certificate', + trustedRootsTitle: 'Trusted Root Certificates', + noCertificates: 'No trusted root certificates found.', + pemSummary: 'X.509 Certificate PEM', + base64Summary: 'X.509 Certificate Base64', }, - } -} + }, +}; export default locale; diff --git a/config/sessionIdCookieConfig.ts b/config/sessionIdCookieConfig.ts index e185abe..b46fb26 100644 --- a/config/sessionIdCookieConfig.ts +++ b/config/sessionIdCookieConfig.ts @@ -1,10 +1,14 @@ -import { Response } from "express"; -import { config } from "."; +import { Response } from 'express'; +import { config } from '.'; // @ts-ignore -export const secure = config?.sessionIdCookieConfiguration?.secure ? config.sessionIdCookieConfiguration.secure : false; +export const secure = config?.sessionIdCookieConfiguration?.secure + ? config.sessionIdCookieConfiguration.secure + : false; // @ts-ignore -export const maxAge = config?.sessionIdCookieConfiguration?.maxAge ? config.sessionIdCookieConfiguration.maxAge : 900000; // 15 mins default +export const maxAge = config?.sessionIdCookieConfiguration?.maxAge + ? config.sessionIdCookieConfiguration.maxAge + : 900000; // 15 mins default export function addSessionIdCookieToResponse(res: Response, session_id: string) { res.cookie('session_id', session_id, { diff --git a/config/titles.ts b/config/titles.ts index 057d7f9..d25a404 100644 --- a/config/titles.ts +++ b/config/titles.ts @@ -1,11 +1,11 @@ // titles.ts -import { config } from "."; -import locale from "./locale"; +import { config } from '.'; +import locale from './locale'; -export const siteTitle = config?.siteConfig?.name || "wwWallet Verifier"; +export const siteTitle = config?.siteConfig?.name || 'wwWallet Verifier'; -export function titleWithSiteName(titleKey: string, currentLocale = "en"): string { - if((locale as any)[currentLocale].pageTitles[titleKey]) { +export function titleWithSiteName(titleKey: string, currentLocale = 'en'): string { + if ((locale as any)[currentLocale].pageTitles[titleKey]) { return `${(locale as any)[currentLocale].pageTitles[titleKey]} | ${siteTitle}`; } return siteTitle; diff --git a/nodemon.json b/nodemon.json index 1047b10..1e5df01 100644 --- a/nodemon.json +++ b/nodemon.json @@ -1,12 +1,5 @@ { - "ignore": [ - "README.md", - "dist/", - "node_modules/", - "samples/" - ], - "watch": [ - "." - ], - "ext": "ts,js,pug" + "ignore": ["README.md", "dist/", "node_modules/", "samples/"], + "watch": ["."], + "ext": "ts,js,pug" } diff --git a/public/js/handle-response-code.js b/public/js/handle-response-code.js index 4ef75a3..95e1a09 100644 --- a/public/js/handle-response-code.js +++ b/public/js/handle-response-code.js @@ -1,14 +1,14 @@ -const form = document.getElementById("ResponseCode"); +const form = document.getElementById('ResponseCode'); if (window.location.hash.startsWith('#response_code')) { - console.log("Got response code") + console.log('Got response code'); const response_code = window.location.hash.split('=')[1]; - console.log("response code = ", response_code) + console.log('response code = ', response_code); if (response_code) { // Create a hidden input field to send the response_code with the form const input = document.createElement('input'); input.type = 'hidden'; - input.name = 'response_code'; // The name of the form field + input.name = 'response_code'; // The name of the form field input.value = response_code; form.appendChild(input); } diff --git a/public/js/header.js b/public/js/header.js index 5f970ca..2d87d8e 100644 --- a/public/js/header.js +++ b/public/js/header.js @@ -1,51 +1,51 @@ (() => { - "use strict"; + 'use strict'; // ------------------------- // Active nav item // ------------------------- - const path = (window.location.pathname || "/").replace(/\/$/, "") || "/"; - const links = document.querySelectorAll("#Header .menu-item a"); + const path = (window.location.pathname || '/').replace(/\/$/, '') || '/'; + const links = document.querySelectorAll('#Header .menu-item a'); for (const link of links) { - const href = (link.getAttribute("href") || "").replace(/\/$/, "") || "/"; + const href = (link.getAttribute('href') || '').replace(/\/$/, '') || '/'; if (href === path) { - link.classList.add("is-active"); - link.setAttribute("aria-current", "page"); + link.classList.add('is-active'); + link.setAttribute('aria-current', 'page'); } } // ------------------------- // Mobile menu toggle // ------------------------- - const toggleBtn = document.getElementById("menu-toggle-button"); - const menu = document.getElementById("main-menu"); + const toggleBtn = document.getElementById('menu-toggle-button'); + const menu = document.getElementById('main-menu'); if (!toggleBtn || !menu) return; const setOpen = (open) => { - menu.classList.toggle("show-menu", open); - toggleBtn.setAttribute("aria-expanded", open ? "true" : "false"); + menu.classList.toggle('show-menu', open); + toggleBtn.setAttribute('aria-expanded', open ? 'true' : 'false'); }; - const isOpen = () => menu.classList.contains("show-menu"); + const isOpen = () => menu.classList.contains('show-menu'); - toggleBtn.addEventListener("click", () => setOpen(!isOpen())); + toggleBtn.addEventListener('click', () => setOpen(!isOpen())); - document.addEventListener("click", (e) => { + document.addEventListener('click', (e) => { if (!isOpen()) return; if (toggleBtn.contains(e.target) || menu.contains(e.target)) return; setOpen(false); }); - document.addEventListener("keydown", (e) => { - if (e.key === "Escape" && isOpen()) setOpen(false); + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape' && isOpen()) setOpen(false); }); - const mq = window.matchMedia("(min-width: 1069px)"); + const mq = window.matchMedia('(min-width: 1069px)'); const onChange = () => { if (mq.matches) setOpen(false); }; - mq.addEventListener?.("change", onChange) ?? mq.addListener(onChange); + mq.addEventListener?.('change', onChange) ?? mq.addListener(onChange); })(); diff --git a/public/js/presentation-request.js b/public/js/presentation-request.js index d9bfbd3..96c4493 100644 --- a/public/js/presentation-request.js +++ b/public/js/presentation-request.js @@ -1,14 +1,14 @@ (function () { const path = window.location.pathname; - const segments = path.split("/").filter(Boolean); + const segments = path.split('/').filter(Boolean); const presentationRequestId = segments[segments.length - 1]; if (!presentationRequestId) return; setInterval(() => { fetch( - "/verifier/public/definitions/presentation-request/status/" + - encodeURIComponent(presentationRequestId), - { method: "GET" } + '/verifier/public/definitions/presentation-request/status/' + + encodeURIComponent(presentationRequestId), + { method: 'GET' }, ) .then((response) => { if (!response.ok) throw new Error(`HTTP error with status ${response.status}`); diff --git a/public/js/request-credentials.js b/public/js/request-credentials.js index 97871b3..dceef62 100644 --- a/public/js/request-credentials.js +++ b/public/js/request-credentials.js @@ -1,13 +1,16 @@ -document.addEventListener("DOMContentLoaded", () => { +document.addEventListener('DOMContentLoaded', () => { const typeRadios = document.querySelectorAll('input[name="type"]'); - const attributesContainer = document.getElementById("attributes-container"); - const scriptEl = document.currentScript || document.querySelector('script[src^="/js/request-credentials.js"]'); + const attributesContainer = document.getElementById('attributes-container'); + const scriptEl = + document.currentScript || document.querySelector('script[src^="/js/request-credentials.js"]'); const dcqlQuery = JSON.parse(scriptEl.dataset.dcqlQuery); - const selectAllLabel = scriptEl.dataset.selectAllLabel || "Select all"; + const selectAllLabel = scriptEl.dataset.selectAllLabel || 'Select all'; - const form = document.querySelector("form"); - const dcqlQueryInput = document.getElementById("dcql-query-input"); - const typeToFormat = new Map(Array.from(typeRadios).map((radio) => [radio.value, radio.dataset.format])); + const form = document.querySelector('form'); + const dcqlQueryInput = document.getElementById('dcql-query-input'); + const typeToFormat = new Map( + Array.from(typeRadios).map((radio) => [radio.value, radio.dataset.format]), + ); function getCredentialByType(type) { const format = typeToFormat.get(type); @@ -25,18 +28,18 @@ document.addEventListener("DOMContentLoaded", () => { const credential = getCredentialByType(type); if (!credential) return; - const masterWrapper = document.createElement("div"); - masterWrapper.classList.add("checkbox-wrapper", "checkbox-wrapper--master"); + const masterWrapper = document.createElement('div'); + masterWrapper.classList.add('checkbox-wrapper', 'checkbox-wrapper--master'); - const masterCheckbox = document.createElement("input"); - masterCheckbox.type = "checkbox"; - masterCheckbox.id = "select-all-attributes"; + const masterCheckbox = document.createElement('input'); + masterCheckbox.type = 'checkbox'; + masterCheckbox.id = 'select-all-attributes'; - const masterLabel = document.createElement("label"); + const masterLabel = document.createElement('label'); masterLabel.htmlFor = masterCheckbox.id; masterLabel.textContent = selectAllLabel; - masterCheckbox.addEventListener("change", () => { + masterCheckbox.addEventListener('change', () => { const attributeCheckboxes = getAttributeCheckboxes(); attributeCheckboxes.forEach((checkbox) => { checkbox.checked = masterCheckbox.checked; @@ -49,21 +52,21 @@ document.addEventListener("DOMContentLoaded", () => { attributesContainer.appendChild(masterWrapper); (credential.claims || []).forEach((claim, idx) => { - const label = claim.path.join("."); - const value = claim.path.join("."); - const fieldWrapper = document.createElement("div"); - fieldWrapper.classList.add("checkbox-wrapper"); - - const input = document.createElement("input"); - input.type = "checkbox"; - input.name = "attributes[]"; + const label = claim.path.join('.'); + const value = claim.path.join('.'); + const fieldWrapper = document.createElement('div'); + fieldWrapper.classList.add('checkbox-wrapper'); + + const input = document.createElement('input'); + input.type = 'checkbox'; + input.name = 'attributes[]'; input.value = value; input.id = `attr-${idx}`; - const labelElement = document.createElement("label"); + const labelElement = document.createElement('label'); labelElement.htmlFor = input.id; labelElement.textContent = label; - input.addEventListener("change", updateSelectionState); + input.addEventListener('change', updateSelectionState); fieldWrapper.appendChild(input); fieldWrapper.appendChild(labelElement); attributesContainer.appendChild(fieldWrapper); @@ -78,28 +81,34 @@ document.addEventListener("DOMContentLoaded", () => { function updateSelectionState() { const masterCheckbox = attributesContainer.querySelector('#select-all-attributes'); const attributeCheckboxes = getAttributeCheckboxes(); - const submitButton = document.querySelector(".request-qr"); - const checkedCount = Array.from(attributeCheckboxes).filter((checkbox) => checkbox.checked).length; + const submitButton = document.querySelector('.request-qr'); + const checkedCount = Array.from(attributeCheckboxes).filter( + (checkbox) => checkbox.checked, + ).length; const anySelected = checkedCount > 0; - const allSelected = attributeCheckboxes.length > 0 && checkedCount === attributeCheckboxes.length; + const allSelected = + attributeCheckboxes.length > 0 && checkedCount === attributeCheckboxes.length; if (masterCheckbox) { masterCheckbox.disabled = attributeCheckboxes.length === 0; masterCheckbox.checked = allSelected; masterCheckbox.indeterminate = !allSelected && anySelected; - masterCheckbox.setAttribute("aria-checked", masterCheckbox.indeterminate ? "mixed" : String(masterCheckbox.checked)); + masterCheckbox.setAttribute( + 'aria-checked', + masterCheckbox.indeterminate ? 'mixed' : String(masterCheckbox.checked), + ); } submitButton.disabled = !anySelected; } typeRadios.forEach((radio) => { - radio.addEventListener("change", (e) => { + radio.addEventListener('change', (e) => { renderFields(e.target.value); }); }); - form.addEventListener("submit", (e) => { + form.addEventListener('submit', (e) => { e.preventDefault(); const selectedType = getSelectedType(); if (!selectedType) return; @@ -107,11 +116,12 @@ document.addEventListener("DOMContentLoaded", () => { const credential = getCredentialByType(selectedType); if (!credential) return; - const selectedClaims = Array.from(attributesContainer.querySelectorAll('input[type="checkbox"]:checked')) - .map(cb => cb.value); + const selectedClaims = Array.from( + attributesContainer.querySelectorAll('input[type="checkbox"]:checked'), + ).map((cb) => cb.value); - const filteredClaims = (credential.claims || []).filter(claim => - selectedClaims.includes(claim.path.join(".")) + const filteredClaims = (credential.claims || []).filter((claim) => + selectedClaims.includes(claim.path.join('.')), ); const filteredCredential = { ...credential, claims: filteredClaims }; diff --git a/public/js/truncate-long-fields.js b/public/js/truncate-long-fields.js index f2adacc..18c5ddd 100644 --- a/public/js/truncate-long-fields.js +++ b/public/js/truncate-long-fields.js @@ -8,7 +8,7 @@ document.addEventListener('DOMContentLoaded', () => { return value; }; - document.querySelectorAll('.claim-value').forEach(td => { + document.querySelectorAll('.claim-value').forEach((td) => { try { const inferredType = td.getAttribute('data-inferred-type') || 'text'; if (inferredType !== 'text') { diff --git a/public/styles/base.css b/public/styles/base.css index 27f0430..1e9ebde 100644 --- a/public/styles/base.css +++ b/public/styles/base.css @@ -88,7 +88,7 @@ h4 { } p { - margin: 0 0 0.50em; + margin: 0 0 0.5em; font-size: 1rem; color: #444; } @@ -135,7 +135,10 @@ a { font-weight: 700; text-decoration: none; - transition: background-color 0.2s ease, transform 0.1s ease, opacity 0.2s ease; + transition: + background-color 0.2s ease, + transform 0.1s ease, + opacity 0.2s ease; } .Btn:hover { @@ -306,7 +309,7 @@ textarea.form-control { transform 180ms ease; } -.form-field .radio-field:has(input[type="radio"]:checked) { +.form-field .radio-field:has(input[type='radio']:checked) { border-color: var(--primary-main-color); box-shadow: 0 0 0 1px color-mix(in srgb, var(--primary-main-color) 30%, transparent) inset, @@ -314,11 +317,11 @@ textarea.form-control { font-weight: 600; } -.form-field .radio-field:has(input[type="radio"]:checked) .format-radio-subtitle { +.form-field .radio-field:has(input[type='radio']:checked) .format-radio-subtitle { font-weight: 400; } -.form-field .radio-field input[type="radio"] { +.form-field .radio-field input[type='radio'] { position: absolute; opacity: 0; pointer-events: none; @@ -339,7 +342,6 @@ textarea.form-control { opacity: 0.75; } - /* Mobile */ @media (max-width: 768px) { .card__container { diff --git a/public/styles/checkbox.css b/public/styles/checkbox.css index 7a60502..d217165 100644 --- a/public/styles/checkbox.css +++ b/public/styles/checkbox.css @@ -14,7 +14,7 @@ } /* Hide the native checkbox */ -.checkbox-wrapper input[type="checkbox"] { +.checkbox-wrapper input[type='checkbox'] { position: absolute; opacity: 0; pointer-events: none; @@ -36,7 +36,7 @@ /* Draw the box */ .checkbox-wrapper label::before { - content: ""; + content: ''; position: absolute; left: 0; top: 0rem; @@ -46,12 +46,14 @@ border-radius: 4px; background: #fff; box-sizing: border-box; - transition: background-color 0.15s ease, border-color 0.15s ease; + transition: + background-color 0.15s ease, + border-color 0.15s ease; } /* Draw the checkmark (hidden by default) */ .checkbox-wrapper label::after { - content: ""; + content: ''; position: absolute; left: 6px; top: 1px; @@ -65,28 +67,28 @@ } /* Checked state */ -.checkbox-wrapper input[type="checkbox"]:checked+label::before { +.checkbox-wrapper input[type='checkbox']:checked + label::before { background: var(--primary-main-color); border-color: var(--primary-main-color); } -.checkbox-wrapper input[type="checkbox"]:checked+label::after { +.checkbox-wrapper input[type='checkbox']:checked + label::after { opacity: 1; } /* Disabled state */ -.checkbox-wrapper input[type="checkbox"]:disabled+label { +.checkbox-wrapper input[type='checkbox']:disabled + label { color: #9ca3af; cursor: not-allowed; } -.checkbox-wrapper input[type="checkbox"]:disabled+label::before { +.checkbox-wrapper input[type='checkbox']:disabled + label::before { background: #d1d5db; border-color: #9ca3af; } /* Keyboard focus (since input is hidden) */ -.checkbox-wrapper input[type="checkbox"]:focus-visible+label::before { +.checkbox-wrapper input[type='checkbox']:focus-visible + label::before { outline: 3px solid color-mix(in srgb, var(--primary-main-color) 40%, transparent); outline-offset: 2px; } diff --git a/public/styles/footer.css b/public/styles/footer.css index a902479..81ff453 100644 --- a/public/styles/footer.css +++ b/public/styles/footer.css @@ -56,7 +56,9 @@ .footer__list a { color: rgba(255, 255, 255, 0.92); text-decoration: none; - transition: opacity 0.2s ease, text-decoration-color 0.2s ease; + transition: + opacity 0.2s ease, + text-decoration-color 0.2s ease; overflow-wrap: anywhere; } diff --git a/public/styles/header.css b/public/styles/header.css index a4bef95..31f87fa 100644 --- a/public/styles/header.css +++ b/public/styles/header.css @@ -38,7 +38,11 @@ #Header .header-border { width: 100%; height: 2px; - background: linear-gradient(358.5deg, var(--secondary-light-color) 0.09%, var(--primary-main-color)); + background: linear-gradient( + 358.5deg, + var(--secondary-light-color) 0.09%, + var(--primary-main-color) + ); } /* Menu (desktop) */ @@ -69,7 +73,7 @@ } #Header li.menu-item a.is-active::after { - content: ""; + content: ''; position: absolute; left: 0; bottom: -6px; diff --git a/public/styles/presentation-request.css b/public/styles/presentation-request.css index 79aae2c..43371d1 100644 --- a/public/styles/presentation-request.css +++ b/public/styles/presentation-request.css @@ -73,7 +73,7 @@ } .details-toggle summary::before { - content: "▸"; + content: '▸'; font-size: 22px; transition: transform 0.2s ease; } @@ -88,8 +88,9 @@ .dcql-query { width: 100%; - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", - "Courier New", monospace; + font-family: + ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', + monospace; background: color-mix(in srgb, var(--primary-main-color) 3%, #fff); border: 1px solid rgba(0, 0, 0, 0.12); diff --git a/public/styles/presentation-success.css b/public/styles/presentation-success.css index af43133..31a0b4d 100644 --- a/public/styles/presentation-success.css +++ b/public/styles/presentation-success.css @@ -213,7 +213,7 @@ } .details-toggle summary::before { - content: "▸"; + content: '▸'; font-size: 22px; transition: transform 0.2s ease; } diff --git a/public/styles/public-definitions.css b/public/styles/public-definitions.css index 9ed35f6..e6c9012 100644 --- a/public/styles/public-definitions.css +++ b/public/styles/public-definitions.css @@ -26,7 +26,10 @@ a.card.def-card { background-color: white; overflow: hidden; - transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease; + transition: + transform 0.18s ease, + box-shadow 0.18s ease, + border-color 0.18s ease; will-change: transform; backface-visibility: hidden; @@ -35,7 +38,7 @@ a.card.def-card { /* left accent strip */ .def-card::before { - content: ""; + content: ''; position: absolute; left: 0; top: 0; @@ -48,7 +51,7 @@ a.card.def-card { /* chevron hint */ .def-card::after { - content: "›"; + content: '›'; position: absolute; color: var(--primary-dark-color); right: 15px; @@ -57,7 +60,9 @@ a.card.def-card { line-height: 1; opacity: 0.5; transform: translateX(0); - transition: transform 0.2s ease, opacity 0.2s ease; + transition: + transform 0.2s ease, + opacity 0.2s ease; pointer-events: none; } @@ -95,9 +100,11 @@ a.card.def-card { /* Accent card (Custom Request) */ .def-card--accent { border: 1px solid color-mix(in srgb, var(--primary-main-color) 30%, #e5e7eb); - background: linear-gradient(135deg, - color-mix(in srgb, var(--primary-main-color) 5%, #fff), - #ffffff); + background: linear-gradient( + 135deg, + color-mix(in srgb, var(--primary-main-color) 5%, #fff), + #ffffff + ); } .def-card--accent::before { diff --git a/public/styles/variables.css b/public/styles/variables.css index 162fbd6..8a451e5 100644 --- a/public/styles/variables.css +++ b/public/styles/variables.css @@ -2,15 +2,15 @@ --primary-main-color: #4d7e3e; --primary-light-color: #71a66e; --primary-lighter-color: #a0c39c; - --primary-dark-color: #3D6431; - --primary-darker-color: #2E4B25; + --primary-dark-color: #3d6431; + --primary-darker-color: #2e4b25; --secondary-color: #949494; - --secondary-light-color: #E0E1DD; + --secondary-light-color: #e0e1dd; --light-gray: #f5f5f5; --dark-gray: #333333; --error-red: rgb(209, 3, 3); --link-light-blue: rgb(0, 162, 255); - --btn-yellow: #F9CA1A; + --btn-yellow: #f9ca1a; --btn-yellow-hov: #caa418; --main-font: 'Roboto Slab', sans-serif; } diff --git a/src/AppDataSource.ts b/src/AppDataSource.ts index 7ff099f..5bc22aa 100644 --- a/src/AppDataSource.ts +++ b/src/AppDataSource.ts @@ -1,32 +1,30 @@ -import { DataSource } from "typeorm"; -import { config } from "../config"; +import { DataSource } from 'typeorm'; +import { config } from '../config'; // Initialize DB connection const AppDataSource: DataSource = new DataSource({ - type: "mysql", + type: 'mysql', host: config.db.host, port: Number(config.db.port), username: config.db.username, password: config.db.password, database: config.db.dbname, - entities: [__dirname + "/entities/*.entity.{js,ts}"], - migrations: [ - __dirname + "/migrations/*.{js,ts}" - ], - synchronize: false + entities: [__dirname + '/entities/*.entity.{js,ts}'], + migrations: [__dirname + '/migrations/*.{js,ts}'], + synchronize: false, }); export async function initDataSource() { let connected = false; - console.log("Connecting with DB..."); + console.log('Connecting with DB...'); await AppDataSource.initialize() .then(() => { - console.log("App Data Source has been initialized!"); + console.log('App Data Source has been initialized!'); connected = true; }) .catch((err) => { - console.error("Error during Data Source initialization", err); + console.error('Error during Data Source initialization', err); }); // if not connected, then retry in loop @@ -34,16 +32,14 @@ export async function initDataSource() { await new Promise((resolve) => setTimeout(resolve, 3000)).then(async () => { await AppDataSource.initialize() .then(() => { - console.log("App Data Source has been initialized!"); + console.log('App Data Source has been initialized!'); connected = true; }) .catch((err) => { - console.error("Error during Data Source initialization", err); + console.error('Error during Data Source initialization', err); }); }); } -}; - - +} export default AppDataSource; diff --git a/src/app.ts b/src/app.ts index 49e3373..64b799f 100644 --- a/src/app.ts +++ b/src/app.ts @@ -23,10 +23,9 @@ async function main() { const app: Express = express(); initDataSource().then(() => { - console.log("Data source initialized"); + console.log('Data source initialized'); }); - app.use(cors({ credentials: true, origin: true })); app.use( @@ -34,21 +33,22 @@ async function main() { express.static(path.join(__dirname, '../../public/images'), { maxAge: '30d', immutable: true, - }) + }), ); // __dirname is "/path/to/dist/src" app.use(express.static(path.join(__dirname, '../../public'))); app.use(cookieParser()); - app.use(session({ - secret: config.appSecret, - cookie: { - expires: null, - maxAge: config.sessionIdCookieConfiguration.maxAge, - }, - })) - + app.use( + session({ + secret: config.appSecret, + cookie: { + expires: null, + maxAge: config.sessionIdCookieConfiguration.maxAge, + }, + }), + ); app.use(bodyParser.urlencoded({ extended: true })); // support url encoded bodies app.use(bodyParser.json()); // support json encoded bodies @@ -61,7 +61,9 @@ async function main() { // Instantiate dependencies const verifierConfigurationService = new VerifierConfigurationService(); - const openidForPresentationReceivingService = new OpenidForPresentationsReceivingService(verifierConfigurationService); + const openidForPresentationReceivingService = new OpenidForPresentationsReceivingService( + verifierConfigurationService, + ); const expressAppService = new ExpressAppService(openidForPresentationReceivingService); await expressAppService.configure(app); @@ -69,24 +71,23 @@ async function main() { app.use((req, res, next) => { res.locals.lang = req.lang; - res.locals.locale = locale["en"]; + res.locals.locale = locale['en']; next(); }); app.use('/verifier', morgan('combined'), verifierRouter); - app.get('/', async (_req: Request, res: Response) => { - return res.render("index", { - title: titleWithSiteName("index"), + return res.render('index', { + title: titleWithSiteName('index'), }); }); app.post('/', async (req, res) => { - if (req.body.verifier == "true") { + if (req.body.verifier == 'true') { return res.redirect('/verifier/public/definitions'); } - }) + }); app.get('/metadata/:filename', (req, res) => { if (req.params.filename !== 'site.webmanifest') { @@ -95,22 +96,22 @@ async function main() { const manifest = { name: config.siteConfig.name, short_name: config.siteConfig.short_name, - start_url: "/", - display: "standalone", + start_url: '/', + display: 'standalone', background_color: config.siteConfig.background_color, theme_color: config.siteConfig.theme_color, icons: [ { - src: "/images/favicon-192x192.png", - sizes: "192x192", - type: "image/png" + src: '/images/favicon-192x192.png', + sizes: '192x192', + type: 'image/png', }, { - src: "/images/favicon-512x512.png", - sizes: "512x512", - type: "image/png" - } - ] + src: '/images/favicon-512x512.png', + sizes: '512x512', + type: 'image/png', + }, + ], }; res.setHeader('Content-Type', 'application/manifest+json'); @@ -119,7 +120,7 @@ async function main() { // catch 404 and forward to error handler app.use((req, _res, next) => { - console.error("URL path not found: ", req.url) + console.error('URL path not found: ', req.url); next(createHttpError(404)); }); @@ -134,8 +135,8 @@ async function main() { }); app.listen(config.port, () => { - console.log(`Wallet enterprise app listening at ${config.url}`) + console.log(`Wallet enterprise app listening at ${config.url}`); }); } -main() +main(); diff --git a/src/entities/Audit.entity.ts b/src/entities/Audit.entity.ts index 2b974d3..5915b57 100644 --- a/src/entities/Audit.entity.ts +++ b/src/entities/Audit.entity.ts @@ -1,27 +1,33 @@ -import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from "typeorm"; +import { + Column, + CreateDateColumn, + Entity, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm'; -@Entity({ name: "audit" }) +@Entity({ name: 'audit' }) export class Audit { @PrimaryGeneratedColumn() id!: number; - @CreateDateColumn({ name: "start_time", type: "datetime" }) + @CreateDateColumn({ name: 'start_time', type: 'datetime' }) startTime!: Date; - @UpdateDateColumn({ name: "last_update_time", type: "datetime" }) + @UpdateDateColumn({ name: 'last_update_time', type: 'datetime' }) lastUpdateTime!: Date; - @Column({ name: "session_id", type: "varchar", length: 255 }) + @Column({ name: 'session_id', type: 'varchar', length: 255 }) sessionId!: string; - @Column({ name: "cross_device", type: "boolean", default: true }) + @Column({ name: 'cross_device', type: 'boolean', default: true }) crossDevice!: boolean; @Column({ - name: "dcql_query", - type: "longtext", + name: 'dcql_query', + type: 'longtext', nullable: true, - default: () => "NULL", + default: () => 'NULL', transformer: { to: (value: Record | null) => { if (value === null) { @@ -33,23 +39,29 @@ export class Audit { if (value === null) { return null; } - if (typeof value !== "string") { + if (typeof value !== 'string') { return value; } try { return JSON.parse(value) as Record; } catch (error) { - console.warn("Audit.dcqlQuery: failed to parse stored value as JSON", error); + console.warn('Audit.dcqlQuery: failed to parse stored value as JSON', error); return null; } }, - } + }, }) dcqlQuery!: Record | null; - @Column({ name: "completed", type: "boolean", default: false }) + @Column({ name: 'completed', type: 'boolean', default: false }) completed!: boolean; - @Column({ name: "error_code", type: "varchar", length: 255, nullable: true, default: () => "NULL" }) + @Column({ + name: 'error_code', + type: 'varchar', + length: 255, + nullable: true, + default: () => 'NULL', + }) errorCode!: string | null; } diff --git a/src/middlewares/csrf.middleware.ts b/src/middlewares/csrf.middleware.ts index aeaf622..94afb63 100644 --- a/src/middlewares/csrf.middleware.ts +++ b/src/middlewares/csrf.middleware.ts @@ -5,24 +5,23 @@ import locale from '../../config/locale'; export function csrfMiddlewareGenerate(req: Request, res: Response, next: NextFunction) { req.csrfToken = () => { const token = randomUUID(); - res.cookie("csrf_token", token); + res.cookie('csrf_token', token); return token; - } + }; next(); - } export function csrfMiddlewareCheck(req: Request, res: Response, next: NextFunction) { - const csrfTokenFromBody = String(req.body["csrf-token"]); - const csrfTokenFromCookies = String(req.cookies["csrf_token"]); + const csrfTokenFromBody = String(req.body['csrf-token']); + const csrfTokenFromCookies = String(req.cookies['csrf_token']); if (csrfTokenFromCookies !== csrfTokenFromBody) { res.render('error', { code: 3006, - msg: "CSRF token error", + msg: 'CSRF token error', lang: req.lang, - locale: locale["en"] - }) + locale: locale['en'], + }); return; } next(); diff --git a/src/middlewares/language.middleware.ts b/src/middlewares/language.middleware.ts index 3b11ef2..c39be9a 100644 --- a/src/middlewares/language.middleware.ts +++ b/src/middlewares/language.middleware.ts @@ -1,15 +1,14 @@ -import { NextFunction, Request, Response } from "express"; -import { Language } from "../types/language.type"; -import locale from "../../config/locale"; - +import { NextFunction, Request, Response } from 'express'; +import { Language } from '../types/language.type'; +import locale from '../../config/locale'; export function LanguageMiddleware(req: Request, res: Response, next: NextFunction) { - const langCode: Language | undefined = req.cookies["lang"]; - if (langCode && Object.keys(locale).includes(langCode)) { // if defined and is supported - req.lang = req.cookies["lang"]; + const langCode: Language | undefined = req.cookies['lang']; + if (langCode && Object.keys(locale).includes(langCode)) { + // if defined and is supported + req.lang = req.cookies['lang']; next(); - } - else { + } else { res.cookie('lang', 'en'); req.lang = 'en'; next(); diff --git a/src/migrations/1768995413499-create_audit_table.ts b/src/migrations/1768995413499-create_audit_table.ts index d7f0485..40a7a61 100644 --- a/src/migrations/1768995413499-create_audit_table.ts +++ b/src/migrations/1768995413499-create_audit_table.ts @@ -1,14 +1,15 @@ -import { MigrationInterface, QueryRunner } from "typeorm"; +import { MigrationInterface, QueryRunner } from 'typeorm'; export class createAuditTable1768995413499 implements MigrationInterface { - name = 'createAuditTable1768995413499' + name = 'createAuditTable1768995413499'; public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`CREATE TABLE \`audit\` (\`id\` int NOT NULL AUTO_INCREMENT, \`start_time\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`last_update_time\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`session_id\` varchar(255) NOT NULL, \`cross_device\` tinyint NOT NULL DEFAULT 0, \`dcql_query\` longtext NULL DEFAULT NULL, \`completed\` tinyint NOT NULL DEFAULT 0, \`error_code\` varchar(255) NULL DEFAULT NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`); + await queryRunner.query( + `CREATE TABLE \`audit\` (\`id\` int NOT NULL AUTO_INCREMENT, \`start_time\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`last_update_time\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`session_id\` varchar(255) NOT NULL, \`cross_device\` tinyint NOT NULL DEFAULT 0, \`dcql_query\` longtext NULL DEFAULT NULL, \`completed\` tinyint NOT NULL DEFAULT 0, \`error_code\` varchar(255) NULL DEFAULT NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`, + ); } public async down(queryRunner: QueryRunner): Promise { await queryRunner.query(`DROP TABLE \`audit\``); } - } diff --git a/src/services/ExpressAppService.ts b/src/services/ExpressAppService.ts index 534829b..eb50730 100644 --- a/src/services/ExpressAppService.ts +++ b/src/services/ExpressAppService.ts @@ -2,13 +2,14 @@ import { Application } from 'express'; import { OpenidForPresentationsReceivingService } from './OpenidForPresentationReceivingService'; export class ExpressAppService { - constructor( - private presentationsReceivingService: OpenidForPresentationsReceivingService, - ) { } + constructor(private presentationsReceivingService: OpenidForPresentationsReceivingService) {} public async configure(app: Application): Promise { - app.get('/verification/request-object', async (req, res) => { this.presentationsReceivingService.getSignedRequestObject({ req, res }) }); - app.post('/verification/direct_post', async (req, res) => { this.presentationsReceivingService.responseHandler({ req, res }) }); - + app.get('/verification/request-object', async (req, res) => { + this.presentationsReceivingService.getSignedRequestObject({ req, res }); + }); + app.post('/verification/direct_post', async (req, res) => { + this.presentationsReceivingService.responseHandler({ req, res }); + }); } } diff --git a/src/services/OpenidForPresentationReceivingService.ts b/src/services/OpenidForPresentationReceivingService.ts index 7062dd9..d36e2e0 100644 --- a/src/services/OpenidForPresentationReceivingService.ts +++ b/src/services/OpenidForPresentationReceivingService.ts @@ -1,58 +1,69 @@ -import { Request, Response } from 'express' -import { OpenidForPresentationsReceivingInterface, VerifierConfigurationInterface } from "./interfaces"; -import base64url from "base64url"; -import { config } from "../../config"; +import { Request, Response } from 'express'; +import { + OpenidForPresentationsReceivingInterface, + VerifierConfigurationInterface, +} from './interfaces'; +import base64url from 'base64url'; +import { config } from '../../config'; import fs from 'fs'; -import path from "path"; +import path from 'path'; import * as z from 'zod'; -import { pemToBase64 } from "../util/pemToBase64"; -import { MemoryStore, OpenID4VPClientErrors, OpenID4VPClientAPI, RPState, OpenID4VPResponseMode } from 'wallet-common'; +import { pemToBase64 } from '../util/pemToBase64'; +import { + MemoryStore, + OpenID4VPClientErrors, + OpenID4VPClientAPI, + RPState, + OpenID4VPResponseMode, +} from 'wallet-common'; import { defaultHttpClient } from 'wallet-common/dist/defaultHttpClient'; -import { webcrypto } from "node:crypto"; +import { webcrypto } from 'node:crypto'; import AppDataSource from '../AppDataSource'; import { Audit } from '../entities/Audit.entity'; -const privateKeyPem = fs.readFileSync(path.join(__dirname, "../../../keys/pem.key"), 'utf-8').toString(); -const leafCert = fs.readFileSync(path.join(__dirname, "../../../keys/pem.crt"), 'utf-8').toString(); +const privateKeyPem = fs + .readFileSync(path.join(__dirname, '../../../keys/pem.key'), 'utf-8') + .toString(); +const leafCert = fs.readFileSync(path.join(__dirname, '../../../keys/pem.crt'), 'utf-8').toString(); - - -const x5c = [ - pemToBase64(leafCert), -]; +const x5c = [pemToBase64(leafCert)]; const ResponseModeSchema = z.nativeEnum(OpenID4VPResponseMode); -const response_mode: OpenID4VPResponseMode = config?.presentationFlow?.response_mode ? ResponseModeSchema.parse(config?.presentationFlow?.response_mode) : OpenID4VPResponseMode.DIRECT_POST_JWT; +const response_mode: OpenID4VPResponseMode = config?.presentationFlow?.response_mode + ? ResponseModeSchema.parse(config?.presentationFlow?.response_mode) + : OpenID4VPResponseMode.DIRECT_POST_JWT; export class OpenidForPresentationsReceivingService implements OpenidForPresentationsReceivingInterface { private static rpStateKV: MemoryStore = new MemoryStore(); public openid4vp: OpenID4VPClientAPI; - constructor( - private configurationService: VerifierConfigurationInterface, - ) { + constructor(private configurationService: VerifierConfigurationInterface) { const trustedCredentialIssuerIdentifiers = config.trustedIssuers as string[] | undefined; this.openid4vp = new OpenID4VPClientAPI( OpenidForPresentationsReceivingService.rpStateKV, { - redirectUri: this.configurationService.getConfiguration().redirect_uri, credentialEngineOptions: { + redirectUri: this.configurationService.getConfiguration().redirect_uri, + credentialEngineOptions: { // @ts-ignore clockTolerance: config.clockTolerance ?? 60, subtle: webcrypto.subtle as SubtleCrypto, lang: 'en-US', trustedCertificates: [...config.trustedRootCertificates] as string[], - trustedCredentialIssuerIdentifiers: trustedCredentialIssuerIdentifiers + trustedCredentialIssuerIdentifiers: trustedCredentialIssuerIdentifiers, }, }, - defaultHttpClient + defaultHttpClient, ); } - - private async createAuditEntry(params: { sessionId: string; crossDevice: boolean; dcqlQuery: Record | null }): Promise { + private async createAuditEntry(params: { + sessionId: string; + crossDevice: boolean; + dcqlQuery: Record | null; + }): Promise { if (!AppDataSource.isInitialized) { - console.warn("Audit skipped: data source not initialized"); + console.warn('Audit skipped: data source not initialized'); return; } const auditRepository = AppDataSource.getRepository(Audit); @@ -68,7 +79,7 @@ export class OpenidForPresentationsReceivingService implements OpenidForPresenta private async updateAuditEntry(sessionId: string, update: Partial): Promise { if (!AppDataSource.isInitialized) { - console.warn("Audit update skipped: data source not initialized"); + console.warn('Audit update skipped: data source not initialized'); return; } const auditRepository = AppDataSource.getRepository(Audit); @@ -84,28 +95,33 @@ export class OpenidForPresentationsReceivingService implements OpenidForPresenta await this.updateAuditEntry(sessionId, { crossDevice }); } - public async getSignedRequestObject(ctx: { req: Request, res: Response }): Promise { + public async getSignedRequestObject(ctx: { req: Request; res: Response }): Promise { if (!ctx.req.query['id'] || typeof ctx.req.query['id'] != 'string') { - return ctx.res.status(500).send({ error: "id does not exist on query params" }); + return ctx.res.status(500).send({ error: 'id does not exist on query params' }); } - const signedRequestResult = await this.openid4vp.getSignedRequestObject(ctx.req.query['id']) + const signedRequestResult = await this.openid4vp.getSignedRequestObject(ctx.req.query['id']); if (!signedRequestResult.ok) { const { error, error_description } = signedRequestResult; if (error === OpenID4VPClientErrors.MissingRPState) { - return ctx.res.status(500).send({ error, error_description}); + return ctx.res.status(500).send({ error, error_description }); } if (error === OpenID4VPClientErrors.SignedRequestObjectInvalidated) { return ctx.res.status(500).send({ error, error_description }); } - return ctx.res.status(500).send({ error: "unknown error"}); + return ctx.res.status(500).send({ error: 'unknown error' }); } const signedRequest = signedRequestResult.value; return ctx.res.send(signedRequest.toString()); } - async generateAuthorizationRequestURL(presentationRequest: any, sessionId: string, callbackEndpoint?: string, clientIdPrefix?: string): Promise<{ url: URL; stateId: string }> { + async generateAuthorizationRequestURL( + presentationRequest: any, + sessionId: string, + callbackEndpoint?: string, + clientIdPrefix?: string, + ): Promise<{ url: URL; stateId: string }> { const generated = await this.openid4vp.generateAuthorizationRequestURL( presentationRequest, sessionId, @@ -115,30 +131,39 @@ export class OpenidForPresentationsReceivingService implements OpenidForPresenta x5c, response_mode, callbackEndpoint, - clientIdPrefix === "x509_hash" || clientIdPrefix === "x509_san_dns" ? clientIdPrefix : "x509_san_dns" + clientIdPrefix === 'x509_hash' || clientIdPrefix === 'x509_san_dns' + ? clientIdPrefix + : 'x509_san_dns', ); await this.createAuditEntry({ sessionId, crossDevice: generated.rpState.is_cross_device, dcqlQuery: presentationRequest?.dcql_query ?? null, }); - return {url: generated.url, stateId: generated.stateId} + return { url: generated.url, stateId: generated.stateId }; } - async responseHandler(ctx: { req: Request, res: Response }): Promise { + async responseHandler(ctx: { req: Request; res: Response }): Promise { // let presentationSubmissionObject: PresentationSubmission | null = qs.parse(decodeURI(presentation_submission)) as any; let vp_token = ctx.req.body?.vp_token; let state = ctx.req.body?.state; - let presentation_submission = ctx.req.body.presentation_submission ? JSON.parse(decodeURI(ctx.req.body.presentation_submission)) as any : null; - - - if (ctx.req.body.response) { // E2EE - const { kid } = JSON.parse(base64url.decode(ctx.req.body.response.split('.')[0])) as { kid: string | undefined }; + let presentation_submission = ctx.req.body.presentation_submission + ? (JSON.parse(decodeURI(ctx.req.body.presentation_submission)) as any) + : null; + + if (ctx.req.body.response) { + // E2EE + const { kid } = JSON.parse(base64url.decode(ctx.req.body.response.split('.')[0])) as { + kid: string | undefined; + }; if (!kid) { - throw new Error("Could not extract kid"); + throw new Error('Could not extract kid'); } - const rpStateResult = await this.openid4vp.handleEncryptedAuthorizationResponse(ctx.req.body.response, kid); + const rpStateResult = await this.openid4vp.handleEncryptedAuthorizationResponse( + ctx.req.body.response, + kid, + ); if (!rpStateResult.ok) { const { error, error_description } = rpStateResult; @@ -158,7 +183,9 @@ export class OpenidForPresentationsReceivingService implements OpenidForPresenta const rpState = rpStateResult.value; await this.updateAuditEntry(rpState.session_id, { completed: true, errorCode: null }); if (!rpState.is_cross_device) { - ctx.res.send({ redirect_uri: rpState.callback_endpoint + '#response_code=' + rpState.response_code }) + ctx.res.send({ + redirect_uri: rpState.callback_endpoint + '#response_code=' + rpState.response_code, + }); return; } @@ -169,22 +196,22 @@ export class OpenidForPresentationsReceivingService implements OpenidForPresenta // non E2EE scenario if (!state) { - console.log("Missing state param"); - ctx.res.status(401).send({ error: "Missing state param" }); + console.log('Missing state param'); + ctx.res.status(401).send({ error: 'Missing state param' }); return; } if (!vp_token) { - console.log("Missing state param"); - await this.updateAuditEntry(state, { errorCode: "Missing vp_token" }); - ctx.res.status(401).send({ error: "Missing vp_token" }); + console.log('Missing state param'); + await this.updateAuditEntry(state, { errorCode: 'Missing vp_token' }); + ctx.res.status(401).send({ error: 'Missing vp_token' }); return; } const rpStateResult = await this.openid4vp.handleResponseDirectPost( state, vp_token, - presentation_submission + presentation_submission, ); if (!rpStateResult.ok) { @@ -195,7 +222,9 @@ export class OpenidForPresentationsReceivingService implements OpenidForPresenta const rpState = rpStateResult.value; await this.updateAuditEntry(rpState.session_id, { completed: true, errorCode: null }); - ctx.res.send({ redirect_uri: rpState.callback_endpoint + '#response_code=' + rpState.response_code }) + ctx.res.send({ + redirect_uri: rpState.callback_endpoint + '#response_code=' + rpState.response_code, + }); return; } } diff --git a/src/services/VerifierConfigurationService.ts b/src/services/VerifierConfigurationService.ts index c3c829b..4790a99 100644 --- a/src/services/VerifierConfigurationService.ts +++ b/src/services/VerifierConfigurationService.ts @@ -1,23 +1,21 @@ -import { OpenidForPresentationsConfiguration } from "../types/OpenidForPresentationsConfiguration.type"; -import { config } from "../../config"; -import { VerifierConfigurationInterface } from "./interfaces"; - +import { OpenidForPresentationsConfiguration } from '../types/OpenidForPresentationsConfiguration.type'; +import { config } from '../../config'; +import { VerifierConfigurationInterface } from './interfaces'; export type DcqlClaim = { - id?: string; // REQUIRED only if using claim_sets, OPTIONAL otherwise + id?: string; // REQUIRED only if using claim_sets, OPTIONAL otherwise path: string[]; - values?: (string | number | boolean)[]; // optional value matching - intent_to_retain?: boolean; // ISO mdoc-specific extension + values?: (string | number | boolean)[]; // optional value matching + intent_to_retain?: boolean; // ISO mdoc-specific extension }; - export type DcqlCredentialQuery = { id: string; - format: "dc+sd-jwt" | "mso_mdoc"; + format: 'dc+sd-jwt' | 'mso_mdoc'; multiple?: boolean; meta: { - vct_values?: string[]; // SD-JWT VC - doctype_value?: string; // ISO mdoc + vct_values?: string[]; // SD-JWT VC + doctype_value?: string; // ISO mdoc }; trusted_authorities?: any; require_cryptographic_holder_binding?: boolean; @@ -36,266 +34,260 @@ export type DcqlQuery = { }; const pidSdJwtClaims: DcqlClaim[] = [ - { path: ["given_name"] }, - { path: ["birth_given_name"] }, - { path: ["family_name"] }, - { path: ["birth_family_name"] }, - { path: ["birthdate"] }, - { path: ["place_of_birth", "country"] }, - { path: ["place_of_birth", "region"] }, - { path: ["place_of_birth", "locality"] }, - - { path: ["nationalities"] }, - { path: ["personal_administrative_number"] }, - { path: ["sex"] }, - - { path: ["address", "formatted"] }, - { path: ["address", "street_address"] }, - { path: ["address", "house_number"] }, - { path: ["address", "postal_code"] }, - { path: ["address", "locality"] }, - { path: ["address", "region"] }, - { path: ["address", "country"] }, - - { path: ["age_equal_or_over", "14"] }, - { path: ["age_equal_or_over", "16"] }, - { path: ["age_equal_or_over", "18"] }, - { path: ["age_equal_or_over", "21"] }, - { path: ["age_equal_or_over", "65"] }, - - { path: ["age_in_years"] }, - { path: ["age_birth_year"] }, - - { path: ["email"] }, - { path: ["phone_number"] }, - - { path: ["issuing_authority"] }, - { path: ["issuing_country"] }, - { path: ["issuing_jurisdiction"] }, - - { path: ["date_of_expiry"] }, - { path: ["date_of_issuance"] }, - { path: ["document_number"] }, - { path: ["picture"] }, + { path: ['given_name'] }, + { path: ['birth_given_name'] }, + { path: ['family_name'] }, + { path: ['birth_family_name'] }, + { path: ['birthdate'] }, + { path: ['place_of_birth', 'country'] }, + { path: ['place_of_birth', 'region'] }, + { path: ['place_of_birth', 'locality'] }, + + { path: ['nationalities'] }, + { path: ['personal_administrative_number'] }, + { path: ['sex'] }, + + { path: ['address', 'formatted'] }, + { path: ['address', 'street_address'] }, + { path: ['address', 'house_number'] }, + { path: ['address', 'postal_code'] }, + { path: ['address', 'locality'] }, + { path: ['address', 'region'] }, + { path: ['address', 'country'] }, + + { path: ['age_equal_or_over', '14'] }, + { path: ['age_equal_or_over', '16'] }, + { path: ['age_equal_or_over', '18'] }, + { path: ['age_equal_or_over', '21'] }, + { path: ['age_equal_or_over', '65'] }, + + { path: ['age_in_years'] }, + { path: ['age_birth_year'] }, + + { path: ['email'] }, + { path: ['phone_number'] }, + + { path: ['issuing_authority'] }, + { path: ['issuing_country'] }, + { path: ['issuing_jurisdiction'] }, + + { path: ['date_of_expiry'] }, + { path: ['date_of_issuance'] }, + { path: ['document_number'] }, + { path: ['picture'] }, ]; const minimalPidSdJwtClaims: DcqlClaim[] = [ - { path: ["given_name"] }, - { path: ["family_name"] }, - { path: ["birthdate"] }, - { path: ["nationalities"] }, - { path: ["date_of_expiry"] }, - { path: ["issuing_authority"] }, - { path: ["issuing_country"] }, + { path: ['given_name'] }, + { path: ['family_name'] }, + { path: ['birthdate'] }, + { path: ['nationalities'] }, + { path: ['date_of_expiry'] }, + { path: ['issuing_authority'] }, + { path: ['issuing_country'] }, ]; const pidMdocClaims: DcqlClaim[] = [ { - path: ["eu.europa.ec.eudi.pid.1", "family_name"], + path: ['eu.europa.ec.eudi.pid.1', 'family_name'], intent_to_retain: false, }, { - path: ["eu.europa.ec.eudi.pid.1", "family_name_birth"], + path: ['eu.europa.ec.eudi.pid.1', 'family_name_birth'], intent_to_retain: false, }, { - path: ["eu.europa.ec.eudi.pid.1", "given_name"], + path: ['eu.europa.ec.eudi.pid.1', 'given_name'], intent_to_retain: false, }, { - path: ["eu.europa.ec.eudi.pid.1", "given_name_birth"], + path: ['eu.europa.ec.eudi.pid.1', 'given_name_birth'], intent_to_retain: false, }, { - path: ["eu.europa.ec.eudi.pid.1", "personal_administrative_number"], + path: ['eu.europa.ec.eudi.pid.1', 'personal_administrative_number'], intent_to_retain: false, }, { - path: ["eu.europa.ec.eudi.pid.1", "birth_date"], + path: ['eu.europa.ec.eudi.pid.1', 'birth_date'], intent_to_retain: false, }, { - path: ["eu.europa.ec.eudi.pid.1", "age_over_18"], + path: ['eu.europa.ec.eudi.pid.1', 'age_over_18'], intent_to_retain: false, }, { - path: ["eu.europa.ec.eudi.pid.1", "age_over_21"], + path: ['eu.europa.ec.eudi.pid.1', 'age_over_21'], intent_to_retain: false, }, { - path: ["eu.europa.ec.eudi.pid.1", "age_in_years"], + path: ['eu.europa.ec.eudi.pid.1', 'age_in_years'], intent_to_retain: false, }, { - path: ["eu.europa.ec.eudi.pid.1", "place_of_birth"], + path: ['eu.europa.ec.eudi.pid.1', 'place_of_birth'], intent_to_retain: false, }, { - path: ["eu.europa.ec.eudi.pid.1", "resident_address"], + path: ['eu.europa.ec.eudi.pid.1', 'resident_address'], intent_to_retain: false, }, { - path: ["eu.europa.ec.eudi.pid.1", "resident_country"], + path: ['eu.europa.ec.eudi.pid.1', 'resident_country'], intent_to_retain: false, }, { - path: ["eu.europa.ec.eudi.pid.1", "resident_state"], + path: ['eu.europa.ec.eudi.pid.1', 'resident_state'], intent_to_retain: false, }, { - path: ["eu.europa.ec.eudi.pid.1", "resident_city"], + path: ['eu.europa.ec.eudi.pid.1', 'resident_city'], intent_to_retain: false, }, { - path: ["eu.europa.ec.eudi.pid.1", "resident_postal_code"], + path: ['eu.europa.ec.eudi.pid.1', 'resident_postal_code'], intent_to_retain: false, }, { - path: ["eu.europa.ec.eudi.pid.1", "resident_street"], + path: ['eu.europa.ec.eudi.pid.1', 'resident_street'], intent_to_retain: false, }, { - path: ["eu.europa.ec.eudi.pid.1", "resident_house_number"], + path: ['eu.europa.ec.eudi.pid.1', 'resident_house_number'], intent_to_retain: false, }, { - path: ["eu.europa.ec.eudi.pid.1", "email_address"], + path: ['eu.europa.ec.eudi.pid.1', 'email_address'], intent_to_retain: false, }, { - path: ["eu.europa.ec.eudi.pid.1", "mobile_phone_number"], + path: ['eu.europa.ec.eudi.pid.1', 'mobile_phone_number'], intent_to_retain: false, }, { - path: ["eu.europa.ec.eudi.pid.1", "issuing_authority"], + path: ['eu.europa.ec.eudi.pid.1', 'issuing_authority'], intent_to_retain: false, }, { - path: ["eu.europa.ec.eudi.pid.1", "issuing_country"], + path: ['eu.europa.ec.eudi.pid.1', 'issuing_country'], intent_to_retain: false, }, { - path: ["eu.europa.ec.eudi.pid.1", "issuing_jurisdiction"], + path: ['eu.europa.ec.eudi.pid.1', 'issuing_jurisdiction'], intent_to_retain: false, }, ]; const bachelorSdJwtClaims: DcqlClaim[] = [ - { path: ["title"] }, - { path: ["expiry_date"] }, - { path: ["eqf_level"] }, - { path: ["birth_date"] }, - { path: ["blueprint_id"] }, - { path: ["graduation_date"] }, - { path: ["family_name"] }, - { path: ["grade"] }, - { path: ["given_name"] }, + { path: ['title'] }, + { path: ['expiry_date'] }, + { path: ['eqf_level'] }, + { path: ['birth_date'] }, + { path: ['blueprint_id'] }, + { path: ['graduation_date'] }, + { path: ['family_name'] }, + { path: ['grade'] }, + { path: ['given_name'] }, ]; const minimalBachelorSdJwtClaims: DcqlClaim[] = [ - { path: ["grade"] }, - { path: ["eqf_level"] }, - { path: ["title"] }, + { path: ['grade'] }, + { path: ['eqf_level'] }, + { path: ['title'] }, ]; const ehicSdJwtClaims: DcqlClaim[] = [ - { path: ["personal_administrative_number"] }, - { path: ["document_number"] }, - { path: ["issuing_country"] }, - { path: ["issuing_authority", "id"] }, - { path: ["issuing_authority", "name"] }, - { path: ["authentic_source", "id"] }, - { path: ["authentic_source", "name"] }, - { path: ["starting_date"] }, - { path: ["ending_date"] }, - { path: ["date_of_expiry"] }, - { path: ["date_of_issuance"] }, + { path: ['personal_administrative_number'] }, + { path: ['document_number'] }, + { path: ['issuing_country'] }, + { path: ['issuing_authority', 'id'] }, + { path: ['issuing_authority', 'name'] }, + { path: ['authentic_source', 'id'] }, + { path: ['authentic_source', 'name'] }, + { path: ['starting_date'] }, + { path: ['ending_date'] }, + { path: ['date_of_expiry'] }, + { path: ['date_of_issuance'] }, ]; const porSdJwtClaims: DcqlClaim[] = [ - { path: ["legal_person_identifier"] }, // Legal entity ID - { path: ["legal_name"] }, // Legal entity name - { path: ["full_powers"] }, // Full Representation Powers - { path: ["eService"] }, // Designated eService - { path: ["effective_from_date"] }, - { path: ["effective_until_date"] }, + { path: ['legal_person_identifier'] }, // Legal entity ID + { path: ['legal_name'] }, // Legal entity name + { path: ['full_powers'] }, // Full Representation Powers + { path: ['eService'] }, // Designated eService + { path: ['effective_from_date'] }, + { path: ['effective_until_date'] }, ]; - const pidSdJwtCredentialQuery: DcqlCredentialQuery = { - id: "pidSdJwt", - format: "dc+sd-jwt", + id: 'pidSdJwt', + format: 'dc+sd-jwt', meta: { - vct_values: ["urn:eudi:pid:1"], + vct_values: ['urn:eudi:pid:1'], }, claims: pidSdJwtClaims, }; const minimalPidSdJwtCredentialQuery: DcqlCredentialQuery = { - id: "minimalSdJwtPID", - format: "dc+sd-jwt", + id: 'minimalSdJwtPID', + format: 'dc+sd-jwt', meta: { - vct_values: ["urn:eudi:pid:1"], + vct_values: ['urn:eudi:pid:1'], }, claims: minimalPidSdJwtClaims, }; const pidMdocCredentialQuery: DcqlCredentialQuery = { - id: "pidMsoMdoc", - format: "mso_mdoc", + id: 'pidMsoMdoc', + format: 'mso_mdoc', meta: { - doctype_value: "eu.europa.ec.eudi.pid.1", + doctype_value: 'eu.europa.ec.eudi.pid.1', }, claims: pidMdocClaims, }; const bachelorCredentialQuery: DcqlCredentialQuery = { - id: "Bachelor", - format: "dc+sd-jwt", + id: 'Bachelor', + format: 'dc+sd-jwt', meta: { - vct_values: ["urn:credential:diploma"], + vct_values: ['urn:credential:diploma'], }, claims: bachelorSdJwtClaims, }; const minimalBachelorCredentialQuery: DcqlCredentialQuery = { - id: "minimalBachelor", - format: "dc+sd-jwt", + id: 'minimalBachelor', + format: 'dc+sd-jwt', meta: { - vct_values: ["urn:credential:diploma"], + vct_values: ['urn:credential:diploma'], }, claims: minimalBachelorSdJwtClaims, }; const europeanHealthInsuranceCardCredentialQuery: DcqlCredentialQuery = { - id: "EuropeanHealthInsuranceCard", - format: "dc+sd-jwt", + id: 'EuropeanHealthInsuranceCard', + format: 'dc+sd-jwt', meta: { - vct_values: ["urn:eudi:ehic:1"], + vct_values: ['urn:eudi:ehic:1'], }, claims: ehicSdJwtClaims, }; const porCredentialQuery: DcqlCredentialQuery = { - id: "POR", - format: "dc+sd-jwt", + id: 'POR', + format: 'dc+sd-jwt', meta: { - vct_values: ["urn:eu.europa.ec.eudi:por:1"], + vct_values: ['urn:eu.europa.ec.eudi:por:1'], }, - claims: porSdJwtClaims + claims: porSdJwtClaims, }; const minimalPidQuery: DcqlQuery = { - credentials: [ - minimalPidSdJwtCredentialQuery - ] + credentials: [minimalPidSdJwtCredentialQuery], }; const customPidDcqlQuery: DcqlQuery = { - credentials: [ - pidSdJwtCredentialQuery, - pidMdocCredentialQuery, - ] + credentials: [pidSdJwtCredentialQuery, pidMdocCredentialQuery], }; const bachelorDcqlQuery: DcqlQuery = { @@ -307,45 +299,30 @@ const europeanHealthInsuranceCardDcqlQuery: DcqlQuery = { }; const minimalPidAndEuropeanHealthInsuranceCardDcqlQuery: DcqlQuery = { - credentials: [ - minimalPidSdJwtCredentialQuery, - europeanHealthInsuranceCardCredentialQuery, - ], + credentials: [minimalPidSdJwtCredentialQuery, europeanHealthInsuranceCardCredentialQuery], credential_sets: [ { - options: [ - [minimalPidSdJwtCredentialQuery.id, europeanHealthInsuranceCardCredentialQuery.id], - ], + options: [[minimalPidSdJwtCredentialQuery.id, europeanHealthInsuranceCardCredentialQuery.id]], required: true, }, ], }; const minimalPidAndPorDcqlQuery: DcqlQuery = { - credentials: [ - minimalPidSdJwtCredentialQuery, - porCredentialQuery, - ], + credentials: [minimalPidSdJwtCredentialQuery, porCredentialQuery], credential_sets: [ { - options: [ - [minimalPidSdJwtCredentialQuery.id, porCredentialQuery.id], - ], + options: [[minimalPidSdJwtCredentialQuery.id, porCredentialQuery.id]], required: true, }, ], }; const minimalPidAndBachelorDcqlQuery: DcqlQuery = { - credentials: [ - minimalPidSdJwtCredentialQuery, - minimalBachelorCredentialQuery, - ], + credentials: [minimalPidSdJwtCredentialQuery, minimalBachelorCredentialQuery], credential_sets: [ { - options: [ - [minimalPidSdJwtCredentialQuery.id, minimalBachelorCredentialQuery.id], - ], + options: [[minimalPidSdJwtCredentialQuery.id, minimalBachelorCredentialQuery.id]], required: true, }, ], @@ -355,76 +332,75 @@ export class VerifierConfigurationService implements VerifierConfigurationInterf getPresentationRequests(): any[] { return [ { - id: "CustomPid", - title: "PID", - description: "Select the format and the fields you want to request", + id: 'CustomPid', + title: 'PID', + description: 'Select the format and the fields you want to request', _selectable: true, dcql_query: customPidDcqlQuery, }, { id: bachelorDcqlQuery.credentials[0].id, - title: "Bachelor Diploma", - description: "Select the fields you want to request", + title: 'Bachelor Diploma', + description: 'Select the fields you want to request', _selectable: true, dcql_query: bachelorDcqlQuery, }, { id: europeanHealthInsuranceCardDcqlQuery.credentials[0].id, - title: "EHIC", - description: "Select the fields you want to request", + title: 'EHIC', + description: 'Select the fields you want to request', _selectable: true, dcql_query: europeanHealthInsuranceCardDcqlQuery, }, { - id: "MinimalPIDAndEuropeanHealthInsuranceCard", - title: "PID + EHIC", - description: "Request a PID along with an EHIC", + id: 'MinimalPIDAndEuropeanHealthInsuranceCard', + title: 'PID + EHIC', + description: 'Request a PID along with an EHIC', dcql_query: minimalPidAndEuropeanHealthInsuranceCardDcqlQuery, }, { - id: "MinimalPIDAndPOR", - title: "PID + POR", - description: "Request a PID along with a POR", + id: 'MinimalPIDAndPOR', + title: 'PID + POR', + description: 'Request a PID along with a POR', dcql_query: minimalPidAndPorDcqlQuery, }, { - id: "MinimalPIDAndBachelorDiploma", - title: "PID + Diploma", - description: "Request a PID along with a Bachelor Diploma", + id: 'MinimalPIDAndBachelorDiploma', + title: 'PID + Diploma', + description: 'Request a PID along with a Bachelor Diploma', dcql_query: minimalPidAndBachelorDcqlQuery, }, { // QES Transaction data - id: "MinimalPIDwithTransactionDataQES", - title: "PID with QES Authorization Transaction Data", - purpose: "Document Signing", + id: 'MinimalPIDwithTransactionDataQES', + title: 'PID with QES Authorization Transaction Data', + purpose: 'Document Signing', description: - "Format: dc+sd-jwt - Transaction Data Type: https://cloudsignatureconsortium.org/2025/qes. " + + 'Format: dc+sd-jwt - Transaction Data Type: https://cloudsignatureconsortium.org/2025/qes. ' + "The user will be requested to authorize the QTSP to create QES for the document 'Personal Loan Agreement'", dcql_query: { ...minimalPidQuery, - credentials: minimalPidQuery.credentials.map(cred => ({ + credentials: minimalPidQuery.credentials.map((cred) => ({ ...cred, - _transaction_data_type: "https://cloudsignatureconsortium.org/2025/qes", + _transaction_data_type: 'https://cloudsignatureconsortium.org/2025/qes', })), - } + }, }, { // QC Request Transaction data - id: "MinimalPIDwithTransactionDataQCRequest", - title: "PID with QC Request Transaction Data", - purpose: "Creation of Signature Certificate", + id: 'MinimalPIDwithTransactionDataQCRequest', + title: 'PID with QC Request Transaction Data', + purpose: 'Creation of Signature Certificate', description: - "Format: dc+sd-jwt - Transaction Data Type: https://cloudsignatureconsortium.org/2025/qc-request. " + - "The user will be requested to give consent for the creation of signature certificates according to the T&C of the QTSP", + 'Format: dc+sd-jwt - Transaction Data Type: https://cloudsignatureconsortium.org/2025/qc-request. ' + + 'The user will be requested to give consent for the creation of signature certificates according to the T&C of the QTSP', dcql_query: { ...minimalPidQuery, - credentials: minimalPidQuery.credentials.map(cred => ({ + credentials: minimalPidQuery.credentials.map((cred) => ({ ...cred, - _transaction_data_type: - "https://cloudsignatureconsortium.org/2025/qc-request", - })) - } + _transaction_data_type: 'https://cloudsignatureconsortium.org/2025/qc-request', + })), + }, }, // { // // example with Transaction Data @@ -447,8 +423,8 @@ export class VerifierConfigurationService implements VerifierConfigurationInterf getConfiguration(): OpenidForPresentationsConfiguration { return { baseUrl: config.url, - redirect_uri: config.url + "/verification/direct_post", - authorizationServerWalletIdentifier: "authorization_server", + redirect_uri: config.url + '/verification/direct_post', + authorizationServerWalletIdentifier: 'authorization_server', }; } } diff --git a/src/services/interfaces.ts b/src/services/interfaces.ts index d6fd488..14acab5 100644 --- a/src/services/interfaces.ts +++ b/src/services/interfaces.ts @@ -1,14 +1,11 @@ -import { Request, Response } from "express"; -import { OpenidForPresentationsConfiguration } from "../types/OpenidForPresentationsConfiguration.type"; - - +import { Request, Response } from 'express'; +import { OpenidForPresentationsConfiguration } from '../types/OpenidForPresentationsConfiguration.type'; export interface OpenidForPresentationsReceivingInterface { - getSignedRequestObject(ctx: { req: Request, res: Response }): Promise; - responseHandler(ctx: { req: Request, res: Response }): Promise; + getSignedRequestObject(ctx: { req: Request; res: Response }): Promise; + responseHandler(ctx: { req: Request; res: Response }): Promise; } - export interface VerifierConfigurationInterface { getConfiguration(): OpenidForPresentationsConfiguration; getPresentationRequests(): any[]; diff --git a/src/types/OpenidForPresentationsConfiguration.type.ts b/src/types/OpenidForPresentationsConfiguration.type.ts index 2e9436d..5804b63 100644 --- a/src/types/OpenidForPresentationsConfiguration.type.ts +++ b/src/types/OpenidForPresentationsConfiguration.type.ts @@ -2,4 +2,4 @@ export type OpenidForPresentationsConfiguration = { baseUrl: string; redirect_uri: string; authorizationServerWalletIdentifier: string; -} +}; diff --git a/src/types/index.d.ts b/src/types/index.d.ts index 1409108..c5b03c5 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -1,4 +1,4 @@ -import { Language } from "./Language.type"; +import { Language } from './Language.type'; declare global { namespace Express { diff --git a/src/types/language.type.ts b/src/types/language.type.ts index 03f71d7..1f835c8 100644 --- a/src/types/language.type.ts +++ b/src/types/language.type.ts @@ -1 +1 @@ -export type Language = "en"; +export type Language = 'en'; diff --git a/src/util/generateRandomIdentifier.ts b/src/util/generateRandomIdentifier.ts index 64f749c..c896331 100644 --- a/src/util/generateRandomIdentifier.ts +++ b/src/util/generateRandomIdentifier.ts @@ -1,7 +1,7 @@ -import { webcrypto } from "crypto"; +import { webcrypto } from 'crypto'; export function generateRandomIdentifier(length: number) { const array = new Uint8Array(length); webcrypto.getRandomValues(array); - return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join(''); + return Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join(''); } diff --git a/src/util/initializeCredentialEngine.ts b/src/util/initializeCredentialEngine.ts index 6019d03..ba99060 100644 --- a/src/util/initializeCredentialEngine.ts +++ b/src/util/initializeCredentialEngine.ts @@ -1,13 +1,21 @@ -import { MsoMdocParser, MsoMdocVerifier, ParsingEngine, prependToPath, PublicKeyResolverEngine, SDJWTVCParser, SDJWTVCVerifier } from 'wallet-common'; +import { + MsoMdocParser, + MsoMdocVerifier, + ParsingEngine, + prependToPath, + PublicKeyResolverEngine, + SDJWTVCParser, + SDJWTVCVerifier, +} from 'wallet-common'; import { config } from '../../config'; -import { webcrypto } from "node:crypto"; +import { webcrypto } from 'node:crypto'; import { CustomCredentialSvg, defaultHttpClient, CredentialRenderingService } from 'wallet-common'; // @ts-ignore const trustedCredentialIssuerIdentifiers = config.trustedIssuers as string[] | undefined; export async function initializeCredentialEngine() { - console.log("Initializing credential engine...") + console.log('Initializing credential engine...'); const ctx = { // @ts-ignore @@ -18,25 +26,43 @@ export async function initializeCredentialEngine() { }; if (trustedCredentialIssuerIdentifiers) { - const result = (await Promise.all(trustedCredentialIssuerIdentifiers.map(async (credentialIssuerIdentifier) => - fetch(prependToPath(credentialIssuerIdentifier, ".well-known/openid-credential-issuer") ?? "") - .then((res) => res.ok ? res.json() : null) - .catch((e) => { console.error(e); return null; }) - ))).filter((r: any) => r !== null); + const result = ( + await Promise.all( + trustedCredentialIssuerIdentifiers.map(async (credentialIssuerIdentifier) => + fetch( + prependToPath(credentialIssuerIdentifier, '.well-known/openid-credential-issuer') ?? '', + ) + .then((res) => (res.ok ? res.json() : null)) + .catch((e) => { + console.error(e); + return null; + }), + ), + ) + ).filter((r: any) => r !== null); - const iacasResponses = (await Promise.all(result.map(async (metadata) => { - if (metadata && metadata.mdoc_iacas_uri) { - return fetch(metadata.mdoc_iacas_uri) - .then((res) => res.ok ? res.json() : null) - .catch((e) => { console.error(e); return null; }) - } - return null; - }))).filter((r: any) => r !== null); + const iacasResponses = ( + await Promise.all( + result.map(async (metadata) => { + if (metadata && metadata.mdoc_iacas_uri) { + return fetch(metadata.mdoc_iacas_uri) + .then((res) => (res.ok ? res.json() : null)) + .catch((e) => { + console.error(e); + return null; + }); + } + return null; + }), + ) + ).filter((r: any) => r !== null); for (const iacaResponse of iacasResponses) { const pemCertificates = iacaResponse.iacas.map((cert: { certificate?: string }) => - cert.certificate ? `-----BEGIN CERTIFICATE-----\n${cert.certificate}\n-----END CERTIFICATE-----\n` : null - ) + cert.certificate + ? `-----BEGIN CERTIFICATE-----\n${cert.certificate}\n-----END CERTIFICATE-----\n` + : null, + ); for (const pem of pemCertificates) { if (pem) { ctx.trustedCertificates.push(pem); @@ -47,9 +73,9 @@ export async function initializeCredentialEngine() { const credentialParsingEngine = ParsingEngine(); credentialParsingEngine.register(SDJWTVCParser({ context: ctx, httpClient: defaultHttpClient })); - console.log("Registered SDJWTVCParser..."); + console.log('Registered SDJWTVCParser...'); credentialParsingEngine.register(MsoMdocParser({ context: ctx, httpClient: defaultHttpClient })); - console.log("Registered MsoMdocParser..."); + console.log('Registered MsoMdocParser...'); const pkResolverEngine = PublicKeyResolverEngine(); const openid4vcRendering = CustomCredentialSvg({ httpClient: defaultHttpClient }); @@ -57,7 +83,11 @@ export async function initializeCredentialEngine() { return { credentialParsingEngine, msoMdocVerifier: MsoMdocVerifier({ context: ctx, pkResolverEngine: pkResolverEngine }), - sdJwtVerifier: SDJWTVCVerifier({ context: ctx, pkResolverEngine: pkResolverEngine, httpClient: defaultHttpClient }), + sdJwtVerifier: SDJWTVCVerifier({ + context: ctx, + pkResolverEngine: pkResolverEngine, + httpClient: defaultHttpClient, + }), openid4vcRendering, credentialRendering, }; diff --git a/src/verifierRouter.ts b/src/verifierRouter.ts index 1a8d4c9..001966f 100644 --- a/src/verifierRouter.ts +++ b/src/verifierRouter.ts @@ -1,116 +1,117 @@ -import { Router } from "express"; +import { Router } from 'express'; import * as qrcode from 'qrcode'; -import { config } from "./../config"; -import { OpenidForPresentationsReceivingService } from "./services/OpenidForPresentationReceivingService"; -import { VerifierConfigurationService } from "./services/VerifierConfigurationService"; -import { generateRandomIdentifier } from "./util/generateRandomIdentifier"; -import { addSessionIdCookieToResponse } from "../config/sessionIdCookieConfig"; -import { initializeCredentialEngine } from "./util/initializeCredentialEngine"; +import { config } from './../config'; +import { OpenidForPresentationsReceivingService } from './services/OpenidForPresentationReceivingService'; +import { VerifierConfigurationService } from './services/VerifierConfigurationService'; +import { generateRandomIdentifier } from './util/generateRandomIdentifier'; +import { addSessionIdCookieToResponse } from '../config/sessionIdCookieConfig'; +import { initializeCredentialEngine } from './util/initializeCredentialEngine'; import Ajv from 'ajv'; -import { titleWithSiteName } from "../config/titles"; +import { titleWithSiteName } from '../config/titles'; const ajv = new Ajv(); const dcqlQuerySchema = { - type: "object", - required: ["credentials"], + type: 'object', + required: ['credentials'], properties: { credentials: { - type: "array", + type: 'array', items: { - type: "object", - required: ["id", "format"], + type: 'object', + required: ['id', 'format'], properties: { - id: { type: "string" }, - format: { type: "string" }, + id: { type: 'string' }, + format: { type: 'string' }, meta: { - type: "object", + type: 'object', properties: { - doctype_value: { type: "string" }, - "sd-jwt_alg_values": { - type: "array", - items: { type: "string" } + doctype_value: { type: 'string' }, + 'sd-jwt_alg_values': { + type: 'array', + items: { type: 'string' }, + }, + 'kb-jwt_alg_values': { + type: 'array', + items: { type: 'string' }, }, - "kb-jwt_alg_values": { - type: "array", - items: { type: "string" } - } }, - additionalProperties: true + additionalProperties: true, }, claims: { - type: "array", + type: 'array', items: { - type: "object", - required: ["path"], + type: 'object', + required: ['path'], properties: { - id: { type: "string" }, + id: { type: 'string' }, path: { - type: "array", - items: { type: "string" } + type: 'array', + items: { type: 'string' }, }, - intent_to_retain: { type: "boolean" }, - filter: { type: "object" } - } - } - } - } - } + intent_to_retain: { type: 'boolean' }, + filter: { type: 'object' }, + }, + }, + }, + }, + }, }, credential_sets: { - type: "array", + type: 'array', items: { - type: "object", - required: ["options", "purpose"], + type: 'object', + required: ['options', 'purpose'], properties: { options: { - type: "array", + type: 'array', items: { - type: "array", - items: { type: "string" } - } + type: 'array', + items: { type: 'string' }, + }, }, - purpose: { type: "string" } - } - } - } - } + purpose: { type: 'string' }, + }, + }, + }, + }, }; -export const sanitizeInput = (input: string): string => - input.replace(/[^\x20-\x7E\n]/g, ''); +export const sanitizeInput = (input: string): string => input.replace(/[^\x20-\x7E\n]/g, ''); -type InferredClaimType = "text" | "imageUri" | "jsonObject" | "jsonArray"; +type InferredClaimType = 'text' | 'imageUri' | 'jsonObject' | 'jsonArray'; const IMAGE_URI_PATTERN = /^data:image\/[a-zA-Z0-9.+-]+;base64,/i; -const inferClaimValueType = (value: unknown): { inferredType: InferredClaimType, parsedJsonValue?: unknown } => { - if (typeof value !== "string") { - return { inferredType: "text" }; +const inferClaimValueType = ( + value: unknown, +): { inferredType: InferredClaimType; parsedJsonValue?: unknown } => { + if (typeof value !== 'string') { + return { inferredType: 'text' }; } const trimmed = value.trim(); if (!trimmed) { - return { inferredType: "text" }; + return { inferredType: 'text' }; } if (IMAGE_URI_PATTERN.test(trimmed)) { - return { inferredType: "imageUri" }; + return { inferredType: 'imageUri' }; } try { const parsedJson = JSON.parse(trimmed); if (Array.isArray(parsedJson)) { - return { inferredType: "jsonArray", parsedJsonValue: parsedJson }; + return { inferredType: 'jsonArray', parsedJsonValue: parsedJson }; } - if (parsedJson !== null && typeof parsedJson === "object") { - return { inferredType: "jsonObject", parsedJsonValue: parsedJson }; + if (parsedJson !== null && typeof parsedJson === 'object') { + return { inferredType: 'jsonObject', parsedJsonValue: parsedJson }; } } catch { // Not JSON content. } - return { inferredType: "text" }; + return { inferredType: 'text' }; }; const enrichClaimsWithInferredTypes = (claims: Record): Record => { @@ -130,24 +131,25 @@ const enrichClaimsWithInferredTypes = (claims: Record): Record { const { certificate } = req.body; try { if (!certificate) { - throw new Error("No certificate provided"); + throw new Error('No certificate provided'); } if (certificate.length > MAX_CERT_LENGTH) { - throw new Error("Certificate too large"); + throw new Error('Certificate too large'); } if (!/^([A-Za-z0-9+/=\s-]+)$/.test(certificate)) { - throw new Error("Invalid characters in certificate input"); + throw new Error('Invalid characters in certificate input'); } const sanitizedCert = sanitizeInput(certificate); const pem = sanitizedCert.includes('-----BEGIN CERTIFICATE-----') @@ -162,69 +164,83 @@ verifierRouter.post('/public/manage-certificates', async (req, res) => { title: titleWithSiteName('manage-certificates'), trustedRootCertificates: config.trustedRootCertificates, error: { - errorMessage: 'error adding x509 certificate' - } + errorMessage: 'error adding x509 certificate', + }, }); } }); verifierRouter.get('/public/manage-certificates', async (_req, res) => { - return res.render('manage-certificates.pug',{ + return res.render('manage-certificates.pug', { title: titleWithSiteName('manage-certificates'), - trustedRootCertificates: config.trustedRootCertificates + trustedRootCertificates: config.trustedRootCertificates, }); -}) +}); verifierRouter.get('/public/definitions', async (_req, res) => { return res.render('public-definitions.pug', { title: titleWithSiteName('public-definitions'), presentationRequests: verifierConfiguration.getPresentationRequests(), }); -}) - +}); -verifierRouter.get('/callback/status', async (req, res) => { // response with the status of the presentation (this endpoint should be protected) +verifierRouter.get('/callback/status', async (req, res) => { + // response with the status of the presentation (this endpoint should be protected) if (!req.cookies['session_id']) { - return res.send({ status: false, error: "Missing session_id from cookies" }); + return res.send({ status: false, error: 'Missing session_id from cookies' }); } - const result = await openidForPresentationReceivingService.openid4vp.getPresentationBySessionId(req.cookies['session_id'], false); + const result = await openidForPresentationReceivingService.openid4vp.getPresentationBySessionId( + req.cookies['session_id'], + false, + ); if (!result.status) { - return res.send({ status: false, error: "Presentation not received" }); + return res.send({ status: false, error: 'Presentation not received' }); } - return res.send({ status: result.status, presentationClaims: result.rpState.claims, presentation: result.rpState.vp_token }); -}) - + return res.send({ + status: result.status, + presentationClaims: result.rpState.claims, + presentation: result.rpState.vp_token, + }); +}); verifierRouter.get('/callback', async (_req, res) => { res.render('handle-response-code', { title: titleWithSiteName('handle-response-code'), }); -}) +}); verifierRouter.post('/callback', async (req, res) => { // this request includes the response code let session_id = req.cookies['session_id']; - if (req.body.response_code) { // response_code is considered more stable than session_id - const s = await openidForPresentationReceivingService.openid4vp.getRPStateByResponseCode(req.body.response_code); + if (req.body.response_code) { + // response_code is considered more stable than session_id + const s = await openidForPresentationReceivingService.openid4vp.getRPStateByResponseCode( + req.body.response_code, + ); if (s) { session_id = s.session_id; } } if (!session_id) { - console.error("Problem with the verification flow") - return res.status(400).send({ error: "Problem with the verification flow" }) + console.error('Problem with the verification flow'); + return res.status(400).send({ error: 'Problem with the verification flow' }); } - const result = await openidForPresentationReceivingService.openid4vp.getPresentationBySessionId(session_id, true); + const result = await openidForPresentationReceivingService.openid4vp.getPresentationBySessionId( + session_id, + true, + ); - if (result.status == false || + if ( + result.status == false || result.rpState.vp_token == null || result.rpState.claims == null || - result.rpState.date_created == null) { + result.rpState.date_created == null + ) { return res.render('error.pug', { title: titleWithSiteName('error'), - msg: result.status == false ? result.error.message : "Unknown error", + msg: result.status == false ? result.error.message : 'Unknown error', code: 0, }); } @@ -253,7 +269,8 @@ verifierRouter.post('/callback', async (req, res) => { } const verificationTimestamp = new Date(date_created).toISOString(); - const descriptorFriendlyNames = res.locals.locale?.presentationSuccess?.claims?.descriptorFriendlyNames || {}; + const descriptorFriendlyNames = + res.locals.locale?.presentationSuccess?.claims?.descriptorFriendlyNames || {}; return res.render('presentation-success.pug', { title: titleWithSiteName('presentation-success'), status: status, @@ -264,98 +281,115 @@ verifierRouter.post('/callback', async (req, res) => { credentialImages: credentialImages, descriptorFriendlyNames: descriptorFriendlyNames, }); -}) +}); -verifierRouter.use('/public/definitions/request-credentials/:presentation_request_id', async (req, res) => { - const presentation_request_id = req.params.presentation_request_id; - if (!presentation_request_id) { - return res.render('error', { - title: titleWithSiteName('error'), - msg: "No presentation request was selected", - code: 0, - }); - } - const presentationRequest = verifierConfiguration.getPresentationRequests().filter(pd => pd.id == presentation_request_id)[0]; - if (!presentationRequest) { - return res.render('error', { - title: titleWithSiteName('error'), - msg: "No presentation request was found", - code: 0, - }); - } - const selectableFields = presentationRequest.dcql_query.credentials - .flatMap((credential: any) => credential.claims) - .map((claim: any) => { - const label = claim.path.join("."); - return [label, claim.path[0]]; +verifierRouter.use( + '/public/definitions/request-credentials/:presentation_request_id', + async (req, res) => { + const presentation_request_id = req.params.presentation_request_id; + if (!presentation_request_id) { + return res.render('error', { + title: titleWithSiteName('error'), + msg: 'No presentation request was selected', + code: 0, + }); + } + const presentationRequest = verifierConfiguration + .getPresentationRequests() + .filter((pd) => pd.id == presentation_request_id)[0]; + if (!presentationRequest) { + return res.render('error', { + title: titleWithSiteName('error'), + msg: 'No presentation request was found', + code: 0, + }); + } + const selectableFields = presentationRequest.dcql_query.credentials + .flatMap((credential: any) => credential.claims) + .map((claim: any) => { + const label = claim.path.join('.'); + return [label, claim.path[0]]; + }); + return res.render('request-credentials', { + title: titleWithSiteName('request-credentials'), + presentationRequestId: presentationRequest.id, + dcqlQuery: presentationRequest.dcql_query, + selectableFields, }); - return res.render('request-credentials', { - title: titleWithSiteName('request-credentials'), - presentationRequestId: presentationRequest.id, - dcqlQuery: presentationRequest.dcql_query, - selectableFields, - }); -}) + }, +); verifierRouter.get('/public/definitions/request-custom-credential', async (_req, res) => { return res.render('request-custom-credential', { title: titleWithSiteName('request-custom-credential'), - schema: dcqlQuerySchema + schema: dcqlQuerySchema, }); -}) +}); verifierRouter.post('/public/definitions/request-custom-credential', async (req, res) => { - if (req.method === "POST" && req.body.action && req.cookies.session_id) { + if (req.method === 'POST' && req.body.action && req.cookies.session_id) { // update is_cross_device --> false since the button was pressed - const rpState = await openidForPresentationReceivingService.openid4vp.getRPStateBySessionId(req.cookies.session_id); + const rpState = await openidForPresentationReceivingService.openid4vp.getRPStateBySessionId( + req.cookies.session_id, + ); if (rpState) { rpState.is_cross_device = false; - await openidForPresentationReceivingService.openid4vp.saveRPState(rpState.session_id, rpState); + await openidForPresentationReceivingService.openid4vp.saveRPState( + rpState.session_id, + rpState, + ); await openidForPresentationReceivingService.setAuditCrossDevice(rpState.session_id, false); } return res.redirect(req.body.action); } let query; - let presentationRequest = {} + let presentationRequest = {}; try { query = JSON.parse(req.body.dcqlQuery); const validate = ajv.compile(dcqlQuerySchema); if (!validate(query)) { return res.render('error.pug', { title: titleWithSiteName('error'), - msg: "Invalid DCQL query format", + msg: 'Invalid DCQL query format', code: 0, }); } presentationRequest = { - id: "EditableDcqlQuery", - title: "Editable DCQL Query", - dcql_query: query - } + id: 'EditableDcqlQuery', + title: 'Editable DCQL Query', + dcql_query: query, + }; } catch (error) { return res.render('error.pug', { title: titleWithSiteName('error'), - msg: "Error while parsing the DCQL query", + msg: 'Error while parsing the DCQL query', code: 0, }); } - const scheme = req.body.scheme + const scheme = req.body.scheme; const newSessionId = generateRandomIdentifier(12); addSessionIdCookieToResponse(res, newSessionId); - const { url } = await openidForPresentationReceivingService.generateAuthorizationRequestURL(presentationRequest, newSessionId, config.url + "/verifier/callback"); - const modifiedUrl = url.toString().replace("openid4vp://cb", scheme) - let authorizationRequestQR = await new Promise((resolve) => { - qrcode.toDataURL(modifiedUrl.toString(), { - margin: 1, - errorCorrectionLevel: 'L', - type: 'image/png' - }, + const { url } = await openidForPresentationReceivingService.generateAuthorizationRequestURL( + presentationRequest, + newSessionId, + config.url + '/verifier/callback', + ); + const modifiedUrl = url.toString().replace('openid4vp://cb', scheme); + let authorizationRequestQR = (await new Promise((resolve) => { + qrcode.toDataURL( + modifiedUrl.toString(), + { + margin: 1, + errorCorrectionLevel: 'L', + type: 'image/png', + }, (err, data) => { - if (err) return resolve("NO_QR"); + if (err) return resolve('NO_QR'); return resolve(data); - }); - }) as string; + }, + ); + })) as string; return res.render('presentation-request.pug', { title: titleWithSiteName('presentation-request'), @@ -365,91 +399,118 @@ verifierRouter.post('/public/definitions/request-custom-credential', async (req, presentationRequest: JSON.stringify(JSON.parse(req.body.dcqlQuery)), state: url.searchParams.get('state'), }); -}) - -verifierRouter.get('/public/definitions/presentation-request/status/:presentation_request_id', async (req, res) => { - console.log("session_id : ", req.cookies['session_id']) - if (req.cookies['session_id'] && req.method == "GET") { - const { status } = await openidForPresentationReceivingService.openid4vp.getPresentationBySessionId(req.cookies['session_id'], false); - if (status == true) { - return res.send({ url: `/verifier/callback` }); - } - else { +}); + +verifierRouter.get( + '/public/definitions/presentation-request/status/:presentation_request_id', + async (req, res) => { + console.log('session_id : ', req.cookies['session_id']); + if (req.cookies['session_id'] && req.method == 'GET') { + const { status } = + await openidForPresentationReceivingService.openid4vp.getPresentationBySessionId( + req.cookies['session_id'], + false, + ); + if (status == true) { + return res.send({ url: `/verifier/callback` }); + } else { + return res.send({}); + } + } else { return res.send({}); } - } - else { - return res.send({}) - } -}) - -verifierRouter.use('/public/definitions/presentation-request/:presentation_request_id', async (req, res) => { - const presentation_request_id = req.params.presentation_request_id; - if (!presentation_request_id) { - return res.render('error', { - title: titleWithSiteName('error'), - msg: "No presentation request was selected", - code: 0, - }); - } - - - let presentationRequest; - if (req.method === "POST" && req.body.dcql_query) { - // Use the filtered query - presentationRequest = { dcql_query: JSON.parse(req.body.dcql_query) }; - } else { - presentationRequest = JSON.parse(JSON.stringify(verifierConfiguration.getPresentationRequests().filter(pd => pd.id == presentation_request_id)[0])) as any; - } - if (!presentationRequest) { - return res.render('error', { - title: titleWithSiteName('error'), - msg: "No presentation request was found", - code: 0, - }); - } - - let scheme = "openid4vp://cb"; - if (req.method === "POST" && req.body.scheme) { - scheme = req.body.scheme; - } + }, +); + +verifierRouter.use( + '/public/definitions/presentation-request/:presentation_request_id', + async (req, res) => { + const presentation_request_id = req.params.presentation_request_id; + if (!presentation_request_id) { + return res.render('error', { + title: titleWithSiteName('error'), + msg: 'No presentation request was selected', + code: 0, + }); + } - if (req.method === "POST" && req.body.action && req.cookies.session_id) { // handle click of "open with..." button - console.log("Cookie = ", req.cookies) - // update is_cross_device --> false since the button was pressed - const rpState = await openidForPresentationReceivingService.openid4vp.getRPStateBySessionId(req.cookies.session_id); - if (rpState) { - rpState.is_cross_device = false; - await openidForPresentationReceivingService.openid4vp.saveRPState(rpState.session_id, rpState); - await openidForPresentationReceivingService.setAuditCrossDevice(rpState.session_id, false); + let presentationRequest; + if (req.method === 'POST' && req.body.dcql_query) { + // Use the filtered query + presentationRequest = { dcql_query: JSON.parse(req.body.dcql_query) }; + } else { + presentationRequest = JSON.parse( + JSON.stringify( + verifierConfiguration + .getPresentationRequests() + .filter((pd) => pd.id == presentation_request_id)[0], + ), + ) as any; } - return res.redirect(req.body.action); - } - const newSessionId = generateRandomIdentifier(12); - addSessionIdCookieToResponse(res, newSessionId); // start session here - const clientIdPrefix = req.body.clientIdPrefix; - const { url } = await openidForPresentationReceivingService.generateAuthorizationRequestURL(presentationRequest, newSessionId, config.url + "/verifier/callback", clientIdPrefix); - const modifiedUrl = url.toString().replace("openid4vp://cb", scheme) - let authorizationRequestQR = await new Promise((resolve) => { - qrcode.toDataURL(modifiedUrl.toString(), { - margin: 1, - errorCorrectionLevel: 'L', - type: 'image/png' - }, - (err, data) => { - if (err) return resolve("NO_QR"); - return resolve(data); + if (!presentationRequest) { + return res.render('error', { + title: titleWithSiteName('error'), + msg: 'No presentation request was found', + code: 0, }); - }) as string; + } - return res.render('presentation-request.pug', { - title: titleWithSiteName('presentation-request'), - wwwalletURL: config.wwwalletURL, - authorizationRequestURL: modifiedUrl, - authorizationRequestQR, - presentationRequest: JSON.stringify(presentationRequest.dcql_query), - state: url.searchParams.get('state'), - }); -}) + let scheme = 'openid4vp://cb'; + if (req.method === 'POST' && req.body.scheme) { + scheme = req.body.scheme; + } + + if (req.method === 'POST' && req.body.action && req.cookies.session_id) { + // handle click of "open with..." button + console.log('Cookie = ', req.cookies); + // update is_cross_device --> false since the button was pressed + const rpState = await openidForPresentationReceivingService.openid4vp.getRPStateBySessionId( + req.cookies.session_id, + ); + if (rpState) { + rpState.is_cross_device = false; + await openidForPresentationReceivingService.openid4vp.saveRPState( + rpState.session_id, + rpState, + ); + await openidForPresentationReceivingService.setAuditCrossDevice(rpState.session_id, false); + } + return res.redirect(req.body.action); + } + const newSessionId = generateRandomIdentifier(12); + addSessionIdCookieToResponse(res, newSessionId); // start session here + const clientIdPrefix = req.body.clientIdPrefix; + const { url } = await openidForPresentationReceivingService.generateAuthorizationRequestURL( + presentationRequest, + newSessionId, + config.url + '/verifier/callback', + clientIdPrefix, + ); + const modifiedUrl = url.toString().replace('openid4vp://cb', scheme); + let authorizationRequestQR = (await new Promise((resolve) => { + qrcode.toDataURL( + modifiedUrl.toString(), + { + margin: 1, + errorCorrectionLevel: 'L', + type: 'image/png', + }, + (err, data) => { + if (err) return resolve('NO_QR'); + return resolve(data); + }, + ); + })) as string; + + return res.render('presentation-request.pug', { + title: titleWithSiteName('presentation-request'), + wwwalletURL: config.wwwalletURL, + authorizationRequestURL: modifiedUrl, + authorizationRequestQR, + presentationRequest: JSON.stringify(presentationRequest.dcql_query), + state: url.searchParams.get('state'), + }); + }, +); export { verifierRouter }; diff --git a/tsconfig.json b/tsconfig.json index d113263..d6bc95a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,10 +22,10 @@ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ /* Language and Environment */ - "target": "ES2020", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + "target": "ES2020" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ // "jsx": "preserve", /* Specify what JSX code is generated. */ - "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + "emitDecoratorMetadata": true /* Emit design-type metadata for decorated declarations in source files. */, // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ @@ -35,9 +35,9 @@ // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ /* Modules */ - "module": "CommonJS", /* Specify what module code is generated. */ + "module": "CommonJS" /* Specify what module code is generated. */, // "rootDir": "./", /* Specify the root folder within your source files. */ - "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */, "allowSyntheticDefaultImports": true, // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ @@ -50,7 +50,7 @@ // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ /* JavaScript Support */ - "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + "allowJs": true /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */, // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ @@ -60,15 +60,15 @@ // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ - "outDir": "./dist", /* Specify an output folder for all emitted files. */ + "outDir": "./dist" /* Specify an output folder for all emitted files. */, // "removeComments": true, /* Disable emitting comments. */ // "noEmit": true, /* Disable emitting files from a compilation. */ // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ - "sourceRoot": "/app/dist", /* Specify the root path for debuggers to find the reference source code. */ + "sourceRoot": "/app/dist" /* Specify the root path for debuggers to find the reference source code. */, // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + "inlineSourceMap": true /* Include sourcemap files inside the emitted JavaScript. */, // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ // "newLine": "crlf", /* Set the newline character for emitting files. */ @@ -82,12 +82,12 @@ /* Interop Constraints */ // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, /* Type Checking */ - "strict": true, /* Enable all strict type-checking options. */ + "strict": true /* Enable all strict type-checking options. */, /* Options enabled by "strict", listed for discoverability and future overrides — start. */ // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ @@ -100,18 +100,18 @@ // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ /* Options enabled by "strict" — end. */ - "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ - "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + "noUnusedLocals": true /* Enable error reporting when local variables aren't read. */, + "noUnusedParameters": true /* Raise an error when a function parameter isn't read. */, // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ - "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + "noFallthroughCasesInSwitch": true /* Enable error reporting for fallthrough cases in switch statements. */, // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ - "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + "noImplicitOverride": true /* Ensure overriding members in derived classes are marked with an override modifier. */, // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ // "allowUnreachableCode": false, /* Disable error reporting for unreachable code. */ /* Completeness */ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true, /* Skip type checking all .d.ts files. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ } } From b80dc22ed180d27e473993beff61a7d0cc167c04 Mon Sep 17 00:00:00 2001 From: gkatrakazas Date: Tue, 20 Oct 2026 14:53:35 +0300 Subject: [PATCH 5/6] chore(ci): add Prettier and ESLint checks to CI and pre-commit --- .github/workflows/code-formatting.yml | 29 +++++++++++++++++++++++++++ .pre-commit-config.yaml | 14 +++++++++++++ 2 files changed, 43 insertions(+) diff --git a/.github/workflows/code-formatting.yml b/.github/workflows/code-formatting.yml index f0d3dd5..6c7534c 100644 --- a/.github/workflows/code-formatting.yml +++ b/.github/workflows/code-formatting.yml @@ -31,3 +31,32 @@ jobs: - name: Check code formatting run: editorconfig-checker -no-color + + prettier-eslint: + name: Check Prettier and ESLint + + runs-on: ubuntu-latest + + permissions: + contents: read + + steps: + - name: Check out code + uses: actions/checkout@v7 + + - name: Set up Node.js + uses: actions/setup-node@v7 + with: + node-version: 24 + cache: yarn + + - name: Install dependencies + env: + YARN_NETWORK_CONCURRENCY: '1' + run: yarn install --frozen-lockfile + + - name: Check Prettier formatting + run: yarn format:check + + - name: Check ESLint rules + run: yarn lint diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f8cc346..9cf2078 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,3 +14,17 @@ repos: name: Check .editorconfig rules args: ['-no-color'] types_or: [text] + + - repo: local + hooks: + - id: prettier + name: Check Prettier formatting + entry: yarn format:check + language: system + pass_filenames: false + + - id: eslint + name: Check ESLint rules + entry: yarn lint + language: system + pass_filenames: false From f8f84821bc94bf1d291abb646740435620002866 Mon Sep 17 00:00:00 2001 From: gkatrakazas Date: Tue, 20 Oct 2026 15:47:14 +0300 Subject: [PATCH 6/6] chore: allow spaces after tabs in EditorConfig checks --- .editorconfig-checker.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.editorconfig-checker.json b/.editorconfig-checker.json index 022358b..52fa6a1 100644 --- a/.editorconfig-checker.json +++ b/.editorconfig-checker.json @@ -1,3 +1,4 @@ { + "SpacesAfterTabs": true, "Exclude": ["^(node_modules|dist|build)/"] }