@atproto/bsky 0.0.198 → 0.0.199
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 +14 -0
- package/dist/api/age-assurance/const.d.ts +11 -0
- package/dist/api/age-assurance/const.d.ts.map +1 -0
- package/dist/api/age-assurance/const.js +142 -0
- package/dist/api/age-assurance/const.js.map +1 -0
- package/dist/api/age-assurance/index.d.ts +4 -0
- package/dist/api/age-assurance/index.d.ts.map +1 -0
- package/dist/api/age-assurance/index.js +24 -0
- package/dist/api/age-assurance/index.js.map +1 -0
- package/dist/api/age-assurance/kws/age-verified.d.ts +109 -0
- package/dist/api/age-assurance/kws/age-verified.d.ts.map +1 -0
- package/dist/api/age-assurance/kws/age-verified.js +63 -0
- package/dist/api/age-assurance/kws/age-verified.js.map +1 -0
- package/dist/api/age-assurance/kws/const.d.ts +13 -0
- package/dist/api/age-assurance/kws/const.d.ts.map +1 -0
- package/dist/api/age-assurance/kws/const.js +36 -0
- package/dist/api/age-assurance/kws/const.js.map +1 -0
- package/dist/api/age-assurance/kws/external-payload.d.ts +75 -0
- package/dist/api/age-assurance/kws/external-payload.d.ts.map +1 -0
- package/dist/api/age-assurance/kws/external-payload.js +124 -0
- package/dist/api/age-assurance/kws/external-payload.js.map +1 -0
- package/dist/api/age-assurance/kws/external-payload.test.d.ts +2 -0
- package/dist/api/age-assurance/kws/external-payload.test.d.ts.map +1 -0
- package/dist/api/age-assurance/kws/external-payload.test.js +65 -0
- package/dist/api/age-assurance/kws/external-payload.test.js.map +1 -0
- package/dist/api/age-assurance/redirects/kws-age-verified.d.ts +4 -0
- package/dist/api/age-assurance/redirects/kws-age-verified.d.ts.map +1 -0
- package/dist/api/age-assurance/redirects/kws-age-verified.js +76 -0
- package/dist/api/age-assurance/redirects/kws-age-verified.js.map +1 -0
- package/dist/api/age-assurance/stash.d.ts +4 -0
- package/dist/api/age-assurance/stash.d.ts.map +1 -0
- package/dist/api/age-assurance/stash.js +19 -0
- package/dist/api/age-assurance/stash.js.map +1 -0
- package/dist/api/age-assurance/types.d.ts +10 -0
- package/dist/api/age-assurance/types.d.ts.map +1 -0
- package/dist/api/age-assurance/types.js +3 -0
- package/dist/api/age-assurance/types.js.map +1 -0
- package/dist/api/age-assurance/util.d.ts +15 -0
- package/dist/api/age-assurance/util.d.ts.map +1 -0
- package/dist/api/age-assurance/util.js +54 -0
- package/dist/api/age-assurance/util.js.map +1 -0
- package/dist/api/age-assurance/webhooks/kws-age-verified.d.ts +4 -0
- package/dist/api/age-assurance/webhooks/kws-age-verified.d.ts.map +1 -0
- package/dist/api/age-assurance/webhooks/kws-age-verified.js +63 -0
- package/dist/api/age-assurance/webhooks/kws-age-verified.js.map +1 -0
- package/dist/api/app/bsky/ageassurance/begin.d.ts +4 -0
- package/dist/api/app/bsky/ageassurance/begin.d.ts.map +1 -0
- package/dist/api/app/bsky/ageassurance/begin.js +131 -0
- package/dist/api/app/bsky/ageassurance/begin.js.map +1 -0
- package/dist/api/app/bsky/ageassurance/getConfig.d.ts +4 -0
- package/dist/api/app/bsky/ageassurance/getConfig.d.ts.map +1 -0
- package/dist/api/app/bsky/ageassurance/getConfig.js +16 -0
- package/dist/api/app/bsky/ageassurance/getConfig.js.map +1 -0
- package/dist/api/app/bsky/ageassurance/getState.d.ts +4 -0
- package/dist/api/app/bsky/ageassurance/getState.d.ts.map +1 -0
- package/dist/api/app/bsky/ageassurance/getState.js +42 -0
- package/dist/api/app/bsky/ageassurance/getState.js.map +1 -0
- package/dist/api/external.d.ts.map +1 -1
- package/dist/api/external.js +2 -0
- package/dist/api/external.js.map +1 -1
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +8 -2
- package/dist/api/index.js.map +1 -1
- package/dist/api/kws/api.d.ts.map +1 -1
- package/dist/api/kws/api.js +44 -26
- package/dist/api/kws/api.js.map +1 -1
- package/dist/api/kws/index.d.ts.map +1 -1
- package/dist/api/kws/index.js +3 -1
- package/dist/api/kws/index.js.map +1 -1
- package/dist/api/kws/webhook.d.ts +3 -1
- package/dist/api/kws/webhook.d.ts.map +1 -1
- package/dist/api/kws/webhook.js +48 -20
- package/dist/api/kws/webhook.js.map +1 -1
- package/dist/config.d.ts +14 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +10 -2
- package/dist/config.js.map +1 -1
- package/dist/data-plane/bsync/index.d.ts.map +1 -1
- package/dist/data-plane/bsync/index.js +22 -0
- package/dist/data-plane/bsync/index.js.map +1 -1
- package/dist/data-plane/server/db/migrations/20251120T004738098Z-update-actor-age-assurance-v2.d.ts +4 -0
- package/dist/data-plane/server/db/migrations/20251120T004738098Z-update-actor-age-assurance-v2.d.ts.map +1 -0
- package/dist/data-plane/server/db/migrations/20251120T004738098Z-update-actor-age-assurance-v2.js +30 -0
- package/dist/data-plane/server/db/migrations/20251120T004738098Z-update-actor-age-assurance-v2.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 -1
- package/dist/data-plane/server/db/migrations/index.js.map +1 -1
- package/dist/data-plane/server/db/pagination.d.ts +3 -3
- package/dist/data-plane/server/db/tables/actor.d.ts +3 -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 +13 -1
- package/dist/data-plane/server/routes/profile.js.map +1 -1
- package/dist/hydration/hydrator.js +1 -1
- package/dist/hydration/hydrator.js.map +1 -1
- package/dist/kws.d.ts +35 -0
- package/dist/kws.d.ts.map +1 -1
- package/dist/kws.js +54 -0
- package/dist/kws.js.map +1 -1
- package/dist/logger.d.ts +1 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +2 -1
- package/dist/logger.js.map +1 -1
- package/dist/proto/bsky_pb.d.ts +8 -0
- package/dist/proto/bsky_pb.d.ts.map +1 -1
- package/dist/proto/bsky_pb.js +20 -0
- package/dist/proto/bsky_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/dist/util/uris.d.ts +2 -2
- package/dist/util/uris.d.ts.map +1 -1
- package/package.json +10 -9
- package/proto/bsky.proto +1 -0
- package/src/api/age-assurance/const.ts +142 -0
- package/src/api/age-assurance/index.ts +34 -0
- package/src/api/age-assurance/kws/age-verified.ts +75 -0
- package/src/api/age-assurance/kws/const.ts +33 -0
- package/src/api/age-assurance/kws/external-payload.test.ts +72 -0
- package/src/api/age-assurance/kws/external-payload.ts +149 -0
- package/src/api/age-assurance/redirects/kws-age-verified.ts +107 -0
- package/src/api/age-assurance/stash.ts +22 -0
- package/src/api/age-assurance/types.ts +10 -0
- package/src/api/age-assurance/util.ts +66 -0
- package/src/api/age-assurance/webhooks/kws-age-verified.ts +75 -0
- package/src/api/app/bsky/ageassurance/begin.ts +167 -0
- package/src/api/app/bsky/ageassurance/getConfig.ts +15 -0
- package/src/api/app/bsky/ageassurance/getState.ts +53 -0
- package/src/api/external.ts +2 -0
- package/src/api/index.ts +6 -0
- package/src/api/kws/api.ts +55 -34
- package/src/api/kws/index.ts +7 -1
- package/src/api/kws/webhook.ts +57 -34
- package/src/config.ts +26 -2
- package/src/data-plane/bsync/index.ts +31 -0
- package/src/data-plane/server/db/migrations/20251120T004738098Z-update-actor-age-assurance-v2.ts +28 -0
- package/src/data-plane/server/db/migrations/index.ts +1 -0
- package/src/data-plane/server/db/tables/actor.ts +3 -0
- package/src/data-plane/server/routes/profile.ts +12 -1
- package/src/hydration/hydrator.ts +1 -1
- package/src/kws.ts +81 -0
- package/src/logger.ts +2 -0
- package/src/proto/bsky_pb.ts +12 -0
- package/src/stash.ts +3 -0
- package/tests/views/age-assurance-v2.test.ts +745 -0
- package/tests/views/age-assurance.test.ts +2 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/tsconfig.tests.tsbuildinfo +1 -1
package/src/api/kws/index.ts
CHANGED
|
@@ -9,7 +9,13 @@ export const createRouter = (ctx: AppContext): Router => {
|
|
|
9
9
|
|
|
10
10
|
const router = Router()
|
|
11
11
|
router.use(raw({ type: 'application/json' }))
|
|
12
|
-
router.post(
|
|
12
|
+
router.post(
|
|
13
|
+
'/age-assurance-webhook',
|
|
14
|
+
webhookAuth({
|
|
15
|
+
secret: ctx.cfg.kws.webhookSecret,
|
|
16
|
+
}),
|
|
17
|
+
webhookHandler(ctx),
|
|
18
|
+
)
|
|
13
19
|
router.get('/age-assurance-verification', verificationHandler(ctx))
|
|
14
20
|
return router
|
|
15
21
|
}
|
package/src/api/kws/webhook.ts
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
import express, { RequestHandler } from 'express'
|
|
2
2
|
import { httpLogger as log } from '../../logger'
|
|
3
|
+
import { AGE_ASSURANCE_CONFIG } from '../age-assurance/const'
|
|
4
|
+
import {
|
|
5
|
+
KWSExternalPayloadVersion,
|
|
6
|
+
parseKWSExternalPayloadV1WithV2Compat,
|
|
7
|
+
} from '../age-assurance/kws/external-payload'
|
|
8
|
+
import { createEvent } from '../age-assurance/stash'
|
|
9
|
+
import { computeAgeAssuranceAccessOrThrow } from '../age-assurance/util'
|
|
3
10
|
import {
|
|
4
11
|
AppContextWithKwsClient,
|
|
5
12
|
KwsWebhookBody,
|
|
6
13
|
webhookBodyIntermediateSchema,
|
|
7
14
|
} from './types'
|
|
8
|
-
import {
|
|
9
|
-
createStashEvent,
|
|
10
|
-
kwsWwwAuthenticate,
|
|
11
|
-
parseExternalPayload,
|
|
12
|
-
validateSignature,
|
|
13
|
-
} from './util'
|
|
15
|
+
import { createStashEvent, kwsWwwAuthenticate, validateSignature } from './util'
|
|
14
16
|
|
|
15
17
|
export const webhookAuth =
|
|
16
|
-
(
|
|
18
|
+
({ secret }: { secret: string }): RequestHandler =>
|
|
17
19
|
(req: express.Request, res: express.Response, next: express.NextFunction) => {
|
|
18
20
|
const body: Buffer = req.body
|
|
19
21
|
const sigHeader = req.headers['x-kws-signature']
|
|
@@ -34,7 +36,7 @@ export const webhookAuth =
|
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
const data = `${timestamp}.${body}`
|
|
37
|
-
validateSignature(
|
|
39
|
+
validateSignature(secret, data, signature)
|
|
38
40
|
next()
|
|
39
41
|
} catch (err) {
|
|
40
42
|
log.error({ err }, 'Invalid KWS webhook signature')
|
|
@@ -51,21 +53,10 @@ type AgeAssuranceWebhookIntermediateBody = {
|
|
|
51
53
|
}
|
|
52
54
|
}
|
|
53
55
|
|
|
54
|
-
const parseBody = (serialized: string):
|
|
56
|
+
const parseBody = (serialized: string): AgeAssuranceWebhookIntermediateBody => {
|
|
55
57
|
try {
|
|
56
58
|
const value: unknown = JSON.parse(serialized)
|
|
57
|
-
|
|
58
|
-
webhookBodyIntermediateSchema.parse(value)
|
|
59
|
-
|
|
60
|
-
return {
|
|
61
|
-
...intermediate,
|
|
62
|
-
payload: {
|
|
63
|
-
...intermediate.payload,
|
|
64
|
-
externalPayload: parseExternalPayload(
|
|
65
|
-
intermediate.payload.externalPayload,
|
|
66
|
-
),
|
|
67
|
-
},
|
|
68
|
-
}
|
|
59
|
+
return webhookBodyIntermediateSchema.parse(value)
|
|
69
60
|
} catch (err) {
|
|
70
61
|
throw new Error(`Invalid webhook body: ${serialized}`, { cause: err })
|
|
71
62
|
}
|
|
@@ -74,7 +65,7 @@ const parseBody = (serialized: string): KwsWebhookBody => {
|
|
|
74
65
|
export const webhookHandler =
|
|
75
66
|
(ctx: AppContextWithKwsClient): RequestHandler =>
|
|
76
67
|
async (req: express.Request, res: express.Response) => {
|
|
77
|
-
let body:
|
|
68
|
+
let body: AgeAssuranceWebhookIntermediateBody
|
|
78
69
|
try {
|
|
79
70
|
body = parseBody(req.body)
|
|
80
71
|
} catch (err) {
|
|
@@ -82,23 +73,55 @@ export const webhookHandler =
|
|
|
82
73
|
return res.status(400).json(err)
|
|
83
74
|
}
|
|
84
75
|
|
|
85
|
-
const {
|
|
86
|
-
payload: {
|
|
87
|
-
status: { verified },
|
|
88
|
-
externalPayload,
|
|
89
|
-
},
|
|
90
|
-
} = body
|
|
91
|
-
const { actorDid, attemptId } = externalPayload
|
|
76
|
+
const { verified } = body.payload.status
|
|
92
77
|
if (!verified) {
|
|
93
78
|
throw new Error('Unexpected KWS webhook call with unverified status')
|
|
94
79
|
}
|
|
95
80
|
|
|
81
|
+
const externalPayload = parseKWSExternalPayloadV1WithV2Compat(
|
|
82
|
+
body.payload.externalPayload,
|
|
83
|
+
)
|
|
84
|
+
const isV2 = externalPayload.version === KWSExternalPayloadVersion.V2
|
|
85
|
+
|
|
86
|
+
let result: ReturnType<typeof computeAgeAssuranceAccessOrThrow> | undefined
|
|
87
|
+
if (isV2) {
|
|
88
|
+
const { attemptId, actorDid, countryCode, regionCode } = externalPayload
|
|
89
|
+
try {
|
|
90
|
+
result = computeAgeAssuranceAccessOrThrow(AGE_ASSURANCE_CONFIG, {
|
|
91
|
+
countryCode: countryCode,
|
|
92
|
+
regionCode: regionCode,
|
|
93
|
+
verifiedMinimumAge: 18, // `adult-verified` is 18+ only
|
|
94
|
+
})
|
|
95
|
+
} catch (err) {
|
|
96
|
+
// internal errors
|
|
97
|
+
log.error(
|
|
98
|
+
{ err, attemptId, actorDid, countryCode, regionCode },
|
|
99
|
+
'Failed to compute age assurance access',
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
96
104
|
try {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
105
|
+
if (isV2) {
|
|
106
|
+
if (result) {
|
|
107
|
+
const { attemptId, actorDid, countryCode, regionCode } =
|
|
108
|
+
externalPayload
|
|
109
|
+
await createEvent(ctx, actorDid, {
|
|
110
|
+
attemptId,
|
|
111
|
+
status: 'assured',
|
|
112
|
+
access: result.access,
|
|
113
|
+
countryCode,
|
|
114
|
+
regionCode,
|
|
115
|
+
})
|
|
116
|
+
} // else do nothing
|
|
117
|
+
} else {
|
|
118
|
+
const { attemptId, actorDid } = externalPayload
|
|
119
|
+
await createStashEvent(ctx, {
|
|
120
|
+
attemptId: attemptId,
|
|
121
|
+
actorDid: actorDid,
|
|
122
|
+
status: 'assured',
|
|
123
|
+
})
|
|
124
|
+
}
|
|
102
125
|
return res.status(200).end()
|
|
103
126
|
} catch (err) {
|
|
104
127
|
log.error({ err }, 'Failed to handle KWS webhook')
|
package/src/config.ts
CHANGED
|
@@ -13,8 +13,22 @@ export interface KwsConfig {
|
|
|
13
13
|
clientId: string
|
|
14
14
|
redirectUrl: string
|
|
15
15
|
userAgent: string
|
|
16
|
+
/**
|
|
17
|
+
* V1 secret used to validate `adult-verifieid` redirects
|
|
18
|
+
*/
|
|
16
19
|
verificationSecret: string
|
|
20
|
+
/**
|
|
21
|
+
* V1 secret used to validate `adult-verified` webhooks
|
|
22
|
+
*/
|
|
17
23
|
webhookSecret: string
|
|
24
|
+
/**
|
|
25
|
+
* V2 secret used to validate `age-verified` webhooks
|
|
26
|
+
*/
|
|
27
|
+
ageVerifiedWebhookSecret: string
|
|
28
|
+
/**
|
|
29
|
+
* V2 secret used to validate `age-verified` redirects
|
|
30
|
+
*/
|
|
31
|
+
ageVerifiedRedirectSecret: string
|
|
18
32
|
}
|
|
19
33
|
|
|
20
34
|
export interface ServerConfigValues {
|
|
@@ -251,6 +265,10 @@ export class ServerConfig {
|
|
|
251
265
|
const kwsUserAgent = process.env.BSKY_KWS_USER_AGENT
|
|
252
266
|
const kwsVerificationSecret = process.env.BSKY_KWS_VERIFICATION_SECRET
|
|
253
267
|
const kwsWebhookSecret = process.env.BSKY_KWS_WEBHOOK_SECRET
|
|
268
|
+
const kwsAgeVerifiedWebhookSecret =
|
|
269
|
+
process.env.BSKY_KWS_AGE_VERIFIED_WEBHOOK_SECRET
|
|
270
|
+
const kwsAgeVerifiedRedirectSecret =
|
|
271
|
+
process.env.BSKY_KWS_AGE_VERIFIED_REDIRECT_SECRET
|
|
254
272
|
if (
|
|
255
273
|
kwsApiKey ||
|
|
256
274
|
kwsApiOrigin ||
|
|
@@ -259,7 +277,9 @@ export class ServerConfig {
|
|
|
259
277
|
kwsRedirectUrl ||
|
|
260
278
|
kwsUserAgent ||
|
|
261
279
|
kwsVerificationSecret ||
|
|
262
|
-
kwsWebhookSecret
|
|
280
|
+
kwsWebhookSecret ||
|
|
281
|
+
kwsAgeVerifiedWebhookSecret ||
|
|
282
|
+
kwsAgeVerifiedRedirectSecret
|
|
263
283
|
) {
|
|
264
284
|
assert(
|
|
265
285
|
kwsApiOrigin &&
|
|
@@ -269,7 +289,9 @@ export class ServerConfig {
|
|
|
269
289
|
kwsUserAgent &&
|
|
270
290
|
kwsVerificationSecret &&
|
|
271
291
|
kwsWebhookSecret &&
|
|
272
|
-
kwsApiKey
|
|
292
|
+
kwsApiKey &&
|
|
293
|
+
kwsAgeVerifiedWebhookSecret &&
|
|
294
|
+
kwsAgeVerifiedRedirectSecret,
|
|
273
295
|
'all KWS environment variables must be set if any are set',
|
|
274
296
|
)
|
|
275
297
|
kws = {
|
|
@@ -281,6 +303,8 @@ export class ServerConfig {
|
|
|
281
303
|
userAgent: kwsUserAgent,
|
|
282
304
|
verificationSecret: kwsVerificationSecret,
|
|
283
305
|
webhookSecret: kwsWebhookSecret,
|
|
306
|
+
ageVerifiedWebhookSecret: kwsAgeVerifiedWebhookSecret,
|
|
307
|
+
ageVerifiedRedirectSecret: kwsAgeVerifiedRedirectSecret,
|
|
284
308
|
}
|
|
285
309
|
}
|
|
286
310
|
|
|
@@ -8,6 +8,7 @@ import { TID } from '@atproto/common'
|
|
|
8
8
|
import { jsonStringToLex } from '@atproto/lexicon'
|
|
9
9
|
import { AtUri } from '@atproto/syntax'
|
|
10
10
|
import { ids } from '../../lexicon/lexicons'
|
|
11
|
+
import { Event as AgeAssuranceV2Event } from '../../lexicon/types/app/bsky/ageassurance/defs'
|
|
11
12
|
import { Bookmark } from '../../lexicon/types/app/bsky/bookmark/defs'
|
|
12
13
|
import { SubjectActivitySubscription } from '../../lexicon/types/app/bsky/notification/defs'
|
|
13
14
|
import { AgeAssuranceEvent } from '../../lexicon/types/app/bsky/unspecced/defs'
|
|
@@ -175,6 +176,8 @@ const createRoutes = (db: Database) => (router: ConnectRouter) =>
|
|
|
175
176
|
namespace === Namespaces.AppBskyUnspeccedDefsAgeAssuranceEvent
|
|
176
177
|
) {
|
|
177
178
|
await handleAgeAssuranceEventOperation(db, req, now)
|
|
179
|
+
} else if (namespace === Namespaces.AppBskyAgeassuranceDefsEvent) {
|
|
180
|
+
await handleAgeAssuranceV2EventOperation(db, req, now)
|
|
178
181
|
} else if (namespace === Namespaces.AppBskyBookmarkDefsBookmark) {
|
|
179
182
|
await handleBookmarkOperation(db, req, now)
|
|
180
183
|
}
|
|
@@ -314,6 +317,34 @@ const handleAgeAssuranceEventOperation = async (
|
|
|
314
317
|
.execute()
|
|
315
318
|
}
|
|
316
319
|
|
|
320
|
+
const handleAgeAssuranceV2EventOperation = async (
|
|
321
|
+
db: Database,
|
|
322
|
+
req: PutOperationRequest,
|
|
323
|
+
_now: string,
|
|
324
|
+
) => {
|
|
325
|
+
const { actorDid, method, payload } = req
|
|
326
|
+
if (method !== Method.CREATE) return
|
|
327
|
+
|
|
328
|
+
const parsed = jsonStringToLex(
|
|
329
|
+
Buffer.from(payload).toString('utf8'),
|
|
330
|
+
) as AgeAssuranceV2Event
|
|
331
|
+
const { status, createdAt, access, countryCode, regionCode } = parsed
|
|
332
|
+
|
|
333
|
+
const update = {
|
|
334
|
+
ageAssuranceStatus: status,
|
|
335
|
+
ageAssuranceLastInitiatedAt: status === 'pending' ? createdAt : undefined,
|
|
336
|
+
ageAssuranceAccess: access,
|
|
337
|
+
ageAssuranceCountryCode: countryCode,
|
|
338
|
+
ageAssuranceRegionCode: regionCode,
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return db.db
|
|
342
|
+
.updateTable('actor')
|
|
343
|
+
.set(update)
|
|
344
|
+
.where('did', '=', actorDid)
|
|
345
|
+
.execute()
|
|
346
|
+
}
|
|
347
|
+
|
|
317
348
|
const handleBookmarkOperation = async (
|
|
318
349
|
db: Database,
|
|
319
350
|
req: PutOperationRequest,
|
package/src/data-plane/server/db/migrations/20251120T004738098Z-update-actor-age-assurance-v2.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
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('ageAssuranceAccess', 'text')
|
|
7
|
+
.execute()
|
|
8
|
+
await db.schema
|
|
9
|
+
.alterTable('actor')
|
|
10
|
+
.addColumn('ageAssuranceCountryCode', 'text')
|
|
11
|
+
.execute()
|
|
12
|
+
await db.schema
|
|
13
|
+
.alterTable('actor')
|
|
14
|
+
.addColumn('ageAssuranceRegionCode', 'text')
|
|
15
|
+
.execute()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
19
|
+
await db.schema.alterTable('actor').dropColumn('ageAssuranceAccess').execute()
|
|
20
|
+
await db.schema
|
|
21
|
+
.alterTable('actor')
|
|
22
|
+
.dropColumn('ageAssuranceCountryCode')
|
|
23
|
+
.execute()
|
|
24
|
+
await db.schema
|
|
25
|
+
.alterTable('actor')
|
|
26
|
+
.dropColumn('ageAssuranceRegionCode')
|
|
27
|
+
.execute()
|
|
28
|
+
}
|
|
@@ -55,3 +55,4 @@ export * as _20250611T140649895Z from './20250611T140649895Z-add-activity-subscr
|
|
|
55
55
|
export * as _20250627T025331240Z from './20250627T025331240Z-add-actor-age-assurance-columns'
|
|
56
56
|
export * as _20250812T183735692Z from './20250812T183735692Z-add-bookmarks'
|
|
57
57
|
export * as _20250813T174955711Z from './20250813T174955711Z-add-post-agg-bookmarks'
|
|
58
|
+
export * as _20251120T004738098Z from './20251120T004738098Z-update-actor-age-assurance-v2'
|
|
@@ -9,6 +9,9 @@ export interface Actor {
|
|
|
9
9
|
trustedVerifier: Generated<boolean>
|
|
10
10
|
ageAssuranceStatus: string | null
|
|
11
11
|
ageAssuranceLastInitiatedAt: string | null
|
|
12
|
+
ageAssuranceAccess: string | null
|
|
13
|
+
ageAssuranceCountryCode: string | null
|
|
14
|
+
ageAssuranceRegionCode: string | null
|
|
12
15
|
}
|
|
13
16
|
|
|
14
17
|
export const tableName = 'actor'
|
|
@@ -134,11 +134,22 @@ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
|
|
|
134
134
|
return undefined
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
+
const status = row?.ageAssuranceStatus ?? 'unknown'
|
|
138
|
+
let access = row?.ageAssuranceAccess
|
|
139
|
+
if (status === 'assured') {
|
|
140
|
+
access = 'full'
|
|
141
|
+
} else if (status === 'blocked') {
|
|
142
|
+
access = 'none'
|
|
143
|
+
} else {
|
|
144
|
+
access = 'unknown'
|
|
145
|
+
}
|
|
146
|
+
|
|
137
147
|
return {
|
|
138
|
-
status: row?.ageAssuranceStatus ?? 'unknown',
|
|
139
148
|
lastInitiatedAt: row?.ageAssuranceLastInitiatedAt
|
|
140
149
|
? Timestamp.fromDate(new Date(row?.ageAssuranceLastInitiatedAt))
|
|
141
150
|
: undefined,
|
|
151
|
+
status,
|
|
152
|
+
access,
|
|
142
153
|
}
|
|
143
154
|
}
|
|
144
155
|
|
package/src/kws.ts
CHANGED
|
@@ -15,8 +15,30 @@ const authResponseSchema = z.object({
|
|
|
15
15
|
})
|
|
16
16
|
|
|
17
17
|
const EXTERNAL_PAYLOAD_CHAR_LIMIT = 200
|
|
18
|
+
/**
|
|
19
|
+
* Thrown when the provided external payload exceeds KWS's character limit.
|
|
20
|
+
* This is most commonly caused by DIDs that are too long, such as for
|
|
21
|
+
* `did:web` DIDs. But it's very rare, and the client has special handling for
|
|
22
|
+
* this case.
|
|
23
|
+
*/
|
|
18
24
|
export class KwsExternalPayloadError extends Error {}
|
|
19
25
|
|
|
26
|
+
export type KWSSendEmailRequestCommon = {
|
|
27
|
+
email: string
|
|
28
|
+
location: string
|
|
29
|
+
language: string
|
|
30
|
+
externalPayload: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type KWSSendEmailRequest =
|
|
34
|
+
| (KWSSendEmailRequestCommon & {
|
|
35
|
+
userContext: 'adult'
|
|
36
|
+
})
|
|
37
|
+
| (KWSSendEmailRequestCommon & {
|
|
38
|
+
userContext: 'age'
|
|
39
|
+
minimumAge: number
|
|
40
|
+
})
|
|
41
|
+
|
|
20
42
|
export class KwsClient {
|
|
21
43
|
constructor(public cfg: KwsConfig) {}
|
|
22
44
|
|
|
@@ -68,6 +90,9 @@ export class KwsClient {
|
|
|
68
90
|
})
|
|
69
91
|
}
|
|
70
92
|
|
|
93
|
+
/**
|
|
94
|
+
* @deprecated Use `sendAdultVerifiedFlowEmail` or `sendAgeVerifiedFlowEmail` instead.
|
|
95
|
+
*/
|
|
71
96
|
async sendEmail({
|
|
72
97
|
countryCode,
|
|
73
98
|
email,
|
|
@@ -113,4 +138,60 @@ export class KwsClient {
|
|
|
113
138
|
|
|
114
139
|
return res.json()
|
|
115
140
|
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Sends a KWS verification email with the given properties.
|
|
144
|
+
*/
|
|
145
|
+
async email(props: KWSSendEmailRequest) {
|
|
146
|
+
const res = await this.fetchWithAuth(
|
|
147
|
+
`${this.cfg.apiOrigin}/v1/verifications/send-email`,
|
|
148
|
+
{
|
|
149
|
+
method: 'POST',
|
|
150
|
+
headers: {
|
|
151
|
+
'Content-Type': 'application/json',
|
|
152
|
+
'User-Agent': this.cfg.userAgent,
|
|
153
|
+
},
|
|
154
|
+
body: JSON.stringify(props),
|
|
155
|
+
},
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
if (!res.ok) {
|
|
159
|
+
const errorText = await res.text()
|
|
160
|
+
log.error(
|
|
161
|
+
{
|
|
162
|
+
status: res.status,
|
|
163
|
+
statusText: res.statusText,
|
|
164
|
+
errorText,
|
|
165
|
+
flow: props.userContext,
|
|
166
|
+
},
|
|
167
|
+
'Failed to send KWS email',
|
|
168
|
+
)
|
|
169
|
+
throw new Error('Failed to send KWS email')
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return res.json()
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Sends an email to the user initiating an `adult` verification flow, which
|
|
177
|
+
* results in `adult-verified` events/webhooks.
|
|
178
|
+
*/
|
|
179
|
+
async sendAdultVerifiedFlowEmail(props: KWSSendEmailRequestCommon) {
|
|
180
|
+
return this.email({
|
|
181
|
+
...props,
|
|
182
|
+
userContext: 'adult',
|
|
183
|
+
})
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Sends an email to the user initiating an `age` verification flow, which
|
|
188
|
+
* results in `age-verified` events/webhooks.
|
|
189
|
+
*/
|
|
190
|
+
async sendAgeVerifiedFlowEmail(props: KWSSendEmailRequestCommon) {
|
|
191
|
+
return this.email({
|
|
192
|
+
...props,
|
|
193
|
+
userContext: 'age',
|
|
194
|
+
minimumAge: 16, // KWS required value
|
|
195
|
+
})
|
|
196
|
+
}
|
|
116
197
|
}
|
package/src/logger.ts
CHANGED
|
@@ -17,6 +17,8 @@ export const featureGatesLogger: ReturnType<typeof subsystemLogger> =
|
|
|
17
17
|
subsystemLogger('bsky:featuregates')
|
|
18
18
|
export const dataplaneLogger: ReturnType<typeof subsystemLogger> =
|
|
19
19
|
subsystemLogger('bsky:dp')
|
|
20
|
+
export const ageAssuranceLogger: ReturnType<typeof subsystemLogger> =
|
|
21
|
+
subsystemLogger('bsky:aa')
|
|
20
22
|
export const httpLogger: ReturnType<typeof subsystemLogger> =
|
|
21
23
|
subsystemLogger('bsky')
|
|
22
24
|
|
package/src/proto/bsky_pb.ts
CHANGED
|
@@ -3856,6 +3856,16 @@ export class AgeAssuranceStatus extends Message<AgeAssuranceStatus> {
|
|
|
3856
3856
|
*/
|
|
3857
3857
|
overrideApplied = false;
|
|
3858
3858
|
|
|
3859
|
+
/**
|
|
3860
|
+
* @generated from field: string access = 4;
|
|
3861
|
+
*/
|
|
3862
|
+
access = "";
|
|
3863
|
+
|
|
3864
|
+
/**
|
|
3865
|
+
* @generated from field: string access_reason = 5;
|
|
3866
|
+
*/
|
|
3867
|
+
accessReason = "";
|
|
3868
|
+
|
|
3859
3869
|
constructor(data?: PartialMessage<AgeAssuranceStatus>) {
|
|
3860
3870
|
super();
|
|
3861
3871
|
proto3.util.initPartial(data, this);
|
|
@@ -3867,6 +3877,8 @@ export class AgeAssuranceStatus extends Message<AgeAssuranceStatus> {
|
|
|
3867
3877
|
{ no: 1, name: "status", kind: "scalar", T: 9 /* ScalarType.STRING */ },
|
|
3868
3878
|
{ no: 2, name: "last_initiated_at", kind: "message", T: Timestamp },
|
|
3869
3879
|
{ no: 3, name: "override_applied", kind: "scalar", T: 8 /* ScalarType.BOOL */ },
|
|
3880
|
+
{ no: 4, name: "access", kind: "scalar", T: 9 /* ScalarType.STRING */ },
|
|
3881
|
+
{ no: 5, name: "access_reason", kind: "scalar", T: 9 /* ScalarType.STRING */ },
|
|
3870
3882
|
]);
|
|
3871
3883
|
|
|
3872
3884
|
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): AgeAssuranceStatus {
|
package/src/stash.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { LexValue, stringifyLex } from '@atproto/lexicon'
|
|
4
4
|
import { BsyncClient } from './bsync'
|
|
5
5
|
import { lexicons } from './lexicon/lexicons'
|
|
6
|
+
import { Event as AgeAssuranceEventV2 } from './lexicon/types/app/bsky/ageassurance/defs'
|
|
6
7
|
import { Bookmark } from './lexicon/types/app/bsky/bookmark/defs'
|
|
7
8
|
import {
|
|
8
9
|
Preferences,
|
|
@@ -22,6 +23,8 @@ export const Namespaces = {
|
|
|
22
23
|
'app.bsky.notification.defs#subjectActivitySubscription' satisfies PickNSID<SubjectActivitySubscription>,
|
|
23
24
|
AppBskyUnspeccedDefsAgeAssuranceEvent:
|
|
24
25
|
'app.bsky.unspecced.defs#ageAssuranceEvent' satisfies PickNSID<AgeAssuranceEvent>,
|
|
26
|
+
AppBskyAgeassuranceDefsEvent:
|
|
27
|
+
'app.bsky.ageassurance.defs#event' satisfies PickNSID<AgeAssuranceEventV2>,
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
export type Namespace = (typeof Namespaces)[keyof typeof Namespaces]
|