@cooperation/vc-storage 1.0.42 → 1.0.43

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.
@@ -73,6 +73,33 @@ export class CredentialEngine {
73
73
  }));
74
74
  return true;
75
75
  }
76
+ /**
77
+ * For recommendations we need the *target VC DID/URI* (VC `id`) in the payload.
78
+ * Callers may still pass a Google Drive file id; if so we resolve and extract the VC `id`.
79
+ */
80
+ async resolveTargetVcId(vcIdOrFileId) {
81
+ if (!vcIdOrFileId)
82
+ throw new Error('Missing target VC reference');
83
+ // Already a DID/URI
84
+ if (vcIdOrFileId.startsWith('urn:') || vcIdOrFileId.startsWith('did:')) {
85
+ return vcIdOrFileId;
86
+ }
87
+ // Otherwise assume it's a Google Drive file id and resolve
88
+ const retrieved = await this.storage.retrieve(vcIdOrFileId);
89
+ if (!retrieved?.data)
90
+ throw new Error(`Unable to resolve VC from file id: ${vcIdOrFileId}`);
91
+ const maybeEnvelope = retrieved.data;
92
+ const payload = typeof maybeEnvelope?.body === 'string'
93
+ ? JSON.parse(maybeEnvelope.body)
94
+ : maybeEnvelope?.body
95
+ ? maybeEnvelope.body
96
+ : maybeEnvelope;
97
+ const resolvedId = payload?.id;
98
+ if (!resolvedId || typeof resolvedId !== 'string') {
99
+ throw new Error(`Resolved VC is missing an 'id' (from file id: ${vcIdOrFileId})`);
100
+ }
101
+ return resolvedId;
102
+ }
76
103
  /**
77
104
  * Create a new DID with Digital Bazaar's Ed25519VerificationKey2020 key pair.
78
105
  * @returns {Promise<{didDocument: object, keyPair: object}>} The created DID document and key pair.
@@ -143,8 +170,10 @@ export class CredentialEngine {
143
170
  case 'RECOMMENDATION':
144
171
  if (!vcFileId)
145
172
  throw new Error('vcFileId is required for recommendation');
173
+ // Ensure the Recommendation VC references the target VC DID/URI (not a Drive file id)
174
+ const targetVcId = await this.resolveTargetVcId(vcFileId);
146
175
  credential = generateUnsignedRecommendation({
147
- vcId: vcFileId,
176
+ vcId: targetVcId,
148
177
  recommendation: data,
149
178
  issuerDid: issuerId,
150
179
  });
@@ -1,4 +1,5 @@
1
- import { DidDocument, KeyPair, FormDataI, RecommendationFormDataI, VerifiableCredential, EmploymentFormDataI, PerformanceReviewFormDataI, VolunteeringFormDataI } from '../../types/credential.js';
1
+ import type { IVerifiableCredential } from '@digitalcredentials/ssi';
2
+ import { DidDocument, KeyPair, FormDataI, RecommendationFormDataI, EmploymentFormDataI, PerformanceReviewFormDataI, VolunteeringFormDataI } from '../../types';
2
3
  import { GoogleDriveStorage } from './GoogleDriveStorage.js';
3
4
  interface SignPropsI {
4
5
  data: FormDataI | RecommendationFormDataI | EmploymentFormDataI | VolunteeringFormDataI | PerformanceReviewFormDataI;
@@ -25,6 +26,11 @@ export declare class CredentialEngine {
25
26
  private getKeyPair;
26
27
  private generateKeyPair;
27
28
  private verifyCreds;
29
+ /**
30
+ * For recommendations we need the *target VC DID/URI* (VC `id`) in the payload.
31
+ * Callers may still pass a Google Drive file id; if so we resolve and extract the VC `id`.
32
+ */
33
+ private resolveTargetVcId;
28
34
  /**
29
35
  * Create a new DID with Digital Bazaar's Ed25519VerificationKey2020 key pair.
30
36
  * @returns {Promise<{didDocument: object, keyPair: object}>} The created DID document and key pair.
@@ -68,13 +74,13 @@ export declare class CredentialEngine {
68
74
  * @returns {Promise<boolean>} The verification result.
69
75
  * @throws Will throw an error if VC verification fails.
70
76
  */
71
- verifyCredential(credential: VerifiableCredential): Promise<boolean>;
77
+ verifyCredential(credential: IVerifiableCredential): Promise<boolean>;
72
78
  /**
73
79
  * Create a Verifiable Presentation (VP)
74
80
  * @param verifiableCredential
75
81
  * @returns
76
82
  */
77
- createPresentation(verifiableCredential: VerifiableCredential[]): Promise<any>;
83
+ createPresentation(verifiableCredential: IVerifiableCredential[]): Promise<any>;
78
84
  /**
79
85
  * Sign a Verifiable Presentation (VP)
80
86
  * @param presentation
@@ -28,5 +28,5 @@ export declare class ResumeVC {
28
28
  */
29
29
  createDID({ keyPair }: {
30
30
  keyPair: any;
31
- }): Promise<import("../../types/credential.js").DidDocument>;
31
+ }): Promise<import("../../types/Credential.js").DidDocument>;
32
32
  }
@@ -1,4 +1,5 @@
1
- import { KeyPair, DidDocument, FormDataI, RecommendationCredential, Credential, RecommendationFormDataI, VerifiableCredential, EmploymentFormDataI, VolunteeringFormDataI, PerformanceReviewFormDataI } from '../../types';
1
+ import { KeyPair, DidDocument, FormDataI, RecommendationFormDataI, EmploymentFormDataI, VolunteeringFormDataI, PerformanceReviewFormDataI } from '../../types';
2
+ import { IVerifiableCredential } from '@digitalcredentials/ssi';
2
3
  /**
3
4
  * Create a DID document using the provided key pair.
4
5
  * @param {KeyPair} keyPair - The key pair used to create the DID document.
@@ -12,201 +13,52 @@ export declare const generateDIDSchema: (keyPair: KeyPair) => Promise<DidDocumen
12
13
  * @param {FormDataI} params
13
14
  * @param {string} params.FormData - The form dta to include in the VC.
14
15
  * @param {string} params.issuerDid - The DID of the issuer.
15
- * @returns {Credential} The created unsigned VC.
16
+ * @returns {IVerifiableCredential} The created unsigned VC.
16
17
  * @throws Will throw an error if the VC creation fails or if issuance date exceeds expiration date.
17
18
  */
18
19
  export declare function generateUnsignedVC({ formData, issuerDid }: {
19
20
  formData: FormDataI;
20
21
  issuerDid: string;
21
- }): Credential;
22
+ }): IVerifiableCredential;
22
23
  /**
23
24
  * Generate an unsigned Recommendation Credential.
24
25
  * Uses the hash of the VC to set the `id` for consistency.
25
26
  * @param {object} params
26
- * @param {VerifiableCredential} params.vc - The Verifiable Credential to base the recommendation on.
27
+ * @param {IVerifiableCredential} params.vc - The Verifiable Credential to base the recommendation on.
27
28
  * @param {RecommendationFormDataI} params.recommendation - The recommendation form data.
28
29
  * @param {string} params.issuerDid - The DID of the issuer.
29
- * @returns {RecommendationCredential} The created unsigned Recommendation Credential.
30
+ * @returns {IVerifiableCredential} The created unsigned Recommendation Credential.
30
31
  * @throws Will throw an error if the recommendation creation fails or if issuance date exceeds expiration date.
31
32
  */
32
33
  export declare function generateUnsignedRecommendation({ vcId, recommendation, issuerDid, }: {
33
34
  vcId: string;
34
35
  recommendation: RecommendationFormDataI;
35
36
  issuerDid: string;
36
- }): RecommendationCredential;
37
+ }): IVerifiableCredential;
37
38
  /**
38
39
  * Generate an unsigned Employment Credential.
39
40
  */
40
41
  export declare function generateUnsignedEmployment({ formData, issuerDid }: {
41
42
  formData: EmploymentFormDataI;
42
43
  issuerDid: string;
43
- }): {
44
- '@context': (string | {
45
- '@vocab': string;
46
- fullName: string;
47
- persons: string;
48
- credentialName: string;
49
- credentialDuration: string;
50
- credentialDescription: string;
51
- portfolio: {
52
- '@id': string;
53
- '@container': string;
54
- };
55
- name: string;
56
- url: string;
57
- evidenceLink: string;
58
- evidenceDescription: string;
59
- company: string;
60
- role: string;
61
- })[];
62
- id: string;
63
- type: string[];
64
- issuer: {
65
- id: string;
66
- type: string[];
67
- };
68
- issuanceDate: string;
69
- credentialSubject: {
70
- type: string[];
71
- fullName: string;
72
- persons: string;
73
- credentialName: string;
74
- credentialDuration: string;
75
- credentialDescription: string;
76
- portfolio: {
77
- name: string;
78
- url: string;
79
- }[];
80
- evidenceLink: string;
81
- evidenceDescription: string;
82
- company: string;
83
- role: string;
84
- };
85
- };
44
+ }): IVerifiableCredential;
86
45
  /**
87
46
  * Generate an unsigned Volunteering Credential.
88
47
  */
89
48
  export declare function generateUnsignedVolunteering({ formData, issuerDid }: {
90
49
  formData: VolunteeringFormDataI;
91
50
  issuerDid: string;
92
- }): {
93
- '@context': (string | {
94
- '@vocab': string;
95
- fullName: string;
96
- persons: string;
97
- volunteerWork: string;
98
- volunteerOrg: string;
99
- volunteerDescription: string;
100
- skillsGained: {
101
- '@id': string;
102
- '@container': string;
103
- };
104
- duration: string;
105
- volunteerDates: string;
106
- portfolio: {
107
- '@id': string;
108
- '@container': string;
109
- };
110
- name: string;
111
- url: string;
112
- evidenceLink: string;
113
- evidenceDescription: string;
114
- })[];
115
- id: string;
116
- type: string[];
117
- issuer: {
118
- id: string;
119
- type: string[];
120
- };
121
- issuanceDate: string;
122
- credentialSubject: {
123
- type: string[];
124
- fullName: string;
125
- persons: string;
126
- volunteerWork: string;
127
- volunteerOrg: string;
128
- volunteerDescription: string;
129
- skillsGained: string[];
130
- duration: string;
131
- volunteerDates: string;
132
- portfolio: {
133
- name: string;
134
- url: string;
135
- }[];
136
- evidenceLink: string;
137
- evidenceDescription: string;
138
- };
139
- };
51
+ }): IVerifiableCredential;
140
52
  /**
141
53
  * Generate an unsigned Performance Review Credential.
142
54
  */
143
55
  export declare function generateUnsignedPerformanceReview({ formData, issuerDid }: {
144
56
  formData: PerformanceReviewFormDataI;
145
57
  issuerDid: string;
146
- }): {
147
- '@context': (string | {
148
- '@vocab': string;
149
- fullName: string;
150
- persons: string;
151
- employeeName: string;
152
- employeeJobTitle: string;
153
- company: string;
154
- role: string;
155
- reviewStartDate: string;
156
- reviewEndDate: string;
157
- reviewDuration: string;
158
- jobKnowledgeRating: string;
159
- teamworkRating: string;
160
- initiativeRating: string;
161
- communicationRating: string;
162
- overallRating: string;
163
- reviewComments: string;
164
- goalsNext: string;
165
- portfolio: {
166
- '@id': string;
167
- '@container': string;
168
- };
169
- name: string;
170
- url: string;
171
- evidenceLink: string;
172
- evidenceDescription: string;
173
- })[];
174
- id: string;
175
- type: string[];
176
- issuer: {
177
- id: string;
178
- type: string[];
179
- };
180
- issuanceDate: string;
181
- credentialSubject: {
182
- type: string[];
183
- fullName: string;
184
- persons: string;
185
- employeeName: string;
186
- employeeJobTitle: string;
187
- company: string;
188
- role: string;
189
- reviewStartDate: string;
190
- reviewEndDate: string;
191
- reviewDuration: string;
192
- jobKnowledgeRating: string;
193
- teamworkRating: string;
194
- initiativeRating: string;
195
- communicationRating: string;
196
- overallRating: string;
197
- reviewComments: string;
198
- goalsNext: string;
199
- portfolio: {
200
- name: string;
201
- url: string;
202
- }[];
203
- evidenceLink: string;
204
- evidenceDescription: string;
205
- };
206
- };
58
+ }): IVerifiableCredential;
207
59
  /**
208
60
  * Extracts the keypair from a Verifiable Credential
209
61
  * @param {Object} credential - The signed Verifiable Credential
210
62
  * @returns {Ed25519VerificationKey2020} keyPair - The generated keypair object
211
63
  */
212
- export declare function extractKeyPairFromCredential(credential: VerifiableCredential): Promise<KeyPair>;
64
+ export declare function extractKeyPairFromCredential(credential: IVerifiableCredential): Promise<KeyPair>;
@@ -22,10 +22,10 @@ export declare const getVCWithRecommendations: ({ vcId, storage }: {
22
22
  * @param {object} data - The data to save.
23
23
  * @param {FileType} data.type - The type of data being saved.
24
24
  * @returns {Promise<object>} - The file object saved to Google Drive.
25
- * @param {string} data.vcId - Optional unique identifier for the VC to link the recommendations.
25
+ * @param {string} data.vcId - When `type` is RECOMMENDATION, this is the Google Drive file id of the parent VC (named "VC") to link against.
26
26
  * @throws Will throw an error if the save operation fails.
27
27
  */
28
- export declare function saveToGoogleDrive({ storage, data, type }: SaveToGooglePropsI): Promise<any>;
28
+ export declare function saveToGoogleDrive({ storage, data, type, vcId }: SaveToGooglePropsI): Promise<any>;
29
29
  /**
30
30
  * Upload any type of file to Google Drive in the Credentials/MEDIAs folder.
31
31
  * @param {GoogleDriveStorage} storage - The GoogleDriveStorage instance.
@@ -60,7 +60,7 @@ export const generateDIDSchema = async (keyPair) => {
60
60
  * @param {FormDataI} params
61
61
  * @param {string} params.FormData - The form dta to include in the VC.
62
62
  * @param {string} params.issuerDid - The DID of the issuer.
63
- * @returns {Credential} The created unsigned VC.
63
+ * @returns {IVerifiableCredential} The created unsigned VC.
64
64
  * @throws Will throw an error if the VC creation fails or if issuance date exceeds expiration date.
65
65
  */
66
66
  export function generateUnsignedVC({ formData, issuerDid }) {
@@ -127,10 +127,10 @@ export function generateUnsignedVC({ formData, issuerDid }) {
127
127
  * Generate an unsigned Recommendation Credential.
128
128
  * Uses the hash of the VC to set the `id` for consistency.
129
129
  * @param {object} params
130
- * @param {VerifiableCredential} params.vc - The Verifiable Credential to base the recommendation on.
130
+ * @param {IVerifiableCredential} params.vc - The Verifiable Credential to base the recommendation on.
131
131
  * @param {RecommendationFormDataI} params.recommendation - The recommendation form data.
132
132
  * @param {string} params.issuerDid - The DID of the issuer.
133
- * @returns {RecommendationCredential} The created unsigned Recommendation Credential.
133
+ * @returns {IVerifiableCredential} The created unsigned Recommendation Credential.
134
134
  * @throws Will throw an error if the recommendation creation fails or if issuance date exceeds expiration date.
135
135
  */
136
136
  export function generateUnsignedRecommendation({ vcId, recommendation, issuerDid, }) {
@@ -149,7 +149,7 @@ export function generateUnsignedRecommendation({ vcId, recommendation, issuerDid
149
149
  portfolio: 'https://schema.org/portfolio',
150
150
  },
151
151
  ],
152
- id: `urn:${generateHashedId({ id: vcId })}`,
152
+ id: ``,
153
153
  type: ['VerifiableCredential', 'https://schema.org/RecommendationCredential'],
154
154
  issuer: {
155
155
  id: issuerDid,
@@ -158,6 +158,7 @@ export function generateUnsignedRecommendation({ vcId, recommendation, issuerDid
158
158
  issuanceDate,
159
159
  expirationDate: recommendation.expirationDate,
160
160
  credentialSubject: {
161
+ id: vcId,
161
162
  name: recommendation.fullName,
162
163
  howKnow: recommendation.howKnow,
163
164
  recommendationText: recommendation.recommendationText,
@@ -169,6 +170,8 @@ export function generateUnsignedRecommendation({ vcId, recommendation, issuerDid
169
170
  })),
170
171
  },
171
172
  };
173
+ // Generate the hashed ID
174
+ unsignedRecommendation.id = 'urn:' + generateHashedId(unsignedRecommendation);
172
175
  return unsignedRecommendation;
173
176
  }
174
177
  /**
@@ -272,7 +275,7 @@ export function generateUnsignedPerformanceReview({ formData, issuerDid }) {
272
275
  */
273
276
  export async function extractKeyPairFromCredential(credential) {
274
277
  const verificationMethod = credential.proof.verificationMethod;
275
- const issuer = credential.issuer.id;
278
+ const issuer = typeof credential.issuer === 'string' ? credential.issuer : credential.issuer.id;
276
279
  // Example of extracting the public key from the DID fragment (verification method)
277
280
  const publicKeyMultibase = verificationMethod.split('#')[1];
278
281
  // Generate the keypair using Ed25519VerificationKey2020
@@ -19,10 +19,10 @@ export const getVCWithRecommendations = async ({ vcId, storage }) => {
19
19
  * @param {object} data - The data to save.
20
20
  * @param {FileType} data.type - The type of data being saved.
21
21
  * @returns {Promise<object>} - The file object saved to Google Drive.
22
- * @param {string} data.vcId - Optional unique identifier for the VC to link the recommendations.
22
+ * @param {string} data.vcId - When `type` is RECOMMENDATION, this is the Google Drive file id of the parent VC (named "VC") to link against.
23
23
  * @throws Will throw an error if the save operation fails.
24
24
  */
25
- export async function saveToGoogleDrive({ storage, data, type }) {
25
+ export async function saveToGoogleDrive({ storage, data, type, vcId }) {
26
26
  try {
27
27
  const fileData = {
28
28
  fileName: type === 'VC' ? 'VC' : `${type}-${Date.now()}`,
@@ -55,6 +55,22 @@ export async function saveToGoogleDrive({ storage, data, type }) {
55
55
  }
56
56
  // Save the file in the specific subfolder
57
57
  const file = await storage.saveFile({ data: fileData, folderId: typeFolderId });
58
+ // If this is a recommendation, optionally link it to a parent VC folder via RELATIONS
59
+ if (type === 'RECOMMENDATION' && vcId) {
60
+ const parents = await storage.getFileParents(vcId);
61
+ const vcFolderId = Array.isArray(parents) ? parents[0] : parents;
62
+ if (!vcFolderId)
63
+ throw new Error('Unable to resolve parent folder for vcId');
64
+ const vcFolderFiles = await storage.findFolderFiles(vcFolderId);
65
+ let relationsFile = vcFolderFiles.find((f) => f.name === 'RELATIONS');
66
+ if (!relationsFile) {
67
+ relationsFile = await storage.createRelationsFile({ vcFolderId });
68
+ }
69
+ await storage.updateRelationsFile({
70
+ relationsFileId: relationsFile.id,
71
+ recommendationFileId: file.id,
72
+ });
73
+ }
58
74
  return file;
59
75
  }
60
76
  catch (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.42",
4
+ "version": "1.0.43",
5
5
  "description": "Sign and store your verifiable credentials.",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/types/index.d.ts",
@@ -25,6 +25,7 @@
25
25
  "@digitalcredentials/ed25519-signature-2020": "^5.0.0",
26
26
  "@digitalcredentials/ed25519-verification-key-2020": "^5.0.0-beta.2",
27
27
  "@digitalcredentials/ezcap": "^5.1.0",
28
+ "@digitalcredentials/ssi": "^5.1.0",
28
29
  "@wallet.storage/fetch-client": "^1.2.0",
29
30
  "add": "^2.0.6",
30
31
  "bnid": "^3.0.0",