login.php

main 218 lines · 7.3 KB Raw
Alex Alex Commit Initial commit 01/10/2026 09:20
1<?php
2require_once 'engine/init.php';
3
4if (lws_is_request()) {
5 lws_handle();
6}
7
8logged_in_redirect();
9theme_open();
10
11// ---------------------------------------------------------------------------
12// Step 2 of login: website 2FA v2 (independent of the game engine).
13//
14// Reached only after user_login() + the legacy TFS 2FA already succeeded, see
15// below. The pending account id lives in the session, never in the form, so a
16// visitor cannot skip straight here with an arbitrary account id.
17// ---------------------------------------------------------------------------
18if (isset($_POST['tfa2_code']) && isset($_SESSION['tfa2_pending']['id'])) {
19 $pending = $_SESSION['tfa2_pending'];
20
21 if (!Token::isValid($_POST['token'] ?? null)) {
22 $errors[] = t('login.token_invalid');
23 } else if ((int)$pending['until'] < time()) {
24 unset($_SESSION['tfa2_pending']);
25 $errors[] = t_default('twofa2.expired', 'That verification step expired. Please log in again.');
26 } else {
27 $accountId = (int)$pending['id'];
28 $code = getValue($_POST['tfa2_code'] ?? null);
29
30 if ($code !== false && znote2fa_verify_login_input($accountId, $code)) {
31 unset($_SESSION['tfa2_pending']);
32 setSession('user_id', $accountId);
33 $_SESSION['tfa2_sv'] = znote2fa_session_version($accountId);
34 Token::generate();
35
36 if (!empty($_POST['tfa2_trust']) && (int)znote2fa_v2_config()['trusted_device_days'] > 0) {
37 znote2fa_trusted_device_issue($accountId);
38 }
39
40 header('Location: myaccount.php');
41 exit();
42 }
43
44 $errors[] = t_default('twofa2.wrong_code', 'That code is not valid. It may have expired, or you may have mistyped it.');
45 }
46}
47
48if (isset($_SESSION['tfa2_pending']['id'])) {
49 $pendingStatus = znote2fa_status((int)$_SESSION['tfa2_pending']['id']);
50
51 if (empty($_POST['tfa2_email_sent']) && $pendingStatus['email_otp_enabled'] && !$pendingStatus['totp_enabled']) {
52 $pendingUser = user_data((int)$_SESSION['tfa2_pending']['id'], 'email', 'name');
53 if (is_array($pendingUser) && !empty($pendingUser['email'])) {
54 if (!znote2fa_email_send_code((int)$_SESSION['tfa2_pending']['id'], (string)$pendingUser['email'], (string)($pendingUser['name'] ?? ''))) {
55 $errors[] = t_default('twofa2.email_delivery_failed', 'The verification e-mail could not be sent. Try again later or use a recovery code.');
56 }
57 } else {
58 $errors[] = t_default('twofa2.email_missing', 'This account has no valid e-mail address. Use a recovery code or contact an administrator.');
59 }
60 }
61 view('login_2fa');
62 theme_close();
63 exit();
64}
65
66if (empty($_POST) === false && !isset($_POST['tfa2_code'])) {
67
68 if ($config['log_ip']) {
69 znote_visitor_insert_detailed_data(5);
70 }
71
72 $username = $_POST['username'];
73 $password = $_POST['password'];
74
75 $loginGuardIp = znote_login_guard_ip();
76 $loginGuardLockedFor = znote_login_guard_lockout_remaining($loginGuardIp);
77
78 if ($loginGuardLockedFor > 0) {
79 $errors[] = t('login.too_many_attempts', ['minutes' => (int)ceil($loginGuardLockedFor / 60)]);
80 } else if (empty($username) || empty($password)) {
81 $errors[] = t('login.empty_fields');
82 } else if (strlen($username) > 32 || strlen($password) > 64) {
83 $errors[] = t('login.too_long');
84 } else if (user_exist($username) === false) {
85 znote_login_guard_record($loginGuardIp, (string)$username, false);
86 $errors[] = t('login.not_found');
87 } /*else if (user_activated($username) === false) {
88 $errors[] = t('login.not_activated');
89 } */else if (!Token::isValid($_POST['token'] ?? null)) {
90 $errors[] = t('login.token_invalid');
91 } else {
92
93 // Starting login. Delegated to the server adapter, which knows whether
94 // this engine identifies an account by name or id and which password
95 // scheme it uses (see engine/adapter/).
96 $login = znote_server_adapter()->login($username, $password);
97 if ($login === false) {
98 znote_login_guard_record($loginGuardIp, (string)$username, false);
99 $errors[] = t('login.wrong_combo');
100 } else {
101 znote_login_guard_record($loginGuardIp, (string)$username, true);
102 // Check if user have access to login
103 $status = false;
104 if ($config['mailserver']['register']) {
105 $authenticate = db()->fetchOne(
106 "SELECT `id` FROM `znote_accounts` WHERE `account_id` = ? AND `active` = 1 LIMIT 1;",
107 [(int)$login]
108 );
109 if ($authenticate !== false) {
110 $status = true;
111 } else {
112 $errors[] = t('login.not_activated');
113 }
114 } else $status = true;
115
116 if ($status) {
117 // Regular login success, now lets check authentication token code
118 if (znote_server_adapter()->supportsLegacyTwoFactor() && $config['twoFactorAuthenticator']) {
119 require_once("engine/function/rfc6238.php");
120
121 // Two factor authentication code / token
122 $authcode = (isset($_POST['authcode'])) ? getValue($_POST['authcode'] ?? null) : false;
123
124 // Load secret values from db
125 $query = db()->fetchOne(
126 "SELECT `a`.`secret` AS `secret`, `za`.`secret` AS `znote_secret`
127 FROM `accounts` AS `a`
128 INNER JOIN `znote_accounts` AS `za` ON `a`.`id` = `za`.`account_id`
129 WHERE `a`.`id` = ?
130 LIMIT 1;",
131 [(int)$login]
132 );
133
134 if ($query === false) {
135 $errors[] = t('login.failed_title');
136 $status = false;
137
138 // If account table HAS a secret, we need to validate it
139 } else if ($query['secret'] !== NULL) {
140
141 // Validate the secret first to make sure all is good.
142 if (TokenAuth6238::verify($query['secret'], $authcode) !== true) {
143 $errors[] = t('login.2fa_wrong');
144 $errors[] = t('login.2fa_hint');
145 $status = false;
146 }
147
148 } else {
149
150 // secret from accounts table is null/not set. Perhaps we can activate it:
151 if ($query['znote_secret'] !== NULL && $authcode !== false && !empty($authcode)) {
152
153 // Validate the secret first to make sure all is good.
154 if (TokenAuth6238::verify($query['znote_secret'], $authcode)) {
155 // Success, enable the 2FA system
156 db()->execute(
157 "UPDATE `accounts` SET `secret` = ? WHERE `id` = ?;",
158 [$query['znote_secret'], (int)$login]
159 );
160 } else {
161 $errors[] = t('login.2fa_activate_failed');
162 $errors[] = t('login.2fa_wrong');
163 $errors[] = t('login.2fa_hint');
164 $status = false;
165 }
166 }
167 }
168 } // End tfs 1.0+ with 2FA auth
169
170 if ($status) {
171 if (!znote_session_regenerate()) {
172 $errors[] = t('login.failed_title');
173 $status = false;
174 }
175 }
176
177 if ($status) {
178 $loginNameRow = user_data($login, 'id', 'name');
179 $isAdminAccount = has_admin_panel_access(is_array($loginNameRow) ? $loginNameRow : array());
180
181 if (znote2fa_required((int)$login, $isAdminAccount) && !znote2fa_trusted_device_check((int)$login)) {
182 $_SESSION['tfa2_pending'] = array('id' => (int)$login, 'until' => time() + 300);
183 header('Location: login.php');
184 exit();
185 }
186
187 setSession('user_id', $login);
188 $_SESSION['tfa2_sv'] = znote2fa_session_version((int)$login);
189 Token::generate();
190
191 // if IP is not set (etc acc created before Znote AAC was in use)
192 $znote_data = user_znote_account_data($login, 'ip');
193 if ($znote_data['ip'] == 0) {
194 $update_data = array(
195 'ip' => getIPLong(),
196 );
197 user_update_znote_account($update_data);
198 }
199
200 // Send them to myaccount.php
201 header('Location: myaccount.php');
202 exit();
203 }
204 }
205 }
206 }
207}
208
209if (empty($errors) === false) {
210 header("HTTP/1.1 401 Not Found");
211}
212
213if (empty($_POST) === true || empty($errors) === false) {
214 view('login_form');
215}
216
217theme_close(); ?>
218
Top