@learncard/create-http-bridge 1.1.253 → 1.1.254
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/CHANGELOG.md +17 -0
- package/dist/index.js +2 -1
- package/package.json +3 -2
- package/scripts/sync-didkit.ts +1 -1
- package/src/app.ts +165 -132
- package/src/didkit_wasm_bg.wasm +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @learncard/create-http-bridge
|
|
2
2
|
|
|
3
|
+
## 1.1.254
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#1540](https://github.com/learningeconomy/LearnCard/pull/1540) [`55284a9b1613d46491a03d2d527b192eb2db64c9`](https://github.com/learningeconomy/LearnCard/commit/55284a9b1613d46491a03d2d527b192eb2db64c9) Thanks [@TaylorBeeston](https://github.com/TaylorBeeston)! - Rebuild DIDKit consumers from the LC-2160 Rust dependency remediation, removing vulnerable legacy dependency branches. Preserve seeded Ed25519 identities and JWE interoperability. Preserve DIDKit's independent workspace lock during WASM builds, and validate native prebuilds for SSI/DIDKit pin changes.
|
|
8
|
+
|
|
9
|
+
RSA Marvin (RUSTSEC-2023-0071) remains unresolved in rsa 0.9.10. Deployment requires separate authorized risk approval. Hosted WASM publication and its immutable default URL must be updated before this release ships.
|
|
10
|
+
|
|
11
|
+
- [#1549](https://github.com/learningeconomy/LearnCard/pull/1549) [`9cc246bd0571b5b29c60b9dcff287c1b69e012cf`](https://github.com/learningeconomy/LearnCard/commit/9cc246bd0571b5b29c60b9dcff287c1b69e012cf) Thanks [@rhen92](https://github.com/rhen92)! - chore: [LC-2175] & [LC-2176] Fixing CodeQL alerts
|
|
12
|
+
|
|
13
|
+
**Breaking change in @learncard/openid4vc-plugin**: `defaultMakeId` now throws if
|
|
14
|
+
`crypto.getRandomValues` is unavailable (no `Math.random()` fallback). This affects
|
|
15
|
+
legacy environments without Web Crypto API — modern browsers and Node.js are unaffected.
|
|
16
|
+
|
|
17
|
+
- Updated dependencies []:
|
|
18
|
+
- @learncard/init@2.4.14
|
|
19
|
+
|
|
3
20
|
## 1.1.253
|
|
4
21
|
|
|
5
22
|
### Patch Changes
|
package/dist/index.js
CHANGED
|
@@ -416,7 +416,7 @@ const App = ({ name = "LearnCardHTTPBridge" }) => {
|
|
|
416
416
|
};
|
|
417
417
|
|
|
418
418
|
var name = "@learncard/create-http-bridge";
|
|
419
|
-
var version = "1.1.
|
|
419
|
+
var version = "1.1.254";
|
|
420
420
|
var description = "Instantly create and deploy a Learn Card Bridge HTTP API via AWS Lambda!";
|
|
421
421
|
var main = "dist/index.js";
|
|
422
422
|
var bin = "dist/index.js";
|
|
@@ -436,6 +436,7 @@ var dependencies = {
|
|
|
436
436
|
commander: "^9.3.0",
|
|
437
437
|
cors: "^2.8.5",
|
|
438
438
|
express: "~4.21.0",
|
|
439
|
+
"express-rate-limit": "^7.1.5",
|
|
439
440
|
figlet: "^1.5.2",
|
|
440
441
|
immer: "^9.0.15",
|
|
441
442
|
ink: "^3.2.0",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@learncard/create-http-bridge",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.254",
|
|
4
4
|
"description": "Instantly create and deploy a Learn Card Bridge HTTP API via AWS Lambda!",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": "dist/index.js",
|
|
@@ -14,12 +14,13 @@
|
|
|
14
14
|
"author": "Learning Economy Foundation (www.learningeconomy.io)",
|
|
15
15
|
"license": "MIT",
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@learncard/init": "2.4.
|
|
17
|
+
"@learncard/init": "2.4.14",
|
|
18
18
|
"@learncard/types": "5.18.3",
|
|
19
19
|
"@rollup/plugin-json": "^4.1.0",
|
|
20
20
|
"commander": "^9.3.0",
|
|
21
21
|
"cors": "^2.8.5",
|
|
22
22
|
"express": "~4.21.0",
|
|
23
|
+
"express-rate-limit": "^7.1.5",
|
|
23
24
|
"figlet": "^1.5.2",
|
|
24
25
|
"immer": "^9.0.15",
|
|
25
26
|
"ink": "^3.2.0",
|
package/scripts/sync-didkit.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'crypto';
|
|
2
2
|
import { readFile, writeFile } from 'fs/promises';
|
|
3
3
|
|
|
4
|
-
const EXPECTED_DIDKIT_SHA256 = '
|
|
4
|
+
const EXPECTED_DIDKIT_SHA256 = '38e9e39677846cfec7f0521cc23d707e2c9e87ae9981f988d78747f023bf64ee';
|
|
5
5
|
const sourceUrl = new URL(
|
|
6
6
|
'../../plugins/didkit/src/didkit/pkg/didkit_wasm_bg.wasm',
|
|
7
7
|
import.meta.url
|
package/src/app.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import express from 'express';
|
|
2
2
|
import cors from 'cors';
|
|
3
|
+
import rateLimit from 'express-rate-limit';
|
|
3
4
|
import { VCValidator, VPValidator, type UnsignedVC, type VP } from '@learncard/types';
|
|
4
5
|
|
|
5
6
|
import { TypedRequest } from './types.helpers';
|
|
@@ -15,6 +16,15 @@ import {
|
|
|
15
16
|
} from './validators';
|
|
16
17
|
import { getLearnCard } from './learn-card';
|
|
17
18
|
|
|
19
|
+
// Rate limiter for routes that perform expensive signing/verification operations
|
|
20
|
+
const authRateLimiter = rateLimit({
|
|
21
|
+
windowMs: 60 * 1000, // 1 minute window
|
|
22
|
+
limit: 100, // limit each IP to 100 requests per windowMs
|
|
23
|
+
message: 'Too many requests, please try again later.',
|
|
24
|
+
standardHeaders: true,
|
|
25
|
+
legacyHeaders: false,
|
|
26
|
+
});
|
|
27
|
+
|
|
18
28
|
const router = express.Router();
|
|
19
29
|
|
|
20
30
|
const app = express();
|
|
@@ -22,6 +32,15 @@ const app = express();
|
|
|
22
32
|
const W3C_V1_CREDENTIALS_CONTEXT = 'https://www.w3.org/2018/credentials/v1';
|
|
23
33
|
const W3C_ALT_V1_CREDENTIALS_CONTEXT = 'https://w3.org/2018/credentials/v1';
|
|
24
34
|
|
|
35
|
+
// Trust proxy for correct IP detection behind load balancers.
|
|
36
|
+
// Only enable when running behind a reverse proxy (nginx, AWS ALB, etc.)
|
|
37
|
+
// Without a real proxy, this allows clients to spoof X-Forwarded-For and bypass rate limits.
|
|
38
|
+
// Set TRUST_PROXY=1 (or number of proxy hops) when behind a load balancer.
|
|
39
|
+
if (process.env.TRUST_PROXY) {
|
|
40
|
+
const trustProxy = parseInt(process.env.TRUST_PROXY, 10);
|
|
41
|
+
app.set('trust proxy', isNaN(trustProxy) ? process.env.TRUST_PROXY : trustProxy);
|
|
42
|
+
}
|
|
43
|
+
|
|
25
44
|
app.use(cors());
|
|
26
45
|
app.use(express.json());
|
|
27
46
|
|
|
@@ -49,7 +68,7 @@ app.delete('/credentials/:id', async (_req: TypedRequest<{}>, res) => {
|
|
|
49
68
|
res.sendStatus(501);
|
|
50
69
|
});
|
|
51
70
|
|
|
52
|
-
app.post('/credentials/issue', async (req: TypedRequest<IssueEndpoint>, res) => {
|
|
71
|
+
app.post('/credentials/issue', authRateLimiter, async (req: TypedRequest<IssueEndpoint>, res) => {
|
|
53
72
|
try {
|
|
54
73
|
const validationResult = await IssueEndpointValidator.spa(req.body);
|
|
55
74
|
|
|
@@ -99,52 +118,56 @@ app.post('/credentials/status', async (_req: TypedRequest<UpdateStatusEndpoint>,
|
|
|
99
118
|
res.sendStatus(501);
|
|
100
119
|
});
|
|
101
120
|
|
|
102
|
-
app.post(
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
121
|
+
app.post(
|
|
122
|
+
'/credentials/verify',
|
|
123
|
+
authRateLimiter,
|
|
124
|
+
async (req: TypedRequest<VerifyCredentialEndpoint>, res) => {
|
|
125
|
+
try {
|
|
126
|
+
const validationResult = await VerifyCredentialEndpointValidator.spa(req.body);
|
|
127
|
+
|
|
128
|
+
if (!validationResult.success) {
|
|
129
|
+
console.error(
|
|
130
|
+
'[/credentials/verify] Validation error: ',
|
|
131
|
+
validationResult.error.message,
|
|
132
|
+
'(received: ',
|
|
133
|
+
req.body,
|
|
134
|
+
')'
|
|
135
|
+
);
|
|
136
|
+
return res.status(400).json(`Invalid input: ${validationResult.error.message}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const validatedBody = validationResult.data;
|
|
140
|
+
const learnCard = await getLearnCard();
|
|
141
|
+
|
|
142
|
+
const verificationResult = await learnCard.invoke.verifyCredential(
|
|
143
|
+
validatedBody.verifiableCredential,
|
|
144
|
+
validatedBody.options
|
|
113
145
|
);
|
|
114
|
-
return res.status(400).json(`Invalid input: ${validationResult.error.message}`);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const validatedBody = validationResult.data;
|
|
118
|
-
const learnCard = await getLearnCard();
|
|
119
|
-
|
|
120
|
-
const verificationResult = await learnCard.invoke.verifyCredential(
|
|
121
|
-
validatedBody.verifiableCredential,
|
|
122
|
-
validatedBody.options
|
|
123
|
-
);
|
|
124
146
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
147
|
+
if (verificationResult.errors.length > 0) {
|
|
148
|
+
console.error(
|
|
149
|
+
'[/credentials/verify] Verification error(s): ',
|
|
150
|
+
verificationResult.errors,
|
|
151
|
+
'(received: ',
|
|
152
|
+
req.body
|
|
153
|
+
);
|
|
154
|
+
return res.status(400).json(verificationResult);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return res.status(200).json(verificationResult);
|
|
158
|
+
} catch (error) {
|
|
159
|
+
console.error('[/credentials/verify] Caught error: ', error, '(received: ', req.body);
|
|
160
|
+
return res.status(400).json(`Invalid input: ${error}`);
|
|
133
161
|
}
|
|
134
|
-
|
|
135
|
-
return res.status(200).json(verificationResult);
|
|
136
|
-
} catch (error) {
|
|
137
|
-
console.error('[/credentials/verify] Caught error: ', error, '(received: ', req.body);
|
|
138
|
-
return res.status(400).json(`Invalid input: ${error}`);
|
|
139
162
|
}
|
|
140
|
-
|
|
163
|
+
);
|
|
141
164
|
|
|
142
165
|
app.post('/credentials/derive', async (_req: TypedRequest<{}>, res) => {
|
|
143
166
|
res.sendStatus(501);
|
|
144
167
|
});
|
|
145
168
|
|
|
146
169
|
// This is non-standard! But very helpful for the VC-API plugin
|
|
147
|
-
app.post('/presentations/issue', async (req: TypedRequest<IssueEndpoint>, res) => {
|
|
170
|
+
app.post('/presentations/issue', authRateLimiter, async (req: TypedRequest<IssueEndpoint>, res) => {
|
|
148
171
|
try {
|
|
149
172
|
const validationResult = await IssuePresentationEndpointValidator.spa(req.body);
|
|
150
173
|
|
|
@@ -174,47 +197,51 @@ app.post('/presentations/issue', async (req: TypedRequest<IssueEndpoint>, res) =
|
|
|
174
197
|
}
|
|
175
198
|
});
|
|
176
199
|
|
|
177
|
-
app.post(
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
200
|
+
app.post(
|
|
201
|
+
'/presentations/verify',
|
|
202
|
+
authRateLimiter,
|
|
203
|
+
async (req: TypedRequest<VerifyPresentationEndpoint>, res) => {
|
|
204
|
+
try {
|
|
205
|
+
const validationResult = await VerifyPresentationEndpointValidator.spa(req.body);
|
|
206
|
+
|
|
207
|
+
if (!validationResult.success) {
|
|
208
|
+
console.error(
|
|
209
|
+
'[/presentations/verify] Validation error: ',
|
|
210
|
+
validationResult.error.message,
|
|
211
|
+
'(received: ',
|
|
212
|
+
req.body,
|
|
213
|
+
')'
|
|
214
|
+
);
|
|
215
|
+
return res.status(400).json(`Invalid input: ${validationResult.error.message}`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const validatedBody = validationResult.data;
|
|
219
|
+
const learnCard = await getLearnCard();
|
|
220
|
+
|
|
221
|
+
if ('presentation' in validatedBody) return res.sendStatus(501);
|
|
222
|
+
|
|
223
|
+
const verificationResult = await learnCard.invoke.verifyPresentation(
|
|
224
|
+
validatedBody.verifiablePresentation,
|
|
225
|
+
validatedBody.options
|
|
188
226
|
);
|
|
189
|
-
return res.status(400).json(`Invalid input: ${validationResult.error.message}`);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const validatedBody = validationResult.data;
|
|
193
|
-
const learnCard = await getLearnCard();
|
|
194
|
-
|
|
195
|
-
if ('presentation' in validatedBody) return res.sendStatus(501);
|
|
196
|
-
|
|
197
|
-
const verificationResult = await learnCard.invoke.verifyPresentation(
|
|
198
|
-
validatedBody.verifiablePresentation,
|
|
199
|
-
validatedBody.options
|
|
200
|
-
);
|
|
201
227
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
228
|
+
if (verificationResult.errors.length > 0) {
|
|
229
|
+
console.error(
|
|
230
|
+
'[/presentations/verify] Verification error(s): ',
|
|
231
|
+
verificationResult.errors,
|
|
232
|
+
'(received: ',
|
|
233
|
+
req.body
|
|
234
|
+
);
|
|
235
|
+
return res.status(400).json(verificationResult);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return res.status(200).json(verificationResult);
|
|
239
|
+
} catch (error) {
|
|
240
|
+
console.error('[/presentations/verify] Caught error: ', error, '(received: ', req.body);
|
|
241
|
+
return res.status(400).json(`Invalid input: ${error}`);
|
|
210
242
|
}
|
|
211
|
-
|
|
212
|
-
return res.status(200).json(verificationResult);
|
|
213
|
-
} catch (error) {
|
|
214
|
-
console.error('[/presentations/verify] Caught error: ', error, '(received: ', req.body);
|
|
215
|
-
return res.status(400).json(`Invalid input: ${error}`);
|
|
216
243
|
}
|
|
217
|
-
|
|
244
|
+
);
|
|
218
245
|
|
|
219
246
|
app.post('/presentations/prove', async (_req: TypedRequest<{}>, res) => {
|
|
220
247
|
res.sendStatus(501);
|
|
@@ -232,74 +259,80 @@ app.delete('/presentations/:id', async (_req: TypedRequest<{}>, res) => {
|
|
|
232
259
|
res.sendStatus(501);
|
|
233
260
|
});
|
|
234
261
|
|
|
235
|
-
app.post(
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
262
|
+
app.post(
|
|
263
|
+
'/exchanges/:uri',
|
|
264
|
+
authRateLimiter,
|
|
265
|
+
async (req: TypedRequest<VP, { challenge?: string }>, res) => {
|
|
266
|
+
try {
|
|
267
|
+
const validationResult = await VPValidator.spa(req.body);
|
|
268
|
+
|
|
269
|
+
if (!validationResult.success) {
|
|
270
|
+
console.error(
|
|
271
|
+
'[/exchanges/:uri] Validation error: ',
|
|
272
|
+
validationResult.error.message,
|
|
273
|
+
'(received: ',
|
|
274
|
+
req.body,
|
|
275
|
+
')'
|
|
276
|
+
);
|
|
277
|
+
return res.status(400).json(`Invalid input: ${validationResult.error.message}`);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const validatedBody = validationResult.data;
|
|
281
|
+
const learnCard = await getLearnCard();
|
|
282
|
+
const challenge = Array.isArray(validatedBody.proof)
|
|
283
|
+
? validatedBody.proof[0]?.challenge
|
|
284
|
+
: validatedBody.proof.challenge;
|
|
285
|
+
|
|
286
|
+
const verification = await learnCard.invoke.verifyPresentation(validatedBody, {
|
|
287
|
+
challenge,
|
|
288
|
+
proofPurpose: 'authentication',
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
if (verification.warnings.length > 0 || verification.errors.length > 0) {
|
|
292
|
+
console.error(
|
|
293
|
+
'[/exchanges/:uri] Validation error: ',
|
|
294
|
+
verification,
|
|
295
|
+
'(received: ',
|
|
296
|
+
req.body,
|
|
297
|
+
')'
|
|
298
|
+
);
|
|
299
|
+
return res
|
|
300
|
+
.status(400)
|
|
301
|
+
.json({ reason: 'Invalid DID Auth VP', verificationResult: verification });
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const subject = validatedBody.holder;
|
|
305
|
+
|
|
306
|
+
const credentialResult = VCValidator.safeParse(
|
|
307
|
+
await learnCard.read.get(req.params.uri)
|
|
246
308
|
);
|
|
247
|
-
return res.status(400).json(`Invalid input: ${validationResult.error.message}`);
|
|
248
|
-
}
|
|
249
309
|
|
|
250
|
-
|
|
251
|
-
const learnCard = await getLearnCard();
|
|
252
|
-
const challenge = Array.isArray(validatedBody.proof)
|
|
253
|
-
? validatedBody.proof[0]?.challenge
|
|
254
|
-
: validatedBody.proof.challenge;
|
|
310
|
+
if (!credentialResult.success) return res.status(404).json('Credential not found');
|
|
255
311
|
|
|
256
|
-
|
|
257
|
-
challenge,
|
|
258
|
-
proofPurpose: 'authentication',
|
|
259
|
-
});
|
|
312
|
+
const credential = credentialResult.data;
|
|
260
313
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
req.body,
|
|
267
|
-
')'
|
|
268
|
-
);
|
|
269
|
-
return res
|
|
270
|
-
.status(400)
|
|
271
|
-
.json({ reason: 'Invalid DID Auth VP', verificationResult: verification });
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
const subject = validatedBody.holder;
|
|
275
|
-
|
|
276
|
-
const credentialResult = VCValidator.safeParse(await learnCard.read.get(req.params.uri));
|
|
314
|
+
credential.issuer = {
|
|
315
|
+
...(typeof credential.issuer === 'string' ? {} : credential.issuer),
|
|
316
|
+
id: learnCard.id.did(),
|
|
317
|
+
};
|
|
318
|
+
credential.issuanceDate = new Date().toISOString();
|
|
277
319
|
|
|
278
|
-
|
|
320
|
+
if (!Array.isArray(credential.credentialSubject) && subject) {
|
|
321
|
+
credential.credentialSubject.id = subject;
|
|
322
|
+
}
|
|
279
323
|
|
|
280
|
-
|
|
324
|
+
const { proof, ...unsignedCredential } = credential;
|
|
325
|
+
void proof;
|
|
281
326
|
|
|
282
|
-
|
|
283
|
-
...(typeof credential.issuer === 'string' ? {} : credential.issuer),
|
|
284
|
-
id: learnCard.id.did(),
|
|
285
|
-
};
|
|
286
|
-
credential.issuanceDate = new Date().toISOString();
|
|
327
|
+
const newVc = await learnCard.invoke.issueCredential(unsignedCredential);
|
|
287
328
|
|
|
288
|
-
|
|
289
|
-
|
|
329
|
+
return res.status(201).json(newVc);
|
|
330
|
+
} catch (error) {
|
|
331
|
+
console.error('[/exchanges/:uri] Caught error: ', error, '(received: ', req.body);
|
|
332
|
+
return res.status(400).json(`Invalid input: ${error}`);
|
|
290
333
|
}
|
|
291
|
-
|
|
292
|
-
const { proof, ...unsignedCredential } = credential;
|
|
293
|
-
void proof;
|
|
294
|
-
|
|
295
|
-
const newVc = await learnCard.invoke.issueCredential(unsignedCredential);
|
|
296
|
-
|
|
297
|
-
return res.status(201).json(newVc);
|
|
298
|
-
} catch (error) {
|
|
299
|
-
console.error('[/exchanges/:uri] Caught error: ', error, '(received: ', req.body);
|
|
300
|
-
return res.status(400).json(`Invalid input: ${error}`);
|
|
301
334
|
}
|
|
302
|
-
|
|
335
|
+
);
|
|
303
336
|
|
|
304
337
|
app.post('/exchanges/:exchangeId/:transactionId', async (_req: TypedRequest<{}>, res) => {
|
|
305
338
|
res.sendStatus(501);
|
package/src/didkit_wasm_bg.wasm
CHANGED
|
Binary file
|