diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index c0e315b798..c71a2861e1 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -74,6 +74,7 @@ export { isDid, asArray, equalsIgnoreOrder, + DateTransformer, } from './utils' export * from './logger' export * from './error' diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts index 4aff975e6f..d5034609ae 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -14,3 +14,4 @@ export * from './objectEquality' export * from './MessageValidator' export * from './did' export * from './array' +export { DateTransformer } from './transformers' diff --git a/packages/core/src/utils/transformers.ts b/packages/core/src/utils/transformers.ts index 005f0065da..a19310b617 100644 --- a/packages/core/src/utils/transformers.ts +++ b/packages/core/src/utils/transformers.ts @@ -30,6 +30,7 @@ export function MetadataTransformer() { */ export function DateTransformer() { return Transform(({ value, type }) => { + if (value === undefined) return undefined if (type === TransformationType.CLASS_TO_PLAIN) { return value.toISOString() } diff --git a/packages/openid4vc/package.json b/packages/openid4vc/package.json index e92cb608b6..b8cd9de92f 100644 --- a/packages/openid4vc/package.json +++ b/packages/openid4vc/package.json @@ -26,9 +26,9 @@ "dependencies": { "@credo-ts/core": "0.5.0", "@sphereon/did-auth-siop": "0.6.2", - "@sphereon/oid4vci-client": "^0.10.1", + "@sphereon/oid4vci-client": "^0.10.2", "@sphereon/oid4vci-common": "^0.10.1", - "@sphereon/oid4vci-issuer": "^0.10.1", + "@sphereon/oid4vci-issuer": "^0.10.2", "@sphereon/ssi-types": "^0.18.1", "rxjs": "^7.8.0" }, diff --git a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerApi.ts b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerApi.ts index e5b042733c..9011424af8 100644 --- a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerApi.ts +++ b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerApi.ts @@ -29,7 +29,15 @@ export class OpenId4VcIssuerApi { return this.openId4VcIssuerService.getAllIssuers(this.agentContext) } + /** + * @deprecated use {@link getIssuerByIssuerId} instead. + * @todo remove in 0.6 + */ public async getByIssuerId(issuerId: string) { + return this.getIssuerByIssuerId(issuerId) + } + + public async getIssuerByIssuerId(issuerId: string) { return this.openId4VcIssuerService.getIssuerByIssuerId(this.agentContext, issuerId) } diff --git a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerService.ts b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerService.ts index 8fad44c055..71e3891958 100644 --- a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerService.ts +++ b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerService.ts @@ -108,13 +108,19 @@ export class OpenId4VcIssuerService { utils.uuid(), ]) - const { uri } = await vcIssuer.createCredentialOfferURI({ + let { uri } = await vcIssuer.createCredentialOfferURI({ grants: await this.getGrantsFromConfig(agentContext, preAuthorizedCodeFlowConfig), credentials: offeredCredentials, credentialOfferUri: hostedCredentialOfferUri, baseUri: options.baseUri, + credentialDataSupplierInput: options.issuanceMetadata, }) + // FIXME: https://github.com/Sphereon-Opensource/OID4VCI/issues/102 + if (uri.includes(hostedCredentialOfferUri)) { + uri = uri.replace(hostedCredentialOfferUri, encodeURIComponent(hostedCredentialOfferUri)) + } + const issuanceSession = await this.openId4VcIssuanceSessionRepository.getSingleByQuery(agentContext, { credentialOfferUri: hostedCredentialOfferUri, }) @@ -158,6 +164,18 @@ export class OpenId4VcIssuerService { const issuer = await this.getIssuerByIssuerId(agentContext, options.issuanceSession.issuerId) + const cNonce = getCNonceFromCredentialRequest(credentialRequest) + if (issuanceSession.cNonce !== cNonce) { + throw new CredoError('The cNonce in the credential request does not match the cNonce in the issuance session.') + } + + if (!issuanceSession.cNonceExpiresAt) { + throw new CredoError('Missing required cNonceExpiresAt in the issuance session. Assuming cNonce is not valid') + } + if (Date.now() > issuanceSession.cNonceExpiresAt.getTime()) { + throw new CredoError('The cNonce has expired.') + } + const vcIssuer = this.getVcIssuer(agentContext, issuer) const credentialResponse = await vcIssuer.issueCredential({ credentialRequest, @@ -166,21 +184,31 @@ export class OpenId4VcIssuerService { // This can just be combined with signing callback right? credentialDataSupplier: this.getCredentialDataSupplier(agentContext, { ...options, issuer }), credentialDataSupplierInput: issuanceSession.issuanceMetadata, - newCNonce: undefined, responseCNonce: undefined, }) + const updatedIssuanceSession = await this.openId4VcIssuanceSessionRepository.getById( + agentContext, + issuanceSession.id + ) + if (!credentialResponse.credential) { - throw new CredoError('No credential found in the issueCredentialResponse.') + updatedIssuanceSession.state = OpenId4VcIssuanceSessionState.Error + updatedIssuanceSession.errorMessage = 'No credential found in the issueCredentialResponse.' + await this.openId4VcIssuanceSessionRepository.update(agentContext, updatedIssuanceSession) + throw new CredoError(updatedIssuanceSession.errorMessage) } if (credentialResponse.acceptance_token) { - throw new CredoError('Acceptance token not yet supported.') + updatedIssuanceSession.state = OpenId4VcIssuanceSessionState.Error + updatedIssuanceSession.errorMessage = 'Acceptance token not yet supported.' + await this.openId4VcIssuanceSessionRepository.update(agentContext, updatedIssuanceSession) + throw new CredoError(updatedIssuanceSession.errorMessage) } return { credentialResponse, - issuanceSession: await this.openId4VcIssuanceSessionRepository.getById(agentContext, issuanceSession.id), + issuanceSession: updatedIssuanceSession, } } @@ -469,6 +497,7 @@ export class OpenId4VcIssuerService { agentContext: AgentContext, options: OpenId4VciCreateCredentialResponseOptions & { issuer: OpenId4VcIssuerRecord + issuanceSession: OpenId4VcIssuanceSessionRecord } ): CredentialDataSupplier => { return async (args: CredentialDataSupplierArgs) => { @@ -497,6 +526,7 @@ export class OpenId4VcIssuerService { this.openId4VcIssuerConfig.credentialEndpoint.credentialRequestToCredentialMapper const signOptions = await mapper({ agentContext, + issuanceSession: options.issuanceSession, holderBinding, credentialOffer, diff --git a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerServiceOptions.ts b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerServiceOptions.ts index 9a2c4dfa42..8c696fabc5 100644 --- a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerServiceOptions.ts +++ b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerServiceOptions.ts @@ -1,3 +1,4 @@ +import type { OpenId4VcIssuanceSessionRecord } from './repository' import type { OpenId4VcCredentialHolderBinding, OpenId4VciCredentialOffer, @@ -37,6 +38,14 @@ export interface OpenId4VciCreateCredentialOfferOptions { baseUri?: string preAuthorizedCodeFlowConfig: OpenId4VciPreAuthorizedCodeFlowConfig + + /** + * Metadata about the issuance, that will be stored in the issuance session record and + * passed to the credential request to credential mapper. This can be used to e.g. store an + * user identifier so user data can be fetched in the credential mapper, or the actual credential + * data. + */ + issuanceMetadata?: Record } export interface OpenId4VciCreateCredentialResponseOptions { @@ -60,6 +69,12 @@ export interface OpenId4VciCreateCredentialResponseOptions { export type OpenId4VciCredentialRequestToCredentialMapper = (options: { agentContext: AgentContext + /** + * The issuance session associated with the credential request. You can extract the + * issuance metadata from this record if passed in the offer creation method. + */ + issuanceSession: OpenId4VcIssuanceSessionRecord + /** * The credential request received from the wallet */ diff --git a/packages/openid4vc/src/openid4vc-issuer/__tests__/openid4vc-issuer.test.ts b/packages/openid4vc/src/openid4vc-issuer/__tests__/openid4vc-issuer.test.ts index d6678c2a09..647ae30d93 100644 --- a/packages/openid4vc/src/openid4vc-issuer/__tests__/openid4vc-issuer.test.ts +++ b/packages/openid4vc/src/openid4vc-issuer/__tests__/openid4vc-issuer.test.ts @@ -42,7 +42,6 @@ import { agentDependencies } from '../../../../node/src' import { OpenId4VciCredentialFormatProfile } from '../../shared' import { OpenId4VcIssuanceSessionState } from '../OpenId4VcIssuanceSessionState' import { OpenId4VcIssuerModule } from '../OpenId4VcIssuerModule' -import { OpenId4VcIssuerService } from '../OpenId4VcIssuerService' import { OpenId4VcIssuanceSessionRepository } from '../repository' const openBadgeCredential = { @@ -288,12 +287,13 @@ describe('OpenId4VcIssuer', () => { const issuanceSessionRepository = issuer.context.dependencyManager.resolve(OpenId4VcIssuanceSessionRepository) result.issuanceSession.cNonce = '1234' + result.issuanceSession.cNonceExpiresAt = new Date(Date.now() + 30000) // 30 seconds await issuanceSessionRepository.update(issuer.context, result.issuanceSession) expect(result).toMatchObject({ credentialOffer: expect.stringMatching( new RegExp( - `^openid-credential-offer://\\?credential_offer_uri=https://openid4vc-issuer.com/${openId4VcIssuer.issuerId}/offers/.*$` + `^openid-credential-offer://\\?credential_offer_uri=https%3A%2F%2Fopenid4vc-issuer.com%2F${openId4VcIssuer.issuerId}%2Foffers%2F.*$` ) ), issuanceSession: { @@ -346,7 +346,7 @@ describe('OpenId4VcIssuer', () => { expect(credentialResponse).toEqual({ c_nonce: expect.any(String), - c_nonce_expires_in: 300000, + c_nonce_expires_in: 300, credential: expect.any(String), format: 'vc+sd-jwt', }) @@ -368,11 +368,15 @@ describe('OpenId4VcIssuer', () => { preAuthorizedCode, userPinRequired: false, }, + issuanceMetadata: { + myIssuance: 'metadata', + }, }) const issuanceSessionRepository = issuer.context.dependencyManager.resolve(OpenId4VcIssuanceSessionRepository) // We need to update the state, as it is checked and we're skipping the access token step result.issuanceSession.cNonce = '1234' + result.issuanceSession.cNonceExpiresAt = new Date(Date.now() + 30000) // 30 seconds result.issuanceSession.state = OpenId4VcIssuanceSessionState.AccessTokenCreated await issuanceSessionRepository.update(issuer.context, result.issuanceSession) @@ -381,16 +385,23 @@ describe('OpenId4VcIssuer', () => { const issuerMetadata = await issuer.modules.openId4VcIssuer.getIssuerMetadata(openId4VcIssuer.issuerId) const { credentialResponse } = await issuer.modules.openId4VcIssuer.createCredentialResponse({ issuanceSessionId: result.issuanceSession.id, - credentialRequestToCredentialMapper: () => ({ - format: 'jwt_vc', - credential: new W3cCredential({ - type: openBadgeCredential.types, - issuer: new W3cIssuer({ id: issuerDid }), - credentialSubject: new W3cCredentialSubject({ id: holderDid }), - issuanceDate: w3cDate(Date.now()), - }), - verificationMethod: issuerVerificationMethod.id, - }), + credentialRequestToCredentialMapper: ({ issuanceSession }) => { + expect(issuanceSession.id).toEqual(result.issuanceSession.id) + expect(issuanceSession.issuanceMetadata).toEqual({ + myIssuance: 'metadata', + }) + + return { + format: 'jwt_vc', + credential: new W3cCredential({ + type: openBadgeCredential.types, + issuer: new W3cIssuer({ id: issuerDid }), + credentialSubject: new W3cCredentialSubject({ id: holderDid }), + issuanceDate: w3cDate(Date.now()), + }), + verificationMethod: issuerVerificationMethod.id, + } + }, credentialRequest: await createCredentialRequest(holder.context, { credentialSupported: openBadgeCredential, issuerMetadata, @@ -401,7 +412,7 @@ describe('OpenId4VcIssuer', () => { expect(credentialResponse).toEqual({ c_nonce: expect.any(String), - c_nonce_expires_in: 300000, + c_nonce_expires_in: 300, credential: expect.any(String), format: 'jwt_vc_json', }) @@ -444,6 +455,7 @@ describe('OpenId4VcIssuer', () => { // We need to update the state, as it is checked and we're skipping the access token step result.issuanceSession.state = OpenId4VcIssuanceSessionState.AccessTokenCreated result.issuanceSession.cNonce = '1234' + result.issuanceSession.cNonceExpiresAt = new Date(Date.now() + 30000) // 30 seconds await issuanceSessionRepository.update(issuer.context, result.issuanceSession) const issuerMetadata = await issuer.modules.openId4VcIssuer.getIssuerMetadata(openId4VcIssuer.issuerId) @@ -478,6 +490,7 @@ describe('OpenId4VcIssuer', () => { const issuanceSessionRepository = issuer.context.dependencyManager.resolve(OpenId4VcIssuanceSessionRepository) // We need to update the state, as it is checked and we're skipping the access token step result.issuanceSession.cNonce = '1234' + result.issuanceSession.cNonceExpiresAt = new Date(Date.now() + 30000) // 30 seconds result.issuanceSession.state = OpenId4VcIssuanceSessionState.AccessTokenCreated await issuanceSessionRepository.update(issuer.context, result.issuanceSession) @@ -504,7 +517,7 @@ describe('OpenId4VcIssuer', () => { expect(credentialResponse).toEqual({ c_nonce: expect.any(String), - c_nonce_expires_in: 300000, + c_nonce_expires_in: 300, credential: expect.any(String), format: 'jwt_vc_json-ld', }) @@ -532,6 +545,7 @@ describe('OpenId4VcIssuer', () => { // We need to update the state, as it is checked and we're skipping the access token step result.issuanceSession.state = OpenId4VcIssuanceSessionState.AccessTokenCreated result.issuanceSession.cNonce = '1234' + result.issuanceSession.cNonceExpiresAt = new Date(Date.now() + 30000) // 30 seconds await issuanceSessionRepository.update(issuer.context, result.issuanceSession) const issuerMetadata = await issuer.modules.openId4VcIssuer.getIssuerMetadata(openId4VcIssuer.issuerId) @@ -569,7 +583,7 @@ describe('OpenId4VcIssuer', () => { expect(credentialOffer).toMatch( new RegExp( - `^openid-credential-offer://\\?credential_offer_uri=https://openid4vc-issuer.com/${openId4VcIssuer.issuerId}/offers/.*$` + `^openid-credential-offer://\\?credential_offer_uri=https%3A%2F%2Fopenid4vc-issuer.com%2F${openId4VcIssuer.issuerId}%2Foffers%2F.*$` ) ) }) @@ -588,6 +602,7 @@ describe('OpenId4VcIssuer', () => { const issuanceSessionRepository = issuer.context.dependencyManager.resolve(OpenId4VcIssuanceSessionRepository) result.issuanceSession.cNonce = '1234' + result.issuanceSession.cNonceExpiresAt = new Date(Date.now() + 30000) // 30 seconds await issuanceSessionRepository.update(issuer.context, result.issuanceSession) expect(result.issuanceSession.credentialOfferPayload?.credentials).toEqual([ @@ -629,7 +644,7 @@ describe('OpenId4VcIssuer', () => { expect(credentialResponse).toEqual({ c_nonce: expect.any(String), - c_nonce_expires_in: 300000, + c_nonce_expires_in: 300, credential: expect.any(String), format: 'jwt_vc_json', }) @@ -653,7 +668,7 @@ describe('OpenId4VcIssuer', () => { expect(credentialResponse2).toEqual({ c_nonce: expect.any(String), - c_nonce_expires_in: 300000, + c_nonce_expires_in: 300, credential: expect.any(String), format: 'jwt_vc_json', }) diff --git a/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcCNonceStateManager.ts b/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcCNonceStateManager.ts index 5e2e768e85..7473719435 100644 --- a/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcCNonceStateManager.ts +++ b/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcCNonceStateManager.ts @@ -3,13 +3,17 @@ import type { CNonceState, IStateManager } from '@sphereon/oid4vci-common' import { CredoError } from '@credo-ts/core' +import { OpenId4VcIssuerModuleConfig } from '../OpenId4VcIssuerModuleConfig' + import { OpenId4VcIssuanceSessionRepository } from './OpenId4VcIssuanceSessionRepository' export class OpenId4VcCNonceStateManager implements IStateManager { private openId4VcIssuanceSessionRepository: OpenId4VcIssuanceSessionRepository + private openId4VcIssuerModuleConfig: OpenId4VcIssuerModuleConfig public constructor(private agentContext: AgentContext, private issuerId: string) { this.openId4VcIssuanceSessionRepository = agentContext.dependencyManager.resolve(OpenId4VcIssuanceSessionRepository) + this.openId4VcIssuerModuleConfig = agentContext.dependencyManager.resolve(OpenId4VcIssuerModuleConfig) } public async set(cNonce: string, stateValue: CNonceState): Promise { @@ -29,7 +33,17 @@ export class OpenId4VcCNonceStateManager implements IStateManager { preAuthorizedCode: stateValue.preAuthorizedCode, }) + // cNonce already matches, no need to update + if (record.cNonce === stateValue.cNonce) { + return + } + + const expiresAtDate = new Date( + Date.now() + this.openId4VcIssuerModuleConfig.accessTokenEndpoint.cNonceExpiresInSeconds * 1000 + ) + record.cNonce = stateValue.cNonce + record.cNonceExpiresAt = expiresAtDate await this.openId4VcIssuanceSessionRepository.update(this.agentContext, record) } @@ -74,6 +88,7 @@ export class OpenId4VcCNonceStateManager implements IStateManager { // We only remove the cNonce from the record, we don't want to remove // the whole issuance session. record.cNonce = undefined + record.cNonceExpiresAt = undefined await this.openId4VcIssuanceSessionRepository.update(this.agentContext, record) return true } diff --git a/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcCredentialOfferSessionStateManager.ts b/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcCredentialOfferSessionStateManager.ts index d76ca90579..c3381b6249 100644 --- a/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcCredentialOfferSessionStateManager.ts +++ b/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcCredentialOfferSessionStateManager.ts @@ -147,7 +147,7 @@ export class OpenId4VcCredentialOfferSessionStateManager implements IStateManage const state = await this.get(preAuthorizedCode) if (!state) { - throw new CredoError(`No cNonce state found for id ${preAuthorizedCode}`) + throw new CredoError(`No credential offer state found for id ${preAuthorizedCode}`) } return state diff --git a/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcIssuanceSessionRecord.ts b/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcIssuanceSessionRecord.ts index 26a73d0a4c..012e914081 100644 --- a/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcIssuanceSessionRecord.ts +++ b/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcIssuanceSessionRecord.ts @@ -2,7 +2,7 @@ import type { OpenId4VciCredentialOfferPayload } from '../../shared' import type { OpenId4VcIssuanceSessionState } from '../OpenId4VcIssuanceSessionState' import type { RecordTags, TagsBase } from '@credo-ts/core' -import { CredoError, BaseRecord, utils } from '@credo-ts/core' +import { CredoError, BaseRecord, utils, DateTransformer } from '@credo-ts/core' export type OpenId4VcIssuanceSessionRecordTags = RecordTags @@ -20,7 +20,9 @@ export interface OpenId4VcIssuanceSessionRecordProps { tags?: TagsBase issuerId: string + cNonce?: string + cNonceExpiresAt?: Date preAuthorizedCode?: string userPin?: string @@ -52,6 +54,12 @@ export class OpenId4VcIssuanceSessionRecord extends BaseRecord expiresAt) { + throw new TokenError(400, TokenErrorResponse.invalid_grant, 'Pre-authorized code has expired') + } } catch (error) { if (error instanceof TokenError) { sendErrorResponse( response, agentContext.config.logger, error.statusCode, - error.responseError + error.getDescription(), - error + error.responseError, + error.getDescription() ) } else { sendErrorResponse(response, agentContext.config.logger, 400, TokenErrorResponse.invalid_request, error) diff --git a/packages/openid4vc/src/openid4vc-issuer/router/credentialOfferEndpoint.ts b/packages/openid4vc/src/openid4vc-issuer/router/credentialOfferEndpoint.ts index f87577e1cb..f1e316d1f3 100644 --- a/packages/openid4vc/src/openid4vc-issuer/router/credentialOfferEndpoint.ts +++ b/packages/openid4vc/src/openid4vc-issuer/router/credentialOfferEndpoint.ts @@ -8,6 +8,7 @@ import { getRequestContext, sendErrorResponse } from '../../shared/router' import { OpenId4VcIssuanceSessionState } from '../OpenId4VcIssuanceSessionState' import { OpenId4VcIssuerEvents } from '../OpenId4VcIssuerEvents' import { OpenId4VcIssuerModuleConfig } from '../OpenId4VcIssuerModuleConfig' +import { OpenId4VcIssuerService } from '../OpenId4VcIssuerService' import { OpenId4VcIssuanceSessionRepository } from '../repository' export interface OpenId4VciCredentialOfferEndpointConfig { @@ -26,19 +27,32 @@ export function configureCredentialOfferEndpoint(router: Router, config: OpenId4 async (request: OpenId4VcIssuanceRequest, response: Response, next) => { const { agentContext, issuer } = getRequestContext(request) + if (!request.params.credentialOfferId || typeof request.params.credentialOfferId !== 'string') { + return sendErrorResponse( + response, + agentContext.config.logger, + 400, + 'invalid_request', + 'Invalid credential offer url' + ) + } + try { + const issuerService = agentContext.dependencyManager.resolve(OpenId4VcIssuerService) + const issuerMetadata = issuerService.getIssuerMetadata(agentContext, issuer) const openId4VcIssuanceSessionRepository = agentContext.dependencyManager.resolve( OpenId4VcIssuanceSessionRepository ) const issuerConfig = agentContext.dependencyManager.resolve(OpenId4VcIssuerModuleConfig) - // TODO: is there a cleaner way to get the host (including port)? - const [, , host] = issuerConfig.baseUrl.split('/') + const fullCredentialOfferUri = joinUriParts(issuerMetadata.issuerUrl, [ + issuerConfig.credentialOfferEndpoint.endpointPath, + request.params.credentialOfferId, + ]) - const credentialOfferUri = `${request.protocol}://${host}${request.originalUrl}` const openId4VcIssuanceSession = await openId4VcIssuanceSessionRepository.findSingleByQuery(agentContext, { issuerId: issuer.issuerId, - credentialOfferUri, + credentialOfferUri: fullCredentialOfferUri, }) if (!openId4VcIssuanceSession || !openId4VcIssuanceSession.credentialOfferPayload) { diff --git a/packages/openid4vc/src/openid4vc-verifier/router/authorizationRequestEndpoint.ts b/packages/openid4vc/src/openid4vc-verifier/router/authorizationRequestEndpoint.ts index dc86147508..01c4736dd8 100644 --- a/packages/openid4vc/src/openid4vc-verifier/router/authorizationRequestEndpoint.ts +++ b/packages/openid4vc/src/openid4vc-verifier/router/authorizationRequestEndpoint.ts @@ -30,6 +30,16 @@ export function configureAuthorizationRequestEndpoint( async (request: OpenId4VcVerificationRequest, response: Response, next) => { const { agentContext, verifier } = getRequestContext(request) + if (!request.params.authorizationRequestId || typeof request.params.authorizationRequestId !== 'string') { + return sendErrorResponse( + response, + agentContext.config.logger, + 400, + 'invalid_request', + 'Invalid authorization request url' + ) + } + try { const verifierService = agentContext.dependencyManager.resolve(OpenId4VcSiopVerifierService) const verificationSessionRepository = agentContext.dependencyManager.resolve( @@ -37,13 +47,16 @@ export function configureAuthorizationRequestEndpoint( ) const verifierConfig = agentContext.dependencyManager.resolve(OpenId4VcVerifierModuleConfig) - // TODO: is there a cleaner way to get the host (including port)? - const [, , host] = verifierConfig.baseUrl.split('/') + // We always use shortened URIs currently + const fullAuthorizationRequestUri = joinUriParts(verifierConfig.baseUrl, [ + verifier.verifierId, + verifierConfig.authorizationRequestEndpoint.endpointPath, + request.params.authorizationRequestId, + ]) - const authorizationRequestUri = `${request.protocol}://${host}${request.originalUrl}` const [verificationSession] = await verifierService.findVerificationSessionsByQuery(agentContext, { verifierId: verifier.verifierId, - authorizationRequestUri, + authorizationRequestUri: fullAuthorizationRequestUri, }) if (!verificationSession) { diff --git a/yarn.lock b/yarn.lock index b346e3c004..d94fde7617 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2618,10 +2618,10 @@ cross-fetch "^4.0.0" did-resolver "^4.1.0" -"@sphereon/oid4vci-client@^0.10.1": - version "0.10.1" - resolved "https://registry.yarnpkg.com/@sphereon/oid4vci-client/-/oid4vci-client-0.10.1.tgz#c78e92a81b6033f785858104e8e89adb7af076e4" - integrity sha512-v3Vk+FepN6xt0CjCMVHMO65RVu4e/ireqq0Ed3J5falXbdVR7xCdFBLa8Lj+bSF1sn0f3WvpmTM1E9e+QnYErg== +"@sphereon/oid4vci-client@^0.10.2": + version "0.10.2" + resolved "https://registry.yarnpkg.com/@sphereon/oid4vci-client/-/oid4vci-client-0.10.2.tgz#70ceff97e6fb8220e8de5e626359ad2ea146ef1e" + integrity sha512-G0vE9/MwdyHQnYpnuaJqbRSIKXCLVyOVhJtJCKuqMEa9oYoNx+DwRKt5zjeiHfVxjjDoauFQ8qP2WOuvsqdR0w== dependencies: "@sphereon/oid4vci-common" "0.10.1" "@sphereon/ssi-types" "^0.18.1" @@ -2639,10 +2639,10 @@ sha.js "^2.4.11" uint8arrays "3.1.1" -"@sphereon/oid4vci-issuer@^0.10.1": - version "0.10.1" - resolved "https://registry.yarnpkg.com/@sphereon/oid4vci-issuer/-/oid4vci-issuer-0.10.1.tgz#7e88b7b0eb1a878d097ae54e605372a925eb4a0b" - integrity sha512-bczrK1gTX6gRSHSu1H+zGL4sUyMNIzAjtNNIBmtFPdQWSoSGVrBDfvf/36/3ag8jr8DtCJ808UTYwYQIWLgJcA== +"@sphereon/oid4vci-issuer@^0.10.2": + version "0.10.2" + resolved "https://registry.yarnpkg.com/@sphereon/oid4vci-issuer/-/oid4vci-issuer-0.10.2.tgz#9d9d2ac73927b59e9feba784d1ea87971af7281e" + integrity sha512-9EteuVxZe2tWfmISLelDWBhSzN4s/TAg74z9VDHoyzX/4EED/wtCYXny8JZRwBZAAc9Pdl/3qADe15d3rOQqJw== dependencies: "@sphereon/oid4vci-common" "0.10.1" "@sphereon/ssi-types" "^0.18.1"