@atproto/bsky 0.0.198 → 0.0.200

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.
Files changed (279) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/api/age-assurance/const.d.ts +11 -0
  3. package/dist/api/age-assurance/const.d.ts.map +1 -0
  4. package/dist/api/age-assurance/const.js +142 -0
  5. package/dist/api/age-assurance/const.js.map +1 -0
  6. package/dist/api/age-assurance/index.d.ts +4 -0
  7. package/dist/api/age-assurance/index.d.ts.map +1 -0
  8. package/dist/api/age-assurance/index.js +24 -0
  9. package/dist/api/age-assurance/index.js.map +1 -0
  10. package/dist/api/age-assurance/kws/age-verified.d.ts +109 -0
  11. package/dist/api/age-assurance/kws/age-verified.d.ts.map +1 -0
  12. package/dist/api/age-assurance/kws/age-verified.js +63 -0
  13. package/dist/api/age-assurance/kws/age-verified.js.map +1 -0
  14. package/dist/api/age-assurance/kws/const.d.ts +13 -0
  15. package/dist/api/age-assurance/kws/const.d.ts.map +1 -0
  16. package/dist/api/age-assurance/kws/const.js +36 -0
  17. package/dist/api/age-assurance/kws/const.js.map +1 -0
  18. package/dist/api/age-assurance/kws/external-payload.d.ts +75 -0
  19. package/dist/api/age-assurance/kws/external-payload.d.ts.map +1 -0
  20. package/dist/api/age-assurance/kws/external-payload.js +124 -0
  21. package/dist/api/age-assurance/kws/external-payload.js.map +1 -0
  22. package/dist/api/age-assurance/kws/external-payload.test.d.ts +2 -0
  23. package/dist/api/age-assurance/kws/external-payload.test.d.ts.map +1 -0
  24. package/dist/api/age-assurance/kws/external-payload.test.js +65 -0
  25. package/dist/api/age-assurance/kws/external-payload.test.js.map +1 -0
  26. package/dist/api/age-assurance/redirects/kws-age-verified.d.ts +4 -0
  27. package/dist/api/age-assurance/redirects/kws-age-verified.d.ts.map +1 -0
  28. package/dist/api/age-assurance/redirects/kws-age-verified.js +76 -0
  29. package/dist/api/age-assurance/redirects/kws-age-verified.js.map +1 -0
  30. package/dist/api/age-assurance/stash.d.ts +4 -0
  31. package/dist/api/age-assurance/stash.d.ts.map +1 -0
  32. package/dist/api/age-assurance/stash.js +19 -0
  33. package/dist/api/age-assurance/stash.js.map +1 -0
  34. package/dist/api/age-assurance/types.d.ts +10 -0
  35. package/dist/api/age-assurance/types.d.ts.map +1 -0
  36. package/dist/api/age-assurance/types.js +3 -0
  37. package/dist/api/age-assurance/types.js.map +1 -0
  38. package/dist/api/age-assurance/util.d.ts +15 -0
  39. package/dist/api/age-assurance/util.d.ts.map +1 -0
  40. package/dist/api/age-assurance/util.js +54 -0
  41. package/dist/api/age-assurance/util.js.map +1 -0
  42. package/dist/api/age-assurance/webhooks/kws-age-verified.d.ts +4 -0
  43. package/dist/api/age-assurance/webhooks/kws-age-verified.d.ts.map +1 -0
  44. package/dist/api/age-assurance/webhooks/kws-age-verified.js +63 -0
  45. package/dist/api/age-assurance/webhooks/kws-age-verified.js.map +1 -0
  46. package/dist/api/app/bsky/ageassurance/begin.d.ts +4 -0
  47. package/dist/api/app/bsky/ageassurance/begin.d.ts.map +1 -0
  48. package/dist/api/app/bsky/ageassurance/begin.js +131 -0
  49. package/dist/api/app/bsky/ageassurance/begin.js.map +1 -0
  50. package/dist/api/app/bsky/ageassurance/getConfig.d.ts +4 -0
  51. package/dist/api/app/bsky/ageassurance/getConfig.d.ts.map +1 -0
  52. package/dist/api/app/bsky/ageassurance/getConfig.js +16 -0
  53. package/dist/api/app/bsky/ageassurance/getConfig.js.map +1 -0
  54. package/dist/api/app/bsky/ageassurance/getState.d.ts +4 -0
  55. package/dist/api/app/bsky/ageassurance/getState.d.ts.map +1 -0
  56. package/dist/api/app/bsky/ageassurance/getState.js +42 -0
  57. package/dist/api/app/bsky/ageassurance/getState.js.map +1 -0
  58. package/dist/api/app/bsky/contact/dismissMatch.d.ts +4 -0
  59. package/dist/api/app/bsky/contact/dismissMatch.d.ts.map +1 -0
  60. package/dist/api/app/bsky/contact/dismissMatch.js +23 -0
  61. package/dist/api/app/bsky/contact/dismissMatch.js.map +1 -0
  62. package/dist/api/app/bsky/contact/getMatches.d.ts +4 -0
  63. package/dist/api/app/bsky/contact/getMatches.d.ts.map +1 -0
  64. package/dist/api/app/bsky/contact/getMatches.js +59 -0
  65. package/dist/api/app/bsky/contact/getMatches.js.map +1 -0
  66. package/dist/api/app/bsky/contact/getSyncStatus.d.ts +4 -0
  67. package/dist/api/app/bsky/contact/getSyncStatus.d.ts.map +1 -0
  68. package/dist/api/app/bsky/contact/getSyncStatus.js +32 -0
  69. package/dist/api/app/bsky/contact/getSyncStatus.js.map +1 -0
  70. package/dist/api/app/bsky/contact/importContacts.d.ts +4 -0
  71. package/dist/api/app/bsky/contact/importContacts.d.ts.map +1 -0
  72. package/dist/api/app/bsky/contact/importContacts.js +62 -0
  73. package/dist/api/app/bsky/contact/importContacts.js.map +1 -0
  74. package/dist/api/app/bsky/contact/removeData.d.ts +4 -0
  75. package/dist/api/app/bsky/contact/removeData.d.ts.map +1 -0
  76. package/dist/api/app/bsky/contact/removeData.js +22 -0
  77. package/dist/api/app/bsky/contact/removeData.js.map +1 -0
  78. package/dist/api/app/bsky/contact/startPhoneVerification.d.ts +4 -0
  79. package/dist/api/app/bsky/contact/startPhoneVerification.d.ts.map +1 -0
  80. package/dist/api/app/bsky/contact/startPhoneVerification.js +23 -0
  81. package/dist/api/app/bsky/contact/startPhoneVerification.js.map +1 -0
  82. package/dist/api/app/bsky/contact/util.d.ts +6 -0
  83. package/dist/api/app/bsky/contact/util.d.ts.map +1 -0
  84. package/dist/api/app/bsky/contact/util.js +10 -0
  85. package/dist/api/app/bsky/contact/util.js.map +1 -0
  86. package/dist/api/app/bsky/contact/verifyPhone.d.ts +4 -0
  87. package/dist/api/app/bsky/contact/verifyPhone.d.ts.map +1 -0
  88. package/dist/api/app/bsky/contact/verifyPhone.js +26 -0
  89. package/dist/api/app/bsky/contact/verifyPhone.js.map +1 -0
  90. package/dist/api/app/bsky/graph/getRelationships.d.ts.map +1 -1
  91. package/dist/api/app/bsky/graph/getRelationships.js +4 -0
  92. package/dist/api/app/bsky/graph/getRelationships.js.map +1 -1
  93. package/dist/api/external.d.ts.map +1 -1
  94. package/dist/api/external.js +2 -0
  95. package/dist/api/external.js.map +1 -1
  96. package/dist/api/index.d.ts.map +1 -1
  97. package/dist/api/index.js +22 -2
  98. package/dist/api/index.js.map +1 -1
  99. package/dist/api/kws/api.d.ts.map +1 -1
  100. package/dist/api/kws/api.js +44 -26
  101. package/dist/api/kws/api.js.map +1 -1
  102. package/dist/api/kws/index.d.ts.map +1 -1
  103. package/dist/api/kws/index.js +3 -1
  104. package/dist/api/kws/index.js.map +1 -1
  105. package/dist/api/kws/webhook.d.ts +3 -1
  106. package/dist/api/kws/webhook.d.ts.map +1 -1
  107. package/dist/api/kws/webhook.js +48 -20
  108. package/dist/api/kws/webhook.js.map +1 -1
  109. package/dist/config.d.ts +22 -0
  110. package/dist/config.d.ts.map +1 -1
  111. package/dist/config.js +31 -2
  112. package/dist/config.js.map +1 -1
  113. package/dist/context.d.ts +3 -0
  114. package/dist/context.d.ts.map +1 -1
  115. package/dist/context.js +3 -0
  116. package/dist/context.js.map +1 -1
  117. package/dist/data-plane/bsync/index.d.ts.map +1 -1
  118. package/dist/data-plane/bsync/index.js +22 -0
  119. package/dist/data-plane/bsync/index.js.map +1 -1
  120. package/dist/data-plane/server/db/migrations/20251120T004738098Z-update-actor-age-assurance-v2.d.ts +4 -0
  121. package/dist/data-plane/server/db/migrations/20251120T004738098Z-update-actor-age-assurance-v2.d.ts.map +1 -0
  122. package/dist/data-plane/server/db/migrations/20251120T004738098Z-update-actor-age-assurance-v2.js +30 -0
  123. package/dist/data-plane/server/db/migrations/20251120T004738098Z-update-actor-age-assurance-v2.js.map +1 -0
  124. package/dist/data-plane/server/db/migrations/index.d.ts +1 -0
  125. package/dist/data-plane/server/db/migrations/index.d.ts.map +1 -1
  126. package/dist/data-plane/server/db/migrations/index.js +2 -1
  127. package/dist/data-plane/server/db/migrations/index.js.map +1 -1
  128. package/dist/data-plane/server/db/pagination.d.ts +3 -3
  129. package/dist/data-plane/server/db/tables/actor.d.ts +3 -0
  130. package/dist/data-plane/server/db/tables/actor.d.ts.map +1 -1
  131. package/dist/data-plane/server/db/tables/actor.js.map +1 -1
  132. package/dist/data-plane/server/routes/profile.d.ts.map +1 -1
  133. package/dist/data-plane/server/routes/profile.js +13 -1
  134. package/dist/data-plane/server/routes/profile.js.map +1 -1
  135. package/dist/hydration/actor.js +1 -1
  136. package/dist/hydration/actor.js.map +1 -1
  137. package/dist/hydration/hydrator.js +1 -1
  138. package/dist/hydration/hydrator.js.map +1 -1
  139. package/dist/index.d.ts.map +1 -1
  140. package/dist/index.js +12 -0
  141. package/dist/index.js.map +1 -1
  142. package/dist/kws.d.ts +35 -0
  143. package/dist/kws.d.ts.map +1 -1
  144. package/dist/kws.js +54 -0
  145. package/dist/kws.js.map +1 -1
  146. package/dist/lexicon/index.d.ts +19 -0
  147. package/dist/lexicon/index.d.ts.map +1 -1
  148. package/dist/lexicon/index.js +48 -1
  149. package/dist/lexicon/index.js.map +1 -1
  150. package/dist/lexicon/lexicons.d.ts +664 -0
  151. package/dist/lexicon/lexicons.d.ts.map +1 -1
  152. package/dist/lexicon/lexicons.js +354 -0
  153. package/dist/lexicon/lexicons.js.map +1 -1
  154. package/dist/lexicon/types/app/bsky/contact/defs.d.ts +24 -0
  155. package/dist/lexicon/types/app/bsky/contact/defs.d.ts.map +1 -0
  156. package/dist/lexicon/types/app/bsky/contact/defs.js +25 -0
  157. package/dist/lexicon/types/app/bsky/contact/defs.js.map +1 -0
  158. package/dist/lexicon/types/app/bsky/contact/dismissMatch.d.ts +25 -0
  159. package/dist/lexicon/types/app/bsky/contact/dismissMatch.d.ts.map +1 -0
  160. package/dist/lexicon/types/app/bsky/contact/dismissMatch.js +7 -0
  161. package/dist/lexicon/types/app/bsky/contact/dismissMatch.js.map +1 -0
  162. package/dist/lexicon/types/app/bsky/contact/getMatches.d.ts +25 -0
  163. package/dist/lexicon/types/app/bsky/contact/getMatches.d.ts.map +1 -0
  164. package/dist/lexicon/types/app/bsky/contact/getMatches.js +7 -0
  165. package/dist/lexicon/types/app/bsky/contact/getMatches.js.map +1 -0
  166. package/dist/lexicon/types/app/bsky/contact/getSyncStatus.d.ts +21 -0
  167. package/dist/lexicon/types/app/bsky/contact/getSyncStatus.d.ts.map +1 -0
  168. package/dist/lexicon/types/app/bsky/contact/getSyncStatus.js +7 -0
  169. package/dist/lexicon/types/app/bsky/contact/getSyncStatus.js.map +1 -0
  170. package/dist/lexicon/types/app/bsky/contact/importContacts.d.ts +30 -0
  171. package/dist/lexicon/types/app/bsky/contact/importContacts.d.ts.map +1 -0
  172. package/dist/lexicon/types/app/bsky/contact/importContacts.js +7 -0
  173. package/dist/lexicon/types/app/bsky/contact/importContacts.js.map +1 -0
  174. package/dist/lexicon/types/app/bsky/contact/removeData.d.ts +23 -0
  175. package/dist/lexicon/types/app/bsky/contact/removeData.d.ts.map +1 -0
  176. package/dist/lexicon/types/app/bsky/contact/removeData.js +7 -0
  177. package/dist/lexicon/types/app/bsky/contact/removeData.js.map +1 -0
  178. package/dist/lexicon/types/app/bsky/contact/startPhoneVerification.d.ts +25 -0
  179. package/dist/lexicon/types/app/bsky/contact/startPhoneVerification.d.ts.map +1 -0
  180. package/dist/lexicon/types/app/bsky/contact/startPhoneVerification.js +7 -0
  181. package/dist/lexicon/types/app/bsky/contact/startPhoneVerification.js.map +1 -0
  182. package/dist/lexicon/types/app/bsky/contact/verifyPhone.d.ts +29 -0
  183. package/dist/lexicon/types/app/bsky/contact/verifyPhone.d.ts.map +1 -0
  184. package/dist/lexicon/types/app/bsky/contact/verifyPhone.js +7 -0
  185. package/dist/lexicon/types/app/bsky/contact/verifyPhone.js.map +1 -0
  186. package/dist/lexicon/types/app/bsky/graph/defs.d.ts +8 -0
  187. package/dist/lexicon/types/app/bsky/graph/defs.d.ts.map +1 -1
  188. package/dist/lexicon/types/app/bsky/graph/defs.js.map +1 -1
  189. package/dist/logger.d.ts +1 -0
  190. package/dist/logger.d.ts.map +1 -1
  191. package/dist/logger.js +2 -1
  192. package/dist/logger.js.map +1 -1
  193. package/dist/proto/bsky_pb.d.ts +4 -0
  194. package/dist/proto/bsky_pb.d.ts.map +1 -1
  195. package/dist/proto/bsky_pb.js +10 -0
  196. package/dist/proto/bsky_pb.js.map +1 -1
  197. package/dist/proto/rolodex_connect.d.ts +83 -0
  198. package/dist/proto/rolodex_connect.d.ts.map +1 -0
  199. package/dist/proto/rolodex_connect.js +90 -0
  200. package/dist/proto/rolodex_connect.js.map +1 -0
  201. package/dist/proto/rolodex_pb.d.ts +363 -0
  202. package/dist/proto/rolodex_pb.d.ts.map +1 -0
  203. package/dist/proto/rolodex_pb.js +1032 -0
  204. package/dist/proto/rolodex_pb.js.map +1 -0
  205. package/dist/rolodex.d.ts +9 -0
  206. package/dist/rolodex.d.ts.map +1 -0
  207. package/dist/rolodex.js +25 -0
  208. package/dist/rolodex.js.map +1 -0
  209. package/dist/stash.d.ts +1 -0
  210. package/dist/stash.d.ts.map +1 -1
  211. package/dist/stash.js +1 -0
  212. package/dist/stash.js.map +1 -1
  213. package/dist/util/uris.d.ts +2 -2
  214. package/dist/util/uris.d.ts.map +1 -1
  215. package/package.json +16 -15
  216. package/proto/bsky.proto +1 -0
  217. package/proto/rolodex.proto +116 -0
  218. package/src/api/age-assurance/const.ts +142 -0
  219. package/src/api/age-assurance/index.ts +34 -0
  220. package/src/api/age-assurance/kws/age-verified.ts +75 -0
  221. package/src/api/age-assurance/kws/const.ts +33 -0
  222. package/src/api/age-assurance/kws/external-payload.test.ts +72 -0
  223. package/src/api/age-assurance/kws/external-payload.ts +149 -0
  224. package/src/api/age-assurance/redirects/kws-age-verified.ts +107 -0
  225. package/src/api/age-assurance/stash.ts +22 -0
  226. package/src/api/age-assurance/types.ts +10 -0
  227. package/src/api/age-assurance/util.ts +66 -0
  228. package/src/api/age-assurance/webhooks/kws-age-verified.ts +75 -0
  229. package/src/api/app/bsky/ageassurance/begin.ts +167 -0
  230. package/src/api/app/bsky/ageassurance/getConfig.ts +15 -0
  231. package/src/api/app/bsky/ageassurance/getState.ts +53 -0
  232. package/src/api/app/bsky/contact/dismissMatch.ts +24 -0
  233. package/src/api/app/bsky/contact/getMatches.ts +111 -0
  234. package/src/api/app/bsky/contact/getSyncStatus.ts +35 -0
  235. package/src/api/app/bsky/contact/importContacts.ts +118 -0
  236. package/src/api/app/bsky/contact/removeData.ts +23 -0
  237. package/src/api/app/bsky/contact/startPhoneVerification.ts +24 -0
  238. package/src/api/app/bsky/contact/util.ts +13 -0
  239. package/src/api/app/bsky/contact/verifyPhone.ts +27 -0
  240. package/src/api/app/bsky/graph/getRelationships.ts +4 -0
  241. package/src/api/external.ts +2 -0
  242. package/src/api/index.ts +20 -0
  243. package/src/api/kws/api.ts +55 -34
  244. package/src/api/kws/index.ts +7 -1
  245. package/src/api/kws/webhook.ts +57 -34
  246. package/src/config.ts +53 -2
  247. package/src/context.ts +6 -0
  248. package/src/data-plane/bsync/index.ts +31 -0
  249. package/src/data-plane/server/db/migrations/20251120T004738098Z-update-actor-age-assurance-v2.ts +28 -0
  250. package/src/data-plane/server/db/migrations/index.ts +1 -0
  251. package/src/data-plane/server/db/tables/actor.ts +3 -0
  252. package/src/data-plane/server/routes/profile.ts +12 -1
  253. package/src/hydration/actor.ts +1 -1
  254. package/src/hydration/hydrator.ts +1 -1
  255. package/src/index.ts +13 -0
  256. package/src/kws.ts +81 -0
  257. package/src/lexicon/index.ts +101 -0
  258. package/src/lexicon/lexicons.ts +375 -0
  259. package/src/lexicon/types/app/bsky/contact/defs.ts +52 -0
  260. package/src/lexicon/types/app/bsky/contact/dismissMatch.ts +43 -0
  261. package/src/lexicon/types/app/bsky/contact/getMatches.ts +43 -0
  262. package/src/lexicon/types/app/bsky/contact/getSyncStatus.ts +39 -0
  263. package/src/lexicon/types/app/bsky/contact/importContacts.ts +49 -0
  264. package/src/lexicon/types/app/bsky/contact/removeData.ts +40 -0
  265. package/src/lexicon/types/app/bsky/contact/startPhoneVerification.ts +43 -0
  266. package/src/lexicon/types/app/bsky/contact/verifyPhone.ts +48 -0
  267. package/src/lexicon/types/app/bsky/graph/defs.ts +8 -0
  268. package/src/logger.ts +2 -0
  269. package/src/proto/bsky_pb.ts +6 -0
  270. package/src/proto/rolodex_connect.ts +89 -0
  271. package/src/proto/rolodex_pb.ts +746 -0
  272. package/src/rolodex.ts +42 -0
  273. package/src/stash.ts +3 -0
  274. package/tests/views/__snapshots__/profile.test.ts.snap +103 -0
  275. package/tests/views/age-assurance-v2.test.ts +745 -0
  276. package/tests/views/age-assurance.test.ts +2 -0
  277. package/tests/views/profile.test.ts +39 -0
  278. package/tsconfig.build.tsbuildinfo +1 -1
  279. package/tsconfig.tests.tsbuildinfo +1 -1
@@ -0,0 +1,167 @@
1
+ import crypto from 'node:crypto'
2
+ import { isEmailValid } from '@hapi/address'
3
+ import { isDisposableEmail } from 'disposable-email-domains-js'
4
+ import { getAgeAssuranceRegionConfig } from '@atproto/api'
5
+ import {
6
+ InvalidRequestError,
7
+ MethodNotImplementedError,
8
+ } from '@atproto/xrpc-server'
9
+ import { AppContext } from '../../../../context'
10
+ import { Server } from '../../../../lexicon'
11
+ import { InputSchema } from '../../../../lexicon/types/app/bsky/ageassurance/begin'
12
+ import { httpLogger as log } from '../../../../logger'
13
+ import { ActorInfo } from '../../../../proto/bsky_pb'
14
+ import { AGE_ASSURANCE_CONFIG } from '../../../age-assurance/const'
15
+ import {
16
+ KWS_SUPPORTED_LANGUAGES,
17
+ KWS_V2_COUNTRIES,
18
+ } from '../../../age-assurance/kws/const'
19
+ import {
20
+ KWSExternalPayloadTooLargeError,
21
+ KWSExternalPayloadVersion,
22
+ serializeKWSExternalPayloadV2,
23
+ } from '../../../age-assurance/kws/external-payload'
24
+ import { createEvent } from '../../../age-assurance/stash'
25
+ import { createLocationString } from '../../../age-assurance/util'
26
+ import { getClientUa } from '../../../kws/util'
27
+
28
+ export default function (server: Server, ctx: AppContext) {
29
+ server.app.bsky.ageassurance.begin({
30
+ auth: ctx.authVerifier.standard,
31
+ handler: async ({ auth, input, req }) => {
32
+ if (!ctx.kwsClient) {
33
+ throw new MethodNotImplementedError(
34
+ 'This service is not configured to support age assurance.',
35
+ )
36
+ }
37
+
38
+ const actorDid = auth.credentials.iss
39
+ const actorInfo = await getAgeVerificationState(ctx, actorDid)
40
+
41
+ if (actorInfo?.ageAssuranceStatus) {
42
+ if (
43
+ actorInfo.ageAssuranceStatus.status !== 'unknown' &&
44
+ actorInfo.ageAssuranceStatus.status !== 'pending'
45
+ ) {
46
+ throw new InvalidRequestError(
47
+ `Cannot initiate age assurance flow from current state: ${actorInfo.ageAssuranceStatus.status}`,
48
+ 'InvalidInitiation',
49
+ )
50
+ }
51
+ }
52
+
53
+ const attemptId = crypto.randomUUID()
54
+ const { email, language, countryCode, regionCode } = validateInput(
55
+ input.body,
56
+ )
57
+
58
+ let externalPayload: string
59
+ try {
60
+ externalPayload = serializeKWSExternalPayloadV2({
61
+ version: KWSExternalPayloadVersion.V2,
62
+ actorDid,
63
+ attemptId,
64
+ countryCode,
65
+ regionCode,
66
+ })
67
+ } catch (err) {
68
+ if (err instanceof KWSExternalPayloadTooLargeError) {
69
+ log.error({ err, actorDid }, err.message)
70
+ throw new InvalidRequestError(
71
+ 'Age Assurance flow failed because DID is too long',
72
+ 'DidTooLong',
73
+ )
74
+ }
75
+ throw err
76
+ }
77
+
78
+ /*
79
+ * Determine if age assurance config exists for this region. The calling
80
+ * application should already have checked for this, so this is just a
81
+ * safeguard.
82
+ */
83
+ const region = getAgeAssuranceRegionConfig(AGE_ASSURANCE_CONFIG, {
84
+ countryCode,
85
+ regionCode,
86
+ })
87
+ if (!region) {
88
+ const message = 'Age Assurance is not required in this region'
89
+ log.error({ actorDid, countryCode, regionCode }, message)
90
+ throw new InvalidRequestError(message, 'RegionNotSupported')
91
+ }
92
+
93
+ const location = createLocationString(countryCode, regionCode)
94
+
95
+ if (KWS_V2_COUNTRIES.has(region.countryCode)) {
96
+ // `age-verified` flow
97
+ await ctx.kwsClient.sendAgeVerifiedFlowEmail({
98
+ location,
99
+ email,
100
+ externalPayload,
101
+ language,
102
+ })
103
+ } else {
104
+ // `adult-verified` flow is what we've been using prior to `age-verified`
105
+ await ctx.kwsClient.sendAdultVerifiedFlowEmail({
106
+ location,
107
+ email,
108
+ externalPayload,
109
+ language,
110
+ })
111
+ }
112
+
113
+ const event = await createEvent(ctx, actorDid, {
114
+ attemptId,
115
+ email,
116
+ // Assumes `app.set('trust proxy', ...)` configured with `true` or specific values.
117
+ initIp: req.ip,
118
+ initUa: getClientUa(req),
119
+ status: 'pending',
120
+ access: 'unknown',
121
+ countryCode,
122
+ regionCode,
123
+ })
124
+
125
+ return {
126
+ encoding: 'application/json',
127
+ body: {
128
+ lastInitiatedAt: event.createdAt,
129
+ status: 'pending',
130
+ access: 'unknown',
131
+ },
132
+ }
133
+ },
134
+ })
135
+ }
136
+
137
+ function validateInput({ email, language, ...rest }: InputSchema): InputSchema {
138
+ if (!isEmailValid(email) || isDisposableEmail(email)) {
139
+ throw new InvalidRequestError(
140
+ 'This email address is not supported, please use a different email.',
141
+ 'InvalidEmail',
142
+ )
143
+ }
144
+
145
+ return {
146
+ email,
147
+ language: KWS_SUPPORTED_LANGUAGES.has(language) ? language : 'en',
148
+ ...rest,
149
+ }
150
+ }
151
+
152
+ async function getAgeVerificationState(
153
+ ctx: AppContext,
154
+ actorDid: string,
155
+ ): Promise<ActorInfo | undefined> {
156
+ try {
157
+ const res = await ctx.dataplane.getActors({
158
+ dids: [actorDid],
159
+ returnAgeAssuranceForDids: [actorDid],
160
+ skipCacheForDids: [actorDid],
161
+ })
162
+
163
+ return res.actors[0]
164
+ } catch (err) {
165
+ return undefined
166
+ }
167
+ }
@@ -0,0 +1,15 @@
1
+ import { AGE_ASSURANCE_CONFIG } from '../../../../api/age-assurance/const'
2
+ import { AppContext } from '../../../../context'
3
+ import { Server } from '../../../../lexicon'
4
+
5
+ export default function (server: Server, ctx: AppContext) {
6
+ server.app.bsky.ageassurance.getConfig({
7
+ auth: ctx.authVerifier.standardOptional,
8
+ handler: async () => {
9
+ return {
10
+ encoding: 'application/json',
11
+ body: AGE_ASSURANCE_CONFIG,
12
+ }
13
+ },
14
+ })
15
+ }
@@ -0,0 +1,53 @@
1
+ import { UpstreamFailureError } from '@atproto/xrpc-server'
2
+ import { AppContext } from '../../../../context'
3
+ import { Server } from '../../../../lexicon'
4
+ import { ActorInfo } from '../../../../proto/bsky_pb'
5
+
6
+ export default function (server: Server, ctx: AppContext) {
7
+ server.app.bsky.ageassurance.getState({
8
+ auth: ctx.authVerifier.standard,
9
+ handler: async ({ auth }) => {
10
+ const viewer = auth.credentials.iss
11
+ const actor = await getActorInfo(ctx, viewer)
12
+
13
+ return {
14
+ encoding: 'application/json',
15
+ body: {
16
+ state: {
17
+ lastInitiatedAt:
18
+ actor.ageAssuranceStatus?.lastInitiatedAt
19
+ ?.toDate()
20
+ .toISOString() || undefined,
21
+ status: actor.ageAssuranceStatus?.status || 'unknown',
22
+ access: actor.ageAssuranceStatus?.access || 'unknown',
23
+ },
24
+ metadata: {
25
+ accountCreatedAt:
26
+ actor.createdAt?.toDate().toISOString() || undefined,
27
+ },
28
+ },
29
+ }
30
+ },
31
+ })
32
+ }
33
+
34
+ const getActorInfo = async (
35
+ ctx: AppContext,
36
+ actorDid: string,
37
+ ): Promise<ActorInfo> => {
38
+ try {
39
+ const res = await ctx.dataplane.getActors({
40
+ dids: [actorDid],
41
+ returnAgeAssuranceForDids: [actorDid],
42
+ skipCacheForDids: [actorDid],
43
+ })
44
+
45
+ return res.actors[0]
46
+ } catch (err) {
47
+ throw new UpstreamFailureError(
48
+ 'Cannot get current age assurance state',
49
+ 'GetAgeAssuranceStateFailed',
50
+ { cause: err },
51
+ )
52
+ }
53
+ }
@@ -0,0 +1,24 @@
1
+ import { AppContext } from '../../../../context'
2
+ import { Server } from '../../../../lexicon'
3
+ import { assertRolodexOrThrowUnimplemented } from './util'
4
+
5
+ export default function (server: Server, ctx: AppContext) {
6
+ server.app.bsky.contact.dismissMatch({
7
+ auth: ctx.authVerifier.standard,
8
+ handler: async ({ input, auth }) => {
9
+ assertRolodexOrThrowUnimplemented(ctx)
10
+
11
+ const actor = auth.credentials.iss
12
+ // TODO: Error handling.
13
+ await ctx.rolodexClient.dismissMatch({
14
+ actor,
15
+ subject: input.body.subject,
16
+ })
17
+
18
+ return {
19
+ encoding: 'application/json',
20
+ body: {},
21
+ }
22
+ },
23
+ })
24
+ }
@@ -0,0 +1,111 @@
1
+ import { mapDefined } from '@atproto/common'
2
+ import { AppContext } from '../../../../context'
3
+ import {
4
+ HydrateCtx,
5
+ HydrationState,
6
+ Hydrator,
7
+ } from '../../../../hydration/hydrator'
8
+ import { Server } from '../../../../lexicon'
9
+ import { QueryParams } from '../../../../lexicon/types/app/bsky/contact/getMatches'
10
+ import {
11
+ HydrationFnInput,
12
+ SkeletonFnInput,
13
+ createPipeline,
14
+ } from '../../../../pipeline'
15
+ import { RolodexClient } from '../../../../rolodex'
16
+ import { Views } from '../../../../views'
17
+ import { assertRolodexOrThrowUnimplemented } from './util'
18
+
19
+ export default function (server: Server, ctx: AppContext) {
20
+ const getMatches = createPipeline(skeleton, hydration, noBlocks, presentation)
21
+ server.app.bsky.contact.getMatches({
22
+ auth: ctx.authVerifier.standard,
23
+ handler: async ({ params, auth, req }) => {
24
+ assertRolodexOrThrowUnimplemented(ctx)
25
+
26
+ const viewer = auth.credentials.iss
27
+ const labelers = ctx.reqLabelers(req)
28
+ const hydrateCtx = await ctx.hydrator.createContext({
29
+ labelers,
30
+ viewer,
31
+ })
32
+
33
+ const result = await getMatches(
34
+ { ...params, hydrateCtx: hydrateCtx.copy({ viewer }) },
35
+ ctx,
36
+ )
37
+
38
+ return {
39
+ encoding: 'application/json',
40
+ body: result,
41
+ }
42
+ },
43
+ })
44
+ }
45
+
46
+ const skeleton = async (
47
+ input: SkeletonFnInput<Context, Params>,
48
+ ): Promise<SkeletonState> => {
49
+ const { params, ctx } = input
50
+ const actor = params.hydrateCtx.viewer
51
+ // TODO: Error handling.
52
+ const { cursor, subjects } = await ctx.rolodexClient.getMatches({
53
+ actor: params.hydrateCtx.viewer,
54
+ limit: params.limit,
55
+ cursor: params.cursor,
56
+ })
57
+ return {
58
+ actor,
59
+ subjects,
60
+ cursor: cursor || undefined,
61
+ }
62
+ }
63
+
64
+ const hydration = async (
65
+ input: HydrationFnInput<Context, Params, SkeletonState>,
66
+ ) => {
67
+ const { ctx, params, skeleton } = input
68
+ const { subjects } = skeleton
69
+ return ctx.hydrator.hydrateProfiles(subjects, params.hydrateCtx)
70
+ }
71
+
72
+ const noBlocks = (inputs: {
73
+ ctx: Context
74
+ skeleton: SkeletonState
75
+ hydration: HydrationState
76
+ }) => {
77
+ const { ctx, skeleton, hydration } = inputs
78
+ skeleton.subjects = skeleton.subjects.filter((subject) => {
79
+ return !ctx.views.viewerBlockExists(subject, hydration)
80
+ })
81
+ return skeleton
82
+ }
83
+
84
+ const presentation = (input: {
85
+ ctx: Context
86
+ params: Params
87
+ skeleton: SkeletonState
88
+ hydration: HydrationState
89
+ }) => {
90
+ const { ctx, skeleton, hydration } = input
91
+ const matches = mapDefined(skeleton.subjects, (did) =>
92
+ ctx.views.profile(did, hydration),
93
+ )
94
+ return { matches }
95
+ }
96
+
97
+ type Context = {
98
+ hydrator: Hydrator
99
+ rolodexClient: RolodexClient
100
+ views: Views
101
+ }
102
+
103
+ type Params = QueryParams & {
104
+ hydrateCtx: HydrateCtx & { viewer: string }
105
+ }
106
+
107
+ type SkeletonState = {
108
+ actor: string
109
+ subjects: string[]
110
+ cursor?: string
111
+ }
@@ -0,0 +1,35 @@
1
+ import { AppContext } from '../../../../context'
2
+ import { Server } from '../../../../lexicon'
3
+ import { SyncStatus } from '../../../../lexicon/types/app/bsky/contact/defs'
4
+ import { assertRolodexOrThrowUnimplemented } from './util'
5
+
6
+ export default function (server: Server, ctx: AppContext) {
7
+ server.app.bsky.contact.getSyncStatus({
8
+ auth: ctx.authVerifier.standard,
9
+ handler: async ({ auth }) => {
10
+ assertRolodexOrThrowUnimplemented(ctx)
11
+
12
+ const actor = auth.credentials.iss
13
+ // TODO: Error handling.
14
+ const res = await ctx.rolodexClient.getSyncStatus({
15
+ actor,
16
+ })
17
+
18
+ let syncStatus: SyncStatus | undefined
19
+ if (res.status && res.status.syncedAt) {
20
+ const syncedAt = res.status?.syncedAt?.toDate().toISOString()
21
+ syncStatus = {
22
+ matchesCount: res.status.matchesCount,
23
+ syncedAt,
24
+ }
25
+ }
26
+
27
+ return {
28
+ encoding: 'application/json',
29
+ body: {
30
+ syncStatus,
31
+ },
32
+ }
33
+ },
34
+ })
35
+ }
@@ -0,0 +1,118 @@
1
+ import { mapDefined } from '@atproto/common'
2
+ import { AppContext } from '../../../../context'
3
+ import {
4
+ HydrateCtx,
5
+ HydrationState,
6
+ Hydrator,
7
+ } from '../../../../hydration/hydrator'
8
+ import { Server } from '../../../../lexicon'
9
+ import { MatchAndContactIndex } from '../../../../lexicon/types/app/bsky/contact/defs'
10
+ import { InputSchema } from '../../../../lexicon/types/app/bsky/contact/importContacts'
11
+ import {
12
+ HydrationFnInput,
13
+ SkeletonFnInput,
14
+ createPipeline,
15
+ noRules,
16
+ } from '../../../../pipeline'
17
+ import { ImportContactsMatch } from '../../../../proto/rolodex_pb'
18
+ import { RolodexClient } from '../../../../rolodex'
19
+ import { Views } from '../../../../views'
20
+ import { assertRolodexOrThrowUnimplemented } from './util'
21
+
22
+ export default function (server: Server, ctx: AppContext) {
23
+ const importContacts = createPipeline(
24
+ skeleton,
25
+ hydration,
26
+ noRules, //
27
+ presentation,
28
+ )
29
+ server.app.bsky.contact.importContacts({
30
+ auth: ctx.authVerifier.standard,
31
+ handler: async ({ input, auth, req }) => {
32
+ assertRolodexOrThrowUnimplemented(ctx)
33
+
34
+ const viewer = auth.credentials.iss
35
+ const labelers = ctx.reqLabelers(req)
36
+ const hydrateCtx = await ctx.hydrator.createContext({
37
+ labelers,
38
+ viewer,
39
+ })
40
+
41
+ const result = await importContacts(
42
+ { ...input.body, hydrateCtx: hydrateCtx.copy({ viewer }) },
43
+ ctx,
44
+ )
45
+
46
+ return {
47
+ encoding: 'application/json',
48
+ body: result,
49
+ }
50
+ },
51
+ })
52
+ }
53
+
54
+ const skeleton = async (
55
+ input: SkeletonFnInput<Context, Params>,
56
+ ): Promise<SkeletonState> => {
57
+ const { params, ctx } = input
58
+ const actor = params.hydrateCtx.viewer
59
+ // TODO: Error handling.
60
+ const { matches } = await ctx.rolodexClient.importContacts({
61
+ actor: params.hydrateCtx.viewer,
62
+ contacts: params.contacts,
63
+ token: params.token,
64
+ })
65
+ return {
66
+ actor,
67
+ matches,
68
+ }
69
+ }
70
+
71
+ const hydration = async (
72
+ input: HydrationFnInput<Context, Params, SkeletonState>,
73
+ ) => {
74
+ const { ctx, params, skeleton } = input
75
+ const { matches } = skeleton
76
+ const subjects = matches.map((m) => m.subject)
77
+ return ctx.hydrator.hydrateProfiles(subjects, params.hydrateCtx)
78
+ }
79
+
80
+ const presentation = (input: {
81
+ ctx: Context
82
+ params: Params
83
+ skeleton: SkeletonState
84
+ hydration: HydrationState
85
+ }) => {
86
+ const { ctx, skeleton, hydration } = input
87
+ const matchesAndContactIndexes = mapDefined(
88
+ skeleton.matches,
89
+ ({ subject, inputIndex }): MatchAndContactIndex | undefined => {
90
+ const profile = ctx.views.profile(subject, hydration)
91
+
92
+ if (!profile) {
93
+ return undefined
94
+ }
95
+
96
+ return {
97
+ contactIndex: inputIndex,
98
+ match: profile,
99
+ }
100
+ },
101
+ )
102
+ return { matchesAndContactIndexes }
103
+ }
104
+
105
+ type Context = {
106
+ hydrator: Hydrator
107
+ rolodexClient: RolodexClient
108
+ views: Views
109
+ }
110
+
111
+ type Params = InputSchema & {
112
+ hydrateCtx: HydrateCtx & { viewer: string }
113
+ }
114
+
115
+ type SkeletonState = {
116
+ actor: string
117
+ matches: ImportContactsMatch[]
118
+ }
@@ -0,0 +1,23 @@
1
+ import { AppContext } from '../../../../context'
2
+ import { Server } from '../../../../lexicon'
3
+ import { assertRolodexOrThrowUnimplemented } from './util'
4
+
5
+ export default function (server: Server, ctx: AppContext) {
6
+ server.app.bsky.contact.removeData({
7
+ auth: ctx.authVerifier.standard,
8
+ handler: async ({ auth }) => {
9
+ assertRolodexOrThrowUnimplemented(ctx)
10
+
11
+ const actor = auth.credentials.iss
12
+ // TODO: Error handling.
13
+ await ctx.rolodexClient.removeData({
14
+ actor,
15
+ })
16
+
17
+ return {
18
+ encoding: 'application/json',
19
+ body: {},
20
+ }
21
+ },
22
+ })
23
+ }
@@ -0,0 +1,24 @@
1
+ import { AppContext } from '../../../../context'
2
+ import { Server } from '../../../../lexicon'
3
+ import { assertRolodexOrThrowUnimplemented } from './util'
4
+
5
+ export default function (server: Server, ctx: AppContext) {
6
+ server.app.bsky.contact.startPhoneVerification({
7
+ auth: ctx.authVerifier.standard,
8
+ handler: async ({ auth, input }) => {
9
+ assertRolodexOrThrowUnimplemented(ctx)
10
+
11
+ const actor = auth.credentials.iss
12
+ // TODO: Error handling.
13
+ await ctx.rolodexClient.startPhoneVerification({
14
+ actor,
15
+ phone: input.body.phone,
16
+ })
17
+
18
+ return {
19
+ encoding: 'application/json',
20
+ body: {},
21
+ }
22
+ },
23
+ })
24
+ }
@@ -0,0 +1,13 @@
1
+ import { MethodNotImplementedError } from '@atproto/xrpc-server'
2
+ import { AppContext } from '../../../..'
3
+ import { RolodexClient } from '../../../../rolodex'
4
+
5
+ export function assertRolodexOrThrowUnimplemented(
6
+ ctx: AppContext,
7
+ ): asserts ctx is AppContext & { rolodexClient: RolodexClient } {
8
+ if (!ctx.rolodexClient) {
9
+ throw new MethodNotImplementedError(
10
+ 'This service is not configured to support contact imports.',
11
+ )
12
+ }
13
+ }
@@ -0,0 +1,27 @@
1
+ import { AppContext } from '../../../../context'
2
+ import { Server } from '../../../../lexicon'
3
+ import { assertRolodexOrThrowUnimplemented } from './util'
4
+
5
+ export default function (server: Server, ctx: AppContext) {
6
+ server.app.bsky.contact.verifyPhone({
7
+ auth: ctx.authVerifier.standard,
8
+ handler: async ({ auth, input }) => {
9
+ assertRolodexOrThrowUnimplemented(ctx)
10
+
11
+ const actor = auth.credentials.iss
12
+ // TODO: Error handling.
13
+ const res = await ctx.rolodexClient.verifyPhone({
14
+ actor,
15
+ verificationCode: input.body.code,
16
+ phone: input.body.phone,
17
+ })
18
+
19
+ return {
20
+ encoding: 'application/json',
21
+ body: {
22
+ token: res.token,
23
+ },
24
+ }
25
+ },
26
+ })
27
+ }
@@ -26,6 +26,10 @@ export default function (server: Server, ctx: AppContext) {
26
26
  did,
27
27
  following: subject.following,
28
28
  followedBy: subject.followedBy,
29
+ blocking: subject.blocking,
30
+ blockedBy: subject.blockedBy,
31
+ blockingByList: subject.blockingByList,
32
+ blockedByList: subject.blockedByList,
29
33
  }
30
34
  : {
31
35
  $type: 'app.bsky.graph.defs#notFoundActor',
@@ -1,5 +1,6 @@
1
1
  import { Router } from 'express'
2
2
  import { AppContext } from '../context'
3
+ import * as aaApi from './age-assurance'
3
4
  import * as kwsApi from './kws'
4
5
 
5
6
  export const createRouter = (ctx: AppContext): Router => {
@@ -7,6 +8,7 @@ export const createRouter = (ctx: AppContext): Router => {
7
8
 
8
9
  if (ctx.kwsClient) {
9
10
  router.use('/kws', kwsApi.createRouter(ctx))
11
+ router.use(aaApi.createRouter(ctx))
10
12
  }
11
13
 
12
14
  return router
package/src/api/index.ts CHANGED
@@ -5,9 +5,19 @@ import getProfiles from './app/bsky/actor/getProfiles'
5
5
  import getSuggestions from './app/bsky/actor/getSuggestions'
6
6
  import searchActors from './app/bsky/actor/searchActors'
7
7
  import searchActorsTypeahead from './app/bsky/actor/searchActorsTypeahead'
8
+ import aaBegin from './app/bsky/ageassurance/begin'
9
+ import aaGetConfig from './app/bsky/ageassurance/getConfig'
10
+ import aaGetState from './app/bsky/ageassurance/getState'
8
11
  import createBookmark from './app/bsky/bookmark/createBookmark'
9
12
  import deleteBookmark from './app/bsky/bookmark/deleteBookmark'
10
13
  import getBookmarks from './app/bsky/bookmark/getBookmarks'
14
+ import dismissMatch from './app/bsky/contact/dismissMatch'
15
+ import getMatches from './app/bsky/contact/getMatches'
16
+ import getSyncStatus from './app/bsky/contact/getSyncStatus'
17
+ import importContacts from './app/bsky/contact/importContacts'
18
+ import removeData from './app/bsky/contact/removeData'
19
+ import startPhoneVerification from './app/bsky/contact/startPhoneVerification'
20
+ import verifyPhone from './app/bsky/contact/verifyPhone'
11
21
  import getActorFeeds from './app/bsky/feed/getActorFeeds'
12
22
  import getActorLikes from './app/bsky/feed/getActorLikes'
13
23
  import getAuthorFeed from './app/bsky/feed/getAuthorFeed'
@@ -91,6 +101,13 @@ export default function (server: Server, ctx: AppContext) {
91
101
  createBookmark(server, ctx)
92
102
  deleteBookmark(server, ctx)
93
103
  getBookmarks(server, ctx)
104
+ dismissMatch(server, ctx)
105
+ getMatches(server, ctx)
106
+ getSyncStatus(server, ctx)
107
+ importContacts(server, ctx)
108
+ removeData(server, ctx)
109
+ startPhoneVerification(server, ctx)
110
+ verifyPhone(server, ctx)
94
111
  getActorFeeds(server, ctx)
95
112
  getSuggestedFeeds(server, ctx)
96
113
  getAuthorFeed(server, ctx)
@@ -156,6 +173,9 @@ export default function (server: Server, ctx: AppContext) {
156
173
  getTaggedSuggestions(server, ctx)
157
174
  getAgeAssuranceState(server, ctx)
158
175
  initAgeAssurance(server, ctx)
176
+ aaGetConfig(server, ctx)
177
+ aaGetState(server, ctx)
178
+ aaBegin(server, ctx)
159
179
  // com.atproto
160
180
  getSubjectStatus(server, ctx)
161
181
  updateSubjectStatus(server, ctx)