@cooperation/vc-storage 1.0.11 → 1.0.13

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,6 +1,6 @@
1
1
  import { Ed25519VerificationKey2020 } from '@digitalbazaar/ed25519-verification-key-2020';
2
2
  import { Ed25519Signature2020 } from '@digitalbazaar/ed25519-signature-2020';
3
- import * as vc from '@digitalbazaar/vc';
3
+ import * as dbVc from '@digitalbazaar/vc';
4
4
  import { v4 as uuidv4 } from 'uuid';
5
5
  import { extractKeyPairFromCredential, generateDIDSchema, generateUnsignedRecommendation, generateUnsignedVC, } from '../utils/credential.js';
6
6
  import { customDocumentLoader } from '../utils/digitalbazaar.js';
@@ -19,11 +19,9 @@ import { GoogleDriveStorage } from './GoogleDriveStorage.js';
19
19
  * @method signPresentation - Sign a Verifiable Presentation (VP).
20
20
  */
21
21
  export class CredentialEngine {
22
- uuid;
23
22
  storage;
24
23
  keyPair;
25
24
  constructor(accessToken) {
26
- this.uuid = uuidv4();
27
25
  this.storage = new GoogleDriveStorage(accessToken);
28
26
  }
29
27
  async getKeyPair(vc) {
@@ -75,8 +73,11 @@ export class CredentialEngine {
75
73
  async createDID() {
76
74
  try {
77
75
  const keyPair = await this.generateKeyPair();
78
- const keyFile = await saveToGoogleDrive(this.storage, keyPair, 'KEYPAIR', this.uuid);
79
- console.log('🚀 ~ CredentialEngine ~ createDID ~ keyFile:', keyFile);
76
+ const keyFile = await saveToGoogleDrive({
77
+ storage: this.storage,
78
+ data: keyPair,
79
+ type: 'KEYPAIR',
80
+ });
80
81
  const didDocument = await generateDIDSchema(keyPair);
81
82
  return { didDocument, keyPair };
82
83
  }
@@ -85,6 +86,18 @@ export class CredentialEngine {
85
86
  throw error;
86
87
  }
87
88
  }
89
+ async findKeysAndDIDs() {
90
+ const keyPairs = (await this.storage.getAllFilesByType('KEYPAIRs'));
91
+ const DIDs = (await this.storage.getAllFilesByType('DIDs'));
92
+ if (DIDs.length === 0 || keyPairs.length === 0)
93
+ return null;
94
+ const keyPair = keyPairs[0].data;
95
+ const didDocument = DIDs[0].data.didDocument;
96
+ return {
97
+ didDocument,
98
+ keyPair,
99
+ };
100
+ }
88
101
  /**
89
102
  * Create a new DID with user metamask address as controller
90
103
  * @param walletrAddress
@@ -94,7 +107,11 @@ export class CredentialEngine {
94
107
  async createWalletDID(walletrAddress) {
95
108
  try {
96
109
  const keyPair = await this.generateKeyPair(walletrAddress);
97
- const keyFile = await saveToGoogleDrive(this.storage, keyPair, 'KEYPAIR', this.uuid);
110
+ const keyFile = await saveToGoogleDrive({
111
+ storage: this.storage,
112
+ data: keyPair,
113
+ type: 'KEYPAIR',
114
+ });
98
115
  console.log('🚀 ~ CredentialEngine ~ createWalletDID ~ keyFile:', keyFile);
99
116
  const didDocument = await generateDIDSchema(keyPair);
100
117
  return { didDocument, keyPair };
@@ -109,20 +126,33 @@ export class CredentialEngine {
109
126
  * @param {'VC' | 'RECOMMENDATION'} type - The signature type.
110
127
  * @param {string} issuerId - The ID of the issuer [currently we put it as the did id]
111
128
  * @param {KeyPair} keyPair - The key pair to use for signing.
129
+ * @param {FormDataI | RecommendationFormDataI} formData - The form data to include in the VC.
130
+ * @param {string} VCId - The ID of the credential when the type is RECOMMENDATION
112
131
  * @returns {Promise<Credential>} The signed VC.
113
132
  * @throws Will throw an error if VC signing fails.
114
133
  */
115
- async signVC(formData, type, keyPair, issuerId) {
116
- let credential;
117
- if (type == 'VC') {
118
- credential = generateUnsignedVC(formData, issuerId, this.uuid);
119
- }
120
- else if (type == 'RECOMMENDATION') {
121
- credential = generateUnsignedRecommendation(formData, issuerId);
134
+ async signVC({ data, type, keyPair, issuerId, vcFileId }) {
135
+ console.log('🚀 ~ CredentialEngine ~ signVC ~ { data, type, keyPair, issuerId, vcFileId }:', {
136
+ data,
137
+ type,
138
+ keyPair,
139
+ issuerId,
140
+ vcFileId,
141
+ });
142
+ let vc;
143
+ let credential = generateUnsignedVC({ formData: data, issuerDid: issuerId });
144
+ if (type == 'RECOMMENDATION' && vcFileId) {
145
+ console.log('WOW');
146
+ vc = (await this.storage.retrieve(vcFileId));
147
+ credential = generateUnsignedRecommendation({ vc, recommendation: data, issuerDid: issuerId });
122
148
  }
123
- const suite = new Ed25519Signature2020({ key: keyPair, verificationMethod: keyPair.id });
124
149
  try {
125
- const signedVC = await vc.issue({ credential, suite, documentLoader: customDocumentLoader });
150
+ console.log('🚀 ~ CredentialEngine ~ signVC ~ credential:', credential);
151
+ if (!credential)
152
+ throw new Error('Invalid credential type');
153
+ const suite = new Ed25519Signature2020({ key: keyPair, verificationMethod: keyPair.id });
154
+ console.log('before');
155
+ const signedVC = await dbVc.issue({ credential, suite, documentLoader: customDocumentLoader });
126
156
  return signedVC;
127
157
  }
128
158
  catch (error) {
@@ -143,7 +173,7 @@ export class CredentialEngine {
143
173
  key: keyPair,
144
174
  verificationMethod: keyPair.id,
145
175
  });
146
- const result = await vc.verifyCredential({
176
+ const result = await dbVc.verifyCredential({
147
177
  credential,
148
178
  suite,
149
179
  documentLoader: customDocumentLoader,
@@ -169,7 +199,7 @@ export class CredentialEngine {
169
199
  const id = `urn:uuid:${uuidv4()}`;
170
200
  const keyPair = await this.getKeyPair(verifiableCredential[0]);
171
201
  console.log('🚀 ~ CredentialEngine ~ createPresentation ~ keyPair:', keyPair);
172
- const VP = await vc.createPresentation({ verifiableCredential, id, holder: keyPair.controller });
202
+ const VP = await dbVc.createPresentation({ verifiableCredential, id, holder: keyPair.controller });
173
203
  return VP;
174
204
  }
175
205
  catch (error) {
@@ -198,7 +228,7 @@ export class CredentialEngine {
198
228
  verificationMethod: this.keyPair.id,
199
229
  });
200
230
  // Sign the presentation
201
- const signedVP = await vc.signPresentation({
231
+ const signedVP = await dbVc.signPresentation({
202
232
  presentation,
203
233
  suite,
204
234
  documentLoader: customDocumentLoader,
@@ -1,4 +1,3 @@
1
- import { generateViewLink } from '../utils/google.js';
2
1
  /**
3
2
  * @class GoogleDriveStorage
4
3
  * @description Class to interact with Google Drive API
@@ -19,7 +18,6 @@ export class GoogleDriveStorage {
19
18
  constructor(accessToken) {
20
19
  this.accessToken = accessToken;
21
20
  }
22
- // Method to fetch data from Google Drive API
23
21
  async fetcher({ method, headers, body, url }) {
24
22
  try {
25
23
  const res = await fetch(url, {
@@ -30,26 +28,39 @@ export class GoogleDriveStorage {
30
28
  }),
31
29
  body,
32
30
  });
33
- // Check for errors in the response
34
- const data = await res.json();
31
+ // Check the Content-Type to ensure it's JSON before parsing
32
+ const contentType = res.headers.get('Content-Type') || '';
33
+ let data;
34
+ if (contentType.includes('application/json')) {
35
+ data = await res.json();
36
+ }
37
+ else {
38
+ const text = await res.text();
39
+ console.error('Unexpected Response Type:', text);
40
+ throw new Error(`Expected JSON response but got: ${contentType}`);
41
+ }
42
+ // Handle non-200 HTTP responses
35
43
  if (!res.ok) {
36
44
  console.error('Error Response:', JSON.stringify(data));
37
- throw new Error(data.error.message || 'Unknown error');
45
+ throw new Error(data?.error?.message || 'Unknown error occurred');
38
46
  }
39
47
  return data;
40
48
  }
41
49
  catch (error) {
42
- console.error('Error fetching data:', error.message);
50
+ console.error('Error fetching data:', error.message || error);
43
51
  throw error;
44
52
  }
45
53
  }
46
- // Method to search for files in Google Drive by query
47
54
  async searchFiles(query) {
48
55
  const result = await this.fetcher({
49
56
  method: 'GET',
50
57
  headers: {},
51
58
  url: `https://www.googleapis.com/drive/v3/files?q=${encodeURIComponent(query)}&trashed=false&fields=files(id,name,mimeType,parents)`,
52
59
  });
60
+ if (!result.files) {
61
+ console.error('No files found:', result);
62
+ return [];
63
+ }
53
64
  return result.files;
54
65
  }
55
66
  async createFolder(folderName, parentFolderId) {
@@ -67,7 +78,7 @@ export class GoogleDriveStorage {
67
78
  console.log('Folder ID:', folder.id);
68
79
  return folder.id;
69
80
  }
70
- async save(data, folderId) {
81
+ async saveFile({ data, folderId }) {
71
82
  try {
72
83
  // Define file metadata, ensure correct folder is assigned
73
84
  const fileMetadata = {
@@ -84,9 +95,8 @@ export class GoogleDriveStorage {
84
95
  method: 'POST',
85
96
  headers: {},
86
97
  body: formData,
87
- url: uploadUrl,
98
+ url: `${uploadUrl}&fields=id,parents`, // Request the file ID and parent folder IDs
88
99
  });
89
- console.log('File uploaded successfully:', file.id);
90
100
  // Set the file permission to "Anyone with the link" can view
91
101
  const permissionUrl = `https://www.googleapis.com/drive/v3/files/${file.id}/permissions`;
92
102
  const permissionData = {
@@ -96,112 +106,42 @@ export class GoogleDriveStorage {
96
106
  await this.fetcher({
97
107
  method: 'POST',
98
108
  url: permissionUrl,
99
- headers: {
100
- 'Content-Type': 'application/json',
101
- },
109
+ headers: {},
102
110
  body: JSON.stringify(permissionData),
103
111
  });
104
112
  console.log('Permission set to public for file:', file.id);
105
- return { id: file.id };
113
+ console.log('Parent folder IDs:', file.parents);
114
+ return file;
106
115
  }
107
116
  catch (error) {
108
117
  console.error('Error uploading file or setting permission:', error.message);
109
118
  return null;
110
119
  }
111
120
  }
112
- /**
113
- * Add comment to VC
114
- * @param fileId - th id of VC file
115
- * @returns
116
- */
117
- async addCommentToFile(vcFileId, recommendationFileId) {
118
- if (!recommendationFileId || !vcFileId || !this.accessToken) {
119
- throw new Error('Missing required parameters: fileId, commentText, or accessToken');
120
- }
121
- const url = `https://www.googleapis.com/drive/v3/files/${vcFileId}/comments?fields=id,content,createdTime`;
122
- const body = {
123
- content: generateViewLink(recommendationFileId),
124
- };
125
- try {
126
- const response = await fetch(url, {
127
- method: 'POST',
128
- headers: {
129
- Authorization: `Bearer ${this.accessToken}`,
130
- 'Content-Type': 'application/json',
131
- },
132
- body: JSON.stringify(body),
133
- });
134
- if (!response.ok) {
135
- const errorDetails = await response.json();
136
- throw new Error(`Failed to add comment: ${JSON.stringify(errorDetails)}`);
137
- }
138
- const result = await response.json();
139
- console.log('Comment added successfully:', result);
140
- return result;
141
- }
142
- catch (error) {
143
- console.error('Error adding comment to file:', error);
144
- throw error;
145
- }
146
- }
147
- /**
148
- * Add commenter role to a file
149
- * @param fileId
150
- * @returns
151
- */
152
- async addCommenterRoleToFile(fileId) {
153
- const url = `https://www.googleapis.com/drive/v3/files/${fileId}/permissions`;
154
- const body = {
155
- role: 'commenter',
156
- type: 'anyone',
157
- };
158
- try {
159
- const response = await fetch(url, {
160
- method: 'POST',
161
- headers: {
162
- Authorization: `Bearer ${this.accessToken}`,
163
- 'Content-Type': 'application/json',
164
- },
165
- body: JSON.stringify(body),
166
- });
167
- if (!response.ok) {
168
- const errorDetails = await response.json();
169
- throw new Error(`Failed to add permission: ${JSON.stringify(errorDetails)}`);
170
- }
171
- const result = await response.json();
172
- console.log('Permission added successfully:', result);
173
- return result;
174
- }
175
- catch (error) {
176
- console.error('Error adding permission:', error.message);
177
- throw error;
178
- }
179
- }
180
121
  /**
181
122
  * Get file from google drive by id
182
123
  * @param id
183
124
  * @returns file content
184
125
  */
185
126
  async retrieve(id) {
186
- const metadataUrl = `https://www.googleapis.com/drive/v3/files/${id}?fields=name`;
127
+ const metadataUrl = `https://www.googleapis.com/drive/v3/files/${id}?fields=id,name`;
187
128
  const dataUrl = `https://www.googleapis.com/drive/v3/files/${id}?alt=media`;
188
129
  try {
189
- console.log(`Starting retrieval for file ID: ${id}`);
190
- // Fetch file metadata to get the name
191
- const metadataResponse = await fetch(metadataUrl, {
130
+ // Initial "touch" request to ensure file accessibility for the current user
131
+ const touchResponse = await fetch(metadataUrl, {
192
132
  method: 'GET',
193
133
  headers: {
194
134
  Authorization: `Bearer ${this.accessToken}`,
195
135
  },
196
136
  });
197
- if (!metadataResponse.ok) {
198
- const errorData = await metadataResponse.json();
199
- console.error(`Failed to retrieve metadata for file ID ${id}:`, errorData);
137
+ if (!touchResponse.ok) {
138
+ const errorData = await touchResponse.json();
139
+ console.error(`Failed to "touch" file for accessibility with ID ${id}:`, errorData);
200
140
  return null;
201
141
  }
202
- const metadata = await metadataResponse.json();
142
+ // Fetch file metadata to get the name
143
+ const metadata = await touchResponse.json();
203
144
  const fileName = metadata.name;
204
- console.log(`File name retrieved: ${fileName}`);
205
145
  // Fetch actual file data
206
146
  const dataResponse = await fetch(dataUrl, {
207
147
  method: 'GET',
@@ -237,9 +177,8 @@ export class GoogleDriveStorage {
237
177
  else {
238
178
  fileData = await dataResponse.arrayBuffer(); // Fallback for other binary types
239
179
  }
240
- console.log('🚀 ~ GoogleDriveStorage ~ retrieve ~ fileData:', fileData);
241
180
  // Return file ID, name, and data
242
- return { id, name: fileName, data: fileData };
181
+ return { id: metadata.id, name: fileName, data: fileData };
243
182
  }
244
183
  catch (error) {
245
184
  console.error(`Error retrieving file with ID ${id}:`, error.message);
@@ -248,7 +187,7 @@ export class GoogleDriveStorage {
248
187
  }
249
188
  /**
250
189
  * Get folder by folderId, if folderId == null you will have them all
251
- * @param id [Optional]
190
+ * @param folderId [Optional]
252
191
  * @returns
253
192
  */
254
193
  findFolders = async (folderId) => {
@@ -310,22 +249,6 @@ export class GoogleDriveStorage {
310
249
  return null;
311
250
  }
312
251
  };
313
- async getFileComments(fileId) {
314
- try {
315
- // Fetch comments on the file using Google Drive API
316
- const commentsResponse = await this.fetcher({
317
- method: 'GET',
318
- headers: {},
319
- url: `https://www.googleapis.com/drive/v3/files/${fileId}/comments?fields=comments(content,author/displayName,createdTime)`,
320
- });
321
- // Return the comments data if available
322
- return commentsResponse.comments || []; // Return an empty array if no comments
323
- }
324
- catch (error) {
325
- console.error(`Failed to fetch comments for file ID: ${fileId}`, error);
326
- return []; // Handle errors by returning an empty array or some error indication
327
- }
328
- }
329
252
  /**
330
253
  * Get all files content for the specified type ('KEYPAIRs' | 'VCs' | 'SESSIONs' | 'DIDs' | 'RECOMMENDATIONs')
331
254
  * @param type
@@ -333,42 +256,64 @@ export class GoogleDriveStorage {
333
256
  */
334
257
  async getAllFilesByType(type) {
335
258
  try {
336
- // Step 1: Find all root folders
259
+ // Step 1: Find the root 'Credentials' folder
337
260
  const rootFolders = await this.findFolders();
338
261
  const credentialsFolder = rootFolders.find((f) => f.name === 'Credentials');
339
- if (!credentialsFolder)
262
+ if (!credentialsFolder) {
263
+ console.error('Credentials folder not found.');
340
264
  return [];
265
+ }
341
266
  const credentialsFolderId = credentialsFolder.id;
342
- // Step 2: Find the subfolder corresponding to the specified type
267
+ // Step 2: Handle special case for 'VCs'
268
+ if (type === 'VCs') {
269
+ // Find the 'VCs' folder under 'Credentials'
270
+ const subfolders = await this.findFolders(credentialsFolderId);
271
+ const targetFolder = subfolders.find((f) => f.name === 'VCs');
272
+ if (!targetFolder) {
273
+ console.error(`Folder for type ${type} not found.`);
274
+ return [];
275
+ }
276
+ const targetFolderId = targetFolder.id;
277
+ // Fetch all 'VC-timestamp' subfolders under 'VCs'
278
+ const vcSubfolders = await this.findFolders(targetFolderId);
279
+ // Retrieve all 'VC.json' files from each 'VC-timestamp' subfolder
280
+ const fileContents = await Promise.all(vcSubfolders.map(async (folder) => {
281
+ const files = await this.findFilesUnderFolder(folder.id);
282
+ return Promise.all(files.map(async (file) => {
283
+ return await this.retrieve(file.id);
284
+ }));
285
+ }));
286
+ // for (const folder of vcSubfolders) {
287
+ // const files = await this.findFilesUnderFolder(folder.id);
288
+ // // Fetch the content of each file
289
+ // for (const file of files) {
290
+ // try {
291
+ // const content = await this.retrieve(file.id);
292
+ // fileContents.push(content);
293
+ // } catch (error) {
294
+ // console.error(`Error retrieving content for file ${file.id}:`, error);
295
+ // }
296
+ // }
297
+ // }
298
+ return fileContents;
299
+ }
300
+ // Step 3: Generic handling for other types
343
301
  const subfolders = await this.findFolders(credentialsFolderId);
344
302
  const targetFolder = subfolders.find((f) => f.name === type);
345
- if (!targetFolder)
303
+ if (!targetFolder) {
304
+ console.error(`Folder for type ${type} not found.`);
346
305
  return [];
347
- // Step 3: Fetch all files in the specified folder
306
+ }
348
307
  const filesResponse = await this.fetcher({
349
308
  method: 'GET',
350
309
  headers: {},
351
310
  url: `https://www.googleapis.com/drive/v3/files?q='${targetFolder.id}' in parents and trashed=false&fields=files(id,name,mimeType,parents)`,
352
311
  });
353
312
  const files = filesResponse.files;
354
- // Step 4: Fetch the content and comments of each file
355
313
  const fileContents = await Promise.all(files.map(async (file) => {
356
- // Fetch file content
357
- const content = await this.fetcher({
358
- method: 'GET',
359
- headers: {},
360
- url: `https://www.googleapis.com/drive/v3/files/${file.id}?alt=media`,
361
- });
362
- // Fetch file comments (if applicable)
363
- const comments = await this.getFileComments(file.id);
364
- return {
365
- id: file.id,
366
- name: file.name,
367
- content,
368
- comments: comments.map((comment) => comment.content),
369
- };
314
+ return await this.retrieve(file.id);
370
315
  }));
371
- return fileContents; // Return the list of files with their content and comments
316
+ return fileContents;
372
317
  }
373
318
  catch (error) {
374
319
  console.error(`Error getting files of type ${type}:`, error);
@@ -400,6 +345,81 @@ export class GoogleDriveStorage {
400
345
  throw error;
401
346
  }
402
347
  }
348
+ async findFileByName(name) {
349
+ // find the file named under Credentials folder
350
+ const rootFolders = await this.findFolders();
351
+ const credentialsFolderId = rootFolders.find((f) => f.name === 'Credentials')?.id;
352
+ if (!credentialsFolderId)
353
+ throw new Error('Credentials folder not found');
354
+ const files = await this.searchFiles(`'${credentialsFolderId}' in parents and name='${name}'`);
355
+ return files[0];
356
+ }
357
+ async findFilesUnderFolder(folderId) {
358
+ if (!folderId)
359
+ throw new Error('Folder ID is required');
360
+ console.log('🚀 ~ GoogleDriveStorage ~ findFilesUnderFolder ~ folderId', folderId);
361
+ const files = await this.searchFiles(`'${folderId}' in parents`);
362
+ return files;
363
+ }
364
+ async updateFileData(fileId, data) {
365
+ const fileMetadata = {
366
+ name: data.fileName,
367
+ mimeType: data.mimeType,
368
+ };
369
+ let uploadUrl = `https://www.googleapis.com/upload/drive/v3/files/${fileId}?uploadType=multipart`;
370
+ const formData = new FormData();
371
+ formData.append('metadata', new Blob([JSON.stringify(fileMetadata)], { type: 'application/json' }));
372
+ formData.append('file', new Blob([data.body], { type: fileMetadata.mimeType }));
373
+ const updatedFile = await this.fetcher({
374
+ method: 'PATCH',
375
+ headers: {},
376
+ body: JSON.stringify(formData),
377
+ url: `${uploadUrl}&fields=id,parents`,
378
+ });
379
+ console.log('File updated:', updatedFile);
380
+ return updatedFile;
381
+ }
382
+ async getFileParents(fileId) {
383
+ console.log('🚀 ~ GoogleDriveStorage ~ getFileParents ~ fileId', fileId);
384
+ const file = await this.fetcher({
385
+ method: 'GET',
386
+ headers: {},
387
+ url: `https://www.googleapis.com/drive/v3/files/${fileId}?fields=parents`,
388
+ });
389
+ return file.parents;
390
+ }
391
+ async updateRelationsFile({ relationsFileId, recommendationFileId }) {
392
+ const relationsFileContent = await this.retrieve(relationsFileId);
393
+ const relationsData = relationsFileContent.data;
394
+ relationsData.recommendations.push(recommendationFileId);
395
+ const updatedContent = JSON.stringify(relationsData);
396
+ const updateResponse = await this.fetcher({
397
+ method: 'PATCH',
398
+ headers: {
399
+ 'Content-Type': 'application/json',
400
+ },
401
+ body: updatedContent,
402
+ url: `https://www.googleapis.com/upload/drive/v3/files/${relationsFileId}?uploadType=media`,
403
+ });
404
+ console.log('🚀 ~ GoogleDriveStorage ~ updateRelationsFile ~ updateResponse:', updateResponse);
405
+ return updateResponse;
406
+ }
407
+ async createRelationsFile({ vcFolderId }) {
408
+ const files = await this.findFilesUnderFolder(vcFolderId);
409
+ const vcFile = files.find((file) => file.name === 'VC');
410
+ const relationsFile = await this.saveFile({
411
+ data: {
412
+ fileName: 'RELATIONS',
413
+ mimeType: 'application/json',
414
+ body: JSON.stringify({
415
+ vc_id: vcFile.id,
416
+ recommendations: [],
417
+ }),
418
+ },
419
+ folderId: vcFolderId,
420
+ });
421
+ return relationsFile;
422
+ }
403
423
  /**
404
424
  * Delete file by id
405
425
  * @param id
@@ -1,4 +1,11 @@
1
- import { DidDocument, KeyPair, VerifiableCredential } from '../../types/credential.js';
1
+ import { DidDocument, KeyPair, FormDataI, RecommendationFormDataI, VerifiableCredential } from '../../types/credential.js';
2
+ interface SignPropsI {
3
+ data: FormDataI | RecommendationFormDataI;
4
+ type: 'VC' | 'RECOMMENDATION';
5
+ keyPair: KeyPair;
6
+ issuerId: string;
7
+ vcFileId?: string;
8
+ }
2
9
  /**
3
10
  * Class representing the Credential Engine.
4
11
  * @class CredentialEngine
@@ -12,7 +19,6 @@ import { DidDocument, KeyPair, VerifiableCredential } from '../../types/credenti
12
19
  * @method signPresentation - Sign a Verifiable Presentation (VP).
13
20
  */
14
21
  export declare class CredentialEngine {
15
- private uuid;
16
22
  private storage;
17
23
  private keyPair;
18
24
  constructor(accessToken: string);
@@ -28,6 +34,10 @@ export declare class CredentialEngine {
28
34
  didDocument: DidDocument;
29
35
  keyPair: KeyPair;
30
36
  }>;
37
+ findKeysAndDIDs(): Promise<{
38
+ didDocument: any;
39
+ keyPair: any;
40
+ }>;
31
41
  /**
32
42
  * Create a new DID with user metamask address as controller
33
43
  * @param walletrAddress
@@ -43,10 +53,12 @@ export declare class CredentialEngine {
43
53
  * @param {'VC' | 'RECOMMENDATION'} type - The signature type.
44
54
  * @param {string} issuerId - The ID of the issuer [currently we put it as the did id]
45
55
  * @param {KeyPair} keyPair - The key pair to use for signing.
56
+ * @param {FormDataI | RecommendationFormDataI} formData - The form data to include in the VC.
57
+ * @param {string} VCId - The ID of the credential when the type is RECOMMENDATION
46
58
  * @returns {Promise<Credential>} The signed VC.
47
59
  * @throws Will throw an error if VC signing fails.
48
60
  */
49
- signVC(formData: any, type: 'VC' | 'RECOMMENDATION', keyPair: KeyPair, issuerId: string): Promise<any>;
61
+ signVC({ data, type, keyPair, issuerId, vcFileId }: SignPropsI): Promise<any>;
50
62
  /**
51
63
  * Verify a Verifiable Credential (VC)
52
64
  * @param {object} credential - The Verifiable Credential to verify.
@@ -67,3 +79,4 @@ export declare class CredentialEngine {
67
79
  */
68
80
  signPresentation(presentation: any): Promise<any>;
69
81
  }
82
+ export {};
@@ -1,4 +1,4 @@
1
- import { DataToSaveI } from '../../types';
1
+ import { DataToSaveI, FilesType } from '../../types';
2
2
  interface FileContent {
3
3
  name: string;
4
4
  content: any;
@@ -25,34 +25,23 @@ export declare class GoogleDriveStorage {
25
25
  private fetcher;
26
26
  private searchFiles;
27
27
  createFolder(folderName: string, parentFolderId?: string): Promise<string>;
28
- save(data: DataToSaveI, folderId: string): Promise<{
29
- id: string;
30
- } | null>;
31
- /**
32
- * Add comment to VC
33
- * @param fileId - th id of VC file
34
- * @returns
35
- */
36
- addCommentToFile(vcFileId: string, recommendationFileId: string): Promise<any>;
37
- /**
38
- * Add commenter role to a file
39
- * @param fileId
40
- * @returns
41
- */
42
- addCommenterRoleToFile(fileId: string): Promise<any>;
28
+ saveFile({ data, folderId }: {
29
+ data: DataToSaveI;
30
+ folderId: string;
31
+ }): Promise<any>;
43
32
  /**
44
33
  * Get file from google drive by id
45
34
  * @param id
46
35
  * @returns file content
47
36
  */
48
37
  retrieve(id: string): Promise<{
49
- id: string;
50
38
  name: string;
51
39
  data: any;
40
+ id: string;
52
41
  } | null>;
53
42
  /**
54
43
  * Get folder by folderId, if folderId == null you will have them all
55
- * @param id [Optional]
44
+ * @param folderId [Optional]
56
45
  * @returns
57
46
  */
58
47
  findFolders: (folderId?: string) => Promise<any[]>;
@@ -62,7 +51,6 @@ export declare class GoogleDriveStorage {
62
51
  * @returns last file content from folder by folderId
63
52
  */
64
53
  findLastFile: (folderId: string) => Promise<any>;
65
- getFileComments(fileId: string): Promise<any>;
66
54
  /**
67
55
  * Get all files content for the specified type ('KEYPAIRs' | 'VCs' | 'SESSIONs' | 'DIDs' | 'RECOMMENDATIONs')
68
56
  * @param type
@@ -76,6 +64,17 @@ export declare class GoogleDriveStorage {
76
64
  * @returns The updated file metadata, including the new name
77
65
  */
78
66
  updateFileName(fileId: string, newFileName: string): Promise<any>;
67
+ findFileByName(name: FilesType): Promise<any>;
68
+ findFilesUnderFolder(folderId: string): Promise<any[]>;
69
+ updateFileData(fileId: string, data: DataToSaveI): Promise<any>;
70
+ getFileParents(fileId: string): Promise<any>;
71
+ updateRelationsFile({ relationsFileId, recommendationFileId }: {
72
+ relationsFileId: string;
73
+ recommendationFileId: string;
74
+ }): Promise<any>;
75
+ createRelationsFile({ vcFolderId }: {
76
+ vcFolderId: string;
77
+ }): Promise<any>;
79
78
  /**
80
79
  * Delete file by id
81
80
  * @param id
@@ -1,20 +1,39 @@
1
1
  import { KeyPair, DidDocument, FormDataI, RecommendationCredential, Credential, RecommendationFormDataI, VerifiableCredential } from '../../types/credential';
2
2
  /**
3
3
  * Create a DID document using the provided key pair.
4
- * @param {object} keyPair - The key pair used to create the DID document.
5
- * @returns {Promise<object>} The created DID document.
4
+ * @param {KeyPair} keyPair - The key pair used to create the DID document.
5
+ * @returns {Promise<DidDocument>} The created DID document.
6
+ * @throws Will throw an error if the DID document generation fails.
6
7
  */
7
8
  export declare const generateDIDSchema: (keyPair: KeyPair) => Promise<DidDocument>;
8
9
  /**
9
- * Generate an unsigned Verifiable Credential (VC)
10
- * @param {object} formData - The form data to include in the VC.
11
- * @param {string} issuerDid - The DID of the issuer.
12
- * @param {string} vcId - The ID of the credential generated by uuidv4()
13
- * @returns {Promise<object>} The created unsigned VC.
14
- * @throws Will throw an error if unsigned VC creation fails.
10
+ * Generate an unsigned Verifiable Credential (VC).
11
+ * Hashes the credential to create a unique ID.
12
+ * @param {FormDataI} params
13
+ * @param {string} params.FormData - The form dta to include in the VC.
14
+ * @param {string} params.issuerDid - The DID of the issuer.
15
+ * @returns {Credential} The created unsigned VC.
16
+ * @throws Will throw an error if the VC creation fails or if issuance date exceeds expiration date.
15
17
  */
16
- export declare function generateUnsignedVC(formData: FormDataI, issuerDid: string, vcId: string): Credential;
17
- export declare function generateUnsignedRecommendation(recommendation: RecommendationFormDataI, issuerDid: string): RecommendationCredential;
18
+ export declare function generateUnsignedVC({ formData, issuerDid }: {
19
+ formData: FormDataI;
20
+ issuerDid: string;
21
+ }): Credential;
22
+ /**
23
+ * Generate an unsigned Recommendation Credential.
24
+ * Uses the hash of the VC to set the `id` for consistency.
25
+ * @param {object} params
26
+ * @param {VerifiableCredential} params.vc - The Verifiable Credential to base the recommendation on.
27
+ * @param {RecommendationFormDataI} params.recommendation - The recommendation form data.
28
+ * @param {string} params.issuerDid - The DID of the issuer.
29
+ * @returns {RecommendationCredential} The created unsigned Recommendation Credential.
30
+ * @throws Will throw an error if the recommendation creation fails or if issuance date exceeds expiration date.
31
+ */
32
+ export declare function generateUnsignedRecommendation({ vc, recommendation, issuerDid, }: {
33
+ vc: any;
34
+ recommendation: RecommendationFormDataI;
35
+ issuerDid: string;
36
+ }): RecommendationCredential;
18
37
  /**
19
38
  * Extracts the keypair from a Verifiable Credential
20
39
  * @param {Object} credential - The signed Verifiable Credential
@@ -1,15 +1,32 @@
1
1
  import { GoogleDriveStorage } from '../models/GoogleDriveStorage.js';
2
+ export type FileType = 'VC' | 'DID' | 'SESSION' | 'RECOMMENDATION' | 'KEYPAIR';
3
+ interface SaveToGooglePropsI {
4
+ storage: GoogleDriveStorage;
5
+ data: any;
6
+ type: FileType;
7
+ vcId?: string;
8
+ }
9
+ export declare const getVCWithRecommendations: ({ vcId, storage }: {
10
+ vcId: string;
11
+ storage: GoogleDriveStorage;
12
+ }) => Promise<{
13
+ vc: {
14
+ name: string;
15
+ data: any;
16
+ id: string;
17
+ };
18
+ recommendations: any[];
19
+ relationsFileId: any;
20
+ }>;
2
21
  /**
3
- * keyFile name = {uuid}-type-timestamp // we need that
4
- * vc.id = urn-uuid-{uuid} // we got that
5
22
  * Save data to Google Drive in the specified folder type.
6
23
  * @param {object} data - The data to save.
7
- * @param {'VC' | 'DID' | 'SESSION' | 'RECOMMENDATION' | 'KEYPAIR'} type - The type of data being saved.
24
+ * @param {FileType} data.type - The type of data being saved.
8
25
  * @returns {Promise<object>} - The file object saved to Google Drive.
9
- * @param {string} uuid - Optional unique identifier for the VC.
26
+ * @param {string} data.vcId - Optional unique identifier for the VC to link the recommendations.
10
27
  * @throws Will throw an error if the save operation fails.
11
28
  */
12
- export declare function saveToGoogleDrive(storage: GoogleDriveStorage, data: any, type: 'VC' | 'DID' | 'SESSION' | 'RECOMMENDATION' | 'KEYPAIR', uuid?: string): Promise<object>;
29
+ export declare function saveToGoogleDrive({ storage, data, type }: SaveToGooglePropsI): Promise<any>;
13
30
  /**
14
31
  * Upload an image to Google Drive in the Credentials/MEDIAs folder.
15
32
  * @param {GoogleDriveStorage} storage - The GoogleDriveStorage instance.
@@ -22,3 +39,4 @@ export declare function uploadImageToGoogleDrive(storage: GoogleDriveStorage, im
22
39
  }>;
23
40
  export declare function generateViewLink(fileId: string): string;
24
41
  export declare function extractGoogleDriveFileId(url: string): string | null;
42
+ export {};
@@ -1,14 +1,28 @@
1
1
  import { Ed25519VerificationKey2020 } from '@digitalbazaar/ed25519-verification-key-2020';
2
+ import crypto from 'crypto';
2
3
  import { v4 as uuidv4 } from 'uuid';
4
+ /**
5
+ * Utility function to generate a hashed ID for a credential.
6
+ * Excludes the `id` field when hashing.
7
+ * @param {object} credential - The credential object to hash.
8
+ * @returns {string} The generated hashed ID.
9
+ */
10
+ function generateHashedId(credential) {
11
+ // Exclude the `id` field from the hash
12
+ const credentialWithoutId = { ...credential, id: undefined };
13
+ const serialized = JSON.stringify(credentialWithoutId);
14
+ return crypto.createHash('sha256').update(serialized).digest('hex');
15
+ }
3
16
  /**
4
17
  * Create a DID document using the provided key pair.
5
- * @param {object} keyPair - The key pair used to create the DID document.
6
- * @returns {Promise<object>} The created DID document.
18
+ * @param {KeyPair} keyPair - The key pair used to create the DID document.
19
+ * @returns {Promise<DidDocument>} The created DID document.
20
+ * @throws Will throw an error if the DID document generation fails.
7
21
  */
8
22
  export const generateDIDSchema = async (keyPair) => {
9
23
  try {
10
24
  const DID = keyPair.controller;
11
- const didDocument = {
25
+ return {
12
26
  '@context': ['https://www.w3.org/ns/did/v1'],
13
27
  id: DID,
14
28
  publicKey: [
@@ -32,7 +46,6 @@ export const generateDIDSchema = async (keyPair) => {
32
46
  },
33
47
  ],
34
48
  };
35
- return didDocument;
36
49
  }
37
50
  catch (error) {
38
51
  console.error('Error creating DID document:', error);
@@ -40,117 +53,126 @@ export const generateDIDSchema = async (keyPair) => {
40
53
  }
41
54
  };
42
55
  /**
43
- * Generate an unsigned Verifiable Credential (VC)
44
- * @param {object} formData - The form data to include in the VC.
45
- * @param {string} issuerDid - The DID of the issuer.
46
- * @param {string} vcId - The ID of the credential generated by uuidv4()
47
- * @returns {Promise<object>} The created unsigned VC.
48
- * @throws Will throw an error if unsigned VC creation fails.
56
+ * Generate an unsigned Verifiable Credential (VC).
57
+ * Hashes the credential to create a unique ID.
58
+ * @param {FormDataI} params
59
+ * @param {string} params.FormData - The form dta to include in the VC.
60
+ * @param {string} params.issuerDid - The DID of the issuer.
61
+ * @returns {Credential} The created unsigned VC.
62
+ * @throws Will throw an error if the VC creation fails or if issuance date exceeds expiration date.
49
63
  */
50
- export function generateUnsignedVC(formData, issuerDid, vcId) {
51
- try {
52
- const issuanceDate = new Date().toISOString();
53
- if (issuanceDate > formData.expirationDate)
54
- throw Error('issuanceDate cannot be after expirationDate');
55
- const unsignedCredential = {
56
- '@context': [
57
- 'https://www.w3.org/2018/credentials/v1',
58
- 'https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.3.json',
59
- {
60
- duration: 'https://schema.org/duration',
61
- fullName: 'https://schema.org/name',
62
- portfolio: 'https://schema.org/portfolio',
63
- evidenceLink: 'https://schema.org/evidenceLink',
64
- evidenceDescription: 'https://schema.org/evidenceDescription',
65
- credentialType: 'https://schema.org/credentialType',
66
- },
67
- ],
68
- id: `urn:uuid:${vcId}`, //! i want this uuid to be in the condition where vcId part == keypair file name which is like this `${uuid ? uuid + '_' : ''}${type}_${timestamp}.json`,
69
- type: ['VerifiableCredential', 'OpenBadgeCredential'],
70
- issuer: {
71
- id: issuerDid,
72
- type: ['Profile'],
64
+ export function generateUnsignedVC({ formData, issuerDid }) {
65
+ const issuanceDate = new Date().toISOString();
66
+ if (issuanceDate > formData.expirationDate)
67
+ throw new Error('issuanceDate cannot be after expirationDate');
68
+ const unsignedCredential = {
69
+ '@context': [
70
+ 'https://www.w3.org/2018/credentials/v1',
71
+ 'https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.3.json',
72
+ {
73
+ duration: 'https://schema.org/duration',
74
+ fullName: 'https://schema.org/name',
75
+ portfolio: 'https://schema.org/portfolio',
76
+ evidenceLink: 'https://schema.org/evidenceLink',
77
+ evidenceDescription: 'https://schema.org/evidenceDescription',
78
+ credentialType: 'https://schema.org/credentialType',
73
79
  },
74
- issuanceDate,
75
- expirationDate: formData.expirationDate,
76
- credentialSubject: {
77
- type: ['AchievementSubject'],
78
- name: formData.fullName,
79
- portfolio: formData.portfolio,
80
- evidenceLink: formData.evidenceLink,
81
- evidenceDescription: formData.achievementDescription,
82
- duration: formData.duration,
83
- credentialType: formData.credentialType,
84
- achievement: [
85
- {
86
- id: `urn:uuid:${uuidv4()}`,
87
- type: ['Achievement'],
88
- criteria: {
89
- narrative: formData.criteriaNarrative,
90
- },
91
- description: formData.achievementDescription,
92
- name: formData.achievementName,
93
- image: formData.evidenceLink
94
- ? {
95
- id: formData.evidenceLink,
96
- type: 'Image',
97
- }
98
- : undefined,
99
- },
100
- ],
101
- },
102
- };
103
- return unsignedCredential;
104
- }
105
- catch (error) {
106
- console.error('Error creating unsigned VC', error);
107
- throw error;
108
- }
109
- }
110
- export function generateUnsignedRecommendation(recommendation, issuerDid) {
111
- try {
112
- const issuanceDate = new Date().toISOString();
113
- if (issuanceDate > recommendation.expirationDate)
114
- throw Error('issuanceDate cannot be after expirationDate');
115
- const unsignedRecommendation = {
116
- '@context': [
117
- 'https://www.w3.org/2018/credentials/v1',
118
- 'https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.3.json',
80
+ ],
81
+ id: '', // Will be set after hashing
82
+ type: ['VerifiableCredential', 'OpenBadgeCredential'],
83
+ issuer: {
84
+ id: issuerDid,
85
+ type: ['Profile'],
86
+ },
87
+ issuanceDate,
88
+ expirationDate: formData.expirationDate,
89
+ credentialSubject: {
90
+ type: ['AchievementSubject'],
91
+ name: formData.fullName,
92
+ portfolio: formData.portfolio.map((item) => ({
93
+ '@type': 'schema:CreativeWork',
94
+ name: item.name,
95
+ url: item.url,
96
+ })),
97
+ evidenceLink: formData.evidenceLink,
98
+ evidenceDescription: formData.achievementDescription,
99
+ duration: formData.duration,
100
+ credentialType: formData.credentialType,
101
+ achievement: [
119
102
  {
120
- howKnow: 'https://schema.org/howKnow',
121
- recommendationText: 'https://schema.org/recommendationText',
122
- qualifications: 'https://schema.org/qualifications',
123
- explainAnswer: 'https://schema.org/explainAnswer',
124
- portfolio: 'https://schema.org/portfolio',
103
+ id: `urn:uuid:${uuidv4()}`,
104
+ type: ['Achievement'],
105
+ criteria: {
106
+ narrative: formData.criteriaNarrative,
107
+ },
108
+ description: formData.achievementDescription,
109
+ name: formData.achievementName,
110
+ image: formData.evidenceLink
111
+ ? {
112
+ id: formData.evidenceLink,
113
+ type: 'Image',
114
+ }
115
+ : undefined,
125
116
  },
126
117
  ],
127
- id: `urn:uuid:${uuidv4()}`, // Unique identifier for the recommendation
128
- type: ['VerifiableCredential', 'https://schema.org/RecommendationCredential'], // Use a fully qualified URI for 'RecommendationCredential'
129
- issuer: {
130
- id: issuerDid,
131
- type: ['Profile'],
132
- },
133
- issuanceDate,
134
- expirationDate: recommendation.expirationDate,
135
- credentialSubject: {
136
- name: recommendation.fullName,
137
- howKnow: recommendation.howKnow,
138
- recommendationText: recommendation.recommendationText,
139
- qualifications: recommendation.qualifications,
140
- explainAnswer: recommendation.explainAnswer,
141
- portfolio: recommendation.portfolio.map((item) => ({
142
- name: item.name,
143
- url: item.url,
144
- })),
118
+ },
119
+ };
120
+ // Generate the hashed ID
121
+ unsignedCredential.id = 'urn:' + generateHashedId(unsignedCredential);
122
+ return unsignedCredential;
123
+ }
124
+ /**
125
+ * Generate an unsigned Recommendation Credential.
126
+ * Uses the hash of the VC to set the `id` for consistency.
127
+ * @param {object} params
128
+ * @param {VerifiableCredential} params.vc - The Verifiable Credential to base the recommendation on.
129
+ * @param {RecommendationFormDataI} params.recommendation - The recommendation form data.
130
+ * @param {string} params.issuerDid - The DID of the issuer.
131
+ * @returns {RecommendationCredential} The created unsigned Recommendation Credential.
132
+ * @throws Will throw an error if the recommendation creation fails or if issuance date exceeds expiration date.
133
+ */
134
+ export function generateUnsignedRecommendation({ vc, recommendation, issuerDid, }) {
135
+ console.log('🚀 ~ vc.id:', vc.id);
136
+ console.log('🚀 ~ vc:', vc);
137
+ console.log('🚀 ~ recommendation:', recommendation);
138
+ const issuanceDate = new Date().toISOString();
139
+ if (issuanceDate > recommendation.expirationDate)
140
+ throw new Error('issuanceDate cannot be after expirationDate');
141
+ const unsignedRecommendation = {
142
+ '@context': [
143
+ 'https://www.w3.org/2018/credentials/v1',
144
+ 'https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.3.json',
145
+ {
146
+ howKnow: 'https://schema.org/howKnow',
147
+ recommendationText: 'https://schema.org/recommendationText',
148
+ qualifications: 'https://schema.org/qualifications',
149
+ explainAnswer: 'https://schema.org/explainAnswer',
150
+ portfolio: 'https://schema.org/portfolio',
145
151
  },
146
- };
147
- console.log('Successfully created Unsigned Recommendation', unsignedRecommendation);
148
- return unsignedRecommendation;
149
- }
150
- catch (error) {
151
- console.error('Error creating unsigned recommendation', error);
152
- throw error;
153
- }
152
+ ],
153
+ id: '', // Will be set after hashing VC
154
+ type: ['VerifiableCredential', 'https://schema.org/RecommendationCredential'],
155
+ issuer: {
156
+ id: issuerDid,
157
+ type: ['Profile'],
158
+ },
159
+ issuanceDate,
160
+ expirationDate: recommendation.expirationDate,
161
+ credentialSubject: {
162
+ name: recommendation.fullName,
163
+ howKnow: recommendation.howKnow,
164
+ 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
+ })),
171
+ },
172
+ };
173
+ // Use the VC's hashed ID for the Recommendation's ID
174
+ unsignedRecommendation.id = vc.data.id;
175
+ return unsignedRecommendation;
154
176
  }
155
177
  /**
156
178
  * Extracts the keypair from a Verifiable Credential
@@ -1,18 +1,29 @@
1
+ export const getVCWithRecommendations = async ({ vcId, storage }) => {
2
+ const vcFolderId = await storage.getFileParents(vcId);
3
+ const files = await storage.findFilesUnderFolder(vcFolderId);
4
+ const relationsFile = files.find((f) => f.name === 'RELATIONS');
5
+ const relationsContent = await storage.retrieve(relationsFile.id);
6
+ const relationsData = relationsContent.data;
7
+ const [vcFileId, recommendationIds] = [relationsData.vc_id, relationsData.recommendations];
8
+ const vc = await storage.retrieve(vcFileId);
9
+ const recommendations = await Promise.all(recommendationIds.map(async (rec) => {
10
+ const recFile = await storage.retrieve(rec);
11
+ return recFile;
12
+ }));
13
+ return { vc: vc, recommendations, relationsFileId: relationsFile.id };
14
+ };
1
15
  /**
2
- * keyFile name = {uuid}-type-timestamp // we need that
3
- * vc.id = urn-uuid-{uuid} // we got that
4
16
  * Save data to Google Drive in the specified folder type.
5
17
  * @param {object} data - The data to save.
6
- * @param {'VC' | 'DID' | 'SESSION' | 'RECOMMENDATION' | 'KEYPAIR'} type - The type of data being saved.
18
+ * @param {FileType} data.type - The type of data being saved.
7
19
  * @returns {Promise<object>} - The file object saved to Google Drive.
8
- * @param {string} uuid - Optional unique identifier for the VC.
20
+ * @param {string} data.vcId - Optional unique identifier for the VC to link the recommendations.
9
21
  * @throws Will throw an error if the save operation fails.
10
22
  */
11
- export async function saveToGoogleDrive(storage, data, type, uuid) {
23
+ export async function saveToGoogleDrive({ storage, data, type }) {
12
24
  try {
13
- const timestamp = Date.now();
14
25
  const fileData = {
15
- fileName: `${uuid ? uuid + '_' : ''}${type}_${timestamp}.json`,
26
+ fileName: type === 'VC' ? 'VC' : `${type}-${Date.now()}`,
16
27
  mimeType: 'application/json',
17
28
  body: JSON.stringify(data),
18
29
  };
@@ -24,34 +35,31 @@ export async function saveToGoogleDrive(storage, data, type, uuid) {
24
35
  let credentialsFolderId;
25
36
  if (!credentialsFolder) {
26
37
  credentialsFolderId = await storage.createFolder('Credentials');
27
- console.log('Created Credentials folder with ID:', credentialsFolderId);
28
38
  }
29
39
  else {
30
40
  credentialsFolderId = credentialsFolder.id;
31
- console.log('Found Credentials folder with ID:', credentialsFolderId);
32
41
  }
33
42
  // Get subfolders within the "Credentials" folder
34
43
  const subfolders = await storage.findFolders(credentialsFolderId);
35
- console.log(`Subfolders in Credentials (ID: ${credentialsFolderId}):`, subfolders);
36
44
  // Find or create the specific subfolder (DIDs or VCs)
37
45
  let typeFolder = subfolders.find((f) => f.name === `${type}s`);
38
46
  let typeFolderId;
39
47
  if (!typeFolder) {
40
48
  typeFolderId = await storage.createFolder(`${type}s`, credentialsFolderId);
41
- console.log(`Created ${type}s folder with ID:`, typeFolderId);
42
49
  }
43
50
  else {
44
51
  typeFolderId = typeFolder.id;
45
- console.log(`Found ${type} files:`, await storage.findLastFile(typeFolderId));
46
- console.log(`Found ${type}s folder with ID:`, typeFolderId);
52
+ }
53
+ if (type === 'VC') {
54
+ // save the data in Credentials/VCs/VC-timestamp/vc.json
55
+ const vcFolderId = await storage.createFolder(`${fileData.fileName}-${Date.now()}`, typeFolderId);
56
+ const file = await storage.saveFile({ data: fileData, folderId: vcFolderId });
57
+ console.log(`File uploaded: ${file?.id} under ${fileData.fileName} folder in VCs folder`);
58
+ return file;
47
59
  }
48
60
  // Save the file in the specific subfolder
49
- const file = await storage.save(fileData, typeFolderId);
61
+ const file = await storage.saveFile({ data: fileData, folderId: typeFolderId });
50
62
  console.log(`File uploaded: ${file?.id} under ${type}s with ID ${typeFolderId} folder in Credentials folder`);
51
- if (file && file.id) {
52
- console.log('Sharing file with second user...');
53
- await storage.addCommenterRoleToFile(file.id);
54
- }
55
63
  return file;
56
64
  }
57
65
  catch (error) {
@@ -92,8 +100,11 @@ export async function uploadImageToGoogleDrive(storage, imageFile) {
92
100
  mimeType: imageFile.type,
93
101
  body: imageFile,
94
102
  };
95
- // Save the image in the "MEDIAs" folder
96
- const uploadedImage = await storage.save(imageData, mediasFolderId);
103
+ // SaveFile the image in the "MEDIAs" folder
104
+ const uploadedImage = await storage.saveFile({
105
+ data: imageData,
106
+ folderId: mediasFolderId,
107
+ });
97
108
  console.log(`Image uploaded: ${uploadedImage?.id} to MEDIAs folder in Credentials`);
98
109
  return uploadedImage;
99
110
  }
@@ -1,6 +1,3 @@
1
- import { CredentialEngine } from '../models/CredentialEngine.js';
2
- import { GoogleDriveStorage } from '../models/GoogleDriveStorage.js';
3
- import { extractGoogleDriveFileId } from './google.js';
4
1
  /**
5
2
  * Create and sign a Verifiable Presentation (VP) from a given Verifiable Credential (VC) file and any associated recommendations.
6
3
  * @param {string} accessTokens - The access tokens for the user.
@@ -14,32 +11,32 @@ export const createAndSignVerifiablePresentation = async (accessTokens, vcFileId
14
11
  return null;
15
12
  }
16
13
  try {
17
- const storage = new GoogleDriveStorage(accessTokens);
18
- const engine = new CredentialEngine(accessTokens);
19
- // Fetch Verifiable Credential (VC)
20
- const verifiableCredential = await storage.retrieve(vcFileId);
21
- if (!verifiableCredential) {
22
- throw new Error('Verifiable Credential not found.');
23
- }
24
- // Fetch VC comments (potential recommendations)
25
- const verifiableCredentialComments = await storage.getFileComments(vcFileId);
26
- let recommendations = [];
27
- // Extract recommendations from comments if present
28
- if (verifiableCredentialComments.length > 0) {
29
- for (const comment of verifiableCredentialComments) {
30
- console.log('🚀 ~ createAndSignVerifiablePresentation ~ comment', comment);
31
- const recommendationFile = await storage.retrieve(extractGoogleDriveFileId(comment.content));
32
- console.log('🚀 ~ createAndSignVerifiablePresentation ~ recommendationFile', recommendationFile);
33
- if (recommendationFile) {
34
- recommendations.push(recommendationFile);
35
- }
36
- }
37
- }
38
- // Create Verifiable Presentation (VP) with the retrieved VC
39
- const presentation = await engine.createPresentation([verifiableCredential.data, ...recommendations]); //! do not edit the array order!!
40
- // Use the key pair to sign the presentation
41
- const signedPresentation = await engine.signPresentation(presentation);
42
- return { signedPresentation };
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 {};
43
40
  }
44
41
  catch (error) {
45
42
  console.error('Error during Verifiable Presentation creation and signing:', error);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cooperation/vc-storage",
3
3
  "type": "module",
4
- "version": "1.0.11",
4
+ "version": "1.0.13",
5
5
  "description": "Sign and store your verifiable credentials.",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/types/index.d.ts",
@@ -21,6 +21,7 @@
21
21
  "@digitalbazaar/ed25519-verification-key-2020": "^4.1.0",
22
22
  "@digitalbazaar/vc": "^6.3.0",
23
23
  "ethers": "^6.13.2",
24
+ "ts-node": "^10.9.2",
24
25
  "tsc": "^2.0.4",
25
26
  "uuid": "^10.0.0"
26
27
  },
@@ -1,10 +0,0 @@
1
- import { GoogleDriveStorage } from '../models/GoogleDriveStorage.js';
2
- /**
3
- * Save data to Google Drive in the specified folder type.
4
- * @param {object} data - The data to save.
5
- * @param {'VC' | 'DID' | 'UnsignedVC'} type - The type of data being saved.
6
- * @returns {Promise<object>} - The file object saved to Google Drive.
7
- * @throws Will throw an error if the save operation fails.
8
- */
9
- export declare function saveToGoogleDrive(storage: GoogleDriveStorage, data: any, type: 'VC' | 'DID' | 'SESSION' | 'RECOMMENDATION'): Promise<object>;
10
- export declare function generateViewLink(fileId: string): string;
@@ -1,65 +0,0 @@
1
- /**
2
- * Save data to Google Drive in the specified folder type.
3
- * @param {object} data - The data to save.
4
- * @param {'VC' | 'DID' | 'UnsignedVC'} type - The type of data being saved.
5
- * @returns {Promise<object>} - The file object saved to Google Drive.
6
- * @throws Will throw an error if the save operation fails.
7
- */
8
- export async function saveToGoogleDrive(storage, data, type) {
9
- try {
10
- const timestamp = Date.now();
11
- const fileData = {
12
- fileName: `${type}-${timestamp}.json`,
13
- mimeType: 'application/json',
14
- body: JSON.stringify(data),
15
- };
16
- // Get all root folders
17
- const rootFolders = await storage.findFolders();
18
- console.log('Root folders:', rootFolders);
19
- // Find or create the "Credentials" folder
20
- let credentialsFolder = rootFolders.find((f) => f.name === 'Credentials');
21
- let credentialsFolderId;
22
- if (!credentialsFolder) {
23
- credentialsFolderId = await storage.createFolder('Credentials');
24
- console.log('Created Credentials folder with ID:', credentialsFolderId);
25
- }
26
- else {
27
- credentialsFolderId = credentialsFolder.id;
28
- console.log('Found Credentials folder with ID:', credentialsFolderId);
29
- }
30
- // Get subfolders within the "Credentials" folder
31
- const subfolders = await storage.findFolders(credentialsFolderId);
32
- console.log(`Subfolders in Credentials (ID: ${credentialsFolderId}):`, subfolders);
33
- // Find or create the specific subfolder (DIDs or VCs)
34
- let typeFolder = subfolders.find((f) => f.name === `${type}s`);
35
- let typeFolderId;
36
- if (!typeFolder) {
37
- typeFolderId = await storage.createFolder(`${type}s`, credentialsFolderId);
38
- console.log(`Created ${type}s folder with ID:`, typeFolderId);
39
- }
40
- else {
41
- typeFolderId = typeFolder.id;
42
- console.log(`Found ${type} files:`, await storage.findLastFile(typeFolderId));
43
- console.log(`Found ${type}s folder with ID:`, typeFolderId);
44
- }
45
- // Save the file in the specific subfolder
46
- const file = await storage.save(fileData, typeFolderId);
47
- console.log(`File uploaded: ${file?.id} under ${type}s with ID ${typeFolderId} folder in Credentials folder`);
48
- if (file && file.id) {
49
- console.log('Sharing file with second user...');
50
- await storage.addCommenterRoleToFile(file.id);
51
- }
52
- return file;
53
- }
54
- catch (error) {
55
- console.error('Error saving to Google Drive:', error);
56
- throw error;
57
- }
58
- }
59
- export function generateViewLink(fileId) {
60
- if (!fileId) {
61
- throw new Error('File ID is required to generate a view link.');
62
- }
63
- // Construct the view URL based on the file ID
64
- return `https://drive.google.com/file/d/${fileId}/view`;
65
- }