АНОНІМНО ВЕРИФІКОВАНО БЕЗ ЦЕНЗУРИ 89 ПІДТВЕРДЖЕНИХ · 158 ЧУТОК · 64 КОМПАНІЙ hash & delete ip - не логуємо АНОНІМНО ВЕРИФІКОВАНО БЕЗ ЦЕНЗУРИ 89 ПІДТВЕРДЖЕНИХ · 158 ЧУТОК · 64 КОМПАНІЙ hash & delete ip - не логуємо АНОНІМНО ВЕРИФІКОВАНО БЕЗ ЦЕНЗУРИ 89 ПІДТВЕРДЖЕНИХ · 158 ЧУТОК · 64 КОМПАНІЙ hash & delete ip - не логуємо
// OPEN DATA LAYER · v1.0

Hash-and-delete

Не «ми не зливаємо ваші дані». Простіше: даних, які можна злити, у нас немає. Оригінали LinkedIn ID і робочих email видаляються одразу після хешування.

// УРОК GLASSDOOR · 2024

Glassdoor зберігав «зашифровані» імена «на всяк випадок». У 2024 році почав показувати реальні імена в профілях. Урок засвоєний: якщо ти можеш розшифрувати - колись ти розшифруєш. У нас не залишається даних, які можна розшифрувати.

// 01 / РЕПОЗИТОРІЇ

ВІДКРИТЕ І ПРИВАТНЕ

● PUBLIC REPO

ebanutoe/privacy

Prisma schema, Auth.js v5 config, хеш-функції, міграції, скрипти зачистки оригіналів. Чого тут немає - алгоритму модерації.

○ PRIVATE REPO

ebanutoe/platform

Логіка підтверджень, anti-spam, ранжування. Закрите - щоб обійти його було складніше.

// 02 / ЩО ВИДАЛЯЄТЬСЯ

HASH AND DELETE

linkedin_subsha256hash & deleteDELETE
work_emailsha256hash & deleteDELETE
ip_address (просто не пишемо)sha256hash & deleteDELETE
// КОД (фрагмент із публічного репо)
// auth/[...nextauth].ts
async signIn({ profile }) {
  const sub  = profile.sub;
  const hash = await sha256(sub);
  await prisma.user.upsert({
    where:  { linkedinHash: hash },
    create: { linkedinHash: hash, pseudonym: randomAnimal() },
    update: {},
  });
  // sub, name, email НЕ зберігаємо
  return true;
}
// 03 / SCHEMA

PRISMA SCHEMA

model User {
  id            String   @id @default(uuid())
  linkedinHash  String   @unique          // sha256(sub)
  emailDomain   String?                   // тільки домен, не повна адреса
  pseudonym     String
  isVerified    Boolean  @default(false)  // -> "Підтверджений співробітник"
  createdAt     DateTime @default(now())

  posts         Post[]
  confirmations Confirmation[]
}

model Post {
  id                 String   @id @default(uuid())
  title              String
  content            String
  category           String   // salary | contract | culture | fop | hiring
  status             String   @default("rumor")  // rumor | confirmed | removed
  confirmationCount  Int      @default(0)
  createdAt          DateTime @default(now())

  company       Company        @relation(fields: [companyId], references: [id])
  companyId     String
  author        User           @relation(fields: [authorId], references: [id])
  authorId      String
  confirmations Confirmation[]
  responses     CompanyResponse[]
}
// 04 / МАТРИЦЯ ЗАХИСТУ

ЩО ДЕ ЛЕЖИТЬ

LinkedIn subsha256, оригінал -> DELETE одразу
LinkedIn nameне зберігаємо, не передаємо
LinkedIn emailне зберігаємо, не передаємо
Робочий emailsha256, зберігаємо тільки домен
IP-адресане логується ніде - ні CDN, ні runtime
User-Agentне логується
СесіяJWT, 4h, без зв'язку з аутентифікацією
Whois доменуprivacy on
ЮрисдикціяUSA · Vercel · Cloudflare
Деанонтехнічно неможливий - оригіналів немає
// 05 / СТЕК

ЧИМ ЦЕ ПОБУДОВАНО

Next.js 16

App Router · Edge runtime

Prisma 6

ORM · міграції

Neon

PostgreSQL serverless

Auth.js v5

LinkedIn OAuth

Vercel · US

хостинг

Cloudflare

DNS · DDoS · WAF