@atproto/pds 0.4.122 → 0.4.124

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 (215) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/account-manager/account-manager.js +17 -7
  3. package/dist/account-manager/account-manager.js.map +1 -1
  4. package/dist/account-manager/db/index.d.ts.map +1 -1
  5. package/dist/account-manager/db/migrations/005-oauth-account-management.d.ts +20 -0
  6. package/dist/account-manager/db/migrations/005-oauth-account-management.d.ts.map +1 -0
  7. package/dist/account-manager/db/migrations/005-oauth-account-management.js +72 -0
  8. package/dist/account-manager/db/migrations/005-oauth-account-management.js.map +1 -0
  9. package/dist/account-manager/db/migrations/index.d.ts +2 -0
  10. package/dist/account-manager/db/migrations/index.d.ts.map +1 -1
  11. package/dist/account-manager/db/migrations/index.js +19 -7
  12. package/dist/account-manager/db/migrations/index.js.map +1 -1
  13. package/dist/account-manager/db/schema/account-device.d.ts +13 -0
  14. package/dist/account-manager/db/schema/account-device.d.ts.map +1 -0
  15. package/dist/account-manager/db/schema/{device-account.js → account-device.js} +2 -2
  16. package/dist/account-manager/db/schema/account-device.js.map +1 -0
  17. package/dist/account-manager/db/schema/authorization-request.d.ts +4 -4
  18. package/dist/account-manager/db/schema/authorization-request.d.ts.map +1 -1
  19. package/dist/account-manager/db/schema/authorization-request.js.map +1 -1
  20. package/dist/account-manager/db/schema/authorized-client.d.ts +16 -0
  21. package/dist/account-manager/db/schema/authorized-client.d.ts.map +1 -0
  22. package/dist/account-manager/db/schema/authorized-client.js +5 -0
  23. package/dist/account-manager/db/schema/authorized-client.js.map +1 -0
  24. package/dist/account-manager/db/schema/index.d.ts +4 -3
  25. package/dist/account-manager/db/schema/index.d.ts.map +1 -1
  26. package/dist/account-manager/db/schema/token.d.ts +5 -5
  27. package/dist/account-manager/db/schema/token.d.ts.map +1 -1
  28. package/dist/account-manager/db/schema/token.js.map +1 -1
  29. package/dist/account-manager/helpers/account-device.d.ts +204 -0
  30. package/dist/account-manager/helpers/account-device.d.ts.map +1 -0
  31. package/dist/account-manager/helpers/account-device.js +54 -0
  32. package/dist/account-manager/helpers/account-device.js.map +1 -0
  33. package/dist/account-manager/helpers/account.d.ts +2 -1
  34. package/dist/account-manager/helpers/account.d.ts.map +1 -1
  35. package/dist/account-manager/helpers/auth.d.ts.map +1 -1
  36. package/dist/account-manager/helpers/auth.js +17 -7
  37. package/dist/account-manager/helpers/auth.js.map +1 -1
  38. package/dist/account-manager/helpers/authorization-request.d.ts.map +1 -1
  39. package/dist/account-manager/helpers/authorization-request.js +4 -4
  40. package/dist/account-manager/helpers/authorization-request.js.map +1 -1
  41. package/dist/account-manager/helpers/authorized-client.d.ts +6 -0
  42. package/dist/account-manager/helpers/authorized-client.d.ts.map +1 -0
  43. package/dist/account-manager/helpers/authorized-client.js +47 -0
  44. package/dist/account-manager/helpers/authorized-client.js.map +1 -0
  45. package/dist/account-manager/helpers/device.d.ts +1 -1
  46. package/dist/account-manager/helpers/device.d.ts.map +1 -1
  47. package/dist/account-manager/helpers/device.js.map +1 -1
  48. package/dist/account-manager/helpers/email-token.d.ts.map +1 -1
  49. package/dist/account-manager/helpers/invite.d.ts.map +1 -1
  50. package/dist/account-manager/helpers/password.d.ts.map +1 -1
  51. package/dist/account-manager/helpers/password.js +17 -7
  52. package/dist/account-manager/helpers/password.js.map +1 -1
  53. package/dist/account-manager/helpers/repo.d.ts.map +1 -1
  54. package/dist/account-manager/helpers/scrypt.d.ts.map +1 -1
  55. package/dist/account-manager/helpers/scrypt.js +17 -7
  56. package/dist/account-manager/helpers/scrypt.js.map +1 -1
  57. package/dist/account-manager/helpers/token.d.ts +566 -59
  58. package/dist/account-manager/helpers/token.d.ts.map +1 -1
  59. package/dist/account-manager/helpers/token.js +17 -32
  60. package/dist/account-manager/helpers/token.js.map +1 -1
  61. package/dist/account-manager/helpers/used-refresh-token.d.ts.map +1 -1
  62. package/dist/account-manager/oauth-store.d.ts +17 -7
  63. package/dist/account-manager/oauth-store.d.ts.map +1 -1
  64. package/dist/account-manager/oauth-store.js +138 -86
  65. package/dist/account-manager/oauth-store.js.map +1 -1
  66. package/dist/actor-store/actor-store.js +17 -7
  67. package/dist/actor-store/actor-store.js.map +1 -1
  68. package/dist/actor-store/blob/transactor.js +17 -7
  69. package/dist/actor-store/blob/transactor.js.map +1 -1
  70. package/dist/actor-store/db/index.d.ts.map +1 -1
  71. package/dist/actor-store/db/migrations/index.js +17 -7
  72. package/dist/actor-store/db/migrations/index.js.map +1 -1
  73. package/dist/actor-store/migrate.d.ts.map +1 -1
  74. package/dist/actor-store/preference/reader.d.ts.map +1 -1
  75. package/dist/actor-store/preference/util.d.ts.map +1 -1
  76. package/dist/actor-store/record/reader.d.ts.map +1 -1
  77. package/dist/actor-store/record/reader.js +17 -7
  78. package/dist/actor-store/record/reader.js.map +1 -1
  79. package/dist/actor-store/repo/sql-repo-reader.d.ts +1 -1
  80. package/dist/api/app/bsky/util/resolver.d.ts.map +1 -1
  81. package/dist/api/com/atproto/identity/signPlcOperation.js +17 -7
  82. package/dist/api/com/atproto/identity/signPlcOperation.js.map +1 -1
  83. package/dist/api/com/atproto/identity/submitPlcOperation.js +17 -7
  84. package/dist/api/com/atproto/identity/submitPlcOperation.js.map +1 -1
  85. package/dist/api/com/atproto/repo/describeRepo.js +17 -7
  86. package/dist/api/com/atproto/repo/describeRepo.js.map +1 -1
  87. package/dist/api/com/atproto/repo/importRepo.d.ts.map +1 -1
  88. package/dist/api/com/atproto/server/createAccount.js +17 -7
  89. package/dist/api/com/atproto/server/createAccount.js.map +1 -1
  90. package/dist/api/com/atproto/server/util.d.ts.map +1 -1
  91. package/dist/api/com/atproto/server/util.js +17 -7
  92. package/dist/api/com/atproto/server/util.js.map +1 -1
  93. package/dist/api/com/atproto/sync/getRecord.js +17 -7
  94. package/dist/api/com/atproto/sync/getRecord.js.map +1 -1
  95. package/dist/api/com/atproto/sync/getRepo.d.ts.map +1 -1
  96. package/dist/api/com/atproto/sync/util.d.ts.map +1 -1
  97. package/dist/api/proxy.d.ts.map +1 -1
  98. package/dist/auth-routes.d.ts.map +1 -1
  99. package/dist/auth-routes.js +2 -3
  100. package/dist/auth-routes.js.map +1 -1
  101. package/dist/auth-verifier.d.ts.map +1 -1
  102. package/dist/auth-verifier.js +19 -13
  103. package/dist/auth-verifier.js.map +1 -1
  104. package/dist/basic-routes.d.ts.map +1 -1
  105. package/dist/config/config.d.ts.map +1 -1
  106. package/dist/config/config.js +1 -1
  107. package/dist/config/config.js.map +1 -1
  108. package/dist/config/env.d.ts +1 -1
  109. package/dist/config/env.d.ts.map +1 -1
  110. package/dist/config/env.js +1 -1
  111. package/dist/config/env.js.map +1 -1
  112. package/dist/config/secrets.d.ts.map +1 -1
  113. package/dist/context.js +18 -8
  114. package/dist/context.js.map +1 -1
  115. package/dist/db/cast.d.ts +17 -13
  116. package/dist/db/cast.d.ts.map +1 -1
  117. package/dist/db/cast.js +13 -52
  118. package/dist/db/cast.js.map +1 -1
  119. package/dist/db/pagination.d.ts.map +1 -1
  120. package/dist/db/util.d.ts.map +1 -1
  121. package/dist/did-cache/db/index.d.ts.map +1 -1
  122. package/dist/disk-blobstore.d.ts.map +1 -1
  123. package/dist/handle/explicit-slurs.d.ts.map +1 -1
  124. package/dist/handle/index.d.ts.map +1 -1
  125. package/dist/index.js +17 -7
  126. package/dist/index.js.map +1 -1
  127. package/dist/lexicon/index.d.ts +4 -0
  128. package/dist/lexicon/index.d.ts.map +1 -1
  129. package/dist/lexicon/index.js +8 -0
  130. package/dist/lexicon/index.js.map +1 -1
  131. package/dist/lexicon/lexicons.d.ts +254 -4
  132. package/dist/lexicon/lexicons.d.ts.map +1 -1
  133. package/dist/lexicon/lexicons.js +134 -2
  134. package/dist/lexicon/lexicons.js.map +1 -1
  135. package/dist/lexicon/types/com/atproto/sync/defs.d.ts +2 -0
  136. package/dist/lexicon/types/com/atproto/sync/defs.d.ts.map +1 -0
  137. package/dist/lexicon/types/com/atproto/sync/defs.js +7 -0
  138. package/dist/lexicon/types/com/atproto/sync/defs.js.map +1 -0
  139. package/dist/lexicon/types/com/atproto/sync/getHostStatus.d.ts +43 -0
  140. package/dist/lexicon/types/com/atproto/sync/getHostStatus.d.ts.map +1 -0
  141. package/dist/lexicon/types/com/atproto/sync/getHostStatus.js +7 -0
  142. package/dist/lexicon/types/com/atproto/sync/getHostStatus.js.map +1 -0
  143. package/dist/lexicon/types/com/atproto/sync/listHosts.d.ts +51 -0
  144. package/dist/lexicon/types/com/atproto/sync/listHosts.d.ts.map +1 -0
  145. package/dist/lexicon/types/com/atproto/sync/listHosts.js +16 -0
  146. package/dist/lexicon/types/com/atproto/sync/listHosts.js.map +1 -0
  147. package/dist/lexicon/types/com/atproto/sync/requestCrawl.d.ts +1 -0
  148. package/dist/lexicon/types/com/atproto/sync/requestCrawl.d.ts.map +1 -1
  149. package/dist/lexicon/util.d.ts.map +1 -1
  150. package/dist/mailer/index.js +17 -7
  151. package/dist/mailer/index.js.map +1 -1
  152. package/dist/mailer/templates/plc-operation.js +1 -1
  153. package/dist/mailer/templates/plc-operation.js.map +1 -1
  154. package/dist/pipethrough.d.ts.map +1 -1
  155. package/dist/read-after-write/util.d.ts.map +1 -1
  156. package/dist/redis.d.ts.map +1 -1
  157. package/dist/repo/prepare.d.ts.map +1 -1
  158. package/dist/repo/prepare.js +17 -7
  159. package/dist/repo/prepare.js.map +1 -1
  160. package/dist/scripts/publish-identity.d.ts.map +1 -1
  161. package/dist/scripts/rebuild-repo.d.ts.map +1 -1
  162. package/dist/scripts/rotate-keys.d.ts.map +1 -1
  163. package/dist/scripts/sequencer-recovery/index.d.ts.map +1 -1
  164. package/dist/scripts/sequencer-recovery/recoverer.d.ts.map +1 -1
  165. package/dist/scripts/sequencer-recovery/recovery-db.d.ts.map +1 -1
  166. package/dist/scripts/sequencer-recovery/repair-repos.d.ts.map +1 -1
  167. package/dist/scripts/util.d.ts.map +1 -1
  168. package/dist/sequencer/db/index.d.ts.map +1 -1
  169. package/dist/sequencer/db/migrations/index.js +17 -7
  170. package/dist/sequencer/db/migrations/index.js.map +1 -1
  171. package/dist/sequencer/events.d.ts +6 -6
  172. package/dist/sequencer/events.d.ts.map +1 -1
  173. package/dist/sequencer/sequencer.d.ts.map +1 -1
  174. package/dist/util/debug.d.ts.map +1 -1
  175. package/dist/util/params.d.ts.map +1 -1
  176. package/dist/well-known.d.ts.map +1 -1
  177. package/package.json +6 -5
  178. package/src/account-manager/db/migrations/005-oauth-account-management.ts +112 -0
  179. package/src/account-manager/db/migrations/index.ts +2 -0
  180. package/src/account-manager/db/schema/account-device.ts +14 -0
  181. package/src/account-manager/db/schema/authorization-request.ts +5 -3
  182. package/src/account-manager/db/schema/authorized-client.ts +19 -0
  183. package/src/account-manager/db/schema/index.ts +5 -3
  184. package/src/account-manager/db/schema/token.ts +7 -4
  185. package/src/account-manager/helpers/account-device.ts +66 -0
  186. package/src/account-manager/helpers/authorization-request.ts +5 -5
  187. package/src/account-manager/helpers/authorized-client.ts +69 -0
  188. package/src/account-manager/helpers/device.ts +3 -1
  189. package/src/account-manager/helpers/token.ts +19 -57
  190. package/src/account-manager/oauth-store.ts +182 -103
  191. package/src/auth-routes.ts +11 -7
  192. package/src/auth-verifier.ts +2 -7
  193. package/src/config/config.ts +1 -1
  194. package/src/config/env.ts +2 -2
  195. package/src/context.ts +2 -2
  196. package/src/db/cast.ts +43 -50
  197. package/src/lexicon/index.ts +24 -0
  198. package/src/lexicon/lexicons.ts +141 -2
  199. package/src/lexicon/types/com/atproto/sync/defs.ts +23 -0
  200. package/src/lexicon/types/com/atproto/sync/getHostStatus.ts +61 -0
  201. package/src/lexicon/types/com/atproto/sync/listHosts.ts +77 -0
  202. package/src/lexicon/types/com/atproto/sync/requestCrawl.ts +1 -0
  203. package/src/mailer/templates/plc-operation.hbs +2 -2
  204. package/tests/db.test.ts +2 -1
  205. package/tsconfig.build.tsbuildinfo +1 -1
  206. package/tsconfig.tests.tsbuildinfo +1 -1
  207. package/dist/account-manager/db/schema/device-account.d.ts +0 -14
  208. package/dist/account-manager/db/schema/device-account.d.ts.map +0 -1
  209. package/dist/account-manager/db/schema/device-account.js.map +0 -1
  210. package/dist/account-manager/helpers/device-account.d.ts +0 -108
  211. package/dist/account-manager/helpers/device-account.d.ts.map +0 -1
  212. package/dist/account-manager/helpers/device-account.js +0 -83
  213. package/dist/account-manager/helpers/device-account.js.map +0 -1
  214. package/src/account-manager/db/schema/device-account.ts +0 -15
  215. package/src/account-manager/helpers/device-account.ts +0 -135
@@ -2,75 +2,34 @@ import { Selectable } from 'kysely'
2
2
  import {
3
3
  Code,
4
4
  NewTokenData,
5
- OAuthAuthorizationDetail,
6
5
  RefreshToken,
7
6
  TokenData,
8
7
  TokenId,
9
- TokenInfo,
10
8
  } from '@atproto/oauth-provider'
11
- import {
12
- fromDateISO,
13
- fromJsonArray,
14
- fromJsonObject,
15
- toDateISO,
16
- toJsonArray,
17
- toJsonObject,
18
- } from '../../db'
9
+ import { fromDateISO, fromJson, toDateISO, toJson } from '../../db'
19
10
  import { AccountDb, Token } from '../db'
20
- import { ActorAccount, selectAccountQB } from './account'
21
- import {
22
- SelectableDeviceAccount,
23
- toAccount,
24
- toDeviceAccountInfo,
25
- } from './device-account'
26
-
27
- type LeftJoined<T> = { [K in keyof T]: null | T[K] }
11
+ import { selectAccountQB } from './account'
28
12
 
29
- export type ActorAccountToken = Selectable<ActorAccount> &
30
- Selectable<Omit<Token, 'id' | 'did'>> &
31
- LeftJoined<SelectableDeviceAccount>
32
-
33
- export const toTokenInfo = (
34
- row: ActorAccountToken,
35
- audience: string,
36
- ): TokenInfo => ({
37
- id: row.tokenId,
38
- data: {
13
+ export function toTokenData(row: Selectable<Token>): TokenData {
14
+ return {
39
15
  createdAt: fromDateISO(row.createdAt),
40
16
  expiresAt: fromDateISO(row.expiresAt),
41
17
  updatedAt: fromDateISO(row.updatedAt),
42
18
  clientId: row.clientId,
43
- clientAuth: fromJsonObject(row.clientAuth),
19
+ clientAuth: fromJson(row.clientAuth),
44
20
  deviceId: row.deviceId,
45
21
  sub: row.did,
46
- parameters: fromJsonObject(row.parameters),
47
- details: row.details
48
- ? fromJsonArray<OAuthAuthorizationDetail>(row.details)
49
- : null,
22
+ parameters: fromJson(row.parameters),
50
23
  code: row.code,
51
- },
52
- account: toAccount(row, audience),
53
- info:
54
- row.authenticatedAt != null &&
55
- row.authorizedClients != null &&
56
- row.remember != null
57
- ? toDeviceAccountInfo(row as SelectableDeviceAccount)
58
- : undefined,
59
- currentRefreshToken: row.currentRefreshToken,
60
- })
24
+ }
25
+ }
61
26
 
62
27
  const selectTokenInfoQB = (db: AccountDb) =>
63
28
  selectAccountQB(db, { includeDeactivated: true })
64
29
  // uses "token_did_idx" index (though unlikely in practice)
65
30
  .innerJoin('token', 'token.did', 'actor.did')
66
- .leftJoin('device_account', (join) =>
67
- join
68
- // uses "device_account_pk" index
69
- .on('device_account.did', '=', 'token.did')
70
- // @ts-expect-error "deviceId" is nullable in token
71
- .on('device_account.deviceId', '=', 'token.deviceId'),
72
- )
73
31
  .select([
32
+ 'token.id',
74
33
  'token.tokenId',
75
34
  'token.createdAt',
76
35
  'token.updatedAt',
@@ -83,9 +42,6 @@ const selectTokenInfoQB = (db: AccountDb) =>
83
42
  'token.details',
84
43
  'token.code',
85
44
  'token.currentRefreshToken',
86
- 'device_account.authenticatedAt',
87
- 'device_account.authorizedClients',
88
- 'device_account.remember',
89
45
  ])
90
46
 
91
47
  export const createQB = (
@@ -100,11 +56,11 @@ export const createQB = (
100
56
  expiresAt: toDateISO(data.expiresAt),
101
57
  updatedAt: toDateISO(data.updatedAt),
102
58
  clientId: data.clientId,
103
- clientAuth: toJsonObject(data.clientAuth),
59
+ clientAuth: toJson(data.clientAuth),
104
60
  deviceId: data.deviceId,
105
61
  did: data.sub,
106
- parameters: toJsonObject(data.parameters),
107
- details: data.details ? toJsonArray(data.details) : null,
62
+ parameters: toJson(data.parameters),
63
+ details: data.details ? toJson(data.details) : null,
108
64
  code: data.code,
109
65
  currentRefreshToken: refreshToken || null,
110
66
  })
@@ -120,6 +76,7 @@ export const findByQB = (
120
76
  db: AccountDb,
121
77
  search: {
122
78
  id?: number
79
+ did?: string
123
80
  code?: Code
124
81
  tokenId?: TokenId
125
82
  currentRefreshToken?: RefreshToken
@@ -127,6 +84,7 @@ export const findByQB = (
127
84
  ) => {
128
85
  if (
129
86
  search.id === undefined &&
87
+ search.did === undefined &&
130
88
  search.code === undefined &&
131
89
  search.tokenId === undefined &&
132
90
  search.currentRefreshToken === undefined
@@ -140,6 +98,10 @@ export const findByQB = (
140
98
  // uses primary key index
141
99
  qb.where('token.id', '=', search.id!),
142
100
  )
101
+ .if(search.did !== undefined, (qb) =>
102
+ // uses "token_did_idx" index
103
+ qb.where('token.did', '=', search.did!),
104
+ )
143
105
  .if(search.code !== undefined, (qb) =>
144
106
  // uses "token_code_idx" partial index (hence the null check)
145
107
  qb
@@ -175,7 +137,7 @@ export const rotateQB = (
175
137
 
176
138
  expiresAt: toDateISO(newData.expiresAt),
177
139
  updatedAt: toDateISO(newData.updatedAt),
178
- clientAuth: toJsonObject(newData.clientAuth),
140
+ clientAuth: toJson(newData.clientAuth),
179
141
  })
180
142
  // uses primary key index
181
143
  .where('id', '=', id)
@@ -1,13 +1,16 @@
1
+ import assert from 'node:assert'
1
2
  import { Client, createOp as createPlcOp } from '@did-plc/lib'
2
3
  import { Selectable } from 'kysely'
3
4
  import { Keypair, Secp256k1Keypair } from '@atproto/crypto'
4
5
  import {
5
6
  Account,
6
- AccountInfo,
7
7
  AccountStore,
8
8
  AuthenticateAccountData,
9
+ AuthorizedClientData,
10
+ AuthorizedClients,
11
+ ClientId,
9
12
  Code,
10
- DeviceAccountInfo,
13
+ DeviceAccount,
11
14
  DeviceData,
12
15
  DeviceId,
13
16
  DeviceStore,
@@ -23,6 +26,7 @@ import {
23
26
  ResetPasswordConfirmData,
24
27
  ResetPasswordRequestData,
25
28
  SignUpData,
29
+ Sub,
26
30
  TokenData,
27
31
  TokenId,
28
32
  TokenInfo,
@@ -35,16 +39,21 @@ import {
35
39
  } from '@atproto/xrpc-server'
36
40
  import { ActorStore } from '../actor-store/actor-store'
37
41
  import { BackgroundQueue } from '../background'
42
+ import { fromDateISO } from '../db'
38
43
  import { ImageUrlBuilder } from '../image/image-url-builder'
44
+ import { dbLogger } from '../logger'
39
45
  import { ServerMailer } from '../mailer'
40
46
  import { Sequencer, syncEvtDataFromCommit } from '../sequencer'
41
47
  import { AccountManager } from './account-manager'
42
- import { AccountStatus, ActorAccount } from './helpers/account'
43
- import * as authRequest from './helpers/authorization-request'
44
- import * as device from './helpers/device'
45
- import * as deviceAccount from './helpers/device-account'
46
- import * as token from './helpers/token'
47
- import * as usedRefreshToken from './helpers/used-refresh-token'
48
+ import * as schemas from './db/schema'
49
+ import * as accountHelper from './helpers/account'
50
+ import { AccountStatus } from './helpers/account'
51
+ import * as accountDeviceHelper from './helpers/account-device'
52
+ import * as authRequestHelper from './helpers/authorization-request'
53
+ import * as authorizedClientHelper from './helpers/authorized-client'
54
+ import * as deviceHelper from './helpers/device'
55
+ import * as tokenHelper from './helpers/token'
56
+ import * as usedRefreshTokenHelper from './helpers/used-refresh-token'
48
57
 
49
58
  /**
50
59
  * This class' purpose is to implement the interface needed by the OAuthProvider
@@ -78,29 +87,6 @@ export class OAuthStore
78
87
  return this.accountManager.serviceDid
79
88
  }
80
89
 
81
- private async buildAccount(row: Selectable<ActorAccount>): Promise<Account> {
82
- const account = deviceAccount.toAccount(row, this.serviceDid)
83
-
84
- if (!account.name || !account.picture) {
85
- const did = account.sub
86
-
87
- const profile = await this.actorStore.read(did, async (store) => {
88
- return store.record.getProfileRecord()
89
- })
90
-
91
- if (profile) {
92
- const { avatar, displayName } = profile
93
-
94
- account.name ||= displayName
95
- account.picture ||= avatar
96
- ? this.imageUrlBuilder.build('avatar', did, avatar.ref.toString())
97
- : undefined
98
- }
99
- }
100
-
101
- return account
102
- }
103
-
104
90
  private async verifyEmailAvailability(email: string): Promise<void> {
105
91
  // @NOTE Email validity & disposability check performed by the OAuthProvider
106
92
 
@@ -244,72 +230,99 @@ export class OAuthStore
244
230
  }
245
231
  }
246
232
 
247
- async addDeviceAccount(
248
- deviceId: DeviceId,
249
- sub: string,
250
- remember: boolean,
251
- ): Promise<DeviceAccountInfo> {
252
- const [row] = await this.db.executeWithRetry(
253
- deviceAccount.createOrUpdateQB(this.db, deviceId, sub, remember),
254
- )
255
- if (!row) throw new Error('Failed to create device account')
256
- return deviceAccount.toDeviceAccountInfo(row)
257
- }
258
-
259
- async addAuthorizedClient(
260
- deviceId: DeviceId,
261
- sub: string,
262
- clientId: string,
233
+ async setAuthorizedClient(
234
+ sub: Sub,
235
+ clientId: ClientId,
236
+ data: AuthorizedClientData,
263
237
  ): Promise<void> {
264
- await this.db.transaction(async (dbTxn) => {
265
- const row = await deviceAccount
266
- .readQB(dbTxn, deviceId, sub)
267
- .executeTakeFirstOrThrow()
238
+ await authorizedClientHelper.upsert(this.db, sub, clientId, data)
239
+ }
268
240
 
269
- const { authorizedClients } = deviceAccount.toDeviceAccountInfo(row)
270
- if (!authorizedClients.includes(clientId)) {
271
- await deviceAccount
272
- .updateQB(dbTxn, deviceId, sub, {
273
- authorizedClients: [...authorizedClients, clientId],
274
- })
275
- .execute()
276
- }
241
+ async getAccount(sub: Sub): Promise<{
242
+ account: Account
243
+ authorizedClients: AuthorizedClients
244
+ }> {
245
+ const accountRow = await accountHelper.getAccount(this.db, sub, {
246
+ includeDeactivated: true,
277
247
  })
248
+
249
+ assert(accountRow, 'Account not found')
250
+
251
+ const account = await this.buildAccount(accountRow)
252
+ const authorizedClients = await authorizedClientHelper.getAuthorizedClients(
253
+ this.db,
254
+ sub,
255
+ )
256
+
257
+ return { account, authorizedClients }
258
+ }
259
+
260
+ async upsertDeviceAccount(deviceId: DeviceId, sub: string): Promise<void> {
261
+ await this.db.executeWithRetry(
262
+ accountDeviceHelper.upsertQB(this.db, deviceId, sub),
263
+ )
278
264
  }
279
265
 
280
266
  async getDeviceAccount(
281
267
  deviceId: DeviceId,
282
268
  sub: string,
283
- ): Promise<AccountInfo | null> {
284
- const row = await deviceAccount
285
- .getAccountInfoQB(this.db, deviceId, sub)
269
+ ): Promise<DeviceAccount | null> {
270
+ const row = await accountDeviceHelper
271
+ .selectQB(this.db, { deviceId, sub })
286
272
  .executeTakeFirst()
287
273
 
288
274
  if (!row) return null
289
275
 
290
276
  return {
277
+ deviceId,
278
+ deviceData: deviceHelper.rowToDeviceData(row),
291
279
  account: await this.buildAccount(row),
292
- info: deviceAccount.toDeviceAccountInfo(row),
280
+ authorizedClients: await authorizedClientHelper.getAuthorizedClients(
281
+ this.db,
282
+ sub,
283
+ ),
284
+ createdAt: fromDateISO(row.adCreatedAt),
285
+ updatedAt: fromDateISO(row.adUpdatedAt),
293
286
  }
294
287
  }
295
288
 
296
- async listDeviceAccounts(deviceId: DeviceId): Promise<AccountInfo[]> {
297
- const rows = await deviceAccount
298
- .listRememberedQB(this.db, deviceId)
299
- .execute()
300
-
301
- return Promise.all(
302
- rows.map(async (row) => ({
303
- account: await this.buildAccount(row),
304
- info: deviceAccount.toDeviceAccountInfo(row),
305
- })),
289
+ async removeDeviceAccount(deviceId: DeviceId, sub: Sub): Promise<void> {
290
+ await this.db.executeWithRetry(
291
+ accountDeviceHelper.removeQB(this.db, deviceId, sub),
306
292
  )
307
293
  }
308
294
 
309
- async removeDeviceAccount(deviceId: DeviceId, sub: string): Promise<void> {
310
- await this.db.executeWithRetry(
311
- deviceAccount.removeQB(this.db, deviceId, sub),
295
+ async listDeviceAccounts(
296
+ filter: { sub: Sub } | { deviceId: DeviceId },
297
+ ): Promise<DeviceAccount[]> {
298
+ const rows = await accountDeviceHelper.selectQB(this.db, filter).execute()
299
+
300
+ const uniqueDids = [...new Set(rows.map((row) => row.did))]
301
+
302
+ // Enrich all distinct account with their profile data
303
+ const accounts = new Map(
304
+ await Promise.all(
305
+ Array.from(uniqueDids, async (did): Promise<[Sub, Account]> => {
306
+ const row = rows.find((r) => r.did === did)!
307
+ return [did, await this.buildAccount(row)]
308
+ }),
309
+ ),
312
310
  )
311
+
312
+ const authorizedClientsMap =
313
+ await authorizedClientHelper.getAuthorizedClientsMulti(
314
+ this.db,
315
+ uniqueDids,
316
+ )
317
+
318
+ return rows.map((row) => ({
319
+ deviceId: row.deviceId,
320
+ deviceData: deviceHelper.rowToDeviceData(row),
321
+ account: accounts.get(row.did)!,
322
+ authorizedClients: authorizedClientsMap.get(row.did)!,
323
+ createdAt: fromDateISO(row.adCreatedAt),
324
+ updatedAt: fromDateISO(row.adUpdatedAt),
325
+ }))
313
326
  }
314
327
 
315
328
  async resetPasswordRequest({
@@ -383,58 +396,70 @@ export class OAuthStore
383
396
  // RequestStore
384
397
 
385
398
  async createRequest(id: RequestId, data: RequestData): Promise<void> {
386
- await this.db.executeWithRetry(authRequest.createQB(this.db, id, data))
399
+ await this.db.executeWithRetry(
400
+ authRequestHelper.createQB(this.db, id, data),
401
+ )
387
402
  }
388
403
 
389
404
  async readRequest(id: RequestId): Promise<RequestData | null> {
390
405
  try {
391
- const row = await authRequest.readQB(this.db, id).executeTakeFirst()
406
+ const row = await authRequestHelper.readQB(this.db, id).executeTakeFirst()
392
407
  if (!row) return null
393
- return authRequest.rowToRequestData(row)
408
+ return authRequestHelper.rowToRequestData(row)
394
409
  } finally {
395
410
  // Take the opportunity to clean up expired requests. Do this after we got
396
411
  // the current (potentially expired) request data to allow the provider to
397
412
  // handle expired requests.
398
413
  this.backgroundQueue.add(async () => {
399
- await this.db.executeWithRetry(authRequest.removeOldExpiredQB(this.db))
414
+ await this.db.executeWithRetry(
415
+ authRequestHelper.removeOldExpiredQB(this.db),
416
+ )
400
417
  })
401
418
  }
402
419
  }
403
420
 
404
421
  async updateRequest(id: RequestId, data: UpdateRequestData): Promise<void> {
405
- await this.db.executeWithRetry(authRequest.updateQB(this.db, id, data))
422
+ await this.db.executeWithRetry(
423
+ authRequestHelper.updateQB(this.db, id, data),
424
+ )
406
425
  }
407
426
 
408
427
  async deleteRequest(id: RequestId): Promise<void> {
409
- await this.db.executeWithRetry(authRequest.removeByIdQB(this.db, id))
428
+ await this.db.executeWithRetry(authRequestHelper.removeByIdQB(this.db, id))
410
429
  }
411
430
 
412
431
  async findRequestByCode(code: Code): Promise<FoundRequestResult | null> {
413
- const row = await authRequest.findByCodeQB(this.db, code).executeTakeFirst()
414
- return row ? authRequest.rowToFoundRequestResult(row) : null
432
+ const row = await authRequestHelper
433
+ .findByCodeQB(this.db, code)
434
+ .executeTakeFirst()
435
+ return row ? authRequestHelper.rowToFoundRequestResult(row) : null
415
436
  }
416
437
 
417
438
  // DeviceStore
418
439
 
419
440
  async createDevice(deviceId: DeviceId, data: DeviceData): Promise<void> {
420
- await this.db.executeWithRetry(device.createQB(this.db, deviceId, data))
441
+ await this.db.executeWithRetry(
442
+ deviceHelper.createQB(this.db, deviceId, data),
443
+ )
421
444
  }
422
445
 
423
446
  async readDevice(deviceId: DeviceId): Promise<null | DeviceData> {
424
- const row = await device.readQB(this.db, deviceId).executeTakeFirst()
425
- return row ? device.rowToDeviceData(row) : null
447
+ const row = await deviceHelper.readQB(this.db, deviceId).executeTakeFirst()
448
+ return row ? deviceHelper.rowToDeviceData(row) : null
426
449
  }
427
450
 
428
451
  async updateDevice(
429
452
  deviceId: DeviceId,
430
453
  data: Partial<DeviceData>,
431
454
  ): Promise<void> {
432
- await this.db.executeWithRetry(device.updateQB(this.db, deviceId, data))
455
+ await this.db.executeWithRetry(
456
+ deviceHelper.updateQB(this.db, deviceId, data),
457
+ )
433
458
  }
434
459
 
435
460
  async deleteDevice(deviceId: DeviceId): Promise<void> {
436
461
  // Will cascade to device_account (device_account_device_id_fk)
437
- await this.db.executeWithRetry(device.removeQB(this.db, deviceId))
462
+ await this.db.executeWithRetry(deviceHelper.removeQB(this.db, deviceId))
438
463
  }
439
464
 
440
465
  // TokenStore
@@ -446,7 +471,7 @@ export class OAuthStore
446
471
  ): Promise<void> {
447
472
  await this.db.transaction(async (dbTxn) => {
448
473
  if (refreshToken) {
449
- const { count } = await usedRefreshToken
474
+ const { count } = await usedRefreshTokenHelper
450
475
  .countQB(dbTxn, refreshToken)
451
476
  .executeTakeFirstOrThrow()
452
477
 
@@ -455,18 +480,25 @@ export class OAuthStore
455
480
  }
456
481
  }
457
482
 
458
- return token.createQB(dbTxn, id, data, refreshToken).execute()
483
+ return tokenHelper.createQB(dbTxn, id, data, refreshToken).execute()
459
484
  })
460
485
  }
461
486
 
487
+ async listAccountTokens(sub: Sub): Promise<TokenInfo[]> {
488
+ const rows = await tokenHelper.findByQB(this.db, { did: sub }).execute()
489
+ return Promise.all(rows.map((row) => this.toTokenInfo(row)))
490
+ }
491
+
462
492
  async readToken(tokenId: TokenId): Promise<TokenInfo | null> {
463
- const row = await token.findByQB(this.db, { tokenId }).executeTakeFirst()
464
- return row ? token.toTokenInfo(row, this.serviceDid) : null
493
+ const row = await tokenHelper
494
+ .findByQB(this.db, { tokenId })
495
+ .executeTakeFirst()
496
+ return row ? this.toTokenInfo(row) : null
465
497
  }
466
498
 
467
499
  async deleteToken(tokenId: TokenId): Promise<void> {
468
500
  // Will cascade to used_refresh_token (used_refresh_token_fk)
469
- await this.db.executeWithRetry(token.removeQB(this.db, tokenId))
501
+ await this.db.executeWithRetry(tokenHelper.removeQB(this.db, tokenId))
470
502
  }
471
503
 
472
504
  async rotateToken(
@@ -476,17 +508,17 @@ export class OAuthStore
476
508
  newData: NewTokenData,
477
509
  ): Promise<void> {
478
510
  const err = await this.db.transaction(async (dbTxn) => {
479
- const { id, currentRefreshToken } = await token
511
+ const { id, currentRefreshToken } = await tokenHelper
480
512
  .forRotateQB(dbTxn, tokenId)
481
513
  .executeTakeFirstOrThrow()
482
514
 
483
515
  if (currentRefreshToken) {
484
- await usedRefreshToken
516
+ await usedRefreshTokenHelper
485
517
  .insertQB(dbTxn, id, currentRefreshToken)
486
518
  .execute()
487
519
  }
488
520
 
489
- const { count } = await usedRefreshToken
521
+ const { count } = await usedRefreshTokenHelper
490
522
  .countQB(dbTxn, newRefreshToken)
491
523
  .executeTakeFirstOrThrow()
492
524
 
@@ -495,7 +527,7 @@ export class OAuthStore
495
527
  return new Error('New refresh token already in use')
496
528
  }
497
529
 
498
- await token
530
+ await tokenHelper
499
531
  .rotateQB(dbTxn, id, newTokenId, newRefreshToken, newData)
500
532
  .execute()
501
533
  })
@@ -506,7 +538,7 @@ export class OAuthStore
506
538
  async findTokenByRefreshToken(
507
539
  refreshToken: RefreshToken,
508
540
  ): Promise<TokenInfo | null> {
509
- const used = await usedRefreshToken
541
+ const used = await usedRefreshTokenHelper
510
542
  .findByTokenQB(this.db, refreshToken)
511
543
  .executeTakeFirst()
512
544
 
@@ -514,12 +546,59 @@ export class OAuthStore
514
546
  ? { id: used.tokenId }
515
547
  : { currentRefreshToken: refreshToken }
516
548
 
517
- const row = await token.findByQB(this.db, search).executeTakeFirst()
518
- return row ? token.toTokenInfo(row, this.serviceDid) : null
549
+ const row = await tokenHelper.findByQB(this.db, search).executeTakeFirst()
550
+ return row ? this.toTokenInfo(row) : null
519
551
  }
520
552
 
521
553
  async findTokenByCode(code: Code): Promise<TokenInfo | null> {
522
- const row = await token.findByQB(this.db, { code }).executeTakeFirst()
523
- return row ? token.toTokenInfo(row, this.serviceDid) : null
554
+ const row = await tokenHelper.findByQB(this.db, { code }).executeTakeFirst()
555
+ return row ? this.toTokenInfo(row) : null
556
+ }
557
+
558
+ private async toTokenInfo(
559
+ row: accountHelper.ActorAccount & Selectable<schemas.Token>,
560
+ ): Promise<TokenInfo> {
561
+ return {
562
+ id: row.tokenId,
563
+ data: tokenHelper.toTokenData(row),
564
+ account: await this.buildAccount(row),
565
+ currentRefreshToken: row.currentRefreshToken,
566
+ }
567
+ }
568
+
569
+ private async buildAccount(
570
+ row: accountHelper.ActorAccount,
571
+ ): Promise<Account> {
572
+ const account: Account = {
573
+ sub: row.did,
574
+ aud: this.serviceDid,
575
+ email: row.email || undefined,
576
+ email_verified: row.email ? row.emailConfirmedAt != null : undefined,
577
+ preferred_username: row.handle || undefined,
578
+ }
579
+
580
+ if (!account.name || !account.picture) {
581
+ const did = account.sub
582
+
583
+ const profile = await this.actorStore
584
+ .read(did, async (store) => {
585
+ return store.record.getProfileRecord()
586
+ })
587
+ .catch((err) => {
588
+ dbLogger.error({ err }, 'Failed to get profile record')
589
+ return null // No need to propagate
590
+ })
591
+
592
+ if (profile) {
593
+ const { avatar, displayName } = profile
594
+
595
+ account.name ||= displayName
596
+ account.picture ||= avatar
597
+ ? this.imageUrlBuilder.build('avatar', did, avatar.ref.toString())
598
+ : undefined
599
+ }
600
+ }
601
+
602
+ return account
524
603
  }
525
604
  }
@@ -1,5 +1,8 @@
1
1
  import { Router } from 'express'
2
- import { oauthProtectedResourceMetadataSchema } from '@atproto/oauth-provider'
2
+ import {
3
+ oauthMiddleware,
4
+ oauthProtectedResourceMetadataSchema,
5
+ } from '@atproto/oauth-provider'
3
6
  import { AppContext } from './context'
4
7
  import { oauthLogger } from './logger'
5
8
 
@@ -30,12 +33,13 @@ export const createRouter = ({ oauthProvider, cfg }: AppContext): Router => {
30
33
  })
31
34
 
32
35
  if (oauthProvider) {
33
- const oauthMiddleware = oauthProvider.httpHandler({
34
- onError: (req, res, err, message) => {
35
- oauthLogger.error({ err, req }, message)
36
- },
37
- })
38
- router.use(oauthMiddleware)
36
+ router.use(
37
+ oauthMiddleware(oauthProvider, {
38
+ onError: (req, res, err, message) => {
39
+ oauthLogger.error({ err, req }, message)
40
+ },
41
+ }),
42
+ )
39
43
  }
40
44
 
41
45
  return router
@@ -139,7 +139,7 @@ export class AuthVerifier {
139
139
 
140
140
  accessStandard =
141
141
  (opts: Partial<AccessOpts> = {}) =>
142
- (ctx: ReqCtx): Promise<AccessOutput> => {
142
+ async (ctx: ReqCtx): Promise<AccessOutput> => {
143
143
  return this.validateAccessToken(
144
144
  ctx,
145
145
  [
@@ -528,18 +528,13 @@ export class AuthVerifier {
528
528
  )
529
529
  }
530
530
 
531
- const isPrivileged = [
532
- AuthScope.Access,
533
- AuthScope.AppPassPrivileged,
534
- ].includes(scopeEquivalent)
535
-
536
531
  return {
537
532
  credentials: {
538
533
  type: 'access',
539
534
  did: result.claims.sub,
540
535
  scope: scopeEquivalent,
541
536
  audience: this.dids.pds,
542
- isPrivileged,
537
+ isPrivileged: scopeEquivalent === AuthScope.AppPassPrivileged,
543
538
  },
544
539
  artifacts: result.token,
545
540
  }
@@ -275,7 +275,7 @@ export const envToCfg = (env: ServerEnvironment): ServerConfig => {
275
275
  name: env.serviceName ?? 'Personal PDS',
276
276
  logo: env.logoUrl,
277
277
  colors: {
278
- brand: env.brandColor,
278
+ primary: env.primaryColor,
279
279
  error: env.errorColor,
280
280
  success: env.successColor,
281
281
  warning: env.warningColor,
package/src/config/env.ts CHANGED
@@ -24,7 +24,7 @@ export const readEnv = (): ServerEnvironment => {
24
24
  hcaptchaTokenSalt: envStr('PDS_HCAPTCHA_TOKEN_SALT'),
25
25
 
26
26
  // branding
27
- brandColor: envStr('PDS_PRIMARY_COLOR'),
27
+ primaryColor: envStr('PDS_PRIMARY_COLOR'),
28
28
  errorColor: envStr('PDS_ERROR_COLOR'),
29
29
  warningColor: envStr('PDS_WARNING_COLOR'),
30
30
  successColor: envStr('PDS_SUCCESS_COLOR'),
@@ -163,7 +163,7 @@ export type ServerEnvironment = {
163
163
  hcaptchaTokenSalt?: string
164
164
 
165
165
  // branding
166
- brandColor?: string
166
+ primaryColor?: string
167
167
  errorColor?: string
168
168
  warningColor?: string
169
169
  successColor?: string