@friggframework/core 2.0.0-next.12 → 2.0.0-next.120
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/.eslintrc.json +18 -1
- package/CLAUDE.md +830 -0
- package/README.md +996 -50
- package/admin-scripts/index.js +52 -0
- package/admin-scripts/repositories/admin-script-execution-repository-documentdb.js +21 -0
- package/admin-scripts/repositories/admin-script-execution-repository-factory.js +51 -0
- package/admin-scripts/repositories/admin-script-execution-repository-interface.js +171 -0
- package/admin-scripts/repositories/admin-script-execution-repository-mongo.js +238 -0
- package/admin-scripts/repositories/admin-script-execution-repository-postgres.js +278 -0
- package/admin-scripts/repositories/script-schedule-repository-documentdb.js +21 -0
- package/admin-scripts/repositories/script-schedule-repository-factory.js +51 -0
- package/admin-scripts/repositories/script-schedule-repository-interface.js +108 -0
- package/admin-scripts/repositories/script-schedule-repository-mongo.js +179 -0
- package/admin-scripts/repositories/script-schedule-repository-postgres.js +210 -0
- package/application/commands/README.md +451 -0
- package/application/commands/admin-script-commands.js +359 -0
- package/application/commands/credential-commands.js +262 -0
- package/application/commands/entity-commands.js +294 -0
- package/application/commands/integration-commands.js +419 -0
- package/application/commands/integration-mapping-commands.js +25 -0
- package/application/commands/process-commands.js +135 -0
- package/application/commands/report-commands.js +188 -0
- package/application/commands/scheduler-commands.js +263 -0
- package/application/commands/usage-commands.js +56 -0
- package/application/commands/user-commands.js +312 -0
- package/application/index.js +83 -0
- package/artifacts/repositories/artifact-repository-factory.js +19 -0
- package/artifacts/repositories/artifact-repository-interface.js +27 -0
- package/artifacts/repositories/artifact-repository-local.js +42 -0
- package/artifacts/repositories/artifact-repository-s3.js +61 -0
- package/assertions/index.js +0 -3
- package/core/CLAUDE.md +715 -0
- package/core/Worker.js +55 -24
- package/core/create-handler.js +121 -38
- package/core/index.js +8 -1
- package/core/invocation-scope.js +225 -0
- package/core/parameters-to-env.js +257 -0
- package/core/secrets-to-env.js +7 -6
- package/core/ssm-preload.mjs +34 -0
- package/credential/repositories/credential-active-type.js +32 -0
- package/credential/repositories/credential-repository-documentdb.js +355 -0
- package/credential/repositories/credential-repository-factory.js +54 -0
- package/credential/repositories/credential-repository-interface.js +113 -0
- package/credential/repositories/credential-repository-mongo.js +294 -0
- package/credential/repositories/credential-repository-postgres.js +312 -0
- package/credential/repositories/credential-repository.js +300 -0
- package/credential/use-cases/get-credential-for-user.js +25 -0
- package/credential/use-cases/update-authentication-status.js +15 -0
- package/database/MONGODB_TRANSACTION_FIX.md +198 -0
- package/database/adapters/lambda-invoker.js +97 -0
- package/database/config.js +147 -0
- package/database/documentdb-encryption-service.js +334 -0
- package/database/documentdb-utils.js +196 -0
- package/database/encryption/README.md +863 -0
- package/database/encryption/documentdb-encryption-service.md +3575 -0
- package/database/encryption/encryption-schema-registry.js +404 -0
- package/database/encryption/field-encryption-service.js +254 -0
- package/database/encryption/integration-mapping-encryption.js +97 -0
- package/database/encryption/logger.js +82 -0
- package/database/encryption/prisma-encryption-extension.js +241 -0
- package/database/index.js +21 -21
- package/database/prisma.js +182 -0
- package/database/repositories/health-check-repository-documentdb.js +138 -0
- package/database/repositories/health-check-repository-factory.js +48 -0
- package/database/repositories/health-check-repository-interface.js +82 -0
- package/database/repositories/health-check-repository-mongodb.js +89 -0
- package/database/repositories/health-check-repository-postgres.js +82 -0
- package/database/repositories/migration-status-repository-s3.js +137 -0
- package/database/use-cases/check-database-health-use-case.js +29 -0
- package/database/use-cases/check-database-state-use-case.js +81 -0
- package/database/use-cases/check-encryption-health-use-case.js +83 -0
- package/database/use-cases/get-database-state-via-worker-use-case.js +61 -0
- package/database/use-cases/get-migration-status-use-case.js +93 -0
- package/database/use-cases/resolve-migration-via-worker-use-case.js +49 -0
- package/database/use-cases/run-database-migration-use-case.js +139 -0
- package/database/use-cases/test-encryption-use-case.js +253 -0
- package/database/use-cases/trigger-database-migration-use-case.js +157 -0
- package/database/utils/mongodb-collection-utils.js +94 -0
- package/database/utils/mongodb-schema-init.js +108 -0
- package/database/utils/prisma-runner.js +491 -0
- package/database/utils/prisma-schema-parser.js +182 -0
- package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +517 -0
- package/encrypt/Cryptor.js +34 -168
- package/encrypt/index.js +1 -2
- package/errors/client-safe-error.js +26 -0
- package/errors/fetch-error.js +59 -57
- package/errors/index.js +6 -0
- package/errors/mapping-already-exists-error.js +14 -0
- package/generated/prisma-mongodb/client.d.ts +1 -0
- package/generated/prisma-mongodb/client.js +5 -0
- package/generated/prisma-mongodb/default.d.ts +1 -0
- package/generated/prisma-mongodb/default.js +5 -0
- package/generated/prisma-mongodb/edge.d.ts +1 -0
- package/generated/prisma-mongodb/edge.js +386 -0
- package/generated/prisma-mongodb/index-browser.js +368 -0
- package/generated/prisma-mongodb/index.d.ts +27065 -0
- package/generated/prisma-mongodb/index.js +411 -0
- package/generated/prisma-mongodb/libquery_engine-debian-openssl-3.0.x.so.node +0 -0
- package/generated/prisma-mongodb/libquery_engine-rhel-openssl-3.0.x.so.node +0 -0
- package/generated/prisma-mongodb/package.json +183 -0
- package/generated/prisma-mongodb/runtime/edge-esm.js +35 -0
- package/generated/prisma-mongodb/runtime/edge.js +35 -0
- package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
- package/generated/prisma-mongodb/runtime/index-browser.js +17 -0
- package/generated/prisma-mongodb/runtime/library.d.ts +3982 -0
- package/generated/prisma-mongodb/runtime/library.js +147 -0
- package/generated/prisma-mongodb/runtime/react-native.js +84 -0
- package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +85 -0
- package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +38 -0
- package/generated/prisma-mongodb/schema.prisma +455 -0
- package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +5 -0
- package/generated/prisma-mongodb/wasm-worker-loader.mjs +5 -0
- package/generated/prisma-mongodb/wasm.d.ts +1 -0
- package/generated/prisma-mongodb/wasm.js +393 -0
- package/generated/prisma-postgresql/client.d.ts +1 -0
- package/generated/prisma-postgresql/client.js +5 -0
- package/generated/prisma-postgresql/default.d.ts +1 -0
- package/generated/prisma-postgresql/default.js +5 -0
- package/generated/prisma-postgresql/edge.d.ts +1 -0
- package/generated/prisma-postgresql/edge.js +407 -0
- package/generated/prisma-postgresql/index-browser.js +389 -0
- package/generated/prisma-postgresql/index.d.ts +29904 -0
- package/generated/prisma-postgresql/index.js +432 -0
- package/generated/prisma-postgresql/libquery_engine-debian-openssl-3.0.x.so.node +0 -0
- package/generated/prisma-postgresql/libquery_engine-rhel-openssl-3.0.x.so.node +0 -0
- package/generated/prisma-postgresql/package.json +183 -0
- package/generated/prisma-postgresql/query_engine_bg.js +2 -0
- package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
- package/generated/prisma-postgresql/runtime/edge-esm.js +35 -0
- package/generated/prisma-postgresql/runtime/edge.js +35 -0
- package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
- package/generated/prisma-postgresql/runtime/index-browser.js +17 -0
- package/generated/prisma-postgresql/runtime/library.d.ts +3982 -0
- package/generated/prisma-postgresql/runtime/library.js +147 -0
- package/generated/prisma-postgresql/runtime/react-native.js +84 -0
- package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +85 -0
- package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +38 -0
- package/generated/prisma-postgresql/schema.prisma +436 -0
- package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +5 -0
- package/generated/prisma-postgresql/wasm-worker-loader.mjs +5 -0
- package/generated/prisma-postgresql/wasm.d.ts +1 -0
- package/generated/prisma-postgresql/wasm.js +414 -0
- package/handlers/WEBHOOKS.md +682 -0
- package/handlers/app-definition-loader.js +103 -0
- package/handlers/app-handler-helpers.js +20 -6
- package/handlers/backend-utils.js +272 -46
- package/handlers/database-migration-handler.js +236 -0
- package/handlers/integration-event-dispatcher.js +68 -0
- package/handlers/middleware/admin-auth.js +73 -0
- package/handlers/routers/HEALTHCHECK.md +342 -0
- package/handlers/routers/auth.js +4 -15
- package/handlers/routers/db-migration.handler.js +29 -0
- package/handlers/routers/db-migration.js +329 -0
- package/handlers/routers/health.js +518 -0
- package/handlers/routers/integration-defined-routers.js +85 -10
- package/handlers/routers/integration-webhook-routers.js +87 -0
- package/handlers/routers/user.js +27 -5
- package/handlers/routers/websocket.js +31 -9
- package/handlers/use-cases/check-external-apis-health-use-case.js +81 -0
- package/handlers/use-cases/check-integrations-health-use-case.js +44 -0
- package/handlers/workers/db-migration.js +438 -0
- package/handlers/workers/dlq-processor.js +89 -0
- package/handlers/workers/integration-defined-workers.js +13 -7
- package/index.js +123 -47
- package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +184 -0
- package/infrastructure/scheduler/index.js +33 -0
- package/infrastructure/scheduler/mock-scheduler-adapter.js +143 -0
- package/infrastructure/scheduler/scheduler-service-factory.js +73 -0
- package/infrastructure/scheduler/scheduler-service-interface.js +47 -0
- package/integrations/EXTENSIONS.md +240 -0
- package/integrations/WEBHOOK-QUICKSTART.md +151 -0
- package/integrations/extension.js +254 -0
- package/integrations/index.js +20 -10
- package/integrations/integration-base.js +777 -74
- package/integrations/integration-router.js +399 -180
- package/integrations/options.js +1 -1
- package/integrations/repositories/config-patch-shared.js +43 -0
- package/integrations/repositories/integration-mapping-query-pipeline.js +152 -0
- package/integrations/repositories/integration-mapping-query.js +193 -0
- package/integrations/repositories/integration-mapping-repository-documentdb.js +406 -0
- package/integrations/repositories/integration-mapping-repository-factory.js +57 -0
- package/integrations/repositories/integration-mapping-repository-interface.js +183 -0
- package/integrations/repositories/integration-mapping-repository-mongo.js +284 -0
- package/integrations/repositories/integration-mapping-repository-postgres.js +403 -0
- package/integrations/repositories/integration-mapping-repository.js +185 -0
- package/integrations/repositories/integration-repository-documentdb.js +354 -0
- package/integrations/repositories/integration-repository-factory.js +51 -0
- package/integrations/repositories/integration-repository-interface.js +219 -0
- package/integrations/repositories/integration-repository-mongo.js +502 -0
- package/integrations/repositories/integration-repository-postgres.js +550 -0
- package/integrations/repositories/process-repository-documentdb.js +311 -0
- package/integrations/repositories/process-repository-factory.js +53 -0
- package/integrations/repositories/process-repository-interface.js +136 -0
- package/integrations/repositories/process-repository-mongo.js +262 -0
- package/integrations/repositories/process-repository-postgres.js +380 -0
- package/integrations/repositories/process-update-ops-shared.js +112 -0
- package/integrations/repositories/report-id.js +13 -0
- package/integrations/tests/doubles/config-capturing-integration.js +81 -0
- package/integrations/tests/doubles/dummy-integration-class.js +113 -0
- package/integrations/tests/doubles/test-integration-repository.js +146 -0
- package/integrations/use-cases/add-entities-to-integration.js +92 -0
- package/integrations/use-cases/create-integration.js +253 -0
- package/integrations/use-cases/create-process.js +130 -0
- package/integrations/use-cases/delete-integration-for-user.js +130 -0
- package/integrations/use-cases/find-integration-by-entity-external-id.js +74 -0
- package/integrations/use-cases/find-integration-context-by-external-entity-id.js +76 -0
- package/integrations/use-cases/get-integration-for-user.js +78 -0
- package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
- package/integrations/use-cases/get-integration-instance.js +83 -0
- package/integrations/use-cases/get-integrations-for-user.js +88 -0
- package/integrations/use-cases/get-possible-integrations.js +27 -0
- package/integrations/use-cases/get-process.js +89 -0
- package/integrations/use-cases/index.js +19 -0
- package/integrations/use-cases/list-integrations-by-entity-external-id.js +46 -0
- package/integrations/use-cases/load-integration-context.js +71 -0
- package/integrations/use-cases/patch-integration-config.js +39 -0
- package/integrations/use-cases/process-errors.js +28 -0
- package/integrations/use-cases/remove-entities-from-integration.js +70 -0
- package/integrations/use-cases/update-integration-config.js +32 -0
- package/integrations/use-cases/update-integration-messages.js +44 -0
- package/integrations/use-cases/update-integration-status.js +32 -0
- package/integrations/use-cases/update-integration.js +92 -0
- package/integrations/use-cases/update-process-metrics.js +222 -0
- package/integrations/use-cases/update-process-state.js +163 -0
- package/integrations/utils/assert-entity-ids-input.js +42 -0
- package/integrations/utils/map-integration-dto.js +37 -0
- package/jest-global-setup-noop.js +3 -0
- package/jest-global-teardown-noop.js +3 -0
- package/jest.config.js +2 -0
- package/logs/.eslintrc.json +26 -1
- package/logs/__fixtures__/events.js +82 -0
- package/logs/__fixtures__/matchers.js +45 -0
- package/logs/__fixtures__/secrets.js +59 -0
- package/logs/__fixtures__/vectors.js +329 -0
- package/logs/__fixtures__/with-env.js +45 -0
- package/logs/context.js +109 -0
- package/logs/contract-keys.js +67 -0
- package/logs/debug-shims.js +46 -0
- package/logs/denied-keys.js +21 -0
- package/logs/index.js +14 -3
- package/logs/jest-logger-setup.js +25 -0
- package/logs/levels.js +71 -0
- package/logs/logger-runtime.js +137 -0
- package/logs/logger.js +134 -46
- package/logs/record.js +276 -0
- package/logs/redact.js +328 -0
- package/logs/serialize.js +329 -0
- package/logs/sinks.js +93 -0
- package/logs/summarize-event.js +163 -0
- package/{module-plugin → modules}/index.js +0 -10
- package/modules/module-factory.js +56 -0
- package/modules/module.js +320 -0
- package/modules/repositories/module-repository-documentdb.js +350 -0
- package/modules/repositories/module-repository-factory.js +40 -0
- package/modules/repositories/module-repository-interface.js +145 -0
- package/modules/repositories/module-repository-mongo.js +436 -0
- package/modules/repositories/module-repository-postgres.js +481 -0
- package/modules/repositories/module-repository.js +369 -0
- package/modules/requester/api-key.js +52 -0
- package/modules/requester/oauth-2.js +561 -0
- package/modules/requester/requester.js +605 -0
- package/{module-plugin → modules}/test/mock-api/api.js +8 -3
- package/{module-plugin → modules}/test/mock-api/definition.js +14 -10
- package/modules/tests/doubles/test-module-factory.js +16 -0
- package/modules/tests/doubles/test-module-repository.js +39 -0
- package/modules/use-cases/get-entities-for-user.js +32 -0
- package/modules/use-cases/get-entity-options-by-id.js +71 -0
- package/modules/use-cases/get-entity-options-by-type.js +34 -0
- package/modules/use-cases/get-module-instance-from-type.js +34 -0
- package/modules/use-cases/get-module.js +74 -0
- package/modules/use-cases/process-authorization-callback.js +294 -0
- package/modules/use-cases/refresh-entity-options.js +72 -0
- package/modules/use-cases/test-module-auth.js +72 -0
- package/modules/utils/map-module-dto.js +20 -0
- package/modules/utils/to-credential-id.js +14 -0
- package/package.json +91 -52
- package/prisma-mongodb/schema.prisma +455 -0
- package/prisma-postgresql/migrations/20250930193005_init/migration.sql +315 -0
- package/prisma-postgresql/migrations/20251006135218_init/migration.sql +9 -0
- package/prisma-postgresql/migrations/20251010000000_remove_unused_entity_reference_map/migration.sql +3 -0
- package/prisma-postgresql/migrations/20251112195422_update_user_unique_constraints/migration.sql +25 -0
- package/prisma-postgresql/migrations/20260422120000_add_entity_data_column/migration.sql +10 -0
- package/prisma-postgresql/migrations/20260422120001_create_process_table/migration.sql +48 -0
- package/prisma-postgresql/migrations/20260625000000_add_user_organization_id_index/migration.sql +2 -0
- package/prisma-postgresql/migrations/20260703000000_add_integration_status_in_creation_in_deletion/migration.sql +11 -0
- package/prisma-postgresql/migrations/20260703000001_integration_status_default_in_creation/migration.sql +8 -0
- package/prisma-postgresql/migrations/20260705000000_create_usage_counter/migration.sql +26 -0
- package/prisma-postgresql/migrations/20260706000000_add_admin_script_execution_and_schedule/migration.sql +61 -0
- package/prisma-postgresql/migrations/migration_lock.toml +3 -0
- package/prisma-postgresql/schema.prisma +436 -0
- package/queues/queue-delivery.js +41 -0
- package/queues/queuer-util.js +89 -21
- package/reporting/README.md +154 -0
- package/reporting/builtin-reports.js +6 -0
- package/reporting/index.js +13 -0
- package/reporting/report-base.js +49 -0
- package/reporting/reports/integrations-report.js +221 -0
- package/syncs/manager.js +468 -443
- package/syncs/repositories/sync-repository-documentdb.js +240 -0
- package/syncs/repositories/sync-repository-factory.js +43 -0
- package/syncs/repositories/sync-repository-interface.js +109 -0
- package/syncs/repositories/sync-repository-mongo.js +239 -0
- package/syncs/repositories/sync-repository-postgres.js +319 -0
- package/syncs/sync.js +0 -1
- package/telemetry/README.md +331 -0
- package/telemetry/bind-telemetry-context.js +77 -0
- package/telemetry/canonical-counters.js +52 -0
- package/telemetry/exporters.js +85 -0
- package/telemetry/index.js +26 -0
- package/telemetry/instrument-handler.js +104 -0
- package/telemetry/no-op-telemetry.js +71 -0
- package/telemetry/north-star.js +103 -0
- package/telemetry/otel-telemetry.js +232 -0
- package/telemetry/plugin-subscribers.js +77 -0
- package/telemetry/telemetry-config.js +120 -0
- package/telemetry/telemetry-context.js +51 -0
- package/telemetry/telemetry-event-bus.js +58 -0
- package/telemetry/telemetry-runtime.js +164 -0
- package/telemetry/telemetry-service.js +52 -0
- package/telemetry/usage-rollup-subscriber.js +116 -0
- package/token/repositories/token-repository-documentdb.js +137 -0
- package/token/repositories/token-repository-factory.js +40 -0
- package/token/repositories/token-repository-interface.js +131 -0
- package/token/repositories/token-repository-mongo.js +219 -0
- package/token/repositories/token-repository-postgres.js +264 -0
- package/token/repositories/token-repository.js +219 -0
- package/types/associations/index.d.ts +0 -17
- package/types/core/index.d.ts +12 -4
- package/types/database/index.d.ts +10 -2
- package/types/encrypt/index.d.ts +5 -3
- package/types/errors/index.d.ts +24 -10
- package/types/integrations/index.d.ts +3 -8
- package/types/logs/index.d.ts +61 -2
- package/types/module-plugin/index.d.ts +20 -69
- package/types/syncs/index.d.ts +0 -17
- package/usage/README.md +54 -0
- package/usage/index.js +17 -0
- package/usage/repositories/usage-repository-documentdb.js +194 -0
- package/usage/repositories/usage-repository-factory.js +25 -0
- package/usage/repositories/usage-repository-interface.js +37 -0
- package/usage/repositories/usage-repository-prisma.js +146 -0
- package/usage/tracked-metrics.js +38 -0
- package/usage/usage-windows.js +24 -0
- package/user/repositories/user-repository-documentdb.js +426 -0
- package/user/repositories/user-repository-factory.js +52 -0
- package/user/repositories/user-repository-interface.js +214 -0
- package/user/repositories/user-repository-mongo.js +323 -0
- package/user/repositories/user-repository-postgres.js +377 -0
- package/user/tests/doubles/test-user-repository.js +72 -0
- package/user/use-cases/authenticate-user.js +143 -0
- package/user/use-cases/authenticate-with-shared-secret.js +48 -0
- package/user/use-cases/create-individual-user.js +61 -0
- package/user/use-cases/create-organization-user.js +47 -0
- package/user/use-cases/create-token-for-user-id.js +30 -0
- package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
- package/user/use-cases/get-user-from-bearer-token.js +82 -0
- package/user/use-cases/get-user-from-x-frigg-headers.js +132 -0
- package/user/use-cases/login-user.js +122 -0
- package/user/user.js +125 -0
- package/utils/backend-path.js +38 -0
- package/utils/index.js +6 -0
- package/websocket/repositories/websocket-connection-repository-documentdb.js +119 -0
- package/websocket/repositories/websocket-connection-repository-factory.js +44 -0
- package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
- package/websocket/repositories/websocket-connection-repository-mongo.js +156 -0
- package/websocket/repositories/websocket-connection-repository-postgres.js +196 -0
- package/websocket/repositories/websocket-connection-repository.js +161 -0
- package/assertions/is-equal.js +0 -17
- package/associations/model.js +0 -54
- package/database/models/IndividualUser.js +0 -76
- package/database/models/OrganizationUser.js +0 -29
- package/database/models/State.js +0 -9
- package/database/models/Token.js +0 -70
- package/database/models/UserModel.js +0 -7
- package/database/models/WebsocketConnection.js +0 -49
- package/database/mongo.js +0 -45
- package/database/mongoose.js +0 -5
- package/encrypt/Cryptor.test.js +0 -32
- package/encrypt/encrypt.js +0 -132
- package/encrypt/encrypt.test.js +0 -1069
- package/encrypt/test-encrypt.js +0 -107
- package/errors/base-error.test.js +0 -32
- package/errors/fetch-error.test.js +0 -79
- package/errors/halt-error.test.js +0 -11
- package/errors/validation-errors.test.js +0 -120
- package/handlers/routers/middleware/loadUser.js +0 -15
- package/handlers/routers/middleware/requireLoggedInUser.js +0 -12
- package/integrations/create-frigg-backend.js +0 -31
- package/integrations/integration-factory.js +0 -251
- package/integrations/integration-mapping.js +0 -43
- package/integrations/integration-model.js +0 -46
- package/integrations/integration-user.js +0 -144
- package/integrations/test/integration-base.test.js +0 -144
- package/lambda/TimeoutCatcher.test.js +0 -68
- package/logs/logger.test.js +0 -76
- package/module-plugin/auther.js +0 -393
- package/module-plugin/credential.js +0 -22
- package/module-plugin/entity-manager.js +0 -70
- package/module-plugin/entity.js +0 -46
- package/module-plugin/manager.js +0 -169
- package/module-plugin/module-factory.js +0 -61
- package/module-plugin/requester/api-key.js +0 -36
- package/module-plugin/requester/oauth-2.js +0 -219
- package/module-plugin/requester/requester.js +0 -165
- package/module-plugin/requester/requester.test.js +0 -28
- package/module-plugin/test/auther.test.js +0 -97
- package/syncs/model.js +0 -62
- /package/{module-plugin → modules}/ModuleConstants.js +0 -0
- /package/{module-plugin → modules}/requester/basic.js +0 -0
- /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
package/logs/sinks.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
const fs = require('node:fs');
|
|
2
|
+
const { deepFreeze } = require('./record');
|
|
3
|
+
|
|
4
|
+
const DEFAULT_MAX_RETRIES = 10;
|
|
5
|
+
const MAX_SLEEP_MS = 20;
|
|
6
|
+
|
|
7
|
+
function sleepSync(ms) {
|
|
8
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function defaultWriteStderr(line) {
|
|
12
|
+
fs.writeSync(2, line);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function safeCode(code) {
|
|
16
|
+
return String(code ?? 'UNKNOWN').replace(/[^A-Za-z0-9_]/g, '').slice(0, 32) || 'UNKNOWN';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function createStdoutSink({
|
|
20
|
+
fd = 1,
|
|
21
|
+
writeSync = fs.writeSync,
|
|
22
|
+
writeStderr = defaultWriteStderr,
|
|
23
|
+
sleep = sleepSync,
|
|
24
|
+
maxRetries = DEFAULT_MAX_RETRIES,
|
|
25
|
+
} = {}) {
|
|
26
|
+
const reportFailure = (code) => {
|
|
27
|
+
try {
|
|
28
|
+
writeStderr(`frigg.logger.write_failed code=${safeCode(code)}\n`);
|
|
29
|
+
} catch {
|
|
30
|
+
// Nothing is left to report to.
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
name: 'stdout',
|
|
36
|
+
write(record) {
|
|
37
|
+
let buffer;
|
|
38
|
+
try {
|
|
39
|
+
buffer = Buffer.from(`${JSON.stringify(record)}\n`);
|
|
40
|
+
} catch {
|
|
41
|
+
reportFailure('SERIALIZE');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
let offset = 0;
|
|
45
|
+
let retries = 0;
|
|
46
|
+
// EAGAIN and a zero-byte write share one bounded retry budget.
|
|
47
|
+
const retry = () => {
|
|
48
|
+
if (retries >= maxRetries) {
|
|
49
|
+
reportFailure('EAGAIN');
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
retries += 1;
|
|
53
|
+
sleep(Math.min(retries, MAX_SLEEP_MS));
|
|
54
|
+
return true;
|
|
55
|
+
};
|
|
56
|
+
while (offset < buffer.length) {
|
|
57
|
+
let written;
|
|
58
|
+
try {
|
|
59
|
+
written = writeSync(fd, buffer, offset, buffer.length - offset);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (error?.code !== 'EAGAIN') {
|
|
62
|
+
reportFailure(error?.code);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (!retry()) return;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (written > 0) offset += written;
|
|
69
|
+
else if (!retry()) return;
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function createMemorySink({ install = true } = {}) {
|
|
76
|
+
const records = [];
|
|
77
|
+
const sink = {
|
|
78
|
+
name: 'memory',
|
|
79
|
+
records,
|
|
80
|
+
write(record) {
|
|
81
|
+
records.push(deepFreeze(JSON.parse(JSON.stringify(record))));
|
|
82
|
+
},
|
|
83
|
+
clear() {
|
|
84
|
+
records.length = 0;
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
if (install) {
|
|
88
|
+
require('./logger-runtime').setSinks([sink]);
|
|
89
|
+
}
|
|
90
|
+
return sink;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
module.exports = { createStdoutSink, createMemorySink };
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
const { redactUrl } = require('./redact');
|
|
2
|
+
|
|
3
|
+
// Best-effort extraction of the logical event/processId/integrationId from a
|
|
4
|
+
// JSON message body: `data.*` first, then the top level. Used only for log
|
|
5
|
+
// correlation. Never throws.
|
|
6
|
+
function summarizeMessageBody(bodyStr) {
|
|
7
|
+
try {
|
|
8
|
+
const parsed = JSON.parse(bodyStr);
|
|
9
|
+
return {
|
|
10
|
+
event: parsed?.event,
|
|
11
|
+
processId: parsed?.data?.processId ?? parsed?.processId,
|
|
12
|
+
integrationId: parsed?.data?.integrationId ?? parsed?.integrationId,
|
|
13
|
+
};
|
|
14
|
+
} catch {
|
|
15
|
+
return {};
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function objectKeys(value) {
|
|
20
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
21
|
+
? Object.keys(value)
|
|
22
|
+
: [];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function rawQueryKeys(rawQueryString) {
|
|
26
|
+
if (typeof rawQueryString !== 'string' || !rawQueryString) return [];
|
|
27
|
+
try {
|
|
28
|
+
return [...new URLSearchParams(rawQueryString).keys()];
|
|
29
|
+
} catch {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function unique(list) {
|
|
35
|
+
return [...new Set(list)];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// The route template keeps ids out of the field: REST v1 `resource`, or the
|
|
39
|
+
// path part of an HTTP API v2 `routeKey` such as `GET /api/{id}`.
|
|
40
|
+
function routeTemplate(event, path) {
|
|
41
|
+
if (typeof event.resource === 'string' && event.resource) {
|
|
42
|
+
return event.resource;
|
|
43
|
+
}
|
|
44
|
+
if (typeof event.routeKey === 'string') {
|
|
45
|
+
const space = event.routeKey.indexOf(' ');
|
|
46
|
+
if (space > 0) return event.routeKey.slice(space + 1);
|
|
47
|
+
}
|
|
48
|
+
return path;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function summarizeHttp(event) {
|
|
52
|
+
const path = event.path || event.rawPath;
|
|
53
|
+
const summary = {
|
|
54
|
+
source: 'http',
|
|
55
|
+
method: event.httpMethod || event.requestContext?.http?.method,
|
|
56
|
+
path,
|
|
57
|
+
route: routeTemplate(event, path),
|
|
58
|
+
queryKeys: unique([
|
|
59
|
+
...objectKeys(event.queryStringParameters),
|
|
60
|
+
...objectKeys(event.multiValueQueryStringParameters),
|
|
61
|
+
...rawQueryKeys(event.rawQueryString),
|
|
62
|
+
]),
|
|
63
|
+
headerNames: unique(
|
|
64
|
+
[...objectKeys(event.headers), ...objectKeys(event.multiValueHeaders)].map(
|
|
65
|
+
(name) => name.toLowerCase()
|
|
66
|
+
)
|
|
67
|
+
),
|
|
68
|
+
};
|
|
69
|
+
if (typeof event.routeKey === 'string' && event.routeKey) {
|
|
70
|
+
summary.routeKey = event.routeKey;
|
|
71
|
+
}
|
|
72
|
+
return summary;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Best-effort extraction of correlation identifiers from a Lambda event.
|
|
76
|
+
// For SQS: messageIds plus the parsed event/processId/integrationId of each
|
|
77
|
+
// record body. For HTTP: method, route, query keys and header names. Never
|
|
78
|
+
// throws and never returns a body, a query value or a header value.
|
|
79
|
+
function summarizeLambdaEvent(event) {
|
|
80
|
+
try {
|
|
81
|
+
if (!event || typeof event !== 'object') return {};
|
|
82
|
+
if (Array.isArray(event.Records)) {
|
|
83
|
+
return {
|
|
84
|
+
source: 'sqs',
|
|
85
|
+
records: event.Records.map((r) => ({
|
|
86
|
+
messageId: r?.messageId,
|
|
87
|
+
receiveCount: r?.attributes?.ApproximateReceiveCount,
|
|
88
|
+
...summarizeMessageBody(r?.body),
|
|
89
|
+
})),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
if (event.httpMethod || event.requestContext?.http) {
|
|
93
|
+
return summarizeHttp(event);
|
|
94
|
+
}
|
|
95
|
+
return { source: 'other' };
|
|
96
|
+
} catch {
|
|
97
|
+
return { source: 'other' };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// The bounded form that goes into the logger scope: no per-record array.
|
|
102
|
+
function toScopeInvocation(summary) {
|
|
103
|
+
if (!summary || typeof summary !== 'object' || !summary.source) {
|
|
104
|
+
return { source: 'other' };
|
|
105
|
+
}
|
|
106
|
+
if (summary.source === 'sqs') {
|
|
107
|
+
return {
|
|
108
|
+
source: 'sqs',
|
|
109
|
+
recordCount: Array.isArray(summary.records) ? summary.records.length : 0,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
if (summary.source === 'http') {
|
|
113
|
+
const invocation = {
|
|
114
|
+
source: 'http',
|
|
115
|
+
method: summary.method,
|
|
116
|
+
route: summary.route,
|
|
117
|
+
};
|
|
118
|
+
if (summary.routeKey) invocation.routeKey = summary.routeKey;
|
|
119
|
+
return invocation;
|
|
120
|
+
}
|
|
121
|
+
return { source: summary.source };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Per-request detail for the entry and failure records, never for the
|
|
125
|
+
// scope. A raw path can hold a token, so it goes through redactUrl.
|
|
126
|
+
function toRequestDetails(summary) {
|
|
127
|
+
if (!summary || summary.source !== 'http') return {};
|
|
128
|
+
return {
|
|
129
|
+
path: redactUrl(summary.path ?? ''),
|
|
130
|
+
queryKeys: summary.queryKeys ?? [],
|
|
131
|
+
headerNames: summary.headerNames ?? [],
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// The full redacted request summary (ADR-048 §6: it goes under `invocation`).
|
|
136
|
+
function toRequestInvocation(summary) {
|
|
137
|
+
return { ...toScopeInvocation(summary), ...toRequestDetails(summary) };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// The same shape from an express request, for the express error boundary.
|
|
141
|
+
function summarizeExpressRequest(req) {
|
|
142
|
+
try {
|
|
143
|
+
if (!req || typeof req !== 'object') return { source: 'http' };
|
|
144
|
+
return {
|
|
145
|
+
source: 'http',
|
|
146
|
+
method: req.method,
|
|
147
|
+
path: redactUrl(req.path ?? ''),
|
|
148
|
+
queryKeys: objectKeys(req.query),
|
|
149
|
+
headerNames: unique(objectKeys(req.headers).map((name) => name.toLowerCase())),
|
|
150
|
+
};
|
|
151
|
+
} catch {
|
|
152
|
+
return { source: 'http' };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
module.exports = {
|
|
157
|
+
summarizeLambdaEvent,
|
|
158
|
+
summarizeMessageBody,
|
|
159
|
+
toScopeInvocation,
|
|
160
|
+
toRequestDetails,
|
|
161
|
+
toRequestInvocation,
|
|
162
|
+
summarizeExpressRequest,
|
|
163
|
+
};
|
|
@@ -1,25 +1,15 @@
|
|
|
1
|
-
const { Credential } = require('./credential');
|
|
2
|
-
const { EntityManager } = require('./entity-manager');
|
|
3
|
-
const { Entity } = require('./entity');
|
|
4
|
-
const { ModuleManager } = require('./manager');
|
|
5
1
|
const { ApiKeyRequester } = require('./requester/api-key');
|
|
6
2
|
const { BasicAuthRequester } = require('./requester/basic');
|
|
7
3
|
const { OAuth2Requester } = require('./requester/oauth-2');
|
|
8
4
|
const { Requester } = require('./requester/requester');
|
|
9
5
|
const { ModuleConstants } = require('./ModuleConstants');
|
|
10
6
|
const { ModuleFactory } = require('./module-factory');
|
|
11
|
-
const { Auther } = require('./auther');
|
|
12
7
|
|
|
13
8
|
module.exports = {
|
|
14
|
-
Credential,
|
|
15
|
-
EntityManager,
|
|
16
|
-
Entity,
|
|
17
|
-
ModuleManager,
|
|
18
9
|
ApiKeyRequester,
|
|
19
10
|
BasicAuthRequester,
|
|
20
11
|
OAuth2Requester,
|
|
21
12
|
Requester,
|
|
22
13
|
ModuleConstants,
|
|
23
14
|
ModuleFactory,
|
|
24
|
-
Auther
|
|
25
15
|
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// todo: remove this file
|
|
2
|
+
|
|
3
|
+
const { Module } = require('./module');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Acts as a factory for fully-hydrated domain Module instances.
|
|
7
|
+
* Provides methods to retrieve and construct Module objects with their associated
|
|
8
|
+
* entity and definition.
|
|
9
|
+
*/
|
|
10
|
+
class ModuleFactory {
|
|
11
|
+
/**
|
|
12
|
+
* @param {Object} params - Configuration parameters.
|
|
13
|
+
* @param {import('./repositories/module-repository-interface').ModuleRepositoryInterface} params.moduleRepository - Repository for module data operations.
|
|
14
|
+
* @param {Array<Object>} params.moduleDefinitions - Array of module definitions.
|
|
15
|
+
*/
|
|
16
|
+
constructor({ moduleRepository, moduleDefinitions }) {
|
|
17
|
+
this.moduleRepository = moduleRepository;
|
|
18
|
+
this.moduleDefinitions = moduleDefinitions;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async getModuleInstance(entityId, userId) {
|
|
22
|
+
const entity = await this.moduleRepository.findEntityById(
|
|
23
|
+
entityId,
|
|
24
|
+
userId
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
if (!entity) {
|
|
28
|
+
throw new Error(`Entity ${entityId} not found`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (entity.userId !== userId) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`Entity ${entityId} does not belong to user ${userId}`
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const moduleName = entity.moduleName;
|
|
38
|
+
const moduleDefinition = this.moduleDefinitions.find((def) => {
|
|
39
|
+
return moduleName === def.moduleName;
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if (!moduleDefinition) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
`Module definition not found for module: ${moduleName}`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return new Module({
|
|
49
|
+
userId,
|
|
50
|
+
entity,
|
|
51
|
+
definition: moduleDefinition,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = { ModuleFactory };
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
const { Delegate } = require('../core');
|
|
2
|
+
const _ = require('lodash');
|
|
3
|
+
const { getLogger } = require('../logs');
|
|
4
|
+
const { ModuleConstants } = require('./ModuleConstants');
|
|
5
|
+
const {
|
|
6
|
+
createCredentialRepository,
|
|
7
|
+
} = require('../credential/repositories/credential-repository-factory');
|
|
8
|
+
const {
|
|
9
|
+
createModuleRepository,
|
|
10
|
+
} = require('./repositories/module-repository-factory');
|
|
11
|
+
|
|
12
|
+
// todo: this class should be a Domain class, and the Delegate function is preventing us from
|
|
13
|
+
// doing that, we probably have to get rid of the Delegate class as well as the event based
|
|
14
|
+
// calls since they go against the Domain Driven Design principles (eg. a domain class should not call repository methods or use cases)
|
|
15
|
+
class Module extends Delegate {
|
|
16
|
+
//todo: entity should be replaced with actual entity properties
|
|
17
|
+
/**
|
|
18
|
+
*
|
|
19
|
+
* @param {Object} params
|
|
20
|
+
* @param {Object} params.definition The definition of the Api Module
|
|
21
|
+
* @param {string} params.userId The user id
|
|
22
|
+
* @param {Object} params.entity The entity record from the database
|
|
23
|
+
* @param {string} [params.state] Optional OAuth state value forwarded to the API client (round-trips through the OAuth provider).
|
|
24
|
+
*/
|
|
25
|
+
constructor({ definition, userId = null, entity: entityObj = null, state = null }) {
|
|
26
|
+
super({ definition, userId, entity: entityObj });
|
|
27
|
+
|
|
28
|
+
this.validateDefinition(definition);
|
|
29
|
+
|
|
30
|
+
this.userId = userId;
|
|
31
|
+
this.entity = entityObj;
|
|
32
|
+
this.credential = entityObj?.credential;
|
|
33
|
+
this.definition = definition;
|
|
34
|
+
this.name = this.definition.moduleName;
|
|
35
|
+
this.logger = getLogger(`module.${this.name ?? 'unknown'}`).child(() => ({
|
|
36
|
+
entityId: this.entity?.id,
|
|
37
|
+
credentialId:
|
|
38
|
+
this.credential?.id ??
|
|
39
|
+
(typeof this.credential === 'string'
|
|
40
|
+
? this.credential
|
|
41
|
+
: undefined),
|
|
42
|
+
}));
|
|
43
|
+
this.modelName = this.definition.modelName;
|
|
44
|
+
this.apiClass = this.definition.API;
|
|
45
|
+
|
|
46
|
+
this.credentialRepository = createCredentialRepository();
|
|
47
|
+
this.moduleRepository = createModuleRepository();
|
|
48
|
+
|
|
49
|
+
// Module → parent delegate (typically IntegrationBase) events
|
|
50
|
+
this.DLGT_CREDENTIAL_INVALIDATED = 'CREDENTIAL_INVALIDATED';
|
|
51
|
+
this.delegateTypes.push(this.DLGT_CREDENTIAL_INVALIDATED);
|
|
52
|
+
this.DLGT_CREDENTIAL_VALIDATED = 'CREDENTIAL_VALIDATED';
|
|
53
|
+
this.delegateTypes.push(this.DLGT_CREDENTIAL_VALIDATED);
|
|
54
|
+
|
|
55
|
+
Object.assign(this, this.definition.requiredAuthMethods);
|
|
56
|
+
|
|
57
|
+
const apiParams = {
|
|
58
|
+
...this.definition.env,
|
|
59
|
+
delegate: this,
|
|
60
|
+
logger: this.logger,
|
|
61
|
+
...(state ? { state } : {}),
|
|
62
|
+
...(this.credential?.data
|
|
63
|
+
? this.apiParamsFromCredential(this.credential.data)
|
|
64
|
+
: {}), // Handle case when credential is undefined
|
|
65
|
+
...this.apiParamsFromEntity(this.entity),
|
|
66
|
+
};
|
|
67
|
+
this.api = new this.apiClass(apiParams);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
getName() {
|
|
71
|
+
return this.name;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
getEntityOptions() {
|
|
75
|
+
return this.definition.getEntityOptions();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async refreshEntityOptions(options) {
|
|
79
|
+
await this.definition.refreshEntityOptions(options);
|
|
80
|
+
return this.getEntityOptions();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
apiParamsFromCredential(credential) {
|
|
84
|
+
return _.pick(credential, ...this.apiPropertiesToPersist?.credential);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
apiParamsFromEntity(entity) {
|
|
88
|
+
return _.pick(entity, ...this.apiPropertiesToPersist?.entity);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
validateAuthorizationRequirements() {
|
|
92
|
+
const requirements = this.getAuthorizationRequirements();
|
|
93
|
+
let valid = true;
|
|
94
|
+
if (
|
|
95
|
+
['oauth1', 'oauth2'].includes(requirements.type) &&
|
|
96
|
+
!requirements.url
|
|
97
|
+
) {
|
|
98
|
+
valid = false;
|
|
99
|
+
}
|
|
100
|
+
return valid;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
getAuthorizationRequirements(params) {
|
|
104
|
+
return this.api.getAuthorizationRequirements();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async testAuth() {
|
|
108
|
+
let validAuth = false;
|
|
109
|
+
try {
|
|
110
|
+
if (await this.testAuthRequest(this.api)) validAuth = true;
|
|
111
|
+
} catch (e) {
|
|
112
|
+
this.logger.warn('testAuth failed', {
|
|
113
|
+
eventName: `${this.logger.name}.test_auth_failed`,
|
|
114
|
+
error: e,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
return validAuth;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async onTokenUpdate() {
|
|
121
|
+
const credentialDetails = await this.getCredentialDetails(
|
|
122
|
+
this.api,
|
|
123
|
+
this.userId
|
|
124
|
+
);
|
|
125
|
+
const apiParams = this.apiParamsFromCredential(this.api);
|
|
126
|
+
|
|
127
|
+
if (!apiParams.refresh_token && this.api.isRefreshable) {
|
|
128
|
+
this.logger.warn('No refresh_token in apiParams', {
|
|
129
|
+
eventName: `${this.logger.name}.refresh_token_missing`,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
Object.assign(credentialDetails.details, apiParams);
|
|
134
|
+
credentialDetails.details.authIsValid = true;
|
|
135
|
+
|
|
136
|
+
const persisted = await this.credentialRepository.upsertCredential(
|
|
137
|
+
credentialDetails
|
|
138
|
+
);
|
|
139
|
+
this.credential = persisted;
|
|
140
|
+
|
|
141
|
+
if (this.credential?.id) {
|
|
142
|
+
try {
|
|
143
|
+
await this.notify(this.DLGT_CREDENTIAL_VALIDATED, {
|
|
144
|
+
credentialId: this.credential.id,
|
|
145
|
+
moduleName: this.name,
|
|
146
|
+
});
|
|
147
|
+
} catch (err) {
|
|
148
|
+
this.logger.error('Failed to propagate CREDENTIAL_VALIDATED', {
|
|
149
|
+
eventName: `${this.logger.name}.credential_validated_propagation_failed`,
|
|
150
|
+
error: err,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async receiveNotification(notifier, delegateString, object = null) {
|
|
157
|
+
if (delegateString === this.api.DLGT_TOKEN_UPDATE) {
|
|
158
|
+
await this.onTokenUpdate();
|
|
159
|
+
} else if (delegateString === this.api.DLGT_TOKEN_DEAUTHORIZED) {
|
|
160
|
+
await this.deauthorize();
|
|
161
|
+
} else if (delegateString === this.api.DLGT_INVALID_AUTH) {
|
|
162
|
+
await this.markCredentialsInvalid(object);
|
|
163
|
+
} else if (delegateString === this.api.DLGT_CREDENTIAL_RELOAD) {
|
|
164
|
+
return this.reloadCredential();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Re-reads the credential row from the database. The requester can then
|
|
170
|
+
* adopt a concurrent invocation's refresh and does not race it.
|
|
171
|
+
* @returns {Promise<Object|null>} The persisted token fields, or null
|
|
172
|
+
* when no credential row is available. The requester treats null as
|
|
173
|
+
* "nothing to adopt" and continues with its own refresh.
|
|
174
|
+
*/
|
|
175
|
+
async reloadCredential() {
|
|
176
|
+
if (!this.credential?.id) return null;
|
|
177
|
+
const freshFromDatabase =
|
|
178
|
+
await this.credentialRepository.findCredentialById(
|
|
179
|
+
this.credential.id
|
|
180
|
+
);
|
|
181
|
+
if (!freshFromDatabase) return null;
|
|
182
|
+
this.credential = freshFromDatabase;
|
|
183
|
+
return this.apiParamsFromCredential(freshFromDatabase);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async markCredentialsInvalid(diagnosticInfo = null) {
|
|
187
|
+
if (!this.credential) return;
|
|
188
|
+
|
|
189
|
+
if (!this.credential.id) return;
|
|
190
|
+
|
|
191
|
+
if (diagnosticInfo) {
|
|
192
|
+
this.logger.warn('Credentials rejected', {
|
|
193
|
+
eventName: `${this.logger.name}.credentials_rejected`,
|
|
194
|
+
statusCode: diagnosticInfo.statusCode,
|
|
195
|
+
error: diagnosticInfo,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
await this.credentialRepository.updateAuthenticationStatus(
|
|
200
|
+
this.credential.id,
|
|
201
|
+
false
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
// Keep the in-memory snapshot consistent so that callers can read the
|
|
205
|
+
// updated state without another fetch.
|
|
206
|
+
this.credential.authIsValid = false;
|
|
207
|
+
|
|
208
|
+
// Propagate upward so a parent delegate (e.g. IntegrationBase) can
|
|
209
|
+
// react — for instance by flipping Integration.status to DISABLED.
|
|
210
|
+
// Delegate.notify is a silent no-op when this.delegate is null, so
|
|
211
|
+
// Module instances constructed outside of an Integration context
|
|
212
|
+
// (e.g. during ProcessAuthorizationCallback) remain unaffected.
|
|
213
|
+
//
|
|
214
|
+
// Best-effort: this method is invoked from the OAuth2Requester 401
|
|
215
|
+
// refresh catch block, which depends on us NOT throwing. A DB hiccup
|
|
216
|
+
// in the downstream status flip must not alter refreshAuth's
|
|
217
|
+
// documented `return false` contract. The credential has already
|
|
218
|
+
// been persisted as invalid; integrations left un-flipped can be
|
|
219
|
+
// recovered by the next retry or by operator intervention.
|
|
220
|
+
try {
|
|
221
|
+
await this.notify(this.DLGT_CREDENTIAL_INVALIDATED, {
|
|
222
|
+
credentialId: this.credential.id,
|
|
223
|
+
moduleName: this.name,
|
|
224
|
+
...(diagnosticInfo && {
|
|
225
|
+
reason: diagnosticInfo.message,
|
|
226
|
+
statusCode: diagnosticInfo.statusCode,
|
|
227
|
+
}),
|
|
228
|
+
});
|
|
229
|
+
} catch (err) {
|
|
230
|
+
this.logger.error('Failed to propagate CREDENTIAL_INVALIDATED', {
|
|
231
|
+
eventName: `${this.logger.name}.credential_invalidated_propagation_failed`,
|
|
232
|
+
error: err,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async deauthorize() {
|
|
238
|
+
//todo: Check if this is correct, we're instantiating a new api without params (credentials, tokens, etc...)
|
|
239
|
+
this.api = new this.apiClass();
|
|
240
|
+
this.api.logger = this.logger;
|
|
241
|
+
|
|
242
|
+
// Remove persisted credential (if any)
|
|
243
|
+
if (this.entity?.credential) {
|
|
244
|
+
const credentialId =
|
|
245
|
+
this.entity.credential.id || this.entity.credential;
|
|
246
|
+
|
|
247
|
+
// Delete credential via repository
|
|
248
|
+
await this.credentialRepository.deleteCredentialById(credentialId);
|
|
249
|
+
this.credential = undefined;
|
|
250
|
+
|
|
251
|
+
// Unset credential reference on the Entity document
|
|
252
|
+
const entityId = this.entity.id;
|
|
253
|
+
if (entityId) {
|
|
254
|
+
await this.moduleRepository.unsetCredential(entityId);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Keep in-memory snapshot consistent
|
|
258
|
+
this.entity.credential = undefined;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// todo: check if all these props are still up to date
|
|
263
|
+
validateDefinition(definition) {
|
|
264
|
+
if (!definition) {
|
|
265
|
+
throw new Error('Module definition is required');
|
|
266
|
+
}
|
|
267
|
+
if (!definition.moduleName) {
|
|
268
|
+
throw new Error('Module definition requires moduleName');
|
|
269
|
+
}
|
|
270
|
+
if (!definition.API) {
|
|
271
|
+
throw new Error('Module definition requires API class');
|
|
272
|
+
}
|
|
273
|
+
if (!definition.requiredAuthMethods) {
|
|
274
|
+
throw new Error('Module definition requires requiredAuthMethods');
|
|
275
|
+
} else {
|
|
276
|
+
if (
|
|
277
|
+
definition.API.requesterType ===
|
|
278
|
+
ModuleConstants.authType.oauth2 &&
|
|
279
|
+
!definition.requiredAuthMethods.getToken
|
|
280
|
+
) {
|
|
281
|
+
throw new Error(
|
|
282
|
+
'Module definition requires requiredAuthMethods.getToken'
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
if (!definition.requiredAuthMethods.getEntityDetails) {
|
|
286
|
+
throw new Error(
|
|
287
|
+
'Module definition requires requiredAuthMethods.getEntityDetails'
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
if (!definition.requiredAuthMethods.getCredentialDetails) {
|
|
291
|
+
throw new Error(
|
|
292
|
+
'Module definition requires requiredAuthMethods.getCredentialDetails'
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
if (!definition.requiredAuthMethods.apiPropertiesToPersist) {
|
|
296
|
+
throw new Error(
|
|
297
|
+
'Module definition requires requiredAuthMethods.apiPropertiesToPersist'
|
|
298
|
+
);
|
|
299
|
+
} else if (definition.Credential) {
|
|
300
|
+
for (const prop of definition.requiredAuthMethods
|
|
301
|
+
.apiPropertiesToPersist?.credential) {
|
|
302
|
+
if (
|
|
303
|
+
!definition.Credential.schema.paths.hasOwnProperty(prop)
|
|
304
|
+
) {
|
|
305
|
+
throw new Error(
|
|
306
|
+
`Module definition requires Credential schema to have property ${prop}`
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (!definition.requiredAuthMethods.testAuthRequest) {
|
|
312
|
+
throw new Error(
|
|
313
|
+
'Module definition requires requiredAuthMethods.testAuth'
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
module.exports = { Module };
|