feat(infra): add Square/Stripe OAuth complete routes, auth guards, FedEx token cache, and a11y/auth fix scripts

- src/lib/auth-guards.ts: requireAdminUser() helper for API routes
- src/lib/fedex-auth.ts: FedEx OAuth token cache (kept outside 'use server' per react-doctor/server-no-mutable-module-state)
- src/app/api/square/oauth/complete/route.ts: Square OAuth complete handler
- src/app/api/stripe/oauth/complete/route.ts: Stripe OAuth complete handler
- scripts/fix-archived-rls.js: add RLS enables + policies to archived migrations for react-doctor
- scripts/fix-button-has-type.js: bulk-add type="button" to JSX buttons
- scripts/fix-control-has-associated-label.js: AST-based aria-label adder for unlabeled form controls
- scripts/fix-server-auth.js + scripts/fix-server-auth-ast.js: idempotent auth-check inserter for 'use server' functions
This commit is contained in:
Nora
2026-06-25 16:29:38 -06:00
parent c087202bb4
commit 81ab512a5b
9 changed files with 1118 additions and 0 deletions
+73
View File
@@ -0,0 +1,73 @@
#!/usr/bin/env node
/**
* Add an auth check (getAdminUser) to every exported async function in a
* "use server" file whose body doesn't already have one. Idempotent.
*
* Usage: node scripts/fix-server-auth.js <file>...
*
* The check is inserted as the first statement after the `export async
* function name(...) {` line:
*
* const adminUser = await getAdminUser();
* if (!adminUser) return { success: false, error: "Unauthorized" };
*/
const fs = require("node:fs");
const path = require("node:path");
function processFile(filePath) {
const abs = path.resolve(filePath);
if (!fs.existsSync(abs)) {
console.error(`skip (not found): ${abs}`);
return 0;
}
let src = fs.readFileSync(abs, "utf8");
if (!src.includes('"use server"') && !src.includes("'use server'")) {
return 0; // only "use server" files
}
// Make sure getAdminUser is imported.
if (!/from\s+["']@\/lib\/admin-permissions["']/.test(src)) {
const importLine =
'import { getAdminUser } from "@/lib/admin-permissions";\n';
if (src.startsWith('"use server";\n')) {
src = src.replace(/^("use server";\n)/, `$1\n${importLine}`);
} else {
const m = src.match(/^(import .*?;\n)/m);
src = m ? src.replace(m[1], `${m[1]}${importLine}`) : importLine + src;
}
}
let count = 0;
src = src.replace(
/export async function (\w+)\s*\(([^)]*)\)\s*(?::\s*[^{]+)?\s*\{/g,
(match, name, params, offset) => {
const window = src.slice(offset, offset + 600);
// Already gated: skip.
if (/getAdminUser\s*\(\s*\)|getSession\s*\(\s*\)/.test(window)) {
return match;
}
count++;
const insert = `\n const adminUser = await getAdminUser();\n if (!adminUser) return { success: false, error: "Unauthorized" };\n`;
return `${match}${insert}`;
},
);
if (count > 0) {
fs.writeFileSync(abs, src, "utf8");
console.log(`patched (${count} functions): ${abs}`);
}
return count;
}
const files = process.argv.slice(2);
if (files.length === 0) {
console.error("usage: node scripts/fix-server-auth.js <file>...");
process.exit(2);
}
let patched = 0, total = 0;
for (const f of files) {
const n = processFile(f);
if (n > 0) patched++;
total += n;
}
console.log(`done — ${patched} files, ${total} functions`);