@sphereon/ssi-sdk.siopv2-oid4vp-rp-rest-api
Version:
443 lines (440 loc) • 18.1 kB
JavaScript
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