added on local at 2026-07-01 21:47:07
| 1 | #!/usr/bin/perl | |
| 2 | #====================================================================== | |
| 3 | # RePricer — login | |
| 4 | # Page body lives in TEMPLATES/repricer_login.html | |
| 5 | # | |
| 6 | # GET : show the login form | |
| 7 | # POST : verify credentials, mint session, set cookie, 302 to /dashboard.cgi | |
| 8 | # on failure, re-render the form with an error | |
| 9 | #====================================================================== | |
| 10 | use strict; | |
| 11 | use warnings; | |
| 12 | ||
| 13 | use lib '/var/www/vhosts/3dshawn.com/repricer.3dshawn.com'; | |
| 14 | use CGI; | |
| 15 | use MODS::Template; | |
| 16 | use MODS::Login; | |
| 17 | use MODS::RePricer::Wrapper; | |
| 18 | use MODS::RePricer::Config; | |
| 19 | ||
| 20 | my $q = CGI->new; | |
| 21 | my $form = $q->Vars; | |
| 22 | my $auth = MODS::Login->new; | |
| 23 | my $wrap = MODS::RePricer::Wrapper->new; | |
| 24 | my $tfile = MODS::Template->new; | |
| 25 | ||
| 26 | $|=1; | |
| 27 | ||
| 28 | # Already signed in? bounce to the dashboard. | |
| 29 | if (my $existing = $auth->login_verify()) { | |
| 30 | print "Status: 302 Found\nLocation: /dashboard.cgi\n\n"; | |
| 31 | exit; | |
| 32 | } | |
| 33 | ||
| 34 | my $error_msg = ''; | |
| 35 | my $email_val = ''; | |
| 36 | my $flash_msg = ''; | |
| 37 | if (defined $form->{reset} && $form->{reset} eq 'ok') { | |
| 38 | $flash_msg = 'Password updated. Sign in with your new password.'; | |
| 39 | } | |
| 40 | ||
| 41 | # ---------- POST handler ---------- | |
| 42 | if (($ENV{REQUEST_METHOD} || '') eq 'POST') { | |
| 43 | my $email = $form->{email} || ''; | |
| 44 | my $pass = $form->{password} || ''; | |
| 45 | ||
| 46 | my ($userinfo, $token) = $auth->login_exec($email, $pass); | |
| 47 | ||
| 48 | if ($userinfo && $token) { | |
| 49 | # Cookie BEFORE redirect -- set-cookie must come before any other header. | |
| 50 | print $auth->set_cookie_header($token); | |
| 51 | # 2FA branch: login_exec stamped two_factor_pending=1 on the new | |
| 52 | # session row; login_verify will reject the session until | |
| 53 | # two_factor_verify.cgi flips that flag, so welcome.cgi would | |
| 54 | # bounce the user. Send them straight to code entry instead. | |
| 55 | my $next = $userinfo->{two_factor_enabled} | |
| 56 | ? '/two_factor_verify.cgi' | |
| 57 | : '/dashboard.cgi'; | |
| 58 | print "Status: 302 Found\nLocation: $next\n\n"; | |
| 59 | exit; | |
| 60 | } | |
| 61 | ||
| 62 | $error_msg = 'Email or password is incorrect.'; | |
| 63 | $email_val = _h($email); | |
| 64 | } | |
| 65 | ||
| 66 | print "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"; | |
| 67 | ||
| 68 | my $tvars = { | |
| 69 | error_msg => $error_msg, | |
| 70 | email_val => $email_val, | |
| 71 | flash_msg => $flash_msg, | |
| 72 | has_flash => length $flash_msg ? 1 : 0, | |
| 73 | }; | |
| 74 | ||
| 75 | my $body = join('', $tfile->template('repricer_login.html', $tvars, undef)); | |
| 76 | ||
| 77 | ||
| 78 | # === Maintenance lockdown overlay (auto-injected 2026-06-23) === | |
| 79 | # When maintenance_locked=1, render a modal over the login form. | |
| 80 | # X-close stays visible so an admin can sign in. Non-admin login | |
| 81 | # attempts fail and the page reloads, restoring the overlay. | |
| 82 | { | |
| 83 | my $_mcfg = MODS::RePricer::Config->new; | |
| 84 | if ($_mcfg->settings('maintenance_locked')) { | |
| 85 | my $_mmsg = $_mcfg->settings('maintenance_message') || '<p>The system is in maintenance mode.</p>'; | |
| 86 | my $_overlay = qq{<div id="maint-overlay" style="position:fixed;inset:0;background:rgba(0,0,0,0.88);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);z-index:9999;display:flex;align-items:center;justify-content:center;padding:24px"><div style="position:relative;background:#0f0f0f;border:1px solid rgba(217,119,6,0.6);border-radius:12px;max-width:680px;width:100%;padding:48px 40px;box-shadow:0 30px 90px rgba(0,0,0,0.6),0 0 0 1px rgba(217,119,6,0.15)"><button type="button" aria-label="Close" onclick="document.getElementById('maint-overlay').style.display='none'" style="position:absolute;top:14px;right:14px;width:36px;height:36px;background:transparent;border:1px solid rgba(255,255,255,0.12);color:#aaa;font-size:22px;line-height:1;border-radius:8px;cursor:pointer">×</button>$_mmsg</div><script>document.addEventListener('keydown',function(e){if(e.key==='Escape'){var o=document.getElementById('maint-overlay');if(o)o.style.display='none';}});</script></div>}; | |
| 87 | $body = $_overlay . $body; | |
| 88 | } | |
| 89 | } | |
| 90 | # === End maintenance lockdown overlay === | |
| 91 | $wrap->render({ | |
| 92 | userinfo => undef, | |
| 93 | title => 'Sign in', | |
| 94 | body => $body, | |
| 95 | }); | |
| 96 | ||
| 97 | #---------------------------------------------------------------------- | |
| 98 | sub _h { | |
| 99 | my $s = shift // ''; | |
| 100 | $s =~ s/&/&/g; $s =~ s/</</g; $s =~ s/>/>/g; | |
| 101 | $s =~ s/"/"/g; $s =~ s/'/'/g; | |
| 102 | return $s; | |
| 103 | } |