@cooperation/vc-storage 1.0.33 → 1.0.40

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.
@@ -120,6 +120,82 @@ export const inlineResumeContext = {
120
120
  Resume: 'https://schema.hropenstandards.org/4.4#Resume',
121
121
  },
122
122
  };
123
+ // 1. Employment Credential Context
124
+ export const employmentCredentialContext = {
125
+ '@context': {
126
+ '@vocab': 'https://schema.hropenstandards.org/4.4/',
127
+ fullName: 'https://schema.org/name',
128
+ persons: 'https://schema.org/name',
129
+ credentialName: 'https://schema.org/jobTitle',
130
+ credentialDuration: 'https://schema.org/duration',
131
+ credentialDescription: 'https://schema.org/description',
132
+ portfolio: {
133
+ '@id': 'https://schema.org/hasPart',
134
+ '@container': '@list'
135
+ },
136
+ name: 'https://schema.org/name',
137
+ url: 'https://schema.org/url',
138
+ evidenceLink: 'https://schema.org/url',
139
+ evidenceDescription: 'https://schema.org/description',
140
+ company: 'https://schema.org/worksFor',
141
+ role: 'https://schema.org/jobTitle'
142
+ }
143
+ };
144
+ // 2. Volunteering Credential Context
145
+ export const volunteeringCredentialContext = {
146
+ '@context': {
147
+ '@vocab': 'https://schema.hropenstandards.org/4.4/',
148
+ fullName: 'https://schema.org/name',
149
+ persons: 'https://schema.org/name',
150
+ volunteerWork: 'https://schema.org/roleName',
151
+ volunteerOrg: 'https://schema.org/organization',
152
+ volunteerDescription: 'https://schema.org/description',
153
+ skillsGained: {
154
+ '@id': 'https://schema.org/skills',
155
+ '@container': '@list'
156
+ },
157
+ duration: 'https://schema.org/duration',
158
+ volunteerDates: 'https://schema.org/temporalCoverage',
159
+ portfolio: {
160
+ '@id': 'https://schema.org/hasPart',
161
+ '@container': '@list'
162
+ },
163
+ name: 'https://schema.org/name',
164
+ url: 'https://schema.org/url',
165
+ evidenceLink: 'https://schema.org/url',
166
+ evidenceDescription: 'https://schema.org/description'
167
+ }
168
+ };
169
+ // 3. Performance Review Credential Context
170
+ export const performanceReviewCredentialContext = {
171
+ '@context': {
172
+ '@vocab': 'https://schema.hropenstandards.org/4.4/',
173
+ fullName: 'https://schema.org/name',
174
+ persons: 'https://schema.org/name',
175
+ employeeName: 'https://schema.org/name',
176
+ employeeJobTitle: 'https://schema.org/jobTitle',
177
+ company: 'https://schema.org/worksFor',
178
+ role: 'https://schema.org/jobTitle',
179
+ reviewStartDate: 'https://schema.org/startDate',
180
+ reviewEndDate: 'https://schema.org/endDate',
181
+ reviewDuration: 'https://schema.org/duration',
182
+ jobKnowledgeRating: 'https://schema.org/assessmentScore',
183
+ teamworkRating: 'https://schema.org/assessmentScore',
184
+ initiativeRating: 'https://schema.org/assessmentScore',
185
+ communicationRating: 'https://schema.org/assessmentScore',
186
+ overallRating: 'https://schema.org/aggregateRating',
187
+ reviewComments: 'https://schema.org/comment',
188
+ goalsNext: 'https://schema.hropenstandards.org/4.4/goalsNext',
189
+ portfolio: {
190
+ '@id': 'https://schema.org/hasPart',
191
+ '@container': '@list'
192
+ },
193
+ name: 'https://schema.org/name',
194
+ url: 'https://schema.org/url',
195
+ evidenceLink: 'https://schema.org/url',
196
+ evidenceDescription: 'https://schema.org/description'
197
+ }
198
+ };
123
199
  const localOBContext = {
124
200
  '@context': {
125
201
  '@protected': true,
@@ -0,0 +1,27 @@
1
+ import { StorageClient } from '@wallet.storage/fetch-client';
2
+ import { Ed25519Signer } from '@did.coop/did-key-ed25519';
3
+ import { v4 as uuidv4 } from 'uuid';
4
+ import { WAS_BASE_URL } from '../../app.config.js';
5
+ /**
6
+ * Create a new WAS space
7
+ * @returns {Promise<{ signer: InstanceType<typeof Ed25519Signer>; spaceId: `urn:uuid:${string}` }>}
8
+ */
9
+ export async function createSpace() {
10
+ const signer = await Ed25519Signer.generate();
11
+ const controller = signer.id.split('#')[0];
12
+ const spaceUUID = uuidv4();
13
+ const spaceId = `urn:uuid:${spaceUUID}`;
14
+ const client = new StorageClient(new URL(WAS_BASE_URL));
15
+ const space = client.space({ signer, id: spaceId });
16
+ const spaceObject = {
17
+ id: spaceId,
18
+ controller,
19
+ };
20
+ const blob = new Blob([JSON.stringify(spaceObject)], { type: 'application/json' });
21
+ const res = await space.put(blob, { signer });
22
+ if (!res.ok) {
23
+ throw new Error(`Failed to initialize WAS space. Status: ${res.status}`);
24
+ }
25
+ console.log('✅ Provisioned and saved new WAS space');
26
+ return { signer, spaceId };
27
+ }
@@ -1,7 +1,9 @@
1
1
  import { Ed25519VerificationKey2020 } from '@digitalbazaar/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
5
  /**
6
+ *
5
7
  * Utility function to generate a hashed ID for a credential.
6
8
  * Excludes the `id` field when hashing.
7
9
  * @param {object} credential - The credential object to hash.
@@ -169,6 +171,100 @@ export function generateUnsignedRecommendation({ vcId, recommendation, issuerDid
169
171
  };
170
172
  return unsignedRecommendation;
171
173
  }
174
+ /**
175
+ * Generate an unsigned Employment Credential.
176
+ */
177
+ export function generateUnsignedEmployment({ formData, issuerDid }) {
178
+ const issuanceDate = new Date().toISOString();
179
+ const unsignedCredential = {
180
+ '@context': ['https://www.w3.org/2018/credentials/v1', employmentCredentialContext['@context']],
181
+ id: '',
182
+ type: ['VerifiableCredential', 'EmploymentCredential'],
183
+ issuer: { id: issuerDid, type: ['Profile'] },
184
+ issuanceDate,
185
+ credentialSubject: {
186
+ type: ['WorkExperience'],
187
+ fullName: formData.fullName,
188
+ persons: formData.persons,
189
+ credentialName: formData.credentialName,
190
+ credentialDuration: formData.credentialDuration,
191
+ credentialDescription: formData.credentialDescription,
192
+ portfolio: formData.portfolio.map((item) => ({ name: item.name, url: item.url })),
193
+ evidenceLink: formData.evidenceLink,
194
+ evidenceDescription: formData.evidenceDescription,
195
+ company: formData.company,
196
+ role: formData.role,
197
+ },
198
+ };
199
+ unsignedCredential.id = 'urn:' + generateHashedId(unsignedCredential);
200
+ return unsignedCredential;
201
+ }
202
+ /**
203
+ * Generate an unsigned Volunteering Credential.
204
+ */
205
+ export function generateUnsignedVolunteering({ formData, issuerDid }) {
206
+ const issuanceDate = new Date().toISOString();
207
+ const unsignedCredential = {
208
+ '@context': ['https://www.w3.org/2018/credentials/v1', volunteeringCredentialContext['@context']],
209
+ id: '',
210
+ type: ['VerifiableCredential', 'VolunteeringCredential'],
211
+ issuer: { id: issuerDid, type: ['Profile'] },
212
+ issuanceDate,
213
+ credentialSubject: {
214
+ type: ['VolunteerRole'],
215
+ fullName: formData.fullName,
216
+ persons: formData.persons,
217
+ volunteerWork: formData.volunteerWork,
218
+ volunteerOrg: formData.volunteerOrg,
219
+ volunteerDescription: formData.volunteerDescription,
220
+ skillsGained: formData.skillsGained ? formData.skillsGained.split(',').map((s) => s.trim()) : undefined,
221
+ duration: formData.duration,
222
+ volunteerDates: formData.volunteerDates,
223
+ portfolio: formData.portfolio.map((item) => ({ name: item.name, url: item.url })),
224
+ evidenceLink: formData.evidenceLink,
225
+ evidenceDescription: formData.evidenceDescription,
226
+ },
227
+ };
228
+ unsignedCredential.id = 'urn:' + generateHashedId(unsignedCredential);
229
+ return unsignedCredential;
230
+ }
231
+ /**
232
+ * Generate an unsigned Performance Review Credential.
233
+ */
234
+ export function generateUnsignedPerformanceReview({ formData, issuerDid }) {
235
+ const issuanceDate = new Date().toISOString();
236
+ const unsignedCredential = {
237
+ '@context': ['https://www.w3.org/2018/credentials/v1', performanceReviewCredentialContext['@context']],
238
+ id: '',
239
+ type: ['VerifiableCredential', 'PerformanceReviewCredential'],
240
+ issuer: { id: issuerDid, type: ['Profile'] },
241
+ issuanceDate,
242
+ credentialSubject: {
243
+ type: ['EndorsementSubject'],
244
+ fullName: formData.fullName,
245
+ persons: formData.persons,
246
+ employeeName: formData.employeeName,
247
+ employeeJobTitle: formData.employeeJobTitle,
248
+ company: formData.company,
249
+ role: formData.role,
250
+ reviewStartDate: formData.reviewStartDate,
251
+ reviewEndDate: formData.reviewEndDate,
252
+ reviewDuration: formData.reviewDuration,
253
+ jobKnowledgeRating: formData.jobKnowledgeRating,
254
+ teamworkRating: formData.teamworkRating,
255
+ initiativeRating: formData.initiativeRating,
256
+ communicationRating: formData.communicationRating,
257
+ overallRating: formData.overallRating,
258
+ reviewComments: formData.reviewComments,
259
+ goalsNext: formData.goalsNext,
260
+ portfolio: formData.portfolio.map((item) => ({ name: item.name, url: item.url })),
261
+ evidenceLink: formData.evidenceLink,
262
+ evidenceDescription: formData.evidenceDescription,
263
+ },
264
+ };
265
+ unsignedCredential.id = 'urn:' + generateHashedId(unsignedCredential);
266
+ return unsignedCredential;
267
+ }
172
268
  /**
173
269
  * Extracts the keypair from a Verifiable Credential
174
270
  * @param {Object} credential - The signed Verifiable Credential
@@ -0,0 +1,22 @@
1
+ import { Ed25519VerificationKey2020 } from '@digitalbazaar/ed25519-verification-key-2020';
2
+ const LOCAL_STORAGE_KEY = 'AppInstanceDID';
3
+ export async function getOrCreateAppInstanceDid() {
4
+ const stored = localStorage.getItem(LOCAL_STORAGE_KEY);
5
+ if (stored) {
6
+ const parsed = JSON.parse(stored);
7
+ const keyPair = await Ed25519VerificationKey2020.from(parsed);
8
+ return { did: keyPair.controller, keyPair };
9
+ }
10
+ const keyPair = await Ed25519VerificationKey2020.generate();
11
+ keyPair.controller = `did:key:${keyPair.publicKeyMultibase}`;
12
+ keyPair.id = `${keyPair.controller}#${keyPair.publicKeyMultibase}`;
13
+ keyPair.revoked = false;
14
+ const did = keyPair.controller;
15
+ localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify({
16
+ controller: keyPair.controller,
17
+ id: keyPair.id,
18
+ publicKeyMultibase: keyPair.publicKeyMultibase,
19
+ privateKeyMultibase: keyPair.privateKeyMultibase,
20
+ }));
21
+ return { did, keyPair };
22
+ }
@@ -1,7 +1,7 @@
1
1
  export const getVCWithRecommendations = async ({ vcId, storage }) => {
2
2
  try {
3
3
  const vcFolderId = await storage.getFileParents(vcId);
4
- const files = await storage.findFilesUnderFolder(vcFolderId);
4
+ const files = await storage.findFolderFiles(vcFolderId);
5
5
  const relationsFile = files.find((f) => f.name === 'RELATIONS');
6
6
  const relationsContent = await storage.retrieve(relationsFile.id);
7
7
  const relationsData = relationsContent.data.body ? JSON.parse(relationsContent.data.body) : relationsContent.data;
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@cooperation/vc-storage",
3
3
  "type": "module",
4
- "version": "1.0.33",
4
+ "version": "1.0.40",
5
5
  "description": "Sign and store your verifiable credentials.",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/types/index.d.ts",
8
8
  "files": [
9
- "dist"
9
+ "dist",
10
+ "app.config.js"
10
11
  ],
11
12
  "scripts": {
12
13
  "build": "tsc",
@@ -16,10 +17,12 @@
16
17
  "author": "cooperation",
17
18
  "license": "ISC",
18
19
  "dependencies": {
20
+ "@did.coop/did-key-ed25519": "^0.0.13",
19
21
  "@digitalbazaar/did-method-key": "^5.2.0",
20
22
  "@digitalbazaar/ed25519-signature-2020": "^5.4.0",
21
23
  "@digitalbazaar/ed25519-verification-key-2020": "^4.1.0",
22
24
  "@digitalbazaar/vc": "^6.3.0",
25
+ "@wallet.storage/fetch-client": "^1.2.0",
23
26
  "add": "^2.0.6",
24
27
  "bnid": "^3.0.0",
25
28
  "crypto-js": "^4.2.0",
@@ -37,7 +40,7 @@
37
40
  "@types/jest": "^29.5.14",
38
41
  "@types/uuid": "^10.0.0",
39
42
  "babel-jest": "^29.7.0",
40
- "typescript": "^5.8.2",
43
+ "typescript": "^5.8.3",
41
44
  "vitest": "^3.0.5"
42
45
  }
43
46
  }
@@ -1,10 +0,0 @@
1
- /**
2
- * Create and sign a Verifiable Presentation (VP) from a given Verifiable Credential (VC) file and any associated recommendations.
3
- * @param {string} accessTokens - The access tokens for the user.
4
- * @param {string} vcFileId - The ID of the Verifiable Credential (VC) file in Google Drive.
5
- * @returns {Promise<{ signedPresentation: object } | null>} - The signed Verifiable Presentation (VP) or null if an error occurs.
6
- * @throws Will throw an error if the VC is not found, a matching key pair cannot be located, or any part of the signing process fails.
7
- */
8
- export declare const createAndSignVerifiablePresentation: (accessTokens: string, vcFileId: string) => Promise<{
9
- signedPresentation: object;
10
- } | null>;
@@ -1,45 +0,0 @@
1
- /**
2
- * Create and sign a Verifiable Presentation (VP) from a given Verifiable Credential (VC) file and any associated recommendations.
3
- * @param {string} accessTokens - The access tokens for the user.
4
- * @param {string} vcFileId - The ID of the Verifiable Credential (VC) file in Google Drive.
5
- * @returns {Promise<{ signedPresentation: object } | null>} - The signed Verifiable Presentation (VP) or null if an error occurs.
6
- * @throws Will throw an error if the VC is not found, a matching key pair cannot be located, or any part of the signing process fails.
7
- */
8
- export const createAndSignVerifiablePresentation = async (accessTokens, vcFileId) => {
9
- if (!accessTokens || !vcFileId) {
10
- console.error('Invalid input: Access tokens and VC file ID are required.');
11
- return null;
12
- }
13
- try {
14
- // const storage = new GoogleDriveStorage(accessTokens);
15
- // const engine = new CredentialEngine(accessTokens);
16
- // // Fetch Verifiable Credential (VC)
17
- // const verifiableCredential = await storage.retrieve(vcFileId);
18
- // if (!verifiableCredential) {
19
- // throw new Error('Verifiable Credential not found.');
20
- // }
21
- // // Fetch VC comments (potential recommendations)
22
- // const verifiableCredentialComments = await storage.getFileComments(vcFileId);
23
- // let recommendations: object[] = [];
24
- // // Extract recommendations from comments if present
25
- // if (verifiableCredentialComments.length > 0) {
26
- // for (const comment of verifiableCredentialComments) {
27
- // console.log('🚀 ~ createAndSignVerifiablePresentation ~ comment', comment);
28
- // const recommendationFile = await storage.retrieve(extractGoogleDriveFileId(comment.content));
29
- // console.log('🚀 ~ createAndSignVerifiablePresentation ~ recommendationFile', recommendationFile);
30
- // if (recommendationFile) {
31
- // recommendations.push(recommendationFile);
32
- // }
33
- // }
34
- // }
35
- // // Create Verifiable Presentation (VP) with the retrieved VC
36
- // const presentation = await engine.createPresentation([verifiableCredential.data, ...recommendations]); //! do not edit the array order!!
37
- // // Use the key pair to sign the presentation
38
- // const signedPresentation = await engine.signPresentation(presentation);
39
- // return {};
40
- }
41
- catch (error) {
42
- console.error('Error during Verifiable Presentation creation and signing:', error);
43
- return null;
44
- }
45
- };