Skip to content

Commit 34163f0

Browse files
committed
fix(cli): handle authentication failures
1 parent 545225a commit 34163f0

5 files changed

Lines changed: 67 additions & 4 deletions

File tree

cli/src/cli.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import { defineCommand, runMain } from 'citty'
33
import { listen } from 'listhen'
44
import { toNodeListener } from 'h3'
55
import { createConnectorApp, generateToken, CONNECTOR_VERSION } from './server'
6-
import { initLogger, showToken, logInfo } from './logger'
6+
import { getNpmUser } from './npm-client'
7+
import { initLogger, showToken, logInfo, showAuthRequired } from './logger'
78

89
const DEFAULT_PORT = 31415
910

@@ -22,9 +23,21 @@ const main = defineCommand({
2223
},
2324
async run({ args }) {
2425
const port = Number.parseInt(args.port as string, 10) || DEFAULT_PORT
25-
const token = generateToken()
2626

2727
initLogger()
28+
29+
// Check npm authentication before starting
30+
logInfo('Checking npm authentication...')
31+
const npmUser = await getNpmUser()
32+
33+
if (!npmUser) {
34+
showAuthRequired()
35+
process.exit(1)
36+
}
37+
38+
logInfo(`Authenticated as: ${npmUser}`)
39+
40+
const token = generateToken()
2841
showToken(token, port)
2942

3043
const app = createConnectorApp(token)

cli/src/logger.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,24 @@ export function showOutro(message: string): void {
7575
p.outro(message)
7676
}
7777

78+
/**
79+
* Show authentication required error in a box
80+
*/
81+
export function showAuthRequired(): void {
82+
p.note(
83+
[
84+
pc.red('Not logged in to npm'),
85+
'',
86+
'Please run the following command to log in:',
87+
'',
88+
` ${pc.cyan('npm login')}`,
89+
'',
90+
'Then restart the connector.',
91+
].join('\n'),
92+
'Authentication required',
93+
)
94+
}
95+
7896
/**
7997
* Create a spinner for async operations
8098
*/

cli/src/npm-client.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ export interface NpmExecResult {
1010
exitCode: number
1111
/** True if the operation failed due to missing/invalid OTP */
1212
requiresOtp?: boolean
13+
/** True if the operation failed due to authentication failure (not logged in or token expired) */
14+
authFailure?: boolean
1315
}
1416

1517
function detectOtpRequired(stderr: string): boolean {
@@ -18,12 +20,29 @@ function detectOtpRequired(stderr: string): boolean {
1820
'one-time password',
1921
'This operation requires a one-time password',
2022
'--otp=<code>',
21-
'OTP',
2223
]
2324
const lowerStderr = stderr.toLowerCase()
2425
return otpPatterns.some(pattern => lowerStderr.includes(pattern.toLowerCase()))
2526
}
2627

28+
function detectAuthFailure(stderr: string): boolean {
29+
const authPatterns = [
30+
'ENEEDAUTH',
31+
'You must be logged in',
32+
'authentication error',
33+
'Unable to authenticate',
34+
'code E401',
35+
'code E403',
36+
'401 Unauthorized',
37+
'403 Forbidden',
38+
'not logged in',
39+
'npm login',
40+
'npm adduser',
41+
]
42+
const lowerStderr = stderr.toLowerCase()
43+
return authPatterns.some(pattern => lowerStderr.includes(pattern.toLowerCase()))
44+
}
45+
2746
function filterNpmWarnings(stderr: string): string {
2847
return stderr
2948
.split('\n')
@@ -70,11 +89,15 @@ export async function execNpm(
7089
const err = error as { stdout?: string, stderr?: string, code?: number }
7190
const stderr = err.stderr?.trim() ?? String(error)
7291
const requiresOtp = detectOtpRequired(stderr)
92+
const authFailure = detectAuthFailure(stderr)
7393

7494
if (!options.silent) {
7595
if (requiresOtp) {
7696
logError('OTP required')
7797
}
98+
else if (authFailure) {
99+
logError('Authentication required - please run "npm login" and restart the connector')
100+
}
78101
else {
79102
logError(filterNpmWarnings(stderr).split('\n')[0] || 'Command failed')
80103
}
@@ -84,9 +107,12 @@ export async function execNpm(
84107
stdout: err.stdout?.trim() ?? '',
85108
stderr: requiresOtp
86109
? 'This operation requires a one-time password (OTP).'
87-
: filterNpmWarnings(stderr),
110+
: authFailure
111+
? 'Authentication failed. Please run "npm login" and restart the connector.'
112+
: filterNpmWarnings(stderr),
88113
exitCode: err.code ?? 1,
89114
requiresOtp,
115+
authFailure,
90116
}
91117
}
92118
}

cli/src/server.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,11 +353,15 @@ export function createConnectorApp(expectedToken: string) {
353353
await Promise.all(runningOps)
354354
}
355355

356+
// Check if any operation had an auth failure
357+
const authFailure = results.some(r => r.result.authFailure)
358+
356359
return {
357360
success: true,
358361
data: {
359362
results,
360363
otpRequired,
364+
authFailure,
361365
},
362366
} as ApiResponse
363367
}),

cli/src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ export interface OperationResult {
3636
exitCode: number
3737
/** True if the operation failed due to missing/invalid OTP */
3838
requiresOtp?: boolean
39+
/** True if the operation failed due to authentication failure (not logged in or token expired) */
40+
authFailure?: boolean
3941
}
4042

4143
export interface PendingOperation {

0 commit comments

Comments
 (0)