Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 28 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,43 @@
#
# IMPORTANT: Never commit .env to git!

# GitHub API token for accessing repositories
# Серверный GitHub API token. Для classic PAT нужен scope `repo` (и доступ
# организации к этому токену). Для fine-grained PAT выберите организацию курса
# как Resource owner, дайте доступ ко всем создаваемым репозиториям и разрешения:
# Administration: write, Contents: read, Checks: read, Actions: read.
# Generate at: https://github.com/settings/tokens
# Required scopes: repo, read:org
GITHUB_TOKEN=ghp_your_github_token_here

# GitHub OAuth App используется только для подтверждения аккаунта студента.
# OAuth App создаётся в аккаунте преподавателя. Для Client Secret нельзя
# использовать префикс VITE_: такие переменные Vite помещает в браузерный bundle.
GITHUB_OAUTH_CLIENT_ID=your_oauth_client_id
GITHUB_OAUTH_CLIENT_SECRET=your_oauth_client_secret

# Точный URL, указанный как "Authorization callback URL" в настройках OAuth App.
# При Caddy из примера production-адрес содержит публичный префикс /api/v1.
GITHUB_OAUTH_CALLBACK_URL=https://labgrader.example.edu/api/v1/join/callback

# Публичный origin frontend для возврата после OAuth. Необязателен при общем
# origin frontend/backend; нужен при локальной разработке на разных портах.
FRONTEND_BASE_URL=https://labgrader.example.edu

# Разрешённые browser origins через запятую. Не используйте "*" вместе с
# административной cookie; при пустом значении берётся origin FRONTEND_BASE_URL.
CORS_ALLOWED_ORIGINS=https://labgrader.example.edu

# Uvicorn должен доверять X-Forwarded-For только от известного reverse proxy.
# Для Docker/Caddy укажите точный IP контейнера Caddy или его минимальный CIDR;
# не используйте "*", если backend доступен кому-либо в обход proxy.
FORWARDED_ALLOW_IPS=SET_EXACT_CADDY_PROXY_IP_OR_CIDR

# Admin credentials for the web interface
ADMIN_LOGIN=your_admin_username
ADMIN_PASSWORD=your_secure_password_here

# Secret key for cookie signing
# Generate with: python3 -c "import secrets; print(secrets.token_hex(32))"
# Для OAuth state обязательно задайте собственное случайное значение.
SECRET_KEY=your_random_secret_key_here

# Google Sheets credentials file path (inside container)
Expand Down
5 changes: 4 additions & 1 deletion backend.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,7 @@ USER appuser

EXPOSE 8000

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
# Proxy headers обрабатываются самим Uvicorn до вызова FastAPI/Slowapi.
# Явно передаём только доверенные IP/CIDR, чтобы X-Forwarded-For от Caddy
# определял студента, а заголовок от недоверенного клиента игнорировался.
CMD ["sh", "-c", "exec uvicorn main:app --host 0.0.0.0 --port 8000 --proxy-headers --forwarded-allow-ips \"${FORWARDED_ALLOW_IPS:-127.0.0.1}\""]
12 changes: 12 additions & 0 deletions docker-compose.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ services:
# Credentials and secrets
CREDENTIALS_FILE: /app/google-credentials/credentials.json
GITHUB_TOKEN: ${GITHUB_TOKEN}
GITHUB_OAUTH_CLIENT_ID: ${GITHUB_OAUTH_CLIENT_ID}
GITHUB_OAUTH_CLIENT_SECRET: ${GITHUB_OAUTH_CLIENT_SECRET}
GITHUB_OAUTH_CALLBACK_URL: ${GITHUB_OAUTH_CALLBACK_URL}
FRONTEND_BASE_URL: ${FRONTEND_BASE_URL}
CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS:-}
# Обязательное доверие только к реальному Caddy позволяет Slowapi видеть
# адрес студента и не даёт произвольному X-Forwarded-For обходить лимит.
FORWARDED_ALLOW_IPS: ${FORWARDED_ALLOW_IPS:?Set trusted Caddy IP or CIDR}
ADMIN_LOGIN: ${ADMIN_LOGIN}
ADMIN_PASSWORD: ${ADMIN_PASSWORD}
SECRET_KEY: ${SECRET_KEY}
Expand All @@ -38,6 +46,10 @@ services:
labels:
caddy: labgrader.markpolyak.ru
# API endpoints
# Внешний Caddy в этом файле не включает access log. Если он включён в
# глобальной конфигурации proxy, параметры code/state callback необходимо
# редактировать там отдельно: приложение не может изменить уже сделанную
# reverse proxy запись.
caddy.handle_path: /api/v1*
caddy.handle_path.0_reverse_proxy: "{{upstreams 8000}}"
# Course logos (served by backend)
Expand Down
22 changes: 22 additions & 0 deletions docs/COURSE_CONFIG.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,28 @@ labs:
short-name: ЛР1
```

### `template-repo` (обязательно для автоматического создания репозитория)
**Тип:** `string`
**Формат:** `owner/repo` без суффикса `.git`
**Описание:** GitHub-репозиторий, из которого создаётся приватный репозиторий
студента при переходе по `/join/{course_id}/{lab_id}`. Исходный репозиторий
должен быть отмечен в GitHub как **Template repository**, а серверный
`GITHUB_TOKEN` должен иметь к нему доступ и право создавать репозитории в
организации курса.

Если поле отсутствует, обычная регистрация и проверка лабораторной продолжают
работать, но ссылка `/join/...` для этой лабораторной вернёт понятную ошибку
конфигурации.

**Пример:**
```yaml
labs:
"1":
github-prefix: os-task1
short-name: ЛР1
template-repo: suai-os-2025/os-task1-template
```

---

## CI/CD опции
Expand Down
53 changes: 53 additions & 0 deletions docs/DEPLOYMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ services:
environment:
CREDENTIALS_FILE: /app/google-credentials/credentials.json
GITHUB_TOKEN: ${GITHUB_TOKEN}
GITHUB_OAUTH_CLIENT_ID: ${GITHUB_OAUTH_CLIENT_ID}
GITHUB_OAUTH_CLIENT_SECRET: ${GITHUB_OAUTH_CLIENT_SECRET}
GITHUB_OAUTH_CALLBACK_URL: ${GITHUB_OAUTH_CALLBACK_URL}
FRONTEND_BASE_URL: ${FRONTEND_BASE_URL}
CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS:-}
FORWARDED_ALLOW_IPS: ${FORWARDED_ALLOW_IPS:?Set trusted Caddy IP or CIDR}
ADMIN_LOGIN: ${ADMIN_LOGIN}
ADMIN_PASSWORD: ${ADMIN_PASSWORD}
SECRET_KEY: ${SECRET_KEY}
Expand All @@ -86,11 +92,58 @@ networks:
```bash
# /opt/labgrader/.env
GITHUB_TOKEN=your_github_token
GITHUB_OAUTH_CLIENT_ID=your_oauth_client_id
GITHUB_OAUTH_CLIENT_SECRET=your_oauth_client_secret
# Caddy из примера удаляет /api/v1 перед передачей запроса в /join/callback.
GITHUB_OAUTH_CALLBACK_URL=https://labgrader.markpolyak.ru/api/v1/join/callback
FRONTEND_BASE_URL=https://labgrader.markpolyak.ru
# Точные frontend origins через запятую, без путей и завершающего слеша.
CORS_ALLOWED_ORIGINS=https://labgrader.markpolyak.ru
# Укажите точный IP контейнера Caddy или минимальный CIDR Docker-сети.
# Не используйте "*", если backend доступен в обход reverse proxy.
FORWARDED_ALLOW_IPS=172.18.0.2
ADMIN_LOGIN=your_admin_login
ADMIN_PASSWORD=your_secure_password
SECRET_KEY=your_secret_key
```

### GitHub setup for automatic repository creation

1. Under the **teacher's** GitHub account, create an OAuth App in
`Settings → Developer settings → OAuth Apps`. Set its callback URL exactly to
`GITHUB_OAUTH_CALLBACK_URL`. With the Caddy `/api/v1` rule above the public
callback is `https://<host>/api/v1/join/callback`, while FastAPI receives it
as the internal `/join/callback` route.
2. Store the issued Client ID and Client Secret only in the server `.env` file.
The secret must never use a `VITE_` prefix because Vite embeds such values in
the browser bundle.
Set a unique random `SECRET_KEY` as well: the `/join` flow rejects the public
development default because this key signs OAuth `state` values.
3. Create one GitHub template repository per lab and enable
**Settings → Template repository**. Add its `owner/repo` value to the lab's
`template-repo` field described in `docs/COURSE_CONFIG.md`.
4. Ensure `GITHUB_TOKEN` can read the template, create private repositories in
the target course organization, manage collaborators, read check runs, and
download GitHub Actions job logs. A classic PAT needs the `repo` scope and
organization access. For a fine-grained PAT select the course organization
as **Resource owner**, grant access to every repository that the service will
grade (including newly generated student repositories), and set repository
permissions **Administration: write**, **Contents: read**, **Checks: read**,
and **Actions: read**. If the organization requires approval for
fine-grained tokens, an owner must approve it before the service can access
private repositories. A token restricted only to the template repository can
create a repository but will receive HTTP 403 while reading CI from the new
student repository.
5. Open `/join/{course_id}/{lab_id}` with a test student account and verify the
complete flow: OAuth approval, private repository creation, invitation, and
a repeated visit that does not recreate or modify the repository.

`FORWARDED_ALLOW_IPS` передаётся Uvicorn через параметр
`--forwarded-allow-ips`. Только запросы от указанного Caddy могут изменить
`request.client.host` посредством `X-Forwarded-For`; поэтому Slowapi создаёт
отдельный rate-limit bucket для каждого студента и не доверяет заголовку,
присланному напрямую.

## Switching Between Branches

### Method 1: Using the Script (Recommended)
Expand Down
8 changes: 8 additions & 0 deletions docs/PROJECT_DESCRIPTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
Платформа решает следующие задачи:

- **Регистрация студентов** — связывание ФИО студента с GitHub аккаунтом
- **Создание репозиториев** — OAuth-подтверждение GitHub аккаунта и создание приватного репозитория из шаблона
- **Автоматическая проверка работ** — валидация кода через GitHub Actions/CI
- **Управление курсами** — поддержка множественных курсов с отдельными настройками
- **Интеграция с Google Sheets** — централизованное хранение данных о студентах и оценках
Expand Down Expand Up @@ -82,6 +83,7 @@
2. **Регистрация** — ввод ФИО и GitHub никнейма с валидацией
3. **Отправка работы на проверку** — автоматический запуск процесса оценивания
4. **Получение результата** — мгновенная обратная связь о статусе проверки
5. **Получение репозитория по общей ссылке** — безопасный вход через GitHub, создание репозитория и восстановление истёкшего приглашения

### Для администраторов

Expand Down Expand Up @@ -136,6 +138,9 @@ lab_grader_web/
│ └── README.md
├── tests/ # Модульные тесты
│ └── test_lab_column_lookup.py
├── grading/
│ ├── github_oauth.py # Серверная идентификация студента через GitHub OAuth
│ └── repository_provisioner.py # Создание репозитория и управление приглашениями
├── docs/ # Документация
│ ├── PROJECT_DESCRIPTION.md
│ ├── DEPLOYMENT.md
Expand Down Expand Up @@ -176,6 +181,9 @@ lab_grader_web/
| GET | `/courses/{course_id}/groups/{group_id}/labs` | Список лабораторных работ |
| POST | `/courses/{course_id}/groups/{group_id}/register` | Регистрация студента |
| POST | `/courses/{course_id}/groups/{group_id}/labs/{lab_id}/grade` | Проверка лабораторной работы |
| GET | `/join/{course_id}/{lab_id}` | Публичная информация для страницы получения репозитория |
| GET | `/join/{course_id}/{lab_id}/start` | Начало GitHub OAuth авторизации |
| GET | `/join/callback` | OAuth callback, создание репозитория и проверка приглашения |

### Административные маршруты

Expand Down
2 changes: 1 addition & 1 deletion frontend/courses-front/index.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!doctype html>
<html lang="en">
<html lang="ru">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/suai-sign-2.svg" />
Expand Down
Loading
Loading