@learncard/ceramic-plugin 1.1.24 → 1.1.25
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.
- package/README.md +12 -7
- package/dist/ceramic-plugin.cjs.development.cjs +10118 -2475
- package/dist/ceramic-plugin.cjs.development.cjs.map +4 -4
- package/dist/ceramic-plugin.cjs.production.min.cjs +382 -270
- package/dist/ceramic-plugin.cjs.production.min.cjs.map +4 -4
- package/dist/ceramic-plugin.esm.js +10118 -2475
- package/dist/ceramic-plugin.esm.js.map +4 -4
- package/package.json +69 -67
- package/src/ceramic.ts +189 -0
- package/src/defaults.ts +6 -0
- package/src/global.d.ts +6 -0
- package/src/helpers.ts +5 -0
- package/src/index.ts +6 -0
- package/src/types.ts +61 -0
package/package.json
CHANGED
|
@@ -1,68 +1,70 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
"
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
2
|
+
"name": "@learncard/ceramic-plugin",
|
|
3
|
+
"version": "1.1.25",
|
|
4
|
+
"description": "",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/ceramic-plugin.esm.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"development": "./src/index.ts",
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/ceramic-plugin.esm.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.cts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"src"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "node ./scripts/build.mjs && shx cp ./scripts/mixedEntypoint.js ./dist/index.cjs && tsc --p tsconfig.json && shx cp ./dist/index.d.ts ./dist/index.d.cts",
|
|
27
|
+
"test": "jest --passWithNoTests",
|
|
28
|
+
"test:watch": "jest --watch",
|
|
29
|
+
"test:coverage": "jest --silent --ci --coverage --coverageReporters=\"text\" --coverageReporters=\"text-summary\""
|
|
30
|
+
},
|
|
31
|
+
"author": "Learning Economy Foundation (www.learningeconomy.io)",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"homepage": "https://github.com/learningeconomy/LearnCard/tree/main/packages/plugins/ceramic/README.md",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "https://github.com/learningeconomy/LearnCard"
|
|
37
|
+
},
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/learningeconomy/LearnCard/issues"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@learncard/didkit-plugin": "workspace:*",
|
|
43
|
+
"@types/jest": "^29.2.2",
|
|
44
|
+
"@types/lodash": "^4.14.191",
|
|
45
|
+
"@types/node": "^17.0.31",
|
|
46
|
+
"aqu": "0.4.3",
|
|
47
|
+
"esbuild": "^0.27.1",
|
|
48
|
+
"esbuild-jest": "^0.5.0",
|
|
49
|
+
"esbuild-plugin-copy": "^1.3.0",
|
|
50
|
+
"jest": "^29.3.0",
|
|
51
|
+
"shx": "^0.3.4",
|
|
52
|
+
"ts-jest": "^29.0.3"
|
|
53
|
+
},
|
|
54
|
+
"types": "./dist/index.d.ts",
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"@ceramicnetwork/common": "2.13.0",
|
|
57
|
+
"@ceramicnetwork/http-client": "2.7.0",
|
|
58
|
+
"@ceramicnetwork/stream-tile": "2.9.0",
|
|
59
|
+
"@glazed/tile-loader": "0.2.1",
|
|
60
|
+
"@learncard/core": "workspace:*",
|
|
61
|
+
"@learncard/types": "workspace:*",
|
|
62
|
+
"did-jwt": "5.9.0",
|
|
63
|
+
"dids": "^3.2.0",
|
|
64
|
+
"hex-lite": "^1.5.0",
|
|
65
|
+
"key-did-provider-ed25519": "^2.0.0",
|
|
66
|
+
"key-did-resolver": "2.3.0",
|
|
67
|
+
"lodash": "^4.17.21"
|
|
68
|
+
},
|
|
69
|
+
"sideEffects": false
|
|
70
|
+
}
|
package/src/ceramic.ts
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
import { DID, DIDResolutionResult } from 'dids';
|
|
3
|
+
import type { JWE } from 'did-jwt';
|
|
4
|
+
import { toUint8Array } from 'hex-lite';
|
|
5
|
+
import KeyDidResolver from 'key-did-resolver';
|
|
6
|
+
import { Ed25519Provider } from 'key-did-provider-ed25519';
|
|
7
|
+
import { TileLoader } from '@glazed/tile-loader';
|
|
8
|
+
import { CeramicClient } from '@ceramicnetwork/http-client';
|
|
9
|
+
import { CreateOpts } from '@ceramicnetwork/common';
|
|
10
|
+
import { TileDocument, TileMetadataArgs } from '@ceramicnetwork/stream-tile';
|
|
11
|
+
import { LearnCard } from '@learncard/core';
|
|
12
|
+
import { InputMetadata } from '@learncard/didkit-plugin';
|
|
13
|
+
import { VCValidator, VC, VPValidator, VP, isStoredCredentialEnvelope } from '@learncard/types';
|
|
14
|
+
|
|
15
|
+
import { streamIdToCeramicURI } from './helpers';
|
|
16
|
+
import type {
|
|
17
|
+
CeramicEncryptionParams,
|
|
18
|
+
CeramicPlugin,
|
|
19
|
+
CeramicPluginDependentMethods,
|
|
20
|
+
CeramicArgs,
|
|
21
|
+
} from './types';
|
|
22
|
+
|
|
23
|
+
import { CeramicURIValidator } from './types';
|
|
24
|
+
import { DEFAULT_CERAMIC_ARGS } from './defaults';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @group Plugins
|
|
28
|
+
*/
|
|
29
|
+
export const getCeramicPlugin = async (
|
|
30
|
+
learnCard: LearnCard<any, any, CeramicPluginDependentMethods>,
|
|
31
|
+
{ ceramicEndpoint, defaultContentFamily }: CeramicArgs = DEFAULT_CERAMIC_ARGS
|
|
32
|
+
): Promise<CeramicPlugin> => {
|
|
33
|
+
const ceramic = new CeramicClient(ceramicEndpoint);
|
|
34
|
+
|
|
35
|
+
const learnCardResolver = async (
|
|
36
|
+
did: string,
|
|
37
|
+
_parsedDid: any,
|
|
38
|
+
_resolver: any,
|
|
39
|
+
options: InputMetadata
|
|
40
|
+
): Promise<DIDResolutionResult> => learnCard.invoke.didResolver(did, options);
|
|
41
|
+
|
|
42
|
+
const resolver = { ...KeyDidResolver.getResolver(), web: learnCardResolver };
|
|
43
|
+
|
|
44
|
+
const did = new DID({ resolver });
|
|
45
|
+
|
|
46
|
+
ceramic.did = did;
|
|
47
|
+
|
|
48
|
+
// use wallet secret key
|
|
49
|
+
// TODO: this is repeating work already done in the wallet. understand what the Ed25519Provider is doing to determine
|
|
50
|
+
// if we can just pass wallet signing key here instead of creating a duplicate from same seed.
|
|
51
|
+
const key = learnCard.invoke.getKey();
|
|
52
|
+
|
|
53
|
+
const ceramicProvider = new Ed25519Provider(toUint8Array(key));
|
|
54
|
+
ceramic.did!.setProvider(ceramicProvider);
|
|
55
|
+
|
|
56
|
+
await ceramic.did!.authenticate();
|
|
57
|
+
|
|
58
|
+
const loader = new TileLoader({ ceramic });
|
|
59
|
+
|
|
60
|
+
const publishContentToCeramic = async (
|
|
61
|
+
content: any,
|
|
62
|
+
metadata: TileMetadataArgs = {},
|
|
63
|
+
options: CreateOpts = {},
|
|
64
|
+
encryption?: CeramicEncryptionParams
|
|
65
|
+
) => {
|
|
66
|
+
if (!content) throw new Error('content is required');
|
|
67
|
+
|
|
68
|
+
// default to current authorized
|
|
69
|
+
if (!metadata.controllers) metadata.controllers = [ceramic.did!.id];
|
|
70
|
+
|
|
71
|
+
// use default
|
|
72
|
+
if (!metadata.family) metadata.family = defaultContentFamily;
|
|
73
|
+
|
|
74
|
+
// default to pinning
|
|
75
|
+
// TODO: expose
|
|
76
|
+
if (!('pin' in options)) options.pin = true;
|
|
77
|
+
|
|
78
|
+
if (encryption?.encrypt) {
|
|
79
|
+
learnCard.debug?.(
|
|
80
|
+
'learnCard.store.Ceramic.upload.publishContentToCeramic - encrypt enabled',
|
|
81
|
+
encryption
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
let recipients = encryption?.recipients || [];
|
|
85
|
+
if (encryption?.controllersCanDecrypt) {
|
|
86
|
+
recipients = [...recipients, ...metadata.controllers];
|
|
87
|
+
}
|
|
88
|
+
content = await ceramic?.did?.createDagJWE(content, _.uniq(recipients));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
learnCard.debug?.(
|
|
92
|
+
'learnCard.store.Ceramic.upload.publishContentToCeramic - content to upload',
|
|
93
|
+
content,
|
|
94
|
+
metadata,
|
|
95
|
+
options
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
// assuming TileDocument for now
|
|
99
|
+
const doc = await TileDocument.create(ceramic, content, metadata, options);
|
|
100
|
+
|
|
101
|
+
return doc.id.toString();
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const readContentFromCeramic = async (streamId: string): Promise<any> => {
|
|
105
|
+
const content = (await loader.load(streamId))?.content;
|
|
106
|
+
learnCard.debug?.('learnCard.read.get.readContentFromCeramic', content);
|
|
107
|
+
if (content?.ciphertext) {
|
|
108
|
+
try {
|
|
109
|
+
return await did.decryptDagJWE(content as JWE);
|
|
110
|
+
} catch (e) {
|
|
111
|
+
learnCard.debug?.(
|
|
112
|
+
'learnCard.read.get.readContentFromCeramic - Could not decrypt credential - DID not authorized.',
|
|
113
|
+
e
|
|
114
|
+
);
|
|
115
|
+
throw new Error('Could not decrypt credential - DID not authorized.');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return content;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const uploadCredential = async (
|
|
122
|
+
vc: VC | VP,
|
|
123
|
+
encryption?: CeramicEncryptionParams | undefined
|
|
124
|
+
) => {
|
|
125
|
+
await VCValidator.or(VPValidator).parseAsync(vc);
|
|
126
|
+
|
|
127
|
+
return streamIdToCeramicURI(await publishContentToCeramic(vc, {}, {}, encryption));
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const envelopeRejection = (envelope: { format: string }) =>
|
|
131
|
+
new Error(
|
|
132
|
+
`Ceramic does not support format-tagged credential envelopes (got format="${envelope.format}"). Use LearnCloud or the LearnCard Network store for non-W3C credentials (SD-JWT-VC, mDoc, etc.).`
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
name: 'Ceramic',
|
|
137
|
+
displayName: 'Ceramic',
|
|
138
|
+
description:
|
|
139
|
+
'Uploads/resolves credentials using the Ceramic Network (https://ceramic.network/)',
|
|
140
|
+
store: {
|
|
141
|
+
upload: async (_learnCard, vc) => {
|
|
142
|
+
_learnCard.debug?.('learnCard.store.Ceramic.upload');
|
|
143
|
+
if (isStoredCredentialEnvelope(vc)) throw envelopeRejection(vc);
|
|
144
|
+
return uploadCredential(vc);
|
|
145
|
+
},
|
|
146
|
+
uploadEncrypted: async (_learnCard, vc, params) => {
|
|
147
|
+
_learnCard.debug?.('learnCard.store.Ceramic.uploadEncrypted');
|
|
148
|
+
if (isStoredCredentialEnvelope(vc)) throw envelopeRejection(vc);
|
|
149
|
+
return uploadCredential(vc, {
|
|
150
|
+
encrypt: true,
|
|
151
|
+
controllersCanDecrypt: true,
|
|
152
|
+
...(params ? { recipients: params.recipients } : {}),
|
|
153
|
+
});
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
read: {
|
|
157
|
+
get: async (_learnCard, uri) => {
|
|
158
|
+
_learnCard.debug?.('learnCard.read.Ceramic.get');
|
|
159
|
+
|
|
160
|
+
if (!uri) return undefined;
|
|
161
|
+
|
|
162
|
+
const verificationResult = await CeramicURIValidator.spa(uri);
|
|
163
|
+
|
|
164
|
+
if (!verificationResult.success) return undefined;
|
|
165
|
+
|
|
166
|
+
const streamId = verificationResult.data.split(':')[2];
|
|
167
|
+
try {
|
|
168
|
+
return await VCValidator.or(VPValidator).parseAsync(
|
|
169
|
+
await readContentFromCeramic(streamId)
|
|
170
|
+
);
|
|
171
|
+
} catch (e) {
|
|
172
|
+
_learnCard.debug?.(e);
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
methods: {
|
|
178
|
+
publishContentToCeramic: async (
|
|
179
|
+
_learnCard,
|
|
180
|
+
cred,
|
|
181
|
+
encryption: CeramicEncryptionParams | undefined
|
|
182
|
+
) => streamIdToCeramicURI(await publishContentToCeramic(cred, {}, {}, encryption)),
|
|
183
|
+
readContentFromCeramic: async (_learnCard, streamId: string) =>
|
|
184
|
+
readContentFromCeramic(streamId),
|
|
185
|
+
getCeramicClient: () => ceramic,
|
|
186
|
+
getDIDObject: () => ceramic.did!,
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
};
|
package/src/defaults.ts
ADDED
package/src/global.d.ts
ADDED
package/src/helpers.ts
ADDED
package/src/index.ts
ADDED
package/src/types.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { DID, CreateJWEOptions, DIDResolutionResult } from 'dids';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { CeramicClient } from '@ceramicnetwork/http-client';
|
|
4
|
+
import { Plugin } from '@learncard/core';
|
|
5
|
+
import { InputMetadata } from '@learncard/didkit-plugin';
|
|
6
|
+
|
|
7
|
+
/** @group CeramicPlugin */
|
|
8
|
+
export type CeramicArgs = {
|
|
9
|
+
ceramicEndpoint: string;
|
|
10
|
+
defaultContentFamily: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/** @group CeramicPlugin */
|
|
14
|
+
export type CeramicURI = `lc:ceramic:${string}`;
|
|
15
|
+
|
|
16
|
+
/** @group CeramicPlugin */
|
|
17
|
+
export const CeramicURIValidator = z
|
|
18
|
+
.string()
|
|
19
|
+
.refine(
|
|
20
|
+
string => string.split(':').length === 3 && string.split(':')[0] === 'lc',
|
|
21
|
+
'URI must be of the form lc:${storage}:${url}'
|
|
22
|
+
)
|
|
23
|
+
.refine(
|
|
24
|
+
string => string.split(':')[1] === 'ceramic',
|
|
25
|
+
'URI must use storage type ceramic (i.e. must be lc:ceramic:${streamID})'
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Settings for toggling JWE Encryption before uploading to Ceramic.
|
|
30
|
+
* @group CeramicPlugin
|
|
31
|
+
* @param encrypt enable JWE encryption on upload.
|
|
32
|
+
* @param controllersCanDecrypt helper to add Ceramic controller DIDs to recipients who can decrypt the JWE credential.
|
|
33
|
+
* @param recipients DIDs who can decrypt the JWE credential.
|
|
34
|
+
* @param options additional CreateJWEOptions for encryption.
|
|
35
|
+
*/
|
|
36
|
+
export type CeramicEncryptionParams = {
|
|
37
|
+
encrypt: boolean;
|
|
38
|
+
controllersCanDecrypt?: boolean;
|
|
39
|
+
recipients?: string[] | undefined;
|
|
40
|
+
options?: CreateJWEOptions | undefined;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/** @group CeramicPlugin */
|
|
44
|
+
export type CeramicPluginMethods = {
|
|
45
|
+
publishContentToCeramic: (
|
|
46
|
+
cred: any,
|
|
47
|
+
encryption: CeramicEncryptionParams | undefined
|
|
48
|
+
) => Promise<CeramicURI>;
|
|
49
|
+
readContentFromCeramic: (streamId: string) => Promise<any>;
|
|
50
|
+
getCeramicClient: () => CeramicClient;
|
|
51
|
+
getDIDObject: () => DID;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** @group CeramicPlugin */
|
|
55
|
+
export type CeramicPluginDependentMethods = {
|
|
56
|
+
getKey: () => string;
|
|
57
|
+
didResolver: (did: string, inputMetadata?: InputMetadata) => Promise<DIDResolutionResult>;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/** @group CeramicPlugin */
|
|
61
|
+
export type CeramicPlugin = Plugin<'Ceramic', 'read' | 'store', CeramicPluginMethods>;
|