@atproto/pds 0.4.226 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/dist/account-manager/account-manager.d.ts +19 -5
- package/dist/account-manager/account-manager.d.ts.map +1 -1
- package/dist/account-manager/account-manager.js +94 -12
- package/dist/account-manager/account-manager.js.map +1 -1
- package/dist/account-manager/helpers/account.d.ts +2 -0
- package/dist/account-manager/helpers/account.d.ts.map +1 -1
- package/dist/account-manager/helpers/account.js +4 -0
- package/dist/account-manager/helpers/account.js.map +1 -1
- package/dist/account-manager/oauth-store.d.ts +5 -1
- package/dist/account-manager/oauth-store.d.ts.map +1 -1
- package/dist/account-manager/oauth-store.js +50 -1
- package/dist/account-manager/oauth-store.js.map +1 -1
- package/dist/api/app/bsky/actor/getPreferences.d.ts.map +1 -1
- package/dist/api/app/bsky/actor/getPreferences.js +7 -2
- package/dist/api/app/bsky/actor/getPreferences.js.map +1 -1
- package/dist/api/app/bsky/actor/putPreferences.d.ts.map +1 -1
- package/dist/api/app/bsky/actor/putPreferences.js +7 -2
- package/dist/api/app/bsky/actor/putPreferences.js.map +1 -1
- package/dist/api/com/atproto/admin/updateAccountEmail.js +1 -1
- package/dist/api/com/atproto/admin/updateAccountEmail.js.map +1 -1
- package/dist/api/com/atproto/server/confirmEmail.d.ts.map +1 -1
- package/dist/api/com/atproto/server/confirmEmail.js +20 -27
- package/dist/api/com/atproto/server/confirmEmail.js.map +1 -1
- package/dist/api/com/atproto/server/getServiceAuth.d.ts.map +1 -1
- package/dist/api/com/atproto/server/getServiceAuth.js +4 -0
- package/dist/api/com/atproto/server/getServiceAuth.js.map +1 -1
- package/dist/api/com/atproto/server/requestEmailConfirmation.d.ts +3 -1
- package/dist/api/com/atproto/server/requestEmailConfirmation.d.ts.map +1 -1
- package/dist/api/com/atproto/server/requestEmailConfirmation.js +44 -39
- package/dist/api/com/atproto/server/requestEmailConfirmation.js.map +1 -1
- package/dist/api/com/atproto/server/requestEmailUpdate.d.ts +3 -1
- package/dist/api/com/atproto/server/requestEmailUpdate.d.ts.map +1 -1
- package/dist/api/com/atproto/server/requestEmailUpdate.js +51 -47
- package/dist/api/com/atproto/server/requestEmailUpdate.js.map +1 -1
- package/dist/api/com/atproto/server/updateEmail.d.ts.map +1 -1
- package/dist/api/com/atproto/server/updateEmail.js +32 -46
- package/dist/api/com/atproto/server/updateEmail.js.map +1 -1
- package/dist/config/config.d.ts +5 -2
- package/dist/config/config.d.ts.map +1 -1
- package/dist/config/config.js +50 -46
- package/dist/config/config.js.map +1 -1
- package/dist/config/env.d.ts +1 -0
- package/dist/config/env.d.ts.map +1 -1
- package/dist/config/env.js +1 -0
- package/dist/config/env.js.map +1 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +2 -2
- package/dist/context.js.map +1 -1
- package/dist/lexicons/app/bsky/embed/external.defs.d.ts +5 -0
- package/dist/lexicons/app/bsky/embed/external.defs.d.ts.map +1 -1
- package/dist/lexicons/app/bsky/embed/external.defs.js +4 -0
- package/dist/lexicons/app/bsky/embed/external.defs.js.map +1 -1
- package/dist/lexicons/chat/bsky/actor/getStatus.defs.d.ts +2 -0
- package/dist/lexicons/chat/bsky/actor/getStatus.defs.d.ts.map +1 -1
- package/dist/lexicons/chat/bsky/actor/getStatus.defs.js +1 -0
- package/dist/lexicons/chat/bsky/actor/getStatus.defs.js.map +1 -1
- package/dist/lexicons/chat/bsky/convo/defs.defs.d.ts +4 -0
- package/dist/lexicons/chat/bsky/convo/defs.defs.d.ts.map +1 -1
- package/dist/lexicons/chat/bsky/convo/defs.defs.js +1 -0
- package/dist/lexicons/chat/bsky/convo/defs.defs.js.map +1 -1
- package/dist/lexicons/com/atproto/server/getServiceAuth.defs.d.ts +2 -2
- package/dist/lexicons/com/atproto/server/getServiceAuth.defs.js +1 -1
- package/dist/lexicons/com/atproto/server/getServiceAuth.defs.js.map +1 -1
- package/dist/mailer/index.d.ts +5 -3
- package/dist/mailer/index.d.ts.map +1 -1
- package/dist/mailer/index.js +20 -9
- package/dist/mailer/index.js.map +1 -1
- package/dist/mailer/templates/confirm-email.js +11 -3
- package/dist/mailer/templates/confirm-email.js.map +2 -2
- package/dist/mailer/templates/delete-account.js +2 -2
- package/dist/mailer/templates/delete-account.js.map +2 -2
- package/dist/mailer/templates/plc-operation.js +2 -2
- package/dist/mailer/templates/plc-operation.js.map +2 -2
- package/dist/mailer/templates/reset-password.js +2 -2
- package/dist/mailer/templates/reset-password.js.map +2 -2
- package/dist/mailer/templates/update-email.js +2 -2
- package/dist/mailer/templates/update-email.js.map +2 -2
- package/dist/mailer/templates.d.ts +11 -0
- package/dist/mailer/templates.d.ts.map +1 -1
- package/dist/mailer/templates.js.map +1 -1
- package/dist/pipethrough.d.ts +3 -0
- package/dist/pipethrough.d.ts.map +1 -1
- package/dist/pipethrough.js +25 -9
- package/dist/pipethrough.js.map +1 -1
- package/package.json +12 -11
- package/src/account-manager/account-manager.ts +136 -15
- package/src/account-manager/helpers/account.ts +9 -1
- package/src/account-manager/oauth-store.ts +80 -1
- package/src/api/app/bsky/actor/getPreferences.ts +11 -2
- package/src/api/app/bsky/actor/putPreferences.ts +11 -2
- package/src/api/com/atproto/admin/updateAccountEmail.ts +1 -1
- package/src/api/com/atproto/server/confirmEmail.ts +24 -29
- package/src/api/com/atproto/server/getServiceAuth.ts +7 -0
- package/src/api/com/atproto/server/requestEmailConfirmation.ts +55 -48
- package/src/api/com/atproto/server/requestEmailUpdate.ts +64 -48
- package/src/api/com/atproto/server/updateEmail.ts +32 -62
- package/src/config/config.ts +69 -57
- package/src/config/env.ts +3 -0
- package/src/context.ts +2 -1
- package/src/mailer/index.ts +35 -11
- package/src/mailer/templates/confirm-email.hbs +18 -17
- package/src/mailer/templates/delete-account.hbs +6 -6
- package/src/mailer/templates/plc-operation.hbs +6 -6
- package/src/mailer/templates/reset-password.hbs +7 -7
- package/src/mailer/templates/update-email.hbs +6 -6
- package/src/mailer/templates.ts +12 -0
- package/src/pipethrough.ts +33 -12
- package/tests/account-manager.test.ts +89 -8
- package/tests/app-passwords.test.ts +5 -5
- package/tests/get-service-auth.test.ts +81 -0
- package/tests/proxied/proxy-header.test.ts +1 -0
- package/tests/proxied/proxy-oauth-aud.test.ts +175 -0
package/src/mailer/index.ts
CHANGED
|
@@ -1,22 +1,38 @@
|
|
|
1
1
|
import { SendMailOptions, Transporter } from 'nodemailer'
|
|
2
2
|
import { htmlToText } from 'nodemailer-html-to-text'
|
|
3
|
-
import {
|
|
3
|
+
import { BrandingConfig, EmailConfig } from '../config/index.js'
|
|
4
4
|
import { mailerLogger } from '../logger.js'
|
|
5
5
|
import * as templates from './templates.js'
|
|
6
6
|
|
|
7
7
|
// @TODO Add support for i18n
|
|
8
8
|
|
|
9
|
+
const DEFAULT_LOGO_URL =
|
|
10
|
+
'https://bsky.social/about/images/email/email_logo_default.png'
|
|
11
|
+
const DEFAULT_MARK_URL =
|
|
12
|
+
'https://bsky.social/about/images/email/email_mark_dark.png'
|
|
13
|
+
const DEFAULT_HOME_URL = 'https://bsky.app'
|
|
14
|
+
const DEFAULT_PRIMARY_COLOR = '#067df7'
|
|
15
|
+
|
|
9
16
|
export class ServerMailer {
|
|
17
|
+
private readonly config: templates.Config
|
|
18
|
+
|
|
10
19
|
constructor(
|
|
11
20
|
public readonly transporter: Transporter,
|
|
12
|
-
private readonly
|
|
21
|
+
private readonly email: EmailConfig | null,
|
|
22
|
+
branding: BrandingConfig,
|
|
13
23
|
) {
|
|
14
24
|
transporter.use('compile', htmlToText())
|
|
15
|
-
}
|
|
16
25
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
26
|
+
this.config = {
|
|
27
|
+
serviceName: branding.name ?? 'Bluesky',
|
|
28
|
+
homeUrl:
|
|
29
|
+
branding.links?.find((link) => link.rel === 'canonical')?.href ??
|
|
30
|
+
DEFAULT_HOME_URL,
|
|
31
|
+
logoUrl: branding.logo ?? DEFAULT_LOGO_URL,
|
|
32
|
+
markUrl: branding.logo ?? DEFAULT_MARK_URL,
|
|
33
|
+
primaryColor: branding.colors?.primary ?? DEFAULT_PRIMARY_COLOR,
|
|
34
|
+
showBskyAppEmailConfirmationLink: email?.disableConfirmationLink !== true,
|
|
35
|
+
}
|
|
20
36
|
}
|
|
21
37
|
|
|
22
38
|
async sendResetPassword(
|
|
@@ -39,14 +55,22 @@ export class ServerMailer {
|
|
|
39
55
|
})
|
|
40
56
|
}
|
|
41
57
|
|
|
42
|
-
async sendConfirmEmail(
|
|
58
|
+
async sendConfirmEmail(
|
|
59
|
+
params: { token: string; locale?: string },
|
|
60
|
+
mailOpts: SendMailOptions,
|
|
61
|
+
) {
|
|
62
|
+
// @TODO (later) handle locale in the template
|
|
43
63
|
await this.sendTemplate('confirmEmail', params, {
|
|
44
64
|
subject: 'Email Confirmation',
|
|
45
65
|
...mailOpts,
|
|
46
66
|
})
|
|
47
67
|
}
|
|
48
68
|
|
|
49
|
-
async sendUpdateEmail(
|
|
69
|
+
async sendUpdateEmail(
|
|
70
|
+
params: { token: string; locale?: string },
|
|
71
|
+
mailOpts: SendMailOptions,
|
|
72
|
+
) {
|
|
73
|
+
// @TODO (later) handle locale in the template
|
|
50
74
|
await this.sendTemplate('updateEmail', params, {
|
|
51
75
|
subject: 'Email Update Requested',
|
|
52
76
|
...mailOpts,
|
|
@@ -67,14 +91,14 @@ export class ServerMailer {
|
|
|
67
91
|
) {
|
|
68
92
|
const html = templates[templateName]({
|
|
69
93
|
...params,
|
|
70
|
-
config:
|
|
94
|
+
config: this.config,
|
|
71
95
|
} as any)
|
|
72
96
|
const res = await this.transporter.sendMail({
|
|
73
97
|
...mailOpts,
|
|
74
|
-
from: mailOpts.from ?? this.
|
|
98
|
+
from: mailOpts.from ?? this.email?.fromAddress,
|
|
75
99
|
html,
|
|
76
100
|
})
|
|
77
|
-
if (!this.
|
|
101
|
+
if (!this.email?.smtpUrl) {
|
|
78
102
|
mailerLogger.debug(
|
|
79
103
|
'No SMTP URL has been configured. Intended to send email:\n' +
|
|
80
104
|
JSON.stringify(res, null, 2),
|
|
@@ -4,14 +4,12 @@
|
|
|
4
4
|
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
|
|
5
5
|
<meta name="x-apple-disable-message-reformatting" />
|
|
6
6
|
<title>Confirm your email</title>
|
|
7
|
-
<meta
|
|
8
|
-
name="description"
|
|
9
|
-
content="To confirm your email, enter the code provided in the app."
|
|
10
|
-
/>
|
|
7
|
+
<meta name="description" content="{{token}} is your verification code." />
|
|
11
8
|
</head>
|
|
12
9
|
<div
|
|
13
10
|
style="display:none;overflow:hidden;line-height:1px;opacity:0;max-height:0;max-width:0"
|
|
14
|
-
>
|
|
11
|
+
>{{token}}
|
|
12
|
+
is your verification code.<div
|
|
15
13
|
> </div>
|
|
16
14
|
</div>
|
|
17
15
|
|
|
@@ -42,8 +40,8 @@
|
|
|
42
40
|
<tbody>
|
|
43
41
|
<tr>
|
|
44
42
|
<td><img
|
|
45
|
-
alt="
|
|
46
|
-
src="
|
|
43
|
+
alt="{{config.serviceName}}"
|
|
44
|
+
src="{{config.logoUrl}}"
|
|
47
45
|
style="display:block;outline:none;border:none;text-decoration:none;width:110px;margin:0 auto"
|
|
48
46
|
/></td>
|
|
49
47
|
</tr>
|
|
@@ -67,12 +65,15 @@
|
|
|
67
65
|
<p
|
|
68
66
|
style="font-size:16px;line-height:1.4;margin:0px 0px;letter-spacing:0.25px;color:hsl(211, 24%, 34.2%);font-family:-apple-system, BlinkMacSystemFont, 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;padding-top:12px;padding-bottom:12px;padding-right:32px"
|
|
69
67
|
>To confirm this email for your account, please enter the
|
|
70
|
-
code below in the app
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
68
|
+
code below in the app{{#if
|
|
69
|
+
config.showBskyAppEmailConfirmationLink
|
|
70
|
+
}}
|
|
71
|
+
or<!-- -->
|
|
72
|
+
<a
|
|
73
|
+
href="https://bsky.app/intent/verify-email?code={{token}}"
|
|
74
|
+
style="color:{{config.primaryColor}};text-decoration:none;text-decoration-line:underline;font-size:16px;letter-spacing:0.25px"
|
|
75
|
+
target="_blank"
|
|
76
|
+
>click here</a>{{/if}}.</p><code
|
|
76
77
|
style="display:block;padding:16px;border-radius:8px;border-width:1px;border-style:solid;background-color:hsl(211, 20%, 95.3%);border-color:hsl(211, 20%, 85.89999999999999%);font-size:14px;letter-spacing:0.25px;font-family:monospace;text-transform:uppercase"
|
|
77
78
|
>{{token}}</code>
|
|
78
79
|
<p
|
|
@@ -109,17 +110,17 @@
|
|
|
109
110
|
<p
|
|
110
111
|
style="font-size:14px;line-height:1.4;margin:0px 0px;color:hsl(211, 20%, 53%);font-family:-apple-system, BlinkMacSystemFont, 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;letter-spacing:0.25px"
|
|
111
112
|
><a
|
|
112
|
-
href="
|
|
113
|
+
href="{{config.homeUrl}}"
|
|
113
114
|
style="color:hsl(211, 20%, 53%);text-decoration:none;text-decoration-line:underline;font-family:-apple-system, BlinkMacSystemFont, 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;margin:0px 0px;line-height:1.0;font-size:14px;letter-spacing:0.25px"
|
|
114
115
|
target="_blank"
|
|
115
|
-
>
|
|
116
|
+
>{{config.serviceName}}</a></p>
|
|
116
117
|
</td>
|
|
117
118
|
<td
|
|
118
119
|
data-id="__react-email-column"
|
|
119
120
|
style="width:24px"
|
|
120
121
|
><img
|
|
121
|
-
alt="
|
|
122
|
-
src="
|
|
122
|
+
alt="{{config.serviceName}}"
|
|
123
|
+
src="{{config.markUrl}}"
|
|
123
124
|
style="display:block;outline:none;border:none;text-decoration:none;width:24px"
|
|
124
125
|
/></td>
|
|
125
126
|
</tr>
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
<tbody>
|
|
44
44
|
<tr>
|
|
45
45
|
<td><img
|
|
46
|
-
alt="
|
|
47
|
-
src="
|
|
46
|
+
alt="{{config.serviceName}}"
|
|
47
|
+
src="{{config.logoUrl}}"
|
|
48
48
|
style="display:block;outline:none;border:none;text-decoration:none;width:110px;margin:0 auto"
|
|
49
49
|
/></td>
|
|
50
50
|
</tr>
|
|
@@ -108,17 +108,17 @@
|
|
|
108
108
|
<p
|
|
109
109
|
style="font-size:14px;line-height:1.4;margin:0px 0px;color:hsl(211, 20%, 53%);font-family:-apple-system, BlinkMacSystemFont, 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;letter-spacing:0.25px"
|
|
110
110
|
><a
|
|
111
|
-
href="
|
|
111
|
+
href="{{config.homeUrl}}"
|
|
112
112
|
style="color:hsl(211, 20%, 53%);text-decoration:none;text-decoration-line:underline;font-family:-apple-system, BlinkMacSystemFont, 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;margin:0px 0px;line-height:1.0;font-size:14px;letter-spacing:0.25px"
|
|
113
113
|
target="_blank"
|
|
114
|
-
>
|
|
114
|
+
>{{config.serviceName}}</a></p>
|
|
115
115
|
</td>
|
|
116
116
|
<td
|
|
117
117
|
data-id="__react-email-column"
|
|
118
118
|
style="width:24px"
|
|
119
119
|
><img
|
|
120
|
-
alt="
|
|
121
|
-
src="
|
|
120
|
+
alt="{{config.serviceName}}"
|
|
121
|
+
src="{{config.markUrl}}"
|
|
122
122
|
style="display:block;outline:none;border:none;text-decoration:none;width:24px"
|
|
123
123
|
/></td>
|
|
124
124
|
</tr>
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
<tbody>
|
|
43
43
|
<tr>
|
|
44
44
|
<td><img
|
|
45
|
-
alt="
|
|
46
|
-
src="
|
|
45
|
+
alt="{{config.serviceName}}"
|
|
46
|
+
src="{{config.logoUrl}}"
|
|
47
47
|
style="display:block;outline:none;border:none;text-decoration:none;width:110px;margin:0 auto"
|
|
48
48
|
/></td>
|
|
49
49
|
</tr>
|
|
@@ -105,17 +105,17 @@
|
|
|
105
105
|
<p
|
|
106
106
|
style="font-size:14px;line-height:1.4;margin:0px 0px;color:hsl(211, 20%, 53%);font-family:-apple-system, BlinkMacSystemFont, 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;letter-spacing:0.25px"
|
|
107
107
|
><a
|
|
108
|
-
href="
|
|
108
|
+
href="{{config.homeUrl}}"
|
|
109
109
|
style="color:hsl(211, 20%, 53%);text-decoration:none;text-decoration-line:underline;font-family:-apple-system, BlinkMacSystemFont, 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;margin:0px 0px;line-height:1.0;font-size:14px;letter-spacing:0.25px"
|
|
110
110
|
target="_blank"
|
|
111
|
-
>
|
|
111
|
+
>{{config.serviceName}}</a></p>
|
|
112
112
|
</td>
|
|
113
113
|
<td
|
|
114
114
|
data-id="__react-email-column"
|
|
115
115
|
style="width:24px"
|
|
116
116
|
><img
|
|
117
|
-
alt="
|
|
118
|
-
src="
|
|
117
|
+
alt="{{config.serviceName}}"
|
|
118
|
+
src="{{config.markUrl}}"
|
|
119
119
|
style="display:block;outline:none;border:none;text-decoration:none;width:24px"
|
|
120
120
|
/></td>
|
|
121
121
|
</tr>
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
<tbody>
|
|
43
43
|
<tr>
|
|
44
44
|
<td><img
|
|
45
|
-
alt="
|
|
46
|
-
src="
|
|
45
|
+
alt="{{config.serviceName}}"
|
|
46
|
+
src="{{config.logoUrl}}"
|
|
47
47
|
style="display:block;outline:none;border:none;text-decoration:none;width:110px;margin:0 auto"
|
|
48
48
|
/></td>
|
|
49
49
|
</tr>
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
style="font-size:16px;line-height:1.4;margin:0px 0px;letter-spacing:0.25px;color:hsl(211, 24%, 34.2%);font-family:-apple-system, BlinkMacSystemFont, 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;padding-top:12px;padding-bottom:12px"
|
|
69
69
|
>We received a request to reset the password for the account<!-- -->
|
|
70
70
|
<span
|
|
71
|
-
style="color:
|
|
71
|
+
style="color:{{config.primaryColor}}"
|
|
72
72
|
>@<!-- -->{{handle}}<!-- -->.</span></p><code
|
|
73
73
|
style="display:block;padding:16px;border-radius:8px;border-width:1px;border-style:solid;background-color:hsl(211, 20%, 95.3%);border-color:hsl(211, 20%, 85.89999999999999%);font-size:14px;letter-spacing:0.25px;font-family:monospace;text-transform:uppercase"
|
|
74
74
|
>{{token}}</code>
|
|
@@ -106,17 +106,17 @@
|
|
|
106
106
|
<p
|
|
107
107
|
style="font-size:14px;line-height:1.4;margin:0px 0px;color:hsl(211, 20%, 53%);font-family:-apple-system, BlinkMacSystemFont, 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;letter-spacing:0.25px"
|
|
108
108
|
><a
|
|
109
|
-
href="
|
|
109
|
+
href="{{config.homeUrl}}"
|
|
110
110
|
style="color:hsl(211, 20%, 53%);text-decoration:none;text-decoration-line:underline;font-family:-apple-system, BlinkMacSystemFont, 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;margin:0px 0px;line-height:1.0;font-size:14px;letter-spacing:0.25px"
|
|
111
111
|
target="_blank"
|
|
112
|
-
>
|
|
112
|
+
>{{config.serviceName}}</a></p>
|
|
113
113
|
</td>
|
|
114
114
|
<td
|
|
115
115
|
data-id="__react-email-column"
|
|
116
116
|
style="width:24px"
|
|
117
117
|
><img
|
|
118
|
-
alt="
|
|
119
|
-
src="
|
|
118
|
+
alt="{{config.serviceName}}"
|
|
119
|
+
src="{{config.markUrl}}"
|
|
120
120
|
style="display:block;outline:none;border:none;text-decoration:none;width:24px"
|
|
121
121
|
/></td>
|
|
122
122
|
</tr>
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
<tbody>
|
|
44
44
|
<tr>
|
|
45
45
|
<td><img
|
|
46
|
-
alt="
|
|
47
|
-
src="
|
|
46
|
+
alt="{{config.serviceName}}"
|
|
47
|
+
src="{{config.logoUrl}}"
|
|
48
48
|
style="display:block;outline:none;border:none;text-decoration:none;width:110px;margin:0 auto"
|
|
49
49
|
/></td>
|
|
50
50
|
</tr>
|
|
@@ -105,17 +105,17 @@
|
|
|
105
105
|
<p
|
|
106
106
|
style="font-size:14px;line-height:1.4;margin:0px 0px;color:hsl(211, 20%, 53%);font-family:-apple-system, BlinkMacSystemFont, 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;letter-spacing:0.25px"
|
|
107
107
|
><a
|
|
108
|
-
href="
|
|
108
|
+
href="{{config.homeUrl}}"
|
|
109
109
|
style="color:hsl(211, 20%, 53%);text-decoration:none;text-decoration-line:underline;font-family:-apple-system, BlinkMacSystemFont, 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;margin:0px 0px;line-height:1.0;font-size:14px;letter-spacing:0.25px"
|
|
110
110
|
target="_blank"
|
|
111
|
-
>
|
|
111
|
+
>{{config.serviceName}}</a></p>
|
|
112
112
|
</td>
|
|
113
113
|
<td
|
|
114
114
|
data-id="__react-email-column"
|
|
115
115
|
style="width:24px"
|
|
116
116
|
><img
|
|
117
|
-
alt="
|
|
118
|
-
src="
|
|
117
|
+
alt="{{config.serviceName}}"
|
|
118
|
+
src="{{config.markUrl}}"
|
|
119
119
|
style="display:block;outline:none;border:none;text-decoration:none;width:24px"
|
|
120
120
|
/></td>
|
|
121
121
|
</tr>
|
package/src/mailer/templates.ts
CHANGED
|
@@ -3,3 +3,15 @@ export { default as deleteAccount } from './templates/delete-account.js'
|
|
|
3
3
|
export { default as confirmEmail } from './templates/confirm-email.js'
|
|
4
4
|
export { default as updateEmail } from './templates/update-email.js'
|
|
5
5
|
export { default as plcOperation } from './templates/plc-operation.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Common config variable that will be injected into all templates.
|
|
9
|
+
*/
|
|
10
|
+
export type Config = {
|
|
11
|
+
serviceName: string
|
|
12
|
+
homeUrl: string
|
|
13
|
+
logoUrl: string
|
|
14
|
+
markUrl: string
|
|
15
|
+
primaryColor: string
|
|
16
|
+
showBskyAppEmailConfirmationLink: boolean
|
|
17
|
+
}
|
package/src/pipethrough.ts
CHANGED
|
@@ -56,9 +56,23 @@ export const proxyHandler = (ctx: AppContext): CatchallHandler => {
|
|
|
56
56
|
throw new InvalidRequestError('Bad token method', 'InvalidToken')
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
const {
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
const {
|
|
60
|
+
url: origin,
|
|
61
|
+
did,
|
|
62
|
+
serviceId,
|
|
63
|
+
} = await parseProxyInfo(ctx, req, lxm)
|
|
64
|
+
// Phase 1 of service auth updates: the scope check sees the combined
|
|
65
|
+
// did#serviceId form (so OAuth callers' rpc:?aud=did#service scopes
|
|
66
|
+
// match), while the outbound service-auth JWT keeps bare-DID aud
|
|
67
|
+
// regardless of session type.
|
|
68
|
+
const scopeAud = `${did}#${serviceId}`
|
|
69
|
+
const tokenAud = did
|
|
70
|
+
|
|
71
|
+
const authResult = await performAuth({
|
|
72
|
+
req,
|
|
73
|
+
res,
|
|
74
|
+
params: { lxm, aud: scopeAud },
|
|
75
|
+
})
|
|
62
76
|
|
|
63
77
|
const { credentials } = excludeErrorResult(authResult)
|
|
64
78
|
|
|
@@ -80,7 +94,7 @@ export const proxyHandler = (ctx: AppContext): CatchallHandler => {
|
|
|
80
94
|
'content-encoding': body && req.headers['content-encoding'],
|
|
81
95
|
'content-length': body && req.headers['content-length'],
|
|
82
96
|
|
|
83
|
-
authorization: `Bearer ${await ctx.serviceAuthJwt(credentials.did,
|
|
97
|
+
authorization: `Bearer ${await ctx.serviceAuthJwt(credentials.did, tokenAud, lxm)}`,
|
|
84
98
|
}
|
|
85
99
|
|
|
86
100
|
const dispatchOptions: Dispatcher.RequestOptions = {
|
|
@@ -216,18 +230,25 @@ export function computeProxyTo(
|
|
|
216
230
|
throw new InvalidRequestError(`No service configured for ${lxm}`)
|
|
217
231
|
}
|
|
218
232
|
|
|
233
|
+
// Bare-DID portion of `proxyTo`, suitable as a service-auth JWT audience
|
|
234
|
+
// (Phase 1 of service auth updates).
|
|
235
|
+
export function bareDidFromProxyTo(proxyTo: string): string {
|
|
236
|
+
const hashIndex = proxyTo.indexOf('#')
|
|
237
|
+
return hashIndex === -1 ? proxyTo : proxyTo.slice(0, hashIndex)
|
|
238
|
+
}
|
|
239
|
+
|
|
219
240
|
export async function parseProxyInfo(
|
|
220
241
|
ctx: AppContext,
|
|
221
242
|
req: Request,
|
|
222
243
|
lxm: string,
|
|
223
|
-
): Promise<{ url: string; did: string }> {
|
|
244
|
+
): Promise<{ url: string; did: string; serviceId: string }> {
|
|
224
245
|
// /!\ Hot path
|
|
225
246
|
|
|
226
247
|
const proxyToHeader = req.header('atproto-proxy')
|
|
227
248
|
if (proxyToHeader) return parseProxyHeader(ctx, proxyToHeader)
|
|
228
249
|
|
|
229
|
-
const { serviceInfo } = defaultService(ctx, lxm)
|
|
230
|
-
if (serviceInfo) return serviceInfo
|
|
250
|
+
const { serviceId, serviceInfo } = defaultService(ctx, lxm)
|
|
251
|
+
if (serviceInfo) return { ...serviceInfo, serviceId }
|
|
231
252
|
|
|
232
253
|
throw new InvalidRequestError(`No service configured for ${lxm}`)
|
|
233
254
|
}
|
|
@@ -236,7 +257,7 @@ export const parseProxyHeader = async (
|
|
|
236
257
|
// Using subset of AppContext for testing purposes
|
|
237
258
|
ctx: Pick<AppContext, 'cfg' | 'idResolver'>,
|
|
238
259
|
proxyTo: string,
|
|
239
|
-
): Promise<{ did: string; url: string }> => {
|
|
260
|
+
): Promise<{ did: string; url: string; serviceId: string }> => {
|
|
240
261
|
// /!\ Hot path
|
|
241
262
|
|
|
242
263
|
const hashIndex = proxyTo.indexOf('#')
|
|
@@ -260,13 +281,14 @@ export const parseProxyHeader = async (
|
|
|
260
281
|
}
|
|
261
282
|
|
|
262
283
|
const did = proxyTo.slice(0, hashIndex)
|
|
284
|
+
const serviceId = proxyTo.slice(hashIndex + 1)
|
|
263
285
|
|
|
264
286
|
// Special case a configured appview, while still proxying correctly any other appview
|
|
265
287
|
if (
|
|
266
288
|
ctx.cfg.bskyAppView &&
|
|
267
289
|
proxyTo === `${ctx.cfg.bskyAppView.did}#bsky_appview`
|
|
268
290
|
) {
|
|
269
|
-
return { did, url: ctx.cfg.bskyAppView.url }
|
|
291
|
+
return { did, url: ctx.cfg.bskyAppView.url, serviceId }
|
|
270
292
|
}
|
|
271
293
|
|
|
272
294
|
const didDoc = await ctx.idResolver.did.resolve(did)
|
|
@@ -274,13 +296,12 @@ export const parseProxyHeader = async (
|
|
|
274
296
|
throw new InvalidRequestError('could not resolve proxy did')
|
|
275
297
|
}
|
|
276
298
|
|
|
277
|
-
const
|
|
278
|
-
const url = getServiceEndpoint(didDoc, { id: serviceId })
|
|
299
|
+
const url = getServiceEndpoint(didDoc, { id: `#${serviceId}` })
|
|
279
300
|
if (!url) {
|
|
280
301
|
throw new InvalidRequestError('could not resolve proxy did service url')
|
|
281
302
|
}
|
|
282
303
|
|
|
283
|
-
return { did, url }
|
|
304
|
+
return { did, url, serviceId }
|
|
284
305
|
}
|
|
285
306
|
|
|
286
307
|
/**
|
|
@@ -108,12 +108,6 @@ describe('account manager', () => {
|
|
|
108
108
|
})
|
|
109
109
|
|
|
110
110
|
it('allows changing the password', async () => {
|
|
111
|
-
const sendResetPasswordMock = jest
|
|
112
|
-
.spyOn(network.pds.ctx.mailer, 'sendResetPassword')
|
|
113
|
-
.mockImplementation(async () => {
|
|
114
|
-
// noop
|
|
115
|
-
})
|
|
116
|
-
|
|
117
111
|
await using page = await PageHelper.from(browser, { languages })
|
|
118
112
|
|
|
119
113
|
await page.goto(new URL('/account', network.pds.url))
|
|
@@ -122,7 +116,11 @@ describe('account manager', () => {
|
|
|
122
116
|
|
|
123
117
|
await page.clickOnText('Mot de passe', 'a')
|
|
124
118
|
|
|
125
|
-
|
|
119
|
+
using sendResetPasswordMock = jest
|
|
120
|
+
.spyOn(network.pds.ctx.mailer, 'sendResetPassword')
|
|
121
|
+
.mockImplementation(async () => {
|
|
122
|
+
// noop
|
|
123
|
+
})
|
|
126
124
|
|
|
127
125
|
await page.clickOnText('Envoyer le code')
|
|
128
126
|
|
|
@@ -145,7 +143,90 @@ describe('account manager', () => {
|
|
|
145
143
|
'Réinitialisation du mot de passe réussie',
|
|
146
144
|
'div',
|
|
147
145
|
)
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('allows validating the email address', async () => {
|
|
149
|
+
await using page = await PageHelper.from(browser, { languages })
|
|
150
|
+
|
|
151
|
+
await page.goto(new URL('/account', network.pds.url))
|
|
152
|
+
|
|
153
|
+
await page.clickOnText('Vérifier maintenant', 'button')
|
|
154
|
+
|
|
155
|
+
using sendConfirmEmailMock = jest
|
|
156
|
+
.spyOn(network.pds.ctx.mailer, 'sendConfirmEmail')
|
|
157
|
+
.mockImplementation(async () => {
|
|
158
|
+
// noop
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
await page.clickOnText('Envoyer le code', 'button')
|
|
162
|
+
|
|
163
|
+
await page.waitForNetworkIdle()
|
|
164
|
+
|
|
165
|
+
expect(sendConfirmEmailMock).toHaveBeenCalledTimes(1)
|
|
166
|
+
|
|
167
|
+
const [params] = sendConfirmEmailMock.mock.lastCall!
|
|
168
|
+
expect(params).toEqual({
|
|
169
|
+
locale: 'fr',
|
|
170
|
+
token: expect.any(String),
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
await page.typeInInput('code', params.token)
|
|
174
|
+
await page.clickOnText('Soumettre')
|
|
175
|
+
|
|
176
|
+
await page.ensureTextVisibility('Adresse email vérifiée', 'div')
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it('allows changing the email address', async () => {
|
|
180
|
+
await using page = await PageHelper.from(browser, { languages })
|
|
181
|
+
|
|
182
|
+
await page.goto(new URL('/account', network.pds.url))
|
|
183
|
+
|
|
184
|
+
await page.clickOnText('Email', 'a')
|
|
185
|
+
|
|
186
|
+
using sendUpdateEmailMock = jest
|
|
187
|
+
.spyOn(network.pds.ctx.mailer, 'sendUpdateEmail')
|
|
188
|
+
.mockImplementation(async () => {
|
|
189
|
+
// noop
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
using sendConfirmEmailMock = jest
|
|
193
|
+
.spyOn(network.pds.ctx.mailer, 'sendConfirmEmail')
|
|
194
|
+
.mockImplementation(async () => {
|
|
195
|
+
// noop
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
await page.clickOnText('Envoyer le code', 'button')
|
|
199
|
+
|
|
200
|
+
await page.waitForNetworkIdle()
|
|
201
|
+
|
|
202
|
+
expect(sendUpdateEmailMock).toHaveBeenCalledTimes(1)
|
|
203
|
+
|
|
204
|
+
const [updateParams] = sendUpdateEmailMock.mock.lastCall!
|
|
205
|
+
expect(updateParams).toEqual({
|
|
206
|
+
locale: 'fr',
|
|
207
|
+
token: expect.any(String),
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
await page.typeInInput('code', updateParams.token)
|
|
211
|
+
await page.typeInInput('email', 'bob-new-email@example.com')
|
|
212
|
+
await page.clickOnText('Soumettre')
|
|
213
|
+
|
|
214
|
+
await page.ensureTextVisibility(
|
|
215
|
+
"Modification de l'adresse email réussie",
|
|
216
|
+
'div',
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
expect(sendConfirmEmailMock).toHaveBeenCalledTimes(1)
|
|
220
|
+
|
|
221
|
+
const [confirmParams] = sendConfirmEmailMock.mock.lastCall!
|
|
222
|
+
expect(confirmParams).toEqual({
|
|
223
|
+
locale: 'fr',
|
|
224
|
+
token: expect.any(String),
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
await page.typeInInput('code', confirmParams.token)
|
|
228
|
+
await page.clickOnText('Soumettre')
|
|
148
229
|
|
|
149
|
-
|
|
230
|
+
await page.ensureTextVisibility('Adresse email vérifiée', 'div')
|
|
150
231
|
})
|
|
151
232
|
})
|
|
@@ -115,7 +115,7 @@ describe('app_passwords', () => {
|
|
|
115
115
|
it('restricts service auth token methods for non-privileged access tokens', async () => {
|
|
116
116
|
const attemptCaseSensitive = appAgent.api.com.atproto.server.getServiceAuth(
|
|
117
117
|
{
|
|
118
|
-
aud:
|
|
118
|
+
aud: network.pds.ctx.cfg.service.did,
|
|
119
119
|
lxm: 'com.atproto.server.createAccount',
|
|
120
120
|
},
|
|
121
121
|
)
|
|
@@ -124,7 +124,7 @@ describe('app_passwords', () => {
|
|
|
124
124
|
)
|
|
125
125
|
const attemptCaseInsensitive =
|
|
126
126
|
appAgent.api.com.atproto.server.getServiceAuth({
|
|
127
|
-
aud:
|
|
127
|
+
aud: network.pds.ctx.cfg.service.did,
|
|
128
128
|
lxm: 'com.atproto.server.createaccount',
|
|
129
129
|
})
|
|
130
130
|
await expect(attemptCaseInsensitive).rejects.toThrow(
|
|
@@ -134,7 +134,7 @@ describe('app_passwords', () => {
|
|
|
134
134
|
|
|
135
135
|
it('allows privileged service auth token scopes for privileged access tokens', async () => {
|
|
136
136
|
await priviAgent.api.com.atproto.server.getServiceAuth({
|
|
137
|
-
aud:
|
|
137
|
+
aud: network.pds.ctx.cfg.service.did,
|
|
138
138
|
lxm: 'com.atproto.server.createAccount',
|
|
139
139
|
})
|
|
140
140
|
})
|
|
@@ -165,7 +165,7 @@ describe('app_passwords', () => {
|
|
|
165
165
|
|
|
166
166
|
// allows privileged app passwords or higher
|
|
167
167
|
const priviAttempt = appAgent.api.com.atproto.server.getServiceAuth({
|
|
168
|
-
aud:
|
|
168
|
+
aud: network.pds.ctx.cfg.service.did,
|
|
169
169
|
lxm: 'com.atproto.server.createAccount',
|
|
170
170
|
})
|
|
171
171
|
await expect(priviAttempt).rejects.toThrow(
|
|
@@ -211,7 +211,7 @@ describe('app_passwords', () => {
|
|
|
211
211
|
|
|
212
212
|
// allows privileged app passwords or higher
|
|
213
213
|
await priviAgent.api.com.atproto.server.getServiceAuth({
|
|
214
|
-
aud:
|
|
214
|
+
aud: network.pds.ctx.cfg.service.did,
|
|
215
215
|
})
|
|
216
216
|
|
|
217
217
|
// allows only full access auth
|