@exposify/cli 0.0.5 → 0.0.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.
Files changed (2) hide show
  1. package/dist/index.js +35 -3
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1232,7 +1232,7 @@ function copyToClipboard(text, deps = {}) {
1232
1232
 
1233
1233
  // src/sign-in.ts
1234
1234
  import { spawn } from "child_process";
1235
- import { existsSync as existsSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
1235
+ import { chmodSync as chmodSync2, existsSync as existsSync2, readFileSync as readFileSync6, statSync, writeFileSync as writeFileSync5 } from "fs";
1236
1236
  import { join as join3 } from "path";
1237
1237
  import { parseEnv as parseEnv2 } from "util";
1238
1238
  import { authStacksOf, dependencyNames } from "@exposify/core";
@@ -1265,11 +1265,16 @@ function writeKeyToEnv(root, key) {
1265
1265
  const path = join3(root, ".env");
1266
1266
  const line = `${KEY_ENV}=${key}`;
1267
1267
  let created = false;
1268
+ let tightened = false;
1268
1269
  if (existsSync2(path)) {
1269
1270
  const text = readFileSync6(path, "utf8");
1270
1271
  const re = new RegExp(`^\\s*(export\\s+)?${KEY_ENV}\\s*=.*$`, "m");
1271
1272
  const next = re.test(text) ? text.replace(re, line) : `${text}${text === "" || text.endsWith("\n") ? "" : "\n"}${line}
1272
1273
  `;
1274
+ if (process.platform !== "win32" && (statSync(path).mode & 63) !== 0) {
1275
+ chmodSync2(path, 384);
1276
+ tightened = true;
1277
+ }
1273
1278
  writeFileSync5(path, next);
1274
1279
  } else {
1275
1280
  writeFileSync5(path, `${line}
@@ -1281,7 +1286,22 @@ function writeKeyToEnv(root, key) {
1281
1286
  ignored = readFileSync6(join3(root, ".gitignore"), "utf8").split("\n").map((l) => l.trim()).some((l) => [".env", "/.env", ".env*", "/.env*", "*.env"].includes(l));
1282
1287
  } catch {
1283
1288
  }
1284
- return { path, created, ignored };
1289
+ return { path, created, tightened, ignored };
1290
+ }
1291
+ var DASHBOARD_HOSTS = ["dashboard.agemno.com"];
1292
+ function isSafeVerifyUrl(verifyUrl, apiUrl) {
1293
+ if (/[\s&|^<>"'`%;$\\]/.test(verifyUrl)) return false;
1294
+ let url;
1295
+ try {
1296
+ url = new URL(verifyUrl);
1297
+ } catch {
1298
+ return false;
1299
+ }
1300
+ const isLocal = (u) => u.hostname === "localhost" || u.hostname === "127.0.0.1" || u.hostname === "[::1]";
1301
+ if (!(url.protocol === "https:" || url.protocol === "http:" && isLocal(url))) return false;
1302
+ if (url.username || url.password) return false;
1303
+ const api = new URL(apiUrl);
1304
+ return url.host === api.host || DASHBOARD_HOSTS.includes(url.host) || isLocal(url) && isLocal(api);
1285
1305
  }
1286
1306
  async function signInToExposify(deps) {
1287
1307
  const fetchImpl = deps.fetchImpl ?? fetch;
@@ -1302,6 +1322,13 @@ async function signInToExposify(deps) {
1302
1322
  } catch (err) {
1303
1323
  return { ok: false, reason: "unreachable", message: `couldn't reach ${deps.apiUrl} (${err.message})` };
1304
1324
  }
1325
+ if (typeof started.verifyUrl !== "string" || !isSafeVerifyUrl(started.verifyUrl, deps.apiUrl)) {
1326
+ return {
1327
+ ok: false,
1328
+ reason: "unsafe",
1329
+ message: `${deps.apiUrl} sent a sign-in link that isn't on its own host or ${DASHBOARD_HOSTS.join(", ")}, so it wasn't opened`
1330
+ };
1331
+ }
1305
1332
  const opened = (deps.openUrl ?? openInBrowser)(started.verifyUrl);
1306
1333
  deps.show({ userCode: started.userCode, verifyUrl: started.verifyUrl, opened });
1307
1334
  const deadline = Date.now() + started.expiresIn * 1e3;
@@ -1331,9 +1358,13 @@ async function signInToExposify(deps) {
1331
1358
  }
1332
1359
  return { ok: false, reason: "expired", message: "the sign-in link expired" };
1333
1360
  }
1361
+ function browserCommand(url, platform = process.platform) {
1362
+ if (platform === "win32") return ["rundll32", "url.dll,FileProtocolHandler", url];
1363
+ return platform === "darwin" ? ["open", url] : ["xdg-open", url];
1364
+ }
1334
1365
  function openInBrowser(url) {
1335
1366
  if (process.env.EXPOSIFY_NO_BROWSER === "1") return false;
1336
- const [cmd, ...args] = process.platform === "darwin" ? ["open", url] : process.platform === "win32" ? ["cmd", "/c", "start", "", url] : ["xdg-open", url];
1367
+ const [cmd, ...args] = browserCommand(url);
1337
1368
  try {
1338
1369
  const child = spawn(cmd, args, { stdio: "ignore", detached: true });
1339
1370
  child.on("error", () => {
@@ -1984,6 +2015,7 @@ ${bold(title)} \xB7 How your users sign in`);
1984
2015
  const written = writeKeyToEnv(root, outcome.key);
1985
2016
  process.env[KEY_ENV] = outcome.key;
1986
2017
  console.log(green(` \u2713 ${written.created ? "Created" : "Updated"} ${relative(cwd, written.path) || written.path} with ${KEY_ENV}`) + ` (project "${outcome.projectName}").`);
2018
+ if (written.tightened) console.log(dim(` .env could be read by other users on this machine; it's owner-only now.`));
1987
2019
  if (!written.ignored) console.log(yellow(` ! Add .env to .gitignore: it now holds a secret.`));
1988
2020
  if (own) console.log(` Your app tells Exposify about its ${sb ? "Supabase Auth" : "login"} when it starts with ${KEY_ENV}: nothing to add in the dashboard.`);
1989
2021
  else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exposify/cli",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",