Реалізація зберігання підписаних документів з аудит-логом на сайті

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Реалізація зберігання підписаних документів з аудит-логом на сайті
Середня
~3-5 робочих днів
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Реалізація зберігання підписаних документів з аудит-логом

Зберігання підписаних документів — це не просто «покласти файл у папку». Вимоги: незмінюваність, цілісність, доступність, аудит кожної дії та відповідність законодавству про архівне зберігання. Порушення будь-якої з цих вимог ставить під сумнів юридичну силу документів.

Принципи зберігання

Незмінюваність — підписаний документ не може бути змінений. Версіонування S3 з MFA Delete або Write-Once-Read-Many (WORM) сховище.

Цілісність — при кожному доступі перевіряємо, що вміст збігається з збереженим хешем.

Розділення — підписані документи зберігаються окремо від робочих чорновиків. Різні S3 bucket'и з різними політиками доступу.

Резервне копіювання — крос-регіональна репліка ція. Втрата підписаного договору — юридичний та репутаційний ризик.

Сховище документів

// Сервіс завантаження в незмінне сховище
class DocumentStorageService {
  async storeSignedDocument(
    documentBytes: Buffer,
    metadata: DocumentMetadata
  ): Promise<StoredDocument> {
    // Хеш документа — незмінюваний ідентифікатор вмісту
    const contentHash = crypto.createHash('sha256').update(documentBytes).digest('hex');

    // Ключ включає хеш для дедублікації
    const s3Key = `signed/${metadata.documentId}/${contentHash}.pdf`;

    await this.s3.putObject({
      Bucket: process.env.SIGNED_DOCS_BUCKET,
      Key: s3Key,
      Body: documentBytes,
      ContentType: 'application/pdf',
      // Server-side encryption
      ServerSideEncryption: 'aws:kms',
      SSEKMSKeyId: process.env.KMS_KEY_ID,
      // Object Lock запобігає видаленню/зміні
      ObjectLockMode: 'COMPLIANCE',
      ObjectLockRetainUntilDate: addYears(new Date(), 10),
      Metadata: {
        'document-id': metadata.documentId,
        'signer-id': metadata.signerId,
        'signed-at': metadata.signedAt.toISOString(),
        'content-hash': contentHash,
      },
    }).promise();

    return {
      s3Key,
      contentHash,
      storageUrl: `s3://${process.env.SIGNED_DOCS_BUCKET}/${s3Key}`,
    };
  }

  async retrieveAndVerify(documentId: string): Promise<{ bytes: Buffer; integrityOk: boolean }> {
    const record = await db.signedDocuments.findByDocumentId(documentId);
    const object = await this.s3.getObject({
      Bucket: process.env.SIGNED_DOCS_BUCKET,
      Key: record.s3Key,
    }).promise();

    const bytes = object.Body as Buffer;
    const currentHash = crypto.createHash('sha256').update(bytes).digest('hex');
    const integrityOk = currentHash === record.contentHash;

    if (!integrityOk) {
      await this.alertIntegrityViolation(documentId, record.contentHash, currentHash);
    }

    return { bytes, integrityOk };
  }
}

Схема БД для документів

CREATE TABLE signed_documents (
  id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  document_id     UUID REFERENCES documents(id),
  version         INT NOT NULL DEFAULT 1,
  s3_key          VARCHAR(1000) NOT NULL UNIQUE,
  content_hash    CHAR(64) NOT NULL,  -- SHA-256
  file_size_bytes BIGINT,
  stored_at       TIMESTAMPTZ DEFAULT NOW(),
  expires_at      TIMESTAMPTZ,        -- Для документів з обмеженим періодом
  deleted_at      TIMESTAMPTZ,        -- М'яке видалення
  delete_reason   TEXT,
  delete_by       UUID REFERENCES users(id)
);

-- Підписи на документі
CREATE TABLE document_signatures (
  id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  signed_doc_id   UUID REFERENCES signed_documents(id),
  signer_id       UUID REFERENCES users(id),
  signer_role     VARCHAR(100),        -- 'initiator', 'approver', 'witness'
  signature_type  VARCHAR(50),         -- 'drawn', 'text', 'sms', 'kep'
  signature_data  JSONB,               -- Залежить від типу
  document_hash_at_signing CHAR(64),  -- Хеш у момент підписування
  signed_at       TIMESTAMPTZ DEFAULT NOW(),
  ip_address      INET,
  user_agent      TEXT
);

Аудит-лог

Кожна дія з документом записується у незмінний журнал:

CREATE TABLE document_audit_log (
  id              BIGSERIAL PRIMARY KEY,  -- Автоінкремент для порядку
  document_id     UUID NOT NULL,
  actor_id        UUID REFERENCES users(id),
  actor_type      VARCHAR(50) DEFAULT 'user',  -- 'user', 'system', 'api'
  action          VARCHAR(200) NOT NULL,
  -- Приклади: 'document.created', 'document.viewed', 'document.signed',
  --           'document.downloaded', 'document.shared', 'document.revoked'
  details         JSONB DEFAULT '{}',
  ip_address      INET,
  user_agent      TEXT,
  session_id      UUID,
  occurred_at     TIMESTAMPTZ DEFAULT NOW()
);

-- Індекс для швидкого пошуку по документу
CREATE INDEX ON document_audit_log (document_id, occurred_at DESC);
CREATE INDEX ON document_audit_log (actor_id, occurred_at DESC);

-- Триггер запобігає видаленню записів аудиту
CREATE RULE no_delete_audit AS ON DELETE TO document_audit_log DO INSTEAD NOTHING;
// Логування кожної дії
async function auditLog(documentId, actorId, action, details = {}) {
  await db.documentAuditLog.create({
    documentId,
    actorId,
    action,
    details,
    ipAddress: request?.ip,
    userAgent: request?.headers?.['user-agent'],
    sessionId: request?.session?.id,
    occurredAt: new Date(),
  });
}

// Middleware: автоматичний лог при завантаженні
app.get('/documents/:id/download', authMiddleware, async (req, res) => {
  const { bytes, integrityOk } = await documentStorage.retrieveAndVerify(req.params.id);
  await auditLog(req.params.id, req.user.id, 'document.downloaded', { integrityOk });
  res.setHeader('Content-Disposition', `attachment; filename="document-${req.params.id}.pdf"`);
  res.send(bytes);
});

Доступ до документів

Підписані документи не повинні бути доступні за прямими S3 URL. Тільки через тимчасові presigned URL, згенеровані сервером після перевірки прав та фіксації в аудит-логе:

async function getDocumentDownloadUrl(documentId, userId) {
  await checkDocumentAccess(documentId, userId); // Викидає 403 якщо немає доступу

  const record = await db.signedDocuments.findByDocumentId(documentId);
  const url = await s3.getSignedUrlPromise('getObject', {
    Bucket: process.env.SIGNED_DOCS_BUCKET,
    Key: record.s3Key,
    Expires: 300, // 5 хвилин
    ResponseContentDisposition: `attachment; filename="document.pdf"`,
  });

  await auditLog(documentId, userId, 'document.viewed');
  return url;
}

Строки зберігання

Тип документа Строк зберігання Основа
Договори купівлі-продажу 10 років Цивільний кодекс
Трудові договори 50 років Федеральний закон № 125
Кадрові документи 75 років Архівне законодавство
Згоди на обробку ПД 3 роки після відзиву Федеральний закон № 152

Автоматичне встановлення expires_at при створенні документа на основі його типу.

Терміни реалізації

Сховище з S3 Object Lock, хеш-верифікацією та аудит-логом — 5–7 днів. Контроль доступу з presigned URL та автоматичним логуванням — 2–3 дні. Інтерфейс історії дій з документом — 2–3 дні.