forked from bluesky-social/atproto
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
PDS proxy to appview performance (bluesky-social#2773)
* accept entryway session tokens * extra check + tests * build * build * pr feedback --------- Co-authored-by: Devin Ivy <[email protected]>
- Loading branch information
Showing
7 changed files
with
270 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ on: | |
push: | ||
branches: | ||
- main | ||
- bsky-tweaks | ||
env: | ||
REGISTRY: ghcr.io | ||
USERNAME: ${{ github.actor }} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
import * as nodeCrypto from 'node:crypto' | ||
import KeyEncoder from 'key-encoder' | ||
import * as ui8 from 'uint8arrays' | ||
import * as jose from 'jose' | ||
import * as crypto from '@atproto/crypto' | ||
import { AtpAgent, AtUri } from '@atproto/api' | ||
import { basicSeed, SeedClient, TestNetwork } from '@atproto/dev-env' | ||
import assert from 'node:assert' | ||
import { MINUTE } from '@atproto/common' | ||
|
||
const keyEncoder = new KeyEncoder('secp256k1') | ||
|
||
const derivePrivKey = async ( | ||
keypair: crypto.ExportableKeypair, | ||
): Promise<nodeCrypto.KeyObject> => { | ||
const privKeyRaw = await keypair.export() | ||
const privKeyEncoded = keyEncoder.encodePrivate( | ||
ui8.toString(privKeyRaw, 'hex'), | ||
'raw', | ||
'pem', | ||
) | ||
return nodeCrypto.createPrivateKey(privKeyEncoded) | ||
} | ||
|
||
// @NOTE temporary measure, see note on entrywaySession in bsky/src/auth-verifier.ts | ||
describe('entryway auth', () => { | ||
let network: TestNetwork | ||
let agent: AtpAgent | ||
let sc: SeedClient | ||
let alice: string | ||
let jwtPrivKey: nodeCrypto.KeyObject | ||
|
||
beforeAll(async () => { | ||
const keypair = await crypto.Secp256k1Keypair.create({ exportable: true }) | ||
jwtPrivKey = await derivePrivKey(keypair) | ||
const entrywayJwtPublicKeyHex = ui8.toString( | ||
keypair.publicKeyBytes(), | ||
'hex', | ||
) | ||
|
||
network = await TestNetwork.create({ | ||
dbPostgresSchema: 'bsky_entryway_auth', | ||
bsky: { | ||
entrywayJwtPublicKeyHex, | ||
}, | ||
}) | ||
agent = network.bsky.getClient() | ||
sc = network.getSeedClient() | ||
await basicSeed(sc) | ||
await network.processAll() | ||
alice = sc.dids.alice | ||
}) | ||
|
||
afterAll(async () => { | ||
await network.close() | ||
}) | ||
|
||
it('works', async () => { | ||
const signer = new jose.SignJWT({ scope: 'com.atproto.access' }) | ||
.setSubject(alice) | ||
.setIssuedAt() | ||
.setExpirationTime('60mins') | ||
.setAudience('did:web:fake.server.bsky.network') | ||
.setProtectedHeader({ | ||
typ: 'at+jwt', // https://www.rfc-editor.org/rfc/rfc9068.html | ||
alg: 'ES256K', | ||
}) | ||
const token = await signer.sign(jwtPrivKey) | ||
const res = await agent.app.bsky.actor.getProfile( | ||
{ actor: sc.dids.bob }, | ||
{ headers: { authorization: `Bearer ${token}` } }, | ||
) | ||
expect(res.data.did).toEqual(sc.dids.bob) | ||
// ensure this request is personalized for alice | ||
const followingUri = res.data.viewer?.following | ||
assert(followingUri) | ||
const parsed = new AtUri(followingUri) | ||
expect(parsed.hostname).toEqual(alice) | ||
}) | ||
|
||
it('does not work on bad scopes', async () => { | ||
const signer = new jose.SignJWT({ scope: 'com.atproto.refresh' }) | ||
.setSubject(alice) | ||
.setIssuedAt() | ||
.setExpirationTime('60mins') | ||
.setAudience('did:web:fake.server.bsky.network') | ||
.setProtectedHeader({ | ||
typ: 'at+jwt', // https://www.rfc-editor.org/rfc/rfc9068.html | ||
alg: 'ES256K', | ||
}) | ||
const token = await signer.sign(jwtPrivKey) | ||
const attempt = agent.app.bsky.actor.getProfile( | ||
{ actor: sc.dids.bob }, | ||
{ headers: { authorization: `Bearer ${token}` } }, | ||
) | ||
await expect(attempt).rejects.toThrow('Bad token scope') | ||
}) | ||
|
||
it('does not work on expired tokens', async () => { | ||
const time = Math.floor((Date.now() - 5 * MINUTE) / 1000) | ||
const signer = new jose.SignJWT({ scope: 'com.atproto.access' }) | ||
.setSubject(alice) | ||
.setIssuedAt() | ||
.setExpirationTime(time) | ||
.setAudience('did:web:fake.server.bsky.network') | ||
.setProtectedHeader({ | ||
typ: 'at+jwt', // https://www.rfc-editor.org/rfc/rfc9068.html | ||
alg: 'ES256K', | ||
}) | ||
const token = await signer.sign(jwtPrivKey) | ||
const attempt = agent.app.bsky.actor.getProfile( | ||
{ actor: sc.dids.bob }, | ||
{ headers: { authorization: `Bearer ${token}` } }, | ||
) | ||
await expect(attempt).rejects.toThrow('Token has expired') | ||
}) | ||
|
||
it('does not work on bad auds', async () => { | ||
const signer = new jose.SignJWT({ scope: 'com.atproto.access' }) | ||
.setSubject(alice) | ||
.setIssuedAt() | ||
.setExpirationTime('60mins') | ||
.setAudience('did:web:my.personal.pds.com') | ||
.setProtectedHeader({ | ||
typ: 'at+jwt', // https://www.rfc-editor.org/rfc/rfc9068.html | ||
alg: 'ES256K', | ||
}) | ||
const token = await signer.sign(jwtPrivKey) | ||
const attempt = agent.app.bsky.actor.getProfile( | ||
{ actor: sc.dids.bob }, | ||
{ headers: { authorization: `Bearer ${token}` } }, | ||
) | ||
await expect(attempt).rejects.toThrow('Bad token aud') | ||
}) | ||
|
||
it('does not work with bad signatures', async () => { | ||
const fakeKey = await crypto.Secp256k1Keypair.create({ exportable: true }) | ||
const fakeJwtKey = await derivePrivKey(fakeKey) | ||
const signer = new jose.SignJWT({ scope: 'com.atproto.access' }) | ||
.setSubject(alice) | ||
.setIssuedAt() | ||
.setExpirationTime('60mins') | ||
.setAudience('did:web:my.personal.pds.com') | ||
.setProtectedHeader({ | ||
typ: 'at+jwt', // https://www.rfc-editor.org/rfc/rfc9068.html | ||
alg: 'ES256K', | ||
}) | ||
const token = await signer.sign(fakeJwtKey) | ||
const attempt = agent.app.bsky.actor.getProfile( | ||
{ actor: sc.dids.bob }, | ||
{ headers: { authorization: `Bearer ${token}` } }, | ||
) | ||
await expect(attempt).rejects.toThrow('Token could not be verified') | ||
}) | ||
|
||
it('does not work on flexible aud routes', async () => { | ||
const signer = new jose.SignJWT({ scope: 'com.atproto.access' }) | ||
.setSubject(alice) | ||
.setIssuedAt() | ||
.setExpirationTime('60mins') | ||
.setAudience('did:web:fake.server.bsky.network') | ||
.setProtectedHeader({ | ||
typ: 'at+jwt', // https://www.rfc-editor.org/rfc/rfc9068.html | ||
alg: 'ES256K', | ||
}) | ||
const token = await signer.sign(jwtPrivKey) | ||
const feedUri = AtUri.make(alice, 'app.bsky.feed.generator', 'fake-feed') | ||
const attempt = agent.app.bsky.feed.getFeed( | ||
{ feed: feedUri.toString() }, | ||
{ headers: { authorization: `Bearer ${token}` } }, | ||
) | ||
await expect(attempt).rejects.toThrow('Malformed token') | ||
}) | ||
}) |
Oops, something went wrong.