@friggframework/core 2.0.0--canary.707.df80bfd.0 → 2.0.0--canary.680.d670696.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 +3 -0
- package/CLAUDE.md +0 -28
- package/README.md +0 -105
- package/application/commands/integration-commands.js +2 -2
- package/application/commands/scheduler-commands.js +7 -11
- package/application/commands/user-commands.js +5 -17
- package/core/Worker.js +7 -31
- package/core/create-handler.js +0 -9
- package/credential/use-cases/credential-operations-for-user.js +25 -110
- package/credential/use-cases/reauthorize-credential-for-user.js +8 -15
- package/database/config.js +24 -48
- package/database/documentdb-encryption-service.js +0 -1
- package/database/documentdb-utils.js +1 -6
- package/database/encryption/encryption-config.js +44 -13
- package/database/encryption/encryption-schema-registry.js +31 -88
- package/database/prisma.js +2 -4
- package/generated/prisma-mongodb/edge.js +4 -27
- package/generated/prisma-mongodb/index-browser.js +1 -24
- package/generated/prisma-mongodb/index.d.ts +48 -3228
- package/generated/prisma-mongodb/index.js +4 -27
- package/generated/prisma-mongodb/package.json +1 -1
- package/generated/prisma-mongodb/schema.prisma +0 -65
- package/generated/prisma-mongodb/wasm.js +4 -27
- package/generated/prisma-postgresql/edge.js +4 -27
- package/generated/prisma-postgresql/index-browser.js +1 -24
- package/generated/prisma-postgresql/index.d.ts +51 -3503
- package/generated/prisma-postgresql/index.js +4 -27
- package/generated/prisma-postgresql/package.json +1 -1
- package/generated/prisma-postgresql/schema.prisma +0 -61
- package/generated/prisma-postgresql/wasm.js +4 -27
- package/handlers/app-definition-loader.js +12 -110
- package/handlers/routers/auth.js +12 -40
- package/handlers/routers/health.js +2 -4
- package/handlers/routers/integration-defined-routers.js +92 -144
- package/handlers/routers/integration-webhook-routers.js +64 -102
- package/index.js +1 -64
- package/infrastructure/scheduler/index.js +6 -19
- package/infrastructure/scheduler/scheduler-service-factory.js +7 -55
- package/integrations/index.js +0 -5
- package/integrations/integration-router.js +73 -115
- 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/use-cases/integration-operations-for-user.js +24 -55
- package/integrations/utils/map-integration-dto.js +1 -44
- package/management-api/management-api-config.js +2 -71
- package/management-api/meta-router.js +2 -21
- package/management-api/openapi/components.js +3 -50
- package/management-api/openapi/v2-operations.js +5 -17
- package/management-api/route-registry.js +6 -123
- package/management-api/v2/dependencies.js +15 -40
- package/management-api/v2/dto.js +0 -20
- package/management-api/v2/handlers/authorization.js +0 -1
- package/management-api/v2/handlers/credentials.js +2 -6
- package/management-api/v2/handlers/entities.js +7 -10
- package/management-api/v2/handlers/integrations.js +3 -6
- package/management-api/v2/request.js +1 -32
- package/modules/index.js +0 -8
- package/modules/module-factory.js +0 -12
- package/modules/module.js +2 -53
- package/modules/proxy/proxy-errors.js +1 -12
- package/modules/proxy/proxy-path.js +3 -7
- package/modules/proxy/proxy-policy.js +5 -34
- 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/proxy-request.js +1 -2
- package/modules/requester/requester.js +3 -8
- package/modules/use-cases/entity-operations-for-user.js +13 -50
- package/modules/use-cases/entity-types.js +7 -31
- package/modules/use-cases/execute-entity-proxy-request.js +15 -107
- package/modules/use-cases/get-authorization-requirements.js +2 -2
- package/modules/use-cases/get-entities-for-user.js +13 -26
- package/modules/use-cases/get-entity-module-for-user.js +7 -34
- 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/multi-step-authorization.js +13 -40
- 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 -65
- package/prisma-postgresql/schema.prisma +0 -61
- package/queues/index.js +0 -25
- package/types/index.d.ts +0 -1
- package/types/modules/index.d.ts +1 -157
- 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/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 -230
- package/extensions/index.js +0 -44
- package/extensions/initialize-app.js +0 -123
- package/extensions/prisma-composer.js +0 -270
- 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/resolve-integration-ownership.js +0 -177
- package/modules/entity-scope.js +0 -15
- package/modules/proxy/proxy-bases.js +0 -169
- package/modules/repositories/entity-not-found.js +0 -18
- 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/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/manage-access.js +0 -21
- 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/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
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Queue message authentication for HTTP-delivered queues.
|
|
3
|
-
*
|
|
4
|
-
* An SQS-triggered Lambda is invoked by AWS itself, so its events are trusted
|
|
5
|
-
* by the transport. An HTTP-delivered queue (a Netlify Background Function,
|
|
6
|
-
* a QStash destination) is a public URL: anyone who can reach it can post a
|
|
7
|
-
* message. These helpers let the sending adapter prove a message came from
|
|
8
|
-
* Frigg and let the receiving adapter check that proof before anything runs.
|
|
9
|
-
*
|
|
10
|
-
* Scheme (Frigg-signed messages):
|
|
11
|
-
* timestamp = whole seconds since the epoch, as a decimal string
|
|
12
|
-
* signature = 'v1=' + hex(HMAC-SHA256(secret, `${timestamp}.${rawBody}`))
|
|
13
|
-
* sent as the `x-frigg-queue-timestamp` and `x-frigg-queue-signature`
|
|
14
|
-
* headers. The receiver recomputes the HMAC over the raw body it was given,
|
|
15
|
-
* compares in constant time, and refuses a timestamp more than
|
|
16
|
-
* MAX_MESSAGE_AGE_SECONDS away from its own clock (replay window).
|
|
17
|
-
*
|
|
18
|
-
* Fail closed: with no secret configured, a message is accepted unsigned only
|
|
19
|
-
* when FRIGG_QUEUE_ALLOW_UNSIGNED=true AND the environment is not production
|
|
20
|
-
* (isProductionEnvironment; a deployed function always is). Anything else is
|
|
21
|
-
* refused.
|
|
22
|
-
*/
|
|
23
|
-
const crypto = require('crypto');
|
|
24
|
-
const { isDeployedRuntime } = require('../providers/runtime-detection');
|
|
25
|
-
|
|
26
|
-
const SIGNING_SECRET_ENV = 'FRIGG_QUEUE_SIGNING_SECRET';
|
|
27
|
-
const ALLOW_UNSIGNED_ENV = 'FRIGG_QUEUE_ALLOW_UNSIGNED';
|
|
28
|
-
const SIGNATURE_HEADER = 'x-frigg-queue-signature';
|
|
29
|
-
const TIMESTAMP_HEADER = 'x-frigg-queue-timestamp';
|
|
30
|
-
const SIGNATURE_VERSION = 'v1';
|
|
31
|
-
const MAX_MESSAGE_AGE_SECONDS = 5 * 60;
|
|
32
|
-
|
|
33
|
-
// Stages core already treats as non-production (database/prisma.js bypasses
|
|
34
|
-
// encryption for the same list).
|
|
35
|
-
const NON_PRODUCTION_STAGES = ['dev', 'test', 'local'];
|
|
36
|
-
const NON_PRODUCTION_NODE_ENVS = ['development', 'test'];
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Production unless the environment says otherwise: an unset STAGE and
|
|
40
|
-
* NODE_ENV count as production, so a missing setting never opens the door.
|
|
41
|
-
* A deployed function (AWS Lambda, or the active provider's platform such as
|
|
42
|
-
* a Netlify Function: providers/runtime-detection) is production whatever
|
|
43
|
-
* STAGE says, so a deployed `dev` stage cannot opt out of signatures.
|
|
44
|
-
*
|
|
45
|
-
* @param {Object} [env=process.env]
|
|
46
|
-
* @returns {boolean}
|
|
47
|
-
*/
|
|
48
|
-
function isProductionEnvironment(env = process.env) {
|
|
49
|
-
if (isDeployedRuntime(env)) return true;
|
|
50
|
-
if (NON_PRODUCTION_STAGES.includes(env.STAGE)) return false;
|
|
51
|
-
if (NON_PRODUCTION_NODE_ENVS.includes(env.NODE_ENV)) return false;
|
|
52
|
-
if (env.NETLIFY_DEV === 'true') return false;
|
|
53
|
-
return true;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Unsigned messages are allowed only by an explicit opt-in outside production.
|
|
58
|
-
*
|
|
59
|
-
* @param {Object} [env=process.env]
|
|
60
|
-
* @returns {boolean}
|
|
61
|
-
*/
|
|
62
|
-
function allowsUnsignedMessages(env = process.env) {
|
|
63
|
-
return env[ALLOW_UNSIGNED_ENV] === 'true' && !isProductionEnvironment(env);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* @param {Object} [env=process.env]
|
|
68
|
-
* @returns {string|undefined} the configured signing secret, if non-empty
|
|
69
|
-
*/
|
|
70
|
-
function readSigningSecret(env = process.env) {
|
|
71
|
-
const secret = env[SIGNING_SECRET_ENV];
|
|
72
|
-
return typeof secret === 'string' && secret.length > 0 ? secret : undefined;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function computeSignature(secret, timestamp, rawBody) {
|
|
76
|
-
const mac = crypto
|
|
77
|
-
.createHmac('sha256', secret)
|
|
78
|
-
.update(`${timestamp}.${rawBody}`)
|
|
79
|
-
.digest('hex');
|
|
80
|
-
return `${SIGNATURE_VERSION}=${mac}`;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Sign a raw message body.
|
|
85
|
-
*
|
|
86
|
-
* @param {Object} params
|
|
87
|
-
* @param {string} params.body - the exact string that will be sent
|
|
88
|
-
* @param {string} params.secret
|
|
89
|
-
* @param {number} [params.now] - milliseconds since the epoch (default Date.now())
|
|
90
|
-
* @returns {{ [header: string]: string }} headers to add to the request
|
|
91
|
-
*/
|
|
92
|
-
function signQueueMessage({ body, secret, now = Date.now() }) {
|
|
93
|
-
if (!secret) {
|
|
94
|
-
throw new Error('A signing secret is required to sign a queue message');
|
|
95
|
-
}
|
|
96
|
-
const timestamp = String(Math.floor(now / 1000));
|
|
97
|
-
return {
|
|
98
|
-
[TIMESTAMP_HEADER]: timestamp,
|
|
99
|
-
[SIGNATURE_HEADER]: computeSignature(secret, timestamp, body),
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function constantTimeEqual(a, b) {
|
|
104
|
-
const left = Buffer.from(a, 'utf8');
|
|
105
|
-
const right = Buffer.from(b, 'utf8');
|
|
106
|
-
// timingSafeEqual throws on unequal lengths; the length of a v1 signature
|
|
107
|
-
// is fixed, so comparing lengths first leaks nothing secret.
|
|
108
|
-
if (left.length !== right.length) return false;
|
|
109
|
-
return crypto.timingSafeEqual(left, right);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Verify a Frigg-signed message.
|
|
114
|
-
*
|
|
115
|
-
* @param {Object} params
|
|
116
|
-
* @param {string|null|undefined} params.body - raw body as received
|
|
117
|
-
* @param {string|undefined} params.timestamp - x-frigg-queue-timestamp
|
|
118
|
-
* @param {string|undefined} params.signature - x-frigg-queue-signature
|
|
119
|
-
* @param {string} params.secret
|
|
120
|
-
* @param {number} [params.now] - milliseconds since the epoch
|
|
121
|
-
* @param {number} [params.maxAgeSeconds]
|
|
122
|
-
* @returns {{ ok: true } | { ok: false, reason: string }}
|
|
123
|
-
*/
|
|
124
|
-
function verifyQueueMessage({
|
|
125
|
-
body,
|
|
126
|
-
timestamp,
|
|
127
|
-
signature,
|
|
128
|
-
secret,
|
|
129
|
-
now = Date.now(),
|
|
130
|
-
maxAgeSeconds = MAX_MESSAGE_AGE_SECONDS,
|
|
131
|
-
}) {
|
|
132
|
-
if (!secret) return { ok: false, reason: 'signing_secret_not_configured' };
|
|
133
|
-
if (!signature || !timestamp) return { ok: false, reason: 'missing_signature' };
|
|
134
|
-
if (typeof body !== 'string') return { ok: false, reason: 'missing_body' };
|
|
135
|
-
if (!/^\d{1,15}$/.test(timestamp)) return { ok: false, reason: 'invalid_timestamp' };
|
|
136
|
-
|
|
137
|
-
const ageSeconds = Math.abs(Math.floor(now / 1000) - Number(timestamp));
|
|
138
|
-
if (ageSeconds > maxAgeSeconds) return { ok: false, reason: 'stale_timestamp' };
|
|
139
|
-
|
|
140
|
-
const expected = computeSignature(secret, timestamp, body);
|
|
141
|
-
if (!constantTimeEqual(expected, signature)) {
|
|
142
|
-
return { ok: false, reason: 'invalid_signature' };
|
|
143
|
-
}
|
|
144
|
-
return { ok: true };
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Case-insensitive header lookup over a Lambda/Netlify v1 event's headers.
|
|
149
|
-
*
|
|
150
|
-
* @param {Object} event
|
|
151
|
-
* @param {string} name - lower-case header name
|
|
152
|
-
* @returns {string|undefined}
|
|
153
|
-
*/
|
|
154
|
-
function readHeader(event, name) {
|
|
155
|
-
const headers = event?.headers || {};
|
|
156
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
157
|
-
if (key.toLowerCase() === name) {
|
|
158
|
-
return Array.isArray(value) ? value[0] : value;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
return undefined;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* The raw request body exactly as signed: a string body as is, a base64
|
|
166
|
-
* body decoded to its UTF-8 text. An already-parsed object body cannot be
|
|
167
|
-
* verified (its bytes are gone), so it reads as absent.
|
|
168
|
-
*
|
|
169
|
-
* @param {Object} event
|
|
170
|
-
* @returns {string|null}
|
|
171
|
-
*/
|
|
172
|
-
function readRawBody(event) {
|
|
173
|
-
if (typeof event?.body !== 'string') return null;
|
|
174
|
-
if (event.isBase64Encoded) {
|
|
175
|
-
return Buffer.from(event.body, 'base64').toString('utf8');
|
|
176
|
-
}
|
|
177
|
-
return event.body;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
module.exports = {
|
|
181
|
-
SIGNING_SECRET_ENV,
|
|
182
|
-
ALLOW_UNSIGNED_ENV,
|
|
183
|
-
SIGNATURE_HEADER,
|
|
184
|
-
TIMESTAMP_HEADER,
|
|
185
|
-
MAX_MESSAGE_AGE_SECONDS,
|
|
186
|
-
isProductionEnvironment,
|
|
187
|
-
allowsUnsignedMessages,
|
|
188
|
-
readSigningSecret,
|
|
189
|
-
signQueueMessage,
|
|
190
|
-
verifyQueueMessage,
|
|
191
|
-
readHeader,
|
|
192
|
-
readRawBody,
|
|
193
|
-
};
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Queue Provider Factory
|
|
3
|
-
*
|
|
4
|
-
* Creates a QueueProvider (send / batchSend / parseEvent) for the active
|
|
5
|
-
* platform. From friggframework/frigg#545, adapted: #545 resolved every
|
|
6
|
-
* provider through resolveProvider(), so an AWS app had to install
|
|
7
|
-
* @friggframework/provider-aws. In this stack the AWS SDK is a core
|
|
8
|
-
* dependency, so the built-in queues are created here and only an unknown
|
|
9
|
-
* name goes through the provider plugin system.
|
|
10
|
-
*
|
|
11
|
-
* Priority order for determining the provider:
|
|
12
|
-
* 1. Explicit `provider` option
|
|
13
|
-
* 2. `appDefinition.queue.provider`
|
|
14
|
-
* 3. `QUEUE_PROVIDER` environment variable
|
|
15
|
-
* 4. Default: SQS
|
|
16
|
-
*
|
|
17
|
-
* Built-in names:
|
|
18
|
-
* - 'sqs' | 'aws' -> SqsQueueProvider
|
|
19
|
-
* - 'netlify-background' | 'netlify' -> NetlifyBackgroundProvider
|
|
20
|
-
* - 'qstash' -> QStashQueueProvider
|
|
21
|
-
* Any other name -> require('@friggframework/provider-<name>').QueueProvider
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
const QUEUE_PROVIDERS = {
|
|
25
|
-
SQS: 'sqs',
|
|
26
|
-
NETLIFY_BACKGROUND: 'netlify-background',
|
|
27
|
-
QSTASH: 'qstash',
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
// Platform provider names (appDefinition.provider) that map to a built-in queue.
|
|
31
|
-
const PLATFORM_ALIASES = {
|
|
32
|
-
aws: QUEUE_PROVIDERS.SQS,
|
|
33
|
-
netlify: QUEUE_PROVIDERS.NETLIFY_BACKGROUND,
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Determine the queue provider from the config hierarchy.
|
|
38
|
-
*
|
|
39
|
-
* @param {Object} [options]
|
|
40
|
-
* @param {string} [options.provider]
|
|
41
|
-
* @param {Object} [options.appDefinition]
|
|
42
|
-
* @returns {string|undefined}
|
|
43
|
-
*/
|
|
44
|
-
function determineProvider(options = {}) {
|
|
45
|
-
if (options.provider) {
|
|
46
|
-
return options.provider;
|
|
47
|
-
}
|
|
48
|
-
if (options.appDefinition?.queue?.provider) {
|
|
49
|
-
return options.appDefinition.queue.provider;
|
|
50
|
-
}
|
|
51
|
-
if (process.env.QUEUE_PROVIDER) {
|
|
52
|
-
return process.env.QUEUE_PROVIDER;
|
|
53
|
-
}
|
|
54
|
-
return undefined;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Create a queue provider instance.
|
|
59
|
-
*
|
|
60
|
-
* @param {Object} [options]
|
|
61
|
-
* @param {string} [options.provider] - Queue provider name
|
|
62
|
-
* @param {Object} [options.appDefinition] - reads `queue.provider`
|
|
63
|
-
* @param {Object} [options.providerOptions] - passed to the provider constructor
|
|
64
|
-
* @returns {import('./queue-provider').QueueProvider}
|
|
65
|
-
*/
|
|
66
|
-
function createQueueProvider(options = {}) {
|
|
67
|
-
const explicit = determineProvider(options);
|
|
68
|
-
const name = PLATFORM_ALIASES[explicit] || explicit || QUEUE_PROVIDERS.SQS;
|
|
69
|
-
const providerOptions = options.providerOptions || {};
|
|
70
|
-
|
|
71
|
-
switch (name) {
|
|
72
|
-
case QUEUE_PROVIDERS.SQS: {
|
|
73
|
-
const { SqsQueueProvider } = require('./providers/sqs-queue-provider');
|
|
74
|
-
return new SqsQueueProvider(providerOptions);
|
|
75
|
-
}
|
|
76
|
-
case QUEUE_PROVIDERS.NETLIFY_BACKGROUND: {
|
|
77
|
-
const {
|
|
78
|
-
NetlifyBackgroundProvider,
|
|
79
|
-
} = require('./providers/netlify-background-provider');
|
|
80
|
-
return new NetlifyBackgroundProvider(providerOptions);
|
|
81
|
-
}
|
|
82
|
-
case QUEUE_PROVIDERS.QSTASH: {
|
|
83
|
-
const { QStashQueueProvider } = require('./providers/qstash-queue-provider');
|
|
84
|
-
return new QStashQueueProvider(providerOptions);
|
|
85
|
-
}
|
|
86
|
-
default: {
|
|
87
|
-
const { resolveProvider } = require('../providers/resolve-provider');
|
|
88
|
-
const provider = resolveProvider(null, { provider: name });
|
|
89
|
-
if (!provider.QueueProvider) {
|
|
90
|
-
throw new Error(`Provider '${provider.name || name}' does not export a QueueProvider`);
|
|
91
|
-
}
|
|
92
|
-
return new provider.QueueProvider(providerOptions);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
module.exports = {
|
|
98
|
-
createQueueProvider,
|
|
99
|
-
determineProvider,
|
|
100
|
-
QUEUE_PROVIDERS,
|
|
101
|
-
};
|
package/queues/queue-provider.js
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Queue Provider Interface (Port)
|
|
3
|
-
*
|
|
4
|
-
* Defines the contract for queue message sending.
|
|
5
|
-
* All queue adapters must extend this interface.
|
|
6
|
-
*
|
|
7
|
-
* Following Frigg's hexagonal architecture pattern:
|
|
8
|
-
* - Port defines WHAT the service does (contract)
|
|
9
|
-
* - Adapters implement HOW (AWS SQS, Netlify Background Functions, QStash, etc.)
|
|
10
|
-
*/
|
|
11
|
-
class QueueProvider {
|
|
12
|
-
/**
|
|
13
|
-
* Send a single message to a queue
|
|
14
|
-
*
|
|
15
|
-
* @param {Object} message - Message payload (will be JSON-serialized)
|
|
16
|
-
* @param {string} queueId - Queue identifier (URL, name, or endpoint depending on provider)
|
|
17
|
-
* @returns {Promise<Object>} Provider-specific response
|
|
18
|
-
*/
|
|
19
|
-
async send(message, queueId) {
|
|
20
|
-
throw new Error('Method send must be implemented by subclass');
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Send a batch of messages to a queue
|
|
25
|
-
*
|
|
26
|
-
* @param {Object[]} entries - Array of message payloads
|
|
27
|
-
* @param {string} queueId - Queue identifier
|
|
28
|
-
* @returns {Promise<Object>} Provider-specific response
|
|
29
|
-
*/
|
|
30
|
-
async batchSend(entries, queueId) {
|
|
31
|
-
throw new Error('Method batchSend must be implemented by subclass');
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Parse an incoming event into an array of message payloads.
|
|
36
|
-
* Each provider has its own event shape (SQS Records, HTTP body, etc.)
|
|
37
|
-
*
|
|
38
|
-
* @param {Object} event - Raw event from the runtime (Lambda event, HTTP request, etc.)
|
|
39
|
-
* @returns {Object[]} Array of parsed message payloads
|
|
40
|
-
*/
|
|
41
|
-
parseEvent(event) {
|
|
42
|
-
throw new Error('Method parseEvent must be implemented by subclass');
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Prove an incoming event was sent by Frigg before anything is parsed or
|
|
47
|
-
* dispatched. HTTP-delivered queues (Netlify Background Functions,
|
|
48
|
-
* QStash) are public URLs, so their adapters check a signature here.
|
|
49
|
-
*
|
|
50
|
-
* The default refuses: an adapter that does not know how to verify its
|
|
51
|
-
* events must not have them run. An adapter whose transport already
|
|
52
|
-
* authenticates the caller (SQS triggering a Lambda) overrides this, and
|
|
53
|
-
* must not set `verifiesHttpDelivery`: an HTTP entry point (Netlify's
|
|
54
|
-
* worker-background) consults only adapters that set it to true, since
|
|
55
|
-
* a transport-trust answer says nothing about an HTTP caller.
|
|
56
|
-
*
|
|
57
|
-
* @param {Object} event - Raw event from the runtime
|
|
58
|
-
* @returns {Promise<{ ok: boolean, reason?: string, unsigned?: boolean }>}
|
|
59
|
-
*/
|
|
60
|
-
async verifyEvent(event) {
|
|
61
|
-
return { ok: false, reason: 'verification_not_implemented' };
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
module.exports = { QueueProvider };
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SQS Queue Client (Adapter)
|
|
3
|
-
*
|
|
4
|
-
* AWS SQS implementation of QueueClientInterface, used by Worker.
|
|
5
|
-
*
|
|
6
|
-
* In friggframework/frigg#545 this adapter moved to
|
|
7
|
-
* @friggframework/provider-aws and Worker required it to be injected. In
|
|
8
|
-
* this stack the AWS SDK stays a core dependency, so the adapter lives here,
|
|
9
|
-
* Worker defaults to it, and provider-aws re-exports it. The SDK is
|
|
10
|
-
* required on first use, not at module load.
|
|
11
|
-
*/
|
|
12
|
-
const { QueueClientInterface } = require('./queue-client-interface');
|
|
13
|
-
|
|
14
|
-
let sqsModule = null;
|
|
15
|
-
function getSqsModule() {
|
|
16
|
-
if (!sqsModule) {
|
|
17
|
-
sqsModule = require('@aws-sdk/client-sqs');
|
|
18
|
-
}
|
|
19
|
-
return sqsModule;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Client config #545's adapter used: IS_OFFLINE credentials, AWS_REGION and
|
|
24
|
-
* AWS_ENDPOINT.
|
|
25
|
-
*/
|
|
26
|
-
function defaultSqsClientConfig() {
|
|
27
|
-
const config = {};
|
|
28
|
-
if (process.env.IS_OFFLINE) {
|
|
29
|
-
config.credentials = {
|
|
30
|
-
accessKeyId: 'test-aws-key',
|
|
31
|
-
secretAccessKey: 'test-aws-secret',
|
|
32
|
-
};
|
|
33
|
-
config.region = 'us-east-1';
|
|
34
|
-
}
|
|
35
|
-
if (!config.region && process.env.AWS_REGION) {
|
|
36
|
-
config.region = process.env.AWS_REGION;
|
|
37
|
-
}
|
|
38
|
-
if (process.env.AWS_ENDPOINT) {
|
|
39
|
-
config.endpoint = process.env.AWS_ENDPOINT;
|
|
40
|
-
}
|
|
41
|
-
return config;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
class SqsQueueClient extends QueueClientInterface {
|
|
45
|
-
/**
|
|
46
|
-
* @param {Object} [options]
|
|
47
|
-
* @param {Object} [options.clientConfig] - SQSClient config; defaults to
|
|
48
|
-
* defaultSqsClientConfig().
|
|
49
|
-
*/
|
|
50
|
-
constructor({ clientConfig } = {}) {
|
|
51
|
-
super();
|
|
52
|
-
this._clientConfig = clientConfig;
|
|
53
|
-
this._sqs = null;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
_client() {
|
|
57
|
-
if (!this._sqs) {
|
|
58
|
-
const { SQSClient } = getSqsModule();
|
|
59
|
-
this._sqs = new SQSClient(this._clientConfig || defaultSqsClientConfig());
|
|
60
|
-
}
|
|
61
|
-
return this._sqs;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
async sendMessage(params) {
|
|
65
|
-
const { SendMessageCommand } = getSqsModule();
|
|
66
|
-
return this._client().send(new SendMessageCommand(params));
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
async sendMessageBatch(params) {
|
|
70
|
-
const { SendMessageBatchCommand } = getSqsModule();
|
|
71
|
-
return this._client().send(new SendMessageBatchCommand(params));
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
async getQueueUrl(params) {
|
|
75
|
-
const { GetQueueUrlCommand } = getSqsModule();
|
|
76
|
-
const data = await this._client().send(new GetQueueUrlCommand(params));
|
|
77
|
-
return data.QueueUrl;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
module.exports = { SqsQueueClient, defaultSqsClientConfig };
|
package/types/user/index.d.ts
DELETED
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
declare module "@friggframework/core/user" {
|
|
2
|
-
export type AdopterJwtAlgorithm =
|
|
3
|
-
| "ES256"
|
|
4
|
-
| "ES384"
|
|
5
|
-
| "RS256"
|
|
6
|
-
| "RS384"
|
|
7
|
-
| "RS512"
|
|
8
|
-
| "HS256"
|
|
9
|
-
| "HS384"
|
|
10
|
-
| "HS512";
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Verification of tokens issued by the adopter's identity system
|
|
14
|
-
* (`authModes.adopterJwt`, ADR-056 §2). Set exactly one of `jwksUri` or `secret`.
|
|
15
|
-
*/
|
|
16
|
-
export interface AdopterJwtConfig {
|
|
17
|
-
/** https URL of the issuer's JWKS (http only for localhost, 127.0.0.0/8, ::1). */
|
|
18
|
-
jwksUri?: string;
|
|
19
|
-
/** Shared HMAC secret for HS* tokens. */
|
|
20
|
-
secret?: string;
|
|
21
|
-
/** Required `iss`. With `jwksUri`, a startup warning is logged when unset. */
|
|
22
|
-
issuer?: string;
|
|
23
|
-
/** Accepted `aud`. Required with `jwksUri`. */
|
|
24
|
-
audience?: string | string[];
|
|
25
|
-
/** Allow-list. Default ES256 and RS256 with `jwksUri`, HS256 with `secret`. */
|
|
26
|
-
algorithms?: AdopterJwtAlgorithm[];
|
|
27
|
-
/** @deprecated Use `algorithms`. Setting both is an error. */
|
|
28
|
-
algorithm?: AdopterJwtAlgorithm;
|
|
29
|
-
/** Skew allowed on `exp` and `nbf`. Default 30. */
|
|
30
|
-
clockToleranceSec?: number;
|
|
31
|
-
/** Default `sub`. */
|
|
32
|
-
userIdClaim?: string;
|
|
33
|
-
/** Default `org_id`. */
|
|
34
|
-
orgIdClaim?: string;
|
|
35
|
-
/** Default `org_role`. Exposed as `User#getOrgRole()`; stored on a new membership. */
|
|
36
|
-
roleClaim?: string;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface UserAuthModes {
|
|
40
|
-
friggToken?: boolean;
|
|
41
|
-
sharedSecret?: boolean;
|
|
42
|
-
adopterJwt?: boolean;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/** The `user` block of the app definition. */
|
|
46
|
-
export interface UserConfig {
|
|
47
|
-
usePassword?: boolean;
|
|
48
|
-
primary?: "individual" | "organization";
|
|
49
|
-
individualUserRequired?: boolean;
|
|
50
|
-
organizationUserRequired?: boolean;
|
|
51
|
-
/**
|
|
52
|
-
* With `x-frigg` headers, refuse (400) to make a person a member of an
|
|
53
|
-
* organization that already existed: anyone not already a member, a
|
|
54
|
-
* brand-new `appUserId` included, is rejected. Naming an organization the
|
|
55
|
-
* request itself creates is still allowed. Default false (a non-member is
|
|
56
|
-
* added).
|
|
57
|
-
*/
|
|
58
|
-
strictMembership?: boolean;
|
|
59
|
-
/**
|
|
60
|
-
* @deprecated Alias of `strictMembership`, with the same new meaning (it
|
|
61
|
-
* used to reject a mismatched individual/organization pair). Logs a warning
|
|
62
|
-
* at startup. `strictMembership` wins when both are set.
|
|
63
|
-
*/
|
|
64
|
-
strictUserValidation?: boolean;
|
|
65
|
-
/**
|
|
66
|
-
* Roles that may manage organization-level things (ADR-056 §3). Default
|
|
67
|
-
* `['ADMIN']`. The role is the adopter token's role claim, else the stored
|
|
68
|
-
* membership `orgRole`, else `MEMBER` (never admin). A shared-secret
|
|
69
|
-
* (`x-frigg` header) caller needs no role, nor does a Frigg bearer token
|
|
70
|
-
* unless `orgAdminGateBearerTokens`. Compared case-insensitively.
|
|
71
|
-
* Validated at load.
|
|
72
|
-
*/
|
|
73
|
-
orgAdminRoles?: string[];
|
|
74
|
-
/**
|
|
75
|
-
* Judge Frigg bearer-token callers by role like adopter-token callers.
|
|
76
|
-
* Default false: they keep their behaviour from before organization roles.
|
|
77
|
-
*/
|
|
78
|
-
orgAdminGateBearerTokens?: boolean;
|
|
79
|
-
authModes?: UserAuthModes;
|
|
80
|
-
jwtConfig?: AdopterJwtConfig;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* One person's membership of one organization (ADR-056 §3.6). `implicit` is
|
|
85
|
-
* true when it is only the legacy `IndividualUser.organizationId` pointer.
|
|
86
|
-
*/
|
|
87
|
-
export interface OrganizationMembership {
|
|
88
|
-
id: string | null;
|
|
89
|
-
individualUserId: string;
|
|
90
|
-
organizationUserId: string;
|
|
91
|
-
/** Defaults to `MEMBER`. */
|
|
92
|
-
orgRole: string;
|
|
93
|
-
createdAt?: Date;
|
|
94
|
-
implicit: boolean;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export interface OrganizationMembershipRepository {
|
|
98
|
-
/** The stored row or null: the legacy `organizationId` pointer is not read here. */
|
|
99
|
-
findMembership(
|
|
100
|
-
individualUserId: string,
|
|
101
|
-
organizationUserId: string
|
|
102
|
-
): Promise<OrganizationMembership | null>;
|
|
103
|
-
/** Idempotent: adds when absent, never changes an existing membership. */
|
|
104
|
-
addMembership(
|
|
105
|
-
individualUserId: string,
|
|
106
|
-
organizationUserId: string,
|
|
107
|
-
options?: { orgRole?: string | null }
|
|
108
|
-
): Promise<OrganizationMembership>;
|
|
109
|
-
/** Stored rows only, oldest first; legacy-pointer people are not here. */
|
|
110
|
-
listMembershipsForOrganization(
|
|
111
|
-
organizationUserId: string
|
|
112
|
-
): Promise<OrganizationMembership[]>;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export function createOrganizationMembershipRepository(): OrganizationMembershipRepository;
|
|
116
|
-
|
|
117
|
-
export class User {
|
|
118
|
-
/** The primary user's id; under `primary: 'organization'` the ACTIVE organization. */
|
|
119
|
-
getId(): string | undefined;
|
|
120
|
-
/** The organization this request acts in (token or `x-frigg-apporgid`), or null. */
|
|
121
|
-
getActiveOrganizationId(): string | null;
|
|
122
|
-
getAppUserId(): string | null;
|
|
123
|
-
getAppOrgId(): string | null;
|
|
124
|
-
getIndividualUser(): any;
|
|
125
|
-
getOrganizationUser(): any;
|
|
126
|
-
getPrimaryUser(): any;
|
|
127
|
-
/** The organization role from the adopter token for this request, or null. */
|
|
128
|
-
getOrgRole(): string | null;
|
|
129
|
-
setOrgRole(orgRole: string | null): void;
|
|
130
|
-
/** The stored `orgRole` of the membership of the active organization, or null. Used when the token carries no role. */
|
|
131
|
-
getMembershipRole(): string | null;
|
|
132
|
-
setMembershipRole(membershipRole: string | null): void;
|
|
133
|
-
}
|
|
134
|
-
}
|