Skip to content

FLPATH-3243 | [UI] Plugin skeleton - App shell and navigation#2425

Open
asmasarw wants to merge 6 commits intoredhat-developer:mainfrom
asmasarw:feature/dcm-nav
Open

FLPATH-3243 | [UI] Plugin skeleton - App shell and navigation#2425
asmasarw wants to merge 6 commits intoredhat-developer:mainfrom
asmasarw:feature/dcm-nav

Conversation

@asmasarw
Copy link
Contributor

@asmasarw asmasarw commented Mar 3, 2026

FLPATH-3243 | [UI] Plugin skeleton - App shell and navigation

  • Added Sidebar Administrator Menu
  • Added Sub-Menu Items (RBAC and DCM)
  • Add API Proxy
  • Enable RBAC Permissions
image

@rhdh-gh-app
Copy link

rhdh-gh-app bot commented Mar 3, 2026

Missing Changesets

The following package(s) are changed by this PR but do not have a changeset:

  • @red-hat-developer-hub/backstage-plugin-dcm-backend
  • @red-hat-developer-hub/backstage-plugin-dcm-common

See CONTRIBUTING.md for more information about how to add changesets.

Changed Packages

Package Name Package Path Changeset Bump Current Version
app workspaces/dcm/packages/app none v0.0.1
backend workspaces/dcm/packages/backend none v0.0.1
@red-hat-developer-hub/backstage-plugin-dcm-backend workspaces/dcm/plugins/dcm-backend none v0.1.0
@red-hat-developer-hub/backstage-plugin-dcm-common workspaces/dcm/plugins/dcm-common none v0.1.0

@rhdh-qodo-merge
Copy link

Review Summary by Qodo

Implement DCM backend API with RBAC and sidebar navigation

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Implement DCM backend API with token and access endpoints
• Add RBAC permission integration and authorization checks
• Create collapsible Administration sidebar menu with DCM and RBAC items
• Configure permission system with RBAC backend plugin integration
• Add SSO token management with caching and configuration support
Diagram
flowchart LR
  A["Backend Plugin"] -->|"registers"| B["RBAC Backend"]
  A -->|"registers"| C["DCM Backend"]
  C -->|"provides"| D["/token endpoint"]
  C -->|"provides"| E["/access endpoint"]
  D -->|"fetches from"| F["Red Hat SSO"]
  E -->|"checks"| G["RBAC Permissions"]
  H["Frontend Root"] -->|"renders"| I["Administration Menu"]
  I -->|"contains"| J["DCM Submenu"]
  I -->|"contains"| K["RBAC Submenu"]
Loading

Grey Divider

File Changes

1. workspaces/dcm/packages/backend/src/index.ts ⚙️ Configuration changes +7/-2

Reorder backend plugins and add RBAC integration

workspaces/dcm/packages/backend/src/index.ts


2. workspaces/dcm/plugins/dcm-backend/src/models/GetTokenResponse.ts ✨ Enhancement +24/-0

Define token response interface with expiration

workspaces/dcm/plugins/dcm-backend/src/models/GetTokenResponse.ts


3. workspaces/dcm/plugins/dcm-backend/src/models/RouterOptions.ts ✨ Enhancement +32/-0

Define router configuration with service dependencies

workspaces/dcm/plugins/dcm-backend/src/models/RouterOptions.ts


View more (21)
4. workspaces/dcm/plugins/dcm-backend/src/plugin.ts ✨ Enhancement +27/-11

Refactor plugin initialization with auth policies

workspaces/dcm/plugins/dcm-backend/src/plugin.ts


5. workspaces/dcm/plugins/dcm-backend/src/router.ts ✨ Enhancement +17/-39

Replace template routes with token and access endpoints

workspaces/dcm/plugins/dcm-backend/src/router.ts


6. workspaces/dcm/plugins/dcm-backend/src/routes/access.ts ✨ Enhancement +50/-0

Implement access authorization endpoint with RBAC

workspaces/dcm/plugins/dcm-backend/src/routes/access.ts


7. workspaces/dcm/plugins/dcm-backend/src/routes/token.ts ✨ Enhancement +64/-0

Implement SSO token retrieval from Red Hat SSO

workspaces/dcm/plugins/dcm-backend/src/routes/token.ts


8. workspaces/dcm/plugins/dcm-backend/src/util/constant.ts ⚙️ Configuration changes +17/-0

Define default SSO base URL constant

workspaces/dcm/plugins/dcm-backend/src/util/constant.ts


9. workspaces/dcm/plugins/dcm-backend/src/util/tokenUtil.ts ✨ Enhancement +92/-0

Implement token caching and refresh logic

workspaces/dcm/plugins/dcm-backend/src/util/tokenUtil.ts


10. workspaces/dcm/plugins/dcm-common/src/index.ts ✨ Enhancement +2/-0

Export RBAC permission definitions from common

workspaces/dcm/plugins/dcm-common/src/index.ts


11. workspaces/dcm/plugins/dcm-common/src/permissions.ts ✨ Enhancement +26/-0

Define DCM plugin read permission for RBAC

workspaces/dcm/plugins/dcm-common/src/permissions.ts


12. workspaces/dcm/examples/template/content/index.js 📝 Documentation +17/-0

Add example template content file

workspaces/dcm/examples/template/content/index.js


13. workspaces/dcm/app-config.yaml ⚙️ Configuration changes +17/-0

Configure RBAC permission system with admin users

workspaces/dcm/app-config.yaml


14. workspaces/dcm/examples/entities.yaml 📝 Documentation +41/-0

Add example catalog entities for testing

workspaces/dcm/examples/entities.yaml


15. workspaces/dcm/examples/org.yaml 📝 Documentation +17/-0

Add example user and group definitions

workspaces/dcm/examples/org.yaml


16. workspaces/dcm/examples/template/content/catalog-info.yaml 📝 Documentation +8/-0

Add example component catalog metadata

workspaces/dcm/examples/template/content/catalog-info.yaml


17. workspaces/dcm/examples/template/content/package.json 📝 Documentation +5/-0

Add example Node.js package configuration

workspaces/dcm/examples/template/content/package.json


18. workspaces/dcm/examples/template/template.yaml 📝 Documentation +74/-0

Add scaffolder template for Node.js service

workspaces/dcm/examples/template/template.yaml


19. workspaces/dcm/package.json Dependencies +3/-1

Add Backstage API version resolutions

workspaces/dcm/package.json


20. workspaces/dcm/packages/app/src/App.tsx Formatting +1/-0

Add React import statement

workspaces/dcm/packages/app/src/App.tsx


21. workspaces/dcm/packages/app/src/components/Root/Root.tsx ✨ Enhancement +196/-32

Implement collapsible Administration menu with submenu items

workspaces/dcm/packages/app/src/components/Root/Root.tsx


22. workspaces/dcm/packages/backend/package.json Dependencies +3/-0

Add RBAC and events backend plugin dependencies

workspaces/dcm/packages/backend/package.json


23. workspaces/dcm/plugins/dcm-backend/package.json Dependencies +4/-0

Add permission and DCM common plugin dependencies

workspaces/dcm/plugins/dcm-backend/package.json


24. workspaces/dcm/plugins/dcm-common/package.json Dependencies +2/-1

Add permission common plugin dependency

workspaces/dcm/plugins/dcm-common/package.json


Grey Divider

Qodo Logo

@rhdh-qodo-merge
Copy link

rhdh-qodo-merge bot commented Mar 3, 2026

Code Review by Qodo

🐞 Bugs (4) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Guest RBAC superuser 🐞 Bug ⛨ Security
Description
Guest auth is enabled and auto-sign-in is configured, but the guest user is also configured as an
RBAC admin and superUser, effectively bypassing authorization for anyone who can access the app.
Code

workspaces/dcm/app-config.yaml[R46-61]

+permission:
+  enabled: true
+  rbac:
+    policyFileReload: true
+    pluginsWithPermission:
+      - catalog
+      - scaffolder
+      - permission
+      - dcm
+    admin:
+      users:
+        - name: group:default/admins
+        - name: user:default/guest
+      superUsers:
+        - name: group:default/admins
+        - name: user:default/guest
Evidence
The DCM workspace enables the guest auth provider and auto-sign-in, while simultaneously granting
that same guest identity admin and superUser roles in RBAC. This combination makes all visitors
privileged by default.

workspaces/dcm/app-config.yaml[42-61]
workspaces/dcm/packages/app/src/App.tsx[79-81]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`user:default/guest` is configured as an RBAC admin and superUser while guest auth + auto sign-in is enabled, making the instance effectively admin-by-default.
### Issue Context
This is a configuration chain issue: guest provider + auto sign-in + guest listed in RBAC admin/superUsers.
### Fix Focus Areas
- workspaces/dcm/app-config.yaml[42-61]
- workspaces/dcm/packages/app/src/App.tsx[79-81]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Un-gated token endpoint 🐞 Bug ⛨ Security
Description
The DCM backend exposes GET /token that returns an RH SSO client_credentials access token to any
user-cookie caller, without performing any permission check; combined with guest auto-sign-in this
can hand out privileged external tokens to arbitrary visitors.
Code

workspaces/dcm/plugins/dcm-backend/src/routes/token.ts[R23-60]

+export const getToken =
+  (options: RouterOptions): RequestHandler =>
+  async (_, response) => {
+    const { logger, config } = options;
+
+    assert(typeof config !== 'undefined', 'Config is undefined');
+
+    logger.info('Requesting new access token');
+
+    const ssoBaseUrl =
+      config.getOptionalString('dcm.ssoBaseUrl') ?? DEFAULT_SSO_BASE_URL;
+    const clientId = config.getString('dcm.clientId');
+    const clientSecret = config.getString('dcm.clientSecret');
+
+    const tokenUrl = `${ssoBaseUrl}/auth/realms/redhat-external/protocol/openid-connect/token`;
+    const body = new URLSearchParams({
+      client_id: clientId,
+      client_secret: clientSecret,
+      scope: 'api.console',
+      grant_type: 'client_credentials',
+    });
+
+    const rhSsoResponse = await fetch(tokenUrl, {
+      method: 'POST',
+      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
+      body: body.toString(),
+    });
+
+    if (rhSsoResponse.ok) {
+      const json = (await rhSsoResponse.json()) as {
+        access_token: string;
+        expires_in: number;
+      };
+      const bodyRes: GetTokenResponse = {
+        accessToken: json.access_token,
+        expiresAt: Date.now() + json.expires_in * 1000,
+      };
+      response.json(bodyRes);
Evidence
The backend explicitly allows user-cookie access to /token, and the handler uses
dcm.clientId/clientSecret to mint a client_credentials token and returns it to the caller. Unlike
/access (which calls the permission evaluator), /token performs no authorization at all.

workspaces/dcm/plugins/dcm-backend/src/plugin.ts[48-55]
workspaces/dcm/plugins/dcm-backend/src/routes/token.ts[32-60]
workspaces/dcm/plugins/dcm-backend/src/routes/access.ts[22-33]
workspaces/dcm/packages/app/src/App.tsx[79-81]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`GET /api/dcm/token` returns an RH SSO `client_credentials` access token to any authenticated user-cookie caller, without any RBAC/permission gate.
### Issue Context
- `/access` performs permission evaluation but `/token` does not.
- Guest auth is configured with auto sign-in, increasing exposure.
### Fix Focus Areas
- workspaces/dcm/plugins/dcm-backend/src/plugin.ts[48-59]
- workspaces/dcm/plugins/dcm-backend/src/routes/token.ts[23-64]
- workspaces/dcm/plugins/dcm-backend/src/routes/access.ts[22-50]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Broken backend tests🐞 Bug ✓ Correctness
Description
The dcm-backend test suite still targets the removed /todos endpoints and the old createRouter
signature, so TypeScript/Jest will fail after the router refactor in this PR.
Code

workspaces/dcm/plugins/dcm-backend/src/router.ts[R25-43]

+export async function createRouter(
+  options: RouterOptions,
+): Promise<express.Router> {
const router = Router();
-  router.use(express.json());
-
-  // TEMPLATE NOTE:
-  // Zod is a powerful library for data validation and recommended in particular
-  // for user-defined schemas. In this case we use it for input validation too.
-  //
-  // If you want to define a schema for your API we recommend using Backstage's
-  // OpenAPI tooling: https://backstage.io/docs/next/openapi/01-getting-started
-  const todoSchema = z.object({
-    title: z.string(),
-    entityRef: z.string().optional(),
-  });
-  router.post('/todos', async (req, res) => {
-    const parsed = todoSchema.safeParse(req.body);
-    if (!parsed.success) {
-      throw new InputError(parsed.error.toString());
-    }
-
-    const result = await todoList.createTodo(parsed.data, {
-      credentials: await httpAuth.credentials(req, { allow: ['user'] }),
-    });
+  router.use(express.json());
-    res.status(201).json(result);
+  const permissionsIntegrationRouter = createPermissionIntegrationRouter({
+    permissions: dcmPluginPermissions,
});
+  router.use(permissionsIntegrationRouter);
-  router.get('/todos', async (_req, res) => {
-    res.json(await todoList.listTodos());
+  router.get('/health', (_req, res) => {
+    res.json({ status: 'ok' });
});
-  router.get('/todos/:id', async (req, res) => {
-    res.json(await todoList.getTodo({ id: req.params.id }));
-  });
+  router.get('/token', getToken(options));
+  router.get('/access', getAccess(options));
Evidence
This PR changes the router to accept a new RouterOptions object and removes the old /todos
endpoints, but the existing router/plugin tests still construct the router with {httpAuth,todoList}
and make requests to /todos and /api/dcm/todos.

workspaces/dcm/plugins/dcm-backend/src/router.ts[25-43]
workspaces/dcm/plugins/dcm-backend/src/router.test.ts[47-60]
workspaces/dcm/plugins/dcm-backend/src/plugin.test.ts[43-69]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
After refactoring the backend router away from the TODO template endpoints, the existing Jest tests still compile against the old API shape and routes.
### Issue Context
- Router now expects `RouterOptions` and exposes `/health`, `/token`, `/access`.
- Tests still call `/todos` endpoints and pass `{ httpAuth, todoList }`.
### Fix Focus Areas
- workspaces/dcm/plugins/dcm-backend/src/router.ts[25-45]
- workspaces/dcm/plugins/dcm-backend/src/router.test.ts[41-83]
- workspaces/dcm/plugins/dcm-backend/src/plugin.test.ts[37-145]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

4. No token caching 🐞 Bug ➹ Performance
Description
The /token handler always calls RH SSO even though a cache-backed tokenUtil exists, increasing
latency and risk of upstream throttling under load.
Code

workspaces/dcm/plugins/dcm-backend/src/routes/token.ts[R45-49]

+    const rhSsoResponse = await fetch(tokenUrl, {
+      method: 'POST',
+      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
+      body: body.toString(),
+    });
Evidence
The /token route fetches a new token on each call, while tokenUtil.ts already implements caching
with an expiry buffer. The util is currently unused by the /token handler.

workspaces/dcm/plugins/dcm-backend/src/routes/token.ts[45-49]
workspaces/dcm/plugins/dcm-backend/src/util/tokenUtil.ts[30-48]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`GET /token` always fetches a fresh RH SSO token even though a cache-backed token retrieval utility exists.
### Issue Context
This can increase latency and upstream SSO load; the repo already has a caching pattern for similar plugins.
### Fix Focus Areas
- workspaces/dcm/plugins/dcm-backend/src/routes/token.ts[23-64]
- workspaces/dcm/plugins/dcm-backend/src/util/tokenUtil.ts[21-92]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment on lines +46 to +61
permission:
enabled: true
rbac:
policyFileReload: true
pluginsWithPermission:
- catalog
- scaffolder
- permission
- dcm
admin:
users:
- name: group:default/admins
- name: user:default/guest
superUsers:
- name: group:default/admins
- name: user:default/guest

Choose a reason for hiding this comment

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

Action required

1. Guest rbac superuser 🐞 Bug ⛨ Security

Guest auth is enabled and auto-sign-in is configured, but the guest user is also configured as an
RBAC admin and superUser, effectively bypassing authorization for anyone who can access the app.
Agent Prompt
### Issue description
`user:default/guest` is configured as an RBAC admin and superUser while guest auth + auto sign-in is enabled, making the instance effectively admin-by-default.

### Issue Context
This is a configuration chain issue: guest provider + auto sign-in + guest listed in RBAC admin/superUsers.

### Fix Focus Areas
- workspaces/dcm/app-config.yaml[42-61]
- workspaces/dcm/packages/app/src/App.tsx[79-81]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +23 to +60
export const getToken =
(options: RouterOptions): RequestHandler =>
async (_, response) => {
const { logger, config } = options;

assert(typeof config !== 'undefined', 'Config is undefined');

logger.info('Requesting new access token');

const ssoBaseUrl =
config.getOptionalString('dcm.ssoBaseUrl') ?? DEFAULT_SSO_BASE_URL;
const clientId = config.getString('dcm.clientId');
const clientSecret = config.getString('dcm.clientSecret');

const tokenUrl = `${ssoBaseUrl}/auth/realms/redhat-external/protocol/openid-connect/token`;
const body = new URLSearchParams({
client_id: clientId,
client_secret: clientSecret,
scope: 'api.console',
grant_type: 'client_credentials',
});

const rhSsoResponse = await fetch(tokenUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: body.toString(),
});

if (rhSsoResponse.ok) {
const json = (await rhSsoResponse.json()) as {
access_token: string;
expires_in: number;
};
const bodyRes: GetTokenResponse = {
accessToken: json.access_token,
expiresAt: Date.now() + json.expires_in * 1000,
};
response.json(bodyRes);

Choose a reason for hiding this comment

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

Action required

2. Un-gated token endpoint 🐞 Bug ⛨ Security

The DCM backend exposes GET /token that returns an RH SSO client_credentials access token to any
user-cookie caller, without performing any permission check; combined with guest auto-sign-in this
can hand out privileged external tokens to arbitrary visitors.
Agent Prompt
### Issue description
`GET /api/dcm/token` returns an RH SSO `client_credentials` access token to any authenticated user-cookie caller, without any RBAC/permission gate.

### Issue Context
- `/access` performs permission evaluation but `/token` does not.
- Guest auth is configured with auto sign-in, increasing exposure.

### Fix Focus Areas
- workspaces/dcm/plugins/dcm-backend/src/plugin.ts[48-59]
- workspaces/dcm/plugins/dcm-backend/src/routes/token.ts[23-64]
- workspaces/dcm/plugins/dcm-backend/src/routes/access.ts[22-50]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +25 to 43
export async function createRouter(
options: RouterOptions,
): Promise<express.Router> {
const router = Router();
router.use(express.json());

// TEMPLATE NOTE:
// Zod is a powerful library for data validation and recommended in particular
// for user-defined schemas. In this case we use it for input validation too.
//
// If you want to define a schema for your API we recommend using Backstage's
// OpenAPI tooling: https://backstage.io/docs/next/openapi/01-getting-started
const todoSchema = z.object({
title: z.string(),
entityRef: z.string().optional(),
});

router.post('/todos', async (req, res) => {
const parsed = todoSchema.safeParse(req.body);
if (!parsed.success) {
throw new InputError(parsed.error.toString());
}

const result = await todoList.createTodo(parsed.data, {
credentials: await httpAuth.credentials(req, { allow: ['user'] }),
});
router.use(express.json());

res.status(201).json(result);
const permissionsIntegrationRouter = createPermissionIntegrationRouter({
permissions: dcmPluginPermissions,
});
router.use(permissionsIntegrationRouter);

router.get('/todos', async (_req, res) => {
res.json(await todoList.listTodos());
router.get('/health', (_req, res) => {
res.json({ status: 'ok' });
});

router.get('/todos/:id', async (req, res) => {
res.json(await todoList.getTodo({ id: req.params.id }));
});
router.get('/token', getToken(options));
router.get('/access', getAccess(options));

This comment was marked as resolved.

@asmasarw asmasarw requested a review from jkilzi as a code owner March 5, 2026 08:18
@sonarqubecloud
Copy link

sonarqubecloud bot commented Mar 5, 2026

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.

1 participant