-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathreset_password.php
More file actions
170 lines (142 loc) · 6.61 KB
/
Copy pathreset_password.php
File metadata and controls
170 lines (142 loc) · 6.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
<?php
$title = "Password Reset";
include_once 'include/header.php';
// SHARED DB CONNECTION
$conn = db();
// Ensure Recovery Codes table exists (supports grids that predate this feature)
function ensure_recovery_table($conn) {
$sql = "CREATE TABLE IF NOT EXISTS ws_recovery_codes (
id INT AUTO_INCREMENT PRIMARY KEY,
PrincipalID CHAR(36) NOT NULL,
code_hash VARCHAR(255) NOT NULL,
is_used TINYINT(1) DEFAULT 0,
INDEX (PrincipalID)
)";
$conn->query($sql);
}
$msg = "";
$msgType = ""; // success or danger
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$fname = trim($_POST['firstName']);
$lname = trim($_POST['lastName']);
$code = strtoupper(preg_replace('/[^A-Z0-9]/', '', trim($_POST['recoveryCode'] ?? '')));
$pass = $_POST['newPassword'];
$pass2 = $_POST['confirmPassword'];
// 1. Basic Validation
if ($pass !== $pass2) {
$msg = "New passwords do not match.";
$msgType = "danger";
} elseif (strlen($pass) < 6) {
$msg = "Password must be at least 6 characters.";
$msgType = "danger";
} else {
// 2. Find User UUID
$sql = "SELECT PrincipalID FROM UserAccounts WHERE FirstName = ? AND LastName = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("ss", $fname, $lname);
$stmt->execute();
$res = $stmt->get_result();
if ($row = $res->fetch_assoc()) {
$uuid = $row['PrincipalID'];
ensure_recovery_table($conn);
// 3. Fetch all UNUSED recovery codes for this user
// We verify them one by one using password_verify()
$codeFound = false;
$codeIDToInvalidate = 0;
$sqlCodes = "SELECT id, code_hash FROM ws_recovery_codes WHERE PrincipalID = ? AND is_used = 0";
$stmtCodes = $conn->prepare($sqlCodes);
$stmtCodes->bind_param("s", $uuid);
$stmtCodes->execute();
$resCodes = $stmtCodes->get_result();
while ($cRow = $resCodes->fetch_assoc()) {
// Check if the input code matches this hash
if (password_verify($code, $cRow['code_hash'])) {
$codeFound = true;
$codeIDToInvalidate = $cRow['id'];
break; // Stop looking, we found a match
}
}
if ($codeFound) {
// 4. Code is valid! Perform Password Reset (atomic: update password + burn code)
$conn->begin_transaction();
try {
// A. Update Password in 'auth' table
$newSalt = md5(uniqid(mt_rand(), true));
$newHash = md5(md5($pass) . ":" . $newSalt);
$upSql = "UPDATE auth SET passwordHash = ?, passwordSalt = ? WHERE UUID = ?";
$upStmt = $conn->prepare($upSql);
$upStmt->bind_param("sss", $newHash, $newSalt, $uuid);
if (!$upStmt->execute()) {
throw new Exception("Database error updating password.");
}
// B. Burn the code (Mark as used) - only if still unused
$burnSql = "UPDATE ws_recovery_codes SET is_used = 1 WHERE id = ? AND is_used = 0";
$burnStmt = $conn->prepare($burnSql);
$burnStmt->bind_param("i", $codeIDToInvalidate);
if (!$burnStmt->execute() || $burnStmt->affected_rows !== 1) {
throw new Exception("Database error invalidating recovery code.");
}
if (!$conn->commit()) {
throw new Exception("Database error finalizing reset.");
}
$msg = "Success! Your password has been reset. You can log in now.";
$msgType = "success";
} catch (Throwable $e) {
$conn->rollback();
$msg = $e->getMessage();
$msgType = "danger";
}
} else {
$msg = "Invalid Recovery Code. Please check your spelling or try a different code.";
$msgType = "danger";
}
} else {
$msg = "Avatar not found.";
$msgType = "danger";
}
}
}
?>
<div class="container mt-5 mb-5" style="max-width: 500px;">
<div class="text-center mb-4">
<h1><i class="bi bi-life-preserver"></i> Account Recovery</h1>
<p class="text-muted">Use one of your saved Recovery Codes to reset your password.</p>
</div>
<?php if ($msg): ?>
<div class="alert alert-<?php echo $msgType; ?>"><?php echo $msg; ?></div>
<?php endif; ?>
<div class="card shadow">
<div class="card-body">
<form method="POST">
<div class="row mb-3">
<div class="col">
<label class="form-label fw-bold">First Name</label>
<input type="text" name="firstName" class="form-control" required>
</div>
<div class="col">
<label class="form-label fw-bold">Last Name</label>
<input type="text" name="lastName" class="form-control" required>
</div>
</div>
<div class="mb-3">
<label class="form-label fw-bold text-danger">Recovery Code</label>
<input type="text" name="recoveryCode" class="form-control text-uppercase" placeholder="e.g. A1B2C3D4" required>
<div class="form-text">Enter any unused code from your saved list.</div>
</div>
<hr>
<div class="mb-3">
<label class="form-label fw-bold">New Password</label>
<input type="password" name="newPassword" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label fw-bold">Confirm New Password</label>
<input type="password" name="confirmPassword" class="form-control" required>
</div>
<div class="d-grid mt-4">
<button type="submit" class="btn btn-danger">Reset Password</button>
</div>
</form>
</div>
</div>
</div>
<?php include_once "include/" . FOOTER_FILE; ?>