@learncard/lca-api-service 1.3.2 → 1.5.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/.env.example +99 -2
- package/AGENTS.md +155 -44
- package/CHANGELOG.md +84 -0
- package/bootstrapLambda.test.ts +76 -0
- package/compose.env +5 -0
- package/config/config.dev.json +23 -0
- package/config/config.example.json +22 -0
- package/config/config.json +1 -0
- package/config/config.production.json +12 -0
- package/config/config.scouts.dev.json +11 -0
- package/config/config.scouts.production.json +11 -0
- package/dist/accesslayer/signing-authority/index.d.ts +8 -2
- package/dist/app.d.ts +361 -8
- package/dist/cache/challenges.d.ts +2 -0
- package/dist/cache/getDel.d.ts +2 -0
- package/dist/cache/getDel.test.d.ts +1 -0
- package/dist/cache/login-tickets.d.ts +12 -0
- package/dist/cache/recoverySessions.d.ts +42 -0
- package/dist/config/applyDockerStageConfig.d.ts +1 -0
- package/dist/config/cacheEnvironment.d.ts +16 -0
- package/dist/config/environment.d.ts +253 -2
- package/dist/config/environment.test.d.ts +1 -0
- package/dist/config/escrowBundleMode.test.d.ts +1 -0
- package/dist/config/escrowDeployment.test.d.ts +1 -0
- package/dist/config/oidcEnvironment.d.ts +74 -0
- package/dist/config/serverlessFunctionEnv.test.d.ts +1 -0
- package/dist/config/stageConfig.d.ts +62 -0
- package/dist/config/stageConfig.test.d.ts +1 -0
- package/dist/didkit_wasm_bg.wasm +0 -0
- package/dist/docker-entry.d.ts +1 -1
- package/dist/helpers/auth.helpers.d.ts +1 -1
- package/dist/helpers/escrowCancelLink.d.ts +7 -0
- package/dist/helpers/escrowCancelLink.test.d.ts +1 -0
- package/dist/helpers/escrowCancelToken.d.ts +4 -0
- package/dist/helpers/escrowCancelToken.test.d.ts +1 -0
- package/dist/helpers/keycloak.helpers.d.ts +23 -0
- package/dist/helpers/learnCard.signingAuthority.test.d.ts +1 -0
- package/dist/helpers/login-ticket.helpers.d.ts +31 -0
- package/dist/helpers/login-ticket.helpers.test.d.ts +1 -0
- package/dist/helpers/oidc.helpers.d.ts +47 -0
- package/dist/helpers/rate-limit.helpers.d.ts +6 -0
- package/dist/helpers/rate-limit.helpers.test.d.ts +1 -0
- package/dist/helpers/rateLimit.helpers.d.ts +4 -0
- package/dist/helpers/seedEncryption.helpers.d.ts +23 -0
- package/dist/helpers/seedEncryption.helpers.test.d.ts +1 -0
- package/dist/helpers/social-token.helpers.d.ts +19 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -2
- package/dist/jobs/escrowBlobRewrap.d.ts +9 -0
- package/dist/jobs/escrowHoldReminders.d.ts +12 -0
- package/dist/migrations/signingAuthoritySeeds.d.ts +13 -0
- package/dist/migrations/signingAuthoritySeedsCli.test.d.ts +1 -0
- package/dist/models/AuthSubject.d.ts +20 -0
- package/dist/models/EscrowHold.d.ts +153 -0
- package/dist/models/Notification.d.ts +5 -0
- package/dist/models/SigningAuthority.d.ts +7 -1
- package/dist/models/UserKey.d.ts +326 -3
- package/dist/models/UserKey.escrowPin.test.d.ts +1 -0
- package/dist/models/index.d.ts +2 -0
- package/dist/oidc.d.ts +5 -0
- package/dist/routes/auth.d.ts +31 -0
- package/dist/routes/escrow.d.ts +164 -0
- package/dist/routes/index.d.ts +6 -0
- package/dist/routes/keys.d.ts +182 -7
- package/dist/routes/notifications.d.ts +5 -0
- package/dist/routes/qr-login.d.ts +1 -1
- package/dist/routes/signing-authority.d.ts +14 -0
- package/dist/services/escrow-enclave/blobIdentity.d.ts +10 -0
- package/dist/services/escrow-enclave/blobIdentity.test.d.ts +1 -0
- package/dist/services/escrow-enclave/cancellation.d.ts +3 -0
- package/dist/services/escrow-enclave/enclaveTrustAnchors.d.ts +1 -0
- package/dist/services/escrow-enclave/index.d.ts +25 -0
- package/dist/services/escrow-enclave/notifications.d.ts +24 -0
- package/dist/services/escrow-enclave/notifications.test.d.ts +1 -0
- package/dist/services/escrow-enclave/production.test.d.ts +1 -0
- package/dist/services/escrow-enclave/remoteEnclave.contract.test.d.ts +1 -0
- package/dist/services/escrow-enclave/remoteEnclave.d.ts +20 -0
- package/dist/services/escrow-enclave/remoteEnclave.test.d.ts +1 -0
- package/dist/services/escrow-enclave/remoteEnclave.tls.test.d.ts +1 -0
- package/dist/services/escrow-enclave/softwareEnclave.d.ts +27 -0
- package/dist/services/escrow-enclave/softwareEnclave.test.d.ts +1 -0
- package/dist/services/escrow-enclave/types.d.ts +111 -0
- package/dist/types/notifications.d.ts +5 -0
- package/dist/types/seed-encryption.d.ts +31 -0
- package/dist/types/seed-migration.d.ts +49 -0
- package/esbuildPlugins.cjs +1 -0
- package/lambda.ts +27 -112
- package/lambdaApp.ts +146 -0
- package/oidcLambda.test.ts +38 -0
- package/oidcLambda.ts +18 -0
- package/oidcLambdaApp.ts +11 -0
- package/package.json +30 -14
- package/scripts/README.md +88 -0
- package/scripts/check-seed-encryption-infrastructure.mjs +139 -0
- package/scripts/check-signing-authority-redis.mjs +93 -0
- package/scripts/escrow-blob-mode-report.test.ts +227 -0
- package/scripts/escrow-blob-mode-report.ts +465 -0
- package/scripts/keycloak-admin.ts +108 -0
- package/scripts/keycloak-provider-links.ts +147 -0
- package/scripts/migrate-signing-authority-seeds-local.sh +51 -0
- package/scripts/migrate-signing-authority-seeds.mjs +105 -0
- package/scripts/print-seed-rollout-flags.ts +21 -0
- package/scripts/provision-keycloak-users.ts +419 -0
- package/seedMigrationApp.test.ts +145 -0
- package/seedMigrationApp.ts +50 -0
- package/seedMigrationLambda.test.ts +40 -0
- package/seedMigrationLambda.ts +19 -0
- package/serverless-local.yml +28 -1
- package/serverless.function-env.cjs +58 -0
- package/serverless.yml +294 -26
- package/src/accesslayer/notifications/index.ts +9 -2
- package/src/accesslayer/signing-authority/create.ts +11 -7
- package/src/accesslayer/signing-authority/index.ts +4 -1
- package/src/app.ts +4 -0
- package/src/cache/challenges.ts +12 -2
- package/src/cache/getDel.test.ts +34 -0
- package/src/cache/getDel.ts +32 -0
- package/src/cache/in-memory-lru.ts +1 -0
- package/src/cache/index.ts +1 -1
- package/src/cache/login-tickets.ts +26 -0
- package/src/cache/recoverySessions.ts +252 -0
- package/src/config/applyDockerStageConfig.ts +6 -0
- package/src/config/cacheEnvironment.ts +34 -0
- package/src/config/environment.test.ts +159 -0
- package/src/config/environment.ts +164 -0
- package/src/config/escrowBundleMode.test.ts +52 -0
- package/src/config/escrowDeployment.test.ts +114 -0
- package/src/config/oidcEnvironment.ts +59 -0
- package/src/config/serverlessFunctionEnv.test.ts +98 -0
- package/src/config/stageConfig.test.ts +26 -0
- package/src/config/stageConfig.ts +24 -0
- package/src/docker-entry.ts +13 -0
- package/src/helpers/auth.helpers.ts +68 -30
- package/src/helpers/escrowCancelLink.test.ts +60 -0
- package/src/helpers/escrowCancelLink.ts +38 -0
- package/src/helpers/escrowCancelToken.test.ts +105 -0
- package/src/helpers/escrowCancelToken.ts +47 -0
- package/src/helpers/keycloak.helpers.ts +120 -0
- package/src/helpers/learnCard.helpers.test.ts +11 -1
- package/src/helpers/learnCard.helpers.ts +57 -45
- package/src/helpers/learnCard.signingAuthority.test.ts +194 -0
- package/src/helpers/login-ticket.helpers.test.ts +45 -0
- package/src/helpers/login-ticket.helpers.ts +91 -0
- package/src/helpers/oidc.helpers.ts +354 -0
- package/src/helpers/rate-limit.helpers.test.ts +61 -0
- package/src/helpers/rate-limit.helpers.ts +73 -0
- package/src/helpers/rateLimit.helpers.ts +112 -0
- package/src/helpers/seedEncryption.helpers.test.ts +427 -0
- package/src/helpers/seedEncryption.helpers.ts +372 -0
- package/src/helpers/signingAuthority.helpers.ts +4 -1
- package/src/helpers/social-token.helpers.ts +144 -0
- package/src/index.ts +1 -0
- package/src/jobs/escrowBlobRewrap.ts +121 -0
- package/src/jobs/escrowHoldReminders.ts +177 -0
- package/src/migrations/signingAuthoritySeeds.ts +430 -0
- package/src/migrations/signingAuthoritySeedsCli.test.ts +70 -0
- package/src/models/AuthSubject.ts +147 -0
- package/src/models/EscrowHold.ts +541 -0
- package/src/models/SigningAuthority.ts +14 -3
- package/src/models/UserKey.escrowPin.test.ts +49 -0
- package/src/models/UserKey.ts +1109 -40
- package/src/models/index.ts +2 -0
- package/src/models/pruneOrphanedRecoveryMethods.ts +3 -1
- package/src/oidc.ts +371 -0
- package/src/routes/auth.ts +173 -0
- package/src/routes/credentials.ts +8 -0
- package/src/routes/escrow.ts +1073 -0
- package/src/routes/firebase.ts +90 -48
- package/src/routes/index.ts +98 -5
- package/src/routes/keys.ts +1022 -174
- package/src/routes/qr-login.ts +51 -53
- package/src/routes/signing-authority.ts +39 -1
- package/src/services/delivery/adapters/postmark.adapter.ts +48 -1
- package/src/services/escrow-enclave/README.md +122 -0
- package/src/services/escrow-enclave/__fixtures__/tls/pinned-ca.pem +11 -0
- package/src/services/escrow-enclave/__fixtures__/tls/pinned-leaf.key +5 -0
- package/src/services/escrow-enclave/__fixtures__/tls/pinned-leaf.pem +12 -0
- package/src/services/escrow-enclave/__fixtures__/tls/rogue-ca.pem +11 -0
- package/src/services/escrow-enclave/__fixtures__/tls/rogue-leaf.key +5 -0
- package/src/services/escrow-enclave/__fixtures__/tls/rogue-leaf.pem +12 -0
- package/src/services/escrow-enclave/blobIdentity.test.ts +42 -0
- package/src/services/escrow-enclave/blobIdentity.ts +55 -0
- package/src/services/escrow-enclave/cancellation.ts +59 -0
- package/src/services/escrow-enclave/enclaveTrustAnchors.ts +34 -0
- package/src/services/escrow-enclave/index.ts +174 -0
- package/src/services/escrow-enclave/notifications.test.ts +377 -0
- package/src/services/escrow-enclave/notifications.ts +348 -0
- package/src/services/escrow-enclave/production.test.ts +34 -0
- package/src/services/escrow-enclave/remoteEnclave.contract.test.ts +344 -0
- package/src/services/escrow-enclave/remoteEnclave.test.ts +490 -0
- package/src/services/escrow-enclave/remoteEnclave.tls.test.ts +62 -0
- package/src/services/escrow-enclave/remoteEnclave.ts +263 -0
- package/src/services/escrow-enclave/softwareEnclave.test.ts +450 -0
- package/src/services/escrow-enclave/softwareEnclave.ts +254 -0
- package/src/services/escrow-enclave/types.ts +122 -0
- package/src/types/seed-encryption.ts +51 -0
- package/src/types/seed-migration.ts +61 -0
- package/test/auth-tickets.spec.ts +480 -0
- package/test/credentials.spec.ts +2 -0
- package/test/helpers/keycloak-broker.ts +181 -0
- package/test/helpers/live-mongo.ts +9 -0
- package/test/jobs/escrowBlobRewrap.spec.ts +262 -0
- package/test/jobs/escrowHoldReminders.spec.ts +319 -0
- package/test/keycloak-admin.spec.ts +58 -0
- package/test/keycloak-broker-roundtrip.integration.spec.ts +40 -0
- package/test/keycloak-migration.integration.spec.ts +266 -0
- package/test/keycloak-provider-links.spec.ts +201 -0
- package/test/keycloak-social-broker.integration.spec.ts +228 -0
- package/test/keycloak-verify.integration.spec.ts +100 -0
- package/test/keycloak-verify.spec.ts +307 -0
- package/test/keys-escrow.spec.ts +2328 -0
- package/test/keys-identity-rebind.spec.ts +456 -0
- package/test/keys-lifecycle-invariants.spec.ts +785 -0
- package/test/keys-recovery-confirmation.spec.ts +586 -0
- package/test/keys-recovery-rate-limit.spec.ts +123 -0
- package/test/keys-secret-leakage.spec.ts +265 -0
- package/test/models/authSubjectIndexes.spec.ts +132 -0
- package/test/models/escrow.spec.ts +572 -0
- package/test/notifications.spec.ts +4 -0
- package/test/oidc-e2e/broker.spec.ts +116 -0
- package/test/oidc-e2e/compose.yaml +33 -0
- package/test/oidc-e2e/fixtures.ts +245 -0
- package/test/oidc-e2e/lock.test.mjs +73 -0
- package/test/oidc-e2e/playwright.config.ts +21 -0
- package/test/oidc-e2e/run.mjs +312 -0
- package/test/oidc-e2e/tsconfig.json +10 -0
- package/test/oidc-e2e/types.ts +21 -0
- package/test/oidc.integration.spec.ts +54 -0
- package/test/oidc.spec.ts +697 -0
- package/test/oidcLambdaApp.boot.spec.ts +105 -0
- package/test/provision-keycloak-users.spec.ts +299 -0
- package/test/signing-authority-http.spec.ts +161 -0
- package/test/signing-authority-seed-migration.spec.ts +414 -0
- package/test/signing-authority.spec.ts +90 -0
- package/tsconfig.build.json +1 -1
- package/tsconfig.json +1 -0
- package/vitest.config.ts +24 -1
- package/vitest.integration.config.ts +25 -3
- package/dist/cache/signing-authority.d.ts +0 -6
- package/src/cache/signing-authority.ts +0 -37
package/lambda.ts
CHANGED
|
@@ -1,123 +1,38 @@
|
|
|
1
|
-
import
|
|
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
|
|
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';
|
|
3
|
+
import { base, stages } from './src/config/stageConfig';
|
|
18
4
|
|
|
19
|
-
|
|
5
|
+
type LambdaApp = typeof import('./lambdaApp');
|
|
20
6
|
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
tracesSampleRate: getTracesSampleRate(),
|
|
29
|
-
beforeSend: sentryBeforeSend,
|
|
30
|
-
integrations: [
|
|
31
|
-
new Sentry.Integrations.Console(),
|
|
32
|
-
new Sentry.Integrations.Http({ tracing: true }),
|
|
33
|
-
new Sentry.Integrations.ContextLines(),
|
|
34
|
-
new Sentry.Integrations.Mongo(),
|
|
35
|
-
],
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
export const swaggerUiHandler = serverlessHttp(toServerlessApplication(app), {
|
|
39
|
-
basePath: '/docs',
|
|
40
|
-
});
|
|
41
|
-
export const didWebHandler = serverlessHttp(toServerlessApplication(didWebApp));
|
|
42
|
-
|
|
43
|
-
export const _openApiHandler = createOpenApiAwsLambdaHandler({
|
|
44
|
-
router: appRouter,
|
|
45
|
-
responseMeta: () => {
|
|
46
|
-
return {
|
|
47
|
-
headers: {
|
|
48
|
-
'Access-Control-Allow-Origin': '*',
|
|
49
|
-
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
|
50
|
-
'Access-Control-Allow-Headers': 'Authorization, Content-Type',
|
|
51
|
-
},
|
|
52
|
-
};
|
|
53
|
-
},
|
|
54
|
-
createContext,
|
|
55
|
-
onError: handleTrpcError,
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
export const _trpcHandler = awsLambdaRequestHandler({
|
|
59
|
-
router: appRouter,
|
|
60
|
-
createContext,
|
|
61
|
-
onError: handleTrpcError,
|
|
62
|
-
responseMeta: () => {
|
|
63
|
-
return {
|
|
64
|
-
headers: {
|
|
65
|
-
'Access-Control-Allow-Origin': '*',
|
|
66
|
-
'Access-Control-Allow-Methods': '*',
|
|
67
|
-
'Access-Control-Allow-Headers': 'authorization',
|
|
68
|
-
},
|
|
69
|
-
};
|
|
70
|
-
},
|
|
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'),
|
|
71
14
|
});
|
|
72
15
|
|
|
73
|
-
export const
|
|
74
|
-
|
|
75
|
-
await promise;
|
|
76
|
-
|
|
77
|
-
if (isWarmupEvent(event)) {
|
|
78
|
-
console.log('[Warmup] Initializing LearnCard...');
|
|
79
|
-
await getEmptyLearnCard();
|
|
80
|
-
console.log('[Warmup] Done!');
|
|
81
|
-
return 'All done! 😄';
|
|
82
|
-
}
|
|
16
|
+
export const trpcHandler = async (...args: Parameters<LambdaApp['trpcHandler']>) =>
|
|
17
|
+
(await getApplication()).trpcHandler(...args);
|
|
83
18
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
statusCode: 200,
|
|
87
|
-
headers: {
|
|
88
|
-
'Access-Control-Allow-Origin': '*',
|
|
89
|
-
'Access-Control-Allow-Headers': '*',
|
|
90
|
-
'Access-Control-Allow-Methods': '*',
|
|
91
|
-
},
|
|
92
|
-
};
|
|
93
|
-
}
|
|
19
|
+
export const openApiHandler = async (...args: Parameters<LambdaApp['openApiHandler']>) =>
|
|
20
|
+
(await getApplication()).openApiHandler(...args);
|
|
94
21
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
);
|
|
22
|
+
export const swaggerUiHandler = async (...args: Parameters<LambdaApp['swaggerUiHandler']>) =>
|
|
23
|
+
(await getApplication()).swaggerUiHandler(...args);
|
|
98
24
|
|
|
99
|
-
export const
|
|
100
|
-
|
|
101
|
-
await promise;
|
|
25
|
+
export const didWebHandler = async (...args: Parameters<LambdaApp['didWebHandler']>) =>
|
|
26
|
+
(await getApplication()).didWebHandler(...args);
|
|
102
27
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
await getEmptyLearnCard();
|
|
106
|
-
console.log('[Warmup] Done!');
|
|
107
|
-
return 'All done! 😄';
|
|
108
|
-
}
|
|
28
|
+
// Preserve the local/offline handler name without importing the full API for OIDC.
|
|
29
|
+
export { handler as oidcHandler } from './oidcLambda';
|
|
109
30
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
'Access-Control-Allow-Origin': '*',
|
|
115
|
-
'Access-Control-Allow-Headers': '*',
|
|
116
|
-
'Access-Control-Allow-Methods': '*',
|
|
117
|
-
},
|
|
118
|
-
};
|
|
119
|
-
}
|
|
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);
|
|
120
35
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
);
|
|
36
|
+
export const escrowBlobRewrapHandler = async (
|
|
37
|
+
...args: Parameters<LambdaApp['escrowBlobRewrapHandler']>
|
|
38
|
+
) => (await getApplication()).escrowBlobRewrapHandler(...args);
|
package/lambdaApp.ts
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
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';
|
|
5
|
+
|
|
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';
|
|
19
|
+
import { runEscrowHoldReminders } from './src/jobs/escrowHoldReminders';
|
|
20
|
+
import { runEscrowBlobRewrap } from './src/jobs/escrowBlobRewrap';
|
|
21
|
+
|
|
22
|
+
const startupPromise = Promise.all([
|
|
23
|
+
getEmptyLearnCard(), // Load WASM in for better cold starts
|
|
24
|
+
ensureUserKeysIndexes(),
|
|
25
|
+
createEscrowHoldsIndexes(),
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
const isWarmupEvent = (event: APIGatewayProxyEventV2): boolean =>
|
|
29
|
+
'source' in event && event.source === 'serverless-plugin-warmup';
|
|
30
|
+
|
|
31
|
+
Sentry.AWSLambda.init({
|
|
32
|
+
dsn: environment.SENTRY_DSN,
|
|
33
|
+
environment: environment.SENTRY_ENV,
|
|
34
|
+
enabled: Boolean(environment.SENTRY_DSN),
|
|
35
|
+
tracesSampleRate: getTracesSampleRate(),
|
|
36
|
+
beforeSend: sentryBeforeSend,
|
|
37
|
+
integrations: [
|
|
38
|
+
new Sentry.Integrations.Console(),
|
|
39
|
+
new Sentry.Integrations.Http({ tracing: true }),
|
|
40
|
+
new Sentry.Integrations.ContextLines(),
|
|
41
|
+
new Sentry.Integrations.Mongo(),
|
|
42
|
+
],
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export const swaggerUiHandler = serverlessHttp(toServerlessApplication(app), {
|
|
46
|
+
basePath: '/docs',
|
|
47
|
+
});
|
|
48
|
+
export const didWebHandler = serverlessHttp(toServerlessApplication(didWebApp));
|
|
49
|
+
|
|
50
|
+
export const _openApiHandler = createOpenApiAwsLambdaHandler({
|
|
51
|
+
router: appRouter,
|
|
52
|
+
responseMeta: () => {
|
|
53
|
+
return {
|
|
54
|
+
headers: {
|
|
55
|
+
'Access-Control-Allow-Origin': '*',
|
|
56
|
+
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
|
57
|
+
'Access-Control-Allow-Headers': 'Authorization, Content-Type',
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
|
+
createContext,
|
|
62
|
+
onError: handleTrpcError,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
export const _trpcHandler = awsLambdaRequestHandler({
|
|
66
|
+
router: appRouter,
|
|
67
|
+
createContext,
|
|
68
|
+
onError: handleTrpcError,
|
|
69
|
+
responseMeta: () => {
|
|
70
|
+
return {
|
|
71
|
+
headers: {
|
|
72
|
+
'Access-Control-Allow-Origin': '*',
|
|
73
|
+
'Access-Control-Allow-Methods': '*',
|
|
74
|
+
'Access-Control-Allow-Headers': 'authorization',
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
export const openApiHandler = Sentry.AWSLambda.wrapHandler(
|
|
81
|
+
async (event: APIGatewayProxyEventV2, context: Context): Promise<APIGatewayProxyResultV2> => {
|
|
82
|
+
await startupPromise;
|
|
83
|
+
|
|
84
|
+
if (isWarmupEvent(event)) {
|
|
85
|
+
console.log('[Warmup] Initializing LearnCard...');
|
|
86
|
+
await getEmptyLearnCard();
|
|
87
|
+
console.log('[Warmup] Done!');
|
|
88
|
+
return 'All done! 😄';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (event.requestContext.http.method === 'OPTIONS') {
|
|
92
|
+
return {
|
|
93
|
+
statusCode: 200,
|
|
94
|
+
headers: {
|
|
95
|
+
'Access-Control-Allow-Origin': '*',
|
|
96
|
+
'Access-Control-Allow-Headers': '*',
|
|
97
|
+
'Access-Control-Allow-Methods': '*',
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return _openApiHandler(event, context);
|
|
103
|
+
}
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
export const trpcHandler = Sentry.AWSLambda.wrapHandler(
|
|
107
|
+
async (event: APIGatewayProxyEventV2, context: Context): Promise<APIGatewayProxyResultV2> => {
|
|
108
|
+
await startupPromise;
|
|
109
|
+
|
|
110
|
+
if (isWarmupEvent(event)) {
|
|
111
|
+
console.log('[Warmup] Initializing LearnCard...');
|
|
112
|
+
await getEmptyLearnCard();
|
|
113
|
+
console.log('[Warmup] Done!');
|
|
114
|
+
return 'All done! 😄';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (event.requestContext.http.method === 'OPTIONS') {
|
|
118
|
+
return {
|
|
119
|
+
statusCode: 200,
|
|
120
|
+
headers: {
|
|
121
|
+
'Access-Control-Allow-Origin': '*',
|
|
122
|
+
'Access-Control-Allow-Headers': '*',
|
|
123
|
+
'Access-Control-Allow-Methods': '*',
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return _trpcHandler(event, context);
|
|
129
|
+
}
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
// Scheduled (EventBridge) functions, not API-Gateway-triggered: they share
|
|
133
|
+
// startupPromise with the handlers above; warmup/OPTIONS handling doesn't apply.
|
|
134
|
+
export const escrowHoldRemindersHandler = Sentry.AWSLambda.wrapHandler(async (): Promise<void> => {
|
|
135
|
+
await startupPromise;
|
|
136
|
+
const counts = await runEscrowHoldReminders();
|
|
137
|
+
console.log('[escrow-hold-reminders] completed', counts);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// P9.3: migrates escrow blobs off a retiring enclave key for accounts that
|
|
141
|
+
// never sign in again (P9.2 already covers accounts that do, on next login).
|
|
142
|
+
export const escrowBlobRewrapHandler = Sentry.AWSLambda.wrapHandler(async (): Promise<void> => {
|
|
143
|
+
await startupPromise;
|
|
144
|
+
const counts = await runEscrowBlobRewrap();
|
|
145
|
+
console.log('[escrow-blob-rewrap] completed', counts);
|
|
146
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { APIGatewayProxyEventV2, Context } from 'aws-lambda';
|
|
2
|
+
import { afterEach, beforeEach, expect, it, vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
const mocks = vi.hoisted(() => ({ applyStage: vi.fn(), handler: vi.fn(), importApp: vi.fn() }));
|
|
5
|
+
vi.mock('./src/config/stageConfig', () => ({ applyLcaApiStageConfig: mocks.applyStage }));
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
vi.resetModules();
|
|
9
|
+
vi.clearAllMocks();
|
|
10
|
+
mocks.handler.mockResolvedValue({ statusCode: 200 });
|
|
11
|
+
vi.doMock('./oidcLambdaApp', () => {
|
|
12
|
+
mocks.importApp();
|
|
13
|
+
expect(mocks.applyStage).toHaveBeenCalledTimes(1);
|
|
14
|
+
return { oidcHandler: mocks.handler };
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
afterEach(() => vi.unstubAllEnvs());
|
|
18
|
+
|
|
19
|
+
it.each([
|
|
20
|
+
['lca-api-dev-oidc', 'dev'],
|
|
21
|
+
['', 'local'],
|
|
22
|
+
])(
|
|
23
|
+
'applies the appropriate stage before the isolated OIDC import (%s)',
|
|
24
|
+
async (functionName, expectedStage) => {
|
|
25
|
+
vi.stubEnv('AWS_LAMBDA_FUNCTION_NAME', functionName);
|
|
26
|
+
vi.stubEnv('LAMBDA_STAGE', 'dev');
|
|
27
|
+
vi.stubEnv('CONFIG_STAGE', 'local');
|
|
28
|
+
const { handler } = await import('./oidcLambda');
|
|
29
|
+
const event = {} as APIGatewayProxyEventV2;
|
|
30
|
+
const context = {} as Context;
|
|
31
|
+
const callback = vi.fn();
|
|
32
|
+
await Promise.all([handler(event, context, callback), handler(event, context, callback)]);
|
|
33
|
+
expect(mocks.applyStage).toHaveBeenCalledExactlyOnceWith(expectedStage);
|
|
34
|
+
expect(mocks.importApp).toHaveBeenCalledTimes(1);
|
|
35
|
+
expect(mocks.handler).toHaveBeenCalledTimes(2);
|
|
36
|
+
expect(mocks.handler).toHaveBeenLastCalledWith(event, context, callback);
|
|
37
|
+
}
|
|
38
|
+
);
|
package/oidcLambda.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { applyLcaApiStageConfig } from './src/config/stageConfig';
|
|
2
|
+
|
|
3
|
+
type OidcApp = typeof import('./oidcLambdaApp');
|
|
4
|
+
|
|
5
|
+
let application: Promise<OidcApp> | undefined;
|
|
6
|
+
|
|
7
|
+
export const handler = async (...args: Parameters<OidcApp['oidcHandler']>) => {
|
|
8
|
+
if (!application) {
|
|
9
|
+
applyLcaApiStageConfig(
|
|
10
|
+
process.env.AWS_LAMBDA_FUNCTION_NAME
|
|
11
|
+
? process.env.LAMBDA_STAGE
|
|
12
|
+
: process.env.CONFIG_STAGE
|
|
13
|
+
);
|
|
14
|
+
// This role cannot read the runtime bundle. Cache imports, including failures.
|
|
15
|
+
application = import('./oidcLambdaApp');
|
|
16
|
+
}
|
|
17
|
+
return (await application).oidcHandler(...args);
|
|
18
|
+
};
|
package/oidcLambdaApp.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import serverlessHttp from 'serverless-http';
|
|
2
|
+
|
|
3
|
+
import { app as oidcApp } from './src/oidc';
|
|
4
|
+
import { toServerlessApplication } from './src/helpers/serverlessApplication';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Dedicated OIDC Lambda entrypoint. Unlike `lambdaApp.ts`, this imports only the
|
|
8
|
+
* Fastify OIDC surface and its focused environment, so the OIDC function boots
|
|
9
|
+
* without SEED/MONGO/Postmark/Firebase or a runtime-secrets bundle.
|
|
10
|
+
*/
|
|
11
|
+
export const oidcHandler = serverlessHttp(toServerlessApplication(oidcApp));
|
package/package.json
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@learncard/lca-api-service",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=24.12 <25"
|
|
7
7
|
},
|
|
8
|
-
"packageManager": "bun@1.
|
|
8
|
+
"packageManager": "bun@1.4.2",
|
|
9
9
|
"main": "dist/index.js",
|
|
10
10
|
"types": "dist/index.d.ts",
|
|
11
11
|
"scripts": {
|
|
12
|
+
"migrate:sa-seeds:local": "bash scripts/migrate-signing-authority-seeds-local.sh",
|
|
13
|
+
"migrate:sa-seeds": "node scripts/migrate-signing-authority-seeds.mjs",
|
|
14
|
+
"check:sa-infra": "node scripts/check-seed-encryption-infrastructure.mjs",
|
|
15
|
+
"sa-seed:flags": "bun --conditions=development scripts/print-seed-rollout-flags.ts",
|
|
16
|
+
"check:sa-redis": "node scripts/check-signing-authority-redis.mjs",
|
|
17
|
+
"provision:keycloak": "bun --conditions=development scripts/provision-keycloak-users.ts",
|
|
12
18
|
"test": "vitest run --config vitest.config.ts",
|
|
13
19
|
"test:watch": "vitest --config vitest.config.ts",
|
|
14
20
|
"test:coverage": "vitest run --coverage --config vitest.config.ts",
|
|
@@ -22,8 +28,10 @@
|
|
|
22
28
|
"start-p-5000": "PORT=5000 serverless offline --httpPort 5000 --websocketPort 5001 --lambdaPort 5002 --albPort 5003 --config serverless-local.yml",
|
|
23
29
|
"dev": "bun --conditions=development --watch src/docker-entry.ts",
|
|
24
30
|
"start:docker": "bun --conditions=development src/docker-entry.ts",
|
|
25
|
-
"serverless-deploy": "node ../../../scripts/prepare-didkit-native-layer.cjs && serverless deploy",
|
|
26
|
-
"serverless-deploy-scouts": "serverless deploy --config serverless-scouts.yml"
|
|
31
|
+
"serverless-deploy": "node scripts/check-seed-encryption-infrastructure.mjs && node ../../../scripts/prepare-didkit-native-layer.cjs && serverless deploy",
|
|
32
|
+
"serverless-deploy-scouts": "serverless deploy --config serverless-scouts.yml",
|
|
33
|
+
"test:oidc:e2e": "node test/oidc-e2e/run.mjs",
|
|
34
|
+
"typecheck:oidc:e2e": "tsc --noEmit -p test/oidc-e2e/tsconfig.json"
|
|
27
35
|
},
|
|
28
36
|
"keywords": [],
|
|
29
37
|
"author": "WeLibrary, LLC (www.welibrary.io)",
|
|
@@ -45,34 +53,42 @@
|
|
|
45
53
|
"serverless-http": "3.2.0",
|
|
46
54
|
"serverless-offline": "^14.4.0",
|
|
47
55
|
"serverless-prune-plugin": "^2.1.0",
|
|
48
|
-
"shx": "^0.3.4"
|
|
56
|
+
"shx": "^0.3.4",
|
|
57
|
+
"@playwright/test": "^1.42.0"
|
|
49
58
|
},
|
|
50
59
|
"dependencies": {
|
|
60
|
+
"@aws-sdk/client-kms": "^3.1139.0",
|
|
61
|
+
"@aws-sdk/client-secrets-manager": "^3.1139.0",
|
|
51
62
|
"@fastify/cors": "^11.2.0",
|
|
63
|
+
"@fastify/formbody": "^8.0.2",
|
|
64
|
+
"@fastify/rate-limit": "^10.2.0",
|
|
52
65
|
"@fastify/static": "^10.1.1",
|
|
53
|
-
"@learncard/core": "9.4.
|
|
54
|
-
"@learncard/
|
|
55
|
-
"@learncard/
|
|
56
|
-
"@learncard/didkit-plugin": "
|
|
57
|
-
"@learncard/
|
|
58
|
-
"@learncard/
|
|
59
|
-
"@learncard/
|
|
60
|
-
"@learncard/
|
|
66
|
+
"@learncard/core": "9.4.38",
|
|
67
|
+
"@learncard/did-web-plugin": "1.1.38",
|
|
68
|
+
"@learncard/didkit-plugin": "1.10.3",
|
|
69
|
+
"@learncard/didkit-plugin-node": "0.3.3",
|
|
70
|
+
"@learncard/email-templates": "1.2.1",
|
|
71
|
+
"@learncard/helpers": "1.6.2",
|
|
72
|
+
"@learncard/init": "2.5.2",
|
|
73
|
+
"@learncard/service-config": "0.1.1",
|
|
74
|
+
"@learncard/types": "5.23.0",
|
|
75
|
+
"@learncard/sss-key-manager": "0.2.1",
|
|
61
76
|
"@sentry/serverless": "^7.61.0",
|
|
62
77
|
"@trpc/server": "11.8.0",
|
|
63
78
|
"@types/jsonwebtoken": "^9.0.8",
|
|
64
79
|
"@types/lodash": "^4.14.191",
|
|
65
80
|
"@types/node-forge": "^1.3.11",
|
|
66
81
|
"axios": "^0.33.0",
|
|
67
|
-
"dotenv": "^16.0.3",
|
|
68
82
|
"dag-jose-utils": "2.0.0",
|
|
69
83
|
"did-jwt": "5.9.0",
|
|
84
|
+
"dotenv": "^16.0.3",
|
|
70
85
|
"express": "^4.18.2",
|
|
71
86
|
"fastify": "^5.8.5",
|
|
72
87
|
"filestack-js": "^3.24.0",
|
|
73
88
|
"firebase-admin": "13.4.0",
|
|
74
89
|
"ioredis": "^5.2.2",
|
|
75
90
|
"ioredis-mock": "^8.2.2",
|
|
91
|
+
"jose": "^6.2.12",
|
|
76
92
|
"jsonwebtoken": "^9.0.2",
|
|
77
93
|
"jwt-decode": "^3.1.2",
|
|
78
94
|
"libsodium-wrappers": "^0.7.11",
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# lca-api scripts
|
|
2
|
+
|
|
3
|
+
Operational scripts run directly with `bun scripts/<name>.ts` from
|
|
4
|
+
`services/learn-card-network/lca-api/`. These are not part of the deployed
|
|
5
|
+
service and are not built by `bun run build`; they import `src/` files by
|
|
6
|
+
relative path (not the `@alias` path mappings used inside `src/`, since
|
|
7
|
+
`scripts/` is intentionally outside this package's `tsconfig.json` `include`
|
|
8
|
+
and those aliases don't resolve from here — see `escrow-blob-mode-report.ts`
|
|
9
|
+
if you need the full explanation).
|
|
10
|
+
|
|
11
|
+
## `strip-email.ts`
|
|
12
|
+
|
|
13
|
+
Removes the email attribute from a Firebase user by UID, via the Identity
|
|
14
|
+
Toolkit REST API (the Admin SDK can't clear an email, only replace it).
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
bun scripts/strip-email.ts <uid>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Requires `GOOGLE_APPLICATION_CREDENTIAL`.
|
|
21
|
+
|
|
22
|
+
## `escrow-blob-mode-report.ts` (P6.2)
|
|
23
|
+
|
|
24
|
+
Read-only counts report over the `userkeys` and `escrowholds` collections:
|
|
25
|
+
escrow blob counts by `enclaveMode` / `enclaveKeyId`, enabled-PIN count,
|
|
26
|
+
pending-hold counts by `releasePolicy`, opted-out count, and a software→nitro
|
|
27
|
+
"re-seal" breakdown. Never writes to the database and never prints PII (counts
|
|
28
|
+
only — no DIDs, emails, or Mongo `_id`s).
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# Human-readable table
|
|
32
|
+
bun scripts/escrow-blob-mode-report.ts
|
|
33
|
+
|
|
34
|
+
# Machine-readable JSON
|
|
35
|
+
bun scripts/escrow-blob-mode-report.ts --json
|
|
36
|
+
|
|
37
|
+
# Also print the CloudWatch PutMetricData payload for the counts
|
|
38
|
+
bun scripts/escrow-blob-mode-report.ts --json --publish-metrics
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Requires the same env as the lca-api server: `SEED`, `MONGO_URI`,
|
|
42
|
+
`MONGO_DB_NAME` (point `MONGO_URI` at whichever environment you want to report
|
|
43
|
+
on). Full flag/limitation reference lives in the script's own header comment.
|
|
44
|
+
|
|
45
|
+
**CloudWatch:** `--publish-metrics` does not call AWS itself — lca-api has no
|
|
46
|
+
`@aws-sdk/client-cloudwatch` dependency (only
|
|
47
|
+
`services/learn-card-network/ai-agent` does elsewhere in this monorepo, and it
|
|
48
|
+
isn't shared with lca-api). The flag prints the exact `MetricData` payload
|
|
49
|
+
(namespace `LearnCard/Escrow`, metrics `EscrowBlobs` per `EnclaveMode`
|
|
50
|
+
dimension and `EscrowBlobsStale`) for the P7.1 monitor Lambda or an ops job
|
|
51
|
+
that already has AWS SDK access to forward.
|
|
52
|
+
|
|
53
|
+
### Running it locally against a throwaway Mongo
|
|
54
|
+
|
|
55
|
+
There's no persistent local Mongo fixture for scripts (unlike `test/*.spec.ts`,
|
|
56
|
+
which get one via `vitest-setup.ts`'s `mongodb-memory-server` global setup).
|
|
57
|
+
To try the report against real data without touching a real environment, spin
|
|
58
|
+
up your own `mongodb-memory-server` instance and point the script at it:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
// tmp-run-report.mjs (delete after use — do not commit)
|
|
62
|
+
import { MongoMemoryServer } from 'mongodb-memory-server';
|
|
63
|
+
import { spawnSync } from 'node:child_process';
|
|
64
|
+
|
|
65
|
+
const mongod = await MongoMemoryServer.create();
|
|
66
|
+
// ...seed `userkeys` / `escrowholds` via a MongoClient here...
|
|
67
|
+
|
|
68
|
+
spawnSync('bun', ['scripts/escrow-blob-mode-report.ts', '--json'], {
|
|
69
|
+
env: {
|
|
70
|
+
...process.env,
|
|
71
|
+
NODE_ENV: 'development',
|
|
72
|
+
SEED: 'a'.repeat(64),
|
|
73
|
+
MONGO_URI: mongod.getUri(),
|
|
74
|
+
MONGO_DB_NAME: 'report-harness',
|
|
75
|
+
},
|
|
76
|
+
stdio: 'inherit',
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
await mongod.stop();
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Testing
|
|
83
|
+
|
|
84
|
+
`escrow-blob-mode-report.test.ts` covers the pure aggregation → report
|
|
85
|
+
formatting logic (zero-count modes, an unknown `enclaveMode`/`releasePolicy`
|
|
86
|
+
value, the CloudWatch payload shape) without touching Mongo. It's picked up by
|
|
87
|
+
the same `bun run test` as `src/**/*.test.ts` — `vitest.config.ts`'s `include`
|
|
88
|
+
was extended to also match `scripts/**/*.test.ts`.
|