@friggframework/core 2.0.0--canary.686.3e27a92.0 → 2.0.0--canary.663.8de669c.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/CLAUDE.md +0 -28
- package/README.md +0 -105
- package/application/commands/integration-commands.js +10 -20
- package/application/commands/scheduler-commands.js +7 -11
- package/application/commands/user-commands.js +5 -17
- package/application/index.js +2 -6
- package/core/Worker.js +7 -31
- package/core/create-handler.js +0 -9
- package/credential/repositories/credential-repository-documentdb.js +0 -34
- package/credential/repositories/credential-repository-interface.js +0 -15
- package/credential/repositories/credential-repository-mongo.js +0 -23
- package/credential/repositories/credential-repository-postgres.js +0 -28
- package/database/config.js +24 -48
- package/database/documentdb-utils.js +1 -6
- package/database/encryption/encryption-config.js +31 -10
- package/database/encryption/encryption-schema-registry.js +12 -93
- package/database/prisma.js +2 -4
- package/generated/prisma-mongodb/edge.js +4 -43
- package/generated/prisma-mongodb/index-browser.js +1 -40
- package/generated/prisma-mongodb/index.d.ts +111 -4739
- package/generated/prisma-mongodb/index.js +4 -43
- package/generated/prisma-mongodb/package.json +1 -1
- package/generated/prisma-mongodb/schema.prisma +0 -92
- package/generated/prisma-mongodb/wasm.js +4 -43
- package/generated/prisma-postgresql/edge.js +4 -43
- package/generated/prisma-postgresql/index-browser.js +1 -40
- package/generated/prisma-postgresql/index.d.ts +33 -4966
- package/generated/prisma-postgresql/index.js +4 -43
- package/generated/prisma-postgresql/package.json +1 -1
- package/generated/prisma-postgresql/schema.prisma +0 -87
- package/generated/prisma-postgresql/wasm.js +4 -43
- package/handlers/app-definition-loader.js +19 -121
- package/handlers/app-handler-helpers.js +3 -25
- package/handlers/backend-utils.js +25 -31
- package/handlers/middleware/admin-auth.js +1 -14
- package/handlers/routers/auth.js +10 -46
- package/handlers/routers/health.js +4 -13
- package/handlers/routers/integration-defined-routers.js +92 -144
- package/handlers/routers/integration-webhook-routers.js +64 -102
- package/handlers/workers/integration-defined-workers.js +2 -5
- package/index.js +1 -65
- package/infrastructure/scheduler/index.js +6 -19
- package/infrastructure/scheduler/scheduler-service-factory.js +7 -55
- package/integrations/index.js +0 -1
- package/integrations/integration-router.js +96 -131
- package/integrations/repositories/integration-repository-documentdb.js +3 -35
- package/integrations/repositories/integration-repository-interface.js +1 -17
- package/integrations/repositories/integration-repository-mongo.js +76 -59
- package/integrations/repositories/integration-repository-postgres.js +76 -62
- package/integrations/tests/doubles/test-integration-repository.js +1 -12
- package/integrations/use-cases/create-integration.js +20 -55
- package/integrations/use-cases/get-integrations-for-user.js +40 -80
- package/integrations/use-cases/index.js +1 -7
- package/integrations/utils/map-integration-dto.js +14 -43
- package/modules/index.js +0 -14
- package/modules/module-factory.js +0 -12
- package/modules/module.js +2 -53
- package/modules/repositories/module-repository-documentdb.js +6 -51
- package/modules/repositories/module-repository-interface.js +2 -19
- package/modules/repositories/module-repository-mongo.js +74 -67
- package/modules/repositories/module-repository-postgres.js +74 -66
- package/modules/repositories/module-repository.js +1 -2
- package/modules/requester/requester.js +2 -27
- package/modules/use-cases/get-entities-for-user.js +13 -26
- package/modules/use-cases/get-entity-options-by-id.js +4 -10
- 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 +4 -10
- package/modules/use-cases/process-authorization-callback.js +19 -53
- package/modules/use-cases/refresh-entity-options.js +4 -10
- package/modules/use-cases/test-module-auth.js +4 -10
- package/package.json +5 -6
- package/prisma-mongodb/schema.prisma +0 -92
- package/prisma-postgresql/schema.prisma +0 -87
- package/queues/index.js +0 -25
- package/types/index.d.ts +0 -2
- package/user/repositories/user-repository-documentdb.js +2 -27
- package/user/repositories/user-repository-interface.js +0 -18
- package/user/repositories/user-repository-mongo.js +0 -24
- package/user/repositories/user-repository-postgres.js +0 -18
- package/user/use-cases/authenticate-user.js +1 -7
- package/user/use-cases/get-user-from-adopter-jwt.js +124 -95
- package/user/use-cases/get-user-from-x-frigg-headers.js +80 -83
- package/user/user.js +1 -64
- package/credential/use-cases/credential-operations-for-user.js +0 -202
- package/credential/use-cases/reauthorize-credential-for-user.js +0 -73
- package/database/organization-where.js +0 -18
- package/database/unique-violation.js +0 -25
- package/extensions/bootstrap-runner.js +0 -43
- package/extensions/extension-loader.js +0 -187
- package/extensions/index.js +0 -41
- package/extensions/initialize-app.js +0 -123
- package/extensions/route-mounter.js +0 -103
- package/extensions/schema-composer.js +0 -188
- package/infrastructure/scheduler/netlify-scheduler-adapter.js +0 -216
- package/infrastructure/scheduler/scheduled-job-repository.js +0 -118
- package/integrations/repositories/integration-not-found.js +0 -14
- package/integrations/schedule-config.js +0 -80
- package/integrations/use-cases/dispatch-ongoing-syncs.js +0 -100
- package/integrations/use-cases/integration-operations-for-user.js +0 -256
- package/integrations/use-cases/resolve-integration-ownership.js +0 -177
- package/management-api/create-management-api-router.js +0 -49
- package/management-api/error-response.js +0 -116
- package/management-api/management-api-config.js +0 -151
- package/management-api/meta-router.js +0 -76
- package/management-api/openapi/build-openapi.js +0 -144
- package/management-api/openapi/components.js +0 -408
- package/management-api/openapi/v1-operations.js +0 -83
- package/management-api/openapi/v2-operations.js +0 -264
- package/management-api/route-registry.js +0 -411
- package/management-api/test-support/http-client.js +0 -39
- package/management-api/use-cases/get-management-api-meta.js +0 -57
- package/management-api/v1-deprecation.js +0 -120
- package/management-api/v2/create-v2-router.js +0 -85
- package/management-api/v2/dependencies.js +0 -281
- package/management-api/v2/dto.js +0 -143
- package/management-api/v2/handlers/authorization.js +0 -94
- package/management-api/v2/handlers/credentials.js +0 -33
- package/management-api/v2/handlers/entities.js +0 -77
- package/management-api/v2/handlers/integrations.js +0 -128
- package/management-api/v2/handlers/proxy.js +0 -34
- package/management-api/v2/request.js +0 -85
- package/management-api/v2/test-support/fake-dependencies.js +0 -103
- package/modules/domain/authorization-session.js +0 -144
- package/modules/entity-scope.js +0 -15
- package/modules/entity-type-registry.js +0 -311
- package/modules/proxy/proxy-errors.js +0 -20
- package/modules/proxy/proxy-path.js +0 -179
- package/modules/proxy/proxy-policy.js +0 -248
- package/modules/repositories/authorization-session-repository-documentdb.js +0 -112
- package/modules/repositories/authorization-session-repository-factory.js +0 -31
- package/modules/repositories/authorization-session-repository-interface.js +0 -47
- package/modules/repositories/authorization-session-repository-prisma.js +0 -87
- package/modules/repositories/entity-not-found.js +0 -18
- package/modules/requester/proxy-request.js +0 -244
- package/modules/scope-config.js +0 -192
- package/modules/scope-policy.js +0 -112
- package/modules/scope-resolution.js +0 -288
- package/modules/scope-visibility.js +0 -247
- package/modules/tests/doubles/in-memory-authorization-session-repository.js +0 -45
- package/modules/tests/doubles/ownership-matching.js +0 -18
- package/modules/use-cases/authorize-entity-for-user.js +0 -61
- package/modules/use-cases/create-entity-from-credential.js +0 -113
- package/modules/use-cases/entity-operations-for-user.js +0 -162
- package/modules/use-cases/entity-types.js +0 -139
- package/modules/use-cases/execute-entity-proxy-request.js +0 -286
- package/modules/use-cases/get-authorization-requirements.js +0 -100
- package/modules/use-cases/get-entity-module-for-user.js +0 -113
- package/modules/use-cases/manage-access.js +0 -21
- package/modules/use-cases/multi-step-authorization.js +0 -329
- package/prisma-postgresql/migrations/20261003000000_add_authorization_session/migration.sql +0 -28
- package/prisma-postgresql/migrations/20261015000000_add_organization_membership/migration.sql +0 -26
- package/prisma-postgresql/migrations/20261016000000_add_entity_integration_organization/migration.sql +0 -25
- package/prisma-postgresql/migrations/20261017000000_add_scheduled_job/migration.sql +0 -28
- package/providers/index.js +0 -23
- package/providers/resolve-provider.js +0 -93
- package/providers/runtime-detection.js +0 -124
- package/queues/providers/index.js +0 -11
- package/queues/providers/netlify-background-provider.js +0 -150
- package/queues/providers/qstash-queue-provider.js +0 -219
- package/queues/providers/sqs-queue-provider.js +0 -70
- package/queues/queue-client-interface.js +0 -55
- package/queues/queue-message-auth.js +0 -193
- package/queues/queue-provider-factory.js +0 -101
- package/queues/queue-provider.js +0 -65
- package/queues/sqs-queue-client.js +0 -81
- package/types/modules/index.d.ts +0 -197
- package/types/user/index.d.ts +0 -134
- package/user/adopter-jwt-config.js +0 -177
- package/user/adopter-jwt-errors.js +0 -59
- package/user/adopter-token-verifier.js +0 -122
- package/user/app-user-helpers.js +0 -97
- package/user/create-user-authentication.js +0 -64
- package/user/membership.js +0 -51
- package/user/repositories/organization-membership-record.js +0 -19
- package/user/repositories/organization-membership-repository-documentdb.js +0 -106
- package/user/repositories/organization-membership-repository-factory.js +0 -42
- package/user/repositories/organization-membership-repository-interface.js +0 -57
- package/user/repositories/organization-membership-repository-mongo.js +0 -13
- package/user/repositories/organization-membership-repository-postgres.js +0 -29
- package/user/repositories/organization-membership-repository-prisma.js +0 -81
- package/user/tests/doubles/adopter-jwt-fixtures.js +0 -220
- package/user/use-cases/ensure-organization-membership.js +0 -92
- package/user/use-cases/find-individual-users-by-organization.js +0 -54
|
@@ -6,12 +6,6 @@ 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');
|
|
15
9
|
|
|
16
10
|
const log = getLogger('frigg.http');
|
|
17
11
|
|
|
@@ -32,15 +26,11 @@ const createApp = (applyMiddleware) => {
|
|
|
32
26
|
if (applyMiddleware) applyMiddleware(app);
|
|
33
27
|
|
|
34
28
|
// 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.
|
|
38
29
|
app.use((err, req, res, next) => {
|
|
39
|
-
const boomError =
|
|
30
|
+
const boomError = err.isBoom ? err : Boom.boomify(err);
|
|
40
31
|
const {
|
|
41
32
|
output: { statusCode = 500 },
|
|
42
33
|
} = boomError;
|
|
43
|
-
const versioned = isVersionedManagementApiPath(req.path);
|
|
44
34
|
|
|
45
35
|
if (statusCode >= 500) {
|
|
46
36
|
log.error('Request failed', {
|
|
@@ -49,6 +39,7 @@ const createApp = (applyMiddleware) => {
|
|
|
49
39
|
invocation: summarizeExpressRequest(req),
|
|
50
40
|
error: boomError,
|
|
51
41
|
});
|
|
42
|
+
res.status(statusCode).json({ error: 'Internal Server Error' });
|
|
52
43
|
} else {
|
|
53
44
|
// A client error needs no stack; the logger scrubs the reason.
|
|
54
45
|
log.warn('Request rejected', {
|
|
@@ -56,21 +47,8 @@ const createApp = (applyMiddleware) => {
|
|
|
56
47
|
statusCode,
|
|
57
48
|
reason: boomError.message,
|
|
58
49
|
});
|
|
50
|
+
res.status(statusCode).json({ error: err.message });
|
|
59
51
|
}
|
|
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 });
|
|
74
52
|
});
|
|
75
53
|
|
|
76
54
|
return app;
|
|
@@ -18,9 +18,8 @@ const {
|
|
|
18
18
|
createModuleRepository,
|
|
19
19
|
} = require('../modules/repositories/module-repository-factory');
|
|
20
20
|
const {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
} = require('../modules/entity-type-registry');
|
|
21
|
+
getModulesDefinitionFromIntegrationClasses,
|
|
22
|
+
} = require('../integrations/utils/map-integration-dto');
|
|
24
23
|
const { processNotFound } = require('../integrations/use-cases/process-errors');
|
|
25
24
|
|
|
26
25
|
const loadRouterFromObject = (IntegrationClass, routerObject) => {
|
|
@@ -62,18 +61,30 @@ const initializeRepositories = () => {
|
|
|
62
61
|
return { processRepository, integrationRepository, moduleRepository };
|
|
63
62
|
};
|
|
64
63
|
|
|
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
|
+
|
|
65
77
|
const loadIntegrationForWebhook = async (integrationId) => {
|
|
66
78
|
const { loadAppDefinition } = require('./app-definition-loader');
|
|
67
|
-
const { integrations: integrationClasses
|
|
68
|
-
loadAppDefinition();
|
|
79
|
+
const { integrations: integrationClasses } = loadAppDefinition();
|
|
69
80
|
|
|
70
81
|
const { integrationRepository, moduleRepository } =
|
|
71
82
|
initializeRepositories();
|
|
72
83
|
|
|
73
|
-
const moduleFactory =
|
|
84
|
+
const moduleFactory = createModuleFactoryWithDefinitions(
|
|
74
85
|
moduleRepository,
|
|
75
|
-
|
|
76
|
-
|
|
86
|
+
integrationClasses
|
|
87
|
+
);
|
|
77
88
|
|
|
78
89
|
const getIntegrationInstance = new GetIntegrationInstance({
|
|
79
90
|
integrationRepository,
|
|
@@ -118,23 +129,13 @@ const integrationExists = async (integrationId) => {
|
|
|
118
129
|
}
|
|
119
130
|
};
|
|
120
131
|
|
|
121
|
-
const loadIntegrationForProcess = async (
|
|
122
|
-
processId,
|
|
123
|
-
integrationClass,
|
|
124
|
-
moduleDefinitions
|
|
125
|
-
) => {
|
|
132
|
+
const loadIntegrationForProcess = async (processId, integrationClass) => {
|
|
126
133
|
const { processRepository, integrationRepository, moduleRepository } =
|
|
127
134
|
initializeRepositories();
|
|
128
135
|
|
|
129
|
-
const moduleFactory =
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
moduleDefinitions ??
|
|
133
|
-
// No app definition handed in: this integration's own modules only.
|
|
134
|
-
getEntityTypeDefinitions(
|
|
135
|
-
getEntityTypeRegistry({ integrations: [integrationClass] })
|
|
136
|
-
),
|
|
137
|
-
});
|
|
136
|
+
const moduleFactory = createModuleFactoryWithDefinitions(moduleRepository, [
|
|
137
|
+
integrationClass,
|
|
138
|
+
]);
|
|
138
139
|
|
|
139
140
|
const getIntegrationInstance = new GetIntegrationInstance({
|
|
140
141
|
integrationRepository,
|
|
@@ -160,13 +161,7 @@ const loadIntegrationForProcess = async (
|
|
|
160
161
|
return instance;
|
|
161
162
|
};
|
|
162
163
|
|
|
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 } = {}) => {
|
|
164
|
+
const createQueueWorker = (integrationClass) => {
|
|
170
165
|
const integrationName = integrationClass.Definition.name;
|
|
171
166
|
|
|
172
167
|
class QueueWorker extends Worker {
|
|
@@ -188,8 +183,7 @@ const createQueueWorker = (integrationClass, { moduleDefinitions } = {}) => {
|
|
|
188
183
|
console.log(`[QueueWorker] hydrating by processId`, logCtx);
|
|
189
184
|
integrationInstance = await loadIntegrationForProcess(
|
|
190
185
|
params.data.processId,
|
|
191
|
-
integrationClass
|
|
192
|
-
moduleDefinitions
|
|
186
|
+
integrationClass
|
|
193
187
|
);
|
|
194
188
|
console.log(`[QueueWorker] hydrated`, {
|
|
195
189
|
...logCtx,
|
|
@@ -29,19 +29,6 @@ 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
|
-
|
|
45
32
|
/**
|
|
46
33
|
* Validate admin API key from request header
|
|
47
34
|
* @param {import('express').Request} req
|
|
@@ -83,4 +70,4 @@ function validateAdminApiKey(req, res, next) {
|
|
|
83
70
|
next();
|
|
84
71
|
}
|
|
85
72
|
|
|
86
|
-
module.exports = { validateAdminApiKey
|
|
73
|
+
module.exports = { validateAdminApiKey };
|
package/handlers/routers/auth.js
CHANGED
|
@@ -1,51 +1,15 @@
|
|
|
1
|
-
const
|
|
2
|
-
const {
|
|
3
|
-
createManagementApiRouter,
|
|
4
|
-
} = require('../../management-api/create-management-api-router');
|
|
5
|
-
const {
|
|
6
|
-
createExtensionRoutesRouter,
|
|
7
|
-
} = require('../../extensions/route-mounter');
|
|
1
|
+
const { createIntegrationRouter } = require('@friggframework/core');
|
|
8
2
|
const { createAppHandler } = require('./../app-handler-helpers');
|
|
9
3
|
|
|
10
|
-
|
|
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;
|
|
4
|
+
const router = createIntegrationRouter();
|
|
19
5
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
6
|
+
router.route('/api/integrations/redirect/:appId').get((req, res) => {
|
|
7
|
+
res.redirect(
|
|
8
|
+
`${process.env.FRONTEND_URI}/redirect/${req.params.appId
|
|
9
|
+
}?${new URLSearchParams(req.query)}`
|
|
10
|
+
);
|
|
11
|
+
});
|
|
23
12
|
|
|
24
|
-
|
|
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
|
-
});
|
|
13
|
+
const handler = createAppHandler('HTTP Event: Auth', router);
|
|
32
14
|
|
|
33
|
-
|
|
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
|
-
};
|
|
15
|
+
module.exports = { handler };
|
|
@@ -2,6 +2,9 @@ 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');
|
|
5
8
|
const {
|
|
6
9
|
createModuleRepository,
|
|
7
10
|
} = require('../../modules/repositories/module-repository-factory');
|
|
@@ -25,25 +28,18 @@ const {
|
|
|
25
28
|
CheckIntegrationsHealthUseCase,
|
|
26
29
|
} = require('../use-cases/check-integrations-health-use-case');
|
|
27
30
|
|
|
28
|
-
const {
|
|
29
|
-
createManagementApiMetaRouter,
|
|
30
|
-
createDocsRedirectRouter,
|
|
31
|
-
} = require('../../management-api/meta-router');
|
|
32
|
-
|
|
33
31
|
const router = Router();
|
|
34
32
|
const healthCheckRepository = createHealthCheckRepository({ prismaClient: prisma });
|
|
35
33
|
|
|
36
34
|
// Load integrations and create factories just like auth router does
|
|
37
35
|
// This verifies the system can properly load integrations
|
|
38
36
|
let moduleFactory, integrationClasses;
|
|
39
|
-
let appDefinition = {};
|
|
40
37
|
try {
|
|
41
38
|
const appDef = loadAppDefinition();
|
|
42
|
-
appDefinition = appDef;
|
|
43
39
|
integrationClasses = appDef.integrations || [];
|
|
44
40
|
|
|
45
41
|
const moduleRepository = createModuleRepository();
|
|
46
|
-
const
|
|
42
|
+
const moduleDefinitions = getModulesDefinitionFromIntegrationClasses(integrationClasses);
|
|
47
43
|
|
|
48
44
|
moduleFactory = new ModuleFactory({
|
|
49
45
|
moduleRepository,
|
|
@@ -89,11 +85,6 @@ const validateApiKey = (req, res, next) => {
|
|
|
89
85
|
next();
|
|
90
86
|
};
|
|
91
87
|
|
|
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
|
-
|
|
97
88
|
router.use(validateApiKey);
|
|
98
89
|
|
|
99
90
|
// Helper to detect VPC configuration
|
|
@@ -1,169 +1,117 @@
|
|
|
1
1
|
const { createAppHandler } = require('./../app-handler-helpers');
|
|
2
|
-
const {
|
|
2
|
+
const {
|
|
3
|
+
loadAppDefinition,
|
|
4
|
+
} = require('../app-definition-loader');
|
|
3
5
|
const express = require('express');
|
|
4
6
|
const { Router } = express;
|
|
5
7
|
const { loadRouterFromObject } = require('../backend-utils');
|
|
6
8
|
const { getExtensionRoutes } = require('../../integrations/extension');
|
|
7
9
|
|
|
10
|
+
const handlers = {};
|
|
11
|
+
const { integrations: integrationClasses } = loadAppDefinition();
|
|
12
|
+
|
|
8
13
|
const routeKey = (method, path) => `${(method || 'ANY').toUpperCase()} ${path}`;
|
|
9
14
|
|
|
10
15
|
// Serverless function keys must be alphanumeric; binding keys are developer-chosen.
|
|
11
16
|
const sanitizeBindingKey = (name) => String(name).replace(/[^A-Za-z0-9]/g, '');
|
|
12
17
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
//todo: this should be in a use case class
|
|
28
|
-
for (const IntegrationClass of integrationClasses) {
|
|
29
|
-
const router = Router();
|
|
30
|
-
const basePath = `/api/${IntegrationClass.Definition.name}-integration`;
|
|
31
|
-
// Track (method, path) tuples to fail fast on conflicts between Definition.routes,
|
|
32
|
-
// extension routes, or two extensions claiming the same path.
|
|
33
|
-
const claimedRoutes = new Map();
|
|
34
|
-
const claim = (method, path, source) => {
|
|
35
|
-
const key = routeKey(method, path);
|
|
36
|
-
if (claimedRoutes.has(key)) {
|
|
37
|
-
const prev = claimedRoutes.get(key);
|
|
38
|
-
throw new Error(
|
|
39
|
-
`Integration "${IntegrationClass.Definition.name}" route conflict: ` +
|
|
40
|
-
`${key} declared by ${prev} and ${source}`
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
claimedRoutes.set(key, source);
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
console.log(
|
|
47
|
-
`\n│ Configuring routes for ${IntegrationClass.Definition.name} Integration:`
|
|
48
|
-
);
|
|
49
|
-
|
|
50
|
-
const routes = IntegrationClass.Definition.routes || [];
|
|
51
|
-
for (const routeDef of routes) {
|
|
52
|
-
if (typeof routeDef === 'function') {
|
|
53
|
-
router.use(basePath, routeDef(IntegrationClass));
|
|
54
|
-
console.log(`│ ANY ${basePath}/* (function handler)`);
|
|
55
|
-
} else if (routeDef instanceof express.Router) {
|
|
56
|
-
router.use(basePath, routeDef);
|
|
57
|
-
console.log(`│ ANY ${basePath}/* (express router)`);
|
|
58
|
-
} else if (typeof routeDef === 'object') {
|
|
59
|
-
claim(routeDef.method, routeDef.path, 'Definition.routes');
|
|
60
|
-
router.use(
|
|
61
|
-
basePath,
|
|
62
|
-
loadRouterFromObject(IntegrationClass, routeDef)
|
|
63
|
-
);
|
|
64
|
-
const method = (routeDef.method || 'ANY').toUpperCase();
|
|
65
|
-
const fullPath = `${basePath}${routeDef.path}`;
|
|
66
|
-
console.log(`│ ${method} ${fullPath}`);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Each extension binding gets its own namespaced handler (/{bindingName}),
|
|
71
|
-
// so two modules' extensions can share a path like /webhooks without colliding.
|
|
72
|
-
const bindingGroups = new Map();
|
|
73
|
-
for (const extRoute of getExtensionRoutes(IntegrationClass)) {
|
|
74
|
-
const namespacedPath = `/${extRoute.bindingName}${extRoute.path}`;
|
|
75
|
-
claim(
|
|
76
|
-
extRoute.method,
|
|
77
|
-
namespacedPath,
|
|
78
|
-
`extension "${extRoute.extensionName}" (binding "${extRoute.bindingName}")`
|
|
79
|
-
);
|
|
80
|
-
if (!bindingGroups.has(extRoute.bindingName)) {
|
|
81
|
-
bindingGroups.set(extRoute.bindingName, {
|
|
82
|
-
router: Router(),
|
|
83
|
-
useDatabase: extRoute.useDatabase,
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
const group = bindingGroups.get(extRoute.bindingName);
|
|
87
|
-
group.router.use(
|
|
88
|
-
`${basePath}/${extRoute.bindingName}`,
|
|
89
|
-
loadRouterFromObject(IntegrationClass, extRoute)
|
|
90
|
-
);
|
|
91
|
-
console.log(
|
|
92
|
-
`│ ${extRoute.method.toUpperCase()} ${basePath}/${
|
|
93
|
-
extRoute.bindingName
|
|
94
|
-
}${extRoute.path} (extension: ${
|
|
95
|
-
extRoute.extensionName
|
|
96
|
-
}, useDatabase: ${extRoute.useDatabase})`
|
|
18
|
+
//todo: this should be in a use case class
|
|
19
|
+
for (const IntegrationClass of integrationClasses) {
|
|
20
|
+
const router = Router();
|
|
21
|
+
const basePath = `/api/${IntegrationClass.Definition.name}-integration`;
|
|
22
|
+
// Track (method, path) tuples to fail fast on conflicts between Definition.routes,
|
|
23
|
+
// extension routes, or two extensions claiming the same path.
|
|
24
|
+
const claimedRoutes = new Map();
|
|
25
|
+
const claim = (method, path, source) => {
|
|
26
|
+
const key = routeKey(method, path);
|
|
27
|
+
if (claimedRoutes.has(key)) {
|
|
28
|
+
const prev = claimedRoutes.get(key);
|
|
29
|
+
throw new Error(
|
|
30
|
+
`Integration "${IntegrationClass.Definition.name}" route conflict: ` +
|
|
31
|
+
`${key} declared by ${prev} and ${source}`
|
|
97
32
|
);
|
|
98
33
|
}
|
|
99
|
-
|
|
34
|
+
claimedRoutes.set(key, source);
|
|
35
|
+
};
|
|
100
36
|
|
|
101
|
-
|
|
102
|
-
key: `${IntegrationClass.Definition.name}`,
|
|
103
|
-
eventName: `HTTP Event: ${IntegrationClass.Definition.name}`,
|
|
104
|
-
router,
|
|
105
|
-
useDatabase: true,
|
|
106
|
-
});
|
|
107
|
-
keys.add(`${IntegrationClass.Definition.name}`);
|
|
37
|
+
console.log(`\n│ Configuring routes for ${IntegrationClass.Definition.name} Integration:`);
|
|
108
38
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
)
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
router: group.router,
|
|
127
|
-
useDatabase: group.useDatabase,
|
|
128
|
-
});
|
|
129
|
-
keys.add(fnKey);
|
|
39
|
+
const routes = IntegrationClass.Definition.routes || [];
|
|
40
|
+
for (const routeDef of routes) {
|
|
41
|
+
if (typeof routeDef === 'function') {
|
|
42
|
+
router.use(basePath, routeDef(IntegrationClass));
|
|
43
|
+
console.log(`│ ANY ${basePath}/* (function handler)`);
|
|
44
|
+
} else if (routeDef instanceof express.Router) {
|
|
45
|
+
router.use(basePath, routeDef);
|
|
46
|
+
console.log(`│ ANY ${basePath}/* (express router)`);
|
|
47
|
+
} else if (typeof routeDef === 'object') {
|
|
48
|
+
claim(routeDef.method, routeDef.path, 'Definition.routes');
|
|
49
|
+
router.use(
|
|
50
|
+
basePath,
|
|
51
|
+
loadRouterFromObject(IntegrationClass, routeDef)
|
|
52
|
+
);
|
|
53
|
+
const method = (routeDef.method || 'ANY').toUpperCase();
|
|
54
|
+
const fullPath = `${basePath}${routeDef.path}`;
|
|
55
|
+
console.log(`│ ${method} ${fullPath}`);
|
|
130
56
|
}
|
|
131
57
|
}
|
|
132
|
-
return entries;
|
|
133
|
-
}
|
|
134
58
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
59
|
+
// Each extension binding gets its own namespaced handler (/{bindingName}),
|
|
60
|
+
// so two modules' extensions can share a path like /webhooks without colliding.
|
|
61
|
+
const bindingGroups = new Map();
|
|
62
|
+
for (const extRoute of getExtensionRoutes(IntegrationClass)) {
|
|
63
|
+
const namespacedPath = `/${extRoute.bindingName}${extRoute.path}`;
|
|
64
|
+
claim(
|
|
65
|
+
extRoute.method,
|
|
66
|
+
namespacedPath,
|
|
67
|
+
`extension "${extRoute.extensionName}" (binding "${extRoute.bindingName}")`
|
|
68
|
+
);
|
|
69
|
+
if (!bindingGroups.has(extRoute.bindingName)) {
|
|
70
|
+
bindingGroups.set(extRoute.bindingName, {
|
|
71
|
+
router: Router(),
|
|
72
|
+
useDatabase: extRoute.useDatabase,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
const group = bindingGroups.get(extRoute.bindingName);
|
|
76
|
+
group.router.use(
|
|
77
|
+
`${basePath}/${extRoute.bindingName}`,
|
|
78
|
+
loadRouterFromObject(IntegrationClass, extRoute)
|
|
79
|
+
);
|
|
80
|
+
console.log(
|
|
81
|
+
`│ ${extRoute.method.toUpperCase()} ${basePath}/${extRoute.bindingName}${extRoute.path} (extension: ${extRoute.extensionName}, useDatabase: ${extRoute.useDatabase})`
|
|
82
|
+
);
|
|
147
83
|
}
|
|
148
|
-
|
|
149
|
-
}
|
|
84
|
+
console.log('│');
|
|
150
85
|
|
|
151
|
-
|
|
152
|
-
// see devtools integration-builder). Built at require time on purpose: a
|
|
153
|
-
// route conflict fails the cold start, not the first request.
|
|
154
|
-
const handlers = {};
|
|
155
|
-
for (const entry of buildIntegrationRouters(loadAppDefinition().integrations)) {
|
|
156
|
-
handlers[entry.key] = {
|
|
86
|
+
handlers[`${IntegrationClass.Definition.name}`] = {
|
|
157
87
|
handler: createAppHandler(
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
entry.useDatabase
|
|
88
|
+
`HTTP Event: ${IntegrationClass.Definition.name}`,
|
|
89
|
+
router
|
|
161
90
|
),
|
|
162
91
|
};
|
|
92
|
+
|
|
93
|
+
for (const [bindingName, group] of bindingGroups) {
|
|
94
|
+
// Wire contract: integration-builder.js (devtools) derives the identical
|
|
95
|
+
// function key for the serverless config. Keep both in sync.
|
|
96
|
+
const fnKey = `${IntegrationClass.Definition.name}__${sanitizeBindingKey(
|
|
97
|
+
bindingName
|
|
98
|
+
)}`;
|
|
99
|
+
// Distinct binding keys can sanitize to the same fnKey — fail loud rather than overwrite.
|
|
100
|
+
if (Object.prototype.hasOwnProperty.call(handlers, fnKey)) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
`Integration "${IntegrationClass.Definition.name}" extension handler conflict: ` +
|
|
103
|
+
`binding "${bindingName}" sanitizes to "${fnKey}", which is already taken. ` +
|
|
104
|
+
`Use binding keys that are distinct after stripping non-alphanumeric characters.`
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
handlers[fnKey] = {
|
|
108
|
+
handler: createAppHandler(
|
|
109
|
+
`HTTP Event: ${IntegrationClass.Definition.name} extension ${bindingName}`,
|
|
110
|
+
group.router,
|
|
111
|
+
group.useDatabase
|
|
112
|
+
),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
163
115
|
}
|
|
164
116
|
|
|
165
|
-
module.exports = {
|
|
166
|
-
handlers,
|
|
167
|
-
buildIntegrationRouters,
|
|
168
|
-
createIntegrationDefinedRouter,
|
|
169
|
-
};
|
|
117
|
+
module.exports = { handlers };
|