UNPKG

@sphereon/did-auth-siop

Version:

Self Issued OpenID V2 (SIOPv2) and OpenID 4 Verifiable Presentations (OID4VP)

219 lines 12.9 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AuthorizationResponse = void 0; const ssi_types_1 = require("@sphereon/ssi-types"); const authorization_request_1 = require("../authorization-request"); const Opts_1 = require("../authorization-request/Opts"); const id_token_1 = require("../id-token"); const types_1 = require("../types"); const Dcql_1 = require("./Dcql"); const OpenID4VP_1 = require("./OpenID4VP"); const Opts_2 = require("./Opts"); const Payload_1 = require("./Payload"); class AuthorizationResponse { constructor({ authorizationResponsePayload, idToken, responseOpts, authorizationRequest, }) { this._authorizationRequest = authorizationRequest; this._options = responseOpts; this._idToken = idToken; this._payload = authorizationResponsePayload; } /** * Creates a SIOP Response Object * * @param requestObject * @param responseOpts * @param verifyOpts */ static fromRequestObject(requestObject, responseOpts, verifyOpts) { return __awaiter(this, void 0, void 0, function* () { (0, Opts_1.assertValidVerifyAuthorizationRequestOpts)(verifyOpts); (0, Opts_2.assertValidResponseOpts)(responseOpts); if (!requestObject || !requestObject.startsWith('ey')) { throw new Error(types_1.SIOPErrors.NO_JWT); } const authorizationRequest = yield authorization_request_1.AuthorizationRequest.fromUriOrJwt(requestObject); return AuthorizationResponse.fromAuthorizationRequest(authorizationRequest, responseOpts, verifyOpts); }); } static fromPayload(authorizationResponsePayload, responseOpts) { return __awaiter(this, void 0, void 0, function* () { if (!authorizationResponsePayload) { throw new Error(types_1.SIOPErrors.NO_RESPONSE); } if (responseOpts) { (0, Opts_2.assertValidResponseOpts)(responseOpts); } const idToken = authorizationResponsePayload.id_token ? yield id_token_1.IDToken.fromIDToken(authorizationResponsePayload.id_token) : undefined; return new AuthorizationResponse({ authorizationResponsePayload, idToken, responseOpts, }); }); } static fromAuthorizationRequest(authorizationRequest, responseOpts, verifyOpts) { return __awaiter(this, void 0, void 0, function* () { (0, Opts_2.assertValidResponseOpts)(responseOpts); if (!authorizationRequest) { throw new Error(types_1.SIOPErrors.NO_REQUEST); } const verifiedRequest = yield authorizationRequest.verify(verifyOpts); return yield AuthorizationResponse.fromVerifiedAuthorizationRequest(verifiedRequest, responseOpts, verifyOpts); }); } static fromVerifiedAuthorizationRequest(verifiedAuthorizationRequest, responseOpts, verifyOpts) { return __awaiter(this, void 0, void 0, function* () { (0, Opts_2.assertValidResponseOpts)(responseOpts); if (!verifiedAuthorizationRequest) { throw new Error(types_1.SIOPErrors.NO_REQUEST); } const authorizationRequest = verifiedAuthorizationRequest.authorizationRequest; // const merged = verifiedAuthorizationRequest.authorizationRequest.requestObject, verifiedAuthorizationRequest.requestObject); // const presentationDefinitions = await PresentationExchange.findValidPresentationDefinitions(merged, await authorizationRequest.getSupportedVersion()); const presentationDefinitions = JSON.parse(JSON.stringify(verifiedAuthorizationRequest.presentationDefinitions)); const wantsIdToken = yield authorizationRequest.containsResponseType(types_1.ResponseType.ID_TOKEN); const hasVpToken = yield authorizationRequest.containsResponseType(types_1.ResponseType.VP_TOKEN); const idToken = wantsIdToken ? yield id_token_1.IDToken.fromVerifiedAuthorizationRequest(verifiedAuthorizationRequest, responseOpts) : undefined; const idTokenPayload = idToken ? yield idToken.payload() : undefined; const authorizationResponsePayload = yield (0, Payload_1.createResponsePayload)(authorizationRequest, responseOpts, idTokenPayload); const response = new AuthorizationResponse({ authorizationResponsePayload, idToken, responseOpts, authorizationRequest, }); if (!hasVpToken) return response; if (responseOpts.presentationExchange) { const wrappedPresentations = response.payload.vp_token ? (0, OpenID4VP_1.extractPresentationsFromVpToken)(response.payload.vp_token, { hasher: verifyOpts.hasher, }) : []; yield (0, OpenID4VP_1.assertValidVerifiablePresentations)({ presentationDefinitions, presentations: wrappedPresentations, verificationCallback: verifyOpts.verification.presentationVerificationCallback, opts: Object.assign(Object.assign({}, responseOpts.presentationExchange), { hasher: verifyOpts.hasher }), }); } else if (verifiedAuthorizationRequest.dcqlQuery) { yield Dcql_1.Dcql.assertValidDcqlPresentationResult(responseOpts.dcqlResponse.dcqlPresentation, verifiedAuthorizationRequest.dcqlQuery, { hasher: verifyOpts.hasher, }); } else { throw new Error('vp_token is present, but no presentation definitions or dcql query provided'); } return response; }); } verify(verifyOpts) { return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c, _d, _e, _f, _g; // Merge payloads checks for inconsistencies in properties which are present in both the auth request and request object const merged = yield this.mergedPayloads({ consistencyCheck: true, hasher: verifyOpts.hasher, }); if (verifyOpts.state && merged.state !== verifyOpts.state) { throw Error(types_1.SIOPErrors.BAD_STATE); } const verifiedIdToken = yield ((_a = this.idToken) === null || _a === void 0 ? void 0 : _a.verify(verifyOpts)); if (this.payload.vp_token && !verifyOpts.presentationDefinitions && !verifyOpts.dcqlQuery) { return Promise.reject(Error('vp_token is present, but no presentation definitions or dcql query provided')); } const emptyPresentationDefinitions = Array.isArray(verifyOpts.presentationDefinitions) && verifyOpts.presentationDefinitions.length === 0; if (!this.payload.vp_token && ((verifyOpts.presentationDefinitions && !emptyPresentationDefinitions) || verifyOpts.dcqlQuery)) { return Promise.reject(Error('Presentation definitions or dcql query provided, but no vp_token present')); } const oid4vp = this.payload.vp_token ? yield (0, OpenID4VP_1.verifyPresentations)(this, verifyOpts) : undefined; // Gather all nonces const allNonces = new Set(); if (oid4vp && (((_b = oid4vp.dcql) === null || _b === void 0 ? void 0 : _b.nonce) || ((_c = oid4vp.presentationExchange) === null || _c === void 0 ? void 0 : _c.nonce))) allNonces.add((_e = (_d = oid4vp.dcql) === null || _d === void 0 ? void 0 : _d.nonce) !== null && _e !== void 0 ? _e : (_f = oid4vp.presentationExchange) === null || _f === void 0 ? void 0 : _f.nonce); if (verifiedIdToken) allNonces.add(verifiedIdToken.payload.nonce); if (merged.nonce) allNonces.add(merged.nonce); // We only verify the nonce if there is one. We handle the case if the nonce is undefined // but it should be defined elsewhere. So if the nonce is undefined we don't have to verify it const firstNonce = Array.from(allNonces)[0]; if (allNonces.size > 1) { throw new Error('both id token and VPs in vp token if present must have a nonce, and all nonces must be the same'); } if (verifyOpts.nonce && firstNonce && firstNonce !== verifyOpts.nonce) { throw Error(types_1.SIOPErrors.BAD_NONCE); } const state = (_g = merged.state) !== null && _g !== void 0 ? _g : verifiedIdToken === null || verifiedIdToken === void 0 ? void 0 : verifiedIdToken.payload.state; if (!state) { throw Error('State is required'); } return Object.assign(Object.assign(Object.assign({ authorizationResponse: this, verifyOpts, nonce: firstNonce, state, correlationId: verifyOpts.correlationId }, (this.idToken && { idToken: verifiedIdToken })), ((oid4vp === null || oid4vp === void 0 ? void 0 : oid4vp.presentationExchange) && { oid4vpSubmission: oid4vp.presentationExchange })), ((oid4vp === null || oid4vp === void 0 ? void 0 : oid4vp.dcql) && { oid4vpSubmissionDcql: oid4vp.dcql })); }); } get authorizationRequest() { return this._authorizationRequest; } get payload() { return this._payload; } get options() { return this._options; } get idToken() { return this._idToken; } getMergedProperty(key, opts) { const merged = this.mergedPayloads(opts); // FIXME this is really bad, expensive... return merged[key]; } mergedPayloads(opts) { var _a, _b; let nonce = this._payload.nonce; if ((_a = this._payload) === null || _a === void 0 ? void 0 : _a.vp_token) { let presentations; try { presentations = (0, OpenID4VP_1.extractPresentationsFromDcqlVpToken)(this._payload.vp_token, opts); } catch (e) { presentations = (0, OpenID4VP_1.extractPresentationsFromVpToken)(this._payload.vp_token, opts); } if (!presentations || (Array.isArray(presentations) && presentations.length === 0)) { return Promise.reject(Error('missing presentation(s)')); } const presentationsArray = Array.isArray(presentations) ? presentations : [presentations]; // We do not verify them, as that is done elsewhere. So we simply can take the first nonce nonce = presentationsArray // FIXME toWrappedVerifiablePresentation() does not extract the nonce yet from mdocs. // However the nonce is validated as part of the mdoc verification process (using the session transcript bytes) // Once it is available we can also test it here, but it will be verified elsewhre as well .filter((presentation) => !ssi_types_1.CredentialMapper.isWrappedMdocPresentation(presentation)) .map(OpenID4VP_1.extractNonceFromWrappedVerifiablePresentation) .find((nonce) => nonce !== undefined); } const idTokenPayload = (_b = this.idToken) === null || _b === void 0 ? void 0 : _b.payload(); if ((opts === null || opts === void 0 ? void 0 : opts.consistencyCheck) !== false && idTokenPayload) { Object.entries(idTokenPayload).forEach((entry) => { if (typeof entry[0] === 'string' && this.payload[entry[0]] && this.payload[entry[0]] !== entry[1]) { throw Error(`Mismatch in Authorization Request and Request object value for ${entry[0]}`); } }); } if (!nonce && this._idToken) { nonce = idTokenPayload.nonce; } return Object.assign(Object.assign(Object.assign({}, this.payload), idTokenPayload), { nonce }); } } exports.AuthorizationResponse = AuthorizationResponse; //# sourceMappingURL=AuthorizationResponse.js.map