Add Clerk authentication with Angular login component and Electron IPC token storage#635
Add Clerk authentication with Angular login component and Electron IPC token storage#635
Conversation
… Electron IPC support Agent-Logs-Url: https://github.com/highperformancecoder/minsky/sessions/dcea3c3b-13de-494d-88b9-bdc278391926 Co-authored-by: highperformancecoder <3075825+highperformancecoder@users.noreply.github.com>
|
@copilot I have added a menu item to call the /login route, but the window doesn't render, and the developer tools window doesn't attach, so I can't debug why it is stalling. |
Agent-Logs-Url: https://github.com/highperformancecoder/minsky/sessions/ac00893a-9811-4d4f-8ef1-9b889eb6d5cc Co-authored-by: highperformancecoder <3075825+highperformancecoder@users.noreply.github.com>
The window wasn't rendering because the menu item was calling Fixed in commit |
|
@copilot The login window is including and which it shouldn't. I can't see these things in the login.component.html file, so where are they coming from?
|
Your comment appears to have been cut off — it ends with "The login window is including". Could you complete the description of what you're seeing so I can investigate? |
|
@copilot The login window is including the minsky-header tag and the div with class example-button-row, that its shouldn't. I can't see this in login.component.html, so have no idea where they are coming from. |
…oute from main layout Agent-Logs-Url: https://github.com/highperformancecoder/minsky/sessions/ce140fa8-df43-43d0-832d-90cac7a292b8 Co-authored-by: highperformancecoder <3075825+highperformancecoder@users.noreply.github.com>
The Fixed in commit |
|
@copilot - why not just use /headless like the other windows? Seems simpler |
…indows Agent-Logs-Url: https://github.com/highperformancecoder/minsky/sessions/0bde9861-8dca-48e7-965d-c608f6258171 Co-authored-by: highperformancecoder <3075825+highperformancecoder@users.noreply.github.com>
Done in commit |
|
@copilot Thanks - looks good. What I need now is to add a promise that I can wait on - so call /headless/login and have a promise I can await on that is resolved when the token is set in the StoreManager. |
Agent-Logs-Url: https://github.com/highperformancecoder/minsky/sessions/f860580e-1854-46ba-96ed-117e3d0cfdce Co-authored-by: highperformancecoder <3075825+highperformancecoder@users.noreply.github.com>
Done in commit import { openLoginWindow } from '../events/electron.events';
const token = await openLoginWindow();
if (token) { /* proceed with authenticated request */ }The |
| const response = await fetch(initialUrl, { | ||
| method: 'GET', | ||
| headers: { | ||
| 'Authorization': `Bearer ${token}` | ||
| }, | ||
| redirect: 'manual' // This tells fetch NOT to follow the link automatically | ||
| }); |
Check failure
Code scanning / CodeQL
Server-side request forgery Critical
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 1 day ago
In general, to fix this problem you should ensure that any URL used in fetch or downloadURL is restricted to a safe subset, typically by validating it against an allow‑list of hostnames and schemes and by rejecting or normalizing any unexpected value. Instead of blindly trusting the download_url field from the backend, parse it with URL, check that it uses https: and points to an expected host (or set of hosts), and only then pass it to fetch/downloadURL. If validation fails, abort the operation or show an error.
The best targeted fix here is to add a small helper, e.g. sanitizeAssetUrl, that takes the tainted URL (like minskyAsset or ravelAsset), parses it, enforces https: and a specific host or host suffix, and returns either a safe normalized string or null. We then: (1) call this helper on minskyAsset and ravelAsset immediately after retrieving them in upgradeUsingClerk, and (2) modify getFinalUrl so it receives only already-sanitized URLs, or at least re-validates initialUrl before using it in fetch. Concretely:
- In
CommandsManager.ts, defineconst allowedDownloadHost = '...';(or a small allow‑list) and asanitizeAssetUrl(assetUrl: string | undefined): string | nullfunction near the other helpers (e.g., afterbackendAPIor beforegetFinalUrl). - In
upgradeUsingClerk, aftergetRelease(...), passminskyAssetandravelAssetthroughsanitizeAssetUrl. If both sanitize tonull, show the “Everything’s up to date” message as before. If one sanitizes tonullbut not the other, proceed only with the safe one, or show an error dialog explaining that the download URL is invalid. - Update calls to
getFinalUrlto pass the sanitized URLs and thetokencorrectly. Also fix the inconsistent callgetFinalUrl(ravelAsset)(missingtoken). - Within
getFinalUrl, perform a quick re-check ofinitialUrl(again parse withURLand ensure allowedprotocol/hostname) before callingfetch, to ensure it is never used with an unsafe target even if another caller reuses it in the future.
This preserves existing functionality (still uses backend-provided URLs) but adds a strict guardrail so that only URLs to the intended domain and scheme are ever used for SSRF‑capable operations.
There was a problem hiding this comment.
OK - the security issue is less severe than made out, as the backend server is trusted. But we can sanitise on delivered names - need to figure out what they are - and prefer to do this in the upgrade* methods, rather than the generic finalURL method.
| return release?.release?.download_url; | ||
| } | ||
| catch (error) { | ||
| console.error(error); |
Check warning
Code scanning / CodeQL
Log injection Medium
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 1 day ago
In general, to fix log injection issues, any value that can contain untrusted content (especially from network or user-controlled sources) should be sanitized before being written to logs. For plain-text logs, this typically means removing or replacing newline (\n) and carriage return (\r) characters so that a single log statement cannot be split into multiple log lines. It also helps to add a clear prefix or context indicating where the untrusted data is.
For this specific case, the best fix without changing existing functionality is to sanitize the error value before passing it to console.error in CommandsManager.getRelease. We should convert error to a string in a controlled way, strip \r and \n, and then log a message that includes that sanitized string. This ensures that regardless of whether the caught value is an Error, a string containing newlines, or something else, the logged line will not be able to inject extra lines. Since we already use standard JavaScript/TypeScript features, we can implement sanitization inline with String.prototype.replace, without introducing new dependencies.
Concretely, in gui-js/apps/minsky-electron/src/app/managers/CommandsManager.ts, around lines 1485–1487, replace console.error(error); with code that builds a safe string: e.g., const msg = String(error).replace(/[\r\n]/g, ' '); console.error('getRelease error:', msg);. No new imports or helper functions are strictly necessary.
| @@ -1483,7 +1483,8 @@ | ||
| return release?.release?.download_url; | ||
| } | ||
| catch (error) { | ||
| console.error(error); | ||
| const safeErrorMessage = String(error).replace(/[\r\n]/g, ' '); | ||
| console.error('getRelease error:', safeErrorMessage); | ||
| return ""; | ||
| } | ||
| } |
|
@CodeRabbit review |
✅ Actions performedReview triggered.
|
📝 WalkthroughWalkthroughThe changes introduce a Clerk-based authentication system integrated with Electron, featuring a login component, IPC handlers for auth token persistence, backend API communication for upgrades, and menu options for user authentication and upgrade workflows. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant LoginUI as Login<br/>Component
participant ClerkSvc as ClerkService
participant Clerk as Clerk<br/>Client
participant Electron as Electron<br/>IPC
participant Store as Store<br/>Manager
User->>LoginUI: Navigate to login
LoginUI->>ClerkSvc: initialize()
ClerkSvc->>Clerk: Create & load Clerk instance
Clerk-->>ClerkSvc: Initialized
ClerkSvc-->>LoginUI: Ready
User->>LoginUI: Enter email & password
LoginUI->>LoginUI: Validate form
LoginUI->>ClerkSvc: signInWithEmailPassword(email, password)
ClerkSvc->>Clerk: signIn.create(email, password)
Clerk-->>ClerkSvc: SignIn result
alt Sign-in successful
ClerkSvc->>Clerk: Activate session
ClerkSvc->>ClerkSvc: getToken()
Clerk-->>ClerkSvc: JWT token
ClerkSvc->>Electron: sendTokenToElectron()
Electron->>Electron: IPC: SET_AUTH_TOKEN
Electron->>Store: Update authToken
Store-->>Electron: Success
Electron-->>ClerkSvc: Response
ClerkSvc-->>LoginUI: Success
LoginUI->>Electron: closeWindow()
else Sign-in failed
ClerkSvc-->>LoginUI: Error
LoginUI->>User: Display error message
end
sequenceDiagram
participant User
participant App as Electron<br/>App
participant Menu as Application<br/>Menu
participant CmdMgr as CommandsManager
participant WinMgr as WindowManager
participant Backend as Backend<br/>API
participant Clerk as Clerk<br/>Auth
participant Store as Store
User->>Menu: Click "Upgrade via Clerk"
Menu->>CmdMgr: upgradeUsingClerk()
CmdMgr->>WinMgr: openLoginWindow()
WinMgr-->>CmdMgr: Promise<token>
CmdMgr->>CmdMgr: Wait for auth token
Note over App: User authenticates in login window
App->>Store: SET_AUTH_TOKEN event
Store->>WinMgr: _resolveAuthToken(token)
WinMgr-->>CmdMgr: Token received
CmdMgr->>CmdMgr: buildState(previous)
CmdMgr->>Backend: getRelease(product, previous, token)
Backend-->>CmdMgr: Download URL
CmdMgr->>CmdMgr: getFinalUrl() with redirect handling
CmdMgr->>CmdMgr: Download & install assets
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 12
🧹 Nitpick comments (4)
gui-js/apps/minsky-web/src/environments/environment.web.ts (1)
9-9: Use a non-empty dev value or explicit startup guard for Clerk key.Line 9 is
''; if Clerk init is switched to config-driven loading, this will fail at runtime with little context. Prefer a real dev key or fail fast with a clear error when unset.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@gui-js/apps/minsky-web/src/environments/environment.web.ts` at line 9, The environment value clerkPublishableKey is an empty string which will cause silent runtime failures when Clerk is initialized; either set a non-empty development publishable key in environment.web.ts (clerkPublishableKey) or add a startup guard where Clerk is initialized that checks environment.clerkPublishableKey and throws/logs a clear error and exits if unset. Update the environment.web.ts constant and/or the Clerk init path to validate clerkPublishableKey before calling the Clerk SDK (e.g., initClerk or wherever Clerk is created) so failures are fast and informative.gui-js/apps/minsky-electron/src/app/managers/ApplicationMenuManager.ts (1)
135-137:Logout Clerklabel does not match the action.Line 136 opens the login window; it does not perform an immediate logout. Either rename this item (e.g., “Clerk Login / Manage Session”) or call a direct logout/clear-token flow.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@gui-js/apps/minsky-electron/src/app/managers/ApplicationMenuManager.ts` around lines 135 - 137, The menu item labeled "Logout Clerk" currently calls WindowManager.openLoginWindow() which only opens the login UI and does not perform a logout; update the menu to either rename the label to reflect the action (e.g., "Clerk Login / Manage Session") or change the click handler to invoke the actual logout/clear-token flow (for example call your logout routine such as AuthService.logout() or SessionManager.clearSession() before or instead of WindowManager.openLoginWindow()); ensure the label and the click handler are consistent (update the label 'Logout Clerk' or replace WindowManager.openLoginWindow() with the real logout function).gui-js/libs/ui-components/src/lib/login/login.component.html (1)
27-27: Announce auth failures to assistive tech.The general
errorMessageis only visually rendered right now, so screen-reader users may miss failed sign-ins.♿ Suggested tweak
- <p *ngIf="errorMessage" class="error-message">{{ errorMessage }}</p> + <p *ngIf="errorMessage" class="error-message" role="alert">{{ errorMessage }}</p>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@gui-js/libs/ui-components/src/lib/login/login.component.html` at line 27, The error message paragraph (the element that displays errorMessage in login.component.html) is only visually rendered and not announced to assistive tech; update that element to be an accessible live region by adding attributes such as aria-live="assertive" and role="alert" (and optionally aria-atomic="true") so screen readers will announce sign-in failures when errorMessage is set; locate the <p> that binds to errorMessage and add these attributes to it.gui-js/libs/core/src/lib/services/clerk/clerk.service.ts (1)
35-60: Consider sending the Electron token as part of sign-in.A successful
signInWithEmailPassword()still requires callers to remember a secondsendTokenToElectron()call. Missing that step would leave Electron waiting onSET_AUTH_TOKENeven though Clerk already created a session.♻️ Suggested refactor
if (result.status === 'complete') { await this.clerk.setActive({ session: result.createdSessionId }); + await this.sendTokenToElectron(); } else {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@gui-js/libs/core/src/lib/services/clerk/clerk.service.ts` around lines 35 - 60, signInWithEmailPassword currently completes Clerk sign-in but doesn't notify Electron, leaving the renderer waiting for SET_AUTH_TOKEN; after successfully setting the active session in signInWithEmailPassword, call sendTokenToElectron (or inline the logic: await this.getToken() then this.electronService.invoke(events.SET_AUTH_TOKEN, token) when this.electronService.isElectron) so the token is sent automatically; update signInWithEmailPassword to await sendTokenToElectron() only on success and keep existing signOut behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@gui-js/apps/minsky-electron/src/app/events/electron.events.ts`:
- Around line 292-297: The handler for ipcMain.handle(events.SET_AUTH_TOKEN) is
persisting the raw bearer token to disk via StoreManager.store.set/delete
(StoreManager.store), which is insecure; update the handler to stop writing the
token to persistent store and instead keep it in memory (e.g., use or create an
in-memory AuthTokenManager singleton or a module-scoped variable accessed by the
renderer via IPC) and remove calls to StoreManager.store.set/delete; if
persistent storage is absolutely required, replace direct disk persistence with
an OS-backed secret store integration (e.g., keytar or platform-specific secure
storage) and ensure any persisted value is encrypted and has a strict
expiry/rotation policy rather than storing the raw bearer token.
In `@gui-js/apps/minsky-electron/src/app/managers/ApplicationMenuManager.ts`:
- Around line 131-133: The "Upgrade via Clerk" menu action currently spins in a
non-cancelable loop; modify CommandsManager.upgradeUsingClerk to stop reopening
the login when WindowManager.openLoginWindow returns null (user
closed/cancelled) by handling a null/undefined authToken result and aborting the
upgrade flow (or returning an explicit cancellation) instead of looping forever;
optionally replace the infinite while (!authToken) with a finite retry policy or
a user confirmation prompt before retrying so the operation can be cancelled by
the user.
In `@gui-js/apps/minsky-electron/src/app/managers/CommandsManager.ts`:
- Around line 1469-1488: getRelease currently swallows backend/auth failures by
returning a falsy URL (via optional chaining and a catch that returns ""), so
upgradeUsingClerk sees "no update" instead of surfacing errors; update
getRelease (the static method) to stop collapsing errors: remove or change the
catch to rethrow the caught error (or throw a new Error with context) so
auth/backend failures propagate, and explicitly validate the parsed response
(check that the parsed object from callBackendAPI and
release.release.download_url exist) before returning the URL — only return an
empty string when you deterministically know there is no release (e.g., no
prevRelease found when previous==true), but do not suppress exceptions from
callBackendAPI or malformed responses.
- Around line 1491-1494: The loop in CommandsManager.upgradeUsingClerk
continuously calls WindowManager.openLoginWindow() until
StoreManager.store.get('authToken') exists, but openLoginWindow() can resolve
null when the user closes the popup; update upgradeUsingClerk to capture the
result of WindowManager.openLoginWindow(), check for null, and
break/return/throw to allow cancelation instead of immediately reopening the
dialog—use the returned value from openLoginWindow() (or a boolean) before
re-checking StoreManager.store.get('authToken') so a closed popup stops the loop
and cancels the upgrade flow.
- Around line 1503-1505: The stored Ravel URL is resolved without the auth
token, causing future installs to use "Bearer undefined"; update the stash logic
so StoreManager.store.set('ravelPlugin', await getFinalUrl(ravelAsset, token))
passes the same auth token that other getFinalUrl calls in this file use (e.g.
the authToken/AuthManager.getToken() value present in CommandsManager). Ensure
you retrieve the current token the same way as other code in this module and
pass it as the second argument to getFinalUrl when ravelAsset is truthy.
- Around line 1399-1405: The default (unsupported-platform) branch in
CommandsManager.ts calls an out-of-scope window.close(), which throws; remove
that call and ensure the flow exits cleanly after
dialog.showMessageBoxSync(WindowManager.getMainWindow(), ...) — either delete
the window.close() line or replace it with a valid call on the WindowManager
(e.g., WindowManager.getMainWindow().close() or app.quit()) so the dialog is
shown and the process exits without referencing an undefined window identifier.
- Around line 1385-1398: The `case 'linux'` block in CommandsManager.ts declares
lets (aexec, osRelease, distroInfo, extractor, state assignment) that leak
across the switch and trigger the noSwitchDeclarations lint rule; fix it by
wrapping the entire case 'linux' body in { ... } so those let/const bindings are
block-scoped and local to that case (ensure you move the existing code for case
'linux' including state={...}, promisify(exec) assignment, os-release selection,
grep calls and extractor usage inside the new braces and keep the final break).
In `@gui-js/apps/minsky-electron/src/app/managers/StoreManager.ts`:
- Line 22: The StoreManager currently persists a sensitive authToken property in
electron-store; instead remove authToken from the persisted store schema and
move storage to the OS credential vault (e.g., use keytar). Update StoreManager
to stop writing/reading authToken from electron-store: add async methods like
getAuthToken/setAuthToken/deleteAuthToken that call keytar (or another OS
credential API), migrate any existing token on startup by reading the old
electron-store value, saving it to keytar, and deleting it from electron-store,
and keep only non-sensitive session state in electron-store.
In `@gui-js/apps/minsky-electron/src/app/managers/WindowManager.ts`:
- Around line 31-32: openLoginWindow currently overwrites the shared resolver
_resolveAuthToken when called a second time, orphaning the first caller; fix by
detecting an in-flight auth promise/resolver and returning that existing promise
instead of creating a new one, and only create/assign a new _resolveAuthToken
and _authTokenPromise when none exists; ensure you also reuse any existing
_loginWindow instead of creating a second popup (see references to
openLoginWindow, _resolveAuthToken, _authTokenPromise and _loginWindow and the
similar logic around lines 387-398) so multiple callers share the same
promise/window rather than replacing the resolver.
In `@gui-js/apps/minsky-web/src/environments/environment.ts`:
- Around line 6-8: You added a standalone environment object with
clerkPublishableKey instead of adding it to the existing AppConfig, so the value
can be unused; modify the code to move clerkPublishableKey into the existing
AppConfig interface/object (extend the AppConfig declaration to include
clerkPublishableKey and set its value there) and remove the separate environment
export, then update ClerkService to read the key from the injected AppConfig
(use the AppConfig property rather than a hardcoded string) so the publishable
key is consistently sourced from the app config.
In `@gui-js/libs/core/src/lib/services/clerk/clerk.service.ts`:
- Around line 18-20: The publishable key is hard-coded as publishableKey and
passed to new Clerk(...) in ClerkService; instead accept the publishable key
from the app-level configuration (e.g. inject a ConfigService or add a
constructor parameter) and use that value when instantiating Clerk (this.clerk =
new Clerk(config.get('CLERK_PUBLISHABLE_KEY'))), including a clear error or
fallback if the config value is missing; update the ClerkService
constructor/initialization to read the injected config and remove the hard-coded
literal.
In `@gui-js/libs/ui-components/src/lib/login/login.component.ts`:
- Around line 54-73: The window is closed unconditionally after onSubmit which
prevents retries on failure and can crash outside Electron; change logic so
electronService.closeWindow() is only called for successful flows and only when
running in Electron: move the closeWindow call into the successful branch after
setting isAuthenticated (inside the try after sendTokenToElectron) and call it
guarded by the Electron runtime check (e.g., electronService.isElectron() or
electronService?.isElectron) or by checking that electronService.closeWindow
exists; apply the same pattern to the signOut flow (the signOut handler) so
signOut only calls electronService.closeWindow() when running in Electron and
after a successful sign-out.
---
Nitpick comments:
In `@gui-js/apps/minsky-electron/src/app/managers/ApplicationMenuManager.ts`:
- Around line 135-137: The menu item labeled "Logout Clerk" currently calls
WindowManager.openLoginWindow() which only opens the login UI and does not
perform a logout; update the menu to either rename the label to reflect the
action (e.g., "Clerk Login / Manage Session") or change the click handler to
invoke the actual logout/clear-token flow (for example call your logout routine
such as AuthService.logout() or SessionManager.clearSession() before or instead
of WindowManager.openLoginWindow()); ensure the label and the click handler are
consistent (update the label 'Logout Clerk' or replace
WindowManager.openLoginWindow() with the real logout function).
In `@gui-js/apps/minsky-web/src/environments/environment.web.ts`:
- Line 9: The environment value clerkPublishableKey is an empty string which
will cause silent runtime failures when Clerk is initialized; either set a
non-empty development publishable key in environment.web.ts
(clerkPublishableKey) or add a startup guard where Clerk is initialized that
checks environment.clerkPublishableKey and throws/logs a clear error and exits
if unset. Update the environment.web.ts constant and/or the Clerk init path to
validate clerkPublishableKey before calling the Clerk SDK (e.g., initClerk or
wherever Clerk is created) so failures are fast and informative.
In `@gui-js/libs/core/src/lib/services/clerk/clerk.service.ts`:
- Around line 35-60: signInWithEmailPassword currently completes Clerk sign-in
but doesn't notify Electron, leaving the renderer waiting for SET_AUTH_TOKEN;
after successfully setting the active session in signInWithEmailPassword, call
sendTokenToElectron (or inline the logic: await this.getToken() then
this.electronService.invoke(events.SET_AUTH_TOKEN, token) when
this.electronService.isElectron) so the token is sent automatically; update
signInWithEmailPassword to await sendTokenToElectron() only on success and keep
existing signOut behavior unchanged.
In `@gui-js/libs/ui-components/src/lib/login/login.component.html`:
- Line 27: The error message paragraph (the element that displays errorMessage
in login.component.html) is only visually rendered and not announced to
assistive tech; update that element to be an accessible live region by adding
attributes such as aria-live="assertive" and role="alert" (and optionally
aria-atomic="true") so screen readers will announce sign-in failures when
errorMessage is set; locate the <p> that binds to errorMessage and add these
attributes to it.
🪄 Autofix (Beta)
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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 33112e71-d975-4bcc-90af-1cf487513a1c
⛔ Files ignored due to path filters (1)
gui-js/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (17)
gui-js/apps/minsky-electron/src/app/events/electron.events.tsgui-js/apps/minsky-electron/src/app/managers/ApplicationMenuManager.tsgui-js/apps/minsky-electron/src/app/managers/CommandsManager.tsgui-js/apps/minsky-electron/src/app/managers/StoreManager.tsgui-js/apps/minsky-electron/src/app/managers/WindowManager.tsgui-js/apps/minsky-web/src/app/app-routing.module.tsgui-js/apps/minsky-web/src/app/app.component.htmlgui-js/apps/minsky-web/src/environments/environment.tsgui-js/apps/minsky-web/src/environments/environment.web.tsgui-js/libs/core/src/index.tsgui-js/libs/core/src/lib/services/clerk/clerk.service.tsgui-js/libs/shared/src/lib/constants/constants.tsgui-js/libs/ui-components/src/index.tsgui-js/libs/ui-components/src/lib/login/login.component.htmlgui-js/libs/ui-components/src/lib/login/login.component.scssgui-js/libs/ui-components/src/lib/login/login.component.tsgui-js/package.json
| ipcMain.handle(events.SET_AUTH_TOKEN, async (event, token: string | null) => { | ||
| if (token) { | ||
| StoreManager.store.set('authToken', token); | ||
| } else { | ||
| StoreManager.store.delete('authToken'); | ||
| } |
There was a problem hiding this comment.
Avoid persisting the raw bearer token in the app store.
This writes a reusable auth credential to disk via StoreManager.store. Any user or process that can read the app-data directory can replay it until expiry. Please keep the token in memory only, or move persistence to OS-backed secret storage.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@gui-js/apps/minsky-electron/src/app/events/electron.events.ts` around lines
292 - 297, The handler for ipcMain.handle(events.SET_AUTH_TOKEN) is persisting
the raw bearer token to disk via StoreManager.store.set/delete
(StoreManager.store), which is insecure; update the handler to stop writing the
token to persistent store and instead keep it in memory (e.g., use or create an
in-memory AuthTokenManager singleton or a module-scoped variable accessed by the
renderer via IPC) and remove calls to StoreManager.store.set/delete; if
persistent storage is absolutely required, replace direct disk persistence with
an OS-backed secret store integration (e.g., keytar or platform-specific secure
storage) and ensure any persisted value is encrypted and has a strict
expiry/rotation policy rather than storing the raw bearer token.
| label: 'Upgrade via Clerk', | ||
| click() {CommandsManager.upgradeUsingClerk();}, | ||
| }, |
There was a problem hiding this comment.
Upgrade via Clerk currently exposes a cancel-loop failure mode.
From the linked flow (CommandsManager.upgradeUsingClerk + WindowManager.openLoginWindow), closing the login window resolves null, but upgrade logic keeps reopening login in a while (!authToken) loop. This makes this menu action non-cancelable for users.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@gui-js/apps/minsky-electron/src/app/managers/ApplicationMenuManager.ts`
around lines 131 - 133, The "Upgrade via Clerk" menu action currently spins in a
non-cancelable loop; modify CommandsManager.upgradeUsingClerk to stop reopening
the login when WindowManager.openLoginWindow returns null (user
closed/cancelled) by handling a null/undefined authToken result and aborting the
upgrade flow (or returning an explicit cancellation) instead of looping forever;
optionally replace the infinite while (!authToken) with a finite retry policy or
a user confirmation prompt before retrying so the operation can be cancelled by
the user.
| case 'linux': | ||
| state={system: 'linux', distro: '', version: '',arch:'', previous: ''}; | ||
| // figure out distro and version from /etc/os-release | ||
| let aexec=promisify(exec); | ||
| let osRelease='/etc/os-release'; | ||
| if (existsSync(process.resourcesPath+'/os-release')) | ||
| osRelease=process.resourcesPath+'/os-release'; | ||
| let distroInfo=await aexec(`grep ^ID= ${osRelease}`); | ||
| // value may or may not be quoted | ||
| let extractor=/.*=['"]?([^'"\n]*)['"]?/; | ||
| state.distro=extractor.exec(distroInfo.stdout)[1]; | ||
| distroInfo=await aexec(`grep ^VERSION_ID= ${osRelease}`); | ||
| state.version=extractor.exec(distroInfo.stdout)[1]; | ||
| break; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Locate the CommandsManager.ts file
find . -name "CommandsManager.ts" -type fRepository: highperformancecoder/minsky
Length of output: 136
🏁 Script executed:
#!/bin/bash
# Once we find the file, read the specific lines mentioned
# First, let's find the file
FILE=$(find . -name "CommandsManager.ts" -type f)
if [ -n "$FILE" ]; then
echo "File found at: $FILE"
# Get total line count to understand file size
wc -l "$FILE"
# Read lines 1380-1410 to capture context around the target lines
sed -n '1380,1410p' "$FILE"
fiRepository: highperformancecoder/minsky
Length of output: 1540
🌐 Web query:
Biome noSwitchDeclarations rule TypeScript JavaScript
💡 Result:
The noSwitchDeclarations rule in Biome is a correctness linter rule that disallows lexical declarations (const, let, function, class) directly in switch clauses. This prevents declarations from being accessible across the entire switch statement, where they might be used uninitialized in other cases. It applies to JavaScript and super languages like TypeScript. Configuration in biome.json: { "linter": { "rules": { "correctness": { "noSwitchDeclarations": "error" } } } } Invalid examples: switch (foo) { case 0: const x = 1; // Error: accessible in other cases break; } Valid fix: Wrap in block switch (foo) { case 0: { const x = 1; break; } } The rule is fixable (suggests wrapping in block) and sources from ESLint's no-case-declarations.
Citations:
- 1: https://biomejs.dev/linter/rules/no-switch-declarations/
- 2: https://github.com/biomejs/biome/blob/main/crates/biome_js_analyze/src/lint/correctness/no_switch_declarations.rs
Wrap the Linux case in braces.
The let declarations in this clause violate Biome's noSwitchDeclarations rule because they are scoped across the entire switch statement instead of being local to the case. Adding braces keeps the bindings local and resolves the lint error.
🧩 Suggested fix
- case 'linux':
- state={system: 'linux', distro: '', version: '',arch:'', previous: ''};
- // figure out distro and version from /etc/os-release
- let aexec=promisify(exec);
- let osRelease='/etc/os-release';
- if (existsSync(process.resourcesPath+'/os-release'))
- osRelease=process.resourcesPath+'/os-release';
- let distroInfo=await aexec(`grep ^ID= ${osRelease}`);
- // value may or may not be quoted
- let extractor=/.*=['"]?([^'"\n]*)['"]?/;
- state.distro=extractor.exec(distroInfo.stdout)[1];
- distroInfo=await aexec(`grep ^VERSION_ID= ${osRelease}`);
- state.version=extractor.exec(distroInfo.stdout)[1];
- break;
+ case 'linux': {
+ state={system: 'linux', distro: '', version: '',arch:'', previous: ''};
+ // figure out distro and version from /etc/os-release
+ const aexec=promisify(exec);
+ let osRelease='/etc/os-release';
+ if (existsSync(process.resourcesPath+'/os-release'))
+ osRelease=process.resourcesPath+'/os-release';
+ let distroInfo=await aexec(`grep ^ID= ${osRelease}`);
+ // value may or may not be quoted
+ const extractor=/.*=['"]?([^'"\n]*)['"]?/;
+ state.distro=extractor.exec(distroInfo.stdout)[1];
+ distroInfo=await aexec(`grep ^VERSION_ID= ${osRelease}`);
+ state.version=extractor.exec(distroInfo.stdout)[1];
+ break;
+ }📝 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.
| case 'linux': | |
| state={system: 'linux', distro: '', version: '',arch:'', previous: ''}; | |
| // figure out distro and version from /etc/os-release | |
| let aexec=promisify(exec); | |
| let osRelease='/etc/os-release'; | |
| if (existsSync(process.resourcesPath+'/os-release')) | |
| osRelease=process.resourcesPath+'/os-release'; | |
| let distroInfo=await aexec(`grep ^ID= ${osRelease}`); | |
| // value may or may not be quoted | |
| let extractor=/.*=['"]?([^'"\n]*)['"]?/; | |
| state.distro=extractor.exec(distroInfo.stdout)[1]; | |
| distroInfo=await aexec(`grep ^VERSION_ID= ${osRelease}`); | |
| state.version=extractor.exec(distroInfo.stdout)[1]; | |
| break; | |
| case 'linux': { | |
| state={system: 'linux', distro: '', version: '',arch:'', previous: ''}; | |
| // figure out distro and version from /etc/os-release | |
| const aexec=promisify(exec); | |
| let osRelease='/etc/os-release'; | |
| if (existsSync(process.resourcesPath+'/os-release')) | |
| osRelease=process.resourcesPath+'/os-release'; | |
| let distroInfo=await aexec(`grep ^ID= ${osRelease}`); | |
| // value may or may not be quoted | |
| const extractor=/.*=['"]?([^'"\n]*)['"]?/; | |
| state.distro=extractor.exec(distroInfo.stdout)[1]; | |
| distroInfo=await aexec(`grep ^VERSION_ID= ${osRelease}`); | |
| state.version=extractor.exec(distroInfo.stdout)[1]; | |
| break; | |
| } |
🧰 Tools
🪛 Biome (2.4.9)
[error] 1388-1388: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
(lint/correctness/noSwitchDeclarations)
[error] 1389-1389: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
(lint/correctness/noSwitchDeclarations)
[error] 1392-1392: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
(lint/correctness/noSwitchDeclarations)
[error] 1394-1394: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
(lint/correctness/noSwitchDeclarations)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@gui-js/apps/minsky-electron/src/app/managers/CommandsManager.ts` around lines
1385 - 1398, The `case 'linux'` block in CommandsManager.ts declares lets
(aexec, osRelease, distroInfo, extractor, state assignment) that leak across the
switch and trigger the noSwitchDeclarations lint rule; fix it by wrapping the
entire case 'linux' body in { ... } so those let/const bindings are block-scoped
and local to that case (ensure you move the existing code for case 'linux'
including state={...}, promisify(exec) assignment, os-release selection, grep
calls and extractor usage inside the new braces and keep the final break).
| default: | ||
| dialog.showMessageBoxSync(WindowManager.getMainWindow(),{ | ||
| message: `In app update is not available for your operating system yet, please check back later`, | ||
| type: 'error', | ||
| }); | ||
| window.close(); | ||
| return; |
There was a problem hiding this comment.
Remove the out-of-scope window.close() call.
There is no local window binding here, so the unsupported-platform branch throws instead of exiting cleanly after showing the dialog.
🧹 Suggested fix
default:
dialog.showMessageBoxSync(WindowManager.getMainWindow(),{
message: `In app update is not available for your operating system yet, please check back later`,
type: 'error',
});
- window.close();
return;
- break;
}📝 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.
| default: | |
| dialog.showMessageBoxSync(WindowManager.getMainWindow(),{ | |
| message: `In app update is not available for your operating system yet, please check back later`, | |
| type: 'error', | |
| }); | |
| window.close(); | |
| return; | |
| default: | |
| dialog.showMessageBoxSync(WindowManager.getMainWindow(),{ | |
| message: `In app update is not available for your operating system yet, please check back later`, | |
| type: 'error', | |
| }); | |
| return; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@gui-js/apps/minsky-electron/src/app/managers/CommandsManager.ts` around lines
1399 - 1405, The default (unsupported-platform) branch in CommandsManager.ts
calls an out-of-scope window.close(), which throws; remove that call and ensure
the flow exits cleanly after
dialog.showMessageBoxSync(WindowManager.getMainWindow(), ...) — either delete
the window.close() line or replace it with a valid call on the WindowManager
(e.g., WindowManager.getMainWindow().close() or app.quit()) so the dialog is
shown and the process exits without referencing an undefined window identifier.
| static async getRelease(product: string, previous: boolean, token: string) { | ||
| let state=await CommandsManager.buildState(previous); | ||
| let query=`product=${product}&os=${state.system}&arch=${state.arch}&distro=${state.distro}&distro_version=${state.version}`; | ||
| try { | ||
| if (previous) { | ||
| let releases=JSON.parse(await callBackendAPI(`${backendAPI}/releases?${query}`, token)); | ||
| let prevRelease; | ||
| for (let release of releases) | ||
| if (semVerLess(release.version, state.previous)) | ||
| prevRelease=release; | ||
| if (prevRelease) return prevRelease.download_url; | ||
| // if not, then treat the request as latest | ||
| } | ||
| let release=JSON.parse(await callBackendAPI(`${backendAPI}/releases/latest?${query}`, token)); | ||
| return release?.release?.download_url; | ||
| } | ||
| catch (error) { | ||
| console.error(error); | ||
| return ""; | ||
| } |
There was a problem hiding this comment.
Don't collapse backend/auth failures into a falsy URL.
release?.release?.download_url and the catch path both turn failures into falsy values. upgradeUsingClerk() then shows the "Everything's up to date" dialog on bad tokens and backend outages instead of prompting re-auth or retry.
🧰 Tools
🪛 GitHub Check: CodeQL
[warning] 1486-1486: Log injection
Log entry depends on a user-provided value.
Log entry depends on a user-provided value.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@gui-js/apps/minsky-electron/src/app/managers/CommandsManager.ts` around lines
1469 - 1488, getRelease currently swallows backend/auth failures by returning a
falsy URL (via optional chaining and a catch that returns ""), so
upgradeUsingClerk sees "no update" instead of surfacing errors; update
getRelease (the static method) to stop collapsing errors: remove or change the
catch to rethrow the caught error (or throw a new Error with context) so
auth/backend failures propagate, and explicitly validate the parsed response
(check that the parsed object from callBackendAPI and
release.release.download_url exist) before returning the URL — only return an
empty string when you deterministically know there is no release (e.g., no
prevRelease found when previous==true), but do not suppress exceptions from
callBackendAPI or malformed responses.
| defaultModelDirectory: string; | ||
| defaultDataDirectory: string; | ||
| ravelPlugin: string; // used for post installation installation of Ravel | ||
| authToken?: string; |
There was a problem hiding this comment.
Avoid persisting JWT bearer tokens in plain electron-store.
Line 22 adds long-lived token storage in the regular app store, which is readable from disk and weakens account security. Store auth tokens in OS credential storage (e.g., keychain/credential vault) and keep only non-sensitive session state in electron-store.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@gui-js/apps/minsky-electron/src/app/managers/StoreManager.ts` at line 22, The
StoreManager currently persists a sensitive authToken property in
electron-store; instead remove authToken from the persisted store schema and
move storage to the OS credential vault (e.g., use keytar). Update StoreManager
to stop writing/reading authToken from electron-store: add async methods like
getAuthToken/setAuthToken/deleteAuthToken that call keytar (or another OS
credential API), migrate any existing token on startup by reading the old
electron-store value, saving it to keytar, and deleting it from electron-store,
and keep only non-sensitive session state in electron-store.
| // Pending resolver for the auth-token promise created by openLoginWindow() | ||
| static _resolveAuthToken: ((token: string | null) => void) | null = null; |
There was a problem hiding this comment.
Reuse the in-flight login promise/window.
A second openLoginWindow() call replaces _resolveAuthToken before the first promise settles. The first caller then waits forever, and the earlier popup becomes orphaned. This is reachable because both the upgrade flow and the menu can open the login window. Return the existing pending promise instead of overwriting the resolver.
Also applies to: 387-398
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@gui-js/apps/minsky-electron/src/app/managers/WindowManager.ts` around lines
31 - 32, openLoginWindow currently overwrites the shared resolver
_resolveAuthToken when called a second time, orphaning the first caller; fix by
detecting an in-flight auth promise/resolver and returning that existing promise
instead of creating a new one, and only create/assign a new _resolveAuthToken
and _authTokenPromise when none exists; ensure you also reuse any existing
_loginWindow instead of creating a second popup (see references to
openLoginWindow, _resolveAuthToken, _authTokenPromise and _loginWindow and the
similar logic around lines 387-398) so multiple callers share the same
promise/window rather than replacing the resolver.
| export const environment = { | ||
| clerkPublishableKey: 'pk_test_cG9zaXRpdmUtcGhvZW5peC04NS5jbGVyay5hY2NvdW50cy5kZXYk', | ||
| }; |
There was a problem hiding this comment.
Consolidate Clerk key into the existing app config surface.
Line 6 introduces a second config object (environment) instead of extending AppConfig, which risks this value never being consumed consistently. With ClerkService still hardcoding the key, this new value is currently ineffective and can drift.
Proposed fix
export const AppConfig = {
production: false,
environment: 'LOCAL',
+ clerkPublishableKey: 'pk_test_cG9zaXRpdmUtcGhvZW5peC04NS5jbGVyay5hY2NvdW50cy5kZXYk',
};
-
-export const environment = {
- clerkPublishableKey: 'pk_test_cG9zaXRpdmUtcGhvZW5peC04NS5jbGVyay5hY2NvdW50cy5kZXYk',
-};🧰 Tools
🪛 Betterleaks (1.1.1)
[high] 7-7: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@gui-js/apps/minsky-web/src/environments/environment.ts` around lines 6 - 8,
You added a standalone environment object with clerkPublishableKey instead of
adding it to the existing AppConfig, so the value can be unused; modify the code
to move clerkPublishableKey into the existing AppConfig interface/object (extend
the AppConfig declaration to include clerkPublishableKey and set its value
there) and remove the separate environment export, then update ClerkService to
read the key from the injected AppConfig (use the AppConfig property rather than
a hardcoded string) so the publishable key is consistently sourced from the app
config.
| const publishableKey = 'pk_test_cG9zaXRpdmUtcGhvZW5peC04NS5jbGVyay5hY2NvdW50cy5kZXYk'; | ||
|
|
||
| this.clerk = new Clerk(publishableKey); |
There was a problem hiding this comment.
Don't hard-code the Clerk tenant in a shared library.
This locks every build to one test publishable key and bypasses the configuration work added elsewhere in the PR. Please inject the publishable key from app-level config instead of embedding it here.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@gui-js/libs/core/src/lib/services/clerk/clerk.service.ts` around lines 18 -
20, The publishable key is hard-coded as publishableKey and passed to new
Clerk(...) in ClerkService; instead accept the publishable key from the
app-level configuration (e.g. inject a ConfigService or add a constructor
parameter) and use that value when instantiating Clerk (this.clerk = new
Clerk(config.get('CLERK_PUBLISHABLE_KEY'))), including a clear error or fallback
if the config value is missing; update the ClerkService
constructor/initialization to read the injected config and remove the hard-coded
literal.
| async onSubmit() { | ||
| if (this.loginForm.invalid) return; | ||
|
|
||
| this.isLoading = true; | ||
| this.errorMessage = ''; | ||
|
|
||
| try { | ||
| await this.clerkService.signInWithEmailPassword( | ||
| this.loginForm.value.email, | ||
| this.loginForm.value.password | ||
| ); | ||
| this.isAuthenticated = true; | ||
| await this.clerkService.sendTokenToElectron(); | ||
| } catch (err: any) { | ||
| this.errorMessage = err?.errors?.[0]?.message ?? err?.message ?? 'Authentication failed.'; | ||
| } finally { | ||
| this.isLoading = false; | ||
| } | ||
| this.electronService.closeWindow(); | ||
| } |
There was a problem hiding this comment.
Gate window closing to successful flows and Electron runtime only.
Line [72] and Line [86] close the window unconditionally. That both (1) closes on failed auth/sign-out (user can’t retry) and (2) can crash in non-Electron runtime because ElectronService.closeWindow() is not guarded (gui-js/libs/core/src/lib/services/electron/electron.service.ts, Line 51).
Proposed fix
async onSubmit() {
if (this.loginForm.invalid) return;
this.isLoading = true;
this.errorMessage = '';
+ let isSuccess = false;
try {
await this.clerkService.signInWithEmailPassword(
this.loginForm.value.email,
this.loginForm.value.password
);
this.isAuthenticated = true;
await this.clerkService.sendTokenToElectron();
+ isSuccess = true;
} catch (err: any) {
this.errorMessage = err?.errors?.[0]?.message ?? err?.message ?? 'Authentication failed.';
} finally {
this.isLoading = false;
}
- this.electronService.closeWindow();
+ if (isSuccess && this.electronService.isElectron) {
+ await this.electronService.closeWindow();
+ }
}
async onSignOut() {
this.isLoading = true;
this.errorMessage = '';
+ let isSuccess = false;
try {
await this.clerkService.signOut();
this.isAuthenticated = false;
+ isSuccess = true;
} catch (err: any) {
this.errorMessage = err?.message ?? 'Sign out failed.';
} finally {
this.isLoading = false;
}
- this.electronService.closeWindow();
+ if (isSuccess && this.electronService.isElectron) {
+ await this.electronService.closeWindow();
+ }
}Also applies to: 75-87
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@gui-js/libs/ui-components/src/lib/login/login.component.ts` around lines 54 -
73, The window is closed unconditionally after onSubmit which prevents retries
on failure and can crash outside Electron; change logic so
electronService.closeWindow() is only called for successful flows and only when
running in Electron: move the closeWindow call into the successful branch after
setting isAuthenticated (inside the try after sendTokenToElectron) and call it
guarded by the Electron runtime check (e.g., electronService.isElectron() or
electronService?.isElectron) or by checking that electronService.closeWindow
exists; apply the same pattern to the signOut flow (the signOut handler) so
signOut only calls electronService.closeWindow() when running in Electron and
after a successful sign-out.
SET_AUTH_TOKENevent constant togui-js/libs/shared/src/lib/constants/constants.tsgui-js/libs/ui-components/src/lib/login/login.component.tsgui-js/libs/ui-components/src/lib/login/login.component.htmlgui-js/libs/ui-components/src/lib/login/login.component.scssgui-js/libs/core/src/lib/services/clerk/clerk.service.tsgui-js/apps/minsky-electron/src/app/events/electron.events.tsto handle JWT token and exportopenLoginWindow(): Promise<string | null>/headless/loginroute (consistent with other popup windows), Clerk config to environment,@clerk/clerk-jsdependencycreatePopupWindowWithRouting(which prependsrendererAppURL) instead of bareloadURLclerk.service.tsopenLoginWindow(): Promise<string | null>fromelectron.events.ts— opens the#/headless/loginpopup and resolves with the JWT token onceSET_AUTH_TOKENfires, ornullif the window is closed before authentication completesOriginal prompt
Create Angular Login Component for Clerk with Electron Integration
Context
The minsky application is an Angular + Electron application located in the
gui-jsdirectory. It uses:gui-js/apps/minsky-webandgui-js/apps/minsky-electron)ipcRendererandipcMainObjectives
Create a complete Clerk authentication integration with the following components:
1. Angular Login Component
Create a new standalone Angular component at
gui-js/libs/ui-components/src/lib/login/login.component.tsthat:@clerk/clerk-js2. Clerk Service
Create an Angular service at
gui-js/libs/core/src/lib/services/clerk/clerk.service.tsthat:ElectronServiceto send tokens via IPC3. Electron Backend Integration
Update the Electron backend to:
gui-js/apps/minsky-electron/src/app/events/electron.events.tsto receive JWT tokensStoreManager)4. Environment Configuration
Update environment files to include Clerk configuration:
gui-js/apps/minsky-web/src/environments/environment.web.ts5. Package Dependencies
Update
gui-js/package.jsonto include:@clerk/clerk-jsfor Clerk SDK6. Export and Integration
gui-js/libs/ui-components/src/index.tsgui-js/libs/core/src/index.tsgui-js/apps/minsky-web/src/app/app-routing.module.ts)Technical Requirements
IPC Communication Pattern
Follow the existing IPC pattern:
Component Structure
Follow the existing standalone component pattern:
Service Pattern
Follow the existing service pattern with dependency injection:
Files to Create
gui-js/libs/ui-components/src/lib/login/login.component.tsgui-js/libs/ui-components/src/lib/login/login.component.htmlgui-js/libs/ui-components/src/lib/login/login.component.scssgui-js/libs/core/src/lib/services/clerk/clerk.service.tsgui-js/libs/shared/src/lib/events.ts(update to add new event constant)Files to Modify
gui-js/apps/minsky-electron/src/app/events/electron.events.ts- Add JWT token handlergui-js/libs/ui-components/src/index.ts- Export login componentgui-js/libs/core/src/index.ts- Export Clerk servicegui-js/apps/minsky-web/src/environments/environment.web.ts- Add Clerk configgui-js/package.json- Add Clerk dependencygui-js/apps/minsky-web/src/app/app-routing.module.ts- Add login route (optional, for demonstration)Implementation Notes
electronService.isElectron)Security Considera...
This pull request was created from Copilot chat.
This change is
Summary by CodeRabbit
Release Notes