flagon-io/g1t

public

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t/services/identity/scripts/create-user.mjs

48 lines1,954 bytesCodeBlame
1// Creates a user (or resets their password) in the remote D1 database and
2// writes the generated password to the gitignored .credentials directory.
3//
4// node scripts/create-user.mjs <username> [email]
5import { execSync } from "node:child_process";
6import { pbkdf2Sync, randomBytes } from "node:crypto";
7import { mkdirSync, rmSync, writeFileSync } from "node:fs";
8import { tmpdir } from "node:os";
9import { join } from "node:path";
10
11const [username, email] = process.argv.slice(2);
12if (!/^[a-z0-9](?:[a-z0-9]|-(?=[a-z0-9])){0,38}$/.test(username ?? "")) {
13 console.error("usage: node scripts/create-user.mjs <username> [email]");
14 process.exit(1);
15}
16if (email && !/^[^\s'@]+@[^\s'@]+$/.test(email)) {
17 console.error("invalid email");
18 process.exit(1);
19}
20
21// Must match hashPassword in src/crypto.ts.
22const iterations = 100_000;
23const password = randomBytes(18).toString("base64url");
24const salt = randomBytes(16);
25const hash = pbkdf2Sync(password, salt, iterations, 32, "sha256");
26const stored = `pbkdf2$${iterations}$${salt.toString("base64")}$${hash.toString("base64")}`;
27
28const sqlFile = join(tmpdir(), `g1t-create-user-${process.pid}.sql`);
29writeFileSync(
30 sqlFile,
31 `INSERT INTO users (id, username, email, password_hash)
32 VALUES ('usr_' || lower(hex(randomblob(13))), '${username}', ${email ? `'${email}'` : "NULL"}, '${stored}')
33 ON CONFLICT (username) DO UPDATE SET password_hash = excluded.password_hash;`,
34);
35try {
36 execSync(`npx wrangler d1 execute g1t --remote --yes --file "${sqlFile}"`, {
37 cwd: join(import.meta.dirname, ".."),
38 stdio: ["ignore", "ignore", "inherit"],
39 });
40} finally {
41 rmSync(sqlFile, { force: true });
42}
43
44const directory = join(import.meta.dirname, "../../../.credentials");
45mkdirSync(directory, { recursive: true });
46const file = join(directory, `${username}.txt`);
47writeFileSync(file, `username: ${username}\npassword: ${password}\n`);
48console.log(`Password for ${username} written to ${file}`);