Skip to content

Add authorization and credential state check on refresh grant - #4861

Merged
thiva-k merged 1 commit into
thunder-id:mainfrom
thiva-k:add-refresh-authz
Aug 12, 2026
Merged

Add authorization and credential state check on refresh grant#4861
thiva-k merged 1 commit into
thunder-id:mainfrom
thiva-k:add-refresh-authz

Conversation

@thiva-k

@thiva-k thiva-k commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Purpose

A refresh re-minted tokens carrying stale scopes: unassigning a role, deleting it, or stripping its permissions had no effect until the refresh token expired. A password reset or a client secret rotation also left existing refresh tokens usable.

Approach

  • Re-evaluate permission scopes against the subject's current role and group assignments on every refresh, dropping the ones they no longer hold. OIDC scopes are not affected.
  • Record credentialUpdatedAt in the entity's system attributes when a password or client secret changes, and reject a refresh token established at or before that instant. The marker is written in the same transaction as the credential, and carried across by the application, agent, and user paths that rebuild the blob.
  • Reject the refresh when the subject no longer resolves to an entity and the client maps no subject attribute.

Scoped to the refresh grant, so other grants are unaffected. Already-issued access tokens still live out their TTL, and applications that map sub to a user attribute skip the subject-derived checks.

Related Issues

  • N/A

Related PRs

  • N/A

Checklist

  • Followed the contribution guidelines.
  • Manual test round performed and verified.
  • Documentation provided. (Add links if there are any)
    • Ran Vale and fixed all errors and warnings
  • Tests provided. (Add links if there are any)
    • Unit Tests
    • Integration Tests
  • Breaking changes. (Fill if applicable)
    • Breaking changes section filled.
    • breaking change label added.

Security checks

  • Followed secure coding standards in WSO2 Secure Coding Guidelines
  • Confirmed that this PR doesn't commit any keys, passwords, tokens, usernames, or other secrets.

Summary by CodeRabbit

  • New Features

    • Refresh-token requests now re-evaluate current permissions and may reduce authorization scopes.
    • Refresh tokens are rejected after password resets, client-secret rotations, or user deletion.
    • Credential changes are tracked to reliably invalidate earlier refresh tokens.
  • Documentation

    • Updated OAuth, token, and application guides to explain scope reduction and refresh-token invalidation.
  • Tests

    • Added comprehensive coverage for permission changes, credential updates, deleted users, and client-secret rotations.

@thiva-k thiva-k added Type/Improvement trigger-pr-builder Add when the PR is ready for CI; starts the PR Builder for this and all later pushes labels Aug 11, 2026
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@thiva-k, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 50 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d85e904a-d48c-4a69-9e6f-1df79cde6d9e

📥 Commits

Reviewing files that changed from the base of the PR and between 9c6b9a3 and dd2236d.

📒 Files selected for processing (12)
  • backend/internal/authnprovider/common/constants.go
  • backend/internal/entity/service.go
  • backend/internal/entity/service_test.go
  • backend/internal/oauth/oauth2/granthandlers/provider.go
  • backend/internal/oauth/oauth2/granthandlers/refresh_token.go
  • backend/internal/oauth/oauth2/granthandlers/refresh_token_test.go
  • docs/content/guides/applications/application-settings.mdx
  • docs/content/guides/applications/manage-applications.mdx
  • docs/content/guides/protocols/oauth-oidc/refresh-token.mdx
  • docs/content/key-concepts/tokens.mdx
  • tests/integration/oauth/token/refresh_token_test.go
  • tests/integration/testutils/api_utils.go
📝 Walkthrough

Walkthrough

This change records credential-update timestamps, preserves them during entity updates, and validates them during refresh-token grants. Refresh-token scopes are re-evaluated against current roles and groups. Unit tests, integration tests, API helpers, and OAuth documentation were updated.

Changes

Credential and refresh-token security

Layer / File(s) Summary
Credential marker storage
backend/internal/authnprovider/common/constants.go, backend/internal/entity/service.go, backend/internal/entity/service_test.go
Credential updates record UTC timestamps. Entity and system-attribute replacements preserve the credential marker. Passkey and flow-secret updates do not stamp it.
Refresh-token validation and authorization
backend/internal/oauth/oauth2/granthandlers/provider.go, backend/internal/oauth/oauth2/granthandlers/refresh_token.go
Refresh-token grants reject tokens issued before password or client-secret changes. They resolve subjects and filter permission scopes using current authorization and group memberships.
Refresh security validation
backend/internal/oauth/oauth2/granthandlers/refresh_token_test.go, tests/integration/oauth/token/refresh_token_test.go, tests/integration/testutils/api_utils.go
Tests cover scope narrowing, credential invalidation, subject handling, provider failures, and end-to-end refresh behavior. Integration helpers update roles, credentials, applications, and assignments.
Refresh-token documentation
docs/content/guides/applications/*.mdx, docs/content/guides/protocols/oauth-oidc/refresh-token.mdx, docs/content/key-concepts/tokens.mdx
Documentation describes refresh-time authorization, reduced permission scopes, and invalidation after credential changes or user deletion.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant RefreshTokenGrantHandler
  participant EntityService
  participant ActorProvider
  participant AuthorizationProvider
  Client->>RefreshTokenGrantHandler: Submit refresh token
  RefreshTokenGrantHandler->>EntityService: Check subject and client credential timestamps
  EntityService-->>RefreshTokenGrantHandler: Return credential metadata
  RefreshTokenGrantHandler->>ActorProvider: Resolve subject groups
  ActorProvider-->>RefreshTokenGrantHandler: Return current groups
  RefreshTokenGrantHandler->>AuthorizationProvider: Re-evaluate permission scopes
  AuthorizationProvider-->>RefreshTokenGrantHandler: Return authorized scopes
  RefreshTokenGrantHandler-->>Client: Issue filtered tokens or invalid_grant
Loading

Possibly related PRs

Suggested reviewers: thamindudilshan, darshanasbg, thumulaperera

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: authorization and credential-state checks for the refresh grant.
Description check ✅ Passed The description covers purpose, approach, scope, testing, documentation, security checks, and related sections with sufficient detail.
Docstring Coverage ✅ Passed Docstring coverage is 89.47% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (6)
backend/internal/oauth/oauth2/granthandlers/refresh_token.go (2)

598-626: 🚀 Performance & Scalability | 🔵 Trivial

The refresh path now makes up to five backend calls per request.

Each refresh adds GetActor for the subject, optionally GetInboundClientByID, optionally GetActor for the client, GetActorGroups, and EvaluateAccessBatch. Refresh is a high-frequency endpoint, and these calls are serial and on the request thread.

Consider adding a short-TTL cache for the credential marker and group memberships, and track latency and error rates for these two providers so a slow authorization engine is visible before it degrades refresh throughput. The TTL bounds how long a revoked permission stays effective, so pick it against the security requirement.

No change is required in this PR.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/internal/oauth/oauth2/granthandlers/refresh_token.go` around lines
598 - 626, No implementation change is required for this comment; leave the
refresh flow around GetActorGroups and EvaluateAccessBatch unchanged.

464-466: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Fail closed or remove the nil guard

The supported engine and server paths initialize actorProvider before OAuth setup, and New rejects a nil provider. This is not a reachable production wiring regression. If the defensive guard remains, return server_error instead of silently bypassing credential, subject, and scope checks.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/internal/oauth/oauth2/granthandlers/refresh_token.go` around lines
464 - 466, Update the actorProvider handling in the OAuth refresh flow: either
remove the unreachable nil guard, or replace its silent nil return with a
server_error response. Preserve the credential, subject, and scope validation
path whenever actorProvider is available.

Source: Learnings

backend/internal/oauth/oauth2/granthandlers/refresh_token_test.go (1)

2180-2185: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Avoid Require inside a MatchedBy callback.

suite.Require().NotEmpty inside the matcher calls runtime.Goexit on failure, from inside mock argument matching. Testify may invoke a matcher more than once when several expectations compete, and a failure there produces a confusing stack rather than a clean assertion failure. Only one expectation is registered here, so the current behavior is fine.

Capture the request and assert after HandleGrant returns.

♻️ Proposed refactor
 	var evaluatedGroupIDs []string
+	var capturedEvaluations []providers.AccessEvaluationRequest
 	suite.mockAuthzService.On("EvaluateAccessBatch", mock.Anything, mock.MatchedBy(
 		func(req providers.AccessEvaluationsRequest) bool {
-			suite.Require().NotEmpty(req.Evaluations)
-			evaluatedGroupIDs = req.Evaluations[0].Subject.GroupIDs
+			capturedEvaluations = req.Evaluations
 			return true
 		})).Return(allowAllEvaluations, nil)

Then after the call:

suite.Require().NotEmpty(capturedEvaluations)
evaluatedGroupIDs = capturedEvaluations[0].Subject.GroupIDs
assert.Equal(suite.T(), []string{"group-1", "group-2"}, evaluatedGroupIDs)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/internal/oauth/oauth2/granthandlers/refresh_token_test.go` around
lines 2180 - 2185, Update the EvaluateAccessBatch matcher to only capture
req.Evaluations without calling suite.Require inside MatchedBy; after
HandleGrant returns, assert the captured evaluations are non-empty, assign
evaluatedGroupIDs from the first evaluation, and perform the expected group ID
assertion there.
tests/integration/oauth/token/refresh_token_test.go (2)

756-778: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Reuse updateApplication instead of duplicating the payload.

ts.updateApplication at lines 530-554 builds exactly this payload. The only difference is the secret value, which is already a parameter. The duplicate block will drift from the helper when the application shape changes.

♻️ Proposed refactor
-	ts.Require().NoError(testutils.UpdateApplication(appID, testutils.Application{
-		ID:                        appID,
-		Name:                      "RefreshSecurityRotateApp",
-		Description:               "Application for refresh token security integration tests",
-		OUID:                      ts.ouID,
-		Type:                      "fullstack",
-		AuthFlowID:                ts.authFlowID,
-		IsRegistrationFlowEnabled: false,
-		AllowedUserTypes:          []string{refreshSecUserType},
-		InboundAuthConfig: []map[string]interface{}{
-			{
-				"type": "oauth2",
-				"config": map[string]interface{}{
-					"clientId":                rotateClientID,
-					"clientSecret":            "rotated-secret-value",
-					"redirectUris":            []string{refreshSecRedirectURI},
-					"grantTypes":              []string{"authorization_code", "refresh_token"},
-					"responseTypes":           []string{"code"},
-					"tokenEndpointAuthMethod": "client_secret_basic",
-				},
-			},
-		},
-	}), "Failed to rotate client secret")
+	ts.Require().NoError(
+		ts.updateApplication(appID, "RefreshSecurityRotateApp", rotateClientID, "rotated-secret-value"),
+		"Failed to rotate client secret")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/integration/oauth/token/refresh_token_test.go` around lines 756 - 778,
Replace the duplicated testutils.UpdateApplication payload in the refresh-token
rotation test with the existing ts.updateApplication helper, passing the rotated
client secret value as its parameter. Preserve the current application update
behavior and error assertion while reusing the helper’s shared payload
construction.

728-808: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

These tests depend on the <= comparison in credentialChangedSince.

The token grant and the credential change happen within the same wall-clock second in a fast test run. credentialChangedSince compares iat <= changedAt.Unix(), so a same-second change still rejects the token and the tests are deterministic. If that comparison ever changes to a strict <, these three tests become flaky rather than failing outright.

No change is required. The coupling is worth a short comment next to the assertions so a future change to the comparison does not get diagnosed as test flakiness.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/integration/oauth/token/refresh_token_test.go` around lines 728 - 808,
The refresh-token rejection tests rely on same-second credential changes being
rejected by the inclusive comparison in credentialChangedSince. Add a concise
comment near the assertions in TestRefresh_PasswordReset_RejectsToken,
TestRefresh_ClientSecretRotated_RejectsToken, and
TestRefresh_ClientSecretRotatedThenAppRenamed_StillRejectsToken documenting this
dependency and warning that changing <= to < would make them timing-sensitive.
tests/integration/testutils/api_utils.go (1)

2044-2051: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

A single marshal produces the same payload.

credentials is a map[string]string, so wrapping it directly removes one marshal call and one error branch.

♻️ Proposed simplification
-	credsJSON, err := json.Marshal(credentials)
-	if err != nil {
-		return fmt.Errorf("failed to marshal credentials: %w", err)
-	}
-	payload, err := json.Marshal(map[string]json.RawMessage{"credentials": credsJSON})
+	payload, err := json.Marshal(map[string]interface{}{"credentials": credentials})
 	if err != nil {
 		return fmt.Errorf("failed to marshal credential update request: %w", err)
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/integration/testutils/api_utils.go` around lines 2044 - 2051, In the
credential update request construction, remove the intermediate credsJSON
marshal and pass credentials directly as the map value in the payload used by
the surrounding request flow. Eliminate the associated first error branch while
preserving the existing error handling for marshaling the final payload.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/internal/application/service.go`:
- Around line 466-478: Prevent stale full-blob system-attribute writes from
overwriting credentialUpdatedAt by updating the application system-attribute
path around GetEntity/buildSystemAttributes to use an atomic marker-preserving
merge or the same serialization used by credential updates; apply the identical
mechanism to the agent update path in backend/internal/agent/service.go lines
300-301, so both paths retain markers committed concurrently.

In `@backend/internal/oauth/oauth2/granthandlers/refresh_token.go`:
- Around line 590-596: The reauthorizeScopes guard must treat an entity with an
empty ID like an unresolved subject, and the fixture must provide real IDs. In
refresh_token.go, extend reauthorizeScopes’s early-return condition to include
subjectEntity.ID == ""; in refresh_token_test.go, update entityMarkedAt to
accept an id parameter, assign it to providers.Entity.ID, and pass matching IDs
at all three call sites.
- Around line 566-586: Update credentialChangedSince to accept ctx and the
logger, and log malformed SystemAttributes JSON, non-string or empty
credentialUpdatedAt values, and invalid RFC3339 timestamps; keep genuinely
absent markers silent and preserve the existing boolean behavior. Update all
callers to pass the required context and logger, then add tests covering absent,
malformed JSON, invalid type/value, and invalid timestamp cases.

In `@docs/content/guides/protocols/oauth-oidc/refresh-token.mdx`:
- Around line 48-59: Update the refresh-token behavior table and surrounding
explanation to qualify the user-deletion and permission-scope rows: these
effects apply when the subject resolves to an entity, but not when the client
maps `sub` to a user attribute and `resolveSubjectEntity` skips resolution.
State that refresh succeeds with unchanged scopes in that configuration, and
verify the wording against `resolveSubjectEntity` and `reauthorizeScopes`.

---

Nitpick comments:
In `@backend/internal/oauth/oauth2/granthandlers/refresh_token_test.go`:
- Around line 2180-2185: Update the EvaluateAccessBatch matcher to only capture
req.Evaluations without calling suite.Require inside MatchedBy; after
HandleGrant returns, assert the captured evaluations are non-empty, assign
evaluatedGroupIDs from the first evaluation, and perform the expected group ID
assertion there.

In `@backend/internal/oauth/oauth2/granthandlers/refresh_token.go`:
- Around line 598-626: No implementation change is required for this comment;
leave the refresh flow around GetActorGroups and EvaluateAccessBatch unchanged.
- Around line 464-466: Update the actorProvider handling in the OAuth refresh
flow: either remove the unreachable nil guard, or replace its silent nil return
with a server_error response. Preserve the credential, subject, and scope
validation path whenever actorProvider is available.

In `@tests/integration/oauth/token/refresh_token_test.go`:
- Around line 756-778: Replace the duplicated testutils.UpdateApplication
payload in the refresh-token rotation test with the existing
ts.updateApplication helper, passing the rotated client secret value as its
parameter. Preserve the current application update behavior and error assertion
while reusing the helper’s shared payload construction.
- Around line 728-808: The refresh-token rejection tests rely on same-second
credential changes being rejected by the inclusive comparison in
credentialChangedSince. Add a concise comment near the assertions in
TestRefresh_PasswordReset_RejectsToken,
TestRefresh_ClientSecretRotated_RejectsToken, and
TestRefresh_ClientSecretRotatedThenAppRenamed_StillRejectsToken documenting this
dependency and warning that changing <= to < would make them timing-sensitive.

In `@tests/integration/testutils/api_utils.go`:
- Around line 2044-2051: In the credential update request construction, remove
the intermediate credsJSON marshal and pass credentials directly as the map
value in the payload used by the surrounding request flow. Eliminate the
associated first error branch while preserving the existing error handling for
marshaling the final payload.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 91e6f81f-74b7-49f4-b2f8-3788f3a0846a

📥 Commits

Reviewing files that changed from the base of the PR and between 1e50b6f and 3cc25df.

📒 Files selected for processing (19)
  • backend/internal/agent/constants.go
  • backend/internal/agent/service.go
  • backend/internal/agent/service_test.go
  • backend/internal/application/constants.go
  • backend/internal/application/declarative_resource.go
  • backend/internal/application/service.go
  • backend/internal/application/service_test.go
  • backend/internal/authnprovider/common/constants.go
  • backend/internal/entity/service.go
  • backend/internal/entity/service_test.go
  • backend/internal/oauth/oauth2/granthandlers/provider.go
  • backend/internal/oauth/oauth2/granthandlers/refresh_token.go
  • backend/internal/oauth/oauth2/granthandlers/refresh_token_test.go
  • docs/content/guides/applications/application-settings.mdx
  • docs/content/guides/applications/manage-applications.mdx
  • docs/content/guides/protocols/oauth-oidc/refresh-token.mdx
  • docs/content/key-concepts/tokens.mdx
  • tests/integration/oauth/token/refresh_token_test.go
  • tests/integration/testutils/api_utils.go

Comment thread backend/internal/application/service.go Outdated
Comment thread backend/internal/oauth/oauth2/granthandlers/refresh_token.go
Comment thread backend/internal/oauth/oauth2/granthandlers/refresh_token.go
Comment thread docs/content/guides/protocols/oauth-oidc/refresh-token.mdx
@codecov

codecov Bot commented Aug 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.91209% with 22 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
backend/internal/entity/service.go 61.40% 11 Missing and 11 partials ⚠️

📢 Thoughts on this report? Let us know!

@thiva-k
thiva-k force-pushed the add-refresh-authz branch from 3cc25df to e540a7b Compare August 11, 2026 19:38

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
backend/internal/oauth/oauth2/granthandlers/refresh_token_test.go (1)

2180-2185: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Do not call suite.Require() inside a mock.MatchedBy predicate.

MatchedBy predicates run during argument matching and again when testify renders a mismatch diagnostic. Require calls runtime.Goexit on failure, which aborts the test from inside the matcher and hides the real assertion. Return a boolean from the predicate and assert after HandleGrant.

♻️ Proposed refactor
 	var evaluatedGroupIDs []string
 	suite.mockAuthzService.On("EvaluateAccessBatch", mock.Anything, mock.MatchedBy(
 		func(req providers.AccessEvaluationsRequest) bool {
-			suite.Require().NotEmpty(req.Evaluations)
+			if len(req.Evaluations) == 0 {
+				return false
+			}
 			evaluatedGroupIDs = req.Evaluations[0].Subject.GroupIDs
 			return true
 		})).Return(allowAllEvaluations, nil)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/internal/oauth/oauth2/granthandlers/refresh_token_test.go` around
lines 2180 - 2185, Update the mock.MatchedBy predicate for EvaluateAccessBatch
to avoid calling suite.Require; capture the evaluation data and return a boolean
indicating whether Evaluations is non-empty, then assert the required non-empty
condition after HandleGrant completes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/internal/oauth/oauth2/granthandlers/refresh_token_test.go`:
- Around line 2459-2481: Move suite.refreshClaimsValid() before the
systemAttributes loop so it registers the ValidateRefreshToken expectation only
once. Remove the per-iteration call while preserving the existing
resetActorMocks setup and unreadable-marker assertions for every test input.

---

Nitpick comments:
In `@backend/internal/oauth/oauth2/granthandlers/refresh_token_test.go`:
- Around line 2180-2185: Update the mock.MatchedBy predicate for
EvaluateAccessBatch to avoid calling suite.Require; capture the evaluation data
and return a boolean indicating whether Evaluations is non-empty, then assert
the required non-empty condition after HandleGrant completes.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 48d7788a-609b-40ef-b0e1-fde3d2f5127f

📥 Commits

Reviewing files that changed from the base of the PR and between 3cc25df and e540a7b.

📒 Files selected for processing (5)
  • backend/internal/agent/service_test.go
  • backend/internal/application/service_test.go
  • backend/internal/entity/service_test.go
  • backend/internal/oauth/oauth2/granthandlers/refresh_token.go
  • backend/internal/oauth/oauth2/granthandlers/refresh_token_test.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • backend/internal/application/service_test.go
  • backend/internal/agent/service_test.go
  • backend/internal/oauth/oauth2/granthandlers/refresh_token.go

Comment thread backend/internal/oauth/oauth2/granthandlers/refresh_token_test.go
@thiva-k
thiva-k force-pushed the add-refresh-authz branch 2 times, most recently from 890c059 to ae940d9 Compare August 11, 2026 20:00
Comment thread backend/internal/application/service.go Outdated
@thiva-k
thiva-k force-pushed the add-refresh-authz branch 2 times, most recently from 6113b03 to 7090ba3 Compare August 12, 2026 05:21
@thiva-k
thiva-k force-pushed the add-refresh-authz branch from 7090ba3 to dd2236d Compare August 12, 2026 05:22
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@thiva-k thiva-k added trigger-pr-builder Add when the PR is ready for CI; starts the PR Builder for this and all later pushes and removed trigger-pr-builder Add when the PR is ready for CI; starts the PR Builder for this and all later pushes labels Aug 12, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
backend/internal/entity/service_test.go (2)

720-735: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert on the blob passed to the store, not on the input entity.

UpdateEntity mutates the caller's entity in place at line 255 of backend/internal/entity/service.go. This test reads incoming.SystemAttributes after the call, so it verifies the input mutation rather than the value written to the store. If that in-place mutation is ever removed, the test still passes while the store receives the wrong blob. Capture the argument passed to store.UpdateEntity, as the two neighbouring tests do for UpdateSystemAttributes.

♻️ Proposed change
 	s.store.On("GetEntity", mock.Anything, stored.ID).Return(*stored, nil)
-	s.store.On("UpdateEntity", mock.Anything, mock.Anything).Return(nil)
+	var written json.RawMessage
+	s.store.On("UpdateEntity", mock.Anything, mock.Anything).
+		Run(func(args mock.Arguments) {
+			e, _ := args.Get(1).(*providers.Entity)
+			written = e.SystemAttributes
+		}).Return(nil)
 
 	incoming := testEntity("e-preserve-2")
 	incoming.SystemAttributes = json.RawMessage(`{"name":"Renamed"}`)
 	_, err := s.svc.UpdateEntity(s.ctx, incoming.ID, incoming)
 	s.Require().NoError(err)
 
 	var attrs map[string]interface{}
-	s.Require().NoError(json.Unmarshal(incoming.SystemAttributes, &attrs))
+	s.Require().NoError(json.Unmarshal(written, &attrs))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/internal/entity/service_test.go` around lines 720 - 735, Update
TestUpdateEntity_PreservesCredentialMarker to capture the entity argument
supplied to store.UpdateEntity and assert the merged SystemAttributes from that
captured value, rather than checking incoming.SystemAttributes after
UpdateEntity returns. Preserve the existing assertions for the renamed field and
credential marker.

682-698: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the mergeReservedAttributes error branch.

mergeReservedAttributes returns an error when the incoming blob is present but unparsable (backend/internal/entity/service.go lines 933-937). No test exercises that branch. It sits on the credential-invalidation path, so a silent regression there would drop the marker. Add one case where the stored entity holds a marker and the caller passes an invalid blob to UpdateSystemAttributes.

Testing new code is required by the coding guidelines: "Write tests for new features and bug fixes, targeting at least 80% coverage."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/internal/entity/service_test.go` around lines 682 - 698, Add a
ServiceTestSuite case covering UpdateSystemAttributes when the stored entity
already has the credential-updated marker and the incoming blob is invalid JSON.
Assert the operation returns the mergeReservedAttributes error and preserves the
existing marker rather than silently replacing the attributes.

Source: Coding guidelines

backend/internal/oauth/oauth2/granthandlers/refresh_token.go (1)

596-645: 🚀 Performance & Scalability | 🔵 Trivial

Consider the added per-refresh backend calls.

Each refresh-token grant now performs up to five additional lookups: GetActor for the subject, GetActor for the client, GetInboundClientByID, GetActorGroups, and EvaluateAccessBatch. Refresh is a hot path for long-lived sessions. Measure the added latency, and consider caching group membership and the client subject-attribute mapping, both of which change far less often than tokens refresh.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/internal/oauth/oauth2/granthandlers/refresh_token.go` around lines
596 - 645, Profile the refresh-token path around reauthorizeScopes and the
related GetActor, GetInboundClientByID, and EvaluateAccessBatch calls to
quantify added latency. Then reduce repeated backend work by reusing
appropriately scoped cached client subject-attribute mappings and GetActorGroups
results, with invalidation or TTL behavior that reflects assignment changes
while preserving authorization correctness.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@backend/internal/entity/service_test.go`:
- Around line 720-735: Update TestUpdateEntity_PreservesCredentialMarker to
capture the entity argument supplied to store.UpdateEntity and assert the merged
SystemAttributes from that captured value, rather than checking
incoming.SystemAttributes after UpdateEntity returns. Preserve the existing
assertions for the renamed field and credential marker.
- Around line 682-698: Add a ServiceTestSuite case covering
UpdateSystemAttributes when the stored entity already has the credential-updated
marker and the incoming blob is invalid JSON. Assert the operation returns the
mergeReservedAttributes error and preserves the existing marker rather than
silently replacing the attributes.

In `@backend/internal/oauth/oauth2/granthandlers/refresh_token.go`:
- Around line 596-645: Profile the refresh-token path around reauthorizeScopes
and the related GetActor, GetInboundClientByID, and EvaluateAccessBatch calls to
quantify added latency. Then reduce repeated backend work by reusing
appropriately scoped cached client subject-attribute mappings and GetActorGroups
results, with invalidation or TTL behavior that reflects assignment changes
while preserving authorization correctness.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: fdbe003e-288f-4e88-8252-9ca56c84b99c

📥 Commits

Reviewing files that changed from the base of the PR and between 9c6b9a3 and dd2236d.

📒 Files selected for processing (12)
  • backend/internal/authnprovider/common/constants.go
  • backend/internal/entity/service.go
  • backend/internal/entity/service_test.go
  • backend/internal/oauth/oauth2/granthandlers/provider.go
  • backend/internal/oauth/oauth2/granthandlers/refresh_token.go
  • backend/internal/oauth/oauth2/granthandlers/refresh_token_test.go
  • docs/content/guides/applications/application-settings.mdx
  • docs/content/guides/applications/manage-applications.mdx
  • docs/content/guides/protocols/oauth-oidc/refresh-token.mdx
  • docs/content/key-concepts/tokens.mdx
  • tests/integration/oauth/token/refresh_token_test.go
  • tests/integration/testutils/api_utils.go
🚧 Files skipped from review as they are similar to previous changes (7)
  • docs/content/guides/applications/application-settings.mdx
  • backend/internal/oauth/oauth2/granthandlers/provider.go
  • backend/internal/authnprovider/common/constants.go
  • tests/integration/testutils/api_utils.go
  • docs/content/guides/applications/manage-applications.mdx
  • docs/content/guides/protocols/oauth-oidc/refresh-token.mdx
  • backend/internal/oauth/oauth2/granthandlers/refresh_token_test.go

@thiva-k
thiva-k added this pull request to the merge queue Aug 12, 2026
Merged via the queue into thunder-id:main with commit 136bea8 Aug 12, 2026
56 of 71 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

trigger-pr-builder Add when the PR is ready for CI; starts the PR Builder for this and all later pushes Type/Bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants