payload-subscribers-plugin 0.0.9 → 0.0.11

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 (54) hide show
  1. package/README.md +233 -26
  2. package/dist/components/app/RequestMagicLink.d.ts +21 -4
  3. package/dist/components/app/RequestMagicLink.js +11 -56
  4. package/dist/components/app/RequestMagicLink.js.map +1 -1
  5. package/dist/components/app/RequestOrSubscribe.d.ts +21 -4
  6. package/dist/components/app/RequestOrSubscribe.js +5 -8
  7. package/dist/components/app/RequestOrSubscribe.js.map +1 -1
  8. package/dist/components/app/SelectOptInChannels.d.ts +26 -3
  9. package/dist/components/app/SelectOptInChannels.js +4 -1
  10. package/dist/components/app/SelectOptInChannels.js.map +1 -1
  11. package/dist/components/app/Subscribe.d.ts +26 -5
  12. package/dist/components/app/Subscribe.js +40 -73
  13. package/dist/components/app/Subscribe.js.map +1 -1
  14. package/dist/components/app/SubscriberMenu.d.ts +18 -6
  15. package/dist/components/app/SubscriberMenu.js +9 -8
  16. package/dist/components/app/SubscriberMenu.js.map +1 -1
  17. package/dist/components/app/Unsubscribe.d.ts +26 -12
  18. package/dist/components/app/Unsubscribe.js +63 -127
  19. package/dist/components/app/Unsubscribe.js.map +1 -1
  20. package/dist/components/app/VerifyMagicLink.d.ts +32 -14
  21. package/dist/components/app/VerifyMagicLink.js +60 -110
  22. package/dist/components/app/VerifyMagicLink.js.map +1 -1
  23. package/dist/contexts/SubscriberProvider.js +1 -1
  24. package/dist/contexts/SubscriberProvider.js.map +1 -1
  25. package/dist/endpoints/requestMagicLink.d.ts +6 -4
  26. package/dist/endpoints/requestMagicLink.js +16 -12
  27. package/dist/endpoints/requestMagicLink.js.map +1 -1
  28. package/dist/endpoints/subscribe.d.ts +6 -2
  29. package/dist/endpoints/subscribe.js +19 -17
  30. package/dist/endpoints/subscribe.js.map +1 -1
  31. package/dist/endpoints/unsubscribe.js +17 -15
  32. package/dist/endpoints/unsubscribe.js.map +1 -1
  33. package/dist/exports/ui.d.ts +4 -0
  34. package/dist/exports/ui.js +4 -0
  35. package/dist/exports/ui.js.map +1 -1
  36. package/dist/helpers/utilities.d.ts +1 -0
  37. package/dist/helpers/utilities.js +6 -0
  38. package/dist/helpers/utilities.js.map +1 -0
  39. package/dist/hooks/useRequestMagicLink.d.ts +35 -0
  40. package/dist/hooks/useRequestMagicLink.js +61 -0
  41. package/dist/hooks/useRequestMagicLink.js.map +1 -0
  42. package/dist/hooks/useSubscribe.d.ts +38 -0
  43. package/dist/hooks/useSubscribe.js +62 -0
  44. package/dist/hooks/useSubscribe.js.map +1 -0
  45. package/dist/hooks/useUnsubscribe.d.ts +43 -0
  46. package/dist/hooks/useUnsubscribe.js +86 -0
  47. package/dist/hooks/useUnsubscribe.js.map +1 -0
  48. package/dist/hooks/useVerifyMagicLink.d.ts +31 -0
  49. package/dist/hooks/useVerifyMagicLink.js +74 -0
  50. package/dist/hooks/useVerifyMagicLink.js.map +1 -0
  51. package/dist/index.d.ts +5 -1
  52. package/dist/index.js +12 -8
  53. package/dist/index.js.map +1 -1
  54. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/contexts/SubscriberProvider.tsx"],"sourcesContent":["'use client'\n\nimport { PayloadSDK } from '@payloadcms/sdk'\nimport { type ReactNode, useCallback, useEffect } from 'react'\nimport { createContext, useContext, useMemo, useState } from 'react'\n\nimport type { Config, Subscriber } from '../copied/payload-types.js'\n\nimport { useServerUrl } from '../react-hooks/useServerUrl.js'\n\n/** Value provided by SubscriberProvider: current subscriber, auth state, and actions. */\nexport type SubscriberContextType = {\n isLoaded: boolean\n logOut: () => void\n permissions: any\n refreshSubscriber: () => void\n subscriber: null | Subscriber\n}\n\nconst SubscriberContext = createContext<SubscriberContextType | undefined>(undefined)\n\n/** Props for SubscriberProvider. */\ninterface ProviderProps {\n children?: ReactNode\n}\n\n/**\n * Provider that fetches and holds the current subscriber auth state (via POST /api/subscriberAuth).\n * Exposes subscriber, permissions, refreshSubscriber, and logOut to descendants. Must wrap any\n * component that uses useSubscriber().\n *\n * @param props.children - React tree to wrap\n * @returns SubscriberContext.Provider with current auth state and actions\n */\nexport function SubscriberProvider({ children }: ProviderProps) {\n // eslint-disable-next-line\n const [subscriber, setSubscriber] = useState<null | (Subscriber & { optIns: string[] })>(null)\n\n const { serverURL } = useServerUrl()\n\n // Keep track of if the selection content is loaded yet\n const [isLoaded, setIsLoaded] = useState(false)\n\n const [permissions, setPermissions] = useState<any>()\n\n const refreshSubscriber = useCallback(async () => {\n const initSubscriber = async () => {\n setIsLoaded(false)\n try {\n const authResponse = await fetch('/api/subscriberAuth', {\n // body: JSON.stringify({}),\n method: 'POST',\n })\n\n if (authResponse.ok) {\n // Call the server function to get the user data\n const { permissions, subscriber } = await authResponse.json()\n // console.log(`subscriber = `, subscriber)\n // console.log(`permissions = `, permissions)\n setPermissions(permissions)\n setSubscriber(subscriber)\n } else {\n setPermissions(null)\n setSubscriber(null)\n }\n } catch (error: unknown) {\n console.log(`authResponse error`, error)\n }\n setIsLoaded(true)\n }\n await initSubscriber()\n }, [serverURL])\n\n const logOut = useCallback(async () => {\n setIsLoaded(false)\n try {\n // const sdk = new PayloadSDK<Config>({\n // baseURL: serverURL || '',\n // })\n // const logoutResponse = await sdk.request({\n // json: {},\n // method: 'POST',\n // path: '/api/logout',\n // })\n // Unsure why sdk isn't working here\n const logoutResponse = await fetch('/api/logout', {\n method: 'POST',\n })\n\n // console.log(`logoutResponse`, logoutResponse)\n\n if (logoutResponse.ok) {\n setSubscriber(null)\n setPermissions(null)\n }\n } catch (error: unknown) {\n console.log(`logoutResponse error`, error)\n }\n setIsLoaded(true)\n }, [])\n\n useEffect(() => {\n void refreshSubscriber()\n }, [refreshSubscriber]) // Empty dependency array for mount/unmount\n\n // Memoize the value to prevent unnecessary re-renders in consumers\n const contextValue: SubscriberContextType = useMemo(\n () => ({\n isLoaded,\n logOut,\n permissions,\n refreshSubscriber,\n subscriber,\n }),\n [isLoaded, logOut, permissions, refreshSubscriber, subscriber],\n )\n\n return <SubscriberContext.Provider value={contextValue}>{children}</SubscriberContext.Provider>\n}\n\n/**\n * Consumes SubscriberContext. Use only inside a SubscriberProvider.\n *\n * @returns Current subscriber (or null), permissions, isLoaded, refreshSubscriber, and logOut\n * @throws Error if used outside SubscriberProvider\n */\nexport function useSubscriber() {\n const context = useContext(SubscriberContext)\n if (context === undefined) {\n throw new Error('useSubscriber must be used within a SubscriberProvider')\n }\n return context\n}\n"],"names":["useCallback","useEffect","createContext","useContext","useMemo","useState","useServerUrl","SubscriberContext","undefined","SubscriberProvider","children","subscriber","setSubscriber","serverURL","isLoaded","setIsLoaded","permissions","setPermissions","refreshSubscriber","initSubscriber","authResponse","fetch","method","ok","json","error","console","log","logOut","logoutResponse","contextValue","Provider","value","useSubscriber","context","Error"],"mappings":"AAAA;;AAGA,SAAyBA,WAAW,EAAEC,SAAS,QAAQ,QAAO;AAC9D,SAASC,aAAa,EAAEC,UAAU,EAAEC,OAAO,EAAEC,QAAQ,QAAQ,QAAO;AAIpE,SAASC,YAAY,QAAQ,iCAAgC;AAW7D,MAAMC,kCAAoBL,cAAiDM;AAO3E;;;;;;;CAOC,GACD,OAAO,SAASC,mBAAmB,EAAEC,QAAQ,EAAiB;IAC5D,2BAA2B;IAC3B,MAAM,CAACC,YAAYC,cAAc,GAAGP,SAAqD;IAEzF,MAAM,EAAEQ,SAAS,EAAE,GAAGP;IAEtB,uDAAuD;IACvD,MAAM,CAACQ,UAAUC,YAAY,GAAGV,SAAS;IAEzC,MAAM,CAACW,aAAaC,eAAe,GAAGZ;IAEtC,MAAMa,oBAAoBlB,YAAY;QACpC,MAAMmB,iBAAiB;YACrBJ,YAAY;YACZ,IAAI;gBACF,MAAMK,eAAe,MAAMC,MAAM,uBAAuB;oBACtD,4BAA4B;oBAC5BC,QAAQ;gBACV;gBAEA,IAAIF,aAAaG,EAAE,EAAE;oBACnB,gDAAgD;oBAChD,MAAM,EAAEP,WAAW,EAAEL,UAAU,EAAE,GAAG,MAAMS,aAAaI,IAAI;oBAC3D,2CAA2C;oBAC3C,6CAA6C;oBAC7CP,eAAeD;oBACfJ,cAAcD;gBAChB,OAAO;oBACLM,eAAe;oBACfL,cAAc;gBAChB;YACF,EAAE,OAAOa,OAAgB;gBACvBC,QAAQC,GAAG,CAAC,CAAC,kBAAkB,CAAC,EAAEF;YACpC;YACAV,YAAY;QACd;QACA,MAAMI;IACR,GAAG;QAACN;KAAU;IAEd,MAAMe,SAAS5B,YAAY;QACzBe,YAAY;QACZ,IAAI;YACF,uCAAuC;YACvC,8BAA8B;YAC9B,KAAK;YACL,6CAA6C;YAC7C,cAAc;YACd,oBAAoB;YACpB,yBAAyB;YACzB,KAAK;YACL,oCAAoC;YACpC,MAAMc,iBAAiB,MAAMR,MAAM,eAAe;gBAChDC,QAAQ;YACV;YAEA,gDAAgD;YAEhD,IAAIO,eAAeN,EAAE,EAAE;gBACrBX,cAAc;gBACdK,eAAe;YACjB;QACF,EAAE,OAAOQ,OAAgB;YACvBC,QAAQC,GAAG,CAAC,CAAC,oBAAoB,CAAC,EAAEF;QACtC;QACAV,YAAY;IACd,GAAG,EAAE;IAELd,UAAU;QACR,KAAKiB;IACP,GAAG;QAACA;KAAkB,GAAE,2CAA2C;IAEnE,mEAAmE;IACnE,MAAMY,eAAsC1B,QAC1C,IAAO,CAAA;YACLU;YACAc;YACAZ;YACAE;YACAP;QACF,CAAA,GACA;QAACG;QAAUc;QAAQZ;QAAaE;QAAmBP;KAAW;IAGhE,qBAAO,KAACJ,kBAAkBwB,QAAQ;QAACC,OAAOF;kBAAepB;;AAC3D;AAEA;;;;;CAKC,GACD,OAAO,SAASuB;IACd,MAAMC,UAAU/B,WAAWI;IAC3B,IAAI2B,YAAY1B,WAAW;QACzB,MAAM,IAAI2B,MAAM;IAClB;IACA,OAAOD;AACT"}
1
+ {"version":3,"sources":["../../src/contexts/SubscriberProvider.tsx"],"sourcesContent":["'use client'\n\nimport { PayloadSDK } from '@payloadcms/sdk'\nimport { type ReactNode, useCallback, useEffect } from 'react'\nimport { createContext, useContext, useMemo, useState } from 'react'\n\nimport type { Config, Subscriber } from '../copied/payload-types.js'\n\nimport { useServerUrl } from '../react-hooks/useServerUrl.js'\n\n/** Value provided by SubscriberProvider: current subscriber, auth state, and actions. */\nexport type SubscriberContextType = {\n isLoaded: boolean\n logOut: () => void\n permissions: any\n refreshSubscriber: () => void\n subscriber: null | Subscriber\n}\n\nconst SubscriberContext = createContext<SubscriberContextType | undefined>(undefined)\n\n/** Props for SubscriberProvider. */\ninterface ProviderProps {\n children?: ReactNode\n}\n\n/**\n * Provider that fetches and holds the current subscriber auth state (via POST /api/subscriberAuth).\n * Exposes subscriber, permissions, refreshSubscriber, and logOut to descendants. Must wrap any\n * component that uses useSubscriber().\n *\n * @param props.children - React tree to wrap\n * @returns SubscriberContext.Provider with current auth state and actions\n */\nexport function SubscriberProvider({ children }: ProviderProps) {\n // eslint-disable-next-line\n const [subscriber, setSubscriber] = useState<null | (Subscriber & { optIns: string[] })>(null)\n\n const { serverURL } = useServerUrl()\n\n // Keep track of if the selection content is loaded yet\n const [isLoaded, setIsLoaded] = useState(false)\n\n const [permissions, setPermissions] = useState<any>()\n\n const refreshSubscriber = useCallback(async () => {\n const initSubscriber = async () => {\n setIsLoaded(false)\n try {\n const authResponse = await fetch('/api/subscriberAuth', {\n // body: JSON.stringify({}),\n method: 'POST',\n })\n\n if (authResponse.ok) {\n // Call the server function to get the user data\n const { permissions, subscriber } = await authResponse.json()\n // console.log(`subscriber = `, subscriber)\n // console.log(`permissions = `, permissions)\n setPermissions(permissions)\n setSubscriber(subscriber)\n } else {\n setPermissions(null)\n setSubscriber(null)\n }\n } catch (error: unknown) {\n console.log(`authResponse error`, error)\n }\n setIsLoaded(true)\n }\n await initSubscriber()\n }, [serverURL])\n\n const logOut = useCallback(async () => {\n setIsLoaded(false)\n try {\n // const sdk = new PayloadSDK<Config>({\n // baseURL: serverURL || '',\n // })\n // const logoutResponse = await sdk.request({\n // json: {},\n // method: 'POST',\n // path: '/api/logout',\n // })\n // Unsure why sdk isn't working here\n const logoutResponse = await fetch('/api/logout', {\n method: 'POST',\n })\n\n // console.log(`logoutResponse`, logoutResponse)\n\n if (logoutResponse.ok) {\n setSubscriber(null)\n setPermissions(null)\n }\n } catch (error: unknown) {\n console.log(`logoutResponse error`, error)\n }\n setIsLoaded(true)\n }, [])\n\n useEffect(() => {\n void refreshSubscriber()\n }, [refreshSubscriber])\n\n // Memoize the value to prevent unnecessary re-renders in consumers\n const contextValue: SubscriberContextType = useMemo(\n () => ({\n isLoaded,\n logOut,\n permissions,\n refreshSubscriber,\n subscriber,\n }),\n [isLoaded, logOut, permissions, refreshSubscriber, subscriber],\n )\n\n return <SubscriberContext.Provider value={contextValue}>{children}</SubscriberContext.Provider>\n}\n\n/**\n * Consumes SubscriberContext. Use only inside a SubscriberProvider.\n *\n * @returns Current subscriber (or null), permissions, isLoaded, refreshSubscriber, and logOut\n * @throws Error if used outside SubscriberProvider\n */\nexport function useSubscriber() {\n const context = useContext(SubscriberContext)\n if (context === undefined) {\n throw new Error('useSubscriber must be used within a SubscriberProvider')\n }\n return context\n}\n"],"names":["useCallback","useEffect","createContext","useContext","useMemo","useState","useServerUrl","SubscriberContext","undefined","SubscriberProvider","children","subscriber","setSubscriber","serverURL","isLoaded","setIsLoaded","permissions","setPermissions","refreshSubscriber","initSubscriber","authResponse","fetch","method","ok","json","error","console","log","logOut","logoutResponse","contextValue","Provider","value","useSubscriber","context","Error"],"mappings":"AAAA;;AAGA,SAAyBA,WAAW,EAAEC,SAAS,QAAQ,QAAO;AAC9D,SAASC,aAAa,EAAEC,UAAU,EAAEC,OAAO,EAAEC,QAAQ,QAAQ,QAAO;AAIpE,SAASC,YAAY,QAAQ,iCAAgC;AAW7D,MAAMC,kCAAoBL,cAAiDM;AAO3E;;;;;;;CAOC,GACD,OAAO,SAASC,mBAAmB,EAAEC,QAAQ,EAAiB;IAC5D,2BAA2B;IAC3B,MAAM,CAACC,YAAYC,cAAc,GAAGP,SAAqD;IAEzF,MAAM,EAAEQ,SAAS,EAAE,GAAGP;IAEtB,uDAAuD;IACvD,MAAM,CAACQ,UAAUC,YAAY,GAAGV,SAAS;IAEzC,MAAM,CAACW,aAAaC,eAAe,GAAGZ;IAEtC,MAAMa,oBAAoBlB,YAAY;QACpC,MAAMmB,iBAAiB;YACrBJ,YAAY;YACZ,IAAI;gBACF,MAAMK,eAAe,MAAMC,MAAM,uBAAuB;oBACtD,4BAA4B;oBAC5BC,QAAQ;gBACV;gBAEA,IAAIF,aAAaG,EAAE,EAAE;oBACnB,gDAAgD;oBAChD,MAAM,EAAEP,WAAW,EAAEL,UAAU,EAAE,GAAG,MAAMS,aAAaI,IAAI;oBAC3D,2CAA2C;oBAC3C,6CAA6C;oBAC7CP,eAAeD;oBACfJ,cAAcD;gBAChB,OAAO;oBACLM,eAAe;oBACfL,cAAc;gBAChB;YACF,EAAE,OAAOa,OAAgB;gBACvBC,QAAQC,GAAG,CAAC,CAAC,kBAAkB,CAAC,EAAEF;YACpC;YACAV,YAAY;QACd;QACA,MAAMI;IACR,GAAG;QAACN;KAAU;IAEd,MAAMe,SAAS5B,YAAY;QACzBe,YAAY;QACZ,IAAI;YACF,uCAAuC;YACvC,8BAA8B;YAC9B,KAAK;YACL,6CAA6C;YAC7C,cAAc;YACd,oBAAoB;YACpB,yBAAyB;YACzB,KAAK;YACL,oCAAoC;YACpC,MAAMc,iBAAiB,MAAMR,MAAM,eAAe;gBAChDC,QAAQ;YACV;YAEA,gDAAgD;YAEhD,IAAIO,eAAeN,EAAE,EAAE;gBACrBX,cAAc;gBACdK,eAAe;YACjB;QACF,EAAE,OAAOQ,OAAgB;YACvBC,QAAQC,GAAG,CAAC,CAAC,oBAAoB,CAAC,EAAEF;QACtC;QACAV,YAAY;IACd,GAAG,EAAE;IAELd,UAAU;QACR,KAAKiB;IACP,GAAG;QAACA;KAAkB;IAEtB,mEAAmE;IACnE,MAAMY,eAAsC1B,QAC1C,IAAO,CAAA;YACLU;YACAc;YACAZ;YACAE;YACAP;QACF,CAAA,GACA;QAACG;QAAUc;QAAQZ;QAAaE;QAAmBP;KAAW;IAGhE,qBAAO,KAACJ,kBAAkBwB,QAAQ;QAACC,OAAOF;kBAAepB;;AAC3D;AAEA;;;;;CAKC,GACD,OAAO,SAASuB;IACd,MAAMC,UAAU/B,WAAWI;IAC3B,IAAI2B,YAAY1B,WAAW;QACzB,MAAM,IAAI2B,MAAM;IAClB;IACA,OAAOD;AACT"}
@@ -11,12 +11,14 @@ export type RequestMagicLinkResponse = {
11
11
  * Sends a magic-link email to the given address (creates a pending subscriber if needed).
12
12
  *
13
13
  * @param options - Config options for the endpoint
14
- * @param options.subscribersCollectionSlug - Collection slug for subscribers (default from Subscribers collection)
15
- * @param options.unsubscribeUrl - The URL to use for unsubscribe links
14
+ * @param options.subscribersCollectionSlug - (required) Collection slug for subscribers (default from Subscribers collection)
15
+ * @param options.unsubscribeURL - (optional) The URL to use for unsubscribe links
16
+ * @param options.verifyURL - (required) The URL to use for verify links
16
17
  * @returns Payload Endpoint config for POST /emailToken
17
18
  */
18
- declare function createEndpointRequestMagicLink({ subscribersCollectionSlug, unsubscribeUrl, }: {
19
+ declare function createEndpointRequestMagicLink({ subscribersCollectionSlug, unsubscribeURL, verifyURL, }: {
19
20
  subscribersCollectionSlug: CollectionSlug;
20
- unsubscribeUrl?: URL;
21
+ unsubscribeURL?: URL;
22
+ verifyURL: URL;
21
23
  }): Endpoint;
22
24
  export default createEndpointRequestMagicLink;
@@ -5,24 +5,28 @@ import { getHmacHash, getTokenAndHash } from '../helpers/token.js';
5
5
  * Sends a magic-link email to the given address (creates a pending subscriber if needed).
6
6
  *
7
7
  * @param options - Config options for the endpoint
8
- * @param options.subscribersCollectionSlug - Collection slug for subscribers (default from Subscribers collection)
9
- * @param options.unsubscribeUrl - The URL to use for unsubscribe links
8
+ * @param options.subscribersCollectionSlug - (required) Collection slug for subscribers (default from Subscribers collection)
9
+ * @param options.unsubscribeURL - (optional) The URL to use for unsubscribe links
10
+ * @param options.verifyURL - (required) The URL to use for verify links
10
11
  * @returns Payload Endpoint config for POST /emailToken
11
- */ function createEndpointRequestMagicLink({ subscribersCollectionSlug = defaultCollectionSlug, unsubscribeUrl }) {
12
+ */ function createEndpointRequestMagicLink({ subscribersCollectionSlug = defaultCollectionSlug, unsubscribeURL, verifyURL }) {
13
+ // verifyURL required
14
+ if (!verifyURL || !verifyURL.href) {
15
+ throw new Error('A verify URL is required');
16
+ }
12
17
  /**
13
- * Handler for POST /emailToken. Accepts email and verifyUrl, creates/updates a pending
18
+ * Handler for POST /emailToken. Takes an email parameter. Creates/updates a pending
14
19
  * subscriber with a verification token, and sends a magic-link email.
15
20
  *
16
- * @param req - Payload request; body must include `email` and `verifyUrl`
21
+ * @param req - Payload request. Expects body to be a json object { email, verifyData }
17
22
  * @returns 200 with `emailResult` and `now` on success; 400 with `error` and `now` on bad data or email failure
18
23
  */ const requestMagicLinkHandler = async (req)=>{
19
24
  const data = req?.json ? await req.json() : {};
20
- const { email, verifyUrl } = data // if by POST data
25
+ const { email, verifyData } = data // if by POST data
21
26
  ;
22
- // const { email } = req.routeParams // if by path
23
- if (!email || !verifyUrl) {
27
+ if (!email) {
24
28
  return Response.json({
25
- error: 'Bad data',
29
+ error: 'Email required',
26
30
  now: new Date().toISOString()
27
31
  }, {
28
32
  status: 400
@@ -55,7 +59,7 @@ import { getHmacHash, getTokenAndHash } from '../helpers/token.js';
55
59
  });
56
60
  if (!createResult) {
57
61
  return Response.json({
58
- error: 'Bad data',
62
+ error: 'Error creating subscriber',
59
63
  now: new Date().toISOString()
60
64
  }, {
61
65
  status: 400
@@ -81,8 +85,8 @@ import { getHmacHash, getTokenAndHash } from '../helpers/token.js';
81
85
  });
82
86
  const { hashToken: unsubscribeHash } = getHmacHash(email);
83
87
  // Send email
84
- const magicLink = `${verifyUrl}${verifyUrl.search ? '&' : '?'}token=${token}&email=${email}`;
85
- const unsubscribeLink = !unsubscribeUrl ? undefined : `${unsubscribeUrl?.href}${unsubscribeUrl?.search ? '&' : '?'}email=${email}&hash=${unsubscribeHash}`;
88
+ const magicLink = `${verifyURL?.href}${verifyURL?.search ? '&' : '?'}token=${encodeURIComponent(token)}&email=${encodeURIComponent(email)}&verifyData=${encodeURIComponent(verifyData)}`;
89
+ const unsubscribeLink = !unsubscribeURL ? undefined : `${unsubscribeURL?.href}${unsubscribeURL?.search ? '&' : '?'}email=${encodeURIComponent(email)}&hash=${encodeURIComponent(unsubscribeHash)}`;
86
90
  const subject = data.subject || 'Your Magic Login Link';
87
91
  const message = `
88
92
  ${data.message || '<p>You requested a magic link to log in. Click the button below</p>'}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/endpoints/requestMagicLink.ts"],"sourcesContent":["import type { CollectionSlug, Endpoint, PayloadHandler, PayloadRequest, TypedUser } from 'payload'\n\nimport crypto from 'crypto'\n\nimport { defaultCollectionSlug } from '../collections/Subscribers.js'\nimport { getHmacHash, getTokenAndHash } from '../helpers/token.js'\n\nexport type RequestMagicLinkResponse =\n | {\n emailResult: any\n now: string\n }\n | {\n error: string\n now: string\n }\n\n/**\n * Factory that creates the request-magic-link endpoint config and handler.\n * Sends a magic-link email to the given address (creates a pending subscriber if needed).\n *\n * @param options - Config options for the endpoint\n * @param options.subscribersCollectionSlug - Collection slug for subscribers (default from Subscribers collection)\n * @param options.unsubscribeUrl - The URL to use for unsubscribe links\n * @returns Payload Endpoint config for POST /emailToken\n */\nfunction createEndpointRequestMagicLink({\n subscribersCollectionSlug = defaultCollectionSlug,\n unsubscribeUrl,\n}: {\n subscribersCollectionSlug: CollectionSlug\n unsubscribeUrl?: URL\n}): Endpoint {\n /**\n * Handler for POST /emailToken. Accepts email and verifyUrl, creates/updates a pending\n * subscriber with a verification token, and sends a magic-link email.\n *\n * @param req - Payload request; body must include `email` and `verifyUrl`\n * @returns 200 with `emailResult` and `now` on success; 400 with `error` and `now` on bad data or email failure\n */\n const requestMagicLinkHandler: PayloadHandler = async (req: PayloadRequest) => {\n const data = req?.json ? await req.json() : {}\n const { email, verifyUrl } = data // if by POST data\n // const { email } = req.routeParams // if by path\n\n if (!email || !verifyUrl) {\n return Response.json(\n { error: 'Bad data', now: new Date().toISOString() } as RequestMagicLinkResponse,\n { status: 400 },\n )\n }\n\n const userResults = await req.payload.find({\n collection: subscribersCollectionSlug,\n where: {\n email: { equals: email },\n },\n })\n const user = userResults.docs[0] as TypedUser\n\n if (!user) {\n //\n // Create subscriber with status 'pending',\n // and an invisible unknowable password,\n //\n const { tokenHash: tokenHash2 } = getTokenAndHash() // Unknowable\n const createResult = await req.payload.create({\n collection: subscribersCollectionSlug,\n data: {\n email,\n password: tokenHash2,\n status: 'pending',\n },\n draft: false,\n })\n if (!createResult) {\n return Response.json(\n { error: 'Bad data', now: new Date().toISOString() } as RequestMagicLinkResponse,\n { status: 400 },\n )\n }\n }\n\n // Update user with verificationToken\n // const token = crypto.randomBytes(32).toString('hex')\n // const tokenHash = crypto.createHash('sha256').update(token).digest('hex')\n // const expiresAt = new Date(Date.now() + 15 * 60 * 1000) // 15 mins\n const { expiresAt, token, tokenHash } = getTokenAndHash(15 * 60 * 1000)\n await req.payload.update({\n collection: subscribersCollectionSlug,\n data: {\n verificationToken: tokenHash,\n verificationTokenExpires: expiresAt?.toISOString(),\n },\n where: {\n email: { equals: user.email },\n },\n })\n const { hashToken: unsubscribeHash } = getHmacHash(email)\n\n // Send email\n const magicLink = `${verifyUrl}${verifyUrl.search ? '&' : '?'}token=${token}&email=${email}`\n const unsubscribeLink = !unsubscribeUrl\n ? undefined\n : `${unsubscribeUrl?.href}${unsubscribeUrl?.search ? '&' : '?'}email=${email}&hash=${unsubscribeHash}`\n const subject = data.subject || 'Your Magic Login Link'\n const message = `\n ${data.message || '<p>You requested a magic link to log in. Click the button below</p>'}\n <p><a href=\"${magicLink}\"><button><b>Login</b></button></a></p>\n ${unsubscribeLink ? `<p>Click here to <a href=\"${unsubscribeLink}\">unsubscribe</a></p>` : ``}\n `\n\n const emailResult = await req.payload.sendEmail({\n html: message,\n subject,\n to: user.email,\n })\n // req.payload.logger.info(`email result: ${JSON.stringify(emailResult)}`)\n // return data; // Return data to allow normal submission if needed\n if (!emailResult) {\n return Response.json(\n {\n error: 'Unknown email result',\n now: new Date().toISOString(),\n } as RequestMagicLinkResponse,\n { status: 400 },\n )\n }\n req.payload.logger.info(`requestMagicLinkHandler email sent \\n ${magicLink}`)\n return Response.json({\n emailResult,\n now: new Date().toISOString(),\n } as RequestMagicLinkResponse)\n }\n\n /** Endpoint config for requesting a magic link. Mount as POST /emailToken. */\n const requestMagicLinkEndpoint: Endpoint = {\n handler: requestMagicLinkHandler,\n method: 'post',\n path: '/emailToken',\n }\n\n return requestMagicLinkEndpoint\n}\n\nexport default createEndpointRequestMagicLink\n"],"names":["defaultCollectionSlug","getHmacHash","getTokenAndHash","createEndpointRequestMagicLink","subscribersCollectionSlug","unsubscribeUrl","requestMagicLinkHandler","req","data","json","email","verifyUrl","Response","error","now","Date","toISOString","status","userResults","payload","find","collection","where","equals","user","docs","tokenHash","tokenHash2","createResult","create","password","draft","expiresAt","token","update","verificationToken","verificationTokenExpires","hashToken","unsubscribeHash","magicLink","search","unsubscribeLink","undefined","href","subject","message","emailResult","sendEmail","html","to","logger","info","requestMagicLinkEndpoint","handler","method","path"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/endpoints/requestMagicLink.ts"],"sourcesContent":["import type { CollectionSlug, Endpoint, PayloadHandler, PayloadRequest, TypedUser } from 'payload'\n\nimport { defaultCollectionSlug } from '../collections/Subscribers.js'\nimport { getHmacHash, getTokenAndHash } from '../helpers/token.js'\n\nexport type RequestMagicLinkResponse =\n | {\n emailResult: any\n now: string\n }\n | {\n error: string\n now: string\n }\n\n/**\n * Factory that creates the request-magic-link endpoint config and handler.\n * Sends a magic-link email to the given address (creates a pending subscriber if needed).\n *\n * @param options - Config options for the endpoint\n * @param options.subscribersCollectionSlug - (required) Collection slug for subscribers (default from Subscribers collection)\n * @param options.unsubscribeURL - (optional) The URL to use for unsubscribe links\n * @param options.verifyURL - (required) The URL to use for verify links\n * @returns Payload Endpoint config for POST /emailToken\n */\nfunction createEndpointRequestMagicLink({\n subscribersCollectionSlug = defaultCollectionSlug,\n unsubscribeURL,\n verifyURL,\n}: {\n subscribersCollectionSlug: CollectionSlug\n unsubscribeURL?: URL\n verifyURL: URL\n}): Endpoint {\n // verifyURL required\n if (!verifyURL || !verifyURL.href) {\n throw new Error('A verify URL is required')\n }\n /**\n * Handler for POST /emailToken. Takes an email parameter. Creates/updates a pending\n * subscriber with a verification token, and sends a magic-link email.\n *\n * @param req - Payload request. Expects body to be a json object { email, verifyData }\n * @returns 200 with `emailResult` and `now` on success; 400 with `error` and `now` on bad data or email failure\n */\n const requestMagicLinkHandler: PayloadHandler = async (req: PayloadRequest) => {\n const data = req?.json ? await req.json() : {}\n const { email, verifyData } = data // if by POST data\n\n if (!email) {\n return Response.json(\n {\n error: 'Email required',\n now: new Date().toISOString(),\n } as RequestMagicLinkResponse,\n { status: 400 },\n )\n }\n\n const userResults = await req.payload.find({\n collection: subscribersCollectionSlug,\n where: {\n email: { equals: email },\n },\n })\n const user = userResults.docs[0] as TypedUser\n\n if (!user) {\n //\n // Create subscriber with status 'pending',\n // and an invisible unknowable password,\n //\n const { tokenHash: tokenHash2 } = getTokenAndHash() // Unknowable\n const createResult = await req.payload.create({\n collection: subscribersCollectionSlug,\n data: {\n email,\n password: tokenHash2,\n status: 'pending',\n },\n draft: false,\n })\n if (!createResult) {\n return Response.json(\n {\n error: 'Error creating subscriber',\n now: new Date().toISOString(),\n } as RequestMagicLinkResponse,\n { status: 400 },\n )\n }\n }\n\n // Update user with verificationToken\n // const token = crypto.randomBytes(32).toString('hex')\n // const tokenHash = crypto.createHash('sha256').update(token).digest('hex')\n // const expiresAt = new Date(Date.now() + 15 * 60 * 1000) // 15 mins\n const { expiresAt, token, tokenHash } = getTokenAndHash(15 * 60 * 1000)\n await req.payload.update({\n collection: subscribersCollectionSlug,\n data: {\n verificationToken: tokenHash,\n verificationTokenExpires: expiresAt?.toISOString(),\n },\n where: {\n email: { equals: user.email },\n },\n })\n const { hashToken: unsubscribeHash } = getHmacHash(email)\n\n // Send email\n const magicLink = `${verifyURL?.href}${verifyURL?.search ? '&' : '?'}token=${encodeURIComponent(token)}&email=${encodeURIComponent(email)}&verifyData=${encodeURIComponent(verifyData)}`\n const unsubscribeLink = !unsubscribeURL\n ? undefined\n : `${unsubscribeURL?.href}${unsubscribeURL?.search ? '&' : '?'}email=${encodeURIComponent(email)}&hash=${encodeURIComponent(unsubscribeHash)}`\n const subject = data.subject || 'Your Magic Login Link'\n const message = `\n ${data.message || '<p>You requested a magic link to log in. Click the button below</p>'}\n <p><a href=\"${magicLink}\"><button><b>Login</b></button></a></p>\n ${unsubscribeLink ? `<p>Click here to <a href=\"${unsubscribeLink}\">unsubscribe</a></p>` : ``}\n `\n\n const emailResult = await req.payload.sendEmail({\n html: message,\n subject,\n to: user.email,\n })\n // req.payload.logger.info(`email result: ${JSON.stringify(emailResult)}`)\n // return data; // Return data to allow normal submission if needed\n if (!emailResult) {\n return Response.json(\n {\n error: 'Unknown email result',\n now: new Date().toISOString(),\n } as RequestMagicLinkResponse,\n { status: 400 },\n )\n }\n req.payload.logger.info(`requestMagicLinkHandler email sent \\n ${magicLink}`)\n return Response.json({\n emailResult,\n now: new Date().toISOString(),\n } as RequestMagicLinkResponse)\n }\n\n /** Endpoint config for requesting a magic link. Mount as POST /emailToken. */\n const requestMagicLinkEndpoint: Endpoint = {\n handler: requestMagicLinkHandler,\n method: 'post',\n path: '/emailToken',\n }\n\n return requestMagicLinkEndpoint\n}\n\nexport default createEndpointRequestMagicLink\n"],"names":["defaultCollectionSlug","getHmacHash","getTokenAndHash","createEndpointRequestMagicLink","subscribersCollectionSlug","unsubscribeURL","verifyURL","href","Error","requestMagicLinkHandler","req","data","json","email","verifyData","Response","error","now","Date","toISOString","status","userResults","payload","find","collection","where","equals","user","docs","tokenHash","tokenHash2","createResult","create","password","draft","expiresAt","token","update","verificationToken","verificationTokenExpires","hashToken","unsubscribeHash","magicLink","search","encodeURIComponent","unsubscribeLink","undefined","subject","message","emailResult","sendEmail","html","to","logger","info","requestMagicLinkEndpoint","handler","method","path"],"mappings":"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"}
@@ -16,10 +16,14 @@ export type SubscribeResponse = {
16
16
  * opt-ins for already-verified subscribers.
17
17
  *
18
18
  * @param options - Config options for the endpoint
19
- * @param options.subscribersCollectionSlug - Collection slug for subscribers (default from Subscribers collection)
19
+ * @param options.subscribersCollectionSlug - (required) Collection slug for subscribers (default from Subscribers collection)
20
+ * @param options.unsubscribeURL - (optional) The URL to use for unsubscribe links
21
+ * @param options.verifyURL - (required) The URL to use for verify links
20
22
  * @returns Payload Endpoint config for POST /subscribe
21
23
  */
22
- declare function createEndpointSubscribe({ subscribersCollectionSlug, }: {
24
+ declare function createEndpointSubscribe({ subscribersCollectionSlug, unsubscribeURL, verifyURL, }: {
23
25
  subscribersCollectionSlug: CollectionSlug;
26
+ unsubscribeURL?: URL;
27
+ verifyURL: URL;
24
28
  }): Endpoint;
25
29
  export default createEndpointSubscribe;
@@ -7,22 +7,21 @@ import { verifyOptIns } from '../helpers/verifyOptIns.js';
7
7
  * opt-ins for already-verified subscribers.
8
8
  *
9
9
  * @param options - Config options for the endpoint
10
- * @param options.subscribersCollectionSlug - Collection slug for subscribers (default from Subscribers collection)
10
+ * @param options.subscribersCollectionSlug - (required) Collection slug for subscribers (default from Subscribers collection)
11
+ * @param options.unsubscribeURL - (optional) The URL to use for unsubscribe links
12
+ * @param options.verifyURL - (required) The URL to use for verify links
11
13
  * @returns Payload Endpoint config for POST /subscribe
12
- */ function createEndpointSubscribe({ subscribersCollectionSlug = defaultCollectionSlug }) {
14
+ */ function createEndpointSubscribe({ subscribersCollectionSlug = defaultCollectionSlug, unsubscribeURL, verifyURL }) {
13
15
  /**
14
- * Handler for POST /subscribe. Accepts email, optIns, and verifyUrl. Creates pending
16
+ * Handler for POST /subscribe. Accepts email and optIns. Creates pending
15
17
  * subscribers and sends verify emails, or updates opt-ins for authenticated subscribers.
16
18
  *
17
- * @param req - Payload request; body: `email`, `optIns` (channel IDs), `verifyUrl`
19
+ * @param req - Payload request. Expects body to be a json object { email, optIns, verifyData? }
18
20
  * @returns 200 with `emailResult`/`now`, or `email`/`optIns`/`now` when opt-ins updated; 400 with `error`/`now` on failure
19
21
  */ const subscribeHandler = async (req)=>{
20
22
  const data = req?.json ? await req.json() : {};
21
- const { email, optIns, unsubscribeUrl, verifyUrl } = data // if by POST data
23
+ const { email, optIns, verifyData } = data // if by POST data
22
24
  ;
23
- // const { email } = req.routeParams // if by path
24
- const verifyUrlObj = new URL(verifyUrl);
25
- const unsubscribeUrlObj = unsubscribeUrl ? new URL(unsubscribeUrl) : undefined;
26
25
  //
27
26
  // HELPERS
28
27
  // Some of these functions make use of the scope within handler,
@@ -57,9 +56,9 @@ import { verifyOptIns } from '../helpers/verifyOptIns.js';
57
56
  });
58
57
  return updateResults;
59
58
  };
60
- const sendVerifyEmail = async ({ email, linkText, message, subject, token, unsubscribeHash, unsubscribeUrl, verifyUrl })=>{
61
- const magicLink = `${verifyUrl.href}${verifyUrl?.search ? '&' : '?'}token=${token}&email=${email}`;
62
- const unsubscribeLink = unsubscribeUrl ? `${unsubscribeUrl.href}${unsubscribeUrl.search ? '&' : '?'}email=${email}&hash=${unsubscribeHash}` : undefined;
59
+ const sendVerifyEmail = async ({ email, linkText, message, subject, token, unsubscribeHash, unsubscribeURL, verifyData, verifyURL })=>{
60
+ const magicLink = `${verifyURL.href}${verifyURL?.search ? '&' : '?'}token=${encodeURIComponent(token)}&email=${encodeURIComponent(email)}${verifyData ? `&verifyData=${encodeURIComponent(verifyData)}` : ``}`;
61
+ const unsubscribeLink = unsubscribeURL ? `${unsubscribeURL.href}${unsubscribeURL.search ? '&' : '?'}email=${encodeURIComponent(email)}&hash=${encodeURIComponent(unsubscribeHash || '')}` : undefined;
63
62
  const html = `
64
63
  ${message}<p><a href="${magicLink}">${linkText}</a></p>
65
64
  ${unsubscribeLink ? `<p>Click here to <a href="${unsubscribeLink}"><b>unsubscribe</b></a></p>` : ``}`;
@@ -168,8 +167,9 @@ ${unsubscribeLink ? `<p>Click here to <a href="${unsubscribeLink}"><b>unsubscrib
168
167
  subject: data.subject || 'Please verify your subscription',
169
168
  token,
170
169
  unsubscribeHash,
171
- unsubscribeUrl: unsubscribeUrlObj,
172
- verifyUrl: verifyUrlObj
170
+ unsubscribeURL,
171
+ verifyData,
172
+ verifyURL
173
173
  });
174
174
  if (!emailResult) {
175
175
  req.payload.logger.error(JSON.stringify({
@@ -226,8 +226,9 @@ ${unsubscribeLink ? `<p>Click here to <a href="${unsubscribeLink}"><b>unsubscrib
226
226
  subject: data.subject || 'Please verify your subscription',
227
227
  token,
228
228
  unsubscribeHash,
229
- unsubscribeUrl: unsubscribeUrlObj,
230
- verifyUrl: verifyUrlObj
229
+ unsubscribeURL,
230
+ verifyData,
231
+ verifyURL
231
232
  });
232
233
  if (!emailResult) {
233
234
  req.payload.logger.error(JSON.stringify({
@@ -281,8 +282,9 @@ ${unsubscribeLink ? `<p>Click here to <a href="${unsubscribeLink}"><b>unsubscrib
281
282
  subject: data.subject || 'Please verify your subscription',
282
283
  token,
283
284
  unsubscribeHash,
284
- unsubscribeUrl: unsubscribeUrlObj,
285
- verifyUrl: verifyUrlObj
285
+ unsubscribeURL,
286
+ verifyData,
287
+ verifyURL
286
288
  });
287
289
  if (!emailResult) {
288
290
  req.payload.logger.error(JSON.stringify({
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/endpoints/subscribe.ts"],"sourcesContent":["import type { CollectionSlug, Endpoint, PayloadHandler } from 'payload'\nimport type { Subscriber } from 'src/copied/payload-types.js'\n\nimport { defaultCollectionSlug } from '../collections/Subscribers.js'\nimport { getHmacHash, getTokenAndHash } from '../helpers/token.js'\nimport { verifyOptIns } from '../helpers/verifyOptIns.js'\n\nexport type SubscribeResponse =\n // When subscriber optIns are updated...\n | {\n email: string\n now: string\n optIns: string[]\n }\n // When a verify link is emailed...\n | {\n emailResult: any\n now: string\n }\n // When any error occurs...\n | {\n error: string\n now: string\n }\n\n/**\n * Factory that creates the subscribe endpoint config and handler.\n * Handles new subscriptions (pending + verify email), magic-link resends, and updating\n * opt-ins for already-verified subscribers.\n *\n * @param options - Config options for the endpoint\n * @param options.subscribersCollectionSlug - Collection slug for subscribers (default from Subscribers collection)\n * @returns Payload Endpoint config for POST /subscribe\n */\nfunction createEndpointSubscribe({\n subscribersCollectionSlug = defaultCollectionSlug,\n}: {\n subscribersCollectionSlug: CollectionSlug\n}): Endpoint {\n /**\n * Handler for POST /subscribe. Accepts email, optIns, and verifyUrl. Creates pending\n * subscribers and sends verify emails, or updates opt-ins for authenticated subscribers.\n *\n * @param req - Payload request; body: `email`, `optIns` (channel IDs), `verifyUrl`\n * @returns 200 with `emailResult`/`now`, or `email`/`optIns`/`now` when opt-ins updated; 400 with `error`/`now` on failure\n */\n const subscribeHandler: PayloadHandler = async (req) => {\n const data = req?.json ? await req.json() : {}\n const {\n email,\n optIns,\n unsubscribeUrl,\n verifyUrl,\n }: { email: string; optIns: string[]; unsubscribeUrl?: string; verifyUrl: string } = data // if by POST data\n // const { email } = req.routeParams // if by path\n const verifyUrlObj: URL = new URL(verifyUrl)\n const unsubscribeUrlObj: undefined | URL = unsubscribeUrl ? new URL(unsubscribeUrl) : undefined\n\n //\n // HELPERS\n // Some of these functions make use of the scope within handler,\n // and would have to be refactored if moved out.\n //\n const createSubscriber = async ({\n optIns,\n password,\n status,\n verificationToken,\n verificationTokenExpires,\n }: {\n email: string\n optIns?: string[]\n password?: string\n status?: 'pending' | 'subscribed' | 'unsubscribed'\n verificationToken?: string\n verificationTokenExpires?: Date\n }) => {\n await req.payload.create({\n collection: subscribersCollectionSlug,\n data: {\n email,\n optIns,\n password,\n status: status || 'pending',\n verificationToken,\n verificationTokenExpires: verificationTokenExpires?.toISOString(),\n },\n draft: false,\n })\n }\n const updateSubscriber = async ({\n id,\n optIns,\n password,\n status,\n verificationToken,\n verificationTokenExpires,\n }: {\n id: string\n optIns?: string[]\n password?: string\n status?: 'pending' | 'subscribed' | 'unsubscribed'\n verificationToken?: string\n verificationTokenExpires?: Date | null\n }) => {\n const updateResults = await req.payload.update({\n id,\n collection: subscribersCollectionSlug,\n data: {\n optIns,\n password,\n status,\n verificationToken,\n verificationTokenExpires: verificationTokenExpires?.toISOString() || null,\n },\n depth: 0,\n })\n return updateResults\n }\n const sendVerifyEmail = async ({\n email,\n linkText,\n message,\n subject,\n token,\n unsubscribeHash,\n unsubscribeUrl,\n verifyUrl,\n }: {\n email: string\n linkText: string\n message: string\n subject: string\n token: string\n unsubscribeHash?: string\n unsubscribeUrl?: URL\n verifyUrl: URL\n }) => {\n const magicLink = `${verifyUrl.href}${verifyUrl?.search ? '&' : '?'}token=${token}&email=${email}`\n const unsubscribeLink = unsubscribeUrl\n ? `${unsubscribeUrl.href}${unsubscribeUrl.search ? '&' : '?'}email=${email}&hash=${unsubscribeHash}`\n : undefined\n const html = `\n${message}<p><a href=\"${magicLink}\">${linkText}</a></p>\n${\n unsubscribeLink ? `<p>Click here to <a href=\"${unsubscribeLink}\"><b>unsubscribe</b></a></p>` : ``\n}`\n\n const emailResult = await req.payload.sendEmail({\n html,\n subject,\n to: email,\n })\n req.payload.logger.info(`subscribe email sent \\n ${magicLink}`)\n return emailResult\n }\n\n //\n // VALIDATE INPUT\n //\n // Require email\n if (!email) {\n req.payload.logger.error(\n JSON.stringify(\n { error: 'Bad data', now: new Date().toISOString() } as SubscribeResponse,\n undefined,\n 2,\n ),\n )\n return Response.json(\n { error: 'Bad data', now: new Date().toISOString() } as SubscribeResponse,\n { status: 400 },\n )\n }\n\n //\n // Validate OptInChannels\n const { invalidOptInsInput, verifiedOptInIDs } = await verifyOptIns(req.payload, optIns)\n\n if (invalidOptInsInput) {\n req.payload.logger.error(\n JSON.stringify(\n {\n error: 'Invalid input: ' + JSON.stringify(optIns),\n now: new Date().toISOString(),\n } as SubscribeResponse,\n undefined,\n 2,\n ),\n )\n return Response.json(\n {\n error: 'Invalid input: ' + JSON.stringify(optIns),\n now: new Date().toISOString(),\n } as SubscribeResponse,\n { status: 400 },\n )\n }\n\n //\n // Verify subscriber exists\n const userResults = await req.payload.find({\n collection: subscribersCollectionSlug,\n where: {\n email: { equals: email },\n },\n })\n const subscriber = userResults.docs[0] as Subscriber\n\n //\n // Now we have a subscriber and validatedOptIns\n // Handle scenarios\n\n //\n // Create the hash for an unsubscribe link\n const { hashToken: unsubscribeHash } = getHmacHash(email)\n\n //\n // ********************************************************\n //\n if (req.user && req.user.email != email) {\n //\n // Error: Auth-ed user doesn't match subscriber email\n //\n req.payload.logger.error(\n JSON.stringify(\n {\n error: 'Unauthorized: ' + email,\n now: new Date().toISOString(),\n } as SubscribeResponse,\n undefined,\n 2,\n ),\n )\n return Response.json(\n {\n error: 'Unauthorized: ' + email,\n now: new Date().toISOString(),\n } as SubscribeResponse,\n { status: 400 },\n )\n }\n\n //\n // ********************************************************\n //\n if (!subscriber) {\n //\n // Create subscriber with status 'pending',\n // and an invisible unknowable password,\n // and send a verify email\n // Pass all optIns through verify link\n //\n const { expiresAt, token, tokenHash } = getTokenAndHash(15 * 60 * 1000) // Use for magic link\n const { tokenHash: tokenHash2 } = getTokenAndHash() // Unknowable\n await createSubscriber({\n email,\n optIns,\n password: tokenHash2,\n status: 'pending',\n verificationToken: tokenHash,\n verificationTokenExpires: expiresAt,\n })\n\n //\n // Send email\n const emailResult = await sendVerifyEmail({\n email,\n linkText: '<b>Verify</b>',\n message: data.message || `<p>Click here to verify your subscription:</p>`,\n subject: data.subject || 'Please verify your subscription',\n token,\n unsubscribeHash,\n unsubscribeUrl: unsubscribeUrlObj,\n verifyUrl: verifyUrlObj,\n })\n if (!emailResult) {\n req.payload.logger.error(\n JSON.stringify(\n { error: 'Unknown email result', now: new Date().toISOString() } as SubscribeResponse,\n undefined,\n 2,\n ),\n )\n return Response.json(\n { error: 'Unknown email result', now: new Date().toISOString() } as SubscribeResponse,\n { status: 400 },\n )\n }\n return Response.json({ emailResult, now: new Date().toISOString() } as SubscribeResponse)\n //\n }\n //\n // ********************************************************\n //\n if (!req.user && subscriber) {\n //\n // Send magic link to log the user in\n // Pass all optIns through verify link\n //\n const { expiresAt, token, tokenHash } = getTokenAndHash(15 * 60 * 1000) // Use for magic link\n // Update subscriber with token for pending email\n const updateResults = await updateSubscriber({\n id: subscriber.id,\n verificationToken: tokenHash,\n verificationTokenExpires: expiresAt,\n })\n if (!updateResults) {\n req.payload.logger.error(\n JSON.stringify(\n { error: 'Unknown error', now: new Date().toISOString() } as SubscribeResponse,\n undefined,\n 2,\n ),\n )\n return Response.json(\n { error: 'Unknown error', now: new Date().toISOString() } as SubscribeResponse,\n { status: 400 },\n )\n }\n\n //\n // Send email\n const emailResult = await sendVerifyEmail({\n email,\n linkText: 'Verify',\n message: data.message || `<h1>Click here to verify your subscription:</h1>`,\n subject: data.subject || 'Please verify your subscription',\n token,\n unsubscribeHash,\n unsubscribeUrl: unsubscribeUrlObj,\n verifyUrl: verifyUrlObj,\n })\n if (!emailResult) {\n req.payload.logger.error(\n JSON.stringify(\n { error: 'Unknown email result', now: new Date().toISOString() } as SubscribeResponse,\n undefined,\n 2,\n ),\n )\n return Response.json(\n { error: 'Unknown email result', now: new Date().toISOString() } as SubscribeResponse,\n { status: 400 },\n )\n }\n return Response.json({ emailResult, now: new Date().toISOString() } as SubscribeResponse)\n }\n //\n // ********************************************************\n //\n if (req.user && subscriber && subscriber.status == 'pending') {\n //\n // Send magic link to verify the email and log the user in\n // Pass all optIns through verify link\n //\n const { expiresAt, token, tokenHash } = getTokenAndHash(15 * 60 * 1000) // Use for magic link\n // Create subscriber with token for pending email\n const updateResults = await updateSubscriber({\n id: subscriber.id,\n verificationToken: tokenHash,\n verificationTokenExpires: expiresAt,\n })\n if (!updateResults) {\n req.payload.logger.error(\n JSON.stringify(\n { error: 'Unknown error', now: new Date().toISOString() } as SubscribeResponse,\n undefined,\n 2,\n ),\n )\n return Response.json(\n { error: 'Unknown error', now: new Date().toISOString() } as SubscribeResponse,\n { status: 400 },\n )\n }\n\n const emailResult = await sendVerifyEmail({\n email,\n linkText: 'Verify',\n message: data.message || `<h1>Click here to verify your email:</h1>`,\n subject: data.subject || 'Please verify your subscription',\n token,\n unsubscribeHash,\n unsubscribeUrl: unsubscribeUrlObj,\n verifyUrl: verifyUrlObj,\n })\n if (!emailResult) {\n req.payload.logger.error(\n JSON.stringify(\n { error: 'Unknown email result', now: new Date().toISOString() } as SubscribeResponse,\n undefined,\n 2,\n ),\n )\n return Response.json(\n { error: 'Unknown email result', now: new Date().toISOString() } as SubscribeResponse,\n { status: 400 },\n )\n }\n return Response.json({ emailResult, now: new Date().toISOString() } as SubscribeResponse)\n }\n\n //\n // ********************************************************\n //\n if (req.user && subscriber && subscriber.status != 'pending') {\n //\n // Update subscriber with status 'subscribed',\n // an invisible unknowable password,\n // and if any optIns input exists, set subscriber optIns\n // to EXACTLY verifiedOptInIDs (potentially unsubscribing from any not in verifiedOptInIDs)\n //\n const { tokenHash } = getTokenAndHash() // Use for magic link\n // Update subscriber with optIns\n const updateResults = (await updateSubscriber({\n id: subscriber.id,\n optIns: verifiedOptInIDs,\n password: tokenHash,\n status: 'subscribed',\n verificationToken: '',\n verificationTokenExpires: null,\n })) as Subscriber\n\n // Return results, including the verified optIns\n return Response.json({\n email: updateResults.email,\n now: new Date().toISOString(),\n optIns: updateResults.optIns,\n } as SubscribeResponse)\n }\n //\n // Uncaught case\n //\n req.payload.logger.error(\n JSON.stringify(\n { error: 'Unknown error', now: new Date().toISOString() } as SubscribeResponse,\n undefined,\n 2,\n ),\n )\n return Response.json(\n { error: 'Unknown error', now: new Date().toISOString() } as SubscribeResponse,\n { status: 400 },\n )\n }\n\n /** Endpoint config for subscription and opt-in updates. Mount as POST /subscribe. */\n const subscribeEndpoint: Endpoint = {\n handler: subscribeHandler,\n method: 'post',\n path: '/subscribe',\n }\n\n return subscribeEndpoint\n}\n\nexport default createEndpointSubscribe\n"],"names":["defaultCollectionSlug","getHmacHash","getTokenAndHash","verifyOptIns","createEndpointSubscribe","subscribersCollectionSlug","subscribeHandler","req","data","json","email","optIns","unsubscribeUrl","verifyUrl","verifyUrlObj","URL","unsubscribeUrlObj","undefined","createSubscriber","password","status","verificationToken","verificationTokenExpires","payload","create","collection","toISOString","draft","updateSubscriber","id","updateResults","update","depth","sendVerifyEmail","linkText","message","subject","token","unsubscribeHash","magicLink","href","search","unsubscribeLink","html","emailResult","sendEmail","to","logger","info","error","JSON","stringify","now","Date","Response","invalidOptInsInput","verifiedOptInIDs","userResults","find","where","equals","subscriber","docs","hashToken","user","expiresAt","tokenHash","tokenHash2","subscribeEndpoint","handler","method","path"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/endpoints/subscribe.ts"],"sourcesContent":["import type { CollectionSlug, Endpoint, PayloadHandler } from 'payload'\nimport type { Subscriber } from 'src/copied/payload-types.js'\n\nimport { defaultCollectionSlug } from '../collections/Subscribers.js'\nimport { getHmacHash, getTokenAndHash } from '../helpers/token.js'\nimport { verifyOptIns } from '../helpers/verifyOptIns.js'\n\nexport type SubscribeResponse =\n // When subscriber optIns are updated...\n | {\n email: string\n now: string\n optIns: string[]\n }\n // When a verify link is emailed...\n | {\n emailResult: any\n now: string\n }\n // When any error occurs...\n | {\n error: string\n now: string\n }\n\n/**\n * Factory that creates the subscribe endpoint config and handler.\n * Handles new subscriptions (pending + verify email), magic-link resends, and updating\n * opt-ins for already-verified subscribers.\n *\n * @param options - Config options for the endpoint\n * @param options.subscribersCollectionSlug - (required) Collection slug for subscribers (default from Subscribers collection)\n * @param options.unsubscribeURL - (optional) The URL to use for unsubscribe links\n * @param options.verifyURL - (required) The URL to use for verify links\n * @returns Payload Endpoint config for POST /subscribe\n */\nfunction createEndpointSubscribe({\n subscribersCollectionSlug = defaultCollectionSlug,\n unsubscribeURL,\n verifyURL,\n}: {\n subscribersCollectionSlug: CollectionSlug\n unsubscribeURL?: URL\n verifyURL: URL\n}): Endpoint {\n /**\n * Handler for POST /subscribe. Accepts email and optIns. Creates pending\n * subscribers and sends verify emails, or updates opt-ins for authenticated subscribers.\n *\n * @param req - Payload request. Expects body to be a json object { email, optIns, verifyData? }\n * @returns 200 with `emailResult`/`now`, or `email`/`optIns`/`now` when opt-ins updated; 400 with `error`/`now` on failure\n */\n const subscribeHandler: PayloadHandler = async (req) => {\n const data = req?.json ? await req.json() : {}\n const {\n email,\n optIns,\n verifyData,\n }: {\n email: string\n optIns: string[]\n verifyData?: string\n } = data // if by POST data\n\n //\n // HELPERS\n // Some of these functions make use of the scope within handler,\n // and would have to be refactored if moved out.\n //\n const createSubscriber = async ({\n optIns,\n password,\n status,\n verificationToken,\n verificationTokenExpires,\n }: {\n email: string\n optIns?: string[]\n password?: string\n status?: 'pending' | 'subscribed' | 'unsubscribed'\n verificationToken?: string\n verificationTokenExpires?: Date\n }) => {\n await req.payload.create({\n collection: subscribersCollectionSlug,\n data: {\n email,\n optIns,\n password,\n status: status || 'pending',\n verificationToken,\n verificationTokenExpires: verificationTokenExpires?.toISOString(),\n },\n draft: false,\n })\n }\n const updateSubscriber = async ({\n id,\n optIns,\n password,\n status,\n verificationToken,\n verificationTokenExpires,\n }: {\n id: string\n optIns?: string[]\n password?: string\n status?: 'pending' | 'subscribed' | 'unsubscribed'\n verificationToken?: string\n verificationTokenExpires?: Date | null\n }) => {\n const updateResults = await req.payload.update({\n id,\n collection: subscribersCollectionSlug,\n data: {\n optIns,\n password,\n status,\n verificationToken,\n verificationTokenExpires: verificationTokenExpires?.toISOString() || null,\n },\n depth: 0,\n })\n return updateResults\n }\n const sendVerifyEmail = async ({\n email,\n linkText,\n message,\n subject,\n token,\n unsubscribeHash,\n unsubscribeURL,\n verifyData,\n verifyURL,\n }: {\n email: string\n linkText: string\n message: string\n subject: string\n token: string\n unsubscribeHash?: string\n unsubscribeURL?: URL\n verifyData?: string\n verifyURL: URL\n }) => {\n const magicLink = `${verifyURL.href}${verifyURL?.search ? '&' : '?'}token=${encodeURIComponent(token)}&email=${encodeURIComponent(email)}${verifyData ? `&verifyData=${encodeURIComponent(verifyData)}` : ``}`\n const unsubscribeLink = unsubscribeURL\n ? `${unsubscribeURL.href}${unsubscribeURL.search ? '&' : '?'}email=${encodeURIComponent(email)}&hash=${encodeURIComponent(unsubscribeHash || '')}`\n : undefined\n const html = `\n${message}<p><a href=\"${magicLink}\">${linkText}</a></p>\n${\n unsubscribeLink ? `<p>Click here to <a href=\"${unsubscribeLink}\"><b>unsubscribe</b></a></p>` : ``\n}`\n\n const emailResult = await req.payload.sendEmail({\n html,\n subject,\n to: email,\n })\n req.payload.logger.info(`subscribe email sent \\n ${magicLink}`)\n return emailResult\n }\n\n //\n // VALIDATE INPUT\n //\n // Require email\n if (!email) {\n req.payload.logger.error(\n JSON.stringify(\n { error: 'Bad data', now: new Date().toISOString() } as SubscribeResponse,\n undefined,\n 2,\n ),\n )\n return Response.json(\n { error: 'Bad data', now: new Date().toISOString() } as SubscribeResponse,\n { status: 400 },\n )\n }\n\n //\n // Validate OptInChannels\n const { invalidOptInsInput, verifiedOptInIDs } = await verifyOptIns(req.payload, optIns)\n\n if (invalidOptInsInput) {\n req.payload.logger.error(\n JSON.stringify(\n {\n error: 'Invalid input: ' + JSON.stringify(optIns),\n now: new Date().toISOString(),\n } as SubscribeResponse,\n undefined,\n 2,\n ),\n )\n return Response.json(\n {\n error: 'Invalid input: ' + JSON.stringify(optIns),\n now: new Date().toISOString(),\n } as SubscribeResponse,\n { status: 400 },\n )\n }\n\n //\n // Verify subscriber exists\n const userResults = await req.payload.find({\n collection: subscribersCollectionSlug,\n where: {\n email: { equals: email },\n },\n })\n const subscriber = userResults.docs[0] as Subscriber\n\n //\n // Now we have a subscriber and validatedOptIns\n // Handle scenarios\n\n //\n // Create the hash for an unsubscribe link\n const { hashToken: unsubscribeHash } = getHmacHash(email)\n\n //\n // ********************************************************\n //\n if (req.user && req.user.email != email) {\n //\n // Error: Auth-ed user doesn't match subscriber email\n //\n req.payload.logger.error(\n JSON.stringify(\n {\n error: 'Unauthorized: ' + email,\n now: new Date().toISOString(),\n } as SubscribeResponse,\n undefined,\n 2,\n ),\n )\n return Response.json(\n {\n error: 'Unauthorized: ' + email,\n now: new Date().toISOString(),\n } as SubscribeResponse,\n { status: 400 },\n )\n }\n\n //\n // ********************************************************\n //\n if (!subscriber) {\n //\n // Create subscriber with status 'pending',\n // and an invisible unknowable password,\n // and send a verify email\n // Pass all optIns through verify link\n //\n const { expiresAt, token, tokenHash } = getTokenAndHash(15 * 60 * 1000) // Use for magic link\n const { tokenHash: tokenHash2 } = getTokenAndHash() // Unknowable\n await createSubscriber({\n email,\n optIns,\n password: tokenHash2,\n status: 'pending',\n verificationToken: tokenHash,\n verificationTokenExpires: expiresAt,\n })\n\n //\n // Send email\n const emailResult = await sendVerifyEmail({\n email,\n linkText: '<b>Verify</b>',\n message: data.message || `<p>Click here to verify your subscription:</p>`,\n subject: data.subject || 'Please verify your subscription',\n token,\n unsubscribeHash,\n unsubscribeURL,\n verifyData,\n verifyURL,\n })\n if (!emailResult) {\n req.payload.logger.error(\n JSON.stringify(\n { error: 'Unknown email result', now: new Date().toISOString() } as SubscribeResponse,\n undefined,\n 2,\n ),\n )\n return Response.json(\n { error: 'Unknown email result', now: new Date().toISOString() } as SubscribeResponse,\n { status: 400 },\n )\n }\n return Response.json({ emailResult, now: new Date().toISOString() } as SubscribeResponse)\n //\n }\n //\n // ********************************************************\n //\n if (!req.user && subscriber) {\n //\n // Send magic link to log the user in\n // Pass all optIns through verify link\n //\n const { expiresAt, token, tokenHash } = getTokenAndHash(15 * 60 * 1000) // Use for magic link\n // Update subscriber with token for pending email\n const updateResults = await updateSubscriber({\n id: subscriber.id,\n verificationToken: tokenHash,\n verificationTokenExpires: expiresAt,\n })\n if (!updateResults) {\n req.payload.logger.error(\n JSON.stringify(\n { error: 'Unknown error', now: new Date().toISOString() } as SubscribeResponse,\n undefined,\n 2,\n ),\n )\n return Response.json(\n { error: 'Unknown error', now: new Date().toISOString() } as SubscribeResponse,\n { status: 400 },\n )\n }\n\n //\n // Send email\n const emailResult = await sendVerifyEmail({\n email,\n linkText: 'Verify',\n message: data.message || `<h1>Click here to verify your subscription:</h1>`,\n subject: data.subject || 'Please verify your subscription',\n token,\n unsubscribeHash,\n unsubscribeURL,\n verifyData,\n verifyURL,\n })\n if (!emailResult) {\n req.payload.logger.error(\n JSON.stringify(\n { error: 'Unknown email result', now: new Date().toISOString() } as SubscribeResponse,\n undefined,\n 2,\n ),\n )\n return Response.json(\n { error: 'Unknown email result', now: new Date().toISOString() } as SubscribeResponse,\n { status: 400 },\n )\n }\n return Response.json({ emailResult, now: new Date().toISOString() } as SubscribeResponse)\n }\n //\n // ********************************************************\n //\n if (req.user && subscriber && subscriber.status == 'pending') {\n //\n // Send magic link to verify the email and log the user in\n // Pass all optIns through verify link\n //\n const { expiresAt, token, tokenHash } = getTokenAndHash(15 * 60 * 1000) // Use for magic link\n // Create subscriber with token for pending email\n const updateResults = await updateSubscriber({\n id: subscriber.id,\n verificationToken: tokenHash,\n verificationTokenExpires: expiresAt,\n })\n if (!updateResults) {\n req.payload.logger.error(\n JSON.stringify(\n { error: 'Unknown error', now: new Date().toISOString() } as SubscribeResponse,\n undefined,\n 2,\n ),\n )\n return Response.json(\n { error: 'Unknown error', now: new Date().toISOString() } as SubscribeResponse,\n { status: 400 },\n )\n }\n\n const emailResult = await sendVerifyEmail({\n email,\n linkText: 'Verify',\n message: data.message || `<h1>Click here to verify your email:</h1>`,\n subject: data.subject || 'Please verify your subscription',\n token,\n unsubscribeHash,\n unsubscribeURL,\n verifyData,\n verifyURL,\n })\n if (!emailResult) {\n req.payload.logger.error(\n JSON.stringify(\n { error: 'Unknown email result', now: new Date().toISOString() } as SubscribeResponse,\n undefined,\n 2,\n ),\n )\n return Response.json(\n { error: 'Unknown email result', now: new Date().toISOString() } as SubscribeResponse,\n { status: 400 },\n )\n }\n return Response.json({ emailResult, now: new Date().toISOString() } as SubscribeResponse)\n }\n\n //\n // ********************************************************\n //\n if (req.user && subscriber && subscriber.status != 'pending') {\n //\n // Update subscriber with status 'subscribed',\n // an invisible unknowable password,\n // and if any optIns input exists, set subscriber optIns\n // to EXACTLY verifiedOptInIDs (potentially unsubscribing from any not in verifiedOptInIDs)\n //\n const { tokenHash } = getTokenAndHash() // Use for magic link\n // Update subscriber with optIns\n const updateResults = (await updateSubscriber({\n id: subscriber.id,\n optIns: verifiedOptInIDs,\n password: tokenHash,\n status: 'subscribed',\n verificationToken: '',\n verificationTokenExpires: null,\n })) as Subscriber\n\n // Return results, including the verified optIns\n return Response.json({\n email: updateResults.email,\n now: new Date().toISOString(),\n optIns: updateResults.optIns,\n } as SubscribeResponse)\n }\n //\n // Uncaught case\n //\n req.payload.logger.error(\n JSON.stringify(\n { error: 'Unknown error', now: new Date().toISOString() } as SubscribeResponse,\n undefined,\n 2,\n ),\n )\n return Response.json(\n { error: 'Unknown error', now: new Date().toISOString() } as SubscribeResponse,\n { status: 400 },\n )\n }\n\n /** Endpoint config for subscription and opt-in updates. Mount as POST /subscribe. */\n const subscribeEndpoint: Endpoint = {\n handler: subscribeHandler,\n method: 'post',\n path: '/subscribe',\n }\n\n return subscribeEndpoint\n}\n\nexport default createEndpointSubscribe\n"],"names":["defaultCollectionSlug","getHmacHash","getTokenAndHash","verifyOptIns","createEndpointSubscribe","subscribersCollectionSlug","unsubscribeURL","verifyURL","subscribeHandler","req","data","json","email","optIns","verifyData","createSubscriber","password","status","verificationToken","verificationTokenExpires","payload","create","collection","toISOString","draft","updateSubscriber","id","updateResults","update","depth","sendVerifyEmail","linkText","message","subject","token","unsubscribeHash","magicLink","href","search","encodeURIComponent","unsubscribeLink","undefined","html","emailResult","sendEmail","to","logger","info","error","JSON","stringify","now","Date","Response","invalidOptInsInput","verifiedOptInIDs","userResults","find","where","equals","subscriber","docs","hashToken","user","expiresAt","tokenHash","tokenHash2","subscribeEndpoint","handler","method","path"],"mappings":"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"}
@@ -10,10 +10,10 @@ import { getHmacHash } from '../helpers/token.js';
10
10
  * @returns Payload Endpoint config for POST /unsubscribe
11
11
  */ function createEndpointUnsubscribe({ subscribersCollectionSlug = defaultCollectionSlug }) {
12
12
  /**
13
- * Handler for POST /unsubscribe. Accepts email, optIns, and verifyUrl. Creates pending
14
- * subscribers and sends verify emails, or updates opt-ins for authenticated subscribers.
13
+ * Handler for POST /unsubscribe. Accepts email and unsubscribeToken. Updates subscriber's status
14
+ * to 'unsubscribed'.
15
15
  *
16
- * @param req - Payload request; body: `email`, `optIns` (channel IDs), `verifyUrl`
16
+ * @param req - Payload request. Expects body to be a json object { email, unsubscribeToken }
17
17
  * @returns 200 with `emailResult`/`now`, or `email`/`optIns`/`now` when opt-ins updated; 400 with `error`/`now` on failure
18
18
  */ const unsubscribeHandler = async (req)=>{
19
19
  const data = req?.json ? await req.json() : {};
@@ -23,25 +23,27 @@ import { getHmacHash } from '../helpers/token.js';
23
23
  //
24
24
  // VALIDATE INPUT
25
25
  //
26
- // Require unsubscribeToken
27
- if (!unsubscribeToken) {
26
+ // Require token and email OR authed user
27
+ if (!unsubscribeToken && (!req.user || req.user?.email != email)) {
28
28
  const result = {
29
29
  error: 'Bad data',
30
30
  now: new Date().toISOString()
31
31
  };
32
- req.payload.logger.error(`unsubscribe: No unsubscribeToken — ${JSON.stringify(result, undefined, 2)}`);
32
+ req.payload.logger.error(`unsubscribe: No unsubscribeToken or authenticated user - ${req.user?.email} — ${JSON.stringify(result, undefined, 2)}`);
33
33
  return Response.json(result);
34
34
  }
35
35
  //
36
36
  // Verify unsubscribeToken
37
- const { hashToken: verifyUnsubscribeToken } = getHmacHash(email);
38
- if (unsubscribeToken != verifyUnsubscribeToken) {
39
- const result = {
40
- error: 'Bad data',
41
- now: new Date().toISOString()
42
- };
43
- req.payload.logger.error(`unsubscribe: unsubscribeToken not verified — ${JSON.stringify(result, undefined, 2)}`);
44
- return Response.json(result);
37
+ if (unsubscribeToken) {
38
+ const { hashToken: verifyUnsubscribeToken } = getHmacHash(email);
39
+ if (unsubscribeToken != verifyUnsubscribeToken) {
40
+ const result = {
41
+ error: 'Bad data',
42
+ now: new Date().toISOString()
43
+ };
44
+ req.payload.logger.error(`unsubscribe: unsubscribeToken not verified — ${JSON.stringify(result, undefined, 2)}`);
45
+ return Response.json(result);
46
+ }
45
47
  }
46
48
  //
47
49
  // Require subscriber exists
@@ -103,7 +105,7 @@ import { getHmacHash } from '../helpers/token.js';
103
105
  message: 'Unsubscribed',
104
106
  now: new Date().toISOString()
105
107
  };
106
- req.payload.logger.error(`unsubscribe: Unhandled scenario — ${JSON.stringify(result, undefined, 2)}`);
108
+ req.payload.logger.info(`unsubscribe: successful — ${JSON.stringify(result, undefined, 2)}`);
107
109
  return Response.json(result);
108
110
  };
109
111
  /** Endpoint config for subscription and opt-in updates. Mount as POST /subscribe. */ const unsubscribeEndpoint = {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/endpoints/unsubscribe.ts"],"sourcesContent":["import type { CollectionSlug, Endpoint, PayloadHandler } from 'payload'\nimport type { Subscriber } from 'src/copied/payload-types.js'\n\nimport { defaultCollectionSlug } from '../collections/Subscribers.js'\nimport { getHmacHash } from '../helpers/token.js'\n\nexport type UnsubscribeResponse =\n // When unsubscriber status is set to 'unsubscribed'...\n | {\n error: string\n now: string\n }\n // When any error occurs...\n | {\n message: string\n now: string\n }\n\n/**\n * Factory that creates the unsubscribe endpoint config and handler.\n * Handles completely unsubscribing a subscriber by marking their\n * status as 'unsubscribed'.\n *\n * @param options - Config options for the endpoint\n * @param options.subscribersCollectionSlug - Collection slug for subscribers (default from Subscribers collection)\n * @returns Payload Endpoint config for POST /unsubscribe\n */\nfunction createEndpointUnsubscribe({\n subscribersCollectionSlug = defaultCollectionSlug,\n}: {\n subscribersCollectionSlug: CollectionSlug\n}): Endpoint {\n /**\n * Handler for POST /unsubscribe. Accepts email, optIns, and verifyUrl. Creates pending\n * subscribers and sends verify emails, or updates opt-ins for authenticated subscribers.\n *\n * @param req - Payload request; body: `email`, `optIns` (channel IDs), `verifyUrl`\n * @returns 200 with `emailResult`/`now`, or `email`/`optIns`/`now` when opt-ins updated; 400 with `error`/`now` on failure\n */\n const unsubscribeHandler: PayloadHandler = async (req) => {\n const data = req?.json ? await req.json() : {}\n const { email, unsubscribeToken }: { email: string; unsubscribeToken: string } = await data // if by POST data\n // const { email } = req.routeParams // if by path\n\n //\n // VALIDATE INPUT\n //\n // Require unsubscribeToken\n if (!unsubscribeToken) {\n const result = { error: 'Bad data', now: new Date().toISOString() } as UnsubscribeResponse\n req.payload.logger.error(\n `unsubscribe: No unsubscribeToken — ${JSON.stringify(result, undefined, 2)}`,\n )\n return Response.json(result)\n }\n\n //\n // Verify unsubscribeToken\n const { hashToken: verifyUnsubscribeToken } = getHmacHash(email)\n if (unsubscribeToken != verifyUnsubscribeToken) {\n const result = { error: 'Bad data', now: new Date().toISOString() } as UnsubscribeResponse\n req.payload.logger.error(\n `unsubscribe: unsubscribeToken not verified — ${JSON.stringify(result, undefined, 2)}`,\n )\n return Response.json(result)\n }\n\n //\n // Require subscriber exists\n const userResults = await req.payload.find({\n collection: subscribersCollectionSlug,\n where: {\n email: { equals: email },\n },\n })\n const subscriber = userResults.docs[0] as Subscriber\n\n if (!subscriber) {\n const result = { error: 'Bad data', now: new Date().toISOString() } as UnsubscribeResponse\n req.payload.logger.error(\n `unsubscribe: No subscriber — ${JSON.stringify(result, undefined, 2)}`,\n )\n return Response.json(result)\n }\n\n //\n // Require authed user to match incoming email\n if (req.user && req.user.email != subscriber.email) {\n //\n // Error: Auth-ed user doesn't match subscriber email\n //\n const result = {\n error: 'Unauthorized: ' + subscriber.email,\n now: new Date().toISOString(),\n } as UnsubscribeResponse\n req.payload.logger.error(\n `unsubscribe: Unauthorized — ${JSON.stringify(result, undefined, 2)}`,\n )\n return Response.json(result)\n }\n\n //\n // Now we have a validated subscriber and unsubscribeToken\n // Mark as unsubscribed\n\n const updateResults = await req.payload.update({\n id: subscriber.id,\n collection: subscribersCollectionSlug,\n data: {\n status: 'unsubscribed',\n },\n depth: 0,\n })\n if (!updateResults) {\n const result = {\n error: 'Unable to unsubscribe. Please try again.',\n now: new Date().toISOString(),\n } as UnsubscribeResponse\n req.payload.logger.error(\n `unsubscribe: Unknown updateResults — ${JSON.stringify(result, undefined, 2)}`,\n )\n return Response.json(result, { status: 400 })\n }\n\n //\n // Success\n //\n const result = { message: 'Unsubscribed', now: new Date().toISOString() } as UnsubscribeResponse\n req.payload.logger.error(\n `unsubscribe: Unhandled scenario — ${JSON.stringify(result, undefined, 2)}`,\n )\n return Response.json(result)\n }\n\n /** Endpoint config for subscription and opt-in updates. Mount as POST /subscribe. */\n const unsubscribeEndpoint: Endpoint = {\n handler: unsubscribeHandler,\n method: 'post',\n path: '/unsubscribe',\n }\n\n return unsubscribeEndpoint\n}\n\nexport default createEndpointUnsubscribe\n"],"names":["defaultCollectionSlug","getHmacHash","createEndpointUnsubscribe","subscribersCollectionSlug","unsubscribeHandler","req","data","json","email","unsubscribeToken","result","error","now","Date","toISOString","payload","logger","JSON","stringify","undefined","Response","hashToken","verifyUnsubscribeToken","userResults","find","collection","where","equals","subscriber","docs","user","updateResults","update","id","status","depth","message","unsubscribeEndpoint","handler","method","path"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/endpoints/unsubscribe.ts"],"sourcesContent":["import type { CollectionSlug, Endpoint, PayloadHandler } from 'payload'\nimport type { Subscriber } from 'src/copied/payload-types.js'\n\nimport { defaultCollectionSlug } from '../collections/Subscribers.js'\nimport { getHmacHash } from '../helpers/token.js'\n\nexport type UnsubscribeResponse =\n // When unsubscriber status is set to 'unsubscribed'...\n | {\n error: string\n now: string\n }\n // When any error occurs...\n | {\n message: string\n now: string\n }\n\n/**\n * Factory that creates the unsubscribe endpoint config and handler.\n * Handles completely unsubscribing a subscriber by marking their\n * status as 'unsubscribed'.\n *\n * @param options - Config options for the endpoint\n * @param options.subscribersCollectionSlug - Collection slug for subscribers (default from Subscribers collection)\n * @returns Payload Endpoint config for POST /unsubscribe\n */\nfunction createEndpointUnsubscribe({\n subscribersCollectionSlug = defaultCollectionSlug,\n}: {\n subscribersCollectionSlug: CollectionSlug\n}): Endpoint {\n /**\n * Handler for POST /unsubscribe. Accepts email and unsubscribeToken. Updates subscriber's status\n * to 'unsubscribed'.\n *\n * @param req - Payload request. Expects body to be a json object { email, unsubscribeToken }\n * @returns 200 with `emailResult`/`now`, or `email`/`optIns`/`now` when opt-ins updated; 400 with `error`/`now` on failure\n */\n const unsubscribeHandler: PayloadHandler = async (req) => {\n const data = req?.json ? await req.json() : {}\n const { email, unsubscribeToken }: { email: string; unsubscribeToken: string } = await data // if by POST data\n // const { email } = req.routeParams // if by path\n\n //\n // VALIDATE INPUT\n //\n // Require token and email OR authed user\n if (!unsubscribeToken && (!req.user || req.user?.email != email)) {\n const result = { error: 'Bad data', now: new Date().toISOString() } as UnsubscribeResponse\n req.payload.logger.error(\n `unsubscribe: No unsubscribeToken or authenticated user - ${req.user?.email} — ${JSON.stringify(result, undefined, 2)}`,\n )\n return Response.json(result)\n }\n\n //\n // Verify unsubscribeToken\n if (unsubscribeToken) {\n const { hashToken: verifyUnsubscribeToken } = getHmacHash(email)\n if (unsubscribeToken != verifyUnsubscribeToken) {\n const result = { error: 'Bad data', now: new Date().toISOString() } as UnsubscribeResponse\n req.payload.logger.error(\n `unsubscribe: unsubscribeToken not verified — ${JSON.stringify(result, undefined, 2)}`,\n )\n return Response.json(result)\n }\n }\n\n //\n // Require subscriber exists\n const userResults = await req.payload.find({\n collection: subscribersCollectionSlug,\n where: {\n email: { equals: email },\n },\n })\n const subscriber = userResults.docs[0] as Subscriber\n\n if (!subscriber) {\n const result = { error: 'Bad data', now: new Date().toISOString() } as UnsubscribeResponse\n req.payload.logger.error(\n `unsubscribe: No subscriber — ${JSON.stringify(result, undefined, 2)}`,\n )\n return Response.json(result)\n }\n\n //\n // Require authed user to match incoming email\n if (req.user && req.user.email != subscriber.email) {\n //\n // Error: Auth-ed user doesn't match subscriber email\n //\n const result = {\n error: 'Unauthorized: ' + subscriber.email,\n now: new Date().toISOString(),\n } as UnsubscribeResponse\n req.payload.logger.error(\n `unsubscribe: Unauthorized — ${JSON.stringify(result, undefined, 2)}`,\n )\n return Response.json(result)\n }\n\n //\n // Now we have a validated subscriber and unsubscribeToken\n // Mark as unsubscribed\n\n const updateResults = await req.payload.update({\n id: subscriber.id,\n collection: subscribersCollectionSlug,\n data: {\n status: 'unsubscribed',\n },\n depth: 0,\n })\n if (!updateResults) {\n const result = {\n error: 'Unable to unsubscribe. Please try again.',\n now: new Date().toISOString(),\n } as UnsubscribeResponse\n req.payload.logger.error(\n `unsubscribe: Unknown updateResults — ${JSON.stringify(result, undefined, 2)}`,\n )\n return Response.json(result, { status: 400 })\n }\n\n //\n // Success\n //\n const result = { message: 'Unsubscribed', now: new Date().toISOString() } as UnsubscribeResponse\n req.payload.logger.info(`unsubscribe: successful — ${JSON.stringify(result, undefined, 2)}`)\n return Response.json(result)\n }\n\n /** Endpoint config for subscription and opt-in updates. Mount as POST /subscribe. */\n const unsubscribeEndpoint: Endpoint = {\n handler: unsubscribeHandler,\n method: 'post',\n path: '/unsubscribe',\n }\n\n return unsubscribeEndpoint\n}\n\nexport default createEndpointUnsubscribe\n"],"names":["defaultCollectionSlug","getHmacHash","createEndpointUnsubscribe","subscribersCollectionSlug","unsubscribeHandler","req","data","json","email","unsubscribeToken","user","result","error","now","Date","toISOString","payload","logger","JSON","stringify","undefined","Response","hashToken","verifyUnsubscribeToken","userResults","find","collection","where","equals","subscriber","docs","updateResults","update","id","status","depth","message","info","unsubscribeEndpoint","handler","method","path"],"mappings":"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"}
@@ -10,4 +10,8 @@ export type { VerifyMagicLinkResponse } from '../components/app/VerifyMagicLink.
10
10
  export { VerifyMagicLink } from '../components/app/VerifyMagicLink.js';
11
11
  export type { SubscriberContextType } from '../contexts/SubscriberProvider.js';
12
12
  export { SubscriberProvider, useSubscriber } from '../contexts/SubscriberProvider.js';
13
+ export { useRequestMagicLink } from '../hooks/useRequestMagicLink.js';
14
+ export { useSubscribe } from '../hooks/useSubscribe.js';
15
+ export { useUnsubscribe } from '../hooks/useUnsubscribe.js';
16
+ export { useVerifyMagicLink } from '../hooks/useVerifyMagicLink.js';
13
17
  export { getServerUrl } from '../server-functions/serverUrl.js';
@@ -5,6 +5,10 @@ export { SubscriberMenu } from '../components/app/SubscriberMenu.js';
5
5
  export { Unsubscribe } from '../components/app/Unsubscribe.js';
6
6
  export { VerifyMagicLink } from '../components/app/VerifyMagicLink.js';
7
7
  export { SubscriberProvider, useSubscriber } from '../contexts/SubscriberProvider.js';
8
+ export { useRequestMagicLink } from '../hooks/useRequestMagicLink.js';
9
+ export { useSubscribe } from '../hooks/useSubscribe.js';
10
+ export { useUnsubscribe } from '../hooks/useUnsubscribe.js';
11
+ export { useVerifyMagicLink } from '../hooks/useVerifyMagicLink.js';
8
12
  export { getServerUrl } from '../server-functions/serverUrl.js';
9
13
 
10
14
  //# sourceMappingURL=ui.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/exports/ui.ts"],"sourcesContent":["export type { RequestMagicLinkResponse } from '../components/app/RequestMagicLink.js'\nexport { RequestMagicLink } from '../components/app/RequestMagicLink.js'\n\nexport { RequestOrSubscribe } from '../components/app/RequestOrSubscribe.js'\n\nexport type { SubscribeResponse } from '../components/app/Subscribe.js'\nexport { Subscribe } from '../components/app/Subscribe.js'\n\nexport { SubscriberMenu } from '../components/app/SubscriberMenu.js'\n\nexport type { UnsubscribeResponse } from '../components/app/Unsubscribe.js'\nexport { Unsubscribe } from '../components/app/Unsubscribe.js'\n\nexport type { VerifyMagicLinkResponse } from '../components/app/VerifyMagicLink.js'\nexport { VerifyMagicLink } from '../components/app/VerifyMagicLink.js'\n\nexport type { SubscriberContextType } from '../contexts/SubscriberProvider.js'\nexport { SubscriberProvider, useSubscriber } from '../contexts/SubscriberProvider.js'\n\nexport { getServerUrl } from '../server-functions/serverUrl.js'\n"],"names":["RequestMagicLink","RequestOrSubscribe","Subscribe","SubscriberMenu","Unsubscribe","VerifyMagicLink","SubscriberProvider","useSubscriber","getServerUrl"],"mappings":"AACA,SAASA,gBAAgB,QAAQ,wCAAuC;AAExE,SAASC,kBAAkB,QAAQ,0CAAyC;AAG5E,SAASC,SAAS,QAAQ,iCAAgC;AAE1D,SAASC,cAAc,QAAQ,sCAAqC;AAGpE,SAASC,WAAW,QAAQ,mCAAkC;AAG9D,SAASC,eAAe,QAAQ,uCAAsC;AAGtE,SAASC,kBAAkB,EAAEC,aAAa,QAAQ,oCAAmC;AAErF,SAASC,YAAY,QAAQ,mCAAkC"}
1
+ {"version":3,"sources":["../../src/exports/ui.ts"],"sourcesContent":["export type { RequestMagicLinkResponse } from '../components/app/RequestMagicLink.js'\nexport { RequestMagicLink } from '../components/app/RequestMagicLink.js'\n\nexport { RequestOrSubscribe } from '../components/app/RequestOrSubscribe.js'\n\nexport type { SubscribeResponse } from '../components/app/Subscribe.js'\nexport { Subscribe } from '../components/app/Subscribe.js'\n\nexport { SubscriberMenu } from '../components/app/SubscriberMenu.js'\n\nexport type { UnsubscribeResponse } from '../components/app/Unsubscribe.js'\nexport { Unsubscribe } from '../components/app/Unsubscribe.js'\n\nexport type { VerifyMagicLinkResponse } from '../components/app/VerifyMagicLink.js'\nexport { VerifyMagicLink } from '../components/app/VerifyMagicLink.js'\n\nexport type { SubscriberContextType } from '../contexts/SubscriberProvider.js'\nexport { SubscriberProvider, useSubscriber } from '../contexts/SubscriberProvider.js'\n\nexport { useRequestMagicLink } from '../hooks/useRequestMagicLink.js'\nexport { useSubscribe } from '../hooks/useSubscribe.js'\nexport { useUnsubscribe } from '../hooks/useUnsubscribe.js'\nexport { useVerifyMagicLink } from '../hooks/useVerifyMagicLink.js'\n\nexport { getServerUrl } from '../server-functions/serverUrl.js'\n"],"names":["RequestMagicLink","RequestOrSubscribe","Subscribe","SubscriberMenu","Unsubscribe","VerifyMagicLink","SubscriberProvider","useSubscriber","useRequestMagicLink","useSubscribe","useUnsubscribe","useVerifyMagicLink","getServerUrl"],"mappings":"AACA,SAASA,gBAAgB,QAAQ,wCAAuC;AAExE,SAASC,kBAAkB,QAAQ,0CAAyC;AAG5E,SAASC,SAAS,QAAQ,iCAAgC;AAE1D,SAASC,cAAc,QAAQ,sCAAqC;AAGpE,SAASC,WAAW,QAAQ,mCAAkC;AAG9D,SAASC,eAAe,QAAQ,uCAAsC;AAGtE,SAASC,kBAAkB,EAAEC,aAAa,QAAQ,oCAAmC;AAErF,SAASC,mBAAmB,QAAQ,kCAAiC;AACrE,SAASC,YAAY,QAAQ,2BAA0B;AACvD,SAASC,cAAc,QAAQ,6BAA4B;AAC3D,SAASC,kBAAkB,QAAQ,iCAAgC;AAEnE,SAASC,YAAY,QAAQ,mCAAkC"}
@@ -0,0 +1 @@
1
+ export declare function isAbsoluteURL(url: string): boolean;
@@ -0,0 +1,6 @@
1
+ export function isAbsoluteURL(url) {
2
+ // Checks if it starts with "//" or contains "://" after the first character
3
+ return url.indexOf('://') > 0 || url.indexOf('//') === 0;
4
+ }
5
+
6
+ //# sourceMappingURL=utilities.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/helpers/utilities.ts"],"sourcesContent":["export function isAbsoluteURL(url: string): boolean {\n // Checks if it starts with \"//\" or contains \"://\" after the first character\n return url.indexOf('://') > 0 || url.indexOf('//') === 0\n}\n"],"names":["isAbsoluteURL","url","indexOf"],"mappings":"AAAA,OAAO,SAASA,cAAcC,GAAW;IACvC,4EAA4E;IAC5E,OAAOA,IAAIC,OAAO,CAAC,SAAS,KAAKD,IAAIC,OAAO,CAAC,UAAU;AACzD"}
@@ -0,0 +1,35 @@
1
+ import type { RequestMagicLinkResponse } from '../endpoints/requestMagicLink.js';
2
+ export { RequestMagicLinkResponse };
3
+ /**
4
+ * Options for the useRequestMagicLink hook.
5
+ *
6
+ * @property handleMagicLinkRequested - Callback when a magic link is successfully requested
7
+ * @property verifyData - Optional data sent with the request (e.g. for verification redirect)
8
+ */
9
+ export interface IUseRequestMagicLinkOptions {
10
+ handleMagicLinkRequested?: (result: RequestMagicLinkResponse) => void;
11
+ verifyData?: string;
12
+ }
13
+ /**
14
+ * Return value of useRequestMagicLink.
15
+ *
16
+ * @property result - Success or error message from the last request
17
+ * @property sendMagicLink - Sends a magic link email for the given address
18
+ * @property status - Current status: 'default' | 'sending' | 'sent' | 'error'
19
+ */
20
+ export interface IUseRequestMagicLink {
21
+ result?: string;
22
+ sendMagicLink: (email: string) => Promise<void>;
23
+ status?: RequestMagicLinkStatusValue;
24
+ }
25
+ export type RequestMagicLinkStatusValue = 'default' | 'error' | 'sending' | 'sent';
26
+ /**
27
+ * Hook to request a magic-login link by email. Calls POST /api/emailToken and exposes
28
+ * sendMagicLink, plus result message and status for UI.
29
+ *
30
+ * @param options - Hook options (see IUseRequestMagicLinkOptions)
31
+ * @param options.handleMagicLinkRequested - Callback when a magic link is successfully requested
32
+ * @param options.verifyData - Optional data sent with the request (e.g. for verification redirect)
33
+ * @returns sendMagicLink function, result message, and status (see IUseRequestMagicLink)
34
+ */
35
+ export declare const useRequestMagicLink: ({ handleMagicLinkRequested, verifyData, }: IUseRequestMagicLinkOptions) => IUseRequestMagicLink;