PDS Admin tool make it easier to moderate your PDS with labels

takedown

+311 -34
+2
drizzle/0002_tricky_kinsey_walden.sql
··· 1 + ALTER TABLE `labels_applied` ADD `uri` text;--> statement-breakpoint 2 + ALTER TABLE `watched_repos` ADD `take_down_issued_date` integer;
+184
drizzle/meta/0002_snapshot.json
··· 1 + { 2 + "version": "6", 3 + "dialect": "sqlite", 4 + "id": "6346fd9a-8d0c-4b29-9393-1e720cbca534", 5 + "prevId": "368e7243-8e16-4e27-a7af-a906d976d75e", 6 + "tables": { 7 + "labeler_cursors": { 8 + "name": "labeler_cursors", 9 + "columns": { 10 + "labeler_id": { 11 + "name": "labeler_id", 12 + "type": "text", 13 + "primaryKey": false, 14 + "notNull": false, 15 + "autoincrement": false 16 + }, 17 + "cursor": { 18 + "name": "cursor", 19 + "type": "integer", 20 + "primaryKey": false, 21 + "notNull": true, 22 + "autoincrement": false 23 + } 24 + }, 25 + "indexes": { 26 + "labeler_cursors_labeler_id_unique": { 27 + "name": "labeler_cursors_labeler_id_unique", 28 + "columns": [ 29 + "labeler_id" 30 + ], 31 + "isUnique": true 32 + } 33 + }, 34 + "foreignKeys": {}, 35 + "compositePrimaryKeys": {}, 36 + "uniqueConstraints": {}, 37 + "checkConstraints": {} 38 + }, 39 + "labels_applied": { 40 + "name": "labels_applied", 41 + "columns": { 42 + "id": { 43 + "name": "id", 44 + "type": "integer", 45 + "primaryKey": true, 46 + "notNull": true, 47 + "autoincrement": true 48 + }, 49 + "did": { 50 + "name": "did", 51 + "type": "text", 52 + "primaryKey": false, 53 + "notNull": true, 54 + "autoincrement": false 55 + }, 56 + "label": { 57 + "name": "label", 58 + "type": "text", 59 + "primaryKey": false, 60 + "notNull": true, 61 + "autoincrement": false 62 + }, 63 + "labeler": { 64 + "name": "labeler", 65 + "type": "text", 66 + "primaryKey": false, 67 + "notNull": true, 68 + "autoincrement": false 69 + }, 70 + "action": { 71 + "name": "action", 72 + "type": "text", 73 + "primaryKey": false, 74 + "notNull": true, 75 + "autoincrement": false 76 + }, 77 + "negated": { 78 + "name": "negated", 79 + "type": "integer", 80 + "primaryKey": false, 81 + "notNull": true, 82 + "autoincrement": false, 83 + "default": false 84 + }, 85 + "date_applied": { 86 + "name": "date_applied", 87 + "type": "integer", 88 + "primaryKey": false, 89 + "notNull": true, 90 + "autoincrement": false 91 + }, 92 + "uri": { 93 + "name": "uri", 94 + "type": "text", 95 + "primaryKey": false, 96 + "notNull": false, 97 + "autoincrement": false 98 + } 99 + }, 100 + "indexes": {}, 101 + "foreignKeys": { 102 + "labels_applied_did_watched_repos_did_fk": { 103 + "name": "labels_applied_did_watched_repos_did_fk", 104 + "tableFrom": "labels_applied", 105 + "tableTo": "watched_repos", 106 + "columnsFrom": [ 107 + "did" 108 + ], 109 + "columnsTo": [ 110 + "did" 111 + ], 112 + "onDelete": "no action", 113 + "onUpdate": "no action" 114 + } 115 + }, 116 + "compositePrimaryKeys": {}, 117 + "uniqueConstraints": {}, 118 + "checkConstraints": {} 119 + }, 120 + "watched_repos": { 121 + "name": "watched_repos", 122 + "columns": { 123 + "did": { 124 + "name": "did", 125 + "type": "text", 126 + "primaryKey": true, 127 + "notNull": true, 128 + "autoincrement": false 129 + }, 130 + "pds_host": { 131 + "name": "pds_host", 132 + "type": "text", 133 + "primaryKey": false, 134 + "notNull": true, 135 + "autoincrement": false 136 + }, 137 + "active": { 138 + "name": "active", 139 + "type": "integer", 140 + "primaryKey": false, 141 + "notNull": true, 142 + "autoincrement": false 143 + }, 144 + "date_first_seen": { 145 + "name": "date_first_seen", 146 + "type": "integer", 147 + "primaryKey": false, 148 + "notNull": true, 149 + "autoincrement": false 150 + }, 151 + "take_down_issued_date": { 152 + "name": "take_down_issued_date", 153 + "type": "integer", 154 + "primaryKey": false, 155 + "notNull": false, 156 + "autoincrement": false 157 + } 158 + }, 159 + "indexes": { 160 + "watched_repos_did_unique": { 161 + "name": "watched_repos_did_unique", 162 + "columns": [ 163 + "did" 164 + ], 165 + "isUnique": true 166 + } 167 + }, 168 + "foreignKeys": {}, 169 + "compositePrimaryKeys": {}, 170 + "uniqueConstraints": {}, 171 + "checkConstraints": {} 172 + } 173 + }, 174 + "views": {}, 175 + "enums": {}, 176 + "_meta": { 177 + "schemas": {}, 178 + "tables": {}, 179 + "columns": {} 180 + }, 181 + "internal": { 182 + "indexes": {} 183 + } 184 + }
+7
drizzle/meta/_journal.json
··· 15 15 "when": 1771625783853, 16 16 "tag": "0001_workable_leech", 17 17 "breakpoints": true 18 + }, 19 + { 20 + "idx": 2, 21 + "version": "6", 22 + "when": 1771889985830, 23 + "tag": "0002_tricky_kinsey_walden", 24 + "breakpoints": true 18 25 } 19 26 ] 20 27 }
+6
src/db/schema.ts
··· 6 6 pdsHost: text("pds_host").notNull(), 7 7 active: integer("active", { mode: "boolean" }).notNull(), 8 8 dateFirstSeen: integer("date_first_seen", { mode: "timestamp" }).notNull(), 9 + //If set means a take down was issued 10 + takeDownIssuedDate: integer("take_down_issued_date", { 11 + mode: "timestamp", 12 + }), 9 13 }); 10 14 11 15 export const labelsApplied = sqliteTable("labels_applied", { ··· 18 22 action: text("action").notNull(), 19 23 negated: integer("negated", { mode: "boolean" }).default(false).notNull(), 20 24 dateApplied: integer("date_applied", { mode: "timestamp" }).notNull(), 25 + // * AT URI of the record, repository (account), or other resource that this label applies to. 26 + uri: text("uri"), 21 27 }); 22 28 23 29 export const labelerCursor = sqliteTable("labeler_cursors", {
+81 -29
src/handlers/handleNewLabel.ts
··· 6 6 import * as schema from "../db/schema.js"; 7 7 import { and, eq } from "drizzle-orm"; 8 8 import type PQueue from "p-queue"; 9 - import { Client, simpleFetchHandler, ok } from "@atcute/client"; 9 + import { Client, simpleFetchHandler } from "@atcute/client"; 10 10 import { ComAtprotoAdminUpdateSubjectStatus } from "@atcute/atproto"; 11 11 const adminAuthHeader = (password: string) => ({ 12 12 Authorization: `Basic ${Buffer.from(`admin:${password}`).toString("base64")}`, ··· 35 35 targetDid = repoDid; 36 36 } 37 37 38 - // TODO: MAKE SURE TO CHECK NEG 39 38 let labledDate = new Date(label.cts); 40 39 logger.debug( 41 40 { ··· 49 48 ); 50 49 51 50 let labelConfig = config.labels[label.val]; 51 + //If the label is a watched one dig in 52 52 if (labelConfig) { 53 53 const isRepoWatched = await db 54 54 .select() ··· 61 61 ) 62 62 .limit(1); 63 63 64 + //If a watched repo/user is the target of the label dig in 64 65 if (isRepoWatched.length > 0) { 65 66 const watchedRepo = isRepoWatched[0]; 66 67 if (watchedRepo == undefined) { ··· 78 79 `Listed label: ${label.val} found added to ${watchedRepo.did}`, 79 80 ); 80 81 82 + // Check if this label already exists 81 83 const existing = await db 82 84 .select() 83 85 .from(schema.labelsApplied) ··· 126 128 labeler: config.host, 127 129 negated: label.neg ?? false, 128 130 dateApplied: labledDate, 131 + targetUri: label.uri, 129 132 takeDown: false, 130 133 }).catch((err) => 131 134 logger.error({ err }, "Error sending label notification email"), 132 135 ), 133 136 ); 134 - case "takedown": 137 + break; 138 + case "takedown": { 139 + let takedownSuccess: boolean; 140 + 135 141 if (pdsConfig.pdsAdminPassword) { 136 142 const rpc = new Client({ 137 143 handler: simpleFetchHandler({ ··· 139 145 }), 140 146 }); 141 147 142 - logger.info("taking down the account"); 143 - //TODO do atcute actions and then send email 144 - if (label.neg) { 145 - //reverse takedown 146 - let reverseTakeDown = rpc.call( 147 - ComAtprotoAdminUpdateSubjectStatus, 148 - { 148 + try { 149 + if (label.neg) { 150 + logger.info({ did: targetDid }, "Reversing takedown"); 151 + await rpc.call(ComAtprotoAdminUpdateSubjectStatus, { 149 152 input: { 150 153 subject: { 151 154 $type: "com.atproto.admin.defs#repoRef", ··· 156 159 }, 157 160 }, 158 161 headers: adminAuthHeader(pdsConfig.pdsAdminPassword), 159 - }, 162 + }); 163 + logger.info( 164 + { did: targetDid }, 165 + "Takedown reversed successfully", 166 + ); 167 + } else { 168 + if (!watchedRepo.takeDownIssuedDate) { 169 + logger.info({ did: targetDid }, "Issuing takedown"); 170 + await rpc.call(ComAtprotoAdminUpdateSubjectStatus, { 171 + input: { 172 + subject: { 173 + $type: "com.atproto.admin.defs#repoRef", 174 + did: targetDid as `did:${string}:${string}.`, 175 + }, 176 + takedown: { 177 + applied: true, 178 + ref: Math.floor(Date.now() / 1000).toString(), 179 + }, 180 + }, 181 + headers: adminAuthHeader(pdsConfig.pdsAdminPassword), 182 + }); 183 + await db.update(schema.watchedRepos).set({ 184 + takeDownIssuedDate: new Date(), 185 + }); 186 + 187 + logger.info( 188 + { did: targetDid }, 189 + "Takedown issued successfully", 190 + ); 191 + takedownSuccess = true; 192 + } else { 193 + logger.info( 194 + { did: targetDid }, 195 + "Duplicate event, not reissuing a takedown", 196 + ); 197 + } 198 + } 199 + } catch (err) { 200 + takedownSuccess = false; 201 + logger.error( 202 + { err, did: targetDid }, 203 + label.neg 204 + ? "Failed to reverse takedown" 205 + : "Failed to issue takedown", 160 206 ); 161 - } else { 162 - //issue takedown 163 - let takeDown = rpc.call(ComAtprotoAdminUpdateSubjectStatus, { 164 - input: { 165 - subject: { 166 - $type: "com.atproto.admin.defs#repoRef", 167 - did: targetDid as `did:${string}:${string}.`, 168 - }, 169 - takedown: { 170 - applied: true, 171 - ref: Math.floor(Date.now() / 1000).toString(), 172 - }, 173 - }, 174 - headers: adminAuthHeader(pdsConfig.pdsAdminPassword), 175 - }); 176 207 } 177 - break; 178 208 } else { 179 - logger.warn("PDS admin password not set, takedown not issued"); 209 + logger.warn( 210 + { did: targetDid }, 211 + "PDS admin password not set, takedown not issued", 212 + ); 180 213 } 181 214 182 - //Send the email here still. IF passwrod is not set always set takedown as false? 215 + await mailQueue.add(() => 216 + sendLabelNotification(pdsConfig.notifyEmails, { 217 + did: targetDid, 218 + pds: pdsConfig.host, 219 + label: label.val, 220 + labeler: config.host, 221 + negated: label.neg ?? false, 222 + dateApplied: labledDate, 223 + takeDown: true, 224 + targetUri: label.uri, 225 + takedownSuccess, 226 + }).catch((err) => 227 + logger.error( 228 + { err }, 229 + "Error sending takedown notification email", 230 + ), 231 + ), 232 + ); 233 + break; 234 + } 183 235 } 184 236 185 237 return;
+2 -1
src/index.ts
··· 10 10 import { backFillPds } from "./pds.js"; 11 11 import { pdsSubscriber } from "./handlers/pdsSubscriber.js"; 12 12 13 - const labelQueue = new PQueue({ concurrency: 2 }); 13 + //Leaveing this at 1 concurrency right now since some labelers do multiple labels at once I've found. 14 + const labelQueue = new PQueue({ concurrency: 1 }); 14 15 const identityQueue = new PQueue({ concurrency: 2 }); 15 16 const mailQueue = new PQueue({ concurrency: 1 }); 16 17
+29 -4
src/mailer.ts
··· 25 25 negated: boolean; 26 26 dateApplied: Date; 27 27 takeDown: boolean; 28 + targetUri: string; 29 + takedownSuccess?: boolean; 28 30 }, 29 31 ) => { 30 - const { did, pds, label, labeler, negated, dateApplied, takeDown } = params; 32 + const { 33 + did, 34 + pds, 35 + label, 36 + labeler, 37 + negated, 38 + dateApplied, 39 + takeDown, 40 + targetUri, 41 + takedownSuccess, 42 + } = params; 31 43 32 44 const subject = `Label "${label}" ${negated ? "negated" : "applied"} — ${did} - ${pds}`; 33 45 let info = [ ··· 39 51 `Labeler: ${labeler}`, 40 52 `Negated: ${negated}`, 41 53 `Date: ${dateApplied.toISOString()}`, 54 + `Target: ${targetUri}`, 42 55 ]; 43 56 44 57 if (takeDown) { 45 - if (negated) { 46 - info.push(`Label negated, takedown reversed.`); 58 + if (takedownSuccess === undefined) { 59 + info.push( 60 + `Takedown action configured but not attempted (admin password not set).`, 61 + ); 62 + } else if (takedownSuccess) { 63 + info.push( 64 + negated 65 + ? `Takedown reversed successfully.` 66 + : `Takedown issued successfully.`, 67 + ); 47 68 } else { 48 - info.push(`Takedown issued.`); 69 + info.push( 70 + negated 71 + ? `Failed to reverse takedown — manual action required.` 72 + : `Failed to issue takedown — manual action required.`, 73 + ); 49 74 } 50 75 } 51 76