Skip to content

Conversation

@mikouaji
Copy link
Contributor

@mikouaji mikouaji commented Feb 10, 2026

This feature extends the npmx connector functionality for handling authorization with 2fa keys configured in npm account settings.
This works by allowing running the npm commands within an interactive shell which does not prompt for OTP but rather asks to verify auth on a web page.

I have added some settings to the connector modal:

  • Use web auth - enables the feature
  • Auto open auth page - opens the auth page automatically in a new tab

Currently tested only on linux
Needs testing on Windows and Mac

Preview before connecting (in Advanced options):
web_auth_settings_disconnected

Preview after connecting:
web_auth_settings_connected

Some previews of it in action:

web_auth_auto_url.mp4
web_auth_no_auto_url.mp4
web_auth_otp_with_web_after.mp4

FYI @neutrino2211 I was supposed to tag you here ;)

@vercel
Copy link

vercel bot commented Feb 10, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
npmx.dev Ready Ready Preview, Comment Feb 11, 2026 8:17pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs.npmx.dev Ignored Ignored Preview Feb 11, 2026 8:17pm
npmx-lunaria Ignored Ignored Feb 11, 2026 8:17pm

Request Review

@github-actions
Copy link

github-actions bot commented Feb 10, 2026

Lunaria Status Overview

🌕 This pull request will trigger status changes.

Learn more

By default, every PR changing files present in the Lunaria configuration's files property will be considered and trigger status changes accordingly.

You can change this by adding one of the keywords present in the ignoreKeywords property in your Lunaria configuration file in the PR's title (ignoring all files) or by including a tracker directive in the merged commit's description.

Tracked Files

File Note
lunaria/files/en-GB.json Localization changed, will be marked as complete. 🔄️
lunaria/files/en-US.json Source changed, localizations will be marked as outdated.
lunaria/files/pl-PL.json Localization changed, will be marked as complete. 🔄️
Warnings reference
Icon Description
🔄️ The source for this localization has been updated since the creation of this pull request, make sure all changes in the source have been applied.

@danielroe
Copy link
Member

this is beautiful

@neutrino2211
Copy link
Contributor

Will pull and test on MacOS in a bit. Thanks a ton! Auth issues have been a pain

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 10, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Integrates a web-based auth fallback and an auto-open-URL setting across UI, composables, CLI and server. ConnectorModal and OperationsQueue expose a connector.autoOpenURL toggle and controls to open auth URLs; OperationsQueue adds web-auth polling, OTP input state and retry flows. useConnector and useSettings propagate autoOpenURL; /execute and executeOperation accept structured options { otp?, interactive?, openUrls? } and thread ExecNpmOptions through execution. CLI gains PTY-based interactive npm execution, URL extraction, onAuthUrl callbacks, new types/schemas, node-pty typings and bundler externals. i18n keys and tests updated.

Possibly related PRs

Suggested reviewers

  • danielroe
  • serhalp
🚥 Pre-merge checks | ✅ 1
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The PR description clearly relates to the changeset, explaining the feature to extend npmx connector for web authentication with 2FA, including settings added to the connector modal and UI previews.

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

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (1)
app/components/Org/OperationsQueue.vue (1)

68-78: Consider extracting a shared retry helper.

handleRetryWithOtp and handleRetryWithWebAuth duplicate the OTP-failed retry loop; a small helper would keep the flow DRY.

@mikouaji
Copy link
Contributor Author

🤦 as always tests need fixing, looking into them

@codecov
Copy link

codecov bot commented Feb 10, 2026

Codecov Report

❌ Patch coverage is 61.19403% with 26 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
app/components/Org/OperationsQueue.vue 58.92% 18 Missing and 5 partials ⚠️
app/composables/useConnector.ts 25.00% 2 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
app/components/Org/OperationsQueue.vue (1)

68-78: Optional: dedupe retry logic.
handleRetryWithWebAuth and handleRetryWithOtp share the same retry loop; a small helper would reduce drift.

♻️ Possible refactor
+async function retryOtpFailedOps(): Promise<void> {
+  const otpFailedOps = activeOperations.value.filter(
+    (op: PendingOperation) => op.status === 'failed' && op.result?.requiresOtp,
+  )
+  for (const op of otpFailedOps) {
+    await retryOperation(op.id)
+  }
+}
+
 async function handleRetryWithOtp() {
   if (!otpInput.value.trim()) return
   const otp = otpInput.value.trim()
   otpInput.value = ''
-  const otpFailedOps = activeOperations.value.filter(
-    (op: PendingOperation) => op.status === 'failed' && op.result?.requiresOtp,
-  )
-  for (const op of otpFailedOps) {
-    await retryOperation(op.id)
-  }
+  await retryOtpFailedOps()
   await handleExecute(otp)
 }
 
 async function handleRetryWithWebAuth() {
-  const otpFailedOps = activeOperations.value.filter(
-    (op: PendingOperation) => op.status === 'failed' && op.result?.requiresOtp,
-  )
-  for (const op of otpFailedOps) {
-    await retryOperation(op.id)
-  }
+  await retryOtpFailedOps()
   await handleExecute()
 }
cli/src/npm-client.ts (1)

168-301: Consider splitting execNpmInteractive into helpers.
This function is well structured but quite long; extracting pieces (spawn setup, onData handling, finalise result) would improve maintainability.

As per coding guidelines, "Keep functions focused and manageable (generally under 50 lines)".

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
cli/src/npm-client.ts (1)

217-258: Consider disposing event subscriptions to prevent potential memory leaks.

The onData and onExit methods return IDisposable objects that should be disposed when no longer needed. While the PTY process exits anyway, explicitly disposing these subscriptions is a best practice, especially if this code is later adapted for long-running scenarios.

Proposed refactor
+    const dataDisposable = child.onData((data: string) => {
-    child.onData((data: string) => {
       output += data
       // ... rest of handler
     })

+    const exitDisposable = child.onExit(({ exitCode }) => {
-    child.onExit(({ exitCode }) => {
       if (resolved) return
       resolved = true
       clearTimeout(timeout)
       if (authUrlTimeout) clearTimeout(authUrlTimeout)
+      dataDisposable.dispose()
+      exitDisposable.dispose()

       // ... rest of handler
     })

@mikouaji
Copy link
Contributor Author

@danielroe fixed all tests etc

@danielroe
Copy link
Member

tested this and seemed to work well on MacOS - in fact I think it could be the default (maybe even non-configurable?) and only if it fails should we fall back to OTP...

@neutrino2211
Copy link
Contributor

Just checked it out. The UX is very clean and the connector isn't giving issues straight away.

Your org management UI is also way better than what I was planning so i think this addresses #34 as well

@neutrino2211
Copy link
Contributor

tested this and seemed to work well on MacOS - in fact I think it could be the default (maybe even non-configurable?) and only if it fails should we fall back to OTP...

Yeah, I'm onboard with this

@danielroe
Copy link
Member

danielroe commented Feb 11, 2026

I don't think this PR touched the org management UI - I think that's back from some time ago.

@mikouaji
Copy link
Contributor Author

in fact I think it could be the default (maybe even non-configurable?) and only if it fails should we fall back to OTP

I can make it work this way, we just need a clean way of handling it, currently there is a 90s timeout if the link is not clicked. Other than that only happy cases work pretty well, other failures still need to be covered.

Your org management UI is also way better than what I was planning so i think this addresses #34 as well

Yeah, nothing was changed there, you must be seeing the UI from before you did anything.

@mikouaji
Copy link
Contributor Author

What about the auto opening setting? I'd leave it so people can choose, some might not like tabs appearing suddenly.

@neutrino2211
Copy link
Contributor

I could have sworn I saw UI for managing org teams and members but to be honest it was pretty late at night so I might have been too exhausted to understand what I was looking at.

I'll take a look again with fresh eyes

@neutrino2211
Copy link
Contributor

What about the auto opening setting? I'd leave it so people can choose, some might not like tabs appearing suddenly.

Yeah. I'm normally the type to hate tabs popping up without me initiating them but I'm so over npm auth that I just left it on

@danielroe
Copy link
Member

we can have auto-open disabled by default ✅

(and we can iterate on this)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cli/src/mock-state.ts (1)

275-318: ⚠️ Potential issue | 🟡 Minor

Include configured URLs in mocked results.

configuredResult.urls is currently dropped, so the aggregated urls output will always miss URLs when per-operation results are injected for tests.

Proposed fix
        const result: OperationResult = {
          stdout: configuredResult.stdout ?? '',
          stderr: configuredResult.stderr ?? '',
          exitCode: configuredResult.exitCode ?? 1,
          requiresOtp: configuredResult.requiresOtp,
          authFailure: configuredResult.authFailure,
+         urls: configuredResult.urls,
        }

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/components/Org/OperationsQueue.vue (1)

107-110: ⚠️ Potential issue | 🟠 Major

Include authFailure ops in the OTP retry queue.

hasOtpFailures now treats authFailure as needing OTP fallback, but handleRetryWithOtp only retries requiresOtp, leaving authFailure ops failed even after OTP entry. Please align the retry filter with the failure detection.

🔧 Suggested fix
-  const otpFailedOps = activeOperations.value.filter(
-    (op: PendingOperation) => op.status === 'failed' && op.result?.requiresOtp,
-  )
+  const otpFailedOps = activeOperations.value.filter(
+    (op: PendingOperation) =>
+      op.status === 'failed' && (op.result?.requiresOtp || op.result?.authFailure),
+  )
🧹 Nitpick comments (1)
cli/src/npm-client.ts (1)

167-305: Consider splitting execNpmInteractive into smaller helpers.
It’s doing prompt detection, timeouts, logging, and result shaping in one block, which makes it harder to test and maintain. As per coding guidelines: Keep functions focused and manageable (generally under 50 lines).

@mikouaji
Copy link
Contributor Author

@danielroe Updated:

  • Web auth is used by default
  • Auto opening is disabled by default
  • Fallback to OTP

@danielroe
Copy link
Member

amazing! ❤️

@danielroe danielroe added this pull request to the merge queue Feb 11, 2026
Merged via the queue into npmx-dev:main with commit 4db33e9 Feb 11, 2026
18 checks passed
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.

3 participants