Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

emergency-pat.ts

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

emergency-pat.tsBlame112 lines · 1 contributor
0dc3291Test User1// 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
9d06598Test User9// Required env: DATABASE_URL (from your local .env, or set inline)
0dc3291Test User10//
9d06598Test User11// Uses RAW SQL via @neondatabase/serverless rather than drizzle's
12// schema layer. Reason: when this script is needed, production is
13// usually missing recent migrations, so drizzle's SELECT (which lists
14// every schema column) blows up on missing columns. Raw SQL only
15// touches the columns we name, so schema drift doesn't matter.
0dc3291Test User16
9d06598Test User17import { neon } from "@neondatabase/serverless";
0dc3291Test User18
19function generateToken(): string {
20 const bytes = crypto.getRandomValues(new Uint8Array(32));
21 return (
22 "glc_" +
23 Array.from(bytes)
24 .map((b) => b.toString(16).padStart(2, "0"))
25 .join("")
26 );
27}
28
29async function hashToken(token: string): Promise<string> {
30 const data = new TextEncoder().encode(token);
31 const hash = await crypto.subtle.digest("SHA-256", data);
32 return Array.from(new Uint8Array(hash))
33 .map((b) => b.toString(16).padStart(2, "0"))
34 .join("");
35}
36
37async function main() {
9d06598Test User38 const url = process.env.DATABASE_URL;
39 if (!url) {
40 console.error("DATABASE_URL is not set.");
41 process.exit(1);
42 }
43 const sql = neon(url);
44
0dc3291Test User45 const requestedUser = process.env.EMERGENCY_PAT_USER?.trim();
46
9d06598Test User47 let userRow: { id: string; username: string } | undefined;
0dc3291Test User48 if (requestedUser) {
c36e102Test User49 // Case-insensitive lookup — username is supposed to be unique-by-value
50 // but capitalization in some early signups varied.
9d06598Test User51 const rows = (await sql`
c36e102Test User52 SELECT id, username FROM users WHERE LOWER(username) = LOWER(${requestedUser}) LIMIT 1
9d06598Test User53 `) as Array<{ id: string; username: string }>;
54 userRow = rows[0];
0dc3291Test User55 } else {
9d06598Test User56 const rows = (await sql`
57 SELECT id, username FROM users WHERE is_admin = true ORDER BY created_at ASC LIMIT 1
58 `) as Array<{ id: string; username: string }>;
59 userRow = rows[0];
c36e102Test User60 }
61
62 if (!userRow) {
63 // Dump the user list so the operator can see what's there.
64 const all = (await sql`
65 SELECT username, email, is_admin, created_at
66 FROM users ORDER BY created_at ASC LIMIT 30
67 `) as Array<{
68 username: string; email: string; is_admin: boolean; created_at: Date;
69 }>;
70 console.error("");
71 console.error(
72 requestedUser
73 ? `No user matched "${requestedUser}". Users in this DB:`
74 : "No admin user. All users in this DB:"
75 );
76 console.error("");
77 for (const u of all) {
0dc3291Test User78 console.error(
c36e102Test User79 ` ${u.username.padEnd(24)} ${u.email.padEnd(40)} admin=${u.is_admin}`
0dc3291Test User80 );
81 }
c36e102Test User82 console.error("");
83 console.error(
84 "Re-run with EMERGENCY_PAT_USER=<exact-username-from-above>"
85 );
86 process.exit(1);
0dc3291Test User87 }
88
89 const token = generateToken();
90 const tokenHash = await hashToken(token);
9d06598Test User91 const tokenPrefix = token.slice(0, 12);
0dc3291Test User92
9d06598Test User93 await sql`
94 INSERT INTO api_tokens (user_id, name, token_hash, token_prefix, scopes)
95 VALUES (${userRow.id}, 'emergency-deploy-fix', ${tokenHash}, ${tokenPrefix}, 'admin')
96 `;
97
98 console.log("");
99 console.log(`User: ${userRow.username}`);
0dc3291Test User100 console.log(`Token: ${token}`);
101 console.log("");
9d06598Test User102 console.log("Copy the token NOW (only shown once). Then run these two:");
0dc3291Test User103 console.log("");
9d06598Test User104 console.log(` git remote set-url gluecron "https://${userRow.username}:${token}@gluecron.com/ccantynz/Gluecron.com.git"`);
0dc3291Test User105 console.log(" git push gluecron main");
106 process.exit(0);
107}
108
109main().catch((err) => {
110 console.error("Failed:", err);
111 process.exit(1);
112});