@sphereon/ssi-sdk.siopv2-oid4vp-rp-rest-api
Version:
606 lines (599 loc) • 25.6 kB
JavaScript
;
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,
authStatusUniversalOID4VPEndpoint: () => authStatusUniversalOID4VPEndpoint,
buildQueryIdFilter: () => buildQueryIdFilter,
createAuthRequestUniversalOID4VPEndpoint: () => createAuthRequestUniversalOID4VPEndpoint,
getAuthRequestSIOPv2Endpoint: () => getAuthRequestSIOPv2Endpoint,
getDefinitionsEndpoint: () => getDefinitionsEndpoint,
removeAuthRequestStateUniversalOID4VPEndpoint: () => removeAuthRequestStateUniversalOID4VPEndpoint,
verifyAuthResponseSIOPv2Endpoint: () => verifyAuthResponseSIOPv2Endpoint
});
module.exports = __toCommonJS(index_exports);
// ../../node_modules/.pnpm/tsup@8.5.1_@microsoft+api-extractor@7.55.0_@types+node@20.19.25__@swc+core@1.15.2_@swc+_119fedc18e19d1717452bb9e989205b3/node_modules/tsup/assets/cjs_shims.js
var getImportMetaUrl = /* @__PURE__ */ __name(() => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href, "getImportMetaUrl");
var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
// src/siop-api-functions.ts
var import_ssi_express_support = require("@sphereon/ssi-express-support");
var import_ssi_types = require("@sphereon/ssi-types");
var import_uuid = require("uuid");
var parseAuthorizationResponse = /* @__PURE__ */ __name((request) => {
const contentType = request.header("content-type");
if (contentType?.startsWith("application/json")) {
const payload = typeof request.body === "string" ? JSON.parse(request.body) : request.body;
return payload;
}
if (contentType?.startsWith("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");
var validatePresentationSubmission = /* @__PURE__ */ __name((query, submission) => {
return query.credentials.every((credential) => credential.id in submission);
}, "validatePresentationSubmission");
function verifyAuthResponseSIOPv2Endpoint(router, context, opts) {
if (opts?.enabled === false) {
console.log(`verifyAuthResponse SIOP endpoint is disabled`);
return;
}
const path2 = opts?.path ?? "/siop/queries/:queryId/auth-responses/:correlationId";
router.post(path2, (0, import_ssi_express_support.checkAuth)(opts?.endpoint), async (request, response) => {
try {
const { correlationId, queryId, tenantId, version } = request.params;
if (!correlationId) {
console.log(`No authorization request could be found for the given url. correlationId: ${correlationId}`);
return (0, import_ssi_express_support.sendErrorResponse)(response, 404, "No authorization request could be found");
}
console.debug("Authorization Response (siop-sessions");
console.debug(JSON.stringify(request.body, null, 2));
const definitionItems = await context.agent.pdmGetDefinitions({
filter: buildQueryIdFilter(queryId, tenantId, version)
});
if (definitionItems.length === 0) {
console.log(`Could not get dcql query with id ${queryId} from agent. Will return 404`);
response.statusCode = 404;
response.statusMessage = `No definition ${queryId}`;
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,
dcqlQuery: definitionItem.query
});
const presentation = verifiedResponse?.oid4vpSubmission?.presentation;
if (presentation && validatePresentationSubmission(definitionItem.query, presentation)) {
console.log("PRESENTATIONS:" + JSON.stringify(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,
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 path2 = opts?.path ?? "/siop/queries/:queryId/auth-requests/:correlationId";
router.get(path2, (0, import_ssi_express_support.checkAuth)(opts?.endpoint), async (request, response) => {
try {
const correlationId = request.params.correlationId;
const queryId = request.params.queryId;
if (!correlationId || !queryId) {
console.log(`No authorization request could be found for the given url. correlationId: ${correlationId}, queryId: ${queryId}`);
return (0, import_ssi_express_support.sendErrorResponse)(response, 404, "No authorization request could be found");
}
const requestState = await context.agent.siopGetAuthRequestState({
correlationId,
errorOnNotFound: false
});
if (!requestState) {
console.log(`No authorization request could be found for the given url in the state manager. correlationId: ${correlationId}, definitionId: ${queryId}`);
return (0, import_ssi_express_support.sendErrorResponse)(response, 404, `No authorization request could be found`);
}
const definitionItems = await context.agent.pdmGetDefinitions({
filter: buildQueryIdFilter(queryId)
});
if (definitionItems.length === 0) {
console.log(`Could not get dcql query with id ${queryId} from agent. Will return 404`);
response.statusCode = 404;
response.statusMessage = `No definition ${queryId}`;
return response.send();
}
const payload = requestState.request?.requestObject?.getPayload();
payload.dcql_query = definitionItems[0].query;
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,
state: "authorization_request_created",
error
});
}
} catch (error) {
return (0, import_ssi_express_support.sendErrorResponse)(response, 500, "Could not get authorization request", error);
}
});
}
__name(getAuthRequestSIOPv2Endpoint, "getAuthRequestSIOPv2Endpoint");
function buildQueryIdFilter(queryId, tenantId, version) {
const queryFilter = {
queryId,
...tenantId ? {
tenantId
} : {},
...version ? {
version
} : {}
};
return [
queryFilter,
...(0, import_uuid.validate)(queryId) ? [
{
id: queryId
}
] : []
];
}
__name(buildQueryIdFilter, "buildQueryIdFilter");
// src/universal-oid4vp-api-functions.ts
var import_did_auth_siop = 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_short_uuid = __toESM(require("short-uuid"), 1);
// src/middleware/validationMiddleware.ts
var import_zod = require("zod");
var validateData = /* @__PURE__ */ __name((schema) => {
return (req, res, next) => {
try {
schema.parse(req.body);
next();
} catch (error) {
if (error instanceof import_zod.ZodError) {
const errorMessages = error.issues.map((issue) => ({
message: `${issue.path.join(".")} is ${issue.message}`
}));
res.status(400).json({
status: 400,
message: "Invalid data",
error_details: errorMessages[0].message
});
} else {
res.status(500).json({
status: 500,
message: "Internal Server Error"
});
}
}
};
}, "validateData");
// src/universal-oid4vp-api-functions.ts
function createAuthRequestUniversalOID4VPEndpoint(router, context, opts) {
if (opts?.enabled === false) {
console.log(`createAuthRequest universal OID4VP endpoint is disabled`);
return;
}
const path2 = opts?.path ?? "/backend/auth/requests";
router.post(path2, (0, import_ssi_express_support2.checkAuth)(opts?.endpoint), validateData(import_did_auth_siop.CreateAuthorizationRequestPayloadSchema), async (request, response) => {
try {
const authRequest = (0, import_did_auth_siop.createAuthorizationRequestFromPayload)(request.body);
const correlationId = authRequest.correlationId ?? import_short_uuid.default.uuid();
const qrCodeOpts = authRequest.qrCode ? {
...authRequest.qrCode
} : opts?.qrCodeOpts;
const queryId = authRequest.queryId;
const definitionItems = await context.agent.pdmGetDefinitions({
filter: buildQueryIdFilter(queryId)
});
if (definitionItems.length === 0) {
console.log(`No query could be found for the given id. Query id: ${queryId}`);
return (0, import_ssi_express_support2.sendErrorResponse)(response, 404, {
status: 404,
message: "No query could be found"
});
}
const requestByReferenceURI = (0, import_ssi_sdk.uriWithBase)(`/siop/queries/${queryId}/auth-requests/${correlationId}`, {
baseURI: authRequest.requestUriBase ?? opts?.siopBaseURI
});
const responseURI = (0, import_ssi_sdk.uriWithBase)(`/siop/queries/${queryId}/auth-responses/${correlationId}`, {
baseURI: opts?.siopBaseURI
});
const authRequestURI = await context.agent.siopCreateAuthRequestURI({
queryId,
correlationId,
nonce: import_short_uuid.default.uuid(),
requestByReferenceURI,
responseURIType: "response_uri",
responseURI,
...authRequest.directPostResponseRedirectUri && {
responseRedirectURI: authRequest.directPostResponseRedirectUri
},
...authRequest.callback && {
callback: authRequest.callback
}
});
let qrCodeDataUri;
if (qrCodeOpts) {
const { AwesomeQR } = await import("awesome-qr");
const qrCode = new AwesomeQR({
text: authRequestURI,
size: qrCodeOpts.size ?? 250,
colorDark: qrCodeOpts.colorDark ?? "#000000",
colorLight: qrCodeOpts.colorLight ?? "#FFFFFF"
});
qrCodeDataUri = `data:image/png;base64,${(await qrCode.draw()).toString("base64")}`;
} else {
qrCodeDataUri = authRequestURI;
}
const authRequestBody = {
query_id: queryId,
correlation_id: correlationId,
request_uri: authRequestURI,
status_uri: `${(0, import_ssi_sdk.uriWithBase)(opts?.webappAuthStatusPath ?? `/backend/auth/status/${correlationId}`, {
baseURI: opts?.webappBaseURI
})}`,
...qrCodeDataUri && {
qr_uri: qrCodeDataUri
}
};
console.log(`Auth Request URI data to send back: ${JSON.stringify(authRequestBody)}`);
return response.status(201).json(authRequestBody);
} catch (error) {
return (0, import_ssi_express_support2.sendErrorResponse)(response, 500, {
status: 500,
message: "Could not create an authorization request URI"
}, error);
}
});
}
__name(createAuthRequestUniversalOID4VPEndpoint, "createAuthRequestUniversalOID4VPEndpoint");
function removeAuthRequestStateUniversalOID4VPEndpoint(router, context, opts) {
if (opts?.enabled === false) {
console.log(`removeAuthStatus universal OID4VP endpoint is disabled`);
return;
}
const path2 = opts?.path ?? "/backend/auth/requests/:correlationId";
router.delete(path2, (0, import_ssi_express_support2.checkAuth)(opts?.endpoint), async (request, response) => {
try {
const correlationId = request.params.correlationId;
const authRequestState = await context.agent.siopGetAuthRequestState({
correlationId,
errorOnNotFound: false
});
if (!authRequestState) {
console.log(`No authorization request could be found for the given correlationId. correlationId: ${correlationId}`);
return (0, import_ssi_express_support2.sendErrorResponse)(response, 404, {
status: 404,
message: "No authorization request could be found"
});
}
await context.agent.siopDeleteAuthState({
correlationId
});
return response.status(204).json();
} catch (error) {
return (0, import_ssi_express_support2.sendErrorResponse)(response, 500, {
status: 500,
message: error.message
}, error);
}
});
}
__name(removeAuthRequestStateUniversalOID4VPEndpoint, "removeAuthRequestStateUniversalOID4VPEndpoint");
function authStatusUniversalOID4VPEndpoint(router, context, opts) {
if (opts?.enabled === false) {
console.log(`authStatus universal OID4VP endpoint is disabled`);
return;
}
const path2 = opts?.path ?? "/backend/auth/status/:correlationId";
router.get(path2, (0, import_ssi_express_support2.checkAuth)(opts?.endpoint), async (request, response) => {
try {
console.log("Received auth-status request...");
const correlationId = request.params.correlationId;
const requestState = await context.agent.siopGetAuthRequestState({
correlationId,
errorOnNotFound: false
});
if (!requestState) {
console.log(`No authorization request could be found for the given correlationId. correlationId: ${correlationId}`);
return (0, import_ssi_express_support2.sendErrorResponse)(response, 404, {
status: 404,
message: "No authorization request could be found"
});
}
let responseState;
if (requestState.status === import_did_auth_siop.AuthorizationRequestStateStatus.RETRIEVED) {
responseState = await context.agent.siopGetAuthResponseState({
correlationId,
errorOnNotFound: false
});
}
const overallState = responseState ?? requestState;
const statusBody = {
status: overallState.status,
correlation_id: overallState.correlationId,
query_id: overallState.queryId,
last_updated: overallState.lastUpdated,
..."verifiedData" in overallState && {
verified_data: overallState.verifiedData
},
...overallState.error && {
message: overallState.error.message
}
};
console.debug(`Will send auth status: ${JSON.stringify(statusBody)}`);
if (overallState.status === "error") {
return response.status(500).json(statusBody);
}
return response.status(200).json(statusBody);
} catch (error) {
return (0, import_ssi_express_support2.sendErrorResponse)(response, 500, {
status: 500,
message: error.message
}, error);
}
});
}
__name(authStatusUniversalOID4VPEndpoint, "authStatusUniversalOID4VPEndpoint");
function getDefinitionsEndpoint(router, context, opts) {
if (opts?.enabled === false) {
console.log(`getDefinitions universal OID4VP endpoint is disabled`);
return;
}
const path2 = opts?.path ?? "/backend/definitions";
router.get(path2, (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, {
status: 500,
message: error.message
}, error);
}
});
}
__name(getDefinitionsEndpoint, "getDefinitionsEndpoint");
// src/siopv2-rp-api-server.ts
var import_ssi_sdk2 = require("@sphereon/ssi-sdk.core");
var import_ssi_express_support3 = require("@sphereon/ssi-express-support");
var import_express = __toESM(require("express"), 1);
var import_fs = __toESM(require("fs"), 1);
var import_path = __toESM(require("path"), 1);
var import_url = require("url");
var import_swagger_ui_express = __toESM(require("swagger-ui-express"), 1);
var __filename2 = (0, import_url.fileURLToPath)(importMetaUrl);
var __dirname = import_path.default.dirname(__filename2);
var SIOPv2RPApiServer = class {
static {
__name(this, "SIOPv2RPApiServer");
}
_express;
_router;
_agent;
_opts;
_basePath;
OID4VP_OPENAPI_FILE = import_path.default.join(__dirname, "..", "oid4vp-openapi.yml");
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",
"webappGetDefinitions"
]
});
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_sdk2.agentContext)(agent);
const features = opts?.enableFeatures ?? [
"rp-status",
"siop"
];
console.log(`SIOPv2 API enabled, with features: ${JSON.stringify(features)}}`);
if (features.includes("rp-status")) {
createAuthRequestUniversalOID4VPEndpoint(this._router, context, opts?.endpointOpts?.webappCreateAuthRequest);
authStatusUniversalOID4VPEndpoint(this._router, context, opts?.endpointOpts?.webappAuthStatus);
removeAuthRequestStateUniversalOID4VPEndpoint(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();
}
/**
* Sets up Swagger UI for API documentation.
* Spec source priority:
* 1. OID4VP_OPENAPI_SPEC env var (URL or file path)
* 2. Bundled universal-OID4VP-openapi.yaml file
* 3. Fallback to SwaggerHub URL
*/
setupSwaggerUi() {
const envSpec = process.env.OID4VP_OPENAPI_SPEC;
const apiDocsPath = "/api-docs";
const specPath = "/api-docs/spec.yaml";
const fullApiDocsPath = `${this._basePath}${apiDocsPath}`;
const fullSpecPath = `${this._basePath}${specPath}`;
let cachedSpec = null;
const isRemoteUrl = envSpec?.startsWith("http://") || envSpec?.startsWith("https://");
const isEnvLocalFile = envSpec && !isRemoteUrl;
const resolvedEnvFile = isEnvLocalFile && !import_path.default.isAbsolute(envSpec) ? import_path.default.join(__dirname, "..", envSpec) : envSpec;
const hasBundledFile = import_fs.default.existsSync(this.OID4VP_OPENAPI_FILE);
const localFile = isEnvLocalFile ? resolvedEnvFile : hasBundledFile ? this.OID4VP_OPENAPI_FILE : void 0;
const isLocalFile = !!localFile;
if (isEnvLocalFile && !import_fs.default.existsSync(resolvedEnvFile)) {
console.log(`[OID4VP] OpenAPI spec file not found at ${resolvedEnvFile}. Swagger UI disabled.`);
return;
}
const specSource = isRemoteUrl ? envSpec : isLocalFile ? localFile : this.OID4VP_SWAGGER_URL;
console.log(`[OID4VP] API docs: ${fullApiDocsPath} (spec: ${specSource})`);
const getSpec = /* @__PURE__ */ __name(async (req) => {
if (cachedSpec) {
return cachedSpec;
}
if (isLocalFile && localFile) {
const content = import_fs.default.readFileSync(localFile, "utf-8");
try {
cachedSpec = JSON.parse(content);
} catch {
cachedSpec = content;
}
} else {
const url = isRemoteUrl ? envSpec : this.OID4VP_SWAGGER_URL;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const text = await response.text();
try {
cachedSpec = JSON.parse(text);
} catch {
cachedSpec = text;
}
}
if (typeof cachedSpec === "object" && cachedSpec !== null && req) {
cachedSpec.servers = [
{
url: `${req.protocol}://${req.get("host")}${this._basePath}`,
description: "This server"
}
];
}
return cachedSpec;
}, "getSpec");
getSpec().catch((err) => console.log(`[OID4VP] Spec pre-fetch failed: ${err.message}`));
this._router.get(specPath, async (req, res) => {
try {
const spec = await getSpec(req);
if (typeof spec === "string") {
res.type("text/yaml").send(spec);
} else {
res.json(spec);
}
} catch (err) {
console.error(`[OID4VP] Spec fetch error: ${err.message}`);
res.status(502).json({
error: "Failed to load OpenAPI spec",
details: err.message
});
}
});
this._router.use(apiDocsPath, import_swagger_ui_express.default.serve, import_swagger_ui_express.default.setup(void 0, {
swaggerOptions: {
url: fullSpecPath
}
}));
}
get express() {
return this._express;
}
get router() {
return this._router;
}
get agent() {
return this._agent;
}
get opts() {
return this._opts;
}
};
//# sourceMappingURL=index.cjs.map