fix
This commit is contained in:
@@ -11,7 +11,9 @@
|
||||
// -password 'choose-a-strong-password' \
|
||||
// -confirm
|
||||
//
|
||||
// Env aliases: PLATFORM_ADMIN_EMAIL, PLATFORM_ADMIN_PASSWORD, DATABASE_URL.
|
||||
// Env aliases: none for credentials — pass -email / -password (or use
|
||||
// `npm run seed:platform-admin -- --email … --password …`). DATABASE_URL may
|
||||
// come from the shell or monorepo-root .env via config.LoadDotEnv.
|
||||
//
|
||||
// -promote-only grants admin on an existing user without changing the password
|
||||
// (-password is ignored). Always requires -confirm.
|
||||
@@ -28,15 +30,18 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/descrybe/descrybe-v2/apps/api/internal/auth"
|
||||
"github.com/descrybe/descrybe-v2/apps/api/internal/config"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config.LoadDotEnv()
|
||||
|
||||
postgresURL := flag.String("postgres", os.Getenv("DATABASE_URL"), "Postgres URL")
|
||||
email := flag.String("email", os.Getenv("PLATFORM_ADMIN_EMAIL"), "Platform admin email")
|
||||
password := flag.String("password", os.Getenv("PLATFORM_ADMIN_PASSWORD"), "Password (min 8; ignored with -promote-only)")
|
||||
email := flag.String("email", "", "Platform admin email (required; do not put in .env)")
|
||||
password := flag.String("password", "", "Password (min 8; ignored with -promote-only; do not put in .env)")
|
||||
name := flag.String("name", "", "Display name (defaults to email local-part)")
|
||||
promoteOnly := flag.Bool("promote-only", false, "Grant platform admin on an existing user; do not set password")
|
||||
confirm := flag.Bool("confirm", false, "Required: acknowledge this writes is_platform_admin + staff_role=admin")
|
||||
@@ -93,11 +98,11 @@ func parseOptions(postgresURL, email, password, name string, promoteOnly, confir
|
||||
}
|
||||
pg := strings.TrimSpace(postgresURL)
|
||||
if pg == "" {
|
||||
return options{}, fmt.Errorf("-postgres / DATABASE_URL is required")
|
||||
return options{}, fmt.Errorf("-postgres / DATABASE_URL is required (export it, pass -postgres, or put DATABASE_URL in monorepo-root .env — the API loads .env, your shell may not)")
|
||||
}
|
||||
emailNorm := strings.ToLower(strings.TrimSpace(email))
|
||||
if emailNorm == "" || !strings.Contains(emailNorm, "@") {
|
||||
return options{}, fmt.Errorf("-email / PLATFORM_ADMIN_EMAIL is required")
|
||||
return options{}, fmt.Errorf("-email is required (pass on the CLI; do not store admin credentials in .env)")
|
||||
}
|
||||
if err := rejectLocalDemoAccount(emailNorm, appEnv); err != nil {
|
||||
return options{}, err
|
||||
@@ -122,7 +127,7 @@ func parseOptions(postgresURL, email, password, name string, promoteOnly, confir
|
||||
}
|
||||
pass := password // keep as provided (do not trim interior spaces)
|
||||
if len(pass) < 8 {
|
||||
return options{}, fmt.Errorf("-password / PLATFORM_ADMIN_PASSWORD must be at least 8 characters")
|
||||
return options{}, fmt.Errorf("-password must be at least 8 characters (pass on the CLI; do not store in .env)")
|
||||
}
|
||||
if err := rejectDemoPassword(pass, appEnv); err != nil {
|
||||
return options{}, err
|
||||
|
||||
@@ -5,6 +5,14 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseOptionsRequiresPostgres(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := parseOptions("", "a@b.com", "password1", "", false, true, "development")
|
||||
if err == nil || !strings.Contains(err.Error(), "DATABASE_URL") {
|
||||
t.Fatalf("err = %v, want DATABASE_URL required", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseOptionsRequiresConfirm(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := parseOptions("postgres://x", "a@b.com", "password1", "", false, false, "development")
|
||||
|
||||
@@ -7,6 +7,13 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// LoadDotEnv loads the monorepo-root .env into the process environment.
|
||||
// Existing variables are never overridden. Safe for CLI tools that only need
|
||||
// DATABASE_URL without a full config.Load (production often has no .env file).
|
||||
func LoadDotEnv() {
|
||||
loadDotEnv()
|
||||
}
|
||||
|
||||
// loadDotEnv loads the monorepo-root .env into the process environment.
|
||||
// Existing variables (including empty ones set by tests) are never overridden.
|
||||
// Missing file is a no-op — production typically injects env without a file.
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"db:wait": "node scripts/wait-postgres.mjs",
|
||||
"migrate": "node scripts/migrate.mjs",
|
||||
"seed": "node scripts/seed-local.mjs",
|
||||
"seed:platform-admin": "node scripts/seed-platform-admin.mjs",
|
||||
"health": "node scripts/health.mjs",
|
||||
"cutover:deploy-check": "node scripts/cutover-deploy-check.mjs",
|
||||
"cutover:deploy-check:code": "node scripts/cutover-deploy-check.mjs --skip-goose --skip-readyz",
|
||||
|
||||
+10
-5
@@ -34,14 +34,19 @@ function run(args) {
|
||||
if (r.status !== 0) process.exit(r.status ?? 1);
|
||||
}
|
||||
|
||||
console.log(`==> set-password ${adminEmail} (platform admin if present)`);
|
||||
console.log(`==> seed-platform-admin ${adminEmail}`);
|
||||
run([
|
||||
"run",
|
||||
"./cmd/migrator",
|
||||
"./cmd/seed-platform-admin",
|
||||
"-postgres",
|
||||
env.DATABASE_URL,
|
||||
"-set-password",
|
||||
`${adminEmail}:${adminPass}`,
|
||||
"-email",
|
||||
adminEmail,
|
||||
"-password",
|
||||
adminPass,
|
||||
"-name",
|
||||
"Local Admin",
|
||||
"-confirm",
|
||||
]);
|
||||
|
||||
console.log(
|
||||
@@ -63,7 +68,7 @@ run([
|
||||
console.log("");
|
||||
console.log("Local accounts:");
|
||||
console.log(` demo: ${demoEmail} / ${demoPass} (also demo@descrybe.test)`);
|
||||
console.log(` admin: ${adminEmail} / ${adminPass} (platform admin when that user exists)`);
|
||||
console.log(` admin: ${adminEmail} / ${adminPass} (platform admin)`);
|
||||
console.log(" plan: Enterprise — Local Demo Co");
|
||||
console.log(`Web: http://localhost:${DEV_WEB_PORT}/login`);
|
||||
console.log(`API: http://localhost:${DEV_API_PORT}`);
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Upsert a platform admin (is_platform_admin + staff_role=admin).
|
||||
* Loads monorepo-root `.env` only for DATABASE_URL (same as the API).
|
||||
* Email/password are CLI flags only — never read from .env.
|
||||
*
|
||||
* Usage (from repo root):
|
||||
* npm run seed:platform-admin -- --email you@example.com --password '…'
|
||||
* npm run seed:platform-admin -- --email you@example.com --promote-only
|
||||
*
|
||||
* Optional: --name "Display Name"
|
||||
*/
|
||||
import { spawnSync } from "node:child_process";
|
||||
import path from "node:path";
|
||||
import { detectHostProfile, withGoLowMem } from "./lowmem-env.mjs";
|
||||
import { loadRootEnv, repoRoot } from "./root-env.mjs";
|
||||
|
||||
const env = withGoLowMem(loadRootEnv(process.env), detectHostProfile());
|
||||
|
||||
function usage(msg) {
|
||||
if (msg) console.error(msg);
|
||||
console.error(`Usage:
|
||||
npm run seed:platform-admin -- --email you@example.com --password 'strong-password'
|
||||
npm run seed:platform-admin -- --email you@example.com --promote-only
|
||||
npm run seed:platform-admin -- --email you@example.com --password '…' --name 'Ops'
|
||||
|
||||
Email/password must be passed on the CLI (not stored in .env).
|
||||
DATABASE_URL comes from the shell or monorepo-root .env.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
/** Parse `--key value` / `--flag` from argv after npm's `--`. */
|
||||
function parseArgs(argv) {
|
||||
const out = { email: "", password: "", name: "", promoteOnly: false };
|
||||
for (let i = 0; i < argv.length; i++) {
|
||||
const a = argv[i];
|
||||
if (a === "--promote-only") {
|
||||
out.promoteOnly = true;
|
||||
continue;
|
||||
}
|
||||
if (a === "--email" || a === "-email") {
|
||||
out.email = String(argv[++i] || "").trim();
|
||||
continue;
|
||||
}
|
||||
if (a === "--password" || a === "-password") {
|
||||
out.password = String(argv[++i] || "");
|
||||
continue;
|
||||
}
|
||||
if (a === "--name" || a === "-name") {
|
||||
out.name = String(argv[++i] || "").trim();
|
||||
continue;
|
||||
}
|
||||
if (a === "--help" || a === "-h") usage();
|
||||
usage(`Unknown argument: ${a}`);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
const opts = parseArgs(process.argv.slice(2));
|
||||
|
||||
if (!env.DATABASE_URL) {
|
||||
usage("DATABASE_URL is required (shell or monorepo-root .env)");
|
||||
}
|
||||
if (!opts.email || !opts.email.includes("@")) {
|
||||
usage("--email is required");
|
||||
}
|
||||
if (!opts.promoteOnly && opts.password.length < 8) {
|
||||
usage("--password is required (min 8 characters), or pass --promote-only");
|
||||
}
|
||||
|
||||
const apiDir = path.join(repoRoot, "apps", "api");
|
||||
const args = [
|
||||
"run",
|
||||
"./cmd/seed-platform-admin",
|
||||
"-postgres",
|
||||
env.DATABASE_URL,
|
||||
"-email",
|
||||
opts.email,
|
||||
"-confirm",
|
||||
];
|
||||
if (opts.name) {
|
||||
args.push("-name", opts.name);
|
||||
}
|
||||
if (opts.promoteOnly) {
|
||||
args.push("-promote-only");
|
||||
} else {
|
||||
args.push("-password", opts.password);
|
||||
}
|
||||
|
||||
console.log(`==> seed-platform-admin ${opts.email}`);
|
||||
const r = spawnSync("go", args, {
|
||||
cwd: apiDir,
|
||||
env,
|
||||
stdio: "inherit",
|
||||
shell: false,
|
||||
});
|
||||
process.exit(r.status ?? 1);
|
||||
Reference in New Issue
Block a user