Diff -- /var/www/vhosts/3dshawn.com/shop.3dshawn.com/support_meta_send.cgi

O Operator
Diff

/var/www/vhosts/3dshawn.com/shop.3dshawn.com/support_meta_send.cgi

added on local at 2026-07-10 00:38:39

Added
+95
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to 4ee0b22d1551
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# 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::ShopCart::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#======================================================================
18use strict;
19use warnings;
20
21my $raw = '';
22if (($ENV{REQUEST_METHOD} || '') eq 'POST') {
23 my $len = $ENV{CONTENT_LENGTH} || 0;
24 read STDIN, $raw, $len if $len > 0 && $len < 65536;
25}
26
27use lib '/var/www/vhosts/3dshawn.com/shop.3dshawn.com';
28use CGI ();
29use JSON::PP ();
30use Digest::SHA qw(hmac_sha256_hex sha256_hex);
31use MODS::DBConnect;
32use MODS::ShopCart::Config;
33use MODS::ShopCart::Support;
34
35sub _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
47my $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
51my $data = eval { JSON::PP::decode_json($raw) };
52_json_out(400, { error => 'bad_json' }) unless $data && ref($data) eq 'HASH';
53
54my $ts = int($data->{ts} || 0);
55my $thread_id = int($data->{thread_id} || 0);
56my $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
62my $cfg = MODS::ShopCart::Config->new;
63my $secret = $cfg->settings('meta_admin_hmac_secret') || '';
64_json_out(500, { error => 'no_secret_configured' }) unless length($secret) >= 32;
65
66my $body_hash = sha256_hex($body_txt);
67my $expected = hmac_sha256_hex("$ts.$thread_id.$body_hash", $secret);
68
69# Constant-time compare
70my $ok = length($sig_hdr) == length($expected);
71my $r = 0;
72if ($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.
81my $db = MODS::DBConnect->new;
82my $dbh = $db->db_connect();
83my $DB = $cfg->settings('database_name');
84my $sup = MODS::ShopCart::Support->new;
85
86my $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) });