sequelize-game-bank 5.0.1 → 5.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sequelize-game-bank",
3
- "version": "5.0.1",
3
+ "version": "5.0.2",
4
4
  "description": "A Sequelize-based gaming economy and banking system featuring user profiles, credits, admin management, and detailed transaction history.",
5
5
  "main": "index.js",
6
6
  "type": "commonjs",
package/public/login.html CHANGED
@@ -1,72 +1,135 @@
1
- <!DOCTYPE html>
1
+ <!doctype html>
2
2
  <html lang="ru">
3
- <head>
4
- <meta charset="UTF-8">
5
- <title>City-bank | Вход в систему</title>
6
- <style>
7
- body { font-family: sans-serif; background: #f4f6f9; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; }
8
- .card { background: white; padding: 30px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); width: 100%; max-width: 380px; }
9
- h2 { text-align: center; color: #007bff; margin-top: 0; }
10
- .group { margin: 15px 0; }
11
- label { display: block; margin-bottom: 5px; font-weight: bold; font-size: 14px; }
12
- input { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; font-size: 16px; }
13
- .btn { width: 100%; padding: 12px; background: #007bff; color: white; border: none; border-radius: 4px; font-size: 16px; font-weight: bold; cursor: pointer; }
14
- .error { color: #dc3545; font-size: 14px; margin-top: 10px; display: none; text-align: center; }
15
- </style>
16
- </head>
17
- <body>
18
- <div class="card">
19
- <h2>ВХОД В CITY-BANK</h2>
20
- <div class="group">
21
- <label for="login-name">Никнейм (Логин)</label>
22
- <input type="text" id="login-name" placeholder="Введите ваш ник">
23
- </div>
24
- <div class="group">
25
- <label for="login-pass">Пароль</label>
26
- <input type="password" id="login-pass" placeholder="Введите пароль">
27
- </div>
28
-
29
- <!-- Скрытый блок 2FA, активируемый бэкендом -->
30
- <div class="group" id="totp-block" style="display: none;">
31
- <label for="login-totp" style="color: #28a745;">Введите временный токен 2FA</label>
32
- <input type="text" id="login-totp" placeholder="000000" style="text-align: center; font-size: 20px; letter-spacing: 2px;">
33
- </div>
34
-
35
- <div id="login-error" class="error"></div>
36
- <button class="btn" onclick="submitLogin()">Войти в кабинет</button>
37
- <p style="text-align: center; font-size: 14px; margin-top: 15px;"><a href="/bank/register.html">Создать аккаунт</a></p>
38
- </div>
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <title>City-bank | Вход в систему</title>
6
+ <style>
7
+ body {
8
+ font-family: sans-serif;
9
+ background: #f4f6f9;
10
+ display: flex;
11
+ justify-content: center;
12
+ align-items: center;
13
+ height: 100vh;
14
+ margin: 0;
15
+ }
16
+ .card {
17
+ background: white;
18
+ padding: 30px;
19
+ border-radius: 8px;
20
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
21
+ width: 100%;
22
+ max-width: 380px;
23
+ }
24
+ h2 {
25
+ text-align: center;
26
+ color: #007bff;
27
+ margin-top: 0;
28
+ }
29
+ .group {
30
+ margin: 15px 0;
31
+ }
32
+ label {
33
+ display: block;
34
+ margin-bottom: 5px;
35
+ font-weight: bold;
36
+ font-size: 14px;
37
+ }
38
+ input {
39
+ width: 100%;
40
+ padding: 10px;
41
+ border: 1px solid #ddd;
42
+ border-radius: 4px;
43
+ box-sizing: border-box;
44
+ font-size: 16px;
45
+ }
46
+ .btn {
47
+ width: 100%;
48
+ padding: 12px;
49
+ background: #007bff;
50
+ color: white;
51
+ border: none;
52
+ border-radius: 4px;
53
+ font-size: 16px;
54
+ font-weight: bold;
55
+ cursor: pointer;
56
+ }
57
+ .error {
58
+ color: #dc3545;
59
+ font-size: 14px;
60
+ margin-top: 10px;
61
+ display: none;
62
+ text-align: center;
63
+ }
64
+ </style>
65
+ </head>
66
+ <body>
67
+ <div class="card">
68
+ <h2>ВХОД В CITY-BANK</h2>
69
+ <div class="group">
70
+ <label for="login-name">Никнейм (Логин)</label>
71
+ <input type="text" id="login-name" placeholder="Введите ваш ник" />
72
+ </div>
73
+ <div class="group">
74
+ <label for="login-pass">Пароль</label>
75
+ <input type="password" id="login-pass" placeholder="Введите пароль" />
76
+ </div>
39
77
 
40
- <script>
41
- async function submitLogin() {
42
- const name = document.getElementById('login-name').value.trim();
43
- const pass = document.getElementById('login-pass').value;
44
- const totp = document.getElementById('login-totp').value.trim();
45
- const errBlock = document.getElementById('login-error');
46
- errBlock.style.display = 'none';
78
+ <!-- Скрытый блок 2FA, активируемый бэкендом -->
79
+ <div class="group" id="totp-block" style="display: none">
80
+ <label for="login-totp" style="color: #28a745"
81
+ >Введите временный токен 2FA</label
82
+ >
83
+ <input
84
+ type="text"
85
+ id="login-totp"
86
+ placeholder="000000"
87
+ style="text-align: center; font-size: 20px; letter-spacing: 2px"
88
+ />
89
+ </div>
47
90
 
48
- try {
49
- const response = await fetch('/api/bank/users/login', {
50
- method: 'POST',
51
- headers: {'Content-Type': 'application/json'},
52
- body: JSON.stringify({ name, password: pass, totpCode: totp || null })
53
- });
54
- const result = await response.json();
91
+ <div id="login-error" class="error"></div>
92
+ <button class="btn" onclick="submitLogin()">Войти в кабинет</button>
93
+ <p style="text-align: center; font-size: 14px; margin-top: 15px">
94
+ <a href="/bank/register.html">Создать аккаунт</a>
95
+ </p>
96
+ </div>
55
97
 
56
- if (response.ok && result.status === true) {
57
- window.location.href = '/bank/general.html';
58
- } else if (result.require2FA) {
59
- document.getElementById('totp-block').style.display = 'block';
60
- errBlock.innerText = result.message;
61
- errBlock.style.display = 'block';
62
- } else {
63
- errBlock.innerText = result.message || "Ошибка авторизации";
64
- errBlock.style.display = 'block';
98
+ <script>
99
+ async function submitLogin() {
100
+ const name = document.getElementById("login-name").value.trim();
101
+ const pass = document.getElementById("login-pass").value;
102
+ const totp = document.getElementById("login-totp").value.trim();
103
+ const errBlock = document.getElementById("login-error");
104
+ errBlock.style.display = "none";
105
+ try {
106
+ const response = await fetch("/api/bank/users/login", {
107
+ method: "POST",
108
+ headers: {
109
+ "Content-Type": "application/json",
110
+ "Bypass-Tunnel-Reminder": "true",
111
+ },
112
+ body: JSON.stringify({
113
+ name,
114
+ password: pass,
115
+ totpCode: totp || null,
116
+ }),
117
+ });
118
+ const result = await response.json();
119
+ if (response.ok && result.status === true) {
120
+ window.location.href = "/bank/general.html";
121
+ } else if (result.require2FA) {
122
+ document.getElementById("totp-block").style.display = "block";
123
+ errBlock.innerText = result.message;
124
+ errBlock.style.display = "block";
125
+ } else {
126
+ errBlock.innerText = result.message || "Ошибка авторизации";
127
+ errBlock.style.display = "block";
128
+ }
129
+ } catch (e) {
130
+ console.error(e);
131
+ }
65
132
  }
66
- } catch(e) {
67
- console.error(e);
68
- }
69
- }
70
- </script>
71
- </body>
133
+ </script>
134
+ </body>
72
135
  </html>
@@ -1,442 +1,516 @@
1
- <!DOCTYPE html>
1
+ <!doctype html>
2
2
  <html lang="ru">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>City-bank | Регистрация нового аккаунта</title>
7
- <style>
8
- :root {
9
- --primary: #007bff;
10
- --success: #28a745;
11
- --danger: #dc3545;
12
- --bg: #f4f6f9;
13
- --card: #ffffff;
14
- --text: #333333;
15
- }
16
- body {
17
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
18
- background-color: var(--bg);
19
- color: var(--text);
20
- display: flex;
21
- justify-content: center;
22
- align-items: center;
23
- height: 100vh;
24
- margin: 0;
25
- }
26
- .register-container {
27
- background: var(--card);
28
- padding: 30px;
29
- border-radius: 12px;
30
- box-shadow: 0 8px 24px rgba(0,0,0,0.1);
31
- width: 100%;
32
- max-width: 450px;
33
- box-sizing: border-box;
34
- }
35
- .step-window {
36
- display: none;
37
- }
38
- .step-window.active {
39
- display: block;
40
- }
41
- h2 {
42
- margin-top: 0;
43
- color: var(--primary);
44
- font-size: 22px;
45
- text-align: center;
46
- text-transform: uppercase;
47
- letter-spacing: 1px;
48
- }
49
- .input-group {
50
- margin: 20px 0;
51
- }
52
- label {
53
- display: block;
54
- margin-bottom: 8px;
55
- font-weight: 600;
56
- font-size: 14px;
57
- }
58
- input[type="text"], input[type="email"], input[type="password"] {
59
- width: 100%;
60
- padding: 12px;
61
- border: 2px solid #ddd;
62
- border-radius: 6px;
63
- font-size: 16px;
64
- box-sizing: border-box;
65
- transition: border-color 0.3s;
66
- }
67
- input:focus {
68
- border-color: var(--primary);
69
- outline: none;
70
- }
71
- .btn {
72
- width: 100%;
73
- padding: 12px;
74
- background: var(--primary);
75
- color: white;
76
- border: none;
77
- border-radius: 6px;
78
- font-size: 16px;
79
- font-weight: bold;
80
- cursor: pointer;
81
- transition: background 0.3s;
82
- }
83
- .btn:hover {
84
- background: #0056b3;
85
- }
86
- .error-msg {
87
- color: var(--danger);
88
- font-size: 14px;
89
- margin-top: 8px;
90
- display: none;
91
- font-weight: 500;
92
- }
93
- .checkbox-group {
94
- display: flex;
95
- align-items: center;
96
- gap: 10px;
97
- margin: 20px 0;
98
- }
99
- .checkbox-group input {
100
- width: 20px;
101
- height: 20px;
102
- cursor: pointer;
103
- }
104
- #qrcode-canvas {
105
- display: block;
106
- margin: 15px auto;
107
- background: #eee;
108
- }
109
- .progress-bar {
110
- display: flex;
111
- justify-content: space-between;
112
- margin-bottom: 25px;
113
- font-size: 12px;
114
- color: #aaa;
115
- }
116
- .progress-step {
117
- font-weight: bold;
118
- }
119
- .progress-step.active {
120
- color: var(--primary);
121
- }
122
- </style>
123
- <!-- Подключаем официальный компилятор QR-кодов для генерации картинок из токенов -->
124
- <script src="https://cdn.jsdelivr.net/npm/qrcodejs/qrcode.min.js"></script>
125
- </head>
126
- <body>
127
-
128
- <div class="register-container">
129
-
130
- <!-- Шаг 1: Никнейм -->
131
- <div id="window-nik" class="step-window active">
132
- <h2> City-bank | Шаг 1</h2>
133
- <div class="progress-bar"><span class="progress-step active">Ник</span> <span>Почта</span> <span>Пароль</span> <span>2FA</span> <span>Профиль</span></div>
134
- <div class="input-group">
135
- <label for="reg-nik">ПРИДУМАЙТЕ НИК-НЕЙМ!</label>
136
- <input type="text" id="reg-nik" placeholder="Ваш игровой ник (логин)">
137
- <div id="err-nik" class="error-msg">Такой никнейм уже занят в системе!</div>
138
- </div>
139
- <button class="btn" onclick="validateNikStep()">Подтвердить никнейм</button>
140
- </div>
141
-
142
- <!-- Шаг 2: Электронная почта -->
143
- <div id="window-gmail" class="step-window">
144
- <h2> City-bank | Шаг 2</h2>
145
- <div class="progress-bar"><span>Ник</span> <span class="progress-step active">Почта</span> <span>Пароль</span> <span>2FA</span> <span>Профиль</span></div>
146
- <div class="input-group">
147
- <label for="reg-gmail">ВВЕДИТЕ ПОЧТУ GOOGLE!</label>
148
- <input type="email" id="reg-gmail" placeholder="example@gmail.com">
149
- <div id="err-gmail" class="error-msg">Этот адрес почты уже используется!</div>
150
- </div>
151
- <button class="btn" onclick="validateGmailStep()">Подтвердить почту</button>
152
- </div>
153
-
154
- <!-- Шаг 3: Безопасный пароль -->
155
- <div id="window-password" class="step-window">
156
- <h2> City-bank | Шаг 3</h2>
157
- <div class="progress-bar"><span>Ник</span> <span>Почта</span> <span class="progress-step active">Пароль</span> <span>2FA</span> <span>Профиль</span></div>
158
- <div class="input-group">
159
- <label for="reg-password">ПРИДУМАЙТЕ НАДЁЖНЫЙ ПАРОЛЬ!</label>
160
- <input type="password" id="reg-password" placeholder="Минимум 8 символов, заглавная, цифра" style="margin-bottom: 15px;">
161
- <label for="reg-password-confirm">ПОДТВЕРДИТЕ ПАРОЛЬ</label>
162
- <input type="password" id="reg-password-confirm" placeholder="Повторите ввод пароля">
163
- <div id="err-password" class="error-msg">Пароли не совпадают или не соответствуют требованиям безопасности!</div>
164
- </div>
165
- <button class="btn" onclick="validatePasswordStep()">Проверить надёжность</button>
166
- </div>
167
-
168
- <!-- Шаг 4: Активация 2FA -->
169
- <div id="window-2fa" class="step-window">
170
- <h2> City-bank | Шаг 4</h2>
171
- <div class="progress-bar"><span>Ник</span> <span>Почта</span> <span>Пароль</span> <span class="progress-step active">2FA</span> <span>Профиль</span></div>
172
- <p style="font-size: 14px; text-align: center;">Включение защиты двухфакторной аутентификации аккаунта.</p>
173
- <div class="checkbox-group">
174
- <input type="checkbox" id="reg-2fa-status">
175
- <label for="reg-2fa-status">Включить защиту 2FA при переводах средств?</label>
176
- </div>
177
-
178
- <!-- Скрытая интерактивная подформа 2FA -->
179
- <div id="2fa-verification-block" style="display: none; text-align: center;">
180
- <p style="font-size: 13px; color: #555;">Сканируйте QR-код приложением аутентификации и введите числовой токен:</p>
181
-
182
- <div id="qrcode-container" style="display: block; margin: 15px auto; width: 256px; height: 256px;"></div>
183
-
184
- <input type="text" id="reg-2fa-code" placeholder="000000" style="text-align: center; font-size: 20px; letter-spacing: 4px; margin-bottom: 10px;">
185
- <div id="err-2fa" class="error-msg">Неверный одноразовый код токена!</div>
186
- </div>
187
-
188
- <button class="btn" id="btn-2fa-next" onclick="validate2FaStep()">Продолжить</button>
189
- </div>
190
-
191
- <!-- Шаг 5: Персональные данные -->
192
- <div id="window-profile" class="step-window">
193
- <h2> City-bank | Шаг 5</h2>
194
- <div class="progress-bar"><span>Ник</span> <span>Почта</span> <span>Пароль</span> <span>2FA</span> <span class="progress-step active">Профиль</span></div>
195
- <div class="input-group">
196
- <label for="reg-realname">ВВЕДИТЕ ВАШЕ НАСТОЯЩЕЕ ИМЯ:</label>
197
- <input type="text" id="reg-realname" placeholder="Иван" style="margin-bottom: 15px;">
198
- <label for="reg-surname">ВВЕДИТЕ НАСТОЯЩУЮ ФАМИЛИЮ:</label>
199
- <input type="text" id="reg-surname" placeholder="Иванов">
200
- <div id="err-profile" class="error-msg">Поля не могут быть пустыми!</div>
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>City-bank | Регистрация нового аккаунта</title>
7
+ <style>
8
+ :root {
9
+ --primary: #007bff;
10
+ --success: #28a745;
11
+ --danger: #dc3545;
12
+ --bg: #f4f6f9;
13
+ --card: #ffffff;
14
+ --text: #333333;
15
+ }
16
+ body {
17
+ font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
18
+ background-color: var(--bg);
19
+ color: var(--text);
20
+ display: flex;
21
+ justify-content: center;
22
+ align-items: center;
23
+ height: 100vh;
24
+ margin: 0;
25
+ }
26
+ .register-container {
27
+ background: var(--card);
28
+ padding: 30px;
29
+ border-radius: 12px;
30
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
31
+ width: 100%;
32
+ max-width: 450px;
33
+ box-sizing: border-box;
34
+ }
35
+ .step-window {
36
+ display: none;
37
+ }
38
+ .step-window.active {
39
+ display: block;
40
+ }
41
+ h2 {
42
+ margin-top: 0;
43
+ color: var(--primary);
44
+ font-size: 22px;
45
+ text-align: center;
46
+ text-transform: uppercase;
47
+ letter-spacing: 1px;
48
+ }
49
+ .input-group {
50
+ margin: 20px 0;
51
+ }
52
+ label {
53
+ display: block;
54
+ margin-bottom: 8px;
55
+ font-weight: 600;
56
+ font-size: 14px;
57
+ }
58
+ input[type="text"],
59
+ input[type="email"],
60
+ input[type="password"] {
61
+ width: 100%;
62
+ padding: 12px;
63
+ border: 2px solid #ddd;
64
+ border-radius: 6px;
65
+ font-size: 16px;
66
+ box-sizing: border-box;
67
+ transition: border-color 0.3s;
68
+ }
69
+ input:focus {
70
+ border-color: var(--primary);
71
+ outline: none;
72
+ }
73
+ .btn {
74
+ width: 100%;
75
+ padding: 12px;
76
+ background: var(--primary);
77
+ color: white;
78
+ border: none;
79
+ border-radius: 6px;
80
+ font-size: 16px;
81
+ font-weight: bold;
82
+ cursor: pointer;
83
+ transition: background 0.3s;
84
+ }
85
+ .btn:hover {
86
+ background: #0056b3;
87
+ }
88
+ .error-msg {
89
+ color: var(--danger);
90
+ font-size: 14px;
91
+ margin-top: 8px;
92
+ display: none;
93
+ font-weight: 500;
94
+ }
95
+ .checkbox-group {
96
+ display: flex;
97
+ align-items: center;
98
+ gap: 10px;
99
+ margin: 20px 0;
100
+ }
101
+ .checkbox-group input {
102
+ width: 20px;
103
+ height: 20px;
104
+ cursor: pointer;
105
+ }
106
+ #qrcode-canvas {
107
+ display: block;
108
+ margin: 15px auto;
109
+ background: #eee;
110
+ }
111
+ .progress-bar {
112
+ display: flex;
113
+ justify-content: space-between;
114
+ margin-bottom: 25px;
115
+ font-size: 12px;
116
+ color: #aaa;
117
+ }
118
+ .progress-step {
119
+ font-weight: bold;
120
+ }
121
+ .progress-step.active {
122
+ color: var(--primary);
123
+ }
124
+ </style>
125
+ <!-- Подключаем официальный компилятор QR-кодов для генерации картинок из токенов -->
126
+ <script src="https://cdn.jsdelivr.net/npm/qrcodejs/qrcode.min.js"></script>
127
+ </head>
128
+ <body>
129
+ <div class="register-container">
130
+ <!-- Шаг 1: Никнейм -->
131
+ <div id="window-nik" class="step-window active">
132
+ <h2>City-bank | Шаг 1</h2>
133
+ <div class="progress-bar">
134
+ <span class="progress-step active">Ник</span> <span>Почта</span>
135
+ <span>Пароль</span> <span>2FA</span> <span>Профиль</span>
136
+ </div>
137
+ <div class="input-group">
138
+ <label for="reg-nik">ПРИДУМАЙТЕ НИК-НЕЙМ!</label>
139
+ <input
140
+ type="text"
141
+ id="reg-nik"
142
+ placeholder="Ваш игровой ник (логин)"
143
+ />
144
+ <div id="err-nik" class="error-msg">
145
+ Такой никнейм уже занят в системе!
146
+ </div>
147
+ </div>
148
+ <button class="btn" onclick="validateNikStep()">
149
+ Подтвердить никнейм
150
+ </button>
151
+ </div>
152
+
153
+ <!-- Шаг 2: Электронная почта -->
154
+ <div id="window-gmail" class="step-window">
155
+ <h2>City-bank | Шаг 2</h2>
156
+ <div class="progress-bar">
157
+ <span>Ник</span> <span class="progress-step active">Почта</span>
158
+ <span>Пароль</span> <span>2FA</span> <span>Профиль</span>
159
+ </div>
160
+ <div class="input-group">
161
+ <label for="reg-gmail">ВВЕДИТЕ ПОЧТУ GOOGLE!</label>
162
+ <input type="email" id="reg-gmail" placeholder="example@gmail.com" />
163
+ <div id="err-gmail" class="error-msg">
164
+ Этот адрес почты уже используется!
165
+ </div>
166
+ </div>
167
+ <button class="btn" onclick="validateGmailStep()">
168
+ Подтвердить почту
169
+ </button>
170
+ </div>
171
+
172
+ <!-- Шаг 3: Безопасный пароль -->
173
+ <div id="window-password" class="step-window">
174
+ <h2>City-bank | Шаг 3</h2>
175
+ <div class="progress-bar">
176
+ <span>Ник</span> <span>Почта</span>
177
+ <span class="progress-step active">Пароль</span> <span>2FA</span>
178
+ <span>Профиль</span>
179
+ </div>
180
+ <div class="input-group">
181
+ <label for="reg-password">ПРИДУМАЙТЕ НАДЁЖНЫЙ ПАРОЛЬ!</label>
182
+ <input
183
+ type="password"
184
+ id="reg-password"
185
+ placeholder="Минимум 8 символов, заглавная, цифра"
186
+ style="margin-bottom: 15px"
187
+ />
188
+ <label for="reg-password-confirm">ПОДТВЕРДИТЕ ПАРОЛЬ</label>
189
+ <input
190
+ type="password"
191
+ id="reg-password-confirm"
192
+ placeholder="Повторите ввод пароля"
193
+ />
194
+ <div id="err-password" class="error-msg">
195
+ Пароли не совпадают или не соответствуют требованиям безопасности!
196
+ </div>
197
+ </div>
198
+ <button class="btn" onclick="validatePasswordStep()">
199
+ Проверить надёжность
200
+ </button>
201
+ </div>
202
+
203
+ <!-- Шаг 4: Активация 2FA -->
204
+ <div id="window-2fa" class="step-window">
205
+ <h2>City-bank | Шаг 4</h2>
206
+ <div class="progress-bar">
207
+ <span>Ник</span> <span>Почта</span> <span>Пароль</span>
208
+ <span class="progress-step active">2FA</span> <span>Профиль</span>
209
+ </div>
210
+ <p style="font-size: 14px; text-align: center">
211
+ Включение защиты двухфакторной аутентификации аккаунта.
212
+ </p>
213
+ <div class="checkbox-group">
214
+ <input type="checkbox" id="reg-2fa-status" />
215
+ <label for="reg-2fa-status"
216
+ >Включить защиту 2FA при переводах средств?</label
217
+ >
218
+ </div>
219
+
220
+ <!-- Скрытая интерактивная подформа 2FA -->
221
+ <div
222
+ id="2fa-verification-block"
223
+ style="display: none; text-align: center"
224
+ >
225
+ <p style="font-size: 13px; color: #555">
226
+ Сканируйте QR-код приложением аутентификации и введите числовой
227
+ токен:
228
+ </p>
229
+
230
+ <div
231
+ id="qrcode-container"
232
+ style="
233
+ display: block;
234
+ margin: 15px auto;
235
+ width: 256px;
236
+ height: 256px;
237
+ "
238
+ ></div>
239
+
240
+ <input
241
+ type="text"
242
+ id="reg-2fa-code"
243
+ placeholder="000000"
244
+ style="
245
+ text-align: center;
246
+ font-size: 20px;
247
+ letter-spacing: 4px;
248
+ margin-bottom: 10px;
249
+ "
250
+ />
251
+ <div id="err-2fa" class="error-msg">
252
+ Неверный одноразовый код токена!
253
+ </div>
254
+ </div>
255
+
256
+ <button class="btn" id="btn-2fa-next" onclick="validate2FaStep()">
257
+ Продолжить
258
+ </button>
259
+ </div>
260
+
261
+ <!-- Шаг 5: Персональные данные -->
262
+ <div id="window-profile" class="step-window">
263
+ <h2>City-bank | Шаг 5</h2>
264
+ <div class="progress-bar">
265
+ <span>Ник</span> <span>Почта</span> <span>Пароль</span>
266
+ <span>2FA</span> <span class="progress-step active">Профиль</span>
267
+ </div>
268
+ <div class="input-group">
269
+ <label for="reg-realname">ВВЕДИТЕ ВАШЕ НАСТОЯЩЕЕ ИМЯ:</label>
270
+ <input
271
+ type="text"
272
+ id="reg-realname"
273
+ placeholder="Иван"
274
+ style="margin-bottom: 15px"
275
+ />
276
+ <label for="reg-surname">ВВЕДИТЕ НАСТОЯЩУЮ ФАМИЛИЮ:</label>
277
+ <input type="text" id="reg-surname" placeholder="Иванов" />
278
+ <div id="err-profile" class="error-msg">
279
+ Поля не могут быть пустыми!
280
+ </div>
281
+ </div>
282
+ <button class="btn" onclick="submitFullRegistration()">
283
+ Завершить регистрацию
284
+ </button>
285
+ </div>
201
286
  </div>
202
- <button class="btn" onclick="submitFullRegistration()">Завершить регистрацию</button>
203
- </div>
204
-
205
- </div>
206
-
207
- <script>
208
- // Глобальный объект для аккумулирования данных по мере прохождения окон
209
- const registrationPayload = {
210
- name: "",
211
- gmail: "",
212
- password: "",
213
- enable2FA: false,
214
- userName: "",
215
- userSurname: ""
216
- };
217
-
218
- // Технический секретный токен 2FA, генерируемый сервером «на лету»
219
- let temporary2FaToken = "";
220
-
221
- // Функция для переключения окон (шагов)
222
- function changeWindow(currentId, nextId) {
223
- document.getElementById(currentId).classList.remove('active');
224
- document.getElementById(nextId).classList.add('active');
225
- }
226
-
227
- // --- АСИНХРОННАЯ ВАЛИДАЦИЯ ШАГОВ ЧЕРЕЗ API БЭКЕНДА ---
228
-
229
- // 👑 ШАГ 1: Валидация никнейма
230
- async function validateNikStep() {
231
- const nikInput = document.getElementById('reg-nik').value.trim();
232
- const errBlock = document.getElementById('err-nik');
233
- errBlock.style.display = 'none';
234
287
 
235
- if (nikInput.length < 3) {
236
- errBlock.innerText = "Никнейм должен содержать минимум 3 символа!";
237
- errBlock.style.display = 'block';
238
- return;
239
- }
240
-
241
- try {
242
- const response = await fetch(`/api/bank/users/check-nik?name=${encodeURIComponent(nikInput)}`);
243
- const result = await response.json();
244
-
245
- if (result.status === true && result.statusCode === 1) {
246
- registrationPayload.name = nikInput;
247
- changeWindow('window-nik', 'window-gmail');
248
- } else {
249
- errBlock.innerText = result.message || "Этот никнейм уже занят!";
250
- errBlock.style.display = 'block';
288
+ <script>
289
+ const registrationPayload = {
290
+ name: "",
291
+ gmail: "",
292
+ password: "",
293
+ enable2FA: false,
294
+ userName: "",
295
+ userSurname: "",
296
+ };
297
+ let temporary2FaToken = "";
298
+ function changeWindow(currentId, nextId) {
299
+ document.getElementById(currentId).classList.remove("active");
300
+ document.getElementById(nextId).classList.add("active");
251
301
  }
252
- } catch (e) {
253
- console.error("Ошибка верификации никнейма:", e);
254
- }
255
- }
256
-
257
- // 👑 ШАГ 2: Валидация электронной почты
258
- async function validateGmailStep() {
259
- const gmailInput = document.getElementById('reg-gmail').value.trim();
260
- const errBlock = document.getElementById('err-gmail');
261
- errBlock.style.display = 'none';
262
-
263
- if (!gmailInput.includes('@')) {
264
- errBlock.innerText = "Введите корректный адрес почты!";
265
- errBlock.style.display = 'block';
266
- return;
267
- }
268
-
269
- try {
270
- const response = await fetch(`/api/bank/users/check-gmail?gmail=${encodeURIComponent(gmailInput)}`);
271
- const result = await response.json();
272
-
273
- if (result.status === true && result.statusCode === 1) {
274
- registrationPayload.gmail = gmailInput;
275
- changeWindow('window-gmail', 'window-password');
276
- } else {
277
- errBlock.innerText = result.message || "Почта уже используется!";
278
- errBlock.style.display = 'block';
302
+ async function validateNikStep() {
303
+ const nikInput = document.getElementById("reg-nik").value.trim();
304
+ const errBlock = document.getElementById("err-nik");
305
+ errBlock.style.display = "none";
306
+ if (nikInput.length < 3) {
307
+ errBlock.innerText = "Никнейм должен содержать минимум 3 символа!";
308
+ errBlock.style.display = "block";
309
+ return;
310
+ }
311
+ try {
312
+ const response = await fetch(
313
+ `/api/bank/users/check-nik?name=${encodeURIComponent(nikInput)}`,
314
+ {
315
+ headers: {
316
+ "Bypass-Tunnel-Reminder": "true",
317
+ },
318
+ },
319
+ );
320
+ const result = await response.json();
321
+ if (result.status === true && result.statusCode === 1) {
322
+ registrationPayload.name = nikInput;
323
+ changeWindow("window-nik", "window-gmail");
324
+ } else {
325
+ errBlock.innerText = result.message || "Этот никнейм уже занят!";
326
+ errBlock.style.display = "block";
327
+ }
328
+ } catch (e) {
329
+ console.error("Ошибка верификации никнейма:", e);
330
+ }
279
331
  }
280
- } catch (e) {
281
- console.error("Ошибка верификации почты:", e);
282
- }
283
- }
284
-
285
- // 👑 ШАГ 3: Валидация надежности пароля
286
- function validatePasswordStep() {
287
- const pass = document.getElementById('reg-password').value;
288
- const confirm = document.getElementById('reg-password-confirm').value;
289
- const errBlock = document.getElementById('err-password');
290
- errBlock.style.display = 'none';
291
-
292
- if (pass !== confirm) {
293
- errBlock.innerText = "Пароли не совпадают!";
294
- errBlock.style.display = 'block';
295
- return;
296
- }
297
-
298
- // Требование ТЗ безопасности (-22)
299
- if (pass.length < 8 || !/[A-Z]/.test(pass) || !/[0-9]/.test(pass)) {
300
- errBlock.innerText = "Пароль слишком простой (нужна заглавная буква, цифра, от 8 знаков)!";
301
- errBlock.style.display = 'block';
302
- return;
303
- }
304
-
305
- registrationPayload.password = pass;
306
- changeWindow('window-password', 'window-2fa');
307
- }
308
-
309
- // 👑 ШАГ 4: Интерактивный трекер 2FA под конструктор библиотеки qrcodejs
310
- document.getElementById('reg-2fa-status').addEventListener('change', async function(e) {
311
- const verificationBlock = document.getElementById('2fa-verification-block');
312
- const nextBtn = document.getElementById('btn-2fa-next');
313
- const qrContainer = document.getElementById('qrcode-container');
314
-
315
- if (e.target.checked) {
316
- verificationBlock.style.display = 'block';
317
- nextBtn.innerText = "Проверить токен 2FA";
318
-
319
- try {
320
- const response = await fetch('/api/bank/users/generate-temporary-2fa', {
321
- method: 'POST',
322
- headers: {'Content-Type': 'application/json'},
323
- body: JSON.stringify({ name: registrationPayload.name || document.getElementById('reg-nik').value.trim() })
324
- });
325
- const result = await response.json();
326
-
327
- if (result.status) {
328
- temporary2FaToken = result.secret;
329
-
330
- // Очищаем контейнер от старых генераций
331
- if (qrContainer) qrContainer.innerHTML = "";
332
-
333
- // Конструктор генерации QR-кода библиотеки davidshimjs/qrcodejs
334
- new QRCode(qrContainer, {
335
- text: result.otpauthUrl,
336
- width: 256,
337
- height: 256,
338
- colorDark : "#000000",
339
- colorLight : "#ffffff",
340
- correctLevel : QRCode.CorrectLevel.H
341
- });
332
+ async function validateGmailStep() {
333
+ const gmailInput = document.getElementById("reg-gmail").value.trim();
334
+ const errBlock = document.getElementById("err-gmail");
335
+ errBlock.style.display = "none";
336
+ if (!gmailInput.includes("@")) {
337
+ errBlock.innerText = "Введите корректный адрес почты!";
338
+ errBlock.style.display = "block";
339
+ return;
340
+ }
341
+ try {
342
+ const response = await fetch(
343
+ `/api/bank/users/check-gmail?gmail=${encodeURIComponent(gmailInput)}`,
344
+ {
345
+ headers: {
346
+ "Bypass-Tunnel-Reminder": "true",
347
+ },
348
+ },
349
+ );
350
+ const result = await response.json();
351
+
352
+ if (result.status === true && result.statusCode === 1) {
353
+ registrationPayload.gmail = gmailInput;
354
+ changeWindow("window-gmail", "window-password");
355
+ } else {
356
+ errBlock.innerText = result.message || "Почта уже используется!";
357
+ errBlock.style.display = "block";
358
+ }
359
+ } catch (e) {
360
+ console.error("Ошибка верификации почты:", e);
342
361
  }
343
- } catch(err) {
344
- console.error("Ошибка запроса 2FA секретов бэкенда:", err);
345
362
  }
346
- } else {
347
- verificationBlock.style.display = 'none';
348
- nextBtn.innerText = "Продолжить";
349
- if (qrContainer) qrContainer.innerHTML = "";
350
- }
351
- });
352
-
353
- // Валидация 6-значного числового токена из приложения
354
- // Валидация 6-значного числового токена из приложения
355
- async function validate2FaStep() {
356
- const is2FaEnabled = document.getElementById('reg-2fa-status').checked;
357
- const errBlock = document.getElementById('err-2fa');
358
- if (errBlock) errBlock.style.display = 'none';
359
-
360
- if (!is2FaEnabled) {
361
- registrationPayload.enable2FA = false;
362
- changeWindow('window-2fa', 'window-profile');
363
- return;
364
- }
365
-
366
- // 🔑 Гарантированно считываем 6 цифр, которые ввел пользователь в инпут
367
- const codeInputElement = document.getElementById('reg-2fa-code');
368
- const codeInput = codeInputElement ? codeInputElement.value.trim() : "";
369
-
370
- if (!codeInput) {
371
- if (errBlock) {
372
- errBlock.innerText = "Пожалуйста, введите 6-значный код!";
373
- errBlock.style.display = 'block';
363
+ function validatePasswordStep() {
364
+ const pass = document.getElementById("reg-password").value;
365
+ const confirm = document.getElementById("reg-password-confirm").value;
366
+ const errBlock = document.getElementById("err-password");
367
+ errBlock.style.display = "none";
368
+ if (pass !== confirm) {
369
+ errBlock.innerText = "Пароли не совпадают!";
370
+ errBlock.style.display = "block";
371
+ return;
372
+ }
373
+ if (pass.length < 8 || !/[A-Z]/.test(pass) || !/[0-9]/.test(pass)) {
374
+ errBlock.innerText =
375
+ "Пароль слишком простой (нужна заглавная буква, цифра, от 8 знаков)!";
376
+ errBlock.style.display = "block";
377
+ return;
378
+ }
379
+ registrationPayload.password = pass;
380
+ changeWindow("window-password", "window-2fa");
374
381
  }
375
- return;
376
- }
377
-
378
- try {
379
- const response = await fetch('/api/bank/users/verify-temporary-2fa', {
380
- method: 'POST',
381
- headers: {'Content-Type': 'application/json'},
382
- body: JSON.stringify({
383
- secret: temporary2FaToken, // Передаем сгенерированный сервером секрет
384
- code: codeInput // Передаем 6 цифр из инпута
385
- })
386
- });
387
-
388
- const result = await response.json();
389
-
390
- if (result.status === true) {
391
- registrationPayload.enable2FA = true;
392
- registrationPayload.twoFaSecret = temporary2FaToken;
393
- changeWindow('window-2fa', 'window-profile'); // Перекидываем на Шаг 5 (Данные профиля)
394
- } else {
395
- if (errBlock) {
396
- errBlock.innerText = result.message || "Неверный одноразовый код токена!";
397
- errBlock.style.display = 'block';
382
+ document
383
+ .getElementById("reg-2fa-status")
384
+ .addEventListener("change", async function (e) {
385
+ const verificationBlock = document.getElementById(
386
+ "2fa-verification-block",
387
+ );
388
+ const nextBtn = document.getElementById("btn-2fa-next");
389
+ const qrContainer = document.getElementById("qrcode-container");
390
+
391
+ if (e.target.checked) {
392
+ verificationBlock.style.display = "block";
393
+ nextBtn.innerText = "Проверить токен 2FA";
394
+
395
+ try {
396
+ const response = await fetch(
397
+ "/api/bank/users/generate-temporary-2fa",
398
+ {
399
+ method: "POST",
400
+ headers: {
401
+ "Content-Type": "application/json",
402
+ "Bypass-Tunnel-Reminder": "true",
403
+ },
404
+ body: JSON.stringify({
405
+ name:
406
+ registrationPayload.name ||
407
+ document.getElementById("reg-nik").value.trim(),
408
+ }),
409
+ },
410
+ );
411
+ const result = await response.json();
412
+
413
+ if (result.status) {
414
+ temporary2FaToken = result.secret;
415
+ if (qrContainer) qrContainer.innerHTML = "";
416
+ new QRCode(qrContainer, {
417
+ text: result.otpauthUrl,
418
+ width: 256,
419
+ height: 256,
420
+ colorDark: "#000000",
421
+ colorLight: "#ffffff",
422
+ correctLevel: QRCode.CorrectLevel.H,
423
+ });
424
+ }
425
+ } catch (err) {
426
+ console.error("Ошибка запроса 2FA секретов бэкенда:", err);
427
+ }
428
+ } else {
429
+ verificationBlock.style.display = "none";
430
+ nextBtn.innerText = "Продолжить";
431
+ if (qrContainer) qrContainer.innerHTML = "";
432
+ }
433
+ });
434
+ async function validate2FaStep() {
435
+ const is2FaEnabled = document.getElementById("reg-2fa-status").checked;
436
+ const errBlock = document.getElementById("err-2fa");
437
+ if (errBlock) errBlock.style.display = "none";
438
+ if (!is2FaEnabled) {
439
+ registrationPayload.enable2FA = false;
440
+ changeWindow("window-2fa", "window-profile");
441
+ return;
442
+ }
443
+ const codeInputElement = document.getElementById("reg-2fa-code");
444
+ const codeInput = codeInputElement ? codeInputElement.value.trim() : "";
445
+ if (!codeInput) {
446
+ if (errBlock) {
447
+ errBlock.innerText = "Пожалуйста, введите 6-значный код!";
448
+ errBlock.style.display = "block";
449
+ }
450
+ return;
451
+ }
452
+ try {
453
+ const response = await fetch("/api/bank/users/verify-temporary-2fa", {
454
+ method: "POST",
455
+ headers: {
456
+ "Content-Type": "application/json",
457
+ "Bypass-Tunnel-Reminder": "true",
458
+ },
459
+ body: JSON.stringify({
460
+ secret: temporary2FaToken,
461
+ code: codeInput,
462
+ }),
463
+ });
464
+ const result = await response.json();
465
+ if (result.status === true) {
466
+ registrationPayload.enable2FA = true;
467
+ registrationPayload.twoFaSecret = temporary2FaToken;
468
+ changeWindow("window-2fa", "window-profile");
469
+ } else {
470
+ if (errBlock) {
471
+ errBlock.innerText =
472
+ result.message || "Неверный одноразовый код токена!";
473
+ errBlock.style.display = "block";
474
+ }
475
+ }
476
+ } catch (e) {
477
+ console.error("Ошибка валидации временного 2FA кода:", e);
398
478
  }
399
479
  }
400
- } catch (e) {
401
- console.error("Ошибка валидации временного 2FA кода:", e);
402
- }
403
- }
404
-
405
- // 👑 ШАГ 5: Финишная регистрация профиля
406
- async function submitFullRegistration() {
407
- const realName = document.getElementById('reg-realname').value.trim();
408
- const surname = document.getElementById('reg-surname').value.trim();
409
- const errBlock = document.getElementById('err-profile');
410
- errBlock.style.display = 'none';
411
-
412
- if (!realName || !surname) {
413
- errBlock.style.display = 'block';
414
- return;
415
- }
416
-
417
- registrationPayload.userName = realName;
418
- registrationPayload.userSurname = surname;
419
-
420
- try {
421
- const response = await fetch('/api/bank/users/register', {
422
- method: 'POST',
423
- headers: {'Content-Type': 'application/json'},
424
- body: JSON.stringify(registrationPayload)
425
- });
426
- const result = await response.json();
427
-
428
- if (result.status === true && result.statusCode === 1) {
429
- alert("Регистрация успешно завершена! Проверьте вашу почту для получения паролей и токенов.");
430
- window.location.href = '/bank/login.html';
431
- } else {
432
- errBlock.innerText = result.message || "Внутренняя ошибка сервера";
433
- errBlock.style.display = 'block';
480
+ async function submitFullRegistration() {
481
+ const realName = document.getElementById("reg-realname").value.trim();
482
+ const surname = document.getElementById("reg-surname").value.trim();
483
+ const errBlock = document.getElementById("err-profile");
484
+ errBlock.style.display = "none";
485
+ if (!realName || !surname) {
486
+ errBlock.style.display = "block";
487
+ return;
488
+ }
489
+ registrationPayload.userName = realName;
490
+ registrationPayload.userSurname = surname;
491
+ try {
492
+ const response = await fetch("/api/bank/users/register", {
493
+ method: "POST",
494
+ headers: {
495
+ "Content-Type": "application/json",
496
+ "Bypass-Tunnel-Reminder": "true",
497
+ },
498
+ body: JSON.stringify(registrationPayload),
499
+ });
500
+ const result = await response.json();
501
+ if (result.status === true && result.statusCode === 1) {
502
+ alert(
503
+ "Регистрация успешно завершена! Проверьте вашу почту для получения паролей и токенов.",
504
+ );
505
+ window.location.href = "/bank/login.html";
506
+ } else {
507
+ errBlock.innerText = result.message || "Внутренняя ошибка сервера";
508
+ errBlock.style.display = "block";
509
+ }
510
+ } catch (e) {
511
+ console.error("Критическая ошибка отправки формы:", e);
512
+ }
434
513
  }
435
- } catch (e) {
436
- console.error("Критическая ошибка отправки формы:", e);
437
- }
438
- }
439
-
440
- </script>
441
- </body>
442
- </html>
514
+ </script>
515
+ </body>
516
+ </html>