@blink-authority-com/claude-code-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 +146 -0
- package/dist/bsec-client.d.ts +35 -0
- package/dist/bsec-client.d.ts.map +1 -0
- package/dist/bsec-client.js +122 -0
- package/dist/bsec-client.js.map +1 -0
- package/dist/docs/cache.d.ts +19 -0
- package/dist/docs/cache.d.ts.map +1 -0
- package/dist/docs/cache.js +92 -0
- package/dist/docs/cache.js.map +1 -0
- package/dist/docs/fetcher.d.ts +19 -0
- package/dist/docs/fetcher.d.ts.map +1 -0
- package/dist/docs/fetcher.js +81 -0
- package/dist/docs/fetcher.js.map +1 -0
- package/dist/docs/parser.d.ts +33 -0
- package/dist/docs/parser.d.ts.map +1 -0
- package/dist/docs/parser.js +145 -0
- package/dist/docs/parser.js.map +1 -0
- package/dist/docs/search.d.ts +38 -0
- package/dist/docs/search.d.ts.map +1 -0
- package/dist/docs/search.js +180 -0
- package/dist/docs/search.js.map +1 -0
- package/dist/docs/tools/api.d.ts +12 -0
- package/dist/docs/tools/api.d.ts.map +1 -0
- package/dist/docs/tools/api.js +58 -0
- package/dist/docs/tools/api.js.map +1 -0
- package/dist/docs/tools/cli.d.ts +12 -0
- package/dist/docs/tools/cli.d.ts.map +1 -0
- package/dist/docs/tools/cli.js +56 -0
- package/dist/docs/tools/cli.js.map +1 -0
- package/dist/docs/tools/concepts.d.ts +18 -0
- package/dist/docs/tools/concepts.d.ts.map +1 -0
- package/dist/docs/tools/concepts.js +136 -0
- package/dist/docs/tools/concepts.js.map +1 -0
- package/dist/docs/tools/sdk.d.ts +12 -0
- package/dist/docs/tools/sdk.d.ts.map +1 -0
- package/dist/docs/tools/sdk.js +72 -0
- package/dist/docs/tools/sdk.js.map +1 -0
- package/dist/docs/tools/search.d.ts +11 -0
- package/dist/docs/tools/search.d.ts.map +1 -0
- package/dist/docs/tools/search.js +36 -0
- package/dist/docs/tools/search.js.map +1 -0
- package/dist/docs/types.d.ts +70 -0
- package/dist/docs/types.d.ts.map +1 -0
- package/dist/docs/types.js +8 -0
- package/dist/docs/types.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +622 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts/debug.d.ts +16 -0
- package/dist/prompts/debug.d.ts.map +1 -0
- package/dist/prompts/debug.js +34 -0
- package/dist/prompts/debug.js.map +1 -0
- package/dist/prompts/integrate.d.ts +16 -0
- package/dist/prompts/integrate.d.ts.map +1 -0
- package/dist/prompts/integrate.js +30 -0
- package/dist/prompts/integrate.js.map +1 -0
- package/dist/prompts/review.d.ts +16 -0
- package/dist/prompts/review.d.ts.map +1 -0
- package/dist/prompts/review.js +28 -0
- package/dist/prompts/review.js.map +1 -0
- package/dist/resources/changelog.d.ts +22 -0
- package/dist/resources/changelog.d.ts.map +1 -0
- package/dist/resources/changelog.js +55 -0
- package/dist/resources/changelog.js.map +1 -0
- package/dist/resources/errors.d.ts +16 -0
- package/dist/resources/errors.d.ts.map +1 -0
- package/dist/resources/errors.js +103 -0
- package/dist/resources/errors.js.map +1 -0
- package/dist/resources/patterns.d.ts +16 -0
- package/dist/resources/patterns.d.ts.map +1 -0
- package/dist/resources/patterns.js +151 -0
- package/dist/resources/patterns.js.map +1 -0
- package/dist/templates/approval-workflow.d.ts +6 -0
- package/dist/templates/approval-workflow.d.ts.map +1 -0
- package/dist/templates/approval-workflow.js +467 -0
- package/dist/templates/approval-workflow.js.map +1 -0
- package/dist/templates/audit-trail.d.ts +6 -0
- package/dist/templates/audit-trail.d.ts.map +1 -0
- package/dist/templates/audit-trail.js +396 -0
- package/dist/templates/audit-trail.js.map +1 -0
- package/dist/templates/cli-tool.d.ts +6 -0
- package/dist/templates/cli-tool.d.ts.map +1 -0
- package/dist/templates/cli-tool.js +346 -0
- package/dist/templates/cli-tool.js.map +1 -0
- package/dist/templates/express-api.d.ts +6 -0
- package/dist/templates/express-api.d.ts.map +1 -0
- package/dist/templates/express-api.js +314 -0
- package/dist/templates/express-api.js.map +1 -0
- package/dist/templates/notary.d.ts +6 -0
- package/dist/templates/notary.d.ts.map +1 -0
- package/dist/templates/notary.js +360 -0
- package/dist/templates/notary.js.map +1 -0
- package/dist/test.d.ts +8 -0
- package/dist/test.d.ts.map +1 -0
- package/dist/test.js +140 -0
- package/dist/test.js.map +1 -0
- package/dist/tools/add-signing.d.ts +19 -0
- package/dist/tools/add-signing.d.ts.map +1 -0
- package/dist/tools/add-signing.js +317 -0
- package/dist/tools/add-signing.js.map +1 -0
- package/dist/tools/decode.d.ts +13 -0
- package/dist/tools/decode.d.ts.map +1 -0
- package/dist/tools/decode.js +262 -0
- package/dist/tools/decode.js.map +1 -0
- package/dist/tools/hierarchy.d.ts +13 -0
- package/dist/tools/hierarchy.d.ts.map +1 -0
- package/dist/tools/hierarchy.js +319 -0
- package/dist/tools/hierarchy.js.map +1 -0
- package/dist/tools/lint.d.ts +13 -0
- package/dist/tools/lint.d.ts.map +1 -0
- package/dist/tools/lint.js +323 -0
- package/dist/tools/lint.js.map +1 -0
- package/dist/tools/scaffold.d.ts +11 -0
- package/dist/tools/scaffold.d.ts.map +1 -0
- package/dist/tools/scaffold.js +84 -0
- package/dist/tools/scaffold.js.map +1 -0
- package/dist/tools/sign-test.d.ts +17 -0
- package/dist/tools/sign-test.d.ts.map +1 -0
- package/dist/tools/sign-test.js +126 -0
- package/dist/tools/sign-test.js.map +1 -0
- package/dist/tools/test-vectors.d.ts +13 -0
- package/dist/tools/test-vectors.d.ts.map +1 -0
- package/dist/tools/test-vectors.js +169 -0
- package/dist/tools/test-vectors.js.map +1 -0
- package/dist/tools/verify-local.d.ts +17 -0
- package/dist/tools/verify-local.d.ts.map +1 -0
- package/dist/tools/verify-local.js +233 -0
- package/dist/tools/verify-local.js.map +1 -0
- package/dist/tools/verify-test.d.ts +17 -0
- package/dist/tools/verify-test.d.ts.map +1 -0
- package/dist/tools/verify-test.js +107 -0
- package/dist/tools/verify-test.js.map +1 -0
- package/package.json +54 -0
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Approval workflow scaffold template.
|
|
3
|
+
* Generates an M-of-N signing workflow with BLINK signatures.
|
|
4
|
+
*/
|
|
5
|
+
export function generateApprovalWorkflow(name, features) {
|
|
6
|
+
const files = {};
|
|
7
|
+
// --- package.json ---
|
|
8
|
+
files['package.json'] = JSON.stringify({
|
|
9
|
+
name,
|
|
10
|
+
version: '0.1.0',
|
|
11
|
+
description: `${name} — M-of-N approval workflow with BLINK Authority signatures`,
|
|
12
|
+
type: 'module',
|
|
13
|
+
scripts: {
|
|
14
|
+
build: 'tsc',
|
|
15
|
+
start: 'node dist/server.js',
|
|
16
|
+
dev: 'tsx src/server.ts',
|
|
17
|
+
},
|
|
18
|
+
dependencies: {
|
|
19
|
+
'@blink-technology/sdk': '^0.14.0',
|
|
20
|
+
express: '^4.21.0',
|
|
21
|
+
dotenv: '^16.4.0',
|
|
22
|
+
},
|
|
23
|
+
devDependencies: {
|
|
24
|
+
'@types/express': '^4.17.21',
|
|
25
|
+
'@types/node': '^22.15.2',
|
|
26
|
+
typescript: '^5.8.3',
|
|
27
|
+
tsx: '^4.19.0',
|
|
28
|
+
},
|
|
29
|
+
}, null, 2);
|
|
30
|
+
// --- tsconfig.json ---
|
|
31
|
+
files['tsconfig.json'] = JSON.stringify({
|
|
32
|
+
compilerOptions: {
|
|
33
|
+
target: 'ES2022',
|
|
34
|
+
module: 'Node16',
|
|
35
|
+
moduleResolution: 'Node16',
|
|
36
|
+
outDir: 'dist',
|
|
37
|
+
rootDir: 'src',
|
|
38
|
+
strict: true,
|
|
39
|
+
esModuleInterop: true,
|
|
40
|
+
skipLibCheck: true,
|
|
41
|
+
forceConsistentCasingInFileNames: true,
|
|
42
|
+
declaration: true,
|
|
43
|
+
sourceMap: true,
|
|
44
|
+
},
|
|
45
|
+
include: ['src/**/*'],
|
|
46
|
+
exclude: ['node_modules', 'dist'],
|
|
47
|
+
}, null, 2);
|
|
48
|
+
// --- .env.example ---
|
|
49
|
+
files['.env.example'] = `# BLINK Secure Engine connection
|
|
50
|
+
BSEC_URL=http://localhost:9100
|
|
51
|
+
BSEC_TOKEN=your-bsec-token-here
|
|
52
|
+
SLOT_NAME=default
|
|
53
|
+
|
|
54
|
+
# Server
|
|
55
|
+
PORT=3000
|
|
56
|
+
|
|
57
|
+
# Approval policy
|
|
58
|
+
REQUIRED_APPROVALS=2
|
|
59
|
+
`;
|
|
60
|
+
// --- src/blink.ts ---
|
|
61
|
+
files['src/blink.ts'] = `/**
|
|
62
|
+
* BLINK Secure Engine connection management.
|
|
63
|
+
*
|
|
64
|
+
* BLINK Authority — https://blink-authority.com
|
|
65
|
+
*/
|
|
66
|
+
|
|
67
|
+
import { BlinkSigner, BlinkVerifier, AlgoSuiteId } from '@blink-technology/sdk';
|
|
68
|
+
|
|
69
|
+
let signer: BlinkSigner;
|
|
70
|
+
let vrfPublicKey: Uint8Array;
|
|
71
|
+
|
|
72
|
+
export async function initBlink(bsecUrl: string, slotName: string): Promise<void> {
|
|
73
|
+
signer = await BlinkSigner.connect(bsecUrl, slotName, AlgoSuiteId.Ed25519);
|
|
74
|
+
vrfPublicKey = await signer.vrfPublicKey();
|
|
75
|
+
console.log(
|
|
76
|
+
\`[blink] Connected to BSEC at \${bsecUrl} \` +
|
|
77
|
+
\`(slot=\${slotName}, pk=\${Buffer.from(vrfPublicKey).toString('hex').slice(0, 16)}...)\`,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function getSigner(): BlinkSigner { return signer; }
|
|
82
|
+
export function getVrfPublicKey(): Uint8Array { return vrfPublicKey; }
|
|
83
|
+
export function getVrfPublicKeyHex(): string { return Buffer.from(vrfPublicKey).toString('hex'); }
|
|
84
|
+
|
|
85
|
+
export function createVerifier(): BlinkVerifier {
|
|
86
|
+
return new BlinkVerifier(vrfPublicKey, {
|
|
87
|
+
freshnessWindowMs: 365 * 24 * 60 * 60 * 1000,
|
|
88
|
+
clockSkewToleranceMs: 60_000,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export async function shutdownBlink(): Promise<void> {
|
|
93
|
+
if (signer) {
|
|
94
|
+
await signer.close();
|
|
95
|
+
console.log('[blink] Disconnected from BLINK Secure Engine');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
`;
|
|
99
|
+
// --- src/policy.ts ---
|
|
100
|
+
files['src/policy.ts'] = `/**
|
|
101
|
+
* Approval policy engine.
|
|
102
|
+
*
|
|
103
|
+
* Defines who can approve and how many approvals are needed (M-of-N).
|
|
104
|
+
* In production, load policies from a database or config file.
|
|
105
|
+
*/
|
|
106
|
+
|
|
107
|
+
export interface ApprovalPolicy {
|
|
108
|
+
/** Minimum number of approvals required. */
|
|
109
|
+
requiredApprovals: number;
|
|
110
|
+
/** List of authorized approver IDs. */
|
|
111
|
+
authorizedApprovers: string[];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Default policy — override via environment or config
|
|
115
|
+
const defaultPolicy: ApprovalPolicy = {
|
|
116
|
+
requiredApprovals: parseInt(process.env['REQUIRED_APPROVALS'] ?? '2', 10),
|
|
117
|
+
authorizedApprovers: ['approver-1', 'approver-2', 'approver-3'],
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/** Get the current approval policy. */
|
|
121
|
+
export function getPolicy(): ApprovalPolicy {
|
|
122
|
+
return defaultPolicy;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Check if an approver is authorized. */
|
|
126
|
+
export function isAuthorized(approverId: string): boolean {
|
|
127
|
+
return defaultPolicy.authorizedApprovers.includes(approverId);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Check if the approval threshold has been met. */
|
|
131
|
+
export function thresholdMet(approvalCount: number): boolean {
|
|
132
|
+
return approvalCount >= defaultPolicy.requiredApprovals;
|
|
133
|
+
}
|
|
134
|
+
`;
|
|
135
|
+
// --- src/multi-sign.ts ---
|
|
136
|
+
files['src/multi-sign.ts'] = `/**
|
|
137
|
+
* Multi-signature (M-of-N) signing logic.
|
|
138
|
+
*
|
|
139
|
+
* Each approval produces an individual BLINK signature.
|
|
140
|
+
* When the threshold is met, a final "composite" signature
|
|
141
|
+
* is created that references all individual approvals.
|
|
142
|
+
*/
|
|
143
|
+
|
|
144
|
+
import { randomUUID } from 'node:crypto';
|
|
145
|
+
import { getSigner, getVrfPublicKeyHex } from './blink.js';
|
|
146
|
+
|
|
147
|
+
export interface Approval {
|
|
148
|
+
id: string;
|
|
149
|
+
requestId: string;
|
|
150
|
+
approverId: string;
|
|
151
|
+
approverName: string;
|
|
152
|
+
comment?: string;
|
|
153
|
+
timestamp: string;
|
|
154
|
+
envelope: string;
|
|
155
|
+
signerPublicKey: string;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export interface ApprovalRequest {
|
|
159
|
+
id: string;
|
|
160
|
+
title: string;
|
|
161
|
+
description: string;
|
|
162
|
+
requiredApprovals: number;
|
|
163
|
+
approvals: Approval[];
|
|
164
|
+
status: 'pending' | 'approved' | 'rejected';
|
|
165
|
+
createdAt: string;
|
|
166
|
+
finalizedAt?: string;
|
|
167
|
+
finalEnvelope?: string;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Build the canonical payload for an individual approval signature.
|
|
172
|
+
*/
|
|
173
|
+
export function buildApprovalPayload(approval: Omit<Approval, 'envelope' | 'signerPublicKey'>): Uint8Array {
|
|
174
|
+
const canonical = {
|
|
175
|
+
type: 'approval',
|
|
176
|
+
id: approval.id,
|
|
177
|
+
requestId: approval.requestId,
|
|
178
|
+
approverId: approval.approverId,
|
|
179
|
+
approverName: approval.approverName,
|
|
180
|
+
timestamp: approval.timestamp,
|
|
181
|
+
...(approval.comment ? { comment: approval.comment } : {}),
|
|
182
|
+
};
|
|
183
|
+
return new TextEncoder().encode(JSON.stringify(canonical));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Build the canonical payload for the final composite signature.
|
|
188
|
+
* Includes the request details and all approval IDs.
|
|
189
|
+
*/
|
|
190
|
+
export function buildFinalPayload(request: ApprovalRequest): Uint8Array {
|
|
191
|
+
const canonical = {
|
|
192
|
+
type: 'final_approval',
|
|
193
|
+
id: request.id,
|
|
194
|
+
title: request.title,
|
|
195
|
+
description: request.description,
|
|
196
|
+
requiredApprovals: request.requiredApprovals,
|
|
197
|
+
approvalIds: request.approvals.map(a => a.id),
|
|
198
|
+
finalizedAt: request.finalizedAt,
|
|
199
|
+
};
|
|
200
|
+
return new TextEncoder().encode(JSON.stringify(canonical));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Sign an individual approval.
|
|
205
|
+
*/
|
|
206
|
+
export async function signApproval(
|
|
207
|
+
requestId: string,
|
|
208
|
+
approverId: string,
|
|
209
|
+
approverName: string,
|
|
210
|
+
comment?: string,
|
|
211
|
+
): Promise<Approval> {
|
|
212
|
+
const partial: Omit<Approval, 'envelope' | 'signerPublicKey'> = {
|
|
213
|
+
id: \`APR-\${randomUUID()}\`,
|
|
214
|
+
requestId,
|
|
215
|
+
approverId,
|
|
216
|
+
approverName,
|
|
217
|
+
comment,
|
|
218
|
+
timestamp: new Date().toISOString(),
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const payload = buildApprovalPayload(partial);
|
|
222
|
+
const envelopeBytes = await getSigner().sign(payload);
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
...partial,
|
|
226
|
+
envelope: Buffer.from(envelopeBytes).toString('base64'),
|
|
227
|
+
signerPublicKey: getVrfPublicKeyHex(),
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Create the final composite signature once the approval threshold is met.
|
|
233
|
+
*/
|
|
234
|
+
export async function signFinal(request: ApprovalRequest): Promise<string> {
|
|
235
|
+
request.finalizedAt = new Date().toISOString();
|
|
236
|
+
const payload = buildFinalPayload(request);
|
|
237
|
+
const envelopeBytes = await getSigner().sign(payload);
|
|
238
|
+
return Buffer.from(envelopeBytes).toString('base64');
|
|
239
|
+
}
|
|
240
|
+
`;
|
|
241
|
+
// --- src/approval.ts ---
|
|
242
|
+
files['src/approval.ts'] = `/**
|
|
243
|
+
* Approval request storage and lifecycle management.
|
|
244
|
+
*/
|
|
245
|
+
|
|
246
|
+
import { randomUUID } from 'node:crypto';
|
|
247
|
+
import type { ApprovalRequest } from './multi-sign.js';
|
|
248
|
+
|
|
249
|
+
// In-memory storage (replace with a database in production)
|
|
250
|
+
const requests = new Map<string, ApprovalRequest>();
|
|
251
|
+
|
|
252
|
+
/** Create a new approval request. */
|
|
253
|
+
export function createRequest(input: {
|
|
254
|
+
title: string;
|
|
255
|
+
description: string;
|
|
256
|
+
requiredApprovals: number;
|
|
257
|
+
}): ApprovalRequest {
|
|
258
|
+
const request: ApprovalRequest = {
|
|
259
|
+
id: \`REQ-\${randomUUID()}\`,
|
|
260
|
+
title: input.title,
|
|
261
|
+
description: input.description,
|
|
262
|
+
requiredApprovals: input.requiredApprovals,
|
|
263
|
+
approvals: [],
|
|
264
|
+
status: 'pending',
|
|
265
|
+
createdAt: new Date().toISOString(),
|
|
266
|
+
};
|
|
267
|
+
requests.set(request.id, request);
|
|
268
|
+
return request;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** Get a request by ID. */
|
|
272
|
+
export function getRequest(id: string): ApprovalRequest | undefined {
|
|
273
|
+
return requests.get(id);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** List all requests. */
|
|
277
|
+
export function listRequests(): ApprovalRequest[] {
|
|
278
|
+
return Array.from(requests.values());
|
|
279
|
+
}
|
|
280
|
+
`;
|
|
281
|
+
// --- src/server.ts ---
|
|
282
|
+
files['src/server.ts'] = `/**
|
|
283
|
+
* ${name} — M-of-N approval workflow with BLINK Authority signatures.
|
|
284
|
+
*
|
|
285
|
+
* Create approval requests, collect individual BLINK-signed approvals,
|
|
286
|
+
* and finalize with a composite signature when the threshold is met.
|
|
287
|
+
*/
|
|
288
|
+
|
|
289
|
+
import 'dotenv/config';
|
|
290
|
+
import express from 'express';
|
|
291
|
+
import { initBlink, shutdownBlink, getVrfPublicKeyHex } from './blink.js';
|
|
292
|
+
import { getPolicy, isAuthorized, thresholdMet } from './policy.js';
|
|
293
|
+
import { createRequest, getRequest, listRequests } from './approval.js';
|
|
294
|
+
import { signApproval, signFinal } from './multi-sign.js';
|
|
295
|
+
|
|
296
|
+
async function main(): Promise<void> {
|
|
297
|
+
const bsecUrl = process.env['BSEC_URL'] ?? 'http://localhost:9100';
|
|
298
|
+
const slotName = process.env['SLOT_NAME'] ?? 'default';
|
|
299
|
+
const port = parseInt(process.env['PORT'] ?? '3000', 10);
|
|
300
|
+
|
|
301
|
+
await initBlink(bsecUrl, slotName);
|
|
302
|
+
|
|
303
|
+
const app = express();
|
|
304
|
+
app.use(express.json());
|
|
305
|
+
|
|
306
|
+
// POST /requests — Create a new approval request
|
|
307
|
+
app.post('/requests', (req, res) => {
|
|
308
|
+
const { title, description } = req.body;
|
|
309
|
+
if (!title || !description) {
|
|
310
|
+
res.status(400).json({ error: 'Missing required fields: title, description' });
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
const policy = getPolicy();
|
|
314
|
+
const request = createRequest({
|
|
315
|
+
title,
|
|
316
|
+
description,
|
|
317
|
+
requiredApprovals: policy.requiredApprovals,
|
|
318
|
+
});
|
|
319
|
+
res.status(201).json(request);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
// GET /requests — List all approval requests
|
|
323
|
+
app.get('/requests', (_req, res) => {
|
|
324
|
+
res.json({ requests: listRequests() });
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
// GET /requests/:id — Get a single request
|
|
328
|
+
app.get('/requests/:id', (req, res) => {
|
|
329
|
+
const request = getRequest(req.params['id']!);
|
|
330
|
+
if (!request) {
|
|
331
|
+
res.status(404).json({ error: 'Request not found' });
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
res.json(request);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// POST /requests/:id/approve — Add an approval
|
|
338
|
+
app.post('/requests/:id/approve', async (req, res, next) => {
|
|
339
|
+
try {
|
|
340
|
+
const request = getRequest(req.params['id']!);
|
|
341
|
+
if (!request) {
|
|
342
|
+
res.status(404).json({ error: 'Request not found' });
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
if (request.status !== 'pending') {
|
|
346
|
+
res.status(409).json({ error: \`Request is \${request.status}, expected pending\` });
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const { approverId, approverName, comment } = req.body;
|
|
351
|
+
if (!approverId || !approverName) {
|
|
352
|
+
res.status(400).json({ error: 'Missing required fields: approverId, approverName' });
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Check authorization
|
|
357
|
+
if (!isAuthorized(approverId)) {
|
|
358
|
+
res.status(403).json({ error: \`Approver \${approverId} is not authorized\` });
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Check for duplicate approval
|
|
363
|
+
if (request.approvals.some(a => a.approverId === approverId)) {
|
|
364
|
+
res.status(409).json({ error: \`Approver \${approverId} has already approved\` });
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Sign the individual approval with BLINK
|
|
369
|
+
const approval = await signApproval(request.id, approverId, approverName, comment);
|
|
370
|
+
request.approvals.push(approval);
|
|
371
|
+
|
|
372
|
+
// Check if threshold is met — if so, finalize with composite signature
|
|
373
|
+
if (thresholdMet(request.approvals.length)) {
|
|
374
|
+
request.status = 'approved';
|
|
375
|
+
request.finalEnvelope = await signFinal(request);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
res.status(201).json({
|
|
379
|
+
approval,
|
|
380
|
+
request: {
|
|
381
|
+
id: request.id,
|
|
382
|
+
status: request.status,
|
|
383
|
+
approvalsReceived: request.approvals.length,
|
|
384
|
+
approvalsRequired: request.requiredApprovals,
|
|
385
|
+
},
|
|
386
|
+
});
|
|
387
|
+
} catch (err) {
|
|
388
|
+
next(err);
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// GET /health
|
|
393
|
+
app.get('/health', (_req, res) => {
|
|
394
|
+
res.json({
|
|
395
|
+
status: 'ok',
|
|
396
|
+
service: '${name}',
|
|
397
|
+
signerPublicKey: getVrfPublicKeyHex(),
|
|
398
|
+
policy: getPolicy(),
|
|
399
|
+
timestamp: new Date().toISOString(),
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
const server = app.listen(port, () => {
|
|
404
|
+
console.log(\`[server] ${name} listening on port \${port}\`);
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
// Graceful shutdown
|
|
408
|
+
const shutdown = async (signal: string) => {
|
|
409
|
+
console.log(\`[server] Received \${signal}, shutting down...\`);
|
|
410
|
+
server.close();
|
|
411
|
+
await shutdownBlink();
|
|
412
|
+
process.exit(0);
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
416
|
+
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
main().catch((err) => {
|
|
420
|
+
console.error('[server] Fatal error:', err);
|
|
421
|
+
process.exit(1);
|
|
422
|
+
});
|
|
423
|
+
`;
|
|
424
|
+
// --- README.md ---
|
|
425
|
+
files['README.md'] = `# ${name}
|
|
426
|
+
|
|
427
|
+
M-of-N approval workflow with **BLINK Authority** signatures. Create requests that
|
|
428
|
+
require multiple approvals, each individually signed with BLINK. When the threshold
|
|
429
|
+
is met, a final composite signature is created.
|
|
430
|
+
|
|
431
|
+
## Quick Start
|
|
432
|
+
|
|
433
|
+
\`\`\`bash
|
|
434
|
+
npm install
|
|
435
|
+
cp .env.example .env
|
|
436
|
+
npm run dev
|
|
437
|
+
\`\`\`
|
|
438
|
+
|
|
439
|
+
## API
|
|
440
|
+
|
|
441
|
+
### POST /requests
|
|
442
|
+
Create a new approval request.
|
|
443
|
+
|
|
444
|
+
### GET /requests
|
|
445
|
+
List all requests.
|
|
446
|
+
|
|
447
|
+
### POST /requests/:id/approve
|
|
448
|
+
Add an approval. Requires \`approverId\` and \`approverName\`.
|
|
449
|
+
When the threshold is met, the request is finalized with a composite BLINK signature.
|
|
450
|
+
|
|
451
|
+
### GET /health
|
|
452
|
+
Health check with policy details.
|
|
453
|
+
|
|
454
|
+
## How M-of-N Works
|
|
455
|
+
|
|
456
|
+
1. A request is created specifying the approval threshold
|
|
457
|
+
2. Authorized approvers submit their approvals
|
|
458
|
+
3. Each approval is individually signed with a BLINK single-use credential
|
|
459
|
+
4. When M approvals are collected (out of N authorized approvers), a final
|
|
460
|
+
composite signature is created referencing all individual approvals
|
|
461
|
+
5. The composite signature can be verified independently
|
|
462
|
+
|
|
463
|
+
Built with [BLINK Authority](https://blink-authority.com).
|
|
464
|
+
`;
|
|
465
|
+
return files;
|
|
466
|
+
}
|
|
467
|
+
//# sourceMappingURL=approval-workflow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval-workflow.js","sourceRoot":"","sources":["../../src/templates/approval-workflow.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,UAAU,wBAAwB,CACtC,IAAY,EACZ,QAAkB;IAElB,MAAM,KAAK,GAA2B,EAAE,CAAC;IAEzC,uBAAuB;IACvB,KAAK,CAAC,cAAc,CAAC,GAAG,IAAI,CAAC,SAAS,CACpC;QACE,IAAI;QACJ,OAAO,EAAE,OAAO;QAChB,WAAW,EAAE,GAAG,IAAI,6DAA6D;QACjF,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE;YACP,KAAK,EAAE,KAAK;YACZ,KAAK,EAAE,qBAAqB;YAC5B,GAAG,EAAE,mBAAmB;SACzB;QACD,YAAY,EAAE;YACZ,uBAAuB,EAAE,SAAS;YAClC,OAAO,EAAE,SAAS;YAClB,MAAM,EAAE,SAAS;SAClB;QACD,eAAe,EAAE;YACf,gBAAgB,EAAE,UAAU;YAC5B,aAAa,EAAE,UAAU;YACzB,UAAU,EAAE,QAAQ;YACpB,GAAG,EAAE,SAAS;SACf;KACF,EACD,IAAI,EACJ,CAAC,CACF,CAAC;IAEF,wBAAwB;IACxB,KAAK,CAAC,eAAe,CAAC,GAAG,IAAI,CAAC,SAAS,CACrC;QACE,eAAe,EAAE;YACf,MAAM,EAAE,QAAQ;YAChB,MAAM,EAAE,QAAQ;YAChB,gBAAgB,EAAE,QAAQ;YAC1B,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,KAAK;YACd,MAAM,EAAE,IAAI;YACZ,eAAe,EAAE,IAAI;YACrB,YAAY,EAAE,IAAI;YAClB,gCAAgC,EAAE,IAAI;YACtC,WAAW,EAAE,IAAI;YACjB,SAAS,EAAE,IAAI;SAChB;QACD,OAAO,EAAE,CAAC,UAAU,CAAC;QACrB,OAAO,EAAE,CAAC,cAAc,EAAE,MAAM,CAAC;KAClC,EACD,IAAI,EACJ,CAAC,CACF,CAAC;IAEF,uBAAuB;IACvB,KAAK,CAAC,cAAc,CAAC,GAAG;;;;;;;;;;CAUzB,CAAC;IAEA,uBAAuB;IACvB,KAAK,CAAC,cAAc,CAAC,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqCzB,CAAC;IAEA,wBAAwB;IACxB,KAAK,CAAC,eAAe,CAAC,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkC1B,CAAC;IAEA,4BAA4B;IAC5B,KAAK,CAAC,mBAAmB,CAAC,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwG9B,CAAC;IAEA,0BAA0B;IAC1B,KAAK,CAAC,iBAAiB,CAAC,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsC5B,CAAC;IAEA,wBAAwB;IACxB,KAAK,CAAC,eAAe,CAAC,GAAG;KACtB,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAiHS,IAAI;;;;;;;;6BAQO,IAAI;;;;;;;;;;;;;;;;;;;CAmBhC,CAAC;IAEA,oBAAoB;IACpB,KAAK,CAAC,WAAW,CAAC,GAAG,KAAK,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuC/B,CAAC;IAEA,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-trail.d.ts","sourceRoot":"","sources":["../../src/templates/audit-trail.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAAE,GACjB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAuZxB"}
|