@friggframework/core 2.0.0--canary.680.6bacd26.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.
Files changed (86) hide show
  1. package/CHANGELOG.md +0 -11
  2. package/application/commands/integration-commands.js +8 -18
  3. package/application/index.js +2 -6
  4. package/credential/repositories/credential-repository-documentdb.js +0 -34
  5. package/credential/repositories/credential-repository-interface.js +0 -15
  6. package/credential/repositories/credential-repository-mongo.js +0 -23
  7. package/credential/repositories/credential-repository-postgres.js +0 -28
  8. package/database/encryption/README.md +0 -2
  9. package/database/encryption/encryption-schema-registry.js +12 -41
  10. package/database/encryption/field-encryption-service.js +5 -69
  11. package/generated/prisma-mongodb/edge.js +4 -20
  12. package/generated/prisma-mongodb/index-browser.js +1 -17
  13. package/generated/prisma-mongodb/index.d.ts +95 -1543
  14. package/generated/prisma-mongodb/index.js +4 -20
  15. package/generated/prisma-mongodb/package.json +1 -1
  16. package/generated/prisma-mongodb/schema.prisma +0 -27
  17. package/generated/prisma-mongodb/wasm.js +4 -20
  18. package/generated/prisma-postgresql/edge.js +4 -20
  19. package/generated/prisma-postgresql/index-browser.js +1 -17
  20. package/generated/prisma-postgresql/index.d.ts +84 -1565
  21. package/generated/prisma-postgresql/index.js +4 -20
  22. package/generated/prisma-postgresql/package.json +1 -1
  23. package/generated/prisma-postgresql/schema.prisma +0 -26
  24. package/generated/prisma-postgresql/wasm.js +4 -20
  25. package/handlers/app-definition-loader.js +8 -19
  26. package/handlers/app-handler-helpers.js +3 -25
  27. package/handlers/backend-utils.js +25 -31
  28. package/handlers/middleware/admin-auth.js +1 -14
  29. package/handlers/routers/auth.js +3 -11
  30. package/handlers/routers/health.js +4 -11
  31. package/handlers/workers/integration-defined-workers.js +2 -5
  32. package/index.js +2 -4
  33. package/integrations/index.js +4 -0
  34. package/integrations/integration-router.js +23 -16
  35. package/integrations/utils/map-integration-dto.js +15 -1
  36. package/modules/index.js +0 -6
  37. package/modules/requester/requester.js +2 -27
  38. package/package.json +5 -5
  39. package/prisma-mongodb/schema.prisma +0 -27
  40. package/prisma-postgresql/schema.prisma +0 -26
  41. package/types/index.d.ts +0 -1
  42. package/credential/use-cases/credential-operations-for-user.js +0 -117
  43. package/credential/use-cases/reauthorize-credential-for-user.js +0 -66
  44. package/database/encryption/test-support/fake-ciphertext.js +0 -15
  45. package/integrations/use-cases/integration-operations-for-user.js +0 -225
  46. package/management-api/create-management-api-router.js +0 -49
  47. package/management-api/error-response.js +0 -116
  48. package/management-api/management-api-config.js +0 -82
  49. package/management-api/meta-router.js +0 -57
  50. package/management-api/openapi/build-openapi.js +0 -144
  51. package/management-api/openapi/components.js +0 -368
  52. package/management-api/openapi/v1-operations.js +0 -83
  53. package/management-api/openapi/v2-operations.js +0 -252
  54. package/management-api/route-registry.js +0 -294
  55. package/management-api/test-support/http-client.js +0 -39
  56. package/management-api/use-cases/get-management-api-meta.js +0 -57
  57. package/management-api/v1-deprecation.js +0 -120
  58. package/management-api/v2/create-v2-router.js +0 -85
  59. package/management-api/v2/dependencies.js +0 -256
  60. package/management-api/v2/dto.js +0 -123
  61. package/management-api/v2/handlers/authorization.js +0 -93
  62. package/management-api/v2/handlers/credentials.js +0 -29
  63. package/management-api/v2/handlers/entities.js +0 -74
  64. package/management-api/v2/handlers/integrations.js +0 -125
  65. package/management-api/v2/handlers/proxy.js +0 -34
  66. package/management-api/v2/request.js +0 -54
  67. package/management-api/v2/test-support/fake-dependencies.js +0 -103
  68. package/modules/domain/authorization-session.js +0 -144
  69. package/modules/entity-type-registry.js +0 -311
  70. package/modules/proxy/proxy-errors.js +0 -20
  71. package/modules/proxy/proxy-path.js +0 -179
  72. package/modules/proxy/proxy-policy.js +0 -248
  73. package/modules/repositories/authorization-session-repository-documentdb.js +0 -112
  74. package/modules/repositories/authorization-session-repository-factory.js +0 -31
  75. package/modules/repositories/authorization-session-repository-interface.js +0 -47
  76. package/modules/repositories/authorization-session-repository-prisma.js +0 -87
  77. package/modules/requester/proxy-request.js +0 -244
  78. package/modules/tests/doubles/in-memory-authorization-session-repository.js +0 -45
  79. package/modules/use-cases/entity-operations-for-user.js +0 -125
  80. package/modules/use-cases/entity-types.js +0 -115
  81. package/modules/use-cases/execute-entity-proxy-request.js +0 -239
  82. package/modules/use-cases/get-authorization-requirements.js +0 -100
  83. package/modules/use-cases/get-entity-module-for-user.js +0 -86
  84. package/modules/use-cases/multi-step-authorization.js +0 -302
  85. package/prisma-postgresql/migrations/20261003000000_add_authorization_session/migration.sql +0 -28
  86. package/types/modules/index.d.ts +0 -58
@@ -1,302 +0,0 @@
1
- const Boom = require('@hapi/boom');
2
- const { FetchError } = require('../../errors/fetch-error');
3
- const { getLogger } = require('../../logs');
4
- const { AuthorizationSession } = require('../domain/authorization-session');
5
- const { findDefinition, getAuthStepCount } = require('./entity-types');
6
-
7
- const log = getLogger('frigg.api.authorize');
8
-
9
- const defaultCreateModule = (params) => {
10
- const { Module } = require('../module');
11
- return new Module(params);
12
- };
13
-
14
- const isMultiStep = (definition) =>
15
- getAuthStepCount(definition) > 1 ||
16
- typeof definition.processAuthorizationStep === 'function';
17
-
18
- const sessionNotFound = () =>
19
- Boom.notFound('Authorization session not found or expired', {
20
- code: 'AUTHORIZATION_SESSION_NOT_FOUND',
21
- });
22
-
23
- /**
24
- * Maps a failure from the module or the provider to a client-facing error.
25
- * Boom errors pass through. A provider HTTP failure is a 502; a failed auth
26
- * test or a ClientSafeError is a 400. Anything else stays a 500.
27
- */
28
- function toAuthorizationError(error, { passMessage = false } = {}) {
29
- if (error?.isBoom) return error;
30
- if (error instanceof FetchError) {
31
- return Boom.badGateway('The provider rejected the authorization request', {
32
- code: 'UPSTREAM_ERROR',
33
- expose: true,
34
- details: { upstreamStatus: error.statusCode ?? null },
35
- });
36
- }
37
- if (error?.isClientSafe || passMessage) {
38
- return Boom.badRequest(error?.message || 'Authorization failed', {
39
- code: 'AUTHORIZATION_FAILED',
40
- });
41
- }
42
- if (error?.message === 'Authorization failed') {
43
- return Boom.badRequest(
44
- 'Authorization failed: the credentials did not pass the module auth test',
45
- { code: 'AUTHORIZATION_FAILED' }
46
- );
47
- }
48
- return error;
49
- }
50
-
51
- /**
52
- * Loads the session a caller continues, checking it belongs to them, to the
53
- * entity type and (for re-authorization) to the credential.
54
- */
55
- async function loadSession(repository, sessionId, binding) {
56
- if (typeof sessionId !== 'string' || !sessionId) throw sessionNotFound();
57
- const session = await repository.findBySessionId(sessionId);
58
- if (!session || session.isExpired() || !session.belongsTo(binding)) {
59
- throw sessionNotFound();
60
- }
61
- if (session.completed) {
62
- throw Boom.conflict('Authorization session is already complete', {
63
- code: 'AUTHORIZATION_SESSION_COMPLETED',
64
- });
65
- }
66
- return session;
67
- }
68
-
69
- async function startSession(repository, binding, maxSteps) {
70
- // Expired sessions are removed opportunistically; PostgreSQL has no TTL.
71
- Promise.resolve()
72
- .then(() => repository.deleteExpired())
73
- .catch((error) =>
74
- log.warn('Expired authorization sessions were not removed', {
75
- eventName: `${log.name}.session_cleanup_failed`,
76
- error,
77
- })
78
- );
79
- return repository.create(AuthorizationSession.start({ ...binding, maxSteps }));
80
- }
81
-
82
- /**
83
- * GET /api/v2/authorize (and GET /api/v2/credentials/:id/reauthorize): what
84
- * the caller must submit next. A multi-step flow gets a session on step 1;
85
- * later steps need that sessionId and must be the session's current step.
86
- */
87
- class GetAuthorizationStep {
88
- /**
89
- * @param {Object} params
90
- * @param {Array<Object>} params.moduleDefinitions
91
- * @param {import('./get-authorization-requirements').GetAuthorizationRequirements} params.getAuthorizationRequirements
92
- * @param {import('../repositories/authorization-session-repository-interface').AuthorizationSessionRepositoryInterface} params.authorizationSessionRepository
93
- */
94
- constructor({ moduleDefinitions, getAuthorizationRequirements, authorizationSessionRepository }) {
95
- this.moduleDefinitions = moduleDefinitions || [];
96
- this.getAuthorizationRequirements = getAuthorizationRequirements;
97
- this.sessions = authorizationSessionRepository;
98
- }
99
-
100
- async execute({ user, entityType, step = 1, sessionId, credentialId = null, state }) {
101
- const definition = findDefinition(this.moduleDefinitions, entityType);
102
- const userId = user.getId();
103
- const requirementsFor = (n) =>
104
- this.getAuthorizationRequirements.execute({ entityType, step: n, userId, state });
105
-
106
- if (!isMultiStep(definition)) return requirementsFor(step);
107
-
108
- const binding = { userId, entityType, credentialId };
109
- let session;
110
- if (sessionId) {
111
- session = await loadSession(this.sessions, sessionId, binding);
112
- if (step !== session.currentStep) {
113
- throw Boom.conflict(
114
- `This session expects step ${session.currentStep}`,
115
- { code: 'STEP_OUT_OF_ORDER', details: { expectedStep: session.currentStep } }
116
- );
117
- }
118
- } else {
119
- if (step !== 1) {
120
- throw Boom.badRequest('sessionId is required after step 1', {
121
- code: 'SESSION_REQUIRED',
122
- });
123
- }
124
- session = await startSession(this.sessions, binding, getAuthStepCount(definition));
125
- }
126
- return { ...(await requirementsFor(step)), sessionId: session.sessionId };
127
- }
128
- }
129
-
130
- /**
131
- * POST /api/v2/authorize (and POST /api/v2/credentials/:id/reauthorize).
132
- *
133
- * Single-step modules complete at once through ProcessAuthorizationCallback
134
- * (the v1 authorize logic). Multi-step modules run
135
- * definition.processAuthorizationStep(api, step, data, stepData) per step;
136
- * when it reports `completed`, its `authData` goes through the same
137
- * callback. Returns either
138
- * `{ status: 'complete', entity, credential }` (domain records; the handler
139
- * masks them) or
140
- * `{ status: 'pending', step, totalSteps, sessionId, requirements, message? }`.
141
- */
142
- class SubmitAuthorizationStep {
143
- constructor({
144
- moduleDefinitions,
145
- getAuthorizationRequirements,
146
- authorizationSessionRepository,
147
- processAuthorizationCallback,
148
- moduleRepository,
149
- credentialRepository,
150
- createModule,
151
- }) {
152
- this.moduleDefinitions = moduleDefinitions || [];
153
- this.getAuthorizationRequirements = getAuthorizationRequirements;
154
- this.sessions = authorizationSessionRepository;
155
- this.processAuthorizationCallback = processAuthorizationCallback;
156
- this.moduleRepository = moduleRepository;
157
- this.credentialRepository = credentialRepository;
158
- this.createModule = createModule || defaultCreateModule;
159
- }
160
-
161
- async execute({ user, entityType, data, step = 1, sessionId, credentialId = null }) {
162
- if (!data || typeof data !== 'object' || Array.isArray(data)) {
163
- throw Boom.badRequest('data must be an object', {
164
- code: 'VALIDATION_ERROR',
165
- details: { field: 'data' },
166
- });
167
- }
168
- const definition = findDefinition(this.moduleDefinitions, entityType);
169
- const userId = user.getId();
170
-
171
- if (!isMultiStep(definition)) {
172
- if (step !== 1) {
173
- throw Boom.badRequest(`${entityType} has a single authorization step`, {
174
- code: 'INVALID_STEP',
175
- });
176
- }
177
- return this.complete({ userId, entityType, params: data, credentialId });
178
- }
179
-
180
- const totalSteps = getAuthStepCount(definition);
181
- const binding = { userId, entityType, credentialId };
182
- let session;
183
- if (sessionId) {
184
- session = await loadSession(this.sessions, sessionId, binding);
185
- if (step === 1 && session.currentStep !== 1) {
186
- session.restart();
187
- } else if (step !== session.currentStep) {
188
- throw Boom.conflict(`This session expects step ${session.currentStep}`, {
189
- code: 'STEP_OUT_OF_ORDER',
190
- details: { expectedStep: session.currentStep },
191
- });
192
- }
193
- } else {
194
- if (step !== 1) {
195
- throw Boom.badRequest('sessionId is required after step 1', {
196
- code: 'SESSION_REQUIRED',
197
- });
198
- }
199
- session = await startSession(this.sessions, binding, totalSteps);
200
- }
201
-
202
- if (typeof definition.processAuthorizationStep !== 'function') {
203
- throw Boom.badImplementation(
204
- `Module ${entityType} declares ${totalSteps} steps but no processAuthorizationStep`
205
- );
206
- }
207
-
208
- const module = this.createModule({ userId, definition });
209
- let result;
210
- try {
211
- result = await definition.processAuthorizationStep(
212
- module.api,
213
- step,
214
- data,
215
- session.stepData
216
- );
217
- } catch (error) {
218
- // processAuthorizationStep errors are written for the end user
219
- // (docs/MULTI_STEP_AUTH_MIGRATION_GUIDE.md), e.g. "Invalid code".
220
- throw toAuthorizationError(error, { passMessage: true });
221
- }
222
-
223
- if (result?.completed) {
224
- session.markComplete();
225
- await this.sessions.update(session);
226
- const outcome = await this.complete({
227
- userId,
228
- entityType,
229
- params: result.authData || {},
230
- credentialId,
231
- });
232
- // The collected data is no longer needed once the credential exists.
233
- await Promise.resolve()
234
- .then(() => this.sessions.deleteBySessionId(session.sessionId))
235
- .catch(() => {});
236
- return outcome;
237
- }
238
-
239
- const nextStep = result?.nextStep ?? step + 1;
240
- try {
241
- session.advanceTo(nextStep, result?.stepData);
242
- } catch (error) {
243
- throw Boom.badImplementation(
244
- `Module ${entityType} returned an invalid next step: ${error.message}`
245
- );
246
- }
247
- await this.sessions.update(session);
248
-
249
- const requirements = await this.getAuthorizationRequirements.execute({
250
- entityType,
251
- step: nextStep,
252
- userId,
253
- });
254
- return {
255
- status: 'pending',
256
- step: nextStep,
257
- totalSteps,
258
- sessionId: session.sessionId,
259
- requirements,
260
- ...(result?.message && { message: String(result.message) }),
261
- };
262
- }
263
-
264
- async complete({ userId, entityType, params, credentialId }) {
265
- let callback;
266
- try {
267
- callback = await this.processAuthorizationCallback.execute(
268
- userId,
269
- entityType,
270
- params
271
- );
272
- } catch (error) {
273
- throw toAuthorizationError(error);
274
- }
275
- const [entity, credential] = await Promise.all([
276
- this.moduleRepository.findEntityById(callback.entity_id),
277
- this.credentialRepository.findCredentialById(callback.credential_id),
278
- ]);
279
- const outcome = {
280
- status: 'complete',
281
- entity,
282
- credential: credential && {
283
- ...credential,
284
- type: entityType,
285
- entityIds: entity ? [String(entity.id)] : [],
286
- },
287
- };
288
- if (credentialId && String(callback.credential_id) !== String(credentialId)) {
289
- // The provider account differs from the one being re-authorized,
290
- // so a separate credential was stored.
291
- outcome.previousCredentialId = String(credentialId);
292
- }
293
- return outcome;
294
- }
295
- }
296
-
297
- module.exports = {
298
- GetAuthorizationStep,
299
- SubmitAuthorizationStep,
300
- toAuthorizationError,
301
- isMultiStep,
302
- };
@@ -1,28 +0,0 @@
1
- -- CreateTable
2
- -- Multi-step authorization state for Management API v2 (/api/v2/authorize).
3
- -- Short-lived rows; "stepData" is encrypted at rest by the application.
4
- CREATE TABLE "AuthorizationSession" (
5
- "id" SERIAL NOT NULL,
6
- "sessionId" TEXT NOT NULL,
7
- "userId" TEXT NOT NULL,
8
- "entityType" TEXT NOT NULL,
9
- "credentialId" TEXT,
10
- "currentStep" INTEGER NOT NULL DEFAULT 1,
11
- "maxSteps" INTEGER NOT NULL,
12
- "stepData" JSONB NOT NULL DEFAULT '{}',
13
- "expiresAt" TIMESTAMP(3) NOT NULL,
14
- "completed" BOOLEAN NOT NULL DEFAULT false,
15
- "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
16
- "updatedAt" TIMESTAMP(3) NOT NULL,
17
-
18
- CONSTRAINT "AuthorizationSession_pkey" PRIMARY KEY ("id")
19
- );
20
-
21
- -- CreateIndex
22
- CREATE UNIQUE INDEX "AuthorizationSession_sessionId_key" ON "AuthorizationSession"("sessionId");
23
-
24
- -- CreateIndex
25
- CREATE INDEX "AuthorizationSession_userId_entityType_idx" ON "AuthorizationSession"("userId", "entityType");
26
-
27
- -- CreateIndex
28
- CREATE INDEX "AuthorizationSession_expiresAt_idx" ON "AuthorizationSession"("expiresAt");
@@ -1,58 +0,0 @@
1
- declare module "@friggframework/core" {
2
- /** Where an entity type comes from: the app's `entities` map or an integration binding it. */
3
- export type EntityTypeSource =
4
- | { kind: "app" }
5
- | { kind: "integration"; name: string };
6
-
7
- export type EntityScope = "individual" | "organization" | "global";
8
-
9
- /** App-level settings carried from the `entities` entry; empty for an inferred-only type. */
10
- export interface EntityTypeSettings {
11
- scope?: EntityScope | { default?: EntityScope; allow?: EntityScope[] };
12
- proxy?: Record<string, unknown>;
13
- display?: Record<string, unknown>;
14
- }
15
-
16
- export interface EntityTypeEntry {
17
- /** The API module definition. */
18
- definition: any;
19
- /** Frozen. `{ kind: 'app' }` first when registered, then each binding integration. */
20
- sources: readonly EntityTypeSource[];
21
- /** Frozen. */
22
- settings: Readonly<EntityTypeSettings>;
23
- }
24
-
25
- /** Entity type name (the definition's moduleName) to its entry. */
26
- export type EntityTypeRegistry = Map<string, EntityTypeEntry>;
27
-
28
- /** One `appDefinition.entities` entry. The key must equal the definition's moduleName (and getName() when defined). */
29
- export interface EntityRegistration extends EntityTypeSettings {
30
- definition: any;
31
- }
32
-
33
- export interface AppDefinitionEntities {
34
- /** false turns inference off: every module an integration binds must be registered. Default true. */
35
- $infer?: boolean;
36
- [entityType: string]: EntityRegistration | boolean | undefined;
37
- }
38
-
39
- export interface AppDefinition {
40
- integrations: any[];
41
- /** App-level entity type registry (ADR-059). */
42
- entities?: AppDefinitionEntities;
43
- [key: string]: unknown;
44
- }
45
-
46
- /**
47
- * Builds the registry from `entities` plus the modules integrations bind.
48
- * Throws EntityRegistryError on an invalid registry.
49
- */
50
- export function getEntityTypeRegistry(
51
- appDefinition?: Pick<AppDefinition, "entities" | "integrations">
52
- ): EntityTypeRegistry;
53
-
54
- /** Invalid `entities` registry. `code` is always `ENTITY_REGISTRY_INVALID`. */
55
- export class EntityRegistryError extends Error {
56
- code: "ENTITY_REGISTRY_INVALID";
57
- }
58
- }