@rebasepro/cli 0.13.1-canary.gc77922a → 0.13.1-canary.gcef69d4
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/dist/commands/auth.d.ts +31 -0
- package/dist/index.es.js +104 -47
- package/dist/index.es.js.map +1 -1
- package/dist/telemetry/payload.d.ts +1 -1
- package/dist/utils/project.d.ts +20 -0
- package/package.json +7 -7
package/dist/commands/auth.d.ts
CHANGED
|
@@ -1 +1,32 @@
|
|
|
1
|
+
/** A user as the admin API returns it, reduced to what this command needs. */
|
|
2
|
+
export interface ResolvedUser {
|
|
3
|
+
id: string;
|
|
4
|
+
email: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Pick the user with exactly this email out of a search response.
|
|
8
|
+
*
|
|
9
|
+
* `/api/admin/users?search=` is an `ILIKE '%…%'` over email **or display
|
|
10
|
+
* name**, ordered by role count descending. This used to take row `[0]` and
|
|
11
|
+
* reset it, then print the email it had been *given* as confirmation — so two
|
|
12
|
+
* ordinary situations ended in a successful-looking reset of somebody else's
|
|
13
|
+
* account:
|
|
14
|
+
*
|
|
15
|
+
* - a substring collision: `bob@example.com` also matches
|
|
16
|
+
* `robert.bob@example.com`;
|
|
17
|
+
* - a display name, which is user-controlled and accepted up to 255
|
|
18
|
+
* characters with no constraint on its content, containing an address
|
|
19
|
+
* belonging to someone else.
|
|
20
|
+
*
|
|
21
|
+
* The ordering makes it worse rather than better — `array_length(roles) DESC
|
|
22
|
+
* NULLS LAST` puts the most privileged match first, so the account most likely
|
|
23
|
+
* to be reset by mistake is an admin's.
|
|
24
|
+
*
|
|
25
|
+
* Returns `undefined` when nothing matched exactly, which the caller reports
|
|
26
|
+
* rather than falling through to a guess. The direct-database fallback below
|
|
27
|
+
* has always matched with `eq(usersTable.email, email)`; this is the same
|
|
28
|
+
* definition, so the command no longer resets different accounts depending on
|
|
29
|
+
* whether the backend happened to be running.
|
|
30
|
+
*/
|
|
31
|
+
export declare function selectUserForEmail(payload: unknown, email: string): ResolvedUser | undefined;
|
|
1
32
|
export declare function authCommand(subcommand: string | undefined, rawArgs: string[]): Promise<void>;
|
package/dist/index.es.js
CHANGED
|
@@ -12,6 +12,7 @@ import crypto from "crypto";
|
|
|
12
12
|
import { execSync, spawn, spawnSync } from "child_process";
|
|
13
13
|
import os from "os";
|
|
14
14
|
import { createRebaseClient } from "@rebasepro/client";
|
|
15
|
+
import dotenv from "dotenv";
|
|
15
16
|
import { createRequire } from "module";
|
|
16
17
|
import { BUNDLE_FORMAT_VERSION, RUNTIME_CONTRACT_VERSION, computeSchemaVersion, deserializeCollections, findStorageSuffixCollision, normalizeStorageSources, storageEnvSuffix } from "@rebasepro/types";
|
|
17
18
|
import { generateSDK } from "@rebasepro/codegen";
|
|
@@ -289,6 +290,34 @@ function findEnvFile(projectRoot) {
|
|
|
289
290
|
return null;
|
|
290
291
|
}
|
|
291
292
|
/**
|
|
293
|
+
* Read the project's `.env` into a plain object.
|
|
294
|
+
*
|
|
295
|
+
* One reader, because there were four: `dotenv` in `start`, a hand-rolled
|
|
296
|
+
* `indexOf("=")` loop in `api-keys`, a single-key regex in `auth`, and its own
|
|
297
|
+
* splitting in `cloud env`. `dotenv` is a declared dependency of this package,
|
|
298
|
+
* so the other three existed for no reason and disagreed with the correct one
|
|
299
|
+
* on the two things people actually write in a `.env`:
|
|
300
|
+
*
|
|
301
|
+
* - `export KEY=value`, which the hand-rolled parser keyed as
|
|
302
|
+
* `export KEY` — so the command reported the key as unset while it was
|
|
303
|
+
* right there in the file;
|
|
304
|
+
* - `KEY=value # comment`, whose comment travelled into the value and then
|
|
305
|
+
* into an `Authorization` header, coming back as a 401 with nothing
|
|
306
|
+
* pointing at the cause.
|
|
307
|
+
*
|
|
308
|
+
* Returns `{}` when the project has no `.env`, so callers can treat "absent"
|
|
309
|
+
* and "empty" alike.
|
|
310
|
+
*/
|
|
311
|
+
function readEnvFile(projectRoot) {
|
|
312
|
+
const envFile = findEnvFile(projectRoot);
|
|
313
|
+
if (!envFile || !fs.existsSync(envFile)) return {};
|
|
314
|
+
try {
|
|
315
|
+
return dotenv.parse(fs.readFileSync(envFile, "utf-8"));
|
|
316
|
+
} catch {
|
|
317
|
+
return {};
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
292
321
|
* Resolve a binary from the project's node_modules/.bin.
|
|
293
322
|
* Checks backend, root, parent monorepo root, then falls back to PATH.
|
|
294
323
|
*/
|
|
@@ -2394,7 +2423,7 @@ async function schemaCommand(subcommand, rawArgs) {
|
|
|
2394
2423
|
return;
|
|
2395
2424
|
}
|
|
2396
2425
|
const projectRoot = requireProjectRoot();
|
|
2397
|
-
recordEvent("cli.
|
|
2426
|
+
recordEvent("cli.schema", { subcommand: subcommand ?? "none" }, { projectRoot });
|
|
2398
2427
|
const backendDir = requireBackendDir(projectRoot);
|
|
2399
2428
|
const activePlugin = getActiveBackendPlugin(backendDir);
|
|
2400
2429
|
if (!activePlugin) {
|
|
@@ -2463,7 +2492,7 @@ async function dbCommand(subcommand, rawArgs) {
|
|
|
2463
2492
|
return;
|
|
2464
2493
|
}
|
|
2465
2494
|
const projectRoot = requireProjectRoot();
|
|
2466
|
-
recordEvent("cli.
|
|
2495
|
+
recordEvent("cli.db", { subcommand: subcommand ?? "none" }, { projectRoot });
|
|
2467
2496
|
const backendDir = requireBackendDir(projectRoot);
|
|
2468
2497
|
const activePlugin = getActiveBackendPlugin(backendDir);
|
|
2469
2498
|
if (!activePlugin) {
|
|
@@ -5327,6 +5356,48 @@ async function startWorkspaceBackend(projectRoot, env) {
|
|
|
5327
5356
|
* Subcommands:
|
|
5328
5357
|
* reset-password — Reset a user's password
|
|
5329
5358
|
*/
|
|
5359
|
+
/**
|
|
5360
|
+
* Pick the user with exactly this email out of a search response.
|
|
5361
|
+
*
|
|
5362
|
+
* `/api/admin/users?search=` is an `ILIKE '%…%'` over email **or display
|
|
5363
|
+
* name**, ordered by role count descending. This used to take row `[0]` and
|
|
5364
|
+
* reset it, then print the email it had been *given* as confirmation — so two
|
|
5365
|
+
* ordinary situations ended in a successful-looking reset of somebody else's
|
|
5366
|
+
* account:
|
|
5367
|
+
*
|
|
5368
|
+
* - a substring collision: `bob@example.com` also matches
|
|
5369
|
+
* `robert.bob@example.com`;
|
|
5370
|
+
* - a display name, which is user-controlled and accepted up to 255
|
|
5371
|
+
* characters with no constraint on its content, containing an address
|
|
5372
|
+
* belonging to someone else.
|
|
5373
|
+
*
|
|
5374
|
+
* The ordering makes it worse rather than better — `array_length(roles) DESC
|
|
5375
|
+
* NULLS LAST` puts the most privileged match first, so the account most likely
|
|
5376
|
+
* to be reset by mistake is an admin's.
|
|
5377
|
+
*
|
|
5378
|
+
* Returns `undefined` when nothing matched exactly, which the caller reports
|
|
5379
|
+
* rather than falling through to a guess. The direct-database fallback below
|
|
5380
|
+
* has always matched with `eq(usersTable.email, email)`; this is the same
|
|
5381
|
+
* definition, so the command no longer resets different accounts depending on
|
|
5382
|
+
* whether the backend happened to be running.
|
|
5383
|
+
*/
|
|
5384
|
+
function selectUserForEmail(payload, email) {
|
|
5385
|
+
const wanted = email.trim().toLowerCase();
|
|
5386
|
+
if (!wanted) return void 0;
|
|
5387
|
+
const rows = Array.isArray(payload) ? payload : payload && typeof payload === "object" && Array.isArray(payload.users) ? payload.users : [];
|
|
5388
|
+
for (const row of rows) {
|
|
5389
|
+
if (!row || typeof row !== "object") continue;
|
|
5390
|
+
const record = row;
|
|
5391
|
+
const rowEmail = typeof record.email === "string" ? record.email.trim().toLowerCase() : void 0;
|
|
5392
|
+
if (!rowEmail || rowEmail !== wanted) continue;
|
|
5393
|
+
const id = typeof record.id === "string" ? record.id : typeof record.uid === "string" ? record.uid : void 0;
|
|
5394
|
+
if (!id) continue;
|
|
5395
|
+
return {
|
|
5396
|
+
id,
|
|
5397
|
+
email: record.email
|
|
5398
|
+
};
|
|
5399
|
+
}
|
|
5400
|
+
}
|
|
5330
5401
|
async function authCommand(subcommand, rawArgs) {
|
|
5331
5402
|
if (!subcommand || subcommand === "--help") {
|
|
5332
5403
|
printAuthHelp();
|
|
@@ -5363,12 +5434,8 @@ async function resetPassword(rawArgs) {
|
|
|
5363
5434
|
process.exit(1);
|
|
5364
5435
|
}
|
|
5365
5436
|
const projectRoot = requireProjectRoot();
|
|
5366
|
-
let envServiceKey;
|
|
5367
5437
|
const envFile = findEnvFile(projectRoot);
|
|
5368
|
-
|
|
5369
|
-
const match = fs.readFileSync(envFile, "utf8").match(/^\s*REBASE_SERVICE_KEY\s*=\s*['"]?(.*?)['"]?\s*$/m);
|
|
5370
|
-
if (match && match[1]) envServiceKey = match[1];
|
|
5371
|
-
} catch {}
|
|
5438
|
+
const envServiceKey = readEnvFile(projectRoot).REBASE_SERVICE_KEY;
|
|
5372
5439
|
let baseUrl = process.env.REBASE_BASE_URL;
|
|
5373
5440
|
let serviceKey = process.env.REBASE_SERVICE_KEY || envServiceKey;
|
|
5374
5441
|
const statePath = path.join(projectRoot, ".rebase", "state.json");
|
|
@@ -5388,7 +5455,7 @@ async function resetPassword(rawArgs) {
|
|
|
5388
5455
|
try {
|
|
5389
5456
|
const finalPass = newPassword || "NewPassword123!";
|
|
5390
5457
|
const cleanBaseUrl = baseUrl.replace(/\/+$/, "");
|
|
5391
|
-
const searchUrl = `${cleanBaseUrl}/api/admin/users?search=${encodeURIComponent(email)}&limit=
|
|
5458
|
+
const searchUrl = `${cleanBaseUrl}/api/admin/users?search=${encodeURIComponent(email)}&limit=50`;
|
|
5392
5459
|
const searchRes = await fetch(searchUrl, { headers: {
|
|
5393
5460
|
"Authorization": `Bearer ${serviceKey}`,
|
|
5394
5461
|
"Accept": "application/json"
|
|
@@ -5396,18 +5463,9 @@ async function resetPassword(rawArgs) {
|
|
|
5396
5463
|
if (!searchRes.ok) throw new Error(`Failed to list users: ${searchRes.statusText}`);
|
|
5397
5464
|
const searchData = await searchRes.json();
|
|
5398
5465
|
if (!searchData || typeof searchData !== "object") throw new Error("Invalid response format from user search API.");
|
|
5399
|
-
|
|
5400
|
-
if (
|
|
5401
|
-
|
|
5402
|
-
if (firstUser && typeof firstUser === "object" && "id" in firstUser && typeof firstUser.id === "string") userId = firstUser.id;
|
|
5403
|
-
else if (firstUser && typeof firstUser === "object" && "uid" in firstUser && typeof firstUser.uid === "string") userId = firstUser.uid;
|
|
5404
|
-
} else if ("users" in searchData && Array.isArray(searchData.users)) {
|
|
5405
|
-
const firstUser = searchData.users[0];
|
|
5406
|
-
if (firstUser && typeof firstUser === "object" && "id" in firstUser && typeof firstUser.id === "string") userId = firstUser.id;
|
|
5407
|
-
else if (firstUser && typeof firstUser === "object" && "uid" in firstUser && typeof firstUser.uid === "string") userId = firstUser.uid;
|
|
5408
|
-
}
|
|
5409
|
-
if (!userId) throw new Error(`User not found with email: ${email}`);
|
|
5410
|
-
const resetUrl = `${cleanBaseUrl}/api/admin/users/${userId}/reset-password`;
|
|
5466
|
+
const matched = selectUserForEmail(searchData, email);
|
|
5467
|
+
if (!matched) throw new Error(`No user has the email ${email}.`);
|
|
5468
|
+
const resetUrl = `${cleanBaseUrl}/api/admin/users/${matched.id}/reset-password`;
|
|
5411
5469
|
const resetRes = await fetch(resetUrl, {
|
|
5412
5470
|
method: "POST",
|
|
5413
5471
|
headers: {
|
|
@@ -5424,7 +5482,7 @@ async function resetPassword(rawArgs) {
|
|
|
5424
5482
|
console.log("API reset successful.");
|
|
5425
5483
|
console.log(chalk.bold(" 🔑 Rebase Auth — Reset Password (via API)"));
|
|
5426
5484
|
console.log("");
|
|
5427
|
-
console.log(` ${chalk.gray("Email:")} ${email}`);
|
|
5485
|
+
console.log(` ${chalk.gray("Email:")} ${matched.email}`);
|
|
5428
5486
|
console.log(` ${chalk.gray("Password:")} ${finalPass}`);
|
|
5429
5487
|
console.log("");
|
|
5430
5488
|
return;
|
|
@@ -5492,10 +5550,12 @@ async function resetPassword() {
|
|
|
5492
5550
|
if (result.length > 0) {
|
|
5493
5551
|
console.log("✅ Password reset for: " + result[0].email);
|
|
5494
5552
|
${!newPassword ? "console.log(\" New password: \" + newPassword);" : ""}
|
|
5495
|
-
|
|
5496
|
-
console.log("✗ User not found: " + email);
|
|
5553
|
+
process.exit(0);
|
|
5497
5554
|
}
|
|
5498
|
-
|
|
5555
|
+
// Nothing was updated, so nothing was reset. Exiting 0 here reported
|
|
5556
|
+
// success for a no-op, which is what a script would have believed.
|
|
5557
|
+
console.error("✗ User not found: " + email);
|
|
5558
|
+
process.exit(1);
|
|
5499
5559
|
}
|
|
5500
5560
|
|
|
5501
5561
|
resetPassword().catch(console.error);
|
|
@@ -5513,11 +5573,20 @@ resetPassword().catch(console.error);
|
|
|
5513
5573
|
stdio: "inherit",
|
|
5514
5574
|
env
|
|
5515
5575
|
});
|
|
5576
|
+
const cleanup = () => {
|
|
5577
|
+
try {
|
|
5578
|
+
fs.unlinkSync(tmpScriptPath);
|
|
5579
|
+
} catch {}
|
|
5580
|
+
};
|
|
5516
5581
|
return new Promise((resolve) => {
|
|
5582
|
+
child.on("error", (err) => {
|
|
5583
|
+
cleanup();
|
|
5584
|
+
console.error(chalk.red("✗ Could not run the reset script."));
|
|
5585
|
+
console.error(chalk.gray(` ${err.message}`));
|
|
5586
|
+
process.exit(1);
|
|
5587
|
+
});
|
|
5517
5588
|
child.on("close", (code) => {
|
|
5518
|
-
|
|
5519
|
-
fs.unlinkSync(tmpScriptPath);
|
|
5520
|
-
} catch {}
|
|
5589
|
+
cleanup();
|
|
5521
5590
|
if (code !== 0) process.exit(code ?? 1);
|
|
5522
5591
|
resolve();
|
|
5523
5592
|
});
|
|
@@ -5813,25 +5882,13 @@ ${chalk.green.bold("Examples")}
|
|
|
5813
5882
|
* create — Create a new API key
|
|
5814
5883
|
* revoke — Revoke an existing API key
|
|
5815
5884
|
*/
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
5824
|
-
const idx = trimmed.indexOf("=");
|
|
5825
|
-
if (idx > 0) {
|
|
5826
|
-
const key = trimmed.slice(0, idx).trim();
|
|
5827
|
-
let value = trimmed.slice(idx + 1).trim();
|
|
5828
|
-
if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
|
|
5829
|
-
env[key] = value;
|
|
5830
|
-
}
|
|
5831
|
-
}
|
|
5832
|
-
}
|
|
5833
|
-
return env;
|
|
5834
|
-
}
|
|
5885
|
+
/**
|
|
5886
|
+
* Was a hand-rolled `indexOf("=")` loop. It keyed `export KEY=value` as
|
|
5887
|
+
* `export KEY` and carried a trailing `# comment` into the value — so a key
|
|
5888
|
+
* that was present read as absent, or reached an `Authorization` header with a
|
|
5889
|
+
* comment attached and came back 401. See `readEnvFile`.
|
|
5890
|
+
*/
|
|
5891
|
+
var loadEnv = readEnvFile;
|
|
5835
5892
|
function resolveBaseUrl(env, projectRoot) {
|
|
5836
5893
|
if (env.REBASE_BASE_URL) return env.REBASE_BASE_URL;
|
|
5837
5894
|
if (projectRoot) try {
|
|
@@ -10940,6 +10997,6 @@ function telemetryNotice() {
|
|
|
10940
10997
|
return chalk.gray(`Usage sharing: ${sharing ? "on" : "off"} — ${chalk.cyan("rebase telemetry")} to inspect or change\n`);
|
|
10941
10998
|
}
|
|
10942
10999
|
//#endregion
|
|
10943
|
-
export { CURRENT_RUNTIME_RANGE, DEFAULT_BUNDLE_DIR, DEFAULT_CONFIG_DIR, DEFAULT_CRONS_DIR, DEFAULT_FUNCTIONS_DIR, DEFAULT_SCHEMA_FILE, DEV_PORT_FILENAME, MANIFEST_FILENAME, ManifestError, TEMPLATE_PLACEHOLDER_FILES, appsCommand, assessManagedCompatibility, authCommand, buildBundle, buildCommand, buildInitQuestions, buildStaticBundle, buildableApps, cloudCommand, collectDeclaredDependencies, configureEnvFile, createRebaseApp, dbCommand, detectFrameworkDepDrift, detectNativeDependencies, detectPackageManager, detectStorageAuthorize, devCommand, doctorCommand, ejectCommand, entry, findBackendApp, findBackendDir, findEnvFile, findFrontendDir, findProjectRoot, findUnusedServerEntry, foldStaticIntoBundle, formatCdTarget, generateSdkCommand, getActiveBackendPlugin, getPMCommands, getProjectPort, isIdentifierLike, isPnpmAvailable, loadManifest, manifestExists, manifestPath, normalizeEsmSpecifiers, pnpmAvailabilityFromProbe, positionals, printInitHelp, requireBackendDir, requireProjectRoot, resetPnpmAvailabilityCache, resolveBackendPaths, resolveCliVersion, resolveExampleBaseUrl, resolveLocalBin, resolvePluginCliScript, resolveRuntimeImageTag, resolveStartPort, resolveTsx, schemaCommand, startCommand, synthesizeManifest, validateManifest, validateProjectName, validateTsxInstallation, writeManifest };
|
|
11000
|
+
export { CURRENT_RUNTIME_RANGE, DEFAULT_BUNDLE_DIR, DEFAULT_CONFIG_DIR, DEFAULT_CRONS_DIR, DEFAULT_FUNCTIONS_DIR, DEFAULT_SCHEMA_FILE, DEV_PORT_FILENAME, MANIFEST_FILENAME, ManifestError, TEMPLATE_PLACEHOLDER_FILES, appsCommand, assessManagedCompatibility, authCommand, buildBundle, buildCommand, buildInitQuestions, buildStaticBundle, buildableApps, cloudCommand, collectDeclaredDependencies, configureEnvFile, createRebaseApp, dbCommand, detectFrameworkDepDrift, detectNativeDependencies, detectPackageManager, detectStorageAuthorize, devCommand, doctorCommand, ejectCommand, entry, findBackendApp, findBackendDir, findEnvFile, findFrontendDir, findProjectRoot, findUnusedServerEntry, foldStaticIntoBundle, formatCdTarget, generateSdkCommand, getActiveBackendPlugin, getPMCommands, getProjectPort, isIdentifierLike, isPnpmAvailable, loadManifest, manifestExists, manifestPath, normalizeEsmSpecifiers, pnpmAvailabilityFromProbe, positionals, printInitHelp, readEnvFile, requireBackendDir, requireProjectRoot, resetPnpmAvailabilityCache, resolveBackendPaths, resolveCliVersion, resolveExampleBaseUrl, resolveLocalBin, resolvePluginCliScript, resolveRuntimeImageTag, resolveStartPort, resolveTsx, schemaCommand, selectUserForEmail, startCommand, synthesizeManifest, validateManifest, validateProjectName, validateTsxInstallation, writeManifest };
|
|
10944
11001
|
|
|
10945
11002
|
//# sourceMappingURL=index.es.js.map
|