Skip to content
Merged
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
1 change: 0 additions & 1 deletion .env.local
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
DATABASE_URL=sqlite://storage/database/data.db?mode=rwc
TORRUST_INDEX_CONFIG_TOML=
TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__SECRET_KEY=MaxVerstappenWC2021
USER_ID=1000
TORRUST_TRACKER_CONFIG_TOML=
TORRUST_TRACKER_CONFIG_OVERRIDE_CORE__DATABASE__DRIVER=Sqlite3
Expand Down
60 changes: 60 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,67 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- 188 crate-level tests for the domain error system (`src/tests/errors/`):
status-code mapping, display messages, `From` impl coverage, and
`ApiError` delegation (ADR-T-006 §1–§4).
- ADR-T-007: Document rationale for JWT system refactor.
- Centralised JWT module (`src/jwt.rs`) consolidating all `jsonwebtoken` usage:
key loading, signing, verification, and algorithm configuration.
- `SessionClaims` with RFC 7519 registered claims (`sub`, `iss`, `aud`, `iat`,
`exp`) plus advisory `role`, `username`, and revocation `gen` fields.
- `VerifyClaims` with `aud: "email-verification"` for purpose separation.
- RSA key pair configuration: `auth.private_key_path` / `auth.public_key_path`
(or inline PEM via `auth.private_key_pem` / `auth.public_key_pem`).
- Ephemeral auto-generated RSA-2048 key pair when no keys are configured.
Sessions do not survive server restarts with ephemeral keys. Deployers who
want persistent sessions supply their own key pair via config.
- `torrust-generate-auth-keypair` CLI binary for generating RSA-2048 key pairs.
Outputs both PEM blocks to stdout; refuses to run if stdout is a terminal.
- Container auto-generation of persistent auth keys on first boot. The entry
script runs `torrust-generate-auth-keypair` and writes the PEM files to
`/etc/torrust/index/auth/` on the volume. Sessions survive restarts with no
manual setup.
- `kid` (Key ID) header in every JWT for future key rotation support.
- Configurable token lifetimes: `auth.session_token_lifetime_secs` (default:
2 weeks) and `auth.email_verification_token_lifetime_secs` (default: ~10 years).
- `token_generation` column on `torrust_users` (migration for SQLite and MySQL).
- Token revocation: password changes, role changes (admin grant), and bans
increment `token_generation`; tokens with an older `gen` claim are rejected.
- Consolidated session validation: `JsonWebToken::validate_session` is the
sole entry point for verifying a session JWT, checking the token-generation
counter, and rejecting banned users. All callers delegate here.
- `BearerToken` extractor rejects missing/malformed `Authorization` headers at
the extraction boundary (`AuthError::TokenNotFound` / `AuthError::TokenInvalid`).
- `ExtractOptionalLoggedInUser` catches extraction rejection and returns `None`
for anonymous requests.
- `AuthError::TokenRevoked` variant for revoked-token responses.
- Crate tests for the JWT module (session + email-verification round-trips,
audience cross-contamination, tampered/garbage tokens).
- Crate tests for `parse_token` (valid extraction, whitespace trimming,
empty bearer, missing prefix, non-ASCII rejection).

### Changed

- **BREAKING:** JWT signing algorithm changed from HMAC-HS256 to RS256
(RSA + SHA-256). Existing HS256 tokens are invalidated; users must re-login.
- **BREAKING:** JWT claims redesigned from `UserClaims { user, exp }` to
`SessionClaims { sub, iss, aud, iat, exp, role, username, gen }`. Existing
tokens without the new claims fail deserialization.
- **BREAKING:** Configuration keys changed — `auth.user_claim_token_pepper` /
`auth.session_signing_key` / `auth.email_verification_signing_key` replaced
by `auth.private_key_path` and `auth.public_key_path` (or inline PEM).
Deployers must generate an RSA key pair.
- **BREAKING:** Replace `ServiceError` (41 variants) and `ServiceResult` with
domain-scoped error enums: `AuthError`, `UserError`, `TorrentError`,
`CategoryTagError`, and a thin `ApiError` wrapper (ADR-T-006).
- `Authentication::get_user_id_from_bearer_token` now takes `BearerToken`
directly instead of `Option<BearerToken>`.
- `ExtractLoggedInUser` and `ExtractOptionalLoggedInUser` use `BearerToken`
directly instead of the old `Extract` wrapper.
- `parse_token` returns `Result` instead of panicking on malformed headers.
- JWT `exp` validation relies solely on the `jsonwebtoken` library; redundant
manual expiration check removed.
- Token signing uses `Result` propagation instead of `.unwrap()` / `.expect()`.
- `UserClaims` is now a type alias for `SessionClaims` (backward-compatible).
- `VerifyClaims` moved from `mailer` into the `jwt` module (re-exported for
backward compatibility).
- Service functions now return domain-specific `Result<T, DomainError>` instead
of `Result<T, ServiceError>`.
- Each domain error co-locates its HTTP status-code mapping via a
Expand All @@ -28,6 +83,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Removed

- `bearer_token::Extract` wrapper struct (replaced by `BearerToken` directly).
- `get_optional_logged_in_user` free function (logic moved into extractors).
- `get_claims_from_bearer_token` private method on `Authentication` (inlined).
- `ClaimTokenPepper` / `JwtSigningSecret` / `user_claim_token_pepper` config
keys (replaced by RSA key pair configuration).
- `ServiceError` enum and `ServiceResult` type alias from `src/errors.rs`.
- `http_status_code_for_service_error` and `map_database_error_to_service_error`
helper functions.
Expand Down
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ pbkdf2 = { version = "0", features = ["simple"] }
pin-project-lite = "0"
rand = "0.10"
regex = "1"
rsa = { version = "0.9", default-features = false, features = ["std", "pem"] }
reqwest = { version = "0", features = ["json", "multipart", "query"] }
serde = { version = "1", features = ["derive", "rc"] }
serde_bencode = "0"
Expand All @@ -88,6 +89,7 @@ serde_derive = "1"
serde_json = "1"
serde_with = "3"
sha-1 = "0"
sha2 = "0"
sqlx = { version = "0", features = ["migrate", "mysql", "runtime-tokio-native-tls", "sqlite", "time"] }
tera = { version = "1", default-features = false }
text-colorizer = "1"
Expand All @@ -102,6 +104,10 @@ url = { version = "2", features = ["serde"] }
urlencoding = "2"
uuid = { version = "1", features = ["v4"] }

[[bin]]
name = "torrust-generate-auth-keypair"
path = "src/bin/generate_auth_keypair.rs"

[dev-dependencies]
tempfile = "3"
which = "8"
Expand Down
7 changes: 5 additions & 2 deletions Containerfile
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@ COPY --from=build_debug \
RUN cargo nextest run --workspace-remap /test/src/ --extract-to /test/src/ --no-run --archive-file /test/torrust-index-debug.tar.zst
RUN cargo nextest run --workspace-remap /test/src/ --target-dir-remap /test/src/target/ --cargo-metadata /test/src/target/nextest/cargo-metadata.json --binaries-metadata /test/src/target/nextest/binaries-metadata.json

RUN mkdir -p /app/bin/; cp -l /test/src/target/debug/torrust-index /app/bin/torrust-index
RUN mkdir -p /app/bin/; \
cp -l /test/src/target/debug/torrust-index /app/bin/torrust-index; \
cp -l /test/src/target/debug/torrust-generate-auth-keypair /app/bin/torrust-generate-auth-keypair
# RUN mkdir /app/lib/; cp -l $(realpath $(ldd /app/bin/torrust-index | grep "libz\.so\.1" | awk '{print $3}')) /app/lib/libz.so.1
RUN chown -R root:root /app; chmod -R u=rw,go=r,a+X /app; chmod -R a+x /app/bin

Expand All @@ -87,7 +89,8 @@ RUN cargo nextest run --workspace-remap /test/src/ --target-dir-remap /test/src/

RUN mkdir -p /app/bin/; \
cp -l /test/src/target/release/torrust-index /app/bin/torrust-index; \
cp -l /test/src/target/release/health_check /app/bin/health_check;
cp -l /test/src/target/release/health_check /app/bin/health_check; \
cp -l /test/src/target/release/torrust-generate-auth-keypair /app/bin/torrust-generate-auth-keypair
# RUN mkdir -p /app/lib/; cp -l $(realpath $(ldd /app/bin/torrust-index | grep "libz\.so\.1" | awk '{print $3}')) /app/lib/libz.so.1
RUN chown -R root:root /app; chmod -R u=rw,go=r,a+X /app; chmod -R a+x /app/bin

Expand Down
28 changes: 22 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,19 +93,34 @@ _Optionally, you may choose to supply the entire configuration as an environment
TORRUST_INDEX_CONFIG_TOML=$(cat "./storage/index/etc/index.toml") cargo run
```

_For deployment, you __should__ override:

- The `tracker_api_token` and the `index_auth_secret_key` by using environmental variables:_
_For deployment, you __should__ override the `tracker_api_token`:_

```sh
# Please use the secret that you generated for the torrust-tracker configuration.
# Override secret in configuration using an environmental variable
# Override secrets in configuration using environmental variables
TORRUST_INDEX_CONFIG_TOML=$(cat "./storage/index/etc/index.toml") \
TORRUST_INDEX_CONFIG_OVERRIDE_TRACKER__TOKEN=$(cat "./storage/tracker/lib/tracker_api_admin_token.secret") \
TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__SECRET_KEY="MaxVerstappenWC2021" \
cargo run
```

_By default, an ephemeral RSA key pair is auto-generated in memory for JWT
signing. Sessions will not survive server restarts. For **persistent sessions**,
generate your own RSA key pair and configure the paths:_

```sh
# Generate an RSA key pair for JWT signing:
openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -pubout -out public.pem

# Supply key paths via environment variables:
TORRUST_INDEX_CONFIG_TOML=$(cat "./storage/index/etc/index.toml") \
TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__PRIVATE_KEY_PATH="/path/to/private.pem" \
TORRUST_INDEX_CONFIG_OVERRIDE_AUTH__PUBLIC_KEY_PATH="/path/to/public.pem" \
cargo run
```

> **Container deployments** auto-generate persistent keys on first boot — no
> manual setup is required. See the [container guide][containers.md] for details.

> Please view our [crate documentation][docs] for more detailed instructions.

### Services
Expand All @@ -127,6 +142,7 @@ The following services are provided by the default configuration:
- [ADR-T-004: Remove `located-error` Package](adr/004-remove-located-error.md) — Replace the `torrust-index-located-error` wrapper with `tracing` for error context.
- [ADR-T-005: Migrate to Rust Edition 2024](adr/005-edition-2024.md) — Migrate the entire workspace to `edition = "2024"` and raise the MSRV to 1.85.
- [ADR-T-006: Refactor the Error System](adr/006-error-system-refactor.md) — Replace the 41-variant `ServiceError` god enum with domain-scoped error enums (`AuthError`, `UserError`, `TorrentError`, `CategoryTagError`) and a thin `ApiError` wrapper.
- [ADR-T-007: Refactor the JWT System](adr/007-jwt-system-refactor.md) — Centralise JWT handling into `src/jwt.rs`, redesign claims to RFC 7519, move to RS256 asymmetric signing, and consolidate session validation into a single code path.

## Contributing

Expand Down
Loading
Loading