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 +1 -1
- package/public/login.html +129 -66
- package/public/register.html +505 -431
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sequelize-game-bank",
|
|
3
|
-
"version": "5.0.
|
|
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
|
-
<!
|
|
1
|
+
<!doctype html>
|
|
2
2
|
<html lang="ru">
|
|
3
|
-
<head>
|
|
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
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
document.getElementById(
|
|
60
|
-
|
|
61
|
-
errBlock
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
</script>
|
|
71
|
-
</body>
|
|
133
|
+
</script>
|
|
134
|
+
</body>
|
|
72
135
|
</html>
|
package/public/register.html
CHANGED
|
@@ -1,442 +1,516 @@
|
|
|
1
|
-
<!
|
|
1
|
+
<!doctype html>
|
|
2
2
|
<html lang="ru">
|
|
3
|
-
<head>
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
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
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
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
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
</script>
|
|
441
|
-
</body>
|
|
442
|
-
</html>
|
|
514
|
+
</script>
|
|
515
|
+
</body>
|
|
516
|
+
</html>
|