@sphereon/did-auth-siop
Version:
Self Issued OpenID V2 (SIOPv2) and OpenID 4 Verifiable Presentations (OID4VP)
289 lines • 17.5 kB
JavaScript
;
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.OP = void 0;
const jarm_1 = require("@sphereon/jarm");
const oid4vc_common_1 = require("@sphereon/oid4vc-common");
const authorization_request_1 = require("../authorization-request");
const Opts_1 = require("../authorization-request/Opts");
const authorization_response_1 = require("../authorization-response");
const helpers_1 = require("../helpers");
const ExtractJwks_1 = require("../helpers/ExtractJwks");
const SIOPSpecVersion_1 = require("../helpers/SIOPSpecVersion");
const types_1 = require("../types");
const OPBuilder_1 = require("./OPBuilder");
const Opts_2 = require("./Opts");
// The OP publishes the formats it supports using the vp_formats_supported metadata parameter as defined above in its "openid-configuration".
class OP {
constructor(opts) {
var _a;
this._createResponseOptions = Object.assign({}, (0, Opts_2.createResponseOptsFromBuilderOrExistingOpts)(opts));
this._verifyRequestOptions = Object.assign({}, (0, Opts_2.createVerifyRequestOptsFromBuilderOrExistingOpts)(opts));
this._eventEmitter = (_a = opts.builder) === null || _a === void 0 ? void 0 : _a.eventEmitter;
}
/**
* This method tries to infer the SIOP specs version based on the request payload.
* If the version cannot be inferred or is not supported it throws an exception.
* This method needs to be called to ensure the OP can handle the request
* @param requestJwtOrUri
* @param requestOpts
*/
verifyAuthorizationRequest(requestJwtOrUri, requestOpts) {
return __awaiter(this, void 0, void 0, function* () {
const correlationId = (requestOpts === null || requestOpts === void 0 ? void 0 : requestOpts.correlationId) || (0, oid4vc_common_1.uuidv4)();
let authorizationRequest;
try {
authorizationRequest = yield authorization_request_1.AuthorizationRequest.fromUriOrJwt(requestJwtOrUri);
yield this.emitEvent(types_1.AuthorizationEvents.ON_AUTH_REQUEST_RECEIVED_SUCCESS, { correlationId, subject: authorizationRequest });
}
catch (error) {
if (error instanceof Error) {
yield this.emitEvent(types_1.AuthorizationEvents.ON_AUTH_REQUEST_RECEIVED_FAILED, {
correlationId,
subject: requestJwtOrUri,
error,
});
}
throw error;
}
try {
const verifiedAuthorizationRequest = yield authorizationRequest.verify(this.newVerifyAuthorizationRequestOpts(Object.assign(Object.assign({}, requestOpts), { correlationId })));
yield this.emitEvent(types_1.AuthorizationEvents.ON_AUTH_REQUEST_VERIFIED_SUCCESS, {
correlationId,
subject: verifiedAuthorizationRequest.authorizationRequest,
});
return verifiedAuthorizationRequest;
}
catch (error) {
yield this.emitEvent(types_1.AuthorizationEvents.ON_AUTH_REQUEST_VERIFIED_FAILED, {
correlationId,
subject: authorizationRequest,
error,
});
throw error;
}
});
}
createAuthorizationResponse(verifiedAuthorizationRequest, responseOpts) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b, _c, _d;
if (verifiedAuthorizationRequest.correlationId &&
(responseOpts === null || responseOpts === void 0 ? void 0 : responseOpts.correlationId) &&
verifiedAuthorizationRequest.correlationId !== responseOpts.correlationId) {
throw new Error(`Request correlation id ${verifiedAuthorizationRequest.correlationId} is different from option correlation id ${responseOpts.correlationId}`);
}
let version = responseOpts === null || responseOpts === void 0 ? void 0 : responseOpts.version;
const rpSupportedVersions = (0, SIOPSpecVersion_1.authorizationRequestVersionDiscovery)(yield verifiedAuthorizationRequest.authorizationRequest.mergedPayloads());
if (version && rpSupportedVersions.length > 0 && !rpSupportedVersions.includes(version)) {
throw Error(`RP does not support spec version ${version}, supported versions: ${rpSupportedVersions.toString()}`);
}
else if (!version) {
version = rpSupportedVersions.reduce((previous, current) => (current.valueOf() > previous.valueOf() ? current : previous), types_1.SupportedVersion.SIOPv2_ID1);
}
const correlationId = (_b = (_a = responseOpts === null || responseOpts === void 0 ? void 0 : responseOpts.correlationId) !== null && _a !== void 0 ? _a : verifiedAuthorizationRequest.correlationId) !== null && _b !== void 0 ? _b : (0, oid4vc_common_1.uuidv4)();
try {
// IF using DIRECT_POST, the response_uri takes precedence over the redirect_uri
let responseUri = verifiedAuthorizationRequest.responseURI;
if (((_c = verifiedAuthorizationRequest.payload) === null || _c === void 0 ? void 0 : _c.response_mode) === types_1.ResponseMode.DIRECT_POST) {
responseUri = (_d = verifiedAuthorizationRequest.authorizationRequestPayload.response_uri) !== null && _d !== void 0 ? _d : responseUri;
}
const response = yield authorization_response_1.AuthorizationResponse.fromVerifiedAuthorizationRequest(verifiedAuthorizationRequest, this.newAuthorizationResponseOpts(Object.assign(Object.assign({}, responseOpts), { version,
correlationId })), verifiedAuthorizationRequest.verifyOpts);
void this.emitEvent(types_1.AuthorizationEvents.ON_AUTH_RESPONSE_CREATE_SUCCESS, {
correlationId,
subject: response,
});
return { correlationId, response, responseURI: responseUri };
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}
catch (error) {
void this.emitEvent(types_1.AuthorizationEvents.ON_AUTH_RESPONSE_CREATE_FAILED, {
correlationId,
subject: verifiedAuthorizationRequest.authorizationRequest,
error,
});
throw error;
}
});
}
static extractEncJwksFromClientMetadata(clientMetadata) {
return __awaiter(this, void 0, void 0, function* () {
// TODO: Currently no mechanisms are in place to deal with multiple 'enc' keys in the client metadata.
// TODO: Maybe we should return all 'enc' keys in the client metadata. In addition the JWKS from the jwks_uri are not fetched if jwks are present.
// TODO: Is that the expected behavior?
const jwks = yield (0, ExtractJwks_1.extractJwksFromJwksMetadata)(clientMetadata);
const encryptionJwk = jwks === null || jwks === void 0 ? void 0 : jwks.keys.find((key) => key.use === 'enc');
if (!encryptionJwk) {
throw new Error('No encryption jwk could be extracted from the client metadata.');
}
return encryptionJwk;
});
}
// TODO SK Can you please put some documentation on it?
submitAuthorizationResponse(authorizationResponse, createJarmResponse) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b, _c, _d, _e, _f, _g, _h;
const { correlationId, response } = authorizationResponse;
if (!correlationId) {
throw Error('No correlation Id provided');
}
const isJarmResponseMode = (responseMode) => {
return responseMode === types_1.ResponseMode.DIRECT_POST_JWT || responseMode === types_1.ResponseMode.QUERY_JWT || responseMode === types_1.ResponseMode.FRAGMENT_JWT;
};
const requestObjectPayload = yield ((_a = response.authorizationRequest.requestObject) === null || _a === void 0 ? void 0 : _a.getPayload());
const responseMode = (_b = requestObjectPayload === null || requestObjectPayload === void 0 ? void 0 : requestObjectPayload.response_mode) !== null && _b !== void 0 ? _b : (_c = response.options) === null || _c === void 0 ? void 0 : _c.responseMode;
if (!response ||
(((_d = response.options) === null || _d === void 0 ? void 0 : _d.responseMode) &&
!(responseMode === types_1.ResponseMode.POST ||
responseMode === types_1.ResponseMode.FORM_POST ||
responseMode === types_1.ResponseMode.DIRECT_POST ||
isJarmResponseMode(responseMode)))) {
throw new Error(types_1.SIOPErrors.BAD_PARAMS);
}
const payload = response.payload;
const idToken = yield ((_e = response.idToken) === null || _e === void 0 ? void 0 : _e.payload());
const responseUri = (_f = authorizationResponse.responseURI) !== null && _f !== void 0 ? _f : idToken === null || idToken === void 0 ? void 0 : idToken.aud;
if (!responseUri) {
throw Error('No response URI present');
}
if (isJarmResponseMode(responseMode)) {
if (responseMode !== types_1.ResponseMode.DIRECT_POST_JWT) {
throw new Error('Only direct_post.jwt response mode is supported for JARM at the moment.');
}
let responseType;
if (idToken && payload.vp_token) {
responseType = 'id_token vp_token';
}
else if (idToken) {
responseType = 'id_token';
}
else if (payload.vp_token) {
responseType = 'vp_token';
}
else {
throw new Error('No id_token or vp_token present in the response payload');
}
const clientMetadata = (_h = (_g = authorizationResponse.response.authorizationRequest.options) === null || _g === void 0 ? void 0 : _g.clientMetadata) !== null && _h !== void 0 ? _h : requestObjectPayload.client_metadata;
const { response } = yield createJarmResponse({
requestObjectPayload,
authorizationResponsePayload: payload,
clientMetadata,
});
try {
const jarmResponse = yield (0, jarm_1.jarmAuthResponseSend)({
authRequestParams: {
response_uri: responseUri,
response_mode: responseMode,
response_type: responseType,
},
authResponse: response,
state: requestObjectPayload.state,
});
void this.emitEvent(types_1.AuthorizationEvents.ON_AUTH_RESPONSE_SENT_SUCCESS, { correlationId, subject: response });
return jarmResponse;
}
catch (error) {
void this.emitEvent(types_1.AuthorizationEvents.ON_AUTH_RESPONSE_SENT_FAILED, {
correlationId,
subject: response,
error,
});
throw error;
}
}
const authResponseAsURI = (0, helpers_1.encodeJsonAsURI)(payload, { arraysWithIndex: ['presentation_submission'] });
try {
const result = yield (0, helpers_1.post)(responseUri, authResponseAsURI, { contentType: types_1.ContentType.FORM_URL_ENCODED, exceptionOnHttpErrorStatus: true });
yield this.emitEvent(types_1.AuthorizationEvents.ON_AUTH_RESPONSE_SENT_SUCCESS, { correlationId, subject: response });
return result.origResponse;
}
catch (error) {
yield this.emitEvent(types_1.AuthorizationEvents.ON_AUTH_RESPONSE_SENT_FAILED, { correlationId, subject: response, error: error });
throw error;
}
});
}
/**
* Create an Authentication Request Payload from a URI string
*
* @param encodedUri
* @param rpRegistrationMetadata
*/
parseAuthorizationRequestURI(encodedUri, rpRegistrationMetadata) {
return __awaiter(this, void 0, void 0, function* () {
const { scheme, requestObjectJwt, authorizationRequestPayload, registrationMetadata } = yield authorization_request_1.URI.parseAndResolve(encodedUri, rpRegistrationMetadata);
return {
encodedUri,
encodingFormat: types_1.UrlEncodingFormat.FORM_URL_ENCODED,
scheme: scheme,
requestObjectJwt,
authorizationRequestPayload,
registration: registrationMetadata,
};
});
}
newAuthorizationResponseOpts(opts) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
const version = (_a = opts.version) !== null && _a !== void 0 ? _a : this._createResponseOptions.version;
let issuer = (_b = opts.issuer) !== null && _b !== void 0 ? _b : (_d = (_c = this._createResponseOptions) === null || _c === void 0 ? void 0 : _c.registration) === null || _d === void 0 ? void 0 : _d.issuer;
if (version === types_1.SupportedVersion.JWT_VC_PRESENTATION_PROFILE_v1) {
issuer = types_1.ResponseIss.JWT_VC_PRESENTATION_V1;
}
else if (version === types_1.SupportedVersion.SIOPv2_ID1) {
issuer = types_1.ResponseIss.SELF_ISSUED_V2;
}
if (!issuer) {
throw Error(`No issuer value present. Either use IDv1, JWT VC Presentation profile version, or provide a DID as issuer value`);
}
// We are taking the whole presentationExchange object from a certain location
const presentationExchange = (_e = opts.presentationExchange) !== null && _e !== void 0 ? _e : this._createResponseOptions.presentationExchange;
const dcqlQuery = (_f = opts.dcqlResponse) !== null && _f !== void 0 ? _f : this._createResponseOptions.dcqlResponse;
const responseURI = (_g = opts.audience) !== null && _g !== void 0 ? _g : this._createResponseOptions.responseURI;
return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, this._createResponseOptions), opts), (presentationExchange && { presentationExchange })), (dcqlQuery && { dcqlQuery })), { registration: Object.assign(Object.assign({}, (_h = this._createResponseOptions) === null || _h === void 0 ? void 0 : _h.registration), { issuer }), responseURI, responseURIType: (_j = this._createResponseOptions.responseURIType) !== null && _j !== void 0 ? _j : (version < types_1.SupportedVersion.SIOPv2_D12_OID4VP_D18 && responseURI ? 'redirect_uri' : undefined) });
}
newVerifyAuthorizationRequestOpts(requestOpts) {
const verification = Object.assign(Object.assign(Object.assign(Object.assign({}, this._verifyRequestOptions), { verifyJwtCallback: this._verifyRequestOptions.verifyJwtCallback }), requestOpts), { verification: (0, Opts_1.mergeVerificationOpts)(this._verifyRequestOptions, requestOpts), correlationId: requestOpts.correlationId });
return verification;
}
emitEvent(type, payload) {
return __awaiter(this, void 0, void 0, function* () {
if (this._eventEmitter) {
this._eventEmitter.emit(type, new types_1.AuthorizationEvent(payload));
}
});
}
addEventListener(register) {
if (!this._eventEmitter) {
throw Error('Cannot add listeners if no event emitter is available');
}
const events = Array.isArray(register.event) ? register.event : [register.event];
for (const event of events) {
this._eventEmitter.addListener(event, register.listener);
}
}
static fromOpts(responseOpts, verifyOpts) {
return new OP({ responseOpts, verifyOpts });
}
static builder() {
return new OPBuilder_1.OPBuilder();
}
get createResponseOptions() {
return this._createResponseOptions;
}
get verifyRequestOptions() {
return this._verifyRequestOptions;
}
static validateJarmMetadata(input) {
return (0, jarm_1.jarmMetadataValidate)(input);
}
}
exports.OP = OP;
//# sourceMappingURL=OP.js.map