Skip to content

Commit

Permalink
Clean up role-based auth (#2255)
Browse files Browse the repository at this point in the history
* tidy bsky auth

* hook up new auth verifier

* update auth throughout ozone

* handle mod signing keys

* add client proxy heads to pds

* hook up rest of routes

* simplify pipethrough & add some SSRF protection

* tests

* fix bad var

* remove basic auth in ozone

* wip

* fix key parsing in pds

* fix up all ozone tests

* fix admin auth test

* rename test

* fix ozone test

* clean up tokens in pds

* fix up pds tests

* fix up ozone tests

* add pipethrough to write routes

* reenable proxied admin test

* add moderator accounts to ozone in dev-env

* update did doc id values

* null creds string -> `none`

* fix fetchLabels auth check

* ✨ Add a couple more proxied requests that we use in ozone ui

* Add runit to the services/bsky Dockerfile (#2254)

add runit to the services/bsky Dockerfile

* Improve tag detection (#2260)

* Allow tags to lead with and contain only numbers

* Break tags on other whitespace characters

* Export regexes from rich text detection

* Add test

* Add test

* Disallow number-only tags

* Avoid combining enclosing screen chars

* Allow full-width number sign

* Clarify tests

* Fix punctuation edge case

* Reorder

* Simplify, add another test

* Another test, comment

* Version packages (#2261)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* 🐛 Increment attempt count after each attempt to push ozone event (#2239)

* Ozone delegates email sending to actor's pds (#2272)

* ozone delegates email sending to user's pds

* lexicon: add content field to mod email event

* test email sending via mod event

* add dev dep for nodemailer in ozone

* fix auth verifier method

* build branch

* build branch

* fix url check

* better error handling for get account infos

* fix labeler service id

* fix iss on auth headers

* fix dev-env ozone did

* fix tests & another jwt issuer

* fix proxy auth

* ozone: fix ip check

* fix aud check on pds mod service auth

* tidy

* Update packages/pds/tests/proxied/admin.test.ts

Co-authored-by: devin ivy <[email protected]>

* fix pipethrough of headers

* fix moderation status tests

* fix auth on ozone routes

* update iss on daemon

---------

Co-authored-by: Foysal Ahamed <[email protected]>
Co-authored-by: Jake Gold <[email protected]>
Co-authored-by: Eric Bailey <[email protected]>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: devin ivy <[email protected]>
  • Loading branch information
7 people authored Mar 7, 2024
1 parent 2267f1e commit 71f9cc9
Show file tree
Hide file tree
Showing 80 changed files with 869 additions and 1,212 deletions.
1 change: 0 additions & 1 deletion .github/workflows/build-and-push-bsky-ghcr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ on:
push:
branches:
- main
- pds-proxy-headers
env:
REGISTRY: ghcr.io
USERNAME: ${{ github.actor }}
Expand Down
1 change: 0 additions & 1 deletion .github/workflows/build-and-push-ozone-aws.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ on:
push:
branches:
- main
- pds-proxy-headers
env:
REGISTRY: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_REGISTRY }}
USERNAME: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_USERNAME }}
Expand Down
1 change: 0 additions & 1 deletion .github/workflows/build-and-push-pds-ghcr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ on:
push:
branches:
- main
- pds-proxy-headers
env:
REGISTRY: ghcr.io
USERNAME: ${{ github.actor }}
Expand Down
4 changes: 2 additions & 2 deletions packages/dev-env/src/bsky.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { AtpAgent } from '@atproto/api'
import { Secp256k1Keypair } from '@atproto/crypto'
import { Client as PlcClient } from '@did-plc/lib'
import { BskyConfig } from './types'
import { ADMIN_PASSWORD, MOD_PASSWORD, TRIAGE_PASSWORD } from './const'
import { ADMIN_PASSWORD } from './const'
import { BackgroundQueue } from '@atproto/bsky/src/data-plane/server/background'

export class TestBsky {
Expand Down Expand Up @@ -64,7 +64,7 @@ export class TestBsky {
modServiceDid: cfg.modServiceDid ?? 'did:example:invalidMod',
labelsFromIssuerDids: ['did:example:labeler'], // this did is also used as the labeler in seeds
...cfg,
adminPasswords: [ADMIN_PASSWORD, MOD_PASSWORD, TRIAGE_PASSWORD],
adminPasswords: [ADMIN_PASSWORD],
})

// Separate migration db in case migration changes some connection state that we need in the tests, e.g. "alter database ... set ..."
Expand Down
2 changes: 0 additions & 2 deletions packages/dev-env/src/const.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
export const ADMIN_PASSWORD = 'admin-pass'
export const MOD_PASSWORD = 'mod-pass'
export const TRIAGE_PASSWORD = 'triage-pass'
export const JWT_SECRET = 'jwt-secret'
2 changes: 2 additions & 0 deletions packages/dev-env/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ export * from './network'
export * from './network-no-appview'
export * from './pds'
export * from './plc'
export * from './ozone'
export * from './feed-gen'
export * from './seed'
export * from './moderator-client'
export * from './types'
export * from './util'
23 changes: 23 additions & 0 deletions packages/dev-env/src/mock/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,29 @@ export async function generateMockSetup(env: TestNetwork) {
)
}

// Create moderator accounts
const triageRes =
await clients.loggedout.api.com.atproto.server.createAccount({
email: '[email protected]',
handle: 'triage.test',
password: 'triage-pass',
})
env.ozone.addAdminDid(triageRes.data.did)
const modRes = await clients.loggedout.api.com.atproto.server.createAccount({
email: '[email protected]',
handle: 'mod.test',
password: 'mod-pass',
})
env.ozone.addAdminDid(modRes.data.did)
const adminRes = await clients.loggedout.api.com.atproto.server.createAccount(
{
email: '[email protected]',
handle: 'admin-mod.test',
password: 'admin-mod-pass',
},
)
env.ozone.addAdminDid(adminRes.data.did)

// Report one user
const reporter = picka(users)
await reporter.agent.api.com.atproto.moderation.createReport({
Expand Down
138 changes: 138 additions & 0 deletions packages/dev-env/src/moderator-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import AtpAgent from '@atproto/api'
import { InputSchema as TakeActionInput } from '@atproto/api/src/client/types/com/atproto/admin/emitModerationEvent'
import { QueryParams as QueryStatusesParams } from '@atproto/api/src/client/types/com/atproto/admin/queryModerationStatuses'
import { QueryParams as QueryEventsParams } from '@atproto/api/src/client/types/com/atproto/admin/queryModerationEvents'
import { TestOzone } from './ozone'

type ModLevel = 'admin' | 'moderator' | 'triage'

export class ModeratorClient {
agent: AtpAgent
constructor(public ozone: TestOzone) {
this.agent = ozone.getClient()
}

async getEvent(id: number, role?: ModLevel) {
const result = await this.agent.api.com.atproto.admin.getModerationEvent(
{ id },
{
headers: await this.ozone.modHeaders(role),
},
)
return result.data
}

async queryModerationStatuses(input: QueryStatusesParams, role?: ModLevel) {
const result =
await this.agent.api.com.atproto.admin.queryModerationStatuses(input, {
headers: await this.ozone.modHeaders(role),
})
return result.data
}

async queryModerationEvents(input: QueryEventsParams, role?: ModLevel) {
const result = await this.agent.api.com.atproto.admin.queryModerationEvents(
input,
{
headers: await this.ozone.modHeaders(role),
},
)
return result.data
}

async emitModerationEvent(
opts: {
event: TakeActionInput['event']
subject: TakeActionInput['subject']
subjectBlobCids?: TakeActionInput['subjectBlobCids']
reason?: string
createdBy?: string
meta?: TakeActionInput['meta']
},
role?: ModLevel,
) {
const {
event,
subject,
subjectBlobCids,
reason = 'X',
createdBy = 'did:example:admin',
} = opts
const result = await this.agent.api.com.atproto.admin.emitModerationEvent(
{ event, subject, subjectBlobCids, createdBy, reason },
{
encoding: 'application/json',
headers: await this.ozone.modHeaders(role),
},
)
return result.data
}

async reverseModerationAction(
opts: {
id: number
subject: TakeActionInput['subject']
reason?: string
createdBy?: string
},
role?: ModLevel,
) {
const { subject, reason = 'X', createdBy = 'did:example:admin' } = opts
const result = await this.agent.api.com.atproto.admin.emitModerationEvent(
{
subject,
event: {
$type: 'com.atproto.admin.defs#modEventReverseTakedown',
comment: reason,
},
createdBy,
},
{
encoding: 'application/json',
headers: await this.ozone.modHeaders(role),
},
)
return result.data
}

async performTakedown(
opts: {
subject: TakeActionInput['subject']
subjectBlobCids?: TakeActionInput['subjectBlobCids']
durationInHours?: number
reason?: string
},
role?: ModLevel,
) {
const { durationInHours, ...rest } = opts
return this.emitModerationEvent(
{
event: {
$type: 'com.atproto.admin.defs#modEventTakedown',
durationInHours,
},
...rest,
},
role,
)
}

async performReverseTakedown(
opts: {
subject: TakeActionInput['subject']
subjectBlobCids?: TakeActionInput['subjectBlobCids']
reason?: string
},
role?: ModLevel,
) {
return this.emitModerationEvent(
{
event: {
$type: 'com.atproto.admin.defs#modEventReverseTakedown',
},
...opts,
},
role,
)
}
}
1 change: 1 addition & 0 deletions packages/dev-env/src/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export class TestNetwork extends TestNetworkNoAppView {
await this.pds.processAll()
await this.processFullSubscription(timeout)
await this.bsky.sub.background.processAll()
await this.ozone.processAll()
}

async serviceHeaders(did: string, aud?: string) {
Expand Down
86 changes: 62 additions & 24 deletions packages/dev-env/src/ozone.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
import getPort from 'get-port'
import * as ui8 from 'uint8arrays'
import * as plc from '@did-plc/lib'
import * as ozone from '@atproto/ozone'
import { AtpAgent } from '@atproto/api'
import { createServiceJwt } from '@atproto/xrpc-server'
import { Keypair, Secp256k1Keypair } from '@atproto/crypto'
import * as plc from '@did-plc/lib'
import { OzoneConfig } from './types'
import { ADMIN_PASSWORD, MOD_PASSWORD, TRIAGE_PASSWORD } from './const'
import { DidAndKey, OzoneConfig } from './types'
import { ADMIN_PASSWORD } from './const'
import { createDidAndKey } from './util'
import { ModeratorClient } from './moderator-client'

export class TestOzone {
constructor(
public url: string,
public port: number,
public server: ozone.OzoneService,
public daemon: ozone.OzoneDaemon,
public adminAccnt: DidAndKey,
public moderatorAccnt: DidAndKey,
public triageAccnt: DidAndKey,
) {}

static async create(config: OzoneConfig): Promise<TestOzone> {
Expand All @@ -24,6 +30,24 @@ export class TestOzone {
serverDid = await createOzoneDid(config.plcUrl, serviceKeypair)
}

const admin = await createDidAndKey({
plcUrl: config.plcUrl,
handle: 'admin.ozone',
pds: 'https://pds.invalid',
})

const moderator = await createDidAndKey({
plcUrl: config.plcUrl,
handle: 'moderator.ozone',
pds: 'https://pds.invalid',
})

const triage = await createDidAndKey({
plcUrl: config.plcUrl,
handle: 'triage.ozone',
pds: 'https://pds.invalid',
})

const port = config.port || (await getPort())
const url = `http://localhost:${port}`

Expand All @@ -37,11 +61,13 @@ export class TestOzone {
signingKeyHex,
...config,
adminPassword: ADMIN_PASSWORD,
moderatorPassword: MOD_PASSWORD,
triagePassword: TRIAGE_PASSWORD,
adminDids: [],
moderatorDids: [],
triageDids: [],
adminDids: [...(config.adminDids ?? []), admin.did],
moderatorDids: [
...(config.moderatorDids ?? []),
config.appviewDid,
moderator.did,
],
triageDids: [...(config.triageDids ?? []), triage.did],
}

// Separate migration db in case migration changes some connection state that we need in the tests, e.g. "alter database ... set ..."
Expand Down Expand Up @@ -70,7 +96,7 @@ export class TestOzone {
// don't do event reversal in dev-env
await daemon.ctx.eventReverser.destroy()

return new TestOzone(url, port, server, daemon)
return new TestOzone(url, port, server, daemon, admin, moderator, triage)
}

get ctx(): ozone.AppContext {
Expand All @@ -81,23 +107,35 @@ export class TestOzone {
return new AtpAgent({ service: this.url })
}

adminAuth(role: 'admin' | 'moderator' | 'triage' = 'admin'): string {
const password =
role === 'triage'
? TRIAGE_PASSWORD
: role === 'moderator'
? MOD_PASSWORD
: ADMIN_PASSWORD
return (
'Basic ' +
ui8.toString(ui8.fromString(`admin:${password}`, 'utf8'), 'base64pad')
)
getModClient() {
return new ModeratorClient(this)
}

adminAuthHeaders(role?: 'admin' | 'moderator' | 'triage') {
return {
authorization: this.adminAuth(role),
}
addAdminDid(did: string) {
this.ctx.cfg.access.admins.push(did)
}

addModeratorDid(did: string) {
this.ctx.cfg.access.moderators.push(did)
}

addTriageDid(did: string) {
this.ctx.cfg.access.triage.push(did)
}

async modHeaders(role: 'admin' | 'moderator' | 'triage' = 'moderator') {
const account =
role === 'admin'
? this.adminAccnt
: role === 'moderator'
? this.moderatorAccnt
: this.triageAccnt
const jwt = await createServiceJwt({
iss: account.did,
aud: this.ctx.cfg.service.did,
keypair: account.key,
})
return { authorization: `Bearer ${jwt}` }
}

async processAll() {
Expand Down
Loading

0 comments on commit 71f9cc9

Please sign in to comment.