@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
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED CODE - DO NOT MODIFY
|
|
3
|
+
*/
|
|
4
|
+
import express from 'express'
|
|
5
|
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
|
|
6
|
+
import { lexicons } from '../../../../lexicons'
|
|
7
|
+
import { isObj, hasProp } from '../../../../util'
|
|
8
|
+
import { CID } from 'multiformats/cid'
|
|
9
|
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
|
|
10
|
+
|
|
11
|
+
export interface QueryParams {}
|
|
12
|
+
|
|
13
|
+
export interface InputSchema {
|
|
14
|
+
root: string
|
|
15
|
+
[k: string]: unknown
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface HandlerInput {
|
|
19
|
+
encoding: 'application/json'
|
|
20
|
+
body: InputSchema
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface HandlerError {
|
|
24
|
+
status: number
|
|
25
|
+
message?: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type HandlerOutput = HandlerError | void
|
|
29
|
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
|
|
30
|
+
auth: HA
|
|
31
|
+
params: QueryParams
|
|
32
|
+
input: HandlerInput
|
|
33
|
+
req: express.Request
|
|
34
|
+
res: express.Response
|
|
35
|
+
}
|
|
36
|
+
export type Handler<HA extends HandlerAuth = never> = (
|
|
37
|
+
ctx: HandlerReqCtx<HA>,
|
|
38
|
+
) => Promise<HandlerOutput> | HandlerOutput
|
package/src/logger.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { stdSerializers } from 'pino'
|
|
2
2
|
import pinoHttp from 'pino-http'
|
|
3
3
|
import { subsystemLogger } from '@atproto/common'
|
|
4
|
-
import * as jose from 'jose'
|
|
5
|
-
import { parseBasicAuth } from './auth-verifier'
|
|
6
4
|
|
|
7
5
|
export const dbLogger = subsystemLogger('pds:db')
|
|
8
6
|
export const didCacheLogger = subsystemLogger('pds:did-cache')
|
|
@@ -13,44 +11,91 @@ export const mailerLogger = subsystemLogger('pds:mailer')
|
|
|
13
11
|
export const labelerLogger = subsystemLogger('pds:labeler')
|
|
14
12
|
export const crawlerLogger = subsystemLogger('pds:crawler')
|
|
15
13
|
export const httpLogger = subsystemLogger('pds')
|
|
14
|
+
export const fetchLogger = subsystemLogger('pds:fetch')
|
|
15
|
+
export const oauthLogger = subsystemLogger('pds:oauth')
|
|
16
16
|
|
|
17
17
|
export const loggerMiddleware = pinoHttp({
|
|
18
18
|
logger: httpLogger,
|
|
19
19
|
serializers: {
|
|
20
|
-
err:
|
|
21
|
-
|
|
22
|
-
code: err?.code,
|
|
23
|
-
message: err?.message,
|
|
24
|
-
}
|
|
25
|
-
},
|
|
26
|
-
req: (req) => {
|
|
27
|
-
const serialized = pino.stdSerializers.req(req)
|
|
28
|
-
const authHeader = serialized.headers.authorization || ''
|
|
29
|
-
let auth: string | undefined = undefined
|
|
30
|
-
if (authHeader.startsWith('Bearer ')) {
|
|
31
|
-
const token = authHeader.slice('Bearer '.length)
|
|
32
|
-
const { sub } = jose.decodeJwt(token)
|
|
33
|
-
if (sub) {
|
|
34
|
-
auth = 'Bearer ' + sub
|
|
35
|
-
} else {
|
|
36
|
-
auth = 'Bearer Invalid'
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
if (authHeader.startsWith('Basic ')) {
|
|
40
|
-
const parsed = parseBasicAuth(authHeader)
|
|
41
|
-
if (!parsed) {
|
|
42
|
-
auth = 'Basic Invalid'
|
|
43
|
-
} else {
|
|
44
|
-
auth = 'Basic ' + parsed.username
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
return {
|
|
48
|
-
...serialized,
|
|
49
|
-
headers: {
|
|
50
|
-
...serialized.headers,
|
|
51
|
-
authorization: auth,
|
|
52
|
-
},
|
|
53
|
-
}
|
|
54
|
-
},
|
|
20
|
+
err: errSerializer,
|
|
21
|
+
req: reqSerializer,
|
|
55
22
|
},
|
|
56
23
|
})
|
|
24
|
+
|
|
25
|
+
function errSerializer(err: any) {
|
|
26
|
+
return {
|
|
27
|
+
code: err?.code,
|
|
28
|
+
message: err?.message,
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function reqSerializer(req: any) {
|
|
33
|
+
const serialized = stdSerializers.req(req)
|
|
34
|
+
serialized.headers = obfuscateHeaders(serialized.headers)
|
|
35
|
+
return serialized
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function obfuscateHeaders(headers: Record<string, string>) {
|
|
39
|
+
const obfuscatedHeaders: Record<string, string> = {}
|
|
40
|
+
for (const key in headers) {
|
|
41
|
+
if (key.toLowerCase() === 'authorization') {
|
|
42
|
+
obfuscatedHeaders[key] = obfuscateAuthHeader(headers[key])
|
|
43
|
+
} else if (key.toLowerCase() === 'dpop') {
|
|
44
|
+
obfuscatedHeaders[key] = obfuscateJws(headers[key]) || 'Invalid'
|
|
45
|
+
} else {
|
|
46
|
+
obfuscatedHeaders[key] = headers[key]
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return obfuscatedHeaders
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function obfuscateAuthHeader(authHeader: string): string {
|
|
53
|
+
// This is a hot path (runs on every request). Avoid using split() or regex.
|
|
54
|
+
|
|
55
|
+
const spaceIdx = authHeader.indexOf(' ')
|
|
56
|
+
if (spaceIdx === -1) return 'Invalid'
|
|
57
|
+
|
|
58
|
+
const type = authHeader.slice(0, spaceIdx)
|
|
59
|
+
switch (type.toLowerCase()) {
|
|
60
|
+
case 'bearer':
|
|
61
|
+
return `${type} ${obfuscateBearer(authHeader.slice(spaceIdx + 1))}`
|
|
62
|
+
case 'dpop':
|
|
63
|
+
return `${type} ${obfuscateJws(authHeader.slice(spaceIdx + 1)) || 'Invalid'}`
|
|
64
|
+
case 'basic':
|
|
65
|
+
return `${type} ${obfuscateBasic(authHeader.slice(spaceIdx + 1)) || 'Invalid'}`
|
|
66
|
+
default:
|
|
67
|
+
return `Invalid`
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function obfuscateBasic(token: string): null | string {
|
|
72
|
+
if (!token) return null
|
|
73
|
+
const buffer = Buffer.from(token, 'base64')
|
|
74
|
+
if (!buffer.length) return null // Buffer.from will silently ignore invalid base64 chars
|
|
75
|
+
const authHeader = buffer.toString('utf8')
|
|
76
|
+
const colIdx = authHeader.indexOf(':')
|
|
77
|
+
if (colIdx === -1) return null
|
|
78
|
+
const username = authHeader.slice(0, colIdx)
|
|
79
|
+
return `${username}:***`
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function obfuscateBearer(token: string): string {
|
|
83
|
+
return obfuscateJws(token) || obfuscateToken(token)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function obfuscateToken(token: string): string {
|
|
87
|
+
return token ? '***' : ''
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function obfuscateJws(token: string): null | string {
|
|
91
|
+
const firstDot = token.indexOf('.')
|
|
92
|
+
if (firstDot === -1) return null
|
|
93
|
+
|
|
94
|
+
const secondDot = token.indexOf('.', firstDot + 1)
|
|
95
|
+
if (secondDot === -1) return null
|
|
96
|
+
|
|
97
|
+
if (token.indexOf('.', secondDot + 1) !== -1) return null
|
|
98
|
+
|
|
99
|
+
// Strip the signature
|
|
100
|
+
return token.slice(0, secondDot) + '.obfuscated'
|
|
101
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AccountInfo,
|
|
3
|
+
AccountStore,
|
|
4
|
+
DeviceId,
|
|
5
|
+
LoginCredentials,
|
|
6
|
+
} from '@atproto/oauth-provider'
|
|
7
|
+
|
|
8
|
+
import { AccountManager } from '../account-manager/index'
|
|
9
|
+
import { ActorStore } from '../actor-store/index'
|
|
10
|
+
import { ProfileViewBasic } from '../lexicon/types/app/bsky/actor/defs'
|
|
11
|
+
import { LocalViewerCreator } from '../read-after-write/index'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Although the {@link AccountManager} class implements the {@link AccountStore}
|
|
15
|
+
* interface, the accounts it returns do not contain any profile information
|
|
16
|
+
* (display name, avatar, etc). This is due to the fact that the account manager
|
|
17
|
+
* does not have access to the account's repos. The {@link DetailedAccountStore}
|
|
18
|
+
* is a wrapper around the {@link AccountManager} that enriches the accounts
|
|
19
|
+
* with profile information using the account's repos through the
|
|
20
|
+
* {@link ActorStore}.
|
|
21
|
+
*/
|
|
22
|
+
export class DetailedAccountStore implements AccountStore {
|
|
23
|
+
constructor(
|
|
24
|
+
private accountManager: AccountManager,
|
|
25
|
+
private actorStore: ActorStore,
|
|
26
|
+
private localViewer: LocalViewerCreator,
|
|
27
|
+
) {}
|
|
28
|
+
|
|
29
|
+
private async getProfile(did: string): Promise<ProfileViewBasic | null> {
|
|
30
|
+
// TODO: Should we cache this?
|
|
31
|
+
return this.actorStore.read(did, async (actorStoreReader) => {
|
|
32
|
+
const localViewer = this.localViewer(actorStoreReader)
|
|
33
|
+
return localViewer.getProfileBasic()
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private async enrichAccountInfo(
|
|
38
|
+
accountInfo: AccountInfo,
|
|
39
|
+
): Promise<AccountInfo> {
|
|
40
|
+
const { account } = accountInfo
|
|
41
|
+
if (!account.picture || !account.name) {
|
|
42
|
+
const profile = await this.getProfile(account.sub)
|
|
43
|
+
if (profile) {
|
|
44
|
+
account.picture ||= profile.avatar
|
|
45
|
+
account.name ||= profile.displayName
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return accountInfo
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async authenticateAccount(
|
|
53
|
+
credentials: LoginCredentials,
|
|
54
|
+
deviceId: DeviceId,
|
|
55
|
+
): Promise<AccountInfo | null> {
|
|
56
|
+
const accountInfo = await this.accountManager.authenticateAccount(
|
|
57
|
+
credentials,
|
|
58
|
+
deviceId,
|
|
59
|
+
)
|
|
60
|
+
if (!accountInfo) return null
|
|
61
|
+
return this.enrichAccountInfo(accountInfo)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async addAuthorizedClient(
|
|
65
|
+
deviceId: DeviceId,
|
|
66
|
+
sub: string,
|
|
67
|
+
clientId: string,
|
|
68
|
+
): Promise<void> {
|
|
69
|
+
return this.accountManager.addAuthorizedClient(deviceId, sub, clientId)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async getDeviceAccount(
|
|
73
|
+
deviceId: DeviceId,
|
|
74
|
+
sub: string,
|
|
75
|
+
): Promise<AccountInfo | null> {
|
|
76
|
+
const accountInfo = await this.accountManager.getDeviceAccount(
|
|
77
|
+
deviceId,
|
|
78
|
+
sub,
|
|
79
|
+
)
|
|
80
|
+
if (!accountInfo) return null
|
|
81
|
+
return this.enrichAccountInfo(accountInfo)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async listDeviceAccounts(deviceId: DeviceId): Promise<AccountInfo[]> {
|
|
85
|
+
const accountInfos = await this.accountManager.listDeviceAccounts(deviceId)
|
|
86
|
+
return Promise.all(
|
|
87
|
+
accountInfos.map(async (accountInfo) =>
|
|
88
|
+
this.enrichAccountInfo(accountInfo),
|
|
89
|
+
),
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async removeDeviceAccount(deviceId: DeviceId, sub: string): Promise<void> {
|
|
94
|
+
return this.accountManager.removeDeviceAccount(deviceId, sub)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AccessTokenType,
|
|
3
|
+
OAuthProvider,
|
|
4
|
+
OAuthProviderOptions,
|
|
5
|
+
} from '@atproto/oauth-provider'
|
|
6
|
+
|
|
7
|
+
import { AccountManager } from '../account-manager/index'
|
|
8
|
+
import { ActorStore } from '../actor-store/index'
|
|
9
|
+
import { oauthLogger } from '../logger'
|
|
10
|
+
import { LocalViewerCreator } from '../read-after-write/index'
|
|
11
|
+
import { DetailedAccountStore } from './detailed-account-store'
|
|
12
|
+
|
|
13
|
+
export type AuthProviderOptions = {
|
|
14
|
+
accountManager: AccountManager
|
|
15
|
+
actorStore: ActorStore
|
|
16
|
+
localViewer: LocalViewerCreator
|
|
17
|
+
} & Pick<
|
|
18
|
+
OAuthProviderOptions,
|
|
19
|
+
'issuer' | 'redis' | 'keyset' | 'dpopSecret' | 'customization'
|
|
20
|
+
> &
|
|
21
|
+
Required<Pick<OAuthProviderOptions, 'safeFetch'>>
|
|
22
|
+
|
|
23
|
+
export class PdsOAuthProvider extends OAuthProvider {
|
|
24
|
+
constructor({
|
|
25
|
+
accountManager,
|
|
26
|
+
actorStore,
|
|
27
|
+
localViewer,
|
|
28
|
+
keyset,
|
|
29
|
+
redis,
|
|
30
|
+
dpopSecret,
|
|
31
|
+
issuer,
|
|
32
|
+
customization,
|
|
33
|
+
safeFetch,
|
|
34
|
+
}: AuthProviderOptions) {
|
|
35
|
+
super({
|
|
36
|
+
issuer,
|
|
37
|
+
keyset,
|
|
38
|
+
dpopSecret,
|
|
39
|
+
redis,
|
|
40
|
+
safeFetch,
|
|
41
|
+
customization,
|
|
42
|
+
metadata: {
|
|
43
|
+
// PdsOAuthProvider is used when the PDS is both an authorization server
|
|
44
|
+
// & resource server, in which case the issuer origin is also the
|
|
45
|
+
// resource server uri.
|
|
46
|
+
protected_resources: [new URL(issuer).origin],
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
accountStore: new DetailedAccountStore(
|
|
50
|
+
accountManager,
|
|
51
|
+
actorStore,
|
|
52
|
+
localViewer,
|
|
53
|
+
),
|
|
54
|
+
requestStore: accountManager,
|
|
55
|
+
deviceStore: accountManager,
|
|
56
|
+
tokenStore: accountManager,
|
|
57
|
+
|
|
58
|
+
// If the PDS is both an authorization server & resource server (no
|
|
59
|
+
// entryway), there is no need to use JWTs as access tokens. Instead,
|
|
60
|
+
// the PDS can use tokenId as access tokens. This allows the PDS to
|
|
61
|
+
// always use up-to-date token data from the token store.
|
|
62
|
+
accessTokenType: AccessTokenType.id,
|
|
63
|
+
|
|
64
|
+
onClientInfo: (clientId) => ({
|
|
65
|
+
isFirstParty: clientId === 'https://bsky.app/',
|
|
66
|
+
// @TODO make client client list configurable:
|
|
67
|
+
isTrusted: undefined,
|
|
68
|
+
}),
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
createRouter() {
|
|
73
|
+
return this.httpHandler({
|
|
74
|
+
onError: (req, res, err, message) => oauthLogger.error({ err }, message),
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
}
|
package/src/pipethrough.ts
CHANGED
|
@@ -22,7 +22,8 @@ export const proxyHandler = (ctx: AppContext): CatchallHandler => {
|
|
|
22
22
|
const { url, aud } = await formatUrlAndAud(ctx, req)
|
|
23
23
|
const auth = await accessStandard({ req })
|
|
24
24
|
const headers = await formatHeaders(ctx, req, aud, auth.credentials.did)
|
|
25
|
-
const body =
|
|
25
|
+
const body: webStream.ReadableStream<Uint8Array> =
|
|
26
|
+
stream.Readable.toWeb(req)
|
|
26
27
|
const reqInit = formatReqInit(req, headers, body)
|
|
27
28
|
const proxyRes = await makeRequest(url, reqInit)
|
|
28
29
|
await pipeProxyRes(proxyRes, res)
|
|
@@ -113,7 +114,7 @@ export const formatHeaders = async (
|
|
|
113
114
|
const formatReqInit = (
|
|
114
115
|
req: express.Request,
|
|
115
116
|
headers: Record<string, string>,
|
|
116
|
-
body?: Uint8Array | ReadableStream
|
|
117
|
+
body?: Uint8Array | webStream.ReadableStream<Uint8Array>,
|
|
117
118
|
): RequestInit => {
|
|
118
119
|
if (req.method === 'GET') {
|
|
119
120
|
return {
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { TestNetworkNoAppView, SeedClient } from '@atproto/dev-env'
|
|
2
2
|
import AtpAgent from '@atproto/api'
|
|
3
3
|
import usersSeed from './seeds/users'
|
|
4
|
+
import { AuthScope } from '../dist/auth-verifier'
|
|
4
5
|
|
|
5
6
|
describe('user preferences', () => {
|
|
6
7
|
let network: TestNetworkNoAppView
|
|
7
8
|
let agent: AtpAgent
|
|
8
9
|
let sc: SeedClient
|
|
10
|
+
let appPassHeaders: { authorization: string }
|
|
9
11
|
|
|
10
12
|
beforeAll(async () => {
|
|
11
13
|
network = await TestNetworkNoAppView.create({
|
|
@@ -14,6 +16,16 @@ describe('user preferences', () => {
|
|
|
14
16
|
agent = network.pds.getClient()
|
|
15
17
|
sc = network.getSeedClient()
|
|
16
18
|
await usersSeed(sc)
|
|
19
|
+
const appPass = await network.pds.ctx.accountManager.createAppPassword(
|
|
20
|
+
sc.dids.alice,
|
|
21
|
+
'test app pass',
|
|
22
|
+
false,
|
|
23
|
+
)
|
|
24
|
+
const res = await agent.com.atproto.server.createSession({
|
|
25
|
+
identifier: sc.dids.alice,
|
|
26
|
+
password: appPass.password,
|
|
27
|
+
})
|
|
28
|
+
appPassHeaders = { authorization: `Bearer ${res.data.accessJwt}` }
|
|
17
29
|
})
|
|
18
30
|
|
|
19
31
|
afterAll(async () => {
|
|
@@ -46,6 +58,7 @@ describe('user preferences', () => {
|
|
|
46
58
|
store.pref.putPreferences(
|
|
47
59
|
[{ $type: 'com.atproto.server.defs#unknown' }],
|
|
48
60
|
'com.atproto',
|
|
61
|
+
AuthScope.Access,
|
|
49
62
|
),
|
|
50
63
|
)
|
|
51
64
|
const { data } = await agent.api.app.bsky.actor.getPreferences(
|
|
@@ -96,7 +109,7 @@ describe('user preferences', () => {
|
|
|
96
109
|
// Ensure other prefs were not clobbered
|
|
97
110
|
const otherPrefs = await network.pds.ctx.actorStore.read(
|
|
98
111
|
sc.dids.alice,
|
|
99
|
-
(store) => store.pref.getPreferences('com.atproto'),
|
|
112
|
+
(store) => store.pref.getPreferences('com.atproto', AuthScope.Access),
|
|
100
113
|
)
|
|
101
114
|
expect(otherPrefs).toEqual([{ $type: 'com.atproto.server.defs#unknown' }])
|
|
102
115
|
})
|
|
@@ -178,4 +191,57 @@ describe('user preferences', () => {
|
|
|
178
191
|
'Input/preferences/1 must be an object which includes the "$type" property',
|
|
179
192
|
)
|
|
180
193
|
})
|
|
194
|
+
|
|
195
|
+
it('does not read permissioned preferences with an app password', async () => {
|
|
196
|
+
await agent.api.app.bsky.actor.putPreferences(
|
|
197
|
+
{
|
|
198
|
+
preferences: [
|
|
199
|
+
{
|
|
200
|
+
$type: 'app.bsky.actor.defs#personalDetailsPref',
|
|
201
|
+
birthDate: new Date().toISOString(),
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
},
|
|
205
|
+
{ headers: sc.getHeaders(sc.dids.alice), encoding: 'application/json' },
|
|
206
|
+
)
|
|
207
|
+
const res = await agent.api.app.bsky.actor.getPreferences(
|
|
208
|
+
{},
|
|
209
|
+
{ headers: appPassHeaders },
|
|
210
|
+
)
|
|
211
|
+
expect(res.data.preferences).toEqual([])
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
it('does not write permissioned preferences with an app password', async () => {
|
|
215
|
+
const tryPut = agent.api.app.bsky.actor.putPreferences(
|
|
216
|
+
{
|
|
217
|
+
preferences: [
|
|
218
|
+
{
|
|
219
|
+
$type: 'app.bsky.actor.defs#personalDetailsPref',
|
|
220
|
+
birthDate: new Date().toISOString(),
|
|
221
|
+
},
|
|
222
|
+
],
|
|
223
|
+
},
|
|
224
|
+
{ headers: appPassHeaders, encoding: 'application/json' },
|
|
225
|
+
)
|
|
226
|
+
await expect(tryPut).rejects.toThrow(
|
|
227
|
+
/Do not have authorization to set preferences/,
|
|
228
|
+
)
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
it('does not remove permissioned preferences with an app password', async () => {
|
|
232
|
+
await agent.api.app.bsky.actor.putPreferences(
|
|
233
|
+
{
|
|
234
|
+
preferences: [],
|
|
235
|
+
},
|
|
236
|
+
{ headers: appPassHeaders, encoding: 'application/json' },
|
|
237
|
+
)
|
|
238
|
+
const res = await agent.api.app.bsky.actor.getPreferences(
|
|
239
|
+
{},
|
|
240
|
+
{ headers: sc.getHeaders(sc.dids.alice) },
|
|
241
|
+
)
|
|
242
|
+
const scopedPref = res.data.preferences.find(
|
|
243
|
+
(pref) => pref.$type === 'app.bsky.actor.defs#personalDetailsPref',
|
|
244
|
+
)
|
|
245
|
+
expect(scopedPref).toBeDefined()
|
|
246
|
+
})
|
|
181
247
|
})
|
|
@@ -59,7 +59,9 @@ Object {
|
|
|
59
59
|
"replyCount": 0,
|
|
60
60
|
"repostCount": 0,
|
|
61
61
|
"uri": "record(0)",
|
|
62
|
-
"viewer": Object {
|
|
62
|
+
"viewer": Object {
|
|
63
|
+
"threadMuted": false,
|
|
64
|
+
},
|
|
63
65
|
},
|
|
64
66
|
},
|
|
65
67
|
Object {
|
|
@@ -178,6 +180,7 @@ Object {
|
|
|
178
180
|
"uri": "record(2)",
|
|
179
181
|
"viewer": Object {
|
|
180
182
|
"like": "record(8)",
|
|
183
|
+
"threadMuted": false,
|
|
181
184
|
},
|
|
182
185
|
},
|
|
183
186
|
},
|