← back to CelebritySignatures
backend: add POST /api/auth/delete (Apple 5.1.1(v) account deletion)
77c8879525e6e60345fc2a542bff79ab654b6e31 · 2026-08-05 15:43:37 -0700 · Steve
Cookie-authed; removes the user from users.json, their sessions, saves, and
private uploads (+ unlinks the files). Required by App Store guidelines for the
new native iOS app. Reuses existing currentUser/load/store/sendJSON/clearCookie
helpers. node --check passes. Prod deploy stays gated.
Files touched
Diff
commit 77c8879525e6e60345fc2a542bff79ab654b6e31
Author: Steve <steve@designerwallcoverings.com>
Date: Wed Aug 5 15:43:37 2026 -0700
backend: add POST /api/auth/delete (Apple 5.1.1(v) account deletion)
Cookie-authed; removes the user from users.json, their sessions, saves, and
private uploads (+ unlinks the files). Required by App Store guidelines for the
new native iOS app. Reuses existing currentUser/load/store/sendJSON/clearCookie
helpers. node --check passes. Prod deploy stays gated.
---
server.js | 29 ++++++++++++++++++++++++++++-
1 file changed, 28 insertions(+), 1 deletion(-)
diff --git a/server.js b/server.js
index 65188cd..fa6b203 100644
--- a/server.js
+++ b/server.js
@@ -3,7 +3,7 @@
// Adds lightweight email/password accounts (scrypt + cookie sessions, JSON-backed)
// so a visitor can SAVE a mural placement they set in the wall studio.
import { createServer } from 'node:http';
-import { readFile, writeFile, appendFile, mkdir } from 'node:fs/promises';
+import { readFile, writeFile, appendFile, mkdir, unlink } from 'node:fs/promises';
import { extname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { scryptSync, randomBytes, timingSafeEqual, createHash } from 'node:crypto';
@@ -225,6 +225,33 @@ createServer(async (req, res) => {
const u = await currentUser(req);
return sendJSON(res, 200, u ? { ok: true, user: pubUser(u) } : { ok: true, user: null });
}
+ // Account deletion — Apple App Store Guideline 5.1.1(v). Cookie-authed via the
+ // same currentUser(req) mechanism as the rest of /api/auth/*. Removes the user
+ // and ALL their data (sessions, saved placements, uploaded signature files),
+ // then clears the session cookie exactly like /api/auth/logout.
+ if (path === '/api/auth/delete' && M === 'POST') {
+ const u = await currentUser(req);
+ if (!u) return sendJSON(res, 401, { ok: false, error: 'not signed in' });
+ const uid = u.id;
+ // a. drop the user object
+ const users = await load('users.json', []);
+ await store('users.json', users.filter(x => x.id !== uid));
+ // b. drop every session belonging to this user
+ const sessions = await load('sessions.json', {});
+ for (const [tok, s] of Object.entries(sessions)) { if (s && s.userId === uid) delete sessions[tok]; }
+ await store('sessions.json', sessions);
+ // c. drop this user's saved placements
+ const saves = await load('saves.json', []);
+ await store('saves.json', saves.filter(s => s.userId !== uid));
+ // d. delete this user's private upload files (best-effort), then drop the rows
+ const ups = await load('celebrity-uploads.json', []);
+ for (const x of ups.filter(e => e.userId === uid)) {
+ if (x.file) { try { await unlink(join(UPLOADS_DIR, x.file)); } catch {} }
+ }
+ await store('celebrity-uploads.json', ups.filter(e => e.userId !== uid));
+ // e. clear the session cookie (same header the logout route uses)
+ return sendJSON(res, 200, { ok: true }, { 'Set-Cookie': clearCookie });
+ }
// ===== GAME LEADERBOARD (public, no account needed) =====
// Top-20 per game+difficulty. HARDENED (Cody gate, yoloforever C2):
← 4bbb628 AdSense onboarding: add /privacy policy page + site-wide foo
·
back to CelebritySignatures
·
iOS app: native Expo/EAS scaffold over celebsignatures.com A 73d9137 →