@friggframework/core 2.0.0--canary.682.3c8b68a.0 → 2.0.0--canary.685.022c3c3.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 (34) hide show
  1. package/CLAUDE.md +23 -0
  2. package/README.md +1 -1
  3. package/credential/use-cases/credential-operations-for-user.js +110 -25
  4. package/credential/use-cases/reauthorize-credential-for-user.js +15 -8
  5. package/handlers/app-definition-loader.js +9 -0
  6. package/index.js +4 -0
  7. package/integrations/integration-router.js +7 -5
  8. package/integrations/use-cases/integration-operations-for-user.js +16 -6
  9. package/management-api/management-api-config.js +70 -4
  10. package/management-api/openapi/components.js +1 -0
  11. package/management-api/openapi/v2-operations.js +6 -3
  12. package/management-api/v2/dependencies.js +2 -0
  13. package/management-api/v2/dto.js +3 -0
  14. package/management-api/v2/handlers/credentials.js +6 -2
  15. package/modules/repositories/entity-not-found.js +18 -0
  16. package/modules/repositories/module-repository-documentdb.js +3 -2
  17. package/modules/repositories/module-repository-mongo.js +2 -1
  18. package/modules/repositories/module-repository-postgres.js +2 -1
  19. package/modules/repositories/module-repository.js +2 -1
  20. package/modules/scope-config.js +23 -1
  21. package/modules/scope-policy.js +34 -3
  22. package/modules/scope-resolution.js +11 -0
  23. package/modules/scope-visibility.js +63 -8
  24. package/modules/use-cases/authorize-entity-for-user.js +4 -2
  25. package/modules/use-cases/entity-operations-for-user.js +9 -4
  26. package/modules/use-cases/execute-entity-proxy-request.js +50 -18
  27. package/modules/use-cases/get-entity-module-for-user.js +9 -2
  28. package/modules/use-cases/manage-access.js +21 -0
  29. package/modules/use-cases/multi-step-authorization.js +4 -1
  30. package/package.json +5 -5
  31. package/types/modules/index.d.ts +42 -0
  32. package/types/user/index.d.ts +7 -0
  33. package/user/use-cases/authenticate-user.js +7 -1
  34. package/user/user.js +24 -1
@@ -68,12 +68,49 @@ declare module "@friggframework/core" {
68
68
  owner?: "derive" | "individual" | "organization";
69
69
  }
70
70
 
71
+ /** One operation a module or the app lets the entity proxy forward (ADR-052). */
72
+ export interface ProxyAllowRule {
73
+ method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "*";
74
+ /** Relative to the module's base URL. `:param` is one segment; a trailing `/**` is a subtree. */
75
+ path: string;
76
+ }
77
+
78
+ /** `appDefinition.managementApi.proxy`: the entity proxy, POST /api/v2/entities/:id/proxy (ADR-052). */
79
+ export interface ManagementApiProxyConfig {
80
+ /** Mount the route. Default false. */
81
+ enable?: boolean;
82
+ /** Upstream timeout in ms; can only be lowered from 25000. */
83
+ timeoutMs?: number;
84
+ /** Largest request body in bytes; default 1 MiB, at most 10 MiB. */
85
+ maxRequestBytes?: number;
86
+ /** Largest response body in bytes; can only be lowered from 5 MiB. */
87
+ maxResponseBytes?: number;
88
+ /**
89
+ * Entity scopes the proxy accepts (ADR-056 §3.5). Default `['individual']`.
90
+ * Add `'organization'` to let a member with use access proxy through the
91
+ * organization's shared credential. `global` is never accepted. An unknown,
92
+ * empty or repeated value throws ManagementApiConfigError at load.
93
+ */
94
+ scopes?: Array<Exclude<EntityScope, "global">>;
95
+ /** Per-module allow-list override; `'module'` uses the module's own list. */
96
+ modules?: Record<string, { allow: "module" | ProxyAllowRule[] }>;
97
+ }
98
+
99
+ /** `appDefinition.managementApi` (ADR-052, ADR-053). */
100
+ export interface ManagementApiConfig {
101
+ /** Serve the deprecated v1 routes. Default true. */
102
+ v1?: boolean;
103
+ proxy?: ManagementApiProxyConfig;
104
+ }
105
+
71
106
  export interface AppDefinition {
72
107
  integrations: any[];
73
108
  /** App-level entity type registry (ADR-059). */
74
109
  entities?: AppDefinitionEntities;
75
110
  /** Who owns what a person connects (ADR-056). */
76
111
  ownership?: AppOwnership;
112
+ /** Management API versions and the entity proxy (ADR-052, ADR-053, ADR-056 §3.5). */
113
+ managementApi?: ManagementApiConfig;
77
114
  [key: string]: unknown;
78
115
  }
79
116
 
@@ -138,6 +175,11 @@ declare module "@friggframework/core" {
138
175
  appDefinition?: Pick<AppDefinition, "entities" | "integrations">
139
176
  ): EntityTypeRegistry;
140
177
 
178
+ /** Invalid `managementApi` setting (today: `proxy.scopes`), thrown at app-definition load. */
179
+ export class ManagementApiConfigError extends Error {
180
+ code: "MANAGEMENT_API_CONFIG_INVALID";
181
+ }
182
+
141
183
  /** Invalid `entities` registry. `code` is always `ENTITY_REGISTRY_INVALID`. */
142
184
  export class EntityRegistryError extends Error {
143
185
  code: "ENTITY_REGISTRY_INVALID";
@@ -62,6 +62,13 @@ declare module "@friggframework/core/user" {
62
62
  * at startup. `strictMembership` wins when both are set.
63
63
  */
64
64
  strictUserValidation?: boolean;
65
+ /**
66
+ * Roles (the adopter token's role claim) that may manage organization-level
67
+ * things (ADR-056 §3). Default `['ADMIN']`; a token with no role claim is
68
+ * `MEMBER`, never admin; a shared-secret (`x-frigg` header) caller needs no
69
+ * role. Compared case-insensitively. Validated at load.
70
+ */
71
+ orgAdminRoles?: string[];
65
72
  authModes?: UserAuthModes;
66
73
  jwtConfig?: AdopterJwtConfig;
67
74
  }
@@ -1,5 +1,6 @@
1
1
  const Boom = require('@hapi/boom');
2
2
  const { getLogger } = require('../../logs');
3
+ const { AUTH_SOURCES } = require('../user');
3
4
 
4
5
  const log = getLogger('frigg.user.authentication');
5
6
 
@@ -68,10 +69,13 @@ class AuthenticateUser {
68
69
  // Validate the API key (authentication)
69
70
  await this.authenticateWithSharedSecret.execute(apiKey);
70
71
  // Get user from x-frigg headers (authorization)
71
- return await this.getUserFromXFriggHeaders.execute(
72
+ const user = await this.getUserFromXFriggHeaders.execute(
72
73
  appUserId,
73
74
  appOrgId
74
75
  );
76
+ // The app's own backend: trusted to manage organization things.
77
+ user.setAuthSource(AUTH_SOURCES.SHARED_SECRET);
78
+ return user;
75
79
  }
76
80
  log.debug('No x-frigg-api-key header, skipping shared secret', {
77
81
  eventName: `${log.name}.shared_secret_skipped`,
@@ -90,6 +94,7 @@ class AuthenticateUser {
90
94
  // Detect JWT format (3 parts separated by dots)
91
95
  if (token && token.split('.').length === 3) {
92
96
  const user = await this.getUserFromAdopterJwt.execute(token);
97
+ user.setAuthSource(AUTH_SOURCES.ADOPTER_JWT);
93
98
  // Validate x-frigg headers match JWT claims if present
94
99
  if (appUserId || appOrgId) {
95
100
  this.validateUserMatch(user, appUserId, appOrgId);
@@ -106,6 +111,7 @@ class AuthenticateUser {
106
111
  const user = await this.getUserFromBearerToken.execute(
107
112
  req.headers.authorization
108
113
  );
114
+ user.setAuthSource(AUTH_SOURCES.BEARER_TOKEN);
109
115
  // Validate x-frigg headers match token user if present
110
116
  if (appUserId || appOrgId) {
111
117
  this.validateUserMatch(user, appUserId, appOrgId);
package/user/user.js CHANGED
@@ -1,5 +1,18 @@
1
1
  const bcrypt = require('bcryptjs');
2
2
 
3
+ /**
4
+ * How the request that produced a `User` proved who it is. Set by
5
+ * AuthenticateUser, never inferred from a missing value. Organization roles
6
+ * (ADR-056 §3, §3.6) depend on it: a shared-secret caller is the app's own
7
+ * backend and is trusted to manage; a person with a token has the role the
8
+ * token carries, or `MEMBER`.
9
+ */
10
+ const AUTH_SOURCES = Object.freeze({
11
+ SHARED_SECRET: 'shared-secret',
12
+ ADOPTER_JWT: 'adopter-jwt',
13
+ BEARER_TOKEN: 'bearer-token',
14
+ });
15
+
3
16
  /**
4
17
  * Represents a user in the system. The User class is a domain entity,
5
18
  * @class User
@@ -21,6 +34,8 @@ class User {
21
34
  // Organization role from an adopter token claim (ADR-056 §2). Per request:
22
35
  // the role stored on a membership is only what the first token carried.
23
36
  this.orgRole = null;
37
+ // How this request authenticated (one of AUTH_SOURCES); null until AuthenticateUser says.
38
+ this.authSource = null;
24
39
 
25
40
  this.config = {
26
41
  primary,
@@ -84,6 +99,14 @@ class User {
84
99
  return this.config.individualUserRequired;
85
100
  }
86
101
 
102
+ getAuthSource() {
103
+ return this.authSource;
104
+ }
105
+
106
+ setAuthSource(authSource) {
107
+ this.authSource = authSource ?? null;
108
+ }
109
+
87
110
  getOrgRole() {
88
111
  return this.orgRole;
89
112
  }
@@ -149,4 +172,4 @@ class User {
149
172
  }
150
173
  }
151
174
 
152
- module.exports = { User };
175
+ module.exports = { User, AUTH_SOURCES };