UNPKG

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

Version:

443 lines (440 loc) • 18.1 kB
var __defProp = Object.defineProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); // src/siop-api-functions.ts import { PresentationDefinitionLocation } from "@sphereon/did-auth-siop"; import { checkAuth, sendErrorResponse } from "@sphereon/ssi-express-support"; import { CredentialMapper } from "@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("]") || 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, 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 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 ?? 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 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, 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 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 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 sendErrorResponse(response, 500, "Could not get authorization request", e); } finally { await context.agent.siopUpdateAuthRequestState({ correlationId, definitionId, state: "sent", error }); } } catch (error) { return sendErrorResponse(response, 500, "Could not get authorization request", error); } }); } __name(getAuthRequestSIOPv2Endpoint, "getAuthRequestSIOPv2Endpoint"); // src/webapp-api-functions.ts import { AuthorizationResponseStateStatus } from "@sphereon/did-auth-siop"; import { checkAuth as checkAuth2, sendErrorResponse as sendErrorResponse2 } from "@sphereon/ssi-express-support"; import { uriWithBase } from "@sphereon/ssi-sdk.siopv2-oid4vp-common"; import { VerifiedDataMode } from "@sphereon/ssi-sdk.siopv2-oid4vp-rp-auth"; import uuid from "short-uuid"; import { shaHasher as defaultHasher } from "@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, checkAuth2(opts?.endpoint), async (request, response) => { try { const definitionId = request.params.definitionId; if (!definitionId) { return sendErrorResponse2(response, 400, "No definitionId query parameter provided"); } const state = request.body.state ?? uuid.uuid(); const correlationId = request.body.correlationId ?? state; const qrCodeOpts = request.body.qrCodeOpts ?? opts?.qrCodeOpts; const requestByReferenceURI = uriWithBase(`/siop/definitions/${definitionId}/auth-requests/${state}`, { baseURI: opts?.siopBaseURI }); const responseURI = 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: uuid.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: `${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 sendErrorResponse2(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, checkAuth2(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 = 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 === AuthorizationResponseStateStatus.VERIFIED ? { payload: await responseState.response.mergedPayloads({ hasher: defaultHasher }), 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 sendErrorResponse2(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, checkAuth2(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 sendErrorResponse2(response, 404, "No authorization request could be found"); } response.statusCode = 200; return response.json(await context.agent.siopDeleteAuthState({ definitionId, correlationId })); } catch (error) { return sendErrorResponse2(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, checkAuth2(opts?.endpoint), async (request, response) => { try { const definitions = await context.agent.pdmGetDefinitions(); response.statusCode = 200; return response.json(definitions); } catch (error) { return sendErrorResponse2(response, 500, error.message, error); } }); } __name(getDefinitionsEndpoint, "getDefinitionsEndpoint"); // src/siopv2-rp-api-server.ts import { agentContext } from "@sphereon/ssi-sdk.core"; import { copyGlobalAuthToEndpoints } from "@sphereon/ssi-express-support"; import express from "express"; import swaggerUi from "swagger-ui-express"; 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; copyGlobalAuthToEndpoints({ opts, keys: [ "webappCreateAuthRequest", "webappAuthStatus", "webappDeleteAuthRequest" ] }); if (opts?.endpointOpts?.globalAuth?.secureSiopEndpoints) { copyGlobalAuthToEndpoints({ opts, keys: [ "siopGetAuthRequest", "siopVerifyAuthResponse" ] }); } this._opts = opts; this._express = args.expressSupport.express; this._router = express.Router(); const context = 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(); }, swaggerUi.serveFiles(swagger, options), swaggerUi.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; } }; export { SIOPv2RPApiServer, authStatusWebappEndpoint, createAuthRequestWebappEndpoint, getAuthRequestSIOPv2Endpoint, getDefinitionsEndpoint, removeAuthRequestStateWebappEndpoint, verifyAuthResponseSIOPv2Endpoint }; //# sourceMappingURL=index.js.map