@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.
- package/CHANGELOG.md +31 -0
- package/LICENSE +21 -0
- package/README.md +49 -0
- package/dist/access/filterTargets.js +51 -0
- package/dist/access/filterTargets.js.map +1 -0
- package/dist/auth/cookies.js +49 -0
- package/dist/auth/cookies.js.map +1 -0
- package/dist/auth/expiryCookies.js +59 -0
- package/dist/auth/expiryCookies.js.map +1 -0
- package/dist/auth/issue.js +126 -0
- package/dist/auth/issue.js.map +1 -0
- package/dist/auth/mode.js +69 -0
- package/dist/auth/mode.js.map +1 -0
- package/dist/auth/origin.js +55 -0
- package/dist/auth/origin.js.map +1 -0
- package/dist/auth/revoke.js +24 -0
- package/dist/auth/revoke.js.map +1 -0
- package/dist/auth/tenantCookie.js +74 -0
- package/dist/auth/tenantCookie.js.map +1 -0
- package/dist/endpoints/codes.d.ts +6 -0
- package/dist/endpoints/current.js +66 -0
- package/dist/endpoints/current.js.map +1 -0
- package/dist/endpoints/exit.js +242 -0
- package/dist/endpoints/exit.js.map +1 -0
- package/dist/endpoints/shared.js +76 -0
- package/dist/endpoints/shared.js.map +1 -0
- package/dist/endpoints/start.js +332 -0
- package/dist/endpoints/start.js.map +1 -0
- package/dist/endpoints/terminate.js +73 -0
- package/dist/endpoints/terminate.js.map +1 -0
- package/dist/exports/client.d.ts +9 -0
- package/dist/exports/client.js +10 -0
- package/dist/exports/i18n.d.ts +3 -0
- package/dist/exports/i18n.js +3 -0
- package/dist/exports/rsc.d.ts +4 -0
- package/dist/exports/rsc.js +4 -0
- package/dist/exports/types.d.ts +3 -0
- package/dist/exports/types.js +1 -0
- package/dist/getImpersonation.d.ts +49 -0
- package/dist/getImpersonation.js +78 -0
- package/dist/getImpersonation.js.map +1 -0
- package/dist/ids.js +27 -0
- package/dist/ids.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +55 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin/assertTargets.js +23 -0
- package/dist/plugin/assertTargets.js.map +1 -0
- package/dist/plugin/constants.d.ts +6 -0
- package/dist/plugin/constants.js +20 -0
- package/dist/plugin/constants.js.map +1 -0
- package/dist/plugin/decorateAuth.js +99 -0
- package/dist/plugin/decorateAuth.js.map +1 -0
- package/dist/plugin/lookup.js +23 -0
- package/dist/plugin/lookup.js.map +1 -0
- package/dist/plugin/normalizeOptions.js +83 -0
- package/dist/plugin/normalizeOptions.js.map +1 -0
- package/dist/plugin/registerAdmin.js +49 -0
- package/dist/plugin/registerAdmin.js.map +1 -0
- package/dist/plugin/registerCollection.js +183 -0
- package/dist/plugin/registerCollection.js.map +1 -0
- package/dist/plugin/registerEndpoints.js +19 -0
- package/dist/plugin/registerEndpoints.js.map +1 -0
- package/dist/plugin/registerHooks.js +59 -0
- package/dist/plugin/registerHooks.js.map +1 -0
- package/dist/plugin/registerRetention.js +57 -0
- package/dist/plugin/registerRetention.js.map +1 -0
- package/dist/plugin/registerTranslations.js +19 -0
- package/dist/plugin/registerTranslations.js.map +1 -0
- package/dist/plugin/registry.js +11 -0
- package/dist/plugin/registry.js.map +1 -0
- package/dist/plugin/startable.js +21 -0
- package/dist/plugin/startable.js.map +1 -0
- package/dist/session/close.js +65 -0
- package/dist/session/close.js.map +1 -0
- package/dist/session/closeStale.d.ts +11 -0
- package/dist/session/closeStale.js +65 -0
- package/dist/session/closeStale.js.map +1 -0
- package/dist/session/locks.js +32 -0
- package/dist/session/locks.js.map +1 -0
- package/dist/session/resolve.js +91 -0
- package/dist/session/resolve.js.map +1 -0
- package/dist/translations/ar.js +52 -0
- package/dist/translations/ar.js.map +1 -0
- package/dist/translations/de.js +52 -0
- package/dist/translations/de.js.map +1 -0
- package/dist/translations/en.js +52 -0
- package/dist/translations/en.js.map +1 -0
- package/dist/translations/es.js +52 -0
- package/dist/translations/es.js.map +1 -0
- package/dist/translations/fr.js +52 -0
- package/dist/translations/fr.js.map +1 -0
- package/dist/translations/id.js +52 -0
- package/dist/translations/id.js.map +1 -0
- package/dist/translations/index.d.ts +24 -0
- package/dist/translations/index.js +49 -0
- package/dist/translations/index.js.map +1 -0
- package/dist/translations/keys.d.ts +56 -0
- package/dist/translations/keys.js +56 -0
- package/dist/translations/keys.js.map +1 -0
- package/dist/translations/ko.js +52 -0
- package/dist/translations/ko.js.map +1 -0
- package/dist/translations/lookup.js +17 -0
- package/dist/translations/lookup.js.map +1 -0
- package/dist/translations/pt.js +52 -0
- package/dist/translations/pt.js.map +1 -0
- package/dist/translations/ru.js +52 -0
- package/dist/translations/ru.js.map +1 -0
- package/dist/translations/server.js +13 -0
- package/dist/translations/server.js.map +1 -0
- package/dist/translations/uk.js +52 -0
- package/dist/translations/uk.js.map +1 -0
- package/dist/translations/useTranslation.js +12 -0
- package/dist/translations/useTranslation.js.map +1 -0
- package/dist/translations/zh.js +52 -0
- package/dist/translations/zh.js.map +1 -0
- package/dist/types.d.ts +214 -0
- package/dist/types.js +11 -0
- package/dist/types.js.map +1 -0
- package/dist/ui/EndSessionMenuItem.d.ts +5 -0
- package/dist/ui/EndSessionMenuItem.js +45 -0
- package/dist/ui/EndSessionMenuItem.js.map +1 -0
- package/dist/ui/ImpersonateIcon.js +20 -0
- package/dist/ui/ImpersonateIcon.js.map +1 -0
- package/dist/ui/ImpersonationAction.d.ts +10 -0
- package/dist/ui/ImpersonationAction.js +48 -0
- package/dist/ui/ImpersonationAction.js.map +1 -0
- package/dist/ui/ImpersonationBar.d.ts +36 -0
- package/dist/ui/ImpersonationBar.js +357 -0
- package/dist/ui/ImpersonationBar.js.map +1 -0
- package/dist/ui/ImpersonationConfig.js +25 -0
- package/dist/ui/ImpersonationConfig.js.map +1 -0
- package/dist/ui/ImpersonationDocumentButton.d.ts +5 -0
- package/dist/ui/ImpersonationDocumentButton.js +85 -0
- package/dist/ui/ImpersonationDocumentButton.js.map +1 -0
- package/dist/ui/ImpersonationFrontendBanner.d.ts +41 -0
- package/dist/ui/ImpersonationFrontendBanner.js +58 -0
- package/dist/ui/ImpersonationFrontendBanner.js.map +1 -0
- package/dist/ui/ImpersonationFrontendExit.d.ts +17 -0
- package/dist/ui/ImpersonationFrontendExit.js +45 -0
- package/dist/ui/ImpersonationFrontendExit.js.map +1 -0
- package/dist/ui/ImpersonationProvider.d.ts +16 -0
- package/dist/ui/ImpersonationProvider.js +127 -0
- package/dist/ui/ImpersonationProvider.js.map +1 -0
- package/dist/ui/ImpersonationSwitcher.d.ts +21 -0
- package/dist/ui/ImpersonationSwitcher.js +251 -0
- package/dist/ui/ImpersonationSwitcher.js.map +1 -0
- package/dist/ui/ImpersonationUserCard.d.ts +31 -0
- package/dist/ui/ImpersonationUserCard.js +51 -0
- package/dist/ui/ImpersonationUserCard.js.map +1 -0
- package/dist/ui/StartConfirmModal.d.ts +28 -0
- package/dist/ui/StartConfirmModal.js +65 -0
- package/dist/ui/StartConfirmModal.js.map +1 -0
- package/dist/ui/api.js +66 -0
- package/dist/ui/api.js.map +1 -0
- package/dist/ui/barPlacement.js +107 -0
- package/dist/ui/barPlacement.js.map +1 -0
- package/dist/ui/frontendBanner.css +22 -0
- package/dist/ui/impersonation.css +244 -0
- package/dist/ui/useImpersonation.d.ts +43 -0
- package/dist/ui/useImpersonation.js +36 -0
- package/dist/ui/useImpersonation.js.map +1 -0
- package/package.json +126 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { expireCookie, writeCookie } from "./cookies.js";
|
|
2
|
+
import { defaultClearOnSwitch } from "../plugin/constants.js";
|
|
3
|
+
import { parseCookies } from "payload/shared";
|
|
4
|
+
//#region src/auth/tenantCookie.ts
|
|
5
|
+
/** Same charset Payload tenant ids use (ObjectId, int, uuid). Rejects cookie injection. */
|
|
6
|
+
const TENANT_COOKIE_VALUE = /^[\w.-]{1,128}$/;
|
|
7
|
+
const tenantCookieName = (cookiePrefix) => defaultClearOnSwitch(cookiePrefix)[0];
|
|
8
|
+
const isManagedTenantCookie = (clearOnSwitch, cookiePrefix) => clearOnSwitch.includes(tenantCookieName(cookiePrefix));
|
|
9
|
+
const clearOnSwitchWithoutTenant = (clearOnSwitch, cookiePrefix) => clearOnSwitch.filter((name) => name !== tenantCookieName(cookiePrefix));
|
|
10
|
+
const readTenantCookie = (headers, cookiePrefix) => {
|
|
11
|
+
const raw = parseCookies(headers).get(tenantCookieName(cookiePrefix));
|
|
12
|
+
return raw && TENANT_COOKIE_VALUE.test(raw) ? raw : void 0;
|
|
13
|
+
};
|
|
14
|
+
const setTenantCookie = ({ authConfig, cookiePrefix, value }) => writeCookie({
|
|
15
|
+
authConfig,
|
|
16
|
+
httpOnly: false,
|
|
17
|
+
name: tenantCookieName(cookiePrefix),
|
|
18
|
+
value
|
|
19
|
+
});
|
|
20
|
+
const expireTenantCookie = ({ authConfig, cookiePrefix }) => expireCookie({
|
|
21
|
+
authConfig,
|
|
22
|
+
httpOnly: false,
|
|
23
|
+
name: tenantCookieName(cookiePrefix)
|
|
24
|
+
});
|
|
25
|
+
const idsFrom = (value) => {
|
|
26
|
+
if (value == null) return [];
|
|
27
|
+
if (Array.isArray(value)) return value.flatMap(idsFrom);
|
|
28
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
29
|
+
const id = String(value);
|
|
30
|
+
return TENANT_COOKIE_VALUE.test(id) ? [id] : [];
|
|
31
|
+
}
|
|
32
|
+
if (typeof value === "object") {
|
|
33
|
+
if ("id" in value) return idsFrom(value.id);
|
|
34
|
+
if ("value" in value) return idsFrom(value.value);
|
|
35
|
+
}
|
|
36
|
+
return [];
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* `@payloadcms/plugin-multi-tenant` puts assignments on `tenants` (hasMany) and
|
|
40
|
+
* sometimes a single `tenant`. Only a unique readable id is safe to write as the selector.
|
|
41
|
+
*/
|
|
42
|
+
const uniqueAssignedTenantId = (user) => {
|
|
43
|
+
const ids = [...new Set([...idsFrom(user.tenants), ...idsFrom(user.tenant)])];
|
|
44
|
+
return ids.length === 1 ? ids[0] : void 0;
|
|
45
|
+
};
|
|
46
|
+
const startTenantCookies = ({ authConfig, cookiePrefix, mode, options, target }) => {
|
|
47
|
+
if (!isManagedTenantCookie(options.cookies.clearOnSwitch, cookiePrefix) || mode === "parallel") return [];
|
|
48
|
+
const assigned = uniqueAssignedTenantId(target);
|
|
49
|
+
if (assigned) return [setTenantCookie({
|
|
50
|
+
authConfig,
|
|
51
|
+
cookiePrefix,
|
|
52
|
+
value: assigned
|
|
53
|
+
})];
|
|
54
|
+
return [expireTenantCookie({
|
|
55
|
+
authConfig,
|
|
56
|
+
cookiePrefix
|
|
57
|
+
})];
|
|
58
|
+
};
|
|
59
|
+
const exitTenantCookies = ({ authConfig, cookiePrefix, mode, options, snapshot }) => {
|
|
60
|
+
if (!isManagedTenantCookie(options.cookies.clearOnSwitch, cookiePrefix) || mode === "parallel") return [];
|
|
61
|
+
if (snapshot && TENANT_COOKIE_VALUE.test(snapshot)) return [setTenantCookie({
|
|
62
|
+
authConfig,
|
|
63
|
+
cookiePrefix,
|
|
64
|
+
value: snapshot
|
|
65
|
+
})];
|
|
66
|
+
return [expireTenantCookie({
|
|
67
|
+
authConfig,
|
|
68
|
+
cookiePrefix
|
|
69
|
+
})];
|
|
70
|
+
};
|
|
71
|
+
//#endregion
|
|
72
|
+
export { clearOnSwitchWithoutTenant, exitTenantCookies, readTenantCookie, startTenantCookies };
|
|
73
|
+
|
|
74
|
+
//# sourceMappingURL=tenantCookie.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenantCookie.js","names":[],"sources":["../../src/auth/tenantCookie.ts"],"sourcesContent":["import type { SanitizedCollectionConfig } from 'payload'\nimport { parseCookies } from 'payload/shared'\n\nimport { defaultClearOnSwitch } from '../plugin/constants'\nimport type { ImpersonationMode, ResolvedOptions } from '../types'\nimport { expireCookie, writeCookie } from './cookies'\n\ntype AuthConfig = SanitizedCollectionConfig['auth']\n\n/** Same charset Payload tenant ids use (ObjectId, int, uuid). Rejects cookie injection. */\nconst TENANT_COOKIE_VALUE = /^[\\w.-]{1,128}$/\n\nexport const tenantCookieName = (cookiePrefix: string): string =>\n\tdefaultClearOnSwitch(cookiePrefix)[0] as string\n\nexport const isManagedTenantCookie = (clearOnSwitch: string[], cookiePrefix: string): boolean =>\n\tclearOnSwitch.includes(tenantCookieName(cookiePrefix))\n\nexport const clearOnSwitchWithoutTenant = (\n\tclearOnSwitch: string[],\n\tcookiePrefix: string\n): string[] => clearOnSwitch.filter((name) => name !== tenantCookieName(cookiePrefix))\n\nexport const readTenantCookie = (headers: Headers, cookiePrefix: string): string | undefined => {\n\tconst raw = parseCookies(headers).get(tenantCookieName(cookiePrefix))\n\treturn raw && TENANT_COOKIE_VALUE.test(raw) ? raw : undefined\n}\n\nexport const setTenantCookie = ({\n\tauthConfig,\n\tcookiePrefix,\n\tvalue,\n}: {\n\tauthConfig: AuthConfig\n\tcookiePrefix: string\n\tvalue: string\n}): string =>\n\twriteCookie({\n\t\tauthConfig,\n\t\thttpOnly: false,\n\t\tname: tenantCookieName(cookiePrefix),\n\t\tvalue,\n\t})\n\nexport const expireTenantCookie = ({\n\tauthConfig,\n\tcookiePrefix,\n}: {\n\tauthConfig: AuthConfig\n\tcookiePrefix: string\n}): string =>\n\texpireCookie({\n\t\tauthConfig,\n\t\thttpOnly: false,\n\t\tname: tenantCookieName(cookiePrefix),\n\t})\n\nconst idsFrom = (value: unknown): string[] => {\n\tif (value == null) {\n\t\treturn []\n\t}\n\tif (Array.isArray(value)) {\n\t\treturn value.flatMap(idsFrom)\n\t}\n\tif (typeof value === 'string' || typeof value === 'number') {\n\t\tconst id = String(value)\n\t\treturn TENANT_COOKIE_VALUE.test(id) ? [id] : []\n\t}\n\tif (typeof value === 'object') {\n\t\tif ('id' in value) {\n\t\t\treturn idsFrom((value as { id: unknown }).id)\n\t\t}\n\t\tif ('value' in value) {\n\t\t\treturn idsFrom((value as { value: unknown }).value)\n\t\t}\n\t}\n\treturn []\n}\n\n/**\n * `@payloadcms/plugin-multi-tenant` puts assignments on `tenants` (hasMany) and\n * sometimes a single `tenant`. Only a unique readable id is safe to write as the selector.\n */\nexport const uniqueAssignedTenantId = (user: Record<string, unknown>): string | undefined => {\n\tconst ids = [...new Set([...idsFrom(user.tenants), ...idsFrom(user.tenant)])]\n\treturn ids.length === 1 ? ids[0] : undefined\n}\n\nexport const startTenantCookies = ({\n\tauthConfig,\n\tcookiePrefix,\n\tmode,\n\toptions,\n\ttarget,\n}: {\n\tauthConfig: AuthConfig\n\tcookiePrefix: string\n\tmode: ImpersonationMode\n\toptions: ResolvedOptions\n\ttarget: Record<string, unknown>\n}): string[] => {\n\tif (!isManagedTenantCookie(options.cookies.clearOnSwitch, cookiePrefix) || mode === 'parallel') {\n\t\treturn []\n\t}\n\tconst assigned = uniqueAssignedTenantId(target)\n\tif (assigned) {\n\t\treturn [setTenantCookie({ authConfig, cookiePrefix, value: assigned })]\n\t}\n\treturn [expireTenantCookie({ authConfig, cookiePrefix })]\n}\n\nexport const exitTenantCookies = ({\n\tauthConfig,\n\tcookiePrefix,\n\tmode,\n\toptions,\n\tsnapshot,\n}: {\n\tauthConfig: AuthConfig\n\tcookiePrefix: string\n\tmode: ImpersonationMode\n\toptions: ResolvedOptions\n\tsnapshot?: null | string\n}): string[] => {\n\tif (!isManagedTenantCookie(options.cookies.clearOnSwitch, cookiePrefix) || mode === 'parallel') {\n\t\treturn []\n\t}\n\tif (snapshot && TENANT_COOKIE_VALUE.test(snapshot)) {\n\t\treturn [setTenantCookie({ authConfig, cookiePrefix, value: snapshot })]\n\t}\n\treturn [expireTenantCookie({ authConfig, cookiePrefix })]\n}\n"],"mappings":";;;;;AAUA,MAAM,sBAAsB;AAE5B,MAAa,oBAAoB,iBAChC,qBAAqB,YAAY,EAAE;AAEpC,MAAa,yBAAyB,eAAyB,iBAC9D,cAAc,SAAS,iBAAiB,YAAY,CAAC;AAEtD,MAAa,8BACZ,eACA,iBACc,cAAc,QAAQ,SAAS,SAAS,iBAAiB,YAAY,CAAC;AAErF,MAAa,oBAAoB,SAAkB,iBAA6C;CAC/F,MAAM,MAAM,aAAa,OAAO,EAAE,IAAI,iBAAiB,YAAY,CAAC;CACpE,OAAO,OAAO,oBAAoB,KAAK,GAAG,IAAI,MAAM,KAAA;AACrD;AAEA,MAAa,mBAAmB,EAC/B,YACA,cACA,YAMA,YAAY;CACX;CACA,UAAU;CACV,MAAM,iBAAiB,YAAY;CACnC;AACD,CAAC;AAEF,MAAa,sBAAsB,EAClC,YACA,mBAKA,aAAa;CACZ;CACA,UAAU;CACV,MAAM,iBAAiB,YAAY;AACpC,CAAC;AAEF,MAAM,WAAW,UAA6B;CAC7C,IAAI,SAAS,MACZ,OAAO,CAAC;CAET,IAAI,MAAM,QAAQ,KAAK,GACtB,OAAO,MAAM,QAAQ,OAAO;CAE7B,IAAI,OAAO,UAAU,YAAY,OAAO,UAAU,UAAU;EAC3D,MAAM,KAAK,OAAO,KAAK;EACvB,OAAO,oBAAoB,KAAK,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC;CAC/C;CACA,IAAI,OAAO,UAAU,UAAU;EAC9B,IAAI,QAAQ,OACX,OAAO,QAAS,MAA0B,EAAE;EAE7C,IAAI,WAAW,OACd,OAAO,QAAS,MAA6B,KAAK;CAEpD;CACA,OAAO,CAAC;AACT;;;;;AAMA,MAAa,0BAA0B,SAAsD;CAC5F,MAAM,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,QAAQ,KAAK,OAAO,GAAG,GAAG,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC;CAC5E,OAAO,IAAI,WAAW,IAAI,IAAI,KAAK,KAAA;AACpC;AAEA,MAAa,sBAAsB,EAClC,YACA,cACA,MACA,SACA,aAOe;CACf,IAAI,CAAC,sBAAsB,QAAQ,QAAQ,eAAe,YAAY,KAAK,SAAS,YACnF,OAAO,CAAC;CAET,MAAM,WAAW,uBAAuB,MAAM;CAC9C,IAAI,UACH,OAAO,CAAC,gBAAgB;EAAE;EAAY;EAAc,OAAO;CAAS,CAAC,CAAC;CAEvE,OAAO,CAAC,mBAAmB;EAAE;EAAY;CAAa,CAAC,CAAC;AACzD;AAEA,MAAa,qBAAqB,EACjC,YACA,cACA,MACA,SACA,eAOe;CACf,IAAI,CAAC,sBAAsB,QAAQ,QAAQ,eAAe,YAAY,KAAK,SAAS,YACnF,OAAO,CAAC;CAET,IAAI,YAAY,oBAAoB,KAAK,QAAQ,GAChD,OAAO,CAAC,gBAAgB;EAAE;EAAY;EAAc,OAAO;CAAS,CAAC,CAAC;CAEvE,OAAO,CAAC,mBAAmB;EAAE;EAAY;CAAa,CAAC,CAAC;AACzD"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
//#region src/endpoints/codes.d.ts
|
|
2
|
+
declare const FAILURE_CODES: readonly ["alreadyImpersonating", "failed", "forbidden", "impersonatorGone", "impersonatorSessionExpired", "invalidBody", "notImpersonating", "origin", "reasonRequired", "selfTarget", "targetNotFound", "targetTrashed", "targetUnverified", "unsupportedAuth", "unsupportedCollection"];
|
|
3
|
+
type FailureCode = (typeof FAILURE_CODES)[number];
|
|
4
|
+
//#endregion
|
|
5
|
+
export { FailureCode };
|
|
6
|
+
//# sourceMappingURL=codes.d.ts.map
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { isPastAbsoluteExpiry, resolveCurrent } from "../session/resolve.js";
|
|
2
|
+
import { absoluteExpiryCookies } from "../auth/expiryCookies.js";
|
|
3
|
+
import { closeAndRevoke } from "../session/close.js";
|
|
4
|
+
import { boundSid } from "../types.js";
|
|
5
|
+
import { statusFromRow } from "../getImpersonation.js";
|
|
6
|
+
import { getOptions, json } from "./shared.js";
|
|
7
|
+
//#region src/endpoints/current.ts
|
|
8
|
+
const currentHandler = async (req) => {
|
|
9
|
+
const options = getOptions(req);
|
|
10
|
+
const user = req.user;
|
|
11
|
+
const sid = boundSid(user, options.session.binding);
|
|
12
|
+
if (!user || !sid) return json({
|
|
13
|
+
body: { active: false },
|
|
14
|
+
req,
|
|
15
|
+
status: 200
|
|
16
|
+
});
|
|
17
|
+
const row = await resolveCurrent({
|
|
18
|
+
headers: req.headers,
|
|
19
|
+
options,
|
|
20
|
+
payload: req.payload,
|
|
21
|
+
req,
|
|
22
|
+
sid
|
|
23
|
+
});
|
|
24
|
+
if (!row || isPastAbsoluteExpiry(row)) {
|
|
25
|
+
if (row && isPastAbsoluteExpiry(row)) {
|
|
26
|
+
await closeAndRevoke({
|
|
27
|
+
endedBy: "expired",
|
|
28
|
+
options,
|
|
29
|
+
payload: req.payload,
|
|
30
|
+
record: row,
|
|
31
|
+
req
|
|
32
|
+
});
|
|
33
|
+
return json({
|
|
34
|
+
body: { active: false },
|
|
35
|
+
cookies: await absoluteExpiryCookies({
|
|
36
|
+
options,
|
|
37
|
+
payload: req.payload,
|
|
38
|
+
row,
|
|
39
|
+
sid,
|
|
40
|
+
user
|
|
41
|
+
}),
|
|
42
|
+
req,
|
|
43
|
+
status: 200
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return json({
|
|
47
|
+
body: { active: false },
|
|
48
|
+
req,
|
|
49
|
+
status: 200
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return json({
|
|
53
|
+
body: statusFromRow(row, sid),
|
|
54
|
+
req,
|
|
55
|
+
status: 200
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
const currentEndpoint = (path) => ({
|
|
59
|
+
handler: currentHandler,
|
|
60
|
+
method: "get",
|
|
61
|
+
path
|
|
62
|
+
});
|
|
63
|
+
//#endregion
|
|
64
|
+
export { currentEndpoint };
|
|
65
|
+
|
|
66
|
+
//# sourceMappingURL=current.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"current.js","names":[],"sources":["../../src/endpoints/current.ts"],"sourcesContent":["import type { Endpoint, PayloadRequest } from 'payload'\n\nimport { absoluteExpiryCookies } from '../auth/expiryCookies'\nimport { statusFromRow } from '../getImpersonation'\nimport { closeAndRevoke } from '../session/close'\nimport { isPastAbsoluteExpiry, resolveCurrent } from '../session/resolve'\nimport { boundSid } from '../types'\nimport { getOptions, json } from './shared'\n\nexport const currentHandler = async (req: PayloadRequest): Promise<Response> => {\n\tconst options = getOptions(req)\n\tconst user = req.user\n\tconst sid = boundSid(user, options.session.binding)\n\tif (!user || !sid) {\n\t\treturn json({ body: { active: false }, req, status: 200 })\n\t}\n\n\tconst row = await resolveCurrent({\n\t\theaders: req.headers,\n\t\toptions,\n\t\tpayload: req.payload,\n\t\treq,\n\t\tsid,\n\t})\n\tif (!row || isPastAbsoluteExpiry(row)) {\n\t\tif (row && isPastAbsoluteExpiry(row)) {\n\t\t\tawait closeAndRevoke({\n\t\t\t\tendedBy: 'expired',\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\tconst cookies = await absoluteExpiryCookies({\n\t\t\t\toptions,\n\t\t\t\tpayload: req.payload,\n\t\t\t\trow,\n\t\t\t\tsid,\n\t\t\t\tuser,\n\t\t\t})\n\t\t\treturn json({ body: { active: false }, cookies, req, status: 200 })\n\t\t}\n\t\treturn json({ body: { active: false }, req, status: 200 })\n\t}\n\n\treturn json({\n\t\tbody: statusFromRow(row, sid),\n\t\treq,\n\t\tstatus: 200,\n\t})\n}\n\nexport const currentEndpoint = (path: string): Endpoint => ({\n\thandler: currentHandler,\n\tmethod: 'get',\n\tpath,\n})\n"],"mappings":";;;;;;;AASA,MAAa,iBAAiB,OAAO,QAA2C;CAC/E,MAAM,UAAU,WAAW,GAAG;CAC9B,MAAM,OAAO,IAAI;CACjB,MAAM,MAAM,SAAS,MAAM,QAAQ,QAAQ,OAAO;CAClD,IAAI,CAAC,QAAQ,CAAC,KACb,OAAO,KAAK;EAAE,MAAM,EAAE,QAAQ,MAAM;EAAG;EAAK,QAAQ;CAAI,CAAC;CAG1D,MAAM,MAAM,MAAM,eAAe;EAChC,SAAS,IAAI;EACb;EACA,SAAS,IAAI;EACb;EACA;CACD,CAAC;CACD,IAAI,CAAC,OAAO,qBAAqB,GAAG,GAAG;EACtC,IAAI,OAAO,qBAAqB,GAAG,GAAG;GACrC,MAAM,eAAe;IACpB,SAAS;IACT;IACA,SAAS,IAAI;IACb,QAAQ;IACR;GACD,CAAC;GAQD,OAAO,KAAK;IAAE,MAAM,EAAE,QAAQ,MAAM;IAAG,SAAA,MAPjB,sBAAsB;KAC3C;KACA,SAAS,IAAI;KACb;KACA;KACA;IACD,CAAC;IAC+C;IAAK,QAAQ;GAAI,CAAC;EACnE;EACA,OAAO,KAAK;GAAE,MAAM,EAAE,QAAQ,MAAM;GAAG;GAAK,QAAQ;EAAI,CAAC;CAC1D;CAEA,OAAO,KAAK;EACX,MAAM,cAAc,KAAK,GAAG;EAC5B;EACA,QAAQ;CACT,CAAC;AACF;AAEA,MAAa,mBAAmB,UAA4B;CAC3D,SAAS;CACT,QAAQ;CACR;AACD"}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { collectionBySlug } from "../ids.js";
|
|
2
|
+
import { findOpenBySid, relationOf } from "../session/resolve.js";
|
|
3
|
+
import { expireCookie, expireCookies, expirePayloadCookie } from "../auth/cookies.js";
|
|
4
|
+
import { isolatedCookieNameFor } from "../auth/mode.js";
|
|
5
|
+
import { closeAndRevoke } from "../session/close.js";
|
|
6
|
+
import { boundSid } from "../types.js";
|
|
7
|
+
import { callerSid, fail, getOptions, isApiKeyStrategy, json, prepareMutation, rejectGate } from "./shared.js";
|
|
8
|
+
import { resignSession } from "../auth/issue.js";
|
|
9
|
+
import { clearOnSwitchWithoutTenant, exitTenantCookies } from "../auth/tenantCookie.js";
|
|
10
|
+
import { releaseLocks } from "../session/locks.js";
|
|
11
|
+
//#region src/endpoints/exit.ts
|
|
12
|
+
const exitHandler = async (req) => {
|
|
13
|
+
const gate = await prepareMutation(req);
|
|
14
|
+
if (gate) return rejectGate(req, gate);
|
|
15
|
+
const options = getOptions(req);
|
|
16
|
+
const user = req.user;
|
|
17
|
+
if (!user) return fail({
|
|
18
|
+
error: "forbidden",
|
|
19
|
+
req,
|
|
20
|
+
status: 403
|
|
21
|
+
});
|
|
22
|
+
if (isApiKeyStrategy(user) || !callerSid(user)) return fail({
|
|
23
|
+
error: "unsupportedAuth",
|
|
24
|
+
req,
|
|
25
|
+
status: 403
|
|
26
|
+
});
|
|
27
|
+
const sid = boundSid(user, options.session.binding);
|
|
28
|
+
if (!sid) return fail({
|
|
29
|
+
error: "unsupportedAuth",
|
|
30
|
+
req,
|
|
31
|
+
status: 403
|
|
32
|
+
});
|
|
33
|
+
const row = await findOpenBySid({
|
|
34
|
+
options,
|
|
35
|
+
payload: req.payload,
|
|
36
|
+
req,
|
|
37
|
+
sid
|
|
38
|
+
});
|
|
39
|
+
if (!row) return fail({
|
|
40
|
+
error: "notImpersonating",
|
|
41
|
+
req,
|
|
42
|
+
status: 409
|
|
43
|
+
});
|
|
44
|
+
const impersonator = relationOf(row.impersonator);
|
|
45
|
+
const target = relationOf(row.target);
|
|
46
|
+
const authConfig = (target ? collectionBySlug(req.payload, target.collection) : void 0)?.config.auth ?? collectionBySlug(req.payload, req.payload.config.admin.user)?.config.auth;
|
|
47
|
+
if (!authConfig) return fail({
|
|
48
|
+
error: "failed",
|
|
49
|
+
req,
|
|
50
|
+
status: 500
|
|
51
|
+
});
|
|
52
|
+
const cookiePrefix = req.payload.config.cookiePrefix;
|
|
53
|
+
const extraClearOnSwitch = clearOnSwitchWithoutTenant(options.cookies.clearOnSwitch, cookiePrefix);
|
|
54
|
+
const expireHintAndClear = expireCookies({
|
|
55
|
+
authConfig,
|
|
56
|
+
cookiePrefix,
|
|
57
|
+
names: [...extraClearOnSwitch, options.hintCookieName]
|
|
58
|
+
});
|
|
59
|
+
if (!impersonator || !collectionBySlug(req.payload, impersonator.collection)) {
|
|
60
|
+
await closeAndRevoke({
|
|
61
|
+
endedBy: "impersonatorGone",
|
|
62
|
+
options,
|
|
63
|
+
payload: req.payload,
|
|
64
|
+
record: row,
|
|
65
|
+
req
|
|
66
|
+
});
|
|
67
|
+
await releaseLocks({
|
|
68
|
+
payload: req.payload,
|
|
69
|
+
record: row,
|
|
70
|
+
req
|
|
71
|
+
});
|
|
72
|
+
return fail({
|
|
73
|
+
cookies: [expirePayloadCookie({
|
|
74
|
+
authConfig,
|
|
75
|
+
cookiePrefix
|
|
76
|
+
}), ...expireHintAndClear],
|
|
77
|
+
error: "impersonatorGone",
|
|
78
|
+
req,
|
|
79
|
+
status: 409
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
let impersonatorDoc = null;
|
|
83
|
+
try {
|
|
84
|
+
impersonatorDoc = await req.payload.findByID({
|
|
85
|
+
id: impersonator.id,
|
|
86
|
+
collection: impersonator.collection,
|
|
87
|
+
depth: 0,
|
|
88
|
+
overrideAccess: true,
|
|
89
|
+
req
|
|
90
|
+
});
|
|
91
|
+
} catch {
|
|
92
|
+
impersonatorDoc = null;
|
|
93
|
+
}
|
|
94
|
+
if (!impersonatorDoc || impersonatorDoc.deletedAt) {
|
|
95
|
+
await closeAndRevoke({
|
|
96
|
+
endedBy: "impersonatorGone",
|
|
97
|
+
options,
|
|
98
|
+
payload: req.payload,
|
|
99
|
+
record: row,
|
|
100
|
+
req
|
|
101
|
+
});
|
|
102
|
+
await releaseLocks({
|
|
103
|
+
payload: req.payload,
|
|
104
|
+
record: row,
|
|
105
|
+
req
|
|
106
|
+
});
|
|
107
|
+
return fail({
|
|
108
|
+
cookies: [expirePayloadCookie({
|
|
109
|
+
authConfig,
|
|
110
|
+
cookiePrefix
|
|
111
|
+
}), ...expireHintAndClear],
|
|
112
|
+
error: "impersonatorGone",
|
|
113
|
+
req,
|
|
114
|
+
status: 409
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
if (row.mode === "parallel") {
|
|
118
|
+
const closed = await closeAndRevoke({
|
|
119
|
+
endedBy: "exit",
|
|
120
|
+
options,
|
|
121
|
+
payload: req.payload,
|
|
122
|
+
record: row,
|
|
123
|
+
req
|
|
124
|
+
});
|
|
125
|
+
await releaseLocks({
|
|
126
|
+
payload: req.payload,
|
|
127
|
+
record: closed,
|
|
128
|
+
req
|
|
129
|
+
});
|
|
130
|
+
let isolatedName;
|
|
131
|
+
if (target) {
|
|
132
|
+
const targetUser = await req.payload.db.findOne({
|
|
133
|
+
collection: target.collection,
|
|
134
|
+
req,
|
|
135
|
+
where: { id: { equals: target.id } }
|
|
136
|
+
});
|
|
137
|
+
if (targetUser) {
|
|
138
|
+
targetUser.collection = target.collection;
|
|
139
|
+
isolatedName = await isolatedCookieNameFor({
|
|
140
|
+
collection: target.collection,
|
|
141
|
+
payload: req.payload,
|
|
142
|
+
user: targetUser
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return json({
|
|
147
|
+
body: { ok: true },
|
|
148
|
+
cookies: [
|
|
149
|
+
...isolatedName ? [expireCookie({
|
|
150
|
+
authConfig,
|
|
151
|
+
name: isolatedName
|
|
152
|
+
})] : [],
|
|
153
|
+
expireCookie({
|
|
154
|
+
authConfig,
|
|
155
|
+
name: options.hintCookieName
|
|
156
|
+
}),
|
|
157
|
+
...expireCookies({
|
|
158
|
+
authConfig,
|
|
159
|
+
cookiePrefix,
|
|
160
|
+
names: extraClearOnSwitch
|
|
161
|
+
})
|
|
162
|
+
],
|
|
163
|
+
req,
|
|
164
|
+
status: 200
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
const restored = await resignSession({
|
|
168
|
+
collection: impersonator.collection,
|
|
169
|
+
payload: req.payload,
|
|
170
|
+
req,
|
|
171
|
+
sid: row.impersonatorSid,
|
|
172
|
+
userId: impersonator.id
|
|
173
|
+
});
|
|
174
|
+
if (!restored) {
|
|
175
|
+
await closeAndRevoke({
|
|
176
|
+
endedBy: "expired",
|
|
177
|
+
options,
|
|
178
|
+
payload: req.payload,
|
|
179
|
+
record: row,
|
|
180
|
+
req
|
|
181
|
+
});
|
|
182
|
+
await releaseLocks({
|
|
183
|
+
payload: req.payload,
|
|
184
|
+
record: row,
|
|
185
|
+
req
|
|
186
|
+
});
|
|
187
|
+
return fail({
|
|
188
|
+
cookies: [expirePayloadCookie({
|
|
189
|
+
authConfig,
|
|
190
|
+
cookiePrefix
|
|
191
|
+
}), ...expireHintAndClear],
|
|
192
|
+
error: "impersonatorSessionExpired",
|
|
193
|
+
req,
|
|
194
|
+
status: 409
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
const closed = await closeAndRevoke({
|
|
198
|
+
endedBy: "exit",
|
|
199
|
+
options,
|
|
200
|
+
payload: req.payload,
|
|
201
|
+
record: row,
|
|
202
|
+
req
|
|
203
|
+
});
|
|
204
|
+
await releaseLocks({
|
|
205
|
+
payload: req.payload,
|
|
206
|
+
record: closed,
|
|
207
|
+
req
|
|
208
|
+
});
|
|
209
|
+
return json({
|
|
210
|
+
body: { ok: true },
|
|
211
|
+
cookies: [
|
|
212
|
+
restored.cookie,
|
|
213
|
+
expireCookie({
|
|
214
|
+
authConfig,
|
|
215
|
+
name: options.hintCookieName
|
|
216
|
+
}),
|
|
217
|
+
...exitTenantCookies({
|
|
218
|
+
authConfig,
|
|
219
|
+
cookiePrefix,
|
|
220
|
+
mode: closed.mode,
|
|
221
|
+
options,
|
|
222
|
+
snapshot: row.impersonatorTenantCookie
|
|
223
|
+
}),
|
|
224
|
+
...expireCookies({
|
|
225
|
+
authConfig,
|
|
226
|
+
cookiePrefix,
|
|
227
|
+
names: extraClearOnSwitch
|
|
228
|
+
})
|
|
229
|
+
],
|
|
230
|
+
req,
|
|
231
|
+
status: 200
|
|
232
|
+
});
|
|
233
|
+
};
|
|
234
|
+
const exitEndpoint = (path) => ({
|
|
235
|
+
handler: exitHandler,
|
|
236
|
+
method: "post",
|
|
237
|
+
path
|
|
238
|
+
});
|
|
239
|
+
//#endregion
|
|
240
|
+
export { exitEndpoint };
|
|
241
|
+
|
|
242
|
+
//# sourceMappingURL=exit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exit.js","names":[],"sources":["../../src/endpoints/exit.ts"],"sourcesContent":["import type { CollectionSlug, Endpoint, PayloadRequest } from 'payload'\n\nimport { expireCookie, expireCookies, expirePayloadCookie } from '../auth/cookies'\nimport { resignSession } from '../auth/issue'\nimport { isolatedCookieNameFor } from '../auth/mode'\nimport { clearOnSwitchWithoutTenant, exitTenantCookies } from '../auth/tenantCookie'\nimport { collectionBySlug } from '../ids'\nimport { closeAndRevoke } from '../session/close'\nimport { releaseLocks } from '../session/locks'\nimport { findOpenBySid, relationOf } from '../session/resolve'\nimport { boundSid } from '../types'\nimport {\n\tcallerSid,\n\tfail,\n\tgetOptions,\n\tisApiKeyStrategy,\n\tjson,\n\tprepareMutation,\n\trejectGate,\n} from './shared'\n\nexport const exitHandler = async (req: PayloadRequest): Promise<Response> => {\n\tconst gate = await prepareMutation(req)\n\tif (gate) {\n\t\treturn rejectGate(req, gate)\n\t}\n\n\tconst options = getOptions(req)\n\tconst user = req.user\n\tif (!user) {\n\t\treturn fail({ error: 'forbidden', req, status: 403 })\n\t}\n\tif (isApiKeyStrategy(user) || !callerSid(user)) {\n\t\treturn fail({ error: 'unsupportedAuth', req, status: 403 })\n\t}\n\n\tconst sid = boundSid(user, options.session.binding)\n\tif (!sid) {\n\t\treturn fail({ error: 'unsupportedAuth', req, status: 403 })\n\t}\n\n\tconst row = await findOpenBySid({ options, payload: req.payload, req, sid })\n\tif (!row) {\n\t\treturn fail({ error: 'notImpersonating', req, status: 409 })\n\t}\n\n\tconst impersonator = relationOf(row.impersonator)\n\tconst target = relationOf(row.target)\n\tconst targetCollection = target ? collectionBySlug(req.payload, target.collection) : undefined\n\tconst authConfig =\n\t\ttargetCollection?.config.auth ??\n\t\tcollectionBySlug(req.payload, req.payload.config.admin.user)?.config.auth\n\tif (!authConfig) {\n\t\treturn fail({ error: 'failed', req, status: 500 })\n\t}\n\n\tconst cookiePrefix = req.payload.config.cookiePrefix\n\tconst extraClearOnSwitch = clearOnSwitchWithoutTenant(options.cookies.clearOnSwitch, cookiePrefix)\n\tconst expireHintAndClear = expireCookies({\n\t\tauthConfig,\n\t\tcookiePrefix,\n\t\tnames: [...extraClearOnSwitch, options.hintCookieName],\n\t})\n\n\tif (!impersonator || !collectionBySlug(req.payload, impersonator.collection)) {\n\t\tawait closeAndRevoke({\n\t\t\tendedBy: 'impersonatorGone',\n\t\t\toptions,\n\t\t\tpayload: req.payload,\n\t\t\trecord: row,\n\t\t\treq,\n\t\t})\n\t\tawait releaseLocks({ payload: req.payload, record: row, req })\n\t\treturn fail({\n\t\t\tcookies: [\n\t\t\t\texpirePayloadCookie({\n\t\t\t\t\tauthConfig,\n\t\t\t\t\tcookiePrefix,\n\t\t\t\t}),\n\t\t\t\t...expireHintAndClear,\n\t\t\t],\n\t\t\terror: 'impersonatorGone',\n\t\t\treq,\n\t\t\tstatus: 409,\n\t\t})\n\t}\n\n\tlet impersonatorDoc: { deletedAt?: string; lockUntil?: Date | string } | null = null\n\ttry {\n\t\timpersonatorDoc = (await req.payload.findByID({\n\t\t\tid: impersonator.id,\n\t\t\tcollection: impersonator.collection as CollectionSlug,\n\t\t\tdepth: 0,\n\t\t\toverrideAccess: true,\n\t\t\treq,\n\t\t})) as { deletedAt?: string; lockUntil?: Date | string }\n\t} catch {\n\t\timpersonatorDoc = null\n\t}\n\n\tif (!impersonatorDoc || impersonatorDoc.deletedAt) {\n\t\tawait closeAndRevoke({\n\t\t\tendedBy: 'impersonatorGone',\n\t\t\toptions,\n\t\t\tpayload: req.payload,\n\t\t\trecord: row,\n\t\t\treq,\n\t\t})\n\t\tawait releaseLocks({ payload: req.payload, record: row, req })\n\t\treturn fail({\n\t\t\tcookies: [\n\t\t\t\texpirePayloadCookie({\n\t\t\t\t\tauthConfig,\n\t\t\t\t\tcookiePrefix,\n\t\t\t\t}),\n\t\t\t\t...expireHintAndClear,\n\t\t\t],\n\t\t\terror: 'impersonatorGone',\n\t\t\treq,\n\t\t\tstatus: 409,\n\t\t})\n\t}\n\n\tif (row.mode === 'parallel') {\n\t\tconst closed = await closeAndRevoke({\n\t\t\tendedBy: 'exit',\n\t\t\toptions,\n\t\t\tpayload: req.payload,\n\t\t\trecord: row,\n\t\t\treq,\n\t\t})\n\t\tawait releaseLocks({ payload: req.payload, record: closed, req })\n\t\tlet isolatedName: string | undefined\n\t\tif (target) {\n\t\t\tconst targetUser = (await req.payload.db.findOne({\n\t\t\t\tcollection: target.collection as CollectionSlug,\n\t\t\t\treq,\n\t\t\t\twhere: { id: { equals: target.id } },\n\t\t\t})) as { collection?: string } | null\n\t\t\tif (targetUser) {\n\t\t\t\ttargetUser.collection = target.collection\n\t\t\t\tisolatedName = await isolatedCookieNameFor({\n\t\t\t\t\tcollection: target.collection as CollectionSlug,\n\t\t\t\t\tpayload: req.payload,\n\t\t\t\t\tuser: targetUser as never,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t\treturn json({\n\t\t\tbody: { ok: true },\n\t\t\tcookies: [\n\t\t\t\t...(isolatedName ? [expireCookie({ authConfig, name: isolatedName })] : []),\n\t\t\t\texpireCookie({ authConfig, name: options.hintCookieName }),\n\t\t\t\t...expireCookies({\n\t\t\t\t\tauthConfig,\n\t\t\t\t\tcookiePrefix,\n\t\t\t\t\tnames: extraClearOnSwitch,\n\t\t\t\t}),\n\t\t\t],\n\t\t\treq,\n\t\t\tstatus: 200,\n\t\t})\n\t}\n\n\tconst restored = await resignSession({\n\t\tcollection: impersonator.collection as CollectionSlug,\n\t\tpayload: req.payload,\n\t\treq,\n\t\tsid: row.impersonatorSid,\n\t\tuserId: impersonator.id,\n\t})\n\n\tif (!restored) {\n\t\tawait closeAndRevoke({\n\t\t\tendedBy: 'expired',\n\t\t\toptions,\n\t\t\tpayload: req.payload,\n\t\t\trecord: row,\n\t\t\treq,\n\t\t})\n\t\tawait releaseLocks({ payload: req.payload, record: row, req })\n\t\treturn fail({\n\t\t\tcookies: [\n\t\t\t\texpirePayloadCookie({\n\t\t\t\t\tauthConfig,\n\t\t\t\t\tcookiePrefix,\n\t\t\t\t}),\n\t\t\t\t...expireHintAndClear,\n\t\t\t],\n\t\t\terror: 'impersonatorSessionExpired',\n\t\t\treq,\n\t\t\tstatus: 409,\n\t\t})\n\t}\n\n\tconst closed = await closeAndRevoke({\n\t\tendedBy: 'exit',\n\t\toptions,\n\t\tpayload: req.payload,\n\t\trecord: row,\n\t\treq,\n\t})\n\tawait releaseLocks({ payload: req.payload, record: closed, req })\n\n\treturn json({\n\t\tbody: { ok: true },\n\t\tcookies: [\n\t\t\trestored.cookie,\n\t\t\texpireCookie({ authConfig, name: options.hintCookieName }),\n\t\t\t...exitTenantCookies({\n\t\t\t\tauthConfig,\n\t\t\t\tcookiePrefix,\n\t\t\t\tmode: closed.mode,\n\t\t\t\toptions,\n\t\t\t\tsnapshot: row.impersonatorTenantCookie,\n\t\t\t}),\n\t\t\t...expireCookies({\n\t\t\t\tauthConfig,\n\t\t\t\tcookiePrefix,\n\t\t\t\tnames: extraClearOnSwitch,\n\t\t\t}),\n\t\t],\n\t\treq,\n\t\tstatus: 200,\n\t})\n}\n\nexport const exitEndpoint = (path: string): Endpoint => ({\n\thandler: exitHandler,\n\tmethod: 'post',\n\tpath,\n})\n"],"mappings":"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"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { getRegistry } from "../plugin/registry.js";
|
|
2
|
+
import { asAuthUser } from "../types.js";
|
|
3
|
+
import { isJsonContentType, verifyMutationOrigin } from "../auth/origin.js";
|
|
4
|
+
import { addDataAndFileToRequest, headersWithCors } from "payload";
|
|
5
|
+
//#region src/endpoints/shared.ts
|
|
6
|
+
const NO_STORE = { "Cache-Control": "no-store" };
|
|
7
|
+
const json = ({ body, cookies = [], req, status }) => {
|
|
8
|
+
const headers = headersWithCors({
|
|
9
|
+
headers: new Headers(NO_STORE),
|
|
10
|
+
req
|
|
11
|
+
});
|
|
12
|
+
for (const cookie of cookies) headers.append("Set-Cookie", cookie);
|
|
13
|
+
return Response.json(body, {
|
|
14
|
+
headers,
|
|
15
|
+
status
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
const fail = ({ cookies, error, req, status }) => json({
|
|
19
|
+
body: { error },
|
|
20
|
+
cookies,
|
|
21
|
+
req,
|
|
22
|
+
status
|
|
23
|
+
});
|
|
24
|
+
const rejectGate = (req, gate) => fail({
|
|
25
|
+
error: gate,
|
|
26
|
+
req,
|
|
27
|
+
status: gate === "invalidBody" ? 400 : 403
|
|
28
|
+
});
|
|
29
|
+
const getOptions = (req) => {
|
|
30
|
+
const options = getRegistry(req.payload.config);
|
|
31
|
+
if (!options) throw new Error("@10x-media/impersonation: plugin options were not registered");
|
|
32
|
+
return options;
|
|
33
|
+
};
|
|
34
|
+
const callerSid = (user) => {
|
|
35
|
+
if (!user) return;
|
|
36
|
+
return asAuthUser(user)._sid;
|
|
37
|
+
};
|
|
38
|
+
const isApiKeyStrategy = (user) => {
|
|
39
|
+
const strategy = asAuthUser(user)._strategy;
|
|
40
|
+
return typeof strategy === "string" && strategy.endsWith("-api-key");
|
|
41
|
+
};
|
|
42
|
+
const prepareMutation = async (req) => {
|
|
43
|
+
const options = getOptions(req);
|
|
44
|
+
if (!verifyMutationOrigin({
|
|
45
|
+
headers: req.headers,
|
|
46
|
+
options,
|
|
47
|
+
payload: req.payload
|
|
48
|
+
})) return "origin";
|
|
49
|
+
if (!isJsonContentType(req.headers)) return "invalidBody";
|
|
50
|
+
await addDataAndFileToRequest(req);
|
|
51
|
+
return null;
|
|
52
|
+
};
|
|
53
|
+
/** Only the literal `true` allows. A Payload `Where` object is deny, never allow-all. */
|
|
54
|
+
const isLiteralTrue = (value) => value === true;
|
|
55
|
+
const safeRedirect = (value, fallback) => {
|
|
56
|
+
if (typeof value !== "string" || !value.startsWith("/") || value.startsWith("//")) return fallback;
|
|
57
|
+
try {
|
|
58
|
+
if (new URL(value, "http://x").origin !== "http://x") return fallback;
|
|
59
|
+
} catch {
|
|
60
|
+
return fallback;
|
|
61
|
+
}
|
|
62
|
+
return value;
|
|
63
|
+
};
|
|
64
|
+
const trimReason = (value, maxLength) => {
|
|
65
|
+
if (typeof value !== "string") return;
|
|
66
|
+
const trimmed = value.trim().slice(0, maxLength);
|
|
67
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
68
|
+
};
|
|
69
|
+
const defaultRedirect = (req, collection) => {
|
|
70
|
+
if (collection === req.payload.config.admin.user) return req.payload.config.routes.admin;
|
|
71
|
+
return "/";
|
|
72
|
+
};
|
|
73
|
+
//#endregion
|
|
74
|
+
export { callerSid, defaultRedirect, fail, getOptions, isApiKeyStrategy, isLiteralTrue, json, prepareMutation, rejectGate, safeRedirect, trimReason };
|
|
75
|
+
|
|
76
|
+
//# sourceMappingURL=shared.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.js","names":[],"sources":["../../src/endpoints/shared.ts"],"sourcesContent":["import {\n\taddDataAndFileToRequest,\n\theadersWithCors,\n\ttype PayloadRequest,\n\ttype TypedUser,\n} from 'payload'\n\nimport { isJsonContentType, verifyMutationOrigin } from '../auth/origin'\nimport { getRegistry } from '../plugin/registry'\nimport { asAuthUser, type FailureCode, type ResolvedOptions } from '../types'\n\nexport const NO_STORE = { 'Cache-Control': 'no-store' }\n\nexport const json = ({\n\tbody,\n\tcookies = [],\n\treq,\n\tstatus,\n}: {\n\tbody: unknown\n\tcookies?: string[]\n\treq: PayloadRequest\n\tstatus: number\n}): Response => {\n\tconst headers = headersWithCors({ headers: new Headers(NO_STORE), req })\n\tfor (const cookie of cookies) {\n\t\theaders.append('Set-Cookie', cookie)\n\t}\n\treturn Response.json(body, { headers, status })\n}\n\nexport const fail = ({\n\tcookies,\n\terror,\n\treq,\n\tstatus,\n}: {\n\tcookies?: string[]\n\terror: FailureCode\n\treq: PayloadRequest\n\tstatus: number\n}): Response => json({ body: { error }, cookies, req, status })\n\nexport const rejectGate = (req: PayloadRequest, gate: FailureCode): Response =>\n\tfail({ error: gate, req, status: gate === 'invalidBody' ? 400 : 403 })\n\nexport const getOptions = (req: PayloadRequest): ResolvedOptions => {\n\tconst options = getRegistry(req.payload.config)\n\tif (!options) {\n\t\tthrow new Error('@10x-media/impersonation: plugin options were not registered')\n\t}\n\treturn options\n}\n\nexport const callerSid = (user: null | TypedUser | undefined): string | undefined => {\n\tif (!user) {\n\t\treturn undefined\n\t}\n\treturn asAuthUser(user)._sid\n}\n\nexport const isApiKeyStrategy = (user: TypedUser): boolean => {\n\tconst strategy = asAuthUser(user)._strategy\n\treturn typeof strategy === 'string' && strategy.endsWith('-api-key')\n}\n\nexport const prepareMutation = async (req: PayloadRequest): Promise<FailureCode | null> => {\n\tconst options = getOptions(req)\n\tif (!verifyMutationOrigin({ headers: req.headers, options, payload: req.payload })) {\n\t\treturn 'origin'\n\t}\n\tif (!isJsonContentType(req.headers)) {\n\t\treturn 'invalidBody'\n\t}\n\tawait addDataAndFileToRequest(req)\n\treturn null\n}\n\n/** Only the literal `true` allows. A Payload `Where` object is deny, never allow-all. */\nexport const isLiteralTrue = (value: unknown): value is true => value === true\n\nexport const safeRedirect = (value: unknown, fallback: string): string => {\n\tif (typeof value !== 'string' || !value.startsWith('/') || value.startsWith('//')) {\n\t\treturn fallback\n\t}\n\ttry {\n\t\tif (new URL(value, 'http://x').origin !== 'http://x') {\n\t\t\treturn fallback\n\t\t}\n\t} catch {\n\t\treturn fallback\n\t}\n\treturn value\n}\n\nexport const trimReason = (value: unknown, maxLength: number): string | undefined => {\n\tif (typeof value !== 'string') {\n\t\treturn undefined\n\t}\n\tconst trimmed = value.trim().slice(0, maxLength)\n\treturn trimmed.length > 0 ? trimmed : undefined\n}\n\nexport const defaultRedirect = (req: PayloadRequest, collection: string): string => {\n\tconst adminCollection = req.payload.config.admin.user\n\tif (collection === adminCollection) {\n\t\treturn req.payload.config.routes.admin\n\t}\n\treturn '/'\n}\n"],"mappings":";;;;;AAWA,MAAa,WAAW,EAAE,iBAAiB,WAAW;AAEtD,MAAa,QAAQ,EACpB,MACA,UAAU,CAAC,GACX,KACA,aAMe;CACf,MAAM,UAAU,gBAAgB;EAAE,SAAS,IAAI,QAAQ,QAAQ;EAAG;CAAI,CAAC;CACvE,KAAK,MAAM,UAAU,SACpB,QAAQ,OAAO,cAAc,MAAM;CAEpC,OAAO,SAAS,KAAK,MAAM;EAAE;EAAS;CAAO,CAAC;AAC/C;AAEA,MAAa,QAAQ,EACpB,SACA,OACA,KACA,aAMe,KAAK;CAAE,MAAM,EAAE,MAAM;CAAG;CAAS;CAAK;AAAO,CAAC;AAE9D,MAAa,cAAc,KAAqB,SAC/C,KAAK;CAAE,OAAO;CAAM;CAAK,QAAQ,SAAS,gBAAgB,MAAM;AAAI,CAAC;AAEtE,MAAa,cAAc,QAAyC;CACnE,MAAM,UAAU,YAAY,IAAI,QAAQ,MAAM;CAC9C,IAAI,CAAC,SACJ,MAAM,IAAI,MAAM,8DAA8D;CAE/E,OAAO;AACR;AAEA,MAAa,aAAa,SAA2D;CACpF,IAAI,CAAC,MACJ;CAED,OAAO,WAAW,IAAI,EAAE;AACzB;AAEA,MAAa,oBAAoB,SAA6B;CAC7D,MAAM,WAAW,WAAW,IAAI,EAAE;CAClC,OAAO,OAAO,aAAa,YAAY,SAAS,SAAS,UAAU;AACpE;AAEA,MAAa,kBAAkB,OAAO,QAAqD;CAC1F,MAAM,UAAU,WAAW,GAAG;CAC9B,IAAI,CAAC,qBAAqB;EAAE,SAAS,IAAI;EAAS;EAAS,SAAS,IAAI;CAAQ,CAAC,GAChF,OAAO;CAER,IAAI,CAAC,kBAAkB,IAAI,OAAO,GACjC,OAAO;CAER,MAAM,wBAAwB,GAAG;CACjC,OAAO;AACR;;AAGA,MAAa,iBAAiB,UAAkC,UAAU;AAE1E,MAAa,gBAAgB,OAAgB,aAA6B;CACzE,IAAI,OAAO,UAAU,YAAY,CAAC,MAAM,WAAW,GAAG,KAAK,MAAM,WAAW,IAAI,GAC/E,OAAO;CAER,IAAI;EACH,IAAI,IAAI,IAAI,OAAO,UAAU,EAAE,WAAW,YACzC,OAAO;CAET,QAAQ;EACP,OAAO;CACR;CACA,OAAO;AACR;AAEA,MAAa,cAAc,OAAgB,cAA0C;CACpF,IAAI,OAAO,UAAU,UACpB;CAED,MAAM,UAAU,MAAM,KAAK,EAAE,MAAM,GAAG,SAAS;CAC/C,OAAO,QAAQ,SAAS,IAAI,UAAU,KAAA;AACvC;AAEA,MAAa,mBAAmB,KAAqB,eAA+B;CAEnF,IAAI,eADoB,IAAI,QAAQ,OAAO,MAAM,MAEhD,OAAO,IAAI,QAAQ,OAAO,OAAO;CAElC,OAAO;AACR"}
|