@atproto/pds 0.4.33 → 0.4.35
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +17 -0
- package/dist/account-manager/db/migrations/004-oauth.d.ts +4 -0
- package/dist/account-manager/db/migrations/004-oauth.d.ts.map +1 -0
- package/dist/account-manager/db/migrations/004-oauth.js +106 -0
- package/dist/account-manager/db/migrations/004-oauth.js.map +1 -0
- package/dist/account-manager/db/migrations/index.d.ts +2 -0
- package/dist/account-manager/db/migrations/index.d.ts.map +1 -1
- package/dist/account-manager/db/migrations/index.js +2 -0
- package/dist/account-manager/db/migrations/index.js.map +1 -1
- package/dist/account-manager/db/schema/authorization-request.d.ts +19 -0
- package/dist/account-manager/db/schema/authorization-request.d.ts.map +1 -0
- package/dist/account-manager/db/schema/authorization-request.js +5 -0
- package/dist/account-manager/db/schema/authorization-request.js.map +1 -0
- package/dist/account-manager/db/schema/device-account.d.ts +14 -0
- package/dist/account-manager/db/schema/device-account.d.ts.map +1 -0
- package/dist/account-manager/db/schema/device-account.js +5 -0
- package/dist/account-manager/db/schema/device-account.js.map +1 -0
- package/dist/account-manager/db/schema/device.d.ts +16 -0
- package/dist/account-manager/db/schema/device.d.ts.map +1 -0
- package/dist/account-manager/db/schema/device.js +5 -0
- package/dist/account-manager/db/schema/device.js.map +1 -0
- package/dist/account-manager/db/schema/index.d.ts +11 -1
- package/dist/account-manager/db/schema/index.d.ts.map +1 -1
- package/dist/account-manager/db/schema/token.d.ts +24 -0
- package/dist/account-manager/db/schema/token.d.ts.map +1 -0
- package/dist/account-manager/db/schema/token.js +5 -0
- package/dist/account-manager/db/schema/token.js.map +1 -0
- package/dist/account-manager/db/schema/used-refresh-token.d.ts +12 -0
- package/dist/account-manager/db/schema/used-refresh-token.d.ts.map +1 -0
- package/dist/account-manager/db/schema/used-refresh-token.js +5 -0
- package/dist/account-manager/db/schema/used-refresh-token.js.map +1 -0
- package/dist/account-manager/helpers/account.d.ts +27 -5
- package/dist/account-manager/helpers/account.d.ts.map +1 -1
- package/dist/account-manager/helpers/account.js +15 -14
- package/dist/account-manager/helpers/account.js.map +1 -1
- package/dist/account-manager/helpers/authorization-request.d.ts +12 -0
- package/dist/account-manager/helpers/authorization-request.d.ts.map +1 -0
- package/dist/account-manager/helpers/authorization-request.js +59 -0
- package/dist/account-manager/helpers/authorization-request.js.map +1 -0
- package/dist/account-manager/helpers/device-account.d.ts +108 -0
- package/dist/account-manager/helpers/device-account.d.ts.map +1 -0
- package/dist/account-manager/helpers/device-account.js +82 -0
- package/dist/account-manager/helpers/device-account.js.map +1 -0
- package/dist/account-manager/helpers/device.d.ts +9 -0
- package/dist/account-manager/helpers/device.d.ts.map +1 -0
- package/dist/account-manager/helpers/device.js +32 -0
- package/dist/account-manager/helpers/device.js.map +1 -0
- package/dist/account-manager/helpers/token.d.ts +485 -0
- package/dist/account-manager/helpers/token.d.ts.map +1 -0
- package/dist/account-manager/helpers/token.js +123 -0
- package/dist/account-manager/helpers/token.js.map +1 -0
- package/dist/account-manager/helpers/used-refresh-token.d.ts +10 -0
- package/dist/account-manager/helpers/used-refresh-token.d.ts.map +1 -0
- package/dist/account-manager/helpers/used-refresh-token.js +25 -0
- package/dist/account-manager/helpers/used-refresh-token.js.map +1 -0
- package/dist/account-manager/index.d.ts +36 -6
- package/dist/account-manager/index.d.ts.map +1 -1
- package/dist/account-manager/index.js +223 -22
- package/dist/account-manager/index.js.map +1 -1
- package/dist/actor-store/preference/reader.d.ts +2 -1
- package/dist/actor-store/preference/reader.d.ts.map +1 -1
- package/dist/actor-store/preference/reader.js +3 -1
- package/dist/actor-store/preference/reader.js.map +1 -1
- package/dist/actor-store/preference/transactor.d.ts +2 -1
- package/dist/actor-store/preference/transactor.d.ts.map +1 -1
- package/dist/actor-store/preference/transactor.js +7 -1
- package/dist/actor-store/preference/transactor.js.map +1 -1
- package/dist/actor-store/preference/util.d.ts +3 -0
- package/dist/actor-store/preference/util.d.ts.map +1 -0
- package/dist/actor-store/preference/util.js +12 -0
- package/dist/actor-store/preference/util.js.map +1 -0
- package/dist/actor-store/record/reader.d.ts +1 -1
- package/dist/api/app/bsky/actor/getPreferences.d.ts.map +1 -1
- package/dist/api/app/bsky/actor/getPreferences.js +1 -6
- 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 +1 -1
- package/dist/api/app/bsky/actor/putPreferences.js.map +1 -1
- package/dist/api/app/bsky/util/resolver.d.ts +1 -1
- package/dist/api/com/atproto/server/createSession.d.ts.map +1 -1
- package/dist/api/com/atproto/server/createSession.js +7 -31
- package/dist/api/com/atproto/server/createSession.js.map +1 -1
- package/dist/api/com/atproto/server/deleteSession.d.ts.map +1 -1
- package/dist/api/com/atproto/server/deleteSession.js +14 -13
- package/dist/api/com/atproto/server/deleteSession.js.map +1 -1
- package/dist/api/com/atproto/server/getSession.d.ts.map +1 -1
- package/dist/api/com/atproto/server/getSession.js +4 -2
- package/dist/api/com/atproto/server/getSession.js.map +1 -1
- package/dist/api/com/atproto/server/refreshSession.d.ts.map +1 -1
- package/dist/api/com/atproto/server/refreshSession.js +4 -2
- package/dist/api/com/atproto/server/refreshSession.js.map +1 -1
- package/dist/api/com/atproto/sync/getRepoStatus.d.ts.map +1 -1
- package/dist/api/com/atproto/sync/getRepoStatus.js +2 -1
- package/dist/api/com/atproto/sync/getRepoStatus.js.map +1 -1
- package/dist/api/com/atproto/sync/listRepos.js +2 -2
- package/dist/api/com/atproto/sync/listRepos.js.map +1 -1
- package/dist/api/proxy.d.ts.map +1 -1
- package/dist/api/proxy.js +15 -2
- package/dist/api/proxy.js.map +1 -1
- package/dist/auth-routes.d.ts +4 -0
- package/dist/auth-routes.d.ts.map +1 -0
- package/dist/auth-routes.js +24 -0
- package/dist/auth-routes.js.map +1 -0
- package/dist/auth-verifier.d.ts +32 -11
- package/dist/auth-verifier.d.ts.map +1 -1
- package/dist/auth-verifier.js +238 -79
- package/dist/auth-verifier.js.map +1 -1
- package/dist/config/config.d.ts +12 -0
- package/dist/config/config.d.ts.map +1 -1
- package/dist/config/config.js +45 -0
- package/dist/config/config.js.map +1 -1
- package/dist/config/env.d.ts +8 -0
- package/dist/config/env.d.ts.map +1 -1
- package/dist/config/env.js +10 -0
- package/dist/config/env.js.map +1 -1
- package/dist/config/secrets.d.ts +1 -0
- package/dist/config/secrets.d.ts.map +1 -1
- package/dist/config/secrets.js +1 -0
- package/dist/config/secrets.js.map +1 -1
- package/dist/context.d.ts +6 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +71 -13
- package/dist/context.js.map +1 -1
- package/dist/db/cast.d.ts +15 -0
- package/dist/db/cast.d.ts.map +1 -0
- package/dist/db/cast.js +66 -0
- package/dist/db/cast.js.map +1 -0
- package/dist/db/db.d.ts +2 -2
- package/dist/db/db.d.ts.map +1 -1
- package/dist/db/db.js +9 -7
- package/dist/db/db.js.map +1 -1
- package/dist/db/index.d.ts +1 -0
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +1 -0
- package/dist/db/index.js.map +1 -1
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +5 -0
- package/dist/error.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/lexicon/index.d.ts +4 -0
- package/dist/lexicon/index.d.ts.map +1 -1
- package/dist/lexicon/index.js +8 -0
- package/dist/lexicon/index.js.map +1 -1
- package/dist/lexicon/lexicons.d.ts +51 -0
- package/dist/lexicon/lexicons.d.ts.map +1 -1
- package/dist/lexicon/lexicons.js +51 -0
- package/dist/lexicon/lexicons.js.map +1 -1
- package/dist/lexicon/types/app/bsky/feed/defs.d.ts +1 -0
- package/dist/lexicon/types/app/bsky/feed/defs.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/feed/defs.js.map +1 -1
- package/dist/lexicon/types/app/bsky/graph/muteThread.d.ts +29 -0
- package/dist/lexicon/types/app/bsky/graph/muteThread.d.ts.map +1 -0
- package/dist/lexicon/types/app/bsky/graph/muteThread.js +3 -0
- package/dist/lexicon/types/app/bsky/graph/muteThread.js.map +1 -0
- package/dist/lexicon/types/app/bsky/graph/unmuteThread.d.ts +29 -0
- package/dist/lexicon/types/app/bsky/graph/unmuteThread.d.ts.map +1 -0
- package/dist/lexicon/types/app/bsky/graph/unmuteThread.js +3 -0
- package/dist/lexicon/types/app/bsky/graph/unmuteThread.js.map +1 -0
- package/dist/logger.d.ts +13 -11
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +80 -64
- package/dist/logger.js.map +1 -1
- package/dist/oauth/detailed-account-store.d.ts +27 -0
- package/dist/oauth/detailed-account-store.d.ts.map +1 -0
- package/dist/oauth/detailed-account-store.js +76 -0
- package/dist/oauth/detailed-account-store.js.map +1 -0
- package/dist/oauth/provider.d.ts +16 -0
- package/dist/oauth/provider.d.ts.map +1 -0
- package/dist/oauth/provider.js +45 -0
- package/dist/oauth/provider.js.map +1 -0
- package/dist/pipethrough.d.ts.map +1 -1
- package/dist/pipethrough.js.map +1 -1
- package/dist/sequencer/events.d.ts +2 -2
- package/example.env +21 -3
- package/package.json +9 -7
- package/src/account-manager/db/migrations/004-oauth.ts +122 -0
- package/src/account-manager/db/migrations/index.ts +2 -0
- package/src/account-manager/db/schema/authorization-request.ts +26 -0
- package/src/account-manager/db/schema/device-account.ts +15 -0
- package/src/account-manager/db/schema/device.ts +18 -0
- package/src/account-manager/db/schema/index.ts +15 -0
- package/src/account-manager/db/schema/token.ts +34 -0
- package/src/account-manager/db/schema/used-refresh-token.ts +13 -0
- package/src/account-manager/helpers/account.ts +16 -21
- package/src/account-manager/helpers/authorization-request.ts +82 -0
- package/src/account-manager/helpers/device-account.ts +135 -0
- package/src/account-manager/helpers/device.ts +45 -0
- package/src/account-manager/helpers/token.ts +185 -0
- package/src/account-manager/helpers/used-refresh-token.ts +30 -0
- package/src/account-manager/index.ts +325 -20
- package/src/actor-store/preference/reader.ts +8 -2
- package/src/actor-store/preference/transactor.ts +10 -0
- package/src/actor-store/preference/util.ts +8 -0
- package/src/api/app/bsky/actor/getPreferences.ts +2 -9
- package/src/api/app/bsky/actor/putPreferences.ts +5 -1
- package/src/api/com/atproto/server/createSession.ts +8 -44
- package/src/api/com/atproto/server/deleteSession.ts +14 -20
- package/src/api/com/atproto/server/getSession.ts +7 -2
- package/src/api/com/atproto/server/refreshSession.ts +6 -2
- package/src/api/com/atproto/sync/getRepoStatus.ts +3 -1
- package/src/api/com/atproto/sync/listRepos.ts +1 -1
- package/src/api/proxy.ts +18 -2
- package/src/auth-routes.ts +27 -0
- package/src/auth-verifier.ts +312 -92
- package/src/config/config.ts +66 -0
- package/src/config/env.ts +24 -0
- package/src/config/secrets.ts +2 -0
- package/src/context.ts +80 -14
- package/src/db/cast.ts +59 -0
- package/src/db/db.ts +15 -12
- package/src/db/index.ts +1 -0
- package/src/error.ts +7 -0
- package/src/index.ts +2 -0
- package/src/lexicon/index.ts +24 -0
- package/src/lexicon/lexicons.ts +52 -0
- package/src/lexicon/types/app/bsky/feed/defs.ts +1 -0
- package/src/lexicon/types/app/bsky/graph/muteThread.ts +38 -0
- package/src/lexicon/types/app/bsky/graph/unmuteThread.ts +38 -0
- package/src/logger.ts +83 -38
- package/src/oauth/detailed-account-store.ts +96 -0
- package/src/oauth/provider.ts +77 -0
- package/src/pipethrough.ts +3 -2
- package/tests/preferences.test.ts +67 -1
- package/tests/proxied/__snapshots__/feedgen.test.ts.snap +4 -1
- package/tests/proxied/__snapshots__/views.test.ts.snap +116 -38
package/src/config/config.ts
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
import path from 'node:path'
|
2
2
|
import assert from 'node:assert'
|
3
3
|
import { DAY, HOUR, SECOND } from '@atproto/common'
|
4
|
+
import { Customization } from '@atproto/oauth-provider'
|
4
5
|
import { ServerEnvironment } from './env'
|
5
6
|
|
6
7
|
// off-config but still from env:
|
@@ -234,6 +235,54 @@ export const envToCfg = (env: ServerEnvironment): ServerConfig => {
|
|
234
235
|
|
235
236
|
const crawlersCfg: ServerConfig['crawlers'] = env.crawlers ?? []
|
236
237
|
|
238
|
+
const fetchCfg: ServerConfig['fetch'] = {
|
239
|
+
disableSsrfProtection: env.fetchDisableSsrfProtection ?? false,
|
240
|
+
}
|
241
|
+
|
242
|
+
const oauthCfg: ServerConfig['oauth'] = entrywayCfg
|
243
|
+
? {
|
244
|
+
issuer: entrywayCfg.url,
|
245
|
+
provider: false,
|
246
|
+
}
|
247
|
+
: {
|
248
|
+
issuer: serviceCfg.publicUrl,
|
249
|
+
provider: {
|
250
|
+
customization: {
|
251
|
+
name: env.serviceName ?? 'Personal PDS',
|
252
|
+
logo: env.logoUrl,
|
253
|
+
colors: {
|
254
|
+
primary: env.primaryColor,
|
255
|
+
error: env.errorColor,
|
256
|
+
},
|
257
|
+
links: [
|
258
|
+
{
|
259
|
+
title: 'Home',
|
260
|
+
href: env.homeUrl,
|
261
|
+
rel: 'bookmark',
|
262
|
+
},
|
263
|
+
{
|
264
|
+
title: 'Terms of Service',
|
265
|
+
href: env.termsOfServiceUrl,
|
266
|
+
rel: 'terms-of-service',
|
267
|
+
},
|
268
|
+
{
|
269
|
+
title: 'Privacy Policy',
|
270
|
+
href: env.privacyPolicyUrl,
|
271
|
+
rel: 'privacy-policy',
|
272
|
+
},
|
273
|
+
{
|
274
|
+
title: 'Support',
|
275
|
+
href: env.supportUrl,
|
276
|
+
rel: 'help',
|
277
|
+
},
|
278
|
+
].filter(
|
279
|
+
(f): f is typeof f & { href: NonNullable<(typeof f)['href']> } =>
|
280
|
+
f.href != null,
|
281
|
+
),
|
282
|
+
},
|
283
|
+
},
|
284
|
+
}
|
285
|
+
|
237
286
|
return {
|
238
287
|
service: serviceCfg,
|
239
288
|
db: dbCfg,
|
@@ -251,6 +300,8 @@ export const envToCfg = (env: ServerEnvironment): ServerConfig => {
|
|
251
300
|
redis: redisCfg,
|
252
301
|
rateLimits: rateLimitsCfg,
|
253
302
|
crawlers: crawlersCfg,
|
303
|
+
fetch: fetchCfg,
|
304
|
+
oauth: oauthCfg,
|
254
305
|
}
|
255
306
|
}
|
256
307
|
|
@@ -271,6 +322,8 @@ export type ServerConfig = {
|
|
271
322
|
redis: RedisScratchConfig | null
|
272
323
|
rateLimits: RateLimitsConfig
|
273
324
|
crawlers: string[]
|
325
|
+
fetch: FetchConfig
|
326
|
+
oauth: OAuthConfig
|
274
327
|
}
|
275
328
|
|
276
329
|
export type ServiceConfig = {
|
@@ -337,6 +390,19 @@ export type EntrywayConfig = {
|
|
337
390
|
plcRotationKey: string
|
338
391
|
}
|
339
392
|
|
393
|
+
export type FetchConfig = {
|
394
|
+
disableSsrfProtection: boolean
|
395
|
+
}
|
396
|
+
|
397
|
+
export type OAuthConfig = {
|
398
|
+
issuer: string
|
399
|
+
provider:
|
400
|
+
| false
|
401
|
+
| {
|
402
|
+
customization: Customization
|
403
|
+
}
|
404
|
+
}
|
405
|
+
|
340
406
|
export type InvitesConfig =
|
341
407
|
| {
|
342
408
|
required: true
|
package/src/config/env.ts
CHANGED
@@ -6,14 +6,22 @@ export const readEnv = (): ServerEnvironment => {
|
|
6
6
|
port: envInt('PDS_PORT'),
|
7
7
|
hostname: envStr('PDS_HOSTNAME'),
|
8
8
|
serviceDid: envStr('PDS_SERVICE_DID'),
|
9
|
+
serviceName: envStr('PDS_SERVICE_NAME'),
|
9
10
|
version: envStr('PDS_VERSION'),
|
11
|
+
homeUrl: envStr('PDS_HOME_URL'),
|
12
|
+
logoUrl: envStr('PDS_LOGO_URL'),
|
10
13
|
privacyPolicyUrl: envStr('PDS_PRIVACY_POLICY_URL'),
|
14
|
+
supportUrl: envStr('PDS_SUPPORT_URL'),
|
11
15
|
termsOfServiceUrl: envStr('PDS_TERMS_OF_SERVICE_URL'),
|
12
16
|
contactEmailAddress: envStr('PDS_CONTACT_EMAIL_ADDRESS'),
|
13
17
|
acceptingImports: envBool('PDS_ACCEPTING_REPO_IMPORTS'),
|
14
18
|
blobUploadLimit: envInt('PDS_BLOB_UPLOAD_LIMIT'),
|
15
19
|
devMode: envBool('PDS_DEV_MODE'),
|
16
20
|
|
21
|
+
// branding
|
22
|
+
primaryColor: envStr('PDS_PRIMARY_COLOR'),
|
23
|
+
errorColor: envStr('PDS_ERROR_COLOR'),
|
24
|
+
|
17
25
|
// database
|
18
26
|
dataDirectory: envStr('PDS_DATA_DIRECTORY'),
|
19
27
|
disableWalAutoCheckpoint: envBool('PDS_SQLITE_DISABLE_WAL_AUTO_CHECKPOINT'),
|
@@ -97,6 +105,7 @@ export const readEnv = (): ServerEnvironment => {
|
|
97
105
|
crawlers: envList('PDS_CRAWLERS'),
|
98
106
|
|
99
107
|
// secrets
|
108
|
+
dpopSecret: envStr('PDS_DPOP_SECRET'),
|
100
109
|
jwtSecret: envStr('PDS_JWT_SECRET'),
|
101
110
|
adminPassword: envStr('PDS_ADMIN_PASSWORD'),
|
102
111
|
|
@@ -106,6 +115,9 @@ export const readEnv = (): ServerEnvironment => {
|
|
106
115
|
plcRotationKeyK256PrivateKeyHex: envStr(
|
107
116
|
'PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX',
|
108
117
|
),
|
118
|
+
|
119
|
+
// fetch
|
120
|
+
fetchDisableSsrfProtection: envBool('PDS_DISABLE_SSRF_PROTECTION'),
|
109
121
|
}
|
110
122
|
}
|
111
123
|
|
@@ -114,14 +126,22 @@ export type ServerEnvironment = {
|
|
114
126
|
port?: number
|
115
127
|
hostname?: string
|
116
128
|
serviceDid?: string
|
129
|
+
serviceName?: string
|
117
130
|
version?: string
|
131
|
+
homeUrl?: string
|
132
|
+
logoUrl?: string
|
118
133
|
privacyPolicyUrl?: string
|
134
|
+
supportUrl?: string
|
119
135
|
termsOfServiceUrl?: string
|
120
136
|
contactEmailAddress?: string
|
121
137
|
acceptingImports?: boolean
|
122
138
|
blobUploadLimit?: number
|
123
139
|
devMode?: boolean
|
124
140
|
|
141
|
+
// branding
|
142
|
+
primaryColor?: string
|
143
|
+
errorColor?: string
|
144
|
+
|
125
145
|
// database
|
126
146
|
dataDirectory?: string
|
127
147
|
disableWalAutoCheckpoint?: boolean
|
@@ -203,10 +223,14 @@ export type ServerEnvironment = {
|
|
203
223
|
crawlers?: string[]
|
204
224
|
|
205
225
|
// secrets
|
226
|
+
dpopSecret?: string
|
206
227
|
jwtSecret?: string
|
207
228
|
adminPassword?: string
|
208
229
|
|
209
230
|
// keys
|
210
231
|
plcRotationKeyKmsKeyId?: string
|
211
232
|
plcRotationKeyK256PrivateKeyHex?: string
|
233
|
+
|
234
|
+
// fetch
|
235
|
+
fetchDisableSsrfProtection?: boolean
|
212
236
|
}
|
package/src/config/secrets.ts
CHANGED
@@ -27,6 +27,7 @@ export const envToSecrets = (env: ServerEnvironment): ServerSecrets => {
|
|
27
27
|
}
|
28
28
|
|
29
29
|
return {
|
30
|
+
dpopSecret: env.dpopSecret,
|
30
31
|
jwtSecret: env.jwtSecret,
|
31
32
|
adminPassword: env.adminPassword,
|
32
33
|
plcRotationKey,
|
@@ -34,6 +35,7 @@ export const envToSecrets = (env: ServerEnvironment): ServerSecrets => {
|
|
34
35
|
}
|
35
36
|
|
36
37
|
export type ServerSecrets = {
|
38
|
+
dpopSecret?: string
|
37
39
|
jwtSecret: string
|
38
40
|
adminPassword: string
|
39
41
|
plcRotationKey: SigningKeyKms | SigningKeyMemory
|
package/src/context.ts
CHANGED
@@ -12,12 +12,21 @@ import {
|
|
12
12
|
RateLimiterOpts,
|
13
13
|
createServiceAuthHeaders,
|
14
14
|
} from '@atproto/xrpc-server'
|
15
|
+
import {
|
16
|
+
JoseKey,
|
17
|
+
Fetch,
|
18
|
+
safeFetchWrap,
|
19
|
+
OAuthVerifier,
|
20
|
+
} from '@atproto/oauth-provider'
|
21
|
+
|
15
22
|
import { ServerConfig, ServerSecrets } from './config'
|
23
|
+
import { PdsOAuthProvider } from './oauth/provider'
|
16
24
|
import {
|
17
25
|
AuthVerifier,
|
18
26
|
createPublicKeyObject,
|
19
27
|
createSecretKeyObject,
|
20
28
|
} from './auth-verifier'
|
29
|
+
import { fetchLogger } from './logger'
|
21
30
|
import { ServerMailer } from './mailer'
|
22
31
|
import { ModerationMailer } from './mailer/moderation'
|
23
32
|
import { BlobStore } from '@atproto/repo'
|
@@ -50,6 +59,8 @@ export type AppContextOptions = {
|
|
50
59
|
moderationAgent?: AtpAgent
|
51
60
|
reportingAgent?: AtpAgent
|
52
61
|
entrywayAgent?: AtpAgent
|
62
|
+
safeFetch: Fetch
|
63
|
+
authProvider?: PdsOAuthProvider
|
53
64
|
authVerifier: AuthVerifier
|
54
65
|
plcRotationKey: crypto.Keypair
|
55
66
|
cfg: ServerConfig
|
@@ -74,7 +85,9 @@ export class AppContext {
|
|
74
85
|
public moderationAgent: AtpAgent | undefined
|
75
86
|
public reportingAgent: AtpAgent | undefined
|
76
87
|
public entrywayAgent: AtpAgent | undefined
|
88
|
+
public safeFetch: Fetch
|
77
89
|
public authVerifier: AuthVerifier
|
90
|
+
public authProvider?: PdsOAuthProvider
|
78
91
|
public plcRotationKey: crypto.Keypair
|
79
92
|
public cfg: ServerConfig
|
80
93
|
|
@@ -97,7 +110,9 @@ export class AppContext {
|
|
97
110
|
this.moderationAgent = opts.moderationAgent
|
98
111
|
this.reportingAgent = opts.reportingAgent
|
99
112
|
this.entrywayAgent = opts.entrywayAgent
|
113
|
+
this.safeFetch = opts.safeFetch
|
100
114
|
this.authVerifier = opts.authVerifier
|
115
|
+
this.authProvider = opts.authProvider
|
101
116
|
this.plcRotationKey = opts.plcRotationKey
|
102
117
|
this.cfg = opts.cfg
|
103
118
|
}
|
@@ -206,7 +221,12 @@ export class AppContext {
|
|
206
221
|
: undefined
|
207
222
|
|
208
223
|
const jwtSecretKey = createSecretKeyObject(secrets.jwtSecret)
|
224
|
+
const jwtPublicKey = cfg.entryway
|
225
|
+
? createPublicKeyObject(cfg.entryway.jwtPublicKeyHex)
|
226
|
+
: null
|
227
|
+
|
209
228
|
const accountManager = new AccountManager(
|
229
|
+
backgroundQueue,
|
210
230
|
cfg.db.accountDbLoc,
|
211
231
|
jwtSecretKey,
|
212
232
|
cfg.service.did,
|
@@ -214,20 +234,6 @@ export class AppContext {
|
|
214
234
|
)
|
215
235
|
await accountManager.migrateOrThrow()
|
216
236
|
|
217
|
-
const jwtKey = cfg.entryway
|
218
|
-
? createPublicKeyObject(cfg.entryway.jwtPublicKeyHex)
|
219
|
-
: jwtSecretKey
|
220
|
-
|
221
|
-
const authVerifier = new AuthVerifier(accountManager, idResolver, {
|
222
|
-
jwtKey, // @TODO support multiple keys?
|
223
|
-
adminPass: secrets.adminPassword,
|
224
|
-
dids: {
|
225
|
-
pds: cfg.service.did,
|
226
|
-
entryway: cfg.entryway?.did,
|
227
|
-
modService: cfg.modService?.did,
|
228
|
-
},
|
229
|
-
})
|
230
|
-
|
231
237
|
const plcRotationKey =
|
232
238
|
secrets.plcRotationKey.provider === 'kms'
|
233
239
|
? await KmsKeypair.load({
|
@@ -250,6 +256,64 @@ export class AppContext {
|
|
250
256
|
appviewCdnUrlPattern: cfg.bskyAppView?.cdnUrlPattern,
|
251
257
|
})
|
252
258
|
|
259
|
+
// A fetch() function that protects against SSRF attacks, large responses &
|
260
|
+
// known bad domains. This function can safely be used to fetch user
|
261
|
+
// provided URLs (unless "disableSsrfProtection" is true, of course).
|
262
|
+
const safeFetch = safeFetchWrap({
|
263
|
+
allowHttp: cfg.fetch.disableSsrfProtection,
|
264
|
+
responseMaxSize: 512 * 1024, // 512kB
|
265
|
+
ssrfProtection: !cfg.fetch.disableSsrfProtection,
|
266
|
+
fetch: async (input, init) => {
|
267
|
+
const request = input instanceof Request ? input : null
|
268
|
+
const method = init?.method ?? request?.method ?? 'GET'
|
269
|
+
const uri = request?.url ?? String(input)
|
270
|
+
fetchLogger.debug({ method, uri }, 'fetch')
|
271
|
+
return globalThis.fetch(input, init)
|
272
|
+
},
|
273
|
+
})
|
274
|
+
|
275
|
+
const authProvider = cfg.oauth.provider
|
276
|
+
? new PdsOAuthProvider({
|
277
|
+
issuer: cfg.oauth.issuer,
|
278
|
+
keyset: [
|
279
|
+
// Note: OpenID compatibility would require an RS256 private key in this list
|
280
|
+
await JoseKey.fromKeyLike(jwtSecretKey, undefined, 'HS256'),
|
281
|
+
],
|
282
|
+
accountManager,
|
283
|
+
actorStore,
|
284
|
+
localViewer,
|
285
|
+
redis: redisScratch,
|
286
|
+
dpopSecret: secrets.dpopSecret,
|
287
|
+
customization: cfg.oauth.provider.customization,
|
288
|
+
safeFetch,
|
289
|
+
})
|
290
|
+
: undefined
|
291
|
+
|
292
|
+
const oauthVerifier: OAuthVerifier =
|
293
|
+
authProvider ?? // OAuthProvider extends OAuthVerifier
|
294
|
+
new OAuthVerifier({
|
295
|
+
issuer: cfg.oauth.issuer,
|
296
|
+
keyset: [await JoseKey.fromKeyLike(jwtPublicKey!, undefined, 'ES256K')],
|
297
|
+
dpopSecret: secrets.dpopSecret,
|
298
|
+
redis: redisScratch,
|
299
|
+
})
|
300
|
+
|
301
|
+
const authVerifier = new AuthVerifier(
|
302
|
+
accountManager,
|
303
|
+
idResolver,
|
304
|
+
oauthVerifier,
|
305
|
+
{
|
306
|
+
publicUrl: cfg.service.publicUrl,
|
307
|
+
jwtKey: jwtPublicKey ?? jwtSecretKey,
|
308
|
+
adminPass: secrets.adminPassword,
|
309
|
+
dids: {
|
310
|
+
pds: cfg.service.did,
|
311
|
+
entryway: cfg.entryway?.did,
|
312
|
+
modService: cfg.modService?.did,
|
313
|
+
},
|
314
|
+
},
|
315
|
+
)
|
316
|
+
|
253
317
|
return new AppContext({
|
254
318
|
actorStore,
|
255
319
|
blobstore,
|
@@ -269,7 +333,9 @@ export class AppContext {
|
|
269
333
|
moderationAgent,
|
270
334
|
reportingAgent,
|
271
335
|
entrywayAgent,
|
336
|
+
safeFetch,
|
272
337
|
authVerifier,
|
338
|
+
authProvider,
|
273
339
|
plcRotationKey,
|
274
340
|
cfg,
|
275
341
|
...(overrides ?? {}),
|
package/src/db/cast.ts
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
export type DateISO = `${string}T${string}Z`
|
2
|
+
export const toDateISO = (date: Date): DateISO => date.toISOString() as DateISO
|
3
|
+
export const fromDateISO = (date: DateISO): Date => new Date(date)
|
4
|
+
|
5
|
+
export type Json = string
|
6
|
+
export const toJson = (obj: unknown): Json => {
|
7
|
+
const json = JSON.stringify(obj)
|
8
|
+
if (json === undefined) throw new TypeError('Input not JSONifyable')
|
9
|
+
return json as Json
|
10
|
+
}
|
11
|
+
export const fromJson = <T>(json: Json): T => {
|
12
|
+
try {
|
13
|
+
return JSON.parse(json) as T
|
14
|
+
} catch (cause) {
|
15
|
+
throw new TypeError('Database contains invalid JSON', { cause })
|
16
|
+
}
|
17
|
+
}
|
18
|
+
|
19
|
+
export type JsonArray = `[${string}]`
|
20
|
+
export const isJsonArray = (json: string): json is JsonArray =>
|
21
|
+
// Although the JSON in the DB should have been encoded using toJson,
|
22
|
+
// there should not be any leading or trailing whitespace. We will still trim
|
23
|
+
// the string to protect against any manual editing of the DB.
|
24
|
+
json.trimStart().startsWith('[') && json.trimEnd().endsWith(']')
|
25
|
+
export function assertJsonArray(json: string): asserts json is JsonArray {
|
26
|
+
if (!isJsonArray(json)) throw new TypeError('Not an Array')
|
27
|
+
}
|
28
|
+
export const toJsonArray = (obj: readonly unknown[]): JsonArray => {
|
29
|
+
const json = toJson(obj)
|
30
|
+
assertJsonArray(json)
|
31
|
+
return json as JsonArray
|
32
|
+
}
|
33
|
+
export const fromJsonArray = <T>(json: JsonArray): T[] => {
|
34
|
+
assertJsonArray(json)
|
35
|
+
return fromJson(json) as T[]
|
36
|
+
}
|
37
|
+
|
38
|
+
export type JsonObject = `{${string}}`
|
39
|
+
const isJsonObject = (json: string): json is JsonObject =>
|
40
|
+
// Although the JSON in the DB should have been encoded using toJson,
|
41
|
+
// there should not be any leading or trailing whitespace. We will still trim
|
42
|
+
// the string to protect against any manual editing of the DB.
|
43
|
+
json.trimStart().startsWith('{') && json.trimEnd().endsWith('}')
|
44
|
+
function assertJsonObject(json: string): asserts json is JsonObject {
|
45
|
+
if (!isJsonObject(json)) throw new TypeError('Not an Object')
|
46
|
+
}
|
47
|
+
export const toJsonObject = (
|
48
|
+
obj: Readonly<Record<string, unknown>>,
|
49
|
+
): JsonObject => {
|
50
|
+
const json = toJson(obj)
|
51
|
+
assertJsonObject(json)
|
52
|
+
return json as JsonObject
|
53
|
+
}
|
54
|
+
export const fromJsonObject = <T extends Record<string, unknown>>(
|
55
|
+
json: JsonObject,
|
56
|
+
): T => {
|
57
|
+
assertJsonObject(json)
|
58
|
+
return fromJson(json) as T
|
59
|
+
}
|
package/src/db/db.ts
CHANGED
@@ -51,7 +51,7 @@ export class Database<Schema> {
|
|
51
51
|
}
|
52
52
|
|
53
53
|
async transactionNoRetry<T>(
|
54
|
-
fn: (db: Database<Schema>) => Promise<T>,
|
54
|
+
fn: (db: Database<Schema>) => T | Promise<T>,
|
55
55
|
): Promise<T> {
|
56
56
|
this.assertNotTransaction()
|
57
57
|
const leakyTxPlugin = new LeakyTxPlugin()
|
@@ -60,22 +60,25 @@ export class Database<Schema> {
|
|
60
60
|
.transaction()
|
61
61
|
.execute(async (txn) => {
|
62
62
|
const dbTxn = new Database(txn)
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
63
|
+
try {
|
64
|
+
const txRes = await fn(dbTxn)
|
65
|
+
leakyTxPlugin.endTx()
|
66
|
+
const hooks = dbTxn.commitHooks
|
67
|
+
return { hooks, txRes }
|
68
|
+
} catch (err) {
|
69
|
+
leakyTxPlugin.endTx()
|
70
|
+
// ensure that all in-flight queries are flushed & the connection is open
|
71
|
+
await txn.getExecutor().provideConnection(async () => {})
|
72
|
+
throw err
|
73
|
+
}
|
73
74
|
})
|
74
75
|
hooks.map((hook) => hook())
|
75
76
|
return txRes
|
76
77
|
}
|
77
78
|
|
78
|
-
async transaction<T>(
|
79
|
+
async transaction<T>(
|
80
|
+
fn: (db: Database<Schema>) => T | Promise<T>,
|
81
|
+
): Promise<T> {
|
79
82
|
return retrySqlite(() => this.transactionNoRetry(fn))
|
80
83
|
}
|
81
84
|
|
package/src/db/index.ts
CHANGED
package/src/error.ts
CHANGED
@@ -1,12 +1,19 @@
|
|
1
1
|
import { XRPCError } from '@atproto/xrpc-server'
|
2
2
|
import { ErrorRequestHandler } from 'express'
|
3
3
|
import { httpLogger as log } from './logger'
|
4
|
+
import { OAuthError } from '@atproto/oauth-provider'
|
4
5
|
|
5
6
|
export const handler: ErrorRequestHandler = (err, _req, res, next) => {
|
6
7
|
log.error(err, 'unexpected internal server error')
|
7
8
|
if (res.headersSent) {
|
8
9
|
return next(err)
|
9
10
|
}
|
11
|
+
|
12
|
+
if (err instanceof OAuthError) {
|
13
|
+
res.status(err.status).json(err.toJSON())
|
14
|
+
return
|
15
|
+
}
|
16
|
+
|
10
17
|
const serverError = XRPCError.fromError(err)
|
11
18
|
res.status(serverError.type).json(serverError.payload)
|
12
19
|
}
|
package/src/index.ts
CHANGED
@@ -11,6 +11,7 @@ import events from 'events'
|
|
11
11
|
import { Options as XrpcServerOptions } from '@atproto/xrpc-server'
|
12
12
|
import { DAY, HOUR, MINUTE, SECOND } from '@atproto/common'
|
13
13
|
import API from './api'
|
14
|
+
import * as authRoutes from './auth-routes'
|
14
15
|
import * as basicRoutes from './basic-routes'
|
15
16
|
import * as wellKnown from './well-known'
|
16
17
|
import * as error from './error'
|
@@ -99,6 +100,7 @@ export class PDS {
|
|
99
100
|
|
100
101
|
server = API(server, ctx)
|
101
102
|
|
103
|
+
app.use(authRoutes.createRouter(ctx))
|
102
104
|
app.use(basicRoutes.createRouter(ctx))
|
103
105
|
app.use(wellKnown.createRouter(ctx))
|
104
106
|
app.use(server.xrpc.router)
|
package/src/lexicon/index.ts
CHANGED
@@ -120,8 +120,10 @@ import * as AppBskyGraphGetRelationships from './types/app/bsky/graph/getRelatio
|
|
120
120
|
import * as AppBskyGraphGetSuggestedFollowsByActor from './types/app/bsky/graph/getSuggestedFollowsByActor'
|
121
121
|
import * as AppBskyGraphMuteActor from './types/app/bsky/graph/muteActor'
|
122
122
|
import * as AppBskyGraphMuteActorList from './types/app/bsky/graph/muteActorList'
|
123
|
+
import * as AppBskyGraphMuteThread from './types/app/bsky/graph/muteThread'
|
123
124
|
import * as AppBskyGraphUnmuteActor from './types/app/bsky/graph/unmuteActor'
|
124
125
|
import * as AppBskyGraphUnmuteActorList from './types/app/bsky/graph/unmuteActorList'
|
126
|
+
import * as AppBskyGraphUnmuteThread from './types/app/bsky/graph/unmuteThread'
|
125
127
|
import * as AppBskyLabelerGetServices from './types/app/bsky/labeler/getServices'
|
126
128
|
import * as AppBskyNotificationGetUnreadCount from './types/app/bsky/notification/getUnreadCount'
|
127
129
|
import * as AppBskyNotificationListNotifications from './types/app/bsky/notification/listNotifications'
|
@@ -1604,6 +1606,17 @@ export class AppBskyGraphNS {
|
|
1604
1606
|
return this._server.xrpc.method(nsid, cfg)
|
1605
1607
|
}
|
1606
1608
|
|
1609
|
+
muteThread<AV extends AuthVerifier>(
|
1610
|
+
cfg: ConfigOf<
|
1611
|
+
AV,
|
1612
|
+
AppBskyGraphMuteThread.Handler<ExtractAuth<AV>>,
|
1613
|
+
AppBskyGraphMuteThread.HandlerReqCtx<ExtractAuth<AV>>
|
1614
|
+
>,
|
1615
|
+
) {
|
1616
|
+
const nsid = 'app.bsky.graph.muteThread' // @ts-ignore
|
1617
|
+
return this._server.xrpc.method(nsid, cfg)
|
1618
|
+
}
|
1619
|
+
|
1607
1620
|
unmuteActor<AV extends AuthVerifier>(
|
1608
1621
|
cfg: ConfigOf<
|
1609
1622
|
AV,
|
@@ -1625,6 +1638,17 @@ export class AppBskyGraphNS {
|
|
1625
1638
|
const nsid = 'app.bsky.graph.unmuteActorList' // @ts-ignore
|
1626
1639
|
return this._server.xrpc.method(nsid, cfg)
|
1627
1640
|
}
|
1641
|
+
|
1642
|
+
unmuteThread<AV extends AuthVerifier>(
|
1643
|
+
cfg: ConfigOf<
|
1644
|
+
AV,
|
1645
|
+
AppBskyGraphUnmuteThread.Handler<ExtractAuth<AV>>,
|
1646
|
+
AppBskyGraphUnmuteThread.HandlerReqCtx<ExtractAuth<AV>>
|
1647
|
+
>,
|
1648
|
+
) {
|
1649
|
+
const nsid = 'app.bsky.graph.unmuteThread' // @ts-ignore
|
1650
|
+
return this._server.xrpc.method(nsid, cfg)
|
1651
|
+
}
|
1628
1652
|
}
|
1629
1653
|
|
1630
1654
|
export class AppBskyLabelerNS {
|
package/src/lexicon/lexicons.ts
CHANGED
@@ -5102,6 +5102,9 @@ export const schemaDict = {
|
|
5102
5102
|
type: 'string',
|
5103
5103
|
format: 'at-uri',
|
5104
5104
|
},
|
5105
|
+
threadMuted: {
|
5106
|
+
type: 'boolean',
|
5107
|
+
},
|
5105
5108
|
replyDisabled: {
|
5106
5109
|
type: 'boolean',
|
5107
5110
|
},
|
@@ -7675,6 +7678,30 @@ export const schemaDict = {
|
|
7675
7678
|
},
|
7676
7679
|
},
|
7677
7680
|
},
|
7681
|
+
AppBskyGraphMuteThread: {
|
7682
|
+
lexicon: 1,
|
7683
|
+
id: 'app.bsky.graph.muteThread',
|
7684
|
+
defs: {
|
7685
|
+
main: {
|
7686
|
+
type: 'procedure',
|
7687
|
+
description:
|
7688
|
+
'Mutes a thread preventing notifications from the thread and any of its children. Mutes are private in Bluesky. Requires auth.',
|
7689
|
+
input: {
|
7690
|
+
encoding: 'application/json',
|
7691
|
+
schema: {
|
7692
|
+
type: 'object',
|
7693
|
+
required: ['root'],
|
7694
|
+
properties: {
|
7695
|
+
root: {
|
7696
|
+
type: 'string',
|
7697
|
+
format: 'at-uri',
|
7698
|
+
},
|
7699
|
+
},
|
7700
|
+
},
|
7701
|
+
},
|
7702
|
+
},
|
7703
|
+
},
|
7704
|
+
},
|
7678
7705
|
AppBskyGraphUnmuteActor: {
|
7679
7706
|
lexicon: 1,
|
7680
7707
|
id: 'app.bsky.graph.unmuteActor',
|
@@ -7721,6 +7748,29 @@ export const schemaDict = {
|
|
7721
7748
|
},
|
7722
7749
|
},
|
7723
7750
|
},
|
7751
|
+
AppBskyGraphUnmuteThread: {
|
7752
|
+
lexicon: 1,
|
7753
|
+
id: 'app.bsky.graph.unmuteThread',
|
7754
|
+
defs: {
|
7755
|
+
main: {
|
7756
|
+
type: 'procedure',
|
7757
|
+
description: 'Unmutes the specified thread. Requires auth.',
|
7758
|
+
input: {
|
7759
|
+
encoding: 'application/json',
|
7760
|
+
schema: {
|
7761
|
+
type: 'object',
|
7762
|
+
required: ['root'],
|
7763
|
+
properties: {
|
7764
|
+
root: {
|
7765
|
+
type: 'string',
|
7766
|
+
format: 'at-uri',
|
7767
|
+
},
|
7768
|
+
},
|
7769
|
+
},
|
7770
|
+
},
|
7771
|
+
},
|
7772
|
+
},
|
7773
|
+
},
|
7724
7774
|
AppBskyLabelerDefs: {
|
7725
7775
|
lexicon: 1,
|
7726
7776
|
id: 'app.bsky.labeler.defs',
|
@@ -11105,8 +11155,10 @@ export const ids = {
|
|
11105
11155
|
AppBskyGraphListitem: 'app.bsky.graph.listitem',
|
11106
11156
|
AppBskyGraphMuteActor: 'app.bsky.graph.muteActor',
|
11107
11157
|
AppBskyGraphMuteActorList: 'app.bsky.graph.muteActorList',
|
11158
|
+
AppBskyGraphMuteThread: 'app.bsky.graph.muteThread',
|
11108
11159
|
AppBskyGraphUnmuteActor: 'app.bsky.graph.unmuteActor',
|
11109
11160
|
AppBskyGraphUnmuteActorList: 'app.bsky.graph.unmuteActorList',
|
11161
|
+
AppBskyGraphUnmuteThread: 'app.bsky.graph.unmuteThread',
|
11110
11162
|
AppBskyLabelerDefs: 'app.bsky.labeler.defs',
|
11111
11163
|
AppBskyLabelerGetServices: 'app.bsky.labeler.getServices',
|
11112
11164
|
AppBskyLabelerService: 'app.bsky.labeler.service',
|
@@ -0,0 +1,38 @@
|
|
1
|
+
/**
|
2
|
+
* GENERATED CODE - DO NOT MODIFY
|
3
|
+
*/
|
4
|
+
import express from 'express'
|
5
|
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
|
6
|
+
import { lexicons } from '../../../../lexicons'
|
7
|
+
import { isObj, hasProp } from '../../../../util'
|
8
|
+
import { CID } from 'multiformats/cid'
|
9
|
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
|
10
|
+
|
11
|
+
export interface QueryParams {}
|
12
|
+
|
13
|
+
export interface InputSchema {
|
14
|
+
root: string
|
15
|
+
[k: string]: unknown
|
16
|
+
}
|
17
|
+
|
18
|
+
export interface HandlerInput {
|
19
|
+
encoding: 'application/json'
|
20
|
+
body: InputSchema
|
21
|
+
}
|
22
|
+
|
23
|
+
export interface HandlerError {
|
24
|
+
status: number
|
25
|
+
message?: string
|
26
|
+
}
|
27
|
+
|
28
|
+
export type HandlerOutput = HandlerError | void
|
29
|
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
|
30
|
+
auth: HA
|
31
|
+
params: QueryParams
|
32
|
+
input: HandlerInput
|
33
|
+
req: express.Request
|
34
|
+
res: express.Response
|
35
|
+
}
|
36
|
+
export type Handler<HA extends HandlerAuth = never> = (
|
37
|
+
ctx: HandlerReqCtx<HA>,
|
38
|
+
) => Promise<HandlerOutput> | HandlerOutput
|