mirror of
https://github.com/getnora-io/nora.git
synced 2026-04-12 06:50:31 +00:00
style: cargo fmt
DevITWay
This commit is contained in:
414
CHANGELOG.md.bak
Normal file
414
CHANGELOG.md.bak
Normal file
@@ -0,0 +1,414 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to NORA will be documented in this file.
|
||||
|
||||
---
|
||||
|
||||
## [0.2.18] - 2026-01-31
|
||||
|
||||
### Changed
|
||||
- Logo styling refinements
|
||||
|
||||
---
|
||||
|
||||
## [0.2.17] - 2026-01-31
|
||||
|
||||
### Added
|
||||
- Copyright headers to all source files (Volkov Pavel | DevITWay)
|
||||
- SPDX-License-Identifier: MIT in all .rs files
|
||||
|
||||
---
|
||||
|
||||
## [0.2.16] - 2026-01-31
|
||||
|
||||
### Changed
|
||||
- N○RA branding: stylized O logo across dashboard
|
||||
- Fixed O letter alignment in logo
|
||||
|
||||
---
|
||||
|
||||
## [0.2.15] - 2026-01-31
|
||||
|
||||
### Fixed
|
||||
- Code formatting (cargo fmt)
|
||||
|
||||
---
|
||||
|
||||
## [0.2.14] - 2026-01-31
|
||||
|
||||
### Fixed
|
||||
- Docker dashboard now shows actual image size from manifest layers (config + layers sum)
|
||||
- Previously showed only manifest file size (~500 B instead of actual image size)
|
||||
|
||||
---
|
||||
|
||||
## [0.2.13] - 2026-01-31
|
||||
|
||||
### Fixed
|
||||
- npm dashboard now shows correct version count and package sizes
|
||||
- Parses metadata.json for versions, dist.unpackedSize, and time.modified
|
||||
- Previously showed 0 versions / 0 B for all packages
|
||||
|
||||
---
|
||||
|
||||
## [0.2.12] - 2026-01-30
|
||||
|
||||
### Added
|
||||
|
||||
#### Configurable Rate Limiting
|
||||
- Rate limits now configurable via `config.toml` and environment variables
|
||||
- New config section `[rate_limit]` with parameters: `auth_rps`, `auth_burst`, `upload_rps`, `upload_burst`, `general_rps`, `general_burst`
|
||||
- Environment variables: `NORA_RATE_LIMIT_{AUTH|UPLOAD|GENERAL}_{RPS|BURST}`
|
||||
|
||||
#### Secrets Provider Architecture
|
||||
- Trait-based secrets management (`SecretsProvider` trait)
|
||||
- ENV provider as default (12-Factor App pattern)
|
||||
- Protected secrets with `zeroize` (memory zeroed on drop)
|
||||
- Redacted Debug impl prevents secret leakage in logs
|
||||
- New config section `[secrets]` with `provider` and `clear_env` options
|
||||
|
||||
#### Docker Image Metadata
|
||||
- Support for image metadata retrieval
|
||||
|
||||
#### Documentation
|
||||
- Bilingual onboarding guide (EN/RU)
|
||||
|
||||
---
|
||||
|
||||
## [0.2.11] - 2026-01-26
|
||||
|
||||
### Added
|
||||
- Internationalization (i18n) support
|
||||
- PyPI registry proxy
|
||||
- UI improvements
|
||||
|
||||
---
|
||||
|
||||
## [0.2.10] - 2026-01-26
|
||||
|
||||
### Changed
|
||||
- Dark theme applied to all UI pages
|
||||
|
||||
---
|
||||
|
||||
## [0.2.9] - 2026-01-26
|
||||
|
||||
### Changed
|
||||
- Version bump release
|
||||
|
||||
---
|
||||
|
||||
## [0.2.8] - 2026-01-26
|
||||
|
||||
### Added
|
||||
- Dashboard endpoint added to OpenAPI documentation
|
||||
|
||||
---
|
||||
|
||||
## [0.2.7] - 2026-01-26
|
||||
|
||||
### Added
|
||||
- Dynamic version display in UI sidebar
|
||||
|
||||
---
|
||||
|
||||
## [0.2.6] - 2026-01-26
|
||||
|
||||
### Added
|
||||
|
||||
#### Dashboard Metrics
|
||||
- Global stats panel: downloads, uploads, artifacts, cache hit rate, storage
|
||||
- Extended registry cards with artifact count, size, counters
|
||||
- Activity log (last 20 events)
|
||||
|
||||
#### UI
|
||||
- Dark theme (bg: #0f172a, cards: #1e293b)
|
||||
|
||||
---
|
||||
|
||||
## [0.2.5] - 2026-01-26
|
||||
|
||||
### Fixed
|
||||
- Docker push/pull: added PATCH endpoint for chunked uploads
|
||||
|
||||
---
|
||||
|
||||
## [0.2.4] - 2026-01-26
|
||||
|
||||
### Fixed
|
||||
- Rate limiting: health/metrics endpoints now exempt
|
||||
- Increased upload rate limits for Docker parallel requests
|
||||
|
||||
---
|
||||
|
||||
## [0.2.0] - 2026-01-25
|
||||
|
||||
### Added
|
||||
|
||||
#### UI: SVG Brand Icons
|
||||
- Replaced emoji icons with proper SVG brand icons (Simple Icons style)
|
||||
- Docker, Maven, npm, Cargo, PyPI icons now render as scalable vector graphics
|
||||
- Consistent icon styling across dashboard, sidebar, and detail pages
|
||||
|
||||
#### Testing Infrastructure
|
||||
- Unit tests for LocalStorage (8 tests): put/get, list, stat, health_check
|
||||
- Unit tests for S3Storage with wiremock HTTP mocking (11 tests)
|
||||
- Integration tests for auth/htpasswd (7 tests)
|
||||
- Token lifecycle tests (11 tests)
|
||||
- Validation tests (21 tests)
|
||||
- **Total: 75 tests passing**
|
||||
|
||||
#### Security: Input Validation (`validation.rs`)
|
||||
- Path traversal protection: rejects `../`, `..\\`, null bytes, absolute paths
|
||||
- Docker image name validation per OCI distribution spec
|
||||
- Content digest validation (`sha256:[64 hex]`, `sha512:[128 hex]`)
|
||||
- Docker tag/reference validation
|
||||
- Storage key length limits (max 1024 chars)
|
||||
|
||||
#### Security: Rate Limiting (`rate_limit.rs`)
|
||||
- Auth endpoints: 1 req/sec, burst 5 (brute-force protection)
|
||||
- Upload endpoints: 10 req/sec, burst 20
|
||||
- General endpoints: 100 req/sec, burst 200
|
||||
- Uses `tower_governor` 0.8 with `PeerIpKeyExtractor`
|
||||
|
||||
#### Observability: Request ID Tracking (`request_id.rs`)
|
||||
- `X-Request-ID` header added to all responses
|
||||
- Accepts upstream request ID or generates UUID v4
|
||||
- Tracing spans include request_id for log correlation
|
||||
|
||||
#### CLI: Migrate Command (`migrate.rs`)
|
||||
- `nora migrate --from local --to s3` - migrate between storage backends
|
||||
- `--dry-run` flag for preview without copying
|
||||
- Progress bar with indicatif
|
||||
- Skips existing files in destination
|
||||
- Summary statistics (migrated, skipped, failed, bytes)
|
||||
|
||||
#### Error Handling (`error.rs`)
|
||||
- `AppError` enum with `IntoResponse` for Axum
|
||||
- Automatic conversion from `StorageError` and `ValidationError`
|
||||
- JSON error responses with request_id support
|
||||
|
||||
### Changed
|
||||
- `StorageError` now uses `thiserror` derive macro
|
||||
- `TokenError` now uses `thiserror` derive macro
|
||||
- Storage wrapper validates keys before delegating to backend
|
||||
- Docker registry handlers validate name, digest, reference inputs
|
||||
- Body size limit set to 100MB default via `DefaultBodyLimit`
|
||||
|
||||
### Dependencies Added
|
||||
- `thiserror = "2"` - typed error handling
|
||||
- `tower_governor = "0.8"` - rate limiting
|
||||
- `governor = "0.10"` - rate limiting backend
|
||||
- `tempfile = "3"` (dev) - temporary directories for tests
|
||||
- `wiremock = "0.6"` (dev) - HTTP mocking for S3 tests
|
||||
|
||||
### Files Added
|
||||
- `src/validation.rs` - input validation module
|
||||
- `src/migrate.rs` - storage migration module
|
||||
- `src/error.rs` - application error types
|
||||
- `src/request_id.rs` - request ID middleware
|
||||
- `src/rate_limit.rs` - rate limiting configuration
|
||||
|
||||
---
|
||||
|
||||
## [0.1.0] - 2026-01-24
|
||||
|
||||
### Added
|
||||
- Multi-protocol support: Docker Registry v2, Maven, npm, Cargo, PyPI
|
||||
- Web UI dashboard
|
||||
- Swagger UI (`/api-docs`)
|
||||
- Storage backends: Local filesystem, S3-compatible
|
||||
- Smart proxy/cache for Maven and npm
|
||||
- Health checks (`/health`, `/ready`)
|
||||
- Basic authentication (htpasswd with bcrypt)
|
||||
- API tokens (revocable, per-user)
|
||||
- Prometheus metrics (`/metrics`)
|
||||
- JSON structured logging
|
||||
- Environment variable configuration
|
||||
- Graceful shutdown (SIGTERM/SIGINT)
|
||||
- Backup/restore commands
|
||||
|
||||
---
|
||||
|
||||
# Журнал изменений (RU)
|
||||
|
||||
Все значимые изменения NORA документируются в этом файле.
|
||||
|
||||
---
|
||||
|
||||
## [0.2.12] - 2026-01-30
|
||||
|
||||
### Добавлено
|
||||
|
||||
#### Настраиваемый Rate Limiting
|
||||
- Rate limits настраиваются через `config.toml` и переменные окружения
|
||||
- Новая секция `[rate_limit]` с параметрами: `auth_rps`, `auth_burst`, `upload_rps`, `upload_burst`, `general_rps`, `general_burst`
|
||||
- Переменные окружения: `NORA_RATE_LIMIT_{AUTH|UPLOAD|GENERAL}_{RPS|BURST}`
|
||||
|
||||
#### Архитектура Secrets Provider
|
||||
- Trait-based управление секретами (`SecretsProvider` trait)
|
||||
- ENV provider по умолчанию (12-Factor App паттерн)
|
||||
- Защищённые секреты с `zeroize` (память обнуляется при drop)
|
||||
- Redacted Debug impl предотвращает утечку секретов в логи
|
||||
- Новая секция `[secrets]` с опциями `provider` и `clear_env`
|
||||
|
||||
#### Docker Image Metadata
|
||||
- Поддержка получения метаданных образов
|
||||
|
||||
#### Документация
|
||||
- Двуязычный onboarding guide (EN/RU)
|
||||
|
||||
---
|
||||
|
||||
## [0.2.11] - 2026-01-26
|
||||
|
||||
### Добавлено
|
||||
- Поддержка интернационализации (i18n)
|
||||
- PyPI registry proxy
|
||||
- Улучшения UI
|
||||
|
||||
---
|
||||
|
||||
## [0.2.10] - 2026-01-26
|
||||
|
||||
### Изменено
|
||||
- Тёмная тема применена ко всем страницам UI
|
||||
|
||||
---
|
||||
|
||||
## [0.2.9] - 2026-01-26
|
||||
|
||||
### Изменено
|
||||
- Релиз с обновлением версии
|
||||
|
||||
---
|
||||
|
||||
## [0.2.8] - 2026-01-26
|
||||
|
||||
### Добавлено
|
||||
- Dashboard endpoint добавлен в OpenAPI документацию
|
||||
|
||||
---
|
||||
|
||||
## [0.2.7] - 2026-01-26
|
||||
|
||||
### Добавлено
|
||||
- Динамическое отображение версии в сайдбаре UI
|
||||
|
||||
---
|
||||
|
||||
## [0.2.6] - 2026-01-26
|
||||
|
||||
### Добавлено
|
||||
|
||||
#### Dashboard Metrics
|
||||
- Глобальная панель статистики: downloads, uploads, artifacts, cache hit rate, storage
|
||||
- Расширенные карточки реестров с количеством артефактов, размером, счётчиками
|
||||
- Лог активности (последние 20 событий)
|
||||
|
||||
#### UI
|
||||
- Тёмная тема (bg: #0f172a, cards: #1e293b)
|
||||
|
||||
---
|
||||
|
||||
## [0.2.5] - 2026-01-26
|
||||
|
||||
### Исправлено
|
||||
- Docker push/pull: добавлен PATCH endpoint для chunked uploads
|
||||
|
||||
---
|
||||
|
||||
## [0.2.4] - 2026-01-26
|
||||
|
||||
### Исправлено
|
||||
- Rate limiting: health/metrics endpoints теперь исключены
|
||||
- Увеличены лимиты upload для параллельных Docker запросов
|
||||
|
||||
---
|
||||
|
||||
## [0.2.0] - 2026-01-25
|
||||
|
||||
### Добавлено
|
||||
|
||||
#### UI: SVG иконки брендов
|
||||
- Эмоджи заменены на SVG иконки брендов (стиль Simple Icons)
|
||||
- Docker, Maven, npm, Cargo, PyPI теперь отображаются как векторная графика
|
||||
- Единый стиль иконок на дашборде, сайдбаре и страницах деталей
|
||||
|
||||
#### Тестовая инфраструктура
|
||||
- Unit-тесты для LocalStorage (8 тестов): put/get, list, stat, health_check
|
||||
- Unit-тесты для S3Storage с HTTP-мокированием wiremock (11 тестов)
|
||||
- Интеграционные тесты auth/htpasswd (7 тестов)
|
||||
- Тесты жизненного цикла токенов (11 тестов)
|
||||
- Тесты валидации (21 тест)
|
||||
- **Всего: 75 тестов проходят**
|
||||
|
||||
#### Безопасность: Валидация ввода (`validation.rs`)
|
||||
- Защита от path traversal: отклоняет `../`, `..\\`, null-байты, абсолютные пути
|
||||
- Валидация имён Docker-образов по спецификации OCI distribution
|
||||
- Валидация дайджестов (`sha256:[64 hex]`, `sha512:[128 hex]`)
|
||||
- Валидация тегов и ссылок Docker
|
||||
- Ограничение длины ключей хранилища (макс. 1024 символа)
|
||||
|
||||
#### Безопасность: Rate Limiting (`rate_limit.rs`)
|
||||
- Auth endpoints: 1 req/sec, burst 5 (защита от брутфорса)
|
||||
- Upload endpoints: 10 req/sec, burst 20
|
||||
- Общие endpoints: 100 req/sec, burst 200
|
||||
- Использует `tower_governor` 0.8 с `PeerIpKeyExtractor`
|
||||
|
||||
#### Наблюдаемость: Отслеживание Request ID (`request_id.rs`)
|
||||
- Заголовок `X-Request-ID` добавляется ко всем ответам
|
||||
- Принимает upstream request ID или генерирует UUID v4
|
||||
- Tracing spans включают request_id для корреляции логов
|
||||
|
||||
#### CLI: Команда миграции (`migrate.rs`)
|
||||
- `nora migrate --from local --to s3` - миграция между storage backends
|
||||
- Флаг `--dry-run` для предпросмотра без копирования
|
||||
- Прогресс-бар с indicatif
|
||||
- Пропуск существующих файлов в destination
|
||||
- Итоговая статистика (migrated, skipped, failed, bytes)
|
||||
|
||||
#### Обработка ошибок (`error.rs`)
|
||||
- Enum `AppError` с `IntoResponse` для Axum
|
||||
- Автоматическая конверсия из `StorageError` и `ValidationError`
|
||||
- JSON-ответы об ошибках с поддержкой request_id
|
||||
|
||||
### Изменено
|
||||
- `StorageError` теперь использует макрос `thiserror`
|
||||
- `TokenError` теперь использует макрос `thiserror`
|
||||
- Storage wrapper валидирует ключи перед делегированием backend
|
||||
- Docker registry handlers валидируют name, digest, reference
|
||||
- Лимит размера body установлен в 100MB через `DefaultBodyLimit`
|
||||
|
||||
### Добавлены зависимости
|
||||
- `thiserror = "2"` - типизированная обработка ошибок
|
||||
- `tower_governor = "0.8"` - rate limiting
|
||||
- `governor = "0.10"` - backend для rate limiting
|
||||
- `tempfile = "3"` (dev) - временные директории для тестов
|
||||
- `wiremock = "0.6"` (dev) - HTTP-мокирование для S3 тестов
|
||||
|
||||
### Добавлены файлы
|
||||
- `src/validation.rs` - модуль валидации ввода
|
||||
- `src/migrate.rs` - модуль миграции хранилища
|
||||
- `src/error.rs` - типы ошибок приложения
|
||||
- `src/request_id.rs` - middleware для request ID
|
||||
- `src/rate_limit.rs` - конфигурация rate limiting
|
||||
|
||||
---
|
||||
|
||||
## [0.1.0] - 2026-01-24
|
||||
|
||||
### Добавлено
|
||||
- Мульти-протокольная поддержка: Docker Registry v2, Maven, npm, Cargo, PyPI
|
||||
- Web UI дашборд
|
||||
- Swagger UI (`/api-docs`)
|
||||
- Storage backends: локальная файловая система, S3-совместимое хранилище
|
||||
- Умный прокси/кэш для Maven и npm
|
||||
- Health checks (`/health`, `/ready`)
|
||||
- Базовая аутентификация (htpasswd с bcrypt)
|
||||
- API токены (отзываемые, per-user)
|
||||
- Prometheus метрики (`/metrics`)
|
||||
- JSON структурированное логирование
|
||||
- Конфигурация через переменные окружения
|
||||
- Graceful shutdown (SIGTERM/SIGINT)
|
||||
- Команды backup/restore
|
||||
@@ -45,11 +45,7 @@ pub struct AuditLog {
|
||||
impl AuditLog {
|
||||
pub fn new(storage_path: &str) -> Self {
|
||||
let path = PathBuf::from(storage_path).join("audit.jsonl");
|
||||
let writer = match OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(&path)
|
||||
{
|
||||
let writer = match OpenOptions::new().create(true).append(true).open(&path) {
|
||||
Ok(f) => {
|
||||
info!(path = %path.display(), "Audit log initialized");
|
||||
Mutex::new(Some(f))
|
||||
|
||||
@@ -13,8 +13,8 @@ use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::AppState;
|
||||
use crate::tokens::Role;
|
||||
use crate::AppState;
|
||||
|
||||
/// Htpasswd-based authentication
|
||||
#[derive(Clone)]
|
||||
@@ -247,12 +247,18 @@ async fn create_token(
|
||||
};
|
||||
|
||||
let role = match req.role.as_str() {
|
||||
"read" => Role::Read,
|
||||
"write" => Role::Write,
|
||||
"admin" => Role::Admin,
|
||||
_ => return (StatusCode::BAD_REQUEST, "Invalid role. Use: read, write, admin").into_response(),
|
||||
};
|
||||
match token_store.create_token(&req.username, req.ttl_days, req.description, role) {
|
||||
"read" => Role::Read,
|
||||
"write" => Role::Write,
|
||||
"admin" => Role::Admin,
|
||||
_ => {
|
||||
return (
|
||||
StatusCode::BAD_REQUEST,
|
||||
"Invalid role. Use: read, write, admin",
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
};
|
||||
match token_store.create_token(&req.username, req.ttl_days, req.description, role) {
|
||||
Ok(token) => Json(CreateTokenResponse {
|
||||
token,
|
||||
expires_in_days: req.ttl_days,
|
||||
|
||||
@@ -29,7 +29,10 @@ pub async fn run_gc(storage: &Storage, dry_run: bool) -> GcResult {
|
||||
|
||||
// 2. Collect all referenced digests from manifests
|
||||
let referenced = collect_referenced_digests(storage).await;
|
||||
info!("Found {} referenced digests from manifests", referenced.len());
|
||||
info!(
|
||||
"Found {} referenced digests from manifests",
|
||||
referenced.len()
|
||||
);
|
||||
|
||||
// 3. Find orphans
|
||||
let mut orphan_keys: Vec<String> = Vec::new();
|
||||
|
||||
@@ -8,11 +8,11 @@ mod backup;
|
||||
mod config;
|
||||
mod dashboard_metrics;
|
||||
mod error;
|
||||
mod gc;
|
||||
mod health;
|
||||
mod metrics;
|
||||
mod migrate;
|
||||
mod openapi;
|
||||
mod gc;
|
||||
mod rate_limit;
|
||||
mod registry;
|
||||
mod repo_index;
|
||||
|
||||
@@ -51,7 +51,9 @@ async fn download(
|
||||
"cargo",
|
||||
"LOCAL",
|
||||
));
|
||||
state.audit.log(AuditEntry::new("pull", "api", "", "cargo", ""));
|
||||
state
|
||||
.audit
|
||||
.log(AuditEntry::new("pull", "api", "", "cargo", ""));
|
||||
(StatusCode::OK, data).into_response()
|
||||
}
|
||||
Err(_) => StatusCode::NOT_FOUND.into_response(),
|
||||
|
||||
@@ -312,7 +312,10 @@ async fn upload_blob(
|
||||
StatusCode::CREATED,
|
||||
[
|
||||
(header::LOCATION, location),
|
||||
(HeaderName::from_static("docker-content-digest"), digest.to_string()),
|
||||
(
|
||||
HeaderName::from_static("docker-content-digest"),
|
||||
digest.to_string(),
|
||||
),
|
||||
],
|
||||
)
|
||||
.into_response()
|
||||
@@ -489,7 +492,13 @@ async fn put_manifest(
|
||||
"docker",
|
||||
"LOCAL",
|
||||
));
|
||||
state.audit.log(AuditEntry::new("push", "api", &format!("{}:{}", name, reference), "docker", "manifest"));
|
||||
state.audit.log(AuditEntry::new(
|
||||
"push",
|
||||
"api",
|
||||
&format!("{}:{}", name, reference),
|
||||
"docker",
|
||||
"manifest",
|
||||
));
|
||||
state.repo_index.invalidate("docker");
|
||||
|
||||
let location = format!("/v2/{}/manifests/{}", name, reference);
|
||||
|
||||
@@ -43,7 +43,9 @@ async fn download(State(state): State<Arc<AppState>>, Path(path): Path<String>)
|
||||
"maven",
|
||||
"CACHE",
|
||||
));
|
||||
state.audit.log(AuditEntry::new("cache_hit", "api", "", "maven", ""));
|
||||
state
|
||||
.audit
|
||||
.log(AuditEntry::new("cache_hit", "api", "", "maven", ""));
|
||||
return with_content_type(&path, data).into_response();
|
||||
}
|
||||
|
||||
@@ -60,7 +62,9 @@ async fn download(State(state): State<Arc<AppState>>, Path(path): Path<String>)
|
||||
"maven",
|
||||
"PROXY",
|
||||
));
|
||||
state.audit.log(AuditEntry::new("proxy_fetch", "api", "", "maven", ""));
|
||||
state
|
||||
.audit
|
||||
.log(AuditEntry::new("proxy_fetch", "api", "", "maven", ""));
|
||||
|
||||
let storage = state.storage.clone();
|
||||
let key_clone = key.clone();
|
||||
@@ -106,7 +110,9 @@ async fn upload(
|
||||
"maven",
|
||||
"LOCAL",
|
||||
));
|
||||
state.audit.log(AuditEntry::new("push", "api", "", "maven", ""));
|
||||
state
|
||||
.audit
|
||||
.log(AuditEntry::new("push", "api", "", "maven", ""));
|
||||
state.repo_index.invalidate("maven");
|
||||
StatusCode::CREATED
|
||||
}
|
||||
|
||||
@@ -49,7 +49,9 @@ async fn handle_request(State(state): State<Arc<AppState>>, Path(path): Path<Str
|
||||
"npm",
|
||||
"CACHE",
|
||||
));
|
||||
state.audit.log(AuditEntry::new("cache_hit", "api", "", "npm", ""));
|
||||
state
|
||||
.audit
|
||||
.log(AuditEntry::new("cache_hit", "api", "", "npm", ""));
|
||||
}
|
||||
return with_content_type(is_tarball, data).into_response();
|
||||
}
|
||||
@@ -69,7 +71,9 @@ async fn handle_request(State(state): State<Arc<AppState>>, Path(path): Path<Str
|
||||
"npm",
|
||||
"PROXY",
|
||||
));
|
||||
state.audit.log(AuditEntry::new("proxy_fetch", "api", "", "npm", ""));
|
||||
state
|
||||
.audit
|
||||
.log(AuditEntry::new("proxy_fetch", "api", "", "npm", ""));
|
||||
}
|
||||
|
||||
let storage = state.storage.clone();
|
||||
|
||||
@@ -116,7 +116,9 @@ async fn download_file(
|
||||
"pypi",
|
||||
"CACHE",
|
||||
));
|
||||
state.audit.log(AuditEntry::new("cache_hit", "api", "", "pypi", ""));
|
||||
state
|
||||
.audit
|
||||
.log(AuditEntry::new("cache_hit", "api", "", "pypi", ""));
|
||||
|
||||
let content_type = if filename.ends_with(".whl") {
|
||||
"application/zip"
|
||||
@@ -158,7 +160,9 @@ async fn download_file(
|
||||
"pypi",
|
||||
"PROXY",
|
||||
));
|
||||
state.audit.log(AuditEntry::new("proxy_fetch", "api", "", "pypi", ""));
|
||||
state
|
||||
.audit
|
||||
.log(AuditEntry::new("proxy_fetch", "api", "", "pypi", ""));
|
||||
|
||||
// Cache in local storage
|
||||
let storage = state.storage.clone();
|
||||
|
||||
@@ -36,7 +36,9 @@ async fn download(State(state): State<Arc<AppState>>, Path(path): Path<String>)
|
||||
state
|
||||
.activity
|
||||
.push(ActivityEntry::new(ActionType::Pull, path, "raw", "LOCAL"));
|
||||
state.audit.log(AuditEntry::new("pull", "api", "", "raw", ""));
|
||||
state
|
||||
.audit
|
||||
.log(AuditEntry::new("pull", "api", "", "raw", ""));
|
||||
|
||||
// Guess content type from extension
|
||||
let content_type = guess_content_type(&key);
|
||||
@@ -74,7 +76,9 @@ async fn upload(
|
||||
state
|
||||
.activity
|
||||
.push(ActivityEntry::new(ActionType::Push, path, "raw", "LOCAL"));
|
||||
state.audit.log(AuditEntry::new("push", "api", "", "raw", ""));
|
||||
state
|
||||
.audit
|
||||
.log(AuditEntry::new("push", "api", "", "raw", ""));
|
||||
StatusCode::CREATED.into_response()
|
||||
}
|
||||
Err(_) => StatusCode::INTERNAL_SERVER_ERROR.into_response(),
|
||||
|
||||
@@ -40,7 +40,6 @@ impl Role {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// API Token metadata stored on disk
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TokenInfo {
|
||||
@@ -260,7 +259,9 @@ mod tests {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let store = TokenStore::new(temp_dir.path());
|
||||
|
||||
let token = store.create_token("testuser", 30, None, Role::Write).unwrap();
|
||||
let token = store
|
||||
.create_token("testuser", 30, None, Role::Write)
|
||||
.unwrap();
|
||||
let (user, role) = store.verify_token(&token).unwrap();
|
||||
|
||||
assert_eq!(user, "testuser");
|
||||
@@ -291,7 +292,9 @@ mod tests {
|
||||
let store = TokenStore::new(temp_dir.path());
|
||||
|
||||
// Create token and manually set it as expired
|
||||
let token = store.create_token("testuser", 1, None, Role::Write).unwrap();
|
||||
let token = store
|
||||
.create_token("testuser", 1, None, Role::Write)
|
||||
.unwrap();
|
||||
let token_hash = hash_token(&token);
|
||||
let file_path = temp_dir.path().join(format!("{}.json", &token_hash[..16]));
|
||||
|
||||
@@ -330,7 +333,9 @@ mod tests {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let store = TokenStore::new(temp_dir.path());
|
||||
|
||||
let token = store.create_token("testuser", 30, None, Role::Write).unwrap();
|
||||
let token = store
|
||||
.create_token("testuser", 30, None, Role::Write)
|
||||
.unwrap();
|
||||
let token_hash = hash_token(&token);
|
||||
let hash_prefix = &token_hash[..16];
|
||||
|
||||
@@ -375,7 +380,9 @@ mod tests {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let store = TokenStore::new(temp_dir.path());
|
||||
|
||||
let token = store.create_token("testuser", 30, None, Role::Write).unwrap();
|
||||
let token = store
|
||||
.create_token("testuser", 30, None, Role::Write)
|
||||
.unwrap();
|
||||
|
||||
// First verification
|
||||
store.verify_token(&token).unwrap();
|
||||
@@ -391,7 +398,12 @@ mod tests {
|
||||
let store = TokenStore::new(temp_dir.path());
|
||||
|
||||
store
|
||||
.create_token("testuser", 30, Some("CI/CD Pipeline".to_string()), Role::Admin)
|
||||
.create_token(
|
||||
"testuser",
|
||||
30,
|
||||
Some("CI/CD Pipeline".to_string()),
|
||||
Role::Admin,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let tokens = store.list_tokens("testuser");
|
||||
|
||||
Reference in New Issue
Block a user