Skip to content

Commit

Permalink
feature: allow multiple lookup types to be used to lookup a credentia…
Browse files Browse the repository at this point in the history
…l offer
  • Loading branch information
nklomp committed Feb 16, 2025
1 parent 64fc725 commit abbc01b
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 13 deletions.
4 changes: 2 additions & 2 deletions packages/issuer-rest/lib/oid4vci-api-functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export function getCredentialOfferReferenceEndpoint(router: Router, issuer: VcIs
error_description: `query parameter 'id' is missing`,
})
}
const session = await issuer.getCredentialOfferSessionById(id as string, 'correlationId')
const session = await issuer.getCredentialOfferSessionById(id as string)
if (!session || !session.credentialOffer || session.status !== 'OFFER_CREATED') {
if (session?.status) {
LOG.warning(
Expand Down Expand Up @@ -404,7 +404,7 @@ export function getCredentialOfferEndpoint(router: Router, issuer: VcIssuer, opt
router.get(path, async (request: Request, response: Response) => {
try {
const { id } = request.params
const session = await issuer.getCredentialOfferSessionById(id, 'correlationId')
const session = await issuer.getCredentialOfferSessionById(id)
if (!session || !session.credentialOffer) {
return sendErrorResponse(response, 404, {
error: 'invalid_request',
Expand Down
19 changes: 12 additions & 7 deletions packages/issuer/lib/VcIssuer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import { CompactSdJwtVc, CredentialMapper, InitiatorType, SubSystem, System, W3C
import ShortUUID from 'short-uuid'

import { assertValidPinNumber, createCredentialOfferObject, createCredentialOfferURIFromObject, CredentialOfferGrantInput } from './functions'
import { LookupStateManager, MemoryStates } from './state-manager'
import { LookupStateManager, lookupStateManagerMultiGetAsserted, MemoryStates } from './state-manager'
import { CredentialDataSupplier, CredentialDataSupplierArgs, CredentialIssuanceInput, CredentialSignerCallback } from './types'

import { LOG } from './index'
Expand Down Expand Up @@ -99,14 +99,20 @@ export class VcIssuer {

public async getCredentialOfferSessionById(
id: string,
lookup?: 'uri' | 'preAuthorizedCode' | 'issuerState' | 'correlationId',
lookups: Array<'uri' | 'preAuthorizedCode' | 'issuerState' | 'correlationId'> = ['preAuthorizedCode', 'issuerState', 'correlationId'],
): Promise<CredentialOfferSession> {
// preAuth and issuerState can be looked up directly
if (lookup && lookup !== 'preAuthorizedCode' && lookup !== 'issuerState') {
if (Array.isArray(lookups) && lookups.length > 0) {
if (!this.uris) {
return Promise.reject(Error('Cannot lookup credential offer by id if URI state manager is not set'))
}
return new LookupStateManager<URIState, CredentialOfferSession>(this.uris, this._credentialOfferSessions, lookup).getAsserted(id)
return lookupStateManagerMultiGetAsserted({
id,
keyValueMapper: this._uris,
valueStateManager: this._credentialOfferSessions,
lookups: ['preAuthorizedCode', 'issuerState', 'correlationId'],
})
// return new LookupStateManager<URIState, CredentialOfferSession>(this.uris, this._credentialOfferSessions, lookup).getFromMultiple(id)
}
const session = await this._credentialOfferSessions.get(id)
if (!session) {
Expand All @@ -117,11 +123,10 @@ export class VcIssuer {

public async deleteCredentialOfferSessionById(
id: string,
lookup: 'uri' | 'preAuthorizedCode' | 'issuerState' | 'correlationId' = 'correlationId',
lookups: Array<'uri' | 'preAuthorizedCode' | 'issuerState' | 'correlationId'> = ['preAuthorizedCode', 'issuerState'],
): Promise<CredentialOfferSession> {
const session = await this.getCredentialOfferSessionById(id, lookup)
const session = await this.getCredentialOfferSessionById(id, lookups)
if (session) {
new LookupStateManager<URIState, CredentialOfferSession>(this.uris, this._credentialOfferSessions, lookup).delete(id)
if (session.preAuthorizedCode && (await this._credentialOfferSessions.has(session.preAuthorizedCode))) {
await this._credentialOfferSessions.delete(session.preAuthorizedCode)
}
Expand Down
8 changes: 4 additions & 4 deletions packages/issuer/lib/__tests__/VcIssuer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -752,14 +752,14 @@ describe('VcIssuer without did', () => {
},
})

const session = await vcIssuer.getCredentialOfferSessionById(result.session.preAuthorizedCode!)
const session = await vcIssuer.getCredentialOfferSessionById(result.session.preAuthorizedCode!, ['uri'])

expect(session).toBeDefined()
expect(session.credentialOffer).toEqual(result.session.credentialOffer)
})

it('should throw error when getting session with invalid uri', async () => {
await expect(vcIssuer.getCredentialOfferSessionById('https://example.com/invalid-uri')).rejects.toThrow(/No session found for/)
await expect(vcIssuer.getCredentialOfferSessionById('https://example.com/invalid-uri')).rejects.toThrow('no value found for id https://example.com/invalid-uri')
})

it('should throw error when getting session by uri without uri state manager', async () => {
Expand All @@ -774,8 +774,8 @@ describe('VcIssuer without did', () => {
.withInMemoryCNonceState()
.build()

await expect(vcIssuerWithoutUriState.getCredentialOfferSessionById('https://example.com/some-uri', 'uri')).rejects.toThrow(
'issuer state or pre-authorized key not found (https://example.com/some-uri)',
await expect(vcIssuerWithoutUriState.getCredentialOfferSessionById('https://example.com/some-uri')).rejects.toThrow(
'no value found for id https://example.com/some-uri',
)
})
})
36 changes: 36 additions & 0 deletions packages/issuer/lib/state-manager/LookupStateManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,42 @@

import { IStateManager, StateType } from '@sphereon/oid4vci-common'

export async function lookupStateManagerMultiGetAsserted<K extends StateType, V extends StateType>(args: {
id: string
lookups: string[]
keyValueMapper: IStateManager<K>
valueStateManager: IStateManager<V>
}) {
const value = await lookupStateManagerMultiGet(args)
if (value) {
return value
}
return Promise.reject(Error(`no value found for id ${args.id}`))
}
export async function lookupStateManagerMultiGet<K extends StateType, V extends StateType>({
id,
lookups,
keyValueMapper,
valueStateManager,
}: {
id: string
lookups: string[]
keyValueMapper: IStateManager<K>
valueStateManager: IStateManager<V>
}) {
for (const lookup of lookups) {
try {
const value = await new LookupStateManager(keyValueMapper, valueStateManager, lookup).get(id)
if (value) {
return value
}
} catch (e) {
// intentionally ignore the error
}
}
return valueStateManager.get(id)
}

export class LookupStateManager<K extends StateType, V extends StateType> implements IStateManager<V> {
constructor(
private keyValueMapper: IStateManager<K>,
Expand Down

0 comments on commit abbc01b

Please sign in to comment.