@atproto/pds 0.4.33 → 0.4.35

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 (227) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/account-manager/db/migrations/004-oauth.d.ts +4 -0
  3. package/dist/account-manager/db/migrations/004-oauth.d.ts.map +1 -0
  4. package/dist/account-manager/db/migrations/004-oauth.js +106 -0
  5. package/dist/account-manager/db/migrations/004-oauth.js.map +1 -0
  6. package/dist/account-manager/db/migrations/index.d.ts +2 -0
  7. package/dist/account-manager/db/migrations/index.d.ts.map +1 -1
  8. package/dist/account-manager/db/migrations/index.js +2 -0
  9. package/dist/account-manager/db/migrations/index.js.map +1 -1
  10. package/dist/account-manager/db/schema/authorization-request.d.ts +19 -0
  11. package/dist/account-manager/db/schema/authorization-request.d.ts.map +1 -0
  12. package/dist/account-manager/db/schema/authorization-request.js +5 -0
  13. package/dist/account-manager/db/schema/authorization-request.js.map +1 -0
  14. package/dist/account-manager/db/schema/device-account.d.ts +14 -0
  15. package/dist/account-manager/db/schema/device-account.d.ts.map +1 -0
  16. package/dist/account-manager/db/schema/device-account.js +5 -0
  17. package/dist/account-manager/db/schema/device-account.js.map +1 -0
  18. package/dist/account-manager/db/schema/device.d.ts +16 -0
  19. package/dist/account-manager/db/schema/device.d.ts.map +1 -0
  20. package/dist/account-manager/db/schema/device.js +5 -0
  21. package/dist/account-manager/db/schema/device.js.map +1 -0
  22. package/dist/account-manager/db/schema/index.d.ts +11 -1
  23. package/dist/account-manager/db/schema/index.d.ts.map +1 -1
  24. package/dist/account-manager/db/schema/token.d.ts +24 -0
  25. package/dist/account-manager/db/schema/token.d.ts.map +1 -0
  26. package/dist/account-manager/db/schema/token.js +5 -0
  27. package/dist/account-manager/db/schema/token.js.map +1 -0
  28. package/dist/account-manager/db/schema/used-refresh-token.d.ts +12 -0
  29. package/dist/account-manager/db/schema/used-refresh-token.d.ts.map +1 -0
  30. package/dist/account-manager/db/schema/used-refresh-token.js +5 -0
  31. package/dist/account-manager/db/schema/used-refresh-token.js.map +1 -0
  32. package/dist/account-manager/helpers/account.d.ts +27 -5
  33. package/dist/account-manager/helpers/account.d.ts.map +1 -1
  34. package/dist/account-manager/helpers/account.js +15 -14
  35. package/dist/account-manager/helpers/account.js.map +1 -1
  36. package/dist/account-manager/helpers/authorization-request.d.ts +12 -0
  37. package/dist/account-manager/helpers/authorization-request.d.ts.map +1 -0
  38. package/dist/account-manager/helpers/authorization-request.js +59 -0
  39. package/dist/account-manager/helpers/authorization-request.js.map +1 -0
  40. package/dist/account-manager/helpers/device-account.d.ts +108 -0
  41. package/dist/account-manager/helpers/device-account.d.ts.map +1 -0
  42. package/dist/account-manager/helpers/device-account.js +82 -0
  43. package/dist/account-manager/helpers/device-account.js.map +1 -0
  44. package/dist/account-manager/helpers/device.d.ts +9 -0
  45. package/dist/account-manager/helpers/device.d.ts.map +1 -0
  46. package/dist/account-manager/helpers/device.js +32 -0
  47. package/dist/account-manager/helpers/device.js.map +1 -0
  48. package/dist/account-manager/helpers/token.d.ts +485 -0
  49. package/dist/account-manager/helpers/token.d.ts.map +1 -0
  50. package/dist/account-manager/helpers/token.js +123 -0
  51. package/dist/account-manager/helpers/token.js.map +1 -0
  52. package/dist/account-manager/helpers/used-refresh-token.d.ts +10 -0
  53. package/dist/account-manager/helpers/used-refresh-token.d.ts.map +1 -0
  54. package/dist/account-manager/helpers/used-refresh-token.js +25 -0
  55. package/dist/account-manager/helpers/used-refresh-token.js.map +1 -0
  56. package/dist/account-manager/index.d.ts +36 -6
  57. package/dist/account-manager/index.d.ts.map +1 -1
  58. package/dist/account-manager/index.js +223 -22
  59. package/dist/account-manager/index.js.map +1 -1
  60. package/dist/actor-store/preference/reader.d.ts +2 -1
  61. package/dist/actor-store/preference/reader.d.ts.map +1 -1
  62. package/dist/actor-store/preference/reader.js +3 -1
  63. package/dist/actor-store/preference/reader.js.map +1 -1
  64. package/dist/actor-store/preference/transactor.d.ts +2 -1
  65. package/dist/actor-store/preference/transactor.d.ts.map +1 -1
  66. package/dist/actor-store/preference/transactor.js +7 -1
  67. package/dist/actor-store/preference/transactor.js.map +1 -1
  68. package/dist/actor-store/preference/util.d.ts +3 -0
  69. package/dist/actor-store/preference/util.d.ts.map +1 -0
  70. package/dist/actor-store/preference/util.js +12 -0
  71. package/dist/actor-store/preference/util.js.map +1 -0
  72. package/dist/actor-store/record/reader.d.ts +1 -1
  73. package/dist/api/app/bsky/actor/getPreferences.d.ts.map +1 -1
  74. package/dist/api/app/bsky/actor/getPreferences.js +1 -6
  75. package/dist/api/app/bsky/actor/getPreferences.js.map +1 -1
  76. package/dist/api/app/bsky/actor/putPreferences.d.ts.map +1 -1
  77. package/dist/api/app/bsky/actor/putPreferences.js +1 -1
  78. package/dist/api/app/bsky/actor/putPreferences.js.map +1 -1
  79. package/dist/api/app/bsky/util/resolver.d.ts +1 -1
  80. package/dist/api/com/atproto/server/createSession.d.ts.map +1 -1
  81. package/dist/api/com/atproto/server/createSession.js +7 -31
  82. package/dist/api/com/atproto/server/createSession.js.map +1 -1
  83. package/dist/api/com/atproto/server/deleteSession.d.ts.map +1 -1
  84. package/dist/api/com/atproto/server/deleteSession.js +14 -13
  85. package/dist/api/com/atproto/server/deleteSession.js.map +1 -1
  86. package/dist/api/com/atproto/server/getSession.d.ts.map +1 -1
  87. package/dist/api/com/atproto/server/getSession.js +4 -2
  88. package/dist/api/com/atproto/server/getSession.js.map +1 -1
  89. package/dist/api/com/atproto/server/refreshSession.d.ts.map +1 -1
  90. package/dist/api/com/atproto/server/refreshSession.js +4 -2
  91. package/dist/api/com/atproto/server/refreshSession.js.map +1 -1
  92. package/dist/api/com/atproto/sync/getRepoStatus.d.ts.map +1 -1
  93. package/dist/api/com/atproto/sync/getRepoStatus.js +2 -1
  94. package/dist/api/com/atproto/sync/getRepoStatus.js.map +1 -1
  95. package/dist/api/com/atproto/sync/listRepos.js +2 -2
  96. package/dist/api/com/atproto/sync/listRepos.js.map +1 -1
  97. package/dist/api/proxy.d.ts.map +1 -1
  98. package/dist/api/proxy.js +15 -2
  99. package/dist/api/proxy.js.map +1 -1
  100. package/dist/auth-routes.d.ts +4 -0
  101. package/dist/auth-routes.d.ts.map +1 -0
  102. package/dist/auth-routes.js +24 -0
  103. package/dist/auth-routes.js.map +1 -0
  104. package/dist/auth-verifier.d.ts +32 -11
  105. package/dist/auth-verifier.d.ts.map +1 -1
  106. package/dist/auth-verifier.js +238 -79
  107. package/dist/auth-verifier.js.map +1 -1
  108. package/dist/config/config.d.ts +12 -0
  109. package/dist/config/config.d.ts.map +1 -1
  110. package/dist/config/config.js +45 -0
  111. package/dist/config/config.js.map +1 -1
  112. package/dist/config/env.d.ts +8 -0
  113. package/dist/config/env.d.ts.map +1 -1
  114. package/dist/config/env.js +10 -0
  115. package/dist/config/env.js.map +1 -1
  116. package/dist/config/secrets.d.ts +1 -0
  117. package/dist/config/secrets.d.ts.map +1 -1
  118. package/dist/config/secrets.js +1 -0
  119. package/dist/config/secrets.js.map +1 -1
  120. package/dist/context.d.ts +6 -0
  121. package/dist/context.d.ts.map +1 -1
  122. package/dist/context.js +71 -13
  123. package/dist/context.js.map +1 -1
  124. package/dist/db/cast.d.ts +15 -0
  125. package/dist/db/cast.d.ts.map +1 -0
  126. package/dist/db/cast.js +66 -0
  127. package/dist/db/cast.js.map +1 -0
  128. package/dist/db/db.d.ts +2 -2
  129. package/dist/db/db.d.ts.map +1 -1
  130. package/dist/db/db.js +9 -7
  131. package/dist/db/db.js.map +1 -1
  132. package/dist/db/index.d.ts +1 -0
  133. package/dist/db/index.d.ts.map +1 -1
  134. package/dist/db/index.js +1 -0
  135. package/dist/db/index.js.map +1 -1
  136. package/dist/error.d.ts.map +1 -1
  137. package/dist/error.js +5 -0
  138. package/dist/error.js.map +1 -1
  139. package/dist/index.d.ts.map +1 -1
  140. package/dist/index.js +2 -0
  141. package/dist/index.js.map +1 -1
  142. package/dist/lexicon/index.d.ts +4 -0
  143. package/dist/lexicon/index.d.ts.map +1 -1
  144. package/dist/lexicon/index.js +8 -0
  145. package/dist/lexicon/index.js.map +1 -1
  146. package/dist/lexicon/lexicons.d.ts +51 -0
  147. package/dist/lexicon/lexicons.d.ts.map +1 -1
  148. package/dist/lexicon/lexicons.js +51 -0
  149. package/dist/lexicon/lexicons.js.map +1 -1
  150. package/dist/lexicon/types/app/bsky/feed/defs.d.ts +1 -0
  151. package/dist/lexicon/types/app/bsky/feed/defs.d.ts.map +1 -1
  152. package/dist/lexicon/types/app/bsky/feed/defs.js.map +1 -1
  153. package/dist/lexicon/types/app/bsky/graph/muteThread.d.ts +29 -0
  154. package/dist/lexicon/types/app/bsky/graph/muteThread.d.ts.map +1 -0
  155. package/dist/lexicon/types/app/bsky/graph/muteThread.js +3 -0
  156. package/dist/lexicon/types/app/bsky/graph/muteThread.js.map +1 -0
  157. package/dist/lexicon/types/app/bsky/graph/unmuteThread.d.ts +29 -0
  158. package/dist/lexicon/types/app/bsky/graph/unmuteThread.d.ts.map +1 -0
  159. package/dist/lexicon/types/app/bsky/graph/unmuteThread.js +3 -0
  160. package/dist/lexicon/types/app/bsky/graph/unmuteThread.js.map +1 -0
  161. package/dist/logger.d.ts +13 -11
  162. package/dist/logger.d.ts.map +1 -1
  163. package/dist/logger.js +80 -64
  164. package/dist/logger.js.map +1 -1
  165. package/dist/oauth/detailed-account-store.d.ts +27 -0
  166. package/dist/oauth/detailed-account-store.d.ts.map +1 -0
  167. package/dist/oauth/detailed-account-store.js +76 -0
  168. package/dist/oauth/detailed-account-store.js.map +1 -0
  169. package/dist/oauth/provider.d.ts +16 -0
  170. package/dist/oauth/provider.d.ts.map +1 -0
  171. package/dist/oauth/provider.js +45 -0
  172. package/dist/oauth/provider.js.map +1 -0
  173. package/dist/pipethrough.d.ts.map +1 -1
  174. package/dist/pipethrough.js.map +1 -1
  175. package/dist/sequencer/events.d.ts +2 -2
  176. package/example.env +21 -3
  177. package/package.json +9 -7
  178. package/src/account-manager/db/migrations/004-oauth.ts +122 -0
  179. package/src/account-manager/db/migrations/index.ts +2 -0
  180. package/src/account-manager/db/schema/authorization-request.ts +26 -0
  181. package/src/account-manager/db/schema/device-account.ts +15 -0
  182. package/src/account-manager/db/schema/device.ts +18 -0
  183. package/src/account-manager/db/schema/index.ts +15 -0
  184. package/src/account-manager/db/schema/token.ts +34 -0
  185. package/src/account-manager/db/schema/used-refresh-token.ts +13 -0
  186. package/src/account-manager/helpers/account.ts +16 -21
  187. package/src/account-manager/helpers/authorization-request.ts +82 -0
  188. package/src/account-manager/helpers/device-account.ts +135 -0
  189. package/src/account-manager/helpers/device.ts +45 -0
  190. package/src/account-manager/helpers/token.ts +185 -0
  191. package/src/account-manager/helpers/used-refresh-token.ts +30 -0
  192. package/src/account-manager/index.ts +325 -20
  193. package/src/actor-store/preference/reader.ts +8 -2
  194. package/src/actor-store/preference/transactor.ts +10 -0
  195. package/src/actor-store/preference/util.ts +8 -0
  196. package/src/api/app/bsky/actor/getPreferences.ts +2 -9
  197. package/src/api/app/bsky/actor/putPreferences.ts +5 -1
  198. package/src/api/com/atproto/server/createSession.ts +8 -44
  199. package/src/api/com/atproto/server/deleteSession.ts +14 -20
  200. package/src/api/com/atproto/server/getSession.ts +7 -2
  201. package/src/api/com/atproto/server/refreshSession.ts +6 -2
  202. package/src/api/com/atproto/sync/getRepoStatus.ts +3 -1
  203. package/src/api/com/atproto/sync/listRepos.ts +1 -1
  204. package/src/api/proxy.ts +18 -2
  205. package/src/auth-routes.ts +27 -0
  206. package/src/auth-verifier.ts +312 -92
  207. package/src/config/config.ts +66 -0
  208. package/src/config/env.ts +24 -0
  209. package/src/config/secrets.ts +2 -0
  210. package/src/context.ts +80 -14
  211. package/src/db/cast.ts +59 -0
  212. package/src/db/db.ts +15 -12
  213. package/src/db/index.ts +1 -0
  214. package/src/error.ts +7 -0
  215. package/src/index.ts +2 -0
  216. package/src/lexicon/index.ts +24 -0
  217. package/src/lexicon/lexicons.ts +52 -0
  218. package/src/lexicon/types/app/bsky/feed/defs.ts +1 -0
  219. package/src/lexicon/types/app/bsky/graph/muteThread.ts +38 -0
  220. package/src/lexicon/types/app/bsky/graph/unmuteThread.ts +38 -0
  221. package/src/logger.ts +83 -38
  222. package/src/oauth/detailed-account-store.ts +96 -0
  223. package/src/oauth/provider.ts +77 -0
  224. package/src/pipethrough.ts +3 -2
  225. package/tests/preferences.test.ts +67 -1
  226. package/tests/proxied/__snapshots__/feedgen.test.ts.snap +4 -1
  227. package/tests/proxied/__snapshots__/views.test.ts.snap +116 -38
@@ -47,9 +47,9 @@ export declare const commitEvt: z.ZodObject<{
47
47
  }>, "many">;
48
48
  blobs: z.ZodArray<z.ZodEffects<z.ZodEffects<z.ZodAny, any, any>, CID, any>, "many">;
49
49
  }, "strip", z.ZodTypeAny, {
50
- rev: string;
51
50
  repo: string;
52
51
  blobs: CID[];
52
+ rev: string;
53
53
  rebase: boolean;
54
54
  tooBig: boolean;
55
55
  commit: CID;
@@ -62,9 +62,9 @@ export declare const commitEvt: z.ZodObject<{
62
62
  }[];
63
63
  prev: CID | null;
64
64
  }, {
65
- rev: string;
66
65
  repo: string;
67
66
  blobs: any[];
67
+ rev: string;
68
68
  rebase: boolean;
69
69
  tooBig: boolean;
70
70
  since: string | null;
package/example.env CHANGED
@@ -1,10 +1,10 @@
1
1
  # See more env options in src/config/env.ts
2
2
  # Hostname - the public domain that you intend to deploy your service at
3
3
  PDS_HOSTNAME="example.com"
4
+ PDS_PORT="2583"
4
5
 
5
6
  # Database config - use one or the other
6
- PDS_DB_SQLITE_LOCATION="db.test"
7
- # PDS_DB_POSTGRES_URL="postgresql://pg:password@localhost:5433/postgres"
7
+ PDS_DATA_DIRECTORY="data"
8
8
 
9
9
  # Blobstore - filesystem location to store uploaded blobs
10
10
  PDS_BLOBSTORE_DISK_LOCATION="blobs"
@@ -14,6 +14,7 @@ PDS_REPO_SIGNING_KEY_K256_PRIVATE_KEY_HEX="3ee68..."
14
14
  PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX="e049f..."
15
15
 
16
16
  # Secrets - update to secure high-entropy strings
17
+ PDS_DPOP_SECRET="32-random-bytes-hex-encoded"
17
18
  PDS_JWT_SECRET="jwt-secret"
18
19
  PDS_ADMIN_PASSWORD="admin-pass"
19
20
 
@@ -21,4 +22,21 @@ PDS_ADMIN_PASSWORD="admin-pass"
21
22
  PDS_DID_PLC_URL="https://plc.bsky-sandbox.dev"
22
23
  PDS_BSKY_APP_VIEW_ENDPOINT="https://api.bsky-sandbox.dev"
23
24
  PDS_BSKY_APP_VIEW_DID="did:web:api.bsky-sandbox.dev"
24
- PDS_CRAWLERS="https://bgs.bsky-sandbox.dev"
25
+ PDS_CRAWLERS="https://bgs.bsky-sandbox.dev"
26
+
27
+ # OAuth Provider
28
+ PDS_OAUTH_PROVIDER_NAME="John's self hosted PDS"
29
+ PDS_OAUTH_PROVIDER_LOGO=
30
+ PDS_OAUTH_PROVIDER_PRIMARY_COLOR="#7507e3"
31
+ PDS_OAUTH_PROVIDER_ERROR_COLOR=
32
+ PDS_OAUTH_PROVIDER_HOME_LINK=
33
+ PDS_OAUTH_PROVIDER_TOS_LINK=
34
+ PDS_OAUTH_PROVIDER_POLICY_LINK=
35
+ PDS_OAUTH_PROVIDER_SUPPORT_LINK=
36
+
37
+ # Debugging
38
+ NODE_TLS_REJECT_UNAUTHORIZED=1
39
+ LOG_ENABLED=0
40
+ LOG_LEVEL=info
41
+ PDS_INVITE_REQUIRED=1
42
+ PDS_DISABLE_SSRF_PROTECTION=0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atproto/pds",
3
- "version": "0.4.33",
3
+ "version": "0.4.35",
4
4
  "license": "MIT",
5
5
  "description": "Reference implementation of atproto Personal Data Server (PDS)",
6
6
  "keywords": [
@@ -18,7 +18,7 @@
18
18
  "bin": "dist/bin.js",
19
19
  "dependencies": {
20
20
  "@did-plc/lib": "^0.0.4",
21
- "better-sqlite3": "^9.4.0",
21
+ "better-sqlite3": "^10.0.0",
22
22
  "bytes": "^3.1.2",
23
23
  "compression": "^1.7.4",
24
24
  "cors": "^2.8.5",
@@ -37,18 +37,20 @@
37
37
  "nodemailer": "^6.8.0",
38
38
  "nodemailer-html-to-text": "^3.2.0",
39
39
  "p-queue": "^6.6.2",
40
- "pino": "^8.15.0",
40
+ "pino": "^8.21.0",
41
41
  "pino-http": "^8.2.1",
42
42
  "sharp": "^0.32.6",
43
43
  "typed-emitter": "^2.1.0",
44
44
  "uint8arrays": "3.0.0",
45
- "zod": "^3.21.4",
46
- "@atproto/api": "^0.12.18",
45
+ "zod": "^3.23.8",
46
+ "@atproto-labs/fetch-node": "0.1.0",
47
+ "@atproto/api": "^0.12.19",
47
48
  "@atproto/aws": "^0.2.0",
48
49
  "@atproto/common": "^0.4.0",
49
50
  "@atproto/crypto": "^0.4.0",
50
51
  "@atproto/identity": "^0.4.0",
51
52
  "@atproto/lexicon": "^0.4.0",
53
+ "@atproto/oauth-provider": "^0.1.0",
52
54
  "@atproto/repo": "^0.4.0",
53
55
  "@atproto/syntax": "^0.3.0",
54
56
  "@atproto/xrpc": "^0.5.0",
@@ -70,8 +72,8 @@
70
72
  "jest": "^28.1.2",
71
73
  "ts-node": "^10.8.2",
72
74
  "ws": "^8.12.0",
73
- "@atproto/api": "^0.12.18",
74
- "@atproto/bsky": "^0.0.61",
75
+ "@atproto/api": "^0.12.19",
76
+ "@atproto/bsky": "^0.0.62",
75
77
  "@atproto/lex-cli": "^0.4.0"
76
78
  },
77
79
  "scripts": {
@@ -0,0 +1,122 @@
1
+ import { Kysely, sql } from 'kysely'
2
+
3
+ export async function up(db: Kysely<unknown>): Promise<void> {
4
+ await db.schema
5
+ .createTable('authorization_request')
6
+ .addColumn('id', 'varchar', (col) => col.primaryKey())
7
+ .addColumn('did', 'varchar')
8
+ .addColumn('deviceId', 'varchar')
9
+ .addColumn('clientId', 'varchar', (col) => col.notNull())
10
+ .addColumn('clientAuth', 'varchar', (col) => col.notNull())
11
+ .addColumn('parameters', 'varchar', (col) => col.notNull())
12
+ .addColumn('expiresAt', 'varchar', (col) => col.notNull())
13
+ .addColumn('code', 'varchar')
14
+ .execute()
15
+
16
+ await db.schema
17
+ .createIndex('authorization_request_code_idx')
18
+ .unique()
19
+ .on('authorization_request')
20
+ // https://github.com/kysely-org/kysely/issues/302
21
+ .expression(sql`code DESC) WHERE (code IS NOT NULL`)
22
+ .execute()
23
+
24
+ await db.schema
25
+ .createIndex('authorization_request_expires_at_idx')
26
+ .on('authorization_request')
27
+ .column('expiresAt')
28
+ .execute()
29
+
30
+ await db.schema
31
+ .createTable('device')
32
+ .addColumn('id', 'varchar', (col) => col.primaryKey())
33
+ .addColumn('sessionId', 'varchar', (col) => col.notNull())
34
+ .addColumn('userAgent', 'varchar')
35
+ .addColumn('ipAddress', 'varchar', (col) => col.notNull())
36
+ .addColumn('lastSeenAt', 'varchar', (col) => col.notNull())
37
+ .addUniqueConstraint('device_session_id_idx', ['sessionId'])
38
+ .execute()
39
+
40
+ await db.schema
41
+ .createTable('device_account')
42
+ .addColumn('did', 'varchar', (col) => col.notNull())
43
+ .addColumn('deviceId', 'varchar', (col) => col.notNull())
44
+ .addColumn('authenticatedAt', 'varchar', (col) => col.notNull())
45
+ .addColumn('remember', 'boolean', (col) => col.notNull())
46
+ .addColumn('authorizedClients', 'varchar', (col) => col.notNull())
47
+ .addPrimaryKeyConstraint('device_account_pk', [
48
+ 'deviceId', // first because this table will be joined from the "device" table
49
+ 'did',
50
+ ])
51
+ .addForeignKeyConstraint(
52
+ 'device_account_device_id_fk',
53
+ ['deviceId'],
54
+ 'device',
55
+ ['id'],
56
+ (qb) => qb.onDelete('cascade').onUpdate('cascade'),
57
+ )
58
+ .execute()
59
+
60
+ await db.schema
61
+ .createTable('token')
62
+ .addColumn('id', 'integer', (col) => col.primaryKey().autoIncrement())
63
+ .addColumn('did', 'varchar', (col) => col.notNull())
64
+ .addColumn('tokenId', 'varchar', (col) => col.notNull())
65
+ .addColumn('createdAt', 'varchar', (col) => col.notNull())
66
+ .addColumn('updatedAt', 'varchar', (col) => col.notNull())
67
+ .addColumn('expiresAt', 'varchar', (col) => col.notNull())
68
+ .addColumn('clientId', 'varchar', (col) => col.notNull())
69
+ .addColumn('clientAuth', 'varchar', (col) => col.notNull())
70
+ .addColumn('deviceId', 'varchar')
71
+ .addColumn('parameters', 'varchar', (col) => col.notNull())
72
+ .addColumn('details', 'varchar')
73
+ .addColumn('code', 'varchar')
74
+ .addColumn('currentRefreshToken', 'varchar')
75
+ .addUniqueConstraint('token_current_refresh_token_unique_idx', [
76
+ 'currentRefreshToken',
77
+ ])
78
+ .addUniqueConstraint('token_id_unique_idx', ['tokenId'])
79
+ .execute()
80
+
81
+ await db.schema
82
+ .createIndex('token_did_idx')
83
+ .on('token')
84
+ .column('did')
85
+ .execute()
86
+
87
+ await db.schema
88
+ .createIndex('token_code_idx')
89
+ .unique()
90
+ .on('token')
91
+ // https://github.com/kysely-org/kysely/issues/302
92
+ .expression(sql`code DESC) WHERE (code IS NOT NULL`)
93
+ .execute()
94
+
95
+ await db.schema
96
+ .createTable('used_refresh_token')
97
+ .addColumn('refreshToken', 'varchar', (col) => col.primaryKey())
98
+ .addColumn('tokenId', 'integer', (col) => col.notNull())
99
+ .addForeignKeyConstraint(
100
+ 'used_refresh_token_fk',
101
+ ['tokenId'],
102
+ 'token',
103
+ ['id'],
104
+ // uses "used_refresh_token_id_idx" index (when cascading)
105
+ (qb) => qb.onDelete('cascade').onUpdate('cascade'),
106
+ )
107
+ .execute()
108
+
109
+ await db.schema
110
+ .createIndex('used_refresh_token_id_idx')
111
+ .on('used_refresh_token')
112
+ .column('tokenId')
113
+ .execute()
114
+ }
115
+
116
+ export async function down(db: Kysely<unknown>): Promise<void> {
117
+ await db.schema.dropTable('used_refresh_token').execute()
118
+ await db.schema.dropTable('token').execute()
119
+ await db.schema.dropTable('device_account').execute()
120
+ await db.schema.dropTable('device').execute()
121
+ await db.schema.dropTable('authorization_request').execute()
122
+ }
@@ -1,9 +1,11 @@
1
1
  import * as mig001 from './001-init'
2
2
  import * as mig002 from './002-account-deactivation'
3
3
  import * as mig003 from './003-privileged-app-passwords'
4
+ import * as mig004 from './004-oauth'
4
5
 
5
6
  export default {
6
7
  '001': mig001,
7
8
  '002': mig002,
8
9
  '003': mig003,
10
+ '004': mig004,
9
11
  }
@@ -0,0 +1,26 @@
1
+ import {
2
+ Code,
3
+ DeviceId,
4
+ OAuthClientId,
5
+ RequestId,
6
+ } from '@atproto/oauth-provider'
7
+ import { Selectable } from 'kysely'
8
+ import { DateISO, JsonObject } from '../../../db'
9
+
10
+ export interface AuthorizationRequest {
11
+ id: RequestId
12
+ did: string | null
13
+ deviceId: DeviceId | null
14
+
15
+ clientId: OAuthClientId
16
+ clientAuth: JsonObject
17
+ parameters: JsonObject
18
+ expiresAt: DateISO
19
+ code: Code | null
20
+ }
21
+
22
+ export type AuthorizationRequestEntry = Selectable<AuthorizationRequest>
23
+
24
+ export const tableName = 'authorization_request'
25
+
26
+ export type PartialDB = { [tableName]: AuthorizationRequest }
@@ -0,0 +1,15 @@
1
+ import { DeviceId } from '@atproto/oauth-provider'
2
+ import { DateISO, JsonArray } from '../../../db'
3
+
4
+ export interface DeviceAccount {
5
+ did: string
6
+ deviceId: DeviceId
7
+
8
+ authenticatedAt: DateISO
9
+ authorizedClients: JsonArray
10
+ remember: 0 | 1
11
+ }
12
+
13
+ export const tableName = 'device_account'
14
+
15
+ export type PartialDB = { [tableName]: DeviceAccount }
@@ -0,0 +1,18 @@
1
+ import { DeviceId, SessionId } from '@atproto/oauth-provider'
2
+ import { Selectable } from 'kysely'
3
+ import { DateISO } from '../../../db'
4
+
5
+ export interface Device {
6
+ id: DeviceId
7
+ sessionId: SessionId
8
+
9
+ userAgent: string | null
10
+ ipAddress: string
11
+ lastSeenAt: DateISO
12
+ }
13
+
14
+ export type DeviceEntry = Selectable<Device>
15
+
16
+ export const tableName = 'device'
17
+
18
+ export type PartialDB = { [tableName]: Device }
@@ -1,5 +1,10 @@
1
1
  import * as actor from './actor'
2
2
  import * as account from './account'
3
+ import * as device from './device'
4
+ import * as deviceAccount from './device-account'
5
+ import * as oauthRequest from './authorization-request'
6
+ import * as token from './token'
7
+ import * as usedRefreshToken from './used-refresh-token'
3
8
  import * as repoRoot from './repo-root'
4
9
  import * as refreshToken from './refresh-token'
5
10
  import * as appPassword from './app-password'
@@ -8,6 +13,11 @@ import * as emailToken from './email-token'
8
13
 
9
14
  export type DatabaseSchema = actor.PartialDB &
10
15
  account.PartialDB &
16
+ device.PartialDB &
17
+ deviceAccount.PartialDB &
18
+ oauthRequest.PartialDB &
19
+ token.PartialDB &
20
+ usedRefreshToken.PartialDB &
11
21
  refreshToken.PartialDB &
12
22
  appPassword.PartialDB &
13
23
  repoRoot.PartialDB &
@@ -16,6 +26,11 @@ export type DatabaseSchema = actor.PartialDB &
16
26
 
17
27
  export type { Actor, ActorEntry } from './actor'
18
28
  export type { Account, AccountEntry } from './account'
29
+ export type { Device } from './device'
30
+ export type { DeviceAccount } from './device-account'
31
+ export type { AuthorizationRequest } from './authorization-request'
32
+ export type { Token } from './token'
33
+ export type { UsedRefreshToken } from './used-refresh-token'
19
34
  export type { RepoRoot } from './repo-root'
20
35
  export type { RefreshToken } from './refresh-token'
21
36
  export type { AppPassword } from './app-password'
@@ -0,0 +1,34 @@
1
+ import {
2
+ Code,
3
+ DeviceId,
4
+ OAuthClientId,
5
+ RefreshToken,
6
+ Sub,
7
+ TokenId,
8
+ } from '@atproto/oauth-provider'
9
+ import { Generated, Selectable } from 'kysely'
10
+
11
+ import { DateISO, JsonArray, JsonObject } from '../../../db/cast'
12
+
13
+ export interface Token {
14
+ id: Generated<number>
15
+ did: Sub
16
+
17
+ tokenId: TokenId
18
+ createdAt: DateISO
19
+ updatedAt: DateISO
20
+ expiresAt: DateISO
21
+ clientId: OAuthClientId
22
+ clientAuth: JsonObject
23
+ deviceId: DeviceId | null
24
+ parameters: JsonObject
25
+ details: JsonArray | null
26
+ code: Code | null
27
+ currentRefreshToken: RefreshToken | null
28
+ }
29
+
30
+ export type TokenEntry = Selectable<Token>
31
+
32
+ export const tableName = 'token'
33
+
34
+ export type PartialDB = { [tableName]: Token }
@@ -0,0 +1,13 @@
1
+ import { RefreshToken } from '@atproto/oauth-provider'
2
+ import { Selectable } from 'kysely'
3
+
4
+ export interface UsedRefreshToken {
5
+ tokenId: number
6
+ refreshToken: RefreshToken
7
+ }
8
+
9
+ export type UsedRefreshTokenEntry = Selectable<UsedRefreshToken>
10
+
11
+ export const tableName = 'used_refresh_token'
12
+
13
+ export type PartialDB = { [tableName]: UsedRefreshToken }
@@ -9,8 +9,6 @@ export type ActorAccount = ActorEntry & {
9
9
  email: string | null
10
10
  emailConfirmedAt: string | null
11
11
  invitesDisabled: 0 | 1 | null
12
- active: boolean
13
- status?: AccountStatus
14
12
  }
15
13
 
16
14
  export type AvailabilityFlags = {
@@ -26,7 +24,7 @@ export enum AccountStatus {
26
24
  Deactivated = 'deactivated',
27
25
  }
28
26
 
29
- const selectAccountQB = (db: AccountDb, flags?: AvailabilityFlags) => {
27
+ export const selectAccountQB = (db: AccountDb, flags?: AvailabilityFlags) => {
30
28
  const { includeTakenDown = false, includeDeactivated = false } = flags ?? {}
31
29
  const { ref } = db.db.dynamic
32
30
  return db.db
@@ -63,7 +61,7 @@ export const getAccount = async (
63
61
  }
64
62
  })
65
63
  .executeTakeFirst()
66
- return found ? { ...found, ...formatAccountStatus(found) } : null
64
+ return found || null
67
65
  }
68
66
 
69
67
  export const getAccountByEmail = async (
@@ -74,7 +72,7 @@ export const getAccountByEmail = async (
74
72
  const found = await selectAccountQB(db, flags)
75
73
  .where('email', '=', email.toLowerCase())
76
74
  .executeTakeFirst()
77
- return found ? { ...found, ...formatAccountStatus(found) } : null
75
+ return found || null
78
76
  }
79
77
 
80
78
  export const registerActor = async (
@@ -267,22 +265,19 @@ export const activateAccount = async (db: AccountDb, did: string) => {
267
265
  )
268
266
  }
269
267
 
270
- export const formatAccountStatus = (account: {
271
- takedownRef: string | null
272
- deactivatedAt: string | null
273
- }): {
274
- active: boolean
275
- status?: AccountStatus
276
- } => {
277
- let status: AccountStatus | undefined = undefined
278
- if (account.takedownRef) {
279
- status = AccountStatus.Takendown
268
+ export const formatAccountStatus = (
269
+ account: null | {
270
+ takedownRef: string | null
271
+ deactivatedAt: string | null
272
+ },
273
+ ) => {
274
+ if (!account) {
275
+ return { active: false, status: AccountStatus.Deleted } as const
276
+ } else if (account.takedownRef) {
277
+ return { active: false, status: AccountStatus.Takendown } as const
280
278
  } else if (account.deactivatedAt) {
281
- status = AccountStatus.Deactivated
282
- }
283
- const active = !status
284
- return {
285
- active,
286
- status,
279
+ return { active: false, status: AccountStatus.Deactivated } as const
280
+ } else {
281
+ return { active: true, status: undefined } as const
287
282
  }
288
283
  }
@@ -0,0 +1,82 @@
1
+ import {
2
+ Code,
3
+ FoundRequestResult,
4
+ RequestData,
5
+ RequestId,
6
+ UpdateRequestData,
7
+ } from '@atproto/oauth-provider'
8
+ import { AccountDb, AuthorizationRequest } from '../db'
9
+ import { fromDateISO, fromJsonObject, toDateISO, toJsonObject } from '../../db'
10
+ import { Insertable, Selectable } from 'kysely'
11
+
12
+ export const rowToRequestData = (
13
+ row: Selectable<AuthorizationRequest>,
14
+ ): RequestData => ({
15
+ clientId: row.clientId,
16
+ clientAuth: fromJsonObject(row.clientAuth),
17
+ parameters: fromJsonObject(row.parameters),
18
+ expiresAt: fromDateISO(row.expiresAt),
19
+ deviceId: row.deviceId,
20
+ sub: row.did,
21
+ code: row.code,
22
+ })
23
+
24
+ export const rowToFoundRequestResult = (
25
+ row: Selectable<AuthorizationRequest>,
26
+ ): FoundRequestResult => ({
27
+ id: row.id,
28
+ data: rowToRequestData(row),
29
+ })
30
+
31
+ const requestDataToRow = (
32
+ id: RequestId,
33
+ data: RequestData,
34
+ ): Insertable<AuthorizationRequest> => ({
35
+ id,
36
+ did: data.sub,
37
+ deviceId: data.deviceId,
38
+
39
+ clientId: data.clientId,
40
+ clientAuth: toJsonObject(data.clientAuth),
41
+ parameters: toJsonObject(data.parameters),
42
+ expiresAt: toDateISO(data.expiresAt),
43
+ code: data.code,
44
+ })
45
+
46
+ export const createQB = (db: AccountDb, id: RequestId, data: RequestData) =>
47
+ db.db.insertInto('authorization_request').values(requestDataToRow(id, data))
48
+
49
+ export const readQB = (db: AccountDb, id: RequestId) =>
50
+ db.db.selectFrom('authorization_request').where('id', '=', id).selectAll()
51
+
52
+ export const updateQB = (
53
+ db: AccountDb,
54
+ id: RequestId,
55
+ { code, sub, deviceId, expiresAt }: UpdateRequestData,
56
+ ) =>
57
+ db.db
58
+ .updateTable('authorization_request')
59
+ .if(code !== undefined, (qb) => qb.set({ code }))
60
+ .if(sub !== undefined, (qb) => qb.set({ did: sub }))
61
+ .if(deviceId !== undefined, (qb) => qb.set({ deviceId }))
62
+ .if(expiresAt != null, (qb) => qb.set({ expiresAt: toDateISO(expiresAt!) }))
63
+ .where('id', '=', id)
64
+
65
+ export const removeOldExpiredQB = (db: AccountDb, delay = 600e3) =>
66
+ // We allow some delay for the expiration time so that expired requests
67
+ // can still be returned to the OAuthProvider library for error handling.
68
+ db.db
69
+ .deleteFrom('authorization_request')
70
+ // uses "authorization_request_expires_at_idx" index
71
+ .where('expiresAt', '<', toDateISO(new Date(Date.now() - delay)))
72
+
73
+ export const removeByIdQB = (db: AccountDb, id: RequestId) =>
74
+ db.db.deleteFrom('authorization_request').where('id', '=', id)
75
+
76
+ export const findByCodeQB = (db: AccountDb, code: Code) =>
77
+ db.db
78
+ .selectFrom('authorization_request')
79
+ // uses "authorization_request_code_idx" partial index (hence the null check)
80
+ .where('code', '=', code)
81
+ .where('code', 'is not', null)
82
+ .selectAll()