@learncard/open-badge-v2-plugin 1.0.0
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/LICENSE +21 -0
- package/README.md +141 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/open-badge-v2-plugin.cjs.development.js +142 -0
- package/dist/open-badge-v2-plugin.cjs.development.js.map +7 -0
- package/dist/open-badge-v2-plugin.cjs.production.min.js +2 -0
- package/dist/open-badge-v2-plugin.cjs.production.min.js.map +7 -0
- package/dist/open-badge-v2-plugin.esm.js +121 -0
- package/dist/open-badge-v2-plugin.esm.js.map +7 -0
- package/dist/plugin.d.ts +4 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/types.d.ts +14 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +44 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Learning Economy Foundation <sdk@learningeconomy.io>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# @learncard/open-badge-v2-plugin
|
|
2
|
+
|
|
3
|
+
Wrap legacy Open Badges v2.0 (OBv2) assertions into self‑issued W3C Verifiable Credentials (VCs) so they can be stored, indexed, and used within the LearnCard ecosystem.
|
|
4
|
+
|
|
5
|
+
- Name: `OpenBadgeV2`
|
|
6
|
+
- Method: `wrapOpenBadgeV2(obv2Assertion: object | string): Promise<VC>`
|
|
7
|
+
- Context added: `https://ctx.learncard.com/wrappers/obv2/1.0.0.json`
|
|
8
|
+
- VC type produced: `LegacyOpenBadgeCredential`
|
|
9
|
+
|
|
10
|
+
## Why this plugin?
|
|
11
|
+
|
|
12
|
+
Many issuers still publish Open Badges v2.0 assertions (JSON), not VCs. This plugin provides a backwards‑compatibility bridge by wrapping the raw OBv2 assertion inside a signed VC you control, preserving the original badge payload under `legacyAssertion`.
|
|
13
|
+
|
|
14
|
+
This enables:
|
|
15
|
+
- Use of legacy badges anywhere a VC is expected (storage, indexing, sharing).
|
|
16
|
+
- A stable LearnCard vocabulary for the wrapper (`LegacyOpenBadgeCredential`).
|
|
17
|
+
- Consistent DID‑based signing and verification workflows.
|
|
18
|
+
|
|
19
|
+
## What it produces
|
|
20
|
+
|
|
21
|
+
A signed VC with these characteristics:
|
|
22
|
+
- `@context`: `["https://www.w3.org/ns/credentials/v2", "https://ctx.learncard.com/wrappers/obv2/1.0.0.json"]`
|
|
23
|
+
- `type`: `["VerifiableCredential", "LegacyOpenBadgeCredential"]`
|
|
24
|
+
- `issuer`: your LearnCard DID
|
|
25
|
+
- `validFrom`: `new Date().toISOString()`
|
|
26
|
+
- `credentialSubject.id`: your LearnCard DID
|
|
27
|
+
- `legacyAssertion`: the original OBv2 assertion JSON
|
|
28
|
+
|
|
29
|
+
Notes:
|
|
30
|
+
- The wrapper signs the fact that “you attest to this legacy assertion,” not that you were the original OBv2 issuer.
|
|
31
|
+
- Minimal OBv2 validation is performed: `id` (string), `type` includes `"Assertion"`, `issuedOn` (string).
|
|
32
|
+
|
|
33
|
+
## Requirements
|
|
34
|
+
|
|
35
|
+
- A LearnCard instance with the VC plugin installed (used to sign the VC).
|
|
36
|
+
- Fetch available for remote inputs (Node 18+ or a polyfill like `undici`).
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
This package is part of the monorepo. In external usage, install from npm (when published) and ensure peer packages are available.
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# inside the monorepo
|
|
44
|
+
pnpm nx run open-badge-v2-plugin:build
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Usage
|
|
48
|
+
|
|
49
|
+
Below examples assume you already have a `learnCard` instance and you add both the VC plugin and this plugin.
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import { getVCPlugin } from '@learncard/vc-plugin';
|
|
53
|
+
import { openBadgeV2Plugin } from '@learncard/open-badge-v2-plugin';
|
|
54
|
+
|
|
55
|
+
// Ensure VC plugin is installed first (required for signing)
|
|
56
|
+
await learnCard.addPlugin(getVCPlugin(learnCard));
|
|
57
|
+
|
|
58
|
+
// Install the OpenBadge v2 wrapper plugin
|
|
59
|
+
await learnCard.addPlugin(openBadgeV2Plugin(learnCard));
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 1) Wrap from a remote HTTP(S) URL
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
const url = 'https://example.org/badges/assertions/123.json';
|
|
66
|
+
const vc = await learnCard.invoke.wrapOpenBadgeV2(url);
|
|
67
|
+
|
|
68
|
+
// Optional: verify the signed VC
|
|
69
|
+
const verification = await learnCard.invoke.verifyCredential(vc);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 2) Wrap from IPFS
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
const ipfsUrl = 'ipfs://bafybeigdyr...';
|
|
76
|
+
const vc = await learnCard.invoke.wrapOpenBadgeV2(ipfsUrl);
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 3) Wrap from a raw OBv2 object
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
const obv2 = {
|
|
83
|
+
'@context': 'https://w3id.org/openbadges/v2',
|
|
84
|
+
type: 'Assertion',
|
|
85
|
+
id: 'https://issuer.example.org/assertions/abc',
|
|
86
|
+
issuedOn: '2024-05-02T12:34:56Z',
|
|
87
|
+
recipient: { type: 'email', identity: 'user@example.org', hashed: false },
|
|
88
|
+
badge: 'https://issuer.example.org/badges/my-badge'
|
|
89
|
+
// ... any other OBv2 fields ...
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const vc = await learnCard.invoke.wrapOpenBadgeV2(obv2);
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 4) Wrap from a JSON string
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
const obv2String = JSON.stringify(obv2);
|
|
99
|
+
const vc = await learnCard.invoke.wrapOpenBadgeV2(obv2String);
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## API
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
wrapOpenBadgeV2(obv2Assertion: object | string): Promise<VC>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
- `obv2Assertion` may be:
|
|
109
|
+
- A raw OBv2 assertion object
|
|
110
|
+
- A URL string (http(s)://, ipfs://, ipns://) pointing to the assertion JSON
|
|
111
|
+
- A JSON string containing the assertion
|
|
112
|
+
|
|
113
|
+
Validation rules enforced:
|
|
114
|
+
- `assertion.id` must be a non‑empty string
|
|
115
|
+
- `assertion.type` must include `"Assertion"`
|
|
116
|
+
- `assertion.issuedOn` must be a string
|
|
117
|
+
|
|
118
|
+
## JSON‑LD Context
|
|
119
|
+
|
|
120
|
+
The wrapper vocabulary is published at:
|
|
121
|
+
|
|
122
|
+
- `https://ctx.learncard.com/wrappers/obv2/1.0.0.json`
|
|
123
|
+
|
|
124
|
+
It defines:
|
|
125
|
+
- `LegacyOpenBadgeCredential`
|
|
126
|
+
- `legacyAssertion` as `@type: @json` for embedding raw OBv2 JSON
|
|
127
|
+
|
|
128
|
+
## Development
|
|
129
|
+
|
|
130
|
+
- Build: `pnpm nx run open-badge-v2-plugin:build`
|
|
131
|
+
- Source: `packages/plugins/open-badge-v2/src/`
|
|
132
|
+
- Context file: `packages/learn-card-contexts/wrappers/obv2/1.0.0.json`
|
|
133
|
+
|
|
134
|
+
## Caveats
|
|
135
|
+
|
|
136
|
+
- Wrapping does not retroactively convert OBv2 into a first‑party VC issued by the original badge issuer; it produces a VC signed by your DID that embeds the legacy assertion.
|
|
137
|
+
- Always fetch and wrap assertions from trusted sources.
|
|
138
|
+
|
|
139
|
+
## License
|
|
140
|
+
|
|
141
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAC7C,cAAc,SAAS,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/index.ts
|
|
22
|
+
var src_exports = {};
|
|
23
|
+
__export(src_exports, {
|
|
24
|
+
OBV2_WRAPPER_CONTEXT_URL: () => OBV2_WRAPPER_CONTEXT_URL,
|
|
25
|
+
openBadgeV2Plugin: () => openBadgeV2Plugin
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(src_exports);
|
|
28
|
+
|
|
29
|
+
// src/types.ts
|
|
30
|
+
var OBV2_WRAPPER_CONTEXT_URL = "https://ctx.learncard.com/wrappers/obv2/1.0.0.json";
|
|
31
|
+
|
|
32
|
+
// src/plugin.ts
|
|
33
|
+
var VC_V2_CONTEXT = "https://www.w3.org/ns/credentials/v2";
|
|
34
|
+
var isUrl = /* @__PURE__ */ __name((value) => /^(https?:)?\/\//i.test(value) || value.startsWith("ipfs://") || value.startsWith("ipns://"), "isUrl");
|
|
35
|
+
var isValidAndSafeUrl = /* @__PURE__ */ __name((url) => {
|
|
36
|
+
try {
|
|
37
|
+
const parsedUrl = new URL(url);
|
|
38
|
+
const allowedProtocols = ["https:", "http:", "ipfs:", "ipns:"];
|
|
39
|
+
if (!allowedProtocols.includes(parsedUrl.protocol)) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`wrapOpenBadgeV2: Protocol '${parsedUrl.protocol}' is not allowed. Only HTTP(S), IPFS, and IPNS are supported`
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
if (parsedUrl.protocol === "ipfs:" || parsedUrl.protocol === "ipns:") {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const hostname = parsedUrl.hostname.toLowerCase();
|
|
48
|
+
if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "0.0.0.0" || hostname === "::1") {
|
|
49
|
+
throw new Error("wrapOpenBadgeV2: Access to localhost is not allowed");
|
|
50
|
+
}
|
|
51
|
+
if (hostname.startsWith("10.") || hostname.startsWith("192.168.") || hostname.startsWith("172.") && /^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(hostname)) {
|
|
52
|
+
throw new Error("wrapOpenBadgeV2: Access to private IP ranges is not allowed");
|
|
53
|
+
}
|
|
54
|
+
if (hostname.startsWith("169.254.")) {
|
|
55
|
+
throw new Error("wrapOpenBadgeV2: Access to link-local addresses is not allowed");
|
|
56
|
+
}
|
|
57
|
+
} catch (error) {
|
|
58
|
+
if (error instanceof TypeError) {
|
|
59
|
+
throw new Error("wrapOpenBadgeV2: Invalid URL format");
|
|
60
|
+
}
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
}, "isValidAndSafeUrl");
|
|
64
|
+
async function getObv2Assertion(input) {
|
|
65
|
+
if (typeof input === "string") {
|
|
66
|
+
const trimmed = input.trim();
|
|
67
|
+
if (!isUrl(trimmed) && (trimmed.startsWith("{") || trimmed.startsWith("["))) {
|
|
68
|
+
try {
|
|
69
|
+
return JSON.parse(trimmed);
|
|
70
|
+
} catch (e) {
|
|
71
|
+
throw new Error("wrapOpenBadgeV2: Provided string is not a valid URL or JSON");
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
isValidAndSafeUrl(trimmed);
|
|
75
|
+
try {
|
|
76
|
+
const controller = new AbortController();
|
|
77
|
+
const timeoutId = setTimeout(() => controller.abort(), 1e4);
|
|
78
|
+
const res = await fetch(trimmed, {
|
|
79
|
+
signal: controller.signal
|
|
80
|
+
});
|
|
81
|
+
clearTimeout(timeoutId);
|
|
82
|
+
if (!res.ok)
|
|
83
|
+
throw new Error(`wrapOpenBadgeV2: Failed to fetch OBv2 assertion: ${res.status}`);
|
|
84
|
+
return await res.json();
|
|
85
|
+
} catch (error) {
|
|
86
|
+
if (error instanceof TypeError) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`wrapOpenBadgeV2: Network error while fetching OBv2 assertion: ${error.message}`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
if (error?.name === "AbortError") {
|
|
92
|
+
throw new Error("wrapOpenBadgeV2: Request timed out after 10 seconds");
|
|
93
|
+
}
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (typeof input !== "object" || input === null) {
|
|
98
|
+
throw new Error("wrapOpenBadgeV2: Input must be a valid object");
|
|
99
|
+
}
|
|
100
|
+
return input;
|
|
101
|
+
}
|
|
102
|
+
__name(getObv2Assertion, "getObv2Assertion");
|
|
103
|
+
function validateObv2(obv2) {
|
|
104
|
+
if (!obv2 || typeof obv2 !== "object")
|
|
105
|
+
throw new Error("wrapOpenBadgeV2: Missing assertion");
|
|
106
|
+
const id = obv2.id;
|
|
107
|
+
if (typeof id !== "string" || !id.length)
|
|
108
|
+
throw new Error("wrapOpenBadgeV2: assertion.id is required");
|
|
109
|
+
const t = obv2.type;
|
|
110
|
+
const hasAssertionType = typeof t === "string" && t === "Assertion" || Array.isArray(t) && t.includes("Assertion");
|
|
111
|
+
if (!hasAssertionType)
|
|
112
|
+
throw new Error("wrapOpenBadgeV2: assertion.type must include 'Assertion'");
|
|
113
|
+
if (!obv2.issuedOn || typeof obv2.issuedOn !== "string") {
|
|
114
|
+
throw new Error("wrapOpenBadgeV2: assertion.issuedOn (string) is required");
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
__name(validateObv2, "validateObv2");
|
|
118
|
+
var openBadgeV2Plugin = /* @__PURE__ */ __name((learnCard) => ({
|
|
119
|
+
name: "OpenBadgeV2",
|
|
120
|
+
displayName: "OpenBadge v2 Wrapper",
|
|
121
|
+
description: "Wrap legacy OpenBadge v2.0 assertions into self-issued Verifiable Credentials",
|
|
122
|
+
methods: {
|
|
123
|
+
wrapOpenBadgeV2: async (_learnCard, obv2Assertion) => {
|
|
124
|
+
const issuerDid = learnCard.id.did();
|
|
125
|
+
const obv2 = await getObv2Assertion(obv2Assertion);
|
|
126
|
+
validateObv2(obv2);
|
|
127
|
+
const unsigned = {
|
|
128
|
+
"@context": [VC_V2_CONTEXT, OBV2_WRAPPER_CONTEXT_URL],
|
|
129
|
+
id: `urn:uuid:${_learnCard.invoke.crypto().randomUUID()}`,
|
|
130
|
+
type: ["VerifiableCredential", "LegacyOpenBadgeCredential"],
|
|
131
|
+
issuer: issuerDid,
|
|
132
|
+
validFrom: new Date().toISOString(),
|
|
133
|
+
credentialSubject: {
|
|
134
|
+
id: issuerDid
|
|
135
|
+
},
|
|
136
|
+
legacyAssertion: obv2
|
|
137
|
+
};
|
|
138
|
+
return learnCard.invoke.issueCredential(unsigned, { proofPurpose: "assertionMethod" });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}), "openBadgeV2Plugin");
|
|
142
|
+
//# sourceMappingURL=open-badge-v2-plugin.cjs.development.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/index.ts", "../src/types.ts", "../src/plugin.ts"],
|
|
4
|
+
"sourcesContent": ["export { openBadgeV2Plugin } from './plugin';\nexport * from './types';\n", "import { Plugin } from '@learncard/core';\nimport { VC } from '@learncard/types';\nimport { VCPluginMethods } from '@learncard/vc-plugin';\nimport { CryptoPluginMethods } from '@learncard/crypto-plugin';\n\nexport type OpenBadgeV2PluginDependentMethods = VCPluginMethods & CryptoPluginMethods;\n\nexport type OpenBadgeV2PluginMethods = {\n /**\n * Wrap a legacy OpenBadge v2.0 assertion (object or URL) into a self-issued VC\n */\n wrapOpenBadgeV2: (obv2Assertion: object | string) => Promise<VC>;\n};\n\nexport type OpenBadgeV2Plugin = Plugin<\n 'OpenBadgeV2',\n any,\n OpenBadgeV2PluginMethods,\n 'id',\n OpenBadgeV2PluginDependentMethods\n>;\n\nexport const OBV2_WRAPPER_CONTEXT_URL = 'https://ctx.learncard.com/wrappers/obv2/1.0.0.json';\n", "import { LearnCard } from '@learncard/core';\nimport { UnsignedVC, VC } from '@learncard/types';\n\nimport {\n OpenBadgeV2Plugin,\n OBV2_WRAPPER_CONTEXT_URL,\n OpenBadgeV2PluginDependentMethods,\n} from './types';\n\nconst VC_V2_CONTEXT = 'https://www.w3.org/ns/credentials/v2';\n\nconst isUrl = (value: string): boolean =>\n /^(https?:)?\\/\\//i.test(value) || value.startsWith('ipfs://') || value.startsWith('ipns://');\n\nconst isValidAndSafeUrl = (url: string): void => {\n try {\n const parsedUrl = new URL(url);\n\n // Only allow http, https, ipfs, ipns protocols\n const allowedProtocols = ['https:', 'http:', 'ipfs:', 'ipns:'];\n if (!allowedProtocols.includes(parsedUrl.protocol)) {\n throw new Error(\n `wrapOpenBadgeV2: Protocol '${parsedUrl.protocol}' is not allowed. Only HTTP(S), IPFS, and IPNS are supported`\n );\n }\n\n // Skip internal network checks for IPFS/IPNS as they don't use traditional IP addresses\n if (parsedUrl.protocol === 'ipfs:' || parsedUrl.protocol === 'ipns:') {\n return;\n }\n\n // Block localhost and internal networks for HTTP(S)\n const hostname = parsedUrl.hostname.toLowerCase();\n\n // Block localhost variations\n if (\n hostname === 'localhost' ||\n hostname === '127.0.0.1' ||\n hostname === '0.0.0.0' ||\n hostname === '::1'\n ) {\n throw new Error('wrapOpenBadgeV2: Access to localhost is not allowed');\n }\n\n // Block private IP ranges (RFC 1918)\n if (\n hostname.startsWith('10.') ||\n hostname.startsWith('192.168.') ||\n (hostname.startsWith('172.') && /^172\\.(1[6-9]|2[0-9]|3[0-1])\\./.test(hostname))\n ) {\n throw new Error('wrapOpenBadgeV2: Access to private IP ranges is not allowed');\n }\n\n // Block link-local addresses\n if (hostname.startsWith('169.254.')) {\n throw new Error('wrapOpenBadgeV2: Access to link-local addresses is not allowed');\n }\n } catch (error) {\n if (error instanceof TypeError) {\n throw new Error('wrapOpenBadgeV2: Invalid URL format');\n }\n throw error;\n }\n};\n\nasync function getObv2Assertion(input: object | string): Promise<Record<string, any>> {\n if (typeof input === 'string') {\n // Treat string as URL. If it looks like raw JSON, try to parse for developer ergonomics\n const trimmed = input.trim();\n if (!isUrl(trimmed) && (trimmed.startsWith('{') || trimmed.startsWith('['))) {\n try {\n return JSON.parse(trimmed);\n } catch (e) {\n throw new Error('wrapOpenBadgeV2: Provided string is not a valid URL or JSON');\n }\n }\n\n // Validate URL for security\n isValidAndSafeUrl(trimmed);\n\n try {\n // Add timeout to prevent hanging requests (10 seconds)\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 10000);\n\n const res = await fetch(trimmed, {\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!res.ok)\n throw new Error(`wrapOpenBadgeV2: Failed to fetch OBv2 assertion: ${res.status}`);\n\n return await res.json();\n } catch (error: any) {\n if (error instanceof TypeError) {\n throw new Error(\n `wrapOpenBadgeV2: Network error while fetching OBv2 assertion: ${error.message}`\n );\n }\n if (error?.name === 'AbortError') {\n throw new Error('wrapOpenBadgeV2: Request timed out after 10 seconds');\n }\n throw error;\n }\n }\n\n // Add basic type validation before assertion\n if (typeof input !== 'object' || input === null) {\n throw new Error('wrapOpenBadgeV2: Input must be a valid object');\n }\n\n return input as Record<string, any>;\n}\n\nfunction validateObv2(obv2: Record<string, any>): void {\n if (!obv2 || typeof obv2 !== 'object') throw new Error('wrapOpenBadgeV2: Missing assertion');\n\n const id = obv2.id;\n if (typeof id !== 'string' || !id.length)\n throw new Error('wrapOpenBadgeV2: assertion.id is required');\n\n const t = obv2.type;\n const hasAssertionType =\n (typeof t === 'string' && t === 'Assertion') ||\n (Array.isArray(t) && t.includes('Assertion'));\n if (!hasAssertionType)\n throw new Error(\"wrapOpenBadgeV2: assertion.type must include 'Assertion'\");\n\n if (!obv2.issuedOn || typeof obv2.issuedOn !== 'string') {\n throw new Error('wrapOpenBadgeV2: assertion.issuedOn (string) is required');\n }\n}\n\nexport const openBadgeV2Plugin = (\n learnCard: LearnCard<any, 'id', OpenBadgeV2PluginDependentMethods>\n): OpenBadgeV2Plugin => ({\n name: 'OpenBadgeV2',\n displayName: 'OpenBadge v2 Wrapper',\n description: 'Wrap legacy OpenBadge v2.0 assertions into self-issued Verifiable Credentials',\n methods: {\n wrapOpenBadgeV2: async (_learnCard, obv2Assertion: object | string): Promise<VC> => {\n const issuerDid = learnCard.id.did();\n\n const obv2 = await getObv2Assertion(obv2Assertion);\n validateObv2(obv2);\n\n const unsigned: UnsignedVC = {\n '@context': [VC_V2_CONTEXT, OBV2_WRAPPER_CONTEXT_URL],\n id: `urn:uuid:${_learnCard.invoke.crypto().randomUUID()}`,\n type: ['VerifiableCredential', 'LegacyOpenBadgeCredential'],\n issuer: issuerDid,\n validFrom: new Date().toISOString(),\n credentialSubject: {\n id: issuerDid,\n },\n legacyAssertion: obv2,\n };\n\n return learnCard.invoke.issueCredential(unsigned, { proofPurpose: 'assertionMethod' });\n },\n },\n});\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";var i=Object.defineProperty;var g=Object.getOwnPropertyDescriptor;var l=Object.getOwnPropertyNames;var c=Object.prototype.hasOwnProperty;var s=(r,e)=>i(r,"name",{value:e,configurable:!0});var w=(r,e)=>{for(var t in e)i(r,t,{get:e[t],enumerable:!0})},h=(r,e,t,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of l(e))!c.call(r,n)&&n!==t&&i(r,n,{get:()=>e[n],enumerable:!(o=g(e,n))||o.enumerable});return r};var f=r=>h(i({},"__esModule",{value:!0}),r);var B={};w(B,{OBV2_WRAPPER_CONTEXT_URL:()=>a,openBadgeV2Plugin:()=>d});module.exports=f(B);var a="https://ctx.learncard.com/wrappers/obv2/1.0.0.json";var u="https://www.w3.org/ns/credentials/v2",O=s(r=>/^(https?:)?\/\//i.test(r)||r.startsWith("ipfs://")||r.startsWith("ipns://"),"isUrl"),V=s(r=>{try{let e=new URL(r);if(!["https:","http:","ipfs:","ipns:"].includes(e.protocol))throw new Error(`wrapOpenBadgeV2: Protocol '${e.protocol}' is not allowed. Only HTTP(S), IPFS, and IPNS are supported`);if(e.protocol==="ipfs:"||e.protocol==="ipns:")return;let o=e.hostname.toLowerCase();if(o==="localhost"||o==="127.0.0.1"||o==="0.0.0.0"||o==="::1")throw new Error("wrapOpenBadgeV2: Access to localhost is not allowed");if(o.startsWith("10.")||o.startsWith("192.168.")||o.startsWith("172.")&&/^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(o))throw new Error("wrapOpenBadgeV2: Access to private IP ranges is not allowed");if(o.startsWith("169.254."))throw new Error("wrapOpenBadgeV2: Access to link-local addresses is not allowed")}catch(e){throw e instanceof TypeError?new Error("wrapOpenBadgeV2: Invalid URL format"):e}},"isValidAndSafeUrl");async function m(r){if(typeof r=="string"){let e=r.trim();if(!O(e)&&(e.startsWith("{")||e.startsWith("[")))try{return JSON.parse(e)}catch{throw new Error("wrapOpenBadgeV2: Provided string is not a valid URL or JSON")}V(e);try{let t=new AbortController,o=setTimeout(()=>t.abort(),1e4),n=await fetch(e,{signal:t.signal});if(clearTimeout(o),!n.ok)throw new Error(`wrapOpenBadgeV2: Failed to fetch OBv2 assertion: ${n.status}`);return await n.json()}catch(t){throw t instanceof TypeError?new Error(`wrapOpenBadgeV2: Network error while fetching OBv2 assertion: ${t.message}`):t?.name==="AbortError"?new Error("wrapOpenBadgeV2: Request timed out after 10 seconds"):t}}if(typeof r!="object"||r===null)throw new Error("wrapOpenBadgeV2: Input must be a valid object");return r}s(m,"getObv2Assertion");function y(r){if(!r||typeof r!="object")throw new Error("wrapOpenBadgeV2: Missing assertion");let e=r.id;if(typeof e!="string"||!e.length)throw new Error("wrapOpenBadgeV2: assertion.id is required");let t=r.type;if(!(typeof t=="string"&&t==="Assertion"||Array.isArray(t)&&t.includes("Assertion")))throw new Error("wrapOpenBadgeV2: assertion.type must include 'Assertion'");if(!r.issuedOn||typeof r.issuedOn!="string")throw new Error("wrapOpenBadgeV2: assertion.issuedOn (string) is required")}s(y,"validateObv2");var d=s(r=>({name:"OpenBadgeV2",displayName:"OpenBadge v2 Wrapper",description:"Wrap legacy OpenBadge v2.0 assertions into self-issued Verifiable Credentials",methods:{wrapOpenBadgeV2:async(e,t)=>{let o=r.id.did(),n=await m(t);y(n);let p={"@context":[u,a],id:`urn:uuid:${e.invoke.crypto().randomUUID()}`,type:["VerifiableCredential","LegacyOpenBadgeCredential"],issuer:o,validFrom:new Date().toISOString(),credentialSubject:{id:o},legacyAssertion:n};return r.invoke.issueCredential(p,{proofPurpose:"assertionMethod"})}}}),"openBadgeV2Plugin");
|
|
2
|
+
//# sourceMappingURL=open-badge-v2-plugin.cjs.production.min.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/index.ts", "../src/types.ts", "../src/plugin.ts"],
|
|
4
|
+
"sourcesContent": ["export { openBadgeV2Plugin } from './plugin';\nexport * from './types';\n", "import { Plugin } from '@learncard/core';\nimport { VC } from '@learncard/types';\nimport { VCPluginMethods } from '@learncard/vc-plugin';\nimport { CryptoPluginMethods } from '@learncard/crypto-plugin';\n\nexport type OpenBadgeV2PluginDependentMethods = VCPluginMethods & CryptoPluginMethods;\n\nexport type OpenBadgeV2PluginMethods = {\n /**\n * Wrap a legacy OpenBadge v2.0 assertion (object or URL) into a self-issued VC\n */\n wrapOpenBadgeV2: (obv2Assertion: object | string) => Promise<VC>;\n};\n\nexport type OpenBadgeV2Plugin = Plugin<\n 'OpenBadgeV2',\n any,\n OpenBadgeV2PluginMethods,\n 'id',\n OpenBadgeV2PluginDependentMethods\n>;\n\nexport const OBV2_WRAPPER_CONTEXT_URL = 'https://ctx.learncard.com/wrappers/obv2/1.0.0.json';\n", "import { LearnCard } from '@learncard/core';\nimport { UnsignedVC, VC } from '@learncard/types';\n\nimport {\n OpenBadgeV2Plugin,\n OBV2_WRAPPER_CONTEXT_URL,\n OpenBadgeV2PluginDependentMethods,\n} from './types';\n\nconst VC_V2_CONTEXT = 'https://www.w3.org/ns/credentials/v2';\n\nconst isUrl = (value: string): boolean =>\n /^(https?:)?\\/\\//i.test(value) || value.startsWith('ipfs://') || value.startsWith('ipns://');\n\nconst isValidAndSafeUrl = (url: string): void => {\n try {\n const parsedUrl = new URL(url);\n\n // Only allow http, https, ipfs, ipns protocols\n const allowedProtocols = ['https:', 'http:', 'ipfs:', 'ipns:'];\n if (!allowedProtocols.includes(parsedUrl.protocol)) {\n throw new Error(\n `wrapOpenBadgeV2: Protocol '${parsedUrl.protocol}' is not allowed. Only HTTP(S), IPFS, and IPNS are supported`\n );\n }\n\n // Skip internal network checks for IPFS/IPNS as they don't use traditional IP addresses\n if (parsedUrl.protocol === 'ipfs:' || parsedUrl.protocol === 'ipns:') {\n return;\n }\n\n // Block localhost and internal networks for HTTP(S)\n const hostname = parsedUrl.hostname.toLowerCase();\n\n // Block localhost variations\n if (\n hostname === 'localhost' ||\n hostname === '127.0.0.1' ||\n hostname === '0.0.0.0' ||\n hostname === '::1'\n ) {\n throw new Error('wrapOpenBadgeV2: Access to localhost is not allowed');\n }\n\n // Block private IP ranges (RFC 1918)\n if (\n hostname.startsWith('10.') ||\n hostname.startsWith('192.168.') ||\n (hostname.startsWith('172.') && /^172\\.(1[6-9]|2[0-9]|3[0-1])\\./.test(hostname))\n ) {\n throw new Error('wrapOpenBadgeV2: Access to private IP ranges is not allowed');\n }\n\n // Block link-local addresses\n if (hostname.startsWith('169.254.')) {\n throw new Error('wrapOpenBadgeV2: Access to link-local addresses is not allowed');\n }\n } catch (error) {\n if (error instanceof TypeError) {\n throw new Error('wrapOpenBadgeV2: Invalid URL format');\n }\n throw error;\n }\n};\n\nasync function getObv2Assertion(input: object | string): Promise<Record<string, any>> {\n if (typeof input === 'string') {\n // Treat string as URL. If it looks like raw JSON, try to parse for developer ergonomics\n const trimmed = input.trim();\n if (!isUrl(trimmed) && (trimmed.startsWith('{') || trimmed.startsWith('['))) {\n try {\n return JSON.parse(trimmed);\n } catch (e) {\n throw new Error('wrapOpenBadgeV2: Provided string is not a valid URL or JSON');\n }\n }\n\n // Validate URL for security\n isValidAndSafeUrl(trimmed);\n\n try {\n // Add timeout to prevent hanging requests (10 seconds)\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 10000);\n\n const res = await fetch(trimmed, {\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!res.ok)\n throw new Error(`wrapOpenBadgeV2: Failed to fetch OBv2 assertion: ${res.status}`);\n\n return await res.json();\n } catch (error: any) {\n if (error instanceof TypeError) {\n throw new Error(\n `wrapOpenBadgeV2: Network error while fetching OBv2 assertion: ${error.message}`\n );\n }\n if (error?.name === 'AbortError') {\n throw new Error('wrapOpenBadgeV2: Request timed out after 10 seconds');\n }\n throw error;\n }\n }\n\n // Add basic type validation before assertion\n if (typeof input !== 'object' || input === null) {\n throw new Error('wrapOpenBadgeV2: Input must be a valid object');\n }\n\n return input as Record<string, any>;\n}\n\nfunction validateObv2(obv2: Record<string, any>): void {\n if (!obv2 || typeof obv2 !== 'object') throw new Error('wrapOpenBadgeV2: Missing assertion');\n\n const id = obv2.id;\n if (typeof id !== 'string' || !id.length)\n throw new Error('wrapOpenBadgeV2: assertion.id is required');\n\n const t = obv2.type;\n const hasAssertionType =\n (typeof t === 'string' && t === 'Assertion') ||\n (Array.isArray(t) && t.includes('Assertion'));\n if (!hasAssertionType)\n throw new Error(\"wrapOpenBadgeV2: assertion.type must include 'Assertion'\");\n\n if (!obv2.issuedOn || typeof obv2.issuedOn !== 'string') {\n throw new Error('wrapOpenBadgeV2: assertion.issuedOn (string) is required');\n }\n}\n\nexport const openBadgeV2Plugin = (\n learnCard: LearnCard<any, 'id', OpenBadgeV2PluginDependentMethods>\n): OpenBadgeV2Plugin => ({\n name: 'OpenBadgeV2',\n displayName: 'OpenBadge v2 Wrapper',\n description: 'Wrap legacy OpenBadge v2.0 assertions into self-issued Verifiable Credentials',\n methods: {\n wrapOpenBadgeV2: async (_learnCard, obv2Assertion: object | string): Promise<VC> => {\n const issuerDid = learnCard.id.did();\n\n const obv2 = await getObv2Assertion(obv2Assertion);\n validateObv2(obv2);\n\n const unsigned: UnsignedVC = {\n '@context': [VC_V2_CONTEXT, OBV2_WRAPPER_CONTEXT_URL],\n id: `urn:uuid:${_learnCard.invoke.crypto().randomUUID()}`,\n type: ['VerifiableCredential', 'LegacyOpenBadgeCredential'],\n issuer: issuerDid,\n validFrom: new Date().toISOString(),\n credentialSubject: {\n id: issuerDid,\n },\n legacyAssertion: obv2,\n };\n\n return learnCard.invoke.issueCredential(unsigned, { proofPurpose: 'assertionMethod' });\n },\n },\n});\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["src_exports", "__export", "OBV2_WRAPPER_CONTEXT_URL", "openBadgeV2Plugin", "__toCommonJS", "OBV2_WRAPPER_CONTEXT_URL", "VC_V2_CONTEXT", "isUrl", "__name", "value", "isValidAndSafeUrl", "url", "parsedUrl", "hostname", "error", "getObv2Assertion", "input", "trimmed", "controller", "timeoutId", "res", "validateObv2", "obv2", "id", "openBadgeV2Plugin", "learnCard", "_learnCard", "obv2Assertion", "issuerDid", "unsigned", "OBV2_WRAPPER_CONTEXT_URL"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
|
|
4
|
+
// src/types.ts
|
|
5
|
+
var OBV2_WRAPPER_CONTEXT_URL = "https://ctx.learncard.com/wrappers/obv2/1.0.0.json";
|
|
6
|
+
|
|
7
|
+
// src/plugin.ts
|
|
8
|
+
var VC_V2_CONTEXT = "https://www.w3.org/ns/credentials/v2";
|
|
9
|
+
var isUrl = /* @__PURE__ */ __name((value) => /^(https?:)?\/\//i.test(value) || value.startsWith("ipfs://") || value.startsWith("ipns://"), "isUrl");
|
|
10
|
+
var isValidAndSafeUrl = /* @__PURE__ */ __name((url) => {
|
|
11
|
+
try {
|
|
12
|
+
const parsedUrl = new URL(url);
|
|
13
|
+
const allowedProtocols = ["https:", "http:", "ipfs:", "ipns:"];
|
|
14
|
+
if (!allowedProtocols.includes(parsedUrl.protocol)) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
`wrapOpenBadgeV2: Protocol '${parsedUrl.protocol}' is not allowed. Only HTTP(S), IPFS, and IPNS are supported`
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
if (parsedUrl.protocol === "ipfs:" || parsedUrl.protocol === "ipns:") {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const hostname = parsedUrl.hostname.toLowerCase();
|
|
23
|
+
if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "0.0.0.0" || hostname === "::1") {
|
|
24
|
+
throw new Error("wrapOpenBadgeV2: Access to localhost is not allowed");
|
|
25
|
+
}
|
|
26
|
+
if (hostname.startsWith("10.") || hostname.startsWith("192.168.") || hostname.startsWith("172.") && /^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(hostname)) {
|
|
27
|
+
throw new Error("wrapOpenBadgeV2: Access to private IP ranges is not allowed");
|
|
28
|
+
}
|
|
29
|
+
if (hostname.startsWith("169.254.")) {
|
|
30
|
+
throw new Error("wrapOpenBadgeV2: Access to link-local addresses is not allowed");
|
|
31
|
+
}
|
|
32
|
+
} catch (error) {
|
|
33
|
+
if (error instanceof TypeError) {
|
|
34
|
+
throw new Error("wrapOpenBadgeV2: Invalid URL format");
|
|
35
|
+
}
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
}, "isValidAndSafeUrl");
|
|
39
|
+
async function getObv2Assertion(input) {
|
|
40
|
+
if (typeof input === "string") {
|
|
41
|
+
const trimmed = input.trim();
|
|
42
|
+
if (!isUrl(trimmed) && (trimmed.startsWith("{") || trimmed.startsWith("["))) {
|
|
43
|
+
try {
|
|
44
|
+
return JSON.parse(trimmed);
|
|
45
|
+
} catch (e) {
|
|
46
|
+
throw new Error("wrapOpenBadgeV2: Provided string is not a valid URL or JSON");
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
isValidAndSafeUrl(trimmed);
|
|
50
|
+
try {
|
|
51
|
+
const controller = new AbortController();
|
|
52
|
+
const timeoutId = setTimeout(() => controller.abort(), 1e4);
|
|
53
|
+
const res = await fetch(trimmed, {
|
|
54
|
+
signal: controller.signal
|
|
55
|
+
});
|
|
56
|
+
clearTimeout(timeoutId);
|
|
57
|
+
if (!res.ok)
|
|
58
|
+
throw new Error(`wrapOpenBadgeV2: Failed to fetch OBv2 assertion: ${res.status}`);
|
|
59
|
+
return await res.json();
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (error instanceof TypeError) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`wrapOpenBadgeV2: Network error while fetching OBv2 assertion: ${error.message}`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
if (error?.name === "AbortError") {
|
|
67
|
+
throw new Error("wrapOpenBadgeV2: Request timed out after 10 seconds");
|
|
68
|
+
}
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (typeof input !== "object" || input === null) {
|
|
73
|
+
throw new Error("wrapOpenBadgeV2: Input must be a valid object");
|
|
74
|
+
}
|
|
75
|
+
return input;
|
|
76
|
+
}
|
|
77
|
+
__name(getObv2Assertion, "getObv2Assertion");
|
|
78
|
+
function validateObv2(obv2) {
|
|
79
|
+
if (!obv2 || typeof obv2 !== "object")
|
|
80
|
+
throw new Error("wrapOpenBadgeV2: Missing assertion");
|
|
81
|
+
const id = obv2.id;
|
|
82
|
+
if (typeof id !== "string" || !id.length)
|
|
83
|
+
throw new Error("wrapOpenBadgeV2: assertion.id is required");
|
|
84
|
+
const t = obv2.type;
|
|
85
|
+
const hasAssertionType = typeof t === "string" && t === "Assertion" || Array.isArray(t) && t.includes("Assertion");
|
|
86
|
+
if (!hasAssertionType)
|
|
87
|
+
throw new Error("wrapOpenBadgeV2: assertion.type must include 'Assertion'");
|
|
88
|
+
if (!obv2.issuedOn || typeof obv2.issuedOn !== "string") {
|
|
89
|
+
throw new Error("wrapOpenBadgeV2: assertion.issuedOn (string) is required");
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
__name(validateObv2, "validateObv2");
|
|
93
|
+
var openBadgeV2Plugin = /* @__PURE__ */ __name((learnCard) => ({
|
|
94
|
+
name: "OpenBadgeV2",
|
|
95
|
+
displayName: "OpenBadge v2 Wrapper",
|
|
96
|
+
description: "Wrap legacy OpenBadge v2.0 assertions into self-issued Verifiable Credentials",
|
|
97
|
+
methods: {
|
|
98
|
+
wrapOpenBadgeV2: async (_learnCard, obv2Assertion) => {
|
|
99
|
+
const issuerDid = learnCard.id.did();
|
|
100
|
+
const obv2 = await getObv2Assertion(obv2Assertion);
|
|
101
|
+
validateObv2(obv2);
|
|
102
|
+
const unsigned = {
|
|
103
|
+
"@context": [VC_V2_CONTEXT, OBV2_WRAPPER_CONTEXT_URL],
|
|
104
|
+
id: `urn:uuid:${_learnCard.invoke.crypto().randomUUID()}`,
|
|
105
|
+
type: ["VerifiableCredential", "LegacyOpenBadgeCredential"],
|
|
106
|
+
issuer: issuerDid,
|
|
107
|
+
validFrom: new Date().toISOString(),
|
|
108
|
+
credentialSubject: {
|
|
109
|
+
id: issuerDid
|
|
110
|
+
},
|
|
111
|
+
legacyAssertion: obv2
|
|
112
|
+
};
|
|
113
|
+
return learnCard.invoke.issueCredential(unsigned, { proofPurpose: "assertionMethod" });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}), "openBadgeV2Plugin");
|
|
117
|
+
export {
|
|
118
|
+
OBV2_WRAPPER_CONTEXT_URL,
|
|
119
|
+
openBadgeV2Plugin
|
|
120
|
+
};
|
|
121
|
+
//# sourceMappingURL=open-badge-v2-plugin.esm.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/types.ts", "../src/plugin.ts"],
|
|
4
|
+
"sourcesContent": ["import { Plugin } from '@learncard/core';\nimport { VC } from '@learncard/types';\nimport { VCPluginMethods } from '@learncard/vc-plugin';\nimport { CryptoPluginMethods } from '@learncard/crypto-plugin';\n\nexport type OpenBadgeV2PluginDependentMethods = VCPluginMethods & CryptoPluginMethods;\n\nexport type OpenBadgeV2PluginMethods = {\n /**\n * Wrap a legacy OpenBadge v2.0 assertion (object or URL) into a self-issued VC\n */\n wrapOpenBadgeV2: (obv2Assertion: object | string) => Promise<VC>;\n};\n\nexport type OpenBadgeV2Plugin = Plugin<\n 'OpenBadgeV2',\n any,\n OpenBadgeV2PluginMethods,\n 'id',\n OpenBadgeV2PluginDependentMethods\n>;\n\nexport const OBV2_WRAPPER_CONTEXT_URL = 'https://ctx.learncard.com/wrappers/obv2/1.0.0.json';\n", "import { LearnCard } from '@learncard/core';\nimport { UnsignedVC, VC } from '@learncard/types';\n\nimport {\n OpenBadgeV2Plugin,\n OBV2_WRAPPER_CONTEXT_URL,\n OpenBadgeV2PluginDependentMethods,\n} from './types';\n\nconst VC_V2_CONTEXT = 'https://www.w3.org/ns/credentials/v2';\n\nconst isUrl = (value: string): boolean =>\n /^(https?:)?\\/\\//i.test(value) || value.startsWith('ipfs://') || value.startsWith('ipns://');\n\nconst isValidAndSafeUrl = (url: string): void => {\n try {\n const parsedUrl = new URL(url);\n\n // Only allow http, https, ipfs, ipns protocols\n const allowedProtocols = ['https:', 'http:', 'ipfs:', 'ipns:'];\n if (!allowedProtocols.includes(parsedUrl.protocol)) {\n throw new Error(\n `wrapOpenBadgeV2: Protocol '${parsedUrl.protocol}' is not allowed. Only HTTP(S), IPFS, and IPNS are supported`\n );\n }\n\n // Skip internal network checks for IPFS/IPNS as they don't use traditional IP addresses\n if (parsedUrl.protocol === 'ipfs:' || parsedUrl.protocol === 'ipns:') {\n return;\n }\n\n // Block localhost and internal networks for HTTP(S)\n const hostname = parsedUrl.hostname.toLowerCase();\n\n // Block localhost variations\n if (\n hostname === 'localhost' ||\n hostname === '127.0.0.1' ||\n hostname === '0.0.0.0' ||\n hostname === '::1'\n ) {\n throw new Error('wrapOpenBadgeV2: Access to localhost is not allowed');\n }\n\n // Block private IP ranges (RFC 1918)\n if (\n hostname.startsWith('10.') ||\n hostname.startsWith('192.168.') ||\n (hostname.startsWith('172.') && /^172\\.(1[6-9]|2[0-9]|3[0-1])\\./.test(hostname))\n ) {\n throw new Error('wrapOpenBadgeV2: Access to private IP ranges is not allowed');\n }\n\n // Block link-local addresses\n if (hostname.startsWith('169.254.')) {\n throw new Error('wrapOpenBadgeV2: Access to link-local addresses is not allowed');\n }\n } catch (error) {\n if (error instanceof TypeError) {\n throw new Error('wrapOpenBadgeV2: Invalid URL format');\n }\n throw error;\n }\n};\n\nasync function getObv2Assertion(input: object | string): Promise<Record<string, any>> {\n if (typeof input === 'string') {\n // Treat string as URL. If it looks like raw JSON, try to parse for developer ergonomics\n const trimmed = input.trim();\n if (!isUrl(trimmed) && (trimmed.startsWith('{') || trimmed.startsWith('['))) {\n try {\n return JSON.parse(trimmed);\n } catch (e) {\n throw new Error('wrapOpenBadgeV2: Provided string is not a valid URL or JSON');\n }\n }\n\n // Validate URL for security\n isValidAndSafeUrl(trimmed);\n\n try {\n // Add timeout to prevent hanging requests (10 seconds)\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 10000);\n\n const res = await fetch(trimmed, {\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!res.ok)\n throw new Error(`wrapOpenBadgeV2: Failed to fetch OBv2 assertion: ${res.status}`);\n\n return await res.json();\n } catch (error: any) {\n if (error instanceof TypeError) {\n throw new Error(\n `wrapOpenBadgeV2: Network error while fetching OBv2 assertion: ${error.message}`\n );\n }\n if (error?.name === 'AbortError') {\n throw new Error('wrapOpenBadgeV2: Request timed out after 10 seconds');\n }\n throw error;\n }\n }\n\n // Add basic type validation before assertion\n if (typeof input !== 'object' || input === null) {\n throw new Error('wrapOpenBadgeV2: Input must be a valid object');\n }\n\n return input as Record<string, any>;\n}\n\nfunction validateObv2(obv2: Record<string, any>): void {\n if (!obv2 || typeof obv2 !== 'object') throw new Error('wrapOpenBadgeV2: Missing assertion');\n\n const id = obv2.id;\n if (typeof id !== 'string' || !id.length)\n throw new Error('wrapOpenBadgeV2: assertion.id is required');\n\n const t = obv2.type;\n const hasAssertionType =\n (typeof t === 'string' && t === 'Assertion') ||\n (Array.isArray(t) && t.includes('Assertion'));\n if (!hasAssertionType)\n throw new Error(\"wrapOpenBadgeV2: assertion.type must include 'Assertion'\");\n\n if (!obv2.issuedOn || typeof obv2.issuedOn !== 'string') {\n throw new Error('wrapOpenBadgeV2: assertion.issuedOn (string) is required');\n }\n}\n\nexport const openBadgeV2Plugin = (\n learnCard: LearnCard<any, 'id', OpenBadgeV2PluginDependentMethods>\n): OpenBadgeV2Plugin => ({\n name: 'OpenBadgeV2',\n displayName: 'OpenBadge v2 Wrapper',\n description: 'Wrap legacy OpenBadge v2.0 assertions into self-issued Verifiable Credentials',\n methods: {\n wrapOpenBadgeV2: async (_learnCard, obv2Assertion: object | string): Promise<VC> => {\n const issuerDid = learnCard.id.did();\n\n const obv2 = await getObv2Assertion(obv2Assertion);\n validateObv2(obv2);\n\n const unsigned: UnsignedVC = {\n '@context': [VC_V2_CONTEXT, OBV2_WRAPPER_CONTEXT_URL],\n id: `urn:uuid:${_learnCard.invoke.crypto().randomUUID()}`,\n type: ['VerifiableCredential', 'LegacyOpenBadgeCredential'],\n issuer: issuerDid,\n validFrom: new Date().toISOString(),\n credentialSubject: {\n id: issuerDid,\n },\n legacyAssertion: obv2,\n };\n\n return learnCard.invoke.issueCredential(unsigned, { proofPurpose: 'assertionMethod' });\n },\n },\n});\n"],
|
|
5
|
+
"mappings": ";;;;AAsBO,IAAM,2BAA2B;;;ACbxC,IAAM,gBAAgB;AAEtB,IAAM,QAAQ,wBAAC,UACX,mBAAmB,KAAK,KAAK,KAAK,MAAM,WAAW,SAAS,KAAK,MAAM,WAAW,SAAS,GADjF;AAGd,IAAM,oBAAoB,wBAAC,QAAsB;AAC7C,MAAI;AACA,UAAM,YAAY,IAAI,IAAI,GAAG;AAG7B,UAAM,mBAAmB,CAAC,UAAU,SAAS,SAAS,OAAO;AAC7D,QAAI,CAAC,iBAAiB,SAAS,UAAU,QAAQ,GAAG;AAChD,YAAM,IAAI;AAAA,QACN,8BAA8B,UAAU;AAAA,MAC5C;AAAA,IACJ;AAGA,QAAI,UAAU,aAAa,WAAW,UAAU,aAAa,SAAS;AAClE;AAAA,IACJ;AAGA,UAAM,WAAW,UAAU,SAAS,YAAY;AAGhD,QACI,aAAa,eACb,aAAa,eACb,aAAa,aACb,aAAa,OACf;AACE,YAAM,IAAI,MAAM,qDAAqD;AAAA,IACzE;AAGA,QACI,SAAS,WAAW,KAAK,KACzB,SAAS,WAAW,UAAU,KAC7B,SAAS,WAAW,MAAM,KAAK,iCAAiC,KAAK,QAAQ,GAChF;AACE,YAAM,IAAI,MAAM,6DAA6D;AAAA,IACjF;AAGA,QAAI,SAAS,WAAW,UAAU,GAAG;AACjC,YAAM,IAAI,MAAM,gEAAgE;AAAA,IACpF;AAAA,EACJ,SAAS,OAAP;AACE,QAAI,iBAAiB,WAAW;AAC5B,YAAM,IAAI,MAAM,qCAAqC;AAAA,IACzD;AACA,UAAM;AAAA,EACV;AACJ,GAjD0B;AAmD1B,eAAe,iBAAiB,OAAsD;AAClF,MAAI,OAAO,UAAU,UAAU;AAE3B,UAAM,UAAU,MAAM,KAAK;AAC3B,QAAI,CAAC,MAAM,OAAO,MAAM,QAAQ,WAAW,GAAG,KAAK,QAAQ,WAAW,GAAG,IAAI;AACzE,UAAI;AACA,eAAO,KAAK,MAAM,OAAO;AAAA,MAC7B,SAAS,GAAP;AACE,cAAM,IAAI,MAAM,6DAA6D;AAAA,MACjF;AAAA,IACJ;AAGA,sBAAkB,OAAO;AAEzB,QAAI;AAEA,YAAM,aAAa,IAAI,gBAAgB;AACvC,YAAM,YAAY,WAAW,MAAM,WAAW,MAAM,GAAG,GAAK;AAE5D,YAAM,MAAM,MAAM,MAAM,SAAS;AAAA,QAC7B,QAAQ,WAAW;AAAA,MACvB,CAAC;AAED,mBAAa,SAAS;AAEtB,UAAI,CAAC,IAAI;AACL,cAAM,IAAI,MAAM,oDAAoD,IAAI,QAAQ;AAEpF,aAAO,MAAM,IAAI,KAAK;AAAA,IAC1B,SAAS,OAAP;AACE,UAAI,iBAAiB,WAAW;AAC5B,cAAM,IAAI;AAAA,UACN,iEAAiE,MAAM;AAAA,QAC3E;AAAA,MACJ;AACA,UAAI,OAAO,SAAS,cAAc;AAC9B,cAAM,IAAI,MAAM,qDAAqD;AAAA,MACzE;AACA,YAAM;AAAA,IACV;AAAA,EACJ;AAGA,MAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC7C,UAAM,IAAI,MAAM,+CAA+C;AAAA,EACnE;AAEA,SAAO;AACX;AAjDe;AAmDf,SAAS,aAAa,MAAiC;AACnD,MAAI,CAAC,QAAQ,OAAO,SAAS;AAAU,UAAM,IAAI,MAAM,oCAAoC;AAE3F,QAAM,KAAK,KAAK;AAChB,MAAI,OAAO,OAAO,YAAY,CAAC,GAAG;AAC9B,UAAM,IAAI,MAAM,2CAA2C;AAE/D,QAAM,IAAI,KAAK;AACf,QAAM,mBACD,OAAO,MAAM,YAAY,MAAM,eAC/B,MAAM,QAAQ,CAAC,KAAK,EAAE,SAAS,WAAW;AAC/C,MAAI,CAAC;AACD,UAAM,IAAI,MAAM,0DAA0D;AAE9E,MAAI,CAAC,KAAK,YAAY,OAAO,KAAK,aAAa,UAAU;AACrD,UAAM,IAAI,MAAM,0DAA0D;AAAA,EAC9E;AACJ;AAjBS;AAmBF,IAAM,oBAAoB,wBAC7B,eACqB;AAAA,EACrB,MAAM;AAAA,EACN,aAAa;AAAA,EACb,aAAa;AAAA,EACb,SAAS;AAAA,IACL,iBAAiB,OAAO,YAAY,kBAAgD;AAChF,YAAM,YAAY,UAAU,GAAG,IAAI;AAEnC,YAAM,OAAO,MAAM,iBAAiB,aAAa;AACjD,mBAAa,IAAI;AAEjB,YAAM,WAAuB;AAAA,QACzB,YAAY,CAAC,eAAe,wBAAwB;AAAA,QACpD,IAAI,YAAY,WAAW,OAAO,OAAO,EAAE,WAAW;AAAA,QACtD,MAAM,CAAC,wBAAwB,2BAA2B;AAAA,QAC1D,QAAQ;AAAA,QACR,WAAW,IAAI,KAAK,EAAE,YAAY;AAAA,QAClC,mBAAmB;AAAA,UACf,IAAI;AAAA,QACR;AAAA,QACA,iBAAiB;AAAA,MACrB;AAEA,aAAO,UAAU,OAAO,gBAAgB,UAAU,EAAE,cAAc,kBAAkB,CAAC;AAAA,IACzF;AAAA,EACJ;AACJ,IA5BiC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { LearnCard } from '@learncard/core';
|
|
2
|
+
import { OpenBadgeV2Plugin, OpenBadgeV2PluginDependentMethods } from './types';
|
|
3
|
+
export declare const openBadgeV2Plugin: (learnCard: LearnCard<any, "id", OpenBadgeV2PluginDependentMethods>) => OpenBadgeV2Plugin;
|
|
4
|
+
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAG5C,OAAO,EACH,iBAAiB,EAEjB,iCAAiC,EACpC,MAAM,SAAS,CAAC;AAgIjB,eAAO,MAAM,iBAAiB,cACf,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,iCAAiC,CAAC,KACnE,iBA0BD,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Plugin } from '@learncard/core';
|
|
2
|
+
import { VC } from '@learncard/types';
|
|
3
|
+
import { VCPluginMethods } from '@learncard/vc-plugin';
|
|
4
|
+
import { CryptoPluginMethods } from '@learncard/crypto-plugin';
|
|
5
|
+
export type OpenBadgeV2PluginDependentMethods = VCPluginMethods & CryptoPluginMethods;
|
|
6
|
+
export type OpenBadgeV2PluginMethods = {
|
|
7
|
+
/**
|
|
8
|
+
* Wrap a legacy OpenBadge v2.0 assertion (object or URL) into a self-issued VC
|
|
9
|
+
*/
|
|
10
|
+
wrapOpenBadgeV2: (obv2Assertion: object | string) => Promise<VC>;
|
|
11
|
+
};
|
|
12
|
+
export type OpenBadgeV2Plugin = Plugin<'OpenBadgeV2', any, OpenBadgeV2PluginMethods, 'id', OpenBadgeV2PluginDependentMethods>;
|
|
13
|
+
export declare const OBV2_WRAPPER_CONTEXT_URL = "https://ctx.learncard.com/wrappers/obv2/1.0.0.json";
|
|
14
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAE/D,MAAM,MAAM,iCAAiC,GAAG,eAAe,GAAG,mBAAmB,CAAC;AAEtF,MAAM,MAAM,wBAAwB,GAAG;IACnC;;OAEG;IACH,eAAe,EAAE,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,KAAK,OAAO,CAAC,EAAE,CAAC,CAAC;CACpE,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAClC,aAAa,EACb,GAAG,EACH,wBAAwB,EACxB,IAAI,EACJ,iCAAiC,CACpC,CAAC;AAEF,eAAO,MAAM,wBAAwB,uDAAuD,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@learncard/open-badge-v2-plugin",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "OpenBadge v2 wrapper plugin for LearnCard. Wraps OBv2 assertions into self-issued VCs.",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/open-badge-v2-plugin.esm.js",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"author": "Learning Economy Foundation (www.learningeconomy.io)",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"homepage": "https://github.com/WeLibraryOS/LearnCard#readme",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/WeLibraryOS/LearnCard.git"
|
|
16
|
+
},
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/WeLibraryOS/LearnCard/issues"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/jest": "^29.2.2",
|
|
22
|
+
"@types/node": "^17.0.31",
|
|
23
|
+
"aqu": "0.4.3",
|
|
24
|
+
"esbuild": "^0.14.38",
|
|
25
|
+
"esbuild-jest": "^0.5.0",
|
|
26
|
+
"esbuild-plugin-copy": "^1.3.0",
|
|
27
|
+
"jest": "^29.3.0",
|
|
28
|
+
"shx": "^0.3.4",
|
|
29
|
+
"ts-jest": "^29.0.3"
|
|
30
|
+
},
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@learncard/core": "9.3.35",
|
|
34
|
+
"@learncard/vc-plugin": "1.1.57",
|
|
35
|
+
"@learncard/crypto-plugin": "1.0.46",
|
|
36
|
+
"@learncard/types": "5.8.3"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "node ./scripts/build.mjs && shx cp ./scripts/mixedEntypoint.js ./dist/index.js && tsc --p tsconfig.json",
|
|
40
|
+
"test": "jest --passWithNoTests",
|
|
41
|
+
"test:watch": "jest --watch",
|
|
42
|
+
"test:coverage": "jest --silent --ci --coverage --coverageReporters=\"text\" --coverageReporters=\"text-summary\""
|
|
43
|
+
}
|
|
44
|
+
}
|