@atproto/pds 0.4.53 → 0.4.55
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 +31 -0
- package/dist/account-manager/helpers/auth.d.ts.map +1 -1
- package/dist/account-manager/helpers/auth.js +8 -2
- package/dist/account-manager/helpers/auth.js.map +1 -1
- package/dist/api/com/atproto/admin/sendEmail.js +1 -4
- package/dist/api/com/atproto/admin/sendEmail.js.map +1 -1
- package/dist/api/com/atproto/admin/updateAccountEmail.js +2 -2
- package/dist/api/com/atproto/admin/updateAccountEmail.js.map +1 -1
- package/dist/api/com/atproto/admin/updateAccountPassword.js +2 -2
- package/dist/api/com/atproto/admin/updateAccountPassword.js.map +1 -1
- package/dist/api/com/atproto/identity/requestPlcOperationSignature.d.ts +1 -1
- package/dist/api/com/atproto/identity/requestPlcOperationSignature.d.ts.map +1 -1
- package/dist/api/com/atproto/identity/requestPlcOperationSignature.js +8 -3
- package/dist/api/com/atproto/identity/requestPlcOperationSignature.js.map +1 -1
- package/dist/api/com/atproto/identity/signPlcOperation.d.ts +1 -1
- package/dist/api/com/atproto/identity/signPlcOperation.d.ts.map +1 -1
- package/dist/api/com/atproto/identity/signPlcOperation.js +9 -3
- package/dist/api/com/atproto/identity/signPlcOperation.js.map +1 -1
- package/dist/api/com/atproto/identity/updateHandle.d.ts.map +1 -1
- package/dist/api/com/atproto/identity/updateHandle.js +8 -3
- package/dist/api/com/atproto/identity/updateHandle.js.map +1 -1
- package/dist/api/com/atproto/server/activateAccount.d.ts +1 -1
- package/dist/api/com/atproto/server/activateAccount.d.ts.map +1 -1
- package/dist/api/com/atproto/server/activateAccount.js +9 -4
- package/dist/api/com/atproto/server/activateAccount.js.map +1 -1
- package/dist/api/com/atproto/server/confirmEmail.d.ts +1 -1
- package/dist/api/com/atproto/server/confirmEmail.d.ts.map +1 -1
- package/dist/api/com/atproto/server/confirmEmail.js +8 -3
- package/dist/api/com/atproto/server/confirmEmail.js.map +1 -1
- package/dist/api/com/atproto/server/createAppPassword.d.ts.map +1 -1
- package/dist/api/com/atproto/server/createAppPassword.js +8 -2
- package/dist/api/com/atproto/server/createAppPassword.js.map +1 -1
- package/dist/api/com/atproto/server/deactivateAccount.d.ts +1 -1
- package/dist/api/com/atproto/server/deactivateAccount.d.ts.map +1 -1
- package/dist/api/com/atproto/server/deactivateAccount.js +8 -3
- package/dist/api/com/atproto/server/deactivateAccount.js.map +1 -1
- package/dist/api/com/atproto/server/getAccountInviteCodes.d.ts +1 -1
- package/dist/api/com/atproto/server/getAccountInviteCodes.d.ts.map +1 -1
- package/dist/api/com/atproto/server/getAccountInviteCodes.js +9 -3
- package/dist/api/com/atproto/server/getAccountInviteCodes.js.map +1 -1
- package/dist/api/com/atproto/server/getServiceAuth.d.ts.map +1 -1
- package/dist/api/com/atproto/server/getServiceAuth.js +7 -4
- package/dist/api/com/atproto/server/getServiceAuth.js.map +1 -1
- package/dist/api/com/atproto/server/listAppPasswords.d.ts.map +1 -1
- package/dist/api/com/atproto/server/listAppPasswords.js +8 -2
- package/dist/api/com/atproto/server/listAppPasswords.js.map +1 -1
- package/dist/api/com/atproto/server/requestAccountDelete.d.ts +1 -1
- package/dist/api/com/atproto/server/requestAccountDelete.d.ts.map +1 -1
- package/dist/api/com/atproto/server/requestAccountDelete.js +8 -3
- package/dist/api/com/atproto/server/requestAccountDelete.js.map +1 -1
- package/dist/api/com/atproto/server/requestEmailConfirmation.d.ts +1 -1
- package/dist/api/com/atproto/server/requestEmailConfirmation.d.ts.map +1 -1
- package/dist/api/com/atproto/server/requestEmailConfirmation.js +8 -3
- package/dist/api/com/atproto/server/requestEmailConfirmation.js.map +1 -1
- package/dist/api/com/atproto/server/requestEmailUpdate.d.ts +1 -1
- package/dist/api/com/atproto/server/requestEmailUpdate.d.ts.map +1 -1
- package/dist/api/com/atproto/server/requestEmailUpdate.js +8 -2
- package/dist/api/com/atproto/server/requestEmailUpdate.js.map +1 -1
- package/dist/api/com/atproto/server/revokeAppPassword.d.ts.map +1 -1
- package/dist/api/com/atproto/server/revokeAppPassword.js +8 -3
- package/dist/api/com/atproto/server/revokeAppPassword.js.map +1 -1
- package/dist/api/com/atproto/server/updateEmail.d.ts +1 -1
- package/dist/api/com/atproto/server/updateEmail.d.ts.map +1 -1
- package/dist/api/com/atproto/server/updateEmail.js +6 -4
- package/dist/api/com/atproto/server/updateEmail.js.map +1 -1
- package/dist/api/proxy.js +5 -1
- package/dist/api/proxy.js.map +1 -1
- package/dist/auth-routes.d.ts.map +1 -1
- package/dist/auth-routes.js +3 -1
- package/dist/auth-routes.js.map +1 -1
- package/dist/auth-verifier.d.ts +2 -2
- package/dist/auth-verifier.d.ts.map +1 -1
- package/dist/auth-verifier.js +46 -15
- package/dist/auth-verifier.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -6
- package/dist/index.js.map +1 -1
- package/dist/lexicon/lexicons.d.ts +4 -0
- package/dist/lexicon/lexicons.d.ts.map +1 -1
- package/dist/lexicon/lexicons.js +4 -0
- package/dist/lexicon/lexicons.js.map +1 -1
- package/dist/lexicon/types/app/bsky/feed/getPostThread.d.ts +1 -0
- package/dist/lexicon/types/app/bsky/feed/getPostThread.d.ts.map +1 -1
- package/dist/oauth/provider.d.ts.map +1 -1
- package/dist/oauth/provider.js +1 -0
- package/dist/oauth/provider.js.map +1 -1
- package/dist/pipethrough.d.ts +1 -0
- package/dist/pipethrough.d.ts.map +1 -1
- package/dist/pipethrough.js +23 -2
- package/dist/pipethrough.js.map +1 -1
- package/package.json +11 -11
- package/src/account-manager/helpers/auth.ts +8 -2
- package/src/api/com/atproto/admin/sendEmail.ts +5 -5
- package/src/api/com/atproto/admin/updateAccountEmail.ts +1 -1
- package/src/api/com/atproto/admin/updateAccountPassword.ts +1 -1
- package/src/api/com/atproto/identity/requestPlcOperationSignature.ts +13 -5
- package/src/api/com/atproto/identity/signPlcOperation.ts +15 -6
- package/src/api/com/atproto/identity/updateHandle.ts +10 -3
- package/src/api/com/atproto/server/activateAccount.ts +14 -5
- package/src/api/com/atproto/server/confirmEmail.ts +13 -5
- package/src/api/com/atproto/server/createAppPassword.ts +12 -3
- package/src/api/com/atproto/server/deactivateAccount.ts +11 -4
- package/src/api/com/atproto/server/getAccountInviteCodes.ts +14 -5
- package/src/api/com/atproto/server/getServiceAuth.ts +14 -9
- package/src/api/com/atproto/server/listAppPasswords.ts +11 -3
- package/src/api/com/atproto/server/requestAccountDelete.ts +12 -4
- package/src/api/com/atproto/server/requestEmailConfirmation.ts +12 -4
- package/src/api/com/atproto/server/requestEmailUpdate.ts +13 -4
- package/src/api/com/atproto/server/revokeAppPassword.ts +10 -3
- package/src/api/com/atproto/server/updateEmail.ts +14 -6
- package/src/api/proxy.ts +5 -1
- package/src/auth-routes.ts +3 -1
- package/src/auth-verifier.ts +63 -21
- package/src/index.ts +6 -7
- package/src/lexicon/lexicons.ts +4 -0
- package/src/lexicon/types/app/bsky/feed/getPostThread.ts +1 -0
- package/src/oauth/provider.ts +2 -0
- package/src/pipethrough.ts +25 -1
- package/tests/app-passwords.test.ts +2 -2
- package/tests/auth.test.ts +1 -1
- package/tests/entryway.test.ts +30 -4
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import assert from 'node:assert'
|
|
2
|
+
|
|
1
3
|
import { DAY, HOUR } from '@atproto/common'
|
|
2
4
|
import { InvalidRequestError } from '@atproto/xrpc-server'
|
|
3
|
-
|
|
5
|
+
|
|
4
6
|
import AppContext from '../../../../context'
|
|
5
|
-
import {
|
|
7
|
+
import { Server } from '../../../../lexicon'
|
|
8
|
+
import { ids } from '../../../../lexicon/lexicons'
|
|
6
9
|
|
|
7
10
|
export default function (server: Server, ctx: AppContext) {
|
|
8
11
|
server.com.atproto.server.requestAccountDelete({
|
|
@@ -19,7 +22,7 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
19
22
|
},
|
|
20
23
|
],
|
|
21
24
|
auth: ctx.authVerifier.accessFull({ checkTakedown: true }),
|
|
22
|
-
handler: async ({ auth
|
|
25
|
+
handler: async ({ auth }) => {
|
|
23
26
|
const did = auth.credentials.did
|
|
24
27
|
const account = await ctx.accountManager.getAccount(did, {
|
|
25
28
|
includeDeactivated: true,
|
|
@@ -30,9 +33,14 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
if (ctx.entrywayAgent) {
|
|
36
|
+
assert(ctx.cfg.entryway)
|
|
33
37
|
await ctx.entrywayAgent.com.atproto.server.requestAccountDelete(
|
|
34
38
|
undefined,
|
|
35
|
-
|
|
39
|
+
await ctx.serviceAuthHeaders(
|
|
40
|
+
auth.credentials.did,
|
|
41
|
+
ctx.cfg.entryway.did,
|
|
42
|
+
ids.ComAtprotoServerRequestAccountDelete,
|
|
43
|
+
),
|
|
36
44
|
)
|
|
37
45
|
return
|
|
38
46
|
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import assert from 'node:assert'
|
|
2
|
+
|
|
1
3
|
import { DAY, HOUR } from '@atproto/common'
|
|
2
4
|
import { InvalidRequestError } from '@atproto/xrpc-server'
|
|
3
|
-
|
|
5
|
+
|
|
4
6
|
import AppContext from '../../../../context'
|
|
5
|
-
import {
|
|
7
|
+
import { Server } from '../../../../lexicon'
|
|
8
|
+
import { ids } from '../../../../lexicon/lexicons'
|
|
6
9
|
|
|
7
10
|
export default function (server: Server, ctx: AppContext) {
|
|
8
11
|
server.com.atproto.server.requestEmailConfirmation({
|
|
@@ -19,7 +22,7 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
19
22
|
},
|
|
20
23
|
],
|
|
21
24
|
auth: ctx.authVerifier.accessStandard({ checkTakedown: true }),
|
|
22
|
-
handler: async ({ auth
|
|
25
|
+
handler: async ({ auth }) => {
|
|
23
26
|
const did = auth.credentials.did
|
|
24
27
|
const account = await ctx.accountManager.getAccount(did, {
|
|
25
28
|
includeDeactivated: true,
|
|
@@ -30,9 +33,14 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
if (ctx.entrywayAgent) {
|
|
36
|
+
assert(ctx.cfg.entryway)
|
|
33
37
|
await ctx.entrywayAgent.com.atproto.server.requestEmailConfirmation(
|
|
34
38
|
undefined,
|
|
35
|
-
|
|
39
|
+
await ctx.serviceAuthHeaders(
|
|
40
|
+
auth.credentials.did,
|
|
41
|
+
ctx.cfg.entryway.did,
|
|
42
|
+
ids.ComAtprotoServerRequestEmailConfirmation,
|
|
43
|
+
),
|
|
36
44
|
)
|
|
37
45
|
return
|
|
38
46
|
}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
import assert from 'node:assert'
|
|
2
|
+
|
|
1
3
|
import { DAY, HOUR } from '@atproto/common'
|
|
2
4
|
import { InvalidRequestError } from '@atproto/xrpc-server'
|
|
3
|
-
|
|
5
|
+
|
|
4
6
|
import AppContext from '../../../../context'
|
|
5
|
-
import {
|
|
7
|
+
import { Server } from '../../../../lexicon'
|
|
8
|
+
import { resultPassthru } from '../../../proxy'
|
|
9
|
+
import { ids } from '../../../../lexicon/lexicons'
|
|
6
10
|
|
|
7
11
|
export default function (server: Server, ctx: AppContext) {
|
|
8
12
|
server.com.atproto.server.requestEmailUpdate({
|
|
@@ -19,7 +23,7 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
19
23
|
},
|
|
20
24
|
],
|
|
21
25
|
auth: ctx.authVerifier.accessStandard({ checkTakedown: true }),
|
|
22
|
-
handler: async ({ auth
|
|
26
|
+
handler: async ({ auth }) => {
|
|
23
27
|
const did = auth.credentials.did
|
|
24
28
|
const account = await ctx.accountManager.getAccount(did, {
|
|
25
29
|
includeDeactivated: true,
|
|
@@ -30,10 +34,15 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
if (ctx.entrywayAgent) {
|
|
37
|
+
assert(ctx.cfg.entryway)
|
|
33
38
|
return resultPassthru(
|
|
34
39
|
await ctx.entrywayAgent.com.atproto.server.requestEmailUpdate(
|
|
35
40
|
undefined,
|
|
36
|
-
|
|
41
|
+
await ctx.serviceAuthHeaders(
|
|
42
|
+
auth.credentials.did,
|
|
43
|
+
ctx.cfg.entryway.did,
|
|
44
|
+
ids.ComAtprotoServerRequestEmailUpdate,
|
|
45
|
+
),
|
|
37
46
|
),
|
|
38
47
|
)
|
|
39
48
|
}
|
|
@@ -1,15 +1,22 @@
|
|
|
1
|
+
import assert from 'node:assert'
|
|
2
|
+
|
|
1
3
|
import AppContext from '../../../../context'
|
|
2
4
|
import { Server } from '../../../../lexicon'
|
|
3
|
-
import {
|
|
5
|
+
import { ids } from '../../../../lexicon/lexicons'
|
|
4
6
|
|
|
5
7
|
export default function (server: Server, ctx: AppContext) {
|
|
6
8
|
server.com.atproto.server.revokeAppPassword({
|
|
7
9
|
auth: ctx.authVerifier.accessStandard(),
|
|
8
|
-
handler: async ({ auth, input
|
|
10
|
+
handler: async ({ auth, input }) => {
|
|
9
11
|
if (ctx.entrywayAgent) {
|
|
12
|
+
assert(ctx.cfg.entryway)
|
|
10
13
|
await ctx.entrywayAgent.com.atproto.server.revokeAppPassword(
|
|
11
14
|
input.body,
|
|
12
|
-
|
|
15
|
+
await ctx.serviceAuthHeaders(
|
|
16
|
+
auth.credentials.did,
|
|
17
|
+
ctx.cfg.entryway.did,
|
|
18
|
+
ids.ComAtprotoServerRevokeAppPassword,
|
|
19
|
+
),
|
|
13
20
|
)
|
|
14
21
|
return
|
|
15
22
|
}
|
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
import
|
|
1
|
+
import assert from 'node:assert'
|
|
2
|
+
|
|
2
3
|
import { InvalidRequestError } from '@atproto/xrpc-server'
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
import { authPassthru } from '../../../proxy'
|
|
4
|
+
import disposable from 'disposable-email'
|
|
5
|
+
|
|
6
6
|
import { UserAlreadyExistsError } from '../../../../account-manager/helpers/account'
|
|
7
|
+
import AppContext from '../../../../context'
|
|
8
|
+
import { Server } from '../../../../lexicon'
|
|
9
|
+
import { ids } from '../../../../lexicon/lexicons'
|
|
7
10
|
|
|
8
11
|
export default function (server: Server, ctx: AppContext) {
|
|
9
12
|
server.com.atproto.server.updateEmail({
|
|
10
13
|
auth: ctx.authVerifier.accessFull({ checkTakedown: true }),
|
|
11
|
-
handler: async ({ auth, input
|
|
14
|
+
handler: async ({ auth, input }) => {
|
|
12
15
|
const did = auth.credentials.did
|
|
13
16
|
const { token, email } = input.body
|
|
14
17
|
if (!disposable.validate(email)) {
|
|
@@ -24,9 +27,14 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
if (ctx.entrywayAgent) {
|
|
30
|
+
assert(ctx.cfg.entryway)
|
|
27
31
|
await ctx.entrywayAgent.com.atproto.server.updateEmail(
|
|
28
32
|
input.body,
|
|
29
|
-
|
|
33
|
+
await ctx.serviceAuthHeaders(
|
|
34
|
+
auth.credentials.did,
|
|
35
|
+
ctx.cfg.entryway.did,
|
|
36
|
+
ids.ComAtprotoServerUpdateEmail,
|
|
37
|
+
),
|
|
30
38
|
)
|
|
31
39
|
return
|
|
32
40
|
}
|
package/src/api/proxy.ts
CHANGED
|
@@ -37,7 +37,11 @@ export function authPassthru(req: IncomingMessage, withEncoding?: boolean) {
|
|
|
37
37
|
// This is fine since app views are usually called using the requester's
|
|
38
38
|
// credentials when "auth.credentials.type === 'access'", which is the only
|
|
39
39
|
// case were DPoP is used.
|
|
40
|
-
|
|
40
|
+
const [type] = authorization.split(' ', 1)
|
|
41
|
+
if (!type) {
|
|
42
|
+
throw new InvalidRequestError('Invalid authorization header')
|
|
43
|
+
}
|
|
44
|
+
if (type.toLowerCase() === 'dpop' || req.headers['dpop']) {
|
|
41
45
|
throw new InvalidRequestError('DPoP requests cannot be proxied')
|
|
42
46
|
}
|
|
43
47
|
|
package/src/auth-routes.ts
CHANGED
|
@@ -11,11 +11,13 @@ export const createRouter = ({ authProvider, cfg }: AppContext): Router => {
|
|
|
11
11
|
resource: cfg.service.publicUrl,
|
|
12
12
|
authorization_servers: [cfg.entryway?.url ?? cfg.service.publicUrl],
|
|
13
13
|
bearer_methods_supported: ['header'],
|
|
14
|
-
scopes_supported: [
|
|
14
|
+
scopes_supported: [],
|
|
15
15
|
resource_documentation: 'https://atproto.com',
|
|
16
16
|
})
|
|
17
17
|
|
|
18
18
|
router.get('/.well-known/oauth-protected-resource', (req, res) => {
|
|
19
|
+
res.setHeader('Access-Control-Allow-Origin', '*')
|
|
20
|
+
res.setHeader('Access-Control-Allow-Method', '*')
|
|
19
21
|
res.status(200).json(oauthProtectedResourceMetadata)
|
|
20
22
|
})
|
|
21
23
|
|
package/src/auth-verifier.ts
CHANGED
|
@@ -320,10 +320,11 @@ export class AuthVerifier {
|
|
|
320
320
|
|
|
321
321
|
protected async validateRefreshToken(
|
|
322
322
|
ctx: ReqCtx,
|
|
323
|
-
verifyOptions?: Omit<jose.JWTVerifyOptions, 'audience'>,
|
|
323
|
+
verifyOptions?: Omit<jose.JWTVerifyOptions, 'audience' | 'typ'>,
|
|
324
324
|
): Promise<ValidatedRefreshBearer> {
|
|
325
325
|
const result = await this.validateBearerToken(ctx, [AuthScope.Refresh], {
|
|
326
326
|
...verifyOptions,
|
|
327
|
+
typ: 'refresh+jwt',
|
|
327
328
|
// when using entryway, proxying refresh credentials
|
|
328
329
|
audience: this.dids.entryway ? this.dids.entryway : this.dids.pds,
|
|
329
330
|
})
|
|
@@ -340,7 +341,8 @@ export class AuthVerifier {
|
|
|
340
341
|
protected async validateBearerToken(
|
|
341
342
|
ctx: ReqCtx,
|
|
342
343
|
scopes: AuthScope[],
|
|
343
|
-
verifyOptions
|
|
344
|
+
verifyOptions: jose.JWTVerifyOptions &
|
|
345
|
+
Required<Pick<jose.JWTVerifyOptions, 'audience' | 'typ'>>,
|
|
344
346
|
): Promise<ValidatedBearer> {
|
|
345
347
|
this.setAuthHeaders(ctx)
|
|
346
348
|
|
|
@@ -351,15 +353,26 @@ export class AuthVerifier {
|
|
|
351
353
|
|
|
352
354
|
const { payload, protectedHeader } = await this.jwtVerify(
|
|
353
355
|
token,
|
|
354
|
-
|
|
356
|
+
// @TODO: Once all access & refresh tokens have a "typ" claim (i.e. 90
|
|
357
|
+
// days after this code was deployed), replace the following line with
|
|
358
|
+
// "verifyOptions," (to re-enable the verification of the "typ" property
|
|
359
|
+
// from verifyJwt()). Once the change is made, the "if" block below that
|
|
360
|
+
// checks for "typ" can be removed.
|
|
361
|
+
{
|
|
362
|
+
...verifyOptions,
|
|
363
|
+
typ: undefined,
|
|
364
|
+
},
|
|
355
365
|
)
|
|
356
366
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
367
|
+
// @TODO: remove the next check once all access & refresh tokens have "typ"
|
|
368
|
+
// Note: when removing the check, make sure that the "verifyOptions"
|
|
369
|
+
// contains the "typ" property, so that the token is verified correctly by
|
|
370
|
+
// this.verifyJwt()
|
|
371
|
+
if (protectedHeader.typ && verifyOptions.typ !== protectedHeader.typ) {
|
|
372
|
+
// Temporarily allow historical tokens without "typ" to pass through. See:
|
|
373
|
+
// createAccessToken() and createRefreshToken() in
|
|
374
|
+
// src/account-manager/helpers/auth.ts
|
|
375
|
+
throw new InvalidRequestError('Invalid token type', 'InvalidToken')
|
|
363
376
|
}
|
|
364
377
|
|
|
365
378
|
const { sub, aud, scope } = payload
|
|
@@ -372,8 +385,9 @@ export class AuthVerifier {
|
|
|
372
385
|
) {
|
|
373
386
|
throw new InvalidRequestError('Malformed token', 'InvalidToken')
|
|
374
387
|
}
|
|
375
|
-
if (
|
|
376
|
-
//
|
|
388
|
+
if (payload['cnf'] !== undefined) {
|
|
389
|
+
// Proof-of-Possession (PoP) tokens are not allowed here
|
|
390
|
+
// https://www.rfc-editor.org/rfc/rfc7800.html
|
|
377
391
|
throw new InvalidRequestError('Malformed token', 'InvalidToken')
|
|
378
392
|
}
|
|
379
393
|
if (!isAuthScope(scope) || (scopes.length > 0 && !scopes.includes(scope))) {
|
|
@@ -452,13 +466,6 @@ export class AuthVerifier {
|
|
|
452
466
|
ctx: ReqCtx,
|
|
453
467
|
scopes: AuthScope[],
|
|
454
468
|
): Promise<AccessOutput> {
|
|
455
|
-
if (!scopes.includes(AuthScope.Access)) {
|
|
456
|
-
throw new InvalidRequestError(
|
|
457
|
-
'DPoP access token cannot be used for this request',
|
|
458
|
-
'InvalidToken',
|
|
459
|
-
)
|
|
460
|
-
}
|
|
461
|
-
|
|
462
469
|
this.setAuthHeaders(ctx)
|
|
463
470
|
|
|
464
471
|
const { req } = ctx
|
|
@@ -489,13 +496,48 @@ export class AuthVerifier {
|
|
|
489
496
|
throw new InvalidRequestError('Malformed token', 'InvalidToken')
|
|
490
497
|
}
|
|
491
498
|
|
|
499
|
+
const tokenScopes = new Set(result.claims.scope?.split(' '))
|
|
500
|
+
|
|
501
|
+
if (!tokenScopes.has('transition:generic')) {
|
|
502
|
+
throw new AuthRequiredError(
|
|
503
|
+
'Missing required scope: transition:generic',
|
|
504
|
+
'InvalidToken',
|
|
505
|
+
)
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
const scopeEquivalent: AuthScope = tokenScopes.has('transition:chat.bsky')
|
|
509
|
+
? AuthScope.AppPassPrivileged
|
|
510
|
+
: AuthScope.AppPass
|
|
511
|
+
|
|
512
|
+
if (!scopes.includes(scopeEquivalent)) {
|
|
513
|
+
// AppPassPrivileged is sufficient but was not provided "transition:chat.bsky"
|
|
514
|
+
if (scopes.includes(AuthScope.AppPassPrivileged)) {
|
|
515
|
+
throw new InvalidRequestError(
|
|
516
|
+
'Missing required scope: transition:chat.bsky',
|
|
517
|
+
'InvalidToken',
|
|
518
|
+
)
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// AuthScope.Access and AuthScope.SignupQueued do not have an OAuth
|
|
522
|
+
// scope equivalent.
|
|
523
|
+
throw new InvalidRequestError(
|
|
524
|
+
'DPoP access token cannot be used for this request',
|
|
525
|
+
'InvalidToken',
|
|
526
|
+
)
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
const isPrivileged = [
|
|
530
|
+
AuthScope.Access,
|
|
531
|
+
AuthScope.AppPassPrivileged,
|
|
532
|
+
].includes(scopeEquivalent)
|
|
533
|
+
|
|
492
534
|
return {
|
|
493
535
|
credentials: {
|
|
494
536
|
type: 'access',
|
|
495
537
|
did: result.claims.sub,
|
|
496
|
-
scope:
|
|
538
|
+
scope: scopeEquivalent,
|
|
497
539
|
audience: this.dids.pds,
|
|
498
|
-
isPrivileged
|
|
540
|
+
isPrivileged,
|
|
499
541
|
},
|
|
500
542
|
artifacts: result.token,
|
|
501
543
|
}
|
|
@@ -522,7 +564,7 @@ export class AuthVerifier {
|
|
|
522
564
|
const { did, scope, token, audience } = await this.validateBearerToken(
|
|
523
565
|
ctx,
|
|
524
566
|
scopes,
|
|
525
|
-
{ audience: this.dids.pds },
|
|
567
|
+
{ audience: this.dids.pds, typ: 'at+jwt' },
|
|
526
568
|
)
|
|
527
569
|
const isPrivileged = [
|
|
528
570
|
AuthScope.Access,
|
package/src/index.ts
CHANGED
|
@@ -54,12 +54,6 @@ export class PDS {
|
|
|
54
54
|
secrets: ServerSecrets,
|
|
55
55
|
overrides?: Partial<AppContextOptions>,
|
|
56
56
|
): Promise<PDS> {
|
|
57
|
-
const app = express()
|
|
58
|
-
app.set('trust proxy', true)
|
|
59
|
-
app.use(cors({ maxAge: DAY / SECOND }))
|
|
60
|
-
app.use(loggerMiddleware)
|
|
61
|
-
app.use(compression())
|
|
62
|
-
|
|
63
57
|
const ctx = await AppContext.fromConfig(cfg, secrets, overrides)
|
|
64
58
|
|
|
65
59
|
const xrpcOpts: XrpcServerOptions = {
|
|
@@ -100,7 +94,12 @@ export class PDS {
|
|
|
100
94
|
|
|
101
95
|
server = API(server, ctx)
|
|
102
96
|
|
|
103
|
-
app
|
|
97
|
+
const app = express()
|
|
98
|
+
app.set('trust proxy', true)
|
|
99
|
+
app.use(loggerMiddleware)
|
|
100
|
+
app.use(compression())
|
|
101
|
+
app.use(authRoutes.createRouter(ctx)) // Before CORS
|
|
102
|
+
app.use(cors({ maxAge: DAY / SECOND }))
|
|
104
103
|
app.use(basicRoutes.createRouter(ctx))
|
|
105
104
|
app.use(wellKnown.createRouter(ctx))
|
|
106
105
|
app.use(server.xrpc.router)
|
package/src/lexicon/lexicons.ts
CHANGED
package/src/oauth/provider.ts
CHANGED
|
@@ -44,6 +44,8 @@ export class PdsOAuthProvider extends OAuthProvider {
|
|
|
44
44
|
// & resource server, in which case the issuer origin is also the
|
|
45
45
|
// resource server uri.
|
|
46
46
|
protected_resources: [new URL(issuer).origin],
|
|
47
|
+
|
|
48
|
+
scopes_supported: ['transition:generic', 'transition:chat.bsky'],
|
|
47
49
|
},
|
|
48
50
|
|
|
49
51
|
accountStore: new DetailedAccountStore(
|
package/src/pipethrough.ts
CHANGED
|
@@ -22,7 +22,10 @@ export const proxyHandler = (ctx: AppContext): CatchallHandler => {
|
|
|
22
22
|
try {
|
|
23
23
|
const { url, aud, nsid } = await formatUrlAndAud(ctx, req)
|
|
24
24
|
const auth = await accessStandard({ req, res })
|
|
25
|
-
if (
|
|
25
|
+
if (
|
|
26
|
+
PROTECTED_METHODS.has(nsid) ||
|
|
27
|
+
(!auth.credentials.isPrivileged && PRIVILEGED_METHODS.has(nsid))
|
|
28
|
+
) {
|
|
26
29
|
throw new InvalidRequestError('Bad token method', 'InvalidToken')
|
|
27
30
|
}
|
|
28
31
|
const headers = await formatHeaders(ctx, req, {
|
|
@@ -276,6 +279,27 @@ export const PRIVILEGED_METHODS = new Set([
|
|
|
276
279
|
ids.ComAtprotoServerCreateAccount,
|
|
277
280
|
])
|
|
278
281
|
|
|
282
|
+
// These endpoints are related to account management and must be used directly,
|
|
283
|
+
// not proxied or service-authed. Service auth may be utilized between PDS and
|
|
284
|
+
// entryway for these methods.
|
|
285
|
+
export const PROTECTED_METHODS = new Set([
|
|
286
|
+
ids.ComAtprotoAdminSendEmail,
|
|
287
|
+
ids.ComAtprotoIdentityRequestPlcOperationSignature,
|
|
288
|
+
ids.ComAtprotoIdentitySignPlcOperation,
|
|
289
|
+
ids.ComAtprotoIdentityUpdateHandle,
|
|
290
|
+
ids.ComAtprotoServerActivateAccount,
|
|
291
|
+
ids.ComAtprotoServerConfirmEmail,
|
|
292
|
+
ids.ComAtprotoServerCreateAppPassword,
|
|
293
|
+
ids.ComAtprotoServerDeactivateAccount,
|
|
294
|
+
ids.ComAtprotoServerGetAccountInviteCodes,
|
|
295
|
+
ids.ComAtprotoServerListAppPasswords,
|
|
296
|
+
ids.ComAtprotoServerRequestAccountDelete,
|
|
297
|
+
ids.ComAtprotoServerRequestEmailConfirmation,
|
|
298
|
+
ids.ComAtprotoServerRequestEmailUpdate,
|
|
299
|
+
ids.ComAtprotoServerRevokeAppPassword,
|
|
300
|
+
ids.ComAtprotoServerUpdateEmail,
|
|
301
|
+
])
|
|
302
|
+
|
|
279
303
|
const defaultService = (
|
|
280
304
|
ctx: AppContext,
|
|
281
305
|
nsid: string,
|
|
@@ -118,7 +118,7 @@ describe('app_passwords', () => {
|
|
|
118
118
|
lxm: 'com.atproto.server.createAccount',
|
|
119
119
|
})
|
|
120
120
|
await expect(attempt).rejects.toThrow(
|
|
121
|
-
/
|
|
121
|
+
/insufficient access to request a service auth token for the following method/,
|
|
122
122
|
)
|
|
123
123
|
})
|
|
124
124
|
|
|
@@ -159,7 +159,7 @@ describe('app_passwords', () => {
|
|
|
159
159
|
lxm: 'com.atproto.server.createAccount',
|
|
160
160
|
})
|
|
161
161
|
await expect(priviAttempt).rejects.toThrow(
|
|
162
|
-
/
|
|
162
|
+
/insufficient access to request a service auth token for the following method/,
|
|
163
163
|
)
|
|
164
164
|
|
|
165
165
|
// allows only full access auth
|
package/tests/auth.test.ts
CHANGED
|
@@ -243,7 +243,7 @@ describe('auth', () => {
|
|
|
243
243
|
password: 'password',
|
|
244
244
|
})
|
|
245
245
|
const refreshWithAccess = refreshSession(account.accessJwt)
|
|
246
|
-
await expect(refreshWithAccess).rejects.toThrow('
|
|
246
|
+
await expect(refreshWithAccess).rejects.toThrow('Invalid token type')
|
|
247
247
|
})
|
|
248
248
|
|
|
249
249
|
it('expired refresh token cannot be used to refresh a session.', async () => {
|
package/tests/entryway.test.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import * as os from 'node:os'
|
|
2
2
|
import * as path from 'node:path'
|
|
3
|
+
import assert from 'node:assert'
|
|
4
|
+
import { decodeJwt } from 'jose'
|
|
3
5
|
import * as plcLib from '@did-plc/lib'
|
|
6
|
+
import { parseReqNsid } from '@atproto/xrpc-server'
|
|
4
7
|
import { AtpAgent } from '@atproto/api'
|
|
5
8
|
import { Secp256k1Keypair, randomStr } from '@atproto/crypto'
|
|
6
9
|
import { SeedClient, TestPds, TestPlc, mockResolvers } from '@atproto/dev-env'
|
|
@@ -114,10 +117,11 @@ describe('entryway', () => {
|
|
|
114
117
|
it('updates handle from entryway.', async () => {
|
|
115
118
|
await entrywayAgent.api.com.atproto.identity.updateHandle(
|
|
116
119
|
{ handle: 'alice3.test' },
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
120
|
+
await pds.ctx.serviceAuthHeaders(
|
|
121
|
+
alice,
|
|
122
|
+
'did:example:entryway',
|
|
123
|
+
'com.atproto.identity.updateHandle',
|
|
124
|
+
),
|
|
121
125
|
)
|
|
122
126
|
const doc = await entryway.ctx.idResolver.did.resolve(alice)
|
|
123
127
|
const handleToDid =
|
|
@@ -182,6 +186,28 @@ const createEntryway = async (
|
|
|
182
186
|
const server = await pdsEntryway.PDS.create(cfg, secrets)
|
|
183
187
|
await server.ctx.db.migrateToLatestOrThrow()
|
|
184
188
|
await server.start()
|
|
189
|
+
// patch entryway access token verification to handle internal service auth pds -> entryway
|
|
190
|
+
const origValidateAccessToken =
|
|
191
|
+
server.ctx.authVerifier.validateAccessToken.bind(server.ctx.authVerifier)
|
|
192
|
+
server.ctx.authVerifier.validateAccessToken = async (req, scopes) => {
|
|
193
|
+
const jwt = req.headers.authorization?.replace('Bearer ', '') ?? ''
|
|
194
|
+
const claims = decodeJwt(jwt)
|
|
195
|
+
if (claims.aud === 'did:example:entryway') {
|
|
196
|
+
assert(claims.lxm === parseReqNsid(req), 'bad lxm claim in service auth')
|
|
197
|
+
assert(claims.aud, 'missing aud claim in service auth')
|
|
198
|
+
assert(claims.iss, 'missing iss claim in service auth')
|
|
199
|
+
return {
|
|
200
|
+
artifacts: jwt,
|
|
201
|
+
credentials: {
|
|
202
|
+
type: 'access',
|
|
203
|
+
scope: 'com.atproto.access' as any,
|
|
204
|
+
audience: claims.aud,
|
|
205
|
+
did: claims.iss,
|
|
206
|
+
},
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return origValidateAccessToken(req, scopes)
|
|
210
|
+
}
|
|
185
211
|
// @TODO temp hack because entryway teardown calls signupActivator.run() by mistake
|
|
186
212
|
server.ctx.signupActivator.run = server.ctx.signupActivator.destroy
|
|
187
213
|
return server
|