@pnpm/network.web-auth 1000.0.0 → 1101.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.d.ts +2 -1
- package/lib/index.js +1 -0
- package/lib/promptBrowserOpen.d.ts +32 -0
- package/lib/promptBrowserOpen.js +69 -0
- package/lib/withOtpHandling.d.ts +12 -3
- package/lib/withOtpHandling.js +7 -1
- package/package.json +4 -3
package/lib/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { generateQrCode } from './generateQrCode.js';
|
|
2
2
|
export { pollForWebAuthToken, type PollForWebAuthTokenParams, type WebAuthContext, type WebAuthFetchOptions, type WebAuthFetchResponse, type WebAuthFetchResponseHeaders, } from './pollForWebAuthToken.js';
|
|
3
|
+
export { promptBrowserOpen, type PromptBrowserOpenContext, type PromptBrowserOpenParams, type PromptBrowserOpenReadlineInterface, } from './promptBrowserOpen.js';
|
|
3
4
|
export { WebAuthTimeoutError } from './WebAuthTimeoutError.js';
|
|
4
|
-
export { isOtpError, type OtpContext, type OtpEnquirer, type OtpHandlingParams, OtpNonInteractiveError, type OtpPromptOptions, type OtpPromptResponse, OtpSecondChallengeError, SyntheticOtpError, withOtpHandling, } from './withOtpHandling.js';
|
|
5
|
+
export { isOtpError, type OtpContext, type OtpEnquirer, type OtpHandlingParams, OtpNonInteractiveError, type OtpProcess, type OtpPromptOptions, type OtpPromptResponse, OtpSecondChallengeError, SyntheticOtpError, withOtpHandling, } from './withOtpHandling.js';
|
package/lib/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { generateQrCode } from './generateQrCode.js';
|
|
2
2
|
export { pollForWebAuthToken, } from './pollForWebAuthToken.js';
|
|
3
|
+
export { promptBrowserOpen, } from './promptBrowserOpen.js';
|
|
3
4
|
export { WebAuthTimeoutError } from './WebAuthTimeoutError.js';
|
|
4
5
|
export { isOtpError, OtpNonInteractiveError, OtpSecondChallengeError, SyntheticOtpError, withOtpHandling, } from './withOtpHandling.js';
|
|
5
6
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface PromptBrowserOpenReadlineInterface {
|
|
2
|
+
once: (event: string, listener: () => void) => void;
|
|
3
|
+
close: () => void;
|
|
4
|
+
}
|
|
5
|
+
export interface PromptBrowserOpenContext {
|
|
6
|
+
createReadlineInterface?: () => PromptBrowserOpenReadlineInterface;
|
|
7
|
+
globalInfo: (message: string) => void;
|
|
8
|
+
globalWarn: (message: string) => void;
|
|
9
|
+
process: {
|
|
10
|
+
stdin: {
|
|
11
|
+
isTTY?: boolean;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export interface PromptBrowserOpenParams {
|
|
16
|
+
authUrl: string;
|
|
17
|
+
context: PromptBrowserOpenContext;
|
|
18
|
+
pollPromise: Promise<string>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Wraps a token-polling promise with an optional "Press ENTER to open in
|
|
22
|
+
* browser" prompt.
|
|
23
|
+
*
|
|
24
|
+
* While the poll runs in the background, listens for the user pressing Enter
|
|
25
|
+
* to open the authentication URL in their browser. When the poll completes
|
|
26
|
+
* (regardless of whether the user pressed Enter), the keyboard listener is
|
|
27
|
+
* cleaned up.
|
|
28
|
+
*
|
|
29
|
+
* Error-tolerant: failures in the keyboard listener or browser opening are
|
|
30
|
+
* logged as warnings and do not interrupt the poll.
|
|
31
|
+
*/
|
|
32
|
+
export declare function promptBrowserOpen({ authUrl, context, pollPromise }: PromptBrowserOpenParams): Promise<string>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import open from 'open';
|
|
2
|
+
/**
|
|
3
|
+
* Wraps a token-polling promise with an optional "Press ENTER to open in
|
|
4
|
+
* browser" prompt.
|
|
5
|
+
*
|
|
6
|
+
* While the poll runs in the background, listens for the user pressing Enter
|
|
7
|
+
* to open the authentication URL in their browser. When the poll completes
|
|
8
|
+
* (regardless of whether the user pressed Enter), the keyboard listener is
|
|
9
|
+
* cleaned up.
|
|
10
|
+
*
|
|
11
|
+
* Error-tolerant: failures in the keyboard listener or browser opening are
|
|
12
|
+
* logged as warnings and do not interrupt the poll.
|
|
13
|
+
*/
|
|
14
|
+
export async function promptBrowserOpen({ authUrl, context, pollPromise, }) {
|
|
15
|
+
const { createReadlineInterface, globalInfo, globalWarn, process } = context;
|
|
16
|
+
if (!createReadlineInterface || !process.stdin.isTTY) {
|
|
17
|
+
return pollPromise;
|
|
18
|
+
}
|
|
19
|
+
// The authUrl comes from an untrusted registry response, so only allow
|
|
20
|
+
// http(s) URLs through to `open()`.
|
|
21
|
+
let canonicalUrl;
|
|
22
|
+
try {
|
|
23
|
+
const parsed = new URL(authUrl);
|
|
24
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
25
|
+
return pollPromise;
|
|
26
|
+
}
|
|
27
|
+
canonicalUrl = parsed.href;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return pollPromise;
|
|
31
|
+
}
|
|
32
|
+
let rl;
|
|
33
|
+
try {
|
|
34
|
+
rl = createReadlineInterface();
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
globalWarn(`Could not set up keyboard listener: ${String(err)}`);
|
|
38
|
+
return pollPromise;
|
|
39
|
+
}
|
|
40
|
+
globalInfo('Press ENTER to open the URL in your browser.');
|
|
41
|
+
rl.once('line', () => {
|
|
42
|
+
const handleOpenError = (err) => {
|
|
43
|
+
globalWarn(`Could not open browser automatically: ${String(err)}`);
|
|
44
|
+
globalInfo('Please open the URL shown above manually.');
|
|
45
|
+
};
|
|
46
|
+
try {
|
|
47
|
+
open(canonicalUrl).catch(handleOpenError);
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
handleOpenError(err);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
// Only await pollPromise — do NOT await the Enter keypress.
|
|
54
|
+
//
|
|
55
|
+
// The Enter listener is a fire-and-forget side effect. Users may authenticate
|
|
56
|
+
// on their phone (via QR code or pasted URL) without ever pressing Enter, so
|
|
57
|
+
// the poll must be able to complete independently.
|
|
58
|
+
//
|
|
59
|
+
// npm uses Promise.all([opener, poll]) which blocks the entire flow until the
|
|
60
|
+
// user presses Enter — even if authentication already succeeded on another
|
|
61
|
+
// device: <https://github.com/npm/npm-profile/blob/d1a48be4/lib/index.js#L85-L98>
|
|
62
|
+
try {
|
|
63
|
+
return await pollPromise;
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
rl.close();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=promptBrowserOpen.js.map
|
package/lib/withOtpHandling.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { PnpmError } from '@pnpm/error';
|
|
2
2
|
import type { WebAuthFetchOptions, WebAuthFetchResponse } from './pollForWebAuthToken.js';
|
|
3
|
+
import type { PromptBrowserOpenReadlineInterface } from './promptBrowserOpen.js';
|
|
3
4
|
export interface OtpEnquirer {
|
|
4
5
|
prompt: (options: OtpPromptOptions) => Promise<OtpPromptResponse | undefined>;
|
|
5
6
|
}
|
|
@@ -14,16 +15,24 @@ export interface OtpPromptResponse {
|
|
|
14
15
|
interface OtpDate {
|
|
15
16
|
now: () => number;
|
|
16
17
|
}
|
|
18
|
+
export interface OtpProcess {
|
|
19
|
+
platform?: NodeJS.Platform;
|
|
20
|
+
stdin: {
|
|
21
|
+
isTTY?: boolean;
|
|
22
|
+
};
|
|
23
|
+
stdout: {
|
|
24
|
+
isTTY?: boolean;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
17
27
|
export interface OtpContext {
|
|
18
28
|
Date: OtpDate;
|
|
19
29
|
setTimeout: (cb: () => void, ms: number) => void;
|
|
30
|
+
createReadlineInterface?: () => PromptBrowserOpenReadlineInterface;
|
|
20
31
|
enquirer: OtpEnquirer;
|
|
21
32
|
fetch: (url: string, options: WebAuthFetchOptions) => Promise<WebAuthFetchResponse>;
|
|
22
33
|
globalInfo: (message: string) => void;
|
|
23
34
|
globalWarn: (message: string) => void;
|
|
24
|
-
process:
|
|
25
|
-
isTTY?: boolean;
|
|
26
|
-
}>;
|
|
35
|
+
process: OtpProcess;
|
|
27
36
|
}
|
|
28
37
|
interface OtpErrorBody {
|
|
29
38
|
authUrl?: string;
|
package/lib/withOtpHandling.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { PnpmError } from '@pnpm/error';
|
|
2
2
|
import { generateQrCode } from './generateQrCode.js';
|
|
3
3
|
import { pollForWebAuthToken } from './pollForWebAuthToken.js';
|
|
4
|
+
import { promptBrowserOpen } from './promptBrowserOpen.js';
|
|
4
5
|
export const isOtpError = (error) => error != null &&
|
|
5
6
|
typeof error === 'object' &&
|
|
6
7
|
'code' in error &&
|
|
@@ -35,11 +36,16 @@ export async function withOtpHandling({ context, fetchOptions, operation, }) {
|
|
|
35
36
|
if (error.body?.authUrl && error.body?.doneUrl) {
|
|
36
37
|
const qrCode = generateQrCode(error.body.authUrl);
|
|
37
38
|
globalInfo(`Authenticate your account at:\n${error.body.authUrl}\n\n${qrCode}`);
|
|
38
|
-
|
|
39
|
+
const pollPromise = pollForWebAuthToken({
|
|
39
40
|
context,
|
|
40
41
|
doneUrl: error.body.doneUrl,
|
|
41
42
|
fetchOptions,
|
|
42
43
|
});
|
|
44
|
+
otp = await promptBrowserOpen({
|
|
45
|
+
authUrl: error.body.authUrl,
|
|
46
|
+
context,
|
|
47
|
+
pollPromise,
|
|
48
|
+
});
|
|
43
49
|
}
|
|
44
50
|
else {
|
|
45
51
|
const enquirerResponse = await enquirer.prompt({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pnpm/network.web-auth",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1101.0.0",
|
|
4
4
|
"description": "Web-based authentication flow with QR code display and token polling",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pnpm",
|
|
@@ -25,13 +25,14 @@
|
|
|
25
25
|
"!*.map"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
+
"open": "^7.4.2",
|
|
28
29
|
"qrcode-terminal": "^0.12.0",
|
|
29
|
-
"@pnpm/error": "
|
|
30
|
+
"@pnpm/error": "1100.0.0"
|
|
30
31
|
},
|
|
31
32
|
"devDependencies": {
|
|
32
33
|
"@jest/globals": "30.3.0",
|
|
33
34
|
"@types/qrcode-terminal": "^0.12.2",
|
|
34
|
-
"@pnpm/network.web-auth": "
|
|
35
|
+
"@pnpm/network.web-auth": "1101.0.0"
|
|
35
36
|
},
|
|
36
37
|
"engines": {
|
|
37
38
|
"node": ">=22.13"
|