@sphereon/did-auth-siop
Version:
Self Issued OpenID V2 (SIOPv2) and OpenID 4 Verifiable Presentations (OID4VP)
392 lines (345 loc) • 13.4 kB
text/typescript
import { EventEmitter } from 'events'
import { IPresentationDefinition } from '@sphereon/pex'
import { Hasher } from '@sphereon/ssi-types'
import { DcqlQuery } from 'dcql'
import { PropertyTarget, PropertyTargets } from '../authorization-request'
import { PresentationVerificationCallback } from '../authorization-response'
import {
AuthorizationRequestPayload,
ClientIdScheme,
ClientMetadataOpts,
CreateJwtCallback,
ObjectBy,
PassBy,
RequestAud,
RequestObjectPayload,
ResponseIss,
ResponseMode,
ResponseType,
RevocationVerification,
RevocationVerificationCallback,
SupportedVersion,
VerifyJwtCallback,
} from '../types'
import { assignIfAuth, assignIfRequestObject, isTarget, isTargetOrNoTargets } from './Opts'
import { RP } from './RP'
import { IRPSessionManager } from './types'
export class RPBuilder {
requestObjectBy: ObjectBy
createJwtCallback?: CreateJwtCallback
verifyJwtCallback?: VerifyJwtCallback
revocationVerification?: RevocationVerification
revocationVerificationCallback?: RevocationVerificationCallback
presentationVerificationCallback?: PresentationVerificationCallback
supportedVersions: SupportedVersion[]
eventEmitter?: EventEmitter
sessionManager?: IRPSessionManager
_responseRedirectUri?: string
private _authorizationRequestPayload: Partial<AuthorizationRequestPayload> = {}
private _requestObjectPayload: Partial<RequestObjectPayload> = {}
clientMetadata?: ClientMetadataOpts = undefined
clientId: string
entityId: string
clientIdScheme: string
hasher: Hasher
private constructor(supportedRequestVersion?: SupportedVersion) {
if (supportedRequestVersion) {
this.addSupportedVersion(supportedRequestVersion)
}
}
withScope(scope: string, targets?: PropertyTargets): RPBuilder {
this._authorizationRequestPayload.scope = assignIfAuth({ propertyValue: scope, targets }, false)
this._requestObjectPayload.scope = assignIfRequestObject({ propertyValue: scope, targets }, true)
return this
}
withResponseType(responseType: ResponseType | ResponseType[] | string, targets?: PropertyTargets): RPBuilder {
const propertyValue = Array.isArray(responseType) ? responseType.join(' ').trim() : responseType
this._authorizationRequestPayload.response_type = assignIfAuth({ propertyValue, targets }, false)
this._requestObjectPayload.response_type = assignIfRequestObject({ propertyValue, targets }, true)
return this
}
withHasher(hasher: Hasher): RPBuilder {
this.hasher = hasher
return this
}
withClientId(clientId: string, targets?: PropertyTargets): RPBuilder {
this._authorizationRequestPayload.client_id = assignIfAuth({ propertyValue: clientId, targets }, false)
this._requestObjectPayload.client_id = assignIfRequestObject({ propertyValue: clientId, targets }, true)
this.clientId = clientId
return this
}
withClientIdScheme(clientIdScheme: ClientIdScheme, targets?: PropertyTargets): RPBuilder {
this._authorizationRequestPayload.client_id_scheme = assignIfAuth({ propertyValue: clientIdScheme, targets }, false)
this._requestObjectPayload.client_id_scheme = assignIfRequestObject({ propertyValue: clientIdScheme, targets }, true)
this.clientIdScheme = clientIdScheme
return this
}
withEntityId(entityId: string, targets?: PropertyTargets): RPBuilder {
this._authorizationRequestPayload.entity_id = assignIfAuth({ propertyValue: entityId, targets }, false)
this._requestObjectPayload.entity_id = assignIfRequestObject({ propertyValue: entityId, targets }, true)
this.entityId = entityId
return this
}
withIssuer(issuer: ResponseIss, targets?: PropertyTargets): RPBuilder {
this._authorizationRequestPayload.iss = assignIfAuth({ propertyValue: issuer, targets }, false)
this._requestObjectPayload.iss = assignIfRequestObject({ propertyValue: issuer, targets }, true)
return this
}
withAudience(issuer: RequestAud, targets?: PropertyTargets): RPBuilder {
this._authorizationRequestPayload.aud = assignIfAuth({ propertyValue: issuer, targets }, false)
this._requestObjectPayload.aud = assignIfRequestObject({ propertyValue: issuer, targets }, true)
return this
}
withPresentationVerification(presentationVerificationCallback: PresentationVerificationCallback): RPBuilder {
this.presentationVerificationCallback = presentationVerificationCallback
return this
}
withRevocationVerification(mode: RevocationVerification): RPBuilder {
this.revocationVerification = mode
return this
}
withRevocationVerificationCallback(callback: RevocationVerificationCallback): RPBuilder {
this.revocationVerificationCallback = callback
return this
}
withAuthorizationEndpoint(authorizationEndpoint: string, targets?: PropertyTargets): RPBuilder {
this._authorizationRequestPayload.authorization_endpoint = assignIfAuth(
{
propertyValue: authorizationEndpoint,
targets,
},
false,
)
this._requestObjectPayload.authorization_endpoint = assignIfRequestObject(
{
propertyValue: authorizationEndpoint,
targets,
},
true,
)
return this
}
withRedirectUri(redirectUri: string, targets?: PropertyTargets): RPBuilder {
this._authorizationRequestPayload.redirect_uri = assignIfAuth({ propertyValue: redirectUri, targets }, false)
this._requestObjectPayload.redirect_uri = assignIfRequestObject({ propertyValue: redirectUri, targets }, true)
return this
}
withResponseRedirectUri(responseRedirectUri: string): RPBuilder {
this._responseRedirectUri = responseRedirectUri
return this
}
withResponseUri(redirectUri: string, targets?: PropertyTargets): RPBuilder {
this._authorizationRequestPayload.response_uri = assignIfAuth({ propertyValue: redirectUri, targets }, false)
this._requestObjectPayload.response_uri = assignIfRequestObject({ propertyValue: redirectUri, targets }, true)
return this
}
withRequestByReference(referenceUri: string): RPBuilder {
return this.withRequestBy(PassBy.REFERENCE, referenceUri /*, PropertyTarget.AUTHORIZATION_REQUEST*/)
}
withRequestByValue(): RPBuilder {
return this.withRequestBy(PassBy.VALUE, undefined /*, PropertyTarget.AUTHORIZATION_REQUEST*/)
}
withRequestBy(passBy: PassBy, referenceUri?: string /*, targets?: PropertyTargets*/): RPBuilder {
if (passBy === PassBy.REFERENCE && !referenceUri) {
throw Error('Cannot use pass by reference without a reference URI')
}
this.requestObjectBy = {
passBy,
reference_uri: referenceUri,
targets: PropertyTarget.AUTHORIZATION_REQUEST,
}
return this
}
withResponseMode(responseMode: ResponseMode, targets?: PropertyTargets): RPBuilder {
this._authorizationRequestPayload.response_mode = assignIfAuth({ propertyValue: responseMode, targets }, false)
this._requestObjectPayload.response_mode = assignIfRequestObject({ propertyValue: responseMode, targets }, true)
return this
}
withClientMetadata(clientMetadata: ClientMetadataOpts, targets?: PropertyTargets): RPBuilder {
clientMetadata.targets = targets
if (this.getSupportedRequestVersion() < SupportedVersion.SIOPv2_D11) {
this._authorizationRequestPayload.registration = assignIfAuth(
{
propertyValue: clientMetadata,
targets,
},
false,
)
this._requestObjectPayload.registration = assignIfRequestObject(
{
propertyValue: clientMetadata,
targets,
},
true,
)
} else {
this._authorizationRequestPayload.client_metadata = assignIfAuth(
{
propertyValue: clientMetadata,
targets,
},
false,
)
this._requestObjectPayload.client_metadata = assignIfRequestObject(
{
propertyValue: clientMetadata,
targets,
},
true,
)
}
this.clientMetadata = clientMetadata
//fixme: Add URL
return this
}
withCreateJwtCallback(createJwtCallback: CreateJwtCallback): RPBuilder {
this.createJwtCallback = createJwtCallback
return this
}
withVerifyJwtCallback(verifyJwtCallback: VerifyJwtCallback): RPBuilder {
this.verifyJwtCallback = verifyJwtCallback
return this
}
withDcqlQuery(dcqlQuery: DcqlQuery | string, targets?: PropertyTargets): RPBuilder {
if (this.getSupportedRequestVersion() >= SupportedVersion.SIOPv2_D12_OID4VP_D20) {
this._authorizationRequestPayload.dcql_query = assignIfAuth(
{
propertyValue: typeof dcqlQuery === 'string' ? dcqlQuery : JSON.stringify(dcqlQuery),
targets,
},
false,
)
this._requestObjectPayload.dcql_query = assignIfRequestObject(
{
propertyValue: typeof dcqlQuery === 'string' ? dcqlQuery : JSON.stringify(dcqlQuery),
targets,
},
true,
)
// FIXME SPRIND-144 we need to find a way in the config to select dcql vs PD without breaking OID4VC-DEMO
this._authorizationRequestPayload.presentation_definition = undefined
this._authorizationRequestPayload.presentation_definition_uri = undefined
this._requestObjectPayload.presentation_definition = undefined
this._requestObjectPayload.presentation_definition_uri = undefined
}
return this
}
withPresentationDefinition(
definitionOpts: {
definition: IPresentationDefinition
definitionUri?: string
},
targets?: PropertyTargets,
): RPBuilder {
if (this._authorizationRequestPayload.dcql_query) {
return this
}
const { definition, definitionUri } = definitionOpts
if (this.getSupportedRequestVersion() < SupportedVersion.SIOPv2_D11) {
const definitionProperties = {
presentation_definition: definition,
presentation_definition_uri: definitionUri,
}
const vp_token = { ...definitionProperties }
if (isTarget(PropertyTarget.AUTHORIZATION_REQUEST, targets)) {
this._authorizationRequestPayload.claims = {
...(this._authorizationRequestPayload.claims ? this._authorizationRequestPayload.claims : {}),
vp_token: vp_token,
}
}
if (isTargetOrNoTargets(PropertyTarget.REQUEST_OBJECT, targets)) {
this._requestObjectPayload.claims = {
...(this._requestObjectPayload.claims ? this._requestObjectPayload.claims : {}),
vp_token: vp_token,
}
}
} else {
this._authorizationRequestPayload.presentation_definition = assignIfAuth(
{
propertyValue: definition,
targets,
},
false,
)
this._authorizationRequestPayload.presentation_definition_uri = assignIfAuth(
{
propertyValue: definitionUri,
targets,
},
true,
)
this._requestObjectPayload.presentation_definition = assignIfRequestObject(
{
propertyValue: definition,
targets,
},
true,
)
this._requestObjectPayload.presentation_definition_uri = assignIfRequestObject(
{
propertyValue: definitionUri,
targets,
},
true,
)
}
return this
}
private initSupportedVersions() {
if (!this.supportedVersions) {
this.supportedVersions = []
}
}
addSupportedVersion(supportedVersion: SupportedVersion): RPBuilder {
this.initSupportedVersions()
if (!this.supportedVersions.includes(supportedVersion)) {
this.supportedVersions.push(supportedVersion)
}
return this
}
withSupportedVersions(supportedVersion: SupportedVersion[] | SupportedVersion): RPBuilder {
const versions = Array.isArray(supportedVersion) ? supportedVersion : [supportedVersion]
for (const version of versions) {
this.addSupportedVersion(version)
}
return this
}
withEventEmitter(eventEmitter?: EventEmitter): RPBuilder {
this.eventEmitter = eventEmitter ?? new EventEmitter()
return this
}
withSessionManager(sessionManager: IRPSessionManager): RPBuilder {
this.sessionManager = sessionManager
return this
}
public getSupportedRequestVersion(requireVersion?: boolean): SupportedVersion | undefined {
if (!this.supportedVersions || this.supportedVersions.length === 0) {
if (requireVersion !== false) {
throw Error('No supported version supplied/available')
}
return undefined
}
return this.supportedVersions[0]
}
public static newInstance(supportedVersion?: SupportedVersion) {
return new RPBuilder(supportedVersion)
}
build(): RP {
if (this.sessionManager && !this.eventEmitter) {
throw Error('Please enable the event emitter on the RP when using a replay registry')
}
// We do not want others to directly use the RP class
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return new RP({ builder: this })
}
get authorizationRequestPayload(): Partial<AuthorizationRequestPayload> {
return this._authorizationRequestPayload
}
get requestObjectPayload(): Partial<RequestObjectPayload> {
return this._requestObjectPayload
}
/* public mergedPayload(): Partial<AuthorizationRequestPayload> {
return { ...this.authorizationRequestPayload, ...this.requestObjectPayload };
}*/
}