@learncard/lca-api-service 1.5.0 → 1.5.2
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/.env.example +2 -2
- package/CHANGELOG.md +23 -0
- package/bundle.mjs +9 -1
- package/config/config.dev.json +1 -1
- package/dist/app.d.ts +220 -1
- package/dist/index.js +2 -2
- package/dist/models/EscrowHold.d.ts +30 -2
- package/dist/models/UserKey.d.ts +89 -5
- package/dist/publish-metafile.json +1 -0
- package/dist/routes/escrow.d.ts +218 -0
- package/dist/routes/index.d.ts +4 -4
- package/dist/routes/keys.d.ts +2 -1
- package/dist/services/escrow-enclave/authorityContract.d.ts +170 -0
- package/dist/services/escrow-enclave/types.d.ts +38 -2
- package/package.json +9 -10
- package/project.json +8 -1
- package/serverless.yml +2 -0
- package/src/config/environment.ts +1 -1
- package/src/config/escrowDeployment.test.ts +8 -0
- package/src/jobs/escrowBlobRewrap.ts +45 -2
- package/src/jobs/escrowHoldReminders.ts +50 -1
- package/src/models/EscrowHold.ts +128 -10
- package/src/models/UserKey.ts +59 -8
- package/src/routes/escrow.ts +371 -51
- package/src/routes/index.ts +1 -1
- package/src/routes/keys.ts +5 -0
- package/src/services/escrow-enclave/README.md +31 -0
- package/src/services/escrow-enclave/authorityContract.ts +126 -0
- package/src/services/escrow-enclave/cancellation.ts +2 -2
- package/src/services/escrow-enclave/remoteEnclave.test.ts +216 -3
- package/src/services/escrow-enclave/remoteEnclave.ts +129 -23
- package/src/services/escrow-enclave/types.ts +51 -3
- package/test/jobs/escrowHoldReminders.spec.ts +195 -3
- package/test/keys-escrow.spec.ts +522 -11
- package/test/models/escrow.spec.ts +13 -0
package/dist/routes/escrow.d.ts
CHANGED
|
@@ -28,6 +28,45 @@ export declare const escrowRouter: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
28
28
|
};
|
|
29
29
|
meta: import("trpc-to-openapi").OpenApiMeta;
|
|
30
30
|
}>;
|
|
31
|
+
getAuthorityState: import("@trpc/server").TRPCMutationProcedure<{
|
|
32
|
+
input: {
|
|
33
|
+
authToken: string;
|
|
34
|
+
providerType: "firebase" | "supertokens" | "keycloak" | "oidc";
|
|
35
|
+
nonce: string;
|
|
36
|
+
};
|
|
37
|
+
output: {
|
|
38
|
+
state: {
|
|
39
|
+
tenant: string;
|
|
40
|
+
authorityGeneration: string;
|
|
41
|
+
did: string;
|
|
42
|
+
enrollmentVersion: number;
|
|
43
|
+
enrollmentEpoch: number;
|
|
44
|
+
status: "empty" | "active" | "disabled";
|
|
45
|
+
envelope: {
|
|
46
|
+
version: 1;
|
|
47
|
+
algorithm: "P-256-HKDF-SHA256-AES-256-GCM";
|
|
48
|
+
keyId: string;
|
|
49
|
+
ephemeralPublicKey: string;
|
|
50
|
+
salt: string;
|
|
51
|
+
iv: string;
|
|
52
|
+
ciphertext: string;
|
|
53
|
+
} | null;
|
|
54
|
+
shareVersion: number | null;
|
|
55
|
+
hasPin: boolean;
|
|
56
|
+
pinGeneration: number;
|
|
57
|
+
attemptsUsed: number;
|
|
58
|
+
};
|
|
59
|
+
proof: {
|
|
60
|
+
version: 1;
|
|
61
|
+
keyId: string;
|
|
62
|
+
measurement: number[];
|
|
63
|
+
payload: string;
|
|
64
|
+
signature: string;
|
|
65
|
+
kind: "snapshot";
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
meta: import("trpc-to-openapi").OpenApiMeta;
|
|
69
|
+
}>;
|
|
31
70
|
enroll: import("@trpc/server").TRPCMutationProcedure<{
|
|
32
71
|
input: {
|
|
33
72
|
authToken: string;
|
|
@@ -45,10 +84,56 @@ export declare const escrowRouter: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
45
84
|
enclaveKeyId: string;
|
|
46
85
|
pinSalt?: string | undefined;
|
|
47
86
|
clearPin?: boolean | undefined;
|
|
87
|
+
signedIntent?: {
|
|
88
|
+
intent: {
|
|
89
|
+
version: 2;
|
|
90
|
+
tenant: string;
|
|
91
|
+
did: string;
|
|
92
|
+
authorityGeneration: string;
|
|
93
|
+
expectedEnrollmentVersion: number;
|
|
94
|
+
action: "enroll" | "replace" | "disable" | "carry";
|
|
95
|
+
sourceBlobIdentity: string | null;
|
|
96
|
+
targetBlobIdentity: string | null;
|
|
97
|
+
pinAction: "set" | "change" | "clear" | "keep";
|
|
98
|
+
shareVersion: number;
|
|
99
|
+
enrollmentEpoch: number;
|
|
100
|
+
requestId: string;
|
|
101
|
+
allowRewrap: boolean;
|
|
102
|
+
recoveryEmail: string | null;
|
|
103
|
+
recoveryLanguage: string | null;
|
|
104
|
+
};
|
|
105
|
+
signature: string;
|
|
106
|
+
} | undefined;
|
|
107
|
+
sourceEnvelope?: {
|
|
108
|
+
version: 1;
|
|
109
|
+
algorithm: "P-256-HKDF-SHA256-AES-256-GCM";
|
|
110
|
+
keyId: string;
|
|
111
|
+
ephemeralPublicKey: string;
|
|
112
|
+
salt: string;
|
|
113
|
+
iv: string;
|
|
114
|
+
ciphertext: string;
|
|
115
|
+
} | null | undefined;
|
|
48
116
|
};
|
|
49
117
|
output: {
|
|
50
118
|
success: true;
|
|
51
119
|
shareVersion: number;
|
|
120
|
+
envelope?: {
|
|
121
|
+
version: 1;
|
|
122
|
+
algorithm: "P-256-HKDF-SHA256-AES-256-GCM";
|
|
123
|
+
keyId: string;
|
|
124
|
+
ephemeralPublicKey: string;
|
|
125
|
+
salt: string;
|
|
126
|
+
iv: string;
|
|
127
|
+
ciphertext: string;
|
|
128
|
+
} | undefined;
|
|
129
|
+
receipt?: {
|
|
130
|
+
version: 1;
|
|
131
|
+
keyId: string;
|
|
132
|
+
measurement: number[];
|
|
133
|
+
signature: string;
|
|
134
|
+
kind: "commit";
|
|
135
|
+
payload: string;
|
|
136
|
+
} | undefined;
|
|
52
137
|
};
|
|
53
138
|
meta: import("trpc-to-openapi").OpenApiMeta;
|
|
54
139
|
}>;
|
|
@@ -60,6 +145,62 @@ export declare const escrowRouter: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
60
145
|
};
|
|
61
146
|
output: {
|
|
62
147
|
success: true;
|
|
148
|
+
receipt?: {
|
|
149
|
+
version: 1;
|
|
150
|
+
keyId: string;
|
|
151
|
+
measurement: number[];
|
|
152
|
+
signature: string;
|
|
153
|
+
kind: "commit";
|
|
154
|
+
payload: string;
|
|
155
|
+
} | undefined;
|
|
156
|
+
};
|
|
157
|
+
meta: import("trpc-to-openapi").OpenApiMeta;
|
|
158
|
+
}>;
|
|
159
|
+
disable: import("@trpc/server").TRPCMutationProcedure<{
|
|
160
|
+
input: {
|
|
161
|
+
authToken: string;
|
|
162
|
+
providerType: "firebase" | "supertokens" | "keycloak" | "oidc";
|
|
163
|
+
optOut: boolean;
|
|
164
|
+
signedIntent: {
|
|
165
|
+
intent: {
|
|
166
|
+
version: 2;
|
|
167
|
+
tenant: string;
|
|
168
|
+
did: string;
|
|
169
|
+
authorityGeneration: string;
|
|
170
|
+
expectedEnrollmentVersion: number;
|
|
171
|
+
action: "enroll" | "replace" | "disable" | "carry";
|
|
172
|
+
sourceBlobIdentity: string | null;
|
|
173
|
+
targetBlobIdentity: string | null;
|
|
174
|
+
pinAction: "set" | "change" | "clear" | "keep";
|
|
175
|
+
shareVersion: number;
|
|
176
|
+
enrollmentEpoch: number;
|
|
177
|
+
requestId: string;
|
|
178
|
+
allowRewrap: boolean;
|
|
179
|
+
recoveryEmail: string | null;
|
|
180
|
+
recoveryLanguage: string | null;
|
|
181
|
+
};
|
|
182
|
+
signature: string;
|
|
183
|
+
};
|
|
184
|
+
sourceEnvelope?: {
|
|
185
|
+
version: 1;
|
|
186
|
+
algorithm: "P-256-HKDF-SHA256-AES-256-GCM";
|
|
187
|
+
keyId: string;
|
|
188
|
+
ephemeralPublicKey: string;
|
|
189
|
+
salt: string;
|
|
190
|
+
iv: string;
|
|
191
|
+
ciphertext: string;
|
|
192
|
+
} | undefined;
|
|
193
|
+
};
|
|
194
|
+
output: {
|
|
195
|
+
success: true;
|
|
196
|
+
receipt?: {
|
|
197
|
+
version: 1;
|
|
198
|
+
keyId: string;
|
|
199
|
+
measurement: number[];
|
|
200
|
+
signature: string;
|
|
201
|
+
kind: "commit";
|
|
202
|
+
payload: string;
|
|
203
|
+
} | undefined;
|
|
63
204
|
};
|
|
64
205
|
meta: import("trpc-to-openapi").OpenApiMeta;
|
|
65
206
|
}>;
|
|
@@ -89,10 +230,66 @@ export declare const escrowRouter: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
89
230
|
releaseAfter: string;
|
|
90
231
|
status: "pending";
|
|
91
232
|
resumeToken: string | null;
|
|
233
|
+
holdRecord?: {
|
|
234
|
+
[x: string]: unknown;
|
|
235
|
+
hold: {
|
|
236
|
+
[x: string]: unknown;
|
|
237
|
+
holdId: string;
|
|
238
|
+
did: string;
|
|
239
|
+
shareVersion: number;
|
|
240
|
+
blobHash: string;
|
|
241
|
+
enrollmentEpoch: number;
|
|
242
|
+
releasePolicy: "pin" | "hold";
|
|
243
|
+
clientEphemeralPublicKey: string;
|
|
244
|
+
createdLo: number;
|
|
245
|
+
createdHi: number;
|
|
246
|
+
policyVersion: number;
|
|
247
|
+
signature: string;
|
|
248
|
+
};
|
|
249
|
+
holdDurationMs: number;
|
|
250
|
+
ledgerSeq: number;
|
|
251
|
+
} | undefined;
|
|
252
|
+
requiresConfirmation?: boolean | undefined;
|
|
92
253
|
pinSalt?: string | undefined;
|
|
93
254
|
};
|
|
94
255
|
meta: import("trpc-to-openapi").OpenApiMeta;
|
|
95
256
|
}>;
|
|
257
|
+
confirmRecovery: import("@trpc/server").TRPCMutationProcedure<{
|
|
258
|
+
input: {
|
|
259
|
+
holdId: string;
|
|
260
|
+
resumeToken: string;
|
|
261
|
+
code: string;
|
|
262
|
+
};
|
|
263
|
+
output: {
|
|
264
|
+
success: true;
|
|
265
|
+
receipt?: {
|
|
266
|
+
version: 1;
|
|
267
|
+
keyId: string;
|
|
268
|
+
measurement: number[];
|
|
269
|
+
signature: string;
|
|
270
|
+
kind: "commit";
|
|
271
|
+
payload: string;
|
|
272
|
+
} | {
|
|
273
|
+
version: 1;
|
|
274
|
+
keyId: string;
|
|
275
|
+
measurement: number[];
|
|
276
|
+
payload: string;
|
|
277
|
+
signature: string;
|
|
278
|
+
kind: "snapshot";
|
|
279
|
+
} | undefined;
|
|
280
|
+
};
|
|
281
|
+
meta: import("trpc-to-openapi").OpenApiMeta;
|
|
282
|
+
}>;
|
|
283
|
+
resendRecoveryCode: import("@trpc/server").TRPCMutationProcedure<{
|
|
284
|
+
input: {
|
|
285
|
+
holdId: string;
|
|
286
|
+
resumeToken: string;
|
|
287
|
+
};
|
|
288
|
+
output: {
|
|
289
|
+
success: true;
|
|
290
|
+
};
|
|
291
|
+
meta: import("trpc-to-openapi").OpenApiMeta;
|
|
292
|
+
}>;
|
|
96
293
|
getStatus: import("@trpc/server").TRPCQueryProcedure<{
|
|
97
294
|
input: {
|
|
98
295
|
holdId?: string | undefined;
|
|
@@ -109,6 +306,27 @@ export declare const escrowRouter: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
109
306
|
releasePolicy: "pin" | "hold";
|
|
110
307
|
cancelledAt?: string | undefined;
|
|
111
308
|
completedAt?: string | undefined;
|
|
309
|
+
requiresConfirmation?: boolean | undefined;
|
|
310
|
+
confirmed?: boolean | undefined;
|
|
311
|
+
holdRecord?: {
|
|
312
|
+
[x: string]: unknown;
|
|
313
|
+
hold: {
|
|
314
|
+
[x: string]: unknown;
|
|
315
|
+
holdId: string;
|
|
316
|
+
did: string;
|
|
317
|
+
shareVersion: number;
|
|
318
|
+
blobHash: string;
|
|
319
|
+
enrollmentEpoch: number;
|
|
320
|
+
releasePolicy: "pin" | "hold";
|
|
321
|
+
clientEphemeralPublicKey: string;
|
|
322
|
+
createdLo: number;
|
|
323
|
+
createdHi: number;
|
|
324
|
+
policyVersion: number;
|
|
325
|
+
signature: string;
|
|
326
|
+
};
|
|
327
|
+
holdDurationMs: number;
|
|
328
|
+
ledgerSeq: number;
|
|
329
|
+
} | undefined;
|
|
112
330
|
} | null;
|
|
113
331
|
};
|
|
114
332
|
meta: import("trpc-to-openapi").OpenApiMeta;
|
package/dist/routes/index.d.ts
CHANGED
|
@@ -35,6 +35,7 @@ export declare const t: import("@trpc/server").TRPCRootObject<Context, OpenApiMe
|
|
|
35
35
|
export declare const createContext: (options: CreateAWSLambdaContextOptions<APIGatewayProxyEventV2> | CreateFastifyContextOptions | NodeHTTPCreateContextFnOptions<IncomingMessage, ServerResponse>) => Promise<Context>;
|
|
36
36
|
export declare const redactSecretFields: (value: unknown, depth?: number, seen?: Set<object>) => unknown;
|
|
37
37
|
export declare const openRoute: import("@trpc/server").TRPCProcedureBuilder<Context, OpenApiMeta, {
|
|
38
|
+
tenant: ResolvedTenant;
|
|
38
39
|
debug: boolean | undefined;
|
|
39
40
|
user: {
|
|
40
41
|
did: string;
|
|
@@ -42,11 +43,11 @@ export declare const openRoute: import("@trpc/server").TRPCProcedureBuilder<Cont
|
|
|
42
43
|
authorizedDid?: boolean;
|
|
43
44
|
} | undefined;
|
|
44
45
|
domain: string;
|
|
45
|
-
tenant: ResolvedTenant;
|
|
46
46
|
clientIp: string | undefined;
|
|
47
47
|
providerToken: string | undefined;
|
|
48
48
|
}, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, false>;
|
|
49
49
|
export declare const didRoute: import("@trpc/server").TRPCProcedureBuilder<Context, OpenApiMeta, {
|
|
50
|
+
tenant: ResolvedTenant;
|
|
50
51
|
debug: boolean | undefined;
|
|
51
52
|
user: {
|
|
52
53
|
did: string;
|
|
@@ -54,11 +55,11 @@ export declare const didRoute: import("@trpc/server").TRPCProcedureBuilder<Conte
|
|
|
54
55
|
authorizedDid?: boolean;
|
|
55
56
|
};
|
|
56
57
|
domain: string;
|
|
57
|
-
tenant: ResolvedTenant;
|
|
58
58
|
clientIp: string | undefined;
|
|
59
59
|
providerToken: string | undefined;
|
|
60
60
|
}, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, false>;
|
|
61
61
|
export declare const authorizedDidRoute: import("@trpc/server").TRPCProcedureBuilder<Context, OpenApiMeta, {
|
|
62
|
+
tenant: ResolvedTenant;
|
|
62
63
|
debug: boolean | undefined;
|
|
63
64
|
user: {
|
|
64
65
|
did: string;
|
|
@@ -66,11 +67,11 @@ export declare const authorizedDidRoute: import("@trpc/server").TRPCProcedureBui
|
|
|
66
67
|
authorizedDid?: boolean;
|
|
67
68
|
};
|
|
68
69
|
domain: string;
|
|
69
|
-
tenant: ResolvedTenant;
|
|
70
70
|
clientIp: string | undefined;
|
|
71
71
|
providerToken: string | undefined;
|
|
72
72
|
}, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, false>;
|
|
73
73
|
export declare const didAndChallengeRoute: import("@trpc/server").TRPCProcedureBuilder<Context, OpenApiMeta, {
|
|
74
|
+
tenant: ResolvedTenant;
|
|
74
75
|
debug: boolean | undefined;
|
|
75
76
|
user: {
|
|
76
77
|
did: string;
|
|
@@ -78,7 +79,6 @@ export declare const didAndChallengeRoute: import("@trpc/server").TRPCProcedureB
|
|
|
78
79
|
authorizedDid?: boolean;
|
|
79
80
|
};
|
|
80
81
|
domain: string;
|
|
81
|
-
tenant: ResolvedTenant;
|
|
82
82
|
clientIp: string | undefined;
|
|
83
83
|
providerToken: string | undefined;
|
|
84
84
|
}, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, false>;
|
package/dist/routes/keys.d.ts
CHANGED
|
@@ -67,6 +67,7 @@ export declare const keysRouter: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
67
67
|
attemptsRemaining: number;
|
|
68
68
|
salt?: string | undefined;
|
|
69
69
|
};
|
|
70
|
+
primaryDid: string | null;
|
|
70
71
|
};
|
|
71
72
|
meta: import("trpc-to-openapi").OpenApiMeta;
|
|
72
73
|
}>;
|
|
@@ -152,7 +153,7 @@ export declare const keysRouter: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
152
153
|
attemptsRemaining: number;
|
|
153
154
|
salt?: string | undefined;
|
|
154
155
|
};
|
|
155
|
-
sssActivationState: "
|
|
156
|
+
sssActivationState: "active" | "provisional";
|
|
156
157
|
escrowStale?: "mode-mismatch" | "key-rotated" | undefined;
|
|
157
158
|
} | null;
|
|
158
159
|
meta: import("trpc-to-openapi").OpenApiMeta;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const safeInteger: z.ZodNumber;
|
|
3
|
+
export declare const AuthorityEnvelopeValidator: z.ZodObject<{
|
|
4
|
+
version: z.ZodLiteral<1>;
|
|
5
|
+
algorithm: z.ZodLiteral<"P-256-HKDF-SHA256-AES-256-GCM">;
|
|
6
|
+
keyId: z.ZodString;
|
|
7
|
+
ephemeralPublicKey: z.ZodString;
|
|
8
|
+
salt: z.ZodString;
|
|
9
|
+
iv: z.ZodString;
|
|
10
|
+
ciphertext: z.ZodString;
|
|
11
|
+
}, z.core.$strict>;
|
|
12
|
+
export declare const AuthorityRecordValidator: z.ZodObject<{
|
|
13
|
+
version: z.ZodLiteral<1>;
|
|
14
|
+
kind: z.ZodEnum<{
|
|
15
|
+
proposal: "proposal";
|
|
16
|
+
commit: "commit";
|
|
17
|
+
snapshot: "snapshot";
|
|
18
|
+
}>;
|
|
19
|
+
keyId: z.ZodString;
|
|
20
|
+
measurement: z.ZodArray<z.ZodNumber>;
|
|
21
|
+
payload: z.ZodString;
|
|
22
|
+
signature: z.ZodString;
|
|
23
|
+
}, z.core.$strict>;
|
|
24
|
+
export declare const AuthorityCommitValidator: z.ZodObject<{
|
|
25
|
+
version: z.ZodLiteral<1>;
|
|
26
|
+
keyId: z.ZodString;
|
|
27
|
+
measurement: z.ZodArray<z.ZodNumber>;
|
|
28
|
+
signature: z.ZodString;
|
|
29
|
+
kind: z.ZodLiteral<"commit">;
|
|
30
|
+
payload: z.ZodString;
|
|
31
|
+
}, z.core.$strict>;
|
|
32
|
+
export declare const AuthorityHoldSnapshotValidator: z.ZodObject<{
|
|
33
|
+
version: z.ZodLiteral<1>;
|
|
34
|
+
keyId: z.ZodString;
|
|
35
|
+
measurement: z.ZodArray<z.ZodNumber>;
|
|
36
|
+
payload: z.ZodString;
|
|
37
|
+
signature: z.ZodString;
|
|
38
|
+
kind: z.ZodLiteral<"snapshot">;
|
|
39
|
+
}, z.core.$strict>;
|
|
40
|
+
export declare const AuthorityHoldReceiptValidator: z.ZodUnion<readonly [z.ZodObject<{
|
|
41
|
+
version: z.ZodLiteral<1>;
|
|
42
|
+
keyId: z.ZodString;
|
|
43
|
+
measurement: z.ZodArray<z.ZodNumber>;
|
|
44
|
+
signature: z.ZodString;
|
|
45
|
+
kind: z.ZodLiteral<"commit">;
|
|
46
|
+
payload: z.ZodString;
|
|
47
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
48
|
+
version: z.ZodLiteral<1>;
|
|
49
|
+
keyId: z.ZodString;
|
|
50
|
+
measurement: z.ZodArray<z.ZodNumber>;
|
|
51
|
+
payload: z.ZodString;
|
|
52
|
+
signature: z.ZodString;
|
|
53
|
+
kind: z.ZodLiteral<"snapshot">;
|
|
54
|
+
}, z.core.$strict>]>;
|
|
55
|
+
export declare const SignedEnrollmentIntentValidator: z.ZodObject<{
|
|
56
|
+
intent: z.ZodObject<{
|
|
57
|
+
version: z.ZodLiteral<2>;
|
|
58
|
+
tenant: z.ZodString;
|
|
59
|
+
did: z.ZodString;
|
|
60
|
+
authorityGeneration: z.ZodString;
|
|
61
|
+
expectedEnrollmentVersion: z.ZodNumber;
|
|
62
|
+
action: z.ZodEnum<{
|
|
63
|
+
enroll: "enroll";
|
|
64
|
+
replace: "replace";
|
|
65
|
+
disable: "disable";
|
|
66
|
+
carry: "carry";
|
|
67
|
+
}>;
|
|
68
|
+
sourceBlobIdentity: z.ZodNullable<z.ZodString>;
|
|
69
|
+
targetBlobIdentity: z.ZodNullable<z.ZodString>;
|
|
70
|
+
pinAction: z.ZodEnum<{
|
|
71
|
+
set: "set";
|
|
72
|
+
change: "change";
|
|
73
|
+
clear: "clear";
|
|
74
|
+
keep: "keep";
|
|
75
|
+
}>;
|
|
76
|
+
shareVersion: z.ZodNumber;
|
|
77
|
+
enrollmentEpoch: z.ZodNumber;
|
|
78
|
+
requestId: z.ZodString;
|
|
79
|
+
allowRewrap: z.ZodBoolean;
|
|
80
|
+
recoveryEmail: z.ZodNullable<z.ZodString>;
|
|
81
|
+
recoveryLanguage: z.ZodNullable<z.ZodString>;
|
|
82
|
+
}, z.core.$strict>;
|
|
83
|
+
signature: z.ZodString;
|
|
84
|
+
}, z.core.$strict>;
|
|
85
|
+
export declare const AuthorityStateValidator: z.ZodObject<{
|
|
86
|
+
tenant: z.ZodString;
|
|
87
|
+
authorityGeneration: z.ZodString;
|
|
88
|
+
did: z.ZodString;
|
|
89
|
+
enrollmentVersion: z.ZodNumber;
|
|
90
|
+
enrollmentEpoch: z.ZodNumber;
|
|
91
|
+
status: z.ZodEnum<{
|
|
92
|
+
empty: "empty";
|
|
93
|
+
active: "active";
|
|
94
|
+
disabled: "disabled";
|
|
95
|
+
}>;
|
|
96
|
+
envelope: z.ZodNullable<z.ZodObject<{
|
|
97
|
+
version: z.ZodLiteral<1>;
|
|
98
|
+
algorithm: z.ZodLiteral<"P-256-HKDF-SHA256-AES-256-GCM">;
|
|
99
|
+
keyId: z.ZodString;
|
|
100
|
+
ephemeralPublicKey: z.ZodString;
|
|
101
|
+
salt: z.ZodString;
|
|
102
|
+
iv: z.ZodString;
|
|
103
|
+
ciphertext: z.ZodString;
|
|
104
|
+
}, z.core.$strict>>;
|
|
105
|
+
shareVersion: z.ZodNullable<z.ZodNumber>;
|
|
106
|
+
hasPin: z.ZodBoolean;
|
|
107
|
+
pinGeneration: z.ZodNumber;
|
|
108
|
+
attemptsUsed: z.ZodNumber;
|
|
109
|
+
}, z.core.$strict>;
|
|
110
|
+
export declare const AuthorityStateResponseValidator: z.ZodObject<{
|
|
111
|
+
state: z.ZodObject<{
|
|
112
|
+
tenant: z.ZodString;
|
|
113
|
+
authorityGeneration: z.ZodString;
|
|
114
|
+
did: z.ZodString;
|
|
115
|
+
enrollmentVersion: z.ZodNumber;
|
|
116
|
+
enrollmentEpoch: z.ZodNumber;
|
|
117
|
+
status: z.ZodEnum<{
|
|
118
|
+
empty: "empty";
|
|
119
|
+
active: "active";
|
|
120
|
+
disabled: "disabled";
|
|
121
|
+
}>;
|
|
122
|
+
envelope: z.ZodNullable<z.ZodObject<{
|
|
123
|
+
version: z.ZodLiteral<1>;
|
|
124
|
+
algorithm: z.ZodLiteral<"P-256-HKDF-SHA256-AES-256-GCM">;
|
|
125
|
+
keyId: z.ZodString;
|
|
126
|
+
ephemeralPublicKey: z.ZodString;
|
|
127
|
+
salt: z.ZodString;
|
|
128
|
+
iv: z.ZodString;
|
|
129
|
+
ciphertext: z.ZodString;
|
|
130
|
+
}, z.core.$strict>>;
|
|
131
|
+
shareVersion: z.ZodNullable<z.ZodNumber>;
|
|
132
|
+
hasPin: z.ZodBoolean;
|
|
133
|
+
pinGeneration: z.ZodNumber;
|
|
134
|
+
attemptsUsed: z.ZodNumber;
|
|
135
|
+
}, z.core.$strict>;
|
|
136
|
+
proof: z.ZodObject<{
|
|
137
|
+
version: z.ZodLiteral<1>;
|
|
138
|
+
keyId: z.ZodString;
|
|
139
|
+
measurement: z.ZodArray<z.ZodNumber>;
|
|
140
|
+
payload: z.ZodString;
|
|
141
|
+
signature: z.ZodString;
|
|
142
|
+
kind: z.ZodLiteral<"snapshot">;
|
|
143
|
+
}, z.core.$strict>;
|
|
144
|
+
}, z.core.$strict>;
|
|
145
|
+
export declare const AuthorityMutationValidator: z.ZodObject<{
|
|
146
|
+
envelope: z.ZodNullable<z.ZodObject<{
|
|
147
|
+
version: z.ZodLiteral<1>;
|
|
148
|
+
algorithm: z.ZodLiteral<"P-256-HKDF-SHA256-AES-256-GCM">;
|
|
149
|
+
keyId: z.ZodString;
|
|
150
|
+
ephemeralPublicKey: z.ZodString;
|
|
151
|
+
salt: z.ZodString;
|
|
152
|
+
iv: z.ZodString;
|
|
153
|
+
ciphertext: z.ZodString;
|
|
154
|
+
}, z.core.$strict>>;
|
|
155
|
+
receipt: z.ZodObject<{
|
|
156
|
+
version: z.ZodLiteral<1>;
|
|
157
|
+
keyId: z.ZodString;
|
|
158
|
+
measurement: z.ZodArray<z.ZodNumber>;
|
|
159
|
+
signature: z.ZodString;
|
|
160
|
+
kind: z.ZodLiteral<"commit">;
|
|
161
|
+
payload: z.ZodString;
|
|
162
|
+
}, z.core.$strict>;
|
|
163
|
+
authorityGeneration: z.ZodString;
|
|
164
|
+
enrollmentVersion: z.ZodNumber;
|
|
165
|
+
enrollmentEpoch: z.ZodNumber;
|
|
166
|
+
}, z.core.$strict>;
|
|
167
|
+
export type AuthorityRecord = z.infer<typeof AuthorityRecordValidator>;
|
|
168
|
+
export type AuthorityHoldReceipt = z.infer<typeof AuthorityHoldReceiptValidator>;
|
|
169
|
+
export type AuthorityMutationResult = z.infer<typeof AuthorityMutationValidator>;
|
|
170
|
+
export type AuthorityStateResponse = z.infer<typeof AuthorityStateResponseValidator>;
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import type { EscrowEnvelope } from '@learncard/sss-key-manager';
|
|
1
|
+
import type { EscrowEnvelope, SignedEscrowEnrollmentIntent } from '@learncard/sss-key-manager';
|
|
2
|
+
import type { AuthorityRecord, AuthorityHoldReceipt, AuthorityMutationResult, AuthorityStateResponse } from './authorityContract';
|
|
3
|
+
export interface ApplyEnrollmentInput {
|
|
4
|
+
signedIntent: SignedEscrowEnrollmentIntent;
|
|
5
|
+
sourceEnvelope: EscrowEnvelope | null;
|
|
6
|
+
targetEnvelope: EscrowEnvelope | null;
|
|
7
|
+
}
|
|
2
8
|
export interface EnclaveAttestation {
|
|
3
9
|
mode: 'software' | 'nitro';
|
|
4
10
|
keyId: string;
|
|
@@ -42,11 +48,21 @@ export interface EnclaveCreateHoldInput {
|
|
|
42
48
|
clientEphemeralPublicKey: string;
|
|
43
49
|
}
|
|
44
50
|
export interface CancelHoldRequest {
|
|
51
|
+
requestId?: string;
|
|
45
52
|
envelope: EscrowEnvelope;
|
|
46
53
|
hold: EscrowHoldRecord;
|
|
47
54
|
clientEphemeralPublicKey: string;
|
|
48
55
|
expectedDid: string;
|
|
49
56
|
}
|
|
57
|
+
export interface ConfirmHoldRequest extends CancelHoldRequest {
|
|
58
|
+
code: string;
|
|
59
|
+
}
|
|
60
|
+
export type ResendCodeRequest = CancelHoldRequest;
|
|
61
|
+
export interface SendReleaseWarningRequest {
|
|
62
|
+
requestId?: string;
|
|
63
|
+
holdId: string;
|
|
64
|
+
expectedDid: string;
|
|
65
|
+
}
|
|
50
66
|
export interface VerifyEscrowBlobInput {
|
|
51
67
|
envelope: EscrowEnvelope;
|
|
52
68
|
expectedDid: string;
|
|
@@ -66,6 +82,7 @@ export interface ReleaseRequest extends CancelHoldRequest {
|
|
|
66
82
|
}
|
|
67
83
|
export interface ReleaseResult {
|
|
68
84
|
sealed: EscrowEnvelope;
|
|
85
|
+
receipt?: AuthorityRecord;
|
|
69
86
|
}
|
|
70
87
|
export interface CarryPinVerifierInput {
|
|
71
88
|
sourceEnvelope: EscrowEnvelope;
|
|
@@ -83,6 +100,11 @@ export interface RewrapEscrowBlobInput {
|
|
|
83
100
|
sourceEnrollmentEpoch: number;
|
|
84
101
|
}
|
|
85
102
|
export interface EscrowEnclave {
|
|
103
|
+
getEnrollmentState?(input: {
|
|
104
|
+
expectedDid: string;
|
|
105
|
+
nonce: string;
|
|
106
|
+
}): Promise<AuthorityStateResponse>;
|
|
107
|
+
applyEnrollmentIntent?(input: ApplyEnrollmentInput): Promise<AuthorityMutationResult>;
|
|
86
108
|
getAttestation(nonce?: Uint8Array): Promise<EnclaveAttestation>;
|
|
87
109
|
verifyEscrowBlob(input: VerifyEscrowBlobInput): Promise<VerifyEscrowBlobResult>;
|
|
88
110
|
carryPinVerifier(input: CarryPinVerifierInput): Promise<{
|
|
@@ -90,12 +112,26 @@ export interface EscrowEnclave {
|
|
|
90
112
|
}>;
|
|
91
113
|
rewrapEscrowBlob(input: RewrapEscrowBlobInput): Promise<{
|
|
92
114
|
envelope: EscrowEnvelope;
|
|
115
|
+
authority?: AuthorityMutationResult;
|
|
93
116
|
}>;
|
|
94
117
|
releaseEscrow(input: ReleaseRequest): Promise<ReleaseResult>;
|
|
95
118
|
createHold(input: EnclaveCreateHoldInput): Promise<{
|
|
96
119
|
holdRecord: EscrowHoldRecord;
|
|
97
120
|
}>;
|
|
98
|
-
cancelHold(input: CancelHoldRequest): Promise<void
|
|
121
|
+
cancelHold(input: CancelHoldRequest): Promise<void | {
|
|
122
|
+
receipt: AuthorityRecord;
|
|
123
|
+
}>;
|
|
124
|
+
confirmHold?(input: ConfirmHoldRequest): Promise<{
|
|
125
|
+
receipt: AuthorityHoldReceipt;
|
|
126
|
+
confirmedAtMs: number;
|
|
127
|
+
}>;
|
|
128
|
+
resendCode?(input: ResendCodeRequest): Promise<{
|
|
129
|
+
receipt: AuthorityRecord;
|
|
130
|
+
}>;
|
|
131
|
+
sendReleaseWarning?(input: SendReleaseWarningRequest): Promise<{
|
|
132
|
+
receipt: AuthorityHoldReceipt;
|
|
133
|
+
releaseWarningSentAtMs: number;
|
|
134
|
+
}>;
|
|
99
135
|
}
|
|
100
136
|
export declare class EscrowPolicyError extends Error {
|
|
101
137
|
constructor();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@learncard/lca-api-service",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.2",
|
|
4
4
|
"description": "",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=24.12 <25"
|
|
@@ -63,16 +63,15 @@
|
|
|
63
63
|
"@fastify/formbody": "^8.0.2",
|
|
64
64
|
"@fastify/rate-limit": "^10.2.0",
|
|
65
65
|
"@fastify/static": "^10.1.1",
|
|
66
|
-
"@learncard/core": "9.4.
|
|
67
|
-
"@learncard/did-web-plugin": "1.1.
|
|
68
|
-
"@learncard/didkit-plugin": "1.10.
|
|
69
|
-
"@learncard/didkit-plugin-node": "0.3.
|
|
66
|
+
"@learncard/core": "9.4.39",
|
|
67
|
+
"@learncard/did-web-plugin": "1.1.39",
|
|
68
|
+
"@learncard/didkit-plugin": "1.10.4",
|
|
69
|
+
"@learncard/didkit-plugin-node": "0.3.4",
|
|
70
70
|
"@learncard/email-templates": "1.2.1",
|
|
71
|
-
"@learncard/helpers": "1.6.
|
|
72
|
-
"@learncard/init": "2.5.
|
|
73
|
-
"@learncard/
|
|
74
|
-
"@learncard/
|
|
75
|
-
"@learncard/sss-key-manager": "0.2.1",
|
|
71
|
+
"@learncard/helpers": "1.6.3",
|
|
72
|
+
"@learncard/init": "2.5.4",
|
|
73
|
+
"@learncard/types": "5.23.1",
|
|
74
|
+
"@learncard/sss-key-manager": "0.2.2",
|
|
76
75
|
"@sentry/serverless": "^7.61.0",
|
|
77
76
|
"@trpc/server": "11.8.0",
|
|
78
77
|
"@types/jsonwebtoken": "^9.0.8",
|
package/project.json
CHANGED
|
@@ -14,7 +14,14 @@
|
|
|
14
14
|
"targets": {
|
|
15
15
|
"build": {
|
|
16
16
|
"executor": "nx:run-script",
|
|
17
|
-
"inputs": [
|
|
17
|
+
"inputs": [
|
|
18
|
+
"source",
|
|
19
|
+
"{projectRoot}/bundle.mjs",
|
|
20
|
+
"{projectRoot}/esbuildPlugins.cjs",
|
|
21
|
+
"{projectRoot}/package.json",
|
|
22
|
+
"{projectRoot}/tsconfig*.json",
|
|
23
|
+
"{workspaceRoot}/packages/service-config/src/**/*"
|
|
24
|
+
],
|
|
18
25
|
"options": {
|
|
19
26
|
"script": "build"
|
|
20
27
|
}
|
package/serverless.yml
CHANGED
|
@@ -24,6 +24,8 @@ custom:
|
|
|
24
24
|
packager: pnpm
|
|
25
25
|
plugins: esbuildPlugins.cjs
|
|
26
26
|
bundle: true
|
|
27
|
+
# Deployment builds run once; avoid leaking rebuild contexts when plugins fail.
|
|
28
|
+
skipRebuild: true
|
|
27
29
|
minify: true
|
|
28
30
|
# @learncard/init's default (node.import) entry uses createRequire('./index.cjs'),
|
|
29
31
|
# which esbuild cannot bundle — the emitted Lambda bundle is left with a runtime
|
|
@@ -64,7 +64,7 @@ export const lcaApiEnvironmentShape = {
|
|
|
64
64
|
* silently DoS recovery. See services/escrow-ledger-monitor/README.md. */
|
|
65
65
|
ESCROW_RELEASE_KILL_SWITCH: optionalEnvironmentBoolean,
|
|
66
66
|
ESCROW_ENCLAVE_REMOTE_TIMEOUT_MS: optionalEnvironmentString
|
|
67
|
-
.transform(value => (value === undefined ?
|
|
67
|
+
.transform(value => (value === undefined ? 15_000 : Number(value)))
|
|
68
68
|
.pipe(z.number().int().positive().max(30_000)),
|
|
69
69
|
ESCROW_HOLD_DURATION_MS: optionalEnvironmentString
|
|
70
70
|
.transform(value => (value === undefined ? 604_800_000 : Number(value)))
|
|
@@ -101,6 +101,14 @@ describe('escrow deployment wiring', () => {
|
|
|
101
101
|
);
|
|
102
102
|
});
|
|
103
103
|
|
|
104
|
+
it('keeps every checked-in enclave timeout above the host forwarding deadline', () => {
|
|
105
|
+
// enclave 10s < host 12s < lca-api; an equal or shorter value races the response.
|
|
106
|
+
for (const [stage, config] of Object.entries(stages)) {
|
|
107
|
+
const value = (config as Record<string, string>).ESCROW_ENCLAVE_REMOTE_TIMEOUT_MS;
|
|
108
|
+
if (value !== undefined) expect(Number(value), stage).toBeGreaterThan(12_000);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
104
112
|
it('never checks in escrow credentials', () => {
|
|
105
113
|
for (const [stage, config] of Object.entries(stages)) {
|
|
106
114
|
for (const name of [...SOURCES.bundle, ...SOURCES.toggle]) {
|