Skip to content

Add JWT authentication for Montonio Stargate API#28

Merged
rammrain merged 2 commits into
mainfrom
task-12
Apr 10, 2026
Merged

Add JWT authentication for Montonio Stargate API#28
rammrain merged 2 commits into
mainfrom
task-12

Conversation

@rammrain
Copy link
Copy Markdown
Member

@rammrain rammrain commented Apr 10, 2026

Summary

  • Add MontonioTokenProvider in ee.bitweb.montonio.sdk.auth — thread-safe JWT generation using com.auth0:java-jwt
  • GET requests: cached Bearer token in Authorization header (auto-renews 30s before expiry)
  • POST requests: request body serialized into JWT claims, sent as {"data":"<token>"} per Montonio API spec
  • Comprehensive unit tests covering token structure, claims, caching, renewal, thread safety, and signature verification

Closes #12
Follow-up: #27 (JWT webhook/return validation)

Test plan

  • All existing tests pass (no regressions)
  • MontonioTokenProviderTest — 18 tests covering auth tokens, data tokens, caching, thread safety, error handling
  • MontonioHttpClientTest — updated + 2 new tests for Bearer header on GET, JWT-wrapped body on POST
  • Full build green (./gradlew build)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • JWT-based authentication for API requests
    • GET requests use cached bearer tokens for improved performance
    • POST requests send per-request JWT-wrapped payloads
    • Token signing and verification integrated
  • Documentation

    • Added design doc describing JWT modes, caching and error handling
  • Tests

    • Comprehensive tests for auth/data tokens, caching, concurrency and HTTP behaviour

Implement token generation and request signing using com.auth0:java-jwt.
GET requests attach a cached Bearer token; POST requests wrap the
payload as signed JWT claims in {"data":"<token>"} per the Montonio API
spec. MontonioTokenProvider is thread-safe and supports injectable Clock
for testing.

Closes #12
Related: #27 (webhook/return JWT validation — follow-up)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 10, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8d4a9e2d-8021-4968-9aa7-50e2d191d105

📥 Commits

Reviewing files that changed from the base of the PR and between 9388802 and 575354c.

📒 Files selected for processing (4)
  • build.gradle
  • docs/plans/2026-04-10-jwt-authentication-design.md
  • src/main/java/ee/bitweb/montonio/sdk/http/MontonioHttpClient.java
  • src/test/java/ee/bitweb/montonio/sdk/auth/MontonioTokenProviderTest.java
✅ Files skipped from review due to trivial changes (1)
  • build.gradle

📝 Walkthrough
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarises the primary change: adding JWT authentication for the Montonio Stargate API, which is the core focus of all modifications.
Linked Issues check ✅ Passed All requirements from issue #12 are met: HS256 signing implemented, access key included in claims, configurable expiration supported, Bearer token attachment to requests, automatic renewal before expiry, and comprehensive unit tests added.
Out of Scope Changes check ✅ Passed All changes directly support JWT authentication implementation per issue #12: dependency addition, token provider class, HTTP client integration, design documentation, and test coverage. No unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch task-12

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
docs/plans/2026-04-10-jwt-authentication-design.md (1)

130-132: Minor clarification on getDataToken() synchronisation.

Line 132 states getDataToken() "only synchronizes if sharing mutable resources", but the implementation correctly doesn't synchronise at all since it has no shared mutable state. Consider simplifying to: "getDataToken() requires no synchronisation as it does not access shared state."

📝 Suggested clarification
 - `synchronized(this)` guards all reads/writes to `cachedToken` and `cachedTokenExpiry`
 - Token generation (HMAC-SHA256) takes microseconds, so lock contention is negligible
-- `getDataToken()` does not touch cached state, so it only synchronizes if sharing mutable resources
+- `getDataToken()` requires no synchronisation as it does not access shared state
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/plans/2026-04-10-jwt-authentication-design.md` around lines 130 - 132,
The sentence about getDataToken() should be simplified to state it requires no
synchronization because it does not access shared mutable state; update the line
referencing synchronized(this) / getDataToken() so it clearly says
getDataToken() requires no synchronization and does not touch cachedToken or
cachedTokenExpiry (remove the "only synchronizes if sharing mutable resources"
phrasing and replace with the explicit clarification).
src/main/java/ee/bitweb/montonio/sdk/http/MontonioHttpClient.java (1)

45-53: Consider extracting ObjectMapper creation to reduce duplication.

The ObjectMapper builder configuration is duplicated between lines 39-41 and 49-51. While minor, extracting this to a private static method would reduce maintenance burden if the configuration changes.

♻️ Suggested refactor
+    private static ObjectMapper createObjectMapper() {
+        return JsonMapper.builder()
+                .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
+                .build();
+    }
+
     MontonioHttpClient(MontonioSdkConfiguration configuration, HttpClient httpClient) {
         this.configuration = configuration;
         this.httpClient = httpClient;
-        this.objectMapper = JsonMapper.builder()
-                .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
-                .build();
+        this.objectMapper = createObjectMapper();
         this.tokenProvider = new MontonioTokenProvider(configuration, objectMapper);
     }

     MontonioHttpClient(MontonioSdkConfiguration configuration, HttpClient httpClient,
                        MontonioTokenProvider tokenProvider) {
         this.configuration = configuration;
         this.httpClient = httpClient;
-        this.objectMapper = JsonMapper.builder()
-                .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
-                .build();
+        this.objectMapper = createObjectMapper();
         this.tokenProvider = tokenProvider;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/ee/bitweb/montonio/sdk/http/MontonioHttpClient.java` around
lines 45 - 53, Extract the repeated ObjectMapper construction into a single
private static helper (e.g., createObjectMapper) and call it from the
MontonioHttpClient constructor and any other places where
JsonMapper.builder().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES).build()
is duplicated; update MontonioHttpClient to assign this.objectMapper =
createObjectMapper() and ensure the helper returns a configured ObjectMapper
built with JsonMapper.builder() and the DeserializationFeature setting so future
config changes are centralized.
src/test/java/ee/bitweb/montonio/sdk/auth/MontonioTokenProviderTest.java (1)

303-305: Consider adding awaitTermination() after shutdown() for cleaner test isolation.

executor.shutdown() only initiates an orderly shutdown but doesn't wait for tasks to complete. While doneLatch.await() ensures all tasks have finished their work, adding awaitTermination() is a defensive practice that ensures the executor is fully terminated before the test completes.

♻️ Suggested improvement
         startLatch.countDown();
         doneLatch.await();
         executor.shutdown();
+        assertTrue(executor.awaitTermination(5, java.util.concurrent.TimeUnit.SECONDS));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/test/java/ee/bitweb/montonio/sdk/auth/MontonioTokenProviderTest.java`
around lines 303 - 305, In MontonioTokenProviderTest, after calling
executor.shutdown() (in the test where startLatch/doneLatch are used), call
executor.awaitTermination(...) with a reasonable timeout (e.g., a few seconds)
and handle InterruptedException appropriately (re-interrupt or fail the test) to
ensure the executor is fully terminated before the test exits; optionally assert
executor.isTerminated() to enforce cleaner test isolation.
build.gradle (1)

33-33: Consider upgrading to com.auth0:java-jwt:4.5.0.

Version 4.4.0 exists and is functional; however, 4.5.0 is the current latest release on Maven Central. If your project maintains dependencies on recent stable versions, upgrading is recommended. For security advisories, use your project's built-in dependency scanning tools (such as Gradle Dependency Check or similar) to verify no known vulnerabilities affect either version.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@build.gradle` at line 33, The dependency declaration for com.auth0:java-jwt
is pinned to version 4.4.0; update the implementation coordinate to use the
newer stable release 4.5.0 by editing the implementation line that currently
reads "com.auth0:java-jwt:4.4.0" to "com.auth0:java-jwt:4.5.0" in build.gradle,
then run a Gradle refresh/resolve to verify compatibility and run your project's
tests to ensure no regressions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@build.gradle`:
- Line 33: The dependency declaration for com.auth0:java-jwt is pinned to
version 4.4.0; update the implementation coordinate to use the newer stable
release 4.5.0 by editing the implementation line that currently reads
"com.auth0:java-jwt:4.4.0" to "com.auth0:java-jwt:4.5.0" in build.gradle, then
run a Gradle refresh/resolve to verify compatibility and run your project's
tests to ensure no regressions.

In `@docs/plans/2026-04-10-jwt-authentication-design.md`:
- Around line 130-132: The sentence about getDataToken() should be simplified to
state it requires no synchronization because it does not access shared mutable
state; update the line referencing synchronized(this) / getDataToken() so it
clearly says getDataToken() requires no synchronization and does not touch
cachedToken or cachedTokenExpiry (remove the "only synchronizes if sharing
mutable resources" phrasing and replace with the explicit clarification).

In `@src/main/java/ee/bitweb/montonio/sdk/http/MontonioHttpClient.java`:
- Around line 45-53: Extract the repeated ObjectMapper construction into a
single private static helper (e.g., createObjectMapper) and call it from the
MontonioHttpClient constructor and any other places where
JsonMapper.builder().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES).build()
is duplicated; update MontonioHttpClient to assign this.objectMapper =
createObjectMapper() and ensure the helper returns a configured ObjectMapper
built with JsonMapper.builder() and the DeserializationFeature setting so future
config changes are centralized.

In `@src/test/java/ee/bitweb/montonio/sdk/auth/MontonioTokenProviderTest.java`:
- Around line 303-305: In MontonioTokenProviderTest, after calling
executor.shutdown() (in the test where startLatch/doneLatch are used), call
executor.awaitTermination(...) with a reasonable timeout (e.g., a few seconds)
and handle InterruptedException appropriately (re-interrupt or fail the test) to
ensure the executor is fully terminated before the test exits; optionally assert
executor.isTerminated() to enforce cleaner test isolation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8cce44e1-5d44-42ab-82aa-0d4ae89ee194

📥 Commits

Reviewing files that changed from the base of the PR and between d539078 and 9388802.

📒 Files selected for processing (6)
  • build.gradle
  • docs/plans/2026-04-10-jwt-authentication-design.md
  • src/main/java/ee/bitweb/montonio/sdk/auth/MontonioTokenProvider.java
  • src/main/java/ee/bitweb/montonio/sdk/http/MontonioHttpClient.java
  • src/test/java/ee/bitweb/montonio/sdk/auth/MontonioTokenProviderTest.java
  • src/test/java/ee/bitweb/montonio/sdk/http/MontonioHttpClientTest.java

Upgrade java-jwt to 4.5.0, extract ObjectMapper creation to static
helper, add awaitTermination to concurrent test, clarify getDataToken
synchronisation wording in design doc.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@rammrain rammrain merged commit 20059d3 into main Apr 10, 2026
5 checks passed
@rammrain rammrain deleted the task-12 branch April 10, 2026 08:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JWT authentication — token generation and request signing

1 participant