Skip to content

Add copy to clipboard buttons to display token page#200

Open
vojtechszocs wants to merge 3 commits into
openshift:masterfrom
vojtechszocs:token-page-copy-to-clipboard
Open

Add copy to clipboard buttons to display token page#200
vojtechszocs wants to merge 3 commits into
openshift:masterfrom
vojtechszocs:token-page-copy-to-clipboard

Conversation

@vojtechszocs

@vojtechszocs vojtechszocs commented Oct 29, 2025

Copy link
Copy Markdown

This PR modifies the token display page by adding "Show" and "Copy" buttons.

Initially, all snippets have their content hidden using *** placeholder text.


Click on "Show" button to reveal the content and make the "Show" button disappear for that snippet.


Click on "Copy" button to copy the content to clipboard using the standard Clipboard API.


Clicking the "Copy" button changes its text and disables it for 3 seconds, then resets the button to its normal state.

  • success - shows "Copied!"
  • error - shows "Error!" - note 📝 error is logged to browser console using console.error

Summary by CodeRabbit

  • New Features
    • Token and command displays now feature interactive "Copy" and "Show" buttons for improved usability.
    • Copy button provides immediate visual feedback confirming successful copying or indicating errors.
    • Command text rendering now generates on the client-side, enabling better interactivity and visual presentation.

@openshift-ci openshift-ci Bot requested review from ibihim and liouk October 29, 2025 19:39
Comment thread pkg/server/tokenrequest/tokenrequest.go Outdated
Comment thread pkg/server/tokenrequest/tokenrequest.go Outdated
Comment thread pkg/server/tokenrequest/tokenrequest.go Outdated
@vojtechszocs vojtechszocs force-pushed the token-page-copy-to-clipboard branch from 0f2e803 to f02b3b4 Compare October 30, 2025 16:47
Comment thread pkg/server/tokenrequest/tokenrequest.go Outdated
Comment thread pkg/server/tokenrequest/tokenrequest.go Outdated
@vojtechszocs vojtechszocs force-pushed the token-page-copy-to-clipboard branch 2 times, most recently from 3632827 to 3cd1e22 Compare November 3, 2025 18:39
Comment thread pkg/server/tokenrequest/tokenrequest.go Outdated
@vojtechszocs vojtechszocs force-pushed the token-page-copy-to-clipboard branch from 3cd1e22 to c9a96d7 Compare November 3, 2025 18:54

@spadgett spadgett left a comment

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.

/lgtm

Thanks @vojtechszocs 👍

/hold
for @everettraven to review

@openshift-ci openshift-ci Bot added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Nov 3, 2025
@openshift-ci openshift-ci Bot added the lgtm Indicates that a PR is ready to be merged. label Nov 3, 2025
@vojtechszocs

Copy link
Copy Markdown
Author

/retest

@everettraven everettraven 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.

This seems fine to me. I don't think I have approval permissions. Will likely need @ibihim for approval.

/lgtm

Comment thread pkg/server/tokenrequest/tokenrequest.go Outdated
<code>{{.AccessToken}}</code>

<script>
var commands = {

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.

Maybe this is my old school JS, but isn't it better to have something like this namespaced approach:

  var openshift = openshift || {};
  openshift.commands = openshift.commands || {};
  openshift.commands.login = '...';
  openshift.commands.apiCall = '...';

or a IIFE that is completely isolated?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Before this PR, the token request page had no JavaScript so I'm not sure if using the openshift variable to namespace the commands is really needed.

I wanted to keep the changes at minimum so I went with a single commands variable to hold the strings to copy.

var commands = { foo: 'x' };

is functionally the same as

var commands = {};
commands.foo = 'x';

As for IIFE, these are typically used to avoid pollution of the global object namespace but I don't think we need them in our case.

The idea is to define some global object to store the strings to be referenced by the copy button's onclick handler.

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.

I still think that it is always worth to introduce best practices, but nevertheless, this isn't a thing that I would keep blocking the PR.

Comment thread pkg/server/tokenrequest/tokenrequest.go Outdated
oc login
<span class="nowrap">--token={{.AccessToken}}</span>
<span class="nowrap">--server={{.PublicMasterURL}}</span>
<button onclick="navigator.clipboard.writeText(commands.login)">Copy</button>

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.

Shouldn't we add some feedback for the user, like changing the button text to "Copied!" or so?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

This would add more complexity, which is something I wanted to avoid.

The page template is missing standard HTML structure, including <!doctype html>, <head>, <body> etc. which cause browsers to render this page it in "quirks" mode.

I could try to add some custom-styled "Copied!" feedback text upon clicking the button, but do we really need it?

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.

Implemented.

Comment thread pkg/server/tokenrequest/tokenrequest.go Outdated
oc login
<span class="nowrap">--token={{.AccessToken}}</span>
<span class="nowrap">--server={{.PublicMasterURL}}</span>
<button onclick="navigator.clipboard.writeText(commands.login)">Copy</button>

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.

https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/writeText

Isn't it best practice to handle the error somehow?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

We just call the clipboard.writeText function without handling the resulting Promise.

This function is async and resolves when the clipboard's contents have been updated. If we had a "Copied!" feedback text implemented, we'd be handing the Promise, but currently we don't need that.

I'm not sure we need an explicit error handler here, unless we want to implement the feedback text, could be something like "Failed to copy the snippet to clipboard, see browser logs for details" or similar.

In practice, clipboard.writeText function could fail if the page was forbidden to copy text to clipboard via setting page permissions, e.g. for Chromium based browsers [1]:

Writing requires either the clipboard-write permission or transient activation.

[1] https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API#security_considerations

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.

@vojtechszocs Maybe just throw up an alert on an error? Something like...? "Copy to clipboard failed. Select the content to copy it manually."

In practice, I'd expect clipboard write to almost never fail.

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.

Implemented

@vojtechszocs vojtechszocs force-pushed the token-page-copy-to-clipboard branch from c9a96d7 to 412eb21 Compare December 10, 2025 19:58
@openshift-ci openshift-ci Bot removed the lgtm Indicates that a PR is ready to be merged. label Dec 10, 2025
@ibihim

ibihim commented Dec 19, 2025

Copy link
Copy Markdown
Contributor

I wish there would have been a more detailed explanation why it is fine to get rid of the intermediate CSRF step, but after bouncing my head for an hour on this topic, the worst thing I could imagine is that a disgruntled Alice could sneak in her tokens on your page.

@ibihim

ibihim commented Dec 19, 2025

Copy link
Copy Markdown
Contributor

I will approve once @everettraven lgtm'ed it... or @liouk

@everettraven everettraven 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.

I'm concerned about us making a breaking change to the acceptable requests here - because it is out there, and has been out there for a while, I would not be surprised if there is some kind of end user workflow that depends on sending a properly formed POST request to display the token.

Additionally, do we have any kind of testing in place for UX/UI related changes we can update / put in place here?

If there is not existing testing infrastructure here it isn't the end of the world, but if it exists we should make sure the UX changes are appropriately tested.

Comment thread pkg/server/tokenrequest/tokenrequest.go
<input type="hidden" name="code" value="{{.Code}}">
<input type="hidden" name="csrf" value="{{.CSRF}}">
<button type="submit">
Display Token

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.

We can't remove this. This was there to prevent cross-site request forgery.

/hold

@openshift-bot

Copy link
Copy Markdown
Contributor

Issues go stale after 90d of inactivity.

Mark the issue as fresh by commenting /remove-lifecycle stale.
Stale issues rot after an additional 30d of inactivity and eventually close.
Exclude this issue from closing by commenting /lifecycle frozen.

If this issue is safe to close now please do so with /close.

/lifecycle stale

@openshift-ci openshift-ci Bot added the lifecycle/stale Denotes an issue or PR has remained open with no activity and has become stale. label Mar 20, 2026
@coderabbitai

coderabbitai Bot commented Mar 20, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 34ba435c-42aa-412a-86a2-df66ebbcf558

📥 Commits

Reviewing files that changed from the base of the PR and between 34ca6e5 and 89206d3.

📒 Files selected for processing (1)
  • pkg/server/tokenrequest/tokenrequest.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/server/tokenrequest/tokenrequest.go

Walkthrough

Token HTML rendering moved client-side: server now injects JS-safe strings into the template; three empty <pre> placeholders are populated by a client-side codeSnippet function that formats commands, adds Copy/Show buttons, and uses navigator.clipboard.writeText for copying.

Changes

Cohort / File(s) Summary
Token Display Client-Side Rendering
pkg/server/tokenrequest/tokenrequest.go
Reworked token rendering from server-side static HTML to client-side JS population. tokenData now includes JS-safe AccessTokenJSStr and PublicMasterURLJSStr. Template replaces inline <pre> content with empty placeholders and injects a codeSnippet JS function that formats text into nowrap spans, appends Copy/Show buttons, and uses the Clipboard API with transient success/error button states. CSS updated for button positioning and disabled styling inside <pre> elements.

Sequence Diagram(s)

sequenceDiagram
  participant Server
  participant Browser
  participant Clipboard
  participant User

  Server->>Browser: Serve template with `AccessTokenJSStr` and `PublicMasterURLJSStr`
  Browser->>Browser: Populate three `<pre>` placeholders via `codeSnippet` (render spans, add Copy/Show buttons)
  User->>Browser: Click "Copy"
  Browser->>Clipboard: navigator.clipboard.writeText(commandText)
  Clipboard-->>Browser: Resolve/Reject
  Browser-->>User: Update button state (Copied / Error / Show toggles)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 11 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (11 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly and directly describes the main change: adding copy to clipboard buttons to the display token page.
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.
Stable And Deterministic Test Names ✅ Passed PR modifies only pkg/server/tokenrequest/tokenrequest.go with no test files or Ginkgo tests affected.
Test Structure And Quality ✅ Passed This PR modifies only source code (tokenrequest.go), not test files, so the Ginkgo test quality check is not applicable.
Microshift Test Compatibility ✅ Passed PR modifies pkg/server/tokenrequest/tokenrequest.go for client-side functionality; no new Ginkgo e2e tests added.
Single Node Openshift (Sno) Test Compatibility ✅ Passed This PR modifies only tokenrequest.go with HTML/CSS/JavaScript changes for copy-to-clipboard functionality. No Ginkgo e2e tests are added or modified, making the SNO test compatibility check not applicable.
Topology-Aware Scheduling Compatibility ✅ Passed This PR modifies only pkg/server/tokenrequest/tokenrequest.go to add copy-to-clipboard functionality, without any changes to deployment manifests, operator code, or controllers. Topology-aware scheduling compatibility check is not applicable.
Ote Binary Stdout Contract ✅ Passed The OTE Binary Stdout Contract check is not applicable as this repository is a standard OAuth server service, not an OTE test binary, and modified code contains only HTTP handlers without process-level stdout writes.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed PR does not add any new Ginkgo e2e tests; changes are limited to template rendering and client-side JavaScript functionality.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Review rate limit: 9/10 reviews remaining, refill in 6 minutes.

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

@openshift-bot

Copy link
Copy Markdown
Contributor

Stale issues rot after 30d of inactivity.

Mark the issue as fresh by commenting /remove-lifecycle rotten.
Rotten issues close after an additional 30d of inactivity.
Exclude this issue from closing by commenting /lifecycle frozen.

If this issue is safe to close now please do so with /close.

/lifecycle rotten
/remove-lifecycle stale

@openshift-ci openshift-ci Bot added lifecycle/rotten Denotes an issue or PR that has aged beyond stale and will be auto-closed. and removed lifecycle/stale Denotes an issue or PR has remained open with no activity and has become stale. labels Apr 19, 2026
@vojtechszocs vojtechszocs force-pushed the token-page-copy-to-clipboard branch from 412eb21 to 34ca6e5 Compare April 28, 2026 15:42

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
pkg/server/tokenrequest/tokenrequest.go (1)

147-148: ⚠️ Potential issue | 🟠 Major

Avoid template.JSStr for runtime values and avoid innerHTML rendering for snippets.

On Line 147-Line 148 and Line 183-Line 186, casting runtime data to template.JSStr marks it as trusted. Combined with Line 274-Line 275 (innerHTML), this weakens escaping guarantees and can become an injection vector if token/URL formats ever change.

Proposed fix
@@
-	data.AccessTokenJSStr = template.JSStr(data.AccessToken)
-	data.PublicMasterURLJSStr = template.JSStr(data.PublicMasterURL)
+	data.AccessTokenJSStr = data.AccessToken
+	data.PublicMasterURLJSStr = data.PublicMasterURL
@@
-	AccessToken          string
-	AccessTokenJSStr     template.JSStr
-	PublicMasterURL      string
-	PublicMasterURLJSStr template.JSStr
-	LogoutURL            string
+	AccessToken          string
+	AccessTokenJSStr     string
+	PublicMasterURL      string
+	PublicMasterURLJSStr string
+	LogoutURL            string
@@
-      const snippetHTML = textBlocks
-        .map((text) => '<span class="nowrap">' + text + '</span>')
-        .join(' ');
+      const renderSnippet = (container) => {
+        container.textContent = '';
+        textBlocks.forEach((text, idx) => {
+          if (idx > 0) container.appendChild(document.createTextNode(' '));
+          const span = document.createElement('span');
+          span.className = 'nowrap';
+          span.textContent = text;
+          container.appendChild(span);
+        });
+      };
@@
-      showButton.onclick = () => {
-        e.innerHTML = snippetHTML;
-        e.appendChild(copyButton);
-      };
+      showButton.onclick = () => {
+        renderSnippet(e);
+        e.appendChild(copyButton);
+      };
In Go's html/template, does casting user/runtime strings to template.JSStr bypass contextual escaping, and is this recommended for untrusted input in <script> string contexts?

Also applies to: 183-186, 243-245, 274-275, 295-307

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

In `@pkg/server/tokenrequest/tokenrequest.go` around lines 147 - 148, The code
currently casts runtime values to template.JSStr (data.AccessTokenJSStr,
data.PublicMasterURLJSStr) and later relies on innerHTML, which bypasses
contextual escaping; remove those template.JSStr casts and instead pass the raw
strings (data.AccessToken, data.PublicMasterURL) into the template, update the
template to avoid innerHTML by setting textContent or using data-attributes (or
render a JSON-encoded value via json.Marshal and only mark the already-encoded
JSON safe with template.JS), and ensure any place that previously read
AccessTokenJSStr/PublicMasterURLJSStr in the template is updated to use the safe
rendering approach so runtime values are escaped by html/template rather than
trusted unconditionally.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@pkg/server/tokenrequest/tokenrequest.go`:
- Around line 147-148: The code currently casts runtime values to template.JSStr
(data.AccessTokenJSStr, data.PublicMasterURLJSStr) and later relies on
innerHTML, which bypasses contextual escaping; remove those template.JSStr casts
and instead pass the raw strings (data.AccessToken, data.PublicMasterURL) into
the template, update the template to avoid innerHTML by setting textContent or
using data-attributes (or render a JSON-encoded value via json.Marshal and only
mark the already-encoded JSON safe with template.JS), and ensure any place that
previously read AccessTokenJSStr/PublicMasterURLJSStr in the template is updated
to use the safe rendering approach so runtime values are escaped by
html/template rather than trusted unconditionally.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: c316f08e-9d51-4f66-9600-9d42e3910e7e

📥 Commits

Reviewing files that changed from the base of the PR and between 3aa95c0 and 34ca6e5.

📒 Files selected for processing (1)
  • pkg/server/tokenrequest/tokenrequest.go

@vojtechszocs

Copy link
Copy Markdown
Author

Sorry, this PR got stuck in a limbo state.

@ibihim

I wish there would have been a more detailed explanation why it is fine to get rid of the intermediate CSRF step, but after bouncing my head for an hour on this topic, the worst thing I could imagine is that a disgruntled Alice could sneak in her tokens on your page.

AFAIK we have customer RFEs that suggested removing the intermediate "Display Token" form submit (CSRF) step since it's one more page to go through when accessing token data.

That said, I've removed this change from the PR to preserve the existing behavior.

@everettraven

I'm concerned about us making a breaking change to the acceptable requests here - because it is out there, and has been out there for a while, I would not be surprised if there is some kind of end user workflow that depends on sending a properly formed POST request to display the token.

That makes perfect sense 👍

Comment thread pkg/server/tokenrequest/tokenrequest.go Outdated

@everettraven everettraven 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.

Changes seem fine to me. Will defer to @spadgett for any web-specific concerns.

@everettraven

Copy link
Copy Markdown
Contributor

/lgtm

@openshift-ci openshift-ci Bot added the lgtm Indicates that a PR is ready to be merged. label Apr 29, 2026
@openshift-ci openshift-ci Bot removed the lgtm Indicates that a PR is ready to be merged. label Apr 29, 2026
@vojtechszocs

Copy link
Copy Markdown
Author

PR updated to ensure that snippet content is treated as text only.

This can be tested by manually changing the snippet text blocks inside tokenTemplate for example:

codeSnippet(document.getElementById('token'), [
  '{{.AccessTokenJSStr}} <foo>&test</foo>',
]);

@everettraven

Copy link
Copy Markdown
Contributor

/approve

@spadgett spadgett left a comment

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.

/lgtm

@openshift-ci openshift-ci Bot added the lgtm Indicates that a PR is ready to be merged. label Apr 29, 2026
@openshift-ci

openshift-ci Bot commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: everettraven, spadgett, vojtechszocs
Once this PR has been reviewed and has the lgtm label, please assign deads2k for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@vojtechszocs

Copy link
Copy Markdown
Author

/hold cancel

@openshift-ci openshift-ci Bot removed the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Apr 30, 2026
@vojtechszocs

Copy link
Copy Markdown
Author

/retest

1 similar comment
@vojtechszocs

Copy link
Copy Markdown
Author

/retest

@openshift-ci

openshift-ci Bot commented May 18, 2026

Copy link
Copy Markdown
Contributor

@vojtechszocs: all tests passed!

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lgtm Indicates that a PR is ready to be merged. lifecycle/rotten Denotes an issue or PR that has aged beyond stale and will be auto-closed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants