@de-otio/trellis 0.4.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/dist/db.d.ts +36 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +39 -0
- package/dist/db.js.map +1 -0
- package/dist/env.d.ts +107 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +268 -0
- package/dist/env.js.map +1 -0
- package/dist/extensions.d.ts +15 -0
- package/dist/extensions.d.ts.map +1 -0
- package/dist/extensions.js +35 -0
- package/dist/extensions.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/lambda/cleanup-cron.d.ts +2 -0
- package/dist/lambda/cleanup-cron.d.ts.map +1 -0
- package/dist/lambda/cleanup-cron.js +38 -0
- package/dist/lambda/cleanup-cron.js.map +1 -0
- package/dist/lambda/create-auth-challenge.d.ts +2 -0
- package/dist/lambda/create-auth-challenge.d.ts.map +1 -0
- package/dist/lambda/create-auth-challenge.js +108 -0
- package/dist/lambda/create-auth-challenge.js.map +1 -0
- package/dist/lambda/custom-message.d.ts +3 -0
- package/dist/lambda/custom-message.d.ts.map +1 -0
- package/dist/lambda/custom-message.js +29 -0
- package/dist/lambda/custom-message.js.map +1 -0
- package/dist/lambda/define-auth-challenge.d.ts +2 -0
- package/dist/lambda/define-auth-challenge.d.ts.map +1 -0
- package/dist/lambda/define-auth-challenge.js +27 -0
- package/dist/lambda/define-auth-challenge.js.map +1 -0
- package/dist/lambda/delete-account-worker.d.ts +3 -0
- package/dist/lambda/delete-account-worker.d.ts.map +1 -0
- package/dist/lambda/delete-account-worker.js +122 -0
- package/dist/lambda/delete-account-worker.js.map +1 -0
- package/dist/lambda/diagnostics-proxy.d.ts +6 -0
- package/dist/lambda/diagnostics-proxy.d.ts.map +1 -0
- package/dist/lambda/diagnostics-proxy.js +154 -0
- package/dist/lambda/diagnostics-proxy.js.map +1 -0
- package/dist/lambda/e2e-sweeper.d.ts +18 -0
- package/dist/lambda/e2e-sweeper.d.ts.map +1 -0
- package/dist/lambda/e2e-sweeper.js +127 -0
- package/dist/lambda/e2e-sweeper.js.map +1 -0
- package/dist/lambda/federation-outbox-worker.d.ts +3 -0
- package/dist/lambda/federation-outbox-worker.d.ts.map +1 -0
- package/dist/lambda/federation-outbox-worker.js +11 -0
- package/dist/lambda/federation-outbox-worker.js.map +1 -0
- package/dist/lambda/followers-events-worker.d.ts +3 -0
- package/dist/lambda/followers-events-worker.d.ts.map +1 -0
- package/dist/lambda/followers-events-worker.js +11 -0
- package/dist/lambda/followers-events-worker.js.map +1 -0
- package/dist/lambda/hourly-cron.d.ts +2 -0
- package/dist/lambda/hourly-cron.d.ts.map +1 -0
- package/dist/lambda/hourly-cron.js +101 -0
- package/dist/lambda/hourly-cron.js.map +1 -0
- package/dist/lambda/link-check-worker.d.ts +3 -0
- package/dist/lambda/link-check-worker.d.ts.map +1 -0
- package/dist/lambda/link-check-worker.js +11 -0
- package/dist/lambda/link-check-worker.js.map +1 -0
- package/dist/lambda/maintenance-cron.d.ts +2 -0
- package/dist/lambda/maintenance-cron.d.ts.map +1 -0
- package/dist/lambda/maintenance-cron.js +127 -0
- package/dist/lambda/maintenance-cron.js.map +1 -0
- package/dist/lambda/media-processing-worker.d.ts +3 -0
- package/dist/lambda/media-processing-worker.d.ts.map +1 -0
- package/dist/lambda/media-processing-worker.js +95 -0
- package/dist/lambda/media-processing-worker.js.map +1 -0
- package/dist/lambda/media-reconciliation-worker.d.ts +3 -0
- package/dist/lambda/media-reconciliation-worker.d.ts.map +1 -0
- package/dist/lambda/media-reconciliation-worker.js +11 -0
- package/dist/lambda/media-reconciliation-worker.js.map +1 -0
- package/dist/lambda/nightly-cron.d.ts +2 -0
- package/dist/lambda/nightly-cron.d.ts.map +1 -0
- package/dist/lambda/nightly-cron.js +348 -0
- package/dist/lambda/nightly-cron.js.map +1 -0
- package/dist/lambda/post-confirmation.d.ts +3 -0
- package/dist/lambda/post-confirmation.d.ts.map +1 -0
- package/dist/lambda/post-confirmation.js +79 -0
- package/dist/lambda/post-confirmation.js.map +1 -0
- package/dist/lambda/pre-signup.d.ts +3 -0
- package/dist/lambda/pre-signup.d.ts.map +1 -0
- package/dist/lambda/pre-signup.js +35 -0
- package/dist/lambda/pre-signup.js.map +1 -0
- package/dist/lambda/pre-token-generation.d.ts +3 -0
- package/dist/lambda/pre-token-generation.d.ts.map +1 -0
- package/dist/lambda/pre-token-generation.js +79 -0
- package/dist/lambda/pre-token-generation.js.map +1 -0
- package/dist/lambda/tools/check-health.d.ts +6 -0
- package/dist/lambda/tools/check-health.d.ts.map +1 -0
- package/dist/lambda/tools/check-health.js +24 -0
- package/dist/lambda/tools/check-health.js.map +1 -0
- package/dist/lambda/tools/describe-services.d.ts +20 -0
- package/dist/lambda/tools/describe-services.d.ts.map +1 -0
- package/dist/lambda/tools/describe-services.js +41 -0
- package/dist/lambda/tools/describe-services.js.map +1 -0
- package/dist/lambda/tools/get-cost-report.d.ts +16 -0
- package/dist/lambda/tools/get-cost-report.d.ts.map +1 -0
- package/dist/lambda/tools/get-cost-report.js +54 -0
- package/dist/lambda/tools/get-cost-report.js.map +1 -0
- package/dist/lambda/tools/get-errors.d.ts +11 -0
- package/dist/lambda/tools/get-errors.d.ts.map +1 -0
- package/dist/lambda/tools/get-errors.js +62 -0
- package/dist/lambda/tools/get-errors.js.map +1 -0
- package/dist/lambda/tools/get-feature-flags.d.ts +8 -0
- package/dist/lambda/tools/get-feature-flags.d.ts.map +1 -0
- package/dist/lambda/tools/get-feature-flags.js +23 -0
- package/dist/lambda/tools/get-feature-flags.js.map +1 -0
- package/dist/lambda/tools/get-queue-status.d.ts +9 -0
- package/dist/lambda/tools/get-queue-status.d.ts.map +1 -0
- package/dist/lambda/tools/get-queue-status.js +46 -0
- package/dist/lambda/tools/get-queue-status.js.map +1 -0
- package/dist/lambda/tools/search-logs.d.ts +19 -0
- package/dist/lambda/tools/search-logs.d.ts.map +1 -0
- package/dist/lambda/tools/search-logs.js +55 -0
- package/dist/lambda/tools/search-logs.js.map +1 -0
- package/dist/lambda/tools/send-alert.d.ts +8 -0
- package/dist/lambda/tools/send-alert.d.ts.map +1 -0
- package/dist/lambda/tools/send-alert.js +29 -0
- package/dist/lambda/tools/send-alert.js.map +1 -0
- package/dist/lambda/verify-auth-challenge.d.ts +2 -0
- package/dist/lambda/verify-auth-challenge.d.ts.map +1 -0
- package/dist/lambda/verify-auth-challenge.js +35 -0
- package/dist/lambda/verify-auth-challenge.js.map +1 -0
- package/dist/lib/abuse-metrics.d.ts +44 -0
- package/dist/lib/abuse-metrics.d.ts.map +1 -0
- package/dist/lib/abuse-metrics.js +322 -0
- package/dist/lib/abuse-metrics.js.map +1 -0
- package/dist/lib/activitypub/activity-processor.d.ts +89 -0
- package/dist/lib/activitypub/activity-processor.d.ts.map +1 -0
- package/dist/lib/activitypub/activity-processor.js +709 -0
- package/dist/lib/activitypub/activity-processor.js.map +1 -0
- package/dist/lib/activitypub/activity-service.d.ts +47 -0
- package/dist/lib/activitypub/activity-service.d.ts.map +1 -0
- package/dist/lib/activitypub/activity-service.js +165 -0
- package/dist/lib/activitypub/activity-service.js.map +1 -0
- package/dist/lib/activitypub/actor.d.ts +44 -0
- package/dist/lib/activitypub/actor.d.ts.map +1 -0
- package/dist/lib/activitypub/actor.js +116 -0
- package/dist/lib/activitypub/actor.js.map +1 -0
- package/dist/lib/activitypub/audience-service.d.ts +52 -0
- package/dist/lib/activitypub/audience-service.d.ts.map +1 -0
- package/dist/lib/activitypub/audience-service.js +245 -0
- package/dist/lib/activitypub/audience-service.js.map +1 -0
- package/dist/lib/activitypub/crypto.d.ts +42 -0
- package/dist/lib/activitypub/crypto.d.ts.map +1 -0
- package/dist/lib/activitypub/crypto.js +129 -0
- package/dist/lib/activitypub/crypto.js.map +1 -0
- package/dist/lib/activitypub/delivery-service.d.ts +44 -0
- package/dist/lib/activitypub/delivery-service.d.ts.map +1 -0
- package/dist/lib/activitypub/delivery-service.js +259 -0
- package/dist/lib/activitypub/delivery-service.js.map +1 -0
- package/dist/lib/activitypub/dispatchers/entity-actor.d.ts +50 -0
- package/dist/lib/activitypub/dispatchers/entity-actor.d.ts.map +1 -0
- package/dist/lib/activitypub/dispatchers/entity-actor.js +247 -0
- package/dist/lib/activitypub/dispatchers/entity-actor.js.map +1 -0
- package/dist/lib/activitypub/dispatchers/group-actor.d.ts +47 -0
- package/dist/lib/activitypub/dispatchers/group-actor.d.ts.map +1 -0
- package/dist/lib/activitypub/dispatchers/group-actor.js +202 -0
- package/dist/lib/activitypub/dispatchers/group-actor.js.map +1 -0
- package/dist/lib/activitypub/dispatchers/user-actor.d.ts +47 -0
- package/dist/lib/activitypub/dispatchers/user-actor.d.ts.map +1 -0
- package/dist/lib/activitypub/dispatchers/user-actor.js +207 -0
- package/dist/lib/activitypub/dispatchers/user-actor.js.map +1 -0
- package/dist/lib/activitypub/dm-service.d.ts +35 -0
- package/dist/lib/activitypub/dm-service.d.ts.map +1 -0
- package/dist/lib/activitypub/dm-service.js +73 -0
- package/dist/lib/activitypub/dm-service.js.map +1 -0
- package/dist/lib/activitypub/entity-profile-service.d.ts +43 -0
- package/dist/lib/activitypub/entity-profile-service.d.ts.map +1 -0
- package/dist/lib/activitypub/entity-profile-service.js +80 -0
- package/dist/lib/activitypub/entity-profile-service.js.map +1 -0
- package/dist/lib/activitypub/fedify/config.d.ts +15 -0
- package/dist/lib/activitypub/fedify/config.d.ts.map +1 -0
- package/dist/lib/activitypub/fedify/config.js +38 -0
- package/dist/lib/activitypub/fedify/config.js.map +1 -0
- package/dist/lib/activitypub/fedify/context.d.ts +21 -0
- package/dist/lib/activitypub/fedify/context.d.ts.map +1 -0
- package/dist/lib/activitypub/fedify/context.js +67 -0
- package/dist/lib/activitypub/fedify/context.js.map +1 -0
- package/dist/lib/activitypub/fedify/runtime.d.ts +18 -0
- package/dist/lib/activitypub/fedify/runtime.d.ts.map +1 -0
- package/dist/lib/activitypub/fedify/runtime.js +26 -0
- package/dist/lib/activitypub/fedify/runtime.js.map +1 -0
- package/dist/lib/activitypub/friendship-service.d.ts +15 -0
- package/dist/lib/activitypub/friendship-service.d.ts.map +1 -0
- package/dist/lib/activitypub/friendship-service.js +26 -0
- package/dist/lib/activitypub/friendship-service.js.map +1 -0
- package/dist/lib/activitypub/group-service.d.ts +83 -0
- package/dist/lib/activitypub/group-service.d.ts.map +1 -0
- package/dist/lib/activitypub/group-service.js +301 -0
- package/dist/lib/activitypub/group-service.js.map +1 -0
- package/dist/lib/activitypub/http-signatures.d.ts +41 -0
- package/dist/lib/activitypub/http-signatures.d.ts.map +1 -0
- package/dist/lib/activitypub/http-signatures.js +284 -0
- package/dist/lib/activitypub/http-signatures.js.map +1 -0
- package/dist/lib/activitypub/jsonld.d.ts +39 -0
- package/dist/lib/activitypub/jsonld.d.ts.map +1 -0
- package/dist/lib/activitypub/jsonld.js +97 -0
- package/dist/lib/activitypub/jsonld.js.map +1 -0
- package/dist/lib/activitypub/listeners/friends-collection.d.ts +20 -0
- package/dist/lib/activitypub/listeners/friends-collection.d.ts.map +1 -0
- package/dist/lib/activitypub/listeners/friends-collection.js +105 -0
- package/dist/lib/activitypub/listeners/friends-collection.js.map +1 -0
- package/dist/lib/activitypub/listeners/http-signatures.d.ts +29 -0
- package/dist/lib/activitypub/listeners/http-signatures.d.ts.map +1 -0
- package/dist/lib/activitypub/listeners/http-signatures.js +208 -0
- package/dist/lib/activitypub/listeners/http-signatures.js.map +1 -0
- package/dist/lib/activitypub/listeners/inbox.d.ts +34 -0
- package/dist/lib/activitypub/listeners/inbox.d.ts.map +1 -0
- package/dist/lib/activitypub/listeners/inbox.js +226 -0
- package/dist/lib/activitypub/listeners/inbox.js.map +1 -0
- package/dist/lib/activitypub/listeners/outbox.d.ts +20 -0
- package/dist/lib/activitypub/listeners/outbox.d.ts.map +1 -0
- package/dist/lib/activitypub/listeners/outbox.js +117 -0
- package/dist/lib/activitypub/listeners/outbox.js.map +1 -0
- package/dist/lib/activitypub/remote-fetch-service.d.ts +108 -0
- package/dist/lib/activitypub/remote-fetch-service.d.ts.map +1 -0
- package/dist/lib/activitypub/remote-fetch-service.js +364 -0
- package/dist/lib/activitypub/remote-fetch-service.js.map +1 -0
- package/dist/lib/activitypub/services/abuse-prevention.d.ts +52 -0
- package/dist/lib/activitypub/services/abuse-prevention.d.ts.map +1 -0
- package/dist/lib/activitypub/services/abuse-prevention.js +118 -0
- package/dist/lib/activitypub/services/abuse-prevention.js.map +1 -0
- package/dist/lib/activitypub/services/dm-service-fedify.d.ts +46 -0
- package/dist/lib/activitypub/services/dm-service-fedify.d.ts.map +1 -0
- package/dist/lib/activitypub/services/dm-service-fedify.js +168 -0
- package/dist/lib/activitypub/services/dm-service-fedify.js.map +1 -0
- package/dist/lib/activitypub/services/fedify-converters.d.ts +51 -0
- package/dist/lib/activitypub/services/fedify-converters.d.ts.map +1 -0
- package/dist/lib/activitypub/services/fedify-converters.js +109 -0
- package/dist/lib/activitypub/services/fedify-converters.js.map +1 -0
- package/dist/lib/activitypub/services/fedify-delivery.d.ts +41 -0
- package/dist/lib/activitypub/services/fedify-delivery.d.ts.map +1 -0
- package/dist/lib/activitypub/services/fedify-delivery.js +186 -0
- package/dist/lib/activitypub/services/fedify-delivery.js.map +1 -0
- package/dist/lib/activitypub/services/follow-activity-service.d.ts +34 -0
- package/dist/lib/activitypub/services/follow-activity-service.d.ts.map +1 -0
- package/dist/lib/activitypub/services/follow-activity-service.js +64 -0
- package/dist/lib/activitypub/services/follow-activity-service.js.map +1 -0
- package/dist/lib/activitypub/services/post-service-fedify.d.ts +49 -0
- package/dist/lib/activitypub/services/post-service-fedify.d.ts.map +1 -0
- package/dist/lib/activitypub/services/post-service-fedify.js +281 -0
- package/dist/lib/activitypub/services/post-service-fedify.js.map +1 -0
- package/dist/lib/activitypub/services/remote-activity-handler.d.ts +22 -0
- package/dist/lib/activitypub/services/remote-activity-handler.d.ts.map +1 -0
- package/dist/lib/activitypub/services/remote-activity-handler.js +136 -0
- package/dist/lib/activitypub/services/remote-activity-handler.js.map +1 -0
- package/dist/lib/activitypub/standalone-mode.d.ts +34 -0
- package/dist/lib/activitypub/standalone-mode.d.ts.map +1 -0
- package/dist/lib/activitypub/standalone-mode.js +127 -0
- package/dist/lib/activitypub/standalone-mode.js.map +1 -0
- package/dist/lib/activitypub/webfinger/server.d.ts +16 -0
- package/dist/lib/activitypub/webfinger/server.d.ts.map +1 -0
- package/dist/lib/activitypub/webfinger/server.js +221 -0
- package/dist/lib/activitypub/webfinger/server.js.map +1 -0
- package/dist/lib/age-gate-middleware.d.ts +19 -0
- package/dist/lib/age-gate-middleware.d.ts.map +1 -0
- package/dist/lib/age-gate-middleware.js +26 -0
- package/dist/lib/age-gate-middleware.js.map +1 -0
- package/dist/lib/age-gate.d.ts +37 -0
- package/dist/lib/age-gate.d.ts.map +1 -0
- package/dist/lib/age-gate.js +96 -0
- package/dist/lib/age-gate.js.map +1 -0
- package/dist/lib/age-tier-transition.d.ts +21 -0
- package/dist/lib/age-tier-transition.d.ts.map +1 -0
- package/dist/lib/age-tier-transition.js +190 -0
- package/dist/lib/age-tier-transition.js.map +1 -0
- package/dist/lib/audit-logger.d.ts +142 -0
- package/dist/lib/audit-logger.d.ts.map +1 -0
- package/dist/lib/audit-logger.js +326 -0
- package/dist/lib/audit-logger.js.map +1 -0
- package/dist/lib/auth/cognito-jwt.d.ts +20 -0
- package/dist/lib/auth/cognito-jwt.d.ts.map +1 -0
- package/dist/lib/auth/cognito-jwt.js +56 -0
- package/dist/lib/auth/cognito-jwt.js.map +1 -0
- package/dist/lib/auth-context-manager.d.ts +116 -0
- package/dist/lib/auth-context-manager.d.ts.map +1 -0
- package/dist/lib/auth-context-manager.js +130 -0
- package/dist/lib/auth-context-manager.js.map +1 -0
- package/dist/lib/auth-handler.d.ts +19 -0
- package/dist/lib/auth-handler.d.ts.map +1 -0
- package/dist/lib/auth-handler.js +76 -0
- package/dist/lib/auth-handler.js.map +1 -0
- package/dist/lib/badge-handler.d.ts +20 -0
- package/dist/lib/badge-handler.d.ts.map +1 -0
- package/dist/lib/badge-handler.js +142 -0
- package/dist/lib/badge-handler.js.map +1 -0
- package/dist/lib/circle-handler.d.ts +22 -0
- package/dist/lib/circle-handler.d.ts.map +1 -0
- package/dist/lib/circle-handler.js +224 -0
- package/dist/lib/circle-handler.js.map +1 -0
- package/dist/lib/circuit-breaker.d.ts +27 -0
- package/dist/lib/circuit-breaker.d.ts.map +1 -0
- package/dist/lib/circuit-breaker.js +63 -0
- package/dist/lib/circuit-breaker.js.map +1 -0
- package/dist/lib/comment-handler.d.ts +77 -0
- package/dist/lib/comment-handler.d.ts.map +1 -0
- package/dist/lib/comment-handler.js +953 -0
- package/dist/lib/comment-handler.js.map +1 -0
- package/dist/lib/connection-code-handler.d.ts +17 -0
- package/dist/lib/connection-code-handler.d.ts.map +1 -0
- package/dist/lib/connection-code-handler.js +293 -0
- package/dist/lib/connection-code-handler.js.map +1 -0
- package/dist/lib/content-discovery.d.ts +113 -0
- package/dist/lib/content-discovery.d.ts.map +1 -0
- package/dist/lib/content-discovery.js +519 -0
- package/dist/lib/content-discovery.js.map +1 -0
- package/dist/lib/context-aware-data-access.d.ts +89 -0
- package/dist/lib/context-aware-data-access.d.ts.map +1 -0
- package/dist/lib/context-aware-data-access.js +97 -0
- package/dist/lib/context-aware-data-access.js.map +1 -0
- package/dist/lib/cors-handler.d.ts +29 -0
- package/dist/lib/cors-handler.d.ts.map +1 -0
- package/dist/lib/cors-handler.js +225 -0
- package/dist/lib/cors-handler.js.map +1 -0
- package/dist/lib/cost-accumulator.d.ts +58 -0
- package/dist/lib/cost-accumulator.d.ts.map +1 -0
- package/dist/lib/cost-accumulator.js +173 -0
- package/dist/lib/cost-accumulator.js.map +1 -0
- package/dist/lib/crypto/encryption-service.d.ts +100 -0
- package/dist/lib/crypto/encryption-service.d.ts.map +1 -0
- package/dist/lib/crypto/encryption-service.js +293 -0
- package/dist/lib/crypto/encryption-service.js.map +1 -0
- package/dist/lib/crypto/index.d.ts +22 -0
- package/dist/lib/crypto/index.d.ts.map +1 -0
- package/dist/lib/crypto/index.js +28 -0
- package/dist/lib/crypto/index.js.map +1 -0
- package/dist/lib/crypto/types.d.ts +71 -0
- package/dist/lib/crypto/types.d.ts.map +1 -0
- package/dist/lib/crypto/types.js +3 -0
- package/dist/lib/crypto/types.js.map +1 -0
- package/dist/lib/crypto/versioning.d.ts +112 -0
- package/dist/lib/crypto/versioning.d.ts.map +1 -0
- package/dist/lib/crypto/versioning.js +148 -0
- package/dist/lib/crypto/versioning.js.map +1 -0
- package/dist/lib/crypto/voting/elgamal-encryption.d.ts +156 -0
- package/dist/lib/crypto/voting/elgamal-encryption.d.ts.map +1 -0
- package/dist/lib/crypto/voting/elgamal-encryption.js +172 -0
- package/dist/lib/crypto/voting/elgamal-encryption.js.map +1 -0
- package/dist/lib/crypto/voting/encryption-scheme.d.ts +138 -0
- package/dist/lib/crypto/voting/encryption-scheme.d.ts.map +1 -0
- package/dist/lib/crypto/voting/encryption-scheme.js +13 -0
- package/dist/lib/crypto/voting/encryption-scheme.js.map +1 -0
- package/dist/lib/crypto/voting/hash-utils.d.ts +58 -0
- package/dist/lib/crypto/voting/hash-utils.d.ts.map +1 -0
- package/dist/lib/crypto/voting/hash-utils.js +73 -0
- package/dist/lib/crypto/voting/hash-utils.js.map +1 -0
- package/dist/lib/crypto/voting/hybrid-encryption.d.ts +109 -0
- package/dist/lib/crypto/voting/hybrid-encryption.d.ts.map +1 -0
- package/dist/lib/crypto/voting/hybrid-encryption.js +134 -0
- package/dist/lib/crypto/voting/hybrid-encryption.js.map +1 -0
- package/dist/lib/crypto/voting/index.d.ts +18 -0
- package/dist/lib/crypto/voting/index.d.ts.map +1 -0
- package/dist/lib/crypto/voting/index.js +27 -0
- package/dist/lib/crypto/voting/index.js.map +1 -0
- package/dist/lib/crypto/voting/post-quantum-encryption.d.ts +107 -0
- package/dist/lib/crypto/voting/post-quantum-encryption.d.ts.map +1 -0
- package/dist/lib/crypto/voting/post-quantum-encryption.js +123 -0
- package/dist/lib/crypto/voting/post-quantum-encryption.js.map +1 -0
- package/dist/lib/csrf.d.ts +95 -0
- package/dist/lib/csrf.d.ts.map +1 -0
- package/dist/lib/csrf.js +174 -0
- package/dist/lib/csrf.js.map +1 -0
- package/dist/lib/data-router.d.ts +209 -0
- package/dist/lib/data-router.d.ts.map +1 -0
- package/dist/lib/data-router.js +792 -0
- package/dist/lib/data-router.js.map +1 -0
- package/dist/lib/database-circuit-breaker.d.ts +75 -0
- package/dist/lib/database-circuit-breaker.d.ts.map +1 -0
- package/dist/lib/database-circuit-breaker.js +155 -0
- package/dist/lib/database-circuit-breaker.js.map +1 -0
- package/dist/lib/database-config.d.ts +20 -0
- package/dist/lib/database-config.d.ts.map +1 -0
- package/dist/lib/database-config.js +46 -0
- package/dist/lib/database-config.js.map +1 -0
- package/dist/lib/database-connection-manager.d.ts +99 -0
- package/dist/lib/database-connection-manager.d.ts.map +1 -0
- package/dist/lib/database-connection-manager.js +495 -0
- package/dist/lib/database-connection-manager.js.map +1 -0
- package/dist/lib/database-monitor.d.ts +89 -0
- package/dist/lib/database-monitor.d.ts.map +1 -0
- package/dist/lib/database-monitor.js +199 -0
- package/dist/lib/database-monitor.js.map +1 -0
- package/dist/lib/database-rate-limiter.d.ts +41 -0
- package/dist/lib/database-rate-limiter.d.ts.map +1 -0
- package/dist/lib/database-rate-limiter.js +90 -0
- package/dist/lib/database-rate-limiter.js.map +1 -0
- package/dist/lib/database-wrapper-helper.d.ts +44 -0
- package/dist/lib/database-wrapper-helper.d.ts.map +1 -0
- package/dist/lib/database-wrapper-helper.js +104 -0
- package/dist/lib/database-wrapper-helper.js.map +1 -0
- package/dist/lib/database-wrapper.d.ts +51 -0
- package/dist/lib/database-wrapper.d.ts.map +1 -0
- package/dist/lib/database-wrapper.js +109 -0
- package/dist/lib/database-wrapper.js.map +1 -0
- package/dist/lib/db-query-helper.d.ts +130 -0
- package/dist/lib/db-query-helper.d.ts.map +1 -0
- package/dist/lib/db-query-helper.js +105 -0
- package/dist/lib/db-query-helper.js.map +1 -0
- package/dist/lib/discovery-handler.d.ts +19 -0
- package/dist/lib/discovery-handler.d.ts.map +1 -0
- package/dist/lib/discovery-handler.js +195 -0
- package/dist/lib/discovery-handler.js.map +1 -0
- package/dist/lib/domain-reputation-service.d.ts +112 -0
- package/dist/lib/domain-reputation-service.d.ts.map +1 -0
- package/dist/lib/domain-reputation-service.js +344 -0
- package/dist/lib/domain-reputation-service.js.map +1 -0
- package/dist/lib/email-privacy.d.ts +54 -0
- package/dist/lib/email-privacy.d.ts.map +1 -0
- package/dist/lib/email-privacy.js +72 -0
- package/dist/lib/email-privacy.js.map +1 -0
- package/dist/lib/email-provider.d.ts +133 -0
- package/dist/lib/email-provider.d.ts.map +1 -0
- package/dist/lib/email-provider.js +391 -0
- package/dist/lib/email-provider.js.map +1 -0
- package/dist/lib/encryption-key-service.d.ts +115 -0
- package/dist/lib/encryption-key-service.d.ts.map +1 -0
- package/dist/lib/encryption-key-service.js +272 -0
- package/dist/lib/encryption-key-service.js.map +1 -0
- package/dist/lib/entity-handler.d.ts +59 -0
- package/dist/lib/entity-handler.d.ts.map +1 -0
- package/dist/lib/entity-handler.js +866 -0
- package/dist/lib/entity-handler.js.map +1 -0
- package/dist/lib/entity-relationship-handler.d.ts +19 -0
- package/dist/lib/entity-relationship-handler.d.ts.map +1 -0
- package/dist/lib/entity-relationship-handler.js +242 -0
- package/dist/lib/entity-relationship-handler.js.map +1 -0
- package/dist/lib/entity-tagging-errors.d.ts +32 -0
- package/dist/lib/entity-tagging-errors.d.ts.map +1 -0
- package/dist/lib/entity-tagging-errors.js +53 -0
- package/dist/lib/entity-tagging-errors.js.map +1 -0
- package/dist/lib/entity-tagging-validator.d.ts +47 -0
- package/dist/lib/entity-tagging-validator.d.ts.map +1 -0
- package/dist/lib/entity-tagging-validator.js +84 -0
- package/dist/lib/entity-tagging-validator.js.map +1 -0
- package/dist/lib/exif-stripper.d.ts +37 -0
- package/dist/lib/exif-stripper.d.ts.map +1 -0
- package/dist/lib/exif-stripper.js +62 -0
- package/dist/lib/exif-stripper.js.map +1 -0
- package/dist/lib/extension-context.d.ts +19 -0
- package/dist/lib/extension-context.d.ts.map +1 -0
- package/dist/lib/extension-context.js +78 -0
- package/dist/lib/extension-context.js.map +1 -0
- package/dist/lib/extension-route-wrapper.d.ts +21 -0
- package/dist/lib/extension-route-wrapper.d.ts.map +1 -0
- package/dist/lib/extension-route-wrapper.js +113 -0
- package/dist/lib/extension-route-wrapper.js.map +1 -0
- package/dist/lib/extension-validator.d.ts +12 -0
- package/dist/lib/extension-validator.d.ts.map +1 -0
- package/dist/lib/extension-validator.js +60 -0
- package/dist/lib/extension-validator.js.map +1 -0
- package/dist/lib/feature-flags.d.ts +56 -0
- package/dist/lib/feature-flags.d.ts.map +1 -0
- package/dist/lib/feature-flags.js +140 -0
- package/dist/lib/feature-flags.js.map +1 -0
- package/dist/lib/feature-toggle-service.d.ts +48 -0
- package/dist/lib/feature-toggle-service.d.ts.map +1 -0
- package/dist/lib/feature-toggle-service.js +167 -0
- package/dist/lib/feature-toggle-service.js.map +1 -0
- package/dist/lib/feed-handler.d.ts +152 -0
- package/dist/lib/feed-handler.d.ts.map +1 -0
- package/dist/lib/feed-handler.js +784 -0
- package/dist/lib/feed-handler.js.map +1 -0
- package/dist/lib/feed-pagination.d.ts +42 -0
- package/dist/lib/feed-pagination.d.ts.map +1 -0
- package/dist/lib/feed-pagination.js +54 -0
- package/dist/lib/feed-pagination.js.map +1 -0
- package/dist/lib/feed-personalization.d.ts +52 -0
- package/dist/lib/feed-personalization.d.ts.map +1 -0
- package/dist/lib/feed-personalization.js +105 -0
- package/dist/lib/feed-personalization.js.map +1 -0
- package/dist/lib/followers-events.d.ts +37 -0
- package/dist/lib/followers-events.d.ts.map +1 -0
- package/dist/lib/followers-events.js +149 -0
- package/dist/lib/followers-events.js.map +1 -0
- package/dist/lib/followers-handler.d.ts +21 -0
- package/dist/lib/followers-handler.d.ts.map +1 -0
- package/dist/lib/followers-handler.js +35 -0
- package/dist/lib/followers-handler.js.map +1 -0
- package/dist/lib/friends-handler.d.ts +78 -0
- package/dist/lib/friends-handler.d.ts.map +1 -0
- package/dist/lib/friends-handler.js +390 -0
- package/dist/lib/friends-handler.js.map +1 -0
- package/dist/lib/graph/dual-write-service.d.ts +116 -0
- package/dist/lib/graph/dual-write-service.d.ts.map +1 -0
- package/dist/lib/graph/dual-write-service.js +332 -0
- package/dist/lib/graph/dual-write-service.js.map +1 -0
- package/dist/lib/graph/dual-write.d.ts +396 -0
- package/dist/lib/graph/dual-write.d.ts.map +1 -0
- package/dist/lib/graph/dual-write.js +53 -0
- package/dist/lib/graph/dual-write.js.map +1 -0
- package/dist/lib/graph/errors.d.ts +90 -0
- package/dist/lib/graph/errors.d.ts.map +1 -0
- package/dist/lib/graph/errors.js +131 -0
- package/dist/lib/graph/errors.js.map +1 -0
- package/dist/lib/graph/graph-factory.d.ts +64 -0
- package/dist/lib/graph/graph-factory.d.ts.map +1 -0
- package/dist/lib/graph/graph-factory.js +190 -0
- package/dist/lib/graph/graph-factory.js.map +1 -0
- package/dist/lib/graph/graph-schema-init.d.ts +31 -0
- package/dist/lib/graph/graph-schema-init.d.ts.map +1 -0
- package/dist/lib/graph/graph-schema-init.js +105 -0
- package/dist/lib/graph/graph-schema-init.js.map +1 -0
- package/dist/lib/graph/graph-service.d.ts +479 -0
- package/dist/lib/graph/graph-service.d.ts.map +1 -0
- package/dist/lib/graph/graph-service.js +21 -0
- package/dist/lib/graph/graph-service.js.map +1 -0
- package/dist/lib/graph/index.d.ts +40 -0
- package/dist/lib/graph/index.d.ts.map +1 -0
- package/dist/lib/graph/index.js +74 -0
- package/dist/lib/graph/index.js.map +1 -0
- package/dist/lib/graph/neo4j-graph-service.d.ts +186 -0
- package/dist/lib/graph/neo4j-graph-service.d.ts.map +1 -0
- package/dist/lib/graph/neo4j-graph-service.js +1625 -0
- package/dist/lib/graph/neo4j-graph-service.js.map +1 -0
- package/dist/lib/graph/reconciliation-service.d.ts +113 -0
- package/dist/lib/graph/reconciliation-service.d.ts.map +1 -0
- package/dist/lib/graph/reconciliation-service.js +533 -0
- package/dist/lib/graph/reconciliation-service.js.map +1 -0
- package/dist/lib/graph/scoring-engine.d.ts +154 -0
- package/dist/lib/graph/scoring-engine.d.ts.map +1 -0
- package/dist/lib/graph/scoring-engine.js +286 -0
- package/dist/lib/graph/scoring-engine.js.map +1 -0
- package/dist/lib/graph/types.d.ts +480 -0
- package/dist/lib/graph/types.d.ts.map +1 -0
- package/dist/lib/graph/types.js +10 -0
- package/dist/lib/graph/types.js.map +1 -0
- package/dist/lib/hook-dispatcher.d.ts +21 -0
- package/dist/lib/hook-dispatcher.d.ts.map +1 -0
- package/dist/lib/hook-dispatcher.js +62 -0
- package/dist/lib/hook-dispatcher.js.map +1 -0
- package/dist/lib/id-generator.d.ts +29 -0
- package/dist/lib/id-generator.d.ts.map +1 -0
- package/dist/lib/id-generator.js +51 -0
- package/dist/lib/id-generator.js.map +1 -0
- package/dist/lib/input-sanitizer.d.ts +55 -0
- package/dist/lib/input-sanitizer.d.ts.map +1 -0
- package/dist/lib/input-sanitizer.js +167 -0
- package/dist/lib/input-sanitizer.js.map +1 -0
- package/dist/lib/internal-docs-dashboard.json +112 -0
- package/dist/lib/internal-docs-handler.d.ts +60 -0
- package/dist/lib/internal-docs-handler.d.ts.map +1 -0
- package/dist/lib/internal-docs-handler.js +570 -0
- package/dist/lib/internal-docs-handler.js.map +1 -0
- package/dist/lib/internal-docs-navigation.d.ts +42 -0
- package/dist/lib/internal-docs-navigation.d.ts.map +1 -0
- package/dist/lib/internal-docs-navigation.js +73 -0
- package/dist/lib/internal-docs-navigation.js.map +1 -0
- package/dist/lib/internal-docs-navigation.json +169 -0
- package/dist/lib/invitation-handler.d.ts +133 -0
- package/dist/lib/invitation-handler.d.ts.map +1 -0
- package/dist/lib/invitation-handler.js +1335 -0
- package/dist/lib/invitation-handler.js.map +1 -0
- package/dist/lib/ip-scrubber.d.ts +59 -0
- package/dist/lib/ip-scrubber.d.ts.map +1 -0
- package/dist/lib/ip-scrubber.js +101 -0
- package/dist/lib/ip-scrubber.js.map +1 -0
- package/dist/lib/kv/dynamodb-kv.d.ts +39 -0
- package/dist/lib/kv/dynamodb-kv.d.ts.map +1 -0
- package/dist/lib/kv/dynamodb-kv.js +239 -0
- package/dist/lib/kv/dynamodb-kv.js.map +1 -0
- package/dist/lib/link-security-handler.d.ts +140 -0
- package/dist/lib/link-security-handler.d.ts.map +1 -0
- package/dist/lib/link-security-handler.js +460 -0
- package/dist/lib/link-security-handler.js.map +1 -0
- package/dist/lib/logger.d.ts +100 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/logger.js +201 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/media-cleanup-handler.d.ts +46 -0
- package/dist/lib/media-cleanup-handler.d.ts.map +1 -0
- package/dist/lib/media-cleanup-handler.js +190 -0
- package/dist/lib/media-cleanup-handler.js.map +1 -0
- package/dist/lib/media-handler.d.ts +150 -0
- package/dist/lib/media-handler.d.ts.map +1 -0
- package/dist/lib/media-handler.js +1544 -0
- package/dist/lib/media-handler.js.map +1 -0
- package/dist/lib/media-metadata-extractor.d.ts +71 -0
- package/dist/lib/media-metadata-extractor.d.ts.map +1 -0
- package/dist/lib/media-metadata-extractor.js +278 -0
- package/dist/lib/media-metadata-extractor.js.map +1 -0
- package/dist/lib/media-metrics.d.ts +57 -0
- package/dist/lib/media-metrics.d.ts.map +1 -0
- package/dist/lib/media-metrics.js +202 -0
- package/dist/lib/media-metrics.js.map +1 -0
- package/dist/lib/metadata/index.d.ts +6 -0
- package/dist/lib/metadata/index.d.ts.map +1 -0
- package/dist/lib/metadata/index.js +22 -0
- package/dist/lib/metadata/index.js.map +1 -0
- package/dist/lib/metadata/metadata-config.d.ts +17 -0
- package/dist/lib/metadata/metadata-config.d.ts.map +1 -0
- package/dist/lib/metadata/metadata-config.js +23 -0
- package/dist/lib/metadata/metadata-config.js.map +1 -0
- package/dist/lib/metadata/metadata-errors.d.ts +18 -0
- package/dist/lib/metadata/metadata-errors.d.ts.map +1 -0
- package/dist/lib/metadata/metadata-errors.js +26 -0
- package/dist/lib/metadata/metadata-errors.js.map +1 -0
- package/dist/lib/metadata/metadata-extractor.d.ts +17 -0
- package/dist/lib/metadata/metadata-extractor.d.ts.map +1 -0
- package/dist/lib/metadata/metadata-extractor.js +264 -0
- package/dist/lib/metadata/metadata-extractor.js.map +1 -0
- package/dist/lib/metadata/metadata-sanitizer.d.ts +9 -0
- package/dist/lib/metadata/metadata-sanitizer.d.ts.map +1 -0
- package/dist/lib/metadata/metadata-sanitizer.js +100 -0
- package/dist/lib/metadata/metadata-sanitizer.js.map +1 -0
- package/dist/lib/metadata/metadata-schemas.d.ts +131 -0
- package/dist/lib/metadata/metadata-schemas.d.ts.map +1 -0
- package/dist/lib/metadata/metadata-schemas.js +71 -0
- package/dist/lib/metadata/metadata-schemas.js.map +1 -0
- package/dist/lib/mfa/mfa-handler.d.ts +60 -0
- package/dist/lib/mfa/mfa-handler.d.ts.map +1 -0
- package/dist/lib/mfa/mfa-handler.js +150 -0
- package/dist/lib/mfa/mfa-handler.js.map +1 -0
- package/dist/lib/mfa/totp-service.d.ts +41 -0
- package/dist/lib/mfa/totp-service.d.ts.map +1 -0
- package/dist/lib/mfa/totp-service.js +183 -0
- package/dist/lib/mfa/totp-service.js.map +1 -0
- package/dist/lib/middleware/comment-rate-limit.d.ts +19 -0
- package/dist/lib/middleware/comment-rate-limit.d.ts.map +1 -0
- package/dist/lib/middleware/comment-rate-limit.js +113 -0
- package/dist/lib/middleware/comment-rate-limit.js.map +1 -0
- package/dist/lib/middleware/feature-toggle-rate-limit.d.ts +53 -0
- package/dist/lib/middleware/feature-toggle-rate-limit.d.ts.map +1 -0
- package/dist/lib/middleware/feature-toggle-rate-limit.js +164 -0
- package/dist/lib/middleware/feature-toggle-rate-limit.js.map +1 -0
- package/dist/lib/middleware.d.ts +70 -0
- package/dist/lib/middleware.d.ts.map +1 -0
- package/dist/lib/middleware.js +375 -0
- package/dist/lib/middleware.js.map +1 -0
- package/dist/lib/moderation-handler.d.ts +55 -0
- package/dist/lib/moderation-handler.d.ts.map +1 -0
- package/dist/lib/moderation-handler.js +230 -0
- package/dist/lib/moderation-handler.js.map +1 -0
- package/dist/lib/notification-handler.d.ts +65 -0
- package/dist/lib/notification-handler.d.ts.map +1 -0
- package/dist/lib/notification-handler.js +236 -0
- package/dist/lib/notification-handler.js.map +1 -0
- package/dist/lib/notification-preferences-handler.d.ts +27 -0
- package/dist/lib/notification-preferences-handler.d.ts.map +1 -0
- package/dist/lib/notification-preferences-handler.js +119 -0
- package/dist/lib/notification-preferences-handler.js.map +1 -0
- package/dist/lib/openai-budget.d.ts +45 -0
- package/dist/lib/openai-budget.d.ts.map +1 -0
- package/dist/lib/openai-budget.js +175 -0
- package/dist/lib/openai-budget.js.map +1 -0
- package/dist/lib/orphaned-media-handler.d.ts +60 -0
- package/dist/lib/orphaned-media-handler.d.ts.map +1 -0
- package/dist/lib/orphaned-media-handler.js +487 -0
- package/dist/lib/orphaned-media-handler.js.map +1 -0
- package/dist/lib/parental-control-handler.d.ts +42 -0
- package/dist/lib/parental-control-handler.d.ts.map +1 -0
- package/dist/lib/parental-control-handler.js +327 -0
- package/dist/lib/parental-control-handler.js.map +1 -0
- package/dist/lib/parental-link-handler.d.ts +38 -0
- package/dist/lib/parental-link-handler.d.ts.map +1 -0
- package/dist/lib/parental-link-handler.js +217 -0
- package/dist/lib/parental-link-handler.js.map +1 -0
- package/dist/lib/performance-metrics.d.ts +125 -0
- package/dist/lib/performance-metrics.d.ts.map +1 -0
- package/dist/lib/performance-metrics.js +277 -0
- package/dist/lib/performance-metrics.js.map +1 -0
- package/dist/lib/post-handler.d.ts +94 -0
- package/dist/lib/post-handler.d.ts.map +1 -0
- package/dist/lib/post-handler.js +1346 -0
- package/dist/lib/post-handler.js.map +1 -0
- package/dist/lib/privacy-defaults.d.ts +32 -0
- package/dist/lib/privacy-defaults.d.ts.map +1 -0
- package/dist/lib/privacy-defaults.js +85 -0
- package/dist/lib/privacy-defaults.js.map +1 -0
- package/dist/lib/privacy-handler.d.ts +43 -0
- package/dist/lib/privacy-handler.d.ts.map +1 -0
- package/dist/lib/privacy-handler.js +124 -0
- package/dist/lib/privacy-handler.js.map +1 -0
- package/dist/lib/queue/sqs-queue.d.ts +16 -0
- package/dist/lib/queue/sqs-queue.d.ts.map +1 -0
- package/dist/lib/queue/sqs-queue.js +39 -0
- package/dist/lib/queue/sqs-queue.js.map +1 -0
- package/dist/lib/queue-consumers/media-reconciliation-consumer.d.ts +9 -0
- package/dist/lib/queue-consumers/media-reconciliation-consumer.d.ts.map +1 -0
- package/dist/lib/queue-consumers/media-reconciliation-consumer.js +37 -0
- package/dist/lib/queue-consumers/media-reconciliation-consumer.js.map +1 -0
- package/dist/lib/quiet-hours.d.ts +33 -0
- package/dist/lib/quiet-hours.d.ts.map +1 -0
- package/dist/lib/quiet-hours.js +51 -0
- package/dist/lib/quiet-hours.js.map +1 -0
- package/dist/lib/rate-limit.d.ts +95 -0
- package/dist/lib/rate-limit.d.ts.map +1 -0
- package/dist/lib/rate-limit.js +247 -0
- package/dist/lib/rate-limit.js.map +1 -0
- package/dist/lib/reaction-handler.d.ts +68 -0
- package/dist/lib/reaction-handler.d.ts.map +1 -0
- package/dist/lib/reaction-handler.js +858 -0
- package/dist/lib/reaction-handler.js.map +1 -0
- package/dist/lib/recaptcha.d.ts +16 -0
- package/dist/lib/recaptcha.d.ts.map +1 -0
- package/dist/lib/recaptcha.js +71 -0
- package/dist/lib/recaptcha.js.map +1 -0
- package/dist/lib/redirect-resolver.d.ts +78 -0
- package/dist/lib/redirect-resolver.d.ts.map +1 -0
- package/dist/lib/redirect-resolver.js +276 -0
- package/dist/lib/redirect-resolver.js.map +1 -0
- package/dist/lib/region-config.d.ts +179 -0
- package/dist/lib/region-config.d.ts.map +1 -0
- package/dist/lib/region-config.js +474 -0
- package/dist/lib/region-config.js.map +1 -0
- package/dist/lib/region-detection.d.ts +110 -0
- package/dist/lib/region-detection.d.ts.map +1 -0
- package/dist/lib/region-detection.js +408 -0
- package/dist/lib/region-detection.js.map +1 -0
- package/dist/lib/relationship-handler.d.ts +19 -0
- package/dist/lib/relationship-handler.d.ts.map +1 -0
- package/dist/lib/relationship-handler.js +233 -0
- package/dist/lib/relationship-handler.js.map +1 -0
- package/dist/lib/request-context.d.ts +103 -0
- package/dist/lib/request-context.d.ts.map +1 -0
- package/dist/lib/request-context.js +179 -0
- package/dist/lib/request-context.js.map +1 -0
- package/dist/lib/route-helpers.d.ts +74 -0
- package/dist/lib/route-helpers.d.ts.map +1 -0
- package/dist/lib/route-helpers.js +190 -0
- package/dist/lib/route-helpers.js.map +1 -0
- package/dist/lib/route-matcher.d.ts +24 -0
- package/dist/lib/route-matcher.d.ts.map +1 -0
- package/dist/lib/route-matcher.js +96 -0
- package/dist/lib/route-matcher.js.map +1 -0
- package/dist/lib/router.d.ts +26 -0
- package/dist/lib/router.d.ts.map +1 -0
- package/dist/lib/router.js +90 -0
- package/dist/lib/router.js.map +1 -0
- package/dist/lib/routes/activitypub/actor.d.ts +12 -0
- package/dist/lib/routes/activitypub/actor.d.ts.map +1 -0
- package/dist/lib/routes/activitypub/actor.js +141 -0
- package/dist/lib/routes/activitypub/actor.js.map +1 -0
- package/dist/lib/routes/activitypub/audiences.d.ts +12 -0
- package/dist/lib/routes/activitypub/audiences.d.ts.map +1 -0
- package/dist/lib/routes/activitypub/audiences.js +325 -0
- package/dist/lib/routes/activitypub/audiences.js.map +1 -0
- package/dist/lib/routes/activitypub/collections.d.ts +12 -0
- package/dist/lib/routes/activitypub/collections.d.ts.map +1 -0
- package/dist/lib/routes/activitypub/collections.js +127 -0
- package/dist/lib/routes/activitypub/collections.js.map +1 -0
- package/dist/lib/routes/activitypub/entity-profile.d.ts +9 -0
- package/dist/lib/routes/activitypub/entity-profile.d.ts.map +1 -0
- package/dist/lib/routes/activitypub/entity-profile.js +136 -0
- package/dist/lib/routes/activitypub/entity-profile.js.map +1 -0
- package/dist/lib/routes/activitypub/friends.d.ts +15 -0
- package/dist/lib/routes/activitypub/friends.d.ts.map +1 -0
- package/dist/lib/routes/activitypub/friends.js +33 -0
- package/dist/lib/routes/activitypub/friends.js.map +1 -0
- package/dist/lib/routes/activitypub/group.d.ts +8 -0
- package/dist/lib/routes/activitypub/group.d.ts.map +1 -0
- package/dist/lib/routes/activitypub/group.js +436 -0
- package/dist/lib/routes/activitypub/group.js.map +1 -0
- package/dist/lib/routes/activitypub/inbox.d.ts +15 -0
- package/dist/lib/routes/activitypub/inbox.d.ts.map +1 -0
- package/dist/lib/routes/activitypub/inbox.js +125 -0
- package/dist/lib/routes/activitypub/inbox.js.map +1 -0
- package/dist/lib/routes/activitypub/messages.d.ts +12 -0
- package/dist/lib/routes/activitypub/messages.d.ts.map +1 -0
- package/dist/lib/routes/activitypub/messages.js +374 -0
- package/dist/lib/routes/activitypub/messages.js.map +1 -0
- package/dist/lib/routes/activitypub/outbox.d.ts +15 -0
- package/dist/lib/routes/activitypub/outbox.d.ts.map +1 -0
- package/dist/lib/routes/activitypub/outbox.js +33 -0
- package/dist/lib/routes/activitypub/outbox.js.map +1 -0
- package/dist/lib/routes/activitypub/post.d.ts +12 -0
- package/dist/lib/routes/activitypub/post.d.ts.map +1 -0
- package/dist/lib/routes/activitypub/post.js +213 -0
- package/dist/lib/routes/activitypub/post.js.map +1 -0
- package/dist/lib/routes/activitypub/webfinger.d.ts +11 -0
- package/dist/lib/routes/activitypub/webfinger.d.ts.map +1 -0
- package/dist/lib/routes/activitypub/webfinger.js +27 -0
- package/dist/lib/routes/activitypub/webfinger.js.map +1 -0
- package/dist/lib/routes/admin-costs.d.ts +8 -0
- package/dist/lib/routes/admin-costs.d.ts.map +1 -0
- package/dist/lib/routes/admin-costs.js +89 -0
- package/dist/lib/routes/admin-costs.js.map +1 -0
- package/dist/lib/routes/admin.d.ts +6 -0
- package/dist/lib/routes/admin.d.ts.map +1 -0
- package/dist/lib/routes/admin.js +1450 -0
- package/dist/lib/routes/admin.js.map +1 -0
- package/dist/lib/routes/auth.d.ts +6 -0
- package/dist/lib/routes/auth.d.ts.map +1 -0
- package/dist/lib/routes/auth.js +49 -0
- package/dist/lib/routes/auth.js.map +1 -0
- package/dist/lib/routes/badges.d.ts +6 -0
- package/dist/lib/routes/badges.d.ts.map +1 -0
- package/dist/lib/routes/badges.js +66 -0
- package/dist/lib/routes/badges.js.map +1 -0
- package/dist/lib/routes/circles.d.ts +8 -0
- package/dist/lib/routes/circles.d.ts.map +1 -0
- package/dist/lib/routes/circles.js +135 -0
- package/dist/lib/routes/circles.js.map +1 -0
- package/dist/lib/routes/comments.d.ts +6 -0
- package/dist/lib/routes/comments.d.ts.map +1 -0
- package/dist/lib/routes/comments.js +228 -0
- package/dist/lib/routes/comments.js.map +1 -0
- package/dist/lib/routes/connection-codes.d.ts +8 -0
- package/dist/lib/routes/connection-codes.d.ts.map +1 -0
- package/dist/lib/routes/connection-codes.js +84 -0
- package/dist/lib/routes/connection-codes.js.map +1 -0
- package/dist/lib/routes/content-discovery.d.ts +11 -0
- package/dist/lib/routes/content-discovery.d.ts.map +1 -0
- package/dist/lib/routes/content-discovery.js +211 -0
- package/dist/lib/routes/content-discovery.js.map +1 -0
- package/dist/lib/routes/dashboard.d.ts +8 -0
- package/dist/lib/routes/dashboard.d.ts.map +1 -0
- package/dist/lib/routes/dashboard.js +1139 -0
- package/dist/lib/routes/dashboard.js.map +1 -0
- package/dist/lib/routes/deletion.d.ts +6 -0
- package/dist/lib/routes/deletion.d.ts.map +1 -0
- package/dist/lib/routes/deletion.js +213 -0
- package/dist/lib/routes/deletion.js.map +1 -0
- package/dist/lib/routes/discovery.d.ts +8 -0
- package/dist/lib/routes/discovery.d.ts.map +1 -0
- package/dist/lib/routes/discovery.js +67 -0
- package/dist/lib/routes/discovery.js.map +1 -0
- package/dist/lib/routes/employees.d.ts +6 -0
- package/dist/lib/routes/employees.d.ts.map +1 -0
- package/dist/lib/routes/employees.js +82 -0
- package/dist/lib/routes/employees.js.map +1 -0
- package/dist/lib/routes/entities.d.ts +8 -0
- package/dist/lib/routes/entities.d.ts.map +1 -0
- package/dist/lib/routes/entities.js +467 -0
- package/dist/lib/routes/entities.js.map +1 -0
- package/dist/lib/routes/entity-relationships.d.ts +8 -0
- package/dist/lib/routes/entity-relationships.d.ts.map +1 -0
- package/dist/lib/routes/entity-relationships.js +118 -0
- package/dist/lib/routes/entity-relationships.js.map +1 -0
- package/dist/lib/routes/export.d.ts +6 -0
- package/dist/lib/routes/export.d.ts.map +1 -0
- package/dist/lib/routes/export.js +118 -0
- package/dist/lib/routes/export.js.map +1 -0
- package/dist/lib/routes/feature-flags.d.ts +8 -0
- package/dist/lib/routes/feature-flags.d.ts.map +1 -0
- package/dist/lib/routes/feature-flags.js +75 -0
- package/dist/lib/routes/feature-flags.js.map +1 -0
- package/dist/lib/routes/feeds.d.ts +6 -0
- package/dist/lib/routes/feeds.d.ts.map +1 -0
- package/dist/lib/routes/feeds.js +131 -0
- package/dist/lib/routes/feeds.js.map +1 -0
- package/dist/lib/routes/followers.d.ts +6 -0
- package/dist/lib/routes/followers.d.ts.map +1 -0
- package/dist/lib/routes/followers.js +405 -0
- package/dist/lib/routes/followers.js.map +1 -0
- package/dist/lib/routes/friends.d.ts +6 -0
- package/dist/lib/routes/friends.d.ts.map +1 -0
- package/dist/lib/routes/friends.js +116 -0
- package/dist/lib/routes/friends.js.map +1 -0
- package/dist/lib/routes/health.d.ts +6 -0
- package/dist/lib/routes/health.d.ts.map +1 -0
- package/dist/lib/routes/health.js +129 -0
- package/dist/lib/routes/health.js.map +1 -0
- package/dist/lib/routes/index.d.ts +21 -0
- package/dist/lib/routes/index.d.ts.map +1 -0
- package/dist/lib/routes/index.js +212 -0
- package/dist/lib/routes/index.js.map +1 -0
- package/dist/lib/routes/internal-docs.d.ts +6 -0
- package/dist/lib/routes/internal-docs.d.ts.map +1 -0
- package/dist/lib/routes/internal-docs.js +44 -0
- package/dist/lib/routes/internal-docs.js.map +1 -0
- package/dist/lib/routes/invitations.d.ts +6 -0
- package/dist/lib/routes/invitations.d.ts.map +1 -0
- package/dist/lib/routes/invitations.js +67 -0
- package/dist/lib/routes/invitations.js.map +1 -0
- package/dist/lib/routes/link-reports.d.ts +11 -0
- package/dist/lib/routes/link-reports.d.ts.map +1 -0
- package/dist/lib/routes/link-reports.js +287 -0
- package/dist/lib/routes/link-reports.js.map +1 -0
- package/dist/lib/routes/map.d.ts +6 -0
- package/dist/lib/routes/map.d.ts.map +1 -0
- package/dist/lib/routes/map.js +21 -0
- package/dist/lib/routes/map.js.map +1 -0
- package/dist/lib/routes/media-metadata-visibility.d.ts +3 -0
- package/dist/lib/routes/media-metadata-visibility.d.ts.map +1 -0
- package/dist/lib/routes/media-metadata-visibility.js +184 -0
- package/dist/lib/routes/media-metadata-visibility.js.map +1 -0
- package/dist/lib/routes/media.d.ts +9 -0
- package/dist/lib/routes/media.d.ts.map +1 -0
- package/dist/lib/routes/media.js +1910 -0
- package/dist/lib/routes/media.js.map +1 -0
- package/dist/lib/routes/mfa.d.ts +8 -0
- package/dist/lib/routes/mfa.d.ts.map +1 -0
- package/dist/lib/routes/mfa.js +193 -0
- package/dist/lib/routes/mfa.js.map +1 -0
- package/dist/lib/routes/notifications.d.ts +9 -0
- package/dist/lib/routes/notifications.d.ts.map +1 -0
- package/dist/lib/routes/notifications.js +230 -0
- package/dist/lib/routes/notifications.js.map +1 -0
- package/dist/lib/routes/orphaned-media-health.d.ts +9 -0
- package/dist/lib/routes/orphaned-media-health.d.ts.map +1 -0
- package/dist/lib/routes/orphaned-media-health.js +121 -0
- package/dist/lib/routes/orphaned-media-health.js.map +1 -0
- package/dist/lib/routes/orphaned-media.d.ts +8 -0
- package/dist/lib/routes/orphaned-media.d.ts.map +1 -0
- package/dist/lib/routes/orphaned-media.js +111 -0
- package/dist/lib/routes/orphaned-media.js.map +1 -0
- package/dist/lib/routes/out.d.ts +17 -0
- package/dist/lib/routes/out.d.ts.map +1 -0
- package/dist/lib/routes/out.js +339 -0
- package/dist/lib/routes/out.js.map +1 -0
- package/dist/lib/routes/parental-controls.d.ts +8 -0
- package/dist/lib/routes/parental-controls.d.ts.map +1 -0
- package/dist/lib/routes/parental-controls.js +282 -0
- package/dist/lib/routes/parental-controls.js.map +1 -0
- package/dist/lib/routes/posts.d.ts +6 -0
- package/dist/lib/routes/posts.d.ts.map +1 -0
- package/dist/lib/routes/posts.js +518 -0
- package/dist/lib/routes/posts.js.map +1 -0
- package/dist/lib/routes/privacy.d.ts +6 -0
- package/dist/lib/routes/privacy.d.ts.map +1 -0
- package/dist/lib/routes/privacy.js +66 -0
- package/dist/lib/routes/privacy.js.map +1 -0
- package/dist/lib/routes/products.d.ts +9 -0
- package/dist/lib/routes/products.d.ts.map +1 -0
- package/dist/lib/routes/products.js +224 -0
- package/dist/lib/routes/products.js.map +1 -0
- package/dist/lib/routes/relationships.d.ts +8 -0
- package/dist/lib/routes/relationships.d.ts.map +1 -0
- package/dist/lib/routes/relationships.js +118 -0
- package/dist/lib/routes/relationships.js.map +1 -0
- package/dist/lib/routes/sentiments.d.ts +6 -0
- package/dist/lib/routes/sentiments.d.ts.map +1 -0
- package/dist/lib/routes/sentiments.js +285 -0
- package/dist/lib/routes/sentiments.js.map +1 -0
- package/dist/lib/routes/taxonomy-analytics.d.ts +8 -0
- package/dist/lib/routes/taxonomy-analytics.d.ts.map +1 -0
- package/dist/lib/routes/taxonomy-analytics.js +151 -0
- package/dist/lib/routes/taxonomy-analytics.js.map +1 -0
- package/dist/lib/routes/taxonomy.d.ts +15 -0
- package/dist/lib/routes/taxonomy.d.ts.map +1 -0
- package/dist/lib/routes/taxonomy.js +370 -0
- package/dist/lib/routes/taxonomy.js.map +1 -0
- package/dist/lib/routes/types.d.ts +46 -0
- package/dist/lib/routes/types.d.ts.map +1 -0
- package/dist/lib/routes/types.js +8 -0
- package/dist/lib/routes/types.js.map +1 -0
- package/dist/lib/routes/upload-sessions.d.ts +9 -0
- package/dist/lib/routes/upload-sessions.d.ts.map +1 -0
- package/dist/lib/routes/upload-sessions.js +268 -0
- package/dist/lib/routes/upload-sessions.js.map +1 -0
- package/dist/lib/routes/user.d.ts +8 -0
- package/dist/lib/routes/user.d.ts.map +1 -0
- package/dist/lib/routes/user.js +287 -0
- package/dist/lib/routes/user.js.map +1 -0
- package/dist/lib/routes-all.d.ts +9 -0
- package/dist/lib/routes-all.d.ts.map +1 -0
- package/dist/lib/routes-all.js +170 -0
- package/dist/lib/routes-all.js.map +1 -0
- package/dist/lib/routes.d.ts +10 -0
- package/dist/lib/routes.d.ts.map +1 -0
- package/dist/lib/routes.js +16 -0
- package/dist/lib/routes.js.map +1 -0
- package/dist/lib/scaling-health.d.ts +48 -0
- package/dist/lib/scaling-health.d.ts.map +1 -0
- package/dist/lib/scaling-health.js +363 -0
- package/dist/lib/scaling-health.js.map +1 -0
- package/dist/lib/scheduled/media-stale-cleanup.d.ts +11 -0
- package/dist/lib/scheduled/media-stale-cleanup.d.ts.map +1 -0
- package/dist/lib/scheduled/media-stale-cleanup.js +79 -0
- package/dist/lib/scheduled/media-stale-cleanup.js.map +1 -0
- package/dist/lib/scheduled/orphaned-media-monitor.d.ts +97 -0
- package/dist/lib/scheduled/orphaned-media-monitor.d.ts.map +1 -0
- package/dist/lib/scheduled/orphaned-media-monitor.js +399 -0
- package/dist/lib/scheduled/orphaned-media-monitor.js.map +1 -0
- package/dist/lib/schemas.d.ts +314 -0
- package/dist/lib/schemas.d.ts.map +1 -0
- package/dist/lib/schemas.js +235 -0
- package/dist/lib/schemas.js.map +1 -0
- package/dist/lib/secret-resolver.d.ts +88 -0
- package/dist/lib/secret-resolver.d.ts.map +1 -0
- package/dist/lib/secret-resolver.js +183 -0
- package/dist/lib/secret-resolver.js.map +1 -0
- package/dist/lib/security-event-cleaner.d.ts +61 -0
- package/dist/lib/security-event-cleaner.d.ts.map +1 -0
- package/dist/lib/security-event-cleaner.js +74 -0
- package/dist/lib/security-event-cleaner.js.map +1 -0
- package/dist/lib/security-headers.d.ts +36 -0
- package/dist/lib/security-headers.d.ts.map +1 -0
- package/dist/lib/security-headers.js +87 -0
- package/dist/lib/security-headers.js.map +1 -0
- package/dist/lib/security-monitor.d.ts +92 -0
- package/dist/lib/security-monitor.d.ts.map +1 -0
- package/dist/lib/security-monitor.js +287 -0
- package/dist/lib/security-monitor.js.map +1 -0
- package/dist/lib/sentiment-digest.d.ts +19 -0
- package/dist/lib/sentiment-digest.d.ts.map +1 -0
- package/dist/lib/sentiment-digest.js +99 -0
- package/dist/lib/sentiment-digest.js.map +1 -0
- package/dist/lib/sentiment-display.d.ts +20 -0
- package/dist/lib/sentiment-display.d.ts.map +1 -0
- package/dist/lib/sentiment-display.js +38 -0
- package/dist/lib/sentiment-display.js.map +1 -0
- package/dist/lib/services/image-normalizer.d.ts +15 -0
- package/dist/lib/services/image-normalizer.d.ts.map +1 -0
- package/dist/lib/services/image-normalizer.js +22 -0
- package/dist/lib/services/image-normalizer.js.map +1 -0
- package/dist/lib/services/media-reconciliation-service.d.ts +25 -0
- package/dist/lib/services/media-reconciliation-service.d.ts.map +1 -0
- package/dist/lib/services/media-reconciliation-service.js +191 -0
- package/dist/lib/services/media-reconciliation-service.js.map +1 -0
- package/dist/lib/services/media-upload-service.d.ts +25 -0
- package/dist/lib/services/media-upload-service.d.ts.map +1 -0
- package/dist/lib/services/media-upload-service.js +240 -0
- package/dist/lib/services/media-upload-service.js.map +1 -0
- package/dist/lib/services/user-data-deletion.d.ts +30 -0
- package/dist/lib/services/user-data-deletion.d.ts.map +1 -0
- package/dist/lib/services/user-data-deletion.js +118 -0
- package/dist/lib/services/user-data-deletion.js.map +1 -0
- package/dist/lib/session-awareness.d.ts +35 -0
- package/dist/lib/session-awareness.d.ts.map +1 -0
- package/dist/lib/session-awareness.js +79 -0
- package/dist/lib/session-awareness.js.map +1 -0
- package/dist/lib/session-config.d.ts +87 -0
- package/dist/lib/session-config.d.ts.map +1 -0
- package/dist/lib/session-config.js +165 -0
- package/dist/lib/session-config.js.map +1 -0
- package/dist/lib/session-manager.d.ts +103 -0
- package/dist/lib/session-manager.d.ts.map +1 -0
- package/dist/lib/session-manager.js +492 -0
- package/dist/lib/session-manager.js.map +1 -0
- package/dist/lib/sso-auth-handler.d.ts +12 -0
- package/dist/lib/sso-auth-handler.d.ts.map +1 -0
- package/dist/lib/sso-auth-handler.js +24 -0
- package/dist/lib/sso-auth-handler.js.map +1 -0
- package/dist/lib/storage/s3-storage.d.ts +29 -0
- package/dist/lib/storage/s3-storage.d.ts.map +1 -0
- package/dist/lib/storage/s3-storage.js +135 -0
- package/dist/lib/storage/s3-storage.js.map +1 -0
- package/dist/lib/tag-suggestions-handler.d.ts +52 -0
- package/dist/lib/tag-suggestions-handler.d.ts.map +1 -0
- package/dist/lib/tag-suggestions-handler.js +195 -0
- package/dist/lib/tag-suggestions-handler.js.map +1 -0
- package/dist/lib/taxonomy-handler-factory.d.ts +18 -0
- package/dist/lib/taxonomy-handler-factory.d.ts.map +1 -0
- package/dist/lib/taxonomy-handler-factory.js +29 -0
- package/dist/lib/taxonomy-handler-factory.js.map +1 -0
- package/dist/lib/taxonomy-handler.d.ts +142 -0
- package/dist/lib/taxonomy-handler.d.ts.map +1 -0
- package/dist/lib/taxonomy-handler.js +636 -0
- package/dist/lib/taxonomy-handler.js.map +1 -0
- package/dist/lib/taxonomy-metrics.d.ts +76 -0
- package/dist/lib/taxonomy-metrics.d.ts.map +1 -0
- package/dist/lib/taxonomy-metrics.js +201 -0
- package/dist/lib/taxonomy-metrics.js.map +1 -0
- package/dist/lib/taxonomy-search-metrics.d.ts +45 -0
- package/dist/lib/taxonomy-search-metrics.d.ts.map +1 -0
- package/dist/lib/taxonomy-search-metrics.js +75 -0
- package/dist/lib/taxonomy-search-metrics.js.map +1 -0
- package/dist/lib/tenant-context.d.ts +35 -0
- package/dist/lib/tenant-context.d.ts.map +1 -0
- package/dist/lib/tenant-context.js +54 -0
- package/dist/lib/tenant-context.js.map +1 -0
- package/dist/lib/terminology.d.ts +25 -0
- package/dist/lib/terminology.d.ts.map +1 -0
- package/dist/lib/terminology.js +44 -0
- package/dist/lib/terminology.js.map +1 -0
- package/dist/lib/theme.d.ts +29 -0
- package/dist/lib/theme.d.ts.map +1 -0
- package/dist/lib/theme.js +38 -0
- package/dist/lib/theme.js.map +1 -0
- package/dist/lib/threat-intel-service.d.ts +67 -0
- package/dist/lib/threat-intel-service.d.ts.map +1 -0
- package/dist/lib/threat-intel-service.js +193 -0
- package/dist/lib/threat-intel-service.js.map +1 -0
- package/dist/lib/types/media-reconciliation.d.ts +64 -0
- package/dist/lib/types/media-reconciliation.d.ts.map +1 -0
- package/dist/lib/types/media-reconciliation.js +8 -0
- package/dist/lib/types/media-reconciliation.js.map +1 -0
- package/dist/lib/upload-session-handler.d.ts +56 -0
- package/dist/lib/upload-session-handler.d.ts.map +1 -0
- package/dist/lib/upload-session-handler.js +312 -0
- package/dist/lib/upload-session-handler.js.map +1 -0
- package/dist/lib/user-badge.d.ts +56 -0
- package/dist/lib/user-badge.d.ts.map +1 -0
- package/dist/lib/user-badge.js +92 -0
- package/dist/lib/user-badge.js.map +1 -0
- package/dist/lib/user-deletion-handler-enhanced.d.ts +96 -0
- package/dist/lib/user-deletion-handler-enhanced.d.ts.map +1 -0
- package/dist/lib/user-deletion-handler-enhanced.js +401 -0
- package/dist/lib/user-deletion-handler-enhanced.js.map +1 -0
- package/dist/lib/user-deprovisioning.d.ts +43 -0
- package/dist/lib/user-deprovisioning.d.ts.map +1 -0
- package/dist/lib/user-deprovisioning.js +138 -0
- package/dist/lib/user-deprovisioning.js.map +1 -0
- package/dist/lib/user-export-handler.d.ts +172 -0
- package/dist/lib/user-export-handler.d.ts.map +1 -0
- package/dist/lib/user-export-handler.js +435 -0
- package/dist/lib/user-export-handler.js.map +1 -0
- package/dist/lib/validate-request.d.ts +46 -0
- package/dist/lib/validate-request.d.ts.map +1 -0
- package/dist/lib/validate-request.js +127 -0
- package/dist/lib/validate-request.js.map +1 -0
- package/dist/lib/validation/feature-toggle-schemas.d.ts +410 -0
- package/dist/lib/validation/feature-toggle-schemas.d.ts.map +1 -0
- package/dist/lib/validation/feature-toggle-schemas.js +274 -0
- package/dist/lib/validation/feature-toggle-schemas.js.map +1 -0
- package/dist/lib/validation/validate-request.d.ts +75 -0
- package/dist/lib/validation/validate-request.d.ts.map +1 -0
- package/dist/lib/validation/validate-request.js +183 -0
- package/dist/lib/validation/validate-request.js.map +1 -0
- package/dist/lib/validation.d.ts +50 -0
- package/dist/lib/validation.d.ts.map +1 -0
- package/dist/lib/validation.js +122 -0
- package/dist/lib/validation.js.map +1 -0
- package/dist/lib/version.d.ts +36 -0
- package/dist/lib/version.d.ts.map +1 -0
- package/dist/lib/version.js +44 -0
- package/dist/lib/version.js.map +1 -0
- package/dist/server.d.ts +13 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +254 -0
- package/dist/server.js.map +1 -0
- package/dist/types/cloudflare-compat.d.ts +134 -0
- package/dist/types/cloudflare-compat.d.ts.map +1 -0
- package/dist/types/cloudflare-compat.js +7 -0
- package/dist/types/cloudflare-compat.js.map +1 -0
- package/dist/worker.d.ts +16 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +21 -0
- package/dist/worker.js.map +1 -0
- package/package.json +91 -0
- package/src/lambda/cleanup-cron.ts +37 -0
- package/src/lambda/create-auth-challenge.ts +112 -0
- package/src/lambda/custom-message.ts +30 -0
- package/src/lambda/define-auth-challenge.ts +24 -0
- package/src/lambda/delete-account-worker.ts +96 -0
- package/src/lambda/diagnostics-proxy.ts +139 -0
- package/src/lambda/e2e-sweeper.ts +140 -0
- package/src/lambda/federation-outbox-worker.ts +8 -0
- package/src/lambda/followers-events-worker.ts +8 -0
- package/src/lambda/hourly-cron.ts +103 -0
- package/src/lambda/link-check-worker.ts +8 -0
- package/src/lambda/maintenance-cron.ts +95 -0
- package/src/lambda/media-processing-worker.ts +68 -0
- package/src/lambda/media-reconciliation-worker.ts +8 -0
- package/src/lambda/nightly-cron.ts +338 -0
- package/src/lambda/post-confirmation.ts +80 -0
- package/src/lambda/pre-signup.ts +39 -0
- package/src/lambda/pre-token-generation.ts +93 -0
- package/src/lambda/tools/check-health.ts +22 -0
- package/src/lambda/tools/describe-services.ts +45 -0
- package/src/lambda/tools/get-cost-report.ts +64 -0
- package/src/lambda/tools/get-errors.ts +78 -0
- package/src/lambda/tools/get-feature-flags.ts +27 -0
- package/src/lambda/tools/get-queue-status.ts +60 -0
- package/src/lambda/tools/search-logs.ts +75 -0
- package/src/lambda/tools/send-alert.ts +37 -0
- package/src/lambda/verify-auth-challenge.ts +37 -0
|
@@ -0,0 +1,1335 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Invitation Handler
|
|
4
|
+
*
|
|
5
|
+
* Handles user-to-user invitations for invitation-only sign-up mode.
|
|
6
|
+
* Users can create invitation codes that others can use to sign up.
|
|
7
|
+
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
+
}) : function(o, v) {
|
|
22
|
+
o["default"] = v;
|
|
23
|
+
});
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.InvitationHandler = void 0;
|
|
43
|
+
const db_1 = require("../db");
|
|
44
|
+
const feature_toggle_service_1 = require("./feature-toggle-service");
|
|
45
|
+
const logger_1 = require("./logger");
|
|
46
|
+
const rate_limit_1 = require("./rate-limit");
|
|
47
|
+
const secret_resolver_1 = require("./secret-resolver");
|
|
48
|
+
const security_headers_1 = require("./security-headers");
|
|
49
|
+
const session_manager_1 = require("./session-manager");
|
|
50
|
+
/**
|
|
51
|
+
* Invitation Handler class
|
|
52
|
+
*/
|
|
53
|
+
class InvitationHandler {
|
|
54
|
+
sessionManager;
|
|
55
|
+
securityHeaders;
|
|
56
|
+
rateLimiter;
|
|
57
|
+
logger;
|
|
58
|
+
constructor(env) {
|
|
59
|
+
this.sessionManager = new session_manager_1.SessionManager();
|
|
60
|
+
this.securityHeaders = new security_headers_1.SecurityHeaders();
|
|
61
|
+
this.rateLimiter = new rate_limit_1.RateLimiter();
|
|
62
|
+
this.logger = logger_1.Logger.getInstance(env);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Generate a cryptographically secure session token for tracking who scanned an invitation
|
|
66
|
+
* SECURITY: Uses crypto.getRandomValues() for CSPRNG to prevent spoofing
|
|
67
|
+
*/
|
|
68
|
+
generateSessionToken() {
|
|
69
|
+
// Generate 32 random bytes (256 bits of entropy)
|
|
70
|
+
const randomBytes = new Uint8Array(32);
|
|
71
|
+
crypto.getRandomValues(randomBytes);
|
|
72
|
+
// Convert to base64url for URL-safe encoding
|
|
73
|
+
// Use base64url encoding (RFC 4648) which is safe for URLs
|
|
74
|
+
// Cloudflare Workers support btoa, but we'll use a more compatible approach
|
|
75
|
+
let base64 = "";
|
|
76
|
+
for (let i = 0; i < randomBytes.length; i += 3) {
|
|
77
|
+
const a = randomBytes[i];
|
|
78
|
+
const b = randomBytes[i + 1] || 0;
|
|
79
|
+
const c = randomBytes[i + 2] || 0;
|
|
80
|
+
const bitmap = (a << 16) | (b << 8) | c;
|
|
81
|
+
base64 += String.fromCharCode((bitmap >> 18) & 63, (bitmap >> 12) & 63, (bitmap >> 6) & 63, bitmap & 63);
|
|
82
|
+
}
|
|
83
|
+
// Convert to base64 and then to base64url
|
|
84
|
+
// Cloudflare Workers always have btoa available
|
|
85
|
+
const base64String = btoa(base64);
|
|
86
|
+
const base64url = base64String
|
|
87
|
+
.replace(/\+/g, "-")
|
|
88
|
+
.replace(/\//g, "_")
|
|
89
|
+
.replace(/=/g, "");
|
|
90
|
+
return `inv_${base64url}`;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Store session token in KV for validation during signup
|
|
94
|
+
* Key: invitation-session:{code}, Value: { token, email?, expiresAt }
|
|
95
|
+
* TTL: 1 hour (3600 seconds)
|
|
96
|
+
*/
|
|
97
|
+
async storeSessionToken(code, token, email, env) {
|
|
98
|
+
if (!env.INVITATIONS_KV) {
|
|
99
|
+
// If KV not available, we can't store tokens - this is a fallback scenario
|
|
100
|
+
this.logger.warn("[InvitationHandler] INVITATIONS_KV not available, cannot store session token");
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const expiresAt = new Date(Date.now() + 3600 * 1000); // 1 hour from now
|
|
104
|
+
const key = `invitation-session:${code.toUpperCase()}`;
|
|
105
|
+
const value = JSON.stringify({
|
|
106
|
+
token,
|
|
107
|
+
email: email || null,
|
|
108
|
+
expiresAt: expiresAt.toISOString(),
|
|
109
|
+
});
|
|
110
|
+
await env.INVITATIONS_KV.put(key, value, {
|
|
111
|
+
expirationTtl: 3600, // 1 hour
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Validate session token for an invitation code
|
|
116
|
+
* Returns the stored email if token is valid, null otherwise
|
|
117
|
+
*/
|
|
118
|
+
async validateSessionToken(code, token, env) {
|
|
119
|
+
if (!env.INVITATIONS_KV) {
|
|
120
|
+
// If KV not available, we can't validate tokens - allow for backward compatibility
|
|
121
|
+
this.logger.warn("[InvitationHandler] INVITATIONS_KV not available, cannot validate session token");
|
|
122
|
+
return { valid: true }; // Allow for backward compatibility
|
|
123
|
+
}
|
|
124
|
+
const key = `invitation-session:${code.toUpperCase()}`;
|
|
125
|
+
const stored = await env.INVITATIONS_KV.get(key);
|
|
126
|
+
if (!stored) {
|
|
127
|
+
return { valid: false };
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
const data = JSON.parse(stored);
|
|
131
|
+
// Check if token matches
|
|
132
|
+
if (data.token !== token) {
|
|
133
|
+
return { valid: false };
|
|
134
|
+
}
|
|
135
|
+
// Check if expired
|
|
136
|
+
if (data.expiresAt && new Date(data.expiresAt) < new Date()) {
|
|
137
|
+
return { valid: false };
|
|
138
|
+
}
|
|
139
|
+
return { valid: true, email: data.email || null };
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
this.logger.error("[InvitationHandler] Error parsing session token data:", error);
|
|
143
|
+
return { valid: false };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Generate a cryptographically secure random invitation code
|
|
148
|
+
* SECURITY: Uses crypto.getRandomValues() for CSPRNG (Cryptographically Secure Pseudo-Random Number Generator)
|
|
149
|
+
* Increased length to 10 characters for better security (1.1 trillion combinations)
|
|
150
|
+
*/
|
|
151
|
+
generateInvitationCode() {
|
|
152
|
+
// SECURITY: Use crypto.getRandomValues() instead of Math.random() for cryptographic security
|
|
153
|
+
// Math.random() is predictable and not suitable for security-sensitive operations
|
|
154
|
+
const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; // Exclude ambiguous chars (0, O, I, 1)
|
|
155
|
+
const codeLength = 10; // Increased from 8 to 10 for better security
|
|
156
|
+
// Use crypto.getRandomValues for secure random generation
|
|
157
|
+
const randomValues = new Uint32Array(codeLength);
|
|
158
|
+
crypto.getRandomValues(randomValues);
|
|
159
|
+
let code = "";
|
|
160
|
+
for (let i = 0; i < codeLength; i++) {
|
|
161
|
+
// Use modulo to map random value to character index
|
|
162
|
+
const index = randomValues[i] % chars.length;
|
|
163
|
+
code += chars[index];
|
|
164
|
+
}
|
|
165
|
+
return code;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Delete expired invitations for a user
|
|
169
|
+
*
|
|
170
|
+
* Automatically removes invitations that have expired (expiresAt < now).
|
|
171
|
+
* This keeps the database clean and ensures expired invitations don't count toward limits.
|
|
172
|
+
*/
|
|
173
|
+
async deleteExpiredInvitations(userId, env, request) {
|
|
174
|
+
const { sharedDatabaseConnectionManager } = await Promise.resolve().then(() => __importStar(require("./database-connection-manager")));
|
|
175
|
+
const { withQueryTimeoutAndRetry, QueryTimeoutPresets } = await Promise.resolve().then(() => __importStar(require("./db-query-helper")));
|
|
176
|
+
const { detectRegionSync } = await Promise.resolve().then(() => __importStar(require("./region-detection")));
|
|
177
|
+
const dbManager = sharedDatabaseConnectionManager;
|
|
178
|
+
const regionRequest = request || new Request("https://api.example.com");
|
|
179
|
+
const region = detectRegionSync(regionRequest, env);
|
|
180
|
+
const now = new Date();
|
|
181
|
+
const deletedCount = await withQueryTimeoutAndRetry(dbManager, region, env, async (client) => {
|
|
182
|
+
const result = await client.invitation.deleteMany({
|
|
183
|
+
where: {
|
|
184
|
+
createdBy: userId,
|
|
185
|
+
used: false, // Only delete unused expired invitations
|
|
186
|
+
expiresAt: {
|
|
187
|
+
not: null, // Has expiration date
|
|
188
|
+
lt: now, // Expired
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
return result.count;
|
|
193
|
+
}, {
|
|
194
|
+
...QueryTimeoutPresets.BACKGROUND, // Background cleanup, less urgent
|
|
195
|
+
defaultValue: 0,
|
|
196
|
+
context: {
|
|
197
|
+
operation: "deleteExpiredInvitations",
|
|
198
|
+
userId,
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
if (deletedCount > 0) {
|
|
202
|
+
this.logger.info(`[InvitationHandler] Deleted ${deletedCount} expired invitation(s) for user ${userId}`);
|
|
203
|
+
}
|
|
204
|
+
return deletedCount;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Check if user has reached the limit for simultaneously open invitations
|
|
208
|
+
*
|
|
209
|
+
* An invitation is considered "open" if it:
|
|
210
|
+
* - Has not been used (used = false)
|
|
211
|
+
* - Has not expired (expiresAt is null or in the future)
|
|
212
|
+
*
|
|
213
|
+
* This prevents abuse while allowing users to create more invitations
|
|
214
|
+
* by deleting unused ones.
|
|
215
|
+
*
|
|
216
|
+
* Automatically deletes expired invitations before checking the limit.
|
|
217
|
+
*/
|
|
218
|
+
async checkOpenInvitationLimit(userId, maxOpen, env, request) {
|
|
219
|
+
// First, clean up expired invitations
|
|
220
|
+
await this.deleteExpiredInvitations(userId, env, request);
|
|
221
|
+
// Use DatabaseConnectionManager for clear state management
|
|
222
|
+
const { sharedDatabaseConnectionManager } = await Promise.resolve().then(() => __importStar(require("./database-connection-manager")));
|
|
223
|
+
const { withQueryTimeoutAndRetry, QueryTimeoutPresets } = await Promise.resolve().then(() => __importStar(require("./db-query-helper")));
|
|
224
|
+
const { detectRegionSync } = await Promise.resolve().then(() => __importStar(require("./region-detection")));
|
|
225
|
+
// Create connection manager instance
|
|
226
|
+
const dbManager = sharedDatabaseConnectionManager;
|
|
227
|
+
// Use provided request or create a dummy one for region detection
|
|
228
|
+
const regionRequest = request || new Request("https://api.example.com");
|
|
229
|
+
const region = detectRegionSync(regionRequest, env);
|
|
230
|
+
const now = new Date();
|
|
231
|
+
const count = await withQueryTimeoutAndRetry(dbManager, region, env, async (client) => {
|
|
232
|
+
return client.invitation.count({
|
|
233
|
+
where: {
|
|
234
|
+
createdBy: userId,
|
|
235
|
+
used: false, // Not used
|
|
236
|
+
OR: [
|
|
237
|
+
{ expiresAt: null }, // No expiration
|
|
238
|
+
{ expiresAt: { gt: now } }, // Not expired yet
|
|
239
|
+
],
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
}, {
|
|
243
|
+
...QueryTimeoutPresets.USER_FACING,
|
|
244
|
+
defaultValue: 0,
|
|
245
|
+
context: {
|
|
246
|
+
operation: "checkOpenInvitationLimit",
|
|
247
|
+
userId,
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
return {
|
|
251
|
+
allowed: count < maxOpen,
|
|
252
|
+
count,
|
|
253
|
+
limit: maxOpen,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Create a new invitation
|
|
258
|
+
* POST /api/invitations
|
|
259
|
+
*
|
|
260
|
+
* Requires authentication. Users can have up to 10 simultaneously open invitations.
|
|
261
|
+
* An invitation is "open" if it's not used and not expired.
|
|
262
|
+
* Users can delete unused invitations to create new ones.
|
|
263
|
+
* SECURITY: Checks sign-up mode - if disabled, prevents invitation creation.
|
|
264
|
+
*/
|
|
265
|
+
async handleCreateInvitation(request, env) {
|
|
266
|
+
try {
|
|
267
|
+
// Check authentication
|
|
268
|
+
const { Secrets } = await Promise.resolve().then(() => __importStar(require("./secret-resolver")));
|
|
269
|
+
const sessionSecret = Secrets.getSessionSecret(env);
|
|
270
|
+
const session = await this.sessionManager.getSession(request, sessionSecret, env);
|
|
271
|
+
if (!session) {
|
|
272
|
+
return this.securityHeaders.createSecureResponse(JSON.stringify({ error: "Unauthorized" }), { status: 401, headers: { "content-type": "application/json" } });
|
|
273
|
+
}
|
|
274
|
+
// Use DatabaseConnectionManager for all database operations with timeout protection
|
|
275
|
+
const { sharedDatabaseConnectionManager } = await Promise.resolve().then(() => __importStar(require("./database-connection-manager")));
|
|
276
|
+
const { withQueryTimeoutAndRetry, QueryTimeoutPresets } = await Promise.resolve().then(() => __importStar(require("./db-query-helper")));
|
|
277
|
+
const { detectRegionSync } = await Promise.resolve().then(() => __importStar(require("./region-detection")));
|
|
278
|
+
const dbManager = sharedDatabaseConnectionManager;
|
|
279
|
+
const region = detectRegionSync(request, env);
|
|
280
|
+
// SECURITY: Check sign-up mode - if disabled, prevent invitation creation
|
|
281
|
+
const signupToggle = await withQueryTimeoutAndRetry(dbManager, region, env, async (client) => {
|
|
282
|
+
const toggleService = new feature_toggle_service_1.FeatureToggleService(client);
|
|
283
|
+
return await toggleService.getToggle("user_signup_mode");
|
|
284
|
+
}, {
|
|
285
|
+
...QueryTimeoutPresets.CRITICAL, // Critical for security checks
|
|
286
|
+
defaultValue: null, // If timeout, assume open mode (fail open for availability)
|
|
287
|
+
context: {
|
|
288
|
+
operation: "handleCreateInvitation_checkSignupMode",
|
|
289
|
+
userId: session.userId,
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
let signupMode = "open";
|
|
293
|
+
if (signupToggle?.description) {
|
|
294
|
+
const modeMatch = signupToggle.description.match(/user_signup_mode:(open|invitation_only|disabled)/);
|
|
295
|
+
if (modeMatch) {
|
|
296
|
+
signupMode = modeMatch[1];
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
else if (signupToggle?.enabled === false) {
|
|
300
|
+
signupMode = "disabled";
|
|
301
|
+
}
|
|
302
|
+
// CRITICAL: If sign-up is disabled, prevent invitation creation
|
|
303
|
+
if (signupMode === "disabled") {
|
|
304
|
+
return this.securityHeaders.createSecureResponse(JSON.stringify({
|
|
305
|
+
error: "Sign-up disabled",
|
|
306
|
+
message: "User sign-up is currently disabled. Invitations cannot be created.",
|
|
307
|
+
}), { status: 403, headers: { "content-type": "application/json" } });
|
|
308
|
+
}
|
|
309
|
+
// Check if user exists and is not suspended
|
|
310
|
+
const user = await withQueryTimeoutAndRetry(dbManager, region, env, async (client) => {
|
|
311
|
+
return await client.user.findUnique({
|
|
312
|
+
where: { id: session.userId },
|
|
313
|
+
select: { suspended: true },
|
|
314
|
+
});
|
|
315
|
+
}, {
|
|
316
|
+
...QueryTimeoutPresets.CRITICAL, // Critical for security checks
|
|
317
|
+
defaultValue: null, // If timeout, assume user not found
|
|
318
|
+
context: {
|
|
319
|
+
operation: "handleCreateInvitation_checkUser",
|
|
320
|
+
userId: session.userId,
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
if (!user) {
|
|
324
|
+
return this.securityHeaders.createSecureResponse(JSON.stringify({ error: "User not found" }), { status: 404, headers: { "content-type": "application/json" } });
|
|
325
|
+
}
|
|
326
|
+
if (user.suspended) {
|
|
327
|
+
return this.securityHeaders.createSecureResponse(JSON.stringify({ error: "Account suspended" }), { status: 403, headers: { "content-type": "application/json" } });
|
|
328
|
+
}
|
|
329
|
+
// SECURITY: Rate limit invitation creation (10 per hour per user)
|
|
330
|
+
const rateLimitResult = this.rateLimiter.checkRateLimit(request, "/api/invitations", 10, // 10 invitations
|
|
331
|
+
3600, // per hour
|
|
332
|
+
session.userId, undefined, session.userId);
|
|
333
|
+
if (!rateLimitResult.allowed) {
|
|
334
|
+
return this.securityHeaders.createSecureResponse(JSON.stringify({
|
|
335
|
+
error: "Rate limit exceeded",
|
|
336
|
+
message: "Too many invitation creation requests. Please try again later.",
|
|
337
|
+
retryAfter: Math.ceil((rateLimitResult.resetAt - Date.now()) / 1000),
|
|
338
|
+
}), {
|
|
339
|
+
status: 429,
|
|
340
|
+
headers: {
|
|
341
|
+
"content-type": "application/json",
|
|
342
|
+
"Retry-After": Math.ceil((rateLimitResult.resetAt - Date.now()) / 1000).toString(),
|
|
343
|
+
},
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
// Validate request body with Zod schema
|
|
347
|
+
const { validateRequest } = await Promise.resolve().then(() => __importStar(require("./validate-request")));
|
|
348
|
+
const { createInvitationSchema } = await Promise.resolve().then(() => __importStar(require("./schemas")));
|
|
349
|
+
const validation = await validateRequest(request, createInvitationSchema);
|
|
350
|
+
if (!validation.success) {
|
|
351
|
+
return this.securityHeaders.addSecurityHeaders(validation.error);
|
|
352
|
+
}
|
|
353
|
+
const { email, expiresInDays, recaptchaToken } = validation.data;
|
|
354
|
+
// SECURITY: Verify reCAPTCHA token if provided
|
|
355
|
+
if (recaptchaToken && env.RECAPTCHA_SECRET_KEY) {
|
|
356
|
+
const { verifyRecaptcha } = await Promise.resolve().then(() => __importStar(require("./recaptcha")));
|
|
357
|
+
const recaptchaResult = await verifyRecaptcha(recaptchaToken, env.RECAPTCHA_SECRET_KEY);
|
|
358
|
+
if (!recaptchaResult.valid) {
|
|
359
|
+
this.logger.warn("[InvitationHandler] reCAPTCHA verification failed:", recaptchaResult.error);
|
|
360
|
+
return this.securityHeaders.createSecureResponse(JSON.stringify({
|
|
361
|
+
error: "reCAPTCHA verification failed",
|
|
362
|
+
message: recaptchaResult.error ||
|
|
363
|
+
"Please complete the reCAPTCHA verification.",
|
|
364
|
+
}), { status: 400, headers: { "content-type": "application/json" } });
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
else if (!recaptchaToken && env.RECAPTCHA_SECRET_KEY) {
|
|
368
|
+
// If reCAPTCHA is configured but token is missing, require it
|
|
369
|
+
return this.securityHeaders.createSecureResponse(JSON.stringify({
|
|
370
|
+
error: "reCAPTCHA verification required",
|
|
371
|
+
message: "Please complete the reCAPTCHA verification.",
|
|
372
|
+
}), { status: 400, headers: { "content-type": "application/json" } });
|
|
373
|
+
}
|
|
374
|
+
// Email is already validated and sanitized by Zod schema (lowercase, email format)
|
|
375
|
+
// expiresInDays is validated by Zod (1-365 range, default 30)
|
|
376
|
+
// Check limit for simultaneously open invitations
|
|
377
|
+
const maxOpen = 10;
|
|
378
|
+
const limitCheck = await this.checkOpenInvitationLimit(session.userId, maxOpen, env, request);
|
|
379
|
+
if (!limitCheck.allowed) {
|
|
380
|
+
return this.securityHeaders.createSecureResponse(JSON.stringify({
|
|
381
|
+
error: "Invitation limit reached",
|
|
382
|
+
message: `You have ${limitCheck.count} open invitations. The limit is ${limitCheck.limit} simultaneously open invitations. Please delete unused invitations to create new ones.`,
|
|
383
|
+
count: limitCheck.count,
|
|
384
|
+
limit: limitCheck.limit,
|
|
385
|
+
}), { status: 429, headers: { "content-type": "application/json" } });
|
|
386
|
+
}
|
|
387
|
+
if (email) {
|
|
388
|
+
// Create invitation with email
|
|
389
|
+
return await this.createInvitationWithEmail(session.userId, email, expiresInDays, limitCheck, env, request);
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
// Create invitation without email (limit check already done above)
|
|
393
|
+
// No email restriction - create general invitation
|
|
394
|
+
return await this.createInvitationWithEmail(session.userId, null, expiresInDays, limitCheck, env, request);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
catch (error) {
|
|
398
|
+
this.logger.error("[InvitationHandler] Error creating invitation:", error);
|
|
399
|
+
return this.securityHeaders.createSecureResponse(JSON.stringify({ error: "Internal server error" }), { status: 500, headers: { "content-type": "application/json" } });
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Internal helper to create invitation (extracted for code reuse)
|
|
404
|
+
*/
|
|
405
|
+
async createInvitationWithEmail(userId, email, expiresInDays, limitCheck, env, request) {
|
|
406
|
+
try {
|
|
407
|
+
// Use DatabaseConnectionManager for timeout and retry protection
|
|
408
|
+
const { sharedDatabaseConnectionManager } = await Promise.resolve().then(() => __importStar(require("./database-connection-manager")));
|
|
409
|
+
const { withQueryTimeoutAndRetry, QueryTimeoutPresets } = await Promise.resolve().then(() => __importStar(require("./db-query-helper")));
|
|
410
|
+
const { detectRegionSync } = await Promise.resolve().then(() => __importStar(require("./region-detection")));
|
|
411
|
+
const dbManager = sharedDatabaseConnectionManager;
|
|
412
|
+
const regionRequest = request || new Request("https://api.example.com");
|
|
413
|
+
const region = detectRegionSync(regionRequest, env);
|
|
414
|
+
// SECURITY: Generate cryptographically secure unique invitation code
|
|
415
|
+
let code;
|
|
416
|
+
let attempts = 0;
|
|
417
|
+
const maxAttempts = 10;
|
|
418
|
+
// Check for existing code with timeout protection
|
|
419
|
+
do {
|
|
420
|
+
code = this.generateInvitationCode();
|
|
421
|
+
// SECURITY: Prisma uses parameterized queries - SQL injection safe
|
|
422
|
+
const existing = await withQueryTimeoutAndRetry(dbManager, region, env, async (client) => {
|
|
423
|
+
return await client.invitation.findUnique({
|
|
424
|
+
where: { code },
|
|
425
|
+
});
|
|
426
|
+
}, {
|
|
427
|
+
...QueryTimeoutPresets.USER_FACING, // 3s initial, 2s retry
|
|
428
|
+
defaultValue: null, // Return null on timeout (treat as not found)
|
|
429
|
+
context: {
|
|
430
|
+
operation: "createInvitationWithEmail_checkCode",
|
|
431
|
+
userId,
|
|
432
|
+
},
|
|
433
|
+
});
|
|
434
|
+
if (!existing)
|
|
435
|
+
break;
|
|
436
|
+
attempts++;
|
|
437
|
+
} while (attempts < maxAttempts);
|
|
438
|
+
if (attempts >= maxAttempts) {
|
|
439
|
+
// SECURITY: Don't reveal internal details - generic error
|
|
440
|
+
return this.securityHeaders.createSecureResponse(JSON.stringify({
|
|
441
|
+
error: "Failed to create invitation. Please try again.",
|
|
442
|
+
}), { status: 500, headers: { "content-type": "application/json" } });
|
|
443
|
+
}
|
|
444
|
+
// SECURITY: Validate and calculate expiration date
|
|
445
|
+
let expiresAt = null;
|
|
446
|
+
if (expiresInDays) {
|
|
447
|
+
// Already validated above (1-365 days)
|
|
448
|
+
expiresAt = new Date();
|
|
449
|
+
expiresAt.setDate(expiresAt.getDate() + expiresInDays);
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
// Default: 30 days
|
|
453
|
+
expiresAt = new Date();
|
|
454
|
+
expiresAt.setDate(expiresAt.getDate() + 30);
|
|
455
|
+
}
|
|
456
|
+
// SECURITY: Create invitation using Prisma (parameterized queries prevent SQL injection)
|
|
457
|
+
// Use timeout protection for the create operation
|
|
458
|
+
const invitation = await withQueryTimeoutAndRetry(dbManager, region, env, async (client) => {
|
|
459
|
+
return await client.invitation.create({
|
|
460
|
+
data: {
|
|
461
|
+
code,
|
|
462
|
+
createdBy: userId,
|
|
463
|
+
email: email, // Already sanitized (lowercase, validated)
|
|
464
|
+
expiresAt,
|
|
465
|
+
},
|
|
466
|
+
select: {
|
|
467
|
+
id: true,
|
|
468
|
+
code: true,
|
|
469
|
+
createdBy: true,
|
|
470
|
+
email: true,
|
|
471
|
+
used: true,
|
|
472
|
+
usedBy: true,
|
|
473
|
+
usedAt: true,
|
|
474
|
+
scannedAt: true,
|
|
475
|
+
expiresAt: true,
|
|
476
|
+
createdAt: true,
|
|
477
|
+
},
|
|
478
|
+
});
|
|
479
|
+
}, {
|
|
480
|
+
...QueryTimeoutPresets.USER_FACING, // 3s initial, 2s retry
|
|
481
|
+
context: {
|
|
482
|
+
operation: "createInvitationWithEmail_create",
|
|
483
|
+
userId,
|
|
484
|
+
},
|
|
485
|
+
});
|
|
486
|
+
return this.securityHeaders.createSecureResponse(JSON.stringify({
|
|
487
|
+
invitation: {
|
|
488
|
+
id: invitation.id,
|
|
489
|
+
code: invitation.code,
|
|
490
|
+
createdBy: invitation.createdBy,
|
|
491
|
+
email: invitation.email,
|
|
492
|
+
used: invitation.used,
|
|
493
|
+
usedBy: invitation.usedBy,
|
|
494
|
+
usedByEmail: null, // Not applicable for newly created invitation
|
|
495
|
+
usedAt: invitation.usedAt?.toISOString(),
|
|
496
|
+
scannedAt: invitation.scannedAt?.toISOString(),
|
|
497
|
+
expiresAt: invitation.expiresAt?.toISOString(),
|
|
498
|
+
createdAt: invitation.createdAt.toISOString(),
|
|
499
|
+
},
|
|
500
|
+
remainingToday: limitCheck.limit - limitCheck.count - 1,
|
|
501
|
+
}), { status: 201, headers: { "content-type": "application/json" } });
|
|
502
|
+
}
|
|
503
|
+
catch (error) {
|
|
504
|
+
this.logger.error("[InvitationHandler] Error creating invitation:", error);
|
|
505
|
+
// SECURITY: Don't expose internal error details
|
|
506
|
+
return this.securityHeaders.createSecureResponse(JSON.stringify({ error: "Internal server error" }), { status: 500, headers: { "content-type": "application/json" } });
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Get inviter info for friend confirmation
|
|
511
|
+
* GET /api/invitations/inviter-info
|
|
512
|
+
*
|
|
513
|
+
* Returns the inviter information for a newly signed up user.
|
|
514
|
+
*/
|
|
515
|
+
async handleGetInviterInfo(request, env) {
|
|
516
|
+
try {
|
|
517
|
+
// Check authentication
|
|
518
|
+
const { Secrets } = await Promise.resolve().then(() => __importStar(require("./secret-resolver")));
|
|
519
|
+
const sessionSecret = Secrets.getSessionSecret(env);
|
|
520
|
+
const session = await this.sessionManager.getSession(request, sessionSecret, env);
|
|
521
|
+
if (!session) {
|
|
522
|
+
return this.securityHeaders.createSecureResponse(JSON.stringify({ error: "Unauthorized" }), { status: 401, headers: { "content-type": "application/json" } });
|
|
523
|
+
}
|
|
524
|
+
// Get inviter info from KV
|
|
525
|
+
if (!env.INVITATIONS_KV) {
|
|
526
|
+
return this.securityHeaders.createSecureResponse(JSON.stringify({ inviterId: null, inviterEmail: null }), { status: 200, headers: { "content-type": "application/json" } });
|
|
527
|
+
}
|
|
528
|
+
const key = `inviter-info:${session.userId}`;
|
|
529
|
+
const stored = await env.INVITATIONS_KV.get(key);
|
|
530
|
+
if (!stored) {
|
|
531
|
+
return this.securityHeaders.createSecureResponse(JSON.stringify({ inviterId: null, inviterEmail: null }), { status: 200, headers: { "content-type": "application/json" } });
|
|
532
|
+
}
|
|
533
|
+
try {
|
|
534
|
+
const data = JSON.parse(stored);
|
|
535
|
+
// Delete after reading (one-time use)
|
|
536
|
+
await env.INVITATIONS_KV.delete(key);
|
|
537
|
+
return this.securityHeaders.createSecureResponse(JSON.stringify({
|
|
538
|
+
inviterId: data.inviterId,
|
|
539
|
+
inviterEmail: data.inviterEmail,
|
|
540
|
+
}), { status: 200, headers: { "content-type": "application/json" } });
|
|
541
|
+
}
|
|
542
|
+
catch (e) {
|
|
543
|
+
return this.securityHeaders.createSecureResponse(JSON.stringify({ inviterId: null, inviterEmail: null }), { status: 200, headers: { "content-type": "application/json" } });
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
catch (error) {
|
|
547
|
+
this.logger.error("[InvitationHandler] Error getting inviter info:", error);
|
|
548
|
+
return this.securityHeaders.createSecureResponse(JSON.stringify({ error: "Internal server error" }), { status: 500, headers: { "content-type": "application/json" } });
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* List user's invitations
|
|
553
|
+
* GET /api/invitations
|
|
554
|
+
*
|
|
555
|
+
* Returns all invitations created by the authenticated user.
|
|
556
|
+
*/
|
|
557
|
+
async handleListInvitations(request, env) {
|
|
558
|
+
let session = null;
|
|
559
|
+
try {
|
|
560
|
+
// Check authentication
|
|
561
|
+
session = await this.sessionManager.getSession(request, secret_resolver_1.Secrets.getSessionSecret(env), env);
|
|
562
|
+
if (!session) {
|
|
563
|
+
return this.securityHeaders.createSecureResponse(JSON.stringify({ error: "Unauthorized" }), { status: 401, headers: { "content-type": "application/json" } });
|
|
564
|
+
}
|
|
565
|
+
// Use DatabaseConnectionManager for clear state management
|
|
566
|
+
const { sharedDatabaseConnectionManager } = await Promise.resolve().then(() => __importStar(require("./database-connection-manager")));
|
|
567
|
+
const { withQueryTimeoutAndRetry, QueryTimeoutPresets } = await Promise.resolve().then(() => __importStar(require("./db-query-helper")));
|
|
568
|
+
const { detectRegionSync } = await Promise.resolve().then(() => __importStar(require("./region-detection")));
|
|
569
|
+
// Create connection manager instance (manages its own pool state)
|
|
570
|
+
const dbManager = sharedDatabaseConnectionManager;
|
|
571
|
+
// Determine region from request
|
|
572
|
+
const region = detectRegionSync(request, env);
|
|
573
|
+
// Clean up expired invitations before listing
|
|
574
|
+
await this.deleteExpiredInvitations(session.userId, env, request);
|
|
575
|
+
// Execute query with timeout and retry logic
|
|
576
|
+
// queryFn receives a fresh PrismaClient on each call (including retry)
|
|
577
|
+
const invitations = await withQueryTimeoutAndRetry(dbManager, region, env, async (client) => {
|
|
578
|
+
// session is guaranteed to be non-null here (checked above)
|
|
579
|
+
const userId = session.userId;
|
|
580
|
+
const invitations = await client.invitation.findMany({
|
|
581
|
+
where: { createdBy: userId },
|
|
582
|
+
orderBy: { createdAt: "desc" },
|
|
583
|
+
take: 100,
|
|
584
|
+
select: {
|
|
585
|
+
id: true,
|
|
586
|
+
code: true,
|
|
587
|
+
createdBy: true,
|
|
588
|
+
email: true,
|
|
589
|
+
used: true,
|
|
590
|
+
usedBy: true,
|
|
591
|
+
usedAt: true,
|
|
592
|
+
scannedAt: true,
|
|
593
|
+
expiresAt: true,
|
|
594
|
+
createdAt: true,
|
|
595
|
+
},
|
|
596
|
+
});
|
|
597
|
+
// If any invitations are used, fetch user emails separately to avoid join overhead
|
|
598
|
+
const usedInvitationIds = invitations
|
|
599
|
+
.filter((inv) => inv.used && inv.usedBy)
|
|
600
|
+
.map((inv) => inv.usedBy)
|
|
601
|
+
.filter((id, index, self) => self.indexOf(id) === index);
|
|
602
|
+
let userEmailsMap = {};
|
|
603
|
+
if (usedInvitationIds.length > 0) {
|
|
604
|
+
const users = await client.user.findMany({
|
|
605
|
+
where: { id: { in: usedInvitationIds } },
|
|
606
|
+
select: { id: true, email: true },
|
|
607
|
+
});
|
|
608
|
+
userEmailsMap = Object.fromEntries(users.map((user) => [
|
|
609
|
+
user.id,
|
|
610
|
+
user.email,
|
|
611
|
+
]));
|
|
612
|
+
}
|
|
613
|
+
return invitations.map((inv) => ({
|
|
614
|
+
...inv,
|
|
615
|
+
user: inv.usedBy
|
|
616
|
+
? { email: userEmailsMap[inv.usedBy] || null }
|
|
617
|
+
: null,
|
|
618
|
+
}));
|
|
619
|
+
}, {
|
|
620
|
+
...QueryTimeoutPresets.USER_FACING, // 3s initial, 2s retry = 5s max total
|
|
621
|
+
defaultValue: [], // Return empty array on timeout (graceful degradation)
|
|
622
|
+
context: {
|
|
623
|
+
operation: "handleListInvitations",
|
|
624
|
+
userId: session.userId,
|
|
625
|
+
},
|
|
626
|
+
});
|
|
627
|
+
return this.securityHeaders.createSecureResponse(JSON.stringify({
|
|
628
|
+
invitations: invitations.map((inv) => ({
|
|
629
|
+
id: inv.id,
|
|
630
|
+
code: inv.code,
|
|
631
|
+
createdBy: inv.createdBy, // User ID who created the invitation
|
|
632
|
+
email: inv.email, // Email restriction (if invitation was created for specific email)
|
|
633
|
+
used: inv.used,
|
|
634
|
+
usedBy: inv.usedBy,
|
|
635
|
+
usedByEmail: inv.user?.email || null, // Email of user who accepted the invitation
|
|
636
|
+
usedAt: inv.usedAt?.toISOString(),
|
|
637
|
+
scannedAt: inv.scannedAt?.toISOString() || null, // Session token scan timestamp
|
|
638
|
+
expiresAt: inv.expiresAt?.toISOString(),
|
|
639
|
+
createdAt: inv.createdAt.toISOString(),
|
|
640
|
+
})),
|
|
641
|
+
}), { status: 200, headers: { "content-type": "application/json" } });
|
|
642
|
+
}
|
|
643
|
+
catch (error) {
|
|
644
|
+
this.logger.error("[InvitationHandler] Error listing invitations:", {
|
|
645
|
+
error: error.message,
|
|
646
|
+
errorCode: error.code,
|
|
647
|
+
stack: error.stack,
|
|
648
|
+
userId: session?.userId,
|
|
649
|
+
});
|
|
650
|
+
// Provide more specific error messages for timeout scenarios
|
|
651
|
+
let errorMessage = "Internal server error";
|
|
652
|
+
if (error.message?.includes("timeout")) {
|
|
653
|
+
errorMessage = "Request timed out. Please try again in a moment.";
|
|
654
|
+
}
|
|
655
|
+
return this.securityHeaders.createSecureResponse(JSON.stringify({ error: errorMessage }), { status: 500, headers: { "content-type": "application/json" } });
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Delete an invitation
|
|
660
|
+
* DELETE /api/invitations/:id
|
|
661
|
+
*
|
|
662
|
+
* Allows the creator to delete their own invitation.
|
|
663
|
+
* Cannot delete invitations that are currently being used (scanned but not yet completed signup).
|
|
664
|
+
*
|
|
665
|
+
* SECURITY: Only the creator can delete their own invitations.
|
|
666
|
+
*/
|
|
667
|
+
async handleDeleteInvitation(request, env) {
|
|
668
|
+
let release;
|
|
669
|
+
try {
|
|
670
|
+
// Check authentication
|
|
671
|
+
const { Secrets } = await Promise.resolve().then(() => __importStar(require("./secret-resolver")));
|
|
672
|
+
const sessionSecret = Secrets.getSessionSecret(env);
|
|
673
|
+
const session = await this.sessionManager.getSession(request, sessionSecret, env);
|
|
674
|
+
if (!session) {
|
|
675
|
+
return this.securityHeaders.createSecureResponse(JSON.stringify({ error: "Unauthorized" }), { status: 401, headers: { "content-type": "application/json" } });
|
|
676
|
+
}
|
|
677
|
+
// Extract invitation ID from URL path
|
|
678
|
+
const url = new URL(request.url);
|
|
679
|
+
const pathParts = url.pathname.split("/");
|
|
680
|
+
const invitationId = pathParts[pathParts.length - 1];
|
|
681
|
+
if (!invitationId) {
|
|
682
|
+
return this.securityHeaders.createSecureResponse(JSON.stringify({ error: "Invitation ID is required" }), { status: 400, headers: { "content-type": "application/json" } });
|
|
683
|
+
}
|
|
684
|
+
// Get fresh database client (may have been cleared by previous operations)
|
|
685
|
+
let db = (0, db_1.createPrisma)(env);
|
|
686
|
+
release = db.release;
|
|
687
|
+
// Find the invitation and verify ownership with timeout protection
|
|
688
|
+
// Add detailed logging to diagnose where the hang occurs
|
|
689
|
+
this.logger.info("[InvitationHandler] Delete: Starting findUnique query", {
|
|
690
|
+
invitationId,
|
|
691
|
+
userId: session.userId,
|
|
692
|
+
timestamp: new Date().toISOString(),
|
|
693
|
+
});
|
|
694
|
+
const findStartTime = Date.now();
|
|
695
|
+
const findQueryPromise = db.invitation.findUnique({
|
|
696
|
+
where: { id: invitationId },
|
|
697
|
+
select: {
|
|
698
|
+
id: true,
|
|
699
|
+
code: true,
|
|
700
|
+
createdBy: true,
|
|
701
|
+
used: true,
|
|
702
|
+
scannedAt: true,
|
|
703
|
+
},
|
|
704
|
+
});
|
|
705
|
+
const findTimeoutPromise = new Promise((_, reject) => {
|
|
706
|
+
setTimeout(() => {
|
|
707
|
+
const elapsed = Date.now() - findStartTime;
|
|
708
|
+
this.logger.error("[InvitationHandler] Delete: findUnique timeout", {
|
|
709
|
+
invitationId,
|
|
710
|
+
elapsedMs: elapsed,
|
|
711
|
+
timestamp: new Date().toISOString(),
|
|
712
|
+
});
|
|
713
|
+
reject(new Error(`Database query timeout after ${elapsed}ms`));
|
|
714
|
+
}, 5000); // 5 seconds - very aggressive timeout for diagnosis
|
|
715
|
+
});
|
|
716
|
+
let invitation;
|
|
717
|
+
try {
|
|
718
|
+
invitation = await Promise.race([findQueryPromise, findTimeoutPromise]);
|
|
719
|
+
const findElapsed = Date.now() - findStartTime;
|
|
720
|
+
this.logger.info("[InvitationHandler] Delete: findUnique completed", {
|
|
721
|
+
invitationId,
|
|
722
|
+
elapsedMs: findElapsed,
|
|
723
|
+
found: !!invitation,
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
catch (findError) {
|
|
727
|
+
this.logger.error("[InvitationHandler] Delete: findUnique failed", {
|
|
728
|
+
invitationId,
|
|
729
|
+
error: findError.message,
|
|
730
|
+
elapsedMs: Date.now() - findStartTime,
|
|
731
|
+
});
|
|
732
|
+
// If findUnique times out, the connection is likely stale
|
|
733
|
+
// Clear pool cache and retry once with fresh connection
|
|
734
|
+
await release();
|
|
735
|
+
const { sharedDatabaseConnectionManager } = await Promise.resolve().then(() => __importStar(require("./database-connection-manager")));
|
|
736
|
+
this.logger.warn("[InvitationHandler] Delete: Clearing pool cache and retrying...");
|
|
737
|
+
sharedDatabaseConnectionManager.clearPools();
|
|
738
|
+
db = (0, db_1.createPrisma)(env);
|
|
739
|
+
release = db.release;
|
|
740
|
+
// Retry findUnique with fresh connection (very short timeout)
|
|
741
|
+
const retryStartTime = Date.now();
|
|
742
|
+
const retryPromise = db.invitation.findUnique({
|
|
743
|
+
where: { id: invitationId },
|
|
744
|
+
select: {
|
|
745
|
+
id: true,
|
|
746
|
+
code: true,
|
|
747
|
+
createdBy: true,
|
|
748
|
+
used: true,
|
|
749
|
+
scannedAt: true,
|
|
750
|
+
},
|
|
751
|
+
});
|
|
752
|
+
const retryTimeout = new Promise((_, reject) => {
|
|
753
|
+
setTimeout(() => {
|
|
754
|
+
reject(new Error("Retry timeout"));
|
|
755
|
+
}, 3000); // 3 seconds for retry
|
|
756
|
+
});
|
|
757
|
+
try {
|
|
758
|
+
invitation = await Promise.race([retryPromise, retryTimeout]);
|
|
759
|
+
this.logger.info("[InvitationHandler] Delete: Retry succeeded", {
|
|
760
|
+
invitationId,
|
|
761
|
+
elapsedMs: Date.now() - retryStartTime,
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
catch (retryError) {
|
|
765
|
+
this.logger.error("[InvitationHandler] Delete: Retry also failed", {
|
|
766
|
+
invitationId,
|
|
767
|
+
error: retryError.message,
|
|
768
|
+
});
|
|
769
|
+
return this.securityHeaders.createSecureResponse(JSON.stringify({
|
|
770
|
+
error: "Database query timeout",
|
|
771
|
+
message: "Unable to access invitation. Please try again.",
|
|
772
|
+
}), { status: 503, headers: { "content-type": "application/json" } });
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
if (!invitation) {
|
|
776
|
+
return this.securityHeaders.createSecureResponse(JSON.stringify({ error: "Invitation not found" }), { status: 404, headers: { "content-type": "application/json" } });
|
|
777
|
+
}
|
|
778
|
+
// SECURITY: Only the creator can delete their own invitation
|
|
779
|
+
if (invitation.createdBy !== session.userId) {
|
|
780
|
+
return this.securityHeaders.createSecureResponse(JSON.stringify({
|
|
781
|
+
error: "Forbidden: You can only delete your own invitations",
|
|
782
|
+
}), { status: 403, headers: { "content-type": "application/json" } });
|
|
783
|
+
}
|
|
784
|
+
// Check if invitation is currently being used (scanned but not yet completed)
|
|
785
|
+
// We should prevent deletion if someone is in the process of signing up
|
|
786
|
+
if (invitation.scannedAt && !invitation.used) {
|
|
787
|
+
return this.securityHeaders.createSecureResponse(JSON.stringify({
|
|
788
|
+
error: "Cannot delete invitation that is currently being used",
|
|
789
|
+
message: "This invitation has been scanned and is in the process of being used. Please wait for the signup to complete or expire.",
|
|
790
|
+
}), { status: 409, headers: { "content-type": "application/json" } });
|
|
791
|
+
}
|
|
792
|
+
// Delete the invitation with timeout protection
|
|
793
|
+
// Add detailed logging to diagnose where the hang occurs
|
|
794
|
+
this.logger.info("[InvitationHandler] Delete: Starting delete query", {
|
|
795
|
+
invitationId,
|
|
796
|
+
timestamp: new Date().toISOString(),
|
|
797
|
+
});
|
|
798
|
+
const deleteStartTime = Date.now();
|
|
799
|
+
const deleteQueryPromise = db.invitation.delete({
|
|
800
|
+
where: { id: invitationId },
|
|
801
|
+
});
|
|
802
|
+
const deleteTimeoutPromise = new Promise((_, reject) => {
|
|
803
|
+
setTimeout(() => {
|
|
804
|
+
const elapsed = Date.now() - deleteStartTime;
|
|
805
|
+
this.logger.error("[InvitationHandler] Delete: delete timeout", {
|
|
806
|
+
invitationId,
|
|
807
|
+
elapsedMs: elapsed,
|
|
808
|
+
timestamp: new Date().toISOString(),
|
|
809
|
+
});
|
|
810
|
+
reject(new Error(`Database delete timeout after ${elapsed}ms - invitation may be locked by another transaction`));
|
|
811
|
+
}, 5000); // 5 seconds - very aggressive timeout for diagnosis
|
|
812
|
+
});
|
|
813
|
+
try {
|
|
814
|
+
await Promise.race([deleteQueryPromise, deleteTimeoutPromise]);
|
|
815
|
+
const deleteElapsed = Date.now() - deleteStartTime;
|
|
816
|
+
this.logger.info("[InvitationHandler] Delete: delete completed", {
|
|
817
|
+
invitationId,
|
|
818
|
+
elapsedMs: deleteElapsed,
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
catch (error) {
|
|
822
|
+
// Log additional context for debugging lock contention
|
|
823
|
+
const elapsed = Date.now() - deleteStartTime;
|
|
824
|
+
this.logger.error("[InvitationHandler] Delete failed:", {
|
|
825
|
+
invitationId,
|
|
826
|
+
error: error.message,
|
|
827
|
+
errorCode: error.code,
|
|
828
|
+
elapsedMs: elapsed,
|
|
829
|
+
hint: "This may indicate row-level lock contention, stale connection, or database issue",
|
|
830
|
+
});
|
|
831
|
+
throw error;
|
|
832
|
+
}
|
|
833
|
+
// Clean up any related KV entries (session tokens)
|
|
834
|
+
if (env.INVITATIONS_KV) {
|
|
835
|
+
const sessionKey = `invitation-session:${invitation.code}`;
|
|
836
|
+
const codeKey = `invitation_code:${invitation.code}`;
|
|
837
|
+
try {
|
|
838
|
+
await env.INVITATIONS_KV.delete(sessionKey);
|
|
839
|
+
// Also try to delete any email-based entries (we don't know which emails, so we can't clean those up)
|
|
840
|
+
// But the session token entry is the important one
|
|
841
|
+
}
|
|
842
|
+
catch (error) {
|
|
843
|
+
// KV deletion is best-effort, don't fail if it doesn't exist
|
|
844
|
+
this.logger.warn("[InvitationHandler] Failed to delete KV entry:", error);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
return this.securityHeaders.createSecureResponse(JSON.stringify({
|
|
848
|
+
success: true,
|
|
849
|
+
message: "Invitation deleted successfully",
|
|
850
|
+
}), { status: 200, headers: { "content-type": "application/json" } });
|
|
851
|
+
}
|
|
852
|
+
catch (error) {
|
|
853
|
+
this.logger.error("[InvitationHandler] Error deleting invitation:", {
|
|
854
|
+
error: error.message,
|
|
855
|
+
errorCode: error.code,
|
|
856
|
+
stack: error.stack,
|
|
857
|
+
});
|
|
858
|
+
// Provide more specific error messages for timeout scenarios
|
|
859
|
+
let errorMessage = "Internal server error";
|
|
860
|
+
if (error.message?.includes("timeout")) {
|
|
861
|
+
errorMessage =
|
|
862
|
+
"Delete operation timed out. The invitation may be in use by another process. Please try again in a moment.";
|
|
863
|
+
}
|
|
864
|
+
else if (error.code === "P2025") {
|
|
865
|
+
// Prisma error code for record not found
|
|
866
|
+
errorMessage = "Invitation not found or already deleted";
|
|
867
|
+
}
|
|
868
|
+
return this.securityHeaders.createSecureResponse(JSON.stringify({ error: errorMessage }), { status: 500, headers: { "content-type": "application/json" } });
|
|
869
|
+
}
|
|
870
|
+
finally {
|
|
871
|
+
if (release) {
|
|
872
|
+
await release();
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
/**
|
|
877
|
+
* Validate an invitation code
|
|
878
|
+
* POST /api/invitations/validate
|
|
879
|
+
*
|
|
880
|
+
* Public endpoint to validate an invitation code before sign-up.
|
|
881
|
+
* Returns whether the code is valid and can be used.
|
|
882
|
+
*
|
|
883
|
+
* SECURITY: Rate limited to prevent brute force attacks.
|
|
884
|
+
*/
|
|
885
|
+
async handleValidateInvitation(request, env) {
|
|
886
|
+
try {
|
|
887
|
+
// SECURITY: Rate limit validation attempts to prevent brute force (reduced to 10 per hour per IP)
|
|
888
|
+
const rateLimitResult = this.rateLimiter.checkRateLimit(request, "/api/invitations/validate", 10, // 10 validation attempts (reduced from 20)
|
|
889
|
+
3600, // per hour
|
|
890
|
+
undefined, undefined, undefined);
|
|
891
|
+
if (!rateLimitResult.allowed) {
|
|
892
|
+
return this.securityHeaders.createSecureResponse(JSON.stringify({
|
|
893
|
+
valid: false,
|
|
894
|
+
error: "Too many validation attempts. Please try again later.",
|
|
895
|
+
}), {
|
|
896
|
+
status: 200, // Return 200 to not reveal rate limiting
|
|
897
|
+
headers: {
|
|
898
|
+
"content-type": "application/json",
|
|
899
|
+
"Retry-After": Math.ceil((rateLimitResult.resetAt - Date.now()) / 1000).toString(),
|
|
900
|
+
},
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
// Validate request body with Zod schema
|
|
904
|
+
const { validateRequest } = await Promise.resolve().then(() => __importStar(require("./validate-request")));
|
|
905
|
+
const { validateInvitationSchema } = await Promise.resolve().then(() => __importStar(require("./schemas")));
|
|
906
|
+
const validation = await validateRequest(request, validateInvitationSchema);
|
|
907
|
+
if (!validation.success) {
|
|
908
|
+
return this.securityHeaders.addSecurityHeaders(validation.error);
|
|
909
|
+
}
|
|
910
|
+
const { code: sanitizedCode, email } = validation.data;
|
|
911
|
+
// Code is already validated and sanitized by Zod schema (trimmed, uppercase, max 100 chars)
|
|
912
|
+
const db = (0, db_1.createPrisma)(env);
|
|
913
|
+
this.logger.info(`[InvitationHandler] Validating invitation code: ${sanitizedCode}, email: ${email || "none"}`);
|
|
914
|
+
// SECURITY: Check expiration BEFORE claiming to prevent wasted writes
|
|
915
|
+
// First, do a quick check without locking
|
|
916
|
+
const invitationCheck = await db.invitation.findUnique({
|
|
917
|
+
where: { code: sanitizedCode },
|
|
918
|
+
select: {
|
|
919
|
+
id: true,
|
|
920
|
+
used: true,
|
|
921
|
+
expiresAt: true,
|
|
922
|
+
email: true,
|
|
923
|
+
scannedAt: true,
|
|
924
|
+
},
|
|
925
|
+
});
|
|
926
|
+
this.logger.info("[InvitationHandler] Initial invitation check result:", invitationCheck
|
|
927
|
+
? {
|
|
928
|
+
id: invitationCheck.id,
|
|
929
|
+
used: invitationCheck.used,
|
|
930
|
+
scannedAt: invitationCheck.scannedAt,
|
|
931
|
+
expiresAt: invitationCheck.expiresAt,
|
|
932
|
+
hasEmail: !!invitationCheck.email,
|
|
933
|
+
}
|
|
934
|
+
: "not found");
|
|
935
|
+
if (!invitationCheck) {
|
|
936
|
+
this.logger.warn("[InvitationHandler] Invitation not found:", sanitizedCode);
|
|
937
|
+
// SECURITY: Use generic error message to prevent code enumeration
|
|
938
|
+
return this.securityHeaders.createSecureResponse(JSON.stringify({
|
|
939
|
+
valid: false,
|
|
940
|
+
error: "Invalid or unavailable invitation code",
|
|
941
|
+
}), { status: 200, headers: { "content-type": "application/json" } });
|
|
942
|
+
}
|
|
943
|
+
// Check expiration first (before claiming)
|
|
944
|
+
if (invitationCheck.expiresAt && new Date() > invitationCheck.expiresAt) {
|
|
945
|
+
this.logger.warn(`[InvitationHandler] Invitation expired: ${sanitizedCode}, expires at ${invitationCheck.expiresAt}, now ${new Date()}`);
|
|
946
|
+
return this.securityHeaders.createSecureResponse(JSON.stringify({
|
|
947
|
+
valid: false,
|
|
948
|
+
error: "Invalid or unavailable invitation code",
|
|
949
|
+
}), { status: 200, headers: { "content-type": "application/json" } });
|
|
950
|
+
}
|
|
951
|
+
// Check if already used
|
|
952
|
+
if (invitationCheck.used) {
|
|
953
|
+
this.logger.warn("[InvitationHandler] Invitation already used:", sanitizedCode);
|
|
954
|
+
return this.securityHeaders.createSecureResponse(JSON.stringify({
|
|
955
|
+
valid: false,
|
|
956
|
+
error: "Invalid or unavailable invitation code",
|
|
957
|
+
}), { status: 200, headers: { "content-type": "application/json" } });
|
|
958
|
+
}
|
|
959
|
+
// SECURITY: If invitation is email-restricted, email is REQUIRED
|
|
960
|
+
if (invitationCheck.email && !email) {
|
|
961
|
+
return this.securityHeaders.createSecureResponse(JSON.stringify({
|
|
962
|
+
valid: false,
|
|
963
|
+
error: "Email address is required for this invitation code",
|
|
964
|
+
emailRequired: true,
|
|
965
|
+
}), { status: 200, headers: { "content-type": "application/json" } });
|
|
966
|
+
}
|
|
967
|
+
// SECURITY: For email-restricted invitations, email is REQUIRED and must match
|
|
968
|
+
// For open invitations, email is NOT collected during validation (only during signup)
|
|
969
|
+
let sanitizedEmail = undefined;
|
|
970
|
+
if (invitationCheck.email) {
|
|
971
|
+
// Email-restricted invitation - email is required
|
|
972
|
+
if (!email) {
|
|
973
|
+
return this.securityHeaders.createSecureResponse(JSON.stringify({
|
|
974
|
+
valid: false,
|
|
975
|
+
error: "Email address is required for this invitation code",
|
|
976
|
+
emailRequired: true,
|
|
977
|
+
}), { status: 200, headers: { "content-type": "application/json" } });
|
|
978
|
+
}
|
|
979
|
+
const trimmedEmail = email.trim().toLowerCase();
|
|
980
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
981
|
+
if (emailRegex.test(trimmedEmail) && trimmedEmail.length <= 254) {
|
|
982
|
+
sanitizedEmail = trimmedEmail;
|
|
983
|
+
}
|
|
984
|
+
else {
|
|
985
|
+
return this.securityHeaders.createSecureResponse(JSON.stringify({
|
|
986
|
+
valid: false,
|
|
987
|
+
error: "Invalid email format",
|
|
988
|
+
}), { status: 200, headers: { "content-type": "application/json" } });
|
|
989
|
+
}
|
|
990
|
+
// SECURITY: Check if email matches restriction (case-insensitive comparison)
|
|
991
|
+
if (invitationCheck.email.toLowerCase() !== sanitizedEmail) {
|
|
992
|
+
// SECURITY: Don't reveal the restricted email to prevent enumeration
|
|
993
|
+
return this.securityHeaders.createSecureResponse(JSON.stringify({
|
|
994
|
+
valid: false,
|
|
995
|
+
error: "Invalid or unavailable invitation code",
|
|
996
|
+
}), { status: 200, headers: { "content-type": "application/json" } });
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
// For open invitations (no email restriction), we don't collect or store email during validation
|
|
1000
|
+
// Email will be collected and validated during actual signup
|
|
1001
|
+
// Check if already scanned - if so, return existing token if available
|
|
1002
|
+
let sessionToken;
|
|
1003
|
+
if (invitationCheck.scannedAt) {
|
|
1004
|
+
this.logger.info("[InvitationHandler] Invitation already scanned at:", invitationCheck.scannedAt);
|
|
1005
|
+
// Code was already scanned - check if we have a valid session token
|
|
1006
|
+
if (env.INVITATIONS_KV) {
|
|
1007
|
+
const key = `invitation-session:${sanitizedCode}`;
|
|
1008
|
+
const stored = await env.INVITATIONS_KV.get(key);
|
|
1009
|
+
this.logger.info(`[InvitationHandler] Checking KV for existing token, key: ${key}, found: ${!!stored}`);
|
|
1010
|
+
if (stored) {
|
|
1011
|
+
try {
|
|
1012
|
+
const data = JSON.parse(stored);
|
|
1013
|
+
// Return existing token if valid
|
|
1014
|
+
if (data.token &&
|
|
1015
|
+
(!data.expiresAt || new Date(data.expiresAt) > new Date())) {
|
|
1016
|
+
this.logger.info("[InvitationHandler] Returning existing valid token");
|
|
1017
|
+
return this.securityHeaders.createSecureResponse(JSON.stringify({
|
|
1018
|
+
valid: true,
|
|
1019
|
+
token: data.token,
|
|
1020
|
+
emailRestricted: !!invitationCheck.email,
|
|
1021
|
+
requiredEmail: invitationCheck.email || null,
|
|
1022
|
+
}), {
|
|
1023
|
+
status: 200,
|
|
1024
|
+
headers: { "content-type": "application/json" },
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
else {
|
|
1028
|
+
this.logger.warn("[InvitationHandler] Stored token expired or invalid");
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
catch (e) {
|
|
1032
|
+
this.logger.error("[InvitationHandler] Error parsing stored token data:", e);
|
|
1033
|
+
// Invalid stored data, continue to generate new token
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
// If no valid token found, code was scanned but token expired - reject
|
|
1038
|
+
this.logger.warn("[InvitationHandler] Invitation already scanned but no valid token found, rejecting");
|
|
1039
|
+
return this.securityHeaders.createSecureResponse(JSON.stringify({
|
|
1040
|
+
valid: false,
|
|
1041
|
+
error: "Invalid or unavailable invitation code",
|
|
1042
|
+
}), { status: 200, headers: { "content-type": "application/json" } });
|
|
1043
|
+
}
|
|
1044
|
+
this.logger.info("[InvitationHandler] Invitation not yet scanned, proceeding to claim");
|
|
1045
|
+
// First time scanning - atomically claim the invitation code with SELECT FOR UPDATE
|
|
1046
|
+
sessionToken = this.generateSessionToken();
|
|
1047
|
+
const now = new Date();
|
|
1048
|
+
// Use transaction to prevent race conditions
|
|
1049
|
+
// Note: Using findUnique + update instead of $queryRaw for better compatibility
|
|
1050
|
+
const claimed = await db.$transaction(async (tx) => {
|
|
1051
|
+
// Find the invitation by code
|
|
1052
|
+
const inv = await tx.invitation.findUnique({
|
|
1053
|
+
where: { code: sanitizedCode },
|
|
1054
|
+
select: {
|
|
1055
|
+
id: true,
|
|
1056
|
+
scannedAt: true,
|
|
1057
|
+
scannedBy: true,
|
|
1058
|
+
used: true,
|
|
1059
|
+
expiresAt: true,
|
|
1060
|
+
email: true,
|
|
1061
|
+
},
|
|
1062
|
+
});
|
|
1063
|
+
if (!inv) {
|
|
1064
|
+
this.logger.warn("[InvitationHandler] Invitation not found in transaction:", sanitizedCode);
|
|
1065
|
+
return { success: false, reason: "not_found" };
|
|
1066
|
+
}
|
|
1067
|
+
// Check all conditions
|
|
1068
|
+
if (inv.used) {
|
|
1069
|
+
this.logger.warn("[InvitationHandler] Invitation already used:", sanitizedCode);
|
|
1070
|
+
return { success: false, reason: "already_used" };
|
|
1071
|
+
}
|
|
1072
|
+
if (inv.scannedAt) {
|
|
1073
|
+
this.logger.warn(`[InvitationHandler] Invitation already scanned: ${sanitizedCode}, at ${inv.scannedAt}`);
|
|
1074
|
+
return { success: false, reason: "already_scanned" };
|
|
1075
|
+
}
|
|
1076
|
+
if (inv.expiresAt && new Date() > inv.expiresAt) {
|
|
1077
|
+
this.logger.warn(`[InvitationHandler] Invitation expired: ${sanitizedCode}, expires at ${inv.expiresAt}`);
|
|
1078
|
+
return { success: false, reason: "expired" };
|
|
1079
|
+
}
|
|
1080
|
+
// Claim it atomically - update will fail if record doesn't exist
|
|
1081
|
+
// The transaction provides atomicity, and we've already checked conditions above
|
|
1082
|
+
try {
|
|
1083
|
+
const updated = await tx.invitation.update({
|
|
1084
|
+
where: { code: sanitizedCode },
|
|
1085
|
+
data: {
|
|
1086
|
+
scannedAt: now,
|
|
1087
|
+
scannedBy: sessionToken, // Store token as scannedBy for reference
|
|
1088
|
+
},
|
|
1089
|
+
});
|
|
1090
|
+
this.logger.info(`[InvitationHandler] Successfully claimed invitation: ${sanitizedCode}, scannedAt: ${updated.scannedAt}`);
|
|
1091
|
+
return { success: true };
|
|
1092
|
+
}
|
|
1093
|
+
catch (error) {
|
|
1094
|
+
// If update failed (e.g., record was deleted or already updated), return failure
|
|
1095
|
+
// This handles race conditions where another request claimed it first
|
|
1096
|
+
this.logger.error(`[InvitationHandler] Failed to update invitation in transaction: ${sanitizedCode}, error: ${error.message}${error.code ? `, code: ${error.code}` : ""}`);
|
|
1097
|
+
return {
|
|
1098
|
+
success: false,
|
|
1099
|
+
reason: "update_failed",
|
|
1100
|
+
error: error.message,
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
});
|
|
1104
|
+
if (!claimed.success) {
|
|
1105
|
+
this.logger.error(`[InvitationHandler] Failed to claim invitation: ${sanitizedCode}, reason: ${claimed.reason || "unknown"}${claimed.error ? `, error: ${claimed.error}` : ""}`);
|
|
1106
|
+
return this.securityHeaders.createSecureResponse(JSON.stringify({
|
|
1107
|
+
valid: false,
|
|
1108
|
+
error: "Invalid or unavailable invitation code",
|
|
1109
|
+
}), { status: 200, headers: { "content-type": "application/json" } });
|
|
1110
|
+
}
|
|
1111
|
+
// Store session token in KV for validation during signup
|
|
1112
|
+
await this.storeSessionToken(sanitizedCode, sessionToken, sanitizedEmail, env);
|
|
1113
|
+
return this.securityHeaders.createSecureResponse(JSON.stringify({
|
|
1114
|
+
valid: true,
|
|
1115
|
+
token: sessionToken,
|
|
1116
|
+
emailRestricted: !!invitationCheck.email,
|
|
1117
|
+
requiredEmail: invitationCheck.email || null,
|
|
1118
|
+
}), { status: 200, headers: { "content-type": "application/json" } });
|
|
1119
|
+
}
|
|
1120
|
+
catch (error) {
|
|
1121
|
+
this.logger.error("[InvitationHandler] Error validating invitation:", error);
|
|
1122
|
+
return this.securityHeaders.createSecureResponse(JSON.stringify({ error: "Internal server error" }), { status: 500, headers: { "content-type": "application/json" } });
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
/**
|
|
1126
|
+
* Mark an invitation as used
|
|
1127
|
+
* Called internally when a user signs up with an invitation code
|
|
1128
|
+
*
|
|
1129
|
+
* SECURITY: Uses database transaction to prevent race conditions (multiple signups with same code)
|
|
1130
|
+
*
|
|
1131
|
+
* Also automatically creates a friendship between the inviter and the new user.
|
|
1132
|
+
*/
|
|
1133
|
+
async markInvitationAsUsed(code, userId, token, email, env) {
|
|
1134
|
+
try {
|
|
1135
|
+
const db = (0, db_1.createPrisma)(env);
|
|
1136
|
+
// SECURITY: Sanitize code input
|
|
1137
|
+
const sanitizedCode = code.trim().toUpperCase();
|
|
1138
|
+
if (!/^[A-Z0-9]+$/.test(sanitizedCode)) {
|
|
1139
|
+
return { success: false, error: "Invalid invitation code format" };
|
|
1140
|
+
}
|
|
1141
|
+
// SECURITY: Validate session token if provided
|
|
1142
|
+
let tokenValidation = null;
|
|
1143
|
+
if (token) {
|
|
1144
|
+
tokenValidation = await this.validateSessionToken(sanitizedCode, token, env);
|
|
1145
|
+
if (!tokenValidation.valid) {
|
|
1146
|
+
return {
|
|
1147
|
+
success: false,
|
|
1148
|
+
error: "Invalid or expired invitation session. Please scan the QR code again.",
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
// Note: Email match enforcement is handled below after we check if invitation is email-restricted
|
|
1152
|
+
// For open invitations (no email restriction), we allow email correction to handle typos
|
|
1153
|
+
}
|
|
1154
|
+
else {
|
|
1155
|
+
// SECURITY: If no token provided, check if code was scanned (backward compatibility)
|
|
1156
|
+
// This allows for email-based invitations that don't go through QR scanning
|
|
1157
|
+
// But we should still validate that the code exists and is valid
|
|
1158
|
+
}
|
|
1159
|
+
// SECURITY: Use transaction to prevent race conditions
|
|
1160
|
+
// Note: Using findUnique + update instead of $queryRaw for better compatibility
|
|
1161
|
+
const result = await db.$transaction(async (tx) => {
|
|
1162
|
+
// Find the invitation by code
|
|
1163
|
+
const invitation = await tx.invitation.findUnique({
|
|
1164
|
+
where: { code: sanitizedCode },
|
|
1165
|
+
select: {
|
|
1166
|
+
id: true,
|
|
1167
|
+
createdBy: true,
|
|
1168
|
+
expiresAt: true,
|
|
1169
|
+
used: true,
|
|
1170
|
+
email: true,
|
|
1171
|
+
},
|
|
1172
|
+
});
|
|
1173
|
+
if (!invitation) {
|
|
1174
|
+
return { success: false, error: "Invalid invitation code" };
|
|
1175
|
+
}
|
|
1176
|
+
if (invitation.used) {
|
|
1177
|
+
return {
|
|
1178
|
+
success: false,
|
|
1179
|
+
error: "Invitation code has already been used",
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
if (invitation.expiresAt && new Date() > invitation.expiresAt) {
|
|
1183
|
+
return { success: false, error: "Invitation code has expired" };
|
|
1184
|
+
}
|
|
1185
|
+
// SECURITY: Check email restriction if invitation is restricted
|
|
1186
|
+
// For email-restricted invitations, enforce exact match
|
|
1187
|
+
// For open invitations (no email restriction), allow email correction to handle typos
|
|
1188
|
+
if (invitation.email &&
|
|
1189
|
+
invitation.email.toLowerCase() !== email.toLowerCase()) {
|
|
1190
|
+
return {
|
|
1191
|
+
success: false,
|
|
1192
|
+
error: "This invitation code is restricted to a different email address",
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1195
|
+
// For open invitations, email is not stored in the session token during validation
|
|
1196
|
+
// The email provided during signup is the only email we use
|
|
1197
|
+
// No email validation needed for open invitations - they can use any valid email
|
|
1198
|
+
// Get creator information
|
|
1199
|
+
const creator = await tx.user.findUnique({
|
|
1200
|
+
where: { id: invitation.createdBy },
|
|
1201
|
+
select: {
|
|
1202
|
+
id: true,
|
|
1203
|
+
email: true,
|
|
1204
|
+
},
|
|
1205
|
+
});
|
|
1206
|
+
if (!creator) {
|
|
1207
|
+
return { success: false, error: "Invitation creator not found" };
|
|
1208
|
+
}
|
|
1209
|
+
// SECURITY: Atomic update - marks as used and links to user in single operation
|
|
1210
|
+
// The transaction provides atomicity, and we've already checked conditions above
|
|
1211
|
+
try {
|
|
1212
|
+
this.logger.info("[InvitationHandler] Marking invitation as used:", {
|
|
1213
|
+
invitationId: invitation.id,
|
|
1214
|
+
code: sanitizedCode,
|
|
1215
|
+
userId: userId,
|
|
1216
|
+
email: email,
|
|
1217
|
+
});
|
|
1218
|
+
const updated = await tx.invitation.update({
|
|
1219
|
+
where: { id: invitation.id },
|
|
1220
|
+
data: {
|
|
1221
|
+
used: true,
|
|
1222
|
+
usedBy: userId,
|
|
1223
|
+
usedAt: new Date(),
|
|
1224
|
+
},
|
|
1225
|
+
});
|
|
1226
|
+
this.logger.info("[InvitationHandler] Successfully marked invitation as used:", {
|
|
1227
|
+
invitationId: updated.id,
|
|
1228
|
+
used: updated.used,
|
|
1229
|
+
usedBy: updated.usedBy,
|
|
1230
|
+
usedAt: updated.usedAt,
|
|
1231
|
+
});
|
|
1232
|
+
return {
|
|
1233
|
+
success: true,
|
|
1234
|
+
inviterId: creator.id,
|
|
1235
|
+
inviterEmail: creator.email,
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
catch (error) {
|
|
1239
|
+
// If update failed (e.g., record was deleted or already updated), return failure
|
|
1240
|
+
// This handles race conditions where another request used it first
|
|
1241
|
+
this.logger.error("[InvitationHandler] Failed to mark invitation as used:", {
|
|
1242
|
+
invitationId: invitation.id,
|
|
1243
|
+
code: sanitizedCode,
|
|
1244
|
+
error: error.message,
|
|
1245
|
+
errorCode: error.code,
|
|
1246
|
+
});
|
|
1247
|
+
return {
|
|
1248
|
+
success: false,
|
|
1249
|
+
error: "Invitation code has already been used",
|
|
1250
|
+
};
|
|
1251
|
+
}
|
|
1252
|
+
});
|
|
1253
|
+
if (!result.success) {
|
|
1254
|
+
return result;
|
|
1255
|
+
}
|
|
1256
|
+
// Don't create friendship automatically - return inviter info for frontend to prompt user
|
|
1257
|
+
// Friendship will be created after user confirms
|
|
1258
|
+
return result;
|
|
1259
|
+
}
|
|
1260
|
+
catch (error) {
|
|
1261
|
+
this.logger.error("[InvitationHandler] Error marking invitation as used:", error);
|
|
1262
|
+
return { success: false, error: "Internal server error" };
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
/**
|
|
1266
|
+
* Create a friendship between the inviter and the new user
|
|
1267
|
+
* This is called automatically when an invitation is used
|
|
1268
|
+
*/
|
|
1269
|
+
async createFriendshipFromInvitation(inviterId, inviterEmail, newUserId, newUserEmail, env) {
|
|
1270
|
+
if (!env.FRIENDS_KV) {
|
|
1271
|
+
this.logger.warn("[InvitationHandler] FRIENDS_KV not available, skipping friendship creation");
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
1274
|
+
try {
|
|
1275
|
+
// Generate friendship ID
|
|
1276
|
+
const friendshipId = `friend_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
1277
|
+
const now = new Date().toISOString();
|
|
1278
|
+
// Create friendship object
|
|
1279
|
+
const friendship = {
|
|
1280
|
+
id: friendshipId,
|
|
1281
|
+
requesterId: newUserId,
|
|
1282
|
+
requesterEmail: newUserEmail,
|
|
1283
|
+
addresseeId: inviterId,
|
|
1284
|
+
addresseeEmail: inviterEmail,
|
|
1285
|
+
status: "ACCEPTED", // Automatically accepted since invitation was used
|
|
1286
|
+
createdAt: now,
|
|
1287
|
+
acceptedAt: now,
|
|
1288
|
+
};
|
|
1289
|
+
// Store bidirectional friendships in KV
|
|
1290
|
+
const requesterKey = `friendship:${newUserId}:${inviterId}`;
|
|
1291
|
+
const addresseeKey = `friendship:${inviterId}:${newUserId}`;
|
|
1292
|
+
await env.FRIENDS_KV.put(requesterKey, JSON.stringify(friendship));
|
|
1293
|
+
await env.FRIENDS_KV.put(addresseeKey, JSON.stringify({
|
|
1294
|
+
...friendship,
|
|
1295
|
+
requesterId: inviterId,
|
|
1296
|
+
requesterEmail: inviterEmail,
|
|
1297
|
+
addresseeId: newUserId,
|
|
1298
|
+
addresseeEmail: newUserEmail,
|
|
1299
|
+
}));
|
|
1300
|
+
// Add to friends lists (with MAX_FRIENDS check)
|
|
1301
|
+
await this.addToFriendsList(newUserId, inviterId, env);
|
|
1302
|
+
await this.addToFriendsList(inviterId, newUserId, env);
|
|
1303
|
+
this.logger.info(`[InvitationHandler] Created friendship between ${newUserId} and ${inviterId} from invitation`);
|
|
1304
|
+
}
|
|
1305
|
+
catch (error) {
|
|
1306
|
+
// Log error but don't throw - friendship creation failure shouldn't prevent invitation from being marked as used
|
|
1307
|
+
this.logger.error("[InvitationHandler] Error creating friendship from invitation:", error);
|
|
1308
|
+
if (error.message?.includes("Maximum number of friends")) {
|
|
1309
|
+
this.logger.warn(`[InvitationHandler] Could not create friendship: ${error.message}`);
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
/**
|
|
1314
|
+
* Add friend to user's friends list (helper for maintaining list)
|
|
1315
|
+
* Throws error if MAX_FRIENDS limit is reached
|
|
1316
|
+
*/
|
|
1317
|
+
async addToFriendsList(userId, friendId, env) {
|
|
1318
|
+
if (!env.FRIENDS_KV)
|
|
1319
|
+
return;
|
|
1320
|
+
const MAX_FRIENDS = 500; // Same limit as FriendsHandler
|
|
1321
|
+
const friendsListKey = `friends-list:${userId}`;
|
|
1322
|
+
const friendsListStr = await env.FRIENDS_KV.get(friendsListKey);
|
|
1323
|
+
const friendsList = friendsListStr ? JSON.parse(friendsListStr) : [];
|
|
1324
|
+
if (!friendsList.includes(friendId)) {
|
|
1325
|
+
// Check if user has reached the maximum number of friends
|
|
1326
|
+
if (friendsList.length >= MAX_FRIENDS) {
|
|
1327
|
+
throw new Error(`Maximum number of friends (${MAX_FRIENDS}) reached`);
|
|
1328
|
+
}
|
|
1329
|
+
friendsList.push(friendId);
|
|
1330
|
+
await env.FRIENDS_KV.put(friendsListKey, JSON.stringify(friendsList));
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
exports.InvitationHandler = InvitationHandler;
|
|
1335
|
+
//# sourceMappingURL=invitation-handler.js.map
|