@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.
- package/CHANGELOG.md +17 -0
- package/dist/account-manager/db/migrations/004-oauth.d.ts +4 -0
- package/dist/account-manager/db/migrations/004-oauth.d.ts.map +1 -0
- package/dist/account-manager/db/migrations/004-oauth.js +106 -0
- package/dist/account-manager/db/migrations/004-oauth.js.map +1 -0
- package/dist/account-manager/db/migrations/index.d.ts +2 -0
- package/dist/account-manager/db/migrations/index.d.ts.map +1 -1
- package/dist/account-manager/db/migrations/index.js +2 -0
- package/dist/account-manager/db/migrations/index.js.map +1 -1
- package/dist/account-manager/db/schema/authorization-request.d.ts +19 -0
- package/dist/account-manager/db/schema/authorization-request.d.ts.map +1 -0
- package/dist/account-manager/db/schema/authorization-request.js +5 -0
- package/dist/account-manager/db/schema/authorization-request.js.map +1 -0
- package/dist/account-manager/db/schema/device-account.d.ts +14 -0
- package/dist/account-manager/db/schema/device-account.d.ts.map +1 -0
- package/dist/account-manager/db/schema/device-account.js +5 -0
- package/dist/account-manager/db/schema/device-account.js.map +1 -0
- package/dist/account-manager/db/schema/device.d.ts +16 -0
- package/dist/account-manager/db/schema/device.d.ts.map +1 -0
- package/dist/account-manager/db/schema/device.js +5 -0
- package/dist/account-manager/db/schema/device.js.map +1 -0
- package/dist/account-manager/db/schema/index.d.ts +11 -1
- package/dist/account-manager/db/schema/index.d.ts.map +1 -1
- package/dist/account-manager/db/schema/token.d.ts +24 -0
- package/dist/account-manager/db/schema/token.d.ts.map +1 -0
- package/dist/account-manager/db/schema/token.js +5 -0
- package/dist/account-manager/db/schema/token.js.map +1 -0
- package/dist/account-manager/db/schema/used-refresh-token.d.ts +12 -0
- package/dist/account-manager/db/schema/used-refresh-token.d.ts.map +1 -0
- package/dist/account-manager/db/schema/used-refresh-token.js +5 -0
- package/dist/account-manager/db/schema/used-refresh-token.js.map +1 -0
- package/dist/account-manager/helpers/account.d.ts +27 -5
- package/dist/account-manager/helpers/account.d.ts.map +1 -1
- package/dist/account-manager/helpers/account.js +15 -14
- package/dist/account-manager/helpers/account.js.map +1 -1
- package/dist/account-manager/helpers/authorization-request.d.ts +12 -0
- package/dist/account-manager/helpers/authorization-request.d.ts.map +1 -0
- package/dist/account-manager/helpers/authorization-request.js +59 -0
- package/dist/account-manager/helpers/authorization-request.js.map +1 -0
- package/dist/account-manager/helpers/device-account.d.ts +108 -0
- package/dist/account-manager/helpers/device-account.d.ts.map +1 -0
- package/dist/account-manager/helpers/device-account.js +82 -0
- package/dist/account-manager/helpers/device-account.js.map +1 -0
- package/dist/account-manager/helpers/device.d.ts +9 -0
- package/dist/account-manager/helpers/device.d.ts.map +1 -0
- package/dist/account-manager/helpers/device.js +32 -0
- package/dist/account-manager/helpers/device.js.map +1 -0
- package/dist/account-manager/helpers/token.d.ts +485 -0
- package/dist/account-manager/helpers/token.d.ts.map +1 -0
- package/dist/account-manager/helpers/token.js +123 -0
- package/dist/account-manager/helpers/token.js.map +1 -0
- package/dist/account-manager/helpers/used-refresh-token.d.ts +10 -0
- package/dist/account-manager/helpers/used-refresh-token.d.ts.map +1 -0
- package/dist/account-manager/helpers/used-refresh-token.js +25 -0
- package/dist/account-manager/helpers/used-refresh-token.js.map +1 -0
- package/dist/account-manager/index.d.ts +36 -6
- package/dist/account-manager/index.d.ts.map +1 -1
- package/dist/account-manager/index.js +223 -22
- package/dist/account-manager/index.js.map +1 -1
- package/dist/actor-store/preference/reader.d.ts +2 -1
- package/dist/actor-store/preference/reader.d.ts.map +1 -1
- package/dist/actor-store/preference/reader.js +3 -1
- package/dist/actor-store/preference/reader.js.map +1 -1
- package/dist/actor-store/preference/transactor.d.ts +2 -1
- package/dist/actor-store/preference/transactor.d.ts.map +1 -1
- package/dist/actor-store/preference/transactor.js +7 -1
- package/dist/actor-store/preference/transactor.js.map +1 -1
- package/dist/actor-store/preference/util.d.ts +3 -0
- package/dist/actor-store/preference/util.d.ts.map +1 -0
- package/dist/actor-store/preference/util.js +12 -0
- package/dist/actor-store/preference/util.js.map +1 -0
- package/dist/actor-store/record/reader.d.ts +1 -1
- package/dist/api/app/bsky/actor/getPreferences.d.ts.map +1 -1
- package/dist/api/app/bsky/actor/getPreferences.js +1 -6
- package/dist/api/app/bsky/actor/getPreferences.js.map +1 -1
- package/dist/api/app/bsky/actor/putPreferences.d.ts.map +1 -1
- package/dist/api/app/bsky/actor/putPreferences.js +1 -1
- package/dist/api/app/bsky/actor/putPreferences.js.map +1 -1
- package/dist/api/app/bsky/util/resolver.d.ts +1 -1
- package/dist/api/com/atproto/server/createSession.d.ts.map +1 -1
- package/dist/api/com/atproto/server/createSession.js +7 -31
- package/dist/api/com/atproto/server/createSession.js.map +1 -1
- package/dist/api/com/atproto/server/deleteSession.d.ts.map +1 -1
- package/dist/api/com/atproto/server/deleteSession.js +14 -13
- package/dist/api/com/atproto/server/deleteSession.js.map +1 -1
- package/dist/api/com/atproto/server/getSession.d.ts.map +1 -1
- package/dist/api/com/atproto/server/getSession.js +4 -2
- package/dist/api/com/atproto/server/getSession.js.map +1 -1
- package/dist/api/com/atproto/server/refreshSession.d.ts.map +1 -1
- package/dist/api/com/atproto/server/refreshSession.js +4 -2
- package/dist/api/com/atproto/server/refreshSession.js.map +1 -1
- package/dist/api/com/atproto/sync/getRepoStatus.d.ts.map +1 -1
- package/dist/api/com/atproto/sync/getRepoStatus.js +2 -1
- package/dist/api/com/atproto/sync/getRepoStatus.js.map +1 -1
- package/dist/api/com/atproto/sync/listRepos.js +2 -2
- package/dist/api/com/atproto/sync/listRepos.js.map +1 -1
- package/dist/api/proxy.d.ts.map +1 -1
- package/dist/api/proxy.js +15 -2
- package/dist/api/proxy.js.map +1 -1
- package/dist/auth-routes.d.ts +4 -0
- package/dist/auth-routes.d.ts.map +1 -0
- package/dist/auth-routes.js +24 -0
- package/dist/auth-routes.js.map +1 -0
- package/dist/auth-verifier.d.ts +32 -11
- package/dist/auth-verifier.d.ts.map +1 -1
- package/dist/auth-verifier.js +238 -79
- package/dist/auth-verifier.js.map +1 -1
- package/dist/config/config.d.ts +12 -0
- package/dist/config/config.d.ts.map +1 -1
- package/dist/config/config.js +45 -0
- package/dist/config/config.js.map +1 -1
- package/dist/config/env.d.ts +8 -0
- package/dist/config/env.d.ts.map +1 -1
- package/dist/config/env.js +10 -0
- package/dist/config/env.js.map +1 -1
- package/dist/config/secrets.d.ts +1 -0
- package/dist/config/secrets.d.ts.map +1 -1
- package/dist/config/secrets.js +1 -0
- package/dist/config/secrets.js.map +1 -1
- package/dist/context.d.ts +6 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +71 -13
- package/dist/context.js.map +1 -1
- package/dist/db/cast.d.ts +15 -0
- package/dist/db/cast.d.ts.map +1 -0
- package/dist/db/cast.js +66 -0
- package/dist/db/cast.js.map +1 -0
- package/dist/db/db.d.ts +2 -2
- package/dist/db/db.d.ts.map +1 -1
- package/dist/db/db.js +9 -7
- package/dist/db/db.js.map +1 -1
- package/dist/db/index.d.ts +1 -0
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +1 -0
- package/dist/db/index.js.map +1 -1
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +5 -0
- package/dist/error.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/lexicon/index.d.ts +4 -0
- package/dist/lexicon/index.d.ts.map +1 -1
- package/dist/lexicon/index.js +8 -0
- package/dist/lexicon/index.js.map +1 -1
- package/dist/lexicon/lexicons.d.ts +51 -0
- package/dist/lexicon/lexicons.d.ts.map +1 -1
- package/dist/lexicon/lexicons.js +51 -0
- package/dist/lexicon/lexicons.js.map +1 -1
- package/dist/lexicon/types/app/bsky/feed/defs.d.ts +1 -0
- package/dist/lexicon/types/app/bsky/feed/defs.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/feed/defs.js.map +1 -1
- package/dist/lexicon/types/app/bsky/graph/muteThread.d.ts +29 -0
- package/dist/lexicon/types/app/bsky/graph/muteThread.d.ts.map +1 -0
- package/dist/lexicon/types/app/bsky/graph/muteThread.js +3 -0
- package/dist/lexicon/types/app/bsky/graph/muteThread.js.map +1 -0
- package/dist/lexicon/types/app/bsky/graph/unmuteThread.d.ts +29 -0
- package/dist/lexicon/types/app/bsky/graph/unmuteThread.d.ts.map +1 -0
- package/dist/lexicon/types/app/bsky/graph/unmuteThread.js +3 -0
- package/dist/lexicon/types/app/bsky/graph/unmuteThread.js.map +1 -0
- package/dist/logger.d.ts +13 -11
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +80 -64
- package/dist/logger.js.map +1 -1
- package/dist/oauth/detailed-account-store.d.ts +27 -0
- package/dist/oauth/detailed-account-store.d.ts.map +1 -0
- package/dist/oauth/detailed-account-store.js +76 -0
- package/dist/oauth/detailed-account-store.js.map +1 -0
- package/dist/oauth/provider.d.ts +16 -0
- package/dist/oauth/provider.d.ts.map +1 -0
- package/dist/oauth/provider.js +45 -0
- package/dist/oauth/provider.js.map +1 -0
- package/dist/pipethrough.d.ts.map +1 -1
- package/dist/pipethrough.js.map +1 -1
- package/dist/sequencer/events.d.ts +2 -2
- package/example.env +21 -3
- package/package.json +9 -7
- package/src/account-manager/db/migrations/004-oauth.ts +122 -0
- package/src/account-manager/db/migrations/index.ts +2 -0
- package/src/account-manager/db/schema/authorization-request.ts +26 -0
- package/src/account-manager/db/schema/device-account.ts +15 -0
- package/src/account-manager/db/schema/device.ts +18 -0
- package/src/account-manager/db/schema/index.ts +15 -0
- package/src/account-manager/db/schema/token.ts +34 -0
- package/src/account-manager/db/schema/used-refresh-token.ts +13 -0
- package/src/account-manager/helpers/account.ts +16 -21
- package/src/account-manager/helpers/authorization-request.ts +82 -0
- package/src/account-manager/helpers/device-account.ts +135 -0
- package/src/account-manager/helpers/device.ts +45 -0
- package/src/account-manager/helpers/token.ts +185 -0
- package/src/account-manager/helpers/used-refresh-token.ts +30 -0
- package/src/account-manager/index.ts +325 -20
- package/src/actor-store/preference/reader.ts +8 -2
- package/src/actor-store/preference/transactor.ts +10 -0
- package/src/actor-store/preference/util.ts +8 -0
- package/src/api/app/bsky/actor/getPreferences.ts +2 -9
- package/src/api/app/bsky/actor/putPreferences.ts +5 -1
- package/src/api/com/atproto/server/createSession.ts +8 -44
- package/src/api/com/atproto/server/deleteSession.ts +14 -20
- package/src/api/com/atproto/server/getSession.ts +7 -2
- package/src/api/com/atproto/server/refreshSession.ts +6 -2
- package/src/api/com/atproto/sync/getRepoStatus.ts +3 -1
- package/src/api/com/atproto/sync/listRepos.ts +1 -1
- package/src/api/proxy.ts +18 -2
- package/src/auth-routes.ts +27 -0
- package/src/auth-verifier.ts +312 -92
- package/src/config/config.ts +66 -0
- package/src/config/env.ts +24 -0
- package/src/config/secrets.ts +2 -0
- package/src/context.ts +80 -14
- package/src/db/cast.ts +59 -0
- package/src/db/db.ts +15 -12
- package/src/db/index.ts +1 -0
- package/src/error.ts +7 -0
- package/src/index.ts +2 -0
- package/src/lexicon/index.ts +24 -0
- package/src/lexicon/lexicons.ts +52 -0
- package/src/lexicon/types/app/bsky/feed/defs.ts +1 -0
- package/src/lexicon/types/app/bsky/graph/muteThread.ts +38 -0
- package/src/lexicon/types/app/bsky/graph/unmuteThread.ts +38 -0
- package/src/logger.ts +83 -38
- package/src/oauth/detailed-account-store.ts +96 -0
- package/src/oauth/provider.ts +77 -0
- package/src/pipethrough.ts +3 -2
- package/tests/preferences.test.ts +67 -1
- package/tests/proxied/__snapshots__/feedgen.test.ts.snap +4 -1
- package/tests/proxied/__snapshots__/views.test.ts.snap +116 -38
|
@@ -1,25 +1,57 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { HOUR, wait } from '@atproto/common'
|
|
2
|
+
import {
|
|
3
|
+
AccountInfo,
|
|
4
|
+
AccountStore,
|
|
5
|
+
Code,
|
|
6
|
+
DeviceData,
|
|
7
|
+
DeviceId,
|
|
8
|
+
DeviceStore,
|
|
9
|
+
FoundRequestResult,
|
|
10
|
+
LoginCredentials,
|
|
11
|
+
NewTokenData,
|
|
12
|
+
RefreshToken,
|
|
13
|
+
RequestData,
|
|
14
|
+
RequestId,
|
|
15
|
+
RequestStore,
|
|
16
|
+
TokenData,
|
|
17
|
+
TokenId,
|
|
18
|
+
TokenInfo,
|
|
19
|
+
TokenStore,
|
|
20
|
+
UpdateRequestData,
|
|
21
|
+
} from '@atproto/oauth-provider'
|
|
22
|
+
import { AuthRequiredError } from '@atproto/xrpc-server'
|
|
3
23
|
import { CID } from 'multiformats/cid'
|
|
24
|
+
import { KeyObject } from 'node:crypto'
|
|
25
|
+
|
|
26
|
+
import { AuthScope } from '../auth-verifier'
|
|
27
|
+
import { BackgroundQueue } from '../background'
|
|
28
|
+
import { softDeleted } from '../db'
|
|
29
|
+
import { StatusAttr } from '../lexicon/types/com/atproto/admin/defs'
|
|
4
30
|
import { AccountDb, EmailTokenPurpose, getDb, getMigrator } from './db'
|
|
5
|
-
import * as scrypt from './helpers/scrypt'
|
|
6
31
|
import * as account from './helpers/account'
|
|
7
|
-
import { AccountStatus } from './helpers/account'
|
|
8
|
-
import { ActorAccount } from './helpers/account'
|
|
9
|
-
import * as repo from './helpers/repo'
|
|
32
|
+
import { AccountStatus, ActorAccount } from './helpers/account'
|
|
10
33
|
import * as auth from './helpers/auth'
|
|
34
|
+
import * as authRequest from './helpers/authorization-request'
|
|
35
|
+
import * as deviceAccount from './helpers/device-account'
|
|
36
|
+
import * as device from './helpers/device'
|
|
37
|
+
import * as emailToken from './helpers/email-token'
|
|
11
38
|
import * as invite from './helpers/invite'
|
|
12
39
|
import * as password from './helpers/password'
|
|
13
|
-
import * as
|
|
14
|
-
import
|
|
15
|
-
import
|
|
40
|
+
import * as repo from './helpers/repo'
|
|
41
|
+
import * as scrypt from './helpers/scrypt'
|
|
42
|
+
import * as token from './helpers/token'
|
|
43
|
+
import * as usedRefreshToken from './helpers/used-refresh-token'
|
|
16
44
|
|
|
17
45
|
export { AccountStatus } from './helpers/account'
|
|
46
|
+
export { formatAccountStatus } from './helpers/account'
|
|
18
47
|
|
|
19
|
-
export class AccountManager
|
|
48
|
+
export class AccountManager
|
|
49
|
+
implements AccountStore, RequestStore, DeviceStore, TokenStore
|
|
50
|
+
{
|
|
20
51
|
db: AccountDb
|
|
21
52
|
|
|
22
53
|
constructor(
|
|
54
|
+
private backgroundQueue: BackgroundQueue,
|
|
23
55
|
dbLocation: string,
|
|
24
56
|
private jwtKey: KeyObject,
|
|
25
57
|
private serviceDid: string,
|
|
@@ -73,15 +105,9 @@ export class AccountManager {
|
|
|
73
105
|
includeDeactivated: true,
|
|
74
106
|
includeTakenDown: true,
|
|
75
107
|
})
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
return AccountStatus.Takendown
|
|
80
|
-
} else if (got.deactivatedAt) {
|
|
81
|
-
return AccountStatus.Deactivated
|
|
82
|
-
} else {
|
|
83
|
-
return AccountStatus.Active
|
|
84
|
-
}
|
|
108
|
+
|
|
109
|
+
const res = account.formatAccountStatus(got)
|
|
110
|
+
return res.active ? AccountStatus.Active : res.status
|
|
85
111
|
}
|
|
86
112
|
|
|
87
113
|
async createAccount(opts: {
|
|
@@ -148,10 +174,11 @@ export class AccountManager {
|
|
|
148
174
|
}
|
|
149
175
|
|
|
150
176
|
async takedownAccount(did: string, takedown: StatusAttr) {
|
|
151
|
-
await this.db.transaction((dbTxn) =>
|
|
177
|
+
await this.db.transaction(async (dbTxn) =>
|
|
152
178
|
Promise.all([
|
|
153
179
|
account.updateAccountTakedownStatus(dbTxn, did, takedown),
|
|
154
180
|
auth.revokeRefreshTokensByDid(dbTxn, did),
|
|
181
|
+
token.removeByDidQB(dbTxn, did).execute(),
|
|
155
182
|
]),
|
|
156
183
|
)
|
|
157
184
|
}
|
|
@@ -250,6 +277,63 @@ export class AccountManager {
|
|
|
250
277
|
return auth.revokeRefreshToken(this.db, id)
|
|
251
278
|
}
|
|
252
279
|
|
|
280
|
+
// Login
|
|
281
|
+
// ----------
|
|
282
|
+
|
|
283
|
+
async login({
|
|
284
|
+
identifier,
|
|
285
|
+
password,
|
|
286
|
+
}: {
|
|
287
|
+
identifier: string
|
|
288
|
+
password: string
|
|
289
|
+
}): Promise<{
|
|
290
|
+
user: ActorAccount
|
|
291
|
+
appPassword: password.AppPassDescript | null
|
|
292
|
+
}> {
|
|
293
|
+
const start = Date.now()
|
|
294
|
+
try {
|
|
295
|
+
const identifierNormalized = identifier.toLowerCase()
|
|
296
|
+
|
|
297
|
+
const user = identifierNormalized.includes('@')
|
|
298
|
+
? await this.getAccountByEmail(identifierNormalized, {
|
|
299
|
+
includeDeactivated: true,
|
|
300
|
+
includeTakenDown: true,
|
|
301
|
+
})
|
|
302
|
+
: await this.getAccount(identifierNormalized, {
|
|
303
|
+
includeDeactivated: true,
|
|
304
|
+
includeTakenDown: true,
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
if (!user) {
|
|
308
|
+
throw new AuthRequiredError('Invalid identifier or password')
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
let appPassword: password.AppPassDescript | null = null
|
|
312
|
+
const validAccountPass = await this.verifyAccountPassword(
|
|
313
|
+
user.did,
|
|
314
|
+
password,
|
|
315
|
+
)
|
|
316
|
+
if (!validAccountPass) {
|
|
317
|
+
appPassword = await this.verifyAppPassword(user.did, password)
|
|
318
|
+
if (appPassword === null) {
|
|
319
|
+
throw new AuthRequiredError('Invalid identifier or password')
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (softDeleted(user)) {
|
|
324
|
+
throw new AuthRequiredError(
|
|
325
|
+
'Account has been taken down',
|
|
326
|
+
'AccountTakedown',
|
|
327
|
+
)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return { user, appPassword }
|
|
331
|
+
} finally {
|
|
332
|
+
// Mitigate timing attacks
|
|
333
|
+
await wait(350 - (Date.now() - start))
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
253
337
|
// Passwords
|
|
254
338
|
// ----------
|
|
255
339
|
|
|
@@ -399,4 +483,225 @@ export class AccountManager {
|
|
|
399
483
|
]),
|
|
400
484
|
)
|
|
401
485
|
}
|
|
486
|
+
|
|
487
|
+
// AccountStore
|
|
488
|
+
|
|
489
|
+
async authenticateAccount(
|
|
490
|
+
{ username: identifier, password, remember = false }: LoginCredentials,
|
|
491
|
+
deviceId: DeviceId,
|
|
492
|
+
): Promise<AccountInfo | null> {
|
|
493
|
+
try {
|
|
494
|
+
const { user, appPassword } = await this.login({ identifier, password })
|
|
495
|
+
|
|
496
|
+
if (appPassword) {
|
|
497
|
+
throw new AuthRequiredError('App passwords are not allowed')
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
await this.db.executeWithRetry(
|
|
501
|
+
deviceAccount.createOrUpdateQB(this.db, deviceId, user.did, remember),
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
return await this.getDeviceAccount(deviceId, user.did)
|
|
505
|
+
} catch (err) {
|
|
506
|
+
if (err instanceof AuthRequiredError) return null
|
|
507
|
+
throw err
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
async addAuthorizedClient(
|
|
512
|
+
deviceId: DeviceId,
|
|
513
|
+
sub: string,
|
|
514
|
+
clientId: string,
|
|
515
|
+
): Promise<void> {
|
|
516
|
+
await this.db.transaction(async (dbTxn) => {
|
|
517
|
+
const row = await deviceAccount
|
|
518
|
+
.readQB(dbTxn, deviceId, sub)
|
|
519
|
+
.executeTakeFirstOrThrow()
|
|
520
|
+
|
|
521
|
+
const { authorizedClients } = deviceAccount.toDeviceAccountInfo(row)
|
|
522
|
+
if (!authorizedClients.includes(clientId)) {
|
|
523
|
+
await deviceAccount
|
|
524
|
+
.updateQB(dbTxn, deviceId, sub, {
|
|
525
|
+
authorizedClients: [...authorizedClients, clientId],
|
|
526
|
+
})
|
|
527
|
+
.execute()
|
|
528
|
+
}
|
|
529
|
+
})
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
async getDeviceAccount(
|
|
533
|
+
deviceId: DeviceId,
|
|
534
|
+
sub: string,
|
|
535
|
+
): Promise<AccountInfo | null> {
|
|
536
|
+
const row = await deviceAccount
|
|
537
|
+
.getAccountInfoQB(this.db, deviceId, sub)
|
|
538
|
+
.executeTakeFirst()
|
|
539
|
+
|
|
540
|
+
if (!row) return null
|
|
541
|
+
|
|
542
|
+
return {
|
|
543
|
+
account: deviceAccount.toAccount(row, this.serviceDid),
|
|
544
|
+
info: deviceAccount.toDeviceAccountInfo(row),
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
async listDeviceAccounts(deviceId: DeviceId): Promise<AccountInfo[]> {
|
|
549
|
+
const rows = await deviceAccount
|
|
550
|
+
.listRememberedQB(this.db, deviceId)
|
|
551
|
+
.execute()
|
|
552
|
+
|
|
553
|
+
return rows.map((row) => ({
|
|
554
|
+
account: deviceAccount.toAccount(row, this.serviceDid),
|
|
555
|
+
info: deviceAccount.toDeviceAccountInfo(row),
|
|
556
|
+
}))
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
async removeDeviceAccount(deviceId: DeviceId, sub: string): Promise<void> {
|
|
560
|
+
await this.db.executeWithRetry(
|
|
561
|
+
deviceAccount.removeQB(this.db, deviceId, sub),
|
|
562
|
+
)
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// RequestStore
|
|
566
|
+
|
|
567
|
+
async createRequest(id: RequestId, data: RequestData): Promise<void> {
|
|
568
|
+
await this.db.executeWithRetry(authRequest.createQB(this.db, id, data))
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
async readRequest(id: RequestId): Promise<RequestData | null> {
|
|
572
|
+
try {
|
|
573
|
+
const row = await authRequest.readQB(this.db, id).executeTakeFirst()
|
|
574
|
+
if (!row) return null
|
|
575
|
+
return authRequest.rowToRequestData(row)
|
|
576
|
+
} finally {
|
|
577
|
+
// Take the opportunity to clean up expired requests. Do this after we got
|
|
578
|
+
// the current (potentially expired) request data to allow the provider to
|
|
579
|
+
// handle expired requests.
|
|
580
|
+
this.backgroundQueue.add(async () => {
|
|
581
|
+
await this.db.executeWithRetry(authRequest.removeOldExpiredQB(this.db))
|
|
582
|
+
})
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
async updateRequest(id: RequestId, data: UpdateRequestData): Promise<void> {
|
|
587
|
+
await this.db.executeWithRetry(authRequest.updateQB(this.db, id, data))
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
async deleteRequest(id: RequestId): Promise<void> {
|
|
591
|
+
await this.db.executeWithRetry(authRequest.removeByIdQB(this.db, id))
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
async findRequestByCode(code: Code): Promise<FoundRequestResult | null> {
|
|
595
|
+
const row = await authRequest.findByCodeQB(this.db, code).executeTakeFirst()
|
|
596
|
+
return row ? authRequest.rowToFoundRequestResult(row) : null
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// DeviceStore
|
|
600
|
+
|
|
601
|
+
async createDevice(deviceId: DeviceId, data: DeviceData): Promise<void> {
|
|
602
|
+
await this.db.executeWithRetry(device.createQB(this.db, deviceId, data))
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
async readDevice(deviceId: DeviceId): Promise<null | DeviceData> {
|
|
606
|
+
const row = await device.readQB(this.db, deviceId).executeTakeFirst()
|
|
607
|
+
return row ? device.rowToDeviceData(row) : null
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
async updateDevice(
|
|
611
|
+
deviceId: DeviceId,
|
|
612
|
+
data: Partial<DeviceData>,
|
|
613
|
+
): Promise<void> {
|
|
614
|
+
await this.db.executeWithRetry(device.updateQB(this.db, deviceId, data))
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
async deleteDevice(deviceId: DeviceId): Promise<void> {
|
|
618
|
+
// Will cascade to device_account (device_account_device_id_fk)
|
|
619
|
+
await this.db.executeWithRetry(device.removeQB(this.db, deviceId))
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// TokenStore
|
|
623
|
+
|
|
624
|
+
async createToken(
|
|
625
|
+
id: TokenId,
|
|
626
|
+
data: TokenData,
|
|
627
|
+
refreshToken?: RefreshToken,
|
|
628
|
+
): Promise<void> {
|
|
629
|
+
await this.db.transaction(async (dbTxn) => {
|
|
630
|
+
if (refreshToken) {
|
|
631
|
+
const { count } = await usedRefreshToken
|
|
632
|
+
.countQB(dbTxn, refreshToken)
|
|
633
|
+
.executeTakeFirstOrThrow()
|
|
634
|
+
|
|
635
|
+
if (count > 0) {
|
|
636
|
+
throw new Error('Refresh token already in use')
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
return token.createQB(dbTxn, id, data, refreshToken).execute()
|
|
641
|
+
})
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
async readToken(tokenId: TokenId): Promise<TokenInfo | null> {
|
|
645
|
+
const row = await token.findByQB(this.db, { tokenId }).executeTakeFirst()
|
|
646
|
+
return row ? token.toTokenInfo(row, this.serviceDid) : null
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
async deleteToken(tokenId: TokenId): Promise<void> {
|
|
650
|
+
// Will cascade to used_refresh_token (used_refresh_token_fk)
|
|
651
|
+
await this.db.executeWithRetry(token.removeQB(this.db, tokenId))
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
async rotateToken(
|
|
655
|
+
tokenId: TokenId,
|
|
656
|
+
newTokenId: TokenId,
|
|
657
|
+
newRefreshToken: RefreshToken,
|
|
658
|
+
newData: NewTokenData,
|
|
659
|
+
): Promise<void> {
|
|
660
|
+
const err = await this.db.transaction(async (dbTxn) => {
|
|
661
|
+
const { id, currentRefreshToken } = await token
|
|
662
|
+
.forRotateQB(dbTxn, tokenId)
|
|
663
|
+
.executeTakeFirstOrThrow()
|
|
664
|
+
|
|
665
|
+
if (currentRefreshToken) {
|
|
666
|
+
await usedRefreshToken
|
|
667
|
+
.insertQB(dbTxn, id, currentRefreshToken)
|
|
668
|
+
.execute()
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
const { count } = await usedRefreshToken
|
|
672
|
+
.countQB(dbTxn, newRefreshToken)
|
|
673
|
+
.executeTakeFirstOrThrow()
|
|
674
|
+
|
|
675
|
+
if (count > 0) {
|
|
676
|
+
// Do NOT throw (we don't want the transaction to be rolled back)
|
|
677
|
+
return new Error('New refresh token already in use')
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
await token
|
|
681
|
+
.rotateQB(dbTxn, id, newTokenId, newRefreshToken, newData)
|
|
682
|
+
.execute()
|
|
683
|
+
})
|
|
684
|
+
|
|
685
|
+
if (err) throw err
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
async findTokenByRefreshToken(
|
|
689
|
+
refreshToken: RefreshToken,
|
|
690
|
+
): Promise<TokenInfo | null> {
|
|
691
|
+
const used = await usedRefreshToken
|
|
692
|
+
.findByTokenQB(this.db, refreshToken)
|
|
693
|
+
.executeTakeFirst()
|
|
694
|
+
|
|
695
|
+
const search = used
|
|
696
|
+
? { id: used.tokenId }
|
|
697
|
+
: { currentRefreshToken: refreshToken }
|
|
698
|
+
|
|
699
|
+
const row = await token.findByQB(this.db, search).executeTakeFirst()
|
|
700
|
+
return row ? token.toTokenInfo(row, this.serviceDid) : null
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
async findTokenByCode(code: Code): Promise<TokenInfo | null> {
|
|
704
|
+
const row = await token.findByQB(this.db, { code }).executeTakeFirst()
|
|
705
|
+
return row ? token.toTokenInfo(row, this.serviceDid) : null
|
|
706
|
+
}
|
|
402
707
|
}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import { AuthScope } from '../../auth-verifier'
|
|
1
2
|
import { ActorDb } from '../db'
|
|
3
|
+
import { prefInScope } from './util'
|
|
2
4
|
|
|
3
5
|
export class PreferenceReader {
|
|
4
6
|
constructor(public db: ActorDb) {}
|
|
5
7
|
|
|
6
|
-
async getPreferences(
|
|
8
|
+
async getPreferences(
|
|
9
|
+
namespace: string,
|
|
10
|
+
scope: AuthScope,
|
|
11
|
+
): Promise<AccountPreference[]> {
|
|
7
12
|
const prefsRes = await this.db.db
|
|
8
13
|
.selectFrom('account_pref')
|
|
9
14
|
.orderBy('id')
|
|
@@ -11,7 +16,8 @@ export class PreferenceReader {
|
|
|
11
16
|
.execute()
|
|
12
17
|
return prefsRes
|
|
13
18
|
.filter((pref) => !namespace || prefMatchNamespace(namespace, pref.name))
|
|
14
|
-
.
|
|
19
|
+
.filter((pref) => prefInScope(scope, pref.name))
|
|
20
|
+
.map((pref) => JSON.parse(pref.valueJson) as AccountPreference)
|
|
15
21
|
}
|
|
16
22
|
}
|
|
17
23
|
|
|
@@ -4,11 +4,14 @@ import {
|
|
|
4
4
|
AccountPreference,
|
|
5
5
|
prefMatchNamespace,
|
|
6
6
|
} from './reader'
|
|
7
|
+
import { AuthScope } from '../../auth-verifier'
|
|
8
|
+
import { prefInScope } from './util'
|
|
7
9
|
|
|
8
10
|
export class PreferenceTransactor extends PreferenceReader {
|
|
9
11
|
async putPreferences(
|
|
10
12
|
values: AccountPreference[],
|
|
11
13
|
namespace: string,
|
|
14
|
+
scope: AuthScope,
|
|
12
15
|
): Promise<void> {
|
|
13
16
|
this.db.assertTransaction()
|
|
14
17
|
if (!values.every((value) => prefMatchNamespace(namespace, value.$type))) {
|
|
@@ -16,6 +19,12 @@ export class PreferenceTransactor extends PreferenceReader {
|
|
|
16
19
|
`Some preferences are not in the ${namespace} namespace`,
|
|
17
20
|
)
|
|
18
21
|
}
|
|
22
|
+
const notInScope = values.filter((val) => !prefInScope(scope, val.$type))
|
|
23
|
+
if (notInScope.length > 0) {
|
|
24
|
+
throw new InvalidRequestError(
|
|
25
|
+
`Do not have authorization to set preferences: ${notInScope.join(', ')}`,
|
|
26
|
+
)
|
|
27
|
+
}
|
|
19
28
|
// get all current prefs for user and prep new pref rows
|
|
20
29
|
const allPrefs = await this.db.db
|
|
21
30
|
.selectFrom('account_pref')
|
|
@@ -29,6 +38,7 @@ export class PreferenceTransactor extends PreferenceReader {
|
|
|
29
38
|
})
|
|
30
39
|
const allPrefIdsInNamespace = allPrefs
|
|
31
40
|
.filter((pref) => prefMatchNamespace(namespace, pref.name))
|
|
41
|
+
.filter((pref) => prefInScope(scope, pref.name))
|
|
32
42
|
.map((pref) => pref.id)
|
|
33
43
|
// replace all prefs in given namespace
|
|
34
44
|
if (allPrefIdsInNamespace.length) {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { AuthScope } from '../../auth-verifier'
|
|
2
|
+
|
|
3
|
+
const FULL_ACCESS_ONLY_PREFS = ['app.bsky.actor.defs#personalDetailsPref']
|
|
4
|
+
|
|
5
|
+
export const prefInScope = (scope: AuthScope, prefType: string) => {
|
|
6
|
+
if (scope === AuthScope.Access) return true
|
|
7
|
+
return !FULL_ACCESS_ONLY_PREFS.includes(prefType)
|
|
8
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Server } from '../../../../lexicon'
|
|
2
2
|
import AppContext from '../../../../context'
|
|
3
|
-
import { AuthScope } from '../../../../auth-verifier'
|
|
4
3
|
|
|
5
4
|
export default function (server: Server, ctx: AppContext) {
|
|
6
5
|
if (!ctx.cfg.bskyAppView) return
|
|
@@ -8,15 +7,9 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
8
7
|
auth: ctx.authVerifier.accessStandard(),
|
|
9
8
|
handler: async ({ auth }) => {
|
|
10
9
|
const requester = auth.credentials.did
|
|
11
|
-
|
|
12
|
-
store.pref.getPreferences('app.bsky'),
|
|
10
|
+
const preferences = await ctx.actorStore.read(requester, (store) =>
|
|
11
|
+
store.pref.getPreferences('app.bsky', auth.credentials.scope),
|
|
13
12
|
)
|
|
14
|
-
if (auth.credentials.scope !== AuthScope.Access) {
|
|
15
|
-
// filter out personal details for app passwords
|
|
16
|
-
preferences = preferences.filter(
|
|
17
|
-
(pref) => pref.$type !== 'app.bsky.actor.defs#personalDetailsPref',
|
|
18
|
-
)
|
|
19
|
-
}
|
|
20
13
|
return {
|
|
21
14
|
encoding: 'application/json',
|
|
22
15
|
body: { preferences },
|
|
@@ -19,7 +19,11 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
await ctx.actorStore.transact(requester, async (actorTxn) => {
|
|
22
|
-
await actorTxn.pref.putPreferences(
|
|
22
|
+
await actorTxn.pref.putPreferences(
|
|
23
|
+
checkedPreferences,
|
|
24
|
+
'app.bsky',
|
|
25
|
+
auth.credentials.scope,
|
|
26
|
+
)
|
|
23
27
|
})
|
|
24
28
|
},
|
|
25
29
|
})
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { DAY, MINUTE } from '@atproto/common'
|
|
2
2
|
import { INVALID_HANDLE } from '@atproto/syntax'
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
import { formatAccountStatus } from '../../../../account-manager'
|
|
4
5
|
import AppContext from '../../../../context'
|
|
5
|
-
import { softDeleted } from '../../../../db/util'
|
|
6
6
|
import { Server } from '../../../../lexicon'
|
|
7
|
-
import { didDocForSession } from './util'
|
|
8
7
|
import { authPassthru, resultPassthru } from '../../../proxy'
|
|
9
|
-
import {
|
|
8
|
+
import { didDocForSession } from './util'
|
|
10
9
|
|
|
11
10
|
export default function (server: Server, ctx: AppContext) {
|
|
12
11
|
server.com.atproto.server.createSession({
|
|
@@ -32,50 +31,15 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
32
31
|
)
|
|
33
32
|
}
|
|
34
33
|
|
|
35
|
-
const {
|
|
36
|
-
const identifier = input.body.identifier.toLowerCase()
|
|
37
|
-
|
|
38
|
-
const user = identifier.includes('@')
|
|
39
|
-
? await ctx.accountManager.getAccountByEmail(identifier, {
|
|
40
|
-
includeDeactivated: true,
|
|
41
|
-
includeTakenDown: true,
|
|
42
|
-
})
|
|
43
|
-
: await ctx.accountManager.getAccount(identifier, {
|
|
44
|
-
includeDeactivated: true,
|
|
45
|
-
includeTakenDown: true,
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
if (!user) {
|
|
49
|
-
throw new AuthRequiredError('Invalid identifier or password')
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
let appPassword: AppPassDescript | null = null
|
|
53
|
-
const validAccountPass = await ctx.accountManager.verifyAccountPassword(
|
|
54
|
-
user.did,
|
|
55
|
-
password,
|
|
56
|
-
)
|
|
57
|
-
if (!validAccountPass) {
|
|
58
|
-
appPassword = await ctx.accountManager.verifyAppPassword(
|
|
59
|
-
user.did,
|
|
60
|
-
password,
|
|
61
|
-
)
|
|
62
|
-
if (appPassword === null) {
|
|
63
|
-
throw new AuthRequiredError('Invalid identifier or password')
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (softDeleted(user)) {
|
|
68
|
-
throw new AuthRequiredError(
|
|
69
|
-
'Account has been taken down',
|
|
70
|
-
'AccountTakedown',
|
|
71
|
-
)
|
|
72
|
-
}
|
|
34
|
+
const { user, appPassword } = await ctx.accountManager.login(input.body)
|
|
73
35
|
|
|
74
36
|
const [{ accessJwt, refreshJwt }, didDoc] = await Promise.all([
|
|
75
37
|
ctx.accountManager.createSession(user.did, appPassword),
|
|
76
38
|
didDocForSession(ctx, user.did),
|
|
77
39
|
])
|
|
78
40
|
|
|
41
|
+
const { status, active } = formatAccountStatus(user)
|
|
42
|
+
|
|
79
43
|
return {
|
|
80
44
|
encoding: 'application/json',
|
|
81
45
|
body: {
|
|
@@ -86,8 +50,8 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
86
50
|
emailConfirmed: !!user.emailConfirmedAt,
|
|
87
51
|
accessJwt,
|
|
88
52
|
refreshJwt,
|
|
89
|
-
active
|
|
90
|
-
status
|
|
53
|
+
active,
|
|
54
|
+
status,
|
|
91
55
|
},
|
|
92
56
|
}
|
|
93
57
|
},
|
|
@@ -1,28 +1,22 @@
|
|
|
1
|
-
import { AuthScope } from '../../../../auth-verifier'
|
|
2
1
|
import AppContext from '../../../../context'
|
|
3
2
|
import { Server } from '../../../../lexicon'
|
|
4
3
|
import { authPassthru } from '../../../proxy'
|
|
5
4
|
|
|
6
5
|
export default function (server: Server, ctx: AppContext) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
const { entrywayAgent } = ctx
|
|
7
|
+
if (entrywayAgent) {
|
|
8
|
+
server.com.atproto.server.deleteSession(async (reqCtx) => {
|
|
9
|
+
await entrywayAgent.com.atproto.server.deleteSession(
|
|
10
10
|
undefined,
|
|
11
|
-
authPassthru(req, true),
|
|
11
|
+
authPassthru(reqCtx.req, true),
|
|
12
12
|
)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
if (!id) {
|
|
23
|
-
throw new Error('Unexpected missing refresh token id')
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
await ctx.accountManager.revokeRefreshToken(id)
|
|
27
|
-
})
|
|
13
|
+
})
|
|
14
|
+
} else {
|
|
15
|
+
server.com.atproto.server.deleteSession({
|
|
16
|
+
auth: ctx.authVerifier.refreshExpired,
|
|
17
|
+
handler: async ({ auth }) => {
|
|
18
|
+
await ctx.accountManager.revokeRefreshToken(auth.credentials.tokenId)
|
|
19
|
+
},
|
|
20
|
+
})
|
|
21
|
+
}
|
|
28
22
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { InvalidRequestError } from '@atproto/xrpc-server'
|
|
2
2
|
import { INVALID_HANDLE } from '@atproto/syntax'
|
|
3
|
+
|
|
4
|
+
import { formatAccountStatus } from '../../../../account-manager'
|
|
3
5
|
import AppContext from '../../../../context'
|
|
4
6
|
import { Server } from '../../../../lexicon'
|
|
5
7
|
import { authPassthru, resultPassthru } from '../../../proxy'
|
|
@@ -31,6 +33,9 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
31
33
|
`Could not find user info for account: ${did}`,
|
|
32
34
|
)
|
|
33
35
|
}
|
|
36
|
+
|
|
37
|
+
const { status, active } = formatAccountStatus(user)
|
|
38
|
+
|
|
34
39
|
return {
|
|
35
40
|
encoding: 'application/json',
|
|
36
41
|
body: {
|
|
@@ -39,8 +44,8 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
39
44
|
email: user.email ?? undefined,
|
|
40
45
|
didDoc,
|
|
41
46
|
emailConfirmed: !!user.emailConfirmedAt,
|
|
42
|
-
active
|
|
43
|
-
status
|
|
47
|
+
active,
|
|
48
|
+
status,
|
|
44
49
|
},
|
|
45
50
|
}
|
|
46
51
|
},
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { INVALID_HANDLE } from '@atproto/syntax'
|
|
2
2
|
import { AuthRequiredError, InvalidRequestError } from '@atproto/xrpc-server'
|
|
3
|
+
|
|
4
|
+
import { formatAccountStatus } from '../../../../account-manager'
|
|
3
5
|
import AppContext from '../../../../context'
|
|
4
6
|
import { softDeleted } from '../../../../db/util'
|
|
5
7
|
import { Server } from '../../../../lexicon'
|
|
@@ -44,6 +46,8 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
44
46
|
throw new InvalidRequestError('Token has been revoked', 'ExpiredToken')
|
|
45
47
|
}
|
|
46
48
|
|
|
49
|
+
const { status, active } = formatAccountStatus(user)
|
|
50
|
+
|
|
47
51
|
return {
|
|
48
52
|
encoding: 'application/json',
|
|
49
53
|
body: {
|
|
@@ -52,8 +56,8 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
52
56
|
handle: user.handle ?? INVALID_HANDLE,
|
|
53
57
|
accessJwt: rotated.accessJwt,
|
|
54
58
|
refreshJwt: rotated.refreshJwt,
|
|
55
|
-
active
|
|
56
|
-
status
|
|
59
|
+
active,
|
|
60
|
+
status,
|
|
57
61
|
},
|
|
58
62
|
}
|
|
59
63
|
},
|