@learncard/create-http-bridge 1.1.34
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 +400 -0
- package/LICENSE +25 -0
- package/README.md +34 -0
- package/cli/App.tsx +74 -0
- package/cli/Banner.tsx +18 -0
- package/cli/Button.tsx +62 -0
- package/cli/Cloning.tsx +51 -0
- package/cli/Form.tsx +80 -0
- package/cli/FullScreenBox.tsx +19 -0
- package/cli/Info.tsx +79 -0
- package/cli/Input.tsx +33 -0
- package/cli/curriedStateSlice.ts +565 -0
- package/cli/index.tsx +17 -0
- package/cli/random.ts +3 -0
- package/cli/types.ts +8 -0
- package/dist/index.js +803 -0
- package/esbuildWasmPlugin.cjs +31 -0
- package/lambda.ts +18 -0
- package/package.json +56 -0
- package/project.json +22 -0
- package/rollup.config.js +12 -0
- package/serverless.yml +56 -0
- package/src/app.ts +297 -0
- package/src/didkit_wasm_bg.wasm +0 -0
- package/src/learn-card.ts +13 -0
- package/src/types.helpers.ts +7 -0
- package/src/validators.ts +59 -0
- package/tsconfig.json +36 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const wasmPlugin = {
|
|
2
|
+
name: 'wasm',
|
|
3
|
+
setup(build) {
|
|
4
|
+
let path = require('path');
|
|
5
|
+
let fs = require('fs');
|
|
6
|
+
|
|
7
|
+
// Resolve ".wasm" files to a path with a namespace
|
|
8
|
+
build.onResolve({ filter: /\.wasm$/ }, args => {
|
|
9
|
+
if (args.resolveDir === '') {
|
|
10
|
+
return; // Ignore unresolvable paths
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
path: path.isAbsolute(args.path)
|
|
14
|
+
? args.path
|
|
15
|
+
: path.join(args.resolveDir, args.path),
|
|
16
|
+
namespace: 'wasm-binary',
|
|
17
|
+
};
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// Virtual modules in the "wasm-binary" namespace contain the
|
|
21
|
+
// actual bytes of the WebAssembly file. This uses esbuild's
|
|
22
|
+
// built-in "binary" loader instead of manually embedding the
|
|
23
|
+
// binary data inside JavaScript code ourselves.
|
|
24
|
+
build.onLoad({ filter: /.*/, namespace: 'wasm-binary' }, async args => ({
|
|
25
|
+
contents: await fs.promises.readFile(args.path),
|
|
26
|
+
loader: 'binary',
|
|
27
|
+
}));
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
module.exports = [wasmPlugin];
|
package/lambda.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { APIGatewayProxyHandlerV2 } from 'aws-lambda';
|
|
2
|
+
import serverlessHttp from 'serverless-http';
|
|
3
|
+
|
|
4
|
+
import app from './src/app';
|
|
5
|
+
import { getLearnCard } from './src/learn-card';
|
|
6
|
+
|
|
7
|
+
const _handler = serverlessHttp(app);
|
|
8
|
+
|
|
9
|
+
export const handler: APIGatewayProxyHandlerV2 = async (event, context) => {
|
|
10
|
+
if ((event as any).source === 'serverless-plugin-warmup') {
|
|
11
|
+
console.log('[Warmup] Initializing wallet...');
|
|
12
|
+
await getLearnCard(); // Intentionally don't wait for the wallet to init!
|
|
13
|
+
console.log('[Warmup] Done!');
|
|
14
|
+
return 'All done! 😄';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return _handler(event, context);
|
|
18
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@learncard/create-http-bridge",
|
|
3
|
+
"version": "1.1.34",
|
|
4
|
+
"description": "Instantly create and deploy a Learn Card Bridge HTTP API via AWS Lambda!",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": "dist/index.js",
|
|
7
|
+
"author": "Learning Economy Foundation (www.learningeconomy.io)",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@rollup/plugin-json": "^4.1.0",
|
|
11
|
+
"commander": "^9.3.0",
|
|
12
|
+
"cors": "^2.8.5",
|
|
13
|
+
"express": "~4.16.1",
|
|
14
|
+
"figlet": "^1.5.2",
|
|
15
|
+
"immer": "^9.0.15",
|
|
16
|
+
"ink": "^3.2.0",
|
|
17
|
+
"ink-gradient": "^2.0.0",
|
|
18
|
+
"ink-spinner": "^4.0.3",
|
|
19
|
+
"ink-syntax-highlight": "^1.0.1",
|
|
20
|
+
"ink-text-input": "^4.0.3",
|
|
21
|
+
"ink-use-stdout-dimensions": "^1.0.5",
|
|
22
|
+
"inquirer": "^8.2.4",
|
|
23
|
+
"lookpath": "^1.2.2",
|
|
24
|
+
"react": "^18.2.0",
|
|
25
|
+
"rollup": "^2.71.1",
|
|
26
|
+
"rollup-plugin-esbuild": "^4.9.1",
|
|
27
|
+
"serverless-esbuild": "^1.25.0",
|
|
28
|
+
"serverless-http": "^3.0.1",
|
|
29
|
+
"serverless-plugin-warmup": "^8.1.0",
|
|
30
|
+
"simple-git": "^3.9.0",
|
|
31
|
+
"use-immer": "^0.7.0",
|
|
32
|
+
"zod": "^3.20.2",
|
|
33
|
+
"@learncard/core": "8.5.4",
|
|
34
|
+
"@learncard/types": "5.2.6"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/aws-lambda": "^8.10.106",
|
|
38
|
+
"@types/cors": "^2.8.12",
|
|
39
|
+
"@types/express": "^4.17.13",
|
|
40
|
+
"@types/figlet": "^1.5.4",
|
|
41
|
+
"@types/ink-gradient": "^2.0.1",
|
|
42
|
+
"@types/inquirer": "^8.2.1",
|
|
43
|
+
"@types/node": "^18.0.0",
|
|
44
|
+
"@types/react": "^17.0.2",
|
|
45
|
+
"nodemon": "^2.0.16",
|
|
46
|
+
"serverless-plugin-typescript": "^2.1.0",
|
|
47
|
+
"shx": "^0.3.4",
|
|
48
|
+
"ts-node": "^10.8.1",
|
|
49
|
+
"typescript": "^4.7.4"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"serverless-deploy": "shx cp ../learn-card-core/src/didkit/pkg/didkit_wasm_bg.wasm src/didkit_wasm_bg.wasm && serverless deploy",
|
|
53
|
+
"start": "rollup -c && node dist/index.js",
|
|
54
|
+
"build": "rollup -c"
|
|
55
|
+
}
|
|
56
|
+
}
|
package/project.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
|
3
|
+
"name": "create-http-bridge",
|
|
4
|
+
"sourceRoot": "packages/learn-card-bridge-http/src",
|
|
5
|
+
"projectType": "library",
|
|
6
|
+
"root": "packages/learn-card-bridge-http",
|
|
7
|
+
"tags": [],
|
|
8
|
+
"implicitDependencies": ["types", "core"],
|
|
9
|
+
"namedInputs": {
|
|
10
|
+
"source": ["{projectRoot}/src/**/*"],
|
|
11
|
+
"dist": ["{projectRoot}/dist/**/*"]
|
|
12
|
+
},
|
|
13
|
+
"targets": {
|
|
14
|
+
"build": {
|
|
15
|
+
"executor": "nx:run-script",
|
|
16
|
+
"inputs": ["source"],
|
|
17
|
+
"options": {
|
|
18
|
+
"script": "build"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
package/rollup.config.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import json from '@rollup/plugin-json';
|
|
2
|
+
import esbuild from 'rollup-plugin-esbuild';
|
|
3
|
+
|
|
4
|
+
const packageJson = require('./package.json');
|
|
5
|
+
|
|
6
|
+
export default [
|
|
7
|
+
{
|
|
8
|
+
input: ['cli/index.tsx'],
|
|
9
|
+
output: [{ file: packageJson.bin, format: 'cjs', banner: '#!/usr/bin/env node' }],
|
|
10
|
+
plugins: [json(), esbuild()],
|
|
11
|
+
},
|
|
12
|
+
];
|
package/serverless.yml
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# serverless.yml
|
|
2
|
+
|
|
3
|
+
useDotenv: true
|
|
4
|
+
|
|
5
|
+
service: learn-card-http-bridge
|
|
6
|
+
|
|
7
|
+
plugins:
|
|
8
|
+
- serverless-esbuild
|
|
9
|
+
- serverless-plugin-warmup
|
|
10
|
+
|
|
11
|
+
custom:
|
|
12
|
+
esbuild:
|
|
13
|
+
packager: pnpm
|
|
14
|
+
plugins: esbuildWasmPlugin.cjs
|
|
15
|
+
warmup:
|
|
16
|
+
default:
|
|
17
|
+
enabled: true
|
|
18
|
+
|
|
19
|
+
provider:
|
|
20
|
+
name: aws
|
|
21
|
+
runtime: nodejs14.x
|
|
22
|
+
stage: dev
|
|
23
|
+
region: us-east-1
|
|
24
|
+
timeout: 29
|
|
25
|
+
httpApi:
|
|
26
|
+
cors: true
|
|
27
|
+
environment:
|
|
28
|
+
WALLET_SEED: ${env:WALLET_SEED}
|
|
29
|
+
|
|
30
|
+
functions:
|
|
31
|
+
api:
|
|
32
|
+
handler: lambda.handler
|
|
33
|
+
memorySize: 2048
|
|
34
|
+
events:
|
|
35
|
+
- httpApi: 'GET /'
|
|
36
|
+
- httpApi: 'GET /did'
|
|
37
|
+
- httpApi: 'GET /credentials'
|
|
38
|
+
- httpApi: 'GET /credentials/{id}'
|
|
39
|
+
- httpApi: 'DELETE /credentials/{id}'
|
|
40
|
+
- httpApi: 'POST /credentials/issue'
|
|
41
|
+
- httpApi: 'POST /credentials/status'
|
|
42
|
+
- httpApi: 'POST /credentials/verify'
|
|
43
|
+
- httpApi: 'POST /credentials/derive'
|
|
44
|
+
- httpApi: 'POST /presentations/issue'
|
|
45
|
+
- httpApi: 'POST /presentations/verify'
|
|
46
|
+
- httpApi: 'POST /presentations/prove'
|
|
47
|
+
- httpApi: 'GET /presentations'
|
|
48
|
+
- httpApi: 'GET /presentations/{id}'
|
|
49
|
+
- httpApi: 'DELETE /presentations/{id}'
|
|
50
|
+
- httpApi: 'POST /exchanges/{exchangeId}'
|
|
51
|
+
- httpApi: 'POST /exchanges/{exchangeId}/{transactionId}'
|
|
52
|
+
warmup:
|
|
53
|
+
default:
|
|
54
|
+
enabled:
|
|
55
|
+
- dev
|
|
56
|
+
- prod
|
package/src/app.ts
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import cors from 'cors';
|
|
3
|
+
import { VP, VPValidator } from '@learncard/types';
|
|
4
|
+
|
|
5
|
+
import { TypedRequest } from './types.helpers';
|
|
6
|
+
import {
|
|
7
|
+
IssueEndpoint,
|
|
8
|
+
IssueEndpointValidator,
|
|
9
|
+
IssuePresentationEndpointValidator,
|
|
10
|
+
UpdateStatusEndpoint,
|
|
11
|
+
VerifyCredentialEndpoint,
|
|
12
|
+
VerifyCredentialEndpointValidator,
|
|
13
|
+
VerifyPresentationEndpoint,
|
|
14
|
+
VerifyPresentationEndpointValidator,
|
|
15
|
+
} from './validators';
|
|
16
|
+
import { getLearnCard } from './learn-card';
|
|
17
|
+
|
|
18
|
+
const router = express.Router();
|
|
19
|
+
|
|
20
|
+
const app = express();
|
|
21
|
+
|
|
22
|
+
app.use(cors());
|
|
23
|
+
app.use(express.json());
|
|
24
|
+
|
|
25
|
+
app.get('/', async (_req: TypedRequest<{}>, res) => {
|
|
26
|
+
res.sendStatus(501);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// This is non-standard! But very helpful for the VC-API plugin
|
|
30
|
+
app.get('/did', async (_req: TypedRequest<{}>, res) => {
|
|
31
|
+
const learnCard = await getLearnCard();
|
|
32
|
+
|
|
33
|
+
res.status(200).send(learnCard.id.did());
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
app.get('/credentials', async (_req: TypedRequest<{}>, res) => {
|
|
37
|
+
res.sendStatus(501);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
app.get('/credentials/:id', async (req: TypedRequest<{}>, res) => {
|
|
41
|
+
console.log('credentials' + req.params.id);
|
|
42
|
+
res.sendStatus(501);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
app.delete('/credentials/:id', async (_req: TypedRequest<{}>, res) => {
|
|
46
|
+
res.sendStatus(501);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
app.post('/credentials/issue', async (req: TypedRequest<IssueEndpoint>, res) => {
|
|
50
|
+
try {
|
|
51
|
+
// If incoming credential doesn't have an issuanceDate, default it to right now
|
|
52
|
+
if (req.body?.credential && !('issuanceDate' in (req.body?.credential ?? {}))) {
|
|
53
|
+
req.body.credential.issuanceDate = new Date().toISOString();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const validationResult = await IssueEndpointValidator.spa(req.body);
|
|
57
|
+
|
|
58
|
+
if (!validationResult.success) {
|
|
59
|
+
console.error(
|
|
60
|
+
'[/credentials/issue] Validation error: ',
|
|
61
|
+
validationResult.error.message,
|
|
62
|
+
'(received: ',
|
|
63
|
+
req.body,
|
|
64
|
+
')'
|
|
65
|
+
);
|
|
66
|
+
return res.status(400).json(`Invalid input: ${validationResult.error.message}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const validatedBody = validationResult.data;
|
|
70
|
+
const learnCard = await getLearnCard();
|
|
71
|
+
const { credentialStatus, ...options } = validatedBody.options ?? {};
|
|
72
|
+
|
|
73
|
+
const issuedCredential = await learnCard.invoke.issueCredential(
|
|
74
|
+
validatedBody.credential,
|
|
75
|
+
options
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
return res.status(201).json(issuedCredential);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error('[/credentials/issue] Caught error: ', error, '(received: ', req.body);
|
|
81
|
+
return res.status(400).json(`Invalid input: ${error}`);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
app.post('/credentials/status', async (_req: TypedRequest<UpdateStatusEndpoint>, res) => {
|
|
86
|
+
res.sendStatus(501);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
app.post('/credentials/verify', async (req: TypedRequest<VerifyCredentialEndpoint>, res) => {
|
|
90
|
+
try {
|
|
91
|
+
const validationResult = await VerifyCredentialEndpointValidator.spa(req.body);
|
|
92
|
+
|
|
93
|
+
if (!validationResult.success) {
|
|
94
|
+
console.error(
|
|
95
|
+
'[/credentials/verify] Validation error: ',
|
|
96
|
+
validationResult.error.message,
|
|
97
|
+
'(received: ',
|
|
98
|
+
req.body,
|
|
99
|
+
')'
|
|
100
|
+
);
|
|
101
|
+
return res.status(400).json(`Invalid input: ${validationResult.error.message}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const validatedBody = validationResult.data;
|
|
105
|
+
const learnCard = await getLearnCard();
|
|
106
|
+
|
|
107
|
+
const verificationResult = await learnCard.invoke.verifyCredential(
|
|
108
|
+
validatedBody.verifiableCredential,
|
|
109
|
+
validatedBody.options
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
if (verificationResult.errors.length > 0) {
|
|
113
|
+
console.error(
|
|
114
|
+
'[/credentials/verify] Verification error(s): ',
|
|
115
|
+
verificationResult.errors,
|
|
116
|
+
'(received: ',
|
|
117
|
+
req.body
|
|
118
|
+
);
|
|
119
|
+
return res.status(400).json(verificationResult);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Pass a test for interop 🙃
|
|
123
|
+
verificationResult.checks = verificationResult.checks.filter(
|
|
124
|
+
check => check !== 'expiration'
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
return res.status(200).json(verificationResult);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.error('[/credentials/verify] Caught error: ', error, '(received: ', req.body);
|
|
130
|
+
return res.status(400).json(`Invalid input: ${error}`);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
app.post('/credentials/derive', async (_req: TypedRequest<{}>, res) => {
|
|
135
|
+
res.sendStatus(501);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// This is non-standard! But very helpful for the VC-API plugin
|
|
139
|
+
app.post('/presentations/issue', async (req: TypedRequest<IssueEndpoint>, res) => {
|
|
140
|
+
try {
|
|
141
|
+
const validationResult = await IssuePresentationEndpointValidator.spa(req.body);
|
|
142
|
+
|
|
143
|
+
if (!validationResult.success) {
|
|
144
|
+
console.error(
|
|
145
|
+
'[/presentations/issue] Validation error: ',
|
|
146
|
+
validationResult.error.message,
|
|
147
|
+
'(received: ',
|
|
148
|
+
req.body,
|
|
149
|
+
')'
|
|
150
|
+
);
|
|
151
|
+
return res.status(400).json(`Invalid input: ${validationResult.error.message}`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const validatedBody = validationResult.data;
|
|
155
|
+
const learnCard = await getLearnCard();
|
|
156
|
+
|
|
157
|
+
const issuedPresentation = await learnCard.invoke.issuePresentation(
|
|
158
|
+
validatedBody.presentation,
|
|
159
|
+
validatedBody.options
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
return res.status(201).json(issuedPresentation);
|
|
163
|
+
} catch (error) {
|
|
164
|
+
console.error('[/presentations/issue] Caught error: ', error, '(received: ', req.body);
|
|
165
|
+
return res.status(400).json(`Invalid input: ${error}`);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
app.post('/presentations/verify', async (req: TypedRequest<VerifyPresentationEndpoint>, res) => {
|
|
170
|
+
try {
|
|
171
|
+
const validationResult = await VerifyPresentationEndpointValidator.spa(req.body);
|
|
172
|
+
|
|
173
|
+
if (!validationResult.success) {
|
|
174
|
+
console.error(
|
|
175
|
+
'[/presentations/verify] Validation error: ',
|
|
176
|
+
validationResult.error.message,
|
|
177
|
+
'(received: ',
|
|
178
|
+
req.body,
|
|
179
|
+
')'
|
|
180
|
+
);
|
|
181
|
+
return res.status(400).json(`Invalid input: ${validationResult.error.message}`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const validatedBody = validationResult.data;
|
|
185
|
+
const learnCard = await getLearnCard();
|
|
186
|
+
|
|
187
|
+
if ('presentation' in validatedBody) return res.sendStatus(501);
|
|
188
|
+
|
|
189
|
+
const verificationResult = await learnCard.invoke.verifyPresentation(
|
|
190
|
+
validatedBody.verifiablePresentation,
|
|
191
|
+
validatedBody.options
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
if (verificationResult.errors.length > 0) {
|
|
195
|
+
console.error(
|
|
196
|
+
'[/presentations/verify] Verification error(s): ',
|
|
197
|
+
verificationResult.errors,
|
|
198
|
+
'(received: ',
|
|
199
|
+
req.body
|
|
200
|
+
);
|
|
201
|
+
return res.status(400).json(verificationResult);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return res.status(200).json(verificationResult);
|
|
205
|
+
} catch (error) {
|
|
206
|
+
console.error('[/presentations/verify] Caught error: ', error, '(received: ', req.body);
|
|
207
|
+
return res.status(400).json(`Invalid input: ${error}`);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
app.post('/presentations/prove', async (_req: TypedRequest<{}>, res) => {
|
|
212
|
+
res.sendStatus(501);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
app.get('/presentations', async (_req: TypedRequest<{}>, res) => {
|
|
216
|
+
res.sendStatus(501);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
app.get('/presentations/:id', async (_req: TypedRequest<{}>, res) => {
|
|
220
|
+
res.sendStatus(501);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
app.delete('/presentations/:id', async (_req: TypedRequest<{}>, res) => {
|
|
224
|
+
res.sendStatus(501);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
app.post('/exchanges/:uri', async (req: TypedRequest<VP, { challenge?: string }>, res) => {
|
|
228
|
+
try {
|
|
229
|
+
const validationResult = await VPValidator.spa(req.body);
|
|
230
|
+
|
|
231
|
+
if (!validationResult.success) {
|
|
232
|
+
console.error(
|
|
233
|
+
'[/exchanges/:uri] Validation error: ',
|
|
234
|
+
validationResult.error.message,
|
|
235
|
+
'(received: ',
|
|
236
|
+
req.body,
|
|
237
|
+
')'
|
|
238
|
+
);
|
|
239
|
+
return res.status(400).json(`Invalid input: ${validationResult.error.message}`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const validatedBody = validationResult.data;
|
|
243
|
+
const learnCard = await getLearnCard();
|
|
244
|
+
const challenge = Array.isArray(validatedBody.proof)
|
|
245
|
+
? validatedBody.proof[0]?.challenge
|
|
246
|
+
: validatedBody.proof.challenge;
|
|
247
|
+
|
|
248
|
+
const verification = await learnCard.invoke.verifyPresentation(validatedBody, {
|
|
249
|
+
challenge,
|
|
250
|
+
proofPurpose: 'authentication',
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
if (verification.warnings.length > 0 || verification.errors.length > 0) {
|
|
254
|
+
console.error(
|
|
255
|
+
'[/exchanges/:uri] Validation error: ',
|
|
256
|
+
verification,
|
|
257
|
+
'(received: ',
|
|
258
|
+
req.body,
|
|
259
|
+
')'
|
|
260
|
+
);
|
|
261
|
+
return res
|
|
262
|
+
.status(400)
|
|
263
|
+
.json({ reason: 'Invalid DID Auth VP', verificationResult: verification });
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const subject = validatedBody.holder;
|
|
267
|
+
|
|
268
|
+
const credential = await learnCard.read.get(req.params.uri);
|
|
269
|
+
|
|
270
|
+
credential.issuer = {
|
|
271
|
+
...(typeof credential.issuer === 'string' ? {} : credential.issuer),
|
|
272
|
+
id: learnCard.id.did(),
|
|
273
|
+
};
|
|
274
|
+
credential.issuanceDate = new Date().toISOString();
|
|
275
|
+
|
|
276
|
+
if (!Array.isArray(credential.credentialSubject) && subject) {
|
|
277
|
+
credential.credentialSubject.id = subject;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
delete credential.proof;
|
|
281
|
+
|
|
282
|
+
const newVc = await learnCard.invoke.issueCredential(credential);
|
|
283
|
+
|
|
284
|
+
return res.status(201).json(newVc);
|
|
285
|
+
} catch (error) {
|
|
286
|
+
console.error('[/exchanges/:uri] Caught error: ', error, '(received: ', req.body);
|
|
287
|
+
return res.status(400).json(`Invalid input: ${error}`);
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
app.post('/exchanges/:exchangeId/:transactionId', async (_req: TypedRequest<{}>, res) => {
|
|
292
|
+
res.sendStatus(501);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
app.use('', router);
|
|
296
|
+
|
|
297
|
+
export default app;
|
|
Binary file
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import { initLearnCard } from '@learncard/core';
|
|
3
|
+
import didkit from './didkit_wasm_bg.wasm';
|
|
4
|
+
|
|
5
|
+
export const getLearnCard = async () => {
|
|
6
|
+
const seed = process.env.WALLET_SEED;
|
|
7
|
+
|
|
8
|
+
if (!seed) {
|
|
9
|
+
throw new Error('No seed set! Please make a .env file and set WALLET_SEED to your seed!');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return initLearnCard({ seed, didkit });
|
|
13
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import {
|
|
3
|
+
UnsignedVCValidator,
|
|
4
|
+
UnsignedVPValidator,
|
|
5
|
+
VCValidator,
|
|
6
|
+
VPValidator,
|
|
7
|
+
} from '@learncard/types';
|
|
8
|
+
|
|
9
|
+
export const IssueEndpointValidator = z.object({
|
|
10
|
+
credential: UnsignedVCValidator,
|
|
11
|
+
options: z
|
|
12
|
+
.object({
|
|
13
|
+
created: z.string().optional(),
|
|
14
|
+
challenge: z.string().optional(),
|
|
15
|
+
domain: z.string().optional(),
|
|
16
|
+
credentialStatus: z.object({ type: z.string() }).optional(),
|
|
17
|
+
})
|
|
18
|
+
.optional(),
|
|
19
|
+
});
|
|
20
|
+
export type IssueEndpoint = z.infer<typeof IssueEndpointValidator>;
|
|
21
|
+
|
|
22
|
+
export const IssuePresentationEndpointValidator = z.object({
|
|
23
|
+
presentation: UnsignedVPValidator,
|
|
24
|
+
options: z
|
|
25
|
+
.object({
|
|
26
|
+
created: z.string().optional(),
|
|
27
|
+
challenge: z.string().optional(),
|
|
28
|
+
domain: z.string().optional(),
|
|
29
|
+
})
|
|
30
|
+
.optional(),
|
|
31
|
+
});
|
|
32
|
+
export type IssuePresentationEndpoint = z.infer<typeof IssuePresentationEndpointValidator>;
|
|
33
|
+
|
|
34
|
+
export const UpdateStatusEndpointValidator = z.object({
|
|
35
|
+
credentialId: z.string(),
|
|
36
|
+
credentialStatus: z
|
|
37
|
+
.object({ type: z.string().optional(), status: z.string().optional() })
|
|
38
|
+
.array()
|
|
39
|
+
.optional(),
|
|
40
|
+
});
|
|
41
|
+
export type UpdateStatusEndpoint = z.infer<typeof UpdateStatusEndpointValidator>;
|
|
42
|
+
|
|
43
|
+
export const VerifyCredentialEndpointValidator = z.object({
|
|
44
|
+
verifiableCredential: VCValidator,
|
|
45
|
+
options: z
|
|
46
|
+
.object({ challenge: z.string().optional(), domain: z.string().optional() })
|
|
47
|
+
.optional(),
|
|
48
|
+
});
|
|
49
|
+
export type VerifyCredentialEndpoint = z.infer<typeof VerifyCredentialEndpointValidator>;
|
|
50
|
+
|
|
51
|
+
export const VerifyPresentationEndpointValidator = z
|
|
52
|
+
.object({
|
|
53
|
+
verifiablePresentation: VPValidator,
|
|
54
|
+
options: z
|
|
55
|
+
.object({ challenge: z.string().optional(), domain: z.string().optional() })
|
|
56
|
+
.optional(),
|
|
57
|
+
})
|
|
58
|
+
.or(z.object({ presentation: UnsignedVPValidator }));
|
|
59
|
+
export type VerifyPresentationEndpoint = z.infer<typeof VerifyPresentationEndpointValidator>;
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"lib": ["es2017"],
|
|
4
|
+
"module": "esnext",
|
|
5
|
+
"moduleResolution": "node",
|
|
6
|
+
"resolveJsonModule": true,
|
|
7
|
+
"removeComments": true,
|
|
8
|
+
"preserveConstEnums": true,
|
|
9
|
+
"strict": true,
|
|
10
|
+
"alwaysStrict": true,
|
|
11
|
+
"strictNullChecks": true,
|
|
12
|
+
"noUncheckedIndexedAccess": true,
|
|
13
|
+
"jsx": "react",
|
|
14
|
+
|
|
15
|
+
"noImplicitAny": true,
|
|
16
|
+
"noImplicitReturns": true,
|
|
17
|
+
"noImplicitThis": true,
|
|
18
|
+
"noUnusedLocals": true,
|
|
19
|
+
"noUnusedParameters": true,
|
|
20
|
+
"allowUnreachableCode": false,
|
|
21
|
+
"noFallthroughCasesInSwitch": true,
|
|
22
|
+
|
|
23
|
+
"target": "es2017",
|
|
24
|
+
"outDir": "dist",
|
|
25
|
+
"declaration": true,
|
|
26
|
+
"sourceMap": true,
|
|
27
|
+
"baseUrl": ".",
|
|
28
|
+
|
|
29
|
+
"esModuleInterop": true,
|
|
30
|
+
"allowSyntheticDefaultImports": true,
|
|
31
|
+
"allowJs": false,
|
|
32
|
+
"skipLibCheck": true,
|
|
33
|
+
"forceConsistentCasingInFileNames": true
|
|
34
|
+
},
|
|
35
|
+
"exclude": ["./dist/**/*", "./node_modules/**/*"]
|
|
36
|
+
}
|