diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..0f4376d --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,40 @@ +# GitHub Copilot Instructions + +This repository implements a NIP-42 authentication proxy for Nostr relays. It adds client authentication support to upstream relays (like strfry) that don't natively support it. + +## Guidelines + +- Use Conventional Commits for titles and commit messages (e.g., `feat(scope): message`). +- Ensure pull requests include a clear description and test results. +- Reference related issues using `Closes #123` when applicable. +- Run `mvn -q verify` before committing code. +- Document new features in the README or related docs. +- Maintain Java 21 compatibility and update `pom.xml` for new dependencies. +- Remove unused imports. + +## Relevant Nostr NIPs + +When implementing features, consult the relevant NIP specifications: + +- [NIP-01](https://github.com/nostr-protocol/nips/blob/master/01.md) - Basic protocol flow (events, subscriptions, messages) +- [NIP-42](https://github.com/nostr-protocol/nips/blob/master/42.md) - Client authentication (core to this proxy) +- [NIP-04](https://github.com/nostr-protocol/nips/blob/master/04.md) - Encrypted direct messages (protected kind) +- [NIP-17](https://github.com/nostr-protocol/nips/blob/master/17.md) - Private direct messages (protected kinds 14, 15) +- [NIP-46](https://github.com/nostr-protocol/nips/blob/master/46.md) - Nostr Connect / remote signing (protected kind 24133) +- [NIP-47](https://github.com/nostr-protocol/nips/blob/master/47.md) - Wallet Connect (protected kinds 23194-23197) +- [NIP-59](https://github.com/nostr-protocol/nips/blob/master/59.md) - Gift wraps (protected kinds 13, 1059) + +## Architecture + +``` +Client (port 7777) ←→ NIP-42 Auth Proxy ←→ Upstream Relay (strfry @ 7778) +``` + +Key components: +- `auth/` - NIP-42 authentication and BIP-340 signature verification +- `session/` - WebSocket session management and state tracking +- `access/` - Access control (open, allowlist, blocklist modes) +- `handler/` - WebSocket message handling and upstream routing +- `upstream/` - Upstream relay client connections + +These instructions help GitHub Copilot produce code that respects the repository's conventions and protocol requirements. \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..5de893e --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,28 @@ +version: 2 + +# Private registries used by Dependabot +registries: + maven-releases: + type: maven-repository + url: https://maven.398ja.xyz/releases + username: ${{secrets.MVN_USER}} + password: ${{secrets.MVN_PASSWORD}} + maven-snapshots: + type: maven-repository + url: https://maven.398ja.xyz/snapshots + username: ${{secrets.MVN_USER}} + password: ${{secrets.MVN_PASSWORD}} + +updates: + - package-ecosystem: "maven" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 5 + target-branch: "develop" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 5 + target-branch: "develop" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..2fb9275 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,17 @@ +## Summary + + +## What changed? + + +## Breaking changes +- [ ] BREAKING: this change introduces breaking API or behavior + +## Review focus + + +## Checklist +- [ ] Tests added or updated +- [ ] `mvn -q verify` passes +- [ ] Documentation updated (README, docs, etc.) +- [ ] No unused imports \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..dad8912 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,46 @@ +name: CI + +on: + push: + branches: [main, develop] + pull_request: + branches: [main, develop] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-java@v5 + with: + java-version: '21' + distribution: 'temurin' + cache: 'maven' + - name: Build with Maven + run: mvn -q verify + - name: Upload surefire reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: surefire-reports + path: '**/target/surefire-reports' + if-no-files-found: ignore + - name: Upload JaCoCo coverage + if: always() + uses: actions/upload-artifact@v4 + with: + name: jacoco-exec + path: '**/target/jacoco.exec' + if-no-files-found: ignore + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v5 + with: + files: '**/target/site/jacoco/jacoco.xml' + token: ${{ secrets.CODECOV_TOKEN }} + - name: Upload test results to Codecov + if: ${{ !cancelled() }} + uses: codecov/test-results-action@v1 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: '**/target/surefire-reports/*.xml' + diff --git a/.github/workflows/enforce_conventional_commits.yml b/.github/workflows/enforce_conventional_commits.yml new file mode 100644 index 0000000..89dc3bd --- /dev/null +++ b/.github/workflows/enforce_conventional_commits.yml @@ -0,0 +1,29 @@ +name: Conventional Commits + +permissions: + contents: read + pull-requests: read + +on: + pull_request: + branches: + - main + - develop + +jobs: + commit-lint: + name: Verify Conventional Commits + if: (github.event_name == 'pull_request' && !startsWith(github.event.pull_request.head.ref, 'release-please--')) + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Check Commit Messages + uses: wagoid/commitlint-github-action@v6 + with: + configFile: .commitlintrc.yml + token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/google-java-format.yml b/.github/workflows/google-java-format.yml new file mode 100644 index 0000000..536a472 --- /dev/null +++ b/.github/workflows/google-java-format.yml @@ -0,0 +1,26 @@ +name: Format + +on: + pull_request: + branches: + - main + +permissions: + contents: write + +jobs: + + formatting: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 # v2 minimum required + - name: Set up JDK 21 + uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: '21' + - uses: axel-op/googlejavaformat-action@v4 + with: + args: "--replace" + # Recommended if you use MacOS: + github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/qodana.yml b/.github/workflows/qodana.yml new file mode 100644 index 0000000..82fee59 --- /dev/null +++ b/.github/workflows/qodana.yml @@ -0,0 +1,28 @@ +name: Qodana +on: + workflow_dispatch: + pull_request: + push: + branches: + - main + - develop + +jobs: + qodana: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + checks: write + steps: + - uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.sha }} # to check out the actual pull request commit, not the merge commit + fetch-depth: 0 # a full history is required for pull request analysis + - name: 'Qodana Scan' + uses: JetBrains/qodana-action@v2025.2 + with: + pr-mode: false + env: + QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }} + QODANA_ENDPOINT: 'https://qodana.cloud' \ No newline at end of file diff --git a/.gitignore b/.gitignore index 95a79dc..d5781da 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,4 @@ logs/ *.swp *.swo *~ +/.claude/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b5fb607 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,46 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Fixed + +- Add null checks for upstream response body in NIP-11 handlers +- Use system Maven instead of wrapper in CI workflow + +### Changed + +- Update `actions/setup-java` to v5 in google-java-format workflow + +### Security + +- Upgrade Spring Boot 3.5.5 → 3.5.8 (fixes CVE-2025-41249, CVE-2025-41254, CVE-2025-55754, CVE-2025-11226) +- Upgrade commons-lang3 3.17.0 → 3.18.0 (fixes CVE-2025-48924) +- Upgrade commons-compress 1.24.0 → 1.27.1 (fixes CVE-2024-25710, CVE-2024-26308) + +## [0.1.1] - 2026-01-06 + +### Fixed + +- Enforce auth requirement for REQ messages when `require-auth=true` + +## [0.1.0] - 2026-01-06 + +### Added + +- Initial standalone nostr-auth-proxy project +- NIP-42 client authentication with BIP-340 signature verification +- WebSocket proxy to upstream Nostr relays (strfry) +- Session management with per-pubkey connection limits +- Access control modes: open, allowlist, blocklist +- Protection for privacy-sensitive event kinds (DMs, wallet data, etc.) +- Spring Boot Actuator health checks and Prometheus metrics +- Docker image support via Jib + +[Unreleased]: https://github.com/tcheeric/nostr-auth-proxy/compare/v0.1.1...HEAD +[0.1.1]: https://github.com/tcheeric/nostr-auth-proxy/compare/v0.1.0...v0.1.1 +[0.1.0]: https://github.com/tcheeric/nostr-auth-proxy/releases/tag/v0.1.0 diff --git a/pom.xml b/pom.xml index 2461547..f817011 100644 --- a/pom.xml +++ b/pom.xml @@ -8,13 +8,13 @@ org.springframework.boot spring-boot-starter-parent - 3.5.5 + 3.5.8 xyz.tcheeric nostr-auth-proxy - 0.1.0 + 0.1.1 jar Nostr Auth Proxy @@ -26,8 +26,21 @@ 1.81 1.20.4 4.3.0 + + 3.18.0 + + + + + org.apache.commons + commons-compress + 1.27.1 + + + + reposilite-releases diff --git a/src/main/java/xyz/tcheeric/nostr/authproxy/filter/Nip11FilterConfig.java b/src/main/java/xyz/tcheeric/nostr/authproxy/filter/Nip11FilterConfig.java index 66c61c2..12cb6b4 100644 --- a/src/main/java/xyz/tcheeric/nostr/authproxy/filter/Nip11FilterConfig.java +++ b/src/main/java/xyz/tcheeric/nostr/authproxy/filter/Nip11FilterConfig.java @@ -111,9 +111,10 @@ private void handleNip11Request(HttpServletResponse response) throws IOException request, String.class); + String body = upstreamResponse.getBody(); response.setContentType("application/nostr+json"); response.setStatus(HttpServletResponse.SC_OK); - response.getWriter().write(upstreamResponse.getBody()); + response.getWriter().write(body != null ? body : "{}"); response.getWriter().flush(); log.info("nip11_response_sent"); diff --git a/src/main/java/xyz/tcheeric/nostr/authproxy/handler/Nip11HandshakeInterceptor.java b/src/main/java/xyz/tcheeric/nostr/authproxy/handler/Nip11HandshakeInterceptor.java index 4363622..9ac1e33 100644 --- a/src/main/java/xyz/tcheeric/nostr/authproxy/handler/Nip11HandshakeInterceptor.java +++ b/src/main/java/xyz/tcheeric/nostr/authproxy/handler/Nip11HandshakeInterceptor.java @@ -83,8 +83,9 @@ private void handleNip11Request(ServerHttpResponse response) throws IOException request, String.class); + String body = upstreamResponse.getBody(); response.getHeaders().set("Content-Type", "application/nostr+json"); - response.getBody().write(upstreamResponse.getBody().getBytes()); + response.getBody().write(body != null ? body.getBytes() : "{}".getBytes()); response.getBody().flush(); log.info("nip11_response_sent");