Skip to content

Commit 4afb9e1

Browse files
committed
refactor(oid4vc): resolve security warnings, harden schema validation, and fix linting
Signed-off-by: Sagar Khole <sagar.khole@ayanworks.com>
1 parent 70f9446 commit 4afb9e1

2 files changed

Lines changed: 44 additions & 10 deletions

File tree

apps/oid4vc-issuance/libs/helpers/credential-sessions.builder.ts

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -539,17 +539,33 @@ function buildJwtVcJsonLdCredential(
539539
const vct = (templateRecord.attributes as any)?.vct;
540540
let typeName = '';
541541
if ('string' === typeof vct) {
542-
const lastSlash = vct.lastIndexOf('/');
543-
typeName = -1 !== lastSlash ? vct.substring(lastSlash + 1) : vct;
542+
const cleanVct = vct.replace(/\/+$/, '');
543+
const lastSlash = cleanVct.lastIndexOf('/');
544+
typeName = -1 !== lastSlash ? cleanVct.substring(lastSlash + 1) : cleanVct;
544545
} else {
545546
typeName = templateRecord.name.replace(/\s+/g, '');
546547
}
547548

548549
const templateContext = (templateRecord.attributes as any)?.context;
549-
const context = Array.isArray(templateContext) ? templateContext : ['https://www.w3.org/2018/credentials/v1'];
550+
const context = Array.isArray(templateContext) ? [...templateContext] : ['https://www.w3.org/2018/credentials/v1'];
551+
const requiredContextUrl = 'https://www.w3.org/2018/credentials/v1';
552+
const normalizeUrlForComparison = (value: unknown): string | null => {
553+
if ('string' !== typeof value) {
554+
return null;
555+
}
556+
try {
557+
return new URL(value).toString();
558+
} catch {
559+
return null;
560+
}
561+
};
562+
const normalizedRequiredContext = normalizeUrlForComparison(requiredContextUrl);
563+
const hasRequiredContext = context.some(
564+
(ctx) => null !== normalizedRequiredContext && normalizeUrlForComparison(ctx) === normalizedRequiredContext
565+
);
550566

551-
if (!context.includes('https://www.w3.org/2018/credentials/v1')) {
552-
context.unshift('https://www.w3.org/2018/credentials/v1');
567+
if (!hasRequiredContext) {
568+
context.unshift(requiredContextUrl);
553569
}
554570

555571
const wrappedPayload: Record<string, any> = {

apps/oid4vc-issuance/src/oid4vc-issuance.service.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,28 @@ export class Oid4vcIssuanceService {
9898
let internalSchemaId: string | undefined;
9999

100100
if ('schemaUrl' in template && template.schemaUrl) {
101-
const parts = template.schemaUrl.split('/');
102-
const potentialUuid = parts[parts.length - 1];
103-
if (uuidRegex.test(potentialUuid)) {
104-
internalSchemaId = potentialUuid;
101+
let isInternal = false;
102+
try {
103+
const schemaUrlObj = new URL(template.schemaUrl);
104+
const serverUrlStr = process.env.SCHEMA_FILE_SERVER_URL;
105+
if (serverUrlStr) {
106+
const serverUrlObj = new URL(serverUrlStr);
107+
if (schemaUrlObj.hostname === serverUrlObj.hostname) {
108+
isInternal = true;
109+
}
110+
}
111+
} catch (error) {
112+
if (process.env.SCHEMA_FILE_SERVER_URL && template.schemaUrl.startsWith(process.env.SCHEMA_FILE_SERVER_URL)) {
113+
isInternal = true;
114+
}
115+
}
116+
117+
if (isInternal) {
118+
const parts = template.schemaUrl.split('/');
119+
const potentialUuid = parts[parts.length - 1];
120+
if (uuidRegex.test(potentialUuid)) {
121+
internalSchemaId = potentialUuid;
122+
}
105123
}
106124
}
107125

@@ -113,7 +131,7 @@ export class Oid4vcIssuanceService {
113131
.toPromise();
114132
} catch (error) {
115133
this.logger.error(`Error fetching schema ${internalSchemaId} from ledger: ${error.message}`);
116-
return;
134+
throw new NotFoundException(`Schema with ID ${internalSchemaId} not found or unreachable: ${error.message}`);
117135
}
118136

119137
if (schemaResponse && schemaResponse.attributes) {

0 commit comments

Comments
 (0)