UNPKG

@sphereon/ssi-sdk.siopv2-oid4vp-rp-rest-api

Version:

474 lines (470 loc) • 20.8 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { SIOPv2RPApiServer: () => SIOPv2RPApiServer, authStatusWebappEndpoint: () => authStatusWebappEndpoint, createAuthRequestWebappEndpoint: () => createAuthRequestWebappEndpoint, getAuthRequestSIOPv2Endpoint: () => getAuthRequestSIOPv2Endpoint, getDefinitionsEndpoint: () => getDefinitionsEndpoint, removeAuthRequestStateWebappEndpoint: () => removeAuthRequestStateWebappEndpoint, verifyAuthResponseSIOPv2Endpoint: () => verifyAuthResponseSIOPv2Endpoint }); module.exports = __toCommonJS(index_exports); // src/siop-api-functions.ts var import_did_auth_siop = require("@sphereon/did-auth-siop"); var import_ssi_express_support = require("@sphereon/ssi-express-support"); var import_ssi_types = require("@sphereon/ssi-types"); var parseAuthorizationResponse = /* @__PURE__ */ __name((request) => { const contentType = request.header("content-type"); if (contentType === "application/json") { const payload = typeof request.body === "string" ? JSON.parse(request.body) : request.body; return payload; } if (contentType === "application/x-www-form-urlencoded") { const payload = request.body; if (typeof payload.presentation_submission === "string") { console.log(`Supplied presentation_submission was a string instead of JSON. Correcting, but external party should fix their implementation!`); payload.presentation_submission = JSON.parse(payload.presentation_submission); } if (typeof payload.vp_token === "string") { const { vp_token } = payload; if (vp_token.startsWith("[") && vp_token.endsWith("]") || import_ssi_types.CredentialMapper.isJsonLdAsString(vp_token)) { payload.vp_token = JSON.parse(vp_token); } } return payload; } throw new Error(`Unsupported content type: ${contentType}. Currently only application/x-www-form-urlencoded and application/json (for direct_post) are supported`); }, "parseAuthorizationResponse"); function verifyAuthResponseSIOPv2Endpoint(router, context, opts) { if (opts?.enabled === false) { console.log(`verifyAuthResponse SIOP endpoint is disabled`); return; } const path = opts?.path ?? "/siop/definitions/:definitionId/auth-responses/:correlationId"; router.post(path, (0, import_ssi_express_support.checkAuth)(opts?.endpoint), async (request, response) => { try { const { correlationId, definitionId, tenantId, version } = request.params; if (!correlationId || !definitionId) { console.log(`No authorization request could be found for the given url. correlationId: ${correlationId}, definitionId: ${definitionId}`); return (0, import_ssi_express_support.sendErrorResponse)(response, 404, "No authorization request could be found"); } console.log("Authorization Response (siop-sessions"); console.log(JSON.stringify(request.body, null, 2)); const definitionItems = await context.agent.pdmGetDefinitions({ filter: [ { definitionId, tenantId, version } ] }); if (definitionItems.length === 0) { console.log(`Could not get definition ${definitionId} from agent. Will return 404`); response.statusCode = 404; response.statusMessage = `No definition ${definitionId}`; return response.send(); } const authorizationResponse = parseAuthorizationResponse(request); console.log(`URI: ${JSON.stringify(authorizationResponse)}`); const definitionItem = definitionItems[0]; const verifiedResponse = await context.agent.siopVerifyAuthResponse({ authorizationResponse, correlationId, definitionId, presentationDefinitions: [ { location: opts?.presentationDefinitionLocation ?? import_did_auth_siop.PresentationDefinitionLocation.TOPLEVEL_PRESENTATION_DEF, definition: definitionItem.definitionPayload } ], dcqlQuery: definitionItem.dcqlPayload }); const wrappedPresentation = verifiedResponse?.oid4vpSubmission?.presentations[0]; if (wrappedPresentation) { console.log("PRESENTATION:" + JSON.stringify(wrappedPresentation.presentation, null, 2)); response.statusCode = 200; const authorizationChallengeValidationResponse = { presentation_during_issuance_session: verifiedResponse.correlationId }; if (authorizationResponse.is_first_party) { response.setHeader("Content-Type", "application/json"); return response.send(JSON.stringify(authorizationChallengeValidationResponse)); } const responseRedirectURI = await context.agent.siopGetRedirectURI({ correlationId, definitionId, state: verifiedResponse.state }); if (responseRedirectURI) { response.setHeader("Content-Type", "application/json"); return response.send(JSON.stringify({ redirect_uri: responseRedirectURI })); } } else { console.log("Missing Presentation (Verifiable Credentials)"); response.statusCode = 500; response.statusMessage = "Missing Presentation (Verifiable Credentials)"; } return response.send(); } catch (error) { console.error(error); return (0, import_ssi_express_support.sendErrorResponse)(response, 500, "Could not verify auth status", error); } }); } __name(verifyAuthResponseSIOPv2Endpoint, "verifyAuthResponseSIOPv2Endpoint"); function getAuthRequestSIOPv2Endpoint(router, context, opts) { if (opts?.enabled === false) { console.log(`getAuthRequest SIOP endpoint is disabled`); return; } const path = opts?.path ?? "/siop/definitions/:definitionId/auth-requests/:correlationId"; router.get(path, (0, import_ssi_express_support.checkAuth)(opts?.endpoint), async (request, response) => { try { const correlationId = request.params.correlationId; const definitionId = request.params.definitionId; if (!correlationId || !definitionId) { console.log(`No authorization request could be found for the given url. correlationId: ${correlationId}, definitionId: ${definitionId}`); return (0, import_ssi_express_support.sendErrorResponse)(response, 404, "No authorization request could be found"); } const requestState = await context.agent.siopGetAuthRequestState({ correlationId, definitionId, errorOnNotFound: false }); if (!requestState) { console.log(`No authorization request could be found for the given url in the state manager. correlationId: ${correlationId}, definitionId: ${definitionId}`); return (0, import_ssi_express_support.sendErrorResponse)(response, 404, `No authorization request could be found`); } const requestObject = await requestState.request?.requestObject?.toJwt(); console.log("JWT Request object:"); console.log(requestObject); let error; try { response.statusCode = 200; response.setHeader("Content-Type", "application/jwt"); return response.send(requestObject); } catch (e) { error = typeof e === "string" ? e : e instanceof Error ? e.message : void 0; return (0, import_ssi_express_support.sendErrorResponse)(response, 500, "Could not get authorization request", e); } finally { await context.agent.siopUpdateAuthRequestState({ correlationId, definitionId, state: "sent", error }); } } catch (error) { return (0, import_ssi_express_support.sendErrorResponse)(response, 500, "Could not get authorization request", error); } }); } __name(getAuthRequestSIOPv2Endpoint, "getAuthRequestSIOPv2Endpoint"); // src/webapp-api-functions.ts var import_did_auth_siop2 = require("@sphereon/did-auth-siop"); var import_ssi_express_support2 = require("@sphereon/ssi-express-support"); var import_ssi_sdk = require("@sphereon/ssi-sdk.siopv2-oid4vp-common"); var import_ssi_sdk2 = require("@sphereon/ssi-sdk.siopv2-oid4vp-rp-auth"); var import_short_uuid = __toESM(require("short-uuid"), 1); var import_ssi_sdk3 = require("@sphereon/ssi-sdk.core"); function createAuthRequestWebappEndpoint(router, context, opts) { if (opts?.enabled === false) { console.log(`createAuthRequest Webapp endpoint is disabled`); return; } const path = opts?.path ?? "/webapp/definitions/:definitionId/auth-requests"; router.post(path, (0, import_ssi_express_support2.checkAuth)(opts?.endpoint), async (request, response) => { try { const definitionId = request.params.definitionId; if (!definitionId) { return (0, import_ssi_express_support2.sendErrorResponse)(response, 400, "No definitionId query parameter provided"); } const state = request.body.state ?? import_short_uuid.default.uuid(); const correlationId = request.body.correlationId ?? state; const qrCodeOpts = request.body.qrCodeOpts ?? opts?.qrCodeOpts; const requestByReferenceURI = (0, import_ssi_sdk.uriWithBase)(`/siop/definitions/${definitionId}/auth-requests/${state}`, { baseURI: opts?.siopBaseURI }); const responseURI = (0, import_ssi_sdk.uriWithBase)(`/siop/definitions/${definitionId}/auth-responses/${state}`, { baseURI: opts?.siopBaseURI }); const responseRedirectURI = ("response_redirect_uri" in request.body && request.body.response_redirect_uri) ?? ("responseRedirectURI" in request.body && request.body.responseRedirectURI); const authRequestURI = await context.agent.siopCreateAuthRequestURI({ definitionId, correlationId, state, nonce: import_short_uuid.default.uuid(), requestByReferenceURI, responseURIType: "response_uri", responseURI, ...responseRedirectURI && { responseRedirectURI } }); let qrCodeDataUri; if (qrCodeOpts) { const { AwesomeQR } = await import("awesome-qr"); const qrCode = new AwesomeQR({ ...qrCodeOpts, text: authRequestURI }); qrCodeDataUri = `data:image/png;base64,${(await qrCode.draw()).toString("base64")}`; } const authRequestBody = { correlationId, state, definitionId, authRequestURI, authStatusURI: `${(0, import_ssi_sdk.uriWithBase)(opts?.webappAuthStatusPath ?? "/webapp/auth-status", { baseURI: opts?.webappBaseURI })}`, ...qrCodeDataUri && { qrCodeDataUri } }; console.log(`Auth Request URI data to send back: ${JSON.stringify(authRequestBody)}`); return response.json(authRequestBody); } catch (error) { return (0, import_ssi_express_support2.sendErrorResponse)(response, 500, "Could not create an authorization request URI", error); } }); } __name(createAuthRequestWebappEndpoint, "createAuthRequestWebappEndpoint"); function authStatusWebappEndpoint(router, context, opts) { if (opts?.enabled === false) { console.log(`authStatus Webapp endpoint is disabled`); return; } const path = opts?.path ?? "/webapp/auth-status"; router.post(path, (0, import_ssi_express_support2.checkAuth)(opts?.endpoint), async (request, response) => { try { console.log("Received auth-status request..."); const correlationId = request.body.correlationId; const definitionId = request.body.definitionId; const requestState = correlationId && definitionId ? await context.agent.siopGetAuthRequestState({ correlationId, definitionId, errorOnNotFound: false }) : void 0; if (!requestState || !definitionId || !correlationId) { console.log(`No authentication request mapping could be found for the given URL. correlation: ${correlationId}, definitionId: ${definitionId}`); response.statusCode = 404; const statusBody2 = { status: requestState ? requestState.status : "error", error: "No authentication request mapping could be found for the given URL.", correlationId, definitionId, lastUpdated: requestState ? requestState.lastUpdated : Date.now() }; return response.json(statusBody2); } let includeVerifiedData = import_ssi_sdk2.VerifiedDataMode.NONE; if ("includeVerifiedData" in request.body) { includeVerifiedData = request.body.includeVerifiedData; } let responseState; if (requestState.status === "sent") { responseState = await context.agent.siopGetAuthResponseState({ correlationId, definitionId, includeVerifiedData, errorOnNotFound: false }); } const overallState = responseState ?? requestState; const statusBody = { status: overallState.status, ...overallState.error ? { error: overallState.error?.message } : {}, correlationId, definitionId, lastUpdated: overallState.lastUpdated, ...responseState && responseState.status === import_did_auth_siop2.AuthorizationResponseStateStatus.VERIFIED ? { payload: await responseState.response.mergedPayloads({ hasher: import_ssi_sdk3.shaHasher }), verifiedData: responseState.verifiedData } : {} }; console.debug(`Will send auth status: ${JSON.stringify(statusBody)}`); if (overallState.status === "error") { response.statusCode = 500; return response.json(statusBody); } response.statusCode = 200; return response.json(statusBody); } catch (error) { return (0, import_ssi_express_support2.sendErrorResponse)(response, 500, error.message, error); } }); } __name(authStatusWebappEndpoint, "authStatusWebappEndpoint"); function removeAuthRequestStateWebappEndpoint(router, context, opts) { if (opts?.enabled === false) { console.log(`removeAuthStatus Webapp endpoint is disabled`); return; } const path = opts?.path ?? "/webapp/definitions/:definitionId/auth-requests/:correlationId"; router.delete(path, (0, import_ssi_express_support2.checkAuth)(opts?.endpoint), async (request, response) => { try { const correlationId = request.params.correlationId; const definitionId = request.params.definitionId; if (!correlationId || !definitionId) { console.log(`No authorization request could be found for the given url. correlationId: ${correlationId}, definitionId: ${definitionId}`); return (0, import_ssi_express_support2.sendErrorResponse)(response, 404, "No authorization request could be found"); } response.statusCode = 200; return response.json(await context.agent.siopDeleteAuthState({ definitionId, correlationId })); } catch (error) { return (0, import_ssi_express_support2.sendErrorResponse)(response, 500, error.message, error); } }); } __name(removeAuthRequestStateWebappEndpoint, "removeAuthRequestStateWebappEndpoint"); function getDefinitionsEndpoint(router, context, opts) { if (opts?.enabled === false) { console.log(`getDefinitions Webapp endpoint is disabled`); return; } const path = opts?.path ?? "/webapp/definitions"; router.get(path, (0, import_ssi_express_support2.checkAuth)(opts?.endpoint), async (request, response) => { try { const definitions = await context.agent.pdmGetDefinitions(); response.statusCode = 200; return response.json(definitions); } catch (error) { return (0, import_ssi_express_support2.sendErrorResponse)(response, 500, error.message, error); } }); } __name(getDefinitionsEndpoint, "getDefinitionsEndpoint"); // src/siopv2-rp-api-server.ts var import_ssi_sdk4 = require("@sphereon/ssi-sdk.core"); var import_ssi_express_support3 = require("@sphereon/ssi-express-support"); var import_express = __toESM(require("express"), 1); var import_swagger_ui_express = __toESM(require("swagger-ui-express"), 1); var SIOPv2RPApiServer = class { static { __name(this, "SIOPv2RPApiServer"); } _express; _router; _agent; _opts; _basePath; OID4VP_SWAGGER_URL = "https://api.swaggerhub.com/apis/SphereonInt/OID4VP/0.1.0"; constructor(args) { const { agent, opts } = args; this._agent = agent; (0, import_ssi_express_support3.copyGlobalAuthToEndpoints)({ opts, keys: [ "webappCreateAuthRequest", "webappAuthStatus", "webappDeleteAuthRequest" ] }); if (opts?.endpointOpts?.globalAuth?.secureSiopEndpoints) { (0, import_ssi_express_support3.copyGlobalAuthToEndpoints)({ opts, keys: [ "siopGetAuthRequest", "siopVerifyAuthResponse" ] }); } this._opts = opts; this._express = args.expressSupport.express; this._router = import_express.default.Router(); const context = (0, import_ssi_sdk4.agentContext)(agent); const features = opts?.enableFeatures ?? [ "rp-status", "siop" ]; console.log(`SIOPv2 API enabled, with features: ${JSON.stringify(features)}}`); if (features.includes("rp-status")) { createAuthRequestWebappEndpoint(this._router, context, opts?.endpointOpts?.webappCreateAuthRequest); authStatusWebappEndpoint(this._router, context, opts?.endpointOpts?.webappAuthStatus); removeAuthRequestStateWebappEndpoint(this._router, context, opts?.endpointOpts?.webappDeleteAuthRequest); getDefinitionsEndpoint(this._router, context, opts?.endpointOpts?.webappGetDefinitions); } if (features.includes("siop")) { getAuthRequestSIOPv2Endpoint(this._router, context, opts?.endpointOpts?.siopGetAuthRequest); verifyAuthResponseSIOPv2Endpoint(this._router, context, opts?.endpointOpts?.siopVerifyAuthResponse); } this._basePath = opts?.endpointOpts?.basePath ?? ""; this._express.use(this._basePath, this.router); this._express.set("trust proxy", opts?.endpointOpts?.trustProxy ?? true); this.setupSwaggerUi(); } setupSwaggerUi() { fetch(this.OID4VP_SWAGGER_URL).then((res) => res.json()).then((swagger) => { const apiDocs = `${this._basePath}/api-docs`; console.log(`[OID4P] API docs available at ${apiDocs}`); this._router.use("/api-docs", (req, res, next) => { const regex = `${apiDocs.replace(/\//, "/")}`.replace("/oid4vp", "").replace(/\/api-docs.*/, ""); swagger.servers = [ { url: `${req.protocol}://${req.get("host")}${regex}`, description: "This server" } ]; req.swaggerDoc = swagger; next(); }, import_swagger_ui_express.default.serveFiles(swagger, options), import_swagger_ui_express.default.setup()); }).catch((err) => { console.log(`[OID4VP] Unable to fetch swagger document: ${err}. Will not host api-docs on this instance`); }); const options = {}; } get express() { return this._express; } get router() { return this._router; } get agent() { return this._agent; } get opts() { return this._opts; } }; //# sourceMappingURL=index.cjs.map