niranzwp 0.8.5 → 0.8.6

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/bin/niranzwp.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { loginWithAppPassword, loginManual } from '../lib/auth.js';
2
+ import { loginWithAppPassword, loginManual, credentialsFromEnv } from '../lib/auth.js';
3
3
  import { saveProfile, saveOAuthProfile, getProfile, listProfiles, deleteProfile, storageKind, configDir } from '../lib/store.js';
4
4
  import { discover, registerClient, startDeviceFlow, pollForToken } from '../lib/oauth.js';
5
5
  import { CliError } from '../lib/errors.js';
@@ -257,9 +257,31 @@ async function main() {
257
257
  // password on its own success page and the user carries it here. The
258
258
  // only route that works when the browser cannot reach this machine --
259
259
  // SSH, a VPS, a phone browser against a CLI running elsewhere.
260
- const creds = flags.manual === true
261
- ? await loginManual(site, { open: flags.open !== false })
262
- : await loginWithAppPassword(site, { open: flags.open !== false });
260
+ /*
261
+ * The environment first, and a refusal rather than a hang.
262
+ *
263
+ * Both routes below need a person -- one to approve in a browser, one
264
+ * to type at a prompt -- and neither can tell you that. Given the
265
+ * variables, this skips them entirely; without them, and with no
266
+ * terminal to prompt at, it says so instead of waiting.
267
+ */
268
+ const fromEnv = credentialsFromEnv();
269
+ let creds;
270
+
271
+ if (fromEnv) {
272
+ const info = await probe(site);
273
+ creds = { siteUrl: site.replace(/\/+$/, ''), ...fromEnv, info };
274
+ } else if (process.stdin.isTTY !== true && flags.manual !== true) {
275
+ throw new CliError(
276
+ 'no_terminal',
277
+ 'There is no terminal here to approve or type at, and no credentials in the environment.',
278
+ { hint: 'Set NIRANZWP_USER and NIRANZWP_APP_PASSWORD, or pipe the two answers into --manual.' }
279
+ );
280
+ } else {
281
+ creds = flags.manual === true
282
+ ? await loginManual(site, { open: flags.open !== false })
283
+ : await loginWithAppPassword(site, { open: flags.open !== false });
284
+ }
263
285
 
264
286
  // A pasted credential can be mistyped, so prove it works before
265
287
  // storing it. The loopback flow's credential came from WordPress
package/lib/auth.js CHANGED
@@ -111,6 +111,27 @@ export async function loginWithAppPassword(siteUrl, { appName = 'NiranzWP CLI',
111
111
  console.error('Approve this connection in your browser:');
112
112
  console.error(` ${authorize}`);
113
113
  console.error('');
114
+ /*
115
+ * Said before the wait, not after it.
116
+ *
117
+ * The callback is http://127.0.0.1, and WordPress accepts an
118
+ * http:// redirect only when the site's own environment type is
119
+ * "local" -- wp_is_authorize_application_redirect_url_valid()
120
+ * exempts nothing by host, not loopback, not private ranges. On a
121
+ * live site the browser is shown "The URL must be served over a
122
+ * secure connection." and this listener, which cannot see that
123
+ * page, waits out its five minutes in silence.
124
+ *
125
+ * Whether a given site will refuse is not knowable from here --
126
+ * the environment type is not in the REST root, and a public site
127
+ * can still be set to "local", wrongly but effectively. So this
128
+ * does not guess. It says what that error means and what to do
129
+ * about it, where it will be read.
130
+ */
131
+ console.error('If the browser says "The URL must be served over a secure connection",');
132
+ console.error('this site will not hand the password back here. Stop and run:');
133
+ console.error(` niranzwp auth login ${siteUrl} --manual`);
134
+ console.error('');
114
135
  if (open) openBrowser(authorize.toString());
115
136
  });
116
137
  });
@@ -156,6 +177,30 @@ export async function loginManual(siteUrl, { appName = 'NiranzWP CLI', open = tr
156
177
  return { siteUrl: siteUrl.replace(/\/+$/, ''), user: user.trim(), password: password.trim(), info };
157
178
  }
158
179
 
180
+ /**
181
+ * Credentials from the environment, for anything with nobody sitting at it.
182
+ *
183
+ * Every other route here needs a person: a browser to approve in, or a
184
+ * terminal to type into. A script, a CI job or an agent has neither, and used
185
+ * to discover that by waiting five minutes for a callback that was never
186
+ * coming. Two variables connect directly:
187
+ *
188
+ * NIRANZWP_USER=editor NIRANZWP_APP_PASSWORD='abcd efgh ...' \
189
+ * niranzwp auth login https://example.com
190
+ *
191
+ * Deliberately not flags. A password given on the command line is written
192
+ * into shell history and shows up in `ps` to every other user on the machine.
193
+ * An environment variable does neither.
194
+ *
195
+ * @returns {{user: string, password: string} | null}
196
+ */
197
+ export function credentialsFromEnv() {
198
+ const user = (process.env.NIRANZWP_USER || '').trim();
199
+ const password = (process.env.NIRANZWP_APP_PASSWORD || '').trim();
200
+
201
+ return user && password ? { user, password } : null;
202
+ }
203
+
159
204
  /**
160
205
  * Both prompts on ONE readline interface.
161
206
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "niranzwp",
3
- "version": "0.8.5",
3
+ "version": "0.8.6",
4
4
  "description": "A CLI for WordPress. Works on any site via Application Passwords, and unlocks Abilities where a site provides them.",
5
5
  "type": "module",
6
6
  "bin": {