@10x-media/impersonation 0.1.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/LICENSE +21 -0
  3. package/README.md +49 -0
  4. package/dist/access/filterTargets.js +51 -0
  5. package/dist/access/filterTargets.js.map +1 -0
  6. package/dist/auth/cookies.js +49 -0
  7. package/dist/auth/cookies.js.map +1 -0
  8. package/dist/auth/expiryCookies.js +59 -0
  9. package/dist/auth/expiryCookies.js.map +1 -0
  10. package/dist/auth/issue.js +126 -0
  11. package/dist/auth/issue.js.map +1 -0
  12. package/dist/auth/mode.js +69 -0
  13. package/dist/auth/mode.js.map +1 -0
  14. package/dist/auth/origin.js +55 -0
  15. package/dist/auth/origin.js.map +1 -0
  16. package/dist/auth/revoke.js +24 -0
  17. package/dist/auth/revoke.js.map +1 -0
  18. package/dist/auth/tenantCookie.js +74 -0
  19. package/dist/auth/tenantCookie.js.map +1 -0
  20. package/dist/endpoints/codes.d.ts +6 -0
  21. package/dist/endpoints/current.js +66 -0
  22. package/dist/endpoints/current.js.map +1 -0
  23. package/dist/endpoints/exit.js +242 -0
  24. package/dist/endpoints/exit.js.map +1 -0
  25. package/dist/endpoints/shared.js +76 -0
  26. package/dist/endpoints/shared.js.map +1 -0
  27. package/dist/endpoints/start.js +332 -0
  28. package/dist/endpoints/start.js.map +1 -0
  29. package/dist/endpoints/terminate.js +73 -0
  30. package/dist/endpoints/terminate.js.map +1 -0
  31. package/dist/exports/client.d.ts +9 -0
  32. package/dist/exports/client.js +10 -0
  33. package/dist/exports/i18n.d.ts +3 -0
  34. package/dist/exports/i18n.js +3 -0
  35. package/dist/exports/rsc.d.ts +4 -0
  36. package/dist/exports/rsc.js +4 -0
  37. package/dist/exports/types.d.ts +3 -0
  38. package/dist/exports/types.js +1 -0
  39. package/dist/getImpersonation.d.ts +49 -0
  40. package/dist/getImpersonation.js +78 -0
  41. package/dist/getImpersonation.js.map +1 -0
  42. package/dist/ids.js +27 -0
  43. package/dist/ids.js.map +1 -0
  44. package/dist/index.d.ts +20 -0
  45. package/dist/index.js +55 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/plugin/assertTargets.js +23 -0
  48. package/dist/plugin/assertTargets.js.map +1 -0
  49. package/dist/plugin/constants.d.ts +6 -0
  50. package/dist/plugin/constants.js +20 -0
  51. package/dist/plugin/constants.js.map +1 -0
  52. package/dist/plugin/decorateAuth.js +99 -0
  53. package/dist/plugin/decorateAuth.js.map +1 -0
  54. package/dist/plugin/lookup.js +23 -0
  55. package/dist/plugin/lookup.js.map +1 -0
  56. package/dist/plugin/normalizeOptions.js +83 -0
  57. package/dist/plugin/normalizeOptions.js.map +1 -0
  58. package/dist/plugin/registerAdmin.js +49 -0
  59. package/dist/plugin/registerAdmin.js.map +1 -0
  60. package/dist/plugin/registerCollection.js +183 -0
  61. package/dist/plugin/registerCollection.js.map +1 -0
  62. package/dist/plugin/registerEndpoints.js +19 -0
  63. package/dist/plugin/registerEndpoints.js.map +1 -0
  64. package/dist/plugin/registerHooks.js +59 -0
  65. package/dist/plugin/registerHooks.js.map +1 -0
  66. package/dist/plugin/registerRetention.js +57 -0
  67. package/dist/plugin/registerRetention.js.map +1 -0
  68. package/dist/plugin/registerTranslations.js +19 -0
  69. package/dist/plugin/registerTranslations.js.map +1 -0
  70. package/dist/plugin/registry.js +11 -0
  71. package/dist/plugin/registry.js.map +1 -0
  72. package/dist/plugin/startable.js +21 -0
  73. package/dist/plugin/startable.js.map +1 -0
  74. package/dist/session/close.js +65 -0
  75. package/dist/session/close.js.map +1 -0
  76. package/dist/session/closeStale.d.ts +11 -0
  77. package/dist/session/closeStale.js +65 -0
  78. package/dist/session/closeStale.js.map +1 -0
  79. package/dist/session/locks.js +32 -0
  80. package/dist/session/locks.js.map +1 -0
  81. package/dist/session/resolve.js +91 -0
  82. package/dist/session/resolve.js.map +1 -0
  83. package/dist/translations/ar.js +52 -0
  84. package/dist/translations/ar.js.map +1 -0
  85. package/dist/translations/de.js +52 -0
  86. package/dist/translations/de.js.map +1 -0
  87. package/dist/translations/en.js +52 -0
  88. package/dist/translations/en.js.map +1 -0
  89. package/dist/translations/es.js +52 -0
  90. package/dist/translations/es.js.map +1 -0
  91. package/dist/translations/fr.js +52 -0
  92. package/dist/translations/fr.js.map +1 -0
  93. package/dist/translations/id.js +52 -0
  94. package/dist/translations/id.js.map +1 -0
  95. package/dist/translations/index.d.ts +24 -0
  96. package/dist/translations/index.js +49 -0
  97. package/dist/translations/index.js.map +1 -0
  98. package/dist/translations/keys.d.ts +56 -0
  99. package/dist/translations/keys.js +56 -0
  100. package/dist/translations/keys.js.map +1 -0
  101. package/dist/translations/ko.js +52 -0
  102. package/dist/translations/ko.js.map +1 -0
  103. package/dist/translations/lookup.js +17 -0
  104. package/dist/translations/lookup.js.map +1 -0
  105. package/dist/translations/pt.js +52 -0
  106. package/dist/translations/pt.js.map +1 -0
  107. package/dist/translations/ru.js +52 -0
  108. package/dist/translations/ru.js.map +1 -0
  109. package/dist/translations/server.js +13 -0
  110. package/dist/translations/server.js.map +1 -0
  111. package/dist/translations/uk.js +52 -0
  112. package/dist/translations/uk.js.map +1 -0
  113. package/dist/translations/useTranslation.js +12 -0
  114. package/dist/translations/useTranslation.js.map +1 -0
  115. package/dist/translations/zh.js +52 -0
  116. package/dist/translations/zh.js.map +1 -0
  117. package/dist/types.d.ts +214 -0
  118. package/dist/types.js +11 -0
  119. package/dist/types.js.map +1 -0
  120. package/dist/ui/EndSessionMenuItem.d.ts +5 -0
  121. package/dist/ui/EndSessionMenuItem.js +45 -0
  122. package/dist/ui/EndSessionMenuItem.js.map +1 -0
  123. package/dist/ui/ImpersonateIcon.js +20 -0
  124. package/dist/ui/ImpersonateIcon.js.map +1 -0
  125. package/dist/ui/ImpersonationAction.d.ts +10 -0
  126. package/dist/ui/ImpersonationAction.js +48 -0
  127. package/dist/ui/ImpersonationAction.js.map +1 -0
  128. package/dist/ui/ImpersonationBar.d.ts +36 -0
  129. package/dist/ui/ImpersonationBar.js +357 -0
  130. package/dist/ui/ImpersonationBar.js.map +1 -0
  131. package/dist/ui/ImpersonationConfig.js +25 -0
  132. package/dist/ui/ImpersonationConfig.js.map +1 -0
  133. package/dist/ui/ImpersonationDocumentButton.d.ts +5 -0
  134. package/dist/ui/ImpersonationDocumentButton.js +85 -0
  135. package/dist/ui/ImpersonationDocumentButton.js.map +1 -0
  136. package/dist/ui/ImpersonationFrontendBanner.d.ts +41 -0
  137. package/dist/ui/ImpersonationFrontendBanner.js +58 -0
  138. package/dist/ui/ImpersonationFrontendBanner.js.map +1 -0
  139. package/dist/ui/ImpersonationFrontendExit.d.ts +17 -0
  140. package/dist/ui/ImpersonationFrontendExit.js +45 -0
  141. package/dist/ui/ImpersonationFrontendExit.js.map +1 -0
  142. package/dist/ui/ImpersonationProvider.d.ts +16 -0
  143. package/dist/ui/ImpersonationProvider.js +127 -0
  144. package/dist/ui/ImpersonationProvider.js.map +1 -0
  145. package/dist/ui/ImpersonationSwitcher.d.ts +21 -0
  146. package/dist/ui/ImpersonationSwitcher.js +251 -0
  147. package/dist/ui/ImpersonationSwitcher.js.map +1 -0
  148. package/dist/ui/ImpersonationUserCard.d.ts +31 -0
  149. package/dist/ui/ImpersonationUserCard.js +51 -0
  150. package/dist/ui/ImpersonationUserCard.js.map +1 -0
  151. package/dist/ui/StartConfirmModal.d.ts +28 -0
  152. package/dist/ui/StartConfirmModal.js +65 -0
  153. package/dist/ui/StartConfirmModal.js.map +1 -0
  154. package/dist/ui/api.js +66 -0
  155. package/dist/ui/api.js.map +1 -0
  156. package/dist/ui/barPlacement.js +107 -0
  157. package/dist/ui/barPlacement.js.map +1 -0
  158. package/dist/ui/frontendBanner.css +22 -0
  159. package/dist/ui/impersonation.css +244 -0
  160. package/dist/ui/useImpersonation.d.ts +43 -0
  161. package/dist/ui/useImpersonation.js +36 -0
  162. package/dist/ui/useImpersonation.js.map +1 -0
  163. package/package.json +126 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registerCollection.js","names":[],"sources":["../../src/plugin/registerCollection.ts"],"sourcesContent":["import type { CollectionConfig, CollectionSlug, Config } from 'payload'\n\nimport { keys } from '../translations/keys'\nimport { labelForKey } from '../translations/server'\nimport type { ResolvedOptions } from '../types'\nimport { ADMIN_GROUP } from './constants'\n\nconst deny = () => false\n\nexport const buildRecordsCollection = ({\n\tauthSlugs,\n\toptions,\n}: {\n\tauthSlugs: CollectionSlug[]\n\toptions: ResolvedOptions\n}): CollectionConfig => {\n\tconst relationTo: CollectionSlug[] = authSlugs.length > 0 ? authSlugs : ['users']\n\n\treturn {\n\t\tslug: options.collectionSlug,\n\t\taccess: {\n\t\t\tcreate: deny,\n\t\t\tdelete: deny,\n\t\t\tread: options.access.readRecords,\n\t\t\tupdate: deny,\n\t\t},\n\t\tadmin: {\n\t\t\tdefaultColumns: ['target', 'impersonator', 'startedAt', 'endedAt', 'endedBy'],\n\t\t\tgroup: ADMIN_GROUP,\n\t\t\thidden: !options.ui.sessionsCollection,\n\t\t\tuseAsTitle: 'targetTitle',\n\t\t\t...(options.ui.recordAction\n\t\t\t\t? {\n\t\t\t\t\t\tcomponents: {\n\t\t\t\t\t\t\tedit: {\n\t\t\t\t\t\t\t\tbeforeDocumentControls: ['@10x-media/impersonation/client#EndSessionMenuItem'],\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t},\n\t\tfields: [\n\t\t\t{\n\t\t\t\tname: 'impersonator',\n\t\t\t\ttype: 'relationship',\n\t\t\t\tlabel: labelForKey(keys.fieldImpersonator),\n\t\t\t\trelationTo,\n\t\t\t\trequired: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'target',\n\t\t\t\ttype: 'relationship',\n\t\t\t\tlabel: labelForKey(keys.fieldTarget),\n\t\t\t\trelationTo,\n\t\t\t\trequired: true,\n\t\t\t},\n\t\t\t{ name: 'impersonatorEmail', type: 'text', index: true },\n\t\t\t{ name: 'targetEmail', type: 'text', index: true },\n\t\t\t{ name: 'impersonatorTitle', type: 'text' },\n\t\t\t{ name: 'targetTitle', type: 'text' },\n\t\t\t{\n\t\t\t\tname: 'mode',\n\t\t\t\ttype: 'select',\n\t\t\t\tlabel: labelForKey(keys.fieldMode),\n\t\t\t\toptions: [\n\t\t\t\t\t{ label: 'swap', value: 'swap' },\n\t\t\t\t\t{ label: 'parallel', value: 'parallel' },\n\t\t\t\t],\n\t\t\t\trequired: true,\n\t\t\t},\n\t\t\t{ name: 'targetSid', type: 'text', index: true, required: true },\n\t\t\t{ name: 'impersonatorSid', type: 'text', index: true, required: true },\n\t\t\t{ name: 'impersonatorTenantCookie', type: 'text' },\n\t\t\t{\n\t\t\t\tname: 'startedAt',\n\t\t\t\ttype: 'date',\n\t\t\t\tlabel: labelForKey(keys.fieldStartedAt),\n\t\t\t\trequired: true,\n\t\t\t},\n\t\t\t{ name: 'endedAt', type: 'date', index: true, label: labelForKey(keys.fieldEndedAt) },\n\t\t\t{\n\t\t\t\tname: 'endedBy',\n\t\t\t\ttype: 'select',\n\t\t\t\tlabel: labelForKey(keys.fieldEndedBy),\n\t\t\t\toptions: [\n\t\t\t\t\t{ label: 'exit', value: 'exit' },\n\t\t\t\t\t{ label: 'logout', value: 'logout' },\n\t\t\t\t\t{ label: 'terminated', value: 'terminated' },\n\t\t\t\t\t{ label: 'expired', value: 'expired' },\n\t\t\t\t\t{ label: 'impersonatorGone', value: 'impersonatorGone' },\n\t\t\t\t\t{ label: 'targetGone', value: 'targetGone' },\n\t\t\t\t\t{ label: 'failed', value: 'failed' },\n\t\t\t\t],\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'reason',\n\t\t\t\tmaxLength: 500,\n\t\t\t\ttype: 'text',\n\t\t\t\tlabel: labelForKey(keys.fieldReason),\n\t\t\t},\n\t\t\t{ name: 'ip', type: 'text' },\n\t\t\t{ name: 'userAgent', type: 'text' },\n\t\t\t{ name: 'impersonatorLocale', type: 'text' },\n\t\t\t{ name: 'absoluteExpiresAt', type: 'date' },\n\t\t\t{ name: 'targetLocked', type: 'checkbox' },\n\t\t],\n\t\tindexes: [{ fields: ['targetSid', 'endedAt'] }, { fields: ['impersonatorSid', 'endedAt'] }],\n\t\tlabels: {\n\t\t\tplural: labelForKey(keys.collectionPlural),\n\t\t\tsingular: labelForKey(keys.collectionSingular),\n\t\t},\n\t\ttimestamps: true,\n\t}\n}\n\nexport const registerCollection = (config: Config, options: ResolvedOptions): void => {\n\tconst authSlugs = (config.collections ?? [])\n\t\t.filter((collection) => Boolean(collection.auth))\n\t\t.map(({ slug }) => slug as CollectionSlug)\n\tconst built = buildRecordsCollection({ authSlugs, options })\n\tconfig.collections = [\n\t\t...(config.collections ?? []),\n\t\toptions.collectionOverrides ? options.collectionOverrides(built) : built,\n\t]\n}\n"],"mappings":";;;;AAOA,MAAM,aAAa;AAEnB,MAAa,0BAA0B,EACtC,WACA,cAIuB;CACvB,MAAM,aAA+B,UAAU,SAAS,IAAI,YAAY,CAAC,OAAO;CAEhF,OAAO;EACN,MAAM,QAAQ;EACd,QAAQ;GACP,QAAQ;GACR,QAAQ;GACR,MAAM,QAAQ,OAAO;GACrB,QAAQ;EACT;EACA,OAAO;GACN,gBAAgB;IAAC;IAAU;IAAgB;IAAa;IAAW;GAAS;GAC5E,OAAO;GACP,QAAQ,CAAC,QAAQ,GAAG;GACpB,YAAY;GACZ,GAAI,QAAQ,GAAG,eACZ,EACA,YAAY,EACX,MAAM,EACL,wBAAwB,CAAC,oDAAoD,EAC9E,EACD,EACD,IACC,CAAC;EACL;EACA,QAAQ;GACP;IACC,MAAM;IACN,MAAM;IACN,OAAO,YAAY,KAAK,iBAAiB;IACzC;IACA,UAAU;GACX;GACA;IACC,MAAM;IACN,MAAM;IACN,OAAO,YAAY,KAAK,WAAW;IACnC;IACA,UAAU;GACX;GACA;IAAE,MAAM;IAAqB,MAAM;IAAQ,OAAO;GAAK;GACvD;IAAE,MAAM;IAAe,MAAM;IAAQ,OAAO;GAAK;GACjD;IAAE,MAAM;IAAqB,MAAM;GAAO;GAC1C;IAAE,MAAM;IAAe,MAAM;GAAO;GACpC;IACC,MAAM;IACN,MAAM;IACN,OAAO,YAAY,KAAK,SAAS;IACjC,SAAS,CACR;KAAE,OAAO;KAAQ,OAAO;IAAO,GAC/B;KAAE,OAAO;KAAY,OAAO;IAAW,CACxC;IACA,UAAU;GACX;GACA;IAAE,MAAM;IAAa,MAAM;IAAQ,OAAO;IAAM,UAAU;GAAK;GAC/D;IAAE,MAAM;IAAmB,MAAM;IAAQ,OAAO;IAAM,UAAU;GAAK;GACrE;IAAE,MAAM;IAA4B,MAAM;GAAO;GACjD;IACC,MAAM;IACN,MAAM;IACN,OAAO,YAAY,KAAK,cAAc;IACtC,UAAU;GACX;GACA;IAAE,MAAM;IAAW,MAAM;IAAQ,OAAO;IAAM,OAAO,YAAY,KAAK,YAAY;GAAE;GACpF;IACC,MAAM;IACN,MAAM;IACN,OAAO,YAAY,KAAK,YAAY;IACpC,SAAS;KACR;MAAE,OAAO;MAAQ,OAAO;KAAO;KAC/B;MAAE,OAAO;MAAU,OAAO;KAAS;KACnC;MAAE,OAAO;MAAc,OAAO;KAAa;KAC3C;MAAE,OAAO;MAAW,OAAO;KAAU;KACrC;MAAE,OAAO;MAAoB,OAAO;KAAmB;KACvD;MAAE,OAAO;MAAc,OAAO;KAAa;KAC3C;MAAE,OAAO;MAAU,OAAO;KAAS;IACpC;GACD;GACA;IACC,MAAM;IACN,WAAW;IACX,MAAM;IACN,OAAO,YAAY,KAAK,WAAW;GACpC;GACA;IAAE,MAAM;IAAM,MAAM;GAAO;GAC3B;IAAE,MAAM;IAAa,MAAM;GAAO;GAClC;IAAE,MAAM;IAAsB,MAAM;GAAO;GAC3C;IAAE,MAAM;IAAqB,MAAM;GAAO;GAC1C;IAAE,MAAM;IAAgB,MAAM;GAAW;EAC1C;EACA,SAAS,CAAC,EAAE,QAAQ,CAAC,aAAa,SAAS,EAAE,GAAG,EAAE,QAAQ,CAAC,mBAAmB,SAAS,EAAE,CAAC;EAC1F,QAAQ;GACP,QAAQ,YAAY,KAAK,gBAAgB;GACzC,UAAU,YAAY,KAAK,kBAAkB;EAC9C;EACA,YAAY;CACb;AACD;AAEA,MAAa,sBAAsB,QAAgB,YAAmC;CAIrF,MAAM,QAAQ,uBAAuB;EAAE,YAHpB,OAAO,eAAe,CAAC,GACxC,QAAQ,eAAe,QAAQ,WAAW,IAAI,CAAC,EAC/C,KAAK,EAAE,WAAW,IAC2B;EAAG;CAAQ,CAAC;CAC3D,OAAO,cAAc,CACpB,GAAI,OAAO,eAAe,CAAC,GAC3B,QAAQ,sBAAsB,QAAQ,oBAAoB,KAAK,IAAI,KACpE;AACD"}
@@ -0,0 +1,19 @@
1
+ import { currentEndpoint } from "../endpoints/current.js";
2
+ import { exitEndpoint } from "../endpoints/exit.js";
3
+ import { startEndpoint } from "../endpoints/start.js";
4
+ import { terminateEndpoint } from "../endpoints/terminate.js";
5
+ //#region src/plugin/registerEndpoints.ts
6
+ const registerEndpoints = (config, options) => {
7
+ const base = options.apiPath.endsWith("/") ? options.apiPath.slice(0, -1) : options.apiPath;
8
+ config.endpoints = [
9
+ ...config.endpoints ?? [],
10
+ startEndpoint(`${base}/start`),
11
+ exitEndpoint(`${base}/exit`),
12
+ currentEndpoint(base),
13
+ terminateEndpoint(`${base}/:id/end`)
14
+ ];
15
+ };
16
+ //#endregion
17
+ export { registerEndpoints };
18
+
19
+ //# sourceMappingURL=registerEndpoints.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registerEndpoints.js","names":[],"sources":["../../src/plugin/registerEndpoints.ts"],"sourcesContent":["import type { Config } from 'payload'\n\nimport { currentEndpoint } from '../endpoints/current'\nimport { exitEndpoint } from '../endpoints/exit'\nimport { startEndpoint } from '../endpoints/start'\nimport { terminateEndpoint } from '../endpoints/terminate'\nimport type { ResolvedOptions } from '../types'\n\nexport const registerEndpoints = (config: Config, options: ResolvedOptions): void => {\n\tconst base = options.apiPath.endsWith('/') ? options.apiPath.slice(0, -1) : options.apiPath\n\tconfig.endpoints = [\n\t\t...(config.endpoints ?? []),\n\t\tstartEndpoint(`${base}/start`),\n\t\texitEndpoint(`${base}/exit`),\n\t\tcurrentEndpoint(base),\n\t\tterminateEndpoint(`${base}/:id/end`),\n\t]\n}\n"],"mappings":";;;;;AAQA,MAAa,qBAAqB,QAAgB,YAAmC;CACpF,MAAM,OAAO,QAAQ,QAAQ,SAAS,GAAG,IAAI,QAAQ,QAAQ,MAAM,GAAG,EAAE,IAAI,QAAQ;CACpF,OAAO,YAAY;EAClB,GAAI,OAAO,aAAa,CAAC;EACzB,cAAc,GAAG,KAAK,OAAO;EAC7B,aAAa,GAAG,KAAK,MAAM;EAC3B,gBAAgB,IAAI;EACpB,kBAAkB,GAAG,KAAK,SAAS;CACpC;AACD"}
@@ -0,0 +1,59 @@
1
+ import { collectionBySlug } from "../ids.js";
2
+ import { findOpenBySid } from "../session/resolve.js";
3
+ import { expireCookie } from "../auth/cookies.js";
4
+ import { closeAndRevoke } from "../session/close.js";
5
+ import { boundSid } from "../types.js";
6
+ import { releaseLocks } from "../session/locks.js";
7
+ //#region src/plugin/registerHooks.ts
8
+ const registerHooks = (config, options) => {
9
+ const afterLogout = async ({ req }) => {
10
+ try {
11
+ const user = req.user;
12
+ const sid = boundSid(user, options.session.binding);
13
+ if (!user || !sid) return;
14
+ const row = await findOpenBySid({
15
+ options,
16
+ payload: req.payload,
17
+ req,
18
+ sid
19
+ });
20
+ if (!row) return;
21
+ const closed = await closeAndRevoke({
22
+ endedBy: "logout",
23
+ options,
24
+ payload: req.payload,
25
+ record: row,
26
+ req
27
+ });
28
+ await releaseLocks({
29
+ payload: req.payload,
30
+ record: closed,
31
+ req
32
+ });
33
+ const authConfig = (user.collection ? collectionBySlug(req.payload, user.collection)?.config.auth : void 0) ?? collectionBySlug(req.payload, req.payload.config.admin.user)?.config.auth;
34
+ if (authConfig && req.responseHeaders) req.responseHeaders.append("Set-Cookie", expireCookie({
35
+ authConfig,
36
+ name: options.hintCookieName
37
+ }));
38
+ } catch (error) {
39
+ req.payload.logger.error({
40
+ err: error,
41
+ msg: "@10x-media/impersonation: afterLogout failed to close the session"
42
+ });
43
+ }
44
+ };
45
+ config.collections = (config.collections ?? []).map((collection) => {
46
+ if (!collection.auth) return collection;
47
+ return {
48
+ ...collection,
49
+ hooks: {
50
+ ...collection.hooks,
51
+ afterLogout: [...collection.hooks?.afterLogout ?? [], afterLogout]
52
+ }
53
+ };
54
+ });
55
+ };
56
+ //#endregion
57
+ export { registerHooks };
58
+
59
+ //# sourceMappingURL=registerHooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registerHooks.js","names":[],"sources":["../../src/plugin/registerHooks.ts"],"sourcesContent":["import type { CollectionAfterLogoutHook, Config } from 'payload'\n\nimport { expireCookie } from '../auth/cookies'\nimport { collectionBySlug } from '../ids'\nimport { closeAndRevoke } from '../session/close'\nimport { releaseLocks } from '../session/locks'\nimport { findOpenBySid } from '../session/resolve'\nimport type { ResolvedOptions } from '../types'\nimport { boundSid } from '../types'\n\nexport const registerHooks = (config: Config, options: ResolvedOptions): void => {\n\tconst afterLogout: CollectionAfterLogoutHook = async ({ req }) => {\n\t\ttry {\n\t\t\tconst user = req.user\n\t\t\tconst sid = boundSid(user, options.session.binding)\n\t\t\tif (!user || !sid) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst row = await findOpenBySid({\n\t\t\t\toptions,\n\t\t\t\tpayload: req.payload,\n\t\t\t\treq,\n\t\t\t\tsid,\n\t\t\t})\n\t\t\tif (!row) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst closed = await closeAndRevoke({\n\t\t\t\tendedBy: 'logout',\n\t\t\t\toptions,\n\t\t\t\tpayload: req.payload,\n\t\t\t\trecord: row,\n\t\t\t\treq,\n\t\t\t})\n\t\t\tawait releaseLocks({ payload: req.payload, record: closed, req })\n\t\t\tconst authConfig =\n\t\t\t\t(user.collection\n\t\t\t\t\t? collectionBySlug(req.payload, user.collection)?.config.auth\n\t\t\t\t\t: undefined) ?? collectionBySlug(req.payload, req.payload.config.admin.user)?.config.auth\n\t\t\tif (authConfig && req.responseHeaders) {\n\t\t\t\treq.responseHeaders.append(\n\t\t\t\t\t'Set-Cookie',\n\t\t\t\t\texpireCookie({ authConfig, name: options.hintCookieName })\n\t\t\t\t)\n\t\t\t}\n\t\t} catch (error) {\n\t\t\treq.payload.logger.error({\n\t\t\t\terr: error,\n\t\t\t\tmsg: '@10x-media/impersonation: afterLogout failed to close the session',\n\t\t\t})\n\t\t}\n\t}\n\n\tconfig.collections = (config.collections ?? []).map((collection) => {\n\t\tif (!collection.auth) {\n\t\t\treturn collection\n\t\t}\n\t\treturn {\n\t\t\t...collection,\n\t\t\thooks: {\n\t\t\t\t...collection.hooks,\n\t\t\t\tafterLogout: [...(collection.hooks?.afterLogout ?? []), afterLogout],\n\t\t\t},\n\t\t}\n\t})\n}\n"],"mappings":";;;;;;;AAUA,MAAa,iBAAiB,QAAgB,YAAmC;CAChF,MAAM,cAAyC,OAAO,EAAE,UAAU;EACjE,IAAI;GACH,MAAM,OAAO,IAAI;GACjB,MAAM,MAAM,SAAS,MAAM,QAAQ,QAAQ,OAAO;GAClD,IAAI,CAAC,QAAQ,CAAC,KACb;GAED,MAAM,MAAM,MAAM,cAAc;IAC/B;IACA,SAAS,IAAI;IACb;IACA;GACD,CAAC;GACD,IAAI,CAAC,KACJ;GAED,MAAM,SAAS,MAAM,eAAe;IACnC,SAAS;IACT;IACA,SAAS,IAAI;IACb,QAAQ;IACR;GACD,CAAC;GACD,MAAM,aAAa;IAAE,SAAS,IAAI;IAAS,QAAQ;IAAQ;GAAI,CAAC;GAChE,MAAM,cACJ,KAAK,aACH,iBAAiB,IAAI,SAAS,KAAK,UAAU,GAAG,OAAO,OACvD,KAAA,MAAc,iBAAiB,IAAI,SAAS,IAAI,QAAQ,OAAO,MAAM,IAAI,GAAG,OAAO;GACvF,IAAI,cAAc,IAAI,iBACrB,IAAI,gBAAgB,OACnB,cACA,aAAa;IAAE;IAAY,MAAM,QAAQ;GAAe,CAAC,CAC1D;EAEF,SAAS,OAAO;GACf,IAAI,QAAQ,OAAO,MAAM;IACxB,KAAK;IACL,KAAK;GACN,CAAC;EACF;CACD;CAEA,OAAO,eAAe,OAAO,eAAe,CAAC,GAAG,KAAK,eAAe;EACnE,IAAI,CAAC,WAAW,MACf,OAAO;EAER,OAAO;GACN,GAAG;GACH,OAAO;IACN,GAAG,WAAW;IACd,aAAa,CAAC,GAAI,WAAW,OAAO,eAAe,CAAC,GAAI,WAAW;GACpE;EACD;CACD,CAAC;AACF"}
@@ -0,0 +1,57 @@
1
+ import { closeStaleImpersonations } from "../session/closeStale.js";
2
+ //#region src/plugin/registerRetention.ts
3
+ const DEFAULT_CRON = "0 3 * * *";
4
+ const DEFAULT_QUEUE = "impersonation-retention";
5
+ const PAGE = 100;
6
+ /**
7
+ * Opt-in job: close stale open rows, then delete closed rows older than
8
+ * `deleteAfterDays`. The host must run the queue.
9
+ */
10
+ const registerRetention = (config, options) => {
11
+ const retention = options.retention;
12
+ if (!retention) return;
13
+ const task = {
14
+ slug: "impersonationRetention",
15
+ schedule: [{
16
+ cron: retention.cron ?? DEFAULT_CRON,
17
+ queue: retention.queue ?? DEFAULT_QUEUE
18
+ }],
19
+ handler: async ({ req }) => {
20
+ const closed = await closeStaleImpersonations(req.payload);
21
+ const cutoff = (/* @__PURE__ */ new Date(Date.now() - retention.deleteAfterDays * 864e5)).toISOString();
22
+ let deleted = 0;
23
+ for (;;) {
24
+ const { docs } = await req.payload.find({
25
+ collection: options.collectionSlug,
26
+ depth: 0,
27
+ limit: PAGE,
28
+ overrideAccess: true,
29
+ pagination: false,
30
+ where: { and: [{ endedAt: { exists: true } }, { endedAt: { less_than_equal: cutoff } }] }
31
+ });
32
+ if (docs.length === 0) break;
33
+ for (const doc of docs) {
34
+ await req.payload.delete({
35
+ collection: options.collectionSlug,
36
+ id: doc.id,
37
+ overrideAccess: true
38
+ });
39
+ deleted += 1;
40
+ }
41
+ if (docs.length < PAGE) break;
42
+ }
43
+ return { output: {
44
+ closed,
45
+ deleted
46
+ } };
47
+ }
48
+ };
49
+ config.jobs = {
50
+ ...config.jobs,
51
+ tasks: [...config.jobs?.tasks ?? [], task]
52
+ };
53
+ };
54
+ //#endregion
55
+ export { registerRetention };
56
+
57
+ //# sourceMappingURL=registerRetention.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registerRetention.js","names":[],"sources":["../../src/plugin/registerRetention.ts"],"sourcesContent":["import type { Config, TaskConfig } from 'payload'\n\nimport { closeStaleImpersonations } from '../session/closeStale'\nimport type { ResolvedOptions } from '../types'\n\nconst DEFAULT_CRON = '0 3 * * *'\nconst DEFAULT_QUEUE = 'impersonation-retention'\nconst PAGE = 100\n\n/**\n * Opt-in job: close stale open rows, then delete closed rows older than\n * `deleteAfterDays`. The host must run the queue.\n */\nexport const registerRetention = (config: Config, options: ResolvedOptions): void => {\n\tconst retention = options.retention\n\tif (!retention) {\n\t\treturn\n\t}\n\n\tconst task = {\n\t\tslug: 'impersonationRetention',\n\t\tschedule: [{ cron: retention.cron ?? DEFAULT_CRON, queue: retention.queue ?? DEFAULT_QUEUE }],\n\t\thandler: async ({ req }) => {\n\t\t\tconst closed = await closeStaleImpersonations(req.payload)\n\t\t\tconst cutoff = new Date(Date.now() - retention.deleteAfterDays * 86_400_000).toISOString()\n\t\t\tlet deleted = 0\n\t\t\tfor (;;) {\n\t\t\t\tconst { docs } = await req.payload.find({\n\t\t\t\t\tcollection: options.collectionSlug,\n\t\t\t\t\tdepth: 0,\n\t\t\t\t\tlimit: PAGE,\n\t\t\t\t\toverrideAccess: true,\n\t\t\t\t\tpagination: false,\n\t\t\t\t\twhere: {\n\t\t\t\t\t\tand: [{ endedAt: { exists: true } }, { endedAt: { less_than_equal: cutoff } }],\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\tif (docs.length === 0) {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tfor (const doc of docs) {\n\t\t\t\t\tawait req.payload.delete({\n\t\t\t\t\t\tcollection: options.collectionSlug,\n\t\t\t\t\t\tid: doc.id,\n\t\t\t\t\t\toverrideAccess: true,\n\t\t\t\t\t})\n\t\t\t\t\tdeleted += 1\n\t\t\t\t}\n\t\t\t\tif (docs.length < PAGE) {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn { output: { closed, deleted } }\n\t\t},\n\t} as TaskConfig\n\n\tconfig.jobs = {\n\t\t...config.jobs,\n\t\ttasks: [...(config.jobs?.tasks ?? []), task],\n\t}\n}\n"],"mappings":";;AAKA,MAAM,eAAe;AACrB,MAAM,gBAAgB;AACtB,MAAM,OAAO;;;;;AAMb,MAAa,qBAAqB,QAAgB,YAAmC;CACpF,MAAM,YAAY,QAAQ;CAC1B,IAAI,CAAC,WACJ;CAGD,MAAM,OAAO;EACZ,MAAM;EACN,UAAU,CAAC;GAAE,MAAM,UAAU,QAAQ;GAAc,OAAO,UAAU,SAAS;EAAc,CAAC;EAC5F,SAAS,OAAO,EAAE,UAAU;GAC3B,MAAM,SAAS,MAAM,yBAAyB,IAAI,OAAO;GACzD,MAAM,0BAAS,IAAI,KAAK,KAAK,IAAI,IAAI,UAAU,kBAAkB,KAAU,GAAE,YAAY;GACzF,IAAI,UAAU;GACd,SAAS;IACR,MAAM,EAAE,SAAS,MAAM,IAAI,QAAQ,KAAK;KACvC,YAAY,QAAQ;KACpB,OAAO;KACP,OAAO;KACP,gBAAgB;KAChB,YAAY;KACZ,OAAO,EACN,KAAK,CAAC,EAAE,SAAS,EAAE,QAAQ,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,iBAAiB,OAAO,EAAE,CAAC,EAC9E;IACD,CAAC;IACD,IAAI,KAAK,WAAW,GACnB;IAED,KAAK,MAAM,OAAO,MAAM;KACvB,MAAM,IAAI,QAAQ,OAAO;MACxB,YAAY,QAAQ;MACpB,IAAI,IAAI;MACR,gBAAgB;KACjB,CAAC;KACD,WAAW;IACZ;IACA,IAAI,KAAK,SAAS,MACjB;GAEF;GACA,OAAO,EAAE,QAAQ;IAAE;IAAQ;GAAQ,EAAE;EACtC;CACD;CAEA,OAAO,OAAO;EACb,GAAG,OAAO;EACV,OAAO,CAAC,GAAI,OAAO,MAAM,SAAS,CAAC,GAAI,IAAI;CAC5C;AACD"}
@@ -0,0 +1,19 @@
1
+ import { toNested, translations } from "../translations/index.js";
2
+ import { deepMergeSimple } from "payload/shared";
3
+ //#region src/plugin/registerTranslations.ts
4
+ /**
5
+ * Merge this plugin's translations into the host config. Plugin-level
6
+ * `overrides` win over the built-ins key-by-key and may add locales the plugin
7
+ * does not ship. A host value wins over both (`deepMergeSimple` lets the second
8
+ * argument override), so projects can override any string.
9
+ */
10
+ const registerTranslations = (config, overrides) => {
11
+ const nested = {};
12
+ for (const [locale, flat] of Object.entries(overrides ?? {})) nested[locale] = toNested(flat);
13
+ config.i18n ??= {};
14
+ config.i18n.translations = deepMergeSimple(deepMergeSimple(translations, nested), config.i18n.translations ?? {});
15
+ };
16
+ //#endregion
17
+ export { registerTranslations };
18
+
19
+ //# sourceMappingURL=registerTranslations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registerTranslations.js","names":[],"sources":["../../src/plugin/registerTranslations.ts"],"sourcesContent":["import type { Config } from 'payload'\nimport { deepMergeSimple } from 'payload/shared'\n\nimport { type TranslationsOption, toNested, translations } from '../translations'\n\ntype Translations = NonNullable<NonNullable<Config['i18n']>['translations']>\n\n/**\n * Merge this plugin's translations into the host config. Plugin-level\n * `overrides` win over the built-ins key-by-key and may add locales the plugin\n * does not ship. A host value wins over both (`deepMergeSimple` lets the second\n * argument override), so projects can override any string.\n */\nexport const registerTranslations = (config: Config, overrides?: TranslationsOption): void => {\n\tconst nested: Record<string, Record<string, Record<string, string>>> = {}\n\tfor (const [locale, flat] of Object.entries(overrides ?? {})) {\n\t\tnested[locale] = toNested(flat)\n\t}\n\tconfig.i18n ??= {}\n\tconfig.i18n.translations = deepMergeSimple<Translations>(\n\t\tdeepMergeSimple(translations, nested),\n\t\tconfig.i18n.translations ?? {}\n\t)\n}\n"],"mappings":";;;;;;;;;AAaA,MAAa,wBAAwB,QAAgB,cAAyC;CAC7F,MAAM,SAAiE,CAAC;CACxE,KAAK,MAAM,CAAC,QAAQ,SAAS,OAAO,QAAQ,aAAa,CAAC,CAAC,GAC1D,OAAO,UAAU,SAAS,IAAI;CAE/B,OAAO,SAAS,CAAC;CACjB,OAAO,KAAK,eAAe,gBAC1B,gBAAgB,cAAc,MAAM,GACpC,OAAO,KAAK,gBAAgB,CAAC,CAC9B;AACD"}
@@ -0,0 +1,11 @@
1
+ import { REGISTRY_KEY } from "./constants.js";
2
+ //#region src/plugin/registry.ts
3
+ const setRegistry = (config, resolved) => {
4
+ config.custom ??= {};
5
+ config.custom[REGISTRY_KEY] = resolved;
6
+ };
7
+ const getRegistry = (config) => config.custom?.[REGISTRY_KEY];
8
+ //#endregion
9
+ export { getRegistry, setRegistry };
10
+
11
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","names":[],"sources":["../../src/plugin/registry.ts"],"sourcesContent":["import type { Config, SanitizedConfig } from 'payload'\n\nimport type { ResolvedOptions } from '../types'\nimport { REGISTRY_KEY } from './constants'\n\nexport const setRegistry = (config: Config, resolved: ResolvedOptions): void => {\n\tconfig.custom ??= {}\n\tconfig.custom[REGISTRY_KEY] = resolved\n}\n\nexport const getRegistry = (\n\tconfig: Pick<Config | SanitizedConfig, 'custom'>\n): ResolvedOptions | undefined =>\n\t(config.custom as Record<string, ResolvedOptions | undefined> | undefined)?.[REGISTRY_KEY]\n"],"mappings":";;AAKA,MAAa,eAAe,QAAgB,aAAoC;CAC/E,OAAO,WAAW,CAAC;CACnB,OAAO,OAAO,gBAAgB;AAC/B;AAEA,MAAa,eACZ,WAEC,OAAO,SAAqE"}
@@ -0,0 +1,21 @@
1
+ //#region src/plugin/startable.ts
2
+ /**
3
+ * Auth collections start will actually mint. Explicit `targets` is an allow
4
+ * list. Otherwise local-strategy + sessions, unless the host supplied all
5
+ * three session seams.
6
+ */
7
+ const isStartableAuthCollection = (collection, options) => {
8
+ if (!collection.auth || collection.slug === options.collectionSlug) return false;
9
+ if (options.targets) return options.targets.includes(collection.slug);
10
+ if (Boolean(options.session.issue && options.session.revoke && options.session.binding)) return true;
11
+ if (collection.auth !== true) {
12
+ if (collection.auth.disableLocalStrategy) return false;
13
+ if (collection.auth.useSessions === false) return false;
14
+ }
15
+ return true;
16
+ };
17
+ const startableCollectionSlugs = (collections, options) => (collections ?? []).filter((collection) => isStartableAuthCollection(collection, options)).map(({ slug }) => slug);
18
+ //#endregion
19
+ export { isStartableAuthCollection, startableCollectionSlugs };
20
+
21
+ //# sourceMappingURL=startable.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"startable.js","names":[],"sources":["../../src/plugin/startable.ts"],"sourcesContent":["import type { CollectionSlug } from 'payload'\n\nimport type { ResolvedOptions } from '../types'\n\ntype Authish = boolean | { disableLocalStrategy?: unknown; useSessions?: boolean }\n\n/**\n * Auth collections start will actually mint. Explicit `targets` is an allow\n * list. Otherwise local-strategy + sessions, unless the host supplied all\n * three session seams.\n */\nexport const isStartableAuthCollection = (\n\tcollection: { auth?: Authish; slug: string },\n\toptions: Pick<ResolvedOptions, 'collectionSlug' | 'session' | 'targets'>\n): boolean => {\n\tif (!collection.auth || collection.slug === options.collectionSlug) {\n\t\treturn false\n\t}\n\tif (options.targets) {\n\t\treturn options.targets.includes(collection.slug as CollectionSlug)\n\t}\n\tconst hasSeams = Boolean(\n\t\toptions.session.issue && options.session.revoke && options.session.binding\n\t)\n\tif (hasSeams) {\n\t\treturn true\n\t}\n\tif (collection.auth !== true) {\n\t\tif (collection.auth.disableLocalStrategy) {\n\t\t\treturn false\n\t\t}\n\t\tif (collection.auth.useSessions === false) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nexport const startableCollectionSlugs = (\n\tcollections: { auth?: Authish; slug: string }[] | undefined,\n\toptions: Pick<ResolvedOptions, 'collectionSlug' | 'session' | 'targets'>\n): CollectionSlug[] =>\n\t(collections ?? [])\n\t\t.filter((collection) => isStartableAuthCollection(collection, options))\n\t\t.map(({ slug }) => slug as CollectionSlug)\n"],"mappings":";;;;;;AAWA,MAAa,6BACZ,YACA,YACa;CACb,IAAI,CAAC,WAAW,QAAQ,WAAW,SAAS,QAAQ,gBACnD,OAAO;CAER,IAAI,QAAQ,SACX,OAAO,QAAQ,QAAQ,SAAS,WAAW,IAAsB;CAKlE,IAHiB,QAChB,QAAQ,QAAQ,SAAS,QAAQ,QAAQ,UAAU,QAAQ,QAAQ,OAEzD,GACV,OAAO;CAER,IAAI,WAAW,SAAS,MAAM;EAC7B,IAAI,WAAW,KAAK,sBACnB,OAAO;EAER,IAAI,WAAW,KAAK,gBAAgB,OACnC,OAAO;CAET;CACA,OAAO;AACR;AAEA,MAAa,4BACZ,aACA,aAEC,eAAe,CAAC,GACf,QAAQ,eAAe,0BAA0B,YAAY,OAAO,CAAC,EACrE,KAAK,EAAE,WAAW,IAAsB"}
@@ -0,0 +1,65 @@
1
+ import { relationOf } from "./resolve.js";
2
+ import { revokeSession } from "../auth/revoke.js";
3
+ //#region src/session/close.ts
4
+ const closeRecord = async ({ endedBy, options, payload, record, req }) => {
5
+ if (record.endedAt) return record;
6
+ const endedAt = (/* @__PURE__ */ new Date()).toISOString();
7
+ let updated;
8
+ try {
9
+ updated = await payload.update({
10
+ id: record.id,
11
+ collection: options.collectionSlug,
12
+ data: {
13
+ endedAt,
14
+ endedBy
15
+ },
16
+ depth: 0,
17
+ overrideAccess: true,
18
+ req
19
+ });
20
+ } catch {
21
+ await payload.db.updateOne({
22
+ id: record.id,
23
+ collection: options.collectionSlug,
24
+ data: {
25
+ endedAt,
26
+ endedBy
27
+ },
28
+ req,
29
+ returning: false
30
+ });
31
+ updated = {
32
+ ...record,
33
+ endedAt,
34
+ endedBy
35
+ };
36
+ }
37
+ await options.onEnd?.({
38
+ endedBy,
39
+ payload,
40
+ record: updated,
41
+ req
42
+ });
43
+ return updated;
44
+ };
45
+ const closeAndRevoke = async ({ endedBy, options, payload, record, req }) => {
46
+ const target = relationOf(record.target);
47
+ if (target) await (options.session.revoke ?? revokeSession)({
48
+ collection: target.collection,
49
+ payload,
50
+ req,
51
+ sid: record.targetSid,
52
+ userId: target.id
53
+ });
54
+ return closeRecord({
55
+ endedBy,
56
+ options,
57
+ payload,
58
+ record,
59
+ req
60
+ });
61
+ };
62
+ //#endregion
63
+ export { closeAndRevoke, closeRecord };
64
+
65
+ //# sourceMappingURL=close.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"close.js","names":[],"sources":["../../src/session/close.ts"],"sourcesContent":["import type { CollectionSlug, Payload, PayloadRequest } from 'payload'\n\nimport { revokeSession } from '../auth/revoke'\nimport type { EndedBy, ImpersonationRecord, ResolvedOptions } from '../types'\nimport { relationOf } from './resolve'\n\nexport const closeRecord = async ({\n\tendedBy,\n\toptions,\n\tpayload,\n\trecord,\n\treq,\n}: {\n\tendedBy: EndedBy\n\toptions: ResolvedOptions\n\tpayload: Payload\n\trecord: ImpersonationRecord\n\treq?: PayloadRequest\n}): Promise<ImpersonationRecord> => {\n\tif (record.endedAt) {\n\t\treturn record\n\t}\n\n\tconst endedAt = new Date().toISOString()\n\tlet updated: ImpersonationRecord\n\ttry {\n\t\tupdated = (await payload.update({\n\t\t\tid: record.id,\n\t\t\tcollection: options.collectionSlug,\n\t\t\tdata: { endedAt, endedBy } as never,\n\t\t\tdepth: 0,\n\t\t\toverrideAccess: true,\n\t\t\treq,\n\t\t})) as unknown as ImpersonationRecord\n\t} catch {\n\t\t// Postgres CASCADE-deletes impersonation_sessions_rels when the\n\t\t// impersonator or target user is deleted. payload.update then fails\n\t\t// required-relationship validation. Close through the adapter instead.\n\t\tawait payload.db.updateOne({\n\t\t\tid: record.id,\n\t\t\tcollection: options.collectionSlug,\n\t\t\tdata: { endedAt, endedBy },\n\t\t\treq,\n\t\t\treturning: false,\n\t\t})\n\t\tupdated = { ...record, endedAt, endedBy }\n\t}\n\n\tawait options.onEnd?.({ endedBy, payload, record: updated, req })\n\treturn updated\n}\n\nexport const closeAndRevoke = async ({\n\tendedBy,\n\toptions,\n\tpayload,\n\trecord,\n\treq,\n}: {\n\tendedBy: EndedBy\n\toptions: ResolvedOptions\n\tpayload: Payload\n\trecord: ImpersonationRecord\n\treq?: PayloadRequest\n}): Promise<ImpersonationRecord> => {\n\tconst target = relationOf(record.target)\n\tif (target) {\n\t\tconst revoke = options.session.revoke ?? revokeSession\n\t\tawait revoke({\n\t\t\tcollection: target.collection as CollectionSlug,\n\t\t\tpayload,\n\t\t\treq,\n\t\t\tsid: record.targetSid,\n\t\t\tuserId: target.id,\n\t\t})\n\t}\n\treturn closeRecord({ endedBy, options, payload, record, req })\n}\n"],"mappings":";;;AAMA,MAAa,cAAc,OAAO,EACjC,SACA,SACA,SACA,QACA,UAOmC;CACnC,IAAI,OAAO,SACV,OAAO;CAGR,MAAM,2BAAU,IAAI,KAAK,GAAE,YAAY;CACvC,IAAI;CACJ,IAAI;EACH,UAAW,MAAM,QAAQ,OAAO;GAC/B,IAAI,OAAO;GACX,YAAY,QAAQ;GACpB,MAAM;IAAE;IAAS;GAAQ;GACzB,OAAO;GACP,gBAAgB;GAChB;EACD,CAAC;CACF,QAAQ;EAIP,MAAM,QAAQ,GAAG,UAAU;GAC1B,IAAI,OAAO;GACX,YAAY,QAAQ;GACpB,MAAM;IAAE;IAAS;GAAQ;GACzB;GACA,WAAW;EACZ,CAAC;EACD,UAAU;GAAE,GAAG;GAAQ;GAAS;EAAQ;CACzC;CAEA,MAAM,QAAQ,QAAQ;EAAE;EAAS;EAAS,QAAQ;EAAS;CAAI,CAAC;CAChE,OAAO;AACR;AAEA,MAAa,iBAAiB,OAAO,EACpC,SACA,SACA,SACA,QACA,UAOmC;CACnC,MAAM,SAAS,WAAW,OAAO,MAAM;CACvC,IAAI,QAEH,OADe,QAAQ,QAAQ,UAAU,eAC5B;EACZ,YAAY,OAAO;EACnB;EACA;EACA,KAAK,OAAO;EACZ,QAAQ,OAAO;CAChB,CAAC;CAEF,OAAO,YAAY;EAAE;EAAS;EAAS;EAAS;EAAQ;CAAI,CAAC;AAC9D"}
@@ -0,0 +1,11 @@
1
+ import { Payload } from "payload";
2
+
3
+ //#region src/session/closeStale.d.ts
4
+ /**
5
+ * Close open rows whose minted sid is gone, whose target is gone, or whose
6
+ * optional absolute cap has passed. Safe to run from a cron or `onInit`.
7
+ */
8
+ declare const closeStaleImpersonations: (payload: Payload) => Promise<number>;
9
+ //#endregion
10
+ export { closeStaleImpersonations };
11
+ //# sourceMappingURL=closeStale.d.ts.map
@@ -0,0 +1,65 @@
1
+ import { collectionBySlug } from "../ids.js";
2
+ import { isPastAbsoluteExpiry, relationOf } from "./resolve.js";
3
+ import { getRegistry } from "../plugin/registry.js";
4
+ import { closeAndRevoke } from "./close.js";
5
+ //#region src/session/closeStale.ts
6
+ const PAGE = 100;
7
+ /**
8
+ * Close open rows whose minted sid is gone, whose target is gone, or whose
9
+ * optional absolute cap has passed. Safe to run from a cron or `onInit`.
10
+ */
11
+ const closeStaleImpersonations = async (payload) => {
12
+ const options = getRegistry(payload.config);
13
+ if (!options) return 0;
14
+ let closed = 0;
15
+ let lastId;
16
+ for (;;) {
17
+ const open = await payload.find({
18
+ collection: options.collectionSlug,
19
+ depth: 0,
20
+ limit: PAGE,
21
+ overrideAccess: true,
22
+ sort: "id",
23
+ where: { and: [{ endedAt: { exists: false } }, ...lastId != null ? [{ id: { greater_than: lastId } }] : []] }
24
+ });
25
+ if (open.docs.length === 0) break;
26
+ for (const doc of open.docs) {
27
+ lastId = doc.id;
28
+ const reason = await staleReason(payload, doc);
29
+ if (!reason) continue;
30
+ await closeAndRevoke({
31
+ endedBy: reason,
32
+ options,
33
+ payload,
34
+ record: doc
35
+ });
36
+ closed += 1;
37
+ }
38
+ if (open.docs.length < PAGE) break;
39
+ }
40
+ return closed;
41
+ };
42
+ const staleReason = async (payload, record) => {
43
+ if (isPastAbsoluteExpiry(record)) return "expired";
44
+ const target = relationOf(record.target);
45
+ if (!target || !collectionBySlug(payload, target.collection)) return "targetGone";
46
+ try {
47
+ const user = await payload.db.findOne({
48
+ collection: target.collection,
49
+ where: { id: { equals: target.id } }
50
+ });
51
+ if (!user) return "targetGone";
52
+ if (!(user.sessions ?? []).some((session) => session.id === record.targetSid)) return "expired";
53
+ } catch (error) {
54
+ payload.logger.error({
55
+ err: error,
56
+ msg: "@10x-media/impersonation: closeStale skipped a row after a target lookup error"
57
+ });
58
+ return null;
59
+ }
60
+ return null;
61
+ };
62
+ //#endregion
63
+ export { closeStaleImpersonations };
64
+
65
+ //# sourceMappingURL=closeStale.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"closeStale.js","names":[],"sources":["../../src/session/closeStale.ts"],"sourcesContent":["import type { CollectionSlug, Payload } from 'payload'\n\nimport { collectionBySlug } from '../ids'\n\nimport { getRegistry } from '../plugin/registry'\nimport type { EndedBy, ImpersonationRecord } from '../types'\nimport { closeAndRevoke } from './close'\nimport { isPastAbsoluteExpiry, relationOf } from './resolve'\n\nconst PAGE = 100\n\n/**\n * Close open rows whose minted sid is gone, whose target is gone, or whose\n * optional absolute cap has passed. Safe to run from a cron or `onInit`.\n */\nexport const closeStaleImpersonations = async (payload: Payload): Promise<number> => {\n\tconst options = getRegistry(payload.config)\n\tif (!options) {\n\t\treturn 0\n\t}\n\n\tlet closed = 0\n\tlet lastId: number | string | undefined\n\n\tfor (;;) {\n\t\tconst open = await payload.find({\n\t\t\tcollection: options.collectionSlug,\n\t\t\tdepth: 0,\n\t\t\tlimit: PAGE,\n\t\t\toverrideAccess: true,\n\t\t\tsort: 'id',\n\t\t\twhere: {\n\t\t\t\tand: [\n\t\t\t\t\t{ endedAt: { exists: false } },\n\t\t\t\t\t...(lastId != null ? [{ id: { greater_than: lastId } }] : []),\n\t\t\t\t],\n\t\t\t},\n\t\t})\n\n\t\tif (open.docs.length === 0) {\n\t\t\tbreak\n\t\t}\n\n\t\tfor (const doc of open.docs as unknown as ImpersonationRecord[]) {\n\t\t\tlastId = doc.id\n\t\t\tconst reason = await staleReason(payload, doc)\n\t\t\tif (!reason) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tawait closeAndRevoke({ endedBy: reason, options, payload, record: doc })\n\t\t\tclosed += 1\n\t\t}\n\n\t\tif (open.docs.length < PAGE) {\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn closed\n}\n\nconst staleReason = async (\n\tpayload: Payload,\n\trecord: ImpersonationRecord\n): Promise<EndedBy | null> => {\n\tif (isPastAbsoluteExpiry(record)) {\n\t\treturn 'expired'\n\t}\n\n\tconst target = relationOf(record.target)\n\tif (!target || !collectionBySlug(payload, target.collection)) {\n\t\treturn 'targetGone'\n\t}\n\n\ttry {\n\t\tconst user = (await payload.db.findOne({\n\t\t\tcollection: target.collection as CollectionSlug,\n\t\t\twhere: { id: { equals: target.id } },\n\t\t})) as { sessions?: { id: string }[] } | null\n\n\t\tif (!user) {\n\t\t\treturn 'targetGone'\n\t\t}\n\n\t\tif (!(user.sessions ?? []).some((session) => session.id === record.targetSid)) {\n\t\t\treturn 'expired'\n\t\t}\n\t} catch (error) {\n\t\tpayload.logger.error({\n\t\t\terr: error,\n\t\t\tmsg: '@10x-media/impersonation: closeStale skipped a row after a target lookup error',\n\t\t})\n\t\treturn null\n\t}\n\n\treturn null\n}\n"],"mappings":";;;;;AASA,MAAM,OAAO;;;;;AAMb,MAAa,2BAA2B,OAAO,YAAsC;CACpF,MAAM,UAAU,YAAY,QAAQ,MAAM;CAC1C,IAAI,CAAC,SACJ,OAAO;CAGR,IAAI,SAAS;CACb,IAAI;CAEJ,SAAS;EACR,MAAM,OAAO,MAAM,QAAQ,KAAK;GAC/B,YAAY,QAAQ;GACpB,OAAO;GACP,OAAO;GACP,gBAAgB;GAChB,MAAM;GACN,OAAO,EACN,KAAK,CACJ,EAAE,SAAS,EAAE,QAAQ,MAAM,EAAE,GAC7B,GAAI,UAAU,OAAO,CAAC,EAAE,IAAI,EAAE,cAAc,OAAO,EAAE,CAAC,IAAI,CAAC,CAC5D,EACD;EACD,CAAC;EAED,IAAI,KAAK,KAAK,WAAW,GACxB;EAGD,KAAK,MAAM,OAAO,KAAK,MAA0C;GAChE,SAAS,IAAI;GACb,MAAM,SAAS,MAAM,YAAY,SAAS,GAAG;GAC7C,IAAI,CAAC,QACJ;GAED,MAAM,eAAe;IAAE,SAAS;IAAQ;IAAS;IAAS,QAAQ;GAAI,CAAC;GACvE,UAAU;EACX;EAEA,IAAI,KAAK,KAAK,SAAS,MACtB;CAEF;CAEA,OAAO;AACR;AAEA,MAAM,cAAc,OACnB,SACA,WAC6B;CAC7B,IAAI,qBAAqB,MAAM,GAC9B,OAAO;CAGR,MAAM,SAAS,WAAW,OAAO,MAAM;CACvC,IAAI,CAAC,UAAU,CAAC,iBAAiB,SAAS,OAAO,UAAU,GAC1D,OAAO;CAGR,IAAI;EACH,MAAM,OAAQ,MAAM,QAAQ,GAAG,QAAQ;GACtC,YAAY,OAAO;GACnB,OAAO,EAAE,IAAI,EAAE,QAAQ,OAAO,GAAG,EAAE;EACpC,CAAC;EAED,IAAI,CAAC,MACJ,OAAO;EAGR,IAAI,EAAE,KAAK,YAAY,CAAC,GAAG,MAAM,YAAY,QAAQ,OAAO,OAAO,SAAS,GAC3E,OAAO;CAET,SAAS,OAAO;EACf,QAAQ,OAAO,MAAM;GACpB,KAAK;GACL,KAAK;EACN,CAAC;EACD,OAAO;CACR;CAEA,OAAO;AACR"}
@@ -0,0 +1,32 @@
1
+ import { collectionBySlug } from "../ids.js";
2
+ import { relationOf } from "./resolve.js";
3
+ //#region src/session/locks.ts
4
+ const LOCKS = "payload-locked-documents";
5
+ const releaseLocks = async ({ payload, record, req }) => {
6
+ if (!collectionBySlug(payload, LOCKS)) return;
7
+ const target = relationOf(record.target);
8
+ if (!target) return;
9
+ const endedAt = record.endedAt ?? (/* @__PURE__ */ new Date()).toISOString();
10
+ try {
11
+ await payload.delete({
12
+ collection: LOCKS,
13
+ overrideAccess: true,
14
+ req,
15
+ where: { and: [
16
+ { "user.relationTo": { equals: target.collection } },
17
+ { "user.value": { equals: target.id } },
18
+ { createdAt: { greater_than_equal: record.startedAt } },
19
+ { createdAt: { less_than_equal: endedAt } }
20
+ ] }
21
+ });
22
+ } catch (error) {
23
+ payload.logger.error({
24
+ err: error,
25
+ msg: "@10x-media/impersonation: releaseLocks failed"
26
+ });
27
+ }
28
+ };
29
+ //#endregion
30
+ export { releaseLocks };
31
+
32
+ //# sourceMappingURL=locks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locks.js","names":[],"sources":["../../src/session/locks.ts"],"sourcesContent":["import type { Payload, PayloadRequest } from 'payload'\n\nimport { collectionBySlug } from '../ids'\nimport type { ImpersonationRecord } from '../types'\nimport { relationOf } from './resolve'\n\nconst LOCKS = 'payload-locked-documents'\n\nexport const releaseLocks = async ({\n\tpayload,\n\trecord,\n\treq,\n}: {\n\tpayload: Payload\n\trecord: ImpersonationRecord\n\treq?: PayloadRequest\n}): Promise<void> => {\n\tif (!collectionBySlug(payload, LOCKS)) {\n\t\treturn\n\t}\n\n\tconst target = relationOf(record.target)\n\tif (!target) {\n\t\treturn\n\t}\n\n\tconst endedAt = record.endedAt ?? new Date().toISOString()\n\n\ttry {\n\t\tawait payload.delete({\n\t\t\tcollection: LOCKS,\n\t\t\toverrideAccess: true,\n\t\t\treq,\n\t\t\twhere: {\n\t\t\t\tand: [\n\t\t\t\t\t{ 'user.relationTo': { equals: target.collection } },\n\t\t\t\t\t{ 'user.value': { equals: target.id } },\n\t\t\t\t\t{ createdAt: { greater_than_equal: record.startedAt } },\n\t\t\t\t\t{ createdAt: { less_than_equal: endedAt } },\n\t\t\t\t],\n\t\t\t},\n\t\t})\n\t} catch (error) {\n\t\tpayload.logger.error({\n\t\t\terr: error,\n\t\t\tmsg: '@10x-media/impersonation: releaseLocks failed',\n\t\t})\n\t}\n}\n"],"mappings":";;;AAMA,MAAM,QAAQ;AAEd,MAAa,eAAe,OAAO,EAClC,SACA,QACA,UAKoB;CACpB,IAAI,CAAC,iBAAiB,SAAS,KAAK,GACnC;CAGD,MAAM,SAAS,WAAW,OAAO,MAAM;CACvC,IAAI,CAAC,QACJ;CAGD,MAAM,UAAU,OAAO,4BAAW,IAAI,KAAK,GAAE,YAAY;CAEzD,IAAI;EACH,MAAM,QAAQ,OAAO;GACpB,YAAY;GACZ,gBAAgB;GAChB;GACA,OAAO,EACN,KAAK;IACJ,EAAE,mBAAmB,EAAE,QAAQ,OAAO,WAAW,EAAE;IACnD,EAAE,cAAc,EAAE,QAAQ,OAAO,GAAG,EAAE;IACtC,EAAE,WAAW,EAAE,oBAAoB,OAAO,UAAU,EAAE;IACtD,EAAE,WAAW,EAAE,iBAAiB,QAAQ,EAAE;GAC3C,EACD;EACD,CAAC;CACF,SAAS,OAAO;EACf,QAAQ,OAAO,MAAM;GACpB,KAAK;GACL,KAAK;EACN,CAAC;CACF;AACD"}
@@ -0,0 +1,91 @@
1
+ import { idsEqual } from "../ids.js";
2
+ import { parseCookies } from "payload/shared";
3
+ import { cache } from "react";
4
+ //#region src/session/resolve.ts
5
+ const openByTargetSid = (sid) => ({ and: [{ endedAt: { exists: false } }, { targetSid: { equals: sid } }] });
6
+ const openByImpersonatorSid = (sid) => ({ and: [{ endedAt: { exists: false } }, { impersonatorSid: { equals: sid } }] });
7
+ const findOpenWhere = async ({ options, payload, req, where }) => {
8
+ try {
9
+ return (await payload.find({
10
+ collection: options.collectionSlug,
11
+ depth: 0,
12
+ limit: 1,
13
+ overrideAccess: true,
14
+ pagination: false,
15
+ req,
16
+ where
17
+ })).docs[0] ?? null;
18
+ } catch {
19
+ return (await payload.db.find({
20
+ collection: options.collectionSlug,
21
+ limit: 1,
22
+ req,
23
+ where
24
+ })).docs[0] ?? null;
25
+ }
26
+ };
27
+ const findOpenByTargetSid = async ({ options, payload, req, sid }) => findOpenWhere({
28
+ options,
29
+ payload,
30
+ req,
31
+ where: openByTargetSid(sid)
32
+ });
33
+ const findOpenByImpersonatorSid = async ({ options, payload, req, sid }) => findOpenWhere({
34
+ options,
35
+ payload,
36
+ req,
37
+ where: openByImpersonatorSid(sid)
38
+ });
39
+ const openBySid = (sid) => ({ and: [{ endedAt: { exists: false } }, { or: [{ targetSid: { equals: sid } }, { impersonatorSid: { equals: sid } }] }] });
40
+ const findOpenBySid = async (args) => findOpenWhere({
41
+ ...args,
42
+ where: openBySid(args.sid)
43
+ });
44
+ /**
45
+ * One lookup per sid for a single admin render: the auth strategy, the provider,
46
+ * and the header action share it. Outside a React render `cache` is a pass-through.
47
+ */
48
+ const cachedFindOpenBySid = cache((sid, options, payload) => findOpenBySid({
49
+ options,
50
+ payload,
51
+ sid
52
+ }));
53
+ const impersonationSide = (row, sid) => row.targetSid === sid ? "target" : "impersonator";
54
+ const rowMatchesSid = (row, sid) => row.targetSid === sid || row.impersonatorSid === sid;
55
+ /** Hint cookie is a lookup aid. The open row must still match `sid`. */
56
+ const resolveCurrent = async ({ headers, options, payload, req, sid }) => {
57
+ const hint = parseCookies(headers).get(options.hintCookieName);
58
+ if (hint) try {
59
+ const row = await payload.findByID({
60
+ id: hint,
61
+ collection: options.collectionSlug,
62
+ depth: 0,
63
+ overrideAccess: true,
64
+ req
65
+ });
66
+ if (row && !row.endedAt && rowMatchesSid(row, sid)) return row;
67
+ } catch {}
68
+ return findOpenBySid({
69
+ options,
70
+ payload,
71
+ req,
72
+ sid
73
+ });
74
+ };
75
+ const relationOf = (value) => {
76
+ if (value && typeof value === "object" && "relationTo" in value) return {
77
+ collection: value.relationTo,
78
+ id: value.value
79
+ };
80
+ return null;
81
+ };
82
+ const isPastAbsoluteExpiry = (row, now = Date.now()) => {
83
+ if (!row.absoluteExpiresAt) return false;
84
+ const expires = new Date(row.absoluteExpiresAt).getTime();
85
+ return Number.isFinite(expires) && expires <= now;
86
+ };
87
+ const sameUser = (left, rightCollection, rightId) => Boolean(left.collection) && left.collection === rightCollection && idsEqual(left.id, rightId);
88
+ //#endregion
89
+ export { cachedFindOpenBySid, findOpenByImpersonatorSid, findOpenBySid, findOpenByTargetSid, impersonationSide, isPastAbsoluteExpiry, openByImpersonatorSid, relationOf, resolveCurrent, sameUser };
90
+
91
+ //# sourceMappingURL=resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.js","names":[],"sources":["../../src/session/resolve.ts"],"sourcesContent":["import type { Payload, PayloadRequest, Where } from 'payload'\nimport { parseCookies } from 'payload/shared'\nimport { cache } from 'react'\nimport { idsEqual } from '../ids'\nimport type { ImpersonationRecord, ResolvedOptions } from '../types'\n\nexport const openByTargetSid = (sid: string): Where => ({\n\tand: [{ endedAt: { exists: false } }, { targetSid: { equals: sid } }],\n})\n\nexport const openByImpersonatorSid = (sid: string): Where => ({\n\tand: [{ endedAt: { exists: false } }, { impersonatorSid: { equals: sid } }],\n})\n\nconst findOpenWhere = async ({\n\toptions,\n\tpayload,\n\treq,\n\twhere,\n}: {\n\toptions: ResolvedOptions\n\tpayload: Payload\n\treq?: PayloadRequest\n\twhere: Where\n}): Promise<ImpersonationRecord | null> => {\n\ttry {\n\t\tconst found = await payload.find({\n\t\t\tcollection: options.collectionSlug,\n\t\t\tdepth: 0,\n\t\t\tlimit: 1,\n\t\t\toverrideAccess: true,\n\t\t\tpagination: false,\n\t\t\treq,\n\t\t\twhere,\n\t\t})\n\t\treturn (found.docs[0] as unknown as ImpersonationRecord | undefined) ?? null\n\t} catch {\n\t\t// Postgres CASCADE on impersonation_sessions_rels can make payload.find\n\t\t// reject a row whose required impersonator or target relationship is gone.\n\t\tconst found = await payload.db.find({\n\t\t\tcollection: options.collectionSlug,\n\t\t\tlimit: 1,\n\t\t\treq,\n\t\t\twhere,\n\t\t})\n\t\treturn (found.docs[0] as unknown as ImpersonationRecord | undefined) ?? null\n\t}\n}\n\nexport const findOpenByTargetSid = async ({\n\toptions,\n\tpayload,\n\treq,\n\tsid,\n}: {\n\toptions: ResolvedOptions\n\tpayload: Payload\n\treq?: PayloadRequest\n\tsid: string\n}): Promise<ImpersonationRecord | null> =>\n\tfindOpenWhere({ options, payload, req, where: openByTargetSid(sid) })\n\nexport const findOpenByImpersonatorSid = async ({\n\toptions,\n\tpayload,\n\treq,\n\tsid,\n}: {\n\toptions: ResolvedOptions\n\tpayload: Payload\n\treq?: PayloadRequest\n\tsid: string\n}): Promise<ImpersonationRecord | null> =>\n\tfindOpenWhere({ options, payload, req, where: openByImpersonatorSid(sid) })\n\nexport const openBySid = (sid: string): Where => ({\n\tand: [\n\t\t{ endedAt: { exists: false } },\n\t\t{ or: [{ targetSid: { equals: sid } }, { impersonatorSid: { equals: sid } }] },\n\t],\n})\n\nexport const findOpenBySid = async (args: {\n\toptions: ResolvedOptions\n\tpayload: Payload\n\treq?: PayloadRequest\n\tsid: string\n}): Promise<ImpersonationRecord | null> => findOpenWhere({ ...args, where: openBySid(args.sid) })\n\n/**\n * One lookup per sid for a single admin render: the auth strategy, the provider,\n * and the header action share it. Outside a React render `cache` is a pass-through.\n */\nexport const cachedFindOpenBySid = cache(\n\t(sid: string, options: ResolvedOptions, payload: Payload): Promise<ImpersonationRecord | null> =>\n\t\tfindOpenBySid({ options, payload, sid })\n)\n\nexport const impersonationSide = (\n\trow: ImpersonationRecord,\n\tsid: string\n): 'impersonator' | 'target' => (row.targetSid === sid ? 'target' : 'impersonator')\n\nconst rowMatchesSid = (row: ImpersonationRecord, sid: string): boolean =>\n\trow.targetSid === sid || row.impersonatorSid === sid\n\n/** Hint cookie is a lookup aid. The open row must still match `sid`. */\nexport const resolveCurrent = async ({\n\theaders,\n\toptions,\n\tpayload,\n\treq,\n\tsid,\n}: {\n\theaders: Headers\n\toptions: ResolvedOptions\n\tpayload: Payload\n\treq?: PayloadRequest\n\tsid: string\n}): Promise<ImpersonationRecord | null> => {\n\tconst hint = parseCookies(headers).get(options.hintCookieName)\n\tif (hint) {\n\t\ttry {\n\t\t\tconst row = (await payload.findByID({\n\t\t\t\tid: hint,\n\t\t\t\tcollection: options.collectionSlug,\n\t\t\t\tdepth: 0,\n\t\t\t\toverrideAccess: true,\n\t\t\t\treq,\n\t\t\t})) as unknown as ImpersonationRecord\n\t\t\tif (row && !row.endedAt && rowMatchesSid(row, sid)) {\n\t\t\t\treturn row\n\t\t\t}\n\t\t} catch {\n\t\t\t// Forged or stale hint: fall through to the sid lookup.\n\t\t}\n\t}\n\n\treturn findOpenBySid({ options, payload, req, sid })\n}\n\nexport const relationOf = (\n\tvalue: ImpersonationRecord['impersonator']\n): { collection: string; id: number | string } | null => {\n\tif (value && typeof value === 'object' && 'relationTo' in value) {\n\t\treturn { collection: value.relationTo, id: value.value }\n\t}\n\treturn null\n}\n\nexport const isPastAbsoluteExpiry = (\n\trow: { absoluteExpiresAt?: null | string },\n\tnow = Date.now()\n): boolean => {\n\tif (!row.absoluteExpiresAt) {\n\t\treturn false\n\t}\n\tconst expires = new Date(row.absoluteExpiresAt).getTime()\n\treturn Number.isFinite(expires) && expires <= now\n}\n\nexport const sameUser = (\n\tleft: { collection?: string; id?: number | string },\n\trightCollection: string,\n\trightId: number | string\n): boolean =>\n\tBoolean(left.collection) && left.collection === rightCollection && idsEqual(left.id, rightId)\n"],"mappings":";;;;AAMA,MAAa,mBAAmB,SAAwB,EACvD,KAAK,CAAC,EAAE,SAAS,EAAE,QAAQ,MAAM,EAAE,GAAG,EAAE,WAAW,EAAE,QAAQ,IAAI,EAAE,CAAC,EACrE;AAEA,MAAa,yBAAyB,SAAwB,EAC7D,KAAK,CAAC,EAAE,SAAS,EAAE,QAAQ,MAAM,EAAE,GAAG,EAAE,iBAAiB,EAAE,QAAQ,IAAI,EAAE,CAAC,EAC3E;AAEA,MAAM,gBAAgB,OAAO,EAC5B,SACA,SACA,KACA,YAM0C;CAC1C,IAAI;EAUH,QAAQ,MATY,QAAQ,KAAK;GAChC,YAAY,QAAQ;GACpB,OAAO;GACP,OAAO;GACP,gBAAgB;GAChB,YAAY;GACZ;GACA;EACD,CAAC,GACa,KAAK,MAAqD;CACzE,QAAQ;EASP,QAAQ,MANY,QAAQ,GAAG,KAAK;GACnC,YAAY,QAAQ;GACpB,OAAO;GACP;GACA;EACD,CAAC,GACa,KAAK,MAAqD;CACzE;AACD;AAEA,MAAa,sBAAsB,OAAO,EACzC,SACA,SACA,KACA,UAOA,cAAc;CAAE;CAAS;CAAS;CAAK,OAAO,gBAAgB,GAAG;AAAE,CAAC;AAErE,MAAa,4BAA4B,OAAO,EAC/C,SACA,SACA,KACA,UAOA,cAAc;CAAE;CAAS;CAAS;CAAK,OAAO,sBAAsB,GAAG;AAAE,CAAC;AAE3E,MAAa,aAAa,SAAwB,EACjD,KAAK,CACJ,EAAE,SAAS,EAAE,QAAQ,MAAM,EAAE,GAC7B,EAAE,IAAI,CAAC,EAAE,WAAW,EAAE,QAAQ,IAAI,EAAE,GAAG,EAAE,iBAAiB,EAAE,QAAQ,IAAI,EAAE,CAAC,EAAE,CAC9E,EACD;AAEA,MAAa,gBAAgB,OAAO,SAKO,cAAc;CAAE,GAAG;CAAM,OAAO,UAAU,KAAK,GAAG;AAAE,CAAC;;;;;AAMhG,MAAa,sBAAsB,OACjC,KAAa,SAA0B,YACvC,cAAc;CAAE;CAAS;CAAS;AAAI,CAAC,CACzC;AAEA,MAAa,qBACZ,KACA,QACgC,IAAI,cAAc,MAAM,WAAW;AAEpE,MAAM,iBAAiB,KAA0B,QAChD,IAAI,cAAc,OAAO,IAAI,oBAAoB;;AAGlD,MAAa,iBAAiB,OAAO,EACpC,SACA,SACA,SACA,KACA,UAO0C;CAC1C,MAAM,OAAO,aAAa,OAAO,EAAE,IAAI,QAAQ,cAAc;CAC7D,IAAI,MACH,IAAI;EACH,MAAM,MAAO,MAAM,QAAQ,SAAS;GACnC,IAAI;GACJ,YAAY,QAAQ;GACpB,OAAO;GACP,gBAAgB;GAChB;EACD,CAAC;EACD,IAAI,OAAO,CAAC,IAAI,WAAW,cAAc,KAAK,GAAG,GAChD,OAAO;CAET,QAAQ,CAER;CAGD,OAAO,cAAc;EAAE;EAAS;EAAS;EAAK;CAAI,CAAC;AACpD;AAEA,MAAa,cACZ,UACwD;CACxD,IAAI,SAAS,OAAO,UAAU,YAAY,gBAAgB,OACzD,OAAO;EAAE,YAAY,MAAM;EAAY,IAAI,MAAM;CAAM;CAExD,OAAO;AACR;AAEA,MAAa,wBACZ,KACA,MAAM,KAAK,IAAI,MACF;CACb,IAAI,CAAC,IAAI,mBACR,OAAO;CAER,MAAM,UAAU,IAAI,KAAK,IAAI,iBAAiB,EAAE,QAAQ;CACxD,OAAO,OAAO,SAAS,OAAO,KAAK,WAAW;AAC/C;AAEA,MAAa,YACZ,MACA,iBACA,YAEA,QAAQ,KAAK,UAAU,KAAK,KAAK,eAAe,mBAAmB,SAAS,KAAK,IAAI,OAAO"}
@@ -0,0 +1,52 @@
1
+ import { keys } from "./keys.js";
2
+ //#region src/translations/ar.ts
3
+ const ar = {
4
+ [keys.actingAs]: "يتصرف باسم {{name}}",
5
+ [keys.collectionPlural]: "جلسات انتحال الهوية",
6
+ [keys.collectionSingular]: "جلسة انتحال الهوية",
7
+ [keys.confirm]: "انتحال الهوية",
8
+ [keys.confirmTitle]: "انتحال هوية هذا المستخدم؟",
9
+ [keys.collapseBar]: "طي شريط انتحال الهوية",
10
+ [keys.expandBar]: "توسيع شريط انتحال الهوية",
11
+ [keys.endSession]: "إنهاء الجلسة",
12
+ [keys.endSessionBody]: "سينتهي انتحال الهوية. يعود الهدف إلى حسابه.",
13
+ [keys.errorAlreadyImpersonating]: "هناك انتحال هوية جارٍ",
14
+ [keys.errorFailed]: "فشل انتحال الهوية",
15
+ [keys.errorForbidden]: "غير مسموح",
16
+ [keys.errorImpersonatorGone]: "حسابك لم يعد متاحا",
17
+ [keys.errorImpersonatorSessionExpired]: "انتهت جلستك الأصلية. سجل الدخول مرة أخرى.",
18
+ [keys.errorInvalidBody]: "طلب غير صالح",
19
+ [keys.errorNotImpersonating]: "لا يوجد انتحال هوية نشط",
20
+ [keys.errorOrigin]: "رفضه فحص المصدر",
21
+ [keys.errorReasonRequired]: "السبب مطلوب",
22
+ [keys.errorSelfTarget]: "لا يمكن انتحال هويتك أنت",
23
+ [keys.errorTargetNotFound]: "المستخدم غير موجود",
24
+ [keys.errorTargetTrashed]: "المستخدم في سلة المهملات",
25
+ [keys.errorTargetUnverified]: "المستخدم غير موثق",
26
+ [keys.errorUnsupportedAuth]: "طريقة المصادقة هذه لا تدعم انتحال الهوية",
27
+ [keys.errorUnsupportedCollection]: "لا يمكن انتحال هوية هذه المجموعة",
28
+ [keys.fieldEndedAt]: "انتهت",
29
+ [keys.fieldEndedBy]: "أنهيت بواسطة",
30
+ [keys.fieldImpersonator]: "المنتحل",
31
+ [keys.fieldMode]: "الوضع",
32
+ [keys.fieldReason]: "السبب",
33
+ [keys.fieldStartedAt]: "بدأت",
34
+ [keys.fieldTarget]: "الهدف",
35
+ [keys.noResults]: "لا توجد نتائج",
36
+ [keys.openDocument]: "فتح المستند",
37
+ [keys.openDrawer]: "فتح في الدرج",
38
+ [keys.openWebsite]: "فتح الموقع",
39
+ [keys.pluginName]: "انتحال الهوية",
40
+ [keys.reasonLabel]: "السبب",
41
+ [keys.reasonPlaceholder]: "سبب اختياري",
42
+ [keys.returnTo]: "العودة إلى {{name}}",
43
+ [keys.searchByName]: "البحث بالاسم",
44
+ [keys.sessionActiveOnSite]: "جلسة {{name}} نشطة على الموقع",
45
+ [keys.sessionEndsAt]: "تنتهي الجلسة عند {{time}}",
46
+ [keys.sessionEndsIn]: "تنتهي الجلسة خلال {{countdown}}",
47
+ [keys.switchToUser]: "انتحال الهوية"
48
+ };
49
+ //#endregion
50
+ export { ar };
51
+
52
+ //# sourceMappingURL=ar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ar.js","names":[],"sources":["../../src/translations/ar.ts"],"sourcesContent":["import { keys, type TranslationKey } from './keys'\n\nexport const ar: Record<TranslationKey, string> = {\n\t[keys.actingAs]: 'يتصرف باسم {{name}}',\n\t[keys.collectionPlural]: 'جلسات انتحال الهوية',\n\t[keys.collectionSingular]: 'جلسة انتحال الهوية',\n\t[keys.confirm]: 'انتحال الهوية',\n\t[keys.confirmTitle]: 'انتحال هوية هذا المستخدم؟',\n\t[keys.collapseBar]: 'طي شريط انتحال الهوية',\n\t[keys.expandBar]: 'توسيع شريط انتحال الهوية',\n\t[keys.endSession]: 'إنهاء الجلسة',\n\t[keys.endSessionBody]: 'سينتهي انتحال الهوية. يعود الهدف إلى حسابه.',\n\t[keys.errorAlreadyImpersonating]: 'هناك انتحال هوية جارٍ',\n\t[keys.errorFailed]: 'فشل انتحال الهوية',\n\t[keys.errorForbidden]: 'غير مسموح',\n\t[keys.errorImpersonatorGone]: 'حسابك لم يعد متاحا',\n\t[keys.errorImpersonatorSessionExpired]: 'انتهت جلستك الأصلية. سجل الدخول مرة أخرى.',\n\t[keys.errorInvalidBody]: 'طلب غير صالح',\n\t[keys.errorNotImpersonating]: 'لا يوجد انتحال هوية نشط',\n\t[keys.errorOrigin]: 'رفضه فحص المصدر',\n\t[keys.errorReasonRequired]: 'السبب مطلوب',\n\t[keys.errorSelfTarget]: 'لا يمكن انتحال هويتك أنت',\n\t[keys.errorTargetNotFound]: 'المستخدم غير موجود',\n\t[keys.errorTargetTrashed]: 'المستخدم في سلة المهملات',\n\t[keys.errorTargetUnverified]: 'المستخدم غير موثق',\n\t[keys.errorUnsupportedAuth]: 'طريقة المصادقة هذه لا تدعم انتحال الهوية',\n\t[keys.errorUnsupportedCollection]: 'لا يمكن انتحال هوية هذه المجموعة',\n\t[keys.fieldEndedAt]: 'انتهت',\n\t[keys.fieldEndedBy]: 'أنهيت بواسطة',\n\t[keys.fieldImpersonator]: 'المنتحل',\n\t[keys.fieldMode]: 'الوضع',\n\t[keys.fieldReason]: 'السبب',\n\t[keys.fieldStartedAt]: 'بدأت',\n\t[keys.fieldTarget]: 'الهدف',\n\t[keys.noResults]: 'لا توجد نتائج',\n\t[keys.openDocument]: 'فتح المستند',\n\t[keys.openDrawer]: 'فتح في الدرج',\n\t[keys.openWebsite]: 'فتح الموقع',\n\t[keys.pluginName]: 'انتحال الهوية',\n\t[keys.reasonLabel]: 'السبب',\n\t[keys.reasonPlaceholder]: 'سبب اختياري',\n\t[keys.returnTo]: 'العودة إلى {{name}}',\n\t[keys.searchByName]: 'البحث بالاسم',\n\t[keys.sessionActiveOnSite]: 'جلسة {{name}} نشطة على الموقع',\n\t[keys.sessionEndsAt]: 'تنتهي الجلسة عند {{time}}',\n\t[keys.sessionEndsIn]: 'تنتهي الجلسة خلال {{countdown}}',\n\t[keys.switchToUser]: 'انتحال الهوية',\n}\n"],"mappings":";;AAEA,MAAa,KAAqC;EAChD,KAAK,WAAW;EAChB,KAAK,mBAAmB;EACxB,KAAK,qBAAqB;EAC1B,KAAK,UAAU;EACf,KAAK,eAAe;EACpB,KAAK,cAAc;EACnB,KAAK,YAAY;EACjB,KAAK,aAAa;EAClB,KAAK,iBAAiB;EACtB,KAAK,4BAA4B;EACjC,KAAK,cAAc;EACnB,KAAK,iBAAiB;EACtB,KAAK,wBAAwB;EAC7B,KAAK,kCAAkC;EACvC,KAAK,mBAAmB;EACxB,KAAK,wBAAwB;EAC7B,KAAK,cAAc;EACnB,KAAK,sBAAsB;EAC3B,KAAK,kBAAkB;EACvB,KAAK,sBAAsB;EAC3B,KAAK,qBAAqB;EAC1B,KAAK,wBAAwB;EAC7B,KAAK,uBAAuB;EAC5B,KAAK,6BAA6B;EAClC,KAAK,eAAe;EACpB,KAAK,eAAe;EACpB,KAAK,oBAAoB;EACzB,KAAK,YAAY;EACjB,KAAK,cAAc;EACnB,KAAK,iBAAiB;EACtB,KAAK,cAAc;EACnB,KAAK,YAAY;EACjB,KAAK,eAAe;EACpB,KAAK,aAAa;EAClB,KAAK,cAAc;EACnB,KAAK,aAAa;EAClB,KAAK,cAAc;EACnB,KAAK,oBAAoB;EACzB,KAAK,WAAW;EAChB,KAAK,eAAe;EACpB,KAAK,sBAAsB;EAC3B,KAAK,gBAAgB;EACrB,KAAK,gBAAgB;EACrB,KAAK,eAAe;AACtB"}