diff options
Diffstat (limited to 'bskylink/tests/index.ts')
-rw-r--r-- | bskylink/tests/index.ts | 241 |
1 files changed, 239 insertions, 2 deletions
diff --git a/bskylink/tests/index.ts b/bskylink/tests/index.ts index c5604c7a1..1b3d06ad1 100644 --- a/bskylink/tests/index.ts +++ b/bskylink/tests/index.ts @@ -1,7 +1,9 @@ import assert from 'node:assert' -import {AddressInfo} from 'node:net' +import {type AddressInfo} from 'node:net' import {after, before, describe, it} from 'node:test' +import {ToolsOzoneSafelinkDefs} from '@atproto/api' + import {Database, envToCfg, LinkService, readEnv} from '../src/index.js' describe('link service', async () => { @@ -15,6 +17,10 @@ describe('link service', async () => { appHostname: 'test.bsky.app', dbPostgresSchema: 'link_test', dbPostgresUrl: process.env.DB_POSTGRES_URL, + safelinkEnabled: true, + ozoneUrl: 'http://localhost:2583', + ozoneAgentHandle: 'mod-authority.test', + ozoneAgentPass: 'hunter2', }) const migrateDb = Database.postgres({ url: cfg.db.url, @@ -26,8 +32,85 @@ describe('link service', async () => { await linkService.start() const {port} = linkService.server?.address() as AddressInfo baseUrl = `http://localhost:${port}` - }) + // Ensure blocklist, whitelist, and safelink rules are set up + const now = new Date().toISOString() + linkService.ctx.cfg.eventCache.smartUpdate({ + $type: 'tools.ozone.safelink.defs#event', + id: 1, + eventType: ToolsOzoneSafelinkDefs.ADDRULE, + url: 'https://en.wikipedia.org/wiki/Fight_Club', + pattern: ToolsOzoneSafelinkDefs.URL, + action: ToolsOzoneSafelinkDefs.WARN, + reason: ToolsOzoneSafelinkDefs.SPAM, + createdBy: 'did:example:admin', + createdAt: now, + comment: 'Do not talk about Fight Club', + }) + linkService.ctx.cfg.eventCache.smartUpdate({ + $type: 'tools.ozone.safelink.defs#event', + id: 2, + eventType: ToolsOzoneSafelinkDefs.ADDRULE, + url: 'https://gist.github.com/MattIPv4/045239bc27b16b2bcf7a3a9a4648c08a', + pattern: ToolsOzoneSafelinkDefs.URL, + action: ToolsOzoneSafelinkDefs.BLOCK, + reason: ToolsOzoneSafelinkDefs.SPAM, + createdBy: 'did:example:admin', + createdAt: now, + comment: 'All Bs', + }) + linkService.ctx.cfg.eventCache.smartUpdate({ + $type: 'tools.ozone.safelink.defs#event', + id: 3, + eventType: ToolsOzoneSafelinkDefs.ADDRULE, + url: 'https://en.wikipedia.org', + pattern: ToolsOzoneSafelinkDefs.DOMAIN, + action: ToolsOzoneSafelinkDefs.WHITELIST, + reason: ToolsOzoneSafelinkDefs.NONE, + createdBy: 'did:example:admin', + createdAt: now, + comment: 'Whitelisting the knowledge base of the internet', + }) + linkService.ctx.cfg.eventCache.smartUpdate({ + $type: 'tools.ozone.safelink.defs#event', + id: 4, + eventType: ToolsOzoneSafelinkDefs.ADDRULE, + url: 'https://www.instagram.com/teamseshbones/?hl=en', + pattern: ToolsOzoneSafelinkDefs.URL, + action: ToolsOzoneSafelinkDefs.BLOCK, + reason: ToolsOzoneSafelinkDefs.SPAM, + createdBy: 'did:example:admin', + createdAt: now, + comment: 'BONES has been erroneously blocked for the sake of this test', + }) + const later = new Date(Date.now() + 1000).toISOString() + linkService.ctx.cfg.eventCache.smartUpdate({ + $type: 'tools.ozone.safelink.defs#event', + id: 5, + eventType: ToolsOzoneSafelinkDefs.REMOVERULE, + url: 'https://www.instagram.com/teamseshbones/?hl=en', + pattern: ToolsOzoneSafelinkDefs.URL, + action: ToolsOzoneSafelinkDefs.REMOVERULE, + reason: ToolsOzoneSafelinkDefs.NONE, + createdBy: 'did:example:admin', + createdAt: later, + comment: + 'BONES has been resurrected to bring good music to the world once again', + }) + linkService.ctx.cfg.eventCache.smartUpdate({ + $type: 'tools.ozone.safelink.defs#event', + id: 6, + eventType: ToolsOzoneSafelinkDefs.ADDRULE, + url: 'https://www.leagueoflegends.com/en-us/', + pattern: ToolsOzoneSafelinkDefs.URL, + action: ToolsOzoneSafelinkDefs.WARN, + reason: ToolsOzoneSafelinkDefs.SPAM, + createdBy: 'did:example:admin', + createdAt: now, + comment: + 'Could be quite the mistake to get into this addicting game, but we will warn instead of block', + }) + }) after(async () => { await linkService?.destroy() }) @@ -76,6 +159,80 @@ describe('link service', async () => { assert.strictEqual(json.message, 'Link not found') }) + it('League of Legends warned', async () => { + const urlToRedirect = 'https://www.leagueoflegends.com/en-us/' + const url = new URL(`${baseUrl}/redirect`) + url.searchParams.set('u', urlToRedirect) + const res = await fetch(url, {redirect: 'manual'}) + assert.strictEqual(res.status, 200) + const html = await res.text() + assert.match( + html, + new RegExp(urlToRedirect.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), + ) + // League of Legends is set to WARN, not BLOCK, so expect a warning (blocked-site div present) + assert.match( + html, + /Warning: Malicious Link/, + 'Expected warning not found in HTML', + ) + }) + + it('Wikipedia whitelisted, url restricted. Redirect safely since wikipedia is whitelisted', async () => { + const urlToRedirect = 'https://en.wikipedia.org/wiki/Fight_Club' + const url = new URL(`${baseUrl}/redirect`) + url.searchParams.set('u', urlToRedirect) + const res = await fetch(url, {redirect: 'manual'}) + assert.strictEqual(res.status, 200) + const html = await res.text() + assert.match(html, /meta http-equiv="refresh"/) + assert.match( + html, + new RegExp(urlToRedirect.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), + ) + // Wikipedia domain is whitelisted, so no blocked-site div should be present + assert.doesNotMatch(html, /"blocked-site"/) + }) + + it('Unsafe redirect with block rule, due to the content of webpage.', async () => { + const urlToRedirect = + 'https://gist.github.com/MattIPv4/045239bc27b16b2bcf7a3a9a4648c08a' + const url = new URL(`${baseUrl}/redirect`) + url.searchParams.set('u', urlToRedirect) + const res = await fetch(url, {redirect: 'manual'}) + assert.strictEqual(res.status, 200) + const html = await res.text() + assert.match( + html, + new RegExp(urlToRedirect.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), + ) + assert.match( + html, + /"blocked-site"/, + 'Expected blocked-site div not found in HTML', + ) + }) + + it('Rule adjustment, safe redirect, 200 response for Instagram Account of teamsesh Bones', async () => { + // Retrieve the latest event after all updates + const result = linkService.ctx.cfg.eventCache.smartGet( + 'https://www.instagram.com/teamseshbones/?hl=en', + ) + assert(result, 'Expected event not found in eventCache') + assert.strictEqual(result.eventType, ToolsOzoneSafelinkDefs.REMOVERULE) + const urlToRedirect = 'https://www.instagram.com/teamseshbones/?hl=en' + const url = new URL(`${baseUrl}/redirect`) + url.searchParams.set('u', urlToRedirect) + const res = await fetch(url, {redirect: 'manual'}) + assert.strictEqual(res.status, 200) + const html = await res.text() + assert.match(html, /meta http-equiv="refresh"/) + assert.match( + html, + new RegExp(urlToRedirect.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), + ) + }) + async function getRedirect(link: string): Promise<[number, string]> { const url = new URL(link) const base = new URL(baseUrl) @@ -121,3 +278,83 @@ describe('link service', async () => { return payload.url } }) + +describe('link service no safelink', async () => { + let linkService: LinkService + let baseUrl: string + before(async () => { + const env = readEnv() + const cfg = envToCfg({ + ...env, + hostnames: ['test.bsky.link'], + appHostname: 'test.bsky.app', + dbPostgresSchema: 'link_test', + dbPostgresUrl: process.env.DB_POSTGRES_URL, + safelinkEnabled: false, + ozoneUrl: 'http://localhost:2583', + ozoneAgentHandle: 'mod-authority.test', + ozoneAgentPass: 'hunter2', + }) + const migrateDb = Database.postgres({ + url: cfg.db.url, + schema: cfg.db.schema, + }) + await migrateDb.migrateToLatestOrThrow() + await migrateDb.close() + linkService = await LinkService.create(cfg) + await linkService.start() + const {port} = linkService.server?.address() as AddressInfo + baseUrl = `http://localhost:${port}` + }) + after(async () => { + await linkService?.destroy() + }) + it('Wikipedia whitelisted, url restricted. Safelink is disabled, so redirect is always safe', async () => { + const urlToRedirect = 'https://en.wikipedia.org/wiki/Fight_Club' + const url = new URL(`${baseUrl}/redirect`) + url.searchParams.set('u', urlToRedirect) + const res = await fetch(url, {redirect: 'manual'}) + assert.strictEqual(res.status, 200) + const html = await res.text() + assert.match(html, /meta http-equiv="refresh"/) + assert.match( + html, + new RegExp(urlToRedirect.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), + ) + // No blocked-site div, always safe + assert.doesNotMatch(html, /"blocked-site"/) + }) + + it('Unsafe redirect with block rule, but safelink is disabled so redirect is always safe', async () => { + const urlToRedirect = + 'https://gist.github.com/MattIPv4/045239bc27b16b2bcf7a3a9a4648c08a' + const url = new URL(`${baseUrl}/redirect`) + url.searchParams.set('u', urlToRedirect) + const res = await fetch(url, {redirect: 'manual'}) + assert.strictEqual(res.status, 200) + const html = await res.text() + assert.match(html, /meta http-equiv="refresh"/) + assert.match( + html, + new RegExp(urlToRedirect.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), + ) + // No blocked-site div, always safe + assert.doesNotMatch(html, /"blocked-site"/) + }) + + it('Rule adjustment, safe redirect, safelink is disabled so always safe', async () => { + const urlToRedirect = 'https://www.instagram.com/teamseshbones/?hl=en' + const url = new URL(`${baseUrl}/redirect`) + url.searchParams.set('u', urlToRedirect) + const res = await fetch(url, {redirect: 'manual'}) + assert.strictEqual(res.status, 200) + const html = await res.text() + assert.match(html, /meta http-equiv="refresh"/) + assert.match( + html, + new RegExp(urlToRedirect.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), + ) + // No blocked-site div, always safe + assert.doesNotMatch(html, /"blocked-site"/) + }) +}) |