payload-subscribers-plugin 0.0.1 → 0.0.4
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/LICENSE +21 -0
- package/package.json +2 -3
- package/dist/components/BeforeDashboardClient.d.ts +0 -1
- package/dist/components/BeforeDashboardClient.js +0 -40
- package/dist/components/BeforeDashboardClient.js.map +0 -1
- package/dist/components/BeforeDashboardServer.d.ts +0 -2
- package/dist/components/BeforeDashboardServer.js +0 -22
- package/dist/components/BeforeDashboardServer.js.map +0 -1
- package/dist/components/BeforeDashboardServer.module.css +0 -5
- package/dist/components/app/RequestMagicLink.module.css +0 -5
- package/dist/components/app/SelectOptInChannels.module.css +0 -5
- package/dist/components/app/Subscribe.module.css +0 -5
- package/dist/components/app/VerifyMagicLink.module.css +0 -5
- package/dist/copied/payload.config.d.ts +0 -2
- package/dist/endpoints/customEndpointHandler.d.ts +0 -2
- package/dist/endpoints/customEndpointHandler.js +0 -7
- package/dist/endpoints/customEndpointHandler.js.map +0 -1
- package/dist/exports/client.d.ts +0 -1
- package/dist/exports/client.js +0 -3
- package/dist/exports/client.js.map +0 -1
- package/dist/exports/rsc.d.ts +0 -1
- package/dist/exports/rsc.js +0 -3
- package/dist/exports/rsc.js.map +0 -1
- package/dist/helpers/serverConfig.d.ts +0 -4
- package/dist/helpers/serverConfig.js +0 -22
- package/dist/helpers/serverConfig.js.map +0 -1
- package/dist/server-functions/subscriberAuth.d.ts +0 -11
- package/src/collections/OptInChannels.ts +0 -45
- package/src/collections/Subscribers.ts +0 -99
- package/src/collections/fields/OptedInChannels.ts +0 -12
- package/src/components/app/RequestMagicLink.tsx +0 -129
- package/src/components/app/RequestOrSubscribe.tsx +0 -58
- package/src/components/app/SelectOptInChannels.tsx +0 -147
- package/src/components/app/Subscribe.tsx +0 -190
- package/src/components/app/SubscriberMenu.tsx +0 -46
- package/src/components/app/VerifyMagicLink.tsx +0 -197
- package/src/components/app/helpers.ts +0 -6
- package/src/components/app/shared.module.css +0 -14
- package/src/contexts/SubscriberProvider.tsx +0 -122
- package/src/copied/payload-types.ts +0 -478
- package/src/endpoints/getOptInChannels.ts +0 -56
- package/src/endpoints/logout.ts +0 -104
- package/src/endpoints/requestMagicLink.ts +0 -139
- package/src/endpoints/subscribe.ts +0 -435
- package/src/endpoints/subscriberAuth.ts +0 -100
- package/src/endpoints/verifyMagicLink.ts +0 -164
- package/src/exports/index.ts +0 -1
- package/src/exports/ui.ts +0 -17
- package/src/helpers/testData.ts +0 -2
- package/src/helpers/token.ts +0 -14
- package/src/helpers/verifyOptIns.ts +0 -39
- package/src/index.ts +0 -207
- package/src/react-hooks/useServerUrl.tsx +0 -18
- package/src/server-functions/serverUrl.ts +0 -38
package/src/endpoints/logout.ts
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import type { CollectionSlug, Endpoint, PayloadHandler } from 'payload'
|
|
2
|
-
|
|
3
|
-
import { headers as nextHeaders } from 'next/headers.js'
|
|
4
|
-
|
|
5
|
-
import { defaultCollectionSlug } from '../collections/Subscribers.js'
|
|
6
|
-
|
|
7
|
-
export type LogoutResponse =
|
|
8
|
-
| {
|
|
9
|
-
error: string
|
|
10
|
-
now: string
|
|
11
|
-
}
|
|
12
|
-
| {
|
|
13
|
-
message: string
|
|
14
|
-
now: string
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* createEndpointLogout
|
|
19
|
-
* @param options
|
|
20
|
-
* @returns
|
|
21
|
-
*
|
|
22
|
-
* Factory to generate the endpoint config with handler based on input option for subscribersCollectionSlug
|
|
23
|
-
*
|
|
24
|
-
*/
|
|
25
|
-
function createEndpointLogout({
|
|
26
|
-
subscribersCollectionSlug = defaultCollectionSlug,
|
|
27
|
-
}: {
|
|
28
|
-
subscribersCollectionSlug: CollectionSlug
|
|
29
|
-
}): Endpoint {
|
|
30
|
-
const logoutHandler: PayloadHandler = async (req) => {
|
|
31
|
-
const headers = await nextHeaders()
|
|
32
|
-
|
|
33
|
-
try {
|
|
34
|
-
const logoutResult = await fetch(
|
|
35
|
-
`${req.payload.config.serverURL}/api/${subscribersCollectionSlug}/logout`,
|
|
36
|
-
{
|
|
37
|
-
headers,
|
|
38
|
-
method: 'POST',
|
|
39
|
-
},
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
const logoutResultData = await logoutResult.json()
|
|
43
|
-
|
|
44
|
-
if (logoutResult.ok) {
|
|
45
|
-
return Response.json({
|
|
46
|
-
message: logoutResultData.message,
|
|
47
|
-
now: new Date().toISOString(),
|
|
48
|
-
} as LogoutResponse)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (
|
|
52
|
-
logoutResult.status == 400 &&
|
|
53
|
-
logoutResultData.errors?.map((e: { message: string }) => e.message).includes('No User')
|
|
54
|
-
) {
|
|
55
|
-
return Response.json(
|
|
56
|
-
{
|
|
57
|
-
error: `Logout failed: 'No User'`,
|
|
58
|
-
now: new Date().toISOString(),
|
|
59
|
-
} as LogoutResponse,
|
|
60
|
-
{
|
|
61
|
-
status: 400,
|
|
62
|
-
},
|
|
63
|
-
)
|
|
64
|
-
}
|
|
65
|
-
return Response.json(
|
|
66
|
-
{
|
|
67
|
-
error: `Logout failed: ${
|
|
68
|
-
logoutResultData.errors
|
|
69
|
-
? logoutResultData.errors?.map((e: { message: string }) => e.message).join(' // ')
|
|
70
|
-
: JSON.stringify(logoutResultData)
|
|
71
|
-
}`,
|
|
72
|
-
now: new Date().toISOString(),
|
|
73
|
-
} as LogoutResponse,
|
|
74
|
-
{
|
|
75
|
-
status: 400,
|
|
76
|
-
},
|
|
77
|
-
)
|
|
78
|
-
} catch (error) {
|
|
79
|
-
// throw new Error(`Logout failed: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
80
|
-
return Response.json(
|
|
81
|
-
{
|
|
82
|
-
error: `Logout failed: ${JSON.stringify(error)}`,
|
|
83
|
-
now: new Date().toISOString(),
|
|
84
|
-
} as LogoutResponse,
|
|
85
|
-
{
|
|
86
|
-
status: 400,
|
|
87
|
-
},
|
|
88
|
-
)
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* logout Endpoint Config
|
|
94
|
-
*/
|
|
95
|
-
const logoutEndpoint: Endpoint = {
|
|
96
|
-
handler: logoutHandler,
|
|
97
|
-
method: 'post',
|
|
98
|
-
path: '/logout',
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return logoutEndpoint
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export default createEndpointLogout
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
import type { CollectionSlug, Endpoint, PayloadHandler, PayloadRequest, TypedUser } from 'payload'
|
|
2
|
-
|
|
3
|
-
import crypto from 'crypto'
|
|
4
|
-
import { defaultCollectionSlug } from '../collections/Subscribers.js'
|
|
5
|
-
|
|
6
|
-
import { getTokenAndHash } from '../helpers/token.js'
|
|
7
|
-
|
|
8
|
-
export type RequestMagicLinkResponse =
|
|
9
|
-
| {
|
|
10
|
-
emailResult: any
|
|
11
|
-
now: string
|
|
12
|
-
}
|
|
13
|
-
| {
|
|
14
|
-
error: string
|
|
15
|
-
now: string
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* createEndpointRequestMagicLink
|
|
20
|
-
* @param options
|
|
21
|
-
* @returns
|
|
22
|
-
*
|
|
23
|
-
* Factory to generate the endpoint config with handler based on input option for subscribersCollectionSlug
|
|
24
|
-
*
|
|
25
|
-
*/
|
|
26
|
-
function createEndpointRequestMagicLink({
|
|
27
|
-
subscribersCollectionSlug = defaultCollectionSlug,
|
|
28
|
-
}: {
|
|
29
|
-
subscribersCollectionSlug: CollectionSlug
|
|
30
|
-
}): Endpoint {
|
|
31
|
-
/**
|
|
32
|
-
* requestMagicLink Endpoint Handler
|
|
33
|
-
* @param req
|
|
34
|
-
* @data { email }
|
|
35
|
-
* @returns { status: 200, json: {message: string, now: date} }
|
|
36
|
-
* @returns { status: 400, json: {error: ('Bad data' | 'Unknown email result'), now: date} }
|
|
37
|
-
*/
|
|
38
|
-
const requestMagicLinkHandler: PayloadHandler = async (req: PayloadRequest) => {
|
|
39
|
-
const data = req?.json ? await req.json() : {}
|
|
40
|
-
const { email, forwardUrl } = data // if by POST data
|
|
41
|
-
// const { email } = req.routeParams // if by path
|
|
42
|
-
|
|
43
|
-
if (!email) {
|
|
44
|
-
return Response.json(
|
|
45
|
-
{ error: 'Bad data', now: new Date().toISOString() } as RequestMagicLinkResponse,
|
|
46
|
-
{ status: 400 },
|
|
47
|
-
)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const userResults = await req.payload.find({
|
|
51
|
-
collection: subscribersCollectionSlug,
|
|
52
|
-
where: {
|
|
53
|
-
email: { equals: email },
|
|
54
|
-
},
|
|
55
|
-
})
|
|
56
|
-
const user = userResults.docs[0] as TypedUser
|
|
57
|
-
|
|
58
|
-
if (!user) {
|
|
59
|
-
//
|
|
60
|
-
// Create subscriber with status 'pending',
|
|
61
|
-
// and an invisible unknowable password,
|
|
62
|
-
//
|
|
63
|
-
const { tokenHash: tokenHash2 } = getTokenAndHash() // Unknowable
|
|
64
|
-
const createResult = await req.payload.create({
|
|
65
|
-
collection: subscribersCollectionSlug,
|
|
66
|
-
data: {
|
|
67
|
-
email,
|
|
68
|
-
password: tokenHash2,
|
|
69
|
-
status: 'pending',
|
|
70
|
-
},
|
|
71
|
-
draft: false,
|
|
72
|
-
})
|
|
73
|
-
if (!createResult) {
|
|
74
|
-
return Response.json(
|
|
75
|
-
{ error: 'Bad data', now: new Date().toISOString() } as RequestMagicLinkResponse,
|
|
76
|
-
{ status: 400 },
|
|
77
|
-
)
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Update user with verificationToken
|
|
82
|
-
const token = crypto.randomBytes(32).toString('hex')
|
|
83
|
-
const tokenHash = crypto.createHash('sha256').update(token).digest('hex')
|
|
84
|
-
const expiresAt = new Date(Date.now() + 15 * 60 * 1000) // 15 mins
|
|
85
|
-
await req.payload.update({
|
|
86
|
-
collection: subscribersCollectionSlug,
|
|
87
|
-
data: {
|
|
88
|
-
verificationToken: tokenHash,
|
|
89
|
-
verificationTokenExpires: expiresAt.toISOString(),
|
|
90
|
-
},
|
|
91
|
-
where: {
|
|
92
|
-
email: { equals: user.email },
|
|
93
|
-
},
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
// Send email
|
|
97
|
-
const forwardUrlParam = forwardUrl ? `&forwardUrl=${encodeURI(forwardUrl)}` : ''
|
|
98
|
-
const magicLink = `${req.payload.config.serverURL}/verify?token=${token}&email=${email}${forwardUrlParam}`
|
|
99
|
-
const subject = data.subject || 'Your Magic Login Link'
|
|
100
|
-
const message = `
|
|
101
|
-
${data.message || '<p>Use this link to log in:</p>'}
|
|
102
|
-
<p><a href="${magicLink}"><b>Login</b></a></p>
|
|
103
|
-
`
|
|
104
|
-
const emailResult = await req.payload.sendEmail({
|
|
105
|
-
html: message,
|
|
106
|
-
subject,
|
|
107
|
-
to: user.email,
|
|
108
|
-
})
|
|
109
|
-
// req.payload.logger.info(`email result: ${JSON.stringify(emailResult)}`)
|
|
110
|
-
// return data; // Return data to allow normal submission if needed
|
|
111
|
-
if (!emailResult) {
|
|
112
|
-
return Response.json(
|
|
113
|
-
{
|
|
114
|
-
error: 'Unknown email result',
|
|
115
|
-
now: new Date().toISOString(),
|
|
116
|
-
} as RequestMagicLinkResponse,
|
|
117
|
-
{ status: 400 },
|
|
118
|
-
)
|
|
119
|
-
}
|
|
120
|
-
req.payload.logger.info(`requestMagicLinkHandler email sent \n ${magicLink}`)
|
|
121
|
-
return Response.json({
|
|
122
|
-
emailResult,
|
|
123
|
-
now: new Date().toISOString(),
|
|
124
|
-
} as RequestMagicLinkResponse)
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* requestMagicLink Endpoint Config
|
|
129
|
-
*/
|
|
130
|
-
const requestMagicLinkEndpoint: Endpoint = {
|
|
131
|
-
handler: requestMagicLinkHandler,
|
|
132
|
-
method: 'post',
|
|
133
|
-
path: '/emailToken',
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return requestMagicLinkEndpoint
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
export default createEndpointRequestMagicLink
|
|
@@ -1,435 +0,0 @@
|
|
|
1
|
-
import type { CollectionSlug, Endpoint, PayloadHandler } from 'payload'
|
|
2
|
-
import type { Subscriber } from 'src/copied/payload-types.js'
|
|
3
|
-
|
|
4
|
-
import { defaultCollectionSlug } from '../collections/Subscribers.js'
|
|
5
|
-
|
|
6
|
-
import { getTokenAndHash } from '../helpers/token.js'
|
|
7
|
-
import { verifyOptIns } from '../helpers/verifyOptIns.js'
|
|
8
|
-
|
|
9
|
-
export type SubscribeResponse =
|
|
10
|
-
// When subscriber optIns are updated...
|
|
11
|
-
| {
|
|
12
|
-
email: string
|
|
13
|
-
now: string
|
|
14
|
-
optIns: string[]
|
|
15
|
-
}
|
|
16
|
-
// When a verify link is emailed...
|
|
17
|
-
| {
|
|
18
|
-
emailResult: any
|
|
19
|
-
now: string
|
|
20
|
-
}
|
|
21
|
-
// When any error occurs...
|
|
22
|
-
| {
|
|
23
|
-
error: string
|
|
24
|
-
now: string
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* createEndpointLogout
|
|
29
|
-
* @param options
|
|
30
|
-
* @returns
|
|
31
|
-
*
|
|
32
|
-
* Factory to generate the endpoint config with handler based on input option for subscribersCollectionSlug
|
|
33
|
-
*
|
|
34
|
-
*/
|
|
35
|
-
function createEndpointSubscribe({
|
|
36
|
-
subscribersCollectionSlug = defaultCollectionSlug,
|
|
37
|
-
}: {
|
|
38
|
-
subscribersCollectionSlug: CollectionSlug
|
|
39
|
-
}): Endpoint {
|
|
40
|
-
/**
|
|
41
|
-
* subscribe Endpoint Handler
|
|
42
|
-
* @param req
|
|
43
|
-
* @data { email }
|
|
44
|
-
* @returns { status: 200, json: {message: string, now: date} }
|
|
45
|
-
* @returns { status: 400, json: {error: ('Bad data' | 'Already subscribed' | 'Unknown email result'), now: date} }
|
|
46
|
-
*/
|
|
47
|
-
const subscribeHandler: PayloadHandler = async (req) => {
|
|
48
|
-
const data = req?.json ? await req.json() : {}
|
|
49
|
-
const {
|
|
50
|
-
afterVerifyUrl,
|
|
51
|
-
email,
|
|
52
|
-
optIns,
|
|
53
|
-
}: { afterVerifyUrl: string; email: string; optIns: string[] } = data // if by POST data
|
|
54
|
-
// const { email } = req.routeParams // if by path
|
|
55
|
-
|
|
56
|
-
//
|
|
57
|
-
// HELPERS
|
|
58
|
-
// Some of these functions make use of the scope within handler,
|
|
59
|
-
// and would have to be refactored if moved out.
|
|
60
|
-
//
|
|
61
|
-
const createSubscriber = async ({
|
|
62
|
-
optIns,
|
|
63
|
-
password,
|
|
64
|
-
status,
|
|
65
|
-
verificationToken,
|
|
66
|
-
verificationTokenExpires,
|
|
67
|
-
}: {
|
|
68
|
-
email: string
|
|
69
|
-
optIns?: string[]
|
|
70
|
-
password?: string
|
|
71
|
-
status?: 'pending' | 'subscribed' | 'unsubscribed'
|
|
72
|
-
verificationToken?: string
|
|
73
|
-
verificationTokenExpires?: Date
|
|
74
|
-
}) => {
|
|
75
|
-
await req.payload.create({
|
|
76
|
-
collection: subscribersCollectionSlug,
|
|
77
|
-
data: {
|
|
78
|
-
email,
|
|
79
|
-
optIns,
|
|
80
|
-
password,
|
|
81
|
-
status: status || 'pending',
|
|
82
|
-
verificationToken,
|
|
83
|
-
verificationTokenExpires: verificationTokenExpires?.toISOString(),
|
|
84
|
-
},
|
|
85
|
-
draft: false,
|
|
86
|
-
})
|
|
87
|
-
}
|
|
88
|
-
const updateSubscriber = async ({
|
|
89
|
-
id,
|
|
90
|
-
optIns,
|
|
91
|
-
password,
|
|
92
|
-
status,
|
|
93
|
-
verificationToken,
|
|
94
|
-
verificationTokenExpires,
|
|
95
|
-
}: {
|
|
96
|
-
id: string
|
|
97
|
-
optIns?: string[]
|
|
98
|
-
password?: string
|
|
99
|
-
status?: 'pending' | 'subscribed' | 'unsubscribed'
|
|
100
|
-
verificationToken?: string
|
|
101
|
-
verificationTokenExpires?: Date | null
|
|
102
|
-
}) => {
|
|
103
|
-
const updateResults = await req.payload.update({
|
|
104
|
-
id,
|
|
105
|
-
collection: subscribersCollectionSlug,
|
|
106
|
-
data: {
|
|
107
|
-
optIns,
|
|
108
|
-
password,
|
|
109
|
-
status,
|
|
110
|
-
verificationToken,
|
|
111
|
-
verificationTokenExpires: verificationTokenExpires?.toISOString() || null,
|
|
112
|
-
},
|
|
113
|
-
depth: 0,
|
|
114
|
-
})
|
|
115
|
-
return updateResults
|
|
116
|
-
}
|
|
117
|
-
const sendVerifyEmail = async ({
|
|
118
|
-
email,
|
|
119
|
-
forwardUrl,
|
|
120
|
-
linkText,
|
|
121
|
-
message,
|
|
122
|
-
subject,
|
|
123
|
-
token,
|
|
124
|
-
}: {
|
|
125
|
-
email: string
|
|
126
|
-
forwardUrl?: string
|
|
127
|
-
linkText: string
|
|
128
|
-
message: string
|
|
129
|
-
subject: string
|
|
130
|
-
token: string
|
|
131
|
-
}) => {
|
|
132
|
-
const forwardUrlParam = forwardUrl ? `&forwardUrl=${encodeURI(forwardUrl)}` : ''
|
|
133
|
-
const magicLink = `${req.payload.config.serverURL}/verify?token=${token}&email=${email}${forwardUrlParam}`
|
|
134
|
-
const html = message + `<p><a href="${magicLink}">${linkText}</a></p>`
|
|
135
|
-
const emailResult = await req.payload.sendEmail({
|
|
136
|
-
html,
|
|
137
|
-
subject,
|
|
138
|
-
to: email,
|
|
139
|
-
})
|
|
140
|
-
req.payload.logger.info(`subscribe email sent \n ${magicLink}`)
|
|
141
|
-
return emailResult
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
//
|
|
145
|
-
// VALIDATE INPUT
|
|
146
|
-
//
|
|
147
|
-
// Require email
|
|
148
|
-
if (!email) {
|
|
149
|
-
req.payload.logger.error(
|
|
150
|
-
JSON.stringify(
|
|
151
|
-
{ error: 'Bad data', now: new Date().toISOString() } as SubscribeResponse,
|
|
152
|
-
undefined,
|
|
153
|
-
2,
|
|
154
|
-
),
|
|
155
|
-
)
|
|
156
|
-
return Response.json(
|
|
157
|
-
{ error: 'Bad data', now: new Date().toISOString() } as SubscribeResponse,
|
|
158
|
-
{ status: 400 },
|
|
159
|
-
)
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
//
|
|
163
|
-
// Validate OptInChannels
|
|
164
|
-
const { invalidOptInsInput, verifiedOptInIDs } = await verifyOptIns(req.payload, optIns)
|
|
165
|
-
|
|
166
|
-
if (invalidOptInsInput) {
|
|
167
|
-
req.payload.logger.error(
|
|
168
|
-
JSON.stringify(
|
|
169
|
-
{
|
|
170
|
-
error: 'Invalid input: ' + JSON.stringify(optIns),
|
|
171
|
-
now: new Date().toISOString(),
|
|
172
|
-
} as SubscribeResponse,
|
|
173
|
-
undefined,
|
|
174
|
-
2,
|
|
175
|
-
),
|
|
176
|
-
)
|
|
177
|
-
return Response.json(
|
|
178
|
-
{
|
|
179
|
-
error: 'Invalid input: ' + JSON.stringify(optIns),
|
|
180
|
-
now: new Date().toISOString(),
|
|
181
|
-
} as SubscribeResponse,
|
|
182
|
-
{ status: 400 },
|
|
183
|
-
)
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
//
|
|
187
|
-
// Verify subscriber exists
|
|
188
|
-
const userResults = await req.payload.find({
|
|
189
|
-
collection: subscribersCollectionSlug,
|
|
190
|
-
where: {
|
|
191
|
-
email: { equals: email },
|
|
192
|
-
},
|
|
193
|
-
})
|
|
194
|
-
const subscriber = userResults.docs[0] as Subscriber
|
|
195
|
-
|
|
196
|
-
//
|
|
197
|
-
// Now we have a subscriber and validatedOptIns
|
|
198
|
-
// Handle scenarios
|
|
199
|
-
//
|
|
200
|
-
// ********************************************************
|
|
201
|
-
//
|
|
202
|
-
if (req.user && req.user.email != email) {
|
|
203
|
-
//
|
|
204
|
-
// Error: Auth-ed user doesn't match subscriber email
|
|
205
|
-
//
|
|
206
|
-
req.payload.logger.error(
|
|
207
|
-
JSON.stringify(
|
|
208
|
-
{
|
|
209
|
-
error: 'Unauthorized: ' + email,
|
|
210
|
-
now: new Date().toISOString(),
|
|
211
|
-
} as SubscribeResponse,
|
|
212
|
-
undefined,
|
|
213
|
-
2,
|
|
214
|
-
),
|
|
215
|
-
)
|
|
216
|
-
return Response.json(
|
|
217
|
-
{
|
|
218
|
-
error: 'Unauthorized: ' + email,
|
|
219
|
-
now: new Date().toISOString(),
|
|
220
|
-
} as SubscribeResponse,
|
|
221
|
-
{ status: 400 },
|
|
222
|
-
)
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
//
|
|
226
|
-
// ********************************************************
|
|
227
|
-
//
|
|
228
|
-
if (!subscriber) {
|
|
229
|
-
//
|
|
230
|
-
// Create subscriber with status 'pending',
|
|
231
|
-
// and an invisible unknowable password,
|
|
232
|
-
// and send a verify email
|
|
233
|
-
// Pass all optIns through verify link
|
|
234
|
-
//
|
|
235
|
-
const { expiresAt, token, tokenHash } = getTokenAndHash(15 * 60 * 1000) // Use for magic link
|
|
236
|
-
const { tokenHash: tokenHash2 } = getTokenAndHash() // Unknowable
|
|
237
|
-
await createSubscriber({
|
|
238
|
-
email,
|
|
239
|
-
optIns,
|
|
240
|
-
password: tokenHash2,
|
|
241
|
-
status: 'pending',
|
|
242
|
-
verificationToken: tokenHash,
|
|
243
|
-
verificationTokenExpires: expiresAt,
|
|
244
|
-
})
|
|
245
|
-
|
|
246
|
-
//
|
|
247
|
-
// Send email
|
|
248
|
-
const emailResult = await sendVerifyEmail({
|
|
249
|
-
email,
|
|
250
|
-
forwardUrl: afterVerifyUrl,
|
|
251
|
-
linkText: '<b>Verify</b>',
|
|
252
|
-
message: data.message || `<p>Click here to verify your subscription:</p>`,
|
|
253
|
-
subject: data.subject || 'Please verify your subscription',
|
|
254
|
-
token,
|
|
255
|
-
})
|
|
256
|
-
if (!emailResult) {
|
|
257
|
-
req.payload.logger.error(
|
|
258
|
-
JSON.stringify(
|
|
259
|
-
{ error: 'Unknown email result', now: new Date().toISOString() } as SubscribeResponse,
|
|
260
|
-
undefined,
|
|
261
|
-
2,
|
|
262
|
-
),
|
|
263
|
-
)
|
|
264
|
-
return Response.json(
|
|
265
|
-
{ error: 'Unknown email result', now: new Date().toISOString() } as SubscribeResponse,
|
|
266
|
-
{ status: 400 },
|
|
267
|
-
)
|
|
268
|
-
}
|
|
269
|
-
return Response.json({ emailResult, now: new Date().toISOString() } as SubscribeResponse)
|
|
270
|
-
//
|
|
271
|
-
}
|
|
272
|
-
//
|
|
273
|
-
// ********************************************************
|
|
274
|
-
//
|
|
275
|
-
if (!req.user && subscriber) {
|
|
276
|
-
//
|
|
277
|
-
// Send magic link to log the user in
|
|
278
|
-
// Pass all optIns through verify link
|
|
279
|
-
//
|
|
280
|
-
const { expiresAt, token, tokenHash } = getTokenAndHash(15 * 60 * 1000) // Use for magic link
|
|
281
|
-
// Update subscriber with token for pending email
|
|
282
|
-
const updateResults = await updateSubscriber({
|
|
283
|
-
id: subscriber.id,
|
|
284
|
-
verificationToken: tokenHash,
|
|
285
|
-
verificationTokenExpires: expiresAt,
|
|
286
|
-
})
|
|
287
|
-
if (!updateResults) {
|
|
288
|
-
req.payload.logger.error(
|
|
289
|
-
JSON.stringify(
|
|
290
|
-
{ error: 'Unknown error', now: new Date().toISOString() } as SubscribeResponse,
|
|
291
|
-
undefined,
|
|
292
|
-
2,
|
|
293
|
-
),
|
|
294
|
-
)
|
|
295
|
-
return Response.json(
|
|
296
|
-
{ error: 'Unknown error', now: new Date().toISOString() } as SubscribeResponse,
|
|
297
|
-
{ status: 400 },
|
|
298
|
-
)
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
//
|
|
302
|
-
// Send email
|
|
303
|
-
const emailResult = await sendVerifyEmail({
|
|
304
|
-
email,
|
|
305
|
-
forwardUrl: afterVerifyUrl,
|
|
306
|
-
linkText: 'Verify',
|
|
307
|
-
message: data.message || `<h1>Click here to verify your subscription:</h1>`,
|
|
308
|
-
subject: data.subject || 'Please verify your subscription',
|
|
309
|
-
token,
|
|
310
|
-
})
|
|
311
|
-
if (!emailResult) {
|
|
312
|
-
req.payload.logger.error(
|
|
313
|
-
JSON.stringify(
|
|
314
|
-
{ error: 'Unknown email result', now: new Date().toISOString() } as SubscribeResponse,
|
|
315
|
-
undefined,
|
|
316
|
-
2,
|
|
317
|
-
),
|
|
318
|
-
)
|
|
319
|
-
return Response.json(
|
|
320
|
-
{ error: 'Unknown email result', now: new Date().toISOString() } as SubscribeResponse,
|
|
321
|
-
{ status: 400 },
|
|
322
|
-
)
|
|
323
|
-
}
|
|
324
|
-
return Response.json({ emailResult, now: new Date().toISOString() } as SubscribeResponse)
|
|
325
|
-
}
|
|
326
|
-
//
|
|
327
|
-
// ********************************************************
|
|
328
|
-
//
|
|
329
|
-
if (req.user && subscriber && subscriber.status == 'pending') {
|
|
330
|
-
//
|
|
331
|
-
// Send magic link to verify the email and log the user in
|
|
332
|
-
// Pass all optIns through verify link
|
|
333
|
-
//
|
|
334
|
-
const { expiresAt, token, tokenHash } = getTokenAndHash(15 * 60 * 1000) // Use for magic link
|
|
335
|
-
// Create subscriber with token for pending email
|
|
336
|
-
const updateResults = await updateSubscriber({
|
|
337
|
-
id: subscriber.id,
|
|
338
|
-
verificationToken: tokenHash,
|
|
339
|
-
verificationTokenExpires: expiresAt,
|
|
340
|
-
})
|
|
341
|
-
if (!updateResults) {
|
|
342
|
-
req.payload.logger.error(
|
|
343
|
-
JSON.stringify(
|
|
344
|
-
{ error: 'Unknown error', now: new Date().toISOString() } as SubscribeResponse,
|
|
345
|
-
undefined,
|
|
346
|
-
2,
|
|
347
|
-
),
|
|
348
|
-
)
|
|
349
|
-
return Response.json(
|
|
350
|
-
{ error: 'Unknown error', now: new Date().toISOString() } as SubscribeResponse,
|
|
351
|
-
{ status: 400 },
|
|
352
|
-
)
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
const emailResult = await sendVerifyEmail({
|
|
356
|
-
email,
|
|
357
|
-
forwardUrl: afterVerifyUrl,
|
|
358
|
-
linkText: 'Verify',
|
|
359
|
-
message: data.message || `<h1>Click here to verify your email:</h1>`,
|
|
360
|
-
subject: data.subject || 'Please verify your subscription',
|
|
361
|
-
token,
|
|
362
|
-
})
|
|
363
|
-
if (!emailResult) {
|
|
364
|
-
req.payload.logger.error(
|
|
365
|
-
JSON.stringify(
|
|
366
|
-
{ error: 'Unknown email result', now: new Date().toISOString() } as SubscribeResponse,
|
|
367
|
-
undefined,
|
|
368
|
-
2,
|
|
369
|
-
),
|
|
370
|
-
)
|
|
371
|
-
return Response.json(
|
|
372
|
-
{ error: 'Unknown email result', now: new Date().toISOString() } as SubscribeResponse,
|
|
373
|
-
{ status: 400 },
|
|
374
|
-
)
|
|
375
|
-
}
|
|
376
|
-
return Response.json({ emailResult, now: new Date().toISOString() } as SubscribeResponse)
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
//
|
|
380
|
-
// ********************************************************
|
|
381
|
-
//
|
|
382
|
-
if (req.user && subscriber && subscriber.status != 'pending') {
|
|
383
|
-
//
|
|
384
|
-
// Update subscriber with status 'subscribed',
|
|
385
|
-
// an invisible unknowable password,
|
|
386
|
-
// and if any optIns input exists, set subscriber optIns
|
|
387
|
-
// to EXACTLY verifiedOptInIDs (potentially unsubscribing from any not in verifiedOptInIDs)
|
|
388
|
-
//
|
|
389
|
-
const { tokenHash } = getTokenAndHash() // Use for magic link
|
|
390
|
-
// Update subscriber with optIns
|
|
391
|
-
const updateResults = (await updateSubscriber({
|
|
392
|
-
id: subscriber.id,
|
|
393
|
-
optIns: verifiedOptInIDs,
|
|
394
|
-
password: tokenHash,
|
|
395
|
-
status: 'subscribed',
|
|
396
|
-
verificationToken: '',
|
|
397
|
-
verificationTokenExpires: null,
|
|
398
|
-
})) as Subscriber
|
|
399
|
-
|
|
400
|
-
// Return results, including the verified optIns
|
|
401
|
-
return Response.json({
|
|
402
|
-
email: updateResults.email,
|
|
403
|
-
now: new Date().toISOString(),
|
|
404
|
-
optIns: updateResults.optIns,
|
|
405
|
-
} as SubscribeResponse)
|
|
406
|
-
}
|
|
407
|
-
//
|
|
408
|
-
// Uncaught case
|
|
409
|
-
//
|
|
410
|
-
req.payload.logger.error(
|
|
411
|
-
JSON.stringify(
|
|
412
|
-
{ error: 'Unknown error', now: new Date().toISOString() } as SubscribeResponse,
|
|
413
|
-
undefined,
|
|
414
|
-
2,
|
|
415
|
-
),
|
|
416
|
-
)
|
|
417
|
-
return Response.json(
|
|
418
|
-
{ error: 'Unknown error', now: new Date().toISOString() } as SubscribeResponse,
|
|
419
|
-
{ status: 400 },
|
|
420
|
-
)
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
/**
|
|
424
|
-
* subscribe Endpoint Config
|
|
425
|
-
*/
|
|
426
|
-
const subscribeEndpoint: Endpoint = {
|
|
427
|
-
handler: subscribeHandler,
|
|
428
|
-
method: 'post',
|
|
429
|
-
path: '/subscribe',
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
return subscribeEndpoint
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
export default createEndpointSubscribe
|