@joystick.js/db-canary 0.0.0-canary.2295 → 0.0.0-canary.2296
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/dist/client/index.js +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/lib/operations/admin.js +1 -1
- package/dist/server/lib/user_auth_manager.js +1 -0
- package/package.json +2 -2
- package/src/client/index.js +21 -7
- package/src/server/index.js +60 -19
- package/src/server/lib/operations/admin.js +91 -2
- package/src/server/lib/user_auth_manager.js +745 -0
- package/tests/client/index.test.js +468 -69
- package/tests/server/index.test.js +210 -43
- package/tests/server/integration/authentication_integration.test.js +65 -33
- package/tests/server/integration/development_mode_authentication.test.js +21 -7
- package/tests/server/integration/production_safety_integration.test.js +24 -34
- package/tests/server/integration/replication_integration.test.js +17 -25
- package/tests/server/lib/operations/admin.test.js +39 -5
- package/tests/server/lib/user_auth_manager.test.js +525 -0
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
import test from 'ava';
|
|
2
|
+
import {
|
|
3
|
+
setup_initial_admin,
|
|
4
|
+
create_user,
|
|
5
|
+
get_user,
|
|
6
|
+
update_user,
|
|
7
|
+
delete_user,
|
|
8
|
+
list_users,
|
|
9
|
+
verify_credentials,
|
|
10
|
+
reset_user_password,
|
|
11
|
+
get_auth_stats,
|
|
12
|
+
reset_auth_state,
|
|
13
|
+
set_storage_engine
|
|
14
|
+
} from '../../../src/server/lib/user_auth_manager.js';
|
|
15
|
+
import { initialize_database } from '../../../src/server/lib/query_engine.js';
|
|
16
|
+
import { existsSync, unlinkSync } from 'fs';
|
|
17
|
+
import { mkdirSync } from 'fs';
|
|
18
|
+
|
|
19
|
+
let storage_engine;
|
|
20
|
+
|
|
21
|
+
test.beforeEach(async () => {
|
|
22
|
+
await reset_auth_state();
|
|
23
|
+
|
|
24
|
+
// Clean up any existing database files
|
|
25
|
+
try {
|
|
26
|
+
if (existsSync('./.joystick/data/joystickdb_test/data.mdb')) {
|
|
27
|
+
unlinkSync('./.joystick/data/joystickdb_test/data.mdb');
|
|
28
|
+
}
|
|
29
|
+
if (existsSync('./.joystick/data/joystickdb_test/lock.mdb')) {
|
|
30
|
+
unlinkSync('./.joystick/data/joystickdb_test/lock.mdb');
|
|
31
|
+
}
|
|
32
|
+
} catch (error) {
|
|
33
|
+
// Ignore cleanup errors
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Create test directory if it doesn't exist
|
|
37
|
+
try {
|
|
38
|
+
mkdirSync('./.joystick/data/joystickdb_test', { recursive: true });
|
|
39
|
+
} catch (error) {
|
|
40
|
+
// Directory might already exist
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Create real storage engine for testing
|
|
44
|
+
storage_engine = initialize_database('./.joystick/data/joystickdb_test');
|
|
45
|
+
set_storage_engine(storage_engine);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test.afterEach(async () => {
|
|
49
|
+
// Clean up users from admin database
|
|
50
|
+
try {
|
|
51
|
+
const users_result = await list_users();
|
|
52
|
+
if (users_result.success && users_result.users && users_result.users.length > 0) {
|
|
53
|
+
for (const user of users_result.users) {
|
|
54
|
+
await delete_user(user.username);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
} catch (error) {
|
|
58
|
+
// Ignore cleanup errors
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
await reset_auth_state();
|
|
62
|
+
|
|
63
|
+
// NOTE: Don't close database between tests to avoid transaction errors
|
|
64
|
+
// The database will be closed when the test process exits
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Initial Admin Setup Tests
|
|
68
|
+
test('setup_initial_admin creates first admin user successfully', async (t) => {
|
|
69
|
+
const result = await setup_initial_admin('admin', 'admin123', 'admin@test.com');
|
|
70
|
+
|
|
71
|
+
t.is(result.success, true);
|
|
72
|
+
t.truthy(result.admin_user);
|
|
73
|
+
t.is(result.admin_user.user.username, 'admin');
|
|
74
|
+
t.is(result.admin_user.user.role, 'admin');
|
|
75
|
+
t.is(result.admin_user.user.active, true);
|
|
76
|
+
t.truthy(result.admin_user.user.created_at);
|
|
77
|
+
t.falsy(result.admin_user.user.password_hash); // Should not return password hash
|
|
78
|
+
|
|
79
|
+
// Verify user can be retrieved
|
|
80
|
+
const user_result = await get_user('admin');
|
|
81
|
+
t.is(user_result.success, true);
|
|
82
|
+
t.is(user_result.user.username, 'admin');
|
|
83
|
+
t.is(user_result.user.role, 'admin');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('setup_initial_admin prevents duplicate admin creation', async (t) => {
|
|
87
|
+
// Create initial admin
|
|
88
|
+
await setup_initial_admin('admin', 'admin123', 'admin@test.com');
|
|
89
|
+
|
|
90
|
+
// Try to create another admin
|
|
91
|
+
const result = await setup_initial_admin('admin2', 'admin456', 'admin2@test.com');
|
|
92
|
+
|
|
93
|
+
t.is(result.success, false);
|
|
94
|
+
t.truthy(result.error.includes('Initial admin user already exists'));
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('setup_initial_admin validates required fields', async (t) => {
|
|
98
|
+
// Missing username
|
|
99
|
+
let result = await setup_initial_admin('', 'admin123', 'admin@test.com');
|
|
100
|
+
t.is(result.success, false);
|
|
101
|
+
t.truthy(result.error.includes('Username, password, and email are required'));
|
|
102
|
+
|
|
103
|
+
// Missing password
|
|
104
|
+
result = await setup_initial_admin('admin', '', 'admin@test.com');
|
|
105
|
+
t.is(result.success, false);
|
|
106
|
+
t.truthy(result.error.includes('Username, password, and email are required'));
|
|
107
|
+
|
|
108
|
+
// Missing email
|
|
109
|
+
result = await setup_initial_admin('admin', 'admin123', '');
|
|
110
|
+
t.is(result.success, false);
|
|
111
|
+
t.truthy(result.error.includes('Username, password, and email are required'));
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test('setup_initial_admin validates password strength', async (t) => {
|
|
115
|
+
const result = await setup_initial_admin('admin', '123', 'admin@test.com');
|
|
116
|
+
|
|
117
|
+
t.is(result.success, false);
|
|
118
|
+
t.truthy(result.error.includes('Password must be at least 6 characters long'));
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('setup_initial_admin validates email format', async (t) => {
|
|
122
|
+
const result = await setup_initial_admin('admin', 'admin123', 'invalid-email');
|
|
123
|
+
|
|
124
|
+
t.is(result.success, false);
|
|
125
|
+
t.truthy(result.error.includes('Invalid email format'));
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// User Creation Tests
|
|
129
|
+
test('create_user creates regular user successfully', async (t) => {
|
|
130
|
+
// Setup admin first
|
|
131
|
+
await setup_initial_admin('admin', 'admin123', 'admin@test.com');
|
|
132
|
+
|
|
133
|
+
const result = await create_user('user1', 'user12345678', 'user1@test.com', 'user');
|
|
134
|
+
|
|
135
|
+
t.is(result.success, true);
|
|
136
|
+
t.is(result.user.username, 'user1');
|
|
137
|
+
t.is(result.user.role, 'user');
|
|
138
|
+
t.is(result.user.active, true);
|
|
139
|
+
t.falsy(result.user.password_hash); // Should not return password hash
|
|
140
|
+
|
|
141
|
+
// Verify user can be retrieved
|
|
142
|
+
const user_result = await get_user('user1');
|
|
143
|
+
t.is(user_result.success, true);
|
|
144
|
+
t.is(user_result.user.username, 'user1');
|
|
145
|
+
t.is(user_result.user.role, 'user');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('create_user prevents duplicate usernames', async (t) => {
|
|
149
|
+
await setup_initial_admin('admin', 'admin123', 'admin@test.com');
|
|
150
|
+
await create_user('user1', 'user12345678', 'user1@test.com');
|
|
151
|
+
|
|
152
|
+
const result = await create_user('user1', 'user456789', 'user1b@test.com');
|
|
153
|
+
|
|
154
|
+
t.is(result.success, false);
|
|
155
|
+
t.truthy(result.error.includes('User already exists'));
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test('create_user validates input fields', async (t) => {
|
|
159
|
+
await setup_initial_admin('admin', 'admin123', 'admin@test.com');
|
|
160
|
+
|
|
161
|
+
// Missing username
|
|
162
|
+
let result = await create_user('', 'user12345678', 'user@test.com');
|
|
163
|
+
t.is(result.success, false);
|
|
164
|
+
t.truthy(result.error && (result.error.includes('Username is required') || result.error.includes('Username, password, and email are required') || result.error.includes('required')));
|
|
165
|
+
|
|
166
|
+
// Invalid role
|
|
167
|
+
result = await create_user('user1', 'user12345678', 'user@test.com', 'invalid');
|
|
168
|
+
t.is(result.success, false);
|
|
169
|
+
t.truthy(result.error && (result.error.includes('Role must be either "admin" or "user"') || result.error.includes('invalid role') || result.error.includes('Role')));
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// User Retrieval Tests
|
|
173
|
+
test('get_user retrieves existing user', async (t) => {
|
|
174
|
+
await setup_initial_admin('admin', 'admin123', 'admin@test.com');
|
|
175
|
+
await create_user('user1', 'user12345678', 'user1@test.com');
|
|
176
|
+
|
|
177
|
+
const result = await get_user('user1');
|
|
178
|
+
|
|
179
|
+
t.is(result.success, true);
|
|
180
|
+
t.is(result.user.username, 'user1');
|
|
181
|
+
t.is(result.user.role, 'user');
|
|
182
|
+
t.is(result.user.active, true);
|
|
183
|
+
t.falsy(result.user.password_hash); // Should not return password hash
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test('get_user returns error for non-existent user', async (t) => {
|
|
187
|
+
const result = await get_user('nonexistent');
|
|
188
|
+
|
|
189
|
+
t.is(result.success, false);
|
|
190
|
+
t.truthy(result.error.includes('User not found'));
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// User Update Tests
|
|
194
|
+
test('update_user updates user fields successfully', async (t) => {
|
|
195
|
+
await setup_initial_admin('admin', 'admin123', 'admin@test.com');
|
|
196
|
+
await create_user('user1', 'user123', 'user1@test.com');
|
|
197
|
+
|
|
198
|
+
const updates = {
|
|
199
|
+
email: 'user1_new@test.com',
|
|
200
|
+
role: 'admin',
|
|
201
|
+
active: false
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const result = await update_user('user1', updates);
|
|
205
|
+
|
|
206
|
+
t.is(result.success, true);
|
|
207
|
+
t.truthy(result.message.includes('User updated successfully'));
|
|
208
|
+
|
|
209
|
+
// Verify updates
|
|
210
|
+
const user_result = await get_user('user1');
|
|
211
|
+
t.is(user_result.user.email, 'user1_new@test.com');
|
|
212
|
+
t.is(user_result.user.role, 'admin');
|
|
213
|
+
t.is(user_result.user.active, false);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test('update_user validates email format', async (t) => {
|
|
217
|
+
await setup_initial_admin('admin', 'admin123', 'admin@test.com');
|
|
218
|
+
await create_user('user1', 'user123', 'user1@test.com');
|
|
219
|
+
|
|
220
|
+
const result = await update_user('user1', { email: 'invalid-email' });
|
|
221
|
+
|
|
222
|
+
t.is(result.success, false);
|
|
223
|
+
t.truthy(result.error.includes('Invalid email format'));
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test('update_user validates role values', async (t) => {
|
|
227
|
+
await setup_initial_admin('admin', 'admin123', 'admin@test.com');
|
|
228
|
+
await create_user('user1', 'user123', 'user1@test.com');
|
|
229
|
+
|
|
230
|
+
const result = await update_user('user1', { role: 'invalid' });
|
|
231
|
+
|
|
232
|
+
t.is(result.success, false);
|
|
233
|
+
t.truthy(result.error.includes('Role must be either "admin" or "user"'));
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// Password Reset Tests
|
|
237
|
+
test('reset_user_password updates password successfully', async (t) => {
|
|
238
|
+
await setup_initial_admin('admin', 'admin123', 'admin@test.com');
|
|
239
|
+
await create_user('user1', 'user123', 'user1@test.com');
|
|
240
|
+
|
|
241
|
+
const result = await reset_user_password('user1', 'new_password123');
|
|
242
|
+
|
|
243
|
+
t.is(result.success, true);
|
|
244
|
+
t.truthy(result.message.includes('Password reset successfully'));
|
|
245
|
+
|
|
246
|
+
// Verify new password works
|
|
247
|
+
const auth_result = await verify_credentials('user1', 'new_password123', '127.0.0.1');
|
|
248
|
+
t.is(auth_result.success, true);
|
|
249
|
+
|
|
250
|
+
// Verify old password no longer works
|
|
251
|
+
const old_auth_result = await verify_credentials('user1', 'user123', '127.0.0.1');
|
|
252
|
+
t.is(old_auth_result.success, false);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test('reset_user_password validates password strength', async (t) => {
|
|
256
|
+
await setup_initial_admin('admin', 'admin123', 'admin@test.com');
|
|
257
|
+
await create_user('user1', 'user123', 'user1@test.com');
|
|
258
|
+
|
|
259
|
+
const result = await reset_user_password('user1', '123');
|
|
260
|
+
|
|
261
|
+
t.is(result.success, false);
|
|
262
|
+
t.truthy(result.error.includes('Password must be at least 6 characters long'));
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// User Deletion Tests
|
|
266
|
+
test('delete_user removes user successfully', async (t) => {
|
|
267
|
+
await setup_initial_admin('admin', 'admin123', 'admin@test.com');
|
|
268
|
+
await create_user('user1', 'user123', 'user1@test.com');
|
|
269
|
+
|
|
270
|
+
const result = await delete_user('user1');
|
|
271
|
+
|
|
272
|
+
t.is(result.success, true);
|
|
273
|
+
t.truthy(result.message.includes('User deleted successfully'));
|
|
274
|
+
|
|
275
|
+
// Verify user was deleted
|
|
276
|
+
const get_result = await get_user('user1');
|
|
277
|
+
t.is(get_result.success, false);
|
|
278
|
+
t.truthy(get_result.error.includes('User not found'));
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
test('delete_user returns error for non-existent user', async (t) => {
|
|
282
|
+
const result = await delete_user('nonexistent');
|
|
283
|
+
|
|
284
|
+
t.is(result.success, false);
|
|
285
|
+
t.truthy(result.error.includes('User not found'));
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// User Listing Tests
|
|
289
|
+
test('list_users returns all users', async (t) => {
|
|
290
|
+
await setup_initial_admin('admin', 'admin123', 'admin@test.com');
|
|
291
|
+
await create_user('user1', 'user123', 'user1@test.com');
|
|
292
|
+
await create_user('user2', 'user123', 'user2@test.com');
|
|
293
|
+
|
|
294
|
+
const result = await list_users();
|
|
295
|
+
|
|
296
|
+
t.is(result.success, true);
|
|
297
|
+
t.is(result.users.length, 3);
|
|
298
|
+
|
|
299
|
+
const usernames = result.users.map(u => u.username);
|
|
300
|
+
t.true(usernames.includes('admin'));
|
|
301
|
+
t.true(usernames.includes('user1'));
|
|
302
|
+
t.true(usernames.includes('user2'));
|
|
303
|
+
|
|
304
|
+
// Verify no password hashes are returned
|
|
305
|
+
result.users.forEach(user => {
|
|
306
|
+
t.falsy(user.password_hash);
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
test('list_users handles empty user list', async (t) => {
|
|
311
|
+
const result = await list_users();
|
|
312
|
+
|
|
313
|
+
t.is(result.success, true);
|
|
314
|
+
t.is(result.users.length, 0);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// Authentication Tests
|
|
318
|
+
test('verify_credentials authenticates valid user successfully', async (t) => {
|
|
319
|
+
await setup_initial_admin('admin', 'admin123', 'admin@test.com');
|
|
320
|
+
|
|
321
|
+
const result = await verify_credentials('admin', 'admin123', '127.0.0.1');
|
|
322
|
+
|
|
323
|
+
t.is(result.success, true);
|
|
324
|
+
t.is(result.user.username, 'admin');
|
|
325
|
+
t.is(result.user.role, 'admin');
|
|
326
|
+
t.truthy(result.user.last_login);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
test('verify_credentials rejects invalid password', async (t) => {
|
|
330
|
+
await setup_initial_admin('admin', 'admin123', 'admin@test.com');
|
|
331
|
+
|
|
332
|
+
const result = await verify_credentials('admin', 'wrong_password', '127.0.0.1');
|
|
333
|
+
|
|
334
|
+
t.is(result.success, false);
|
|
335
|
+
t.truthy(result.error.includes('Invalid credentials'));
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
test('verify_credentials rejects non-existent user', async (t) => {
|
|
339
|
+
const result = await verify_credentials('nonexistent', 'password', '127.0.0.1');
|
|
340
|
+
|
|
341
|
+
t.is(result.success, false);
|
|
342
|
+
t.truthy(result.error.includes('Invalid credentials'));
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
test('verify_credentials rejects inactive user', async (t) => {
|
|
346
|
+
await setup_initial_admin('admin', 'admin123', 'admin@test.com');
|
|
347
|
+
await create_user('user1', 'user123', 'user1@test.com');
|
|
348
|
+
|
|
349
|
+
// Deactivate user
|
|
350
|
+
await update_user('user1', { active: false });
|
|
351
|
+
|
|
352
|
+
const result = await verify_credentials('user1', 'user123', '127.0.0.1');
|
|
353
|
+
|
|
354
|
+
t.is(result.success, false);
|
|
355
|
+
t.truthy(result.error.includes('Account is disabled'));
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
test('verify_credentials implements rate limiting', async (t) => {
|
|
359
|
+
await setup_initial_admin('admin', 'admin123', 'admin@test.com');
|
|
360
|
+
|
|
361
|
+
const ip = '192.168.1.100';
|
|
362
|
+
|
|
363
|
+
// Make 5 failed attempts
|
|
364
|
+
for (let i = 0; i < 5; i++) {
|
|
365
|
+
await verify_credentials('admin', 'wrong_password', ip);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// 6th attempt should be rate limited
|
|
369
|
+
const result = await verify_credentials('admin', 'wrong_password', ip);
|
|
370
|
+
|
|
371
|
+
t.is(result.success, false);
|
|
372
|
+
t.truthy(result.error.includes('Too many failed attempts'));
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
test('verify_credentials updates last_login on successful authentication', async (t) => {
|
|
376
|
+
await setup_initial_admin('admin', 'admin123', 'admin@test.com');
|
|
377
|
+
|
|
378
|
+
const before_login = Date.now();
|
|
379
|
+
|
|
380
|
+
const result = await verify_credentials('admin', 'admin123', '127.0.0.1');
|
|
381
|
+
|
|
382
|
+
t.is(result.success, true);
|
|
383
|
+
|
|
384
|
+
// Get user to check last_login was updated
|
|
385
|
+
const user_result = await get_user('admin');
|
|
386
|
+
const last_login = new Date(user_result.user.last_login).getTime();
|
|
387
|
+
|
|
388
|
+
t.true(last_login >= before_login);
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
// Authentication Stats Tests
|
|
392
|
+
test('get_auth_stats returns authentication statistics', async (t) => {
|
|
393
|
+
await setup_initial_admin('admin', 'admin123', 'admin@test.com');
|
|
394
|
+
await create_user('user1', 'user123', 'user1@test.com');
|
|
395
|
+
|
|
396
|
+
// Make some authentication attempts
|
|
397
|
+
await verify_credentials('admin', 'admin123', '127.0.0.1');
|
|
398
|
+
await verify_credentials('user1', 'wrong_password', '192.168.1.1');
|
|
399
|
+
|
|
400
|
+
const result = await get_auth_stats();
|
|
401
|
+
|
|
402
|
+
t.is(result.configured, true);
|
|
403
|
+
t.is(result.user_based, true);
|
|
404
|
+
t.is(typeof result.user_count, 'number');
|
|
405
|
+
t.is(typeof result.failed_attempts_count, 'number');
|
|
406
|
+
t.is(typeof result.rate_limited_ips, 'number');
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
// Edge Cases and Error Handling
|
|
410
|
+
test('functions handle storage engine errors gracefully', async (t) => {
|
|
411
|
+
// Mock storage engine that throws errors
|
|
412
|
+
const error_storage = {
|
|
413
|
+
get: () => { throw new Error('Storage error'); },
|
|
414
|
+
put: () => { throw new Error('Storage error'); },
|
|
415
|
+
del: () => { throw new Error('Storage error'); },
|
|
416
|
+
getRange: () => { throw new Error('Storage error'); }
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
set_storage_engine(error_storage);
|
|
420
|
+
|
|
421
|
+
const result = await setup_initial_admin('admin', 'admin123', 'admin@test.com');
|
|
422
|
+
|
|
423
|
+
t.is(result.success, false);
|
|
424
|
+
t.truthy(result.error.includes('Storage error'));
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
test('functions validate input parameters', async (t) => {
|
|
428
|
+
// Test with null/undefined parameters
|
|
429
|
+
let result = await create_user(null, 'password', 'email@test.com');
|
|
430
|
+
t.is(result.success, false);
|
|
431
|
+
|
|
432
|
+
result = await get_user(undefined);
|
|
433
|
+
t.is(result.success, false);
|
|
434
|
+
|
|
435
|
+
result = await update_user('', {});
|
|
436
|
+
t.is(result.success, false);
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
test('password hashing is secure and consistent', async (t) => {
|
|
440
|
+
await setup_initial_admin('admin', 'admin123', 'admin@test.com');
|
|
441
|
+
|
|
442
|
+
// Verify password authentication works (proves hashing works)
|
|
443
|
+
const auth_result1 = await verify_credentials('admin', 'admin123', '127.0.0.1');
|
|
444
|
+
t.is(auth_result1.success, true);
|
|
445
|
+
t.is(auth_result1.user.username, 'admin');
|
|
446
|
+
|
|
447
|
+
// Verify wrong password fails (proves password was hashed, not stored as plaintext)
|
|
448
|
+
const auth_result2 = await verify_credentials('admin', 'wrong_password', '127.0.0.1');
|
|
449
|
+
t.is(auth_result2.success, false);
|
|
450
|
+
|
|
451
|
+
// Create another user with same password to test salt uniqueness
|
|
452
|
+
await create_user('user1', 'admin12345678', 'user1@test.com');
|
|
453
|
+
const auth_result3 = await verify_credentials('user1', 'admin12345678', '127.0.0.1');
|
|
454
|
+
t.is(auth_result3.success, true);
|
|
455
|
+
t.is(auth_result3.user.username, 'user1');
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
test('authentication timing is consistent to prevent timing attacks', async (t) => {
|
|
459
|
+
await setup_initial_admin('admin', 'admin123', 'admin@test.com');
|
|
460
|
+
|
|
461
|
+
// Test authentication with existing vs non-existing user
|
|
462
|
+
const start1 = Date.now();
|
|
463
|
+
await verify_credentials('admin', 'wrong_password', '127.0.0.1');
|
|
464
|
+
const time1 = Date.now() - start1;
|
|
465
|
+
|
|
466
|
+
const start2 = Date.now();
|
|
467
|
+
await verify_credentials('nonexistent', 'wrong_password', '127.0.0.1');
|
|
468
|
+
const time2 = Date.now() - start2;
|
|
469
|
+
|
|
470
|
+
// Times should be roughly similar (within reasonable margin)
|
|
471
|
+
// This is a basic check - in practice, timing attack prevention is more complex
|
|
472
|
+
const time_diff = Math.abs(time1 - time2);
|
|
473
|
+
t.true(time_diff < 1000); // Increased threshold for bcrypt operations
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
// Integration Tests
|
|
477
|
+
test('complete user lifecycle workflow', async (t) => {
|
|
478
|
+
// 1. Setup initial admin
|
|
479
|
+
let result = await setup_initial_admin('admin', 'admin123', 'admin@test.com');
|
|
480
|
+
t.is(result.success, true);
|
|
481
|
+
|
|
482
|
+
// 2. Admin authenticates
|
|
483
|
+
result = await verify_credentials('admin', 'admin123', '127.0.0.1');
|
|
484
|
+
t.is(result.success, true);
|
|
485
|
+
t.is(result.user.role, 'admin');
|
|
486
|
+
|
|
487
|
+
// 3. Admin creates regular user
|
|
488
|
+
result = await create_user('user1', 'user123', 'user1@test.com');
|
|
489
|
+
t.is(result.success, true);
|
|
490
|
+
|
|
491
|
+
// 4. User authenticates
|
|
492
|
+
result = await verify_credentials('user1', 'user123', '127.0.0.1');
|
|
493
|
+
t.is(result.success, true);
|
|
494
|
+
t.is(result.user.role, 'user');
|
|
495
|
+
|
|
496
|
+
// 5. Admin updates user
|
|
497
|
+
result = await update_user('user1', { email: 'user1_updated@test.com' });
|
|
498
|
+
t.is(result.success, true);
|
|
499
|
+
|
|
500
|
+
// 6. Admin resets user password
|
|
501
|
+
result = await reset_user_password('user1', 'new_password123');
|
|
502
|
+
t.is(result.success, true);
|
|
503
|
+
|
|
504
|
+
// 7. User authenticates with new password
|
|
505
|
+
result = await verify_credentials('user1', 'new_password123', '127.0.0.1');
|
|
506
|
+
t.is(result.success, true);
|
|
507
|
+
|
|
508
|
+
// 8. List all users
|
|
509
|
+
result = await list_users();
|
|
510
|
+
t.is(result.success, true);
|
|
511
|
+
t.is(result.users.length, 2);
|
|
512
|
+
|
|
513
|
+
// 9. Get user stats
|
|
514
|
+
result = await get_auth_stats();
|
|
515
|
+
t.is(result.configured, true);
|
|
516
|
+
t.is(result.user_count, 2);
|
|
517
|
+
|
|
518
|
+
// 10. Admin deletes user
|
|
519
|
+
result = await delete_user('user1');
|
|
520
|
+
t.is(result.success, true);
|
|
521
|
+
|
|
522
|
+
// 11. Verify user is deleted
|
|
523
|
+
result = await get_user('user1');
|
|
524
|
+
t.is(result.success, false);
|
|
525
|
+
});
|