Diff -- /var/www/vhosts/3dshawn.com/abforge.3dshawn.com/reset_password.cgi
Diff

/var/www/vhosts/3dshawn.com/abforge.3dshawn.com/reset_password.cgi

added on local at 2026-07-01 16:01:37

Added
+154
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to 4b060ed84a67
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# ABForge - Reset password (token redemption)
4#
5# GET ?t=TOKEN : verify the token is valid + unexpired + unused;
6# show the new-password form
7# POST : verify the token AGAIN (defense against TOCTOU);
8# update users.password_hash; mark password_resets.used_at;
9# revoke all existing sessions; redirect to /login.cgi
10# with a "password updated, please sign in" flash
11#======================================================================
12use strict;
13use warnings;
14
15use lib '/var/www/vhosts/abforge.com/httpdocs';
16use CGI;
17use MODS::Template;
18use MODS::DBConnect;
19use MODS::Login;
20use MODS::ABForge::Config;
21use MODS::ABForge::Wrapper;
22
23my $q = CGI->new;
24my $form = $q->Vars;
25my $auth = MODS::Login->new;
26my $wrap = MODS::ABForge::Wrapper->new;
27my $tfile = MODS::Template->new;
28my $db = MODS::DBConnect->new;
29my $cfg = MODS::ABForge::Config->new;
30my $DB = $cfg->settings('database_name');
31
32$|=1;
33
34my $token = defined $form->{t} ? $form->{t} : '';
35$token =~ s/[^a-f0-9]//gi;
36$token = substr($token, 0, 64);
37
38my $method = uc($ENV{REQUEST_METHOD} || 'GET');
39
40my $error_msg = '';
41my $token_ok = 0;
42my $row;
43
44my $dbh = $db->db_connect();
45
46# Pre-migration guard.
47my $tab = $db->db_readwrite($dbh, qq~
48 SELECT COUNT(*) AS n FROM information_schema.tables
49 WHERE table_schema='$DB' AND table_name='password_resets'
50~, $ENV{SCRIPT_NAME}, __LINE__);
51
52if (!$tab || !$tab->{n}) {
53 $db->db_disconnect($dbh);
54 _render_error('Password reset is not enabled on this install yet. Please contact support.');
55 exit;
56}
57
58if (length($token) == 64) {
59 $row = $db->db_readwrite($dbh, qq~
60 SELECT id, user_id, expires_at, used_at
61 FROM `${DB}`.password_resets
62 WHERE token='$token'
63 LIMIT 1
64 ~, $ENV{SCRIPT_NAME}, __LINE__);
65
66 if ($row && $row->{id}) {
67 if ($row->{used_at}) {
68 $error_msg = 'This reset link has already been used. Request a new one from the forgot-password page.';
69 } else {
70 # Compare expires_at against NOW(). DB-side check avoids
71 # any local clock drift between web + DB hosts.
72 my $exp_check = $db->db_readwrite($dbh, qq~
73 SELECT (expires_at >= NOW()) AS still_valid
74 FROM `${DB}`.password_resets WHERE id='$row->{id}'
75 ~, $ENV{SCRIPT_NAME}, __LINE__);
76 if ($exp_check && $exp_check->{still_valid}) {
77 $token_ok = 1;
78 } else {
79 $error_msg = 'This reset link has expired. Request a new one from the forgot-password page.';
80 }
81 }
82 } else {
83 $error_msg = 'That reset link is not valid. Request a new one from the forgot-password page.';
84 }
85} else {
86 $error_msg = 'Missing or malformed reset token.';
87}
88
89# ---- POST: actually rotate the password ----
90if ($method eq 'POST' && $token_ok) {
91 my $pw1 = defined $form->{password} ? $form->{password} : '';
92 my $pw2 = defined $form->{password_confirm} ? $form->{password_confirm} : '';
93
94 if (length($pw1) < 8) {
95 $error_msg = 'Password must be at least 8 characters.';
96 } elsif ($pw1 ne $pw2) {
97 $error_msg = 'The two passwords do not match.';
98 } else {
99 # Mark the token consumed BEFORE rotating the password. If the
100 # password update somehow fails (FK / DB hiccup), the token is
101 # still burned and can't be retried -- safer to force a new
102 # reset request than to allow a replay.
103 $db->db_readwrite($dbh, qq~
104 UPDATE `${DB}`.password_resets
105 SET used_at=NOW()
106 WHERE id='$row->{id}' AND used_at IS NULL
107 ~, $ENV{SCRIPT_NAME}, __LINE__);
108
109 my $ok = $auth->update_password($row->{user_id}, $pw1);
110 $db->db_disconnect($dbh);
111 if ($ok) {
112 print "Status: 302 Found\nLocation: /login.cgi?reset=ok\n\n";
113 exit;
114 }
115 _render_error('We could not save the new password. Try requesting another reset link, and contact support if it keeps failing.');
116 exit;
117 }
118}
119
120$db->db_disconnect($dbh);
121
122print "Content-Type: text/html; charset=utf-8\nCache-Control: no-cache, no-store\n\n";
123
124my $tvars = {
125 token_ok => $token_ok,
126 token => $token_ok ? $token : '',
127 error_msg => $error_msg,
128 has_error => length $error_msg ? 1 : 0,
129};
130
131my $body = join('', $tfile->template('abforge_reset_password.html', $tvars, undef));
132
133$wrap->render({
134 userinfo => undef,
135 title => 'Reset password',
136 body => $body,
137});
138
139sub _render_error {
140 my ($msg) = @_;
141 print "Content-Type: text/html; charset=utf-8\nCache-Control: no-cache\n\n";
142 my $tvars = {
143 token_ok => 0,
144 token => '',
145 error_msg => $msg,
146 has_error => 1,
147 };
148 my $body = join('', $tfile->template('abforge_reset_password.html', $tvars, undef));
149 $wrap->render({
150 userinfo => undef,
151 title => 'Reset password',
152 body => $body,
153 });
154}
Keyboard: j next diff k previous diff g top G bottom r restore c compile-check ? help