@contember/engine-tenant-api 2.1.0-rc.1 → 2.1.0-rc.2

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 (72) hide show
  1. package/dist/development/src/model/queries/membership/ProjectMembersQuery.cjs +1 -1
  2. package/dist/development/src/model/queries/membership/ProjectMembersQuery.cjs.map +1 -1
  3. package/dist/development/src/model/queries/membership/ProjectMembersQuery.js +1 -1
  4. package/dist/development/src/model/queries/membership/ProjectMembersQuery.js.map +1 -1
  5. package/dist/development/src/model/service/PersonAccessManager.cjs +2 -2
  6. package/dist/development/src/model/service/PersonAccessManager.cjs.map +1 -1
  7. package/dist/development/src/model/service/PersonAccessManager.js +2 -2
  8. package/dist/development/src/model/service/PersonAccessManager.js.map +1 -1
  9. package/dist/development/src/model/service/apiKey/ApiKeyManager.cjs +4 -3
  10. package/dist/development/src/model/service/apiKey/ApiKeyManager.cjs.map +1 -1
  11. package/dist/development/src/model/service/apiKey/ApiKeyManager.js +4 -3
  12. package/dist/development/src/model/service/apiKey/ApiKeyManager.js.map +1 -1
  13. package/dist/development/src/model/service/idp/IDPManager.cjs +28 -2
  14. package/dist/development/src/model/service/idp/IDPManager.cjs.map +1 -1
  15. package/dist/development/src/model/service/idp/IDPManager.js +28 -2
  16. package/dist/development/src/model/service/idp/IDPManager.js.map +1 -1
  17. package/dist/development/src/model/utils/DatabaseContext.cjs +7 -2
  18. package/dist/development/src/model/utils/DatabaseContext.cjs.map +1 -1
  19. package/dist/development/src/model/utils/DatabaseContext.js +8 -3
  20. package/dist/development/src/model/utils/DatabaseContext.js.map +1 -1
  21. package/dist/development/src/resolvers/mutation/person/DisablePersonMutationResolver.cjs +1 -1
  22. package/dist/development/src/resolvers/mutation/person/DisablePersonMutationResolver.cjs.map +1 -1
  23. package/dist/development/src/resolvers/mutation/person/DisablePersonMutationResolver.js +1 -1
  24. package/dist/development/src/resolvers/mutation/person/DisablePersonMutationResolver.js.map +1 -1
  25. package/dist/production/src/model/queries/membership/ProjectMembersQuery.cjs +1 -1
  26. package/dist/production/src/model/queries/membership/ProjectMembersQuery.cjs.map +1 -1
  27. package/dist/production/src/model/queries/membership/ProjectMembersQuery.js +1 -1
  28. package/dist/production/src/model/queries/membership/ProjectMembersQuery.js.map +1 -1
  29. package/dist/production/src/model/service/PersonAccessManager.cjs +2 -2
  30. package/dist/production/src/model/service/PersonAccessManager.cjs.map +1 -1
  31. package/dist/production/src/model/service/PersonAccessManager.js +2 -2
  32. package/dist/production/src/model/service/PersonAccessManager.js.map +1 -1
  33. package/dist/production/src/model/service/apiKey/ApiKeyManager.cjs +4 -3
  34. package/dist/production/src/model/service/apiKey/ApiKeyManager.cjs.map +1 -1
  35. package/dist/production/src/model/service/apiKey/ApiKeyManager.js +4 -3
  36. package/dist/production/src/model/service/apiKey/ApiKeyManager.js.map +1 -1
  37. package/dist/production/src/model/service/idp/IDPManager.cjs +28 -2
  38. package/dist/production/src/model/service/idp/IDPManager.cjs.map +1 -1
  39. package/dist/production/src/model/service/idp/IDPManager.js +28 -2
  40. package/dist/production/src/model/service/idp/IDPManager.js.map +1 -1
  41. package/dist/production/src/model/utils/DatabaseContext.cjs +7 -2
  42. package/dist/production/src/model/utils/DatabaseContext.cjs.map +1 -1
  43. package/dist/production/src/model/utils/DatabaseContext.js +8 -3
  44. package/dist/production/src/model/utils/DatabaseContext.js.map +1 -1
  45. package/dist/production/src/resolvers/mutation/person/DisablePersonMutationResolver.cjs +1 -1
  46. package/dist/production/src/resolvers/mutation/person/DisablePersonMutationResolver.cjs.map +1 -1
  47. package/dist/production/src/resolvers/mutation/person/DisablePersonMutationResolver.js +1 -1
  48. package/dist/production/src/resolvers/mutation/person/DisablePersonMutationResolver.js.map +1 -1
  49. package/dist/tests/cases/unit/databaseContextRetry.test.d.ts +2 -0
  50. package/dist/tests/cases/unit/databaseContextRetry.test.d.ts.map +1 -0
  51. package/dist/tests/cases/unit/idp/idpManagerUnknownConfigKeys.test.d.ts +2 -0
  52. package/dist/tests/cases/unit/idp/idpManagerUnknownConfigKeys.test.d.ts.map +1 -0
  53. package/dist/tests/tsconfig.tsbuildinfo +1 -1
  54. package/dist/types/model/queries/membership/ProjectMembersQuery.d.ts.map +1 -1
  55. package/dist/types/model/service/PersonAccessManager.d.ts +2 -1
  56. package/dist/types/model/service/PersonAccessManager.d.ts.map +1 -1
  57. package/dist/types/model/service/apiKey/ApiKeyManager.d.ts.map +1 -1
  58. package/dist/types/model/service/idp/IDPManager.d.ts +18 -0
  59. package/dist/types/model/service/idp/IDPManager.d.ts.map +1 -1
  60. package/dist/types/model/utils/DatabaseContext.d.ts +14 -1
  61. package/dist/types/model/utils/DatabaseContext.d.ts.map +1 -1
  62. package/dist/types/tsconfig.tsbuildinfo +1 -1
  63. package/package.json +1 -1
  64. package/src/model/queries/membership/ProjectMembersQuery.ts +1 -0
  65. package/src/model/service/PersonAccessManager.ts +5 -2
  66. package/src/model/service/apiKey/ApiKeyManager.ts +5 -3
  67. package/src/model/service/idp/IDPManager.ts +30 -2
  68. package/src/model/utils/DatabaseContext.ts +27 -6
  69. package/src/resolvers/mutation/person/DisablePersonMutationResolver.ts +1 -1
  70. package/tests/cases/integration/mocked/listProjectMembersQuery.test.ts +8 -3
  71. package/tests/cases/unit/databaseContextRetry.test.ts +80 -0
  72. package/tests/cases/unit/idp/idpManagerUnknownConfigKeys.test.ts +101 -0
@@ -29,7 +29,7 @@ class ProjectMembersQuery extends Database.DatabaseQuery {
29
29
  return qb.where((it) => it.not((it2) => it2.exists(personQuery)));
30
30
  }
31
31
  return qb;
32
- }).limit(this.projectMemberInput.limit ?? void 0, this.projectMemberInput.offset ?? void 0).getResult(db);
32
+ }).orderBy(["identity", "id"]).limit(this.projectMemberInput.limit ?? void 0, this.projectMemberInput.offset ?? void 0).getResult(db);
33
33
  }
34
34
  }
35
35
  exports.ProjectMembersQuery = ProjectMembersQuery;
@@ -1 +1 @@
1
- {"version":3,"file":"ProjectMembersQuery.cjs","sources":["../../../../../../../packages/engine-tenant-api/src/model/queries/membership/ProjectMembersQuery.ts"],"sourcesContent":["import { DatabaseQuery, DatabaseQueryable, SelectBuilder } from '@contember/database'\nimport { MemberType, ProjectMembersInput } from '../../../schema'\nimport { ImplementationException } from '../../../exceptions'\n\nexport class ProjectMembersQuery extends DatabaseQuery<ProjectMembersQueryResult> {\n\tconstructor(\n\t\tprivate readonly projectId: string,\n\t\tprivate readonly projectMemberInput: ProjectMembersInput,\n\t) {\n\t\tsuper()\n\t}\n\n\tasync fetch({ db }: DatabaseQueryable): Promise<ProjectMembersQueryResult> {\n\t\treturn await SelectBuilder.create<{ id: string; description: string }>()\n\t\t\t.select('id')\n\t\t\t.select('description')\n\t\t\t.from('identity')\n\t\t\t.where(it =>\n\t\t\t\tit.exists(\n\t\t\t\t\tbuilder =>\n\t\t\t\t\t\tbuilder\n\t\t\t\t\t\t\t.from('project_membership')\n\t\t\t\t\t\t\t.where(expr => expr.columnsEq(['project_membership', 'identity_id'], ['identity', 'id']))\n\t\t\t\t\t\t\t.where({ project_id: this.projectId }),\n\t\t\t\t)\n\t\t\t)\n\t\t\t.match(qb => {\n\t\t\t\tconst identityId = this.projectMemberInput.filter?.identityId\n\t\t\t\treturn identityId ? qb.where(it => it.in(['identity', 'id'], identityId)) : qb\n\t\t\t})\n\t\t\t.match(qb => {\n\t\t\t\tconst { email, memberType, personId } = this.projectMemberInput.filter ?? {}\n\t\t\t\tif ((email || personId) && memberType === 'API_KEY') {\n\t\t\t\t\tthrow new ImplementationException()\n\t\t\t\t}\n\n\t\t\t\tconst personQuery = SelectBuilder.create()\n\t\t\t\t\t.from('person')\n\t\t\t\t\t.where(expr => expr.columnsEq(['person', 'identity_id'], ['identity', 'id']))\n\t\t\t\t\t.match(qb => email ? qb.where(it => it.in('email', email)) : qb)\n\t\t\t\t\t.match(qb => personId ? qb.where(it => it.in(['person', 'id'], personId)) : qb)\n\n\t\t\t\tif (email || personId || memberType === 'PERSON') {\n\t\t\t\t\treturn qb.where(it => it.exists(personQuery))\n\t\t\t\t}\n\n\t\t\t\tif (memberType === 'API_KEY') {\n\t\t\t\t\treturn qb.where(it => it.not(it => it.exists(personQuery)))\n\t\t\t\t}\n\n\t\t\t\treturn qb\n\t\t\t})\n\t\t\t.limit(this.projectMemberInput.limit ?? undefined, this.projectMemberInput.offset ?? undefined)\n\t\t\t.getResult(db)\n\t}\n}\n\nexport interface ProjectMembersQueryRow {\n\tid: string\n\tdescription: string\n}\n\nexport type ProjectMembersQueryResult = ProjectMembersQueryRow[]\n"],"names":["DatabaseQuery","SelectBuilder","ImplementationException","qb","it"],"mappings":";;;;AAIO,MAAM,4BAA4BA,SAAAA,cAAyC;AAAA,EACjF,YACkB,WACA,oBAChB;AACD,UAAA;AAHiB,SAAA,YAAA;AACA,SAAA,qBAAA;AAAA,EAGlB;AAAA,EAEA,MAAM,MAAM,EAAE,MAA6D;AAC1E,WAAO,MAAMC,SAAAA,cAAc,OAAA,EACzB,OAAO,IAAI,EACX,OAAO,aAAa,EACpB,KAAK,UAAU,EACf;AAAA,MAAM,QACN,GAAG;AAAA,QACF,CAAA,YACC,QACE,KAAK,oBAAoB,EACzB,MAAM,CAAA,SAAQ,KAAK,UAAU,CAAC,sBAAsB,aAAa,GAAG,CAAC,YAAY,IAAI,CAAC,CAAC,EACvF,MAAM,EAAE,YAAY,KAAK,UAAA,CAAW;AAAA,MAAA;AAAA,IACxC,EAEA,MAAM,CAAA,OAAM;AACZ,YAAM,aAAa,KAAK,mBAAmB,QAAQ;AACnD,aAAO,aAAa,GAAG,MAAM,CAAA,OAAM,GAAG,GAAG,CAAC,YAAY,IAAI,GAAG,UAAU,CAAC,IAAI;AAAA,IAC7E,CAAC,EACA,MAAM,CAAA,OAAM;AACZ,YAAM,EAAE,OAAO,YAAY,SAAA,IAAa,KAAK,mBAAmB,UAAU,CAAA;AAC1E,WAAK,SAAS,aAAa,eAAe,WAAW;AACpD,cAAM,IAAIC,WAAAA,wBAAA;AAAA,MACX;AAEA,YAAM,cAAcD,SAAAA,cAAc,OAAA,EAChC,KAAK,QAAQ,EACb,MAAM,CAAA,SAAQ,KAAK,UAAU,CAAC,UAAU,aAAa,GAAG,CAAC,YAAY,IAAI,CAAC,CAAC,EAC3E,MAAM,CAAAE,QAAM,QAAQA,IAAG,MAAM,CAAA,OAAM,GAAG,GAAG,SAAS,KAAK,CAAC,IAAIA,GAAE,EAC9D,MAAM,CAAAA,QAAM,WAAWA,IAAG,MAAM,CAAA,OAAM,GAAG,GAAG,CAAC,UAAU,IAAI,GAAG,QAAQ,CAAC,IAAIA,GAAE;AAE/E,UAAI,SAAS,YAAY,eAAe,UAAU;AACjD,eAAO,GAAG,MAAM,CAAA,OAAM,GAAG,OAAO,WAAW,CAAC;AAAA,MAC7C;AAEA,UAAI,eAAe,WAAW;AAC7B,eAAO,GAAG,MAAM,CAAA,OAAM,GAAG,IAAI,CAAAC,QAAMA,IAAG,OAAO,WAAW,CAAC,CAAC;AAAA,MAC3D;AAEA,aAAO;AAAA,IACR,CAAC,EACA,MAAM,KAAK,mBAAmB,SAAS,QAAW,KAAK,mBAAmB,UAAU,MAAS,EAC7F,UAAU,EAAE;AAAA,EACf;AACD;;"}
1
+ {"version":3,"file":"ProjectMembersQuery.cjs","sources":["../../../../../../../packages/engine-tenant-api/src/model/queries/membership/ProjectMembersQuery.ts"],"sourcesContent":["import { DatabaseQuery, DatabaseQueryable, SelectBuilder } from '@contember/database'\nimport { MemberType, ProjectMembersInput } from '../../../schema'\nimport { ImplementationException } from '../../../exceptions'\n\nexport class ProjectMembersQuery extends DatabaseQuery<ProjectMembersQueryResult> {\n\tconstructor(\n\t\tprivate readonly projectId: string,\n\t\tprivate readonly projectMemberInput: ProjectMembersInput,\n\t) {\n\t\tsuper()\n\t}\n\n\tasync fetch({ db }: DatabaseQueryable): Promise<ProjectMembersQueryResult> {\n\t\treturn await SelectBuilder.create<{ id: string; description: string }>()\n\t\t\t.select('id')\n\t\t\t.select('description')\n\t\t\t.from('identity')\n\t\t\t.where(it =>\n\t\t\t\tit.exists(\n\t\t\t\t\tbuilder =>\n\t\t\t\t\t\tbuilder\n\t\t\t\t\t\t\t.from('project_membership')\n\t\t\t\t\t\t\t.where(expr => expr.columnsEq(['project_membership', 'identity_id'], ['identity', 'id']))\n\t\t\t\t\t\t\t.where({ project_id: this.projectId }),\n\t\t\t\t)\n\t\t\t)\n\t\t\t.match(qb => {\n\t\t\t\tconst identityId = this.projectMemberInput.filter?.identityId\n\t\t\t\treturn identityId ? qb.where(it => it.in(['identity', 'id'], identityId)) : qb\n\t\t\t})\n\t\t\t.match(qb => {\n\t\t\t\tconst { email, memberType, personId } = this.projectMemberInput.filter ?? {}\n\t\t\t\tif ((email || personId) && memberType === 'API_KEY') {\n\t\t\t\t\tthrow new ImplementationException()\n\t\t\t\t}\n\n\t\t\t\tconst personQuery = SelectBuilder.create()\n\t\t\t\t\t.from('person')\n\t\t\t\t\t.where(expr => expr.columnsEq(['person', 'identity_id'], ['identity', 'id']))\n\t\t\t\t\t.match(qb => email ? qb.where(it => it.in('email', email)) : qb)\n\t\t\t\t\t.match(qb => personId ? qb.where(it => it.in(['person', 'id'], personId)) : qb)\n\n\t\t\t\tif (email || personId || memberType === 'PERSON') {\n\t\t\t\t\treturn qb.where(it => it.exists(personQuery))\n\t\t\t\t}\n\n\t\t\t\tif (memberType === 'API_KEY') {\n\t\t\t\t\treturn qb.where(it => it.not(it => it.exists(personQuery)))\n\t\t\t\t}\n\n\t\t\t\treturn qb\n\t\t\t})\n\t\t\t.orderBy(['identity', 'id'])\n\t\t\t.limit(this.projectMemberInput.limit ?? undefined, this.projectMemberInput.offset ?? undefined)\n\t\t\t.getResult(db)\n\t}\n}\n\nexport interface ProjectMembersQueryRow {\n\tid: string\n\tdescription: string\n}\n\nexport type ProjectMembersQueryResult = ProjectMembersQueryRow[]\n"],"names":["DatabaseQuery","SelectBuilder","ImplementationException","qb","it"],"mappings":";;;;AAIO,MAAM,4BAA4BA,SAAAA,cAAyC;AAAA,EACjF,YACkB,WACA,oBAChB;AACD,UAAA;AAHiB,SAAA,YAAA;AACA,SAAA,qBAAA;AAAA,EAGlB;AAAA,EAEA,MAAM,MAAM,EAAE,MAA6D;AAC1E,WAAO,MAAMC,SAAAA,cAAc,OAAA,EACzB,OAAO,IAAI,EACX,OAAO,aAAa,EACpB,KAAK,UAAU,EACf;AAAA,MAAM,QACN,GAAG;AAAA,QACF,CAAA,YACC,QACE,KAAK,oBAAoB,EACzB,MAAM,CAAA,SAAQ,KAAK,UAAU,CAAC,sBAAsB,aAAa,GAAG,CAAC,YAAY,IAAI,CAAC,CAAC,EACvF,MAAM,EAAE,YAAY,KAAK,UAAA,CAAW;AAAA,MAAA;AAAA,IACxC,EAEA,MAAM,CAAA,OAAM;AACZ,YAAM,aAAa,KAAK,mBAAmB,QAAQ;AACnD,aAAO,aAAa,GAAG,MAAM,CAAA,OAAM,GAAG,GAAG,CAAC,YAAY,IAAI,GAAG,UAAU,CAAC,IAAI;AAAA,IAC7E,CAAC,EACA,MAAM,CAAA,OAAM;AACZ,YAAM,EAAE,OAAO,YAAY,SAAA,IAAa,KAAK,mBAAmB,UAAU,CAAA;AAC1E,WAAK,SAAS,aAAa,eAAe,WAAW;AACpD,cAAM,IAAIC,WAAAA,wBAAA;AAAA,MACX;AAEA,YAAM,cAAcD,SAAAA,cAAc,OAAA,EAChC,KAAK,QAAQ,EACb,MAAM,CAAA,SAAQ,KAAK,UAAU,CAAC,UAAU,aAAa,GAAG,CAAC,YAAY,IAAI,CAAC,CAAC,EAC3E,MAAM,CAAAE,QAAM,QAAQA,IAAG,MAAM,CAAA,OAAM,GAAG,GAAG,SAAS,KAAK,CAAC,IAAIA,GAAE,EAC9D,MAAM,CAAAA,QAAM,WAAWA,IAAG,MAAM,CAAA,OAAM,GAAG,GAAG,CAAC,UAAU,IAAI,GAAG,QAAQ,CAAC,IAAIA,GAAE;AAE/E,UAAI,SAAS,YAAY,eAAe,UAAU;AACjD,eAAO,GAAG,MAAM,CAAA,OAAM,GAAG,OAAO,WAAW,CAAC;AAAA,MAC7C;AAEA,UAAI,eAAe,WAAW;AAC7B,eAAO,GAAG,MAAM,CAAA,OAAM,GAAG,IAAI,CAAAC,QAAMA,IAAG,OAAO,WAAW,CAAC,CAAC;AAAA,MAC3D;AAEA,aAAO;AAAA,IACR,CAAC,EACA,QAAQ,CAAC,YAAY,IAAI,CAAC,EAC1B,MAAM,KAAK,mBAAmB,SAAS,QAAW,KAAK,mBAAmB,UAAU,MAAS,EAC7F,UAAU,EAAE;AAAA,EACf;AACD;;"}
@@ -27,7 +27,7 @@ class ProjectMembersQuery extends DatabaseQuery {
27
27
  return qb.where((it) => it.not((it2) => it2.exists(personQuery)));
28
28
  }
29
29
  return qb;
30
- }).limit(this.projectMemberInput.limit ?? void 0, this.projectMemberInput.offset ?? void 0).getResult(db);
30
+ }).orderBy(["identity", "id"]).limit(this.projectMemberInput.limit ?? void 0, this.projectMemberInput.offset ?? void 0).getResult(db);
31
31
  }
32
32
  }
33
33
  export {
@@ -1 +1 @@
1
- {"version":3,"file":"ProjectMembersQuery.js","sources":["../../../../../../../packages/engine-tenant-api/src/model/queries/membership/ProjectMembersQuery.ts"],"sourcesContent":["import { DatabaseQuery, DatabaseQueryable, SelectBuilder } from '@contember/database'\nimport { MemberType, ProjectMembersInput } from '../../../schema'\nimport { ImplementationException } from '../../../exceptions'\n\nexport class ProjectMembersQuery extends DatabaseQuery<ProjectMembersQueryResult> {\n\tconstructor(\n\t\tprivate readonly projectId: string,\n\t\tprivate readonly projectMemberInput: ProjectMembersInput,\n\t) {\n\t\tsuper()\n\t}\n\n\tasync fetch({ db }: DatabaseQueryable): Promise<ProjectMembersQueryResult> {\n\t\treturn await SelectBuilder.create<{ id: string; description: string }>()\n\t\t\t.select('id')\n\t\t\t.select('description')\n\t\t\t.from('identity')\n\t\t\t.where(it =>\n\t\t\t\tit.exists(\n\t\t\t\t\tbuilder =>\n\t\t\t\t\t\tbuilder\n\t\t\t\t\t\t\t.from('project_membership')\n\t\t\t\t\t\t\t.where(expr => expr.columnsEq(['project_membership', 'identity_id'], ['identity', 'id']))\n\t\t\t\t\t\t\t.where({ project_id: this.projectId }),\n\t\t\t\t)\n\t\t\t)\n\t\t\t.match(qb => {\n\t\t\t\tconst identityId = this.projectMemberInput.filter?.identityId\n\t\t\t\treturn identityId ? qb.where(it => it.in(['identity', 'id'], identityId)) : qb\n\t\t\t})\n\t\t\t.match(qb => {\n\t\t\t\tconst { email, memberType, personId } = this.projectMemberInput.filter ?? {}\n\t\t\t\tif ((email || personId) && memberType === 'API_KEY') {\n\t\t\t\t\tthrow new ImplementationException()\n\t\t\t\t}\n\n\t\t\t\tconst personQuery = SelectBuilder.create()\n\t\t\t\t\t.from('person')\n\t\t\t\t\t.where(expr => expr.columnsEq(['person', 'identity_id'], ['identity', 'id']))\n\t\t\t\t\t.match(qb => email ? qb.where(it => it.in('email', email)) : qb)\n\t\t\t\t\t.match(qb => personId ? qb.where(it => it.in(['person', 'id'], personId)) : qb)\n\n\t\t\t\tif (email || personId || memberType === 'PERSON') {\n\t\t\t\t\treturn qb.where(it => it.exists(personQuery))\n\t\t\t\t}\n\n\t\t\t\tif (memberType === 'API_KEY') {\n\t\t\t\t\treturn qb.where(it => it.not(it => it.exists(personQuery)))\n\t\t\t\t}\n\n\t\t\t\treturn qb\n\t\t\t})\n\t\t\t.limit(this.projectMemberInput.limit ?? undefined, this.projectMemberInput.offset ?? undefined)\n\t\t\t.getResult(db)\n\t}\n}\n\nexport interface ProjectMembersQueryRow {\n\tid: string\n\tdescription: string\n}\n\nexport type ProjectMembersQueryResult = ProjectMembersQueryRow[]\n"],"names":["qb","it"],"mappings":";;AAIO,MAAM,4BAA4B,cAAyC;AAAA,EACjF,YACkB,WACA,oBAChB;AACD,UAAA;AAHiB,SAAA,YAAA;AACA,SAAA,qBAAA;AAAA,EAGlB;AAAA,EAEA,MAAM,MAAM,EAAE,MAA6D;AAC1E,WAAO,MAAM,cAAc,OAAA,EACzB,OAAO,IAAI,EACX,OAAO,aAAa,EACpB,KAAK,UAAU,EACf;AAAA,MAAM,QACN,GAAG;AAAA,QACF,CAAA,YACC,QACE,KAAK,oBAAoB,EACzB,MAAM,CAAA,SAAQ,KAAK,UAAU,CAAC,sBAAsB,aAAa,GAAG,CAAC,YAAY,IAAI,CAAC,CAAC,EACvF,MAAM,EAAE,YAAY,KAAK,UAAA,CAAW;AAAA,MAAA;AAAA,IACxC,EAEA,MAAM,CAAA,OAAM;AACZ,YAAM,aAAa,KAAK,mBAAmB,QAAQ;AACnD,aAAO,aAAa,GAAG,MAAM,CAAA,OAAM,GAAG,GAAG,CAAC,YAAY,IAAI,GAAG,UAAU,CAAC,IAAI;AAAA,IAC7E,CAAC,EACA,MAAM,CAAA,OAAM;AACZ,YAAM,EAAE,OAAO,YAAY,SAAA,IAAa,KAAK,mBAAmB,UAAU,CAAA;AAC1E,WAAK,SAAS,aAAa,eAAe,WAAW;AACpD,cAAM,IAAI,wBAAA;AAAA,MACX;AAEA,YAAM,cAAc,cAAc,OAAA,EAChC,KAAK,QAAQ,EACb,MAAM,CAAA,SAAQ,KAAK,UAAU,CAAC,UAAU,aAAa,GAAG,CAAC,YAAY,IAAI,CAAC,CAAC,EAC3E,MAAM,CAAAA,QAAM,QAAQA,IAAG,MAAM,CAAA,OAAM,GAAG,GAAG,SAAS,KAAK,CAAC,IAAIA,GAAE,EAC9D,MAAM,CAAAA,QAAM,WAAWA,IAAG,MAAM,CAAA,OAAM,GAAG,GAAG,CAAC,UAAU,IAAI,GAAG,QAAQ,CAAC,IAAIA,GAAE;AAE/E,UAAI,SAAS,YAAY,eAAe,UAAU;AACjD,eAAO,GAAG,MAAM,CAAA,OAAM,GAAG,OAAO,WAAW,CAAC;AAAA,MAC7C;AAEA,UAAI,eAAe,WAAW;AAC7B,eAAO,GAAG,MAAM,CAAA,OAAM,GAAG,IAAI,CAAAC,QAAMA,IAAG,OAAO,WAAW,CAAC,CAAC;AAAA,MAC3D;AAEA,aAAO;AAAA,IACR,CAAC,EACA,MAAM,KAAK,mBAAmB,SAAS,QAAW,KAAK,mBAAmB,UAAU,MAAS,EAC7F,UAAU,EAAE;AAAA,EACf;AACD;"}
1
+ {"version":3,"file":"ProjectMembersQuery.js","sources":["../../../../../../../packages/engine-tenant-api/src/model/queries/membership/ProjectMembersQuery.ts"],"sourcesContent":["import { DatabaseQuery, DatabaseQueryable, SelectBuilder } from '@contember/database'\nimport { MemberType, ProjectMembersInput } from '../../../schema'\nimport { ImplementationException } from '../../../exceptions'\n\nexport class ProjectMembersQuery extends DatabaseQuery<ProjectMembersQueryResult> {\n\tconstructor(\n\t\tprivate readonly projectId: string,\n\t\tprivate readonly projectMemberInput: ProjectMembersInput,\n\t) {\n\t\tsuper()\n\t}\n\n\tasync fetch({ db }: DatabaseQueryable): Promise<ProjectMembersQueryResult> {\n\t\treturn await SelectBuilder.create<{ id: string; description: string }>()\n\t\t\t.select('id')\n\t\t\t.select('description')\n\t\t\t.from('identity')\n\t\t\t.where(it =>\n\t\t\t\tit.exists(\n\t\t\t\t\tbuilder =>\n\t\t\t\t\t\tbuilder\n\t\t\t\t\t\t\t.from('project_membership')\n\t\t\t\t\t\t\t.where(expr => expr.columnsEq(['project_membership', 'identity_id'], ['identity', 'id']))\n\t\t\t\t\t\t\t.where({ project_id: this.projectId }),\n\t\t\t\t)\n\t\t\t)\n\t\t\t.match(qb => {\n\t\t\t\tconst identityId = this.projectMemberInput.filter?.identityId\n\t\t\t\treturn identityId ? qb.where(it => it.in(['identity', 'id'], identityId)) : qb\n\t\t\t})\n\t\t\t.match(qb => {\n\t\t\t\tconst { email, memberType, personId } = this.projectMemberInput.filter ?? {}\n\t\t\t\tif ((email || personId) && memberType === 'API_KEY') {\n\t\t\t\t\tthrow new ImplementationException()\n\t\t\t\t}\n\n\t\t\t\tconst personQuery = SelectBuilder.create()\n\t\t\t\t\t.from('person')\n\t\t\t\t\t.where(expr => expr.columnsEq(['person', 'identity_id'], ['identity', 'id']))\n\t\t\t\t\t.match(qb => email ? qb.where(it => it.in('email', email)) : qb)\n\t\t\t\t\t.match(qb => personId ? qb.where(it => it.in(['person', 'id'], personId)) : qb)\n\n\t\t\t\tif (email || personId || memberType === 'PERSON') {\n\t\t\t\t\treturn qb.where(it => it.exists(personQuery))\n\t\t\t\t}\n\n\t\t\t\tif (memberType === 'API_KEY') {\n\t\t\t\t\treturn qb.where(it => it.not(it => it.exists(personQuery)))\n\t\t\t\t}\n\n\t\t\t\treturn qb\n\t\t\t})\n\t\t\t.orderBy(['identity', 'id'])\n\t\t\t.limit(this.projectMemberInput.limit ?? undefined, this.projectMemberInput.offset ?? undefined)\n\t\t\t.getResult(db)\n\t}\n}\n\nexport interface ProjectMembersQueryRow {\n\tid: string\n\tdescription: string\n}\n\nexport type ProjectMembersQueryResult = ProjectMembersQueryRow[]\n"],"names":["qb","it"],"mappings":";;AAIO,MAAM,4BAA4B,cAAyC;AAAA,EACjF,YACkB,WACA,oBAChB;AACD,UAAA;AAHiB,SAAA,YAAA;AACA,SAAA,qBAAA;AAAA,EAGlB;AAAA,EAEA,MAAM,MAAM,EAAE,MAA6D;AAC1E,WAAO,MAAM,cAAc,OAAA,EACzB,OAAO,IAAI,EACX,OAAO,aAAa,EACpB,KAAK,UAAU,EACf;AAAA,MAAM,QACN,GAAG;AAAA,QACF,CAAA,YACC,QACE,KAAK,oBAAoB,EACzB,MAAM,CAAA,SAAQ,KAAK,UAAU,CAAC,sBAAsB,aAAa,GAAG,CAAC,YAAY,IAAI,CAAC,CAAC,EACvF,MAAM,EAAE,YAAY,KAAK,UAAA,CAAW;AAAA,MAAA;AAAA,IACxC,EAEA,MAAM,CAAA,OAAM;AACZ,YAAM,aAAa,KAAK,mBAAmB,QAAQ;AACnD,aAAO,aAAa,GAAG,MAAM,CAAA,OAAM,GAAG,GAAG,CAAC,YAAY,IAAI,GAAG,UAAU,CAAC,IAAI;AAAA,IAC7E,CAAC,EACA,MAAM,CAAA,OAAM;AACZ,YAAM,EAAE,OAAO,YAAY,SAAA,IAAa,KAAK,mBAAmB,UAAU,CAAA;AAC1E,WAAK,SAAS,aAAa,eAAe,WAAW;AACpD,cAAM,IAAI,wBAAA;AAAA,MACX;AAEA,YAAM,cAAc,cAAc,OAAA,EAChC,KAAK,QAAQ,EACb,MAAM,CAAA,SAAQ,KAAK,UAAU,CAAC,UAAU,aAAa,GAAG,CAAC,YAAY,IAAI,CAAC,CAAC,EAC3E,MAAM,CAAAA,QAAM,QAAQA,IAAG,MAAM,CAAA,OAAM,GAAG,GAAG,SAAS,KAAK,CAAC,IAAIA,GAAE,EAC9D,MAAM,CAAAA,QAAM,WAAWA,IAAG,MAAM,CAAA,OAAM,GAAG,GAAG,CAAC,UAAU,IAAI,GAAG,QAAQ,CAAC,IAAIA,GAAE;AAE/E,UAAI,SAAS,YAAY,eAAe,UAAU;AACjD,eAAO,GAAG,MAAM,CAAA,OAAM,GAAG,OAAO,WAAW,CAAC;AAAA,MAC7C;AAEA,UAAI,eAAe,WAAW;AAC7B,eAAO,GAAG,MAAM,CAAA,OAAM,GAAG,IAAI,CAAAC,QAAMA,IAAG,OAAO,WAAW,CAAC,CAAC;AAAA,MAC3D;AAEA,aAAO;AAAA,IACR,CAAC,EACA,QAAQ,CAAC,YAAY,IAAI,CAAC,EAC1B,MAAM,KAAK,mBAAmB,SAAS,QAAW,KAAK,mBAAmB,UAAU,MAAS,EAC7F,UAAU,EAAE;AAAA,EACf;AACD;"}
@@ -6,7 +6,7 @@ class PersonAccessManager {
6
6
  constructor(apiKeyManager) {
7
7
  this.apiKeyManager = apiKeyManager;
8
8
  }
9
- async disablePerson(dbContext, person) {
9
+ async disablePerson(dbContext, person, logger) {
10
10
  return await dbContext.transaction(async (trx) => {
11
11
  if (person.disabled_at !== null) {
12
12
  return new Response.ResponseError("PERSON_ALREADY_DISABLED", "Person is already disabled");
@@ -14,7 +14,7 @@ class PersonAccessManager {
14
14
  await this.disablePersonAccount(trx, person.id);
15
15
  await this.disableIdentityApiKeys(trx, person.identity_id);
16
16
  return new Response.ResponseOk(null);
17
- });
17
+ }, { retry: { logger } });
18
18
  }
19
19
  async disablePersonAccount(dbContext, personId) {
20
20
  await dbContext.commandBus.execute(new DisablePersonCommand.DisablePersonCommand(personId));
@@ -1 +1 @@
1
- {"version":3,"file":"PersonAccessManager.cjs","sources":["../../../../../../packages/engine-tenant-api/src/model/service/PersonAccessManager.ts"],"sourcesContent":["import { PersonQuery, PersonRow } from '../queries'\nimport { DatabaseContext } from '../utils'\nimport { DisablePersonCommand } from '../commands/person/DisablePersonCommand'\nimport { ApiKeyManager } from './apiKey'\nimport { Response, ResponseError, ResponseOk } from '../utils/Response'\nimport { DisablePersonErrorCode } from '../../schema'\n\nclass PersonAccessManager {\n\tconstructor(private readonly apiKeyManager: ApiKeyManager) {}\n\n\tasync disablePerson(dbContext: DatabaseContext, person: PersonRow): Promise<PersonDisableAccessResponse> {\n\t\treturn await dbContext.transaction(async trx => {\n\t\t\tif (person.disabled_at !== null) {\n\t\t\t\treturn new ResponseError('PERSON_ALREADY_DISABLED', 'Person is already disabled')\n\t\t\t}\n\n\t\t\t// Deactivate person & invalidate all api keys associated with person identity\n\t\t\tawait this.disablePersonAccount(trx, person.id)\n\t\t\tawait this.disableIdentityApiKeys(trx, person.identity_id)\n\n\t\t\treturn new ResponseOk(null)\n\t\t})\n\t}\n\n\tprivate async disablePersonAccount(dbContext: DatabaseContext, personId: string) {\n\t\tawait dbContext.commandBus.execute(new DisablePersonCommand(personId))\n\t}\n\n\tprivate async disableIdentityApiKeys(dbContext: DatabaseContext, personIdentityId: string) {\n\t\tawait this.apiKeyManager.disableIdentityApiKeys(dbContext, personIdentityId)\n\t}\n}\n\nexport type PersonDisableAccessResponse = Response<null, DisablePersonErrorCode>\n\nexport { PersonAccessManager }\n"],"names":["ResponseError","ResponseOk","DisablePersonCommand"],"mappings":";;;;AAOA,MAAM,oBAAoB;AAAA,EACzB,YAA6B,eAA8B;AAA9B,SAAA,gBAAA;AAAA,EAA+B;AAAA,EAE5D,MAAM,cAAc,WAA4B,QAAyD;AACxG,WAAO,MAAM,UAAU,YAAY,OAAM,QAAO;AAC/C,UAAI,OAAO,gBAAgB,MAAM;AAChC,eAAO,IAAIA,SAAAA,cAAc,2BAA2B,4BAA4B;AAAA,MACjF;AAGA,YAAM,KAAK,qBAAqB,KAAK,OAAO,EAAE;AAC9C,YAAM,KAAK,uBAAuB,KAAK,OAAO,WAAW;AAEzD,aAAO,IAAIC,SAAAA,WAAW,IAAI;AAAA,IAC3B,CAAC;AAAA,EACF;AAAA,EAEA,MAAc,qBAAqB,WAA4B,UAAkB;AAChF,UAAM,UAAU,WAAW,QAAQ,IAAIC,qBAAAA,qBAAqB,QAAQ,CAAC;AAAA,EACtE;AAAA,EAEA,MAAc,uBAAuB,WAA4B,kBAA0B;AAC1F,UAAM,KAAK,cAAc,uBAAuB,WAAW,gBAAgB;AAAA,EAC5E;AACD;;"}
1
+ {"version":3,"file":"PersonAccessManager.cjs","sources":["../../../../../../packages/engine-tenant-api/src/model/service/PersonAccessManager.ts"],"sourcesContent":["import { Logger } from '@contember/logger'\nimport { PersonQuery, PersonRow } from '../queries'\nimport { DatabaseContext } from '../utils'\nimport { DisablePersonCommand } from '../commands/person/DisablePersonCommand'\nimport { ApiKeyManager } from './apiKey'\nimport { Response, ResponseError, ResponseOk } from '../utils/Response'\nimport { DisablePersonErrorCode } from '../../schema'\n\nclass PersonAccessManager {\n\tconstructor(private readonly apiKeyManager: ApiKeyManager) {}\n\n\tasync disablePerson(dbContext: DatabaseContext, person: PersonRow, logger: Logger): Promise<PersonDisableAccessResponse> {\n\t\t// Retried: the api_key update races the target's own background session tracking and loses with a 40001,\n\t\t// which would otherwise leave the person enabled behind an internal error. Nothing here escapes the transaction.\n\t\treturn await dbContext.transaction(async trx => {\n\t\t\tif (person.disabled_at !== null) {\n\t\t\t\treturn new ResponseError('PERSON_ALREADY_DISABLED', 'Person is already disabled')\n\t\t\t}\n\n\t\t\t// Deactivate person & invalidate all api keys associated with person identity\n\t\t\tawait this.disablePersonAccount(trx, person.id)\n\t\t\tawait this.disableIdentityApiKeys(trx, person.identity_id)\n\n\t\t\treturn new ResponseOk(null)\n\t\t}, { retry: { logger } })\n\t}\n\n\tprivate async disablePersonAccount(dbContext: DatabaseContext, personId: string) {\n\t\tawait dbContext.commandBus.execute(new DisablePersonCommand(personId))\n\t}\n\n\tprivate async disableIdentityApiKeys(dbContext: DatabaseContext, personIdentityId: string) {\n\t\tawait this.apiKeyManager.disableIdentityApiKeys(dbContext, personIdentityId)\n\t}\n}\n\nexport type PersonDisableAccessResponse = Response<null, DisablePersonErrorCode>\n\nexport { PersonAccessManager }\n"],"names":["ResponseError","ResponseOk","DisablePersonCommand"],"mappings":";;;;AAQA,MAAM,oBAAoB;AAAA,EACzB,YAA6B,eAA8B;AAA9B,SAAA,gBAAA;AAAA,EAA+B;AAAA,EAE5D,MAAM,cAAc,WAA4B,QAAmB,QAAsD;AAGxH,WAAO,MAAM,UAAU,YAAY,OAAM,QAAO;AAC/C,UAAI,OAAO,gBAAgB,MAAM;AAChC,eAAO,IAAIA,SAAAA,cAAc,2BAA2B,4BAA4B;AAAA,MACjF;AAGA,YAAM,KAAK,qBAAqB,KAAK,OAAO,EAAE;AAC9C,YAAM,KAAK,uBAAuB,KAAK,OAAO,WAAW;AAEzD,aAAO,IAAIC,SAAAA,WAAW,IAAI;AAAA,IAC3B,GAAG,EAAE,OAAO,EAAE,OAAA,GAAU;AAAA,EACzB;AAAA,EAEA,MAAc,qBAAqB,WAA4B,UAAkB;AAChF,UAAM,UAAU,WAAW,QAAQ,IAAIC,qBAAAA,qBAAqB,QAAQ,CAAC;AAAA,EACtE;AAAA,EAEA,MAAc,uBAAuB,WAA4B,kBAA0B;AAC1F,UAAM,KAAK,cAAc,uBAAuB,WAAW,gBAAgB;AAAA,EAC5E;AACD;;"}
@@ -4,7 +4,7 @@ class PersonAccessManager {
4
4
  constructor(apiKeyManager) {
5
5
  this.apiKeyManager = apiKeyManager;
6
6
  }
7
- async disablePerson(dbContext, person) {
7
+ async disablePerson(dbContext, person, logger) {
8
8
  return await dbContext.transaction(async (trx) => {
9
9
  if (person.disabled_at !== null) {
10
10
  return new ResponseError("PERSON_ALREADY_DISABLED", "Person is already disabled");
@@ -12,7 +12,7 @@ class PersonAccessManager {
12
12
  await this.disablePersonAccount(trx, person.id);
13
13
  await this.disableIdentityApiKeys(trx, person.identity_id);
14
14
  return new ResponseOk(null);
15
- });
15
+ }, { retry: { logger } });
16
16
  }
17
17
  async disablePersonAccount(dbContext, personId) {
18
18
  await dbContext.commandBus.execute(new DisablePersonCommand(personId));
@@ -1 +1 @@
1
- {"version":3,"file":"PersonAccessManager.js","sources":["../../../../../../packages/engine-tenant-api/src/model/service/PersonAccessManager.ts"],"sourcesContent":["import { PersonQuery, PersonRow } from '../queries'\nimport { DatabaseContext } from '../utils'\nimport { DisablePersonCommand } from '../commands/person/DisablePersonCommand'\nimport { ApiKeyManager } from './apiKey'\nimport { Response, ResponseError, ResponseOk } from '../utils/Response'\nimport { DisablePersonErrorCode } from '../../schema'\n\nclass PersonAccessManager {\n\tconstructor(private readonly apiKeyManager: ApiKeyManager) {}\n\n\tasync disablePerson(dbContext: DatabaseContext, person: PersonRow): Promise<PersonDisableAccessResponse> {\n\t\treturn await dbContext.transaction(async trx => {\n\t\t\tif (person.disabled_at !== null) {\n\t\t\t\treturn new ResponseError('PERSON_ALREADY_DISABLED', 'Person is already disabled')\n\t\t\t}\n\n\t\t\t// Deactivate person & invalidate all api keys associated with person identity\n\t\t\tawait this.disablePersonAccount(trx, person.id)\n\t\t\tawait this.disableIdentityApiKeys(trx, person.identity_id)\n\n\t\t\treturn new ResponseOk(null)\n\t\t})\n\t}\n\n\tprivate async disablePersonAccount(dbContext: DatabaseContext, personId: string) {\n\t\tawait dbContext.commandBus.execute(new DisablePersonCommand(personId))\n\t}\n\n\tprivate async disableIdentityApiKeys(dbContext: DatabaseContext, personIdentityId: string) {\n\t\tawait this.apiKeyManager.disableIdentityApiKeys(dbContext, personIdentityId)\n\t}\n}\n\nexport type PersonDisableAccessResponse = Response<null, DisablePersonErrorCode>\n\nexport { PersonAccessManager }\n"],"names":[],"mappings":";;AAOA,MAAM,oBAAoB;AAAA,EACzB,YAA6B,eAA8B;AAA9B,SAAA,gBAAA;AAAA,EAA+B;AAAA,EAE5D,MAAM,cAAc,WAA4B,QAAyD;AACxG,WAAO,MAAM,UAAU,YAAY,OAAM,QAAO;AAC/C,UAAI,OAAO,gBAAgB,MAAM;AAChC,eAAO,IAAI,cAAc,2BAA2B,4BAA4B;AAAA,MACjF;AAGA,YAAM,KAAK,qBAAqB,KAAK,OAAO,EAAE;AAC9C,YAAM,KAAK,uBAAuB,KAAK,OAAO,WAAW;AAEzD,aAAO,IAAI,WAAW,IAAI;AAAA,IAC3B,CAAC;AAAA,EACF;AAAA,EAEA,MAAc,qBAAqB,WAA4B,UAAkB;AAChF,UAAM,UAAU,WAAW,QAAQ,IAAI,qBAAqB,QAAQ,CAAC;AAAA,EACtE;AAAA,EAEA,MAAc,uBAAuB,WAA4B,kBAA0B;AAC1F,UAAM,KAAK,cAAc,uBAAuB,WAAW,gBAAgB;AAAA,EAC5E;AACD;"}
1
+ {"version":3,"file":"PersonAccessManager.js","sources":["../../../../../../packages/engine-tenant-api/src/model/service/PersonAccessManager.ts"],"sourcesContent":["import { Logger } from '@contember/logger'\nimport { PersonQuery, PersonRow } from '../queries'\nimport { DatabaseContext } from '../utils'\nimport { DisablePersonCommand } from '../commands/person/DisablePersonCommand'\nimport { ApiKeyManager } from './apiKey'\nimport { Response, ResponseError, ResponseOk } from '../utils/Response'\nimport { DisablePersonErrorCode } from '../../schema'\n\nclass PersonAccessManager {\n\tconstructor(private readonly apiKeyManager: ApiKeyManager) {}\n\n\tasync disablePerson(dbContext: DatabaseContext, person: PersonRow, logger: Logger): Promise<PersonDisableAccessResponse> {\n\t\t// Retried: the api_key update races the target's own background session tracking and loses with a 40001,\n\t\t// which would otherwise leave the person enabled behind an internal error. Nothing here escapes the transaction.\n\t\treturn await dbContext.transaction(async trx => {\n\t\t\tif (person.disabled_at !== null) {\n\t\t\t\treturn new ResponseError('PERSON_ALREADY_DISABLED', 'Person is already disabled')\n\t\t\t}\n\n\t\t\t// Deactivate person & invalidate all api keys associated with person identity\n\t\t\tawait this.disablePersonAccount(trx, person.id)\n\t\t\tawait this.disableIdentityApiKeys(trx, person.identity_id)\n\n\t\t\treturn new ResponseOk(null)\n\t\t}, { retry: { logger } })\n\t}\n\n\tprivate async disablePersonAccount(dbContext: DatabaseContext, personId: string) {\n\t\tawait dbContext.commandBus.execute(new DisablePersonCommand(personId))\n\t}\n\n\tprivate async disableIdentityApiKeys(dbContext: DatabaseContext, personIdentityId: string) {\n\t\tawait this.apiKeyManager.disableIdentityApiKeys(dbContext, personIdentityId)\n\t}\n}\n\nexport type PersonDisableAccessResponse = Response<null, DisablePersonErrorCode>\n\nexport { PersonAccessManager }\n"],"names":[],"mappings":";;AAQA,MAAM,oBAAoB;AAAA,EACzB,YAA6B,eAA8B;AAA9B,SAAA,gBAAA;AAAA,EAA+B;AAAA,EAE5D,MAAM,cAAc,WAA4B,QAAmB,QAAsD;AAGxH,WAAO,MAAM,UAAU,YAAY,OAAM,QAAO;AAC/C,UAAI,OAAO,gBAAgB,MAAM;AAChC,eAAO,IAAI,cAAc,2BAA2B,4BAA4B;AAAA,MACjF;AAGA,YAAM,KAAK,qBAAqB,KAAK,OAAO,EAAE;AAC9C,YAAM,KAAK,uBAAuB,KAAK,OAAO,WAAW;AAEzD,aAAO,IAAI,WAAW,IAAI;AAAA,IAC3B,GAAG,EAAE,OAAO,EAAE,OAAA,GAAU;AAAA,EACzB;AAAA,EAEA,MAAc,qBAAqB,WAA4B,UAAkB;AAChF,UAAM,UAAU,WAAW,QAAQ,IAAI,qBAAqB,QAAQ,CAAC;AAAA,EACtE;AAAA,EAEA,MAAc,uBAAuB,WAA4B,kBAA0B;AAC1F,UAAM,KAAK,cAAc,uBAAuB,WAAW,gBAAgB;AAAA,EAC5E;AACD;"}
@@ -40,15 +40,16 @@ class ApiKeyManager {
40
40
  if (apiKeyRow.expires_at !== null && apiKeyRow.expires_at <= now) {
41
41
  return new Response.ResponseError("disabled", `API key expired at ${apiKeyRow.expires_at.toISOString()}`);
42
42
  }
43
- setImmediate(async () => {
44
- await dbContext.commandBus.execute(
43
+ setImmediate(() => {
44
+ dbContext.commandBus.execute(
45
45
  new ProlongApiKeyCommand.ProlongApiKeyCommand(
46
46
  apiKeyRow.id,
47
47
  apiKeyRow.type,
48
48
  apiKeyRow.expiration,
49
49
  apiKeyRow.expires_at
50
50
  )
51
- );
51
+ ).catch(() => {
52
+ });
52
53
  });
53
54
  return new Response.ResponseOk(new VerifyResult(apiKeyRow.identity_id, apiKeyRow.id, apiKeyRow.roles, apiKeyRow.person_id));
54
55
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ApiKeyManager.cjs","sources":["../../../../../../../packages/engine-tenant-api/src/model/service/apiKey/ApiKeyManager.ts"],"sourcesContent":["import {\n\tCreateApiKeyCommand,\n\tDisableApiKeyCommand,\n\tDisableIdentityApiKeysCommand,\n\tDisableOneOffApiKeyCommand,\n\tProlongApiKeyCommand,\n} from '../../commands'\nimport { ApiKey } from '../../type'\n\nimport { Response, ResponseError, ResponseOk } from '../../utils/Response'\nimport { DatabaseContext, TokenHash } from '../../utils'\nimport { ApiKeyService, CreateApiKeyResponse } from './ApiKeyService'\nimport assert from 'node:assert'\nimport { Acl } from '@contember/schema'\nimport { ApiKeyByIdQuery, ApiKeyByTokenQuery, ApiKeyRow, ConfigurationQuery } from '../../queries'\nimport PostgresInterval from 'postgres-interval'\nimport { Config } from '../../type/Config'\nimport { intervalToSeconds } from '../../utils/interval'\n\nexport class ApiKeyManager {\n\tconstructor(\n\t\tprivate readonly apiKeyService: ApiKeyService,\n\t) {}\n\n\tasync verifyAndProlong(\n\t\tdbContext: DatabaseContext,\n\t\treadDbContext: DatabaseContext,\n\t\ttoken: string,\n\t): Promise<VerifyResponse> {\n\t\tconst apiKeyRow = await readDbContext.queryHandler.fetch(new ApiKeyByTokenQuery(token))\n\t\tif (apiKeyRow === null) {\n\t\t\treturn new ResponseError(VerifyErrorCode.NOT_FOUND, 'API key was not found')\n\t\t}\n\n\t\tif (apiKeyRow.disabled_at !== null) {\n\t\t\treturn new ResponseError(\n\t\t\t\tVerifyErrorCode.DISABLED,\n\t\t\t\t`API key was disabled at ${apiKeyRow.disabled_at.toISOString()}`,\n\t\t\t)\n\t\t}\n\n\t\tconst now = new Date()\n\t\tif (apiKeyRow.expires_at !== null && apiKeyRow.expires_at <= now) {\n\t\t\treturn new ResponseError(VerifyErrorCode.DISABLED, `API key expired at ${apiKeyRow.expires_at.toISOString()}`)\n\t\t}\n\n\t\tsetImmediate(async () => {\n\t\t\tawait dbContext.commandBus.execute(\n\t\t\t\tnew ProlongApiKeyCommand(\n\t\t\t\t\tapiKeyRow.id,\n\t\t\t\t\tapiKeyRow.type,\n\t\t\t\t\tapiKeyRow.expiration,\n\t\t\t\t\tapiKeyRow.expires_at,\n\t\t\t\t),\n\t\t\t)\n\t\t})\n\n\t\treturn new ResponseOk(new VerifyResult(apiKeyRow.identity_id, apiKeyRow.id, apiKeyRow.roles, apiKeyRow.person_id))\n\t}\n\n\tasync createSessionApiKey(dbContext: DatabaseContext, identityId: string, expiration?: number): Promise<string> {\n\t\tconst config = await dbContext.queryHandler.fetch(new ConfigurationQuery())\n\t\tconst expirationResolved = expiration ?? (intervalToSeconds(config.login.defaultTokenExpiration) / 60)\n\t\tconst expirationCapped = config.login.maxTokenExpiration\n\t\t\t? Math.min(expirationResolved, intervalToSeconds(config.login.maxTokenExpiration) / 60)\n\t\t\t: expirationResolved\n\t\tconst command = new CreateApiKeyCommand({ type: ApiKey.Type.SESSION, identityId, expiration: expirationCapped })\n\t\tconst token = (await dbContext.commandBus.execute(command)).token\n\t\tassert(token !== undefined)\n\t\treturn token\n\t}\n\n\tasync findApiKey(dbContext: DatabaseContext, apiKeyId: string): Promise<ApiKeyRow | null> {\n\t\treturn await dbContext.queryHandler.fetch(\n\t\t\tnew ApiKeyByIdQuery(apiKeyId),\n\t\t)\n\t}\n\n\tasync disableOneOffApiKey(dbContext: DatabaseContext, apiKeyId: string): Promise<void> {\n\t\tawait dbContext.commandBus.execute(new DisableOneOffApiKeyCommand(apiKeyId))\n\t}\n\n\tasync disableApiKey(dbContext: DatabaseContext, apiKeyId: string): Promise<boolean> {\n\t\treturn await dbContext.commandBus.execute(new DisableApiKeyCommand(apiKeyId))\n\t}\n\n\tasync disableIdentityApiKeys(dbContext: DatabaseContext, identityId: string): Promise<void> {\n\t\tawait dbContext.commandBus.execute(new DisableIdentityApiKeysCommand(identityId))\n\t}\n\n\tasync createGlobalPermanentApiKey(\n\t\tdbContext: DatabaseContext,\n\t\tdescription: string,\n\t\troles: readonly string[],\n\t\ttokenHash?: TokenHash,\n\t): Promise<CreateApiKeyResponse> {\n\t\treturn await dbContext.transaction(async db => {\n\t\t\treturn await this.apiKeyService.createPermanentApiKey(db, description, roles, tokenHash)\n\t\t})\n\t}\n\n\tasync createProjectPermanentApiKey(\n\t\tdbContext: DatabaseContext,\n\t\tprojectId: string,\n\t\tmemberships: readonly Acl.Membership[],\n\t\tdescription: string,\n\t\ttokenHash?: TokenHash,\n\t): Promise<CreateApiKeyResponse> {\n\t\treturn await dbContext.transaction(async db => {\n\t\t\treturn await this.apiKeyService.createProjectPermanentApiKey(db, projectId, memberships, description, tokenHash)\n\t\t})\n\t}\n}\n\nexport type VerifyResponse = Response<VerifyResult, VerifyErrorCode>\n\nexport class VerifyResult {\n\treadonly valid = true\n\n\tconstructor(\n\t\tpublic readonly identityId: string,\n\t\tpublic readonly apiKeyId: string,\n\t\tpublic readonly roles: string[],\n\t\tpublic readonly personId: string | null,\n\t) {}\n}\n\nexport enum VerifyErrorCode {\n\tNOT_FOUND = 'not_found',\n\tDISABLED = 'disabled',\n\tEXPIRED = 'expired',\n\tNO_AUTH_HEADER = 'no_auth_header',\n\tINVALID_AUTH_HEADER = 'invalid_auth_header',\n}\n"],"names":["ApiKeyByTokenQuery","ResponseError","ProlongApiKeyCommand","ResponseOk","ConfigurationQuery","intervalToSeconds","CreateApiKeyCommand","ApiKey","ApiKeyByIdQuery","DisableOneOffApiKeyCommand","DisableApiKeyCommand","DisableIdentityApiKeysCommand","VerifyErrorCode"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAmBO,MAAM,cAAc;AAAA,EAC1B,YACkB,eAChB;AADgB,SAAA,gBAAA;AAAA,EACf;AAAA,EAEH,MAAM,iBACL,WACA,eACA,OAC0B;AAC1B,UAAM,YAAY,MAAM,cAAc,aAAa,MAAM,IAAIA,YAAAA,mBAAmB,KAAK,CAAC;AACtF,QAAI,cAAc,MAAM;AACvB,aAAO,IAAIC,SAAAA,cAAc,aAA2B,uBAAuB;AAAA,IAC5E;AAEA,QAAI,UAAU,gBAAgB,MAAM;AACnC,aAAO,IAAIA,SAAAA;AAAAA,QACV;AAAA,QACA,2BAA2B,UAAU,YAAY,YAAA,CAAa;AAAA,MAAA;AAAA,IAEhE;AAEA,UAAM,0BAAU,KAAA;AAChB,QAAI,UAAU,eAAe,QAAQ,UAAU,cAAc,KAAK;AACjE,aAAO,IAAIA,SAAAA,cAAc,YAA0B,sBAAsB,UAAU,WAAW,YAAA,CAAa,EAAE;AAAA,IAC9G;AAEA,iBAAa,YAAY;AACxB,YAAM,UAAU,WAAW;AAAA,QAC1B,IAAIC,qBAAAA;AAAAA,UACH,UAAU;AAAA,UACV,UAAU;AAAA,UACV,UAAU;AAAA,UACV,UAAU;AAAA,QAAA;AAAA,MACX;AAAA,IAEF,CAAC;AAED,WAAO,IAAIC,SAAAA,WAAW,IAAI,aAAa,UAAU,aAAa,UAAU,IAAI,UAAU,OAAO,UAAU,SAAS,CAAC;AAAA,EAClH;AAAA,EAEA,MAAM,oBAAoB,WAA4B,YAAoB,YAAsC;AAC/G,UAAM,SAAS,MAAM,UAAU,aAAa,MAAM,IAAIC,mBAAAA,oBAAoB;AAC1E,UAAM,qBAAqB,cAAeC,SAAAA,kBAAkB,OAAO,MAAM,sBAAsB,IAAI;AACnG,UAAM,mBAAmB,OAAO,MAAM,qBACnC,KAAK,IAAI,oBAAoBA,SAAAA,kBAAkB,OAAO,MAAM,kBAAkB,IAAI,EAAE,IACpF;AACH,UAAM,UAAU,IAAIC,wCAAoB,EAAE,MAAMC,OAAAA,OAAO,KAAK,SAAS,YAAY,YAAY,iBAAA,CAAkB;AAC/G,UAAM,SAAS,MAAM,UAAU,WAAW,QAAQ,OAAO,GAAG;AAC5D,WAAO,UAAU,MAAS;AAC1B,WAAO;AAAA,EACR;AAAA,EAEA,MAAM,WAAW,WAA4B,UAA6C;AACzF,WAAO,MAAM,UAAU,aAAa;AAAA,MACnC,IAAIC,YAAAA,gBAAgB,QAAQ;AAAA,IAAA;AAAA,EAE9B;AAAA,EAEA,MAAM,oBAAoB,WAA4B,UAAiC;AACtF,UAAM,UAAU,WAAW,QAAQ,IAAIC,2BAAAA,2BAA2B,QAAQ,CAAC;AAAA,EAC5E;AAAA,EAEA,MAAM,cAAc,WAA4B,UAAoC;AACnF,WAAO,MAAM,UAAU,WAAW,QAAQ,IAAIC,qBAAAA,qBAAqB,QAAQ,CAAC;AAAA,EAC7E;AAAA,EAEA,MAAM,uBAAuB,WAA4B,YAAmC;AAC3F,UAAM,UAAU,WAAW,QAAQ,IAAIC,8BAAAA,8BAA8B,UAAU,CAAC;AAAA,EACjF;AAAA,EAEA,MAAM,4BACL,WACA,aACA,OACA,WACgC;AAChC,WAAO,MAAM,UAAU,YAAY,OAAM,OAAM;AAC9C,aAAO,MAAM,KAAK,cAAc,sBAAsB,IAAI,aAAa,OAAO,SAAS;AAAA,IACxF,CAAC;AAAA,EACF;AAAA,EAEA,MAAM,6BACL,WACA,WACA,aACA,aACA,WACgC;AAChC,WAAO,MAAM,UAAU,YAAY,OAAM,OAAM;AAC9C,aAAO,MAAM,KAAK,cAAc,6BAA6B,IAAI,WAAW,aAAa,aAAa,SAAS;AAAA,IAChH,CAAC;AAAA,EACF;AACD;AAIO,MAAM,aAAa;AAAA,EAGzB,YACiB,YACA,UACA,OACA,UACf;AAJe,SAAA,aAAA;AACA,SAAA,WAAA;AACA,SAAA,QAAA;AACA,SAAA,WAAA;AANjB,kBAAA,MAAS,SAAQ,IAAA;AAAA,EAOd;AACJ;AAEO,IAAK,oCAAAC,qBAAL;AACNA,mBAAA,WAAA,IAAY;AACZA,mBAAA,UAAA,IAAW;AACXA,mBAAA,SAAA,IAAU;AACVA,mBAAA,gBAAA,IAAiB;AACjBA,mBAAA,qBAAA,IAAsB;AALX,SAAAA;AAAA,GAAA,mBAAA,CAAA,CAAA;;;;"}
1
+ {"version":3,"file":"ApiKeyManager.cjs","sources":["../../../../../../../packages/engine-tenant-api/src/model/service/apiKey/ApiKeyManager.ts"],"sourcesContent":["import {\n\tCreateApiKeyCommand,\n\tDisableApiKeyCommand,\n\tDisableIdentityApiKeysCommand,\n\tDisableOneOffApiKeyCommand,\n\tProlongApiKeyCommand,\n} from '../../commands'\nimport { ApiKey } from '../../type'\n\nimport { Response, ResponseError, ResponseOk } from '../../utils/Response'\nimport { DatabaseContext, TokenHash } from '../../utils'\nimport { ApiKeyService, CreateApiKeyResponse } from './ApiKeyService'\nimport assert from 'node:assert'\nimport { Acl } from '@contember/schema'\nimport { ApiKeyByIdQuery, ApiKeyByTokenQuery, ApiKeyRow, ConfigurationQuery } from '../../queries'\nimport PostgresInterval from 'postgres-interval'\nimport { Config } from '../../type/Config'\nimport { intervalToSeconds } from '../../utils/interval'\n\nexport class ApiKeyManager {\n\tconstructor(\n\t\tprivate readonly apiKeyService: ApiKeyService,\n\t) {}\n\n\tasync verifyAndProlong(\n\t\tdbContext: DatabaseContext,\n\t\treadDbContext: DatabaseContext,\n\t\ttoken: string,\n\t): Promise<VerifyResponse> {\n\t\tconst apiKeyRow = await readDbContext.queryHandler.fetch(new ApiKeyByTokenQuery(token))\n\t\tif (apiKeyRow === null) {\n\t\t\treturn new ResponseError(VerifyErrorCode.NOT_FOUND, 'API key was not found')\n\t\t}\n\n\t\tif (apiKeyRow.disabled_at !== null) {\n\t\t\treturn new ResponseError(\n\t\t\t\tVerifyErrorCode.DISABLED,\n\t\t\t\t`API key was disabled at ${apiKeyRow.disabled_at.toISOString()}`,\n\t\t\t)\n\t\t}\n\n\t\tconst now = new Date()\n\t\tif (apiKeyRow.expires_at !== null && apiKeyRow.expires_at <= now) {\n\t\t\treturn new ResponseError(VerifyErrorCode.DISABLED, `API key expired at ${apiKeyRow.expires_at.toISOString()}`)\n\t\t}\n\n\t\t// Detached (runs after the response), so a rejection here would be an unhandledRejection and can take the\n\t\t// worker down. Tracking is best-effort — the next request writes it again.\n\t\tsetImmediate(() => {\n\t\t\tdbContext.commandBus.execute(\n\t\t\t\tnew ProlongApiKeyCommand(\n\t\t\t\t\tapiKeyRow.id,\n\t\t\t\t\tapiKeyRow.type,\n\t\t\t\t\tapiKeyRow.expiration,\n\t\t\t\t\tapiKeyRow.expires_at,\n\t\t\t\t),\n\t\t\t).catch(() => {})\n\t\t})\n\n\t\treturn new ResponseOk(new VerifyResult(apiKeyRow.identity_id, apiKeyRow.id, apiKeyRow.roles, apiKeyRow.person_id))\n\t}\n\n\tasync createSessionApiKey(dbContext: DatabaseContext, identityId: string, expiration?: number): Promise<string> {\n\t\tconst config = await dbContext.queryHandler.fetch(new ConfigurationQuery())\n\t\tconst expirationResolved = expiration ?? (intervalToSeconds(config.login.defaultTokenExpiration) / 60)\n\t\tconst expirationCapped = config.login.maxTokenExpiration\n\t\t\t? Math.min(expirationResolved, intervalToSeconds(config.login.maxTokenExpiration) / 60)\n\t\t\t: expirationResolved\n\t\tconst command = new CreateApiKeyCommand({ type: ApiKey.Type.SESSION, identityId, expiration: expirationCapped })\n\t\tconst token = (await dbContext.commandBus.execute(command)).token\n\t\tassert(token !== undefined)\n\t\treturn token\n\t}\n\n\tasync findApiKey(dbContext: DatabaseContext, apiKeyId: string): Promise<ApiKeyRow | null> {\n\t\treturn await dbContext.queryHandler.fetch(\n\t\t\tnew ApiKeyByIdQuery(apiKeyId),\n\t\t)\n\t}\n\n\tasync disableOneOffApiKey(dbContext: DatabaseContext, apiKeyId: string): Promise<void> {\n\t\tawait dbContext.commandBus.execute(new DisableOneOffApiKeyCommand(apiKeyId))\n\t}\n\n\tasync disableApiKey(dbContext: DatabaseContext, apiKeyId: string): Promise<boolean> {\n\t\treturn await dbContext.commandBus.execute(new DisableApiKeyCommand(apiKeyId))\n\t}\n\n\tasync disableIdentityApiKeys(dbContext: DatabaseContext, identityId: string): Promise<void> {\n\t\tawait dbContext.commandBus.execute(new DisableIdentityApiKeysCommand(identityId))\n\t}\n\n\tasync createGlobalPermanentApiKey(\n\t\tdbContext: DatabaseContext,\n\t\tdescription: string,\n\t\troles: readonly string[],\n\t\ttokenHash?: TokenHash,\n\t): Promise<CreateApiKeyResponse> {\n\t\treturn await dbContext.transaction(async db => {\n\t\t\treturn await this.apiKeyService.createPermanentApiKey(db, description, roles, tokenHash)\n\t\t})\n\t}\n\n\tasync createProjectPermanentApiKey(\n\t\tdbContext: DatabaseContext,\n\t\tprojectId: string,\n\t\tmemberships: readonly Acl.Membership[],\n\t\tdescription: string,\n\t\ttokenHash?: TokenHash,\n\t): Promise<CreateApiKeyResponse> {\n\t\treturn await dbContext.transaction(async db => {\n\t\t\treturn await this.apiKeyService.createProjectPermanentApiKey(db, projectId, memberships, description, tokenHash)\n\t\t})\n\t}\n}\n\nexport type VerifyResponse = Response<VerifyResult, VerifyErrorCode>\n\nexport class VerifyResult {\n\treadonly valid = true\n\n\tconstructor(\n\t\tpublic readonly identityId: string,\n\t\tpublic readonly apiKeyId: string,\n\t\tpublic readonly roles: string[],\n\t\tpublic readonly personId: string | null,\n\t) {}\n}\n\nexport enum VerifyErrorCode {\n\tNOT_FOUND = 'not_found',\n\tDISABLED = 'disabled',\n\tEXPIRED = 'expired',\n\tNO_AUTH_HEADER = 'no_auth_header',\n\tINVALID_AUTH_HEADER = 'invalid_auth_header',\n}\n"],"names":["ApiKeyByTokenQuery","ResponseError","ProlongApiKeyCommand","ResponseOk","ConfigurationQuery","intervalToSeconds","CreateApiKeyCommand","ApiKey","ApiKeyByIdQuery","DisableOneOffApiKeyCommand","DisableApiKeyCommand","DisableIdentityApiKeysCommand","VerifyErrorCode"],"mappings":"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"}
@@ -38,15 +38,16 @@ class ApiKeyManager {
38
38
  if (apiKeyRow.expires_at !== null && apiKeyRow.expires_at <= now) {
39
39
  return new ResponseError("disabled", `API key expired at ${apiKeyRow.expires_at.toISOString()}`);
40
40
  }
41
- setImmediate(async () => {
42
- await dbContext.commandBus.execute(
41
+ setImmediate(() => {
42
+ dbContext.commandBus.execute(
43
43
  new ProlongApiKeyCommand(
44
44
  apiKeyRow.id,
45
45
  apiKeyRow.type,
46
46
  apiKeyRow.expiration,
47
47
  apiKeyRow.expires_at
48
48
  )
49
- );
49
+ ).catch(() => {
50
+ });
50
51
  });
51
52
  return new ResponseOk(new VerifyResult(apiKeyRow.identity_id, apiKeyRow.id, apiKeyRow.roles, apiKeyRow.person_id));
52
53
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ApiKeyManager.js","sources":["../../../../../../../packages/engine-tenant-api/src/model/service/apiKey/ApiKeyManager.ts"],"sourcesContent":["import {\n\tCreateApiKeyCommand,\n\tDisableApiKeyCommand,\n\tDisableIdentityApiKeysCommand,\n\tDisableOneOffApiKeyCommand,\n\tProlongApiKeyCommand,\n} from '../../commands'\nimport { ApiKey } from '../../type'\n\nimport { Response, ResponseError, ResponseOk } from '../../utils/Response'\nimport { DatabaseContext, TokenHash } from '../../utils'\nimport { ApiKeyService, CreateApiKeyResponse } from './ApiKeyService'\nimport assert from 'node:assert'\nimport { Acl } from '@contember/schema'\nimport { ApiKeyByIdQuery, ApiKeyByTokenQuery, ApiKeyRow, ConfigurationQuery } from '../../queries'\nimport PostgresInterval from 'postgres-interval'\nimport { Config } from '../../type/Config'\nimport { intervalToSeconds } from '../../utils/interval'\n\nexport class ApiKeyManager {\n\tconstructor(\n\t\tprivate readonly apiKeyService: ApiKeyService,\n\t) {}\n\n\tasync verifyAndProlong(\n\t\tdbContext: DatabaseContext,\n\t\treadDbContext: DatabaseContext,\n\t\ttoken: string,\n\t): Promise<VerifyResponse> {\n\t\tconst apiKeyRow = await readDbContext.queryHandler.fetch(new ApiKeyByTokenQuery(token))\n\t\tif (apiKeyRow === null) {\n\t\t\treturn new ResponseError(VerifyErrorCode.NOT_FOUND, 'API key was not found')\n\t\t}\n\n\t\tif (apiKeyRow.disabled_at !== null) {\n\t\t\treturn new ResponseError(\n\t\t\t\tVerifyErrorCode.DISABLED,\n\t\t\t\t`API key was disabled at ${apiKeyRow.disabled_at.toISOString()}`,\n\t\t\t)\n\t\t}\n\n\t\tconst now = new Date()\n\t\tif (apiKeyRow.expires_at !== null && apiKeyRow.expires_at <= now) {\n\t\t\treturn new ResponseError(VerifyErrorCode.DISABLED, `API key expired at ${apiKeyRow.expires_at.toISOString()}`)\n\t\t}\n\n\t\tsetImmediate(async () => {\n\t\t\tawait dbContext.commandBus.execute(\n\t\t\t\tnew ProlongApiKeyCommand(\n\t\t\t\t\tapiKeyRow.id,\n\t\t\t\t\tapiKeyRow.type,\n\t\t\t\t\tapiKeyRow.expiration,\n\t\t\t\t\tapiKeyRow.expires_at,\n\t\t\t\t),\n\t\t\t)\n\t\t})\n\n\t\treturn new ResponseOk(new VerifyResult(apiKeyRow.identity_id, apiKeyRow.id, apiKeyRow.roles, apiKeyRow.person_id))\n\t}\n\n\tasync createSessionApiKey(dbContext: DatabaseContext, identityId: string, expiration?: number): Promise<string> {\n\t\tconst config = await dbContext.queryHandler.fetch(new ConfigurationQuery())\n\t\tconst expirationResolved = expiration ?? (intervalToSeconds(config.login.defaultTokenExpiration) / 60)\n\t\tconst expirationCapped = config.login.maxTokenExpiration\n\t\t\t? Math.min(expirationResolved, intervalToSeconds(config.login.maxTokenExpiration) / 60)\n\t\t\t: expirationResolved\n\t\tconst command = new CreateApiKeyCommand({ type: ApiKey.Type.SESSION, identityId, expiration: expirationCapped })\n\t\tconst token = (await dbContext.commandBus.execute(command)).token\n\t\tassert(token !== undefined)\n\t\treturn token\n\t}\n\n\tasync findApiKey(dbContext: DatabaseContext, apiKeyId: string): Promise<ApiKeyRow | null> {\n\t\treturn await dbContext.queryHandler.fetch(\n\t\t\tnew ApiKeyByIdQuery(apiKeyId),\n\t\t)\n\t}\n\n\tasync disableOneOffApiKey(dbContext: DatabaseContext, apiKeyId: string): Promise<void> {\n\t\tawait dbContext.commandBus.execute(new DisableOneOffApiKeyCommand(apiKeyId))\n\t}\n\n\tasync disableApiKey(dbContext: DatabaseContext, apiKeyId: string): Promise<boolean> {\n\t\treturn await dbContext.commandBus.execute(new DisableApiKeyCommand(apiKeyId))\n\t}\n\n\tasync disableIdentityApiKeys(dbContext: DatabaseContext, identityId: string): Promise<void> {\n\t\tawait dbContext.commandBus.execute(new DisableIdentityApiKeysCommand(identityId))\n\t}\n\n\tasync createGlobalPermanentApiKey(\n\t\tdbContext: DatabaseContext,\n\t\tdescription: string,\n\t\troles: readonly string[],\n\t\ttokenHash?: TokenHash,\n\t): Promise<CreateApiKeyResponse> {\n\t\treturn await dbContext.transaction(async db => {\n\t\t\treturn await this.apiKeyService.createPermanentApiKey(db, description, roles, tokenHash)\n\t\t})\n\t}\n\n\tasync createProjectPermanentApiKey(\n\t\tdbContext: DatabaseContext,\n\t\tprojectId: string,\n\t\tmemberships: readonly Acl.Membership[],\n\t\tdescription: string,\n\t\ttokenHash?: TokenHash,\n\t): Promise<CreateApiKeyResponse> {\n\t\treturn await dbContext.transaction(async db => {\n\t\t\treturn await this.apiKeyService.createProjectPermanentApiKey(db, projectId, memberships, description, tokenHash)\n\t\t})\n\t}\n}\n\nexport type VerifyResponse = Response<VerifyResult, VerifyErrorCode>\n\nexport class VerifyResult {\n\treadonly valid = true\n\n\tconstructor(\n\t\tpublic readonly identityId: string,\n\t\tpublic readonly apiKeyId: string,\n\t\tpublic readonly roles: string[],\n\t\tpublic readonly personId: string | null,\n\t) {}\n}\n\nexport enum VerifyErrorCode {\n\tNOT_FOUND = 'not_found',\n\tDISABLED = 'disabled',\n\tEXPIRED = 'expired',\n\tNO_AUTH_HEADER = 'no_auth_header',\n\tINVALID_AUTH_HEADER = 'invalid_auth_header',\n}\n"],"names":["VerifyErrorCode"],"mappings":"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"}
1
+ {"version":3,"file":"ApiKeyManager.js","sources":["../../../../../../../packages/engine-tenant-api/src/model/service/apiKey/ApiKeyManager.ts"],"sourcesContent":["import {\n\tCreateApiKeyCommand,\n\tDisableApiKeyCommand,\n\tDisableIdentityApiKeysCommand,\n\tDisableOneOffApiKeyCommand,\n\tProlongApiKeyCommand,\n} from '../../commands'\nimport { ApiKey } from '../../type'\n\nimport { Response, ResponseError, ResponseOk } from '../../utils/Response'\nimport { DatabaseContext, TokenHash } from '../../utils'\nimport { ApiKeyService, CreateApiKeyResponse } from './ApiKeyService'\nimport assert from 'node:assert'\nimport { Acl } from '@contember/schema'\nimport { ApiKeyByIdQuery, ApiKeyByTokenQuery, ApiKeyRow, ConfigurationQuery } from '../../queries'\nimport PostgresInterval from 'postgres-interval'\nimport { Config } from '../../type/Config'\nimport { intervalToSeconds } from '../../utils/interval'\n\nexport class ApiKeyManager {\n\tconstructor(\n\t\tprivate readonly apiKeyService: ApiKeyService,\n\t) {}\n\n\tasync verifyAndProlong(\n\t\tdbContext: DatabaseContext,\n\t\treadDbContext: DatabaseContext,\n\t\ttoken: string,\n\t): Promise<VerifyResponse> {\n\t\tconst apiKeyRow = await readDbContext.queryHandler.fetch(new ApiKeyByTokenQuery(token))\n\t\tif (apiKeyRow === null) {\n\t\t\treturn new ResponseError(VerifyErrorCode.NOT_FOUND, 'API key was not found')\n\t\t}\n\n\t\tif (apiKeyRow.disabled_at !== null) {\n\t\t\treturn new ResponseError(\n\t\t\t\tVerifyErrorCode.DISABLED,\n\t\t\t\t`API key was disabled at ${apiKeyRow.disabled_at.toISOString()}`,\n\t\t\t)\n\t\t}\n\n\t\tconst now = new Date()\n\t\tif (apiKeyRow.expires_at !== null && apiKeyRow.expires_at <= now) {\n\t\t\treturn new ResponseError(VerifyErrorCode.DISABLED, `API key expired at ${apiKeyRow.expires_at.toISOString()}`)\n\t\t}\n\n\t\t// Detached (runs after the response), so a rejection here would be an unhandledRejection and can take the\n\t\t// worker down. Tracking is best-effort — the next request writes it again.\n\t\tsetImmediate(() => {\n\t\t\tdbContext.commandBus.execute(\n\t\t\t\tnew ProlongApiKeyCommand(\n\t\t\t\t\tapiKeyRow.id,\n\t\t\t\t\tapiKeyRow.type,\n\t\t\t\t\tapiKeyRow.expiration,\n\t\t\t\t\tapiKeyRow.expires_at,\n\t\t\t\t),\n\t\t\t).catch(() => {})\n\t\t})\n\n\t\treturn new ResponseOk(new VerifyResult(apiKeyRow.identity_id, apiKeyRow.id, apiKeyRow.roles, apiKeyRow.person_id))\n\t}\n\n\tasync createSessionApiKey(dbContext: DatabaseContext, identityId: string, expiration?: number): Promise<string> {\n\t\tconst config = await dbContext.queryHandler.fetch(new ConfigurationQuery())\n\t\tconst expirationResolved = expiration ?? (intervalToSeconds(config.login.defaultTokenExpiration) / 60)\n\t\tconst expirationCapped = config.login.maxTokenExpiration\n\t\t\t? Math.min(expirationResolved, intervalToSeconds(config.login.maxTokenExpiration) / 60)\n\t\t\t: expirationResolved\n\t\tconst command = new CreateApiKeyCommand({ type: ApiKey.Type.SESSION, identityId, expiration: expirationCapped })\n\t\tconst token = (await dbContext.commandBus.execute(command)).token\n\t\tassert(token !== undefined)\n\t\treturn token\n\t}\n\n\tasync findApiKey(dbContext: DatabaseContext, apiKeyId: string): Promise<ApiKeyRow | null> {\n\t\treturn await dbContext.queryHandler.fetch(\n\t\t\tnew ApiKeyByIdQuery(apiKeyId),\n\t\t)\n\t}\n\n\tasync disableOneOffApiKey(dbContext: DatabaseContext, apiKeyId: string): Promise<void> {\n\t\tawait dbContext.commandBus.execute(new DisableOneOffApiKeyCommand(apiKeyId))\n\t}\n\n\tasync disableApiKey(dbContext: DatabaseContext, apiKeyId: string): Promise<boolean> {\n\t\treturn await dbContext.commandBus.execute(new DisableApiKeyCommand(apiKeyId))\n\t}\n\n\tasync disableIdentityApiKeys(dbContext: DatabaseContext, identityId: string): Promise<void> {\n\t\tawait dbContext.commandBus.execute(new DisableIdentityApiKeysCommand(identityId))\n\t}\n\n\tasync createGlobalPermanentApiKey(\n\t\tdbContext: DatabaseContext,\n\t\tdescription: string,\n\t\troles: readonly string[],\n\t\ttokenHash?: TokenHash,\n\t): Promise<CreateApiKeyResponse> {\n\t\treturn await dbContext.transaction(async db => {\n\t\t\treturn await this.apiKeyService.createPermanentApiKey(db, description, roles, tokenHash)\n\t\t})\n\t}\n\n\tasync createProjectPermanentApiKey(\n\t\tdbContext: DatabaseContext,\n\t\tprojectId: string,\n\t\tmemberships: readonly Acl.Membership[],\n\t\tdescription: string,\n\t\ttokenHash?: TokenHash,\n\t): Promise<CreateApiKeyResponse> {\n\t\treturn await dbContext.transaction(async db => {\n\t\t\treturn await this.apiKeyService.createProjectPermanentApiKey(db, projectId, memberships, description, tokenHash)\n\t\t})\n\t}\n}\n\nexport type VerifyResponse = Response<VerifyResult, VerifyErrorCode>\n\nexport class VerifyResult {\n\treadonly valid = true\n\n\tconstructor(\n\t\tpublic readonly identityId: string,\n\t\tpublic readonly apiKeyId: string,\n\t\tpublic readonly roles: string[],\n\t\tpublic readonly personId: string | null,\n\t) {}\n}\n\nexport enum VerifyErrorCode {\n\tNOT_FOUND = 'not_found',\n\tDISABLED = 'disabled',\n\tEXPIRED = 'expired',\n\tNO_AUTH_HEADER = 'no_auth_header',\n\tINVALID_AUTH_HEADER = 'invalid_auth_header',\n}\n"],"names":["VerifyErrorCode"],"mappings":"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"}
@@ -24,7 +24,7 @@ class IDPManager {
24
24
  return await db.transaction(async (db2) => {
25
25
  try {
26
26
  const providerService = this.idpRegistry.getHandler(idp.type);
27
- providerService.validateConfiguration(idp.configuration);
27
+ this.assertNoUnknownConfigurationKeys(providerService.validateConfiguration(idp.configuration), idp.configuration);
28
28
  } catch (e) {
29
29
  if (e instanceof IdentityProviderNotFoundError.IdentityProviderNotFoundError) {
30
30
  return new Response.ResponseError("UNKNOWN_TYPE", `IDP type ${idp.type} not found`);
@@ -93,7 +93,7 @@ class IDPManager {
93
93
  const type = data.type ?? existing.type;
94
94
  const providerService = this.idpRegistry.getHandler(type);
95
95
  if (data.configuration !== void 0 || type !== existing.type) {
96
- providerService.validateConfiguration(newConfiguration);
96
+ this.assertNoUnknownConfigurationKeys(providerService.validateConfiguration(newConfiguration), newConfiguration);
97
97
  }
98
98
  } catch (e) {
99
99
  if (e instanceof InvalidIDPConfigurationError.InvalidIDPConfigurationError) {
@@ -110,6 +110,32 @@ class IDPManager {
110
110
  return new Response.ResponseOk(null);
111
111
  });
112
112
  }
113
+ /**
114
+ * Reject top-level `configuration` keys the provider does not recognise.
115
+ *
116
+ * Every provider configuration is parsed with `Typesafe.partial`, which silently DROPS unknown
117
+ * properties. Without this check `addIDP` / `updateIDP` answer `ok` to a configuration the
118
+ * provider will never act on — a typo like `fetchUserinfo` (lowercase i) reads as a successfully
119
+ * applied setting while the option stays off, and an option that a given engine version does not
120
+ * support yet looks indistinguishable from one that works.
121
+ *
122
+ * The parsed result carries exactly the keys the provider understood, so the difference against
123
+ * the submitted object is the set of ignored keys. All of them are reported at once (rather than
124
+ * the first, as `Typesafe.noExtraProps` would) so a configuration with several typos takes one
125
+ * round trip to fix.
126
+ *
127
+ * Write time only: reading a stored configuration is deliberately left untouched, so a row written
128
+ * before this check keeps authenticating instead of locking its users out.
129
+ */
130
+ assertNoUnknownConfigurationKeys(parsed, submitted) {
131
+ const known = new Set(Object.keys(parsed));
132
+ const unknown = Object.keys(submitted).filter((key) => !known.has(key));
133
+ if (unknown.length > 0) {
134
+ throw new InvalidIDPConfigurationError.InvalidIDPConfigurationError(
135
+ `unknown configuration ${unknown.length === 1 ? "property" : "properties"} ${unknown.join(", ")}; remove ${unknown.length === 1 ? "it" : "them"} or check the spelling — the provider would ignore ${unknown.length === 1 ? "it" : "them"}`
136
+ );
137
+ }
138
+ }
113
139
  async listIDP(db) {
114
140
  const result = await db.queryHandler.fetch(new IdentityProvidersQuery.IdentityProvidersQuery());
115
141
  return result.map((it) => {
@@ -1 +1 @@
1
- {"version":3,"file":"IDPManager.cjs","sources":["../../../../../../../packages/engine-tenant-api/src/model/service/idp/IDPManager.ts"],"sourcesContent":["import { Response, ResponseError, ResponseOk } from '../../utils/Response'\nimport { AddIdpErrorCode, DisableIdpErrorCode, EnableIdpErrorCode, UpdateIdpErrorCode } from '../../../schema'\nimport { DatabaseContext } from '../../utils'\nimport { IdentityProviderData } from '../../type'\nimport { IdentityProviderBySlugQuery, IdentityProvidersQuery } from '../../queries'\nimport { CreateIdpCommand } from '../../commands/idp/CreateIdpCommand'\nimport { IDPHandlerRegistry } from './IDPHandlerRegistry'\nimport { IdentityProviderNotFoundError } from './IdentityProviderNotFoundError'\nimport { InvalidIDPConfigurationError } from './InvalidIDPConfigurationError'\nimport { DisableIdpCommand } from '../../commands/idp/DisableIdpCommand'\nimport { EnableIdpCommand } from '../../commands/idp/EnableIdpCommand'\nimport { UpdateIdpCommand, UpdateIdpData } from '../../commands/idp/UpdateIdpCommand'\nimport { IdentityProviderDto } from '../../queries/idp/types'\n\nexport class IDPManager {\n\tconstructor(\n\t\tprivate readonly idpRegistry: IDPHandlerRegistry,\n\t) {\n\t}\n\n\tpublic async addIDP(db: DatabaseContext, idp: IdentityProviderData): Promise<RegisterIDPResponse> {\n\t\treturn await db.transaction(async (db): Promise<RegisterIDPResponse> => {\n\t\t\ttry {\n\t\t\t\tconst providerService = this.idpRegistry.getHandler(idp.type)\n\t\t\t\tproviderService.validateConfiguration(idp.configuration)\n\t\t\t} catch (e) {\n\t\t\t\tif (e instanceof IdentityProviderNotFoundError) {\n\t\t\t\t\treturn new ResponseError('UNKNOWN_TYPE', `IDP type ${idp.type} not found`)\n\t\t\t\t}\n\t\t\t\tif (e instanceof InvalidIDPConfigurationError) {\n\t\t\t\t\treturn new ResponseError('INVALID_CONFIGURATION', `Invalid IDP configuration: ${e.message}`)\n\t\t\t\t}\n\t\t\t\tthrow e\n\t\t\t}\n\n\t\t\tconst existing = await db.queryHandler.fetch(new IdentityProviderBySlugQuery(idp.slug))\n\t\t\tif (existing) {\n\t\t\t\treturn new ResponseError('ALREADY_EXISTS', `IDP with slug ${idp.slug} already exists`)\n\t\t\t}\n\t\t\tawait db.commandBus.execute(new CreateIdpCommand(idp))\n\t\t\treturn new ResponseOk(null)\n\t\t})\n\t}\n\n\tpublic async disableIDP(db: DatabaseContext, slug: string): Promise<DisableIDPResponse> {\n\t\treturn await db.transaction(async db => {\n\t\t\tconst existing = await db.queryHandler.fetch(new IdentityProviderBySlugQuery(slug))\n\t\t\tif (!existing) {\n\t\t\t\treturn new ResponseError('NOT_FOUND', `IDP ${slug} not found`)\n\t\t\t}\n\t\t\tif (!existing.disabledAt) {\n\t\t\t\tawait db.commandBus.execute(new DisableIdpCommand(existing.id))\n\t\t\t}\n\t\t\treturn new ResponseOk(null)\n\t\t})\n\t}\n\n\tpublic async enableIDP(db: DatabaseContext, slug: string): Promise<EnableIDPResponse> {\n\t\treturn await db.transaction(async db => {\n\t\t\tconst existing = await db.queryHandler.fetch(new IdentityProviderBySlugQuery(slug))\n\t\t\tif (!existing) {\n\t\t\t\treturn new ResponseError('NOT_FOUND', `IDP ${slug} not found`)\n\t\t\t}\n\t\t\tif (existing.disabledAt) {\n\t\t\t\tawait db.commandBus.execute(new EnableIdpCommand(existing.id))\n\t\t\t}\n\t\t\treturn new ResponseOk(null)\n\t\t})\n\t}\n\n\tpublic async updateIDP(db: DatabaseContext, slug: string, data: UpdateIdpData, mergeConfiguration: boolean): Promise<UpdateIDPResponse> {\n\t\treturn await db.transaction(async db => {\n\t\t\tconst existing = await db.queryHandler.fetch(new IdentityProviderBySlugQuery(slug))\n\t\t\tif (!existing) {\n\t\t\t\treturn new ResponseError('NOT_FOUND', `IDP ${slug} not found`)\n\t\t\t}\n\n\t\t\tconst newConfiguration = (() => {\n\t\t\t\tif (!data.configuration) {\n\t\t\t\t\treturn existing.configuration\n\t\t\t\t}\n\t\t\t\tif (!mergeConfiguration) {\n\t\t\t\t\treturn data.configuration\n\t\t\t\t}\n\n\t\t\t\tconst newConfiguration = { ...existing.configuration }\n\t\t\t\tfor (const [key, value] of Object.entries(data.configuration)) {\n\t\t\t\t\tif (value === null) {\n\t\t\t\t\t\tdelete newConfiguration[key]\n\t\t\t\t\t} else {\n\t\t\t\t\t\tnewConfiguration[key] = value\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn newConfiguration\n\t\t\t})()\n\n\t\t\ttry {\n\t\t\t\tconst type = data.type ?? existing.type\n\t\t\t\tconst providerService = this.idpRegistry.getHandler(type)\n\t\t\t\tif (data.configuration !== undefined || type !== existing.type) {\n\t\t\t\t\tproviderService.validateConfiguration(newConfiguration)\n\t\t\t\t}\n\t\t\t} catch (e) {\n\t\t\t\tif (e instanceof InvalidIDPConfigurationError) {\n\t\t\t\t\treturn new ResponseError('INVALID_CONFIGURATION', `Invalid IDP configuration: ${e.message}`)\n\t\t\t\t}\n\t\t\t\tthrow e\n\t\t\t}\n\n\t\t\tawait db.commandBus.execute(\n\t\t\t\tnew UpdateIdpCommand(existing.id, {\n\t\t\t\t\t...data,\n\t\t\t\t\tconfiguration: data.configuration ? newConfiguration : undefined,\n\t\t\t\t}),\n\t\t\t)\n\n\t\t\treturn new ResponseOk(null)\n\t\t})\n\t}\n\n\tpublic async listIDP(db: DatabaseContext): Promise<IdentityProviderDto[]> {\n\t\tconst result = await db.queryHandler.fetch(new IdentityProvidersQuery())\n\t\treturn result.map(it => {\n\t\t\tconst providerService = this.idpRegistry.getHandlerOrNull(it.type)\n\t\t\tconst configuration = providerService?.getPublicConfiguration\n\t\t\t\t? providerService.getPublicConfiguration(it.configuration)\n\t\t\t\t: it.configuration\n\n\t\t\treturn { ...it, configuration }\n\t\t})\n\t}\n}\n\nexport type RegisterIDPResponse = Response<null, AddIdpErrorCode>\nexport type DisableIDPResponse = Response<null, DisableIdpErrorCode>\nexport type EnableIDPResponse = Response<null, EnableIdpErrorCode>\nexport type UpdateIDPResponse = Response<null, UpdateIdpErrorCode>\n"],"names":["db","IdentityProviderNotFoundError","ResponseError","InvalidIDPConfigurationError","IdentityProviderBySlugQuery","CreateIdpCommand","ResponseOk","DisableIdpCommand","EnableIdpCommand","newConfiguration","UpdateIdpCommand","IdentityProvidersQuery"],"mappings":"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"}
1
+ {"version":3,"file":"IDPManager.cjs","sources":["../../../../../../../packages/engine-tenant-api/src/model/service/idp/IDPManager.ts"],"sourcesContent":["import { Response, ResponseError, ResponseOk } from '../../utils/Response'\nimport { AddIdpErrorCode, DisableIdpErrorCode, EnableIdpErrorCode, UpdateIdpErrorCode } from '../../../schema'\nimport { DatabaseContext } from '../../utils'\nimport { IdentityProviderData } from '../../type'\nimport { IdentityProviderBySlugQuery, IdentityProvidersQuery } from '../../queries'\nimport { CreateIdpCommand } from '../../commands/idp/CreateIdpCommand'\nimport { IDPHandlerRegistry } from './IDPHandlerRegistry'\nimport { IdentityProviderNotFoundError } from './IdentityProviderNotFoundError'\nimport { InvalidIDPConfigurationError } from './InvalidIDPConfigurationError'\nimport { DisableIdpCommand } from '../../commands/idp/DisableIdpCommand'\nimport { EnableIdpCommand } from '../../commands/idp/EnableIdpCommand'\nimport { UpdateIdpCommand, UpdateIdpData } from '../../commands/idp/UpdateIdpCommand'\nimport { IdentityProviderDto } from '../../queries/idp/types'\n\nexport class IDPManager {\n\tconstructor(\n\t\tprivate readonly idpRegistry: IDPHandlerRegistry,\n\t) {\n\t}\n\n\tpublic async addIDP(db: DatabaseContext, idp: IdentityProviderData): Promise<RegisterIDPResponse> {\n\t\treturn await db.transaction(async (db): Promise<RegisterIDPResponse> => {\n\t\t\ttry {\n\t\t\t\tconst providerService = this.idpRegistry.getHandler(idp.type)\n\t\t\t\tthis.assertNoUnknownConfigurationKeys(providerService.validateConfiguration(idp.configuration), idp.configuration)\n\t\t\t} catch (e) {\n\t\t\t\tif (e instanceof IdentityProviderNotFoundError) {\n\t\t\t\t\treturn new ResponseError('UNKNOWN_TYPE', `IDP type ${idp.type} not found`)\n\t\t\t\t}\n\t\t\t\tif (e instanceof InvalidIDPConfigurationError) {\n\t\t\t\t\treturn new ResponseError('INVALID_CONFIGURATION', `Invalid IDP configuration: ${e.message}`)\n\t\t\t\t}\n\t\t\t\tthrow e\n\t\t\t}\n\n\t\t\tconst existing = await db.queryHandler.fetch(new IdentityProviderBySlugQuery(idp.slug))\n\t\t\tif (existing) {\n\t\t\t\treturn new ResponseError('ALREADY_EXISTS', `IDP with slug ${idp.slug} already exists`)\n\t\t\t}\n\t\t\tawait db.commandBus.execute(new CreateIdpCommand(idp))\n\t\t\treturn new ResponseOk(null)\n\t\t})\n\t}\n\n\tpublic async disableIDP(db: DatabaseContext, slug: string): Promise<DisableIDPResponse> {\n\t\treturn await db.transaction(async db => {\n\t\t\tconst existing = await db.queryHandler.fetch(new IdentityProviderBySlugQuery(slug))\n\t\t\tif (!existing) {\n\t\t\t\treturn new ResponseError('NOT_FOUND', `IDP ${slug} not found`)\n\t\t\t}\n\t\t\tif (!existing.disabledAt) {\n\t\t\t\tawait db.commandBus.execute(new DisableIdpCommand(existing.id))\n\t\t\t}\n\t\t\treturn new ResponseOk(null)\n\t\t})\n\t}\n\n\tpublic async enableIDP(db: DatabaseContext, slug: string): Promise<EnableIDPResponse> {\n\t\treturn await db.transaction(async db => {\n\t\t\tconst existing = await db.queryHandler.fetch(new IdentityProviderBySlugQuery(slug))\n\t\t\tif (!existing) {\n\t\t\t\treturn new ResponseError('NOT_FOUND', `IDP ${slug} not found`)\n\t\t\t}\n\t\t\tif (existing.disabledAt) {\n\t\t\t\tawait db.commandBus.execute(new EnableIdpCommand(existing.id))\n\t\t\t}\n\t\t\treturn new ResponseOk(null)\n\t\t})\n\t}\n\n\tpublic async updateIDP(db: DatabaseContext, slug: string, data: UpdateIdpData, mergeConfiguration: boolean): Promise<UpdateIDPResponse> {\n\t\treturn await db.transaction(async db => {\n\t\t\tconst existing = await db.queryHandler.fetch(new IdentityProviderBySlugQuery(slug))\n\t\t\tif (!existing) {\n\t\t\t\treturn new ResponseError('NOT_FOUND', `IDP ${slug} not found`)\n\t\t\t}\n\n\t\t\tconst newConfiguration = (() => {\n\t\t\t\tif (!data.configuration) {\n\t\t\t\t\treturn existing.configuration\n\t\t\t\t}\n\t\t\t\tif (!mergeConfiguration) {\n\t\t\t\t\treturn data.configuration\n\t\t\t\t}\n\n\t\t\t\tconst newConfiguration = { ...existing.configuration }\n\t\t\t\tfor (const [key, value] of Object.entries(data.configuration)) {\n\t\t\t\t\tif (value === null) {\n\t\t\t\t\t\tdelete newConfiguration[key]\n\t\t\t\t\t} else {\n\t\t\t\t\t\tnewConfiguration[key] = value\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn newConfiguration\n\t\t\t})()\n\n\t\t\ttry {\n\t\t\t\tconst type = data.type ?? existing.type\n\t\t\t\tconst providerService = this.idpRegistry.getHandler(type)\n\t\t\t\tif (data.configuration !== undefined || type !== existing.type) {\n\t\t\t\t\tthis.assertNoUnknownConfigurationKeys(providerService.validateConfiguration(newConfiguration), newConfiguration)\n\t\t\t\t}\n\t\t\t} catch (e) {\n\t\t\t\tif (e instanceof InvalidIDPConfigurationError) {\n\t\t\t\t\treturn new ResponseError('INVALID_CONFIGURATION', `Invalid IDP configuration: ${e.message}`)\n\t\t\t\t}\n\t\t\t\tthrow e\n\t\t\t}\n\n\t\t\tawait db.commandBus.execute(\n\t\t\t\tnew UpdateIdpCommand(existing.id, {\n\t\t\t\t\t...data,\n\t\t\t\t\tconfiguration: data.configuration ? newConfiguration : undefined,\n\t\t\t\t}),\n\t\t\t)\n\n\t\t\treturn new ResponseOk(null)\n\t\t})\n\t}\n\n\t/**\n\t * Reject top-level `configuration` keys the provider does not recognise.\n\t *\n\t * Every provider configuration is parsed with `Typesafe.partial`, which silently DROPS unknown\n\t * properties. Without this check `addIDP` / `updateIDP` answer `ok` to a configuration the\n\t * provider will never act on — a typo like `fetchUserinfo` (lowercase i) reads as a successfully\n\t * applied setting while the option stays off, and an option that a given engine version does not\n\t * support yet looks indistinguishable from one that works.\n\t *\n\t * The parsed result carries exactly the keys the provider understood, so the difference against\n\t * the submitted object is the set of ignored keys. All of them are reported at once (rather than\n\t * the first, as `Typesafe.noExtraProps` would) so a configuration with several typos takes one\n\t * round trip to fix.\n\t *\n\t * Write time only: reading a stored configuration is deliberately left untouched, so a row written\n\t * before this check keeps authenticating instead of locking its users out.\n\t */\n\tprivate assertNoUnknownConfigurationKeys(parsed: {}, submitted: Record<string, unknown>): void {\n\t\tconst known = new Set(Object.keys(parsed))\n\t\tconst unknown = Object.keys(submitted).filter(key => !known.has(key))\n\t\tif (unknown.length > 0) {\n\t\t\tthrow new InvalidIDPConfigurationError(\n\t\t\t\t`unknown configuration ${unknown.length === 1 ? 'property' : 'properties'} ${unknown.join(', ')}; `\n\t\t\t\t\t+ `remove ${unknown.length === 1 ? 'it' : 'them'} or check the spelling — the provider would ignore ${unknown.length === 1 ? 'it' : 'them'}`,\n\t\t\t)\n\t\t}\n\t}\n\n\tpublic async listIDP(db: DatabaseContext): Promise<IdentityProviderDto[]> {\n\t\tconst result = await db.queryHandler.fetch(new IdentityProvidersQuery())\n\t\treturn result.map(it => {\n\t\t\tconst providerService = this.idpRegistry.getHandlerOrNull(it.type)\n\t\t\tconst configuration = providerService?.getPublicConfiguration\n\t\t\t\t? providerService.getPublicConfiguration(it.configuration)\n\t\t\t\t: it.configuration\n\n\t\t\treturn { ...it, configuration }\n\t\t})\n\t}\n}\n\nexport type RegisterIDPResponse = Response<null, AddIdpErrorCode>\nexport type DisableIDPResponse = Response<null, DisableIdpErrorCode>\nexport type EnableIDPResponse = Response<null, EnableIdpErrorCode>\nexport type UpdateIDPResponse = Response<null, UpdateIdpErrorCode>\n"],"names":["db","IdentityProviderNotFoundError","ResponseError","InvalidIDPConfigurationError","IdentityProviderBySlugQuery","CreateIdpCommand","ResponseOk","DisableIdpCommand","EnableIdpCommand","newConfiguration","UpdateIdpCommand","IdentityProvidersQuery"],"mappings":"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"}
@@ -22,7 +22,7 @@ class IDPManager {
22
22
  return await db.transaction(async (db2) => {
23
23
  try {
24
24
  const providerService = this.idpRegistry.getHandler(idp.type);
25
- providerService.validateConfiguration(idp.configuration);
25
+ this.assertNoUnknownConfigurationKeys(providerService.validateConfiguration(idp.configuration), idp.configuration);
26
26
  } catch (e) {
27
27
  if (e instanceof IdentityProviderNotFoundError) {
28
28
  return new ResponseError("UNKNOWN_TYPE", `IDP type ${idp.type} not found`);
@@ -91,7 +91,7 @@ class IDPManager {
91
91
  const type = data.type ?? existing.type;
92
92
  const providerService = this.idpRegistry.getHandler(type);
93
93
  if (data.configuration !== void 0 || type !== existing.type) {
94
- providerService.validateConfiguration(newConfiguration);
94
+ this.assertNoUnknownConfigurationKeys(providerService.validateConfiguration(newConfiguration), newConfiguration);
95
95
  }
96
96
  } catch (e) {
97
97
  if (e instanceof InvalidIDPConfigurationError) {
@@ -108,6 +108,32 @@ class IDPManager {
108
108
  return new ResponseOk(null);
109
109
  });
110
110
  }
111
+ /**
112
+ * Reject top-level `configuration` keys the provider does not recognise.
113
+ *
114
+ * Every provider configuration is parsed with `Typesafe.partial`, which silently DROPS unknown
115
+ * properties. Without this check `addIDP` / `updateIDP` answer `ok` to a configuration the
116
+ * provider will never act on — a typo like `fetchUserinfo` (lowercase i) reads as a successfully
117
+ * applied setting while the option stays off, and an option that a given engine version does not
118
+ * support yet looks indistinguishable from one that works.
119
+ *
120
+ * The parsed result carries exactly the keys the provider understood, so the difference against
121
+ * the submitted object is the set of ignored keys. All of them are reported at once (rather than
122
+ * the first, as `Typesafe.noExtraProps` would) so a configuration with several typos takes one
123
+ * round trip to fix.
124
+ *
125
+ * Write time only: reading a stored configuration is deliberately left untouched, so a row written
126
+ * before this check keeps authenticating instead of locking its users out.
127
+ */
128
+ assertNoUnknownConfigurationKeys(parsed, submitted) {
129
+ const known = new Set(Object.keys(parsed));
130
+ const unknown = Object.keys(submitted).filter((key) => !known.has(key));
131
+ if (unknown.length > 0) {
132
+ throw new InvalidIDPConfigurationError(
133
+ `unknown configuration ${unknown.length === 1 ? "property" : "properties"} ${unknown.join(", ")}; remove ${unknown.length === 1 ? "it" : "them"} or check the spelling — the provider would ignore ${unknown.length === 1 ? "it" : "them"}`
134
+ );
135
+ }
136
+ }
111
137
  async listIDP(db) {
112
138
  const result = await db.queryHandler.fetch(new IdentityProvidersQuery());
113
139
  return result.map((it) => {
@@ -1 +1 @@
1
- {"version":3,"file":"IDPManager.js","sources":["../../../../../../../packages/engine-tenant-api/src/model/service/idp/IDPManager.ts"],"sourcesContent":["import { Response, ResponseError, ResponseOk } from '../../utils/Response'\nimport { AddIdpErrorCode, DisableIdpErrorCode, EnableIdpErrorCode, UpdateIdpErrorCode } from '../../../schema'\nimport { DatabaseContext } from '../../utils'\nimport { IdentityProviderData } from '../../type'\nimport { IdentityProviderBySlugQuery, IdentityProvidersQuery } from '../../queries'\nimport { CreateIdpCommand } from '../../commands/idp/CreateIdpCommand'\nimport { IDPHandlerRegistry } from './IDPHandlerRegistry'\nimport { IdentityProviderNotFoundError } from './IdentityProviderNotFoundError'\nimport { InvalidIDPConfigurationError } from './InvalidIDPConfigurationError'\nimport { DisableIdpCommand } from '../../commands/idp/DisableIdpCommand'\nimport { EnableIdpCommand } from '../../commands/idp/EnableIdpCommand'\nimport { UpdateIdpCommand, UpdateIdpData } from '../../commands/idp/UpdateIdpCommand'\nimport { IdentityProviderDto } from '../../queries/idp/types'\n\nexport class IDPManager {\n\tconstructor(\n\t\tprivate readonly idpRegistry: IDPHandlerRegistry,\n\t) {\n\t}\n\n\tpublic async addIDP(db: DatabaseContext, idp: IdentityProviderData): Promise<RegisterIDPResponse> {\n\t\treturn await db.transaction(async (db): Promise<RegisterIDPResponse> => {\n\t\t\ttry {\n\t\t\t\tconst providerService = this.idpRegistry.getHandler(idp.type)\n\t\t\t\tproviderService.validateConfiguration(idp.configuration)\n\t\t\t} catch (e) {\n\t\t\t\tif (e instanceof IdentityProviderNotFoundError) {\n\t\t\t\t\treturn new ResponseError('UNKNOWN_TYPE', `IDP type ${idp.type} not found`)\n\t\t\t\t}\n\t\t\t\tif (e instanceof InvalidIDPConfigurationError) {\n\t\t\t\t\treturn new ResponseError('INVALID_CONFIGURATION', `Invalid IDP configuration: ${e.message}`)\n\t\t\t\t}\n\t\t\t\tthrow e\n\t\t\t}\n\n\t\t\tconst existing = await db.queryHandler.fetch(new IdentityProviderBySlugQuery(idp.slug))\n\t\t\tif (existing) {\n\t\t\t\treturn new ResponseError('ALREADY_EXISTS', `IDP with slug ${idp.slug} already exists`)\n\t\t\t}\n\t\t\tawait db.commandBus.execute(new CreateIdpCommand(idp))\n\t\t\treturn new ResponseOk(null)\n\t\t})\n\t}\n\n\tpublic async disableIDP(db: DatabaseContext, slug: string): Promise<DisableIDPResponse> {\n\t\treturn await db.transaction(async db => {\n\t\t\tconst existing = await db.queryHandler.fetch(new IdentityProviderBySlugQuery(slug))\n\t\t\tif (!existing) {\n\t\t\t\treturn new ResponseError('NOT_FOUND', `IDP ${slug} not found`)\n\t\t\t}\n\t\t\tif (!existing.disabledAt) {\n\t\t\t\tawait db.commandBus.execute(new DisableIdpCommand(existing.id))\n\t\t\t}\n\t\t\treturn new ResponseOk(null)\n\t\t})\n\t}\n\n\tpublic async enableIDP(db: DatabaseContext, slug: string): Promise<EnableIDPResponse> {\n\t\treturn await db.transaction(async db => {\n\t\t\tconst existing = await db.queryHandler.fetch(new IdentityProviderBySlugQuery(slug))\n\t\t\tif (!existing) {\n\t\t\t\treturn new ResponseError('NOT_FOUND', `IDP ${slug} not found`)\n\t\t\t}\n\t\t\tif (existing.disabledAt) {\n\t\t\t\tawait db.commandBus.execute(new EnableIdpCommand(existing.id))\n\t\t\t}\n\t\t\treturn new ResponseOk(null)\n\t\t})\n\t}\n\n\tpublic async updateIDP(db: DatabaseContext, slug: string, data: UpdateIdpData, mergeConfiguration: boolean): Promise<UpdateIDPResponse> {\n\t\treturn await db.transaction(async db => {\n\t\t\tconst existing = await db.queryHandler.fetch(new IdentityProviderBySlugQuery(slug))\n\t\t\tif (!existing) {\n\t\t\t\treturn new ResponseError('NOT_FOUND', `IDP ${slug} not found`)\n\t\t\t}\n\n\t\t\tconst newConfiguration = (() => {\n\t\t\t\tif (!data.configuration) {\n\t\t\t\t\treturn existing.configuration\n\t\t\t\t}\n\t\t\t\tif (!mergeConfiguration) {\n\t\t\t\t\treturn data.configuration\n\t\t\t\t}\n\n\t\t\t\tconst newConfiguration = { ...existing.configuration }\n\t\t\t\tfor (const [key, value] of Object.entries(data.configuration)) {\n\t\t\t\t\tif (value === null) {\n\t\t\t\t\t\tdelete newConfiguration[key]\n\t\t\t\t\t} else {\n\t\t\t\t\t\tnewConfiguration[key] = value\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn newConfiguration\n\t\t\t})()\n\n\t\t\ttry {\n\t\t\t\tconst type = data.type ?? existing.type\n\t\t\t\tconst providerService = this.idpRegistry.getHandler(type)\n\t\t\t\tif (data.configuration !== undefined || type !== existing.type) {\n\t\t\t\t\tproviderService.validateConfiguration(newConfiguration)\n\t\t\t\t}\n\t\t\t} catch (e) {\n\t\t\t\tif (e instanceof InvalidIDPConfigurationError) {\n\t\t\t\t\treturn new ResponseError('INVALID_CONFIGURATION', `Invalid IDP configuration: ${e.message}`)\n\t\t\t\t}\n\t\t\t\tthrow e\n\t\t\t}\n\n\t\t\tawait db.commandBus.execute(\n\t\t\t\tnew UpdateIdpCommand(existing.id, {\n\t\t\t\t\t...data,\n\t\t\t\t\tconfiguration: data.configuration ? newConfiguration : undefined,\n\t\t\t\t}),\n\t\t\t)\n\n\t\t\treturn new ResponseOk(null)\n\t\t})\n\t}\n\n\tpublic async listIDP(db: DatabaseContext): Promise<IdentityProviderDto[]> {\n\t\tconst result = await db.queryHandler.fetch(new IdentityProvidersQuery())\n\t\treturn result.map(it => {\n\t\t\tconst providerService = this.idpRegistry.getHandlerOrNull(it.type)\n\t\t\tconst configuration = providerService?.getPublicConfiguration\n\t\t\t\t? providerService.getPublicConfiguration(it.configuration)\n\t\t\t\t: it.configuration\n\n\t\t\treturn { ...it, configuration }\n\t\t})\n\t}\n}\n\nexport type RegisterIDPResponse = Response<null, AddIdpErrorCode>\nexport type DisableIDPResponse = Response<null, DisableIdpErrorCode>\nexport type EnableIDPResponse = Response<null, EnableIdpErrorCode>\nexport type UpdateIDPResponse = Response<null, UpdateIdpErrorCode>\n"],"names":["db","newConfiguration"],"mappings":"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"}
1
+ {"version":3,"file":"IDPManager.js","sources":["../../../../../../../packages/engine-tenant-api/src/model/service/idp/IDPManager.ts"],"sourcesContent":["import { Response, ResponseError, ResponseOk } from '../../utils/Response'\nimport { AddIdpErrorCode, DisableIdpErrorCode, EnableIdpErrorCode, UpdateIdpErrorCode } from '../../../schema'\nimport { DatabaseContext } from '../../utils'\nimport { IdentityProviderData } from '../../type'\nimport { IdentityProviderBySlugQuery, IdentityProvidersQuery } from '../../queries'\nimport { CreateIdpCommand } from '../../commands/idp/CreateIdpCommand'\nimport { IDPHandlerRegistry } from './IDPHandlerRegistry'\nimport { IdentityProviderNotFoundError } from './IdentityProviderNotFoundError'\nimport { InvalidIDPConfigurationError } from './InvalidIDPConfigurationError'\nimport { DisableIdpCommand } from '../../commands/idp/DisableIdpCommand'\nimport { EnableIdpCommand } from '../../commands/idp/EnableIdpCommand'\nimport { UpdateIdpCommand, UpdateIdpData } from '../../commands/idp/UpdateIdpCommand'\nimport { IdentityProviderDto } from '../../queries/idp/types'\n\nexport class IDPManager {\n\tconstructor(\n\t\tprivate readonly idpRegistry: IDPHandlerRegistry,\n\t) {\n\t}\n\n\tpublic async addIDP(db: DatabaseContext, idp: IdentityProviderData): Promise<RegisterIDPResponse> {\n\t\treturn await db.transaction(async (db): Promise<RegisterIDPResponse> => {\n\t\t\ttry {\n\t\t\t\tconst providerService = this.idpRegistry.getHandler(idp.type)\n\t\t\t\tthis.assertNoUnknownConfigurationKeys(providerService.validateConfiguration(idp.configuration), idp.configuration)\n\t\t\t} catch (e) {\n\t\t\t\tif (e instanceof IdentityProviderNotFoundError) {\n\t\t\t\t\treturn new ResponseError('UNKNOWN_TYPE', `IDP type ${idp.type} not found`)\n\t\t\t\t}\n\t\t\t\tif (e instanceof InvalidIDPConfigurationError) {\n\t\t\t\t\treturn new ResponseError('INVALID_CONFIGURATION', `Invalid IDP configuration: ${e.message}`)\n\t\t\t\t}\n\t\t\t\tthrow e\n\t\t\t}\n\n\t\t\tconst existing = await db.queryHandler.fetch(new IdentityProviderBySlugQuery(idp.slug))\n\t\t\tif (existing) {\n\t\t\t\treturn new ResponseError('ALREADY_EXISTS', `IDP with slug ${idp.slug} already exists`)\n\t\t\t}\n\t\t\tawait db.commandBus.execute(new CreateIdpCommand(idp))\n\t\t\treturn new ResponseOk(null)\n\t\t})\n\t}\n\n\tpublic async disableIDP(db: DatabaseContext, slug: string): Promise<DisableIDPResponse> {\n\t\treturn await db.transaction(async db => {\n\t\t\tconst existing = await db.queryHandler.fetch(new IdentityProviderBySlugQuery(slug))\n\t\t\tif (!existing) {\n\t\t\t\treturn new ResponseError('NOT_FOUND', `IDP ${slug} not found`)\n\t\t\t}\n\t\t\tif (!existing.disabledAt) {\n\t\t\t\tawait db.commandBus.execute(new DisableIdpCommand(existing.id))\n\t\t\t}\n\t\t\treturn new ResponseOk(null)\n\t\t})\n\t}\n\n\tpublic async enableIDP(db: DatabaseContext, slug: string): Promise<EnableIDPResponse> {\n\t\treturn await db.transaction(async db => {\n\t\t\tconst existing = await db.queryHandler.fetch(new IdentityProviderBySlugQuery(slug))\n\t\t\tif (!existing) {\n\t\t\t\treturn new ResponseError('NOT_FOUND', `IDP ${slug} not found`)\n\t\t\t}\n\t\t\tif (existing.disabledAt) {\n\t\t\t\tawait db.commandBus.execute(new EnableIdpCommand(existing.id))\n\t\t\t}\n\t\t\treturn new ResponseOk(null)\n\t\t})\n\t}\n\n\tpublic async updateIDP(db: DatabaseContext, slug: string, data: UpdateIdpData, mergeConfiguration: boolean): Promise<UpdateIDPResponse> {\n\t\treturn await db.transaction(async db => {\n\t\t\tconst existing = await db.queryHandler.fetch(new IdentityProviderBySlugQuery(slug))\n\t\t\tif (!existing) {\n\t\t\t\treturn new ResponseError('NOT_FOUND', `IDP ${slug} not found`)\n\t\t\t}\n\n\t\t\tconst newConfiguration = (() => {\n\t\t\t\tif (!data.configuration) {\n\t\t\t\t\treturn existing.configuration\n\t\t\t\t}\n\t\t\t\tif (!mergeConfiguration) {\n\t\t\t\t\treturn data.configuration\n\t\t\t\t}\n\n\t\t\t\tconst newConfiguration = { ...existing.configuration }\n\t\t\t\tfor (const [key, value] of Object.entries(data.configuration)) {\n\t\t\t\t\tif (value === null) {\n\t\t\t\t\t\tdelete newConfiguration[key]\n\t\t\t\t\t} else {\n\t\t\t\t\t\tnewConfiguration[key] = value\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn newConfiguration\n\t\t\t})()\n\n\t\t\ttry {\n\t\t\t\tconst type = data.type ?? existing.type\n\t\t\t\tconst providerService = this.idpRegistry.getHandler(type)\n\t\t\t\tif (data.configuration !== undefined || type !== existing.type) {\n\t\t\t\t\tthis.assertNoUnknownConfigurationKeys(providerService.validateConfiguration(newConfiguration), newConfiguration)\n\t\t\t\t}\n\t\t\t} catch (e) {\n\t\t\t\tif (e instanceof InvalidIDPConfigurationError) {\n\t\t\t\t\treturn new ResponseError('INVALID_CONFIGURATION', `Invalid IDP configuration: ${e.message}`)\n\t\t\t\t}\n\t\t\t\tthrow e\n\t\t\t}\n\n\t\t\tawait db.commandBus.execute(\n\t\t\t\tnew UpdateIdpCommand(existing.id, {\n\t\t\t\t\t...data,\n\t\t\t\t\tconfiguration: data.configuration ? newConfiguration : undefined,\n\t\t\t\t}),\n\t\t\t)\n\n\t\t\treturn new ResponseOk(null)\n\t\t})\n\t}\n\n\t/**\n\t * Reject top-level `configuration` keys the provider does not recognise.\n\t *\n\t * Every provider configuration is parsed with `Typesafe.partial`, which silently DROPS unknown\n\t * properties. Without this check `addIDP` / `updateIDP` answer `ok` to a configuration the\n\t * provider will never act on — a typo like `fetchUserinfo` (lowercase i) reads as a successfully\n\t * applied setting while the option stays off, and an option that a given engine version does not\n\t * support yet looks indistinguishable from one that works.\n\t *\n\t * The parsed result carries exactly the keys the provider understood, so the difference against\n\t * the submitted object is the set of ignored keys. All of them are reported at once (rather than\n\t * the first, as `Typesafe.noExtraProps` would) so a configuration with several typos takes one\n\t * round trip to fix.\n\t *\n\t * Write time only: reading a stored configuration is deliberately left untouched, so a row written\n\t * before this check keeps authenticating instead of locking its users out.\n\t */\n\tprivate assertNoUnknownConfigurationKeys(parsed: {}, submitted: Record<string, unknown>): void {\n\t\tconst known = new Set(Object.keys(parsed))\n\t\tconst unknown = Object.keys(submitted).filter(key => !known.has(key))\n\t\tif (unknown.length > 0) {\n\t\t\tthrow new InvalidIDPConfigurationError(\n\t\t\t\t`unknown configuration ${unknown.length === 1 ? 'property' : 'properties'} ${unknown.join(', ')}; `\n\t\t\t\t\t+ `remove ${unknown.length === 1 ? 'it' : 'them'} or check the spelling — the provider would ignore ${unknown.length === 1 ? 'it' : 'them'}`,\n\t\t\t)\n\t\t}\n\t}\n\n\tpublic async listIDP(db: DatabaseContext): Promise<IdentityProviderDto[]> {\n\t\tconst result = await db.queryHandler.fetch(new IdentityProvidersQuery())\n\t\treturn result.map(it => {\n\t\t\tconst providerService = this.idpRegistry.getHandlerOrNull(it.type)\n\t\t\tconst configuration = providerService?.getPublicConfiguration\n\t\t\t\t? providerService.getPublicConfiguration(it.configuration)\n\t\t\t\t: it.configuration\n\n\t\t\treturn { ...it, configuration }\n\t\t})\n\t}\n}\n\nexport type RegisterIDPResponse = Response<null, AddIdpErrorCode>\nexport type DisableIDPResponse = Response<null, DisableIdpErrorCode>\nexport type EnableIDPResponse = Response<null, EnableIdpErrorCode>\nexport type UpdateIDPResponse = Response<null, UpdateIdpErrorCode>\n"],"names":["db","newConfiguration"],"mappings":"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"}
@@ -20,11 +20,16 @@ class DatabaseContext {
20
20
  get queryHandler() {
21
21
  return this.client.createQueryHandler();
22
22
  }
23
- async transaction(cb) {
24
- return await this.client.transaction(async (db) => {
23
+ async transaction(cb, options = {}) {
24
+ const run = async () => await this.client.transaction(async (db) => {
25
25
  await db.query(Database.Connection.REPEATABLE_READ);
26
26
  return await cb(new DatabaseContext(db, this.providers));
27
27
  });
28
+ const retry = options.retry;
29
+ if (!retry) {
30
+ return await run();
31
+ }
32
+ return await Database.retryTransaction(run, (message) => retry.logger.warn(message));
28
33
  }
29
34
  async scope(cb) {
30
35
  return await this.client.scope(async (db) => {
@@ -1 +1 @@
1
- {"version":3,"file":"DatabaseContext.cjs","sources":["../../../../../../packages/engine-tenant-api/src/model/utils/DatabaseContext.ts"],"sourcesContent":["import { Client, Connection } from '@contember/database'\nimport { Providers } from '../providers'\nimport { CommandBus } from '../commands'\nimport { BatchLoaderArgs, initBatchLoader, ItemLoader } from '../../utils/batchQuery'\n\nexport class DatabaseContext<Conn extends Connection.ConnectionLike = Connection.ConnectionLike> {\n\tprivate loaders = new Map<BatchLoaderArgs<any, any, any>, ItemLoader<any, any>>()\n\n\tconstructor(\n\t\tpublic readonly client: Client<Conn>,\n\t\tpublic readonly providers: Providers,\n\t) {\n\t}\n\n\tpublic get commandBus() {\n\t\treturn new CommandBus(this.client, this.providers)\n\t}\n\n\tpublic get queryHandler() {\n\t\treturn this.client.createQueryHandler()\n\t}\n\n\tpublic async transaction<T>(cb: (dbContext: DatabaseContext<Connection.TransactionLike>) => Promise<T>): Promise<T> {\n\t\treturn await this.client.transaction(async db => {\n\t\t\tawait db.query(Connection.REPEATABLE_READ)\n\t\t\treturn await cb(new DatabaseContext(db, this.providers))\n\t\t})\n\t}\n\n\tpublic async scope<T>(cb: (dbContext: DatabaseContext<Connection.AcquiredConnectionLike>) => Promise<T>): Promise<T> {\n\t\treturn await this.client.scope(async db => {\n\t\t\treturn await cb(new DatabaseContext(db, this.providers))\n\t\t})\n\t}\n\n\tpublic batchLoad<Arg, Result, Item>(loaderArgs: BatchLoaderArgs<Arg, Result, Item>, arg: Arg): Promise<Item> {\n\t\tconst existing = this.loaders.get(loaderArgs)\n\t\tif (existing) {\n\t\t\treturn existing(arg)\n\t\t}\n\t\tconst newLoader = initBatchLoader(loaderArgs, this)\n\t\tthis.loaders.set(loaderArgs, newLoader)\n\t\treturn newLoader(arg)\n\t}\n}\n"],"names":["CommandBus","Connection","initBatchLoader"],"mappings":";;;;;;;;;;AAKO,MAAM,gBAAoF;AAAA,EAGhG,YACiB,QACA,WACf;AAFe,SAAA,SAAA;AACA,SAAA,YAAA;AAJjB,kBAAA,MAAQ,+BAAc,IAAA,CAA0D;AAAA,EAMhF;AAAA,EAEA,IAAW,aAAa;AACvB,WAAO,IAAIA,WAAAA,WAAW,KAAK,QAAQ,KAAK,SAAS;AAAA,EAClD;AAAA,EAEA,IAAW,eAAe;AACzB,WAAO,KAAK,OAAO,mBAAA;AAAA,EACpB;AAAA,EAEA,MAAa,YAAe,IAAwF;AACnH,WAAO,MAAM,KAAK,OAAO,YAAY,OAAM,OAAM;AAChD,YAAM,GAAG,MAAMC,SAAAA,WAAW,eAAe;AACzC,aAAO,MAAM,GAAG,IAAI,gBAAgB,IAAI,KAAK,SAAS,CAAC;AAAA,IACxD,CAAC;AAAA,EACF;AAAA,EAEA,MAAa,MAAS,IAA+F;AACpH,WAAO,MAAM,KAAK,OAAO,MAAM,OAAM,OAAM;AAC1C,aAAO,MAAM,GAAG,IAAI,gBAAgB,IAAI,KAAK,SAAS,CAAC;AAAA,IACxD,CAAC;AAAA,EACF;AAAA,EAEO,UAA6B,YAAgD,KAAyB;AAC5G,UAAM,WAAW,KAAK,QAAQ,IAAI,UAAU;AAC5C,QAAI,UAAU;AACb,aAAO,SAAS,GAAG;AAAA,IACpB;AACA,UAAM,YAAYC,WAAAA,gBAAgB,YAAY,IAAI;AAClD,SAAK,QAAQ,IAAI,YAAY,SAAS;AACtC,WAAO,UAAU,GAAG;AAAA,EACrB;AACD;;"}
1
+ {"version":3,"file":"DatabaseContext.cjs","sources":["../../../../../../packages/engine-tenant-api/src/model/utils/DatabaseContext.ts"],"sourcesContent":["import { Client, Connection, retryTransaction } from '@contember/database'\nimport { Logger } from '@contember/logger'\nimport { Providers } from '../providers'\nimport { CommandBus } from '../commands'\nimport { BatchLoaderArgs, initBatchLoader, ItemLoader } from '../../utils/batchQuery'\n\nexport interface TransactionOptions {\n\t/**\n\t * Repeat the transaction when postgres aborts it with a serialization failure (40001) — the standing cost of the\n\t * repeatable-read level every transaction here runs at.\n\t *\n\t * Opt in only when the callback has no effect outside the transaction: several managers send a mail from inside\n\t * one (InviteManager, EmailChangeManager, PasswordlessSignInManager) and a retry would send it twice.\n\t */\n\tretry?: { logger: Logger }\n}\n\nexport class DatabaseContext<Conn extends Connection.ConnectionLike = Connection.ConnectionLike> {\n\tprivate loaders = new Map<BatchLoaderArgs<any, any, any>, ItemLoader<any, any>>()\n\n\tconstructor(\n\t\tpublic readonly client: Client<Conn>,\n\t\tpublic readonly providers: Providers,\n\t) {\n\t}\n\n\tpublic get commandBus() {\n\t\treturn new CommandBus(this.client, this.providers)\n\t}\n\n\tpublic get queryHandler() {\n\t\treturn this.client.createQueryHandler()\n\t}\n\n\tpublic async transaction<T>(\n\t\tcb: (dbContext: DatabaseContext<Connection.TransactionLike>) => Promise<T>,\n\t\toptions: TransactionOptions = {},\n\t): Promise<T> {\n\t\tconst run = async () =>\n\t\t\tawait this.client.transaction(async db => {\n\t\t\t\tawait db.query(Connection.REPEATABLE_READ)\n\t\t\t\treturn await cb(new DatabaseContext(db, this.providers))\n\t\t\t})\n\t\tconst retry = options.retry\n\t\tif (!retry) {\n\t\t\treturn await run()\n\t\t}\n\t\treturn await retryTransaction(run, message => retry.logger.warn(message))\n\t}\n\n\tpublic async scope<T>(cb: (dbContext: DatabaseContext<Connection.AcquiredConnectionLike>) => Promise<T>): Promise<T> {\n\t\treturn await this.client.scope(async db => {\n\t\t\treturn await cb(new DatabaseContext(db, this.providers))\n\t\t})\n\t}\n\n\tpublic batchLoad<Arg, Result, Item>(loaderArgs: BatchLoaderArgs<Arg, Result, Item>, arg: Arg): Promise<Item> {\n\t\tconst existing = this.loaders.get(loaderArgs)\n\t\tif (existing) {\n\t\t\treturn existing(arg)\n\t\t}\n\t\tconst newLoader = initBatchLoader(loaderArgs, this)\n\t\tthis.loaders.set(loaderArgs, newLoader)\n\t\treturn newLoader(arg)\n\t}\n}\n"],"names":["CommandBus","Connection","retryTransaction","initBatchLoader"],"mappings":";;;;;;;;;;AAiBO,MAAM,gBAAoF;AAAA,EAGhG,YACiB,QACA,WACf;AAFe,SAAA,SAAA;AACA,SAAA,YAAA;AAJjB,kBAAA,MAAQ,+BAAc,IAAA,CAA0D;AAAA,EAMhF;AAAA,EAEA,IAAW,aAAa;AACvB,WAAO,IAAIA,WAAAA,WAAW,KAAK,QAAQ,KAAK,SAAS;AAAA,EAClD;AAAA,EAEA,IAAW,eAAe;AACzB,WAAO,KAAK,OAAO,mBAAA;AAAA,EACpB;AAAA,EAEA,MAAa,YACZ,IACA,UAA8B,IACjB;AACb,UAAM,MAAM,YACX,MAAM,KAAK,OAAO,YAAY,OAAM,OAAM;AACzC,YAAM,GAAG,MAAMC,SAAAA,WAAW,eAAe;AACzC,aAAO,MAAM,GAAG,IAAI,gBAAgB,IAAI,KAAK,SAAS,CAAC;AAAA,IACxD,CAAC;AACF,UAAM,QAAQ,QAAQ;AACtB,QAAI,CAAC,OAAO;AACX,aAAO,MAAM,IAAA;AAAA,IACd;AACA,WAAO,MAAMC,SAAAA,iBAAiB,KAAK,CAAA,YAAW,MAAM,OAAO,KAAK,OAAO,CAAC;AAAA,EACzE;AAAA,EAEA,MAAa,MAAS,IAA+F;AACpH,WAAO,MAAM,KAAK,OAAO,MAAM,OAAM,OAAM;AAC1C,aAAO,MAAM,GAAG,IAAI,gBAAgB,IAAI,KAAK,SAAS,CAAC;AAAA,IACxD,CAAC;AAAA,EACF;AAAA,EAEO,UAA6B,YAAgD,KAAyB;AAC5G,UAAM,WAAW,KAAK,QAAQ,IAAI,UAAU;AAC5C,QAAI,UAAU;AACb,aAAO,SAAS,GAAG;AAAA,IACpB;AACA,UAAM,YAAYC,WAAAA,gBAAgB,YAAY,IAAI;AAClD,SAAK,QAAQ,IAAI,YAAY,SAAS;AACtC,WAAO,UAAU,GAAG;AAAA,EACrB;AACD;;"}
@@ -1,4 +1,4 @@
1
- import { Connection } from "@contember/database";
1
+ import { retryTransaction, Connection } from "@contember/database";
2
2
  import "../type/ApiKey.js";
3
3
  import { initBatchLoader } from "../../utils/batchQuery.js";
4
4
  import "node:crypto";
@@ -18,11 +18,16 @@ class DatabaseContext {
18
18
  get queryHandler() {
19
19
  return this.client.createQueryHandler();
20
20
  }
21
- async transaction(cb) {
22
- return await this.client.transaction(async (db) => {
21
+ async transaction(cb, options = {}) {
22
+ const run = async () => await this.client.transaction(async (db) => {
23
23
  await db.query(Connection.REPEATABLE_READ);
24
24
  return await cb(new DatabaseContext(db, this.providers));
25
25
  });
26
+ const retry = options.retry;
27
+ if (!retry) {
28
+ return await run();
29
+ }
30
+ return await retryTransaction(run, (message) => retry.logger.warn(message));
26
31
  }
27
32
  async scope(cb) {
28
33
  return await this.client.scope(async (db) => {
@@ -1 +1 @@
1
- {"version":3,"file":"DatabaseContext.js","sources":["../../../../../../packages/engine-tenant-api/src/model/utils/DatabaseContext.ts"],"sourcesContent":["import { Client, Connection } from '@contember/database'\nimport { Providers } from '../providers'\nimport { CommandBus } from '../commands'\nimport { BatchLoaderArgs, initBatchLoader, ItemLoader } from '../../utils/batchQuery'\n\nexport class DatabaseContext<Conn extends Connection.ConnectionLike = Connection.ConnectionLike> {\n\tprivate loaders = new Map<BatchLoaderArgs<any, any, any>, ItemLoader<any, any>>()\n\n\tconstructor(\n\t\tpublic readonly client: Client<Conn>,\n\t\tpublic readonly providers: Providers,\n\t) {\n\t}\n\n\tpublic get commandBus() {\n\t\treturn new CommandBus(this.client, this.providers)\n\t}\n\n\tpublic get queryHandler() {\n\t\treturn this.client.createQueryHandler()\n\t}\n\n\tpublic async transaction<T>(cb: (dbContext: DatabaseContext<Connection.TransactionLike>) => Promise<T>): Promise<T> {\n\t\treturn await this.client.transaction(async db => {\n\t\t\tawait db.query(Connection.REPEATABLE_READ)\n\t\t\treturn await cb(new DatabaseContext(db, this.providers))\n\t\t})\n\t}\n\n\tpublic async scope<T>(cb: (dbContext: DatabaseContext<Connection.AcquiredConnectionLike>) => Promise<T>): Promise<T> {\n\t\treturn await this.client.scope(async db => {\n\t\t\treturn await cb(new DatabaseContext(db, this.providers))\n\t\t})\n\t}\n\n\tpublic batchLoad<Arg, Result, Item>(loaderArgs: BatchLoaderArgs<Arg, Result, Item>, arg: Arg): Promise<Item> {\n\t\tconst existing = this.loaders.get(loaderArgs)\n\t\tif (existing) {\n\t\t\treturn existing(arg)\n\t\t}\n\t\tconst newLoader = initBatchLoader(loaderArgs, this)\n\t\tthis.loaders.set(loaderArgs, newLoader)\n\t\treturn newLoader(arg)\n\t}\n}\n"],"names":[],"mappings":";;;;;;;;AAKO,MAAM,gBAAoF;AAAA,EAGhG,YACiB,QACA,WACf;AAFe,SAAA,SAAA;AACA,SAAA,YAAA;AAJjB,kBAAA,MAAQ,+BAAc,IAAA,CAA0D;AAAA,EAMhF;AAAA,EAEA,IAAW,aAAa;AACvB,WAAO,IAAI,WAAW,KAAK,QAAQ,KAAK,SAAS;AAAA,EAClD;AAAA,EAEA,IAAW,eAAe;AACzB,WAAO,KAAK,OAAO,mBAAA;AAAA,EACpB;AAAA,EAEA,MAAa,YAAe,IAAwF;AACnH,WAAO,MAAM,KAAK,OAAO,YAAY,OAAM,OAAM;AAChD,YAAM,GAAG,MAAM,WAAW,eAAe;AACzC,aAAO,MAAM,GAAG,IAAI,gBAAgB,IAAI,KAAK,SAAS,CAAC;AAAA,IACxD,CAAC;AAAA,EACF;AAAA,EAEA,MAAa,MAAS,IAA+F;AACpH,WAAO,MAAM,KAAK,OAAO,MAAM,OAAM,OAAM;AAC1C,aAAO,MAAM,GAAG,IAAI,gBAAgB,IAAI,KAAK,SAAS,CAAC;AAAA,IACxD,CAAC;AAAA,EACF;AAAA,EAEO,UAA6B,YAAgD,KAAyB;AAC5G,UAAM,WAAW,KAAK,QAAQ,IAAI,UAAU;AAC5C,QAAI,UAAU;AACb,aAAO,SAAS,GAAG;AAAA,IACpB;AACA,UAAM,YAAY,gBAAgB,YAAY,IAAI;AAClD,SAAK,QAAQ,IAAI,YAAY,SAAS;AACtC,WAAO,UAAU,GAAG;AAAA,EACrB;AACD;"}
1
+ {"version":3,"file":"DatabaseContext.js","sources":["../../../../../../packages/engine-tenant-api/src/model/utils/DatabaseContext.ts"],"sourcesContent":["import { Client, Connection, retryTransaction } from '@contember/database'\nimport { Logger } from '@contember/logger'\nimport { Providers } from '../providers'\nimport { CommandBus } from '../commands'\nimport { BatchLoaderArgs, initBatchLoader, ItemLoader } from '../../utils/batchQuery'\n\nexport interface TransactionOptions {\n\t/**\n\t * Repeat the transaction when postgres aborts it with a serialization failure (40001) — the standing cost of the\n\t * repeatable-read level every transaction here runs at.\n\t *\n\t * Opt in only when the callback has no effect outside the transaction: several managers send a mail from inside\n\t * one (InviteManager, EmailChangeManager, PasswordlessSignInManager) and a retry would send it twice.\n\t */\n\tretry?: { logger: Logger }\n}\n\nexport class DatabaseContext<Conn extends Connection.ConnectionLike = Connection.ConnectionLike> {\n\tprivate loaders = new Map<BatchLoaderArgs<any, any, any>, ItemLoader<any, any>>()\n\n\tconstructor(\n\t\tpublic readonly client: Client<Conn>,\n\t\tpublic readonly providers: Providers,\n\t) {\n\t}\n\n\tpublic get commandBus() {\n\t\treturn new CommandBus(this.client, this.providers)\n\t}\n\n\tpublic get queryHandler() {\n\t\treturn this.client.createQueryHandler()\n\t}\n\n\tpublic async transaction<T>(\n\t\tcb: (dbContext: DatabaseContext<Connection.TransactionLike>) => Promise<T>,\n\t\toptions: TransactionOptions = {},\n\t): Promise<T> {\n\t\tconst run = async () =>\n\t\t\tawait this.client.transaction(async db => {\n\t\t\t\tawait db.query(Connection.REPEATABLE_READ)\n\t\t\t\treturn await cb(new DatabaseContext(db, this.providers))\n\t\t\t})\n\t\tconst retry = options.retry\n\t\tif (!retry) {\n\t\t\treturn await run()\n\t\t}\n\t\treturn await retryTransaction(run, message => retry.logger.warn(message))\n\t}\n\n\tpublic async scope<T>(cb: (dbContext: DatabaseContext<Connection.AcquiredConnectionLike>) => Promise<T>): Promise<T> {\n\t\treturn await this.client.scope(async db => {\n\t\t\treturn await cb(new DatabaseContext(db, this.providers))\n\t\t})\n\t}\n\n\tpublic batchLoad<Arg, Result, Item>(loaderArgs: BatchLoaderArgs<Arg, Result, Item>, arg: Arg): Promise<Item> {\n\t\tconst existing = this.loaders.get(loaderArgs)\n\t\tif (existing) {\n\t\t\treturn existing(arg)\n\t\t}\n\t\tconst newLoader = initBatchLoader(loaderArgs, this)\n\t\tthis.loaders.set(loaderArgs, newLoader)\n\t\treturn newLoader(arg)\n\t}\n}\n"],"names":[],"mappings":";;;;;;;;AAiBO,MAAM,gBAAoF;AAAA,EAGhG,YACiB,QACA,WACf;AAFe,SAAA,SAAA;AACA,SAAA,YAAA;AAJjB,kBAAA,MAAQ,+BAAc,IAAA,CAA0D;AAAA,EAMhF;AAAA,EAEA,IAAW,aAAa;AACvB,WAAO,IAAI,WAAW,KAAK,QAAQ,KAAK,SAAS;AAAA,EAClD;AAAA,EAEA,IAAW,eAAe;AACzB,WAAO,KAAK,OAAO,mBAAA;AAAA,EACpB;AAAA,EAEA,MAAa,YACZ,IACA,UAA8B,IACjB;AACb,UAAM,MAAM,YACX,MAAM,KAAK,OAAO,YAAY,OAAM,OAAM;AACzC,YAAM,GAAG,MAAM,WAAW,eAAe;AACzC,aAAO,MAAM,GAAG,IAAI,gBAAgB,IAAI,KAAK,SAAS,CAAC;AAAA,IACxD,CAAC;AACF,UAAM,QAAQ,QAAQ;AACtB,QAAI,CAAC,OAAO;AACX,aAAO,MAAM,IAAA;AAAA,IACd;AACA,WAAO,MAAM,iBAAiB,KAAK,CAAA,YAAW,MAAM,OAAO,KAAK,OAAO,CAAC;AAAA,EACzE;AAAA,EAEA,MAAa,MAAS,IAA+F;AACpH,WAAO,MAAM,KAAK,OAAO,MAAM,OAAM,OAAM;AAC1C,aAAO,MAAM,GAAG,IAAI,gBAAgB,IAAI,KAAK,SAAS,CAAC;AAAA,IACxD,CAAC;AAAA,EACF;AAAA,EAEO,UAA6B,YAAgD,KAAyB;AAC5G,UAAM,WAAW,KAAK,QAAQ,IAAI,UAAU;AAC5C,QAAI,UAAU;AACb,aAAO,SAAS,GAAG;AAAA,IACpB;AACA,UAAM,YAAY,gBAAgB,YAAY,IAAI;AAClD,SAAK,QAAQ,IAAI,YAAY,SAAS;AACtC,WAAO,UAAU,GAAG;AAAA,EACrB;AACD;"}
@@ -39,7 +39,7 @@ class DisablePersonMutationResolver {
39
39
  action: PermissionActions.PermissionActions.PERSON_DISABLE(targetPerson.roles),
40
40
  message: "You are not allowed to disable person account"
41
41
  });
42
- const result = await this.personAccessManager.disablePerson(context.db, targetPerson);
42
+ const result = await this.personAccessManager.disablePerson(context.db, targetPerson, context.logger);
43
43
  await context.logAuthAction({
44
44
  type: "person_disable",
45
45
  response: result,
@@ -1 +1 @@
1
- {"version":3,"file":"DisablePersonMutationResolver.cjs","sources":["../../../../../../../packages/engine-tenant-api/src/resolvers/mutation/person/DisablePersonMutationResolver.ts"],"sourcesContent":["import { DisablePersonResponse, MutationDisablePersonArgs, MutationResolvers } from '../../../schema'\nimport { TenantResolverContext } from '../../TenantResolverContext'\nimport { PermissionActions, PersonAccessManager } from '../../../model'\nimport { PersonManager } from '../../../model/service/PersonManager'\nimport { createErrorResponse } from '../../errorUtils'\n\nexport class DisablePersonMutationResolver implements MutationResolvers {\n\tconstructor(\n\t\tprivate readonly personAccessManager: PersonAccessManager,\n\t\tprivate readonly personManager: PersonManager,\n\t) {}\n\n\tasync disablePerson(\n\t\tparent: any,\n\t\targs: MutationDisablePersonArgs,\n\t\tcontext: TenantResolverContext,\n\t): Promise<DisablePersonResponse> {\n\t\tconst targetPerson = await this.personManager.findPersonById(context.db, args.personId)\n\n\t\tif (targetPerson === null) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: 'PERSON_NOT_FOUND',\n\t\t\t\t\tdeveloperMessage: `Person <${args.personId}> was not found`,\n\t\t\t\t},\n\t\t\t}\n\t\t}\n\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_DISABLE(targetPerson.roles),\n\t\t\tmessage: 'You are not allowed to disable person account',\n\t\t})\n\n\t\tconst result = await this.personAccessManager.disablePerson(context.db, targetPerson)\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'person_disable',\n\t\t\tresponse: result,\n\t\t\tpersonId: targetPerson.id,\n\t\t})\n\t\tif (!result.ok) {\n\t\t\treturn createErrorResponse(result.error, result.errorMessage)\n\t\t}\n\n\t\treturn {\n\t\t\tok: true,\n\t\t}\n\t}\n}\n"],"names":["PermissionActions","createErrorResponse"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAMO,MAAM,8BAA2D;AAAA,EACvE,YACkB,qBACA,eAChB;AAFgB,SAAA,sBAAA;AACA,SAAA,gBAAA;AAAA,EACf;AAAA,EAEH,MAAM,cACL,QACA,MACA,SACiC;AACjC,UAAM,eAAe,MAAM,KAAK,cAAc,eAAe,QAAQ,IAAI,KAAK,QAAQ;AAEtF,QAAI,iBAAiB,MAAM;AAC1B,aAAO;AAAA,QACN,IAAI;AAAA,QACJ,OAAO;AAAA,UACN,MAAM;AAAA,UACN,kBAAkB,WAAW,KAAK,QAAQ;AAAA,QAAA;AAAA,MAC3C;AAAA,IAEF;AAEA,UAAM,QAAQ,cAAc;AAAA,MAC3B,QAAQA,kBAAAA,kBAAkB,eAAe,aAAa,KAAK;AAAA,MAC3D,SAAS;AAAA,IAAA,CACT;AAED,UAAM,SAAS,MAAM,KAAK,oBAAoB,cAAc,QAAQ,IAAI,YAAY;AACpF,UAAM,QAAQ,cAAc;AAAA,MAC3B,MAAM;AAAA,MACN,UAAU;AAAA,MACV,UAAU,aAAa;AAAA,IAAA,CACvB;AACD,QAAI,CAAC,OAAO,IAAI;AACf,aAAOC,WAAAA,oBAAoB,OAAO,OAAO,OAAO,YAAY;AAAA,IAC7D;AAEA,WAAO;AAAA,MACN,IAAI;AAAA,IAAA;AAAA,EAEN;AACD;;"}
1
+ {"version":3,"file":"DisablePersonMutationResolver.cjs","sources":["../../../../../../../packages/engine-tenant-api/src/resolvers/mutation/person/DisablePersonMutationResolver.ts"],"sourcesContent":["import { DisablePersonResponse, MutationDisablePersonArgs, MutationResolvers } from '../../../schema'\nimport { TenantResolverContext } from '../../TenantResolverContext'\nimport { PermissionActions, PersonAccessManager } from '../../../model'\nimport { PersonManager } from '../../../model/service/PersonManager'\nimport { createErrorResponse } from '../../errorUtils'\n\nexport class DisablePersonMutationResolver implements MutationResolvers {\n\tconstructor(\n\t\tprivate readonly personAccessManager: PersonAccessManager,\n\t\tprivate readonly personManager: PersonManager,\n\t) {}\n\n\tasync disablePerson(\n\t\tparent: any,\n\t\targs: MutationDisablePersonArgs,\n\t\tcontext: TenantResolverContext,\n\t): Promise<DisablePersonResponse> {\n\t\tconst targetPerson = await this.personManager.findPersonById(context.db, args.personId)\n\n\t\tif (targetPerson === null) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: 'PERSON_NOT_FOUND',\n\t\t\t\t\tdeveloperMessage: `Person <${args.personId}> was not found`,\n\t\t\t\t},\n\t\t\t}\n\t\t}\n\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_DISABLE(targetPerson.roles),\n\t\t\tmessage: 'You are not allowed to disable person account',\n\t\t})\n\n\t\tconst result = await this.personAccessManager.disablePerson(context.db, targetPerson, context.logger)\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'person_disable',\n\t\t\tresponse: result,\n\t\t\tpersonId: targetPerson.id,\n\t\t})\n\t\tif (!result.ok) {\n\t\t\treturn createErrorResponse(result.error, result.errorMessage)\n\t\t}\n\n\t\treturn {\n\t\t\tok: true,\n\t\t}\n\t}\n}\n"],"names":["PermissionActions","createErrorResponse"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAMO,MAAM,8BAA2D;AAAA,EACvE,YACkB,qBACA,eAChB;AAFgB,SAAA,sBAAA;AACA,SAAA,gBAAA;AAAA,EACf;AAAA,EAEH,MAAM,cACL,QACA,MACA,SACiC;AACjC,UAAM,eAAe,MAAM,KAAK,cAAc,eAAe,QAAQ,IAAI,KAAK,QAAQ;AAEtF,QAAI,iBAAiB,MAAM;AAC1B,aAAO;AAAA,QACN,IAAI;AAAA,QACJ,OAAO;AAAA,UACN,MAAM;AAAA,UACN,kBAAkB,WAAW,KAAK,QAAQ;AAAA,QAAA;AAAA,MAC3C;AAAA,IAEF;AAEA,UAAM,QAAQ,cAAc;AAAA,MAC3B,QAAQA,kBAAAA,kBAAkB,eAAe,aAAa,KAAK;AAAA,MAC3D,SAAS;AAAA,IAAA,CACT;AAED,UAAM,SAAS,MAAM,KAAK,oBAAoB,cAAc,QAAQ,IAAI,cAAc,QAAQ,MAAM;AACpG,UAAM,QAAQ,cAAc;AAAA,MAC3B,MAAM;AAAA,MACN,UAAU;AAAA,MACV,UAAU,aAAa;AAAA,IAAA,CACvB;AACD,QAAI,CAAC,OAAO,IAAI;AACf,aAAOC,WAAAA,oBAAoB,OAAO,OAAO,OAAO,YAAY;AAAA,IAC7D;AAEA,WAAO;AAAA,MACN,IAAI;AAAA,IAAA;AAAA,EAEN;AACD;;"}
@@ -37,7 +37,7 @@ class DisablePersonMutationResolver {
37
37
  action: PermissionActions.PERSON_DISABLE(targetPerson.roles),
38
38
  message: "You are not allowed to disable person account"
39
39
  });
40
- const result = await this.personAccessManager.disablePerson(context.db, targetPerson);
40
+ const result = await this.personAccessManager.disablePerson(context.db, targetPerson, context.logger);
41
41
  await context.logAuthAction({
42
42
  type: "person_disable",
43
43
  response: result,
@@ -1 +1 @@
1
- {"version":3,"file":"DisablePersonMutationResolver.js","sources":["../../../../../../../packages/engine-tenant-api/src/resolvers/mutation/person/DisablePersonMutationResolver.ts"],"sourcesContent":["import { DisablePersonResponse, MutationDisablePersonArgs, MutationResolvers } from '../../../schema'\nimport { TenantResolverContext } from '../../TenantResolverContext'\nimport { PermissionActions, PersonAccessManager } from '../../../model'\nimport { PersonManager } from '../../../model/service/PersonManager'\nimport { createErrorResponse } from '../../errorUtils'\n\nexport class DisablePersonMutationResolver implements MutationResolvers {\n\tconstructor(\n\t\tprivate readonly personAccessManager: PersonAccessManager,\n\t\tprivate readonly personManager: PersonManager,\n\t) {}\n\n\tasync disablePerson(\n\t\tparent: any,\n\t\targs: MutationDisablePersonArgs,\n\t\tcontext: TenantResolverContext,\n\t): Promise<DisablePersonResponse> {\n\t\tconst targetPerson = await this.personManager.findPersonById(context.db, args.personId)\n\n\t\tif (targetPerson === null) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: 'PERSON_NOT_FOUND',\n\t\t\t\t\tdeveloperMessage: `Person <${args.personId}> was not found`,\n\t\t\t\t},\n\t\t\t}\n\t\t}\n\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_DISABLE(targetPerson.roles),\n\t\t\tmessage: 'You are not allowed to disable person account',\n\t\t})\n\n\t\tconst result = await this.personAccessManager.disablePerson(context.db, targetPerson)\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'person_disable',\n\t\t\tresponse: result,\n\t\t\tpersonId: targetPerson.id,\n\t\t})\n\t\tif (!result.ok) {\n\t\t\treturn createErrorResponse(result.error, result.errorMessage)\n\t\t}\n\n\t\treturn {\n\t\t\tok: true,\n\t\t}\n\t}\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;AAMO,MAAM,8BAA2D;AAAA,EACvE,YACkB,qBACA,eAChB;AAFgB,SAAA,sBAAA;AACA,SAAA,gBAAA;AAAA,EACf;AAAA,EAEH,MAAM,cACL,QACA,MACA,SACiC;AACjC,UAAM,eAAe,MAAM,KAAK,cAAc,eAAe,QAAQ,IAAI,KAAK,QAAQ;AAEtF,QAAI,iBAAiB,MAAM;AAC1B,aAAO;AAAA,QACN,IAAI;AAAA,QACJ,OAAO;AAAA,UACN,MAAM;AAAA,UACN,kBAAkB,WAAW,KAAK,QAAQ;AAAA,QAAA;AAAA,MAC3C;AAAA,IAEF;AAEA,UAAM,QAAQ,cAAc;AAAA,MAC3B,QAAQ,kBAAkB,eAAe,aAAa,KAAK;AAAA,MAC3D,SAAS;AAAA,IAAA,CACT;AAED,UAAM,SAAS,MAAM,KAAK,oBAAoB,cAAc,QAAQ,IAAI,YAAY;AACpF,UAAM,QAAQ,cAAc;AAAA,MAC3B,MAAM;AAAA,MACN,UAAU;AAAA,MACV,UAAU,aAAa;AAAA,IAAA,CACvB;AACD,QAAI,CAAC,OAAO,IAAI;AACf,aAAO,oBAAoB,OAAO,OAAO,OAAO,YAAY;AAAA,IAC7D;AAEA,WAAO;AAAA,MACN,IAAI;AAAA,IAAA;AAAA,EAEN;AACD;"}
1
+ {"version":3,"file":"DisablePersonMutationResolver.js","sources":["../../../../../../../packages/engine-tenant-api/src/resolvers/mutation/person/DisablePersonMutationResolver.ts"],"sourcesContent":["import { DisablePersonResponse, MutationDisablePersonArgs, MutationResolvers } from '../../../schema'\nimport { TenantResolverContext } from '../../TenantResolverContext'\nimport { PermissionActions, PersonAccessManager } from '../../../model'\nimport { PersonManager } from '../../../model/service/PersonManager'\nimport { createErrorResponse } from '../../errorUtils'\n\nexport class DisablePersonMutationResolver implements MutationResolvers {\n\tconstructor(\n\t\tprivate readonly personAccessManager: PersonAccessManager,\n\t\tprivate readonly personManager: PersonManager,\n\t) {}\n\n\tasync disablePerson(\n\t\tparent: any,\n\t\targs: MutationDisablePersonArgs,\n\t\tcontext: TenantResolverContext,\n\t): Promise<DisablePersonResponse> {\n\t\tconst targetPerson = await this.personManager.findPersonById(context.db, args.personId)\n\n\t\tif (targetPerson === null) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: 'PERSON_NOT_FOUND',\n\t\t\t\t\tdeveloperMessage: `Person <${args.personId}> was not found`,\n\t\t\t\t},\n\t\t\t}\n\t\t}\n\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_DISABLE(targetPerson.roles),\n\t\t\tmessage: 'You are not allowed to disable person account',\n\t\t})\n\n\t\tconst result = await this.personAccessManager.disablePerson(context.db, targetPerson, context.logger)\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'person_disable',\n\t\t\tresponse: result,\n\t\t\tpersonId: targetPerson.id,\n\t\t})\n\t\tif (!result.ok) {\n\t\t\treturn createErrorResponse(result.error, result.errorMessage)\n\t\t}\n\n\t\treturn {\n\t\t\tok: true,\n\t\t}\n\t}\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;AAMO,MAAM,8BAA2D;AAAA,EACvE,YACkB,qBACA,eAChB;AAFgB,SAAA,sBAAA;AACA,SAAA,gBAAA;AAAA,EACf;AAAA,EAEH,MAAM,cACL,QACA,MACA,SACiC;AACjC,UAAM,eAAe,MAAM,KAAK,cAAc,eAAe,QAAQ,IAAI,KAAK,QAAQ;AAEtF,QAAI,iBAAiB,MAAM;AAC1B,aAAO;AAAA,QACN,IAAI;AAAA,QACJ,OAAO;AAAA,UACN,MAAM;AAAA,UACN,kBAAkB,WAAW,KAAK,QAAQ;AAAA,QAAA;AAAA,MAC3C;AAAA,IAEF;AAEA,UAAM,QAAQ,cAAc;AAAA,MAC3B,QAAQ,kBAAkB,eAAe,aAAa,KAAK;AAAA,MAC3D,SAAS;AAAA,IAAA,CACT;AAED,UAAM,SAAS,MAAM,KAAK,oBAAoB,cAAc,QAAQ,IAAI,cAAc,QAAQ,MAAM;AACpG,UAAM,QAAQ,cAAc;AAAA,MAC3B,MAAM;AAAA,MACN,UAAU;AAAA,MACV,UAAU,aAAa;AAAA,IAAA,CACvB;AACD,QAAI,CAAC,OAAO,IAAI;AACf,aAAO,oBAAoB,OAAO,OAAO,OAAO,YAAY;AAAA,IAC7D;AAEA,WAAO;AAAA,MACN,IAAI;AAAA,IAAA;AAAA,EAEN;AACD;"}