Diff -- /var/www/vhosts/3dshawn.com/crm.3dshawn.com/account_close.cgi

O Operator
Diff

/var/www/vhosts/3dshawn.com/crm.3dshawn.com/account_close.cgi

added on local at 2026-07-01 15:02:14

Added
+125
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to 08a60464f119
Restore this content →
Unified diff
Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.
1#!/usr/bin/perl
2#======================================================================
3# ContactForge -- account self-close.
4#
5# GET /account_close.cgi -> confirmation page
6# POST /account_close.cgi action=close -> set account_status=closed,
7# revoke sessions, log out,
8# redirect to / with a flash.
9#
10# Closing is reversible by support: account_status flips back to
11# 'active' on appeal. We do NOT hard-delete anything -- models, orders,
12# invoices, and contact download history stay intact so existing contacts
13# can still pull their files for 30 days (downloads.cgi gates on the
14# download_until column, not user.status).
15#======================================================================
16use strict;
17use warnings;
18
19use lib '/var/www/vhosts/3dshawn.com/crm.3dshawn.com';
20use CGI;
21use MODS::Template;
22use MODS::DBConnect;
23use MODS::Login;
24use MODS::ContactForge::Config;
25use MODS::ContactForge::Wrapper;
26use MODS::ContactForge::Permissions;
27
28$|=1;
29
30my $q = CGI->new;
31my $form = $q->Vars;
32my $auth = MODS::Login->new;
33my $tfile = MODS::Template->new;
34my $db = MODS::DBConnect->new;
35my $cfg = MODS::ContactForge::Config->new;
36my $wrap = MODS::ContactForge::Wrapper->new;
37my $DB = $cfg->settings('database_name');
38
39my $userinfo = $auth->login_verify();
40unless ($userinfo) {
41 print "Status: 302 Found\nLocation: /login.cgi\n\n";
42 exit;
43}
44# Only the account owner can close it -- team members never can.
45my $perm = MODS::ContactForge::Permissions->new;
46$perm->require_owner($userinfo, '/profile.cgi?denied=owner_only');
47
48my $uid = $userinfo->{user_id};
49$uid =~ s/[^0-9]//g;
50
51# ---------- POST: close the account ----------------------------------
52if (($ENV{REQUEST_METHOD} || '') eq 'POST'
53 && ($form->{action} || '') eq 'close'
54 && ($form->{confirm} || '') eq 'CLOSE') {
55
56 my $dbh = $db->db_connect();
57
58 # Mark the account closed. We do NOT delete -- downloads, orders,
59 # tax records all need to stay queryable for compliance + contact
60 # service. account_status='closed' is the soft-delete flag every
61 # other CGI already gates on.
62 $db->db_readwrite($dbh, qq~
63 UPDATE `${DB}`.users
64 SET account_status='closed'
65 WHERE id='$uid'
66 LIMIT 1
67 ~, $ENV{SCRIPT_NAME}, __LINE__);
68
69 # Revoke every active session -- including this one -- so the
70 # closed account is not still logged in on this device or any
71 # other. user_sessions.revoked_at is the cookie-validity gate.
72 $db->db_readwrite($dbh, qq~
73 UPDATE `${DB}`.user_sessions
74 SET revoked_at=NOW()
75 WHERE user_id='$uid'
76 AND revoked_at IS NULL
77 ~, $ENV{SCRIPT_NAME}, __LINE__);
78
79 # Take the dashboard out of public reach by flipping it to
80 # 'closed' (the dashboard-status enum has the same value; that
81 # path renders a maintenance page to contacts + hides it from
82 # search). New orders on a closed dashboard would block fulfill
83 # so this is the right hand-off.
84 $db->db_readwrite($dbh, qq~
85 UPDATE `${DB}`.dashboards
86 SET status='closed'
87 WHERE user_id='$uid'
88 AND status='live'
89 ~, $ENV{SCRIPT_NAME}, __LINE__);
90
91 $db->db_disconnect($dbh);
92
93 # Clear the auth cookie + bounce to a public goodbye URL.
94 my $cookie_name = $cfg->settings('cookie_name') || 'contactforge_session';
95 my $expire_cookie = qq~$cookie_name=; Max-Age=0; Path=/; HttpOnly; SameSite=Lax~;
96 print "Status: 302 Found\n";
97 print "Set-Cookie: $expire_cookie\n";
98 print "Location: /?closed=1\n\n";
99 exit;
100}
101
102# ---------- GET: render the confirmation page ------------------------
103my $tvars = {
104 display_name => _h($userinfo->{display_name} || ''),
105 email => _h($userinfo->{email} || ''),
106};
107
108print "Content-Type: text/html; charset=utf-8\nCache-Control: no-store, no-cache, must-revalidate, max-age=0\nPragma: no-cache\nExpires: 0\n\n";
109
110my $body = join('', $tfile->template('cf_account_close.html', $tvars, $userinfo));
111
112$wrap->render({
113 userinfo => $userinfo,
114 page_key => 'my_profile',
115 title => 'Close account',
116 body => $body,
117});
118exit;
119
120sub _h {
121 my $s = shift // '';
122 $s =~ s/&/&amp;/g; $s =~ s/</&lt;/g; $s =~ s/>/&gt;/g;
123 $s =~ s/"/&quot;/g; $s =~ s/'/&#39;/g;
124 return $s;
125}