Diff -- /var/www/vhosts/3dshawn.com/admin.3dshawn.com/settings.cgi
Diff

/var/www/vhosts/3dshawn.com/admin.3dshawn.com/settings.cgi

added on local at 2026-07-01 14:22:48

Added
+191
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to 47919bf595e4
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# Meta-Admin -- /settings.cgi
4#
5# Editable platform_settings for the meta-admin app itself: brand,
6# cookie, session minutes, and the AES encryption key used to scramble
7# site_connections passwords at rest. Renders an editable card per
8# setting; POSTs to /settings_action.cgi.
9#======================================================================
10use strict;
11use warnings;
12use lib '/var/www/vhosts/3dshawn.com/admin.3dshawn.com';
13use CGI;
14use MODS::Login;
15use MODS::DBConnect;
16use MODS::MetaAdmin::Config;
17use MODS::MetaAdmin::Wrapper;
18
19my $q = CGI->new;
20my $form = $q->Vars;
21my $auth = MODS::Login->new;
22my $cfg = MODS::MetaAdmin::Config->new;
23my $wrap = MODS::MetaAdmin::Wrapper->new;
24my $u = $auth->login_verify;
25
26my $entry = ($ENV{SCRIPT_NAME} || '') =~ m{/([^/]+)\.cgi$} ? $1 : 'settings';
27
28if ($entry eq 'settings_action') {
29 unless ($u) { print "Status: 302 Found\nLocation: /login.cgi\n\n"; exit; }
30 _handle_action($q, $form);
31 exit;
32}
33
34unless ($u) { print "Status: 302 Found\nLocation: /login.cgi\n\n"; exit; }
35
36# Pull every platform_settings row so the editor surfaces ALL keys,
37# not just the ones the code reads. Adding a new row to the DB makes
38# it editable here automatically.
39my $db = MODS::DBConnect->new;
40my $dbh = $db->db_connect;
41my @rows = $db->db_readwrite_multiple($dbh, q~
42 SELECT setting_key, setting_value, is_secret, updated_at
43 FROM platform_settings ORDER BY setting_key
44~, __FILE__, __LINE__);
45$db->db_disconnect($dbh);
46
47# Metadata for the keys we know about -- label + group + description.
48# Anything not in this list still renders under "Other" so the page
49# never hides a settable value.
50my %META = (
51 brand_name => { label => 'Brand name', group => 'Brand', secret => 0,
52 desc => 'Shows in the sidebar header + browser title.' },
53 brand_tagline => { label => 'Brand tagline', group => 'Brand', secret => 0,
54 desc => 'One-line tag under the brand name.' },
55 cookie_name => { label => 'Session cookie name', group => 'Session', secret => 0,
56 desc => 'Name of the cookie used to keep you signed in. Changing kicks every open session.' },
57 session_minutes => { label => 'Session minutes', group => 'Session', secret => 0,
58 desc => 'How long a session lasts before re-login. Default 720 (12h).' },
59 site_conn_enc_key => { label => 'Site-connection encryption key', group => 'Security', secret => 1,
60 desc => 'AES key used to encrypt site_connections passwords at rest. Rotating invalidates every saved site password (must re-enter on Sites).' },
61);
62my @group_order = qw(Brand Session Security Other);
63
64# Bucket rows by group.
65my %by_group;
66foreach my $r (@rows) {
67 my $key = $r->{setting_key};
68 my $m = $META{$key} || { label => $key, group => 'Other', secret => $r->{is_secret} ? 1 : 0, desc => '' };
69 push @{ $by_group{ $m->{group} } }, {
70 key => $key,
71 value => $r->{setting_value},
72 secret => $m->{secret} || $r->{is_secret} ? 1 : 0,
73 label => $m->{label},
74 desc => $m->{desc} || '',
75 updated_at => $r->{updated_at} || '',
76 };
77}
78
79sub _esc {
80 my $s = shift; $s //= '';
81 $s =~ s/&/&amp;/g; $s =~ s/</&lt;/g; $s =~ s/>/&gt;/g;
82 $s =~ s/"/&quot;/g;
83 return $s;
84}
85
86my $body = '';
87
88# Flash banners
89my $flash = '';
90if ($form->{saved}) { $flash = qq~<div class="banner success">Settings saved.</div>~; }
91if ($form->{err}) {
92 my $e = _esc($form->{err});
93 $flash = qq~<div class="banner danger">$e</div>~;
94}
95
96$body .= q~
97<div class="page-head"><div class="left">
98 <span class="page-eyebrow"><span class="dot"></span> Operator</span>
99 <h1 class="page-title">Settings</h1>
100 <p class="page-subtitle">Brand, session, and security keys for the meta-admin app itself. Site DB credentials live under <a href="/sites.cgi">Sites</a>.</p>
101</div></div>
102~;
103$body .= $flash;
104
105# One module per group with one form per row.
106foreach my $g (@group_order) {
107 my $list = $by_group{$g} or next;
108 next unless scalar @$list;
109 my $glow = $g eq 'Security' ? 'glow-magenta'
110 : $g eq 'Brand' ? 'glow-cyan'
111 : $g eq 'Session' ? 'glow-violet'
112 : 'glow-lime';
113 my $g_h = _esc($g);
114 $body .= qq~<div class="module $glow" style="margin-bottom:16px">
115 <div class="module-head"><div class="left"><div class="module-icon"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg></div><div class="module-title">$g_h</div></div></div>
116 <div class="module-body">~;
117 foreach my $r (@$list) {
118 my $k = _esc($r->{key});
119 my $lbl = _esc($r->{label});
120 my $desc = _esc($r->{desc});
121 my $val = _esc($r->{value} // '');
122 my $upd = _esc($r->{updated_at});
123 my $input;
124 if ($r->{secret}) {
125 $input = qq~<input type="password" name="value" placeholder="(unchanged -- leave blank to keep)" autocomplete="new-password">~;
126 } elsif (length($r->{value} // '') > 80) {
127 $input = qq~<textarea name="value" rows="3">$val</textarea>~;
128 } else {
129 $input = qq~<input type="text" name="value" value="$val">~;
130 }
131 $body .= qq~<form method="POST" action="/settings_action.cgi" style="border-bottom:1px solid var(--bg-line);padding:12px 0;display:grid;grid-template-columns:240px 1fr auto;gap:14px;align-items:start">
132 <input type="hidden" name="op" value="save">
133 <input type="hidden" name="key" value="$k">
134 <div>
135 <div style="font-weight:600;font-size:13px;color:var(--col-text)">$lbl</div>
136 <div style="font-size:11px;color:var(--col-text-3);margin-top:4px;font-family:'JetBrains Mono',monospace">$k</div>
137 <div style="font-size:11px;color:var(--col-text-dim);margin-top:6px;line-height:1.5">$desc</div>
138 <div style="font-size:10px;color:var(--col-text-dim);margin-top:6px">Updated $upd</div>
139 </div>
140 <div>$input</div>
141 <button type="submit" class="btn btn-sm">Save</button>
142 </form>~;
143 }
144 $body .= q~</div></div>~;
145}
146
147$wrap->render(title => 'Settings', page_key => 'settings', body => $body, userinfo => $u);
148
149#======================================================================
150# settings_action entry: POST handler
151# op=save -- UPSERT a single platform_settings row by key.
152#======================================================================
153sub _act_go {
154 my $q = shift || '';
155 print "Status: 302 Found\nLocation: /settings.cgi$q\n\n";
156}
157sub _act_url_esc {
158 my $s = shift; $s //= '';
159 $s =~ s/([^A-Za-z0-9 .,'\$+-])/ /g;
160 $s =~ s/ /+/g;
161 return $s;
162}
163
164sub _handle_action {
165 my ($q, $f) = @_;
166
167 my $op = lc($f->{op} || '');
168 my $key = $f->{key} // '';
169 my $val = $f->{value}; # blank means "keep existing"
170 unless ($op eq 'save' && length $key) { _act_go('?err=' . _act_url_esc('bad input')); return; }
171 # Empty value on save means "keep existing" -- the secret pattern.
172 if (!defined $val || $val eq '') { _act_go('?saved=1'); return; }
173
174 # Sanity-cap absurd values.
175 $val = substr($val, 0, 16384);
176
177 my $db = MODS::DBConnect->new;
178 my $dbh = $db->db_connect;
179 unless ($dbh) { _act_go('?err=' . _act_url_esc('db unavailable')); return; }
180 my $k_q = $key; $k_q =~ s/'/''/g;
181 my $v_q = $val; $v_q =~ s/'/''/g;
182 $db->db_readwrite($dbh, qq~
183 INSERT INTO platform_settings (setting_key, setting_value)
184 VALUES ('$k_q', '$v_q')
185 ON DUPLICATE KEY UPDATE setting_value=VALUES(setting_value), updated_at=NOW()
186 ~, __FILE__, __LINE__);
187 $db->db_disconnect($dbh);
188
189 _act_go('?saved=1');
190 return;
191}
Keyboard: j next diff k previous diff g top G bottom r restore c compile-check ? help