Diff -- /var/www/vhosts/3dshawn.com/affiliate.3dshawn.com/support_meta_send.cgi
Diff
/var/www/vhosts/3dshawn.com/affiliate.3dshawn.com/support_meta_send.cgi
added on local at 2026-07-10 00:38:36
Added
+95
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to 5dd917a06c4b
to 5dd917a06c4b
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 | # Cross-site support reply endpoint. Accepts a signed JSON POST from | |
| 4 | # Meta-Admin's operator console and delegates to this site's normal | |
| 5 | # MODS::AffSoft::Support::send_message so all the usual bookkeeping | |
| 6 | # (unread flags, status flip, last_message_at) fires. | |
| 7 | # | |
| 8 | # Wire-format: | |
| 9 | # POST /support_meta_send.cgi | |
| 10 | # Content-Type: application/json | |
| 11 | # Body: {"ts": 1720000000, "thread_id": 42, "body": "hello"} | |
| 12 | # Header: X-Meta-Sig: sha256_hex_hmac(secret, "$ts.$thread_id.$body_sha256") | |
| 13 | # | |
| 14 | # Shared secret: | |
| 15 | # platform_settings.meta_admin_hmac_secret (mirror on Meta-Admin). | |
| 16 | # 5-minute clock skew tolerance. Constant-time signature compare. | |
| 17 | #====================================================================== | |
| 18 | use strict; | |
| 19 | use warnings; | |
| 20 | ||
| 21 | my $raw = ''; | |
| 22 | if (($ENV{REQUEST_METHOD} || '') eq 'POST') { | |
| 23 | my $len = $ENV{CONTENT_LENGTH} || 0; | |
| 24 | read STDIN, $raw, $len if $len > 0 && $len < 65536; | |
| 25 | } | |
| 26 | ||
| 27 | use lib '/var/www/vhosts/3dshawn.com/affiliate.3dshawn.com'; | |
| 28 | use CGI (); | |
| 29 | use JSON::PP (); | |
| 30 | use Digest::SHA qw(hmac_sha256_hex sha256_hex); | |
| 31 | use MODS::DBConnect; | |
| 32 | use MODS::AffSoft::Config; | |
| 33 | use MODS::AffSoft::Support; | |
| 34 | ||
| 35 | sub _json_out { | |
| 36 | my ($code, $body) = @_; | |
| 37 | my $status_line = $code == 200 ? '' : "Status: $code\n"; | |
| 38 | print "${status_line}Content-Type: application/json\n\n"; | |
| 39 | print JSON::PP::encode_json($body); | |
| 40 | exit; | |
| 41 | } | |
| 42 | ||
| 43 | _json_out(405, { error => 'method_not_allowed' }) | |
| 44 | unless ($ENV{REQUEST_METHOD} || '') eq 'POST'; | |
| 45 | _json_out(400, { error => 'empty_body' }) unless length $raw; | |
| 46 | ||
| 47 | my $sig_hdr = $ENV{HTTP_X_META_SIG} || ''; | |
| 48 | $sig_hdr =~ s/[^a-f0-9]//gi; | |
| 49 | _json_out(400, { error => 'no_signature' }) unless length($sig_hdr) == 64; | |
| 50 | ||
| 51 | my $data = eval { JSON::PP::decode_json($raw) }; | |
| 52 | _json_out(400, { error => 'bad_json' }) unless $data && ref($data) eq 'HASH'; | |
| 53 | ||
| 54 | my $ts = int($data->{ts} || 0); | |
| 55 | my $thread_id = int($data->{thread_id} || 0); | |
| 56 | my $body_txt = defined($data->{body}) ? $data->{body} : ''; | |
| 57 | ||
| 58 | _json_out(400, { error => 'missing_thread_id' }) unless $thread_id > 0; | |
| 59 | _json_out(400, { error => 'empty_body' }) unless length $body_txt; | |
| 60 | _json_out(400, { error => 'stale_timestamp' }) if abs(time - $ts) > 300; | |
| 61 | ||
| 62 | my $cfg = MODS::AffSoft::Config->new; | |
| 63 | my $secret = $cfg->settings('meta_admin_hmac_secret') || ''; | |
| 64 | _json_out(500, { error => 'no_secret_configured' }) unless length($secret) >= 32; | |
| 65 | ||
| 66 | my $body_hash = sha256_hex($body_txt); | |
| 67 | my $expected = hmac_sha256_hex("$ts.$thread_id.$body_hash", $secret); | |
| 68 | ||
| 69 | # Constant-time compare | |
| 70 | my $ok = length($sig_hdr) == length($expected); | |
| 71 | my $r = 0; | |
| 72 | if ($ok) { | |
| 73 | for (my $i = 0; $i < length($sig_hdr); $i++) { | |
| 74 | $r |= ord(substr($sig_hdr, $i, 1)) ^ ord(substr($expected, $i, 1)); | |
| 75 | } | |
| 76 | $ok = ($r == 0); | |
| 77 | } | |
| 78 | _json_out(403, { error => 'bad_signature' }) unless $ok; | |
| 79 | ||
| 80 | # Signature valid -- dispatch to site's Support.pm. | |
| 81 | my $db = MODS::DBConnect->new; | |
| 82 | my $dbh = $db->db_connect(); | |
| 83 | my $DB = $cfg->settings('database_name'); | |
| 84 | my $sup = MODS::AffSoft::Support->new; | |
| 85 | ||
| 86 | my $mid = $sup->send_message($db, $dbh, $DB, { | |
| 87 | thread_id => $thread_id, | |
| 88 | sender_role => 'admin', | |
| 89 | sender_user_id => 0, # NULL in DB -- portfolio-level operator, no local user row | |
| 90 | body => $body_txt, | |
| 91 | }); | |
| 92 | ||
| 93 | $db->db_disconnect($dbh); | |
| 94 | ||
| 95 | _json_out(200, { ok => 1, message_id => ($mid || 0) }); |