@learncard/lca-api-service 1.4.0 → 1.5.1

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.
Files changed (191) hide show
  1. package/.env.example +73 -0
  2. package/AGENTS.md +106 -10
  3. package/CHANGELOG.md +52 -0
  4. package/bootstrapLambda.test.ts +76 -0
  5. package/bundle.mjs +9 -1
  6. package/config/config.dev.json +23 -0
  7. package/config/config.example.json +22 -0
  8. package/config/config.json +1 -0
  9. package/config/config.production.json +12 -0
  10. package/config/config.scouts.dev.json +11 -0
  11. package/config/config.scouts.production.json +11 -0
  12. package/dist/app.d.ts +67 -3
  13. package/dist/cache/getDel.d.ts +2 -0
  14. package/dist/cache/getDel.test.d.ts +1 -0
  15. package/dist/cache/login-tickets.d.ts +12 -0
  16. package/dist/config/applyDockerStageConfig.d.ts +1 -0
  17. package/dist/config/cacheEnvironment.d.ts +16 -0
  18. package/dist/config/environment.d.ts +101 -0
  19. package/dist/config/escrowBundleMode.test.d.ts +1 -0
  20. package/dist/config/escrowDeployment.test.d.ts +1 -0
  21. package/dist/config/oidcEnvironment.d.ts +74 -0
  22. package/dist/config/serverlessFunctionEnv.test.d.ts +1 -0
  23. package/dist/config/stageConfig.d.ts +62 -0
  24. package/dist/config/stageConfig.test.d.ts +1 -0
  25. package/dist/didkit_wasm_bg.wasm +0 -0
  26. package/dist/docker-entry.d.ts +1 -1
  27. package/dist/helpers/auth.helpers.d.ts +1 -1
  28. package/dist/helpers/escrowCancelLink.d.ts +7 -0
  29. package/dist/helpers/escrowCancelLink.test.d.ts +1 -0
  30. package/dist/helpers/escrowCancelToken.d.ts +4 -0
  31. package/dist/helpers/escrowCancelToken.test.d.ts +1 -0
  32. package/dist/helpers/keycloak.helpers.d.ts +23 -0
  33. package/dist/helpers/login-ticket.helpers.d.ts +31 -0
  34. package/dist/helpers/login-ticket.helpers.test.d.ts +1 -0
  35. package/dist/helpers/oidc.helpers.d.ts +47 -0
  36. package/dist/helpers/rate-limit.helpers.d.ts +6 -0
  37. package/dist/helpers/rate-limit.helpers.test.d.ts +1 -0
  38. package/dist/helpers/social-token.helpers.d.ts +19 -0
  39. package/dist/index.d.ts +1 -0
  40. package/dist/index.js +2 -2
  41. package/dist/jobs/escrowBlobRewrap.d.ts +9 -0
  42. package/dist/jobs/escrowHoldReminders.d.ts +12 -0
  43. package/dist/models/AuthSubject.d.ts +20 -0
  44. package/dist/models/EscrowHold.d.ts +79 -1
  45. package/dist/models/Notification.d.ts +5 -0
  46. package/dist/models/UserKey.d.ts +11 -1
  47. package/dist/models/index.d.ts +1 -0
  48. package/dist/oidc.d.ts +5 -0
  49. package/dist/publish-metafile.json +1 -0
  50. package/dist/routes/auth.d.ts +31 -0
  51. package/dist/routes/escrow.d.ts +18 -4
  52. package/dist/routes/keys.d.ts +1 -0
  53. package/dist/routes/notifications.d.ts +5 -0
  54. package/dist/routes/signing-authority.d.ts +14 -0
  55. package/dist/services/escrow-enclave/blobIdentity.d.ts +10 -0
  56. package/dist/services/escrow-enclave/blobIdentity.test.d.ts +1 -0
  57. package/dist/services/escrow-enclave/cancellation.d.ts +3 -0
  58. package/dist/services/escrow-enclave/enclaveTrustAnchors.d.ts +1 -0
  59. package/dist/services/escrow-enclave/index.d.ts +16 -0
  60. package/dist/services/escrow-enclave/notifications.d.ts +19 -4
  61. package/dist/services/escrow-enclave/notifications.test.d.ts +1 -0
  62. package/dist/services/escrow-enclave/production.test.d.ts +1 -0
  63. package/dist/services/escrow-enclave/remoteEnclave.contract.test.d.ts +1 -0
  64. package/dist/services/escrow-enclave/remoteEnclave.d.ts +20 -0
  65. package/dist/services/escrow-enclave/remoteEnclave.test.d.ts +1 -0
  66. package/dist/services/escrow-enclave/remoteEnclave.tls.test.d.ts +1 -0
  67. package/dist/services/escrow-enclave/softwareEnclave.d.ts +12 -2
  68. package/dist/services/escrow-enclave/types.d.ts +52 -8
  69. package/dist/types/notifications.d.ts +5 -0
  70. package/esbuildPlugins.cjs +1 -0
  71. package/lambda.ts +27 -117
  72. package/lambdaApp.ts +146 -0
  73. package/oidcLambda.test.ts +38 -0
  74. package/oidcLambda.ts +18 -0
  75. package/oidcLambdaApp.ts +11 -0
  76. package/package.json +22 -13
  77. package/project.json +8 -1
  78. package/scripts/README.md +88 -0
  79. package/scripts/check-seed-encryption-infrastructure.mjs +18 -2
  80. package/scripts/escrow-blob-mode-report.test.ts +227 -0
  81. package/scripts/escrow-blob-mode-report.ts +465 -0
  82. package/scripts/keycloak-admin.ts +108 -0
  83. package/scripts/keycloak-provider-links.ts +147 -0
  84. package/scripts/print-seed-rollout-flags.ts +21 -0
  85. package/scripts/provision-keycloak-users.ts +419 -0
  86. package/seedMigrationApp.test.ts +145 -0
  87. package/seedMigrationApp.ts +50 -0
  88. package/seedMigrationLambda.test.ts +30 -135
  89. package/seedMigrationLambda.ts +15 -46
  90. package/serverless-local.yml +25 -1
  91. package/serverless.function-env.cjs +58 -0
  92. package/serverless.yml +174 -37
  93. package/src/accesslayer/notifications/index.ts +9 -2
  94. package/src/accesslayer/signing-authority/index.ts +4 -1
  95. package/src/app.ts +2 -0
  96. package/src/cache/getDel.test.ts +34 -0
  97. package/src/cache/getDel.ts +32 -0
  98. package/src/cache/index.ts +1 -1
  99. package/src/cache/login-tickets.ts +26 -0
  100. package/src/config/applyDockerStageConfig.ts +6 -0
  101. package/src/config/cacheEnvironment.ts +34 -0
  102. package/src/config/environment.test.ts +45 -2
  103. package/src/config/environment.ts +90 -23
  104. package/src/config/escrowBundleMode.test.ts +52 -0
  105. package/src/config/escrowDeployment.test.ts +114 -0
  106. package/src/config/oidcEnvironment.ts +59 -0
  107. package/src/config/serverlessFunctionEnv.test.ts +98 -0
  108. package/src/config/stageConfig.test.ts +26 -0
  109. package/src/config/stageConfig.ts +24 -0
  110. package/src/docker-entry.ts +9 -0
  111. package/src/helpers/auth.helpers.ts +68 -30
  112. package/src/helpers/escrowCancelLink.test.ts +60 -0
  113. package/src/helpers/escrowCancelLink.ts +38 -0
  114. package/src/helpers/escrowCancelToken.test.ts +105 -0
  115. package/src/helpers/escrowCancelToken.ts +47 -0
  116. package/src/helpers/keycloak.helpers.ts +120 -0
  117. package/src/helpers/login-ticket.helpers.test.ts +45 -0
  118. package/src/helpers/login-ticket.helpers.ts +91 -0
  119. package/src/helpers/oidc.helpers.ts +354 -0
  120. package/src/helpers/rate-limit.helpers.test.ts +61 -0
  121. package/src/helpers/rate-limit.helpers.ts +73 -0
  122. package/src/helpers/social-token.helpers.ts +144 -0
  123. package/src/index.ts +1 -0
  124. package/src/jobs/escrowBlobRewrap.ts +121 -0
  125. package/src/jobs/escrowHoldReminders.ts +177 -0
  126. package/src/models/AuthSubject.ts +147 -0
  127. package/src/models/EscrowHold.ts +321 -5
  128. package/src/models/UserKey.ts +91 -6
  129. package/src/models/index.ts +1 -0
  130. package/src/oidc.ts +371 -0
  131. package/src/routes/auth.ts +173 -0
  132. package/src/routes/escrow.ts +281 -34
  133. package/src/routes/keys.ts +5 -0
  134. package/src/routes/signing-authority.ts +39 -1
  135. package/src/services/delivery/adapters/postmark.adapter.ts +43 -1
  136. package/src/services/escrow-enclave/README.md +81 -10
  137. package/src/services/escrow-enclave/__fixtures__/tls/pinned-ca.pem +11 -0
  138. package/src/services/escrow-enclave/__fixtures__/tls/pinned-leaf.key +5 -0
  139. package/src/services/escrow-enclave/__fixtures__/tls/pinned-leaf.pem +12 -0
  140. package/src/services/escrow-enclave/__fixtures__/tls/rogue-ca.pem +11 -0
  141. package/src/services/escrow-enclave/__fixtures__/tls/rogue-leaf.key +5 -0
  142. package/src/services/escrow-enclave/__fixtures__/tls/rogue-leaf.pem +12 -0
  143. package/src/services/escrow-enclave/blobIdentity.test.ts +42 -0
  144. package/src/services/escrow-enclave/blobIdentity.ts +55 -0
  145. package/src/services/escrow-enclave/cancellation.ts +59 -0
  146. package/src/services/escrow-enclave/enclaveTrustAnchors.ts +34 -0
  147. package/src/services/escrow-enclave/index.ts +145 -7
  148. package/src/services/escrow-enclave/notifications.test.ts +377 -0
  149. package/src/services/escrow-enclave/notifications.ts +340 -6
  150. package/src/services/escrow-enclave/production.test.ts +34 -0
  151. package/src/services/escrow-enclave/remoteEnclave.contract.test.ts +344 -0
  152. package/src/services/escrow-enclave/remoteEnclave.test.ts +490 -0
  153. package/src/services/escrow-enclave/remoteEnclave.tls.test.ts +62 -0
  154. package/src/services/escrow-enclave/remoteEnclave.ts +263 -0
  155. package/src/services/escrow-enclave/softwareEnclave.test.ts +231 -30
  156. package/src/services/escrow-enclave/softwareEnclave.ts +97 -11
  157. package/src/services/escrow-enclave/types.ts +63 -17
  158. package/test/auth-tickets.spec.ts +480 -0
  159. package/test/helpers/keycloak-broker.ts +181 -0
  160. package/test/helpers/live-mongo.ts +9 -0
  161. package/test/jobs/escrowBlobRewrap.spec.ts +262 -0
  162. package/test/jobs/escrowHoldReminders.spec.ts +319 -0
  163. package/test/keycloak-admin.spec.ts +58 -0
  164. package/test/keycloak-broker-roundtrip.integration.spec.ts +40 -0
  165. package/test/keycloak-migration.integration.spec.ts +266 -0
  166. package/test/keycloak-provider-links.spec.ts +201 -0
  167. package/test/keycloak-social-broker.integration.spec.ts +228 -0
  168. package/test/keycloak-verify.integration.spec.ts +100 -0
  169. package/test/keycloak-verify.spec.ts +307 -0
  170. package/test/keys-escrow.spec.ts +963 -24
  171. package/test/models/authSubjectIndexes.spec.ts +132 -0
  172. package/test/models/escrow.spec.ts +375 -13
  173. package/test/notifications.spec.ts +4 -0
  174. package/test/oidc-e2e/broker.spec.ts +116 -0
  175. package/test/oidc-e2e/compose.yaml +33 -0
  176. package/test/oidc-e2e/fixtures.ts +245 -0
  177. package/test/oidc-e2e/lock.test.mjs +73 -0
  178. package/test/oidc-e2e/playwright.config.ts +21 -0
  179. package/test/oidc-e2e/run.mjs +312 -0
  180. package/test/oidc-e2e/tsconfig.json +10 -0
  181. package/test/oidc-e2e/types.ts +21 -0
  182. package/test/oidc.integration.spec.ts +54 -0
  183. package/test/oidc.spec.ts +697 -0
  184. package/test/oidcLambdaApp.boot.spec.ts +105 -0
  185. package/test/provision-keycloak-users.spec.ts +299 -0
  186. package/test/signing-authority.spec.ts +50 -0
  187. package/tsconfig.build.json +1 -1
  188. package/tsconfig.json +1 -0
  189. package/vitest.config.ts +24 -1
  190. package/vitest.integration.config.ts +20 -3
  191. package/serverless.escrow-env.cjs +0 -20
@@ -0,0 +1,31 @@
1
+ export declare const authRouter: import("@trpc/server").TRPCBuiltRouter<{
2
+ ctx: import("@routes").Context;
3
+ meta: import("trpc-to-openapi").OpenApiMeta;
4
+ errorShape: import("@trpc/server").TRPCDefaultErrorShape;
5
+ transformer: false;
6
+ }, import("@trpc/server").TRPCDecorateCreateRouterOptions<{
7
+ requestLoginTicket: import("@trpc/server").TRPCMutationProcedure<{
8
+ input: {
9
+ email: string;
10
+ code: string;
11
+ };
12
+ output: {
13
+ success: boolean;
14
+ ticket?: string | undefined;
15
+ error?: string | undefined;
16
+ };
17
+ meta: import("trpc-to-openapi").OpenApiMeta;
18
+ }>;
19
+ requestSocialLoginTicket: import("@trpc/server").TRPCMutationProcedure<{
20
+ input: {
21
+ provider: "google" | "apple";
22
+ idToken: string;
23
+ };
24
+ output: {
25
+ success: boolean;
26
+ ticket?: string | undefined;
27
+ error?: string | undefined;
28
+ };
29
+ meta: import("trpc-to-openapi").OpenApiMeta;
30
+ }>;
31
+ }>>;
@@ -1,11 +1,14 @@
1
+ import { type Context } from '@routes';
1
2
  export declare const escrowRouter: import("@trpc/server").TRPCBuiltRouter<{
2
- ctx: import("@routes").Context;
3
+ ctx: Context;
3
4
  meta: import("trpc-to-openapi").OpenApiMeta;
4
5
  errorShape: import("@trpc/server").TRPCDefaultErrorShape;
5
6
  transformer: false;
6
7
  }, import("@trpc/server").TRPCDecorateCreateRouterOptions<{
7
8
  getAttestation: import("@trpc/server").TRPCQueryProcedure<{
8
- input: Record<string, never>;
9
+ input: {
10
+ nonce?: string | undefined;
11
+ };
9
12
  output: {
10
13
  attestation: {
11
14
  mode: "software" | "nitro";
@@ -19,6 +22,7 @@ export declare const escrowRouter: import("@trpc/server").TRPCBuiltRouter<{
19
22
  };
20
23
  document: string;
21
24
  issuedAt: string;
25
+ previousKeyIds?: string[] | undefined;
22
26
  };
23
27
  holdDurationMs: number;
24
28
  };
@@ -79,10 +83,10 @@ export declare const escrowRouter: import("@trpc/server").TRPCBuiltRouter<{
79
83
  recoverySessionToken?: string | undefined;
80
84
  };
81
85
  output: {
86
+ holdId: string;
87
+ releasePolicy: "pin" | "hold";
82
88
  requestedAt: string;
83
89
  releaseAfter: string;
84
- releasePolicy: "pin" | "hold";
85
- holdId: string;
86
90
  status: "pending";
87
91
  resumeToken: string | null;
88
92
  pinSalt?: string | undefined;
@@ -120,6 +124,16 @@ export declare const escrowRouter: import("@trpc/server").TRPCBuiltRouter<{
120
124
  };
121
125
  meta: import("trpc-to-openapi").OpenApiMeta;
122
126
  }>;
127
+ cancelRecoveryByLink: import("@trpc/server").TRPCMutationProcedure<{
128
+ input: {
129
+ holdId: string;
130
+ token: string;
131
+ };
132
+ output: {
133
+ cancelled: boolean;
134
+ };
135
+ meta: import("trpc-to-openapi").OpenApiMeta;
136
+ }>;
123
137
  completeRecovery: import("@trpc/server").TRPCMutationProcedure<{
124
138
  input: {
125
139
  holdId: string;
@@ -153,6 +153,7 @@ export declare const keysRouter: import("@trpc/server").TRPCBuiltRouter<{
153
153
  salt?: string | undefined;
154
154
  };
155
155
  sssActivationState: "provisional" | "active";
156
+ escrowStale?: "mode-mismatch" | "key-rotated" | undefined;
156
157
  } | null;
157
158
  meta: import("trpc-to-openapi").OpenApiMeta;
158
159
  }>;
@@ -207,6 +207,11 @@ export declare const notificationsRouter: import("@trpc/server").TRPCBuiltRouter
207
207
  approvedAt: string;
208
208
  contractUpdatedAt: string;
209
209
  } | undefined;
210
+ referral?: {
211
+ requestId: string;
212
+ requestedBy: string;
213
+ externalReferenceId?: string | undefined;
214
+ } | undefined;
210
215
  uris?: string[] | undefined;
211
216
  } | undefined;
212
217
  inbox?: {
@@ -18,6 +18,20 @@ export declare const signingAuthorityRouter: import("@trpc/server").TRPCBuiltRou
18
18
  };
19
19
  meta: import("trpc-to-openapi").OpenApiMeta;
20
20
  }>;
21
+ ensureManagedSigningAuthority: import("@trpc/server").TRPCMutationProcedure<{
22
+ input: {
23
+ name: string;
24
+ ownerDid: string;
25
+ };
26
+ output: {
27
+ ownerDid: string;
28
+ name: string;
29
+ did: string;
30
+ endpoint: string;
31
+ _id?: string | undefined;
32
+ };
33
+ meta: import("trpc-to-openapi").OpenApiMeta;
34
+ }>;
21
35
  signingAuthorities: import("@trpc/server").TRPCQueryProcedure<{
22
36
  input: void;
23
37
  output: {
@@ -0,0 +1,10 @@
1
+ export interface EscrowEnvelopeFields {
2
+ version: number;
3
+ algorithm: string;
4
+ keyId: string;
5
+ ephemeralPublicKey: string;
6
+ salt: string;
7
+ iv: string;
8
+ ciphertext: string;
9
+ }
10
+ export declare const escrowBlobIdentity: (envelope: EscrowEnvelopeFields) => string;
@@ -0,0 +1,3 @@
1
+ import { type EscrowHold, type MongoUserKeyType } from '@models';
2
+ export declare const logEscrowCancellationPending: (urgent: boolean) => void;
3
+ export declare const cancelHoldInEnclave: (hold: EscrowHold, userKey?: MongoUserKeyType | null, now?: Date) => Promise<boolean>;
@@ -0,0 +1 @@
1
+ export declare const pinnedEnclaveCa: (baseUrl: string) => string | undefined;
@@ -1,9 +1,25 @@
1
+ import type { EscrowBlobStaleReason } from '@learncard/types';
1
2
  import { type EscrowEnclave } from './types';
2
3
  export * from './types';
3
4
  export * from './softwareEnclave';
5
+ export * from './remoteEnclave';
4
6
  export * from './notifications';
5
7
  export declare const isEscrowEnabled: () => boolean;
8
+ export declare const isEscrowRemoteMode: () => boolean;
9
+ export declare const activeEscrowClientMode: () => "software" | "nitro";
6
10
  export declare const getEscrowHoldDurationMs: () => number;
7
11
  export declare const getEscrowHoldRestartMinAgeMs: () => number;
8
12
  export declare const getEscrowEnclave: () => EscrowEnclave;
13
+ export type { EscrowBlobStaleReason };
14
+ interface EscrowAttestationIdentity {
15
+ keyId: string;
16
+ previousKeyIds: string[];
17
+ mode: 'software' | 'nitro';
18
+ }
19
+ export declare const getEnclaveAttestationIdentity: () => Promise<EscrowAttestationIdentity>;
20
+ export declare const getEscrowBlobStaleReason: (blob: {
21
+ enclaveMode: "software" | "nitro";
22
+ enclaveKeyId: string;
23
+ }) => Promise<EscrowBlobStaleReason | undefined>;
9
24
  export declare const __setEscrowEnclaveForTests: (replacement: EscrowEnclave | undefined) => void;
25
+ export declare const __resetEscrowAttestationCacheForTests: () => void;
@@ -1,9 +1,24 @@
1
- import type { EscrowHold } from '../../models/EscrowHold';
2
- import type { MongoUserKeyType } from '../../models/UserKey';
1
+ import type { ResolvedTenant } from '@learncard/email-templates';
2
+ import type { EscrowHold, MongoUserKeyType } from '@models';
3
+ import { type DeliveryService } from '../delivery';
4
+ export type EscrowHoldEventKind = 'started' | 'reminder' | 'completed' | 'cancelled' | 'pin-locked';
3
5
  export interface EscrowHoldEvent {
4
- kind: 'started' | 'cancelled' | 'completed';
6
+ kind: EscrowHoldEventKind;
5
7
  hold: EscrowHold;
6
8
  userKey: MongoUserKeyType;
7
9
  reason?: 'superseded';
10
+ cancelToken?: string;
11
+ tenant?: ResolvedTenant;
12
+ activeHold?: EscrowHold;
8
13
  }
9
- export declare const notifyEscrowHoldEvent: (event: EscrowHoldEvent) => Promise<void>;
14
+ type SendPushNotification = typeof import('@helpers/pushNotifications.helpers').sendPushNotification;
15
+ export interface EscrowHoldNotifierDeps {
16
+ send: DeliveryService['send'];
17
+ sendPush: SendPushNotification;
18
+ now: () => Date;
19
+ rotateCancelToken: (holdId: string) => Promise<string | null>;
20
+ recordNotification: (holdId: string, kind: EscrowHoldEventKind) => Promise<void>;
21
+ }
22
+ export declare const ESCROW_NOTIFICATION_TIMEOUT_MS = 3000;
23
+ export declare const notifyEscrowHoldEvent: (event: EscrowHoldEvent, overrides?: Partial<EscrowHoldNotifierDeps>) => Promise<boolean>;
24
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,20 @@
1
+ import { type EscrowEnclave } from './types';
2
+ export interface RemoteEnclaveTransportResponse {
3
+ status: number;
4
+ data: unknown;
5
+ }
6
+ export interface RemoteEnclaveTransport {
7
+ post(url: string, body: unknown, config: {
8
+ headers: Record<string, string>;
9
+ timeout: number;
10
+ }): Promise<RemoteEnclaveTransportResponse>;
11
+ }
12
+ export declare const createHttpTransport: (caPem?: string) => RemoteEnclaveTransport;
13
+ export interface RemoteEnclaveConfig {
14
+ baseUrl: string;
15
+ token: string;
16
+ timeoutMs: number;
17
+ caPem?: string;
18
+ transport?: RemoteEnclaveTransport;
19
+ }
20
+ export declare const createRemoteEnclave: (config: RemoteEnclaveConfig) => EscrowEnclave;
@@ -1,17 +1,27 @@
1
- import { type EscrowEnclave, type EnclaveAttestation, type VerifyEscrowBlobInput, type VerifyEscrowBlobResult, type ReleaseRequest, type ReleaseResult, type CarryPinVerifierInput } from './types';
1
+ import type { EnclaveCreateHoldInput, EscrowHoldRecord, CancelHoldRequest } from './types';
2
+ import { type EscrowEnclave, type EnclaveAttestation, type VerifyEscrowBlobInput, type VerifyEscrowBlobResult, type ReleaseRequest, type ReleaseResult, type CarryPinVerifierInput, type RewrapEscrowBlobInput } from './types';
2
3
  export interface SoftwareEnclaveConfig {
3
4
  privateKeys: Record<string, string>;
4
5
  activeKeyId: string;
6
+ holdDurationMs?: number;
5
7
  }
6
8
  export declare class SoftwareEnclave implements EscrowEnclave {
7
9
  private readonly privateKeys;
8
10
  private readonly activeKeyId;
11
+ private readonly holdDurationMs;
9
12
  constructor(config: SoftwareEnclaveConfig);
10
- getAttestation(): Promise<EnclaveAttestation>;
13
+ createHold(input: EnclaveCreateHoldInput): Promise<{
14
+ holdRecord: EscrowHoldRecord;
15
+ }>;
16
+ cancelHold(input: CancelHoldRequest): Promise<void>;
17
+ getAttestation(_nonce?: Uint8Array): Promise<EnclaveAttestation>;
11
18
  private decrypt;
12
19
  verifyEscrowBlob(input: VerifyEscrowBlobInput): Promise<VerifyEscrowBlobResult>;
13
20
  carryPinVerifier(input: CarryPinVerifierInput): Promise<{
14
21
  envelope: import("@learncard/sss-key-manager").EscrowEnvelope;
15
22
  }>;
23
+ rewrapEscrowBlob(input: RewrapEscrowBlobInput): Promise<{
24
+ envelope: import("@learncard/sss-key-manager").EscrowEnvelope;
25
+ }>;
16
26
  releaseEscrow(input: ReleaseRequest): Promise<ReleaseResult>;
17
27
  }
@@ -1,8 +1,8 @@
1
1
  import type { EscrowEnvelope } from '@learncard/sss-key-manager';
2
- import type { EscrowHold } from '../../models/EscrowHold';
3
2
  export interface EnclaveAttestation {
4
3
  mode: 'software' | 'nitro';
5
4
  keyId: string;
5
+ previousKeyIds?: string[];
6
6
  publicKey: string;
7
7
  measurements: {
8
8
  imageSha384?: string;
@@ -13,7 +13,40 @@ export interface EnclaveAttestation {
13
13
  document: string;
14
14
  issuedAt: string;
15
15
  }
16
- export type EscrowHoldForEnclave = Pick<EscrowHold, '_id' | 'status' | 'releaseAfter' | 'releasePolicy' | 'primaryDid' | 'shareVersion' | 'clientEphemeralPublicKey'>;
16
+ export interface EscrowHoldRecord {
17
+ [key: string]: unknown;
18
+ hold: {
19
+ [key: string]: unknown;
20
+ holdId: string;
21
+ did: string;
22
+ shareVersion: number;
23
+ blobHash: string;
24
+ enrollmentEpoch: number;
25
+ releasePolicy: 'hold' | 'pin';
26
+ clientEphemeralPublicKey: string;
27
+ createdLo: number;
28
+ createdHi: number;
29
+ policyVersion: number;
30
+ signature: string;
31
+ };
32
+ holdDurationMs: number;
33
+ ledgerSeq: number;
34
+ }
35
+ export interface EnclaveCreateHoldInput {
36
+ envelope: EscrowEnvelope;
37
+ holdId: string;
38
+ expectedDid: string;
39
+ expectedShareVersion: number;
40
+ enrollmentEpoch: number;
41
+ releasePolicy: 'hold' | 'pin';
42
+ clientEphemeralPublicKey: string;
43
+ }
44
+ export interface CancelHoldRequest {
45
+ envelope: EscrowEnvelope;
46
+ hold: EscrowHoldRecord;
47
+ clientEphemeralPublicKey: string;
48
+ expectedDid: string;
49
+ }
17
50
  export interface VerifyEscrowBlobInput {
18
51
  envelope: EscrowEnvelope;
19
52
  expectedDid: string;
@@ -27,11 +60,7 @@ export type VerifyEscrowBlobResult = {
27
60
  hasPin: boolean;
28
61
  reason: string;
29
62
  };
30
- export interface ReleaseRequest {
31
- envelope: EscrowEnvelope;
32
- hold: EscrowHoldForEnclave;
33
- clientEphemeralPublicKey: string;
34
- expectedDid: string;
63
+ export interface ReleaseRequest extends CancelHoldRequest {
35
64
  now?: Date;
36
65
  pinProof?: string;
37
66
  }
@@ -44,14 +73,29 @@ export interface CarryPinVerifierInput {
44
73
  expectedDid: string;
45
74
  sourceShareVersion: number;
46
75
  targetShareVersion: number;
76
+ sourceEnrollmentEpoch: number;
77
+ targetEnrollmentEpoch: number;
78
+ }
79
+ export interface RewrapEscrowBlobInput {
80
+ envelope: EscrowEnvelope;
81
+ expectedDid: string;
82
+ expectedShareVersion: number;
83
+ sourceEnrollmentEpoch: number;
47
84
  }
48
85
  export interface EscrowEnclave {
49
- getAttestation(): Promise<EnclaveAttestation>;
86
+ getAttestation(nonce?: Uint8Array): Promise<EnclaveAttestation>;
50
87
  verifyEscrowBlob(input: VerifyEscrowBlobInput): Promise<VerifyEscrowBlobResult>;
51
88
  carryPinVerifier(input: CarryPinVerifierInput): Promise<{
52
89
  envelope: EscrowEnvelope;
53
90
  }>;
91
+ rewrapEscrowBlob(input: RewrapEscrowBlobInput): Promise<{
92
+ envelope: EscrowEnvelope;
93
+ }>;
54
94
  releaseEscrow(input: ReleaseRequest): Promise<ReleaseResult>;
95
+ createHold(input: EnclaveCreateHoldInput): Promise<{
96
+ holdRecord: EscrowHoldRecord;
97
+ }>;
98
+ cancelHold(input: CancelHoldRequest): Promise<void>;
55
99
  }
56
100
  export declare class EscrowPolicyError extends Error {
57
101
  constructor();
@@ -237,6 +237,11 @@ export declare const PaginatedNotificationsValidator: z.ZodObject<{
237
237
  approvedAt: z.ZodString;
238
238
  contractUpdatedAt: z.ZodString;
239
239
  }, z.core.$strip>>;
240
+ referral: z.ZodOptional<z.ZodObject<{
241
+ requestId: z.ZodString;
242
+ requestedBy: z.ZodString;
243
+ externalReferenceId: z.ZodOptional<z.ZodString>;
244
+ }, z.core.$strip>>;
240
245
  id: z.ZodString;
241
246
  action: z.ZodEnum<{
242
247
  write: "write";
@@ -41,6 +41,7 @@ module.exports = [
41
41
  forceCjsSodiumWrapperPlugin,
42
42
  copyDidkitWasmPlugin,
43
43
  sentryEsbuildPlugin({
44
+ telemetry: process.env.SENTRY_BUILD_TELEMETRY !== 'false',
44
45
  authToken: process.env.SENTRY_AUTH_TOKEN,
45
46
  org: process.env.SENTRY_ORG,
46
47
  project: process.env.SENTRY_PROJECT,
package/lambda.ts CHANGED
@@ -1,128 +1,38 @@
1
- import serverlessHttp from 'serverless-http';
2
- import type { Context, APIGatewayProxyResultV2, APIGatewayProxyEventV2 } from 'aws-lambda';
3
- import { awsLambdaRequestHandler } from '@trpc/server/adapters/aws-lambda';
4
- import * as Sentry from '@sentry/serverless';
1
+ import { bootstrapLambda } from '@learncard/service-config';
5
2
 
6
- import app from './src/openapi';
7
- import didWebApp from './src/dids';
8
- import { appRouter, createContext } from './src/app';
9
- import { getEmptyLearnCard } from './src/helpers/learnCard.helpers';
10
- import { createOpenApiAwsLambdaHandler } from './src/helpers/shim';
11
- import {
12
- handleTrpcError,
13
- sentryBeforeSend,
14
- getTracesSampleRate,
15
- } from './src/helpers/sentry.helpers';
16
- import { environment } from './src/config/environment';
17
- import { toServerlessApplication } from './src/helpers/serverlessApplication';
18
- import { ensureUserKeysIndexes, createEscrowHoldsIndexes } from './src/models';
3
+ import { base, stages } from './src/config/stageConfig';
19
4
 
20
- const startupPromise = Promise.all([
21
- getEmptyLearnCard(), // Load WASM in for better cold starts
22
- ensureUserKeysIndexes(),
23
- createEscrowHoldsIndexes(),
24
- ]);
5
+ type LambdaApp = typeof import('./lambdaApp');
25
6
 
26
- const isWarmupEvent = (event: APIGatewayProxyEventV2): boolean =>
27
- 'source' in event && event.source === 'serverless-plugin-warmup';
28
-
29
- Sentry.AWSLambda.init({
30
- dsn: environment.SENTRY_DSN,
31
- environment: environment.SENTRY_ENV,
32
- enabled: Boolean(environment.SENTRY_DSN),
33
- tracesSampleRate: getTracesSampleRate(),
34
- beforeSend: sentryBeforeSend,
35
- integrations: [
36
- new Sentry.Integrations.Console(),
37
- new Sentry.Integrations.Http({ tracing: true }),
38
- new Sentry.Integrations.ContextLines(),
39
- new Sentry.Integrations.Mongo(),
40
- ],
41
- });
42
-
43
- export const swaggerUiHandler = serverlessHttp(toServerlessApplication(app), {
44
- basePath: '/docs',
45
- });
46
- export const didWebHandler = serverlessHttp(toServerlessApplication(didWebApp));
47
-
48
- export const _openApiHandler = createOpenApiAwsLambdaHandler({
49
- router: appRouter,
50
- responseMeta: () => {
51
- return {
52
- headers: {
53
- 'Access-Control-Allow-Origin': '*',
54
- 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
55
- 'Access-Control-Allow-Headers': 'Authorization, Content-Type',
56
- },
57
- };
58
- },
59
- createContext,
60
- onError: handleTrpcError,
61
- });
62
-
63
- export const _trpcHandler = awsLambdaRequestHandler({
64
- router: appRouter,
65
- createContext,
66
- onError: handleTrpcError,
67
- responseMeta: () => {
68
- return {
69
- headers: {
70
- 'Access-Control-Allow-Origin': '*',
71
- 'Access-Control-Allow-Methods': '*',
72
- 'Access-Control-Allow-Headers': 'authorization',
73
- },
74
- };
75
- },
7
+ const getApplication = bootstrapLambda<LambdaApp>({
8
+ base,
9
+ stages,
10
+ stage: process.env.AWS_LAMBDA_FUNCTION_NAME
11
+ ? process.env.LAMBDA_STAGE
12
+ : process.env.CONFIG_STAGE,
13
+ importApp: () => import('./lambdaApp'),
76
14
  });
77
15
 
78
- export const openApiHandler = Sentry.AWSLambda.wrapHandler(
79
- async (event: APIGatewayProxyEventV2, context: Context): Promise<APIGatewayProxyResultV2> => {
80
- await startupPromise;
81
-
82
- if (isWarmupEvent(event)) {
83
- console.log('[Warmup] Initializing LearnCard...');
84
- await getEmptyLearnCard();
85
- console.log('[Warmup] Done!');
86
- return 'All done! 😄';
87
- }
16
+ export const trpcHandler = async (...args: Parameters<LambdaApp['trpcHandler']>) =>
17
+ (await getApplication()).trpcHandler(...args);
88
18
 
89
- if (event.requestContext.http.method === 'OPTIONS') {
90
- return {
91
- statusCode: 200,
92
- headers: {
93
- 'Access-Control-Allow-Origin': '*',
94
- 'Access-Control-Allow-Headers': '*',
95
- 'Access-Control-Allow-Methods': '*',
96
- },
97
- };
98
- }
19
+ export const openApiHandler = async (...args: Parameters<LambdaApp['openApiHandler']>) =>
20
+ (await getApplication()).openApiHandler(...args);
99
21
 
100
- return _openApiHandler(event, context);
101
- }
102
- );
22
+ export const swaggerUiHandler = async (...args: Parameters<LambdaApp['swaggerUiHandler']>) =>
23
+ (await getApplication()).swaggerUiHandler(...args);
103
24
 
104
- export const trpcHandler = Sentry.AWSLambda.wrapHandler(
105
- async (event: APIGatewayProxyEventV2, context: Context): Promise<APIGatewayProxyResultV2> => {
106
- await startupPromise;
25
+ export const didWebHandler = async (...args: Parameters<LambdaApp['didWebHandler']>) =>
26
+ (await getApplication()).didWebHandler(...args);
107
27
 
108
- if (isWarmupEvent(event)) {
109
- console.log('[Warmup] Initializing LearnCard...');
110
- await getEmptyLearnCard();
111
- console.log('[Warmup] Done!');
112
- return 'All done! 😄';
113
- }
28
+ // Preserve the local/offline handler name without importing the full API for OIDC.
29
+ export { handler as oidcHandler } from './oidcLambda';
114
30
 
115
- if (event.requestContext.http.method === 'OPTIONS') {
116
- return {
117
- statusCode: 200,
118
- headers: {
119
- 'Access-Control-Allow-Origin': '*',
120
- 'Access-Control-Allow-Headers': '*',
121
- 'Access-Control-Allow-Methods': '*',
122
- },
123
- };
124
- }
31
+ // Scheduled (EventBridge) escrow jobs share the API bootstrap: they need Mongo/Postmark from the bundle.
32
+ export const escrowHoldRemindersHandler = async (
33
+ ...args: Parameters<LambdaApp['escrowHoldRemindersHandler']>
34
+ ) => (await getApplication()).escrowHoldRemindersHandler(...args);
125
35
 
126
- return _trpcHandler(event, context);
127
- }
128
- );
36
+ export const escrowBlobRewrapHandler = async (
37
+ ...args: Parameters<LambdaApp['escrowBlobRewrapHandler']>
38
+ ) => (await getApplication()).escrowBlobRewrapHandler(...args);