@pnpm/network.web-auth 1101.4.3 → 1101.5.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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @pnpm/network.web-auth
2
2
 
3
+ ## 1101.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - `pnpm stage approve` now approves several staged packages at once. Run it without a stage id to pick from the staged versions interactively, or pass a list of stage ids. The whole batch is approved with a single one-time password, and pnpm asks for a new one only once the registry stops accepting it. Inside a workspace, the selected packages are approved in dependency order, and a package whose workspace dependency could not be approved is skipped instead of being published against a dependency that never reached the registry.
8
+
3
9
  ## 1101.4.3
4
10
 
5
11
  ### Patch Changes
package/lib/index.d.ts CHANGED
@@ -3,4 +3,4 @@ export { generateQrCode } from './generateQrCode.js';
3
3
  export { pollForWebAuthToken, type PollForWebAuthTokenParams, type WebAuthContext, type WebAuthFetchOptions, type WebAuthFetchResponse, type WebAuthFetchResponseBody, type WebAuthFetchResponseBodyReader, type WebAuthFetchResponseHeaders, } from './pollForWebAuthToken.js';
4
4
  export { promptBrowserOpen, type PromptBrowserOpenContext, type PromptBrowserOpenParams, type PromptBrowserOpenReadlineInterface, } from './promptBrowserOpen.js';
5
5
  export { WebAuthTimeoutError } from './WebAuthTimeoutError.js';
6
- export { canonicalHttpUrl, isOtpError, type OtpContext, type OtpEnquirer, type OtpHandlingParams, OtpNonInteractiveError, type OtpProcess, OtpSecondChallengeError, SyntheticOtpError, withOtpHandling, } from './withOtpHandling.js';
6
+ export { canonicalHttpUrl, createOtpSession, isOtpError, type OtpContext, type OtpEnquirer, type OtpHandlingParams, OtpNonInteractiveError, type OtpProcess, OtpSecondChallengeError, type OtpSession, type OtpSessionParams, SyntheticOtpError, withOtpHandling, } from './withOtpHandling.js';
package/lib/index.js CHANGED
@@ -3,5 +3,5 @@ export { generateQrCode } from './generateQrCode.js';
3
3
  export { pollForWebAuthToken, } from './pollForWebAuthToken.js';
4
4
  export { promptBrowserOpen, } from './promptBrowserOpen.js';
5
5
  export { WebAuthTimeoutError } from './WebAuthTimeoutError.js';
6
- export { canonicalHttpUrl, isOtpError, OtpNonInteractiveError, OtpSecondChallengeError, SyntheticOtpError, withOtpHandling, } from './withOtpHandling.js';
6
+ export { canonicalHttpUrl, createOtpSession, isOtpError, OtpNonInteractiveError, OtpSecondChallengeError, SyntheticOtpError, withOtpHandling, } from './withOtpHandling.js';
7
7
  //# sourceMappingURL=index.js.map
@@ -42,6 +42,33 @@ export interface OtpHandlingParams<T> {
42
42
  fetchOptions: WebAuthFetchOptions;
43
43
  operation: (otp?: string) => Promise<T>;
44
44
  }
45
+ export interface OtpSessionParams {
46
+ context: OtpContext;
47
+ fetchOptions: WebAuthFetchOptions;
48
+ }
49
+ export interface OtpSession {
50
+ /**
51
+ * Runs `operation` with the one-time password this session holds, obtaining
52
+ * one on demand.
53
+ *
54
+ * The first operation runs without a password (the caller may still send a
55
+ * configured `--otp`); the password obtained from a challenge is kept and
56
+ * passed to every later operation, so a batch of operations costs one
57
+ * authentication instead of one per operation. When a kept password stops
58
+ * being accepted — a classic OTP expires within a minute — the challenge it
59
+ * triggers obtains a new one and the operation is retried with it.
60
+ */
61
+ run: <T>(operation: (otp?: string) => Promise<T>) => Promise<T>;
62
+ }
63
+ /**
64
+ * Creates an {@link OtpSession}: OTP challenge handling shared across a series
65
+ * of operations.
66
+ *
67
+ * @throws {@link OtpNonInteractiveError} if OTP is required but the terminal is not interactive.
68
+ * @throws {@link OtpSecondChallengeError} if the registry challenges an operation again right after
69
+ * a freshly obtained one-time password was submitted for it.
70
+ */
71
+ export declare function createOtpSession({ context, fetchOptions }: OtpSessionParams): OtpSession;
45
72
  /**
46
73
  * Wraps an operation with OTP (one-time password) challenge handling.
47
74
  *
@@ -51,6 +78,9 @@ export interface OtpHandlingParams<T> {
51
78
  * 2. Falls back to prompting the user for a classic OTP code.
52
79
  * 3. Retries the operation with the obtained OTP.
53
80
  *
81
+ * Use {@link createOtpSession} instead when several operations authenticate
82
+ * against the same registry in one run, so they share one one-time password.
83
+ *
54
84
  * @throws {@link OtpNonInteractiveError} if OTP is required but the terminal is not interactive.
55
85
  * @throws {@link OtpSecondChallengeError} if the registry requests OTP a second time after one was submitted.
56
86
  * @throws the original error if OTP handling is not applicable.
@@ -6,6 +6,43 @@ export const isOtpError = (error) => error != null &&
6
6
  typeof error === 'object' &&
7
7
  'code' in error &&
8
8
  error.code === 'EOTP';
9
+ /**
10
+ * Creates an {@link OtpSession}: OTP challenge handling shared across a series
11
+ * of operations.
12
+ *
13
+ * @throws {@link OtpNonInteractiveError} if OTP is required but the terminal is not interactive.
14
+ * @throws {@link OtpSecondChallengeError} if the registry challenges an operation again right after
15
+ * a freshly obtained one-time password was submitted for it.
16
+ */
17
+ export function createOtpSession({ context, fetchOptions }) {
18
+ let sessionOtp;
19
+ return {
20
+ async run(operation) {
21
+ let error;
22
+ try {
23
+ return await operation(sessionOtp);
24
+ }
25
+ catch (err) {
26
+ if (!isOtpError(err))
27
+ throw err;
28
+ error = err;
29
+ }
30
+ const otp = await resolveOtpChallenge(context, fetchOptions, error);
31
+ if (otp == null)
32
+ throw error;
33
+ sessionOtp = otp;
34
+ try {
35
+ return await operation(otp);
36
+ }
37
+ catch (retryError) {
38
+ if (isOtpError(retryError)) {
39
+ throw new OtpSecondChallengeError();
40
+ }
41
+ throw retryError;
42
+ }
43
+ },
44
+ };
45
+ }
9
46
  /**
10
47
  * Wraps an operation with OTP (one-time password) challenge handling.
11
48
  *
@@ -15,6 +52,9 @@ export const isOtpError = (error) => error != null &&
15
52
  * 2. Falls back to prompting the user for a classic OTP code.
16
53
  * 3. Retries the operation with the obtained OTP.
17
54
  *
55
+ * Use {@link createOtpSession} instead when several operations authenticate
56
+ * against the same registry in one run, so they share one one-time password.
57
+ *
18
58
  * @throws {@link OtpNonInteractiveError} if OTP is required but the terminal is not interactive.
19
59
  * @throws {@link OtpSecondChallengeError} if the registry requests OTP a second time after one was submitted.
20
60
  * @throws the original error if OTP handling is not applicable.
@@ -22,60 +62,48 @@ export const isOtpError = (error) => error != null &&
22
62
  * @see https://github.com/npm/cli/blob/7d900c46/lib/utils/otplease.js for npm's implementation.
23
63
  */
24
64
  export async function withOtpHandling({ context, fetchOptions, operation, }) {
25
- const { enquirer, globalInfo, globalWarn, process, } = context;
65
+ return createOtpSession({ context, fetchOptions }).run(operation);
66
+ }
67
+ /**
68
+ * Satisfies an OTP challenge, either through the web-based authentication flow
69
+ * (when the challenge carries both `authUrl` and `doneUrl`) or by prompting for
70
+ * a classic one-time password.
71
+ *
72
+ * @returns the one-time password, or `undefined` when the user supplied none.
73
+ */
74
+ async function resolveOtpChallenge(context, fetchOptions, error) {
75
+ const { enquirer, globalInfo, globalWarn, process } = context;
76
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
77
+ throw new OtpNonInteractiveError(error.body);
78
+ }
79
+ const authUrl = canonicalHttpUrl(error.body?.authUrl);
80
+ const doneUrl = canonicalHttpUrl(error.body?.doneUrl);
81
+ if (authUrl != null && doneUrl != null) {
82
+ globalInfo(formatAuthUrlMessage(authUrl, globalWarn));
83
+ const pollPromise = pollForWebAuthToken({
84
+ context,
85
+ doneUrl,
86
+ fetchOptions,
87
+ });
88
+ return promptBrowserOpen({
89
+ authUrl,
90
+ context,
91
+ pollPromise,
92
+ });
93
+ }
94
+ let otp;
26
95
  try {
27
- return await operation();
96
+ otp = await enquirer.input({
97
+ message: 'This operation requires a one-time password.\nEnter OTP:',
98
+ });
28
99
  }
29
- catch (error) {
30
- if (!isOtpError(error))
31
- throw error;
32
- if (!process.stdin.isTTY || !process.stdout.isTTY) {
33
- throw new OtpNonInteractiveError(error.body);
34
- }
35
- let otp;
36
- const authUrl = canonicalHttpUrl(error.body?.authUrl);
37
- const doneUrl = canonicalHttpUrl(error.body?.doneUrl);
38
- if (authUrl != null && doneUrl != null) {
39
- globalInfo(formatAuthUrlMessage(authUrl, globalWarn));
40
- const pollPromise = pollForWebAuthToken({
41
- context,
42
- doneUrl,
43
- fetchOptions,
44
- });
45
- otp = await promptBrowserOpen({
46
- authUrl,
47
- context,
48
- pollPromise,
49
- });
50
- }
51
- else {
52
- let otpValue;
53
- try {
54
- otpValue = await enquirer.input({
55
- message: 'This operation requires a one-time password.\nEnter OTP:',
56
- });
57
- }
58
- catch (err) {
59
- if (err instanceof Error && err.name === 'ExitPromptError') {
60
- throw error;
61
- }
62
- throw err;
63
- }
64
- otp = otpValue || undefined;
65
- }
66
- if (otp != null) {
67
- try {
68
- return await operation(otp);
69
- }
70
- catch (retryError) {
71
- if (isOtpError(retryError)) {
72
- throw new OtpSecondChallengeError();
73
- }
74
- throw retryError;
75
- }
76
- }
77
- throw error;
100
+ catch (err) {
101
+ // The user aborted the prompt: re-throw the original challenge.
102
+ if (err instanceof Error && err.name === 'ExitPromptError')
103
+ return undefined;
104
+ throw err;
78
105
  }
106
+ return otp || undefined;
79
107
  }
80
108
  /**
81
109
  * Synthetic instance of {@link OtpError} meant to be thrown by the callbacks of {@link withOtpHandling}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pnpm/network.web-auth",
3
- "version": "1101.4.3",
3
+ "version": "1101.5.0",
4
4
  "description": "Web-based authentication flow with QR code display and token polling",
5
5
  "keywords": [
6
6
  "pnpm",
@@ -34,7 +34,7 @@
34
34
  },
35
35
  "devDependencies": {
36
36
  "@jest/globals": "30.4.1",
37
- "@pnpm/network.web-auth": "1101.4.3",
37
+ "@pnpm/network.web-auth": "1101.5.0",
38
38
  "@types/qrcode-terminal": "^0.12.2"
39
39
  },
40
40
  "engines": {