b934b396b1
* feat(api): support creation of oauth based AtpAgents * oauth: misc fixes for confidential clients * fix(xprc): remove ReadableStream.from polyfill * OAuth docs tweaks (#2679) * OAuth: clarification about client_name being shown * OAuth: re-write handle resolution privacy concern * avoid relying on ReadableStream.from in xrpc-server tests * feat(oauth-types): expose "ALLOW_UNSECURE_ORIGINS" constant * feat(handle-resolver): expose "AtprotoIdentityDidMethods" type * fix(oauth-client): ensure that the oauth metadata document contains client_id_metadata_document_supported * fix(oauth-types): prevent unknown query string in loopback client id * fix(identity-resolver): check that handle is in did doc's "alsoKnownAs" * feat(oauth-client:oauth-resolver): allow logging in using either the PDS URL or Entryway URL * fix(oauth-client): return better error in case of invalid "oauth-protected-resource" status code * refactor(did): group atproto specific checks in own * feat(api): relax typing of "appLabelers" and "labelers" AtpClient properties * allow any did as labeller (for tests mainly) * fix(api): allow to override "atproto-proxy" on a per-request basis * remove release candidate versions from changelog * update changeset for api and xrpc packages * Add missing changeset * revert RC versions * Proper wording in OAUTH.md api example * remove "pre" changeset file * xrpc: restore original behavior of setHEader and unsetHeader * docs: add comment for XrpcClient 's constructor arg * feat(api): expose "schemas" publicly * feat(api): allow customizing the whatwg fetch function of the AtpAgent * docs(api): improve migration docs * docs: change reference to BskyAgent to AtpAgent * docs: mention the breaking change regarding setSessionPersistHandler * fix(api): better split AtpClient concerns * fix(xrpc): remove unused import * refactor(api): simplify class hierarchu by removeing AtpClient * fix(api): mock proper method for facets detection * restore ability to restore session asynchronously * feat(api): allow instantiating Agent with same argument as super class * docs(api): properly extend Agent class * style(xrpc): var name * docs(api): remove "async" to header getter --------- Co-authored-by: Devin Ivy <devinivy@gmail.com> Co-authored-by: bnewbold <bnewbold@robocracy.org> Co-authored-by: Hailey <me@haileyok.com>
331 lines
9.6 KiB
TypeScript
331 lines
9.6 KiB
TypeScript
import { AtpAgent } from '@atproto/api'
|
|
import { TestNetwork, SeedClient } from '@atproto/dev-env'
|
|
import basicSeed from '../seeds/basic'
|
|
import { forSnapshot } from '../_util'
|
|
|
|
describe('proxies admin requests', () => {
|
|
let network: TestNetwork
|
|
let agent: AtpAgent
|
|
let sc: SeedClient
|
|
|
|
let moderator: string
|
|
|
|
beforeAll(async () => {
|
|
network = await TestNetwork.create({
|
|
dbPostgresSchema: 'proxy_admin',
|
|
pds: {
|
|
inviteRequired: true,
|
|
},
|
|
})
|
|
agent = network.pds.getClient()
|
|
sc = network.getSeedClient()
|
|
const { data: invite } =
|
|
await agent.api.com.atproto.server.createInviteCode(
|
|
{ useCount: 10 },
|
|
{
|
|
encoding: 'application/json',
|
|
headers: network.pds.adminAuthHeaders(),
|
|
},
|
|
)
|
|
await basicSeed(sc, {
|
|
inviteCode: invite.code,
|
|
addModLabels: network.bsky,
|
|
})
|
|
const modAccount = await sc.createAccount('moderator', {
|
|
handle: 'testmod.test',
|
|
email: 'testmod@test.com',
|
|
password: 'testmod-pass',
|
|
inviteCode: invite.code,
|
|
})
|
|
moderator = modAccount.did
|
|
await network.ozone.addModeratorDid(moderator)
|
|
|
|
await network.processAll()
|
|
})
|
|
|
|
beforeAll(async () => {
|
|
const { data: invite } =
|
|
await agent.api.com.atproto.server.createInviteCode(
|
|
{ useCount: 1, forAccount: sc.dids.alice },
|
|
{
|
|
headers: network.pds.adminAuthHeaders(),
|
|
encoding: 'application/json',
|
|
},
|
|
)
|
|
await agent.api.com.atproto.admin.disableAccountInvites(
|
|
{ account: sc.dids.bob },
|
|
{ headers: network.pds.adminAuthHeaders(), encoding: 'application/json' },
|
|
)
|
|
await sc.createAccount('eve', {
|
|
handle: 'eve.test',
|
|
email: 'eve@test.com',
|
|
password: 'password',
|
|
inviteCode: invite.code,
|
|
})
|
|
await network.processAll()
|
|
})
|
|
|
|
afterAll(async () => {
|
|
await network.close()
|
|
})
|
|
|
|
it('creates reports of a repo.', async () => {
|
|
const { data: reportA } =
|
|
await agent.api.com.atproto.moderation.createReport(
|
|
{
|
|
reasonType: 'com.atproto.moderation.defs#reasonSpam',
|
|
subject: {
|
|
$type: 'com.atproto.admin.defs#repoRef',
|
|
did: sc.dids.bob,
|
|
},
|
|
},
|
|
{
|
|
headers: sc.getHeaders(sc.dids.alice),
|
|
encoding: 'application/json',
|
|
},
|
|
)
|
|
const { data: reportB } =
|
|
await agent.api.com.atproto.moderation.createReport(
|
|
{
|
|
reasonType: 'com.atproto.moderation.defs#reasonOther',
|
|
reason: 'impersonation',
|
|
subject: {
|
|
$type: 'com.atproto.admin.defs#repoRef',
|
|
did: sc.dids.bob,
|
|
},
|
|
},
|
|
{
|
|
headers: sc.getHeaders(sc.dids.carol),
|
|
encoding: 'application/json',
|
|
},
|
|
)
|
|
expect(forSnapshot([reportA, reportB])).toMatchSnapshot()
|
|
})
|
|
|
|
it('takes actions and resolves reports', async () => {
|
|
const post = sc.posts[sc.dids.bob][1]
|
|
const { data: actionA } = await agent.api.tools.ozone.moderation.emitEvent(
|
|
{
|
|
event: { $type: 'tools.ozone.moderation.defs#modEventAcknowledge' },
|
|
subject: {
|
|
$type: 'com.atproto.repo.strongRef',
|
|
uri: post.ref.uriStr,
|
|
cid: post.ref.cidStr,
|
|
},
|
|
createdBy: 'did:example:admin',
|
|
reason: 'Y',
|
|
},
|
|
{
|
|
headers: sc.getHeaders(moderator),
|
|
encoding: 'application/json',
|
|
},
|
|
)
|
|
expect(forSnapshot(actionA)).toMatchSnapshot()
|
|
const { data: actionB } = await agent.api.tools.ozone.moderation.emitEvent(
|
|
{
|
|
event: { $type: 'tools.ozone.moderation.defs#modEventAcknowledge' },
|
|
subject: {
|
|
$type: 'com.atproto.admin.defs#repoRef',
|
|
did: sc.dids.bob,
|
|
},
|
|
createdBy: 'did:example:admin',
|
|
reason: 'Y',
|
|
},
|
|
{
|
|
headers: sc.getHeaders(moderator),
|
|
encoding: 'application/json',
|
|
},
|
|
)
|
|
expect(forSnapshot(actionB)).toMatchSnapshot()
|
|
})
|
|
|
|
it('fetches moderation events.', async () => {
|
|
const { data: result } = await agent.api.tools.ozone.moderation.queryEvents(
|
|
{
|
|
subject: sc.posts[sc.dids.bob][1].ref.uriStr,
|
|
},
|
|
{ headers: sc.getHeaders(moderator) },
|
|
)
|
|
expect(forSnapshot(result.events)).toMatchSnapshot()
|
|
})
|
|
|
|
it('fetches repo details.', async () => {
|
|
const { data: result } = await agent.api.tools.ozone.moderation.getRepo(
|
|
{ did: sc.dids.eve },
|
|
{ headers: sc.getHeaders(moderator) },
|
|
)
|
|
expect(forSnapshot(result)).toMatchSnapshot()
|
|
})
|
|
|
|
it('fetches record details.', async () => {
|
|
const post = sc.posts[sc.dids.bob][1]
|
|
const { data: result } = await agent.api.tools.ozone.moderation.getRecord(
|
|
{ uri: post.ref.uriStr },
|
|
{ headers: sc.getHeaders(moderator) },
|
|
)
|
|
expect(forSnapshot(result)).toMatchSnapshot()
|
|
})
|
|
|
|
it('fetches event details.', async () => {
|
|
const { data: result } = await agent.api.tools.ozone.moderation.getEvent(
|
|
{ id: 2 },
|
|
{ headers: sc.getHeaders(moderator) },
|
|
)
|
|
expect(forSnapshot(result)).toMatchSnapshot()
|
|
})
|
|
|
|
it('fetches a list of events.', async () => {
|
|
const { data: result } = await agent.api.tools.ozone.moderation.queryEvents(
|
|
{ subject: sc.dids.bob },
|
|
{ headers: sc.getHeaders(moderator) },
|
|
)
|
|
expect(forSnapshot(result.events)).toMatchSnapshot()
|
|
})
|
|
|
|
it('searches repos.', async () => {
|
|
const { data: result } = await agent.api.tools.ozone.moderation.searchRepos(
|
|
{ term: 'alice' },
|
|
{ headers: sc.getHeaders(moderator) },
|
|
)
|
|
expect(forSnapshot(result.repos)).toMatchSnapshot()
|
|
})
|
|
|
|
it('passes through errors.', async () => {
|
|
const tryGetRepo = agent.api.tools.ozone.moderation.getRepo(
|
|
{ did: 'did:does:not:exist' },
|
|
{ headers: sc.getHeaders(moderator) },
|
|
)
|
|
await expect(tryGetRepo).rejects.toThrow('Repo not found')
|
|
const tryGetRecord = agent.api.tools.ozone.moderation.getRecord(
|
|
{ uri: 'at://did:does:not:exist/bad.collection.name/badrkey' },
|
|
{ headers: sc.getHeaders(moderator) },
|
|
)
|
|
await expect(tryGetRecord).rejects.toThrow('Record not found')
|
|
})
|
|
|
|
it('takesdown and labels repos, and reverts.', async () => {
|
|
// takedown repo
|
|
await agent.api.tools.ozone.moderation.emitEvent(
|
|
{
|
|
event: { $type: 'tools.ozone.moderation.defs#modEventTakedown' },
|
|
subject: {
|
|
$type: 'com.atproto.admin.defs#repoRef',
|
|
did: sc.dids.alice,
|
|
},
|
|
createdBy: 'did:example:admin',
|
|
reason: 'Y',
|
|
createLabelVals: ['dogs'],
|
|
negateLabelVals: ['cats'],
|
|
},
|
|
{
|
|
headers: sc.getHeaders(moderator),
|
|
encoding: 'application/json',
|
|
},
|
|
)
|
|
await network.processAll()
|
|
// check profile and labels
|
|
const tryGetProfileAppview = agent.api.app.bsky.actor.getProfile(
|
|
{ actor: sc.dids.alice },
|
|
{
|
|
headers: { ...sc.getHeaders(sc.dids.carol) },
|
|
},
|
|
)
|
|
await expect(tryGetProfileAppview).rejects.toThrow(
|
|
'Account has been suspended',
|
|
)
|
|
// reverse action
|
|
await agent.api.tools.ozone.moderation.emitEvent(
|
|
{
|
|
subject: {
|
|
$type: 'com.atproto.admin.defs#repoRef',
|
|
did: sc.dids.alice,
|
|
},
|
|
event: {
|
|
$type: 'tools.ozone.moderation.defs#modEventReverseTakedown',
|
|
},
|
|
createdBy: 'did:example:admin',
|
|
reason: 'X',
|
|
},
|
|
{
|
|
headers: sc.getHeaders(moderator),
|
|
encoding: 'application/json',
|
|
},
|
|
)
|
|
await network.processAll()
|
|
// check profile and labels
|
|
const { data: profileAppview } = await agent.api.app.bsky.actor.getProfile(
|
|
{ actor: sc.dids.alice },
|
|
{
|
|
headers: { ...sc.getHeaders(sc.dids.carol) },
|
|
},
|
|
)
|
|
expect(profileAppview).toEqual(
|
|
expect.objectContaining({ did: sc.dids.alice, handle: 'alice.test' }),
|
|
)
|
|
})
|
|
|
|
it('takesdown and labels records, and reverts.', async () => {
|
|
const post = sc.posts[sc.dids.alice][0]
|
|
// takedown post
|
|
await agent.api.tools.ozone.moderation.emitEvent(
|
|
{
|
|
event: { $type: 'tools.ozone.moderation.defs#modEventTakedown' },
|
|
subject: {
|
|
$type: 'com.atproto.repo.strongRef',
|
|
uri: post.ref.uriStr,
|
|
cid: post.ref.cidStr,
|
|
},
|
|
createdBy: 'did:example:admin',
|
|
reason: 'Y',
|
|
createLabelVals: ['dogs'],
|
|
negateLabelVals: ['cats'],
|
|
},
|
|
{
|
|
headers: sc.getHeaders(moderator),
|
|
encoding: 'application/json',
|
|
},
|
|
)
|
|
await network.processAll()
|
|
|
|
// check takedown label has been created
|
|
const label = await network.ozone.ctx.db.db
|
|
.selectFrom('label')
|
|
.selectAll()
|
|
.where('val', '=', '!takedown')
|
|
.where('uri', '=', post.ref.uriStr)
|
|
.where('cid', '=', post.ref.cidStr)
|
|
.executeTakeFirst()
|
|
expect(label).toBeDefined()
|
|
expect(label?.neg).toBe(false)
|
|
|
|
// reverse action
|
|
await agent.api.tools.ozone.moderation.emitEvent(
|
|
{
|
|
subject: {
|
|
$type: 'com.atproto.repo.strongRef',
|
|
uri: post.ref.uriStr,
|
|
cid: post.ref.cidStr,
|
|
},
|
|
event: { $type: 'tools.ozone.moderation.defs#modEventReverseTakedown' },
|
|
createdBy: 'did:example:admin',
|
|
reason: 'X',
|
|
},
|
|
{
|
|
headers: sc.getHeaders(moderator),
|
|
encoding: 'application/json',
|
|
},
|
|
)
|
|
await network.processAll()
|
|
|
|
// check takedown label has been negated
|
|
const labelNeg = await network.ozone.ctx.db.db
|
|
.selectFrom('label')
|
|
.selectAll()
|
|
.where('val', '=', '!takedown')
|
|
.where('uri', '=', post.ref.uriStr)
|
|
.where('cid', '=', post.ref.cidStr)
|
|
.executeTakeFirst()
|
|
expect(labelNeg?.neg).toBe(true)
|
|
})
|
|
})
|