From b04855869a9f39308aebd93b662b536847757bfd Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Fri, 15 Mar 2024 22:11:51 +0000 Subject: [PATCH 01/85] Basic Implementation of Policy Constraints Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> --- .gitignore | 4 +- test_client.sh | 0 vclogin/__tests__/evaluateLoginPolicy.test.js | 50 ++-- vclogin/__tests__/extractClaims.test.js | 12 + .../testdata/policies/acceptAnything.json | 2 +- .../policies/acceptEmailFromAltme.json | 2 +- .../policies/acceptEmailFromAltmeConstr.json | 21 ++ .../policies/acceptEmployeeFromAnyone.json | 2 +- .../acceptEmployeeFromAnyoneConstr.json | 53 ++++ .../testdata/policies/acceptFromAltme.json | 2 +- vclogin/lib/evaluateLoginPolicy.ts | 89 ------- vclogin/lib/extractClaims.ts | 228 +++++++++++++++++- vclogin/package.json | 2 +- vclogin/pages/api/presentCredential.ts | 3 +- vclogin/types/LoginPolicy.ts | 11 +- 15 files changed, 352 insertions(+), 129 deletions(-) mode change 100644 => 100755 test_client.sh create mode 100644 vclogin/__tests__/testdata/policies/acceptEmailFromAltmeConstr.json create mode 100644 vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyoneConstr.json delete mode 100644 vclogin/lib/evaluateLoginPolicy.ts diff --git a/.gitignore b/.gitignore index 978ab7e..76dac55 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ # vscode settings -.vscode \ No newline at end of file +.vscode + +.DS_Store \ No newline at end of file diff --git a/test_client.sh b/test_client.sh old mode 100644 new mode 100755 diff --git a/vclogin/__tests__/evaluateLoginPolicy.test.js b/vclogin/__tests__/evaluateLoginPolicy.test.js index 44df67a..6439a8e 100644 --- a/vclogin/__tests__/evaluateLoginPolicy.test.js +++ b/vclogin/__tests__/evaluateLoginPolicy.test.js @@ -3,10 +3,7 @@ * SPDX-License-Identifier: MIT */ -import { - isTrustedPresentation, - exportedForTesting, -} from "@/lib/evaluateLoginPolicy"; +import { isTrustedPresentation } from "@/lib/extractClaims"; import vpEmployee from "@/testdata/presentations/VP_EmployeeCredential.json"; import vpEmail from "@/testdata/presentations/VP_EmailPass.json"; import vpTezos from "@/testdata/presentations/VP_TezosAssociatedAddress.json"; @@ -14,6 +11,8 @@ import policyAcceptAnything from "@/testdata/policies/acceptAnything.json"; import policyEmployeeFromAnyone from "@/testdata/policies/acceptEmployeeFromAnyone.json"; import policyEmailFromAltme from "@/testdata/policies/acceptEmailFromAltme.json"; import policyFromAltme from "@/testdata/policies/acceptFromAltme.json"; +import policyEmailFromAltmeConstr from "@/testdata/policies/acceptEmailFromAltmeConstr.json"; +import policyEmployeeFromAnyoneConstr from "@/testdata/policies/acceptEmployeeFromAnyoneConstr.json"; describe("evaluateLoginPolicy", () => { it("defaults to false if no policy is available", () => { @@ -54,34 +53,25 @@ describe("evaluateLoginPolicy", () => { trusted = isTrustedPresentation(vpEmployee, policyFromAltme); expect(trusted).toBe(false); }); -}); - -describe("utility function for VP policy validation", () => { - let hasUniquePath = exportedForTesting.hasUniquePath; - it("should return true when there is a unique path", () => { - const patternFits = [ - ["A", "B"], - ["C", "D"], - ["E", "F"], - ]; - const usedCreds = ["A", "C", "E"]; - expect(hasUniquePath(patternFits, usedCreds)).toBe(true); - }); - - it("should return false when there is no unique path", () => { - const patternFits = [ - ["A", "B"], - ["C", "D"], - ["E", "F"], - ]; - const usedCreds = ["A", "C", "E", "B"]; - expect(hasUniquePath(patternFits, usedCreds)).toBe(false); + it("accepts only VP with credential(s) with simple constraint", () => { + var trusted = isTrustedPresentation(vpEmail, policyEmailFromAltmeConstr); + expect(trusted).toBe(true); + trusted = isTrustedPresentation(vpEmployee, policyEmailFromAltmeConstr); + expect(trusted).toBe(false); + trusted = isTrustedPresentation(vpTezos, policyEmailFromAltmeConstr); + expect(trusted).toBe(false); }); - it("should return false when the patternFits array has only one subarray with no elements", () => { - const patternFits = [[]]; - const usedCreds = []; - expect(hasUniquePath(patternFits, usedCreds)).toBe(false); + it("accepts only VP with credential(s) with complicated constraint", () => { + var trusted = isTrustedPresentation( + vpEmployee, + policyEmployeeFromAnyoneConstr, + ); + expect(trusted).toBe(true); + trusted = isTrustedPresentation(vpEmail, policyEmployeeFromAnyoneConstr); + expect(trusted).toBe(false); + trusted = isTrustedPresentation(vpTezos, policyEmployeeFromAnyoneConstr); + expect(trusted).toBe(false); }); }); diff --git a/vclogin/__tests__/extractClaims.test.js b/vclogin/__tests__/extractClaims.test.js index 00cc008..92f0525 100644 --- a/vclogin/__tests__/extractClaims.test.js +++ b/vclogin/__tests__/extractClaims.test.js @@ -8,6 +8,7 @@ import vpEmployee from "@/testdata/presentations/VP_EmployeeCredential.json"; import vpEmail from "@/testdata/presentations/VP_EmailPass.json"; import policyAcceptAnything from "@/testdata/policies/acceptAnything.json"; import policyEmailFromAltme from "@/testdata/policies/acceptEmailFromAltme.json"; +import policyEmailFromAltmeConstr from "@/testdata/policies/acceptEmailFromAltmeConstr.json"; import policyEmployeeFromAnyone from "@/testdata/policies/acceptEmployeeFromAnyone.json"; describe("extractClaims", () => { @@ -47,6 +48,17 @@ describe("extractClaims", () => { expect(claims).toStrictEqual(expected); }); + it("all designated claims from an EmailPass Credential are mapped (constrained)", () => { + var claims = extractClaims(vpEmail, policyEmailFromAltmeConstr); + var expected = { + tokenId: { + email: "felix.hoops@tum.de", + }, + tokenAccess: {}, + }; + expect(claims).toStrictEqual(expected); + }); + it("all designated claims from an EmployeeCredential are extracted", () => { var claims = extractClaims(vpEmployee, policyEmployeeFromAnyone); var expected = { diff --git a/vclogin/__tests__/testdata/policies/acceptAnything.json b/vclogin/__tests__/testdata/policies/acceptAnything.json index d4c7855..15d0d37 100644 --- a/vclogin/__tests__/testdata/policies/acceptAnything.json +++ b/vclogin/__tests__/testdata/policies/acceptAnything.json @@ -1,6 +1,6 @@ [ { - "credentialID": "credential1", + "credentialId": "credential1", "patterns": [ { "issuer": "*", diff --git a/vclogin/__tests__/testdata/policies/acceptEmailFromAltme.json b/vclogin/__tests__/testdata/policies/acceptEmailFromAltme.json index c096912..e409d18 100644 --- a/vclogin/__tests__/testdata/policies/acceptEmailFromAltme.json +++ b/vclogin/__tests__/testdata/policies/acceptEmailFromAltme.json @@ -1,6 +1,6 @@ [ { - "credentialID": "one", + "credentialId": "one", "patterns": [ { "issuer": "did:web:app.altme.io:issuer", diff --git a/vclogin/__tests__/testdata/policies/acceptEmailFromAltmeConstr.json b/vclogin/__tests__/testdata/policies/acceptEmailFromAltmeConstr.json new file mode 100644 index 0000000..7e69486 --- /dev/null +++ b/vclogin/__tests__/testdata/policies/acceptEmailFromAltmeConstr.json @@ -0,0 +1,21 @@ +[ + { + "credentialId": "one", + "patterns": [ + { + "issuer": "did:web:app.altme.io:issuer", + "claims": [ + { + "claimPath": "$.credentialSubject.email", + "token": "id_token" + } + ], + "constraint": { + "op": "equalsDID", + "a": "$VP.proof.verificationMethod", + "b": "$.credentialSubject.id" + } + } + ] + } +] diff --git a/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyone.json b/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyone.json index fe9a4af..b9d87dd 100644 --- a/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyone.json +++ b/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyone.json @@ -1,6 +1,6 @@ [ { - "credentialID": "one", + "credentialId": "one", "patterns": [ { "issuer": "*", diff --git a/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyoneConstr.json b/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyoneConstr.json new file mode 100644 index 0000000..f809654 --- /dev/null +++ b/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyoneConstr.json @@ -0,0 +1,53 @@ +[ + { + "credentialId": "one", + "patterns": [ + { + "issuer": "*", + "claims": [ + { + "claimPath": "$.credentialSubject.hasLegallyBindingName", + "newPath": "$.companyName" + }, + { + "claimPath": "$.credentialSubject.name", + "token": "id_token" + }, + { + "claimPath": "$.credentialSubject.email", + "token": "id_token" + } + ], + "constraint": { + "op": "and", + "a": { + "op": "equalsDID", + "a": "$VP.proof.verificationMethod", + "b": "$.credentialSubject.id" + }, + "b": { + "op": "and", + "a": { + "op": "endsWith", + "a": "$.credentialSubject.email", + "b": "@test.com" + }, + "b": { + "op": "or", + "a": { + "op": "matches", + "a": "$.credentialSubject.title", + "b": "C[EOT]O" + }, + "b": { + "op": "equals", + "a": "$.credentialSubject.hasJurisdiction", + "b": "GER" + } + } + } + } + } + ] + } +] diff --git a/vclogin/__tests__/testdata/policies/acceptFromAltme.json b/vclogin/__tests__/testdata/policies/acceptFromAltme.json index affcff5..f94c5b1 100644 --- a/vclogin/__tests__/testdata/policies/acceptFromAltme.json +++ b/vclogin/__tests__/testdata/policies/acceptFromAltme.json @@ -1,6 +1,6 @@ [ { - "credentialID": "one", + "credentialId": "one", "patterns": [ { "issuer": "did:web:app.altme.io:issuer", diff --git a/vclogin/lib/evaluateLoginPolicy.ts b/vclogin/lib/evaluateLoginPolicy.ts deleted file mode 100644 index dc35b93..0000000 --- a/vclogin/lib/evaluateLoginPolicy.ts +++ /dev/null @@ -1,89 +0,0 @@ -/** - * Copyright 2024 Software Engineering for Business Information Systems (sebis) . - * SPDX-License-Identifier: MIT - */ - -import { CredentialPattern, LoginPolicy } from "@/types/LoginPolicy"; -import jp from "jsonpath"; -import { getConfiguredLoginPolicy } from "@/config/loginPolicy"; - -export const isTrustedPresentation = (VP: any, policy?: LoginPolicy) => { - var configuredPolicy = getConfiguredLoginPolicy(); - if (!policy && configuredPolicy === undefined) return false; - - var usedPolicy = policy ? policy : configuredPolicy!; - - var creds = VP.verifiableCredential; - var credArr: Array; - if (!Array.isArray(creds)) { - credArr = [creds]; - } else { - credArr = creds; - } - - // collect all credentials that fit an expected credential - var patternFits = []; - for (let expectation of usedPolicy) { - let fittingCreds = []; - for (let cred of credArr) { - if (isCredentialFittingPatternList(cred, expectation.patterns)) { - fittingCreds.push(cred); - } - } - patternFits.push(fittingCreds); - } - - return hasUniquePath(patternFits, []); -}; - -const hasUniquePath = (patternFits: any[][], usedCreds: any[]): boolean => { - if (patternFits.length === 1) return patternFits[0].length > 0; - - for (let cred of patternFits[0]) { - if (!usedCreds.includes(cred)) { - usedCreds.push(cred); - let newPatternFits = patternFits.slice(1); - if (hasUniquePath(newPatternFits, usedCreds)) { - return true; - } - usedCreds.pop(); - } - } - return false; -}; - -const isCredentialFittingPatternList = ( - cred: any, - patterns: CredentialPattern[], -): boolean => { - for (let pattern of patterns) { - if (isCredentialFittingPattern(cred, pattern)) { - return true; - } - } - return false; -}; - -const isCredentialFittingPattern = ( - cred: any, - pattern: CredentialPattern, -): boolean => { - if (cred.issuer !== pattern.issuer && pattern.issuer !== "*") { - return false; - } - - for (const claim of pattern.claims) { - if ( - (!Object.hasOwn(claim, "required") || claim.required) && - jp.paths(cred, claim.claimPath).length === 0 - ) { - return false; - } - } - - return true; -}; - -export const exportedForTesting = { - hasUniquePath, -}; diff --git a/vclogin/lib/extractClaims.ts b/vclogin/lib/extractClaims.ts index e2f3eca..f94780b 100644 --- a/vclogin/lib/extractClaims.ts +++ b/vclogin/lib/extractClaims.ts @@ -3,10 +3,28 @@ * SPDX-License-Identifier: MIT */ -import { LoginPolicy, ClaimEntry } from "@/types/LoginPolicy"; +import { + LoginPolicy, + ClaimEntry, + CredentialPattern, + VcConstraint, +} from "@/types/LoginPolicy"; import jp from "jsonpath"; import { getConfiguredLoginPolicy } from "@/config/loginPolicy"; +export const isTrustedPresentation = (VP: any, policy?: LoginPolicy) => { + var configuredPolicy = getConfiguredLoginPolicy(); + if (!policy && configuredPolicy === undefined) return false; + + var usedPolicy = policy ? policy : configuredPolicy!; + + const creds = Array.isArray(VP.verifiableCredential) + ? VP.verifiableCredential + : [VP.verifiableCredential]; + + return getConstraintFit(creds, usedPolicy, VP).length > 0; +}; + export const extractClaims = (VP: any, policy?: LoginPolicy) => { var configuredPolicy = getConfiguredLoginPolicy(); if (!policy && configuredPolicy === undefined) return false; @@ -16,6 +34,7 @@ export const extractClaims = (VP: any, policy?: LoginPolicy) => { const creds = Array.isArray(VP.verifiableCredential) ? VP.verifiableCredential : [VP.verifiableCredential]; + const vcClaims = creds.map((vc: any) => extractClaimsFromVC(vc, usedPolicy)); const claims = vcClaims.reduce( (acc: any, vc: any) => Object.assign(acc, vc), @@ -24,6 +43,213 @@ export const extractClaims = (VP: any, policy?: LoginPolicy) => { return claims; }; +const getConstraintFit = (creds: any[], policy: LoginPolicy, VP: any): any[] => { + const patternFits = getPatternClaimFits(creds, policy); + const uniqueFits = getAllUniqueDraws(patternFits); + if (uniqueFits.length === 0) { + return []; + } + for (let fit of uniqueFits) { + if (isValidConstraintFit(fit, policy, VP)) { + return fit; + } + } + return []; +}; + +const getPatternClaimFits = (creds: any[], policy: LoginPolicy): any[][] => { + // collect all credentials that fit an expected credential claim-wise + var patternFits = []; + for (let expectation of policy) { + let fittingCreds = []; + for (let cred of creds) { + if (isCredentialFittingPatternList(cred, expectation.patterns)) { + fittingCreds.push(cred); + } + } + patternFits.push(fittingCreds); + } + + return patternFits; +}; + +const isCredentialFittingPatternList = ( + cred: any, + + patterns: CredentialPattern[], +): boolean => { + for (let pattern of patterns) { + if (isCredentialFittingPattern(cred, pattern)) { + return true; + } + } + + return false; +}; + +const isCredentialFittingPattern = ( + cred: any, + + pattern: CredentialPattern, +): boolean => { + if (cred.issuer !== pattern.issuer && pattern.issuer !== "*") { + return false; + } + + for (const claim of pattern.claims) { + if ( + (!Object.hasOwn(claim, "required") || claim.required) && + jp.paths(cred, claim.claimPath).length === 0 + ) { + return false; + } + } + + return true; +}; + +const getAllUniqueDraws = (patternFits: any[][]): any[][] => { + const draws = getAllUniqueDrawsHelper(patternFits, []); + return draws.filter((draw) => draw.length == patternFits.length); +}; + +const getAllUniqueDrawsHelper = ( + patternFits: any[][], + usedIds: any[], +): any[][] => { + if (patternFits.length === 0) { + return []; + } + + let uniqueDraws: any[][] = []; + for (let cred of patternFits[0]) { + if (!usedIds.includes(cred.id)) { + uniqueDraws.push([ + cred, + ...getAllUniqueDrawsHelper(patternFits.slice(1), [...usedIds, cred.id]), + ]); + } + } + return uniqueDraws; +}; + +const isValidConstraintFit = ( + credFit: any[], + policy: LoginPolicy, + VP: any, +): boolean => { + const credDict: any = {}; + for (let i = 0; i < policy.length; i++) { + credDict[policy[i].credentialId] = credFit[i]; + } + + for (let i = 0; i < policy.length; i++) { + const cred = credFit[i]; + const expectation = policy[i]; + var oneFittingPattern = false; + for (let pattern of expectation.patterns) { + if (isCredentialFittingPattern(cred, pattern)) { + if (pattern.constraint) { + const res = evaluateConstraint( + pattern.constraint, + cred, + credDict, + VP, + ); + if (res) { + oneFittingPattern = true; + break; + } + } + } + } + if (!oneFittingPattern) { + return true; + } + } + return false; +}; + +const evaluateConstraint = ( + constraint: VcConstraint, + cred: any, + credDict: any, + VP: any, +): boolean => { + var a = "", + b = ""; + switch (constraint.op) { + case "equals": + case "equalsDID": + case "startsWith": + case "endsWith": + case "matches": + a = resolveValue(constraint.a as string, cred, credDict, VP); + b = resolveValue(constraint.b as string, cred, credDict, VP); + } + + switch (constraint.op) { + case "equals": + return a === b; + case "equalsDID": + return ( + a.split("#").slice(0, -1).join("#") === + b.split("#").slice(0, -1).join("#") + ); + case "startsWith": + return a.startsWith(b); + case "endsWith": + return a.endsWith(b); + case "matches": + return a.match(b) !== null; + case "and": + return ( + evaluateConstraint(constraint.a as VcConstraint, cred, credDict, VP) && + evaluateConstraint(constraint.b as VcConstraint, cred, credDict, VP) + ); + case "or": + return ( + evaluateConstraint(constraint.a as VcConstraint, cred, credDict, VP) || + evaluateConstraint(constraint.b as VcConstraint, cred, credDict, VP) + ); + case "not": + return !evaluateConstraint( + constraint.a as VcConstraint, + cred, + credDict, + VP, + ); + } + throw Error("Unknown constraint operator: " + constraint.op); +}; + +const resolveValue = ( + expression: string, + cred: any, + credDict: any, + VP: any, +): string => { + if (expression.startsWith("$")) { + var nodes: any; + if (expression.startsWith("$.")) { + nodes = jp.nodes(cred, expression); + } else if (expression.startsWith("$VP.")) { + nodes = jp.nodes(VP, "$" + expression.slice(3)); + } else { + nodes = jp.nodes( + credDict[expression.slice(1).split(".")[0]], + expression.slice(1).split(".").slice(1).join("."), + ); + } + if (nodes.length > 1 || nodes.length <= 0) { + throw Error("JSON Paths in constraints must be single-valued"); + } + return nodes[0].value; + } + + return expression; +}; + const extractClaimsFromVC = (VC: any, policy: LoginPolicy) => { for (let expectation of policy) { for (let pattern of expectation.patterns) { diff --git a/vclogin/package.json b/vclogin/package.json index d131b24..2d00987 100644 --- a/vclogin/package.json +++ b/vclogin/package.json @@ -7,7 +7,7 @@ "build": "next build", "start": "next start", "lint": "next lint", - "test": "jest" + "test": "jest --coverage" }, "dependencies": { "@material-tailwind/react": "^2.0.3", diff --git a/vclogin/pages/api/presentCredential.ts b/vclogin/pages/api/presentCredential.ts index 5abae1e..2c34bd1 100644 --- a/vclogin/pages/api/presentCredential.ts +++ b/vclogin/pages/api/presentCredential.ts @@ -7,8 +7,7 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { verifyAuthenticationPresentation } from "@/lib/verifyPresentation"; import { hydraAdmin } from "@/config/ory"; import { Redis } from "ioredis"; -import { isTrustedPresentation } from "@/lib/evaluateLoginPolicy"; -import { extractClaims } from "@/lib/extractClaims"; +import { isTrustedPresentation, extractClaims } from "@/lib/extractClaims"; import * as jose from "jose"; import { keyToDID, keyToVerificationMethod } from "@spruceid/didkit-wasm-node"; import { generatePresentationDefinition } from "@/lib/generatePresentationDefinition"; diff --git a/vclogin/types/LoginPolicy.ts b/vclogin/types/LoginPolicy.ts index 89ce736..381fcea 100644 --- a/vclogin/types/LoginPolicy.ts +++ b/vclogin/types/LoginPolicy.ts @@ -3,6 +3,12 @@ * SPDX-License-Identifier: MIT */ +export type VcConstraint = { + op: string; + a: VcConstraint | string; + b: VcConstraint | string; +}; + export type ClaimEntry = { claimPath: string; newPath?: string; @@ -12,9 +18,12 @@ export type ClaimEntry = { export type CredentialPattern = { issuer: string; claims: ClaimEntry[]; + constraint?: VcConstraint; }; + export type ExpectedCredential = { - credentialID: string; + credentialId: string; patterns: CredentialPattern[]; }; + export type LoginPolicy = ExpectedCredential[]; From 76df29d75ec39ce4105423fe3045f6cde532c910 Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Fri, 15 Mar 2024 22:11:51 +0000 Subject: [PATCH 02/85] Basic Implementation of Policy Constraints Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> --- .gitignore | 4 +- test_client.sh | 0 vclogin/__tests__/evaluateLoginPolicy.test.js | 50 ++-- vclogin/__tests__/extractClaims.test.js | 12 + .../testdata/policies/acceptAnything.json | 2 +- .../policies/acceptEmailFromAltme.json | 2 +- .../policies/acceptEmailFromAltmeConstr.json | 21 ++ .../policies/acceptEmployeeFromAnyone.json | 2 +- .../acceptEmployeeFromAnyoneConstr.json | 53 ++++ .../testdata/policies/acceptFromAltme.json | 2 +- vclogin/lib/evaluateLoginPolicy.ts | 89 ------- vclogin/lib/extractClaims.ts | 232 +++++++++++++++++- vclogin/package.json | 2 +- vclogin/pages/api/presentCredential.ts | 3 +- vclogin/types/LoginPolicy.ts | 11 +- 15 files changed, 356 insertions(+), 129 deletions(-) mode change 100644 => 100755 test_client.sh create mode 100644 vclogin/__tests__/testdata/policies/acceptEmailFromAltmeConstr.json create mode 100644 vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyoneConstr.json delete mode 100644 vclogin/lib/evaluateLoginPolicy.ts diff --git a/.gitignore b/.gitignore index 978ab7e..76dac55 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ # vscode settings -.vscode \ No newline at end of file +.vscode + +.DS_Store \ No newline at end of file diff --git a/test_client.sh b/test_client.sh old mode 100644 new mode 100755 diff --git a/vclogin/__tests__/evaluateLoginPolicy.test.js b/vclogin/__tests__/evaluateLoginPolicy.test.js index 44df67a..6439a8e 100644 --- a/vclogin/__tests__/evaluateLoginPolicy.test.js +++ b/vclogin/__tests__/evaluateLoginPolicy.test.js @@ -3,10 +3,7 @@ * SPDX-License-Identifier: MIT */ -import { - isTrustedPresentation, - exportedForTesting, -} from "@/lib/evaluateLoginPolicy"; +import { isTrustedPresentation } from "@/lib/extractClaims"; import vpEmployee from "@/testdata/presentations/VP_EmployeeCredential.json"; import vpEmail from "@/testdata/presentations/VP_EmailPass.json"; import vpTezos from "@/testdata/presentations/VP_TezosAssociatedAddress.json"; @@ -14,6 +11,8 @@ import policyAcceptAnything from "@/testdata/policies/acceptAnything.json"; import policyEmployeeFromAnyone from "@/testdata/policies/acceptEmployeeFromAnyone.json"; import policyEmailFromAltme from "@/testdata/policies/acceptEmailFromAltme.json"; import policyFromAltme from "@/testdata/policies/acceptFromAltme.json"; +import policyEmailFromAltmeConstr from "@/testdata/policies/acceptEmailFromAltmeConstr.json"; +import policyEmployeeFromAnyoneConstr from "@/testdata/policies/acceptEmployeeFromAnyoneConstr.json"; describe("evaluateLoginPolicy", () => { it("defaults to false if no policy is available", () => { @@ -54,34 +53,25 @@ describe("evaluateLoginPolicy", () => { trusted = isTrustedPresentation(vpEmployee, policyFromAltme); expect(trusted).toBe(false); }); -}); - -describe("utility function for VP policy validation", () => { - let hasUniquePath = exportedForTesting.hasUniquePath; - it("should return true when there is a unique path", () => { - const patternFits = [ - ["A", "B"], - ["C", "D"], - ["E", "F"], - ]; - const usedCreds = ["A", "C", "E"]; - expect(hasUniquePath(patternFits, usedCreds)).toBe(true); - }); - - it("should return false when there is no unique path", () => { - const patternFits = [ - ["A", "B"], - ["C", "D"], - ["E", "F"], - ]; - const usedCreds = ["A", "C", "E", "B"]; - expect(hasUniquePath(patternFits, usedCreds)).toBe(false); + it("accepts only VP with credential(s) with simple constraint", () => { + var trusted = isTrustedPresentation(vpEmail, policyEmailFromAltmeConstr); + expect(trusted).toBe(true); + trusted = isTrustedPresentation(vpEmployee, policyEmailFromAltmeConstr); + expect(trusted).toBe(false); + trusted = isTrustedPresentation(vpTezos, policyEmailFromAltmeConstr); + expect(trusted).toBe(false); }); - it("should return false when the patternFits array has only one subarray with no elements", () => { - const patternFits = [[]]; - const usedCreds = []; - expect(hasUniquePath(patternFits, usedCreds)).toBe(false); + it("accepts only VP with credential(s) with complicated constraint", () => { + var trusted = isTrustedPresentation( + vpEmployee, + policyEmployeeFromAnyoneConstr, + ); + expect(trusted).toBe(true); + trusted = isTrustedPresentation(vpEmail, policyEmployeeFromAnyoneConstr); + expect(trusted).toBe(false); + trusted = isTrustedPresentation(vpTezos, policyEmployeeFromAnyoneConstr); + expect(trusted).toBe(false); }); }); diff --git a/vclogin/__tests__/extractClaims.test.js b/vclogin/__tests__/extractClaims.test.js index 00cc008..92f0525 100644 --- a/vclogin/__tests__/extractClaims.test.js +++ b/vclogin/__tests__/extractClaims.test.js @@ -8,6 +8,7 @@ import vpEmployee from "@/testdata/presentations/VP_EmployeeCredential.json"; import vpEmail from "@/testdata/presentations/VP_EmailPass.json"; import policyAcceptAnything from "@/testdata/policies/acceptAnything.json"; import policyEmailFromAltme from "@/testdata/policies/acceptEmailFromAltme.json"; +import policyEmailFromAltmeConstr from "@/testdata/policies/acceptEmailFromAltmeConstr.json"; import policyEmployeeFromAnyone from "@/testdata/policies/acceptEmployeeFromAnyone.json"; describe("extractClaims", () => { @@ -47,6 +48,17 @@ describe("extractClaims", () => { expect(claims).toStrictEqual(expected); }); + it("all designated claims from an EmailPass Credential are mapped (constrained)", () => { + var claims = extractClaims(vpEmail, policyEmailFromAltmeConstr); + var expected = { + tokenId: { + email: "felix.hoops@tum.de", + }, + tokenAccess: {}, + }; + expect(claims).toStrictEqual(expected); + }); + it("all designated claims from an EmployeeCredential are extracted", () => { var claims = extractClaims(vpEmployee, policyEmployeeFromAnyone); var expected = { diff --git a/vclogin/__tests__/testdata/policies/acceptAnything.json b/vclogin/__tests__/testdata/policies/acceptAnything.json index d4c7855..15d0d37 100644 --- a/vclogin/__tests__/testdata/policies/acceptAnything.json +++ b/vclogin/__tests__/testdata/policies/acceptAnything.json @@ -1,6 +1,6 @@ [ { - "credentialID": "credential1", + "credentialId": "credential1", "patterns": [ { "issuer": "*", diff --git a/vclogin/__tests__/testdata/policies/acceptEmailFromAltme.json b/vclogin/__tests__/testdata/policies/acceptEmailFromAltme.json index c096912..e409d18 100644 --- a/vclogin/__tests__/testdata/policies/acceptEmailFromAltme.json +++ b/vclogin/__tests__/testdata/policies/acceptEmailFromAltme.json @@ -1,6 +1,6 @@ [ { - "credentialID": "one", + "credentialId": "one", "patterns": [ { "issuer": "did:web:app.altme.io:issuer", diff --git a/vclogin/__tests__/testdata/policies/acceptEmailFromAltmeConstr.json b/vclogin/__tests__/testdata/policies/acceptEmailFromAltmeConstr.json new file mode 100644 index 0000000..7e69486 --- /dev/null +++ b/vclogin/__tests__/testdata/policies/acceptEmailFromAltmeConstr.json @@ -0,0 +1,21 @@ +[ + { + "credentialId": "one", + "patterns": [ + { + "issuer": "did:web:app.altme.io:issuer", + "claims": [ + { + "claimPath": "$.credentialSubject.email", + "token": "id_token" + } + ], + "constraint": { + "op": "equalsDID", + "a": "$VP.proof.verificationMethod", + "b": "$.credentialSubject.id" + } + } + ] + } +] diff --git a/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyone.json b/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyone.json index fe9a4af..b9d87dd 100644 --- a/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyone.json +++ b/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyone.json @@ -1,6 +1,6 @@ [ { - "credentialID": "one", + "credentialId": "one", "patterns": [ { "issuer": "*", diff --git a/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyoneConstr.json b/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyoneConstr.json new file mode 100644 index 0000000..f809654 --- /dev/null +++ b/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyoneConstr.json @@ -0,0 +1,53 @@ +[ + { + "credentialId": "one", + "patterns": [ + { + "issuer": "*", + "claims": [ + { + "claimPath": "$.credentialSubject.hasLegallyBindingName", + "newPath": "$.companyName" + }, + { + "claimPath": "$.credentialSubject.name", + "token": "id_token" + }, + { + "claimPath": "$.credentialSubject.email", + "token": "id_token" + } + ], + "constraint": { + "op": "and", + "a": { + "op": "equalsDID", + "a": "$VP.proof.verificationMethod", + "b": "$.credentialSubject.id" + }, + "b": { + "op": "and", + "a": { + "op": "endsWith", + "a": "$.credentialSubject.email", + "b": "@test.com" + }, + "b": { + "op": "or", + "a": { + "op": "matches", + "a": "$.credentialSubject.title", + "b": "C[EOT]O" + }, + "b": { + "op": "equals", + "a": "$.credentialSubject.hasJurisdiction", + "b": "GER" + } + } + } + } + } + ] + } +] diff --git a/vclogin/__tests__/testdata/policies/acceptFromAltme.json b/vclogin/__tests__/testdata/policies/acceptFromAltme.json index affcff5..f94c5b1 100644 --- a/vclogin/__tests__/testdata/policies/acceptFromAltme.json +++ b/vclogin/__tests__/testdata/policies/acceptFromAltme.json @@ -1,6 +1,6 @@ [ { - "credentialID": "one", + "credentialId": "one", "patterns": [ { "issuer": "did:web:app.altme.io:issuer", diff --git a/vclogin/lib/evaluateLoginPolicy.ts b/vclogin/lib/evaluateLoginPolicy.ts deleted file mode 100644 index dc35b93..0000000 --- a/vclogin/lib/evaluateLoginPolicy.ts +++ /dev/null @@ -1,89 +0,0 @@ -/** - * Copyright 2024 Software Engineering for Business Information Systems (sebis) . - * SPDX-License-Identifier: MIT - */ - -import { CredentialPattern, LoginPolicy } from "@/types/LoginPolicy"; -import jp from "jsonpath"; -import { getConfiguredLoginPolicy } from "@/config/loginPolicy"; - -export const isTrustedPresentation = (VP: any, policy?: LoginPolicy) => { - var configuredPolicy = getConfiguredLoginPolicy(); - if (!policy && configuredPolicy === undefined) return false; - - var usedPolicy = policy ? policy : configuredPolicy!; - - var creds = VP.verifiableCredential; - var credArr: Array; - if (!Array.isArray(creds)) { - credArr = [creds]; - } else { - credArr = creds; - } - - // collect all credentials that fit an expected credential - var patternFits = []; - for (let expectation of usedPolicy) { - let fittingCreds = []; - for (let cred of credArr) { - if (isCredentialFittingPatternList(cred, expectation.patterns)) { - fittingCreds.push(cred); - } - } - patternFits.push(fittingCreds); - } - - return hasUniquePath(patternFits, []); -}; - -const hasUniquePath = (patternFits: any[][], usedCreds: any[]): boolean => { - if (patternFits.length === 1) return patternFits[0].length > 0; - - for (let cred of patternFits[0]) { - if (!usedCreds.includes(cred)) { - usedCreds.push(cred); - let newPatternFits = patternFits.slice(1); - if (hasUniquePath(newPatternFits, usedCreds)) { - return true; - } - usedCreds.pop(); - } - } - return false; -}; - -const isCredentialFittingPatternList = ( - cred: any, - patterns: CredentialPattern[], -): boolean => { - for (let pattern of patterns) { - if (isCredentialFittingPattern(cred, pattern)) { - return true; - } - } - return false; -}; - -const isCredentialFittingPattern = ( - cred: any, - pattern: CredentialPattern, -): boolean => { - if (cred.issuer !== pattern.issuer && pattern.issuer !== "*") { - return false; - } - - for (const claim of pattern.claims) { - if ( - (!Object.hasOwn(claim, "required") || claim.required) && - jp.paths(cred, claim.claimPath).length === 0 - ) { - return false; - } - } - - return true; -}; - -export const exportedForTesting = { - hasUniquePath, -}; diff --git a/vclogin/lib/extractClaims.ts b/vclogin/lib/extractClaims.ts index e2f3eca..d07b497 100644 --- a/vclogin/lib/extractClaims.ts +++ b/vclogin/lib/extractClaims.ts @@ -3,10 +3,28 @@ * SPDX-License-Identifier: MIT */ -import { LoginPolicy, ClaimEntry } from "@/types/LoginPolicy"; +import { + LoginPolicy, + ClaimEntry, + CredentialPattern, + VcConstraint, +} from "@/types/LoginPolicy"; import jp from "jsonpath"; import { getConfiguredLoginPolicy } from "@/config/loginPolicy"; +export const isTrustedPresentation = (VP: any, policy?: LoginPolicy) => { + var configuredPolicy = getConfiguredLoginPolicy(); + if (!policy && configuredPolicy === undefined) return false; + + var usedPolicy = policy ? policy : configuredPolicy!; + + const creds = Array.isArray(VP.verifiableCredential) + ? VP.verifiableCredential + : [VP.verifiableCredential]; + + return getConstraintFit(creds, usedPolicy, VP).length > 0; +}; + export const extractClaims = (VP: any, policy?: LoginPolicy) => { var configuredPolicy = getConfiguredLoginPolicy(); if (!policy && configuredPolicy === undefined) return false; @@ -16,6 +34,7 @@ export const extractClaims = (VP: any, policy?: LoginPolicy) => { const creds = Array.isArray(VP.verifiableCredential) ? VP.verifiableCredential : [VP.verifiableCredential]; + const vcClaims = creds.map((vc: any) => extractClaimsFromVC(vc, usedPolicy)); const claims = vcClaims.reduce( (acc: any, vc: any) => Object.assign(acc, vc), @@ -24,6 +43,217 @@ export const extractClaims = (VP: any, policy?: LoginPolicy) => { return claims; }; +const getConstraintFit = ( + creds: any[], + policy: LoginPolicy, + VP: any, +): any[] => { + const patternFits = getPatternClaimFits(creds, policy); + const uniqueFits = getAllUniqueDraws(patternFits); + if (uniqueFits.length === 0) { + return []; + } + for (let fit of uniqueFits) { + if (isValidConstraintFit(fit, policy, VP)) { + return fit; + } + } + return []; +}; + +const getPatternClaimFits = (creds: any[], policy: LoginPolicy): any[][] => { + // collect all credentials that fit an expected credential claim-wise + var patternFits = []; + for (let expectation of policy) { + let fittingCreds = []; + for (let cred of creds) { + if (isCredentialFittingPatternList(cred, expectation.patterns)) { + fittingCreds.push(cred); + } + } + patternFits.push(fittingCreds); + } + + return patternFits; +}; + +const isCredentialFittingPatternList = ( + cred: any, + + patterns: CredentialPattern[], +): boolean => { + for (let pattern of patterns) { + if (isCredentialFittingPattern(cred, pattern)) { + return true; + } + } + + return false; +}; + +const isCredentialFittingPattern = ( + cred: any, + + pattern: CredentialPattern, +): boolean => { + if (cred.issuer !== pattern.issuer && pattern.issuer !== "*") { + return false; + } + + for (const claim of pattern.claims) { + if ( + (!Object.hasOwn(claim, "required") || claim.required) && + jp.paths(cred, claim.claimPath).length === 0 + ) { + return false; + } + } + + return true; +}; + +const getAllUniqueDraws = (patternFits: any[][]): any[][] => { + const draws = getAllUniqueDrawsHelper(patternFits, []); + return draws.filter((draw) => draw.length == patternFits.length); +}; + +const getAllUniqueDrawsHelper = ( + patternFits: any[][], + usedIds: any[], +): any[][] => { + if (patternFits.length === 0) { + return []; + } + + let uniqueDraws: any[][] = []; + for (let cred of patternFits[0]) { + if (!usedIds.includes(cred.id)) { + uniqueDraws.push([ + cred, + ...getAllUniqueDrawsHelper(patternFits.slice(1), [...usedIds, cred.id]), + ]); + } + } + return uniqueDraws; +}; + +const isValidConstraintFit = ( + credFit: any[], + policy: LoginPolicy, + VP: any, +): boolean => { + const credDict: any = {}; + for (let i = 0; i < policy.length; i++) { + credDict[policy[i].credentialId] = credFit[i]; + } + + for (let i = 0; i < policy.length; i++) { + const cred = credFit[i]; + const expectation = policy[i]; + var oneFittingPattern = false; + for (let pattern of expectation.patterns) { + if (isCredentialFittingPattern(cred, pattern)) { + if (pattern.constraint) { + const res = evaluateConstraint( + pattern.constraint, + cred, + credDict, + VP, + ); + if (res) { + oneFittingPattern = true; + break; + } + } + } + } + if (!oneFittingPattern) { + return true; + } + } + return false; +}; + +const evaluateConstraint = ( + constraint: VcConstraint, + cred: any, + credDict: any, + VP: any, +): boolean => { + var a = "", + b = ""; + switch (constraint.op) { + case "equals": + case "equalsDID": + case "startsWith": + case "endsWith": + case "matches": + a = resolveValue(constraint.a as string, cred, credDict, VP); + b = resolveValue(constraint.b as string, cred, credDict, VP); + } + + switch (constraint.op) { + case "equals": + return a === b; + case "equalsDID": + return ( + a.split("#").slice(0, -1).join("#") === + b.split("#").slice(0, -1).join("#") + ); + case "startsWith": + return a.startsWith(b); + case "endsWith": + return a.endsWith(b); + case "matches": + return a.match(b) !== null; + case "and": + return ( + evaluateConstraint(constraint.a as VcConstraint, cred, credDict, VP) && + evaluateConstraint(constraint.b as VcConstraint, cred, credDict, VP) + ); + case "or": + return ( + evaluateConstraint(constraint.a as VcConstraint, cred, credDict, VP) || + evaluateConstraint(constraint.b as VcConstraint, cred, credDict, VP) + ); + case "not": + return !evaluateConstraint( + constraint.a as VcConstraint, + cred, + credDict, + VP, + ); + } + throw Error("Unknown constraint operator: " + constraint.op); +}; + +const resolveValue = ( + expression: string, + cred: any, + credDict: any, + VP: any, +): string => { + if (expression.startsWith("$")) { + var nodes: any; + if (expression.startsWith("$.")) { + nodes = jp.nodes(cred, expression); + } else if (expression.startsWith("$VP.")) { + nodes = jp.nodes(VP, "$" + expression.slice(3)); + } else { + nodes = jp.nodes( + credDict[expression.slice(1).split(".")[0]], + expression.slice(1).split(".").slice(1).join("."), + ); + } + if (nodes.length > 1 || nodes.length <= 0) { + throw Error("JSON Paths in constraints must be single-valued"); + } + return nodes[0].value; + } + + return expression; +}; + const extractClaimsFromVC = (VC: any, policy: LoginPolicy) => { for (let expectation of policy) { for (let pattern of expectation.patterns) { diff --git a/vclogin/package.json b/vclogin/package.json index d131b24..2d00987 100644 --- a/vclogin/package.json +++ b/vclogin/package.json @@ -7,7 +7,7 @@ "build": "next build", "start": "next start", "lint": "next lint", - "test": "jest" + "test": "jest --coverage" }, "dependencies": { "@material-tailwind/react": "^2.0.3", diff --git a/vclogin/pages/api/presentCredential.ts b/vclogin/pages/api/presentCredential.ts index 5abae1e..2c34bd1 100644 --- a/vclogin/pages/api/presentCredential.ts +++ b/vclogin/pages/api/presentCredential.ts @@ -7,8 +7,7 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { verifyAuthenticationPresentation } from "@/lib/verifyPresentation"; import { hydraAdmin } from "@/config/ory"; import { Redis } from "ioredis"; -import { isTrustedPresentation } from "@/lib/evaluateLoginPolicy"; -import { extractClaims } from "@/lib/extractClaims"; +import { isTrustedPresentation, extractClaims } from "@/lib/extractClaims"; import * as jose from "jose"; import { keyToDID, keyToVerificationMethod } from "@spruceid/didkit-wasm-node"; import { generatePresentationDefinition } from "@/lib/generatePresentationDefinition"; diff --git a/vclogin/types/LoginPolicy.ts b/vclogin/types/LoginPolicy.ts index 89ce736..381fcea 100644 --- a/vclogin/types/LoginPolicy.ts +++ b/vclogin/types/LoginPolicy.ts @@ -3,6 +3,12 @@ * SPDX-License-Identifier: MIT */ +export type VcConstraint = { + op: string; + a: VcConstraint | string; + b: VcConstraint | string; +}; + export type ClaimEntry = { claimPath: string; newPath?: string; @@ -12,9 +18,12 @@ export type ClaimEntry = { export type CredentialPattern = { issuer: string; claims: ClaimEntry[]; + constraint?: VcConstraint; }; + export type ExpectedCredential = { - credentialID: string; + credentialId: string; patterns: CredentialPattern[]; }; + export type LoginPolicy = ExpectedCredential[]; From aca8efe297d3e73c062bf617ec047a3aaeab833f Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Fri, 15 Mar 2024 22:33:47 +0000 Subject: [PATCH 03/85] Build fix Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> --- vclogin/lib/generatePresentationDefinition.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vclogin/lib/generatePresentationDefinition.ts b/vclogin/lib/generatePresentationDefinition.ts index e4d988b..0f39789 100644 --- a/vclogin/lib/generatePresentationDefinition.ts +++ b/vclogin/lib/generatePresentationDefinition.ts @@ -55,21 +55,21 @@ export const generatePresentationDefinition = (policy: LoginPolicy) => { let req = { rule: "pick", count: 1, - from: "group_" + expectation.credentialID, + from: "group_" + expectation.credentialId, }; pd.submission_requirements.push(req); } for (let pattern of expectation.patterns) { let descr: any = { - id: expectation.credentialID, + id: expectation.credentialId, purpose: "Sign-in", - name: "Input descriptor for " + expectation.credentialID, + name: "Input descriptor for " + expectation.credentialId, constraints: {}, }; if (expectation.patterns.length > 1) { - descr.group = ["group_" + expectation.credentialID]; + descr.group = ["group_" + expectation.credentialId]; } let fields = pattern.claims From 768e9d5e55a8f780ad4d2163127e123b3d82543b Mon Sep 17 00:00:00 2001 From: Ilayda Cansin Koc Date: Thu, 18 Apr 2024 14:28:11 +0000 Subject: [PATCH 04/85] Add new API endpoints for authentication response and test requests --- vclogin/pages/api/getAuthResponse.ts | 32 ++++++++++++++++++++++++++++ vclogin/pages/api/new.ts | 16 ++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 vclogin/pages/api/getAuthResponse.ts create mode 100644 vclogin/pages/api/new.ts diff --git a/vclogin/pages/api/getAuthResponse.ts b/vclogin/pages/api/getAuthResponse.ts new file mode 100644 index 0000000..36d1040 --- /dev/null +++ b/vclogin/pages/api/getAuthResponse.ts @@ -0,0 +1,32 @@ +import { NextApiRequest, NextApiResponse } from "next"; +import { Redis } from "ioredis"; + +var redis: Redis; +try { + redis = new Redis(parseInt(process.env.REDIS_PORT!), process.env.REDIS_HOST!); +} catch (error) { + console.error("Failed to connect to Redis:", error); +} + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse, +) { + //Get Policy from request body + const { uuid } = req.body; + console.log("GET AUTH RESPONSE API"); + + // Read auth_res from redis and check if it matches the uuid + const auth_res = await redis.get("auth_res"); + console.log("auth_res: ", auth_res, uuid); + try { + if (auth_res === uuid) { + console.log("auth_res matches uuid"); + return res.status(200).json({ res: "success" }); + } else { + return res.status(200).json({ res: "no_match" }); + } + } catch { + return res.status(200).json({ res: "error" }); + } +} diff --git a/vclogin/pages/api/new.ts b/vclogin/pages/api/new.ts new file mode 100644 index 0000000..65d5d3c --- /dev/null +++ b/vclogin/pages/api/new.ts @@ -0,0 +1,16 @@ +import { NextApiRequest, NextApiResponse } from "next"; +import { Redis } from "ioredis"; + +var redis: Redis; +try { + redis = new Redis(parseInt(process.env.REDIS_PORT!), process.env.REDIS_HOST!); +} catch (error) { + console.error("Failed to connect to Redis:", error); +} + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse, +) { + return res.status(200).json({ message: "Hello World" }); +} From f4d23d58a1123f4c2b4e56b595d990cb75b93491 Mon Sep 17 00:00:00 2001 From: Ilayda Cansin Koc Date: Sun, 9 Jun 2024 17:25:37 +0000 Subject: [PATCH 05/85] revert: delete unused API endpoints for authentication response and test requests --- vclogin/pages/api/getAuthResponse.ts | 32 ---------------------------- vclogin/pages/api/new.ts | 16 -------------- 2 files changed, 48 deletions(-) delete mode 100644 vclogin/pages/api/getAuthResponse.ts delete mode 100644 vclogin/pages/api/new.ts diff --git a/vclogin/pages/api/getAuthResponse.ts b/vclogin/pages/api/getAuthResponse.ts deleted file mode 100644 index 36d1040..0000000 --- a/vclogin/pages/api/getAuthResponse.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { NextApiRequest, NextApiResponse } from "next"; -import { Redis } from "ioredis"; - -var redis: Redis; -try { - redis = new Redis(parseInt(process.env.REDIS_PORT!), process.env.REDIS_HOST!); -} catch (error) { - console.error("Failed to connect to Redis:", error); -} - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse, -) { - //Get Policy from request body - const { uuid } = req.body; - console.log("GET AUTH RESPONSE API"); - - // Read auth_res from redis and check if it matches the uuid - const auth_res = await redis.get("auth_res"); - console.log("auth_res: ", auth_res, uuid); - try { - if (auth_res === uuid) { - console.log("auth_res matches uuid"); - return res.status(200).json({ res: "success" }); - } else { - return res.status(200).json({ res: "no_match" }); - } - } catch { - return res.status(200).json({ res: "error" }); - } -} diff --git a/vclogin/pages/api/new.ts b/vclogin/pages/api/new.ts deleted file mode 100644 index 65d5d3c..0000000 --- a/vclogin/pages/api/new.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { NextApiRequest, NextApiResponse } from "next"; -import { Redis } from "ioredis"; - -var redis: Redis; -try { - redis = new Redis(parseInt(process.env.REDIS_PORT!), process.env.REDIS_HOST!); -} catch (error) { - console.error("Failed to connect to Redis:", error); -} - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse, -) { - return res.status(200).json({ message: "Hello World" }); -} From 48de0dc801aa57e553ac6c2fdbe49f2bf58e4de9 Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Mon, 10 Jun 2024 10:40:17 +0200 Subject: [PATCH 06/85] OIDC Discovery Fix Fixes issue #20 Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> --- compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compose.yaml b/compose.yaml index eb92048..1590968 100644 --- a/compose.yaml +++ b/compose.yaml @@ -43,7 +43,7 @@ services: # - TRACING_PROVIDERS_JAEGER_LOCAL_AGENT_ADDRESS=jaeger:6831 # - TRACING_PROVIDERS_JAEGER_SAMPLING_TYPE=const # - TRACING_PROVIDERS_JAEGER_SAMPLING_VALUE=1 - - WEBFINGER_OIDC_DISCOVERY_USERINFO_URL=http://hydra:4444/userinfo + - WEBFINGER_OIDC_DISCOVERY_USERINFO_URL=http://localhost:5004/userinfo - OIDC_DYNAMIC_CLIENT_REGISTRATION_ENABLED=true restart: on-failure From 3a7a2983dd7ecc82d3bc4553671963fd85c07be2 Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Mon, 10 Jun 2024 12:45:28 +0200 Subject: [PATCH 07/85] Changed default token Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> --- README.md | 6 +++--- vclogin/__tests__/extractClaims.test.js | 6 ++++-- .../testdata/policies/acceptEmailFromAltme.json | 3 +-- .../testdata/policies/acceptEmailFromAltmeConstr.json | 3 +-- .../testdata/policies/acceptEmployeeFromAnyone.json | 6 ++---- .../policies/acceptEmployeeFromAnyoneConstr.json | 6 ++---- vclogin/lib/extractClaims.ts | 10 ++++------ 7 files changed, 17 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index eee1a8e..8829498 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ ![build workflow](https://github.com/GAIA-X4PLC-AAD/ssi-to-oidc-bridge/actions/workflows/node.js.yml/badge.svg) > [!WARNING] -> This repository is intended for prototyping and as a reference implementation. +> This repository is intended for prototyping and as a reference implementation. At this time, no security guarantees can be given. > [!NOTE] > A preprint of our paper providing more background information is available [on arXiv](https://arxiv.org/abs/2401.09488). @@ -195,7 +195,7 @@ Now you can develop and it will hot-reload. ## Policy Configuration -The login policy is the one configuration file that configures the bridge's behavior. The most simple example of one looks like this and accepts any credential, while forwarding all subject fields to the `access_token`: +The login policy is the one configuration file that configures the bridge's behavior. The most simple example of one looks like this and accepts any credential, while forwarding all subject fields to the `id_token`: ```JSON [ @@ -223,7 +223,7 @@ A pattern object has the following fields: - `claimPath` is a JSONPath that points to one or more values in the credential. If it points to multiple values, they will be aggregated in a new object and indexed by just their final JSONPath component. _This is generally convenient, but can lead to values being overwritten if not careful and working with a credential that uses the same path components in different depths._ - `newPath` is the new path of the value relative to the root of the token it will be written into. This value is optional, as long as `claimPath` points to exactly one value. In that case, it defaults to `$.`. -- `token` optionally defines if the claim value ends up either in `"id_token"` or `"access_token"`, with the latter being the default. +- `token` optionally defines if the claim value ends up either in `"id_token"` or `"access_token"`, with the former being the default. - `required` is optional and defaults to `false` ## Token Introspection diff --git a/vclogin/__tests__/extractClaims.test.js b/vclogin/__tests__/extractClaims.test.js index 92f0525..01094ca 100644 --- a/vclogin/__tests__/extractClaims.test.js +++ b/vclogin/__tests__/extractClaims.test.js @@ -16,6 +16,8 @@ describe("extractClaims", () => { var claims = extractClaims(vpEmployee, policyAcceptAnything); var expected = { tokenAccess: { + }, + tokenId: { subjectData: { id: "did:key:z6MkkdC46uhBGjMYS2ZDLUwCrTWdaqZdTD3596sN4397oRNd", hash: "9ecf754ffdad0c6de238f60728a90511780b2f7dbe2f0ea015115515f3f389cd", @@ -31,8 +33,8 @@ describe("extractClaims", () => { hasJurisdiction: "GER", surname: "Surname", }, + }, - tokenId: {}, }; expect(claims).toStrictEqual(expected); }); @@ -63,11 +65,11 @@ describe("extractClaims", () => { var claims = extractClaims(vpEmployee, policyEmployeeFromAnyone); var expected = { tokenAccess: { - companyName: "deltaDAO AG", }, tokenId: { email: "test@test.com", name: "Name Surname", + companyName: "deltaDAO AG", }, }; expect(claims).toStrictEqual(expected); diff --git a/vclogin/__tests__/testdata/policies/acceptEmailFromAltme.json b/vclogin/__tests__/testdata/policies/acceptEmailFromAltme.json index e409d18..72e3c73 100644 --- a/vclogin/__tests__/testdata/policies/acceptEmailFromAltme.json +++ b/vclogin/__tests__/testdata/policies/acceptEmailFromAltme.json @@ -6,8 +6,7 @@ "issuer": "did:web:app.altme.io:issuer", "claims": [ { - "claimPath": "$.credentialSubject.email", - "token": "id_token" + "claimPath": "$.credentialSubject.email" } ] } diff --git a/vclogin/__tests__/testdata/policies/acceptEmailFromAltmeConstr.json b/vclogin/__tests__/testdata/policies/acceptEmailFromAltmeConstr.json index 7e69486..4f41086 100644 --- a/vclogin/__tests__/testdata/policies/acceptEmailFromAltmeConstr.json +++ b/vclogin/__tests__/testdata/policies/acceptEmailFromAltmeConstr.json @@ -6,8 +6,7 @@ "issuer": "did:web:app.altme.io:issuer", "claims": [ { - "claimPath": "$.credentialSubject.email", - "token": "id_token" + "claimPath": "$.credentialSubject.email" } ], "constraint": { diff --git a/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyone.json b/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyone.json index b9d87dd..2c581b8 100644 --- a/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyone.json +++ b/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyone.json @@ -10,12 +10,10 @@ "newPath": "$.companyName" }, { - "claimPath": "$.credentialSubject.name", - "token": "id_token" + "claimPath": "$.credentialSubject.name" }, { - "claimPath": "$.credentialSubject.email", - "token": "id_token" + "claimPath": "$.credentialSubject.email" } ] } diff --git a/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyoneConstr.json b/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyoneConstr.json index f809654..78c9d64 100644 --- a/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyoneConstr.json +++ b/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyoneConstr.json @@ -10,12 +10,10 @@ "newPath": "$.companyName" }, { - "claimPath": "$.credentialSubject.name", - "token": "id_token" + "claimPath": "$.credentialSubject.name" }, { - "claimPath": "$.credentialSubject.email", - "token": "id_token" + "claimPath": "$.credentialSubject.email" } ], "constraint": { diff --git a/vclogin/lib/extractClaims.ts b/vclogin/lib/extractClaims.ts index d07b497..7162ac3 100644 --- a/vclogin/lib/extractClaims.ts +++ b/vclogin/lib/extractClaims.ts @@ -79,7 +79,6 @@ const getPatternClaimFits = (creds: any[], policy: LoginPolicy): any[][] => { const isCredentialFittingPatternList = ( cred: any, - patterns: CredentialPattern[], ): boolean => { for (let pattern of patterns) { @@ -93,7 +92,6 @@ const isCredentialFittingPatternList = ( const isCredentialFittingPattern = ( cred: any, - pattern: CredentialPattern, ): boolean => { if (cred.issuer !== pattern.issuer && pattern.issuer !== "*") { @@ -284,7 +282,7 @@ const extractClaimsFromVC = (VC: any, policy: LoginPolicy) => { if (!newPath) { throw Error( "New path not defined for multi-valued claim: " + - claim.claimPath, + claim.claimPath, ); } @@ -304,9 +302,9 @@ const extractClaimsFromVC = (VC: any, policy: LoginPolicy) => { } const claimTarget = - claim.token === "id_token" - ? extractedClaims.tokenId - : extractedClaims.tokenAccess; + claim.token === "access_token" + ? extractedClaims.tokenAccess + : extractedClaims.tokenId; jp.value(claimTarget, newPath, value); } From 0a5debaaed02ffd8df211885a2c0cec8f08df647 Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Mon, 10 Jun 2024 12:45:28 +0200 Subject: [PATCH 08/85] Changed default token Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> --- README.md | 6 +++--- vclogin/__tests__/extractClaims.test.js | 9 ++++----- .../testdata/policies/acceptEmailFromAltme.json | 3 +-- .../testdata/policies/acceptEmailFromAltmeConstr.json | 3 +-- .../testdata/policies/acceptEmployeeFromAnyone.json | 6 ++---- .../policies/acceptEmployeeFromAnyoneConstr.json | 6 ++---- vclogin/lib/extractClaims.ts | 8 +++----- 7 files changed, 16 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index eee1a8e..8829498 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ ![build workflow](https://github.com/GAIA-X4PLC-AAD/ssi-to-oidc-bridge/actions/workflows/node.js.yml/badge.svg) > [!WARNING] -> This repository is intended for prototyping and as a reference implementation. +> This repository is intended for prototyping and as a reference implementation. At this time, no security guarantees can be given. > [!NOTE] > A preprint of our paper providing more background information is available [on arXiv](https://arxiv.org/abs/2401.09488). @@ -195,7 +195,7 @@ Now you can develop and it will hot-reload. ## Policy Configuration -The login policy is the one configuration file that configures the bridge's behavior. The most simple example of one looks like this and accepts any credential, while forwarding all subject fields to the `access_token`: +The login policy is the one configuration file that configures the bridge's behavior. The most simple example of one looks like this and accepts any credential, while forwarding all subject fields to the `id_token`: ```JSON [ @@ -223,7 +223,7 @@ A pattern object has the following fields: - `claimPath` is a JSONPath that points to one or more values in the credential. If it points to multiple values, they will be aggregated in a new object and indexed by just their final JSONPath component. _This is generally convenient, but can lead to values being overwritten if not careful and working with a credential that uses the same path components in different depths._ - `newPath` is the new path of the value relative to the root of the token it will be written into. This value is optional, as long as `claimPath` points to exactly one value. In that case, it defaults to `$.`. -- `token` optionally defines if the claim value ends up either in `"id_token"` or `"access_token"`, with the latter being the default. +- `token` optionally defines if the claim value ends up either in `"id_token"` or `"access_token"`, with the former being the default. - `required` is optional and defaults to `false` ## Token Introspection diff --git a/vclogin/__tests__/extractClaims.test.js b/vclogin/__tests__/extractClaims.test.js index 92f0525..4aae794 100644 --- a/vclogin/__tests__/extractClaims.test.js +++ b/vclogin/__tests__/extractClaims.test.js @@ -15,7 +15,8 @@ describe("extractClaims", () => { it("all subject claims from an EmployeeCredential are extracted", () => { var claims = extractClaims(vpEmployee, policyAcceptAnything); var expected = { - tokenAccess: { + tokenAccess: {}, + tokenId: { subjectData: { id: "did:key:z6MkkdC46uhBGjMYS2ZDLUwCrTWdaqZdTD3596sN4397oRNd", hash: "9ecf754ffdad0c6de238f60728a90511780b2f7dbe2f0ea015115515f3f389cd", @@ -32,7 +33,6 @@ describe("extractClaims", () => { surname: "Surname", }, }, - tokenId: {}, }; expect(claims).toStrictEqual(expected); }); @@ -62,12 +62,11 @@ describe("extractClaims", () => { it("all designated claims from an EmployeeCredential are extracted", () => { var claims = extractClaims(vpEmployee, policyEmployeeFromAnyone); var expected = { - tokenAccess: { - companyName: "deltaDAO AG", - }, + tokenAccess: {}, tokenId: { email: "test@test.com", name: "Name Surname", + companyName: "deltaDAO AG", }, }; expect(claims).toStrictEqual(expected); diff --git a/vclogin/__tests__/testdata/policies/acceptEmailFromAltme.json b/vclogin/__tests__/testdata/policies/acceptEmailFromAltme.json index e409d18..72e3c73 100644 --- a/vclogin/__tests__/testdata/policies/acceptEmailFromAltme.json +++ b/vclogin/__tests__/testdata/policies/acceptEmailFromAltme.json @@ -6,8 +6,7 @@ "issuer": "did:web:app.altme.io:issuer", "claims": [ { - "claimPath": "$.credentialSubject.email", - "token": "id_token" + "claimPath": "$.credentialSubject.email" } ] } diff --git a/vclogin/__tests__/testdata/policies/acceptEmailFromAltmeConstr.json b/vclogin/__tests__/testdata/policies/acceptEmailFromAltmeConstr.json index 7e69486..4f41086 100644 --- a/vclogin/__tests__/testdata/policies/acceptEmailFromAltmeConstr.json +++ b/vclogin/__tests__/testdata/policies/acceptEmailFromAltmeConstr.json @@ -6,8 +6,7 @@ "issuer": "did:web:app.altme.io:issuer", "claims": [ { - "claimPath": "$.credentialSubject.email", - "token": "id_token" + "claimPath": "$.credentialSubject.email" } ], "constraint": { diff --git a/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyone.json b/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyone.json index b9d87dd..2c581b8 100644 --- a/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyone.json +++ b/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyone.json @@ -10,12 +10,10 @@ "newPath": "$.companyName" }, { - "claimPath": "$.credentialSubject.name", - "token": "id_token" + "claimPath": "$.credentialSubject.name" }, { - "claimPath": "$.credentialSubject.email", - "token": "id_token" + "claimPath": "$.credentialSubject.email" } ] } diff --git a/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyoneConstr.json b/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyoneConstr.json index f809654..78c9d64 100644 --- a/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyoneConstr.json +++ b/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyoneConstr.json @@ -10,12 +10,10 @@ "newPath": "$.companyName" }, { - "claimPath": "$.credentialSubject.name", - "token": "id_token" + "claimPath": "$.credentialSubject.name" }, { - "claimPath": "$.credentialSubject.email", - "token": "id_token" + "claimPath": "$.credentialSubject.email" } ], "constraint": { diff --git a/vclogin/lib/extractClaims.ts b/vclogin/lib/extractClaims.ts index d07b497..8ef2c35 100644 --- a/vclogin/lib/extractClaims.ts +++ b/vclogin/lib/extractClaims.ts @@ -79,7 +79,6 @@ const getPatternClaimFits = (creds: any[], policy: LoginPolicy): any[][] => { const isCredentialFittingPatternList = ( cred: any, - patterns: CredentialPattern[], ): boolean => { for (let pattern of patterns) { @@ -93,7 +92,6 @@ const isCredentialFittingPatternList = ( const isCredentialFittingPattern = ( cred: any, - pattern: CredentialPattern, ): boolean => { if (cred.issuer !== pattern.issuer && pattern.issuer !== "*") { @@ -304,9 +302,9 @@ const extractClaimsFromVC = (VC: any, policy: LoginPolicy) => { } const claimTarget = - claim.token === "id_token" - ? extractedClaims.tokenId - : extractedClaims.tokenAccess; + claim.token === "access_token" + ? extractedClaims.tokenAccess + : extractedClaims.tokenId; jp.value(claimTarget, newPath, value); } From 465ae3c8cf35563f69115586480c2585ef331cb7 Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Mon, 10 Jun 2024 17:29:17 +0200 Subject: [PATCH 09/85] Adopted absolute imports Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> --- .../{ => lib}/evaluateLoginPolicy.test.js | 0 .../__tests__/{ => lib}/extractClaims.test.js | 0 .../generatePresentationDefinition.test.js | 0 .../{ => lib}/verifyPresentation.test.js | 0 vclogin/__tests__/{ => pages}/index.test.js | 0 vclogin/__tests__/{ => pages}/login.test.js | 0 vclogin/lib/extractClaims.ts | 2 +- vclogin/tsconfig.json | 38 ++++++++++++++++--- 8 files changed, 34 insertions(+), 6 deletions(-) rename vclogin/__tests__/{ => lib}/evaluateLoginPolicy.test.js (100%) rename vclogin/__tests__/{ => lib}/extractClaims.test.js (100%) rename vclogin/__tests__/{ => lib}/generatePresentationDefinition.test.js (100%) rename vclogin/__tests__/{ => lib}/verifyPresentation.test.js (100%) rename vclogin/__tests__/{ => pages}/index.test.js (100%) rename vclogin/__tests__/{ => pages}/login.test.js (100%) diff --git a/vclogin/__tests__/evaluateLoginPolicy.test.js b/vclogin/__tests__/lib/evaluateLoginPolicy.test.js similarity index 100% rename from vclogin/__tests__/evaluateLoginPolicy.test.js rename to vclogin/__tests__/lib/evaluateLoginPolicy.test.js diff --git a/vclogin/__tests__/extractClaims.test.js b/vclogin/__tests__/lib/extractClaims.test.js similarity index 100% rename from vclogin/__tests__/extractClaims.test.js rename to vclogin/__tests__/lib/extractClaims.test.js diff --git a/vclogin/__tests__/generatePresentationDefinition.test.js b/vclogin/__tests__/lib/generatePresentationDefinition.test.js similarity index 100% rename from vclogin/__tests__/generatePresentationDefinition.test.js rename to vclogin/__tests__/lib/generatePresentationDefinition.test.js diff --git a/vclogin/__tests__/verifyPresentation.test.js b/vclogin/__tests__/lib/verifyPresentation.test.js similarity index 100% rename from vclogin/__tests__/verifyPresentation.test.js rename to vclogin/__tests__/lib/verifyPresentation.test.js diff --git a/vclogin/__tests__/index.test.js b/vclogin/__tests__/pages/index.test.js similarity index 100% rename from vclogin/__tests__/index.test.js rename to vclogin/__tests__/pages/index.test.js diff --git a/vclogin/__tests__/login.test.js b/vclogin/__tests__/pages/login.test.js similarity index 100% rename from vclogin/__tests__/login.test.js rename to vclogin/__tests__/pages/login.test.js diff --git a/vclogin/lib/extractClaims.ts b/vclogin/lib/extractClaims.ts index 8ef2c35..7162ac3 100644 --- a/vclogin/lib/extractClaims.ts +++ b/vclogin/lib/extractClaims.ts @@ -282,7 +282,7 @@ const extractClaimsFromVC = (VC: any, policy: LoginPolicy) => { if (!newPath) { throw Error( "New path not defined for multi-valued claim: " + - claim.claimPath, + claim.claimPath, ); } diff --git a/vclogin/tsconfig.json b/vclogin/tsconfig.json index 6ee3994..e5a03ed 100644 --- a/vclogin/tsconfig.json +++ b/vclogin/tsconfig.json @@ -1,7 +1,11 @@ { "compilerOptions": { "target": "es5", - "lib": ["dom", "dom.iterable", "esnext"], + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], "allowJs": true, "skipLibCheck": true, "strict": true, @@ -14,11 +18,35 @@ "isolatedModules": true, "jsx": "preserve", "incremental": true, + "baseUrl": ".", "paths": { - "@/*": ["./*"] + "@/testdata/*": [ + "__tests__/testdata/*" + ], + "@/lib/*": [ + "lib/*" + ], + "@/config/*": [ + "config/*" + ], + "@/types/*": [ + "types/*" + ], + "@/pages/*": [ + "pages/*" + ], }, - "typeRoots": ["./types"] + "typeRoots": [ + "./types" + ] }, - "include": ["next-env.d.ts", "types/**/*.ts", "**/*.ts", "**/*.tsx"], - "exclude": ["node_modules"] + "include": [ + "next-env.d.ts", + "types/**/*.ts", + "**/*.ts", + "**/*.tsx" + ], + "exclude": [ + "node_modules" + ] } From 9325e78a4ed142a1e0bb2d2f523dd5016a0f3339 Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Mon, 10 Jun 2024 17:29:17 +0200 Subject: [PATCH 10/85] Adopted absolute imports Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> --- .../{ => lib}/evaluateLoginPolicy.test.js | 0 .../__tests__/{ => lib}/extractClaims.test.js | 0 .../generatePresentationDefinition.test.js | 0 .../{ => lib}/verifyPresentation.test.js | 0 vclogin/__tests__/{ => pages}/index.test.js | 0 vclogin/__tests__/{ => pages}/login.test.js | 0 vclogin/tsconfig.json | 38 ++++++++++++++++--- 7 files changed, 33 insertions(+), 5 deletions(-) rename vclogin/__tests__/{ => lib}/evaluateLoginPolicy.test.js (100%) rename vclogin/__tests__/{ => lib}/extractClaims.test.js (100%) rename vclogin/__tests__/{ => lib}/generatePresentationDefinition.test.js (100%) rename vclogin/__tests__/{ => lib}/verifyPresentation.test.js (100%) rename vclogin/__tests__/{ => pages}/index.test.js (100%) rename vclogin/__tests__/{ => pages}/login.test.js (100%) diff --git a/vclogin/__tests__/evaluateLoginPolicy.test.js b/vclogin/__tests__/lib/evaluateLoginPolicy.test.js similarity index 100% rename from vclogin/__tests__/evaluateLoginPolicy.test.js rename to vclogin/__tests__/lib/evaluateLoginPolicy.test.js diff --git a/vclogin/__tests__/extractClaims.test.js b/vclogin/__tests__/lib/extractClaims.test.js similarity index 100% rename from vclogin/__tests__/extractClaims.test.js rename to vclogin/__tests__/lib/extractClaims.test.js diff --git a/vclogin/__tests__/generatePresentationDefinition.test.js b/vclogin/__tests__/lib/generatePresentationDefinition.test.js similarity index 100% rename from vclogin/__tests__/generatePresentationDefinition.test.js rename to vclogin/__tests__/lib/generatePresentationDefinition.test.js diff --git a/vclogin/__tests__/verifyPresentation.test.js b/vclogin/__tests__/lib/verifyPresentation.test.js similarity index 100% rename from vclogin/__tests__/verifyPresentation.test.js rename to vclogin/__tests__/lib/verifyPresentation.test.js diff --git a/vclogin/__tests__/index.test.js b/vclogin/__tests__/pages/index.test.js similarity index 100% rename from vclogin/__tests__/index.test.js rename to vclogin/__tests__/pages/index.test.js diff --git a/vclogin/__tests__/login.test.js b/vclogin/__tests__/pages/login.test.js similarity index 100% rename from vclogin/__tests__/login.test.js rename to vclogin/__tests__/pages/login.test.js diff --git a/vclogin/tsconfig.json b/vclogin/tsconfig.json index 6ee3994..e5a03ed 100644 --- a/vclogin/tsconfig.json +++ b/vclogin/tsconfig.json @@ -1,7 +1,11 @@ { "compilerOptions": { "target": "es5", - "lib": ["dom", "dom.iterable", "esnext"], + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], "allowJs": true, "skipLibCheck": true, "strict": true, @@ -14,11 +18,35 @@ "isolatedModules": true, "jsx": "preserve", "incremental": true, + "baseUrl": ".", "paths": { - "@/*": ["./*"] + "@/testdata/*": [ + "__tests__/testdata/*" + ], + "@/lib/*": [ + "lib/*" + ], + "@/config/*": [ + "config/*" + ], + "@/types/*": [ + "types/*" + ], + "@/pages/*": [ + "pages/*" + ], }, - "typeRoots": ["./types"] + "typeRoots": [ + "./types" + ] }, - "include": ["next-env.d.ts", "types/**/*.ts", "**/*.ts", "**/*.tsx"], - "exclude": ["node_modules"] + "include": [ + "next-env.d.ts", + "types/**/*.ts", + "**/*.ts", + "**/*.tsx" + ], + "exclude": [ + "node_modules" + ] } From aa0ac0f63c39a3c0c4f6c29908b981496c595382 Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Tue, 11 Jun 2024 11:55:33 +0200 Subject: [PATCH 11/85] Fix missing styles source Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> --- .github/workflows/prettier.yml | 2 +- vclogin/tsconfig.json | 42 ++++++++-------------------------- 2 files changed, 11 insertions(+), 33 deletions(-) diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml index b229fa4..1d66581 100644 --- a/.github/workflows/prettier.yml +++ b/.github/workflows/prettier.yml @@ -22,5 +22,5 @@ jobs: uses: creyD/prettier_action@v4.3 with: # This part is also where you can pass other options, for example: - prettier_options: --write **/*.{js,ts,tsx,md} + prettier_options: --write **/*.{js,ts,tsx,md,json} same_commit: true diff --git a/vclogin/tsconfig.json b/vclogin/tsconfig.json index e5a03ed..c9076cb 100644 --- a/vclogin/tsconfig.json +++ b/vclogin/tsconfig.json @@ -1,11 +1,7 @@ { "compilerOptions": { "target": "es5", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], + "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "strict": true, @@ -20,33 +16,15 @@ "incremental": true, "baseUrl": ".", "paths": { - "@/testdata/*": [ - "__tests__/testdata/*" - ], - "@/lib/*": [ - "lib/*" - ], - "@/config/*": [ - "config/*" - ], - "@/types/*": [ - "types/*" - ], - "@/pages/*": [ - "pages/*" - ], + "@/testdata/*": ["__tests__/testdata/*"], + "@/lib/*": ["lib/*"], + "@/config/*": ["config/*"], + "@/types/*": ["types/*"], + "@/pages/*": ["pages/*"], + "@/styles/*": ["styles/*"] }, - "typeRoots": [ - "./types" - ] + "typeRoots": ["./types"] }, - "include": [ - "next-env.d.ts", - "types/**/*.ts", - "**/*.ts", - "**/*.tsx" - ], - "exclude": [ - "node_modules" - ] + "include": ["next-env.d.ts", "types/**/*.ts", "**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] } From 7287aa6f9000115cea5c1cae167c3c11ed4fb97e Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Tue, 11 Jun 2024 12:02:02 +0200 Subject: [PATCH 12/85] Readme disclaimer Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> --- README.md | 94 +++++++++++++++++++++++++++---------------------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index 8829498..3190a1e 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ > This repository is intended for prototyping and as a reference implementation. At this time, no security guarantees can be given. > [!NOTE] -> A preprint of our paper providing more background information is available [on arXiv](https://arxiv.org/abs/2401.09488). +> A preprint of our paper providing more background information is available [on arXiv](https://arxiv.org/abs/2401.09488). While it is slightly outdated now, it provides a good introduction. > [!NOTE] > This software artifact was originally intended to support only Gaia-X Participant Credentials. It has since evolved to be fully configurable for almost any Verifiable Credential, almost any wallet application, and almost any current OIDC client. @@ -62,55 +62,55 @@ The user's browser starts out on the service website, which takes the role of an ```mermaid sequenceDiagram - autonumber - actor User - participant Browser - participant Client as OIDC Client (Web Server) + autonumber + actor User + participant Browser + participant Client as OIDC Client (Web Server)     participant Wallet as Smartphone Wallet     participant VPLS as vclogin     participant Redis     participant OP as Ory Hydra - User->>Browser: Click "Sign-in" - Browser->>OP: Redirect to /authorize - OP->>VPLS: Redirect to /login?login_challenge= - VPLS->>VPLS: Generate random UUID to replace challenge - VPLS->>Redis: Save (UUID,challenge) and (challenge,UUID) - VPLS->>Browser: Send login page - Browser->>User: Show login page with QR Code - User->>Wallet: Scan QR code containing SIOP Provider Invocation - Wallet->>VPLS: GET /api/presentCredential?login_id= - VPLS->>VPLS: Generate and sign Auth Request JWT - VPLS->>Wallet: Auth Request with Presentation Definition - Wallet->>VPLS: GET /api/clientMetadata - VPLS->>Wallet: Return static Client Metadata - Wallet->>User: Prompt for VC selection and consent - User->>Wallet: Choose VC(s) and confirm - Wallet->>Wallet: Create and sign VP - Wallet->>VPLS: Submit Auth Response via POST /api/presentCredential - VPLS->>VPLS: Verify VP - VPLS->>VPLS: Process claims from VP - VPLS->>Redis: Get challenge using UUID - VPLS->>OP: Confirm sign-in for subject DID using challenge - OP->>VPLS: Client redirect link - VPLS->>Redis: Save (subject DID, claims) - VPLS->>Redis: Save ("redirect" + UUID, redirect) - loop Every few seconds - Browser->>VPLS: Try to retrieve redirect using challenge - Note over Client,Redis: Failed lookups omitted - end - Browser->>VPLS: Get redirect using challenge - VPLS->>Redis: Get UUID using challenge - VPLS->>Redis: Get redirect using UUID - VPLS->>OP: Redirect to Hydra - OP->>VPLS: Redirect to /api/consent?consent_challenge= - VPLS->>OP: Get consent metadata using challenge2 - OP->>VPLS: Metadata including subject DID - VPLS->>Redis: Get claims using subject DID - VPLS->>OP: Confirm consent and send user claims - OP->>Client: Redirect to client callback with code - Client->>OP: Get tokens using code - OP->>Client: Return id_token and access_token - Client->>Browser: Provide access to protected service + User->>Browser: Click "Sign-in" + Browser->>OP: Redirect to /authorize + OP->>VPLS: Redirect to /login?login_challenge= + VPLS->>VPLS: Generate random UUID to replace challenge + VPLS->>Redis: Save (UUID,challenge) and (challenge,UUID) + VPLS->>Browser: Send login page + Browser->>User: Show login page with QR Code + User->>Wallet: Scan QR code containing SIOP Provider Invocation + Wallet->>VPLS: GET /api/presentCredential?login_id= + VPLS->>VPLS: Generate and sign Auth Request JWT + VPLS->>Wallet: Auth Request with Presentation Definition + Wallet->>VPLS: GET /api/clientMetadata + VPLS->>Wallet: Return static Client Metadata + Wallet->>User: Prompt for VC selection and consent + User->>Wallet: Choose VC(s) and confirm + Wallet->>Wallet: Create and sign VP + Wallet->>VPLS: Submit Auth Response via POST /api/presentCredential + VPLS->>VPLS: Verify VP + VPLS->>VPLS: Process claims from VP + VPLS->>Redis: Get challenge using UUID + VPLS->>OP: Confirm sign-in for subject DID using challenge + OP->>VPLS: Client redirect link + VPLS->>Redis: Save (subject DID, claims) + VPLS->>Redis: Save ("redirect" + UUID, redirect) + loop Every few seconds + Browser->>VPLS: Try to retrieve redirect using challenge + Note over Client,Redis: Failed lookups omitted + end + Browser->>VPLS: Get redirect using challenge + VPLS->>Redis: Get UUID using challenge + VPLS->>Redis: Get redirect using UUID + VPLS->>OP: Redirect to Hydra + OP->>VPLS: Redirect to /api/consent?consent_challenge= + VPLS->>OP: Get consent metadata using challenge2 + OP->>VPLS: Metadata including subject DID + VPLS->>Redis: Get claims using subject DID + VPLS->>OP: Confirm consent and send user claims + OP->>Client: Redirect to client callback with code + Client->>OP: Get tokens using code + OP->>Client: Return id_token and access_token + Client->>Browser: Provide access to protected service ``` ## Running a Local Deployment @@ -118,7 +118,7 @@ sequenceDiagram A local deployment is a great way to test the bridge and to use it for prototyping an OIDC client service you are developing. > [!IMPORTANT] -> You need to use a tool like ngrok for testing so your smartphone wallet can access the vclogin backend. However, it can lead to issues with `application/x-www-form-urlencoded` request bodies used in the flow (https://ngrok.com/docs/ngrok-agent/changelog/#changes-in-22). But you can manually replay that request on the ngrok interface, if you run into problems. +> You need to use a tool like ngrok for testing so your smartphone wallet can access the vclogin backend. However, it can lead to issues with `application/x-www-form-urlencoded` request bodies used in the flow (). But you can manually replay that request on the ngrok interface, if you run into problems. 1. `$ ngrok http 5002`, which will set up a randomly generated URL 2. enter the domain for the vclogin service into the env file `/vclogin/.env` with key `EXTERNAL_URL` From a7d85792c20aa87f2bd3bcf8a9db761d6fab8828 Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Tue, 11 Jun 2024 12:19:23 +0200 Subject: [PATCH 13/85] Less Hydra logs Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> --- README.md | 12 ++++++++++++ compose.yaml | 5 +++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3190a1e..7f509a3 100644 --- a/README.md +++ b/README.md @@ -193,6 +193,18 @@ _Note: The PEX_DESCRIPTOR_OVERRIDE is optional and provides a way to override th Now you can develop and it will hot-reload. +## Logging Configuration + +### Ory Hydra + +Hydra is set to a minimal log output. To expand log output, edit the hydra service in `compose.yaml`: + +```yaml +- LOG_LEVEL=debug +- LOG_FORMAT=json +- LOG_LEAK_SENSITIVE_VALUES=true +``` + ## Policy Configuration The login policy is the one configuration file that configures the bridge's behavior. The most simple example of one looks like this and accepts any credential, while forwarding all subject fields to the `id_token`: diff --git a/compose.yaml b/compose.yaml index 1590968..25ea305 100644 --- a/compose.yaml +++ b/compose.yaml @@ -35,8 +35,9 @@ services: - SERVE_PUBLIC_CORS_ALLOWED_METHODS=POST,GET,PUT,DELETE - SERVE_ADMIN_CORS_ENABLED=true - SERVE_ADMIN_CORS_ALLOWED_METHODS=POST,GET,PUT,DELETE - - LOG_LEVEL=debug - - LOG_LEAK_SENSITIVE_VALUES=true + - LOG_LEVEL=error + - LOG_FORMAT=json + - LOG_LEAK_SENSITIVE_VALUES=false - OAUTH2_EXPOSE_INTERNAL_ERRORS=1 # - TRACING_PROVIDER=jaeger # - TRACING_PROVIDERS_JAEGER_SAMPLING_SERVER_URL=http://jaeger:5778/sampling From aa12eb9b88ff33ae71dfe98627bc94155acd9236 Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Tue, 11 Jun 2024 16:27:42 +0200 Subject: [PATCH 14/85] Logging improvements with pino Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> --- vclogin/.eslintrc.json | 5 +- vclogin/config/logger.ts | 10 ++ vclogin/config/redis.ts | 40 ++++++ vclogin/lib/verifyPresentation.ts | 64 ++++----- vclogin/middleware/logging.ts | 22 ++++ vclogin/package-lock.json | 172 +++++++++++++++++++++++++ vclogin/package.json | 2 + vclogin/pages/api/clientMetadata.ts | 8 +- vclogin/pages/api/consent.ts | 25 ++-- vclogin/pages/api/presentCredential.ts | 52 +++----- vclogin/tsconfig.json | 3 +- 11 files changed, 307 insertions(+), 96 deletions(-) create mode 100644 vclogin/config/logger.ts create mode 100644 vclogin/config/redis.ts create mode 100644 vclogin/middleware/logging.ts diff --git a/vclogin/.eslintrc.json b/vclogin/.eslintrc.json index 76a186d..6a27e5d 100644 --- a/vclogin/.eslintrc.json +++ b/vclogin/.eslintrc.json @@ -1,3 +1,6 @@ { - "extends": ["next/core-web-vitals", "plugin:react-hooks/recommended"] + "extends": ["next/core-web-vitals", "plugin:react-hooks/recommended"], + "rules": { + "no-console": "error" + } } diff --git a/vclogin/config/logger.ts b/vclogin/config/logger.ts new file mode 100644 index 0000000..3ac2b8a --- /dev/null +++ b/vclogin/config/logger.ts @@ -0,0 +1,10 @@ +/** + * Copyright 2024 Software Engineering for Business Information Systems (sebis) . + * SPDX-License-Identifier: MIT + */ + +import pino from "pino"; + +export const logger = pino({ + level: process.env.NODE_ENV === "production" ? "info" : "debug", +}); diff --git a/vclogin/config/redis.ts b/vclogin/config/redis.ts new file mode 100644 index 0000000..a3933b0 --- /dev/null +++ b/vclogin/config/redis.ts @@ -0,0 +1,40 @@ +/** + * Copyright 2024 Software Engineering for Business Information Systems (sebis) . + * SPDX-License-Identifier: MIT + */ + +import { Redis, RedisKey, RedisOptions, RedisValue } from "ioredis"; +import { logger } from "@/config/logger"; + +const redisConfig: RedisOptions = { + port: parseInt(process.env.REDIS_PORT ? process.env.REDIS_PORT : "6379", 10), + host: process.env.REDIS_HOST, + lazyConnect: true, + maxRetriesPerRequest: 10, +}; + +let redis: Redis; + +try { + redis = new Redis(redisConfig); +} catch (error) { + logger.error(error, "Critically failed initializing Redis"); +} + +export const redisGet = async (key: RedisKey): Promise => { + let res = null; + try { + res = await redis.get(key); + } catch (error) { + logger.error(error, "Redis repeatedly failed to get value"); + } + return res; +}; + +export const redisSet = ( + key: RedisKey, + value: RedisValue, + seconds: string | number, +) => { + redis.set(key, value, "EX", seconds); +}; diff --git a/vclogin/lib/verifyPresentation.ts b/vclogin/lib/verifyPresentation.ts index 7b37f75..f66d9a6 100644 --- a/vclogin/lib/verifyPresentation.ts +++ b/vclogin/lib/verifyPresentation.ts @@ -7,11 +7,16 @@ import { verifyCredential, verifyPresentation, } from "@spruceid/didkit-wasm-node"; +import { logger } from "@/config/logger"; export const verifyAuthenticationPresentation = async (VP: any) => { try { if (!VP?.verifiableCredential) { - console.error("Unable to detect verifiable credentials in the VP"); + logger.error("Unable to find VCs in VP"); + return false; + } + + if (!(await verifyJustPresentation(VP))) { return false; } @@ -20,52 +25,37 @@ export const verifyAuthenticationPresentation = async (VP: any) => { : [VP.verifiableCredential]; for (const cred of creds) { - if (!(await verifyPresentationHelper(cred, VP))) { + if (!(await verifyJustCredential(cred))) { return false; } } return true; } catch (error) { - console.error(error); + logger.error(error, "Failed during VP verification"); return false; } }; -const verifyPresentationHelper = async (VC: any, VP: any): Promise => { - if ( - VP.holder && - VP.holder === VC.credentialSubject.id && - VP.proof.verificationMethod.split("#")[0] === VP.holder - ) { - // Verify the signature on the VC - const verifyOptionsString = "{}"; - const verifyResult = JSON.parse( - await verifyCredential(JSON.stringify(VC), verifyOptionsString), - ); - // If credential verification is successful, verify the presentation - if (verifyResult?.errors?.length === 0) { - const res = JSON.parse( - await verifyPresentation(JSON.stringify(VP), verifyOptionsString), - ); - // If verification is successful - if (res.errors.length === 0) { - return true; - } else { - const errorMessage = `Unable to verify presentation: ${res.errors.join( - ", ", - )}`; - console.error(errorMessage); - } - } else { - const errorMessage = `Unable to verify credential: ${verifyResult.errors.join( - ", ", - )}`; - console.error(errorMessage); - } +const verifyJustPresentation = async (VP: any): Promise => { + const res = JSON.parse(await verifyPresentation(JSON.stringify(VP), "{}")); + // If verification is successful + if (res.errors.length === 0) { + return true; } else { - const errorMessage = "The credential subject does not match the VP holder."; - console.error(errorMessage); + logger.error({ errors: res.errors }, "Unable to verify VP"); + return false; + } +}; + +const verifyJustCredential = async (VC: any): Promise => { + // Verify the signature on the VC + const res = JSON.parse(await verifyCredential(JSON.stringify(VC), "{}")); + // If verification is successful + if (res?.errors?.length === 0) { + return true; + } else { + logger.error({ errors: res.errors }, "Unable to verify VC"); + return false; } - return false; }; diff --git a/vclogin/middleware/logging.ts b/vclogin/middleware/logging.ts new file mode 100644 index 0000000..81162e5 --- /dev/null +++ b/vclogin/middleware/logging.ts @@ -0,0 +1,22 @@ +/** + * Copyright 2024 Software Engineering for Business Information Systems (sebis) . + * SPDX-License-Identifier: MIT + */ + +import { logger } from "@/config/logger"; +import { NextApiRequest, NextApiResponse } from "next"; +import { pinoHttp } from "pino-http"; + +const loggerHttp = pinoHttp({ logger: logger }); + +export const withLogging = ( + handler: (a: NextApiRequest, b: NextApiResponse) => Promise, +) => { + return async ( + req: NextApiRequest, + res: NextApiResponse, + ): Promise => { + loggerHttp(req, res); + return handler(req, res); + }; +}; diff --git a/vclogin/package-lock.json b/vclogin/package-lock.json index bde43c6..a81d443 100644 --- a/vclogin/package-lock.json +++ b/vclogin/package-lock.json @@ -21,6 +21,8 @@ "next": "13.3.0", "next-connect": "^1.0.0", "next-qrcode": "^2.5.1", + "pino": "^9.1.0", + "pino-http": "^10.1.0", "react": "18.2.0", "react-dom": "18.2.0", "uuid": "^9.0.0" @@ -2625,6 +2627,17 @@ "optional": true, "peer": true }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/acorn": { "version": "8.8.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", @@ -2922,6 +2935,14 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/autoprefixer": { "version": "10.4.14", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz", @@ -4767,6 +4788,22 @@ "node": ">=0.10.0" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -4859,6 +4896,14 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" }, + "node_modules/fast-redact": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", + "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", + "engines": { + "node": ">=6" + } + }, "node_modules/fastq": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", @@ -8015,6 +8060,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -8220,6 +8273,67 @@ "node": ">=0.10.0" } }, + "node_modules/pino": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.1.0.tgz", + "integrity": "sha512-qUcgfrlyOtjwhNLdbhoL7NR4NkHjzykAPw0V2QLFbvu/zss29h4NkRnibyFzBrNCbzCOY3WZ9hhKSwfOkNggYA==", + "dependencies": { + "atomic-sleep": "^1.0.0", + "fast-redact": "^3.1.1", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^1.2.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^3.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz", + "integrity": "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==", + "dependencies": { + "readable-stream": "^4.0.0", + "split2": "^4.0.0" + } + }, + "node_modules/pino-abstract-transport/node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/pino-http": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/pino-http/-/pino-http-10.1.0.tgz", + "integrity": "sha512-rQgRaVfmZnDcOZXvZUUkiG3wDYVTSyYWAhxkGUgw3py3Y1nFXucRSLYPB5HKgG64oy9gLiDARiQxxWXnI1u3zA==", + "dependencies": { + "get-caller-file": "^2.0.5", + "pino": "^9.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^3.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", + "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==" + }, "node_modules/pirates": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", @@ -8470,11 +8584,24 @@ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, + "node_modules/process-warning": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz", + "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==" + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -8577,6 +8704,11 @@ } ] }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" + }, "node_modules/quick-lru": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", @@ -8651,6 +8783,14 @@ "node": ">=8.10.0" } }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "engines": { + "node": ">= 12.13.0" + } + }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -8895,6 +9035,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-stable-stringify": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", + "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", + "engines": { + "node": ">=10" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -9044,6 +9192,14 @@ "node": ">=8" } }, + "node_modules/sonic-boom": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.0.1.tgz", + "integrity": "sha512-hTSD/6JMLyT4r9zeof6UtuBDpjJ9sO08/nmS5djaA9eozT9oOlNdpXSnzcgj4FTqpk3nkLrs61l4gip9r1HCrQ==", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -9071,6 +9227,14 @@ "source-map": "^0.6.0" } }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -9650,6 +9814,14 @@ "node": ">=0.8" } }, + "node_modules/thread-stream": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.0.2.tgz", + "integrity": "sha512-cBL4xF2A3lSINV4rD5tyqnKH4z/TgWPvT+NaVhJDSwK962oo/Ye7cHSMbDzwcu7tAE1SfU6Q4XtV6Hucmi6Hlw==", + "dependencies": { + "real-require": "^0.2.0" + } + }, "node_modules/tiny-glob": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz", diff --git a/vclogin/package.json b/vclogin/package.json index 2d00987..410683e 100644 --- a/vclogin/package.json +++ b/vclogin/package.json @@ -23,6 +23,8 @@ "next": "13.3.0", "next-connect": "^1.0.0", "next-qrcode": "^2.5.1", + "pino": "^9.1.0", + "pino-http": "^10.1.0", "react": "18.2.0", "react-dom": "18.2.0", "uuid": "^9.0.0" diff --git a/vclogin/pages/api/clientMetadata.ts b/vclogin/pages/api/clientMetadata.ts index 5289cd8..d81dc92 100644 --- a/vclogin/pages/api/clientMetadata.ts +++ b/vclogin/pages/api/clientMetadata.ts @@ -4,15 +4,12 @@ */ import type { NextApiRequest, NextApiResponse } from "next"; +import { withLogging } from "@/middleware/logging"; -export default async function handler( - req: NextApiRequest, - res: NextApiResponse, -) { +async function handler(req: NextApiRequest, res: NextApiResponse) { try { const { method } = req; if (method === "GET") { - console.log("METADATA API GET"); const metadata = { scopes_supported: ["openid"], response_types_supported: ["id_token", "vp_token"], @@ -68,4 +65,5 @@ export default async function handler( } } +export default withLogging(handler); export const config = { api: { bodyParser: false } }; diff --git a/vclogin/pages/api/consent.ts b/vclogin/pages/api/consent.ts index 3fa83da..04e702d 100644 --- a/vclogin/pages/api/consent.ts +++ b/vclogin/pages/api/consent.ts @@ -5,31 +5,21 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { hydraAdmin } from "@/config/ory"; -import { Redis } from "ioredis"; +import { redisGet } from "@/config/redis"; +import { withLogging } from "@/middleware/logging"; -var redis: Redis; -try { - redis = new Redis(parseInt(process.env.REDIS_PORT!), process.env.REDIS_HOST!); -} catch (error) { - console.error("Failed to connect to Redis:", error); -} - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse, -) { +async function handler(req: NextApiRequest, res: NextApiResponse) { try { const { method } = req; if (method === "GET") { - console.log("CONSENT API GET"); - const challenge = req.query["consent_challenge"] as string; - const { data: body } = - await hydraAdmin.adminGetOAuth2ConsentRequest(challenge); + const { data: body } = await hydraAdmin.adminGetOAuth2ConsentRequest( + challenge, + ); // get user identity and fetch user claims from redis - const userClaims = JSON.parse((await redis.get("" + body.subject))!); + const userClaims = JSON.parse((await redisGet("" + body.subject))!); hydraAdmin .adminAcceptOAuth2ConsentRequest(challenge, { @@ -64,4 +54,5 @@ export default async function handler( } } +export default withLogging(handler); export const config = { api: { bodyParser: false } }; diff --git a/vclogin/pages/api/presentCredential.ts b/vclogin/pages/api/presentCredential.ts index 2c34bd1..119afdf 100644 --- a/vclogin/pages/api/presentCredential.ts +++ b/vclogin/pages/api/presentCredential.ts @@ -6,33 +6,23 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { verifyAuthenticationPresentation } from "@/lib/verifyPresentation"; import { hydraAdmin } from "@/config/ory"; -import { Redis } from "ioredis"; import { isTrustedPresentation, extractClaims } from "@/lib/extractClaims"; import * as jose from "jose"; import { keyToDID, keyToVerificationMethod } from "@spruceid/didkit-wasm-node"; import { generatePresentationDefinition } from "@/lib/generatePresentationDefinition"; import { getConfiguredLoginPolicy } from "@/config/loginPolicy"; +import { withLogging } from "@/middleware/logging"; +import { logger } from "@/config/logger"; +import { redisSet, redisGet } from "@/config/redis"; -var redis: Redis; -try { - redis = new Redis(parseInt(process.env.REDIS_PORT!), process.env.REDIS_HOST!); -} catch (error) { - console.error("Failed to connect to Redis:", error); -} - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse, -) { +async function handler(req: NextApiRequest, res: NextApiResponse) { try { const { method } = req; if (method === "GET") { - console.log("LOGIN API GET"); - console.log(req.query); const presentation_definition = generatePresentationDefinition( getConfiguredLoginPolicy()!, ); - const did = await keyToDID("key", process.env.DID_KEY_JWK!); + const did = keyToDID("key", process.env.DID_KEY_JWK!); const verificationMethod = await keyToVerificationMethod( "key", process.env.DID_KEY_JWK!, @@ -65,33 +55,30 @@ export default async function handler( .setExpirationTime("1 hour") .sign(privateKey) .catch((err) => { - console.log(err); + logger.error(err, "Failed signing presentation definition token"); res.status(500).end(); }); - console.log("TOKEN: " + token); res .status(200) .appendHeader("Content-Type", "application/oauth-authz-req+jwt") .send(token); } else if (method === "POST") { - console.log("LOGIN API POST"); - // Parse the JSON string into a JavaScript object const presentation = JSON.parse(req.body.vp_token); - console.log("Presentation: \n", req.body.vp_token); + logger.debug(req.body.vp_token, "Verifiable Presentaiton was sent"); // Verify the presentation and the status of the credential if (await verifyAuthenticationPresentation(presentation)) { // Evaluate if the VP should be trusted if (isTrustedPresentation(presentation)) { - console.log("Presentation verified"); + logger.debug("Verifiable Presentation verified"); } else { - console.log("Presentation not trusted"); + logger.debug("Verifiable Presentation not trusted"); res.status(500).end(); return; } } else { - console.log("Presentation invalid"); + logger.debug("Verifiable Presentation not valid"); res.status(500).end(); return; } @@ -100,13 +87,13 @@ export default async function handler( const userClaims = extractClaims(presentation); const subject = presentation["holder"]; const login_id = presentation["proof"]["challenge"]; - const challenge = (await redis.get("" + login_id))!; - console.log("Logging in: " + subject + " with challenge: " + challenge); + const challenge = (await redisGet("" + login_id))!; + logger.debug({ subject, challenge }, "Sign-in confirmed"); // hydra login await hydraAdmin .adminGetOAuth2LoginRequest(challenge) - .then(({ data: loginRequest }) => + .then(({}) => hydraAdmin .adminAcceptOAuth2LoginRequest(challenge, { // Subject is an alias for user ID. A subject can be a random string, a UUID, an email address, .... @@ -130,23 +117,17 @@ export default async function handler( }) .then(({ data: body }) => { const MAX_AGE = 30; // 30 seconds - const EXPIRY_MS = "EX"; // seconds // save the user claims to redis - redis.set( - "" + subject, - JSON.stringify(userClaims), - EXPIRY_MS, - MAX_AGE, - ); + redisSet("" + subject, JSON.stringify(userClaims), MAX_AGE); // save the redirect address to redis for the browser - redis.set( + redisSet( "redirect" + login_id, String(body.redirect_to), - EXPIRY_MS, MAX_AGE, ); + // phone just gets a 200 ok res.status(200).end(); }), @@ -161,4 +142,5 @@ export default async function handler( } } +export default withLogging(handler); export const config = { api: { bodyParser: true } }; diff --git a/vclogin/tsconfig.json b/vclogin/tsconfig.json index c9076cb..c8a8bfc 100644 --- a/vclogin/tsconfig.json +++ b/vclogin/tsconfig.json @@ -21,7 +21,8 @@ "@/config/*": ["config/*"], "@/types/*": ["types/*"], "@/pages/*": ["pages/*"], - "@/styles/*": ["styles/*"] + "@/styles/*": ["styles/*"], + "@/middleware/*": ["middleware/*"] }, "typeRoots": ["./types"] }, From dd5eea37179142defea9199fcaa7228f0675d9a6 Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Tue, 11 Jun 2024 16:41:26 +0200 Subject: [PATCH 15/85] Text fixes Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> --- README.md | 4 ++++ vclogin/pages/index.tsx | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7f509a3..2ee06a7 100644 --- a/README.md +++ b/README.md @@ -195,6 +195,10 @@ Now you can develop and it will hot-reload. ## Logging Configuration +### vclogin + +The vclogin server uses the `pino` library for logging. Due to the peculiarities of NextJS, http events are only logged for API routes. + ### Ory Hydra Hydra is set to a minimal log output. To expand log output, edit the hydra service in `compose.yaml`: diff --git a/vclogin/pages/index.tsx b/vclogin/pages/index.tsx index 57f97be..547f6c2 100644 --- a/vclogin/pages/index.tsx +++ b/vclogin/pages/index.tsx @@ -26,11 +26,11 @@ export default function Home() { id="gx-text" className="text-6xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-blue-500 to-purple-600 transition duration-500 ease-in-out transform -translate-y-full" > - GX Credentials Bridge + SSI-to-OIDC Bridge
- This page supports VC-based logins. + This application supports VC-based logins for other services.
From a8b1d0263c8be7a40d4f777f8ed81b22c3c60de2 Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Tue, 11 Jun 2024 16:41:26 +0200 Subject: [PATCH 16/85] Text fixes Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> --- README.md | 4 ++++ vclogin/pages/api/consent.ts | 5 ++--- vclogin/pages/index.tsx | 4 ++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 7f509a3..2ee06a7 100644 --- a/README.md +++ b/README.md @@ -195,6 +195,10 @@ Now you can develop and it will hot-reload. ## Logging Configuration +### vclogin + +The vclogin server uses the `pino` library for logging. Due to the peculiarities of NextJS, http events are only logged for API routes. + ### Ory Hydra Hydra is set to a minimal log output. To expand log output, edit the hydra service in `compose.yaml`: diff --git a/vclogin/pages/api/consent.ts b/vclogin/pages/api/consent.ts index 04e702d..400cba6 100644 --- a/vclogin/pages/api/consent.ts +++ b/vclogin/pages/api/consent.ts @@ -14,9 +14,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { if (method === "GET") { const challenge = req.query["consent_challenge"] as string; - const { data: body } = await hydraAdmin.adminGetOAuth2ConsentRequest( - challenge, - ); + const { data: body } = + await hydraAdmin.adminGetOAuth2ConsentRequest(challenge); // get user identity and fetch user claims from redis const userClaims = JSON.parse((await redisGet("" + body.subject))!); diff --git a/vclogin/pages/index.tsx b/vclogin/pages/index.tsx index 57f97be..547f6c2 100644 --- a/vclogin/pages/index.tsx +++ b/vclogin/pages/index.tsx @@ -26,11 +26,11 @@ export default function Home() { id="gx-text" className="text-6xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-blue-500 to-purple-600 transition duration-500 ease-in-out transform -translate-y-full" > - GX Credentials Bridge + SSI-to-OIDC Bridge
- This page supports VC-based logins. + This application supports VC-based logins for other services.
From 1acabb303a7f06f5a2929dfb3193b4703ac81d0b Mon Sep 17 00:00:00 2001 From: Ilayda Cansin Koc Date: Thu, 18 Apr 2024 14:42:07 +0000 Subject: [PATCH 17/85] add initial dynamic enpoint logic Signed-off-by: Ilayda Cansin Koc --- test_client.sh | 9 +- .../pages/api/dynamic/clientMetadataById.ts | 71 ++++++++++ .../api/dynamic/createTempAuthorization.ts | 26 ++++ vclogin/pages/api/dynamic/getQRCodeString.ts | 28 ++++ .../api/dynamic/presentCredentialById.ts | 122 ++++++++++++++++++ vclogin/pages/login.tsx | 33 ++--- 6 files changed, 262 insertions(+), 27 deletions(-) create mode 100644 vclogin/pages/api/dynamic/clientMetadataById.ts create mode 100644 vclogin/pages/api/dynamic/createTempAuthorization.ts create mode 100644 vclogin/pages/api/dynamic/getQRCodeString.ts create mode 100644 vclogin/pages/api/dynamic/presentCredentialById.ts diff --git a/test_client.sh b/test_client.sh index 3c472a6..41acd50 100755 --- a/test_client.sh +++ b/test_client.sh @@ -8,8 +8,9 @@ client=$(docker run --rm -it \ --grant-type authorization_code \ --response-type token,code,id_token \ --scope openid \ - --redirect-uri http://localhost:9010/callback \ + --redirect-uri http://localhost:3000/welcome \ -e http://hydra:4445 \ + --token-endpoint-auth-method client_secret_post \ --format json ) echo $client @@ -24,8 +25,8 @@ docker run --rm -it \ --port 9010 \ --client-id $client_id \ --client-secret some-secret \ - --redirect http://localhost:9010/callback \ + --redirect http://localhost:3000/welcome \ --scope openid \ --auth-url http://localhost:5004/oauth2/auth \ - --token-url http://hydra:4444/oauth2/token \ - -e http://hydra:4444 + --token-url http://localhost:5004/oauth2/token \ + -e http://hydra:4444 \ No newline at end of file diff --git a/vclogin/pages/api/dynamic/clientMetadataById.ts b/vclogin/pages/api/dynamic/clientMetadataById.ts new file mode 100644 index 0000000..c8f3684 --- /dev/null +++ b/vclogin/pages/api/dynamic/clientMetadataById.ts @@ -0,0 +1,71 @@ +/** + * Copyright 2024 Software Engineering for Business Information Systems (sebis) . + * SPDX-License-Identifier: MIT + */ + +import type { NextApiRequest, NextApiResponse } from "next"; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse, +) { + try { + const { method } = req; + if (method === "GET") { + console.log("METADATA API GET"); + const metadata = { + scopes_supported: ["openid"], + response_types_supported: ["id_token", "vp_token"], + response_modes_supported: ["query"], + subject_types_supported: ["public"], + id_token_signing_alg_values_supported: [ + "ES256", + "ES256k", + "EdDSA", + "RS256", + ], + request_object_signing_alg_values_supported: [ + "ES256", + "ES256K", + "EdDSA", + "RS256", + ], + vp_formats: { + jwt_vp: { + alg_values_supported: ["ES256", "ES256K", "EdDSA", "RS256"], + }, + jwt_vc: { + alg_values_supported: ["ES256", "ES256K", "EdDSA", "RS256"], + }, + }, + subject_syntax_types_supported: [ + "did:key", + "did:ebsi", + "did:tz", + "did:pkh", + "did:key", + "did:ethr", + ], + subject_syntax_types_discriminations: [ + "did:key:jwk_jcs-pub", + "did:ebsi:v1", + ], + subject_trust_frameworks_supported: ["ebsi"], + id_token_types_supported: ["subject_signed_id_token"], + client_name: "VP Login Service", + request_uri_parameter_supported: true, + request_parameter_supported: false, + redirect_uris: [ + process.env.NEXT_PUBLIC_INTERNET_URL + "/api/presentCredentialById", + ], + }; + res.status(200).json(metadata); + } else { + res.status(500).end(); + } + } catch (e) { + res.status(500).end(); + } +} + +export const config = { api: { bodyParser: false } }; diff --git a/vclogin/pages/api/dynamic/createTempAuthorization.ts b/vclogin/pages/api/dynamic/createTempAuthorization.ts new file mode 100644 index 0000000..694b8a6 --- /dev/null +++ b/vclogin/pages/api/dynamic/createTempAuthorization.ts @@ -0,0 +1,26 @@ +import { NextApiRequest, NextApiResponse } from "next"; +import { Redis } from "ioredis"; + +var redis: Redis; +try { + redis = new Redis(parseInt(process.env.REDIS_PORT!), process.env.REDIS_HOST!); +} catch (error) { + console.error("Failed to connect to Redis:", error); +} + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse, +) { + //Get Policy from request body + const policy = req.body; + + //Store Policy in Redis with key UUID and value as Policy with an expiry of 5 minutes + const uuid = crypto.randomUUID(); + try { + await redis.set(uuid, JSON.stringify(policy), "EX", 300); + return res.status(200).json({ uuid }); + } catch (error) { + return res.status(500).json({ error }); + } +} diff --git a/vclogin/pages/api/dynamic/getQRCodeString.ts b/vclogin/pages/api/dynamic/getQRCodeString.ts new file mode 100644 index 0000000..ce57dd6 --- /dev/null +++ b/vclogin/pages/api/dynamic/getQRCodeString.ts @@ -0,0 +1,28 @@ +import { NextApiRequest, NextApiResponse } from "next"; +import { Redis } from "ioredis"; + +var redis: Redis; +try { + redis = new Redis(parseInt(process.env.REDIS_PORT!), process.env.REDIS_HOST!); +} catch (error) { + console.error("Failed to connect to Redis:", error); +} + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse, +) { + //Get Policy from request body + const { uuid, userId } = req.body; + + //Generate QR Code String from UUID + const qrCodeString = + "openid-vc://?client_id=" + + userId + + "&request_uri=" + + encodeURIComponent( + process.env.EXTERNAL_URL + "/api/presentCredentialById?login_id=" + uuid, + ); + + return res.status(200).json({ qrCodeString }); +} diff --git a/vclogin/pages/api/dynamic/presentCredentialById.ts b/vclogin/pages/api/dynamic/presentCredentialById.ts new file mode 100644 index 0000000..8cbb8f4 --- /dev/null +++ b/vclogin/pages/api/dynamic/presentCredentialById.ts @@ -0,0 +1,122 @@ +import { NextApiRequest, NextApiResponse } from "next"; +import { Redis } from "ioredis"; +import { generatePresentationDefinition } from "@/lib/generatePresentationDefinition"; +import { LoginPolicy } from "@/types/LoginPolicy"; +import { keyToDID, keyToVerificationMethod } from "@spruceid/didkit-wasm-node"; +import * as jose from "jose"; +import { hydraAdmin } from "@/config/ory"; +import { extractClaims, isTrustedPresentation } from "@/lib/extractClaims"; +import { verifyAuthenticationPresentation } from "@/lib/verifyPresentation"; + +var redis: Redis; +try { + redis = new Redis(parseInt(process.env.REDIS_PORT!), process.env.REDIS_HOST!); +} catch (error) { + console.error("Failed to connect to Redis:", error); +} + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse, +) { + try { + const { method } = req; + + if (method === "GET") { + console.log("LOGIN API GET BY ID"); + console.log(req.query); + // Get login_id from query + const uuid = req.query["login_id"]; + + // fetch policy from redis using uuid + const policy = await redis.get("" + uuid); + + // generate presentation definition using policy + const presentation_definition = generatePresentationDefinition( + policy as unknown as LoginPolicy, + ); + + const did = await keyToDID("key", process.env.DID_KEY_JWK!); + const verificationMethod = await keyToVerificationMethod( + "key", + process.env.DID_KEY_JWK!, + ); + const challenge = req.query["login_id"]; + const payload = { + client_id: did, + client_id_scheme: "did", + client_metadata_uri: + process.env.EXTERNAL_URL + "/api/clientMetadataById", + nonce: challenge, + presentation_definition, + response_mode: "direct_post", + response_type: "vp_token", + response_uri: + process.env.EXTERNAL_URL + "/api/dynamic/presentCredentialById", + state: challenge, + }; + const privateKey = await jose.importJWK( + JSON.parse(process.env.DID_KEY_JWK!), + "EdDSA", + ); + const token = await new jose.SignJWT(payload) + .setProtectedHeader({ + alg: "EdDSA", + kid: verificationMethod, + typ: "JWT", + }) + .setIssuedAt() + .setIssuer(did) + .setAudience("https://self-issued.me/v2") // by definition + .setExpirationTime("1 hour") + .sign(privateKey) + .catch((err) => { + console.log(err); + res.status(500).end(); + }); + console.log("TOKEN: " + token); + res + .status(200) + .appendHeader("Content-Type", "application/oauth-authz-req+jwt") + .send(token); + console.log("presentation_definition: " + presentation_definition); + res.send({ status: "success" }); + } else if (method === "POST") { + console.log("LOGIN API POST BY ID"); + + // Parse the JSON string into a JavaScript object + const presentation = JSON.parse(req.body.vp_token); + console.log("Presentation: \n", req.body.vp_token); + + // Verify the presentation and the status of the credential + if (await verifyAuthenticationPresentation(presentation)) { + // Evaluate if the VP should be trusted + if (isTrustedPresentation(presentation)) { + console.log("Presentation verified"); + } else { + console.log("Presentation not trusted"); + res.status(500).end(); + return; + } + } else { + console.log("Presentation invalid"); + res.status(500).end(); + return; + } + + // Get the user claims + const userClaims = extractClaims(presentation); + const subject = presentation["holder"]; + const uuid = presentation["proof"]["challenge"]; + console.log(userClaims); + + res.status(200).end(); + // This will handle any error that happens when making HTTP calls to hydra + } else { + res.status(500).end(); + } + } catch (error) { + res.status(500).end(); + } + const { method } = req; +} diff --git a/vclogin/pages/login.tsx b/vclogin/pages/login.tsx index 7c35814..6bbdb35 100644 --- a/vclogin/pages/login.tsx +++ b/vclogin/pages/login.tsx @@ -37,31 +37,17 @@ export default function Login(props: any) { }); return ( -
-
-

This is experimental software. Use with caution!

- -
- +
-

- SSI-to-OIDC Bridge -

+ Sign-in with SSI-to-OIDC Bridge +
-

Scan the code to sign in!

+
-
-
-   +
+ Scan QR Code with SSI Wallet +
); @@ -124,6 +110,7 @@ export async function getServerSideProps(context: NextPageContext) { } const did = await keyToDID("key", process.env.DID_KEY_JWK!); + console.log("DID: " + did); return { props: { loginId, externalUrl: process.env.EXTERNAL_URL, clientId: did }, From db3d3028dbe0bf82841615f9d49d3d6f311d9647 Mon Sep 17 00:00:00 2001 From: Ilayda Cansin Koc Date: Thu, 18 Apr 2024 14:45:28 +0000 Subject: [PATCH 18/85] fix: move files to dynamic folder Signed-off-by: Ilayda Cansin Koc --- vclogin/pages/api/{ => dynamic}/getAuthResponse.ts | 0 vclogin/pages/api/dynamic/presentCredentialById.ts | 4 ++++ 2 files changed, 4 insertions(+) rename vclogin/pages/api/{ => dynamic}/getAuthResponse.ts (100%) diff --git a/vclogin/pages/api/getAuthResponse.ts b/vclogin/pages/api/dynamic/getAuthResponse.ts similarity index 100% rename from vclogin/pages/api/getAuthResponse.ts rename to vclogin/pages/api/dynamic/getAuthResponse.ts diff --git a/vclogin/pages/api/dynamic/presentCredentialById.ts b/vclogin/pages/api/dynamic/presentCredentialById.ts index 8cbb8f4..61bd69f 100644 --- a/vclogin/pages/api/dynamic/presentCredentialById.ts +++ b/vclogin/pages/api/dynamic/presentCredentialById.ts @@ -110,6 +110,10 @@ export default async function handler( const uuid = presentation["proof"]["challenge"]; console.log(userClaims); + const MAX_AGE = 20 * 60; + const EXPIRY_MS = "EX"; + + await redis.set("auth_res", uuid, EXPIRY_MS, MAX_AGE); res.status(200).end(); // This will handle any error that happens when making HTTP calls to hydra } else { From ad208dc350100c4d93d5747693a49039e690a7ca Mon Sep 17 00:00:00 2001 From: Ilayda Cansin Koc Date: Sat, 27 Apr 2024 22:22:35 +0000 Subject: [PATCH 19/85] enable parametric policy from sp Signed-off-by: Ilayda Cansin Koc --- test_client.sh | 7 +- .../policies/acceptVerifiableIdFromAltme.json | 24 +++ vclogin/config/loginPolicy.ts | 2 +- vclogin/lib/extractClaims.ts | 8 +- vclogin/lib/generatePresentationDefinition.ts | 3 +- .../pages/api/dynamic/clientMetadataById.ts | 3 +- .../api/dynamic/createTempAuthorization.ts | 30 ++- vclogin/pages/api/dynamic/getAuthResponse.ts | 28 +-- vclogin/pages/api/dynamic/getQRCodeString.ts | 7 +- .../api/dynamic/presentCredentialById.ts | 189 +++++++++++------- 10 files changed, 195 insertions(+), 106 deletions(-) create mode 100644 vclogin/__tests__/testdata/policies/acceptVerifiableIdFromAltme.json diff --git a/test_client.sh b/test_client.sh index 41acd50..98e6a73 100755 --- a/test_client.sh +++ b/test_client.sh @@ -8,7 +8,7 @@ client=$(docker run --rm -it \ --grant-type authorization_code \ --response-type token,code,id_token \ --scope openid \ - --redirect-uri http://localhost:3000/welcome \ + --redirect-uri http://localhost:3001 \ -e http://hydra:4445 \ --token-endpoint-auth-method client_secret_post \ --format json ) @@ -25,8 +25,9 @@ docker run --rm -it \ --port 9010 \ --client-id $client_id \ --client-secret some-secret \ - --redirect http://localhost:3000/welcome \ + --redirect http://localhost:3001 \ --scope openid \ --auth-url http://localhost:5004/oauth2/auth \ --token-url http://localhost:5004/oauth2/token \ - -e http://hydra:4444 \ No newline at end of file + -e http://hydra:4444 + diff --git a/vclogin/__tests__/testdata/policies/acceptVerifiableIdFromAltme.json b/vclogin/__tests__/testdata/policies/acceptVerifiableIdFromAltme.json new file mode 100644 index 0000000..0f1d024 --- /dev/null +++ b/vclogin/__tests__/testdata/policies/acceptVerifiableIdFromAltme.json @@ -0,0 +1,24 @@ +[ + { + "credentialId": "1", + "patterns": [ + { + "issuer": "did:web:app.altme.io:issuer", + "claims": [ + { + "claimPath": "$.credentialSubject.dateOfBirth", + "token": "id_token" + }, + { + "claimPath": "$.credentialSubject.firstName", + "token": "id_token" + }, + { + "claimPath": "$.credentialSubject.familyName", + "token": "id_token" + } + ] + } + ] + } +] diff --git a/vclogin/config/loginPolicy.ts b/vclogin/config/loginPolicy.ts index ba57168..30e0b69 100644 --- a/vclogin/config/loginPolicy.ts +++ b/vclogin/config/loginPolicy.ts @@ -7,7 +7,7 @@ import { promises as fs } from "fs"; import { LoginPolicy } from "@/types/LoginPolicy"; var configuredPolicy: LoginPolicy | undefined = undefined; -fs.readFile(process.env.LOGIN_POLICY as string, "utf8").then((file) => { +fs?.readFile(process.env.LOGIN_POLICY as string, "utf8").then((file) => { configuredPolicy = JSON.parse(file); }); diff --git a/vclogin/lib/extractClaims.ts b/vclogin/lib/extractClaims.ts index d07b497..93bdc1c 100644 --- a/vclogin/lib/extractClaims.ts +++ b/vclogin/lib/extractClaims.ts @@ -31,15 +31,18 @@ export const extractClaims = (VP: any, policy?: LoginPolicy) => { var usedPolicy = policy ? policy : configuredPolicy!; + console.log("Used Policy", usedPolicy); const creds = Array.isArray(VP.verifiableCredential) ? VP.verifiableCredential : [VP.verifiableCredential]; - + console.log("Credentials", creds); const vcClaims = creds.map((vc: any) => extractClaimsFromVC(vc, usedPolicy)); + console.log("Extracted VC Claims", vcClaims); const claims = vcClaims.reduce( (acc: any, vc: any) => Object.assign(acc, vc), {}, ); + console.log("Extracted Claims", claims); return claims; }; @@ -256,6 +259,7 @@ const resolveValue = ( const extractClaimsFromVC = (VC: any, policy: LoginPolicy) => { for (let expectation of policy) { + console.log("Expectation", expectation); for (let pattern of expectation.patterns) { if (pattern.issuer === VC.issuer || pattern.issuer === "*") { const containsAllRequired = @@ -309,7 +313,7 @@ const extractClaimsFromVC = (VC: any, policy: LoginPolicy) => { : extractedClaims.tokenAccess; jp.value(claimTarget, newPath, value); } - + console.log("Extracted Claims", extractedClaims); return extractedClaims; } } diff --git a/vclogin/lib/generatePresentationDefinition.ts b/vclogin/lib/generatePresentationDefinition.ts index 0f39789..7b36166 100644 --- a/vclogin/lib/generatePresentationDefinition.ts +++ b/vclogin/lib/generatePresentationDefinition.ts @@ -8,7 +8,7 @@ import { promises as fs } from "fs"; var inputDescriptorOverride: any = undefined; if (process.env.PEX_DESCRIPTOR_OVERRIDE) { - fs.readFile(process.env.PEX_DESCRIPTOR_OVERRIDE as string, "utf8").then( + fs?.readFile(process.env.PEX_DESCRIPTOR_OVERRIDE as string, "utf8").then( (file) => { inputDescriptorOverride = JSON.parse(file); }, @@ -81,6 +81,7 @@ export const generatePresentationDefinition = (policy: LoginPolicy) => { if (fields.length > 0) { descr.constraints.fields = fields; } + console.log(descr); pd.input_descriptors.push(descr); } } diff --git a/vclogin/pages/api/dynamic/clientMetadataById.ts b/vclogin/pages/api/dynamic/clientMetadataById.ts index c8f3684..d24a662 100644 --- a/vclogin/pages/api/dynamic/clientMetadataById.ts +++ b/vclogin/pages/api/dynamic/clientMetadataById.ts @@ -56,7 +56,8 @@ export default async function handler( request_uri_parameter_supported: true, request_parameter_supported: false, redirect_uris: [ - process.env.NEXT_PUBLIC_INTERNET_URL + "/api/presentCredentialById", + process.env.NEXT_PUBLIC_INTERNET_URL + + "/api/dynamic/presentCredentialById", ], }; res.status(200).json(metadata); diff --git a/vclogin/pages/api/dynamic/createTempAuthorization.ts b/vclogin/pages/api/dynamic/createTempAuthorization.ts index 694b8a6..f86612b 100644 --- a/vclogin/pages/api/dynamic/createTempAuthorization.ts +++ b/vclogin/pages/api/dynamic/createTempAuthorization.ts @@ -1,5 +1,6 @@ import { NextApiRequest, NextApiResponse } from "next"; import { Redis } from "ioredis"; +import crypto from "crypto"; var redis: Redis; try { @@ -15,12 +16,33 @@ export default async function handler( //Get Policy from request body const policy = req.body; - //Store Policy in Redis with key UUID and value as Policy with an expiry of 5 minutes - const uuid = crypto.randomUUID(); try { - await redis.set(uuid, JSON.stringify(policy), "EX", 300); + // store policy in redis with uuid as key + const uuid = crypto.randomUUID(); + await redis.set(uuid, policy, "EX", 300); return res.status(200).json({ uuid }); } catch (error) { - return res.status(500).json({ error }); + return res.status(500).json({ redirect: "/error" }); } } + +/* + //read credential id from policy + const hash = crypto.createHash("sha256").update(policy).digest("hex"); + + try { + //check if policy already exists + const existingPolicy = await redis.get(hash); + if (existingPolicy) { + return res.status(200).json({ uuid: hash }); + } else { + try { + const hash = crypto.createHash("sha256").update(policy).digest("hex"); + + await redis.set(hash, JSON.stringify(policy), "EX", 300); + + return res.status(200).json({ uuid: hash }); + } catch (error) { + return res.status(500).json({ error }); + } + } */ diff --git a/vclogin/pages/api/dynamic/getAuthResponse.ts b/vclogin/pages/api/dynamic/getAuthResponse.ts index 36d1040..06afade 100644 --- a/vclogin/pages/api/dynamic/getAuthResponse.ts +++ b/vclogin/pages/api/dynamic/getAuthResponse.ts @@ -12,21 +12,21 @@ export default async function handler( req: NextApiRequest, res: NextApiResponse, ) { - //Get Policy from request body - const { uuid } = req.body; - console.log("GET AUTH RESPONSE API"); + //read uuid from query params + const uuid = req.query["uuid"]; + console.log("uuid: ", uuid); // Read auth_res from redis and check if it matches the uuid - const auth_res = await redis.get("auth_res"); - console.log("auth_res: ", auth_res, uuid); - try { - if (auth_res === uuid) { - console.log("auth_res matches uuid"); - return res.status(200).json({ res: "success" }); - } else { - return res.status(200).json({ res: "no_match" }); - } - } catch { - return res.status(200).json({ res: "error" }); + + //auth_res kept in redis like auth_res:uuid, read auth_res using uuid + const auth_res = await redis.get("auth_res:" + uuid); + + if (auth_res) { + //if auth_res found, return it along claims + const claims = await redis.get("claims"); + res.status(200).json({ auth_res, claims }); + } else { + //if auth_res not found, return error + res.status(200).json({ auth_res: "error_not_found" }); } } diff --git a/vclogin/pages/api/dynamic/getQRCodeString.ts b/vclogin/pages/api/dynamic/getQRCodeString.ts index ce57dd6..98d99b9 100644 --- a/vclogin/pages/api/dynamic/getQRCodeString.ts +++ b/vclogin/pages/api/dynamic/getQRCodeString.ts @@ -12,8 +12,7 @@ export default async function handler( req: NextApiRequest, res: NextApiResponse, ) { - //Get Policy from request body - const { uuid, userId } = req.body; + const { userId, uuid } = JSON.parse(req.body); //Generate QR Code String from UUID const qrCodeString = @@ -21,7 +20,9 @@ export default async function handler( userId + "&request_uri=" + encodeURIComponent( - process.env.EXTERNAL_URL + "/api/presentCredentialById?login_id=" + uuid, + process.env.EXTERNAL_URL + + "/api/dynamic/presentCredentialById?login_id=" + + uuid, ); return res.status(200).json({ qrCodeString }); diff --git a/vclogin/pages/api/dynamic/presentCredentialById.ts b/vclogin/pages/api/dynamic/presentCredentialById.ts index 61bd69f..d54c244 100644 --- a/vclogin/pages/api/dynamic/presentCredentialById.ts +++ b/vclogin/pages/api/dynamic/presentCredentialById.ts @@ -1,12 +1,13 @@ import { NextApiRequest, NextApiResponse } from "next"; import { Redis } from "ioredis"; import { generatePresentationDefinition } from "@/lib/generatePresentationDefinition"; -import { LoginPolicy } from "@/types/LoginPolicy"; +import { ExpectedCredential, LoginPolicy } from "@/types/LoginPolicy"; import { keyToDID, keyToVerificationMethod } from "@spruceid/didkit-wasm-node"; import * as jose from "jose"; import { hydraAdmin } from "@/config/ory"; import { extractClaims, isTrustedPresentation } from "@/lib/extractClaims"; import { verifyAuthenticationPresentation } from "@/lib/verifyPresentation"; +import { promises as fs } from "fs"; var redis: Redis; try { @@ -31,56 +32,62 @@ export default async function handler( // fetch policy from redis using uuid const policy = await redis.get("" + uuid); - // generate presentation definition using policy - const presentation_definition = generatePresentationDefinition( - policy as unknown as LoginPolicy, - ); - - const did = await keyToDID("key", process.env.DID_KEY_JWK!); - const verificationMethod = await keyToVerificationMethod( - "key", - process.env.DID_KEY_JWK!, - ); - const challenge = req.query["login_id"]; - const payload = { - client_id: did, - client_id_scheme: "did", - client_metadata_uri: - process.env.EXTERNAL_URL + "/api/clientMetadataById", - nonce: challenge, - presentation_definition, - response_mode: "direct_post", - response_type: "vp_token", - response_uri: - process.env.EXTERNAL_URL + "/api/dynamic/presentCredentialById", - state: challenge, - }; - const privateKey = await jose.importJWK( - JSON.parse(process.env.DID_KEY_JWK!), - "EdDSA", - ); - const token = await new jose.SignJWT(payload) - .setProtectedHeader({ - alg: "EdDSA", - kid: verificationMethod, - typ: "JWT", - }) - .setIssuedAt() - .setIssuer(did) - .setAudience("https://self-issued.me/v2") // by definition - .setExpirationTime("1 hour") - .sign(privateKey) - .catch((err) => { - console.log(err); - res.status(500).end(); - }); - console.log("TOKEN: " + token); - res - .status(200) - .appendHeader("Content-Type", "application/oauth-authz-req+jwt") - .send(token); - console.log("presentation_definition: " + presentation_definition); - res.send({ status: "success" }); + //if policy is found + if (policy) { + const policyObject = JSON.parse(policy) as LoginPolicy; + + // generate presentation definition using policy + const presentation_definition = + generatePresentationDefinition(policyObject); + + const did = await keyToDID("key", process.env.DID_KEY_JWK!); + const verificationMethod = await keyToVerificationMethod( + "key", + process.env.DID_KEY_JWK!, + ); + const challenge = req.query["login_id"]; + const payload = { + client_id: did, + client_id_scheme: "did", + client_metadata_uri: + process.env.EXTERNAL_URL + "/api/dynamic/clientMetadataById", + nonce: challenge, + presentation_definition, + response_mode: "direct_post", + response_type: "vp_token", + response_uri: + process.env.EXTERNAL_URL + "/api/dynamic/presentCredentialById", + state: challenge, + }; + const privateKey = await jose.importJWK( + JSON.parse(process.env.DID_KEY_JWK!), + "EdDSA", + ); + const token = await new jose.SignJWT(payload) + .setProtectedHeader({ + alg: "EdDSA", + kid: verificationMethod, + typ: "JWT", + }) + .setIssuedAt() + .setIssuer(did) + .setAudience("https://self-issued.me/v2") // by definition + .setExpirationTime("1 hour") + .sign(privateKey) + .catch((err) => { + console.log(err); + res.status(500).end(); + }); + console.log("TOKEN: " + token); + res + .status(200) + .appendHeader("Content-Type", "application/oauth-authz-req+jwt") + .send(token); + console.log("presentation_definition: " + presentation_definition); + res.send({ status: "success" }); + } else { + res.status(500).end({ error: "No policy found" }); + } } else if (method === "POST") { console.log("LOGIN API POST BY ID"); @@ -88,39 +95,67 @@ export default async function handler( const presentation = JSON.parse(req.body.vp_token); console.log("Presentation: \n", req.body.vp_token); - // Verify the presentation and the status of the credential - if (await verifyAuthenticationPresentation(presentation)) { - // Evaluate if the VP should be trusted - if (isTrustedPresentation(presentation)) { - console.log("Presentation verified"); + const subject = presentation["holder"]; + const uuid = presentation["proof"]["challenge"]; + const policy = await redis.get("" + uuid); + console.log("Policy: \n", JSON.parse(policy!)); + + if (policy) { + const policyObject = JSON.parse(policy) as LoginPolicy; + + // Constants for Redis to store the authentication result + const MAX_AGE = 20 * 60; + const EXPIRY_MS = "EX"; + + // Verify the presentation and the status of the credential + if (await verifyAuthenticationPresentation(presentation)) { + // Evaluate if the VP should be trusted + if (isTrustedPresentation(presentation, policyObject)) { + console.log("Presentation verified"); + + // Get the user claims when the presentation is trusted + const userClaims = extractClaims(presentation, policyObject); + console.log(userClaims); + + // Store the authentication result in Redis + await redis.set("auth_res:" + uuid, "success", EXPIRY_MS, MAX_AGE); + // Store the user claims in Redis + await redis.set( + "claims", + JSON.stringify(userClaims.tokenId), + EXPIRY_MS, + MAX_AGE, + ); + } else { + console.log("Presentation not trusted"); + + await redis.set( + "auth_res:" + uuid, + "error_presentation_not_trused", + EXPIRY_MS, + MAX_AGE, + ); + // Wallet gets an error message + res.status(500).end(); + return; + } } else { - console.log("Presentation not trusted"); + console.log("Presentation invalid"); + await redis.set( + "auth_res:" + uuid, + "error_invalid_presentation", + EXPIRY_MS, + MAX_AGE, + ); res.status(500).end(); return; } - } else { - console.log("Presentation invalid"); - res.status(500).end(); - return; - } - // Get the user claims - const userClaims = extractClaims(presentation); - const subject = presentation["holder"]; - const uuid = presentation["proof"]["challenge"]; - console.log(userClaims); - - const MAX_AGE = 20 * 60; - const EXPIRY_MS = "EX"; - - await redis.set("auth_res", uuid, EXPIRY_MS, MAX_AGE); - res.status(200).end(); - // This will handle any error that happens when making HTTP calls to hydra - } else { - res.status(500).end(); + // Wallet gets 200 status code + res.status(200).end(); + } } } catch (error) { res.status(500).end(); } - const { method } = req; } From 39307f51ba7699d870fb03cb682006ab3d476a66 Mon Sep 17 00:00:00 2001 From: Ilayda Cansin Koc Date: Wed, 1 May 2024 21:58:30 +0000 Subject: [PATCH 20/85] fix: define types for input descriptor and presentation definition Signed-off-by: Ilayda Cansin Koc --- vclogin/types/InputDescriptor.ts | 21 +++++++++++++++++++++ vclogin/types/PresentationDefinition.ts | 21 +++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 vclogin/types/InputDescriptor.ts create mode 100644 vclogin/types/PresentationDefinition.ts diff --git a/vclogin/types/InputDescriptor.ts b/vclogin/types/InputDescriptor.ts new file mode 100644 index 0000000..d0104db --- /dev/null +++ b/vclogin/types/InputDescriptor.ts @@ -0,0 +1,21 @@ +type Fields = { + path: string[]; + filter?: { + type: string; + pattern: string; + }; +}; + +type Constraints = { + fields?: Fields[]; +}; + +export type InputDescriptor = { + id: string; + purpose: string; + name: string; + group?: string[]; + constraints: Constraints; +}; + +export type InputDescriptors = InputDescriptor[]; diff --git a/vclogin/types/PresentationDefinition.ts b/vclogin/types/PresentationDefinition.ts new file mode 100644 index 0000000..efda8e9 --- /dev/null +++ b/vclogin/types/PresentationDefinition.ts @@ -0,0 +1,21 @@ +import { InputDescriptor } from "./InputDescriptor"; + +export type PresentationDefinition = { + format: { + ldp_vc: { + proof_type: string[]; + }; + ldp_vp: { + proof_type: string[]; + }; + }; + id: string; + name: string; + purpose: string; + input_descriptors: InputDescriptor[]; + submission_requirements?: { + rule: string; + count: number; + from: string; + }[]; +}; From 5838a46f93ab2e5f5d1e882aeb8be8cc000c2991 Mon Sep 17 00:00:00 2001 From: Ilayda Cansin Koc Date: Wed, 1 May 2024 21:58:44 +0000 Subject: [PATCH 21/85] add verifiable id descriptor Signed-off-by: Ilayda Cansin Koc --- .../pex/descriptorVerifiableIDFromAltme.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 vclogin/__tests__/testdata/pex/descriptorVerifiableIDFromAltme.json diff --git a/vclogin/__tests__/testdata/pex/descriptorVerifiableIDFromAltme.json b/vclogin/__tests__/testdata/pex/descriptorVerifiableIDFromAltme.json new file mode 100644 index 0000000..5c78bc2 --- /dev/null +++ b/vclogin/__tests__/testdata/pex/descriptorVerifiableIDFromAltme.json @@ -0,0 +1,18 @@ +[ + { + "id": "verifiableId", + "name": "Input descriptor for login credential", + "purpose": "Sign-in to MVG", + "constraints": { + "fields": [ + { + "path": ["$.credentialSubject.type"], + "filter": { + "type": "string", + "pattern": "VerifiableId" + } + } + ] + } + } +] From a874fdd6270692968c4e1b13af7c7a41fe2cbd14 Mon Sep 17 00:00:00 2001 From: Ilayda Cansin Koc Date: Wed, 1 May 2024 21:59:49 +0000 Subject: [PATCH 22/85] add logic to parametrically use input descriptor for each incr auth Signed-off-by: Ilayda Cansin Koc --- vclogin/lib/generatePresentationDefinition.ts | 25 ++++++++++++++----- .../api/dynamic/createTempAuthorization.ts | 17 +++++++++++-- vclogin/pages/api/dynamic/getAuthResponse.ts | 4 +-- .../api/dynamic/presentCredentialById.ts | 23 +++++++++++------ 4 files changed, 52 insertions(+), 17 deletions(-) diff --git a/vclogin/lib/generatePresentationDefinition.ts b/vclogin/lib/generatePresentationDefinition.ts index 7b36166..8d1144c 100644 --- a/vclogin/lib/generatePresentationDefinition.ts +++ b/vclogin/lib/generatePresentationDefinition.ts @@ -3,7 +3,9 @@ * SPDX-License-Identifier: MIT */ +import { InputDescriptor, InputDescriptors } from "@/types/InputDescriptor"; import { LoginPolicy } from "@/types/LoginPolicy"; +import { PresentationDefinition } from "@/types/PresentationDefinition"; import { promises as fs } from "fs"; var inputDescriptorOverride: any = undefined; @@ -15,12 +17,16 @@ if (process.env.PEX_DESCRIPTOR_OVERRIDE) { ); } -export const generatePresentationDefinition = (policy: LoginPolicy) => { +export const generatePresentationDefinition = ( + policy: LoginPolicy, + incrAuthInputDescriptor?: InputDescriptors, +) => { if (policy === undefined) throw Error( "A policy must be specified to generate a presentation definition", ); - var pd: any = { + + var pd: PresentationDefinition = { format: { ldp_vc: { proof_type: [ @@ -42,12 +48,19 @@ export const generatePresentationDefinition = (policy: LoginPolicy) => { id: crypto.randomUUID(), name: "VC Login Service", purpose: "Sign-in", - input_descriptors: [] as any[], + input_descriptors: [] as InputDescriptors, }; - if (inputDescriptorOverride) { + if (inputDescriptorOverride && !incrAuthInputDescriptor) { pd.input_descriptors = inputDescriptorOverride; return pd; + } else if (incrAuthInputDescriptor) { + pd.input_descriptors = incrAuthInputDescriptor; + console.log( + "Using input descriptor override for incremental authorization", + pd, + ); + return pd; } for (let expectation of policy) { @@ -57,11 +70,11 @@ export const generatePresentationDefinition = (policy: LoginPolicy) => { count: 1, from: "group_" + expectation.credentialId, }; - pd.submission_requirements.push(req); + pd.submission_requirements!.push(req); } for (let pattern of expectation.patterns) { - let descr: any = { + let descr: InputDescriptor = { id: expectation.credentialId, purpose: "Sign-in", name: "Input descriptor for " + expectation.credentialId, diff --git a/vclogin/pages/api/dynamic/createTempAuthorization.ts b/vclogin/pages/api/dynamic/createTempAuthorization.ts index f86612b..db61def 100644 --- a/vclogin/pages/api/dynamic/createTempAuthorization.ts +++ b/vclogin/pages/api/dynamic/createTempAuthorization.ts @@ -14,12 +14,25 @@ export default async function handler( res: NextApiResponse, ) { //Get Policy from request body - const policy = req.body; + const body = JSON.parse(req.body); + const policy = body.policy; + const inputDescriptor = body.inputDescriptor; try { // store policy in redis with uuid as key const uuid = crypto.randomUUID(); - await redis.set(uuid, policy, "EX", 300); + await redis.set(uuid + "_policy", JSON.stringify(policy), "EX", 300); + + //check if inputDescriptor is present + if (inputDescriptor) { + //store inputDescriptor in redis with uuid as key + await redis.set( + uuid + "_inputDescriptor", + JSON.stringify(inputDescriptor), + "EX", + 300, + ); + } return res.status(200).json({ uuid }); } catch (error) { return res.status(500).json({ redirect: "/error" }); diff --git a/vclogin/pages/api/dynamic/getAuthResponse.ts b/vclogin/pages/api/dynamic/getAuthResponse.ts index 06afade..97629bc 100644 --- a/vclogin/pages/api/dynamic/getAuthResponse.ts +++ b/vclogin/pages/api/dynamic/getAuthResponse.ts @@ -19,11 +19,11 @@ export default async function handler( // Read auth_res from redis and check if it matches the uuid //auth_res kept in redis like auth_res:uuid, read auth_res using uuid - const auth_res = await redis.get("auth_res:" + uuid); + const auth_res = await redis.get(uuid + "_auth-res"); if (auth_res) { //if auth_res found, return it along claims - const claims = await redis.get("claims"); + const claims = await redis.get(uuid + "_claims"); res.status(200).json({ auth_res, claims }); } else { //if auth_res not found, return error diff --git a/vclogin/pages/api/dynamic/presentCredentialById.ts b/vclogin/pages/api/dynamic/presentCredentialById.ts index d54c244..eaa3221 100644 --- a/vclogin/pages/api/dynamic/presentCredentialById.ts +++ b/vclogin/pages/api/dynamic/presentCredentialById.ts @@ -18,7 +18,7 @@ try { export default async function handler( req: NextApiRequest, - res: NextApiResponse, + res: NextApiResponse, //todo look for separate handles ) { try { const { method } = req; @@ -30,15 +30,21 @@ export default async function handler( const uuid = req.query["login_id"]; // fetch policy from redis using uuid - const policy = await redis.get("" + uuid); + const policy = await redis.get(uuid + "_policy"); + + // fetch inputDescriptor from redis using uuid + const inputDescriptor = await redis.get(uuid + "_inputDescriptor"); + console.log("inputDescriptor: ", JSON.parse(inputDescriptor!)); //if policy is found if (policy) { const policyObject = JSON.parse(policy) as LoginPolicy; // generate presentation definition using policy - const presentation_definition = - generatePresentationDefinition(policyObject); + const presentation_definition = generatePresentationDefinition( + policyObject, + inputDescriptor ? JSON.parse(inputDescriptor) : undefined, + ); const did = await keyToDID("key", process.env.DID_KEY_JWK!); const verificationMethod = await keyToVerificationMethod( @@ -97,11 +103,12 @@ export default async function handler( const subject = presentation["holder"]; const uuid = presentation["proof"]["challenge"]; - const policy = await redis.get("" + uuid); + const policy = await redis.get(uuid + "_policy"); console.log("Policy: \n", JSON.parse(policy!)); if (policy) { const policyObject = JSON.parse(policy) as LoginPolicy; + console.log("here", policyObject); // Constants for Redis to store the authentication result const MAX_AGE = 20 * 60; @@ -109,6 +116,7 @@ export default async function handler( // Verify the presentation and the status of the credential if (await verifyAuthenticationPresentation(presentation)) { + console.log("Presentation valid"); // Evaluate if the VP should be trusted if (isTrustedPresentation(presentation, policyObject)) { console.log("Presentation verified"); @@ -118,10 +126,11 @@ export default async function handler( console.log(userClaims); // Store the authentication result in Redis - await redis.set("auth_res:" + uuid, "success", EXPIRY_MS, MAX_AGE); + await redis.set(uuid + "_auth-res", "success", EXPIRY_MS, MAX_AGE); + // Store the user claims in Redis await redis.set( - "claims", + uuid + "_claims", JSON.stringify(userClaims.tokenId), EXPIRY_MS, MAX_AGE, From 1eeb8eada9518d1c742cc6a699dc5e44d984bbc0 Mon Sep 17 00:00:00 2001 From: Ilayda Cansin Koc Date: Wed, 1 May 2024 22:00:14 +0000 Subject: [PATCH 23/85] update redirect URIs in test_client.sh Signed-off-by: Ilayda Cansin Koc --- test_client.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test_client.sh b/test_client.sh index 98e6a73..f209ded 100755 --- a/test_client.sh +++ b/test_client.sh @@ -8,7 +8,7 @@ client=$(docker run --rm -it \ --grant-type authorization_code \ --response-type token,code,id_token \ --scope openid \ - --redirect-uri http://localhost:3001 \ + --redirect-uri "http://localhost:3000/api/auth/callback/oidc" \ -e http://hydra:4445 \ --token-endpoint-auth-method client_secret_post \ --format json ) @@ -25,7 +25,7 @@ docker run --rm -it \ --port 9010 \ --client-id $client_id \ --client-secret some-secret \ - --redirect http://localhost:3001 \ + --redirect "http://localhost:3000/api/auth/callback/oidc" \ --scope openid \ --auth-url http://localhost:5004/oauth2/auth \ --token-url http://localhost:5004/oauth2/token \ From 502eabfc188d3198552b44e0847d5b003f4c9ede Mon Sep 17 00:00:00 2001 From: Ilayda Cansin Koc Date: Wed, 1 May 2024 22:39:12 +0000 Subject: [PATCH 24/85] refactor, create getToken and getMetadata to reuse it in both methods Signed-off-by: Ilayda Cansin Koc --- vclogin/lib/generatePresentationDefinition.ts | 2 +- vclogin/lib/getMetadata.ts | 47 ++++++++++++++ vclogin/lib/getToken.ts | 57 ++++++++++++++++ vclogin/pages/api/clientMetadata.ts | 50 ++------------ .../pages/api/dynamic/clientMetadataById.ts | 54 ++------------- .../api/dynamic/createTempAuthorization.ts | 4 +- .../api/dynamic/presentCredentialById.ts | 65 +++++-------------- vclogin/pages/api/new.ts | 16 ----- vclogin/pages/api/presentCredential.ts | 58 +++++------------ 9 files changed, 152 insertions(+), 201 deletions(-) create mode 100644 vclogin/lib/getMetadata.ts create mode 100644 vclogin/lib/getToken.ts delete mode 100644 vclogin/pages/api/new.ts diff --git a/vclogin/lib/generatePresentationDefinition.ts b/vclogin/lib/generatePresentationDefinition.ts index 8d1144c..623db22 100644 --- a/vclogin/lib/generatePresentationDefinition.ts +++ b/vclogin/lib/generatePresentationDefinition.ts @@ -94,7 +94,7 @@ export const generatePresentationDefinition = ( if (fields.length > 0) { descr.constraints.fields = fields; } - console.log(descr); + pd.input_descriptors.push(descr); } } diff --git a/vclogin/lib/getMetadata.ts b/vclogin/lib/getMetadata.ts new file mode 100644 index 0000000..6e4445b --- /dev/null +++ b/vclogin/lib/getMetadata.ts @@ -0,0 +1,47 @@ +export const getMetadata = (redirect_uris: string[]) => { + const metadata = { + scopes_supported: ["openid"], + response_types_supported: ["id_token", "vp_token"], + response_modes_supported: ["query"], + subject_types_supported: ["public"], + id_token_signing_alg_values_supported: [ + "ES256", + "ES256k", + "EdDSA", + "RS256", + ], + request_object_signing_alg_values_supported: [ + "ES256", + "ES256K", + "EdDSA", + "RS256", + ], + vp_formats: { + jwt_vp: { + alg_values_supported: ["ES256", "ES256K", "EdDSA", "RS256"], + }, + jwt_vc: { + alg_values_supported: ["ES256", "ES256K", "EdDSA", "RS256"], + }, + }, + subject_syntax_types_supported: [ + "did:key", + "did:ebsi", + "did:tz", + "did:pkh", + "did:key", + "did:ethr", + ], + subject_syntax_types_discriminations: [ + "did:key:jwk_jcs-pub", + "did:ebsi:v1", + ], + subject_trust_frameworks_supported: ["ebsi"], + id_token_types_supported: ["subject_signed_id_token"], + client_name: "VP Login Service", + request_uri_parameter_supported: true, + request_parameter_supported: false, + redirect_uris, + }; + return metadata; +}; diff --git a/vclogin/lib/getToken.ts b/vclogin/lib/getToken.ts new file mode 100644 index 0000000..43bb81d --- /dev/null +++ b/vclogin/lib/getToken.ts @@ -0,0 +1,57 @@ +import { PresentationDefinition } from "@/types/PresentationDefinition"; +import { keyToDID, keyToVerificationMethod } from "@spruceid/didkit-wasm-node"; +import * as jose from "jose"; +import { NextApiResponse } from "next/types"; + +/** + * + * @param challenge + * @param client_metadata_uri + * @param response_uri + * @param presentation_definition + * @param res + */ +export const getToken = async ( + challenge: string, + client_metadata_uri: string, + response_uri: string, + presentation_definition: PresentationDefinition, + res: NextApiResponse, +) => { + const did = await keyToDID("key", process.env.DID_KEY_JWK!); + const verificationMethod = await keyToVerificationMethod( + "key", + process.env.DID_KEY_JWK!, + ); + const payload = { + client_id: did, + client_id_scheme: "did", + client_metadata_uri, + nonce: challenge, + presentation_definition, + response_mode: "direct_post", + response_type: "vp_token", + response_uri, + state: challenge, + }; + const privateKey = await jose.importJWK( + JSON.parse(process.env.DID_KEY_JWK!), + "EdDSA", + ); + const token = await new jose.SignJWT(payload) + .setProtectedHeader({ + alg: "EdDSA", + kid: verificationMethod, + typ: "JWT", + }) + .setIssuedAt() + .setIssuer(did) + .setAudience("https://self-issued.me/v2") // by definition + .setExpirationTime("1 hour") + .sign(privateKey) + .catch((err) => { + console.log(err); + res.status(500).end(); + }); + return token; +}; diff --git a/vclogin/pages/api/clientMetadata.ts b/vclogin/pages/api/clientMetadata.ts index 5289cd8..6ee2efd 100644 --- a/vclogin/pages/api/clientMetadata.ts +++ b/vclogin/pages/api/clientMetadata.ts @@ -3,6 +3,7 @@ * SPDX-License-Identifier: MIT */ +import { getMetadata } from "@/lib/getMetadata"; import type { NextApiRequest, NextApiResponse } from "next"; export default async function handler( @@ -13,52 +14,9 @@ export default async function handler( const { method } = req; if (method === "GET") { console.log("METADATA API GET"); - const metadata = { - scopes_supported: ["openid"], - response_types_supported: ["id_token", "vp_token"], - response_modes_supported: ["query"], - subject_types_supported: ["public"], - id_token_signing_alg_values_supported: [ - "ES256", - "ES256k", - "EdDSA", - "RS256", - ], - request_object_signing_alg_values_supported: [ - "ES256", - "ES256K", - "EdDSA", - "RS256", - ], - vp_formats: { - jwt_vp: { - alg_values_supported: ["ES256", "ES256K", "EdDSA", "RS256"], - }, - jwt_vc: { - alg_values_supported: ["ES256", "ES256K", "EdDSA", "RS256"], - }, - }, - subject_syntax_types_supported: [ - "did:key", - "did:ebsi", - "did:tz", - "did:pkh", - "did:key", - "did:ethr", - ], - subject_syntax_types_discriminations: [ - "did:key:jwk_jcs-pub", - "did:ebsi:v1", - ], - subject_trust_frameworks_supported: ["ebsi"], - id_token_types_supported: ["subject_signed_id_token"], - client_name: "VP Login Service", - request_uri_parameter_supported: true, - request_parameter_supported: false, - redirect_uris: [ - process.env.NEXT_PUBLIC_INTERNET_URL + "/api/presentCredential", - ], - }; + const metadata = getMetadata([ + process.env.NEXT_PUBLIC_INTERNET_URL + "/api/presentCredential", + ]); res.status(200).json(metadata); } else { res.status(500).end(); diff --git a/vclogin/pages/api/dynamic/clientMetadataById.ts b/vclogin/pages/api/dynamic/clientMetadataById.ts index d24a662..de545c0 100644 --- a/vclogin/pages/api/dynamic/clientMetadataById.ts +++ b/vclogin/pages/api/dynamic/clientMetadataById.ts @@ -3,6 +3,7 @@ * SPDX-License-Identifier: MIT */ +import { getMetadata } from "@/lib/getMetadata"; import type { NextApiRequest, NextApiResponse } from "next"; export default async function handler( @@ -12,54 +13,11 @@ export default async function handler( try { const { method } = req; if (method === "GET") { - console.log("METADATA API GET"); - const metadata = { - scopes_supported: ["openid"], - response_types_supported: ["id_token", "vp_token"], - response_modes_supported: ["query"], - subject_types_supported: ["public"], - id_token_signing_alg_values_supported: [ - "ES256", - "ES256k", - "EdDSA", - "RS256", - ], - request_object_signing_alg_values_supported: [ - "ES256", - "ES256K", - "EdDSA", - "RS256", - ], - vp_formats: { - jwt_vp: { - alg_values_supported: ["ES256", "ES256K", "EdDSA", "RS256"], - }, - jwt_vc: { - alg_values_supported: ["ES256", "ES256K", "EdDSA", "RS256"], - }, - }, - subject_syntax_types_supported: [ - "did:key", - "did:ebsi", - "did:tz", - "did:pkh", - "did:key", - "did:ethr", - ], - subject_syntax_types_discriminations: [ - "did:key:jwk_jcs-pub", - "did:ebsi:v1", - ], - subject_trust_frameworks_supported: ["ebsi"], - id_token_types_supported: ["subject_signed_id_token"], - client_name: "VP Login Service", - request_uri_parameter_supported: true, - request_parameter_supported: false, - redirect_uris: [ - process.env.NEXT_PUBLIC_INTERNET_URL + - "/api/dynamic/presentCredentialById", - ], - }; + console.log("METADATA BY ID API GET"); + const metadata = getMetadata([ + process.env.NEXT_PUBLIC_INTERNET_URL + + "/api/dynamic/presentCredentialById", + ]); res.status(200).json(metadata); } else { res.status(500).end(); diff --git a/vclogin/pages/api/dynamic/createTempAuthorization.ts b/vclogin/pages/api/dynamic/createTempAuthorization.ts index db61def..2b4e3b2 100644 --- a/vclogin/pages/api/dynamic/createTempAuthorization.ts +++ b/vclogin/pages/api/dynamic/createTempAuthorization.ts @@ -14,9 +14,7 @@ export default async function handler( res: NextApiResponse, ) { //Get Policy from request body - const body = JSON.parse(req.body); - const policy = body.policy; - const inputDescriptor = body.inputDescriptor; + const { policy, inputDescriptor } = JSON.parse(req.body); try { // store policy in redis with uuid as key diff --git a/vclogin/pages/api/dynamic/presentCredentialById.ts b/vclogin/pages/api/dynamic/presentCredentialById.ts index eaa3221..d9cf5ba 100644 --- a/vclogin/pages/api/dynamic/presentCredentialById.ts +++ b/vclogin/pages/api/dynamic/presentCredentialById.ts @@ -8,6 +8,7 @@ import { hydraAdmin } from "@/config/ory"; import { extractClaims, isTrustedPresentation } from "@/lib/extractClaims"; import { verifyAuthenticationPresentation } from "@/lib/verifyPresentation"; import { promises as fs } from "fs"; +import { getToken } from "@/lib/getToken"; var redis: Redis; try { @@ -25,7 +26,7 @@ export default async function handler( if (method === "GET") { console.log("LOGIN API GET BY ID"); - console.log(req.query); + // Get login_id from query const uuid = req.query["login_id"]; @@ -41,58 +42,31 @@ export default async function handler( const policyObject = JSON.parse(policy) as LoginPolicy; // generate presentation definition using policy + // and inputDescriptor if it exists const presentation_definition = generatePresentationDefinition( policyObject, inputDescriptor ? JSON.parse(inputDescriptor) : undefined, ); - const did = await keyToDID("key", process.env.DID_KEY_JWK!); - const verificationMethod = await keyToVerificationMethod( - "key", - process.env.DID_KEY_JWK!, - ); const challenge = req.query["login_id"]; - const payload = { - client_id: did, - client_id_scheme: "did", - client_metadata_uri: + + if (challenge) { + const token = await getToken( + challenge as string, process.env.EXTERNAL_URL + "/api/dynamic/clientMetadataById", - nonce: challenge, - presentation_definition, - response_mode: "direct_post", - response_type: "vp_token", - response_uri: process.env.EXTERNAL_URL + "/api/dynamic/presentCredentialById", - state: challenge, - }; - const privateKey = await jose.importJWK( - JSON.parse(process.env.DID_KEY_JWK!), - "EdDSA", - ); - const token = await new jose.SignJWT(payload) - .setProtectedHeader({ - alg: "EdDSA", - kid: verificationMethod, - typ: "JWT", - }) - .setIssuedAt() - .setIssuer(did) - .setAudience("https://self-issued.me/v2") // by definition - .setExpirationTime("1 hour") - .sign(privateKey) - .catch((err) => { - console.log(err); - res.status(500).end(); - }); - console.log("TOKEN: " + token); - res - .status(200) - .appendHeader("Content-Type", "application/oauth-authz-req+jwt") - .send(token); - console.log("presentation_definition: " + presentation_definition); - res.send({ status: "success" }); + presentation_definition, + res, + ); + + res + .status(200) + .appendHeader("Content-Type", "application/oauth-authz-req+jwt") + .send(token); + } } else { - res.status(500).end({ error: "No policy found" }); + res.status(500).end(); + return; } } else if (method === "POST") { console.log("LOGIN API POST BY ID"); @@ -101,14 +75,11 @@ export default async function handler( const presentation = JSON.parse(req.body.vp_token); console.log("Presentation: \n", req.body.vp_token); - const subject = presentation["holder"]; const uuid = presentation["proof"]["challenge"]; const policy = await redis.get(uuid + "_policy"); - console.log("Policy: \n", JSON.parse(policy!)); if (policy) { const policyObject = JSON.parse(policy) as LoginPolicy; - console.log("here", policyObject); // Constants for Redis to store the authentication result const MAX_AGE = 20 * 60; diff --git a/vclogin/pages/api/new.ts b/vclogin/pages/api/new.ts deleted file mode 100644 index 65d5d3c..0000000 --- a/vclogin/pages/api/new.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { NextApiRequest, NextApiResponse } from "next"; -import { Redis } from "ioredis"; - -var redis: Redis; -try { - redis = new Redis(parseInt(process.env.REDIS_PORT!), process.env.REDIS_HOST!); -} catch (error) { - console.error("Failed to connect to Redis:", error); -} - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse, -) { - return res.status(200).json({ message: "Hello World" }); -} diff --git a/vclogin/pages/api/presentCredential.ts b/vclogin/pages/api/presentCredential.ts index 2c34bd1..063dd3a 100644 --- a/vclogin/pages/api/presentCredential.ts +++ b/vclogin/pages/api/presentCredential.ts @@ -12,6 +12,7 @@ import * as jose from "jose"; import { keyToDID, keyToVerificationMethod } from "@spruceid/didkit-wasm-node"; import { generatePresentationDefinition } from "@/lib/generatePresentationDefinition"; import { getConfiguredLoginPolicy } from "@/config/loginPolicy"; +import { getToken } from "@/lib/getToken"; var redis: Redis; try { @@ -32,47 +33,24 @@ export default async function handler( const presentation_definition = generatePresentationDefinition( getConfiguredLoginPolicy()!, ); - const did = await keyToDID("key", process.env.DID_KEY_JWK!); - const verificationMethod = await keyToVerificationMethod( - "key", - process.env.DID_KEY_JWK!, - ); + const challenge = req.query["login_id"]; - const payload = { - client_id: did, - client_id_scheme: "did", - client_metadata_uri: process.env.EXTERNAL_URL + "/api/clientMetadata", - nonce: challenge, - presentation_definition, - response_mode: "direct_post", - response_type: "vp_token", - response_uri: process.env.EXTERNAL_URL + "/api/presentCredential", - state: challenge, - }; - const privateKey = await jose.importJWK( - JSON.parse(process.env.DID_KEY_JWK!), - "EdDSA", - ); - const token = await new jose.SignJWT(payload) - .setProtectedHeader({ - alg: "EdDSA", - kid: verificationMethod, - typ: "JWT", - }) - .setIssuedAt() - .setIssuer(did) - .setAudience("https://self-issued.me/v2") // by definition - .setExpirationTime("1 hour") - .sign(privateKey) - .catch((err) => { - console.log(err); - res.status(500).end(); - }); - console.log("TOKEN: " + token); - res - .status(200) - .appendHeader("Content-Type", "application/oauth-authz-req+jwt") - .send(token); + + if (challenge) { + const token = await getToken( + challenge as string, + process.env.EXTERNAL_URL + "/api/clientMetadata", + process.env.EXTERNAL_URL + "/api/presentCredential", + presentation_definition, + res, + ); + console.log("Token: \n", token); + + res + .status(200) + .appendHeader("Content-Type", "application/oauth-authz-req+jwt") + .send(token); + } } else if (method === "POST") { console.log("LOGIN API POST"); From 8e38b37d9b11ef6f7300d1af2501682daacb49a6 Mon Sep 17 00:00:00 2001 From: Ilayda Cansin Koc Date: Wed, 1 May 2024 23:00:47 +0000 Subject: [PATCH 25/85] separete get and post method Signed-off-by: Ilayda Cansin Koc --- .../api/dynamic/presentCredentialById.ts | 223 +++++++++--------- vclogin/pages/api/presentCredential.ts | 222 ++++++++--------- 2 files changed, 233 insertions(+), 212 deletions(-) diff --git a/vclogin/pages/api/dynamic/presentCredentialById.ts b/vclogin/pages/api/dynamic/presentCredentialById.ts index d9cf5ba..79b0860 100644 --- a/vclogin/pages/api/dynamic/presentCredentialById.ts +++ b/vclogin/pages/api/dynamic/presentCredentialById.ts @@ -17,123 +17,134 @@ try { console.error("Failed to connect to Redis:", error); } -export default async function handler( - req: NextApiRequest, - res: NextApiResponse, //todo look for separate handles -) { - try { - const { method } = req; +const getHandler = async (req: NextApiRequest, res: NextApiResponse) => { + console.log("LOGIN API GET BY ID"); + + // Get login_id from query + const uuid = req.query["login_id"]; + + // fetch policy from redis using uuid + const policy = await redis.get(uuid + "_policy"); + + // fetch inputDescriptor from redis using uuid + const inputDescriptor = await redis.get(uuid + "_inputDescriptor"); + console.log("inputDescriptor: ", JSON.parse(inputDescriptor!)); + + //if policy is found + if (policy) { + const policyObject = JSON.parse(policy) as LoginPolicy; + + // generate presentation definition using policy + // and inputDescriptor if it exists + const presentation_definition = generatePresentationDefinition( + policyObject, + inputDescriptor ? JSON.parse(inputDescriptor) : undefined, + ); + + const challenge = req.query["login_id"]; + + if (challenge) { + const token = await getToken( + challenge as string, + process.env.EXTERNAL_URL + "/api/dynamic/clientMetadataById", + process.env.EXTERNAL_URL + "/api/dynamic/presentCredentialById", + presentation_definition, + res, + ); + + res + .status(200) + .appendHeader("Content-Type", "application/oauth-authz-req+jwt") + .send(token); + } + } else { + res.status(500).end(); + return; + } +}; - if (method === "GET") { - console.log("LOGIN API GET BY ID"); +const postHandler = async (req: NextApiRequest, res: NextApiResponse) => { + console.log("LOGIN API POST BY ID"); - // Get login_id from query - const uuid = req.query["login_id"]; + // Parse the JSON string into a JavaScript object + const presentation = JSON.parse(req.body.vp_token); + console.log("Presentation: \n", req.body.vp_token); - // fetch policy from redis using uuid - const policy = await redis.get(uuid + "_policy"); + const uuid = presentation["proof"]["challenge"]; + const policy = await redis.get(uuid + "_policy"); - // fetch inputDescriptor from redis using uuid - const inputDescriptor = await redis.get(uuid + "_inputDescriptor"); - console.log("inputDescriptor: ", JSON.parse(inputDescriptor!)); + if (policy) { + const policyObject = JSON.parse(policy) as LoginPolicy; - //if policy is found - if (policy) { - const policyObject = JSON.parse(policy) as LoginPolicy; + // Constants for Redis to store the authentication result + const MAX_AGE = 20 * 60; + const EXPIRY_MS = "EX"; - // generate presentation definition using policy - // and inputDescriptor if it exists - const presentation_definition = generatePresentationDefinition( - policyObject, - inputDescriptor ? JSON.parse(inputDescriptor) : undefined, - ); + // Verify the presentation and the status of the credential + if (await verifyAuthenticationPresentation(presentation)) { + console.log("Presentation valid"); + // Evaluate if the VP should be trusted + if (isTrustedPresentation(presentation, policyObject)) { + console.log("Presentation verified"); + + // Get the user claims when the presentation is trusted + const userClaims = extractClaims(presentation, policyObject); + console.log(userClaims); - const challenge = req.query["login_id"]; - - if (challenge) { - const token = await getToken( - challenge as string, - process.env.EXTERNAL_URL + "/api/dynamic/clientMetadataById", - process.env.EXTERNAL_URL + "/api/dynamic/presentCredentialById", - presentation_definition, - res, - ); - - res - .status(200) - .appendHeader("Content-Type", "application/oauth-authz-req+jwt") - .send(token); - } + // Store the authentication result in Redis + await redis.set(uuid + "_auth-res", "success", EXPIRY_MS, MAX_AGE); + + // Store the user claims in Redis + await redis.set( + uuid + "_claims", + JSON.stringify(userClaims.tokenId), + EXPIRY_MS, + MAX_AGE, + ); } else { + console.log("Presentation not trusted"); + + await redis.set( + "auth_res:" + uuid, + "error_presentation_not_trused", + EXPIRY_MS, + MAX_AGE, + ); + // Wallet gets an error message res.status(500).end(); return; } - } else if (method === "POST") { - console.log("LOGIN API POST BY ID"); - - // Parse the JSON string into a JavaScript object - const presentation = JSON.parse(req.body.vp_token); - console.log("Presentation: \n", req.body.vp_token); - - const uuid = presentation["proof"]["challenge"]; - const policy = await redis.get(uuid + "_policy"); - - if (policy) { - const policyObject = JSON.parse(policy) as LoginPolicy; - - // Constants for Redis to store the authentication result - const MAX_AGE = 20 * 60; - const EXPIRY_MS = "EX"; - - // Verify the presentation and the status of the credential - if (await verifyAuthenticationPresentation(presentation)) { - console.log("Presentation valid"); - // Evaluate if the VP should be trusted - if (isTrustedPresentation(presentation, policyObject)) { - console.log("Presentation verified"); - - // Get the user claims when the presentation is trusted - const userClaims = extractClaims(presentation, policyObject); - console.log(userClaims); - - // Store the authentication result in Redis - await redis.set(uuid + "_auth-res", "success", EXPIRY_MS, MAX_AGE); - - // Store the user claims in Redis - await redis.set( - uuid + "_claims", - JSON.stringify(userClaims.tokenId), - EXPIRY_MS, - MAX_AGE, - ); - } else { - console.log("Presentation not trusted"); - - await redis.set( - "auth_res:" + uuid, - "error_presentation_not_trused", - EXPIRY_MS, - MAX_AGE, - ); - // Wallet gets an error message - res.status(500).end(); - return; - } - } else { - console.log("Presentation invalid"); - await redis.set( - "auth_res:" + uuid, - "error_invalid_presentation", - EXPIRY_MS, - MAX_AGE, - ); - res.status(500).end(); - return; - } - - // Wallet gets 200 status code - res.status(200).end(); - } + } else { + console.log("Presentation invalid"); + await redis.set( + "auth_res:" + uuid, + "error_invalid_presentation", + EXPIRY_MS, + MAX_AGE, + ); + res.status(500).end(); + return; + } + + // Wallet gets 200 status code + res.status(200).end(); + } +}; + +const handlers: any = { + POST: postHandler, + GET: getHandler, +}; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse, //todo look for separate handles +) { + try { + const { method } = req; + if (method) { + const execute = handlers[method.toUpperCase()]; + return execute(req, res); } } catch (error) { res.status(500).end(); diff --git a/vclogin/pages/api/presentCredential.ts b/vclogin/pages/api/presentCredential.ts index 063dd3a..190ce20 100644 --- a/vclogin/pages/api/presentCredential.ts +++ b/vclogin/pages/api/presentCredential.ts @@ -21,118 +21,128 @@ try { console.error("Failed to connect to Redis:", error); } +const getHandler = async (req: NextApiRequest, res: NextApiResponse) => { + console.log("LOGIN API GET"); + console.log(req.query); + const presentation_definition = generatePresentationDefinition( + getConfiguredLoginPolicy()!, + ); + + const challenge = req.query["login_id"]; + + if (challenge) { + const token = await getToken( + challenge as string, + process.env.EXTERNAL_URL + "/api/clientMetadata", + process.env.EXTERNAL_URL + "/api/presentCredential", + presentation_definition, + res, + ); + console.log("Token: \n", token); + + res + .status(200) + .appendHeader("Content-Type", "application/oauth-authz-req+jwt") + .send(token); + } +}; + +const postHandler = async (req: NextApiRequest, res: NextApiResponse) => { + console.log("LOGIN API POST"); + + // Parse the JSON string into a JavaScript object + const presentation = JSON.parse(req.body.vp_token); + console.log("Presentation: \n", req.body.vp_token); + + // Verify the presentation and the status of the credential + if (await verifyAuthenticationPresentation(presentation)) { + // Evaluate if the VP should be trusted + if (isTrustedPresentation(presentation)) { + console.log("Presentation verified"); + } else { + console.log("Presentation not trusted"); + res.status(500).end(); + return; + } + } else { + console.log("Presentation invalid"); + res.status(500).end(); + return; + } + + // Get the user claims + const userClaims = extractClaims(presentation); + const subject = presentation["holder"]; + const login_id = presentation["proof"]["challenge"]; + const challenge = (await redis.get("" + login_id))!; + console.log("Logging in: " + subject + " with challenge: " + challenge); + + // hydra login + await hydraAdmin + .adminGetOAuth2LoginRequest(challenge) + .then(({ data: loginRequest }) => + hydraAdmin + .adminAcceptOAuth2LoginRequest(challenge, { + // Subject is an alias for user ID. A subject can be a random string, a UUID, an email address, .... + subject, + // This tells hydra to remember the browser and automatically authenticate the user in future requests. This will + // set the "skip" parameter in the other route to true on subsequent requests! + remember: Boolean(false), + // When the session expires, in seconds. Set this to 0 so it will never expire. + remember_for: 3600, + // Sets which "level" (e.g. 2-factor authentication) of authentication the user has. The value is really arbitrary + // and optional. In the context of OpenID Connect, a value of 0 indicates the lowest authorization level. + // acr: '0', + // + // If the environment variable CONFORMITY_FAKE_CLAIMS is set we are assuming that + // the app is built for the automated OpenID Connect Conformity Test Suite. You + // can peak inside the code for some ideas, but be aware that all data is fake + // and this only exists to fake a login system which works in accordance to OpenID Connect. + // + // If that variable is not set, the ACR value will be set to the default passed here ('0') + acr: "0", + }) + .then(({ data: body }) => { + const MAX_AGE = 30; // 30 seconds + const EXPIRY_MS = "EX"; // seconds + + // save the user claims to redis + redis.set( + "" + subject, + JSON.stringify(userClaims), + EXPIRY_MS, + MAX_AGE, + ); + + // save the redirect address to redis for the browser + redis.set( + "redirect" + login_id, + String(body.redirect_to), + EXPIRY_MS, + MAX_AGE, + ); + // phone just gets a 200 ok + res.status(200).end(); + }), + ) + // This will handle any error that happens when making HTTP calls to hydra + .catch((_) => res.status(401).end()); +}; + +const handlers: any = { + GET: getHandler, + POST: postHandler, +}; + export default async function handler( req: NextApiRequest, res: NextApiResponse, ) { try { const { method } = req; - if (method === "GET") { - console.log("LOGIN API GET"); - console.log(req.query); - const presentation_definition = generatePresentationDefinition( - getConfiguredLoginPolicy()!, - ); - - const challenge = req.query["login_id"]; - - if (challenge) { - const token = await getToken( - challenge as string, - process.env.EXTERNAL_URL + "/api/clientMetadata", - process.env.EXTERNAL_URL + "/api/presentCredential", - presentation_definition, - res, - ); - console.log("Token: \n", token); - - res - .status(200) - .appendHeader("Content-Type", "application/oauth-authz-req+jwt") - .send(token); - } - } else if (method === "POST") { - console.log("LOGIN API POST"); - - // Parse the JSON string into a JavaScript object - const presentation = JSON.parse(req.body.vp_token); - console.log("Presentation: \n", req.body.vp_token); - - // Verify the presentation and the status of the credential - if (await verifyAuthenticationPresentation(presentation)) { - // Evaluate if the VP should be trusted - if (isTrustedPresentation(presentation)) { - console.log("Presentation verified"); - } else { - console.log("Presentation not trusted"); - res.status(500).end(); - return; - } - } else { - console.log("Presentation invalid"); - res.status(500).end(); - return; - } - - // Get the user claims - const userClaims = extractClaims(presentation); - const subject = presentation["holder"]; - const login_id = presentation["proof"]["challenge"]; - const challenge = (await redis.get("" + login_id))!; - console.log("Logging in: " + subject + " with challenge: " + challenge); - - // hydra login - await hydraAdmin - .adminGetOAuth2LoginRequest(challenge) - .then(({ data: loginRequest }) => - hydraAdmin - .adminAcceptOAuth2LoginRequest(challenge, { - // Subject is an alias for user ID. A subject can be a random string, a UUID, an email address, .... - subject, - // This tells hydra to remember the browser and automatically authenticate the user in future requests. This will - // set the "skip" parameter in the other route to true on subsequent requests! - remember: Boolean(false), - // When the session expires, in seconds. Set this to 0 so it will never expire. - remember_for: 3600, - // Sets which "level" (e.g. 2-factor authentication) of authentication the user has. The value is really arbitrary - // and optional. In the context of OpenID Connect, a value of 0 indicates the lowest authorization level. - // acr: '0', - // - // If the environment variable CONFORMITY_FAKE_CLAIMS is set we are assuming that - // the app is built for the automated OpenID Connect Conformity Test Suite. You - // can peak inside the code for some ideas, but be aware that all data is fake - // and this only exists to fake a login system which works in accordance to OpenID Connect. - // - // If that variable is not set, the ACR value will be set to the default passed here ('0') - acr: "0", - }) - .then(({ data: body }) => { - const MAX_AGE = 30; // 30 seconds - const EXPIRY_MS = "EX"; // seconds - - // save the user claims to redis - redis.set( - "" + subject, - JSON.stringify(userClaims), - EXPIRY_MS, - MAX_AGE, - ); - - // save the redirect address to redis for the browser - redis.set( - "redirect" + login_id, - String(body.redirect_to), - EXPIRY_MS, - MAX_AGE, - ); - // phone just gets a 200 ok - res.status(200).end(); - }), - ) - // This will handle any error that happens when making HTTP calls to hydra - .catch((_) => res.status(401).end()); - } else { - res.status(500).end(); + if (method) { + const execute = handlers[method.toUpperCase()]; + return execute(req, res); } } catch (e) { res.status(500).end(); From 4d6cddf04364ead2086991898e4d0c71893613ec Mon Sep 17 00:00:00 2001 From: Ilayda Cansin Koc Date: Wed, 1 May 2024 23:02:06 +0000 Subject: [PATCH 26/85] use async/await for execute function Signed-off-by: Ilayda Cansin Koc --- vclogin/pages/api/dynamic/presentCredentialById.ts | 2 +- vclogin/pages/api/presentCredential.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vclogin/pages/api/dynamic/presentCredentialById.ts b/vclogin/pages/api/dynamic/presentCredentialById.ts index 79b0860..f8652ae 100644 --- a/vclogin/pages/api/dynamic/presentCredentialById.ts +++ b/vclogin/pages/api/dynamic/presentCredentialById.ts @@ -144,7 +144,7 @@ export default async function handler( const { method } = req; if (method) { const execute = handlers[method.toUpperCase()]; - return execute(req, res); + return await execute(req, res); } } catch (error) { res.status(500).end(); diff --git a/vclogin/pages/api/presentCredential.ts b/vclogin/pages/api/presentCredential.ts index 190ce20..86df0a8 100644 --- a/vclogin/pages/api/presentCredential.ts +++ b/vclogin/pages/api/presentCredential.ts @@ -142,7 +142,7 @@ export default async function handler( const { method } = req; if (method) { const execute = handlers[method.toUpperCase()]; - return execute(req, res); + return await execute(req, res); } } catch (e) { res.status(500).end(); From 31afb38200234f9330d79d693914bf40db2c78ed Mon Sep 17 00:00:00 2001 From: Ilayda Cansin Koc Date: Fri, 17 May 2024 15:14:22 +0000 Subject: [PATCH 27/85] chore: flatten credFit array in extractClaims.ts Signed-off-by: Ilayda Cansin Koc --- vclogin/lib/extractClaims.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/vclogin/lib/extractClaims.ts b/vclogin/lib/extractClaims.ts index 93bdc1c..6e3d671 100644 --- a/vclogin/lib/extractClaims.ts +++ b/vclogin/lib/extractClaims.ts @@ -146,6 +146,7 @@ const isValidConstraintFit = ( VP: any, ): boolean => { const credDict: any = {}; + credFit = credFit.flat(Infinity); for (let i = 0; i < policy.length; i++) { credDict[policy[i].credentialId] = credFit[i]; } From e13df54e909b2af2acbc7f50acef74a82141311c Mon Sep 17 00:00:00 2001 From: Ilayda Cansin Koc Date: Mon, 20 May 2024 13:45:34 +0000 Subject: [PATCH 28/85] fix: use async await to read file Signed-off-by: Ilayda Cansin Koc --- vclogin/config/loginPolicy.ts | 16 ++++++++-------- vclogin/lib/extractClaims.ts | 8 ++++---- .../pages/api/dynamic/presentCredentialById.ts | 10 +++------- vclogin/pages/api/presentCredential.ts | 8 +++----- 4 files changed, 18 insertions(+), 24 deletions(-) diff --git a/vclogin/config/loginPolicy.ts b/vclogin/config/loginPolicy.ts index 30e0b69..6ad339f 100644 --- a/vclogin/config/loginPolicy.ts +++ b/vclogin/config/loginPolicy.ts @@ -4,13 +4,13 @@ */ import { promises as fs } from "fs"; -import { LoginPolicy } from "@/types/LoginPolicy"; -var configuredPolicy: LoginPolicy | undefined = undefined; -fs?.readFile(process.env.LOGIN_POLICY as string, "utf8").then((file) => { - configuredPolicy = JSON.parse(file); -}); - -export const getConfiguredLoginPolicy = () => { - return configuredPolicy; +export const getConfiguredLoginPolicy = async () => { + try { + const file = await fs.readFile(process.env.LOGIN_POLICY as string, "utf8"); + return JSON.parse(file); + } catch (error) { + console.error("Failed to read login policy:", error); + return undefined; + } }; diff --git a/vclogin/lib/extractClaims.ts b/vclogin/lib/extractClaims.ts index 6e3d671..0770df3 100644 --- a/vclogin/lib/extractClaims.ts +++ b/vclogin/lib/extractClaims.ts @@ -12,8 +12,8 @@ import { import jp from "jsonpath"; import { getConfiguredLoginPolicy } from "@/config/loginPolicy"; -export const isTrustedPresentation = (VP: any, policy?: LoginPolicy) => { - var configuredPolicy = getConfiguredLoginPolicy(); +export const isTrustedPresentation = async (VP: any, policy?: LoginPolicy) => { + var configuredPolicy = await getConfiguredLoginPolicy(); if (!policy && configuredPolicy === undefined) return false; var usedPolicy = policy ? policy : configuredPolicy!; @@ -25,8 +25,8 @@ export const isTrustedPresentation = (VP: any, policy?: LoginPolicy) => { return getConstraintFit(creds, usedPolicy, VP).length > 0; }; -export const extractClaims = (VP: any, policy?: LoginPolicy) => { - var configuredPolicy = getConfiguredLoginPolicy(); +export const extractClaims = async (VP: any, policy?: LoginPolicy) => { + var configuredPolicy = await getConfiguredLoginPolicy(); if (!policy && configuredPolicy === undefined) return false; var usedPolicy = policy ? policy : configuredPolicy!; diff --git a/vclogin/pages/api/dynamic/presentCredentialById.ts b/vclogin/pages/api/dynamic/presentCredentialById.ts index f8652ae..d1ee97f 100644 --- a/vclogin/pages/api/dynamic/presentCredentialById.ts +++ b/vclogin/pages/api/dynamic/presentCredentialById.ts @@ -1,13 +1,9 @@ import { NextApiRequest, NextApiResponse } from "next"; import { Redis } from "ioredis"; import { generatePresentationDefinition } from "@/lib/generatePresentationDefinition"; -import { ExpectedCredential, LoginPolicy } from "@/types/LoginPolicy"; -import { keyToDID, keyToVerificationMethod } from "@spruceid/didkit-wasm-node"; -import * as jose from "jose"; -import { hydraAdmin } from "@/config/ory"; +import { LoginPolicy } from "@/types/LoginPolicy"; import { extractClaims, isTrustedPresentation } from "@/lib/extractClaims"; import { verifyAuthenticationPresentation } from "@/lib/verifyPresentation"; -import { promises as fs } from "fs"; import { getToken } from "@/lib/getToken"; var redis: Redis; @@ -84,11 +80,11 @@ const postHandler = async (req: NextApiRequest, res: NextApiResponse) => { if (await verifyAuthenticationPresentation(presentation)) { console.log("Presentation valid"); // Evaluate if the VP should be trusted - if (isTrustedPresentation(presentation, policyObject)) { + if (await isTrustedPresentation(presentation, policyObject)) { console.log("Presentation verified"); // Get the user claims when the presentation is trusted - const userClaims = extractClaims(presentation, policyObject); + const userClaims = await extractClaims(presentation, policyObject); console.log(userClaims); // Store the authentication result in Redis diff --git a/vclogin/pages/api/presentCredential.ts b/vclogin/pages/api/presentCredential.ts index 86df0a8..deb1ef1 100644 --- a/vclogin/pages/api/presentCredential.ts +++ b/vclogin/pages/api/presentCredential.ts @@ -8,8 +8,6 @@ import { verifyAuthenticationPresentation } from "@/lib/verifyPresentation"; import { hydraAdmin } from "@/config/ory"; import { Redis } from "ioredis"; import { isTrustedPresentation, extractClaims } from "@/lib/extractClaims"; -import * as jose from "jose"; -import { keyToDID, keyToVerificationMethod } from "@spruceid/didkit-wasm-node"; import { generatePresentationDefinition } from "@/lib/generatePresentationDefinition"; import { getConfiguredLoginPolicy } from "@/config/loginPolicy"; import { getToken } from "@/lib/getToken"; @@ -25,7 +23,7 @@ const getHandler = async (req: NextApiRequest, res: NextApiResponse) => { console.log("LOGIN API GET"); console.log(req.query); const presentation_definition = generatePresentationDefinition( - getConfiguredLoginPolicy()!, + await getConfiguredLoginPolicy()!, ); const challenge = req.query["login_id"]; @@ -57,7 +55,7 @@ const postHandler = async (req: NextApiRequest, res: NextApiResponse) => { // Verify the presentation and the status of the credential if (await verifyAuthenticationPresentation(presentation)) { // Evaluate if the VP should be trusted - if (isTrustedPresentation(presentation)) { + if (await isTrustedPresentation(presentation)) { console.log("Presentation verified"); } else { console.log("Presentation not trusted"); @@ -71,7 +69,7 @@ const postHandler = async (req: NextApiRequest, res: NextApiResponse) => { } // Get the user claims - const userClaims = extractClaims(presentation); + const userClaims = await extractClaims(presentation); const subject = presentation["holder"]; const login_id = presentation["proof"]["challenge"]; const challenge = (await redis.get("" + login_id))!; From 7b801d1d542136d9f30a4c0ab98589ac47d094fa Mon Sep 17 00:00:00 2001 From: Ilayda Cansin Koc Date: Mon, 27 May 2024 21:22:02 +0000 Subject: [PATCH 29/85] chore: update OIDC_CLIENT_ID in .env file with client ID from test_client.sh, and add middleware for unauth access Signed-off-by: Ilayda Cansin Koc --- test_client.sh | 9 +++++++ vclogin/middleware.ts | 24 +++++++++++++++++++ .../api/dynamic/createTempAuthorization.ts | 23 +----------------- vclogin/pages/api/dynamic/getQRCodeString.ts | 2 +- vclogin/pages/api/presentCredential.ts | 3 ++- vclogin/pages/api/test.ts | 10 ++++++++ 6 files changed, 47 insertions(+), 24 deletions(-) create mode 100644 vclogin/middleware.ts create mode 100644 vclogin/pages/api/test.ts diff --git a/test_client.sh b/test_client.sh index f209ded..b83b344 100755 --- a/test_client.sh +++ b/test_client.sh @@ -17,6 +17,15 @@ echo $client client_id=$(echo $client | jq -r '.client_id') + +env_file="./vclogin/.env" + +if grep -q "OIDC_CLIENT_ID=" "$env_file"; then + sed -i "s/^OIDC_CLIENT_ID=.*/OIDC_CLIENT_ID=$client_id/" "$env_file" +else + echo "OIDC_CLIENT_ID=$client_id" >> "$env_file" +fi + docker run --rm -it \ --network ory-hydra-net \ -p 9010:9010 \ diff --git a/vclogin/middleware.ts b/vclogin/middleware.ts new file mode 100644 index 0000000..ed6d683 --- /dev/null +++ b/vclogin/middleware.ts @@ -0,0 +1,24 @@ +import { NextResponse } from "next/server"; +import type { NextRequest } from "next/server"; + +const excludePath = [ + "/api/dynamic/presentCredentialById", + "/api/dynamic/clientMetadataById", + "/api/presentCredential", + "/api/clientMetadata", +]; + +export function middleware(req: NextRequest, res: NextResponse) { + const authHeader = req.headers.get("Authorization"); + const oidcClientId = authHeader?.split(" ")[1]; + + if (excludePath.includes(req.nextUrl.pathname)) { + return NextResponse.next(); + } + + if (oidcClientId && oidcClientId === process.env.OIDC_CLIENT_ID) { + return NextResponse.next(); + } else { + return new Response("Unauthorized", { status: 401 }); + } +} diff --git a/vclogin/pages/api/dynamic/createTempAuthorization.ts b/vclogin/pages/api/dynamic/createTempAuthorization.ts index 2b4e3b2..085ab6b 100644 --- a/vclogin/pages/api/dynamic/createTempAuthorization.ts +++ b/vclogin/pages/api/dynamic/createTempAuthorization.ts @@ -14,7 +14,7 @@ export default async function handler( res: NextApiResponse, ) { //Get Policy from request body - const { policy, inputDescriptor } = JSON.parse(req.body); + const { policy, inputDescriptor } = req.body; try { // store policy in redis with uuid as key @@ -36,24 +36,3 @@ export default async function handler( return res.status(500).json({ redirect: "/error" }); } } - -/* - //read credential id from policy - const hash = crypto.createHash("sha256").update(policy).digest("hex"); - - try { - //check if policy already exists - const existingPolicy = await redis.get(hash); - if (existingPolicy) { - return res.status(200).json({ uuid: hash }); - } else { - try { - const hash = crypto.createHash("sha256").update(policy).digest("hex"); - - await redis.set(hash, JSON.stringify(policy), "EX", 300); - - return res.status(200).json({ uuid: hash }); - } catch (error) { - return res.status(500).json({ error }); - } - } */ diff --git a/vclogin/pages/api/dynamic/getQRCodeString.ts b/vclogin/pages/api/dynamic/getQRCodeString.ts index 98d99b9..617335e 100644 --- a/vclogin/pages/api/dynamic/getQRCodeString.ts +++ b/vclogin/pages/api/dynamic/getQRCodeString.ts @@ -12,7 +12,7 @@ export default async function handler( req: NextApiRequest, res: NextApiResponse, ) { - const { userId, uuid } = JSON.parse(req.body); + const { userId, uuid } = req.body; //Generate QR Code String from UUID const qrCodeString = diff --git a/vclogin/pages/api/presentCredential.ts b/vclogin/pages/api/presentCredential.ts index deb1ef1..b859ec6 100644 --- a/vclogin/pages/api/presentCredential.ts +++ b/vclogin/pages/api/presentCredential.ts @@ -21,7 +21,6 @@ try { const getHandler = async (req: NextApiRequest, res: NextApiResponse) => { console.log("LOGIN API GET"); - console.log(req.query); const presentation_definition = generatePresentationDefinition( await getConfiguredLoginPolicy()!, ); @@ -42,6 +41,8 @@ const getHandler = async (req: NextApiRequest, res: NextApiResponse) => { .status(200) .appendHeader("Content-Type", "application/oauth-authz-req+jwt") .send(token); + } else { + res.status(500).end(); } }; diff --git a/vclogin/pages/api/test.ts b/vclogin/pages/api/test.ts new file mode 100644 index 0000000..2deea60 --- /dev/null +++ b/vclogin/pages/api/test.ts @@ -0,0 +1,10 @@ +//generate me a simple json returning api + +import { NextApiRequest, NextApiResponse } from "next"; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse, +) { + return res.status(200).json({ message: "Hello World" }); +} From 1cf1a097bd67235f33d34a71f3ae835f75e1068d Mon Sep 17 00:00:00 2001 From: Ilayda Cansin Koc Date: Thu, 6 Jun 2024 14:28:27 +0000 Subject: [PATCH 30/85] chore: Update middleware to protect additional paths and use API key for authorization Signed-off-by: Ilayda Cansin Koc --- vclogin/middleware.ts | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/vclogin/middleware.ts b/vclogin/middleware.ts index ed6d683..31972a7 100644 --- a/vclogin/middleware.ts +++ b/vclogin/middleware.ts @@ -1,24 +1,20 @@ import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; -const excludePath = [ - "/api/dynamic/presentCredentialById", - "/api/dynamic/clientMetadataById", - "/api/presentCredential", - "/api/clientMetadata", +const protectedPaths = [ + "/api/dynamic/createTempAuthorization", + "/api/dynamic/getAuthResponse", + "/api/dynamic/getQRCodeString", ]; -export function middleware(req: NextRequest, res: NextResponse) { +export function middleware(req: NextRequest) { const authHeader = req.headers.get("Authorization"); - const oidcClientId = authHeader?.split(" ")[1]; - - if (excludePath.includes(req.nextUrl.pathname)) { - return NextResponse.next(); - } - - if (oidcClientId && oidcClientId === process.env.OIDC_CLIENT_ID) { + const path = req.nextUrl.pathname; + const apiKey = authHeader?.split(" ")[1]; + if (protectedPaths.includes(path) && apiKey === process.env.API_KEY) { return NextResponse.next(); - } else { + } else if (protectedPaths.includes(path) && apiKey !== process.env.API_KEY) { return new Response("Unauthorized", { status: 401 }); } + return NextResponse.next(); } From a56d660c16498f412cdb1461283bb2b56608be61 Mon Sep 17 00:00:00 2001 From: Ilayda Cansin Koc Date: Thu, 6 Jun 2024 14:33:00 +0000 Subject: [PATCH 31/85] chore: Update test_client.sh to use OIDC_CLIENT_ID from .env file Signed-off-by: Ilayda Cansin Koc --- test_client.sh | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/test_client.sh b/test_client.sh index b83b344..0ae5774 100755 --- a/test_client.sh +++ b/test_client.sh @@ -10,22 +10,10 @@ client=$(docker run --rm -it \ --scope openid \ --redirect-uri "http://localhost:3000/api/auth/callback/oidc" \ -e http://hydra:4445 \ - --token-endpoint-auth-method client_secret_post \ --format json ) echo $client -client_id=$(echo $client | jq -r '.client_id') - - -env_file="./vclogin/.env" - -if grep -q "OIDC_CLIENT_ID=" "$env_file"; then - sed -i "s/^OIDC_CLIENT_ID=.*/OIDC_CLIENT_ID=$client_id/" "$env_file" -else - echo "OIDC_CLIENT_ID=$client_id" >> "$env_file" -fi - docker run --rm -it \ --network ory-hydra-net \ -p 9010:9010 \ From d753b232f4b584d4e76fce0a60619e5d635bf243 Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Mon, 10 Jun 2024 10:40:17 +0200 Subject: [PATCH 32/85] OIDC Discovery Fix Fixes issue #20 Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Signed-off-by: Ilayda Cansin Koc --- compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compose.yaml b/compose.yaml index eb92048..1590968 100644 --- a/compose.yaml +++ b/compose.yaml @@ -43,7 +43,7 @@ services: # - TRACING_PROVIDERS_JAEGER_LOCAL_AGENT_ADDRESS=jaeger:6831 # - TRACING_PROVIDERS_JAEGER_SAMPLING_TYPE=const # - TRACING_PROVIDERS_JAEGER_SAMPLING_VALUE=1 - - WEBFINGER_OIDC_DISCOVERY_USERINFO_URL=http://hydra:4444/userinfo + - WEBFINGER_OIDC_DISCOVERY_USERINFO_URL=http://localhost:5004/userinfo - OIDC_DYNAMIC_CLIENT_REGISTRATION_ENABLED=true restart: on-failure From f9fa302d7c067b42cfcf07015da8a3dc1285a6ec Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Mon, 10 Jun 2024 12:45:28 +0200 Subject: [PATCH 33/85] Changed default token Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Signed-off-by: Ilayda Cansin Koc --- README.md | 6 +++--- vclogin/__tests__/extractClaims.test.js | 6 ++++-- .../testdata/policies/acceptEmailFromAltme.json | 3 +-- .../testdata/policies/acceptEmailFromAltmeConstr.json | 3 +-- .../testdata/policies/acceptEmployeeFromAnyone.json | 6 ++---- .../policies/acceptEmployeeFromAnyoneConstr.json | 6 ++---- vclogin/lib/extractClaims.ts | 10 ++++------ 7 files changed, 17 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index eee1a8e..8829498 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ ![build workflow](https://github.com/GAIA-X4PLC-AAD/ssi-to-oidc-bridge/actions/workflows/node.js.yml/badge.svg) > [!WARNING] -> This repository is intended for prototyping and as a reference implementation. +> This repository is intended for prototyping and as a reference implementation. At this time, no security guarantees can be given. > [!NOTE] > A preprint of our paper providing more background information is available [on arXiv](https://arxiv.org/abs/2401.09488). @@ -195,7 +195,7 @@ Now you can develop and it will hot-reload. ## Policy Configuration -The login policy is the one configuration file that configures the bridge's behavior. The most simple example of one looks like this and accepts any credential, while forwarding all subject fields to the `access_token`: +The login policy is the one configuration file that configures the bridge's behavior. The most simple example of one looks like this and accepts any credential, while forwarding all subject fields to the `id_token`: ```JSON [ @@ -223,7 +223,7 @@ A pattern object has the following fields: - `claimPath` is a JSONPath that points to one or more values in the credential. If it points to multiple values, they will be aggregated in a new object and indexed by just their final JSONPath component. _This is generally convenient, but can lead to values being overwritten if not careful and working with a credential that uses the same path components in different depths._ - `newPath` is the new path of the value relative to the root of the token it will be written into. This value is optional, as long as `claimPath` points to exactly one value. In that case, it defaults to `$.`. -- `token` optionally defines if the claim value ends up either in `"id_token"` or `"access_token"`, with the latter being the default. +- `token` optionally defines if the claim value ends up either in `"id_token"` or `"access_token"`, with the former being the default. - `required` is optional and defaults to `false` ## Token Introspection diff --git a/vclogin/__tests__/extractClaims.test.js b/vclogin/__tests__/extractClaims.test.js index 92f0525..01094ca 100644 --- a/vclogin/__tests__/extractClaims.test.js +++ b/vclogin/__tests__/extractClaims.test.js @@ -16,6 +16,8 @@ describe("extractClaims", () => { var claims = extractClaims(vpEmployee, policyAcceptAnything); var expected = { tokenAccess: { + }, + tokenId: { subjectData: { id: "did:key:z6MkkdC46uhBGjMYS2ZDLUwCrTWdaqZdTD3596sN4397oRNd", hash: "9ecf754ffdad0c6de238f60728a90511780b2f7dbe2f0ea015115515f3f389cd", @@ -31,8 +33,8 @@ describe("extractClaims", () => { hasJurisdiction: "GER", surname: "Surname", }, + }, - tokenId: {}, }; expect(claims).toStrictEqual(expected); }); @@ -63,11 +65,11 @@ describe("extractClaims", () => { var claims = extractClaims(vpEmployee, policyEmployeeFromAnyone); var expected = { tokenAccess: { - companyName: "deltaDAO AG", }, tokenId: { email: "test@test.com", name: "Name Surname", + companyName: "deltaDAO AG", }, }; expect(claims).toStrictEqual(expected); diff --git a/vclogin/__tests__/testdata/policies/acceptEmailFromAltme.json b/vclogin/__tests__/testdata/policies/acceptEmailFromAltme.json index e409d18..72e3c73 100644 --- a/vclogin/__tests__/testdata/policies/acceptEmailFromAltme.json +++ b/vclogin/__tests__/testdata/policies/acceptEmailFromAltme.json @@ -6,8 +6,7 @@ "issuer": "did:web:app.altme.io:issuer", "claims": [ { - "claimPath": "$.credentialSubject.email", - "token": "id_token" + "claimPath": "$.credentialSubject.email" } ] } diff --git a/vclogin/__tests__/testdata/policies/acceptEmailFromAltmeConstr.json b/vclogin/__tests__/testdata/policies/acceptEmailFromAltmeConstr.json index 7e69486..4f41086 100644 --- a/vclogin/__tests__/testdata/policies/acceptEmailFromAltmeConstr.json +++ b/vclogin/__tests__/testdata/policies/acceptEmailFromAltmeConstr.json @@ -6,8 +6,7 @@ "issuer": "did:web:app.altme.io:issuer", "claims": [ { - "claimPath": "$.credentialSubject.email", - "token": "id_token" + "claimPath": "$.credentialSubject.email" } ], "constraint": { diff --git a/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyone.json b/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyone.json index b9d87dd..2c581b8 100644 --- a/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyone.json +++ b/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyone.json @@ -10,12 +10,10 @@ "newPath": "$.companyName" }, { - "claimPath": "$.credentialSubject.name", - "token": "id_token" + "claimPath": "$.credentialSubject.name" }, { - "claimPath": "$.credentialSubject.email", - "token": "id_token" + "claimPath": "$.credentialSubject.email" } ] } diff --git a/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyoneConstr.json b/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyoneConstr.json index f809654..78c9d64 100644 --- a/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyoneConstr.json +++ b/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyoneConstr.json @@ -10,12 +10,10 @@ "newPath": "$.companyName" }, { - "claimPath": "$.credentialSubject.name", - "token": "id_token" + "claimPath": "$.credentialSubject.name" }, { - "claimPath": "$.credentialSubject.email", - "token": "id_token" + "claimPath": "$.credentialSubject.email" } ], "constraint": { diff --git a/vclogin/lib/extractClaims.ts b/vclogin/lib/extractClaims.ts index 0770df3..0eb5c0b 100644 --- a/vclogin/lib/extractClaims.ts +++ b/vclogin/lib/extractClaims.ts @@ -82,7 +82,6 @@ const getPatternClaimFits = (creds: any[], policy: LoginPolicy): any[][] => { const isCredentialFittingPatternList = ( cred: any, - patterns: CredentialPattern[], ): boolean => { for (let pattern of patterns) { @@ -96,7 +95,6 @@ const isCredentialFittingPatternList = ( const isCredentialFittingPattern = ( cred: any, - pattern: CredentialPattern, ): boolean => { if (cred.issuer !== pattern.issuer && pattern.issuer !== "*") { @@ -289,7 +287,7 @@ const extractClaimsFromVC = (VC: any, policy: LoginPolicy) => { if (!newPath) { throw Error( "New path not defined for multi-valued claim: " + - claim.claimPath, + claim.claimPath, ); } @@ -309,9 +307,9 @@ const extractClaimsFromVC = (VC: any, policy: LoginPolicy) => { } const claimTarget = - claim.token === "id_token" - ? extractedClaims.tokenId - : extractedClaims.tokenAccess; + claim.token === "access_token" + ? extractedClaims.tokenAccess + : extractedClaims.tokenId; jp.value(claimTarget, newPath, value); } console.log("Extracted Claims", extractedClaims); From 4184e4ef611107134c681060e41564d6c2d6ad95 Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Mon, 10 Jun 2024 12:45:28 +0200 Subject: [PATCH 34/85] Changed default token Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Signed-off-by: Ilayda Cansin Koc --- vclogin/__tests__/extractClaims.test.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/vclogin/__tests__/extractClaims.test.js b/vclogin/__tests__/extractClaims.test.js index 01094ca..4aae794 100644 --- a/vclogin/__tests__/extractClaims.test.js +++ b/vclogin/__tests__/extractClaims.test.js @@ -15,8 +15,7 @@ describe("extractClaims", () => { it("all subject claims from an EmployeeCredential are extracted", () => { var claims = extractClaims(vpEmployee, policyAcceptAnything); var expected = { - tokenAccess: { - }, + tokenAccess: {}, tokenId: { subjectData: { id: "did:key:z6MkkdC46uhBGjMYS2ZDLUwCrTWdaqZdTD3596sN4397oRNd", @@ -33,7 +32,6 @@ describe("extractClaims", () => { hasJurisdiction: "GER", surname: "Surname", }, - }, }; expect(claims).toStrictEqual(expected); @@ -64,8 +62,7 @@ describe("extractClaims", () => { it("all designated claims from an EmployeeCredential are extracted", () => { var claims = extractClaims(vpEmployee, policyEmployeeFromAnyone); var expected = { - tokenAccess: { - }, + tokenAccess: {}, tokenId: { email: "test@test.com", name: "Name Surname", From 64f7c7a58e52d1496dc6684b6b6a858b8638aa98 Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Mon, 10 Jun 2024 17:29:17 +0200 Subject: [PATCH 35/85] Adopted absolute imports Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Signed-off-by: Ilayda Cansin Koc --- .../{ => lib}/evaluateLoginPolicy.test.js | 0 .../__tests__/{ => lib}/extractClaims.test.js | 0 .../generatePresentationDefinition.test.js | 0 .../{ => lib}/verifyPresentation.test.js | 0 vclogin/__tests__/{ => pages}/index.test.js | 0 vclogin/__tests__/{ => pages}/login.test.js | 0 vclogin/tsconfig.json | 38 ++++++++++++++++--- 7 files changed, 33 insertions(+), 5 deletions(-) rename vclogin/__tests__/{ => lib}/evaluateLoginPolicy.test.js (100%) rename vclogin/__tests__/{ => lib}/extractClaims.test.js (100%) rename vclogin/__tests__/{ => lib}/generatePresentationDefinition.test.js (100%) rename vclogin/__tests__/{ => lib}/verifyPresentation.test.js (100%) rename vclogin/__tests__/{ => pages}/index.test.js (100%) rename vclogin/__tests__/{ => pages}/login.test.js (100%) diff --git a/vclogin/__tests__/evaluateLoginPolicy.test.js b/vclogin/__tests__/lib/evaluateLoginPolicy.test.js similarity index 100% rename from vclogin/__tests__/evaluateLoginPolicy.test.js rename to vclogin/__tests__/lib/evaluateLoginPolicy.test.js diff --git a/vclogin/__tests__/extractClaims.test.js b/vclogin/__tests__/lib/extractClaims.test.js similarity index 100% rename from vclogin/__tests__/extractClaims.test.js rename to vclogin/__tests__/lib/extractClaims.test.js diff --git a/vclogin/__tests__/generatePresentationDefinition.test.js b/vclogin/__tests__/lib/generatePresentationDefinition.test.js similarity index 100% rename from vclogin/__tests__/generatePresentationDefinition.test.js rename to vclogin/__tests__/lib/generatePresentationDefinition.test.js diff --git a/vclogin/__tests__/verifyPresentation.test.js b/vclogin/__tests__/lib/verifyPresentation.test.js similarity index 100% rename from vclogin/__tests__/verifyPresentation.test.js rename to vclogin/__tests__/lib/verifyPresentation.test.js diff --git a/vclogin/__tests__/index.test.js b/vclogin/__tests__/pages/index.test.js similarity index 100% rename from vclogin/__tests__/index.test.js rename to vclogin/__tests__/pages/index.test.js diff --git a/vclogin/__tests__/login.test.js b/vclogin/__tests__/pages/login.test.js similarity index 100% rename from vclogin/__tests__/login.test.js rename to vclogin/__tests__/pages/login.test.js diff --git a/vclogin/tsconfig.json b/vclogin/tsconfig.json index 6ee3994..e5a03ed 100644 --- a/vclogin/tsconfig.json +++ b/vclogin/tsconfig.json @@ -1,7 +1,11 @@ { "compilerOptions": { "target": "es5", - "lib": ["dom", "dom.iterable", "esnext"], + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], "allowJs": true, "skipLibCheck": true, "strict": true, @@ -14,11 +18,35 @@ "isolatedModules": true, "jsx": "preserve", "incremental": true, + "baseUrl": ".", "paths": { - "@/*": ["./*"] + "@/testdata/*": [ + "__tests__/testdata/*" + ], + "@/lib/*": [ + "lib/*" + ], + "@/config/*": [ + "config/*" + ], + "@/types/*": [ + "types/*" + ], + "@/pages/*": [ + "pages/*" + ], }, - "typeRoots": ["./types"] + "typeRoots": [ + "./types" + ] }, - "include": ["next-env.d.ts", "types/**/*.ts", "**/*.ts", "**/*.tsx"], - "exclude": ["node_modules"] + "include": [ + "next-env.d.ts", + "types/**/*.ts", + "**/*.ts", + "**/*.tsx" + ], + "exclude": [ + "node_modules" + ] } From 777d90d5f75b9bfd0ecf76e13a9b9f4e65e99074 Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Tue, 11 Jun 2024 11:55:33 +0200 Subject: [PATCH 36/85] Fix missing styles source Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Signed-off-by: Ilayda Cansin Koc --- .github/workflows/prettier.yml | 2 +- vclogin/tsconfig.json | 42 ++++++++-------------------------- 2 files changed, 11 insertions(+), 33 deletions(-) diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml index b229fa4..1d66581 100644 --- a/.github/workflows/prettier.yml +++ b/.github/workflows/prettier.yml @@ -22,5 +22,5 @@ jobs: uses: creyD/prettier_action@v4.3 with: # This part is also where you can pass other options, for example: - prettier_options: --write **/*.{js,ts,tsx,md} + prettier_options: --write **/*.{js,ts,tsx,md,json} same_commit: true diff --git a/vclogin/tsconfig.json b/vclogin/tsconfig.json index e5a03ed..c9076cb 100644 --- a/vclogin/tsconfig.json +++ b/vclogin/tsconfig.json @@ -1,11 +1,7 @@ { "compilerOptions": { "target": "es5", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], + "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "strict": true, @@ -20,33 +16,15 @@ "incremental": true, "baseUrl": ".", "paths": { - "@/testdata/*": [ - "__tests__/testdata/*" - ], - "@/lib/*": [ - "lib/*" - ], - "@/config/*": [ - "config/*" - ], - "@/types/*": [ - "types/*" - ], - "@/pages/*": [ - "pages/*" - ], + "@/testdata/*": ["__tests__/testdata/*"], + "@/lib/*": ["lib/*"], + "@/config/*": ["config/*"], + "@/types/*": ["types/*"], + "@/pages/*": ["pages/*"], + "@/styles/*": ["styles/*"] }, - "typeRoots": [ - "./types" - ] + "typeRoots": ["./types"] }, - "include": [ - "next-env.d.ts", - "types/**/*.ts", - "**/*.ts", - "**/*.tsx" - ], - "exclude": [ - "node_modules" - ] + "include": ["next-env.d.ts", "types/**/*.ts", "**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] } From 600e2441150ff8d66a4fa606cd7afac33269eaef Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Tue, 11 Jun 2024 12:02:02 +0200 Subject: [PATCH 37/85] Readme disclaimer Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Signed-off-by: Ilayda Cansin Koc --- README.md | 94 +++++++++++++++++++++++++++---------------------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index 8829498..3190a1e 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ > This repository is intended for prototyping and as a reference implementation. At this time, no security guarantees can be given. > [!NOTE] -> A preprint of our paper providing more background information is available [on arXiv](https://arxiv.org/abs/2401.09488). +> A preprint of our paper providing more background information is available [on arXiv](https://arxiv.org/abs/2401.09488). While it is slightly outdated now, it provides a good introduction. > [!NOTE] > This software artifact was originally intended to support only Gaia-X Participant Credentials. It has since evolved to be fully configurable for almost any Verifiable Credential, almost any wallet application, and almost any current OIDC client. @@ -62,55 +62,55 @@ The user's browser starts out on the service website, which takes the role of an ```mermaid sequenceDiagram - autonumber - actor User - participant Browser - participant Client as OIDC Client (Web Server) + autonumber + actor User + participant Browser + participant Client as OIDC Client (Web Server)     participant Wallet as Smartphone Wallet     participant VPLS as vclogin     participant Redis     participant OP as Ory Hydra - User->>Browser: Click "Sign-in" - Browser->>OP: Redirect to /authorize - OP->>VPLS: Redirect to /login?login_challenge= - VPLS->>VPLS: Generate random UUID to replace challenge - VPLS->>Redis: Save (UUID,challenge) and (challenge,UUID) - VPLS->>Browser: Send login page - Browser->>User: Show login page with QR Code - User->>Wallet: Scan QR code containing SIOP Provider Invocation - Wallet->>VPLS: GET /api/presentCredential?login_id= - VPLS->>VPLS: Generate and sign Auth Request JWT - VPLS->>Wallet: Auth Request with Presentation Definition - Wallet->>VPLS: GET /api/clientMetadata - VPLS->>Wallet: Return static Client Metadata - Wallet->>User: Prompt for VC selection and consent - User->>Wallet: Choose VC(s) and confirm - Wallet->>Wallet: Create and sign VP - Wallet->>VPLS: Submit Auth Response via POST /api/presentCredential - VPLS->>VPLS: Verify VP - VPLS->>VPLS: Process claims from VP - VPLS->>Redis: Get challenge using UUID - VPLS->>OP: Confirm sign-in for subject DID using challenge - OP->>VPLS: Client redirect link - VPLS->>Redis: Save (subject DID, claims) - VPLS->>Redis: Save ("redirect" + UUID, redirect) - loop Every few seconds - Browser->>VPLS: Try to retrieve redirect using challenge - Note over Client,Redis: Failed lookups omitted - end - Browser->>VPLS: Get redirect using challenge - VPLS->>Redis: Get UUID using challenge - VPLS->>Redis: Get redirect using UUID - VPLS->>OP: Redirect to Hydra - OP->>VPLS: Redirect to /api/consent?consent_challenge= - VPLS->>OP: Get consent metadata using challenge2 - OP->>VPLS: Metadata including subject DID - VPLS->>Redis: Get claims using subject DID - VPLS->>OP: Confirm consent and send user claims - OP->>Client: Redirect to client callback with code - Client->>OP: Get tokens using code - OP->>Client: Return id_token and access_token - Client->>Browser: Provide access to protected service + User->>Browser: Click "Sign-in" + Browser->>OP: Redirect to /authorize + OP->>VPLS: Redirect to /login?login_challenge= + VPLS->>VPLS: Generate random UUID to replace challenge + VPLS->>Redis: Save (UUID,challenge) and (challenge,UUID) + VPLS->>Browser: Send login page + Browser->>User: Show login page with QR Code + User->>Wallet: Scan QR code containing SIOP Provider Invocation + Wallet->>VPLS: GET /api/presentCredential?login_id= + VPLS->>VPLS: Generate and sign Auth Request JWT + VPLS->>Wallet: Auth Request with Presentation Definition + Wallet->>VPLS: GET /api/clientMetadata + VPLS->>Wallet: Return static Client Metadata + Wallet->>User: Prompt for VC selection and consent + User->>Wallet: Choose VC(s) and confirm + Wallet->>Wallet: Create and sign VP + Wallet->>VPLS: Submit Auth Response via POST /api/presentCredential + VPLS->>VPLS: Verify VP + VPLS->>VPLS: Process claims from VP + VPLS->>Redis: Get challenge using UUID + VPLS->>OP: Confirm sign-in for subject DID using challenge + OP->>VPLS: Client redirect link + VPLS->>Redis: Save (subject DID, claims) + VPLS->>Redis: Save ("redirect" + UUID, redirect) + loop Every few seconds + Browser->>VPLS: Try to retrieve redirect using challenge + Note over Client,Redis: Failed lookups omitted + end + Browser->>VPLS: Get redirect using challenge + VPLS->>Redis: Get UUID using challenge + VPLS->>Redis: Get redirect using UUID + VPLS->>OP: Redirect to Hydra + OP->>VPLS: Redirect to /api/consent?consent_challenge= + VPLS->>OP: Get consent metadata using challenge2 + OP->>VPLS: Metadata including subject DID + VPLS->>Redis: Get claims using subject DID + VPLS->>OP: Confirm consent and send user claims + OP->>Client: Redirect to client callback with code + Client->>OP: Get tokens using code + OP->>Client: Return id_token and access_token + Client->>Browser: Provide access to protected service ``` ## Running a Local Deployment @@ -118,7 +118,7 @@ sequenceDiagram A local deployment is a great way to test the bridge and to use it for prototyping an OIDC client service you are developing. > [!IMPORTANT] -> You need to use a tool like ngrok for testing so your smartphone wallet can access the vclogin backend. However, it can lead to issues with `application/x-www-form-urlencoded` request bodies used in the flow (https://ngrok.com/docs/ngrok-agent/changelog/#changes-in-22). But you can manually replay that request on the ngrok interface, if you run into problems. +> You need to use a tool like ngrok for testing so your smartphone wallet can access the vclogin backend. However, it can lead to issues with `application/x-www-form-urlencoded` request bodies used in the flow (). But you can manually replay that request on the ngrok interface, if you run into problems. 1. `$ ngrok http 5002`, which will set up a randomly generated URL 2. enter the domain for the vclogin service into the env file `/vclogin/.env` with key `EXTERNAL_URL` From da2deb45ddd9ee01b2acf983d4d89cb81c88f3e2 Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Tue, 11 Jun 2024 12:19:23 +0200 Subject: [PATCH 38/85] Less Hydra logs Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Signed-off-by: Ilayda Cansin Koc --- README.md | 12 ++++++++++++ compose.yaml | 5 +++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3190a1e..7f509a3 100644 --- a/README.md +++ b/README.md @@ -193,6 +193,18 @@ _Note: The PEX_DESCRIPTOR_OVERRIDE is optional and provides a way to override th Now you can develop and it will hot-reload. +## Logging Configuration + +### Ory Hydra + +Hydra is set to a minimal log output. To expand log output, edit the hydra service in `compose.yaml`: + +```yaml +- LOG_LEVEL=debug +- LOG_FORMAT=json +- LOG_LEAK_SENSITIVE_VALUES=true +``` + ## Policy Configuration The login policy is the one configuration file that configures the bridge's behavior. The most simple example of one looks like this and accepts any credential, while forwarding all subject fields to the `id_token`: diff --git a/compose.yaml b/compose.yaml index 1590968..25ea305 100644 --- a/compose.yaml +++ b/compose.yaml @@ -35,8 +35,9 @@ services: - SERVE_PUBLIC_CORS_ALLOWED_METHODS=POST,GET,PUT,DELETE - SERVE_ADMIN_CORS_ENABLED=true - SERVE_ADMIN_CORS_ALLOWED_METHODS=POST,GET,PUT,DELETE - - LOG_LEVEL=debug - - LOG_LEAK_SENSITIVE_VALUES=true + - LOG_LEVEL=error + - LOG_FORMAT=json + - LOG_LEAK_SENSITIVE_VALUES=false - OAUTH2_EXPOSE_INTERNAL_ERRORS=1 # - TRACING_PROVIDER=jaeger # - TRACING_PROVIDERS_JAEGER_SAMPLING_SERVER_URL=http://jaeger:5778/sampling From 069d8dddf97f8255a86a1c59c819bf454b582e21 Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Tue, 11 Jun 2024 16:27:42 +0200 Subject: [PATCH 39/85] Logging improvements with pino Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Signed-off-by: Ilayda Cansin Koc --- vclogin/.eslintrc.json | 5 +- vclogin/config/logger.ts | 10 + vclogin/config/redis.ts | 40 ++++ vclogin/lib/verifyPresentation.ts | 64 +++---- vclogin/middleware/logging.ts | 22 +++ vclogin/package-lock.json | 172 +++++++++++++++++ vclogin/package.json | 2 + vclogin/pages/api/clientMetadata.ts | 57 +++++- vclogin/pages/api/consent.ts | 25 +-- vclogin/pages/api/presentCredential.ts | 253 ++++++++++++------------- vclogin/tsconfig.json | 3 +- 11 files changed, 459 insertions(+), 194 deletions(-) create mode 100644 vclogin/config/logger.ts create mode 100644 vclogin/config/redis.ts create mode 100644 vclogin/middleware/logging.ts diff --git a/vclogin/.eslintrc.json b/vclogin/.eslintrc.json index 76a186d..6a27e5d 100644 --- a/vclogin/.eslintrc.json +++ b/vclogin/.eslintrc.json @@ -1,3 +1,6 @@ { - "extends": ["next/core-web-vitals", "plugin:react-hooks/recommended"] + "extends": ["next/core-web-vitals", "plugin:react-hooks/recommended"], + "rules": { + "no-console": "error" + } } diff --git a/vclogin/config/logger.ts b/vclogin/config/logger.ts new file mode 100644 index 0000000..3ac2b8a --- /dev/null +++ b/vclogin/config/logger.ts @@ -0,0 +1,10 @@ +/** + * Copyright 2024 Software Engineering for Business Information Systems (sebis) . + * SPDX-License-Identifier: MIT + */ + +import pino from "pino"; + +export const logger = pino({ + level: process.env.NODE_ENV === "production" ? "info" : "debug", +}); diff --git a/vclogin/config/redis.ts b/vclogin/config/redis.ts new file mode 100644 index 0000000..a3933b0 --- /dev/null +++ b/vclogin/config/redis.ts @@ -0,0 +1,40 @@ +/** + * Copyright 2024 Software Engineering for Business Information Systems (sebis) . + * SPDX-License-Identifier: MIT + */ + +import { Redis, RedisKey, RedisOptions, RedisValue } from "ioredis"; +import { logger } from "@/config/logger"; + +const redisConfig: RedisOptions = { + port: parseInt(process.env.REDIS_PORT ? process.env.REDIS_PORT : "6379", 10), + host: process.env.REDIS_HOST, + lazyConnect: true, + maxRetriesPerRequest: 10, +}; + +let redis: Redis; + +try { + redis = new Redis(redisConfig); +} catch (error) { + logger.error(error, "Critically failed initializing Redis"); +} + +export const redisGet = async (key: RedisKey): Promise => { + let res = null; + try { + res = await redis.get(key); + } catch (error) { + logger.error(error, "Redis repeatedly failed to get value"); + } + return res; +}; + +export const redisSet = ( + key: RedisKey, + value: RedisValue, + seconds: string | number, +) => { + redis.set(key, value, "EX", seconds); +}; diff --git a/vclogin/lib/verifyPresentation.ts b/vclogin/lib/verifyPresentation.ts index 7b37f75..f66d9a6 100644 --- a/vclogin/lib/verifyPresentation.ts +++ b/vclogin/lib/verifyPresentation.ts @@ -7,11 +7,16 @@ import { verifyCredential, verifyPresentation, } from "@spruceid/didkit-wasm-node"; +import { logger } from "@/config/logger"; export const verifyAuthenticationPresentation = async (VP: any) => { try { if (!VP?.verifiableCredential) { - console.error("Unable to detect verifiable credentials in the VP"); + logger.error("Unable to find VCs in VP"); + return false; + } + + if (!(await verifyJustPresentation(VP))) { return false; } @@ -20,52 +25,37 @@ export const verifyAuthenticationPresentation = async (VP: any) => { : [VP.verifiableCredential]; for (const cred of creds) { - if (!(await verifyPresentationHelper(cred, VP))) { + if (!(await verifyJustCredential(cred))) { return false; } } return true; } catch (error) { - console.error(error); + logger.error(error, "Failed during VP verification"); return false; } }; -const verifyPresentationHelper = async (VC: any, VP: any): Promise => { - if ( - VP.holder && - VP.holder === VC.credentialSubject.id && - VP.proof.verificationMethod.split("#")[0] === VP.holder - ) { - // Verify the signature on the VC - const verifyOptionsString = "{}"; - const verifyResult = JSON.parse( - await verifyCredential(JSON.stringify(VC), verifyOptionsString), - ); - // If credential verification is successful, verify the presentation - if (verifyResult?.errors?.length === 0) { - const res = JSON.parse( - await verifyPresentation(JSON.stringify(VP), verifyOptionsString), - ); - // If verification is successful - if (res.errors.length === 0) { - return true; - } else { - const errorMessage = `Unable to verify presentation: ${res.errors.join( - ", ", - )}`; - console.error(errorMessage); - } - } else { - const errorMessage = `Unable to verify credential: ${verifyResult.errors.join( - ", ", - )}`; - console.error(errorMessage); - } +const verifyJustPresentation = async (VP: any): Promise => { + const res = JSON.parse(await verifyPresentation(JSON.stringify(VP), "{}")); + // If verification is successful + if (res.errors.length === 0) { + return true; } else { - const errorMessage = "The credential subject does not match the VP holder."; - console.error(errorMessage); + logger.error({ errors: res.errors }, "Unable to verify VP"); + return false; + } +}; + +const verifyJustCredential = async (VC: any): Promise => { + // Verify the signature on the VC + const res = JSON.parse(await verifyCredential(JSON.stringify(VC), "{}")); + // If verification is successful + if (res?.errors?.length === 0) { + return true; + } else { + logger.error({ errors: res.errors }, "Unable to verify VC"); + return false; } - return false; }; diff --git a/vclogin/middleware/logging.ts b/vclogin/middleware/logging.ts new file mode 100644 index 0000000..81162e5 --- /dev/null +++ b/vclogin/middleware/logging.ts @@ -0,0 +1,22 @@ +/** + * Copyright 2024 Software Engineering for Business Information Systems (sebis) . + * SPDX-License-Identifier: MIT + */ + +import { logger } from "@/config/logger"; +import { NextApiRequest, NextApiResponse } from "next"; +import { pinoHttp } from "pino-http"; + +const loggerHttp = pinoHttp({ logger: logger }); + +export const withLogging = ( + handler: (a: NextApiRequest, b: NextApiResponse) => Promise, +) => { + return async ( + req: NextApiRequest, + res: NextApiResponse, + ): Promise => { + loggerHttp(req, res); + return handler(req, res); + }; +}; diff --git a/vclogin/package-lock.json b/vclogin/package-lock.json index bde43c6..a81d443 100644 --- a/vclogin/package-lock.json +++ b/vclogin/package-lock.json @@ -21,6 +21,8 @@ "next": "13.3.0", "next-connect": "^1.0.0", "next-qrcode": "^2.5.1", + "pino": "^9.1.0", + "pino-http": "^10.1.0", "react": "18.2.0", "react-dom": "18.2.0", "uuid": "^9.0.0" @@ -2625,6 +2627,17 @@ "optional": true, "peer": true }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/acorn": { "version": "8.8.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", @@ -2922,6 +2935,14 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/autoprefixer": { "version": "10.4.14", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz", @@ -4767,6 +4788,22 @@ "node": ">=0.10.0" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -4859,6 +4896,14 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" }, + "node_modules/fast-redact": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", + "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", + "engines": { + "node": ">=6" + } + }, "node_modules/fastq": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", @@ -8015,6 +8060,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -8220,6 +8273,67 @@ "node": ">=0.10.0" } }, + "node_modules/pino": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.1.0.tgz", + "integrity": "sha512-qUcgfrlyOtjwhNLdbhoL7NR4NkHjzykAPw0V2QLFbvu/zss29h4NkRnibyFzBrNCbzCOY3WZ9hhKSwfOkNggYA==", + "dependencies": { + "atomic-sleep": "^1.0.0", + "fast-redact": "^3.1.1", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^1.2.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^3.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz", + "integrity": "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==", + "dependencies": { + "readable-stream": "^4.0.0", + "split2": "^4.0.0" + } + }, + "node_modules/pino-abstract-transport/node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/pino-http": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/pino-http/-/pino-http-10.1.0.tgz", + "integrity": "sha512-rQgRaVfmZnDcOZXvZUUkiG3wDYVTSyYWAhxkGUgw3py3Y1nFXucRSLYPB5HKgG64oy9gLiDARiQxxWXnI1u3zA==", + "dependencies": { + "get-caller-file": "^2.0.5", + "pino": "^9.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^3.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", + "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==" + }, "node_modules/pirates": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", @@ -8470,11 +8584,24 @@ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, + "node_modules/process-warning": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz", + "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==" + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -8577,6 +8704,11 @@ } ] }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" + }, "node_modules/quick-lru": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", @@ -8651,6 +8783,14 @@ "node": ">=8.10.0" } }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "engines": { + "node": ">= 12.13.0" + } + }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -8895,6 +9035,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-stable-stringify": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", + "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", + "engines": { + "node": ">=10" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -9044,6 +9192,14 @@ "node": ">=8" } }, + "node_modules/sonic-boom": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.0.1.tgz", + "integrity": "sha512-hTSD/6JMLyT4r9zeof6UtuBDpjJ9sO08/nmS5djaA9eozT9oOlNdpXSnzcgj4FTqpk3nkLrs61l4gip9r1HCrQ==", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -9071,6 +9227,14 @@ "source-map": "^0.6.0" } }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -9650,6 +9814,14 @@ "node": ">=0.8" } }, + "node_modules/thread-stream": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.0.2.tgz", + "integrity": "sha512-cBL4xF2A3lSINV4rD5tyqnKH4z/TgWPvT+NaVhJDSwK962oo/Ye7cHSMbDzwcu7tAE1SfU6Q4XtV6Hucmi6Hlw==", + "dependencies": { + "real-require": "^0.2.0" + } + }, "node_modules/tiny-glob": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz", diff --git a/vclogin/package.json b/vclogin/package.json index 2d00987..410683e 100644 --- a/vclogin/package.json +++ b/vclogin/package.json @@ -23,6 +23,8 @@ "next": "13.3.0", "next-connect": "^1.0.0", "next-qrcode": "^2.5.1", + "pino": "^9.1.0", + "pino-http": "^10.1.0", "react": "18.2.0", "react-dom": "18.2.0", "uuid": "^9.0.0" diff --git a/vclogin/pages/api/clientMetadata.ts b/vclogin/pages/api/clientMetadata.ts index 6ee2efd..0560b1a 100644 --- a/vclogin/pages/api/clientMetadata.ts +++ b/vclogin/pages/api/clientMetadata.ts @@ -5,18 +5,58 @@ import { getMetadata } from "@/lib/getMetadata"; import type { NextApiRequest, NextApiResponse } from "next"; +import { withLogging } from "@/middleware/logging"; -export default async function handler( - req: NextApiRequest, - res: NextApiResponse, -) { +async function handler(req: NextApiRequest, res: NextApiResponse) { try { const { method } = req; if (method === "GET") { - console.log("METADATA API GET"); - const metadata = getMetadata([ - process.env.NEXT_PUBLIC_INTERNET_URL + "/api/presentCredential", - ]); + const metadata = { + scopes_supported: ["openid"], + response_types_supported: ["id_token", "vp_token"], + response_modes_supported: ["query"], + subject_types_supported: ["public"], + id_token_signing_alg_values_supported: [ + "ES256", + "ES256k", + "EdDSA", + "RS256", + ], + request_object_signing_alg_values_supported: [ + "ES256", + "ES256K", + "EdDSA", + "RS256", + ], + vp_formats: { + jwt_vp: { + alg_values_supported: ["ES256", "ES256K", "EdDSA", "RS256"], + }, + jwt_vc: { + alg_values_supported: ["ES256", "ES256K", "EdDSA", "RS256"], + }, + }, + subject_syntax_types_supported: [ + "did:key", + "did:ebsi", + "did:tz", + "did:pkh", + "did:key", + "did:ethr", + ], + subject_syntax_types_discriminations: [ + "did:key:jwk_jcs-pub", + "did:ebsi:v1", + ], + subject_trust_frameworks_supported: ["ebsi"], + id_token_types_supported: ["subject_signed_id_token"], + client_name: "VP Login Service", + request_uri_parameter_supported: true, + request_parameter_supported: false, + redirect_uris: [ + process.env.NEXT_PUBLIC_INTERNET_URL + "/api/presentCredential", + ], + }; res.status(200).json(metadata); } else { res.status(500).end(); @@ -26,4 +66,5 @@ export default async function handler( } } +export default withLogging(handler); export const config = { api: { bodyParser: false } }; diff --git a/vclogin/pages/api/consent.ts b/vclogin/pages/api/consent.ts index 3fa83da..04e702d 100644 --- a/vclogin/pages/api/consent.ts +++ b/vclogin/pages/api/consent.ts @@ -5,31 +5,21 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { hydraAdmin } from "@/config/ory"; -import { Redis } from "ioredis"; +import { redisGet } from "@/config/redis"; +import { withLogging } from "@/middleware/logging"; -var redis: Redis; -try { - redis = new Redis(parseInt(process.env.REDIS_PORT!), process.env.REDIS_HOST!); -} catch (error) { - console.error("Failed to connect to Redis:", error); -} - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse, -) { +async function handler(req: NextApiRequest, res: NextApiResponse) { try { const { method } = req; if (method === "GET") { - console.log("CONSENT API GET"); - const challenge = req.query["consent_challenge"] as string; - const { data: body } = - await hydraAdmin.adminGetOAuth2ConsentRequest(challenge); + const { data: body } = await hydraAdmin.adminGetOAuth2ConsentRequest( + challenge, + ); // get user identity and fetch user claims from redis - const userClaims = JSON.parse((await redis.get("" + body.subject))!); + const userClaims = JSON.parse((await redisGet("" + body.subject))!); hydraAdmin .adminAcceptOAuth2ConsentRequest(challenge, { @@ -64,4 +54,5 @@ export default async function handler( } } +export default withLogging(handler); export const config = { api: { bodyParser: false } }; diff --git a/vclogin/pages/api/presentCredential.ts b/vclogin/pages/api/presentCredential.ts index b859ec6..6a1e3a2 100644 --- a/vclogin/pages/api/presentCredential.ts +++ b/vclogin/pages/api/presentCredential.ts @@ -6,146 +6,139 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { verifyAuthenticationPresentation } from "@/lib/verifyPresentation"; import { hydraAdmin } from "@/config/ory"; -import { Redis } from "ioredis"; import { isTrustedPresentation, extractClaims } from "@/lib/extractClaims"; import { generatePresentationDefinition } from "@/lib/generatePresentationDefinition"; import { getConfiguredLoginPolicy } from "@/config/loginPolicy"; -import { getToken } from "@/lib/getToken"; +import { withLogging } from "@/middleware/logging"; +import { logger } from "@/config/logger"; +import { redisSet, redisGet } from "@/config/redis"; -var redis: Redis; -try { - redis = new Redis(parseInt(process.env.REDIS_PORT!), process.env.REDIS_HOST!); -} catch (error) { - console.error("Failed to connect to Redis:", error); -} - -const getHandler = async (req: NextApiRequest, res: NextApiResponse) => { - console.log("LOGIN API GET"); - const presentation_definition = generatePresentationDefinition( - await getConfiguredLoginPolicy()!, - ); - - const challenge = req.query["login_id"]; - - if (challenge) { - const token = await getToken( - challenge as string, - process.env.EXTERNAL_URL + "/api/clientMetadata", - process.env.EXTERNAL_URL + "/api/presentCredential", - presentation_definition, - res, - ); - console.log("Token: \n", token); - - res - .status(200) - .appendHeader("Content-Type", "application/oauth-authz-req+jwt") - .send(token); - } else { - res.status(500).end(); - } -}; - -const postHandler = async (req: NextApiRequest, res: NextApiResponse) => { - console.log("LOGIN API POST"); - - // Parse the JSON string into a JavaScript object - const presentation = JSON.parse(req.body.vp_token); - console.log("Presentation: \n", req.body.vp_token); - - // Verify the presentation and the status of the credential - if (await verifyAuthenticationPresentation(presentation)) { - // Evaluate if the VP should be trusted - if (await isTrustedPresentation(presentation)) { - console.log("Presentation verified"); - } else { - console.log("Presentation not trusted"); - res.status(500).end(); - return; - } - } else { - console.log("Presentation invalid"); - res.status(500).end(); - return; - } - - // Get the user claims - const userClaims = await extractClaims(presentation); - const subject = presentation["holder"]; - const login_id = presentation["proof"]["challenge"]; - const challenge = (await redis.get("" + login_id))!; - console.log("Logging in: " + subject + " with challenge: " + challenge); - - // hydra login - await hydraAdmin - .adminGetOAuth2LoginRequest(challenge) - .then(({ data: loginRequest }) => - hydraAdmin - .adminAcceptOAuth2LoginRequest(challenge, { - // Subject is an alias for user ID. A subject can be a random string, a UUID, an email address, .... - subject, - // This tells hydra to remember the browser and automatically authenticate the user in future requests. This will - // set the "skip" parameter in the other route to true on subsequent requests! - remember: Boolean(false), - // When the session expires, in seconds. Set this to 0 so it will never expire. - remember_for: 3600, - // Sets which "level" (e.g. 2-factor authentication) of authentication the user has. The value is really arbitrary - // and optional. In the context of OpenID Connect, a value of 0 indicates the lowest authorization level. - // acr: '0', - // - // If the environment variable CONFORMITY_FAKE_CLAIMS is set we are assuming that - // the app is built for the automated OpenID Connect Conformity Test Suite. You - // can peak inside the code for some ideas, but be aware that all data is fake - // and this only exists to fake a login system which works in accordance to OpenID Connect. - // - // If that variable is not set, the ACR value will be set to the default passed here ('0') - acr: "0", - }) - .then(({ data: body }) => { - const MAX_AGE = 30; // 30 seconds - const EXPIRY_MS = "EX"; // seconds - - // save the user claims to redis - redis.set( - "" + subject, - JSON.stringify(userClaims), - EXPIRY_MS, - MAX_AGE, - ); - - // save the redirect address to redis for the browser - redis.set( - "redirect" + login_id, - String(body.redirect_to), - EXPIRY_MS, - MAX_AGE, - ); - // phone just gets a 200 ok - res.status(200).end(); - }), - ) - // This will handle any error that happens when making HTTP calls to hydra - .catch((_) => res.status(401).end()); -}; - -const handlers: any = { - GET: getHandler, - POST: postHandler, -}; - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse, -) { +async function handler(req: NextApiRequest, res: NextApiResponse) { try { const { method } = req; - if (method) { - const execute = handlers[method.toUpperCase()]; - return await execute(req, res); + if (method === "GET") { + const presentation_definition = generatePresentationDefinition( + getConfiguredLoginPolicy()!, + ); + const did = keyToDID("key", process.env.DID_KEY_JWK!); + const verificationMethod = await keyToVerificationMethod( + "key", + process.env.DID_KEY_JWK!, + ); + const challenge = req.query["login_id"]; + const payload = { + client_id: did, + client_id_scheme: "did", + client_metadata_uri: process.env.EXTERNAL_URL + "/api/clientMetadata", + nonce: challenge, + presentation_definition, + response_mode: "direct_post", + response_type: "vp_token", + response_uri: process.env.EXTERNAL_URL + "/api/presentCredential", + state: challenge, + }; + const privateKey = await jose.importJWK( + JSON.parse(process.env.DID_KEY_JWK!), + "EdDSA", + ); + const token = await new jose.SignJWT(payload) + .setProtectedHeader({ + alg: "EdDSA", + kid: verificationMethod, + typ: "JWT", + }) + .setIssuedAt() + .setIssuer(did) + .setAudience("https://self-issued.me/v2") // by definition + .setExpirationTime("1 hour") + .sign(privateKey) + .catch((err) => { + logger.error(err, "Failed signing presentation definition token"); + res.status(500).end(); + }); + res + .status(200) + .appendHeader("Content-Type", "application/oauth-authz-req+jwt") + .send(token); + } else if (method === "POST") { + // Parse the JSON string into a JavaScript object + const presentation = JSON.parse(req.body.vp_token); + logger.debug(req.body.vp_token, "Verifiable Presentaiton was sent"); + + // Verify the presentation and the status of the credential + if (await verifyAuthenticationPresentation(presentation)) { + // Evaluate if the VP should be trusted + if (isTrustedPresentation(presentation)) { + logger.debug("Verifiable Presentation verified"); + } else { + logger.debug("Verifiable Presentation not trusted"); + res.status(500).end(); + return; + } + } else { + logger.debug("Verifiable Presentation not valid"); + res.status(500).end(); + return; + } + + // Get the user claims + const userClaims = extractClaims(presentation); + const subject = presentation["holder"]; + const login_id = presentation["proof"]["challenge"]; + const challenge = (await redisGet("" + login_id))!; + logger.debug({ subject, challenge }, "Sign-in confirmed"); + + // hydra login + await hydraAdmin + .adminGetOAuth2LoginRequest(challenge) + .then(({}) => + hydraAdmin + .adminAcceptOAuth2LoginRequest(challenge, { + // Subject is an alias for user ID. A subject can be a random string, a UUID, an email address, .... + subject, + // This tells hydra to remember the browser and automatically authenticate the user in future requests. This will + // set the "skip" parameter in the other route to true on subsequent requests! + remember: Boolean(false), + // When the session expires, in seconds. Set this to 0 so it will never expire. + remember_for: 3600, + // Sets which "level" (e.g. 2-factor authentication) of authentication the user has. The value is really arbitrary + // and optional. In the context of OpenID Connect, a value of 0 indicates the lowest authorization level. + // acr: '0', + // + // If the environment variable CONFORMITY_FAKE_CLAIMS is set we are assuming that + // the app is built for the automated OpenID Connect Conformity Test Suite. You + // can peak inside the code for some ideas, but be aware that all data is fake + // and this only exists to fake a login system which works in accordance to OpenID Connect. + // + // If that variable is not set, the ACR value will be set to the default passed here ('0') + acr: "0", + }) + .then(({ data: body }) => { + const MAX_AGE = 30; // 30 seconds + + // save the user claims to redis + redisSet("" + subject, JSON.stringify(userClaims), MAX_AGE); + + // save the redirect address to redis for the browser + redisSet( + "redirect" + login_id, + String(body.redirect_to), + MAX_AGE, + ); + + // phone just gets a 200 ok + res.status(200).end(); + }), + ) + // This will handle any error that happens when making HTTP calls to hydra + .catch((_) => res.status(401).end()); + } else { + res.status(500).end(); } } catch (e) { res.status(500).end(); } } +export default withLogging(handler); export const config = { api: { bodyParser: true } }; diff --git a/vclogin/tsconfig.json b/vclogin/tsconfig.json index c9076cb..c8a8bfc 100644 --- a/vclogin/tsconfig.json +++ b/vclogin/tsconfig.json @@ -21,7 +21,8 @@ "@/config/*": ["config/*"], "@/types/*": ["types/*"], "@/pages/*": ["pages/*"], - "@/styles/*": ["styles/*"] + "@/styles/*": ["styles/*"], + "@/middleware/*": ["middleware/*"] }, "typeRoots": ["./types"] }, From 4830bca5f9225a7b770ff66d43094cde27d59ebc Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Tue, 11 Jun 2024 16:41:26 +0200 Subject: [PATCH 40/85] Text fixes Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Signed-off-by: Ilayda Cansin Koc --- README.md | 4 ++++ vclogin/pages/index.tsx | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7f509a3..2ee06a7 100644 --- a/README.md +++ b/README.md @@ -195,6 +195,10 @@ Now you can develop and it will hot-reload. ## Logging Configuration +### vclogin + +The vclogin server uses the `pino` library for logging. Due to the peculiarities of NextJS, http events are only logged for API routes. + ### Ory Hydra Hydra is set to a minimal log output. To expand log output, edit the hydra service in `compose.yaml`: diff --git a/vclogin/pages/index.tsx b/vclogin/pages/index.tsx index 57f97be..547f6c2 100644 --- a/vclogin/pages/index.tsx +++ b/vclogin/pages/index.tsx @@ -26,11 +26,11 @@ export default function Home() { id="gx-text" className="text-6xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-blue-500 to-purple-600 transition duration-500 ease-in-out transform -translate-y-full" > - GX Credentials Bridge + SSI-to-OIDC Bridge
- This page supports VC-based logins. + This application supports VC-based logins for other services.
From 77cd47414ed4926febe58963d481a80ddceb45bc Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Tue, 11 Jun 2024 16:41:26 +0200 Subject: [PATCH 41/85] Text fixes Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Signed-off-by: Ilayda Cansin Koc --- vclogin/pages/api/consent.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/vclogin/pages/api/consent.ts b/vclogin/pages/api/consent.ts index 04e702d..400cba6 100644 --- a/vclogin/pages/api/consent.ts +++ b/vclogin/pages/api/consent.ts @@ -14,9 +14,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { if (method === "GET") { const challenge = req.query["consent_challenge"] as string; - const { data: body } = await hydraAdmin.adminGetOAuth2ConsentRequest( - challenge, - ); + const { data: body } = + await hydraAdmin.adminGetOAuth2ConsentRequest(challenge); // get user identity and fetch user claims from redis const userClaims = JSON.parse((await redisGet("" + body.subject))!); From 3a34b2dd4c77d46304b489a4762f4c1bb2caa7d4 Mon Sep 17 00:00:00 2001 From: Ilayda Cansin Koc Date: Tue, 11 Jun 2024 19:13:06 +0000 Subject: [PATCH 42/85] chore: Update dependencies and improve logging - Update npm dependencies to the latest stable version - Implement logging improvements using pino - Replace console.error with logger.error for error handling - Remove unnecessary console.log statements Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Signed-off-by: Ilayda Cansin Koc --- package-lock.json | 6 ++++++ vclogin/config/loginPolicy.ts | 3 ++- vclogin/lib/extractClaims.ts | 15 ++++++------- vclogin/lib/generatePresentationDefinition.ts | 3 ++- vclogin/lib/getToken.ts | 3 ++- .../pages/api/dynamic/clientMetadataById.ts | 3 ++- .../api/dynamic/createTempAuthorization.ts | 3 ++- vclogin/pages/api/dynamic/getAuthResponse.ts | 5 +++-- vclogin/pages/api/dynamic/getQRCodeString.ts | 3 ++- .../api/dynamic/presentCredentialById.ts | 21 ++++++++++--------- vclogin/pages/api/presentCredential.ts | 6 ++++-- vclogin/pages/login.tsx | 3 ++- 12 files changed, 46 insertions(+), 28 deletions(-) create mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..35d2e52 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "ssi-to-oidc-bridge", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/vclogin/config/loginPolicy.ts b/vclogin/config/loginPolicy.ts index 6ad339f..7140d2b 100644 --- a/vclogin/config/loginPolicy.ts +++ b/vclogin/config/loginPolicy.ts @@ -4,13 +4,14 @@ */ import { promises as fs } from "fs"; +import { logger } from "@/config/logger"; export const getConfiguredLoginPolicy = async () => { try { const file = await fs.readFile(process.env.LOGIN_POLICY as string, "utf8"); return JSON.parse(file); } catch (error) { - console.error("Failed to read login policy:", error); + logger.error("Failed to read login policy:", error); return undefined; } }; diff --git a/vclogin/lib/extractClaims.ts b/vclogin/lib/extractClaims.ts index 0eb5c0b..1247a85 100644 --- a/vclogin/lib/extractClaims.ts +++ b/vclogin/lib/extractClaims.ts @@ -11,6 +11,7 @@ import { } from "@/types/LoginPolicy"; import jp from "jsonpath"; import { getConfiguredLoginPolicy } from "@/config/loginPolicy"; +import { logger } from "@/config/logger"; export const isTrustedPresentation = async (VP: any, policy?: LoginPolicy) => { var configuredPolicy = await getConfiguredLoginPolicy(); @@ -31,18 +32,18 @@ export const extractClaims = async (VP: any, policy?: LoginPolicy) => { var usedPolicy = policy ? policy : configuredPolicy!; - console.log("Used Policy", usedPolicy); + logger.info("Used Policy", usedPolicy); const creds = Array.isArray(VP.verifiableCredential) ? VP.verifiableCredential : [VP.verifiableCredential]; - console.log("Credentials", creds); + logger.info("Credentials", creds); const vcClaims = creds.map((vc: any) => extractClaimsFromVC(vc, usedPolicy)); - console.log("Extracted VC Claims", vcClaims); + logger.info("Extracted VC Claims", vcClaims); const claims = vcClaims.reduce( (acc: any, vc: any) => Object.assign(acc, vc), {}, ); - console.log("Extracted Claims", claims); + logger.info("Extracted Claims", claims); return claims; }; @@ -258,7 +259,7 @@ const resolveValue = ( const extractClaimsFromVC = (VC: any, policy: LoginPolicy) => { for (let expectation of policy) { - console.log("Expectation", expectation); + logger.info("Expectation", expectation); for (let pattern of expectation.patterns) { if (pattern.issuer === VC.issuer || pattern.issuer === "*") { const containsAllRequired = @@ -287,7 +288,7 @@ const extractClaimsFromVC = (VC: any, policy: LoginPolicy) => { if (!newPath) { throw Error( "New path not defined for multi-valued claim: " + - claim.claimPath, + claim.claimPath, ); } @@ -312,7 +313,7 @@ const extractClaimsFromVC = (VC: any, policy: LoginPolicy) => { : extractedClaims.tokenId; jp.value(claimTarget, newPath, value); } - console.log("Extracted Claims", extractedClaims); + logger.info("Extracted Claims", extractedClaims); return extractedClaims; } } diff --git a/vclogin/lib/generatePresentationDefinition.ts b/vclogin/lib/generatePresentationDefinition.ts index 623db22..b9dd8a2 100644 --- a/vclogin/lib/generatePresentationDefinition.ts +++ b/vclogin/lib/generatePresentationDefinition.ts @@ -7,6 +7,7 @@ import { InputDescriptor, InputDescriptors } from "@/types/InputDescriptor"; import { LoginPolicy } from "@/types/LoginPolicy"; import { PresentationDefinition } from "@/types/PresentationDefinition"; import { promises as fs } from "fs"; +import { logger } from "@/config/logger"; var inputDescriptorOverride: any = undefined; if (process.env.PEX_DESCRIPTOR_OVERRIDE) { @@ -56,7 +57,7 @@ export const generatePresentationDefinition = ( return pd; } else if (incrAuthInputDescriptor) { pd.input_descriptors = incrAuthInputDescriptor; - console.log( + logger.info( "Using input descriptor override for incremental authorization", pd, ); diff --git a/vclogin/lib/getToken.ts b/vclogin/lib/getToken.ts index 43bb81d..75993bd 100644 --- a/vclogin/lib/getToken.ts +++ b/vclogin/lib/getToken.ts @@ -2,6 +2,7 @@ import { PresentationDefinition } from "@/types/PresentationDefinition"; import { keyToDID, keyToVerificationMethod } from "@spruceid/didkit-wasm-node"; import * as jose from "jose"; import { NextApiResponse } from "next/types"; +import { logger } from "@/config/logger"; /** * @@ -50,7 +51,7 @@ export const getToken = async ( .setExpirationTime("1 hour") .sign(privateKey) .catch((err) => { - console.log(err); + logger.error(err); res.status(500).end(); }); return token; diff --git a/vclogin/pages/api/dynamic/clientMetadataById.ts b/vclogin/pages/api/dynamic/clientMetadataById.ts index de545c0..26ebba3 100644 --- a/vclogin/pages/api/dynamic/clientMetadataById.ts +++ b/vclogin/pages/api/dynamic/clientMetadataById.ts @@ -5,6 +5,7 @@ import { getMetadata } from "@/lib/getMetadata"; import type { NextApiRequest, NextApiResponse } from "next"; +import { logger } from "@/config/logger"; export default async function handler( req: NextApiRequest, @@ -13,7 +14,7 @@ export default async function handler( try { const { method } = req; if (method === "GET") { - console.log("METADATA BY ID API GET"); + logger.info("METADATA BY ID API GET"); const metadata = getMetadata([ process.env.NEXT_PUBLIC_INTERNET_URL + "/api/dynamic/presentCredentialById", diff --git a/vclogin/pages/api/dynamic/createTempAuthorization.ts b/vclogin/pages/api/dynamic/createTempAuthorization.ts index 085ab6b..2441f1b 100644 --- a/vclogin/pages/api/dynamic/createTempAuthorization.ts +++ b/vclogin/pages/api/dynamic/createTempAuthorization.ts @@ -1,12 +1,13 @@ import { NextApiRequest, NextApiResponse } from "next"; import { Redis } from "ioredis"; import crypto from "crypto"; +import { logger } from "@/config/logger"; var redis: Redis; try { redis = new Redis(parseInt(process.env.REDIS_PORT!), process.env.REDIS_HOST!); } catch (error) { - console.error("Failed to connect to Redis:", error); + logger.error("Failed to connect to Redis:", error); } export default async function handler( diff --git a/vclogin/pages/api/dynamic/getAuthResponse.ts b/vclogin/pages/api/dynamic/getAuthResponse.ts index 97629bc..1520f07 100644 --- a/vclogin/pages/api/dynamic/getAuthResponse.ts +++ b/vclogin/pages/api/dynamic/getAuthResponse.ts @@ -1,11 +1,12 @@ import { NextApiRequest, NextApiResponse } from "next"; import { Redis } from "ioredis"; +import { logger } from "@/config/logger"; var redis: Redis; try { redis = new Redis(parseInt(process.env.REDIS_PORT!), process.env.REDIS_HOST!); } catch (error) { - console.error("Failed to connect to Redis:", error); + logger.error("Failed to connect to Redis:", error); } export default async function handler( @@ -14,7 +15,7 @@ export default async function handler( ) { //read uuid from query params const uuid = req.query["uuid"]; - console.log("uuid: ", uuid); + logger.info("uuid: ", uuid); // Read auth_res from redis and check if it matches the uuid diff --git a/vclogin/pages/api/dynamic/getQRCodeString.ts b/vclogin/pages/api/dynamic/getQRCodeString.ts index 617335e..eff5809 100644 --- a/vclogin/pages/api/dynamic/getQRCodeString.ts +++ b/vclogin/pages/api/dynamic/getQRCodeString.ts @@ -1,11 +1,12 @@ import { NextApiRequest, NextApiResponse } from "next"; import { Redis } from "ioredis"; +import { logger } from "@/config/logger"; var redis: Redis; try { redis = new Redis(parseInt(process.env.REDIS_PORT!), process.env.REDIS_HOST!); } catch (error) { - console.error("Failed to connect to Redis:", error); + logger.error("Failed to connect to Redis:", error); } export default async function handler( diff --git a/vclogin/pages/api/dynamic/presentCredentialById.ts b/vclogin/pages/api/dynamic/presentCredentialById.ts index d1ee97f..fc99bb5 100644 --- a/vclogin/pages/api/dynamic/presentCredentialById.ts +++ b/vclogin/pages/api/dynamic/presentCredentialById.ts @@ -5,16 +5,17 @@ import { LoginPolicy } from "@/types/LoginPolicy"; import { extractClaims, isTrustedPresentation } from "@/lib/extractClaims"; import { verifyAuthenticationPresentation } from "@/lib/verifyPresentation"; import { getToken } from "@/lib/getToken"; +import { logger } from "@/config/logger"; var redis: Redis; try { redis = new Redis(parseInt(process.env.REDIS_PORT!), process.env.REDIS_HOST!); } catch (error) { - console.error("Failed to connect to Redis:", error); + logger.error("Failed to connect to Redis:", error); } const getHandler = async (req: NextApiRequest, res: NextApiResponse) => { - console.log("LOGIN API GET BY ID"); + logger.info("LOGIN API GET BY ID"); // Get login_id from query const uuid = req.query["login_id"]; @@ -24,7 +25,7 @@ const getHandler = async (req: NextApiRequest, res: NextApiResponse) => { // fetch inputDescriptor from redis using uuid const inputDescriptor = await redis.get(uuid + "_inputDescriptor"); - console.log("inputDescriptor: ", JSON.parse(inputDescriptor!)); + logger.info("inputDescriptor: ", JSON.parse(inputDescriptor!)); //if policy is found if (policy) { @@ -60,11 +61,11 @@ const getHandler = async (req: NextApiRequest, res: NextApiResponse) => { }; const postHandler = async (req: NextApiRequest, res: NextApiResponse) => { - console.log("LOGIN API POST BY ID"); + logger.info("LOGIN API POST BY ID"); // Parse the JSON string into a JavaScript object const presentation = JSON.parse(req.body.vp_token); - console.log("Presentation: \n", req.body.vp_token); + logger.info("Presentation: \n", req.body.vp_token); const uuid = presentation["proof"]["challenge"]; const policy = await redis.get(uuid + "_policy"); @@ -78,14 +79,14 @@ const postHandler = async (req: NextApiRequest, res: NextApiResponse) => { // Verify the presentation and the status of the credential if (await verifyAuthenticationPresentation(presentation)) { - console.log("Presentation valid"); + logger.info("Presentation valid"); // Evaluate if the VP should be trusted if (await isTrustedPresentation(presentation, policyObject)) { - console.log("Presentation verified"); + logger.info("Presentation verified"); // Get the user claims when the presentation is trusted const userClaims = await extractClaims(presentation, policyObject); - console.log(userClaims); + logger.info(userClaims); // Store the authentication result in Redis await redis.set(uuid + "_auth-res", "success", EXPIRY_MS, MAX_AGE); @@ -98,7 +99,7 @@ const postHandler = async (req: NextApiRequest, res: NextApiResponse) => { MAX_AGE, ); } else { - console.log("Presentation not trusted"); + logger.info("Presentation not trusted"); await redis.set( "auth_res:" + uuid, @@ -111,7 +112,7 @@ const postHandler = async (req: NextApiRequest, res: NextApiResponse) => { return; } } else { - console.log("Presentation invalid"); + logger.info("Presentation invalid"); await redis.set( "auth_res:" + uuid, "error_invalid_presentation", diff --git a/vclogin/pages/api/presentCredential.ts b/vclogin/pages/api/presentCredential.ts index 6a1e3a2..2a1b4e4 100644 --- a/vclogin/pages/api/presentCredential.ts +++ b/vclogin/pages/api/presentCredential.ts @@ -12,13 +12,15 @@ import { getConfiguredLoginPolicy } from "@/config/loginPolicy"; import { withLogging } from "@/middleware/logging"; import { logger } from "@/config/logger"; import { redisSet, redisGet } from "@/config/redis"; +import { keyToDID, keyToVerificationMethod } from "@spruceid/didkit-wasm-node"; +import * as jose from "jose"; async function handler(req: NextApiRequest, res: NextApiResponse) { try { const { method } = req; if (method === "GET") { const presentation_definition = generatePresentationDefinition( - getConfiguredLoginPolicy()!, + await getConfiguredLoginPolicy()!, ); const did = keyToDID("key", process.env.DID_KEY_JWK!); const verificationMethod = await keyToVerificationMethod( @@ -68,7 +70,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { // Verify the presentation and the status of the credential if (await verifyAuthenticationPresentation(presentation)) { // Evaluate if the VP should be trusted - if (isTrustedPresentation(presentation)) { + if (await isTrustedPresentation(presentation)) { logger.debug("Verifiable Presentation verified"); } else { logger.debug("Verifiable Presentation not trusted"); diff --git a/vclogin/pages/login.tsx b/vclogin/pages/login.tsx index 6bbdb35..de93256 100644 --- a/vclogin/pages/login.tsx +++ b/vclogin/pages/login.tsx @@ -9,6 +9,7 @@ import { useRouter } from "next/router"; import { useQRCode } from "next-qrcode"; import { useEffect } from "react"; import { keyToDID } from "@spruceid/didkit-wasm-node"; +import { logger } from "@/config/logger"; export default function Login(props: any) { const router = useRouter(); @@ -110,7 +111,7 @@ export async function getServerSideProps(context: NextPageContext) { } const did = await keyToDID("key", process.env.DID_KEY_JWK!); - console.log("DID: " + did); + logger.info("DID: " + did); return { props: { loginId, externalUrl: process.env.EXTERNAL_URL, clientId: did }, From 41a9c187f1a4beaba19575591a67f7222f41c9e4 Mon Sep 17 00:00:00 2001 From: Ilayda Cansin Koc Date: Sun, 9 Jun 2024 17:25:37 +0000 Subject: [PATCH 43/85] revert: delete unused API endpoints for authentication response and test requests Signed-off-by: Ilayda Cansin Koc --- vclogin/pages/api/test.ts | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 vclogin/pages/api/test.ts diff --git a/vclogin/pages/api/test.ts b/vclogin/pages/api/test.ts deleted file mode 100644 index 2deea60..0000000 --- a/vclogin/pages/api/test.ts +++ /dev/null @@ -1,10 +0,0 @@ -//generate me a simple json returning api - -import { NextApiRequest, NextApiResponse } from "next"; - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse, -) { - return res.status(200).json({ message: "Hello World" }); -} From 1dc3cdbd2c2541b2b40b17d0c163afb14207b01f Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Mon, 10 Jun 2024 12:45:28 +0200 Subject: [PATCH 44/85] Changed default token Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Signed-off-by: Ilayda Cansin Koc --- vclogin/lib/extractClaims.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vclogin/lib/extractClaims.ts b/vclogin/lib/extractClaims.ts index 1247a85..611b351 100644 --- a/vclogin/lib/extractClaims.ts +++ b/vclogin/lib/extractClaims.ts @@ -288,7 +288,7 @@ const extractClaimsFromVC = (VC: any, policy: LoginPolicy) => { if (!newPath) { throw Error( "New path not defined for multi-valued claim: " + - claim.claimPath, + claim.claimPath, ); } From ee00bf53c25def2862670833e6903d25eb8ead1d Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Mon, 10 Jun 2024 17:29:17 +0200 Subject: [PATCH 45/85] Adopted absolute imports Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Signed-off-by: Ilayda Cansin Koc --- vclogin/tsconfig.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/vclogin/tsconfig.json b/vclogin/tsconfig.json index c8a8bfc..7e05946 100644 --- a/vclogin/tsconfig.json +++ b/vclogin/tsconfig.json @@ -15,14 +15,13 @@ "jsx": "preserve", "incremental": true, "baseUrl": ".", + "baseUrl": ".", "paths": { "@/testdata/*": ["__tests__/testdata/*"], "@/lib/*": ["lib/*"], "@/config/*": ["config/*"], "@/types/*": ["types/*"], - "@/pages/*": ["pages/*"], - "@/styles/*": ["styles/*"], - "@/middleware/*": ["middleware/*"] + "@/pages/*": ["pages/*"] }, "typeRoots": ["./types"] }, From 5947bce79bf263eddc8d70ce90bb56b3f97a7582 Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Mon, 10 Jun 2024 17:29:17 +0200 Subject: [PATCH 46/85] Adopted absolute imports Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Signed-off-by: Ilayda Cansin Koc --- vclogin/tsconfig.json | 1 - 1 file changed, 1 deletion(-) diff --git a/vclogin/tsconfig.json b/vclogin/tsconfig.json index 7e05946..d274a0e 100644 --- a/vclogin/tsconfig.json +++ b/vclogin/tsconfig.json @@ -15,7 +15,6 @@ "jsx": "preserve", "incremental": true, "baseUrl": ".", - "baseUrl": ".", "paths": { "@/testdata/*": ["__tests__/testdata/*"], "@/lib/*": ["lib/*"], From 26f7282cd7be82927343076303e5f8761b473e7b Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Tue, 11 Jun 2024 11:55:33 +0200 Subject: [PATCH 47/85] Fix missing styles source Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Signed-off-by: Ilayda Cansin Koc --- vclogin/tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vclogin/tsconfig.json b/vclogin/tsconfig.json index d274a0e..c9076cb 100644 --- a/vclogin/tsconfig.json +++ b/vclogin/tsconfig.json @@ -20,7 +20,8 @@ "@/lib/*": ["lib/*"], "@/config/*": ["config/*"], "@/types/*": ["types/*"], - "@/pages/*": ["pages/*"] + "@/pages/*": ["pages/*"], + "@/styles/*": ["styles/*"] }, "typeRoots": ["./types"] }, From 65b9fc462a8a1c555c12aa4268cc80ed032c6ed6 Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Tue, 11 Jun 2024 12:19:23 +0200 Subject: [PATCH 48/85] Less Hydra logs Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Signed-off-by: Ilayda Cansin Koc --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index 2ee06a7..7f509a3 100644 --- a/README.md +++ b/README.md @@ -195,10 +195,6 @@ Now you can develop and it will hot-reload. ## Logging Configuration -### vclogin - -The vclogin server uses the `pino` library for logging. Due to the peculiarities of NextJS, http events are only logged for API routes. - ### Ory Hydra Hydra is set to a minimal log output. To expand log output, edit the hydra service in `compose.yaml`: From a8451bd9ce23bcf7a8da1921d27963f952f1424c Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Tue, 11 Jun 2024 16:27:42 +0200 Subject: [PATCH 49/85] Logging improvements with pino Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Signed-off-by: Ilayda Cansin Koc --- vclogin/pages/api/consent.ts | 5 +++-- vclogin/tsconfig.json | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/vclogin/pages/api/consent.ts b/vclogin/pages/api/consent.ts index 400cba6..04e702d 100644 --- a/vclogin/pages/api/consent.ts +++ b/vclogin/pages/api/consent.ts @@ -14,8 +14,9 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { if (method === "GET") { const challenge = req.query["consent_challenge"] as string; - const { data: body } = - await hydraAdmin.adminGetOAuth2ConsentRequest(challenge); + const { data: body } = await hydraAdmin.adminGetOAuth2ConsentRequest( + challenge, + ); // get user identity and fetch user claims from redis const userClaims = JSON.parse((await redisGet("" + body.subject))!); diff --git a/vclogin/tsconfig.json b/vclogin/tsconfig.json index c9076cb..c8a8bfc 100644 --- a/vclogin/tsconfig.json +++ b/vclogin/tsconfig.json @@ -21,7 +21,8 @@ "@/config/*": ["config/*"], "@/types/*": ["types/*"], "@/pages/*": ["pages/*"], - "@/styles/*": ["styles/*"] + "@/styles/*": ["styles/*"], + "@/middleware/*": ["middleware/*"] }, "typeRoots": ["./types"] }, From aa739106bd01efa669c28e17ac8d70366e1fc6c7 Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Tue, 11 Jun 2024 16:41:26 +0200 Subject: [PATCH 50/85] Text fixes Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Signed-off-by: Ilayda Cansin Koc --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 7f509a3..2ee06a7 100644 --- a/README.md +++ b/README.md @@ -195,6 +195,10 @@ Now you can develop and it will hot-reload. ## Logging Configuration +### vclogin + +The vclogin server uses the `pino` library for logging. Due to the peculiarities of NextJS, http events are only logged for API routes. + ### Ory Hydra Hydra is set to a minimal log output. To expand log output, edit the hydra service in `compose.yaml`: From 4b08dcbffb47ea0e82818dcb15133e212a66d825 Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Tue, 11 Jun 2024 16:41:26 +0200 Subject: [PATCH 51/85] Text fixes Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Signed-off-by: Ilayda Cansin Koc --- vclogin/pages/api/consent.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/vclogin/pages/api/consent.ts b/vclogin/pages/api/consent.ts index 04e702d..400cba6 100644 --- a/vclogin/pages/api/consent.ts +++ b/vclogin/pages/api/consent.ts @@ -14,9 +14,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { if (method === "GET") { const challenge = req.query["consent_challenge"] as string; - const { data: body } = await hydraAdmin.adminGetOAuth2ConsentRequest( - challenge, - ); + const { data: body } = + await hydraAdmin.adminGetOAuth2ConsentRequest(challenge); // get user identity and fetch user claims from redis const userClaims = JSON.parse((await redisGet("" + body.subject))!); From d8b544d563525e0f74513b80dcd59aeea2a02f10 Mon Sep 17 00:00:00 2001 From: Ilayda Cansin Koc Date: Tue, 11 Jun 2024 19:28:17 +0000 Subject: [PATCH 52/85] test for dmo Signed-off-by: Ilayda Cansin Koc --- vclogin/pages/login.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vclogin/pages/login.tsx b/vclogin/pages/login.tsx index de93256..8ac7647 100644 --- a/vclogin/pages/login.tsx +++ b/vclogin/pages/login.tsx @@ -111,7 +111,7 @@ export async function getServerSideProps(context: NextPageContext) { } const did = await keyToDID("key", process.env.DID_KEY_JWK!); - logger.info("DID: " + did); + logger.debug("DID: " + did); return { props: { loginId, externalUrl: process.env.EXTERNAL_URL, clientId: did }, From 3b4ddd079c2c01d29ab3d2d56e1da412f0dbf358 Mon Sep 17 00:00:00 2001 From: Ilayda Cansin Koc Date: Tue, 11 Jun 2024 19:30:03 +0000 Subject: [PATCH 53/85] update to logger.debug, use lib for common piece of code in endpoints Signed-off-by: Ilayda Cansin Koc --- vclogin/lib/extractClaims.ts | 12 +- vclogin/lib/generatePresentationDefinition.ts | 2 +- vclogin/pages/api/clientMetadata.ts | 49 +--- .../pages/api/dynamic/clientMetadataById.ts | 2 +- vclogin/pages/api/dynamic/getAuthResponse.ts | 2 +- .../api/dynamic/presentCredentialById.ts | 18 +- vclogin/pages/api/presentCredential.ts | 214 +++++++++--------- 7 files changed, 127 insertions(+), 172 deletions(-) diff --git a/vclogin/lib/extractClaims.ts b/vclogin/lib/extractClaims.ts index 611b351..a5e6528 100644 --- a/vclogin/lib/extractClaims.ts +++ b/vclogin/lib/extractClaims.ts @@ -32,18 +32,18 @@ export const extractClaims = async (VP: any, policy?: LoginPolicy) => { var usedPolicy = policy ? policy : configuredPolicy!; - logger.info("Used Policy", usedPolicy); + logger.debug("Used Policy", usedPolicy); const creds = Array.isArray(VP.verifiableCredential) ? VP.verifiableCredential : [VP.verifiableCredential]; - logger.info("Credentials", creds); + logger.debug("Credentials", creds); const vcClaims = creds.map((vc: any) => extractClaimsFromVC(vc, usedPolicy)); - logger.info("Extracted VC Claims", vcClaims); + logger.debug("Extracted VC Claims", vcClaims); const claims = vcClaims.reduce( (acc: any, vc: any) => Object.assign(acc, vc), {}, ); - logger.info("Extracted Claims", claims); + logger.debug("Extracted Claims", claims); return claims; }; @@ -259,7 +259,7 @@ const resolveValue = ( const extractClaimsFromVC = (VC: any, policy: LoginPolicy) => { for (let expectation of policy) { - logger.info("Expectation", expectation); + logger.debug("Expectation", expectation); for (let pattern of expectation.patterns) { if (pattern.issuer === VC.issuer || pattern.issuer === "*") { const containsAllRequired = @@ -313,7 +313,7 @@ const extractClaimsFromVC = (VC: any, policy: LoginPolicy) => { : extractedClaims.tokenId; jp.value(claimTarget, newPath, value); } - logger.info("Extracted Claims", extractedClaims); + logger.debug("Extracted Claims", extractedClaims); return extractedClaims; } } diff --git a/vclogin/lib/generatePresentationDefinition.ts b/vclogin/lib/generatePresentationDefinition.ts index b9dd8a2..de43c96 100644 --- a/vclogin/lib/generatePresentationDefinition.ts +++ b/vclogin/lib/generatePresentationDefinition.ts @@ -57,7 +57,7 @@ export const generatePresentationDefinition = ( return pd; } else if (incrAuthInputDescriptor) { pd.input_descriptors = incrAuthInputDescriptor; - logger.info( + logger.debug( "Using input descriptor override for incremental authorization", pd, ); diff --git a/vclogin/pages/api/clientMetadata.ts b/vclogin/pages/api/clientMetadata.ts index 0560b1a..22df5ed 100644 --- a/vclogin/pages/api/clientMetadata.ts +++ b/vclogin/pages/api/clientMetadata.ts @@ -11,52 +11,9 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { try { const { method } = req; if (method === "GET") { - const metadata = { - scopes_supported: ["openid"], - response_types_supported: ["id_token", "vp_token"], - response_modes_supported: ["query"], - subject_types_supported: ["public"], - id_token_signing_alg_values_supported: [ - "ES256", - "ES256k", - "EdDSA", - "RS256", - ], - request_object_signing_alg_values_supported: [ - "ES256", - "ES256K", - "EdDSA", - "RS256", - ], - vp_formats: { - jwt_vp: { - alg_values_supported: ["ES256", "ES256K", "EdDSA", "RS256"], - }, - jwt_vc: { - alg_values_supported: ["ES256", "ES256K", "EdDSA", "RS256"], - }, - }, - subject_syntax_types_supported: [ - "did:key", - "did:ebsi", - "did:tz", - "did:pkh", - "did:key", - "did:ethr", - ], - subject_syntax_types_discriminations: [ - "did:key:jwk_jcs-pub", - "did:ebsi:v1", - ], - subject_trust_frameworks_supported: ["ebsi"], - id_token_types_supported: ["subject_signed_id_token"], - client_name: "VP Login Service", - request_uri_parameter_supported: true, - request_parameter_supported: false, - redirect_uris: [ - process.env.NEXT_PUBLIC_INTERNET_URL + "/api/presentCredential", - ], - }; + const metadata = getMetadata([ + process.env.NEXT_PUBLIC_INTERNET_URL + "/api/presentCredential", + ]); res.status(200).json(metadata); } else { res.status(500).end(); diff --git a/vclogin/pages/api/dynamic/clientMetadataById.ts b/vclogin/pages/api/dynamic/clientMetadataById.ts index 26ebba3..579937a 100644 --- a/vclogin/pages/api/dynamic/clientMetadataById.ts +++ b/vclogin/pages/api/dynamic/clientMetadataById.ts @@ -14,7 +14,7 @@ export default async function handler( try { const { method } = req; if (method === "GET") { - logger.info("METADATA BY ID API GET"); + logger.debug("METADATA BY ID API GET"); const metadata = getMetadata([ process.env.NEXT_PUBLIC_INTERNET_URL + "/api/dynamic/presentCredentialById", diff --git a/vclogin/pages/api/dynamic/getAuthResponse.ts b/vclogin/pages/api/dynamic/getAuthResponse.ts index 1520f07..5aa94fe 100644 --- a/vclogin/pages/api/dynamic/getAuthResponse.ts +++ b/vclogin/pages/api/dynamic/getAuthResponse.ts @@ -15,7 +15,7 @@ export default async function handler( ) { //read uuid from query params const uuid = req.query["uuid"]; - logger.info("uuid: ", uuid); + logger.debug("uuid: ", uuid); // Read auth_res from redis and check if it matches the uuid diff --git a/vclogin/pages/api/dynamic/presentCredentialById.ts b/vclogin/pages/api/dynamic/presentCredentialById.ts index fc99bb5..b423b31 100644 --- a/vclogin/pages/api/dynamic/presentCredentialById.ts +++ b/vclogin/pages/api/dynamic/presentCredentialById.ts @@ -15,7 +15,7 @@ try { } const getHandler = async (req: NextApiRequest, res: NextApiResponse) => { - logger.info("LOGIN API GET BY ID"); + logger.debug("LOGIN API GET BY ID"); // Get login_id from query const uuid = req.query["login_id"]; @@ -25,7 +25,7 @@ const getHandler = async (req: NextApiRequest, res: NextApiResponse) => { // fetch inputDescriptor from redis using uuid const inputDescriptor = await redis.get(uuid + "_inputDescriptor"); - logger.info("inputDescriptor: ", JSON.parse(inputDescriptor!)); + logger.debug("inputDescriptor: ", JSON.parse(inputDescriptor!)); //if policy is found if (policy) { @@ -61,11 +61,11 @@ const getHandler = async (req: NextApiRequest, res: NextApiResponse) => { }; const postHandler = async (req: NextApiRequest, res: NextApiResponse) => { - logger.info("LOGIN API POST BY ID"); + logger.debug("LOGIN API POST BY ID"); // Parse the JSON string into a JavaScript object const presentation = JSON.parse(req.body.vp_token); - logger.info("Presentation: \n", req.body.vp_token); + logger.debug("Presentation: \n", req.body.vp_token); const uuid = presentation["proof"]["challenge"]; const policy = await redis.get(uuid + "_policy"); @@ -79,14 +79,14 @@ const postHandler = async (req: NextApiRequest, res: NextApiResponse) => { // Verify the presentation and the status of the credential if (await verifyAuthenticationPresentation(presentation)) { - logger.info("Presentation valid"); + logger.debug("Presentation valid"); // Evaluate if the VP should be trusted if (await isTrustedPresentation(presentation, policyObject)) { - logger.info("Presentation verified"); + logger.debug("Presentation verified"); // Get the user claims when the presentation is trusted const userClaims = await extractClaims(presentation, policyObject); - logger.info(userClaims); + logger.debug(userClaims); // Store the authentication result in Redis await redis.set(uuid + "_auth-res", "success", EXPIRY_MS, MAX_AGE); @@ -99,7 +99,7 @@ const postHandler = async (req: NextApiRequest, res: NextApiResponse) => { MAX_AGE, ); } else { - logger.info("Presentation not trusted"); + logger.debug("Presentation not trusted"); await redis.set( "auth_res:" + uuid, @@ -112,7 +112,7 @@ const postHandler = async (req: NextApiRequest, res: NextApiResponse) => { return; } } else { - logger.info("Presentation invalid"); + logger.debug("Presentation invalid"); await redis.set( "auth_res:" + uuid, "error_invalid_presentation", diff --git a/vclogin/pages/api/presentCredential.ts b/vclogin/pages/api/presentCredential.ts index 2a1b4e4..e01c4d9 100644 --- a/vclogin/pages/api/presentCredential.ts +++ b/vclogin/pages/api/presentCredential.ts @@ -14,133 +14,131 @@ import { logger } from "@/config/logger"; import { redisSet, redisGet } from "@/config/redis"; import { keyToDID, keyToVerificationMethod } from "@spruceid/didkit-wasm-node"; import * as jose from "jose"; +import { getToken } from "@/lib/getToken"; -async function handler(req: NextApiRequest, res: NextApiResponse) { +const getHandler = async (req: NextApiRequest, res: NextApiResponse) => { try { - const { method } = req; - if (method === "GET") { - const presentation_definition = generatePresentationDefinition( - await getConfiguredLoginPolicy()!, - ); - const did = keyToDID("key", process.env.DID_KEY_JWK!); - const verificationMethod = await keyToVerificationMethod( - "key", - process.env.DID_KEY_JWK!, - ); - const challenge = req.query["login_id"]; - const payload = { - client_id: did, - client_id_scheme: "did", - client_metadata_uri: process.env.EXTERNAL_URL + "/api/clientMetadata", - nonce: challenge, + const presentation_definition = generatePresentationDefinition( + await getConfiguredLoginPolicy()!, + ); + const challenge = req.query["login_id"]?.toString(); + + if (challenge) { + const token = await getToken( + challenge, + process.env.EXTERNAL_URL + "/api/clientMetadata", + process.env.EXTERNAL_URL + "/api/presentCredential", presentation_definition, - response_mode: "direct_post", - response_type: "vp_token", - response_uri: process.env.EXTERNAL_URL + "/api/presentCredential", - state: challenge, - }; - const privateKey = await jose.importJWK( - JSON.parse(process.env.DID_KEY_JWK!), - "EdDSA", + res, ); - const token = await new jose.SignJWT(payload) - .setProtectedHeader({ - alg: "EdDSA", - kid: verificationMethod, - typ: "JWT", - }) - .setIssuedAt() - .setIssuer(did) - .setAudience("https://self-issued.me/v2") // by definition - .setExpirationTime("1 hour") - .sign(privateKey) - .catch((err) => { - logger.error(err, "Failed signing presentation definition token"); - res.status(500).end(); - }); - res - .status(200) - .appendHeader("Content-Type", "application/oauth-authz-req+jwt") - .send(token); - } else if (method === "POST") { - // Parse the JSON string into a JavaScript object - const presentation = JSON.parse(req.body.vp_token); - logger.debug(req.body.vp_token, "Verifiable Presentaiton was sent"); + if (!token) { + res.status(500).end(); + return; + } else { + res + .status(200) + .appendHeader("Content-Type", "application/oauth-authz-req+jwt") + .send(token); + } + } + } catch { + res.status(500).end(); + } +}; +const postHandler = async (req: NextApiRequest, res: NextApiResponse) => { + try { + // Parse the JSON string into a JavaScript object + const presentation = JSON.parse(req.body.vp_token); + logger.debug(req.body.vp_token, "Verifiable Presentaiton was sent"); - // Verify the presentation and the status of the credential - if (await verifyAuthenticationPresentation(presentation)) { - // Evaluate if the VP should be trusted - if (await isTrustedPresentation(presentation)) { - logger.debug("Verifiable Presentation verified"); - } else { - logger.debug("Verifiable Presentation not trusted"); - res.status(500).end(); - return; - } + // Verify the presentation and the status of the credential + if (await verifyAuthenticationPresentation(presentation)) { + // Evaluate if the VP should be trusted + if (await isTrustedPresentation(presentation)) { + logger.debug("Verifiable Presentation verified"); } else { - logger.debug("Verifiable Presentation not valid"); + logger.debug("Verifiable Presentation not trusted"); res.status(500).end(); return; } + } else { + logger.debug("Verifiable Presentation not valid"); + res.status(500).end(); + return; + } - // Get the user claims - const userClaims = extractClaims(presentation); - const subject = presentation["holder"]; - const login_id = presentation["proof"]["challenge"]; - const challenge = (await redisGet("" + login_id))!; - logger.debug({ subject, challenge }, "Sign-in confirmed"); + // Get the user claims + const userClaims = extractClaims(presentation); + const subject = presentation["holder"]; + const login_id = presentation["proof"]["challenge"]; + const challenge = (await redisGet("" + login_id))!; + logger.debug({ subject, challenge }, "Sign-in confirmed"); - // hydra login - await hydraAdmin - .adminGetOAuth2LoginRequest(challenge) - .then(({}) => - hydraAdmin - .adminAcceptOAuth2LoginRequest(challenge, { - // Subject is an alias for user ID. A subject can be a random string, a UUID, an email address, .... - subject, - // This tells hydra to remember the browser and automatically authenticate the user in future requests. This will - // set the "skip" parameter in the other route to true on subsequent requests! - remember: Boolean(false), - // When the session expires, in seconds. Set this to 0 so it will never expire. - remember_for: 3600, - // Sets which "level" (e.g. 2-factor authentication) of authentication the user has. The value is really arbitrary - // and optional. In the context of OpenID Connect, a value of 0 indicates the lowest authorization level. - // acr: '0', - // - // If the environment variable CONFORMITY_FAKE_CLAIMS is set we are assuming that - // the app is built for the automated OpenID Connect Conformity Test Suite. You - // can peak inside the code for some ideas, but be aware that all data is fake - // and this only exists to fake a login system which works in accordance to OpenID Connect. - // - // If that variable is not set, the ACR value will be set to the default passed here ('0') - acr: "0", - }) - .then(({ data: body }) => { - const MAX_AGE = 30; // 30 seconds + // hydra login + await hydraAdmin + .adminGetOAuth2LoginRequest(challenge) + .then(({}) => + hydraAdmin + .adminAcceptOAuth2LoginRequest(challenge, { + // Subject is an alias for user ID. A subject can be a random string, a UUID, an email address, .... + subject, + // This tells hydra to remember the browser and automatically authenticate the user in future requests. This will + // set the "skip" parameter in the other route to true on subsequent requests! + remember: Boolean(false), + // When the session expires, in seconds. Set this to 0 so it will never expire. + remember_for: 3600, + // Sets which "level" (e.g. 2-factor authentication) of authentication the user has. The value is really arbitrary + // and optional. In the context of OpenID Connect, a value of 0 indicates the lowest authorization level. + // acr: '0', + // + // If the environment variable CONFORMITY_FAKE_CLAIMS is set we are assuming that + // the app is built for the automated OpenID Connect Conformity Test Suite. You + // can peak inside the code for some ideas, but be aware that all data is fake + // and this only exists to fake a login system which works in accordance to OpenID Connect. + // + // If that variable is not set, the ACR value will be set to the default passed here ('0') + acr: "0", + }) + .then(({ data: body }) => { + const MAX_AGE = 30; // 30 seconds - // save the user claims to redis - redisSet("" + subject, JSON.stringify(userClaims), MAX_AGE); + // save the user claims to redis + redisSet("" + subject, JSON.stringify(userClaims), MAX_AGE); - // save the redirect address to redis for the browser - redisSet( - "redirect" + login_id, - String(body.redirect_to), - MAX_AGE, - ); + // save the redirect address to redis for the browser + redisSet("redirect" + login_id, String(body.redirect_to), MAX_AGE); - // phone just gets a 200 ok - res.status(200).end(); - }), - ) - // This will handle any error that happens when making HTTP calls to hydra - .catch((_) => res.status(401).end()); + // phone just gets a 200 ok + res.status(200).end(); + }), + ) + // This will handle any error that happens when making HTTP calls to hydra + .catch((_) => res.status(401).end()); + } catch { + res.status(500).end(); + } +}; + +const handlers: any = { + POST: postHandler, + GET: getHandler, +}; + +async function handler( + req: NextApiRequest, + res: NextApiResponse, //todo look for separate handles +) { + try { + const { method } = req; + if (method && (method === "POST" || method === "GET")) { + const execute = handlers[method.toUpperCase()]; + return await execute(req, res); } else { res.status(500).end(); } - } catch (e) { + } catch (error) { res.status(500).end(); } } - export default withLogging(handler); export const config = { api: { bodyParser: true } }; From d00fbdeb378d85abb2837b410101a884676b12ba Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Mon, 10 Jun 2024 12:45:28 +0200 Subject: [PATCH 54/85] Changed default token Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> --- vclogin/__tests__/lib/extractClaims.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/vclogin/__tests__/lib/extractClaims.test.js b/vclogin/__tests__/lib/extractClaims.test.js index 4aae794..d6d650e 100644 --- a/vclogin/__tests__/lib/extractClaims.test.js +++ b/vclogin/__tests__/lib/extractClaims.test.js @@ -67,6 +67,7 @@ describe("extractClaims", () => { email: "test@test.com", name: "Name Surname", companyName: "deltaDAO AG", + companyName: "deltaDAO AG", }, }; expect(claims).toStrictEqual(expected); From 1a414152b0ac69b76d76eeca6666fa6df2246ade Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Mon, 10 Jun 2024 12:45:28 +0200 Subject: [PATCH 55/85] Changed default token Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> --- vclogin/__tests__/lib/extractClaims.test.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vclogin/__tests__/lib/extractClaims.test.js b/vclogin/__tests__/lib/extractClaims.test.js index d6d650e..64bde36 100644 --- a/vclogin/__tests__/lib/extractClaims.test.js +++ b/vclogin/__tests__/lib/extractClaims.test.js @@ -15,6 +15,8 @@ describe("extractClaims", () => { it("all subject claims from an EmployeeCredential are extracted", () => { var claims = extractClaims(vpEmployee, policyAcceptAnything); var expected = { + tokenAccess: {}, + tokenId: { tokenAccess: {}, tokenId: { subjectData: { @@ -62,12 +64,12 @@ describe("extractClaims", () => { it("all designated claims from an EmployeeCredential are extracted", () => { var claims = extractClaims(vpEmployee, policyEmployeeFromAnyone); var expected = { + tokenAccess: {}, tokenAccess: {}, tokenId: { email: "test@test.com", name: "Name Surname", companyName: "deltaDAO AG", - companyName: "deltaDAO AG", }, }; expect(claims).toStrictEqual(expected); From adfa398a71513b4279db3e2ef8bc91c4a71eadca Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Mon, 10 Jun 2024 17:29:17 +0200 Subject: [PATCH 56/85] Adopted absolute imports Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> --- vclogin/tsconfig.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/vclogin/tsconfig.json b/vclogin/tsconfig.json index c8a8bfc..7e05946 100644 --- a/vclogin/tsconfig.json +++ b/vclogin/tsconfig.json @@ -15,14 +15,13 @@ "jsx": "preserve", "incremental": true, "baseUrl": ".", + "baseUrl": ".", "paths": { "@/testdata/*": ["__tests__/testdata/*"], "@/lib/*": ["lib/*"], "@/config/*": ["config/*"], "@/types/*": ["types/*"], - "@/pages/*": ["pages/*"], - "@/styles/*": ["styles/*"], - "@/middleware/*": ["middleware/*"] + "@/pages/*": ["pages/*"] }, "typeRoots": ["./types"] }, From a41e499fdf3fad9304277ae46fee64f10a2518cd Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Mon, 10 Jun 2024 17:29:17 +0200 Subject: [PATCH 57/85] Adopted absolute imports Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> --- vclogin/tsconfig.json | 1 - 1 file changed, 1 deletion(-) diff --git a/vclogin/tsconfig.json b/vclogin/tsconfig.json index 7e05946..d274a0e 100644 --- a/vclogin/tsconfig.json +++ b/vclogin/tsconfig.json @@ -15,7 +15,6 @@ "jsx": "preserve", "incremental": true, "baseUrl": ".", - "baseUrl": ".", "paths": { "@/testdata/*": ["__tests__/testdata/*"], "@/lib/*": ["lib/*"], From 38c6b2c1ec06d0a8edc2e6f091a1953be9c9f1f1 Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Tue, 11 Jun 2024 11:55:33 +0200 Subject: [PATCH 58/85] Fix missing styles source Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> --- vclogin/tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vclogin/tsconfig.json b/vclogin/tsconfig.json index d274a0e..c9076cb 100644 --- a/vclogin/tsconfig.json +++ b/vclogin/tsconfig.json @@ -20,7 +20,8 @@ "@/lib/*": ["lib/*"], "@/config/*": ["config/*"], "@/types/*": ["types/*"], - "@/pages/*": ["pages/*"] + "@/pages/*": ["pages/*"], + "@/styles/*": ["styles/*"] }, "typeRoots": ["./types"] }, From 1eff76076342cd2c62fe5338b99445b3f5cf4f12 Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Tue, 11 Jun 2024 16:27:42 +0200 Subject: [PATCH 59/85] Logging improvements with pino Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> --- vclogin/pages/api/consent.ts | 5 +++-- vclogin/pages/api/presentCredential.ts | 1 - vclogin/tsconfig.json | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/vclogin/pages/api/consent.ts b/vclogin/pages/api/consent.ts index 400cba6..04e702d 100644 --- a/vclogin/pages/api/consent.ts +++ b/vclogin/pages/api/consent.ts @@ -14,8 +14,9 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { if (method === "GET") { const challenge = req.query["consent_challenge"] as string; - const { data: body } = - await hydraAdmin.adminGetOAuth2ConsentRequest(challenge); + const { data: body } = await hydraAdmin.adminGetOAuth2ConsentRequest( + challenge, + ); // get user identity and fetch user claims from redis const userClaims = JSON.parse((await redisGet("" + body.subject))!); diff --git a/vclogin/pages/api/presentCredential.ts b/vclogin/pages/api/presentCredential.ts index e01c4d9..ad920ee 100644 --- a/vclogin/pages/api/presentCredential.ts +++ b/vclogin/pages/api/presentCredential.ts @@ -12,7 +12,6 @@ import { getConfiguredLoginPolicy } from "@/config/loginPolicy"; import { withLogging } from "@/middleware/logging"; import { logger } from "@/config/logger"; import { redisSet, redisGet } from "@/config/redis"; -import { keyToDID, keyToVerificationMethod } from "@spruceid/didkit-wasm-node"; import * as jose from "jose"; import { getToken } from "@/lib/getToken"; diff --git a/vclogin/tsconfig.json b/vclogin/tsconfig.json index c9076cb..c8a8bfc 100644 --- a/vclogin/tsconfig.json +++ b/vclogin/tsconfig.json @@ -21,7 +21,8 @@ "@/config/*": ["config/*"], "@/types/*": ["types/*"], "@/pages/*": ["pages/*"], - "@/styles/*": ["styles/*"] + "@/styles/*": ["styles/*"], + "@/middleware/*": ["middleware/*"] }, "typeRoots": ["./types"] }, From 0dcd0321fbeb1ba673117f8b1b4996a8b2d9686e Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Tue, 11 Jun 2024 16:41:26 +0200 Subject: [PATCH 60/85] Text fixes Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Signed-off-by: Ilayda Cansin Koc --- vclogin/pages/api/consent.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/vclogin/pages/api/consent.ts b/vclogin/pages/api/consent.ts index 04e702d..400cba6 100644 --- a/vclogin/pages/api/consent.ts +++ b/vclogin/pages/api/consent.ts @@ -14,9 +14,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { if (method === "GET") { const challenge = req.query["consent_challenge"] as string; - const { data: body } = await hydraAdmin.adminGetOAuth2ConsentRequest( - challenge, - ); + const { data: body } = + await hydraAdmin.adminGetOAuth2ConsentRequest(challenge); // get user identity and fetch user claims from redis const userClaims = JSON.parse((await redisGet("" + body.subject))!); From 842b6e379545e2505f84ffac2eee5dc962186e84 Mon Sep 17 00:00:00 2001 From: Ilayda Cansin Koc Date: Sun, 7 Jul 2024 19:08:14 +0000 Subject: [PATCH 61/85] refactor extractClaims.ts Signed-off-by: Ilayda Cansin Koc --- vclogin/lib/extractClaims.ts | 116 +++++++++++++++++++++++++---------- 1 file changed, 85 insertions(+), 31 deletions(-) diff --git a/vclogin/lib/extractClaims.ts b/vclogin/lib/extractClaims.ts index a5e6528..5ba3e7d 100644 --- a/vclogin/lib/extractClaims.ts +++ b/vclogin/lib/extractClaims.ts @@ -11,7 +11,6 @@ import { } from "@/types/LoginPolicy"; import jp from "jsonpath"; import { getConfiguredLoginPolicy } from "@/config/loginPolicy"; -import { logger } from "@/config/logger"; export const isTrustedPresentation = async (VP: any, policy?: LoginPolicy) => { var configuredPolicy = await getConfiguredLoginPolicy(); @@ -32,18 +31,26 @@ export const extractClaims = async (VP: any, policy?: LoginPolicy) => { var usedPolicy = policy ? policy : configuredPolicy!; - logger.debug("Used Policy", usedPolicy); const creds = Array.isArray(VP.verifiableCredential) ? VP.verifiableCredential : [VP.verifiableCredential]; - logger.debug("Credentials", creds); + const vcClaims = creds.map((vc: any) => extractClaimsFromVC(vc, usedPolicy)); - logger.debug("Extracted VC Claims", vcClaims); - const claims = vcClaims.reduce( - (acc: any, vc: any) => Object.assign(acc, vc), - {}, - ); - logger.debug("Extracted Claims", claims); + + const claims: any = {}; + + vcClaims.forEach((claim: any) => { + // Merge tokenId properties + claims.tokenId = Object.assign({}, claims.tokenId, claim.tokenId); + + // Merge tokenAccess properties + claims.tokenAccess = Object.assign( + {}, + claims.tokenAccess, + claim.tokenAccess, + ); + }); + return claims; }; @@ -115,6 +122,7 @@ const isCredentialFittingPattern = ( }; const getAllUniqueDraws = (patternFits: any[][]): any[][] => { + // get all unique draws of credentials that fit the expected credential claims const draws = getAllUniqueDrawsHelper(patternFits, []); return draws.filter((draw) => draw.length == patternFits.length); }; @@ -126,7 +134,6 @@ const getAllUniqueDrawsHelper = ( if (patternFits.length === 0) { return []; } - let uniqueDraws: any[][] = []; for (let cred of patternFits[0]) { if (!usedIds.includes(cred.id)) { @@ -149,11 +156,12 @@ const isValidConstraintFit = ( for (let i = 0; i < policy.length; i++) { credDict[policy[i].credentialId] = credFit[i]; } + // check if all constraints are fulfilled + var fittingArr = []; for (let i = 0; i < policy.length; i++) { const cred = credFit[i]; const expectation = policy[i]; - var oneFittingPattern = false; for (let pattern of expectation.patterns) { if (isCredentialFittingPattern(cred, pattern)) { if (pattern.constraint) { @@ -164,15 +172,19 @@ const isValidConstraintFit = ( VP, ); if (res) { - oneFittingPattern = true; - break; + // if one pattern fits, the credential is fitting + fittingArr.push(true); + } else { + // if one pattern does not fit, the credential is not fitting + fittingArr.push(false); } } } } - if (!oneFittingPattern) { - return true; - } + } + // if all patterns fit, the credential is fitting + if (!fittingArr.includes(false)) { + return true; } return false; }; @@ -230,36 +242,69 @@ const evaluateConstraint = ( throw Error("Unknown constraint operator: " + constraint.op); }; -const resolveValue = ( +const resolveSingleNodeValue = ( expression: string, cred: any, - credDict: any, VP: any, ): string => { + var nodes: any; if (expression.startsWith("$")) { - var nodes: any; - if (expression.startsWith("$.")) { - nodes = jp.nodes(cred, expression); + if (expression.startsWith("$1.")) { + nodes = jp.nodes(cred, "$" + expression.slice(2)); } else if (expression.startsWith("$VP.")) { nodes = jp.nodes(VP, "$" + expression.slice(3)); - } else { - nodes = jp.nodes( - credDict[expression.slice(1).split(".")[0]], - expression.slice(1).split(".").slice(1).join("."), - ); } - if (nodes.length > 1 || nodes.length <= 0) { + if (nodes === undefined) { + return expression; + } else if (nodes.length > 1 || nodes.length <= 0) { throw Error("JSON Paths in constraints must be single-valued"); } return nodes[0].value; } - return expression; }; +const resolveValue = ( + expression: string, + cred: any, + credDict: any, + VP: any, +): string => { + var nodes: any; + if (Object.entries(credDict).length > 0) { + // store object key's value in array to prevent querying wrong key + let keyValues = []; + for (const [key, value] of Object.entries(credDict)) { + keyValues.push(key); + } + + for (const [key, value] of Object.entries(credDict)) { + if (expression.startsWith("$" + key + ".")) { + for (const [key2, value2] of Object.entries(credDict)) { + // check if both keys are in credDict + if (keyValues.includes(key2) && keyValues.includes(key)) { + if (key !== key2) { + nodes = jp.nodes(value2, expression.slice(2 + key.length)); + if (nodes.length <= 1 && nodes.length > 0) { + return nodes[0].value; + } + } + } else { + // if key is not found in credDict + throw Error("Key not found in credDict"); + } + } + } + } + resolveSingleNodeValue(expression, cred, VP); + } + return resolveSingleNodeValue(expression, cred, VP); +}; + const extractClaimsFromVC = (VC: any, policy: LoginPolicy) => { + let reiterateOuterLoop = false; + for (let expectation of policy) { - logger.debug("Expectation", expectation); for (let pattern of expectation.patterns) { if (pattern.issuer === VC.issuer || pattern.issuer === "*") { const containsAllRequired = @@ -281,6 +326,7 @@ const extractClaimsFromVC = (VC: any, policy: LoginPolicy) => { for (let claim of pattern.claims) { const nodes = jp.nodes(VC, claim.claimPath); + let newPath = claim.newPath; let value: any; @@ -288,7 +334,7 @@ const extractClaimsFromVC = (VC: any, policy: LoginPolicy) => { if (!newPath) { throw Error( "New path not defined for multi-valued claim: " + - claim.claimPath, + claim.claimPath, ); } @@ -301,6 +347,10 @@ const extractClaimsFromVC = (VC: any, policy: LoginPolicy) => { .reduce((acc: any, vals: any) => Object.assign(acc, vals), {}); } else { if (!newPath) { + if (nodes.length === 0 || nodes === undefined) { + reiterateOuterLoop = true; + break; + } newPath = "$." + nodes[0].path[nodes[0].path.length - 1]; } @@ -313,7 +363,11 @@ const extractClaimsFromVC = (VC: any, policy: LoginPolicy) => { : extractedClaims.tokenId; jp.value(claimTarget, newPath, value); } - logger.debug("Extracted Claims", extractedClaims); + + if (reiterateOuterLoop) { + reiterateOuterLoop = false; + break; // Break inner loop + } return extractedClaims; } } From 9d1e32ddf9d43404bfbdd8448371a919fe329cdc Mon Sep 17 00:00:00 2001 From: Ilayda Cansin Koc Date: Mon, 8 Jul 2024 22:59:32 +0200 Subject: [PATCH 62/85] refactor clientMetadata endpoint Signed-off-by: Ilayda Cansin Koc --- vclogin/pages/api/clientMetadata.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/vclogin/pages/api/clientMetadata.ts b/vclogin/pages/api/clientMetadata.ts index e3cc376..c39aac1 100644 --- a/vclogin/pages/api/clientMetadata.ts +++ b/vclogin/pages/api/clientMetadata.ts @@ -5,13 +5,18 @@ import { getMetadata } from "@/lib/getMetadata"; import type { NextApiRequest, NextApiResponse } from "next"; +import { logger } from "@/config/logger"; -async function handler(req: NextApiRequest, res: NextApiResponse) { +export default async function handler( + req: NextApiRequest, + res: NextApiResponse, +) { try { const { method } = req; if (method === "GET") { + logger.debug("METADATA BY ID API GET"); const metadata = getMetadata([ - process.env.NEXT_PUBLIC_INTERNET_URL + "/api/presentCredential", + process.env.NEXT_PUBLIC_INTERNET_URL + "/api/dynamic/presentCredential", ]); res.status(200).json(metadata); } else { From 9566fac2ac8548bdd6cdf267da5c5c4f6175cded Mon Sep 17 00:00:00 2001 From: Ilayda Cansin Koc Date: Mon, 8 Jul 2024 23:01:31 +0200 Subject: [PATCH 63/85] update package-lock.json via npm i Signed-off-by: Ilayda Cansin Koc --- vclogin/package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vclogin/package-lock.json b/vclogin/package-lock.json index 2cc38bc..a1facbc 100644 --- a/vclogin/package-lock.json +++ b/vclogin/package-lock.json @@ -1,12 +1,12 @@ { "name": "ssi-to-oidc-bridge", - "version": "0.1.1", + "version": "1.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ssi-to-oidc-bridge", - "version": "0.1.1", + "version": "1.2.0", "dependencies": { "@material-tailwind/react": "^2.0.3", "@ory/hydra-client": "^2.2.0", From 2831eb9ba3c5f963d693617ac217ee3ff0591092 Mon Sep 17 00:00:00 2001 From: Ilayda Cansin Koc Date: Mon, 8 Jul 2024 23:05:06 +0200 Subject: [PATCH 64/85] use async await to get the loginPolicy Signed-off-by: Ilayda Cansin Koc --- vclogin/config/loginPolicy.ts | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/vclogin/config/loginPolicy.ts b/vclogin/config/loginPolicy.ts index 6d00e55..81f6c1a 100644 --- a/vclogin/config/loginPolicy.ts +++ b/vclogin/config/loginPolicy.ts @@ -4,18 +4,21 @@ */ import { promises as fs } from "fs"; -import { LoginPolicy } from "@/types/LoginPolicy"; import { logger } from "./logger"; -var configuredPolicy: LoginPolicy | undefined = undefined; -if (process.env.LOGIN_POLICY) { - fs.readFile(process.env.LOGIN_POLICY as string, "utf8").then((file) => { - configuredPolicy = JSON.parse(file); - }); -} else if (process.env.NODE_ENV !== "test") { - logger.error("No login policy set"); -} - -export const getConfiguredLoginPolicy = () => { - return configuredPolicy; +export const getConfiguredLoginPolicy = async () => { + try { + if (process.env.LOGIN_POLICY) { + const file = await fs.readFile( + process.env.LOGIN_POLICY as string, + "utf8", + ); + return JSON.parse(file); + } else if (process.env.NODE_ENV !== "test") { + logger.error("No login policy set"); + } + } catch (error) { + logger.error("Failed to read login policy:", error); + return undefined; + } }; From f00f9b2e86c93a6085b4fe7b8ca18e96c60ff72f Mon Sep 17 00:00:00 2001 From: Ilayda Cansin Koc Date: Thu, 11 Jul 2024 19:15:31 +0200 Subject: [PATCH 65/85] adjust unit tests for multi VC VP and constraints check Signed-off-by: Ilayda Cansin Koc --- .../testdata/policies/acceptAnything.json | 2 +- .../policies/acceptAnythingMisconfigured.json | 15 ++ .../policies/acceptAnythingMultiVC.json | 32 ++++ .../policies/acceptEmailFromAltme.json | 2 +- .../policies/acceptEmailFromAltmeConstr.json | 2 +- .../policies/acceptEmployeeFromAnyone.json | 2 +- .../acceptEmployeeFromAnyoneConstr.json | 2 +- .../testdata/policies/acceptFromAltme.json | 2 +- .../acceptMultiEmailFromAltmeConstr.json | 38 +++++ ...acceptMultiEmailFromAltmeSimpleConstr.json | 33 ++++ .../acceptMultiVCFromAltmeConstr.json | 38 +++++ .../acceptMultiVCFromAltmeMisconfigured.json | 28 ++++ .../acceptMultiVCFromAltmeSimpleConstr.json | 33 ++++ .../policies/acceptVerifiableIdFromAltme.json | 9 +- .../presentations/VP_MultiEmailPass.json | 123 +++++++++++++++ .../testdata/presentations/VP_MultiVC.json | 122 +++++++++++++++ .../unit/lib/evaluateLoginPolicy.test.ts | 143 +++++++++++++++--- .../__tests__/unit/lib/extractClaims.test.ts | 101 ++++++++++++- .../unit/lib/verifyPresentation.test.ts | 20 ++- vclogin/lib/extractClaims.ts | 35 +++-- 20 files changed, 716 insertions(+), 66 deletions(-) create mode 100644 vclogin/__tests__/testdata/policies/acceptAnythingMisconfigured.json create mode 100644 vclogin/__tests__/testdata/policies/acceptAnythingMultiVC.json create mode 100644 vclogin/__tests__/testdata/policies/acceptMultiEmailFromAltmeConstr.json create mode 100644 vclogin/__tests__/testdata/policies/acceptMultiEmailFromAltmeSimpleConstr.json create mode 100644 vclogin/__tests__/testdata/policies/acceptMultiVCFromAltmeConstr.json create mode 100644 vclogin/__tests__/testdata/policies/acceptMultiVCFromAltmeMisconfigured.json create mode 100644 vclogin/__tests__/testdata/policies/acceptMultiVCFromAltmeSimpleConstr.json create mode 100644 vclogin/__tests__/testdata/presentations/VP_MultiEmailPass.json create mode 100644 vclogin/__tests__/testdata/presentations/VP_MultiVC.json diff --git a/vclogin/__tests__/testdata/policies/acceptAnything.json b/vclogin/__tests__/testdata/policies/acceptAnything.json index 15d0d37..3aa0ad4 100644 --- a/vclogin/__tests__/testdata/policies/acceptAnything.json +++ b/vclogin/__tests__/testdata/policies/acceptAnything.json @@ -1,6 +1,6 @@ [ { - "credentialId": "credential1", + "credentialId": "1", "patterns": [ { "issuer": "*", diff --git a/vclogin/__tests__/testdata/policies/acceptAnythingMisconfigured.json b/vclogin/__tests__/testdata/policies/acceptAnythingMisconfigured.json new file mode 100644 index 0000000..1ea07d2 --- /dev/null +++ b/vclogin/__tests__/testdata/policies/acceptAnythingMisconfigured.json @@ -0,0 +1,15 @@ +[ + { + "credentialId": "some random string", + "patterns": [ + { + "issuer": "*", + "claims": [ + { + "claimPath": "$.credentialSubject.id" + } + ] + } + ] + } +] diff --git a/vclogin/__tests__/testdata/policies/acceptAnythingMultiVC.json b/vclogin/__tests__/testdata/policies/acceptAnythingMultiVC.json new file mode 100644 index 0000000..9c913ea --- /dev/null +++ b/vclogin/__tests__/testdata/policies/acceptAnythingMultiVC.json @@ -0,0 +1,32 @@ +[ + { + "credentialId": "1", + "patterns": [ + { + "issuer": "*", + "claims": [ + { + "claimPath": "$1.credentialSubject.*", + "newPath": "$.firstCredentialSubject", + "required": false + } + ] + } + ] + }, + { + "credentialId": "2", + "patterns": [ + { + "issuer": "*", + "claims": [ + { + "claimPath": "$.credentialSubject.*", + "newPath": "$.secondCredentialSubject", + "required": false + } + ] + } + ] + } +] diff --git a/vclogin/__tests__/testdata/policies/acceptEmailFromAltme.json b/vclogin/__tests__/testdata/policies/acceptEmailFromAltme.json index 72e3c73..1712abc 100644 --- a/vclogin/__tests__/testdata/policies/acceptEmailFromAltme.json +++ b/vclogin/__tests__/testdata/policies/acceptEmailFromAltme.json @@ -1,6 +1,6 @@ [ { - "credentialId": "one", + "credentialId": "1", "patterns": [ { "issuer": "did:web:app.altme.io:issuer", diff --git a/vclogin/__tests__/testdata/policies/acceptEmailFromAltmeConstr.json b/vclogin/__tests__/testdata/policies/acceptEmailFromAltmeConstr.json index 4f41086..17885d5 100644 --- a/vclogin/__tests__/testdata/policies/acceptEmailFromAltmeConstr.json +++ b/vclogin/__tests__/testdata/policies/acceptEmailFromAltmeConstr.json @@ -1,6 +1,6 @@ [ { - "credentialId": "one", + "credentialId": "1", "patterns": [ { "issuer": "did:web:app.altme.io:issuer", diff --git a/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyone.json b/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyone.json index 2c581b8..b2e023a 100644 --- a/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyone.json +++ b/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyone.json @@ -1,6 +1,6 @@ [ { - "credentialId": "one", + "credentialId": "1", "patterns": [ { "issuer": "*", diff --git a/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyoneConstr.json b/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyoneConstr.json index 78c9d64..8cff46f 100644 --- a/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyoneConstr.json +++ b/vclogin/__tests__/testdata/policies/acceptEmployeeFromAnyoneConstr.json @@ -1,6 +1,6 @@ [ { - "credentialId": "one", + "credentialId": "1", "patterns": [ { "issuer": "*", diff --git a/vclogin/__tests__/testdata/policies/acceptFromAltme.json b/vclogin/__tests__/testdata/policies/acceptFromAltme.json index f94c5b1..2c183d8 100644 --- a/vclogin/__tests__/testdata/policies/acceptFromAltme.json +++ b/vclogin/__tests__/testdata/policies/acceptFromAltme.json @@ -1,6 +1,6 @@ [ { - "credentialId": "one", + "credentialId": "1", "patterns": [ { "issuer": "did:web:app.altme.io:issuer", diff --git a/vclogin/__tests__/testdata/policies/acceptMultiEmailFromAltmeConstr.json b/vclogin/__tests__/testdata/policies/acceptMultiEmailFromAltmeConstr.json new file mode 100644 index 0000000..0673b68 --- /dev/null +++ b/vclogin/__tests__/testdata/policies/acceptMultiEmailFromAltmeConstr.json @@ -0,0 +1,38 @@ +[ + { + "credentialId": "1", + "patterns": [ + { + "issuer": "did:web:app.altme.io:issuer", + "claims": [ + { + "claimPath": "$.credentialSubject.email" + } + ], + "constraint": { + "op": "equalsDID", + "a": "$VP.proof.verificationMethod", + "b": "$1.credentialSubject.id" + } + } + ] + }, + { + "credentialId": "2", + "patterns": [ + { + "issuer": "did:web:app.altme.io:issuer", + "claims": [ + { + "claimPath": "$.credentialSubject.type" + } + ], + "constraint": { + "op": "equalsDID", + "a": "$2.credentialSubject.id", + "b": "$1.credentialSubject.id" + } + } + ] + } +] diff --git a/vclogin/__tests__/testdata/policies/acceptMultiEmailFromAltmeSimpleConstr.json b/vclogin/__tests__/testdata/policies/acceptMultiEmailFromAltmeSimpleConstr.json new file mode 100644 index 0000000..a1ef319 --- /dev/null +++ b/vclogin/__tests__/testdata/policies/acceptMultiEmailFromAltmeSimpleConstr.json @@ -0,0 +1,33 @@ +[ + { + "credentialId": "1", + "patterns": [ + { + "issuer": "did:web:app.altme.io:issuer", + "claims": [ + { + "claimPath": "$.credentialSubject.email" + } + ], + "constraint": { + "op": "equalsDID", + "a": "$VP.proof.verificationMethod", + "b": "$1.credentialSubject.id" + } + } + ] + }, + { + "credentialId": "2", + "patterns": [ + { + "issuer": "did:web:app.altme.io:issuer", + "claims": [ + { + "claimPath": "$.credentialSubject.type" + } + ] + } + ] + } +] diff --git a/vclogin/__tests__/testdata/policies/acceptMultiVCFromAltmeConstr.json b/vclogin/__tests__/testdata/policies/acceptMultiVCFromAltmeConstr.json new file mode 100644 index 0000000..03bf27b --- /dev/null +++ b/vclogin/__tests__/testdata/policies/acceptMultiVCFromAltmeConstr.json @@ -0,0 +1,38 @@ +[ + { + "credentialId": "1", + "patterns": [ + { + "issuer": "did:web:app.altme.io:issuer", + "claims": [ + { + "claimPath": "$.credentialSubject.email" + } + ], + "constraint": { + "op": "equalsDID", + "a": "$VP.proof.verificationMethod", + "b": "$1.credentialSubject.id" + } + } + ] + }, + { + "credentialId": "2", + "patterns": [ + { + "issuer": "did:web:app.altme.io:issuer", + "claims": [ + { + "claimPath": "$.credentialSubject.firstName" + } + ], + "constraint": { + "op": "equals", + "a": "$2.credentialSubject.id", + "b": "$1.credentialSubject.id" + } + } + ] + } +] diff --git a/vclogin/__tests__/testdata/policies/acceptMultiVCFromAltmeMisconfigured.json b/vclogin/__tests__/testdata/policies/acceptMultiVCFromAltmeMisconfigured.json new file mode 100644 index 0000000..5e1db23 --- /dev/null +++ b/vclogin/__tests__/testdata/policies/acceptMultiVCFromAltmeMisconfigured.json @@ -0,0 +1,28 @@ +[ + { + "credentialId": "one", + "patterns": [ + { + "issuer": "*", + "claims": [ + { + "claimPath": "$.credentialSubject.type" + } + ] + } + ] + }, + { + "credentialId": "two", + "patterns": [ + { + "issuer": "*", + "claims": [ + { + "claimPath": "$.credentialSubject.id" + } + ] + } + ] + } +] diff --git a/vclogin/__tests__/testdata/policies/acceptMultiVCFromAltmeSimpleConstr.json b/vclogin/__tests__/testdata/policies/acceptMultiVCFromAltmeSimpleConstr.json new file mode 100644 index 0000000..a3fa9fa --- /dev/null +++ b/vclogin/__tests__/testdata/policies/acceptMultiVCFromAltmeSimpleConstr.json @@ -0,0 +1,33 @@ +[ + { + "credentialId": "1", + "patterns": [ + { + "issuer": "did:web:app.altme.io:issuer", + "claims": [ + { + "claimPath": "$.credentialSubject.email" + } + ], + "constraint": { + "op": "equalsDID", + "a": "$VP.proof.verificationMethod", + "b": "$1.credentialSubject.id" + } + } + ] + }, + { + "credentialId": "2", + "patterns": [ + { + "issuer": "did:web:app.altme.io:issuer", + "claims": [ + { + "claimPath": "$.credentialSubject.firstName" + } + ] + } + ] + } +] diff --git a/vclogin/__tests__/testdata/policies/acceptVerifiableIdFromAltme.json b/vclogin/__tests__/testdata/policies/acceptVerifiableIdFromAltme.json index 0f1d024..9b587b6 100644 --- a/vclogin/__tests__/testdata/policies/acceptVerifiableIdFromAltme.json +++ b/vclogin/__tests__/testdata/policies/acceptVerifiableIdFromAltme.json @@ -6,16 +6,13 @@ "issuer": "did:web:app.altme.io:issuer", "claims": [ { - "claimPath": "$.credentialSubject.dateOfBirth", - "token": "id_token" + "claimPath": "$.credentialSubject.dateOfBirth" }, { - "claimPath": "$.credentialSubject.firstName", - "token": "id_token" + "claimPath": "$.credentialSubject.firstName" }, { - "claimPath": "$.credentialSubject.familyName", - "token": "id_token" + "claimPath": "$.credentialSubject.familyName" } ] } diff --git a/vclogin/__tests__/testdata/presentations/VP_MultiEmailPass.json b/vclogin/__tests__/testdata/presentations/VP_MultiEmailPass.json new file mode 100644 index 0000000..c894f23 --- /dev/null +++ b/vclogin/__tests__/testdata/presentations/VP_MultiEmailPass.json @@ -0,0 +1,123 @@ +{ + "@context": ["https://www.w3.org/2018/credentials/v1"], + "id": "urn:uuid:2c0b29f7-c530-4405-b509-d34d4a8a4028", + "type": ["VerifiablePresentation"], + "verifiableCredential": [ + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + { + "EmailPass": { + "@context": { + "@protected": true, + "@version": 1.1, + "email": "schema:email", + "id": "@id", + "issuedBy": { + "@context": { + "@protected": true, + "@version": 1.1, + "logo": { + "@id": "schema:image", + "@type": "@id" + }, + "name": "schema:name" + }, + "@id": "schema:issuedBy" + }, + "schema": "https://schema.org/", + "type": "@type" + }, + "@id": "https://github.com/TalaoDAO/context#emailpass" + } + } + ], + "id": "urn:uuid:a8e2b30c-eeb6-11ee-a822-0a1628958560", + "type": ["VerifiableCredential", "EmailPass"], + "credentialSubject": { + "id": "did:key:z6Mkj5B9HcSKWGuuawpBcvy5wQwZJ9g2k5HzfmXPAjwbQ9TT", + "issuedBy": { + "name": "Altme" + }, + "email": "first.vc@gmail.com", + "type": "EmailPass" + }, + "issuer": "did:web:app.altme.io:issuer", + "issuanceDate": "2024-03-30T16:57:55Z", + "proof": { + "type": "Ed25519Signature2018", + "proofPurpose": "assertionMethod", + "verificationMethod": "did:web:app.altme.io:issuer#key-1", + "created": "2024-03-30T16:57:58.048Z", + "jws": "eyJhbGciOiJFZERTQSIsImNyaXQiOlsiYjY0Il0sImI2NCI6ZmFsc2V9..F9EXluMjwPtCe8A0WKusX0zSsCi6JKh0HDLuNk47-Wvig_8wrwh56IocbNMyNG8b2J4wGuXUuGLTkQuftx6iDA" + }, + "expirationDate": "2025-03-30T16:57:55.390537Z", + "issued": "2024-03-30T16:57:57Z", + "validUntil": "2025-03-30T16:57:57.995257Z", + "validFrom": "2024-03-30T16:57:57Z" + }, + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + { + "EmailPass": { + "@context": { + "@protected": true, + "@version": 1.1, + "email": "schema:email", + "id": "@id", + "issuedBy": { + "@context": { + "@protected": true, + "@version": 1.1, + "logo": { + "@id": "schema:image", + "@type": "@id" + }, + "name": "schema:name" + }, + "@id": "schema:issuedBy" + }, + "schema": "https://schema.org/", + "type": "@type" + }, + "@id": "https://github.com/TalaoDAO/context#emailpass" + } + } + ], + "id": "urn:uuid:b55c651d-3f84-11ef-a702-0a1628958560", + "type": ["VerifiableCredential", "EmailPass"], + "credentialSubject": { + "id": "did:key:z6Mkj5B9HcSKWGuuawpBcvy5wQwZJ9g2k5HzfmXPAjwbQ9TT", + "type": "EmailPass", + "issuedBy": { + "name": "Altme" + }, + "email": "second.vc@gmail.com" + }, + "issuer": "did:web:app.altme.io:issuer", + "issuanceDate": "2024-07-11T12:54:20Z", + "proof": { + "type": "Ed25519Signature2018", + "proofPurpose": "assertionMethod", + "verificationMethod": "did:web:app.altme.io:issuer#key-1", + "created": "2024-07-11T12:54:28.288Z", + "jws": "eyJhbGciOiJFZERTQSIsImNyaXQiOlsiYjY0Il0sImI2NCI6ZmFsc2V9..kI_Fp52mHMxGR59cHPBf3NZD7QL5OqnsOR112hGJ9zmyAhd0288o9Hzazz-AmsMvW2LaaEDx_qWFwDwQ9Gi0DA" + }, + "expirationDate": "2025-07-11T12:54:20.699044Z", + "validFrom": "2024-07-11T12:54:28Z", + "validUntil": "2025-07-11T12:54:28.273969Z", + "issued": "2024-07-11T12:54:28Z" + } + ], + "proof": { + "type": "Ed25519Signature2018", + "proofPurpose": "authentication", + "challenge": "615e51a4-3f84-11ef-876f-0a1628958560", + "verificationMethod": "did:key:z6Mkj5B9HcSKWGuuawpBcvy5wQwZJ9g2k5HzfmXPAjwbQ9TT#z6Mkj5B9HcSKWGuuawpBcvy5wQwZJ9g2k5HzfmXPAjwbQ9TT", + "created": "2024-07-11T12:52:05.187708Z", + "domain": "https://talao.co/", + "jws": "eyJhbGciOiJFZERTQSIsImNyaXQiOlsiYjY0Il0sImI2NCI6ZmFsc2V9..ux7ny5CsclcU_4oovjitC4r7EktTOBH_qbdbca7OUKNbyFW-6CbREe0xj_izYxEqESvoJ-YfuvTkpgR35uw7AQ" + }, + "holder": "did:key:z6Mkj5B9HcSKWGuuawpBcvy5wQwZJ9g2k5HzfmXPAjwbQ9TT" +} diff --git a/vclogin/__tests__/testdata/presentations/VP_MultiVC.json b/vclogin/__tests__/testdata/presentations/VP_MultiVC.json new file mode 100644 index 0000000..959963d --- /dev/null +++ b/vclogin/__tests__/testdata/presentations/VP_MultiVC.json @@ -0,0 +1,122 @@ +{ + "@context": ["https://www.w3.org/2018/credentials/v1"], + "id": "urn:uuid:2c0b29f7-c530-4405-b509-d34d4a8a4028", + "type": ["VerifiablePresentation"], + "verifiableCredential": [ + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + { + "EmailPass": { + "@context": { + "@protected": true, + "@version": 1.1, + "email": "schema:email", + "id": "@id", + "issuedBy": { + "@context": { + "@protected": true, + "@version": 1.1, + "logo": { + "@id": "schema:image", + "@type": "@id" + }, + "name": "schema:name" + }, + "@id": "schema:issuedBy" + }, + "schema": "https://schema.org/", + "type": "@type" + }, + "@id": "https://github.com/TalaoDAO/context#emailpass" + } + } + ], + "id": "urn:uuid:a8e2b30c-eeb6-11ee-a822-0a1628958560", + "type": ["VerifiableCredential", "EmailPass"], + "credentialSubject": { + "id": "did:key:z6Mkj5B9HcSKWGuuawpBcvy5wQwZJ9g2k5HzfmXPAjwbQ9TT", + "issuedBy": { + "name": "Altme" + }, + "email": "first.vc@gmail.com", + "type": "EmailPass" + }, + "issuer": "did:web:app.altme.io:issuer", + "issuanceDate": "2024-03-30T16:57:55Z", + "proof": { + "type": "Ed25519Signature2018", + "proofPurpose": "assertionMethod", + "verificationMethod": "did:web:app.altme.io:issuer#key-1", + "created": "2024-03-30T16:57:58.048Z", + "jws": "eyJhbGciOiJFZERTQSIsImNyaXQiOlsiYjY0Il0sImI2NCI6ZmFsc2V9..F9EXluMjwPtCe8A0WKusX0zSsCi6JKh0HDLuNk47-Wvig_8wrwh56IocbNMyNG8b2J4wGuXUuGLTkQuftx6iDA" + }, + "expirationDate": "2025-03-30T16:57:55.390537Z", + "issued": "2024-03-30T16:57:57Z", + "validUntil": "2025-03-30T16:57:57.995257Z", + "validFrom": "2024-03-30T16:57:57Z" + }, + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + { + "VerifiableId": { + "@context": { + "@protected": true, + "@version": 1.1, + "dateIssued": "schema:dateIssued", + "dateOfBirth": "schema:birthDate", + "familyName": "schema:lastName", + "firstName": "schema:firstName", + "gender": "schema:gender", + "id": "@id", + "idRecto": "schema:image", + "idVerso": "schema:image", + "schema": "https://schema.org/", + "type": "@type" + }, + "@id": "urn:employeecredential" + } + } + ], + "id": "urn:uuid:f1fcf591-1396-11ef-8875-0a1628958560", + "type": ["VerifiableCredential", "VerifiableId"], + "credentialSubject": { + "id": "did:key:z6Mkj5B9HcSKWGuuawpBcvy5wQwZJ9g2k5HzfmXPAjwbQ9TT", + "dateOfBirth": "1930-10-01", + "firstName": "Bianca", + "gender": "F", + "dateIssued": "2022-12-20", + "familyName": "Castafiori", + "type": "VerifiableId" + }, + "issuer": "did:web:app.altme.io:issuer", + "issuanceDate": "2024-05-16T15:14:02Z", + "proof": { + "type": "EcdsaSecp256k1Signature2019", + "proofPurpose": "assertionMethod", + "verificationMethod": "did:web:app.altme.io:issuer#key-3", + "created": "2024-05-16T15:14:09.781Z", + "jws": "eyJhbGciOiJFUzI1NksiLCJjcml0IjpbImI2NCJdLCJiNjQiOmZhbHNlfQ..smC_xxfdYC9fVhv2ETHWLBR9KCFlXRkujTJjQH6vAA47J5nal7QlVNeDGcb_nOlASBiGY_pG4PzaqoPe6-9q4A" + }, + "expirationDate": "2025-05-16T15:14:02Z", + "credentialSchema": { + "id": "https://api-conformance.ebsi.eu/trusted-schemas-registry/v2/schemas/z22ZAMdQtNLwi51T2vdZXGGZaYyjrsuP1yzWyXZirCAHv", + "type": "FullJsonSchemaValidator2021" + }, + "validUntil": "2025-05-16T15:14:09.768457Z", + "issued": "2024-05-16T15:14:09Z", + "validFrom": "2024-05-16T15:14:09Z" + } + ], + "proof": { + "type": "Ed25519Signature2018", + "proofPurpose": "authentication", + "challenge": "615e51a4-3f84-11ef-876f-0a1628958560", + "verificationMethod": "did:key:z6Mkj5B9HcSKWGuuawpBcvy5wQwZJ9g2k5HzfmXPAjwbQ9TT#z6Mkj5B9HcSKWGuuawpBcvy5wQwZJ9g2k5HzfmXPAjwbQ9TT", + "created": "2024-07-11T12:52:05.187708Z", + "domain": "https://talao.co/", + "jws": "eyJhbGciOiJFZERTQSIsImNyaXQiOlsiYjY0Il0sImI2NCI6ZmFsc2V9..ux7ny5CsclcU_4oovjitC4r7EktTOBH_qbdbca7OUKNbyFW-6CbREe0xj_izYxEqESvoJ-YfuvTkpgR35uw7AQ" + }, + "holder": "did:key:z6Mkj5B9HcSKWGuuawpBcvy5wQwZJ9g2k5HzfmXPAjwbQ9TT" +} diff --git a/vclogin/__tests__/unit/lib/evaluateLoginPolicy.test.ts b/vclogin/__tests__/unit/lib/evaluateLoginPolicy.test.ts index 90e38d2..a5e8b98 100644 --- a/vclogin/__tests__/unit/lib/evaluateLoginPolicy.test.ts +++ b/vclogin/__tests__/unit/lib/evaluateLoginPolicy.test.ts @@ -6,6 +6,8 @@ import { describe, it, expect } from "vitest"; import { isTrustedPresentation } from "@/lib/extractClaims"; import vpEmployee from "@/testdata/presentations/VP_EmployeeCredential.json"; +import vpMultiEmail from "@/testdata/presentations/VP_MultiEmailPass.json"; +import vpMultiVC from "@/testdata/presentations/VP_MultiVC.json"; import vpEmail from "@/testdata/presentations/VP_EmailPass.json"; import vpTezos from "@/testdata/presentations/VP_TezosAssociatedAddress.json"; import policyAcceptAnything from "@/testdata/policies/acceptAnything.json"; @@ -14,65 +16,156 @@ import policyEmailFromAltme from "@/testdata/policies/acceptEmailFromAltme.json" import policyFromAltme from "@/testdata/policies/acceptFromAltme.json"; import policyEmailFromAltmeConstr from "@/testdata/policies/acceptEmailFromAltmeConstr.json"; import policyEmployeeFromAnyoneConstr from "@/testdata/policies/acceptEmployeeFromAnyoneConstr.json"; +import policyMultiEmailFromAltmeConstr from "@/testdata/policies/acceptMultiEmailFromAltmeConstr.json"; +import policyMultiEmailFromAltmeSimpleConstr from "@/testdata/policies/acceptMultiEmailFromAltmeSimpleConstr.json"; +import policyMultiVCFromAltmeConstr from "@/testdata/policies/acceptMultiVCFromAltmeConstr.json"; +import policyMultiVCFromAltmeSimpleConstr from "@/testdata/policies/acceptMultiVCFromAltmeSimpleConstr.json"; describe("evaluateLoginPolicy", () => { - it("defaults to false if no policy is available", () => { - var trusted = isTrustedPresentation(vpEmployee, undefined); + it("defaults to false if no policy is available", async () => { + var trusted = await isTrustedPresentation(vpEmployee, undefined); expect(trusted).toBe(false); }); - it("accepts valid VPs with acceptAnything policy", () => { - var trusted = isTrustedPresentation(vpEmployee, policyAcceptAnything); + it("accepts valid VPs with acceptAnything policy", async () => { + var trusted = await isTrustedPresentation(vpEmployee, policyAcceptAnything); expect(trusted).toBe(true); - trusted = isTrustedPresentation(vpEmail, policyAcceptAnything); + trusted = await isTrustedPresentation(vpEmail, policyAcceptAnything); expect(trusted).toBe(true); - trusted = isTrustedPresentation(vpTezos, policyAcceptAnything); + trusted = await isTrustedPresentation(vpTezos, policyAcceptAnything); expect(trusted).toBe(true); }); - it("accepts only VP with credential(s) of a certain type", () => { - var trusted = isTrustedPresentation(vpEmployee, policyEmployeeFromAnyone); + it("accepts only VP with credential(s) of a certain type", async () => { + var trusted = await isTrustedPresentation( + vpEmployee, + policyEmployeeFromAnyone, + ); expect(trusted).toBe(true); - trusted = isTrustedPresentation(vpEmail, policyEmployeeFromAnyone); + trusted = await isTrustedPresentation(vpEmail, policyEmployeeFromAnyone); expect(trusted).toBe(false); - trusted = isTrustedPresentation(vpTezos, policyEmployeeFromAnyone); + trusted = await isTrustedPresentation(vpTezos, policyEmployeeFromAnyone); expect(trusted).toBe(false); }); - it("accepts only VP with credential(s) of a certain type from a certain issuer", () => { - var trusted = isTrustedPresentation(vpEmail, policyEmailFromAltme); + it("accepts only VP with credential(s) of a certain type from a certain issuer", async () => { + var trusted = await isTrustedPresentation(vpEmail, policyEmailFromAltme); expect(trusted).toBe(true); - trusted = isTrustedPresentation(vpEmployee, policyEmailFromAltme); + trusted = await isTrustedPresentation(vpEmployee, policyEmailFromAltme); expect(trusted).toBe(false); - trusted = isTrustedPresentation(vpTezos, policyEmailFromAltme); + trusted = await isTrustedPresentation(vpTezos, policyEmailFromAltme); expect(trusted).toBe(false); }); - it("accepts all VP from a certain issuer", () => { - var trusted = isTrustedPresentation(vpEmail, policyFromAltme); + it("accepts all VP from a certain issuer", async () => { + var trusted = await isTrustedPresentation(vpEmail, policyFromAltme); expect(trusted).toBe(true); - trusted = isTrustedPresentation(vpEmployee, policyFromAltme); + trusted = await isTrustedPresentation(vpEmployee, policyFromAltme); expect(trusted).toBe(false); }); - it("accepts only VP with credential(s) with simple constraint", () => { - var trusted = isTrustedPresentation(vpEmail, policyEmailFromAltmeConstr); + it("accepts only VP with credential(s) with simple constraint", async () => { + var trusted = await isTrustedPresentation( + vpEmail, + policyEmailFromAltmeConstr, + ); expect(trusted).toBe(true); - trusted = isTrustedPresentation(vpEmployee, policyEmailFromAltmeConstr); + trusted = await isTrustedPresentation( + vpEmployee, + policyEmailFromAltmeConstr, + ); expect(trusted).toBe(false); - trusted = isTrustedPresentation(vpTezos, policyEmailFromAltmeConstr); + trusted = await isTrustedPresentation(vpTezos, policyEmailFromAltmeConstr); expect(trusted).toBe(false); }); - it("accepts only VP with credential(s) with complicated constraint", () => { - var trusted = isTrustedPresentation( + it("accepts only VP with credential(s) with complicated constraint", async () => { + var trusted = await isTrustedPresentation( vpEmployee, policyEmployeeFromAnyoneConstr, ); expect(trusted).toBe(true); - trusted = isTrustedPresentation(vpEmail, policyEmployeeFromAnyoneConstr); + trusted = await isTrustedPresentation( + vpEmail, + policyEmployeeFromAnyoneConstr, + ); expect(trusted).toBe(false); - trusted = isTrustedPresentation(vpTezos, policyEmployeeFromAnyoneConstr); + trusted = await isTrustedPresentation( + vpTezos, + policyEmployeeFromAnyoneConstr, + ); + expect(trusted).toBe(false); + }); + + it("accepts only VP with two credentials (same type of VCs that have common credentialSubject fields) with cross constraints", async () => { + var trusted = await isTrustedPresentation( + vpMultiEmail, + policyMultiEmailFromAltmeConstr, + ); + expect(trusted).toBe(true); + trusted = await isTrustedPresentation( + vpEmail, + policyMultiEmailFromAltmeConstr, + ); + expect(trusted).toBe(false); + trusted = await isTrustedPresentation( + vpTezos, + policyMultiEmailFromAltmeConstr, + ); + expect(trusted).toBe(false); + }); + + it("accepts only VP with two credentials (same type of VCs that have common credentialSubject fields) with simple constraints", async () => { + var trusted = await isTrustedPresentation( + vpMultiEmail, + policyMultiEmailFromAltmeSimpleConstr, + ); + expect(trusted).toBe(true); + trusted = await isTrustedPresentation( + vpEmail, + policyMultiEmailFromAltmeSimpleConstr, + ); + expect(trusted).toBe(false); + trusted = await isTrustedPresentation( + vpTezos, + policyMultiEmailFromAltmeSimpleConstr, + ); + expect(trusted).toBe(false); + }); + + it("accepts only VP with two credentials (different type of VCs that have common credentialSubject fields) with cross constraints", async () => { + var trusted = await isTrustedPresentation( + vpMultiVC, + policyMultiVCFromAltmeConstr, + ); + expect(trusted).toBe(true); + trusted = await isTrustedPresentation( + vpEmail, + policyMultiVCFromAltmeConstr, + ); + expect(trusted).toBe(false); + trusted = await isTrustedPresentation( + vpMultiEmail, + policyMultiVCFromAltmeConstr, + ); + expect(trusted).toBe(false); + }); + + it("accepts only VP with two credentials (different type of VCs that have common credentialSubject fields) with simple constraints", async () => { + var trusted = await isTrustedPresentation( + vpMultiVC, + policyMultiVCFromAltmeSimpleConstr, + ); + expect(trusted).toBe(true); + trusted = await isTrustedPresentation( + vpEmail, + policyMultiVCFromAltmeSimpleConstr, + ); + expect(trusted).toBe(false); + trusted = await isTrustedPresentation( + vpMultiEmail, + policyMultiVCFromAltmeSimpleConstr, + ); expect(trusted).toBe(false); }); }); diff --git a/vclogin/__tests__/unit/lib/extractClaims.test.ts b/vclogin/__tests__/unit/lib/extractClaims.test.ts index 182e8f4..65daf38 100644 --- a/vclogin/__tests__/unit/lib/extractClaims.test.ts +++ b/vclogin/__tests__/unit/lib/extractClaims.test.ts @@ -7,14 +7,21 @@ import { describe, it, expect } from "vitest"; import { extractClaims } from "@/lib/extractClaims"; import vpEmployee from "@/testdata/presentations/VP_EmployeeCredential.json"; import vpEmail from "@/testdata/presentations/VP_EmailPass.json"; +import vpMultiEmail from "@/testdata/presentations/VP_MultiEmailPass.json"; +import vpMultiVC from "@/testdata/presentations/VP_MultiVC.json"; import policyAcceptAnything from "@/testdata/policies/acceptAnything.json"; +import policyAcceptAnythingMisconfigured from "@/testdata/policies/acceptAnythingMisconfigured.json"; +import policyAcceptAnythingMultiVC from "@/testdata/policies/acceptAnythingMultiVC.json"; import policyEmailFromAltme from "@/testdata/policies/acceptEmailFromAltme.json"; import policyEmailFromAltmeConstr from "@/testdata/policies/acceptEmailFromAltmeConstr.json"; import policyEmployeeFromAnyone from "@/testdata/policies/acceptEmployeeFromAnyone.json"; +import policyMultiEmailFromAltmeConstr from "@/testdata/policies/acceptMultiEmailFromAltmeConstr.json"; +import policyMultiVCromAltmeConstr from "@/testdata/policies/acceptMultiVCFromAltmeConstr.json"; +import policyAcceptMultiVCMisconfigured from "@/testdata/policies/acceptMultiVCFromAltmeMisconfigured.json"; describe("extractClaims", () => { - it("all subject claims from an EmployeeCredential are extracted", () => { - var claims = extractClaims(vpEmployee, policyAcceptAnything); + it("all subject claims from an EmployeeCredential are extracted", async () => { + var claims = await extractClaims(vpEmployee, policyAcceptAnything); var expected = { tokenAccess: {}, tokenId: { @@ -38,8 +45,8 @@ describe("extractClaims", () => { expect(claims).toStrictEqual(expected); }); - it("all designated claims from an EmailPass Credential are mapped", () => { - var claims = extractClaims(vpEmail, policyEmailFromAltme); + it("all designated claims from an EmailPass Credential are mapped", async () => { + var claims = await extractClaims(vpEmail, policyEmailFromAltme); var expected = { tokenId: { email: "felix.hoops@tum.de", @@ -49,8 +56,8 @@ describe("extractClaims", () => { expect(claims).toStrictEqual(expected); }); - it("all designated claims from an EmailPass Credential are mapped (constrained)", () => { - var claims = extractClaims(vpEmail, policyEmailFromAltmeConstr); + it("all designated claims from an EmailPass Credential are mapped (constrained)", async () => { + var claims = await extractClaims(vpEmail, policyEmailFromAltmeConstr); var expected = { tokenId: { email: "felix.hoops@tum.de", @@ -60,8 +67,8 @@ describe("extractClaims", () => { expect(claims).toStrictEqual(expected); }); - it("all designated claims from an EmployeeCredential are extracted", () => { - var claims = extractClaims(vpEmployee, policyEmployeeFromAnyone); + it("all designated claims from an EmployeeCredential are extracted", async () => { + var claims = await extractClaims(vpEmployee, policyEmployeeFromAnyone); var expected = { tokenAccess: {}, tokenId: { @@ -72,4 +79,82 @@ describe("extractClaims", () => { }; expect(claims).toStrictEqual(expected); }); + + it("all designated claims from a multi VC (EmailPass) are extracted", async () => { + var claims = await extractClaims( + vpMultiEmail, + policyMultiEmailFromAltmeConstr, + ); + var expected = { + tokenAccess: {}, + tokenId: { + email: "first.vc@gmail.com", + type: "EmailPass", + }, + }; + expect(claims).toStrictEqual(expected); + }); + + it("all designated claims from a multi VC (EmailPass and VerifiableId) are extracted", async () => { + var claims = await extractClaims(vpMultiVC, policyMultiVCromAltmeConstr); + var expected = { + tokenAccess: {}, + tokenId: { + email: "first.vc@gmail.com", + firstName: "Bianca", + }, + }; + expect(claims).toStrictEqual(expected); + }); + + it("all designated claims from a multi VC (EmailPass and VerifiableId) with misconfigured policy", async () => { + var claims = await extractClaims( + vpMultiVC, + policyAcceptMultiVCMisconfigured, + ); + var expected = { + tokenAccess: {}, + tokenId: {}, + }; + expect(claims).toStrictEqual(expected); + }); + + it("all designated claims from a EmailPass with misconfigured policy", async () => { + var claims = await extractClaims( + vpEmail, + policyAcceptAnythingMisconfigured, + ); + var expected = { + tokenAccess: {}, + tokenId: {}, + }; + expect(claims).toStrictEqual(expected); + }); + + it("all designated claims from a multi VC (EmailPass and VerifiableId)", async () => { + var claims = await extractClaims(vpMultiVC, policyAcceptAnythingMultiVC); + var expected = { + tokenAccess: {}, + tokenId: { + firstCredentialSubject: { + email: "first.vc@gmail.com", + id: "did:key:z6Mkj5B9HcSKWGuuawpBcvy5wQwZJ9g2k5HzfmXPAjwbQ9TT", + issuedBy: { + name: "Altme", + }, + type: "EmailPass", + }, + secondCredentialSubject: { + dateIssued: "2022-12-20", + dateOfBirth: "1930-10-01", + familyName: "Castafiori", + firstName: "Bianca", + gender: "F", + id: "did:key:z6Mkj5B9HcSKWGuuawpBcvy5wQwZJ9g2k5HzfmXPAjwbQ9TT", + type: "VerifiableId", + }, + }, + }; + expect(claims).toStrictEqual(expected); + }); }); diff --git a/vclogin/__tests__/unit/lib/verifyPresentation.test.ts b/vclogin/__tests__/unit/lib/verifyPresentation.test.ts index bf9b896..35713ac 100644 --- a/vclogin/__tests__/unit/lib/verifyPresentation.test.ts +++ b/vclogin/__tests__/unit/lib/verifyPresentation.test.ts @@ -12,20 +12,18 @@ import vpEmployee from "@/testdata/presentations/VP_EmployeeCredential.json"; // of all those web requests would be lots of work describe("verifyPresentation", () => { // kind of a sanity check - it("didkit verifies a valid Employee VC", () => { - return verifyCredential( + it("didkit verifies a valid Employee VC", async () => { + const result = await verifyCredential( JSON.stringify(vpEmployee.verifiableCredential), "{}", - ).then((result) => { - const verifyResult = JSON.parse(result); - //console.log(verifyResult); - expect(verifyResult.errors.length).toBe(0); - }); + ); + const verifyResult = JSON.parse(result); + //console.log(verifyResult); + expect(verifyResult.errors.length).toBe(0); }); - it("verifies a valid VP with Employee VC", () => { - return verifyAuthenticationPresentation(vpEmployee).then((result) => { - expect(result).toBe(true); - }); + it("verifies a valid VP with Employee VC", async () => { + const result = await verifyAuthenticationPresentation(vpEmployee); + expect(result).toBe(true); }); }); diff --git a/vclogin/lib/extractClaims.ts b/vclogin/lib/extractClaims.ts index 644ff37..3d85e01 100644 --- a/vclogin/lib/extractClaims.ts +++ b/vclogin/lib/extractClaims.ts @@ -17,7 +17,6 @@ export const isTrustedPresentation = async (VP: any, policy?: LoginPolicy) => { if (!policy && configuredPolicy === undefined) return false; var usedPolicy = policy ? policy : configuredPolicy!; - const creds = Array.isArray(VP.verifiableCredential) ? VP.verifiableCredential : [VP.verifiableCredential]; @@ -35,7 +34,11 @@ export const extractClaims = async (VP: any, policy?: LoginPolicy) => { ? VP.verifiableCredential : [VP.verifiableCredential]; - const vcClaims = creds.map((vc: any) => extractClaimsFromVC(vc, usedPolicy)); + const vcClaims = creds.map((vc: any, credentialIndex: number) => + // Important: credentialIndex defines helps us to extract the correct claims from the policy + // Ideally, the credentialIndex should be the same as the credentialId in the policy + extractClaimsFromVC(vc, usedPolicy, (credentialIndex + 1).toString()), + ); const claims: any = {}; vcClaims.forEach((claim: any) => { @@ -109,9 +112,10 @@ const isCredentialFittingPattern = ( } for (const claim of pattern.claims) { + const claimPath = claim.claimPath.replace(/\$\d+\./g, "$."); if ( (!Object.hasOwn(claim, "required") || claim.required) && - jp.paths(cred, claim.claimPath).length === 0 + jp.paths(cred, claimPath).length === 0 ) { return false; } @@ -151,6 +155,7 @@ const isValidConstraintFit = ( VP: any, ): boolean => { const credDict: any = {}; + credFit = credFit.flat(Infinity); for (let i = 0; i < policy.length; i++) { credDict[policy[i].credentialId] = credFit[i]; } @@ -210,10 +215,8 @@ const evaluateConstraint = ( case "equals": return a === b; case "equalsDID": - return ( - a.split("#").slice(0, -1).join("#") === - b.split("#").slice(0, -1).join("#") - ); + b = b.includes("#") ? b.split("#").slice(0, -1).join("#") : b; + return a.split("#").slice(0, -1).join("#") === b; case "startsWith": return a.startsWith(b); case "endsWith": @@ -308,14 +311,23 @@ const resolveValue = ( return resolveSingleNodeValue(expression, cred, VP); }; -const extractClaimsFromVC = (VC: any, policy: LoginPolicy) => { +const extractClaimsFromVC = ( + VC: any, + policy: LoginPolicy, + credentialIndex: string, +) => { let reiterateOuterLoop = false; for (let expectation of policy) { + const credentialId = expectation.credentialId; for (let pattern of expectation.patterns) { + if (credentialId !== credentialIndex) { + break; + } if (pattern.issuer === VC.issuer || pattern.issuer === "*") { const containsAllRequired = pattern.claims.filter((claim: ClaimEntry) => { - const claimPathLength = jp.paths(VC, claim.claimPath).length; + const claimPath = claim.claimPath.replace(`${credentialId}`, ""); + const claimPathLength = jp.paths(VC, claimPath).length; return claim.required && claimPathLength === 1; }).length > 0 || pattern.claims.filter((claim: ClaimEntry) => claim.required) @@ -331,7 +343,9 @@ const extractClaimsFromVC = (VC: any, policy: LoginPolicy) => { }; for (let claim of pattern.claims) { - const nodes = jp.nodes(VC, claim.claimPath); + const claimPath = claim.claimPath.replace(`${credentialId}`, ""); + + const nodes = jp.nodes(VC, claimPath); let newPath = claim.newPath; let value: any; @@ -366,6 +380,7 @@ const extractClaimsFromVC = (VC: any, policy: LoginPolicy) => { claim.token === "access_token" ? extractedClaims.tokenAccess : extractedClaims.tokenId; + jp.value(claimTarget, newPath, value); } From 019d6af6098818ca8a83bf6faf52bb17d4905961 Mon Sep 17 00:00:00 2001 From: Ilayda Cansin Koc Date: Wed, 24 Jul 2024 17:28:29 +0200 Subject: [PATCH 66/85] remove old tests that are not needed anymore Signed-off-by: Ilayda Cansin Koc --- .../__tests__/lib/evaluateLoginPolicy.test.js | 77 ------------------- vclogin/__tests__/lib/extractClaims.test.js | 74 ------------------ .../generatePresentationDefinition.test.js | 33 -------- .../__tests__/lib/verifyPresentation.test.js | 30 -------- vclogin/__tests__/pages/index.test.js | 18 ----- vclogin/__tests__/pages/login.test.js | 44 ----------- 6 files changed, 276 deletions(-) delete mode 100644 vclogin/__tests__/lib/evaluateLoginPolicy.test.js delete mode 100644 vclogin/__tests__/lib/extractClaims.test.js delete mode 100644 vclogin/__tests__/lib/generatePresentationDefinition.test.js delete mode 100644 vclogin/__tests__/lib/verifyPresentation.test.js delete mode 100644 vclogin/__tests__/pages/index.test.js delete mode 100644 vclogin/__tests__/pages/login.test.js diff --git a/vclogin/__tests__/lib/evaluateLoginPolicy.test.js b/vclogin/__tests__/lib/evaluateLoginPolicy.test.js deleted file mode 100644 index 6439a8e..0000000 --- a/vclogin/__tests__/lib/evaluateLoginPolicy.test.js +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Copyright 2024 Software Engineering for Business Information Systems (sebis) . - * SPDX-License-Identifier: MIT - */ - -import { isTrustedPresentation } from "@/lib/extractClaims"; -import vpEmployee from "@/testdata/presentations/VP_EmployeeCredential.json"; -import vpEmail from "@/testdata/presentations/VP_EmailPass.json"; -import vpTezos from "@/testdata/presentations/VP_TezosAssociatedAddress.json"; -import policyAcceptAnything from "@/testdata/policies/acceptAnything.json"; -import policyEmployeeFromAnyone from "@/testdata/policies/acceptEmployeeFromAnyone.json"; -import policyEmailFromAltme from "@/testdata/policies/acceptEmailFromAltme.json"; -import policyFromAltme from "@/testdata/policies/acceptFromAltme.json"; -import policyEmailFromAltmeConstr from "@/testdata/policies/acceptEmailFromAltmeConstr.json"; -import policyEmployeeFromAnyoneConstr from "@/testdata/policies/acceptEmployeeFromAnyoneConstr.json"; - -describe("evaluateLoginPolicy", () => { - it("defaults to false if no policy is available", () => { - var trusted = isTrustedPresentation(vpEmployee, undefined); - expect(trusted).toBe(false); - }); - - it("accepts valid VPs with acceptAnything policy", () => { - var trusted = isTrustedPresentation(vpEmployee, policyAcceptAnything); - expect(trusted).toBe(true); - trusted = isTrustedPresentation(vpEmail, policyAcceptAnything); - expect(trusted).toBe(true); - trusted = isTrustedPresentation(vpTezos, policyAcceptAnything); - expect(trusted).toBe(true); - }); - - it("accepts only VP with credential(s) of a certain type", () => { - var trusted = isTrustedPresentation(vpEmployee, policyEmployeeFromAnyone); - expect(trusted).toBe(true); - trusted = isTrustedPresentation(vpEmail, policyEmployeeFromAnyone); - expect(trusted).toBe(false); - trusted = isTrustedPresentation(vpTezos, policyEmployeeFromAnyone); - expect(trusted).toBe(false); - }); - - it("accepts only VP with credential(s) of a certain type from a certain issuer", () => { - var trusted = isTrustedPresentation(vpEmail, policyEmailFromAltme); - expect(trusted).toBe(true); - trusted = isTrustedPresentation(vpEmployee, policyEmailFromAltme); - expect(trusted).toBe(false); - trusted = isTrustedPresentation(vpTezos, policyEmailFromAltme); - expect(trusted).toBe(false); - }); - - it("accepts all VP from a certain issuer", () => { - var trusted = isTrustedPresentation(vpEmail, policyFromAltme); - expect(trusted).toBe(true); - trusted = isTrustedPresentation(vpEmployee, policyFromAltme); - expect(trusted).toBe(false); - }); - - it("accepts only VP with credential(s) with simple constraint", () => { - var trusted = isTrustedPresentation(vpEmail, policyEmailFromAltmeConstr); - expect(trusted).toBe(true); - trusted = isTrustedPresentation(vpEmployee, policyEmailFromAltmeConstr); - expect(trusted).toBe(false); - trusted = isTrustedPresentation(vpTezos, policyEmailFromAltmeConstr); - expect(trusted).toBe(false); - }); - - it("accepts only VP with credential(s) with complicated constraint", () => { - var trusted = isTrustedPresentation( - vpEmployee, - policyEmployeeFromAnyoneConstr, - ); - expect(trusted).toBe(true); - trusted = isTrustedPresentation(vpEmail, policyEmployeeFromAnyoneConstr); - expect(trusted).toBe(false); - trusted = isTrustedPresentation(vpTezos, policyEmployeeFromAnyoneConstr); - expect(trusted).toBe(false); - }); -}); diff --git a/vclogin/__tests__/lib/extractClaims.test.js b/vclogin/__tests__/lib/extractClaims.test.js deleted file mode 100644 index 4aae794..0000000 --- a/vclogin/__tests__/lib/extractClaims.test.js +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Copyright 2024 Software Engineering for Business Information Systems (sebis) . - * SPDX-License-Identifier: MIT - */ - -import { extractClaims } from "@/lib/extractClaims"; -import vpEmployee from "@/testdata/presentations/VP_EmployeeCredential.json"; -import vpEmail from "@/testdata/presentations/VP_EmailPass.json"; -import policyAcceptAnything from "@/testdata/policies/acceptAnything.json"; -import policyEmailFromAltme from "@/testdata/policies/acceptEmailFromAltme.json"; -import policyEmailFromAltmeConstr from "@/testdata/policies/acceptEmailFromAltmeConstr.json"; -import policyEmployeeFromAnyone from "@/testdata/policies/acceptEmployeeFromAnyone.json"; - -describe("extractClaims", () => { - it("all subject claims from an EmployeeCredential are extracted", () => { - var claims = extractClaims(vpEmployee, policyAcceptAnything); - var expected = { - tokenAccess: {}, - tokenId: { - subjectData: { - id: "did:key:z6MkkdC46uhBGjMYS2ZDLUwCrTWdaqZdTD3596sN4397oRNd", - hash: "9ecf754ffdad0c6de238f60728a90511780b2f7dbe2f0ea015115515f3f389cd", - leiCode: "391200FJBNU0YW987L26", - hasLegallyBindingName: "deltaDAO AG", - ethereumAddress: "0x4C84a36fCDb7Bc750294A7f3B5ad5CA8F74C4A52", - email: "test@test.com", - hasRegistrationNumber: "DEK1101R.HRB170364", - name: "Name Surname", - hasCountry: "GER", - type: "EmployeeCredential", - title: "CEO", - hasJurisdiction: "GER", - surname: "Surname", - }, - }, - }; - expect(claims).toStrictEqual(expected); - }); - - it("all designated claims from an EmailPass Credential are mapped", () => { - var claims = extractClaims(vpEmail, policyEmailFromAltme); - var expected = { - tokenId: { - email: "felix.hoops@tum.de", - }, - tokenAccess: {}, - }; - expect(claims).toStrictEqual(expected); - }); - - it("all designated claims from an EmailPass Credential are mapped (constrained)", () => { - var claims = extractClaims(vpEmail, policyEmailFromAltmeConstr); - var expected = { - tokenId: { - email: "felix.hoops@tum.de", - }, - tokenAccess: {}, - }; - expect(claims).toStrictEqual(expected); - }); - - it("all designated claims from an EmployeeCredential are extracted", () => { - var claims = extractClaims(vpEmployee, policyEmployeeFromAnyone); - var expected = { - tokenAccess: {}, - tokenId: { - email: "test@test.com", - name: "Name Surname", - companyName: "deltaDAO AG", - }, - }; - expect(claims).toStrictEqual(expected); - }); -}); diff --git a/vclogin/__tests__/lib/generatePresentationDefinition.test.js b/vclogin/__tests__/lib/generatePresentationDefinition.test.js deleted file mode 100644 index afa6a04..0000000 --- a/vclogin/__tests__/lib/generatePresentationDefinition.test.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright 2024 Software Engineering for Business Information Systems (sebis) . - * SPDX-License-Identifier: MIT - */ - -import { generatePresentationDefinition } from "@/lib/generatePresentationDefinition"; -import policyAcceptAnything from "@/testdata/policies/acceptAnything.json"; -import policyEmployeeFromAnyone from "@/testdata/policies/acceptEmployeeFromAnyone.json"; -import policyEmailFromAltme from "@/testdata/policies/acceptEmailFromAltme.json"; -import policyFromAltme from "@/testdata/policies/acceptFromAltme.json"; - -import crypto from "crypto"; - -Object.defineProperty(global, "crypto", { - value: { - randomUUID: () => crypto.randomUUID(), - }, -}); - -describe("generatePresentationDefinition", () => { - it("runs without error", () => { - expect(generatePresentationDefinition(policyAcceptAnything)).not.toBe( - undefined, - ); - expect(generatePresentationDefinition(policyEmployeeFromAnyone)).not.toBe( - undefined, - ); - expect(generatePresentationDefinition(policyEmailFromAltme)).not.toBe( - undefined, - ); - expect(generatePresentationDefinition(policyFromAltme)).not.toBe(undefined); - }); -}); diff --git a/vclogin/__tests__/lib/verifyPresentation.test.js b/vclogin/__tests__/lib/verifyPresentation.test.js deleted file mode 100644 index a962d0f..0000000 --- a/vclogin/__tests__/lib/verifyPresentation.test.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright 2024 Software Engineering for Business Information Systems (sebis) . - * SPDX-License-Identifier: MIT - */ - -import { verifyCredential } from "@spruceid/didkit-wasm-node"; -import { verifyAuthenticationPresentation } from "@/lib/verifyPresentation"; -import vpEmployee from "@/testdata/presentations/VP_EmployeeCredential.json"; - -// WARNING: all of this relies on web requests (e.g., contexts, status) and may fail in the future, but proper mocking -// of all those web requests would be lots of work -describe("verifyPresentation", () => { - // kind of a sanity check - it("didkit verifies a valid Employee VC", () => { - return verifyCredential( - JSON.stringify(vpEmployee.verifiableCredential), - "{}", - ).then((result) => { - const verifyResult = JSON.parse(result); - //console.log(verifyResult); - expect(verifyResult.errors.length).toBe(0); - }); - }); - - it("verifies a valid VP with Employee VC", () => { - return verifyAuthenticationPresentation(vpEmployee).then((result) => { - expect(result).toBe(true); - }); - }); -}); diff --git a/vclogin/__tests__/pages/index.test.js b/vclogin/__tests__/pages/index.test.js deleted file mode 100644 index 1d6f79b..0000000 --- a/vclogin/__tests__/pages/index.test.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright 2024 Software Engineering for Business Information Systems (sebis) . - * SPDX-License-Identifier: MIT - */ - -import { render, screen } from "@testing-library/react"; -import Home from "@/pages/index"; -import "@testing-library/jest-dom"; - -describe("Home", () => { - it("renders a heading", () => { - render(); - const heading = screen.getByRole("heading", { - name: /GX\sCredentials\sBridge/i, - }); - expect(heading).toBeInTheDocument(); - }); -}); diff --git a/vclogin/__tests__/pages/login.test.js b/vclogin/__tests__/pages/login.test.js deleted file mode 100644 index b50f5c8..0000000 --- a/vclogin/__tests__/pages/login.test.js +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright 2024 Software Engineering for Business Information Systems (sebis) . - * SPDX-License-Identifier: MIT - */ - -import { render, screen } from "@testing-library/react"; -import Login from "@/pages/login"; -import "@testing-library/jest-dom"; - -jest.mock("next/router", () => jest.requireActual("next-router-mock")); -const searchParams = { login_challenge: "123" }; -jest.mock("next/navigation", () => ({ - __esModule: true, - useSearchParams: () => ({ - get: jest.fn((key) => searchParams[key]), - }), -})); - -describe("Login", () => { - it("renders a heading", () => { - render(); - const heading = screen.getByRole("heading", { - name: /Bridge/i, - }); - expect(heading).toBeInTheDocument(); - }); - - it("renders a CTA", () => { - render(); - const heading = screen.getByRole("heading", { - name: /Scan/i, - }); - - expect(heading).toBeInTheDocument(); - }); - - it("renders a qr code", () => { - render(); - const mainElement = screen.getByRole("main"); - // find canvas node under mainElement - const qrCanvas = mainElement.querySelector("canvas"); - expect(qrCanvas).toBeInTheDocument(); - }); -}); From 35fbc119f8b0acbd9a1c2e3d7b5d3cd814da87a4 Mon Sep 17 00:00:00 2001 From: Ilayda Cansin Koc Date: Wed, 24 Jul 2024 17:32:43 +0200 Subject: [PATCH 67/85] add a policy with complex constraints add test for the new policy refactor extractClaims to fix a bug related to accept policy with more than two VCs and complex constraints Signed-off-by: Ilayda Cansin Koc --- .../acceptMultiEmailFromAltmeConstr.json | 2 +- .../acceptMultiVCFromAltmeComplexConstr.json | 62 +++++++++++++++++++ .../unit/lib/evaluateLoginPolicy.test.ts | 23 ++++++- vclogin/lib/extractClaims.ts | 21 +++---- 4 files changed, 94 insertions(+), 14 deletions(-) create mode 100644 vclogin/__tests__/testdata/policies/acceptMultiVCFromAltmeComplexConstr.json diff --git a/vclogin/__tests__/testdata/policies/acceptMultiEmailFromAltmeConstr.json b/vclogin/__tests__/testdata/policies/acceptMultiEmailFromAltmeConstr.json index 0673b68..cea5fde 100644 --- a/vclogin/__tests__/testdata/policies/acceptMultiEmailFromAltmeConstr.json +++ b/vclogin/__tests__/testdata/policies/acceptMultiEmailFromAltmeConstr.json @@ -28,7 +28,7 @@ } ], "constraint": { - "op": "equalsDID", + "op": "equals", "a": "$2.credentialSubject.id", "b": "$1.credentialSubject.id" } diff --git a/vclogin/__tests__/testdata/policies/acceptMultiVCFromAltmeComplexConstr.json b/vclogin/__tests__/testdata/policies/acceptMultiVCFromAltmeComplexConstr.json new file mode 100644 index 0000000..560a7e5 --- /dev/null +++ b/vclogin/__tests__/testdata/policies/acceptMultiVCFromAltmeComplexConstr.json @@ -0,0 +1,62 @@ +[ + { + "credentialId": "1", + "patterns": [ + { + "issuer": "did:web:app.altme.io:issuer", + "claims": [ + { + "claimPath": "$.credentialSubject.email" + } + ], + "constraint": { + "op": "equalsDID", + "a": "$VP.proof.verificationMethod", + "b": "$1.credentialSubject.id" + } + } + ] + }, + { + "credentialId": "2", + "patterns": [ + { + "issuer": "did:web:app.altme.io:issuer", + "claims": [ + { + "claimPath": "$.credentialSubject.type" + } + ], + "constraint": { + "op": "and", + "a": { + "op": "equalsDID", + "a": "$VP.proof.verificationMethod", + "b": "$1.credentialSubject.id" + }, + "b": { + "op": "and", + "a": { + "op": "endsWith", + "a": "$2.credentialSubject.firstName", + "b": "ca" + }, + "b": { + "op": "or", + "a": { + "op": "matches", + "a": "$2.credentialSubject.familyName", + "b": "C[aoe]stafiori" + }, + "b": { + "op": "equals", + "a": "$2.credentialSubject.type", + "b": "VerifiableId" + } + } + } + } + } + ] + } +] diff --git a/vclogin/__tests__/unit/lib/evaluateLoginPolicy.test.ts b/vclogin/__tests__/unit/lib/evaluateLoginPolicy.test.ts index a5e8b98..c3022a9 100644 --- a/vclogin/__tests__/unit/lib/evaluateLoginPolicy.test.ts +++ b/vclogin/__tests__/unit/lib/evaluateLoginPolicy.test.ts @@ -20,6 +20,7 @@ import policyMultiEmailFromAltmeConstr from "@/testdata/policies/acceptMultiEmai import policyMultiEmailFromAltmeSimpleConstr from "@/testdata/policies/acceptMultiEmailFromAltmeSimpleConstr.json"; import policyMultiVCFromAltmeConstr from "@/testdata/policies/acceptMultiVCFromAltmeConstr.json"; import policyMultiVCFromAltmeSimpleConstr from "@/testdata/policies/acceptMultiVCFromAltmeSimpleConstr.json"; +import policyMultiVCFromAltmeComplexConstr from "@/testdata/policies/acceptMultiVCFromAltmeComplexConstr.json"; describe("evaluateLoginPolicy", () => { it("defaults to false if no policy is available", async () => { @@ -133,7 +134,7 @@ describe("evaluateLoginPolicy", () => { expect(trusted).toBe(false); }); - it("accepts only VP with two credentials (different type of VCs that have common credentialSubject fields) with cross constraints", async () => { + it("accepts only VP with two credentials with cross constraints", async () => { var trusted = await isTrustedPresentation( vpMultiVC, policyMultiVCFromAltmeConstr, @@ -151,7 +152,7 @@ describe("evaluateLoginPolicy", () => { expect(trusted).toBe(false); }); - it("accepts only VP with two credentials (different type of VCs that have common credentialSubject fields) with simple constraints", async () => { + it("accepts only VP with two credentials with simple constraints", async () => { var trusted = await isTrustedPresentation( vpMultiVC, policyMultiVCFromAltmeSimpleConstr, @@ -169,3 +170,21 @@ describe("evaluateLoginPolicy", () => { expect(trusted).toBe(false); }); }); + +it("accepts only VP with two credentials with complex constraints", async () => { + var trusted = await isTrustedPresentation( + vpMultiVC, + policyMultiVCFromAltmeComplexConstr, + ); + expect(trusted).toBe(true); + trusted = await isTrustedPresentation( + vpEmail, + policyMultiVCFromAltmeComplexConstr, + ); + expect(trusted).toBe(false); + trusted = await isTrustedPresentation( + vpMultiEmail, + policyMultiVCFromAltmeComplexConstr, + ); + expect(trusted).toBe(false); +}); diff --git a/vclogin/lib/extractClaims.ts b/vclogin/lib/extractClaims.ts index 3d85e01..ed67b80 100644 --- a/vclogin/lib/extractClaims.ts +++ b/vclogin/lib/extractClaims.ts @@ -66,8 +66,9 @@ const getConstraintFit = ( if (uniqueFits.length === 0) { return []; } + let fittingArr: boolean[] = []; for (let fit of uniqueFits) { - if (isValidConstraintFit(fit, policy, VP)) { + if (isValidConstraintFit(fit, policy, VP, fittingArr)) { return fit; } } @@ -153,6 +154,7 @@ const isValidConstraintFit = ( credFit: any[], policy: LoginPolicy, VP: any, + fittingArr: boolean[], ): boolean => { const credDict: any = {}; credFit = credFit.flat(Infinity); @@ -160,8 +162,6 @@ const isValidConstraintFit = ( credDict[policy[i].credentialId] = credFit[i]; } - var fittingArr = []; - for (let i = 0; i < policy.length; i++) { const cred = credFit[i]; const expectation = policy[i]; @@ -184,11 +184,10 @@ const isValidConstraintFit = ( } } } - // if all patterns fit, the credential is fitting - if (!fittingArr.includes(false)) { - return true; - } - return false; + } + // if all items in fittingArr are true, the credentials in a VP are fitting + if (fittingArr.every((item) => item === true)) { + return true; } return false; }; @@ -291,10 +290,10 @@ const resolveValue = ( for (const [key, value] of Object.entries(credDict)) { if (expression.startsWith("$" + key + ".")) { for (const [key2, value2] of Object.entries(credDict)) { - // check if both keys are in credDict + // check if key and key2 are in credDict if (keyValues.includes(key2) && keyValues.includes(key)) { - if (key !== key2) { - nodes = jp.nodes(value2, expression.slice(2 + key.length)); + if (key !== key2 && expression.replace("$", "").startsWith(key)) { + nodes = jp.nodes(value, expression.slice(key.length + 2)); if (nodes.length <= 1 && nodes.length > 0) { return nodes[0].value; } From cd5cadce8c403ac3a98ad30c89f9495b13dbc172 Mon Sep 17 00:00:00 2001 From: Ilayda Cansin Koc Date: Wed, 24 Jul 2024 21:11:00 +0200 Subject: [PATCH 68/85] instead of redis.set or get use redisGet and redisSet Signed-off-by: Ilayda Cansin Koc --- .../api/dynamic/createTempAuthorization.ts | 19 ++------- vclogin/pages/api/dynamic/getAuthResponse.ts | 13 ++----- vclogin/pages/api/dynamic/getQRCodeString.ts | 9 ----- .../api/dynamic/presentCredentialById.ts | 39 ++++--------------- 4 files changed, 14 insertions(+), 66 deletions(-) diff --git a/vclogin/pages/api/dynamic/createTempAuthorization.ts b/vclogin/pages/api/dynamic/createTempAuthorization.ts index 2441f1b..5da0f1d 100644 --- a/vclogin/pages/api/dynamic/createTempAuthorization.ts +++ b/vclogin/pages/api/dynamic/createTempAuthorization.ts @@ -1,14 +1,6 @@ import { NextApiRequest, NextApiResponse } from "next"; -import { Redis } from "ioredis"; import crypto from "crypto"; -import { logger } from "@/config/logger"; - -var redis: Redis; -try { - redis = new Redis(parseInt(process.env.REDIS_PORT!), process.env.REDIS_HOST!); -} catch (error) { - logger.error("Failed to connect to Redis:", error); -} +import { redisSet } from "@/config/redis"; export default async function handler( req: NextApiRequest, @@ -20,17 +12,12 @@ export default async function handler( try { // store policy in redis with uuid as key const uuid = crypto.randomUUID(); - await redis.set(uuid + "_policy", JSON.stringify(policy), "EX", 300); + redisSet(uuid + "_policy", JSON.stringify(policy), 300); //check if inputDescriptor is present if (inputDescriptor) { //store inputDescriptor in redis with uuid as key - await redis.set( - uuid + "_inputDescriptor", - JSON.stringify(inputDescriptor), - "EX", - 300, - ); + redisSet(uuid + "_inputDescriptor", JSON.stringify(inputDescriptor), 300); } return res.status(200).json({ uuid }); } catch (error) { diff --git a/vclogin/pages/api/dynamic/getAuthResponse.ts b/vclogin/pages/api/dynamic/getAuthResponse.ts index 5aa94fe..855ebe9 100644 --- a/vclogin/pages/api/dynamic/getAuthResponse.ts +++ b/vclogin/pages/api/dynamic/getAuthResponse.ts @@ -1,13 +1,6 @@ import { NextApiRequest, NextApiResponse } from "next"; -import { Redis } from "ioredis"; import { logger } from "@/config/logger"; - -var redis: Redis; -try { - redis = new Redis(parseInt(process.env.REDIS_PORT!), process.env.REDIS_HOST!); -} catch (error) { - logger.error("Failed to connect to Redis:", error); -} +import { redisGet } from "@/config/redis"; export default async function handler( req: NextApiRequest, @@ -20,11 +13,11 @@ export default async function handler( // Read auth_res from redis and check if it matches the uuid //auth_res kept in redis like auth_res:uuid, read auth_res using uuid - const auth_res = await redis.get(uuid + "_auth-res"); + const auth_res = await redisGet(uuid + "_auth-res"); if (auth_res) { //if auth_res found, return it along claims - const claims = await redis.get(uuid + "_claims"); + const claims = await redisGet(uuid + "_claims"); res.status(200).json({ auth_res, claims }); } else { //if auth_res not found, return error diff --git a/vclogin/pages/api/dynamic/getQRCodeString.ts b/vclogin/pages/api/dynamic/getQRCodeString.ts index eff5809..bd38e7f 100644 --- a/vclogin/pages/api/dynamic/getQRCodeString.ts +++ b/vclogin/pages/api/dynamic/getQRCodeString.ts @@ -1,13 +1,4 @@ import { NextApiRequest, NextApiResponse } from "next"; -import { Redis } from "ioredis"; -import { logger } from "@/config/logger"; - -var redis: Redis; -try { - redis = new Redis(parseInt(process.env.REDIS_PORT!), process.env.REDIS_HOST!); -} catch (error) { - logger.error("Failed to connect to Redis:", error); -} export default async function handler( req: NextApiRequest, diff --git a/vclogin/pages/api/dynamic/presentCredentialById.ts b/vclogin/pages/api/dynamic/presentCredentialById.ts index b423b31..bfc4beb 100644 --- a/vclogin/pages/api/dynamic/presentCredentialById.ts +++ b/vclogin/pages/api/dynamic/presentCredentialById.ts @@ -1,18 +1,11 @@ import { NextApiRequest, NextApiResponse } from "next"; -import { Redis } from "ioredis"; import { generatePresentationDefinition } from "@/lib/generatePresentationDefinition"; import { LoginPolicy } from "@/types/LoginPolicy"; import { extractClaims, isTrustedPresentation } from "@/lib/extractClaims"; import { verifyAuthenticationPresentation } from "@/lib/verifyPresentation"; import { getToken } from "@/lib/getToken"; import { logger } from "@/config/logger"; - -var redis: Redis; -try { - redis = new Redis(parseInt(process.env.REDIS_PORT!), process.env.REDIS_HOST!); -} catch (error) { - logger.error("Failed to connect to Redis:", error); -} +import { redisSet, redisGet } from "@/config/redis"; const getHandler = async (req: NextApiRequest, res: NextApiResponse) => { logger.debug("LOGIN API GET BY ID"); @@ -21,10 +14,10 @@ const getHandler = async (req: NextApiRequest, res: NextApiResponse) => { const uuid = req.query["login_id"]; // fetch policy from redis using uuid - const policy = await redis.get(uuid + "_policy"); + const policy = await redisGet(uuid + "_policy"); // fetch inputDescriptor from redis using uuid - const inputDescriptor = await redis.get(uuid + "_inputDescriptor"); + const inputDescriptor = await redisGet(uuid + "_inputDescriptor"); logger.debug("inputDescriptor: ", JSON.parse(inputDescriptor!)); //if policy is found @@ -68,14 +61,13 @@ const postHandler = async (req: NextApiRequest, res: NextApiResponse) => { logger.debug("Presentation: \n", req.body.vp_token); const uuid = presentation["proof"]["challenge"]; - const policy = await redis.get(uuid + "_policy"); + const policy = await redisGet(uuid + "_policy"); if (policy) { const policyObject = JSON.parse(policy) as LoginPolicy; // Constants for Redis to store the authentication result const MAX_AGE = 20 * 60; - const EXPIRY_MS = "EX"; // Verify the presentation and the status of the credential if (await verifyAuthenticationPresentation(presentation)) { @@ -89,36 +81,21 @@ const postHandler = async (req: NextApiRequest, res: NextApiResponse) => { logger.debug(userClaims); // Store the authentication result in Redis - await redis.set(uuid + "_auth-res", "success", EXPIRY_MS, MAX_AGE); + redisSet(uuid + "_auth-res", "success", MAX_AGE); // Store the user claims in Redis - await redis.set( - uuid + "_claims", - JSON.stringify(userClaims.tokenId), - EXPIRY_MS, - MAX_AGE, - ); + redisSet(uuid + "_claims", JSON.stringify(userClaims.tokenId), MAX_AGE); } else { logger.debug("Presentation not trusted"); - await redis.set( - "auth_res:" + uuid, - "error_presentation_not_trused", - EXPIRY_MS, - MAX_AGE, - ); + redisSet("auth_res:" + uuid, "error_presentation_not_trused", MAX_AGE); // Wallet gets an error message res.status(500).end(); return; } } else { logger.debug("Presentation invalid"); - await redis.set( - "auth_res:" + uuid, - "error_invalid_presentation", - EXPIRY_MS, - MAX_AGE, - ); + redisSet("auth_res:" + uuid, "error_invalid_presentation", MAX_AGE); res.status(500).end(); return; } From 216e3d49a8d400d593a3abb7f67cac8e45abf544 Mon Sep 17 00:00:00 2001 From: Ilayda Cansin Koc Date: Wed, 24 Jul 2024 21:11:55 +0200 Subject: [PATCH 69/85] introduce policy type to order credentials in a presentation Signed-off-by: Ilayda Cansin Koc --- .../pex/descriptorVerifiableIDFromAltme.json | 2 +- .../policies/acceptAnythingMultiVC.json | 2 + .../acceptMultiEmailFromAltmeConstr.json | 2 + ...acceptMultiEmailFromAltmeSimpleConstr.json | 2 + .../acceptMultiVCFromAltmeComplexConstr.json | 2 + .../acceptMultiVCFromAltmeConstr.json | 2 + .../acceptMultiVCFromAltmeMisconfigured.json | 2 + .../acceptMultiVCFromAltmeSimpleConstr.json | 2 + .../testdata/presentations/VP_MultiVC.json | 104 +++++++++--------- vclogin/lib/extractClaims.ts | 38 ++++++- vclogin/types/LoginPolicy.ts | 1 + 11 files changed, 101 insertions(+), 58 deletions(-) diff --git a/vclogin/__tests__/testdata/pex/descriptorVerifiableIDFromAltme.json b/vclogin/__tests__/testdata/pex/descriptorVerifiableIDFromAltme.json index 5c78bc2..a9edeb5 100644 --- a/vclogin/__tests__/testdata/pex/descriptorVerifiableIDFromAltme.json +++ b/vclogin/__tests__/testdata/pex/descriptorVerifiableIDFromAltme.json @@ -2,7 +2,7 @@ { "id": "verifiableId", "name": "Input descriptor for login credential", - "purpose": "Sign-in to MVG", + "purpose": "Please provide your VerifiableId credential to sign-in.", "constraints": { "fields": [ { diff --git a/vclogin/__tests__/testdata/policies/acceptAnythingMultiVC.json b/vclogin/__tests__/testdata/policies/acceptAnythingMultiVC.json index 9c913ea..fb3552f 100644 --- a/vclogin/__tests__/testdata/policies/acceptAnythingMultiVC.json +++ b/vclogin/__tests__/testdata/policies/acceptAnythingMultiVC.json @@ -1,6 +1,7 @@ [ { "credentialId": "1", + "type": "EmailPass", "patterns": [ { "issuer": "*", @@ -16,6 +17,7 @@ }, { "credentialId": "2", + "type": "VerifiableId", "patterns": [ { "issuer": "*", diff --git a/vclogin/__tests__/testdata/policies/acceptMultiEmailFromAltmeConstr.json b/vclogin/__tests__/testdata/policies/acceptMultiEmailFromAltmeConstr.json index cea5fde..54be6e0 100644 --- a/vclogin/__tests__/testdata/policies/acceptMultiEmailFromAltmeConstr.json +++ b/vclogin/__tests__/testdata/policies/acceptMultiEmailFromAltmeConstr.json @@ -1,6 +1,7 @@ [ { "credentialId": "1", + "type": "EmailPass", "patterns": [ { "issuer": "did:web:app.altme.io:issuer", @@ -19,6 +20,7 @@ }, { "credentialId": "2", + "type": "EmailPass", "patterns": [ { "issuer": "did:web:app.altme.io:issuer", diff --git a/vclogin/__tests__/testdata/policies/acceptMultiEmailFromAltmeSimpleConstr.json b/vclogin/__tests__/testdata/policies/acceptMultiEmailFromAltmeSimpleConstr.json index a1ef319..17aeea9 100644 --- a/vclogin/__tests__/testdata/policies/acceptMultiEmailFromAltmeSimpleConstr.json +++ b/vclogin/__tests__/testdata/policies/acceptMultiEmailFromAltmeSimpleConstr.json @@ -1,6 +1,7 @@ [ { "credentialId": "1", + "type": "EmailPass", "patterns": [ { "issuer": "did:web:app.altme.io:issuer", @@ -19,6 +20,7 @@ }, { "credentialId": "2", + "type": "EmailPass", "patterns": [ { "issuer": "did:web:app.altme.io:issuer", diff --git a/vclogin/__tests__/testdata/policies/acceptMultiVCFromAltmeComplexConstr.json b/vclogin/__tests__/testdata/policies/acceptMultiVCFromAltmeComplexConstr.json index 560a7e5..8bf34c4 100644 --- a/vclogin/__tests__/testdata/policies/acceptMultiVCFromAltmeComplexConstr.json +++ b/vclogin/__tests__/testdata/policies/acceptMultiVCFromAltmeComplexConstr.json @@ -1,6 +1,7 @@ [ { "credentialId": "1", + "type": "EmailPass", "patterns": [ { "issuer": "did:web:app.altme.io:issuer", @@ -19,6 +20,7 @@ }, { "credentialId": "2", + "type": "VerifiableId", "patterns": [ { "issuer": "did:web:app.altme.io:issuer", diff --git a/vclogin/__tests__/testdata/policies/acceptMultiVCFromAltmeConstr.json b/vclogin/__tests__/testdata/policies/acceptMultiVCFromAltmeConstr.json index 03bf27b..cfda74e 100644 --- a/vclogin/__tests__/testdata/policies/acceptMultiVCFromAltmeConstr.json +++ b/vclogin/__tests__/testdata/policies/acceptMultiVCFromAltmeConstr.json @@ -1,6 +1,7 @@ [ { "credentialId": "1", + "type": "EmailPass", "patterns": [ { "issuer": "did:web:app.altme.io:issuer", @@ -19,6 +20,7 @@ }, { "credentialId": "2", + "type": "VerifiableId", "patterns": [ { "issuer": "did:web:app.altme.io:issuer", diff --git a/vclogin/__tests__/testdata/policies/acceptMultiVCFromAltmeMisconfigured.json b/vclogin/__tests__/testdata/policies/acceptMultiVCFromAltmeMisconfigured.json index 5e1db23..18923fe 100644 --- a/vclogin/__tests__/testdata/policies/acceptMultiVCFromAltmeMisconfigured.json +++ b/vclogin/__tests__/testdata/policies/acceptMultiVCFromAltmeMisconfigured.json @@ -1,6 +1,7 @@ [ { "credentialId": "one", + "type": "EmailPass", "patterns": [ { "issuer": "*", @@ -14,6 +15,7 @@ }, { "credentialId": "two", + "type": "VerifiableId", "patterns": [ { "issuer": "*", diff --git a/vclogin/__tests__/testdata/policies/acceptMultiVCFromAltmeSimpleConstr.json b/vclogin/__tests__/testdata/policies/acceptMultiVCFromAltmeSimpleConstr.json index a3fa9fa..7d94e79 100644 --- a/vclogin/__tests__/testdata/policies/acceptMultiVCFromAltmeSimpleConstr.json +++ b/vclogin/__tests__/testdata/policies/acceptMultiVCFromAltmeSimpleConstr.json @@ -1,6 +1,7 @@ [ { "credentialId": "1", + "type": "EmailPass", "patterns": [ { "issuer": "did:web:app.altme.io:issuer", @@ -19,6 +20,7 @@ }, { "credentialId": "2", + "type": "VerifiableId", "patterns": [ { "issuer": "did:web:app.altme.io:issuer", diff --git a/vclogin/__tests__/testdata/presentations/VP_MultiVC.json b/vclogin/__tests__/testdata/presentations/VP_MultiVC.json index 959963d..828bf32 100644 --- a/vclogin/__tests__/testdata/presentations/VP_MultiVC.json +++ b/vclogin/__tests__/testdata/presentations/VP_MultiVC.json @@ -3,6 +3,58 @@ "id": "urn:uuid:2c0b29f7-c530-4405-b509-d34d4a8a4028", "type": ["VerifiablePresentation"], "verifiableCredential": [ + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + { + "VerifiableId": { + "@context": { + "@protected": true, + "@version": 1.1, + "dateIssued": "schema:dateIssued", + "dateOfBirth": "schema:birthDate", + "familyName": "schema:lastName", + "firstName": "schema:firstName", + "gender": "schema:gender", + "id": "@id", + "idRecto": "schema:image", + "idVerso": "schema:image", + "schema": "https://schema.org/", + "type": "@type" + }, + "@id": "urn:employeecredential" + } + } + ], + "id": "urn:uuid:f1fcf591-1396-11ef-8875-0a1628958560", + "type": ["VerifiableCredential", "VerifiableId"], + "credentialSubject": { + "id": "did:key:z6Mkj5B9HcSKWGuuawpBcvy5wQwZJ9g2k5HzfmXPAjwbQ9TT", + "dateOfBirth": "1930-10-01", + "firstName": "Bianca", + "gender": "F", + "dateIssued": "2022-12-20", + "familyName": "Castafiori", + "type": "VerifiableId" + }, + "issuer": "did:web:app.altme.io:issuer", + "issuanceDate": "2024-05-16T15:14:02Z", + "proof": { + "type": "EcdsaSecp256k1Signature2019", + "proofPurpose": "assertionMethod", + "verificationMethod": "did:web:app.altme.io:issuer#key-3", + "created": "2024-05-16T15:14:09.781Z", + "jws": "eyJhbGciOiJFUzI1NksiLCJjcml0IjpbImI2NCJdLCJiNjQiOmZhbHNlfQ..smC_xxfdYC9fVhv2ETHWLBR9KCFlXRkujTJjQH6vAA47J5nal7QlVNeDGcb_nOlASBiGY_pG4PzaqoPe6-9q4A" + }, + "expirationDate": "2025-05-16T15:14:02Z", + "credentialSchema": { + "id": "https://api-conformance.ebsi.eu/trusted-schemas-registry/v2/schemas/z22ZAMdQtNLwi51T2vdZXGGZaYyjrsuP1yzWyXZirCAHv", + "type": "FullJsonSchemaValidator2021" + }, + "validUntil": "2025-05-16T15:14:09.768457Z", + "issued": "2024-05-16T15:14:09Z", + "validFrom": "2024-05-16T15:14:09Z" + }, { "@context": [ "https://www.w3.org/2018/credentials/v1", @@ -55,58 +107,6 @@ "issued": "2024-03-30T16:57:57Z", "validUntil": "2025-03-30T16:57:57.995257Z", "validFrom": "2024-03-30T16:57:57Z" - }, - { - "@context": [ - "https://www.w3.org/2018/credentials/v1", - { - "VerifiableId": { - "@context": { - "@protected": true, - "@version": 1.1, - "dateIssued": "schema:dateIssued", - "dateOfBirth": "schema:birthDate", - "familyName": "schema:lastName", - "firstName": "schema:firstName", - "gender": "schema:gender", - "id": "@id", - "idRecto": "schema:image", - "idVerso": "schema:image", - "schema": "https://schema.org/", - "type": "@type" - }, - "@id": "urn:employeecredential" - } - } - ], - "id": "urn:uuid:f1fcf591-1396-11ef-8875-0a1628958560", - "type": ["VerifiableCredential", "VerifiableId"], - "credentialSubject": { - "id": "did:key:z6Mkj5B9HcSKWGuuawpBcvy5wQwZJ9g2k5HzfmXPAjwbQ9TT", - "dateOfBirth": "1930-10-01", - "firstName": "Bianca", - "gender": "F", - "dateIssued": "2022-12-20", - "familyName": "Castafiori", - "type": "VerifiableId" - }, - "issuer": "did:web:app.altme.io:issuer", - "issuanceDate": "2024-05-16T15:14:02Z", - "proof": { - "type": "EcdsaSecp256k1Signature2019", - "proofPurpose": "assertionMethod", - "verificationMethod": "did:web:app.altme.io:issuer#key-3", - "created": "2024-05-16T15:14:09.781Z", - "jws": "eyJhbGciOiJFUzI1NksiLCJjcml0IjpbImI2NCJdLCJiNjQiOmZhbHNlfQ..smC_xxfdYC9fVhv2ETHWLBR9KCFlXRkujTJjQH6vAA47J5nal7QlVNeDGcb_nOlASBiGY_pG4PzaqoPe6-9q4A" - }, - "expirationDate": "2025-05-16T15:14:02Z", - "credentialSchema": { - "id": "https://api-conformance.ebsi.eu/trusted-schemas-registry/v2/schemas/z22ZAMdQtNLwi51T2vdZXGGZaYyjrsuP1yzWyXZirCAHv", - "type": "FullJsonSchemaValidator2021" - }, - "validUntil": "2025-05-16T15:14:09.768457Z", - "issued": "2024-05-16T15:14:09Z", - "validFrom": "2024-05-16T15:14:09Z" } ], "proof": { diff --git a/vclogin/lib/extractClaims.ts b/vclogin/lib/extractClaims.ts index ed67b80..7980930 100644 --- a/vclogin/lib/extractClaims.ts +++ b/vclogin/lib/extractClaims.ts @@ -17,11 +17,16 @@ export const isTrustedPresentation = async (VP: any, policy?: LoginPolicy) => { if (!policy && configuredPolicy === undefined) return false; var usedPolicy = policy ? policy : configuredPolicy!; - const creds = Array.isArray(VP.verifiableCredential) + + let creds = Array.isArray(VP.verifiableCredential) ? VP.verifiableCredential : [VP.verifiableCredential]; - return getConstraintFit(creds, usedPolicy, VP).length > 0; + // reorder the credentials in the VP to match the type in the policy file + // because the order of the credentials in the VP is not guaranteed + let reorderedCreds = orderCredsByType(creds, usedPolicy); + + return getConstraintFit(reorderedCreds, usedPolicy, VP).length > 0; }; export const extractClaims = async (VP: any, policy?: LoginPolicy) => { @@ -34,9 +39,13 @@ export const extractClaims = async (VP: any, policy?: LoginPolicy) => { ? VP.verifiableCredential : [VP.verifiableCredential]; - const vcClaims = creds.map((vc: any, credentialIndex: number) => - // Important: credentialIndex defines helps us to extract the correct claims from the policy - // Ideally, the credentialIndex should be the same as the credentialId in the policy + // reorder the credentials in the VP to match the type in the policy file + // because the order of the credentials in the VP is not guaranteed + let reorderedCreds = orderCredsByType(creds, usedPolicy); + + const vcClaims = reorderedCreds.map((vc: any, credentialIndex: number) => + // Important: credentialIndex helps us to extract the correct claims from the policy. + // Ideally, the credentialIndex should be the same as the credentialId in the policy. extractClaimsFromVC(vc, usedPolicy, (credentialIndex + 1).toString()), ); const claims: any = {}; @@ -56,6 +65,25 @@ export const extractClaims = async (VP: any, policy?: LoginPolicy) => { return claims; }; +const orderCredsByType = (creds: any[], policy: LoginPolicy): any[] => { + let orderedCreds: any[] = []; + if (creds.length > 1) { + for (let policyObj of policy) { + for (let cred of creds) { + if ( + cred.credentialSubject.type === policyObj.type && + !orderedCreds.includes(cred) + ) { + orderedCreds.push(cred); + } + } + } + } else { + orderedCreds = creds; + } + return orderedCreds; +}; + const getConstraintFit = ( creds: any[], policy: LoginPolicy, diff --git a/vclogin/types/LoginPolicy.ts b/vclogin/types/LoginPolicy.ts index 381fcea..d71bc43 100644 --- a/vclogin/types/LoginPolicy.ts +++ b/vclogin/types/LoginPolicy.ts @@ -23,6 +23,7 @@ export type CredentialPattern = { export type ExpectedCredential = { credentialId: string; + type?: string; patterns: CredentialPattern[]; }; From 41d87d457f18e4f777c9eed646ad830b83ae0252 Mon Sep 17 00:00:00 2001 From: Ilayda Cansin Koc Date: Thu, 25 Jul 2024 14:45:53 +0200 Subject: [PATCH 70/85] add test data and test for triple VC add checks on extractClaims to ensure the VC order is correct and the correct policy is applied. refactor extractClaims Signed-off-by: Ilayda Cansin Koc --- .../testdata/policies/acceptTripleVC.json | 59 ++++ .../presentations/VP_MultiEmailPass.json | 2 +- .../testdata/presentations/VP_MultiVC.json | 2 +- .../testdata/presentations/VP_TripleVC.json | 317 ++++++++++++++++++ .../unit/lib/evaluateLoginPolicy.test.ts | 51 ++- vclogin/lib/extractClaims.ts | 46 ++- 6 files changed, 440 insertions(+), 37 deletions(-) create mode 100644 vclogin/__tests__/testdata/policies/acceptTripleVC.json create mode 100644 vclogin/__tests__/testdata/presentations/VP_TripleVC.json diff --git a/vclogin/__tests__/testdata/policies/acceptTripleVC.json b/vclogin/__tests__/testdata/policies/acceptTripleVC.json new file mode 100644 index 0000000..d9b5d59 --- /dev/null +++ b/vclogin/__tests__/testdata/policies/acceptTripleVC.json @@ -0,0 +1,59 @@ +[ + { + "credentialId": "3", + "type": "EmployeeCredential", + "patterns": [ + { + "issuer": "did:tz:tz1NyjrTUNxDpPaqNZ84ipGELAcTWYg6s5Du", + "claims": [ + { + "claimPath": "$.credentialSubject.name" + } + ], + "constraint": { + "op": "equals", + "a": "$2.credentialSubject.id", + "b": "$1.credentialSubject.id" + } + } + ] + }, + { + "credentialId": "2", + "type": "EmailPass", + "patterns": [ + { + "issuer": "did:web:app.altme.io:issuer", + "claims": [ + { + "claimPath": "$.credentialSubject.email" + } + ], + "constraint": { + "op": "equals", + "a": "$2.credentialSubject.id", + "b": "$1.credentialSubject.id" + } + } + ] + }, + { + "credentialId": "1", + "type": "VerifiableId", + "patterns": [ + { + "issuer": "did:web:app.altme.io:issuer", + "claims": [ + { + "claimPath": "$.credentialSubject.firstName" + } + ], + "constraint": { + "op": "equalsDID", + "a": "$VP.proof.verificationMethod", + "b": "$1.credentialSubject.id" + } + } + ] + } +] diff --git a/vclogin/__tests__/testdata/presentations/VP_MultiEmailPass.json b/vclogin/__tests__/testdata/presentations/VP_MultiEmailPass.json index c894f23..28df525 100644 --- a/vclogin/__tests__/testdata/presentations/VP_MultiEmailPass.json +++ b/vclogin/__tests__/testdata/presentations/VP_MultiEmailPass.json @@ -32,7 +32,7 @@ } } ], - "id": "urn:uuid:a8e2b30c-eeb6-11ee-a822-0a1628958560", + "id": "urn:uuid:a8e2b30c-eeb6-11ee-a822-0a1628958561", "type": ["VerifiableCredential", "EmailPass"], "credentialSubject": { "id": "did:key:z6Mkj5B9HcSKWGuuawpBcvy5wQwZJ9g2k5HzfmXPAjwbQ9TT", diff --git a/vclogin/__tests__/testdata/presentations/VP_MultiVC.json b/vclogin/__tests__/testdata/presentations/VP_MultiVC.json index 828bf32..fbcc2ff 100644 --- a/vclogin/__tests__/testdata/presentations/VP_MultiVC.json +++ b/vclogin/__tests__/testdata/presentations/VP_MultiVC.json @@ -84,7 +84,7 @@ } } ], - "id": "urn:uuid:a8e2b30c-eeb6-11ee-a822-0a1628958560", + "id": "urn:uuid:a8e2b30c-eeb6-11ee-a822-0a1628958561", "type": ["VerifiableCredential", "EmailPass"], "credentialSubject": { "id": "did:key:z6Mkj5B9HcSKWGuuawpBcvy5wQwZJ9g2k5HzfmXPAjwbQ9TT", diff --git a/vclogin/__tests__/testdata/presentations/VP_TripleVC.json b/vclogin/__tests__/testdata/presentations/VP_TripleVC.json new file mode 100644 index 0000000..ebe85d8 --- /dev/null +++ b/vclogin/__tests__/testdata/presentations/VP_TripleVC.json @@ -0,0 +1,317 @@ +{ + "@context": ["https://www.w3.org/2018/credentials/v1"], + "id": "urn:uuid:2c0b29f7-c530-4405-b509-d34d4a8a4028", + "type": ["VerifiablePresentation"], + "verifiableCredential": [ + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + { + "VerifiableId": { + "@context": { + "@protected": true, + "@version": 1.1, + "dateIssued": "schema:dateIssued", + "dateOfBirth": "schema:birthDate", + "familyName": "schema:lastName", + "firstName": "schema:firstName", + "gender": "schema:gender", + "id": "@id", + "idRecto": "schema:image", + "idVerso": "schema:image", + "schema": "https://schema.org/", + "type": "@type" + }, + "@id": "urn:employeecredential" + } + } + ], + "id": "urn:uuid:f1fcf591-1396-11ef-8875-0a1628958562", + "type": ["VerifiableCredential", "VerifiableId"], + "credentialSubject": { + "id": "did:key:z6Mkj5B9HcSKWGuuawpBcvy5wQwZJ9g2k5HzfmXPAjwbQ9TT", + "dateOfBirth": "1930-10-01", + "firstName": "Bianca", + "gender": "F", + "dateIssued": "2022-12-20", + "familyName": "Castafiori", + "type": "VerifiableId" + }, + "issuer": "did:web:app.altme.io:issuer", + "issuanceDate": "2024-05-16T15:14:02Z", + "proof": { + "type": "EcdsaSecp256k1Signature2019", + "proofPurpose": "assertionMethod", + "verificationMethod": "did:web:app.altme.io:issuer#key-3", + "created": "2024-05-16T15:14:09.781Z", + "jws": "eyJhbGciOiJFUzI1NksiLCJjcml0IjpbImI2NCJdLCJiNjQiOmZhbHNlfQ..smC_xxfdYC9fVhv2ETHWLBR9KCFlXRkujTJjQH6vAA47J5nal7QlVNeDGcb_nOlASBiGY_pG4PzaqoPe6-9q4A" + }, + "expirationDate": "2025-05-16T15:14:02Z", + "credentialSchema": { + "id": "https://api-conformance.ebsi.eu/trusted-schemas-registry/v2/schemas/z22ZAMdQtNLwi51T2vdZXGGZaYyjrsuP1yzWyXZirCAHv", + "type": "FullJsonSchemaValidator2021" + }, + "validUntil": "2025-05-16T15:14:09.768457Z", + "issued": "2024-05-16T15:14:09Z", + "validFrom": "2024-05-16T15:14:09Z" + }, + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + { + "StatusList2021Entry": { + "@context": { + "@protected": true, + "id": "@id", + "statusListCredential": { + "@id": "https://w3id.org/vc/status-list#statusListCredential", + "@type": "@id" + }, + "statusListIndex": "https://w3id.org/vc/status-list#statusListIndex", + "statusPurpose": "https://w3id.org/vc/status-list#statusPurpose", + "type": "@type" + }, + "@id": "https://w3id.org/vc/status-list#StatusList2021Entry" + }, + "EmployeeCredential": { + "@context": { + "@protected": true, + "@version": 1.1, + "email": "schema:email", + "ethereumAddress": "schema:identifier", + "hasCountry": "schema:addressCountry", + "hasJurisdiction": "schema:addressCountry", + "hasLegallyBindingName": "schema:legalName", + "hasRegistrationNumber": "schema:identifier", + "hash": "schema:sha256", + "id": "@id", + "leiCode": "schema:leiCode", + "name": "schema:name", + "parentOrganisation": "schema:legalName", + "schema": "https://schema.org/", + "subOrganisation": "schema:legalName", + "surname": "schema:givenName", + "title": "schema:jobTitle", + "type": "@type" + }, + "@id": "urn:employeecredential" + } + } + ], + "id": "urn:uuid:2eb827bc-8ecc-11ee-9722-0a1628958561", + "type": ["VerifiableCredential", "EmployeeCredential"], + "credentialSubject": { + "id": "did:key:z6MkkdC46uhBGjMYS2ZDLUwCrTWdaqZdTD3596sN4397oRNd", + "hash": "9ecf754ffdad0c6de238f60728a90511780b2f7dbe2f0ea015115515f3f389cd", + "leiCode": "391200FJBNU0YW987L26", + "hasLegallyBindingName": "deltaDAO AG", + "ethereumAddress": "0x4C84a36fCDb7Bc750294A7f3B5ad5CA8F74C4A52", + "email": "test@test.com", + "hasRegistrationNumber": "DEK1101R.HRB170364", + "name": "Name Surname", + "hasCountry": "GER", + "type": "EmployeeCredential", + "title": "CEO", + "hasJurisdiction": "GER", + "surname": "Surname" + }, + "issuer": "did:tz:tz1NyjrTUNxDpPaqNZ84ipGELAcTWYg6s5Du", + "issuanceDate": "2023-11-29T15:30:10.335704Z", + "proof": { + "@context": { + "Ed25519BLAKE2BDigestSize20Base58CheckEncodedSignature2021": { + "@context": { + "@protected": true, + "@version": 1.1, + "challenge": "https://w3id.org/security#challenge", + "created": { + "@id": "http://purl.org/dc/terms/created", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "domain": "https://w3id.org/security#domain", + "expires": { + "@id": "https://w3id.org/security#expiration", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "id": "@id", + "jws": "https://w3id.org/security#jws", + "nonce": "https://w3id.org/security#nonce", + "proofPurpose": { + "@context": { + "@protected": true, + "@version": 1.1, + "assertionMethod": { + "@container": "@set", + "@id": "https://w3id.org/security#assertionMethod", + "@type": "@id" + }, + "authentication": { + "@container": "@set", + "@id": "https://w3id.org/security#authenticationMethod", + "@type": "@id" + }, + "id": "@id", + "type": "@type" + }, + "@id": "https://w3id.org/security#proofPurpose", + "@type": "@vocab" + }, + "publicKeyJwk": { + "@id": "https://w3id.org/security#publicKeyJwk", + "@type": "@json" + }, + "type": "@type", + "verificationMethod": { + "@id": "https://w3id.org/security#verificationMethod", + "@type": "@id" + } + }, + "@id": "https://w3id.org/security#Ed25519BLAKE2BDigestSize20Base58CheckEncodedSignature2021" + }, + "Ed25519PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021": { + "@id": "https://w3id.org/security#Ed25519PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021" + }, + "P256BLAKE2BDigestSize20Base58CheckEncodedSignature2021": { + "@context": { + "@protected": true, + "@version": 1.1, + "challenge": "https://w3id.org/security#challenge", + "created": { + "@id": "http://purl.org/dc/terms/created", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "domain": "https://w3id.org/security#domain", + "expires": { + "@id": "https://w3id.org/security#expiration", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "id": "@id", + "jws": "https://w3id.org/security#jws", + "nonce": "https://w3id.org/security#nonce", + "proofPurpose": { + "@context": { + "@protected": true, + "@version": 1.1, + "assertionMethod": { + "@container": "@set", + "@id": "https://w3id.org/security#assertionMethod", + "@type": "@id" + }, + "authentication": { + "@container": "@set", + "@id": "https://w3id.org/security#authenticationMethod", + "@type": "@id" + }, + "id": "@id", + "type": "@type" + }, + "@id": "https://w3id.org/security#proofPurpose", + "@type": "@vocab" + }, + "publicKeyJwk": { + "@id": "https://w3id.org/security#publicKeyJwk", + "@type": "@json" + }, + "type": "@type", + "verificationMethod": { + "@id": "https://w3id.org/security#verificationMethod", + "@type": "@id" + } + }, + "@id": "https://w3id.org/security#P256BLAKE2BDigestSize20Base58CheckEncodedSignature2021" + }, + "P256PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021": { + "@id": "https://w3id.org/security#P256PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021" + } + }, + "type": "Ed25519BLAKE2BDigestSize20Base58CheckEncodedSignature2021", + "proofPurpose": "assertionMethod", + "verificationMethod": "did:tz:tz1NyjrTUNxDpPaqNZ84ipGELAcTWYg6s5Du#blockchainAccountId", + "created": "2023-11-29T15:30:27.583Z", + "jws": "eyJhbGciOiJFZEJsYWtlMmIiLCJjcml0IjpbImI2NCJdLCJiNjQiOmZhbHNlfQ..EHmQL4JQ6RLZVFob3mH_Rlue3Nv9qyeug0ZYtysWJOfC-dJqCphb3li9llmSmazB1vvCFvG5WKTg2ooXpowYCg", + "publicKeyJwk": { + "crv": "Ed25519", + "kty": "OKP", + "x": "FUoLewH4w4-KdaPH2cjZbL--CKYxQRWR05Yd_bIbhQo" + } + }, + "expirationDate": "2024-11-28T15:30:10.335716Z", + "credentialStatus": { + "id": "https://revocation-registry.abc-federation.dev.gaiax.ovh/api/v1/revocations/credentials/ABC-Federation-revocation#51", + "type": "StatusList2021Entry", + "statusListCredential": "https://revocation-registry.abc-federation.dev.gaiax.ovh/api/v1/revocations/credentials/ABC-Federation-revocation", + "statusPurpose": "revocation", + "statusListIndex": "51" + }, + "credentialSchema": { + "id": "https://raw.githubusercontent.com/walt-id/waltid-ssikit-vclib/master/src/test/resources/schemas/ParticipantCredential.json", + "type": "JsonSchemaValidator2018" + }, + "validFrom": "2023-07-20T15:36:41Z", + "issued": "2023-07-20T15:36:41Z" + }, + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + { + "EmailPass": { + "@context": { + "@protected": true, + "@version": 1.1, + "email": "schema:email", + "id": "@id", + "issuedBy": { + "@context": { + "@protected": true, + "@version": 1.1, + "logo": { + "@id": "schema:image", + "@type": "@id" + }, + "name": "schema:name" + }, + "@id": "schema:issuedBy" + }, + "schema": "https://schema.org/", + "type": "@type" + }, + "@id": "https://github.com/TalaoDAO/context#emailpass" + } + } + ], + "id": "urn:uuid:a8e2b30c-eeb6-11ee-a822-0a1628958560", + "type": ["VerifiableCredential", "EmailPass"], + "credentialSubject": { + "id": "did:key:z6Mkj5B9HcSKWGuuawpBcvy5wQwZJ9g2k5HzfmXPAjwbQ9TT", + "issuedBy": { + "name": "Altme" + }, + "email": "first.vc@gmail.com", + "type": "EmailPass" + }, + "issuer": "did:web:app.altme.io:issuer", + "issuanceDate": "2024-03-30T16:57:55Z", + "proof": { + "type": "Ed25519Signature2018", + "proofPurpose": "assertionMethod", + "verificationMethod": "did:web:app.altme.io:issuer#key-1", + "created": "2024-03-30T16:57:58.048Z", + "jws": "eyJhbGciOiJFZERTQSIsImNyaXQiOlsiYjY0Il0sImI2NCI6ZmFsc2V9..F9EXluMjwPtCe8A0WKusX0zSsCi6JKh0HDLuNk47-Wvig_8wrwh56IocbNMyNG8b2J4wGuXUuGLTkQuftx6iDA" + }, + "expirationDate": "2025-03-30T16:57:55.390537Z", + "issued": "2024-03-30T16:57:57Z", + "validUntil": "2025-03-30T16:57:57.995257Z", + "validFrom": "2024-03-30T16:57:57Z" + } + ], + "proof": { + "type": "Ed25519Signature2018", + "proofPurpose": "authentication", + "challenge": "615e51a4-3f84-11ef-876f-0a1628958560", + "verificationMethod": "did:key:z6Mkj5B9HcSKWGuuawpBcvy5wQwZJ9g2k5HzfmXPAjwbQ9TT#z6Mkj5B9HcSKWGuuawpBcvy5wQwZJ9g2k5HzfmXPAjwbQ9TT", + "created": "2024-07-11T12:52:05.187708Z", + "domain": "https://talao.co/", + "jws": "eyJhbGciOiJFZERTQSIsImNyaXQiOlsiYjY0Il0sImI2NCI6ZmFsc2V9..ux7ny5CsclcU_4oovjitC4r7EktTOBH_qbdbca7OUKNbyFW-6CbREe0xj_izYxEqESvoJ-YfuvTkpgR35uw7AQ" + }, + "holder": "did:key:z6Mkj5B9HcSKWGuuawpBcvy5wQwZJ9g2k5HzfmXPAjwbQ9TT" +} diff --git a/vclogin/__tests__/unit/lib/evaluateLoginPolicy.test.ts b/vclogin/__tests__/unit/lib/evaluateLoginPolicy.test.ts index c3022a9..0f58ff3 100644 --- a/vclogin/__tests__/unit/lib/evaluateLoginPolicy.test.ts +++ b/vclogin/__tests__/unit/lib/evaluateLoginPolicy.test.ts @@ -8,6 +8,7 @@ import { isTrustedPresentation } from "@/lib/extractClaims"; import vpEmployee from "@/testdata/presentations/VP_EmployeeCredential.json"; import vpMultiEmail from "@/testdata/presentations/VP_MultiEmailPass.json"; import vpMultiVC from "@/testdata/presentations/VP_MultiVC.json"; +import vpTripleVC from "@/testdata/presentations/VP_TripleVC.json"; import vpEmail from "@/testdata/presentations/VP_EmailPass.json"; import vpTezos from "@/testdata/presentations/VP_TezosAssociatedAddress.json"; import policyAcceptAnything from "@/testdata/policies/acceptAnything.json"; @@ -21,6 +22,7 @@ import policyMultiEmailFromAltmeSimpleConstr from "@/testdata/policies/acceptMul import policyMultiVCFromAltmeConstr from "@/testdata/policies/acceptMultiVCFromAltmeConstr.json"; import policyMultiVCFromAltmeSimpleConstr from "@/testdata/policies/acceptMultiVCFromAltmeSimpleConstr.json"; import policyMultiVCFromAltmeComplexConstr from "@/testdata/policies/acceptMultiVCFromAltmeComplexConstr.json"; +import policyTripleVCSimpleConstr from "@/testdata/policies/acceptTripleVC.json"; describe("evaluateLoginPolicy", () => { it("defaults to false if no policy is available", async () => { @@ -169,22 +171,37 @@ describe("evaluateLoginPolicy", () => { ); expect(trusted).toBe(false); }); -}); -it("accepts only VP with two credentials with complex constraints", async () => { - var trusted = await isTrustedPresentation( - vpMultiVC, - policyMultiVCFromAltmeComplexConstr, - ); - expect(trusted).toBe(true); - trusted = await isTrustedPresentation( - vpEmail, - policyMultiVCFromAltmeComplexConstr, - ); - expect(trusted).toBe(false); - trusted = await isTrustedPresentation( - vpMultiEmail, - policyMultiVCFromAltmeComplexConstr, - ); - expect(trusted).toBe(false); + it("accepts only VP with two credentials with complex constraints", async () => { + var trusted = await isTrustedPresentation( + vpMultiVC, + policyMultiVCFromAltmeComplexConstr, + ); + expect(trusted).toBe(true); + trusted = await isTrustedPresentation( + vpEmail, + policyMultiVCFromAltmeComplexConstr, + ); + expect(trusted).toBe(false); + trusted = await isTrustedPresentation( + vpMultiEmail, + policyMultiVCFromAltmeComplexConstr, + ); + expect(trusted).toBe(false); + }); + + it("accepts only VP with three credentials with simple constraints", async () => { + var trusted = await isTrustedPresentation( + vpTripleVC, + policyTripleVCSimpleConstr, + ); + expect(trusted).toBe(true); + trusted = await isTrustedPresentation(vpEmail, policyTripleVCSimpleConstr); + expect(trusted).toBe(false); + trusted = await isTrustedPresentation( + vpMultiEmail, + policyTripleVCSimpleConstr, + ); + expect(trusted).toBe(false); + }); }); diff --git a/vclogin/lib/extractClaims.ts b/vclogin/lib/extractClaims.ts index 7980930..5741bc5 100644 --- a/vclogin/lib/extractClaims.ts +++ b/vclogin/lib/extractClaims.ts @@ -22,11 +22,15 @@ export const isTrustedPresentation = async (VP: any, policy?: LoginPolicy) => { ? VP.verifiableCredential : [VP.verifiableCredential]; + const reorderedPolicy = usedPolicy.sort( + (a: { credentialId: string }, b: { credentialId: string }) => + parseFloat(a.credentialId) - parseFloat(b.credentialId), + ); // reorder the credentials in the VP to match the type in the policy file // because the order of the credentials in the VP is not guaranteed - let reorderedCreds = orderCredsByType(creds, usedPolicy); + let reorderedCreds = orderCredsByType(creds, reorderedPolicy); - return getConstraintFit(reorderedCreds, usedPolicy, VP).length > 0; + return getConstraintFit(reorderedCreds, reorderedPolicy, VP).length > 0; }; export const extractClaims = async (VP: any, policy?: LoginPolicy) => { @@ -39,14 +43,21 @@ export const extractClaims = async (VP: any, policy?: LoginPolicy) => { ? VP.verifiableCredential : [VP.verifiableCredential]; + // we need a way to map each policy object to the correct credential to extract the claims + // to do so, we need to reorder the policy objects to match the order of the credentials based on credentialId. + const reorderedPolicy = usedPolicy.sort( + (a: { credentialId: string }, b: { credentialId: string }) => + parseFloat(a.credentialId) - parseFloat(b.credentialId), + ); + // reorder the credentials in the VP to match the type in the policy file // because the order of the credentials in the VP is not guaranteed - let reorderedCreds = orderCredsByType(creds, usedPolicy); + let reorderedCreds = orderCredsByType(creds, reorderedPolicy); const vcClaims = reorderedCreds.map((vc: any, credentialIndex: number) => - // Important: credentialIndex helps us to extract the correct claims from the policy. + // Important: credentialIndex helps us to extract the correct claims from the correct policy. // Ideally, the credentialIndex should be the same as the credentialId in the policy. - extractClaimsFromVC(vc, usedPolicy, (credentialIndex + 1).toString()), + extractClaimsFromVC(vc, reorderedPolicy, (credentialIndex + 1).toString()), ); const claims: any = {}; @@ -141,10 +152,9 @@ const isCredentialFittingPattern = ( } for (const claim of pattern.claims) { - const claimPath = claim.claimPath.replace(/\$\d+\./g, "$."); if ( (!Object.hasOwn(claim, "required") || claim.required) && - jp.paths(cred, claimPath).length === 0 + jp.paths(cred, claim.claimPath).length === 0 ) { return false; } @@ -155,7 +165,8 @@ const isCredentialFittingPattern = ( const getAllUniqueDraws = (patternFits: any[][]): any[][] => { const draws = getAllUniqueDrawsHelper(patternFits, []); - return draws.filter((draw) => draw.length == patternFits.length); + const flatDraws = draws.map((draw) => draw.flat(Infinity)); + return flatDraws.filter((draw) => draw.length == patternFits.length); }; const getAllUniqueDrawsHelper = ( @@ -169,10 +180,12 @@ const getAllUniqueDrawsHelper = ( let uniqueDraws: any[][] = []; for (let cred of patternFits[0]) { if (!usedIds.includes(cred.id)) { - uniqueDraws.push([ - cred, - ...getAllUniqueDrawsHelper(patternFits.slice(1), [...usedIds, cred.id]), + const newDraws = getAllUniqueDrawsHelper(patternFits.slice(1), [ + ...usedIds, + cred.id, ]); + + uniqueDraws.push([cred, ...newDraws]); } } return uniqueDraws; @@ -311,13 +324,13 @@ const resolveValue = ( if (Object.entries(credDict).length > 0) { // store object key's value in array to prevent querying wrong key let keyValues = []; - for (const [key, value] of Object.entries(credDict)) { + for (const [key, _value] of Object.entries(credDict)) { keyValues.push(key); } for (const [key, value] of Object.entries(credDict)) { if (expression.startsWith("$" + key + ".")) { - for (const [key2, value2] of Object.entries(credDict)) { + for (const [key2, _value2] of Object.entries(credDict)) { // check if key and key2 are in credDict if (keyValues.includes(key2) && keyValues.includes(key)) { if (key !== key2 && expression.replace("$", "").startsWith(key)) { @@ -353,8 +366,7 @@ const extractClaimsFromVC = ( if (pattern.issuer === VC.issuer || pattern.issuer === "*") { const containsAllRequired = pattern.claims.filter((claim: ClaimEntry) => { - const claimPath = claim.claimPath.replace(`${credentialId}`, ""); - const claimPathLength = jp.paths(VC, claimPath).length; + const claimPathLength = jp.paths(VC, claim.claimPath).length; return claim.required && claimPathLength === 1; }).length > 0 || pattern.claims.filter((claim: ClaimEntry) => claim.required) @@ -370,9 +382,7 @@ const extractClaimsFromVC = ( }; for (let claim of pattern.claims) { - const claimPath = claim.claimPath.replace(`${credentialId}`, ""); - - const nodes = jp.nodes(VC, claimPath); + const nodes = jp.nodes(VC, claim.claimPath); let newPath = claim.newPath; let value: any; From 6c8fa50d6e0450a45c6f55283975be9ed77a6731 Mon Sep 17 00:00:00 2001 From: Ilayda Cansin Koc Date: Thu, 25 Jul 2024 14:47:57 +0200 Subject: [PATCH 71/85] edit README.md for dynamic_bridge_endpoint approach Signed-off-by: Ilayda Cansin Koc --- README.md | 168 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 166 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7e54374..74deb07 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,12 @@ You operate a service and want to allow your users to sign in using Verifiable Credentials from a mobile wallet. But building that takes considerable time and expertise. +> [!NOTE] As a new feature, the bridge now supports incremental authorization. +> This allows the service provider to request additional Verifiable Credentials +> from the user via the bridge. Please see the +> [Incremental Authorization Flow](#incremental-authorization-flow) section for +> more details. + ### The Solution A service provider can run this dockerized bridge software that acts as a normal @@ -147,6 +153,40 @@ sequenceDiagram Client->>Browser: Provide access to protected service ``` +## Incremental Authorization Flow + +The user assumed to be logged in via the bridge and the service provider +requests additional VC from user to perform incremental authorization. + +```mermaid +sequenceDiagram + autonumber + actor User + participant Wallet + participant B as SSI-to-OIDC Bridge + participant SP as Service Provider + + SP ->> B: "POST /api/dynamic/createTempAuthorization" + B-->>SP: "Return UUID" + + SP->>B: "GET /api/dynamic/getQRCodeString" + B-->>SP: "Return QR code string" + + SP->>User: "Send Auth. page containing QR code" + + SP->>B: "GET /api/dynamic/getAuthResponse" + User->>Wallet: "Scan QR code" + Wallet->>B: "GET /api/dynamic/presentCredentialById" + B-->>Wallet: "Return metadata" + Wallet-->>User: "Prompt user" + + User ->>Wallet: "Select VC(s)" + Wallet->>B: "POST /api/dynamic/presentCredentialById" + B-->>Wallet: "Success" + + B-->>SP: "Return Auth Response" +``` + ## Running a Local Deployment A local deployment is a great way to test the bridge and to use it for @@ -278,7 +318,7 @@ credential, while forwarding all subject fields to the `id_token`: ```JSON [ { - "credentialID": "credential1", + "credentialID": "1", "patterns": [ { "issuer": "*", @@ -295,6 +335,11 @@ credential, while forwarding all subject fields to the `id_token`: ] ``` +> [!IMPORTANT] >`credentialID` helps us to extract the correct claims from the +> VC. Ideally, it should be an integer string starting from 1 and incrementing +> by 1 for each policy object in a policy file. It helps us to identify from +> which VC the claims should be extracted. + A login policy is always an array of objects that represent expected Verifiable Credentials. For each expected credential, we have to specify a unique ID used for internal tracking. We also need to provide an array of pattern objects, @@ -323,7 +368,7 @@ use of this could look like this: ```json [ { - "credentialId": "one", + "credentialId": "1", "patterns": [ { "issuer": "did:web:app.altme.io:issuer", @@ -384,6 +429,125 @@ logical operators that can combine multiple constraints: - _or_ Takes two constraint objects `a` and `b`. - _not_ Takes one constraint object `a` +### Multiple Policy Objects + +The bridge also supports multiple policy objects in a policy file. This allows +for more complex scenarios where multiple credentials are expected to perform +authorization. An example of such a policy file is: + +```json +[ + { + "credentialId": "1", + "type": "EmailPass", + "patterns": [ + { + "issuer": "did:web:app.altme.io:issuer", + "claims": [ + { + "claimPath": "$.credentialSubject.email" + } + ] + } + ] + }, + { + "credentialId": "2", + "type": "VerifiableId", + "patterns": [ + { + "issuer": "did:web:app.altme.io:issuer", + "claims": [ + { + "claimPath": "$.credentialSubject.id" + } + ] + } + ] + } +] +``` + +> [!IMPORTANT] > Each `credentialId` should be unique across all policy objects, +> and should have integer string values starting from 1. This helps us determine +> the correct policy object to apply to the VCs. + +> [!IMPORTANT] > Altough the `type` field is an optional parameter, it needs to +> be present in a policy file that has multiple policy objects. + +The `type` field helps to determine which policy object should be applied to +which type of credential. When multiple policy objects are used, this field +becomes important because the order of VCs in the VP is not guaranteed. Users +might submit VCs in a random order, so the type field ensures that each +credential is matched with the correct policy regardless of the submission +order. + +In the code snippet above, the first policy object is applied to the VC with +type `EmailPass` and the second policy object is applied to the VC with type +`VerifiableId`. If the type fields are the same for multiple policy objects, the +bridge will apply the policy objects to the VCs in the order they are defined in +the policy file. + +### Multiple Constraints + +For each policy object, you can define constraints as defined in +[Constraints](#constraints). An example of such a policy file is: + +```json +[ + { + "credentialId": "1", + "type": "EmailPass", + "patterns": [ + { + "issuer": "did:web:app.altme.io:issuer", + "claims": [ + { + "claimPath": "$.credentialSubject.email" + } + ], + "constraint": { + "op": "equalsDID", + "a": "$VP.proof.verificationMethod", + "b": "$1.credentialSubject.id" + } + } + ] + }, + { + "credentialId": "2", + "type": "VerifiableId", + "patterns": [ + { + "issuer": "did:web:app.altme.io:issuer", + "claims": [ + { + "claimPath": "$.credentialSubject.id" + } + ], + "constraint": { + "op": "equals", + "a": "$2.credentialSubject.id", + "b": "$1.credentialSubject.id" + } + } + ] + } +] +``` + +You can cross reference different VCs using the constraints. As in the example +below, the first VC's `credentialSubject.id` is compared with the second VC's +`credentialSubject.id` in the second policy object. + +> [!IMPORTANT] > You need to correctly define the JSONPaths of the constraint +> operands to be able to perform constraints check. The JSONPaths should have a +> structure like `$.` when having multiple policy +> objects. + +In the code snippet above, `a` operand of the constraint in the second policy +object refers to the `credentialSubject.id` of the VC with type `VerifiableId`. + ## Token Introspection Look into the access token like this: From 3eabc09102eb2aad19f208ab1df3a4bae6a59000 Mon Sep 17 00:00:00 2001 From: Ilayda Cansin Koc Date: Thu, 25 Jul 2024 14:59:20 +0200 Subject: [PATCH 72/85] fix failing test Signed-off-by: Ilayda Cansin Koc --- vclogin/__tests__/testdata/policies/acceptAnythingMultiVC.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vclogin/__tests__/testdata/policies/acceptAnythingMultiVC.json b/vclogin/__tests__/testdata/policies/acceptAnythingMultiVC.json index fb3552f..f00e676 100644 --- a/vclogin/__tests__/testdata/policies/acceptAnythingMultiVC.json +++ b/vclogin/__tests__/testdata/policies/acceptAnythingMultiVC.json @@ -7,7 +7,7 @@ "issuer": "*", "claims": [ { - "claimPath": "$1.credentialSubject.*", + "claimPath": "$.credentialSubject.*", "newPath": "$.firstCredentialSubject", "required": false } From 846329c746efc18d44a08f7fc6420f49d325c14c Mon Sep 17 00:00:00 2001 From: Ilayda Cansin Koc Date: Sun, 28 Jul 2024 22:10:47 +0200 Subject: [PATCH 73/85] add swagger for dynamic api endpoint documentation adjust readme.md for swagger documentation Signed-off-by: Ilayda Cansin Koc --- README.md | 72 +- vclogin/package-lock.json | 1693 ++++++++++++++++++++++++++++++++++-- vclogin/package.json | 3 + vclogin/pages/api-docs.tsx | 11 + vclogin/swagger.json | 201 +++++ 5 files changed, 1900 insertions(+), 80 deletions(-) create mode 100644 vclogin/pages/api-docs.tsx create mode 100644 vclogin/swagger.json diff --git a/README.md b/README.md index 74deb07..a4593c2 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,9 @@ You operate a service and want to allow your users to sign in using Verifiable Credentials from a mobile wallet. But building that takes considerable time and expertise. -> [!NOTE] As a new feature, the bridge now supports incremental authorization. + +> [!NOTE] +> As a new feature, the bridge now supports incremental authorization. > This allows the service provider to request additional Verifiable Credentials > from the user via the bridge. Please see the > [Incremental Authorization Flow](#incremental-authorization-flow) section for @@ -187,6 +189,27 @@ sequenceDiagram B-->>SP: "Return Auth Response" ``` +### API Documentation + +This documentation provides all the necessary information to interact with the +dynamic API endpoints. The API is documented using Swagger, which provides a +user-friendly interface to explore and test the API. + + +> [!NOTE] +> To access the Swagger documentation, you need to run the bridge and +> navigate to `http://localhost:5002/api-docs`. + +To authenticate requests to the dynamic API in Swagger, you need to provide a +valid API key. The API key is stored in the `.env` file in the `vclogin` folder. + + +> [!NOTE] +> To authenticate requests to the dynamic API in Swagger, first click on +> the "Authorize" button in the top right corner of the Swagger UI. Then, enter +> the API key in the "Value" field with the format `API_KEY `and click +> on the "Authorize" button. + ## Running a Local Deployment A local deployment is a great way to test the bridge and to use it for @@ -335,11 +358,6 @@ credential, while forwarding all subject fields to the `id_token`: ] ``` -> [!IMPORTANT] >`credentialID` helps us to extract the correct claims from the -> VC. Ideally, it should be an integer string starting from 1 and incrementing -> by 1 for each policy object in a policy file. It helps us to identify from -> which VC the claims should be extracted. - A login policy is always an array of objects that represent expected Verifiable Credentials. For each expected credential, we have to specify a unique ID used for internal tracking. We also need to provide an array of pattern objects, @@ -432,7 +450,7 @@ logical operators that can combine multiple constraints: ### Multiple Policy Objects The bridge also supports multiple policy objects in a policy file. This allows -for more complex scenarios where multiple credentials are expected to perform +for more complex scenarios where multiple credentials are needed to perform authorization. An example of such a policy file is: ```json @@ -468,25 +486,39 @@ authorization. An example of such a policy file is: ] ``` -> [!IMPORTANT] > Each `credentialId` should be unique across all policy objects, + +> [!IMPORTANT] +> Each `credentialId` should be unique across all policy objects, > and should have integer string values starting from 1. This helps us determine > the correct policy object to apply to the VCs. -> [!IMPORTANT] > Altough the `type` field is an optional parameter, it needs to -> be present in a policy file that has multiple policy objects. + +> [!IMPORTANT] +> Altough the `type` field is an optional parameter, it needs to be +> present in a policy file that has multiple policy objects. + + +> [!NOTE] +> First we reorder of policy objects in a policy file based on the `credentialId` and +> then we reorder the credentials in the VP based on the `type` field in the reordered policy file. +> This way we can ensure that the correct policy object is applied to the correct credential. The `type` field helps to determine which policy object should be applied to which type of credential. When multiple policy objects are used, this field -becomes important because the order of VCs in the VP is not guaranteed. Users -might submit VCs in a random order, so the type field ensures that each +becomes important because the order of VCs in the VP is not guaranteed. + +Users might submit VCs in a random order, so the type field ensures that each credential is matched with the correct policy regardless of the submission order. -In the code snippet above, the first policy object is applied to the VC with -type `EmailPass` and the second policy object is applied to the VC with type -`VerifiableId`. If the type fields are the same for multiple policy objects, the -bridge will apply the policy objects to the VCs in the order they are defined in -the policy file. +In the code snippet above + +- The first policy object is applied to the VC with type `EmailPass`. +- The second policy object is applied to the VC with type `VerifiableId`. + +If the type fields are the same for multiple policy objects, the bridge will +apply the policy objects to the VCs in the order they are defined in the policy +file. ### Multiple Constraints @@ -540,13 +572,15 @@ You can cross reference different VCs using the constraints. As in the example below, the first VC's `credentialSubject.id` is compared with the second VC's `credentialSubject.id` in the second policy object. -> [!IMPORTANT] > You need to correctly define the JSONPaths of the constraint + +> [!IMPORTANT] +> You need to correctly define the JSONPaths of the constraint > operands to be able to perform constraints check. The JSONPaths should have a > structure like `$.` when having multiple policy > objects. In the code snippet above, `a` operand of the constraint in the second policy -object refers to the `credentialSubject.id` of the VC with type `VerifiableId`. +object refers to the `credentialSubject.id` of the second VC. ## Token Introspection diff --git a/vclogin/package-lock.json b/vclogin/package-lock.json index a1facbc..6d0c749 100644 --- a/vclogin/package-lock.json +++ b/vclogin/package-lock.json @@ -26,6 +26,8 @@ "pino-http": "^10.1.0", "react": "18.2.0", "react-dom": "18.2.0", + "swagger-ui": "^5.17.14", + "swagger-ui-react": "^5.17.14", "ua-parser-js": "^1.0.38", "uuid": "^9.0.0" }, @@ -38,6 +40,7 @@ "@types/node": "^18.15.13", "@types/react": "18.0.37", "@types/react-dom": "18.0.11", + "@types/swagger-ui-react": "^4.18.3", "@types/ua-parser-js": "^0.7.39", "@types/uuid": "^9.0.1", "@vitest/coverage-v8": "^1.6.0", @@ -264,7 +267,6 @@ "version": "7.21.0", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", - "dev": true, "dependencies": { "regenerator-runtime": "^0.13.11" }, @@ -272,6 +274,23 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/runtime-corejs3": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.24.8.tgz", + "integrity": "sha512-DXG/BhegtMHhnN7YPIvxWd303/9aXvYFD1TjNL3CD6tUrhI2LVsg3Lck0aql5TRH29n4sj3emcROypkZVUfSuA==", + "dependencies": { + "core-js-pure": "^3.30.2", + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime-corejs3/node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, "node_modules/@babel/types": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", @@ -298,6 +317,11 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "node_modules/@braintree/sanitize-url": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.0.2.tgz", + "integrity": "sha512-NVf/1YycDMs6+FxS0Tb/W8MjJRDQdXF+tBfDtZ5UZeiRUkTmwKc4vmYCKZTyymfJk1gnMsauvZSX/HiV9jOABw==" + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -1632,6 +1656,471 @@ "resolved": "https://registry.npmjs.org/@stablelib/wipe/-/wipe-1.0.1.tgz", "integrity": "sha512-WfqfX/eXGiAd3RJe4VU2snh/ZPwtSjLG4ynQ/vYzvghTh7dHFcI1wl+nrkWG6lGhukOxOsUHfv8dUXr58D0ayg==" }, + "node_modules/@swagger-api/apidom-ast": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ast/-/apidom-ast-1.0.0-alpha.6.tgz", + "integrity": "sha512-uzDNIeTLFeITzK7yX9PSsu3dl92rHP/gKMNAlJhmDRr7r+OLr5dCpAzyZ0WvONRxjxR8Otj5LX4AD12+EX32fg==", + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-error": "^1.0.0-alpha.6", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "unraw": "^3.0.0" + } + }, + "node_modules/@swagger-api/apidom-core": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-core/-/apidom-core-1.0.0-alpha.6.tgz", + "integrity": "sha512-5u7dK3+8cF2h5bHEI/qrA6JrfXX+HIHSmUgPGbeMAqSCEfpsjnsngXK6gHtd4ktLlPz3TplNZAQl88wIp+39nw==", + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-ast": "^1.0.0-alpha.6", + "@swagger-api/apidom-error": "^1.0.0-alpha.6", + "@types/ramda": "~0.30.0", + "minim": "~0.23.8", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "short-unique-id": "^5.0.2", + "ts-mixer": "^6.0.3" + } + }, + "node_modules/@swagger-api/apidom-error": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-error/-/apidom-error-1.0.0-alpha.6.tgz", + "integrity": "sha512-eOcqaXwLitjp5CIGYR0W2oM6p4UiTL7EjNdkCcfrELKHdgNS6U7kZdl3KCBlOuMb14CijwtZNEJbIGhhGZUYHg==", + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7" + } + }, + "node_modules/@swagger-api/apidom-json-pointer": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-json-pointer/-/apidom-json-pointer-1.0.0-alpha.6.tgz", + "integrity": "sha512-8ULBcQRQ1UPgqJ+ZuuKjmeKeuxqbuIUHwWHRRA848jK5+IHmNw/Cp68MhNiwYXLmTLkTIGaDubcOplMeHCxSyA==", + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-alpha.6", + "@swagger-api/apidom-error": "^1.0.0-alpha.6", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-ns-api-design-systems": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-api-design-systems/-/apidom-ns-api-design-systems-1.0.0-alpha.6.tgz", + "integrity": "sha512-JRiImw3XKrfm22pzlx7uM6XYJtWM71QkCLy86gOTBFybWgTOCECnN4c8jFBnYl6KYuIb2VV9kXZs38xjK4NfBQ==", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-alpha.6", + "@swagger-api/apidom-error": "^1.0.0-alpha.6", + "@swagger-api/apidom-ns-openapi-3-1": "^1.0.0-alpha.6", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + } + }, + "node_modules/@swagger-api/apidom-ns-asyncapi-2": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-asyncapi-2/-/apidom-ns-asyncapi-2-1.0.0-alpha.6.tgz", + "integrity": "sha512-I8Yq+AmJPUJihGneBv1/m/ly+2dp4FJiCxW/auRQSicvYIV7hoBO5qGZqcEEoVt7OpuhFbFqI2pwnambz90Uvg==", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-alpha.6", + "@swagger-api/apidom-ns-json-schema-draft-7": "^1.0.0-alpha.6", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + } + }, + "node_modules/@swagger-api/apidom-ns-json-schema-draft-4": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-4/-/apidom-ns-json-schema-draft-4-1.0.0-alpha.6.tgz", + "integrity": "sha512-E8JjqdDgopnLd4HXEXGSrc6rkbDyB8Qk6sYgmyT6lB8caFUMRdJ5Rp57fPePETnVpegb8cAuKjBdjTImX1gQ3Q==", + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-ast": "^1.0.0-alpha.6", + "@swagger-api/apidom-core": "^1.0.0-alpha.6", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.4" + } + }, + "node_modules/@swagger-api/apidom-ns-json-schema-draft-6": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-6/-/apidom-ns-json-schema-draft-6-1.0.0-alpha.6.tgz", + "integrity": "sha512-uzYmV65nn7i6nlp7Kp7ldGfAoXWPPquIocoHLWDBTx5sPdS+ALu2T2yvytav0z6StKeV+gU2HZjMLVRWdLzLZA==", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-alpha.6", + "@swagger-api/apidom-error": "^1.0.0-alpha.6", + "@swagger-api/apidom-ns-json-schema-draft-4": "^1.0.0-alpha.6", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.4" + } + }, + "node_modules/@swagger-api/apidom-ns-json-schema-draft-7": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-7/-/apidom-ns-json-schema-draft-7-1.0.0-alpha.6.tgz", + "integrity": "sha512-dWEVUVMByOs5JIMsgcceETOYH3nTiAHoIIjXbYeHP6m6HaNP8IE5ex0ZgfmQc29uH0E6H+6aYAv1flfvy56rVQ==", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-alpha.6", + "@swagger-api/apidom-error": "^1.0.0-alpha.6", + "@swagger-api/apidom-ns-json-schema-draft-6": "^1.0.0-alpha.6", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.4" + } + }, + "node_modules/@swagger-api/apidom-ns-openapi-2": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-2/-/apidom-ns-openapi-2-1.0.0-alpha.6.tgz", + "integrity": "sha512-sPwvOY+FGd5yEAijYLupmIYwf4HIpW6yegzrz6uUvGmONZpiCNIidCu+2m6GyYCoZ/lQZdPMw29DuU2O4iiDKw==", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-alpha.6", + "@swagger-api/apidom-error": "^1.0.0-alpha.6", + "@swagger-api/apidom-ns-json-schema-draft-4": "^1.0.0-alpha.6", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + } + }, + "node_modules/@swagger-api/apidom-ns-openapi-3-0": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-0/-/apidom-ns-openapi-3-0-1.0.0-alpha.6.tgz", + "integrity": "sha512-kE4s17j69DDvXrf7xeRTunmSQJLiX52fCX1YnfC81e1IPm3q/mdpkZiysM87FuJQQj522fX2G+QUIJlDkD5U9w==", + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-alpha.6", + "@swagger-api/apidom-error": "^1.0.0-alpha.6", + "@swagger-api/apidom-ns-json-schema-draft-4": "^1.0.0-alpha.6", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + } + }, + "node_modules/@swagger-api/apidom-ns-openapi-3-1": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-1/-/apidom-ns-openapi-3-1-1.0.0-alpha.6.tgz", + "integrity": "sha512-2QA2z9beyaVyZDOXbLg4Nu8o8xKWo9L0WHWOP+hg/haGRyyPHXgyg2XdwRuFBozBI9wBaIfEg1lvNC+J0taDjg==", + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-ast": "^1.0.0-alpha.6", + "@swagger-api/apidom-core": "^1.0.0-alpha.6", + "@swagger-api/apidom-json-pointer": "^1.0.0-alpha.6", + "@swagger-api/apidom-ns-openapi-3-0": "^1.0.0-alpha.6", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + } + }, + "node_modules/@swagger-api/apidom-ns-workflows-1": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-workflows-1/-/apidom-ns-workflows-1-1.0.0-alpha.6.tgz", + "integrity": "sha512-9kXU7hUdz25TTGF8b1pmKGugkET4gkW7ING+qSUjU5nWdrkdUIVuq1o8qjaZwRDRvkNynnlRbWHqXeWgRWyi/w==", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-alpha.6", + "@swagger-api/apidom-ns-openapi-3-1": "^1.0.0-alpha.6", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-api-design-systems-json": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-api-design-systems-json/-/apidom-parser-adapter-api-design-systems-json-1.0.0-alpha.6.tgz", + "integrity": "sha512-7THBmhvwTmsb1eFXvj/tbIK91g5tzkvhxGSUVbpGt1zApEFmKvjZbDhGnMx15CImIUURW1QZ6TQ/cZ7jRWk8kQ==", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-alpha.6", + "@swagger-api/apidom-ns-api-design-systems": "^1.0.0-alpha.6", + "@swagger-api/apidom-parser-adapter-json": "^1.0.0-alpha.6", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-api-design-systems-yaml": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-api-design-systems-yaml/-/apidom-parser-adapter-api-design-systems-yaml-1.0.0-alpha.6.tgz", + "integrity": "sha512-xD0aRgRGPaM1J+H3nRg8qP6bQ4fNtsUopoc6JEKzi7NJxd+r/mZV4pVa+Gr6CS+xv4d6TJ53UCJmGsjgmR1bQw==", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-alpha.6", + "@swagger-api/apidom-ns-api-design-systems": "^1.0.0-alpha.6", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-alpha.6", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-asyncapi-json-2": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-json-2/-/apidom-parser-adapter-asyncapi-json-2-1.0.0-alpha.6.tgz", + "integrity": "sha512-BVouq+7XiS2/HmNHd/CHHieyRT5mTN+kqYACnKV/TAzC5+fK3t2mcdng4I81m3Mzb9OJ/VpHiEVlSZiWZoPU/Q==", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-alpha.6", + "@swagger-api/apidom-ns-asyncapi-2": "^1.0.0-alpha.6", + "@swagger-api/apidom-parser-adapter-json": "^1.0.0-alpha.6", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-yaml-2/-/apidom-parser-adapter-asyncapi-yaml-2-1.0.0-alpha.6.tgz", + "integrity": "sha512-CsUu5t6ijflz0DDjdoxE/OUbSjBAeh5v2zfMRVOfGdnNDhDhrE/3P0VTpdKdVmbWQ1ueIbq2QaC8thQ+Jcxwyg==", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-alpha.6", + "@swagger-api/apidom-ns-asyncapi-2": "^1.0.0-alpha.6", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-alpha.6", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-json": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-json/-/apidom-parser-adapter-json-1.0.0-alpha.6.tgz", + "integrity": "sha512-ruEkgvJSmBUUsGZdYiHeczekTWCJSWHrNvhAczY6c1ZFhpCukZg9tCqdVhnni/LPp4r4h7BdNldjY7dtrUkCiQ==", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-ast": "^1.0.0-alpha.6", + "@swagger-api/apidom-core": "^1.0.0-alpha.6", + "@swagger-api/apidom-error": "^1.0.0-alpha.6", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "tree-sitter": "=0.20.4", + "tree-sitter-json": "=0.20.2", + "web-tree-sitter": "=0.20.3" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-2": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-2/-/apidom-parser-adapter-openapi-json-2-1.0.0-alpha.6.tgz", + "integrity": "sha512-RLvjHvjURnqfWxEdLFOW6agBS8CzVyV9++Vg4TSB9gPCNsTlz5w9iy82NYvApExHJIlN55Ga92OZ6CuWXJ8iKw==", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-alpha.6", + "@swagger-api/apidom-ns-openapi-2": "^1.0.0-alpha.6", + "@swagger-api/apidom-parser-adapter-json": "^1.0.0-alpha.6", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-3-0": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-0/-/apidom-parser-adapter-openapi-json-3-0-1.0.0-alpha.6.tgz", + "integrity": "sha512-cf9+M9vySTrUZW+m2SR04IMnl+5QX6P7S2xgFF705ySOMkPiA9GTgAZJFqwzncAEPovkp7/A24adxyhFz52iZg==", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-alpha.6", + "@swagger-api/apidom-ns-openapi-3-0": "^1.0.0-alpha.6", + "@swagger-api/apidom-parser-adapter-json": "^1.0.0-alpha.6", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-3-1": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-1/-/apidom-parser-adapter-openapi-json-3-1-1.0.0-alpha.6.tgz", + "integrity": "sha512-Z7TCUWB/VotmHU5kjUcAlu3qMHCVr1pOpnsuI01I6vCHGJOqUZPelnNqVyw5tjiVbgwDCKzXiPSQo9gGG1HoGA==", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-alpha.6", + "@swagger-api/apidom-ns-openapi-3-1": "^1.0.0-alpha.6", + "@swagger-api/apidom-parser-adapter-json": "^1.0.0-alpha.6", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-2": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-2/-/apidom-parser-adapter-openapi-yaml-2-1.0.0-alpha.6.tgz", + "integrity": "sha512-XI0qlTjL2Q1TMvzxjjEki2iuJqt43C0mwGHremjcpbNHpJejnkEGFDPJqs1rp3RobwRl1ftHVFJi7JVPiA8Zvw==", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-alpha.6", + "@swagger-api/apidom-ns-openapi-2": "^1.0.0-alpha.6", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-alpha.6", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-0/-/apidom-parser-adapter-openapi-yaml-3-0-1.0.0-alpha.6.tgz", + "integrity": "sha512-yGd5dP52BrBMO4/nCJdcvotxCbmbXYOi/nQrj7rL4/7VFdKbC4ngT0ggprvKE8CVQC99qPz4qR1y728QdioPAg==", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-alpha.6", + "@swagger-api/apidom-ns-openapi-3-0": "^1.0.0-alpha.6", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-alpha.6", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-1/-/apidom-parser-adapter-openapi-yaml-3-1-1.0.0-alpha.6.tgz", + "integrity": "sha512-4F/rWh7bi97y20SRskrqz9UdO+YwHOn+vcOvNs5/arI5niSmTeAN3dgH9emTx1LJi8d7woUAct+TEqshwoh/zQ==", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-alpha.6", + "@swagger-api/apidom-ns-openapi-3-1": "^1.0.0-alpha.6", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-alpha.6", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-workflows-json-1": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-workflows-json-1/-/apidom-parser-adapter-workflows-json-1-1.0.0-alpha.6.tgz", + "integrity": "sha512-K2gZFUHtp+Vw9rcizanIJLxSsaYQWNh1QtsEVGtAkn3RBVa130i3umcgQBKuvaBzfhi+Zr21sR4LSrs4XiRHiQ==", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-alpha.6", + "@swagger-api/apidom-ns-workflows-1": "^1.0.0-alpha.6", + "@swagger-api/apidom-parser-adapter-json": "^1.0.0-alpha.6", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-workflows-yaml-1": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-workflows-yaml-1/-/apidom-parser-adapter-workflows-yaml-1-1.0.0-alpha.6.tgz", + "integrity": "sha512-yaJ9Iir43odK/zTB0tVL43RBC4ktQvNRfuT21vedqNaxO9J2pjTPy9NkIXJuOrcizinAASDLLUYX/b0UONhVxg==", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-alpha.6", + "@swagger-api/apidom-ns-workflows-1": "^1.0.0-alpha.6", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-alpha.6", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-yaml-1-2": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-yaml-1-2/-/apidom-parser-adapter-yaml-1-2-1.0.0-alpha.6.tgz", + "integrity": "sha512-qevJf2IRvskyvgeGnkJXCGKFnmrcnuMoFHoboI3nJFqdesN74g1hGm1VIVAOOkM4AcdG1w7BviCHEt4YEYGPcQ==", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-ast": "^1.0.0-alpha.6", + "@swagger-api/apidom-core": "^1.0.0-alpha.6", + "@swagger-api/apidom-error": "^1.0.0-alpha.6", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "tree-sitter": "=0.20.4", + "tree-sitter-yaml": "=0.5.0", + "web-tree-sitter": "=0.20.3" + } + }, + "node_modules/@swagger-api/apidom-reference": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-reference/-/apidom-reference-1.0.0-alpha.6.tgz", + "integrity": "sha512-DbsxxgQCVd8ZTJag3EOtzJ2rtsaq4z5z/A4nEgzVQhStuHdRwrbQfxem1g7p6dOK2VrGEGf73UllGJvGV+uPpg==", + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-alpha.6", + "@types/ramda": "~0.30.0", + "axios": "^1.4.0", + "minimatch": "^7.4.3", + "process": "^0.11.10", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + }, + "optionalDependencies": { + "@swagger-api/apidom-error": "^1.0.0-alpha.1", + "@swagger-api/apidom-json-pointer": "^1.0.0-alpha.1", + "@swagger-api/apidom-ns-asyncapi-2": "^1.0.0-alpha.1", + "@swagger-api/apidom-ns-openapi-2": "^1.0.0-alpha.1", + "@swagger-api/apidom-ns-openapi-3-0": "^1.0.0-alpha.1", + "@swagger-api/apidom-ns-openapi-3-1": "^1.0.0-alpha.1", + "@swagger-api/apidom-ns-workflows-1": "^1.0.0-alpha.1", + "@swagger-api/apidom-parser-adapter-api-design-systems-json": "^1.0.0-alpha.1", + "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": "^1.0.0-alpha.1", + "@swagger-api/apidom-parser-adapter-asyncapi-json-2": "^1.0.0-alpha.1", + "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": "^1.0.0-alpha.1", + "@swagger-api/apidom-parser-adapter-json": "^1.0.0-alpha.1", + "@swagger-api/apidom-parser-adapter-openapi-json-2": "^1.0.0-alpha.1", + "@swagger-api/apidom-parser-adapter-openapi-json-3-0": "^1.0.0-alpha.1", + "@swagger-api/apidom-parser-adapter-openapi-json-3-1": "^1.0.0-alpha.1", + "@swagger-api/apidom-parser-adapter-openapi-yaml-2": "^1.0.0-alpha.1", + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": "^1.0.0-alpha.1", + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": "^1.0.0-alpha.1", + "@swagger-api/apidom-parser-adapter-workflows-json-1": "^1.0.0-alpha.1", + "@swagger-api/apidom-parser-adapter-workflows-yaml-1": "^1.0.0-alpha.1", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-alpha.1" + } + }, + "node_modules/@swagger-api/apidom-reference/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@swagger-api/apidom-reference/node_modules/minimatch": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.6.tgz", + "integrity": "sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@swc/counter": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", @@ -2056,6 +2545,14 @@ "@types/send": "*" } }, + "node_modules/@types/hast": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", + "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", + "dependencies": { + "@types/unist": "^2" + } + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -2095,7 +2592,7 @@ "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", - "dev": true + "devOptional": true }, "node_modules/@types/qs": { "version": "6.9.7", @@ -2103,6 +2600,14 @@ "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", "dev": true }, + "node_modules/@types/ramda": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.30.1.tgz", + "integrity": "sha512-aoyF/ADPL6N+/NXXfhPWF+Qj6w1Cql59m9wX0Gi15uyF+bpzXeLd63HPdiTDE2bmLXfNcVufsDPKmbfOrOzTBA==", + "dependencies": { + "types-ramda": "^0.30.1" + } + }, "node_modules/@types/range-parser": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", @@ -2173,12 +2678,31 @@ "@types/node": "*" } }, + "node_modules/@types/swagger-ui-react": { + "version": "4.18.3", + "resolved": "https://registry.npmjs.org/@types/swagger-ui-react/-/swagger-ui-react-4.18.3.tgz", + "integrity": "sha512-Mo/R7IjDVwtiFPs84pWvh5pI9iyNGBjmfielxqbOh2Jv+8WVSDVe8Nu25kb5BOuV2xmGS3o33jr6nwDJMBcX+Q==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/ua-parser-js": { "version": "0.7.39", "resolved": "https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.39.tgz", "integrity": "sha512-P/oDfpofrdtF5xw433SPALpdSchtJmY7nsJItf8h3KXqOslkbySh8zq4dSWXH2oTjRvJ5PczVEoCZPow6GicLg==", "dev": true }, + "node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, "node_modules/@types/uuid": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.1.tgz", @@ -2742,6 +3266,11 @@ "node": ">= 8" } }, + "node_modules/apg-lite": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/apg-lite/-/apg-lite-1.0.4.tgz", + "integrity": "sha512-B32zCN3IdHIc99Vy7V9BaYTUzLeRA8YXYY1aQD1/5I2aqIrO0coi4t6hJPqMisidlBxhyME8UexkHt31SlR6Og==" + }, "node_modules/append-field": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", @@ -3017,6 +3546,14 @@ "node": ">=8.0.0" } }, + "node_modules/autolinker": { + "version": "3.16.2", + "resolved": "https://registry.npmjs.org/autolinker/-/autolinker-3.16.2.tgz", + "integrity": "sha512-JiYl7j2Z19F9NdTmirENSUUIIL/9MytEWtmzhfmsKPCp9E+G35Y0UNCMoM9tFigxT59qSc8Ml2dlZXOCVTYwuA==", + "dependencies": { + "tslib": "^2.3.0" + } + }, "node_modules/autoprefixer": { "version": "10.4.14", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz", @@ -3102,8 +3639,7 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/bare-events": { "version": "2.3.1", @@ -3208,7 +3744,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, + "devOptional": true, "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -3219,7 +3755,7 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, + "devOptional": true, "funding": [ { "type": "github", @@ -3406,7 +3942,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "dev": true, "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -3499,6 +4034,33 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/check-error": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", @@ -3550,6 +4112,12 @@ "node": ">= 6" } }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "devOptional": true + }, "node_modules/cipher-base": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", @@ -3560,9 +4128,9 @@ } }, "node_modules/classnames": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", - "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" }, "node_modules/client-only": { "version": "0.0.1", @@ -3614,6 +4182,15 @@ "node": ">= 0.8" } }, + "node_modules/comma-separated-tokens": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", + "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/command-exists": { "version": "1.2.9", "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", @@ -3702,6 +4279,32 @@ "integrity": "sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==", "dev": true }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/copy-to-clipboard": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", + "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", + "dependencies": { + "toggle-selection": "^1.0.6" + } + }, + "node_modules/core-js-pure": { + "version": "3.37.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.37.1.tgz", + "integrity": "sha512-J/r5JTHSmzTxbiYYrzXg9w1VpqrYt+gexenBE9pugeyhwPZTAEJddyiReJWsLO6uNQ8xJZFbod6XC7KKwatCiA==", + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", @@ -3806,6 +4409,11 @@ "node": ">= 8" } }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==" + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -3852,7 +4460,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==", - "dev": true + "devOptional": true }, "node_modules/damerau-levenshtein": { "version": "1.0.8", @@ -3959,6 +4567,21 @@ "optional": true, "peer": true }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "optional": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/deep-eql": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", @@ -3999,6 +4622,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -4016,7 +4647,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -4080,6 +4710,15 @@ "node": ">=6" } }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -4182,12 +4821,6 @@ "node": ">= 8.0" } }, - "node_modules/dockerode/node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true - }, "node_modules/dockerode/node_modules/tar-fs": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.1.tgz", @@ -4227,6 +4860,19 @@ "node": ">=12" } }, + "node_modules/dompurify": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.4.tgz", + "integrity": "sha512-2gnshi6OshmuKil8rMZuQCGiUF3cUxHY3NGDzUAdUx/NPEe5DVnO8BDoAQouvgwnx0R/+a6jUn36Z0FSdq8vww==" + }, + "node_modules/drange": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/drange/-/drange-1.1.1.tgz", + "integrity": "sha512-pYxfDYpued//QpnLIm4Avk7rsNtAtQkUES2cwAYSvD/wd2pKD71gN2Ebj3e7klzXwjocvE8c5vx/1fxwpqmSxA==", + "engines": { + "node": ">=4" + } + }, "node_modules/electron-to-chromium": { "version": "1.4.596", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.596.tgz", @@ -4262,7 +4908,7 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, + "devOptional": true, "dependencies": { "once": "^1.4.0" } @@ -4366,7 +5012,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dev": true, "dependencies": { "get-intrinsic": "^1.2.4" }, @@ -4378,7 +5023,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "engines": { "node": ">= 0.4" } @@ -5023,6 +5667,15 @@ "node": ">=0.8.x" } }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "optional": true, + "engines": { + "node": ">=6" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -5063,6 +5716,11 @@ "node": ">= 6" } }, + "node_modules/fast-json-patch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz", + "integrity": "sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==" + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -5090,6 +5748,18 @@ "reusify": "^1.0.4" } }, + "node_modules/fault": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz", + "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==", + "dependencies": { + "format": "^0.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -5190,6 +5860,14 @@ "node": ">= 6" } }, + "node_modules/format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", + "engines": { + "node": ">=0.4.x" + } + }, "node_modules/fraction.js": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", @@ -5235,7 +5913,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true + "devOptional": true }, "node_modules/fs.realpath": { "version": "1.0.0", @@ -5261,7 +5939,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -5314,7 +5991,6 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "dev": true, "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2", @@ -5367,6 +6043,12 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "optional": true + }, "node_modules/glob": { "version": "7.1.7", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", @@ -5471,7 +6153,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, "dependencies": { "get-intrinsic": "^1.1.3" }, @@ -5524,7 +6205,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, "dependencies": { "es-define-property": "^1.0.0" }, @@ -5536,7 +6216,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -5548,7 +6227,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -5597,7 +6275,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "dependencies": { "function-bind": "^1.1.2" }, @@ -5605,13 +6282,46 @@ "node": ">= 0.4" } }, - "node_modules/hey-listen": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/hey-listen/-/hey-listen-1.0.8.tgz", - "integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==" + "node_modules/hast-util-parse-selector": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz", + "integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } }, - "node_modules/hmac-drbg": { - "version": "1.0.1", + "node_modules/hastscript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz", + "integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^1.0.0", + "hast-util-parse-selector": "^2.0.0", + "property-information": "^5.0.0", + "space-separated-tokens": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hey-listen": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/hey-listen/-/hey-listen-1.0.8.tgz", + "integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==" + }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "engines": { + "node": "*" + } + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", "dependencies": { @@ -5713,6 +6423,14 @@ "node": ">= 4" } }, + "node_modules/immutable": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz", + "integrity": "sha512-15gZoQ38eYjEjxkorfbcgBKBL6R7T459OuK+CpcWt7O3KF4uPCx2tD0uFETlUDIyo+1789crbMhTvQBSR5yBMg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -5752,6 +6470,12 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "optional": true + }, "node_modules/internal-slot": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", @@ -5766,6 +6490,14 @@ "node": ">= 0.4" } }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/ioredis": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.3.2.tgz", @@ -5789,6 +6521,28 @@ "url": "https://opencollective.com/ioredis" } }, + "node_modules/is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "dependencies": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -5920,6 +6674,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-docker": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", @@ -5964,6 +6727,15 @@ "node": ">=0.10.0" } }, + "node_modules/is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-map": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", @@ -6228,6 +7000,11 @@ "integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==", "dev": true }, + "node_modules/js-file-download": { + "version": "0.4.12", + "resolved": "https://registry.npmjs.org/js-file-download/-/js-file-download-0.4.12.tgz", + "integrity": "sha512-rML+NkoD08p5Dllpjo0ffy4jRHeY6Zsapvr/W86N7E0yuzAO6qa5X9+xog6zQNlH102J7IXljNY2FtS6Lj3ucg==" + }, "node_modules/js-sdsl": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", @@ -6497,6 +7274,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" + }, "node_modules/lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", @@ -6557,11 +7344,24 @@ "get-func-name": "^2.0.1" } }, + "node_modules/lowlight": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz", + "integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==", + "dependencies": { + "fault": "^1.0.0", + "highlight.js": "~10.7.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, + "devOptional": true, "dependencies": { "yallist": "^4.0.0" }, @@ -6682,6 +7482,29 @@ "node": ">= 0.6" } }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minim": { + "version": "0.23.8", + "resolved": "https://registry.npmjs.org/minim/-/minim-0.23.8.tgz", + "integrity": "sha512-bjdr2xW1dBCMsMGGsUeqM4eFI60m94+szhxWys+B1ztIt6gWSfeGBdSVCIawezeHYLYn0j6zrsXdQS/JllBzww==", + "dependencies": { + "lodash": "^4.15.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -6727,7 +7550,7 @@ "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "dev": true + "devOptional": true }, "node_modules/mlly": { "version": "1.7.1", @@ -6784,7 +7607,6 @@ "version": "2.19.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.19.0.tgz", "integrity": "sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==", - "dev": true, "optional": true }, "node_modules/nanoid": { @@ -6804,6 +7626,12 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", + "optional": true + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -6967,6 +7795,41 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/node-abi": { + "version": "3.65.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.65.0.tgz", + "integrity": "sha512-ThjYBfoDNr08AWx6hGaRbfPwxKV9kVzAzOzlLKbk2CuqXE2xnCh+cbAGnwM3t8Lq4v9rUB7VfondlkBckcJrVA==", + "optional": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==" + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -6986,6 +7849,22 @@ } } }, + "node_modules/node-fetch-commonjs": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch-commonjs/-/node-fetch-commonjs-3.3.2.tgz", + "integrity": "sha512-VBlAiynj3VMLrotgwOS3OyECFxas5y7ltLcK4t41lMUZeaK15Ym4QRkqN0EQKAFL42q9i21EPKjzLUPfltR72A==", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, "node_modules/node-fetch/node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -7058,7 +7937,6 @@ "version": "1.13.1", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -7179,7 +8057,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, + "devOptional": true, "dependencies": { "wrappy": "1" } @@ -7201,6 +8079,28 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/openapi-path-templating": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/openapi-path-templating/-/openapi-path-templating-1.6.0.tgz", + "integrity": "sha512-1atBNwOUrZXthTvlvvX8k8ovFEF3iA8mDidYMkdOtvVdndBhTrspbwGXNOzEUaJhm9iUl4Tf5uQaeTLAJvwPig==", + "dependencies": { + "apg-lite": "^1.0.3" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/openapi-server-url-templating": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/openapi-server-url-templating/-/openapi-server-url-templating-1.1.0.tgz", + "integrity": "sha512-dtyTFKx2xVcO0W8JKaluXIHC9l/MLjHeflBaWjiWNMCHp/TBs9dEjQDbj/VFlHR4omFOKjjmqm1pW1aCAhmPBg==", + "dependencies": { + "apg-lite": "^1.0.3" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", @@ -7267,6 +8167,23 @@ "node": ">=6" } }, + "node_modules/parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "dependencies": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -7620,6 +8537,44 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true }, + "node_modules/prebuild-install": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz", + "integrity": "sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prebuild-install/node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "optional": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -7644,6 +8599,14 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/prismjs": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", + "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -7711,6 +8674,18 @@ "node": ">=10" } }, + "node_modules/property-information": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", + "integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==", + "dependencies": { + "xtend": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -7728,7 +8703,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, + "devOptional": true, "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -7760,13 +8735,24 @@ "node": ">=10.13.0" } }, + "node_modules/qs": { + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.3.tgz", + "integrity": "sha512-AWJm14H1vVaO/iNZ4/hO+HyaTehuy9nRqVdkTqlJt0HWvBiBIEXFmb4C0DGeYo3Xes9rrEW+TxHsaigCbN5ICQ==", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true, - "optional": true, - "peer": true + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" }, "node_modules/queue-microtask": { "version": "1.2.3", @@ -7811,6 +8797,74 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ramda": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz", + "integrity": "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ramda" + } + }, + "node_modules/ramda-adjunct": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ramda-adjunct/-/ramda-adjunct-5.0.1.tgz", + "integrity": "sha512-UTQCcWnoiuYH+ua+jGg3GTktcmCSD2W7OO2++tmv8p2Ze+N9VgVACERg4g36rRfIXklVMtqazyBLBWXfoPKgRQ==", + "engines": { + "node": ">=0.10.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ramda-adjunct" + }, + "peerDependencies": { + "ramda": ">= 0.30.0" + } + }, + "node_modules/randexp": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.5.3.tgz", + "integrity": "sha512-U+5l2KrcMNOUPYvazA3h5ekF80FHTUG+87SEAmHZmolh1M+i/WyTCxVzmi+tidIa1tM4BSe8g2Y/D3loWDjj+w==", + "dependencies": { + "drange": "^1.0.2", + "ret": "^0.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "optional": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -7822,6 +8876,30 @@ "node": ">=0.10.0" } }, + "node_modules/react-copy-to-clipboard": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz", + "integrity": "sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==", + "dependencies": { + "copy-to-clipboard": "^3.3.1", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": "^15.3.0 || 16 || 17 || 18" + } + }, + "node_modules/react-debounce-input": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/react-debounce-input/-/react-debounce-input-3.3.0.tgz", + "integrity": "sha512-VEqkvs8JvY/IIZvh71Z0TC+mdbxERvYF33RcebnodlsUZ8RSgyKe2VWaHXv4+/8aoOgXLxWrdsYs2hDhcwbUgA==", + "dependencies": { + "lodash.debounce": "^4", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": "^15.3.0 || 16 || 17 || 18" + } + }, "node_modules/react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", @@ -7834,11 +8912,55 @@ "react": "^18.2.0" } }, + "node_modules/react-immutable-proptypes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/react-immutable-proptypes/-/react-immutable-proptypes-2.2.0.tgz", + "integrity": "sha512-Vf4gBsePlwdGvSZoLSBfd4HAP93HDauMY4fDjXhreg/vg6F3Fj/MXDNyTbltPC/xZKmZc+cjLu3598DdYK6sgQ==", + "dependencies": { + "invariant": "^2.2.2" + }, + "peerDependencies": { + "immutable": ">=3.6.2" + } + }, + "node_modules/react-immutable-pure-component": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/react-immutable-pure-component/-/react-immutable-pure-component-2.2.2.tgz", + "integrity": "sha512-vkgoMJUDqHZfXXnjVlG3keCxSO/U6WeDQ5/Sl0GK2cH8TOxEzQ5jXqDXHEL/jqk6fsNxV05oH5kD7VNMUE2k+A==", + "peerDependencies": { + "immutable": ">= 2 || >= 4.0.0-rc", + "react": ">= 16.6", + "react-dom": ">= 16.6" + } + }, + "node_modules/react-inspector": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/react-inspector/-/react-inspector-6.0.2.tgz", + "integrity": "sha512-x+b7LxhmHXjHoU/VrFAzw5iutsILRoYyDq97EDYdFpPLcvqtEzk4ZSZSQjnFPbr5T57tLXnHcqFYoN1pI6u8uQ==", + "peerDependencies": { + "react": "^16.8.4 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-syntax-highlighter": { + "version": "15.5.0", + "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.5.0.tgz", + "integrity": "sha512-+zq2myprEnQmH5yw6Gqc8lD55QHnpKaU8TOcFeC/Lg/MQSs8UknEA0JC4nTZGFAXC2J2Hyj/ijJ7NlabyPi2gg==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "highlight.js": "^10.4.1", + "lowlight": "^1.17.0", + "prismjs": "^1.27.0", + "refractor": "^3.6.0" + }, + "peerDependencies": { + "react": ">= 0.14.0" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -7930,11 +9052,45 @@ "node": ">=4" } }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" + }, + "node_modules/redux-immutable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/redux-immutable/-/redux-immutable-4.0.0.tgz", + "integrity": "sha512-SchSn/DWfGb3oAejd+1hhHx01xUoxY+V7TeK0BKqpkLKiQPVFf7DYzEaKmrEVxsWxielKfSK9/Xq66YyxgR1cg==", + "peerDependencies": { + "immutable": "^3.8.1 || ^4.0.0-rc.1" + } + }, + "node_modules/refractor": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz", + "integrity": "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==", + "dependencies": { + "hastscript": "^6.0.0", + "parse-entities": "^2.0.0", + "prismjs": "~1.27.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/prismjs": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz", + "integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==", + "engines": { + "node": ">=6" + } + }, "node_modules/regenerator-runtime": { "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", - "dev": true + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, "node_modules/regexp.prototype.flags": { "version": "1.5.2", @@ -7962,6 +9118,37 @@ "node": ">=8" } }, + "node_modules/remarkable": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/remarkable/-/remarkable-2.0.1.tgz", + "integrity": "sha512-YJyMcOH5lrR+kZdmB0aJJ4+93bEojRZ1HGDn9Eagu6ibg7aVZhc3OWbbShRid+Q5eAfsEqWxpe+g5W5nYNfNiA==", + "dependencies": { + "argparse": "^1.0.10", + "autolinker": "^3.11.0" + }, + "bin": { + "remarkable": "bin/remarkable.js" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/remarkable/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "engines": { + "node": ">=0.10" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -7987,10 +9174,12 @@ "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true, - "optional": true, - "peer": true + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==" }, "node_modules/resolve": { "version": "1.22.2", @@ -8017,6 +9206,14 @@ "node": ">=4" } }, + "node_modules/ret": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz", + "integrity": "sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==", + "engines": { + "node": ">=4" + } + }, "node_modules/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", @@ -8220,7 +9417,7 @@ "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, + "devOptional": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -8231,6 +9428,20 @@ "node": ">=10" } }, + "node_modules/serialize-error": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz", + "integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -8240,7 +9451,6 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -8301,11 +9511,19 @@ "node": ">=8" } }, + "node_modules/short-unique-id": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/short-unique-id/-/short-unique-id-5.2.0.tgz", + "integrity": "sha512-cMGfwNyfDZ/nzJ2k2M+ClthBIh//GlZl1JEf47Uoa9XR11bz8Pa2T2wQO4bVrRdH48LrIDWJahQziKo3MjhsWg==", + "bin": { + "short-unique-id": "bin/short-unique-id", + "suid": "bin/short-unique-id" + } + }, "node_modules/side-channel": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", - "dev": true, "dependencies": { "call-bind": "^1.0.7", "es-errors": "^1.3.0", @@ -8331,6 +9549,51 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true, + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -8365,6 +9628,15 @@ "node": ">=0.10.0" } }, + "node_modules/space-separated-tokens": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", + "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/split-ca": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz", @@ -8379,6 +9651,11 @@ "node": ">= 10.x" } }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + }, "node_modules/ssh-remote-port-forward": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/ssh-remote-port-forward/-/ssh-remote-port-forward-1.0.4.tgz", @@ -8800,6 +10077,182 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swagger-client": { + "version": "3.28.2", + "resolved": "https://registry.npmjs.org/swagger-client/-/swagger-client-3.28.2.tgz", + "integrity": "sha512-g30KCdSVyZlMulWOJnheNo7Ea+L06OZebl0oRU6zHd5Zf5AZKHTqurKRdNOLsMWA3l3bWJiEh7s3JlzFJHRmoQ==", + "dependencies": { + "@babel/runtime-corejs3": "^7.22.15", + "@swagger-api/apidom-core": ">=1.0.0-alpha.5 <1.0.0-beta.0", + "@swagger-api/apidom-error": ">=1.0.0-alpha.5 <1.0.0-beta.0", + "@swagger-api/apidom-json-pointer": ">=1.0.0-alpha.5 <1.0.0-beta.0", + "@swagger-api/apidom-ns-openapi-3-1": ">=1.0.0-alpha.5 <1.0.0-beta.0", + "@swagger-api/apidom-reference": ">=1.0.0-alpha.5 <1.0.0-beta.0", + "cookie": "~0.6.0", + "deepmerge": "~4.3.0", + "fast-json-patch": "^3.0.0-1", + "js-yaml": "^4.1.0", + "node-abort-controller": "^3.1.1", + "node-fetch-commonjs": "^3.3.2", + "openapi-path-templating": "^1.5.1", + "openapi-server-url-templating": "^1.0.0", + "qs": "^6.10.2", + "ramda-adjunct": "^5.0.0", + "traverse": "=0.6.8" + } + }, + "node_modules/swagger-ui": { + "version": "5.17.14", + "resolved": "https://registry.npmjs.org/swagger-ui/-/swagger-ui-5.17.14.tgz", + "integrity": "sha512-z9pOymwowrgkoMKNsRSN6QjOyYrMAnc4iohEebnS/ELT5jjdB13Lu4f3Bl4+o2Mw5B6QOyo4vibr/A/Vb7UbMQ==", + "dependencies": { + "@babel/runtime-corejs3": "^7.24.5", + "@braintree/sanitize-url": "=7.0.2", + "base64-js": "^1.5.1", + "classnames": "^2.5.1", + "css.escape": "1.5.1", + "deep-extend": "0.6.0", + "dompurify": "=3.1.4", + "ieee754": "^1.2.1", + "immutable": "^3.x.x", + "js-file-download": "^0.4.12", + "js-yaml": "=4.1.0", + "lodash": "^4.17.21", + "prop-types": "^15.8.1", + "randexp": "^0.5.3", + "randombytes": "^2.1.0", + "react": ">=16.8.0 <19", + "react-copy-to-clipboard": "5.1.0", + "react-debounce-input": "=3.3.0", + "react-dom": ">=16.8.0 <19", + "react-immutable-proptypes": "2.2.0", + "react-immutable-pure-component": "^2.2.0", + "react-inspector": "^6.0.1", + "react-redux": "^9.1.2", + "react-syntax-highlighter": "^15.5.0", + "redux": "^5.0.1", + "redux-immutable": "^4.0.0", + "remarkable": "^2.0.1", + "reselect": "^5.1.0", + "serialize-error": "^8.1.0", + "sha.js": "^2.4.11", + "swagger-client": "^3.28.1", + "url-parse": "^1.5.10", + "xml": "=1.0.1", + "xml-but-prettier": "^1.0.1", + "zenscroll": "^4.0.2" + } + }, + "node_modules/swagger-ui-react": { + "version": "5.17.14", + "resolved": "https://registry.npmjs.org/swagger-ui-react/-/swagger-ui-react-5.17.14.tgz", + "integrity": "sha512-mCXerZrbcn4ftPYifUF0+iKIRTHoVCv0HcJc/sXl9nCe3oeWdsjmOWVqKabzzAkAa0NwsbKNJFv2UL/Ivnf6VQ==", + "dependencies": { + "@babel/runtime-corejs3": "^7.24.5", + "@braintree/sanitize-url": "=7.0.2", + "base64-js": "^1.5.1", + "classnames": "^2.5.1", + "css.escape": "1.5.1", + "deep-extend": "0.6.0", + "dompurify": "=3.1.4", + "ieee754": "^1.2.1", + "immutable": "^3.x.x", + "js-file-download": "^0.4.12", + "js-yaml": "=4.1.0", + "lodash": "^4.17.21", + "prop-types": "^15.8.1", + "randexp": "^0.5.3", + "randombytes": "^2.1.0", + "react-copy-to-clipboard": "5.1.0", + "react-debounce-input": "=3.3.0", + "react-immutable-proptypes": "2.2.0", + "react-immutable-pure-component": "^2.2.0", + "react-inspector": "^6.0.1", + "react-redux": "^9.1.2", + "react-syntax-highlighter": "^15.5.0", + "redux": "^5.0.1", + "redux-immutable": "^4.0.0", + "remarkable": "^2.0.1", + "reselect": "^5.1.0", + "serialize-error": "^8.1.0", + "sha.js": "^2.4.11", + "swagger-client": "^3.28.1", + "url-parse": "^1.5.10", + "xml": "=1.0.1", + "xml-but-prettier": "^1.0.1", + "zenscroll": "^4.0.2" + }, + "peerDependencies": { + "react": ">=16.8.0 <19", + "react-dom": ">=16.8.0 <19" + } + }, + "node_modules/swagger-ui-react/node_modules/@types/react": { + "version": "18.3.3", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", + "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", + "optional": true, + "peer": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/swagger-ui-react/node_modules/react-redux": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.1.2.tgz", + "integrity": "sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==", + "dependencies": { + "@types/use-sync-external-store": "^0.0.3", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25", + "react": "^18.0", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/swagger-ui/node_modules/@types/react": { + "version": "18.3.3", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", + "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", + "optional": true, + "peer": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/swagger-ui/node_modules/react-redux": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.1.2.tgz", + "integrity": "sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==", + "dependencies": { + "@types/use-sync-external-store": "^0.0.3", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25", + "react": "^18.0", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -8947,7 +10400,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dev": true, + "devOptional": true, "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", @@ -9104,6 +10557,11 @@ "node": ">=8.0" } }, + "node_modules/toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==" + }, "node_modules/tough-cookie": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", @@ -9135,12 +10593,59 @@ "node": ">=12" } }, + "node_modules/traverse": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.8.tgz", + "integrity": "sha512-aXJDbk6SnumuaZSANd21XAo15ucCDE38H4fkqiGsc3MhCK+wOlZvLP9cB/TvpHT0mOyWgC4Z8EwRlzqYSUzdsA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tree-sitter": { + "version": "0.20.4", + "resolved": "https://registry.npmjs.org/tree-sitter/-/tree-sitter-0.20.4.tgz", + "integrity": "sha512-rjfR5dc4knG3jnJNN/giJ9WOoN1zL/kZyrS0ILh+eqq8RNcIbiXA63JsMEgluug0aNvfQvK4BfCErN1vIzvKog==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "nan": "^2.17.0", + "prebuild-install": "^7.1.1" + } + }, + "node_modules/tree-sitter-json": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/tree-sitter-json/-/tree-sitter-json-0.20.2.tgz", + "integrity": "sha512-eUxrowp4F1QEGk/i7Sa+Xl8Crlfp7J0AXxX1QdJEQKQYMWhgMbCIgyQvpO3Q0P9oyTrNQxRLlRipDS44a8EtRw==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "nan": "^2.18.0" + } + }, + "node_modules/tree-sitter-yaml": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/tree-sitter-yaml/-/tree-sitter-yaml-0.5.0.tgz", + "integrity": "sha512-POJ4ZNXXSWIG/W4Rjuyg36MkUD4d769YRUGKRqN+sVaj/VCo6Dh6Pkssn1Rtewd5kybx+jT1BWMyWN0CijXnMA==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "nan": "^2.14.0" + } + }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", "dev": true }, + "node_modules/ts-mixer": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", + "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==" + }, "node_modules/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", @@ -9194,6 +10699,11 @@ "optional": true, "peer": true }, + "node_modules/ts-toolbelt": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-9.6.0.tgz", + "integrity": "sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==" + }, "node_modules/tsconfck": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.0.tgz", @@ -9252,6 +10762,18 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "optional": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", @@ -9283,7 +10805,6 @@ "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, "engines": { "node": ">=10" }, @@ -9400,6 +10921,14 @@ } ] }, + "node_modules/types-ramda": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.30.1.tgz", + "integrity": "sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==", + "dependencies": { + "ts-toolbelt": "^9.6.0" + } + }, "node_modules/typescript": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", @@ -9486,6 +11015,11 @@ "node": ">= 4.0.0" } }, + "node_modules/unraw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unraw/-/unraw-3.0.0.tgz", + "integrity": "sha512-08/DA66UF65OlpUDIQtbJyrqTR0jTAlJ+jsnkQ4jxR7+K5g5YG1APZKQSMCE1vqqmD+2pv6+IdEjmopFatacvg==" + }, "node_modules/update-browserslist-db": { "version": "1.0.13", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", @@ -9529,14 +11063,19 @@ "version": "1.5.10", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "optional": true, - "peer": true, "dependencies": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", + "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -9894,6 +11433,20 @@ "node": ">=10.13.0" } }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/web-tree-sitter": { + "version": "0.20.3", + "resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.20.3.tgz", + "integrity": "sha512-zKGJW9r23y3BcJusbgvnOH2OYAW40MXAOi9bi3Gcc7T4Gms9WWgXF8m6adsJWpGJEhgOzCrfiz1IzKowJWrtYw==", + "optional": true + }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -10062,7 +11615,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "devOptional": true }, "node_modules/ws": { "version": "8.17.1", @@ -10087,6 +11640,19 @@ } } }, + "node_modules/xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==" + }, + "node_modules/xml-but-prettier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml-but-prettier/-/xml-but-prettier-1.0.1.tgz", + "integrity": "sha512-C2CJaadHrZTqESlH03WOyw0oZTtoy2uEg6dSDF6YRg+9GnYNub53RRemLpnvtbHDFelxMx4LajiFsYeR6XJHgQ==", + "dependencies": { + "repeat-string": "^1.5.2" + } + }, "node_modules/xml-name-validator": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", @@ -10123,7 +11689,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "devOptional": true }, "node_modules/yaml": { "version": "1.10.2", @@ -10238,6 +11804,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zenscroll": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zenscroll/-/zenscroll-4.0.2.tgz", + "integrity": "sha512-jEA1znR7b4C/NnaycInCU6h/d15ZzCd1jmsruqOKnZP6WXQSMH3W2GL+OXbkruslU4h+Tzuos0HdswzRUk/Vgg==" + }, "node_modules/zip-stream": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", diff --git a/vclogin/package.json b/vclogin/package.json index 7ae2d27..42a0133 100644 --- a/vclogin/package.json +++ b/vclogin/package.json @@ -30,6 +30,8 @@ "pino-http": "^10.1.0", "react": "18.2.0", "react-dom": "18.2.0", + "swagger-ui": "^5.17.14", + "swagger-ui-react": "^5.17.14", "ua-parser-js": "^1.0.38", "uuid": "^9.0.0" }, @@ -42,6 +44,7 @@ "@types/node": "^18.15.13", "@types/react": "18.0.37", "@types/react-dom": "18.0.11", + "@types/swagger-ui-react": "^4.18.3", "@types/ua-parser-js": "^0.7.39", "@types/uuid": "^9.0.1", "@vitest/coverage-v8": "^1.6.0", diff --git a/vclogin/pages/api-docs.tsx b/vclogin/pages/api-docs.tsx new file mode 100644 index 0000000..1db446d --- /dev/null +++ b/vclogin/pages/api-docs.tsx @@ -0,0 +1,11 @@ +"use client"; + +import SwaggerUI from "swagger-ui-react"; +import "swagger-ui-react/swagger-ui.css"; +import swaggerDocument from "../swagger.json"; + +function ReactSwagger() { + return ; +} + +export default ReactSwagger; diff --git a/vclogin/swagger.json b/vclogin/swagger.json new file mode 100644 index 0000000..da14466 --- /dev/null +++ b/vclogin/swagger.json @@ -0,0 +1,201 @@ +{ + "swagger": "2.0", + "info": { + "title": "SSI-to-OIDC Bridge Dynamic API", + "description": "API documentation for dynamic API", + "version": "1.0.0" + }, + "host": "localhost:5002", + "schemes": ["http"], + "securityDefinitions": { + "ApiKeyAuth": { + "type": "apiKey", + "name": "Authorization", + "in": "header", + "description": "API key required to authenticate requests. Format: 'API_KEY '" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "paths": { + "/api/dynamic/createTempAuthorization": { + "post": { + "summary": "Create temporary authorization and store authorization policy and input descriptor", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "Authorization policy", + "schema": { + "type": "object", + "properties": { + "policy": { + "type": "object", + "description": "Policy to be stored", + "example": [ + { + "credentialId": "1", + "type": "VerifiableId", + "patterns": [ + { + "issuer": "did:web:app.altme.io:issuer", + "claims": [ + { + "claimPath": "$.credentialSubject.firstName" + } + ] + } + ] + } + ] + }, + "inputDescriptor": { + "type": "object", + "description": "Input descriptor to be stored", + "nullable": true, + "example": [ + { + "id": "input_descriptor_1", + "name": "Input descriptor for authorization", + "purpose": "Please provide a valid Verifiable Id VC", + "constraints": { + "fields": [ + { + "path": ["$.credentialSubject.type"], + "filter": { + "type": "string", + "pattern": "VerifiableId" + } + } + ] + } + } + ] + } + }, + "required": ["policy"] + } + } + ], + "responses": { + "200": { + "description": "Successful response", + "schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "description": "UUID of the created temporary authorization" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/api/dynamic/getQRCodeString": { + "get": { + "summary": "Get QR code string by UUID", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "Authorization policy", + "schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "description": "UUID of the temporary authorization", + "example": "9d2b91fe-ab18-4a61-9b33-3d06e8d6c96f" + }, + "userId": { + "type": "string", + "description": "userId of the user", + "nullable": true, + "example": "did:key:z6Mkj5B9HcSKWGuuawpBcvy5wQwZJ9g2k5HzfmXPAjwbQ9TT" + } + }, + "required": ["policy"] + } + } + ], + "responses": { + "200": { + "description": "Successful response", + "schema": { + "type": "object", + "properties": { + "qrCodeString": { + "type": "string", + "description": "QR code string", + "example": "openid-vc://?client_id=did:key:z6Mkj5B9HcSKWGuuawpBcvy5wQwZJ9g2k5HzfmXPAjwbQ9TT&request_uri=https%3A%2F%2Fexample.com%2Fapi%2Fdynamic%2FpresentCredentialById%3Flogin_id%3D9b25be26-8939-4a8c-a6da-9a145fa8242d" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/api/dynamic/getAuthResponse": { + "get": { + "summary": "Get authtorization response and user claims by UUID", + "parameters": [ + { + "name": "uuid", + "in": "query", + "description": "UUID to fetch auth_res and claims", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Successful response", + "schema": { + "type": "object", + "properties": { + "auth_res": { + "type": "string", + "description": "Authentication result", + "enum": [ + "success", + "error_presentation_not_trused", + "error_invalid_presentation", + "error_not_found" + ], + "example": "success" + }, + "claims": { + "type": "object", + "description": "User claims" + } + } + } + } + } + } + } + } +} From f2115b6145a024da72aecfa0bd08348d11a0c6d9 Mon Sep 17 00:00:00 2001 From: Ilayda Cansin Koc Date: Sun, 28 Jul 2024 22:21:14 +0200 Subject: [PATCH 74/85] refactor readme.md Signed-off-by: Ilayda Cansin Koc --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index a4593c2..9976bc4 100644 --- a/README.md +++ b/README.md @@ -197,7 +197,7 @@ user-friendly interface to explore and test the API. > [!NOTE] -> To access the Swagger documentation, you need to run the bridge and +> To access the Swagger documentation, you need to run the bridge in development mode and > navigate to `http://localhost:5002/api-docs`. To authenticate requests to the dynamic API in Swagger, you need to provide a @@ -238,10 +238,12 @@ proper domain has to be set up. `/vclogin/.env` with key `PEX_DESCRIPTOR_OVERRIDE` if direct control over what wallets are asked for is desired (example for quick testing: `./__tests__/testdata/pex/descriptorEmailFromAltme.json`) -6. at this point, it needs to be ensured that the container for the vclogin +6. to be able to test dynamic endpoint APIs, you need to provide an API key in + the `.env` file in the `vclogin` folder with the key `API_KEY`. +7. at this point, it needs to be ensured that the container for the vclogin service is freshly built with the new env file: `docker compose down && docker compose build` -7. `$ docker compose up` +8. `$ docker compose up` To validate the running bridge with a simple OIDC client: @@ -309,6 +311,7 @@ PEX_DESCRIPTOR_OVERRIDE=./__tests__/testdata/pex/descriptorAnything.json HYDRA_ADMIN_URL=http://localhost:5001 REDIS_HOST=localhost REDIS_PORT=6379 +API_KEY= ``` _Note: The PEX_DESCRIPTOR_OVERRIDE is optional and provides a way to override @@ -489,19 +492,19 @@ authorization. An example of such a policy file is: > [!IMPORTANT] > Each `credentialId` should be unique across all policy objects, -> and should have integer string values starting from 1. This helps us determine +> and should have integer string values starting from 1, incrementing by 1 for each subsequent policy object. This helps us determine > the correct policy object to apply to the VCs. > [!IMPORTANT] > Altough the `type` field is an optional parameter, it needs to be -> present in a policy file that has multiple policy objects. +> present in a policy file that has multiple policy objects. This is crucial for the accurate application of policies. > [!NOTE] -> First we reorder of policy objects in a policy file based on the `credentialId` and -> then we reorder the credentials in the VP based on the `type` field in the reordered policy file. -> This way we can ensure that the correct policy object is applied to the correct credential. +> First we reorder the policy objects in a policy file based on the `credentialId` and +> then we reorder the credentials in the VP based on the `type` field from the reordered policy file. +> This ensures that each credential is matched with the correct policy object. The `type` field helps to determine which policy object should be applied to which type of credential. When multiple policy objects are used, this field @@ -579,9 +582,6 @@ below, the first VC's `credentialSubject.id` is compared with the second VC's > structure like `$.` when having multiple policy > objects. -In the code snippet above, `a` operand of the constraint in the second policy -object refers to the `credentialSubject.id` of the second VC. - ## Token Introspection Look into the access token like this: From 946be1b27b699f44094f378e4c5a62d1185020f4 Mon Sep 17 00:00:00 2001 From: Ilayda Cansin Koc Date: Mon, 29 Jul 2024 12:52:26 +0200 Subject: [PATCH 75/85] use VC type instead of credential subject type Signed-off-by: Ilayda Cansin Koc --- vclogin/lib/extractClaims.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vclogin/lib/extractClaims.ts b/vclogin/lib/extractClaims.ts index 5741bc5..5293839 100644 --- a/vclogin/lib/extractClaims.ts +++ b/vclogin/lib/extractClaims.ts @@ -82,7 +82,7 @@ const orderCredsByType = (creds: any[], policy: LoginPolicy): any[] => { for (let policyObj of policy) { for (let cred of creds) { if ( - cred.credentialSubject.type === policyObj.type && + cred.type.includes(policyObj.type) && !orderedCreds.includes(cred) ) { orderedCreds.push(cred); From fcfa844b4e0cc23dd996fd8474bb102862dd3c03 Mon Sep 17 00:00:00 2001 From: Ilayda Cansin Koc Date: Sun, 11 Aug 2024 21:05:00 +0200 Subject: [PATCH 76/85] chore: Await extraction of user claims in presentCredential API Signed-off-by: Ilayda Cansin Koc --- vclogin/pages/api/presentCredential.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vclogin/pages/api/presentCredential.ts b/vclogin/pages/api/presentCredential.ts index 1490e4e..db0997e 100644 --- a/vclogin/pages/api/presentCredential.ts +++ b/vclogin/pages/api/presentCredential.ts @@ -85,7 +85,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { } // Get the user claims - const userClaims = extractClaims(presentation); + const userClaims = await extractClaims(presentation); const subject = presentation["holder"]; const login_id = presentation["proof"]["challenge"]; const challenge = (await redisGet("" + login_id))!; From 3d323a5c47509e957a29f6620b13fb01b6c6887d Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Fri, 23 Aug 2024 13:46:28 +0200 Subject: [PATCH 77/85] fix minimal test script Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> --- test_client.sh | 51 +++++++++++++++++++++++++------------------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/test_client.sh b/test_client.sh index 0ae5774..c05aaa4 100755 --- a/test_client.sh +++ b/test_client.sh @@ -1,30 +1,31 @@ #!/bin/bash client=$(docker run --rm -it \ - --network ory-hydra-net \ - oryd/hydra:v2.2.0 \ - create client --skip-tls-verify \ - --name testclient \ - --secret some-secret \ - --grant-type authorization_code \ - --response-type token,code,id_token \ - --scope openid \ - --redirect-uri "http://localhost:3000/api/auth/callback/oidc" \ - -e http://hydra:4445 \ - --format json ) + --network ory-hydra-net \ + oryd/hydra:v2.2.0 \ + create client --skip-tls-verify \ + --name testclient \ + --secret some-secret \ + --grant-type authorization_code \ + --response-type token,code,id_token \ + --scope openid \ + --redirect-uri "http://localhost:9010/callback" \ + -e http://hydra:4445 \ + --format json) -echo $client +echo "$client" -docker run --rm -it \ - --network ory-hydra-net \ - -p 9010:9010 \ - oryd/hydra:v2.2.0 \ - perform authorization-code --skip-tls-verify \ - --port 9010 \ - --client-id $client_id \ - --client-secret some-secret \ - --redirect "http://localhost:3000/api/auth/callback/oidc" \ - --scope openid \ - --auth-url http://localhost:5004/oauth2/auth \ - --token-url http://localhost:5004/oauth2/token \ - -e http://hydra:4444 +client_id=$(echo "$client" | jq -r ".client_id") +docker run --rm -it \ + --network ory-hydra-net \ + -p 9010:9010 \ + oryd/hydra:v2.2.0 \ + perform authorization-code --skip-tls-verify \ + --port 9010 \ + --client-id "$client_id" \ + --client-secret some-secret \ + --redirect "http://localhost:9010/callback" \ + --scope openid \ + --auth-url http://localhost:5004/oauth2/auth \ + --token-url http://hydra:4444/oauth2/token \ + -e http://hydra:4444 From 3b3bd20983416692dfcffa90badcc2e568bb3d0e Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Fri, 23 Aug 2024 14:00:21 +0200 Subject: [PATCH 78/85] package maintenance Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> --- package-lock.json | 6 - vclogin/package-lock.json | 1906 +++++++++++++++++++++++-------------- vclogin/package.json | 6 +- 3 files changed, 1195 insertions(+), 723 deletions(-) delete mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 35d2e52..0000000 --- a/package-lock.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "ssi-to-oidc-bridge", - "lockfileVersion": 3, - "requires": true, - "packages": {} -} diff --git a/vclogin/package-lock.json b/vclogin/package-lock.json index 6d0c749..1ded84c 100644 --- a/vclogin/package-lock.json +++ b/vclogin/package-lock.json @@ -26,8 +26,6 @@ "pino-http": "^10.1.0", "react": "18.2.0", "react-dom": "18.2.0", - "swagger-ui": "^5.17.14", - "swagger-ui-react": "^5.17.14", "ua-parser-js": "^1.0.38", "uuid": "^9.0.0" }, @@ -51,7 +49,9 @@ "eslint-plugin-react-hooks": "^4.6.0", "postcss": "^8.4.39", "prettier": "^2.8.8", - "tailwindcss": "3.3.1", + "swagger-ui": "^5.17.14", + "swagger-ui-react": "^5.17.14", + "tailwindcss": "^3.2.0", "typescript": "5.0.4", "vite": "^5.3.1", "vite-tsconfig-paths": "^4.3.2", @@ -59,6 +59,19 @@ "whatwg-fetch": "^3.6.19" } }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@ampproject/remapping": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", @@ -267,6 +280,7 @@ "version": "7.21.0", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", + "dev": true, "dependencies": { "regenerator-runtime": "^0.13.11" }, @@ -278,6 +292,7 @@ "version": "7.24.8", "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.24.8.tgz", "integrity": "sha512-DXG/BhegtMHhnN7YPIvxWd303/9aXvYFD1TjNL3CD6tUrhI2LVsg3Lck0aql5TRH29n4sj3emcROypkZVUfSuA==", + "dev": true, "dependencies": { "core-js-pure": "^3.30.2", "regenerator-runtime": "^0.14.0" @@ -289,7 +304,8 @@ "node_modules/@babel/runtime-corejs3/node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true }, "node_modules/@babel/types": { "version": "7.24.7", @@ -320,33 +336,8 @@ "node_modules/@braintree/sanitize-url": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.0.2.tgz", - "integrity": "sha512-NVf/1YycDMs6+FxS0Tb/W8MjJRDQdXF+tBfDtZ5UZeiRUkTmwKc4vmYCKZTyymfJk1gnMsauvZSX/HiV9jOABw==" - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } + "integrity": "sha512-NVf/1YycDMs6+FxS0Tb/W8MjJRDQdXF+tBfDtZ5UZeiRUkTmwKc4vmYCKZTyymfJk1gnMsauvZSX/HiV9jOABw==", + "dev": true }, "node_modules/@emotion/is-prop-valid": { "version": "0.8.8", @@ -872,6 +863,109 @@ "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==" }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", @@ -1030,6 +1124,7 @@ "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-13.3.0.tgz", "integrity": "sha512-wuGN5qSEjSgcq9fVkH0Y/qIPFjnZtW3ZPwfjJOn7l/rrf6y8J24h/lo61kwqunTyzZJm/ETGfGVU9PUs8cnzEA==", "dev": true, + "license": "MIT", "dependencies": { "glob": "7.1.7" } @@ -1223,24 +1318,15 @@ "axios": "^1.6.1" } }, - "node_modules/@pkgr/utils": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.3.1.tgz", - "integrity": "sha512-wfzX8kc1PMyUILA+1Z/EqoE4UCXGy0iRGMhPwdfae1+f0OXlLqCk+By+aMzgJBzR9AzS4CDizioG6Ss1gvAFJw==", + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "is-glob": "^4.0.3", - "open": "^8.4.0", - "picocolors": "^1.0.0", - "tiny-glob": "^0.2.9", - "tslib": "^2.4.0" - }, + "license": "MIT", + "optional": true, "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts" + "node": ">=14" } }, "node_modules/@playwright/test": { @@ -1467,10 +1553,11 @@ ] }, "node_modules/@rushstack/eslint-patch": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz", - "integrity": "sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==", - "dev": true + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.4.tgz", + "integrity": "sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA==", + "dev": true, + "license": "MIT" }, "node_modules/@sd-jwt/decode": { "version": "0.6.1", @@ -1660,6 +1747,7 @@ "version": "1.0.0-alpha.6", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ast/-/apidom-ast-1.0.0-alpha.6.tgz", "integrity": "sha512-uzDNIeTLFeITzK7yX9PSsu3dl92rHP/gKMNAlJhmDRr7r+OLr5dCpAzyZ0WvONRxjxR8Otj5LX4AD12+EX32fg==", + "dev": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", "@swagger-api/apidom-error": "^1.0.0-alpha.6", @@ -1673,6 +1761,7 @@ "version": "1.0.0-alpha.6", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-core/-/apidom-core-1.0.0-alpha.6.tgz", "integrity": "sha512-5u7dK3+8cF2h5bHEI/qrA6JrfXX+HIHSmUgPGbeMAqSCEfpsjnsngXK6gHtd4ktLlPz3TplNZAQl88wIp+39nw==", + "dev": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", "@swagger-api/apidom-ast": "^1.0.0-alpha.6", @@ -1689,6 +1778,7 @@ "version": "1.0.0-alpha.6", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-error/-/apidom-error-1.0.0-alpha.6.tgz", "integrity": "sha512-eOcqaXwLitjp5CIGYR0W2oM6p4UiTL7EjNdkCcfrELKHdgNS6U7kZdl3KCBlOuMb14CijwtZNEJbIGhhGZUYHg==", + "dev": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7" } @@ -1697,6 +1787,7 @@ "version": "1.0.0-alpha.6", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-json-pointer/-/apidom-json-pointer-1.0.0-alpha.6.tgz", "integrity": "sha512-8ULBcQRQ1UPgqJ+ZuuKjmeKeuxqbuIUHwWHRRA848jK5+IHmNw/Cp68MhNiwYXLmTLkTIGaDubcOplMeHCxSyA==", + "dev": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", "@swagger-api/apidom-core": "^1.0.0-alpha.6", @@ -1710,6 +1801,7 @@ "version": "1.0.0-alpha.6", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-api-design-systems/-/apidom-ns-api-design-systems-1.0.0-alpha.6.tgz", "integrity": "sha512-JRiImw3XKrfm22pzlx7uM6XYJtWM71QkCLy86gOTBFybWgTOCECnN4c8jFBnYl6KYuIb2VV9kXZs38xjK4NfBQ==", + "dev": true, "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", @@ -1726,6 +1818,7 @@ "version": "1.0.0-alpha.6", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-asyncapi-2/-/apidom-ns-asyncapi-2-1.0.0-alpha.6.tgz", "integrity": "sha512-I8Yq+AmJPUJihGneBv1/m/ly+2dp4FJiCxW/auRQSicvYIV7hoBO5qGZqcEEoVt7OpuhFbFqI2pwnambz90Uvg==", + "dev": true, "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", @@ -1741,6 +1834,7 @@ "version": "1.0.0-alpha.6", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-4/-/apidom-ns-json-schema-draft-4-1.0.0-alpha.6.tgz", "integrity": "sha512-E8JjqdDgopnLd4HXEXGSrc6rkbDyB8Qk6sYgmyT6lB8caFUMRdJ5Rp57fPePETnVpegb8cAuKjBdjTImX1gQ3Q==", + "dev": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", "@swagger-api/apidom-ast": "^1.0.0-alpha.6", @@ -1755,6 +1849,7 @@ "version": "1.0.0-alpha.6", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-6/-/apidom-ns-json-schema-draft-6-1.0.0-alpha.6.tgz", "integrity": "sha512-uzYmV65nn7i6nlp7Kp7ldGfAoXWPPquIocoHLWDBTx5sPdS+ALu2T2yvytav0z6StKeV+gU2HZjMLVRWdLzLZA==", + "dev": true, "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", @@ -1771,6 +1866,7 @@ "version": "1.0.0-alpha.6", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-7/-/apidom-ns-json-schema-draft-7-1.0.0-alpha.6.tgz", "integrity": "sha512-dWEVUVMByOs5JIMsgcceETOYH3nTiAHoIIjXbYeHP6m6HaNP8IE5ex0ZgfmQc29uH0E6H+6aYAv1flfvy56rVQ==", + "dev": true, "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", @@ -1787,6 +1883,7 @@ "version": "1.0.0-alpha.6", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-2/-/apidom-ns-openapi-2-1.0.0-alpha.6.tgz", "integrity": "sha512-sPwvOY+FGd5yEAijYLupmIYwf4HIpW6yegzrz6uUvGmONZpiCNIidCu+2m6GyYCoZ/lQZdPMw29DuU2O4iiDKw==", + "dev": true, "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", @@ -1803,6 +1900,7 @@ "version": "1.0.0-alpha.6", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-0/-/apidom-ns-openapi-3-0-1.0.0-alpha.6.tgz", "integrity": "sha512-kE4s17j69DDvXrf7xeRTunmSQJLiX52fCX1YnfC81e1IPm3q/mdpkZiysM87FuJQQj522fX2G+QUIJlDkD5U9w==", + "dev": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", "@swagger-api/apidom-core": "^1.0.0-alpha.6", @@ -1818,6 +1916,7 @@ "version": "1.0.0-alpha.6", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-1/-/apidom-ns-openapi-3-1-1.0.0-alpha.6.tgz", "integrity": "sha512-2QA2z9beyaVyZDOXbLg4Nu8o8xKWo9L0WHWOP+hg/haGRyyPHXgyg2XdwRuFBozBI9wBaIfEg1lvNC+J0taDjg==", + "dev": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", "@swagger-api/apidom-ast": "^1.0.0-alpha.6", @@ -1834,6 +1933,7 @@ "version": "1.0.0-alpha.6", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-workflows-1/-/apidom-ns-workflows-1-1.0.0-alpha.6.tgz", "integrity": "sha512-9kXU7hUdz25TTGF8b1pmKGugkET4gkW7ING+qSUjU5nWdrkdUIVuq1o8qjaZwRDRvkNynnlRbWHqXeWgRWyi/w==", + "dev": true, "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", @@ -1849,6 +1949,7 @@ "version": "1.0.0-alpha.6", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-api-design-systems-json/-/apidom-parser-adapter-api-design-systems-json-1.0.0-alpha.6.tgz", "integrity": "sha512-7THBmhvwTmsb1eFXvj/tbIK91g5tzkvhxGSUVbpGt1zApEFmKvjZbDhGnMx15CImIUURW1QZ6TQ/cZ7jRWk8kQ==", + "dev": true, "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", @@ -1864,6 +1965,7 @@ "version": "1.0.0-alpha.6", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-api-design-systems-yaml/-/apidom-parser-adapter-api-design-systems-yaml-1.0.0-alpha.6.tgz", "integrity": "sha512-xD0aRgRGPaM1J+H3nRg8qP6bQ4fNtsUopoc6JEKzi7NJxd+r/mZV4pVa+Gr6CS+xv4d6TJ53UCJmGsjgmR1bQw==", + "dev": true, "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", @@ -1879,6 +1981,7 @@ "version": "1.0.0-alpha.6", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-json-2/-/apidom-parser-adapter-asyncapi-json-2-1.0.0-alpha.6.tgz", "integrity": "sha512-BVouq+7XiS2/HmNHd/CHHieyRT5mTN+kqYACnKV/TAzC5+fK3t2mcdng4I81m3Mzb9OJ/VpHiEVlSZiWZoPU/Q==", + "dev": true, "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", @@ -1894,6 +1997,7 @@ "version": "1.0.0-alpha.6", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-yaml-2/-/apidom-parser-adapter-asyncapi-yaml-2-1.0.0-alpha.6.tgz", "integrity": "sha512-CsUu5t6ijflz0DDjdoxE/OUbSjBAeh5v2zfMRVOfGdnNDhDhrE/3P0VTpdKdVmbWQ1ueIbq2QaC8thQ+Jcxwyg==", + "dev": true, "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", @@ -1909,6 +2013,7 @@ "version": "1.0.0-alpha.6", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-json/-/apidom-parser-adapter-json-1.0.0-alpha.6.tgz", "integrity": "sha512-ruEkgvJSmBUUsGZdYiHeczekTWCJSWHrNvhAczY6c1ZFhpCukZg9tCqdVhnni/LPp4r4h7BdNldjY7dtrUkCiQ==", + "dev": true, "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", @@ -1927,6 +2032,7 @@ "version": "1.0.0-alpha.6", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-2/-/apidom-parser-adapter-openapi-json-2-1.0.0-alpha.6.tgz", "integrity": "sha512-RLvjHvjURnqfWxEdLFOW6agBS8CzVyV9++Vg4TSB9gPCNsTlz5w9iy82NYvApExHJIlN55Ga92OZ6CuWXJ8iKw==", + "dev": true, "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", @@ -1942,6 +2048,7 @@ "version": "1.0.0-alpha.6", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-0/-/apidom-parser-adapter-openapi-json-3-0-1.0.0-alpha.6.tgz", "integrity": "sha512-cf9+M9vySTrUZW+m2SR04IMnl+5QX6P7S2xgFF705ySOMkPiA9GTgAZJFqwzncAEPovkp7/A24adxyhFz52iZg==", + "dev": true, "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", @@ -1957,6 +2064,7 @@ "version": "1.0.0-alpha.6", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-1/-/apidom-parser-adapter-openapi-json-3-1-1.0.0-alpha.6.tgz", "integrity": "sha512-Z7TCUWB/VotmHU5kjUcAlu3qMHCVr1pOpnsuI01I6vCHGJOqUZPelnNqVyw5tjiVbgwDCKzXiPSQo9gGG1HoGA==", + "dev": true, "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", @@ -1972,6 +2080,7 @@ "version": "1.0.0-alpha.6", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-2/-/apidom-parser-adapter-openapi-yaml-2-1.0.0-alpha.6.tgz", "integrity": "sha512-XI0qlTjL2Q1TMvzxjjEki2iuJqt43C0mwGHremjcpbNHpJejnkEGFDPJqs1rp3RobwRl1ftHVFJi7JVPiA8Zvw==", + "dev": true, "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", @@ -1987,6 +2096,7 @@ "version": "1.0.0-alpha.6", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-0/-/apidom-parser-adapter-openapi-yaml-3-0-1.0.0-alpha.6.tgz", "integrity": "sha512-yGd5dP52BrBMO4/nCJdcvotxCbmbXYOi/nQrj7rL4/7VFdKbC4ngT0ggprvKE8CVQC99qPz4qR1y728QdioPAg==", + "dev": true, "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", @@ -2002,6 +2112,7 @@ "version": "1.0.0-alpha.6", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-1/-/apidom-parser-adapter-openapi-yaml-3-1-1.0.0-alpha.6.tgz", "integrity": "sha512-4F/rWh7bi97y20SRskrqz9UdO+YwHOn+vcOvNs5/arI5niSmTeAN3dgH9emTx1LJi8d7woUAct+TEqshwoh/zQ==", + "dev": true, "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", @@ -2017,6 +2128,7 @@ "version": "1.0.0-alpha.6", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-workflows-json-1/-/apidom-parser-adapter-workflows-json-1-1.0.0-alpha.6.tgz", "integrity": "sha512-K2gZFUHtp+Vw9rcizanIJLxSsaYQWNh1QtsEVGtAkn3RBVa130i3umcgQBKuvaBzfhi+Zr21sR4LSrs4XiRHiQ==", + "dev": true, "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", @@ -2032,6 +2144,7 @@ "version": "1.0.0-alpha.6", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-workflows-yaml-1/-/apidom-parser-adapter-workflows-yaml-1-1.0.0-alpha.6.tgz", "integrity": "sha512-yaJ9Iir43odK/zTB0tVL43RBC4ktQvNRfuT21vedqNaxO9J2pjTPy9NkIXJuOrcizinAASDLLUYX/b0UONhVxg==", + "dev": true, "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", @@ -2047,6 +2160,7 @@ "version": "1.0.0-alpha.6", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-yaml-1-2/-/apidom-parser-adapter-yaml-1-2-1.0.0-alpha.6.tgz", "integrity": "sha512-qevJf2IRvskyvgeGnkJXCGKFnmrcnuMoFHoboI3nJFqdesN74g1hGm1VIVAOOkM4AcdG1w7BviCHEt4YEYGPcQ==", + "dev": true, "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", @@ -2065,6 +2179,7 @@ "version": "1.0.0-alpha.6", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-reference/-/apidom-reference-1.0.0-alpha.6.tgz", "integrity": "sha512-DbsxxgQCVd8ZTJag3EOtzJ2rtsaq4z5z/A4nEgzVQhStuHdRwrbQfxem1g7p6dOK2VrGEGf73UllGJvGV+uPpg==", + "dev": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", "@swagger-api/apidom-core": "^1.0.0-alpha.6", @@ -2103,6 +2218,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, "dependencies": { "balanced-match": "^1.0.0" } @@ -2111,6 +2227,7 @@ "version": "7.4.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.6.tgz", "integrity": "sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==", + "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -2438,30 +2555,6 @@ "node": ">= 10" } }, - "node_modules/@tsconfig/node10": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", - "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/@tsconfig/node16": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", @@ -2549,6 +2642,7 @@ "version": "2.3.10", "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", + "dev": true, "dependencies": { "@types/unist": "^2" } @@ -2557,7 +2651,8 @@ "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/jsonpath": { "version": "0.2.4", @@ -2592,7 +2687,7 @@ "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", - "devOptional": true + "dev": true }, "node_modules/@types/qs": { "version": "6.9.7", @@ -2604,6 +2699,7 @@ "version": "0.30.1", "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.30.1.tgz", "integrity": "sha512-aoyF/ADPL6N+/NXXfhPWF+Qj6w1Cql59m9wX0Gi15uyF+bpzXeLd63HPdiTDE2bmLXfNcVufsDPKmbfOrOzTBA==", + "dev": true, "dependencies": { "types-ramda": "^0.30.1" } @@ -2696,12 +2792,14 @@ "node_modules/@types/unist": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", - "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==", + "dev": true }, "node_modules/@types/use-sync-external-store": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", - "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==", + "dev": true }, "node_modules/@types/uuid": { "version": "9.0.1", @@ -2710,14 +2808,15 @@ "dev": true }, "node_modules/@typescript-eslint/parser": { - "version": "5.59.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.0.tgz", - "integrity": "sha512-qK9TZ70eJtjojSUMrrEwA9ZDQ4N0e/AuoOIgXuNBorXYcBDk397D2r5MIe1B3cok/oCtdNC5j+lUUpVB+Dpb+w==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "5.59.0", - "@typescript-eslint/types": "5.59.0", - "@typescript-eslint/typescript-estree": "5.59.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", "debug": "^4.3.4" }, "engines": { @@ -2737,13 +2836,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.59.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.0.tgz", - "integrity": "sha512-tsoldKaMh7izN6BvkK6zRMINj4Z2d6gGhO2UsI8zGZY3XhLq1DndP3Ycjhi1JwdwPRwtLMW4EFPgpuKhbCGOvQ==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "5.59.0", - "@typescript-eslint/visitor-keys": "5.59.0" + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2754,10 +2854,11 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "5.59.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.0.tgz", - "integrity": "sha512-yR2h1NotF23xFFYKHZs17QJnB51J/s+ud4PYU4MqdZbzeNxpgUr05+dNeCN/bb6raslHvGdd6BFCkVhpPk/ZeA==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", "dev": true, + "license": "MIT", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -2767,13 +2868,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.59.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.0.tgz", - "integrity": "sha512-sUNnktjmI8DyGzPdZ8dRwW741zopGxltGs/SAPgGL/AAgDpiLsCFLcMNSpbfXfmnNeHmK9h3wGmCkGRGAoUZAg==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "5.59.0", - "@typescript-eslint/visitor-keys": "5.59.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -2794,12 +2896,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.59.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.0.tgz", - "integrity": "sha512-qZ3iXxQhanchCeaExlKPV3gDQFxMUmU35xfd5eCXB6+kUw1TUAbIy2n7QIrwz9s98DQLzNWyHp61fY0da4ZcbA==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "5.59.0", + "@typescript-eslint/types": "5.62.0", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -3251,7 +3354,8 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/anymatch": { "version": "3.1.3", @@ -3269,7 +3373,8 @@ "node_modules/apg-lite": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/apg-lite/-/apg-lite-1.0.4.tgz", - "integrity": "sha512-B32zCN3IdHIc99Vy7V9BaYTUzLeRA8YXYY1aQD1/5I2aqIrO0coi4t6hJPqMisidlBxhyME8UexkHt31SlR6Og==" + "integrity": "sha512-B32zCN3IdHIc99Vy7V9BaYTUzLeRA8YXYY1aQD1/5I2aqIrO0coi4t6hJPqMisidlBxhyME8UexkHt31SlR6Og==", + "dev": true }, "node_modules/append-field": { "version": "1.0.0", @@ -3374,12 +3479,13 @@ } }, "node_modules/aria-query": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", - "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "dequal": "^2.0.3" + "deep-equal": "^2.0.5" } }, "node_modules/array-buffer-byte-length": { @@ -3399,15 +3505,17 @@ } }, "node_modules/array-includes": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", - "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "get-intrinsic": "^1.1.3", + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", "is-string": "^1.0.7" }, "engines": { @@ -3422,19 +3530,63 @@ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", + "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array.prototype.flat": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", - "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", "es-shim-unscopables": "^1.0.0" }, "engines": { @@ -3445,14 +3597,15 @@ } }, "node_modules/array.prototype.flatmap": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", - "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", "es-shim-unscopables": "^1.0.0" }, "engines": { @@ -3463,16 +3616,20 @@ } }, "node_modules/array.prototype.tosorted": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz", - "integrity": "sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0", - "get-intrinsic": "^1.1.3" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" } }, "node_modules/arraybuffer.prototype.slice": { @@ -3516,10 +3673,11 @@ } }, "node_modules/ast-types-flow": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", - "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==", - "dev": true + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "dev": true, + "license": "MIT" }, "node_modules/async": { "version": "3.2.5", @@ -3550,6 +3708,7 @@ "version": "3.16.2", "resolved": "https://registry.npmjs.org/autolinker/-/autolinker-3.16.2.tgz", "integrity": "sha512-JiYl7j2Z19F9NdTmirENSUUIIL/9MytEWtmzhfmsKPCp9E+G35Y0UNCMoM9tFigxT59qSc8Ml2dlZXOCVTYwuA==", + "dev": true, "dependencies": { "tslib": "^2.3.0" } @@ -3603,18 +3762,20 @@ } }, "node_modules/axe-core": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.7.0.tgz", - "integrity": "sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.0.tgz", + "integrity": "sha512-Mr2ZakwQ7XUAjp7pAwQWRhhK8mQQ6JAaNWSjmjxil0R8BPioMtQsTLOolGYkji1rcL++3dCqZA3zWqpT+9Ew6g==", "dev": true, + "license": "MPL-2.0", "engines": { "node": ">=4" } }, "node_modules/axios": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", - "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", + "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", + "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -3626,6 +3787,7 @@ "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz", "integrity": "sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==", "dev": true, + "license": "Apache-2.0", "dependencies": { "deep-equal": "^2.0.5" } @@ -3639,7 +3801,8 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true }, "node_modules/bare-events": { "version": "2.3.1", @@ -3744,7 +3907,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "devOptional": true, + "dev": true, "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -3755,7 +3918,7 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "devOptional": true, + "dev": true, "funding": [ { "type": "github", @@ -3942,6 +4105,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -4038,6 +4202,7 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", + "dev": true, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -4047,6 +4212,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", + "dev": true, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -4056,6 +4222,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", + "dev": true, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -4116,7 +4283,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "devOptional": true + "dev": true }, "node_modules/cipher-base": { "version": "1.0.4", @@ -4186,6 +4353,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==", + "dev": true, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -4202,6 +4370,7 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 6" } @@ -4283,6 +4452,7 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "dev": true, "engines": { "node": ">= 0.6" } @@ -4291,6 +4461,7 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", + "dev": true, "dependencies": { "toggle-selection": "^1.0.6" } @@ -4299,6 +4470,7 @@ "version": "3.37.1", "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.37.1.tgz", "integrity": "sha512-J/r5JTHSmzTxbiYYrzXg9w1VpqrYt+gexenBE9pugeyhwPZTAEJddyiReJWsLO6uNQ8xJZFbod6XC7KKwatCiA==", + "dev": true, "hasInstallScript": true, "funding": { "type": "opencollective", @@ -4387,14 +4559,6 @@ "sha.js": "^2.4.0" } }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -4412,13 +4576,15 @@ "node_modules/css.escape": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", - "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==" + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true, + "license": "MIT", "bin": { "cssesc": "bin/cssesc" }, @@ -4460,13 +4626,14 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==", - "devOptional": true + "dev": true }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", - "dev": true + "dev": true, + "license": "BSD-2-Clause" }, "node_modules/data-urls": { "version": "3.0.2", @@ -4571,6 +4738,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, "optional": true, "dependencies": { "mimic-response": "^3.1.0" @@ -4595,16 +4763,18 @@ } }, "node_modules/deep-equal": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.0.tgz", - "integrity": "sha512-RdpzE0Hv4lhowpIUKKMJfeH6C1pXdtT1/it80ubgWqwI3qpuxUBpC1S4hnHg+zjnuOoDkzUtUCEEkG+XG5l3Mw==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", + "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "es-get-iterator": "^1.1.2", - "get-intrinsic": "^1.1.3", + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.5", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.2", "is-arguments": "^1.1.1", - "is-array-buffer": "^3.0.1", + "is-array-buffer": "^3.0.2", "is-date-object": "^1.0.5", "is-regex": "^1.1.4", "is-shared-array-buffer": "^1.0.2", @@ -4612,11 +4782,14 @@ "object-is": "^1.1.5", "object-keys": "^1.1.1", "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", + "regexp.prototype.flags": "^1.5.1", "side-channel": "^1.0.4", "which-boxed-primitive": "^1.0.2", "which-collection": "^1.0.1", - "which-typed-array": "^1.1.9" + "which-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4626,6 +4799,7 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, "engines": { "node": ">=4.0.0" } @@ -4647,6 +4821,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -4659,15 +4834,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/define-lazy-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/define-properties": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", @@ -4701,19 +4867,11 @@ "node": ">=0.10" } }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/detect-libc": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "dev": true, "optional": true, "engines": { "node": ">=8" @@ -4725,17 +4883,6 @@ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", "dev": true }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", @@ -4755,6 +4902,7 @@ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, + "license": "MIT", "dependencies": { "path-type": "^4.0.0" }, @@ -4780,18 +4928,6 @@ "node": ">= 6.0.0" } }, - "node_modules/docker-compose/node_modules/yaml": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz", - "integrity": "sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==", - "dev": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/docker-modem": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-3.0.8.tgz", @@ -4863,16 +4999,25 @@ "node_modules/dompurify": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.4.tgz", - "integrity": "sha512-2gnshi6OshmuKil8rMZuQCGiUF3cUxHY3NGDzUAdUx/NPEe5DVnO8BDoAQouvgwnx0R/+a6jUn36Z0FSdq8vww==" + "integrity": "sha512-2gnshi6OshmuKil8rMZuQCGiUF3cUxHY3NGDzUAdUx/NPEe5DVnO8BDoAQouvgwnx0R/+a6jUn36Z0FSdq8vww==", + "dev": true }, "node_modules/drange": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/drange/-/drange-1.1.1.tgz", "integrity": "sha512-pYxfDYpued//QpnLIm4Avk7rsNtAtQkUES2cwAYSvD/wd2pKD71gN2Ebj3e7klzXwjocvE8c5vx/1fxwpqmSxA==", + "dev": true, "engines": { "node": ">=4" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, "node_modules/electron-to-chromium": { "version": "1.4.596", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.596.tgz", @@ -4880,9 +5025,10 @@ "dev": true }, "node_modules/elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "version": "6.5.7", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.7.tgz", + "integrity": "sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==", + "license": "MIT", "dependencies": { "bn.js": "^4.11.9", "brorand": "^1.1.0", @@ -4894,10 +5040,10 @@ } }, "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" }, "node_modules/encode-utf8": { "version": "1.0.3", @@ -4908,16 +5054,17 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "devOptional": true, + "dev": true, "dependencies": { "once": "^1.4.0" } }, "node_modules/enhanced-resolve": { - "version": "5.13.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.13.0.tgz", - "integrity": "sha512-eyV8f0y1+bzyfh8xAwW/WTSZpLbjhqc4ne9eGSH4Zo2ejdyiNG9pU6mf9DG8a7+Auk6MFTlNOT4Y2y/9k8GKVg==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", "dev": true, + "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -5012,6 +5159,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, "dependencies": { "get-intrinsic": "^1.2.4" }, @@ -5023,6 +5171,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, "engines": { "node": ">= 0.4" } @@ -5032,6 +5181,7 @@ "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.3", @@ -5047,6 +5197,32 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-iterator-helpers": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.19.tgz", + "integrity": "sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.3", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "iterator.prototype": "^1.1.2", + "safe-array-concat": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-object-atoms": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", @@ -5074,12 +5250,13 @@ } }, "node_modules/es-shim-unscopables": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", - "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", "dev": true, + "license": "MIT", "dependencies": { - "has": "^1.0.3" + "hasown": "^2.0.0" } }, "node_modules/es-to-primitive": { @@ -5243,6 +5420,7 @@ "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-13.3.0.tgz", "integrity": "sha512-6YEwmFBX0VjBd3ODGW9df0Is0FLaRFdMN8eAahQG9CN6LjQ28J8AFr19ngxqMSg7Qv6Uca/3VeeBosJh1bzu0w==", "dev": true, + "license": "MIT", "dependencies": { "@next/eslint-plugin-next": "13.3.0", "@rushstack/eslint-patch": "^1.1.3", @@ -5265,14 +5443,15 @@ } }, "node_modules/eslint-import-resolver-node": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", - "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", "dev": true, + "license": "MIT", "dependencies": { "debug": "^3.2.7", - "is-core-module": "^2.11.0", - "resolve": "^1.22.1" + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" } }, "node_modules/eslint-import-resolver-node/node_modules/debug": { @@ -5280,24 +5459,25 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, + "license": "MIT", "dependencies": { "ms": "^2.1.1" } }, "node_modules/eslint-import-resolver-typescript": { - "version": "3.5.5", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.5.5.tgz", - "integrity": "sha512-TdJqPHs2lW5J9Zpe17DZNQuDnox4xo2o+0tE7Pggain9Rbc19ik8kFtXdxZ250FVx2kF4vlt2RSf4qlUpG7bhw==", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.1.tgz", + "integrity": "sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==", "dev": true, + "license": "ISC", "dependencies": { "debug": "^4.3.4", "enhanced-resolve": "^5.12.0", "eslint-module-utils": "^2.7.4", + "fast-glob": "^3.3.1", "get-tsconfig": "^4.5.0", - "globby": "^13.1.3", "is-core-module": "^2.11.0", - "is-glob": "^4.0.3", - "synckit": "^0.8.5" + "is-glob": "^4.0.3" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -5310,42 +5490,12 @@ "eslint-plugin-import": "*" } }, - "node_modules/eslint-import-resolver-typescript/node_modules/globby": { - "version": "13.1.4", - "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.4.tgz", - "integrity": "sha512-iui/IiiW+QrJ1X1hKH5qwlMQyv34wJAYwH1vrf8b9kBA4sNiif3gKsMHa+BrdnOpEudWjpotfa7LrTzB1ERS/g==", - "dev": true, - "dependencies": { - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.11", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint-import-resolver-typescript/node_modules/slash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", - "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/eslint-module-utils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", - "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz", + "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==", "dev": true, + "license": "MIT", "dependencies": { "debug": "^3.2.7" }, @@ -5363,31 +5513,35 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, + "license": "MIT", "dependencies": { "ms": "^2.1.1" } }, "node_modules/eslint-plugin-import": { - "version": "2.27.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", - "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", "dev": true, + "license": "MIT", "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flat": "^1.3.1", - "array.prototype.flatmap": "^1.3.1", + "array-includes": "^3.1.7", + "array.prototype.findlastindex": "^1.2.3", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", "debug": "^3.2.7", "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.7", - "eslint-module-utils": "^2.7.4", - "has": "^1.0.3", - "is-core-module": "^2.11.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.8.0", + "hasown": "^2.0.0", + "is-core-module": "^2.13.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", - "object.values": "^1.1.6", - "resolve": "^1.22.1", - "semver": "^6.3.0", - "tsconfig-paths": "^3.14.1" + "object.fromentries": "^2.0.7", + "object.groupby": "^1.0.1", + "object.values": "^1.1.7", + "semver": "^6.3.1", + "tsconfig-paths": "^3.15.0" }, "engines": { "node": ">=4" @@ -5401,6 +5555,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, + "license": "MIT", "dependencies": { "ms": "^2.1.1" } @@ -5410,6 +5565,7 @@ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, + "license": "Apache-2.0", "dependencies": { "esutils": "^2.0.2" }, @@ -5422,32 +5578,34 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/eslint-plugin-jsx-a11y": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.7.1.tgz", - "integrity": "sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.20.7", - "aria-query": "^5.1.3", - "array-includes": "^3.1.6", - "array.prototype.flatmap": "^1.3.1", - "ast-types-flow": "^0.0.7", - "axe-core": "^4.6.2", - "axobject-query": "^3.1.1", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.9.0.tgz", + "integrity": "sha512-nOFOCaJG2pYqORjK19lqPqxMO/JpvdCZdPtNdxY3kvom3jTvkAbOvQvD8wuD0G8BYR0IGAGYDlzqWJOh/ybn2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "aria-query": "~5.1.3", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.9.1", + "axobject-query": "~3.1.1", "damerau-levenshtein": "^1.0.8", "emoji-regex": "^9.2.2", - "has": "^1.0.3", - "jsx-ast-utils": "^3.3.3", - "language-tags": "=1.0.5", + "es-iterator-helpers": "^1.0.19", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", "minimatch": "^3.1.2", - "object.entries": "^1.1.6", - "object.fromentries": "^2.0.6", - "semver": "^6.3.0" + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.0" }, "engines": { "node": ">=4.0" @@ -5456,42 +5614,44 @@ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" } }, - "node_modules/eslint-plugin-jsx-a11y/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/eslint-plugin-jsx-a11y/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true, - "bin": { - "semver": "bin/semver.js" - } + "license": "MIT" }, "node_modules/eslint-plugin-react": { - "version": "7.32.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.32.2.tgz", - "integrity": "sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg==", + "version": "7.35.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.35.0.tgz", + "integrity": "sha512-v501SSMOWv8gerHkk+IIQBkcGRGrO2nfybfj5pLxuJNFTPxxA3PSryhXTK+9pNbtkggheDdsC0E9Q8CuPk6JKA==", "dev": true, + "license": "MIT", "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flatmap": "^1.3.1", - "array.prototype.tosorted": "^1.1.1", + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.2", + "array.prototype.tosorted": "^1.1.4", "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.0.19", "estraverse": "^5.3.0", + "hasown": "^2.0.2", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", - "object.entries": "^1.1.6", - "object.fromentries": "^2.0.6", - "object.hasown": "^1.1.2", - "object.values": "^1.1.6", + "object.entries": "^1.1.8", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.0", "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.4", - "semver": "^6.3.0", - "string.prototype.matchall": "^4.0.8" + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.11", + "string.prototype.repeat": "^1.0.0" }, "engines": { "node": ">=4" }, "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, "node_modules/eslint-plugin-react-hooks": { @@ -5511,6 +5671,7 @@ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, + "license": "Apache-2.0", "dependencies": { "esutils": "^2.0.2" }, @@ -5519,12 +5680,13 @@ } }, "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.4", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", - "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", "dev": true, + "license": "MIT", "dependencies": { - "is-core-module": "^2.9.0", + "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -5540,6 +5702,7 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" } @@ -5671,6 +5834,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "dev": true, "optional": true, "engines": { "node": ">=6" @@ -5689,10 +5853,11 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -5719,7 +5884,8 @@ "node_modules/fast-json-patch": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz", - "integrity": "sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==" + "integrity": "sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==", + "dev": true }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", @@ -5752,6 +5918,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz", "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==", + "dev": true, "dependencies": { "format": "^0.2.0" }, @@ -5847,6 +6014,36 @@ "is-callable": "^1.1.3" } }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -5864,6 +6061,7 @@ "version": "0.2.2", "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", + "dev": true, "engines": { "node": ">=0.4.x" } @@ -5913,7 +6111,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "devOptional": true + "dev": true }, "node_modules/fs.realpath": { "version": "1.0.0", @@ -5939,6 +6137,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -5991,6 +6190,7 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2", @@ -6035,10 +6235,14 @@ } }, "node_modules/get-tsconfig": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.5.0.tgz", - "integrity": "sha512-MjhiaIWCJ1sAU4pIQ5i5OfOuHHxVo1oYeNsWTON7jxYkod8pHocXeh+SSbmu5OZZZK73B6cbJ2XADzXehLyovQ==", + "version": "4.7.6", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.6.tgz", + "integrity": "sha512-ZAqrLlu18NbDdRaHq+AKXzAmqIUPswPWKUchfytdAjiRFnCe5ojG2bstg6mRiZabkKfCoL/e98pbBELIV/YCeA==", "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, "funding": { "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } @@ -6047,6 +6251,7 @@ "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "dev": true, "optional": true }, "node_modules/glob": { @@ -6117,17 +6322,12 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/globalyzer": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz", - "integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==", - "dev": true - }, "node_modules/globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, + "license": "MIT", "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", @@ -6153,6 +6353,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, "dependencies": { "get-intrinsic": "^1.1.3" }, @@ -6171,18 +6372,6 @@ "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", "dev": true }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", @@ -6205,6 +6394,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, "dependencies": { "es-define-property": "^1.0.0" }, @@ -6216,6 +6406,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -6227,6 +6418,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -6275,6 +6467,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, "dependencies": { "function-bind": "^1.1.2" }, @@ -6286,6 +6479,7 @@ "version": "2.2.5", "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz", "integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==", + "dev": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" @@ -6295,6 +6489,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz", "integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==", + "dev": true, "dependencies": { "@types/hast": "^2.0.0", "comma-separated-tokens": "^1.0.0", @@ -6316,6 +6511,7 @@ "version": "10.7.3", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "dev": true, "engines": { "node": "*" } @@ -6427,6 +6623,7 @@ "version": "3.8.2", "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz", "integrity": "sha512-15gZoQ38eYjEjxkorfbcgBKBL6R7T459OuK+CpcWt7O3KF4uPCx2tD0uFETlUDIyo+1789crbMhTvQBSR5yBMg==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -6474,6 +6671,7 @@ "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, "optional": true }, "node_modules/internal-slot": { @@ -6494,6 +6692,7 @@ "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, "dependencies": { "loose-envify": "^1.0.0" } @@ -6525,6 +6724,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "dev": true, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -6534,6 +6734,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "dev": true, "dependencies": { "is-alphabetical": "^1.0.0", "is-decimal": "^1.0.0" @@ -6548,6 +6749,7 @@ "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -6580,6 +6782,22 @@ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" }, + "node_modules/is-async-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", + "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-bigint": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", @@ -6633,12 +6851,16 @@ } }, "node_modules/is-core-module": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz", - "integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==", + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", "dev": true, + "license": "MIT", "dependencies": { - "has": "^1.0.3" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6678,26 +6900,12 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "dev": true, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true, - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -6707,6 +6915,19 @@ "node": ">=0.10.0" } }, + "node_modules/is-finalizationregistry": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", + "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -6715,6 +6936,22 @@ "node": ">=8" } }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -6731,16 +6968,21 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", + "dev": true, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, "node_modules/is-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", - "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -6815,10 +7057,14 @@ } }, "node_modules/is-set": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", - "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -6884,10 +7130,14 @@ } }, "node_modules/is-weakmap": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", - "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -6905,30 +7155,22 @@ } }, "node_modules/is-weakset": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", - "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", + "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", @@ -6977,11 +7219,42 @@ "node": ">=8" } }, + "node_modules/iterator.prototype": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz", + "integrity": "sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "reflect.getprototypeof": "^1.0.4", + "set-function-name": "^2.0.1" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jiti": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz", - "integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==", + "version": "1.21.6", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", + "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", "dev": true, + "license": "MIT", "bin": { "jiti": "bin/jiti.js" } @@ -7003,7 +7276,8 @@ "node_modules/js-file-download": { "version": "0.4.12", "resolved": "https://registry.npmjs.org/js-file-download/-/js-file-download-0.4.12.tgz", - "integrity": "sha512-rML+NkoD08p5Dllpjo0ffy4jRHeY6Zsapvr/W86N7E0yuzAO6qa5X9+xog6zQNlH102J7IXljNY2FtS6Lj3ucg==" + "integrity": "sha512-rML+NkoD08p5Dllpjo0ffy4jRHeY6Zsapvr/W86N7E0yuzAO6qa5X9+xog6zQNlH102J7IXljNY2FtS6Lj3ucg==", + "dev": true }, "node_modules/js-sdsl": { "version": "4.4.0", @@ -7105,6 +7379,7 @@ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, + "license": "MIT", "dependencies": { "minimist": "^1.2.0" }, @@ -7135,13 +7410,16 @@ } }, "node_modules/jsx-ast-utils": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz", - "integrity": "sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==", + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", "dev": true, + "license": "MIT", "dependencies": { - "array-includes": "^3.1.5", - "object.assign": "^4.1.3" + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" }, "engines": { "node": ">=4.0" @@ -7154,18 +7432,23 @@ "dev": true }, "node_modules/language-subtag-registry": { - "version": "0.3.22", - "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", - "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==", - "dev": true + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "dev": true, + "license": "CC0-1.0" }, "node_modules/language-tags": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", - "integrity": "sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", "dev": true, + "license": "MIT", "dependencies": { - "language-subtag-registry": "~0.3.2" + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" } }, "node_modules/lazystream": { @@ -7234,6 +7517,7 @@ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" } @@ -7277,12 +7561,14 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true }, "node_modules/lodash.defaults": { "version": "4.2.0", @@ -7348,6 +7634,7 @@ "version": "1.20.0", "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz", "integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==", + "dev": true, "dependencies": { "fault": "^1.0.0", "highlight.js": "~10.7.0" @@ -7361,7 +7648,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "devOptional": true, + "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -7404,14 +7691,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/material-ripple-effects": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/material-ripple-effects/-/material-ripple-effects-2.0.1.tgz", @@ -7451,12 +7730,13 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", "dev": true, + "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -7486,6 +7766,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, "optional": true, "engines": { "node": ">=10" @@ -7498,6 +7779,7 @@ "version": "0.23.8", "resolved": "https://registry.npmjs.org/minim/-/minim-0.23.8.tgz", "integrity": "sha512-bjdr2xW1dBCMsMGGsUeqM4eFI60m94+szhxWys+B1ztIt6gWSfeGBdSVCIawezeHYLYn0j6zrsXdQS/JllBzww==", + "dev": true, "dependencies": { "lodash": "^4.15.0" }, @@ -7535,6 +7817,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -7550,7 +7842,7 @@ "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "devOptional": true + "dev": true }, "node_modules/mlly": { "version": "1.7.1", @@ -7597,6 +7889,7 @@ "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", "dev": true, + "license": "MIT", "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", @@ -7607,6 +7900,7 @@ "version": "2.19.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.19.0.tgz", "integrity": "sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==", + "dev": true, "optional": true }, "node_modules/nanoid": { @@ -7630,6 +7924,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", + "dev": true, "optional": true }, "node_modules/natural-compare": { @@ -7799,6 +8094,7 @@ "version": "3.65.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.65.0.tgz", "integrity": "sha512-ThjYBfoDNr08AWx6hGaRbfPwxKV9kVzAzOzlLKbk2CuqXE2xnCh+cbAGnwM3t8Lq4v9rUB7VfondlkBckcJrVA==", + "dev": true, "optional": true, "dependencies": { "semver": "^7.3.5" @@ -7810,12 +8106,14 @@ "node_modules/node-abort-controller": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", - "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==" + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", + "dev": true }, "node_modules/node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "dev": true, "funding": [ { "type": "github", @@ -7853,6 +8151,7 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/node-fetch-commonjs/-/node-fetch-commonjs-3.3.2.tgz", "integrity": "sha512-VBlAiynj3VMLrotgwOS3OyECFxas5y7ltLcK4t41lMUZeaK15Ym4QRkqN0EQKAFL42q9i21EPKjzLUPfltR72A==", + "dev": true, "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" @@ -7937,18 +8236,20 @@ "version": "1.13.1", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" }, "engines": { "node": ">= 0.4" @@ -7985,28 +8286,31 @@ } }, "node_modules/object.entries": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz", - "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==", + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", + "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" } }, "node_modules/object.fromentries": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz", - "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -8015,28 +8319,31 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object.hasown": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.2.tgz", - "integrity": "sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==", + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", "dev": true, + "license": "MIT", "dependencies": { - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">= 0.4" } }, "node_modules/object.values": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", - "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -8057,32 +8364,16 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "devOptional": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/open": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", - "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", "dev": true, "dependencies": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "wrappy": "1" } }, "node_modules/openapi-path-templating": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/openapi-path-templating/-/openapi-path-templating-1.6.0.tgz", "integrity": "sha512-1atBNwOUrZXthTvlvvX8k8ovFEF3iA8mDidYMkdOtvVdndBhTrspbwGXNOzEUaJhm9iUl4Tf5uQaeTLAJvwPig==", + "dev": true, "dependencies": { "apg-lite": "^1.0.3" }, @@ -8094,6 +8385,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/openapi-server-url-templating/-/openapi-server-url-templating-1.1.0.tgz", "integrity": "sha512-dtyTFKx2xVcO0W8JKaluXIHC9l/MLjHeflBaWjiWNMCHp/TBs9dEjQDbj/VFlHR4omFOKjjmqm1pW1aCAhmPBg==", + "dev": true, "dependencies": { "apg-lite": "^1.0.3" }, @@ -8156,6 +8448,13 @@ "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -8171,6 +8470,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "dev": true, "dependencies": { "character-entities": "^1.0.0", "character-entities-legacy": "^1.0.0", @@ -8245,7 +8545,32 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" }, "node_modules/path-type": { "version": "4.0.0", @@ -8292,6 +8617,7 @@ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -8358,10 +8684,11 @@ "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==" }, "node_modules/pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 6" } @@ -8464,17 +8791,18 @@ } }, "node_modules/postcss-import": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.1.0.tgz", - "integrity": "sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==", + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", "dev": true, + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", "resolve": "^1.1.7" }, "engines": { - "node": ">=10.0.0" + "node": ">=14.0.0" }, "peerDependencies": { "postcss": "^8.0.0" @@ -8499,30 +8827,87 @@ "postcss": "^8.4.21" } }, - "node_modules/postcss-nested": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.0.tgz", - "integrity": "sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==", + "node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", "dependencies": { - "postcss-selector-parser": "^6.0.10" + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" }, "engines": { - "node": ">=12.0" + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/lilconfig": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", + "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" }, "peerDependencies": { "postcss": "^8.2.14" } }, "node_modules/postcss-selector-parser": { - "version": "6.0.11", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz", - "integrity": "sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", "dev": true, + "license": "MIT", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -8541,6 +8926,7 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz", "integrity": "sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==", + "dev": true, "optional": true, "dependencies": { "detect-libc": "^2.0.0", @@ -8567,6 +8953,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, "optional": true, "dependencies": { "chownr": "^1.1.1", @@ -8603,6 +8990,7 @@ "version": "1.29.0", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "dev": true, "engines": { "node": ">=6" } @@ -8678,6 +9066,7 @@ "version": "5.6.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", "integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==", + "dev": true, "dependencies": { "xtend": "^4.0.0" }, @@ -8703,7 +9092,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "devOptional": true, + "dev": true, "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -8739,6 +9128,7 @@ "version": "6.12.3", "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.3.tgz", "integrity": "sha512-AWJm14H1vVaO/iNZ4/hO+HyaTehuy9nRqVdkTqlJt0HWvBiBIEXFmb4C0DGeYo3Xes9rrEW+TxHsaigCbN5ICQ==", + "dev": true, "dependencies": { "side-channel": "^1.0.6" }, @@ -8752,7 +9142,8 @@ "node_modules/querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true }, "node_modules/queue-microtask": { "version": "1.2.3", @@ -8785,22 +9176,11 @@ "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" }, - "node_modules/quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/ramda": { "version": "0.30.1", "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz", "integrity": "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==", + "dev": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/ramda" @@ -8810,6 +9190,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ramda-adjunct/-/ramda-adjunct-5.0.1.tgz", "integrity": "sha512-UTQCcWnoiuYH+ua+jGg3GTktcmCSD2W7OO2++tmv8p2Ze+N9VgVACERg4g36rRfIXklVMtqazyBLBWXfoPKgRQ==", + "dev": true, "engines": { "node": ">=0.10.3" }, @@ -8825,6 +9206,7 @@ "version": "0.5.3", "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.5.3.tgz", "integrity": "sha512-U+5l2KrcMNOUPYvazA3h5ekF80FHTUG+87SEAmHZmolh1M+i/WyTCxVzmi+tidIa1tM4BSe8g2Y/D3loWDjj+w==", + "dev": true, "dependencies": { "drange": "^1.0.2", "ret": "^0.2.0" @@ -8837,6 +9219,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, "dependencies": { "safe-buffer": "^5.1.0" } @@ -8845,6 +9228,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, "optional": true, "dependencies": { "deep-extend": "^0.6.0", @@ -8860,6 +9244,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, "optional": true, "engines": { "node": ">=0.10.0" @@ -8880,6 +9265,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz", "integrity": "sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==", + "dev": true, "dependencies": { "copy-to-clipboard": "^3.3.1", "prop-types": "^15.8.1" @@ -8892,6 +9278,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/react-debounce-input/-/react-debounce-input-3.3.0.tgz", "integrity": "sha512-VEqkvs8JvY/IIZvh71Z0TC+mdbxERvYF33RcebnodlsUZ8RSgyKe2VWaHXv4+/8aoOgXLxWrdsYs2hDhcwbUgA==", + "dev": true, "dependencies": { "lodash.debounce": "^4", "prop-types": "^15.8.1" @@ -8916,6 +9303,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/react-immutable-proptypes/-/react-immutable-proptypes-2.2.0.tgz", "integrity": "sha512-Vf4gBsePlwdGvSZoLSBfd4HAP93HDauMY4fDjXhreg/vg6F3Fj/MXDNyTbltPC/xZKmZc+cjLu3598DdYK6sgQ==", + "dev": true, "dependencies": { "invariant": "^2.2.2" }, @@ -8927,6 +9315,7 @@ "version": "2.2.2", "resolved": "https://registry.npmjs.org/react-immutable-pure-component/-/react-immutable-pure-component-2.2.2.tgz", "integrity": "sha512-vkgoMJUDqHZfXXnjVlG3keCxSO/U6WeDQ5/Sl0GK2cH8TOxEzQ5jXqDXHEL/jqk6fsNxV05oH5kD7VNMUE2k+A==", + "dev": true, "peerDependencies": { "immutable": ">= 2 || >= 4.0.0-rc", "react": ">= 16.6", @@ -8937,6 +9326,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/react-inspector/-/react-inspector-6.0.2.tgz", "integrity": "sha512-x+b7LxhmHXjHoU/VrFAzw5iutsILRoYyDq97EDYdFpPLcvqtEzk4ZSZSQjnFPbr5T57tLXnHcqFYoN1pI6u8uQ==", + "dev": true, "peerDependencies": { "react": "^16.8.4 || ^17.0.0 || ^18.0.0" } @@ -8950,6 +9340,7 @@ "version": "15.5.0", "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.5.0.tgz", "integrity": "sha512-+zq2myprEnQmH5yw6Gqc8lD55QHnpKaU8TOcFeC/Lg/MQSs8UknEA0JC4nTZGFAXC2J2Hyj/ijJ7NlabyPi2gg==", + "dev": true, "dependencies": { "@babel/runtime": "^7.3.1", "highlight.js": "^10.4.1", @@ -8966,6 +9357,7 @@ "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", "dev": true, + "license": "MIT", "dependencies": { "pify": "^2.3.0" } @@ -9055,20 +9447,45 @@ "node_modules/redux": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", - "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "dev": true }, "node_modules/redux-immutable": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/redux-immutable/-/redux-immutable-4.0.0.tgz", "integrity": "sha512-SchSn/DWfGb3oAejd+1hhHx01xUoxY+V7TeK0BKqpkLKiQPVFf7DYzEaKmrEVxsWxielKfSK9/Xq66YyxgR1cg==", + "dev": true, "peerDependencies": { "immutable": "^3.8.1 || ^4.0.0-rc.1" } }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", + "integrity": "sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.1", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.3", + "which-builtin-type": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/refractor": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz", "integrity": "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==", + "dev": true, "dependencies": { "hastscript": "^6.0.0", "parse-entities": "^2.0.0", @@ -9083,6 +9500,7 @@ "version": "1.27.0", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz", "integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==", + "dev": true, "engines": { "node": ">=6" } @@ -9090,7 +9508,8 @@ "node_modules/regenerator-runtime": { "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true }, "node_modules/regexp.prototype.flags": { "version": "1.5.2", @@ -9122,6 +9541,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/remarkable/-/remarkable-2.0.1.tgz", "integrity": "sha512-YJyMcOH5lrR+kZdmB0aJJ4+93bEojRZ1HGDn9Eagu6ibg7aVZhc3OWbbShRid+Q5eAfsEqWxpe+g5W5nYNfNiA==", + "dev": true, "dependencies": { "argparse": "^1.0.10", "autolinker": "^3.11.0" @@ -9137,6 +9557,7 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, "dependencies": { "sprintf-js": "~1.0.2" } @@ -9145,6 +9566,7 @@ "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "dev": true, "engines": { "node": ">=0.10" } @@ -9174,20 +9596,23 @@ "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true }, "node_modules/reselect": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", - "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==" + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "dev": true }, "node_modules/resolve": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", - "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dev": true, + "license": "MIT", "dependencies": { - "is-core-module": "^2.11.0", + "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -9206,10 +9631,21 @@ "node": ">=4" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/ret": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz", "integrity": "sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==", + "dev": true, "engines": { "node": ">=4" } @@ -9417,7 +9853,7 @@ "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "devOptional": true, + "dev": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -9432,6 +9868,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz", "integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==", + "dev": true, "dependencies": { "type-fest": "^0.20.2" }, @@ -9451,6 +9888,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -9515,6 +9953,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/short-unique-id/-/short-unique-id-5.2.0.tgz", "integrity": "sha512-cMGfwNyfDZ/nzJ2k2M+ClthBIh//GlZl1JEf47Uoa9XR11bz8Pa2T2wQO4bVrRdH48LrIDWJahQziKo3MjhsWg==", + "dev": true, "bin": { "short-unique-id": "bin/short-unique-id", "suid": "bin/short-unique-id" @@ -9524,6 +9963,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dev": true, "dependencies": { "call-bind": "^1.0.7", "es-errors": "^1.3.0", @@ -9553,6 +9993,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true, "funding": [ { "type": "github", @@ -9573,6 +10014,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "dev": true, "funding": [ { "type": "github", @@ -9599,6 +10041,7 @@ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -9632,6 +10075,7 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==", + "dev": true, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -9654,7 +10098,8 @@ "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true }, "node_modules/ssh-remote-port-forward": { "version": "1.0.4", @@ -9800,6 +10245,7 @@ "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", "dev": true, + "license": "MIT", "dependencies": { "internal-slot": "^1.0.4" }, @@ -9850,10 +10296,32 @@ "node": ">=8" } }, - "node_modules/string-width/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.includes": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.0.tgz", + "integrity": "sha512-E34CkBgyeqNDcrbU76cDjL5JLcVrtSdYq0MEh/B10r17pRP4ciHLwTgnuLV8Ay6cgEMLkcBkFCKyFZ43YldYzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } }, "node_modules/string.prototype.matchall": { "version": "4.0.11", @@ -9881,6 +10349,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, "node_modules/string.prototype.trim": { "version": "1.2.9", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", @@ -9941,11 +10420,26 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -10012,14 +10506,15 @@ } }, "node_modules/sucrase": { - "version": "3.32.0", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.32.0.tgz", - "integrity": "sha512-ydQOU34rpSyj2TGyz4D2p8rbktIOZ8QY9s+DGLvFU1i5pWJE8vkpruCjGCMHsdXwnD7JDcS+noSwM/a7zyNFDQ==", + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", - "glob": "7.1.6", + "glob": "^10.3.10", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", @@ -10030,24 +10525,51 @@ "sucrase-node": "bin/sucrase-node" }, "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" } }, "node_modules/sucrase/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, + "license": "ISC", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sucrase/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -10070,6 +10592,7 @@ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -10081,6 +10604,7 @@ "version": "3.28.2", "resolved": "https://registry.npmjs.org/swagger-client/-/swagger-client-3.28.2.tgz", "integrity": "sha512-g30KCdSVyZlMulWOJnheNo7Ea+L06OZebl0oRU6zHd5Zf5AZKHTqurKRdNOLsMWA3l3bWJiEh7s3JlzFJHRmoQ==", + "dev": true, "dependencies": { "@babel/runtime-corejs3": "^7.22.15", "@swagger-api/apidom-core": ">=1.0.0-alpha.5 <1.0.0-beta.0", @@ -10105,6 +10629,7 @@ "version": "5.17.14", "resolved": "https://registry.npmjs.org/swagger-ui/-/swagger-ui-5.17.14.tgz", "integrity": "sha512-z9pOymwowrgkoMKNsRSN6QjOyYrMAnc4iohEebnS/ELT5jjdB13Lu4f3Bl4+o2Mw5B6QOyo4vibr/A/Vb7UbMQ==", + "dev": true, "dependencies": { "@babel/runtime-corejs3": "^7.24.5", "@braintree/sanitize-url": "=7.0.2", @@ -10147,6 +10672,7 @@ "version": "5.17.14", "resolved": "https://registry.npmjs.org/swagger-ui-react/-/swagger-ui-react-5.17.14.tgz", "integrity": "sha512-mCXerZrbcn4ftPYifUF0+iKIRTHoVCv0HcJc/sXl9nCe3oeWdsjmOWVqKabzzAkAa0NwsbKNJFv2UL/Ivnf6VQ==", + "dev": true, "dependencies": { "@babel/runtime-corejs3": "^7.24.5", "@braintree/sanitize-url": "=7.0.2", @@ -10191,6 +10717,7 @@ "version": "18.3.3", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", + "dev": true, "optional": true, "peer": true, "dependencies": { @@ -10202,6 +10729,7 @@ "version": "9.1.2", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.1.2.tgz", "integrity": "sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==", + "dev": true, "dependencies": { "@types/use-sync-external-store": "^0.0.3", "use-sync-external-store": "^1.0.0" @@ -10224,6 +10752,7 @@ "version": "18.3.3", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", + "dev": true, "optional": true, "peer": true, "dependencies": { @@ -10235,6 +10764,7 @@ "version": "9.1.2", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.1.2.tgz", "integrity": "sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==", + "dev": true, "dependencies": { "@types/use-sync-external-store": "^0.0.3", "use-sync-external-store": "^1.0.0" @@ -10261,22 +10791,6 @@ "optional": true, "peer": true }, - "node_modules/synckit": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz", - "integrity": "sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==", - "dev": true, - "dependencies": { - "@pkgr/utils": "^2.3.1", - "tslib": "^2.5.0" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts" - } - }, "node_modules/tabbable": { "version": "6.1.2", "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.1.2.tgz", @@ -10292,74 +10806,41 @@ } }, "node_modules/tailwindcss": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.1.tgz", - "integrity": "sha512-Vkiouc41d4CEq0ujXl6oiGFQ7bA3WEhUZdTgXAhtKxSy49OmKs8rEfQmupsfF0IGW8fv2iQkp1EVUuapCFrZ9g==", + "version": "3.4.10", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.10.tgz", + "integrity": "sha512-KWZkVPm7yJRhdu4SRSl9d4AK2wM3a50UsvgHZO7xY77NQr2V+fIrEuoDGQcbvswWvFGbS2f6e+jC/6WJm1Dl0w==", "dev": true, + "license": "MIT", "dependencies": { + "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.5.3", - "color-name": "^1.1.4", "didyoumean": "^1.2.2", "dlv": "^1.1.3", - "fast-glob": "^3.2.12", + "fast-glob": "^3.3.0", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", - "jiti": "^1.17.2", - "lilconfig": "^2.0.6", + "jiti": "^1.21.0", + "lilconfig": "^2.1.0", "micromatch": "^4.0.5", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.0.0", - "postcss": "^8.0.9", - "postcss-import": "^14.1.0", - "postcss-js": "^4.0.0", - "postcss-load-config": "^3.1.4", - "postcss-nested": "6.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", "postcss-selector-parser": "^6.0.11", - "postcss-value-parser": "^4.2.0", - "quick-lru": "^5.1.1", - "resolve": "^1.22.1", - "sucrase": "^3.29.0" + "resolve": "^1.22.2", + "sucrase": "^3.32.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" }, "engines": { - "node": ">=12.13.0" - }, - "peerDependencies": { - "postcss": "^8.0.9" - } - }, - "node_modules/tailwindcss/node_modules/postcss-load-config": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", - "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", - "dev": true, - "dependencies": { - "lilconfig": "^2.0.5", - "yaml": "^1.10.2" - }, - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": ">=8.0.9", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "postcss": { - "optional": true - }, - "ts-node": { - "optional": true - } + "node": ">=14.0.0" } }, "node_modules/tapable": { @@ -10367,6 +10848,7 @@ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -10400,7 +10882,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "devOptional": true, + "dev": true, "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", @@ -10469,6 +10951,7 @@ "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", "dev": true, + "license": "MIT", "dependencies": { "any-promise": "^1.0.0" } @@ -10478,6 +10961,7 @@ "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", "dev": true, + "license": "MIT", "dependencies": { "thenify": ">= 3.1.0 < 4" }, @@ -10493,16 +10977,6 @@ "real-require": "^0.2.0" } }, - "node_modules/tiny-glob": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz", - "integrity": "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==", - "dev": true, - "dependencies": { - "globalyzer": "0.1.0", - "globrex": "^0.1.2" - } - }, "node_modules/tinybench": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.8.0.tgz", @@ -10560,7 +11034,8 @@ "node_modules/toggle-selection": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", - "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==" + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==", + "dev": true }, "node_modules/tough-cookie": { "version": "4.1.4", @@ -10597,6 +11072,7 @@ "version": "0.6.8", "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.8.tgz", "integrity": "sha512-aXJDbk6SnumuaZSANd21XAo15ucCDE38H4fkqiGsc3MhCK+wOlZvLP9cB/TvpHT0mOyWgC4Z8EwRlzqYSUzdsA==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -10608,6 +11084,7 @@ "version": "0.20.4", "resolved": "https://registry.npmjs.org/tree-sitter/-/tree-sitter-0.20.4.tgz", "integrity": "sha512-rjfR5dc4knG3jnJNN/giJ9WOoN1zL/kZyrS0ILh+eqq8RNcIbiXA63JsMEgluug0aNvfQvK4BfCErN1vIzvKog==", + "dev": true, "hasInstallScript": true, "optional": true, "dependencies": { @@ -10619,6 +11096,7 @@ "version": "0.20.2", "resolved": "https://registry.npmjs.org/tree-sitter-json/-/tree-sitter-json-0.20.2.tgz", "integrity": "sha512-eUxrowp4F1QEGk/i7Sa+Xl8Crlfp7J0AXxX1QdJEQKQYMWhgMbCIgyQvpO3Q0P9oyTrNQxRLlRipDS44a8EtRw==", + "dev": true, "hasInstallScript": true, "optional": true, "dependencies": { @@ -10629,6 +11107,7 @@ "version": "0.5.0", "resolved": "https://registry.npmjs.org/tree-sitter-yaml/-/tree-sitter-yaml-0.5.0.tgz", "integrity": "sha512-POJ4ZNXXSWIG/W4Rjuyg36MkUD4d769YRUGKRqN+sVaj/VCo6Dh6Pkssn1Rtewd5kybx+jT1BWMyWN0CijXnMA==", + "dev": true, "hasInstallScript": true, "optional": true, "dependencies": { @@ -10639,70 +11118,20 @@ "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "dev": true + "dev": true, + "license": "Apache-2.0" }, "node_modules/ts-mixer": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", - "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==" - }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/ts-node/node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "optional": true, - "peer": true + "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==", + "dev": true }, "node_modules/ts-toolbelt": { "version": "9.6.0", "resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-9.6.0.tgz", - "integrity": "sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==" + "integrity": "sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==", + "dev": true }, "node_modules/tsconfck": { "version": "3.1.0", @@ -10725,10 +11154,11 @@ } }, "node_modules/tsconfig-paths": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", - "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dev": true, + "license": "MIT", "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", @@ -10746,6 +11176,7 @@ "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", "dev": true, + "license": "MIT", "dependencies": { "tslib": "^1.8.1" }, @@ -10760,12 +11191,14 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "dev": true, + "license": "0BSD" }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, "optional": true, "dependencies": { "safe-buffer": "^5.0.1" @@ -10805,6 +11238,7 @@ "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, "engines": { "node": ">=10" }, @@ -10925,6 +11359,7 @@ "version": "0.30.1", "resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.30.1.tgz", "integrity": "sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==", + "dev": true, "dependencies": { "ts-toolbelt": "^9.6.0" } @@ -11018,7 +11453,8 @@ "node_modules/unraw": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/unraw/-/unraw-3.0.0.tgz", - "integrity": "sha512-08/DA66UF65OlpUDIQtbJyrqTR0jTAlJ+jsnkQ4jxR7+K5g5YG1APZKQSMCE1vqqmD+2pv6+IdEjmopFatacvg==" + "integrity": "sha512-08/DA66UF65OlpUDIQtbJyrqTR0jTAlJ+jsnkQ4jxR7+K5g5YG1APZKQSMCE1vqqmD+2pv6+IdEjmopFatacvg==", + "dev": true }, "node_modules/update-browserslist-db": { "version": "1.0.13", @@ -11063,6 +11499,7 @@ "version": "1.5.10", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, "dependencies": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" @@ -11072,6 +11509,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==", + "dev": true, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } @@ -11089,14 +11527,6 @@ "uuid": "dist/bin/uuid" } }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/vite": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.1.tgz", @@ -11437,6 +11867,7 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "dev": true, "engines": { "node": ">= 8" } @@ -11445,6 +11876,7 @@ "version": "0.20.3", "resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.20.3.tgz", "integrity": "sha512-zKGJW9r23y3BcJusbgvnOH2OYAW40MXAOi9bi3Gcc7T4Gms9WWgXF8m6adsJWpGJEhgOzCrfiz1IzKowJWrtYw==", + "dev": true, "optional": true }, "node_modules/webidl-conversions": { @@ -11535,16 +11967,47 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/which-builtin-type": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.4.tgz", + "integrity": "sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w==", + "dev": true, + "license": "MIT", + "dependencies": { + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.0.5", + "is-finalizationregistry": "^1.0.2", + "is-generator-function": "^1.0.10", + "is-regex": "^1.1.4", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/which-collection": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", - "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", "dev": true, + "license": "MIT", "dependencies": { - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-weakmap": "^2.0.1", - "is-weakset": "^2.0.1" + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -11611,11 +12074,30 @@ "node": ">=8" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "devOptional": true + "dev": true }, "node_modules/ws": { "version": "8.17.1", @@ -11643,12 +12125,14 @@ "node_modules/xml": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", - "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==" + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==", + "dev": true }, "node_modules/xml-but-prettier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/xml-but-prettier/-/xml-but-prettier-1.0.1.tgz", "integrity": "sha512-C2CJaadHrZTqESlH03WOyw0oZTtoy2uEg6dSDF6YRg+9GnYNub53RRemLpnvtbHDFelxMx4LajiFsYeR6XJHgQ==", + "dev": true, "dependencies": { "repeat-string": "^1.5.2" } @@ -11689,15 +12173,19 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "devOptional": true + "dev": true }, "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.0.tgz", + "integrity": "sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==", "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, "engines": { - "node": ">= 6" + "node": ">= 14" } }, "node_modules/yargs": { @@ -11781,17 +12269,6 @@ "node": ">=8" } }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -11807,7 +12284,8 @@ "node_modules/zenscroll": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/zenscroll/-/zenscroll-4.0.2.tgz", - "integrity": "sha512-jEA1znR7b4C/NnaycInCU6h/d15ZzCd1jmsruqOKnZP6WXQSMH3W2GL+OXbkruslU4h+Tzuos0HdswzRUk/Vgg==" + "integrity": "sha512-jEA1znR7b4C/NnaycInCU6h/d15ZzCd1jmsruqOKnZP6WXQSMH3W2GL+OXbkruslU4h+Tzuos0HdswzRUk/Vgg==", + "dev": true }, "node_modules/zip-stream": { "version": "4.1.1", diff --git a/vclogin/package.json b/vclogin/package.json index 42a0133..9b0160b 100644 --- a/vclogin/package.json +++ b/vclogin/package.json @@ -30,8 +30,6 @@ "pino-http": "^10.1.0", "react": "18.2.0", "react-dom": "18.2.0", - "swagger-ui": "^5.17.14", - "swagger-ui-react": "^5.17.14", "ua-parser-js": "^1.0.38", "uuid": "^9.0.0" }, @@ -55,7 +53,9 @@ "eslint-plugin-react-hooks": "^4.6.0", "postcss": "^8.4.39", "prettier": "^2.8.8", - "tailwindcss": "3.3.1", + "swagger-ui": "^5.17.14", + "swagger-ui-react": "^5.17.14", + "tailwindcss": "^3.2.0", "typescript": "5.0.4", "vite": "^5.3.1", "vite-tsconfig-paths": "^4.3.2", From fa9045e9fac3c522669b068c2d8c849d095bb905 Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Fri, 23 Aug 2024 15:08:53 +0200 Subject: [PATCH 79/85] Fix license headers Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> --- vclogin/lib/generatePresentationDefinition.ts | 3 ++- vclogin/{middleware.ts => middleware/protection.ts} | 5 +++++ vclogin/pages/_app.tsx | 4 ---- vclogin/pages/api-docs.tsx | 5 +++++ vclogin/pages/api/dynamic/createTempAuthorization.ts | 5 +++++ vclogin/pages/api/dynamic/getAuthResponse.ts | 5 +++++ vclogin/pages/api/dynamic/getQRCodeString.ts | 5 +++++ vclogin/pages/api/dynamic/presentCredentialById.ts | 5 +++++ vclogin/types/InputDescriptor.ts | 5 +++++ vclogin/types/PresentationDefinition.ts | 5 +++++ 10 files changed, 42 insertions(+), 5 deletions(-) rename vclogin/{middleware.ts => middleware/protection.ts} (83%) diff --git a/vclogin/lib/generatePresentationDefinition.ts b/vclogin/lib/generatePresentationDefinition.ts index 30b737b..844e379 100644 --- a/vclogin/lib/generatePresentationDefinition.ts +++ b/vclogin/lib/generatePresentationDefinition.ts @@ -46,7 +46,7 @@ export const generatePresentationDefinition = ( }, }, id: crypto.randomUUID(), - name: "VC Login Service", + name: "SSI-to-OIDC Bridge", purpose: "Sign-in", input_descriptors: [] as InputDescriptor[], }; @@ -66,6 +66,7 @@ export const generatePresentationDefinition = ( for (let expectation of policy) { if (expectation.patterns.length > 1) { let req = { + name: "Group " + expectation.credentialId, rule: "pick", count: 1, from: "group_" + expectation.credentialId, diff --git a/vclogin/middleware.ts b/vclogin/middleware/protection.ts similarity index 83% rename from vclogin/middleware.ts rename to vclogin/middleware/protection.ts index 31972a7..43e9935 100644 --- a/vclogin/middleware.ts +++ b/vclogin/middleware/protection.ts @@ -1,3 +1,8 @@ +/** + * Copyright 2024 Software Engineering for Business Information Systems (sebis) . + * SPDX-License-Identifier: MIT + */ + import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; diff --git a/vclogin/pages/_app.tsx b/vclogin/pages/_app.tsx index 1824e85..c0da6fe 100644 --- a/vclogin/pages/_app.tsx +++ b/vclogin/pages/_app.tsx @@ -3,10 +3,6 @@ * SPDX-License-Identifier: MIT */ -/** - * Copyright 2024 Software Engineering for Business Information Systems (sebis) . - * SPDX-License-Identifier: MIT - */ import "@/styles/globals.css"; import type { AppProps } from "next/app"; diff --git a/vclogin/pages/api-docs.tsx b/vclogin/pages/api-docs.tsx index 1db446d..a345150 100644 --- a/vclogin/pages/api-docs.tsx +++ b/vclogin/pages/api-docs.tsx @@ -1,3 +1,8 @@ +/** + * Copyright 2024 Software Engineering for Business Information Systems (sebis) . + * SPDX-License-Identifier: MIT + */ + "use client"; import SwaggerUI from "swagger-ui-react"; diff --git a/vclogin/pages/api/dynamic/createTempAuthorization.ts b/vclogin/pages/api/dynamic/createTempAuthorization.ts index 5da0f1d..9e5cc74 100644 --- a/vclogin/pages/api/dynamic/createTempAuthorization.ts +++ b/vclogin/pages/api/dynamic/createTempAuthorization.ts @@ -1,3 +1,8 @@ +/** + * Copyright 2024 Software Engineering for Business Information Systems (sebis) . + * SPDX-License-Identifier: MIT + */ + import { NextApiRequest, NextApiResponse } from "next"; import crypto from "crypto"; import { redisSet } from "@/config/redis"; diff --git a/vclogin/pages/api/dynamic/getAuthResponse.ts b/vclogin/pages/api/dynamic/getAuthResponse.ts index 855ebe9..1e3d23a 100644 --- a/vclogin/pages/api/dynamic/getAuthResponse.ts +++ b/vclogin/pages/api/dynamic/getAuthResponse.ts @@ -1,3 +1,8 @@ +/** + * Copyright 2024 Software Engineering for Business Information Systems (sebis) . + * SPDX-License-Identifier: MIT + */ + import { NextApiRequest, NextApiResponse } from "next"; import { logger } from "@/config/logger"; import { redisGet } from "@/config/redis"; diff --git a/vclogin/pages/api/dynamic/getQRCodeString.ts b/vclogin/pages/api/dynamic/getQRCodeString.ts index bd38e7f..ae37d63 100644 --- a/vclogin/pages/api/dynamic/getQRCodeString.ts +++ b/vclogin/pages/api/dynamic/getQRCodeString.ts @@ -1,3 +1,8 @@ +/** + * Copyright 2024 Software Engineering for Business Information Systems (sebis) . + * SPDX-License-Identifier: MIT + */ + import { NextApiRequest, NextApiResponse } from "next"; export default async function handler( diff --git a/vclogin/pages/api/dynamic/presentCredentialById.ts b/vclogin/pages/api/dynamic/presentCredentialById.ts index bfc4beb..9af418c 100644 --- a/vclogin/pages/api/dynamic/presentCredentialById.ts +++ b/vclogin/pages/api/dynamic/presentCredentialById.ts @@ -1,3 +1,8 @@ +/** + * Copyright 2024 Software Engineering for Business Information Systems (sebis) . + * SPDX-License-Identifier: MIT + */ + import { NextApiRequest, NextApiResponse } from "next"; import { generatePresentationDefinition } from "@/lib/generatePresentationDefinition"; import { LoginPolicy } from "@/types/LoginPolicy"; diff --git a/vclogin/types/InputDescriptor.ts b/vclogin/types/InputDescriptor.ts index d0104db..a491be6 100644 --- a/vclogin/types/InputDescriptor.ts +++ b/vclogin/types/InputDescriptor.ts @@ -1,3 +1,8 @@ +/** + * Copyright 2024 Software Engineering for Business Information Systems (sebis) . + * SPDX-License-Identifier: MIT + */ + type Fields = { path: string[]; filter?: { diff --git a/vclogin/types/PresentationDefinition.ts b/vclogin/types/PresentationDefinition.ts index efda8e9..8d62324 100644 --- a/vclogin/types/PresentationDefinition.ts +++ b/vclogin/types/PresentationDefinition.ts @@ -1,3 +1,8 @@ +/** + * Copyright 2024 Software Engineering for Business Information Systems (sebis) . + * SPDX-License-Identifier: MIT + */ + import { InputDescriptor } from "./InputDescriptor"; export type PresentationDefinition = { From 8c575f1792dd2e728f1be5becb9c547e4a8ce006 Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Fri, 23 Aug 2024 15:17:22 +0200 Subject: [PATCH 80/85] Fix more license headers Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> --- vclogin/cache/config.json | 3 --- vclogin/lib/generatePresentationDefinition.ts | 1 + vclogin/lib/getMetadata.ts | 5 +++++ vclogin/lib/getToken.ts | 5 +++++ 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/vclogin/cache/config.json b/vclogin/cache/config.json index b07cfd6..cd23589 100644 --- a/vclogin/cache/config.json +++ b/vclogin/cache/config.json @@ -1,6 +1,3 @@ -// Copyright 2024 Software Engineering for Business Information Systems (sebis) . -// SPDX-License-Identifier: MIT - { "telemetry": { "notifiedAt": "1682077309825", diff --git a/vclogin/lib/generatePresentationDefinition.ts b/vclogin/lib/generatePresentationDefinition.ts index 844e379..0d37ddc 100644 --- a/vclogin/lib/generatePresentationDefinition.ts +++ b/vclogin/lib/generatePresentationDefinition.ts @@ -2,6 +2,7 @@ * Copyright 2024 Software Engineering for Business Information Systems (sebis) . * SPDX-License-Identifier: MIT */ + import { InputDescriptor } from "@/types/InputDescriptor"; import { PresentationDefinition } from "@/types/PresentationDefinition"; import { LoginPolicy } from "@/types/LoginPolicy"; diff --git a/vclogin/lib/getMetadata.ts b/vclogin/lib/getMetadata.ts index 6e4445b..530ad77 100644 --- a/vclogin/lib/getMetadata.ts +++ b/vclogin/lib/getMetadata.ts @@ -1,3 +1,8 @@ +/** + * Copyright 2024 Software Engineering for Business Information Systems (sebis) . + * SPDX-License-Identifier: MIT + */ + export const getMetadata = (redirect_uris: string[]) => { const metadata = { scopes_supported: ["openid"], diff --git a/vclogin/lib/getToken.ts b/vclogin/lib/getToken.ts index 75993bd..d85c4d5 100644 --- a/vclogin/lib/getToken.ts +++ b/vclogin/lib/getToken.ts @@ -1,3 +1,8 @@ +/** + * Copyright 2024 Software Engineering for Business Information Systems (sebis) . + * SPDX-License-Identifier: MIT + */ + import { PresentationDefinition } from "@/types/PresentationDefinition"; import { keyToDID, keyToVerificationMethod } from "@spruceid/didkit-wasm-node"; import * as jose from "jose"; From 68cb0b692f909883bd02088666bf11097d57d31f Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Fri, 23 Aug 2024 17:45:49 +0200 Subject: [PATCH 81/85] Add http logging middleware Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> --- vclogin/middleware.ts | 25 +++++++++++++++++++ vclogin/middleware/protection.ts | 25 ------------------- vclogin/pages/api/clientMetadata.ts | 7 +++--- .../pages/api/dynamic/clientMetadataById.ts | 7 +++--- .../api/dynamic/createTempAuthorization.ts | 8 +++--- vclogin/pages/api/dynamic/getAuthResponse.ts | 8 +++--- vclogin/pages/api/dynamic/getQRCodeString.ts | 8 +++--- .../api/dynamic/presentCredentialById.ts | 5 +++- 8 files changed, 47 insertions(+), 46 deletions(-) create mode 100644 vclogin/middleware.ts delete mode 100644 vclogin/middleware/protection.ts diff --git a/vclogin/middleware.ts b/vclogin/middleware.ts new file mode 100644 index 0000000..c453ec7 --- /dev/null +++ b/vclogin/middleware.ts @@ -0,0 +1,25 @@ +/** + * Copyright 2024 Software Engineering for Business Information Systems (sebis) . + * SPDX-License-Identifier: MIT + */ + +import { NextResponse } from "next/server"; +import type { NextRequest } from "next/server"; + +export function middleware(req: NextRequest) { + // enforce authorization for sensitive endpoints needed for dynamic authorization + if (req.nextUrl.pathname.startsWith("/api/dynamic")) { + const authHeader = req.headers.get("Authorization"); + const apiKey = authHeader?.split(" ")[1]; + if (apiKey !== process.env.API_KEY) { + return new Response("Unauthorized", { status: 401 }); + } + } + // would like to do logging here but because nextjs middleware runs in Edge Runtime + // it does not seem to like pino logger + return NextResponse.next(); +} + +export const config = { + matcher: "/(api/.*)", +}; diff --git a/vclogin/middleware/protection.ts b/vclogin/middleware/protection.ts deleted file mode 100644 index 43e9935..0000000 --- a/vclogin/middleware/protection.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright 2024 Software Engineering for Business Information Systems (sebis) . - * SPDX-License-Identifier: MIT - */ - -import { NextResponse } from "next/server"; -import type { NextRequest } from "next/server"; - -const protectedPaths = [ - "/api/dynamic/createTempAuthorization", - "/api/dynamic/getAuthResponse", - "/api/dynamic/getQRCodeString", -]; - -export function middleware(req: NextRequest) { - const authHeader = req.headers.get("Authorization"); - const path = req.nextUrl.pathname; - const apiKey = authHeader?.split(" ")[1]; - if (protectedPaths.includes(path) && apiKey === process.env.API_KEY) { - return NextResponse.next(); - } else if (protectedPaths.includes(path) && apiKey !== process.env.API_KEY) { - return new Response("Unauthorized", { status: 401 }); - } - return NextResponse.next(); -} diff --git a/vclogin/pages/api/clientMetadata.ts b/vclogin/pages/api/clientMetadata.ts index c39aac1..b3d5d47 100644 --- a/vclogin/pages/api/clientMetadata.ts +++ b/vclogin/pages/api/clientMetadata.ts @@ -6,11 +6,9 @@ import { getMetadata } from "@/lib/getMetadata"; import type { NextApiRequest, NextApiResponse } from "next"; import { logger } from "@/config/logger"; +import { withLogging } from "@/middleware/logging"; -export default async function handler( - req: NextApiRequest, - res: NextApiResponse, -) { +async function handler(req: NextApiRequest, res: NextApiResponse) { try { const { method } = req; if (method === "GET") { @@ -28,3 +26,4 @@ export default async function handler( } export const config = { api: { bodyParser: false } }; +export default withLogging(handler); diff --git a/vclogin/pages/api/dynamic/clientMetadataById.ts b/vclogin/pages/api/dynamic/clientMetadataById.ts index 579937a..9181337 100644 --- a/vclogin/pages/api/dynamic/clientMetadataById.ts +++ b/vclogin/pages/api/dynamic/clientMetadataById.ts @@ -6,11 +6,9 @@ import { getMetadata } from "@/lib/getMetadata"; import type { NextApiRequest, NextApiResponse } from "next"; import { logger } from "@/config/logger"; +import { withLogging } from "@/middleware/logging"; -export default async function handler( - req: NextApiRequest, - res: NextApiResponse, -) { +async function handler(req: NextApiRequest, res: NextApiResponse) { try { const { method } = req; if (method === "GET") { @@ -29,3 +27,4 @@ export default async function handler( } export const config = { api: { bodyParser: false } }; +export default withLogging(handler); diff --git a/vclogin/pages/api/dynamic/createTempAuthorization.ts b/vclogin/pages/api/dynamic/createTempAuthorization.ts index 9e5cc74..d5a5b34 100644 --- a/vclogin/pages/api/dynamic/createTempAuthorization.ts +++ b/vclogin/pages/api/dynamic/createTempAuthorization.ts @@ -6,11 +6,9 @@ import { NextApiRequest, NextApiResponse } from "next"; import crypto from "crypto"; import { redisSet } from "@/config/redis"; +import { withLogging } from "@/middleware/logging"; -export default async function handler( - req: NextApiRequest, - res: NextApiResponse, -) { +async function handler(req: NextApiRequest, res: NextApiResponse) { //Get Policy from request body const { policy, inputDescriptor } = req.body; @@ -29,3 +27,5 @@ export default async function handler( return res.status(500).json({ redirect: "/error" }); } } + +export default withLogging(handler); diff --git a/vclogin/pages/api/dynamic/getAuthResponse.ts b/vclogin/pages/api/dynamic/getAuthResponse.ts index 1e3d23a..e7216ad 100644 --- a/vclogin/pages/api/dynamic/getAuthResponse.ts +++ b/vclogin/pages/api/dynamic/getAuthResponse.ts @@ -6,11 +6,9 @@ import { NextApiRequest, NextApiResponse } from "next"; import { logger } from "@/config/logger"; import { redisGet } from "@/config/redis"; +import { withLogging } from "@/middleware/logging"; -export default async function handler( - req: NextApiRequest, - res: NextApiResponse, -) { +async function handler(req: NextApiRequest, res: NextApiResponse) { //read uuid from query params const uuid = req.query["uuid"]; logger.debug("uuid: ", uuid); @@ -29,3 +27,5 @@ export default async function handler( res.status(200).json({ auth_res: "error_not_found" }); } } + +export default withLogging(handler); diff --git a/vclogin/pages/api/dynamic/getQRCodeString.ts b/vclogin/pages/api/dynamic/getQRCodeString.ts index ae37d63..1a0245c 100644 --- a/vclogin/pages/api/dynamic/getQRCodeString.ts +++ b/vclogin/pages/api/dynamic/getQRCodeString.ts @@ -3,12 +3,10 @@ * SPDX-License-Identifier: MIT */ +import { withLogging } from "@/middleware/logging"; import { NextApiRequest, NextApiResponse } from "next"; -export default async function handler( - req: NextApiRequest, - res: NextApiResponse, -) { +async function handler(req: NextApiRequest, res: NextApiResponse) { const { userId, uuid } = req.body; //Generate QR Code String from UUID @@ -24,3 +22,5 @@ export default async function handler( return res.status(200).json({ qrCodeString }); } + +export default withLogging(handler); diff --git a/vclogin/pages/api/dynamic/presentCredentialById.ts b/vclogin/pages/api/dynamic/presentCredentialById.ts index 9af418c..065195e 100644 --- a/vclogin/pages/api/dynamic/presentCredentialById.ts +++ b/vclogin/pages/api/dynamic/presentCredentialById.ts @@ -11,6 +11,7 @@ import { verifyAuthenticationPresentation } from "@/lib/verifyPresentation"; import { getToken } from "@/lib/getToken"; import { logger } from "@/config/logger"; import { redisSet, redisGet } from "@/config/redis"; +import { withLogging } from "@/middleware/logging"; const getHandler = async (req: NextApiRequest, res: NextApiResponse) => { logger.debug("LOGIN API GET BY ID"); @@ -115,7 +116,7 @@ const handlers: any = { GET: getHandler, }; -export default async function handler( +async function handler( req: NextApiRequest, res: NextApiResponse, //todo look for separate handles ) { @@ -129,3 +130,5 @@ export default async function handler( res.status(500).end(); } } + +export default withLogging(handler); From 7d791093450bd7c761fbc32db2ed4b2dffda944e Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Mon, 26 Aug 2024 15:38:21 +0200 Subject: [PATCH 82/85] Logger cleanup Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> --- vclogin/pages/api/clientMetadata.ts | 2 -- .../pages/api/dynamic/clientMetadataById.ts | 2 -- vclogin/pages/api/dynamic/getAuthResponse.ts | 2 -- .../api/dynamic/presentCredentialById.ts | 23 ++++++++++--------- vclogin/pages/api/presentCredential.ts | 3 +-- 5 files changed, 13 insertions(+), 19 deletions(-) diff --git a/vclogin/pages/api/clientMetadata.ts b/vclogin/pages/api/clientMetadata.ts index b3d5d47..ab67435 100644 --- a/vclogin/pages/api/clientMetadata.ts +++ b/vclogin/pages/api/clientMetadata.ts @@ -5,14 +5,12 @@ import { getMetadata } from "@/lib/getMetadata"; import type { NextApiRequest, NextApiResponse } from "next"; -import { logger } from "@/config/logger"; import { withLogging } from "@/middleware/logging"; async function handler(req: NextApiRequest, res: NextApiResponse) { try { const { method } = req; if (method === "GET") { - logger.debug("METADATA BY ID API GET"); const metadata = getMetadata([ process.env.NEXT_PUBLIC_INTERNET_URL + "/api/dynamic/presentCredential", ]); diff --git a/vclogin/pages/api/dynamic/clientMetadataById.ts b/vclogin/pages/api/dynamic/clientMetadataById.ts index 9181337..e7bb873 100644 --- a/vclogin/pages/api/dynamic/clientMetadataById.ts +++ b/vclogin/pages/api/dynamic/clientMetadataById.ts @@ -5,14 +5,12 @@ import { getMetadata } from "@/lib/getMetadata"; import type { NextApiRequest, NextApiResponse } from "next"; -import { logger } from "@/config/logger"; import { withLogging } from "@/middleware/logging"; async function handler(req: NextApiRequest, res: NextApiResponse) { try { const { method } = req; if (method === "GET") { - logger.debug("METADATA BY ID API GET"); const metadata = getMetadata([ process.env.NEXT_PUBLIC_INTERNET_URL + "/api/dynamic/presentCredentialById", diff --git a/vclogin/pages/api/dynamic/getAuthResponse.ts b/vclogin/pages/api/dynamic/getAuthResponse.ts index e7216ad..4bb41c2 100644 --- a/vclogin/pages/api/dynamic/getAuthResponse.ts +++ b/vclogin/pages/api/dynamic/getAuthResponse.ts @@ -4,14 +4,12 @@ */ import { NextApiRequest, NextApiResponse } from "next"; -import { logger } from "@/config/logger"; import { redisGet } from "@/config/redis"; import { withLogging } from "@/middleware/logging"; async function handler(req: NextApiRequest, res: NextApiResponse) { //read uuid from query params const uuid = req.query["uuid"]; - logger.debug("uuid: ", uuid); // Read auth_res from redis and check if it matches the uuid diff --git a/vclogin/pages/api/dynamic/presentCredentialById.ts b/vclogin/pages/api/dynamic/presentCredentialById.ts index 065195e..34bc058 100644 --- a/vclogin/pages/api/dynamic/presentCredentialById.ts +++ b/vclogin/pages/api/dynamic/presentCredentialById.ts @@ -14,8 +14,6 @@ import { redisSet, redisGet } from "@/config/redis"; import { withLogging } from "@/middleware/logging"; const getHandler = async (req: NextApiRequest, res: NextApiResponse) => { - logger.debug("LOGIN API GET BY ID"); - // Get login_id from query const uuid = req.query["login_id"]; @@ -24,7 +22,10 @@ const getHandler = async (req: NextApiRequest, res: NextApiResponse) => { // fetch inputDescriptor from redis using uuid const inputDescriptor = await redisGet(uuid + "_inputDescriptor"); - logger.debug("inputDescriptor: ", JSON.parse(inputDescriptor!)); + logger.debug( + JSON.parse(inputDescriptor!), + "Input descriptor used by dynamic endpoint", + ); //if policy is found if (policy) { @@ -60,11 +61,9 @@ const getHandler = async (req: NextApiRequest, res: NextApiResponse) => { }; const postHandler = async (req: NextApiRequest, res: NextApiResponse) => { - logger.debug("LOGIN API POST BY ID"); - // Parse the JSON string into a JavaScript object const presentation = JSON.parse(req.body.vp_token); - logger.debug("Presentation: \n", req.body.vp_token); + logger.debug(req.body.vp_token, "Verifiable Presentation was sent"); const uuid = presentation["proof"]["challenge"]; const policy = await redisGet(uuid + "_policy"); @@ -77,14 +76,16 @@ const postHandler = async (req: NextApiRequest, res: NextApiResponse) => { // Verify the presentation and the status of the credential if (await verifyAuthenticationPresentation(presentation)) { - logger.debug("Presentation valid"); // Evaluate if the VP should be trusted if (await isTrustedPresentation(presentation, policyObject)) { - logger.debug("Presentation verified"); + logger.debug("Verifiable Presentation verified"); // Get the user claims when the presentation is trusted const userClaims = await extractClaims(presentation, policyObject); - logger.debug(userClaims); + logger.debug( + userClaims, + "Claims extracted from Verifiable Presentation", + ); // Store the authentication result in Redis redisSet(uuid + "_auth-res", "success", MAX_AGE); @@ -92,7 +93,7 @@ const postHandler = async (req: NextApiRequest, res: NextApiResponse) => { // Store the user claims in Redis redisSet(uuid + "_claims", JSON.stringify(userClaims.tokenId), MAX_AGE); } else { - logger.debug("Presentation not trusted"); + logger.debug("Verifiable Presentation not trusted"); redisSet("auth_res:" + uuid, "error_presentation_not_trused", MAX_AGE); // Wallet gets an error message @@ -100,7 +101,7 @@ const postHandler = async (req: NextApiRequest, res: NextApiResponse) => { return; } } else { - logger.debug("Presentation invalid"); + logger.debug("Verifiable Presentation invalid"); redisSet("auth_res:" + uuid, "error_invalid_presentation", MAX_AGE); res.status(500).end(); return; diff --git a/vclogin/pages/api/presentCredential.ts b/vclogin/pages/api/presentCredential.ts index db0997e..0c607e6 100644 --- a/vclogin/pages/api/presentCredential.ts +++ b/vclogin/pages/api/presentCredential.ts @@ -19,7 +19,6 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { try { const { method } = req; if (method === "GET") { - logger.error("FIRST GET"); const presentation_definition = generatePresentationDefinition( await getConfiguredLoginPolicy()!, ); @@ -79,7 +78,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { return; } } else { - logger.debug("Verifiable Presentation not valid"); + logger.debug("Verifiable Presentation invalid"); res.status(500).end(); return; } From 5a71101698f6b5cf719afdaed9353146244c5f27 Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Mon, 26 Aug 2024 17:08:18 +0200 Subject: [PATCH 83/85] First API test Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> --- README.md | 16 +- vclogin/.env.test | 1 + .../unit/pages/api/clientMetadata.test.ts | 40 +++++ vclogin/lib/getMetadata.ts | 2 +- vclogin/package-lock.json | 142 +++++++++++++++++- vclogin/package.json | 1 + vclogin/tsconfig.json | 1 + 7 files changed, 191 insertions(+), 12 deletions(-) create mode 100644 vclogin/__tests__/unit/pages/api/clientMetadata.test.ts diff --git a/README.md b/README.md index 9976bc4..8d1072d 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Credentials from a mobile wallet. But building that takes considerable time and expertise. -> [!NOTE] +> [!NOTE] > As a new feature, the bridge now supports incremental authorization. > This allows the service provider to request additional Verifiable Credentials > from the user via the bridge. Please see the @@ -196,7 +196,7 @@ dynamic API endpoints. The API is documented using Swagger, which provides a user-friendly interface to explore and test the API. -> [!NOTE] +> [!NOTE] > To access the Swagger documentation, you need to run the bridge in development mode and > navigate to `http://localhost:5002/api-docs`. @@ -204,7 +204,7 @@ To authenticate requests to the dynamic API in Swagger, you need to provide a valid API key. The API key is stored in the `.env` file in the `vclogin` folder. -> [!NOTE] +> [!NOTE] > To authenticate requests to the dynamic API in Swagger, first click on > the "Authorize" button in the top right corner of the Swagger UI. Then, enter > the API key in the "Value" field with the format `API_KEY `and click @@ -327,7 +327,7 @@ refer to the end of the previous section. ## Running Tests -This repository includes unit tests with `jest` and end-to-end tests with +This repository includes unit tests with `vitest` and end-to-end tests with `playwright`. You may run them as follows: ```bash @@ -490,18 +490,18 @@ authorization. An example of such a policy file is: ``` -> [!IMPORTANT] +> [!IMPORTANT] > Each `credentialId` should be unique across all policy objects, > and should have integer string values starting from 1, incrementing by 1 for each subsequent policy object. This helps us determine > the correct policy object to apply to the VCs. -> [!IMPORTANT] +> [!IMPORTANT] > Altough the `type` field is an optional parameter, it needs to be > present in a policy file that has multiple policy objects. This is crucial for the accurate application of policies. -> [!NOTE] +> [!NOTE] > First we reorder the policy objects in a policy file based on the `credentialId` and > then we reorder the credentials in the VP based on the `type` field from the reordered policy file. > This ensures that each credential is matched with the correct policy object. @@ -576,7 +576,7 @@ below, the first VC's `credentialSubject.id` is compared with the second VC's `credentialSubject.id` in the second policy object. -> [!IMPORTANT] +> [!IMPORTANT] > You need to correctly define the JSONPaths of the constraint > operands to be able to perform constraints check. The JSONPaths should have a > structure like `$.` when having multiple policy diff --git a/vclogin/.env.test b/vclogin/.env.test index 29bdbd0..45ed675 100644 --- a/vclogin/.env.test +++ b/vclogin/.env.test @@ -1,2 +1,3 @@ LOGIN_POLICY=./__tests__/testdata/policies/acceptAnything.json DID_KEY_JWK={"kty":"OKP","crv":"Ed25519","x":"cwa3dufHNLg8aQb2eEUqTyoM1cKQW3XnOkMkj_AAl5M","d":"me03qhLByT-NKrfXDeji-lpADSpVOKWoaMUzv5EyzKY"} +EXTERNAL_URL=http://example.com diff --git a/vclogin/__tests__/unit/pages/api/clientMetadata.test.ts b/vclogin/__tests__/unit/pages/api/clientMetadata.test.ts new file mode 100644 index 0000000..9244a9d --- /dev/null +++ b/vclogin/__tests__/unit/pages/api/clientMetadata.test.ts @@ -0,0 +1,40 @@ +/** + * Copyright 2024 Software Engineering for Business Information Systems (sebis) . + * SPDX-License-Identifier: MIT + */ + +import { describe, it, expect } from "vitest"; +import { createMocks } from "node-mocks-http"; +import handler from "@/api/clientMetadata"; +import type { NextApiRequest, NextApiResponse } from "next"; + +describe("/api/clientMetadata", () => { + const mockRequest = () => { + const { req, res } = createMocks({ + method: "GET", + url: "api/clientMetadata", + }); + return { req, res }; + }; + + it("returns 200 OK", async () => { + const { req, res } = mockRequest(); + + await handler(req, res); + + expect(res.statusCode).toBe(200); + }); + + it("returns valid JSON", async () => { + const { req, res } = mockRequest(); + + await handler(req, res); + + expect(res._isJSON()).toBe(true); + expect(res._getJSONData()).toEqual( + expect.objectContaining({ + client_name: "SSI-to-OIDC Bridge", + }), + ); + }); +}); diff --git a/vclogin/lib/getMetadata.ts b/vclogin/lib/getMetadata.ts index 530ad77..991ad08 100644 --- a/vclogin/lib/getMetadata.ts +++ b/vclogin/lib/getMetadata.ts @@ -43,7 +43,7 @@ export const getMetadata = (redirect_uris: string[]) => { ], subject_trust_frameworks_supported: ["ebsi"], id_token_types_supported: ["subject_signed_id_token"], - client_name: "VP Login Service", + client_name: "SSI-to-OIDC Bridge", request_uri_parameter_supported: true, request_parameter_supported: false, redirect_uris, diff --git a/vclogin/package-lock.json b/vclogin/package-lock.json index 1ded84c..183445a 100644 --- a/vclogin/package-lock.json +++ b/vclogin/package-lock.json @@ -47,6 +47,7 @@ "eslint": "8.38.0", "eslint-config-next": "13.3.0", "eslint-plugin-react-hooks": "^4.6.0", + "node-mocks-http": "^1.15.1", "postcss": "^8.4.39", "prettier": "^2.8.8", "swagger-ui": "^5.17.14", @@ -2615,10 +2616,11 @@ "dev": true }, "node_modules/@types/express": { - "version": "4.17.17", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", - "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", @@ -3217,6 +3219,20 @@ "node": ">=6.5" } }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { "version": "8.12.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz", @@ -4448,6 +4464,19 @@ "integrity": "sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==", "dev": true }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cookie": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", @@ -4867,6 +4896,16 @@ "node": ">=0.10" } }, + "node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/detect-libc": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", @@ -6107,6 +6146,16 @@ "tslib": "^2.1.0" } }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -7714,6 +7763,16 @@ "node": ">= 0.6" } }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -7729,6 +7788,16 @@ "node": ">= 8" } }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/micromatch": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", @@ -7743,6 +7812,19 @@ "node": ">=8.6" } }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -7933,6 +8015,16 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/next": { "version": "14.2.4", "resolved": "https://registry.npmjs.org/next/-/next-14.2.4.tgz", @@ -8183,6 +8275,30 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/node-mocks-http": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/node-mocks-http/-/node-mocks-http-1.15.1.tgz", + "integrity": "sha512-X/GpUpNNiPDYUeUD183W8V4OW6OHYWI29w/QDyb+c/GzOfVEAlo6HjbW9++eXT2aV2lGg+uS+XqTD2q0pNREQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "^4.17.21", + "@types/node": "*", + "accepts": "^1.3.7", + "content-disposition": "^0.5.3", + "depd": "^1.1.0", + "fresh": "^0.5.2", + "merge-descriptors": "^1.0.1", + "methods": "^1.1.2", + "mime": "^1.3.4", + "parseurl": "^1.3.3", + "range-parser": "^1.2.0", + "type-is": "^1.6.18" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/node-releases": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", @@ -8515,6 +8631,16 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -9224,6 +9350,16 @@ "safe-buffer": "^5.1.0" } }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", diff --git a/vclogin/package.json b/vclogin/package.json index 9b0160b..6167003 100644 --- a/vclogin/package.json +++ b/vclogin/package.json @@ -51,6 +51,7 @@ "eslint": "8.38.0", "eslint-config-next": "13.3.0", "eslint-plugin-react-hooks": "^4.6.0", + "node-mocks-http": "^1.15.1", "postcss": "^8.4.39", "prettier": "^2.8.8", "swagger-ui": "^5.17.14", diff --git a/vclogin/tsconfig.json b/vclogin/tsconfig.json index 2029343..3abb003 100644 --- a/vclogin/tsconfig.json +++ b/vclogin/tsconfig.json @@ -18,6 +18,7 @@ "paths": { "@/testdata/*": ["__tests__/testdata/*"], "@/lib/*": ["lib/*"], + "@/api/*": ["pages/api/*"], "@/config/*": ["config/*"], "@/types/*": ["types/*"], "@/pages/*": ["pages/*"], From dffdcdc906844154cd4fb479c412be3c825e21d5 Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Fri, 30 Aug 2024 17:02:45 +0200 Subject: [PATCH 84/85] Version bump Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> --- vclogin/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vclogin/package.json b/vclogin/package.json index 6167003..ca21fe3 100644 --- a/vclogin/package.json +++ b/vclogin/package.json @@ -1,6 +1,6 @@ { "name": "ssi-to-oidc-bridge", - "version": "1.2.0", + "version": "1.3.0", "private": true, "type": "module", "scripts": { From c39c3e6c2a70c284f2b029945ad16bd3c07524c1 Mon Sep 17 00:00:00 2001 From: Felix Hoops <9974641+jfelixh@users.noreply.github.com> Date: Fri, 30 Aug 2024 19:01:37 +0200 Subject: [PATCH 85/85] Fixed test env loading Signed-off-by: Felix Hoops <9974641+jfelixh@users.noreply.github.com> --- .../unit/pages/api/presentCredential.test.ts | 76 +++ vclogin/package-lock.json | 607 ++++++++---------- vclogin/package.json | 4 +- vclogin/pages/api/presentCredential.ts | 8 +- vclogin/vite.config.ts | 33 +- 5 files changed, 353 insertions(+), 375 deletions(-) create mode 100644 vclogin/__tests__/unit/pages/api/presentCredential.test.ts diff --git a/vclogin/__tests__/unit/pages/api/presentCredential.test.ts b/vclogin/__tests__/unit/pages/api/presentCredential.test.ts new file mode 100644 index 0000000..8353e4b --- /dev/null +++ b/vclogin/__tests__/unit/pages/api/presentCredential.test.ts @@ -0,0 +1,76 @@ +/** + * Copyright 2024 Software Engineering for Business Information Systems (sebis) . + * SPDX-License-Identifier: MIT + */ + +import { describe, it, expect } from "vitest"; +import { RequestMethod, createMocks } from "node-mocks-http"; +import handler from "@/api/presentCredential"; +import type { NextApiRequest, NextApiResponse } from "next"; +import * as jose from "jose"; +import { keyToDID, keyToVerificationMethod } from "@spruceid/didkit-wasm-node"; + +describe("api/test/presentCredential", () => { + const mockRequest = (method: RequestMethod) => { + const { req, res } = createMocks({ + method: method, + url: "api/presentCredential", + query: { + login_challenge: "testchallenge", + }, + }); + return { req, res }; + }; + + it("returns 200 OK", async () => { + const { req, res } = mockRequest("GET"); + + await handler(req, res); + + expect(res.statusCode).toBe(200); + }); + + it("returns valid JWT", async () => { + const { req, res } = mockRequest("GET"); + + await handler(req, res); + + const did = keyToDID("key", process.env.DID_KEY_JWK!); + const publicKey = await jose.importJWK( + JSON.parse(process.env.DID_KEY_JWK!), + "EdDSA", + ); + const verificationMethod = await keyToVerificationMethod( + "key", + process.env.DID_KEY_JWK!, + ); + const { payload, protectedHeader } = await jose.jwtVerify( + res._getData(), + publicKey, + { + issuer: did, + audience: "https://self-issued.me/v2", + }, + ); + + expect(payload).toEqual( + expect.objectContaining({ + iss: did, + client_id: did, + client_id_scheme: "did", + client_metadata_uri: process.env.EXTERNAL_URL + "/api/clientMetadata", + response_mode: "direct_post", + response_type: "vp_token", + response_uri: process.env.EXTERNAL_URL + "/api/presentCredential", + }), + ); + + expect(protectedHeader).toEqual({ + alg: "EdDSA", + kid: verificationMethod, + typ: "oauth-authz-req+jwt", + }); + + // check that the payload contains a presentation_definition member + }); +}); diff --git a/vclogin/package-lock.json b/vclogin/package-lock.json index 183445a..0e53898 100644 --- a/vclogin/package-lock.json +++ b/vclogin/package-lock.json @@ -1,12 +1,12 @@ { "name": "ssi-to-oidc-bridge", - "version": "1.2.0", + "version": "1.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ssi-to-oidc-bridge", - "version": "1.2.0", + "version": "1.3.0", "dependencies": { "@material-tailwind/react": "^2.0.3", "@ory/hydra-client": "^2.2.0", @@ -41,7 +41,7 @@ "@types/swagger-ui-react": "^4.18.3", "@types/ua-parser-js": "^0.7.39", "@types/uuid": "^9.0.1", - "@vitest/coverage-v8": "^1.6.0", + "@vitest/coverage-v8": "^2.0.5", "@wasm-tool/wasm-pack-plugin": "^1.7.0", "autoprefixer": "10.4.14", "eslint": "8.38.0", @@ -56,7 +56,7 @@ "typescript": "5.0.4", "vite": "^5.3.1", "vite-tsconfig-paths": "^4.3.2", - "vitest": "^1.6.0", + "vitest": "^2.0.5", "whatwg-fetch": "^3.6.19" } }, @@ -74,13 +74,14 @@ } }, "node_modules/@ampproject/remapping": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", - "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" @@ -972,31 +973,21 @@ "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" @@ -1012,10 +1003,11 @@ } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.0.0" } @@ -1609,12 +1601,6 @@ "node": ">=16" } }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true - }, "node_modules/@sphereon/pex": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/@sphereon/pex/-/pex-3.3.3.tgz", @@ -2916,206 +2902,119 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.6.0.tgz", - "integrity": "sha512-KvapcbMY/8GYIG0rlwwOKCVNRc0OL20rrhFkg/CHNzncV03TE2XWvO5w9uZYoxNiMEBacAJt3unSOiZ7svePew==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.0.5.tgz", + "integrity": "sha512-qeFcySCg5FLO2bHHSa0tAZAOnAUbp4L6/A5JDuj9+bt53JREl8hpLjLHEWF0e/gWc8INVpJaqA7+Ene2rclpZg==", "dev": true, + "license": "MIT", "dependencies": { - "@ampproject/remapping": "^2.2.1", + "@ampproject/remapping": "^2.3.0", "@bcoe/v8-coverage": "^0.2.3", - "debug": "^4.3.4", + "debug": "^4.3.5", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", - "istanbul-lib-source-maps": "^5.0.4", - "istanbul-reports": "^3.1.6", - "magic-string": "^0.30.5", - "magicast": "^0.3.3", - "picocolors": "^1.0.0", - "std-env": "^3.5.0", - "strip-literal": "^2.0.0", - "test-exclude": "^6.0.0" + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magic-string": "^0.30.10", + "magicast": "^0.3.4", + "std-env": "^3.7.0", + "test-exclude": "^7.0.1", + "tinyrainbow": "^1.2.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "1.6.0" - } - }, - "node_modules/@vitest/coverage-v8/node_modules/istanbul-lib-source-maps": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.4.tgz", - "integrity": "sha512-wHOoEsNJTVltaJp8eVkm8w+GVkVNHT2YDYo53YdzQEL2gWm1hBX5cGFR9hQJtuGLebidVX7et3+dmDZrmclduw==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.23", - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0" - }, - "engines": { - "node": ">=10" + "vitest": "2.0.5" } }, "node_modules/@vitest/expect": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.0.tgz", - "integrity": "sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.0.5.tgz", + "integrity": "sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/spy": "1.6.0", - "@vitest/utils": "1.6.0", - "chai": "^4.3.10" + "@vitest/spy": "2.0.5", + "@vitest/utils": "2.0.5", + "chai": "^5.1.1", + "tinyrainbow": "^1.2.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/runner": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.0.tgz", - "integrity": "sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==", + "node_modules/@vitest/pretty-format": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.0.5.tgz", + "integrity": "sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/utils": "1.6.0", - "p-limit": "^5.0.0", - "pathe": "^1.1.1" + "tinyrainbow": "^1.2.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/runner/node_modules/p-limit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", - "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "node_modules/@vitest/runner": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.0.5.tgz", + "integrity": "sha512-TfRfZa6Bkk9ky4tW0z20WKXFEwwvWhRY+84CnSEtq4+3ZvDlJyY32oNTJtM7AW9ihW90tX/1Q78cb6FjoAs+ig==", "dev": true, + "license": "MIT", "dependencies": { - "yocto-queue": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@vitest/runner/node_modules/yocto-queue": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", - "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", - "dev": true, - "engines": { - "node": ">=12.20" + "@vitest/utils": "2.0.5", + "pathe": "^1.1.2" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/snapshot": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.0.tgz", - "integrity": "sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.0.5.tgz", + "integrity": "sha512-SgCPUeDFLaM0mIUHfaArq8fD2WbaXG/zVXjRupthYfYGzc8ztbFbu6dUNOblBG7XLMR1kEhS/DNnfCZ2IhdDew==", "dev": true, + "license": "MIT", "dependencies": { - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "pretty-format": "^29.7.0" + "@vitest/pretty-format": "2.0.5", + "magic-string": "^0.30.10", + "pathe": "^1.1.2" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/snapshot/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@vitest/snapshot/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@vitest/snapshot/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true - }, "node_modules/@vitest/spy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.0.tgz", - "integrity": "sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.0.5.tgz", + "integrity": "sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==", "dev": true, + "license": "MIT", "dependencies": { - "tinyspy": "^2.2.0" + "tinyspy": "^3.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.0.tgz", - "integrity": "sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.0.5.tgz", + "integrity": "sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==", "dev": true, + "license": "MIT", "dependencies": { - "diff-sequences": "^29.6.3", + "@vitest/pretty-format": "2.0.5", "estree-walker": "^3.0.3", - "loupe": "^2.3.7", - "pretty-format": "^29.7.0" + "loupe": "^3.1.1", + "tinyrainbow": "^1.2.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/utils/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@vitest/utils/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@vitest/utils/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true - }, "node_modules/@wasm-tool/wasm-pack-plugin": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/@wasm-tool/wasm-pack-plugin/-/wasm-pack-plugin-1.7.0.tgz", @@ -3271,6 +3170,8 @@ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", "dev": true, + "optional": true, + "peer": true, "engines": { "node": ">=0.4.0" } @@ -3680,12 +3581,13 @@ } }, "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, + "license": "MIT", "engines": { - "node": "*" + "node": ">=12" } }, "node_modules/ast-types-flow": { @@ -4113,6 +4015,7 @@ "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -4181,21 +4084,20 @@ ] }, "node_modules/chai": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", - "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", + "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", "dev": true, + "license": "MIT", "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.3", - "deep-eql": "^4.1.3", - "get-func-name": "^2.0.2", - "loupe": "^2.3.6", - "pathval": "^1.1.1", - "type-detect": "^4.0.8" + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" }, "engines": { - "node": ">=4" + "node": ">=12" } }, "node_modules/chalk": { @@ -4245,15 +4147,13 @@ } }, "node_modules/check-error": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", - "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", "dev": true, - "dependencies": { - "get-func-name": "^2.0.2" - }, + "license": "MIT", "engines": { - "node": "*" + "node": ">= 16" } }, "node_modules/chokidar": { @@ -4458,12 +4358,6 @@ "safe-buffer": "~5.1.0" } }, - "node_modules/confbox": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.7.tgz", - "integrity": "sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==", - "dev": true - }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -4732,9 +4626,10 @@ } }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "license": "MIT", "dependencies": { "ms": "2.1.2" }, @@ -4780,13 +4675,11 @@ } }, "node_modules/deep-eql": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", - "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", "dev": true, - "dependencies": { - "type-detect": "^4.0.0" - }, + "license": "MIT", "engines": { "node": ">=6" } @@ -4922,15 +4815,6 @@ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", "dev": true }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/dijkstrajs": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", @@ -5841,6 +5725,7 @@ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, + "license": "MIT", "dependencies": { "@types/estree": "^1.0.0" } @@ -6231,6 +6116,7 @@ "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", "dev": true, + "license": "MIT", "engines": { "node": "*" } @@ -6593,7 +6479,8 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/http-proxy-agent": { "version": "5.0.0", @@ -7237,6 +7124,7 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=8" } @@ -7246,6 +7134,7 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", @@ -7255,11 +7144,27 @@ "node": ">=10" } }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/istanbul-reports": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", - "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" @@ -7576,22 +7481,6 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, - "node_modules/local-pkg": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz", - "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==", - "dev": true, - "dependencies": { - "mlly": "^1.4.2", - "pkg-types": "^1.0.3" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -7671,10 +7560,11 @@ } }, "node_modules/loupe": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", - "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz", + "integrity": "sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==", "dev": true, + "license": "MIT", "dependencies": { "get-func-name": "^2.0.1" } @@ -7730,6 +7620,7 @@ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, + "license": "MIT", "dependencies": { "semver": "^7.5.3" }, @@ -7926,18 +7817,6 @@ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", "dev": true }, - "node_modules/mlly": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.1.tgz", - "integrity": "sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==", - "dev": true, - "dependencies": { - "acorn": "^8.11.3", - "pathe": "^1.1.2", - "pkg-types": "^1.1.1", - "ufo": "^1.5.3" - } - }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -8710,15 +8589,17 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", "dev": true, + "license": "MIT", "engines": { - "node": "*" + "node": ">= 14.16" } }, "node_modules/picocolors": { @@ -8819,17 +8700,6 @@ "node": ">= 6" } }, - "node_modules/pkg-types": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.1.1.tgz", - "integrity": "sha512-ko14TjmDuQJ14zsotODv7dBlwxKhUKQEhuhmbqo1uCi9BB0Z2alo/wAXg6q1dTR5TyuqYyWhjtfe/Tsh+X28jQ==", - "dev": true, - "dependencies": { - "confbox": "^0.1.7", - "mlly": "^1.7.0", - "pathe": "^1.1.2" - } - }, "node_modules/playwright": { "version": "1.44.1", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.44.1.tgz", @@ -10117,7 +9987,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/signal-exit": { "version": "3.0.7", @@ -10279,7 +10150,8 @@ "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/standard-as-callback": { "version": "2.1.0", @@ -10592,24 +10464,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strip-literal": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.0.tgz", - "integrity": "sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==", - "dev": true, - "dependencies": { - "js-tokens": "^9.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/strip-literal/node_modules/js-tokens": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.0.tgz", - "integrity": "sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==", - "dev": true - }, "node_modules/style-value-types": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/style-value-types/-/style-value-types-5.0.0.tgz", @@ -11031,17 +10885,65 @@ } }, "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", "dev": true, + "license": "ISC", "dependencies": { "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" + "glob": "^10.4.1", + "minimatch": "^9.0.4" }, "engines": { - "node": ">=8" + "node": ">=18" + } + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/testcontainers": { @@ -11120,19 +11022,31 @@ "dev": true }, "node_modules/tinypool": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", - "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.1.tgz", + "integrity": "sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==", "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "license": "MIT", "engines": { "node": ">=14.0.0" } }, "node_modules/tinyspy": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", - "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.0.tgz", + "integrity": "sha512-q5nmENpTHgiPVd1cJDDc9cVoYN5x4vCvwT3FMilvKPKneCBZAxn2YWQjDF0UMcE9k0Cay1gBiDfTMU0g+mPMQA==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.0.0" } @@ -11361,15 +11275,6 @@ "node": ">= 0.8.0" } }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -11535,12 +11440,6 @@ "node": "*" } }, - "node_modules/ufo": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.3.tgz", - "integrity": "sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==", - "dev": true - }, "node_modules/uint8arrays": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.1.tgz", @@ -11719,15 +11618,16 @@ } }, "node_modules/vite-node": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.0.tgz", - "integrity": "sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.0.5.tgz", + "integrity": "sha512-LdsW4pxj0Ot69FAoXZ1yTnA9bjGohr2yNBU7QKRxpz8ITSkhuDl6h3zS/tvgz4qrNjeRnvrWeXQ8ZF7Um4W00Q==", "dev": true, + "license": "MIT", "dependencies": { "cac": "^6.7.14", - "debug": "^4.3.4", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", + "debug": "^4.3.5", + "pathe": "^1.1.2", + "tinyrainbow": "^1.2.0", "vite": "^5.0.0" }, "bin": { @@ -11774,31 +11674,31 @@ } }, "node_modules/vitest": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.0.tgz", - "integrity": "sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.0.5.tgz", + "integrity": "sha512-8GUxONfauuIdeSl5f9GTgVEpg5BTOlplET4WEDaeY2QBiN8wSm68vxN/tb5z405OwppfoCavnwXafiaYBC/xOA==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/expect": "1.6.0", - "@vitest/runner": "1.6.0", - "@vitest/snapshot": "1.6.0", - "@vitest/spy": "1.6.0", - "@vitest/utils": "1.6.0", - "acorn-walk": "^8.3.2", - "chai": "^4.3.10", - "debug": "^4.3.4", + "@ampproject/remapping": "^2.3.0", + "@vitest/expect": "2.0.5", + "@vitest/pretty-format": "^2.0.5", + "@vitest/runner": "2.0.5", + "@vitest/snapshot": "2.0.5", + "@vitest/spy": "2.0.5", + "@vitest/utils": "2.0.5", + "chai": "^5.1.1", + "debug": "^4.3.5", "execa": "^8.0.1", - "local-pkg": "^0.5.0", - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "std-env": "^3.5.0", - "strip-literal": "^2.0.0", - "tinybench": "^2.5.1", - "tinypool": "^0.8.3", + "magic-string": "^0.30.10", + "pathe": "^1.1.2", + "std-env": "^3.7.0", + "tinybench": "^2.8.0", + "tinypool": "^1.0.0", + "tinyrainbow": "^1.2.0", "vite": "^5.0.0", - "vite-node": "1.6.0", - "why-is-node-running": "^2.2.2" + "vite-node": "2.0.5", + "why-is-node-running": "^2.3.0" }, "bin": { "vitest": "vitest.mjs" @@ -11812,8 +11712,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "1.6.0", - "@vitest/ui": "1.6.0", + "@vitest/browser": "2.0.5", + "@vitest/ui": "2.0.5", "happy-dom": "*", "jsdom": "*" }, @@ -12174,10 +12074,11 @@ } }, "node_modules/why-is-node-running": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", - "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, + "license": "MIT", "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" diff --git a/vclogin/package.json b/vclogin/package.json index ca21fe3..4746a01 100644 --- a/vclogin/package.json +++ b/vclogin/package.json @@ -45,7 +45,7 @@ "@types/swagger-ui-react": "^4.18.3", "@types/ua-parser-js": "^0.7.39", "@types/uuid": "^9.0.1", - "@vitest/coverage-v8": "^1.6.0", + "@vitest/coverage-v8": "^2.0.5", "@wasm-tool/wasm-pack-plugin": "^1.7.0", "autoprefixer": "10.4.14", "eslint": "8.38.0", @@ -60,7 +60,7 @@ "typescript": "5.0.4", "vite": "^5.3.1", "vite-tsconfig-paths": "^4.3.2", - "vitest": "^1.6.0", + "vitest": "^2.0.5", "whatwg-fetch": "^3.6.19" }, "browser": { diff --git a/vclogin/pages/api/presentCredential.ts b/vclogin/pages/api/presentCredential.ts index 0c607e6..07dfcc6 100644 --- a/vclogin/pages/api/presentCredential.ts +++ b/vclogin/pages/api/presentCredential.ts @@ -47,7 +47,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { .setProtectedHeader({ alg: "EdDSA", kid: verificationMethod, - typ: "JWT", + typ: "oauth-authz-req+jwt", }) .setIssuedAt() .setIssuer(did) @@ -58,10 +58,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { logger.error(err, "Failed signing presentation definition token"); res.status(500).end(); }); - res - .status(200) - .appendHeader("Content-Type", "application/oauth-authz-req+jwt") - .send(token); + res.status(200); + res.send(token); } else if (method === "POST") { // Parse the JSON string into a JavaScript object const presentation = JSON.parse(req.body.vp_token); diff --git a/vclogin/vite.config.ts b/vclogin/vite.config.ts index 05d2f78..8a11cbd 100644 --- a/vclogin/vite.config.ts +++ b/vclogin/vite.config.ts @@ -1,21 +1,24 @@ /// -import { defineConfig } from "vite"; +import { loadEnv, defineConfig } from "vite"; import tsconfigPaths from "vite-tsconfig-paths"; import { coverageConfigDefaults } from "vitest/config"; -export default defineConfig({ - plugins: [tsconfigPaths()], - test: { - globals: true, - setupFiles: ["./__tests__/unit/testSetupFile.ts"], - include: ["**/__tests__/**/*.test.ts"], - coverage: { - exclude: [ - "*.config.?(c|m)[jt]s", - "pages/*.tsx", - "pages/common/*.tsx", - ...coverageConfigDefaults.exclude, - ], +export default defineConfig(({ mode }) => { + return { + plugins: [tsconfigPaths()], + test: { + globals: true, + env: loadEnv(mode, process.cwd(), ""), + setupFiles: ["./__tests__/unit/testSetupFile.ts"], + include: ["**/__tests__/**/*.test.ts"], + coverage: { + exclude: [ + "*.config.?(c|m)[jt]s", + "pages/*.tsx", + "pages/common/*.tsx", + ...coverageConfigDefaults.exclude, + ], + }, }, - }, + }; });