@friggframework/core 2.0.0--canary.703.12b980b.0 → 2.0.0--canary.707.df80bfd.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/CHANGELOG.md +11 -0
- package/CLAUDE.md +28 -0
- package/README.md +105 -0
- package/application/commands/integration-commands.js +20 -10
- package/application/commands/scheduler-commands.js +11 -7
- package/application/commands/user-commands.js +17 -5
- package/application/index.js +6 -2
- package/core/Worker.js +31 -7
- package/core/create-handler.js +9 -0
- package/credential/repositories/credential-repository-documentdb.js +34 -0
- package/credential/repositories/credential-repository-interface.js +15 -0
- package/credential/repositories/credential-repository-mongo.js +23 -0
- package/credential/repositories/credential-repository-postgres.js +28 -0
- package/credential/use-cases/credential-operations-for-user.js +202 -0
- package/credential/use-cases/reauthorize-credential-for-user.js +73 -0
- package/database/config.js +48 -24
- package/database/documentdb-encryption-service.js +9 -22
- package/database/documentdb-utils.js +2 -2
- package/database/encryption/README.md +78 -14
- package/database/encryption/documentdb-encryption-service.md +78 -1
- package/database/encryption/encryption-config.js +219 -0
- package/database/encryption/encryption-schema-registry.js +121 -24
- package/database/encryption/field-encryption-service.js +69 -5
- package/database/encryption/integration-mapping-encryption.js +1 -1
- package/database/encryption/test-support/fake-ciphertext.js +15 -0
- package/database/organization-where.js +18 -0
- package/database/prisma.js +15 -38
- package/database/unique-violation.js +25 -0
- package/database/use-cases/check-encryption-health-use-case.js +43 -52
- package/errors/index.js +0 -4
- package/extensions/bootstrap-runner.js +43 -0
- package/extensions/extension-loader.js +230 -0
- package/extensions/index.js +44 -0
- package/extensions/initialize-app.js +123 -0
- package/extensions/prisma-composer.js +270 -0
- package/extensions/route-mounter.js +103 -0
- package/extensions/schema-composer.js +188 -0
- package/generated/prisma-mongodb/edge.js +33 -42
- package/generated/prisma-mongodb/index-browser.js +30 -39
- package/generated/prisma-mongodb/index.d.ts +3187 -2005
- package/generated/prisma-mongodb/index.js +33 -42
- package/generated/prisma-mongodb/package.json +1 -1
- package/generated/prisma-mongodb/schema.prisma +86 -59
- package/generated/prisma-mongodb/wasm.js +33 -42
- package/generated/prisma-postgresql/edge.js +33 -42
- package/generated/prisma-postgresql/index-browser.js +30 -39
- package/generated/prisma-postgresql/index.d.ts +3380 -2177
- package/generated/prisma-postgresql/index.js +33 -42
- package/generated/prisma-postgresql/package.json +1 -1
- package/generated/prisma-postgresql/schema.prisma +81 -57
- package/generated/prisma-postgresql/wasm.js +33 -42
- package/handlers/WEBHOOKS.md +0 -29
- package/handlers/app-definition-loader.js +128 -19
- package/handlers/app-handler-helpers.js +27 -8
- package/handlers/backend-utils.js +31 -25
- package/handlers/integration-event-dispatcher.js +3 -7
- package/handlers/middleware/admin-auth.js +14 -1
- package/handlers/routers/auth.js +46 -10
- package/handlers/routers/health.js +13 -4
- package/handlers/routers/integration-defined-routers.js +144 -92
- package/handlers/routers/integration-webhook-routers.js +100 -50
- package/handlers/workers/integration-defined-workers.js +5 -2
- package/index.js +66 -16
- package/infrastructure/scheduler/index.js +19 -6
- package/infrastructure/scheduler/netlify-scheduler-adapter.js +216 -0
- package/infrastructure/scheduler/scheduled-job-repository.js +118 -0
- package/infrastructure/scheduler/scheduler-service-factory.js +55 -7
- package/integrations/index.js +1 -14
- package/integrations/integration-base.js +2 -163
- package/integrations/integration-router.js +132 -102
- package/integrations/repositories/integration-mapping-repository-documentdb.js +0 -50
- package/integrations/repositories/integration-mapping-repository-interface.js +0 -15
- package/integrations/repositories/integration-mapping-repository-mongo.js +0 -29
- package/integrations/repositories/integration-mapping-repository-postgres.js +0 -30
- package/integrations/repositories/integration-mapping-repository.js +0 -29
- package/integrations/repositories/integration-not-found.js +14 -0
- package/integrations/repositories/integration-repository-documentdb.js +37 -9
- package/integrations/repositories/integration-repository-interface.js +17 -4
- package/integrations/repositories/integration-repository-mongo.js +64 -96
- package/integrations/repositories/integration-repository-postgres.js +63 -81
- package/integrations/repositories/process-repository-mongo.js +1 -1
- package/integrations/schedule-config.js +80 -0
- package/integrations/tests/doubles/test-integration-repository.js +12 -1
- package/integrations/use-cases/create-integration.js +56 -31
- package/integrations/use-cases/delete-integration-for-user.js +4 -4
- package/integrations/use-cases/dispatch-ongoing-syncs.js +100 -0
- package/integrations/use-cases/get-integration-for-user.js +2 -4
- package/integrations/use-cases/get-integration-instance.js +4 -4
- package/integrations/use-cases/get-integrations-for-user.js +79 -39
- package/integrations/use-cases/get-possible-integrations.js +2 -7
- package/integrations/use-cases/index.js +7 -1
- package/integrations/use-cases/integration-operations-for-user.js +256 -0
- package/integrations/use-cases/resolve-integration-ownership.js +177 -0
- package/integrations/use-cases/update-integration.js +4 -4
- package/integrations/utils/map-integration-dto.js +43 -14
- package/management-api/create-management-api-router.js +49 -0
- package/management-api/error-response.js +116 -0
- package/management-api/management-api-config.js +151 -0
- package/management-api/meta-router.js +76 -0
- package/management-api/openapi/build-openapi.js +144 -0
- package/management-api/openapi/components.js +415 -0
- package/management-api/openapi/v1-operations.js +83 -0
- package/management-api/openapi/v2-operations.js +264 -0
- package/management-api/route-registry.js +411 -0
- package/management-api/test-support/http-client.js +39 -0
- package/management-api/use-cases/get-management-api-meta.js +57 -0
- package/management-api/v1-deprecation.js +120 -0
- package/management-api/v2/create-v2-router.js +85 -0
- package/management-api/v2/dependencies.js +281 -0
- package/management-api/v2/dto.js +143 -0
- package/management-api/v2/handlers/authorization.js +94 -0
- package/management-api/v2/handlers/credentials.js +33 -0
- package/management-api/v2/handlers/entities.js +77 -0
- package/management-api/v2/handlers/integrations.js +128 -0
- package/management-api/v2/handlers/proxy.js +34 -0
- package/management-api/v2/request.js +85 -0
- package/management-api/v2/test-support/fake-dependencies.js +103 -0
- package/modules/domain/authorization-session.js +144 -0
- package/modules/entity-scope.js +15 -0
- package/modules/entity-type-registry.js +311 -0
- package/modules/index.js +14 -0
- package/modules/module-factory.js +12 -0
- package/modules/module.js +53 -2
- package/modules/proxy/proxy-bases.js +169 -0
- package/modules/proxy/proxy-errors.js +31 -0
- package/modules/proxy/proxy-path.js +183 -0
- package/modules/proxy/proxy-policy.js +277 -0
- package/modules/repositories/authorization-session-repository-documentdb.js +112 -0
- package/modules/repositories/authorization-session-repository-factory.js +31 -0
- package/modules/repositories/authorization-session-repository-interface.js +47 -0
- package/modules/repositories/authorization-session-repository-prisma.js +87 -0
- package/modules/repositories/entity-not-found.js +18 -0
- package/modules/repositories/module-repository-documentdb.js +51 -6
- package/modules/repositories/module-repository-interface.js +19 -2
- package/modules/repositories/module-repository-mongo.js +67 -74
- package/modules/repositories/module-repository-postgres.js +66 -74
- package/modules/repositories/module-repository.js +2 -1
- package/modules/requester/proxy-request.js +245 -0
- package/modules/requester/requester.js +35 -5
- package/modules/scope-config.js +192 -0
- package/modules/scope-policy.js +112 -0
- package/modules/scope-resolution.js +288 -0
- package/modules/scope-visibility.js +247 -0
- package/modules/tests/doubles/in-memory-authorization-session-repository.js +45 -0
- package/modules/tests/doubles/ownership-matching.js +18 -0
- package/modules/use-cases/authorize-entity-for-user.js +61 -0
- package/modules/use-cases/create-entity-from-credential.js +113 -0
- package/modules/use-cases/entity-operations-for-user.js +162 -0
- package/modules/use-cases/entity-types.js +139 -0
- package/modules/use-cases/execute-entity-proxy-request.js +331 -0
- package/modules/use-cases/get-authorization-requirements.js +100 -0
- package/modules/use-cases/get-entities-for-user.js +26 -13
- package/modules/use-cases/get-entity-module-for-user.js +113 -0
- package/modules/use-cases/get-entity-options-by-id.js +10 -4
- package/modules/use-cases/get-entity-options-by-type.js +1 -1
- package/modules/use-cases/get-module-instance-from-type.js +1 -1
- package/modules/use-cases/get-module.js +10 -4
- package/modules/use-cases/manage-access.js +21 -0
- package/modules/use-cases/multi-step-authorization.js +329 -0
- package/modules/use-cases/process-authorization-callback.js +53 -19
- package/modules/use-cases/refresh-entity-options.js +10 -4
- package/modules/use-cases/test-module-auth.js +10 -4
- package/package.json +6 -7
- package/prisma-mongodb/schema.prisma +86 -59
- package/prisma-postgresql/migrations/20261003000000_add_authorization_session/migration.sql +28 -0
- package/prisma-postgresql/migrations/20261015000000_add_organization_membership/migration.sql +26 -0
- package/prisma-postgresql/migrations/20261016000000_add_entity_integration_organization/migration.sql +25 -0
- package/prisma-postgresql/migrations/20261017000000_add_scheduled_job/migration.sql +28 -0
- package/prisma-postgresql/schema.prisma +81 -57
- package/providers/index.js +23 -0
- package/providers/resolve-provider.js +93 -0
- package/providers/runtime-detection.js +124 -0
- package/queues/index.js +25 -0
- package/queues/providers/index.js +11 -0
- package/queues/providers/netlify-background-provider.js +150 -0
- package/queues/providers/qstash-queue-provider.js +219 -0
- package/queues/providers/sqs-queue-provider.js +70 -0
- package/queues/queue-client-interface.js +55 -0
- package/queues/queue-message-auth.js +193 -0
- package/queues/queue-provider-factory.js +101 -0
- package/queues/queue-provider.js +65 -0
- package/queues/queuer-util.js +2 -9
- package/queues/sqs-queue-client.js +81 -0
- package/types/index.d.ts +2 -0
- package/types/modules/index.d.ts +214 -0
- package/types/user/index.d.ts +134 -0
- package/user/adopter-jwt-config.js +177 -0
- package/user/adopter-jwt-errors.js +59 -0
- package/user/adopter-token-verifier.js +122 -0
- package/user/app-user-helpers.js +97 -0
- package/user/create-user-authentication.js +64 -0
- package/user/membership.js +51 -0
- package/user/repositories/organization-membership-record.js +19 -0
- package/user/repositories/organization-membership-repository-documentdb.js +106 -0
- package/user/repositories/organization-membership-repository-factory.js +42 -0
- package/user/repositories/organization-membership-repository-interface.js +57 -0
- package/user/repositories/organization-membership-repository-mongo.js +13 -0
- package/user/repositories/organization-membership-repository-postgres.js +29 -0
- package/user/repositories/organization-membership-repository-prisma.js +81 -0
- package/user/repositories/user-repository-documentdb.js +27 -2
- package/user/repositories/user-repository-interface.js +18 -0
- package/user/repositories/user-repository-mongo.js +24 -0
- package/user/repositories/user-repository-postgres.js +18 -0
- package/user/tests/doubles/adopter-jwt-fixtures.js +220 -0
- package/user/use-cases/authenticate-user.js +7 -1
- package/user/use-cases/ensure-organization-membership.js +92 -0
- package/user/use-cases/find-individual-users-by-organization.js +54 -0
- package/user/use-cases/get-user-from-adopter-jwt.js +95 -124
- package/user/use-cases/get-user-from-x-frigg-headers.js +83 -80
- package/user/user.js +64 -1
- package/errors/mapping-already-exists-error.js +0 -14
- package/handlers/workers/integration-triggers.js +0 -25
- package/infrastructure/scheduler/recurrence.js +0 -204
- package/integrations/define/DESIGN.md +0 -122
- package/integrations/define/config.js +0 -98
- package/integrations/define/define-app.js +0 -66
- package/integrations/define/define-extension.js +0 -154
- package/integrations/define/define-integration.js +0 -335
- package/integrations/define/examples/deal-alerts.js +0 -140
- package/integrations/define/extension-binding.js +0 -174
- package/integrations/define/index.js +0 -11
- package/integrations/define/json-schema-lite.js +0 -68
- package/integrations/define/subscriptions.js +0 -186
- package/integrations/define/tests/fixtures/hubspot.js +0 -109
- package/integrations/define/tests/fixtures/slack.js +0 -26
- package/integrations/define/use.js +0 -39
- package/integrations/definitions/README.md +0 -278
- package/integrations/definitions/canonical.js +0 -11
- package/integrations/definitions/capabilities/AUDIT.md +0 -480
- package/integrations/definitions/capabilities/acmeerp.capabilities.json +0 -633
- package/integrations/definitions/capabilities/asana.capabilities.json +0 -279
- package/integrations/definitions/capabilities/calendly.capabilities.json +0 -385
- package/integrations/definitions/capabilities/docusign.capabilities.json +0 -314
- package/integrations/definitions/capabilities/fathom.capabilities.json +0 -238
- package/integrations/definitions/capabilities/github.capabilities.json +0 -585
- package/integrations/definitions/capabilities/hubspot.capabilities.json +0 -822
- package/integrations/definitions/capabilities/jira.capabilities.json +0 -391
- package/integrations/definitions/capabilities/klaviyo.capabilities.json +0 -503
- package/integrations/definitions/capabilities/linear.capabilities.json +0 -378
- package/integrations/definitions/capabilities/netsuite.capabilities.json +0 -535
- package/integrations/definitions/capabilities/notion.capabilities.json +0 -208
- package/integrations/definitions/capabilities/phone.capabilities.json +0 -511
- package/integrations/definitions/capabilities/pipedrive.capabilities.json +0 -430
- package/integrations/definitions/capabilities/quickbooks.capabilities.json +0 -635
- package/integrations/definitions/capabilities/salesforce.capabilities.json +0 -568
- package/integrations/definitions/capabilities/shopify.capabilities.json +0 -1128
- package/integrations/definitions/capabilities/slack.capabilities.json +0 -373
- package/integrations/definitions/capabilities/stripe.capabilities.json +0 -630
- package/integrations/definitions/capabilities/webflow.capabilities.json +0 -196
- package/integrations/definitions/capabilities/zendesk.capabilities.json +0 -422
- package/integrations/definitions/compile.js +0 -80
- package/integrations/definitions/config-form.js +0 -137
- package/integrations/definitions/examples/acme-erp-quickbooks.definition.json +0 -467
- package/integrations/definitions/examples/asana-slack.definition.json +0 -219
- package/integrations/definitions/examples/calendly-salesforce.definition.json +0 -459
- package/integrations/definitions/examples/docusign-salesforce.definition.json +0 -305
- package/integrations/definitions/examples/fathom-hubspot-notion.definition.json +0 -575
- package/integrations/definitions/examples/github-linear.definition.json +0 -322
- package/integrations/definitions/examples/hubspot-slack.definition.json +0 -427
- package/integrations/definitions/examples/klaviyo-hubspot.definition.json +0 -336
- package/integrations/definitions/examples/phone-hubspot.definition.json +0 -633
- package/integrations/definitions/examples/pipedrive-hubspot.definition.json +0 -136
- package/integrations/definitions/examples/pipedrive-phone.definition.json +0 -340
- package/integrations/definitions/examples/records/phone-hubspot.definition-version.json +0 -652
- package/integrations/definitions/examples/records/phone-hubspot.integration.json +0 -86
- package/integrations/definitions/examples/records/phone-hubspot.mappings.json +0 -39
- package/integrations/definitions/examples/records/phone-hubspot.workflow-run.json +0 -62
- package/integrations/definitions/examples/shopify-klaviyo.definition.json +0 -488
- package/integrations/definitions/examples/shopify-netsuite.definition.json +0 -622
- package/integrations/definitions/examples/stripe-quickbooks.definition.json +0 -476
- package/integrations/definitions/examples/webflow-hubspot-slack.definition.json +0 -262
- package/integrations/definitions/examples/zendesk-jira.definition.json +0 -604
- package/integrations/definitions/expressions.js +0 -229
- package/integrations/definitions/index.js +0 -48
- package/integrations/definitions/integration-runtime.js +0 -779
- package/integrations/definitions/interpreter.js +0 -104
- package/integrations/definitions/manifests.js +0 -177
- package/integrations/definitions/renderers/common.js +0 -67
- package/integrations/definitions/renderers/html.js +0 -129
- package/integrations/definitions/renderers/hubspot.js +0 -59
- package/integrations/definitions/renderers/preview.js +0 -154
- package/integrations/definitions/renderers/slack.js +0 -146
- package/integrations/definitions/repositories/definition-version-repository.js +0 -67
- package/integrations/definitions/repositories/workflow-run-repository.js +0 -80
- package/integrations/definitions/router.js +0 -170
- package/integrations/definitions/runtime.js +0 -268
- package/integrations/definitions/schema/capability-manifest.v0.1.schema.json +0 -549
- package/integrations/definitions/schema/installation.v0.1.schema.json +0 -130
- package/integrations/definitions/schema/integration-definition.v0.1.schema.json +0 -909
- package/integrations/definitions/schema/records.md +0 -105
- package/integrations/definitions/schema-check.js +0 -58
- package/integrations/definitions/scripts/check-manifests.js +0 -220
- package/integrations/definitions/scripts/validate.js +0 -66
- package/integrations/definitions/steps.js +0 -123
- package/integrations/definitions/sync-engine.js +0 -297
- package/integrations/definitions/tests/helpers-fake-crm.js +0 -49
- package/integrations/definitions/transforms.js +0 -91
- package/integrations/definitions/use-cases/definition-use-cases.js +0 -245
- package/integrations/definitions/validate.js +0 -436
- package/integrations/definitions/versioning.js +0 -176
- package/integrations/definitions/webhooks.js +0 -119
- package/integrations/integration-types.js +0 -73
- package/integrations/triggers.js +0 -244
- package/integrations/use-cases/run-trigger-tick.js +0 -155
- package/integrations/use-cases/run-trigger.js +0 -77
- package/prisma-postgresql/migrations/20261018000000_add_data_defined_integrations/migration.sql +0 -66
- package/prisma-postgresql/migrations/20261019000000_workflow_run_waits/migration.sql +0 -7
- package/prisma-postgresql/migrations/20261019120000_definition_workspace/migration.sql +0 -5
|
@@ -2,21 +2,27 @@ const { findNearestBackendPackageJson } = require('@friggframework/core/utils');
|
|
|
2
2
|
const path = require('node:path');
|
|
3
3
|
const fs = require('fs-extra');
|
|
4
4
|
const { resolveTelemetryConfig } = require('../telemetry/telemetry-config');
|
|
5
|
+
const { assertAdopterJwtConfig } = require('../user/adopter-jwt-config');
|
|
5
6
|
const { registerDeniedKeys } = require('../logs/denied-keys');
|
|
6
7
|
const {
|
|
7
8
|
extractCredentialFieldsFromModules,
|
|
8
9
|
} = require('../database/encryption/encryption-schema-registry');
|
|
9
10
|
const {
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
getEntityTypeRegistry,
|
|
12
|
+
getEntityTypeDefinitions,
|
|
13
|
+
} = require('../modules/entity-type-registry');
|
|
14
|
+
const { resolveOwnershipConfig } = require('../modules/entity-scope');
|
|
15
|
+
const { assertProxyConfig } = require('../modules/proxy/proxy-bases');
|
|
16
|
+
const {
|
|
17
|
+
resolveManagementApiConfig,
|
|
18
|
+
} = require('../management-api/management-api-config');
|
|
19
|
+
const { resolveScheduleConfig } = require('../integrations/schedule-config');
|
|
12
20
|
|
|
13
21
|
// Handlers without a database never load the encryption registry, so the
|
|
14
22
|
// credential leaf keys are registered for redaction here too.
|
|
15
|
-
function registerCredentialLogKeys(appDefinition,
|
|
23
|
+
function registerCredentialLogKeys(appDefinition, moduleDefinitions) {
|
|
16
24
|
try {
|
|
17
|
-
extractCredentialFieldsFromModules(
|
|
18
|
-
getModulesDefinitionFromIntegrationClasses(integrations)
|
|
19
|
-
);
|
|
25
|
+
extractCredentialFieldsFromModules(moduleDefinitions);
|
|
20
26
|
} catch {
|
|
21
27
|
// An odd integration shape must not stop the app from loading.
|
|
22
28
|
}
|
|
@@ -29,18 +35,53 @@ function registerCredentialLogKeys(appDefinition, integrations) {
|
|
|
29
35
|
}
|
|
30
36
|
|
|
31
37
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* @description Searches for the nearest backend package.json, loads the corresponding index.js file,
|
|
35
|
-
* and extracts the application definition containing integrations and user configuration.
|
|
36
|
-
* @returns {{integrations: Array<object>, userConfig: object | null, adminScripts: Array<object>, reports: Array<object>, admin: object, telemetry: object, logging: object | null}} An object containing the application definition.
|
|
37
|
-
* @throws {Error} Throws error if backend package.json cannot be found.
|
|
38
|
-
* @throws {Error} Throws error if index.js file cannot be found in the backend directory.
|
|
39
|
-
* @example
|
|
40
|
-
* const { integrations, userConfig, telemetry } = loadAppDefinition();
|
|
41
|
-
* console.log(`Found ${integrations.length} integrations`);
|
|
38
|
+
* The definition handed to setAppDefinition(), or null when the backend is
|
|
39
|
+
* discovered from process.cwd().
|
|
42
40
|
*/
|
|
43
|
-
|
|
41
|
+
let presetDefinition = null;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Pre-set the app definition so loadAppDefinition() does not discover the
|
|
45
|
+
* backend through process.cwd().
|
|
46
|
+
*
|
|
47
|
+
* Platforms such as Netlify need this: the function bundler (nft/esbuild)
|
|
48
|
+
* only traces static require() calls, and at runtime process.cwd() is the
|
|
49
|
+
* function root, not the backend. A generated function entry point therefore
|
|
50
|
+
* requires the backend's index.js statically and passes its Definition here.
|
|
51
|
+
*
|
|
52
|
+
* The preset definition is validated and derived exactly like a discovered
|
|
53
|
+
* one: adopter-JWT config, the entity registry, ownership and the Management
|
|
54
|
+
* API config all apply, so a Netlify function and an AWS Lambda load the
|
|
55
|
+
* same app the same way. Pass null to clear it.
|
|
56
|
+
*
|
|
57
|
+
* @param {Object|null} definition - the backend's `Definition` export
|
|
58
|
+
*/
|
|
59
|
+
function setAppDefinition(definition) {
|
|
60
|
+
if (
|
|
61
|
+
definition !== null &&
|
|
62
|
+
(typeof definition !== 'object' || Array.isArray(definition))
|
|
63
|
+
) {
|
|
64
|
+
throw new TypeError(
|
|
65
|
+
'setAppDefinition expects the app Definition object (or null)'
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
presetDefinition = definition;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The definition preset with setAppDefinition(), unvalidated, or null.
|
|
73
|
+
*
|
|
74
|
+
* For low-level readers that only need one raw field before the app is
|
|
75
|
+
* fully loaded (the database type, the custom encryption schema) and keep
|
|
76
|
+
* their own process.cwd() discovery as the fallback.
|
|
77
|
+
*
|
|
78
|
+
* @returns {Object|null}
|
|
79
|
+
*/
|
|
80
|
+
function getPresetAppDefinition() {
|
|
81
|
+
return presetDefinition;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function discoverAppDefinition() {
|
|
44
85
|
const backendPath = findNearestBackendPackageJson();
|
|
45
86
|
if (!backendPath) {
|
|
46
87
|
throw new Error('Could not find backend package.json');
|
|
@@ -53,18 +94,78 @@ function loadAppDefinition() {
|
|
|
53
94
|
}
|
|
54
95
|
|
|
55
96
|
const backendJsFile = require(backendFilePath);
|
|
56
|
-
|
|
97
|
+
return backendJsFile.Definition;
|
|
98
|
+
}
|
|
57
99
|
|
|
100
|
+
/**
|
|
101
|
+
* Loads the App definition: the one preset with setAppDefinition(), or the
|
|
102
|
+
* nearest backend package's index.js.
|
|
103
|
+
* @function loadAppDefinition
|
|
104
|
+
* @description Uses the definition preset by setAppDefinition() when there is one; otherwise searches for the nearest backend package.json, loads the corresponding index.js file,
|
|
105
|
+
* and extracts the application definition containing integrations and user configuration.
|
|
106
|
+
* @returns {{appDefinition: object, integrations: Array<object>, entities: object | null, entityTypeRegistry: Map<string, object>, moduleDefinitions: Array<object>, ownership: {scope: ({default: string, allow: string[]}|null), individualAcross: string}, userConfig: object | null, adminScripts: Array<object>, reports: Array<object>, admin: object, telemetry: object, logging: object | null, managementApi: object}} An object containing the application definition. `appDefinition` is the raw Definition object.
|
|
107
|
+
* @throws {Error} Throws error if backend package.json cannot be found.
|
|
108
|
+
* @throws {Error} Throws error if index.js file cannot be found in the backend directory.
|
|
109
|
+
* @throws {Error} Throws error if user.authModes.adopterJwt is enabled without a usable user.jwtConfig.
|
|
110
|
+
* @throws {OwnershipConfigError} Throws if an ownership or scope setting is invalid (ADR-056 §3.1).
|
|
111
|
+
* @throws {ManagementApiConfigError} Throws if `managementApi.proxy.scopes` is invalid (ADR-056 §3.5).
|
|
112
|
+
* @throws {ProxyConfigError} Throws if a module's `proxy.baseUrls` or an allow rule's `base` is invalid (ADR-052).
|
|
113
|
+
* @throws {ScheduleConfigError} Throws if an integration's `Definition.schedule` is invalid.
|
|
114
|
+
* @example
|
|
115
|
+
* const { integrations, userConfig, telemetry } = loadAppDefinition();
|
|
116
|
+
* console.log(`Found ${integrations.length} integrations`);
|
|
117
|
+
*/
|
|
118
|
+
function loadAppDefinition() {
|
|
119
|
+
const appDefinition = presetDefinition || discoverAppDefinition();
|
|
120
|
+
return resolveAppDefinition(appDefinition);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function resolveAppDefinition(appDefinition) {
|
|
58
124
|
const {
|
|
59
125
|
integrations = [],
|
|
126
|
+
entities = null,
|
|
60
127
|
user: userConfig = null,
|
|
61
128
|
adminScripts = [],
|
|
62
129
|
reports = [],
|
|
63
130
|
admin = {},
|
|
64
131
|
logging = null,
|
|
132
|
+
managementApi = {},
|
|
65
133
|
} = appDefinition;
|
|
66
134
|
|
|
67
|
-
|
|
135
|
+
// Fail at load, not on the first request: an adopter-token mode with no
|
|
136
|
+
// key source would otherwise start cleanly and answer every login with 500.
|
|
137
|
+
assertAdopterJwtConfig(userConfig);
|
|
138
|
+
|
|
139
|
+
// Fail fast: a bad `entities` registry (key/name mismatch, conflicting
|
|
140
|
+
// definitions, `$infer: false` with an unregistered module) must stop the
|
|
141
|
+
// app at load, not on the first request that needs a module.
|
|
142
|
+
const entityTypeRegistry = getEntityTypeRegistry(appDefinition);
|
|
143
|
+
const moduleDefinitions = getEntityTypeDefinitions(entityTypeRegistry);
|
|
144
|
+
|
|
145
|
+
// Same reason for entity scopes (ADR-056 §3.1): a mistyped scope, an unknown
|
|
146
|
+
// `individualAcross`, or an organization-owned integration that binds an
|
|
147
|
+
// individual entity stops the app at load.
|
|
148
|
+
const ownership = resolveOwnershipConfig(appDefinition, entityTypeRegistry);
|
|
149
|
+
|
|
150
|
+
// And for the Management API: `managementApi.proxy.scopes` decides whose
|
|
151
|
+
// credentials the entity proxy may act through (ADR-056 §3.5), so an
|
|
152
|
+
// unknown scope stops the app here rather than being read as a default.
|
|
153
|
+
const managementApiConfig = resolveManagementApiConfig(managementApi);
|
|
154
|
+
|
|
155
|
+
// Named proxy bases (ADR-052 amendment): a module that declares both
|
|
156
|
+
// `baseUrl` and `baseUrls`, a bad base URL or name, or an allow rule for a
|
|
157
|
+
// base the module lacks, stops the app here.
|
|
158
|
+
assertProxyConfig(moduleDefinitions, managementApiConfig.proxy);
|
|
159
|
+
|
|
160
|
+
// And for each integration's `Definition.schedule`: a mistyped scope would
|
|
161
|
+
// otherwise tick ONGOING_SYNC per class when per installation was meant.
|
|
162
|
+
for (const IntegrationClass of integrations) {
|
|
163
|
+
if (IntegrationClass?.Definition) {
|
|
164
|
+
resolveScheduleConfig(IntegrationClass.Definition);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
registerCredentialLogKeys(appDefinition, moduleDefinitions);
|
|
68
169
|
|
|
69
170
|
// Degrade consistently: an invalid telemetry block must never take down a
|
|
70
171
|
// router bundle that loads the app definition at module scope (telemetry is
|
|
@@ -88,16 +189,24 @@ function loadAppDefinition() {
|
|
|
88
189
|
}
|
|
89
190
|
|
|
90
191
|
return {
|
|
192
|
+
appDefinition,
|
|
91
193
|
integrations,
|
|
194
|
+
entities,
|
|
195
|
+
entityTypeRegistry,
|
|
196
|
+
moduleDefinitions,
|
|
197
|
+
ownership,
|
|
92
198
|
userConfig,
|
|
93
199
|
adminScripts,
|
|
94
200
|
reports,
|
|
95
201
|
admin,
|
|
96
202
|
telemetry,
|
|
97
203
|
logging,
|
|
204
|
+
managementApi,
|
|
98
205
|
};
|
|
99
206
|
}
|
|
100
207
|
|
|
101
208
|
module.exports = {
|
|
102
209
|
loadAppDefinition,
|
|
210
|
+
setAppDefinition,
|
|
211
|
+
getPresetAppDefinition,
|
|
103
212
|
};
|
|
@@ -6,17 +6,20 @@ const bodyParser = require('body-parser');
|
|
|
6
6
|
const cors = require('cors');
|
|
7
7
|
const Boom = require('@hapi/boom');
|
|
8
8
|
const serverlessHttp = require('serverless-http');
|
|
9
|
+
const {
|
|
10
|
+
API_VERSION_HEADER,
|
|
11
|
+
isVersionedManagementApiPath,
|
|
12
|
+
toBoom,
|
|
13
|
+
toV2ErrorBody,
|
|
14
|
+
} = require('../management-api/error-response');
|
|
9
15
|
|
|
10
16
|
const log = getLogger('frigg.http');
|
|
11
17
|
|
|
12
18
|
const createApp = (applyMiddleware) => {
|
|
13
19
|
const app = express();
|
|
14
20
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const keepRaw = (req, res, buf) => { req.rawBody = buf; };
|
|
18
|
-
app.use(bodyParser.json({ limit: '10mb', verify: keepRaw }));
|
|
19
|
-
app.use(bodyParser.urlencoded({ extended: true, verify: keepRaw }));
|
|
21
|
+
app.use(bodyParser.json({ limit: '10mb' }));
|
|
22
|
+
app.use(bodyParser.urlencoded({ extended: true }));
|
|
20
23
|
app.use(
|
|
21
24
|
cors({
|
|
22
25
|
origin: '*',
|
|
@@ -29,11 +32,15 @@ const createApp = (applyMiddleware) => {
|
|
|
29
32
|
if (applyMiddleware) applyMiddleware(app);
|
|
30
33
|
|
|
31
34
|
// The express boundary: send the error response and log it one time.
|
|
35
|
+
// A Boom error keeps its status; other errors keep theirs only when they
|
|
36
|
+
// mark a 4xx safe to expose (body-parser's 400/413 via http-errors), so a
|
|
37
|
+
// malformed body is a 400, not a 500.
|
|
32
38
|
app.use((err, req, res, next) => {
|
|
33
|
-
const boomError =
|
|
39
|
+
const boomError = toBoom(err);
|
|
34
40
|
const {
|
|
35
41
|
output: { statusCode = 500 },
|
|
36
42
|
} = boomError;
|
|
43
|
+
const versioned = isVersionedManagementApiPath(req.path);
|
|
37
44
|
|
|
38
45
|
if (statusCode >= 500) {
|
|
39
46
|
log.error('Request failed', {
|
|
@@ -42,7 +49,6 @@ const createApp = (applyMiddleware) => {
|
|
|
42
49
|
invocation: summarizeExpressRequest(req),
|
|
43
50
|
error: boomError,
|
|
44
51
|
});
|
|
45
|
-
res.status(statusCode).json({ error: 'Internal Server Error' });
|
|
46
52
|
} else {
|
|
47
53
|
// A client error needs no stack; the logger scrubs the reason.
|
|
48
54
|
log.warn('Request rejected', {
|
|
@@ -50,8 +56,21 @@ const createApp = (applyMiddleware) => {
|
|
|
50
56
|
statusCode,
|
|
51
57
|
reason: boomError.message,
|
|
52
58
|
});
|
|
53
|
-
res.status(statusCode).json({ error: err.message });
|
|
54
59
|
}
|
|
60
|
+
|
|
61
|
+
if (versioned) {
|
|
62
|
+
// Management API v2 / meta: { error: { code, message, details? } }.
|
|
63
|
+
if (req.path.startsWith('/api/v2')) {
|
|
64
|
+
res.set(API_VERSION_HEADER, '2');
|
|
65
|
+
}
|
|
66
|
+
return res.status(statusCode).json(toV2ErrorBody(boomError));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// v1 and other routes keep their historical body.
|
|
70
|
+
if (statusCode >= 500) {
|
|
71
|
+
return res.status(statusCode).json({ error: 'Internal Server Error' });
|
|
72
|
+
}
|
|
73
|
+
return res.status(statusCode).json({ error: err.message });
|
|
55
74
|
});
|
|
56
75
|
|
|
57
76
|
return app;
|
|
@@ -18,8 +18,9 @@ const {
|
|
|
18
18
|
createModuleRepository,
|
|
19
19
|
} = require('../modules/repositories/module-repository-factory');
|
|
20
20
|
const {
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
getEntityTypeRegistry,
|
|
22
|
+
getEntityTypeDefinitions,
|
|
23
|
+
} = require('../modules/entity-type-registry');
|
|
23
24
|
const { processNotFound } = require('../integrations/use-cases/process-errors');
|
|
24
25
|
|
|
25
26
|
const loadRouterFromObject = (IntegrationClass, routerObject) => {
|
|
@@ -61,30 +62,18 @@ const initializeRepositories = () => {
|
|
|
61
62
|
return { processRepository, integrationRepository, moduleRepository };
|
|
62
63
|
};
|
|
63
64
|
|
|
64
|
-
const createModuleFactoryWithDefinitions = (
|
|
65
|
-
moduleRepository,
|
|
66
|
-
integrationClasses
|
|
67
|
-
) => {
|
|
68
|
-
const moduleDefinitions =
|
|
69
|
-
getModulesDefinitionFromIntegrationClasses(integrationClasses);
|
|
70
|
-
|
|
71
|
-
return new ModuleFactory({
|
|
72
|
-
moduleRepository,
|
|
73
|
-
moduleDefinitions,
|
|
74
|
-
});
|
|
75
|
-
};
|
|
76
|
-
|
|
77
65
|
const loadIntegrationForWebhook = async (integrationId) => {
|
|
78
66
|
const { loadAppDefinition } = require('./app-definition-loader');
|
|
79
|
-
const { integrations: integrationClasses } =
|
|
67
|
+
const { integrations: integrationClasses, moduleDefinitions } =
|
|
68
|
+
loadAppDefinition();
|
|
80
69
|
|
|
81
70
|
const { integrationRepository, moduleRepository } =
|
|
82
71
|
initializeRepositories();
|
|
83
72
|
|
|
84
|
-
const moduleFactory =
|
|
73
|
+
const moduleFactory = new ModuleFactory({
|
|
85
74
|
moduleRepository,
|
|
86
|
-
|
|
87
|
-
);
|
|
75
|
+
moduleDefinitions,
|
|
76
|
+
});
|
|
88
77
|
|
|
89
78
|
const getIntegrationInstance = new GetIntegrationInstance({
|
|
90
79
|
integrationRepository,
|
|
@@ -129,13 +118,23 @@ const integrationExists = async (integrationId) => {
|
|
|
129
118
|
}
|
|
130
119
|
};
|
|
131
120
|
|
|
132
|
-
const loadIntegrationForProcess = async (
|
|
121
|
+
const loadIntegrationForProcess = async (
|
|
122
|
+
processId,
|
|
123
|
+
integrationClass,
|
|
124
|
+
moduleDefinitions
|
|
125
|
+
) => {
|
|
133
126
|
const { processRepository, integrationRepository, moduleRepository } =
|
|
134
127
|
initializeRepositories();
|
|
135
128
|
|
|
136
|
-
const moduleFactory =
|
|
137
|
-
|
|
138
|
-
|
|
129
|
+
const moduleFactory = new ModuleFactory({
|
|
130
|
+
moduleRepository,
|
|
131
|
+
moduleDefinitions:
|
|
132
|
+
moduleDefinitions ??
|
|
133
|
+
// No app definition handed in: this integration's own modules only.
|
|
134
|
+
getEntityTypeDefinitions(
|
|
135
|
+
getEntityTypeRegistry({ integrations: [integrationClass] })
|
|
136
|
+
),
|
|
137
|
+
});
|
|
139
138
|
|
|
140
139
|
const getIntegrationInstance = new GetIntegrationInstance({
|
|
141
140
|
integrationRepository,
|
|
@@ -161,7 +160,13 @@ const loadIntegrationForProcess = async (processId, integrationClass) => {
|
|
|
161
160
|
return instance;
|
|
162
161
|
};
|
|
163
162
|
|
|
164
|
-
|
|
163
|
+
/**
|
|
164
|
+
* @param {Function} integrationClass
|
|
165
|
+
* @param {{moduleDefinitions?: Array<object>}} [options] - the app's offered
|
|
166
|
+
* module definitions (`loadAppDefinition().moduleDefinitions`). When omitted
|
|
167
|
+
* the worker hydrates with this integration's own modules only.
|
|
168
|
+
*/
|
|
169
|
+
const createQueueWorker = (integrationClass, { moduleDefinitions } = {}) => {
|
|
165
170
|
const integrationName = integrationClass.Definition.name;
|
|
166
171
|
|
|
167
172
|
class QueueWorker extends Worker {
|
|
@@ -183,7 +188,8 @@ const createQueueWorker = (integrationClass) => {
|
|
|
183
188
|
console.log(`[QueueWorker] hydrating by processId`, logCtx);
|
|
184
189
|
integrationInstance = await loadIntegrationForProcess(
|
|
185
190
|
params.data.processId,
|
|
186
|
-
integrationClass
|
|
191
|
+
integrationClass,
|
|
192
|
+
moduleDefinitions
|
|
187
193
|
);
|
|
188
194
|
console.log(`[QueueWorker] hydrated`, {
|
|
189
195
|
...logCtx,
|
|
@@ -19,13 +19,9 @@ class IntegrationEventDispatcher {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
async dispatchJob({ event, data, context, delivery }) {
|
|
22
|
-
return this._dispatch(event, (instance, handler) =>
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
return typeof instance.runEvent === 'function'
|
|
26
|
-
? instance.runEvent(event, invoke)
|
|
27
|
-
: invoke();
|
|
28
|
-
});
|
|
22
|
+
return this._dispatch(event, (instance, handler) =>
|
|
23
|
+
handler.call(instance, { data, context, delivery })
|
|
24
|
+
);
|
|
29
25
|
}
|
|
30
26
|
|
|
31
27
|
/**
|
|
@@ -29,6 +29,19 @@ function secretsMatch(provided, expected) {
|
|
|
29
29
|
return crypto.timingSafeEqual(providedHash, expectedHash);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* True when `provided` matches the configured ADMIN_API_KEY. False when no
|
|
34
|
+
* key is configured or none was provided. For callers that change a response
|
|
35
|
+
* for admins rather than reject everyone else (GET /api/meta).
|
|
36
|
+
* @param {string|undefined} provided
|
|
37
|
+
* @returns {boolean}
|
|
38
|
+
*/
|
|
39
|
+
function isValidAdminApiKey(provided) {
|
|
40
|
+
const expectedKey = process.env.ADMIN_API_KEY;
|
|
41
|
+
if (!expectedKey || !provided) return false;
|
|
42
|
+
return secretsMatch(provided, expectedKey);
|
|
43
|
+
}
|
|
44
|
+
|
|
32
45
|
/**
|
|
33
46
|
* Validate admin API key from request header
|
|
34
47
|
* @param {import('express').Request} req
|
|
@@ -70,4 +83,4 @@ function validateAdminApiKey(req, res, next) {
|
|
|
70
83
|
next();
|
|
71
84
|
}
|
|
72
85
|
|
|
73
|
-
module.exports = { validateAdminApiKey };
|
|
86
|
+
module.exports = { validateAdminApiKey, isValidAdminApiKey };
|
package/handlers/routers/auth.js
CHANGED
|
@@ -1,15 +1,51 @@
|
|
|
1
|
-
const
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const {
|
|
3
|
+
createManagementApiRouter,
|
|
4
|
+
} = require('../../management-api/create-management-api-router');
|
|
5
|
+
const {
|
|
6
|
+
createExtensionRoutesRouter,
|
|
7
|
+
} = require('../../extensions/route-mounter');
|
|
2
8
|
const { createAppHandler } = require('./../app-handler-helpers');
|
|
3
9
|
|
|
4
|
-
|
|
10
|
+
// The Management API (ADR-053): v2 under /api/v2, the deprecated v1 at the
|
|
11
|
+
// unprefixed paths (or 410 when managementApi.v1 is false).
|
|
12
|
+
//
|
|
13
|
+
// Built on first access, not at require time (friggframework/frigg#545,
|
|
14
|
+
// c40eca49): a host that presets the app definition (setAppDefinition, as
|
|
15
|
+
// provider-netlify's function entry points do) can require this module
|
|
16
|
+
// before or after doing so.
|
|
17
|
+
let router;
|
|
18
|
+
let handler;
|
|
5
19
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}?${new URLSearchParams(req.query)}`
|
|
10
|
-
);
|
|
11
|
-
});
|
|
20
|
+
function buildRouter() {
|
|
21
|
+
const built = express.Router();
|
|
22
|
+
built.use(createManagementApiRouter());
|
|
12
23
|
|
|
13
|
-
|
|
24
|
+
// The OAuth provider's redirect target. Unversioned: it is neither part
|
|
25
|
+
// of v1 nor v2, so it is never deprecated or disabled.
|
|
26
|
+
built.route('/api/integrations/redirect/:appId').get((req, res) => {
|
|
27
|
+
res.redirect(
|
|
28
|
+
`${process.env.FRONTEND_URI}/redirect/${req.params.appId
|
|
29
|
+
}?${new URLSearchParams(req.query)}`
|
|
30
|
+
);
|
|
31
|
+
});
|
|
14
32
|
|
|
15
|
-
|
|
33
|
+
// Core extension routes (appDefinition.coreExtensions[].routes), each
|
|
34
|
+
// behind the admin API key.
|
|
35
|
+
built.use(createExtensionRoutesRouter());
|
|
36
|
+
|
|
37
|
+
return built;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = {
|
|
41
|
+
get router() {
|
|
42
|
+
if (!router) router = buildRouter();
|
|
43
|
+
return router;
|
|
44
|
+
},
|
|
45
|
+
get handler() {
|
|
46
|
+
if (!handler) {
|
|
47
|
+
handler = createAppHandler('HTTP Event: Auth', module.exports.router);
|
|
48
|
+
}
|
|
49
|
+
return handler;
|
|
50
|
+
},
|
|
51
|
+
};
|
|
@@ -2,9 +2,6 @@ const { Router } = require('express');
|
|
|
2
2
|
const { createAppHandler } = require('./../app-handler-helpers');
|
|
3
3
|
const { loadAppDefinition } = require('./../app-definition-loader');
|
|
4
4
|
const { ModuleFactory } = require('../../modules/module-factory');
|
|
5
|
-
const {
|
|
6
|
-
getModulesDefinitionFromIntegrationClasses,
|
|
7
|
-
} = require('../../integrations/utils/map-integration-dto');
|
|
8
5
|
const {
|
|
9
6
|
createModuleRepository,
|
|
10
7
|
} = require('../../modules/repositories/module-repository-factory');
|
|
@@ -28,18 +25,25 @@ const {
|
|
|
28
25
|
CheckIntegrationsHealthUseCase,
|
|
29
26
|
} = require('../use-cases/check-integrations-health-use-case');
|
|
30
27
|
|
|
28
|
+
const {
|
|
29
|
+
createManagementApiMetaRouter,
|
|
30
|
+
createDocsRedirectRouter,
|
|
31
|
+
} = require('../../management-api/meta-router');
|
|
32
|
+
|
|
31
33
|
const router = Router();
|
|
32
34
|
const healthCheckRepository = createHealthCheckRepository({ prismaClient: prisma });
|
|
33
35
|
|
|
34
36
|
// Load integrations and create factories just like auth router does
|
|
35
37
|
// This verifies the system can properly load integrations
|
|
36
38
|
let moduleFactory, integrationClasses;
|
|
39
|
+
let appDefinition = {};
|
|
37
40
|
try {
|
|
38
41
|
const appDef = loadAppDefinition();
|
|
42
|
+
appDefinition = appDef;
|
|
39
43
|
integrationClasses = appDef.integrations || [];
|
|
40
44
|
|
|
41
45
|
const moduleRepository = createModuleRepository();
|
|
42
|
-
const moduleDefinitions =
|
|
46
|
+
const { moduleDefinitions } = appDef;
|
|
43
47
|
|
|
44
48
|
moduleFactory = new ModuleFactory({
|
|
45
49
|
moduleRepository,
|
|
@@ -85,6 +89,11 @@ const validateApiKey = (req, res, next) => {
|
|
|
85
89
|
next();
|
|
86
90
|
};
|
|
87
91
|
|
|
92
|
+
// Management API discovery (ADR-053 §4) and the /api/docs redirect to it are
|
|
93
|
+
// public and DB-free, so they are mounted ahead of the health API key check.
|
|
94
|
+
router.use(createManagementApiMetaRouter({ appDefinition }));
|
|
95
|
+
router.use(createDocsRedirectRouter());
|
|
96
|
+
|
|
88
97
|
router.use(validateApiKey);
|
|
89
98
|
|
|
90
99
|
// Helper to detect VPC configuration
|