@cooperation/vc-storage 1.0.43 → 1.0.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,8 @@
1
- import { Ed25519VerificationKey2020 } from '@digitalbazaar/ed25519-verification-key-2020';
1
+ import { Ed25519VerificationKey2020 } from '@digitalcredentials/ed25519-verification-key-2020';
2
2
  import { v4 as uuidv4 } from 'uuid';
3
3
  import CryptoJS from 'crypto-js';
4
- import { employmentCredentialContext, volunteeringCredentialContext, performanceReviewCredentialContext } from './context.js';
4
+ import { employmentCredentialContext, volunteeringCredentialContext, performanceReviewCredentialContext, recommendationCredentialContexts, skillClaimCredentialContexts, } from './context.js';
5
5
  /**
6
- *
7
6
  * Utility function to generate a hashed ID for a credential.
8
7
  * Excludes the `id` field when hashing.
9
8
  * @param {object} credential - The credential object to hash.
@@ -124,54 +123,38 @@ export function generateUnsignedVC({ formData, issuerDid }) {
124
123
  return unsignedCredential;
125
124
  }
126
125
  /**
127
- * Generate an unsigned Recommendation Credential.
128
- * Uses the hash of the VC to set the `id` for consistency.
129
- * @param {object} params
130
- * @param {IVerifiableCredential} params.vc - The Verifiable Credential to base the recommendation on.
131
- * @param {RecommendationFormDataI} params.recommendation - The recommendation form data.
132
- * @param {string} params.issuerDid - The DID of the issuer.
133
- * @returns {IVerifiableCredential} The created unsigned Recommendation Credential.
134
- * @throws Will throw an error if the recommendation creation fails or if issuance date exceeds expiration date.
126
+ * Generate an unsigned Recommendation Credential (VC Data Model v2).
127
+ * Uses the target skill-claim VC id on credentialSubject.id.
135
128
  */
136
- export function generateUnsignedRecommendation({ vcId, recommendation, issuerDid, }) {
137
- const issuanceDate = new Date().toISOString();
138
- if (issuanceDate > recommendation.expirationDate)
139
- throw new Error('issuanceDate cannot be after expirationDate');
129
+ export function generateUnsignedRecommendation({ vcId, recommendation, issuerDid, evidence = [], }) {
140
130
  const unsignedRecommendation = {
141
- '@context': [
142
- 'https://www.w3.org/2018/credentials/v1',
143
- 'https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.3.json',
144
- {
145
- howKnow: 'https://schema.org/howKnow',
146
- recommendationText: 'https://schema.org/recommendationText',
147
- qualifications: 'https://schema.org/qualifications',
148
- explainAnswer: 'https://schema.org/explainAnswer',
149
- portfolio: 'https://schema.org/portfolio',
150
- },
151
- ],
152
- id: ``,
131
+ '@context': recommendationCredentialContexts,
132
+ id: `urn:uuid:${uuidv4()}`,
153
133
  type: ['VerifiableCredential', 'https://schema.org/RecommendationCredential'],
154
- issuer: {
155
- id: issuerDid,
156
- type: ['Profile'],
157
- },
158
- issuanceDate,
159
- expirationDate: recommendation.expirationDate,
134
+ issuer: { id: issuerDid, type: ['Profile'] },
135
+ validFrom: new Date().toISOString(),
160
136
  credentialSubject: {
161
137
  id: vcId,
162
138
  name: recommendation.fullName,
139
+ ...(recommendation.recipientName ? { recipientName: recommendation.recipientName } : {}),
163
140
  howKnow: recommendation.howKnow,
164
141
  recommendationText: recommendation.recommendationText,
165
- qualifications: recommendation.qualifications,
166
- explainAnswer: recommendation.explainAnswer,
167
- portfolio: recommendation.portfolio.map((item) => ({
168
- name: item.name,
169
- url: item.url,
170
- })),
142
+ ...(recommendation.qualifications ? { qualifications: recommendation.qualifications } : {}),
143
+ ...(recommendation.explainAnswer ? { explainAnswer: recommendation.explainAnswer } : {}),
144
+ ...(recommendation.portfolio?.length ? { portfolio: recommendation.portfolio } : {}),
145
+ ...(recommendation.skillsEndorsed?.length ? { skillsEndorsed: recommendation.skillsEndorsed } : {}),
171
146
  },
147
+ ...(evidence.length
148
+ ? {
149
+ evidence: evidence.map((e) => ({
150
+ id: e.id,
151
+ type: Array.isArray(e.type) ? e.type[0] : e.type || 'Evidence',
152
+ name: e.name,
153
+ description: e.description || '',
154
+ })),
155
+ }
156
+ : {}),
172
157
  };
173
- // Generate the hashed ID
174
- unsignedRecommendation.id = 'urn:' + generateHashedId(unsignedRecommendation);
175
158
  return unsignedRecommendation;
176
159
  }
177
160
  /**
@@ -268,6 +251,47 @@ export function generateUnsignedPerformanceReview({ formData, issuerDid }) {
268
251
  unsignedCredential.id = 'urn:' + generateHashedId(unsignedCredential);
269
252
  return unsignedCredential;
270
253
  }
254
+ /**
255
+ * Generate an unsigned SkillClaimCredential (HR Context / VC Data Model v2).
256
+ *
257
+ * Key differences from the legacy OpenBadgeCredential:
258
+ * - Uses `https://www.w3.org/ns/credentials/v2` and `https://w3id.org/hr/v1` contexts
259
+ * - VC subtype is `SkillClaimCredential` (not `OpenBadgeCredential`)
260
+ * - `credentialSubject.type` is `SkillClaim` with a `person` object and `skill` array
261
+ * - Evidence lives at the credential root (not inside `credentialSubject`)
262
+ * - No `issuanceDate`/`expirationDate` set by the author
263
+ * - Issuer is a plain DID string (no `type` wrapper)
264
+ */
265
+ export function generateUnsignedSkillClaim({ formData, issuerDid, }) {
266
+ const unsignedCredential = {
267
+ '@context': skillClaimCredentialContexts,
268
+ id: `urn:uuid:${uuidv4()}`,
269
+ type: ['VerifiableCredential', 'SkillClaimCredential'],
270
+ issuer: issuerDid,
271
+ credentialSubject: {
272
+ type: ['SkillClaim'],
273
+ person: {
274
+ id: formData.personId || issuerDid,
275
+ name: formData.personName,
276
+ },
277
+ skill: formData.skills.map((s) => ({
278
+ id: `urn:uuid:${uuidv4()}`,
279
+ name: s.name,
280
+ description: s.description,
281
+ durationPerformed: s.durationPerformed,
282
+ narrative: s.narrative,
283
+ image: s.image,
284
+ })),
285
+ },
286
+ evidence: formData.evidence?.length ? formData.evidence.map((e) => ({
287
+ id: e.id,
288
+ type: e.type || 'Evidence',
289
+ name: e.name,
290
+ description: e.description || "",
291
+ })) : [],
292
+ };
293
+ return unsignedCredential;
294
+ }
271
295
  /**
272
296
  * Extracts the keypair from a Verifiable Credential
273
297
  * @param {Object} credential - The signed Verifiable Credential
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Document loader using @digitalcredentials/security-document-loader.
3
+ * Adds hr-context for SkillClaimCredential support.
4
+ * @see https://github.com/digitalcredentials/security-document-loader
5
+ */
6
+ import { securityLoader } from '@digitalcredentials/security-document-loader';
7
+ import hrContext from 'hr-context';
8
+ const loader = securityLoader();
9
+ loader.addStatic(hrContext.CONTEXT_URL_V1, hrContext.CONTEXT_V1);
10
+ loader.addStatic('https://w3id.org/hr/v1', hrContext.CONTEXT_V1);
11
+ const builtLoader = loader.build();
12
+ /** Document loader compatible with @digitalcredentials/vc */
13
+ export const customDocumentLoader = async (url) => {
14
+ const result = await builtLoader(url);
15
+ return {
16
+ contextUrl: result.contextUrl ?? null,
17
+ documentUrl: result.documentUrl ?? url,
18
+ document: result.document,
19
+ };
20
+ };
@@ -1,4 +1,4 @@
1
- import { Ed25519VerificationKey2020 } from '@digitalbazaar/ed25519-verification-key-2020';
1
+ import { Ed25519VerificationKey2020 } from '@digitalcredentials/ed25519-verification-key-2020';
2
2
  import { base58btc } from 'multiformats/bases/base58';
3
3
  export async function decodeSeed(encodedSeed) {
4
4
  try {
@@ -1,26 +1,40 @@
1
1
  import { Ed25519VerificationKey2020 } from '@digitalbazaar/ed25519-verification-key-2020';
2
+ import { suiteContext as ed25519Context } from '@digitalbazaar/ed25519-signature-2020';
2
3
  import { driver as didKeyDriver } from '@digitalbazaar/did-method-key';
3
- import { defaultDocumentLoader } from '@digitalbazaar/vc';
4
- import { localOBContext, localED25519Context } from '../utils/context.js';
4
+ import { defaultDocumentLoader } from '@digitalcredentials/vc';
5
+ import { contexts as obContexts } from '@digitalcredentials/open-badges-context';
6
+ import { contexts as credV1Contexts } from 'credentials-context';
7
+ import { contexts as credV2Contexts } from '@digitalcredentials/credentials-v2-context';
8
+ import hrContext from 'hr-context';
5
9
  // Initialize the DID method key driver
6
10
  const didKeyDriverInstance = didKeyDriver();
7
11
  didKeyDriverInstance.use({
8
12
  multibaseMultikeyHeader: 'z6Mk',
9
13
  fromMultibase: Ed25519VerificationKey2020.from,
10
14
  });
15
+ // Patch the upstream hr-context socCode bug: "@type":"@set" -> "@container":"@set"
16
+ const hrCtxData = structuredClone(hrContext.CONTEXT_V1);
17
+ if (hrCtxData?.['@context']?.socCode) {
18
+ delete hrCtxData['@context'].socCode['@type'];
19
+ hrCtxData['@context'].socCode['@container'] = '@set';
20
+ }
21
+ // Build context map from installed packages
22
+ const contextMap = new Map([
23
+ ...credV1Contexts,
24
+ ...credV2Contexts,
25
+ ...obContexts,
26
+ ...ed25519Context.contexts,
27
+ [hrContext.CONTEXT_URL_V1, hrCtxData],
28
+ ['https://w3id.org/hr/v1', hrCtxData],
29
+ ]);
11
30
  // Custom document loader
12
31
  export const customDocumentLoader = async (url) => {
13
- // Context map for local contexts
14
- const contextMap = {
15
- 'https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.3.json': localOBContext,
16
- 'https://w3id.org/security/suites/ed25519-2020/v1': localED25519Context,
17
- };
18
- // Return local context if it matches the URL
19
- if (contextMap[url]) {
32
+ const context = contextMap.get(url);
33
+ if (context) {
20
34
  return {
21
35
  contextUrl: null,
22
36
  documentUrl: url,
23
- document: contextMap[url],
37
+ document: context,
24
38
  };
25
39
  }
26
40
  // Handle did:key resolution
@@ -32,6 +46,6 @@ export const customDocumentLoader = async (url) => {
32
46
  document: didDocument,
33
47
  };
34
48
  }
35
- // Fallback to the default document loader for unknown URLs
49
+ // Fallback to the default document loader
36
50
  return defaultDocumentLoader(url);
37
51
  };
@@ -1,4 +1,4 @@
1
- import { Ed25519VerificationKey2020 } from '@digitalbazaar/ed25519-verification-key-2020';
1
+ import { Ed25519VerificationKey2020 } from '@digitalcredentials/ed25519-verification-key-2020';
2
2
  const LOCAL_STORAGE_KEY = 'AppInstanceDID';
3
3
  export async function getOrCreateAppInstanceDid() {
4
4
  const stored = localStorage.getItem(LOCAL_STORAGE_KEY);
@@ -10,7 +10,6 @@ export async function getOrCreateAppInstanceDid() {
10
10
  const keyPair = await Ed25519VerificationKey2020.generate();
11
11
  keyPair.controller = `did:key:${keyPair.publicKeyMultibase}`;
12
12
  keyPair.id = `${keyPair.controller}#${keyPair.publicKeyMultibase}`;
13
- keyPair.revoked = false;
14
13
  const did = keyPair.controller;
15
14
  localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify({
16
15
  controller: keyPair.controller,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cooperation/vc-storage",
3
3
  "type": "module",
4
- "version": "1.0.43",
4
+ "version": "1.0.45",
5
5
  "description": "Sign and store your verifiable credentials.",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/types/index.d.ts",
@@ -12,26 +12,27 @@
12
12
  "scripts": {
13
13
  "build": "tsc",
14
14
  "test": "vitest",
15
+ "test:all-vcs": "npm run build && node src/scripts/testAllVcs.js",
15
16
  "version": "npm version patch"
16
17
  },
17
18
  "author": "cooperation",
18
19
  "license": "ISC",
19
20
  "dependencies": {
20
21
  "@did.coop/did-key-ed25519": "^0.0.13",
21
- "@digitalbazaar/did-method-key": "^5.2.0",
22
- "@digitalbazaar/ed25519-signature-2020": "^5.4.0",
23
- "@digitalbazaar/ed25519-verification-key-2020": "^4.1.0",
24
- "@digitalbazaar/vc": "^6.3.0",
22
+ "@digitalcredentials/did-method-key": "^3.0.0",
25
23
  "@digitalcredentials/ed25519-signature-2020": "^5.0.0",
26
24
  "@digitalcredentials/ed25519-verification-key-2020": "^5.0.0-beta.2",
27
25
  "@digitalcredentials/ezcap": "^5.1.0",
26
+ "@digitalcredentials/security-document-loader": "^8.0.0",
28
27
  "@digitalcredentials/ssi": "^5.1.0",
28
+ "@digitalcredentials/vc": "^10.0.2",
29
29
  "@wallet.storage/fetch-client": "^1.2.0",
30
30
  "add": "^2.0.6",
31
31
  "bnid": "^3.0.0",
32
32
  "crypto-js": "^4.2.0",
33
33
  "crypto-ld": "^7.0.0",
34
34
  "ethers": "^6.13.2",
35
+ "hr-context": "^0.1.6",
35
36
  "jest": "^29.7.0",
36
37
  "multiformats": "^13.3.6",
37
38
  "ts-jest": "^29.2.5",
@@ -42,6 +43,7 @@
42
43
  "devDependencies": {
43
44
  "@types/fs-extra": "^11.0.4",
44
45
  "@types/jest": "^29.5.14",
46
+ "@types/node": "^25.9.0",
45
47
  "@types/uuid": "^10.0.0",
46
48
  "babel-jest": "^29.7.0",
47
49
  "typescript": "^5.8.3",