Commit0dc3291unknown_key
chore(scripts): emergency PAT creator (bypasses live site)
chore(scripts): emergency PAT creator (bypasses live site) One-off helper for when the live site is broken (eg the SW reload loop currently blocking /settings/tokens) but you need a PAT to git-push the fix. Writes a PAT row directly into the production DB via DATABASE_URL — no HTTP, no browser, no auth dance. Run: bun run scripts/emergency-pat.ts Env: DATABASE_URL (from your local .env) Prints the raw token once — copy it, then `git push gluecron main` to ship whatever fix is currently blocked. Designed to be deleted or just ignored after the immediate crisis is over. https://claude.ai/code/session_01QFLWDxWw65DX6enMcS5Lwe
1 file changed+91−00dc3291cdc0b8edc773eb1ee4d668f0af9cadb8c
1 changed file+91−0
Addedscripts/emergency-pat.ts+91−0View fileUnifiedSplit
@@ -0,0 +1,91 @@
1// Emergency PAT creator — bypasses the live site entirely.
2//
3// Use case: the deployed site is broken (e.g. SW reload loop) such that
4// you can't reach /settings/tokens in the browser, but you need a PAT
5// to `git push` the fix. This script writes a PAT row directly into
6// the production database via DATABASE_URL.
7//
8// Run: bun run scripts/emergency-pat.ts
9// Required env: DATABASE_URL (from your local .env)
10//
11// The script:
12// 1. Loads .env
13// 2. Finds the first admin user (or a user matching $EMERGENCY_PAT_USER)
14// 3. Inserts a fresh PAT with `admin` scope
15// 4. Prints the raw token — you only see it once, so copy it now
16
17import { db } from "../src/db/index";
18import { users, apiTokens } from "../src/db/schema";
19import { eq } from "drizzle-orm";
20
21function generateToken(): string {
22 const bytes = crypto.getRandomValues(new Uint8Array(32));
23 return (
24 "glc_" +
25 Array.from(bytes)
26 .map((b) => b.toString(16).padStart(2, "0"))
27 .join("")
28 );
29}
30
31async function hashToken(token: string): Promise<string> {
32 const data = new TextEncoder().encode(token);
33 const hash = await crypto.subtle.digest("SHA-256", data);
34 return Array.from(new Uint8Array(hash))
35 .map((b) => b.toString(16).padStart(2, "0"))
36 .join("");
37}
38
39async function main() {
40 const requestedUser = process.env.EMERGENCY_PAT_USER?.trim();
41 let user: typeof users.$inferSelect | undefined;
42
43 if (requestedUser) {
44 [user] = await db
45 .select()
46 .from(users)
47 .where(eq(users.username, requestedUser))
48 .limit(1);
49 if (!user) {
50 console.error(`No user with username "${requestedUser}".`);
51 process.exit(1);
52 }
53 } else {
54 [user] = await db
55 .select()
56 .from(users)
57 .where(eq(users.isAdmin, true))
58 .limit(1);
59 if (!user) {
60 console.error(
61 "No admin user found. Set EMERGENCY_PAT_USER=<username> and rerun."
62 );
63 process.exit(1);
64 }
65 }
66
67 const token = generateToken();
68 const tokenHash = await hashToken(token);
69 await db.insert(apiTokens).values({
70 userId: user.id,
71 name: "emergency-deploy-fix",
72 tokenHash,
73 tokenPrefix: token.slice(0, 12),
74 scopes: "admin",
75 });
76
77 console.log(`User: ${user.username} (id ${user.id})`);
78 console.log(`Token: ${token}`);
79 console.log("");
80 console.log("Copy the token above NOW — it is hashed in the DB and");
81 console.log("cannot be recovered later. Then push with:");
82 console.log("");
83 console.log(` git remote set-url gluecron "https://${user.username}:${token}@gluecron.com/ccantynz/Gluecron.com.git"`);
84 console.log(" git push gluecron main");
85 process.exit(0);
86}
87
88main().catch((err) => {
89 console.error("Failed:", err);
90 process.exit(1);
91});
092