@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
|
@@ -1,44 +1,39 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Server } from '@atproto/xrpc-server'
|
|
2
2
|
import { AppContext } from '../../../../context.js'
|
|
3
3
|
import { com } from '../../../../lexicons/index.js'
|
|
4
|
+
import { requestEmailConfirmationAuth } from './requestEmailConfirmation.js'
|
|
4
5
|
|
|
5
6
|
export default function (server: Server, ctx: AppContext) {
|
|
6
|
-
|
|
7
|
-
auth: ctx.authVerifier.authorization({
|
|
8
|
-
checkTakedown: true,
|
|
9
|
-
authorize: (permissions) => {
|
|
10
|
-
permissions.assertAccount({ attr: 'email', action: 'manage' })
|
|
11
|
-
},
|
|
12
|
-
}),
|
|
13
|
-
handler: async ({ auth, input: { body }, req }) => {
|
|
14
|
-
const { did } = auth.credentials
|
|
7
|
+
const { entrywayClient } = ctx
|
|
15
8
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
})
|
|
19
|
-
if (!user) {
|
|
20
|
-
throw new InvalidRequestError('user not found', 'AccountNotFound')
|
|
21
|
-
}
|
|
9
|
+
// @NOTE Ensure that both endpoints use the same authentication logic
|
|
10
|
+
const auth = requestEmailConfirmationAuth(ctx)
|
|
22
11
|
|
|
23
|
-
|
|
12
|
+
if (entrywayClient) {
|
|
13
|
+
server.add(com.atproto.server.confirmEmail, {
|
|
14
|
+
auth,
|
|
15
|
+
handler: async ({ auth, input: { body }, req }) => {
|
|
24
16
|
const { headers } = await ctx.entrywayAuthHeaders(
|
|
25
17
|
req,
|
|
26
18
|
auth.credentials.did,
|
|
27
19
|
com.atproto.server.confirmEmail.$lxm,
|
|
28
20
|
)
|
|
29
|
-
await
|
|
21
|
+
await entrywayClient.xrpc(com.atproto.server.confirmEmail, {
|
|
30
22
|
headers,
|
|
31
23
|
body,
|
|
32
24
|
})
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
25
|
+
},
|
|
26
|
+
})
|
|
27
|
+
} else {
|
|
28
|
+
server.add(com.atproto.server.confirmEmail, {
|
|
29
|
+
auth,
|
|
30
|
+
handler: async ({ auth, input: { body } }) => {
|
|
31
|
+
await ctx.accountManager.confirmEmail(
|
|
32
|
+
auth.credentials.did,
|
|
33
|
+
body.email,
|
|
34
|
+
body.token,
|
|
35
|
+
)
|
|
36
|
+
},
|
|
37
|
+
})
|
|
38
|
+
}
|
|
44
39
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { HOUR, MINUTE } from '@atproto/common'
|
|
2
|
+
import { isAtprotoDid, isAtprotoDidRefAbsolute } from '@atproto/did'
|
|
2
3
|
import { l } from '@atproto/lex'
|
|
3
4
|
import {
|
|
4
5
|
InvalidRequestError,
|
|
@@ -34,6 +35,12 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
34
35
|
// @NOTE "exp" is expressed in seconds since epoch, not milliseconds
|
|
35
36
|
const { aud, exp, lxm = null } = params
|
|
36
37
|
|
|
38
|
+
if (!isAtprotoDid(aud) && !isAtprotoDidRefAbsolute(aud)) {
|
|
39
|
+
throw new InvalidRequestError(
|
|
40
|
+
'aud must be a valid atproto DID or did#serviceId reference',
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
37
44
|
// Takendown accounts should not be able to generate service auth tokens except for methods necessary for account migration
|
|
38
45
|
if (auth.credentials.type === 'access') {
|
|
39
46
|
// @NOTE We should probably use "ForbiddenError" here. Using
|
|
@@ -1,61 +1,68 @@
|
|
|
1
1
|
import { DAY, HOUR } from '@atproto/common'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
MethodAuthVerifier,
|
|
4
|
+
MethodRateLimit,
|
|
5
|
+
Server,
|
|
6
|
+
} from '@atproto/xrpc-server'
|
|
7
|
+
import { AccessOutput, OAuthOutput } from '../../../../auth-output.js'
|
|
3
8
|
import { AppContext } from '../../../../context.js'
|
|
4
9
|
import { com } from '../../../../lexicons/index.js'
|
|
5
10
|
|
|
11
|
+
// Exposed as a utility to ensure auth in confirmEmail and requestEmailConfirmation
|
|
12
|
+
export function requestEmailConfirmationAuth(
|
|
13
|
+
ctx: AppContext,
|
|
14
|
+
): MethodAuthVerifier<AccessOutput | OAuthOutput> {
|
|
15
|
+
return ctx.authVerifier.authorization({
|
|
16
|
+
checkTakedown: true,
|
|
17
|
+
authorize: (permissions) => {
|
|
18
|
+
permissions.assertAccount({ attr: 'email', action: 'manage' })
|
|
19
|
+
},
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const rateLimit: MethodRateLimit<AccessOutput | OAuthOutput> = [
|
|
24
|
+
{
|
|
25
|
+
durationMs: DAY,
|
|
26
|
+
points: 15,
|
|
27
|
+
calcKey: ({ auth }) => auth.credentials.did,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
durationMs: HOUR,
|
|
31
|
+
points: 5,
|
|
32
|
+
calcKey: ({ auth }) => auth.credentials.did,
|
|
33
|
+
},
|
|
34
|
+
]
|
|
35
|
+
|
|
6
36
|
export default function (server: Server, ctx: AppContext) {
|
|
7
|
-
|
|
8
|
-
rateLimit: [
|
|
9
|
-
{
|
|
10
|
-
durationMs: DAY,
|
|
11
|
-
points: 15,
|
|
12
|
-
calcKey: ({ auth }) => auth.credentials.did,
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
durationMs: HOUR,
|
|
16
|
-
points: 5,
|
|
17
|
-
calcKey: ({ auth }) => auth.credentials.did,
|
|
18
|
-
},
|
|
19
|
-
],
|
|
20
|
-
auth: ctx.authVerifier.authorization({
|
|
21
|
-
checkTakedown: true,
|
|
22
|
-
authorize: (permissions) => {
|
|
23
|
-
permissions.assertAccount({ attr: 'email', action: 'manage' })
|
|
24
|
-
},
|
|
25
|
-
}),
|
|
26
|
-
handler: async ({ auth, req }) => {
|
|
27
|
-
const did = auth.credentials.did
|
|
28
|
-
const account = await ctx.accountManager.getAccount(did, {
|
|
29
|
-
includeDeactivated: true,
|
|
30
|
-
includeTakenDown: true,
|
|
31
|
-
})
|
|
32
|
-
if (!account) {
|
|
33
|
-
throw new InvalidRequestError('account not found')
|
|
34
|
-
}
|
|
37
|
+
const { entrywayClient } = ctx
|
|
35
38
|
|
|
36
|
-
|
|
39
|
+
const auth = requestEmailConfirmationAuth(ctx)
|
|
40
|
+
|
|
41
|
+
if (entrywayClient) {
|
|
42
|
+
server.add(com.atproto.server.requestEmailConfirmation, {
|
|
43
|
+
auth,
|
|
44
|
+
rateLimit,
|
|
45
|
+
handler: async ({ auth, req }) => {
|
|
37
46
|
const { headers } = await ctx.entrywayAuthHeaders(
|
|
38
47
|
req,
|
|
39
48
|
auth.credentials.did,
|
|
40
49
|
com.atproto.server.requestEmailConfirmation.$lxm,
|
|
41
50
|
)
|
|
42
51
|
|
|
43
|
-
await
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
did,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
},
|
|
60
|
-
})
|
|
52
|
+
await entrywayClient.xrpc(com.atproto.server.requestEmailConfirmation, {
|
|
53
|
+
headers,
|
|
54
|
+
})
|
|
55
|
+
},
|
|
56
|
+
})
|
|
57
|
+
} else {
|
|
58
|
+
server.add(com.atproto.server.requestEmailConfirmation, {
|
|
59
|
+
auth,
|
|
60
|
+
rateLimit,
|
|
61
|
+
handler: async ({ auth }) => {
|
|
62
|
+
const did = auth.credentials.did
|
|
63
|
+
const locale = undefined // @TODO get the locale somehow
|
|
64
|
+
await ctx.accountManager.requestEmailConfirmation(did, { locale })
|
|
65
|
+
},
|
|
66
|
+
})
|
|
67
|
+
}
|
|
61
68
|
}
|
|
@@ -1,70 +1,86 @@
|
|
|
1
1
|
import { DAY, HOUR } from '@atproto/common'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
ForbiddenError,
|
|
4
|
+
MethodAuthVerifier,
|
|
5
|
+
MethodRateLimit,
|
|
6
|
+
Server,
|
|
7
|
+
} from '@atproto/xrpc-server'
|
|
8
|
+
import { AccessOutput, OAuthOutput } from '../../../../auth-output.js'
|
|
9
|
+
import { ACCESS_FULL } from '../../../../auth-scope.js'
|
|
3
10
|
import { AppContext } from '../../../../context.js'
|
|
4
11
|
import { com } from '../../../../lexicons/index.js'
|
|
5
12
|
|
|
13
|
+
// Exposed as a utility to ensure auth in updateEmail and requestEmailUpdate
|
|
14
|
+
// stay consistent.
|
|
15
|
+
export function requestEmailUpdateAuth(
|
|
16
|
+
ctx: AppContext,
|
|
17
|
+
): MethodAuthVerifier<AccessOutput | OAuthOutput> {
|
|
18
|
+
return ctx.authVerifier.authorization({
|
|
19
|
+
checkTakedown: true,
|
|
20
|
+
scopes: ACCESS_FULL,
|
|
21
|
+
authorize: () => {
|
|
22
|
+
throw new ForbiddenError(
|
|
23
|
+
'Use the account manager interface to update email address associated with an account',
|
|
24
|
+
)
|
|
25
|
+
},
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const rateLimit: MethodRateLimit<AccessOutput | OAuthOutput> = [
|
|
30
|
+
{
|
|
31
|
+
durationMs: DAY,
|
|
32
|
+
points: 15,
|
|
33
|
+
calcKey: ({ auth }) => auth.credentials.did,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
durationMs: HOUR,
|
|
37
|
+
points: 5,
|
|
38
|
+
calcKey: ({ auth }) => auth.credentials.did,
|
|
39
|
+
},
|
|
40
|
+
]
|
|
41
|
+
|
|
6
42
|
export default function (server: Server, ctx: AppContext) {
|
|
7
43
|
const { entrywayClient } = ctx
|
|
8
44
|
|
|
9
|
-
|
|
10
|
-
rateLimit: [
|
|
11
|
-
{
|
|
12
|
-
durationMs: DAY,
|
|
13
|
-
points: 15,
|
|
14
|
-
calcKey: ({ auth }) => auth.credentials.did,
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
durationMs: HOUR,
|
|
18
|
-
points: 5,
|
|
19
|
-
calcKey: ({ auth }) => auth.credentials.did,
|
|
20
|
-
},
|
|
21
|
-
],
|
|
22
|
-
auth: ctx.authVerifier.authorization({
|
|
23
|
-
checkTakedown: true,
|
|
24
|
-
authorize: (permissions) => {
|
|
25
|
-
permissions.assertAccount({ attr: 'email', action: 'manage' })
|
|
26
|
-
},
|
|
27
|
-
}),
|
|
28
|
-
handler: async ({ auth, req }) => {
|
|
29
|
-
const did = auth.credentials.did
|
|
30
|
-
const account = await ctx.accountManager.getAccount(did, {
|
|
31
|
-
includeDeactivated: true,
|
|
32
|
-
includeTakenDown: true,
|
|
33
|
-
})
|
|
34
|
-
if (!account) {
|
|
35
|
-
throw new InvalidRequestError('account not found')
|
|
36
|
-
}
|
|
45
|
+
const auth = requestEmailUpdateAuth(ctx)
|
|
37
46
|
|
|
38
|
-
|
|
47
|
+
if (entrywayClient) {
|
|
48
|
+
server.add(com.atproto.server.requestEmailUpdate, {
|
|
49
|
+
rateLimit,
|
|
50
|
+
auth,
|
|
51
|
+
handler: async ({ auth, req }) => {
|
|
39
52
|
const { headers } = await ctx.entrywayAuthHeaders(
|
|
40
53
|
req,
|
|
41
54
|
auth.credentials.did,
|
|
42
55
|
com.atproto.server.requestEmailUpdate.$lxm,
|
|
43
56
|
)
|
|
57
|
+
|
|
44
58
|
return entrywayClient.xrpc(com.atproto.server.requestEmailUpdate, {
|
|
45
59
|
headers,
|
|
46
60
|
})
|
|
47
|
-
}
|
|
61
|
+
},
|
|
62
|
+
})
|
|
63
|
+
} else {
|
|
64
|
+
server.add(com.atproto.server.requestEmailUpdate, {
|
|
65
|
+
rateLimit,
|
|
66
|
+
auth,
|
|
67
|
+
handler: async ({ auth }) => {
|
|
68
|
+
const did = auth.credentials.did
|
|
48
69
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
70
|
+
// @TODO get the locale somehow (either by adding a field in the request
|
|
71
|
+
// body, or by using the `Accept-Language` header).
|
|
72
|
+
const locale = undefined
|
|
52
73
|
|
|
53
|
-
|
|
54
|
-
if (tokenRequired) {
|
|
55
|
-
const token = await ctx.accountManager.createEmailToken(
|
|
74
|
+
const { tokenRequired } = await ctx.accountManager.requestEmailUpdate(
|
|
56
75
|
did,
|
|
57
|
-
|
|
76
|
+
{ locale },
|
|
58
77
|
)
|
|
59
|
-
await ctx.mailer.sendUpdateEmail({ token }, { to: account.email })
|
|
60
|
-
}
|
|
61
78
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
})
|
|
79
|
+
return {
|
|
80
|
+
encoding: 'application/json' as const,
|
|
81
|
+
body: { tokenRequired },
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
})
|
|
85
|
+
}
|
|
70
86
|
}
|
|
@@ -1,82 +1,52 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { isDisposableEmail } from 'disposable-email-domains-js'
|
|
3
|
-
import {
|
|
4
|
-
ForbiddenError,
|
|
5
|
-
InvalidRequestError,
|
|
6
|
-
Server,
|
|
7
|
-
} from '@atproto/xrpc-server'
|
|
1
|
+
import { InvalidRequestError, Server } from '@atproto/xrpc-server'
|
|
8
2
|
import { UserAlreadyExistsError } from '../../../../account-manager/helpers/account.js'
|
|
9
|
-
import { ACCESS_FULL } from '../../../../auth-scope.js'
|
|
10
3
|
import { AppContext } from '../../../../context.js'
|
|
11
4
|
import { com } from '../../../../lexicons/index.js'
|
|
5
|
+
import { requestEmailUpdateAuth } from './requestEmailUpdate.js'
|
|
12
6
|
|
|
13
7
|
export default function (server: Server, ctx: AppContext) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
authorize: () => {
|
|
19
|
-
throw new ForbiddenError(
|
|
20
|
-
'OAuth credentials are not supported for this endpoint',
|
|
21
|
-
)
|
|
22
|
-
},
|
|
23
|
-
}),
|
|
24
|
-
handler: async ({ auth, input: { body }, req }) => {
|
|
25
|
-
const did = auth.credentials.did
|
|
26
|
-
const { token, email } = body
|
|
27
|
-
if (!isEmailValid(email) || isDisposableEmail(email)) {
|
|
28
|
-
throw new InvalidRequestError(
|
|
29
|
-
'This email address is not supported, please use a different email.',
|
|
30
|
-
)
|
|
31
|
-
}
|
|
32
|
-
const account = await ctx.accountManager.getAccount(did, {
|
|
33
|
-
includeDeactivated: true,
|
|
34
|
-
})
|
|
35
|
-
if (!account) {
|
|
36
|
-
throw new InvalidRequestError('account not found')
|
|
37
|
-
}
|
|
8
|
+
const { entrywayClient } = ctx
|
|
9
|
+
|
|
10
|
+
// @NOTE Ensure that both endpoints use the same authentication logic
|
|
11
|
+
const auth = requestEmailUpdateAuth(ctx)
|
|
38
12
|
|
|
39
|
-
|
|
13
|
+
if (entrywayClient) {
|
|
14
|
+
server.add(com.atproto.server.updateEmail, {
|
|
15
|
+
auth,
|
|
16
|
+
handler: async ({ auth, input: { body }, req }) => {
|
|
40
17
|
const { headers } = await ctx.entrywayAuthHeaders(
|
|
41
18
|
req,
|
|
42
19
|
auth.credentials.did,
|
|
43
20
|
com.atproto.server.updateEmail.$lxm,
|
|
44
21
|
)
|
|
45
22
|
|
|
46
|
-
await
|
|
23
|
+
await entrywayClient.xrpc(com.atproto.server.updateEmail, {
|
|
47
24
|
headers,
|
|
48
25
|
body,
|
|
49
26
|
})
|
|
27
|
+
},
|
|
28
|
+
})
|
|
29
|
+
} else {
|
|
30
|
+
server.add(com.atproto.server.updateEmail, {
|
|
31
|
+
auth,
|
|
32
|
+
handler: async ({ auth, input: { body } }) => {
|
|
33
|
+
const did = auth.credentials.did
|
|
34
|
+
const { token, email } = body
|
|
50
35
|
|
|
51
|
-
|
|
52
|
-
|
|
36
|
+
// @TODO get the locale somehow (either by adding a field in the request
|
|
37
|
+
// body, or by using the `Accept-Language` header).
|
|
38
|
+
const locale = undefined
|
|
53
39
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
)
|
|
61
|
-
}
|
|
62
|
-
await ctx.accountManager.assertValidEmailToken(
|
|
63
|
-
did,
|
|
64
|
-
'update_email',
|
|
65
|
-
token,
|
|
66
|
-
)
|
|
67
|
-
}
|
|
40
|
+
try {
|
|
41
|
+
await ctx.accountManager.updateEmail(did, email, token, { locale })
|
|
42
|
+
} catch (cause) {
|
|
43
|
+
if (cause instanceof UserAlreadyExistsError) {
|
|
44
|
+
throw new InvalidRequestError(cause.message, undefined, { cause })
|
|
45
|
+
}
|
|
68
46
|
|
|
69
|
-
|
|
70
|
-
await ctx.accountManager.updateEmail({ did, email })
|
|
71
|
-
} catch (err) {
|
|
72
|
-
if (err instanceof UserAlreadyExistsError) {
|
|
73
|
-
throw new InvalidRequestError(
|
|
74
|
-
'This email address is already in use, please use a different email.',
|
|
75
|
-
)
|
|
76
|
-
} else {
|
|
77
|
-
throw err
|
|
47
|
+
throw cause
|
|
78
48
|
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
49
|
+
},
|
|
50
|
+
})
|
|
51
|
+
}
|
|
82
52
|
}
|
package/src/config/config.ts
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import assert from 'node:assert'
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import { DAY, HOUR, SECOND } from '@atproto/common'
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
BrandingInput as BrandingConfig,
|
|
6
|
+
HcaptchaConfig,
|
|
7
|
+
} from '@atproto/oauth-provider'
|
|
5
8
|
import { ensureValidDid } from '@atproto/syntax'
|
|
6
9
|
import { ServerEnvironment } from './env.js'
|
|
7
10
|
|
|
11
|
+
export type { BrandingConfig }
|
|
12
|
+
|
|
8
13
|
// off-config but still from env:
|
|
9
14
|
// logging: LOG_LEVEL, LOG_SYSTEMS, LOG_ENABLED, LOG_DESTINATION
|
|
10
15
|
|
|
@@ -152,6 +157,7 @@ export const envToCfg = (env: ServerEnvironment): ServerConfig => {
|
|
|
152
157
|
emailCfg = {
|
|
153
158
|
smtpUrl: env.emailSmtpUrl,
|
|
154
159
|
fromAddress: env.emailFromAddress,
|
|
160
|
+
disableConfirmationLink: env.emailDisableConfirmationLink ?? false,
|
|
155
161
|
}
|
|
156
162
|
}
|
|
157
163
|
|
|
@@ -167,6 +173,7 @@ export const envToCfg = (env: ServerEnvironment): ServerConfig => {
|
|
|
167
173
|
moderationEmailCfg = {
|
|
168
174
|
smtpUrl: env.moderationEmailSmtpUrl,
|
|
169
175
|
fromAddress: env.moderationEmailAddress,
|
|
176
|
+
disableConfirmationLink: false,
|
|
170
177
|
}
|
|
171
178
|
}
|
|
172
179
|
|
|
@@ -254,6 +261,62 @@ export const envToCfg = (env: ServerEnvironment): ServerConfig => {
|
|
|
254
261
|
preferCompressed: env.proxyPreferCompressed ?? false,
|
|
255
262
|
}
|
|
256
263
|
|
|
264
|
+
const brandingCfg = {
|
|
265
|
+
name: env.serviceName ?? `${hostname} PDS`,
|
|
266
|
+
logo: env.logoUrl,
|
|
267
|
+
colors: {
|
|
268
|
+
light: env.lightColor,
|
|
269
|
+
dark: env.darkColor,
|
|
270
|
+
|
|
271
|
+
contrastSaturation: env.contrastSaturation,
|
|
272
|
+
|
|
273
|
+
primary: env.primaryColor,
|
|
274
|
+
primaryContrast: env.primaryColorContrast,
|
|
275
|
+
primaryHue: env.primaryColorHue,
|
|
276
|
+
|
|
277
|
+
error: env.errorColor,
|
|
278
|
+
errorContrast: env.errorColorContrast,
|
|
279
|
+
errorHue: env.errorColorHue,
|
|
280
|
+
|
|
281
|
+
warning: env.warningColor,
|
|
282
|
+
warningContrast: env.warningColorContrast,
|
|
283
|
+
warningHue: env.warningColorHue,
|
|
284
|
+
|
|
285
|
+
info: env.infoColor,
|
|
286
|
+
infoContrast: env.infoColorContrast,
|
|
287
|
+
infoHue: env.infoColorHue,
|
|
288
|
+
|
|
289
|
+
success: env.successColor,
|
|
290
|
+
successContrast: env.successColorContrast,
|
|
291
|
+
successHue: env.successColorHue,
|
|
292
|
+
},
|
|
293
|
+
links: [
|
|
294
|
+
{
|
|
295
|
+
title: { en: 'Home', fr: 'Accueil' },
|
|
296
|
+
href: env.homeUrl,
|
|
297
|
+
rel: 'canonical' as const, // Prevents login page from being indexed
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
title: { en: 'Terms of Service' },
|
|
301
|
+
href: env.termsOfServiceUrl,
|
|
302
|
+
rel: 'terms-of-service' as const,
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
title: { en: 'Privacy Policy' },
|
|
306
|
+
href: env.privacyPolicyUrl,
|
|
307
|
+
rel: 'privacy-policy' as const,
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
title: { en: 'Support' },
|
|
311
|
+
href: env.supportUrl,
|
|
312
|
+
rel: 'help' as const,
|
|
313
|
+
},
|
|
314
|
+
].filter(
|
|
315
|
+
<T extends { href?: string }>(f: T): f is T & { href: string } =>
|
|
316
|
+
f.href != null && f.href !== '',
|
|
317
|
+
),
|
|
318
|
+
}
|
|
319
|
+
|
|
257
320
|
const oauthCfg: ServerConfig['oauth'] = entrywayCfg
|
|
258
321
|
? {
|
|
259
322
|
issuer: entrywayCfg.url,
|
|
@@ -272,61 +335,7 @@ export const envToCfg = (env: ServerEnvironment): ServerConfig => {
|
|
|
272
335
|
tokenSalt: env.hcaptchaTokenSalt,
|
|
273
336
|
}
|
|
274
337
|
: undefined,
|
|
275
|
-
branding:
|
|
276
|
-
name: env.serviceName ?? `${hostname} PDS`,
|
|
277
|
-
logo: env.logoUrl,
|
|
278
|
-
colors: {
|
|
279
|
-
light: env.lightColor,
|
|
280
|
-
dark: env.darkColor,
|
|
281
|
-
|
|
282
|
-
contrastSaturation: env.contrastSaturation,
|
|
283
|
-
|
|
284
|
-
primary: env.primaryColor,
|
|
285
|
-
primaryContrast: env.primaryColorContrast,
|
|
286
|
-
primaryHue: env.primaryColorHue,
|
|
287
|
-
|
|
288
|
-
error: env.errorColor,
|
|
289
|
-
errorContrast: env.errorColorContrast,
|
|
290
|
-
errorHue: env.errorColorHue,
|
|
291
|
-
|
|
292
|
-
warning: env.warningColor,
|
|
293
|
-
warningContrast: env.warningColorContrast,
|
|
294
|
-
warningHue: env.warningColorHue,
|
|
295
|
-
|
|
296
|
-
info: env.infoColor,
|
|
297
|
-
infoContrast: env.infoColorContrast,
|
|
298
|
-
infoHue: env.infoColorHue,
|
|
299
|
-
|
|
300
|
-
success: env.successColor,
|
|
301
|
-
successContrast: env.successColorContrast,
|
|
302
|
-
successHue: env.successColorHue,
|
|
303
|
-
},
|
|
304
|
-
links: [
|
|
305
|
-
{
|
|
306
|
-
title: { en: 'Home', fr: 'Accueil' },
|
|
307
|
-
href: env.homeUrl,
|
|
308
|
-
rel: 'canonical' as const, // Prevents login page from being indexed
|
|
309
|
-
},
|
|
310
|
-
{
|
|
311
|
-
title: { en: 'Terms of Service' },
|
|
312
|
-
href: env.termsOfServiceUrl,
|
|
313
|
-
rel: 'terms-of-service' as const,
|
|
314
|
-
},
|
|
315
|
-
{
|
|
316
|
-
title: { en: 'Privacy Policy' },
|
|
317
|
-
href: env.privacyPolicyUrl,
|
|
318
|
-
rel: 'privacy-policy' as const,
|
|
319
|
-
},
|
|
320
|
-
{
|
|
321
|
-
title: { en: 'Support' },
|
|
322
|
-
href: env.supportUrl,
|
|
323
|
-
rel: 'help' as const,
|
|
324
|
-
},
|
|
325
|
-
].filter(
|
|
326
|
-
<T extends { href?: string }>(f: T): f is T & { href: string } =>
|
|
327
|
-
f.href != null && f.href !== '',
|
|
328
|
-
),
|
|
329
|
-
},
|
|
338
|
+
branding: brandingCfg,
|
|
330
339
|
trustedClients: env.trustedOAuthClients,
|
|
331
340
|
},
|
|
332
341
|
}
|
|
@@ -358,6 +367,7 @@ export const envToCfg = (env: ServerEnvironment): ServerConfig => {
|
|
|
358
367
|
fetch: fetchCfg,
|
|
359
368
|
lexicon: lexiconCfg,
|
|
360
369
|
proxy: proxyCfg,
|
|
370
|
+
branding: brandingCfg,
|
|
361
371
|
oauth: oauthCfg,
|
|
362
372
|
}
|
|
363
373
|
}
|
|
@@ -381,6 +391,7 @@ export type ServerConfig = {
|
|
|
381
391
|
crawlers: string[]
|
|
382
392
|
fetch: FetchConfig
|
|
383
393
|
proxy: ProxyConfig
|
|
394
|
+
branding: BrandingConfig
|
|
384
395
|
oauth: OAuthConfig
|
|
385
396
|
lexicon: LexiconResolverConfig
|
|
386
397
|
}
|
|
@@ -477,7 +488,7 @@ export type OAuthConfig = {
|
|
|
477
488
|
issuer: string
|
|
478
489
|
provider?: {
|
|
479
490
|
hcaptcha?: HcaptchaConfig
|
|
480
|
-
branding:
|
|
491
|
+
branding: BrandingConfig
|
|
481
492
|
trustedClients?: string[]
|
|
482
493
|
}
|
|
483
494
|
}
|
|
@@ -499,6 +510,7 @@ export type InvitesConfig =
|
|
|
499
510
|
export type EmailConfig = {
|
|
500
511
|
smtpUrl: string
|
|
501
512
|
fromAddress: string
|
|
513
|
+
disableConfirmationLink: boolean
|
|
502
514
|
}
|
|
503
515
|
|
|
504
516
|
export type SubscriptionConfig = {
|
package/src/config/env.ts
CHANGED
|
@@ -97,6 +97,9 @@ export function readEnv() {
|
|
|
97
97
|
// email
|
|
98
98
|
emailSmtpUrl: envStr('PDS_EMAIL_SMTP_URL'),
|
|
99
99
|
emailFromAddress: envStr('PDS_EMAIL_FROM_ADDRESS'),
|
|
100
|
+
emailDisableConfirmationLink: envBool(
|
|
101
|
+
'PDS_EMAIL_DISABLE_CONFIRMATION_LINK',
|
|
102
|
+
),
|
|
100
103
|
moderationEmailSmtpUrl: envStr('PDS_MODERATION_EMAIL_SMTP_URL'),
|
|
101
104
|
moderationEmailAddress: envStr('PDS_MODERATION_EMAIL_ADDRESS'),
|
|
102
105
|
|
package/src/context.ts
CHANGED
|
@@ -156,7 +156,7 @@ export class AppContext {
|
|
|
156
156
|
? nodemailer.createTransport(cfg.email.smtpUrl)
|
|
157
157
|
: nodemailer.createTransport({ jsonTransport: true })
|
|
158
158
|
|
|
159
|
-
const mailer = new ServerMailer(mailTransport, cfg)
|
|
159
|
+
const mailer = new ServerMailer(mailTransport, cfg.email, cfg.branding)
|
|
160
160
|
|
|
161
161
|
const modMailTransport =
|
|
162
162
|
cfg.moderationEmail !== null
|
|
@@ -270,6 +270,7 @@ export class AppContext {
|
|
|
270
270
|
const accountManager = new AccountManager(
|
|
271
271
|
idResolver,
|
|
272
272
|
jwtSecretKey,
|
|
273
|
+
mailer,
|
|
273
274
|
cfg.service.did,
|
|
274
275
|
cfg.identity.serviceHandleDomains,
|
|
275
276
|
cfg.db,
|