Skip to content

Add multiple client secrets support for OAuth/OIDC applications - #10587

Open
AfraHussaindeen wants to merge 4 commits into
wso2:masterfrom
AfraHussaindeen:master_multiple-client-secrets
Open

Add multiple client secrets support for OAuth/OIDC applications#10587
AfraHussaindeen wants to merge 4 commits into
wso2:masterfrom
AfraHussaindeen:master_multiple-client-secrets

Conversation

@AfraHussaindeen

@AfraHussaindeen AfraHussaindeen commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Purpose

Introduces a config-gated UI for managing multiple client secrets per OAuth/OIDC application in the Console, enabling seamless secret rotation (generate a new secret, roll over, then delete/expire the old one) without downtime.

Overview of changes

The feature is surfaced on the OIDC protocol tab of the application edit view and is gated behind a config flag, so the legacy single-secret UI is untouched when the feature is off.

OIDC protocol tab (ClientSecretsSection)

  • Shows the current secret with a Generate New Secret action.
  • Generate modal with expiration options (30 / 60 / 90 / 180 days, custom days, or never expires).
  • Expandable, lazily-loaded Previous Client Secrets list (scrollable beyond 3), each with a delete action and a colour-coded expiry status (Active /
    Expires Soon / Expired / Never), matching the connection-status dot styling.
  • Proactively disables the generate button once the configured max count is reached (with a tooltip); the server 409 is the authoritative backstop.
  • Supports client-secret hashing mode — the newly generated plaintext secret is revealed once in a modal.

Application page

  • Secret expiry banner shown when at least one active secret is within the critical-expiry window, with a "View details" link to the protocol tab.

Danger zone

  • Revoke All Client Secrets — revokes every secret and regenerates a fresh one (with an error-styled, checkbox confirmation).

APIs (api/application.ts, useGetOAuthClientSecrets)

  • GET /inbound-protocols/oidc/secrets — list secrets (SWR hook)
  • POST /inbound-protocols/oidc/secrets — create a secret ({ expiresAt? }, epoch seconds)
  • DELETE /inbound-protocols/oidc/secrets/{id} — delete a secret

Permissions

  • View / generate / delete are gated by the client-secret read/create scopes when the applications.enforceClientSecretPermission feature flag is on, and fall back to the existing application read/update permissions otherwise.

Configuration

Console config lives under the applications feature (rendered from the toml at server startup). Driven by the framework deployment.toml (defaults: enable = true, max_secret_count = 2):

  [oauth.multiple_client_secrets]
  enable = true
  max_secret_count = 2

Screen Recordings

  • When the user has the client secrets view, create, delete and regenerate permissions.
Untitled.mov
  • When the user has client secret view permission only
Screen.Recording.2026-08-11.at.23.56.43.mov
  • When hashing persistance mode is enabled
Screenshot 2026-08-12 at 00 04 56

Related PRs

@AfraHussaindeen
AfraHussaindeen marked this pull request as draft July 28, 2026 02:11
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The console adds configurable multiple-client-secret management for OIDC applications. It adds creation, deletion, expiry status, generation and confirmation modals, previous-secret display, expiry alerts, revoke-all controls, permissions, feature-flag wiring, and localized strings.

Changes

OIDC Client Secret Management

Layer / File(s) Summary
Contracts, configuration, and API
apps/console/..., features/admin.applications.v1/api/*, features/admin.applications.v1/models/*, modules/i18n/...
Configuration enables multiple secrets and sets limits. Models, translations, retrieval, creation, and deletion APIs define the client-secret contract.
Expiry detection and secret display
features/admin.applications.v1/components/client-secrets/client-secret-{utils,status,row,value-field}.*, features/admin.applications.v1/components/banners/secret-expiry-banner.tsx, features/admin.applications.v1/pages/application-edit.tsx
Expiry utilities classify secrets. Display components render values and statuses. The edit page shows a banner for critically expiring secrets.
Generation, deletion, and secret list management
features/admin.applications.v1/components/client-secrets/{client-secrets-section,generate-client-secret-modal,generated-client-secret-modal,delete-client-secret-modal,previous-client-secrets}.*
The client-secrets section manages limits, generation, deletion, alerts, previous secrets, and modal flows.
Feature-flagged application wiring and revocation
features/admin.applications.v1/components/forms/inbound-oidc-form.tsx, features/admin.applications.v1/components/settings/access-configuration.tsx, features/admin.applications.v1/components/client-secrets/revoke-all-client-secrets-danger-zone.tsx
The OIDC form selects the multiple-secret experience. Access configuration adds guarded revoke-all behavior for eligible applications.

Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Changeset Required ❌ Error The master...HEAD changed-file list contains feature, module, and console files but no new .changeset/*.md file; only the pre-existing README.md exists. Add a new .changeset/*.md file, excluding README.md, that lists the affected packages and required version updates.
Description check ⚠️ Warning The description clearly explains the feature and related PRs, but it omits the required issues, checklist, security, and developer checklist sections. Add the missing template sections and complete the required checklist, security checks, related issue reference, and developer checklist.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the primary change: adding multiple client-secret support for OAuth/OIDC applications.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • 🛠️ create changeset

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Biome (2.5.6)
features/admin.applications.v1/api/application.ts

File contains syntax errors that prevent linting: Line 328: Catch clause variable type annotation must be 'any' or 'unknown' if specified.


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.

@codecov

codecov Bot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 73.50%. Comparing base (0a908a7) to head (d0ea6c4).
⚠️ Report is 41 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master   #10587      +/-   ##
==========================================
+ Coverage   73.46%   73.50%   +0.04%     
==========================================
  Files         471      471              
  Lines       71454    71562     +108     
  Branches      266      266              
==========================================
+ Hits        52492    52600     +108     
  Misses      18855    18855              
  Partials      107      107              
Files with missing lines Coverage Δ
...18n/src/translations/en-US/portals/applications.ts 100.00% <100.00%> (ø)

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@AfraHussaindeen
AfraHussaindeen force-pushed the master_multiple-client-secrets branch from 0151ad8 to 02825c6 Compare July 28, 2026 05:17
Comment thread apps/console/src/public/deployment.config.json Outdated
Comment thread apps/console/src/public/deployment.config.json Outdated
Comment thread features/admin.applications.v1/api/application.ts Outdated
Comment thread features/admin.applications.v1/api/application.ts Outdated
Comment thread features/admin.applications.v1/api/application.ts Outdated
Comment thread features/admin.applications.v1/api/use-get-oauth-client-secrets.ts Outdated
@AfraHussaindeen
AfraHussaindeen force-pushed the master_multiple-client-secrets branch from 8c24941 to 6821902 Compare August 6, 2026 05:02
Comment thread features/admin.applications.v1/models/application-inbound.ts
Comment thread features/admin.applications.v1/components/banners/secret-expiry-banner.tsx Outdated
Comment thread features/admin.applications.v1/components/forms/inbound-oidc-form.tsx Outdated
Comment on lines +82 to +87
case ClientSecretExpiryState.EXPIRED:
return "grey";
case ClientSecretExpiryState.EXPIRING_CRITICAL:
return "red";
case ClientSecretExpiryState.EXPIRING_WARNING:
return "yellow";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

as discussed in the code review, were you able to consider using red for expired serets and yellow for secrets that are to be expired soon?

Comment on lines +26 to +27
const EXPIRY_WARNING_DAYS: number = 20;
const EXPIRY_CRITICAL_DAYS: number = 10;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

shall we settle on a single expiry waring time period instead, for the sake of simplified UX?

Comment on lines +35 to +43
/** Green — not expiring anytime soon. */
ACTIVE = "active",
/** Green — never expires. */
NEVER = "never",
/** Yellow — expiring within the warning window. */
EXPIRING_WARNING = "expiringWarning",
/** Red — expiring within the critical window. */
EXPIRING_CRITICAL = "expiringCritical",
/** Grey — already expired. */

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do we really need to mention the colors in the doc comments here?

@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: 9

🧹 Nitpick comments (1)
features/admin.applications.v1/components/client-secrets/generate-client-secret-modal.tsx (1)

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

Replace the any ref type with the form ref type.

formRef is typed as React.MutableRefObject<any>. The coding guidelines prohibit any. Use the exported ref type of Form from @wso2is/forms, or unknown with a narrow local interface that declares triggerSubmit.

As per coding guidelines: "Never use any type; use proper types or unknown with type guards instead".

♻️ Proposed minimal typing
+interface FormRefInterface {
+    triggerSubmit: () => void;
+}
+
-    const formRef: React.MutableRefObject<any> = useRef(null);
+    const formRef: React.MutableRefObject<FormRefInterface> = useRef<FormRefInterface>(null);
🤖 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
`@features/admin.applications.v1/components/client-secrets/generate-client-secret-modal.tsx`
at line 91, Replace the any-based type on formRef with the exported Form ref
type from `@wso2is/forms`; if unavailable, use unknown with a narrow
interface/type guard exposing triggerSubmit, while preserving the existing ref
usage.

Source: Coding guidelines

🤖 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
`@apps/console/java/org.wso2.identity.apps.console.server.feature/resources/deployment.config.json.j2`:
- Around line 847-850: Update the isMultipleClientSecretsEnabled default in the
deployment configuration template to emit true when
oauth.multiple_client_secrets.enable is undefined, while preserving the
configured value when it is provided.

In `@features/admin.applications.v1/components/banners/secret-expiry-banner.tsx`:
- Around line 71-74: Update the selector callbacks in the
isMultipleClientSecretsEnabled and applicationFeatureConfig useSelector calls to
include explicit return type annotations, while retaining the AppState parameter
type. Use the nullable FeatureAccessConfigInterface type consistently for
applicationFeatureConfig if the configuration may be absent.

In
`@features/admin.applications.v1/components/client-secrets/client-secret-status.tsx`:
- Around line 22-25: Replace the Semantic UI controls in ClientSecretStatus with
the themed Oxygen UI status element, using per-component imports and preserving
expiry-status semantics and existing data-componentid behavior. In
features/admin.applications.v1/components/client-secrets/client-secret-value-field.tsx
at lines 23-24, replace Input with the Oxygen UI TextField while preserving
masking, copy behavior, and data-componentid.

In
`@features/admin.applications.v1/components/client-secrets/client-secret-utils.ts`:
- Around line 75-84: Update the expiry-state classification in the client-secret
utility to check whether secret.expiresAt converted to milliseconds is less than
or equal to referenceTime before evaluating the EXPIRY_CRITICAL_DAYS and
EXPIRY_WARNING_DAYS thresholds; classify that elapsed timestamp as
ClientSecretExpiryState.EXPIRED, while preserving the existing threshold
behavior for future expirations.

In
`@features/admin.applications.v1/components/client-secrets/client-secret-value-field.tsx`:
- Around line 31-40: Update ClientSecretValueFieldPropsInterface and
ClientSecretValueField so value is optional and the masked read-only field
renders whenever hideSecretValue is true or value is absent. Preserve the
existing show/copy behavior only when a value is available and hideSecretValue
is false.

In
`@features/admin.applications.v1/components/client-secrets/delete-client-secret-modal.tsx`:
- Around line 19-21: Migrate delete-client-secret-modal.tsx by replacing
ConfirmationModal and Semantic UI subcomponents with Oxygen Dialog, Checkbox,
and Button while preserving the assertion checkbox, non-dismissible backdrop,
callbacks, and existing data-componentid values. In previous-client-secrets.tsx,
replace ContentLoader, Message, and Popup with Oxygen equivalents, remove
previous-client-secrets.scss and hardcoded dimensions, and use MUI styled or
limited theme-based sx values for styling.

In
`@features/admin.applications.v1/components/client-secrets/generate-client-secret-modal.tsx`:
- Around line 131-142: Update validateForm to reject customExpiryDays values
that are not integers while preserving the existing 1–3650 range validation. In
resolveExpiresAt, floor the computed custom expiration timestamp so expiresAt is
always an integer Unix epoch value.

In `@features/admin.applications.v1/components/settings/access-configuration.tsx`:
- Around line 1038-1064: Update resolveClientSecretsDangerZone to require the
selected protocol to be OIDC in its existing guard, alongside the inbound OIDC
configuration checks. Ensure the RevokeAllClientSecretsDangerZone is not
rendered when viewing SAML settings, while preserving the current exclusions and
behavior for the OIDC view.

In `@modules/i18n/src/translations/en-US/portals/applications.ts`:
- Around line 199-203: Update the delete confirmation content near the
client-secret confirmation strings so it states that clients using the deleted
secret can no longer authenticate, while explicitly clarifying that other client
secrets remain valid; preserve the existing notice about active tokens and
authorization codes unless it conflicts with this scope.

---

Nitpick comments:
In
`@features/admin.applications.v1/components/client-secrets/generate-client-secret-modal.tsx`:
- Line 91: Replace the any-based type on formRef with the exported Form ref type
from `@wso2is/forms`; if unavailable, use unknown with a narrow interface/type
guard exposing triggerSubmit, while preserving the existing ref usage.
🪄 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.yml

Review profile: CHILL

Plan: Pro Plus

Run ID: 5012c6f1-4a34-469e-aeed-758a5520142d

📥 Commits

Reviewing files that changed from the base of the PR and between dbc3132 and d0ea6c4.

📒 Files selected for processing (26)
  • apps/console/java/org.wso2.identity.apps.console.server.feature/resources/deployment.config.json.j2
  • apps/console/src/public/deployment.config.json
  • features/admin.applications.v1/api/application.ts
  • features/admin.applications.v1/api/use-get-oauth-client-secrets.ts
  • features/admin.applications.v1/components/banners/secret-expiry-banner.tsx
  • features/admin.applications.v1/components/client-secrets/client-secret-row.scss
  • features/admin.applications.v1/components/client-secrets/client-secret-row.tsx
  • features/admin.applications.v1/components/client-secrets/client-secret-status.scss
  • features/admin.applications.v1/components/client-secrets/client-secret-status.tsx
  • features/admin.applications.v1/components/client-secrets/client-secret-utils.ts
  • features/admin.applications.v1/components/client-secrets/client-secret-value-field.scss
  • features/admin.applications.v1/components/client-secrets/client-secret-value-field.tsx
  • features/admin.applications.v1/components/client-secrets/client-secrets-section.scss
  • features/admin.applications.v1/components/client-secrets/client-secrets-section.tsx
  • features/admin.applications.v1/components/client-secrets/delete-client-secret-modal.tsx
  • features/admin.applications.v1/components/client-secrets/generate-client-secret-modal.tsx
  • features/admin.applications.v1/components/client-secrets/generated-client-secret-modal.tsx
  • features/admin.applications.v1/components/client-secrets/previous-client-secrets.scss
  • features/admin.applications.v1/components/client-secrets/previous-client-secrets.tsx
  • features/admin.applications.v1/components/client-secrets/revoke-all-client-secrets-danger-zone.tsx
  • features/admin.applications.v1/components/forms/inbound-oidc-form.tsx
  • features/admin.applications.v1/components/settings/access-configuration.tsx
  • features/admin.applications.v1/models/application-inbound.ts
  • features/admin.applications.v1/pages/application-edit.tsx
  • modules/i18n/src/models/namespaces/applications-ns.ts
  • modules/i18n/src/translations/en-US/portals/applications.ts

Comment on lines +847 to +850
"isMultipleClientSecretsEnabled": {% if oauth.multiple_client_secrets.enable is defined %}{{ oauth.multiple_client_secrets.enable }}{% else %}false{% endif %},
"maxGracefulRefreshTokenReuseLimit": {% if oauth.graceful_refresh_token_rotation.maximum_reuse_limit is defined %}{{ oauth.graceful_refresh_token_rotation.maximum_reuse_limit }}{% else %}5{% endif %},
"maxGracefulRefreshTokenRotationValidityPeriod": {% if oauth.graceful_refresh_token_rotation.maximum_validity_period is defined %}{{ oauth.graceful_refresh_token_rotation.maximum_validity_period }}{% else %}60{% endif %}
"maxGracefulRefreshTokenRotationValidityPeriod": {% if oauth.graceful_refresh_token_rotation.maximum_validity_period is defined %}{{ oauth.graceful_refresh_token_rotation.maximum_validity_period }}{% else %}60{% endif %},
"multipleClientSecretsMaxCount": {% if oauth.multiple_client_secrets.max_secret_count is defined %}{{ oauth.multiple_client_secrets.max_secret_count }}{% else %}2{% endif %}

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 3 'multiple_client_secrets|isMultipleClientSecretsEnabled|multipleClientSecretsMaxCount' \
  apps/console

Repository: wso2/identity-apps

Length of output: 3174


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- all repository references ---'
rg -n -C 4 'multiple_client_secrets|isMultipleClientSecretsEnabled|multipleClientSecretsMaxCount' .

printf '%s\n' '--- candidate deployment configuration files ---'
git ls-files | rg '(^|/)(deployment\.config|deployment.*config|config.*deployment)|multiple.*secret' | head -200

printf '%s\n' '--- template and configuration context ---'
sed -n '810,865p' apps/console/java/org.wso2.identity.apps.console.server.feature/resources/deployment.config.json.j2
sed -n '560,610p' apps/console/src/public/deployment.config.json

Repository: wso2/identity-apps

Length of output: 21094


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- OAuth configuration declarations and defaults ---'
rg -n -i -C 3 'multiple[._-]?client[._-]?secret|maximum[._-]?reuse|maximum[._-]?validity' \
  --glob '!apps/console/src/public/deployment.config.json' \
  --glob '!apps/console/java/org.wso2.identity.apps.console.server.feature/resources/deployment.config.json.j2' .

printf '%s\n' '--- deployment.config consumers and fallback behavior ---'
rg -n -C 4 'deployment\.config\.json|DeploymentConfig|config\.ui\.features|features\?\.applications' \
  features modules apps/console/src --glob '*.{ts,tsx,js,json}'

printf '%s\n' '--- tracked change summary ---'
git status --short
git diff --stat

Repository: wso2/identity-apps

Length of output: 50375


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- exact feature references outside generated/static config ---'
rg -n -C 2 'multiple_client_secrets|isMultipleClientSecretsEnabled|multipleClientSecretsMaxCount' \
  --glob '*.j2' --glob '*.yaml' --glob '*.yml' --glob '*.toml' --glob '*.json' \
  --glob '*.properties' --glob '*.md' --glob '*.ts' --glob '*.tsx' --glob '*.js' \
  . | head -300

printf '%s\n' '--- deployment config loading ---'
rg -n -C 5 'getConfig\(\)|deployment\.config\.json|AppUtils' \
  features/admin.core.v1 apps/console --glob '*.{ts,tsx,js,json}' | head -300

printf '%s\n' '--- repository state ---'
git status --short
git diff --name-only

Repository: wso2/identity-apps

Length of output: 31650


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import json
import re
from pathlib import Path

template = Path("apps/console/java/org.wso2.identity.apps.console.server.feature/resources/deployment.config.json.j2").read_text()
public = json.loads(Path("apps/console/src/public/deployment.config.json").read_text())

pattern = re.compile(
    r'"isMultipleClientSecretsEnabled":\s*'
    r'\{%\s*if\s+oauth\.multiple_client_secrets\.enable\s+is\s+defined\s*%\}'
    r'(?P<defined>.*?)'
    r'\{%\s*else\s*%\}(?P<fallback>.*?)\{%\s*endif\s*%\}'
)
match = pattern.search(template)
if not match:
    raise SystemExit("template expression not found")

public_value = (
    public["ui"]["features"]["applications"]["properties"]
    ["isMultipleClientSecretsEnabled"]
)

print("server_template_defined_value:", match.group("defined").strip())
print("server_template_absent_value:", match.group("fallback").strip())
print("checked_in_public_value:", public_value)

assert match.group("fallback").strip() == "false"
assert public_value is True
print("mismatch_for_absent_server_setting: true")
PY

Repository: wso2/identity-apps

Length of output: 337


Set the server-side default to true. The template currently emits false when oauth.multiple_client_secrets.enable is absent, while the Console default is true. This disables multiple client secrets in deployments that omit the server setting.

🤖 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
`@apps/console/java/org.wso2.identity.apps.console.server.feature/resources/deployment.config.json.j2`
around lines 847 - 850, Update the isMultipleClientSecretsEnabled default in the
deployment configuration template to emit true when
oauth.multiple_client_secrets.enable is undefined, while preserving the
configured value when it is provided.

Comment on lines +71 to +74
const isMultipleClientSecretsEnabled: boolean = useSelector((state: AppState) =>
Boolean(state?.config?.ui?.features?.applications?.properties?.isMultipleClientSecretsEnabled));
const applicationFeatureConfig: FeatureAccessConfigInterface = useSelector((state: AppState) =>
state?.config?.ui?.features?.applications);

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add return types to the selector callbacks.

Line 71 and Line 73 annotate the result variables, but the selector callbacks still rely on inferred return types. Add explicit return types to both callbacks. If the application feature configuration can be absent, use the nullable type consistently.

As per coding guidelines: “Always include explicit type annotations on useSelector hook with the selector function parameter type and return type.”

🤖 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 `@features/admin.applications.v1/components/banners/secret-expiry-banner.tsx`
around lines 71 - 74, Update the selector callbacks in the
isMultipleClientSecretsEnabled and applicationFeatureConfig useSelector calls to
include explicit return type annotations, while retaining the AppState parameter
type. Use the nullable FeatureAccessConfigInterface type consistently for
applicationFeatureConfig if the configuration may be absent.

Source: Coding guidelines

Comment on lines +22 to +25
import { Label, SemanticCOLORS } from "semantic-ui-react";
import { ClientSecretExpiryState, resolveClientSecretExpiry } from "./client-secret-utils";
import "./client-secret-status.scss";
import { ClientSecretInterface } from "../../models/application-inbound";

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.

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- target files ---'
git ls-files 'features/admin.applications.v1/components/client-secrets/client-secret-status.tsx' \
  'features/admin.applications.v1/components/client-secrets/client-secret-value-field.tsx'

printf '%s\n' '--- status outline ---'
ast-grep outline features/admin.applications.v1/components/client-secrets/client-secret-status.tsx

printf '%s\n' '--- value field outline ---'
ast-grep outline features/admin.applications.v1/components/client-secrets/client-secret-value-field.tsx

printf '%s\n' '--- status source ---'
cat -n features/admin.applications.v1/components/client-secrets/client-secret-status.tsx

printf '%s\n' '--- value field source ---'
cat -n features/admin.applications.v1/components/client-secrets/client-secret-value-field.tsx

printf '%s\n' '--- nearby Oxygen UI usage ---'
rg -n --glob '*.tsx' --glob '*.ts' \
  "from ['\"]`@oxygen-ui/react`|`@oxygen-ui/react/`" \
  features/admin.applications.v1/components | head -200

printf '%s\n' '--- package declarations ---'
rg -n --glob 'package.json' --glob 'pnpm-lock.yaml' --glob 'yarn.lock' --glob 'package-lock.json' \
  '"`@oxygen-ui/react`"|oxygen-ui' .

Repository: wso2/identity-apps

Length of output: 35961


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- working tree status ---'
git status --short

printf '%s\n' '--- target diff summary ---'
git diff --stat -- \
  features/admin.applications.v1/components/client-secrets/client-secret-status.tsx \
  features/admin.applications.v1/components/client-secrets/client-secret-value-field.tsx

printf '%s\n' '--- target diff ---'
git diff -- \
  features/admin.applications.v1/components/client-secrets/client-secret-status.tsx \
  features/admin.applications.v1/components/client-secrets/client-secret-value-field.tsx

printf '%s\n' '--- client-secret styles ---'
for file in \
  features/admin.applications.v1/components/client-secrets/client-secret-status.scss \
  features/admin.applications.v1/components/client-secrets/client-secret-value-field.scss; do
  if [ -f "$file" ]; then
    printf '%s\n' "--- $file ---"
    cat -n "$file"
  fi
done

printf '%s\n' '--- status and input alternatives in this feature ---'
rg -n --glob '*.tsx' --glob '*.ts' \
  'SemanticCOLORS|<Label\b|from ["'\'']semantic-ui-react["'\'']|<Input\b|<TextField\b|<Chip\b|status.*indicator|Status' \
  features/admin.applications.v1/components | head -250

printf '%s\n' '--- Oxygen input component references ---'
rg -n --glob '*.tsx' --glob '*.ts' \
  'from ["'\'']`@oxygen-ui/react/`(Input|TextField|Chip|Badge)["'\'']|<Input\b|<TextField\b|<Chip\b|<Badge\b' \
  features modules apps | head -300

Repository: wso2/identity-apps

Length of output: 50374


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- concise Oxygen control imports ---'
rg -n --glob '*.tsx' --glob '*.ts' \
  'import (Input|TextField|Chip|Box|Badge|.*) from "`@oxygen-ui/react/`(Input|TextField|Chip|Box|Badge)"' \
  features/admin.applications.v1 features/admin.onboarding.v1 features/admin.push-providers.v1 | head -120

printf '%s\n' '--- reusable copyable field implementation ---'
rg -l --glob '*.tsx' 'CopyInputField|copyable-field' features modules apps | head -30

printf '%s\n' '--- nearby styled Oxygen components ---'
rg -l --glob '*.tsx' 'styled\(.*`@mui/material/styles`|from "`@mui/material/styles`"' \
  features/admin.applications.v1/components/client-secrets \
  features/admin.applications.v1/components \
  features/admin.onboarding.v1/components/shared | head -30

printf '%s\n' '--- exact target import and control inventory ---'
python3 - <<'PY'
from pathlib import Path
import re

paths = [
    Path("features/admin.applications.v1/components/client-secrets/client-secret-status.tsx"),
    Path("features/admin.applications.v1/components/client-secrets/client-secret-value-field.tsx"),
]
for path in paths:
    text = path.read_text()
    imports = re.findall(r'import .*? from "([^"]+)";', text)
    controls = re.findall(r'<([A-Z][A-Za-z0-9_]*)\b', text)
    print(path)
    print("  semantic-ui-react import:", "semantic-ui-react" in imports)
    print("  imported controls:", [item for item in imports if "semantic-ui-react" in item or "oxygen-ui" in item])
    print("  JSX controls:", controls)
    print("  data-componentid count:", text.count("data-componentid"))
PY

Repository: wso2/identity-apps

Length of output: 9064


🌐 Web query:

Oxygen UI React 2.4.6 component documentation Input TextField Chip status indicator

💡 Result:

Oxygen UI is a design system and React component library developed by WSO2, which provides a set of components built on top of Material-UI [1][2][3]. As of version 2.4.6, Oxygen UI does not provide a specific, standalone "Status Indicator" component [3][4]. Instead, developers typically implement status indicators using the Material-UI Chip component, which is available within the Oxygen UI ecosystem [1][2][5]. Key takeaways for the components mentioned: 1. TextField: You can use the standard Material-UI TextField component directly from the Oxygen UI library [1][2][3]. It is used for form inputs and integrates seamlessly with the design system's theme [6]. 2. Chip: While not explicitly labeled as a "status indicator" component in the library documentation, the Chip component is the standard way to represent status [5]. For example, in a ListingTable, a Chip is commonly used to visualize status with dynamic colors (e.g., using 'success' for active items) [5]. 3. Documentation: Documentation for components can be found in the Oxygen UI Storybook [1][7][8]. Since Oxygen UI is powered by Material-UI, you can often refer to official Material-UI documentation for core component behaviors, while relying on the Oxygen UI package for brand-specific theming and custom extensions [1][2][9]. To get started, ensure you have the library installed [3]: npm install @oxygen-ui/react You can then import components as follows [3][4]: import { TextField, Chip } from '@oxygen-ui/react';

Citations:


Replace Semantic UI controls in the client-secret components.

Use Oxygen UI per-component imports. Replace Label/SemanticCOLORS with a themed Oxygen UI status element, and replace Input with an Oxygen UI TextField. Preserve data-componentid, masking, copy behavior, and expiry-status semantics.

📍 Affects 2 files
  • features/admin.applications.v1/components/client-secrets/client-secret-status.tsx#L22-L25 (this comment)
  • features/admin.applications.v1/components/client-secrets/client-secret-value-field.tsx#L23-L24
🤖 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
`@features/admin.applications.v1/components/client-secrets/client-secret-status.tsx`
around lines 22 - 25, Replace the Semantic UI controls in ClientSecretStatus
with the themed Oxygen UI status element, using per-component imports and
preserving expiry-status semantics and existing data-componentid behavior. In
features/admin.applications.v1/components/client-secrets/client-secret-value-field.tsx
at lines 23-24, replace Input with the Oxygen UI TextField while preserving
masking, copy behavior, and data-componentid.

Source: Coding guidelines

Comment on lines +75 to +84
const daysRemaining: number = Math.ceil((secret.expiresAt * 1000 - referenceTime) / MILLISECONDS_PER_DAY);
const formattedDate: string = formatDate(secret.expiresAt);

/* The secret is active here (EXPIRED is trusted from the API above); classify how soon it expires. */
let state: ClientSecretExpiryState;

if (daysRemaining <= EXPIRY_CRITICAL_DAYS) {
state = ClientSecretExpiryState.EXPIRING_CRITICAL;
} else if (daysRemaining <= EXPIRY_WARNING_DAYS) {
state = ClientSecretExpiryState.EXPIRING_WARNING;

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Classify elapsed expiry timestamps as expired.

If expiresAt is before referenceTime but status is still ACTIVE, daysRemaining is negative and this function returns EXPIRING_CRITICAL. The status line then shows “Expires Soon” with a past date.

Check secret.expiresAt * 1000 <= referenceTime before the warning thresholds and return EXPIRED.

Proposed fix
     const daysRemaining: number = Math.ceil((secret.expiresAt * 1000 - referenceTime) / MILLISECONDS_PER_DAY);
     const formattedDate: string = formatDate(secret.expiresAt);
 
+    if (secret.expiresAt * 1000 <= referenceTime) {
+        return {
+            formattedDate,
+            state: ClientSecretExpiryState.EXPIRED
+        };
+    }
+
     /* The secret is active here (EXPIRED is trusted from the API above); classify how soon it expires. */
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const daysRemaining: number = Math.ceil((secret.expiresAt * 1000 - referenceTime) / MILLISECONDS_PER_DAY);
const formattedDate: string = formatDate(secret.expiresAt);
/* The secret is active here (EXPIRED is trusted from the API above); classify how soon it expires. */
let state: ClientSecretExpiryState;
if (daysRemaining <= EXPIRY_CRITICAL_DAYS) {
state = ClientSecretExpiryState.EXPIRING_CRITICAL;
} else if (daysRemaining <= EXPIRY_WARNING_DAYS) {
state = ClientSecretExpiryState.EXPIRING_WARNING;
const daysRemaining: number = Math.ceil((secret.expiresAt * 1000 - referenceTime) / MILLISECONDS_PER_DAY);
const formattedDate: string = formatDate(secret.expiresAt);
if (secret.expiresAt * 1000 <= referenceTime) {
return {
formattedDate,
state: ClientSecretExpiryState.EXPIRED
};
}
/* The secret is active here (EXPIRED is trusted from the API above); classify how soon it expires. */
let state: ClientSecretExpiryState;
if (daysRemaining <= EXPIRY_CRITICAL_DAYS) {
state = ClientSecretExpiryState.EXPIRING_CRITICAL;
} else if (daysRemaining <= EXPIRY_WARNING_DAYS) {
state = ClientSecretExpiryState.EXPIRING_WARNING;
🤖 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
`@features/admin.applications.v1/components/client-secrets/client-secret-utils.ts`
around lines 75 - 84, Update the expiry-state classification in the
client-secret utility to check whether secret.expiresAt converted to
milliseconds is less than or equal to referenceTime before evaluating the
EXPIRY_CRITICAL_DAYS and EXPIRY_WARNING_DAYS thresholds; classify that elapsed
timestamp as ClientSecretExpiryState.EXPIRED, while preserving the existing
threshold behavior for future expirations.

Comment on lines +31 to +40
interface ClientSecretValueFieldPropsInterface extends IdentifiableComponentInterface {
/**
* The client secret value to display (masked with show/copy).
*/
value: string;
/**
* Whether the secret value is unavailable (e.g. when client secret hashing is enabled).
* When true, a masked read-only field is shown without the show/copy actions.
*/
hideSecretValue?: boolean;

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

fd -HI -t f '^tsconfig.*\.json$' . -x sh -c '
  echo "=== $1 ==="
  jq ".compilerOptions | { strict, strictNullChecks }" "$1"
' sh {}

rg -n -C 3 \
  'secretValue\??:|value:\s*string|value=\{\s*secret\?\.secretValue\s*\}' \
  features/admin.applications.v1/models/application-inbound.ts \
  features/admin.applications.v1/components/client-secrets/client-secret-value-field.tsx \
  features/admin.applications.v1/components/client-secrets/client-secret-row.tsx

Repository: wso2/identity-apps

Length of output: 11376


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '=== application-inbound.ts matches ==='
rg -n -C 5 'secretValue' features/admin.applications.v1/models/application-inbound.ts

printf '%s\n' '=== client-secret-value-field.tsx ==='
cat -n features/admin.applications.v1/components/client-secrets/client-secret-value-field.tsx

printf '%s\n' '=== client-secret-row.tsx ==='
cat -n features/admin.applications.v1/components/client-secrets/client-secret-row.tsx

printf '%s\n' '=== applications feature TypeScript configuration ==='
cat -n features/admin.applications.v1/tsconfig.json
printf '%s\n' '=== shared React TypeScript configuration ==='
cat -n tsconfig.base.react.json

Repository: wso2/identity-apps

Length of output: 9437


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '=== ClientSecretValueField call sites ==='
rg -n -C 4 'ClientSecretValueField|hideSecretValue|secretValue' features/admin.applications.v1/components features/admin.applications.v1/hooks features/admin.applications.v1/utils 2>/dev/null || true

printf '%s\n' '=== CopyInputField declarations and implementation references ==='
rg -n -C 5 'CopyInputField' modules features | head -n 240

Repository: wso2/identity-apps

Length of output: 47968


Handle missing secretValue in the masked branch.

ClientSecretInterface.secretValue is optional, but ClientSecretRow passes it to CopyInputField when hideSecretValue is false. Make value optional and render the masked read-only field when hideSecretValue is true or value is absent. The current TypeScript configuration does not catch this undefined value.

🤖 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
`@features/admin.applications.v1/components/client-secrets/client-secret-value-field.tsx`
around lines 31 - 40, Update ClientSecretValueFieldPropsInterface and
ClientSecretValueField so value is optional and the masked read-only field
renders whenever hideSecretValue is true or value is absent. Preserve the
existing show/copy behavior only when a value is available and hideSecretValue
is false.

Comment on lines +19 to +21
import { IdentifiableComponentInterface } from "@wso2is/core/models";
import { ConfirmationModal } from "@wso2is/react-components";
import React, { FunctionComponent, ReactElement } from "react";

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.

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- candidate files ---'
for f in \
  features/admin.applications.v1/components/client-secrets/delete-client-secret-modal.tsx \
  features/admin.applications.v1/components/client-secrets/previous-client-secrets.tsx
do
  printf '\n--- %s ---\n' "$f"
  wc -l "$f"
  sed -n '1,260p' "$f"
done

printf '%s\n' '--- related dialog and styling patterns ---'
rg -n --glob '*.{ts,tsx}' \
  'ConfirmationModal|ConfirmationDialog|styled\(|<Dialog|disable[A-Za-z]*Click|assert' \
  features/admin.applications.v1/components features 2>/dev/null | head -240

Repository: wso2/identity-apps

Length of output: 41793


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- change status ---'
git status --short
git diff --stat -- \
  features/admin.applications.v1/components/client-secrets/delete-client-secret-modal.tsx \
  features/admin.applications.v1/components/client-secrets/previous-client-secrets.tsx

printf '%s\n' '--- Oxygen dialog patterns ---'
rg -n --glob '*.{ts,tsx}' \
  'from "`@oxygen-ui/react/`(Dialog|DialogTitle|DialogContent|DialogActions|Checkbox|Button|Alert|Typography)"|<Dialog|DialogTitle|DialogContent|DialogActions' \
  . | head -240

printf '%s\n' '--- candidate stylesheet ---'
sed -n '1,240p' features/admin.applications.v1/components/client-secrets/previous-client-secrets.scss

printf '%s\n' '--- component provenance and usages ---'
rg -n --glob '*.{ts,tsx}' \
  'DeleteClientSecretModal|PreviousClientSecrets' \
  features/admin.applications.v1
git log --oneline --follow -- \
  features/admin.applications.v1/components/client-secrets/delete-client-secret-modal.tsx \
  features/admin.applications.v1/components/client-secrets/previous-client-secrets.tsx | head -20

Repository: wso2/identity-apps

Length of output: 36650


🏁 Script executed:

#!/bin/bash
set -eu

for f in \
  features/admin.applications.v1/components/client-secrets/delete-client-secret-modal.tsx \
  features/admin.applications.v1/components/client-secrets/previous-client-secrets.tsx
do
  printf '\n--- history: %s ---\n' "$f"
  git log --oneline -8 -- "$f"
  printf '%s\n' '--- tracked file metadata ---'
  git ls-files --stage -- "$f"
done

printf '%s\n' '--- Oxygen modal implementation pattern ---'
sed -n '1,190p' features/admin.tenants.v1/components/add-tenant/add-tenant-modal.tsx

printf '%s\n' '--- client-secret component history summary ---'
git log --oneline -12 -- features/admin.applications.v1/components/client-secrets

Repository: wso2/identity-apps

Length of output: 7233


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- ConfirmationModal definition and dependencies ---'
rg -n --glob '*.{ts,tsx}' \
  'const ConfirmationModal|function ConfirmationModal|ConfirmationModal\s*=|export.*ConfirmationModal|ConfirmationModalProps' \
  . | head -120
rg -n --glob '*.{ts,tsx,scss}' \
  'semantic-ui|semantic-ui-react|Modal\.|Dimmer|closeOnDimmerClick|assertionHint' \
  packages features/admin.applications.v1/components/client-secrets 2>/dev/null | head -180

printf '%s\n' '--- commit contents introducing the components ---'
git show --stat --oneline 68219024e09 -- \
  features/admin.applications.v1/components/client-secrets
git show --format= --no-ext-diff 68219024e09 -- \
  features/admin.applications.v1/components/client-secrets/delete-client-secret-modal.tsx \
  features/admin.applications.v1/components/client-secrets/previous-client-secrets.tsx \
  features/admin.applications.v1/components/client-secrets/previous-client-secrets.scss | sed -n '1,360p'

printf '%s\n' '--- styling conventions in the client-secret directory ---'
find features/admin.applications.v1/components/client-secrets -maxdepth 1 -type f -print
rg -n --glob '*.{ts,tsx}' 'styled|sx=' features/admin.applications.v1/components/client-secrets

Repository: wso2/identity-apps

Length of output: 34014


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- ConfirmationModal implementation ---'
sed -n '1,230p' modules/react-components/src/components/modal/confirmation-modal/confirmation-modal.tsx
sed -n '1,170p' modules/react-components/src/components/modal/confirmation-modal/confirmation-modal-header.tsx
sed -n '1,170p' modules/react-components/src/components/modal/confirmation-modal/confirmation-modal-message.tsx

Repository: wso2/identity-apps

Length of output: 11903


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- legacy react-components used by PreviousClientSecrets ---'
rg -n --glob '*.{ts,tsx}' \
  'export .*ContentLoader|const ContentLoader|export .*Message|const Message|export .*Popup|const Popup' \
  modules/react-components/src | head -100
rg -n --glob '*.{ts,tsx}' \
  'from "semantic-ui-react"' \
  modules/react-components/src/components/{loader,message,popup} modules/react-components/src 2>/dev/null | head -100

Repository: wso2/identity-apps

Length of output: 16714


Migrate the new client-secret components to Oxygen UI and theme-based styling.

  • Replace ConfirmationModal and its Semantic UI subcomponents with Oxygen Dialog, Checkbox, and Button. Preserve the assertion checkbox, non-dismissible backdrop, callbacks, and data-componentid values.
  • Replace ContentLoader, Message, and Popup in previous-client-secrets.tsx with Oxygen components. Replace previous-client-secrets.scss and hardcoded dimensions with MUI styled or limited sx using theme values.
📍 Affects 2 files
  • features/admin.applications.v1/components/client-secrets/delete-client-secret-modal.tsx#L19-L21 (this comment)
  • features/admin.applications.v1/components/client-secrets/previous-client-secrets.tsx#L19-L27
🤖 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
`@features/admin.applications.v1/components/client-secrets/delete-client-secret-modal.tsx`
around lines 19 - 21, Migrate delete-client-secret-modal.tsx by replacing
ConfirmationModal and Semantic UI subcomponents with Oxygen Dialog, Checkbox,
and Button while preserving the assertion checkbox, non-dismissible backdrop,
callbacks, and existing data-componentid values. In previous-client-secrets.tsx,
replace ContentLoader, Message, and Popup with Oxygen equivalents, remove
previous-client-secrets.scss and hardcoded dimensions, and use MUI styled or
limited theme-based sx values for styling.

Source: Coding guidelines

Comment on lines +131 to +142
const resolveExpiresAt = (values: GenerateClientSecretFormValues): number | undefined => {
const nowInSeconds: number = Math.floor(Date.now() / 1000);

switch (values.expiration) {
case ClientSecretExpirationOption.NO_EXPIRATION:
return undefined;
case ClientSecretExpirationOption.CUSTOM:
return nowInSeconds + Number(values.customExpiryDays) * SECONDS_PER_DAY;
default:
return nowInSeconds + Number(values.expiration) * SECONDS_PER_DAY;
}
};

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reject fractional custom expiry values.

validateForm accepts any numeric string in the range 1 to 3650, including decimals such as 1.5. inputType="number" does not block decimals. resolveExpiresAt then computes nowInSeconds + 1.5 * SECONDS_PER_DAY, which produces a non-integer expiresAt. The API contract defines expiresAt as Unix epoch seconds, so the request carries an invalid value.

Add an integer check in validateForm, and floor the computed timestamp in resolveExpiresAt.

🐛 Proposed fix
             case ClientSecretExpirationOption.CUSTOM:
-                return nowInSeconds + Number(values.customExpiryDays) * SECONDS_PER_DAY;
+                return nowInSeconds + Math.floor(Number(values.customExpiryDays)) * SECONDS_PER_DAY;
-            } else if (isNaN(days) || days < CUSTOM_EXPIRY_MIN_DAYS || days > CUSTOM_EXPIRY_MAX_DAYS) {
+            } else if (!Number.isInteger(days) || days < CUSTOM_EXPIRY_MIN_DAYS || days > CUSTOM_EXPIRY_MAX_DAYS) {
                 errors.customExpiryDays =
                     t("applications:clientSecrets.wizard.customExpiry.validations.invalid");
             }

Also applies to: 188-204

🤖 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
`@features/admin.applications.v1/components/client-secrets/generate-client-secret-modal.tsx`
around lines 131 - 142, Update validateForm to reject customExpiryDays values
that are not integers while preserving the existing 1–3650 range validation. In
resolveExpiresAt, floor the computed custom expiration timestamp so expiresAt is
always an integer Unix epoch value.

Comment on lines +1038 to +1064
const resolveClientSecretsDangerZone = (): ReactElement => {

const oidcConfig: OIDCDataInterface = inboundProtocolConfig?.[ SupportedAuthProtocolTypes.OIDC ];

/*
* Public clients (SPAs, mobile apps) authenticate without a client secret, so there is
* nothing to revoke or regenerate for them. System and default applications are excluded
* as well, to mirror the client secrets listing.
*/
if (!isMultipleClientSecretsEnabled
|| isSystemApplication
|| isDefaultApplication
|| !oidcConfig?.clientId
|| oidcConfig?.publicClient
|| oidcConfig?.state === State.REVOKED) {

return null;
}

return (
<RevokeAllClientSecretsDangerZone
onRevokeAll={ handleRevokeAllClientSecrets }
readOnly={ readOnly || !hasApplicationUpdatePermissions }
data-componentid={ `${ componentId }-revoke-all-danger-zone` }
/>
);
};

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Scope the danger zone to the OIDC protocol view.

resolveClientSecretsDangerZone gates on the presence of an OIDC inbound config, but Line 1446 renders it unconditionally next to resolveInboundProtocolSettingsForm(). For an application that has both SAML and OIDC inbound protocols, the "Revoke All Client Secrets" danger zone appears while the user views the SAML settings. The action then applies to a protocol that is not on screen.

Add a selectedProtocol check to the guard.

🐛 Proposed guard
         const oidcConfig: OIDCDataInterface = inboundProtocolConfig?.[ SupportedAuthProtocolTypes.OIDC ];
 
+        const isOIDCProtocolSelected: boolean = selectedProtocol === SupportedAuthProtocolTypes.OIDC
+            || selectedProtocol === SupportedAuthProtocolTypes.OAUTH2_OIDC
+            || selectedProtocol === SupportedAuthProtocolTypes.OAUTH2;
+
         if (!isMultipleClientSecretsEnabled
+            || !isOIDCProtocolSelected
             || isSystemApplication
             || isDefaultApplication
             || !oidcConfig?.clientId
             || oidcConfig?.publicClient
             || oidcConfig?.state === State.REVOKED) {
 
             return null;
         }

Also applies to: 1446-1446

🤖 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 `@features/admin.applications.v1/components/settings/access-configuration.tsx`
around lines 1038 - 1064, Update resolveClientSecretsDangerZone to require the
selected protocol to be OIDC in its existing guard, alongside the inbound OIDC
configuration checks. Ensure the RevokeAllClientSecretsDangerZone is not
rendered when viewing SAML settings, while preserving the current exclusions and
behavior for the OIDC view.

Comment on lines +199 to +203
content: "If you delete this client secret, the application will no longer be able to " +
"authenticate. However, active tokens and authorization codes will not be revoked.",
header: "Are you sure?",
message: "This action is irreversible and permanently deletes the client secret. " +
"Please proceed with caution."

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Correct the delete confirmation scope.

Multiple client secrets can remain valid after this operation. The current text states that the whole application can no longer authenticate.

State that clients using the deleted secret can no longer authenticate. State that other client secrets remain valid.

Proposed fix
- content: "If you delete this client secret, the application will no longer be able to " +
-     "authenticate. However, active tokens and authorization codes will not be revoked.",
+ content: "Clients using this client secret will no longer be able to authenticate. " +
+     "Other client secrets remain valid. Active tokens and authorization codes will not be revoked.",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
content: "If you delete this client secret, the application will no longer be able to " +
"authenticate. However, active tokens and authorization codes will not be revoked.",
header: "Are you sure?",
message: "This action is irreversible and permanently deletes the client secret. " +
"Please proceed with caution."
content: "Clients using this client secret will no longer be able to authenticate. " +
"Other client secrets remain valid. Active tokens and authorization codes will not be revoked.",
header: "Are you sure?",
message: "This action is irreversible and permanently deletes the client secret. " +
"Please proceed with caution."
🤖 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 `@modules/i18n/src/translations/en-US/portals/applications.ts` around lines 199
- 203, Update the delete confirmation content near the client-secret
confirmation strings so it states that clients using the deleted secret can no
longer authenticate, while explicitly clarifying that other client secrets
remain valid; preserve the existing notice about active tokens and authorization
codes unless it conflicts with this scope.

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.

2 participants