coss 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -10
- package/dist/authjs.d.ts +64 -0
- package/dist/authjs.js +97 -0
- package/dist/better-auth.d.ts +128 -0
- package/dist/better-auth.js +269 -0
- package/dist/client-CCVBtkpV.js +2364 -0
- package/dist/client-avnHVMry.d.ts +3850 -0
- package/dist/device.js +1 -1
- package/dist/index.d.ts +4 -3101
- package/dist/index.js +2 -1956
- package/dist/integration-p3vtLNgq.d.ts +35 -0
- package/dist/mime-C8bhhjvD.js +152 -0
- package/dist/nodemailer.d.ts +75 -0
- package/dist/nodemailer.js +193 -0
- package/package.json +32 -2
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { c as CossSendError, l as lazyClient, o as sha256Hex, r as escapeHtml } from "./mime-C8bhhjvD.js";
|
|
2
|
+
//#region src/better-auth.ts
|
|
3
|
+
/**
|
|
4
|
+
* `coss/better-auth`: Better Auth's email callbacks, sent through coss.com.
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* import { betterAuth } from "better-auth";
|
|
8
|
+
* import { cossEmail } from "coss/better-auth";
|
|
9
|
+
*
|
|
10
|
+
* const email = cossEmail({ from: "Acme <auth@acme.com>", appName: "Acme" });
|
|
11
|
+
*
|
|
12
|
+
* export const auth = betterAuth({
|
|
13
|
+
* emailVerification: { sendVerificationEmail: email.sendVerificationEmail },
|
|
14
|
+
* emailAndPassword: { enabled: true, sendResetPassword: email.sendResetPassword },
|
|
15
|
+
* });
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* The types here describe only what the callbacks read, so the SDK has no dependency on Better
|
|
19
|
+
* Auth; `test/integration-types.ts` checks them against Better Auth's own.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Better Auth email callbacks that send through coss.com. Each throws `CossSendError` when coss.com
|
|
23
|
+
* refuses the email.
|
|
24
|
+
*
|
|
25
|
+
* Auth emails go out with `category: "account"` and open and click tracking off: click tracking
|
|
26
|
+
* would route the secret links through coss.com's redirect.
|
|
27
|
+
*/
|
|
28
|
+
function cossEmail(options) {
|
|
29
|
+
const client = lazyClient("better-auth", options);
|
|
30
|
+
const templates = {
|
|
31
|
+
...DEFAULT_TEMPLATES,
|
|
32
|
+
...options.templates
|
|
33
|
+
};
|
|
34
|
+
const appName = options.appName?.trim() || void 0;
|
|
35
|
+
async function send(params, sendOptions) {
|
|
36
|
+
const result = await client().email.send({
|
|
37
|
+
category: options.category ?? "account",
|
|
38
|
+
tracking: {
|
|
39
|
+
opens: false,
|
|
40
|
+
clicks: false
|
|
41
|
+
},
|
|
42
|
+
...params,
|
|
43
|
+
from: params.from ?? options.from
|
|
44
|
+
}, sendOptions);
|
|
45
|
+
if (result.error) throw new CossSendError(result.error);
|
|
46
|
+
return result.data;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Sends one auth email. A link's token makes a stable idempotency key, so the same callback
|
|
50
|
+
* run twice sends once; the token itself is hashed, never sent as the key.
|
|
51
|
+
*/
|
|
52
|
+
async function sendAuthEmail(to, content, token) {
|
|
53
|
+
const idempotencyKey = token ? `better-auth_${await sha256Hex(token)}` : void 0;
|
|
54
|
+
await send({
|
|
55
|
+
to,
|
|
56
|
+
...content
|
|
57
|
+
}, { idempotencyKey });
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
sendVerificationEmail: (data) => sendAuthEmail(data.user.email, templates.verifyEmail({
|
|
61
|
+
...data,
|
|
62
|
+
appName
|
|
63
|
+
}), data.token),
|
|
64
|
+
sendResetPassword: (data) => sendAuthEmail(data.user.email, templates.resetPassword({
|
|
65
|
+
...data,
|
|
66
|
+
appName
|
|
67
|
+
}), data.token),
|
|
68
|
+
sendChangeEmailConfirmation: (data) => sendAuthEmail(data.user.email, templates.changeEmailConfirmation({
|
|
69
|
+
...data,
|
|
70
|
+
appName
|
|
71
|
+
}), data.token),
|
|
72
|
+
sendDeleteAccountVerification: (data) => sendAuthEmail(data.user.email, templates.deleteAccount({
|
|
73
|
+
...data,
|
|
74
|
+
appName
|
|
75
|
+
}), data.token),
|
|
76
|
+
sendMagicLink: (data) => sendAuthEmail(data.email, templates.magicLink({
|
|
77
|
+
...data,
|
|
78
|
+
appName
|
|
79
|
+
}), data.token),
|
|
80
|
+
sendVerificationOTP: (data) => sendAuthEmail(data.email, templates.verificationOtp({
|
|
81
|
+
...data,
|
|
82
|
+
appName
|
|
83
|
+
})),
|
|
84
|
+
sendInvitationEmail: async (data) => {
|
|
85
|
+
if (!options.invitationUrl) throw new Error("coss/better-auth: sendInvitationEmail needs the `invitationUrl` option (Better Auth passes no URL for invitations).");
|
|
86
|
+
const url = options.invitationUrl(data);
|
|
87
|
+
await sendAuthEmail(data.email, templates.invitation({
|
|
88
|
+
...data,
|
|
89
|
+
url,
|
|
90
|
+
appName
|
|
91
|
+
}));
|
|
92
|
+
},
|
|
93
|
+
send
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/** A plain, unbranded email: a heading, a paragraph, a button, and a closing line. */
|
|
97
|
+
function layout(parts) {
|
|
98
|
+
const action = parts.action ? `<p style="margin:24px 0"><a href="${escapeHtml(parts.action.url)}" style="display:inline-block;padding:10px 18px;border-radius:6px;background:#111;color:#fff;text-decoration:none;font-weight:600">${escapeHtml(parts.action.label)}</a></p>
|
|
99
|
+
<p style="color:#666;font-size:13px">Or paste this link into your browser:<br><a href="${escapeHtml(parts.action.url)}" style="color:#666;word-break:break-all">${escapeHtml(parts.action.url)}</a></p>` : "";
|
|
100
|
+
const code = parts.code ? `<p style="margin:24px 0;font-size:28px;letter-spacing:6px;font-weight:600;font-family:ui-monospace,Menlo,monospace">${escapeHtml(parts.code)}</p>` : "";
|
|
101
|
+
return `<!doctype html>
|
|
102
|
+
<html><body style="margin:0;padding:24px;background:#fff;color:#111;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.5">
|
|
103
|
+
<div style="max-width:480px;margin:0 auto">
|
|
104
|
+
<h1 style="font-size:20px;margin:0 0 16px">${escapeHtml(parts.heading)}</h1>
|
|
105
|
+
<p>${escapeHtml(parts.intro)}</p>
|
|
106
|
+
${action}${code}
|
|
107
|
+
<p style="color:#666;font-size:13px">${escapeHtml(parts.outro)}</p>
|
|
108
|
+
</div>
|
|
109
|
+
</body></html>`;
|
|
110
|
+
}
|
|
111
|
+
function text(lines) {
|
|
112
|
+
return `${lines.filter((line) => line !== void 0).join("\n\n")}\n`;
|
|
113
|
+
}
|
|
114
|
+
const IGNORE = "If you didn't ask for this, you can ignore this email.";
|
|
115
|
+
function on(appName) {
|
|
116
|
+
return appName ? ` on ${appName}` : "";
|
|
117
|
+
}
|
|
118
|
+
const OTP_SUBJECTS = {
|
|
119
|
+
"sign-in": "Your sign-in code",
|
|
120
|
+
"email-verification": "Your verification code",
|
|
121
|
+
"forget-password": "Your password reset code",
|
|
122
|
+
"change-email": "Your code to change your email address"
|
|
123
|
+
};
|
|
124
|
+
/** The default templates: plain, unbranded, with a text version of each. */
|
|
125
|
+
const DEFAULT_TEMPLATES = {
|
|
126
|
+
verifyEmail: ({ url, appName }) => {
|
|
127
|
+
const intro = `Confirm this is your email address to finish setting up your account${on(appName)}.`;
|
|
128
|
+
return {
|
|
129
|
+
subject: "Verify your email address",
|
|
130
|
+
html: layout({
|
|
131
|
+
heading: "Verify your email address",
|
|
132
|
+
intro,
|
|
133
|
+
action: {
|
|
134
|
+
label: "Verify email",
|
|
135
|
+
url
|
|
136
|
+
},
|
|
137
|
+
outro: IGNORE
|
|
138
|
+
}),
|
|
139
|
+
text: text([
|
|
140
|
+
intro,
|
|
141
|
+
url,
|
|
142
|
+
IGNORE
|
|
143
|
+
])
|
|
144
|
+
};
|
|
145
|
+
},
|
|
146
|
+
resetPassword: ({ url, appName }) => {
|
|
147
|
+
const intro = `Someone asked to reset the password of your account${on(appName)}. Choose a new one with this link.`;
|
|
148
|
+
return {
|
|
149
|
+
subject: "Reset your password",
|
|
150
|
+
html: layout({
|
|
151
|
+
heading: "Reset your password",
|
|
152
|
+
intro,
|
|
153
|
+
action: {
|
|
154
|
+
label: "Reset password",
|
|
155
|
+
url
|
|
156
|
+
},
|
|
157
|
+
outro: `${IGNORE} Your password won't change.`
|
|
158
|
+
}),
|
|
159
|
+
text: text([
|
|
160
|
+
intro,
|
|
161
|
+
url,
|
|
162
|
+
`${IGNORE} Your password won't change.`
|
|
163
|
+
])
|
|
164
|
+
};
|
|
165
|
+
},
|
|
166
|
+
changeEmailConfirmation: ({ newEmail, url, appName }) => {
|
|
167
|
+
const intro = `Someone asked to change the email address of your account${on(appName)} to ${newEmail}. Confirm the change with this link.`;
|
|
168
|
+
return {
|
|
169
|
+
subject: "Confirm your new email address",
|
|
170
|
+
html: layout({
|
|
171
|
+
heading: "Confirm your new email address",
|
|
172
|
+
intro,
|
|
173
|
+
action: {
|
|
174
|
+
label: "Confirm change",
|
|
175
|
+
url
|
|
176
|
+
},
|
|
177
|
+
outro: IGNORE
|
|
178
|
+
}),
|
|
179
|
+
text: text([
|
|
180
|
+
intro,
|
|
181
|
+
url,
|
|
182
|
+
IGNORE
|
|
183
|
+
])
|
|
184
|
+
};
|
|
185
|
+
},
|
|
186
|
+
deleteAccount: ({ url, appName }) => {
|
|
187
|
+
const intro = `Someone asked to delete your account${on(appName)}. This can't be undone. Confirm with this link.`;
|
|
188
|
+
return {
|
|
189
|
+
subject: "Confirm account deletion",
|
|
190
|
+
html: layout({
|
|
191
|
+
heading: "Confirm account deletion",
|
|
192
|
+
intro,
|
|
193
|
+
action: {
|
|
194
|
+
label: "Delete my account",
|
|
195
|
+
url
|
|
196
|
+
},
|
|
197
|
+
outro: `${IGNORE} Your account stays as it is.`
|
|
198
|
+
}),
|
|
199
|
+
text: text([
|
|
200
|
+
intro,
|
|
201
|
+
url,
|
|
202
|
+
`${IGNORE} Your account stays as it is.`
|
|
203
|
+
])
|
|
204
|
+
};
|
|
205
|
+
},
|
|
206
|
+
magicLink: ({ url, appName }) => {
|
|
207
|
+
const heading = appName ? `Sign in to ${appName}` : "Sign in";
|
|
208
|
+
const intro = "Use this link to sign in. It works once and expires soon.";
|
|
209
|
+
return {
|
|
210
|
+
subject: heading,
|
|
211
|
+
html: layout({
|
|
212
|
+
heading,
|
|
213
|
+
intro,
|
|
214
|
+
action: {
|
|
215
|
+
label: "Sign in",
|
|
216
|
+
url
|
|
217
|
+
},
|
|
218
|
+
outro: IGNORE
|
|
219
|
+
}),
|
|
220
|
+
text: text([
|
|
221
|
+
intro,
|
|
222
|
+
url,
|
|
223
|
+
IGNORE
|
|
224
|
+
])
|
|
225
|
+
};
|
|
226
|
+
},
|
|
227
|
+
verificationOtp: ({ otp, type, appName }) => {
|
|
228
|
+
const subject = OTP_SUBJECTS[type] ?? "Your verification code";
|
|
229
|
+
const intro = `Enter this code${on(appName)}. It expires in a few minutes.`;
|
|
230
|
+
return {
|
|
231
|
+
subject,
|
|
232
|
+
html: layout({
|
|
233
|
+
heading: subject,
|
|
234
|
+
intro,
|
|
235
|
+
code: otp,
|
|
236
|
+
outro: IGNORE
|
|
237
|
+
}),
|
|
238
|
+
text: text([
|
|
239
|
+
intro,
|
|
240
|
+
otp,
|
|
241
|
+
IGNORE
|
|
242
|
+
])
|
|
243
|
+
};
|
|
244
|
+
},
|
|
245
|
+
invitation: ({ organization, inviter, url, appName }) => {
|
|
246
|
+
const who = inviter.user.name?.trim() || inviter.user.email;
|
|
247
|
+
const subject = `Join ${organization.name}${on(appName)}`;
|
|
248
|
+
const intro = `${who} invited you to join ${organization.name}${on(appName)}.`;
|
|
249
|
+
return {
|
|
250
|
+
subject,
|
|
251
|
+
html: layout({
|
|
252
|
+
heading: subject,
|
|
253
|
+
intro,
|
|
254
|
+
action: {
|
|
255
|
+
label: "Accept invitation",
|
|
256
|
+
url
|
|
257
|
+
},
|
|
258
|
+
outro: "If you weren't expecting this invitation, you can ignore this email."
|
|
259
|
+
}),
|
|
260
|
+
text: text([
|
|
261
|
+
intro,
|
|
262
|
+
url,
|
|
263
|
+
"If you weren't expecting this invitation, you can ignore this email."
|
|
264
|
+
])
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
//#endregion
|
|
269
|
+
export { CossSendError, DEFAULT_TEMPLATES, cossEmail };
|