@sphereon/oid4vci-issuer
Version:
OpenID 4 Verifiable Credential Issuance issuer REST endpoints
1,330 lines (1,319 loc) • 74.6 kB
JavaScript
"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);
// lib/index.ts
var index_exports = {};
__export(index_exports, {
AuthorizationServerMetadataBuilder: () => AuthorizationServerMetadataBuilder,
CredentialOfferStateBuilder: () => CredentialOfferStateBuilder,
CredentialSupportedBuilderV1_13: () => CredentialSupportedBuilderV1_13,
DisplayBuilder: () => DisplayBuilder,
IssuerMetadataBuilderV1_13: () => IssuerMetadataBuilderV1_13,
LOG: () => LOG,
LookupStateManager: () => LookupStateManager,
MemoryStates: () => MemoryStates,
VcIssuer: () => VcIssuer,
VcIssuerBuilder: () => VcIssuerBuilder,
assertValidAccessTokenRequest: () => assertValidAccessTokenRequest,
assertValidPinNumber: () => assertValidPinNumber,
createAccessTokenResponse: () => createAccessTokenResponse,
createCredentialOfferObject: () => createCredentialOfferObject,
createCredentialOfferObjectv1_0_11: () => createCredentialOfferObjectv1_0_11,
createCredentialOfferURI: () => createCredentialOfferURI,
createCredentialOfferURIFromObject: () => createCredentialOfferURIFromObject,
createCredentialOfferURIv1_0_11: () => createCredentialOfferURIv1_0_11,
generateAccessToken: () => generateAccessToken,
isPreAuthorizedCodeExpired: () => isPreAuthorizedCodeExpired,
isValidGrant: () => isValidGrant,
lookupStateManagerMultiGet: () => lookupStateManagerMultiGet,
lookupStateManagerMultiGetAsserted: () => lookupStateManagerMultiGetAsserted,
oidcAccessTokenVerifyCallback: () => oidcAccessTokenVerifyCallback
});
module.exports = __toCommonJS(index_exports);
var import_oid4vci_common7 = require("@sphereon/oid4vci-common");
// lib/builder/CredentialSupportedBuilderV1_13.ts
var import_oid4vci_common = require("@sphereon/oid4vci-common");
var CredentialSupportedBuilderV1_13 = class {
static {
__name(this, "CredentialSupportedBuilderV1_13");
}
format;
scope;
credentialName;
credentialDefinition;
cryptographicBindingMethodsSupported;
credentialSigningAlgValuesSupported;
proofTypesSupported;
display;
credentialSubject;
withFormat(credentialFormat) {
this.format = credentialFormat;
return this;
}
withCredentialName(credentialName) {
this.credentialName = credentialName;
return this;
}
withCredentialDefinition(credentialDefinition) {
if (!credentialDefinition.type) {
throw new Error("credentialDefinition should contain a type array");
}
this.credentialDefinition = credentialDefinition;
return this;
}
withScope(scope) {
this.scope = scope;
return this;
}
addCryptographicBindingMethod(method) {
if (!Array.isArray(method)) {
this.cryptographicBindingMethodsSupported = this.cryptographicBindingMethodsSupported ? [
...this.cryptographicBindingMethodsSupported,
method
] : [
method
];
} else {
this.cryptographicBindingMethodsSupported = this.cryptographicBindingMethodsSupported ? [
...this.cryptographicBindingMethodsSupported,
...method
] : method;
}
return this;
}
withCryptographicBindingMethod(method) {
this.cryptographicBindingMethodsSupported = Array.isArray(method) ? method : [
method
];
return this;
}
addCredentialSigningAlgValuesSupported(algValues) {
if (!Array.isArray(algValues)) {
this.credentialSigningAlgValuesSupported = this.credentialSigningAlgValuesSupported ? [
...this.credentialSigningAlgValuesSupported,
algValues
] : [
algValues
];
} else {
this.credentialSigningAlgValuesSupported = this.credentialSigningAlgValuesSupported ? [
...this.credentialSigningAlgValuesSupported,
...algValues
] : algValues;
}
return this;
}
withCredentialSigningAlgValuesSupported(algValues) {
this.credentialSigningAlgValuesSupported = Array.isArray(algValues) ? algValues : [
algValues
];
return this;
}
addProofTypesSupported(keyProofType, proofType) {
if (!this.proofTypesSupported) {
this.proofTypesSupported = {};
}
this.proofTypesSupported[keyProofType] = proofType;
return this;
}
withProofTypesSupported(proofTypesSupported) {
this.proofTypesSupported = proofTypesSupported;
return this;
}
addCredentialSupportedDisplay(credentialDisplay) {
if (!Array.isArray(credentialDisplay)) {
this.display = this.display ? [
...this.display,
credentialDisplay
] : [
credentialDisplay
];
} else {
this.display = this.display ? [
...this.display,
...credentialDisplay
] : credentialDisplay;
}
return this;
}
withCredentialSupportedDisplay(credentialDisplay) {
this.display = Array.isArray(credentialDisplay) ? credentialDisplay : [
credentialDisplay
];
return this;
}
withCredentialSubject(credentialSubject) {
this.credentialSubject = credentialSubject;
return this;
}
addCredentialSubjectPropertyDisplay(subjectProperty, issuerCredentialSubjectDisplay) {
if (!this.credentialSubject) {
this.credentialSubject = {};
}
this.credentialSubject[subjectProperty] = issuerCredentialSubjectDisplay;
return this;
}
build() {
if (!this.format) {
throw new Error(import_oid4vci_common.TokenErrorResponse.invalid_request);
}
const credentialSupported = {
format: this.format
};
if (!this.credentialDefinition) {
throw new Error("credentialDefinition is required");
}
credentialSupported.credential_definition = this.credentialDefinition;
if (this.scope) {
credentialSupported.scope = this.scope;
}
if (!this.credentialName) {
throw new Error("A unique credential name is required");
}
if (this.credentialSigningAlgValuesSupported) {
credentialSupported.credential_signing_alg_values_supported = this.credentialSigningAlgValuesSupported;
}
if (this.cryptographicBindingMethodsSupported) {
credentialSupported.cryptographic_binding_methods_supported = this.cryptographicBindingMethodsSupported;
}
if (this.display) {
credentialSupported.display = this.display;
}
const supportedConfiguration = {};
supportedConfiguration[this.credentialName] = credentialSupported;
return supportedConfiguration;
}
};
// lib/builder/VcIssuerBuilder.ts
var import_oid4vci_common5 = require("@sphereon/oid4vci-common");
// lib/VcIssuer.ts
var import_oid4vc_common3 = require("@sphereon/oid4vc-common");
var import_oid4vci_common4 = require("@sphereon/oid4vci-common");
var import_ssi_types = require("@sphereon/ssi-types");
var import_short_uuid = __toESM(require("short-uuid"), 1);
// lib/functions/CredentialOfferUtils.ts
var import_oid4vc_common = require("@sphereon/oid4vc-common");
var import_oid4vci_common2 = require("@sphereon/oid4vci-common");
function createCredentialOfferGrants(inputGrants) {
if (!inputGrants || Object.keys(inputGrants).length === 0) {
return void 0;
}
const grants = {};
if (inputGrants?.[import_oid4vci_common2.PRE_AUTH_GRANT_LITERAL]) {
const grant = {
...inputGrants[import_oid4vci_common2.PRE_AUTH_GRANT_LITERAL],
"pre-authorized_code": inputGrants[import_oid4vci_common2.PRE_AUTH_GRANT_LITERAL]["pre-authorized_code"] ?? (0, import_oid4vc_common.uuidv4)()
};
if (grant.tx_code && !grant.tx_code.length) {
grant.tx_code.length = 4;
}
grants[import_oid4vci_common2.PRE_AUTH_GRANT_LITERAL] = grant;
}
if (inputGrants?.authorization_code) {
grants.authorization_code = {
...inputGrants.authorization_code,
// TODO: it should be possible to create offer without issuer_state
// this is added to avoid breaking changes.
issuer_state: inputGrants.authorization_code.issuer_state ?? (0, import_oid4vc_common.uuidv4)()
};
}
return grants;
}
__name(createCredentialOfferGrants, "createCredentialOfferGrants");
function parseCredentialOfferSchemeAndBaseUri(scheme, baseUri, credentialIssuer) {
const newScheme = scheme?.replace("://", "") ?? (baseUri?.includes("://") ? baseUri.split("://")[0] : "openid-credential-offer");
let newBaseUri;
if (baseUri) {
newBaseUri = baseUri;
} else if (newScheme.startsWith("http")) {
if (credentialIssuer) {
newBaseUri = credentialIssuer;
if (!newBaseUri.startsWith(`${newScheme}://`)) {
throw Error(`scheme ${newScheme} is different from base uri ${newBaseUri}`);
}
} else {
throw Error(`A '${newScheme}' scheme requires a URI to be present as baseUri`);
}
} else {
newBaseUri = "";
}
newBaseUri = newBaseUri?.replace(`${newScheme}://`, "");
return {
scheme: newScheme,
baseUri: newBaseUri
};
}
__name(parseCredentialOfferSchemeAndBaseUri, "parseCredentialOfferSchemeAndBaseUri");
function createCredentialOfferObject(issuerMetadata, opts) {
if (!issuerMetadata && !opts?.credentialOffer && !opts?.credentialOfferUri) {
throw new Error("You have to provide issuerMetadata or credentialOffer object for creating a deeplink");
}
const grants = createCredentialOfferGrants(opts?.grants);
let credential_offer;
if (opts?.credentialOffer) {
credential_offer = {
...opts.credentialOffer
};
} else {
if (!issuerMetadata?.credential_configurations_supported) {
throw new Error("credential_configurations_supported is mandatory in the metadata");
}
credential_offer = {
credential_issuer: issuerMetadata.credential_issuer,
credential_configuration_ids: Object.keys(issuerMetadata.credential_configurations_supported)
};
}
if (grants) {
credential_offer.grants = grants;
}
if (opts?.client_id) {
credential_offer.client_id = opts.client_id;
}
return {
credential_offer,
credential_offer_uri: opts?.credentialOfferUri
};
}
__name(createCredentialOfferObject, "createCredentialOfferObject");
function createCredentialOfferObjectv1_0_11(issuerMetadata, opts) {
if (!issuerMetadata && !opts?.credentialOffer && !opts?.credentialOfferUri) {
throw new Error("You have to provide issuerMetadata or credentialOffer object for creating a deeplink");
}
const grants = createCredentialOfferGrants(opts?.grants);
if (grants?.[import_oid4vci_common2.PRE_AUTH_GRANT_LITERAL]?.tx_code) {
const { tx_code, ...rest } = grants[import_oid4vci_common2.PRE_AUTH_GRANT_LITERAL];
grants[import_oid4vci_common2.PRE_AUTH_GRANT_LITERAL] = {
user_pin_required: true,
...rest
};
}
let credential_offer;
if (opts?.credentialOffer) {
credential_offer = {
...opts.credentialOffer,
credentials: opts.credentialOffer?.credentials ?? issuerMetadata?.credentials_supported.map((s) => s.id).filter((i) => i !== void 0)
};
} else {
if (!issuerMetadata) {
throw new Error("Issuer metadata is required when no credential offer is provided");
}
credential_offer = {
credential_issuer: issuerMetadata.credential_issuer,
credentials: issuerMetadata?.credentials_supported.map((s) => s.id).filter((i) => i !== void 0)
};
}
return {
credential_offer,
credential_offer_uri: opts?.credentialOfferUri
};
}
__name(createCredentialOfferObjectv1_0_11, "createCredentialOfferObjectv1_0_11");
function createCredentialOfferURIFromObject(credentialOffer, offerMode, opts) {
const { scheme, baseUri } = parseCredentialOfferSchemeAndBaseUri(opts?.scheme, opts?.baseUri, credentialOffer.credential_offer?.credential_issuer);
if (offerMode === "REFERENCE") {
if (!credentialOffer.credential_offer_uri) {
throw Error(`credential_offer_uri must be set for offerMode ${offerMode}`);
}
if (credentialOffer.credential_offer_uri.includes("credential_offer_uri=")) {
return credentialOffer.credential_offer_uri;
}
return `${scheme}://${baseUri}?credential_offer_uri=${encodeURIComponent(credentialOffer.credential_offer_uri)}`;
} else if (offerMode === "VALUE") {
return `${scheme}://${baseUri}?credential_offer=${encodeURIComponent(JSON.stringify(credentialOffer.credential_offer))}`;
}
throw Error(`unsupported offerMode ${offerMode}`);
}
__name(createCredentialOfferURIFromObject, "createCredentialOfferURIFromObject");
function createCredentialOfferURI(offerMode, issuerMetadata, opts) {
const credentialOffer = createCredentialOfferObject(issuerMetadata, opts);
return createCredentialOfferURIFromObject(credentialOffer, offerMode, opts);
}
__name(createCredentialOfferURI, "createCredentialOfferURI");
function createCredentialOfferURIv1_0_11(offerMode, issuerMetadata, opts) {
const credentialOffer = createCredentialOfferObjectv1_0_11(issuerMetadata, opts);
return createCredentialOfferURIFromObject(credentialOffer, offerMode, opts);
}
__name(createCredentialOfferURIv1_0_11, "createCredentialOfferURIv1_0_11");
var isPreAuthorizedCodeExpired = /* @__PURE__ */ __name((state, expirationDurationInSeconds) => {
const now = +/* @__PURE__ */ new Date();
const expirationTime = state.createdAt + expirationDurationInSeconds * 1e3;
return now >= expirationTime;
}, "isPreAuthorizedCodeExpired");
var assertValidPinNumber = /* @__PURE__ */ __name((pin, pinLength) => {
if (pin && !RegExp(`[\\d\\D]{${pinLength ?? 6}}`).test(pin)) {
throw Error(`${import_oid4vci_common2.PIN_NOT_MATCH_ERROR}`);
}
}, "assertValidPinNumber");
// lib/functions/ASOidcClient.ts
var import_oid4vc_common2 = require("@sphereon/oid4vc-common");
var import_ssi_express_support = require("@sphereon/ssi-express-support");
function oidcAccessTokenVerifyCallback(opts) {
const clientMetadata = opts.clientMetadata ?? {
client_id: opts.credentialIssuer
};
return async (args) => {
const oidcIssuer = await (0, import_ssi_express_support.oidcDiscoverIssuer)({
issuerUrl: opts.authorizationServer
});
const oidcClient = await (0, import_ssi_express_support.oidcGetClient)(oidcIssuer.issuer, clientMetadata);
const introspection = await oidcClient.introspect(args.jwt);
if (!introspection.active) {
return Promise.reject(Error("Access token is not active or invalid"));
}
const jwt = {
header: (0, import_oid4vc_common2.decodeProtectedHeader)(args.jwt),
payload: (0, import_oid4vc_common2.decodeJwt)(args.jwt)
};
return {
jwt,
alg: jwt.header.alg,
...jwt.header.jwk && {
jwk: jwt.header.jwk
},
...jwt.header.x5c && {
x5c: jwt.header.x5c
},
...jwt.header.kid && {
kid: jwt.header.kid
}
};
};
}
__name(oidcAccessTokenVerifyCallback, "oidcAccessTokenVerifyCallback");
// lib/state-manager/MemoryStates.ts
var import_oid4vci_common3 = require("@sphereon/oid4vci-common");
var MemoryStates = class {
static {
__name(this, "MemoryStates");
}
expiresInMS;
states;
cleanupIntervalId;
constructor(opts) {
this.expiresInMS = opts?.expiresInSec !== void 0 ? opts?.expiresInSec * 1e3 : 18e4;
this.states = /* @__PURE__ */ new Map();
}
async clearAll() {
this.states.clear();
}
async clearExpired(timestamp) {
const states = Array.from(this.states.entries());
const ts = timestamp ?? +/* @__PURE__ */ new Date();
for (const [id, state] of states) {
if (state.expiresAt && state.expiresAt < ts) {
this.states.delete(id);
} else if (!state.expiresAt) {
if (state.createdAt + this.expiresInMS < ts) {
this.states.delete(id);
}
}
}
}
async delete(id) {
if (!id) {
throw Error("No id supplied");
}
return this.states.delete(id);
}
async getAsserted(id) {
if (!id) {
throw Error("No id supplied");
}
let result;
if (await this.has(id)) {
result = await this.get(id);
}
if (!result) {
throw new Error(import_oid4vci_common3.STATE_MISSING_ERROR + ` (${id})`);
}
return result;
}
async get(id) {
return this.states.get(id);
}
async has(id) {
if (!id) {
throw Error("No id supplied");
}
return this.states.has(id);
}
async set(id, stateValue) {
if (!id) {
throw Error("No id supplied");
}
this.states.set(id, stateValue);
}
async startCleanupRoutine(timeout) {
if (!this.cleanupIntervalId) {
this.cleanupIntervalId = setInterval(() => this.clearExpired(), timeout ?? 3e4);
}
}
async stopCleanupRoutine() {
if (this.cleanupIntervalId) {
clearInterval(this.cleanupIntervalId);
}
}
};
// lib/state-manager/LookupStateManager.ts
async function lookupStateManagerMultiGetAsserted(args) {
const value = await lookupStateManagerMultiGet(args);
if (value) {
return value;
}
return Promise.reject(Error(`no value found for id ${args.id}`));
}
__name(lookupStateManagerMultiGetAsserted, "lookupStateManagerMultiGetAsserted");
async function lookupStateManagerMultiGet({ id, lookups, keyValueMapper, valueStateManager }) {
for (const lookup of lookups) {
try {
const value = await new LookupStateManager(keyValueMapper, valueStateManager, lookup).get(id);
if (value) {
return value;
}
} catch (e) {
}
}
return valueStateManager.get(id);
}
__name(lookupStateManagerMultiGet, "lookupStateManagerMultiGet");
var LookupStateManager = class {
static {
__name(this, "LookupStateManager");
}
keyValueMapper;
valueStateManager;
lookup;
constructor(keyValueMapper, valueStateManager, lookup) {
this.keyValueMapper = keyValueMapper;
this.valueStateManager = valueStateManager;
this.lookup = lookup;
}
startCleanupRoutine(timeout) {
this.keyValueMapper.startCleanupRoutine(timeout);
return this.valueStateManager.startCleanupRoutine(timeout);
}
stopCleanupRoutine() {
this.keyValueMapper.stopCleanupRoutine();
return this.valueStateManager.stopCleanupRoutine();
}
async clearAll() {
this.keyValueMapper.clearAll();
this.valueStateManager.clearAll();
}
async clearExpired(timestamp) {
this.keyValueMapper.clearExpired(timestamp);
this.valueStateManager.clearExpired(timestamp);
}
async assertedValueId(key) {
const prop = this.lookup;
const valueId = await this.keyValueMapper.getAsserted(key).then((keyState) => keyState && prop in keyState ? keyState[prop] : void 0);
if (typeof valueId !== "string") {
throw Error("no value id could be derived for key" + key);
}
return valueId;
}
async valueId(key) {
const prop = this.lookup;
return await this.keyValueMapper.get(key).then((keyState) => keyState && prop in keyState ? keyState[prop] : void 0);
}
async delete(id) {
return await this.assertedValueId(id).then(async (value) => {
await this.keyValueMapper.delete(id);
return await this.valueStateManager.delete(value);
});
}
async get(id) {
return this.valueId(id).then((value) => value ? this.valueStateManager.get(value) : void 0);
}
async has(id) {
return this.valueId(id).then((value) => value ? this.valueStateManager.has(value) : false);
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async set(_id, _stateValue) {
throw Error(`Please use the setMappedMethod that accepts both and id, value and object`);
}
async setMapped(valueKey, keyObject, stateValue) {
const keys = keyObject;
if (!(this.lookup in keys) || !keys[this.lookup]) {
return Promise.reject(new Error(`keyValue ${keyObject} does not contain the lookup property ${this.lookup}`));
}
const key = keys[this.lookup];
await this.keyValueMapper.set(key, keyObject);
await this.valueStateManager.set(valueKey, stateValue);
}
async getAsserted(id) {
return this.assertedValueId(id).then((value) => this.valueStateManager.getAsserted(value));
}
};
// lib/state-manager/CredentialOfferStateBuilder.ts
var CredentialOfferStateBuilder = class {
static {
__name(this, "CredentialOfferStateBuilder");
}
credentialOfferState;
constructor() {
this.credentialOfferState = {};
}
credentialOffer(credentialOffer) {
this.credentialOfferState.credentialOffer = credentialOffer;
return this;
}
createdAt(timestamp) {
this.credentialOfferState.createdAt = timestamp;
return this;
}
build() {
if (!this.credentialOfferState.createdAt) {
this.credentialOfferState.createdAt = +/* @__PURE__ */ new Date();
}
if (!this.credentialOfferState.credentialOffer) {
throw new Error("Not all properties are present to build an IssuerState object");
}
return this.credentialOfferState;
}
};
// lib/VcIssuer.ts
var shortUUID = (0, import_short_uuid.default)();
var VcIssuer = class {
static {
__name(this, "VcIssuer");
}
_issuerMetadata;
_authorizationServerMetadata;
_defaultCredentialOfferBaseUri;
_credentialSignerCallback;
_jwtVerifyCallback;
_credentialDataSupplier;
_credentialOfferSessions;
_cNonces;
_uris;
_cNonceExpiresIn;
_asClientOpts;
constructor(issuerMetadata, authorizationServerMetadata, args) {
this._issuerMetadata = issuerMetadata;
this._authorizationServerMetadata = authorizationServerMetadata;
this._defaultCredentialOfferBaseUri = args.defaultCredentialOfferBaseUri;
this._credentialOfferSessions = args.credentialOfferSessions ?? new MemoryStates();
this._uris = args.uris ?? new MemoryStates();
this._cNonces = args.cNonces;
this._credentialSignerCallback = args?.credentialSignerCallback;
this._jwtVerifyCallback = args?.jwtVerifyCallback;
this._credentialDataSupplier = args?.credentialDataSupplier;
this._cNonceExpiresIn = args?.cNonceExpiresIn ?? (process.env.C_NONCE_EXPIRES_IN ? parseInt(process.env.C_NONCE_EXPIRES_IN) : 300);
this._asClientOpts = args?.asClientOpts;
}
async getCredentialOfferSessionById(id, lookups = [
"preAuthorizedCode",
"issuerState",
"correlationId"
]) {
if (Array.isArray(lookups) && lookups.length > 0) {
if (!this.uris) {
return Promise.reject(Error("Cannot lookup credential offer by id if URI state manager is not set"));
}
return lookupStateManagerMultiGetAsserted({
id,
keyValueMapper: this._uris,
valueStateManager: this._credentialOfferSessions,
lookups: [
"preAuthorizedCode",
"issuerState",
"correlationId"
]
});
}
const session = await this._credentialOfferSessions.get(id);
if (!session) {
return Promise.reject(Error(`No session found for id ${id}`));
}
return session;
}
async deleteCredentialOfferSessionById(id, lookups = [
"preAuthorizedCode",
"issuerState"
]) {
const session = await this.getCredentialOfferSessionById(id, lookups);
if (session) {
if (session.preAuthorizedCode && await this._credentialOfferSessions.has(session.preAuthorizedCode)) {
await this._credentialOfferSessions.delete(session.preAuthorizedCode);
}
if (session.issuerState && await this._credentialOfferSessions.has(session.issuerState)) {
await this._credentialOfferSessions.delete(session.issuerState);
}
}
return session;
}
async processNotification({ preAuthorizedCode, issuerState, notification }) {
const sessionId = preAuthorizedCode ?? issuerState;
const session = sessionId ? await this.getCredentialOfferSessionById(sessionId) : void 0;
if (!session || !sessionId) {
LOG.error(`No session or session id found ${sessionId}`);
return Error("invalid_notification_request");
}
if (notification.notification_id !== session.notification_id) {
LOG.error(`Notification id ${notification.notification_id} not found in session. session notification id ${session.notification_id}`);
return Error("invalid_notification_id");
} else if (session.notification) {
LOG.info(`Overwriting existing notification, as a new notification came in ${session.notification_id}`);
}
await this.updateSession({
preAuthorizedCode,
issuerState,
notification
});
LOG.info(`Processed notification ${notification} for ${session.notification_id}`);
return session;
}
async createCredentialOfferURI(opts) {
const { offerMode = "VALUE", correlationId = shortUUID.generate(), credential_configuration_ids, statusListOpts, credentialOfferUri, redirectUri } = opts;
if (offerMode === "REFERENCE" && !credentialOfferUri) {
return Promise.reject(Error("credentialOfferUri must be supplied for offerMode REFERENCE!"));
}
const grants = opts.grants ? {
...opts.grants
} : {};
if (opts.pinLength !== void 0) {
if (grants[import_oid4vci_common4.PRE_AUTH_GRANT_LITERAL]) {
grants[import_oid4vci_common4.PRE_AUTH_GRANT_LITERAL].tx_code = {
...grants[import_oid4vci_common4.PRE_AUTH_GRANT_LITERAL].tx_code,
length: grants[import_oid4vci_common4.PRE_AUTH_GRANT_LITERAL].tx_code?.length ?? opts.pinLength
};
}
}
if (grants[import_oid4vci_common4.PRE_AUTH_GRANT_LITERAL]?.tx_code && !grants[import_oid4vci_common4.PRE_AUTH_GRANT_LITERAL]?.tx_code?.length) {
grants[import_oid4vci_common4.PRE_AUTH_GRANT_LITERAL].tx_code.length = 4;
}
const baseUri = opts?.baseUri ?? this.defaultCredentialOfferBaseUri;
const credentialOfferObject = createCredentialOfferObject(this._issuerMetadata, {
...opts,
grants,
credentialOffer: credential_configuration_ids ? {
credential_issuer: this._issuerMetadata.credential_issuer,
credential_configuration_ids
} : void 0
});
const preAuthGrant = credentialOfferObject.credential_offer.grants?.[import_oid4vci_common4.PRE_AUTH_GRANT_LITERAL];
const authGrant = credentialOfferObject.credential_offer.grants?.authorization_code;
const preAuthorizedCode = preAuthGrant?.["pre-authorized_code"];
const issuerState = authGrant?.issuer_state;
const txCode = preAuthGrant?.tx_code;
let userPin;
if (preAuthGrant?.tx_code) {
const pinLength = preAuthGrant.tx_code.length ?? 4;
userPin = ("" + Math.round((Math.pow(10, pinLength) - 1) * Math.random())).padStart(pinLength, "0");
assertValidPinNumber(userPin, pinLength);
}
const createdAt = +/* @__PURE__ */ new Date();
const lastUpdatedAt = createdAt;
const expirationInMs = (opts.sessionLifeTimeInSec ?? 10 * 60) * 1e3;
const expiresAt = createdAt + Math.abs(expirationInMs);
if (offerMode === "REFERENCE") {
if (!this.uris) {
throw Error("No URI state manager set, whilst apparently credential offer by reference is being used");
}
const offerUri = opts.credentialOfferUri?.replace(":id", correlationId);
if (!offerUri) {
return Promise.reject(Error("credentialOfferUri must be supplied for offerMode REFERENCE!"));
}
credentialOfferObject.credential_offer_uri = offerUri;
await this.uris.set(correlationId, {
uri: offerUri,
createdAt,
expiresAt,
preAuthorizedCode,
issuerState,
correlationId
});
}
const credentialOffer = await (0, import_oid4vci_common4.toUniformCredentialOfferRequest)({
credential_offer: credentialOfferObject.credential_offer,
credential_offer_uri: credentialOfferObject.credential_offer_uri
}, {
version: import_oid4vci_common4.OpenId4VCIVersion.VER_1_0_13,
resolve: false
});
const status = import_oid4vci_common4.IssueStatus.OFFER_CREATED;
const session = {
redirectUri,
preAuthorizedCode,
issuerState,
createdAt,
lastUpdatedAt,
expiresAt,
status,
notification_id: (0, import_oid4vc_common3.uuidv4)(),
...opts.client_id && {
clientId: opts.client_id
},
...userPin && {
txCode: userPin
},
...opts.credentialDataSupplierInput && {
credentialDataSupplierInput: opts.credentialDataSupplierInput
},
credentialOffer,
statusLists: statusListOpts
};
const uri = createCredentialOfferURIFromObject(credentialOffer, offerMode, {
...opts,
baseUri
});
if (preAuthorizedCode) {
const lookupManager = new LookupStateManager(this.uris, this._credentialOfferSessions, "correlationId");
await lookupManager.setMapped(preAuthorizedCode, {
preAuthorizedCode,
uri,
createdAt,
expiresAt,
correlationId,
issuerState
}, session);
}
if (issuerState) {
const lookupManager = new LookupStateManager(this.uris, this._credentialOfferSessions, "correlationId");
await lookupManager.setMapped(issuerState, {
preAuthorizedCode,
uri,
createdAt,
expiresAt,
correlationId,
issuerState
}, session);
}
let qrCodeDataUri;
if (opts.qrCodeOpts) {
const { AwesomeQR } = await import("awesome-qr");
const qrCode = new AwesomeQR({
...opts.qrCodeOpts,
text: uri
});
qrCodeDataUri = `data:image/png;base64,${(await qrCode.draw()).toString("base64")}`;
}
const credentialOfferResult = {
session,
uri,
qrCodeDataUri,
correlationId,
txCode,
...userPin !== void 0 && {
userPin,
pinLength: userPin?.length ?? 0
}
};
import_oid4vci_common4.EVENTS.emit(import_oid4vci_common4.CredentialOfferEventNames.OID4VCI_OFFER_CREATED, {
eventName: import_oid4vci_common4.CredentialOfferEventNames.OID4VCI_OFFER_CREATED,
id: correlationId,
data: credentialOfferResult,
initiator: "<Unknown>",
initiatorType: import_ssi_types.InitiatorType.EXTERNAL,
system: import_ssi_types.System.OID4VCI,
issuer: this.issuerMetadata.credential_issuer,
subsystem: import_ssi_types.SubSystem.API,
createdAt,
expiresAt
});
return credentialOfferResult;
}
/**
* issueCredentialFromIssueRequest
* @param opts issuerRequestParams
* - issueCredentialsRequest the credential request
* - issuerState the state of the issuer
* - jwtVerifyCallback callback that verifies the Proof of Possession JWT
* - issuerCallback callback to issue a Verifiable Credential
* - cNonce an existing c_nonce
*/
async issueCredential(opts) {
const credentialRequest = opts.credentialRequest;
let preAuthorizedCode;
let issuerState;
try {
if (!("credential_identifier" in credentialRequest) && !credentialRequest.format) {
throw new Error("credential request should either have a credential_identifier or format and type");
}
if (credentialRequest.format && !this.isMetadataSupportCredentialRequestFormat(credentialRequest.format)) {
throw new Error(import_oid4vci_common4.TokenErrorResponse.invalid_request);
}
const validated = await this.validateCredentialRequestProof({
...opts,
tokenExpiresIn: opts.tokenExpiresIn ?? 180
});
preAuthorizedCode = validated.preAuthorizedCode;
issuerState = validated.issuerState;
const { preAuthSession, authSession, cNonceState, jwtVerifyResult } = validated;
const did = jwtVerifyResult.did;
const jwk = jwtVerifyResult.jwk;
const kid = jwtVerifyResult.kid;
const newcNonce = opts.newCNonce ? opts.newCNonce : (0, import_oid4vc_common3.uuidv4)();
const newcNonceState = {
cNonce: newcNonce,
createdAt: +/* @__PURE__ */ new Date(),
...authSession?.issuerState && {
issuerState: authSession.issuerState
},
...preAuthSession && {
preAuthorizedCode: preAuthSession.preAuthorizedCode
}
};
await this.cNonces.set(newcNonce, newcNonceState);
if (!opts.credential && this._credentialDataSupplier === void 0 && opts.credentialDataSupplier === void 0) {
throw Error(`Either a credential needs to be supplied or a credentialDataSupplier`);
}
let credential;
let format = credentialRequest.format;
let signerCallback = opts.credentialSignerCallback;
const session = preAuthorizedCode && preAuthSession ? preAuthSession : authSession;
if (opts.credential) {
credential = opts.credential;
} else {
const credentialDataSupplier = typeof opts.credentialDataSupplier === "function" ? opts.credentialDataSupplier : this._credentialDataSupplier;
if (typeof credentialDataSupplier !== "function") {
throw Error("Data supplier is mandatory if no credential is supplied");
}
if (!session) {
throw Error("Either a preAuth or Auth session is required, none found");
}
const credentialOffer = session.credentialOffer;
if (!credentialOffer) {
throw Error("Credential Offer missing");
}
const credentialDataSupplierInput = opts.credentialDataSupplierInput ?? session.credentialDataSupplierInput;
const result = await credentialDataSupplier({
...cNonceState ? {
...cNonceState
} : {
...authSession
},
credentialRequest: opts.credentialRequest,
credentialSupplierConfig: this._issuerMetadata.credential_supplier_config,
credentialOffer,
...credentialDataSupplierInput && {
credentialDataSupplierInput
}
});
credential = result.credential;
if (result.format) {
format = result.format;
}
if (typeof result.signCallback === "function") {
signerCallback = result.signCallback;
}
}
if (!credential) {
throw Error("A credential needs to be supplied at this point");
}
if (import_ssi_types.CredentialMapper.isSdJwtDecodedCredentialPayload(credential) && (kid || jwk) && !credential.cnf) {
if (kid) {
credential.cnf = {
kid
};
}
if (jwk) {
credential.cnf = {
jwk
};
}
} else if (did && !import_ssi_types.CredentialMapper.isSdJwtDecodedCredentialPayload(credential) && credential.credentialSubject !== void 0) {
const credentialSubjects = Array.isArray(credential.credentialSubject) ? credential.credentialSubject : [
credential.credentialSubject
];
credentialSubjects.map((subject) => {
if (!subject.id) {
subject.id = did;
}
return subject;
});
credential.credentialSubject = Array.isArray(credential.credentialSubject) ? credentialSubjects : credentialSubjects[0];
} else {
}
let issuer = void 0;
if (credential.iss) {
issuer = credential.iss;
} else if (credential.issuer) {
if (typeof credential.issuer === "string") {
issuer = credential.issuer;
} else if (typeof credential.issuer === "object" && "id" in credential.issuer && typeof credential.issuer.id === "string") {
issuer = credential.issuer.id;
}
}
const verifiableCredential = await this.issueCredentialImpl({
credentialRequest: opts.credentialRequest,
format,
credential,
jwtVerifyResult,
issuer,
...session && {
statusLists: session.statusLists
}
}, signerCallback);
if (!verifiableCredential) {
throw new Error(import_oid4vci_common4.CREDENTIAL_MISSING_ERROR);
}
if (cNonceState) {
await this.cNonces.delete(cNonceState.cNonce);
}
let notification_id;
if (preAuthorizedCode && preAuthSession) {
preAuthSession.lastUpdatedAt = +/* @__PURE__ */ new Date();
preAuthSession.status = import_oid4vci_common4.IssueStatus.CREDENTIAL_ISSUED;
notification_id = preAuthSession.notification_id;
await this._credentialOfferSessions.set(preAuthorizedCode, preAuthSession);
} else if (issuerState && authSession) {
authSession.lastUpdatedAt = +/* @__PURE__ */ new Date();
authSession.status = import_oid4vci_common4.IssueStatus.CREDENTIAL_ISSUED;
notification_id = authSession.notification_id;
await this._credentialOfferSessions.set(issuerState, authSession);
}
const response = {
credential: verifiableCredential,
// format: credentialRequest.format,
c_nonce: newcNonce,
c_nonce_expires_in: this._cNonceExpiresIn,
...notification_id && {
notification_id
}
};
const experimentalSubjectIssuance = opts.credentialRequest.credential_subject_issuance;
if (experimentalSubjectIssuance?.subject_proof_mode) {
if (experimentalSubjectIssuance.subject_proof_mode !== "proof_replace") {
throw Error("Only proof replace is supported currently");
}
response.transaction_id = authSession?.issuerState;
response.credential_subject_issuance = experimentalSubjectIssuance;
}
return response;
} catch (error) {
await this.updateSession({
preAuthorizedCode,
issuerState,
error
});
throw error;
}
}
async updateSession({ preAuthorizedCode, error, issuerState, notification }) {
let issueState = void 0;
if (error) {
issueState = import_oid4vci_common4.IssueStatus.ERROR;
} else if (notification) {
if (notification.event == "credential_accepted") {
issueState = import_oid4vci_common4.IssueStatus.NOTIFICATION_CREDENTIAL_ACCEPTED;
} else if (notification.event == "credential_deleted") {
issueState = import_oid4vci_common4.IssueStatus.NOTIFICATION_CREDENTIAL_DELETED;
} else if (notification.event == "credential_failure") {
issueState = import_oid4vci_common4.IssueStatus.NOTIFICATION_CREDENTIAL_FAILURE;
}
}
if (preAuthorizedCode) {
const preAuthSession = await this._credentialOfferSessions.get(preAuthorizedCode);
if (preAuthSession) {
preAuthSession.lastUpdatedAt = +/* @__PURE__ */ new Date();
if (issueState) {
preAuthSession.status = issueState;
}
if (error) {
preAuthSession.error = error instanceof Error ? error.message : error?.toString();
}
preAuthSession.notification_id;
if (notification) {
preAuthSession.notification = notification;
}
await this._credentialOfferSessions.set(preAuthorizedCode, preAuthSession);
}
}
if (issuerState) {
const authSession = await this._credentialOfferSessions.get(issuerState);
if (authSession) {
authSession.lastUpdatedAt = +/* @__PURE__ */ new Date();
if (issueState) {
authSession.status = issueState;
}
if (error) {
authSession.error = error instanceof Error ? error.message : error?.toString();
}
if (notification) {
authSession.notification = notification;
}
await this._credentialOfferSessions.set(issuerState, authSession);
}
}
}
/*
private async retrieveGrantsAndCredentialOfferSession(id: string): Promise<{
clientId?: string;
grants?: Grant,
session: CredentialOfferSession
}> {
const session: CredentialOfferSession | undefined = await this._credentialOfferSessions.getAsserted(id)
const clientId = session?.clientId
const grants = session?.credentialOffer?.credential_offer?.grants
if (!grants?.authorization_code?.issuer_state && !grants?.[PRE_AUTH_GRANT_LITERAL]?.[PRE_AUTH_CODE_LITERAL]) {
throw new Error(GRANTS_MUST_NOT_BE_UNDEFINED)
}
return { session, clientId, grants }
}*/
async validateCredentialRequestProof({ credentialRequest, jwtVerifyCallback, tokenExpiresIn }) {
let preAuthorizedCode;
let issuerState;
const supportedIssuanceFormats = [
"jwt_vc_json",
"jwt_vc_json-ld",
"vc+sd-jwt",
"ldp_vc",
"mso_mdoc"
];
try {
if (credentialRequest.format && !supportedIssuanceFormats.includes(credentialRequest.format)) {
throw Error(`Format ${credentialRequest.format} not supported yet`);
} else if (typeof this._jwtVerifyCallback !== "function" && typeof jwtVerifyCallback !== "function") {
throw new Error(import_oid4vci_common4.JWT_VERIFY_CONFIG_ERROR);
} else if (!credentialRequest.proof) {
throw Error("Proof of possession is required. No proof value present in credential request");
}
const jwtVerifyResult = jwtVerifyCallback ? await jwtVerifyCallback(credentialRequest.proof) : await this._jwtVerifyCallback(credentialRequest.proof);
const { didDocument, did, jwt } = jwtVerifyResult;
const { header, payload } = jwt;
const { iss, aud, iat, nonce } = payload;
const issuer_state = "issuer_state" in credentialRequest && credentialRequest.issuer_state ? credentialRequest.issuer_state : void 0;
if (!nonce && !issuer_state) {
throw Error("No nonce was found in the Proof of Possession");
}
let createdAt;
let cNonceState;
if (nonce) {
cNonceState = await this.cNonces.getAsserted(nonce);
preAuthorizedCode = cNonceState.preAuthorizedCode;
issuerState = cNonceState.issuerState;
createdAt = cNonceState.createdAt;
} else if (issuer_state) {
const session = await this._credentialOfferSessions.getAsserted(issuer_state);
issuerState = issuer_state;
createdAt = session.createdAt;
} else {
throw Error("No nonce or issuer_state was found in the Proof of Possession");
}
const alg = jwtVerifyResult.alg ?? header.alg;
const kid = jwtVerifyResult.kid ?? header.kid;
const jwk = jwtVerifyResult.jwk ?? header.jwk;
const x5c = jwtVerifyResult.x5c ?? header.x5c;
const typ = header.typ;
if (typ !== "openid4vci-proof+jwt") {
throw Error(import_oid4vci_common4.TYP_ERROR);
} else if (!alg) {
throw Error(import_oid4vci_common4.ALG_ERROR);
} else if (x5c && (kid || jwk)) {
throw Error(import_oid4vci_common4.KID_JWK_X5C_ERROR);
} else if (kid && !did) {
if (!jwk && !x5c) {
throw Error(import_oid4vci_common4.KID_DID_NO_DID_ERROR);
} else {
console.log(`KID present but no DID, using JWK or x5c`);
}
} else if (did && !didDocument) {
throw Error(import_oid4vci_common4.DID_NO_DIDDOC_ERROR);
}
const preAuthSession = preAuthorizedCode ? await this.credentialOfferSessions.get(preAuthorizedCode) : void 0;
const authSession = issuerState ? await this.credentialOfferSessions.get(issuerState) : void 0;
if (!preAuthSession && !authSession) {
throw Error("Either a pre-authorized code or issuer state needs to be present");
}
if (preAuthSession) {
if (!preAuthSession.preAuthorizedCode || preAuthSession.preAuthorizedCode !== preAuthorizedCode) {
throw Error("Invalid pre-authorized code");
}
preAuthSession.lastUpdatedAt = +/* @__PURE__ */ new Date();
preAuthSession.status = import_oid4vci_common4.IssueStatus.CREDENTIAL_REQUEST_RECEIVED;
await this._credentialOfferSessions.set(preAuthorizedCode, preAuthSession);
}
if (authSession) {
if (!authSession.issuerState || authSession.issuerState !== issuerState) {
throw Error("Invalid issuer state");
}
authSession.lastUpdatedAt = +/* @__PURE__ */ new Date();
authSession.status = import_oid4vci_common4.IssueStatus.CREDENTIAL_REQUEST_RECEIVED;
}
if (!iss && authSession?.credentialOffer.credential_offer?.grants?.authorization_code) {
throw new Error(import_oid4vci_common4.NO_ISS_IN_AUTHORIZATION_CODE_CONTEXT);
}
if (!aud || aud !== this._issuerMetadata.credential_issuer) {
throw new Error(import_oid4vci_common4.AUD_ERROR);
}
if (!iat) {
throw new Error(import_oid4vci_common4.IAT_ERROR);
} else if (iat > Math.round(createdAt / 1e3) + tokenExpiresIn) {
throw new Error(import_oid4vci_common4.IAT_ERROR);
}
return {
jwtVerifyResult,
preAuthorizedCode,
preAuthSession,
issuerState,
authSession,
cNonceState
};
} catch (error) {
await this.updateSession({
preAuthorizedCode,
issuerState,
error
});
throw error;
}
}
isMetadataSupportCredentialRequestFormat(requestFormat) {
if (!this._issuerMetadata.credential_configurations_supported) {
return false;
}
for (const credentialSupported of Object.values(this._issuerMetadata["credential_configurations_supported"])) {
if (!Array.isArray(requestFormat) && credentialSupported.format === requestFormat) {
return true;
} else if (Array.isArray(requestFormat)) {
for (const format of requestFormat) {
if (credentialSupported.format === format) {
return true;
}
}
}
}
return false;
}
async issueCredentialImpl(opts, issuerCallback) {
if (!opts.credential && !opts.credentialRequest || !this._credentialSignerCallback) {
throw new Error(import_oid4vci_common4.ISSUER_CONFIG_ERROR);
}
const credential = issuerCallback ? await issuerCallback(opts) : await this._credentialSignerCallback(opts);
import_oid4vci_common4.EVENTS.emit(import_oid4vci_common4.CredentialEventNames.OID4VCI_CREDENTIAL_ISSUED, {
eventName: import_oid4vci_common4.CredentialEventNames.OID4VCI_CREDENTIAL_ISSUED,
id: (0, import_oid4vc_common3.uuidv4)(),
data: credential,
// TODO: Format, request etc
initiator: opts.issuer ?? "<unknown>",
initiatorType: import_ssi_types.InitiatorType.EXTERNAL,
system: import_ssi_types.System.OID4VCI,
subsystem: import_ssi_types.SubSystem.VC_ISSUER
});
return credential;
}
get credentialSignerCallback() {
return this._credentialSignerCallback;
}
get jwtVerifyCallback() {
return this._jwtVerifyCallback;
}
get credentialDataSupplier() {
return this._credentialDataSupplier;
}
get uris() {
return this._uris;
}
get cNonceExpiresIn() {
return this._cNonceExpiresIn;
}
get credentialOfferSessions() {
return this._credentialOfferSessions;
}
get cNonces() {
return this._cNonces;
}
get defaultCredentialOfferBaseUri() {
return this._defaultCredentialOfferBaseUri;
}
get issuerMetadata() {
return this._issuerMetadata;
}
get authorizationServerMetadata() {
return this._authorizationServerMetadata;
}
get asClientOpts() {
return this._asClientOpts;
}
};
// lib/builder/VcIssuerBuilder.ts
var VcIssuerBuilder = class {
static {
__name(this, "VcIssuerBuilder");
}
issuerMetadataBuilder;
issuerMetadata = {};
authorizationServerMetadata = {};
asClientOpts;
txCode;
defaultCredentialOfferBaseUri;
userPinRequired;
cNonceExpiresIn;
credentialOfferStateManager;
credentialOfferURIManager;
cNonceStateManager;
credentialSignerCallback;
jwtVerifyCallback;
credentialDataSupplier;
withIssuerMetadata(issuerMetadata) {
if (!issuerMetadata.credential_configurations_supported) {
throw new Error("IssuerMetadata should be from type v1_0_13 or higher.");
}
this.issuerMetadata = issuerMetadata;
return this;
}
withASClientMetadata(clientMetadata) {
this.asClientOpts = clientMetadata;
return this;
}
withASClientMetadataParams({ client_id, client_secret, redirect_uris, response_types, ...other }) {
this.asClientOpts = {
...other,
client_id,
client_secret,
redirect_uris,
response_types
};
return this;
}
withAuthorizationMetadata(authorizationServerMetadata) {
this.authorizationServerMetadata = authorizationServerMetadata;
return this;
}
withIssuerMetadataBuilder(builder) {
this.issuerMetadataBuilder = builder;
return this;
}
withDefaultCredentialOfferBaseUri(baseUri) {
this.defaultCredentialOfferBaseUri = baseUri;
return this;
}
withCredentialIssuer(issuer) {
this.issuerMetadata.credential_issuer = issuer;
return this;
}
withAuthorizationServers(authorizationServers) {
this.issuerMetadata.authorization_servers = typeof authorizationServers === "string" ? [
authorizationServers
] : authorizationServers;
return this;
}
withCredentialE