@atproto/bsky 0.0.170 → 0.0.172
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 +20 -0
- package/dist/api/app/bsky/notification/registerPush.d.ts.map +1 -1
- package/dist/api/app/bsky/notification/registerPush.js +6 -7
- package/dist/api/app/bsky/notification/registerPush.js.map +1 -1
- package/dist/api/app/bsky/notification/unregisterPush.d.ts +4 -0
- package/dist/api/app/bsky/notification/unregisterPush.d.ts.map +1 -0
- package/dist/api/app/bsky/notification/unregisterPush.js +33 -0
- package/dist/api/app/bsky/notification/unregisterPush.js.map +1 -0
- package/dist/api/app/bsky/notification/util.d.ts +4 -0
- package/dist/api/app/bsky/notification/util.d.ts.map +1 -1
- package/dist/api/app/bsky/notification/util.js +14 -1
- package/dist/api/app/bsky/notification/util.js.map +1 -1
- package/dist/api/app/bsky/unspecced/getAgeAssuranceState.d.ts +4 -0
- package/dist/api/app/bsky/unspecced/getAgeAssuranceState.d.ts.map +1 -0
- package/dist/api/app/bsky/unspecced/getAgeAssuranceState.js +36 -0
- package/dist/api/app/bsky/unspecced/getAgeAssuranceState.js.map +1 -0
- package/dist/api/app/bsky/unspecced/initAgeAssurance.d.ts +4 -0
- package/dist/api/app/bsky/unspecced/initAgeAssurance.d.ts.map +1 -0
- package/dist/api/app/bsky/unspecced/initAgeAssurance.js +59 -0
- package/dist/api/app/bsky/unspecced/initAgeAssurance.js.map +1 -0
- package/dist/api/external.d.ts +4 -0
- package/dist/api/external.d.ts.map +1 -0
- package/dist/api/external.js +47 -0
- package/dist/api/external.js.map +1 -0
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +6 -1
- package/dist/api/index.js.map +1 -1
- package/dist/api/kws/api.d.ts +4 -0
- package/dist/api/kws/api.d.ts.map +1 -0
- package/dist/api/kws/api.js +60 -0
- package/dist/api/kws/api.js.map +1 -0
- package/dist/api/kws/index.d.ts +4 -0
- package/dist/api/kws/index.d.ts.map +1 -0
- package/dist/api/kws/index.js +21 -0
- package/dist/api/kws/index.js.map +1 -0
- package/dist/api/kws/types.d.ts +100 -0
- package/dist/api/kws/types.d.ts.map +1 -0
- package/dist/api/kws/types.js +29 -0
- package/dist/api/kws/types.js.map +1 -0
- package/dist/api/kws/util.d.ts +21 -0
- package/dist/api/kws/util.d.ts.map +1 -0
- package/dist/api/kws/util.js +78 -0
- package/dist/api/kws/util.js.map +1 -0
- package/dist/api/kws/webhook.d.ts +5 -0
- package/dist/api/kws/webhook.d.ts.map +1 -0
- package/dist/api/kws/webhook.js +80 -0
- package/dist/api/kws/webhook.js.map +1 -0
- package/dist/config.d.ts +12 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +40 -0
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +3 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +3 -0
- package/dist/context.js.map +1 -1
- package/dist/data-plane/bsync/index.d.ts.map +1 -1
- package/dist/data-plane/bsync/index.js +52 -33
- package/dist/data-plane/bsync/index.js.map +1 -1
- package/dist/data-plane/server/db/migrations/20250627T025331240Z-add-actor-age-assurance-columns.d.ts +4 -0
- package/dist/data-plane/server/db/migrations/20250627T025331240Z-add-actor-age-assurance-columns.d.ts.map +1 -0
- package/dist/data-plane/server/db/migrations/20250627T025331240Z-add-actor-age-assurance-columns.js +22 -0
- package/dist/data-plane/server/db/migrations/20250627T025331240Z-add-actor-age-assurance-columns.js.map +1 -0
- package/dist/data-plane/server/db/migrations/index.d.ts +1 -0
- package/dist/data-plane/server/db/migrations/index.d.ts.map +1 -1
- package/dist/data-plane/server/db/migrations/index.js +2 -0
- package/dist/data-plane/server/db/migrations/index.js.map +1 -1
- package/dist/data-plane/server/db/tables/actor.d.ts +2 -0
- package/dist/data-plane/server/db/tables/actor.d.ts.map +1 -1
- package/dist/data-plane/server/db/tables/actor.js.map +1 -1
- package/dist/data-plane/server/routes/profile.d.ts.map +1 -1
- package/dist/data-plane/server/routes/profile.js +14 -1
- package/dist/data-plane/server/routes/profile.js.map +1 -1
- package/dist/feature-gates.d.ts +2 -1
- package/dist/feature-gates.d.ts.map +1 -1
- package/dist/feature-gates.js +1 -0
- package/dist/feature-gates.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/kws.d.ts +16 -0
- package/dist/kws.d.ts.map +1 -0
- package/dist/kws.js +86 -0
- package/dist/kws.js.map +1 -0
- package/dist/lexicon/index.d.ts +8 -2
- package/dist/lexicon/index.d.ts.map +1 -1
- package/dist/lexicon/index.js +16 -4
- package/dist/lexicon/index.js.map +1 -1
- package/dist/lexicon/lexicons.d.ts +374 -82
- package/dist/lexicon/lexicons.d.ts.map +1 -1
- package/dist/lexicon/lexicons.js +191 -42
- package/dist/lexicon/lexicons.js.map +1 -1
- package/dist/lexicon/types/app/bsky/notification/unregisterPush.d.ts +17 -0
- package/dist/lexicon/types/app/bsky/notification/unregisterPush.d.ts.map +1 -0
- package/dist/lexicon/types/app/bsky/notification/unregisterPush.js +7 -0
- package/dist/lexicon/types/app/bsky/notification/unregisterPush.js.map +1 -0
- package/dist/lexicon/types/app/bsky/unspecced/defs.d.ts +32 -0
- package/dist/lexicon/types/app/bsky/unspecced/defs.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/unspecced/defs.js +18 -0
- package/dist/lexicon/types/app/bsky/unspecced/defs.js.map +1 -1
- package/dist/lexicon/types/app/bsky/unspecced/getAgeAssuranceState.d.ts +18 -0
- package/dist/lexicon/types/app/bsky/unspecced/getAgeAssuranceState.d.ts.map +1 -0
- package/dist/lexicon/types/app/bsky/unspecced/getAgeAssuranceState.js +7 -0
- package/dist/lexicon/types/app/bsky/unspecced/getAgeAssuranceState.js.map +1 -0
- package/dist/lexicon/types/app/bsky/unspecced/initAgeAssurance.d.ts +28 -0
- package/dist/lexicon/types/app/bsky/unspecced/initAgeAssurance.d.ts.map +1 -0
- package/dist/lexicon/types/app/bsky/unspecced/initAgeAssurance.js +7 -0
- package/dist/lexicon/types/app/bsky/unspecced/initAgeAssurance.js.map +1 -0
- package/dist/proto/bsky_pb.d.ts +33 -0
- package/dist/proto/bsky_pb.d.ts.map +1 -1
- package/dist/proto/bsky_pb.js +112 -4
- package/dist/proto/bsky_pb.js.map +1 -1
- package/dist/proto/courier_connect.d.ts +19 -1
- package/dist/proto/courier_connect.d.ts.map +1 -1
- package/dist/proto/courier_connect.js +18 -0
- package/dist/proto/courier_connect.js.map +1 -1
- package/dist/proto/courier_pb.d.ts +76 -0
- package/dist/proto/courier_pb.d.ts.map +1 -1
- package/dist/proto/courier_pb.js +233 -1
- package/dist/proto/courier_pb.js.map +1 -1
- package/dist/stash.d.ts +1 -0
- package/dist/stash.d.ts.map +1 -1
- package/dist/stash.js +1 -0
- package/dist/stash.js.map +1 -1
- package/package.json +7 -4
- package/proto/bsky.proto +8 -0
- package/proto/courier.proto +18 -0
- package/src/api/app/bsky/notification/registerPush.ts +5 -8
- package/src/api/app/bsky/notification/unregisterPush.ts +38 -0
- package/src/api/app/bsky/notification/util.ts +18 -0
- package/src/api/app/bsky/unspecced/getAgeAssuranceState.ts +46 -0
- package/src/api/app/bsky/unspecced/initAgeAssurance.ts +71 -0
- package/src/api/external.ts +13 -0
- package/src/api/index.ts +6 -0
- package/src/api/kws/api.ts +92 -0
- package/src/api/kws/index.ts +23 -0
- package/src/api/kws/types.ts +67 -0
- package/src/api/kws/util.ts +111 -0
- package/src/api/kws/webhook.ts +107 -0
- package/src/config.ts +59 -0
- package/src/context.ts +6 -0
- package/src/data-plane/bsync/index.ts +69 -33
- package/src/data-plane/server/db/migrations/20250627T025331240Z-add-actor-age-assurance-columns.ts +22 -0
- package/src/data-plane/server/db/migrations/index.ts +1 -0
- package/src/data-plane/server/db/tables/actor.ts +2 -0
- package/src/data-plane/server/routes/profile.ts +16 -1
- package/src/feature-gates.ts +1 -0
- package/src/index.ts +7 -1
- package/src/kws.ts +108 -0
- package/src/lexicon/index.ts +50 -11
- package/src/lexicon/lexicons.ts +201 -43
- package/src/lexicon/types/app/bsky/notification/unregisterPush.ts +36 -0
- package/src/lexicon/types/app/bsky/unspecced/defs.ts +50 -0
- package/src/lexicon/types/app/bsky/unspecced/getAgeAssuranceState.ts +34 -0
- package/src/lexicon/types/app/bsky/unspecced/initAgeAssurance.ts +47 -0
- package/src/proto/bsky_pb.ts +90 -0
- package/src/proto/courier_connect.ts +22 -0
- package/src/proto/courier_pb.ts +246 -0
- package/src/stash.ts +3 -0
- package/tests/views/age-assurance.test.ts +425 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/tsconfig.tests.tsbuildinfo +1 -1
package/src/config.ts
CHANGED
|
@@ -6,6 +6,17 @@ type LiveNowConfig = {
|
|
|
6
6
|
domains: string[]
|
|
7
7
|
}[]
|
|
8
8
|
|
|
9
|
+
export interface KwsConfig {
|
|
10
|
+
apiKey: string
|
|
11
|
+
apiOrigin: string
|
|
12
|
+
authOrigin: string
|
|
13
|
+
clientId: string
|
|
14
|
+
redirectUrl: string
|
|
15
|
+
userAgent: string
|
|
16
|
+
verificationSecret: string
|
|
17
|
+
webhookSecret: string
|
|
18
|
+
}
|
|
19
|
+
|
|
9
20
|
export interface ServerConfigValues {
|
|
10
21
|
// service
|
|
11
22
|
version?: string
|
|
@@ -72,6 +83,7 @@ export interface ServerConfigValues {
|
|
|
72
83
|
proxyMaxResponseSize?: number
|
|
73
84
|
proxyMaxRetries?: number
|
|
74
85
|
proxyPreferCompressed?: boolean
|
|
86
|
+
kws?: KwsConfig
|
|
75
87
|
}
|
|
76
88
|
|
|
77
89
|
export class ServerConfig {
|
|
@@ -222,6 +234,48 @@ export class ServerConfig {
|
|
|
222
234
|
const proxyPreferCompressed =
|
|
223
235
|
process.env.BSKY_PROXY_PREFER_COMPRESSED === 'true'
|
|
224
236
|
|
|
237
|
+
let kws: KwsConfig | undefined
|
|
238
|
+
const kwsApiKey = process.env.BSKY_KWS_API_KEY
|
|
239
|
+
const kwsApiOrigin = process.env.BSKY_KWS_API_ORIGIN
|
|
240
|
+
const kwsAuthOrigin = process.env.BSKY_KWS_AUTH_ORIGIN
|
|
241
|
+
const kwsClientId = process.env.BSKY_KWS_CLIENT_ID
|
|
242
|
+
const kwsRedirectUrl = process.env.BSKY_KWS_REDIRECT_URL
|
|
243
|
+
const kwsUserAgent = process.env.BSKY_KWS_USER_AGENT
|
|
244
|
+
const kwsVerificationSecret = process.env.BSKY_KWS_VERIFICATION_SECRET
|
|
245
|
+
const kwsWebhookSecret = process.env.BSKY_KWS_WEBHOOK_SECRET
|
|
246
|
+
if (
|
|
247
|
+
kwsApiKey ||
|
|
248
|
+
kwsApiOrigin ||
|
|
249
|
+
kwsAuthOrigin ||
|
|
250
|
+
kwsClientId ||
|
|
251
|
+
kwsRedirectUrl ||
|
|
252
|
+
kwsUserAgent ||
|
|
253
|
+
kwsVerificationSecret ||
|
|
254
|
+
kwsWebhookSecret
|
|
255
|
+
) {
|
|
256
|
+
assert(
|
|
257
|
+
kwsApiOrigin &&
|
|
258
|
+
kwsAuthOrigin &&
|
|
259
|
+
kwsClientId &&
|
|
260
|
+
kwsRedirectUrl &&
|
|
261
|
+
kwsUserAgent &&
|
|
262
|
+
kwsVerificationSecret &&
|
|
263
|
+
kwsWebhookSecret &&
|
|
264
|
+
kwsApiKey,
|
|
265
|
+
'all KWS environment variables must be set if any are set',
|
|
266
|
+
)
|
|
267
|
+
kws = {
|
|
268
|
+
apiKey: kwsApiKey,
|
|
269
|
+
apiOrigin: kwsApiOrigin,
|
|
270
|
+
authOrigin: kwsAuthOrigin,
|
|
271
|
+
clientId: kwsClientId,
|
|
272
|
+
redirectUrl: kwsRedirectUrl,
|
|
273
|
+
userAgent: kwsUserAgent,
|
|
274
|
+
verificationSecret: kwsVerificationSecret,
|
|
275
|
+
webhookSecret: kwsWebhookSecret,
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
225
279
|
return new ServerConfig({
|
|
226
280
|
version,
|
|
227
281
|
debugMode,
|
|
@@ -279,6 +333,7 @@ export class ServerConfig {
|
|
|
279
333
|
proxyMaxResponseSize,
|
|
280
334
|
proxyMaxRetries,
|
|
281
335
|
proxyPreferCompressed,
|
|
336
|
+
kws,
|
|
282
337
|
...stripUndefineds(overrides ?? {}),
|
|
283
338
|
})
|
|
284
339
|
}
|
|
@@ -513,6 +568,10 @@ export class ServerConfig {
|
|
|
513
568
|
get proxyPreferCompressed(): boolean {
|
|
514
569
|
return this.cfg.proxyPreferCompressed ?? true
|
|
515
570
|
}
|
|
571
|
+
|
|
572
|
+
get kws() {
|
|
573
|
+
return this.cfg.kws
|
|
574
|
+
}
|
|
516
575
|
}
|
|
517
576
|
|
|
518
577
|
function stripUndefineds(
|
package/src/context.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { CourierClient } from './courier'
|
|
|
12
12
|
import { DataPlaneClient, HostList } from './data-plane/client'
|
|
13
13
|
import { FeatureGates } from './feature-gates'
|
|
14
14
|
import { Hydrator } from './hydration/hydrator'
|
|
15
|
+
import { KwsClient } from './kws'
|
|
15
16
|
import { httpLogger as log } from './logger'
|
|
16
17
|
import { StashClient } from './stash'
|
|
17
18
|
import {
|
|
@@ -41,6 +42,7 @@ export class AppContext {
|
|
|
41
42
|
authVerifier: AuthVerifier
|
|
42
43
|
featureGates: FeatureGates
|
|
43
44
|
blobDispatcher: Dispatcher
|
|
45
|
+
kwsClient: KwsClient | undefined
|
|
44
46
|
},
|
|
45
47
|
) {}
|
|
46
48
|
|
|
@@ -116,6 +118,10 @@ export class AppContext {
|
|
|
116
118
|
return this.opts.blobDispatcher
|
|
117
119
|
}
|
|
118
120
|
|
|
121
|
+
get kwsClient(): KwsClient | undefined {
|
|
122
|
+
return this.opts.kwsClient
|
|
123
|
+
}
|
|
124
|
+
|
|
119
125
|
reqLabelers(req: express.Request): ParsedLabelers {
|
|
120
126
|
const val = req.header('atproto-accept-labelers')
|
|
121
127
|
let parsed: ParsedLabelers | null
|
|
@@ -9,6 +9,7 @@ import { jsonStringToLex } from '@atproto/lexicon'
|
|
|
9
9
|
import { AtUri } from '@atproto/syntax'
|
|
10
10
|
import { ids } from '../../lexicon/lexicons'
|
|
11
11
|
import { SubjectActivitySubscription } from '../../lexicon/types/app/bsky/notification/defs'
|
|
12
|
+
import { AgeAssuranceEvent } from '../../lexicon/types/app/bsky/unspecced/defs'
|
|
12
13
|
import { httpLogger } from '../../logger'
|
|
13
14
|
import { Service } from '../../proto/bsync_connect'
|
|
14
15
|
import {
|
|
@@ -159,10 +160,10 @@ const createRoutes = (db: Database) => (router: ConnectRouter) =>
|
|
|
159
160
|
|
|
160
161
|
const now = new Date().toISOString()
|
|
161
162
|
|
|
162
|
-
//
|
|
163
|
+
// Index all items into private_data.
|
|
163
164
|
await handleGenericOperation(db, req, now)
|
|
164
165
|
|
|
165
|
-
//
|
|
166
|
+
// Maintain bespoke indexes for certain namespaces.
|
|
166
167
|
if (
|
|
167
168
|
namespace ===
|
|
168
169
|
Namespaces.AppBskyNotificationDefsSubjectActivitySubscription
|
|
@@ -174,6 +175,16 @@ const createRoutes = (db: Database) => (router: ConnectRouter) =>
|
|
|
174
175
|
'mock bsync put operation failed',
|
|
175
176
|
),
|
|
176
177
|
)
|
|
178
|
+
} else if (
|
|
179
|
+
namespace === Namespaces.AppBskyUnspeccedDefsAgeAssuranceEvent
|
|
180
|
+
) {
|
|
181
|
+
await handleAgeAssuranceEventOperation(db, req, now).catch(
|
|
182
|
+
(err: unknown) =>
|
|
183
|
+
httpLogger.warn(
|
|
184
|
+
{ err, namespace },
|
|
185
|
+
'mock bsync put operation failed',
|
|
186
|
+
),
|
|
187
|
+
)
|
|
177
188
|
}
|
|
178
189
|
|
|
179
190
|
return {
|
|
@@ -197,6 +208,43 @@ const createRoutes = (db: Database) => (router: ConnectRouter) =>
|
|
|
197
208
|
},
|
|
198
209
|
})
|
|
199
210
|
|
|
211
|
+
// upsert into or remove from private_data
|
|
212
|
+
const handleGenericOperation = async (
|
|
213
|
+
db: Database,
|
|
214
|
+
req: PutOperationRequest,
|
|
215
|
+
now: string,
|
|
216
|
+
) => {
|
|
217
|
+
const { actorDid, namespace, key, method, payload } = req
|
|
218
|
+
if (method === Method.CREATE || method === Method.UPDATE) {
|
|
219
|
+
await db.db
|
|
220
|
+
.insertInto('private_data')
|
|
221
|
+
.values({
|
|
222
|
+
actorDid,
|
|
223
|
+
namespace,
|
|
224
|
+
key,
|
|
225
|
+
payload: Buffer.from(payload).toString('utf8'),
|
|
226
|
+
indexedAt: now,
|
|
227
|
+
updatedAt: now,
|
|
228
|
+
})
|
|
229
|
+
.onConflict((oc) =>
|
|
230
|
+
oc.columns(['actorDid', 'namespace', 'key']).doUpdateSet({
|
|
231
|
+
payload: excluded(db.db, 'payload'),
|
|
232
|
+
updatedAt: excluded(db.db, 'updatedAt'),
|
|
233
|
+
}),
|
|
234
|
+
)
|
|
235
|
+
.execute()
|
|
236
|
+
} else if (method === Method.DELETE) {
|
|
237
|
+
await db.db
|
|
238
|
+
.deleteFrom('private_data')
|
|
239
|
+
.where('actorDid', '=', actorDid)
|
|
240
|
+
.where('namespace', '=', namespace)
|
|
241
|
+
.where('key', '=', key)
|
|
242
|
+
.execute()
|
|
243
|
+
} else {
|
|
244
|
+
assert.fail(`unexpected method ${method}`)
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
200
248
|
const handleSubjectActivitySubscriptionOperation = async (
|
|
201
249
|
db: Database,
|
|
202
250
|
req: PutOperationRequest,
|
|
@@ -246,39 +294,27 @@ const handleSubjectActivitySubscriptionOperation = async (
|
|
|
246
294
|
.execute()
|
|
247
295
|
}
|
|
248
296
|
|
|
249
|
-
|
|
250
|
-
const handleGenericOperation = async (
|
|
297
|
+
const handleAgeAssuranceEventOperation = async (
|
|
251
298
|
db: Database,
|
|
252
299
|
req: PutOperationRequest,
|
|
253
|
-
|
|
300
|
+
_now: string,
|
|
254
301
|
) => {
|
|
255
|
-
const { actorDid,
|
|
256
|
-
if (method
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
})
|
|
267
|
-
.onConflict((oc) =>
|
|
268
|
-
oc.columns(['actorDid', 'namespace', 'key']).doUpdateSet({
|
|
269
|
-
payload: excluded(db.db, 'payload'),
|
|
270
|
-
updatedAt: excluded(db.db, 'updatedAt'),
|
|
271
|
-
}),
|
|
272
|
-
)
|
|
273
|
-
.execute()
|
|
274
|
-
} else if (method === Method.DELETE) {
|
|
275
|
-
await db.db
|
|
276
|
-
.deleteFrom('private_data')
|
|
277
|
-
.where('actorDid', '=', actorDid)
|
|
278
|
-
.where('namespace', '=', namespace)
|
|
279
|
-
.where('key', '=', key)
|
|
280
|
-
.execute()
|
|
281
|
-
} else {
|
|
282
|
-
assert.fail(`unexpected method ${method}`)
|
|
302
|
+
const { actorDid, method, payload } = req
|
|
303
|
+
if (method !== Method.CREATE) return
|
|
304
|
+
|
|
305
|
+
const parsed = jsonStringToLex(
|
|
306
|
+
Buffer.from(payload).toString('utf8'),
|
|
307
|
+
) as AgeAssuranceEvent
|
|
308
|
+
const { status, createdAt } = parsed
|
|
309
|
+
|
|
310
|
+
const update = {
|
|
311
|
+
ageAssuranceStatus: status,
|
|
312
|
+
ageAssuranceLastInitiatedAt: status === 'pending' ? createdAt : undefined,
|
|
283
313
|
}
|
|
314
|
+
|
|
315
|
+
return db.db
|
|
316
|
+
.updateTable('actor')
|
|
317
|
+
.set(update)
|
|
318
|
+
.where('did', '=', actorDid)
|
|
319
|
+
.execute()
|
|
284
320
|
}
|
package/src/data-plane/server/db/migrations/20250627T025331240Z-add-actor-age-assurance-columns.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Kysely } from 'kysely'
|
|
2
|
+
|
|
3
|
+
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
4
|
+
await db.schema
|
|
5
|
+
.alterTable('actor')
|
|
6
|
+
.addColumn('ageAssuranceStatus', 'text')
|
|
7
|
+
.execute()
|
|
8
|
+
|
|
9
|
+
await db.schema
|
|
10
|
+
.alterTable('actor')
|
|
11
|
+
.addColumn('ageAssuranceLastInitiatedAt', 'varchar')
|
|
12
|
+
.execute()
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
16
|
+
await db.schema.alterTable('actor').dropColumn('ageAssuranceStatus').execute()
|
|
17
|
+
|
|
18
|
+
await db.schema
|
|
19
|
+
.alterTable('actor')
|
|
20
|
+
.dropColumn('ageAssuranceLastInitiatedAt')
|
|
21
|
+
.execute()
|
|
22
|
+
}
|
|
@@ -52,3 +52,4 @@ export * as _20250526T023712742Z from './20250526T023712742Z-like-repost-via'
|
|
|
52
52
|
export * as _20250528T221913281Z from './20250528T221913281Z-add-record-tags'
|
|
53
53
|
export * as _20250602T190357447Z from './20250602T190357447Z-add-private-data'
|
|
54
54
|
export * as _20250611T140649895Z from './20250611T140649895Z-add-activity-subscription'
|
|
55
|
+
export * as _20250627T025331240Z from './20250627T025331240Z-add-actor-age-assurance-columns'
|
|
@@ -22,7 +22,7 @@ type VerifiedBy = {
|
|
|
22
22
|
|
|
23
23
|
export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
|
|
24
24
|
async getActors(req) {
|
|
25
|
-
const { dids } = req
|
|
25
|
+
const { dids, returnAgeAssuranceForDids } = req
|
|
26
26
|
if (dids.length === 0) {
|
|
27
27
|
return { actors: [] }
|
|
28
28
|
}
|
|
@@ -105,6 +105,7 @@ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
|
|
|
105
105
|
}
|
|
106
106
|
return acc
|
|
107
107
|
}, {} as VerifiedBy)
|
|
108
|
+
const ageAssuranceForDids = new Set(returnAgeAssuranceForDids)
|
|
108
109
|
|
|
109
110
|
const activitySubscription = () => {
|
|
110
111
|
const record = parseRecordBytes<AppBskyNotificationDeclaration.Record>(
|
|
@@ -128,6 +129,19 @@ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
|
|
|
128
129
|
}
|
|
129
130
|
}
|
|
130
131
|
|
|
132
|
+
const ageAssuranceStatus = () => {
|
|
133
|
+
if (!ageAssuranceForDids.has(did)) {
|
|
134
|
+
return undefined
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
status: row?.ageAssuranceStatus ?? 'unknown',
|
|
139
|
+
lastInitiatedAt: row?.ageAssuranceLastInitiatedAt
|
|
140
|
+
? Timestamp.fromDate(new Date(row?.ageAssuranceLastInitiatedAt))
|
|
141
|
+
: undefined,
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
131
145
|
return {
|
|
132
146
|
exists: !!row,
|
|
133
147
|
handle: row?.handle ?? undefined,
|
|
@@ -149,6 +163,7 @@ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
|
|
|
149
163
|
tags: [],
|
|
150
164
|
profileTags: [],
|
|
151
165
|
allowActivitySubscriptionsFrom: activitySubscription(),
|
|
166
|
+
ageAssuranceStatus: ageAssuranceStatus(),
|
|
152
167
|
}
|
|
153
168
|
})
|
|
154
169
|
return { actors }
|
package/src/feature-gates.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { AtpAgent } from '@atproto/api'
|
|
|
10
10
|
import { DAY, SECOND } from '@atproto/common'
|
|
11
11
|
import { Keypair } from '@atproto/crypto'
|
|
12
12
|
import { IdResolver } from '@atproto/identity'
|
|
13
|
-
import API, { blobResolver, health, wellKnown } from './api'
|
|
13
|
+
import API, { blobResolver, external, health, wellKnown } from './api'
|
|
14
14
|
import { createBlobDispatcher } from './api/blob-dispatcher'
|
|
15
15
|
import { AuthVerifier, createPublicKeyObject } from './auth-verifier'
|
|
16
16
|
import { authWithApiKey as bsyncAuth, createBsyncClient } from './bsync'
|
|
@@ -27,6 +27,7 @@ import { FeatureGates } from './feature-gates'
|
|
|
27
27
|
import { Hydrator } from './hydration/hydrator'
|
|
28
28
|
import * as imageServer from './image/server'
|
|
29
29
|
import { ImageUriBuilder } from './image/uri'
|
|
30
|
+
import { createKwsClient } from './kws'
|
|
30
31
|
import { createServer } from './lexicon'
|
|
31
32
|
import { loggerMiddleware } from './logger'
|
|
32
33
|
import { createStashClient } from './stash'
|
|
@@ -58,6 +59,7 @@ export class BskyAppView {
|
|
|
58
59
|
}): BskyAppView {
|
|
59
60
|
const { config, signingKey } = opts
|
|
60
61
|
const app = express()
|
|
62
|
+
app.set('trust proxy', true)
|
|
61
63
|
app.use(cors({ maxAge: DAY / SECOND }))
|
|
62
64
|
app.use(loggerMiddleware)
|
|
63
65
|
app.use(compression())
|
|
@@ -150,6 +152,8 @@ export class BskyAppView {
|
|
|
150
152
|
})
|
|
151
153
|
: undefined
|
|
152
154
|
|
|
155
|
+
const kwsClient = config.kws ? createKwsClient(config.kws) : undefined
|
|
156
|
+
|
|
153
157
|
const entrywayJwtPublicKey = config.entrywayJwtPublicKeyHex
|
|
154
158
|
? createPublicKeyObject(config.entrywayJwtPublicKeyHex)
|
|
155
159
|
: undefined
|
|
@@ -186,6 +190,7 @@ export class BskyAppView {
|
|
|
186
190
|
authVerifier,
|
|
187
191
|
featureGates,
|
|
188
192
|
blobDispatcher,
|
|
193
|
+
kwsClient,
|
|
189
194
|
})
|
|
190
195
|
|
|
191
196
|
let server = createServer({
|
|
@@ -205,6 +210,7 @@ export class BskyAppView {
|
|
|
205
210
|
app.use(imageServer.createMiddleware(ctx, { prefix: '/img/' }))
|
|
206
211
|
app.use(server.xrpc.router)
|
|
207
212
|
app.use(error.handler)
|
|
213
|
+
app.use('/external', external.createRouter(ctx))
|
|
208
214
|
|
|
209
215
|
return new BskyAppView({ ctx, app })
|
|
210
216
|
}
|
package/src/kws.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { KwsExternalPayload } from './api/kws/types'
|
|
3
|
+
import { serializeExternalPayload } from './api/kws/util'
|
|
4
|
+
import { buildBasicAuth } from './auth-verifier'
|
|
5
|
+
import { KwsConfig } from './config'
|
|
6
|
+
import { httpLogger as log } from './logger'
|
|
7
|
+
|
|
8
|
+
export const createKwsClient = (cfg: KwsConfig): KwsClient => {
|
|
9
|
+
return new KwsClient(cfg)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Not `.strict()` to avoid breaking if KWS adds fields.
|
|
13
|
+
const authResponseSchema = z.object({
|
|
14
|
+
access_token: z.string(),
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
export class KwsClient {
|
|
18
|
+
constructor(public cfg: KwsConfig) {}
|
|
19
|
+
|
|
20
|
+
private async auth() {
|
|
21
|
+
try {
|
|
22
|
+
const res = await fetch(
|
|
23
|
+
`${this.cfg.authOrigin}/auth/realms/kws/protocol/openid-connect/token`,
|
|
24
|
+
{
|
|
25
|
+
method: 'POST',
|
|
26
|
+
headers: {
|
|
27
|
+
Accept: 'application/json',
|
|
28
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
29
|
+
Authorization: buildBasicAuth(this.cfg.clientId, this.cfg.apiKey),
|
|
30
|
+
},
|
|
31
|
+
body: new URLSearchParams({
|
|
32
|
+
grant_type: 'client_credentials',
|
|
33
|
+
scope: 'verification',
|
|
34
|
+
}),
|
|
35
|
+
},
|
|
36
|
+
)
|
|
37
|
+
if (!res.ok) {
|
|
38
|
+
const errorText = await res.text()
|
|
39
|
+
throw new Error(
|
|
40
|
+
`Failed to fetch age assurance access token: status: ${res.status}, statusText: ${res.statusText}, errorText: ${errorText}`,
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const auth = await res.json()
|
|
45
|
+
const authResponse = authResponseSchema.parse(auth)
|
|
46
|
+
return authResponse.access_token
|
|
47
|
+
} catch (err) {
|
|
48
|
+
log.error({ err }, 'Failed to authenticate with KWS')
|
|
49
|
+
throw err
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private async fetchWithAuth(
|
|
54
|
+
url: string,
|
|
55
|
+
init: RequestInit,
|
|
56
|
+
): Promise<Response> {
|
|
57
|
+
const accessToken = await this.auth()
|
|
58
|
+
|
|
59
|
+
return fetch(url, {
|
|
60
|
+
...init,
|
|
61
|
+
headers: {
|
|
62
|
+
...(init.headers ?? {}),
|
|
63
|
+
Authorization: `Bearer ${accessToken}`,
|
|
64
|
+
},
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async sendEmail({
|
|
69
|
+
countryCode,
|
|
70
|
+
email,
|
|
71
|
+
externalPayload,
|
|
72
|
+
language,
|
|
73
|
+
}: {
|
|
74
|
+
countryCode: string
|
|
75
|
+
email: string
|
|
76
|
+
externalPayload: KwsExternalPayload
|
|
77
|
+
language: string
|
|
78
|
+
}) {
|
|
79
|
+
const res = await this.fetchWithAuth(
|
|
80
|
+
`${this.cfg.apiOrigin}/v1/verifications/send-email`,
|
|
81
|
+
{
|
|
82
|
+
method: 'POST',
|
|
83
|
+
headers: {
|
|
84
|
+
'Content-Type': 'application/json',
|
|
85
|
+
'User-Agent': this.cfg.userAgent,
|
|
86
|
+
},
|
|
87
|
+
body: JSON.stringify({
|
|
88
|
+
email,
|
|
89
|
+
externalPayload: serializeExternalPayload(externalPayload),
|
|
90
|
+
language,
|
|
91
|
+
location: countryCode,
|
|
92
|
+
userContext: 'adult',
|
|
93
|
+
}),
|
|
94
|
+
},
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
if (!res.ok) {
|
|
98
|
+
const errorText = await res.text()
|
|
99
|
+
log.error(
|
|
100
|
+
{ status: res.status, statusText: res.statusText, errorText },
|
|
101
|
+
'Failed to send age assurance email',
|
|
102
|
+
)
|
|
103
|
+
throw new Error('Failed to send age assurance email')
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return res.json()
|
|
107
|
+
}
|
|
108
|
+
}
|
package/src/lexicon/index.ts
CHANGED
|
@@ -109,8 +109,8 @@ import * as AppBskyFeedGetFeedGenerators from './types/app/bsky/feed/getFeedGene
|
|
|
109
109
|
import * as AppBskyFeedGetFeedSkeleton from './types/app/bsky/feed/getFeedSkeleton.js'
|
|
110
110
|
import * as AppBskyFeedGetLikes from './types/app/bsky/feed/getLikes.js'
|
|
111
111
|
import * as AppBskyFeedGetListFeed from './types/app/bsky/feed/getListFeed.js'
|
|
112
|
-
import * as AppBskyFeedGetPosts from './types/app/bsky/feed/getPosts.js'
|
|
113
112
|
import * as AppBskyFeedGetPostThread from './types/app/bsky/feed/getPostThread.js'
|
|
113
|
+
import * as AppBskyFeedGetPosts from './types/app/bsky/feed/getPosts.js'
|
|
114
114
|
import * as AppBskyFeedGetQuotes from './types/app/bsky/feed/getQuotes.js'
|
|
115
115
|
import * as AppBskyFeedGetRepostedBy from './types/app/bsky/feed/getRepostedBy.js'
|
|
116
116
|
import * as AppBskyFeedGetSuggestedFeeds from './types/app/bsky/feed/getSuggestedFeeds.js'
|
|
@@ -147,7 +147,9 @@ import * as AppBskyNotificationPutActivitySubscription from './types/app/bsky/no
|
|
|
147
147
|
import * as AppBskyNotificationPutPreferences from './types/app/bsky/notification/putPreferences.js'
|
|
148
148
|
import * as AppBskyNotificationPutPreferencesV2 from './types/app/bsky/notification/putPreferencesV2.js'
|
|
149
149
|
import * as AppBskyNotificationRegisterPush from './types/app/bsky/notification/registerPush.js'
|
|
150
|
+
import * as AppBskyNotificationUnregisterPush from './types/app/bsky/notification/unregisterPush.js'
|
|
150
151
|
import * as AppBskyNotificationUpdateSeen from './types/app/bsky/notification/updateSeen.js'
|
|
152
|
+
import * as AppBskyUnspeccedGetAgeAssuranceState from './types/app/bsky/unspecced/getAgeAssuranceState.js'
|
|
151
153
|
import * as AppBskyUnspeccedGetConfig from './types/app/bsky/unspecced/getConfig.js'
|
|
152
154
|
import * as AppBskyUnspeccedGetPopularFeedGenerators from './types/app/bsky/unspecced/getPopularFeedGenerators.js'
|
|
153
155
|
import * as AppBskyUnspeccedGetPostThreadOtherV2 from './types/app/bsky/unspecced/getPostThreadOtherV2.js'
|
|
@@ -163,6 +165,7 @@ import * as AppBskyUnspeccedGetTaggedSuggestions from './types/app/bsky/unspecce
|
|
|
163
165
|
import * as AppBskyUnspeccedGetTrendingTopics from './types/app/bsky/unspecced/getTrendingTopics.js'
|
|
164
166
|
import * as AppBskyUnspeccedGetTrends from './types/app/bsky/unspecced/getTrends.js'
|
|
165
167
|
import * as AppBskyUnspeccedGetTrendsSkeleton from './types/app/bsky/unspecced/getTrendsSkeleton.js'
|
|
168
|
+
import * as AppBskyUnspeccedInitAgeAssurance from './types/app/bsky/unspecced/initAgeAssurance.js'
|
|
166
169
|
import * as AppBskyUnspeccedSearchActorsSkeleton from './types/app/bsky/unspecced/searchActorsSkeleton.js'
|
|
167
170
|
import * as AppBskyUnspeccedSearchPostsSkeleton from './types/app/bsky/unspecced/searchPostsSkeleton.js'
|
|
168
171
|
import * as AppBskyUnspeccedSearchStarterPacksSkeleton from './types/app/bsky/unspecced/searchStarterPacksSkeleton.js'
|
|
@@ -1597,27 +1600,27 @@ export class AppBskyFeedNS {
|
|
|
1597
1600
|
return this._server.xrpc.method(nsid, cfg)
|
|
1598
1601
|
}
|
|
1599
1602
|
|
|
1600
|
-
|
|
1603
|
+
getPostThread<A extends Auth = void>(
|
|
1601
1604
|
cfg: MethodConfigOrHandler<
|
|
1602
1605
|
A,
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
+
AppBskyFeedGetPostThread.QueryParams,
|
|
1607
|
+
AppBskyFeedGetPostThread.HandlerInput,
|
|
1608
|
+
AppBskyFeedGetPostThread.HandlerOutput
|
|
1606
1609
|
>,
|
|
1607
1610
|
) {
|
|
1608
|
-
const nsid = 'app.bsky.feed.
|
|
1611
|
+
const nsid = 'app.bsky.feed.getPostThread' // @ts-ignore
|
|
1609
1612
|
return this._server.xrpc.method(nsid, cfg)
|
|
1610
1613
|
}
|
|
1611
1614
|
|
|
1612
|
-
|
|
1615
|
+
getPosts<A extends Auth = void>(
|
|
1613
1616
|
cfg: MethodConfigOrHandler<
|
|
1614
1617
|
A,
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
+
AppBskyFeedGetPosts.QueryParams,
|
|
1619
|
+
AppBskyFeedGetPosts.HandlerInput,
|
|
1620
|
+
AppBskyFeedGetPosts.HandlerOutput
|
|
1618
1621
|
>,
|
|
1619
1622
|
) {
|
|
1620
|
-
const nsid = 'app.bsky.feed.
|
|
1623
|
+
const nsid = 'app.bsky.feed.getPosts' // @ts-ignore
|
|
1621
1624
|
return this._server.xrpc.method(nsid, cfg)
|
|
1622
1625
|
}
|
|
1623
1626
|
|
|
@@ -2077,6 +2080,18 @@ export class AppBskyNotificationNS {
|
|
|
2077
2080
|
return this._server.xrpc.method(nsid, cfg)
|
|
2078
2081
|
}
|
|
2079
2082
|
|
|
2083
|
+
unregisterPush<A extends Auth = void>(
|
|
2084
|
+
cfg: MethodConfigOrHandler<
|
|
2085
|
+
A,
|
|
2086
|
+
AppBskyNotificationUnregisterPush.QueryParams,
|
|
2087
|
+
AppBskyNotificationUnregisterPush.HandlerInput,
|
|
2088
|
+
AppBskyNotificationUnregisterPush.HandlerOutput
|
|
2089
|
+
>,
|
|
2090
|
+
) {
|
|
2091
|
+
const nsid = 'app.bsky.notification.unregisterPush' // @ts-ignore
|
|
2092
|
+
return this._server.xrpc.method(nsid, cfg)
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2080
2095
|
updateSeen<A extends Auth = void>(
|
|
2081
2096
|
cfg: MethodConfigOrHandler<
|
|
2082
2097
|
A,
|
|
@@ -2105,6 +2120,18 @@ export class AppBskyUnspeccedNS {
|
|
|
2105
2120
|
this._server = server
|
|
2106
2121
|
}
|
|
2107
2122
|
|
|
2123
|
+
getAgeAssuranceState<A extends Auth = void>(
|
|
2124
|
+
cfg: MethodConfigOrHandler<
|
|
2125
|
+
A,
|
|
2126
|
+
AppBskyUnspeccedGetAgeAssuranceState.QueryParams,
|
|
2127
|
+
AppBskyUnspeccedGetAgeAssuranceState.HandlerInput,
|
|
2128
|
+
AppBskyUnspeccedGetAgeAssuranceState.HandlerOutput
|
|
2129
|
+
>,
|
|
2130
|
+
) {
|
|
2131
|
+
const nsid = 'app.bsky.unspecced.getAgeAssuranceState' // @ts-ignore
|
|
2132
|
+
return this._server.xrpc.method(nsid, cfg)
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2108
2135
|
getConfig<A extends Auth = void>(
|
|
2109
2136
|
cfg: MethodConfigOrHandler<
|
|
2110
2137
|
A,
|
|
@@ -2285,6 +2312,18 @@ export class AppBskyUnspeccedNS {
|
|
|
2285
2312
|
return this._server.xrpc.method(nsid, cfg)
|
|
2286
2313
|
}
|
|
2287
2314
|
|
|
2315
|
+
initAgeAssurance<A extends Auth = void>(
|
|
2316
|
+
cfg: MethodConfigOrHandler<
|
|
2317
|
+
A,
|
|
2318
|
+
AppBskyUnspeccedInitAgeAssurance.QueryParams,
|
|
2319
|
+
AppBskyUnspeccedInitAgeAssurance.HandlerInput,
|
|
2320
|
+
AppBskyUnspeccedInitAgeAssurance.HandlerOutput
|
|
2321
|
+
>,
|
|
2322
|
+
) {
|
|
2323
|
+
const nsid = 'app.bsky.unspecced.initAgeAssurance' // @ts-ignore
|
|
2324
|
+
return this._server.xrpc.method(nsid, cfg)
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2288
2327
|
searchActorsSkeleton<A extends Auth = void>(
|
|
2289
2328
|
cfg: MethodConfigOrHandler<
|
|
2290
2329
|
A,
|