Bluesky app fork with some witchin' additions 馃挮
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 380 lines 14 kB view raw
1import assert from 'node:assert' 2import {type AddressInfo} from 'node:net' 3import {after, before, describe, it} from 'node:test' 4 5import {Database, envToCfg, LinkService, readEnv} from '../src/index.js' 6 7describe.skip('link service', async () => { 8 let linkService: LinkService 9 let baseUrl: string 10 before(async () => { 11 const env = readEnv() 12 const cfg = envToCfg({ 13 ...env, 14 hostnames: ['test.bsky.link'], 15 appHostname: 'test.bsky.app', 16 dbPostgresSchema: 'link_test', 17 dbPostgresUrl: process.env.DB_POSTGRES_URL, 18 safelinkEnabled: true, 19 safelinkPdsUrl: 'http://localhost:2583', 20 safelinkAgentIdentifier: 'mod-authority.test', 21 safelinkAgentPass: 'hunter2', 22 }) 23 const migrateDb = Database.postgres({ 24 url: cfg.db.url, 25 schema: cfg.db.schema, 26 }) 27 await migrateDb.migrateToLatestOrThrow() 28 await migrateDb.close() 29 linkService = await LinkService.create(cfg) 30 await linkService.start() 31 const {port} = linkService.server?.address() as AddressInfo 32 baseUrl = `http://localhost:${port}` 33 34 /* 35 // Ensure blocklist, whitelist, and safelink rules are set up 36 const now = new Date().toISOString() 37 linkService.ctx.cfg.eventCache.smartUpdate({ 38 $type: 'tools.ozone.safelink.defs#event', 39 id: 1, 40 eventType: ToolsOzoneSafelinkDefs.ADDRULE, 41 url: 'https://en.wikipedia.org/wiki/Fight_Club', 42 pattern: ToolsOzoneSafelinkDefs.URL, 43 action: ToolsOzoneSafelinkDefs.WARN, 44 reason: ToolsOzoneSafelinkDefs.SPAM, 45 createdBy: 'did:example:admin', 46 createdAt: now, 47 comment: 'Do not talk about Fight Club', 48 }) 49 linkService.ctx.cfg.eventCache.smartUpdate({ 50 $type: 'tools.ozone.safelink.defs#event', 51 id: 2, 52 eventType: ToolsOzoneSafelinkDefs.ADDRULE, 53 url: 'https://gist.github.com/MattIPv4/045239bc27b16b2bcf7a3a9a4648c08a', 54 pattern: ToolsOzoneSafelinkDefs.URL, 55 action: ToolsOzoneSafelinkDefs.BLOCK, 56 reason: ToolsOzoneSafelinkDefs.SPAM, 57 createdBy: 'did:example:admin', 58 createdAt: now, 59 comment: 'All Bs', 60 }) 61 linkService.ctx.cfg.eventCache.smartUpdate({ 62 $type: 'tools.ozone.safelink.defs#event', 63 id: 3, 64 eventType: ToolsOzoneSafelinkDefs.ADDRULE, 65 url: 'https://en.wikipedia.org', 66 pattern: ToolsOzoneSafelinkDefs.DOMAIN, 67 action: ToolsOzoneSafelinkDefs.WHITELIST, 68 reason: ToolsOzoneSafelinkDefs.NONE, 69 createdBy: 'did:example:admin', 70 createdAt: now, 71 comment: 'Whitelisting the knowledge base of the internet', 72 }) 73 linkService.ctx.cfg.eventCache.smartUpdate({ 74 $type: 'tools.ozone.safelink.defs#event', 75 id: 4, 76 eventType: ToolsOzoneSafelinkDefs.ADDRULE, 77 url: 'https://www.instagram.com/teamseshbones/?hl=en', 78 pattern: ToolsOzoneSafelinkDefs.URL, 79 action: ToolsOzoneSafelinkDefs.BLOCK, 80 reason: ToolsOzoneSafelinkDefs.SPAM, 81 createdBy: 'did:example:admin', 82 createdAt: now, 83 comment: 'BONES has been erroneously blocked for the sake of this test', 84 }) 85 const later = new Date(Date.now() + 1000).toISOString() 86 linkService.ctx.cfg.eventCache.smartUpdate({ 87 $type: 'tools.ozone.safelink.defs#event', 88 id: 5, 89 eventType: ToolsOzoneSafelinkDefs.REMOVERULE, 90 url: 'https://www.instagram.com/teamseshbones/?hl=en', 91 pattern: ToolsOzoneSafelinkDefs.URL, 92 action: ToolsOzoneSafelinkDefs.REMOVERULE, 93 reason: ToolsOzoneSafelinkDefs.NONE, 94 createdBy: 'did:example:admin', 95 createdAt: later, 96 comment: 97 'BONES has been resurrected to bring good music to the world once again', 98 }) 99 linkService.ctx.cfg.eventCache.smartUpdate({ 100 $type: 'tools.ozone.safelink.defs#event', 101 id: 6, 102 eventType: ToolsOzoneSafelinkDefs.ADDRULE, 103 url: 'https://www.leagueoflegends.com/en-us/', 104 pattern: ToolsOzoneSafelinkDefs.URL, 105 action: ToolsOzoneSafelinkDefs.WARN, 106 reason: ToolsOzoneSafelinkDefs.SPAM, 107 createdBy: 'did:example:admin', 108 createdAt: now, 109 comment: 110 'Could be quite the mistake to get into this addicting game, but we will warn instead of block', 111 }) 112 */ 113 }) 114 after(async () => { 115 await linkService?.destroy() 116 }) 117 118 it('creates a starter pack link', async () => { 119 const link = await getLink('/start/did:example:alice/xxx') 120 const url = new URL(link) 121 assert.strictEqual(url.origin, 'https://test.bsky.link') 122 assert.match(url.pathname, /^\/[a-z0-9]+$/i) 123 }) 124 125 it('normalizes input paths and provides same link each time.', async () => { 126 const link1 = await getLink('/start/did%3Aexample%3Abob/yyy') 127 const link2 = await getLink('/start/did:example:bob/yyy/') 128 assert.strictEqual(link1, link2) 129 }) 130 131 it('serves permanent redirect, preserving query params.', async () => { 132 const link = await getLink('/start/did:example:carol/zzz/') 133 const [status, location] = await getRedirect(`${link}?a=b`) 134 assert.strictEqual(status, 301) 135 const locationUrl = new URL(location) 136 assert.strictEqual( 137 locationUrl.pathname + locationUrl.search, 138 '/start/did:example:carol/zzz?a=b', 139 ) 140 }) 141 142 it('returns json object with url when requested', async () => { 143 const link = await getLink('/start/did:example:carol/zzz/') 144 const [status, json] = await getJsonRedirect(link) 145 assert.strictEqual(status, 200) 146 assert(json.url) 147 const url = new URL(json.url) 148 assert.strictEqual(url.pathname, '/start/did:example:carol/zzz') 149 }) 150 151 it('returns 404 for unknown link when requesting json', async () => { 152 const [status, json] = await getJsonRedirect( 153 'https://test.bsky.link/unknown', 154 ) 155 assert(json.error) 156 assert(json.message) 157 assert.strictEqual(status, 404) 158 assert.strictEqual(json.error, 'NotFound') 159 assert.strictEqual(json.message, 'Link not found') 160 }) 161 162 it('League of Legends warned', async () => { 163 const urlToRedirect = 'https://www.leagueoflegends.com/en-us/' 164 const url = new URL(`${baseUrl}/redirect`) 165 url.searchParams.set('u', urlToRedirect) 166 const res = await fetch(url, {redirect: 'manual'}) 167 assert.strictEqual(res.status, 200) 168 const html = await res.text() 169 assert.match( 170 html, 171 new RegExp(urlToRedirect.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), 172 ) 173 // League of Legends is set to WARN, not BLOCK, so expect a warning (blocked-site div present) 174 assert.match( 175 html, 176 /Warning: Malicious Link/, 177 'Expected warning not found in HTML', 178 ) 179 }) 180 181 it('Wikipedia whitelisted, url restricted. Redirect safely since wikipedia is whitelisted', async () => { 182 const urlToRedirect = 'https://en.wikipedia.org/wiki/Fight_Club' 183 const url = new URL(`${baseUrl}/redirect`) 184 url.searchParams.set('u', urlToRedirect) 185 const res = await fetch(url, {redirect: 'manual'}) 186 assert.strictEqual(res.status, 200) 187 const html = await res.text() 188 assert.match(html, /meta http-equiv="refresh"/) 189 assert.match( 190 html, 191 new RegExp(urlToRedirect.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), 192 ) 193 // Wikipedia domain is whitelisted, so no blocked-site div should be present 194 assert.doesNotMatch(html, /"blocked-site"/) 195 }) 196 197 it('Unsafe redirect with block rule, due to the content of webpage.', async () => { 198 const urlToRedirect = 199 'https://gist.github.com/MattIPv4/045239bc27b16b2bcf7a3a9a4648c08a' 200 const url = new URL(`${baseUrl}/redirect`) 201 url.searchParams.set('u', urlToRedirect) 202 const res = await fetch(url, {redirect: 'manual'}) 203 assert.strictEqual(res.status, 200) 204 const html = await res.text() 205 assert.match( 206 html, 207 new RegExp(urlToRedirect.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), 208 ) 209 assert.match( 210 html, 211 /"blocked-site"/, 212 'Expected blocked-site div not found in HTML', 213 ) 214 }) 215 216 /* 217 it('Rule adjustment, safe redirect, 200 response for Instagram Account of teamsesh Bones', async () => { 218 // Retrieve the latest event after all updates 219 const result = linkService.ctx.cfg.eventCache.smartGet( 220 'https://www.instagram.com/teamseshbones/?hl=en', 221 ) 222 assert(result, 'Expected event not found in eventCache') 223 assert.strictEqual(result.eventType, ToolsOzoneSafelinkDefs.REMOVERULE) 224 const urlToRedirect = 'https://www.instagram.com/teamseshbones/?hl=en' 225 const url = new URL(`${baseUrl}/redirect`) 226 url.searchParams.set('u', urlToRedirect) 227 const res = await fetch(url, {redirect: 'manual'}) 228 assert.strictEqual(res.status, 200) 229 const html = await res.text() 230 assert.match(html, /meta http-equiv="refresh"/) 231 assert.match( 232 html, 233 new RegExp(urlToRedirect.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), 234 ) 235 }) 236 */ 237 238 async function getRedirect(link: string): Promise<[number, string]> { 239 const url = new URL(link) 240 const base = new URL(baseUrl) 241 url.protocol = base.protocol 242 url.host = base.host 243 const res = await fetch(url, {redirect: 'manual'}) 244 await res.arrayBuffer() // drain 245 assert( 246 res.status === 301 || res.status === 303, 247 'response was not a redirect', 248 ) 249 return [res.status, res.headers.get('location') ?? ''] 250 } 251 252 async function getJsonRedirect( 253 link: string, 254 ): Promise<[number, {url?: string; error?: string; message?: string}]> { 255 const url = new URL(link) 256 const base = new URL(baseUrl) 257 url.protocol = base.protocol 258 url.host = base.host 259 const res = await fetch(url, { 260 redirect: 'manual', 261 headers: {accept: 'application/json,text/html'}, 262 }) 263 assert( 264 res.headers.get('content-type')?.startsWith('application/json'), 265 'content type was not json', 266 ) 267 const json = await res.json() 268 return [res.status, json] 269 } 270 271 async function getLink(path: string): Promise<string> { 272 const res = await fetch(new URL('/link', baseUrl), { 273 method: 'post', 274 headers: {'content-type': 'application/json'}, 275 body: JSON.stringify({path}), 276 }) 277 assert.strictEqual(res.status, 200) 278 const payload = await res.json() 279 assert(typeof payload.url === 'string') 280 return payload.url 281 } 282}) 283 284describe('link service no safelink', async () => { 285 let linkService: LinkService 286 let baseUrl: string 287 before(async () => { 288 const env = readEnv() 289 const cfg = envToCfg({ 290 ...env, 291 hostnames: ['test.bsky.link'], 292 appHostname: 'test.bsky.app', 293 dbPostgresSchema: 'link_test', 294 dbPostgresUrl: process.env.DB_POSTGRES_URL, 295 safelinkEnabled: false, 296 safelinkPdsUrl: 'http://localhost:2583', 297 safelinkAgentIdentifier: 'mod-authority.test', 298 safelinkAgentPass: 'hunter2', 299 metricsApiHost: 'http://localhost:2584', 300 }) 301 const migrateDb = Database.postgres({ 302 url: cfg.db.url, 303 schema: cfg.db.schema, 304 }) 305 await migrateDb.migrateToLatestOrThrow() 306 await migrateDb.close() 307 linkService = await LinkService.create(cfg) 308 await linkService.start() 309 const {port} = linkService.server?.address() as AddressInfo 310 baseUrl = `http://localhost:${port}` 311 }) 312 after(async () => { 313 await linkService?.destroy() 314 }) 315 it('Wikipedia whitelisted, url restricted. Safelink is disabled, so redirect is always safe', async () => { 316 const urlToRedirect = 'https://en.wikipedia.org/wiki/Fight_Club' 317 const url = new URL(`${baseUrl}/redirect`) 318 url.searchParams.set('u', urlToRedirect) 319 const res = await fetch(url, {redirect: 'manual'}) 320 assert.strictEqual(res.status, 200) 321 const html = await res.text() 322 assert.match(html, /meta http-equiv="refresh"/) 323 assert.match( 324 html, 325 new RegExp(urlToRedirect.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), 326 ) 327 // No blocked-site div, always safe 328 assert.doesNotMatch(html, /"blocked-site"/) 329 }) 330 331 it('Unsafe redirect with block rule, but safelink is disabled so redirect is always safe', async () => { 332 const urlToRedirect = 333 'https://gist.github.com/MattIPv4/045239bc27b16b2bcf7a3a9a4648c08a' 334 const url = new URL(`${baseUrl}/redirect`) 335 url.searchParams.set('u', urlToRedirect) 336 const res = await fetch(url, {redirect: 'manual'}) 337 assert.strictEqual(res.status, 200) 338 const html = await res.text() 339 assert.match(html, /meta http-equiv="refresh"/) 340 assert.match( 341 html, 342 new RegExp(urlToRedirect.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), 343 ) 344 // No blocked-site div, always safe 345 assert.doesNotMatch(html, /"blocked-site"/) 346 }) 347 348 it('Rule adjustment, safe redirect, safelink is disabled so always safe', async () => { 349 const urlToRedirect = 'https://www.instagram.com/teamseshbones/?hl=en' 350 const url = new URL(`${baseUrl}/redirect`) 351 url.searchParams.set('u', urlToRedirect) 352 const res = await fetch(url, {redirect: 'manual'}) 353 assert.strictEqual(res.status, 200) 354 const html = await res.text() 355 assert.match(html, /meta http-equiv="refresh"/) 356 assert.match( 357 html, 358 new RegExp(urlToRedirect.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), 359 ) 360 // No blocked-site div, always safe 361 assert.doesNotMatch(html, /"blocked-site"/) 362 }) 363 364 it('normal redirect with query params', async () => { 365 const urlToRedirect = 'https://bsky.app/settings' 366 const url = new URL(`${baseUrl}/redirect`) 367 url.searchParams.set('u', urlToRedirect) 368 url.searchParams.set('utm_source', 'test') 369 const res = await fetch(url, {redirect: 'manual'}) 370 assert.strictEqual(res.status, 200) 371 const html = await res.text() 372 assert.match(html, /meta http-equiv="refresh"/) 373 assert.match( 374 html, 375 new RegExp(urlToRedirect.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), 376 ) 377 // No blocked-site div, always safe 378 assert.doesNotMatch(html, /"blocked-site"/) 379 }) 380})