@friggframework/core 2.0.0--canary.678.58db4b2.0 → 2.0.0--canary.681.d970b9e.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 (47) hide show
  1. package/README.md +15 -3
  2. package/application/commands/user-commands.js +17 -5
  3. package/database/documentdb-utils.js +6 -1
  4. package/database/unique-violation.js +25 -0
  5. package/generated/prisma-mongodb/edge.js +13 -4
  6. package/generated/prisma-mongodb/index-browser.js +10 -1
  7. package/generated/prisma-mongodb/index.d.ts +1922 -94
  8. package/generated/prisma-mongodb/index.js +13 -4
  9. package/generated/prisma-mongodb/package.json +1 -1
  10. package/generated/prisma-mongodb/schema.prisma +32 -0
  11. package/generated/prisma-mongodb/wasm.js +13 -4
  12. package/generated/prisma-postgresql/edge.js +13 -4
  13. package/generated/prisma-postgresql/index-browser.js +10 -1
  14. package/generated/prisma-postgresql/index.d.ts +2053 -83
  15. package/generated/prisma-postgresql/index.js +13 -4
  16. package/generated/prisma-postgresql/package.json +1 -1
  17. package/generated/prisma-postgresql/schema.prisma +29 -0
  18. package/generated/prisma-postgresql/wasm.js +13 -4
  19. package/index.js +12 -0
  20. package/integrations/integration-router.js +8 -30
  21. package/management-api/v2/dependencies.js +2 -11
  22. package/package.json +5 -5
  23. package/prisma-mongodb/schema.prisma +32 -0
  24. package/prisma-postgresql/migrations/20261015000000_add_organization_membership/migration.sql +26 -0
  25. package/prisma-postgresql/schema.prisma +29 -0
  26. package/types/user/index.d.ts +52 -2
  27. package/user/adopter-jwt-errors.js +0 -6
  28. package/user/app-user-helpers.js +32 -8
  29. package/user/create-user-authentication.js +64 -0
  30. package/user/membership.js +51 -0
  31. package/user/repositories/organization-membership-record.js +19 -0
  32. package/user/repositories/organization-membership-repository-documentdb.js +104 -0
  33. package/user/repositories/organization-membership-repository-factory.js +42 -0
  34. package/user/repositories/organization-membership-repository-interface.js +57 -0
  35. package/user/repositories/organization-membership-repository-mongo.js +13 -0
  36. package/user/repositories/organization-membership-repository-postgres.js +29 -0
  37. package/user/repositories/organization-membership-repository-prisma.js +81 -0
  38. package/user/repositories/user-repository-documentdb.js +23 -0
  39. package/user/repositories/user-repository-interface.js +18 -0
  40. package/user/repositories/user-repository-mongo.js +24 -0
  41. package/user/repositories/user-repository-postgres.js +18 -0
  42. package/user/tests/doubles/adopter-jwt-fixtures.js +101 -11
  43. package/user/use-cases/ensure-organization-membership.js +92 -0
  44. package/user/use-cases/find-individual-users-by-organization.js +54 -0
  45. package/user/use-cases/get-user-from-adopter-jwt.js +32 -58
  46. package/user/use-cases/get-user-from-x-frigg-headers.js +49 -38
  47. package/user/user.js +18 -2
package/README.md CHANGED
@@ -393,7 +393,7 @@ value that is not a three-part JWT is not offered to the verifier at all; it fal
393
393
  | `clockToleranceSec` | `30` | Skew allowed on `exp` and `nbf`. |
394
394
  | `userIdClaim` | `sub` | App user id (required in the token). |
395
395
  | `orgIdClaim` | `org_id` | App organization id (optional). |
396
- | `roleClaim` | `org_role` | Read and exposed as `user.getOrgRole()`; not stored yet. |
396
+ | `roleClaim` | `org_role` | Exposed as `user.getOrgRole()` for the request, and stored as the `orgRole` of a person's membership the first time they join that organization (`MEMBER` when the token names none). |
397
397
 
398
398
  Rules worth knowing:
399
399
 
@@ -406,8 +406,20 @@ Rules worth knowing:
406
406
  `token_missing_user_claim`, `token_malformed`. The message never contains the token or its claims.
407
407
  Any other verification failure, such as an unreachable or unresolvable JWKS, is `503`
408
408
  `jwks_unavailable`, not a 401, so clients do not drop good sessions.
409
- - A person belongs to one organization for now: a token naming a second one gets `403`
410
- `multi_org_unsupported` and nothing is moved (ADR-056 §3.6 replaces this with memberships).
409
+ - **The token's organization is the request's active organization**, and a person can belong to
410
+ several (ADR-056 §3.6). The first time a person presents a token for an organization they are
411
+ not a member of, Frigg adds an `OrganizationMembership` and the request runs as that
412
+ organization (`user.getActiveOrganizationId()`; `user.getId()` under `primary: 'organization'`).
413
+ Nothing is moved: `IndividualUser.organizationId` is a legacy pointer, set only on a person's
414
+ first link and read as an implicit membership. The same applies to `x-frigg-apporgid` with
415
+ `sharedSecret`. With `user.strictMembership: true` those headers cannot add anyone to an organization
416
+ that already existed (400 for a non-member, a brand-new `appUserId` included); only an organization
417
+ the request itself creates is allowed. `strictUserValidation` is a deprecated alias with that same
418
+ meaning. See
419
+ [Organization memberships](../../docs/guides/organization-memberships.md). A token naming a second
420
+ organization is no longer a `403` (`multi_org_unsupported` is gone).
421
+ - Two first logins for the same new person or organization, even in different organizations, both
422
+ succeed: a create that loses to a unique constraint finds the winner's row.
411
423
 
412
424
  ### 8. Lambda Utilities (`/lambda`)
413
425
 
@@ -1,6 +1,12 @@
1
1
  const {
2
2
  createUserRepository,
3
3
  } = require('../../user/repositories/user-repository-factory');
4
+ const {
5
+ createOrganizationMembershipRepository,
6
+ } = require('../../user/repositories/organization-membership-repository-factory');
7
+ const {
8
+ FindIndividualUsersByOrganization,
9
+ } = require('../../user/use-cases/find-individual-users-by-organization');
4
10
 
5
11
  const ERROR_CODE_MAP = {
6
12
  USER_NOT_FOUND: 404,
@@ -26,6 +32,12 @@ function mapErrorToResponse(error) {
26
32
  */
27
33
  function createUserCommands() {
28
34
  const userRepository = createUserRepository();
35
+ const findIndividualUsersByOrganization =
36
+ new FindIndividualUsersByOrganization({
37
+ userRepository,
38
+ organizationMembershipRepository:
39
+ createOrganizationMembershipRepository(),
40
+ });
29
41
 
30
42
  return {
31
43
  /**
@@ -170,7 +182,8 @@ function createUserCommands() {
170
182
  },
171
183
 
172
184
  /**
173
- * Find all individual users linked to an organization user
185
+ * Find all individual users who belong to an organization user: those with
186
+ * a membership and those the legacy organizationId pointer places there.
174
187
  * @param {string} organizationUserId - Organization user ID to search for
175
188
  * @returns {Promise<Object[]>} Array of individual user objects (empty if none)
176
189
  */
@@ -182,10 +195,9 @@ function createUserCommands() {
182
195
  throw error;
183
196
  }
184
197
 
185
- const users =
186
- await userRepository.findIndividualUsersByOrganizationId(
187
- organizationUserId
188
- );
198
+ const users = await findIndividualUsersByOrganization.execute(
199
+ organizationUserId
200
+ );
189
201
 
190
202
  return (users || []).map((user) => ({
191
203
  id: user._id?.toString() || user.id,
@@ -58,7 +58,12 @@ async function insertOne(client, collection, document) {
58
58
 
59
59
  // Provide helpful context for common errors
60
60
  if (error.code === 11000) {
61
- throw new Error(`${errorMsg} - Duplicate key violation`);
61
+ // `code` lets callers recognise a lost race without parsing text
62
+ // (see database/unique-violation.js).
63
+ throw Object.assign(
64
+ new Error(`${errorMsg} - Duplicate key violation`),
65
+ { code: 11000 }
66
+ );
62
67
  }
63
68
  throw new Error(errorMsg);
64
69
  }
@@ -0,0 +1,25 @@
1
+ /** Mongo/DocumentDB duplicate-key error code. */
2
+ const DUPLICATE_KEY_CODE = 11000;
3
+ /** Prisma's unique-constraint error code (PostgreSQL and MongoDB). */
4
+ const PRISMA_UNIQUE_CODE = 'P2002';
5
+
6
+ /**
7
+ * Whether `error` is a unique-constraint violation, however the database
8
+ * layer raised it: a Prisma `P2002`, or a duplicate-key `11000`
9
+ * (`documentdb-utils` `insertOne` sets `code` on the error it throws).
10
+ * Matches on the code only, never on message text.
11
+ *
12
+ * @param {unknown} error
13
+ * @returns {boolean}
14
+ */
15
+ function isUniqueViolation(error) {
16
+ return (
17
+ error?.code === PRISMA_UNIQUE_CODE || error?.code === DUPLICATE_KEY_CODE
18
+ );
19
+ }
20
+
21
+ module.exports = {
22
+ isUniqueViolation,
23
+ DUPLICATE_KEY_CODE,
24
+ PRISMA_UNIQUE_CODE,
25
+ };