UNPKG

@sphereon/oid4vci-client

Version:

OpenID for Verifiable Credential Issuance (OpenID4VCI) client

235 lines (211 loc) • 6.03 kB
import { JWK } from '@sphereon/oid4vc-common'; import { AccessTokenResponse, Alg, createProofOfPossession, EndpointMetadata, Jwt, NO_JWT_PROVIDED, OpenId4VCIVersion, PoPMode, PROOF_CANT_BE_CONSTRUCTED, ProofOfPossession, ProofOfPossessionCallbacks, Typ, } from '@sphereon/oid4vci-common'; export class ProofOfPossessionBuilder<DIDDoc = never> { private readonly proof?: ProofOfPossession; private readonly callbacks?: ProofOfPossessionCallbacks; private readonly version: OpenId4VCIVersion; private readonly mode: PoPMode = 'pop'; private kid?: string; private jwk?: JWK; private aud?: string | string[]; private clientId?: string; private issuer?: string; private jwt?: Jwt; private alg?: string; private jti?: string; private cNonce?: string; private typ?: Typ; private constructor({ proof, callbacks, jwt, accessTokenResponse, version, mode = 'pop', }: { proof?: ProofOfPossession; callbacks?: ProofOfPossessionCallbacks; accessTokenResponse?: AccessTokenResponse; jwt?: Jwt; version: OpenId4VCIVersion; mode?: PoPMode; }) { this.mode = mode; this.proof = proof; this.callbacks = callbacks; this.version = version; if (jwt) { this.withJwt(jwt); } else { this.withTyp(version < OpenId4VCIVersion.VER_1_0_11 || mode === 'JWT' ? 'JWT' : 'openid4vci-proof+jwt'); } if (accessTokenResponse) { this.withAccessTokenResponse(accessTokenResponse); } } static manual({ jwt, callbacks, version, mode = 'JWT', }: { jwt?: Jwt; callbacks: ProofOfPossessionCallbacks; version: OpenId4VCIVersion; mode?: PoPMode; }): ProofOfPossessionBuilder { return new ProofOfPossessionBuilder({ callbacks, jwt, version, mode }); } static fromJwt({ jwt, callbacks, version, mode = 'pop', }: { jwt: Jwt; callbacks: ProofOfPossessionCallbacks; version: OpenId4VCIVersion; mode?: PoPMode; }): ProofOfPossessionBuilder { return new ProofOfPossessionBuilder({ callbacks, jwt, version, mode }); } static fromAccessTokenResponse({ accessTokenResponse, callbacks, version, mode = 'pop', }: { accessTokenResponse: AccessTokenResponse; callbacks: ProofOfPossessionCallbacks; version: OpenId4VCIVersion; mode?: PoPMode; }): ProofOfPossessionBuilder { return new ProofOfPossessionBuilder({ callbacks, accessTokenResponse, version, mode }); } static fromProof(proof: ProofOfPossession, version: OpenId4VCIVersion): ProofOfPossessionBuilder { return new ProofOfPossessionBuilder({ proof, version }); } withAud(aud: string | string[]): this { this.aud = aud; return this; } withClientId(clientId: string): this { this.clientId = clientId; return this; } withKid(kid: string): this { this.kid = kid; return this; } withJWK(jwk: JWK): this { this.jwk = jwk; return this; } withIssuer(issuer: string): this { this.issuer = issuer; return this; } withAlg(alg: Alg | string): this { this.alg = alg; return this; } withJti(jti: string): this { this.jti = jti; return this; } withTyp(typ: Typ): this { if (this.mode === 'pop' && this.version >= OpenId4VCIVersion.VER_1_0_11) { if (!!typ && typ !== 'openid4vci-proof+jwt') { throw Error(`typ must be openid4vci-proof+jwt for version 1.0.11 and up. Provided: ${typ}`); } } else { if (!!typ && typ !== 'JWT') { throw Error(`typ must be jwt for version 1.0.10 and below. Provided: ${typ}`); } } this.typ = typ; return this; } withAccessTokenNonce(cNonce: string): this { this.cNonce = cNonce; return this; } withAccessTokenResponse(accessToken: AccessTokenResponse): this { if (accessToken.c_nonce) { this.withAccessTokenNonce(accessToken.c_nonce); } return this; } withEndpointMetadata(endpointMetadata: EndpointMetadata): this { this.withIssuer(endpointMetadata.issuer); return this; } withJwt(jwt: Jwt): this { if (!jwt) { throw new Error(NO_JWT_PROVIDED); } this.jwt = jwt; if (!jwt.header) { throw Error(`No JWT header present`); } else if (!jwt.payload) { throw Error(`No JWT payload present`); } if (jwt.header.kid) { this.withKid(jwt.header.kid); } if (jwt.header.typ) { this.withTyp(jwt.header.typ as Typ); } if (!this.typ && this.version >= OpenId4VCIVersion.VER_1_0_11) { this.withTyp('openid4vci-proof+jwt'); } this.withAlg(jwt.header.alg); if (Array.isArray(jwt.payload.aud)) { // Rather do this than take the first value, as it might be very hard to figure out why something is failing throw Error('We cannot handle multiple aud values currently'); } if (jwt.payload) { if (jwt.payload.iss) this.mode === 'pop' ? this.withClientId(jwt.payload.iss) : this.withIssuer(jwt.payload.iss); if (jwt.payload.aud) this.mode === 'pop' ? this.withIssuer(jwt.payload.aud) : this.withAud(jwt.payload.aud); if (jwt.payload.jti) this.withJti(jwt.payload.jti); if (jwt.payload.nonce) this.withAccessTokenNonce(jwt.payload.nonce); } return this; } public async build(): Promise<ProofOfPossession> { if (this.proof) { return Promise.resolve(this.proof); } else if (this.callbacks) { return await createProofOfPossession( this.mode, this.callbacks, { typ: this.typ ?? (this.version < OpenId4VCIVersion.VER_1_0_11 || this.mode === 'JWT' ? 'JWT' : 'openid4vci-proof+jwt'), kid: this.kid, jwk: this.jwk, jti: this.jti, alg: this.alg, aud: this.aud, issuer: this.issuer, clientId: this.clientId, ...(this.cNonce && { nonce: this.cNonce }), }, this.jwt, ); } throw new Error(PROOF_CANT_BE_CONSTRUCTED); } }