From 7fba932f56b3bcfdf30d3a1d5a01f6b3e903d028 Mon Sep 17 00:00:00 2001 From: Niels Klomp Date: Mon, 22 Jan 2024 06:53:23 +0100 Subject: [PATCH] chore: working towards strict mode enabled for SIOP --- jest.config.cjs | 38 ++++ jest.json | 1 + package.json | 2 +- packages/callback-example/tsconfig.json | 12 +- packages/client/lib/AccessTokenClient.ts | 4 +- packages/client/lib/CredentialOfferClient.ts | 4 +- packages/client/lib/OpenID4VCIClient.ts | 10 +- .../lib/__tests__/AuthzFlowType.spec.ts | 2 +- .../__tests__/CredentialRequestClient.spec.ts | 14 +- .../lib/__tests__/MetadataClient.spec.ts | 4 +- packages/client/tsconfig.json | 8 +- packages/common/tsconfig.json | 4 +- .../issuer-rest/lib/IssuerTokenEndpoint.ts | 13 +- .../lib/__tests__/ClientIssuerIT.spec.ts | 20 +- packages/issuer-rest/tsconfig.json | 10 +- .../lib/functions/CredentialOfferUtils.ts | 2 + packages/issuer/tsconfig.json | 8 +- packages/siopv2/README.md | 2 +- .../AuthenticationRequest.request.spec.ts | 0 .../AuthenticationRequest.verify.spec.ts | 0 .../AuthenticationResponse.response.spec.ts | 0 .../AuthenticationResponse.verify.spec.ts | 0 .../{test => __tests__}/DocumentLoader.ts | 0 .../HttpUtils.fetch.spec.ts | 0 .../siopv2/{test => __tests__}/IT.spec.ts | 0 .../{test => __tests__}/OP.request.spec.ts | 0 .../PresentationExchange.spec.ts | 0 .../{test => __tests__}/RP.request.spec.ts | 0 .../siopv2/{test => __tests__}/SdJwt.spec.ts | 2 +- .../siopv2/{test => __tests__}/TestUtils.ts | 2 +- .../{test => __tests__}/data/mockedData.ts | 0 .../e2e/mattr.launchpad.spec.ts | 0 .../functions/DidJWT.spec.ts | 0 .../functions/DidSiopMetadata.spec.ts | 0 .../functions/Encodings.spec.ts | 0 .../functions/LanguageTagUtils.spec.ts | 0 .../functions/LinkedDomainValidations.spec.ts | 0 .../interop/auth0/auth0.spec.ts | 0 .../interop/auth0/fixtures.ts | 0 .../interop/mattr/fixtures.ts | 0 .../siopv2/{test => __tests__}/modules.d.ts | 0 .../regressions/ClientIdIsObject.spec.ts | 0 .../jwtVCPresentationProfile.spec.ts | 2 +- packages/siopv2/jest.config.cjs | 27 --- packages/siopv2/package.json | 25 ++- .../AuthorizationRequest.ts | 37 ++-- .../siopv2/src/authorization-request/Opts.ts | 6 +- .../src/authorization-request/Payload.ts | 34 ++- .../RequestRegistration.ts | 4 +- .../siopv2/src/authorization-request/URI.ts | 87 +++++--- .../AuthorizationResponse.ts | 2 +- .../src/authorization-response/OpenID4VP.ts | 4 +- .../src/authorization-response/Payload.ts | 2 +- .../PresentationExchange.ts | 29 ++- packages/siopv2/src/did/DIDResolution.ts | 12 +- packages/siopv2/src/did/DidJWT.ts | 206 ++++++++++-------- .../siopv2/src/did/LinkedDomainValidations.ts | 10 +- packages/siopv2/src/helpers/Encodings.ts | 11 +- packages/siopv2/src/helpers/HttpUtils.ts | 19 +- packages/siopv2/src/helpers/Keys.ts | 4 +- .../siopv2/src/helpers/LanguageTagUtils.ts | 6 +- packages/siopv2/src/helpers/Metadata.ts | 138 ++++++------ packages/siopv2/src/helpers/ObjectUtils.ts | 8 +- packages/siopv2/src/helpers/State.ts | 15 +- packages/siopv2/src/id-token/IDToken.ts | 4 +- packages/siopv2/src/op/Opts.ts | 4 +- .../src/request-object/RequestObject.ts | 2 +- packages/siopv2/tsconfig.build.json | 2 +- packages/siopv2/tsconfig.json | 37 ++-- packages/tsconfig.json | 16 +- pnpm-lock.yaml | 13 +- 71 files changed, 544 insertions(+), 384 deletions(-) create mode 100644 jest.config.cjs rename packages/siopv2/{test => __tests__}/AuthenticationRequest.request.spec.ts (100%) rename packages/siopv2/{test => __tests__}/AuthenticationRequest.verify.spec.ts (100%) rename packages/siopv2/{test => __tests__}/AuthenticationResponse.response.spec.ts (100%) rename packages/siopv2/{test => __tests__}/AuthenticationResponse.verify.spec.ts (100%) rename packages/siopv2/{test => __tests__}/DocumentLoader.ts (100%) rename packages/siopv2/{test => __tests__}/HttpUtils.fetch.spec.ts (100%) rename packages/siopv2/{test => __tests__}/IT.spec.ts (100%) rename packages/siopv2/{test => __tests__}/OP.request.spec.ts (100%) rename packages/siopv2/{test => __tests__}/PresentationExchange.spec.ts (100%) rename packages/siopv2/{test => __tests__}/RP.request.spec.ts (100%) rename packages/siopv2/{test => __tests__}/SdJwt.spec.ts (99%) rename packages/siopv2/{test => __tests__}/TestUtils.ts (99%) rename packages/siopv2/{test => __tests__}/data/mockedData.ts (100%) rename packages/siopv2/{test => __tests__}/e2e/mattr.launchpad.spec.ts (100%) rename packages/siopv2/{test => __tests__}/functions/DidJWT.spec.ts (100%) rename packages/siopv2/{test => __tests__}/functions/DidSiopMetadata.spec.ts (100%) rename packages/siopv2/{test => __tests__}/functions/Encodings.spec.ts (100%) rename packages/siopv2/{test => __tests__}/functions/LanguageTagUtils.spec.ts (100%) rename packages/siopv2/{test => __tests__}/functions/LinkedDomainValidations.spec.ts (100%) rename packages/siopv2/{test => __tests__}/interop/auth0/auth0.spec.ts (100%) rename packages/siopv2/{test => __tests__}/interop/auth0/fixtures.ts (100%) rename packages/siopv2/{test => __tests__}/interop/mattr/fixtures.ts (100%) rename packages/siopv2/{test => __tests__}/modules.d.ts (100%) rename packages/siopv2/{test => __tests__}/regressions/ClientIdIsObject.spec.ts (100%) rename packages/siopv2/{test => __tests__}/spec-compliance/jwtVCPresentationProfile.spec.ts (99%) delete mode 100644 packages/siopv2/jest.config.cjs diff --git a/jest.config.cjs b/jest.config.cjs new file mode 100644 index 00000000..8c3ccbb5 --- /dev/null +++ b/jest.config.cjs @@ -0,0 +1,38 @@ +module.exports = { + preset: "ts-jest", + testEnvironment: "node", + moduleNameMapper: { + "^jose/(.*)$": "/node_modules/.pnpm/jose@4.15.4/node_modules/jose/dist/node/cjs/$1", + }, + rootDir: ".", + // roots: ["/src/", "/test/"], + testMatch: ["**/?(*.)+(spec|test).+(ts|tsx|js)"], + transform: { + "^.+\\.(ts|tsx)?$": "ts-jest", + }, + moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json"], + coverageDirectory: "./coverage/", + collectCoverageFrom: [ + "packages/**/src/**/*.ts", + "packages/**/lib/**/*.ts", + "!**/examples/**", + "!packages/cli/**", + "!**/types/**", + "!**/dist/**", + "!**/coverage/**", + "!**/node_modules/**/__tests__/**", + "!**/node_modules/**/*.test.ts", + "!**/node_modules/**", + "!**/packages/**/index.ts", + "!**/src/schemas/**", + "!**/src/**/*.d.ts", + "!jest.config.cjs", + "!**/generator/**", + "!index.ts", + + ], + collectCoverage: true, + reporters: ["default", ["jest-junit", { outputDirectory: "./coverage" }]], + "automock": false, + "verbose": true +}; diff --git a/jest.json b/jest.json index 6abdc3d4..5e30480b 100644 --- a/jest.json +++ b/jest.json @@ -1,6 +1,7 @@ { "preset": "ts-jest", "moduleFileExtensions": [ + "js", "ts", "tsx", "js", diff --git a/package.json b/package.json index 2fd5164f..d06436fa 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "build": "pnpm -r --stream build", "build:clean": "lerna clean -y && pnpm install && lerna run build:clean --concurrency 1", "test:ci": "jest --config=jest.json", - "test": "jest --verbose --config=jest.json --coverage=true --detectOpenHandles", + "test": "jest --verbose --config=jest.json --coverage=true --detectOpenHandles --maxWorkers=1", "clean": "rimraf --glob **/dist **/coverage **/pnpm-lock.yaml packages/**/node_modules node_modules packages/**/tsconfig.tsbuildinfo", "publish:latest": "lerna publish --conventional-commits --include-merged-tags --create-release github --yes --dist-tag latest --registry https://registry.npmjs.org", "publish:next": "lerna publish --conventional-prerelease --force-publish --canary --no-git-tag-version --include-merged-tags --preid next --pre-dist-tag next --yes --registry https://registry.npmjs.org", diff --git a/packages/callback-example/tsconfig.json b/packages/callback-example/tsconfig.json index 76734c06..a7b6eff2 100644 --- a/packages/callback-example/tsconfig.json +++ b/packages/callback-example/tsconfig.json @@ -3,17 +3,17 @@ "compilerOptions": { "rootDir": "./lib", "outDir": "./dist", - "declarationDir": "./dist" + "declarationDir": "./dist", }, "references": [ { - "path": "../common" + "path": "../common", }, { - "path": "../client" + "path": "../client", }, { - "path": "../issuer" - } - ] + "path": "../issuer", + }, + ], } diff --git a/packages/client/lib/AccessTokenClient.ts b/packages/client/lib/AccessTokenClient.ts index 01984f26..f35cbd00 100644 --- a/packages/client/lib/AccessTokenClient.ts +++ b/packages/client/lib/AccessTokenClient.ts @@ -76,8 +76,8 @@ export class AccessTokenClient { metadata: metadata ? metadata : issuerOpts?.fetchMetadata - ? await MetadataClient.retrieveAllMetadata(issuerOpts.issuer, { errorOnNotFound: false }) - : undefined, + ? await MetadataClient.retrieveAllMetadata(issuerOpts.issuer, { errorOnNotFound: false }) + : undefined, }); return this.sendAuthCode(requestTokenURL, accessTokenRequest); diff --git a/packages/client/lib/CredentialOfferClient.ts b/packages/client/lib/CredentialOfferClient.ts index 6884a69f..80c2458d 100644 --- a/packages/client/lib/CredentialOfferClient.ts +++ b/packages/client/lib/CredentialOfferClient.ts @@ -99,8 +99,8 @@ export class CredentialOfferClient { uriTypeProperties: isUri ? ['credential_offer_uri'] : version >= OpenId4VCIVersion.VER_1_0_11 - ? ['credential_issuer', 'credential_type'] - : ['issuer', 'credential_type'], + ? ['credential_issuer', 'credential_type'] + : ['issuer', 'credential_type'], param, version, }); diff --git a/packages/client/lib/OpenID4VCIClient.ts b/packages/client/lib/OpenID4VCIClient.ts index a1b37eda..2904bbfb 100644 --- a/packages/client/lib/OpenID4VCIClient.ts +++ b/packages/client/lib/OpenID4VCIClient.ts @@ -340,8 +340,9 @@ export class OpenID4VCIClient { } else if (!response.successBody) { debug(`Access token error. No success body`); throw Error( - `Retrieving an access token from ${this._endpointMetadata - ?.token_endpoint} for issuer ${this.getIssuer()} failed as there was no success response body`, + `Retrieving an access token from ${ + this._endpointMetadata?.token_endpoint + } for issuer ${this.getIssuer()} failed as there was no success response body`, ); } this._accessTokenResponse = response.successBody; @@ -459,8 +460,9 @@ export class OpenID4VCIClient { } else if (!response.successBody) { debug(`Credential request error. No success body`); throw Error( - `Retrieving a credential from ${this._endpointMetadata - ?.credential_endpoint} for issuer ${this.getIssuer()} failed as there was no success response body`, + `Retrieving a credential from ${ + this._endpointMetadata?.credential_endpoint + } for issuer ${this.getIssuer()} failed as there was no success response body`, ); } return response.successBody; diff --git a/packages/client/lib/__tests__/AuthzFlowType.spec.ts b/packages/client/lib/__tests__/AuthzFlowType.spec.ts index 776e8a95..de8da498 100644 --- a/packages/client/lib/__tests__/AuthzFlowType.spec.ts +++ b/packages/client/lib/__tests__/AuthzFlowType.spec.ts @@ -1,4 +1,4 @@ -import { AuthzFlowType, CredentialOfferPayload } from '@sphereon/oid4vc-common'; +import { AuthzFlowType, CredentialOfferPayload } from '../../../common/lib'; //todo: this file is just testing v9, we probably want to add v11 tests here as well describe('Authorization Flow Type determination', () => { diff --git a/packages/client/lib/__tests__/CredentialRequestClient.spec.ts b/packages/client/lib/__tests__/CredentialRequestClient.spec.ts index 7784db7b..d822a81e 100644 --- a/packages/client/lib/__tests__/CredentialRequestClient.spec.ts +++ b/packages/client/lib/__tests__/CredentialRequestClient.spec.ts @@ -1,5 +1,11 @@ import { KeyObject } from 'crypto'; +import * as jose from 'jose'; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +import nock from 'nock'; + +import { CredentialRequestClientBuilder, MetadataClient, ProofOfPossessionBuilder } from '..'; import { Alg, EndpointMetadata, @@ -10,13 +16,7 @@ import { ProofOfPossession, URL_NOT_VALID, WellKnownEndpoints, -} from '@sphereon/oid4vc-common'; -import * as jose from 'jose'; -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -import nock from 'nock'; - -import { CredentialRequestClientBuilder, MetadataClient, ProofOfPossessionBuilder } from '..'; +} from '../../../common/lib'; import { CredentialOfferClient } from '../CredentialOfferClient'; import { IDENTIPROOF_ISSUER_URL, IDENTIPROOF_OID4VCI_METADATA, INITIATION_TEST, WALT_OID4VCI_METADATA } from './MetadataMocks'; diff --git a/packages/client/lib/__tests__/MetadataClient.spec.ts b/packages/client/lib/__tests__/MetadataClient.spec.ts index c0bba8a6..1d67c6d7 100644 --- a/packages/client/lib/__tests__/MetadataClient.spec.ts +++ b/packages/client/lib/__tests__/MetadataClient.spec.ts @@ -1,8 +1,6 @@ -import { getIssuerFromCredentialOfferPayload, WellKnownEndpoints } from '@sphereon/oid4vc-common'; -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore import nock from 'nock'; +import { getIssuerFromCredentialOfferPayload, WellKnownEndpoints } from '../../../common/lib'; import { CredentialOfferClient } from '../CredentialOfferClient'; import { MetadataClient } from '../MetadataClient'; diff --git a/packages/client/tsconfig.json b/packages/client/tsconfig.json index e8f815cc..64703078 100644 --- a/packages/client/tsconfig.json +++ b/packages/client/tsconfig.json @@ -6,11 +6,11 @@ "declarationDir": "dist", "esModuleInterop": true, "allowSyntheticDefaultImports": true, - "moduleResolution": "Node" + "moduleResolution": "Node", }, "references": [ { - "path": "../common" - } - ] + "path": "../common", + }, + ], } diff --git a/packages/common/tsconfig.json b/packages/common/tsconfig.json index 30cdb4c1..10224877 100644 --- a/packages/common/tsconfig.json +++ b/packages/common/tsconfig.json @@ -5,6 +5,6 @@ "outDir": "./dist", "declarationDir": "./dist", "esModuleInterop": true, - "moduleResolution": "Node" - } + "moduleResolution": "Node", + }, } diff --git a/packages/issuer-rest/lib/IssuerTokenEndpoint.ts b/packages/issuer-rest/lib/IssuerTokenEndpoint.ts index 84cf3d63..c90f4719 100644 --- a/packages/issuer-rest/lib/IssuerTokenEndpoint.ts +++ b/packages/issuer-rest/lib/IssuerTokenEndpoint.ts @@ -74,10 +74,15 @@ export const verifyTokenRequest = ({ }) } catch (error) { if (error instanceof TokenError) { - return sendErrorResponse(response, error.statusCode, { - error: error.responseError, - error_description: error.getDescription(), - }) + return sendErrorResponse( + response, + error.statusCode, + { + error: error.responseError, + error_description: error.getDescription(), + }, + error, + ) } else { return sendErrorResponse(response, 400, { error: TokenErrorResponse.invalid_request, error_description: (error as Error).message }, error) } diff --git a/packages/issuer-rest/lib/__tests__/ClientIssuerIT.spec.ts b/packages/issuer-rest/lib/__tests__/ClientIssuerIT.spec.ts index 7e410200..f1001482 100644 --- a/packages/issuer-rest/lib/__tests__/ClientIssuerIT.spec.ts +++ b/packages/issuer-rest/lib/__tests__/ClientIssuerIT.spec.ts @@ -1,6 +1,15 @@ import { KeyObject } from 'crypto' import * as didKeyDriver from '@digitalcredentials/did-method-key' +import { VcIssuer } from '@sphereon/oid4vci-issuer/dist/VcIssuer' +import { CredentialSupportedBuilderV1_11, VcIssuerBuilder } from '@sphereon/oid4vci-issuer/dist/builder' +import { MemoryStates } from '@sphereon/oid4vci-issuer/dist/state-manager' +import { ExpressBuilder, ExpressSupport } from '@sphereon/ssi-express-support' +import { IProofPurpose, IProofType } from '@sphereon/ssi-types' +import { DIDDocument } from 'did-resolver' +import * as jose from 'jose' + +import { OpenID4VCIClient } from '../../../client/lib' import { AccessTokenResponse, Alg, @@ -11,16 +20,7 @@ import { JWTHeader, JWTPayload, OpenId4VCIVersion, -} from '@sphereon/oid4vc-common' -import { OpenID4VCIClient } from '@sphereon/oid4vci-client' -import { VcIssuer } from '@sphereon/oid4vci-issuer/dist/VcIssuer' -import { CredentialSupportedBuilderV1_11, VcIssuerBuilder } from '@sphereon/oid4vci-issuer/dist/builder' -import { MemoryStates } from '@sphereon/oid4vci-issuer/dist/state-manager' -import { ExpressBuilder, ExpressSupport } from '@sphereon/ssi-express-support' -import { IProofPurpose, IProofType } from '@sphereon/ssi-types' -import { DIDDocument } from 'did-resolver' -import * as jose from 'jose' - +} from '../../../common/lib' import { OID4VCIServer } from '../OID4VCIServer' const ISSUER_URL = 'http://localhost:3456/test' diff --git a/packages/issuer-rest/tsconfig.json b/packages/issuer-rest/tsconfig.json index a4c084ee..b0aba774 100644 --- a/packages/issuer-rest/tsconfig.json +++ b/packages/issuer-rest/tsconfig.json @@ -4,14 +4,14 @@ "rootDir": "./lib", "outDir": "./dist", "declarationDir": "./dist", - "esModuleInterop": true + "esModuleInterop": true, }, "references": [ { - "path": "../common" + "path": "../common", }, { - "path": "../issuer" - } - ] + "path": "../issuer", + }, + ], } diff --git a/packages/issuer/lib/functions/CredentialOfferUtils.ts b/packages/issuer/lib/functions/CredentialOfferUtils.ts index 140f6d3c..fb7ea648 100644 --- a/packages/issuer/lib/functions/CredentialOfferUtils.ts +++ b/packages/issuer/lib/functions/CredentialOfferUtils.ts @@ -49,6 +49,8 @@ export function createCredentialOfferObject( if (opts?.credentialOffer) { credential_offer = { ...opts.credentialOffer, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore credentials: opts.credentialOffer?.credentials ?? issuerMetadata?.credentials_supported, } } else { diff --git a/packages/issuer/tsconfig.json b/packages/issuer/tsconfig.json index d7e8814a..ec378b88 100644 --- a/packages/issuer/tsconfig.json +++ b/packages/issuer/tsconfig.json @@ -3,11 +3,11 @@ "compilerOptions": { "rootDir": "./lib", "outDir": "./dist", - "declarationDir": "./dist" + "declarationDir": "./dist", }, "references": [ { - "path": "../common" - } - ] + "path": "../common", + }, + ], } diff --git a/packages/siopv2/README.md b/packages/siopv2/README.md index b88d3b2f..21de22e3 100644 --- a/packages/siopv2/README.md +++ b/packages/siopv2/README.md @@ -369,7 +369,7 @@ const op = OP.builder() scopesSupported: [Scope.OPENID_DIDAUTHN, Scope.OPENID], subjectTypesSupported: [SubjectType.PAIRWISE], subjectSyntaxTypesSupported: ['did:ethr'], - passBy: PassBy.VALUE, + requestPassBy: PassBy.VALUE, }) .build(); ``` diff --git a/packages/siopv2/test/AuthenticationRequest.request.spec.ts b/packages/siopv2/__tests__/AuthenticationRequest.request.spec.ts similarity index 100% rename from packages/siopv2/test/AuthenticationRequest.request.spec.ts rename to packages/siopv2/__tests__/AuthenticationRequest.request.spec.ts diff --git a/packages/siopv2/test/AuthenticationRequest.verify.spec.ts b/packages/siopv2/__tests__/AuthenticationRequest.verify.spec.ts similarity index 100% rename from packages/siopv2/test/AuthenticationRequest.verify.spec.ts rename to packages/siopv2/__tests__/AuthenticationRequest.verify.spec.ts diff --git a/packages/siopv2/test/AuthenticationResponse.response.spec.ts b/packages/siopv2/__tests__/AuthenticationResponse.response.spec.ts similarity index 100% rename from packages/siopv2/test/AuthenticationResponse.response.spec.ts rename to packages/siopv2/__tests__/AuthenticationResponse.response.spec.ts diff --git a/packages/siopv2/test/AuthenticationResponse.verify.spec.ts b/packages/siopv2/__tests__/AuthenticationResponse.verify.spec.ts similarity index 100% rename from packages/siopv2/test/AuthenticationResponse.verify.spec.ts rename to packages/siopv2/__tests__/AuthenticationResponse.verify.spec.ts diff --git a/packages/siopv2/test/DocumentLoader.ts b/packages/siopv2/__tests__/DocumentLoader.ts similarity index 100% rename from packages/siopv2/test/DocumentLoader.ts rename to packages/siopv2/__tests__/DocumentLoader.ts diff --git a/packages/siopv2/test/HttpUtils.fetch.spec.ts b/packages/siopv2/__tests__/HttpUtils.fetch.spec.ts similarity index 100% rename from packages/siopv2/test/HttpUtils.fetch.spec.ts rename to packages/siopv2/__tests__/HttpUtils.fetch.spec.ts diff --git a/packages/siopv2/test/IT.spec.ts b/packages/siopv2/__tests__/IT.spec.ts similarity index 100% rename from packages/siopv2/test/IT.spec.ts rename to packages/siopv2/__tests__/IT.spec.ts diff --git a/packages/siopv2/test/OP.request.spec.ts b/packages/siopv2/__tests__/OP.request.spec.ts similarity index 100% rename from packages/siopv2/test/OP.request.spec.ts rename to packages/siopv2/__tests__/OP.request.spec.ts diff --git a/packages/siopv2/test/PresentationExchange.spec.ts b/packages/siopv2/__tests__/PresentationExchange.spec.ts similarity index 100% rename from packages/siopv2/test/PresentationExchange.spec.ts rename to packages/siopv2/__tests__/PresentationExchange.spec.ts diff --git a/packages/siopv2/test/RP.request.spec.ts b/packages/siopv2/__tests__/RP.request.spec.ts similarity index 100% rename from packages/siopv2/test/RP.request.spec.ts rename to packages/siopv2/__tests__/RP.request.spec.ts diff --git a/packages/siopv2/test/SdJwt.spec.ts b/packages/siopv2/__tests__/SdJwt.spec.ts similarity index 99% rename from packages/siopv2/test/SdJwt.spec.ts rename to packages/siopv2/__tests__/SdJwt.spec.ts index 3e571cf0..e349052e 100644 --- a/packages/siopv2/test/SdJwt.spec.ts +++ b/packages/siopv2/__tests__/SdJwt.spec.ts @@ -4,6 +4,7 @@ import { IPresentationDefinition, SdJwtDecodedVerifiableCredentialWithKbJwtInput import { OriginalVerifiableCredential } from '@sphereon/ssi-types'; import { IVerifyCallbackArgs, IVerifyCredentialResult } from '@sphereon/wellknown-dids-client'; +import { ResponseType } from '../../common/lib'; import { OP, PassBy, @@ -13,7 +14,6 @@ import { PresentationVerificationCallback, PropertyTarget, ResponseIss, - ResponseType, RevocationVerification, RP, Scope, diff --git a/packages/siopv2/test/TestUtils.ts b/packages/siopv2/__tests__/TestUtils.ts similarity index 99% rename from packages/siopv2/test/TestUtils.ts rename to packages/siopv2/__tests__/TestUtils.ts index e4d7b8d0..16f90438 100644 --- a/packages/siopv2/test/TestUtils.ts +++ b/packages/siopv2/__tests__/TestUtils.ts @@ -9,6 +9,7 @@ import jwt_decode from 'jwt-decode'; import moment from 'moment'; import { v4 as uuidv4 } from 'uuid'; +import { ResponseType } from '../../common/lib'; import { assertValidMetadata, base64ToHexString, @@ -16,7 +17,6 @@ import { KeyCurve, KeyType, ResponseIss, - ResponseType, RPRegistrationMetadataPayload, Scope, SigningAlgo, diff --git a/packages/siopv2/test/data/mockedData.ts b/packages/siopv2/__tests__/data/mockedData.ts similarity index 100% rename from packages/siopv2/test/data/mockedData.ts rename to packages/siopv2/__tests__/data/mockedData.ts diff --git a/packages/siopv2/test/e2e/mattr.launchpad.spec.ts b/packages/siopv2/__tests__/e2e/mattr.launchpad.spec.ts similarity index 100% rename from packages/siopv2/test/e2e/mattr.launchpad.spec.ts rename to packages/siopv2/__tests__/e2e/mattr.launchpad.spec.ts diff --git a/packages/siopv2/test/functions/DidJWT.spec.ts b/packages/siopv2/__tests__/functions/DidJWT.spec.ts similarity index 100% rename from packages/siopv2/test/functions/DidJWT.spec.ts rename to packages/siopv2/__tests__/functions/DidJWT.spec.ts diff --git a/packages/siopv2/test/functions/DidSiopMetadata.spec.ts b/packages/siopv2/__tests__/functions/DidSiopMetadata.spec.ts similarity index 100% rename from packages/siopv2/test/functions/DidSiopMetadata.spec.ts rename to packages/siopv2/__tests__/functions/DidSiopMetadata.spec.ts diff --git a/packages/siopv2/test/functions/Encodings.spec.ts b/packages/siopv2/__tests__/functions/Encodings.spec.ts similarity index 100% rename from packages/siopv2/test/functions/Encodings.spec.ts rename to packages/siopv2/__tests__/functions/Encodings.spec.ts diff --git a/packages/siopv2/test/functions/LanguageTagUtils.spec.ts b/packages/siopv2/__tests__/functions/LanguageTagUtils.spec.ts similarity index 100% rename from packages/siopv2/test/functions/LanguageTagUtils.spec.ts rename to packages/siopv2/__tests__/functions/LanguageTagUtils.spec.ts diff --git a/packages/siopv2/test/functions/LinkedDomainValidations.spec.ts b/packages/siopv2/__tests__/functions/LinkedDomainValidations.spec.ts similarity index 100% rename from packages/siopv2/test/functions/LinkedDomainValidations.spec.ts rename to packages/siopv2/__tests__/functions/LinkedDomainValidations.spec.ts diff --git a/packages/siopv2/test/interop/auth0/auth0.spec.ts b/packages/siopv2/__tests__/interop/auth0/auth0.spec.ts similarity index 100% rename from packages/siopv2/test/interop/auth0/auth0.spec.ts rename to packages/siopv2/__tests__/interop/auth0/auth0.spec.ts diff --git a/packages/siopv2/test/interop/auth0/fixtures.ts b/packages/siopv2/__tests__/interop/auth0/fixtures.ts similarity index 100% rename from packages/siopv2/test/interop/auth0/fixtures.ts rename to packages/siopv2/__tests__/interop/auth0/fixtures.ts diff --git a/packages/siopv2/test/interop/mattr/fixtures.ts b/packages/siopv2/__tests__/interop/mattr/fixtures.ts similarity index 100% rename from packages/siopv2/test/interop/mattr/fixtures.ts rename to packages/siopv2/__tests__/interop/mattr/fixtures.ts diff --git a/packages/siopv2/test/modules.d.ts b/packages/siopv2/__tests__/modules.d.ts similarity index 100% rename from packages/siopv2/test/modules.d.ts rename to packages/siopv2/__tests__/modules.d.ts diff --git a/packages/siopv2/test/regressions/ClientIdIsObject.spec.ts b/packages/siopv2/__tests__/regressions/ClientIdIsObject.spec.ts similarity index 100% rename from packages/siopv2/test/regressions/ClientIdIsObject.spec.ts rename to packages/siopv2/__tests__/regressions/ClientIdIsObject.spec.ts diff --git a/packages/siopv2/test/spec-compliance/jwtVCPresentationProfile.spec.ts b/packages/siopv2/__tests__/spec-compliance/jwtVCPresentationProfile.spec.ts similarity index 99% rename from packages/siopv2/test/spec-compliance/jwtVCPresentationProfile.spec.ts rename to packages/siopv2/__tests__/spec-compliance/jwtVCPresentationProfile.spec.ts index 603db343..bd8b880b 100644 --- a/packages/siopv2/test/spec-compliance/jwtVCPresentationProfile.spec.ts +++ b/packages/siopv2/__tests__/spec-compliance/jwtVCPresentationProfile.spec.ts @@ -6,6 +6,7 @@ import { KeyLike } from 'jose'; import nock from 'nock'; import * as u8a from 'uint8arrays'; +import { ResponseType } from '../../../common/lib'; import { AuthorizationRequest, AuthorizationResponse, @@ -19,7 +20,6 @@ import { PresentationVerificationCallback, PropertyTarget, ResponseMode, - ResponseType, RevocationVerification, RP, SigningAlgo, diff --git a/packages/siopv2/jest.config.cjs b/packages/siopv2/jest.config.cjs deleted file mode 100644 index e3773a16..00000000 --- a/packages/siopv2/jest.config.cjs +++ /dev/null @@ -1,27 +0,0 @@ -module.exports = { - preset: "ts-jest", - testEnvironment: "node", - moduleNameMapper: { - "^jose/(.*)$": "/node_modules/jose/dist/node/cjs/$1", - }, - rootDir: ".", - roots: ["/src/", "/test/"], - testMatch: ["**/?(*.)+(spec|test).+(ts|tsx|js)"], - transform: { - "^.+\\.(ts|tsx)?$": "ts-jest", - }, - moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json"], - coverageDirectory: "./coverage/", - collectCoverageFrom: [ - "src/**/*.{ts,tsx}", - "!src/schemas/**", - "!src/**/*.d.ts", - "!**/node_modules/**", - "!jest.config.cjs", - "!generator/**", - "!index.ts", - - ], - collectCoverage: true, - reporters: ["default", ["jest-junit", { outputDirectory: "./coverage" }]], -}; diff --git a/packages/siopv2/package.json b/packages/siopv2/package.json index 3fd88850..14cfa8ac 100644 --- a/packages/siopv2/package.json +++ b/packages/siopv2/package.json @@ -16,48 +16,48 @@ "build:schemaGenerator": "node --loader=tsimp/loader generator/schemaGenerator.ts", "clean": "rimraf dist coverage", "fix.old": "run-s fix:*", - "fix:prettier.old": "prettier \"{src,test}/**/*.ts\" --write", + "fix:prettier.old": "prettier \"{src,s__}/**/*.ts\" --write", "fix:lint.old": "eslint . --ext .ts --fix", - "test": "run-s build test:*", - "test:lint": "eslint . --ext .ts", - "test:prettier": "prettier \"{src,test}/**/*.ts\" --list-different", - "test:cov": "jest --ci --coverage && codecov", + "test.old": "run-s build test:*", + "test:lint.old": "eslint . --ext .ts", + "test:prettier.old": "prettier \"{src,__tests__}/**/*.ts\" --list-different", + "test:cov.old": "jest --ci --coverage && codecov", "uninstall": "rimraf dist coverage node_modules" }, "engines": { "node": ">=18" }, "dependencies": { + "@astronautlabs/jsonpath": "^1.1.2", "@sphereon/did-uni-client": "^0.6.1", - "qs": "^6.11.2", + "@sphereon/oid4vc-common": "workspace:*", "@sphereon/pex": "^3.0.1", "@sphereon/pex-models": "^2.1.5", "@sphereon/ssi-types": "0.18.1", "@sphereon/wellknown-dids-client": "^0.1.3", - "@sphereon/oid4vc-common": "workspace:*", - "@astronautlabs/jsonpath": "^1.1.2", - "sha.js": "^2.4.11", "cross-fetch": "^4.0.0", "did-jwt": "6.11.6", "did-resolver": "^4.1.0", "events": "^3.3.0", "language-tags": "^1.0.9", "multiformats": "^11.0.2", + "qs": "^6.11.2", + "sha.js": "^2.4.11", "uint8arrays": "^3.1.1", "uuid": "^9.0.0" }, "devDependencies": { - "@types/qs": "^6.9.11", "@digitalcredentials/did-method-key": "^2.0.3", "@digitalcredentials/ed25519-signature-2020": "^3.0.2", "@digitalcredentials/jsonld-signatures": "^9.3.2", "@digitalcredentials/vc": "^6.0.0", + "@transmute/did-key-ed25519": "^0.3.0-unstable.10", "@transmute/ed25519-key-pair": "0.7.0-unstable.82", "@transmute/ed25519-signature-2018": "^0.7.0-unstable.82", - "@transmute/did-key-ed25519": "^0.3.0-unstable.10", - "did-resolver": "^4.1.0", "@types/jest": "^29.5.11", "@types/language-tags": "^1.0.4", + "@types/qs": "^6.9.11", + "@types/sha.js": "^2.4.4", "@types/uuid": "^9.0.7", "@typescript-eslint/eslint-plugin": "^5.52.0", "@typescript-eslint/parser": "^5.52.0", @@ -65,6 +65,7 @@ "bs58": "^5.0.0", "codecov": "^3.8.3", "cspell": "^6.26.3", + "did-resolver": "^4.1.0", "dotenv": "^16.3.1", "eslint": "^8.34.0", "eslint-config-prettier": "^8.6.0", diff --git a/packages/siopv2/src/authorization-request/AuthorizationRequest.ts b/packages/siopv2/src/authorization-request/AuthorizationRequest.ts index 93f9c552..f868efba 100644 --- a/packages/siopv2/src/authorization-request/AuthorizationRequest.ts +++ b/packages/siopv2/src/authorization-request/AuthorizationRequest.ts @@ -11,7 +11,6 @@ import { PassBy, RequestObjectJwt, RequestObjectPayload, - RequestStateInfo, ResponseURIType, RPRegistrationMetadataPayload, Schema, @@ -39,7 +38,7 @@ export class AuthorizationRequest { this._uri = uri; } - public static async fromUriOrJwt(jwtOrUri: string | URI): Promise { + public static async fromUriOrJwt(jwtOrUri?: string | URI): Promise { if (!jwtOrUri) { throw Error(SIOPErrors.NO_REQUEST); } @@ -66,6 +65,9 @@ export class AuthorizationRequest { const requestObjectArg = opts.requestObject.passBy !== PassBy.NONE ? (requestObject ? requestObject : await RequestObject.fromOpts(opts)) : undefined; const requestPayload = opts?.payload ? await createAuthorizationRequestPayload(opts, requestObjectArg) : undefined; + if (!requestPayload) { + throw Error(SIOPErrors.BAD_PARAMS); + } return new AuthorizationRequest(requestPayload, requestObjectArg, opts); } @@ -100,9 +102,9 @@ export class AuthorizationRequest { return authorizationRequestVersionDiscovery(mergedPayload); } - async uri(): Promise { + async uri(opts?: { requestPassBy?: PassBy }): Promise { if (!this._uri) { - this._uri = await URI.fromAuthorizationRequest(this); + this._uri = await URI.fromAuthorizationRequest(this, { requestPassBy: opts?.requestPassBy ?? PassBy.REFERENCE }); } return this._uri; } @@ -115,8 +117,8 @@ export class AuthorizationRequest { async verify(opts: VerifyAuthorizationRequestOpts): Promise { assertValidVerifyAuthorizationRequestOpts(opts); - let requestObjectPayload: RequestObjectPayload; - let verifiedJwt: VerifiedJWT; + let requestObjectPayload: RequestObjectPayload | undefined = undefined; + let verifiedJwt: VerifiedJWT | undefined = undefined; const jwt = await this.requestObjectJwt(); if (jwt) { @@ -143,7 +145,7 @@ export class AuthorizationRequest { // AuthorizationRequest.assertValidRequestObject(origAuthenticationRequest); // We use the orig request for default values, but the JWT payload contains signed request object properties - const mergedPayload = { ...this.payload, ...requestObjectPayload }; + const mergedPayload = { ...this.payload, ...requestObjectPayload } as RequestObjectPayload; if (opts.state && mergedPayload.state !== opts.state) { throw new Error(`${SIOPErrors.BAD_STATE} payload: ${mergedPayload.state}, supplied: ${opts.state}`); } else if (opts.nonce && mergedPayload.nonce !== opts.nonce) { @@ -151,7 +153,7 @@ export class AuthorizationRequest { } const registrationPropertyKey = mergedPayload['registration'] || mergedPayload['registration_uri'] ? 'registration' : 'client_metadata'; - let registrationMetadataPayload: RPRegistrationMetadataPayload; + let registrationMetadataPayload: RPRegistrationMetadataPayload | undefined = undefined; if (mergedPayload[registrationPropertyKey] || mergedPayload[`${registrationPropertyKey}_uri`]) { registrationMetadataPayload = await fetchByReferenceOrUseByValue( mergedPayload[`${registrationPropertyKey}_uri`], @@ -177,6 +179,9 @@ export class AuthorizationRequest { } else { throw new Error(`${SIOPErrors.INVALID_REQUEST}, redirect_uri or response_uri is needed`); } + if (!mergedPayload.scope) { + throw new Error(`${SIOPErrors.INVALID_REQUEST}, Scope should be present`); + } await checkWellknownDIDFromRequest(mergedPayload, opts); @@ -215,7 +220,7 @@ export class AuthorizationRequest { throw Error(SIOPErrors.BAD_PARAMS); } const requestObject = await RequestObject.fromJwt(jwt); - const payload: AuthorizationRequestPayload = { ...(await requestObject.getPayload()) } as AuthorizationRequestPayload; + const payload: AuthorizationRequestPayload = { ...(requestObject && (await requestObject.getPayload())) } as AuthorizationRequestPayload; // Although this was a RequestObject we instantiate it as AuthzRequest and then copy in the JWT as the request Object payload.request = jwt; return new AuthorizationRequest({ ...payload }, requestObject); @@ -230,18 +235,8 @@ export class AuthorizationRequest { return new AuthorizationRequest(uriObject.authorizationRequestPayload, requestObject, undefined, uriObject); } - public async toStateInfo(): Promise { - const requestObject = await this.requestObject.getPayload(); - return { - client_id: this.options.clientMetadata.client_id, - iat: requestObject.iat ?? this.payload.iat, - nonce: requestObject.nonce ?? this.payload.nonce, - state: this.payload.state, - }; - } - public async containsResponseType(singleType: ResponseType | string): Promise { - const responseType: string = await this.getMergedProperty('response_type'); + const responseType: string | undefined = await this.getMergedProperty('response_type'); return responseType?.includes(singleType) === true; } @@ -251,7 +246,7 @@ export class AuthorizationRequest { } public async mergedPayloads(): Promise { - return { ...this.payload, ...(this.requestObject && (await this.requestObject.getPayload())) }; + return { ...this.payload, ...(this.requestObject && (await this.requestObject.getPayload())) } as RequestObjectPayload; } public async getPresentationDefinitions(version?: SupportedVersion): Promise { diff --git a/packages/siopv2/src/authorization-request/Opts.ts b/packages/siopv2/src/authorization-request/Opts.ts index a534e598..0db4ce9c 100644 --- a/packages/siopv2/src/authorization-request/Opts.ts +++ b/packages/siopv2/src/authorization-request/Opts.ts @@ -1,5 +1,5 @@ import { assertValidRequestObjectOpts } from '../request-object/Opts'; -import { ExternalVerification, InternalVerification, isExternalVerification, isInternalVerification, SIOPErrors } from '../types'; +import { ClientMetadataOpts, ExternalVerification, InternalVerification, isExternalVerification, isInternalVerification, SIOPErrors } from '../types'; import { assertValidRequestRegistrationOpts } from './RequestRegistration'; import { CreateAuthorizationRequestOpts, VerifyAuthorizationRequestOpts } from './types'; @@ -18,7 +18,9 @@ export const assertValidAuthorizationRequestOpts = (opts: CreateAuthorizationReq throw new Error(SIOPErrors.BAD_PARAMS); } assertValidRequestObjectOpts(opts.requestObject, false); - assertValidRequestRegistrationOpts(opts['registration'] ? opts['registration'] : opts.clientMetadata); + assertValidRequestRegistrationOpts( + 'registration' in opts && opts['registration'] ? (opts['registration'] as ClientMetadataOpts) : opts.clientMetadata, + ); }; export const mergeVerificationOpts = ( diff --git a/packages/siopv2/src/authorization-request/Payload.ts b/packages/siopv2/src/authorization-request/Payload.ts index a9c29dcb..50695915 100644 --- a/packages/siopv2/src/authorization-request/Payload.ts +++ b/packages/siopv2/src/authorization-request/Payload.ts @@ -9,7 +9,6 @@ import { AuthorizationRequestPayload, CheckLinkedDomain, ClaimPayloadVID1, - ClientMetadataOpts, PassBy, RequestObjectPayload, RPRegistrationMetadataPayload, @@ -20,14 +19,16 @@ import { import { createRequestRegistration } from './RequestRegistration'; import { ClaimPayloadOptsVID1, CreateAuthorizationRequestOpts, PropertyTarget, VerifyAuthorizationRequestOpts } from './types'; -export const createPresentationDefinitionClaimsProperties = (opts: ClaimPayloadOptsVID1): ClaimPayloadVID1 => { - if (!opts || !opts.vp_token || (!opts.vp_token.presentation_definition && !opts.vp_token.presentation_definition_uri)) { +export const createPresentationDefinitionClaimsProperties = (opts?: ClaimPayloadOptsVID1): ClaimPayloadVID1 | undefined => { + if (!opts?.vp_token || (!opts.vp_token.presentation_definition && !opts.vp_token.presentation_definition_uri)) { return undefined; } - const discoveryResult = PEX.definitionVersionDiscovery(opts.vp_token.presentation_definition); - if (discoveryResult.error) { - throw new Error(SIOPErrors.REQUEST_CLAIMS_PRESENTATION_DEFINITION_NOT_VALID); - } + if (opts.vp_token.presentation_definition) { + const discoveryResult = PEX.definitionVersionDiscovery(opts.vp_token.presentation_definition); + if (discoveryResult.error) { + throw new Error(SIOPErrors.REQUEST_CLAIMS_PRESENTATION_DEFINITION_NOT_VALID); + } + } // todo: Also check definition by reference? return { ...(opts.id_token ? { id_token: opts.id_token } : {}), @@ -48,17 +49,20 @@ export const createAuthorizationRequestPayload = async ( const state = payload?.state ?? undefined; const nonce = payload?.nonce ? getNonce(state, payload.nonce) : undefined; // TODO: if opts['registration] throw Error to get rid of test code using that key - const clientMetadata = opts['registration'] ? opts['registration'] : (opts.clientMetadata as ClientMetadataOpts); + // registration is from older specs + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const clientMetadata = opts['registration'] ? opts['registration'] : opts.clientMetadata; const registration = await createRequestRegistration(clientMetadata, opts); const claims = - opts.version >= SupportedVersion.SIOPv2_ID1 ? opts.payload.claims : createPresentationDefinitionClaimsProperties(opts.payload.claims); + opts.version >= SupportedVersion.SIOPv2_ID1 ? opts?.payload?.claims : createPresentationDefinitionClaimsProperties(opts?.payload?.claims); const isRequestTarget = isTargetOrNoTargets(PropertyTarget.AUTHORIZATION_REQUEST, opts.requestObject.targets); const isRequestByValue = opts.requestObject.passBy === PassBy.VALUE; if (isRequestTarget && isRequestByValue && !requestObject) { throw Error(SIOPErrors.NO_JWT); } - const request = isRequestByValue ? await requestObject.toJwt() : undefined; + const request = isRequestByValue && requestObject ? await requestObject.toJwt() : undefined; const authRequestPayload = { ...payload, @@ -67,14 +71,20 @@ export const createAuthorizationRequestPayload = async ( ...(isRequestTarget && isRequestByValue && { request }), ...(nonce && { nonce }), ...(state && { state }), - ...(registration.payload && isTarget(PropertyTarget.AUTHORIZATION_REQUEST, registration.clientMetadataOpts.targets) ? registration.payload : {}), + ...(registration.payload && + isTarget( + PropertyTarget.AUTHORIZATION_REQUEST, + registration.clientMetadataOpts.targets ?? [PropertyTarget.AUTHORIZATION_REQUEST, PropertyTarget.REQUEST_OBJECT], + ) + ? registration.payload + : {}), ...(claims && { claims }), }; return removeNullUndefined(authRequestPayload); }; -export const assertValidRPRegistrationMedataPayload = (regObj: RPRegistrationMetadataPayload) => { +export const assertValidRPRegistrationMedataPayload = (regObj?: RPRegistrationMetadataPayload) => { if (regObj) { const valid = RPRegistrationMetadataPayloadSchema(regObj); if (!valid) { diff --git a/packages/siopv2/src/authorization-request/RequestRegistration.ts b/packages/siopv2/src/authorization-request/RequestRegistration.ts index 1af7af4c..e213dbb3 100644 --- a/packages/siopv2/src/authorization-request/RequestRegistration.ts +++ b/packages/siopv2/src/authorization-request/RequestRegistration.ts @@ -15,7 +15,7 @@ import { CreateAuthorizationRequestOpts } from './types'; /*const ajv = new Ajv({ allowUnionTypes: true, strict: false }); const validateRPRegistrationMetadata = ajv.compile(RPRegistrationMetadataPayloadSchema);*/ -export const assertValidRequestRegistrationOpts = (opts: ClientMetadataOpts) => { +export const assertValidRequestRegistrationOpts = (opts?: ClientMetadataOpts) => { if (!opts) { throw new Error(SIOPErrors.REGISTRATION_NOT_SET); } else if (opts.passBy !== PassBy.REFERENCE && opts.passBy !== PassBy.VALUE) { @@ -93,6 +93,8 @@ const createRPRegistrationMetadataPayload = (opts: RPRegistrationMetadataOpts): const languageTaggedFields: Map = LanguageTagUtils.getLanguageTaggedPropertiesMapped(opts, languageTagEnabledFieldsNamesMapping); languageTaggedFields.forEach((value: string, key: string) => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore rpRegistrationMetadataPayload[key] = value; }); diff --git a/packages/siopv2/src/authorization-request/URI.ts b/packages/siopv2/src/authorization-request/URI.ts index 4494efe4..a126210c 100644 --- a/packages/siopv2/src/authorization-request/URI.ts +++ b/packages/siopv2/src/authorization-request/URI.ts @@ -22,20 +22,23 @@ import { CreateAuthorizationRequestOpts } from './types'; export class URI implements AuthorizationRequestURI { private readonly _scheme: string; - private readonly _requestObjectJwt: RequestObjectJwt | undefined; + private readonly _requestObjectJwt?: RequestObjectJwt; private readonly _authorizationRequestPayload: AuthorizationRequestPayload; private readonly _encodedUri: string; // The encoded URI private readonly _encodingFormat: UrlEncodingFormat; // private _requestObjectBy: ObjectBy; - private _registrationMetadataPayload: RPRegistrationMetadataPayload; + private _registrationMetadataPayload: RPRegistrationMetadataPayload | undefined; - private constructor({ scheme, encodedUri, encodingFormat, authorizationRequestPayload, requestObjectJwt }: Partial) { - this._scheme = scheme; - this._encodedUri = encodedUri; - this._encodingFormat = encodingFormat; - this._authorizationRequestPayload = authorizationRequestPayload; - this._requestObjectJwt = requestObjectJwt; + private constructor(args: Partial) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const a = args as AuthorizationRequestURI; + this._scheme = a.scheme; + this._encodedUri = a.encodedUri; + this._encodingFormat = a.encodingFormat; + this._authorizationRequestPayload = a.authorizationRequestPayload; + this._requestObjectJwt = a.requestObjectJwt; } public static async fromUri(uri: string): Promise { @@ -73,7 +76,7 @@ export class URI implements AuthorizationRequestURI { throw Error(SIOPErrors.BAD_PARAMS); } const authorizationRequest = await AuthorizationRequest.fromOpts(opts); - return await URI.fromAuthorizationRequest(authorizationRequest); + return await URI.fromAuthorizationRequest(authorizationRequest, { requestPassBy: opts.requestObject.passBy }); } public async toAuthorizationRequest(): Promise { @@ -110,26 +113,32 @@ export class URI implements AuthorizationRequestURI { * part of the URI and which become part of the Request Object. If you generate a URI based upon the result of this method, * the URI will be constructed based on the Request Object only! */ - static async fromRequestObject(requestObject: RequestObject): Promise { + static async fromRequestObject(requestObject: RequestObject, opts: { requestPassBy: PassBy }): Promise { if (!requestObject) { throw Error(SIOPErrors.BAD_PARAMS); } - return await URI.fromAuthorizationRequestPayload(requestObject.options, await AuthorizationRequest.fromUriOrJwt(await requestObject.toJwt())); + return await URI.fromAuthorizationRequestPayload({ + opts: { ...requestObject.options, ...opts }, + authorizationRequestPayload: await AuthorizationRequest.fromUriOrJwt(await requestObject.toJwt()), + }); } - static async fromAuthorizationRequest(authorizationRequest: AuthorizationRequest): Promise { + static async fromAuthorizationRequest(authorizationRequest: AuthorizationRequest, opts: { requestPassBy: PassBy }): Promise { if (!authorizationRequest) { throw Error(SIOPErrors.BAD_PARAMS); } - return await URI.fromAuthorizationRequestPayload( - { - ...authorizationRequest.options.requestObject, - version: authorizationRequest.options.version, - uriScheme: authorizationRequest.options.uriScheme, + return await URI.fromAuthorizationRequestPayload({ + opts: { + ...(authorizationRequest.options && { + ...authorizationRequest.options.requestObject, + version: authorizationRequest.options.version, + uriScheme: authorizationRequest.options.uriScheme, + }), + requestPassBy: opts.requestPassBy, }, - authorizationRequest.payload, - authorizationRequest.requestObject, - ); + authorizationRequestPayload: authorizationRequest.payload, + requestObject: authorizationRequest.requestObject, + }); } /** @@ -137,12 +146,17 @@ export class URI implements AuthorizationRequestURI { * @param opts Options to define the Uri Request * @param authorizationRequestPayload * + * @param requestObject */ - private static async fromAuthorizationRequestPayload( - opts: { uriScheme?: string; passBy: PassBy; reference_uri?: string; version?: SupportedVersion }, - authorizationRequestPayload: AuthorizationRequestPayload, - requestObject?: RequestObject, - ): Promise { + private static async fromAuthorizationRequestPayload({ + opts, + authorizationRequestPayload, + requestObject, + }: { + authorizationRequestPayload: AuthorizationRequestPayload | string; + requestObject?: RequestObject; + opts: { uriScheme?: string; requestPassBy: PassBy; reference_uri?: string; version?: SupportedVersion }; + }): Promise { if (!authorizationRequestPayload) { if (!requestObject || !(await requestObject.getPayload())) { throw Error(SIOPErrors.BAD_PARAMS); @@ -154,16 +168,21 @@ export class URI implements AuthorizationRequestURI { const requestObjectJwt = requestObject ? await requestObject.toJwt() : typeof authorizationRequestPayload === 'string' - ? authorizationRequestPayload - : authorizationRequestPayload.request; + ? authorizationRequestPayload + : authorizationRequestPayload.request; if (isJwt && (!requestObjectJwt || !requestObjectJwt.startsWith('ey'))) { throw Error(SIOPErrors.NO_JWT); } - const requestObjectPayload: RequestObjectPayload = requestObjectJwt ? (decodeJWT(requestObjectJwt).payload as RequestObjectPayload) : undefined; + const requestObjectPayload: RequestObjectPayload | undefined = requestObjectJwt + ? (decodeJWT(requestObjectJwt).payload as RequestObjectPayload) + : undefined; if (requestObjectPayload) { // Only used to validate if the request object contains presentation definition(s) - await PresentationExchange.findValidPresentationDefinitions({ ...authorizationRequestPayload, ...requestObjectPayload }); + await PresentationExchange.findValidPresentationDefinitions({ + ...(typeof authorizationRequestPayload !== 'string' && authorizationRequestPayload), + ...requestObjectPayload, + }); assertValidRequestObjectPayload(requestObjectPayload); if (requestObjectPayload.registration) { @@ -175,7 +194,7 @@ export class URI implements AuthorizationRequestURI { if (!uniformAuthorizationRequestPayload) { throw Error(SIOPErrors.BAD_PARAMS); } - const type = opts.passBy; + const type = opts.requestPassBy; if (!type) { throw new Error(SIOPErrors.REQUEST_OBJECT_TYPE_NOT_SET); } @@ -229,7 +248,11 @@ export class URI implements AuthorizationRequestURI { throw Error(SIOPErrors.BAD_PARAMS); } // We strip the uri scheme before passing it to the decode function - const scheme: string = uri.match(/^([a-zA-Z][a-zA-Z0-9-_]*:\/\/)/g)[0]; + const matches = uri.match(/^([a-zA-Z][a-zA-Z0-9-_]*:\/\/)/g) + if (!Array.isArray(matches)) { + throw Error(SIOPErrors.BAD_PARAMS + `: no scheme`); + } + const scheme: string = matches[0]; const authorizationRequestPayload = decodeUriAsJson(uri) as AuthorizationRequestPayload; return { scheme, authorizationRequestPayload }; } @@ -268,7 +291,7 @@ export class URI implements AuthorizationRequestURI { return this._scheme; } - get registrationMetadataPayload(): RPRegistrationMetadataPayload { + get registrationMetadataPayload(): RPRegistrationMetadataPayload | undefined { return this._registrationMetadataPayload; } } diff --git a/packages/siopv2/src/authorization-response/AuthorizationResponse.ts b/packages/siopv2/src/authorization-response/AuthorizationResponse.ts index fab6dcf5..683bcf70 100644 --- a/packages/siopv2/src/authorization-response/AuthorizationResponse.ts +++ b/packages/siopv2/src/authorization-response/AuthorizationResponse.ts @@ -104,7 +104,7 @@ export class AuthorizationResponse { // const hasVpToken = await authorizationRequest.containsResponseType(ResponseType.VP_TOKEN); const idToken = wantsIdToken ? await IDToken.fromVerifiedAuthorizationRequest(verifiedAuthorizationRequest, responseOpts) : undefined; - const idTokenPayload = wantsIdToken ? await idToken.payload() : undefined; + const idTokenPayload = idToken ? await idToken.payload() : undefined; const authorizationResponsePayload = await createResponsePayload(authorizationRequest, responseOpts, idTokenPayload); const response = new AuthorizationResponse({ authorizationResponsePayload, diff --git a/packages/siopv2/src/authorization-response/OpenID4VP.ts b/packages/siopv2/src/authorization-response/OpenID4VP.ts index 894e254e..3f3a5927 100644 --- a/packages/siopv2/src/authorization-response/OpenID4VP.ts +++ b/packages/siopv2/src/authorization-response/OpenID4VP.ts @@ -198,7 +198,7 @@ export const putPresentationSubmissionInLocation = async ( export const assertValidVerifiablePresentations = async (args: { presentationDefinitions: PresentationDefinitionWithLocation[]; presentations: WrappedVerifiablePresentation[]; - verificationCallback: PresentationVerificationCallback; + verificationCallback?: PresentationVerificationCallback; opts?: { limitDisclosureSignatureSuites?: string[]; restrictToFormats?: Format; @@ -226,7 +226,7 @@ export const assertValidVerifiablePresentations = async (args: { throw new Error(SIOPErrors.AUTH_REQUEST_DOESNT_EXPECT_VP); } else if (args.presentationDefinitions && presentationsWithFormat && args.presentationDefinitions.length != presentationsWithFormat.length) { throw new Error(SIOPErrors.AUTH_REQUEST_EXPECTS_VP); - } else if (args.presentationDefinitions && !args.opts.presentationSubmission) { + } else if (args.presentationDefinitions && !args.opts?.presentationSubmission) { throw new Error(`No presentation submission present. Please use presentationSubmission opt argument!`); } else if (args.presentationDefinitions && presentationsWithFormat) { await PresentationExchange.validatePresentationsAgainstDefinitions( diff --git a/packages/siopv2/src/authorization-response/Payload.ts b/packages/siopv2/src/authorization-response/Payload.ts index 31ec61a8..781e85e9 100644 --- a/packages/siopv2/src/authorization-response/Payload.ts +++ b/packages/siopv2/src/authorization-response/Payload.ts @@ -11,7 +11,7 @@ export const createResponsePayload = async ( authorizationRequest: AuthorizationRequest, responseOpts: AuthorizationResponseOpts, idTokenPayload?: IDTokenPayload, -): Promise => { +): Promise => { assertValidResponseOpts(responseOpts); if (!authorizationRequest) { throw new Error(SIOPErrors.NO_REQUEST); diff --git a/packages/siopv2/src/authorization-response/PresentationExchange.ts b/packages/siopv2/src/authorization-response/PresentationExchange.ts index 67d113a0..0a273aa1 100644 --- a/packages/siopv2/src/authorization-response/PresentationExchange.ts +++ b/packages/siopv2/src/authorization-response/PresentationExchange.ts @@ -64,14 +64,14 @@ export class PresentationExchange { ...options, presentationSubmissionLocation: PresentationSubmissionLocation.EXTERNAL, proofOptions: { - ...options.proofOptions, + ...options?.proofOptions, proofPurpose: options?.proofOptions?.proofPurpose ?? IProofPurpose.authentication, type: options?.proofOptions?.type ?? IProofType.EcdsaSecp256k1Signature2019, /* challenge: options?.proofOptions?.challenge, domain: options?.proofOptions?.domain,*/ }, signatureOptions: { - ...options.signatureOptions, + ...options?.signatureOptions, // verificationMethod: options?.signatureOptions?.verificationMethod, keyEncoding: options?.signatureOptions?.keyEncoding ?? KeyEncoding.Hex, }, @@ -150,7 +150,7 @@ export class PresentationExchange { } // console.log(`Presentation (validate): ${JSON.stringify(verifiablePresentation)}`); const evaluationResults: EvaluationResults = new PEX({ hasher: opts?.hasher }).evaluatePresentation(presentationDefinition, wvp.original, opts); - if (evaluationResults.errors.length) { + if (evaluationResults.errors?.length) { throw new Error(`message: ${SIOPErrors.COULD_NOT_FIND_VCS_MATCHING_PD}, details: ${JSON.stringify(evaluationResults.errors)}`); } return evaluationResults; @@ -158,8 +158,14 @@ export class PresentationExchange { public static assertValidPresentationSubmission(presentationSubmission: PresentationSubmission) { const validationResult = PEX.validateSubmission(presentationSubmission); - if (validationResult[0].message != 'ok') { - throw new Error(`${SIOPErrors.RESPONSE_OPTS_PRESENTATIONS_SUBMISSION_IS_NOT_VALID}, details ${JSON.stringify(validationResult[0])}`); + if (Array.isArray(validationResult)) { + if (validationResult[0].message != 'ok') { + throw new Error(`${SIOPErrors.RESPONSE_OPTS_PRESENTATIONS_SUBMISSION_IS_NOT_VALID}, details ${JSON.stringify(validationResult[0])}`); + } + } else { + if (validationResult.message != 'ok') { + throw new Error(`${SIOPErrors.RESPONSE_OPTS_PRESENTATIONS_SUBMISSION_IS_NOT_VALID}, details ${JSON.stringify(validationResult)}`); + } } } @@ -283,8 +289,14 @@ export class PresentationExchange { private static assertValidPresentationDefinition(presentationDefinition: IPresentationDefinition) { const validationResult = PEX.validateDefinition(presentationDefinition); - if (validationResult[0].message != 'ok') { - throw new Error(`${SIOPErrors.REQUEST_CLAIMS_PRESENTATION_DEFINITION_NOT_VALID}`); + if (Array.isArray(validationResult)) { + if (validationResult[0].message != 'ok') { + throw new Error(`${SIOPErrors.REQUEST_CLAIMS_PRESENTATION_DEFINITION_NOT_VALID}`); + } + } else { + if (validationResult.message != 'ok') { + throw new Error(`${SIOPErrors.REQUEST_CLAIMS_PRESENTATION_DEFINITION_NOT_VALID}`); + } } } @@ -384,6 +396,9 @@ export class PresentationExchange { ...opts, presentationSubmission, }); + if (!evaluationResults.value) { + throw new Error(`${SIOPErrors.RESPONSE_OPTS_PRESENTATIONS_SUBMISSION_IS_NOT_VALID}, details no presentation submission`); + } PresentationExchange.assertValidPresentationSubmission(evaluationResults.value); await PresentationExchange.validatePresentationAgainstDefinition(definition, checkedPresentation, { ...opts, diff --git a/packages/siopv2/src/did/DIDResolution.ts b/packages/siopv2/src/did/DIDResolution.ts index b3b5f445..ee1285e2 100644 --- a/packages/siopv2/src/did/DIDResolution.ts +++ b/packages/siopv2/src/did/DIDResolution.ts @@ -31,6 +31,8 @@ export function getResolver(opts: ResolveOpts): Resolvable { const uniResolver = getUniResolver(getMethodFromDid(didMethod), { resolveUrl: opts.resolveUrl }); uniResolvers.push(uniResolver); } + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore return new Resolver(...uniResolvers); } else { if (opts?.noUniversalResolverFallback) { @@ -83,10 +85,16 @@ export function getResolverUnion( } else { methodResolver = resolverMap.get(dm); } + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore uniResolvers.push(methodResolver); }); return subjectTypes.indexOf(SubjectSyntaxTypesSupportedValues.DID.valueOf()) !== -1 + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore ? new Resolver(...{ fallbackResolver, ...uniResolvers }) + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore : new Resolver(...uniResolvers); } @@ -100,7 +108,7 @@ export function mergeAllDidMethods(subjectSyntaxTypesSupported: string | string[ return Array.from(unionSubjectSyntaxTypes) as string[]; } -export async function resolveDidDocument(did: string, opts?: ResolveOpts): Promise { +export async function resolveDidDocument(did: string, opts?: ResolveOpts): Promise { // todo: The accept is only there because did:key used by Veramo requires it. According to the spec it is optional. It should not hurt, but let's test const result = await getResolver({ ...opts }).resolve(did, { accept: 'application/did+ld+json' }); if (result?.didResolutionMetadata?.error) { @@ -112,5 +120,5 @@ export async function resolveDidDocument(did: string, opts?: ResolveOpts): Promi // todo: This looks like a bug. It seems that sometimes we get back a DID document directly instead of a did resolution results return result as unknown as DIDDocument; } - return result.didDocument; + return result.didDocument ?? undefined; } diff --git a/packages/siopv2/src/did/DidJWT.ts b/packages/siopv2/src/did/DidJWT.ts index 76e203bd..f71f1fbc 100644 --- a/packages/siopv2/src/did/DidJWT.ts +++ b/packages/siopv2/src/did/DidJWT.ts @@ -10,15 +10,15 @@ import { JWTPayload, JWTVerifyOptions, Signer, - verifyJWT, -} from 'did-jwt'; -import { JWTDecoded } from 'did-jwt/lib/JWT'; -import { Resolvable } from 'did-resolver'; + verifyJWT +} from 'did-jwt' +import { JWTDecoded } from 'did-jwt/lib/JWT' +import { Resolvable } from 'did-resolver' -import { ClaimPayloadCommonOpts } from '../authorization-request'; -import { AuthorizationResponseOpts } from '../authorization-response'; -import { post } from '../helpers'; -import { RequestObjectOpts } from '../request-object'; +import { ClaimPayloadCommonOpts } from '../authorization-request' +import { AuthorizationResponseOpts } from '../authorization-response' +import { post } from '../helpers' +import { RequestObjectOpts } from '../request-object' import { DEFAULT_EXPIRATION_TIME, IDTokenPayload, @@ -31,8 +31,8 @@ import { SigningAlgo, SIOPErrors, SIOPResonse, - VerifiedJWT, -} from '../types'; + VerifiedJWT +} from '../types' /** * Verifies given JWT. If the JWT is valid, the promise returns an object including the JWT, the payload of the JWT, @@ -56,7 +56,7 @@ import { * @return {Promise} a promise which resolves with a response object or rejects with an error */ export async function verifyDidJWT(jwt: string, resolver: Resolvable, options: JWTVerifyOptions): Promise { - return verifyJWT(jwt, { ...options, resolver }); + return verifyJWT(jwt, { ...options, resolver }) } /** @@ -79,57 +79,72 @@ export async function verifyDidJWT(jwt: string, resolver: Resolvable, options: J export async function createDidJWT( payload: Partial, { issuer, signer, expiresIn, canonicalize }: JWTOptions, - header: Partial, + header: Partial ): Promise { - return createJWT(payload, { issuer, signer, expiresIn, canonicalize }, header); + return createJWT(payload, { issuer, signer, expiresIn, canonicalize }, header) } export async function signIDTokenPayload(payload: IDTokenPayload, opts: AuthorizationResponseOpts) { + if (!opts.signature) { + throw Error(SIOPErrors.BAD_SIGNATURE_PARAMS) + } if (!payload.sub) { - payload.sub = opts.signature.did; + payload.sub = opts.signature.did } - const issuer = opts.registration.issuer || payload.iss; + const issuer = opts.registration?.issuer ?? payload.iss if (!issuer || !(issuer.includes(ResponseIss.SELF_ISSUED_V2) || issuer === payload.sub)) { - throw new Error(SIOPErrors.NO_SELFISSUED_ISS); + throw new Error(SIOPErrors.NO_SELFISSUED_ISS) } if (!payload.iss) { - payload.iss = issuer; + payload.iss = issuer } if (isInternalSignature(opts.signature)) { - return signDidJwtInternal(payload, issuer, opts.signature.hexPrivateKey, opts.signature.alg, opts.signature.kid, opts.signature.customJwtSigner); + if (!opts.signature.kid) { + throw Error(SIOPErrors.BAD_SIGNATURE_PARAMS) + } + return signDidJwtInternal(payload, issuer, opts.signature.hexPrivateKey, opts.signature.alg, opts.signature.kid, opts.signature.customJwtSigner) } else if (isExternalSignature(opts.signature)) { - return signDidJwtExternal(payload, opts.signature.signatureUri, opts.signature.authZToken, opts.signature.alg, opts.signature.kid); + if (!opts.signature.authZToken) { + throw Error(SIOPErrors.BAD_SIGNATURE_PARAMS) + } + return signDidJwtExternal(payload, opts.signature.signatureUri, opts.signature.authZToken, opts.signature.alg, opts.signature.kid) } else if (isSuppliedSignature(opts.signature)) { - return signDidJwtSupplied(payload, issuer, opts.signature.signature, opts.signature.alg, opts.signature.kid); + return signDidJwtSupplied(payload, issuer, opts.signature.signature, opts.signature.alg, opts.signature.kid) } else { - throw new Error(SIOPErrors.BAD_SIGNATURE_PARAMS); + throw Error(SIOPErrors.BAD_SIGNATURE_PARAMS) } } export async function signRequestObjectPayload(payload: RequestObjectPayload, opts: RequestObjectOpts) { - let issuer = payload.iss; + let issuer = payload.iss if (!issuer) { - issuer = opts.signature.did; + issuer = opts.signature.did } if (!issuer) { - throw Error('No issuer supplied to sign the JWT'); + throw Error('No issuer supplied to sign the JWT') } if (!payload.iss) { - payload.iss = issuer; + payload.iss = issuer } if (!payload.sub) { - payload.sub = opts.signature.did; + payload.sub = opts.signature.did } if (isInternalSignature(opts.signature)) { - return signDidJwtInternal(payload, issuer, opts.signature.hexPrivateKey, opts.signature.alg, opts.signature.kid, opts.signature.customJwtSigner); + if (!opts.signature.kid) { + throw Error(SIOPErrors.BAD_SIGNATURE_PARAMS) + } + return signDidJwtInternal(payload, issuer, opts.signature.hexPrivateKey, opts.signature.alg, opts.signature.kid, opts.signature.customJwtSigner) } else if (isExternalSignature(opts.signature)) { - return signDidJwtExternal(payload, opts.signature.signatureUri, opts.signature.authZToken, opts.signature.alg, opts.signature.kid); + if (!opts.signature.authZToken) { + throw Error(SIOPErrors.BAD_SIGNATURE_PARAMS) + } + return signDidJwtExternal(payload, opts.signature.signatureUri, opts.signature.authZToken, opts.signature.alg, opts.signature.kid) } else if (isSuppliedSignature(opts.signature)) { - return signDidJwtSupplied(payload, issuer, opts.signature.signature, opts.signature.alg, opts.signature.kid); + return signDidJwtSupplied(payload, issuer, opts.signature.signature, opts.signature.alg, opts.signature.kid) } else { - throw new Error(SIOPErrors.BAD_SIGNATURE_PARAMS); + throw new Error(SIOPErrors.BAD_SIGNATURE_PARAMS) } } @@ -139,20 +154,20 @@ async function signDidJwtInternal( hexPrivateKey: string, alg: SigningAlgo, kid: string, - customJwtSigner?: Signer, + customJwtSigner?: Signer ): Promise { - const signer = determineSigner(alg, hexPrivateKey, customJwtSigner); + const signer = determineSigner(alg, hexPrivateKey, customJwtSigner) const header = { alg, - kid, - }; + kid + } const options = { issuer, signer, - expiresIn: DEFAULT_EXPIRATION_TIME, - }; + expiresIn: DEFAULT_EXPIRATION_TIME + } - return await createDidJWT({ ...payload }, options, header); + return await createDidJWT({ ...payload }, options, header) } async function signDidJwtExternal( @@ -160,19 +175,22 @@ async function signDidJwtExternal( signatureUri: string, authZToken: string, alg: SigningAlgo, - kid?: string, + kid?: string ): Promise { const body = { issuer: payload.iss && payload.iss.includes('did:') ? payload.iss : payload.sub, payload, expiresIn: DEFAULT_EXPIRATION_TIME, alg, - selfIssued: payload.iss.includes(ResponseIss.SELF_ISSUED_V2) ? payload.iss : undefined, - kid, - }; + selfIssued: payload.iss?.includes(ResponseIss.SELF_ISSUED_V2) ? payload.iss : undefined, + kid + } - const response: SIOPResonse = await post(signatureUri, JSON.stringify(body), { bearerToken: authZToken }); - return response.successBody.jws; + const response: SIOPResonse = await post(signatureUri, JSON.stringify(body), { bearerToken: authZToken }) + if (!response.successBody) { + throw Error(await response.origResponse.text()) + } + return response.successBody.jws } async function signDidJwtSupplied( @@ -180,125 +198,125 @@ async function signDidJwtSupplied( issuer: string, signer: Signer, alg: SigningAlgo, - kid: string, + kid: string ): Promise { const header = { alg, - kid, - }; + kid + } const options = { issuer, signer, - expiresIn: DEFAULT_EXPIRATION_TIME, - }; + expiresIn: DEFAULT_EXPIRATION_TIME + } - return await createDidJWT({ ...payload }, options, header); + return await createDidJWT({ ...payload }, options, header) } const determineSigner = (alg: SigningAlgo, hexPrivateKey?: string, customSigner?: Signer): Signer => { if (customSigner) { - return customSigner; + return customSigner } else if (!hexPrivateKey) { - throw new Error('no private key provided'); + throw new Error('no private key provided') } - const privateKey = hexToBytes(hexPrivateKey.replace('0x', '')); + const privateKey = hexToBytes(hexPrivateKey.replace('0x', '')) switch (alg) { case SigningAlgo.EDDSA: - return EdDSASigner(privateKey); + return EdDSASigner(privateKey) case SigningAlgo.ES256: - return ES256Signer(privateKey); + return ES256Signer(privateKey) case SigningAlgo.ES256K: - return ES256KSigner(privateKey); + return ES256KSigner(privateKey) case SigningAlgo.PS256: - throw Error('PS256 is not supported yet. Please provide a custom signer'); + throw Error('PS256 is not supported yet. Please provide a custom signer') case SigningAlgo.RS256: - throw Error('RS256 is not supported yet. Please provide a custom signer'); + throw Error('RS256 is not supported yet. Please provide a custom signer') } -}; +} export function getAudience(jwt: string) { - const { payload } = decodeJWT(jwt); + const { payload } = decodeJWT(jwt) if (!payload) { - throw new Error(SIOPErrors.NO_AUDIENCE); + throw new Error(SIOPErrors.NO_AUDIENCE) } else if (!payload.aud) { - return undefined; + return undefined } else if (Array.isArray(payload.aud)) { - throw new Error(SIOPErrors.INVALID_AUDIENCE); + throw new Error(SIOPErrors.INVALID_AUDIENCE) } - return payload.aud; + return payload.aud } //TODO To enable automatic registration, it cannot be a did, but HTTPS URL function assertIssSelfIssuedOrDid(payload: JWTPayload) { if (!payload.sub || !payload.sub.startsWith('did:') || !payload.iss || !isIssSelfIssued(payload)) { - throw new Error(SIOPErrors.NO_ISS_DID); + throw new Error(SIOPErrors.NO_ISS_DID) } } -export function getSubDidFromPayload(payload: JWTPayload, header?: JWTHeader): string { - assertIssSelfIssuedOrDid(payload); +export function getSubDidFromPayload(payload: JWTPayload, header?: JWTHeader): string | undefined { + assertIssSelfIssuedOrDid(payload) if (isIssSelfIssued(payload)) { - let did; + let did if (payload.sub && payload.sub.startsWith('did:')) { - did = payload.sub; + did = payload.sub } if (!did && header && header.kid && header.kid.startsWith('did:')) { - did = header.kid.split('#')[0]; + did = header.kid.split('#')[0] } if (did) { - return did; + return did } } - return payload.sub; + return payload.sub } export function isIssSelfIssued(payload: JWTPayload): boolean { - return payload.iss.includes(ResponseIss.SELF_ISSUED_V1) || payload.iss.includes(ResponseIss.SELF_ISSUED_V2) || payload.iss === payload.sub; + return payload.iss?.includes(ResponseIss.SELF_ISSUED_V1) ?? payload.iss?.includes(ResponseIss.SELF_ISSUED_V2) ?? payload.iss === payload.sub } -export function getIssuerDidFromJWT(jwt: string): string { - const { payload } = parseJWT(jwt); - return getSubDidFromPayload(payload); +export function getIssuerDidFromJWT(jwt: string): string | undefined { + const { payload } = parseJWT(jwt) + return getSubDidFromPayload(payload) } export function parseJWT(jwt: string): JWTDecoded { - const decodedJWT = decodeJWT(jwt); - const { payload, header } = decodedJWT; + const decodedJWT = decodeJWT(jwt) + const { payload, header } = decodedJWT if (!payload || !header) { - throw new Error(SIOPErrors.NO_JWT); + throw new Error(SIOPErrors.NO_JWT) } - return decodedJWT; + return decodedJWT } export function getMethodFromDid(did: string): string { if (!did) { - throw new Error(SIOPErrors.BAD_PARAMS); + throw new Error(SIOPErrors.BAD_PARAMS) } - const split = did.split(':'); + const split = did.split(':') if (split.length == 1 && did.length > 0) { - return did; + return did } else if (!did.startsWith('did:') || split.length < 2) { - throw new Error(SIOPErrors.BAD_PARAMS); + throw new Error(SIOPErrors.BAD_PARAMS) } - return split[1]; + return split[1] } export function getNetworkFromDid(did: string): string { - const network = 'mainnet'; // default - const split = did.split(':'); + const network = 'mainnet' // default + const split = did.split(':') if (!did.startsWith('did:') || split.length < 2) { - throw new Error(SIOPErrors.BAD_PARAMS); + throw new Error(SIOPErrors.BAD_PARAMS) } if (split.length === 4) { - return split[2]; + return split[2] } else if (split.length > 4) { - return `${split[2]}:${split[3]}`; + return `${split[2]}:${split[3]}` } - return network; + return network } /** @@ -306,10 +324,10 @@ export function getNetworkFromDid(did: string): string { * @param didOrMethod */ export function toSIOPRegistrationDidMethod(didOrMethod: string) { - let prefix = didOrMethod; + let prefix = didOrMethod if (!didOrMethod.startsWith('did:')) { - prefix = 'did:' + didOrMethod; + prefix = 'did:' + didOrMethod } - const split = prefix.split(':'); - return `${split[0]}:${split[1]}`; + const split = prefix.split(':') + return `${split[0]}:${split[1]}` } diff --git a/packages/siopv2/src/did/LinkedDomainValidations.ts b/packages/siopv2/src/did/LinkedDomainValidations.ts index ab8af62c..5230b218 100644 --- a/packages/siopv2/src/did/LinkedDomainValidations.ts +++ b/packages/siopv2/src/did/LinkedDomainValidations.ts @@ -1,3 +1,4 @@ +import { BAD_PARAMS } from '@sphereon/oid4vc-common' import { IDomainLinkageValidation, ValidationStatusEnum, VerifyCallback, WDCErrors, WellKnownDidVerifier } from '@sphereon/wellknown-dids-client'; import { CheckLinkedDomain, DIDDocument, ExternalVerification, InternalVerification } from '../types'; @@ -10,7 +11,7 @@ function getValidationErrorMessages(validationResult: IDomainLinkageValidation): if (validationResult.message) { messages.push(validationResult.message); } - if (validationResult?.endpointDescriptors.length) { + if (validationResult?.endpointDescriptors?.length) { for (const endpointDescriptor of validationResult.endpointDescriptors) { if (endpointDescriptor.message) { messages.push(endpointDescriptor.message); @@ -67,6 +68,9 @@ export async function validateLinkedDomainWithDid(did: string, verification: Int return; } try { + if (typeof wellknownDIDVerifyCallback !== 'function') { + throw Error(BAD_PARAMS + `: verify callback not supplied`) + } const validationResult = await checkWellKnownDid({ didDocument, verifyCallback: wellknownDIDVerifyCallback }); if (validationResult.status === ValidationStatusEnum.INVALID) { const validationErrorMessages = getValidationErrorMessages(validationResult); @@ -75,7 +79,9 @@ export async function validateLinkedDomainWithDid(did: string, verification: Int throw new Error(messageCondition.message ? messageCondition.message : validationErrorMessages[0]); } } - } catch (err) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + } catch (err: any) { const messageCondition: { status: boolean; message?: string } = checkInvalidMessages([err.message]); if (checkLinkedDomain === CheckLinkedDomain.ALWAYS || (checkLinkedDomain === CheckLinkedDomain.IF_PRESENT && !messageCondition.status)) { throw new Error(err.message); diff --git a/packages/siopv2/src/helpers/Encodings.ts b/packages/siopv2/src/helpers/Encodings.ts index af335a7a..7560ff67 100644 --- a/packages/siopv2/src/helpers/Encodings.ts +++ b/packages/siopv2/src/helpers/Encodings.ts @@ -14,9 +14,16 @@ export function decodeUriAsJson(uri: string) { } const parts = parse(queryString, { plainObjects: true, depth: 10, parameterLimit: 5000, ignoreQueryPrefix: true }); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore const descriptors = parts?.claims?.['vp_token']?.presentation_definition?.['input_descriptors']; if (descriptors && Array.isArray(descriptors)) { + if (!parts.claims) { + parts.claims = {} + } // Whenever we have a [{'uri': 'str1'}, 'uri': 'str2'] qs changes this to {uri: ['str1','str2']} which means schema validation fails. So we have to fix that + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore parts.claims['vp_token'].presentation_definition['input_descriptors'] = descriptors.map((descriptor: InputDescriptorV1) => { if (Array.isArray(descriptor.schema)) { descriptor.schema = descriptor.schema.flatMap((val) => { @@ -32,7 +39,7 @@ export function decodeUriAsJson(uri: string) { }); } - const json = {}; + const json: Record = {}; for (const key in parts) { const value = parts[key]; if (!value) { @@ -67,6 +74,8 @@ export function encodeJsonAsURI(json: unknown, _opts?: { arraysWithIndex?: strin return encodeURIComponent(key.replace(' ', '')); } + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore for (const [key, value] of Object.entries(json)) { if (!value) { continue; diff --git a/packages/siopv2/src/helpers/HttpUtils.ts b/packages/siopv2/src/helpers/HttpUtils.ts index a44a17b5..dcc666b7 100644 --- a/packages/siopv2/src/helpers/HttpUtils.ts +++ b/packages/siopv2/src/helpers/HttpUtils.ts @@ -63,11 +63,17 @@ const siopFetch = async ( } const headers = opts?.customHeaders ? opts.customHeaders : {}; if (opts?.bearerToken) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore headers['Authorization'] = `Bearer ${opts.bearerToken}`; } const method = opts?.method ? opts.method : body ? 'POST' : 'GET'; const accept = opts?.accept ? opts.accept : 'application/json'; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore headers['Content-Type'] = opts?.contentType ? opts.contentType : method !== 'GET' ? 'application/json' : undefined; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore headers['Accept'] = accept; const payload: RequestInit = { @@ -87,6 +93,8 @@ const siopFetch = async ( const textResponseBody = await clonedResponse.text(); const isJSONResponse = + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore (accept === 'application/json' || origResponse.headers['Content-Type'] === 'application/json') && textResponseBody.trim().startsWith('{'); const responseBody = isJSONResponse ? JSON.parse(textResponseBody) : textResponseBody; @@ -119,15 +127,18 @@ export const getWithUrl = async (url: string, textResponse?: boolean): Promis }*/ }; -export const fetchByReferenceOrUseByValue = async (referenceURI: string, valueObject: T, textResponse?: boolean): Promise => { - let response: T = valueObject; +export const fetchByReferenceOrUseByValue = async (referenceURI?: string, valueObject?: T, textResponse?: boolean): Promise => { + let response: T | undefined = valueObject; if (referenceURI) { try { response = await getWithUrl(referenceURI, textResponse); - } catch (e) { + } catch (e: unknown) { console.log(e); - throw new Error(`${SIOPErrors.REG_PASS_BY_REFERENCE_INCORRECTLY}: ${e.message}, URL: ${referenceURI}`); + throw new Error(`${SIOPErrors.REG_PASS_BY_REFERENCE_INCORRECTLY}: ${e instanceof Error ? e.message : e}, URL: ${referenceURI}`); } } + if (!response) { + throw Error(`Could not pass by value ${valueObject} or reference ${referenceURI}`) + } return response; }; diff --git a/packages/siopv2/src/helpers/Keys.ts b/packages/siopv2/src/helpers/Keys.ts index 896325a9..ec1a9865 100644 --- a/packages/siopv2/src/helpers/Keys.ts +++ b/packages/siopv2/src/helpers/Keys.ts @@ -82,8 +82,8 @@ export const getThumbprint = async (hexPrivateKey: string, did: string): Promise }; */ -const check = (value, description) => { - if (typeof value !== 'string' || !value) { +const check = (value: string | undefined, description: string) => { + if (!value) { throw Error(`${description} missing or invalid`); } }; diff --git a/packages/siopv2/src/helpers/LanguageTagUtils.ts b/packages/siopv2/src/helpers/LanguageTagUtils.ts index 68ad2b8f..ee7d4055 100644 --- a/packages/siopv2/src/helpers/LanguageTagUtils.ts +++ b/packages/siopv2/src/helpers/LanguageTagUtils.ts @@ -14,7 +14,7 @@ export class LanguageTagUtils { * @param source is the object from which the language enabled fields and their values will be extracted. */ static getAllLanguageTaggedProperties(source: unknown): Map { - return this.getLanguageTaggedPropertiesMapped(source, undefined); + return this.getLanguageTaggedPropertiesMapped(source, new Map()); } /** @@ -42,6 +42,8 @@ export class LanguageTagUtils { const discoveredLanguageTaggedFields: Map = new Map(); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore Object.entries(source).forEach(([key, value]) => { const languageTagSeparatorIndexInKey: number = key.indexOf(this.LANGUAGE_TAG_SEPARATOR); @@ -102,7 +104,7 @@ export class LanguageTagUtils { return languageTagSeparatorIndex > 0; } - private static assertValidTargetFieldNames(languageTagEnabledFieldsNamesMapping: Map): void { + private static assertValidTargetFieldNames(languageTagEnabledFieldsNamesMapping?: Map): void { if (languageTagEnabledFieldsNamesMapping) { if (!languageTagEnabledFieldsNamesMapping.size) { throw new Error(SIOPErrors.BAD_PARAMS + ' LanguageTagEnabledFieldsNamesMapping must be non-null or non-empty'); diff --git a/packages/siopv2/src/helpers/Metadata.ts b/packages/siopv2/src/helpers/Metadata.ts index b059fc16..48b986bc 100644 --- a/packages/siopv2/src/helpers/Metadata.ts +++ b/packages/siopv2/src/helpers/Metadata.ts @@ -1,120 +1,132 @@ -import { Format } from '@sphereon/pex-models'; +import { Format } from '@sphereon/pex-models' import { CommonSupportedMetadata, DiscoveryMetadataPayload, RPRegistrationMetadataPayload, SIOPErrors, - SubjectSyntaxTypesSupportedValues, -} from '../types'; + SubjectSyntaxTypesSupportedValues +} from '../types' export function assertValidMetadata(opMetadata: DiscoveryMetadataPayload, rpMetadata: RPRegistrationMetadataPayload): CommonSupportedMetadata { - let subjectSyntaxTypesSupported = []; - const credentials = supportedCredentialsFormats(rpMetadata.vp_formats, opMetadata.vp_formats); - const isValidSubjectSyntax = verifySubjectSyntaxes(rpMetadata.subject_syntax_types_supported); - if (isValidSubjectSyntax && rpMetadata.subject_syntax_types_supported) { - subjectSyntaxTypesSupported = supportedSubjectSyntaxTypes(rpMetadata.subject_syntax_types_supported, opMetadata.subject_syntax_types_supported); + let subjectSyntaxTypesSupported: string[] = [] + const credentials = supportedCredentialsFormats(rpMetadata.vp_formats, opMetadata.vp_formats) + const isValidSubjectSyntax = verifySubjectSyntaxes(rpMetadata.subject_syntax_types_supported) + if (isValidSubjectSyntax && rpMetadata.subject_syntax_types_supported && opMetadata.subject_syntax_types_supported) { + subjectSyntaxTypesSupported = supportedSubjectSyntaxTypes(rpMetadata.subject_syntax_types_supported, opMetadata.subject_syntax_types_supported) } else if (isValidSubjectSyntax && (!rpMetadata.subject_syntax_types_supported || !rpMetadata.subject_syntax_types_supported.length)) { - if (opMetadata.subject_syntax_types_supported || opMetadata.subject_syntax_types_supported.length) { - subjectSyntaxTypesSupported = [...opMetadata.subject_syntax_types_supported]; + if (opMetadata.subject_syntax_types_supported && Array.isArray(opMetadata.subject_syntax_types_supported)) { + subjectSyntaxTypesSupported = [...opMetadata.subject_syntax_types_supported] } } - return { vp_formats: credentials, subject_syntax_types_supported: subjectSyntaxTypesSupported }; + return { vp_formats: credentials, subject_syntax_types_supported: subjectSyntaxTypesSupported } } function getIntersection(rpMetadata: Array | T, opMetadata: Array | T): Array { - let arrayA, arrayB; + let arrayA, arrayB: Array if (!Array.isArray(rpMetadata)) { - arrayA = [rpMetadata]; + arrayA = [rpMetadata] } else { - arrayA = rpMetadata; + arrayA = rpMetadata } if (!Array.isArray(opMetadata)) { - arrayB = [opMetadata]; + arrayB = [opMetadata] } else { - arrayB = opMetadata; + arrayB = opMetadata } - return arrayA.filter((value) => arrayB.includes(value)); + return arrayA.filter((value) => arrayB.includes(value)) } -function verifySubjectSyntaxes(subjectSyntaxTypesSupported: string[]): boolean { - if (subjectSyntaxTypesSupported.length) { - if (Array.isArray(subjectSyntaxTypesSupported)) { - if ( - subjectSyntaxTypesSupported.length === - subjectSyntaxTypesSupported.filter( - (sst) => - sst.includes(SubjectSyntaxTypesSupportedValues.DID.valueOf()) || sst === SubjectSyntaxTypesSupportedValues.JWK_THUMBPRINT.valueOf(), - ).length - ) { - return true; - } +function verifySubjectSyntaxes(subjectSyntaxTypesSupported?: string[]): boolean { + + if (subjectSyntaxTypesSupported && Array.isArray(subjectSyntaxTypesSupported)) { + if ( + subjectSyntaxTypesSupported.length === + subjectSyntaxTypesSupported.filter( + (sst) => + sst.includes(SubjectSyntaxTypesSupportedValues.DID.valueOf()) || sst === SubjectSyntaxTypesSupportedValues.JWK_THUMBPRINT.valueOf() + ).length + ) { + return true } } - return false; + + return false } function supportedSubjectSyntaxTypes(rpMethods: string[] | string, opMethods: string[] | string): Array { - const rpMethodsList = Array.isArray(rpMethods) ? rpMethods : [rpMethods]; - const opMethodsList = Array.isArray(opMethods) ? opMethods : [opMethods]; - const supportedSubjectSyntaxTypes = getIntersection(rpMethodsList, opMethodsList); + const rpMethodsList = Array.isArray(rpMethods) ? rpMethods : [rpMethods] + const opMethodsList = Array.isArray(opMethods) ? opMethods : [opMethods] + const supportedSubjectSyntaxTypes = getIntersection(rpMethodsList, opMethodsList) if (supportedSubjectSyntaxTypes.indexOf(SubjectSyntaxTypesSupportedValues.DID.valueOf()) !== -1) { - return [SubjectSyntaxTypesSupportedValues.DID.valueOf()]; + return [SubjectSyntaxTypesSupportedValues.DID.valueOf()] } if (rpMethodsList.includes(SubjectSyntaxTypesSupportedValues.DID.valueOf())) { - const supportedExtendedDids: string[] = opMethodsList.filter((method) => method.startsWith('did:')); + const supportedExtendedDids: string[] = opMethodsList.filter((method) => method.startsWith('did:')) if (supportedExtendedDids.length) { - return supportedExtendedDids; + return supportedExtendedDids } } if (opMethodsList.includes(SubjectSyntaxTypesSupportedValues.DID.valueOf())) { - const supportedExtendedDids: string[] = rpMethodsList.filter((method) => method.startsWith('did:')); + const supportedExtendedDids: string[] = rpMethodsList.filter((method) => method.startsWith('did:')) if (supportedExtendedDids.length) { - return supportedExtendedDids; + return supportedExtendedDids } } if (!supportedSubjectSyntaxTypes.length) { - throw Error(SIOPErrors.DID_METHODS_NOT_SUPORTED); + throw Error(SIOPErrors.DID_METHODS_NOT_SUPORTED) } - const supportedDidMethods = supportedSubjectSyntaxTypes.filter((sst) => sst.includes('did:')); + const supportedDidMethods = supportedSubjectSyntaxTypes.filter((sst) => sst.includes('did:')) if (supportedDidMethods.length) { - return supportedDidMethods; + return supportedDidMethods } - return supportedSubjectSyntaxTypes; + return supportedSubjectSyntaxTypes } function getFormatIntersection(rpFormat: Format, opFormat: Format): Format { - const intersectionFormat: Format = {}; - const supportedCredentials = getIntersection(Object.keys(rpFormat), Object.keys(opFormat)); + const intersectionFormat: Format = {} + const supportedCredentials = getIntersection(Object.keys(rpFormat), Object.keys(opFormat)) if (!supportedCredentials.length) { - throw new Error(SIOPErrors.CREDENTIAL_FORMATS_NOT_SUPPORTED); + throw new Error(SIOPErrors.CREDENTIAL_FORMATS_NOT_SUPPORTED) } - supportedCredentials.forEach(function (crFormat: string) { - const rpAlgs = []; - const opAlgs = []; - Object.keys(rpFormat[crFormat]).forEach((k) => rpAlgs.push(...rpFormat[crFormat][k])); - Object.keys(opFormat[crFormat]).forEach((k) => opAlgs.push(...opFormat[crFormat][k])); - let methodKeyRP = undefined; - let methodKeyOP = undefined; - Object.keys(rpFormat[crFormat]).forEach((k) => (methodKeyRP = k)); - Object.keys(opFormat[crFormat]).forEach((k) => (methodKeyOP = k)); + // FIXME: This looks buggy + supportedCredentials.forEach((crFormat: string) => { + const rpAlgs: string[] = [] + const opAlgs: string[] = [] + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + Object.keys(rpFormat[crFormat]).forEach((k) => rpAlgs.push(...rpFormat[crFormat][k])) + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + Object.keys(opFormat[crFormat]).forEach((k) => opAlgs.push(...opFormat[crFormat][k])) + let methodKeyRP = undefined + let methodKeyOP = undefined + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + Object.keys(rpFormat[crFormat]).forEach((k) => (methodKeyRP = k)) + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + Object.keys(opFormat[crFormat]).forEach((k) => (methodKeyOP = k)) if (methodKeyRP !== methodKeyOP) { - throw new Error(SIOPErrors.CREDENTIAL_FORMATS_NOT_SUPPORTED); + throw new Error(SIOPErrors.CREDENTIAL_FORMATS_NOT_SUPPORTED) } - const algs = getIntersection(rpAlgs, opAlgs); + const algs = getIntersection(rpAlgs, opAlgs) if (!algs.length) { - throw new Error(SIOPErrors.CREDENTIAL_FORMATS_NOT_SUPPORTED); + throw new Error(SIOPErrors.CREDENTIAL_FORMATS_NOT_SUPPORTED) + } + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + intersectionFormat[crFormat] = { + methodKeyOP: algs } - intersectionFormat[crFormat] = {}; - intersectionFormat[crFormat][methodKeyOP] = algs; - }); - return intersectionFormat; + }) + return intersectionFormat } export function supportedCredentialsFormats(rpFormat: Format, opFormat: Format): Format { if (!rpFormat || !opFormat || !Object.keys(rpFormat).length || !Object.keys(opFormat).length) { - throw new Error(SIOPErrors.CREDENTIALS_FORMATS_NOT_PROVIDED); + throw new Error(SIOPErrors.CREDENTIALS_FORMATS_NOT_PROVIDED) } - return getFormatIntersection(rpFormat, opFormat); + return getFormatIntersection(rpFormat, opFormat) } diff --git a/packages/siopv2/src/helpers/ObjectUtils.ts b/packages/siopv2/src/helpers/ObjectUtils.ts index 9cfbddca..37a426c7 100644 --- a/packages/siopv2/src/helpers/ObjectUtils.ts +++ b/packages/siopv2/src/helpers/ObjectUtils.ts @@ -9,18 +9,20 @@ export function isStringNullOrEmpty(key: string) { return !key || !key.length; } -export function removeNullUndefined(data: unknown) { +export function removeNullUndefined(data: unknown): T { if (!data) { - return data; + return data as T; } //transform properties into key-values pairs and filter all the empty-values const entries = Object.entries(data).filter(([, value]) => value != null); //map through all the remaining properties and check if the value is an object. //if value is object, use recursion to remove empty properties + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore const clean = entries.map(([key, v]) => { const value = typeof v === 'object' && !Array.isArray(v) ? removeNullUndefined(v) : v; return [key, value]; }); //transform the key-value pairs back to an object. - return Object.fromEntries(clean); + return Object.fromEntries(clean) as T; } diff --git a/packages/siopv2/src/helpers/State.ts b/packages/siopv2/src/helpers/State.ts index bc872ed0..12b7f3d9 100644 --- a/packages/siopv2/src/helpers/State.ts +++ b/packages/siopv2/src/helpers/State.ts @@ -1,14 +1,21 @@ +import { BAD_PARAMS } from '@sphereon/oid4vc-common'; import SHA from 'sha.js'; import { v4 as uuidv4 } from 'uuid'; import { base64urlEncodeBuffer } from './Encodings'; -export function getNonce(state: string, nonce?: string) { - return nonce || toNonce(state); +export function getNonce(state?: string, nonce?: string) { + if (!nonce && !state) { + throw Error(BAD_PARAMS + ': nonce or state required'); + } + + return nonce ?? toNonce(state); } -export function toNonce(input: string): string { - const buff = SHA('sha256').update(input).digest(); +export function toNonce(input?: string): string { + const buff = SHA('sha256') + .update(input ?? uuidv4()) + .digest(); return base64urlEncodeBuffer(buff); } diff --git a/packages/siopv2/src/id-token/IDToken.ts b/packages/siopv2/src/id-token/IDToken.ts index 9c8437f6..978656ea 100644 --- a/packages/siopv2/src/id-token/IDToken.ts +++ b/packages/siopv2/src/id-token/IDToken.ts @@ -37,7 +37,7 @@ export class IDToken { if (!authorizationRequestPayload) { throw new Error(SIOPErrors.NO_REQUEST); } - const idToken = new IDToken(null, await createIDTokenPayload(verifiedAuthorizationRequest, responseOpts), responseOpts); + const idToken = new IDToken(undefined, await createIDTokenPayload(verifiedAuthorizationRequest, responseOpts), responseOpts); if (verifyOpts) { await idToken.verify(verifyOpts); } @@ -63,7 +63,7 @@ export class IDToken { if (!idTokenPayload) { throw new Error(SIOPErrors.NO_JWT); } - const idToken = new IDToken(null, idTokenPayload, responseOpts); + const idToken = new IDToken(undefined, idTokenPayload, responseOpts); if (verifyOpts) { await idToken.verify(verifyOpts); } diff --git a/packages/siopv2/src/op/Opts.ts b/packages/siopv2/src/op/Opts.ts index d55fe3a8..ddd8615c 100644 --- a/packages/siopv2/src/op/Opts.ts +++ b/packages/siopv2/src/op/Opts.ts @@ -33,8 +33,8 @@ export const createResponseOptsFromBuilderOrExistingOpts = (opts: { ...(responseOpts?.version ? { version: responseOpts.version } : Array.isArray(opts.builder.supportedVersions) && opts.builder.supportedVersions.length > 0 - ? { version: opts.builder.supportedVersions[0] } - : {}), + ? { version: opts.builder.supportedVersions[0] } + : {}), }; if (!responseOpts.registration.passBy) { diff --git a/packages/siopv2/src/request-object/RequestObject.ts b/packages/siopv2/src/request-object/RequestObject.ts index 597217ea..93b5d2b2 100644 --- a/packages/siopv2/src/request-object/RequestObject.ts +++ b/packages/siopv2/src/request-object/RequestObject.ts @@ -49,7 +49,7 @@ export class RequestObject { return new RequestObject(mergedOpts, await createRequestObjectPayload(mergedOpts)); } - public static async fromJwt(requestObjectJwt: RequestObjectJwt) { + public static async fromJwt(requestObjectJwt?: RequestObjectJwt) { return requestObjectJwt ? new RequestObject(undefined, undefined, requestObjectJwt) : undefined; } diff --git a/packages/siopv2/tsconfig.build.json b/packages/siopv2/tsconfig.build.json index 57c88787..ae51dbff 100644 --- a/packages/siopv2/tsconfig.build.json +++ b/packages/siopv2/tsconfig.build.json @@ -1,6 +1,6 @@ { "extends": "./tsconfig.json", - "exclude": ["test/**/*.ts", "generator/**/*.ts"], + "exclude": ["__tests__/**/*.ts", "generator/**/*.ts"], "ts-node": { "esm": false } diff --git a/packages/siopv2/tsconfig.json b/packages/siopv2/tsconfig.json index 3cef7c88..9d12ceaa 100644 --- a/packages/siopv2/tsconfig.json +++ b/packages/siopv2/tsconfig.json @@ -10,29 +10,38 @@ "inlineSourceMap": true, "allowJs": true, "esModuleInterop": true, - /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ "resolveJsonModule": true, - /* Include modules imported with .json extension. */ // "strict": true /* Enable all strict type-checking options. */, + /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + "resolveJsonModule": true, + /* Include modules imported with .json extension. */ + // "strict": true /* Enable all strict type-checking options. */, /* Additional Checks */ "noUnusedLocals": true, - /* Report errors on unused locals. */ "noUnusedParameters": true, - /* Report errors on unused parameters. */ "noImplicitReturns": true, - /* Report error when not all code paths in function return a value. */ "noFallthroughCasesInSwitch": true, - /* Report errors for fallthrough cases in switch statement. */ /* Debugging Options */ "strict": false, + /* Report errors on unused locals. */ + "noUnusedParameters": true, + /* Report errors on unused parameters. */ + "noImplicitReturns": true, + /* Report error when not all code paths in function return a value. */ + "noFallthroughCasesInSwitch": true, + /* Report errors for fallthrough cases in switch statement. */ + /* Debugging Options */ + "strict": true, "traceResolution": false, - /* Report module resolution log messages. */ "listEmittedFiles": false, + /* Report module resolution log messages. */ + "listEmittedFiles": false, "skipLibCheck": true, - /* Print names of generated files part of the compilation. */ "listFiles": false, - /* Print names of files part of the compilation. */ "pretty": true, - + /* Print names of generated files part of the compilation. */ + "listFiles": false, + /* Print names of files part of the compilation. */ + "pretty": true, /*"lib": [ "ES2017" ],*/ - "types": ["jest", "node"] + "types": ["jest", "node"], }, - "include": ["index.ts", "src/**/*.ts", "src/**/*.js", "test/**/*.ts", "generator/*.ts"], + "include": ["index.ts", "src/**/*.ts", "src/**/*.js", "__tests__/**/*.ts", "generator/*.ts"], "exclude": ["node_modules/**", "dist/**"], "ts-node": { - "esm": true + "esm": true, }, - "compileOnSave": false + "compileOnSave": false, } diff --git a/packages/tsconfig.json b/packages/tsconfig.json index 30aa1d3e..3f7796e7 100644 --- a/packages/tsconfig.json +++ b/packages/tsconfig.json @@ -3,22 +3,22 @@ "files": [], "references": [ { - "path": "common" + "path": "common", }, { - "path": "issuer" + "path": "issuer", }, { - "path": "client" + "path": "client", }, { - "path": "issuer-rest" + "path": "issuer-rest", }, { - "path": "callback-example" + "path": "callback-example", }, { - "path": "siopv2" - } - ] + "path": "siopv2", + }, + ], } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3c243c39..3e79d88a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -424,6 +424,9 @@ importers: '@types/qs': specifier: ^6.9.11 version: 6.9.11 + '@types/sha.js': + specifier: ^2.4.4 + version: 2.4.4 '@types/uuid': specifier: ^9.0.7 version: 9.0.7 @@ -4865,6 +4868,12 @@ packages: '@types/node': 18.19.8 dev: true + /@types/sha.js@2.4.4: + resolution: {integrity: sha512-Qukd+D6S2Hm0wLVt2Vh+/eWBIoUt+wF8jWjBsG4F8EFQRwKtYvtXCPcNl2OEUQ1R+eTr3xuSaBYUyM3WD1x/Qw==} + dependencies: + '@types/node': 18.19.8 + dev: true + /@types/stack-utils@2.0.3: resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} @@ -7558,7 +7567,7 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 5.62.0(eslint@8.56.0)(typescript@4.9.5) + '@typescript-eslint/parser': 5.62.0(eslint@8.56.0)(typescript@5.3.3) debug: 3.2.7 eslint: 8.56.0 eslint-import-resolver-node: 0.3.9 @@ -7587,7 +7596,7 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 5.62.0(eslint@8.56.0)(typescript@4.9.5) + '@typescript-eslint/parser': 5.62.0(eslint@8.56.0)(typescript@5.3.3) array-includes: 3.1.7 array.prototype.findlastindex: 1.2.3 array.prototype.flat: 1.3.2