@cooperation/vc-storage 1.0.1 → 1.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,172 @@
1
+ import { Ed25519VerificationKey2020 } from '@digitalbazaar/ed25519-verification-key-2020';
2
+ import { v4 as uuidv4 } from 'uuid';
3
+ /**
4
+ * 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.
7
+ */
8
+ export const generateDIDSchema = async (keyPair) => {
9
+ try {
10
+ const DID = keyPair.controller;
11
+ const didDocument = {
12
+ '@context': ['https://www.w3.org/ns/did/v1'],
13
+ id: DID,
14
+ publicKey: [
15
+ {
16
+ id: keyPair.id,
17
+ type: 'Ed25519VerificationKey2020',
18
+ controller: DID,
19
+ publicKeyMultibase: keyPair.publicKeyMultibase,
20
+ },
21
+ ],
22
+ authentication: [keyPair.id],
23
+ assertionMethod: [keyPair.id],
24
+ capabilityDelegation: [keyPair.id],
25
+ capabilityInvocation: [keyPair.id],
26
+ keyAgreement: [
27
+ {
28
+ id: `${keyPair.id}-keyAgreement`,
29
+ type: 'X25519KeyAgreementKey2020',
30
+ controller: DID,
31
+ publicKeyMultibase: keyPair.publicKeyMultibase,
32
+ },
33
+ ],
34
+ };
35
+ return didDocument;
36
+ }
37
+ catch (error) {
38
+ console.error('Error creating DID document:', error);
39
+ throw error;
40
+ }
41
+ };
42
+ /**
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.
49
+ */
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'],
73
+ },
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',
119
+ {
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',
125
+ },
126
+ ],
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
+ })),
145
+ },
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
+ }
154
+ }
155
+ /**
156
+ * Extracts the keypair from a Verifiable Credential
157
+ * @param {Object} credential - The signed Verifiable Credential
158
+ * @returns {Ed25519VerificationKey2020} keyPair - The generated keypair object
159
+ */
160
+ export async function extractKeyPairFromCredential(credential) {
161
+ const verificationMethod = credential.proof.verificationMethod;
162
+ const issuer = credential.issuer.id;
163
+ // Example of extracting the public key from the DID fragment (verification method)
164
+ const publicKeyMultibase = verificationMethod.split('#')[1];
165
+ // Generate the keypair using Ed25519VerificationKey2020
166
+ const keyPair = await Ed25519VerificationKey2020.from({
167
+ id: verificationMethod,
168
+ controller: issuer,
169
+ publicKeyMultibase: publicKeyMultibase,
170
+ });
171
+ return keyPair;
172
+ }
@@ -0,0 +1,37 @@
1
+ import { Ed25519VerificationKey2020 } from '@digitalbazaar/ed25519-verification-key-2020';
2
+ import { driver as didKeyDriver } from '@digitalbazaar/did-method-key';
3
+ import { defaultDocumentLoader } from '@digitalbazaar/vc';
4
+ import { localOBContext, localED25519Context } from '../utils/context.js';
5
+ // Initialize the DID method key driver
6
+ const didKeyDriverInstance = didKeyDriver();
7
+ didKeyDriverInstance.use({
8
+ multibaseMultikeyHeader: 'z6Mk',
9
+ fromMultibase: Ed25519VerificationKey2020.from,
10
+ });
11
+ // Custom document loader
12
+ export const customDocumentLoader = async (url) => {
13
+ // Context map for local contexts
14
+ const contextMap = {
15
+ 'https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.3.json': localOBContext,
16
+ 'https://w3id.org/security/suites/ed25519-2020/v1': localED25519Context,
17
+ };
18
+ // Return local context if it matches the URL
19
+ if (contextMap[url]) {
20
+ return {
21
+ contextUrl: null,
22
+ documentUrl: url,
23
+ document: contextMap[url],
24
+ };
25
+ }
26
+ // Handle did:key resolution
27
+ if (url.startsWith('did:key:')) {
28
+ const didDocument = await didKeyDriverInstance.get({ did: url });
29
+ return {
30
+ contextUrl: null,
31
+ documentUrl: url,
32
+ document: didDocument,
33
+ };
34
+ }
35
+ // Fallback to the default document loader for unknown URLs
36
+ return defaultDocumentLoader(url);
37
+ };
@@ -0,0 +1,79 @@
1
+ /**
2
+ * keyFile name = {uuid}-type-timestamp // we need that
3
+ * vc.id = urn-uuid-{uuid} // we got that
4
+ * Save data to Google Drive in the specified folder type.
5
+ * @param {object} data - The data to save.
6
+ * @param {'VC' | 'DID' | 'SESSION' | 'RECOMMENDATION' | 'KEYPAIR'} type - The type of data being saved.
7
+ * @returns {Promise<object>} - The file object saved to Google Drive.
8
+ * @param {string} uuid - Optional unique identifier for the VC.
9
+ * @throws Will throw an error if the save operation fails.
10
+ */
11
+ export async function saveToGoogleDrive(storage, data, type, uuid) {
12
+ try {
13
+ const timestamp = Date.now();
14
+ const fileData = {
15
+ fileName: `${uuid ? uuid + '_' : ''}${type}_${timestamp}.json`,
16
+ mimeType: 'application/json',
17
+ body: JSON.stringify(data),
18
+ };
19
+ // Get all root folders
20
+ const rootFolders = await storage.findFolders();
21
+ console.log('Root folders:', rootFolders);
22
+ // Find or create the "Credentials" folder
23
+ let credentialsFolder = rootFolders.find((f) => f.name === 'Credentials');
24
+ let credentialsFolderId;
25
+ if (!credentialsFolder) {
26
+ credentialsFolderId = await storage.createFolder('Credentials');
27
+ console.log('Created Credentials folder with ID:', credentialsFolderId);
28
+ }
29
+ else {
30
+ credentialsFolderId = credentialsFolder.id;
31
+ console.log('Found Credentials folder with ID:', credentialsFolderId);
32
+ }
33
+ // Get subfolders within the "Credentials" folder
34
+ const subfolders = await storage.findFolders(credentialsFolderId);
35
+ console.log(`Subfolders in Credentials (ID: ${credentialsFolderId}):`, subfolders);
36
+ // Find or create the specific subfolder (DIDs or VCs)
37
+ let typeFolder = subfolders.find((f) => f.name === `${type}s`);
38
+ let typeFolderId;
39
+ if (!typeFolder) {
40
+ typeFolderId = await storage.createFolder(`${type}s`, credentialsFolderId);
41
+ console.log(`Created ${type}s folder with ID:`, typeFolderId);
42
+ }
43
+ else {
44
+ typeFolderId = typeFolder.id;
45
+ console.log(`Found ${type} files:`, await storage.findLastFile(typeFolderId));
46
+ console.log(`Found ${type}s folder with ID:`, typeFolderId);
47
+ }
48
+ // Save the file in the specific subfolder
49
+ const file = await storage.save(fileData, typeFolderId);
50
+ 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
+ return file;
56
+ }
57
+ catch (error) {
58
+ console.error('Error saving to Google Drive:', error);
59
+ throw error;
60
+ }
61
+ }
62
+ export function generateViewLink(fileId) {
63
+ if (!fileId) {
64
+ throw new Error('File ID is required to generate a view link.');
65
+ }
66
+ // Construct the view URL based on the file ID
67
+ return `https://drive.google.com/file/d/${fileId}/view`;
68
+ }
69
+ export function extractGoogleDriveFileId(url) {
70
+ const regex = /\/d\/([a-zA-Z0-9_-]+)\//;
71
+ const match = url.match(regex);
72
+ if (match && match[1]) {
73
+ return match[1]; // Return the file ID
74
+ }
75
+ else {
76
+ console.error('Invalid Google Drive URL: File ID not found.');
77
+ return null;
78
+ }
79
+ }
@@ -0,0 +1,48 @@
1
+ import { CredentialEngine } from '../models/CredentialEngine.js';
2
+ import { GoogleDriveStorage } from '../models/GoogleDriveStorage.js';
3
+ import { extractGoogleDriveFileId } from './google.js';
4
+ /**
5
+ * Create and sign a Verifiable Presentation (VP) from a given Verifiable Credential (VC) file and any associated recommendations.
6
+ * @param {string} accessTokens - The access tokens for the user.
7
+ * @param {string} vcFileId - The ID of the Verifiable Credential (VC) file in Google Drive.
8
+ * @returns {Promise<{ signedPresentation: object } | null>} - The signed Verifiable Presentation (VP) or null if an error occurs.
9
+ * @throws Will throw an error if the VC is not found, a matching key pair cannot be located, or any part of the signing process fails.
10
+ */
11
+ export const createAndSignVerifiablePresentation = async (accessTokens, vcFileId) => {
12
+ if (!accessTokens || !vcFileId) {
13
+ console.error('Invalid input: Access tokens and VC file ID are required.');
14
+ return null;
15
+ }
16
+ 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, ...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 };
43
+ }
44
+ catch (error) {
45
+ console.error('Error during Verifiable Presentation creation and signing:', error);
46
+ return null;
47
+ }
48
+ };
@@ -2,6 +2,7 @@
2
2
  * Save data to Google Drive in the specified folder type.
3
3
  * @param {object} data - The data to save.
4
4
  * @param {'VC' | 'DID' | 'UnsignedVC'} type - The type of data being saved.
5
+ * @returns {Promise<object>} - The file object saved to Google Drive.
5
6
  * @throws Will throw an error if the save operation fails.
6
7
  */
7
8
  export async function saveToGoogleDrive(storage, data, type) {
@@ -44,6 +45,10 @@ export async function saveToGoogleDrive(storage, data, type) {
44
45
  // Save the file in the specific subfolder
45
46
  const file = await storage.save(fileData, typeFolderId);
46
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
+ }
47
52
  return file;
48
53
  }
49
54
  catch (error) {
@@ -51,3 +56,10 @@ export async function saveToGoogleDrive(storage, data, type) {
51
56
  throw error;
52
57
  }
53
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
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cooperation/vc-storage",
3
3
  "type": "module",
4
- "version": "1.0.1",
4
+ "version": "1.0.5",
5
5
  "description": "Sign and store your verifiable credentials.",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/types/index.d.ts",
@@ -16,6 +16,8 @@
16
16
  "author": "cooperation",
17
17
  "license": "ISC",
18
18
  "dependencies": {
19
+ "@cooperation/vc-storage": "^1.0.4",
20
+ "@digitalbazaar/did-method-key": "^5.2.0",
19
21
  "@digitalbazaar/ed25519-signature-2020": "^5.3.0",
20
22
  "@digitalbazaar/ed25519-verification-key-2020": "^4.1.0",
21
23
  "@digitalbazaar/vc": "^6.3.0",
@@ -26,7 +28,8 @@
26
28
  "devDependencies": {
27
29
  "@types/fs-extra": "^11.0.4",
28
30
  "@types/jest": "^29.5.12",
29
- "typescript": "^5.5.4"
31
+ "@types/uuid": "^10.0.0",
32
+ "typescript": "^5.6.2"
30
33
  },
31
34
  "jest": {
32
35
  "preset": "ts-jest",
@@ -1,36 +0,0 @@
1
- import { Ed25519Signature2020 } from '@digitalbazaar/ed25519-signature-2020';
2
- // import { verifyCredential } from '@digitalbazaar/vc';
3
- import { customDocumentLoader } from './CredentialEngine';
4
- export class CredentialVerifier {
5
- /**
6
- * Verify a signed Verifiable Credential (VC)
7
- * @param {object} signedVC - The signed VC to verify.
8
- * @returns {Promise<boolean>} True if the VC is valid, false otherwise.
9
- * @throws Will throw an error if VC verification fails.
10
- */
11
- async verifyVC(signedVC, keyPair) {
12
- const suite = new Ed25519Signature2020({
13
- key: keyPair,
14
- verificationMethod: keyPair.id,
15
- });
16
- try {
17
- const result = await verifyCredential({
18
- credential: signedVC,
19
- suite,
20
- documentLoader: customDocumentLoader,
21
- });
22
- if (result.verified) {
23
- console.log('VC verification successful');
24
- return true;
25
- }
26
- else {
27
- console.log('VC verification failed', result);
28
- return false;
29
- }
30
- }
31
- catch (error) {
32
- console.error('Error verifying VC:', error);
33
- throw error;
34
- }
35
- }
36
- }
@@ -1,9 +0,0 @@
1
- export declare class CredentialVerifier {
2
- /**
3
- * Verify a signed Verifiable Credential (VC)
4
- * @param {object} signedVC - The signed VC to verify.
5
- * @returns {Promise<boolean>} True if the VC is valid, false otherwise.
6
- * @throws Will throw an error if VC verification fails.
7
- */
8
- verifyVC(signedVC: Credential, keyPair: any): Promise<boolean>;
9
- }