@cooperation/vc-storage 1.0.11 → 1.0.13

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- }