Skip to content

Security: ZapolyarnyDev/PTKnowBackend

Security

SECURITY.md

🔒 Политика безопасности PTKnow

Документ фиксирует текущую модель доступа в PTKnow. Его задача — дать однозначный ответ, кто и к каким точкам API имеет доступ, чтобы это можно было сверять с SecurityConfig, контроллерами, сервисами и слоем политик.

👤 Роли доступа

  • ANONYMOUS - пользователь не вошёл в систему
  • GUEST - пользователь вошёл в систему, но ещё не подтверждён колледжем
  • STUDENT - подтверждённый студент
  • TEACHER - преподаватель
  • ADMIN - администратор платформы

🧩 Владение ресурсом и права редактора

OWNER и EDITOR не являются глобальными ролями.

OWNER - контекстное право владения конкретным ресурсом:

  • OWNER(profile) = profile.userId == currentUser.id
  • OWNER(course) = course.ownerId == currentUser.id
  • OWNER(lesson) = lesson.ownerId == currentUser.id
  • OWNER(file) = файл принадлежит профилю, курсу или уроку, владельцем которого является текущий пользователь

Правила:

  • владение ресурсом не должно проверяться только в контроллере
  • владение ресурсом должно подтверждаться в сервисе или слое политик
  • ADMIN имеет доступ ко всем ресурсам независимо от владения

EDITOR — контекстное право редактирования конкретного курса:

  • EDITOR(course) = пользователь добавлен в список редакторов курса

Правила:

  • EDITOR не является глобальной ролью
  • права редактора должны проверяться в сервисе или слое политик
  • ADMIN имеет доступ к изменению всех ресурсов независимо от статуса редактора

🔁 Переходы ролей

Целевая модель ролей:

  • ANONYMOUS -> GUEST после регистрации
  • GUEST -> STUDENT после подтверждения колледжем
  • GUEST -> TEACHER после подтверждения колледжем или назначения администратором
  • STUDENT -> GUEST и TEACHER -> GUEST при отзыве подтверждения
  • ADMIN назначается только вручную

В текущей реализации уже есть:

  • административное изменение роли
  • журналирование изменения роли
  • запрет на самоназначение привилегированной роли

🚫 Семантика ошибок доступа

  • 401 Unauthorized — пользователь не вошёл в систему или токен недействителен
  • 403 Forbidden — пользователь вошёл в систему, но не имеет права на действие
  • 404 Not Found — ресурс не существует

Принципы:

  • ошибки доступа не должны превращаться в 500
  • внутренние ошибки не должны маскироваться под 401

📝 Запись на курс

enrollment — запись пользователя на курс.

ENROLLED — пользователь записан на конкретный курс.

Правила:

  • проверка записи на курс выполняется в сервисе
  • запись на курс не заменяет владение, права редактора и права администратора, а дополняет их

🧱 Технические инварианты

  • каждая точка API, изменяющая состояние, обязана иметь явное правило доступа
  • каждая проверка владения ресурсом должна находиться в сервисе или слое политик
  • ADMIN имеет полный доступ ко всем ресурсам
  • access- и refresh-токены не должны логироваться целиком
  • refresh-токены в базе хранятся только в виде хэша
  • в рабочем окружении refresh-cookie должны быть HttpOnly и Secure
  • правила в коде должны соответствовать этому документу

📌 Статусы

  • Сделано — правило реализовано и соответствует текущему коду
  • Не сделано — правило описано, но ещё не реализовано
  • Нет в доменной модели — для реализации правила не хватает сущности, связи или части модели

🗺️ Матрица доступа

Аутентификация

  • POST /api/v0/auth/registerANONYMOUSСделано
  • POST /api/v0/auth/loginANONYMOUSСделано
  • POST /api/v0/auth/logout — любой пользователь, вошедший в систему: GUEST, STUDENT, TEACHER, ADMINСделано
  • POST /api/v0/token/refreshANONYMOUS или пользователь, вошедший в систему, при наличии действительного refresh-токена — Сделано

Профиль

  • GET /api/v0/profileGUEST, STUDENT, TEACHER, ADMINСделано
  • GET /api/v0/profile/meOWNER(profile)Сделано
  • GET /api/v0/profile/searchANONYMOUS, GUEST, STUDENT, TEACHER, ADMINСделано
  • GET /api/v0/profile/{handle}ANONYMOUS, GUEST, STUDENT, TEACHER, ADMINСделано
  • GET /api/v0/profile/id/{userId}ADMIN, OWNER(profile)Сделано
  • PATCH /api/v0/profileOWNER(profile)Сделано
  • PUT /api/v0/profileOWNER(profile)Сделано
  • POST /api/v0/profile/avatarOWNER(profile)Сделано
  • DELETE /api/v0/profile/avatarOWNER(profile)Сделано

Файлы

  • GET /api/v0/files/{id} — доступ зависит от fileVisibility вложения и видимости родительского ресурса — Сделано
  • PUBLICANONYMOUS, GUEST, STUDENT, TEACHER, ADMINСделано
  • ENROLLEDENROLLED, OWNER(course|lesson), EDITOR(course), ADMIN; анонимный доступ возможен только для опубликованного родительского курса и только если это допускает общая политика чтения — Сделано
  • PRIVATEOWNER(profile|course|lesson), ADMINСделано
  • GET /api/v0/files/{id}/metaOWNER(file), ADMINСделано
  • DELETE /api/v0/files/{id}OWNER(file), ADMINСделано

Курсы

  • GET /api/v0/courseANONYMOUS, GUEST, STUDENT, TEACHER, ADMIN; для анонимных и обычных пользователей доступны только опубликованные курсы, а владельцы и редакторы видят свои черновики и архивные курсы через правила сервиса — Сделано
  • POST /api/v0/courseTEACHER, ADMINСделано
  • PATCH /api/v0/course/{id}OWNER(course), ADMINСделано
  • PUT /api/v0/course/{id}OWNER(course), ADMINСделано
  • GET /api/v0/course/id/{id}ANONYMOUS для PUBLISHED, ENROLLED, OWNER(course), EDITOR(course), ADMIN; DRAFT и ARCHIVED недоступны пользователям без управляющих прав — Сделано
  • GET /api/v0/course/handle/{handle} — те же правила, что и для GET /api/v0/course/id/{id}Сделано
  • POST /api/v0/course/{id}/previewOWNER(course), EDITOR(course), ADMIN; видимость preview синхронизируется с состоянием курса — Сделано
  • DELETE /api/v0/course/{id}OWNER(course), ADMINСделано
  • POST /api/v0/course/{id}/editors/{userId}OWNER(course), ADMINСделано
  • DELETE /api/v0/course/{id}/editors/{userId}OWNER(course), ADMINСделано
  • POST /api/v0/course/{id}/publishOWNER(course), ADMINСделано
  • POST /api/v0/course/{id}/archiveOWNER(course), ADMINСделано
  • POST /api/v0/course/{id}/enrollGUEST, STUDENT; дополнительно применяются ограничения записи на курс — Сделано
  • DELETE /api/v0/course/{id}/enroll — пользователь может отменить только собственную запись на курс — Сделано
  • GET /api/v0/course/{id}/membersOWNER(course), EDITOR(course), ENROLLED, ADMINСделано
  • GET /api/v0/course/{id}/studentsOWNER(course), ADMINСделано
  • GET /api/v0/course/{id}/teachersOWNER(course), ADMINСделано
  • POST /api/v0/course/{id}/teachersOWNER(course), ADMINСделано
  • DELETE /api/v0/course/{id}/teachers/{teacherId}OWNER(course), ADMINСделано

Уроки

contentMd является частью ресурса lesson и подчиняется тем же правилам доступа, что и сам урок.

  • POST /api/v0/lessons/{courseId}OWNER(course), EDITOR(course), ADMINСделано
  • GET /api/v0/lessons/{lessonId}ANONYMOUS для уроков опубликованного курса, ENROLLED, OWNER(course), EDITOR(course), ADMINСделано
  • GET /api/v0/lessons/course/{courseId}ANONYMOUS для опубликованного курса, ENROLLED, OWNER(course), EDITOR(course), ADMINСделано
  • PATCH /api/v0/lessons/{lessonId}OWNER(lesson), ADMINСделано
  • PUT /api/v0/lessons/{lessonId}OWNER(lesson), ADMINСделано
  • PATCH /api/v0/lessons/{lessonId}/stateOWNER(lesson), ADMINСделано
  • DELETE /api/v0/lessons/{lessonId}OWNER(lesson), OWNER(course), ADMINСделано
  • POST /api/v0/lessons/{lessonId}/materialsOWNER(lesson), ADMINСделано
  • DELETE /api/v0/lessons/{lessonId}/materials/{fileId}OWNER(lesson), ADMINСделано

Администрирование пользователей

  • GET /api/v0/usersADMINСделано
  • GET /api/v0/users/{id}ADMINСделано
  • PATCH /api/v0/users/{id}/roleADMIN; самоназначение привилегированной роли запрещено — Сделано
  • PATCH /api/v0/users/{id}/statusADMIN; запрет на блокировку самого себя реализован — Сделано

There aren't any published security advisories