Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
364cf13
chore: add session wide gh pat feature definition
matejvasek Apr 13, 2026
dfcad97
feat: do not use login step for ocp internal registry
matejvasek Apr 13, 2026
d956234
chore: move the plugin /functions => /functions2
matejvasek Apr 13, 2026
04f7ff3
docs: add session-wide GH PAT design spec
matejvasek Apr 13, 2026
ca446cc
feat: add usePat hook backed by sessionStorage
matejvasek Apr 13, 2026
e42058f
feat: add PatModal component with PAT validation
matejvasek Apr 13, 2026
883d5ab
refactor: useSourceControl accepts pat parameter instead of build-tim…
matejvasek Apr 13, 2026
96fab05
refactor: update FunctionsListPage to call useSourceControl with pat …
matejvasek Apr 13, 2026
a9886a7
feat: integrate session PAT into FunctionsListPage
matejvasek Apr 13, 2026
8d290e4
refactor: remove PAT field from CreateFunctionForm
matejvasek Apr 13, 2026
e302d85
feat: integrate session PAT into FunctionCreatePage with PatModal gate
matejvasek Apr 13, 2026
e302d40
chore: remove build-time __GITHUB_PAT__ from webpack DefinePlugin and…
matejvasek Apr 13, 2026
b520b24
chore: add i18n strings for PatModal
matejvasek Apr 13, 2026
82cf9ac
chore: clean up after session-wide GH PAT feature
matejvasek Apr 13, 2026
1aa820e
chore: update claude progress
matejvasek Apr 13, 2026
2b411b2
fix: remove close button from PatModal
matejvasek Apr 13, 2026
577b5ba
chore: move the plugin /functions2 => /functions
matejvasek Apr 14, 2026
add760b
fixup: docs/plans/completed/004-feat-session-wide-gh-pat.md
matejvasek Apr 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions backend/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"os"
"path/filepath"
"regexp"
"strings"
"sync"

"github.com/ory/viper"
Expand Down Expand Up @@ -163,7 +164,7 @@ func handleFuncCreate(w http.ResponseWriter, r *http.Request) {
return
}

if err := generateCIWorkflow(root, cfg.Branch); err != nil {
if err := generateCIWorkflow(root, cfg.Branch, cfg.Registry); err != nil {
http.Error(w, "failed to generate CI workflow: "+err.Error(), http.StatusInternalServerError)
return
}
Expand Down Expand Up @@ -214,10 +215,14 @@ func handleFuncCreate(w http.ResponseWriter, r *http.Request) {
}
}

func generateCIWorkflow(root, branch string) error {
const ocpInternalRegistry = "image-registry.openshift-image-registry.svc:5000/"

func generateCIWorkflow(root, branch, registry string) error {
ciMu.Lock()
defer ciMu.Unlock()

useRegistryLogin := !strings.HasPrefix(registry, ocpInternalRegistry)

viper.Set(ci.PlatformFlag, ci.DefaultPlatform)
viper.Set(ci.PathFlag, root)
viper.Set(ci.BranchFlag, branch)
Expand All @@ -227,7 +232,7 @@ func generateCIWorkflow(root, branch string) error {
viper.Set(ci.RegistryUserVariableNameFlag, ci.DefaultRegistryUserVariableName)
viper.Set(ci.RegistryPassSecretNameFlag, ci.DefaultRegistryPassSecretName)
viper.Set(ci.RegistryUrlVariableNameFlag, ci.DefaultRegistryUrlVariableName)
viper.Set(ci.UseRegistryLoginFlag, ci.DefaultUseRegistryLogin)
viper.Set(ci.UseRegistryLoginFlag, useRegistryLogin)
viper.Set(ci.WorkflowDispatchFlag, ci.DefaultWorkflowDispatch)
viper.Set(ci.UseRemoteBuildFlag, ci.DefaultUseRemoteBuild)
viper.Set(ci.UseSelfHostedRunnerFlag, ci.DefaultUseSelfHostedRunner)
Expand Down
20 changes: 20 additions & 0 deletions docs/claude-progress.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,26 @@
# Claude Progress Log
# Newest entries first. Agents: append your entry at the top after the header.

---
## 2026-04-13 | Session: Session-Wide GitHub PAT
Worked on: Replace build-time compiled GitHub PAT with runtime session-wide PAT (plan 004)
Completed:
- Design spec: brainstormed approaches, chose React hook + sessionStorage + PatModal, wrote spec to docs/design/
- Implementation plan: 9-task bottom-up plan in docs/plans/active/004, executed via subagent-driven development
- `usePat` hook (`src/hooks/usePat.ts`) backed by sessionStorage key `func-console-gh-pat`
- `PatModal` component (`src/components/PatModal.tsx`) — non-dismissable PF6 modal, validates PAT via Octokit `GET /user`
- `useSourceControl` refactored to accept `pat` parameter with `useMemo` (no more module-level singleton)
- `FunctionsListPage` integrated: `usePat()` + `PatModal` gate, `useFunctionListPage(pat)` skips API when empty
- `FunctionCreatePage` integrated: `usePat()` + `PatModal` gate, passes session PAT to `pushFiles()`
- `CreateFunctionForm` PAT field removed (interface, state, validation, JSX)
- `__GITHUB_PAT__` removed from webpack DefinePlugin and `src/globals.d.ts` deleted
- i18n strings added, webpack build verified
- 9 commits, 46 tests across 11 suites, all passing
- Feature marked as passing in features.json, plan moved to completed/
- feat: do not use login step for OCP internal registry in the generated function GH WF
Left off: Feature complete. Ready for manual testing and PR.
Blockers: None

---
## 2026-04-02 | Session: Function Create Page
Worked on: Full-stack Function Create Page feature (plan 003)
Expand Down
122 changes: 122 additions & 0 deletions docs/design/2026-04-13-session-wide-gh-pat-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Session-Wide GitHub Personal Access Token

**Date:** 2026-04-13
**Status:** Draft
**Feature:** Session wide GitHub Personal Access Token (features.json)

## Problem

The GitHub PAT is currently injected at build time via webpack `DefinePlugin` from the `GITHUB_PAT` environment variable. This compiles the PAT as a string literal into the distributed JavaScript bundle — anyone with access to the bundle can extract it. Additionally, the Function Create form has a separate PAT text input, creating two inconsistent PAT entry points.

## Solution

Replace the build-time PAT with a session-wide runtime PAT. On first visit to any plugin page, a modal prompts the user to enter their PAT. The PAT is validated against the GitHub API, stored in `sessionStorage` (survives refresh, cleared on tab close), and used by all services that need GitHub access.

## Architecture

### PAT Storage Hook — `usePat`

Location: `src/hooks/usePat.ts`

A custom hook backed by `sessionStorage`. Returns `{ pat, setPat, clearPat }`.

- Reads initial value from `sessionStorage` key `func-console-gh-pat` on mount.
- `setPat(value)` writes to `sessionStorage` and updates React state.
- `clearPat()` removes from `sessionStorage` and resets state to empty string.

No React Context is needed. OCP dynamic plugins load each page as an independent module with no shared parent component. Since `sessionStorage` is the actual cross-page shared state, each page calls `usePat()` independently and gets the current value.

### PAT Modal — `PatModal`

Location: `src/components/PatModal.tsx`

A PatternFly `Modal` component displayed when no PAT is set.

**Props:**
- `isOpen: boolean` — controls visibility
- `onSave: (pat: string) => void` — called with validated PAT

**Behavior:**
- Non-dismissable: no close button, no backdrop click dismiss. The user must provide a valid PAT to use the plugin.
- Contains a password `TextInput` for the PAT.
- Save button triggers validation: calls GitHub API `GET /user` via Octokit with the entered PAT.
- On success: calls `onSave` with the PAT.
- On failure: shows an inline PatternFly `Alert` with the error message.
- Save button shows a loading spinner (`isLoading`) while validating.

### Service Layer Changes

**`useSourceControl(pat: string): SourceControlService`**

The hook accepts a `pat` parameter. Returns a `GithubService` instance memoized on `pat` via `useMemo`. No more module-level singleton. The `GithubService` class is unchanged — constructor still takes PAT, creates Octokit once per instance.

**`OctokitGitHubService.pushFiles`** — unchanged. Already accepts PAT as a parameter.

**`useGitHubService`** — unchanged. The `pushFiles` method already takes PAT as an argument.

### Webpack Cleanup

- Remove `DefinePlugin` entry for `__GITHUB_PAT__` from `webpack.config.ts`.
- Remove `declare const __GITHUB_PAT__: string` from `src/globals.d.ts`.

### Page Integration

Each page that requires a PAT renders the modal conditionally:

```tsx
function SomePage() {
const { pat, setPat } = usePat();

return (
<>
<PatModal isOpen={!pat} onSave={setPat} />
{/* page content */}
</>
);
}
```

**FunctionsListPage:**
- Add `usePat()` in the page component, pass `pat` to `useFunctionListPage(pat)`.
- `useFunctionListPage(pat)` passes `pat` to `useSourceControl(pat)`.
- The `useEffect` that fetches repos skips the API call when `pat` is empty (avoids a failed request while the modal is visible).
- Render `PatModal` when `pat` is empty.

**FunctionCreatePage:**
- Add `usePat()`, pass `pat` to `gitHubService.pushFiles()` instead of `data.pat`.
- Render `PatModal` when `pat` is empty.

**CreateFunctionForm:**
- Remove the PAT `TextInput` field.
- Remove `pat` from `CreateFunctionFormData` interface.
- Remove `pat` from form validation and local state.

**FunctionEditPage:** Not modified — PAT integration deferred to when that page is built out.

## Files Changed

| File | Change |
|------|--------|
| `src/hooks/usePat.ts` | New — sessionStorage-backed PAT hook |
| `src/components/PatModal.tsx` | New — modal with PAT validation |
| `webpack.config.ts` | Remove `__GITHUB_PAT__` DefinePlugin entry |
| `src/globals.d.ts` | Remove `__GITHUB_PAT__` declaration |
| `src/services/source-control/useSourceControl.ts` | Accept `pat` param, `useMemo` instance |
| `src/components/CreateFunctionForm.tsx` | Remove PAT field, update interface |
| `src/views/FunctionCreatePage.tsx` | Use `usePat()`, render `PatModal` |
| `src/views/FunctionsListPage.tsx` | Use `usePat()`, pass to `useSourceControl`, render `PatModal` |

## Test Strategy

**New test suites:**

- `usePat.test.ts` — returns empty string when sessionStorage is empty; `setPat` writes to sessionStorage and updates state; `clearPat` removes from sessionStorage and resets state.
- `PatModal.test.tsx` — renders when `isOpen` is true; does not render when false; shows error on invalid PAT (mock Octokit rejects); calls `onSave` with valid PAT (mock Octokit resolves); Save button shows spinner while validating.

**Modified test suites:**

- `CreateFunctionForm.test.tsx` — remove PAT field assertions, update form data expectations.
- `FunctionCreatePage.test.tsx` — remove PAT from form submit data, mock `usePat` to return a token.
- `FunctionsListPage.test.tsx` — mock `usePat`, verify `PatModal` renders when PAT is empty.

**Mocking:** sessionStorage is available in jsdom. Octokit validation call mocked via jest module mock, consistent with existing patterns.
10 changes: 10 additions & 0 deletions docs/features.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@
],
"passes": true
},
{
"category": "functional",
"description": "Session wide GitHub Personal Access Token",
"steps": [
"If a user visits the /functions plugin and the GH PAT is not set then there will be prompt to enter it and store it into the session",
"The GH PAT it then used where needed (e.g. Function Creation page)",
"The GH PAT text box shall be removed from the Function Creation page as it should be stored in the session"
],
"passes": true
},
{
"category": "functional",
"description": "Function List Page renders a table with function entries",
Expand Down
Loading