1<?php
2
3declare(strict_types=1);
4
5namespace ZnoteX\Tests\Security;
6
7use PHPUnit\Framework\TestCase;
8
9require_once dirname(__DIR__, 2) . '/engine/function/rfc6238.php';
10require_once dirname(__DIR__, 2) . '/engine/function/twofa2.php';
11
12final class TwoFactorV2Test extends TestCase
13{
14 protected function setUp(): void
15 {
16 $GLOBALS['config'] = [
17 'session_prefix' => 'znote_',
18 'twoFactorV2' => [],
19 ];
20 }
21
22 public function testConfigFallsBackToSafeDefaultsWhenNothingIsSet(): void
23 {
24 $cfg = \znote2fa_v2_config();
25
26 $this->assertFalse($cfg['enabled']);
27 $this->assertTrue($cfg['email_otp_enabled']);
28 $this->assertFalse($cfg['force_admins']);
29 $this->assertSame(10, $cfg['recovery_codes_count']);
30 $this->assertSame(30, $cfg['trusted_device_days']);
31 }
32
33 public function testConfigHonoursExplicitValues(): void
34 {
35 $GLOBALS['config']['twoFactorV2'] = [
36 'enabled' => true,
37 'email_otp_enabled' => false,
38 'force_admins' => true,
39 'recovery_codes_count' => 0,
40 'trusted_device_days' => -5,
41 ];
42
43 $cfg = \znote2fa_v2_config();
44
45 $this->assertTrue($cfg['enabled']);
46 $this->assertFalse($cfg['email_otp_enabled']);
47 $this->assertTrue($cfg['force_admins']);
48 $this->assertSame(1, $cfg['recovery_codes_count']);
49 $this->assertSame(0, $cfg['trusted_device_days']);
50 }
51
52 public function testEnabledMirrorsTheConfig(): void
53 {
54 $this->assertFalse(\znote2fa_v2_enabled());
55 $GLOBALS['config']['twoFactorV2']['enabled'] = true;
56 $this->assertTrue(\znote2fa_v2_enabled());
57 }
58
59 public function testAdminEnforcementDoesNotCreateAnImpossibleLoginChallenge(): void
60 {
61 $this->assertFalse(\znote2fa_login_challenge_required(true, ['any_enabled' => false]));
62 $this->assertTrue(\znote2fa_login_challenge_required(true, ['any_enabled' => true]));
63 $this->assertFalse(\znote2fa_login_challenge_required(false, ['any_enabled' => true]));
64 }
65
66 public function testTrustedCookieNameFollowsTheSessionPrefix(): void
67 {
68 $this->assertSame('znote_2fa_trust', \znote2fa_trusted_cookie_name());
69
70 $GLOBALS['config']['session_prefix'] = 'myserver_';
71 $this->assertSame('myserver_2fa_trust', \znote2fa_trusted_cookie_name());
72 }
73
74 public function testRecoveryFormatUppercasesAndStripsSeparators(): void
75 {
76 $this->assertSame('ABCDE12345', \znote2fa_recovery_format('abcde-12345'));
77 $this->assertSame('ABCDE12345', \znote2fa_recovery_format(' abcde 12345 '));
78 }
79
80 public function testRecoveryFormatRejectsPunctuationOnly(): void
81 {
82 $this->assertSame('', \znote2fa_recovery_format('---'));
83 }
84
85 public function testRecoveryDecodeToleratesMissingOrMalformedStorage(): void
86 {
87 $this->assertSame([], \znote2fa_recovery_decode(null));
88 $this->assertSame([], \znote2fa_recovery_decode(''));
89 $this->assertSame([], \znote2fa_recovery_decode('not json'));
90 $this->assertSame(['a', 'b'], \znote2fa_recovery_decode('["a","b"]'));
91 }
92
93 public function testVerifyLoginInputRejectsEmptyInput(): void
94 {
95 $this->assertFalse(\znote2fa_verify_login_input(1, ''));
96 $this->assertFalse(\znote2fa_verify_login_input(1, ' '));
97 }
98}
99